[
  {
    "path": ".clang-format",
    "content": "---\nBasedOnStyle: Mozilla\nAccessModifierOffset: '-4'\nAlignAfterOpenBracket: Align\nAlignConsecutiveMacros:\n  Enabled: true\n  AcrossEmptyLines: false\n  AcrossComments: true\nAlignEscapedNewlines: Left\nAlignOperands: true\nAlignTrailingComments:\n  Kind: Always\n  OverEmptyLines: 1\nAllowAllParametersOfDeclarationOnNextLine: true\nAllowShortBlocksOnASingleLine: Never\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: None\nAllowShortIfStatementsOnASingleLine: Never\nAllowShortLoopsOnASingleLine: false\nBinPackArguments: true\nBinPackParameters: true\nBreakAfterReturnType: AllDefinitions\nBreakBeforeBinaryOperators: NonAssignment\nBreakBeforeBraces: Allman\nBreakBeforeTernaryOperators: true\nBreakStringLiterals: false\nColumnLimit: '0'\nContinuationIndentWidth: '4'\nDerivePointerAlignment: false\nIndentCaseLabels: true\nIndentGotoLabels: false\nIndentWidth: '4'\nIndentWrappedFunctionNames: false\nInsertBraces: true\nKeepEmptyLinesAtTheStartOfBlocks: false\nMaxEmptyLinesToKeep: '2'\nPointerAlignment: Right\nReflowComments: true\nSortIncludes: false\nSpaceAfterCStyleCast: false\nSpaceBeforeAssignmentOperators: true\nSpaceBeforeParens: ControlStatements\nSpacesBeforeTrailingComments: '2'\nSpacesInParens: Never\nTabWidth: '4'\nTypeNames: [DWORD]\nUseTab: Never\nWhitespaceSensitiveMacros: [_STRINGIFY]\n---\nLanguage: C\n---\nLanguage: Cpp\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": "# This FILE allows git blame to ignore reformatting changes and instead\n# shows the previous commit that changed the line.\n#\n# To avoid manually building the list of commits this commit\n# adds a file with a list of reformatting commits. TO use:\n#\n#   git blame --ignore-revs-file=.git-blame-ignore-revs file\n#\n# or to automatically always use the file\n#\n#   git config blame.ignoreRevsFile .git-blame-ignore-revs\n\n# Uncrustify 2020/06... (engine, pool, SSO)\nc1ff8f247f91c88a2df5502eeedf42857f9a6831\n\n# Uncrustify the tests/unit_tests/ part of our tree.\nda1574ef7826d73f01e120cbd1ba40ce39a305b7\n\n# Another round of uncrustify code cleanup.\n9cf7b4925a54d93fbea1cadcf3dc0e11f3ce358f\n\n# networking_sitnl.c: uncrustify file\n2c45d268ca65c522fbabb7c4dab5e721296b4623\n\n# Uncrustify tapctl and openvpnmsica\n6280d3d5536174934ee22d3840457d61896e0e3a\n\n# tun.c: uncrustify\nbaef44fc8769bbd99f4d699ce9f63180c29a5455\n\n# networking_sitnl.c: uncrustify file\n2c45d268ca65c522fbabb7c4dab5e721296b4623\n\n# uncrustify openvpn sources\nf57431cdc88f22fa4d7962946f0d3187fe058539\n\n# More broadly enforce Allman style and braces-around-conditionals\n4cd4899e8e80efae03c584a760fd107251735723\n\n# The Great Reformatting - first phase\n81d882d5302b8b647202a6893b57dfdc61fd6df2\n\n# Fix trailing-whitespace errors in last patch.\n3282632d9325267c850072db7545a884a1637f51\n\n# The Great Reformatting of 2022\nabe49856d81f51136d543539202a0bf8fb946474\n\n# Reformat for sp_after_comma=add\ne51d9a73693ee742b36e19fb1718e5e27167831d\n\n# The Great Reformatting of 2025, switching to clang-format\n3cca3367e6e0ffeccb8e39cb2c739d1dcb086701\n# Switching to ColumnLimit 0 for clang-format\n21f7d6e1ad65b1f7db673bc98764dc7325858e0b\n"
  },
  {
    "path": ".gitattributes",
    "content": "*.c eol=lf\n*.h eol=lf\n*.rc eol=lf\n*.txt eol=lf\n*.bat eol=lf\n*.vc*proj* eol=crlf\n*.sln eol=crlf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**IMPORTANT NOTE**\nBugs about OpenVPN Access Server, OpenVPN Connect or any other product by OpenVPN Inc. should be directly reported to OpenVPN Inc. at https://support.openvpn.net\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior. Please make sure to not post any secrets like keys and passwords.\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Version information (please complete the following information):**\n - OS: [e.g. Ubuntu 22.04]\n - OpenVPN version: [e.g. 2.5.8]\n - Repeat for peer if relevant\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "# Thank you for your contribution\n\nYou are welcome to open PR, but they are used for discussion only. All\npatches must eventually go to the openvpn-devel mailing list for review:\n\n* https://lists.sourceforge.net/lists/listinfo/openvpn-devel\n\nPlease send your patch using [git-send-email](https://git-scm.com/docs/git-send-email). For example to send your latest commit to the list:\n\n    $ git send-email --to=openvpn-devel@lists.sourceforge.net HEAD~1\n\nFor details, see these Wiki articles:\n\n* https://community.openvpn.net/openvpn/wiki/DeveloperDocumentation\n* https://community.openvpn.net/openvpn/wiki/Contributing\n"
  },
  {
    "path": ".github/workflows/build.yaml",
    "content": "# The name of our workflow\nname: Build\non:\n  push:\n  pull_request:\n\njobs:\n  clang-format:\n    name: Check code style with clang-format\n    runs-on: ubuntu-24.04\n    steps:\n      - name: Install dependencies\n        run: |\n          sudo apt update && sudo apt install -y python3-pip\n          pip3 install pre-commit\n      - name: Checkout OpenVPN\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Run clang-format\n        run: pre-commit run -a --show-diff-on-failure || true\n      - name: Check for changes\n        run: git diff --output=format-changes.patch\n      - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0\n        with:\n          name: format-changes.patch\n          path: format-changes.patch\n      - name: Set job status\n        run: test ! -s format-changes.patch\n\n  android:\n    strategy:\n      fail-fast: false\n      matrix:\n        abi: [ arm64-v8a ]\n        include:\n          - abi: arm64-v8a\n            vcpkg_triplet: arm64-android\n    runs-on: ubuntu-24.04\n    name: \"Android - ${{ matrix.abi }}\"\n    # Github images already setup NDK with ANDROID_NDK_ROOT pointing to the root\n    # of the SDK\n    env:\n      VCPKG_DEFAULT_TRIPLET: ${{ matrix.vcpkg_triplet }}\n      VCPKG_ROOT: ${{ github.workspace }}/vcpkg\n      VCPKG_INSTALLED_DIR: ${{ github.workspace }}/vcpkg/installed\n    steps:\n      - name: Checkout OpenVPN\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - uses: lukka/get-cmake@f176ccd3f28bda569c43aae4894f06b2435a3375 # v4.2.3\n      - name: Install vcpkg\n        uses: lukka/run-vcpkg@5e0cab206a5ea620130caf672fce3e4a6b5666a1 # v11.5\n        with:\n          vcpkgGitCommitId: e5a1490e1409d175932ef6014519e9ae149ddb7c\n      - name: Install dependencies\n        run: ${VCPKG_ROOT}/vcpkg install openssl lz4 cmocka\n      - name: configure OpenVPN with cmake\n        run: |\n          cmake -S . -B openvpn-build -DUNSUPPORTED_BUILDS=yes \\\n            -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=28 \\\n            -DCMAKE_ANDROID_ARCH_ABI=${{ matrix.abi }} \\\n            -DOPENSSL_ROOT_DIR=${VCPKG_INSTALLED_DIR}/${{ matrix.vcpkg_triplet }} \\\n            -DENABLE_PKCS11=false -DBUILD_TESTING=true -DENABLE_LZO=false\n      - name: Build OpenVPN Android binary with cmake\n        run: cmake --build openvpn-build\n\n\n  mingw:\n    strategy:\n      fail-fast: false\n      matrix:\n        arch: [x86, x64]\n        build: [Release, Debug]\n\n    name: \"gcc-mingw - ${{ matrix.arch }} - ${{matrix.build }} - OSSL\"\n    runs-on: ubuntu-24.04\n    env:\n      VCPKG_ROOT: ${{ github.workspace }}/vcpkg\n    steps:\n      - name: Install dependencies\n        run: sudo apt update && sudo apt install -y mingw-w64 unzip build-essential wget python3-docutils man2html-base\n      - name: Checkout OpenVPN\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - uses: lukka/get-cmake@f176ccd3f28bda569c43aae4894f06b2435a3375 # v4.2.3\n      - name: Restore from cache and install vcpkg\n        uses: lukka/run-vcpkg@5e0cab206a5ea620130caf672fce3e4a6b5666a1 # v11.5\n        with:\n          vcpkgGitCommitId: e5a1490e1409d175932ef6014519e9ae149ddb7c\n          vcpkgJsonGlob: '**/mingw/vcpkg.json'\n\n      - name: Run CMake with vcpkg.json manifest\n        uses: lukka/run-cmake@af1be47fd7c933593f687731bc6fdbee024d3ff4 # v10.8\n        with:\n          configurePreset: mingw-${{ matrix.arch }}\n          buildPreset: mingw-${{ matrix.arch }}\n          buildPresetAdditionalArgs: \"['--config ${{ matrix.build }}']\"\n\n      - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0\n        with:\n          name: openvpn-mingw-${{ matrix.arch }}-${{ matrix.build }}\n          path: |\n            ${{ github.workspace }}/out/build/mingw/${{ matrix.arch }}/**/${{ matrix.build }}/*.exe\n            ${{ github.workspace }}/out/build/mingw/${{ matrix.arch }}/**/${{ matrix.build }}/*.dll\n            !${{ github.workspace }}/out/build/mingw/${{ matrix.arch }}/**/${{ matrix.build }}/test_*.exe\n\n      - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0\n        with:\n          name: openvpn-mingw-${{ matrix.arch }}-${{ matrix.build }}-tests\n          path: |\n            ${{ github.workspace }}/out/build/mingw/${{ matrix.arch }}/**/${{ matrix.build }}/test_*.exe\n            ${{ github.workspace }}/out/build/mingw/${{ matrix.arch }}/${{ matrix.build }}/*.dll\n\n  mingw-unittest:\n    needs: [ mingw ]\n    strategy:\n      fail-fast: false\n      matrix:\n        arch: [x86, x64]\n        test: [argv, auth_token, buffer, cryptoapi, crypto, misc, options_parse, ncp, openvpnserv, packet_id, pkt, provider, ssl, tls_crypt, user_pass]\n        build: [Release, Debug]\n\n    runs-on: windows-2025\n    name: \"mingw unittest ${{ matrix.test }} - ${{ matrix.arch }} - ${{ matrix.build }} - OSSL\"\n    steps:\n      - name: Checkout OpenVPN\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Retrieve mingw unittest\n        uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0\n        with:\n          name: openvpn-mingw-${{ matrix.arch }}-${{ matrix.build }}-tests\n          path: unittests\n      - name: Run ${{ matrix.test }} unit test\n        run: |\n          $test_file=(Get-ChildItem -Path unittests -Filter test_${{ matrix.test }}.exe -Recurse).fullname\n          & $test_file\n        env:\n          srcdir: \"${{ github.workspace }}/tests/unit_tests/openvpn\"\n\n  ubuntu:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-22.04, ubuntu-24.04]\n        sslpkg: [libssl-dev]\n        ssllib: [openssl]\n\n        include:\n          - os: ubuntu-22.04\n            libname: OpenSSL 3.0.2\n            pkcs11pkg: \"libpkcs11-helper1-dev softhsm2 gnutls-bin\"\n            extraconf: --enable-pkcs11\n          - os: ubuntu-24.04\n            libname: OpenSSL 3.0.13\n            pkcs11pkg: \"libpkcs11-helper1-dev softhsm2 gnutls-bin\"\n            extraconf: --enable-pkcs11\n\n    name: \"gcc - ${{matrix.os}} - ${{matrix.libname}} ${{matrix.extraconf}}\"\n    env:\n      SSLPKG: \"${{matrix.sslpkg}}\"\n      PKCS11PKG: \"${{matrix.pkcs11pkg}}\"\n\n    runs-on: ${{matrix.os}}\n    steps:\n      - name: Install dependencies\n        run: sudo apt update && sudo apt install -y liblzo2-dev libpam0g-dev liblz4-dev libcap-ng-dev libnl-genl-3-dev linux-libc-dev man2html libcmocka-dev python3-docutils libtool automake autoconf ${SSLPKG} ${PKCS11PKG}\n      - name: Checkout OpenVPN\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: autoconf\n        run: autoreconf -fvi\n      - name: configure\n        run: ./configure --with-crypto-library=${{matrix.ssllib}} ${{matrix.extraconf}} --enable-werror\n      - name: make all\n        run: make -j3\n      - name: configure checks\n        if: ${{ matrix.extraconf != '--disable-management' }}\n        run: echo 'RUN_SUDO=\"sudo -E\"' >tests/t_server_null.rc\n      - name: make check\n        run: make -j3 check VERBOSE=1\n\n  ubuntu-clang-asan:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-22.04, ubuntu-24.04]\n        ssllib: [openssl]\n\n    name: \"clang-asan - ${{matrix.os}} - ${{matrix.ssllib}}\"\n\n    env:\n      UBSAN_OPTIONS: print_stacktrace=1\n\n    runs-on: ${{matrix.os}}\n    steps:\n      - name: Install dependencies\n        run: sudo apt update && sudo apt install -y liblzo2-dev libpam0g-dev liblz4-dev libcap-ng-dev libnl-genl-3-dev linux-libc-dev man2html clang libcmocka-dev python3-docutils libtool automake autoconf\n      - name: Checkout OpenVPN\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: autoconf\n        run: autoreconf -fvi\n      - name: configure\n        run: CFLAGS=\"-fsanitize=address,undefined -fno-sanitize-recover=all  -fno-omit-frame-pointer -O2\" CC=clang ./configure --with-crypto-library=${{matrix.ssllib}} --enable-werror\n      - name: make all\n        run: make -j3\n      - name: configure checks\n        run: echo 'RUN_SUDO=\"sudo -E\"' >tests/t_server_null.rc\n      - name: make check\n        run: make -j3 check VERBOSE=1\n\n  macos:\n    strategy:\n      fail-fast: false\n      matrix:\n        ssllib: [openssl@3, libressl]\n        build: [normal, asan]\n        os: [macos-14, macos-15, macos-26]\n        include:\n          - build: asan\n            cflags: \"-fsanitize=address,undefined -fno-sanitize-recover=all  -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1\"\n            ldflags: -fsanitize=address,undefined -fno-sanitize-recover=all\n            # Our build system ignores LDFLAGS for plugins\n            configureflags: --disable-plugin-auth-pam  --disable-plugin-down-root\n          - build: normal\n            cflags: \"-O2 -g\"\n            ldflags: \"\"\n            configureflags: \"\"\n\n    runs-on: ${{matrix.os}}\n    name: \"${{matrix.os}} - ${{matrix.ssllib}} - ${{matrix.build}}\"\n    env:\n      CFLAGS: ${{ matrix.cflags }}\n      LDFLAGS: ${{ matrix.ldflags }}\n      UBSAN_OPTIONS: print_stacktrace=1\n    steps:\n      - name: Install dependencies\n        run: brew install ${{matrix.ssllib}} lzo lz4 man2html cmocka libtool automake autoconf\n      - name: Checkout OpenVPN\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Set environment\n        run: |\n          cat >>$GITHUB_ENV <<EOF;\n          OPENSSL_CFLAGS=-I$(brew --prefix ${{matrix.ssllib}})/include\n          OPENSSL_LIBS=-L$(brew --prefix ${{matrix.ssllib}})/lib -lcrypto -lssl\n          LZO_CFLAGS=-I$(brew --prefix lzo)/include\n          LZO_LIBS=-L$(brew --prefix lzo)/lib -llzo2\n          EOF\n      - name: autoconf\n        run: autoreconf -fvi\n      - name: configure\n        run: ./configure --enable-werror ${{matrix.configureflags}}\n      - name: make all\n        run: make -j4\n      - name: configure checks\n        run: echo 'RUN_SUDO=\"sudo -E\"' >tests/t_server_null.rc\n      - name: make check\n        run: make -j4 check VERBOSE=1\n\n  msvc:\n      strategy:\n        fail-fast: false\n        matrix:\n          arch: [amd64, x86, arm64, amd64-clang, x86-clang]\n\n      name: \"msbuild - ${{ matrix.arch }} - openssl\"\n      env:\n        BUILD_CONFIGURATION: Release\n\n      runs-on: windows-2025\n      steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - uses: lukka/get-cmake@f176ccd3f28bda569c43aae4894f06b2435a3375 # v4.2.3\n\n      - name: Install rst2html\n        run: python -m pip install --upgrade pip docutils\n\n      - name: Restore artifacts, or setup vcpkg (do not install any package)\n        uses: lukka/run-vcpkg@5e0cab206a5ea620130caf672fce3e4a6b5666a1 # v11.5\n        with:\n          vcpkgGitCommitId: e5a1490e1409d175932ef6014519e9ae149ddb7c\n          vcpkgJsonGlob: '**/windows/vcpkg.json'\n\n      - name: Run CMake with vcpkg.json manifest (NO TESTS)\n        uses: lukka/run-cmake@af1be47fd7c933593f687731bc6fdbee024d3ff4 # v10.8\n        if: ${{ matrix.arch == 'arm64' }}\n        with:\n          configurePreset: win-${{ matrix.arch }}-release\n          buildPreset: win-${{ matrix.arch }}-release\n\n      - name: Run CMake with vcpkg.json manifest\n        uses: lukka/run-cmake@af1be47fd7c933593f687731bc6fdbee024d3ff4 # v10.8\n        if: ${{ matrix.arch != 'arm64' }}\n        with:\n          configurePreset: win-${{ matrix.arch }}-release\n          buildPreset: win-${{ matrix.arch }}-release\n          testPreset: win-${{ matrix.arch }}-release\n          testPresetAdditionalArgs: \"['--output-on-failure']\"\n\n      - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0\n        with:\n          name: openvpn-msvc-${{ matrix.arch }}\n          path: |\n            ${{ github.workspace }}/out/**/*.exe\n            ${{ github.workspace }}/out/**/*.dll\n            !${{ github.workspace }}/out/**/test_*.exe\n            !${{ github.workspace }}/out/**/CMakeFiles/**\n            !${{ github.workspace }}/out/**/vcpkg_installed/**\n\n  libressl:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-24.04]\n        ssllib: [libressl]\n        build: [ normal, asan ]\n        configureflags: [\"--with-openssl-engine=no\"]\n        include:\n          - build: asan\n            cflags: \"-fsanitize=address -fno-sanitize-recover=all  -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1\"\n            ldflags: -fsanitize=address -fno-sanitize-recover=all\n            cc: clang\n          - build: normal\n            cflags: \"-O2 -g\"\n            ldflags: \"\"\n            cc: gcc\n\n    name: \"${{matrix.cc}} ${{matrix.build}} - ${{matrix.os}} - ${{matrix.ssllib}}\"\n    runs-on: ${{matrix.os}}\n    env:\n      CFLAGS: ${{ matrix.cflags }}\n      LDFLAGS: ${{ matrix.ldflags }}\n      CC: ${{matrix.cc}}\n      UBSAN_OPTIONS: print_stacktrace=1\n\n    steps:\n      - name: Install dependencies\n        run: sudo apt update && sudo apt install -y liblzo2-dev libpam0g-dev liblz4-dev linux-libc-dev man2html clang libcmocka-dev python3-docutils libtool automake autoconf pkg-config libcap-ng-dev libnl-genl-3-dev\n      - name: \"libressl: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          path: libressl\n          # versioning=semver-coerced\n          repository: libressl/portable\n          ref: v4.2.1\n      - name: \"libressl: autogen.sh\"\n        env:\n          LIBRESSL_GIT_OPTIONS: \"--no-single-branch\"\n        run: ./autogen.sh\n        working-directory: libressl\n      - name: \"libressl: configure\"\n        run: ./configure\n        working-directory: libressl\n      - name: \"libressl: make all\"\n        run: make -j3\n        working-directory: libressl\n      - name: \"libressl: make install\"\n        run: sudo make install\n        working-directory: libressl\n      - name: \"ldconfig\"\n        run: sudo ldconfig\n      - name: Checkout OpenVPN\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: autoconf\n        run: autoreconf -fvi\n      - name: configure\n        run: ./configure --with-crypto-library=openssl ${{matrix.configureflags}} --enable-werror\n      - name: make all\n        run: make -j3\n      - name: Ensure the build uses LibreSSL\n        run: |\n          ./src/openvpn/openvpn --version\n          ./src/openvpn/openvpn --version | grep -q \"library versions: LibreSSL\"\n      - name: configure checks\n        run: echo 'RUN_SUDO=\"sudo -E\"' >tests/t_server_null.rc\n      - name: make check\n        run: make -j3 check VERBOSE=1\n\n  mbedtls4:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-24.04]\n        ssllib: [mbedtls4]\n        build: [ normal, asan ]\n        include:\n          - build: asan\n            cflags: \"-fsanitize=address -fno-sanitize-recover=all  -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1\"\n            ldflags: -fsanitize=address -fno-sanitize-recover=all\n            cc: clang\n          - build: normal\n            cflags: \"-O2 -g\"\n            ldflags: \"\"\n            cc: gcc\n\n    name: \"${{matrix.cc}} ${{matrix.build}} - ${{matrix.os}} - ${{matrix.ssllib}}\"\n    runs-on: ${{matrix.os}}\n    env:\n      CFLAGS: ${{ matrix.cflags }}\n      LDFLAGS: ${{ matrix.ldflags }}\n      CC: ${{matrix.cc}}\n      UBSAN_OPTIONS: print_stacktrace=1\n\n    steps:\n      - name: Install dependencies\n        run: sudo apt update && sudo apt install -y liblzo2-dev libpam0g-dev liblz4-dev linux-libc-dev man2html clang libcmocka-dev python3-docutils python3-jinja2 python3-jsonschema libtool automake autoconf pkg-config libcap-ng-dev libnl-genl-3-dev\n      - name: \"mbedtls: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          path: mbedtls\n          submodules: recursive\n          # versioning=semver-coerced\n          repository: Mbed-TLS/mbedtls\n          ref: v4.0.0\n      - uses: lukka/get-cmake@f176ccd3f28bda569c43aae4894f06b2435a3375 # v4.2.3\n      - name: \"mbedtls: cmake\"\n        run: cmake -B build\n        working-directory: mbedtls\n      - name: \"mbedtls: cmake --build\"\n        run: cmake --build build\n        working-directory: mbedtls\n      - name: \"mbedtls: cmake --install\"\n        run: sudo cmake --install build --prefix /usr\n        working-directory: mbedtls\n      - name: Checkout OpenVPN\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: autoconf\n        run: autoreconf -fvi\n      - name: configure\n        run: ./configure --with-crypto-library=mbedtls --enable-werror\n      - name: make all\n        run: make -j3\n      - name: Ensure the build uses mbed TLS 4.x\n        run: |\n          ./src/openvpn/openvpn --version\n          ./src/openvpn/openvpn --version | grep -q \"library versions: mbed TLS 4.\"\n      - name: configure checks\n        run: echo 'RUN_SUDO=\"sudo -E\"' >tests/t_server_null.rc\n      - name: make check\n        run: make -j3 check VERBOSE=1\n\n  aws-lc:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-24.04]\n        ssllib: [ awslc ]\n        build: [ normal, asan ]\n        include:\n          - build: asan\n            cflags: \"-fsanitize=address -fno-sanitize-recover=all  -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1\"\n            ldflags: -fsanitize=address -fno-sanitize-recover=all\n            cc: clang\n            cxx: clang++\n          - build: normal\n            cflags: \"-O2 -g\"\n            ldflags: \"\"\n            cc: gcc\n            cxx: c++\n\n    name: \"${{matrix.cc}} ${{matrix.build}} - ${{matrix.os}} - ${{matrix.ssllib}}\"\n    runs-on: ${{matrix.os}}\n    env:\n      CFLAGS: ${{ matrix.cflags }}\n      LDFLAGS: ${{ matrix.ldflags }}\n      CC: ${{matrix.cc}}\n      CXX: ${{matrix.cxx}}\n      UBSAN_OPTIONS: print_stacktrace=1\n      AWS_LC_INSTALL: /opt/aws-lc\n\n    steps:\n      - name: Install dependencies\n        run: sudo apt update && sudo apt install -y gcc golang make liblzo2-dev libpam0g-dev liblz4-dev linux-libc-dev man2html clang libcmocka-dev python3-docutils python3-jinja2 python3-jsonschema libtool automake autoconf pkg-config libcap-ng-dev libnl-genl-3-dev\n      - name: \"AWS-LC: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          path: aws-lc\n          # versioning=semver-coerced\n          repository: aws/aws-lc\n          ref: v1.70.0\n      - uses: lukka/get-cmake@f176ccd3f28bda569c43aae4894f06b2435a3375 # v4.2.3\n      - name: \"AWS-LC: build\"\n        run: |\n          mkdir build\n          cd build\n          cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=\"${{ env.AWS_LC_INSTALL }}\" -DBUILD_SHARED_LIBS=1 ../\n          ninja install\n        working-directory: aws-lc\n      - name: Checkout OpenVPN\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: autoconf\n        run: autoreconf -fvi\n      - name: configure with AWS-LC\n        run: |\n          OPENSSL_CFLAGS=\"-I${{ env.AWS_LC_INSTALL }}/include\" \\\n          OPENSSL_LIBS=\"-L${{ env.AWS_LC_INSTALL }}/lib -lssl -lcrypto\" \\\n          LDFLAGS=\"-Wl,-rpath=${{ env.AWS_LC_INSTALL }}/lib\" \\\n          ./configure --with-crypto-library=openssl\n      - name: make all\n        run: make -j3\n      - name: Ensure the build uses AWS-LC\n        run: |\n          ./src/openvpn/openvpn --version\n          ./src/openvpn/openvpn --version | grep -q \"library versions: AWS-LC\"\n      - name: configure checks\n        run: echo 'RUN_SUDO=\"sudo -E\"' >tests/t_server_null.rc\n      - name: make check\n        run: make -j3 check VERBOSE=1\n"
  },
  {
    "path": ".github/workflows/coverity-scan.yml",
    "content": "name: coverity-scan\non:\n  schedule:\n    - cron: '0 20 * * *' # Daily at 20:00 UTC\n  workflow_dispatch:\n\njobs:\n  latest:\n    # Running coverity requires the secrets.COVERITY_SCAN_TOKEN token\n    # which is only available on the main repository\n    if: github.repository_owner == 'OpenVPN'\n    runs-on: ubuntu-24.04\n    steps:\n      - name: Check submission cache\n        id: check_submit\n        uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3\n        with:\n          path: |\n            cov-int\n          key: check-submit-${{ github.sha }}\n\n      - name: Install dependencies\n        if: steps.check_submit.outputs.cache-hit != 'true'\n        run: sudo apt update && sudo apt install -y liblzo2-dev libpam0g-dev liblz4-dev libcap-ng-dev libnl-genl-3-dev linux-libc-dev man2html libcmocka-dev python3-docutils libtool automake autoconf libssl-dev libpkcs11-helper1-dev softhsm2 gnutls-bin\n\n      - name: Checkout OpenVPN\n        if: steps.check_submit.outputs.cache-hit != 'true'\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Download Coverity Build Tool\n        if: steps.check_submit.outputs.cache-hit != 'true'\n        run: |\n          wget -q https://scan.coverity.com/download/cxx/linux64 --post-data \"token=$TOKEN&project=OpenVPN%2Fopenvpn\" -O cov-analysis-linux64.tar.gz\n          mkdir cov-analysis-linux64\n          tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64\n        env:\n          TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}\n\n      - name: autoconf\n        if: steps.check_submit.outputs.cache-hit != 'true'\n        run: autoreconf -fvi\n      - name: configure\n        if: steps.check_submit.outputs.cache-hit != 'true'\n        run: ./configure --enable-pkcs11\n\n      - name: Build with cov-build\n        if: steps.check_submit.outputs.cache-hit != 'true'\n        run: |\n          PATH=`pwd`/cov-analysis-linux64/bin:$PATH\n          cov-build --dir cov-int make\n\n      - name: Submit the result to Coverity Scan\n        if: steps.check_submit.outputs.cache-hit != 'true'\n        run: |\n          tar czvf openvpn.tgz cov-int\n          curl --form token=$TOKEN \\\n          --form email=$EMAIL \\\n          --form file=@openvpn.tgz \\\n          --form version=\"$GITHUB_SHA\" \\\n          --form description=\"master\" \\\n          https://scan.coverity.com/builds?project=OpenVPN%2Fopenvpn\n        env:\n          TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}\n          EMAIL: ${{ secrets.COVERITY_SCAN_EMAIL }}\n\n      - name: Cache submission\n        if: steps.check_submit.outputs.cache-hit != 'true'\n        uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3\n        with:\n          path: |\n            cov-int\n          key: ${{ steps.check_submit.outputs.cache-primary-key }}\n"
  },
  {
    "path": ".github/workflows/doxygen.yml",
    "content": "name: Deploy Doxygen documentation to Pages\non:\n  push:\n    branches: [\"master\"]\n  workflow_dispatch:\nconcurrency:\n  group: \"pages\"\n  cancel-in-progress: false\njobs:\n  build:\n    runs-on: ubuntu-24.04\n    if: ${{ github.repository_owner == 'openvpn' || github.event_name == 'workflow_dispatch' }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          path: openvpn\n\n      - name: Install dependencies\n        run: |\n          sudo apt update\n          sudo apt install -y --no-install-recommends \\\n            build-essential doxygen graphviz \\\n            liblzo2-dev libpam0g-dev liblz4-dev libcap-ng-dev libnl-genl-3-dev linux-libc-dev man2html libcmocka-dev python3-docutils libtool automake autoconf libssl-dev\n\n      - name: Build Doxygen documentation\n        id: build\n        run: |\n          cd openvpn\n          autoreconf -f -i\n          cd ..\n          mkdir doxygen\n          cd doxygen\n          ../openvpn/configure\n          make doxygen\n          touch doc/doxygen/html/.nojekyll\n      - name: Upload static files as artifact\n        id: deployment\n        uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0\n        with:\n          path: doxygen/doc/doxygen/html/\n\n  deploy:\n    needs: build\n    permissions:\n      pages: write      # to deploy to Pages\n      id-token: write   # to verify the deployment originates from an appropriate source\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    runs-on: ubuntu-24.04\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5\n"
  },
  {
    "path": ".gitignore",
    "content": "*.[oa]\n*.l[oa]\n*.dll\n*.exe\n*.exe.*\n*.obj\n*.pyc\n*.so\n*~\n*.idb\n*.suo\n*.ncb\n*.log\nout\n.vs\n.deps\n.libs\nMakefile\nMakefile.in\naclocal.m4\nautodefs.h\nautom4te.cache\nconfig.guess\nconfig.h\nconfig.h.in\nconfig.log\nconfig.status\nconfig.sub\nconfigure\nconfigure.h\ndepcomp\nstamp-h1\ninstall-sh\nmissing\nltmain.sh\nlibtool\nm4/libtool.m4\nm4/ltoptions.m4\nm4/ltsugar.m4\nm4/ltversion.m4\nm4/lt~obsolete.m4\n\nbuild\ndoc/openvpn-examples.5\ndoc/openvpn-examples.5.html\ndoc/openvpn.8\ndoc/openvpn.8.html\n/doc/doxygen/html/\n/doc/doxygen/latex/\n/doc/doxygen/openvpn.doxyfile\ndistro/systemd/*.service\ndistro/dns-scripts/dns-updown\nsample/sample-keys/sample-ca/\nvendor/cmocka_build\nvendor/dist\n\ntests/t_client.sh\ntests/t_client-*-20??????-??????/\ntests/t_server_null.rc\nt_client.rc\nt_client_ips.rc\ntests/unit_tests/**/*_testdriver\n\nsrc/openvpn/openvpn\ninclude/openvpn-plugin.h\nconfig-version.h\nnbproject\ntest-driver\ncompile\nstamp-h2\n"
  },
  {
    "path": ".mailmap",
    "content": "Adriaan de Jong <dejong@fox-it.com>             <adriaan@adriaan-VirtualBox.(none)>\nDavid Sommerseth <dazo@eurephia.net>            <dazo@users.sourceforge.net>\nGert Doering <gert@greenie.muc.de>              <gd@medat.de>\nGert Doering <gert@greenie.muc.de>              <gert@fbsd74.ov.greenie.net>\nGert Doering <gert@greenie.muc.de>              <gert@fbsd90.ov.greenie.net>\nGert Doering <gert@greenie.muc.de>              <gert@mobile.greenie.muc.de>\nJames Yonan <james@openvpn.net>                 <james@e7ae566f-a301-0410-adde-c780ea21d3b5>\nJan Just Keijser <janjust@nikhef.nl>            <janjust@nikhef.nl>\nJuanJo Ciarlante <jjo@google.com>               <jjo+ml@google.com>\nKarl O. Pinc <kop@meme.com>                     <kop@mofo.meme.com>\nRobert Fischer <ml-openvpn@trispace.org>        <ml-openvpn@trispace.org>\nSamuli Seppänen <samuli@openvpn.net>            <samuli@openvpn.net>\nSeth Mos <seth.mos@dds.nl>                      <seth.mos@dds.nl>\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/pre-commit/mirrors-clang-format\n    rev: 'v21.1.8'\n    hooks:\n      - id: clang-format\n        files: \\.[ch]$\n        # preserve upstream formatting\n        exclude: ^(src/compat/compat-lz4\\.[ch]|src/openvpn/ovpn_dco_(linux|win)\\.h)$\n"
  },
  {
    "path": ".svncommitters",
    "content": "james = James Yonan <james@openvpn.net>\n"
  },
  {
    "path": "AUTHORS",
    "content": "James Yonan <jim@yonan.net>\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.14)\nset(CMAKE_CONFIGURATION_TYPES \"Release;Debug;ASAN\")\nproject(openvpn)\n\n# This CMake file implements building OpenVPN with CMAKE\n#\n# Note that this is *NOT* the official way to build openvpn on anything\n# other than Windows/mingw despite working on other platforms too. You will need\n# to add -DUNSUPPORTED_BUILDS=true to build on non Windows platforms.\n#\n# This cmake also makes a few assertions like lzo, lz4 being used\n# and OpenSSL having version 1.1.1+ and generally does not offer the same\n# configurability like autoconf\n\nfind_package(PkgConfig REQUIRED)\ninclude(CheckSymbolExists)\ninclude(CheckIncludeFiles)\ninclude(CheckCCompilerFlag)\ninclude(CheckLinkerFlag OPTIONAL)\ninclude(CheckTypeSize)\ninclude(CheckStructHasMember)\ninclude(CTest)\n\noption(UNSUPPORTED_BUILDS \"Allow unsupported builds\" OFF)\n\nif (NOT WIN32 AND NOT ${UNSUPPORTED_BUILDS})\n    message(FATAL_ERROR \"Note: on Unix platform the official and supported build method is using autoconfig. CMake based build should be only used for Windows and internal testing/development.\")\nendif()\n\nif (EXISTS \"${CMAKE_CURRENT_SOURCE_DIR}/config.h\")\n    message(FATAL_ERROR \"The top level source directory has a config.h file. Note that you can't mix in-tree autoconfig builds with out-of-tree cmake builds.\")\nendif ()\n\noption(MBED \"BUILD with mbed\" OFF)\nset(MBED_INCLUDE_PATH \"\" CACHE STRING \"Path to mbed TLS include directory\")\nset(MBED_LIBRARY_PATH \"\" CACHE STRING \"Path to mbed library directory\")\noption(WOLFSSL \"BUILD with wolfSSL\" OFF)\noption(ENABLE_LZ4 \"BUILD with lz4\" ON)\noption(ENABLE_LZO \"BUILD with lzo\" ON)\noption(ENABLE_PKCS11 \"BUILD with pkcs11-helper\" ON)\noption(USE_WERROR \"Treat compiler warnings as errors (-Werror)\" ON)\noption(FAKE_ANDROID \"Target Android but do not use actual cross compile/Android cmake to build for simple compile checks on Linux\")\n\noption(ENABLE_DNS_UPDOWN_BY_DEFAULT \"Run --dns-updown hook by default\" ON)\nset(DNS_UPDOWN_PATH \"${CMAKE_INSTALL_PREFIX}/libexec/openvpn/dns-updown\" CACHE STRING \"Default location for the DNS up/down script\")\n\nset(PLUGIN_DIR \"${CMAKE_INSTALL_PREFIX}/lib/openvpn/plugins\" CACHE FILEPATH \"Location of the plugin directory\")\n\n# Create machine readable compile commands\noption(ENABLE_COMPILE_COMMANDS \"Generate compile_commands.json and a symlink for clangd to find it\" OFF)\nif (ENABLE_COMPILE_COMMANDS)\n    if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build AND NOT IS_SYMLINK ${CMAKE_CURRENT_SOURCE_DIR}/build)\n        message(FATAL_ERROR \"The top level source directory contains a 'build' file or directory. Please remove or rename it. CMake creates a symlink with that name during build.\")\n    endif()\n    set(CMAKE_EXPORT_COMPILE_COMMANDS 1)\n    add_custom_target(\n        symlink-build-dir ALL\n        ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/build\n        )\nendif ()\n\n# AddressSanitize - use CXX=clang++ CC=clang cmake -DCMAKE_BUILD_TYPE=asan to build with ASAN\nset(CMAKE_C_FLAGS_ASAN\n    \"-fsanitize=address,undefined -fno-sanitize-recover=all -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1\"\n    CACHE STRING \"Flags used by the C compiler during AddressSanitizer builds.\"\n    FORCE)\nset(CMAKE_CXX_FLAGS_ASAN\n    \"-fsanitize=address,undefined -fno-sanitize-recover=all -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1\"\n    CACHE STRING \"Flags used by the C++ compiler during AddressSanitizer builds.\"\n    FORCE)\n\nfunction(check_and_add_compiler_flag flag variable)\n    check_c_compiler_flag(${flag} ${variable})\n    if (${variable})\n        add_compile_options(${flag})\n    endif()\nendfunction()\n\nif (MSVC)\n    add_compile_definitions(\n        _CRT_SECURE_NO_WARNINGS\n        _CRT_NONSTDC_NO_DEPRECATE\n        _WINSOCK_DEPRECATED_NO_WARNINGS\n        )\n    if (USE_WERROR)\n        add_compile_options(/WX)\n    endif ()\n    # C4018: signed/unsigned mismatch\n    # C4244: conversion from 'type1' to 'type2', possible loss of data\n    # C4267: conversion from 'size_t' to 'type', possible loss of data\n    add_compile_options(\n        /MP\n        /W3 /wd4018 /wd4267 /wd4244\n        /sdl\n        /Qspectre\n        /guard:cf\n        /FC\n        /ZH:SHA_256\n        \"$<$<CONFIG:Release>:/GL>\"\n        \"$<$<CONFIG:Release>:/Oi>\"\n        \"$<$<CONFIG:Release>:/Gy>\"\n        \"$<$<CONFIG:Release>:/Zi>\"\n        )\n    add_link_options(\n        /Brepro\n        \"$<$<CONFIG:Release>:/LTCG:incremental>\"\n        \"$<$<CONFIG:Release>:/DEBUG:FULL>\"\n        \"$<$<CONFIG:Release>:/OPT:REF>\"\n        \"$<$<CONFIG:Release>:/OPT:ICF>\"\n        )\n    if (${CMAKE_GENERATOR_PLATFORM} STREQUAL \"x64\" OR ${CMAKE_GENERATOR_PLATFORM} STREQUAL \"x86\")\n        add_link_options(\"$<$<CONFIG:Release>:/CETCOMPAT>\")\n    endif()\nelse ()\n    add_compile_options(-Wall -Wuninitialized)\n    check_and_add_compiler_flag(-Wno-stringop-truncation NoStringOpTruncation)\n    check_and_add_compiler_flag(-Wstrict-prototypes StrictPrototypes)\n    check_and_add_compiler_flag(-Wold-style-definition OldStyleDefinition)\n    add_compile_options(-Wconversion -Wno-sign-conversion)\n    add_compile_options(-Wextra -Wno-unused-parameter)\n    # clang doesn't have the different levels but also doesn't include it in -Wextra\n    check_and_add_compiler_flag(-Wimplicit-fallthrough=2 GCCImplicitFallthrough)\n    if (WIN32)\n        # Not sure how to deal with GetProcAddress\n        add_compile_options(-Wno-cast-function-type)\n    endif ()\n    if (USE_WERROR)\n        add_compile_options(-Werror)\n    endif ()\nendif ()\n\nfind_package(Python3 REQUIRED COMPONENTS Interpreter)\nexecute_process(\n    COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/contrib/cmake/parse-version.m4.py ${CMAKE_CURRENT_SOURCE_DIR}/version.m4\n    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}\n    )\ninclude(${CMAKE_CURRENT_BINARY_DIR}/version.cmake)\n\nset(OPENVPN_VERSION_MAJOR ${PRODUCT_VERSION_MAJOR})\nset(OPENVPN_VERSION_MINOR ${PRODUCT_VERSION_MINOR})\nset(OPENVPN_VERSION_PATCH ${PRODUCT_VERSION_PATCH})\nset(OPENVPN_VERSION_RESOURCE ${PRODUCT_VERSION_RESOURCE})\n\nset(CMAKE_C_STANDARD 11)\n\n# Set the various defines for config.h.cmake.in\nif (${CMAKE_SYSTEM_NAME} STREQUAL \"Android\" OR ${FAKE_ANDROID})\n    set(TARGET_ANDROID YES)\n    set(ENABLE_ASYNC_PUSH YES)\n    set(ENABLE_SITNL YES)\n    # Wacky workaround as OpenSSL package detection is otherwise broken (https://stackoverflow.com/questions/45958214/android-cmake-could-not-find-openssl)\n    list(APPEND CMAKE_FIND_ROOT_PATH ${OPENSSL_ROOT_DIR})\nelseif (${CMAKE_SYSTEM_NAME} STREQUAL \"Linux\")\n    set(TARGET_LINUX YES)\n    set(ENABLE_ASYNC_PUSH YES)\n    set(ENABLE_LINUXDCO YES)\n    set(ENABLE_SITNL YES)\n    set(ENABLE_DCO YES)\nelseif (${CMAKE_SYSTEM_NAME} STREQUAL \"FreeBSD\")\n    set(TARGET_FREEBSD YES)\n    set(ENABLE_DCO YES)\n    link_libraries(-lnv)\nelseif (${CMAKE_SYSTEM_NAME} STREQUAL \"OpenBSD\")\n    set(TARGET_OPENBSD YES)\nelseif (${CMAKE_SYSTEM_NAME} STREQUAL \"SunOS\")\n    set(TARGET_SOLARIS YES)\n    set(HAVE_SYS_SOCKIO_H 1)\n    link_libraries(-lnsl -lsocket -lresolv)\nelseif (WIN32)\n    set(ENABLE_DCO YES)\nelseif (APPLE)\n    set(TARGET_DARWIN YES)\nelse()\n    message(FATAL_ERROR \"Unknown system name: \\\"${CMAKE_SYSTEM_NAME}\\\"\")\nendif ()\n\nif (UNIX)\n    set(PATH_SEPARATOR /)\n    set(ENABLE_PORT_SHARE YES)\n    set(HAVE_SA_FAMILY_T YES)\nelseif (WIN32)\n    set(PATH_SEPARATOR \\\\\\\\)\n    set(TARGET_WIN32 YES)\nendif ()\n\ncheck_include_files(unistd.h HAVE_UNISTD_H)\nif (HAVE_UNISTD_H)\n    check_symbol_exists(chroot unistd.h HAVE_CHROOT)\n    check_symbol_exists(chdir unistd.h HAVE_CHDIR)\n    check_symbol_exists(dup unistd.h HAVE_DUP)\n    check_symbol_exists(dup2 unistd.h HAVE_DUP2)\n    check_symbol_exists(fork unistd.h HAVE_FORK)\n    check_symbol_exists(execve unistd.h HAVE_EXECVE)\n    check_symbol_exists(ftruncate unistd.h HAVE_FTRUNCATE)\n    check_symbol_exists(nice unistd.h HAVE_NICE)\n    check_symbol_exists(setgid unistd.h HAVE_SETGID)\n    check_symbol_exists(setuid unistd.h HAVE_SETUID)\n    check_symbol_exists(setsid unistd.h HAVE_SETSID)\n    check_symbol_exists(daemon \"unistd.h;stdlib.h\" HAVE_DAEMON)\n    check_symbol_exists(getpeereid \"unistd.h;sys/socket.h\" HAVE_GETPEEREID)\nendif()\n\ncheck_include_files(grp.h HAVE_GRP_H)\nif (HAVE_GRP_H)\n    check_symbol_exists(getgrnam grp.h HAVE_GETGRNAM)\nendif()\ncheck_include_files(libgen.h HAVE_LIBGEN_H)\nif (HAVE_LIBGEN_H)\n    check_symbol_exists(basename libgen.h HAVE_BASENAME)\n    check_symbol_exists(dirname libgen.h HAVE_DIRNAME)\nendif()\ncheck_include_files(pwd.h HAVE_PWD_H)\nif (HAVE_PWD_H)\n    check_symbol_exists(getpwnam pwd.h HAVE_GETPWNAM)\nendif()\ncheck_include_files(sys/epoll.h HAVE_SYS_EPOLL_H)\nif (HAVE_SYS_EPOLL_H)\n    check_symbol_exists(epoll_create sys/epoll.h HAVE_EPOLL_CREATE)\nendif()\ncheck_include_files(syslog.h HAVE_SYSLOG_H)\nif (HAVE_SYSLOG_H)\n    check_symbol_exists(openlog syslog.h HAVE_OPENLOG)\n    check_symbol_exists(syslog syslog.h HAVE_SYSLOG)\nendif()\ncheck_include_files(sys/mman.h HAVE_SYS_MMAN_H)\nif (HAVE_SYS_MMAN_H)\n    check_symbol_exists(mlockall sys/mman.h HAVE_MLOCKALL)\nendif()\ncheck_include_files(sys/socket.h HAVE_SYS_SOCKET_H)\nif (HAVE_SYS_SOCKET_H)\n    check_symbol_exists(sendmsg sys/socket.h HAVE_SENDMSG)\n    check_symbol_exists(recvmsg sys/socket.h HAVE_RECVMSG)\n    check_symbol_exists(getsockname sys/socket.h HAVE_GETSOCKNAME)\n    # Checking for existence of structs with check_symbol_exists does not work,\n    # so we use check_struct_hash_member with a member instead\n    check_struct_has_member(\"struct cmsghdr\" cmsg_len sys/socket.h HAVE_CMSGHDR)\nendif()\ncheck_include_files(sys/time.h HAVE_SYS_TIME_H)\nif (HAVE_SYS_TIME_H)\n    check_symbol_exists(gettimeofday sys/time.h HAVE_GETTIMEOFDAY)\n    check_symbol_exists(getrlimit \"sys/time.h;sys/resource.h\" HAVE_GETRLIMIT)\nendif()\n\ncheck_symbol_exists(chsize io.h HAVE_CHSIZE)\ncheck_symbol_exists(getrlimit sys/resource.h HAVE_GETRLIMIT)\ncheck_symbol_exists(strsep string.h HAVE_STRSEP)\n\n# Some OS (e.g. FreeBSD) need some basic headers to allow\n# including network headers\nset(NETEXTRA sys/types.h)\ncheck_include_files(\"${NETEXTRA};netinet/in.h\" HAVE_NETINET_IN_H)\nif (HAVE_NETINET_IN_H)\n    list(APPEND NETEXTRA netinet/in.h)\nendif ()\n\ncheck_include_files(arpa/inet.h HAVE_ARPA_INET_H)\ncheck_include_files(dlfcn.h HAVE_DLFCN_H)\ncheck_include_files(dmalloc.h HAVE_DMALLOC_H)\ncheck_include_files(fcntl.h HAVE_FCNTL_H)\ncheck_include_files(err.h HAVE_ERR_H)\ncheck_include_files(netdb.h HAVE_NETDB_H)\ncheck_include_files(\"${NETEXTRA};netinet/in6.h\" HAVE_NETINET_IN_H)\ncheck_include_files(net/if.h HAVE_NET_IF_H)\ncheck_include_files(\"${NETEXTRA};net/if_tun.h\" HAVE_NET_IF_TUN_H)\ncheck_include_files(poll.h HAVE_POLL_H)\ncheck_include_files(\"${NETEXTRA};resolv.h\" HAVE_RESOLV_H)\ncheck_include_files(sys/ioctl.h HAVE_SYS_IOCTL_H)\ncheck_include_files(sys/inotify.h HAVE_SYS_INOTIFY_H)\ncheck_include_files(\"${NETEXTRA};sys/uio.h\" HAVE_SYS_UIO_H)\ncheck_include_files(sys/un.h HAVE_SYS_UN_H)\ncheck_include_files(sys/wait.h HAVE_SYS_WAIT_H)\n\ncheck_include_files(\"${NETEXTRA};netinet/ip.h\" HAVE_NETINET_IP_H)\nif (HAVE_NETINET_IP_H)\n    set(CMAKE_EXTRA_INCLUDE_FILES netinet/ip.h)\n    check_type_size(\"struct in_pktinfo\" IN_PKTINFO)\n    check_struct_has_member(\"struct in_pktinfo\" ipi_spec_dst netinet/ip.h HAVE_IPI_SPEC_DST)\n    check_type_size(\"struct msghdr\" MSGHDR)\n    set(CMAKE_EXTRA_INCLUDE_FILES)\nendif()\n\nfind_program(IFCONFIG_PATH ifconfig)\nfind_program(IPROUTE_PATH ip)\nfind_program(ROUTE_PATH route)\n\nif (${ENABLE_LZ4})\n    pkg_search_module(liblz4 liblz4 REQUIRED IMPORTED_TARGET)\nendif ()\n\nif (${ENABLE_LZO})\n    pkg_search_module(lzo2 lzo2 REQUIRED IMPORTED_TARGET)\nendif ()\n\nif (${ENABLE_PKCS11})\n    pkg_search_module(pkcs11-helper libpkcs11-helper-1 REQUIRED IMPORTED_TARGET)\nendif ()\n\nfunction(check_mbed_configuration)\n    if (NOT (MBED_INCLUDE_PATH STREQUAL \"\") )\n        set(CMAKE_REQUIRED_INCLUDES ${MBED_INCLUDE_PATH})\n    endif ()\n    if (NOT (MBED_LIBRARY_PATH STREQUAL \"\"))\n        set(CMAKE_REQUIRED_LINK_OPTIONS \"-L${MBED_LIBRARY_PATH}\")\n    endif ()\n    set(CMAKE_REQUIRED_LIBRARIES \"mbedtls;mbedx509;mbedcrypto\")\n    check_include_files(psa/crypto.h HAVE_PSA_CRYPTO_H)\nendfunction()\n\nif (${MBED})\n    check_mbed_configuration()\nendif()\n\nfunction(add_library_deps target)\n    if (${MBED})\n        if (NOT (MBED_INCLUDE_PATH STREQUAL \"\") )\n            target_include_directories(${target} PRIVATE ${MBED_INCLUDE_PATH})\n        endif ()\n        if(NOT (MBED_LIBRARY_PATH STREQUAL \"\"))\n            target_link_directories(${target} PRIVATE ${MBED_LIBRARY_PATH})\n        endif ()\n\n        target_link_libraries(${target} PRIVATE -lmbedtls -lmbedx509 -lmbedcrypto)\n    elseif (${WOLFSSL})\n        pkg_search_module(wolfssl wolfssl REQUIRED)\n        target_link_libraries(${target} PUBLIC ${wolfssl_LINK_LIBRARIES})\n        target_include_directories(${target} PRIVATE ${wolfssl_INCLUDE_DIRS}/wolfssl)\n    else ()\n        find_package(OpenSSL REQUIRED)\n        target_link_libraries(${target} PUBLIC OpenSSL::SSL OpenSSL::Crypto)\n        if (WIN32)\n            target_link_libraries(${target} PUBLIC\n                ws2_32.lib crypt32.lib fwpuclnt.lib iphlpapi.lib\n                wininet.lib setupapi.lib rpcrt4.lib wtsapi32.lib ncrypt.lib bcrypt.lib)\n        endif ()\n\n    endif ()\n\n    if (MINGW)\n        target_compile_definitions(${target} PRIVATE\n                WIN32_LEAN_AND_MEAN\n                NTDDI_VERSION=NTDDI_VISTA _WIN32_WINNT=_WIN32_WINNT_VISTA\n        )\n    endif()\n\n    # optional dependencies\n    target_link_libraries(${target} PUBLIC\n        $<TARGET_NAME_IF_EXISTS:PkgConfig::liblz4>\n        $<TARGET_NAME_IF_EXISTS:PkgConfig::lzo2>\n        $<TARGET_NAME_IF_EXISTS:PkgConfig::pkcs11-helper>\n        )\n\n    if (${CMAKE_SYSTEM_NAME} STREQUAL \"Linux\")\n        pkg_search_module(libcapng REQUIRED libcap-ng IMPORTED_TARGET)\n        pkg_search_module(libnl REQUIRED libnl-genl-3.0 IMPORTED_TARGET)\n\n        target_link_libraries(${target} PUBLIC PkgConfig::libcapng PkgConfig::libnl)\n    endif ()\n\nendfunction()\n\nif (${MBED})\n    set(ENABLE_CRYPTO_MBEDTLS YES)\nelseif (${WOLFSSL})\n    set(ENABLE_CRYPTO_OPENSSL YES)\n    set(ENABLE_CRYPTO_WOLFSSL YES)\nelse ()\n    set(ENABLE_CRYPTO_OPENSSL YES)\nendif ()\n\ninclude_directories(${CMAKE_CURRENT_SOURCE_DIR} src/compat include)\n\nadd_custom_command(\n    OUTPUT always_rebuild config-version.h\n    COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/contrib/cmake/git-version.py\n    )\nset(HAVE_CONFIG_VERSION_H YES)\n\nif (BUILD_TESTING)\n    find_package(cmocka CONFIG)\n    if (TARGET cmocka::cmocka)\n        set(CMOCKA_LIBRARIES cmocka::cmocka)\n    else ()\n        pkg_search_module(cmocka cmocka REQUIRED IMPORTED_TARGET)\n        set(CMOCKA_LIBRARIES PkgConfig::cmocka)\n    endif ()\n    set(CMAKE_REQUIRED_LIBRARIES ${CMOCKA_LIBRARIES})\n    check_include_files(cmocka_version.h HAVE_CMOCKA_VERSION_H)\nendif ()\n\nconfigure_file(config.h.cmake.in config.h)\nconfigure_file(include/openvpn-plugin.h.in openvpn-plugin.h)\n# TODO we should remove the need for this, and always include config.h\nadd_compile_definitions(HAVE_CONFIG_H)\n\ninclude_directories(${CMAKE_CURRENT_BINARY_DIR})\n\nadd_subdirectory(doc)\nadd_subdirectory(src/openvpnmsica)\nadd_subdirectory(src/openvpnserv)\nadd_subdirectory(src/tapctl)\n\nset(SOURCE_FILES\n    ${CMAKE_CURRENT_BINARY_DIR}/config.h\n    ${CMAKE_CURRENT_BINARY_DIR}/config-version.h\n    ${CMAKE_CURRENT_BINARY_DIR}/openvpn-plugin.h\n\n    src/compat/compat-basename.c\n    src/compat/compat-daemon.c\n    src/compat/compat-dirname.c\n    src/compat/compat-gettimeofday.c\n    src/compat/compat-strsep.c\n    src/openvpn/argv.c\n    src/openvpn/argv.h\n    src/openvpn/base64.c\n    src/openvpn/base64.h\n    src/openvpn/basic.h\n    src/openvpn/buffer.c\n    src/openvpn/buffer.h\n    src/openvpn/circ_list.h\n    src/openvpn/clinat.c\n    src/openvpn/clinat.h\n    src/openvpn/common.h\n    src/openvpn/comp-lz4.c\n    src/openvpn/comp-lz4.h\n    src/openvpn/comp.c\n    src/openvpn/comp.h\n    src/openvpn/compstub.c\n    src/openvpn/console.c\n    src/openvpn/console_builtin.c\n    src/openvpn/console.h\n    src/openvpn/crypto.c\n    src/openvpn/crypto.h\n    src/openvpn/crypto_backend.h\n    src/openvpn/crypto_epoch.c\n    src/openvpn/crypto_epoch.h\n    src/openvpn/crypto_openssl.c\n    src/openvpn/crypto_openssl.h\n    src/openvpn/crypto_mbedtls.c\n    src/openvpn/crypto_mbedtls.h\n    src/openvpn/cryptoapi.c\n    src/openvpn/cryptoapi.h\n    src/openvpn/dco.c\n    src/openvpn/dco.h\n    src/openvpn/dco_win.c\n    src/openvpn/dco_win.h\n    src/openvpn/dco_linux.c\n    src/openvpn/dco_linux.h\n    src/openvpn/dco_freebsd.c\n    src/openvpn/dco_freebsd.h\n    src/openvpn/dhcp.c\n    src/openvpn/dhcp.h\n    src/openvpn/dns.c\n    src/openvpn/dns.h\n    src/openvpn/errlevel.h\n    src/openvpn/env_set.c\n    src/openvpn/env_set.h\n    src/openvpn/error.c\n    src/openvpn/error.h\n    src/openvpn/event.c\n    src/openvpn/event.h\n    src/openvpn/fdmisc.c\n    src/openvpn/fdmisc.h\n    src/openvpn/forward.c\n    src/openvpn/forward.h\n    src/openvpn/fragment.c\n    src/openvpn/fragment.h\n    src/openvpn/gremlin.c\n    src/openvpn/gremlin.h\n    src/openvpn/helper.c\n    src/openvpn/helper.h\n    src/openvpn/httpdigest.c\n    src/openvpn/httpdigest.h\n    src/openvpn/init.c\n    src/openvpn/init.h\n    src/openvpn/integer.h\n    src/openvpn/interval.c\n    src/openvpn/interval.h\n    src/openvpn/list.c\n    src/openvpn/list.h\n    src/openvpn/lladdr.c\n    src/openvpn/lladdr.h\n    src/openvpn/lzo.c\n    src/openvpn/lzo.h\n    src/openvpn/manage.c\n    src/openvpn/manage.h\n    src/openvpn/mbuf.c\n    src/openvpn/mbuf.h\n    src/openvpn/memdbg.h\n    src/openvpn/misc.c\n    src/openvpn/misc.h\n    src/openvpn/mroute.c\n    src/openvpn/mroute.h\n    src/openvpn/mss.c\n    src/openvpn/mss.h\n    src/openvpn/mtcp.c\n    src/openvpn/mtcp.h\n    src/openvpn/mtu.c\n    src/openvpn/mtu.h\n    src/openvpn/mudp.c\n    src/openvpn/mudp.h\n    src/openvpn/multi.c\n    src/openvpn/multi.h\n    src/openvpn/multi_io.h\n    src/openvpn/multi_io.c\n    src/openvpn/occ.c\n    src/openvpn/occ.h\n    src/openvpn/openvpn.c\n    src/openvpn/openvpn.h\n    src/openvpn/openvpn_win32_resources.rc\n    src/openvpn/options.c\n    src/openvpn/options.h\n    src/openvpn/options_util.c\n    src/openvpn/options_util.h\n    src/openvpn/options_parse.c\n    src/openvpn/otime.c\n    src/openvpn/otime.h\n    src/openvpn/ovpn_dco_win.h\n    src/openvpn/packet_id.c\n    src/openvpn/packet_id.h\n    src/openvpn/ping.c\n    src/openvpn/ping.h\n    src/openvpn/pkcs11.c\n    src/openvpn/pkcs11.h\n    src/openvpn/pkcs11_backend.h\n    src/openvpn/pkcs11_openssl.c\n    src/openvpn/pkcs11_mbedtls.c\n    src/openvpn/platform.c\n    src/openvpn/platform.h\n    src/openvpn/plugin.c\n    src/openvpn/plugin.h\n    src/openvpn/pool.c\n    src/openvpn/pool.h\n    src/openvpn/proto.c\n    src/openvpn/proto.h\n    src/openvpn/proxy.c\n    src/openvpn/proxy.h\n    src/openvpn/ps.c\n    src/openvpn/ps.h\n    src/openvpn/push.c\n    src/openvpn/push_util.c\n    src/openvpn/push.h\n    src/openvpn/pushlist.h\n    src/openvpn/reflect_filter.c\n    src/openvpn/reflect_filter.h\n    src/openvpn/reliable.c\n    src/openvpn/reliable.h\n    src/openvpn/route.c\n    src/openvpn/route.h\n    src/openvpn/run_command.c\n    src/openvpn/run_command.h\n    src/openvpn/schedule.c\n    src/openvpn/schedule.h\n    src/openvpn/session_id.c\n    src/openvpn/session_id.h\n    src/openvpn/shaper.c\n    src/openvpn/shaper.h\n    src/openvpn/sig.c\n    src/openvpn/sig.h\n    src/openvpn/socket.c\n    src/openvpn/socket.h\n    src/openvpn/socket_util.c\n    src/openvpn/socket_util.h\n    src/openvpn/socks.c\n    src/openvpn/socks.h\n    src/openvpn/ssl.c\n    src/openvpn/ssl.h\n    src/openvpn/ssl_backend.h\n    src/openvpn/ssl_common.h\n    src/openvpn/ssl_openssl.c\n    src/openvpn/ssl_openssl.h\n    src/openvpn/ssl_mbedtls.c\n    src/openvpn/ssl_mbedtls.h\n    src/openvpn/ssl_verify.c\n    src/openvpn/ssl_verify.h\n    src/openvpn/ssl_verify_backend.h\n    src/openvpn/ssl_verify_openssl.c\n    src/openvpn/ssl_verify_openssl.h\n    src/openvpn/ssl_verify_mbedtls.c\n    src/openvpn/ssl_verify_mbedtls.h\n    src/openvpn/status.c\n    src/openvpn/status.h\n    src/openvpn/syshead.h\n    src/openvpn/tls_crypt.c\n    src/openvpn/tun.c\n    src/openvpn/tun.h\n    src/openvpn/tun_afunix.c\n    src/openvpn/tun_afunix.h\n    src/openvpn/networking_sitnl.c\n    src/openvpn/networking_freebsd.c\n    src/openvpn/auth_token.c\n    src/openvpn/auth_token.h\n    src/openvpn/ssl_ncp.c\n    src/openvpn/ssl_ncp.h\n    src/openvpn/ssl_pkt.c\n    src/openvpn/ssl_pkt.h\n    src/openvpn/ssl_util.c\n    src/openvpn/ssl_util.h\n    src/openvpn/vlan.c\n    src/openvpn/vlan.h\n    src/openvpn/wfp_block.c\n    src/openvpn/wfp_block.h\n    src/openvpn/win32.c\n    src/openvpn/win32-util.c\n    src/openvpn/win32.h\n    src/openvpn/win32-util.h\n    src/openvpn/xkey_helper.c\n    src/openvpn/xkey_provider.c\n    )\n\nadd_executable(openvpn ${SOURCE_FILES})\n\nadd_library_deps(openvpn)\n\ntarget_compile_options(openvpn PRIVATE -DDEFAULT_DNS_UPDOWN=\\\"${DNS_UPDOWN_PATH}\\\")\n\nif(MINGW)\n    target_compile_options(openvpn PRIVATE -municode -UUNICODE)\n    target_link_options(openvpn PRIVATE -municode)\nendif()\n\nif (MSVC)\n    # we have our own manifest\n    target_link_options(openvpn PRIVATE /MANIFEST:NO)\nendif()\n\nif (${CMAKE_SYSTEM_NAME} STREQUAL \"Linux\")\n    target_link_libraries(openvpn PUBLIC -ldl)\nendif ()\n\nif (NOT WIN32)\n    target_compile_options(openvpn PRIVATE -DPLUGIN_LIBDIR=\\\"${PLUGIN_DIR}\\\")\n\n    find_library(resolv resolv)\n    # some platform like BSDs already include resolver functionality in the libc\n    # and do not have an extra resolv library\n    if (${resolv} OR APPLE)\n        set(RESOLV_LIBRARIES resolv)\n        target_link_libraries(openvpn PUBLIC ${RESOLV_LIBRARIES})\n    endif ()\nendif ()\n\noption(UT_ALLOW_BIG_ALLOC \"Allow unit-tests to use > 1 GB of memory\" ON)\n\nif (BUILD_TESTING)\n    set(unit_tests\n        \"test_argv\"\n        \"test_auth_token\"\n        \"test_buffer\"\n        \"test_crypto\"\n        \"test_dhcp\"\n        \"test_mbuf\"\n        \"test_misc\"\n        \"test_ncp\"\n        \"test_options_parse\"\n        \"test_packet_id\"\n        \"test_pkt\"\n        \"test_provider\"\n        \"test_socket\"\n        \"test_ssl\"\n        \"test_user_pass\"\n        \"test_push_update_msg\"\n        )\n\n    if (WIN32)\n        list(APPEND unit_tests\n            \"test_cryptoapi\"\n            )\n    endif ()\n\n    # MSVC and Apple's LLVM ld do not support --wrap\n    # This test requires cmake >= 3.18, so check if check_linker_flag is\n    # available\n    if (COMMAND check_linker_flag)\n        check_linker_flag(C -Wl,--wrap=parse_line LD_SUPPORTS_WRAP)\n    endif()\n\n    # Clang-cl (which is also MSVC) is wrongly detected to support wrap\n    if (NOT MSVC AND \"${LD_SUPPORTS_WRAP}\")\n        list(APPEND unit_tests\n            \"test_tls_crypt\"\n            )\n    endif ()\n\n    # These tests work on only on Linux since they depend on special Linux features\n    if (${CMAKE_SYSTEM_NAME} STREQUAL \"Linux\")\n        list(APPEND unit_tests\n            \"test_networking\"\n            )\n    endif ()\n\n    if (NOT WIN32 AND ${ENABLE_PKCS11})\n        set(_HAVE_SOFTHSM2 YES)\n        find_program(P11TOOL p11tool)\n        find_program(SOFTHSM2_UTIL softhsm2-util)\n        find_library(SOFTHSM2_MODULE softhsm2 PATH_SUFFIXES softhsm)\n\n        if (P11TOOL STREQUAL \"P11TOOL-NOTFOUND\")\n            message(STATUS \"p11tool not found, pkcs11 UT disabled\")\n            set(_HAVE_SOFTHSM2 NO)\n        elseif (SOFTHSM2_UTIL STREQUAL \"SOFTHSM2_UTIL-NOTFOUND\")\n            message(STATUS \"softhsm2-util not found, pkcs11 UT disabled\")\n            set(_HAVE_SOFTHSM2 NO)\n        elseif (SOFTHSM2_MODULE STREQUAL \"SOFTHSM2_MODULE-NOTFOUND\")\n            message(STATUS \"softhsm2 module not found, pkcs11 UT disabled\")\n            set(_HAVE_SOFTHSM2 NO)\n        endif ()\n\n        if (_HAVE_SOFTHSM2)\n            message(VERBOSE \"pkcs11 UT enabled\")\n            list(APPEND unit_tests\n                \"test_pkcs11\"\n                )\n        endif ()\n    endif ()\n\n    foreach (test_name ${unit_tests})\n        cmake_path(SET _UT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests/unit_tests/openvpn)\n        # test_networking needs special environment\n        if (NOT ${test_name} STREQUAL \"test_networking\")\n            add_test(${test_name} ${test_name})\n            # for compat with autotools make check\n            set_tests_properties(${test_name} PROPERTIES\n                ENVIRONMENT \"srcdir=${_UT_SOURCE_DIR};LSAN_OPTIONS=suppressions=${_UT_SOURCE_DIR}/input/leak_suppr.txt\")\n        endif ()\n        add_executable(${test_name}\n            tests/unit_tests/openvpn/${test_name}.c\n            tests/unit_tests/openvpn/mock_msg.c\n            tests/unit_tests/openvpn/mock_msg.h\n            src/openvpn/platform.c\n            src/openvpn/win32-util.c\n            src/compat/compat-gettimeofday.c\n            )\n\n        add_library_deps(${test_name})\n        target_link_libraries(${test_name} PUBLIC ${CMOCKA_LIBRARIES})\n\n        target_include_directories(${test_name} PRIVATE src/openvpn)\n\n        # for compat with IDEs like Clion that ignore the tests properties\n        # for the environment variable srcdir when running tests as fallback\n        target_compile_definitions(${test_name} PRIVATE \"UNIT_TEST_SOURCEDIR=\\\"${_UT_SOURCE_DIR}\\\"\")\n        if (UT_ALLOW_BIG_ALLOC)\n            target_compile_definitions(${test_name} PRIVATE UNIT_TEST_ALLOW_BIG_ALLOC)\n        endif ()\n\n        if (NOT ${test_name} STREQUAL \"test_buffer\")\n            target_sources(${test_name} PRIVATE\n                src/openvpn/buffer.c\n                )\n        endif ()\n\n    endforeach()\n\n    target_sources(test_auth_token PRIVATE\n        src/openvpn/base64.c\n        src/openvpn/crypto_epoch.c\n        src/openvpn/crypto_mbedtls.c\n        src/openvpn/crypto_openssl.c\n        src/openvpn/crypto.c\n        src/openvpn/otime.c\n        src/openvpn/packet_id.c\n        )\n\n    target_sources(test_buffer PRIVATE\n        tests/unit_tests/openvpn/mock_get_random.c\n        )\n\n    target_sources(test_crypto PRIVATE\n        src/openvpn/crypto_mbedtls.c\n        src/openvpn/crypto_openssl.c\n        src/openvpn/crypto_epoch.c\n        src/openvpn/crypto.c\n        src/openvpn/otime.c\n        src/openvpn/packet_id.c\n        src/openvpn/mtu.c\n        src/openvpn/mss.c\n        )\n\n    target_sources(test_ssl PRIVATE\n            tests/unit_tests/openvpn/mock_management.c\n            tests/unit_tests/openvpn/mock_ssl_dependencies.c\n            tests/unit_tests/openvpn/mock_win32_execve.c\n            src/openvpn/argv.c\n            src/openvpn/base64.c\n            src/openvpn/crypto_epoch.c\n            src/openvpn/crypto_mbedtls.c\n            src/openvpn/crypto_openssl.c\n            src/openvpn/crypto.c\n            src/openvpn/cryptoapi.c\n            src/openvpn/env_set.c\n            src/openvpn/mss.c\n            src/openvpn/mtu.c\n            src/openvpn/options_util.c\n            src/openvpn/otime.c\n            src/openvpn/packet_id.c\n            src/openvpn/run_command.c\n            src/openvpn/ssl_mbedtls.c\n            src/openvpn/ssl_openssl.c\n            src/openvpn/ssl_util.c\n            src/openvpn/ssl_verify_mbedtls.c\n            src/openvpn/ssl_verify_openssl.c\n            src/openvpn/xkey_helper.c\n            src/openvpn/xkey_provider.c\n    )\n\n    target_sources(test_mbuf PRIVATE\n        tests/unit_tests/openvpn/mock_get_random.c\n        src/openvpn/buffer.c\n        src/openvpn/mbuf.c\n        )\n\n    target_sources(test_misc PRIVATE\n        tests/unit_tests/openvpn/mock_get_random.c\n        src/openvpn/options_util.c\n        src/openvpn/ssl_util.c\n        src/openvpn/list.c\n        )\n\n    target_sources(test_ncp PRIVATE\n        src/openvpn/crypto_epoch.c\n        src/openvpn/crypto_mbedtls.c\n        src/openvpn/crypto_openssl.c\n        src/openvpn/crypto.c\n        src/openvpn/otime.c\n        src/openvpn/packet_id.c\n        src/openvpn/ssl_util.c\n        src/compat/compat-strsep.c\n        )\n\n    target_sources(test_options_parse PRIVATE\n        tests/unit_tests/openvpn/mock_get_random.c\n        src/openvpn/options_parse.c\n        src/openvpn/options_util.c\n        )\n\n    target_sources(test_packet_id PRIVATE\n        tests/unit_tests/openvpn/mock_get_random.c\n        src/openvpn/otime.c\n        src/openvpn/packet_id.c\n        src/openvpn/reliable.c\n        src/openvpn/session_id.c\n        )\n\n    target_sources(test_pkt PRIVATE\n        tests/unit_tests/openvpn/mock_win32_execve.c\n        src/openvpn/argv.c\n        src/openvpn/base64.c\n        src/openvpn/crypto_epoch.c\n        src/openvpn/crypto_mbedtls.c\n        src/openvpn/crypto_openssl.c\n        src/openvpn/crypto.c\n        src/openvpn/env_set.c\n        src/openvpn/otime.c\n        src/openvpn/packet_id.c\n        src/openvpn/reliable.c\n        src/openvpn/run_command.c\n        src/openvpn/session_id.c\n        src/openvpn/ssl_pkt.c\n        src/openvpn/tls_crypt.c\n        )\n\n    target_sources(test_provider PRIVATE\n        tests/unit_tests/openvpn/mock_get_random.c\n        src/openvpn/xkey_provider.c\n        src/openvpn/xkey_helper.c\n        src/openvpn/base64.c\n        )\n\n    target_link_libraries(test_socket PUBLIC ${RESOLV_LIBRARIES})\n    target_sources(test_socket PRIVATE\n        tests/unit_tests/openvpn/mock_get_random.c\n        tests/unit_tests/openvpn/mock_management.c\n        tests/unit_tests/openvpn/mock_win32_execve.c\n        src/openvpn/env_set.c\n        src/openvpn/run_command.c\n        src/openvpn/socket_util.c\n        )\n\n    target_sources(test_user_pass PRIVATE\n        tests/unit_tests/openvpn/mock_get_random.c\n        tests/unit_tests/openvpn/mock_win32_execve.c\n        src/openvpn/base64.c\n        src/openvpn/console.c\n        src/openvpn/env_set.c\n        src/openvpn/run_command.c\n        )\n\n    target_sources(test_push_update_msg PRIVATE\n        tests/unit_tests/openvpn/mock_msg.c\n        tests/unit_tests/openvpn/mock_get_random.c\n        src/openvpn/options_util.c\n        src/openvpn/otime.c\n        src/openvpn/list.c\n        )\n\n    target_sources(test_argv PRIVATE\n        tests/unit_tests/openvpn/mock_get_random.c\n        src/openvpn/argv.c\n        )\n\n    if (TARGET test_cryptoapi)\n        target_sources(test_cryptoapi PRIVATE\n            tests/unit_tests/openvpn/mock_get_random.c\n            tests/unit_tests/openvpn/cert_data.h\n            tests/unit_tests/openvpn/pkey_test_utils.c\n            src/openvpn/xkey_provider.c\n            src/openvpn/xkey_helper.c\n            src/openvpn/base64.c\n            )\n    endif ()\n\n    target_compile_definitions(test_dhcp PRIVATE DHCP_UNIT_TEST)\n    target_sources(test_dhcp PRIVATE\n        tests/unit_tests/openvpn/mock_get_random.c\n        )\n\n    if (TARGET test_networking)\n        target_link_options(test_networking PRIVATE -Wl,--wrap=parse_line)\n        target_compile_options(test_networking PRIVATE -UNDEBUG)\n        target_sources(test_networking PRIVATE\n            src/openvpn/networking_sitnl.c\n            src/openvpn/crypto_epoch.c\n            src/openvpn/crypto_mbedtls.c\n            src/openvpn/crypto_openssl.c\n            src/openvpn/crypto.c\n            src/openvpn/crypto_epoch.c\n            src/openvpn/fdmisc.c\n            src/openvpn/otime.c\n            src/openvpn/packet_id.c\n            )\n    endif ()\n\n    if (TARGET test_tls_crypt)\n        target_link_options(test_tls_crypt PRIVATE -Wl,--wrap=parse_line)\n        target_link_options(test_tls_crypt PRIVATE\n            -Wl,--wrap=buffer_read_from_file\n            -Wl,--wrap=buffer_write_file\n            -Wl,--wrap=rand_bytes)\n        target_sources(test_tls_crypt PRIVATE\n            tests/unit_tests/openvpn/mock_win32_execve.c\n            src/openvpn/argv.c\n            src/openvpn/base64.c\n            src/openvpn/crypto_epoch.c\n            src/openvpn/crypto_mbedtls.c\n            src/openvpn/crypto_openssl.c\n            src/openvpn/crypto.c\n            src/openvpn/env_set.c\n            src/openvpn/otime.c\n            src/openvpn/packet_id.c\n            src/openvpn/run_command.c\n            )\n    endif ()\n\n    if (TARGET test_pkcs11)\n        target_compile_options(test_pkcs11 PRIVATE\n            -DP11TOOL_PATH=\\\"${P11TOOL}\\\"\n            -DSOFTHSM2_MODULE_PATH=\\\"${SOFTHSM2_MODULE}\\\"\n            -DSOFTHSM2_UTIL_PATH=\\\"${SOFTHSM2_UTIL}\\\"\n            )\n        target_sources(test_pkcs11 PRIVATE\n            tests/unit_tests/openvpn/mock_get_random.c\n            tests/unit_tests/openvpn/pkey_test_utils.c\n            src/openvpn/argv.c\n            src/openvpn/base64.c\n            src/openvpn/env_set.c\n            src/openvpn/otime.c\n            src/openvpn/pkcs11.c\n            src/openvpn/pkcs11_openssl.c\n            src/openvpn/run_command.c\n            src/openvpn/xkey_helper.c\n            src/openvpn/xkey_provider.c\n            )\n    endif ()\n\nendif (BUILD_TESTING)\n"
  },
  {
    "path": "CMakePresets.json",
    "content": "{\n    \"version\": 3,\n    \"configurePresets\": [\n        {\n            \"name\": \"base\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CMAKE_TOOLCHAIN_FILE\": {\n                    \"value\": \"$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake\",\n                    \"type\": \"FILEPATH\"\n                },\n                \"VCPKG_OVERLAY_TRIPLETS\": {\n                    \"value\": \"${sourceDir}/contrib/vcpkg-triplets\",\n                    \"type\": \"FILEPATH\"\n                },\n                \"VCPKG_OVERLAY_PORTS\": {\n                    \"value\": \"${sourceDir}/contrib/vcpkg-ports\",\n                    \"type\": \"FILEPATH\"\n                }\n            }\n        },\n        {\n            \"name\": \"base-windows\",\n            \"hidden\": true,\n            \"binaryDir\": \"${sourceDir}/out/build/${presetName}\",\n            \"generator\": \"Visual Studio 17 2022\",\n            \"cacheVariables\": {\n                \"VCPKG_MANIFEST_DIR\": \"${sourceDir}/contrib/vcpkg-manifests/windows\",\n                \"VCPKG_HOST_TRIPLET\": \"x64-windows\"\n            },\n            \"vendor\": { \"microsoft.com/VisualStudioSettings/CMake/1.0\": { \"hostOS\": [ \"Windows\" ] } }\n        },\n        {\n            \"name\": \"base-mingw\",\n            \"hidden\": true,\n            \"generator\": \"Ninja Multi-Config\",\n            \"cacheVariables\": {\n                \"CMAKE_SYSTEM_NAME\": {\n                    \"value\": \"Windows\",\n                    \"type\": \"STRING\"\n                },\n                \"VCPKG_MANIFEST_DIR\": \"${sourceDir}/contrib/vcpkg-manifests/mingw\"\n            }\n        },\n        {\n            \"name\": \"x64\",\n            \"hidden\": true,\n            \"architecture\": {\n                \"value\": \"x64\",\n                \"strategy\": \"set\"\n            },\n            \"cacheVariables\": {\n                \"VCPKG_TARGET_TRIPLET\": \"x64-windows-ovpn\"\n            }\n        },\n        {\n            \"name\": \"x64-mingw\",\n            \"hidden\": true,\n            \"binaryDir\": \"out/build/mingw/x64\",\n            \"cacheVariables\": {\n                \"CMAKE_C_COMPILER\": {\n                    \"value\": \"x86_64-w64-mingw32-gcc\",\n                    \"type\": \"STRING\"\n                },\n                \"CMAKE_CXX_COMPILER\": {\n                    \"value\": \"x86_64-w64-mingw32-g++\",\n                    \"type\": \"STRING\"\n                },\n                \"VCPKG_TARGET_TRIPLET\": \"x64-mingw-ovpn\"\n            }\n        },\n        {\n            \"name\": \"arm64\",\n            \"hidden\": true,\n            \"architecture\": {\n                \"value\": \"arm64\",\n                \"strategy\": \"set\"\n            },\n            \"cacheVariables\": {\n                \"VCPKG_TARGET_TRIPLET\": \"arm64-windows-ovpn\"\n            }\n        },\n        {\n            \"name\": \"x86\",\n            \"hidden\": true,\n            \"architecture\": {\n                \"value\": \"Win32\",\n                \"strategy\": \"set\"\n            },\n            \"cacheVariables\": {\n                \"VCPKG_TARGET_TRIPLET\": \"x86-windows-ovpn\"\n            }\n        },\n        {\n            \"name\": \"i686-mingw\",\n            \"hidden\": true,\n            \"binaryDir\": \"out/build/mingw/x86\",\n            \"cacheVariables\": {\n                \"CMAKE_C_COMPILER\": {\n                    \"value\": \"i686-w64-mingw32-gcc\",\n                    \"type\": \"STRING\"\n                },\n                \"CMAKE_CXX_COMPILER\": {\n                    \"value\": \"i686-w64-mingw32-g++\",\n                    \"type\": \"STRING\"\n                },\n                \"VCPKG_TARGET_TRIPLET\": \"x86-mingw-ovpn\"\n            }\n        },\n        {\n            \"name\": \"debug\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CMAKE_BUILD_TYPE\": \"Debug\"\n            }\n        },\n        {\n            \"name\": \"release\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CMAKE_BUILD_TYPE\": \"Release\"\n            }\n        },\n        {\n            \"name\": \"clangtoolset\",\n            \"toolset\": \"ClangCL\"\n        },\n        {\n            \"name\": \"mingw-x64\",\n            \"inherits\": [ \"base\", \"base-mingw\", \"x64-mingw\" ]\n        },\n        {\n            \"name\": \"mingw-x86\",\n            \"inherits\": [ \"base\", \"base-mingw\", \"i686-mingw\" ]\n        },\n        {\n            \"name\": \"win-amd64-release\",\n            \"inherits\": [ \"base\", \"base-windows\", \"x64\", \"release\" ]\n        },\n        {\n            \"name\": \"win-amd64-clang-release\",\n            \"inherits\": [ \"base\", \"base-windows\", \"clangtoolset\", \"x64\", \"release\" ]\n        },\n        {\n            \"name\": \"win-arm64-release\",\n            \"inherits\": [ \"base\", \"base-windows\", \"arm64\", \"release\" ]\n        },\n        {\n            \"name\": \"win-x86-release\",\n            \"inherits\": [ \"base\", \"base-windows\", \"x86\", \"release\" ]\n        },\n        {\n            \"name\": \"win-x86-clang-release\",\n            \"inherits\": [ \"base\", \"base-windows\", \"clangtoolset\", \"x86\", \"release\" ]\n        },\n        {\n            \"name\": \"win-amd64-debug\",\n            \"inherits\": [ \"base\", \"base-windows\", \"x64\", \"debug\" ]\n        },\n        {\n            \"name\": \"win-amd64-clang-debug\",\n            \"inherits\": [ \"base\", \"base-windows\", \"clangtoolset\", \"x64\", \"debug\" ]\n        },\n        {\n            \"name\": \"win-arm64-debug\",\n            \"inherits\": [ \"base\", \"base-windows\", \"arm64\", \"debug\" ]\n        },\n        {\n            \"name\": \"win-x86-debug\",\n            \"inherits\": [ \"base\", \"base-windows\", \"x86\", \"debug\" ]\n        },\n        {\n            \"name\": \"win-x86-clang-debug\",\n            \"inherits\": [ \"base\", \"base-windows\", \"clangtoolset\", \"x86\", \"debug\" ]\n        },\n        {\n            \"name\": \"unix-native\",\n            \"generator\": \"Ninja Multi-Config\",\n            \"binaryDir\": \"out/build/unix\"\n        }\n    ],\n    \"buildPresets\": [\n        {\n            \"name\": \"mingw-x64\",\n            \"configurePreset\": \"mingw-x64\"\n        },\n        {\n            \"name\": \"mingw-x86\",\n            \"configurePreset\": \"mingw-x86\"\n        },\n        {\n            \"name\": \"win-amd64-release\",\n            \"configurePreset\": \"win-amd64-release\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"win-amd64-clang-release\",\n            \"configurePreset\": \"win-amd64-clang-release\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"win-arm64-release\",\n            \"configurePreset\": \"win-arm64-release\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"win-x86-release\",\n            \"configurePreset\": \"win-x86-release\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"win-x86-clang-release\",\n            \"configurePreset\": \"win-x86-clang-release\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"win-amd64-debug\",\n            \"configurePreset\": \"win-amd64-debug\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"win-amd64-clang-debug\",\n            \"configurePreset\": \"win-amd64-clang-debug\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"win-arm64-debug\",\n            \"configurePreset\": \"win-arm64-debug\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"win-x86-debug\",\n            \"configurePreset\": \"win-x86-debug\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"win-x86-clang-debug\",\n            \"configurePreset\": \"win-x86-clang-debug\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"unix-native\",\n            \"configurePreset\": \"unix-native\"\n        }\n    ],\n    \"testPresets\": [\n        {\n            \"name\": \"win-amd64-release\",\n            \"configurePreset\": \"win-amd64-release\"\n        },\n        {\n            \"name\": \"win-amd64-clang-release\",\n            \"configurePreset\": \"win-amd64-clang-release\"\n        },\n        {\n            \"name\": \"win-x86-release\",\n            \"configurePreset\": \"win-x86-release\"\n        },\n        {\n            \"name\": \"win-x86-clang-release\",\n            \"configurePreset\": \"win-x86-clang-release\"\n        },\n        {\n            \"name\": \"win-amd64-debug\",\n            \"configurePreset\": \"win-amd64-debug\"\n        },\n        {\n            \"name\": \"win-amd64-clang-debug\",\n            \"configurePreset\": \"win-amd64-clang-debug\"\n        },\n        {\n            \"name\": \"win-x86-debug\",\n            \"configurePreset\": \"win-x86-debug\"\n        },\n        {\n            \"name\": \"win-x86-clang-debug\",\n            \"configurePreset\": \"win-x86-clang-debug\"\n        },\n        {\n            \"name\": \"unix-native\",\n            \"configurePreset\": \"unix-native\"\n        }\n     ]\n}\n"
  },
  {
    "path": "CONTRIBUTING.rst",
    "content": "CONTRIBUTING TO THE OPENVPN PROJECT\n===================================\n\nPatches should be written against the Git \"master\" branch. Some patches may get\nbackported to a release branch.\n\nThe preferred procedure is to send patches to the \"openvpn-devel\" mailing list:\n\n- https://lists.sourceforge.net/lists/listinfo/openvpn-devel\n\nInstead of directly sending patches to the list you can also create an account\nin our instance of the Gerrit review tool: https://gerrit.openvpn.net/\nSee https://community.openvpn.net/Development/GerritBestPractices.\n\nWhile we do not merge GitHub pull requests as-is, we do allow their use for code\nreview purposes. After the patch has been ACKed (reviewed and accepted), it must\nbe sent to the mailing list. This last step does not necessarily need to be done\nby the patch author, although that is definitely recommended.\n\nWhen sending patches to \"openvpn-devel\" the subject line should be prefixed with\n``[PATCH]``. To avoid merging issues the patches should be generated with\ngit-format-patch or sent using git-send-email. Try to split large patches into\nsmall, atomic pieces to make reviews easier.\n\nPlease make sure that the source code formatting follows the guidelines at\nhttps://community.openvpn.net/Development/CodeStyle. Automated checking can be\ndone with clang-format (https://community.openvpn.net/Development/CodeStyle)\nand the configuration file which can be found in the git repository at ``.clang-format``.\n\nThere is also a git pre-commit hook script, which runs clang-format automatically\neach time you commit and lets you format your code conveniently, if needed.\nTo install the hook simply run: ``dev-tools/git-pre-commit-format.sh install``\n\nIf you want quick feedback on a patch before sending it to openvpn-devel mailing\nlist, you can visit the #openvpn-devel channel on irc.libera.chat. Note that\nyou need to be logged in to Libera to join the channel:\n\n- https://libera.chat/guides/registration\n\nMore detailed contribution instructions are available here:\n\n- https://community.openvpn.net/Development/DeveloperDocumentation\n\nNote that the process for contributing to other OpenVPN projects such as\nopenvpn-build, openvpn-gui, tap-windows6 and easy-rsa may differ from what was\ndescribed above. Please refer to the contribution instructions of each\nrespective project.\n"
  },
  {
    "path": "COPYING",
    "content": "OpenVPN (TM) -- An Open Source VPN daemon\n\nCopyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n\nThis distribution contains multiple components, some\nof which fall under different licenses.  By using OpenVPN\nor any of the bundled components enumerated below, you\nagree to be bound by the conditions of the license for\neach respective component.\n\nOpenVPN trademark\n-----------------\n\n  \"OpenVPN\" is a trademark of OpenVPN Inc\n\n\nOpenVPN license:\n----------------\n\n  OpenVPN is distributed under the GPL license version 2.\n\n  Special exception for linking OpenVPN with OpenSSL:\n\n  In addition, as a special exception, OpenVPN Inc gives\n  permission to link the code of this program with the OpenSSL\n  library (or with modified versions of OpenSSL that use the same\n  license as OpenSSL), and distribute linked combinations including\n  the two.  You must obey the GNU General Public License in all\n  respects for all of the code used other than OpenSSL.  If you modify\n  this file, you may extend this exception to your version of the\n  file, but you are not obligated to do so.  If you do not wish to\n  do so, delete this exception statement from your version.\n\nApache2 linking exception:\n---------------------------\n  In addition, as a special exception, OpenVPN Inc and the\n  contributors give permission to link the code of this program to\n  libraries (the \"Libraries\") licensed under the Apache License\n  version 2.0 (this work and any linked library the \"Combined Work\")\n  and copy and distribute the Combined Work without an obligation to\n  license the Libraries under the GNU General Public License v2\n  (GPL-2.0) as required by Section 2 of the GPL-2.0, and without an\n  obligation to refrain from imposing any additional restrictions in\n  the Apache License version 2 that are not in the GPL-2.0, as\n  required by Section 6 of the GPL-2.0.  You must comply with the\n  GPL-2.0 in all other respects for the Combined Work, including\n  the obligation to provide source code.  If you modify this file, you\n  may extend this exception to your version of the file, but you are\n  not obligated to do so.  If you do not wish to do so, delete this\n  exception statement from your version.\n\nFor better understanding, in plain non-legalese English this basically says:\n\n * The intention for this license exception is to allow OpenVPN to be\n   linked against APL-2 licensed libraries, even where the GPL-2.0 and\n   APL-2 licenses conflict from a legal perspective.\n\n * OpenVPN itself will stay GPL-2.0 and the code belonging to the\n   OpenVPN project must comply to the GPL-2.0 license.  This is NOT\n   dual-licensing of the OpenVPN code base.\n\n * This license exception DOES NOT require NOR expect a license change\n   of the APL-2 based library.  This exception allows using the APL-2\n   library as-is.  However, when distributing a compiled OpenVPN binary\n   linking against APL-2 libraries (\"Combined Work\"), the REQUIREMENT is\n   that the APL-2 library MUST also be available on similar terms as in\n   GPL-2.0, like providing the source code of the library upon request,\n   except in the two specific ways mentioned.\n\n * If the APL-2 based library forbids such linking and distribution,\n   this license exception DOES NOT overrule the restriction of the APL-2\n   based library.  If the APL-2 library cannot satisfy the requirements\n   in this license exception, you CANNOT distribute an OpenVPN binary\n   linked with this library.\n\nLZO linking exception:\n----------------------\n\n  LZO is Copyright (C) Markus F.X.J. Oberhumer,\n  and is licensed under the GPL.\n\n  Special exception for linking OpenVPN with both OpenSSL and LZO:\n\n  Hereby I grant a special exception to the OpenVPN project\n  (https://openvpn.net/) to link the LZO library with\n  the OpenSSL library (https://www.openssl.org).\n\n  Markus F.X.J. Oberhumer\n"
  },
  {
    "path": "COPYRIGHT.GPL",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Moe Ghoul>, 1 April 1989\n  Moe Ghoul, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "ChangeLog",
    "content": "OpenVPN ChangeLog\nCopyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n\nthis marks the start of the 2.8 development cycle\n\nup to the first formal 2.8 pre-release, this file will not be\nmaintained - please look at \"git log\" or \"git shortlog v2.7.0..HEAD\"\nto see what was changed.\n"
  },
  {
    "path": "Changes.rst",
    "content": "Overview of changes in 2.8\n==========================\n\n\nOverview of changes in 2.7\n==========================\nNew features\n------------\nMulti-socket support for servers\n    OpenVPN servers now can listen on multiple sockets at the same time.\n    Multiple ``--local`` statements in the configuration can be used to\n    configure this. This way the same server can e.g. listen for UDP\n    and TCP connections at the same time, or listen on multiple addresses\n    and/or ports.\n\nClient implementations for DNS options sent by server for Linux/BSD/macOS\n    Linux, BSD and macOS versions of OpenVPN now ship with a per-platform\n    default ``--dns-updown`` script that implements proper handling of\n    DNS configuration sent by the server.  The scripts should work on\n    systems that use ``systemd`` or ``resolveconf`` to manage the DNS\n    setup, as well as raw ``/etc/resolv.conf`` files. However, the exact\n    features supported will depend on the configuration method.\n    On Linux and MacOS this should usually make split-DNS configurations\n    supported out-of-the-box now.\n\n    Note that this new script will not be used by default if a ``--up``\n    script is already in use to reduce problems with\n    backwards compatibility.\n\n    See documentation for ``--dns-updown`` and ``--dns`` for more details.\n\nNew client implementation for DNS options sent by server for Windows\n    The Windows client now uses NRPT (Name Resolution Policy Table) to\n    handle DNS configurations. This adds support for split-DNS and DNSSEC\n    and improves the compatbility with local DNS resolvers. Requires the\n    interactive service.\n\nOn Windows the ``block-local`` flag is now enforced with WFP filters.\n    The ``block-local`` flag to ``--redirect-gateway`` and\n    ``--redirect-private`` is now also enforced via the Windows Firewall,\n    making sure packets can't be sent to the local network.\n    This provides stronger protection against TunnelCrack-style attacks.\n\nWindows network adapters are now generated on demand\n    This means that on systems that run multiple OpenVPN connections at\n    the same time the users don't need to manually create enough network\n    adapters anymore (in addition to the ones created by the installer).\n\nWindows automatic service now runs as an unpriviledged user\n    All tasks that need privileges are now delegated to the interactive\n    service.\n    **NOTE** this has the risk of breaking existing setups if the\n    Windows certificate store is used (cryptoapi), and the certificates\n    are not readable for ``NT SERVICE\\OpenVPNService``.\n\nSupport for new version of Linux DCO module\n    OpenVPN DCO module is moving upstream and being merged into the\n    main Linux kernel. For this process some API changes were required.\n    OpenVPN 2.7 will only support the new API. The new module is called\n    ``ovpn``. Out-of-tree builds for older kernels are available. Please\n    see the release announcements for futher information.\n\nSupport for server mode in win-dco driver\n    On Windows the win-dco driver can now be used in server setups.\n\nSupport for TLS client floating in DCO implementations\n    The kernel modules will detect clients floating to a new IP address\n    and notify userland so both data packets (kernel) and TLS packets\n    (sent by userland) can reach the new client IP.\n    (Actual support depends on recent-enough kernel implementation)\n\nEnforcement of AES-GCM usage limit\n    OpenVPN will now enforce the usage limits on AES-GCM with the same\n    confidentiality margin as TLS 1.3 does. This mean that renegotiation will\n    be triggered after roughly 2^28 to 2^31 packets depending of the packet\n    size. More details about usage limit of AES-GCM can be found here:\n\n    https://datatracker.ietf.org/doc/draft-irtf-cfrg-aead-limits/\n\nEpoch data keys and packet format\n    This introduces the epoch data format for AEAD data channel\n    ciphers in TLS mode ciphers. This new data format has a number of\n    improvements over the standard \"DATA_V2\" format.\n\n    - AEAD tag at the end of packet which is more hardware implementation\n      friendly\n    - Automatic key switchover when cipher usage limits are hit, similar to\n      the epoch data keys in (D)TLS 1.3\n    - 64 bit instead of 32 bit packet ids to allow the data channel to be\n      ready for 10 GBit/s without having frequent renegotiation\n    - IV constructed with XOR instead of concatenation to not have (parts) of\n      the real IV on the wire\n\nSupport for Epoch data channel on Windows, using the win-dco driver (2.8.0+)\n\nDefault ciphers in ``--data-ciphers``\n    Ciphers in ``--data-ciphers`` can contain the string DEFAULT that is\n    replaced by the default ciphers used by OpenVPN, making it easier to\n    add an allowed cipher without having to spell out the default ciphers.\n\nTLS alerts\n    OpenVPN 2.7 will send out TLS alerts to peers informing them if the TLS\n    session shuts down or when the TLS implementation informs the peer about\n    an error in the TLS session (e.g. mismatching TLS versions). This improves\n    the user experience as the client shows an error instead of running into\n    a timeout when the server just stops responding completely.\n\nSupport for tun/tap via unix domain socket and lwipovpn support\n    To allow better testing and emulating a full client with a full\n    network stack OpenVPN now allows a program executed to provide\n    a tun/tap device instead of opening a device.\n\n    The co-developed lwipovpn program based on lwIP stack allows to\n    simulate full IP stack. An OpenVPN client using\n    ``--dev-node unix:/path/to/lwipovpn`` can emulate a full client that\n    can be pinged, can serve a website and more without requiring any\n    elevated permission. This can make testing OpenVPN much easier.\n\n    For more details see\n    `lwipovpn on Github <https://github.com/OpenVPN/lwipovpn>`_.\n\nAllow overriding username with ``--override-username``\n    This is intended to allow using ``--auth-gen-token`` in scenarios where the\n    clients use certificates and multi-factor authentication.  This will\n    also generate a ``push \"auth-token-user newusername\"`` directive in\n    push replies.\n\n``--port-share`` now properly supports IPv6\n    Issues with logging of IPv6 addresses were fixed. The feature now allows\n    IPv6 connections towards the proxy receiver.\n\nSupport for Haiku OS\n\nTLS1.3 support with mbedTLS (requires mbedTLS >= 3.6.4)\n\nPUSH_UPDATE client support\n    It is now possible to update parts of the client-side configuration\n    (IP address, routes, MTU, DNS) by sending a new server-to-client\n    control message, ``PUSH_UPDATE,<options>``.\n    See also: https://openvpn.github.io/openvpn-rfc/openvpn-wire-protocol.html\n    NOTE: PUSH_UPDATE client support is currently disabled if DCO\n    is active (on all platforms).\n\nPUSH_UPDATE server support (minimal)\n    New management interface commands ``push-update-broad`` and\n    ``push-update-cid`` to send PUSH_UPDATE option updates to all\n    clients (\"there is a new DNS server\") or only a specific client ID\n    (\"privileges have changed, here's a new IP address\").  See\n    doc/management-notes.txt\n    NOTE: PUSH_UPDATE server support is currently disabled if DCO\n    is active (on all platforms).\n\nSupport for user-defined routing tables on Linux\n    See the ``--route-table`` option in the manpage\n\nPQE support for WolfSSL\n\nTwo new environment variables have been introduced to communicate desired\n    default gateway redirection to plugins like Network Manager,\n    ``route_redirect_gateway_ipv4`` and ``route_redirect_gateway_ipv6``.\n    See the \"Environmental Variables\" section in the man page\n\nImproved logging of service events/errors to event log on Windows.\n\n\"Recursive Routing\" check is now more granular, and will only drop\n   packets-in-tunnel if destination IP, protocol and port matches with\n   those needed to reach the VPN server.  With that change, you can now\n   use policies that direct \"everything that is not OpenVPN\" into the\n   tunnel, and have IP packets to the VPN server address arrive as\n   expected (no such policies are currently installed by OpenVPN)\n   (GH: OpenVPN/openvpn#669).\n\nCOPYING: license details only relevant to our Windows installers have\n   been updated and moved to the openvpn-build repo\n\nImproved BYTECOUNT support - more strictly adhere to timing interval\n   requested, correctly support client and server counters with Linux and\n   Windows DCO offloading.\n\nImprove compatibility with OpenSSL 3.6.0 (do not fail t_lpback selftest)\n\nNew option ``--tls-crypt-v2-max-age n`` to check tls-crypt-v2 timestamps\n   (When a client is older than n days or has no timestamp, the server\n    will reject it)\n\nmbedTLS 4 support has been added.\n   Note that with mbedTLS 4 algorithms need to be translated to\n   mbedTLS 4 internal IDs by OpenVPN, and some names might be\n   missing.\n\n\nDeprecated features\n-------------------\n``secret`` support has been removed (by default).\n    static key mode (non-TLS) is no longer considered \"good and secure enough\"\n    for today's requirements.  Use TLS mode instead.  If deploying a PKI CA\n    is considered \"too complicated\", using ``--peer-fingerprint`` makes\n    TLS mode about as easy as using ``--secret``.\n\n    This mode can still be enabled by using\n    ``--allow-deprecated-insecure-static-crypto`` but will be removed in\n    OpenVPN 2.8.\n\nSupport for wintun Windows driver has been removed.\n    OpenVPN 2.6 added support for the new dco-win driver, so it supported\n    three different device drivers: dco-win, wintun, and tap-windows6.\n    OpenVPN 2.7 now drops the support for wintun driver. By default\n    all modern configs should be supported by dco-win driver. In all\n    other cases OpenVPN will fall back automatically to tap-windows6\n    driver.\n\nNTLMv1 authentication support for HTTP proxies has been removed.\n    This is considered an insecure method of authentication that uses\n    obsolete crypto algorithms.\n    NTLMv2 support is still available, but will be removed in a future\n    release.\n    When configured to authenticate with NTLMv1 (``ntlm`` keyword in\n    ``--http-proxy``) OpenVPN will try NTLMv2 instead.\n\n``persist-key`` option has been enabled by default.\n    All the keys will be kept in memory across restart.\n\nOpenSSL 1.0.2 support has been removed.\n    Support for building with OpenSSL 1.0.2 has been removed. The minimum\n    supported OpenSSL version is now 1.1.0.\n\nmbedTLS 2.x support has been removed\n    Support for building with mbedTLS 2.x has been removed (it is out\n    of support since March 2025, and the necessary compatibility code\n    is making maintenance and support for mbedTLS 4.x hard).\n    The minimum supported mbedTLS version is now 3.2.1.\n\nCompression on send has been removed.\n    OpenVPN 2.7 will never compress data before sending. Decompression of\n    received data is still supported.\n    ``--allow-compression yes`` is now an alias for\n    ``--allow-compression asym``.\n\n``--memstats`` feature removed\n    The ``--memstats`` option was largely undocumented and there is no known\n    user of this feature.  This feature provided very limited statistics\n    (number of users, link bytes read/written) and we do not except any\n    usage because of this.\n\nUsing ``--push`` in a mode that is not ``--mode server`` will now print a\n    clear warning that this is an unsupported operation and might cause\n    negotiation failures.\n\n``--reneg-bytes`` and ``--reneg-packets`` do not work in DCO mode, and will\n    now print an appropriate warning.\n\nOn-connect resolving of ``--remote`` addresses in ``--tcp-server`` mode\n    was not working since 2.4, so the code was completely removed.\n\n\nUser-visible Changes\n--------------------\n- Default for ``--topology`` changed to ``subnet`` for ``--mode server``.\n  Previous releases always used ``net30`` as default. This only affects\n  configs with ``--mode server`` or ``--server`` (the latter implies the\n  former), and ``--dev tun``, and only if IPv4 is enabled.\n  Note that this changes the semantics of ``--ifconfig``, so if you have\n  manual settings for that in your config but not set ``--topology``\n  your config might fail to parse with the new version. Just adding\n  ``--topology net30`` to the config should fix the problem.\n  By default ``--topology`` is pushed from server to client.\n\n- ``--x509-username-field`` will no longer automatically convert fieldnames to\n  uppercase. This was deprecated since OpenVPN 2.4, and has now been removed.\n\n- ``--dh none`` is now the default if ``--dh`` is not specified. Modern TLS\n  implementations will prefer ECDH and other more modern algorithms anyway.\n  And finite field Diffie Hellman is in the proces of being deprecated\n  (see draft-ietf-tls-deprecate-obsolete-kex)\n\n- ``--lport 0`` does not imply ``--bind`` anymore.\n\n- ``--redirect-gateway`` now works correctly if the VPN remote is not\n  reachable by the default gateway.\n\n- ``--show-gateway`` now supports querying the gateway for IPv4 addresses.\n\n- ``--static-challenge`` option now has a third parameter ``format`` that\n  can change how password and challenge response should be combined.\n\n- ``--key`` and ``--cert`` now accept URIs implemented in OpenSSL 3 as well as\n  optional OpenSSL 3 providers loaded using ``--providers`` option.\n\n- ``--cryptoapicert`` now supports issuer name as well as Windows CA template\n  name or OID as selector string.\n\n- TLS handshake debugging information contains much more details now when\n  using recent versions of OpenSSL.\n\n- The ``IV_PLAT_VER`` variable sent by Windows clients now contains the\n  full Windows build version to make it possible to determine the\n  Windows 10 or Windows 11 version used.\n\n- The ``--windows-driver`` option to select between various windows\n  drivers will no longer do anything - it's kept so existing configs\n  will not become invalid, but it is ignored with a warning.  The default\n  is now ``ovpn-dco`` if all options used are compatible with DCO, with\n  a fallback to ``tap-windows6``.  To force TAP (for example because a\n  server pushes DCO incompatible options), use the ``--disable-dco``\n  option.\n\n- Apply more checks to incoming TLS handshake packets before creating\n  new state - namely, verify message ID / acked ID for \"valid range for\n  an initial packet\".  This fixes a problem with clients that float\n  very early but send control channel packet from the pre-float IP\n  (Github: OpenVPN/openvpn#704).\n\n- Use of ``--dh dh2048.pem`` in all sample configs has been replaced\n  with ``--dh none``.  The ``dh2048.pem`` file has been removed.\n\n- The startup delay in ``t_client.sh`` has been reduced from 3s to 1s,\n  making a noticeable difference for setups with many tests.\n\n- Changed from using ``uncrustify`` for code formatting and pre-commit checks\n  to ``clang-format``.  This reformatted quite a bit of code, and requires\n  that regular committers change their pre-commit checks accordingly.\n\n- On Linux, on interfaces where applicable, OpenVPN explicitly configures\n  the broadcast address again.  This was dropped for 2.6.0 \"because\n  computers are smart and can do it themselves\", but the kernel netlink\n  interface isn't, and will install \"0.0.0.0\".  This does not normally\n  matter, but for broadcast-based applications that get the address to\n  use from \"ifconfig\", this change repairs functionality (this has\n  been backported to 2.6.15, but is not in earlier 2.6 versions).\n\n- ``max-routes-per-client 0`` used to be silently upgraded to ``1``.  This\n  now produces an error.\n\n- ``ifconfig`` and ``ifconfig-ipv6`` values are now stored in pre-connect\n  options cache, and will be restored to pre-connect values on reconnects\n  if the server stops pushing the respective option.\n\n- ``tapctl.exe`` helper binary on Windows has been reworked to improve\n  help texts (making clear that it can not only do TAP-Adapters but\n  Win-DCO as well), add printing of the hwid to all adapter outputs, and\n  change the default adapter type created to ``ovpn-dco``.\n\n- The default for ``multihome`` egress interface handling has changed.\n  2.7.0 will default to ipi_ifindex=0, that is, leave the decision to the\n  routing/policy setup of the operating system.  The pre-2.7 behaviour\n  (force egress = ingress interface) can be achieved with the new\n  ``--multihome same-interface`` sub-option.\n\n- Windows ``openvpn.exe`` binary manifest now sets code page UTF8 - which\n  has no direct effect on OpenVPN itself, but this repairs OpenSSL file\n  loading for key/cert files with non-ASCII characters in their file names\n  (GH: OpenVPN/openvpn#920).\n\n- The ``test-crypto`` option no longer requires a ``--secret`` argument and\n  will automatically generate a random key.\n\n- The configure-time option ``--enable-x509-alt-username`` is no longer\n  conditional, and always-on (GH: OpenVPN/openvpn#917).\n\n\nDeprecated features\n-------------------\n``--opt-verify`` feature removed\n    This option was already deprecated and it is now being converted to a\n    no-op. Using this option will only print a warning.\n\n\nOverview of changes in 2.6\n==========================\n\nProject changes\n---------------\n\nWe want to deprecate our old Trac bug tracking system.\nPlease report any issues with this release in GitHub\ninstead: https://github.com/OpenVPN/openvpn/issues\n\nNew features\n------------\nSupport unlimited number of connection entries and remote entries\n\nNew management commands to enumerate and list remote entries\n    Use ``remote-entry-count`` and ``remote-entry-get``\n    commands from the management interface to get the number of\n    remote entries and the entries themselves.\n\nKeying Material Exporters (RFC 5705) based key generation\n    As part of the cipher negotiation OpenVPN will automatically prefer\n    the RFC5705 based key material generation to the current custom\n    OpenVPN PRF. This feature requires OpenSSL or mbed TLS 2.18+.\n\nCompatibility with OpenSSL in FIPS mode\n    OpenVPN will now work with OpenSSL in FIPS mode. Note, no effort\n    has been made to check or implement all the\n    requirements/recommendation of FIPS 140-2. This just allows OpenVPN\n    to be run on a system that be configured OpenSSL in FIPS mode.\n\n``mlock`` will now check if enough memlock-able memory has been reserved,\n    and if less than 100MB RAM are available, use setrlimit() to upgrade\n    the limit.  See Trac #1390.  Not available on OpenSolaris.\n\nCertificate pinning/verify peer fingerprint\n    The ``--peer-fingerprint`` option has been introduced to give users an\n    easy to use alternative to the ``tls-verify`` for matching the\n    fingerprint of the peer. The option takes use a number of allowed\n    SHA256 certificate fingerprints.\n\n    See the man page section \"Small OpenVPN setup with peer-fingerprint\"\n    for a tutorial on how to use this feature. This is also available online\n    under https://github.com/openvpn/openvpn/blob/master/doc/man-sections/example-fingerprint.rst\n\nTLS mode with self-signed certificates\n    When ``--peer-fingerprint`` is used, the ``--ca`` and ``--capath`` option\n    become optional. This allows for small OpenVPN setups without setting up\n    a PKI with Easy-RSA or similar software.\n\nDeferred auth support for scripts\n    The ``--auth-user-pass-verify`` script supports now deferred authentication.\n\nPending auth support for plugins and scripts\n    Both auth plugin and script can now signal pending authentication to\n    the client when using deferred authentication. The new ``client-crresponse``\n    script option and ``OPENVPN_PLUGIN_CLIENT_CRRESPONSE`` plugin function can\n    be used to parse a client response to a ``CR_TEXT`` two factor challenge.\n\n    See ``sample/sample-scripts/totpauth.py`` for an example.\n\nCompatibility mode (``--compat-mode``)\n    The modernisation of defaults can impact the compatibility of OpenVPN 2.6.0\n    with older peers. The options ``--compat-mode`` allows UIs to provide users\n    with an easy way to still connect to older servers.\n\nOpenSSL 3.0 support\n    OpenSSL 3.0 has been added. Most of OpenSSL 3.0 changes are not user visible but\n    improve general compatibility with OpenSSL 3.0. ``--tls-cert-profile insecure``\n    has been added to allow selecting the lowest OpenSSL security level (not\n    recommended, use only if you must). OpenSSL 3.0 no longer supports the Blowfish\n    (and other deprecated) algorithm by default and the new option ``--providers``\n    allows loading the legacy provider to renable these algorithms.\n\nOptional ciphers in ``--data-ciphers``\n    Ciphers in ``--data-ciphers`` can now be prefixed with a ``?`` to mark\n    those as optional and only use them if the SSL library supports them.\n\n\nImproved ``--mssfix`` and ``--fragment`` calculation\n    The ``--mssfix`` and ``--fragment`` options now allow an optional :code:`mtu`\n    parameter to specify that different overhead for IPv4/IPv6 should taken into\n    account and the resulting size is specified as the total size of the VPN packets\n    including IP and UDP headers.\n\nCookie based handshake for UDP server\n    Instead of allocating a connection for each client on the initial packet\n    OpenVPN server will now use an HMAC based cookie as its session id. This\n    way the server can verify it on completing the handshake without keeping\n    state. This eliminates the amplification and resource exhaustion attacks.\n    For tls-crypt-v2 clients, this requires OpenVPN 2.6 clients or later\n    because the client needs to resend its client key on completing the hand\n    shake. The tls-crypt-v2 option allows controlling if older clients are\n    accepted.\n\n    By default the rate of initial packet responses is limited to 100 per 10s\n    interval to avoid OpenVPN servers being abused in reflection attacks\n    (see ``--connect-freq-initial``).\n\nData channel offloading with ovpn-dco\n    2.6.0+ implements support for data-channel offloading where the data packets\n    are directly processed and forwarded in kernel space thanks to the ovpn-dco\n    kernel module. The userspace openvpn program acts purely as a control plane\n    application. Note that DCO will use DATA_V2 packets in P2P mode, therefore,\n    this implies that peers must be running 2.6.0+ in order to have P2P-NCP\n    which brings DATA_V2 packet support.\n\nSession timeout\n    It is now possible to terminate a session (or all) after a specified amount\n    of seconds has passed session commencement. This behaviour can be configured\n    using ``--session-timeout``. This option can be configured on the server, on\n    the client or can also be pushed.\n\nInline auth username and password\n    Username and password can now be specified inline in the configuration file\n    within the <auth-user-pass></auth-user-pass> tags. If the password is\n    missing OpenVPN will prompt for input via stdin. This applies to inline'd\n    http-proxy-user-pass too.\n\nTun MTU can be pushed\n    The  client can now also dynamically configure its MTU and the server\n    will try to push the client MTU when the client supports it. The\n    directive ``--tun-mtu-max`` has been introduced to increase the maximum\n    pushable MTU size (defaults to 1600).\n\nDynamic TLS Crypt\n    When both peers are OpenVPN 2.6.1+, OpenVPN will dynamically create\n    a tls-crypt key that is used for renegotiation. This ensure that only the\n    previously authenticated peer can do trigger renegotiation and complete\n    renegotiations.\n\nImproved control channel packet size control (``max-packet-size``)\n    The size of control channel is no longer tied to\n    ``--link-mtu``/``--tun-mtu`` and can be set using ``--max-packet-size``.\n    Sending large control channel frames is also optimised by allowing 6\n    outstanding packets instead of just 4. ``max-packet-size`` will also set\n    ``mssfix`` to try to limit data-channel packets as well.\n\nDeprecated features\n-------------------\n``inetd`` has been removed\n    This was a very limited and not-well-tested way to run OpenVPN, on TCP\n    and TAP mode only.\n\n``verify-hash`` has been deprecated\n    This option has very limited usefulness and should be replaced by either\n    a better ``--ca`` configuration or with a ``--tls-verify`` script.\n\n``secret`` has been deprecated\n    static key mode (non-TLS) is no longer considered \"good and secure enough\"\n    for today's requirements.  Use TLS mode instead.  If deploying a PKI CA\n    is considered \"too complicated\", using ``--peer-fingerprint`` makes\n    TLS mode about as easy as using ``--secret``.\n\n``ncp-disable`` has been removed\n    This option mainly served a role as debug option when NCP was first\n    introduced. It should now no longer be necessary.\n\nTLS 1.0 and 1.1 are deprecated\n    ``tls-version-min`` is set to 1.2 by default.  OpenVPN 2.6.0 defaults\n    to a minimum TLS version of 1.2 as TLS 1.0 and 1.1 should be generally\n    avoided. Note that OpenVPN versions older than 2.3.7 use TLS 1.0 only.\n\n``--cipher`` argument is no longer appended to ``--data-ciphers``\n    by default. Data cipher negotiation has been introduced in 2.4.0\n    and been significantly improved in 2.5.0. The implicit fallback\n    to the cipher specified in ``--cipher`` has been removed.\n    Effectively, ``--cipher`` is a no-op in TLS mode now, and will\n    only have an effect in pre-shared-key mode (``--secret``).\n    From now on ``--cipher`` should not be used in new configurations\n    for TLS mode.\n    Should backwards compatibility with older OpenVPN peers be\n    required, please see the ``--compat-mode`` instead.\n\n``--prng`` has beeen removed\n    OpenVPN used to implement its own PRNG based on a hash. However implementing\n    a PRNG is better left to a crypto library. So we use the PRNG\n    mbed TLS or OpenSSL now.\n\n``--keysize`` has been removed\n    The ``--keysize`` option was only useful to change the key length when using the\n    BF, CAST6 or RC2 ciphers. For all other ciphers the key size is fixed with the\n    chosen cipher. As OpenVPN v2.6 no longer supports any of these variable length\n    ciphers, this option was removed as well to avoid confusion.\n\nCompression no longer enabled by default\n    Unless an explicit compression option is specified in the configuration,\n    ``--allow-compression`` defaults to ``no`` in OpeNVPN 2.6.0.\n    By default, OpenVPN 2.5 still allowed a server to enable compression by\n    pushing compression related options.\n\nPF (Packet Filtering) support has been removed\n   The built-in PF functionality has been removed from the code base. This\n   feature wasn't really easy to use and was long unmaintained.\n   This implies that also ``--management-client-pf`` and any other compile\n   time or run time related option do not exist any longer.\n\nOption conflict checking is being deprecated and phased out\n    The static option checking (OCC) is no longer useful in typical setups\n    that negotiate most connection parameters. The ``--opt-verify`` and\n    ``--occ-disable`` options are deprecated, and the configure option\n    ``--enable-strict-options`` has been removed. Logging of mismatched\n    options has been moved to debug logging (verb 7).\n\nUser-visible Changes\n--------------------\n- CHACHA20-POLY1305 is included in the default of ``--data-ciphers`` when available.\n- Option ``--prng`` is ignored as we rely on the SSL library random number generator.\n- Option ``--nobind`` is default when ``--client`` or ``--pull`` is used in the configuration\n- :code:`link_mtu` parameter is removed from environment or replaced with 0 when scripts are\n  called with parameters. This parameter is unreliable and no longer internally calculated.\n\n- control channel packet maximum size is no longer influenced by\n  ``--link-mtu``/``--tun-mtu`` and must be set by ``--max-packet-size`` now.\n  The default is 1250 for the control channel size.\n\n- In point-to-point OpenVPN setups (no ``--server``), using\n  ``--explict-exit-notiy`` on one end would terminate the other side at\n  session end.  This is considered a no longer useful default and has\n  been changed to \"restart on reception of explicit-exit-notify message\".\n  If the old behaviour is still desired, ``--remap-usr1 SIGTERM`` can be used.\n\n- FreeBSD tun interfaces with ``--topology subnet`` are now put into real\n  subnet mode (IFF_BROADCAST instead of IFF_POINTOPOINT) - this might upset\n  software that enumerates interfaces, looking for \"broadcast capable?\" and\n  expecting certain results.  Normal uses should not see any difference.\n\n- The default configurations will no longer allow connections to OpenVPN 2.3.x\n  peer or earlier, use the new ``--compat-mode`` option if you need\n  compatibility with older versions. See the manual page on the\n  ``--compat-mode`` for details.\n\n- The ``client-pending-auth`` management command now requires also the\n  key id. The management version has been changed to 5 to indicate this change.\n\n- (OpenVPN 2.6.2) A client will now refuse a connection if pushed compression\n  settings will contradict the setting of allow-compression as this almost\n  always results in a non-working connection.\n\n- The \"kill\" by addr management command now requires also the protocol\n  as string e.g. \"udp\", \"tcp\".\n\nCommon errors with OpenSSL 3.0 and OpenVPN 2.6\n----------------------------------------------\nBoth OpenVPN 2.6 and OpenSSL 3.0 tighten the security considerable, so some\nconfiguration will no longer work. This section will cover the most common\ncauses and error message we have seen and explain their reason and temporary\nworkarounds. You should fix the underlying problems as soon as possible since\nthese workaround are not secure and will eventually stop working in a future\nupdate.\n\n- weak SHA1 or MD5 signature on certificates\n\n  This will happen on either loading of certificates or on connection\n  to a server::\n\n      OpenSSL: error:0A00018E:SSL routines::ca md too weak\n      Cannot load certificate file cert.crt\n      Exiting due to fatal error\n\n  OpenSSL 3.0 no longer allows weak signatures on certificates. You can\n  downgrade your security to allow them by using ``--tls-cert-profile insecure``\n  but should replace/regenerate these certificates as soon as possible.\n\n\n- 1024 bit RSA certificates, 1024 bit DH parameters, other weak keys\n\n  This happens if you use private keys or other cryptographic material that\n  does not meet today's cryptographic standards anymore. Messages are similar\n  to::\n\n      OpenSSL: error:0A00018F:SSL routines::ee key too small\n      OpenSSL: error:1408518A:SSL routines:ssl3_ctx_ctrl:dh key too small\n\n  DH parameters (``--dh``) can be regenerated with ``openssl dhparam 2048``.\n  For other cryptographic keys, these keys and certificates need to be\n  regenerated. TLS Security level can be temporarily lowered with\n  ``--tls-cert-profile legacy`` or even ``--tls-cert-profile insecure``.\n\n- Connecting to a OpenVPN 2.3.x server or allowing OpenVPN 2.3.x or earlier\n  clients\n\n  This will normally result in messages like::\n\n     OPTIONS ERROR: failed to negotiate cipher with server.  Add the server's cipher ('AES-128-CBC') to --data-ciphers (currently 'AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305') if you want to connect to this server.\n\n     or\n\n     client/127.0.0.1:49954 SENT CONTROL [client]: 'AUTH_FAILED,Data channel cipher negotiation failed (no shared cipher)' (status=1)\n\n  You can manually add the missing cipher to the ``--data-ciphers``. The\n  standard ciphers should be included as well, e.g.\n  ``--data-ciphers AES-256-GCM:AES-128-GCM:?Chacha20-Poly1305:?AES-128-CBC``.\n  You can also use the ``--compat-mode`` option. Note that these message may\n  also indicate other cipher configuration problems. See the data channel\n  cipher negotiation manual section for more details. (Available online under\n  https://github.com/OpenVPN/openvpn/blob/master/doc/man-sections/cipher-negotiation.rst)\n\n- Use of a legacy or deprecated cipher (e.g. 64bit block ciphers)\n\n  OpenSSL 3.0 no longer supports a number of insecure and outdated ciphers in\n  its default configuration. Some of these ciphers are known to be vulnerable (SWEET32 attack).\n\n  This will typically manifest itself in messages like::\n\n      OpenSSL: error:0308010C:digital envelope routines::unsupported\n      Cipher algorithm 'BF-CBC' not found\n      Unsupported cipher in --data-ciphers: BF-CBC\n\n  If your OpenSSL distribution comes with the legacy provider (see\n  also ``man OSSL_PROVIDER-legacy``), you can load it with\n  ``--providers legacy default``.  This will re-enable the old algorithms.\n\n- OpenVPN version not supporting TLS 1.2 or later\n\n  The default in OpenVPN 2.6 and also in many distributions is now TLS 1.2 or\n  later. Connecting to a peer that does not support this will results in\n  messages like::\n\n    TLS error: Unsupported protocol. This typically indicates that client and\n    server have no common TLS version enabled. This can be caused by mismatched\n    tls-version-min and tls-version-max options on client and server. If your\n    OpenVPN client is between v2.3.6 and v2.3.2 try adding tls-version-min 1.0\n    to the client configuration to use TLS 1.0+ instead of TLS 1.0 only\n    OpenSSL: error:0A000102:SSL routines::unsupported protocol\n\n  This can be an OpenVPN 2.3.6 or earlier version. ``compat-version 2.3.0`` will\n  enable TLS 1.0 support if supported by the OpenSSL distribution. Note that\n  on some Linux distributions enabling TLS 1.1 or 1.0 is not possible.\n\n\n\nOverview of changes in 2.5\n==========================\n\nNew features\n------------\nClient-specific tls-crypt keys (``--tls-crypt-v2``)\n    ``tls-crypt-v2`` adds the ability to supply each client with a unique\n    tls-crypt key.  This allows large organisations and VPN providers to profit\n    from the same DoS and TLS stack protection that small deployments can\n    already achieve using ``tls-auth`` or ``tls-crypt``.\n\nChaCha20-Poly1305 cipher support\n    Added support for using the ChaCha20-Poly1305 cipher in the OpenVPN data\n    channel.\n\nImproved Data channel cipher negotiation\n    The option ``ncp-ciphers`` has been renamed to ``data-ciphers``.\n    The old name is still accepted. The change in name signals that\n    ``data-ciphers`` is the preferred way to configure data channel\n    ciphers and the data prefix is chosen to avoid the ambiguity that\n    exists with ``--cipher`` for the data cipher and ``tls-cipher``\n    for the TLS ciphers.\n\n    OpenVPN clients will now signal all supported ciphers from the\n    ``data-ciphers`` option to the server via ``IV_CIPHERS``. OpenVPN\n    servers will select the first common cipher from the ``data-ciphers``\n    list instead of blindly pushing the first cipher of the list. This\n    allows to use a configuration like\n    ``data-ciphers ChaCha20-Poly1305:AES-256-GCM`` on the server that\n    prefers ChaCha20-Poly1305 but uses it only if the client supports it.\n\n    See the data channel negotiation section in the manual for more details.\n\nRemoval of BF-CBC support in default configuration:\n    By default OpenVPN 2.5 will only accept AES-256-GCM and AES-128-GCM as\n    data ciphers. OpenVPN 2.4 allows AES-256-GCM,AES-128-GCM and BF-CBC when\n    no --cipher and --ncp-ciphers options are present. Accepting BF-CBC can be\n    enabled by adding\n\n        data-ciphers AES-256-GCM:AES-128-GCM:BF-CBC\n\n    and when you need to support very old peers also\n\n        data-ciphers-fallback BF-CBC\n\n    To offer backwards compatibility with older configs an *explicit*\n\n        cipher BF-CBC\n\n    in the configuration will be automatically translated into adding BF-CBC\n    to the data-ciphers option and setting data-ciphers-fallback to BF-CBC\n    (as in the example commands above). We strongly recommend to switching\n    away from BF-CBC to a more secure cipher.\n\nAsynchronous (deferred) authentication support for auth-pam plugin.\n    See src/plugins/auth-pam/README.auth-pam for details.\n\nDeferred client-connect\n    The ``--client-connect`` option and the connect plugin API allow\n    asynchronous/deferred return of the configuration file in the same way\n    as the auth-plugin.\n\nFaster connection setup\n    A client will signal in the ``IV_PROTO`` variable that it is in pull\n    mode. This allows the server to push the configuration options to\n    the client without waiting for a ``PULL_REQUEST`` message. The feature\n    is automatically enabled if both client and server support it and\n    significantly reduces the connection setup time by avoiding one\n    extra packet round-trip and 1s of internal event delays.\n\nNetlink support\n    On Linux, if configured without ``--enable-iproute2``, configuring IP\n    addresses and adding/removing routes is now done via the netlink(3)\n    kernel interface.  This is much faster than calling ``ifconfig`` or\n    ``route`` and also enables OpenVPN to run with less privileges.\n\n    If configured with --enable-iproute2, the ``ip`` command is used\n    (as in 2.4).  Support for ``ifconfig`` and ``route`` is gone.\n\nWintun support\n    On Windows, OpenVPN can now use ``wintun`` devices.  They are faster\n    than the traditional ``tap9`` tun/tap devices, but do not provide\n    ``--dev tap`` mode - so the official installers contain both.  To use\n    a wintun device, add ``--windows-driver wintun`` to your config\n    (and use of the interactive service is required as wintun needs\n    SYSTEM privileges to enable access).\n\nIPv6-only operation\n    It is now possible to have only IPv6 addresses inside the VPN tunnel,\n    and IPv6-only address pools (2.4 always required IPv4 config/pools\n    and IPv6 was the \"optional extra\").\n\nImproved Windows 10 detection\n    Correctly log OS on Windows 10 now.\n\nLinux VRF support\n    Using the new ``--bind-dev`` option, the OpenVPN outside socket can\n    now be put into a Linux VRF.  See the \"Virtual Routing and Forwarding\"\n    documentation in the man page.\n\nTLS 1.3 support\n    TLS 1.3 support has been added to OpenVPN.  Currently, this requires\n    OpenSSL 1.1.1+.\n    The options ``--tls-ciphersuites`` and ``--tls-groups`` have been\n    added to fine tune TLS protocol options.  Most of the improvements\n    were also backported to OpenVPN 2.4 as part of the maintainance\n    releases.\n\nSupport setting DHCP search domain\n    A new option ``--dhcp-option DOMAIN-SEARCH my.example.com`` has been\n    defined, and Windows support for it is implemented (tun/tap only, no\n    wintun support yet).  Other platforms need to support this via ``--up``\n    script (Linux) or GUI (OSX/Tunnelblick).\n\nper-client changing of ``--data-ciphers`` or ``data-ciphers-fallback``\n    from client-connect script/dir (NOTE: this only changes preference of\n    ciphers for NCP, but can not override what the client announces as\n    \"willing to accept\")\n\nHandle setting of tun/tap interface MTU on Windows\n    If IPv6 is in use, MTU must be >= 1280 (Windows enforces IETF requirements)\n\nAdd support for OpenSSL engines to access private key material (like TPM).\n\nHMAC based auth-token support\n    The ``--auth-gen-token`` support has been improved and now generates HMAC\n    based user token. If the optional ``--auth-gen-token-secret`` option is\n    used clients will be able to seamlessly reconnect to a different server\n    using the same secret file or to the same server after a server restart.\n\nImproved support for pending authentication\n    The protocol has been enhanced to be able to signal that\n    the authentication should use a secondary authentication\n    via web (like SAML) or a two factor authentication without\n    disconnecting the OpenVPN session with AUTH_FAILED. The\n    session will instead be stay in a authenticated state and\n    wait for the second factor authentication to complete.\n\n    This feature currently requires usage of the managent interface\n    on both client and server side. See the ``management-notes.txt``\n    ``client-pending-auth`` and ``cr-response`` commands for more\n    details.\n\nVLAN support\n    OpenVPN servers in TAP mode can now use 802.1q tagged VLANs\n    on the TAP interface to separate clients into different groups\n    that can then be handled differently (different subnets / DHCP,\n    firewall zones, ...) further down the network.  See the new\n    options ``--vlan-tagging``, ``--vlan-accept``, ``--vlan-pvid``.\n\n    802.1q tagging on the client side TAP interface is not handled\n    today (= tags are just forwarded transparently to the server).\n\nSupport building of .msi installers for Windows\n\nAllow unicode search string in ``--cryptoapicert`` option (Windows)\n\nSupport IPv4 configs with /31 netmasks now\n    (By no longer trying to configure ``broadcast x.x.x.x`` in\n    ifconfig calls, /31 support \"just works\")\n\nNew option ``--block-ipv6`` to reject all IPv6 packets (ICMPv6)\n    this is useful if the VPN service has no IPv6, but the clients\n    might have (LAN), to avoid client connections to IPv6-enabled\n    servers leaking \"around\" the IPv4-only VPN.\n\n``--ifconfig-ipv6`` and ``--ifconfig-ipv6-push`` will now accept\n    hostnames and do a DNS lookup to get the IPv6 address to use\n\n\nDeprecated features\n-------------------\nFor an up-to-date list of all deprecated options, see this wiki page:\nhttps://community.openvpn.net/openvpn/wiki/DeprecatedOptions\n\n- ``ncp-disable`` has been deprecated\n    With the improved and matured data channel cipher negotiation, the use\n    of ``ncp-disable`` should not be necessary anymore.\n\n- ``inetd`` has been deprecated\n  This is a very limited and not-well-tested way to run OpenVPN, on TCP\n  and TAP mode only, which complicates the code quite a bit for little gain.\n  To be removed in OpenVPN 2.6 (unless users protest).\n\n- ``no-iv`` has been removed\n  This option was made into a NOOP option with OpenVPN 2.4.  This has now\n  been completely removed.\n\n- ``--client-cert-not-required`` has been removed\n  This option will now cause server configurations to not start.  Use\n  ``--verify-client-cert none`` instead.\n\n- ``--ifconfig-pool-linear`` has been removed\n  This option is removed.  Use ``--topology p2p`` or ``--topology subnet``\n  instead.\n\n- ``--compress xxx`` is considered risky and is warned against, see below.\n\n- ``--key-method 1`` has been removed\n\n\nUser-visible Changes\n--------------------\n- If multiple connect handlers are used (client-connect, ccd, connect\n  plugin) and one of the handler succeeds but a subsequent fails, the\n  client-disconnect-script is now called immediately. Previously it\n  was called, when the VPN session was terminated.\n\n- Support for building with OpenSSL 1.0.1 has been removed. The minimum\n  supported OpenSSL version is now 1.0.2.\n\n- The GET_CONFIG management state is omitted if the server pushes\n  the client configuration almost immediately as result of the\n  faster connection setup feature.\n\n- ``--compress`` is nowadays considered risky, because attacks exist\n  leveraging compression-inside-crypto to reveal plaintext (VORACLE).  So\n  by default, ``--compress xxx`` will now accept incoming compressed\n  packets (for compatibility with peers that have not been upgraded yet),\n  but will not use compression outgoing packets.  This can be controlled with\n  the new option ``--allow-compression yes|no|asym``.\n\n- Stop changing ``--txlen`` aways from OS defaults unless explicitly specified\n  in config file.  OS defaults nowadays are actually larger then what we used\n  to configure, so our defaults sometimes caused packet drops = bad performance.\n\n- remove ``--writepid`` pid file on exit now\n\n- plugin-auth-pam now logs via OpenVPN logging method, no longer to stderr\n  (this means you'll have log messages in syslog or openvpn log file now)\n\n- use ISO 8601 time format for file based logging now (YYYY-MM-DD hh:mm:dd)\n  (syslog is not affected, nor is ``--machine-readable-output``)\n\n- ``--clr-verify`` now loads all CRLs if more than one CRL is in the same\n  file (OpenSSL backend only, mbedTLS always did that)\n\n- when ``--auth-user-pass file`` has no password, and the management interface\n  is active, query management interface (instead of trying console query,\n  which does not work on windows)\n\n- skip expired certificates in Windows certificate store (``--cryptoapicert``)\n\n- ``--socks-proxy`` + ``--proto udp*`` will now allways use IPv4, even if\n  IPv6 is requested and available.  Our SOCKS code does not handle IPv6+UDP,\n  and before that change it would just fail in non-obvious ways.\n\n- TCP listen() backlog queue is now set to 32 - this helps TCP servers that\n  receive lots of \"invalid\" connects by TCP port scanners\n\n- do no longer print OCC warnings (\"option mismatch\") about ``key-method``,\n  ``keydir``, ``tls-auth`` and ``cipher`` - these are either gone now, or\n  negotiated, and the warnings do not serve a useful purpose.\n\n- ``dhcp-option DNS`` and ``dhcp-option DNS6`` are now treated identically\n  (= both accept an IPv4 or IPv6 address for the nameserver)\n\n\nMaintainer-visible changes\n--------------------------\n- the man page is now in maintained in .rst format, so building the openvpn.8\n  manpage from a git checkout now requires python-docutils (if this is missing,\n  the manpage will not be built - which is not considered an error generally,\n  but for package builders or ``make distcheck`` it is).  Release tarballs\n  contain the openvpn.8 file, so unless some .rst is changed, doc-utils are\n  not needed for building.\n\n- OCC support can no longer be disabled\n\n- AEAD support is now required in the crypto library\n\n- ``--disable-server`` has been removed from configure (so it is no longer\n  possible to build a client-/p2p-only OpenVPN binary) - the saving in code\n  size no longer outweighs the extra maintenance effort.\n\n- ``--enable-iproute2`` will disable netlink(3) support, so maybe remove\n  that from package building configs (see above)\n\n- support building with MSVC 2019\n\n- cmocka based unit tests are now only run if cmocka is installed externally\n  (2.4 used to ship a local git submodule which was painful to maintain)\n\n- ``--disable-crypto`` configure option has been removed.  OpenVPN is now always\n  built with crypto support, which makes the code much easier to maintain.\n  This does not affect ``--cipher none`` to do a tunnel without encryption.\n\n- ``--disable-multi`` configure option has been removed\n\n\n\nOverview of changes in 2.4\n==========================\n\n\nNew features\n------------\nSeamless client IP/port floating\n    Added new packet format P_DATA_V2, which includes peer-id. If both the\n    server and client support it, the client sends all data packets in\n    the new format. When a data packet arrives, the server identifies peer\n    by peer-id. If peer's ip/port has changed, server assumes that\n    client has floated, verifies HMAC and updates ip/port in internal structs.\n    This allows the connection to be immediately restored, instead of requiring\n    a TLS handshake before the server accepts packets from the new client\n    ip/port.\n\nData channel cipher negotiation\n    Data channel ciphers (``--cipher``) are now by default negotiated.  If a\n    client advertises support for Negotiable Crypto Parameters (NCP), the\n    server will choose a cipher (by default AES-256-GCM) for the data channel,\n    and tell the client to use that cipher.  Data channel cipher negotiation\n    can be controlled using ``--ncp-ciphers`` and ``--ncp-disable``.\n\n    A more limited version also works in client-to-server and server-to-client\n    scenarios where one of the end points uses a v2.4 client or server and the\n    other side uses an older version.  In such scenarios the v2.4 side will\n    change to the ``--cipher`` set by the remote side, if permitted by by\n    ``--ncp-ciphers``.  For example, a v2.4 client with ``--cipher BF-CBC``\n    and ``ncp-ciphers AES-256-GCM:AES-256-CBC`` can connect to both a v2.3\n    server with ``cipher BF-CBC`` as well as a server with\n    ``cipher AES-256-CBC`` in its config.  The other way around, a v2.3 client\n    with either ``cipher BF-CBC`` or ``cipher AES-256-CBC`` can connect to a\n    v2.4 server with e.g. ``cipher BF-CBC`` and\n    ``ncp-ciphers AES-256-GCM:AES-256-CBC`` in its config.  For this to work\n    it requires that OpenVPN was built without disabling OCC support.\n\nAEAD (GCM) data channel cipher support\n    The data channel now supports AEAD ciphers (currently only GCM).  The AEAD\n    packet format has a smaller crypto overhead than the CBC packet format,\n    (e.g. 20 bytes per packet for AES-128-GCM instead of 36 bytes per packet\n    for AES-128-CBC + HMAC-SHA1).\n\nECDH key exchange\n    The TLS control channel now supports for elliptic curve diffie-hellmann\n    key exchange (ECDH).\n\nImproved Certificate Revocation List (CRL) processing\n    CRLs are now handled by the crypto library (OpenSSL or mbed TLS), instead\n    of inside OpenVPN itself.  The crypto library implementations are more\n    strict than the OpenVPN implementation was.  This might reject peer\n    certificates that would previously be accepted.  If this occurs, OpenVPN\n    will log the crypto library's error description.\n\nDualstack round-robin DNS client connect\n    Instead of only using the first address of each ``--remote`` OpenVPN\n    will now try all addresses (IPv6 and IPv4) of a ``--remote`` entry.\n\nSupport for providing IPv6 DNS servers\n    A new DHCP sub-option ``DNS6`` is added alongside with the already existing\n    ``DNS`` sub-option.  This is used to provide DNS resolvers available over\n    IPv6.  This may be pushed to clients where `` --up`` scripts and ``--plugin``\n    can act upon it through the ``foreign_option_<n>`` environment variables.\n\n    Support for the Windows client picking up this new sub-option is added,\n    however IPv6 DNS resolvers need to be configured via ``netsh`` which requires\n    administrator privileges unless the new interactive services on Windows is\n    being used.  If the interactive service is used, this service will execute\n    ``netsh`` in the background with the proper privileges.\n\nNew improved Windows Background service\n    The new OpenVPNService is based on openvpnserv2, a complete rewrite of the OpenVPN\n    service wrapper. It is intended for launching OpenVPN instances that should be\n    up at all times, instead of being manually launched by a user. OpenVPNService is\n    able to restart individual OpenVPN processes if they crash, and it also works\n    properly on recent Windows versions. OpenVPNServiceLegacy tends to work poorly,\n    if at all, on newer Windows versions (8+) and its use is not recommended.\n\nNew interactive Windows service\n    The installer starts OpenVPNServiceInteractive automatically and configures\n    it to start\tat system startup.\n\n    The interactive Windows service allows unprivileged users to start\n    OpenVPN connections in the global config directory (usually\n    C:\\\\Program Files\\\\OpenVPN\\\\config) using OpenVPN GUI without any\n    extra configuration.\n\n    Users who belong to the built-in Administrator group or to the\n    local \"OpenVPN Administrator\" group can also store configuration\n    files under %USERPROFILE%\\\\OpenVPN\\\\config for use with the\n    interactive service.\n\nredirect-gateway ipv6\n    OpenVPN has now feature parity between IPv4 and IPv6 for redirect\n    gateway including the handling of overlapping IPv6 routes with\n    IPv6 remote VPN server address.\n\nLZ4 Compression and pushable compression\n    Additionally to LZO compression OpenVPN now also supports LZ4 compression.\n    Compression options are now pushable from the server.\n\nFilter pulled options client-side: pull-filter\n    New option to explicitly allow or reject options pushed by the server.\n    May be used multiple times and is applied in the order specified.\n\nPer-client remove push options: push-remove\n    New option to remove options on a per-client basis from the \"push\" list\n    (more fine-grained than ``--push-reset``).\n\nHttp proxy password inside config file\n    Http proxy passwords can be specified with the inline file option\n    ``<http-proxy-user-pass>`` .. ``</http-proxy-user-pass>``\n\nWindows version detection\n    Windows version is detected, logged and possibly signalled to server\n    (IV_PLAT_VER=<nn> if ``--push-peer-info`` is set on client).\n\nAuthentication tokens\n    In situations where it is not suitable to save user passwords on the client,\n    OpenVPN has support for pushing a --auth-token since v2.3.  This option is\n    pushed from the server to the client with a token value to be used instead\n    of the users password.  For this to work, the authentication plug-in would\n    need to implement this support as well.  In OpenVPN 2.4 --auth-gen-token\n    is introduced, which will allow the OpenVPN server to generate a random\n    token and push it to the client without any changes to the authentication\n    modules.  When the clients need to re-authenticate the OpenVPN server will\n    do the authentication internally, instead of sending the re-authentication\n    request to the authentication module .  This feature is especially\n    useful in configurations which use One Time Password (OTP) authentication\n    schemes, as this allows the tunnel keys to be renegotiated regularly without\n    any need to supply new OTP codes.\n\nkeying-material-exporter\n    Keying Material Exporter [RFC-5705] allow additional keying material to be\n    derived from existing TLS channel.\n\nAndroid platform support\n    Support for running on Android using Android's VPNService API has been added.\n    See doc/android.txt for more details. This support is primarily used in\n    the OpenVPN for Android app (https://github.com/schwabe/ics-openvpn)\n\nAIX platform support\n    AIX platform support has been added. The support only includes tap\n    devices since AIX does not provide tun interface.\n\nControl channel encryption (``--tls-crypt``)\n    Use a pre-shared static key (like the ``--tls-auth`` key) to encrypt control\n    channel packets.  Provides more privacy, some obfuscation and poor-man's\n    post-quantum security.\n\nAsynchronous push reply\n    Plug-ins providing support for deferred authentication can benefit from a more\n    responsive authentication where the server sends PUSH_REPLY immediately once\n    the authentication result is ready, instead of waiting for the client to\n    to send PUSH_REQUEST once more.  This requires OpenVPN to be built with\n    ``./configure --enable-async-push``.  This is a compile-time only switch.\n\n\nDeprecated features\n-------------------\nFor an up-to-date list of all deprecated options, see this wiki page:\nhttps://community.openvpn.net/openvpn/wiki/DeprecatedOptions\n\n- ``--key-method 1`` is deprecated in OpenVPN 2.4 and will be removed in v2.5.\n  Migrate away from ``--key-method 1`` as soon as possible.  The recommended\n  approach is to remove the ``--key-method`` option from the configuration\n  files, OpenVPN will then use ``--key-method 2`` by default.  Note that this\n  requires changing the option in both the client and server side configs.\n\n- ``--tls-remote`` is removed in OpenVPN 2.4, as indicated in the v2.3\n  man-pages.  Similar functionality is provided via ``--verify-x509-name``,\n  which does the same job in a better way.\n\n- ``--compat-names`` and ``--no-name-remapping`` were deprecated in OpenVPN 2.3\n  and will be removed in v2.5.  All scripts and plug-ins depending on the old\n  non-standard X.509 subject formatting must be updated to the standardized\n  formatting.  See the man page for more information.\n\n- ``--no-iv`` is deprecated in OpenVPN 2.4 and will be removed in v2.5.\n\n- ``--keysize`` is deprecated in OpenVPN 2.4 and will be removed in v2.6\n  together with the support of ciphers with cipher block size less than\n  128-bits.\n\n- ``--comp-lzo`` is deprecated in OpenVPN 2.4.  Use ``--compress`` instead.\n\n- ``--ifconfig-pool-linear`` has been deprecated since OpenVPN 2.1 and will be\n  removed in v2.5.  Use ``--topology p2p`` instead.\n\n- ``--client-cert-not-required`` is deprecated in OpenVPN 2.4 and will be removed\n  in v2.5.  Use ``--verify-client-cert none`` for a functional equivalent.\n\n- ``--ns-cert-type`` is deprecated in OpenVPN 2.3.18 and v2.4.  It will be removed\n  in v2.5.  Use the far better ``--remote-cert-tls`` option which replaces this\n  feature.\n\n\nUser-visible Changes\n--------------------\n- When using ciphers with cipher blocks less than 128-bits,\n  OpenVPN will complain loudly if the configuration uses ciphers considered\n  weak, such as the SWEET32 attack vector.  In such scenarios, OpenVPN will by\n  default renegotiate for each 64MB of transported data (``--reneg-bytes``).\n  This renegotiation can be disabled, but is HIGHLY DISCOURAGED.\n\n- For certificate DNs with duplicate fields, e.g. \"OU=one,OU=two\", both fields\n  are now exported to the environment, where each second and later occurrence\n  of a field get _$N appended to it's field name, starting at N=1.  For the\n  example above, that would result in e.g. X509_0_OU=one, X509_0_OU_1=two.\n  Note that this breaks setups that rely on the fact that OpenVPN would\n  previously (incorrectly) only export the last occurrence of a field.\n\n- ``proto udp`` and ``proto tcp`` now use both IPv4 and IPv6. The new\n  options ``proto udp4`` and ``proto tcp4`` use IPv4 only.\n\n- ``--sndbuf`` and ``--recvbuf`` default now to OS defaults instead of 64k\n\n- OpenVPN exits with an error if an option has extra parameters;\n  previously they were silently ignored\n\n- ``--tls-auth`` always requires OpenVPN static key files and will no\n  longer work with free form files\n\n- ``--proto udp6/tcp6`` in server mode will now try to always listen to\n  both IPv4 and IPv6 on platforms that allow it. Use ``--bind ipv6only``\n  to explicitly listen only on IPv6.\n\n- Removed ``--enable-password-save`` from configure. This option is now\n  always enabled.\n\n- Stricter default TLS cipher list (override with ``--tls-cipher``), that now\n  also disables:\n\n  * Non-ephemeral key exchange using static (EC)DH keys\n  * DSS private keys\n\n- mbed TLS builds: changed the tls_digest_N values exported to the script\n  environment to be equal to the ones exported by OpenSSL builds, namely\n  the certificate fingerprint (was the hash of the 'to be signed' data).\n\n- mbed TLS builds: minimum RSA key size is now 2048 bits.  Shorter keys will\n  not be accepted, both local and from the peer.\n\n- ``--connect-timeout`` now specifies the timeout until the first TLS packet\n  is received (identical to ``--server-poll-timeout``) and this timeout now\n  includes the removed socks proxy timeout and http proxy timeout.\n\n  In ``--static`` mode ``connect-timeout`` specifies the timeout for TCP and\n  proxy connection establishment\n\n- ``--connect-retry-max`` now specifies the maximum number of unsuccessful\n  attempts of each remote/connection entry before exiting.\n\n- ``--http-proxy-timeout`` and the static non-changeable socks timeout (5s)\n  have been folded into a \"unified\" ``--connect-timeout`` which covers all\n  steps needed to connect to the server, up to the start of the TLS exchange.\n  The default value has been raised to 120s, to handle slow http/socks\n  proxies graciously.  The old \"fail TCP fast\" behaviour can be achieved by\n  adding \"``--connect-timeout 10``\" to the client config.\n\n- ``--http-proxy-retry`` and ``--sock-proxy-retry`` have been removed. Proxy connections\n  will now behave like regular connection entries and generate a USR1 on failure.\n\n- ``--connect-retry`` gets an optional second argument that specifies the maximum\n  time in seconds to wait between reconnection attempts when an exponential\n  backoff is triggered due to repeated retries. Default = 300 seconds.\n\n- Data channel cipher negotiation (see New features section) can override\n  ciphers configured in the config file.  Use ``--ncp-disable`` if you do not want\n  this behavior.\n\n- All tun devices on all platforms are always considered to be IPv6\n  capable. The ``--tun-ipv6`` option is ignored (behaves like it is always\n  on).\n\n- On the client side recursively routed packets, which have the same destination\n  as the VPN server, are dropped. This can be disabled with\n  --allow-recursive-routing option.\n\n- On Windows, when the ``--register-dns`` option is set, OpenVPN no longer\n  restarts the ``dnscache`` service - this had unwanted side effects, and\n  seems to be no longer necessary with currently supported Windows versions.\n\n- If no flags are given, and the interactive Windows service is used, \"def1\"\n  is implicitly set (because \"delete and later reinstall the existing\n  default route\" does not work well here).  If not using the service,\n  the old behaviour is kept.\n\n- OpenVPN now reloads a CRL only if the modication time or file size has\n  changed, instead of for each new connection.  This reduces the connection\n  setup time, in particular when using large CRLs.\n\n- OpenVPN now ships with more up-to-date systemd unit files which take advantage\n  of the improved service management as well as some hardening steps.  The\n  configuration files are picked up from the /etc/openvpn/server/ and\n  /etc/openvpn/client/ directories (depending on unit file).  This also avoids\n  these new unit files and how they work to collide with older pre-existing\n  unit files.\n\n- Using ``--no-iv`` (which is generally not a recommended setup) will\n  require explicitly disabling NCP with ``--disable-ncp``.  This is\n  intentional because NCP will by default use AES-GCM, which requires\n  an IV - so we want users of that option to consciously reconsider.\n\n\nMaintainer-visible changes\n--------------------------\n- OpenVPN no longer supports building with crypto support, but without TLS\n  support.  As a consequence, OPENSSL_CRYPTO_{CFLAGS,LIBS} and\n  OPENSSL_SSL_{CFLAGS,LIBS} have been merged into OPENSSL_{CFLAGS,LIBS}.  This\n  is particularly relevant for maintainers who build their own OpenSSL library,\n  e.g. when cross-compiling.\n\n- Linux distributions using systemd is highly encouraged to ship these new unit\n  files instead of older ones, to provide a unified behaviour across systemd\n  based Linux distributions.\n\n- With OpenVPN 2.4, the project has moved over to depend on and actively use\n  the official C99 standard (-std=c99).  This may fail on some older compiler/libc\n  header combinations.  In most of these situations it is recommended to\n  use -std=gnu99 in CFLAGS.  This is known to be needed when doing\n  i386/i686 builds on RHEL5.\n\n\nVersion 2.4.5\n=============\n\nNew features\n------------\n- The new option ``--tls-cert-profile`` can be used to restrict the set of\n  allowed crypto algorithms in TLS certificates in mbed TLS builds.  The\n  default profile is 'legacy' for now, which allows SHA1+, RSA-1024+ and any\n  elliptic curve certificates.  The default will be changed to the 'preferred'\n  profile in the future, which requires SHA2+, RSA-2048+ and any curve.\n\n\nVersion 2.4.3\n=============\n\nNew features\n------------\n- Support building with OpenSSL 1.1 now (in addition to older versions)\n\n- On Win10, set low interface metric for TAP adapter when block-outside-dns\n  is in use, to make Windows prefer the TAP adapter for DNS queries\n  (avoiding large delays)\n\n\nSecurity\n--------\n- CVE-2017-7522: Fix ``--x509-track`` post-authentication remote DoS\n  A client could crash a v2.4+ mbedtls server, if that server uses the\n  ``--x509-track`` option and the client has a correct, signed and unrevoked\n  certificate that contains an embedded NUL in the certificate subject.\n  Discovered and reported to the OpenVPN security team by Guido Vranken.\n\n- CVE-2017-7521: Fix post-authentication remote-triggerable memory leaks\n  A client could cause a server to leak a few bytes each time it connects to the\n  server.  That can eventually cause the server to run out of memory, and thereby\n  causing the server process to terminate. Discovered and reported to the\n  OpenVPN security team by Guido Vranken.  (OpenSSL builds only.)\n\n- CVE-2017-7521: Fix a potential post-authentication remote code execution\n  attack on servers that use the ``--x509-username-field`` option with an X.509\n  extension field (option argument prefixed with ``ext:``).  A client that can\n  cause a server to run out-of-memory (see above) might be able to cause the\n  server to double free, which in turn might lead to remote code execution.\n  Discovered and reported to the OpenVPN security team by Guido Vranken.\n  (OpenSSL builds only.)\n\n- CVE-2017-7520: Pre-authentication remote crash/information disclosure for\n  clients. If clients use a HTTP proxy with NTLM authentication (i.e.\n  ``--http-proxy <server> <port> [<authfile>|'auto'|'auto-nct'] ntlm2``),\n  a man-in-the-middle attacker between the client and the proxy can cause\n  the client to crash or disclose at most 96 bytes of stack memory. The\n  disclosed stack memory is likely to contain the proxy password. If the\n  proxy password is not reused, this is unlikely to compromise the security\n  of the OpenVPN tunnel itself.  Clients who do not use the ``--http-proxy``\n  option with ntlm2 authentication are not affected.\n\n- CVE-2017-7508: Fix remotely-triggerable ASSERT() on malformed IPv6 packet.\n  This can be used to remotely shutdown an openvpn server or client, if\n  IPv6 and ``--mssfix`` are enabled and the IPv6 networks used inside the VPN\n  are known.\n\n- Fix null-pointer dereference when talking to a malicious http proxy\n  that returns a malformed ``Proxy-Authenticate:`` headers for digest auth.\n\n- Fix overflow check for long ``--tls-cipher`` option\n\n- Windows: Pass correct buffer size to ``GetModuleFileNameW()``\n  (OSTIF/Quarkslabs audit, finding 5.6)\n\n\nUser-visible Changes\n--------------------\n- ``--verify-hash`` can now take an optional flag which changes the hashing\n  algorithm. It can be either SHA1 or SHA256.  The default if not provided is\n  SHA1 to preserve backwards compatibility with existing configurations.\n\n- Restrict the supported ``--x509-username-field`` extension fields to subjectAltName\n  and issuerAltName.  Other extensions probably didn't work anyway, and would\n  cause OpenVPN to crash when a client connects.\n\n\nBugfixes\n--------\n- Fix fingerprint calculation in mbed TLS builds.  This means that mbed TLS users\n  of OpenVPN 2.4.0, v2.4.1 and v2.4.2 that rely on the values of the\n  ``tls_digest_*`` env vars, or that use ``--verify-hash`` will have to change\n  the fingerprint values they check against.  The security impact of the\n  incorrect calculation is very minimal; the last few bytes (max 4, typically\n  4) are not verified by the fingerprint.  We expect no real-world impact,\n  because users that used this feature before will notice that it has suddenly\n  stopped working, and users that didn't will notice that connection setup\n  fails if they specify correct fingerprints.\n\n- Fix edge case with NCP when the server sends an empty PUSH_REPLY message\n  back, and the client would not initialize it's data channel crypto layer\n  properly (trac #903)\n\n- Fix SIGSEGV on unaligned buffer access on OpenBSD/Sparc64\n\n- Fix TCP_NODELAY on OpenBSD\n\n- Remove erroneous limitation on max number of args for ``--plugin``\n\n- Fix NCP behaviour on TLS reconnect (Server would not send a proper\n  \"cipher ...\" message back to the client, leading to client and server\n  using different ciphers) (trac #887)\n\n\nVersion 2.4.2\n=============\n\nBugfixes\n--------\n- Fix memory leak introduced in OpenVPN 2.4.1: if ``--remote-cert-tls`` is\n  used, we leaked some memory on each TLS (re)negotiation.\n\n\nSecurity\n--------\n- Fix a pre-authentication denial-of-service attack on both clients and\n  servers.  By sending a too-large control packet, OpenVPN 2.4.0 or v2.4.1 can\n  be forced to hit an ASSERT() and stop the process.  If ``--tls-auth`` or\n  ``--tls-crypt`` is used, only attackers that have the ``--tls-auth`` or\n  ``--tls-crypt`` key can mount an attack.\n  (OSTIF/Quarkslab audit finding 5.1, CVE-2017-7478)\n\n- Fix an authenticated remote DoS vulnerability that could be triggered by\n  causing a packet id roll over.  An attack is rather inefficient; a peer\n  would need to get us to send at least about 196 GB of data.\n  (OSTIF/Quarkslab audit finding 5.2, CVE-2017-7479)\n\n\nVersion 2.4.1\n=============\n- ``--remote-cert-ku`` now only requires the certificate to have at least the\n  bits set of one of the values in the supplied list, instead of requiring an\n  exact match to one of the values in the list.\n- ``--remote-cert-tls`` now only requires that a keyUsage is present in the\n  certificate, and leaves the verification of the value up to the crypto\n  library, which has more information (i.e. the key exchange method in use)\n  to verify that the keyUsage is correct.\n- ``--ns-cert-type`` is deprecated.  Use ``--remote-cert-tls`` instead.\n  The nsCertType x509 extension is very old, and barely used.\n  ``--remote-cert-tls`` uses the far more common keyUsage and extendedKeyUsage\n  extension instead.  Make sure your certificates carry these to be able to\n  use ``--remote-cert-tls``.\n\n"
  },
  {
    "path": "INSTALL",
    "content": "Installation instructions for OpenVPN, a Secure Tunneling Daemon\n\nCopyright (C) 2002-2022 OpenVPN Inc. This program is free software;\nyou can redistribute it and/or modify\nit under the terms of the GNU General Public License version 2\nas published by the Free Software Foundation.\n\n*************************************************************************\n\nQUICK START:\n\n  Unix:\n    ./configure && make && make install\n\n*************************************************************************\n\nTo download OpenVPN source code of releases, go to:\n\n    https://openvpn.net/community-downloads/\n\nOpenVPN releases are also available as Debian/RPM packages:\n\n    https://community.openvpn.net/openvpn/wiki/OpenvpnSoftwareRepos\n\nOpenVPN development versions can be found here:\n\n    https://github.com/OpenVPN/openvpn\n    https://gitlab.com/OpenVPN/openvpn\n    https://sourceforge.net/p/openvpn/openvpn/ci/master/tree/\n\nThey should all be in sync at any time.\n\nTo download easy-rsa go to:\n\n    https://github.com/OpenVPN/easy-rsa\n\nTo download tap-windows (NDIS 6) driver source code go to:\n\n    https://github.com/OpenVPN/tap-windows6\n\nTo download ovpn-dco Windows driver source code go to:\n\n    https://github.com/OpenVPN/ovpn-dco-win\n\nTo get the cross-compilation environment go to:\n\n    https://github.com/OpenVPN/openvpn-build\n\nFor step-by-step instructions with real-world examples see:\n\n    https://community.openvpn.net/openvpn/wiki/GettingStartedwithOVPN\n    https://community.openvpn.net/openvpn/wiki\n    https://openvpn.net/community-resources/\n\nAlso see the man page for more information.\n\n*************************************************************************\n\nFor a list of supported platforms and architectures, and for\ninstructions how to port OpenVPN to a yet-unsupported architecture,\nsee the file \"PORTS\".\n\n*************************************************************************\n\nSYSTEM REQUIREMENTS:\n  (1) TUN and/or TAP driver to allow user-space programs to control\n      a virtual point-to-point IP or Ethernet device.\n      See TUN/TAP Driver References section below for more info.\n  (2a) OpenSSL library, necessary for encryption, version 1.1.0 or higher\n      required, available from https://www.openssl.org/\n      or\n  (2b) mbed TLS library, an alternative for encryption, version 2.0 or higher\n      required, available from https://tls.mbed.org/\n  (3) on Linux, \"libnl-gen\" is required for kernel netlink support\n  (4) on Linux, \"libcap-ng\" is required for Linux capability handling\n\nOPTIONAL:\n  (5) LZO real-time compression library, required for link compression,\n      available from https://www.oberhumer.com/opensource/lzo/\n      (most supported operating systems have LZO in their installable\n      packages repository.  It might be necessary to add LZO_CFLAGS=\n      and LZO_LIBS= to the configure call to make it find the LZO pieces)\n  (6) LZ4 compression library\n\nOPTIONAL (for developers only):\n  (1) Autoconf 2.59 or higher\n      Automake 1.9 or higher\n      Libtool\n      Git\n  (2) cmocka test framework (https://cmocka.org)\n  (3) If using t_client.sh test framework, fping/fping6 is needed\n      Note: t_client.sh needs an external configured OpenVPN server.\n      See t_client.rc-sample for more info.\n\n*************************************************************************\n\nCHECK OUT SOURCE FROM SOURCE REPOSITORY:\n\n  Clone the repository:\n\n    git clone https://github.com/OpenVPN/openvpn\n    git clone https://gitlab.com/OpenVPN/openvpn\n    git clone git://openvpn.git.sourceforge.net/gitroot/openvpn/openvpn\n\n  Check out stable version:\n\n    git checkout release/2.6\n\n  Check out master (unstable) branch:\n\n    git checkout master\n\n\n*************************************************************************\n\nBUILD COMMANDS FROM TARBALL:\n\n\t./configure\n\tmake\n\tsudo make install\n\n*************************************************************************\n\nBUILD COMMANDS FROM SOURCE REPOSITORY CHECKOUT:\n\n\tautoreconf -i -v -f\n\t./configure\n\tmake\n\tsudo make install\n\n*************************************************************************\n\nBUILD A TARBALL FROM SOURCE REPOSITORY CHECKOUT:\n\n\tautoreconf -i -v -f\n\t./configure\n\tmake distcheck\n\n*************************************************************************\n\nTESTS (after BUILD):\n\nmake check (Run all tests below)\n\nTest Crypto:\n\n./openvpn --genkey secret key\n./openvpn --test-crypto --secret key\n\nTest SSL/TLS negotiations (runs for 2 minutes):\n\n./openvpn --config sample/sample-config-files/loopback-client (In one window)\n./openvpn --config sample/sample-config-files/loopback-server (Simultaneously in another window)\n\nFor more thorough client-server tests you can configure your own, private test\nenvironment. See tests/t_client.rc-sample for details.\n\nTo do the C unit tests, you need to have the \"cmocka\" test framework\ninstalled on your system.  More recent distributions already ship this\nas part of their packages/ports.  If your system does not have it,\nyou can install cmocka with these commands:\n\n  $ git clone https://git.cryptomilk.org/projects/cmocka.git\n  $ cd cmocka\n  $ mkdir build\n  $ cd build\n  $ cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Debug ..\n  $ make\n  $ sudo make install\n\n\n*************************************************************************\n\nOPTIONS for ./configure:\n\n  To get an overview of all the configure options, run \"./configure --help\"\n\nENVIRONMENT for ./configure:\n\n  For more fine-grained control on include + library paths for external\n  components etc., configure can be called with environment variables on\n  the command line, e.g.\n\n     ./configure OPENSSL_CFLAGS=\"-I/usr/local/include\" ...\n\n  these are also explained in \"./configure --help\", so not repeated here.\n\n*************************************************************************\n\nLinux distribution packaging:\n\nEach Linux distribution has their own way of doing packaging and their\nown set of guidelines of how proper packaging should be done.  It\nis therefore recommended to reach out to the Linux distributions you\nwant to have OpenVPN packaged for directly.  The OpenVPN project wants\nto focus more on the OpenVPN development and less on the packaging\nand how packaging is done in all various distributions.\n\nFor more details:\n\n* Arch Linux\n  https://www.archlinux.org/packages/?name=openvpn\n\n* Debian\n  https://packages.debian.org/search?keywords=openvpn&searchon=names\n  https://tracker.debian.org/pkg/openvpn\n\n* Fedora / Fedora EPEL (Red Hat Enterprise Linux/CentOS/Scientific Linux)\n  https://apps.fedoraproject.org/packages/openvpn/overview/\n  https://src.fedoraproject.org/rpms/openvpn\n\n* Gentoo\n  https://packages.gentoo.org/packages/net-vpn/openvpn\n  https://gitweb.gentoo.org/repo/gentoo.git/tree/net-vpn/openvpn\n\n* openSUSE\n  https://build.opensuse.org/package/show/network:vpn/openvpn\n\n* Ubuntu\n  https://packages.ubuntu.com/search?keywords=openvpn\n\nIn addition, the OpenVPN community provides best-effort package\nrepositories for CentOS/Fedora, Debian and Ubuntu:\nhttps://community.openvpn.net/openvpn/wiki/OpenvpnSoftwareRepos\n\n*************************************************************************\n\nTUN/TAP Driver References:\n\n* Linux 2.6 or higher (with integrated TUN/TAP driver):\n\n  (1) load driver:              modprobe tun\n  (2) enable routing:           echo 1 > /proc/sys/net/ipv4/ip_forward\n\n  Note that (1) needs to be done once per reboot.  If you install from RPM (see\n  above) and use the openvpn.init script, these steps are taken care of for you.\n\n* FreeBSD:\n\n  FreeBSD ships with the TUN/TAP driver, and the device nodes for tap0,\n  tap1, tap2, tap3, tun0, tun1, tun2 and tun3 are made by default.\n\n  On FreeBSD versions prior to 12.0-RELEASE, there were independent\n  TUN and TAP drivers, and the TAP driver needed to be loaded manually,\n  using the command:\n\n\t# kldload if_tap\n\n  For recent FreeBSD versions, TUN/TAP are integrated and always loaded.\n\n  FreeBSD 14 contains the ovpn(4) for kernel-level OpenVPN acceleration\n  (DCO) which will be used by OpenVPN 2.6 and up if available.\n\n* OpenBSD:\n\n  OpenBSD has dynamically created tun* devices so you only need\n  to create an empty /etc/hostname.tun0 (tun1, tun2 and so on) for each tun\n  you plan to use to create the device(s) at boot.\n\n* Solaris:\n\n  You need a TUN/TAP kernel driver for OpenVPN to work:\n\n    https://web.archive.org/web/20250504214754/http://www.whiteboard.ne.jp/~admin2/tuntap/\n\n* Haiku:\n\n   Haiku can't yet dynamically create TUN/TAP devices, so you need to manually\n   create one before running openvpn:\n\n     # ifconfig tun/0 up\n\n   A standard reference the dev as \"tun\" in your config is all that's needed\n   to use the tunnel device.\n\n* Windows\n\n  OpenVPN on Windows needs a TUN/TAP kernel driver to work. OpenVPN installers\n  include this driver, so installing it separately is not usually required.\n\n  Starting from Windows 10 2004 / Windows Server 2022, OpenVPN can use the\n  dco-win driver for kernel-level acceleration for OpenVPN client setups.\n  This driver is also included in the community-provided OpenVPN installers.\n\n*************************************************************************\n\nCAVEATS & BUGS:\n\n* See the bug tracker on https://github.com/OpenVPN/openvpn/issues\n  and the wiki on https://community.openvpn.net/wiki for more detailed\n  caveats on operating systems, and for open and resolved bug reports.\n* Note: We only recently switched to GitHub for reporting new issues,\n  old issues can be found at https://community.openvpn.net/openvpn/report\n"
  },
  {
    "path": "Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2010-2026 David Sommerseth <dazo@eurephia.org>\n#  Copyright (C) 2006-2012 Alon Bar-Lev <alon.barlev@gmail.com>\n#\n#  This program is free software; you can redistribute it and/or modify\n#  it under the terms of the GNU General Public License version 2\n#  as published by the Free Software Foundation.\n#\n#  This program is distributed in the hope that it will be useful,\n#  but WITHOUT ANY WARRANTY; without even the implied warranty of\n#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n#  GNU General Public License for more details.\n#\n#  You should have received a copy of the GNU General Public License along\n#  with this program; if not, see <https://www.gnu.org/licenses/>.\n#\n\nACLOCAL_AMFLAGS = -I m4\n\nMAINTAINERCLEANFILES = \\\n\tconfig.log config.status \\\n\t$(srcdir)/Makefile.in \\\n\t$(srcdir)/config.h.in $(srcdir)/config.h.in~ $(srcdir)/configure \\\n\t$(srcdir)/install-sh $(srcdir)/ltmain.sh $(srcdir)/missing \\\n\t$(srcdir)/m4/libtool.m4 $(srcdir)/m4/lt~obsolete.m4 \\\n\t$(srcdir)/m4/ltoptions.m4 $(srcdir)/m4/ltsugar.m4 \\\n\t$(srcdir)/m4/ltversion.m4 \\\n\t$(srcdir)/depcomp $(srcdir)/aclocal.m4 \\\n\t$(srcdir)/config.guess $(srcdir)/config.sub\n\nCLEANFILES = \\\n\tconfig-version.h tests/t_client.sh\n\nEXTRA_DIST = \\\n\tcontrib \\\n\tdebug \\\n\tltrc.inc \\\n\tCMakeLists.txt \\\n\tCMakePresets.json \\\n\tconfig.h.cmake.in \\\n\tforked-test-driver\n\n.PHONY: config-version.h doxygen\n\nif GIT_CHECKOUT\nBUILT_SOURCES = \\\n\tconfig-version.h\nendif\n\nSUBDIRS = distro include src sample doc tests\n\ndist_doc_DATA = \\\n\tREADME \\\n\tREADME.mbedtls \\\n\tChanges.rst \\\n\tCOPYRIGHT.GPL \\\n\tCOPYING\n\ndist_noinst_DATA = \\\n\t.gitignore \\\n\t.gitattributes \\\n\tCONTRIBUTING.rst \\\n\tPORTS \\\n\tREADME.cmake.md \\\n\tREADME.dco.md \\\n\tREADME.ec \\\n\tREADME.wolfssl\n\nconfig-version.h:\n\t@CONFIGURE_GIT_CHFILES=\"`$(GIT) -C \\\"$(top_srcdir)\\\" diff-files --name-status -r --ignore-submodules --quiet -- || echo \\\"+\\\"`\"; \\\n\tCONFIGURE_GIT_UNCOMMITTED=\"`$(GIT) -C \\\"$(top_srcdir)\\\" diff-index --cached  --quiet --ignore-submodules HEAD || echo \\\"*\\\"`\"; \\\n\tCONFIGURE_GIT_REVISION=\"`$(GIT) -C \\\"$(top_srcdir)\\\" rev-parse --symbolic-full-name HEAD | cut -d/ -f3-`/`$(GIT) -C \\\"$(top_srcdir)\\\" rev-parse --short=16 HEAD`\"; \\\n\techo \"#define CONFIGURE_GIT_REVISION \\\"$${CONFIGURE_GIT_REVISION}\\\"\" > config-version.h.tmp; \\\n\techo \"#define CONFIGURE_GIT_FLAGS \\\"$${CONFIGURE_GIT_CHFILES}$${CONFIGURE_GIT_UNCOMMITTED}\\\"\" >> config-version.h.tmp\n\n\t@if ! [ -f config-version.h ] || ! cmp -s config-version.h.tmp config-version.h; then \\\n\t\techo \"replacing config-version.h\"; \\\n\t\tmv config-version.h.tmp config-version.h; \\\n\telse \\\n\t\trm -f config-version.h.tmp; \\\n\tfi\n\ndoxygen:\n\t$(MAKE) -C doc/doxygen doxygen\n"
  },
  {
    "path": "NEWS",
    "content": ""
  },
  {
    "path": "PORTS",
    "content": "OpenVPN\nCopyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n\n  OpenVPN has been written to try to avoid features\n  that are not standardized well across different\n  OSes, so porting OpenVPN itself will probably be\n  straightforward if a tun or tap driver already exists.\n\n  Where special OS features are used, they are usually\n  bracketed with #ifdef HAVE_SOME_FUNCTION.\n\nPLATFORM STATUS:\n\nTier 1 platforms - actively tested for every source commit, across\nmultiple operating system versions\n\n  * Windows 7 and newer\n  * Windows Server 2012 and newer\n  * Linux\n  * FreeBSD\n  * macOS\n\nTier 2 platforms - it worked at some point, but is not actively tested\non \"latest OS, latest OS libraries\" so might break if larger changes\nare done on the platform side\n\n  * OpenBSD\n  * NetBSD\n  * DragonFly BSD\n  * Solaris\n  * AIX\n\nFor underlying CPU architecture, everything 32 bit or 64 bit (Intel, AMD,\nARM, PowerPC, SPARC*) should work fine.  16 bit Architectures are unlikely\nto work.\n\n\nPORTING GUIDELINE TO A NEW PLATFORM:\n\n  * Make sure that OpenSSL will build on your\n    platform.\n  * Make sure that a tun or tap virtual device\n    driver exists for your platform.  See\n    https://vtun.sourceforge.net/tun/ for examples\n    of tun and tap drivers that have been written\n    for Linux, Solaris, and FreeBSD.\n  * Make sure you have autoconf 2.50+ and\n    automake 1.6+.\n  * Edit configure.ac, adding platform specific\n    config code, and a TARGET_YOUROS define.\n  * Add platform-specific includes to syshead.h.\n  * Add an #ifdef TARGET_YOUROS to the do_ifconfig()\n    function in tun.c to generate a correct \"ifconfig\"\n    command for your platform.  Note that OpenVPN\n    determines the ifconfig path at ./configure time.\n  * Possibly add an ifconfig_order() variant for your OS so\n    openvpn knows whether to call ifconfig before\n    or after tun/tap dev open.\n  * Add an #ifdef TARGET_YOUROS block in tun.c and define\n    the open_tun, close_tun, read_tun, and write_tun\n    functions.  If your tun/tap virtual device is\n    sufficiently generic, you may be able to use the\n    default case.\n  * Add appropriate code to route.c to handle\n    the route command on your platform.  This\n    is necessary for the --route option to\n    work correctly.\n  * After you successfully build OpenVPN, run\n    the loopback tests as described in INSTALL.\n  * For the next test, confirm that the UDP socket\n    functionality is working independently of the\n    tun device, by doing something like:\n      ./openvpn --remote localhost --verb 9 --ping 1 --dev null\n  * Now try with --remote [a real host]\n  * Now try with a real tun/tap device, you will\n    need to figure out the appropriate ifconfig\n    command to use once openvpn has opened the tun/tap\n    device.\n  * Once you have simple tests working on the tun device,\n    try more complex tests such as using TLS mode.\n  * Stress test the link by doing ping -f across it.\n  * Make sure that packet fragmenting is happening\n    correctly by doing a ping -s 2000 or higher.\n  * Ensure that OpenVPN on your platform will talk\n    to OpenVPN on other platforms such as Linux.\n    Some tun/tap driver implementations will prepend\n    unnecessary stuff onto the datagram that must be\n    disabled with an explicit ioctl call if cross-platform\n    compatibility is to be preserved.  You can see some\n    examples of this in tun.c.\n  * Try the ultimate stress test which is --gremlin --reneg-sec 10\n    in TLS mode then do a flood ping across the tunnel\n    (ping -f remote-endpoint) in both directions and let\n    it run overnight.  --gremlin will induce massive\n    corruption and packet loss, but you win if you\n    wake up the next morning and both peers are still\n    running and occasionally even succeeding in their\n    attempted once-per-10-seconds TLS handshake. \n  * When it's working, submit your patch to\n    <openvpn-devel@lists.sourceforge.net>\n    and rejoice :)\n"
  },
  {
    "path": "README",
    "content": "OpenVPN -- A Secure tunneling daemon\n\nCopyright (C) 2002-2022 OpenVPN Inc. This program is free software;\nyou can redistribute it and/or modify\nit under the terms of the GNU General Public License version 2\nas published by the Free Software Foundation.\n\n*************************************************************************\n\nTo get the latest release of OpenVPN, go to:\n\n\thttps://openvpn.net/community-downloads/\n\nTo Build and Install,\n\n\ttar -zxf openvpn-<version>.tar.gz\n\tcd openvpn-<version>\n\t./configure\n\tmake\n\tmake install\n\nor see the file INSTALL for more info.\n\nFor information on how to build OpenVPN on/for Windows with MinGW\nor MSVC see README.cmake.md.\n\n*************************************************************************\n\nFor detailed information on OpenVPN, including examples, see the man page\n  http://openvpn.net/man.html\n\nFor a sample VPN configuration, see\n  http://openvpn.net/howto.html\n\nTo report an issue, see\n  https://github.com/OpenVPN/openvpn/issues/new\n\nFor a description of OpenVPN's underlying protocol,\n  see the file ssl.h included in the source distribution.\n\n*************************************************************************\n\nOther Files & Directories:\n\n* configure.ac -- script to rebuild our configure\n  script and makefile.\n\n* sample/sample-scripts/verify-cn\n\n  A sample perl script which can be used with OpenVPN's\n  --tls-verify option to provide a customized authentication\n  test on embedded X509 certificate fields.\n\n* sample/sample-keys/\n\n  Sample RSA keys and certificates.  DON'T USE THESE FILES\n  FOR ANYTHING OTHER THAN TESTING BECAUSE THEY ARE TOTALLY INSECURE.\n\n* sample/sample-config-files/\n\n  A collection of OpenVPN config files and scripts from\n  the HOWTO at http://openvpn.net/howto.html\n\n*************************************************************************\n\nNote that easy-rsa and tap-windows are now maintained in their own subprojects.\nTheir source code is available here:\n\n  https://github.com/OpenVPN/easy-rsa\n  https://github.com/OpenVPN/tap-windows6\n\nCommunity-provided Windows installers (MSI) and Debian packages are built from\n\n  https://github.com/OpenVPN/openvpn-build\n\nSee the INSTALL file for usage information.\n"
  },
  {
    "path": "README.awslc",
    "content": "This version of OpenVPN supports AWS-LC (AWS Libcrypto), AWS's open-source cryptographic library.\n\nIf you encounter bugs in OpenVPN while using AWS-LC:\n1. Try compiling OpenVPN with OpenSSL to determine if the issue is specific to AWS-LC\n2. For AWS-LC-specific issues, please report them at: https://github.com/aws/aws-lc\n\nTo build and install OpenVPN with AWS-LC:\n\n    OPENSSL_CFLAGS=\"-I/${AWS_LC_INSTALL_FOLDER}/include\" \\\n    OPENSSL_LIBS=\"-L/${AWS_LC_INSTALL_FOLDER}/lib -lssl -lcrypto\" \\\n    LDFLAGS=\"-Wl,-rpath=${AWS_LC_INSTALL_FOLDER}/lib\" \\\n    ./configure --with-crypto-library=openssl\n    make\n    make install\n\n*************************************************************************\nDue to limitations in AWS-LC, the following features are missing\n* Windows CryptoAPI support\n"
  },
  {
    "path": "README.cmake.md",
    "content": "OpenVPN Builds with CMake\n=========================\n\nFor Windows builds we do not use the autotools-based buildsystem that we use\nfor our Unix-like (Linux, BSDs, macOS, etc.) builds. Instead we added a\nseparate (CMake)[https://cmake.org/]-based buildsystem.\n\nThis buildsystem supports building for Windows both with MSVC (i.e. Visual\nStudio) and MinGW. MinGW builds are also supported as cross-compile\nfrom Linux.\n\nThe official builds, which are also available as CMake presets (see\n`cmake --list-presets` and `CMakePresets.json`) all use\n(VCPKG)[https://github.com/microsoft/vcpkg/#vcpkg-overview] for dependency\nmanagement. This allows us to do proper supply-chain management and\nalso makes cross-building with MinGW on Linux much simpler. However,\nbuilds are also possible by providing the build dependencies manually,\nbut that might require specifying more information to CMake.\n\nYou need at least CMake version 3.21 or newer for the `CMakePreset.json`\nfile to be supported. Manual builds might be possible with older CMake\nversions, see `cmake_minimum_required` in `CMakeLists.txt`.\n\nIf you're looking to build the full Windows installer MSI, take a look\nat https://github.com/OpenVPN/openvpn-build.git .\n\nMSVC builds\n-----------\n\nThe following tools are expected to be present on the system, you\ncan install them with a package manager of your choice (e.g.\nchocolatey, winget) or manually:\n\n* CMake (>= 3.21)\n* Git\n* Python (3.x), plus the Python module `docutils`\n* Visual Studion 17 (2022), C/C++ Enviroment\n\nFor example, to prepare the required tools with chocolatey, you\ncan use the following commands (Powershell):\n\n    # Installing Chocolatey\n    Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))\n    & choco.exe install -y git --params \"/GitAndUnixToolsOnPath\"\n    & choco.exe install -y python\n    & python.exe -m ensurepip\n    & python.exe -m pip install --upgrade pip\n    & python.exe -m pip install docutils\n    & choco.exe install -y cmake --installargs 'ADD_CMAKE_TO_PATH=System'\n    & choco.exe install -y \"visualstudio2022buildtools\"\n    & choco.exe install -y \"visualstudio2022-workload-vctools\" --params \"--add Microsoft.VisualStudio.Component.UWP.VC.ARM64 --add Microsoft.VisualStudio.Component.VC.Tools.ARM64 --add Microsoft.VisualStudio.Component.VC.ATL.Spectre --add Microsoft.VisualStudio.Component.VC.ATLMFC.Spectre --add Microsoft.VisualStudio.Component.VC.ATL.ARM64.Spectre --add Microsoft.VisualStudio.Component.VC.MFC.ARM64.Spectre --add Microsoft.VisualStudio.Component.VC.Runtimes.ARM64.Spectre --add Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre --quiet\"\n    & choco.exe install -y windows-sdk-10-version-2004-windbg\n\nOne or more restarts of Powershell might be required to pick up new additions\nto `PATH` between steps. A Windows restart is probably required after\ninstalling Visual Studio before being able to use it.\nYou can find the exact commands we use to set up the community build machines\nat https://github.com/OpenVPN/openvpn-buildbot/blob/master/jenkins/windows-server/msibuild.pkr.hcl\n\nTo do a default build, assuming you are in a MSVC 17 2022 environment:\n\n    mkdir C:\\OpenVPN\n    cd C:\\OpenVPN\n    git clone https://github.com/microsoft/vcpkg.git\n    git clone https://github.com/OpenVPN/openvpn.git\n    set VCPKG_ROOT=C:\\OpenVPN\\vcpkg\n    cd openvpn\n    cmake --preset win-amd64-release\n    cmake --build --preset win-amd64-release\n    ctest --preset win-amd64-release\n\nWhen using the presets, the build directory is\n`out/build/<preset-name>/`, you can find the output files there.\nNo install support is provided directly in OpenVPN build, take a look\nat https://github.com/OpenVPN/openvpn-build.git instead.\n\nMinGW builds (cross-compile on Linux)\n-------------------------------------\n\nTo build the Windows executables on a Linux system:\n\n    # install mingw with the package manager of your choice, e.g.\n    sudo apt-get install -y mingw-w64\n    # in addition to mingw we also need a toolchain for host builds, e.g.\n    sudo apt-get install -y build-essential\n    # minimum required tools for vcpkg bootstrap: curl, zip, unzip, tar, e.g.\n    sudo apt-get install -y curl zip unzip tar\n    # additionally vcpkg requires powershell when building Windows binaries.\n    # See https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux\n    # e.g.\n    sudo apt-get install -y wget apt-transport-https software-properties-common\n    wget -q \"https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb\"\n    sudo dpkg -i packages-microsoft-prod.deb\n    sudo apt-get update\n    sudo apt-get install -y powershell\n    # minimum required tools for build: cmake, docutils, git, ninja,\n    # pkg-config, python e.g.\n    sudo apt-get install -y cmake git ninja-build pkg-config python3 python3-docutils\n    # additionally required to build pkcs11-helper: automake, autoconf,\n    # man2html, e.g.\n    sudo apt-get install -y automake autoconf man2html-base\n    mkdir mingw\n    cd mingw\n    git clone https://github.com/microsoft/vcpkg.git\n    git clone https://github.com/OpenVPN/openvpn.git\n    export VCPKG_ROOT=$PWD/vcpkg\n    cd openvpn\n    # requires CMake 3.21 or newer\n    cmake --preset mingw-x64\n    cmake --build --preset mingw-x64\n    # unit tests are built, but no testPreset is provided. You need to copy\n    # them to a Windows system manually\n\nThe instructions have been verified on a Ubuntu 22.04 LTS system in a\nbash shell, and might need adaptions to other Linux distributions/versions.\n\nNote that the MinGW preset builds use the `Ninja multi-config` generator, so\nif you want to build the Debug binaries, use\n\n    cmake --build --preset mingw-x64 --config Debug\n\nThe default build is equivalent to specifying `--config Release`.\n\nWhen using the presets, the build directory is\n`out/build/mingw/<arch>`, you can find the actual output files in\nsub-directories called `<buildtype>`.\nNo install support is provided directly in OpenVPN build, take a look\nat https://github.com/OpenVPN/openvpn-build.git instead.\n\nUnsupported builds\n------------------\n\nThe CMake buildsystem also supports builds on Unix-like platforms. These builds\nare sometimes useful for OpenVPN developers (e.g. when they use IDEs with\nintegrated CMake support). However, they are not officially supported, do not\ninclude any install support and should not be used to distribute/package\nOpenVPN. To emphasize this fact, you need to specify `-DUNSUPPORTED_BUILDS=ON`\nto cmake to be able to use these builds.\n\nThe `unix-native` CMake preset is available for these builds. This preset does\nnot require VCPKG and instead assumes all build-dependencies are provided by\nthe system natively.\n\nGenerating compile_commands.json\n--------------------------------\n\nTo have the CMake buildsystem generate compile_commands.json you can specify\n`-DENABLE_COMPILE_COMMANDS=ON` on the command line or enable the CMake option\nanother way you like. For supported generators the file will then be created.\nAdditionally, the buildsystem will create a symlink `build/` to the --preset\nbuild directory that contains the generated JSON file. This is done so that\nclangd is able to find it.\n\nEnabling this option may cause an error on Windows, since creating a symlink\nis a privileged operation there. If you enable Developer Mode for the system,\nsymlinks can be created by regular users.\n"
  },
  {
    "path": "README.dco.md",
    "content": "OpenVPN data channel offload\n============================\n2.6.0+ implements support for data-channel offloading where the data packets\nare directly processed and forwarded in kernel space thanks to the ovpn-dco\nkernel module. The userspace openvpn program acts purely as a control plane\napplication.\n\n\nOverview of current release\n---------------------------\n- See the \"Limitations by design\" and \"Current limitations\" sections for\n  features that are not and/or will not be supported by OpenVPN + ovpn-dco.\n\n\nGetting started (Linux)\n-----------------------\nThe new DCO linux kernel module (namely `ovpn`) has been merged upstream\nas of linux-6.16. From this kernel version onwards you directly get\nthe DCO module as shipped by your kernel.\nNOTE: the new `ovpn` Linux kernel module is compatible only with OpenVPN\n2.7 and greater.\n\nAlternatively, if you run an older kernel or if you want to use a more\nrecent DCO module than the one shipped by your kernel, you need to use\nthe ovpn-backports project.\n\nTo learn how to use the ovpn-backports project and build your own DCO\nkernel module, please refer to the README file available at:\n\n  https://github.com/OpenVPN/ovpn-backports/blob/main/README.md\n\nThen clone and build OpenVPN (or use OpenVPN 2.7+). For example:\n\n    git clone https://github.com/openvpn/openvpn.git\n    cd openvpn\n    autoreconf -vi\n    ./configure --enable-dco\n    make\n    sudo make install # Or just run src/openvpn/openvpn\n\nWhen starting openvpn it will automatically detect DCO support and use the\nkernel module. Add the option `--disable-dco` to disable data channel offload\nsupport. If the configuration contains an option that is incompatible with\ndata channel offloading, OpenVPN will automatically disable DCO support and\nwarn the user.\n\nShould OpenVPN be configured to use a feature that is not supported by ovpn\nor should the ovpn kernel module not be available on the system, you will\nsee a message like\n\n    Note: Kernel support for ovpn-dco missing, disabling data channel offload.\n\nin your log.\n\n\nGetting started (Windows)\n-------------------------\nOfficial releases published at https://openvpn.net/community-downloads/\ninclude ovpn-dco-win driver since 2.6.0.\n\nThere are also snapshot releases available at\nhttps://build.openvpn.net/downloads/snapshots/github-actions/openvpn2/ .\nThis installer contains the latest OpenVPN code and the ovpn-dco-win driver.\n\n\nDCO and P2P mode\n----------------\nDCO is also available when running OpenVPN in P2P mode without `--pull` /\n`--client` option. P2P mode is useful for scenarios when the OpenVPN tunnel\nshould not interfere with overall routing and behave more like a \"dumb\" tunnel,\nlike GRE.\n\nHowever, DCO requires DATA_V2 to be enabled, which is available for P2P mode\nonly in OpenVPN 2.6 and later.\n\nOpenVPN prints a diagnostic message for the P2P NCP result when running in P2P\nmode:\n\n    P2P mode NCP negotiation result: TLS_export=1, DATA_v2=1, peer-id 9484735, cipher=AES-256-GCM\n\nDouble check that you have `DATA_v2=1` in your output and a supported AEAD\ncipher (AES-XXX-GCM or CHACHA20POLY1305).\n\n\nRouting with ovpn-dco\n---------------------\nThe ovpn-dco kernel module implements a more transparent approach to\nconfiguring routes to clients (aka \"iroutes\") and consults the main kernel\nrouting tables for forwarding decisions.\n\n- Each client has a VPN IPv4 and/or a VPN IPv6 assigned to it;\n- additional IP ranges can be routed to a client by adding a route with\n  a client VPN IP as the gateway/nexthop (i.e. ip route add a.b.c.d/24 via\n  $VPNIP);\n- due to the point above, there is no real need to add a companion `--route` for\n  each `--iroute` directive, unless you want to blackhole traffic when the\n  specific client is not connected;\n- no internal routing is available. If you need truly internal routes, this can\n  be achieved either with filtering using `iptables` or using `ip rule`;\n- client-to-client behaviour, as implemented in userspace, does not exist:\n  packets always reach the tunnel interface and are then re-routed to the\n  destination peer based on the system routing table.\n\n\nLimitations by design\n----------------------\n- Layer 3 (dev tun) only;\n- only the following AEAD ciphers are currently supported: Chacha20-Poly1305\n  and AES-GCM-128/192/256;\n- no support for compression or compression framing:\n  - see also the `--compress migrate` option to move to a setup without\n    compression;\n- various features not implemented since they have better replacements:\n  - `--shaper`, use tc instead;\n  - packet manipulation, use nftables/iptables instead;\n- OpenVPN 2.4.0 is the minimum version required for peers to connect:\n  - older versions are missing support for the AEAD ciphers;\n- topology subnet is the only supported `--topology` for servers;\n- iroute directives install routes on the host operating system, see also\n  Routing with ovpn-dco;\n- (ovpn-dco-win) client and p2p mode only;\n- (ovpn-dco-win) Chacha20-Poly1305 support available starting with Windows 11.\n\n\nCurrent implementation limitations\n-------------------\n- `--persist-tun` not tested;\n- IPv6 mapped IPv4 addresses need Linux 5.4.189+/5.10.110+/5.12+ to work;\n- some incompatible options may not properly fallback to non-dco;\n"
  },
  {
    "path": "README.ec",
    "content": "Since 2.4.0, OpenVPN has official support for elliptic curve crypto. Elliptic\ncurves are an alternative to RSA for asymmetric encryption.\n\nElliptic curve crypto ('ECC') can be used for the ('TLS') control channel only\nin OpenVPN; the data channel (encrypting the actual network traffic) uses\nsymmetric encryption. ECC can be used in TLS for authentication (ECDSA) and key\nexchange (ECDH).\n\nKey exchange (ECDH)\n-------------------\nOpenVPN 2.4.0 and newer automatically initialize ECDH parameters. When ECDSA is\nused for authentication, the curve used for the server certificate will be used\nfor ECDH too. When autodetection fails (e.g. when using RSA certificates)\nOpenVPN lets the crypto library decide if possible, or falls back to the\nsecp384r1 curve. The list of groups/curves that the crypto library will choose\nfrom can be set with the --tls-groups <grouplist> option.\n\nAn administrator can force an OpenVPN/OpenSSL server to use a specific curve\nusing the --ecdh-curve <curvename> option with one of the curves listed as\navailable by the --show-groups option. Clients will use the same curve as\nselected by the server.\n\nNote that not all curves listed by --show-groups are available for use with TLS;\nin that case connecting will fail with a 'no shared cipher' TLS error.\n\nAuthentication (ECDSA)\n----------------------\nSince OpenVPN 2.4.0, using ECDSA certificates works 'out of the box'. Which\nspecific curves and cipher suites are available depends on your version and\nconfiguration of the crypto library. The crypto library will automatically\nselect a cipher suite for the TLS control channel.\n\nSupport for generating an ECDSA certificate chain is available in EasyRSA (in\nspite of it's name) since EasyRSA 3.0. The parameters you're looking for are\n'--use-algo=ec' and '--curve=<curve_name>'. See the EasyRSA documentation for\nmore details on generating ECDSA certificates.\n"
  },
  {
    "path": "README.mbedtls",
    "content": "This version of OpenVPN has mbed TLS support. To enable, follow the\ninstructions below:\n\nTo build and install,\n\n\t./configure --with-crypto-library=mbedtls\n\tmake\n\tmake install\n\nThis version requires mbed TLS version >= 3.2.1. Support for TLS 1.3 requires\nan Mbed TLS version >= 3.6.4.\n\n*************************************************************************\n\nDue to limitations in the mbed TLS library, the following features are missing\nin the mbed TLS version of OpenVPN:\n\n * PKCS#12 file support\n * --capath support - Loading certificate authorities from a directory\n * Windows CryptoAPI support\n * X.509 alternative username fields (must be \"CN\")\n\nPlugin/Script features:\n\n * X.509 subject line has a different format than the OpenSSL subject line\n * X.509 certificate tracking\n"
  },
  {
    "path": "README.wolfssl",
    "content": "Support for wolfSSL is implemented and maintained by wolfSSL Inc. The support is\nimplemented using wolfSSL's compatibility layer. The wolfSSL support in OpenVPN\nreceives very limited testing/support from the OpenVPN community itself.\n\nIf bugs in OpenVPN when using wolfSSL are encountered, the user should try to\nalso compile OpenVPN with OpenSSL to determine if these are bugs in the\nwolfSSL TLS implementation or OpenVPN itself. If bugs are caused by compiling\nwith wolfSSL, please contact support@wolfssl.com directly.\n\nTo Build and Install,\n\n\t./configure --with-crypto-library=wolfssl\n\tmake\n\tmake install\n\n\nThe wolfSSL library will include the installed options.h file by default.\nTo include a custom user_settings.h file for wolfSSL,\n\n./configure --with-crypto-library=wolfssl --disable-wolfssl-options-h\nmake\nmake install\n\n*************************************************************************\nDue to limitations in the wolfSSL TLS library or its compatibility layer, the\nfollowing features are missing\n\n * blowfish support (BF-CBC), you must use something like\n   cipher AES-128-CBC to avoid trying to use BF-CBC\n * Windows CryptoAPI support\n * No TLS1.0 PRF support (No compaitbility with OpenVPN 2.5 or older or\n   other build that do not support TLS EKM)\n\n\n*************************************************************************\nNewer wolfSSL versions (5.8.2 and newer) are GPLv3 licensed and this license is not\ncompatible with OpenVPN's GPLv2 license.\n\nHowever wolfSSL Inc has granted an exception to combine the wolfSSL library\nwith OpenVPN and OpenVPN-NL (https://github.com/wolfSSL/wolfssl/blob/master/LICENSING)\nwith version 5.8.4 and later.\n*************************************************************************\nTo build WolfSSL with post-quantum KEMs built in, the following command is used:\n\n./configure --enable-openvpn --enable-kyber=all --enable-curve25519\n\nWolfSSL supports the following post-quantum KEMs and post-quantum hybrid KEMs which must be specified\nusing the tls-groups option in an OpenVPN config. Unlike OpenSSL, which includes X25519MLKEM768\nin the default config, WolfSSL requires explicit configuration of tls-groups to include\nat least one post-quantum KEM.\n\nML_KEM_512\nML_KEM_768\nML_KEM_1024\n\nP256_ML_KEM_512\nX25519_ML_KEM_512\n\nP384_ML_KEM_768\nP256_ML_KEM_768\nX448_ML_KEM_768\nX25519_ML_KEM_768\n\nP384_ML_KEM_1024\nP521_ML_KEM_1024\n\nThe naming conventions of algorithms differ between WolfSSL and OpenSSL. An example is that\nOpenSSL omits underscores for their naming notation whereas WolfSSL expects them. Additionally,\nOpenSSL does not accept the P curve notation and instead uses the equivalent secp notation.\nA specific example is that WolfSSL expects P384_ML_KEM_1024, while OpenSSL expects secp384r1MLKEM1024.\n"
  },
  {
    "path": "compat.m4",
    "content": "dnl  OpenVPN -- An application to securely tunnel IP networks\ndnl             over a single UDP port, with support for SSL/TLS-based\ndnl             session authentication and key exchange,\ndnl             packet encryption, packet authentication, and\ndnl             packet compression.\ndnl\ndnl  Copyright (C) 2008-2012 Alon Bar-Lev <alon.barlev@gmail.com>\ndnl\ndnl  This program is free software; you can redistribute it and/or modify\ndnl  it under the terms of the GNU General Public License as published by\ndnl  the Free Software Foundation; either version 2 of the License, or\ndnl  (at your option) any later version.\ndnl\ndnl  This program is distributed in the hope that it will be useful,\ndnl  but WITHOUT ANY WARRANTY; without even the implied warranty of\ndnl  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\ndnl  GNU General Public License for more details.\ndnl\ndnl  You should have received a copy of the GNU General Public License along\ndnl  with this program; if not, see <https://www.gnu.org/licenses/>.\n\ndnl Compatibility layer for <autoconf-2.60 <automake-1.10\ndnl REMOVE THIS IN FUTURE!\n\nifdef(\n\t[AS_VAR_IF],\n\t,\n\t[\n\t\tAC_DEFUN([AS_VAR_IF], [dnl\n\t\t\tif test \"$$1\" = \"$2\"; then\n\t\t\t\tm4_ifval([$3], [$3], [:])\n\t\t\telse\n\t\t\t\tm4_ifval([$4], [$4], [:])\n\t\t\tfi\n\t\t])\n\t]\n)\nifdef(\n\t[AC_USE_SYSTEM_EXTENSIONS],\n\t,\n\t[AC_DEFUN([AC_USE_SYSTEM_EXTENSIONS], [GNU_SOURCE])]\n)\nifdef(\n\t[AC_PROG_SED],\n\t,\n\t[AC_DEFUN([AC_PROG_SED], [AC_CHECK_PROGS([SED], [sed])])]\n)\nifdef(\n\t[PKG_CHECK_VAR],\n\t,\n\t[\n\t\tAC_DEFUN([PKG_CHECK_VAR],\n\t\t[AC_REQUIRE([PKG_PROG_PKG_CONFIG])\n\t\tAC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])\n\n\t\t_PKG_CONFIG([$1], [variable=\"][$3][\"], [$2])\n\t\tAS_VAR_COPY([$1], [pkg_cv_][$1])\n\n\t\tAS_VAR_IF([$1], [\"\"], [$5], [$4])\n\t\t])\n\t]\n)\n\nif test -z \"${docdir}\"; then\n\tdocdir=\"\\$(datadir)/doc/\\$(PACKAGE_NAME)\"\n\tAC_SUBST([docdir])\nfi\nif test -z \"${htmldir}\"; then\n\thtmldir=\"\\$(docdir)\"\n\tAC_SUBST([htmldir])\nfi\n"
  },
  {
    "path": "config.h.cmake.in",
    "content": "\n/* Configuration settings */\n#define CONFIGURE_DEFINES \"N/A\"\n\n/* Enable async push */\n#cmakedefine ENABLE_ASYNC_PUSH\n\n/* Use mbed TLS library */\n#cmakedefine ENABLE_CRYPTO_MBEDTLS\n\n/* Use Openssl */\n#cmakedefine ENABLE_CRYPTO_OPENSSL\n\n/* Use wolfSSL crypto library */\n#cmakedefine ENABLE_CRYPTO_WOLFSSL\n\n/* Enable shared data channel offload */\n#cmakedefine ENABLE_DCO\n\n/* Enable debugging support (needed for verb>=4) */\n#define ENABLE_DEBUG 1\n\n/* Enable internal fragmentation support */\n#define ENABLE_FRAGMENT 1\n\n/* Enable linux data channel offload */\n#cmakedefine ENABLE_LINUXDCO\n\n/* Enable LZ4 compression library */\n#cmakedefine ENABLE_LZ4\n\n/* Enable LZO compression library */\n#cmakedefine ENABLE_LZO\n\n/* Enable dns-updown script hook */\n#cmakedefine ENABLE_DNS_UPDOWN\n\n/* Enable management server capability */\n#define ENABLE_MANAGEMENT 1\n\n/* Enable OFB and CFB cipher modes */\n#define ENABLE_OFB_CFB_MODE\n\n/* Enable PKCS11 */\n#cmakedefine ENABLE_PKCS11\n\n/* Enable plug-in support */\n#define ENABLE_PLUGIN 1\n\n/* Enable TCP Server port sharing */\n#cmakedefine ENABLE_PORT_SHARE\n\n/* SELinux support */\n#cmakedefine ENABLE_SELINUX\n\n/* enable sitnl support */\n#cmakedefine ENABLE_SITNL\n\n/* Enable systemd integration */\n/* #undef ENABLE_SYSTEMD */\n\n/* Define to 1 if you have the <arpa/inet.h> header file. */\n#cmakedefine HAVE_ARPA_INET_H 1\n\n/* Define to 1 if you have the `basename' function. */\n#cmakedefine HAVE_BASENAME\n\n/* Define to 1 if you have the `chdir' function. */\n#cmakedefine HAVE_CHDIR\n\n/* Define to 1 if you have the `chroot' function. */\n#cmakedefine HAVE_CHROOT\n\n/* Define to 1 if you have the `chsize' function. */\n#cmakedefine HAVE_CHSIZE\n\n/* struct cmsghdr needed for extended socket error support */\n#cmakedefine HAVE_CMSGHDR\n\n/* git version information in config-version.h */\n#cmakedefine HAVE_CONFIG_VERSION_H\n\n/* cmocka version information available in cmocka_version.h (>= 2.0.0) */\n#cmakedefine HAVE_CMOCKA_VERSION_H\n\n/* Define to 1 if you have the `daemon' function. */\n#cmakedefine HAVE_DAEMON\n\n/* Define to 1 if you have the <direct.h> header file. */\n#cmakedefine HAVE_DIRECT_H\n\n/* Define to 1 if you have the `dirname' function. */\n#cmakedefine HAVE_DIRNAME\n\n/* Define to 1 if you have the <dlfcn.h> header file. */\n#cmakedefine HAVE_DLFCN_H\n\n/* Define to 1 if you have the <dmalloc.h> header file. */\n#cmakedefine HAVE_DMALLOC_H\n\n/* Define to 1 if you have the `dup' function. */\n#cmakedefine HAVE_DUP\n\n/* Define to 1 if you have the `dup2' function. */\n#cmakedefine HAVE_DUP2\n\n/* Define to 1 if you have the `epoll_create' function. */\n#cmakedefine HAVE_EPOLL_CREATE\n\n/* Define to 1 if you have the <err.h> header file. */\n#cmakedefine HAVE_ERR_H\n\n/* Define to 1 if you have the <fcntl.h> header file. */\n#cmakedefine HAVE_FCNTL_H\n\n/* Define to 1 if you have the `fork' function. */\n#cmakedefine HAVE_FORK\n#cmakedefine HAVE_EXECVE\n\n/* Define to 1 if you have the `ftruncate' function. */\n#cmakedefine HAVE_FTRUNCATE\n\n/* Define to 1 if you have the `getgrnam' function. */\n#cmakedefine HAVE_GETGRNAM\n\n/* Define to 1 if you have the `getpeereid' function. */\n#cmakedefine HAVE_GETPEEREID\n\n/* Define to 1 if you have the `getpwnam' function. */\n#cmakedefine HAVE_GETPWNAM\n\n/* Define to 1 if you have the `getrlimit' function. */\n#cmakedefine HAVE_GETRLIMIT\n\n/* Define to 1 if you have the `getsockname' function. */\n#cmakedefine HAVE_GETSOCKNAME\n\n/* Define to 1 if you have the `gettimeofday' function. */\n#cmakedefine HAVE_GETTIMEOFDAY\n\n/* Define to 1 if you have the <grp.h> header file. */\n#cmakedefine HAVE_GRP_H\n\n/* struct in_pktinfo needed for IP_PKTINFO support */\n#cmakedefine HAVE_IN_PKTINFO\n\n/* Define to 1 if you have the <io.h> header file. */\n#cmakedefine HAVE_IO_H\n\n/* struct in_pktinfo.ipi_spec_dst needed for IP_PKTINFO support */\n#cmakedefine HAVE_IPI_SPEC_DST\n\n/* Define to 1 if you have the <libgen.h> header file. */\n#cmakedefine HAVE_LIBGEN_H\n\n/* Define to 1 if you have the <limits.h> header file. */\n#define HAVE_LIMITS_H 1\n\n/* Define to 1 if you have the <lzo1x.h> header file. */\n#define HAVE_LZO1X_H 1\n\n/* Define to 1 if you have the `mlockall' function. */\n#cmakedefine HAVE_MLOCKALL\n\n/* struct msghdr needed for extended socket error support */\n#cmakedefine HAVE_MSGHDR\n\n/* Define to 1 if you have the <netdb.h> header file. */\n#cmakedefine HAVE_NETDB_H\n\n/* Define to 1 if you have the <netinet/in.h> header file. */\n#cmakedefine HAVE_NETINET_IN_H\n\n/* Define to 1 if you have the <netinet/ip.h> header file. */\n#cmakedefine HAVE_NETINET_IP_H\n\n/* Define to 1 if you have the <netinet/tcp.h> header file. */\n#undef HAVE_NETINET_TCP_H\n\n/* Define to 1 if you have the <net/if.h> header file. */\n#cmakedefine HAVE_NET_IF_H\n\n/* Define to 1 if you have the <net/if_tun.h> header file. */\n#cmakedefine HAVE_NET_IF_TUN_H\n\n/* Define to 1 if you have the <net/tun/if_tun.h> header file. */\n#cmakedefine HAVE_NET_TUN_IF_TUN_H\n\n/* Define to 1 if you have the `nice' function. */\n#cmakedefine HAVE_NICE\n\n/* Define to 1 if you have the `openlog' function. */\n#cmakedefine HAVE_OPENLOG\n\n/* OpenSSL engine support available */\n#undef HAVE_OPENSSL_ENGINE\n\n/* Define to 1 if you have the `poll' function. */\n#undef HAVE_POLL\n\n/* Define to 1 if you have the <poll.h> header file. */\n#cmakedefine HAVE_POLL_H\n\n/* Define to 1 if you have the `putenv' function. */\n#undef HAVE_PUTENV\n\n/* Define to 1 if you have the <pwd.h> header file. */\n#cmakedefine HAVE_PWD_H\n\n\n/* Define to 1 if you have the `recvmsg' function. */\n#cmakedefine HAVE_RECVMSG\n#cmakedefine HAVE_SENDMSG\n\n/* Define to 1 if you have the <resolv.h> header file. */\n#cmakedefine HAVE_RESOLV_H\n\n/* sa_family_t, needed to hold AF_* info */\n#cmakedefine HAVE_SA_FAMILY_T\n\n/* Define to 1 if you have the `sd_booted' function. */\n#undef HAVE_SD_BOOTED\n\n/* Define to 1 if you have the `setgid' function. */\n#cmakedefine HAVE_SETGID\n\n/* Define to 1 if you have the `setgroups' function. */\n#undef HAVE_SETGROUPS\n\n/* Define to 1 if you have the `setsid' function. */\n#cmakedefine HAVE_SETSID\n\n/* Define to 1 if you have the `setsockopt' function. */\n#define HAVE_SETSOCKOPT 1\n\n/* Define to 1 if you have the `setuid' function. */\n#cmakedefine HAVE_SETUID\n\n/* Define to 1 if you have the <signal.h> header file. */\n#undef HAVE_SIGNAL_H\n\n/* Define to 1 if you have the `socket' function. */\n#undef HAVE_SOCKET\n\n/* struct sock_extended_err needed for extended socket error support */\n#undef HAVE_SOCK_EXTENDED_ERR\n\n/* Define to 1 if you have the `stat' function. */\n#define HAVE_STAT 1\n\n/* Define to 1 if you have the <stdarg.h> header file. */\n#define HAVE_STDARG_H 1\n\n/* Define to 1 if you have the <stdint.h> header file. */\n#define HAVE_STDINT_H 1\n\n/* Define to 1 if you have the <stdio.h> header file. */\n#define HAVE_STDIO_H 1\n\n/* Define to 1 if you have the <stdlib.h> header file. */\n#define HAVE_STDLIB_H 1\n\n/* Define to 1 if you have the `strdup' function. */\n#undef HAVE_STRDUP\n\n/* Define to 1 if you have the <strings.h> header file. */\n#define HAVE_STRINGS_H 1\n\n/* Define to 1 if you have the <string.h> header file. */\n#define HAVE_STRING_H 1\n\n/* Define to 1 if you have the `strsep' function. */\n#undef HAVE_STRSEP\n\n/* Define to 1 if you have the `syslog' function. */\n#cmakedefine HAVE_SYSLOG\n\n/* Define to 1 if you have the <syslog.h> header file. */\n#cmakedefine HAVE_SYSLOG_H\n\n/* Define to 1 if you have the <sys/epoll.h> header file. */\n#cmakedefine HAVE_SYS_EPOLL_H\n\n/* Define to 1 if you have the <sys/file.h> header file. */\n#undef HAVE_SYS_FILE_H\n\n/* Define to 1 if you have the <sys/inotify.h> header file. */\n#cmakedefine HAVE_SYS_INOTIFY_H\n\n/* Define to 1 if you have the <sys/ioctl.h> header file. */\n#cmakedefine HAVE_SYS_IOCTL_H\n\n/* Define to 1 if you have the <sys/kern_control.h> header file. */\n#undef HAVE_SYS_KERN_CONTROL_H\n\n/* Define to 1 if you have the <sys/mman.h> header file. */\n#cmakedefine HAVE_SYS_MMAN_H\n\n/* Define to 1 if you have the <sys/socket.h> header file. */\n#cmakedefine HAVE_SYS_SOCKET_H\n\n/* Define to 1 if you have the <sys/sockio.h> header file. */\n#cmakedefine HAVE_SYS_SOCKIO_H\n\n/* Define to 1 if you have the <sys/stat.h> header file. */\n#define HAVE_SYS_STAT_H 1\n\n/* Define to 1 if you have the <sys/time.h> header file. */\n#cmakedefine HAVE_SYS_TIME_H\n\n/* Define to 1 if you have the <sys/types.h> header file. */\n#undef HAVE_SYS_TYPES_H\n\n/* Define to 1 if you have the <sys/uio.h> header file. */\n#cmakedefine HAVE_SYS_UIO_H\n\n/* Define to 1 if you have the <sys/un.h> header file. */\n#cmakedefine HAVE_SYS_UN_H\n\n/* Define to 1 if you have the <sys/wait.h> header file. */\n#cmakedefine HAVE_SYS_WAIT_H\n\n/* Define to 1 if you have the <tap-windows.h> header file. */\n#undef HAVE_TAP_WINDOWS_H\n\n/* Define to 1 if you have the <uapi.h> header file. */\n#undef HAVE_UAPI_H\n\n/* Define to 1 if you have the <unistd.h> header file. */\n#cmakedefine HAVE_UNISTD_H\n\n/* Define to 1 if you have the <valgrind/memcheck.h> header file. */\n#undef HAVE_VALGRIND_MEMCHECK_H\n\n/* Availability of different mbed TLS features and APIs */\n#cmakedefine HAVE_PSA_CRYPTO_H\n\n/* Path to ifconfig tool */\n#define IFCONFIG_PATH \"@IFCONFIG_PATH@\"\n\n/* Path to iproute tool */\n#define IPROUTE_PATH \"@IPROUTE_PATH@\"\n\n/* Path to route tool */\n#define ROUTE_PATH \"@ROUTE_PATH@\"\n\n/* OpenVPN version in Windows resource format - string */\n#define OPENVPN_VERSION_RESOURCE @OPENVPN_VERSION_RESOURCE@\n\n/* Name of package */\n#define PACKAGE \"openvpn\"\n\n/* Define to the address where bug reports for this package should be sent. */\n#undef PACKAGE_BUGREPORT\n\n/* Define to the full name of this package. */\n#define PACKAGE_NAME \"OpenVPN\"\n\n/* Define to the full name and version of this package. */\n#define PACKAGE_STRING \"OpenVPN @OPENVPN_VERSION_MAJOR@.@OPENVPN_VERSION_MINOR@@OPENVPN_VERSION_PATCH@\"\n\n/* Define to the version of this package. */\n#define PACKAGE_VERSION \"@OPENVPN_VERSION_MAJOR@.@OPENVPN_VERSION_MINOR@@OPENVPN_VERSION_PATCH@\"\n\n/* Path to systemd-ask-password tool */\n#undef SYSTEMD_ASK_PASSWORD_PATH\n\n/* The tap-windows id */\n#define TAP_WIN_COMPONENT_ID \"tap0901\"\n\n/* The tap-windows version number is required for OpenVPN */\n#define TAP_WIN_MIN_MAJOR 9\n\n/* The tap-windows version number is required for OpenVPN */\n#define TAP_WIN_MIN_MINOR 9\n\n/* Are we running on Mac OS X? */\n#cmakedefine TARGET_DARWIN\n\n/* Are we running on FreeBSD? */\n#cmakedefine TARGET_FREEBSD\n\n/* Are we running on Linux? */\n#cmakedefine TARGET_LINUX\n\n/* Are we running on Solaris/OpenIndiana? */\n#cmakedefine TARGET_SOLARIS\n\n/* Are we running WIN32? */\n#cmakedefine TARGET_WIN32\n\n/* Are we targeting Android? */\n#cmakedefine TARGET_ANDROID\n\n#define TARGET_ALIAS \"@CMAKE_SYSTEM_NAME@\"\n\n/* Enable GNU extensions on systems that have them.  */\n#ifndef _GNU_SOURCE\n# define _GNU_SOURCE 1\n#endif\n"
  },
  {
    "path": "configure.ac",
    "content": "dnl  OpenVPN -- An application to securely tunnel IP networks\ndnl             over a single UDP port, with support for SSL/TLS-based\ndnl             session authentication and key exchange,\ndnl             packet encryption, packet authentication, and\ndnl             packet compression.\ndnl\ndnl  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\ndnl  Copyright (C) 2006-2012 Alon Bar-Lev <alon.barlev@gmail.com>\ndnl\ndnl  This program is free software; you can redistribute it and/or modify\ndnl  it under the terms of the GNU General Public License as published by\ndnl  the Free Software Foundation; either version 2 of the License, or\ndnl  (at your option) any later version.\ndnl\ndnl  This program is distributed in the hope that it will be useful,\ndnl  but WITHOUT ANY WARRANTY; without even the implied warranty of\ndnl  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\ndnl  GNU General Public License for more details.\ndnl\ndnl  You should have received a copy of the GNU General Public License along\ndnl  with this program; if not, see <https://www.gnu.org/licenses/>.\n\ndnl Process this file with autoconf to produce a configure script.\n\nAC_PREREQ(2.60)\n\nm4_include(version.m4)\nAC_INIT([PRODUCT_NAME], [PRODUCT_VERSION], [PRODUCT_BUGREPORT], [PRODUCT_TARNAME])\nm4_include(compat.m4)\nAC_DEFINE([OPENVPN_VERSION_RESOURCE], [PRODUCT_VERSION_RESOURCE], [Version in windows resource format])\nAC_SUBST([OPENVPN_VERSION_MAJOR], [PRODUCT_VERSION_MAJOR], [OpenVPN major version])\nAC_SUBST([OPENVPN_VERSION_MINOR], [PRODUCT_VERSION_MINOR], [OpenVPN minor version])\nAC_SUBST([OPENVPN_VERSION_PATCH], [PRODUCT_VERSION_PATCH], [OpenVPN patch level - may be a string or integer])\nAC_DEFINE([OPENVPN_VERSION_MAJOR], [PRODUCT_VERSION_MAJOR], [OpenVPN major version - integer])\nAC_DEFINE([OPENVPN_VERSION_MINOR], [PRODUCT_VERSION_MINOR], [OpenVPN minor version - integer])\nAC_DEFINE([OPENVPN_VERSION_PATCH], [\"PRODUCT_VERSION_PATCH\"], [OpenVPN patch level - may be a string or integer])\n\nAC_CONFIG_AUX_DIR([.])\nAC_CONFIG_HEADERS([config.h include/openvpn-plugin.h])\nAC_CONFIG_SRCDIR([src/openvpn/syshead.h])\nAC_CONFIG_MACRO_DIR([m4])\n\ndnl Automake 1.14+ warns if sources are in sub-directories but subdir-objects\ndnl options is not enabled. However, automake before 1.15a has a bug that causes\ndnl variable expansion to fail in foo_SOURCES when this option is used.\ndnl As most of our build systems are now likely to use automake 1.16+ add a\ndnl work around to conditionally add subdir-objects option.\nm4_define([subdir_objects], [\n    m4_esyscmd([automake --version |\n                head -1 |\n                awk '{split ($NF,a,\".\"); if (a[1] == 1 && a[2] >= 16) { print \"subdir-objects\" }}'\n    ])\n])\n\n# This foreign option prevents autoreconf from overriding our COPYING and\n# INSTALL targets:\nAM_INIT_AUTOMAKE(foreign subdir_objects 1.9) dnl NB: Do not [quote] this parameter.\nAM_SILENT_RULES([yes])\nAC_CANONICAL_HOST\nAC_USE_SYSTEM_EXTENSIONS\n\nAC_ARG_ENABLE(\n\t[lzo],\n\t[AS_HELP_STRING([--disable-lzo], [disable LZO compression support @<:@default=yes@:>@])],\n\t,\n\t[enable_lzo=\"yes\"]\n)\n\nAC_ARG_ENABLE(\n\t[lz4],\n\t[AS_HELP_STRING([--disable-lz4], [disable LZ4 compression support @<:@default=yes@:>@])],\n\t[enable_lz4=\"$enableval\"],\n\t[enable_lz4=\"yes\"]\n)\n\nAC_ARG_ENABLE(\n\t[comp-stub],\n\t[AS_HELP_STRING([--enable-comp-stub], [disable compression support but still allow limited interoperability with compression-enabled peers @<:@default=no@:>@])],\n\t[enable_comp_stub=\"$enableval\"],\n\t[enable_comp_stub=\"no\"]\n)\n\nAC_ARG_ENABLE(\n\t[ofb-cfb],\n\t[AS_HELP_STRING([--disable-ofb-cfb], [disable support for OFB and CFB cipher modes @<:@default=yes@:>@])],\n\t,\n\t[enable_crypto_ofb_cfb=\"yes\"]\n)\n\nAC_ARG_ENABLE(\n\t[dns-updown-by-default],\n\t[AS_HELP_STRING([--disable-dns-updown-by-default], [disable running --dns-updown by default @<:@default=yes@:>@])],\n\t,\n\t[enable_dns_updown_by_default=\"yes\"]\n)\n\nAC_ARG_ENABLE(\n\t[plugins],\n\t[AS_HELP_STRING([--disable-plugins], [disable plug-in support @<:@default=yes@:>@])],\n\t,\n\t[enable_plugins=\"yes\"]\n)\n\nAC_ARG_ENABLE(\n\t[management],\n\t[AS_HELP_STRING([--disable-management], [disable management server support @<:@default=yes@:>@])],\n\t,\n\t[enable_management=\"yes\"]\n)\n\nAC_ARG_ENABLE(\n\t[pkcs11],\n\t[AS_HELP_STRING([--enable-pkcs11], [enable pkcs11 support @<:@default=no@:>@])],\n\t,\n\t[enable_pkcs11=\"no\"]\n)\n\nAC_ARG_ENABLE(\n\t[fragment],\n\t[AS_HELP_STRING([--disable-fragment], [disable internal fragmentation support (--fragment) @<:@default=yes@:>@])],\n\t,\n\t[enable_fragment=\"yes\"]\n)\n\nAC_ARG_ENABLE(\n\t[port-share],\n\t[AS_HELP_STRING([--disable-port-share], [disable TCP server port-share support (--port-share) @<:@default=yes@:>@])],\n\t,\n\t[enable_port_share=\"yes\"]\n)\n\nAC_ARG_ENABLE(\n\t[debug],\n\t[AS_HELP_STRING([--disable-debug], [disable debugging support (disable gremlin and verb 7+ messages) @<:@default=yes@:>@])],\n\t,\n\t[enable_debug=\"yes\"]\n)\n\nAC_ARG_ENABLE(\n\t[small],\n\t[AS_HELP_STRING([--enable-small], [enable smaller executable size (disable OCC, usage message, and verb 4 parm list) @<:@default=no@:>@])],\n\t,\n\t[enable_small=\"no\"]\n)\n\nAC_ARG_ENABLE(\n\t[dco],\n\t[AS_HELP_STRING([--disable-dco], [disable data channel offload support using the ovpn-dco kernel module @<:@default=yes@:>@ on Linux/FreeBSD, can't disable on Windows])],\n\t,\n\t[\n\t\tcase \"$host\" in\n\t\t\t*-*-linux*)\n\t\t\t\tenable_dco=\"auto\"\n\t\t\t;;\n\t\t\t*-*-freebsd*)\n\t\t\t\tenable_dco=\"auto\"\n\t\t\t;;\n\t\t\t*)\n\t\t\t\t# note that this does not disable it for Windows\n\t\t\t\tenable_dco=\"no\"\n\t\t\t;;\n\t\tesac\n\t]\n)\n\nAC_ARG_ENABLE(\n\t[iproute2],\n\t[AS_HELP_STRING([--enable-iproute2], [enable support for iproute2 (disables DCO) @<:@default=no@:>@])],\n\t,\n\t[enable_iproute2=\"no\"]\n)\n\nAC_ARG_ENABLE(\n\t[plugin-auth-pam],\n\t[AS_HELP_STRING([--disable-plugin-auth-pam], [disable auth-pam plugin @<:@default=platform specific@:>@])],\n\t,\n\t[\n\t\tcase \"$host\" in\n\t\t\t*-*-openbsd*) enable_plugin_auth_pam=\"no\";;\n\t\t\t*-mingw*) enable_plugin_auth_pam=\"no\";;\n\t\t\t*) enable_plugin_auth_pam=\"yes\";;\n\t\tesac\n\t]\n)\n\nAC_ARG_ENABLE(\n\t[plugin-down-root],\n\t[AS_HELP_STRING([--disable-plugin-down-root], [disable down-root plugin @<:@default=platform specific@:>@])],\n\t,\n\t[\n\t\tcase \"$host\" in\n\t\t\t*-mingw*) enable_plugin_down_root=\"no\";;\n\t\t\t*) enable_plugin_down_root=\"yes\";;\n\t\tesac\n\t]\n)\n\nAC_ARG_ENABLE(\n\t[pam-dlopen],\n\t[AS_HELP_STRING([--enable-pam-dlopen], [dlopen libpam @<:@default=no@:>@])],\n\t,\n\t[enable_pam_dlopen=\"no\"]\n)\n\nAC_ARG_ENABLE(\n\t[strict],\n\t[AS_HELP_STRING([--enable-strict], [enable strict compiler warnings (debugging option) @<:@default=no@:>@])],\n\t,\n\t[enable_strict=\"no\"]\n)\n\nAC_ARG_ENABLE(\n\t[pedantic],\n\t[AS_HELP_STRING([--enable-pedantic], [enable pedantic compiler warnings, will not generate a working executable (debugging option) @<:@default=no@:>@])],\n\t,\n\t[enable_pedantic=\"no\"]\n)\n\nAC_ARG_ENABLE(\n\t[werror],\n\t[AS_HELP_STRING([--enable-werror], [promote compiler warnings to errors, will cause builds to fail if the compiler issues warnings (debugging option) @<:@default=no@:>@])],\n\t,\n\t[enable_werror=\"no\"]\n)\n\nAC_ARG_ENABLE(\n\t[strict-options],\n\t[AS_HELP_STRING([--enable-strict-options], [enable strict options check between peers (debugging option) @<:@default=no@:>@])],\n\t,\n\t[enable_strict_options=\"no\"]\n)\n\nAC_ARG_ENABLE(\n\t[selinux],\n\t[AS_HELP_STRING([--enable-selinux], [enable SELinux support @<:@default=no@:>@])],\n\t,\n\t[enable_selinux=\"no\"]\n)\n\nAC_ARG_ENABLE(\n\t[systemd],\n\t[AS_HELP_STRING([--enable-systemd], [enable systemd support @<:@default=no@:>@])],\n\t,\n\t[enable_systemd=\"no\"]\n)\n\nAC_ARG_ENABLE(\n\t[async-push],\n\t[AS_HELP_STRING([--enable-async-push], [enable async-push support for plugins providing deferred authentication @<:@default=no@:>@])],\n\t,\n\t[enable_async_push=\"no\"]\n)\n\nAC_ARG_WITH(\n\t[special-build],\n\t[AS_HELP_STRING([--with-special-build=STRING], [specify special build string])],\n\t[test -n \"${withval}\" && AC_DEFINE_UNQUOTED([CONFIGURE_SPECIAL_BUILD], [\"${withval}\"], [special build string])]\n)\n\nAC_ARG_WITH(\n\t[mem-check],\n\t[AS_HELP_STRING([--with-mem-check=TYPE], [build with debug memory checking, TYPE=no|dmalloc|valgrind|ssl @<:@default=no@:>@])],\n\t[\n\t\tcase \"${withval}\" in\n\t\t\tdmalloc|valgrind|ssl|no) ;;\n\t\t\t*) AC_MSG_ERROR([bad value ${withval} for --mem-check]) ;;\n\t\tesac\n\t],\n\t[with_mem_check=\"no\"]\n)\n\nAC_ARG_WITH(\n\t[crypto-library],\n\t[AS_HELP_STRING([--with-crypto-library=library], [build with the given crypto library, TYPE=openssl|mbedtls|wolfssl @<:@default=openssl@:>@])],\n\t[\n\t\tcase \"${withval}\" in\n\t\t\topenssl|mbedtls|wolfssl) ;;\n\t\t\t*) AC_MSG_ERROR([bad value ${withval} for --with-crypto-library]) ;;\n\t\tesac\n\t],\n\t[with_crypto_library=\"openssl\"]\n)\n\nAC_ARG_ENABLE(\n\t[wolfssl-options-h],\n\t[AS_HELP_STRING([--disable-wolfssl-options-h], [Disable including options.h in wolfSSL @<:@default=yes@:>@])],\n\t,\n\t[enable_wolfssl_options_h=\"yes\"]\n)\n\nAC_ARG_WITH(\n\t[openssl-engine],\n\t[AS_HELP_STRING([--with-openssl-engine], [enable engine support with OpenSSL. Default enabled for OpenSSL < 3.0, auto,yes,no @<:@default=auto@:>@])],\n\t[\n\t\tcase \"${withval}\" in\n\t\t\tauto|yes|no) ;;\n\t\t\t*) AC_MSG_ERROR([bad value ${withval} for --with-engine]) ;;\n\t\tesac\n\t],\n\t[with_openssl_engine=\"auto\"]\n)\n\nAC_ARG_VAR([PLUGINDIR], [Path of plug-in directory @<:@default=LIBDIR/openvpn/plugins@:>@])\nif test -n \"${PLUGINDIR}\"; then\n\tplugindir=\"${PLUGINDIR}\"\nelse\n\tplugindir=\"\\${libdir}/openvpn/plugins\"\nfi\n\nAC_ARG_VAR([SCRIPTDIR], [Path of script directory @<:@default=PKGLIBEXECDIR@:>@])\nif test -n \"${SCRIPTDIR}\"; then\n\tscriptdir=\"${SCRIPTDIR}\"\nelse\n\tscriptdir=\"\\${pkglibexecdir}\"\nfi\n\nAC_DEFINE_UNQUOTED([TARGET_ALIAS], [\"${host}\"], [A string representing our host])\nAM_CONDITIONAL([ENABLE_DNS_UPDOWN],[true])\ncase \"$host\" in\n\t*-*-linux*)\n\t\tAC_DEFINE([TARGET_LINUX], [1], [Are we running on Linux?])\n\t\tAC_DEFINE_UNQUOTED([TARGET_PREFIX], [\"L\"], [Target prefix])\n\t\tAC_SUBST([DNS_UPDOWN_TYPE], [\"systemd\"])\n\t\thave_sitnl=\"yes\"\n\t\tpkg_config_required=\"yes\"\n\t\t;;\n\t*-*-solaris*)\n\t\tAC_DEFINE([TARGET_SOLARIS], [1], [Are we running on Solaris?])\n\t\tAC_DEFINE_UNQUOTED([TARGET_PREFIX], [\"S\"], [Target prefix])\n\t\tAC_SUBST([DNS_UPDOWN_TYPE], [\"resolvconf_file\"])\n\t\tCPPFLAGS=\"$CPPFLAGS -D_XPG4_2\"\n\t\ttest -x /bin/bash && SHELL=\"/bin/bash\"\n\t\t;;\n\t*-*-openbsd*)\n\t\tAC_DEFINE([TARGET_OPENBSD], [1], [Are we running on OpenBSD?])\n\t\tAC_DEFINE_UNQUOTED([TARGET_PREFIX], [\"O\"], [Target prefix])\n\t\tAC_SUBST([DNS_UPDOWN_TYPE], [\"resolvconf_file\"])\n\t\t;;\n\t*-*-freebsd*)\n\t\tAC_DEFINE([TARGET_FREEBSD], [1], [Are we running on FreeBSD?])\n\t\tAC_DEFINE_UNQUOTED([TARGET_PREFIX], [\"F\"], [Target prefix])\n\t\tAC_SUBST([DNS_UPDOWN_TYPE], [\"openresolv\"])\n\t\t;;\n\t*-*-netbsd*)\n\t\tAC_DEFINE([TARGET_NETBSD], [1], [Are we running NetBSD?])\n\t\tAC_DEFINE_UNQUOTED([TARGET_PREFIX], [\"N\"], [Target prefix])\n\t\tAC_SUBST([DNS_UPDOWN_TYPE], [\"openresolv\"])\n\t\t;;\n\t*-*-darwin*)\n\t\tAC_DEFINE([TARGET_DARWIN], [1], [Are we running on Mac OS X?])\n\t\tAC_DEFINE_UNQUOTED([TARGET_PREFIX], [\"M\"], [Target prefix])\n\t\tAC_SUBST([DNS_UPDOWN_TYPE], [\"macos\"])\n\t\thave_tap_header=\"yes\"\n\t\tac_cv_type_struct_in_pktinfo=no\n\t\t;;\n\t*-mingw*)\n\t\tAC_DEFINE([TARGET_WIN32], [1], [Are we running WIN32?])\n\t\tAC_DEFINE([ENABLE_DCO], [1], [DCO is always enabled on Windows])\n\t\tAC_DEFINE_UNQUOTED([TARGET_PREFIX], [\"W\"], [Target prefix])\n\t\tAM_CONDITIONAL([ENABLE_DNS_UPDOWN], [false])\n\t\tAC_SUBST([DNS_UPDOWN_TYPE], [\"windows\"])\n\t\tCPPFLAGS=\"${CPPFLAGS} -DWIN32_LEAN_AND_MEAN\"\n\t\tCPPFLAGS=\"${CPPFLAGS} -DNTDDI_VERSION=NTDDI_VISTA -D_WIN32_WINNT=_WIN32_WINNT_VISTA\"\n\t\tWIN32=yes\n\t\t;;\n\t*-*-dragonfly*)\n\t\tAC_DEFINE([TARGET_DRAGONFLY], [1], [Are we running on DragonFlyBSD?])\n\t\tAC_DEFINE_UNQUOTED([TARGET_PREFIX], [\"D\"], [Target prefix])\n\t\tAC_SUBST([DNS_UPDOWN_TYPE], [\"openresolv\"])\n\t\t;;\n\t*-aix*)\n\t\tAC_DEFINE([TARGET_AIX], [1], [Are we running AIX?])\n\t\tAC_DEFINE_UNQUOTED([TARGET_PREFIX], [\"A\"], [Target prefix])\n\t\tAC_SUBST([DNS_UPDOWN_TYPE], [\"resolvconf_file\"])\n\t\tROUTE=\"/usr/sbin/route\"\n\t\thave_tap_header=\"yes\"\n\t\tac_cv_header_net_if_h=\"no\"\t# exists, but breaks things\n\t\t;;\n\t*-*-haiku*)\n\t\tAC_DEFINE([TARGET_HAIKU], [1], [Are we running Haiku?])\n\t\tAC_DEFINE_UNQUOTED([TARGET_PREFIX], [\"H\"], [Target prefix])\n\t\tAC_SUBST([DNS_UPDOWN_TYPE], [\"haikuos_file\"])\n\t\tLIBS=\"${LIBS} -lnetwork\"\n\t\t;;\n\t*)\n\t\tAC_DEFINE_UNQUOTED([TARGET_PREFIX], [\"X\"], [Target prefix])\n\t\tAC_SUBST([DNS_UPDOWN_TYPE], [\"resolvconf_file\"])\n\t\thave_tap_header=\"yes\"\n\t\t;;\nesac\n\nAM_CONDITIONAL([CROSS_COMPILING], test \"${cross_compiling}\" = \"yes\")\n\nPKG_PROG_PKG_CONFIG\n# Add variable to print if pkg-config is found or not. Users often miss that\nif test \"${PKG_CONFIG}\" = \"\"; then\n\tif test \"${pkg_config_required}\" = \"yes\"; then\n\t\tAC_MSG_ERROR([pkg-config is required])\n\tfi\n\tpkg_config_found=\"(not found)\"\nelse\n\tpkg_config_found=\"(${PKG_CONFIG})\"\nfi\n\nAC_PROG_CC\nAC_PROG_CPP\nAC_PROG_INSTALL\nAC_PROG_LN_S\nAC_PROG_SED\nAC_PROG_MAKE_SET\n\nAC_ARG_VAR([IFCONFIG], [full path to ipconfig utility])\nAC_ARG_VAR([ROUTE], [full path to route utility])\nAC_ARG_VAR([IPROUTE], [full path to ip utility])\nAC_ARG_VAR([NETSTAT], [path to netstat utility]) # tests\nAC_ARG_VAR([GIT], [path to git utility])\nAC_ARG_VAR([SYSTEMD_ASK_PASSWORD], [path to systemd-ask-password utility])\nAC_ARG_VAR([SYSTEMD_UNIT_DIR], [Path of systemd unit directory @<:@default=LIBDIR/systemd/system@:>@])\nAC_ARG_VAR([TMPFILES_DIR], [Path of tmpfiles directory @<:@default=LIBDIR/tmpfiles.d@:>@])\nAC_PATH_PROGS([IFCONFIG], [ifconfig],, [$PATH:/usr/local/sbin:/usr/sbin:/sbin])\nAC_PATH_PROGS([ROUTE], [route],, [$PATH:/usr/local/sbin:/usr/sbin:/sbin])\nAC_PATH_PROGS([IPROUTE], [ip],, [$PATH:/usr/local/sbin:/usr/sbin:/sbin])\nAC_PATH_PROGS([SYSTEMD_ASK_PASSWORD], [systemd-ask-password],, [$PATH:/usr/local/bin:/usr/bin:/bin])\nAC_CHECK_PROGS([NETSTAT], [netstat], [netstat], [$PATH:/usr/local/sbin:/usr/sbin:/sbin:/etc]) # tests\nAC_CHECK_PROGS([GIT], [git]) # optional\nAC_DEFINE_UNQUOTED([IFCONFIG_PATH], [\"$IFCONFIG\"], [Path to ifconfig tool])\nAC_DEFINE_UNQUOTED([IPROUTE_PATH], [\"$IPROUTE\"], [Path to iproute tool])\nAC_DEFINE_UNQUOTED([ROUTE_PATH], [\"$ROUTE\"], [Path to route tool])\nAC_DEFINE_UNQUOTED([SYSTEMD_ASK_PASSWORD_PATH], [\"$SYSTEMD_ASK_PASSWORD\"], [Path to systemd-ask-password tool])\nAC_CHECK_TOOLS([WINDMC], [windmc mc.exe],[no])\n\n#\n#  man page generation - based on python-docutils\n#\nAC_ARG_VAR([RST2MAN], [path to rst2man utility])\nAC_ARG_VAR([RST2HTML], [path to rst2html utility])\nAC_CHECK_PROGS([RST2MAN], [rst2man rst2man.py])\nAC_CHECK_PROGS([RST2HTML], [rst2html rst2html.py])\nAM_CONDITIONAL([HAVE_PYDOCUTILS], [test \"${RST2MAN}\" -a \"${RST2HTML}\"])\n\n# Set -std=c11 unless user already specified a -std=\ncase \"${CFLAGS}\" in\n  *-std=*) ;;\n  *)       CFLAGS=\"${CFLAGS} -std=c11\" ;;\nesac\n\n#\n# Libtool\n#\nifdef(\n\t[LT_INIT],\n\t[\n\t\tLT_INIT([win32-dll])\n\t\tLT_LANG([Windows Resource])\n\t],\n\t[\n\t\tAC_LIBTOOL_WIN32_DLL\n\t\tAC_LIBTOOL_RC\n\t\tAC_PROG_LIBTOOL\n\t]\n)\n\nAC_C_INLINE\nAX_TYPE_SOCKLEN_T\nAC_CHECK_HEADERS([ \\\n\tfcntl.h io.h \\\n\tsys/types.h sys/socket.h \\\n\tunistd.h dlfcn.h \\\n\tnetinet/in.h \\\n\tnetinet/tcp.h arpa/inet.h netdb.h \\\n])\nAC_CHECK_HEADERS([ \\\n\tsys/time.h sys/ioctl.h sys/stat.h \\\n\tsys/mman.h sys/file.h sys/wait.h \\\n\tlibgen.h stropts.h \\\n\tsyslog.h pwd.h grp.h termios.h \\\n\tsys/sockio.h sys/uio.h \\\n\tpoll.h sys/epoll.h err.h \\\n])\n\nSOCKET_INCLUDES=\"\n#include <stdlib.h>\n#ifdef HAVE_SYS_TYPES_H\n#include <sys/types.h>\n#endif\n#ifdef HAVE_SYS_SOCKET_H\n#include <sys/socket.h>\n#endif\n#ifdef HAVE_NET_IF_H\n#include <net/if.h>\n#endif\n#ifdef HAVE_NETINET_IN_H\n#include <netinet/in.h>\n#endif\n#ifdef _WIN32\n#include <windows.h>\n#endif\n#ifdef _WIN32\n#include <winsock2.h>\n#endif\n#ifdef _WIN32\n#include <ws2tcpip.h>\n#endif\n#ifdef HAVE_NETINET_IP_H\n#include <netinet/ip.h>\n#endif\n\"\n\nAC_CHECK_HEADERS(\n\t[net/if.h netinet/ip.h resolv.h sys/un.h sys/kern_control.h],\n\t,\n\t,\n\t[[${SOCKET_INCLUDES}]]\n)\n\nAC_CHECK_TYPE(\n\t[struct msghdr],\n\t[AC_DEFINE([HAVE_MSGHDR], [1], [struct msghdr needed for extended socket error support])],\n\t,\n\t[[${SOCKET_INCLUDES}]]\n)\nAC_CHECK_TYPE(\n\t[struct cmsghdr],\n\t[AC_DEFINE([HAVE_CMSGHDR], [1], [struct cmsghdr needed for extended socket error support])],\n\t,\n\t[[${SOCKET_INCLUDES}]]\n)\nAC_CHECK_TYPE(\n\t[struct in_pktinfo],\n\t[AC_DEFINE([HAVE_IN_PKTINFO], [1], [struct in_pktinfo needed for IP_PKTINFO support])],\n\t,\n\t[[${SOCKET_INCLUDES}]]\n)\nAC_CHECK_TYPE(\n        [sa_family_t],\n        [AC_DEFINE([HAVE_SA_FAMILY_T], [1], [sa_family_t, needed to hold AF_* info])],\n        ,\n        [[${SOCKET_INCLUDES}]]\n)\nAC_CHECK_MEMBER(\n\t[struct in_pktinfo.ipi_spec_dst],\n\t[AC_DEFINE([HAVE_IPI_SPEC_DST], [1], [struct in_pktinfo.ipi_spec_dst needed for IP_PKTINFO support])],\n\t,\n\t[[${SOCKET_INCLUDES}]]\n)\nAC_CHECK_TYPE(\n\t[struct sockaddr_in6],\n\t,\n\t[AC_MSG_ERROR([struct sockaddr_in6 not found, needed for ipv6 transport support.])],\n\t[[${SOCKET_INCLUDES}]]\n)\n\nsaved_LDFLAGS=\"$LDFLAGS\"\nLDFLAGS=\"$LDFLAGS -Wl,--wrap=exit\"\nAC_MSG_CHECKING([linker supports --wrap])\nAC_LINK_IFELSE(\n\t[AC_LANG_PROGRAM(\n\t\t[[\n\t\t\tvoid exit(int);\n\t\t\tvoid __real_exit(int);\n\t\t\tvoid __wrap_exit(int i) {\n\t\t\t\t__real_exit(i);\n\t\t\t}\n\t\t]],\n\t\t[[\n\t\t\texit(0);\n\t\t]]\n\t)],\n\t[\n\t\tAC_MSG_RESULT([yes])\n\t\thave_ld_wrap_support=yes\n\t],\n\t[AC_MSG_RESULT([no])],\n)\nLDFLAGS=\"$saved_LDFLAGS\"\n\nAC_FUNC_FORK\n\nAC_CHECK_FUNCS([ \\\n\tdaemon chroot getpwnam setuid nice dup dup2 \\\n\tsyslog openlog mlockall getrlimit getgrnam setgid \\\n\tsetgroups flock gettimeofday \\\n\tsetsid chdir \\\n\tchsize ftruncate execve getpeereid basename dirname \\\n\tepoll_create strsep \\\n])\n\nAC_CHECK_LIB(\n\t[dl],\n\t[dlopen],\n\t[DL_LIBS=\"-ldl\"]\n)\nAC_SUBST([DL_LIBS])\n\nAC_CHECK_LIB(\n\t[nsl],\n\t[inet_ntoa],\n\t[SOCKETS_LIBS=\"${SOCKETS_LIBS} -lnsl\"]\n)\nAC_CHECK_LIB(\n\t[socket],\n\t[socket],\n\t[SOCKETS_LIBS=\"${SOCKETS_LIBS} -lsocket\"]\n)\nAC_CHECK_LIB(\n\t[resolv],\n\t[gethostbyname],\n\t[SOCKETS_LIBS=\"${SOCKETS_LIBS} -lresolv\"]\n)\nAC_SUBST([SOCKETS_LIBS])\n\nold_LIBS=\"${LIBS}\"\nLIBS=\"${LIBS} ${SOCKETS_LIBS}\"\nAC_CHECK_FUNCS([sendmsg recvmsg])\n\nLIBS=\"${old_LIBS}\"\n\n# we assume res_init() always exist, but need to find out *where*...\nAC_SEARCH_LIBS(__res_init, resolv bind, ,\n    AC_SEARCH_LIBS(res_9_init, resolv bind, ,\n\tAC_SEARCH_LIBS(res_init, resolv bind, , )))\n\nAC_ARG_VAR([TAP_CFLAGS], [C compiler flags for tap])\nold_CFLAGS=\"${CFLAGS}\"\nCFLAGS=\"${CFLAGS} ${TAP_CFLAGS}\"\nAC_CHECK_HEADERS(\n\t[ \\\n\t\tnet/if_tun.h net/tun/if_tun.h \\\n\t\tlinux/if_tun.h \\\n\t\ttap-windows.h \\\n\t],\n\t[have_tap_header=\"yes\"]\n)\nCFLAGS=\"${old_CFLAGS}\"\ntest \"${have_tap_header}\" = \"yes\" || AC_MSG_ERROR([no tap header could be found])\n\nAC_CHECK_LIB(\n\t[selinux],\n\t[setcon],\n\t[SELINUX_LIBS=\"-lselinux\"]\n)\nAC_SUBST([SELINUX_LIBS])\n\nAC_ARG_VAR([LIBPAM_CFLAGS], [C compiler flags for libpam])\nAC_ARG_VAR([LIBPAM_LIBS], [linker flags for libpam])\nif test -z \"${LIBPAM_LIBS}\"; then\n\tAC_CHECK_LIB(\n\t\t[pam],\n\t\t[pam_start],\n\t\t[LIBPAM_LIBS=\"-lpam\"]\n\t)\nfi\n\ncase \"${with_mem_check}\" in\n\tvalgrind)\n\t\tAC_CHECK_HEADERS(\n\t\t\t[valgrind/memcheck.h],\n\t\t\t[\n\t\t\t\tCFLAGS=\"${CFLAGS} -g -fno-inline\"\n\t\t\t\tAC_DEFINE(\n\t\t\t\t\t[USE_VALGRIND],\n\t\t\t\t\t[1],\n\t\t\t\t\t[Use valgrind memory debugging library]\n\t\t\t\t)\n\t\t\t],\n\t\t\t[AC_MSG_ERROR([valgrind headers not found.])]\n\t\t)\n\t\t;;\n\tdmalloc)\n\t\tAC_CHECK_HEADERS(\n\t\t\t[dmalloc.h],\n\t\t\t[AC_CHECK_LIB(\n\t\t\t\t[dmalloc],\n\t\t\t\t[malloc],\n\t\t\t\t[\n\t\t\t\t\tLIBS=\"${LIBS} -ldmalloc\"\n\t\t\t\t\tAC_DEFINE(\n\t\t\t\t\t\t[DMALLOC],\n\t\t\t\t\t\t[1],\n\t\t\t\t\t\t[Use dmalloc memory debugging library]\n\t\t\t\t\t)\n\t\t\t\t],\n\t\t\t\t[AC_MSG_ERROR([dmalloc library not found.])]\n\t\t\t)],\n\t\t\t[AC_MSG_ERROR([dmalloc headers not found.])]\n\t\t)\n\t\t;;\n\tssl)\n\t\tAC_CHECK_LIB(\n\t\t\t[ssl],\n\t\t\t[CRYPTO_mem_ctrl],\n\t\t\t[\n\t\t\t\tAC_DEFINE(\n\t\t\t\t\t[CRYPTO_MDEBUG],\n\t\t\t\t\t[1],\n\t\t\t\t\t[Use memory debugging function in OpenSSL]\n\t\t\t\t)\n\t\t\t\tAC_MSG_NOTICE([NOTE: OpenSSL library must be compiled with CRYPTO_MDEBUG])\n\t\t\t],\n\t\t\t[AC_MSG_ERROR([Memory Debugging function in OpenSSL library not found.])]\n\t\t)\n\t\t;;\nesac\n\nif test \"$enable_dco\" != \"no\"; then\n\tenable_dco_arg=\"$enable_dco\"\n\tif test \"${enable_iproute2}\" = \"yes\"; then\n\t\tAC_MSG_WARN([DCO cannot be enabled when using iproute2])\n\t\tenable_dco=\"no\"\n\tfi\n\tcase \"$host\" in\n\t\t*-*-linux*)\n\t\t\tif test \"$enable_dco\" = \"no\"; then\n\t\t\t\tif test \"$enable_dco_arg\" = \"auto\"; then\n\t\t\t\t\tAC_MSG_WARN([DCO support disabled])\n\t\t\t\telse\n\t\t\t\t\tAC_MSG_ERROR([DCO support can't be enabled])\n\t\t\t\tfi\n\t\t\telse\n\t\t\t\tdnl\n\t\t\t\tdnl Include generic netlink library used to talk to ovpn-dco\n\t\t\t\tdnl\n\t\t\t\tPKG_CHECK_MODULES([LIBNL_GENL],\n\t\t\t\t\t  [libnl-genl-3.0 >= 3.4.0],\n\t\t\t\t\t  [have_libnl=\"yes\"],\n\t\t\t\t\t  [\n\t\t\t\t\t   AC_MSG_ERROR([libnl-genl-3.0 package not found or too old. Is the development package and pkg-config ${pkg_config_found} installed? Must be version 3.4.0 or newer for DCO])\n\t\t\t\t\t  ]\n\t\t\t\t)\n\t\t\t\tOPTIONAL_LIBNL_GENL_CFLAGS=\"${LIBNL_GENL_CFLAGS}\"\n\t\t\t\tOPTIONAL_LIBNL_GENL_LIBS=\"${LIBNL_GENL_LIBS}\"\n\n\t\t\t\tAC_DEFINE(ENABLE_DCO, 1, [Enable shared data channel offload])\n\t\t\t\tAC_MSG_NOTICE([Enabled ovpn-dco support for Linux])\n\t\t\tfi\n\t\t\t;;\n\t\t*-*-freebsd*)\n\t\t\tAC_CHECK_HEADERS([net/if_ovpn.h],\n\t\t\t\t[\n\t\t\t\t LIBS=\"${LIBS} -lnv\"\n\t\t\t\t AC_DEFINE(ENABLE_DCO, 1, [Enable data channel offload for FreeBSD])\n\t\t\t\t AC_MSG_NOTICE([Enabled ovpn-dco support for FreeBSD])\n\t\t\t\t],\n\t\t\t\t[\n\t\t\t\t enable_dco=\"no\"\n\t\t\t\t AC_MSG_WARN([DCO header not found.])\n\t\t\t\t]\n\t\t\t)\n\t\t\tif test \"$enable_dco\" = \"no\"; then\n\t\t\t\tif test \"$enable_dco_arg\" = \"auto\"; then\n\t\t\t\t\tAC_MSG_WARN([DCO support disabled])\n\t\t\t\telse\n\t\t\t\t\tAC_MSG_ERROR([DCO support can't be enabled])\n\t\t\t\tfi\n\t\t\tfi\n\t\t\t;;\n\t\t*-mingw*)\n\t\t\tAC_MSG_NOTICE([NOTE: --enable-dco ignored on Windows because it's always enabled])\n\t\t\t;;\n\t\t*)\n\t\t\tAC_MSG_NOTICE([Ignoring --enable-dco on non supported platform])\n\t\t\t;;\n\tesac\nfi\n\ndnl\ndnl Depend on libcap-ng on Linux\ndnl\ncase \"$host\" in\n\t*-*-linux*)\n\t\tPKG_CHECK_MODULES([LIBCAPNG],\n\t\t\t\t  [libcap-ng],\n\t\t\t\t  [],\n\t\t\t\t  [AC_MSG_ERROR([libcap-ng package not found. Is the development package and pkg-config ${pkg_config_found} installed?])]\n\t\t)\n\t\tAC_CHECK_HEADER([sys/prctl.h],,[AC_MSG_ERROR([sys/prctl.h not found!])])\n\n\t\tOPTIONAL_LIBCAPNG_CFLAGS=\"${LIBCAPNG_CFLAGS}\"\n\t\tOPTIONAL_LIBCAPNG_LIBS=\"${LIBCAPNG_LIBS}\"\n\t\tAC_DEFINE(HAVE_LIBCAPNG, 1, [Enable libcap-ng support])\n\t;;\nesac\n\n\nif test \"${with_crypto_library}\" = \"openssl\"; then\n\tAC_ARG_VAR([OPENSSL_CFLAGS], [C compiler flags for OpenSSL])\n\tAC_ARG_VAR([OPENSSL_LIBS], [linker flags for OpenSSL])\n\n\tif test -z \"${OPENSSL_CFLAGS}\" -a -z \"${OPENSSL_LIBS}\"; then\n\t\t# if the user did not explicitly specify flags, try to autodetect\n\t\tPKG_CHECK_MODULES(\n\t\t\t[OPENSSL],\n\t\t\t[openssl >= 1.1.0],\n\t\t\t[have_openssl=\"yes\"],\n\t\t\t[AC_MSG_WARN([OpenSSL not found by pkg-config ${pkg_config_found}])] # If this fails, we will do another test next\n\t\t)\n\t\tOPENSSL_LIBS=${OPENSSL_LIBS:--lssl -lcrypto}\n\tfi\n\n\tsaved_CFLAGS=\"${CFLAGS}\"\n\tsaved_LIBS=\"${LIBS}\"\n\tCFLAGS=\"${CFLAGS} ${OPENSSL_CFLAGS}\"\n\tLIBS=\"${LIBS} ${OPENSSL_LIBS}\"\n\n\t# If pkgconfig check failed or OPENSSL_CFLAGS/OPENSSL_LIBS env vars\n\t# are used, check the version directly in the OpenSSL include file\n\tif test \"${have_openssl}\" != \"yes\"; then\n\t\tAC_MSG_CHECKING([additionally if OpenSSL is available and version >= 1.1.0])\n\t\tAC_COMPILE_IFELSE(\n\t\t\t[AC_LANG_PROGRAM(\n\t\t\t\t[[\n#include <openssl/opensslv.h>\n\t\t\t\t]],\n\t\t\t\t[[\n/*\t     Version encoding: MNNFFPPS - see opensslv.h for details */\n#if OPENSSL_VERSION_NUMBER < 0x10100000L\n#error OpenSSL too old\n#endif\n\t\t\t\t]]\n\t\t\t)],\n\t\t\t[AC_MSG_RESULT([ok])],\n\t\t\t[AC_MSG_ERROR([OpenSSL version too old])]\n\t\t)\n\tfi\n\n\tAC_CHECK_FUNCS([SSL_CTX_new],\n\t\t\t\t   ,\n\t\t\t\t   [AC_MSG_ERROR([openssl check failed])]\n\t)\n\n\tif test \"${with_openssl_engine}\" = \"auto\"; then\n\t    AC_COMPILE_IFELSE(\n\t\t\t\t    [AC_LANG_PROGRAM(\n\t\t\t\t\t    [[\n\t    #include <openssl/opensslv.h>\n\t    #include <openssl/opensslconf.h>\n\t\t\t\t\t    ]],\n\t\t\t\t\t    [[\n\t    /*\t     Version encoding: MNNFFPPS - see opensslv.h for details */\n\t    #if OPENSSL_VERSION_NUMBER >= 0x30000000L\n\t    #error Engine support disabled by default in OpenSSL 3.0+\n\t    #endif\n\n\t    /*\t     BoringSSL and LibreSSL >= 3.8.1 removed engine support */\n\t    #ifdef OPENSSL_NO_ENGINE\n\t    #error Engine support disabled in openssl/opensslconf.h\n\t    #endif\n\t\t\t\t\t    ]]\n\t\t\t\t    )],\n\t\t\t\t    [have_openssl_engine=\"yes\"],\n\t\t\t\t    [have_openssl_engine=\"no\"]\n\t    )\n\t    if test \"${have_openssl_engine}\" = \"yes\"; then\n\t\tAC_CHECK_FUNCS(\n\t\t    [ \\\n\t\t\tENGINE_load_builtin_engines \\\n\t\t\tENGINE_register_all_complete \\\n\t\t    ],\n\t\t    ,\n\t\t    [have_openssl_engine=\"no\"; break]\n\t\t)\n\t    fi\n\telse\n\t    have_openssl_engine=\"${with_openssl_engine}\"\n\t    if test \"${have_openssl_engine}\" = \"yes\"; then\n\t\tAC_CHECK_FUNCS(\n\t\t    [ \\\n\t\t\tENGINE_load_builtin_engines \\\n\t\t\tENGINE_register_all_complete \\\n\t\t    ],\n\t\t    ,\n\t\t    [AC_MSG_ERROR([OpenSSL engine support not found])]\n\t\t)\n\t    fi\n\tfi\n\tif test \"${have_openssl_engine}\" = \"yes\"; then\n\t\tAC_DEFINE([HAVE_OPENSSL_ENGINE], [1], [OpenSSL engine support available])\n\tfi\n\n\tAC_CHECK_FUNC(\n\t\t[EVP_aes_256_gcm],\n\t\t,\n\t\t[AC_MSG_ERROR([OpenSSL check for AES-256-GCM support failed])]\n\t)\n\n\tCFLAGS=\"${saved_CFLAGS}\"\n\tLIBS=\"${saved_LIBS}\"\n\n\tAC_DEFINE([ENABLE_CRYPTO_OPENSSL], [1], [Use OpenSSL library])\n\tCRYPTO_CFLAGS=\"${OPENSSL_CFLAGS}\"\n\tCRYPTO_LIBS=\"${OPENSSL_LIBS}\"\nelif test \"${with_crypto_library}\" = \"mbedtls\"; then\n\tAC_ARG_VAR([MBEDTLS_CFLAGS], [C compiler flags for mbedtls])\n\tAC_ARG_VAR([MBEDTLS_LIBS], [linker flags for mbedtls])\n\n\tsaved_CFLAGS=\"${CFLAGS}\"\n\tsaved_LIBS=\"${LIBS}\"\n\n\tif test -z \"${MBEDTLS_CFLAGS}\" -a -z \"${MBEDTLS_LIBS}\"; then\n\t\t# if the user did not explicitly specify flags, try to autodetect\n\t\tPKG_CHECK_MODULES([MBEDTLS],\n\t\t\t[mbedtls >= 3.2.1 mbedx509 >= 3.2.1 mbedcrypto >= 3.2.1],\n\t\t\t[have_mbedtls=\"yes\"],\n\t\t\t[LIBS=\"${LIBS} -lmbedtls -lmbedx509 -lmbedcrypto\"]\n\t\t)\n\t\t# mbedtls might not have pkgconfig integration, so try manually\n                if test \"${have_mbedtls}\" != \"yes\"; then\n\t\t\tAC_CHECK_LIB(\n\t\t\t\t[mbedtls],\n\t\t\t\t[mbedtls_ssl_init],\n\t\t\t\t[MBEDTLS_LIBS=\"-lmbedtls -lmbedx509 -lmbedcrypto\"],\n\t\t\t\t[AC_MSG_ERROR([Could not find mbed TLS.])],\n\t\t\t)\n\t\tfi\n\tfi\n\n\tCFLAGS=\"${MBEDTLS_CFLAGS} ${CFLAGS}\"\n\tLIBS=\"${MBEDTLS_LIBS} ${LIBS}\"\n\n\tAC_MSG_CHECKING([mbedtls version])\n\tAC_COMPILE_IFELSE(\n\t\t[AC_LANG_PROGRAM(\n\t\t\t[[\n#include <mbedtls/version.h>\n\t\t\t]],\n\t\t\t[[\n#if MBEDTLS_VERSION_NUMBER < 0x03020100\n#error invalid version\n#endif\n\t\t\t]]\n\t\t)],\n\t\t[AC_MSG_RESULT([ok])],\n\t\t[AC_MSG_ERROR([mbed TLS version >= 3.2.1 required])]\n\t)\n\n\tAC_CHECK_HEADERS(psa/crypto.h)\n\n\tCFLAGS=\"${saved_CFLAGS}\"\n\tLIBS=\"${saved_LIBS}\"\n\tAC_DEFINE([ENABLE_CRYPTO_MBEDTLS], [1], [Use mbed TLS library])\n\tCRYPTO_CFLAGS=\"${MBEDTLS_CFLAGS}\"\n\tCRYPTO_LIBS=\"${MBEDTLS_LIBS}\"\n\nelif test \"${with_crypto_library}\" = \"wolfssl\"; then\n\tAC_ARG_VAR([WOLFSSL_CFLAGS], [C compiler flags for wolfssl. The include directory should\n contain the regular wolfSSL header files but also the wolfSSL OpenSSL header files.\n Ex: -I/usr/local/include -I/usr/local/include/wolfssl])\n\tAC_ARG_VAR([WOLFSSL_LIBS], [linker flags for wolfssl])\n\n\tsaved_CFLAGS=\"${CFLAGS}\"\n\tsaved_LIBS=\"${LIBS}\"\n\n\tif test -z \"${WOLFSSL_CFLAGS}\" -a -z \"${WOLFSSL_LIBS}\"; then\n\t\t# if the user did not explicitly specify flags, try to autodetect\n\t\tPKG_CHECK_MODULES(\n\t\t\t[WOLFSSL],\n\t\t\t[wolfssl],\n\t\t\t[],\n\t\t\t[AC_MSG_ERROR([Could not find wolfSSL using pkg-config ${pkg_config_found}])]\n\t\t)\n\t\tPKG_CHECK_VAR(\n\t\t\t[WOLFSSL_INCLUDEDIR],\n\t\t\t[wolfssl],\n\t\t\t[includedir],\n\t\t\t[],\n\t\t\t[AC_MSG_ERROR([Could not find wolfSSL includedir variable.])]\n\t\t)\n\t\tWOLFSSL_CFLAGS=\"${WOLFSSL_CFLAGS} -I${WOLFSSL_INCLUDEDIR}/wolfssl\"\n\tfi\n\tsaved_CFLAGS=\"${CFLAGS}\"\n\tsaved_LIBS=\"${LIBS}\"\n\tCFLAGS=\"${CFLAGS} ${WOLFSSL_CFLAGS}\"\n\tLIBS=\"${LIBS} ${WOLFSSL_LIBS}\"\n\n\tAC_CHECK_LIB(\n\t\t[wolfssl],\n\t\t[wolfSSL_Init],\n\t\t[],\n\t\t[AC_MSG_ERROR([Could not link wolfSSL library.])]\n\t)\n\tAC_CHECK_HEADER([wolfssl/options.h],,[AC_MSG_ERROR([wolfSSL header wolfssl/options.h not found!])])\n\n\tif test \"${enable_wolfssl_options_h}\" = \"yes\"; then\n\t\tAC_DEFINE([EXTERNAL_OPTS_OPENVPN], [1], [Include options.h from wolfSSL library])\n\telse\n\t\tAC_DEFINE([WOLFSSL_USER_SETTINGS], [1], [Use custom user_settings.h file for wolfSSL library])\n\tfi\n\n\tCFLAGS=\"${saved_CFLAGS}\"\n\tLIBS=\"${saved_LIBS}\"\n\n\tAC_DEFINE([ENABLE_CRYPTO_WOLFSSL], [1], [Use wolfSSL crypto library])\n\tAC_DEFINE([ENABLE_CRYPTO_OPENSSL], [1], [Use wolfSSL openssl compatibility layer])\n\tCRYPTO_CFLAGS=\"${WOLFSSL_CFLAGS}\"\n\tCRYPTO_LIBS=\"${WOLFSSL_LIBS}\"\nelse\n\tAC_MSG_ERROR([Invalid crypto library: ${with_crypto_library}])\nfi\n\nAC_ARG_VAR([LZO_CFLAGS], [C compiler flags for lzo])\nAC_ARG_VAR([LZO_LIBS], [linker flags for lzo])\nif test -z \"${LZO_CFLAGS}\" -a -z \"${LZO_LIBS}\"; then\n    # if the user did not explicitly specify flags, try to autodetect\n    PKG_CHECK_MODULES([LZO],\n\t\t[lzo2],\n\t\t[have_lzo=\"yes\"],\n\t\t[]\n    )\n\n    if test \"${have_lzo}\" != \"yes\"; then\n\t# try to detect without pkg-config\n\thave_lzo=\"yes\"\n\tAC_CHECK_LIB(\n\t\t[lzo2],\n\t\t[lzo1x_1_15_compress],\n\t\t[LZO_LIBS=\"-llzo2\"],\n\t\t[AC_CHECK_LIB(\n\t\t\t[lzo],\n\t\t\t[lzo1x_1_15_compress],\n\t\t\t[LZO_LIBS=\"-llzo\"],\n\t\t\t[have_lzo=\"no\"]\n\t\t)]\n\t)\n    fi\nelse\n    # assume the user configured it correctly\n    have_lzo=\"yes\"\nfi\nif test \"${have_lzo}\" = \"yes\"; then\n\tsaved_CFLAGS=\"${CFLAGS}\"\n\tCFLAGS=\"${CFLAGS} ${LZO_CFLAGS}\"\n\tAC_CHECK_HEADERS(\n\t\t[lzo/lzo1x.h],\n\t\t,\n\t\t[AC_CHECK_HEADERS(\n\t\t\t[lzo1x.h],\n\t\t\t,\n\t\t\t[AC_MSG_ERROR([lzo1x.h is missing])],\n                        [#include <limits.h>\n                         #include <lzodefs.h>\n                         #include <lzoconf.h>]\n\t\t)],\n\t)\n\tCFLAGS=\"${saved_CFLAGS}\"\nfi\n\ndnl\ndnl check for LZ4 library\ndnl\n\nAC_ARG_VAR([LZ4_CFLAGS], [C compiler flags for lz4])\nAC_ARG_VAR([LZ4_LIBS], [linker flags for lz4])\nif test \"$enable_lz4\" = \"yes\" && test \"$enable_comp_stub\" = \"no\"; then\n    if test -z \"${LZ4_CFLAGS}\" -a -z \"${LZ4_LIBS}\"; then\n\t# if the user did not explicitly specify flags, try to autodetect\n\tPKG_CHECK_MODULES([LZ4],\n\t\t\t  [liblz4 >= 1.7.1 liblz4 < 100],\n\t\t\t  [have_lz4=\"yes\"],\n\t\t\t  [LZ4_LIBS=\"-llz4\"] # If this fails, we will do another test next.\n\t\t\t\t\t     # We also add set LZ4_LIBS otherwise the\n\t\t\t\t\t     # linker will not know about the lz4 library\n\t)\n    fi\n\n    saved_CFLAGS=\"${CFLAGS}\"\n    saved_LIBS=\"${LIBS}\"\n    CFLAGS=\"${CFLAGS} ${LZ4_CFLAGS}\"\n    LIBS=\"${LIBS} ${LZ4_LIBS}\"\n\n    # If pkgconfig check failed or LZ4_CFLAGS/LZ4_LIBS env vars\n    # are used, check the version directly in the LZ4 include file\n    if test \"${have_lz4}\" != \"yes\"; then\n\tAC_CHECK_HEADERS([lz4.h],\n\t\t\t [have_lz4h=\"yes\"],\n\t\t\t [])\n\n\tif test \"${have_lz4h}\" = \"yes\" ; then\n\t    AC_MSG_CHECKING([additionally if system LZ4 version >= 1.7.1])\n\t    AC_COMPILE_IFELSE(\n\t\t[AC_LANG_PROGRAM([[\n#include <lz4.h>\n\t\t\t\t ]],\n\t\t\t\t [[\n/* Version encoding: MMNNPP (Major miNor Patch) - see lz4.h for details */\n#if LZ4_VERSION_NUMBER < 10701L\n#error LZ4 is too old\n#endif\n\t\t\t\t ]]\n\t\t\t\t)],\n\t\t[\n\t\t    AC_MSG_RESULT([ok])\n\t\t    have_lz4=\"yes\"\n\t\t],\n\t\t[AC_MSG_ERROR([system LZ4 library is too old])]\n\t    )\n\tfi\n    fi\n\n    # Double check we have a few needed functions\n    if test \"${have_lz4}\" = \"yes\" ; then\n\tAC_CHECK_LIB([lz4],\n\t\t     [LZ4_compress_default],\n\t\t     [],\n\t\t     [have_lz4=\"no\"])\n\tAC_CHECK_LIB([lz4],\n\t\t     [LZ4_decompress_safe],\n\t\t     [],\n\t\t     [have_lz4=\"no\"])\n    fi\n\n    if test \"${have_lz4}\" != \"yes\" ; then\n\tAC_MSG_ERROR([No compatible LZ4 compression library found. Consider --disable-lz4])\n\tLZ4_LIBS=\"\"\n    fi\n    OPTIONAL_LZ4_CFLAGS=\"${LZ4_CFLAGS}\"\n    OPTIONAL_LZ4_LIBS=\"${LZ4_LIBS}\"\n    AC_DEFINE(ENABLE_LZ4, [1], [Enable LZ4 compression library])\n    CFLAGS=\"${saved_CFLAGS}\"\n    LIBS=\"${saved_LIBS}\"\nfi\n\n\ndnl\ndnl Check for systemd\ndnl\nAM_CONDITIONAL([ENABLE_SYSTEMD], [test \"${enable_systemd}\" = \"yes\"])\nif test \"$enable_systemd\" = \"yes\" ; then\n    PKG_CHECK_MODULES([libsystemd], [libsystemd > 216],\n                      [],\n                      [AC_MSG_ERROR([systemd enabled but libsystemd is missing])]\n                      )\n\n    OPTIONAL_SYSTEMD_CFLAGS=\"${libsystemd_CFLAGS}\"\n    OPTIONAL_SYSTEMD_LIBS=\"${libsystemd_LIBS}\"\n    AC_DEFINE(ENABLE_SYSTEMD, 1, [Enable systemd integration])\n\n    if test -n \"${SYSTEMD_UNIT_DIR}\"; then\n        systemdunitdir=\"${SYSTEMD_UNIT_DIR}\"\n    else\n        systemdunitdir=\"\\${libdir}/systemd/system\"\n    fi\n\n    if test -n \"${TMPFILES_DIR}\"; then\n        tmpfilesdir=\"${TMPFILES_DIR}\"\n    else\n        tmpfilesdir=\"\\${libdir}/tmpfiles.d\"\n    fi\nfi\n\n\nAC_MSG_CHECKING([git checkout])\nGIT_CHECKOUT=\"no\"\nif test -n \"${GIT}\"; then\n\tif ${GIT} -C \"$srcdir\" rev-parse --is-inside-work-tree >/dev/null 2>&1; then\n\t\tAC_DEFINE([HAVE_CONFIG_VERSION_H], [1], [extra version available in config-version.h])\n\t\tGIT_CHECKOUT=\"yes\"\n\tfi\nfi\nAC_MSG_RESULT([${GIT_CHECKOUT}])\n\ntest \"${enable_management}\" = \"yes\" && AC_DEFINE([ENABLE_MANAGEMENT], [1], [Enable management server capability])\ntest \"${enable_debug}\" = \"yes\" && AC_DEFINE([ENABLE_DEBUG], [1], [Enable debugging support])\ntest \"${enable_small}\" = \"yes\" && AC_DEFINE([ENABLE_SMALL], [1], [Enable smaller executable size])\ntest \"${enable_fragment}\" = \"yes\" && AC_DEFINE([ENABLE_FRAGMENT], [1], [Enable internal fragmentation support])\ntest \"${enable_port_share}\" = \"yes\" && AC_DEFINE([ENABLE_PORT_SHARE], [1], [Enable TCP Server port sharing])\ntest \"${enable_dns_updown_by_default}\" = \"yes\" && AC_DEFINE([ENABLE_DNS_UPDOWN_BY_DEFAULT], [1], [Enable dns-updown hook by default])\ntest \"${enable_crypto_ofb_cfb}\" = \"yes\" && AC_DEFINE([ENABLE_OFB_CFB_MODE], [1], [Enable OFB and CFB cipher modes])\nOPTIONAL_CRYPTO_CFLAGS=\"${OPTIONAL_CRYPTO_CFLAGS} ${CRYPTO_CFLAGS}\"\nOPTIONAL_CRYPTO_LIBS=\"${OPTIONAL_CRYPTO_LIBS} ${CRYPTO_LIBS}\"\n\nif test \"${enable_plugins}\" = \"yes\"; then\n\tOPTIONAL_DL_LIBS=\"${DL_LIBS}\"\n\tAC_DEFINE([ENABLE_PLUGIN], [1], [Enable plug-in support])\nelse\n\tenable_plugin_auth_pam=\"no\"\n\tenable_plugin_down_root=\"no\"\nfi\n\nAM_CONDITIONAL([HAVE_SITNL], [false])\n\nif test \"${enable_iproute2}\" = \"yes\"; then\n\ttest \"${enable_dco}\" = \"yes\" && AC_MSG_ERROR([iproute2 support cannot be enabled when using DCO])\n\ttest -z \"${IPROUTE}\" && AC_MSG_ERROR([ip utility is required but missing])\n\tAC_DEFINE([ENABLE_IPROUTE], [1], [enable iproute2 support])\nelse if test \"${have_sitnl}\" = \"yes\"; then\n\tAC_DEFINE([ENABLE_SITNL], [1], [enable sitnl support])\n\tAM_CONDITIONAL([HAVE_SITNL], [true])\nelse if test \"${WIN32}\" != \"yes\" -a \"${have_sitnl}\" != \"yes\"; then\n\ttest -z \"${ROUTE}\" && AC_MSG_ERROR([route utility is required but missing])\n\ttest -z \"${IFCONFIG}\" && AC_MSG_ERROR([ifconfig utility is required but missing])\nfi\nfi\nfi\n\nif test \"${enable_selinux}\" = \"yes\"; then\n\ttest -z \"${SELINUX_LIBS}\" && AC_MSG_ERROR([libselinux required but missing])\n\tOPTIONAL_SELINUX_LIBS=\"${SELINUX_LIBS}\"\n\tAC_DEFINE([ENABLE_SELINUX], [1], [SELinux support])\nfi\n\nif test \"${enable_lzo}\" = \"yes\"; then\n\ttest \"${have_lzo}\" != \"yes\" && AC_MSG_ERROR([lzo enabled but missing])\n\tOPTIONAL_LZO_CFLAGS=\"${LZO_CFLAGS}\"\n\tOPTIONAL_LZO_LIBS=\"${LZO_LIBS}\"\n\tAC_DEFINE([ENABLE_LZO], [1], [Enable LZO compression library])\nfi\nif test \"${enable_comp_stub}\" = \"yes\"; then\n\ttest \"${enable_lzo}\" = \"yes\" && AC_MSG_ERROR([Cannot have both comp stub and lzo enabled (use --disable-lzo)])\n\ttest \"${enable_lz4}\" = \"yes\" && AC_MSG_ERROR([Cannot have both comp stub and LZ4 enabled (use --disable-lz4)])\n\tAC_DEFINE([ENABLE_COMP_STUB], [1], [Enable compression stub capability])\nfi\n\nAM_CONDITIONAL([HAVE_SOFTHSM2], [false])\nif test \"${enable_pkcs11}\" = \"yes\"; then\n\tPKG_CHECK_MODULES(\n\t\t[PKCS11_HELPER],\n\t\t[libpkcs11-helper-1 >= 1.11],\n\t\t[have_pkcs11_helper=\"yes\"],\n\t\t[AC_MSG_ERROR([PKCS11 enabled but libpkcs11-helper is missing])]\n\t)\n\tOPTIONAL_PKCS11_HELPER_CFLAGS=\"${PKCS11_HELPER_CFLAGS}\"\n\tOPTIONAL_PKCS11_HELPER_LIBS=\"${PKCS11_HELPER_LIBS}\"\n\tAC_DEFINE([ENABLE_PKCS11], [1], [Enable PKCS11])\n\tPKG_CHECK_MODULES(\n\t\t[P11KIT],\n\t\t[p11-kit-1],\n\t\t[proxy_module=\"`$PKG_CONFIG --variable=proxy_module p11-kit-1`\"\n\t\t AC_DEFINE_UNQUOTED([DEFAULT_PKCS11_MODULE], \"${proxy_module}\", [p11-kit proxy])],\n\t\t[]\n\t)\n\t#\n\t# softhsm2 for pkcs11 tests\n\t#\n\tAC_ARG_VAR([P11TOOL], [full path to p11tool])\n\tAC_PATH_PROGS([P11TOOL], [p11tool],, [$PATH:/usr/local/bin:/usr/bin:/bin])\n\tAC_DEFINE_UNQUOTED([P11TOOL_PATH], [\"$P11TOOL\"], [Path to p11tool])\n\tAC_ARG_VAR([SOFTHSM2_UTIL], [full path to softhsm2-util])\n\tAC_ARG_VAR([SOFTHSM2_MODULE], [full path to softhsm2 module @<:@default=/usr/lib/softhsm/libsofthsm2.so@:>@])\n\tAC_PATH_PROGS([SOFTHSM2_UTIL], [softhsm2-util],, [$PATH:/usr/local/bin:/usr/bin:/bin])\n\ttest -z \"$SOFTHSM2_MODULE\" && SOFTHSM2_MODULE=/usr/lib/softhsm/libsofthsm2.so\n\tAC_DEFINE_UNQUOTED([SOFTHSM2_UTIL_PATH], [\"$SOFTHSM2_UTIL\"], [Path to softhsm2-util])\n\tAC_DEFINE_UNQUOTED([SOFTHSM2_MODULE_PATH], [\"$SOFTHSM2_MODULE\"], [Path to softhsm2 module])\n\tif test \"${with_crypto_library}\" = \"openssl\"; then\n\t\tAM_CONDITIONAL([HAVE_SOFTHSM2], [test \"${P11TOOL}\" -a \"${SOFTHSM2_UTIL}\" -a \"${SOFTHSM2_MODULE}\"])\n\tfi\nfi\n\n# When testing a compiler option, we add -Werror to force\n# an error when the option is unsupported. This is not\n# required for gcc, but some compilers such as clang need it.\nAC_DEFUN([ACL_CHECK_ADD_COMPILE_FLAGS], [\n    old_cflags=\"$CFLAGS\"\n    CFLAGS=\"-Werror $CFLAGS $1\"\n    AC_MSG_CHECKING([whether the compiler accepts $1])\n    AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], [AC_MSG_RESULT([yes])]; CFLAGS=\"$old_cflags $1\",\n        [AC_MSG_RESULT([no]); CFLAGS=\"$old_cflags\"])]\n)\n\nACL_CHECK_ADD_COMPILE_FLAGS([-Wno-stringop-truncation])\nACL_CHECK_ADD_COMPILE_FLAGS([-Wstrict-prototypes])\nACL_CHECK_ADD_COMPILE_FLAGS([-Wold-style-definition])\nACL_CHECK_ADD_COMPILE_FLAGS([-Wconversion -Wno-sign-conversion])\nACL_CHECK_ADD_COMPILE_FLAGS([-Wall])\nACL_CHECK_ADD_COMPILE_FLAGS([-Wextra -Wno-unused-parameter])\n# clang doesn't have the different levels but also doesn't include it in -Wextra\nACL_CHECK_ADD_COMPILE_FLAGS([-Wimplicit-fallthrough=2])\nif test \"${WIN32}\" = \"yes\"; then\n        # Not sure how to deal with GetProcAddress\n\tACL_CHECK_ADD_COMPILE_FLAGS([-Wno-cast-function-type])\nfi\n\nif test \"${enable_pedantic}\" = \"yes\"; then\n\tenable_strict=\"yes\"\n\tCFLAGS=\"${CFLAGS} -pedantic\"\n\tAC_DEFINE([PEDANTIC], [1], [Enable pedantic mode])\nfi\nif test \"${enable_strict}\" = \"yes\"; then\n\tCFLAGS=\"${CFLAGS} -Wsign-compare -Wuninitialized\"\nfi\nif test \"${enable_werror}\" = \"yes\"; then\n\tCFLAGS=\"${CFLAGS} -Werror\"\nfi\n\nif test \"${enable_plugin_auth_pam}\" = \"yes\"; then\n\tPLUGIN_AUTH_PAM_CFLAGS=\"${LIBPAM_CFLAGS}\"\n\tif test \"${enable_pam_dlopen}\" = \"yes\"; then\n\t\tAC_DEFINE([USE_PAM_DLOPEN], [1], [dlopen libpam])\n\t\tPLUGIN_AUTH_PAM_LIBS=\"${DL_LIBS}\"\n\telse\n\t\ttest -z \"${LIBPAM_LIBS}\" && AC_MSG_ERROR([libpam required but missing])\n\t\tPLUGIN_AUTH_PAM_LIBS=\"${LIBPAM_LIBS}\"\n\tfi\nfi\n\nif test \"${enable_async_push}\" = \"yes\"; then\n\tcase \"$host\" in\n\t\t*-*-freebsd1[[0-4]]*)\n\t\t\tPKG_CHECK_MODULES(\n\t\t\t\t[OPTIONAL_INOTIFY],\n\t\t\t\t[libinotify],\n\t\t\t\t[\n\t\t\t\t\tAC_DEFINE([HAVE_SYS_INOTIFY_H])\n\t\t\t\t\tAC_DEFINE([ENABLE_ASYNC_PUSH], [1], [Enable async push])\n\t\t\t\t]\n\t\t\t)\n\t\t;;\n\t\t*)\n\t\t\tAC_CHECK_HEADERS(\n\t\t\t\t[sys/inotify.h],\n\t\t\t\tAC_DEFINE([ENABLE_ASYNC_PUSH], [1], [Enable async push]),\n\t\t\t\tAC_MSG_ERROR([inotify.h not found.])\n\t\t\t)\n\t\t;;\n\tesac\nfi\n\nCONFIGURE_DEFINES=\"`set | grep '^enable_.*=' ; set | grep '^with_.*='`\"\nAC_DEFINE_UNQUOTED([CONFIGURE_DEFINES], [\"`echo ${CONFIGURE_DEFINES}`\"], [Configuration settings])\n\nTAP_WIN_COMPONENT_ID=\"PRODUCT_TAP_WIN_COMPONENT_ID\"\nTAP_WIN_MIN_MAJOR=\"PRODUCT_TAP_WIN_MIN_MAJOR\"\nTAP_WIN_MIN_MINOR=\"PRODUCT_TAP_WIN_MIN_MINOR\"\nAC_DEFINE_UNQUOTED([TAP_WIN_COMPONENT_ID], [\"${TAP_WIN_COMPONENT_ID}\"], [The tap-windows id])\nAC_DEFINE_UNQUOTED([TAP_WIN_MIN_MAJOR], [${TAP_WIN_MIN_MAJOR}], [The tap-windows version number is required for OpenVPN])\nAC_DEFINE_UNQUOTED([TAP_WIN_MIN_MINOR], [${TAP_WIN_MIN_MINOR}], [The tap-windows version number is required for OpenVPN])\nAC_SUBST([TAP_WIN_COMPONENT_ID])\nAC_SUBST([TAP_WIN_MIN_MAJOR])\nAC_SUBST([TAP_WIN_MIN_MINOR])\n\nAC_SUBST([OPTIONAL_DL_LIBS])\nAC_SUBST([OPTIONAL_SELINUX_LIBS])\nAC_SUBST([OPTIONAL_CRYPTO_CFLAGS])\nAC_SUBST([OPTIONAL_CRYPTO_LIBS])\nAC_SUBST([OPTIONAL_LIBCAPNG_CFLAGS])\nAC_SUBST([OPTIONAL_LIBCAPNG_LIBS])\nAC_SUBST([OPTIONAL_LIBNL_GENL_CFLAGS])\nAC_SUBST([OPTIONAL_LIBNL_GENL_LIBS])\nAC_SUBST([OPTIONAL_LZO_CFLAGS])\nAC_SUBST([OPTIONAL_LZO_LIBS])\nAC_SUBST([OPTIONAL_LZ4_CFLAGS])\nAC_SUBST([OPTIONAL_LZ4_LIBS])\nAC_SUBST([OPTIONAL_SYSTEMD_CFLAGS])\nAC_SUBST([OPTIONAL_SYSTEMD_LIBS])\nAC_SUBST([OPTIONAL_PKCS11_HELPER_CFLAGS])\nAC_SUBST([OPTIONAL_PKCS11_HELPER_LIBS])\nAC_SUBST([OPTIONAL_INOTIFY_CFLAGS])\nAC_SUBST([OPTIONAL_INOTIFY_LIBS])\n\nAC_SUBST([PLUGIN_AUTH_PAM_CFLAGS])\nAC_SUBST([PLUGIN_AUTH_PAM_LIBS])\n\nAM_CONDITIONAL([WIN32], [test \"${WIN32}\" = \"yes\"])\nAM_CONDITIONAL([GIT_CHECKOUT], [test \"${GIT_CHECKOUT}\" = \"yes\"])\nAM_CONDITIONAL([ENABLE_PLUGIN_AUTH_PAM], [test \"${enable_plugin_auth_pam}\" = \"yes\"])\nAM_CONDITIONAL([ENABLE_PLUGIN_DOWN_ROOT], [test \"${enable_plugin_down_root}\" = \"yes\"])\nAM_CONDITIONAL([HAVE_LD_WRAP_SUPPORT], [test \"${have_ld_wrap_support}\" = \"yes\"])\nAM_CONDITIONAL([OPENSSL_ENGINE], [test \"${have_openssl_engine}\" = \"yes\"])\n\nsampledir=\"\\$(docdir)/sample\"\nAC_SUBST([plugindir])\nAC_SUBST([scriptdir])\nAC_SUBST([sampledir])\n\nAC_SUBST([systemdunitdir])\nAC_SUBST([tmpfilesdir])\n\nAC_ARG_ENABLE(\n     [unit-tests],\n     [AS_HELP_STRING([--disable-unit-tests],\n                     [Disables building and running the unit tests suite])],\n     [],\n     [enable_unit_tests=\"yes\"]\n)\n\n# Check if cmocka is available - needed for unit testing\nPKG_CHECK_MODULES(\n\t[CMOCKA], [cmocka],\n\t[have_cmocka=\"yes\"],\n\t[AC_MSG_WARN([cmocka.pc not found on the system using pkg-config ${pkg_config_found}.  Unit tests disabled])]\n)\nAM_CONDITIONAL([ENABLE_UNITTESTS], [false])\nif test \"${enable_unit_tests}\" = \"yes\" -a \"${have_cmocka}\" = \"yes\"; then\n   AM_CONDITIONAL([ENABLE_UNITTESTS], [true])\n\n   saved_CFLAGS=\"${CFLAGS}\"\n   CFLAGS=\"${CFLAGS} ${CMOCKA_CFLAGS}\"\n   # detect cmocka < 2.0.0 that had no cmocka_version.h\n   AC_CHECK_HEADERS([cmocka_version.h])\n   CFLAGS=\"${saved_CFLAGS}\"\nfi\nAC_SUBST([ENABLE_UNITTESTS])\n\nTEST_LDFLAGS=\"${OPTIONAL_CRYPTO_LIBS} ${OPTIONAL_PKCS11_HELPER_LIBS} ${OPTIONAL_LIBCAPNG_LIBS}\"\nTEST_LDFLAGS=\"${TEST_LDFLAGS} ${OPTIONAL_LIBNL_GENL_LIBS}\"\nTEST_LDFLAGS=\"${TEST_LDFLAGS} ${OPTIONAL_LZO_LIBS} ${CMOCKA_LIBS}\"\nTEST_CFLAGS=\"${OPTIONAL_CRYPTO_CFLAGS} ${OPTIONAL_PKCS11_HELPER_CFLAGS} ${OPTIONAL_LIBCAPNG_CFLAGS}\"\nTEST_CFLAGS=\"${TEST_CFLAGS} ${OPTIONAL_LIBNL_GENL_CFLAGS} ${OPTIONAL_LZO_CFLAGS}\"\nTEST_CFLAGS=\"${TEST_CFLAGS} -I\\$(top_srcdir)/include ${CMOCKA_CFLAGS}\"\n\nAC_SUBST([TEST_LDFLAGS])\nAC_SUBST([TEST_CFLAGS])\n\nAC_CONFIG_FILES([\n\tMakefile\n\tdistro/Makefile\n\tdistro/systemd/Makefile\n\tdistro/dns-scripts/Makefile\n\tdoc/Makefile\n\tdoc/doxygen/Makefile\n\tdoc/doxygen/openvpn.doxyfile\n\tinclude/Makefile\n\tsample/sample-plugins/Makefile\n\tsrc/Makefile\n\tsrc/compat/Makefile\n\tsrc/openvpn/Makefile\n\tsrc/openvpnmsica/Makefile\n\tsrc/openvpnserv/Makefile\n\tsrc/plugins/Makefile\n\tsrc/plugins/auth-pam/Makefile\n\tsrc/plugins/down-root/Makefile\n\tsrc/tapctl/Makefile\n\ttests/Makefile\n        tests/unit_tests/Makefile\n        tests/unit_tests/example_test/Makefile\n        tests/unit_tests/openvpn/Makefile\n        tests/unit_tests/openvpnserv/Makefile\n        tests/unit_tests/plugins/Makefile\n        tests/unit_tests/plugins/auth-pam/Makefile\n\tsample/Makefile\n])\nAC_CONFIG_FILES([tests/t_client.sh], [chmod +x tests/t_client.sh])\nAC_OUTPUT\n"
  },
  {
    "path": "contrib/OCSP_check/OCSP_check.sh",
    "content": "#!/bin/sh\n\n# Sample script to perform OCSP queries with OpenSSL\n# given a certificate serial number.\n\n# If you run your own CA, you can set up a very simple\n# OCSP server using the -port option to \"openssl ocsp\".\n\n# Full documentation and examples:\n# https://docs.openssl.org/master/man1/openssl-ocsp/#openssl-ocsp\n\n\n# Edit the following values to suit your needs\n\n# OCSP responder URL (mandatory)\n# YOU MUST UNCOMMENT ONE OF THESE AND SET IT TO A VALID SERVER\n#ocsp_url=\"http://ocsp.example.com/\"\n#ocsp_url=\"https://ocsp.secure.example.com/\"\n\n# Path to issuer certificate (mandatory)\n# YOU MUST SET THIS TO THE PATH TO THE CA CERTIFICATE\nissuer=\"/path/to/CAcert.crt\"\n\n# use a nonce in the query, set to \"-no_nonce\" to not use it\nnonce=\"-nonce\"\n\n# Verify the response\n# YOU MUST SET THIS TO THE PATH TO THE RESPONSE VERIFICATION CERT\nverify=\"/path/to/CAcert.crt\"\n\n# Depth in the certificate chain where the cert to verify is.\n# Set to -1 to run the verification at every level (NOTE that\n# in that case you need a more complex script as the various\n# parameters for the query will likely be different at each level)\n# \"0\" is the usual value here, where the client certificate is\ncheck_depth=0\n\ncur_depth=$1     # this is the *CURRENT* depth\ncommon_name=$2   # CN in case you need it\n\n# minimal sanity checks\n\nerr=0\nif [ -z \"$issuer\" ] || [ ! -e \"$issuer\" ]; then\n  echo \"Error: issuer certificate undefined or not found!\" >&2\n  err=1\nfi\n\nif [ -z \"$verify\" ] || [ ! -e \"$verify\" ]; then\n  echo \"Error: verification certificate undefined or not found!\" >&2\n  err=1\nfi\n\nif [ -z \"$ocsp_url\" ]; then\n  echo \"Error: OCSP server URL not defined!\" >&2\n  err=1\nfi\n\nif [ $err -eq 1 ]; then\n  echo \"Did you forget to customize the variables in the script?\" >&2\n  exit 1\nfi\n\n# begin\nif [ $check_depth -eq -1 ] || [ $cur_depth -eq $check_depth ]; then\n\n  eval serial=\"\\$tls_serial_${cur_depth}\"\n\n  # To successfully complete, the following must happen:\n  #\n  # - The serial number must not be empty\n  # - The exit status of \"openssl ocsp\" must be zero\n  # - The output of the above command must contain the line\n  #   \"${serial}: good\"\n  #\n  # Everything else fails with exit status 1.\n\n  if [ -n \"$serial\" ]; then\n\n    # This is only an example; you are encouraged to run this command (without\n    # redirections) manually against your or your CA's OCSP server to see how\n    # it responds, and adapt accordingly.\n    # Sample output that is assumed here:\n    #\n    # Response verify OK\n    # 4287405: good\n    #      This Update: Apr 24 19:38:49 2010 GMT\n    #      Next Update: May  2 14:23:42 2010 GMT\n    #\n    # NOTE: It is needed to check the exit code of OpenSSL explicitly.  OpenSSL\n    #       can in some circumstances give a \"good\" result if it could not\n    #       reach the OSCP server.  In this case, the exit code will indicate\n    #       if OpenSSL itself failed or not.  If OpenSSL's exit code is not 0,\n    #       don't trust the OpenSSL status.\n\n    status=$(openssl ocsp -issuer \"$issuer\" \\\n                    \"$nonce\" \\\n                    -CAfile \"$verify\" \\\n                    -url \"$ocsp_url\" \\\n                    -serial \"${serial}\" 2>&1)\n\n    if [ $? -eq 0 ]; then\n      # check if ocsp didn't report any errors\n      if echo \"$status\" | grep -Eq \"(error|fail)\"; then\n          exit 1\n      fi\n      # check that the reported status of certificate is ok\n      if echo \"$status\" | grep -Eq \"^${serial}: good\"; then\n        # check if signature on the OCSP response verified correctly\n        if echo \"$status\" | grep -Eq \"^Response verify OK\"; then\n            exit 0\n        fi\n      fi\n    fi\n  fi\n  # if we get here, something was wrong\n  exit 1\nfi\n"
  },
  {
    "path": "contrib/README",
    "content": "This directory contains scripts and patches contributed\nby users.\n"
  },
  {
    "path": "contrib/cmake/git-version.py",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2022-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2022-2022 Lev Stipakov <lev@lestisoftware.fi>\n#\n#  This program is free software; you can redistribute it and/or modify\n#  it under the terms of the GNU General Public License version 2\n#  as published by the Free Software Foundation.\n#\n#  This program is distributed in the hope that it will be useful,\n#  but WITHOUT ANY WARRANTY; without even the implied warranty of\n#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n#  GNU General Public License for more details.\n#\n#  You should have received a copy of the GNU General Public License along\n#  with this program; if not, see <https://www.gnu.org/licenses/>.\n#\n\n# Usage: ./git-version.py [directory]\n# Find a good textual representation of the git commit currently checked out.\n# Make that representation available as CONFIGURE_GIT_REVISION in\n# <directory>/config-version.h.\n# It will prefer a tag name if it is checked out exactly, otherwise will use\n# the branch name. 'none' if no branch is checked out (detached HEAD).\n# This is used to enhance the output of openvpn --version with Git information.\n\nimport os\nimport sys\nimport subprocess\n\ndef run_command(args):\n    sp = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)\n    o, _ = sp.communicate()\n    return o.decode(\"utf-8\")[:-1]\n\ndef get_branch_commit_id():\n    commit_id = run_command([\"git\", \"rev-parse\", \"--short=16\", \"HEAD\"])\n    if not commit_id:\n        raise\n    branch = run_command([\"git\", \"describe\", \"--exact-match\"])\n    if not branch:\n        # this returns an array like [\"master\"] or [\"release\", \"2.6\"]\n        branch = run_command([\"git\", \"rev-parse\", \"--symbolic-full-name\", \"HEAD\"]).split(\"/\")[2:]\n        if not branch:\n            branch = [\"none\"]\n        branch = \"/\" .join(branch) # handle cases like release/2.6\n\n    return branch, commit_id\n\ndef main():\n    try:\n        branch, commit_id = get_branch_commit_id()\n    except:\n        branch, commit_id = \"unknown\", \"unknown\"\n\n    prev_content = \"\"\n\n    name = os.path.join(\"%s\" %  (sys.argv[1] if len(sys.argv) > 1 else \".\"), \"config-version.h\")\n    try:\n        with open(name, \"r\") as f:\n            prev_content = f.read()\n    except:\n        # file doesn't exist\n        pass\n\n    content = \"#define CONFIGURE_GIT_REVISION \\\"%s/%s\\\"\\n\" % (branch, commit_id)\n    content += \"#define CONFIGURE_GIT_FLAGS \\\"\\\"\\n\"\n\n    if prev_content != content:\n        print(\"Writing %s\" % name)\n        with open(name, \"w\") as f:\n            f.write(content)\n    else:\n        print(\"Content of %s hasn't changed\" % name)\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "contrib/cmake/parse-version.m4.py",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2022-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2022-2022 Lev Stipakov <lev@lestisoftware.fi>\n#\n#  This program is free software; you can redistribute it and/or modify\n#  it under the terms of the GNU General Public License version 2\n#  as published by the Free Software Foundation.\n#\n#  This program is distributed in the hope that it will be useful,\n#  but WITHOUT ANY WARRANTY; without even the implied warranty of\n#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n#  GNU General Public License for more details.\n#\n#  You should have received a copy of the GNU General Public License along\n#  with this program; if not, see <https://www.gnu.org/licenses/>.\n#\n\n# Usage: ./parse-version.m4.py m4file [directory]\n# Read <m4file>, extract all lines looking like M4 define(), and translate\n# them into CMake style set(). Those are then written out to file\n# <directory>/version.cmake.\n# Intended to be used on top-level version.m4 file.\n\nimport os\nimport re\nimport sys\n\ndef main():\n    assert len(sys.argv) > 1\n    version_path = sys.argv[1]\n    output = []\n    with open(version_path, 'r') as version_file:\n        for line in version_file:\n            match = re.match(r'[ \\t]*define\\(\\[(.*)\\],[ \\t]*\\[(.*)\\]\\)[ \\t]*', line)\n            if match is not None:\n                output.append(match.expand(r'set(\\1 \\2)'))\n    out_path = os.path.join(\"%s\" %  (sys.argv[2] if len(sys.argv) > 2 else \".\"), \"version.cmake\")\n\n    prev_content = \"\"\n    try:\n        with open(out_path, \"r\") as out_file:\n            prev_content = out_file.read()\n    except:\n        # file doesn't exist\n        pass\n\n    content = \"\\n\".join(output) + \"\\n\"\n    if prev_content != content:\n        print(\"Writing %s\" % out_path)\n        with open(out_path, \"w\") as out_file:\n            out_file.write(content)\n    else:\n        print(\"Content of %s hasn't changed\" % out_path)\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "contrib/extract-crl/extractcrl.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n'''\nHelper script for CRL (certificate revocation list) file extraction\nto a directory containing files named as decimal serial numbers of\nthe revoked certificates, to be used with OpenVPN CRL directory\nverify mode. To enable this mode, directory and 'dir' flag needs to\nbe specified as parameters of '--crl-verify' option.\nFor more information refer OpenVPN tls-options.rst.\n\nUsage example:\n    extractcrl.py -f pem /path/to/crl.pem /path/to/outdir\n    extractcrl.py -f der /path/to/crl.crl /path/to/outdir\n    cat /path/to/crl.pem | extractcrl.py -f pem - /path/to/outdir\n    cat /path/to/crl.crl | extractcrl.py -f der - /path/to/outdir\n\nOutput example:\n    Loaded:  309797 revoked certs in 4.136s\n    Scanned: 312006 files in 0.61s\n    Created: 475 files in 0.05s\n    Removed: 2684 files in 0.116s\n'''\n\nimport argparse\nimport os\nimport sys\nimport time\nfrom subprocess import check_output\n\nFILETYPE_PEM = 'PEM'\nFILETYPE_DER = 'DER'\n\ndef measure_time(method):\n    def elapsed(*args, **kwargs):\n        start = time.time()\n        result = method(*args, **kwargs)\n        return result, round(time.time() - start, 3)\n    return elapsed\n\n@measure_time\ndef load_crl(filename, format):\n\n    def try_openssl_module(filename, format):\n        from cryptography import x509\n        load_crl_functions = {\n            FILETYPE_PEM: x509.load_pem_x509_crl,\n            FILETYPE_DER: x509.load_der_x509_crl\n        }\n        if filename == '-':\n            crl = load_crl_functions[format](sys.stdin.buffer.read())\n        else:\n            with open(filename, 'rb') as f:\n                crl = load_crl_functions[format](f.read())\n        return set(r.serial_number for r in crl)\n\n    def try_openssl_exec(filename, format):\n        args = ['openssl', 'crl', '-inform', format, '-text']\n        if filename != '-':\n            args += ['-in', filename]\n        serials = set()\n        for line in check_output(args, universal_newlines=True).splitlines():\n            _, _, serial = line.partition('Serial Number:')\n            if serial:\n                serials.add(int(serial.strip(), 16))\n        return serials\n\n    try:\n        return try_openssl_module(filename, format)\n    except ImportError:\n        return try_openssl_exec(filename, format)\n\n@measure_time\ndef scan_dir(dirname):\n    _, _, files = next(os.walk(dirname))\n    return set(int(f) for f in files if f.isdigit())\n\n@measure_time\ndef create_new_files(dirname, newset, oldset):\n    addset = newset.difference(oldset)\n    for serial in addset:\n        try:\n            with open(os.path.join(dirname, str(serial)), 'xb'): pass\n        except FileExistsError:\n            pass\n    return addset\n\n@measure_time\ndef remove_old_files(dirname, newset, oldset):\n    delset = oldset.difference(newset)\n    for serial in delset:\n        try:\n            os.remove(os.path.join(dirname, str(serial)))\n        except FileNotFoundError:\n            pass\n    return delset\n\ndef check_crlfile(arg):\n    if arg == '-' or os.path.isfile(arg):\n        return arg\n    raise argparse.ArgumentTypeError('No such file \"{}\"'.format(arg))\n\ndef check_outdir(arg):\n    if os.path.isdir(arg):\n        return arg\n    raise argparse.ArgumentTypeError('No such directory: \"{}\"'.format(arg))\n\ndef main():\n    parser = argparse.ArgumentParser(description='OpenVPN CRL extractor')\n    parser.add_argument('-f', '--format',\n        type=str.upper,\n        default=FILETYPE_PEM, choices=[FILETYPE_PEM, FILETYPE_DER],\n        help='input CRL format - default {}'.format(FILETYPE_PEM)\n    )\n    parser.add_argument('crlfile', metavar='CRLFILE|-',\n        type=lambda x: check_crlfile(x),\n        help='input CRL file or \"-\" for stdin'\n    )\n    parser.add_argument('outdir', metavar='OUTDIR',\n        type=lambda x: check_outdir(x),\n        help='output directory for serials numbers'\n    )\n    args = parser.parse_args()\n\n    certs, t = load_crl(args.crlfile, args.format)\n    print('Loaded:  {} revoked certs in {}s'.format(len(certs), t))\n\n    files, t = scan_dir(args.outdir)\n    print('Scanned: {} files in {}s'.format(len(files), t))\n\n    created, t = create_new_files(args.outdir, certs, files)\n    print('Created: {} files in {}s'.format(len(created), t))\n\n    removed, t = remove_old_files(args.outdir, certs, files)\n    print('Removed: {} files in {}s'.format(len(removed), t))\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "contrib/multilevel-init.patch",
    "content": "--- /etc/init.d/openvpn\t2004-05-12 20:30:06.000000000 +0200\n+++ openvpn\t2004-05-12 20:34:33.000000000 +0200\n@@ -58,13 +58,13 @@\n #     returning success or failure status to caller (James Yonan).\n \n # Location of openvpn binary\n-openvpn=\"/usr/sbin/openvpn\"\n+openvpn=/usr/sbin/openvpn\n \n # Lockfile\n-lock=\"/var/lock/subsys/openvpn\"\n+lock=/var/lock/subsys/openvpn\n \n # PID directory\n-piddir=\"/var/run/openvpn\"\n+piddir=/var/run/openvpn\n \n # Our working directory\n work=/etc/openvpn\n@@ -106,7 +106,7 @@\n \n \tif [ -f $lock ]; then\n \t    # we were not shut down correctly\n-\t    for pidf in `/bin/ls $piddir/*.pid $piddir/*/*.pid 2>/dev/null`; do\n+\t    for pidf in `find $piddir -name \"*.pid\" 2>/dev/null`; do\n \t      if [ -s $pidf ]; then\n \t\tkill `cat $pidf` >/dev/null 2>&1\n \t      fi\n@@ -116,12 +116,12 @@\n \t    sleep 2\n \tfi\n \n-\trm -f $piddir/*.pid $piddir/*/*.pid\n+\tfind $piddir -name \"*.pid\"|xargs rm -f\n \n \t# Start every .conf in $work and run .sh if exists\n \terrors=0\n \tsuccesses=0\n-\tfor c in `/bin/ls *.conf */*.conf 2>/dev/null`; do\n+\tfor c in `find * -name \"*.conf\" 2>/dev/null`; do\n \t    bn=${c%%.conf}\n \t    if [ -f \"$bn.sh\" ]; then\n \t\t. $bn.sh\n@@ -147,7 +147,7 @@\n \t;;\n   stop)\n \techo -n $\"Shutting down openvpn: \"\n-\tfor pidf in `/bin/ls $piddir/*.pid $piddir/*/*.pid 2>/dev/null`; do\n+\tfor pidf in `find $piddir -name \"*.pid\" 2>/dev/null`; do\n \t  if [ -s $pidf ]; then\n \t    kill `cat $pidf` >/dev/null 2>&1\n \t  fi\n@@ -163,7 +163,7 @@\n \t;;\n   reload)\n \tif [ -f $lock ]; then\n-\t    for pidf in `/bin/ls $piddir/*.pid $piddir/*/*.pid 2>/dev/null`; do\n+\t    for pidf in `find $piddir -name \"*.pid\" 2>/dev/null`; do\n \t\tif [ -s $pidf ]; then\n \t\t    kill -HUP `cat $pidf` >/dev/null 2>&1\n \t\tfi\n@@ -175,7 +175,7 @@\n \t;;\n   reopen)\n \tif [ -f $lock ]; then\n-\t    for pidf in `/bin/ls $piddir/*.pid $piddir/*/*.pid 2>/dev/null`; do\n+\t    for pidf in `find $piddir -name \"*.pid\" 2>/dev/null`; do\n \t\tif [ -s $pidf ]; then\n \t\t    kill -USR1 `cat $pidf` >/dev/null 2>&1\n \t\tfi\n@@ -195,7 +195,7 @@\n \t;;\n   status)\n \tif [ -f $lock ]; then\n-\t    for pidf in `/bin/ls $piddir/*.pid $piddir/*/*.pid 2>/dev/null`; do\n+\t    for pidf in `find $piddir -name \"*.pid\" 2>/dev/null`; do\n \t\tif [ -s $pidf ]; then\n \t\t    kill -USR2 `cat $pidf` >/dev/null 2>&1\n \t\tfi\n"
  },
  {
    "path": "contrib/openvpn-fwmarkroute-1.00/README",
    "content": "OpenVPN fwmark Routing\nSean Reifschneider, <jafo@tummy.com>\nThursday November 27, 2003\n==========================\n\nThese scripts can be used with OpenVPN up and down scripts to set up\nrouting on a Linux system such that the VPN traffic is sent via normal\nnetwork connectivity, but other traffic to that network runs over the VPN.\nThe idea is to allow encryption of data to the network the remote host is\non, without interfering with the VPN traffic.  You can't simply add a route\nto the remote network, becaues that will cause the VPN traffic to also try\nto run over the VPN, and breaks the VPN.\n\nThese scripts use the Linux \"fwmark\" iptables rules to specify routing\nbased not only on IP address, but also by port and protocol.  This allows\nyou to effectively say \"if the packet is to this IP address on this port\nusing this protocol, then use the normal default gateway, otherwise use the\nVPN gateway.\n\nThis is set up on the client VPN system, not the VPN server.  These scripts\nalso set up all ICMP echo-responses to run across the VPN.  You can\ncomment the lines in the scripts to disable this, but I find this useful\nat coffee shops which have networks that block ICMP.\n\nTo configure this, you need to set up these scripts as your up and down\nscripts in the config file.  You will need to set these values in the\nconfig file:\n\n   up /etc/openvpn/fwmarkroute.up\n   down /etc/openvpn/fwmarkroute.down\n   up-restart\n   up-delay\n\n   setenv remote_netmask_bits 24\n\nNote: For this to work, you can't set the \"user\" or \"group\" config options,\nbecause then the scripts will not run as root.\n\nThe last setting allows you to control the size of the network the remote\nsystem is on.  The remote end has to be set up to route, probably with\nmasquerading or NAT.  The network this netmask relates to is calculated\nusing the value of \"remote\" in the conf file.\n\nSean\n"
  },
  {
    "path": "contrib/openvpn-fwmarkroute-1.00/fwmarkroute.down",
    "content": "#!/bin/sh\n#\n#  Bring down vpn routing.\n\n#  calculate the network address\nremote_network=`ipcalc -n \"$remote\"/\"$remote_netmask_bits\"`\nremote_network=\"${remote_network#*=}\"\n\n#  clear routing via VPN\nip route del \"$remote_network\"/\"$remote_netmask_bits\" via \"$5\" table vpn.out\nip route del table vpnonly.out via \"$5\"\niptables -D OUTPUT -t mangle -p \"$proto\" \\\n\t\t-d \"$remote_network\"/\"$remote_netmask_bits\" \\\n\t\t--dport \"$remote_port\" -j ACCEPT\niptables -D OUTPUT -t mangle -d \"$remote\" -j MARK --set-mark 2\n\n#  undo the ICMP ping tunneling\niptables -D OUTPUT -t mangle --protocol icmp --icmp-type echo-request \\\n\t\t-j MARK --set-mark 3\n\n#  flush route cache\nip route flush cache\n"
  },
  {
    "path": "contrib/openvpn-fwmarkroute-1.00/fwmarkroute.up",
    "content": "#!/bin/sh\n#\n#  Bring up vpn routing.\n\n#  calculate the network address\nremote_network=`ipcalc -n \"$remote\"/\"$remote_netmask_bits\"`\nremote_network=\"${remote_network#*=}\"\n\n#  add the stuff that doesn't change if it's not already there\ngrep -q '^202 ' /etc/iproute2/rt_tables \nif [ \"$?\" -ne 0 ]\nthen\n\techo 202 vpn.out >> /etc/iproute2/rt_tables\nfi\ngrep -q '^203 ' /etc/iproute2/rt_tables \nif [ \"$?\" -ne 0 ]\nthen\n\techo 203 vpnonly.out >> /etc/iproute2/rt_tables\nfi\nip rule ls | grep -q 'lookup vpn.out *$'\nif [ \"$?\" -ne 0 ]\nthen\n\tip rule add fwmark 2 table vpn.out\nfi\nip rule ls | grep -q 'lookup vpnonly.out *$'\nif [ \"$?\" -ne 0 ]\nthen\n\tip rule add fwmark 3 table vpnonly.out\nfi\n\n#  route VPN traffic using the normal table\niptables -A OUTPUT -t mangle -p \"$proto\" -d \"$remote\" --dport \"$remote_port\" \\\n\t\t-j ACCEPT\n\n#  route all other traffic to that host via VPN\niptables -A OUTPUT -t mangle -d \"$remote_network\"/\"$remote_netmask_bits\" \\\n\t\t-j MARK --set-mark 2\n\n#  route all ICMP pings over the VPN\niptables -A OUTPUT -t mangle --protocol icmp --icmp-type echo-request \\\n\t\t-j MARK --set-mark 3\n\n#  NAT traffic going over the VPN, so it doesn't have an unknown address\niptables -t nat -A POSTROUTING -o \"$1\" -j SNAT --to-source \"$4\"\n\n#  add routing commands\nip route add \"$remote_network\"/\"$remote_netmask_bits\" via \"$5\" table vpn.out\nip route add table vpnonly.out via \"$5\"\nip route flush cache\n"
  },
  {
    "path": "contrib/vcpkg-manifests/mingw/vcpkg.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json\",\n  \"name\": \"openvpn\",\n  \"version\": \"2.7\",\n  \"dependencies\": [\n      \"openssl\",\n      \"tap-windows6\",\n      \"lzo\",\n      \"lz4\",\n      \"pkcs11-helper\",\n      \"cmocka\"\n  ]\n}\n"
  },
  {
    "path": "contrib/vcpkg-manifests/windows/vcpkg.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json\",\n  \"name\": \"openvpn\",\n  \"version\": \"2.7\",\n  \"dependencies\": [\n      {\n        \"name\": \"openssl\",\n        \"features\": [\"tools\"]\n      },\n      \"tap-windows6\",\n      \"lzo\",\n      \"lz4\",\n      \"pkcs11-helper\",\n      \"cmocka\",\n      {\n          \"name\": \"pkgconf\",\n          \"host\": true\n      }\n  ]\n}\n"
  },
  {
    "path": "contrib/vcpkg-ports/pkcs11-helper/config-w32-vc.h.in-indicate-OpenSSL.patch",
    "content": "From c2293864de70fec322fe7e559055530ef56b9641 Mon Sep 17 00:00:00 2001\nFrom: Lev Stipakov <lev@openvpn.net>\nDate: Tue, 11 Jan 2022 13:35:42 +0200\nSubject: [PATCH] config-w32-vc.h.in: indicate OpenSSL EC support\n\nSigned-off-by: Lev Stipakov <lev@openvpn.net>\n---\n config-w32-vc.h.in | 12 ++++++++++++\n 1 file changed, 12 insertions(+)\n\ndiff --git a/config-w32-vc.h b/config-w32-vc.h\nindex 6d94841..db83825 100644\n--- a/config-w32-vc.h\n+++ b/config-w32-vc.h\n@@ -218,3 +218,15 @@\n \n /* Define to 1 if you have the `DSA_SIG_set0' function. */\n #define HAVE_DSA_SIG_SET0 1\n+\n+/* Define to 1 if you have the `ECDSA_SIG_set0' function. */\n+#define HAVE_ECDSA_SIG_SET0 1\n+\n+/* Define to 1 if you have the `EC_KEY_METHOD_get_sign' function. */\n+#define HAVE_EC_KEY_METHOD_GET_SIGN 1\n+\n+/* Define to 1 if you have the `EC_KEY_METHOD_set_sign' function. */\n+#define HAVE_EC_KEY_METHOD_SET_SIGN 1\n+\n+/* Define to 1 if OpenSSL has EC support. */\n+#define ENABLE_PKCS11H_OPENSSL_EC 1\n-- \n2.23.0.windows.1\n\n"
  },
  {
    "path": "contrib/vcpkg-ports/pkcs11-helper/nmake-compatibility-with-vcpkg-nmake.patch",
    "content": "From 2d3a2c05383f653544b9c7194dd1349c6d5f3067 Mon Sep 17 00:00:00 2001\nFrom: Lev Stipakov <lev@openvpn.net>\nDate: Tue, 11 Jan 2022 13:24:51 +0200\nSubject: [PATCH] nmake: compatibility with vcpkg nmake\n\nRemove options which contradict or already set\nby vcpkg nmake scripts.\n\nSigned-off-by: Lev Stipakov <lev@openvpn.net>\n---\n lib/Makefile.w32-vc | 8 ++------\n 1 file changed, 2 insertions(+), 6 deletions(-)\n\ndiff --git a/lib/Makefile.w32-vc b/lib/Makefile.w32-vc\nindex 96f1f89..be68a00 100644\n--- a/lib/Makefile.w32-vc\n+++ b/lib/Makefile.w32-vc\n@@ -75,15 +75,11 @@ OPENSSL_LIBS=-LIBPATH:$(OPENSSL_LIB) user32.lib advapi32.lib $(OPENSSL_STATIC)\n CFLAGS = -I../include $(OPENSSL_CFLAGS) -DWIN32 -DWIN32_LEAN_AND_MEAN -D_MBCS -D_CRT_SECURE_NO_DEPRECATE -D_WIN32_WINNT=0x0400\n CC=cl.exe\n RC=rc.exe\n-CCPARAMS=/nologo /W3 /O2 /FD /c\n-\n-CCPARAMS=$(CCPARAMS) /MD\n-CFLAGS=$(CFLAGS) -DNDEBUG\n+CCPARAMS=/c\n \n LINK32=link.exe\n LIB32=lib.exe\n-LINK32_FLAGS=/nologo /subsystem:windows /dll /incremental:no /release\n-LIB32_FLAGS=/nologo\n+LINK32_FLAGS=/dll\n \n HEADERS = \\\n \tconfig.h \\\n-- \n2.23.0.windows.1\n\n"
  },
  {
    "path": "contrib/vcpkg-ports/pkcs11-helper/pkcs11-helper-001-RFC7512.patch",
    "content": "upstream PR: https://github.com/OpenSC/pkcs11-helper/pull/4\n\nRebased to 1.31.0 by selva.nair@gmail.com\n\ncommit 90590b02085edc3830bdfe0942a46c4e7bf3f1ab (HEAD -> master)\nAuthor: David Woodhouse <David.Woodhouse@intel.com>\nDate:   Thu Apr 30 14:58:24 2015 +0100\n\n    Serialize to RFC7512-compliant PKCS#11 URIs\n    \n    Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>\n\ncommit 4d5280da8df591aab701dff4493d13a835a9b29c\nAuthor: David Woodhouse <David.Woodhouse@intel.com>\nDate:   Wed Dec 10 14:00:21 2014 +0000\n\n    Accept RFC7512-compliant PKCS#11 URIs as serialized token/certificate IDs\n    \n    The old format is still accepted for compatibility.\n    \n    Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>\n\ncommit 14e09211c3d50eb06825090c9765e4382cf52f19\nAuthor: David Woodhouse <David.Woodhouse@intel.com>\nDate:   Sun Dec 14 19:42:18 2014 +0000\n\n    Stop _pkcs11h_util_hexToBinary() checking for trailing NUL\n    \n    We are going to want to use this for parsing %XX hex escapes in RFC7512\n    PKCS#11 URIs, where we cannot expect a trailing NUL. Since there's only\n    one existing caller at the moment, it's simple just to let the caller\n    have responsibility for that check.\n    \n    Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>\ndiff --git a/lib/pkcs11h-serialization.c b/lib/pkcs11h-serialization.c\nindex ad275f8..1d077e4 100644\n--- a/lib/pkcs11h-serialization.c\n+++ b/lib/pkcs11h-serialization.c\n@@ -61,29 +61,127 @@\n \n #if defined(ENABLE_PKCS11H_TOKEN) || defined(ENABLE_PKCS11H_CERTIFICATE)\n \n+#define URI_SCHEME \"pkcs11:\"\n+\n+#define token_field_ofs(field) ((unsigned long)&(((struct pkcs11h_token_id_s *)0)->field))\n+#define token_field_size(field) sizeof((((struct pkcs11h_token_id_s *)0)->field))\n+#define token_field(name, field) { name \"=\", sizeof(name), \\\n+\t\t\t\t   token_field_ofs(field), token_field_size(field) }\n+\n+static struct {\n+\tconst char const *name;\n+\tsize_t namelen;\n+\tunsigned long field_ofs;\n+\tsize_t field_size;\n+} __token_fields[] = {\n+\ttoken_field (\"model\", model),\n+\ttoken_field (\"token\", label),\n+\ttoken_field (\"manufacturer\", manufacturerID ),\n+\ttoken_field (\"serial\", serialNumber ),\n+\t{ NULL },\n+};\n+\n+#define               P11_URL_VERBATIM      \"abcdefghijklmnopqrstuvwxyz\" \\\n+                                            \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\" \\\n+                                            \"0123456789_-.\"\n+\n+static\n+int\n+__token_attr_escape(char *uri, char *attr, size_t attrlen)\n+{\n+\tint len = 0, i;\n+\n+\tfor (i = 0; i < attrlen; i++) {\n+\t\tif ((attr[i] != '\\x0') && strchr(P11_URL_VERBATIM, attr[i])) {\n+\t\t\tif (uri) {\n+\t\t\t\t*(uri++) = attr[i];\n+\t\t\t}\n+\t\t\tlen++;\n+\t\t} else {\n+\t\t\tif (uri) {\n+\t\t\t\tsprintf(uri, \"%%%02x\", (unsigned char)attr[i]);\n+\t\t\t\turi += 3;\n+\t\t\t}\n+\t\t\tlen += 3;\n+\t\t}\n+\t}\n+\treturn len;\n+}\n+\n+static\n+CK_RV\n+__generate_pkcs11_uri (\n+\tOUT char * const sz,\n+\tIN OUT size_t *max,\n+\tIN const pkcs11h_certificate_id_t certificate_id,\n+\tIN const pkcs11h_token_id_t token_id\n+) {\n+\tsize_t _max;\n+\tchar *p = sz;\n+\tint i;\n+\n+\t_PKCS11H_ASSERT (max!=NULL);\n+\t_PKCS11H_ASSERT (token_id!=NULL);\n+\n+\t_max = strlen(URI_SCHEME);\n+\tfor (i = 0; __token_fields[i].name; i++) {\n+\t\tchar *field = ((char *)token_id) + __token_fields[i].field_ofs;\n+\n+\t\t_max += __token_fields[i].namelen;\n+\t\t_max += __token_attr_escape (NULL, field, strlen(field));\n+\t\t_max++; /* For a semicolon or trailing NUL */\n+\t}\n+\tif (certificate_id) {\n+\t\t_max += strlen (\";id=\");\n+\t\t_max += __token_attr_escape (NULL,\n+\t\t\t\t\t     (char *)certificate_id->attrCKA_ID,\n+\t\t\t\t\t     certificate_id->attrCKA_ID_size);\n+\t}\n+\n+\tif (!sz) {\n+\t\t*max = _max;\n+\t\treturn CKR_OK;\n+\t}\n+\n+\tif (sz && *max < _max)\n+\t\treturn CKR_ATTRIBUTE_VALUE_INVALID;\n+\n+\tp += sprintf(p, URI_SCHEME);\n+\tfor (i = 0; __token_fields[i].name; i++) {\n+\t\tchar *field = ((char *)token_id) + __token_fields[i].field_ofs;\n+\n+\t\tp += sprintf (p, \"%s\", __token_fields[i].name);\n+\t\tp += __token_attr_escape (p, field, strlen(field));\n+\t\t*(p++) = ';';\n+\t}\n+\tif (certificate_id) {\n+\t\tp += sprintf (p, \"id=\");\n+\t\tp += __token_attr_escape (p,\n+\t\t\t\t\t  (char *)certificate_id->attrCKA_ID,\n+\t\t\t\t\t  certificate_id->attrCKA_ID_size);\n+\t} else {\n+\t\t/* Remove the unneeded trailing semicolon */\n+\t\tp--;\n+\t}\n+\t*(p++) = 0;\n+\n+\t*max = _max;\n+\n+\treturn CKR_OK;\n+}\n+\n CK_RV\n pkcs11h_token_serializeTokenId (\n \tOUT char * const sz,\n \tIN OUT size_t *max,\n \tIN const pkcs11h_token_id_t token_id\n ) {\n-\tconst char *sources[5];\n \tCK_RV rv = CKR_FUNCTION_FAILED;\n-\tsize_t n;\n-\tint e;\n \n \t/*_PKCS11H_ASSERT (sz!=NULL); Not required*/\n \t_PKCS11H_ASSERT (max!=NULL);\n \t_PKCS11H_ASSERT (token_id!=NULL);\n \n-\t{ /* Must be after assert */\n-\t\tsources[0] = token_id->manufacturerID;\n-\t\tsources[1] = token_id->model;\n-\t\tsources[2] = token_id->serialNumber;\n-\t\tsources[3] = token_id->label;\n-\t\tsources[4] = NULL;\n-\t}\n-\n \t_PKCS11H_DEBUG (\n \t\tPKCS11H_LOG_DEBUG2,\n \t\t\"PKCS#11: pkcs11h_token_serializeTokenId entry sz=%p, *max=\"P_Z\", token_id=%p\",\n@@ -92,67 +190,161 @@ pkcs11h_token_serializeTokenId (\n \t\t(void *)token_id\n \t);\n \n-\tn = 0;\n-\tfor (e=0;sources[e] != NULL;e++) {\n-\t\tsize_t t;\n-\t\tif (\n-\t\t\t(rv = _pkcs11h_util_escapeString (\n-\t\t\t\tNULL,\n-\t\t\t\tsources[e],\n-\t\t\t\t&t,\n-\t\t\t\t__PKCS11H_SERIALIZE_INVALID_CHARS\n-\t\t\t)) != CKR_OK\n-\t\t) {\n-\t\t\tgoto cleanup;\n+\trv = __generate_pkcs11_uri(sz, max, NULL, token_id);\n+\n+\t_PKCS11H_DEBUG (\n+\t\tPKCS11H_LOG_DEBUG2,\n+\t\t\"PKCS#11: pkcs11h_token_serializeTokenId return rv=%lu-'%s', *max=\"P_Z\", sz='%s'\",\n+\t\trv,\n+\t\tpkcs11h_getMessage (rv),\n+\t\t*max,\n+\t\tsz\n+\t);\n+\n+\treturn rv;\n+}\n+\n+static\n+CK_RV\n+__parse_token_uri_attr (\n+\tconst char *uri,\n+\tsize_t urilen,\n+\tchar *tokstr,\n+\tsize_t toklen,\n+\tsize_t *parsed_len\n+) {\n+\tsize_t orig_toklen = toklen;\n+\tCK_RV rv = CKR_OK;\n+\n+\twhile (urilen && toklen > 1) {\n+\t\tif (*uri == '%') {\n+\t\t\tsize_t size = 1;\n+\n+\t\t\tif (urilen < 3) {\n+\t\t\t\trv = CKR_ATTRIBUTE_VALUE_INVALID;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\trv = _pkcs11h_util_hexToBinary ((unsigned char *)tokstr,\n+\t\t\t\t\t\t\turi + 1, &size);\n+\t\t\tif (rv != CKR_OK) {\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\turi += 2;\n+\t\t\turilen -= 2;\n+\t\t} else {\n+\t\t\t*tokstr = *uri;\n \t\t}\n-\t\tn+=t;\n+\t\ttokstr++;\n+\t\turi++;\n+\t\ttoklen--;\n+\t\turilen--;\n+\t\ttokstr[0] = 0;\n \t}\n \n-\tif (sz != NULL) {\n-\t\tif (*max < n) {\n-\t\t\trv = CKR_ATTRIBUTE_VALUE_INVALID;\n-\t\t\tgoto cleanup;\n+\tif (urilen) {\n+\t\trv = CKR_ATTRIBUTE_VALUE_INVALID;\n+\t} else if (parsed_len) {\n+\t\t*parsed_len = orig_toklen - toklen;\n+\t}\n+\n+ done:\n+\treturn rv;\n+}\n+\n+static\n+CK_RV\n+__parse_pkcs11_uri (\n+\tOUT pkcs11h_token_id_t token_id,\n+\tOUT pkcs11h_certificate_id_t certificate_id,\n+\tIN const char * const sz\n+) {\n+\tconst char *end, *p;\n+\tCK_RV rv = CKR_OK;\n+\n+\t_PKCS11H_ASSERT (token_id!=NULL);\n+\t_PKCS11H_ASSERT (sz!=NULL);\n+\n+\tif (strncmp (sz, URI_SCHEME, strlen (URI_SCHEME)))\n+\t\treturn CKR_ATTRIBUTE_VALUE_INVALID;\n+\n+\tend = sz + strlen (URI_SCHEME) - 1;\n+\twhile (rv == CKR_OK && end[0] && end[1]) {\n+\t\tint i;\n+\n+\t\tp = end + 1;\n+\t        end = strchr (p, ';');\n+\t\tif (!end)\n+\t\t\tend = p + strlen(p);\n+\n+\t\tfor (i = 0; __token_fields[i].name; i++) {\n+\t\t\t/* Parse the token=, label=, manufacturer= and serial= fields */\n+\t\t\tif (!strncmp(p, __token_fields[i].name, __token_fields[i].namelen)) {\n+\t\t\t\tchar *field = ((char *)token_id) + __token_fields[i].field_ofs;\n+\n+\t\t\t\tp += __token_fields[i].namelen;\n+\t\t\t\trv = __parse_token_uri_attr (p, end - p, field,\n+\t\t\t\t\t\t\t     __token_fields[i].field_size,\n+\t\t\t\t\t\t\t     NULL);\n+\t\t\t\tif (rv != CKR_OK) {\n+\t\t\t\t\tgoto cleanup;\n+\t\t\t\t}\n+\n+\t\t\t\tgoto matched;\n+\t\t\t}\n \t\t}\n+\t\tif (certificate_id && !strncmp(p, \"id=\", 3)) {\n+\t\t\tp += 3;\n+\n+\t\t\trv = _pkcs11h_mem_malloc ((void *)&certificate_id->attrCKA_ID,\n+\t\t\t\t\t\t  end - p + 1);\n+\t\t\tif (rv != CKR_OK) {\n+\t\t\t\tgoto cleanup;\n+\t\t\t}\n \n-\t\tn = 0;\n-\t\tfor (e=0;sources[e] != NULL;e++) {\n-\t\t\tsize_t t = *max-n;\n-\t\t\tif (\n-\t\t\t\t(rv = _pkcs11h_util_escapeString (\n-\t\t\t\t\tsz+n,\n-\t\t\t\t\tsources[e],\n-\t\t\t\t\t&t,\n-\t\t\t\t\t__PKCS11H_SERIALIZE_INVALID_CHARS\n-\t\t\t\t)) != CKR_OK\n-\t\t\t) {\n+\t\t\trv = __parse_token_uri_attr (p, end - p,\n+\t\t\t\t\t\t     (char *)certificate_id->attrCKA_ID,\n+\t\t\t\t\t\t     end - p + 1,\n+\t\t\t\t\t\t     &certificate_id->attrCKA_ID_size);\n+\t\t\tif (rv != CKR_OK) {\n \t\t\t\tgoto cleanup;\n \t\t\t}\n-\t\t\tn+=t;\n-\t\t\tsz[n-1] = '/';\n+\n+\t\t\tgoto matched;\n \t\t}\n-\t\tsz[n-1] = '\\x0';\n-\t}\n \n-\t*max = n;\n-\trv = CKR_OK;\n+\t\t/* We don't parse object= because the match code doesn't support\n+\t\t   matching by label. */\n+\n+\t\t/* Failed to parse PKCS#11 URI element. */\n+\t\treturn CKR_ATTRIBUTE_VALUE_INVALID;\n \n+\t\tmatched:\n+\t\t    ;\n+\t}\n cleanup:\n+\t/* The matching code doesn't support support partial matches; it needs\n+\t * *all* of manufacturer, model, serial and label attributes to be\n+\t * defined. So reject partial URIs early instead of letting it do the\n+\t * wrong thing. We can maybe improve this later. */\n+\tif (!token_id->model[0] || !token_id->label[0] ||\n+\t    !token_id->manufacturerID[0] || !token_id->serialNumber[0]) {\n+\t\treturn CKR_ATTRIBUTE_VALUE_INVALID;\n+\t}\n \n-\t_PKCS11H_DEBUG (\n-\t\tPKCS11H_LOG_DEBUG2,\n-\t\t\"PKCS#11: pkcs11h_token_serializeTokenId return rv=%lu-'%s', *max=\"P_Z\", sz='%s'\",\n-\t\trv,\n-\t\tpkcs11h_getMessage (rv),\n-\t\t*max,\n-\t\tsz\n-\t);\n+\t/* For a certificate ID we need CKA_ID */\n+\tif (certificate_id && !certificate_id->attrCKA_ID_size) {\n+\t\treturn CKR_ATTRIBUTE_VALUE_INVALID;\n+\t}\n \n \treturn rv;\n }\n \n+static\n CK_RV\n-pkcs11h_token_deserializeTokenId (\n-\tOUT pkcs11h_token_id_t *p_token_id,\n+__pkcs11h_token_legacy_deserializeTokenId (\n+\tOUT pkcs11h_token_id_t token_id,\n \tIN const char * const sz\n ) {\n #define __PKCS11H_TARGETS_NUMBER 4\n@@ -161,24 +353,11 @@ pkcs11h_token_deserializeTokenId (\n \t\tsize_t s;\n \t} targets[__PKCS11H_TARGETS_NUMBER];\n \n-\tpkcs11h_token_id_t token_id = NULL;\n \tchar *p1 = NULL;\n \tchar *_sz = NULL;\n \tint e;\n \tCK_RV rv = CKR_FUNCTION_FAILED;\n \n-\t_PKCS11H_ASSERT (p_token_id!=NULL);\n-\t_PKCS11H_ASSERT (sz!=NULL);\n-\n-\t_PKCS11H_DEBUG (\n-\t\tPKCS11H_LOG_DEBUG2,\n-\t\t\"PKCS#11: pkcs11h_token_deserializeTokenId entry p_token_id=%p, sz='%s'\",\n-\t\t(void *)p_token_id,\n-\t\tsz\n-\t);\n-\n-\t*p_token_id = NULL;\n-\n \tif (\n \t\t(rv = _pkcs11h_mem_strdup (\n \t\t\t(void *)&_sz,\n@@ -190,10 +369,6 @@ pkcs11h_token_deserializeTokenId (\n \n \tp1 = _sz;\n \n-\tif ((rv = _pkcs11h_token_newTokenId (&token_id)) != CKR_OK) {\n-\t\tgoto cleanup;\n-\t}\n-\n \ttargets[0].p = token_id->manufacturerID;\n \ttargets[0].s = sizeof (token_id->manufacturerID);\n \ttargets[1].p = token_id->model;\n@@ -252,6 +427,51 @@ pkcs11h_token_deserializeTokenId (\n \t\tp1 = p2+1;\n \t}\n \n+\trv = CKR_OK;\n+\n+cleanup:\n+\n+\tif (_sz != NULL) {\n+\t\t_pkcs11h_mem_free ((void *)&_sz);\n+\t}\n+\n+\treturn rv;\n+#undef __PKCS11H_TARGETS_NUMBER\n+}\n+\n+CK_RV\n+pkcs11h_token_deserializeTokenId (\n+\tOUT pkcs11h_token_id_t *p_token_id,\n+\tIN const char * const sz\n+) {\n+\tpkcs11h_token_id_t token_id = NULL;\n+\tCK_RV rv = CKR_FUNCTION_FAILED;\n+\n+\t_PKCS11H_ASSERT (p_token_id!=NULL);\n+\t_PKCS11H_ASSERT (sz!=NULL);\n+\n+\t_PKCS11H_DEBUG (\n+\t\tPKCS11H_LOG_DEBUG2,\n+\t\t\"PKCS#11: pkcs11h_token_deserializeTokenId entry p_token_id=%p, sz='%s'\",\n+\t\t(void *)p_token_id,\n+\t\tsz\n+\t);\n+\n+\t*p_token_id = NULL;\n+\n+\tif ((rv = _pkcs11h_token_newTokenId (&token_id)) != CKR_OK) {\n+\t\tgoto cleanup;\n+\t}\n+\n+\tif (!strncmp (sz, URI_SCHEME, strlen (URI_SCHEME))) {\n+\t\trv = __parse_pkcs11_uri(token_id, NULL, sz);\n+\t} else {\n+\t\trv = __pkcs11h_token_legacy_deserializeTokenId(token_id, sz);\n+\t}\n+\tif (rv != CKR_OK) {\n+\t\tgoto cleanup;\n+\t}\n+\n \tstrncpy (\n \t\ttoken_id->display,\n \t\ttoken_id->label,\n@@ -264,11 +484,6 @@ pkcs11h_token_deserializeTokenId (\n \trv = CKR_OK;\n \n cleanup:\n-\n-\tif (_sz != NULL) {\n-\t\t_pkcs11h_mem_free ((void *)&_sz);\n-\t}\n-\n \tif (token_id != NULL) {\n \t\tpkcs11h_token_freeTokenId (token_id);\n \t}\n@@ -281,7 +496,6 @@ cleanup:\n \t);\n \n \treturn rv;\n-#undef __PKCS11H_TARGETS_NUMBER\n }\n \n #endif\t\t\t\t/* ENABLE_PKCS11H_TOKEN || ENABLE_PKCS11H_CERTIFICATE */\n@@ -295,9 +509,6 @@ pkcs11h_certificate_serializeCertificateId (\n \tIN const pkcs11h_certificate_id_t certificate_id\n ) {\n \tCK_RV rv = CKR_FUNCTION_FAILED;\n-\tsize_t saved_max = 0;\n-\tsize_t n = 0;\n-\tsize_t _max = 0;\n \n \t/*_PKCS11H_ASSERT (sz!=NULL); Not required */\n \t_PKCS11H_ASSERT (max!=NULL);\n@@ -311,42 +522,7 @@ pkcs11h_certificate_serializeCertificateId (\n \t\t(void *)certificate_id\n \t);\n \n-\tif (sz != NULL) {\n-\t\tsaved_max = n = *max;\n-\t}\n-\t*max = 0;\n-\n-\tif (\n-\t\t(rv = pkcs11h_token_serializeTokenId (\n-\t\t\tsz,\n-\t\t\t&n,\n-\t\t\tcertificate_id->token_id\n-\t\t)) != CKR_OK\n-\t) {\n-\t\tgoto cleanup;\n-\t}\n-\n-\t_max = n + certificate_id->attrCKA_ID_size*2 + 1;\n-\n-\tif (sz != NULL) {\n-\t\tif (saved_max < _max) {\n-\t\t\trv = CKR_ATTRIBUTE_VALUE_INVALID;\n-\t\t\tgoto cleanup;\n-\t\t}\n-\n-\t\tsz[n-1] = '/';\n-\t\trv = _pkcs11h_util_binaryToHex (\n-\t\t\tsz+n,\n-\t\t\tsaved_max-n,\n-\t\t\tcertificate_id->attrCKA_ID,\n-\t\t\tcertificate_id->attrCKA_ID_size\n-\t\t);\n-\t}\n-\n-\t*max = _max;\n-\trv = CKR_OK;\n-\n-cleanup:\n+\trv = __generate_pkcs11_uri(sz, max, certificate_id, certificate_id->token_id);\n \n \t_PKCS11H_DEBUG (\n \t\tPKCS11H_LOG_DEBUG2,\n@@ -360,27 +536,16 @@ cleanup:\n \treturn rv;\n }\n \n+static\n CK_RV\n-pkcs11h_certificate_deserializeCertificateId (\n-\tOUT pkcs11h_certificate_id_t * const p_certificate_id,\n+__pkcs11h_certificate_legacy_deserializeCertificateId (\n+\tOUT pkcs11h_certificate_id_t certificate_id,\n \tIN const char * const sz\n ) {\n-\tpkcs11h_certificate_id_t certificate_id = NULL;\n \tCK_RV rv = CKR_FUNCTION_FAILED;\n \tchar *p = NULL;\n \tchar *_sz = NULL;\n-\n-\t_PKCS11H_ASSERT (p_certificate_id!=NULL);\n-\t_PKCS11H_ASSERT (sz!=NULL);\n-\n-\t*p_certificate_id = NULL;\n-\n-\t_PKCS11H_DEBUG (\n-\t\tPKCS11H_LOG_DEBUG2,\n-\t\t\"PKCS#11: pkcs11h_certificate_deserializeCertificateId entry p_certificate_id=%p, sz='%s'\",\n-\t\t(void *)p_certificate_id,\n-\t\tsz\n-\t);\n+\tsize_t id_hex_len;\n \n \tif (\n \t\t(rv = _pkcs11h_mem_strdup (\n@@ -393,10 +558,6 @@ pkcs11h_certificate_deserializeCertificateId (\n \n \tp = _sz;\n \n-\tif ((rv = _pkcs11h_certificate_newCertificateId (&certificate_id)) != CKR_OK) {\n-\t\tgoto cleanup;\n-\t}\n-\n \tif ((p = strrchr (_sz, '/')) == NULL) {\n \t\trv = CKR_ATTRIBUTE_VALUE_INVALID;\n \t\tgoto cleanup;\n@@ -414,7 +575,12 @@ pkcs11h_certificate_deserializeCertificateId (\n \t\tgoto cleanup;\n \t}\n \n-\tcertificate_id->attrCKA_ID_size = strlen (p)/2;\n+\tid_hex_len = strlen (p);\n+\tif (id_hex_len & 1) {\n+\t\trv = CKR_ATTRIBUTE_VALUE_INVALID;\n+\t\tgoto cleanup;\n+\t}\n+\tcertificate_id->attrCKA_ID_size = id_hex_len/2;\n \n \tif (certificate_id->attrCKA_ID_size == 0) {\n \t\trv = CKR_ATTRIBUTE_VALUE_INVALID;\n@@ -434,21 +600,64 @@ pkcs11h_certificate_deserializeCertificateId (\n \t\tgoto cleanup;\n \t}\n \n+\trv = CKR_OK;\n+\n+cleanup:\n+\n+\tif (_sz != NULL) {\n+\t\t_pkcs11h_mem_free ((void *)&_sz);\n+\t}\n+\n+\treturn rv;\n+\n+}\n+\n+CK_RV\n+pkcs11h_certificate_deserializeCertificateId (\n+\tOUT pkcs11h_certificate_id_t * const p_certificate_id,\n+\tIN const char * const sz\n+) {\n+\tpkcs11h_certificate_id_t certificate_id = NULL;\n+\tCK_RV rv = CKR_FUNCTION_FAILED;\n+\n+\t_PKCS11H_ASSERT (p_certificate_id!=NULL);\n+\t_PKCS11H_ASSERT (sz!=NULL);\n+\n+\t*p_certificate_id = NULL;\n+\n+\t_PKCS11H_DEBUG (\n+\t\tPKCS11H_LOG_DEBUG2,\n+\t\t\"PKCS#11: pkcs11h_certificate_deserializeCertificateId entry p_certificate_id=%p, sz='%s'\",\n+\t\t(void *)p_certificate_id,\n+\t\tsz\n+\t);\n+\n+\tif ((rv = _pkcs11h_certificate_newCertificateId (&certificate_id)) != CKR_OK) {\n+\t\tgoto cleanup;\n+\t}\n+\tif ((rv = _pkcs11h_token_newTokenId (&certificate_id->token_id)) != CKR_OK) {\n+\t\tgoto cleanup;\n+\t}\n+\n+\tif (!strncmp(sz, URI_SCHEME, strlen (URI_SCHEME))) {\n+\t\trv = __parse_pkcs11_uri (certificate_id->token_id, certificate_id, sz);\n+\t} else {\n+\t\trv = __pkcs11h_certificate_legacy_deserializeCertificateId (certificate_id, sz);\n+\t}\n+\tif (rv != CKR_OK) {\n+\t\tgoto cleanup;\n+\t}\n+\n \t*p_certificate_id = certificate_id;\n \tcertificate_id = NULL;\n \trv = CKR_OK;\n \n cleanup:\n-\n \tif (certificate_id != NULL) {\n \t\tpkcs11h_certificate_freeCertificateId (certificate_id);\n \t\tcertificate_id = NULL;\n \t}\n \n-\tif (_sz != NULL) {\n-\t\t_pkcs11h_mem_free ((void *)&_sz);\n-\t}\n-\n \t_PKCS11H_DEBUG (\n \t\tPKCS11H_LOG_DEBUG2,\n \t\t\"PKCS#11: pkcs11h_certificate_deserializeCertificateId return rv=%lu-'%s'\",\ndiff --git a/lib/pkcs11h-util.c b/lib/pkcs11h-util.c\nindex 1fcadfc..a304642 100644\n--- a/lib/pkcs11h-util.c\n+++ b/lib/pkcs11h-util.c\n@@ -123,10 +123,6 @@ _pkcs11h_util_hexToBinary (\n \t\t(*p_target_size)++;\n \t}\n \n-\tif (*p != '\\x0') {\n-\t\tgoto cleanup;\n-\t}\n-\n \tret = CKR_OK;\n \n cleanup:\n"
  },
  {
    "path": "contrib/vcpkg-ports/pkcs11-helper/portfile.cmake",
    "content": "vcpkg_download_distfile(ARCHIVE\n    URLS \"https://github.com/OpenSC/pkcs11-helper/releases/download/pkcs11-helper-${VERSION}/pkcs11-helper-${VERSION}.tar.bz2\"\n    FILENAME \"pkcs11-helper-${VERSION}.tar.bz2\"\n    SHA512 0833efc59e9093dd398a54640d858b01a830ef7adfb40321c1e0ed0afa004500fc1259cc66bc49c5263935adeda0a3bfe658de538eefd66888685a71f731c484\n)\n\nvcpkg_extract_source_archive(\n    SOURCE_PATH\n    ARCHIVE ${ARCHIVE}\n    PATCHES\n        nmake-compatibility-with-vcpkg-nmake.patch\n        config-w32-vc.h.in-indicate-OpenSSL.patch\n        pkcs11-helper-001-RFC7512.patch\n)\n\nif(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW)\n  vcpkg_build_nmake(\n    SOURCE_PATH ${SOURCE_PATH}\n    PROJECT_SUBPATH lib\n    PROJECT_NAME Makefile.w32-vc\n    OPTIONS\n        OPENSSL=1\n        OPENSSL_HOME=${CURRENT_PACKAGES_DIR}/../openssl_${TARGET_TRIPLET}\n  )\n\n  file(INSTALL ${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel/lib/pkcs11-helper.dll.lib DESTINATION ${CURRENT_PACKAGES_DIR}/lib RENAME pkcs11-helper.lib)\n  file(INSTALL ${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg/lib/pkcs11-helper.dll.lib DESTINATION ${CURRENT_PACKAGES_DIR}/debug/lib RENAME pkcs11-helper.lib)\n\n  file(INSTALL ${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel/lib/libpkcs11-helper-1.dll DESTINATION ${CURRENT_PACKAGES_DIR}/bin)\n  file(INSTALL ${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg/lib/libpkcs11-helper-1.dll DESTINATION ${CURRENT_PACKAGES_DIR}/debug/bin)\n\n  set(PACKAGE_VERSION \"${VERSION}\")\n  set(libdir [[${prefix}/lib]])\n  set(exec_prefix [[${prefix}]])\n  set(PKCS11H_FEATURES key_prompt openssl engine_crypto_cryptoapi engine_crypto_openssl debug threading token data certificate slotevent engine_crypto)\n  set(LIBS -lkernel32 -luser32 -lgdi32 -lwinspool -lshell32 -lole32 -loleaut32 -luuid -lcomdlg32 -ladvapi32)\n  if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL \"release\")\n      set(includedir [[${prefix}/include]])\n      set(outfile \"${CURRENT_PACKAGES_DIR}/lib/pkgconfig/libpkcs11-helper-1.pc\")\n      configure_file(\"${SOURCE_PATH}/lib/libpkcs11-helper-1.pc.in\" \"${outfile}\" @ONLY)\n  endif()\n  if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL \"debug\")\n      set(includedir [[${prefix}/../include]])\n      set(outfile \"${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libpkcs11-helper-1.pc\")\n      configure_file(\"${SOURCE_PATH}/lib/libpkcs11-helper-1.pc.in\" \"${outfile}\" @ONLY)\n  endif()\n\n  file(INSTALL ${SOURCE_PATH}/include/pkcs11-helper-1.0 DESTINATION ${CURRENT_PACKAGES_DIR}/include/)\n\nelse()\n  find_program(man_to_html man2html REQUIRED)\n\n  vcpkg_configure_make(\n    SOURCE_PATH ${SOURCE_PATH}\n    OPTIONS --disable-crypto-engine-gnutls --disable-crypto-engine-nss\n    --disable-crypto-engine-polarssl --disable-crypto-engine-mbedtls\n    )\n  vcpkg_install_make()\n\n  file(REMOVE_RECURSE \"${CURRENT_PACKAGES_DIR}/debug/share\")\nendif()\n\nvcpkg_fixup_pkgconfig()\nvcpkg_copy_pdbs()\n\nvcpkg_install_copyright(FILE_LIST \"${SOURCE_PATH}/COPYING\")\n"
  },
  {
    "path": "contrib/vcpkg-ports/pkcs11-helper/vcpkg.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json\",\n  \"name\": \"pkcs11-helper\",\n  \"version\": \"1.31.0\",\n  \"description\": \"pkcs11-helper is a library that simplifies the interaction with PKCS#11 providers for end-user applications.\",\n  \"homepage\": \"https://github.com/OpenSC/pkcs11-helper\",\n  \"license\": \"BSD-3-Clause OR GPL-2.0-only\",\n  \"dependencies\": [\n    \"openssl\"\n  ]\n}\n"
  },
  {
    "path": "contrib/vcpkg-triplets/arm64-windows-ovpn.cmake",
    "content": "set(VCPKG_TARGET_ARCHITECTURE arm64)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE dynamic)\n\nset(STATIC_PORTS lz4 lzo)\nif(PORT IN_LIST STATIC_PORTS)\n    set(VCPKG_LIBRARY_LINKAGE static)\nendif()\n"
  },
  {
    "path": "contrib/vcpkg-triplets/x64-mingw-ovpn.cmake",
    "content": "set(VCPKG_TARGET_ARCHITECTURE x64)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE static)\nset(VCPKG_ENV_PASSTHROUGH PATH)\n\nset(VCPKG_CMAKE_SYSTEM_NAME MinGW)\n\nset(VCPKG_MAKE_BUILD_TRIPLET --host=x86_64-w64-mingw32)\n"
  },
  {
    "path": "contrib/vcpkg-triplets/x64-windows-ovpn.cmake",
    "content": "set(VCPKG_TARGET_ARCHITECTURE x64)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE dynamic)\n\nset(STATIC_PORTS lz4 lzo)\nif(PORT IN_LIST STATIC_PORTS)\n    set(VCPKG_LIBRARY_LINKAGE static)\nendif()\n"
  },
  {
    "path": "contrib/vcpkg-triplets/x86-mingw-ovpn.cmake",
    "content": "set(VCPKG_TARGET_ARCHITECTURE x86)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE static)\nset(VCPKG_ENV_PASSTHROUGH PATH)\n\nset(VCPKG_CMAKE_SYSTEM_NAME MinGW)\n\nset(VCPKG_MAKE_BUILD_TRIPLET --host=i686-w64-mingw32)\n"
  },
  {
    "path": "contrib/vcpkg-triplets/x86-windows-ovpn.cmake",
    "content": "set(VCPKG_TARGET_ARCHITECTURE x86)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE dynamic)\n\nset(STATIC_PORTS lz4 lzo)\nif(PORT IN_LIST STATIC_PORTS)\n    set(VCPKG_LIBRARY_LINKAGE static)\nendif()\n"
  },
  {
    "path": "debug/doval",
    "content": "#!/bin/bash\nPROGDIR=`dirname $0`\nunset LD_LIBRARY_PATH\nvalgrind --tool=memcheck --error-limit=no --suppressions=$PROGDIR/debug/valgrind-suppress --gen-suppressions=all --leak-check=full --show-reachable=yes --num-callers=32 $PROGDIR/openvpn \"$@\"\n"
  },
  {
    "path": "debug/dovalns",
    "content": "#!/bin/bash\nvalgrind --tool=memcheck --error-limit=no --gen-suppressions=all --leak-check=full --show-reachable=yes --num-callers=32 $*\n"
  },
  {
    "path": "debug/valgrind-suppress",
    "content": "{\n   <insert a suppression name here>\n   Memcheck:Addr8\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.5.so\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Addr8\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.5.so\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_udp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Addr8\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.5.so\n   fun:getpwnam_r\n   fun:getpwnam\n   fun:get_user\n   fun:management_open\n   fun:open_management\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Addr8\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   fun:__nss_next\n   fun:gethostbyname_r\n   fun:gethostbyname\n   fun:getaddr\n   fun:resolve_remote\n   fun:link_socket_init_phase1\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Addr8\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.5.so\n   fun:gethostbyname_r\n   fun:gethostbyname\n   fun:getaddr\n   fun:resolve_remote\n   fun:link_socket_init_phase1\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Addr8\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libdl-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libdl-2.5.so\n   fun:dlopen\n   fun:plugin_list_init\n   fun:init_plugins\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Addr8\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libdl-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libdl-2.5.so\n   fun:dlopen\n   fun:plugin_list_init\n   fun:init_plugins\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Addr8\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/libdl-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libdl-2.5.so\n   fun:dlsym\n   fun:libdl_resolve_symbol\n   fun:plugin_list_init\n   fun:init_plugins\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Addr8\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libdl-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libdl-2.5.so\n   fun:dlopen\n   fun:plugin_list_init\n   fun:init_plugins\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Addr8\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/libc-2.7.so\n   obj:/lib/ld-2.7.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.7.so\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Addr8\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/libc-2.7.so\n   obj:/lib/ld-2.7.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.7.so\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_udp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Addr8\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/libc-2.7.so\n   obj:/lib/ld-2.7.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.7.so\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:management_open\n   fun:open_management\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Addr8\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/ld-2.7.so\n   obj:/lib/libc-2.7.so\n   obj:/lib/ld-2.7.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.7.so\n   fun:getpwnam_r\n   fun:getpwnam\n   fun:get_user\n   fun:management_open\n   fun:open_management\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_div\n   fun:BN_MONT_CTX_set\n   fun:BN_MONT_CTX_set_locked\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:RSA_verify\n   fun:EVP_VerifyFinal\n   fun:ASN1_item_verify\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:X509_verify_cert\n   fun:ssl_verify_cert_chain\n   fun:ssl3_get_client_certificate\n   fun:ssl3_accept\n   fun:ssl3_read_bytes\n   fun:ssl3_read\n   obj:/usr/lib/libssl.so.0.9.8\n   fun:BIO_read\n   fun:bio_read\n   fun:tls_process\n   fun:tls_multi_process\n   fun:check_tls_dowork\n   fun:pre_select\n   fun:multi_process_post\n   fun:multi_process_incoming_link\n   fun:multi_tcp_action\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_div\n   fun:BN_MONT_CTX_set\n   fun:BN_MONT_CTX_set_locked\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:ssl3_ctx_ctrl\n   fun:init_ssl\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_div\n   fun:BN_MONT_CTX_set\n   fun:BN_MONT_CTX_set_locked\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:ssl3_ctx_ctrl\n   fun:init_ssl\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_udp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_div\n   fun:BN_MONT_CTX_set\n   fun:BN_MONT_CTX_set_locked\n   obj:/usr/lib/libcrypto.so.0.9.8\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:RSA_sign\n   fun:ssl3_send_server_key_exchange\n   fun:ssl3_accept\n   fun:ssl3_read_bytes\n   fun:ssl3_read\n   obj:/usr/lib/libssl.so.0.9.8\n   fun:BIO_read\n   fun:bio_read\n   fun:tls_process\n   fun:tls_multi_process\n   fun:check_tls_dowork\n   fun:pre_select\n   fun:multi_process_post\n   fun:multi_process_incoming_link\n   fun:multi_tcp_action\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_div\n   fun:BN_MONT_CTX_set\n   fun:BN_mod_exp_mont\n   fun:BN_BLINDING_create_param\n   fun:RSA_setup_blinding\n   obj:/usr/lib/libcrypto.so.0.9.8\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:RSA_sign\n   fun:ssl3_send_server_key_exchange\n   fun:ssl3_accept\n   fun:ssl3_read_bytes\n   fun:ssl3_read\n   obj:/usr/lib/libssl.so.0.9.8\n   fun:BIO_read\n   fun:bio_read\n   fun:tls_process\n   fun:tls_multi_process\n   fun:check_tls_dowork\n   fun:pre_select\n   fun:multi_process_post\n   fun:multi_process_incoming_link\n   fun:multi_tcp_action\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_div\n   fun:BN_nnmod\n   fun:BN_mod_inverse\n   fun:BN_MONT_CTX_set\n   fun:BN_MONT_CTX_set_locked\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:ssl3_ctx_ctrl\n   fun:init_ssl\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_div\n   fun:BN_nnmod\n   fun:BN_mod_inverse\n   fun:BN_MONT_CTX_set\n   fun:BN_MONT_CTX_set_locked\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:ssl3_ctx_ctrl\n   fun:init_ssl\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_udp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_mod_inverse\n   fun:BN_MONT_CTX_set\n   fun:BN_MONT_CTX_set_locked\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:RSA_verify\n   fun:EVP_VerifyFinal\n   fun:ASN1_item_verify\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:X509_verify_cert\n   fun:ssl_verify_cert_chain\n   fun:ssl3_get_client_certificate\n   fun:ssl3_accept\n   fun:ssl3_read_bytes\n   fun:ssl3_read\n   obj:/usr/lib/libssl.so.0.9.8\n   fun:BIO_read\n   fun:bio_read\n   fun:tls_process\n   fun:tls_multi_process\n   fun:check_tls_dowork\n   fun:pre_select\n   fun:multi_process_post\n   fun:multi_process_incoming_link\n   fun:multi_tcp_action\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_mod_inverse\n   fun:BN_MONT_CTX_set\n   fun:BN_MONT_CTX_set_locked\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:ssl3_ctx_ctrl\n   fun:init_ssl\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_mod_inverse\n   fun:BN_MONT_CTX_set\n   fun:BN_MONT_CTX_set_locked\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:ssl3_ctx_ctrl\n   fun:init_ssl\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_udp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_mod_inverse\n   fun:BN_MONT_CTX_set\n   fun:BN_MONT_CTX_set_locked\n   obj:/usr/lib/libcrypto.so.0.9.8\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:RSA_sign\n   fun:ssl3_send_server_key_exchange\n   fun:ssl3_accept\n   fun:ssl3_read_bytes\n   fun:ssl3_read\n   obj:/usr/lib/libssl.so.0.9.8\n   fun:BIO_read\n   fun:bio_read\n   fun:tls_process\n   fun:tls_multi_process\n   fun:check_tls_dowork\n   fun:pre_select\n   fun:multi_process_post\n   fun:multi_process_incoming_link\n   fun:multi_tcp_action\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_mod_inverse\n   fun:BN_MONT_CTX_set\n   fun:BN_mod_exp_mont\n   fun:BN_BLINDING_create_param\n   fun:RSA_setup_blinding\n   obj:/usr/lib/libcrypto.so.0.9.8\n   obj:/usr/lib/libcrypto.so.0.9.8\n   fun:RSA_sign\n   fun:ssl3_send_server_key_exchange\n   fun:ssl3_accept\n   fun:ssl3_read_bytes\n   fun:ssl3_read\n   obj:/usr/lib/libssl.so.0.9.8\n   fun:BIO_read\n   fun:bio_read\n   fun:tls_process\n   fun:tls_multi_process\n   fun:check_tls_dowork\n   fun:pre_select\n   fun:multi_process_post\n   fun:multi_process_incoming_link\n   fun:multi_tcp_action\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:*\n   obj:*\n   obj:*\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   fun:__nss_next\n   fun:gethostbyname_r\n   fun:gethostbyname\n   fun:getaddr\n   fun:resolve_remote\n   fun:link_socket_init_phase1\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.5.so\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.5.so\n   fun:gethostbyname_r\n   fun:gethostbyname\n   fun:getaddr\n   fun:resolve_remote\n   fun:link_socket_init_phase1\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   fun:__nss_next\n   fun:gethostbyname_r\n   fun:gethostbyname\n   fun:getaddr\n   fun:resolve_remote\n   fun:link_socket_init_phase1\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.5.so\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.5.so\n   fun:gethostbyname_r\n   fun:gethostbyname\n   fun:getaddr\n   fun:resolve_remote\n   fun:link_socket_init_phase1\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   fun:__nss_next\n   fun:gethostbyname_r\n   fun:gethostbyname\n   fun:getaddr\n   fun:resolve_remote\n   fun:link_socket_init_phase1\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.5.so\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.5.so\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_udp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.5.so\n   fun:gethostbyname_r\n   fun:gethostbyname\n   fun:getaddr\n   fun:resolve_remote\n   fun:link_socket_init_phase1\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/ld-2.5.so\n   fun:__libc_dlopen_mode\n   fun:__nss_lookup_function\n   obj:/lib/libc-2.5.so\n   fun:getpwnam_r\n   fun:getpwnam\n   fun:get_user\n   fun:management_open\n   fun:open_management\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libc-2.5.so\n   obj:/lib/libdl-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libdl-2.5.so\n   fun:dlsym\n   fun:libdl_resolve_symbol\n   fun:plugin_list_init\n   fun:init_plugins\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libdl-2.5.so\n   obj:/lib/ld-2.5.so\n   obj:/lib/libdl-2.5.so\n   fun:dlopen\n   fun:plugin_list_init\n   fun:init_plugins\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   fun:CRYPTO_malloc\n   fun:sk_new\n   obj:/usr/lib/libssl.so.0.9.8\n   fun:SSL_COMP_get_compression_methods\n   fun:SSL_library_init\n   fun:init_ssl_lib\n   fun:init_static\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   fun:__nss_lookup_function\n   obj:*\n   obj:*\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   fun:__nss_lookup_function\n   obj:*\n   obj:*\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   fun:__nss_lookup_function\n   obj:*\n   obj:*\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_udp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   fun:__nss_lookup_function\n   obj:*\n   obj:*\n   fun:getpwnam_r\n   fun:getpwnam\n   fun:get_user\n   fun:management_open\n   fun:open_management\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   fun:getdelim\n   fun:getpass\n   fun:get_console_input\n   fun:get_user_pass\n   fun:context_init_1\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   fun:tsearch\n   fun:__nss_lookup_function\n   obj:*\n   obj:*\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   fun:tsearch\n   fun:__nss_lookup_function\n   obj:*\n   obj:*\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   fun:tsearch\n   fun:__nss_lookup_function\n   obj:*\n   obj:*\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_udp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   fun:tsearch\n   fun:__nss_lookup_function\n   obj:*\n   obj:*\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:management_open\n   fun:open_management\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   fun:tsearch\n   fun:__nss_lookup_function\n   obj:*\n   obj:*\n   fun:getpwnam_r\n   fun:getpwnam\n   fun:get_user\n   fun:management_open\n   fun:open_management\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   obj:/lib/libc-2.5.so\n   fun:__nss_database_lookup\n   obj:*\n   obj:*\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   obj:/lib/libc-2.5.so\n   fun:__nss_database_lookup\n   obj:*\n   obj:*\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   obj:/lib/libc-2.5.so\n   fun:__nss_database_lookup\n   obj:*\n   obj:*\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_udp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   obj:/lib/libc-2.5.so\n   fun:__nss_database_lookup\n   obj:*\n   obj:*\n   fun:getpwnam_r\n   fun:getpwnam\n   fun:get_user\n   fun:management_open\n   fun:open_management\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   obj:/lib/libc-2.7.so\n   fun:__nss_database_lookup\n   obj:*\n   obj:*\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_tcp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   obj:/lib/libc-2.7.so\n   fun:__nss_database_lookup\n   obj:*\n   obj:*\n   fun:getgrnam_r\n   fun:getgrnam\n   fun:get_group\n   fun:do_init_first_time\n   fun:init_instance\n   fun:init_instance_handle_signals\n   fun:tunnel_server_udp\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Leak\n   fun:malloc\n   obj:/lib/libc-2.7.so\n   fun:__nss_database_lookup\n   obj:*\n   obj:*\n   fun:getpwnam_r\n   fun:getpwnam\n   fun:get_user\n   fun:management_open\n   fun:open_management\n   fun:main\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_mod_inverse\n}\n\n{\n   <insert a suppression name here>\n   Memcheck:Cond\n   fun:BN_div\n}\n"
  },
  {
    "path": "dev-tools/gen-release-tarballs.sh",
    "content": "#!/bin/sh\n# gen-release-tarballs.sh  -  Generates release tarballs with signatures\n#\n# Copyright (C) 2017-2026 - David Sommerseth <davids@openvpn.net>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License\n# as published by the Free Software Foundation; either version 2\n# of the License.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, see <https://www.gnu.org/licenses/>.\n#\nset -u\n\nif [ $# -ne 4 ]; then\n    echo \"Usage: $0 <remote-name> <tag-name> <sign-key> <dest-dir>\"\n    echo \"\"\n    echo \"   remote-name  -- valid remotes: `git remote | tr \\\\\\n ' '`\"\n    echo \"   tag-name     -- An existing release tag\"\n    echo \"   sign-key     -- PGP key used to sign all files\"\n    echo \"   dest-dir     -- Where to put the complete set of release tarballs\"\n    echo \"\"\n    echo \"   Example: $0 origin v2.4.2 /tmp/openvpn-release\"\n    echo\n    exit 1\nfi\n\narg_remote_name=\"$1\"\narg_tag_name=\"$2\"\narg_sign_key=\"$3\"\narg_dest_dir=\"$4\"\n\n#\n# Sanity checks\n#\n\n# Check that the tag exists\ngit tag | grep \"$arg_tag_name\" 1>/dev/null\nif [ $? -ne 0 ]; then\n    echo \"** ERROR **  The tag '$arg_tag_name' does not exist\"\n    exit 2\nfi\n\n# Extract the git URL\ngiturl=\"`git remote get-url $arg_remote_name 2>/dev/null`\"\nif [ $? -ne 0 ]; then\n    echo \"** ERROR ** Invalid git remote name: $arg_remote_name\"\n    exit 2\nfi\n\n# Check we have the needed signing key\necho \"test\" | gpg -a --clearsign -u \"$arg_sign_key\" 2>/dev/null 1>/dev/null\nif [ $? -ne 0 ]; then\n    echo \"** ERROR ** Failed when testing the PGP signing.  Wrong signing key?\"\n    exit 2;\nfi\n\n\n#\n# Helper functions\n#\n\nget_filename()\n{\n    local wildcard=\"$1\"\n\n    res=\"`find . -maxdepth 1 -type f -name \\\"$wildcard\\\" | head -n1 | cut -d/ -f2-`\"\n    if [ $? -ne 0 ]; then\n        echo \"-- 'find' failed.\"\n        exit 5\n    fi\n    if [ -z \"$res\" ]; then\n        echo \"-- Could not find a file with the wildcard: $wildcard\"\n        exit 4\n    fi\n    echo \"$res\"\n}\n\ncopy_files()\n{\n    local fileext=\"$1\"\n    local dest=\"$2\"\n\n    file=\"`get_filename openvpn-*.*.*.$fileext`\"\n    if [ -z \"$file\" ]; then\n        echo \"** ERROR Failed to find source file\"\n        exit 5\n    fi\n    echo \"-- Copying $file\"\n    cp \"$file\" \"$dest\"\n    if [ $? -ne 0 ]; then\n        echo \"** ERROR ** Failed to copy $file to $destdir\"\n        exit 3;\n    fi\n}\n\nsign_file()\n{\n    local signkey=\"$1\"\n    local srchfile=\"$2\"\n    local signtype=\"$3\"\n    local file=\"`get_filename $srchfile`\"\n\n    echo \"-- Signing $file ...\"\n    case \"$signtype\" in\n        inline)\n            # Have the signature in the same file as the data\n            gpg -a --clearsign -u \"$signkey\" \"$file\" 2>/dev/null\n            res=$?\n            if [ $res -eq 0 ]; then\n                rm -f \"$file\"\n            fi\n            ;;\n\n        detached)\n            # Have the signature in a separate file\n            gpg -a --detach-sign -u \"$signkey\" \"$file\" 2>/dev/null\n            res=$?\n            ;;\n\n        *)\n            echo \"** ERROR **  Unknown signing type \\\"$signtype\\\".\"\n            exit 4;\n    esac\n\n    if [ $res -ne 0 ]; then\n        echo \"** ERROR **  Failed to sign the file $PWD/$file\"\n        exit 4;\n    fi\n}\n\n\n#\n# Preparations\n#\n\n# Create the destination directory, using a sub-dir with the tag-name\ndestdir=\"\"\ncase \"$arg_dest_dir\" in\n    /*) # Absolute path\n        destdir=\"$arg_dest_dir/$arg_tag_name\"\n        ;;\n    *)  # Make absolute path from relative path\n        destdir=\"$PWD/$arg_dest_dir/$arg_tag_name\"\n        ;;\nesac\necho \"-- Destination directory: $destdir\"\nif [ -e \"$destdir\" ]; then\n    echo \"** ERROR ** Destination directory already exists.  \"\n    echo \"            Please check your command line carefully.\"\n    exit 2\nfi\n\nmkdir -p \"$destdir\"\nif [ $? -ne 0 ]; then\n    echo \"** ERROR ** Failed to create destination directory\"\n    exit 2\nfi\n\n#\n# Start the release process\n#\n\n# Clone the remote repository\nworkdir=\"`mktemp -d -p /var/tmp openvpn-build-release-XXXXXX`\"\ncd $workdir\necho \"-- Working directory: $workdir\"\necho \"-- git clone $giturl\"\ngit clone $giturl openvpn-gen-tarball 2> \"$workdir/git-clone.log\" 1>&2\nif [ $? -ne 0 ]; then\n    echo \"** ERROR **  git clone failed.  See $workdir/git-clone.log for details\"\n    exit 3;\nfi\ncd openvpn-gen-tarball\n\n# Check out the proper release tag\necho \"-- Checking out tag $arg_tag_name ... \"\ngit checkout -b mkrelease \"$arg_tag_name\" 2> \"$workdir/git-checkout-tag.log\" 1>&2\nif [ $? -ne 0 ]; then\n    echo \"** ERROR **  git checkout failed.  See $workdir/git-checkout-tag.log for details\"\n    exit 3;\nfi\n\n# Prepare the source tree\necho \"-- Running autoreconf + a simple configure ... \"\n(autoreconf -vi && ./configure) 2> \"$workdir/autotools-prep.log\" 1>&2\nif [ $? -ne 0 ]; then\n    echo \"** ERROR **  Failed running autotools.  See $workdir/autotools-prep.log for details\"\n    exit 3;\nfi\n\n# Generate the tar/zip files\necho \"-- Running make distcheck (generates .tar.gz) ... \"\n(make distcheck) 2> \"$workdir/make-distcheck.log\" 1>&2\nif [ $? -ne 0 ]; then\n    echo \"** ERROR **  make distcheck failed.  See $workdir/make-distcheck.log for details\"\n    exit 3;\nfi\ncopy_files tar.gz \"$destdir\"\n\necho \"-- Running make dist-xz (generates .tar.xz) ... \"\n(make dist-xz) 2> \"$workdir/make-dist-xz.log\" 1>&2\nif [ $? -ne 0 ]; then\n    echo \"** ERROR **  make dist-xz failed.  See $workdir/make-dist-xz.log for details\"\n    exit 3;\nfi\ncopy_files tar.xz \"$destdir\"\n\necho \"-- Running make dist-zip (generates .zip) ... \"\n(make dist-zip) 2> \"$workdir/make-dist-zip.log\" 1>&2\nif [ $? -ne 0 ]; then\n    echo \"** ERROR **  make dist-zip failed.  See $workdir/make-dist-zip.log for details\"\n    exit 3;\nfi\ncopy_files zip \"$destdir\"\n\n# Generate SHA256 checksums\ncd \"$destdir\"\nsha256sum openvpn-*.tar.{gz,xz} openvpn-*.zip > \"openvpn-$arg_tag_name.sha256sum\"\n\n# Sign all the files\necho \"-- Signing files ... \"\nsign_file \"$arg_sign_key\" \"openvpn-$arg_tag_name.sha256sum\" inline\nsign_file \"$arg_sign_key\" \"openvpn-*.tar.gz\" detached\nsign_file \"$arg_sign_key\" \"openvpn-*.tar.xz\" detached\nsign_file \"$arg_sign_key\" \"openvpn-*.zip\" detached\n\n# Create a tar-bundle with everything\necho \"-- Creating final tarbundle with everything ...\"\ntar cf \"openvpn-$arg_tag_name.tar\" openvpn-*.{tar.gz,tar.xz,zip}{,.asc} openvpn-*.sha256sum.asc\n\necho \"-- Cleaning up ...\"\n# Save the log files\nmkdir -p \"$destdir/logs\"\nmv $workdir/*.log \"$destdir/logs\"\n\n# Finally, done!\nrm -rf \"$workdir\"\necho \"-- Done\"\nexit 0\n"
  },
  {
    "path": "dev-tools/gerrit-send-mail.py",
    "content": "#!/usr/bin/env python3\n\n#  Copyright (C) 2023-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2023-2026 Frank Lichtenheld <frank.lichtenheld@openvpn.net>\n#\n#  This program is free software; you can redistribute it and/or modify\n#  it under the terms of the GNU General Public License version 2\n#  as published by the Free Software Foundation.\n#\n#  This program is distributed in the hope that it will be useful,\n#  but WITHOUT ANY WARRANTY; without even the implied warranty of\n#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n#  GNU General Public License for more details.\n#\n#  You should have received a copy of the GNU General Public License along\n#  with this program; if not, see <https://www.gnu.org/licenses/>.\n\n# Extract a patch from Gerrit and transform it in a file suitable as input\n# for git send-email.\n\nimport argparse\nimport base64\nfrom datetime import timezone\nimport json\nimport sys\nfrom urllib.parse import urlparse\n\nimport dateutil.parser\nimport requests\n\n\ndef get_details(args):\n    params = {\"o\": [\"CURRENT_REVISION\", \"LABELS\", \"DETAILED_ACCOUNTS\"]}\n    r = requests.get(f\"{args.url}/changes/{args.changeid}\", params=params)\n    print(r.url)\n    json_txt = r.text.removeprefix(\")]}'\\n\")\n    json_data = json.loads(json_txt)\n    assert len(json_data[\"revisions\"]) == 1  # CURRENT_REVISION works as expected\n    revision = json_data[\"revisions\"].popitem()[1][\"_number\"]\n    assert \"Code-Review\" in json_data[\"labels\"]\n    acked_by = []\n    for reviewer in json_data[\"labels\"][\"Code-Review\"][\"all\"]:\n        if \"value\" in reviewer:\n            assert reviewer[\"value\"] >= 0  # no NACK\n            if reviewer[\"value\"] == 2:\n                # fall back to user name if optional fields are not set\n                reviewer_name = reviewer.get(\"display_name\", reviewer[\"name\"])\n                reviewer_mail = reviewer.get(\"email\", reviewer[\"name\"])\n                ack = f\"{reviewer_name} <{reviewer_mail}>\"\n                print(f\"Acked-by: {ack}\")\n                acked_by.append(ack)\n    # construct Signed-off-by in case it is missing\n    owner = json_data[\"owner\"]\n    owner_name = owner.get(\"display_name\", owner[\"name\"])\n    owner_mail = owner.get(\"email\", owner[\"name\"])\n    sign_off = f\"{owner_name} <{owner_mail}>\"\n    print(f\"Signed-off-by: {sign_off}\")\n    change_id = json_data[\"change_id\"]\n    # assumes that the created date in Gerrit is in UTC\n    utc_stamp = (\n        dateutil.parser.parse(json_data[\"created\"])\n        .replace(tzinfo=timezone.utc)\n        .timestamp()\n    )\n    # convert to milliseconds as used in message id\n    created_stamp = int(utc_stamp * 1000)\n    hostname = urlparse(args.url).hostname\n    msg_id = f\"gerrit.{created_stamp}.{change_id}@{hostname}\"\n    return {\n        \"revision\": revision,\n        \"project\": json_data[\"project\"],\n        \"target\": json_data[\"branch\"],\n        \"msg_id\": msg_id,\n        \"acked_by\": acked_by,\n        \"sign_off\": sign_off,\n    }\n\n\ndef get_patch(details, args):\n    r = requests.get(\n        f\"{args.url}/changes/{args.changeid}/revisions/{details['revision']}/patch?download\"\n    )\n    print(r.url)\n    patch_text = base64.b64decode(r.text).decode()\n    return patch_text\n\n\ndef apply_patch_mods(patch_text, details, args):\n    comment_start = patch_text.index(\"\\n---\\n\") + len(\"\\n---\\n\")\n    signed_off_text = \"\"\n    signed_off_comment = \"\"\n    try:\n        signed_off_start = patch_text.rindex(\"\\nSigned-off-by: \")\n        signed_off_end = patch_text.index(\"\\n\", signed_off_start + 1) + 1\n    except ValueError:  # Signed-off missing\n        signed_off_text = f\"Signed-off-by: {details['sign_off']}\\n\"\n        signed_off_comment = \"\\nSigned-off-by line for the author was added as per our policy.\\n\"\n        signed_off_end = patch_text.index(\"\\n---\\n\") + 1\n    assert comment_start > signed_off_end\n    acked_by_text = \"\"\n    acked_by_names = \"\"\n    gerrit_url = f\"{args.url}/c/{details['project']}/+/{args.changeid}\"\n    for ack in details[\"acked_by\"]:\n        acked_by_text += f\"Acked-by: {ack}\\n\"\n        acked_by_names += f\"{ack}\\n\"\n    patch_text_mod = (\n        patch_text[:signed_off_end]\n        + signed_off_text\n        + acked_by_text\n        + f\"Gerrit URL: {gerrit_url}\\n\"\n        + patch_text[signed_off_end:comment_start]\n        + f\"\"\"\nThis change was reviewed on Gerrit and approved by at least one\ndeveloper. I request to merge it to {details[\"target\"]}.\n\nGerrit URL: {gerrit_url}\nThis mail reflects revision {details[\"revision\"]} of this Change.\n{signed_off_comment}\nAcked-by according to Gerrit (reflected above):\n{acked_by_names}\n        \"\"\"\n        + patch_text[comment_start:]\n    )\n    filename = f\"gerrit-{args.changeid}-{details['revision']}.patch\"\n    patch_text_final = patch_text_mod.replace(\"Subject: [PATCH]\", f\"Subject: [PATCH v{details['revision']}]\")\n    with open(filename, \"w\", encoding=\"utf-8\", newline=\"\\n\") as patch_file:\n        patch_file.write(patch_text_final)\n    print(\"send with:\")\n    print(f\"git send-email --in-reply-to {details['msg_id']} {filename}\")\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        prog=\"gerrit-send-mail\",\n        description=\"Send patchset from Gerrit to mailing list\",\n    )\n    parser.add_argument(\"changeid\")\n    parser.add_argument(\"-u\", \"--url\", default=\"https://gerrit.openvpn.net\")\n    args = parser.parse_args()\n\n    details = get_details(args)\n    patch = get_patch(details, args)\n    apply_patch_mods(patch, details, args)\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "dev-tools/git-pre-commit-format.sh",
    "content": "#!/bin/sh\n\n# Copyright (c) 2015, David Martin\n#               2022, Heiko Hund\n#               2025, Frank Lichtenheld\n# All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions are met:\n#\n# * Redistributions of source code must retain the above copyright notice, this\n#   list of conditions and the following disclaimer.\n#\n# * Redistributions in binary form must reproduce the above copyright notice,\n#   this list of conditions and the following disclaimer in the documentation\n#   and/or other materials provided with the distribution.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n# git pre-commit hook that runs a stylecheck.\n# Features:\n#  - abort commit when commit does not comply with the style guidelines\n#  - create a patch of the proposed style changes\n#  - use clang-format or uncrustify depending on presence of .clang-format\n#    config file\n#\n# More info on Uncrustify: https://uncrustify.sourceforge.net/\n\n# This file was taken from a set of unofficial pre-commit hooks available\n# at https://github.com/ddddavidmartin/Pre-commit-hooks and modified to\n# fit the openvpn project's needs\n\n# exit on error\nset -e\n\n\n# If called so, install this script as pre-commit hook\nif [ \"$1\" = \"install\" ] ; then\n    TARGET=\"$(git rev-parse --git-path hooks)/pre-commit\"\n\n    if [ -e \"$TARGET\" ] ; then\n        printf \"$TARGET file exists. Won't overwrite.\\n\"\n        printf \"Aborting installation.\\n\"\n        exit 1\n    fi\n\n    read -p \"Install as $TARGET? [y/N] \" INPUT\n    [ \"$INPUT\" = \"y\" ] || exit 0\n    cp \"$0\" \"$TARGET\"\n    chmod +x $TARGET\n    exit 0\nfi\n\n# check whether the given file matches any of the set extensions\nmatches_extension() {\n    local filename=\"$(basename -- \"$1\")\"\n    local extension=\".${filename##*.}\"\n    local ext\n\n    for ext in .c .h ; do [ \"$ext\" = \"$extension\" ] && return 0; done\n\n    return 1\n}\n\n# necessary check for initial commit\nif git rev-parse --verify HEAD >/dev/null 2>&1 ; then\n    against=HEAD\nelse\n    # Initial commit: diff against an empty tree object\n    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904\nfi\n\nTOPDIR=\"$(git rev-parse --show-toplevel)\"\nif [ -e \"${TOPDIR}/.clang-format\" ]; then\n    TOOL=clang-format\n    TOOL_BIN=$(command -v clang-format)\n    TOOL_CMD=\"$TOOL_BIN\"\n\n    # Allow to use in parallel with pre-commit\n    if [ $(basename \"$0\") = \"pre-commit.legacy\" ]; then\n       echo \"Skipping clang-format check in favor of pre-commit\"\n       exit 0\n    fi\nelse\n    TOOL=uncrustify\n    TOOL_BIN=$(command -v uncrustify)\n    UNCRUST_CONFIG=\"${TOPDIR}/dev-tools/uncrustify.conf\"\n    TOOL_CMD=\"$TOOL_BIN -q -l C -c $UNCRUST_CONFIG\"\n\n    # make sure the config file is correctly set\n    if [ ! -f \"$UNCRUST_CONFIG\" ] ; then\n        printf \"Error: uncrustify config file not found.\\n\"\n        printf \"Expected to find it at $UNCRUST_CONFIG.\\n\"\n        printf \"Aborting commit.\\n\"\n        exit 1\n    fi\nfi\n\nif [ -z \"$TOOL_BIN\" ] ; then\n    printf \"Error: $TOOL executable not found.\\n\"\n    printf \"Is it installed and in your \\$PATH?\\n\"\n    printf \"Aborting commit.\\n\"\n    exit 1\nfi\n\n# create a filename to store our generated patch\npatch=$(mktemp /tmp/ovpn-fmt-patch-XXXXXX)\ntmpout=$(mktemp /tmp/ovpn-fmt-tmp-XXXXXX)\n\n# create one patch containing all changes to the files\n# sed to remove quotes around the filename, if inserted by the system\n# (done sometimes, if the filename contains special characters, like the quote itself)\ngit diff-index --cached --diff-filter=ACMR --name-only $against -- | \\\nsed -e 's/^\"\\(.*\\)\"$/\\1/' | \\\nwhile read file\ndo\n    # ignore file if we do check for file extensions and the file\n    # does not match the extensions .c or .h\n    if ! matches_extension \"$file\"; then\n        continue;\n    fi\n\n    # escape special characters in the target filename:\n    # phase 1 (characters escaped in the output diff):\n    #     - '\\': backslash needs to be escaped in the output diff\n    #     - '\"': quote needs to be escaped in the output diff if present inside\n    #            of the filename, as it used to bracket the entire filename part\n    # phase 2 (characters escaped in the match replacement):\n    #     - '\\': backslash needs to be escaped again for sed itself\n    #            (i.e. double escaping after phase 1)\n    #     - '&': would expand to matched string\n    #     - '|': used as sed split char instead of '/'\n    # printf %s particularly important if the filename contains the % character\n    file_escaped_target=$(printf \"%s\" \"$file\" | sed -e 's/[\\\"]/\\\\&/g' -e 's/[\\&|]/\\\\&/g')\n\n    # uncrustify our sourcefile, create a patch with diff and append it to our $patch\n    # The sed call is necessary to transform the patch from\n    #    --- - timestamp\n    #    +++ $tmpout timestamp\n    # to both lines working on the same file and having a a/ and b/ prefix.\n    # Else it can not be applied with 'git apply'.\n    git show \":$file\" | $TOOL_CMD > \"$tmpout\"\n    git show \":$file\" | diff -u -- - \"$tmpout\" | \\\n        sed -e \"1s|--- -|--- \\\"b/$file_escaped_target\\\"|\" -e \"2s|+++ $tmpout|+++ \\\"a/$file_escaped_target\\\"|\" >> \"$patch\"\ndone\n\nrm -f \"$tmpout\"\n\n# if no patch has been generated all is ok, clean up the file stub and exit\nif [ ! -s \"$patch\" ] ; then\n    rm -f \"$patch\"\n    exit 0\nfi\n\n# a patch has been created, notify the user and exit\nprintf \"Formatting of some code does not follow the project guidelines.\\n\"\n\nif [ $(wc -l < $patch) -gt 80 ] ; then\n    printf \"The file $patch contains the necessary fixes.\\n\"\nelse\n    printf \"Here's the patch that fixes the formatting:\\n\\n\"\n    cat $patch\nfi\n\nprintf \"\\nYou can apply these changes with:\\n git apply $patch\\n\"\nprintf \"(from the root directory of the repository) and then commit again.\\n\"\nprintf \"\\nAborting commit.\\n\"\n\nexit 1\n"
  },
  {
    "path": "dev-tools/update-copyright.sh",
    "content": "#!/bin/sh\n# update-copyright-sh - Simple tool to update the Copyright lines\n#                       in all files checked into git\n#\n# Copyright (C) 2016-2026 OpenVPN Inc <sales@openvpn.net>\n# Copyright (C) 2016-2026 David Sommerseth <davids@openvpn.net>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License\n# as published by the Free Software Foundation; either version 2\n# of the License.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, see <https://www.gnu.org/licenses/>.\n#\n\n# Basic shell sanity\nset -eu\n\n# Simple argument control\nif [ $# -ne 1 ]; then\n    echo \"Usage: $0 <New Copyright Year>\"\n    exit 1\nfi\n\n# Only update Copyright lines with these owners\n# The 'or' operator is GNU sed specific, and must be \\|\nUPDATE_COPYRIGHT_LINES=\"@openvpn\\.net\\|@sentyron\\.com\\|@sophos.com\\|@eurephia\\.org\\|@greenie\\.muc\\.de\\|@rozman.si\\|@unstable\\.cc\\|@rfc2549.org\\|@karger\\.me\\|selva.nair@\"\nCOPY_YEAR=\"$1\"\n\ncd \"$(git rev-parse --show-toplevel)\"\nfor file in $(git ls-files | grep -v vendor/);\ndo\n    echo -n \"Updating $file ...\"\n    # The first sed operation covers 20xx-20yy copyright lines,\n    # The second sed operation changes 20xx -> 20xx-20yy\n    sed -e \"/$UPDATE_COPYRIGHT_LINES/s/\\(Copyright (C) 20..-\\)\\(20..\\)[[:blank:]]\\+/\\1$COPY_YEAR /\" \\\n        -e \"/$UPDATE_COPYRIGHT_LINES/s/\\(Copyright (C) \\)\\(20..\\)[[:blank:]]\\+/\\1\\2-$COPY_YEAR /\" \\\n        -i $file\n    echo \" Done\"\ndone\necho\necho \"** All files updated with $COPY_YEAR as the ending copyright year\"\necho\nexit 0\n"
  },
  {
    "path": "distro/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2006-2012 Alon Bar-Lev <alon.barlev@gmail.com>\n#\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n\nSUBDIRS = systemd\n\nif ENABLE_DNS_UPDOWN\nSUBDIRS += dns-scripts\nendif\n"
  },
  {
    "path": "distro/dns-scripts/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n\nEXTRA_DIST = \\\n\tmacos-dns-updown.sh \\\n\tsystemd-dns-updown.sh \\\n\topenresolv-dns-updown.sh \\\n\thaikuos_file-dns-updown.sh \\\n\tresolvconf_file-dns-updown.sh\n\nscript_SCRIPTS = \\\n\tdns-updown\n\nCLEANFILES = $(script_SCRIPTS)\n\ndns-updown: @DNS_UPDOWN_TYPE@-dns-updown.sh\n\tcp ${srcdir}/@DNS_UPDOWN_TYPE@-dns-updown.sh $@\n\tchmod +x $@\n\nall: $(script_SCRIPTS)\n"
  },
  {
    "path": "distro/dns-scripts/haikuos_file-dns-updown.sh",
    "content": "#!/bin/sh\n#\n# Simple OpenVPN up/down script for modifying Haiku OS resolv.conf\n# (C) Copyright 2024 OpenVPN Inc <sales@openvpn.net>\n#\n# SPDX-License-Identifier: BSD-2-Clause\n#\n# Example env from openvpn (most are not applied):\n#\n#   dns_vars_file /tmp/openvpn_dvf_58b95c0c97b2db43afb5d745f986c53c.tmp\n#\n#      or\n#\n#   dev tun0\n#   script_type dns-up\n#   dns_search_domain_1 mycorp.in\n#   dns_search_domain_2 eu.mycorp.com\n#   dns_server_1_address_1 192.168.99.254\n#   dns_server_1_address_2 fd00::99:53\n#   dns_server_1_port_1 53\n#   dns_server_1_port_2 53\n#   dns_server_1_resolve_domain_1 mycorp.in\n#   dns_server_1_resolve_domain_2 eu.mycorp.com\n#   dns_server_1_dnssec true\n#   dns_server_1_transport DoH\n#   dns_server_1_sni dns.mycorp.in\n#\n\nset -e +u\n\nonly_standard_server_ports() {\n    i=1\n    while true; do\n        eval addr=\\\"\\$dns_server_${n}_address_${i}\\\"\n        [ -n \"$addr\" ] || return 0\n\n        eval port=\\\"\\$dns_server_${n}_port_${i}\\\"\n        [ -z \"$port\" -o \"$port\" = \"53\" ] || return 1\n\n        i=$(expr $i + 1)\n    done\n}\n\nconf=/boot/system/settings/network/resolv.conf\ntest -e \"$conf\" || exit 1\ntest -z \"${dns_vars_file}\" || . \"${dns_vars_file}\"\ncase \"${script_type}\" in\ndns-up)\n    n=1\n    while :; do\n        eval addr=\\\"\\$dns_server_${n}_address_1\\\"\n        [ -n \"$addr\" ] || {\n            echo \"setting DNS failed, no compatible server profile\"\n            exit 1\n        }\n\n        # Skip server profiles which require DNSSEC,\n        # secure transport or use a custom port\n        eval dnssec=\\\"\\$dns_server_${n}_dnssec\\\"\n        eval transport=\\\"\\$dns_server_${n}_transport\\\"\n        [ -z \"$transport\" -o \"$transport\" = \"plain\" ] \\\n            && [ -z \"$dnssec\" -o \"$dnssec\" = \"no\" ] \\\n            && only_standard_server_ports && break\n\n        n=$(expr $n + 1)\n    done\n\n    eval addr1=\\\"\\$dns_server_${n}_address_1\\\"\n    eval addr2=\\\"\\$dns_server_${n}_address_2\\\"\n    eval addr3=\\\"\\$dns_server_${n}_address_3\\\"\n    text=\"### openvpn ${dev} begin ###\\n\"\n    text=\"${text}nameserver $addr1\\n\"\n    test -z \"$addr2\" || text=\"${text}nameserver $addr2\\n\"\n    test -z \"$addr3\" || text=\"${text}nameserver $addr3\\n\"\n\n    test -z \"$dns_search_domain_1\" || {\n        for i in $(seq 1 6); do\n            eval domains=\\\"$domains\\$dns_search_domain_${i} \\\" || break\n        done\n        text=\"${text}search $domains\\n\"\n    }\n    text=\"${text}### openvpn ${dev} end ###\"\n    text=\"${text}\\n$(cat ${conf})\"\n\n    echo \"${text}\" > \"${conf}\"\n    ;;\ndns-down)\n    sed -i'' -e \"/### openvpn ${dev} begin ###/,/### openvpn ${dev} end ###/d\" \"$conf\"\n    ;;\nesac\n"
  },
  {
    "path": "distro/dns-scripts/macos-dns-updown.sh",
    "content": "#!/bin/bash\n#\n# dns-updown - add/remove openvpn provided DNS information\n#\n# (C) Copyright 2025 OpenVPN Inc <sales@openvpn.net>\n#\n# SPDX-License-Identifier: BSD-2-Clause\n#\n# Example env from openvpn (most are not applied):\n#\n#   dns_vars_file /tmp/openvpn_dvf_58b95c0c97b2db43afb5d745f986c53c.tmp\n#\n#      or\n#\n#   dev utun0\n#   script_type dns-up\n#   dns_search_domain_1 mycorp.in\n#   dns_search_domain_2 eu.mycorp.com\n#   dns_server_1_address_1 192.168.99.254\n#   dns_server_1_address_2 fd00::99:53\n#   dns_server_1_port_2 53\n#   dns_server_1_resolve_domain_1 mycorp.in\n#   dns_server_1_resolve_domain_2 eu.mycorp.com\n#   dns_server_1_dnssec true\n#   dns_server_1_transport DoH\n#   dns_server_1_sni dns.mycorp.in\n#\n\nlockdir=/var/lock\nif [ ! -d \"${lockdir}\" ]; then\n    /bin/mkdir \"${lockdir}\"\n    /bin/chmod 1777 \"${lockdir}\"\nfi\n\ni=1\nlockfile=\"${lockdir}/openvpn-dns-updown.lock\"\nwhile ! /usr/bin/shlock -f $lockfile -p $$; do\n    if [ $((++i)) -gt 10 ]; then\n        echo \"dns-updown failed, could not acquire lock\"\n        exit 1\n    fi\n    sleep 0.2\ndone\ntrap \"/bin/rm -f ${lockfile}\" EXIT\n\n[ -z \"${dns_vars_file}\" ] || . \"${dns_vars_file}\"\n\nitf_dns_key=\"State:/Network/Service/openvpn-${dev}/DNS\"\n\nfunction primary_dns_key {\n    local uuid=$(echo \"show State:/Network/Global/IPv4\" | /usr/sbin/scutil | grep \"PrimaryService\" | cut -d: -f2 | xargs)\n    echo \"Setup:/Network/Service/${uuid}/DNS\"\n}\n\nfunction dns_backup_key {\n    local key=\"$(echo \"list State:/Network/Service/openvpn-.*/DnsBackup\" | /usr/sbin/scutil | cut -d= -f2 | xargs)\"\n    if [[ \"${key}\" =~ no\\ key ]]; then\n        echo \"State:/Network/Service/openvpn-${dev}/DnsBackup\"\n    else\n        echo \"${key}\"\n    fi\n}\n\nfunction property_value {\n    local key=\"$1\"\n    local prop=\"$2\"\n\n    [ -n \"${key}\" -a -n \"${prop}\" ] || return\n\n    local match_prop=\"${prop} : (.*)\"\n    local match_array_start=\"${prop} : <array>\"\n    local match_array_elem=\"[0-9]* : (.*)\"\n    local match_array_end=\"}\"\n    local in_array=false\n    local values=\"\"\n\n    echo \"show ${key}\" | /usr/sbin/scutil | while read line; do\n        if [ \"${in_array}\" = false ] && [[ \"${line}\" =~ \"${match_array_start}\" ]]; then\n            in_array=true\n        elif [ \"${in_array}\" = true ] && [[ \"${line}\" =~ ${match_array_elem} ]]; then\n            values+=\"${BASH_REMATCH[1]} \"\n        elif [ \"${in_array}\" = true ] && [[ \"${line}\" =~ \"${match_array_end}\" ]]; then\n            echo \"${values}\"\n            break\n        elif [[ \"${line}\" =~ ${match_prop} ]]; then\n            echo \"${BASH_REMATCH[1]}\"\n            break\n        fi\n    done\n}\n\nfunction only_standard_server_ports {\n    local i=1\n    while :; do\n        local addr_var=dns_server_${n}_address_${i}\n        [ -n \"${!addr_var}\" ] || return 0\n\n        local port_var=dns_server_${n}_port_${i}\n        [ -z \"${!port_var}\" -o \"${!port_var}\" = \"53\" ] || return 1\n\n        i=$((i+1))\n    done\n}\n\nfunction find_compat_profile {\n    local n=1\n    while :; do\n        local addr_var=dns_server_${n}_address_1\n        [ -n \"${!addr_var}\" ] || {\n            echo \"setting DNS failed, no compatible server profile\"\n            exit 1\n        }\n\n        # Skip server profiles which require DNSSEC,\n        # secure transport or use a custom port\n        local dnssec_var=dns_server_${n}_dnssec\n        local transport_var=dns_server_${n}_transport\n        [ -z \"${!transport_var}\" -o \"${!transport_var}\" = \"plain\" ] \\\n            && [ -z \"${!dnssec_var}\" -o \"${!dnssec_var}\" = \"no\" ] \\\n            && only_standard_server_ports && break\n\n        n=$((n+1))\n    done\n    echo $n\n}\n\nfunction get_search_domains {\n    property_value State:/Network/Global/DNS SearchDomains\n}\n\nfunction get_server_addresses {\n    property_value \"$(primary_dns_key)\" ServerAddresses\n}\n\nfunction set_search_domains {\n    [ -n \"$1\" ] || return\n    local dns_key=$(primary_dns_key)\n    local dns_backup_key=\"$(dns_backup_key)\"\n    local search_domains=\"${1}$(get_search_domains)\"\n\n    local cmds=\"\"\n    cmds+=\"get ${dns_key}\\n\"\n    cmds+=\"d.add SearchDomains * ${search_domains}\\n\"\n    cmds+=\"set ${dns_key}\\n\"\n\n    if ! [[ \"${dns_backup_key}\" =~ ${dev}/ ]]; then\n        # Add the domains to the backup in case the default goes down\n        local existing=\"$(property_value ${dns_backup_key} SearchDomains)\"\n        cmds+=\"get ${dns_backup_key}\\n\"\n        cmds+=\"d.add SearchDomains * ${search_domains} ${existing}\\n\"\n        cmds+=\"set ${dns_backup_key}\\n\"\n    fi\n\n    echo -e \"${cmds}\" | /usr/sbin/scutil\n}\n\nfunction unset_search_domains {\n    [ -n \"$1\" ] || return\n    local dns_key=$(primary_dns_key)\n    local dns_backup_key=\"$(dns_backup_key)\"\n    local search_domains=\"$(get_search_domains)\"\n    search_domains=$(echo $search_domains | sed -e \"s/$1//\")\n\n    local cmds=\"\"\n    cmds+=\"get ${dns_key}\\n\"\n    cmds+=\"d.add SearchDomains * ${search_domains}\\n\"\n    cmds+=\"set ${dns_key}\\n\"\n\n    if ! [[ \"${dns_backup_key}\" =~ ${dev}/ ]]; then\n        # Remove the domains from the backup for when the default goes down\n        search_domains=\"$(property_value ${dns_backup_key} SearchDomains)\"\n        search_domains=$(echo $search_domains | sed -e \"s/$1//\")\n        cmds+=\"get ${dns_backup_key}\\n\"\n        cmds+=\"d.add SearchDomains * ${search_domains}\\n\"\n        cmds+=\"set ${dns_backup_key}\\n\"\n    fi\n\n    echo -e \"${cmds}\" | /usr/sbin/scutil\n}\n\nfunction addresses_string {\n    local n=$1\n    local i=1\n    local addresses=\"\"\n    while :; do\n        local addr_var=dns_server_${n}_address_${i}\n        local addr=\"${!addr_var}\"\n        [ -n \"$addr\" ] || break\n        addresses+=\"${addr} \"\n        i=$((i+1))\n    done\n    echo \"$addresses\"\n}\n\nfunction search_domains_string {\n    local n=$1\n    local i=1\n    local search_domains=\"\"\n    while :; do\n        domain_var=dns_search_domain_${i}\n        [ -n \"${!domain_var}\" ] || break\n        # Add as search domain, if it doesn't already exist\n        [[ \"$search_domains\" =~ (^| )${!domain_var}( |$) ]] \\\n            || search_domains+=\"${!domain_var} \"\n        i=$((i+1))\n    done\n    echo \"$search_domains\"\n}\n\nfunction match_domains_string {\n    local n=$1\n    local i=1\n    local match_domains=\"\"\n    while :; do\n        domain_var=dns_server_${n}_resolve_domain_${i}\n        [ -n \"${!domain_var}\" ] || break\n        # Add as match domain, if it doesn't already exist\n        [[ \"$match_domains\" =~ (^| )${!domain_var}( |$) ]] \\\n            || match_domains+=\"${!domain_var} \"\n        i=$((i+1))\n    done\n    echo \"$match_domains\"\n}\n\nfunction set_dns {\n    local n=\"$(find_compat_profile)\"\n    local addresses=\"$(addresses_string $n)\"\n    local search_domains=\"$(search_domains_string $n)\"\n    local match_domains=\"$(match_domains_string $n)\"\n\n    if [ -n \"$match_domains\" ]; then\n        local cmds=\"\"\n        cmds+=\"d.init\\n\"\n        cmds+=\"d.add ServerAddresses * ${addresses}\\n\"\n        cmds+=\"d.add SupplementalMatchDomains * ${match_domains}\\n\"\n        cmds+=\"d.add SupplementalMatchDomainsNoSearch # 1\\n\"\n        cmds+=\"add ${itf_dns_key}\\n\"\n        echo -e \"${cmds}\" | /usr/sbin/scutil\n        set_search_domains \"$search_domains\"\n    else\n        local dns_backup_key=\"$(dns_backup_key)\"\n        [[ \"${dns_backup_key}\" =~ ${dev}/ ]] || {\n            echo \"setting DNS failed, already redirecting to another tunnel\"\n            exit 1\n        }\n\n        local cmds=\"\"\n        cmds+=\"get $(primary_dns_key)\\n\"\n        cmds+=\"set ${dns_backup_key}\\n\"\n        cmds+=\"d.init\\n\"\n        cmds+=\"d.add ServerAddresses * ${addresses}\\n\"\n        cmds+=\"d.add SearchDomains * ${search_domains}\\n\"\n        cmds+=\"d.add SearchOrder # 5000\\n\"\n        cmds+=\"set $(primary_dns_key)\\n\"\n        echo -e \"${cmds}\" | /usr/sbin/scutil\n    fi\n\n    /usr/bin/dscacheutil -flushcache\n}\n\nfunction unset_dns {\n    local n=\"$(find_compat_profile)\"\n    local match_domains=\"$(match_domains_string $n)\"\n\n    if [ -n \"$match_domains\" ]; then\n        local search_domains=\"$(search_domains_string $n)\"\n        echo \"remove ${itf_dns_key}\" | /usr/sbin/scutil\n        unset_search_domains \"$search_domains\"\n    else\n        # Do not unset if this tunnel did not set/backup DNS before\n        local dns_backup_key=\"$(dns_backup_key)\"\n        [[ \"${dns_backup_key}\" =~ ${dev}/ ]] || return\n\n        local cmds=\"\"\n        local servers=\"$(get_server_addresses)\"\n        local addresses=\"$(addresses_string $n)\"\n        # Only restore backup if the server addresses match\n        if [ \"${servers}\" = \"${addresses}\" ]; then\n            cmds+=\"get ${dns_backup_key}\\n\"\n            cmds+=\"set $(primary_dns_key)\\n\"\n        else\n            echo \"not restoring global DNS configuration, server addresses have changed\"\n        fi\n        cmds+=\"remove ${dns_backup_key}\\n\"\n        echo -e \"${cmds}\" | /usr/sbin/scutil\n    fi\n\n    /usr/bin/dscacheutil -flushcache\n}\n\nif [ \"$script_type\" = \"dns-up\" ]; then\n    set_dns\nelse\n    unset_dns\nfi\n"
  },
  {
    "path": "distro/dns-scripts/openresolv-dns-updown.sh",
    "content": "#!/bin/sh\n#\n# Simple OpenVPN up/down script for openresolv integration\n# (C) Copyright 2016 Baptiste Daroussin\n#               2024 OpenVPN Inc <sales@openvpn.net>\n#\n# SPDX-License-Identifier: BSD-2-Clause\n#\n# Example env from openvpn (most are not applied):\n#\n#   dns_vars_file /tmp/openvpn_dvf_58b95c0c97b2db43afb5d745f986c53c.tmp\n#\n#      or\n#\n#   dev tun0\n#   script_type dns-up\n#   dns_search_domain_1 mycorp.in\n#   dns_search_domain_2 eu.mycorp.com\n#   dns_server_1_address_1 192.168.99.254\n#   dns_server_1_address_2 fd00::99:53\n#   dns_server_1_port_1 53\n#   dns_server_1_port_2 53\n#   dns_server_1_resolve_domain_1 mycorp.in\n#   dns_server_1_resolve_domain_2 eu.mycorp.com\n#   dns_server_1_dnssec true\n#   dns_server_1_transport DoH\n#   dns_server_1_sni dns.mycorp.in\n#\n\nset -e +u\n\nonly_standard_server_ports() {\n    i=1\n    while true; do\n        eval addr=\\\"\\$dns_server_${n}_address_${i}\\\"\n        [ -n \"$addr\" ] || return 0\n\n        eval port=\\\"\\$dns_server_${n}_port_${i}\\\"\n        [ -z \"$port\" -o \"$port\" = \"53\" ] || return 1\n\n        i=$(expr $i + 1)\n    done\n}\n\n[ -z \"${dns_vars_file}\" ] || . \"${dns_vars_file}\"\n: ${script_type:=dns-down}\ncase \"${script_type}\" in\ndns-up)\n    n=1\n    while :; do\n        eval addr=\\\"\\$dns_server_${n}_address_1\\\"\n        [ -n \"$addr\" ] || {\n            echo \"setting DNS failed, no compatible server profile\"\n            exit 1\n        }\n\n        # Skip server profiles which require DNSSEC,\n        # secure transport or use a custom port\n        eval dnssec=\\\"\\$dns_server_${n}_dnssec\\\"\n        eval transport=\\\"\\$dns_server_${n}_transport\\\"\n        [ -z \"$transport\" -o \"$transport\" = \"plain\" ] \\\n            && [ -z \"$dnssec\" -o \"$dnssec\" = \"no\" ] \\\n            && only_standard_server_ports && break\n\n        n=$(expr $n + 1)\n    done\n\n    {\n        i=1\n        maxns=3\n        while :; do\n            maxns=$((maxns - 1))\n            [ $maxns -gt 0 ] || break\n            eval option=\\\"\\$dns_server_${n}_address_${i}\\\" || break\n            [ \"${option}\" ] || break\n            i=$((i + 1))\n            echo \"nameserver ${option}\"\n        done\n        i=1\n        maxdom=6\n        while :; do\n            maxdom=$((maxdom - 1))\n            [ $maxdom -gt 0 ] || break\n            eval option=\\\"\\$dns_search_domain_${i}\\\" || break\n            [ \"${option}\" ] || break\n            i=$((i + 1))\n            echo \"search ${option}\"\n        done\n    } | /sbin/resolvconf -a \"${dev}\"\n    ;;\ndns-down)\n    /sbin/resolvconf -d \"${dev}\" -f\n    ;;\nesac\n"
  },
  {
    "path": "distro/dns-scripts/resolvconf_file-dns-updown.sh",
    "content": "#!/bin/sh\n#\n# Simple OpenVPN up/down script for modifying /etc/resolv.conf\n# (C) Copyright 2024 OpenVPN Inc <sales@openvpn.net>\n#\n# SPDX-License-Identifier: BSD-2-Clause\n#\n# Example env from openvpn (most are not applied):\n#\n#   dns_vars_file /tmp/openvpn_dvf_58b95c0c97b2db43afb5d745f986c53c.tmp\n#\n#      or\n#\n#   dev tun0\n#   script_type dns-up\n#   dns_search_domain_1 mycorp.in\n#   dns_search_domain_2 eu.mycorp.com\n#   dns_server_1_address_1 192.168.99.254\n#   dns_server_1_address_2 fd00::99:53\n#   dns_server_1_port_1 53\n#   dns_server_1_port_2 53\n#   dns_server_1_resolve_domain_1 mycorp.in\n#   dns_server_1_resolve_domain_2 eu.mycorp.com\n#   dns_server_1_dnssec true\n#   dns_server_1_transport DoH\n#   dns_server_1_sni dns.mycorp.in\n#\n\nset -e +u\n\nonly_standard_server_ports() {\n    i=1\n    while true; do\n        eval addr=\\\"\\$dns_server_${n}_address_${i}\\\"\n        [ -n \"$addr\" ] || return 0\n\n        eval port=\\\"\\$dns_server_${n}_port_${i}\\\"\n        [ -z \"$port\" -o \"$port\" = \"53\" ] || return 1\n\n        i=$(expr $i + 1)\n    done\n}\n\nconf=/etc/resolv.conf\ntest -e \"$conf\" || exit 1\ntest -z \"${dns_vars_file}\" || . \"${dns_vars_file}\"\ncase \"${script_type}\" in\ndns-up)\n    n=1\n    while :; do\n        eval addr=\\\"\\$dns_server_${n}_address_1\\\"\n        [ -n \"$addr\" ] || {\n            echo \"setting DNS failed, no compatible server profile\"\n            exit 1\n        }\n\n        # Skip server profiles which require DNSSEC,\n        # secure transport or use a custom port\n        eval dnssec=\\\"\\$dns_server_${n}_dnssec\\\"\n        eval transport=\\\"\\$dns_server_${n}_transport\\\"\n        [ -z \"$transport\" -o \"$transport\" = \"plain\" ] \\\n            && [ -z \"$dnssec\" -o \"$dnssec\" = \"no\" ] \\\n            && only_standard_server_ports && break\n\n        n=$(expr $n + 1)\n    done\n\n    eval addr1=\\\"\\$dns_server_${n}_address_1\\\"\n    eval addr2=\\\"\\$dns_server_${n}_address_2\\\"\n    eval addr3=\\\"\\$dns_server_${n}_address_3\\\"\n    text=\"### openvpn ${dev} begin ###\\n\"\n    text=\"${text}nameserver $addr1\\n\"\n    test -z \"$addr2\" || text=\"${text}nameserver $addr2\\n\"\n    test -z \"$addr3\" || text=\"${text}nameserver $addr3\\n\"\n\n    test -z \"$dns_search_domain_1\" || {\n        for i in $(seq 1 6); do\n            eval domains=\\\"$domains\\$dns_search_domain_${i} \\\" || break\n        done\n        text=\"${text}search $domains\\n\"\n    }\n    text=\"${text}### openvpn ${dev} end ###\"\n    text=\"${text}\\n$(cat ${conf})\"\n\n    echo \"${text}\" > \"${conf}\"\n    ;;\ndns-down)\n    sed -i'' -e \"/### openvpn ${dev} begin ###/,/### openvpn ${dev} end ###/d\" \"$conf\"\n    ;;\nesac\n"
  },
  {
    "path": "distro/dns-scripts/systemd-dns-updown.sh",
    "content": "#!/bin/bash\n#\n# dns-updown - add/remove openvpn provided DNS information\n#\n# Copyright (C) 2024-2026 OpenVPN Inc <sales@openvpn.net>\n#\n# SPDX-License-Identifier: GPL-2.0\n#\n# Add/remove openvpn DNS settings from the env into/from\n# the system. Supported backends in this order:\n#\n#   * systemd-resolved\n#   * resolvconf\n#   * /etc/resolv.conf file\n#\n# Example env from openvpn (not all are always applied):\n#\n#   dns_vars_file /tmp/openvpn_dvf_58b95c0c97b2db43afb5d745f986c53c.tmp\n#\n#      or\n#\n#   dev tun0\n#   script_type dns-up\n#   dns_search_domain_1 mycorp.in\n#   dns_search_domain_2 eu.mycorp.com\n#   dns_server_1_address_1 192.168.99.254\n#   dns_server_1_address_2 fd00::99:53\n#   dns_server_1_port_1 53\n#   dns_server_1_port_2 53\n#   dns_server_1_resolve_domain_1 mycorp.in\n#   dns_server_1_resolve_domain_2 eu.mycorp.com\n#   dns_server_1_dnssec true\n#   dns_server_1_transport DoH\n#   dns_server_1_sni dns.mycorp.in\n#\n\n[ -z \"${dns_vars_file}\" ] || . \"${dns_vars_file}\"\n\nfunction do_resolved_servers {\n    local sni=\"\"\n    local transport_var=dns_server_${n}_transport\n    local sni_var=dns_server_${n}_sni\n    [ \"${!transport_var}\" = \"DoT\" ] && sni=\"#${!sni_var}\"\n\n    local i=1\n    local addrs=\"\"\n    while :; do\n        local addr_var=dns_server_${n}_address_${i}\n        local addr=\"${!addr_var}\"\n        [ -n \"$addr\" ] || break\n\n        local port_var=dns_server_${n}_port_${i}\n        if [ -n \"${!port_var}\" ]; then\n            if [[ \"$addr\" =~ : ]]; then\n                addr=\"[$addr]\"\n            fi\n            addrs+=\"${addr}:${!port_var}${sni} \"\n        else\n            addrs+=\"${addr}${sni} \"\n        fi\n        i=$((i+1))\n    done\n\n    resolvectl dns \"$dev\" $addrs\n}\n\nfunction do_resolved_domains {\n    local list=\"\"\n    for domain_var in ${!dns_search_domain_*}; do\n        list+=\"${!domain_var} \"\n    done\n    local domain_var=dns_server_${n}_resolve_domain_1\n    if [ -z \"${!domain_var}\" ]; then\n        resolvectl default-route \"$dev\" true\n        list+=\"~.\"\n    else\n        resolvectl default-route \"$dev\" false\n        local i=1\n        while :; do\n            domain_var=dns_server_${n}_resolve_domain_${i}\n            [ -n \"${!domain_var}\" ] || break\n            # Add as split domain (~ prefix), if it doesn't already exist\n            [[ \"$list\" =~ (^| )\"${!domain_var}\"( |$) ]] \\\n                || list+=\"~${!domain_var} \"\n            i=$((i+1))\n        done\n    fi\n\n    resolvectl domain \"$dev\" $list\n}\n\nfunction do_resolved_dnssec {\n    local dnssec_var=dns_server_${n}_dnssec\n    if [ \"${!dnssec_var}\" = \"optional\" ]; then\n        resolvectl dnssec \"$dev\" allow-downgrade\n    elif [ \"${!dnssec_var}\" = \"yes\" ]; then\n        resolvectl dnssec \"$dev\" true\n    else\n        resolvectl dnssec \"$dev\" false\n    fi\n}\n\nfunction do_resolved_dnsovertls {\n    local transport_var=dns_server_${n}_transport\n    if [ \"${!transport_var}\" = \"DoT\" ]; then\n        resolvectl dnsovertls \"$dev\" true\n    else\n        resolvectl dnsovertls \"$dev\" false\n    fi\n}\n\nfunction do_resolved {\n    [[ \"$(readlink /etc/resolv.conf)\" =~ systemd ]] || return 1\n\n    n=1\n    while :; do\n        local addr_var=dns_server_${n}_address_1\n        [ -n \"${!addr_var}\" ] || {\n            echo \"setting DNS failed, no compatible server profile\"\n            return 1\n        }\n\n        # Skip server profiles which require DNS-over-HTTPS\n        local transport_var=dns_server_${n}_transport\n        [ -n \"${!transport_var}\" -a \"${!transport_var}\" = \"DoH\" ] || break\n\n        n=$((n+1))\n    done\n\n    if [ \"$script_type\" = \"dns-up\" ]; then\n        echo \"setting DNS using resolvectl\"\n        do_resolved_servers\n        do_resolved_domains\n        do_resolved_dnssec\n        do_resolved_dnsovertls\n    else\n        echo \"unsetting DNS using resolvectl\"\n        resolvectl revert \"$dev\"\n    fi\n\n    return 0\n}\n\nfunction only_standard_server_ports {\n    local i=1\n    while :; do\n        local addr_var=dns_server_${n}_address_${i}\n        [ -n \"${!addr_var}\" ] || return 0\n\n        local port_var=dns_server_${n}_port_${i}\n        [ -z \"${!port_var}\" -o \"${!port_var}\" = \"53\" ] || return 1\n\n        i=$((i+1))\n    done\n}\n\nfunction resolv_conf_compat_profile {\n    local n=1\n    while :; do\n        local server_addr_var=dns_server_${n}_address_1\n        [ -n \"${!server_addr_var}\" ] || {\n            echo \"setting DNS failed, no compatible server profile\"\n            exit 1\n        }\n\n        # Skip server profiles which require DNSSEC,\n        # secure transport or use a custom port\n        local dnssec_var=dns_server_${n}_dnssec\n        local transport_var=dns_server_${n}_transport\n        [ -z \"${!transport_var}\" -o \"${!transport_var}\" = \"plain\" ] \\\n            && [ -z \"${!dnssec_var}\" -o \"${!dnssec_var}\" = \"no\" ] \\\n            && only_standard_server_ports && break\n\n        n=$((n+1))\n    done\n    return $n\n}\n\nfunction do_resolvconf {\n    [ -x /sbin/resolvconf ] || return 1\n\n    resolv_conf_compat_profile\n    local n=$?\n\n    if [ \"$script_type\" = \"dns-up\" ]; then\n        echo \"setting DNS using resolvconf\"\n        local domains=\"\"\n        for domain_var in ${!dns_search_domain_*}; do\n            domains+=\"${!domain_var} \"\n        done\n        {\n            local i=1\n            local maxns=3\n            while [ \"${i}\" -le \"${maxns}\" ]; do\n                local addr_var=dns_server_${n}_address_${i}\n                [ -n \"${!addr_var}\" ] || break\n                echo \"nameserver ${!addr_var}\"\n                i=$((i+1))\n            done\n            [ -z \"$domains\" ] || echo \"search $domains\"\n        } | /sbin/resolvconf -a \"$dev\"\n    else\n        echo \"unsetting DNS using resolvconf\"\n        /sbin/resolvconf -d \"$dev\"\n    fi\n\n    return 0\n}\n\nfunction do_resolv_conf_file {\n    conf=/etc/resolv.conf\n    test -e \"$conf\" || exit 1\n\n    resolv_conf_compat_profile\n    local n=$?\n\n    if [ \"$script_type\" = \"dns-up\" ]; then\n        echo \"setting DNS using resolv.conf file\"\n\n        local addr1_var=dns_server_${n}_address_1\n        local addr2_var=dns_server_${n}_address_2\n        local addr3_var=dns_server_${n}_address_3\n        text=\"### openvpn ${dev} begin ###\\n\"\n        text=\"${text}nameserver ${!addr1_var}\\n\"\n        test -z \"${!addr2_var}\" || text=\"${text}nameserver ${!addr2_var}\\n\"\n        test -z \"${!addr3_var}\" || text=\"${text}nameserver ${!addr3_var}\\n\"\n\n        test -z \"$dns_search_domain_1\" || {\n            for i in $(seq 1 6); do\n                eval domains=\\\"$domains\\$dns_search_domain_${i} \\\" || break\n            done\n            text=\"${text}search $domains\\n\"\n        }\n        text=\"${text}### openvpn ${dev} end ###\"\n\n        sed -i \"1i${text}\" \"$conf\"\n    else\n        echo \"unsetting DNS using resolv.conf file\"\n        sed -i \"/### openvpn ${dev} begin ###/,/### openvpn ${dev} end ###/d\" \"$conf\"\n    fi\n\n    return 0\n}\n\ndo_resolved || do_resolvconf || do_resolv_conf_file || {\n    echo \"setting DNS failed, no method succeeded\"\n    exit 1\n}\n"
  },
  {
    "path": "distro/systemd/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2017-2026 OpenVPN Inc <sales@openvpn.net>\n#\n\n%.service: %.service.in Makefile\n\t$(AM_V_GEN)sed \\\n\t\t-e 's|\\@OPENVPN_VERSION_MAJOR\\@|$(OPENVPN_VERSION_MAJOR)|g' \\\n\t\t-e 's|\\@OPENVPN_VERSION_MINOR\\@|$(OPENVPN_VERSION_MINOR)|g' \\\n\t\t-e 's|\\@sbindir\\@|$(sbindir)|g' \\\n\t\t$< > $@.tmp && mv $@.tmp $@\n\nEXTRA_DIST = \\\n\ttmpfiles-openvpn.conf \\\n\topenvpn-client@.service.in \\\n\topenvpn-server@.service.in\n\nif ENABLE_SYSTEMD\nsystemdunit_DATA = \\\n\topenvpn-client@.service \\\n\topenvpn-server@.service\nCLEANFILES = $(systemdunit_DATA)\ntmpfiles_DATA = \\\n\ttmpfiles-openvpn.conf\ndist_doc_DATA = \\\n\tREADME.systemd\n\ninstall-data-hook:\n\tmv $(DESTDIR)$(tmpfilesdir)/tmpfiles-openvpn.conf $(DESTDIR)$(tmpfilesdir)/openvpn.conf\nendif\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n"
  },
  {
    "path": "distro/systemd/README.systemd",
    "content": "OpenVPN and systemd\n===================\n\nAs of OpenVPN v2.4, upstream is shipping systemd unit files to provide a\nfine grained control of each OpenVPN configuration as well as trying to\nrestrict the capabilities the OpenVPN process have on a system.\n\n\nConfiguration profile types\n---------------------------\nThese new unit files separates between client and server profiles.  The\nconfiguration files are kept in separate directories, to provide clarity\nof the profile they run under.\n\nTypically the client profile cannot bind to any ports below port 1024\nand the client configuration is always started with --nobind.\n\nThe server profile is allowed to bind to any ports.  In addition it enables\na client status file, usually found in the /run/openvpn-server directory.\nThe status format is set to version 2 by default.  These settings may be\noverridden by adding --status and/or --status-version in the OpenVPN\nconfiguration file.\n\nNeither of these profiles makes use of PID files, but OpenVPN reports back to\nsystemd its PID once it has initialized.\n\nFor configuration using a peer-to-peer mode (not using --mode server on one\nof the sides) it is recommended to use the client profile.\n\n\nConfiguration files\n-------------------\nThese new unit files expects client configuration files to be made available\nin /etc/openvpn/client.  Similar for the server configurations, it is expected\nto be found in /etc/openvpn/server.  The configuration files must have a .conf\nfile extension.\n\n\nManaging VPN tunnels\n--------------------\nUse the normal systemctl tool to start, stop VPN tunnels, as well as enable\nand disable tunnels at boot time.  The syntax is:\n\n - client configurations:\n    # systemctl $OPER openvpn-client@$CONFIGNAME\n\n - server configurations:\n    # systemctl $OPER openvpn-server@$CONFIGNAME\n\nSimilarly, to view the OpenVPN journal log use a similar syntax:\n\n   # journalctl -u openvpn-client@$CONFIGNAME\n or\n   # journalctl -u openvpn-server@$CONFIGNAME\n\n* Examples\n  Say your server configuration is /etc/openvpn/server/tun0.conf, you\n  start this VPN service like this:\n\n    # systemctl start openvpn-server@tun0\n\n  A client configuration file in /etc/openvpn/client/corpvpn.conf is\n  started like this:\n\n    # systemctl start openvpn-client@corpvpn\n\n  To view the server configuration's journal only listing entries from\n  yesterday and until today:\n\n    # journalctl --since yesterday -u openvpn-server@tun0\n"
  },
  {
    "path": "distro/systemd/openvpn-client@.service.in",
    "content": "[Unit]\nDescription=OpenVPN tunnel for %i\nAfter=network-online.target\nWants=network-online.target\nDocumentation=man:openvpn(8)\nDocumentation=https://openvpn.net/community-resources/reference-manual-for-openvpn-@OPENVPN_VERSION_MAJOR@-@OPENVPN_VERSION_MINOR@/\nDocumentation=https://community.openvpn.net/openvpn/wiki/HOWTO\n\n[Service]\nType=notify\nPrivateTmp=true\nWorkingDirectory=/etc/openvpn/client\nExecStart=@sbindir@/openvpn --suppress-timestamps --nobind --config %i.conf\nCapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_SYS_CHROOT CAP_DAC_OVERRIDE CAP_SYS_NICE\nTasksMax=20\nDeviceAllow=/dev/null rw\nDeviceAllow=/dev/net/tun rw\nProtectSystem=true\nProtectHome=true\nKillMode=process\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "distro/systemd/openvpn-server@.service.in",
    "content": "[Unit]\nDescription=OpenVPN service for %i\nAfter=network-online.target\nWants=network-online.target\nDocumentation=man:openvpn(8)\nDocumentation=https://openvpn.net/community-resources/reference-manual-for-openvpn-@OPENVPN_VERSION_MAJOR@-@OPENVPN_VERSION_MINOR@/\nDocumentation=https://community.openvpn.net/openvpn/wiki/HOWTO\n\n[Service]\nType=notify\nPrivateTmp=true\nWorkingDirectory=/etc/openvpn/server\nExecStart=@sbindir@/openvpn --status %t/openvpn-server/status-%i.log --status-version 2 --suppress-timestamps --config %i.conf\nCapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_SYS_CHROOT CAP_DAC_OVERRIDE CAP_SYS_NICE CAP_AUDIT_WRITE\nTasksMax=20\nDeviceAllow=/dev/null rw\nDeviceAllow=/dev/net/tun rw\nProtectSystem=true\nProtectHome=true\nKillMode=process\nRestartSec=5s\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "distro/systemd/tmpfiles-openvpn.conf",
    "content": "d /run/openvpn-client 0710 root root -\nd /run/openvpn-server 0710 root root -\n"
  },
  {
    "path": "doc/CMakeLists.txt",
    "content": "set(_GENERATE_HTML_DOC YES)\nset(_GENERATE_MAN_DOC  YES)\nset(_MAYBE_PYTHON \"\")\nfind_program(RST2HTML NAMES rst2html rst2html.py)\nfind_program(RST2MAN  NAMES rst2man  rst2man.py)\n\nif (RST2HTML STREQUAL \"RST2HTML-NOTFOUND\")\n    message(STATUS \"rst2html not found, not generating HTML documentation\")\n    set(_GENERATE_HTML_DOC NO)\nelse ()\n    # We only run this for RST2HTML and assume the result would be the same\n    # for RST2MAN\n    if (DEFINED CACHE{DOCUTILS_NEED_PYTHON})\n        if ($CACHE{DOCUTILS_NEED_PYTHON})\n            set(_MAYBE_PYTHON ${PYTHON})\n        endif ()\n    else ()\n        execute_process(\n            COMMAND ${RST2HTML} --version\n            OUTPUT_VARIABLE RST2HTML_VERSION_EXE\n            )\n        execute_process(\n            COMMAND ${PYTHON} ${RST2HTML} --version\n            OUTPUT_VARIABLE RST2HTML_VERSION_PY\n            )\n        set(_DOCUTILS_NEED_PYTHON OFF)\n        if(RST2HTML_VERSION_EXE STREQUAL \"\")\n            if(RST2HTML_VERSION_PY STREQUAL \"\")\n                message(STATUS \"${RST2HTML} found but not working, not generating documentation\")\n                set(_GENERATE_HTML_DOC NO)\n                set(_GENERATE_MAN_DOC NO)\n            else ()\n                message(STATUS \"${RST2HTML} needs to be executed by ${PYTHON} to work\")\n                set(_MAYBE_PYTHON ${PYTHON})\n                set(_DOCUTILS_NEED_PYTHON ON)\n            endif ()\n        endif ()\n        set(DOCUTILS_NEED_PYTHON ${_DOCUTILS_NEED_PYTHON} CACHE BOOL\n            \"Whether we need to call python instead of rst2*.py directly\")\n    endif (DEFINED CACHE{DOCUTILS_NEED_PYTHON})\nendif ()\nif (RST2MAN STREQUAL \"RST2MAN-NOTFOUND\")\n    message(STATUS \"rst2man not found, not generating man pages\")\n    set(_GENERATE_MAN_DOC NO)\nendif ()\n\nset(OPENVPN_SECTIONS\n    man-sections/advanced-options.rst\n    man-sections/cipher-negotiation.rst\n    man-sections/client-options.rst\n    man-sections/connection-profiles.rst\n    man-sections/encryption-options.rst\n    man-sections/generic-options.rst\n    man-sections/inline-files.rst\n    man-sections/link-options.rst\n    man-sections/log-options.rst\n    man-sections/management-options.rst\n    man-sections/network-config.rst\n    man-sections/pkcs11-options.rst\n    man-sections/plugin-options.rst\n    man-sections/protocol-options.rst\n    man-sections/proxy-options.rst\n    man-sections/renegotiation.rst\n    man-sections/signals.rst\n    man-sections/script-options.rst\n    man-sections/server-options.rst\n    man-sections/tls-options.rst\n    man-sections/unsupported-options.rst\n    man-sections/virtual-routing-and-forwarding.rst\n    man-sections/vpn-network-options.rst\n    man-sections/windows-options.rst\n    )\n\nset(OPENVPN_EXAMPLES_SECTIONS\n    man-sections/example-fingerprint.rst\n    man-sections/examples.rst\n    )\n\nset(RST_FLAGS --strict)\n\nif (_GENERATE_HTML_DOC)\n    list(APPEND ALL_DOCS openvpn.8.html openvpn-examples.5.html)\n    add_custom_command(\n        OUTPUT openvpn.8.html\n        COMMAND ${_MAYBE_PYTHON} ${RST2HTML} ${RST_FLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/openvpn.8.rst ${CMAKE_CURRENT_BINARY_DIR}/openvpn.8.html\n        MAIN_DEPENDENCY openvpn.8.rst\n        DEPENDS ${OPENVPN_SECTIONS}\n        )\n    add_custom_command(\n        OUTPUT openvpn-examples.5.html\n        COMMAND ${_MAYBE_PYTHON} ${RST2HTML} ${RST_FLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/openvpn-examples.5.rst ${CMAKE_CURRENT_BINARY_DIR}/openvpn-examples.5.html\n        MAIN_DEPENDENCY openvpn-examples.5.rst\n        DEPENDS ${OPENVPN_EXAMPLES_SECTIONS}\n        )\nendif ()\nif (_GENERATE_MAN_DOC)\n    list(APPEND ALL_DOCS openvpn.8 openvpn-examples.5)\n    add_custom_command(\n        OUTPUT openvpn.8\n        COMMAND ${_MAYBE_PYTHON} ${RST2MAN} ${RST_FLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/openvpn.8.rst ${CMAKE_CURRENT_BINARY_DIR}/openvpn.8\n        MAIN_DEPENDENCY openvpn.8.rst\n        DEPENDS ${OPENVPN_SECTIONS}\n        )\n    add_custom_command(\n        OUTPUT openvpn-examples.5\n        COMMAND ${_MAYBE_PYTHON} ${RST2MAN} ${RST_FLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/openvpn-examples.5.rst ${CMAKE_CURRENT_BINARY_DIR}/openvpn-examples.5\n        MAIN_DEPENDENCY openvpn-examples.5.rst\n        DEPENDS ${OPENVPN_EXAMPLES_SECTIONS}\n        )\nendif ()\n\nadd_custom_target(documentation ALL DEPENDS ${ALL_DOCS})\n"
  },
  {
    "path": "doc/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2006-2012 Alon Bar-Lev <alon.barlev@gmail.com>\n#\n\nSUBDIRS = doxygen\n\n#\n# List of man and HTML pages we build when rst2man/rst2html is available\n#\n# NOTE: Remember to add source .rst files to $(dist_noinst_DATA) below\n#       This could be automated with GNU Make, but we need BSD Make support\n#\nbuild_man_pages = openvpn.8 openvpn-examples.5\nbuild_html_pages = openvpn.8.html openvpn-examples.5.html\n\ndist_doc_DATA = \\\n\tmanagement-notes.txt gui-notes.txt\n\nopenvpn_sections = \\\n\tman-sections/advanced-options.rst \\\n\tman-sections/cipher-negotiation.rst \\\n\tman-sections/client-options.rst \\\n\tman-sections/connection-profiles.rst \\\n\tman-sections/encryption-options.rst \\\n\tman-sections/generic-options.rst \\\n\tman-sections/inline-files.rst \\\n\tman-sections/link-options.rst \\\n\tman-sections/log-options.rst \\\n\tman-sections/management-options.rst \\\n\tman-sections/network-config.rst \\\n\tman-sections/pkcs11-options.rst \\\n\tman-sections/plugin-options.rst \\\n\tman-sections/protocol-options.rst \\\n\tman-sections/proxy-options.rst \\\n\tman-sections/renegotiation.rst \\\n\tman-sections/signals.rst \\\n\tman-sections/script-options.rst \\\n\tman-sections/server-options.rst \\\n\tman-sections/tls-options.rst \\\n\tman-sections/unsupported-options.rst \\\n\tman-sections/virtual-routing-and-forwarding.rst \\\n\tman-sections/vpn-network-options.rst \\\n\tman-sections/windows-options.rst\n\nopenvpn_examples_sections = \\\n\tman-sections/example-fingerprint.rst \\\n\tman-sections/examples.rst\n\ndist_noinst_DATA = \\\n\tandroid.txt \\\n\tinteractive-service-notes.rst \\\n\tkeying-material-exporter.txt \\\n\topenvpn.8.rst \\\n\topenvpn-examples.5.rst \\\n\tREADME.man \\\n\tREADME.plugins \\\n\ttls-crypt-v2.txt \\\n\t$(openvpn_sections) \\\n\t$(openvpn_examples_sections) \\\n\tCMakeLists.txt\n\nEXTRA_DIST = tests\n\n# dependencies\nopenvpn.8 openvpn.8.html: $(openvpn_sections)\nopenvpn-examples.5 openvpn-examples.5.html: $(openvpn_examples_sections)\n\n######  GENERIC  RULES  ##########\n\nSUFFIXES = .8.rst .8 .8.html .5.rst .5 .5.html\n\nRST_FLAGS = --strict\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n\n.8.rst.8 .5.rst.5 :\nif HAVE_PYDOCUTILS\n\t$(RST2MAN) $(RST_FLAGS) $< > $@\nelse\n\t@echo \"Missing python-docutils - skipping man page generation ($@)\"\nendif\n\n.8.rst.8.html .5.rst.5.html :\nif HAVE_PYDOCUTILS\n\t$(RST2HTML) $(RST_FLAGS) $< > $@\nelse\n\t@echo \"Missing python-docutils - skipping html page generation ($@)\"\nendif\n\n\nif HAVE_PYDOCUTILS\ndist_noinst_DATA += $(build_man_pages)\ndist_html_DATA = $(build_html_pages)\n\n# Failsafe - do not delete these files unless we can recreate them\nCLEANFILES = $(build_man_pages) $(build_html_pages)\n\nendif\n\nif WIN32\nelse\ndist_man_MANS = $(build_man_pages)\nendif\n\ndist-hook : $(build_man_pages) $(build_html_pages)\n"
  },
  {
    "path": "doc/README.man",
    "content": "\nman page documentation\n======================\n\nThe man page content maintained in the openvpn.8.rst file and proper man and\nthe html version of the man page are generated using python-docutils.  Both\nthe man page and html file are generated during 'make dist' or 'make distcheck'\nand should be distributed inside the tarball by default.\n\nUsers compiling OpenVPN from the tarball should not need to regenerate the\nman/html files unless the source file needs to be modified.\n\nFurther information:\n\n* Python docutils project:\n  https://docutils.sourceforge.io/\n\n* Quickstart on .rst\n  https://docutils.sourceforge.io/docs/user/rst/quickstart.html\n\n* reStructuredText Markup Specifictaion (.rst)\n  https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html\n"
  },
  {
    "path": "doc/README.plugins",
    "content": "OpenVPN Plugins\n---------------\n\nStarting with OpenVPN 2.0-beta17, compiled plugin modules are\nsupported on any *nix OS which includes libdl or on Windows.\nOne or more modules may be loaded into OpenVPN using\nthe --plugin directive, and each plugin module is capable of\nintercepting any of the script callbacks which OpenVPN supports:\n\n(1) up\n(2) down\n(3) route-up\n(4) ipchange\n(5) tls-verify\n(6) auth-user-pass-verify\n(7) client-connect\n(8) client-disconnect\n(9) learn-address\n\nSee the openvpn-plugin.h file in the top-level directory of the\nOpenVPN source distribution for more detailed information\non the plugin interface.\n\nIncluded Plugins\n----------------\n\nauth-pam -- Authenticate using PAM and a split privilege\n            execution model which functions even if\n            root privileges or the execution environment\n            have been altered with --user/--group/--chroot.\n            Tested on Linux only.\n\ndown-root -- Enable the running of down scripts with root privileges\n             even if --user/--group/--chroot have been used\n             to drop root privileges or change the execution\n             environment.  Not applicable on Windows.\n\nexamples -- A simple example that demonstrates a portable\n            plugin, i.e. one which can be built for *nix\n            or Windows from the same source.\n\nBuilding Plugins\n----------------\n\ncd to the top-level directory of a plugin, and use the\n\"make\" command to build it.  The examples plugin is\nbuilt using a build script, not a makefile.\n"
  },
  {
    "path": "doc/android.txt",
    "content": "This file documents the support in OpenVPN for Android using the\nVPNService API (https://developer.android.com/reference/android/net/VpnService)\nthat has been introduced in Android 4.0 (API 14).\n\nThis support is primarily used in the \"OpenVPN for Android\" app\n(https://github.com/schwabe/ics-openvpn). For building see the developer\nREADME: https://github.com/schwabe/ics-openvpn/blob/master/doc/README.txt\n\nAndroid provides the VPNService API\n(https://developer.android.com/reference/android/net/VpnService)\nwhich allows establishing VPN connections without rooting the device.\n\nUnlike on other platforms, the tun device is openend by UI instead of\nOpenVPN itself.  The VpnService API needs the following parameters:\n\n- IP and netmask of tun interface\n- Networks that should be routed to the tun interface\n- DNS Servers and DNS Domain\n- MTU\n\nAll IPs/Routes are in CIDR style.  Non-CIDR routes are not supported.\nNotable is the lack of support for setting routes to other interfaces\nusually used to avoid the server connection going over the tun\ninterface.  However, Android 13 adds support for exclusion routes that\nserve the same purpose.  The Android VPNService API has the concept\nof protecting a socket from being routed over an interface. Calling\nprotect (fd) will internally bind the socket to the interface used for the\nexternal connection (usually WiFi or mobile data).\n\nTo use OpenVPN with the VPNService API OpenVPN must be built with\nthe TARGET_ANDROID compile option.  Also the UI must use a UNIX\ndomain socket to connect to OpenVPN.  When compiled as TARGET_ANDROID\nOpenVPN will use management callbacks instead of executing traditional\nifconfig/route commands use the need-ok callback mechanism which\nwill ask\n\n> NEED-OK command\n\nwhere command can be:\n\nIFCONFIG6 IPv6/netmask\nIFCONFIG local remoteOrNetmask MTU topology\n\nTo tell the UI which IPs addresses OpenVPN expects on the interface.\nTopology is one of \"net30\",\"p2p\",\"subnet\" or \"undef\".\n\nROUTE6 network/netmask\nROUTE network netmask\n\nTo tell the UI which routes should be set on the tun interface.\n\nDNSSERVER IP server address\nDNS6SERVER IPv6 server address\nDNSDOMAIN searchdomain\n\nTo set the DNS server and search domain.\n\nThe GUI will then respond with a \"needok 'command' ok' or \"needok\n'command' cancel', e.g. \"needok 'IFCONFIG' ok\".\n\nPERSIST_TUN_ACTION\n\nWhen OpenVPN wants to open an fd it will do this query via management.\nThe UI should compare the last configuration of the tun device with the current\ntun configuration and reply with either NOACTION (or always respond with\nOPEN_BEFORE_CLOSE).\n\n- NOACTION: Keep using the old fd\n- OPEN_BEFORE_CLOSE: the normal behaviour when the VPN configuration changed\n\nFor example the UI could respond with\nneedok 'PERSIST_TUN_ACTION' OPEN_BEFORE_CLOSE\n\nTo protect a socket the OpenVPN will send a PROTECTFD to the UI.\nWhen sending the PROTECTFD command command to the UI it will send\nthe fd of the socket as ancillary message over the UNIX socket.\nThe UI will then call protect(fd) on the received socket protecting\nit from being routed over the VPN.\n\nWhen opening a tun device the OpenVPN process will first send all\nroute, ifconfig and DNS related configuration to the UI and after\nthat calls the OPENTUN command to receive a tun fd with the requested\nconfiguration.  The UI will then use the collected information to\ncall the VPNService's establish() method to receive a fd which in\nturn is send to the OpenVPN process as ancillary message to the\n\"needok 'OPENTUN' ok' response.\n\nThe OpenVPN for Android UI extensively uses other features that\nare not specific to Android but are rarely used on other platform.\nFor example using SIGUSR1 and management-hold to restart, pause,\ncontinue the VPN on network changes or the external key management\n--management-external-key option and inline files.\n\nTo better support handover between networks, a the management command\n\nnetwork-change [samenetwork]\n\nis used on the Android platform.  It tells OpenVPN to do the necessary\naction when the network changes.  Currently this is just calling\nthe protect callback when using peer-id regardless of the samenetwork.\nWithout peer-id OpenVPN will generate USR1 when samenetwork is not set.\n"
  },
  {
    "path": "doc/doxygen/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2017-2026 Sentyron B.V. <openvpn@sentyron.com>\n#\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n\nDISTCLEANFILES = openvpn.doxyfile\n\nDOXYGEN_EXTRA_FILES = \\\n\tdoc_compression.h \\\n\tdoc_control_processor.h \\\n\tdoc_control_tls.h \\\n\tdoc_data_control.h \\\n\tdoc_data_crypto.h \\\n\tdoc_eventloop.h \\\n\tdoc_external_multiplexer.h \\\n\tdoc_fragmentation.h \\\n\tdoc_internal_multiplexer.h \\\n\tdoc_key_generation.h \\\n\tdoc_mainpage.h \\\n\tdoc_memory_management.h \\\n\tdoc_protocol_overview.h \\\n\tdoc_reliable.h \\\n\tdoc_tunnel_state.h\n\nEXTRA_DIST = $(DOXYGEN_EXTRA_FILES)\n\n.PHONY: doxygen\ndoxygen: openvpn.doxyfile $(DOXYGEN_EXTRA_FILES)\n\tdoxygen openvpn.doxyfile\n\nclean-local:\n\t-rm -rf html latex\n"
  },
  {
    "path": "doc/doxygen/doc_compression.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Data Channel Compression module documentation file.\n */\n\n/**\n * @defgroup compression Data Channel Compression module\n *\n * This module offers compression of data channel packets.\n *\n * @par State structures\n * The Data Channel Compression module stores its internal state in a \\c\n * lzo_compress_workspace structure.  This state includes flags which\n * control the module's behavior and preallocated working memory.  One\n * such structure is present for each VPN tunnel, and is stored in the \\c\n * context.c2.lzo_compwork of the \\c context associated with that VPN\n * tunnel.\n *\n * @par Initialization and cleanup\n * Every time a new \\c lzo_compress_workspace is needed, it must be\n * initialized using the \\c lzo_compress_init() function.  Similarly,\n * every time a \\c lzo_compress_workspace is no longer needed, it must be\n * cleaned up using the \\c lzo_compress_uninit() function.  These\n * functions take care of the allocation and freeing of internal working\n * memory, but not of the \\c lzo_compress_workspace structures themselves.\n *\n * @par\n * Because of the one-to-one relationship between \\c\n * lzo_compress_workspace structures and VPN tunnels, the above-mentioned\n * initialization and cleanup functions are called directly from the \\c\n * init_instance() and \\c close_instance() functions, which control the\n * initialization and cleanup of VPN tunnel instances and their associated\n * \\c context structures.\n *\n * @par Packet processing functions\n * This module receives data channel packets from the \\link data_control\n * Data Channel Control module\\endlink and processes them according to the\n * settings of the packet's VPN tunnel.  The \\link data_control Data\n * Channel Control module\\endlink uses the following interface functions:\n * - For packets which will be sent to a remote OpenVPN peer: \\c\n *   lzo_compress()\n * - For packets which have been received from a remote OpenVPN peer: \\c\n *   lzo_decompress()\n *\n * @par Settings that control this module's activity\n * Whether or not the Data Channel Compression module is active depends on\n * the compile-time \\c ENABLE_LZO preprocessor macro and the runtime flags\n * stored in \\c lzo_compress_workspace.flags of the associated VPN tunnel.\n * The latter are initialized from \\c options.lzo, which gets its value\n * from the process's configuration sources, such as its configuration\n * file or command line %options.\n *\n * @par Adaptive compression\n * The compression module supports adaptive compression.  If this feature\n * is enabled, the compression routines monitor their own performance and\n * turn compression on or off depending on whether it is leading to\n * significantly reduced payload size.\n *\n * @par Compression algorithms\n * This module uses the Lempel-Ziv-Oberhumer (LZO) compression algorithms.\n * These offer lossless compression and are designed for high-performance\n * decompression.  This module uses the external \\c lzo library's\n * implementation of the algorithms.\n *\n * @par\n * For more information on the LZO library, see:\\n\n * https://www.oberhumer.com/opensource/lzo/\n */\n"
  },
  {
    "path": "doc/doxygen/doc_control_processor.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel Processor module documentation file.\n */\n\n/**\n * @defgroup control_processor Control Channel Processor module\n *\n * This module controls the setup and maintenance of VPN tunnels and the\n * associated security parameters.\n *\n * @par This module's role\n * The Control Channel Processor module lies at the core of OpenVPN's\n * activities.  It handles the setup of new VPN tunnels, the negotiation\n * of data channel security parameters, the managing of active VPN\n * tunnels, and finally the cleanup of expired VPN tunnels.\n *\n * @par State structures\n * A large amount of VPN tunnel state information must be stored within an\n * OpenVPN process.  A wide variety of container structures are used by\n * this module for that purpose.  Several of these structures are listed\n * below, and the function of the first three VPN tunnel state containers\n * is described in more detail later.\n *  - VPN tunnel state containers:\n *     - \\c tls_multi, security parameter state for a single VPN tunnel.\n *       Contains three instances of the \\c tls_session structure.\n *     - \\c tls_session, security parameter state of a single session\n *       within a VPN tunnel.  Contains two instances of the \\c key_state\n *       structure.\n *     - \\c key_state, security parameter state of one TLS and data\n *       channel %key set.\n *  - Data channel security parameter containers:\n *     - \\c key_ctx_bi, container for two sets of OpenSSL cipher and/or\n *       HMAC context (both directions).  Contains two instances of the \\c\n *       key_ctx structure.\n *     - \\c key_ctx, container for one set of OpenSSL cipher and/or HMAC\n *       context (one directions.\n *  - Key material containers:\n *     - \\c key2, container for two sets of cipher and/or HMAC %key\n *       material (both directions).  Contains two instances of the \\c key\n *       structure.\n *     - \\c key, container for one set of cipher and/or HMAC %key material\n *       (one direction).\n *     - \\c key_direction_state, ordering of %key material within the \\c\n *       key2.key array.\n *  - Key method 2 random material containers:\n *     - \\c key_source2, container for both halves of random material used\n *       for %key method 2.  Contains two instances of the \\c key_source\n *       structure.\n *     - \\c key_source, container for one half of random material used for\n *       %key method 2.\n *\n * @par The life of a \\c tls_multi object\n * A \\c tls_multi structure contains all the security parameter state\n * information related to the control and data channels of one VPN tunnel.\n * Its life cycle can be summarized as follows:\n *  -# Initialization: \\c tls_multi_init() and \\c\n *     tls_multi_init_finalize(), which are called (indirectly) from \\c\n *     init_instance() when initializing a new \\c context structure.\n *     - Initializes a \\c tls_multi structure.\n *     - Allocates the three \\c tls_session objects contained by the \\c\n *       tls_multi structure, and initializes as appropriate.\n *  -# Management: \\c tls_multi_process() and \\c tls_pre_decrypt()\n *     - If a new session is initiated by the remote peer, then \\c\n *       tls_pre_decrypt() starts the new session negotiation in the\n *       un-trusted \\c tls_session.\n *     - If the, as yet, un-trusted \\c tls_session authenticates\n *       successfully, then \\c tls_multi_process() moves it so as to be\n *       the active \\c tls_session.\n *     - If an error occurs during processing of a \\c key_state object,\n *       then \\c tls_multi_process() cleans up and initializes the\n *       associated \\c tls_session object.  If the error occurred in the\n *       active \\c key_state of the active \\c tls_session and the\n *       lame-duck \\c key_state of that \\c tls_session has not yet\n *       expired, it is preserved as fallback.\n *  -# Cleanup: \\c tls_multi_free(), which is called (indirectly) from \\c\n *     close_instance() when cleaning up a \\c context structure.\n *     - Cleans up a \\c tls_multi structure.\n *     - Cleans up the three \\c tls_session objects contained by the \\c\n *       tls_multi structure.\n *\n * @par The life of a \\c tls_session object\n * A \\c tls_session structure contains the state information related to an\n * active and a lame-duck \\c key_state.  Its life cycle can be summarized\n * as follows:\n *  -# Initialization: \\c tls_session_init()\n *     - Initializes a \\c tls_session structure.\n *     - Initializes the primary \\c key_state by calling \\c\n *       key_state_init().\n *  -# Renegotiation: \\c key_state_soft_reset()\n *     - Cleans up the old lame-duck \\c key_state by calling \\c\n *       key_state_free().\n *     - Moves the old primary \\c key_state to be the new lame-duck \\c\n *       key_state.\n *     - Initializes a new primary \\c key_state by calling \\c\n *       key_state_init().\n *  -# Cleanup: \\c tls_session_free()\n *     - Cleans up a \\c tls_session structure.\n *     - Cleans up all \\c key_state objects associated with the session by\n *       calling \\c key_state_free() for each.\n *\n * @par The life of a \\c key_state object\n * A \\c key_state structure represents one control and data channel %key\n * set.  It contains an OpenSSL TLS object that encapsulates the control\n * channel, and the data channel security parameters needed by the \\link\n * data_crypto Data Channel Crypto module\\endlink to perform cryptographic\n * operations on data channel packets.  Its life cycle can be summarized\n * as follows:\n *  -# Initialization: \\c key_state_init()\n *     - Initializes a \\c key_state structure.\n *     - Creates a new OpenSSL TLS object to encapsulate this new control\n *       channel session.\n *     - Sets \\c key_state.state to \\c S_INITIAL.\n *     - Allocates several internal buffers.\n *     - Initializes new reliability layer structures for this key set.\n *  -# Negotiation: \\c tls_process()\n *     - The OpenSSL TLS object negotiates a TLS session between itself\n *       and the remote peer's TLS object.\n *     - Key material is generated and exchanged through the TLS session\n *       between OpenVPN peers.\n *     - Both peers initialize their data channel cipher and HMAC key\n *       contexts.\n *     - On successful negotiation, the \\c key_state.state will progress\n *       from \\c S_INITIAL to \\c S_ACTIVE and \\c S_NORMAL.\n *  -# Active tunneling: \\link data_crypto Data Channel Crypto\n *     module\\endlink\n *     - Data channel packet to be sent to a remote OpenVPN peer:\n *        - \\c tls_pre_encrypt() loads the security parameters from the \\c\n *          key_state into a \\c crypto_options structure.\n *        - \\c openvpn_encrypt() uses the \\c crypto_options to an encrypt\n *          and HMAC sign the data channel packet.\n *     - Data channel packet received from a remote OpenVPN peer:\n *        - \\c tls_pre_decrypt() loads the security parameters from the \\c\n *          key_state into a \\c crypto_options structure.\n *        - \\c openvpn_encrypt() uses the \\c crypto_options to\n *          authenticate and decrypt the data channel packet.\n *  -# Cleanup: \\c key_state_free()\n *     - Cleans up a \\c key_state structure together with its OpenSSL TLS\n *       object, key material, internal buffers, and reliability layer\n *       structures.\n *\n * @par Control functions\n * The following two functions drive the Control Channel Processor's\n * activities.\n *  - \\c tls_multi_process(), iterates through the \\c tls_session objects\n *    within a given \\c tls_multi of a VPN tunnel, and calls \\c\n *    tls_process() for each \\c tls_session which is being set up, is\n *    already active, or is busy expiring.\n *  - \\c tls_process(), performs the Control Channel Processor module's\n *    core handling of received control channel messages, and generates\n *    appropriate messages to be sent.\n *\n * @par Functions which control data channel key generation\n *  - Key method 1 key exchange functions were removed from OpenVPN 2.5\n *  - Key method 2 key exchange functions:\n *     - \\c key_method_2_write(), generates and processes key material to\n *       be sent to the remote OpenVPN peer.\n *     - \\c key_method_2_read(), processes key material received from the\n *       remote OpenVPN peer.\n */\n"
  },
  {
    "path": "doc/doxygen/doc_control_tls.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel TLS module documentation file.\n */\n\n/**\n * @defgroup control_tls Control Channel TLS module\n *\n * This module provides secure encapsulation of control channel messages\n * exchanged between OpenVPN peers.\n *\n * The Control Channel TLS module uses the Transport Layer Security (TLS)\n * protocol to provide an encrypted communication channel between the\n * local OpenVPN process and a remote peer.  This protocol simultaneously\n * offers certificate-based authentication of the communicating parties.\n *\n * @par This module's roles\n * The Control Channel TLS module is essential for the security of any\n * OpenVPN-based system.  On the one hand, it performs the security\n * operations necessary to protect control channel messages exchanged\n * between OpenVPN peers.  On the other hand, before the control and data\n * channels are even setup, it controls the exchange of certificates and\n * verification of the remote's identity during negotiation of VPN\n * tunnels.\n *\n * @par\n * The former role is described below.  The latter is described in the\n * documentation for the verify_callback() function.\n *\n * @par\n * In other words, this module takes care of the confidentiality and\n * integrity of data channel communications, and the authentication of\n * both the communicating parties and the control channel messages\n * exchanged.\n *\n * @par Initialization and cleanup\n * Because of the one-to-one relationship between control channel TLS\n * state and \\c key_state structures, the initialization and cleanup of an\n * instance of the Control Channel TLS module's state happens within the\n * key_state_init() and key_state_free() functions.  In other words,\n * each \\c key_state object contains exactly one OpenSSL SSL-BIO object,\n * which is initialized and cleaned up together with the rest of the \\c\n * key_state object.\n *\n * @par Packet processing functions\n * This object behaves somewhat like a black box with a ciphertext and a\n * plaintext I/O port. Its interaction with OpenVPN's control channel\n * during operation takes place within the tls_process() function of\n * the \\link control_processor Control Channel Processor\\endlink.  The\n * following functions are available for processing packets:\n * - If ciphertext received from the remote peer is available in the \\link\n *   reliable Reliability Layer\\endlink:\n *   - Insert it into the ciphertext-side of the SSL-BIO.\n *   - Use function: key_state_write_ciphertext()\n * - If ciphertext can be extracted from the ciphertext-side of the\n *   SSL-BIO:\n *   - Pass it to the \\link reliable Reliability Layer\\endlink for sending\n *     to the remote peer.\n *   - Use function: key_state_read_ciphertext()\n * - If plaintext can be extracted from the plaintext-side of the SSL-BIO:\n *   - Pass it on to the \\link control_processor Control Channel\n *     Processor\\endlink for local processing.\n *   - Use function: key_state_read_plaintext()\n * - If plaintext from the \\link control_processor Control Channel\n *   Processor\\endlink is available to be sent to the remote peer:\n *   - Insert it into the plaintext-side of the SSL-BIO.\n *   - Use function: key_state_write_plaintext() or\n *     key_state_write_plaintext_const()\n *\n * @par Transport Layer Security protocol implementation\n * This module uses the OpenSSL library's implementation of the TLS\n * protocol in the form of an OpenSSL SSL-BIO object.\n *\n * @par\n * For more information on the OpenSSL library's BIO objects, please see:\n *  - OpenSSL's generic BIO objects:\n *    https://docs.openssl.org/master/man7/bio/#bio\n *  - OpenSSL's SSL-BIO object:\n *    https://docs.openssl.org/master/man3/BIO_f_ssl/#bio_f_ssl\n */\n"
  },
  {
    "path": "doc/doxygen/doc_data_control.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Data Channel Control module documentation file.\n */\n\n/**\n * @defgroup data_control Data Channel Control module\n *\n * This module controls the processing of packets as they pass through the\n * data channel.\n *\n * The Data Channel Control module controls the processing of packets as\n * they pass through the data channel.  The processing includes packet\n * compression, fragmentation, and the performing of security operations\n * on the packets.  This module does not do the processing itself, but\n * passes the packet to other data channel modules to perform the\n * appropriate actions.\n *\n * Packets can travel in two directions through the data channel.  They\n * can be going to a remote destination which is reachable through a VPN\n * tunnel, in which case this module prepares them to be sent out through\n * a VPN tunnel.  On the other hand, they can have been received through a\n * VPN tunnel from a remote OpenVPN peer, in which case this module\n * retrieves the packet in its original form as it was before entering the\n * VPN tunnel on the remote OpenVPN peer.  How this module processes\n * packets traveling in the two directions is discussed in more detail\n * below.\n *\n * @par Packets to be sent to a remote OpenVPN peer\n * This module's main function for processing packets traveling in this\n * direction is \\c encrypt_sign(), which performs the following processing\n * steps:\n * - Call the \\link compression Data Channel Compression module\\endlink to\n *   perform packet compression if necessary.\n * - Call the \\link fragmentation Data Channel Fragmentation\n *   module\\endlink to perform packet fragmentation if necessary.\n * - Call the \\link data_crypto Data Channel Crypto module\\endlink to\n *   perform the required security operations.\n *\n * @par\n * See the \\c encrypt_sign() documentation for details of these\n * interactions.\n *\n * @par\n * After the above processing is complete, the packet is ready to be sent\n * to a remote OpenVPN peer as a VPN tunnel packet.  The actual sending of\n * the packet is handled by the \\link external_multiplexer External\n * Multiplexer\\endlink.\n *\n * @par Packets received from a remote OpenVPN peer\n * The function that controls how packets traveling in this direction are\n * processed is \\c process_incoming_link().  That function, however, also\n * performs some of the tasks required for the \\link external_multiplexer\n * External Multiplexer\\endlink and is therefore listed as part of that\n * module, instead of here.\n *\n * @par\n * After the \\c process_incoming_link() function has determined that a\n * received packet is a data channel packet, it performs the following\n * processing steps:\n * - Call the \\link data_crypto Data Channel Crypto module\\endlink to\n *   perform the required security operations.\n * - Call the \\link fragmentation Data Channel Fragmentation\n *   module\\endlink to perform packet reassembly if necessary.\n * - Call the \\link compression Data Channel Compression module\\endlink to\n *   perform packet decompression if necessary.\n *\n * @par\n * See the \\c process_incoming_link() documentation for details of these\n * interactions.\n *\n * @par\n * After the above processing is complete, the packet is in its original\n * form again as it was received by the remote OpenVPN peer.  It can now\n * be routed further to its final destination.  If that destination is a\n * locally reachable host, then the \\link internal_multiplexer Internal\n * Multiplexer\\endlink will send it there.\n */\n"
  },
  {
    "path": "doc/doxygen/doc_data_crypto.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Data Channel Crypto module documentation file.\n */\n\n/**\n * @addtogroup data_crypto Data Channel Crypto module\n *\n * The Data Channel Crypto Module performs cryptographic operations on\n * data channel packets.\n *\n * @par Security parameters\n * This module is merely the user of a VPN tunnel's security parameters.\n * It does not perform the negotiation and setup of the security\n * parameters, nor the %key generation involved.  These actions are done\n * by the \\link control_processor Control Channel Processor\\endlink.  This\n * module receives the appropriate security parameters from that module in\n * the form of a \\c crypto_options structure when they are necessary for\n * processing a packet.\n *\n * @par Packet processing functions\n * This module receives data channel packets from the \\link data_control\n * Data Channel Control module\\endlink and processes them according to the\n * security parameters of the packet's VPN tunnel.  The \\link data_control\n * Data Channel Control module\\endlink uses the following interface\n * functions:\n *  - For packets which will be sent to a remote OpenVPN peer:\n *     - \\c tls_pre_encrypt()\n *     - \\c openvpn_encrypt()\n *     - \\c tls_post_encrypt()\n *  - For packets which have been received from a remote OpenVPN peer:\n *     - \\c tls_pre_decrypt() (documented as part of the \\link\n *       external_multiplexer External Multiplexer\\endlink)\n *     - \\c openvpn_decrypt()\n *\n * @par Settings that control this module's activity\n * How the data channel processes packets received from the \\link data_control\n * Data Channel Control module\\endlink at runtime depends on the associated\n * \\c crypto_options structure.  To perform cryptographic operations, the\n * \\c crypto_options.key_ctx_bi must contain the correct cipher and HMAC\n * security parameters for the direction the packet is traveling in.\n *\n * @par Crypto algorithms\n * This module uses the crypto algorithm implementations of the external\n * crypto library (currently either OpenSSL (default), or mbed TLS).\n */\n"
  },
  {
    "path": "doc/doxygen/doc_eventloop.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Main Event Loop module documentation file.\n */\n\n/**\n * @defgroup eventloop Main Event Loop module\n *\n * This main event loop module drives the packet processing of OpenVPN.\n *\n * OpenVPN is an event driven system.  Its activities are driven by a main\n * event loop, which repeatedly waits for one of several predefined events\n * to occur, and then calls the appropriate module to handle the event.\n * The major types of network events that OpenVPN processes are:\n * - A packet can be read from the external network interface.\n *   - The main event loop activates the \\link external_multiplexer\n *     External Multiplexer\\endlink to read and process the packet.\n * - A packet can be read from the virtual tun/tap network interface.\n *   - The main event loop activates the \\link internal_multiplexer\n *     Internal Multiplexer\\endlink to read and process the packet.\n * - If a packet is ready to be sent out as a VPN tunnel packet: the\n *   external network interface can be written to.\n *   - The main event loop activates the \\link external_multiplexer\n *     External Multiplexer\\endlink to send the packet.\n * - If a packet is ready to be sent to a locally reachable destination:\n *   the virtual tun/tap network interface can be written to.\n *   - The main event loop activates the \\link internal_multiplexer\n *     Internal Multiplexer\\endlink to send the packet.\n *\n * Beside these external events, OpenVPN also processes other types of\n * internal events.  These include scheduled events, such as resending of\n * non-acknowledged control channel messages.\n *\n * @par Main event loop implementations\n *\n * Depending on the mode in which OpenVPN is running, a different main\n * event loop function is called to drive the event processing.  The\n * following implementations are available:\n * - Client mode using UDP or TCP: \\c tunnel_point_to_point()\n * - Server mode using UDP: \\c tunnel_server_udp()\n * - Server mode using TCP: \\c tunnel_server_tcp()\n */\n"
  },
  {
    "path": "doc/doxygen/doc_external_multiplexer.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * External Multiplexer module documentation file.\n */\n\n/**\n * @addtogroup external_multiplexer External Multiplexer module\n *\n * The External Multiplexer is the link between the external network\n * interface and the other OpenVPN modules.  It reads packets from the\n * external network interface, determines which remote OpenVPN peer and\n * VPN tunnel they are associated with, and whether they are data channel\n * or control channel packets.  It then passes the packets on to the\n * appropriate processing module.\n *\n * This module also handles packets traveling in the reverse direction,\n * which have been generated by the local control channel or which have\n * already been processed by the \\link data_control Data Channel Control\n * module\\endlink and are destined for a remote host reachable through a\n * VPN tunnel.\n */\n"
  },
  {
    "path": "doc/doxygen/doc_fragmentation.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Data Channel Fragmentation module documentation file.\n */\n\n/**\n * @defgroup fragmentation Data Channel Fragmentation module\n *\n * The Data Channel Fragmentation module offers fragmentation of data\n * channel packets.\n *\n * @par State structures\n * The Data Channel Fragmentation module stores its internal state in a \\c\n * fragment_master structure.  One such structure is present for each VPN\n * tunnel, and is stored in \\c context.c2.fragment of the \\c context\n * associated with that VPN tunnel.\n *\n * @par\n * The \\c fragment_master structure contains one \\c fragment_list\n * structure \\c fragment_master.incoming.  This is a list of \\c fragment\n * structures, each of which can store the parts of one fragmented packet\n * while it is being reassembled.  The \\c fragment_master structure also\n * contains one \\c buffer called \\c fragment_master.outgoing, in which a\n * data channel large packet to be sent to a remote OpenVPN peer can be\n * broken up into parts to be sent one by one.\n *\n * @par Initialization and cleanup\n * Every time a new \\c fragment_master is needed, it must be allocated and\n * initialized by the \\c fragment_init() function.  Similarly, every time\n * a \\c fragment_master is no longer needed, it must be cleaned up using\n * the \\c fragment_free() function.  These functions take care of the\n * allocation and freeing of the \\c fragment_master structure itself and\n * all internal memory required for the use of that structure.  Note that\n * this behavior is different from that displayed by the \\link compression\n * Data Channel Compression module\\endlink.\n *\n * @par\n * Because of the one-to-one relationship between \\c fragment_master\n * structures and VPN tunnels, the above-mentioned initialization and\n * cleanup functions are called directly from the \\c init_instance() and\n * \\c close_instance() functions, which control the initialization and\n * cleanup of VPN tunnel instances and their associated \\c context\n * structures.\n *\n * @par Packet processing functions\n * This module receives data channel packets from the \\link data_control\n * Data Channel Control module\\endlink and processes them according to the\n * settings of the packet's VPN tunnel.  The \\link data_control Data\n * Channel Control module\\endlink uses the following interface functions:\n * - For packets which will be sent to a remote OpenVPN peer: \\c\n *   fragment_outgoing() \\n This function inspects data channel packets as\n *   they are being made ready to be sent as VPN tunnel packets to a\n *   remote OpenVPN peer.  If a packet's size is larger than its\n *   destination VPN tunnel's maximum transmission unit (MTU), then this\n *   module breaks that packet up into smaller parts, each of which is\n *   smaller than or equal to the VPN tunnel's MTU.  See \\c\n *   fragment_outgoing() for details.\n * - For packets which have been received from a remote OpenVPN peer: \\c\n *   fragment_incoming() \\n This function inspects data channel packets\n *   that have been received from a remote OpenVPN peer through a VPN\n *   tunnel.  It reads the fragmentation header of the packet, and\n *   depending on its value performs the appropriate action.  See \\c\n *   fragment_incoming() for details.\n *\n * @par Settings that control this module's activity\n * Whether the Data Channel Fragmentation module is active or not depends\n * on the compile-time \\c ENABLE_FRAGMENT preprocessor macro and the\n * runtime flag \\c options.fragment, which gets its value from the\n * process's configuration sources, such as the configuration file and\n * commandline %options.\n */\n"
  },
  {
    "path": "doc/doxygen/doc_internal_multiplexer.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Internal Multiplexer module documentation file.\n */\n\n/**\n * @addtogroup internal_multiplexer Internal Multiplexer module\n *\n * The Internal Multiplexer is the link between the virtual tun/tap\n * network interface and the \\link data_control Data Channel Control\n * module\\endlink.  It reads packets from the virtual network interface,\n * determines for which remote OpenVPN peer they are destined, and then\n * passes the packets on to the Data Channel Control module together with\n * information about their destination VPN tunnel instance.\n *\n * This module also handles packets traveling in the reverse direction,\n * which have already been processed by the Data Channel Control module\n * and are destined for a locally reachable host.\n */\n"
  },
  {
    "path": "doc/doxygen/doc_key_generation.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Key generation documentation file.\n */\n\n/**\n * @page key_generation Data channel %key generation\n *\n * This section describes how OpenVPN peers generate and exchange %key\n * material necessary for the security operations performed on data\n * channel packets.\n *\n * The %key generation and exchange process between OpenVPN client and\n * server occurs every time data channel security parameters are\n * negotiated, for example during the initial setup of a VPN tunnel or\n * when the active security parameters expire.  In source code terms, this\n * is when a new key_state structure is initialized.\n *\n * @section key_generation_method Key methods\n *\n * OpenVPN supports two different ways of generating and exchanging %key\n * material between client and server.  These are known as %key method 1\n * and %key method 2.  %Key method 2 is the recommended method. Both are\n * explained below.\n *\n * @subsection key_generation_method_1 Key method 1\n *\n * -# Each host generates its own random material.\n * -# Each host uses its locally generated random material as %key data\n *    for encrypting and signing packets sent to the remote peer.\n * -# Each host then sends its random material to the remote peer, so that\n *    the remote peer can use that %key data for authenticating and\n *    decrypting received packets.\n *\n * @subsection key_generation_method_2 Key method 2\n *\n * There are two methods for generating key data when using key method 2\n * the first is OpenVPN's traditional approach that exchanges random\n * data and uses a PRF and the other is using the RFC5705 keying material\n * exporter to generate the key material. For both methods the random\n * data is exchange but only used in the traditional method.\n *\n * -# The client generates random material in the following amounts:\n *    - Pre-master secret: 48 bytes\n *    - Client's PRF seed for master secret: 32 bytes\n *    - Client's PRF seed for %key expansion: 32 bytes\n * -# The client sends its share of random material to the server.\n * -# The server generates random material in the following amounts:\n *    - Server's PRF seed for master secret: 32 bytes\n *    - Server's PRF seed for %key expansion: 32 bytes\n * -# The server computes the %key expansion using its own and the\n *    client's random material.\n * -# The server sends its share of random material to the client.\n * -# The client computes the %key expansion using its own and the\n *    server's random material.\n *\n * %Key method 2 %key expansion is performed by the \\c\n * generate_key_expansion_openvpn_prf() function.  Please refer to its source\n * code for details of the %key expansion process.\n *\n * When the client sends the IV_PROTO_TLS_KEY_EXPORT flag and the server replies\n * with `key-derivation tls-ekm` the RFC5705 key material exporter with the\n * label EXPORTER-OpenVPN-datakeys is used for the key data.\n *\n * @subsection key_generation_random Source of random material\n *\n * OpenVPN uses the either the OpenSSL library or the mbed TLS library as its\n * source of random material.\n *\n * In OpenSSL, the \\c RAND_bytes() function is called\n * to supply cryptographically strong pseudo-random data.  The following links\n * contain more information on this subject:\n * - For OpenSSL's \\c RAND_bytes() function:\n *   https://docs.openssl.org/master/man3/RAND_bytes/#rand_bytes\n * - For OpenSSL's pseudo-random number generating system:\n *   https://docs.openssl.org/master/man7/RAND/#rand\n * - For OpenSSL's support for external crypto modules:\n *   https://docs.openssl.org/master/man7/provider/#provider\n *\n * In mbed TLS, the Havege random number generator is used. For details, see\n * the mbed TLS documentation.\n *\n * @section key_generation_exchange Key exchange:\n *\n * The %key exchange process is initiated by the OpenVPN process running\n * in client mode.  After the initial three-way handshake has successfully\n * completed, the client sends its share of random material to the server,\n * after which the server responds with its part.  This process is\n * depicted below:\n *\n@verbatim\n  Client           Client                           Server          Server\n  State            Action                           Action          State\n----------  --------------------            --------------------  ----------\n\n             ... waiting until three-way handshake complete ...\nS_START                                                              S_START\n            key_method_?_write()\n            send to server  --> --> --> -->  receive from client\nS_SENT_KEY                                   key_method_?_read()\n                                                                   S_GOT_KEY\n                                            key_method_?_write()\n            receive from server  <-- <-- <-- <--  send to client\n            key_method_?_read()                                   S_SENT_KEY\nS_GOT_KEY\n          ... waiting until control channel fully synchronized ...\nS_ACTIVE                                                            S_ACTIVE\n@endverbatim\n *\n * For more information about the client and server state values, see the\n * \\link control_processor Control Channel Processor module\\endlink.\n *\n * Depending on which %key method is used, the \\c ? in the function names\n * of the diagram above is a \\c 1 or a \\c 2.  For example, if %key method\n * 2 is used, that %key exchange would be started by the client calling \\c\n * key_method_2_write().  These functions are called from the \\link\n * control_processor Control Channel Processor module's\\endlink \\c\n * tls_process() function and control the %key generation and exchange\n * process as follows:\n * - %Key method 1 has been removed in OpenVPN 2.5\n * - %Key method 2:\n *   - \\c key_method_2_write(): generate random material locally, and if\n *     in server mode generate %key expansion.\n *   - \\c key_method_2_read(): read random material received from remote\n *     peer, and if in client mode generate %key expansion.\n *\n * @subsection key_generation_encapsulation Transmission of key material\n *\n * The OpenVPN client and server communicate with each other through their\n * control channel.  This means that all of the data transmitted over the\n * network, such as random material for %key generation, is encapsulated\n * in a TLS layer.  For more details, see the \\link control_tls Control\n * Channel TLS module\\endlink documentation.\n */\n"
  },
  {
    "path": "doc/doxygen/doc_mainpage.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Main page documentation file.\n */\n\n/**\n * @mainpage OpenVPN source code documentation\n *\n * This documentation describes the internal structure of OpenVPN.  It was\n * automatically generated from specially formatted comment blocks in\n * OpenVPN's source code using Doxygen.  (See\n * https://www.doxygen.nl/ for more information on Doxygen)\n *\n * The \\ref mainpage_modules \"Modules section\" below gives an introduction\n * into the high-level module concepts used throughout this documentation.\n * The \\ref mainpage_relatedpages \"Related Pages section\" below describes\n * various special subjects related to OpenVPN's implementation which are\n * discussed in the related pages section.\n *\n * @section mainpage_modules Modules\n *\n * For the purpose of describing the internal structure of OpenVPN, this\n * documentation and the underlying source code has been broken up into a\n * number of conceptually well-defined parts, known as modules. Each\n * module plays a specific role within the OpenVPN process, and in most\n * cases each module has a clear interfacing strategy for interacting with\n * other modules.\n *\n * The following modules have been defined:\n * - Driver module:\n *   - The \\link eventloop Main Event Loop\\endlink: this module drives the\n *     event handling of OpenVPN.  It implements various types of\n *     select-loop which wait until an event happens, and then delegate\n *     the handling of that event to the appropriate module.\n * - Network interface modules:\n *   - The \\link external_multiplexer External Multiplexer\\endlink: this\n *     module sends and receives packets to and from remote OpenVPN peers\n *     over the external network interface.  It also takes care of\n *     demultiplexing received packets to their appropriate VPN tunnel and\n *     splitting control channel and data channel packets.\n *   - The \\link internal_multiplexer Internal Multiplexer\\endlink: this\n *     module sends and receives packets to and from locally reachable\n *     posts over the virtual tun/tap network interface.  It also takes\n *     care of determining through which VPN tunnel a received packet must\n *     be sent to reach its destination.\n * - Control channel modules:\n *   - The \\link reliable Reliability Layer\\endlink: this module offers a\n *     %reliable and sequential transport layer for control channel\n *     messages.\n *   - The \\link control_tls Control Channel TLS module\\endlink: this\n *     module offers a secure encapsulation of control channel messages\n *     using the TLS protocol.\n *   - The \\link control_processor Control Channel Processor\\endlink: his\n *     module manages the setup, maintenance, and shut down of VPN\n *     tunnels.\n * - Data channel modules:\n *   - The \\link data_control Data Channel Control module\\endlink: this\n *     module controls the processing of data channel packets and,\n *     depending on the settings of the packet's VPN tunnel, passes the\n *     packet to the three modules below for handling.\n *   - The \\link data_crypto Data Channel Crypto module\\endlink: this\n *     module performs security operations on data channel packets.\n *   - The \\link fragmentation Data Channel Fragmentation module\\endlink:\n *     this module offers fragmentation of data channel packets larger\n *     than the VPN tunnel's MTU.\n *   - The \\link compression Data Channel Compression module\\endlink: this\n *     module offers compression of data channel packets.\n *\n * @subsection mainpage_modules_example Example event: receiving a packet\n *\n * OpenVPN handles many types of events during operation.  These include\n * external events, such as network traffic being received, and internal\n * events, such as a %key session timing out causing renegotiation.  An\n * example event, receiving a packet over the network, is described here\n * together with which modules play what roles:\n * -# The \\link eventloop Main Event Loop\\endlink detects that a packet\n *    can be read from the external or the virtual tun/tap network\n *    interface.\n * -# The \\link eventloop Main Event Loop\\endlink calls the \\link\n *    external_multiplexer External Multiplexer\\endlink or \\link\n *    internal_multiplexer Internal Multiplexer\\endlink to read and\n *    process the packet.\n * -# The multiplexer module determines the type of packet and its\n *    destination, and passes the packet on to the appropriate handling\n *    module:\n *    - A control channel packet received by the \\link\n *      external_multiplexer External Multiplexer\\endlink is passed on\n *      through the \\link reliable Reliability Layer\\endlink and the \\link\n *      control_tls Control Channel TLS module\\endlink to the \\link\n *      control_processor Control Channel Processor\\endlink.\n *    - A data channel packet received by either multiplexer module is\n *      passed on to the \\link data_control Data Channel Control\n *      module\\endlink.\n * -# The packet is processed by the appropriate control channel or data\n *    channel modules.\n * -# If, after processing the packet, a resulting packet is generated\n *    that needs to be sent to a local or remote destination, it is given\n *    to the \\link external_multiplexer External Multiplexer\\endlink or\n *    \\link internal_multiplexer Internal Multiplexer\\endlink for sending.\n * -# If a packet is waiting to be sent by either multiplexer module and\n *    the \\link eventloop Main Event Loop\\endlink detects that data can be\n *    written to the associated network interface, it calls the\n *    multiplexer module to send the packet.\n *\n * @section mainpage_relatedpages Related pages\n *\n * This documentation includes a number of descriptions of various aspects\n * of OpenVPN and its implementation.  These are not directly related to\n * one module, function, or data structure, and are therefore listed\n * separately under \"Related Pages\".\n *\n * @subsection mainpage_relatedpages_key_generation Data channel key generation\n *\n * The @ref key_generation \"Data channel key generation\" related page\n * describes how, during VPN tunnel setup and renegotiation, OpenVPN peers\n * generate and exchange the %key material required for the symmetric\n * encryption/decryption and HMAC signing/verifying security operations\n * performed on data channel packets.\n *\n * @subsection mainpage_relatedpages_tunnel_state VPN tunnel state\n *\n * The @ref tunnel_state \"Structure of VPN tunnel state storage\" related\n * page describes how an OpenVPN process manages the state information\n * associated with its active VPN tunnels.\n *\n * @subsection mainpage_relatedpages_network_protocol Network protocol\n *\n * The @ref network_protocol \"Network protocol\" related page describes the\n * format and content of VPN tunnel packets exchanged between OpenVPN\n * peers.\n *\n * @subsection mainpage_relatedpages_memory_management Memory management\n *\n * The @ref memory_management \"Memory management strategies\" related page\n * gives a brief introduction into OpenVPN's memory %buffer library and\n * garbage collection facilities.\n */\n"
  },
  {
    "path": "doc/doxygen/doc_memory_management.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Memory management strategies documentation file.\n */\n\n/**\n * @page memory_management OpenVPN's memory management strategies\n *\n * This section describes several implementation details relating to\n * OpenVPN's memory management strategies.\n *\n * During operation, the OpenVPN process performs all kinds of operations\n * on blocks of data.  Receiving packets, encrypting content, prepending\n * headers, etc.  To make the programmer's job easier and to decrease the\n * likelihood of memory-related bugs, OpenVPN uses its own memory %buffer\n * library and garbage collection facilities.  These are described in\n * brief here.\n *\n * @section memory_management_buffer The buffer structure\n *\n * The \\c buffer structure is a wrapper around a block of dynamically\n * allocated memory which keeps track of the block's capacity \\c\n * buffer.capacity and location in memory \\c buffer.data.  This structure\n * supports efficient prepending and appending within the allocated memory\n * through the use of offset \\c buffer.offset and length \\c buffer.len\n * fields.  See the \\c buffer documentation for more details on the\n * structure itself.\n *\n * OpenVPN's %buffer library, implemented in the \\c buffer.h and \\c\n * buffer.c files, contains many utility functions for working with \\c\n * buffer structures.  These functions facilitate common operations, such\n * as allocating, freeing, reading and writing to \\c buffer structures,\n * and even offer several more advanced operations, such as string\n * matching and creating sub-buffers.\n *\n * Not only do these utility functions make working with \\c buffer\n * structures easy, they also perform extensive error checking.  Each\n * function, where necessary, checks whether enough space is available\n * before performing its actions.  This minimizes the chance of bugs\n * leading to %buffer overflows and other vulnerabilities.\n *\n * @section memory_management_frame The frame structure\n *\n * The \\c frame structure keeps track of the maximum allowed packet\n * geometries of a network connection.\n *\n * It is used, for example, to determine the size of \\c buffer structures\n * in which to store data channel packets.  This is done by having each\n * data channel processing module register the maximum amount of extra\n * space it will need for header prepending and content expansion in the\n * \\c frame structure. Once these parameters are known, \\c buffer\n * structures can be allocated, based on the \\c frame parameters, so that\n * they are large enough to allow efficient prepending of headers and\n * processing of content.\n *\n * @section memory_management_garbage Garbage collection\n *\n * OpenVPN has many sizable functions which perform various actions\n * depending on their %context.  This makes it difficult to know in advance\n * exactly how much memory must be allocated.  The garbage collection\n * facilities are used to keep track of dynamic allocations, thereby\n * allowing easy collective freeing of the allocated memory.\n *\n * The garbage collection system is implemented by the \\c gc_arena and \\c\n * gc_entry structures.  The arena represents a garbage collecting unit,\n * and contains a linked list of entries.  Each entry represents one block\n * of dynamically allocated memory.\n *\n * The garbage collection system also contains various utility functions\n * for working with the garbage collection structures.  These include\n * functions for initializing new arenas, allocating memory of a given\n * size and registering the allocation in an arena, and freeing all the\n * allocated memory associated with an arena.\n */\n"
  },
  {
    "path": "doc/doxygen/doc_protocol_overview.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Network protocol overview documentation file.\n */\n\n/**\n * @page network_protocol OpenVPN's network protocol\n *\n * Description of packet structure in OpenVPN's network protocol.\n *\n * This document describes the structure of packets exchanged between\n * OpenVPN peers.  It is based on the protocol description in the \\c ssl.h\n * file.\n *\n * @section network_protocol_external Outer structure of packets exchanged between OpenVPN peers\n *\n * VPN tunnel packets are transported between OpenVPN peers using the UDP\n * or TCP protocols.  Their structure is described below.\n *\n * @subsection network_protocol_external_structure External packet structure\n *\n *  - packet length (16 bits, unsigned) [TCP-mode only]: always sent as\n *    plain text.  Since TCP is a stream protocol, this packet length\n *    defines the packetization of the stream.\n *  - packet opcode and key_id (8 bits) [TLS-mode only]:\n *     - package message type (high 5 bits)\n *     - key_id (low 3 bits): the key_id refers to an already negotiated\n *       TLS session.  OpenVPN seamlessly renegotiates the TLS session by\n *       using a new key_id for the new session.  Overlap (controlled by\n *       user definable parameters) between old and new TLS sessions is\n *       allowed, providing a seamless transition during tunnel operation.\n *  - payload (n bytes)\n *\n * @subsection network_protocol_external_types Message types\n *\n * The type of a VPN tunnel packet is indicated by its opcode.  The\n * following describes the various opcodes available.\n *\n *  - Control channel messages:\n *     - \\ref P_CONTROL_HARD_RESET_CLIENT_V1 -- %Key method 1, initial %key\n *       from client, forget previous state.\n *     - \\ref P_CONTROL_HARD_RESET_SERVER_V1 -- %Key method 1, initial %key\n *       from server, forget previous state.\n *     - \\ref P_CONTROL_HARD_RESET_CLIENT_V2 -- %Key method 2, initial %key\n *       from client, forget previous state.\n *     - \\ref P_CONTROL_HARD_RESET_SERVER_V2 -- %Key method 2, initial %key\n *       from server, forget previous state.\n *     - \\ref P_CONTROL_SOFT_RESET_V1 -- New %key, with a graceful\n *       transition from old to new %key in the sense that a transition\n *       window exists where both the old or new key_id can be used.\n *     - \\ref P_CONTROL_V1 -- Control channel packet (usually TLS\n *       ciphertext).\n *     - \\ref P_ACK_V1 -- Acknowledgement for control channel packets\n *       received.\n *  - Data channel messages:\n *     - \\ref P_DATA_V1 -- Data channel packet containing data channel\n *       ciphertext.\n *     - \\ref P_DATA_V2 -- Data channel packet containing peer-id and data\n *       channel ciphertext.\n *\n * @subsection network_protocol_external_key_id Session IDs and Key IDs\n *\n * OpenVPN uses two different forms of packet identifiers:\n *  - The first form is 64 bits and is used for all control channel\n *    messages.  This form is referred to as a \\c session_id.\n *  - Data channel messages on the other hand use a shortened form of 3\n *    bits for efficiency reasons since the vast majority of OpenVPN\n *    packets in an active tunnel will be data channel messages.  This\n *    form is referred to as a \\c key_id.\n *\n * The control and data channels use independent packet-id sequences,\n * because the data channel is an unreliable channel while the control\n * channel is a %reliable channel.  Each use their own independent HMAC\n * keys.\n *\n * @subsection network_protocol_external_reliable Control channel reliability layer\n *\n * Control channel messages (\\c P_CONTROL_* and \\c P_ACK_* message types)\n * are TLS ciphertext packets which have been encapsulated inside of a\n * reliability layer.  The reliability layer is implemented as a\n * straightforward acknowledge and retransmit model.\n *\n * Acknowledgments of received messages can be encoded in either the\n * dedicated \\c P_ACK_* record or they can be prepended to a \\c\n * P_CONTROL_* message.\n *\n * See the \\link reliable Reliability Layer\\endlink module for a detailed\n * description.\n *\n * @section network_protocol_control Structure of control channel messages\n *\n * @subsection network_protocol_control_ciphertext Structure of ciphertext control channel messages\n *\n * Control channel packets in ciphertext form consist of the following\n * parts:\n *\n *  - local \\c session_id (random 64 bit value to identify TLS session).\n *      (the tls-server side uses a HMAC of the client to create a pseudo\n *       random number for a SYN Cookie like approach)\n *  - HMAC signature of entire encapsulation header for HMAC firewall\n *    [only if \\c --tls-auth is specified] (usually 16 or 20 bytes).\n *  - packet-id for replay protection (4 or 8 bytes, includes sequence\n *    number and optional \\c time_t timestamp).\n *  - acknowledgment packet-id array length (1 byte).\n *  - acknowledgment packet-id array (if length > 0).\n *  - acknowledgment remote session-id (if length > 0).\n *  - packet-id of this message (4 bytes).\n *  - TLS payload ciphertext (n bytes) (only for \\c P_CONTROL_V1).\n *\n * Note that when \\c --tls-auth is used, all message types are protected\n * with an HMAC signature, even the initial packets of the TLS handshake.\n * This makes it easy for OpenVPN to throw away bogus packets quickly,\n * without wasting resources on attempting a TLS handshake which will\n * ultimately fail.\n *\n * @subsection network_protocol_control_key_methods Control channel key methods\n *\n * Once the TLS session has been initialized and authenticated, the TLS\n * channel is used to exchange random %key material for bidirectional\n * cipher and HMAC keys which will be used to secure data channel packets.\n * OpenVPN currently implements two %key methods.  %Key method 1 directly\n * derives keys using random bits obtained from the \\c rand_bytes() function.\n * %Key method 2 mixes random %key material from both sides of the connection\n * using the TLS PRF mixing function.  %Key method 2 is the preferred method and\n * is the default for OpenVPN 2.0+.\n *\n * The @ref key_generation \"Data channel key generation\" related page\n * describes the %key methods in more detail.\n *\n * @subsection network_protocol_control_plaintext Structure of plaintext control channel messages\n *\n *  - %Key method 1 (support removed in OpenVPN 2.5):\n *     - Cipher %key length in bytes (1 byte).\n *     - Cipher %key (n bytes).\n *     - HMAC %key length in bytes (1 byte).\n *     - HMAC %key (n bytes).\n *     - %Options string (n bytes, null terminated, client/server %options\n *       string should match).\n *  - %Key method 2:\n *     - Literal 0 (4 bytes).\n *     - %Key method (1 byte).\n *     - \\c key_source structure (\\c key_source.pre_master only defined\n *       for client -> server).\n *     - %Options string length, including null (2 bytes).\n *     - %Options string (n bytes, null terminated, client/server %options\n *       string must match).\n *     - [The username/password data below is optional, record can end at\n *       this point.]\n *     - Username string length, including null (2 bytes).\n *     - Username string (n bytes, null terminated).\n *     - Password string length, including null (2 bytes).\n *     - Password string (n bytes, null terminated).\n *\n * @section network_protocol_data Structure of data channel messages\n *\n * The P_DATA_* payload represents encapsulated tunnel packets which tend to be\n * either IP packets or Ethernet frames. This is essentially the \"payload\" of\n * the VPN. Data channel packets consist of a data channel header, and a\n * payload. There are two possible formats:\n *\n * @par P_DATA_V1\n * P_DATA_V1 packets have a 1-byte header, carrying the \\ref P_DATA_V1 \\c opcode\n * and \\c key_id, followed by the payload:\\n\n * <tt> [ 5-bit opcode | 3-bit key_id ] [ payload ] </tt>\n *\n * @par P_DATA_V2\n * P_DATA_V2 packets have the same 1-byte opcode/key_id, but carrying the \\ref\n * P_DATA_V2 opcode, followed by a 3-byte peer-id, which uniquely identifies\n * the peer:\\n\n * <tt> [ 5-bit opcode | 3-bit key_id ] [ 24-bit peer-id ] [ payload ] </tt>\n *\n * See @ref data_crypto for details on the data channel payload format.\n *\n */\n"
  },
  {
    "path": "doc/doxygen/doc_reliable.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Reliability Layer module documentation file.\n */\n\n/**\n * @defgroup reliable Reliability Layer module\n *\n * The Reliability Layer is part of OpenVPN's control channel.  It\n * provides a reliable and sequential transport mechanism for control\n * channel messages between OpenVPN peers.  This module forms the\n * interface between the \\link external_multiplexer External\n * Multiplexer\\endlink and the \\link control_tls Control Channel TLS\n * module\\endlink.\n *\n * @par UDP or TCP as VPN tunnel transport\n *\n * This is especially important when OpenVPN is configured to communicate\n * over UDP, because UDP does not offer a reliable and sequential\n * transport.  OpenVPN endpoints can also communicate over TCP which does\n * provide a reliable and sequential transport.  In both cases, using UDP\n * or TCP as an external transport, the internal Reliability Layer is\n * active.\n */\n"
  },
  {
    "path": "doc/doxygen/doc_tunnel_state.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * VPN tunnel state documentation file.\n */\n\n/**\n * @page tunnel_state Structure of the VPN tunnel state storage\n *\n * This section describes how OpenVPN stores its VPN tunnel state during\n * operation.\n *\n * OpenVPN uses several data structures as storage containers for state\n * information of active VPN tunnels.  These are described in this\n * section, together with a little bit of history to help understand the\n * origin of the current architecture.\n *\n * Whether an OpenVPN process is running in client-mode or server-mode\n * determines whether it can support only one or multiple simultaneously\n * active VPN tunnels.  This consequently also determines how the\n * associated state information is wrapped up internally.  This section\n * gives an overview of the differences.\n *\n * @section tunnel_state_history Historic developments\n *\n * In the old v1.x series, an OpenVPN process managed only one single VPN\n * tunnel.  This allowed the VPN tunnel state to be stored together with\n * process-global information in one single \\c context structure.\n *\n * This changed, however, in the v2.x series, as new OpenVPN versions\n * running in server-mode can support multiple simultaneously active VPN\n * tunnels.  This necessitated a redesign of the VPN tunnel state\n * container structures, and modification of the \\link\n * external_multiplexer External Multiplexer\\endlink and \\link\n * internal_multiplexer Internal Multiplexer\\endlink systems.  The\n * majority of these changes are only relevant for OpenVPN processes\n * running in server-mode, and the client-mode structure has remained very\n * similar to the v1.x single-tunnel form.\n *\n * @section tunnel_state_client Client-mode state\n *\n * An OpenVPN process running in client-mode can manage at most one single\n * VPN tunnel at any one time.  The state information for a client's VPN\n * tunnel is stored in a \\c context structure.\n *\n * The \\c context structure is created in the \\c main() function.  That is\n * also where process-wide initialization takes place, such as parsing\n * command line %options and reading configuration files.  The \\c context\n * is then passed to \\c tunnel_point_to_point() which drives OpenVPN's\n * main event processing loop.  These functions are both part of the \\link\n * eventloop Main Event Loop\\endlink module.\n *\n * @subsection tunnel_state_client_init Initialization and cleanup\n *\n * Because there is only one \\c context structure present, it can be\n * initialized and cleaned up from the client's main event processing\n * function.  Before the \\c tunnel_point_to_point() function enters its\n * event loop, it calls \\c init_instance_handle_signals() which calls \\c\n * init_instance() to initialize the single \\c context structure.  After\n * the event loop stops, it calls \\c close_instance() to clean up the \\c\n * context.\n *\n * @subsection tunnel_state_client_event Event processing\n *\n * When the main event processing loop activates the external or internal\n * multiplexer to handle a network event, it is not necessary to determine\n * which VPN tunnel the event is associated with, because there is only\n * one VPN tunnel active.\n *\n * @section tunnel_state_server Server-mode state\n *\n * An OpenVPN process running in server-mode can manage multiple\n * simultaneously active VPN tunnels.  For every VPN tunnel active, in\n * other words for every OpenVPN client which is connected to a server,\n * the OpenVPN server has one \\c context structure in which it stores that\n * particular VPN tunnel's state information.\n *\n * @subsection tunnel_state_server_multi Multi_context and multi_instance structures\n *\n * To support multiple \\c context structures, each is wrapped in a \\c\n * multi_instance structure, and all the \\c multi_instance structures are\n * registered in one single \\c multi_context structure.  The \\link\n * external_multiplexer External Multiplexer\\endlink and \\link\n * internal_multiplexer Internal Multiplexer\\endlink then use the \\c\n * multi_context to retrieve the correct \\c multi_instance and \\c context\n * associated with a given network address.\n *\n * @subsection tunnel_state_server_init Startup and initialization\n *\n * An OpenVPN process running in server-mode starts in the same \\c main()\n * function as it would in client-mode.  The same process-wide\n * initialization is performed, and the resulting state and configuration\n * is stored in a \\c context structure. The server-mode and client-mode\n * processes diverge when the \\c main() function calls one of \\c\n * tunnel_point_to_point() or \\c tunnel_server().\n *\n * In server-mode, \\c main() calls the \\c tunnel_server() function, which\n * transfers control to \\c tunnel_server_udp() or \\c\n * tunnel_server_tcp() depending on the external transport protocol.\n *\n * These functions receive the \\c context created in \\c main().  This\n * object has a special status in server-mode, as it does not represent an\n * active VPN tunnel, but does contain process-wide configuration\n * parameters.  In the source code, it is often stored in \"top\" variables.\n * To distinguish this object from other instances of the same type, its\n * \\c context.mode value is set to \\c CM_TOP.  Other \\c context objects,\n * which do represent active VPN tunnels, have a \\c context.mode set to \\c\n * CM_CHILD_UDP or \\c CM_CHILD_TCP, depending on the external transport\n * protocol.\n *\n * Both \\c tunnel_server_udp_single_threaded() and \\c tunnel_server_tcp()\n * perform similar initialization.  In either case, a \\c multi_context\n * structure is created, and it is initialized according to the\n * configuration stored in the top \\c context by the \\c multi_init() and\n * \\c multi_top_init() functions.\n *\n * @subsection tunnel_state_server_tunnels Creating and destroying VPN tunnels\n *\n * When an OpenVPN client makes a new connection to a server, the server\n * creates a new \\c context and \\c multi_instance.  The latter is\n * registered in the \\c multi_context, which makes it possible for the\n * external and internal multiplexers to retrieve the correct \\c\n * multi_instance and \\c context when a network event occurs.\n *\n * @subsection tunnel_state_server_cleanup Final cleanup\n *\n * After the main event loop exits, both \\c\n * tunnel_server_udp_single_threaded() and \\c tunnel_server_tcp() perform\n * similar cleanup.  They call \\c multi_uninit() followed by \\c\n * multi_top_free() to clean up the \\c multi_context structure.\n */\n"
  },
  {
    "path": "doc/doxygen/openvpn.doxyfile.in",
    "content": "# Doxyfile 1.9.1\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the configuration\n# file that follow. The default is UTF-8 which is also the encoding used for all\n# text before the first occurrence of this tag. Doxygen uses libiconv (or the\n# iconv built into libc) for the transcoding. See\n# https://www.gnu.org/software/libiconv/ for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = OpenVPN\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         =\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          =\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           =\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = @abs_top_builddir@/doc/doxygen\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-\n# directories (in 2 levels) under the output directory of each output format and\n# will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = NO\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,\n# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),\n# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,\n# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),\n# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,\n# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,\n# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,\n# Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all generated output in the proper direction.\n# Possible values are: None, LTR, RTL and Context.\n# The default value is: None.\n\nOUTPUT_TEXT_DIRECTION  = None\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       = \"The $name class\" \\\n                         \"The $name widget\" \\\n                         \"The $name file\" \\\n                         is \\\n                         provides \\\n                         specifies \\\n                         contains \\\n                         represents \\\n                         a \\\n                         an \\\n                         the\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        = @abs_top_srcdir@\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = YES\n\n# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line\n# such as\n# /***************\n# as being the beginning of a Javadoc-style comment \"banner\". If set to NO, the\n# Javadoc-style will behave just like regular comments and it will not be\n# interpreted by doxygen.\n# The default value is: NO.\n\nJAVADOC_BANNER         = NO\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# By default Python docstrings are displayed as preformatted text and doxygen's\n# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the\n# doxygen's special commands can be used and the contents of the docstring\n# documentation blocks is shown as doxygen documentation.\n# The default value is: YES.\n\nPYTHON_DOCSTRING       = YES\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 8\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:\\n\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". You can put \\n's in the value part of an alias to insert\n# newlines (in the resulting output). You can put ^^ in the value part of an\n# alias to insert a newline as if a physical newline was in the original file.\n# When you need a literal { or } or , in the value part of an alias you have to\n# escape them by means of a backslash (\\), this can lead to conflicts with the\n# commands \\{ and \\} for these it is advised to use the version @{ and @} or use\n# a double escape (\\\\{ and \\\\})\n\nALIASES                =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = YES\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice\n# sources only. Doxygen will then generate output that is more tailored for that\n# language. For instance, namespaces will be presented as modules, types will be\n# separated into more groups, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_SLICE  = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,\n# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,\n# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:\n# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser\n# tries to guess whether the code is fixed or free formatted code, this is the\n# default for Fortran type files). For instance to make doxygen treat .inc files\n# as Fortran files (default is PHP), and .f files as C (default is Fortran),\n# use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen. When specifying no_extension you should add\n# * to the FILE_PATTERNS.\n#\n# Note see also the list of default file extension mappings.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See https://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up\n# to that level are automatically included in the table of contents, even if\n# they do not have an id attribute.\n# Note: This feature currently applies only to Markdown headings.\n# Minimum value: 0, maximum value: 99, default value: 5.\n# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.\n\nTOC_INCLUDE_HEADINGS   = 5\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = NO\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# If one adds a struct or class to a group and this option is enabled, then also\n# any nested class or struct is added to the same group. By default this option\n# is disabled and one has to add nested compounds explicitly via \\ingroup.\n# The default value is: NO.\n\nGROUP_NESTED_COMPOUNDS = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use\n# during processing. When set to 0 doxygen will based this on the number of\n# cores available in the system. You can set it explicitly to a value larger\n# than 0 to get more control over the balance between CPU load and processing\n# speed. At this moment only the input processing can be done using multiple\n# threads. Since this is still an experimental feature the default is set to 1,\n# which efficively disables parallel processing. Please report any issues you\n# encounter. Generating dot graphs in parallel is controlled by the\n# DOT_NUM_THREADS setting.\n# Minimum value: 0, maximum value: 32, default value: 1.\n\nNUM_PROC_THREADS       = 1\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = YES\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = YES\n\n# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual\n# methods of a class will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIV_VIRTUAL   = NO\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = YES\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = YES\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = YES\n\n# If this flag is set to YES, the name of an unnamed parameter in a declaration\n# will be determined by the corresponding definition. By default unnamed\n# parameters remain unnamed in the output.\n# The default value is: YES.\n\nRESOLVE_UNNAMED_PARAMS = YES\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# declarations. If set to NO, these declarations will be included in the\n# documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# With the correct setting of option CASE_SENSE_NAMES doxygen will better be\n# able to match the capabilities of the underlying filesystem. In case the\n# filesystem is case sensitive (i.e. it supports files in the same directory\n# whose names only differ in casing), the option must be set to YES to properly\n# deal with such files in case they appear in the input. For filesystems that\n# are not case sensitive the option should be be set to NO to properly deal with\n# output files written for symbols that only differ in casing, such as for two\n# classes, one named CLASS and the other named Class, and to also support\n# references to files without having to specify the exact matching casing. On\n# Windows (including Cygwin) and MacOS, users should typically set this option\n# to NO, whereas on Linux or other Unix flavors it should typically be set to\n# YES.\n# The default value is: system dependent.\n\nCASE_SENSE_NAMES       = NO\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            =\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = NO\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as not documenting some parameters\n# in a documented function, or documenting parameters that don't exist or using\n# markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = YES\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong or incomplete\n# parameter documentation, but not about the absence of documentation. If\n# EXTRACT_ALL is set to YES then this flag will automatically be disabled.\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when\n# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS\n# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but\n# at the end of the doxygen process doxygen will return with a non-zero status.\n# Possible values are: NO, YES and FAIL_ON_WARNINGS.\n# The default value is: NO.\n\nWARN_AS_ERROR          = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr).\n\nWARN_LOGFILE           = @abs_top_builddir@/doc/doxygen.warnings.log\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  = @abs_top_srcdir@\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see:\n# https://www.gnu.org/software/libiconv/) for the list of possible encodings.\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# read by doxygen.\n#\n# Note the list of default checked file patterns might differ from the list of\n# default file extension mappings.\n#\n# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,\n# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,\n# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,\n# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),\n# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl,\n# *.ucf, *.qsf and *.ice.\n\nFILE_PATTERNS          = *.c \\\n                         *.h\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                = @abs_top_srcdir@/out/\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       =\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# AClass::ANamespace, ANamespace::*Test\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories use the pattern */test/*\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           =\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       = *\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             =\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE =\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = YES\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# entity all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = YES\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = YES\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see https://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = YES\n\n# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the\n# clang parser (see:\n# http://clang.llvm.org/) for more accurate parsing at the cost of reduced\n# performance. This can be particularly helpful with template rich C++ code for\n# which doxygen's built-in parser lacks the necessary type information.\n# Note: The availability of this option depends on whether or not doxygen was\n# generated with the -Duse_libclang=ON option for CMake.\n# The default value is: NO.\n\nCLANG_ASSISTED_PARSING = NO\n\n# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to\n# YES then doxygen will add the directory of each input to the include path.\n# The default value is: YES.\n\nCLANG_ADD_INC_PATHS    = YES\n\n# If clang assisted parsing is enabled you can provide the compiler with command\n# line options that you would normally use when invoking the compiler. Note that\n# the include paths will already be set by doxygen for the files and directories\n# specified with INPUT and INCLUDE_PATH.\n# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.\n\nCLANG_OPTIONS          =\n\n# If clang assisted parsing is enabled you can provide the clang parser with the\n# path to the directory containing a file called compile_commands.json. This\n# file is the compilation database (see:\n# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the\n# options used when the source files were built. This is equivalent to\n# specifying the -p option to a clang tool, such as clang-check. These options\n# will then be passed to the parser. Any options specified with CLANG_OPTIONS\n# will be added as well.\n# Note: The availability of this option depends on whether or not doxygen was\n# generated with the -Duse_libclang=ON option for CMake.\n\nCLANG_DATABASE_PATH    =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = NO\n\n# In case all classes in a project start with a common prefix, all classes will\n# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag\n# can be used to specify a prefix (or a list of prefixes) that should be ignored\n# while generating the index headers.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            =\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            =\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        =\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list). For an example see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  =\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a colorwheel, see\n# https://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use grayscales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML\n# page will contain the date and time when the page was generated. Setting this\n# to YES can help to show when doxygen was last run and thus if the\n# documentation is up to date.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_TIMESTAMP         = NO\n\n# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML\n# documentation will contain a main index with vertical navigation menus that\n# are dynamically created via JavaScript. If disabled, the navigation index will\n# consists of multiple levels of tabs that are statically embedded in every HTML\n# page. Disable this option to support browsers that do not have JavaScript,\n# like the Qt help browser.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_MENUS     = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see:\n# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To\n# create a documentation set, doxygen will generate a Makefile in the HTML\n# output directory. Running make will produce the docset in that directory and\n# running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy\n# genXcode/_index.html for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# (see:\n# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows.\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the main .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location (absolute path\n# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to\n# run qhelpgenerator on the generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine-tune the look of the index. As an example, the default style\n# sheet generated by doxygen has an example that shows how to put an image at\n# the root of the tree instead of the PROJECT_NAME. Since the tree basically has\n# the same information as the tab index, you could consider setting\n# DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = NO\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg\n# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see\n# https://inkscape.org) to generate formulas as SVG images instead of PNGs for\n# the HTML output. These images will generally look nicer at scaled resolutions.\n# Possible values are: png (the default) and svg (looks nicer but requires the\n# pdf2svg or inkscape tool).\n# The default value is: png.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FORMULA_FORMAT    = png\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# Use the FORMULA_TRANSPARENT tag to determine whether or not the images\n# generated for formulas are transparent PNGs. Transparent PNGs are not\n# supported properly for IE 6.0, but are supported on all modern browsers.\n#\n# Note that when changing this option you need to delete any form_*.png files in\n# the HTML output directory before the changes have effect.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_TRANSPARENT    = YES\n\n# The FORMULA_MACROFILE can contain LaTeX \\newcommand and \\renewcommand commands\n# to create new LaTeX commands to be used in formulas as building blocks. See\n# the section \"Including formulas\" for details.\n\nFORMULA_MACROFILE      =\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# https://www.mathjax.org) which uses client side JavaScript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. See the MathJax site (see:\n# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details.\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility), NativeMML (i.e. MathML) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from https://www.mathjax.org before deployment.\n# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = https://cdn.jsdelivr.net/npm/mathjax@2\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see:\n# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = NO\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using JavaScript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see:\n# https://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = NO\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see:\n# https://xapian.org/). See the section \"External Indexing and Searching\" for\n# details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = YES\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when not enabling USE_PDFLATEX the default is latex when enabling\n# USE_PDFLATEX the default is pdflatex and when in the later case latex is\n# chosen this is overwritten by pdflatex. For specific output languages the\n# default can have been set differently, this depends on the implementation of\n# the output language.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         = latex\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# Note: This tag is used in the Makefile / make.bat.\n# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file\n# (.tex).\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to\n# generate index for LaTeX. In case there is no backslash (\\) as first character\n# it will be automatically added in the LaTeX code.\n# Note: This tag is used in the generated output file (.tex).\n# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.\n# The default value is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_MAKEINDEX_CMD    = makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = YES\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. The package can be specified just\n# by its name or with the correct syntax as to be used with the LaTeX\n# \\usepackage command. To get the times font for instance you can specify :\n# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}\n# To use the option intlimits with the amsmath package you can specify:\n# EXTRA_PACKAGES=[intlimits]{amsmath}\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the\n# generated LaTeX document. The header should contain everything until the first\n# chapter. If it is left blank doxygen will generate a standard header. See\n# section \"Doxygen usage\" for information on how to let doxygen write the\n# default header to a separate file.\n#\n# Note: Only use a user-defined header if you know what you are doing! The\n# following commands have a special meaning inside the header: $title,\n# $datetime, $date, $doxygenversion, $projectname, $projectnumber,\n# $projectbrief, $projectlogo. Doxygen will replace $title with the empty\n# string, for the replacement values of the other commands the user is referred\n# to HTML_HEADER.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the\n# generated LaTeX document. The footer should contain everything after the last\n# chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer.\n#\n# Note: Only use a user-defined footer if you know what you are doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = YES\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as\n# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX\n# files. Set this option to YES, to get a higher quality PDF documentation.\n#\n# See also section LATEX_CMD_NAME for selecting the engine.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = YES\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode\n# command to the generated LaTeX files. This will instruct LaTeX to keep running\n# if errors occur, instead of asking the user for help. This option is also used\n# when generating formulas in HTML.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source\n# code with syntax highlighting in the LaTeX output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_SOURCE_CODE      = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# https://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated\n# page will contain the date and time when the page was generated. Setting this\n# to NO can help when comparing the output of multiple runs.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_TIMESTAMP        = NO\n\n# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)\n# path from which the emoji images will be read. If a relative path is entered,\n# it will be relative to the LATEX_OUTPUT directory. If left blank the\n# LATEX_OUTPUT directory will be used.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EMOJI_DIRECTORY  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's\n# configuration file, i.e. a series of assignments. You only have to provide\n# replacements, missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's configuration file. A template extensions file can be\n# generated using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code\n# with syntax highlighting in the RTF output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_SOURCE_CODE        = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = YES\n\n# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include\n# namespace members in file scope as well, matching the HTML output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_NS_MEMB_FILE_SCOPE = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the\n# program listings (including syntax highlighting and cross-referencing\n# information) to the DOCBOOK output. Note that enabling this will significantly\n# increase the size of the DOCBOOK output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_PROGRAMLISTING = NO\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures\n# the structure of the code including all documentation. Note that this feature\n# is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = NO\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = NO\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           =\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             = _WIN32 \\\n                         USE_LZO \\\n                         ENABLE_FRAGMENT \\\n                         P2MP \\\n                         ENABLE_CRYPTO_OPENSSL \\\n                         ENABLE_PLUGIN \\\n                         ENABLE_MANAGEMENT \\\n                         ENABLE_OCC \\\n                         HAVE_GETTIMEOFDAY\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external class will be listed in\n# the class index. If set to NO, only the inherited external classes will be\n# listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the modules index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram\n# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to\n# NO turns the diagrams off. Note that this option also works with HAVE_DOT\n# disabled, but it is recommended to install and use dot, since it yields more\n# powerful graphs.\n# The default value is: YES.\n\nCLASS_DIAGRAMS         = NO\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: YES.\n\nHAVE_DOT               = YES\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# When you want a differently looking font in the dot files that doxygen\n# generates you can specify the font name using DOT_FONTNAME. You need to make\n# sure dot is able to find the font, which can be done by putting it in a\n# standard location or by setting the DOTFONTPATH environment variable or by\n# setting DOT_FONTPATH to the directory containing the font.\n# The default value is: Helvetica.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTNAME           = Helvetica\n\n# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of\n# dot graphs.\n# Minimum value: 4, maximum value: 24, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTSIZE           = 10\n\n# By default doxygen will tell dot to use the default font as specified with\n# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set\n# the path where dot can find it using this tag.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for\n# each documented class showing the direct and indirect inheritance relations.\n# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag UML_LOOK is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and\n# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS\n# tag is set to YES, doxygen will add type and arguments for attributes and\n# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen\n# will not generate fields with class member information in the UML graphs. The\n# class diagrams will look similar to the default class diagrams but using UML\n# notation for the relationships.\n# Possible values are: NO, YES and NONE.\n# The default value is: NO.\n# This tag requires that the tag UML_LOOK is set to YES.\n\nDOT_UML_DETAILS        = NO\n\n# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters\n# to display on a single line. If the actual line length exceeds this threshold\n# significantly it will wrapped across multiple lines. Some heuristics are apply\n# to avoid ugly line breaks.\n# Minimum value: 0, maximum value: 1000, default value: 17.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_WRAP_THRESHOLD     = 17\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command. Disabling a call graph can be\n# accomplished by means of the command \\hidecallgraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command. Disabling a caller graph can be\n# accomplished by means of the command \\hidecallergraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot. For an explanation of the image formats see the section\n# output formats in the documentation of the dot tool (Graphviz (see:\n# http://www.graphviz.org/)).\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,\n# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,\n# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,\n# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and\n# png:gdiplus:gdiplus.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               = /usr/bin/dot\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file. If left blank, it is assumed\n# PlantUML is not used or called during a preprocessing step. Doxygen will\n# generate a warning when it encounters a \\startuml command in this case and\n# will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a\n# configuration file for plantuml.\n\nPLANTUML_CFG_FILE      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 150\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 1000\n\n# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent\n# background. This is disabled by default, because dot on Windows does not seem\n# to support this out of the box.\n#\n# Warning: Depending on the platform used, enabling this option may lead to\n# badly anti-aliased labels on the edges of a graph (i.e. they become hard to\n# read).\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_TRANSPARENT        = YES\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = NO\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate\n# files that are used to generate the various graphs.\n#\n# Note: This setting is not only used for dot files but also for msc and\n# plantuml temporary files.\n# The default value is: YES.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "doc/gui-notes.txt",
    "content": "Management Interface \"echo\" protocol\n\n================================================================================\nTHIS IS A PRELIMINARY VERSION OF THIS DOCUMENT. ALL INFORMATION IN IT\nIS SUBJECT TO CHANGE.\n================================================================================\n\n\n    CONTENTS\n        THE OPENVPN --ECHO OPTION\n        ENVIRONMENT COMMAND\n        MESSSAGE COMMANDS\n        PASSWORD COMMANDS\n        QUOTING\n        COMMMAND DETAILS\n\n\n=========================\nTHE OPENVPN --ECHO OPTION\n=========================\n\nThe OpenVPN --echo option causes commands to be sent out through the\nmanagement interface, typically to a Graphic User Interface (GUI) such\nas \"OpenVPN for Android\", \"Tunnelblick\" (for macOS), or \"Windows\nOpenVPN GUI\". It can be included in a configuration file or on a\ncommand line, or can be pushed from the server.\n\nThis document describes the commands that can be sent and how they are\ninterpreted by various GUIs.\n\n * OpenVPN does not process the commands in an --echo option; it only\nsends them out through the management interface.\n\n * \"echo\" commands are processed by the GUI if, as, when, and in the\norder they are received. If no GUI is present the processing of\ncommands may be delayed, the commands may never be processed, or only\nsome commands may be processed. (That can happen if OpenVPN discards\ncommands because its buffer for the commands fills up.)\n\n * There is no mechanism for the GUI to acknowledge the receipt,\nsuccess, or failure of a command.\n\n * \"echo\" commands are stored by OpenVPN (within limits, see the next\npoint) and sent only when the GUI requests them through the management\ninterface. \"echo\" commands in the configuration file or the command\nline are typically requested and processed at the start of a\nconnection attempt. \"echo\" commands that are pushed by the server are\nalso typically asked for at the start of a connection attempt but can\nbe sent at any time. They are processed in the middle of a connection\nattempt or after a connection is established, as the \"push\" options\nare received by the client from the server.\n\n  * OpenVPN's storage for echo commands is limited in size, so a large\nnumber of commands or commands with long messages may require that\nsome commands be removed from the storage. If that happens, some of\nthe commands may not be sent through the management interface when a\nGUI does connect to it or asks for the \"echo\" commands.\n\n * On SIGUSR1 and SIGHUP connection restarts, \"echo\" commands that\nwere sent through the management interface and have been saved by\nOpenVPN are sent again and will be re-processed by the GUI. (The\nmessage commands include a mechanism for muting (skipping) duplicate\nmessages, see MESSAGE COMMANDS, below.)\n\n * OpenVPN limits the number of separate arguments in each line of a\nconfiguration file. Arguments may be quoted to work around this\nlimitation, see QUOTING, below.\n\n * OpenVPN limits the size of each \"echo\" command sent over the\nmanagement interface to 255 bytes, including overhead characters. To\nallow messages of arbitrary length, several message commands can be\nconcatenated together before being displayed to the user, see MESSAGE\nCOMMANDS, below.\n\n * There no indication to the GUI of the source of the command\n(configuration file, command line option, or pushed from a server). It\nmight be possible for the GUI to deduce that a command was pushed from\na server because of timing or other management interface interactions.\n\n\n===================\nENVIRONMENT COMMAND\n===================\n\nTypically, a GUI allows users to specify shell commands (typically\nscripts) to run at certain points in the connection/disconnection\nprocess, in addition to those provided by OpenVPN options such as\n\"--up\" and \"--down\".\n\nThe \"setenv\" command can be used to set environment variables that are\navailable to the scripts run by the GUI. Each \"setenv\" command\nspecifies a value for one environment variable that is available to\nthe scripts that the GUI runs.\n\nThis is similar to Openvpn's \"--setenv\" option, which specifies an\nadditional environment variable that is included in the environment\nvariables that are available to the scripts that OpenVPN runs.\n\n\n=================\nMESSSAGE COMMANDS\n=================\n\nFour commands can be used to display a message to the user from the\nOpenVPN configuration or server:\n\n    msg\n    msg-n\n    msg-window\n    msg-notify\n\n\"msg\" and \"msg-n\" commands are concatenated to construct a message.\nWhen a \"msg-window\"or \"msg-notify\" command is received the message is\ndisplayed to the user.\n\nIdentical messages (same title, text, and destination) received during\none connection may be ignored or muted. Some GUIs may only show the\nfirst message for a connection, or the first message shown in a window\nand the first message shown as a notification.\n\n\n=================\nPASSWORD COMMANDS\n=================\n\nThree commands can be used to control the GUI's storage of usernames,\npasswords, and private keys:\n\n    disable-save-passwords\n    forget-passwords\n    save-passwords\n\n\n=======\nQUOTING\n=======\n\n * In a configuration file, the rest of the line is parsed into\nseparate arguments  and then 'echo' and the arguments are passed, each\nseparated by a single space, through the management interface. For\nexample:\n\n    echo     argument1 argument2\n    echo    \"     argument1      argument2\"\n\nwill be sent through the management interface as\n\n    >ECHO:timestamp,argument1 argument2\n    >ECHO:timestamp,     argument1      argument2\n\n * In a command line option, the single argument following \"--echo\" is\nparsed similarly, so\n\n    --echo   argument1     argument2\n    --echo   \"    argument1     argument2\"\n\nwill be sent through the management interface as\n\n    >ECHO:timestamp,argument1 argument2\n    >ECHO:timestamp,     argument1      argument2\n\n * In a \"push\" option in a server configuration file, the single\noption following \"push\" is parsed similarly, so\n\n    push \"echo argument1 argument2 argument3   argument4\"\n    push \"echo '    argument1 argument2 argument3   argument4'\"\n\nwill be sent through the management interface as\n\n    >ECHO:timestamp,argument1 argument2 argument3 argument4\n    >ECHO:timestamp,     argument1 argument2 argument3   argument4\n\n\n================\nCOMMMAND DETAILS\n================\n\n\nCOMMAND -- disable-save-passwords\n---------------------------------\n\nSyntax: disable-save-passwords\n\nThe GUI is instructed to not allow the user to save passwords or\nprivate keys for the configuration. The user is still allowed to save\nusernames. Any passwords or private keys that have been saved will be\nforgotten.\n\nThis command will be effective at startup only if present in the\nconfiguration file or as a command line option. If pushed from the\nserver, saving passwords will be disabled in password prompts only\nafter the initial prompt has been shown to the user.\n\n    Android: ??????\n\n    Tunnelblick: Planned. This command will disable saving of\npasswords or private keys and forget any saved usernames, passwords,\nor private keys regardless of the normal (non-forced) global or\nper-configuration settings. A computer administrator can \"force\" this\nsetting, overriding this command.\n\n    Windows OpenVPN GUI: Planned. This command will disable saving of\npasswords or private keys and forget any saved usernames, passwords,\nor private keys regardless of any global settings.\n\n\nCOMMAND -- forget-passwords\n---------------------------\n\nSyntax: forget-passwords\n\nThe GUI is instructed to forget any usernames, passwords, and private\nkeys it has saved for the configuration. Useful when pushed from the\nserver so that it is processed after authentication.\n\n    Android: ??????\n\n    Tunnelblick: Planned.\n\n    Windows OpenVPN GUI: supported since release 2.4.1 (GUI version 11.5.0)\n\n\nCOMMAND -- msg\n--------------\n\nSyntax: msg text\n\nThe text is appended to any previous text from \"msg\" or \"msg-n\"\ncommands, and a newline is appended after that.\n\nA trailing newline will be removed from the completed message before\nit is displayed to the user.\n\nThe text may include any UTF-8 character except a comma (\",\"), CR\n(0x0D), LF (0x0A), or NUL (0x00).\n\nThe text may not contain percent (\"%\") except in \"percent encoding\"\nsequences. To display a percent sign, use %25.\n\nThe text may not contain commas (\",\") because of constraints imposed\nby OpenVPN. Commas should be encoded using \"percent encoding\" (URL\nencoding): a '%' character followed by two hexadecimal digits, the\nhigh- and then low-nibble of the ASCII code for the character to be\nshown. Examples: a comma is encoded as %2C or %2c; a percent sign is\nencoded as %25.\n\nText containing comment characters # and ; must be enclosed in quotes to\nsurvive after option parsing by openvpn.\n\nThe insertion of line endings (CR, LF) in the text is discouraged\nbecause it is OS dependent. Instead, use the \"msg\" command, which\nappends a line ending appropriate for the OS on which the GUI is\nrunning.\n\n    Android: Planned.\n\n    Tunnelblick: Planned.\n\n    Windows OpenVPN GUI: supported since release v2.4.11 / v2.5.1\n\t\t\t (GUI version v11.22.0)\n\nCOMMAND -- msg-n\n----------------\n\nSyntax: msg-n text\n\nThe text is appended to any previous text from \"msg\"\" or \"msg-n\"\"\ncommands. (Like \"msg\" except that no newline is appended.)\n\nSee \"COMMAND -- msg\" for details about \"text\".\n\n    Android: Planned.\n\n    Tunnelblick: Planned.\n\n    Windows OpenVPN GUI: supported since release v2.4.11 / v2.5.1\n\t\t\t (GUI version v11.22.0)\n\nCOMMAND -- msg-notify\n---------------------\n\nSyntax: msg-notify title\n\nThe text from previous \"msg\" and/or \"msg-n\" commands is displayed to\nthe user as a notification with title \"title\" and the previous text is\nforgotten.\n\n    Android: Planned.\n\n    Tunnelblick: Planned.\n\n    Windows OpenVPN GUI: supported since release v2.4.11 / v2.5.1\n\t\t\t (GUI version v11.22.0)\n\nNote: The max length that will correctly display as a notification\nmessage is OS dependent.\n\n\nCOMMAND -- msg-window title\n---------------------------\n\nSyntax: msg-window title\n\nThe text from previous \"msg\" and/or \"msg-n\" commands is displayed to\nthe user in a non-modal popup window with title \"title\" and the\nprevious text is forgotten.  How the title is displayed exactly is left\nto the implementation. Could be set as the window title or as a\ndifferently formatted text as the heading of the message, for example.\n\n    Android: Planned.\n\n    Tunnelblick: Planned.\n\n    Windows OpenVPN GUI: supported since release v2.4.11 / v2.5.1\n\t\t\t (GUI version v11.22.0)\n\n\nCOMMAND -- save-passwords\n-------------------------\n\nSyntax: save-passwords\n\nThe GUI is instructed to allow the user to save usernames, passwords\nand private keys for the configuration.\n\nThis command will be effective at startup only if present in the\nconfiguration file or as a command line option. If pushed from the\nserver, saving passwords will be allowed in password prompts only\nafter the initial prompt has been shown to the user.\n\nThis command typically has the effect of presenting the password\ndialogs to the user with a \"save password\" checkbox checked. The user\nmay still uncheck it during the dialog.\n\n    Android: ??????\n\n    Tunnelblick: Planned. Tunnelblick ignores this command. Usernames,\npasswords, and private keys may be saved by default, and this command\nwill not override the separate Tunnelblick global or per-configuration\nsettings used to disable saving them.\n\n    Windows OpenVPN GUI: Supported since release 2.4.1 (GUI version 11.5.0)\n\n\nCOMMAND -- setenv\n-----------------\n\nSyntax: setenv name value\n\nSets an environment variable that will be available to the scripts run\nby the GUI.\n\nThis will set environment variable \"OPENVPN_name\" to value \"value\" for\nthe scripts run by the GUI. \"name\" is changed to \"OPENVPN_name\" to\nprevent overwriting sensitive variables such as PATH. Variables are\nset in the order received, with later values replacing earlier ones\nfor the same \"name\".\n\nNames may include only alphanumeric characters and underscores. A\n\"setenv\" command with an invalid name will be ignored.\n\n    Android: ??????\n\n    Tunnelblick: Planned.\n\n    Windows OpenVPN GUI: supported since release v2.4.7 (GUI version v11.12.0)\nThe variables set by \"setenv\" are merged with those for the process\nenvironment.  In case of duplicate names the one in the setenv list is\nchosen.\n"
  },
  {
    "path": "doc/interactive-service-notes.rst",
    "content": "OpenVPN Interactive Service Notes\n=================================\n\n\nIntroduction\n------------\n\nOpenVPN Interactive Service, also known as \"iservice\" or\n\"OpenVPNServiceInteractive\", is a Windows system service which allows\nunprivileged openvpn.exe process to do certain privileged operations, such as\nadding routes. This removes the need to always run OpenVPN as administrator,\nwhich was the case for a long time, and continues to be the case for OpenVPN\n2.3.x.\n\nThe 2.4.x release and git \"master\" versions of OpenVPN contain the Interactive\nService code and OpenVPN-GUI is setup to use it by default. Starting from\nversion 2.4.0, OpenVPN-GUI is expected to be started as user (do not right-click\nand \"run as administrator\" or do not set the shortcut to run as administrator).\nThis ensures that OpenVPN and the GUI run with limited privileges.\n\n\nHow It Works\n------------\n\nHere is a brief explanation of how the Interactive Service works, based on\n`Gert's email`_ to openvpn-devel mailing list. The example user, *joe*, is not\nan administrator, and does not have any other extra privileges.\n\n- OpenVPN-GUI runs as user *joe*.\n\n- Interactive Service runs as a local Windows service with maximum privileges.\n\n- OpenVPN-GUI connects to the Interactive Service and asks it to \"run\n  openvpn.exe with the given command line options\".\n\n- Interactive Service starts openvpn.exe process as user *joe*, and keeps a\n  service pipe between Interactive Service and openvpn.exe.\n\n- When openvpn.exe wants to perform any operation that require elevation (e.g.\n  ipconfig, route, configure DNS), it sends a request over the service pipe to\n  the Interactive Service, which will then execute it (and clean up should\n  openvpn.exe crash).\n\n- ``--up`` scripts are run by openvpn.exe itself, which is running as user\n  *joe*, all privileges are nicely in place.\n\n- Scripts run by the GUI will run as user *joe*, so that automated tasks like\n  mapping of drives work as expected.\n\nThis avoids the use of scripts for privilege escalation (as was possible by\nrunning an ``--up`` script from openvpn.exe which is run as administrator).\n\n\nClient-Service Communication\n----------------------------\n\nConnecting\n~~~~~~~~~~\n\nThe client (OpenVPN GUI) and the Interactive Service communicate using a named\nmessage pipe. By default, the service provides the ``\\\\.\\pipe\\openvpn\\service``\nnamed pipe.\n\nThe client connects to the pipe for read/write and sets the pipe state to\n``PIPE_READMODE_MESSAGE``::\n\n   HANDLE pipe = CreateFile(_T(\"\\\\\\\\.\\\\pipe\\\\openvpn\\\\service\"),\n       GENERIC_READ | GENERIC_WRITE,\n       0,\n       NULL,\n       OPEN_EXISTING,\n       FILE_FLAG_OVERLAPPED,\n       NULL);\n\n   if (pipe == INVALID_HANDLE_VALUE)\n   {\n       // Error\n   }\n\n   DWORD dwMode = PIPE_READMODE_MESSAGE;\n   if (!SetNamedPipeHandleState(pipe, &dwMode, NULL, NULL)\n   {\n       // Error\n   }\n\n\nopenvpn.exe Startup\n~~~~~~~~~~~~~~~~~~~\n\nAfter the client is connected to the service, the client must send a startup\nmessage to have the service start the openvpn.exe process. The startup message\nis comprised of three UTF-16 strings delimited by U0000 zero characters::\n\n   startupmsg     = workingdir WZERO openvpnoptions WZERO stdin WZERO\n\n   workingdir     = WSTRING\n   openvpnoptions = WSTRING\n   stdin          = WSTRING\n\n   WSTRING        = *WCHAR\n   WCHAR          = %x0001-FFFF\n   WZERO          = %x0000\n\n``workingdir``\n   Represents the folder openvpn.exe process should be started in.\n\n``openvpnoptions``\n   String contains ``--config`` and other OpenVPN command line options, without\n   the ``argv[0]`` executable name (\"openvpn\" or \"openvpn.exe\"). When there is\n   only one option specified, the ``--config`` option is assumed and the option\n   is the configuration filename.\n\n   Note that the interactive service validates the options. OpenVPN\n   configuration file must reside in the configuration folder defined by\n   ``config_dir`` registry value. The configuration file can also reside in any\n   subfolder of the configuration folder. For all other folders the invoking\n   user must be a member of local Administrators group, or a member of the group\n   defined by ``ovpn_admin_group`` registry value (\"OpenVPN Administrators\" by\n   default).\n\n``stdin``\n   The content of the ``stdin`` string is sent to the openvpn.exe process to its\n   stdin stream after it starts.\n\n   When a ``--management ... stdin`` option is present, the openvpn.exe process\n   will prompt for the management interface password on start. In this case, the\n   ``stdin`` must contain the password appended with an LF (U000A) to simulate\n   the [Enter] key after the password is \"typed\" in.\n\n   The openvpn.exe's stdout is redirected to ``NUL``. Should the client require\n   openvpn.exe's stdout, one should specify ``--log`` option.\n\nThe message must be written in a single ``WriteFile()`` call.\n\nExample::\n\n   // Prepare the message.\n   size_t msg_len =\n       wcslen(workingdir) + 1 +\n       wcslen(options   ) + 1 +\n       wcslen(manage_pwd) + 1;\n   wchar_t *msg_data = (wchar_t*)malloc(msg_len*sizeof(wchar_t));\n   _snwprintf(msg_data, msg_len, L\"%s%c%s%c%s\",\n       workingdir, L'\\0',\n       options, L'\\0',\n       manage_pwd)\n\n   // Send the message.\n   DWORD dwBytesWritten;\n   if (!WriteFile(pipe,\n       msg_data,\n       msg_len*sizeof(wchar_t),\n       &dwBytesWritten,\n       NULL))\n   {\n       // Error\n   }\n\n   // Sanitize memory, since the stdin component of the message\n   // contains the management interface password.\n   SecureZeroMemory(msg_data, msg_len*sizeof(wchar_t));\n   free(msg_data);\n\n\nopenvpn.exe Process ID\n~~~~~~~~~~~~~~~~~~~~~~\n\nAfter receiving the startup message, the Interactive Service validates the user\nand specified options before launching the openvpn.exe process.\n\nThe Interactive Service replies with a process ID message. The process ID\nmessage is comprised of three UTF-16 strings delimited by LFs (U000A)::\n\n   pidmsg  = L\"0x00000000\" WLF L\"0x\" pid WLF L\"Process ID\"\n\n   pid     = 8*8WHEXDIG\n\n   WHEXDIG = WDIGIT / L\"A\" / L\"B\" / L\"C\" / L\"D\" / L\"E\" / L\"F\"\n   WDIGIT  = %x0030-0039\n   WLF     = %x000a\n\n``pid``\n   A UTF-16 eight-character hexadecimal process ID of the openvpn.exe process\n   the Interactive Service launched on client's behalf.\n\n\nopenvpn.exe Monitoring and Termination\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAfter the openvpn.exe process is launched, the client can disconnect the pipe to\nthe interactive service. However, it should monitor the openvpn.exe process\nitself. OpenVPN Management Interface is recommended for this.\n\nThe client may choose to stay connected to the pipe. When the openvpn.exe\nprocess terminates, the service disconnects the pipe. Should the openvpn.exe\nprocess terminate with an error, the service sends an error message to the\nclient before disconnecting the pipe.\n\nNote that Interactive Service terminates all child openvpn.exe processes when\nthe service is stopped or restarted. This allows a graceful elevation-required\nclean-up (e.g. restore ipconfig, route, DNS).\n\n\nError Messages\n~~~~~~~~~~~~~~\n\nIn case of an error, the Interactive Service sends an error message to the\nclient. Error messages are comprised of three UTF-16 strings delimited by LFs\n(U000A)::\n\n   errmsg = L\"0x\" errnum WLF func WLF msg\n\n   errnum = 8*8WHEXDIG\n   func   = WSTRING\n   msg    = WSTRING\n\n``errnum``\n   A UTF-16 eight-character hexadecimal error code. Typically, it is one of the\n   Win32 error codes returned by ``GetLastError()``.\n\n   However, it can be one of the Interactive Service specific error codes:\n\n   ===================== ==========\n   Error                 Code\n   ===================== ==========\n   ERROR_OPENVPN_STARTUP 0x20000000\n   ERROR_STARTUP_DATA    0x20000001\n   ERROR_MESSAGE_DATA    0x20000002\n   ERROR_MESSAGE_TYPE    0x20000003\n   ===================== ==========\n\n``func``\n   The name of the function call that failed or an error description.\n\n``msg``\n  The error description returned by a\n  ``FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, 0, errnum, ...)`` call.\n\n\nInteractive Service Configuration\n---------------------------------\n\nThe Interactive Service settings are read from the\n``HKEY_LOCAL_MACHINE\\SOFTWARE\\OpenVPN`` registry key by default.\n\nAll the following registry values are of the ``REG_SZ`` type:\n\n*Default*\n   Installation folder (required, hereinafter ``install_dir``)\n\n``exe_path``\n   The absolute path to the openvpn.exe binary; defaults to\n   ``install_dir \"\\bin\\openvpn.exe\"``.\n\n``config_dir``\n   The path to the configuration folder; defaults to ``install_dir \"\\config\"``.\n\n``priority``\n   openvpn.exe process priority; one of the following strings:\n\n   - ``\"IDLE_PRIORITY_CLASS\"``\n   - ``\"BELOW_NORMAL_PRIORITY_CLASS\"``\n   - ``\"NORMAL_PRIORITY_CLASS\"`` (default)\n   - ``\"ABOVE_NORMAL_PRIORITY_CLASS\"``\n   - ``\"HIGH_PRIORITY_CLASS\"``\n\n``ovpn_admin_group``\n   The name of the local group, whose members are authorized to use the\n   Interactive Service unrestricted; defaults to ``\"OpenVPN Administrators\"``\n\n\nMultiple Interactive Service Instances\n--------------------------------------\n\nOpenVPN 2.4.5 extended the Interactive Service to support multiple side-by-side\nrunning instances. This allows clients to use different Interactive Service\nversions with different settings and/or openvpn.exe binary version on the same\ncomputer.\n\nOpenVPN installs the default Interactive Service instance only. The default\ninstance is used by OpenVPN GUI client and also provides backward compatibility.\n\n\nInstalling a Non-default Interactive Service Instance\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n1. Choose a unique instance name. For example: \"$v2.5-test\". The instance name\n   is appended to the default registry path and service name. We choose to start\n   it with a dollar \"$\" sign analogous to Microsoft SQL Server instance naming\n   scheme. However, this is not imperative.\n\n   Appending the name to the registry path and service name also implies the\n   name cannot contain characters not allowed in Windows paths: \"<\", \">\", double\n   quote etc.\n\n2. Create an ``HKEY_LOCAL_MACHINE\\SOFTWARE\\OpenVPN$v2.5-test`` registry key and\n   configure the Interactive Service instance configuration appropriately.\n\n   This allows using slightly or completely different settings from the default\n   instance.\n\n   See the `Interactive Service Configuration`_ section for the list of registry\n   values.\n\n3. Create and start the instance's Windows service from an elevated command\n   prompt::\n\n      sc create \"OpenVPNServiceInteractive$v2.5-test\" \\\n         start= auto \\\n         binPath= \"<path to openvpnserv.exe> -instance interactive $v2.5-test\" \\\n         depend= tap0901/Dhcp \\\n         DisplayName= \"OpenVPN Interactive Service (v2.5-test)\"\n\n      sc start \"OpenVPNServiceInteractive$v2.5-test\"\n\n   This allows using the same or a different version of openvpnserv.exe than the\n   default instance.\n\n   Note the space after \"=\" character in ``sc`` command line options.\n\n4. Set your OpenVPN client to connect to the\n   ``\\\\.\\pipe\\openvpn$v2.5-test\\service``.\n\n   This allows the client to select a different installed Interactive Service\n   instance at run-time, thus allowing different OpenVPN settings and versions.\n\n   At the time writing, the OpenVPN GUI client supports connecting to the\n   default Interactive Service instance only.\n\n.. _`Gert's email`: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg00097.html\n"
  },
  {
    "path": "doc/keying-material-exporter.txt",
    "content": "OpenVPN                                             Daniel Kubec <niel@rtfm.cz>\nRFC-5705                                                          February 2015\n\n\n                Added support for TLS Keying Material Exporters\n\nKeying Material Exporter [RFC-5705] allow additional keying material to be\nderived from existing TLS channel. This exported keying material can then be\nused for a variety of purposes. TLS allows client and server to establish\nkeying material for use in the upper layers between the TLS end-points and\nchannel bindings is straightforward and well-defined mechanism how to\nauthenticate other layers.\n\n\nOpenVPN Configuration\n\n--keying-material-exporter label len\n\nExport Keying Material [RFC-5705] of len bytes (min. 16 bytes) using label in\nenvironment (exported_keying_material) for use by plugins in\nOPENVPN_PLUGIN_TLS_FINAL callback.\n\nNote that exporter labels have the potential to collide with existing PRF\nlabels. In order to prevent this, labels MUST begin with \"EXPORTER\".\n(This option requires OpenSSL 1.0.1 or newer.)\n\n\nUse Cases:\n\nSecure bindings of AAA information to application layer\n\n   OpenVPN Client                   <------>                 OpenVPN Server\n   [KeyAgreement]                                            [KeyAgreement]\n\n   [TLSExportedKeyingMaterial]                  [TLSExportedKeyingMaterial]\n   [AAASessionKey]                                          [AAASessionKey]\n   Client                           <------>                         Server\n                      [Authenticated layer on top of (D)TLS]\n\n\nTLS side channel authentication and straightforward bindings of AAA information\nto application layer using well-defined mechanism.\n\n   OpenVPN Client                   <------>                 OpenVPN Server\n   [KeyAgreement]                                            [KeyAgreement]\n\n   [TLSExportedKeyingMaterial]                  [TLSExportedKeyingMaterial]\n   [DerivedAAABindingKey]                            [DerivedAAABindingKey]\n                                                  [AuthenticateBindingKeys]\n   Client                           ------->                         Server\n                             [Confidential channel]\n\n\nTLS Message flow for a full handshake\n\n   ClientHello                      -------->\n                                                               ServerHello\n                                                               Certificate*\n                                                         ServerKeyExchange*\n                                                        CertificateRequest*\n                                    <--------              ServerHelloDone\n   Certificate*\n   ClientKeyExchange\n   CertificateVerify*\n   [ChangeCipherSpec]\n   Finished                         -------->\n                                                        [ChangeCipherSpec]\n                                    <--------                     Finished\n\n   GenerateTLSBindingKey                             GenerateTLSBindingKey\n\n   Application Data                 <------->             Application Data\n\n\nTerminology\n\n   AAA                     Authentication, Authorization, and Accounting:\n                           functions that are generally required to control\n                           access to a service and support auditing.\n\n   Secure channel          a packet, datagram, octet stream connection, or\n                           sequence of connections between two end-points that\n                           affords cryptographic integrity and confidentiality\n                           to data exchanged over it.\n\n   Channel binding         the process of establishing that no man-in-the-middle\n                           exists between two end-points that have been\n                           authenticated using secure channel.\n\n   TLS Binding Key         Exported Keying Material [RFC5705]\n\n                           If no context is provided, it then computes:\n                           PRF(SecurityParameters.master_secret, label,\n                               SecurityParameters.client_random +\n                               SecurityParameters.server_random\n                           )[length]\n\n                           If context is provided, it computes:\n                           PRF(SecurityParameters.master_secret, label,\n                               SecurityParameters.client_random +\n                               SecurityParameters.server_random +\n                               context_value_length + context_value\n                           )[length]\n\n   AAA Binding Key         TLS side channel authentication based on secure\n                           channel bindings requires one more key derivation.\n\n                           SHA1(TLSExportedKeyingMaterial + ServerPublicKey)\n\nReference\n\n   [OPENAAA]               \"TLS side channel authentication and straightforward\n                            bindings of AAA information to application\n                            layer using well-defined mechanism.\"\n                           Daniel Kubec <niel@rtfm.cz>              March 2013\n                           https://github.com/n13l/openaaa\n\n   [RFC5705]               \"Keying Material Exporters for TLS\"\n                           E. Rescorla, RFC 5705 March 2010\n                           https://tools.ietf.org/html/rfc5705\n\n   [RFC5929]               \"Channel Bindings for TLS\"\n                           J. Altman, N. Williams, L. Zhu, RFC 5929, July 2010\n                           https://tools.ietf.org/html/rfc5929\n\n   [RFC4680]               \"TLS Handshake Message for Supplemental Data\"\n                           S. Santesson, RFC 4680, September 2006\n                           https://tools.ietf.org/html/rfc4680\n\n   [RFC5878]               \"TLS Authorization Extension\"\n                           M. Brown, R. Housley, RFC 5878, May 2010\n                           https://tools.ietf.org/html/rfc5878\n\n   [RFC5746]               \"TLS Renegotiation Indication Extension\"\n                           E. Rescorla, M. Raym, S. Dispensa, N. Oskov\n                           RFC 5746, February 2010\n                           https://tools.ietf.org/html/rfc5746\n"
  },
  {
    "path": "doc/man-sections/advanced-options.rst",
    "content": "Standalone Debug Options\n------------------------\n\n--show-gateway args\n  (Standalone) Show current IPv4 and IPv6 default gateway and interface\n  towards the gateway (if the protocol in question is enabled).\n\n  Valid syntax:\n  ::\n\n     --show-gateway\n     --show-gateway IPv4-target\n     --show-gateway IPv6-target\n\n  For IPv4 it looks for a 0.0.0.0/0 route, or the specified IPv4 address\n  if the target can be parsed as an IPv4 address.\n  For IPv6 this queries the route towards ::/128, or the specified IPv6\n  target address if the argument is an IPv6 address.\n\n  Adding a target is helpful for diagnostics to see if OpenVPN will do\n  the right thing if there are more specific IPv4/IPv6 routes to a\n  VPN server.\n\n\nAdvanced Expert Options\n-----------------------\nThese are options only required when special tweaking is needed, often\nused when debugging or testing out special usage scenarios.\n\n--hash-size args\n  Set the size of the real address hash table to ``r`` and the virtual\n  address table to ``v``.\n\n  Valid syntax:\n  ::\n\n     hash-size r v\n\n  By default, both tables are sized at 256 buckets.\n\n--bcast-buffers n\n  Allocate ``n`` buffers for broadcast datagrams (default :code:`256`).\n\n--persist-local-ip\n  Preserve initially resolved local IP address and port number across\n  ``SIGUSR1`` or ``--ping-restart`` restarts.\n\n--persist-remote-ip\n  Preserve most recently authenticated remote IP address and port number\n  across :code:`SIGUSR1` or ``--ping-restart`` restarts.\n\n--rcvbuf size\n  Set the TCP/UDP socket receive buffer size. Defaults to operating system\n  default.\n\n--shaper n\n  Limit bandwidth of outgoing tunnel data to ``n`` bytes per second on the\n  TCP/UDP port. Note that this will only work if mode is set to\n  :code:`p2p`.  If you want to limit the bandwidth in both directions, use\n  this option on both peers.\n\n  OpenVPN uses the following algorithm to implement traffic shaping: Given\n  a shaper rate of ``n`` bytes per second, after a datagram write of ``b``\n  bytes is queued on the TCP/UDP port, wait a minimum of ``(b / n)``\n  seconds before queuing the next write.\n\n  It should be noted that OpenVPN supports multiple tunnels between the\n  same two peers, allowing you to construct full-speed and reduced\n  bandwidth tunnels at the same time, routing low-priority data such as\n  off-site backups over the reduced bandwidth tunnel, and other data over\n  the full-speed tunnel.\n\n  Also note that for low bandwidth tunnels (under 1000 bytes per second),\n  you should probably use lower MTU values as well (see above), otherwise\n  the packet latency will grow so large as to trigger timeouts in the TLS\n  layer and TCP connections running over the tunnel.\n\n  OpenVPN allows ``n`` to be between 100 bytes/sec and 100 Mbytes/sec.\n\n--sndbuf size\n  Set the TCP/UDP socket send buffer size. Defaults to operating system\n  default.\n\n--tcp-queue-limit n\n  Maximum number of output packets queued before TCP (default :code:`64`).\n\n  When OpenVPN is tunneling data from a TUN/TAP device to a remote client\n  over a TCP connection, it is possible that the TUN/TAP device might\n  produce data at a faster rate than the TCP connection can support. When\n  the number of output packets queued before sending to the TCP socket\n  reaches this limit for a given client connection, OpenVPN will start to\n  drop outgoing packets directed at this client.\n\n--txqueuelen n\n  *(Linux only)* Set the TX queue length on the TUN/TAP interface.\n  Currently defaults to operating system default.\n\n--disable-dco\n  Disables the opportunistic use of data channel offloading if available.\n  Without this option, OpenVPN will opportunistically use DCO mode if\n  the config options and the running kernel supports using DCO.\n\n  Data channel offload currently requires data-ciphers to only contain\n  AEAD ciphers (AES-GCM and Chacha20-Poly1305) and Linux with the\n  ovpn-dco module.\n\n  Note that some options have no effect or cannot be used when DCO mode\n  is enabled.\n\n  On platforms that do not support DCO ``disable-dco`` has no effect.\n"
  },
  {
    "path": "doc/man-sections/cipher-negotiation.rst",
    "content": "Data channel cipher negotiation\n-------------------------------\n\nOpenVPN 2.4 and higher have the capability to negotiate the data cipher that\nis used to encrypt data packets. This section describes the mechanism in more detail and the\ndifferent backwards compatibility mechanism with older server and clients.\n\nOpenVPN 2.5 and later behaviour\n```````````````````````````````\nWhen both client and server are at least running OpenVPN 2.5, that the order of\nthe ciphers of the server's ``--data-ciphers`` is used to pick the data cipher.\nThat means that the first cipher in that list that is also in the client's\n``--data-ciphers`` list is chosen. If no common cipher is found the client is rejected\nwith a AUTH_FAILED message (as seen in client log):\n\n    AUTH: Received control message: AUTH_FAILED,Data channel cipher negotiation failed (no shared cipher)\n\nOpenVPN 2.5 and later will only allow the ciphers specified in ``--data-ciphers``.\nIf ``--data-ciphers`` is not set the default is :code:`AES-256-GCM:AES-128-GCM`.\nIn 2.6 and later the default is changed to\n:code:`AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305` when Chacha20-Poly1305 is available.\n\nFor backwards compatibility OpenVPN 2.6 and later with ``--compat-mode 2.4.x``\n(or lower) and OpenVPN 2.5 will automatically add a cipher specified using the\n``--cipher`` option to this list.\n\nOpenVPN 2.4 clients\n```````````````````\nThe negotiation support in OpenVPN 2.4 was the first iteration of the implementation\nand still had some quirks. Its main goal was \"upgrade to AES-256-GCM when possible\".\nAn OpenVPN 2.4 client that is built against a crypto library that supports AES in GCM\nmode and does not have ``--ncp-disable`` will always announce support for\n``AES-256-GCM`` and ``AES-128-GCM`` to a server by sending :code:`IV_NCP=2`.\n\nThis only causes a problem if ``--ncp-ciphers`` option has been changed from the\ndefault of :code:`AES-256-GCM:AES-128-GCM` to a value that does not include\nthese two ciphers. When an OpenVPN server tries to use ``AES-256-GCM`` or\n``AES-128-GCM`` the connection will then fail. It is therefore recommended to\nalways have the ``AES-256-GCM`` and ``AES-128-GCM`` ciphers to the ``--ncp-ciphers``\noptions to avoid this behaviour.\n\nOpenVPN 3 clients\n`````````````````\nClients based on the OpenVPN 3.x library (https://github.com/openvpn/openvpn3/)\ndo not have a configurable ``--ncp-ciphers`` or ``--data-ciphers`` option. Newer\nversions by default disable legacy AES-CBC, BF-CBC, and DES-CBC ciphers.\nThese clients will always announce support for all their supported AEAD ciphers\n(``AES-256-GCM``, ``AES-128-GCM`` and in newer versions also ``Chacha20-Poly1305``).\n\nTo support OpenVPN 3.x based clients at least one of these ciphers needs to be\nincluded in the server's ``--data-ciphers`` option.\n\n\nOpenVPN 2.3 and older clients (and clients with ``--ncp-disable``)\n``````````````````````````````````````````````````````````````````\nWhen a client without cipher negotiation support connects to a server the\ncipher specified with the ``--cipher`` option in the client configuration\nmust be included in the ``--data-ciphers`` option of the server to allow\nthe client to connect. Otherwise the client will be sent the ``AUTH_FAILED``\nmessage that indicates no shared cipher.\n\nIf the client is 2.3 or older and has been configured with the\n``--enable-small``  :code:`./configure` argument, using\n``data-ciphers-fallback cipher`` in the server config file with the explicit\ncipher used by the client is necessary.\n\nOpenVPN 2.4 server\n``````````````````\nWhen a client indicates support for ``AES-128-GCM`` and ``AES-256-GCM``\n(with ``IV_NCP=2``) an OpenVPN 2.4 server will send the first\ncipher of the ``--ncp-ciphers`` to the OpenVPN client regardless of what\nthe cipher is. To emulate the behaviour of an OpenVPN 2.4 client as close\nas possible and have compatibility to a setup that depends on this quirk,\nadding ``AES-128-GCM`` and ``AES-256-GCM`` to the client's ``--data-ciphers``\noption is required. OpenVPN 2.5+ will only announce the ``IV_NCP=2`` flag if\nthose ciphers are present.\n\nOpenVPN 2.3 and older servers (and servers with ``--ncp-disable``)\n``````````````````````````````````````````````````````````````````\nThe cipher used by the server must be included in ``--data-ciphers`` to\nallow the client connecting to a server without cipher negotiation\nsupport.\n(For compatibility OpenVPN 2.5 will also accept the cipher set with\n``--cipher``)\n\nIf the server is 2.3 or older and  has been configured with the\n``--enable-small`` :code:`./configure` argument, adding\n``--data-ciphers-fallback cipher`` to the client config with the explicit\ncipher used by the server is necessary.\n\nBlowfish in CBC mode (BF-CBC) deprecation\n`````````````````````````````````````````\nThe ``--cipher`` option defaulted to ``BF-CBC`` in OpenVPN 2.4 and older\nversion. The default was never changed to ensure backwards compatibility.\nIn OpenVPN 2.5 this behaviour has now been changed so that if the ``--cipher``\nis not explicitly set it does not allow the weak ``BF-CBC`` cipher any more\nand needs to explicitly added as ``--cipher BFC-CBC`` or added to\n``--data-ciphers``.\n\nWe strongly recommend to switching away from BF-CBC to a\nmore secure cipher as soon as possible instead.\n"
  },
  {
    "path": "doc/man-sections/client-options.rst",
    "content": "Client Options\n--------------\nThe client options are used when connecting to an OpenVPN server configured\nto use ``--server``, ``--server-bridge``, or ``--mode server`` in its\nconfiguration.\n\n--allow-pull-fqdn\n  Allow client to pull DNS names from server (rather than being limited to\n  IP address) for ``--ifconfig``, ``--route``, and ``--route-gateway``.\n\n--allow-recursive-routing\n  When this option is set, OpenVPN will not drop incoming tun packets with\n  same destination as host.\n\n--auth-token token\n  This is not an option to be used directly in any configuration files,\n  but rather push this option from a ``--client-connect`` script or a\n  ``--plugin`` which hooks into the :code:`OPENVPN_PLUGIN_CLIENT_CONNECT`\n  or :code:`OPENVPN_PLUGIN_CLIENT_CONNECT_V2` calls. This option provides a\n  possibility to replace the clients password with an authentication token\n  during the lifetime of the OpenVPN client.\n\n  Whenever the connection is renegotiated and the\n  ``--auth-user-pass-verify`` script or ``--plugin`` making use of the\n  :code:`OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY` hook is triggered, it will\n  pass over this token as the password instead of the password the user\n  provided. The authentication token can only be reset by a full reconnect\n  where the server can push new options to the client. The password the\n  user entered is never preserved once an authentication token has been\n  set. If the OpenVPN server side rejects the authentication token then\n  the client will receive an :code:`AUTH_FAILED` and disconnect.\n\n  The purpose of this is to enable two factor authentication methods, such\n  as HOTP or TOTP, to be used without needing to retrieve a new OTP code\n  each time the connection is renegotiated. Another use case is to cache\n  authentication data on the client without needing to have the users\n  password cached in memory during the life time of the session.\n\n  To make use of this feature, the ``--client-connect`` script or\n  ``--plugin`` needs to put\n  ::\n\n     push \"auth-token UNIQUE_TOKEN_VALUE\"\n\n  into the file/buffer for dynamic configuration data. This will then make\n  the OpenVPN server to push this value to the client, which replaces the\n  local password with the ``UNIQUE_TOKEN_VALUE``.\n\n  Newer clients (2.4.7+) will fall back to the original password method\n  after a failed auth. Older clients will keep using the token value and\n  react according to ``--auth-retry``\n\n--auth-token-user base64username\n  Companion option to ``--auth-token``. This options allows one to override\n  the username used by the client when reauthenticating with the ``auth-token``.\n  It also allows one to use ``--auth-token`` in setups that normally do not use\n  username and password.\n\n  The username has to be base64 encoded.\n\n--auth-user-pass\n  Authenticate with server using username/password.\n\n  Valid syntaxes:\n  ::\n\n      auth-user-pass\n      auth-user-pass up\n\n  If ``up`` is present, it must be a file containing username/password on 2\n  lines. If the password line is missing, OpenVPN will prompt for one.\n\n  If ``up`` is omitted, username/password will be prompted from the\n  console.\n\n  This option can also be inlined\n  ::\n\n    <auth-user-pass>\n    username\n    [password]\n    </auth-user-pass>\n\n  where password is optional, and will be prompted from the console if\n  missing.\n\n  The server configuration must specify an ``--auth-user-pass-verify``\n  script to verify the username/password provided by the client.\n\n--auth-retry type\n  Controls how OpenVPN responds to username/password verification errors\n  such as the client-side response to an :code:`AUTH_FAILED` message from\n  the server or verification failure of the private key password.\n\n  Normally used to prevent auth errors from being fatal on the client\n  side, and to permit username/password requeries in case of error.\n\n  An :code:`AUTH_FAILED` message is generated by the server if the client\n  fails ``--auth-user-pass`` authentication, or if the server-side\n  ``--client-connect`` script returns an error status when the client\n  tries to connect.\n\n  ``type`` can be one of:\n\n  :code:`none`\n      Client will exit with a fatal error (this is the default).\n\n  :code:`nointeract`\n      Client will retry the connection without requerying\n      for an ``--auth-user-pass`` username/password. Use this option for\n      unattended clients.\n\n  :code:`interact`\n      Client will requery for an ``--auth-user-pass``\n      username/password and/or private key password before attempting a\n      reconnection.\n\n  Note that while this option cannot be pushed, it can be controlled from\n  the management interface.\n\n--client\n  A helper directive designed to simplify the configuration of OpenVPN's\n  client mode. This directive is equivalent to:\n  ::\n\n       pull\n       tls-client\n\n--client-nat args\n  This pushable client option sets up a stateless one-to-one NAT rule on\n  packet addresses (not ports), and is useful in cases where routes or\n  ifconfig settings pushed to the client would create an IP numbering\n  conflict.\n\n  Valid syntax:\n  ::\n\n      client-nat snat|dnat network netmask alias\n\n  Examples:\n  ::\n\n      client-nat snat 192.168.0.0 255.255.0.0 10.64.0.0\n      client-nat dnat 10.64.0.0 255.255.0.0 192.168.0.0\n\n  ``network`` and ``netmask`` (for example :code:`192.168.0.0\n  255.255.0.0`) define the local view of a resource from the client\n  perspective, while ``alias`` (for example :code:`10.64.0.0`) defines the\n  remote view from the server perspective using the same netmask.\n\n  Use :code:`snat` (source NAT) for resources owned by the client and\n  :code:`dnat` (destination NAT) for remote resources.\n\n  Set ``--verb 6`` for debugging info showing the transformation of\n  src/dest addresses in packets.\n\n--connect-retry args\n  Wait ``n`` seconds between connection attempts (default :code:`1`).\n  Repeated reconnection attempts are slowed down after 5 retries per\n  remote by doubling the wait time after each unsuccessful attempt.\n\n  Valid syntaxes:\n  ::\n\n     connect retry n\n     connect retry n max\n\n  If the optional argument ``max`` is specified, the maximum wait time in\n  seconds gets capped at that value (default :code:`300`).\n\n--connect-retry-max n\n  ``n`` specifies the number of times each ``--remote`` or\n  ``<connection>`` entry is tried. Specifying ``n`` as :code:`1` would try\n  each entry exactly once. A successful connection resets the counter.\n  (default *unlimited*).\n\n--connect-timeout n\n  See ``--server-poll-timeout``.\n\n--dns args\n  Client DNS configuration to be used with the connection.\n\n  Valid syntaxes:\n  ::\n\n     dns search-domains domain [domain ...]\n     dns server n address addr[:port] [addr[:port] ...]\n     dns server n resolve-domains domain [domain ...]\n     dns server n dnssec yes|optional|no\n     dns server n transport DoH|DoT|plain\n     dns server n sni server-name\n\n  The ``--dns search-domains`` directive takes one or more domain names\n  to be added as DNS domain suffixes. If it is repeated multiple times within\n  a configuration the domains are appended, thus e.g. domain names pushed by\n  a server will amend locally defined ones.\n\n  The ``--dns server`` directive is used to configure DNS server ``n``.\n  The server id ``n`` must be a value between -128 and 127. For pushed\n  DNS server options it must be between 0 and 127. The server id is used\n  to group options and also for ordering the list of configured DNS servers;\n  lower numbers come first. DNS servers being pushed to a client replace\n  already configured DNS servers with the same server id.\n\n  The ``address`` option configures the IPv4 and / or IPv6 address(es) of\n  the DNS server. Up to eight addresses can be specified per DNS server.\n  Optionally a port can be appended after a colon. IPv6 addresses need to\n  be enclosed in brackets if a port is appended.\n\n  The ``resolve-domains`` option takes one or more DNS domains used to define\n  a split-dns or dns-routing setup, where only the given domains are resolved\n  by the server. Systems which do not support fine grained DNS domain\n  configuration will ignore this setting.\n\n  The ``dnssec`` option is used to configure validation of DNSSEC records.\n  While the exact semantics may differ for resolvers on different systems,\n  ``yes`` likely makes validation mandatory, ``no`` disables it, and ``optional``\n  uses it opportunistically.\n\n  The ``transport`` option enables DNS-over-HTTPS (``DoH``) or DNS-over-TLS (``DoT``)\n  for a DNS server. The ``sni`` option can be used with them to specify the\n  ``server-name`` for TLS server name indication.\n\n  Each server has to have at least one address configured for a configuration\n  to be valid. All the other options can be omitted.\n\n  Note that not all options may be supported on all platforms. As soon support\n  for different systems is implemented, information will be added here how\n  unsupported options are treated.\n\n  The ``--dns`` option will eventually obsolete the ``--dhcp-option`` directive.\n  Until then it will replace configuration at the places ``--dhcp-option`` puts it,\n  so that ``--dns`` overrides ``--dhcp-option``. Thus, ``--dns`` can be used today\n  to migrate from ``--dhcp-option``.\n\n--explicit-exit-notify n\n  In UDP client mode or point-to-point mode, send server/peer an exit\n  notification if tunnel is restarted or OpenVPN process is exited. In\n  client mode, on exit/restart, this option will tell the server to\n  immediately close its client instance object rather than waiting for a\n  timeout.\n\n  If both server and client support sending this message using the control\n  channel, the message will be sent as control-channel message. Otherwise\n  the message is sent as data-channel message, which will be ignored by\n  data-channel offloaded peers.\n\n  The **n** parameter (default :code:`1` if not present) controls the\n  maximum number of attempts that the client will try to resend the exit\n  notification message if messages are sent in data-channel mode.\n\n  In UDP server mode, send :code:`RESTART` control channel command to\n  connected clients. The ``n`` parameter (default :code:`1` if not present)\n  controls client behavior. With ``n`` = :code:`1` client will attempt to\n  reconnect to the same server, with ``n`` = :code:`2` client will advance\n  to the next server.\n\n  OpenVPN will not send any exit notifications unless this option is\n  enabled.\n\n--inactive args\n  Causes OpenVPN to exit after ``n`` seconds of inactivity on the TUN/TAP\n  device. The time length of inactivity is measured since the last\n  incoming or outgoing tunnel packet. The default value is 0 seconds,\n  which disables this feature.\n\n  Valid syntaxes:\n  ::\n\n       inactive n\n       inactive n bytes\n\n  If the optional ``bytes`` parameter is included, exit if less than\n  ``bytes`` of combined in/out traffic are produced on the tun/tap device\n  in ``n`` seconds.\n\n  In any case, OpenVPN's internal ping packets (which are just keepalives)\n  and TLS control packets are not considered \"activity\", nor are they\n  counted as traffic, as they are used internally by OpenVPN and are not\n  an indication of actual user activity.\n\n  NOTE: on FreeBSD with DCO, due to platform limits, the previous paragraph\n  is not correct.  In that case, encapsulation overhead and keepalives are\n  counted, so using this feature needs a sufficiently-high ``bytes`` value to\n  take these extra numbers into account.\n\n--proto-force p\n  When iterating through connection profiles, only consider profiles using\n  protocol ``p`` (:code:`tcp` \\| :code:`udp`).\n\n  Note that this specifically only filters by the transport layer\n  protocol, i.e. UDP or TCP. This does not affect whether IPv4 or\n  IPv6 is used as IP protocol.\n\n  For implementation reasons the option accepts the :code:`4` and :code:`6`\n  suffixes when specifying the protocol\n  (i.e. :code:`udp4` / :code:`udp6` / :code:`tcp4` / :code:`tcp6`).\n  However, these behave the same as without the suffix and should be avoided\n  to prevent confusion.\n\n--pull\n  This option must be used on a client which is connecting to a\n  multi-client server. It indicates to OpenVPN that it should accept\n  options pushed by the server, provided they are part of the legal set of\n  pushable options (note that the ``--pull`` option is implied by\n  ``--client`` ).\n\n  In particular, ``--pull`` allows the server to push routes to the\n  client, so you should not use ``--pull`` or ``--client`` in situations\n  where you don't trust the server to have control over the client's\n  routing table.\n\n--pull-filter args\n  Filter options on the client pushed by the server to the client.\n\n  Valid syntaxes:\n  ::\n\n     pull-filter accept text\n     pull-filter ignore text\n     pull-filter reject text\n\n  Filter options received from the server if the option starts with\n  :code:`text`.  The action flag :code:`accept` allows the option,\n  :code:`ignore` removes it and :code:`reject` flags an error and triggers\n  a :code:`SIGUSR1` restart. The filters may be specified multiple times,\n  and each filter is applied in the order it is specified. The filtering of\n  each option stops as soon as a match is found. Unmatched options are accepted\n  by default.\n\n  Prefix comparison is used to match :code:`text` against the received option so\n  that\n  ::\n\n      pull-filter ignore \"route\"\n\n  would remove all pushed options starting with ``route`` which would\n  include, for example, ``route-gateway``. Enclose *text* in quotes to\n  embed spaces.\n\n  ::\n\n      pull-filter accept \"route 192.168.1.\"\n      pull-filter ignore \"route \"\n\n  would remove all routes that do not start with ``192.168.1``.\n\n  *Note* that :code:`reject` may result in a repeated cycle of failure and\n  reconnect, unless multiple remotes are specified and connection to the\n  next remote succeeds. To silently ignore an option pushed by the server,\n  use :code:`ignore`.\n\n  *Warning:* ``pull-filter`` cannot be relied upon as a security measure to\n  protect against offending options pushed by a server. For example, the\n  filter could be defeated by pushing options with extra spaces between\n  tokens or other formatting variations.\n\n--push-peer-info\n  Push additional information about the client to server. The following\n  data is always pushed to the server:\n\n  :code:`IV_VER=<version>`\n        The client OpenVPN version\n\n  :code:`IV_PLAT=[linux|solaris|openbsd|mac|netbsd|freebsd|win]`\n        The client OS platform\n\n  :code:`IV_PROTO`\n    Details about protocol extensions that the peer supports. The\n    variable is a bitfield and the bits are defined as follows:\n\n    - bit 0: Reserved, should always be zero\n    - bit 1: The peer supports peer-id floating mechanism\n    - bit 2: The client expects a push-reply and the server may\n      send this reply without waiting for a push-request first.\n    - bit 3: The client is capable of doing key derivation using\n      RFC5705 key material exporter.\n    - bit 4: The client is capable of accepting additional arguments\n      to the ``AUTH_PENDING`` message.\n    - bit 5: The client supports doing feature negotiation in P2P mode\n    - bit 6: The client is capable of parsing and receiving the ``--dns`` pushed option\n    - bit 7: The client is capable of sending exit notification via control channel using ``EXIT`` message. Also, the client is accepting the protocol-flags pushed option for the EKM capability\n    - bit 8: The client is capable of accepting ``AUTH_FAILED,TEMP`` messages\n    - bit 9: The client is capable of dynamic tls-crypt\n    - bit 10: The client is capable of data epoch keys\n\n  :code:`IV_NCP=2`\n        Negotiable ciphers, client supports ``--cipher`` pushed by\n        the server, a value of 2 or greater indicates client supports\n        *AES-GCM-128* and *AES-GCM-256*. IV_NCP is *deprecated* in\n        favor of ``IV_CIPHERS``.\n\n  :code:`IV_CIPHERS=<data-ciphers>`\n        The client announces the list of supported ciphers configured with the\n        ``--data-ciphers`` option to the server.\n\n  :code:`IV_MTU=<max_mtu>`\n        The client announces the support of pushable MTU and the maximum MTU\n        it is willing to accept.\n\n  :code:`IV_GUI_VER=<gui_id> <version>`\n        The UI version of a UI if one is running, for example\n        :code:`de.blinkt.openvpn 0.5.47` for the Android app.\n        This may be set by the client UI/GUI using ``--setenv``.\n\n  :code:`IV_SSO=[crtext,][openurl,][proxy_url]`\n        Additional authentication methods supported by the client.\n        This may be set by the client UI/GUI using ``--setenv``.\n\n  The following flags depend on which compression formats are compiled in\n  and whether compression is allowed by options. See `Protocol options`_\n  for more details.\n\n    :code:`IV_LZO=1`\n        If client supports LZO compression.\n\n    :code:`IV_LZO_STUB=1`\n        If client was built with LZO stub capability. This is only sent if\n        ``IV_LZO=1`` is not sent. This means the client can talk to a server\n        configured with ``--comp-lzo no``.\n\n    :code:`IV_LZ4=1` and :code:`IV_LZ4v2=1`\n        If the client supports LZ4 compression.\n\n    :code:`IV_COMP_STUB=1` and :code:`IV_COMP_STUBv2=1`\n        If the client supports stub compression. This means the client can talk\n        to a server configured with ``--compress``.\n\n  When ``--push-peer-info`` is enabled the additional information consists\n  of the following data:\n\n  :code:`IV_HWADDR=<string>`\n        This is intended to be a unique and persistent ID of the client.\n        The string value can be any readable ASCII string up to 64 bytes.\n        OpenVPN 2.x and some other implementations use the MAC address of\n        the client's interface used to reach the default gateway. If this\n        string is generated by the client, it should be consistent and\n        preserved across independent sessions and preferably\n        re-installations and upgrades.\n\n  :code:`IV_SSL=<version string>`\n        The ssl library version used by the client, e.g.\n        :code:`OpenSSL 1.0.2f 28 Jan 2016`.\n\n  :code:`IV_PLAT_VER=x.y`\n        The version of the operating system, e.g. 6.1 for Windows 7.\n        This may be set by the client UI/GUI using ``--setenv``.\n        On Windows systems it is automatically determined by openvpn\n        itself.  On other platforms OpenVPN will default to sending\n        the information returned by the ``uname()`` system call in\n        the ``release`` field, which is usually the currently running\n        kernel version.  This is highly system specific, though.\n\n  :code:`UV_<name>=<value>`\n        Client environment variables whose names start with\n        :code:`UV_`\n\n--remote args\n  Remote host name or IP address, port and protocol.\n\n  Valid syntaxes:\n  ::\n\n     remote host\n     remote host port\n     remote host port proto\n\n  The ``port`` and ``proto`` arguments are optional. The OpenVPN client\n  will try to connect to a server at ``host:port``.  The ``proto`` argument\n  indicates the protocol to use when connecting with the remote, and may be\n  :code:`tcp` or :code:`udp`.  To enforce IPv4 or IPv6 connections add a\n  :code:`4` or :code:`6` suffix; like :code:`udp4` / :code:`udp6`\n  / :code:`tcp4` / :code:`tcp6`.\n\n  On the client, multiple ``--remote`` options may be specified for\n  redundancy, each referring to a different OpenVPN server, in the order\n  specified by the list of ``--remote`` options. Specifying multiple\n  ``--remote`` options for this purpose is a special case of the more\n  general connection-profile feature. See the ``<connection>``\n  documentation below.\n\n  The client will move on to the next host in the list, in the event of\n  connection failure. Note that at any given time, the OpenVPN client will\n  at most be connected to one server.\n\n  Examples:\n  ::\n\n     remote server1.example.net\n     remote server1.example.net 1194\n     remote server2.example.net 1194 tcp\n\n  *Note:*\n     Since UDP is connectionless, connection failure is defined by\n     the ``--ping`` and ``--ping-restart`` options.\n\n     Also, if you use multiple ``--remote`` options, AND you are dropping\n     root privileges on the client with ``--user`` and/or ``--group`` AND\n     the client is running a non-Windows OS, if the client needs to switch\n     to a different server, and that server pushes back different TUN/TAP\n     or route settings, the client may lack the necessary privileges to\n     close and reopen the TUN/TAP interface. This could cause the client\n     to exit with a fatal error.\n\n  If ``--remote`` is unspecified, OpenVPN will listen for packets from any\n  IP address, but will not act on those packets unless they pass all\n  authentication tests. This requirement for authentication is binding on\n  all potential peers, even those from known and supposedly trusted IP\n  addresses (it is very easy to forge a source IP address on a UDP\n  packet).\n\n  When used in TCP mode, ``--remote`` will act as a filter, rejecting\n  connections from any host which does not match ``host``.\n\n  If ``host`` is a DNS name which resolves to multiple IP addresses,\n  OpenVPN will try them in the order that the system getaddrinfo()\n  presents them, so priorization and DNS randomization is done by the\n  system library. Unless an IP version is forced by the protocol\n  specification (4/6 suffix), OpenVPN will try both IPv4 and IPv6\n  addresses, in the order getaddrinfo() returns them.\n\n--remote-random\n  When multiple ``--remote`` address/ports are specified, or if connection\n  profiles are being used, initially randomize the order of the list as a\n  kind of basic load-balancing measure.\n\n--remote-random-hostname\n  Prepend a random string (6 bytes, 12 hex characters) to hostname to\n  prevent DNS caching. For example, \"foo.bar.gov\" would be modified to\n  \"<random-chars>.foo.bar.gov\".\n\n--resolv-retry n\n  If hostname resolve fails for ``--remote``, retry resolve for ``n``\n  seconds before failing.\n\n  Set ``n`` to :code:`infinite` to retry indefinitely.\n\n  By default, ``--resolv-retry infinite`` is enabled. You can disable by\n  setting n=0.\n\n--single-session\n  After initially connecting to a remote peer, disallow any new\n  connections. Using this option means that a remote peer cannot connect,\n  disconnect, and then reconnect.\n\n  If the daemon is reset by a signal or ``--ping-restart``, it will allow\n  one new connection.\n\n  ``--single-session`` can be used with ``--ping-exit`` or ``--inactive``\n  to create a single dynamic session that will exit when finished.\n\n--server-poll-timeout n\n  When connecting to a remote server do not wait for more than ``n``\n  seconds for a response before trying the next server. The default value\n  is :code:`120`. This timeout includes proxy and TCP connect timeouts.\n\n--static-challenge args\n  Enable static challenge/response protocol\n\n  Valid syntax:\n  ::\n\n     static-challenge text echo [format]\n\n  The ``text`` challenge text is presented to the user which describes what\n  information is requested.  The ``echo`` flag indicates if the user's\n  input should be echoed on the screen.  Valid ``echo`` values are\n  :code:`0` or :code:`1`. The optional ``format`` indicates whether\n  the password and response should be combined using the SCRV1 protocol\n  (``format`` = :code:`scrv1`) or simply concatenated (``format`` = :code:`concat`).\n  :code:`scrv1` is the default.\n\n  See management-notes.txt in the OpenVPN distribution for a description of\n  the OpenVPN challenge/response protocol.\n\n.. include:: proxy-options.rst\n"
  },
  {
    "path": "doc/man-sections/connection-profiles.rst",
    "content": "CONNECTION PROFILES\n===================\n\nClient configuration files may contain multiple remote servers which\nit will attempt to connect against.  But there are some configuration\noptions which are related to specific ``--remote`` options.  For these\nuse cases, connection profiles are the solution.\n\nBy encapsulating the ``--remote`` option and related options within\n``<connection>`` and ``</connection>``, these options are handled as a\ngroup.\n\nAn OpenVPN client will try each connection profile sequentially until it\nachieves a successful connection.\n\n``--remote-random`` can be used to initially \"scramble\" the connection\nlist.\n\nHere is an example of connection profile usage::\n\n   client\n   dev tun\n\n   <connection>\n   remote 198.19.34.56 1194 udp\n   </connection>\n\n   <connection>\n   remote 198.19.34.56 443 tcp\n   </connection>\n\n   <connection>\n   remote 198.19.34.56 443 tcp\n   http-proxy 192.168.0.8 8080\n   </connection>\n\n   <connection>\n   remote 198.19.36.99 443 tcp\n   http-proxy 192.168.0.8 8080\n   </connection>\n\n   persist-tun\n   pkcs12 client.p12\n   remote-cert-tls server\n   verb 3\n\nFirst we try to connect to a server at 198.19.34.56:1194 using UDP. If\nthat fails, we then try to connect to 198.19.34.56:443 using TCP. If\nthat also fails, then try connecting through an HTTP proxy at\n192.168.0.8:8080 to 198.19.34.56:443 using TCP. Finally, try to connect\nthrough the same proxy to a server at 198.19.36.99:443 using TCP.\n\nThe following OpenVPN options may be used inside of a ``<connection>``\nblock:\n\n``bind``, ``connect-retry``, ``connect-retry-max``, ``connect-timeout``,\n``explicit-exit-notify``, ``float``, ``fragment``, ``http-proxy``,\n``http-proxy-option``, ``key-direction``, ``link-mtu``, ``local``,\n``lport``, ``mssfix``, ``mtu-disc``, ``nobind``, ``port``, ``proto``,\n``remote``, ``rport``, ``socks-proxy``, ``tls-auth``, ``tls-crypt``,\n``tls-crypt-v2``, ``tun-mtu and``, ``tun-mtu-extra``.\n\nA defaulting mechanism exists for specifying options to apply to all\n``<connection>`` profiles. If any of the above options (with the\nexception of ``remote`` ) appear outside of a ``<connection>`` block,\nbut in a configuration file which has one or more ``<connection>``\nblocks, the option setting will be used as a default for\n``<connection>`` blocks which follow it in the configuration file.\n\nFor example, suppose the ``nobind`` option were placed in the sample\nconfiguration file above, near the top of the file, before the first\n``<connection>`` block. The effect would be as if ``nobind`` were\ndeclared in all ``<connection>`` blocks below it.\n"
  },
  {
    "path": "doc/man-sections/encryption-options.rst",
    "content": "Encryption Options\n------------------\n\nSSL Library information\n```````````````````````\n\n--show-ciphers\n  (Standalone) Show all cipher algorithms to use with the ``--cipher``\n  option.\n\n--show-digests\n  (Standalone) Show all message digest algorithms to use with the\n  ``--auth`` option.\n\n--show-tls\n  (Standalone) Show all TLS ciphers supported by the crypto library.\n  OpenVPN uses TLS to secure the control channel, over which the keys that\n  are used to protect the actual VPN traffic are exchanged. The TLS\n  ciphers will be sorted from highest preference (most secure) to lowest.\n\n  Be aware that whether a cipher suite in this list can actually work\n  depends on the specific setup of both peers (e.g. both peers must\n  support the cipher, and an ECDSA cipher suite will not work if you are\n  using an RSA certificate, etc.).\n\n--show-engines\n  (Standalone) Show currently available hardware-based crypto acceleration\n  engines supported by the OpenSSL library.\n\n--show-groups\n  (Standalone) Show all available elliptic curves/groups to use with the\n  ``--ecdh-curve`` and ``tls-groups`` options.\n\nGenerating key material\n```````````````````````\n\n--genkey args\n  (Standalone) Generate a key to be used of the type keytype. if keyfile\n  is left out or empty the key will be output on stdout. See the following\n  sections for the different keytypes.\n\n  Valid syntax:\n  ::\n\n     --genkey keytype keyfile\n\n  Valid keytype arguments are:\n\n  :code:`secret`                Standard OpenVPN shared secret keys\n\n  :code:`tls-crypt`             Alias for :code:`secret`\n\n  :code:`tls-auth`              Alias for :code:`secret`\n\n  :code:`auth-token`            Key used for ``--auth-gen-token-key``\n\n  :code:`tls-crypt-v2-server`   TLS Crypt v2 server key\n\n  :code:`tls-crypt-v2-client`   TLS Crypt v2 client key\n\n\n  Examples:\n  ::\n\n     $ openvpn --genkey secret shared.key\n     $ openvpn --genkey tls-crypt shared.key\n     $ openvpn --genkey tls-auth shared.key\n     $ openvpn --genkey tls-crypt-v2-server v2crypt-server.key\n     $ openvpn --tls-crypt-v2 v2crypt-server.key --genkey tls-crypt-v2-client v2crypt-client-1.key\n\n  * Generating *Shared Secret Keys*\n    Generate a shared secret, for use with the ``--tls-auth``\n    or ``--tls-crypt`` options.\n\n    Syntax:\n    ::\n\n       $ openvpn --genkey tls-crypt|tls-auth keyfile\n\n    The key is saved in ``keyfile``. Both variants (``tls-crypt`` and\n    ``tls-auth``) generate the same type of key. The aliases are added for\n    convenience.\n\n    This file must be shared with the peer over a pre-existing secure\n    channel such as ``scp``\\(1).\n\n  * Generating *TLS Crypt v2 Server key*\n    Generate a ``--tls-crypt-v2`` key to be used by an OpenVPN server.\n    The key is stored in ``keyfile``.\n\n    Syntax:\n    ::\n\n       --genkey tls-crypt-v2-server keyfile\n\n  * Generating *TLS Crypt v2 Client key*\n    Generate a --tls-crypt-v2 key to be used by OpenVPN clients.  The\n    key is stored in ``keyfile``.\n\n    Syntax\n    ::\n\n       --genkey tls-crypt-v2-client keyfile [metadata]\n\n    If supplied, include the supplied ``metadata`` in the wrapped client\n    key. This metadata must be supplied in base64-encoded form. The\n    metadata must be at most 733 bytes long (980 characters in base64, though\n    note that 980 base64 characters can encode more than 733 bytes).\n\n    If no metadata is supplied, OpenVPN will use a 64-bit unix timestamp\n    representing the current time in UTC, encoded in network order, as\n    metadata for the generated key.\n\n    A tls-crypt-v2 client key is wrapped using a server key. To generate a\n    client key, the user must therefore supply the server key using the\n    ``--tls-crypt-v2`` option.\n\n    Servers can use ``--tls-crypt-v2-verify`` to specify a metadata\n    verification command.\n\n  * Generate *Authentication Token key*\n    Generate a new secret that can be used with **--auth-gen-token-secret**\n\n    Syntax:\n    ::\n\n       --genkey auth-token [keyfile]\n\n    *Note:*\n       This file should be kept secret to the server as anyone that has\n       access to this file will be able to generate auth tokens that the\n       OpenVPN server will accept as valid.\n\n.. include:: renegotiation.rst\n.. include:: tls-options.rst\n.. include:: pkcs11-options.rst\n"
  },
  {
    "path": "doc/man-sections/example-fingerprint.rst",
    "content": "Small OpenVPN setup with peer-fingerprint\n=========================================\nThis section consists of instructions how to build a small OpenVPN setup with the\n:code:`peer-fingerprint` option. This has the advantage of being easy to setup\nand should be suitable for most small lab and home setups without the need for a PKI.\nFor bigger scale setup setting up a PKI (e.g. via easy-rsa) is still recommended.\n\nBoth server and client configuration can be further modified to customise the\nsetup.\n\nServer setup\n------------\n1. Install openvpn\n\n   Compile from source-code (see ``INSTALL`` file) or install via a distribution (apt/yum/ports)\n   or via installer (Windows).\n\n2. Generate a self-signed certificate for the server::\n\n    openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -keyout server.key -out server.crt -nodes -sha256 -days 3650 -subj '/CN=server'\n\n3. Generate SHA256 fingerprint of the server certificate\n\n   Use the OpenSSL command line utility to view the fingerprint of just\n   created certificate::\n\n    openssl x509 -fingerprint -sha256 -in server.crt -noout\n\n   This outputs something similar to::\n\n     SHA256 Fingerprint=00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff\n\n\n4. Write a server configuration (``server.conf``)::\n\n    # The server certificate we created in step 1\n    cert server.crt\n    key server.key\n\n    dh none\n    dev tun\n\n    # Listen on IPv6+IPv4 simultaneously\n    proto udp6\n\n    # The ip address the server will distribute\n    server 10.8.0.0 255.255.255.0\n    server-ipv6 fd00:6f76:706e::/64\n\n    # A tun-mtu of 1400 avoids problems of too big packets after VPN encapsulation\n    tun-mtu 1400\n\n    # The fingerprints of your clients. After adding/removing one here restart the\n    # server\n    <peer-fingerprint>\n    </peer-fingerprint>\n\n    # Notify clients when you restart the server to reconnect quickly\n    explicit-exit-notify 1\n\n    # Ping every 60s, restart if no data received for 5 minutes\n    keepalive 60 300\n\n    # Uncomment the line below if you want to have persistent IP addresses\n    # ifconfig-pool-persist  /etc/openvpn/server/ipp.txt\n\n    # Uncomment the line below to push a DNS server to clients\n    # push \"dhcp-option DNS 1.1.1.1\"\n\n5. Add at least one client as described in the client section.\n\n6. Start the server.\n    - On systemd based distributions move ``server.crt``, ``server.key`` and\n      ``server.conf`` to ``/etc/openvpn/server`` and start it via systemctl::\n\n          sudo mv server.conf server.key server.crt /etc/openvpn/server\n\n          sudo systemctl start openvpn-server@server\n\nAdding a client\n---------------\n1. Install OpenVPN\n\n2. Generate a self-signed certificate for the client. In this example the client\n   name is alice. Each client should have a unique name. Replace alice with a\n   different name for each client.\n   ::\n\n      openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -keyout - -nodes -sha256 -days 3650 -subj '/CN=alice'\n\n   This generate a certificate and a key for the client. The output of the command will look\n   something like this::\n\n      -----BEGIN PRIVATE KEY-----\n      [base64 content]\n      -----END PRIVATE KEY-----\n      -----\n      -----BEGIN CERTIFICATE-----\n      [base 64 content]\n      -----END CERTIFICATE-----\n\n\n3. Create a new client configuration file. In this example we will name the file\n   ``alice.ovpn``::\n\n      # The name of your server to connect to\n      remote yourserver.example.net\n      client\n      # use a random source port instead the fixed 1194\n      nobind\n\n      # Uncomment the following line if you want to route\n      # all traffic via the VPN\n      # redirect-gateway def1 ipv6\n\n      # To set a DNS server\n      # dhcp-option DNS 192.168.234.1\n\n      <key>\n      -----BEGIN PRIVATE KEY-----\n      [Insert here the key created in step 2]\n      -----END PRIVATE KEY-----\n      </key>\n      <cert>\n      -----BEGIN CERTIFICATE-----\n      [Insert here the certificate created in step 2]\n      -----END CERTIFICATE-----\n      </cert>\n\n      # This is the fingerprint of the server that we trust. We generated this fingerprint\n      # in step 2 of the server setup\n      peer-fingerprint 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff\n\n      # The tun-mtu of the client should match the server MTU\n      tun-mtu 1400\n      dev tun\n\n\n4. Generate the fingerprint of the client certificate. For that we will\n   let OpenSSL read the client configuration file as the x509 command will\n   ignore anything that is not between the begin and end markers of the certificate::\n\n      openssl x509 -fingerprint -sha256 -noout -in alice.ovpn\n\n   This will again output something like::\n\n        SHA256 Fingerprint=ff:ee:dd:cc:bb:aa:99:88:77:66:55:44:33:22:11:00:ff:ee:dd:cc:bb:aa:99:88:77:66:55:44:33:22:11:00\n\n5. Edit the ``server.conf`` configuration file and add this new client\n   fingerprint as additional line  between :code:`<peer-fingerprint>`\n   and :code:`</peer-fingerprint>`\n\n   After adding *two* clients the part of configuration would look like this::\n\n      <peer-fingerprint>\n      ff:ee:dd:cc:bb:aa:99:88:77:66:55:44:33:22:11:00:ff:ee:dd:cc:bb:aa:99:88:77:66:55:44:33:22:11:00\n      99:88:77:66:55:44:33:22:11:00:ff:ee:dd:cc:bb:aa:99:88:77:66:55:44:33:22:11:00:88:77:66:55:44:33\n      </peer-fingerprint>\n\n6. (optional) if the client is an older client that does not support the\n   :code:`peer-fingerprint` (e.g. OpenVPN 2.5 and older, OpenVPN Connect 3.3\n   and older), the client config ``alice.ovpn`` can be modified to still work with\n   these clients.\n\n   Remove the line starting with :code:`peer-fingerprint`. Then\n   add a new :code:`<ca>` section at the end of the configuration file\n   with the contents of the ``server.crt`` created in step 2 of the\n   server setup. The end of ``alice.ovpn`` file should look like::\n\n      [...]  # Beginning of the file skipped\n      </cert>\n\n      # The tun-mtu of the client should match the server MTU\n      tun-mtu 1400\n      dev tun\n\n      <ca>\n      [contents of the server.crt]\n      </ca>\n\n   Note that we put the :code:`<ca>` section after the :code:`<cert>` section\n   to make the fingerprint generation from step 4 still work since it will\n   only use the first certificate it finds.\n\n7. Import the file into the OpenVPN client or just use the\n   :code:`openvpn alice.ovpn` to start the VPN.\n"
  },
  {
    "path": "doc/man-sections/examples.rst",
    "content": "EXAMPLES\n========\n\nPrior to running these examples, you should have OpenVPN installed on\ntwo machines with network connectivity between them. If you have not yet\ninstalled OpenVPN, consult the INSTALL file included in the OpenVPN\ndistribution.\n\n\nFirewall Setup:\n---------------\n\nIf firewalls exist between the two machines, they should be set to\nforward the port OpenVPN is configured to use, in both directions.\nThe default for OpenVPN is 1194/udp.  If you do not have control\nover the firewalls between the two machines, you may still be able to\nuse OpenVPN by adding ``--ping 15`` to each of the ``openvpn`` commands\nused below in the examples (this will cause each peer to send out a UDP\nping to its remote peer once every 15 seconds which will cause many\nstateful firewalls to forward packets in both directions without an\nexplicit firewall rule).\n\nPlease see your operating system guides for how to configure the firewall\non your systems.\n\n\nVPN Address Setup:\n------------------\n\nFor purposes of our example, our two machines will be called\n``bob.example.com`` and ``alice.example.com``. If you are constructing a\nVPN over the internet, then replace ``bob.example.com`` and\n``alice.example.com`` with the internet hostname or IP address that each\nmachine will use to contact the other over the internet.\n\nNow we will choose the tunnel endpoints. Tunnel endpoints are private IP\naddresses that only have meaning in the context of the VPN. Each machine\nwill use the tunnel endpoint of the other machine to access it over the\nVPN. In our example, the tunnel endpoint for bob.example.com will be\n10.4.0.1 and for alice.example.com, 10.4.0.2.\n\nOnce the VPN is established, you have essentially created a secure\nalternate path between the two hosts which is addressed by using the\ntunnel endpoints. You can control which network traffic passes between\nthe hosts (a) over the VPN or (b) independently of the VPN, by choosing\nwhether to use (a) the VPN endpoint address or (b) the public internet\naddress, to access the remote host. For example if you are on\nbob.example.com and you wish to connect to ``alice.example.com`` via\n``ssh`` without using the VPN (since **ssh** has its own built-in security)\nyou would use the command ``ssh alice.example.com``. However in the same\nscenario, you could also use the command ``telnet 10.4.0.2`` to create a\ntelnet session with alice.example.com over the VPN, that would use the\nVPN to secure the session rather than ``ssh``.\n\nYou can use any address you wish for the tunnel endpoints but make sure\nthat they are private addresses (such as those that begin with 10 or\n192.168) and that they are not part of any existing subnet on the\nnetworks of either peer, unless you are bridging. If you use an address\nthat is part of your local subnet for either of the tunnel endpoints,\nyou will get a weird feedback loop.\n\n\nExample 1: A simple tunnel without security (not recommended)\n-------------------------------------------------------------\n\nOn bob::\n\n   openvpn --remote alice.example.com --dev tun1 \\\n            --ifconfig 10.4.0.1 10.4.0.2 --verb 9\n\nOn alice::\n\n   openvpn --remote bob.example.com --dev tun1 \\\n            --ifconfig 10.4.0.2 10.4.0.1 --verb 9\n\nNow verify the tunnel is working by pinging across the tunnel.\n\nOn bob::\n\n   ping 10.4.0.2\n\nOn alice::\n\n   ping 10.4.0.1\n\nThe ``--verb 9`` option will produce verbose output, similar to the\n``tcpdump``\\(8) program. Omit the ``--verb 9`` option to have OpenVPN run\nquietly.\n\n\nExample 2: A tunnel with self-signed certificates and fingerprint\n-----------------------------------------------------------------\n\nFirst build a self-signed certificate on bob and display its fingerprint.\n\n::\n\n   openssl req -x509 -newkey ec:<(openssl ecparam -name secp384r1) -keyout bob.pem -out bob.pem -nodes -sha256 -days 3650 -subj '/CN=bob'\n   openssl x509 -noout -sha256 -fingerprint -in bob.pem\n\nand the same on alice::\n\n   openssl req -x509 -newkey ec:<(openssl ecparam -name secp384r1) -keyout alice.pem -out alice.pem -nodes -sha256 -days 3650 -subj '/CN=alice'\n   openssl x509 -noout -sha256 -fingerprint -in alice.pem\n\n\nThese commands will build a text file called ``bob.pem`` or ``alice.pem`` (in ascii format)\nthat contain both self-signed certificate and key and show the fingerprint of the certificates.\nTransfer the fingerprints  over a secure medium such as by using\nthe ``scp``\\(1) or ``ssh``\\(1) program.\n\nOn bob::\n\n   openvpn --ifconfig 10.4.0.1 10.4.0.2 --tls-server --dev tun --dh none \\\n           --cert bob.pem --key bob.pem --cipher AES-256-GCM \\\n           --peer-fingerprint \"$fingerprint_of_alices_cert\"\n\nOn alice::\n\n   openvpn --remote bob.example.com --tls-client --dev tun1   \\\n           --ifconfig 10.4.0.2 10.4.0.1 --cipher AES-256-GCM  \\\n           --cert alice.pem --key alice.pem                   \\\n           --peer-fingerprint \"$fingerprint_of_bobs_cert\"\n\nNow verify the tunnel is working by pinging across the tunnel.\n\nOn bob::\n\n   ping 10.4.0.2\n\nOn alice::\n\n   ping 10.4.0.1\n\nNote: This example use a elliptic curve (``secp384``), which allows\n``--dh`` to be set to ``none``.\n\nExample 3: A tunnel with full PKI and TLS-based security\n--------------------------------------------------------\n\nFor this test, we will designate ``bob`` as the TLS client and ``alice``\nas the TLS server.\n\n*Note:*\n    The client or server designation only has\n    meaning for the TLS subsystem. It has no bearing on OpenVPN's\n    peer-to-peer, UDP-based communication model.*\n\nFirst, build a separate certificate/key pair for both bob and alice (see\nabove where ``--cert`` is discussed for more info). Then construct\nDiffie Hellman parameters (see above where ``--dh`` is discussed for\nmore info). You can also use the included test files :code:`client.crt`,\n:code:`client.key`, :code:`server.crt`, :code:`server.key` and\n:code:`ca.crt`. The ``.crt`` files are certificates/public-keys, the\n``.key`` files are private keys, and :code:`ca.crt` is a certification\nauthority who has signed both :code:`client.crt` and :code:`server.crt`.\nFor Diffie Hellman parameters you can use the included file\n:code:`dh2048.pem`.\n\n*WARNING:*\n    All client, server, and certificate authority certificates\n    and keys included in the OpenVPN distribution are totally\n    insecure and should be used for testing only.\n\nOn bob::\n\n   openvpn --remote alice.example.com --dev tun1    \\\n           --ifconfig 10.4.0.1 10.4.0.2             \\\n           --tls-client --ca ca.crt                 \\\n           --cert client.crt --key client.key       \\\n           --reneg-sec 60 --verb 5\n\nOn alice::\n\n   openvpn --remote bob.example.com --dev tun1      \\\n           --ifconfig 10.4.0.2 10.4.0.1             \\\n           --tls-server --dh dh1024.pem --ca ca.crt \\\n           --cert server.crt --key server.key       \\\n           --reneg-sec 60 --verb 5\n\nNow verify the tunnel is working by pinging across the tunnel.\n\nOn bob::\n\n   ping 10.4.0.2\n\nOn alice::\n\n   ping 10.4.0.1\n\nNotice the ``--reneg-sec 60`` option we used above. That tells OpenVPN\nto renegotiate the data channel keys every minute. Since we used\n``--verb 5`` above, you will see status information on each new key\nnegotiation.\n\nFor production operations, a key renegotiation interval of 60 seconds is\nprobably too frequent. Omit the ``--reneg-sec 60`` option to use\nOpenVPN's default key renegotiation interval of one hour.\n\n\nRouting:\n--------\n\nAssuming you can ping across the tunnel, the next step is to route a\nreal subnet over the secure tunnel. Suppose that bob and alice have two\nnetwork interfaces each, one connected to the internet, and the other to\na private network. Our goal is to securely connect both private\nnetworks. We will assume that bob's private subnet is *10.0.0.0/24* and\nalice's is *10.0.1.0/24*.\n\nFirst, ensure that IP forwarding is enabled on both peers. On Linux,\nenable routing::\n\n    echo 1 > /proc/sys/net/ipv4/ip_forward\n\nThis setting is not persistent.  Please see your operating systems\ndocumentation how to properly configure IP forwarding, which is also\npersistent through system boots.\n\nIf your system is configured with a firewall.  Please see your operating\nsystems guide on how to configure the firewall.  You typically want to\nallow traffic coming from and going to the tun/tap adapter OpenVPN is\nconfigured to use.\n\nOn bob::\n\n   route add -net 10.0.1.0 netmask 255.255.255.0 gw 10.4.0.2\n\nOn alice::\n\n   route add -net 10.0.0.0 netmask 255.255.255.0 gw 10.4.0.1\n\nNow any machine on the *10.0.0.0/24* subnet can access any machine on the\n*10.0.1.0/24* subnet over the secure tunnel (or vice versa).\n\nIn a production environment, you could put the route command(s) in a\nscript and execute with the ``--up`` option.\n"
  },
  {
    "path": "doc/man-sections/generic-options.rst",
    "content": "Generic Options\n---------------\nThis section covers generic options which are accessible regardless of\nwhich mode OpenVPN is configured as.\n\n--help\n\n  Show options.\n\n--auth-nocache\n  Don't cache ``--askpass`` or ``--auth-user-pass`` username/passwords in\n  virtual memory.\n\n  If specified, this directive will cause OpenVPN to immediately forget\n  username/password inputs after they are used. As a result, when OpenVPN\n  needs a username/password, it will prompt for input from stdin, which\n  may be multiple times during the duration of an OpenVPN session.\n\n  When using ``--auth-nocache`` in combination with a user/password file\n  and ``--chroot`` or ``--daemon``, make sure to use an absolute path.\n\n--cd dir\n  Change directory to ``dir`` prior to reading any files such as\n  configuration files, key files, scripts, etc. ``dir`` should be an\n  absolute path, with a leading \"/\", and without any references to the\n  current directory such as :code:`.` or :code:`..`.\n\n  This option is useful when you are running OpenVPN in ``--daemon`` mode,\n  and you want to consolidate all of your OpenVPN control files in one\n  location.\n\n--chroot dir\n  Chroot to ``dir`` after initialization. ``--chroot`` essentially\n  redefines ``dir`` as being the top level directory tree (/). OpenVPN\n  will therefore be unable to access any files outside this tree. This can\n  be desirable from a security standpoint.\n\n  Since the chroot operation is delayed until after initialization, most\n  OpenVPN options that reference files will operate in a pre-chroot\n  context.\n\n  In many cases, the ``dir`` parameter can point to an empty directory,\n  however complications can result when scripts or restarts are executed\n  after the chroot operation.\n\n  Note: The SSL library will probably need /dev/urandom to be available\n  inside the chroot directory ``dir``. This is because SSL libraries\n  occasionally need to collect fresh randomness. Newer linux kernels and some\n  BSDs implement a getrandom() or getentropy() syscall that removes the\n  need for /dev/urandom to be available.\n\n--compat-mode version\n  This option provides a convenient way to alter the defaults of OpenVPN\n  to be more compatible with the version ``version`` specified. All of\n  the changes this option applies can also be achieved using individual\n  configuration options.\n\n  The version specified with this option is the version of OpenVPN peer\n  OpenVPN should try to be compatible with. In general OpenVPN should be\n  compatible with the last two previous version without this option. E.g.\n  OpenVPN 2.6.0 should be compatible with 2.5.x and 2.4.x without this option.\n  However, there might be some edge cases that still require this option even\n  in these cases.\n\n  Note: Using this option reverts defaults to no longer recommended\n  values and should be avoided if possible.\n\n  The following table details what defaults are changed depending on the\n  version specified.\n\n  - 2.5.x or lower: ``--allow-compression asym`` is automatically added\n    to the configuration if no other compression options are present.\n  - 2.4.x or lower: The cipher in ``--cipher`` is appended to\n    ``--data-ciphers``.\n  - 2.3.x or lower: ``--data-ciphers-fallback`` is automatically added with\n    the same cipher as ``--cipher``.\n  - 2.3.6 or lower: ``--tls-version-min 1.0`` is added to the configuration\n    when ``--tls-version-min`` is not explicitly set.\n\n  If not required, this is option should be avoided. Setting this option can\n  lower security or disable features like data-channel offloading.\n\n--config file\n  Load additional config options from ``file`` where each line corresponds\n  to one command line option, but with the leading :code:`--` removed.\n\n  If ``--config file`` is the only option to the openvpn command, the\n  ``--config`` can be removed, and the command can be given as ``openvpn\n  file``\n\n  Note that configuration files can be nested to a reasonable depth.\n\n  Double quotation or single quotation characters (\"\", '') can be used to\n  enclose single parameters containing whitespace, and \"#\" or \";\"\n  characters in the first column can be used to denote comments.\n\n  Note that OpenVPN 2.0 and higher performs backslash-based shell escaping\n  for characters not in single quotations, so the following mappings\n  should be observed:\n  ::\n\n      \\\\       Maps to a single backslash character (\\).\n      \\\"       Pass a literal doublequote character (\"), don't\n               interpret it as enclosing a parameter.\n      \\[SPACE] Pass a literal space or tab character, don't\n               interpret it as a parameter delimiter.\n\n  For example on Windows, use double backslashes to represent pathnames:\n  ::\n\n      secret \"c:\\\\OpenVPN\\\\secret.key\"\n\n\n  For examples of configuration files, see\n  https://openvpn.net/community-resources/how-to/\n\n  Here is an example configuration file:\n  ::\n\n      #\n      # Sample OpenVPN configuration file for\n      # using a pre-shared static key.\n      #\n      # '#' or ';' may be used to delimit comments.\n\n      # Use a dynamic tun device.\n      dev tun\n\n      # Our remote peer\n      remote mypeer.mydomain\n\n      # 10.1.0.1 is our local VPN endpoint\n      # 10.1.0.2 is our remote VPN endpoint\n      ifconfig 10.1.0.1 10.1.0.2\n\n      # Our pre-shared static key\n      secret static.key\n\n--daemon progname\n  Become a daemon after all initialization functions are completed.\n\n  Valid syntaxes::\n\n    daemon\n    daemon progname\n\n  This option will cause all message and error output to be sent to the syslog\n  file (such as :code:`/var/log/messages`), except for the output of\n  scripts and ifconfig commands, which will go to :code:`/dev/null` unless\n  otherwise redirected. The syslog redirection occurs immediately at the\n  point that ``--daemon`` is parsed on the command line even though the\n  daemonization point occurs later. If one of the ``--log`` options is\n  present, it will supersede syslog redirection.\n\n  The optional ``progname`` parameter will cause OpenVPN to report its\n  program name to the system logger as ``progname``. This can be useful in\n  linking OpenVPN messages in the syslog file with specific tunnels. When\n  unspecified, ``progname`` defaults to :code:`openvpn`.\n\n  When OpenVPN is run with the ``--daemon`` option, it will try to delay\n  daemonization until the majority of initialization functions which are\n  capable of generating fatal errors are complete. This means that\n  initialization scripts can test the return status of the openvpn command\n  for a fairly reliable indication of whether the command has correctly\n  initialized and entered the packet forwarding event loop.\n\n  In OpenVPN, the vast majority of errors which occur after initialization\n  are non-fatal.\n\n  Note: as soon as OpenVPN has daemonized, it can not ask for usernames,\n  passwords, or key pass phrases anymore. This has certain consequences,\n  namely that using a password-protected private key will fail unless the\n  ``--askpass`` option is used to tell OpenVPN to ask for the pass phrase\n  (this requirement is new in v2.3.7, and is a consequence of calling\n  daemon() before initializing the crypto layer).\n\n  Further, using ``--daemon`` together with ``--auth-user-pass`` (entered\n  on console) and ``--auth-nocache`` will fail as soon as key\n  renegotiation (and reauthentication) occurs.\n\n--disable-dco\n  Disable \"data channel offload\" (DCO).\n\n  On Linux don't use the ovpn-dco device driver, but rather rely on the\n  legacy tun module.\n\n  You may want to use this option if your server needs to allow clients\n  older than version 2.4 to connect.\n\n--disable-occ\n  **DEPRECATED** Disable \"options consistency check\" (OCC) in configurations\n  that do not use TLS.\n\n  Don't output a warning message if option inconsistencies are detected\n  between peers. An example of an option inconsistency would be where one\n  peer uses ``--dev tun`` while the other peer uses ``--dev tap``.\n\n  Use of this option is discouraged, but is provided as a temporary fix in\n  situations where a recent version of OpenVPN must connect to an old\n  version.\n\n--engine engine-name\n  Enable OpenSSL hardware-based crypto engine functionality.\n\n  Valid syntaxes::\n\n    engine\n    engine engine-name\n\n  If ``engine-name`` is specified, use a specific crypto engine. Use the\n  ``--show-engines`` standalone option to list the crypto engines which\n  are supported by OpenSSL.\n\n--group group\n  Similar to the ``--user`` option, this option changes the group ID of\n  the OpenVPN process to ``group`` after initialization.\n\n--ignore-unknown-option args\n  Valid syntax:\n  ::\n\n     ignore-unknown-option opt1 opt2 opt3 ... optN\n\n  When one of options ``opt1 ... optN`` is encountered in the configuration\n  file the configuration file parsing does not fail if this OpenVPN version\n  does not support the option. Multiple ``--ignore-unknown-option`` options\n  can be given to support a larger number of options to ignore.\n\n  This option should be used with caution, as there are good security\n  reasons for having OpenVPN fail if it detects problems in a config file.\n  Having said that, there are valid reasons for wanting new software\n  features to gracefully degrade when encountered by older software\n  versions.\n\n  ``--ignore-unknown-option`` is available since OpenVPN 2.3.3.\n\n--iproute cmd\n  Set alternate command to execute instead of default ``iproute2`` command.\n  May be used in order to execute OpenVPN in unprivileged environment.\n\n--keying-material-exporter args\n  Save Exported Keying Material [RFC5705] of ``len`` bytes (must be between 16\n  and 4095 bytes) using ``label`` in environment\n  (:code:`exported_keying_material`) for use by plugins in\n  :code:`OPENVPN_PLUGIN_TLS_FINAL` callback.\n\n  Valid syntax:\n  ::\n\n    keying-material-exporter label len\n\n  Note that exporter ``labels`` have the potential to collide with existing\n  PRF labels. In order to prevent this, labels *MUST* begin with\n  :code:`EXPORTER`.\n\n--mlock\n  Disable paging by calling the POSIX mlockall function. Requires that\n  OpenVPN be initially run as root (though OpenVPN can subsequently\n  downgrade its UID using the ``--user`` option).\n\n  Using this option ensures that key material and tunnel data are never\n  written to disk due to virtual memory paging operations which occur\n  under most modern operating systems. It ensures that even if an attacker\n  was able to crack the box running OpenVPN, he would not be able to scan\n  the system swap file to recover previously used ephemeral keys, which\n  are used for a period of time governed by the ``--reneg`` options (see\n  below), then are discarded.\n\n  The downside of using ``--mlock`` is that it will reduce the amount of\n  physical memory available to other applications.\n\n  The limit on how much memory can be locked and how that limit\n  is enforced are OS-dependent. On Linux the default limit that an\n  unprivileged process may lock (RLIMIT_MEMLOCK) is low, and if\n  privileges are dropped later, future memory allocations will very\n  likely fail. The limit can be increased using ulimit or systemd\n  directives depending on how OpenVPN is started.\n\n  If the platform has the getrlimit(2) system call, OpenVPN will check\n  for the amount of mlock-able memory before calling mlockall(2), and\n  tries to increase the limit to 100 MB if less than this is configured.\n  100 Mb is somewhat arbitrary - it is enough for a moderately-sized\n  OpenVPN deployment, but the memory usage might go beyond that if the\n  number of concurrent clients is high.\n\n--nice n\n  Change process priority after initialization (``n`` greater than 0 is\n  lower priority, ``n`` less than zero is higher priority).\n\n--providers providers\n  Load the list of (OpenSSL) providers. This is mainly useful for using an\n  external provider for key management like tpm2-openssl or to load the\n  legacy provider with\n\n  ::\n\n      --providers legacy default\n\n  Behaviour of changing this option between :code:`SIGHUP` might not be well behaving.\n  If you need to change/add/remove this option, fully restart OpenVPN.\n\n--remap-usr1 signal\n  Control whether internally or externally generated :code:`SIGUSR1` signals\n  are remapped to :code:`SIGHUP` (restart without persisting state) or\n  :code:`SIGTERM` (exit).\n\n  ``signal`` can be set to :code:`SIGHUP` or :code:`SIGTERM`. By default,\n  no remapping occurs.\n\n--script-security level\n  This directive offers policy-level control over OpenVPN's usage of\n  external programs and scripts. Lower ``level`` values are more\n  restrictive, higher values are more permissive. Settings for ``level``:\n\n  :code:`0`\n      Strictly no calling of external programs.\n\n  :code:`1`\n      (Default) Only call built-in executables such as ifconfig,\n      ip, route, or netsh.\n\n  :code:`2`\n      Allow calling of built-in executables and user-defined\n      scripts.\n\n  :code:`3`\n      Allow passwords to be passed to scripts via environmental\n      variables (potentially unsafe).\n\n  OpenVPN releases before v2.3 also supported a ``method`` flag which\n  indicated how OpenVPN should call external commands and scripts. This\n  could be either :code:`execve` or :code:`system`. As of OpenVPN 2.3, this\n  flag is no longer accepted. In most \\*nix environments the execve()\n  approach has been used without any issues.\n\n  Some directives such as ``--up`` allow options to be passed to the\n  external script. In these cases make sure the script name does not\n  contain any spaces or the configuration parser will choke because it\n  can't determine where the script name ends and script options start.\n\n  To run scripts in Windows in earlier OpenVPN versions you needed to\n  either add a full path to the script interpreter which can parse the\n  script or use the ``system`` flag to run these scripts. As of OpenVPN\n  2.3 it is now a strict requirement to have full path to the script\n  interpreter when running non-executables files. This is not needed for\n  executable files, such as .exe, .com, .bat or .cmd files. For example,\n  if you have a Visual Basic script, you must use this syntax now:\n\n  ::\n\n     --up 'C:\\\\Windows\\\\System32\\\\wscript.exe C:\\\\Program\\ Files\\\\OpenVPN\\\\config\\\\my-up-script.vbs'\n\n  Please note the single quote marks and the escaping of the backslashes\n  (\\\\) and the space character.\n\n  The reason the support for the :code:`system` flag was removed is due to\n  the security implications with shell expansions when executing scripts\n  via the :code:`system()` call.\n\n--setcon context\n  Apply SELinux ``context`` after initialization. This essentially\n  provides the ability to restrict OpenVPN's rights to only network I/O\n  operations, thanks to SELinux. This goes further than ``--user`` and\n  ``--chroot`` in that those two, while being great security features,\n  unfortunately do not protect against privilege escalation by\n  exploitation of a vulnerable system call. You can of course combine all\n  three, but please note that since setcon requires access to /proc you\n  will have to provide it inside the chroot directory (e.g. with mount\n  --bind).\n\n  Since the setcon operation is delayed until after initialization,\n  OpenVPN can be restricted to just network-related system calls, whereas\n  by applying the context before startup (such as the OpenVPN one provided\n  in the SELinux Reference Policies) you will have to allow many things\n  required only during initialization.\n\n  Like with chroot, complications can result when scripts or restarts are\n  executed after the setcon operation, which is why you should really\n  consider using the ``--persist-tun`` option.\n\n--status args\n  Write operational status to ``file`` every ``n`` seconds. ``n`` defaults\n  to :code:`60` if not specified.\n\n  Valid syntaxes:\n  ::\n\n    status file\n    status file n\n\n  Status can also be written to the syslog by sending a :code:`SIGUSR2`\n  signal.\n\n  With multi-client capability enabled on a server, the status file\n  includes a list of clients and a routing table. The output format can be\n  controlled by the ``--status-version`` option in that case.\n\n  For clients or instances running in point-to-point mode, it will contain\n  the traffic statistics.\n\n--status-version n\n  Set the status file format version number to ``n``.\n\n  This only affects the status file on servers with multi-client\n  capability enabled.  Valid status version values:\n\n  :code:`1`\n      Traditional format (default). The client list contains the\n      following fields comma-separated: Common Name, Real Address, Bytes\n      Received, Bytes Sent, Connected Since.\n\n  :code:`2`\n      A more reliable format for external processing. Compared to\n      version :code:`1`, the client list contains some additional fields:\n      Virtual Address, Virtual IPv6 Address, Username, Client ID, Peer ID,\n      Data Channel Cipher. Future versions may extend the number of fields.\n\n  :code:`3`\n      Identical to :code:`2`, but fields are tab-separated.\n\n--test-crypto\n  Do a self-test of OpenVPN's crypto options by encrypting and decrypting\n  test packets using the data channel encryption options specified above.\n  This option does not require a peer to function, and therefore can be\n  specified without ``--dev`` or ``--remote``.\n\n  The typical usage of ``--test-crypto`` would be something like this:\n  ::\n\n     openvpn --test-crypto\n\n  or\n\n  ::\n\n     openvpn --test-crypto --verb 9\n\n  This option is very useful to test OpenVPN after it has been ported to a\n  new platform, or to isolate problems in the compiler, OpenSSL crypto\n  library, or OpenVPN's crypto code. Since it is a self-test mode,\n  problems with encryption and authentication can be debugged\n  independently of network and tunnel issues.\n\n  Older versions of OpenVPN used the ``--secret`` argument to specify a\n  static key for this test. Newer version generate a random key for the\n  test.\n\n--tmp-dir dir\n  Specify a directory ``dir`` for temporary files instead of the default\n  :code:`TMPDIR` (or \"/tmp\" if unset). Note that it must be writable by the main\n  process after it has dropped root privileges.\n\n  This directory will be used to communicate with scripts and plugins:\n\n  * ``--client-connect`` scripts and :code:`OPENVPN_PLUGIN_CLIENT_CONNECT`\n    plug-in hook to dynamically generate client-specific configuration\n    :code:`client_connect_config_file` and return success/failure via\n    :code:`client_connect_deferred_file` when using deferred client connect\n    method\n\n  * :code:`OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY` plug-in hooks returns\n    success/failure via :code:`auth_control_file` when using deferred auth\n    method and pending authentication via :code:`auth_pending_file`.\n\n--user user\n  Change the user ID of the OpenVPN process to ``user`` after\n  initialization, dropping privileges in the process. This option is\n  useful to protect the system in the event that some hostile party was\n  able to gain control of an OpenVPN session. Though OpenVPN's security\n  features make this unlikely, it is provided as a second line of defense.\n\n  By setting ``user`` to an unprivileged user dedicated to run openvpn,\n  the hostile party would be limited in what damage they could cause. Of\n  course once you take away privileges, you cannot return them to an\n  OpenVPN session. This means, for example, that if you want to reset an\n  OpenVPN daemon with a :code:`SIGUSR1` signal (for example in response to\n  a DHCP reset), you should make use of one or more of the ``--persist``\n  options to ensure that OpenVPN doesn't need to execute any privileged\n  operations in order to restart (such as re-reading key files or running\n  ``ifconfig`` on the TUN device).\n\n  NOTE: Previous versions of openvpn used :code:`nobody` as the example\n  unpriviledged user. It is not recommended to actually use that user\n  since it is usually used by other system services already. Always\n  create a dedicated user for openvpn.\n\n--writepid file\n  Write OpenVPN's main process ID to ``file``.\n"
  },
  {
    "path": "doc/man-sections/inline-files.rst",
    "content": "INLINE FILE SUPPORT\n===================\n\nOpenVPN allows including files in the main configuration for the ``--ca``,\n``--cert``, ``--dh``, ``--extra-certs``, ``--key``, ``--pkcs12``,\n``--crl-verify``, ``--http-proxy-user-pass``, ``--tls-auth``,\n``--auth-gen-token-secret``, ``--peer-fingerprint``, ``--tls-crypt``,\n``--tls-crypt-v2``, ``--verify-hash`` and ``--auth-user-pass`` options.\n\nEach inline file started by the line ``<option>`` and ended by the line\n``</option>``\n\nHere is an example of an inline file usage\n\n::\n\n    <cert>\n    -----BEGIN CERTIFICATE-----\n    [...]\n    -----END CERTIFICATE-----\n    </cert>\n\nWhen using the inline file feature with ``--pkcs12`` the inline file has\nto be base64 encoded. Encoding of a .p12 file into base64 can be done\nfor example with OpenSSL by running :code:`openssl base64 -in input.p12`\n"
  },
  {
    "path": "doc/man-sections/link-options.rst",
    "content": "Link Options\n------------\nThis link options section covers options related to the connection between\nthe local and the remote host.\n\n--bind keywords\n  Bind to local address and port. This is the default unless any of\n  ``--proto tcp-client`` , ``--http-proxy`` or ``--socks-proxy`` are used.\n\n  If the optional :code:`ipv6only` keyword is present OpenVPN will bind only\n  to IPv6 (as opposed to IPv6 and IPv4) when a IPv6 socket is opened.\n\n--float\n  Allow remote peer to change its IP address and/or port number, such as\n  due to DHCP (this is the default if ``--remote`` is not used).\n  ``--float`` when specified with ``--remote`` allows an OpenVPN session\n  to initially connect to a peer at a known address, however if packets\n  arrive from a new address and pass all authentication tests, the new\n  address will take control of the session. This is useful when you are\n  connecting to a peer which holds a dynamic address such as a dial-in\n  user or DHCP client.\n\n  Essentially, ``--float`` tells OpenVPN to accept authenticated packets\n  from any address, not only the address which was specified in the\n  ``--remote`` option.\n\n--fragment args\n\n  Valid syntax:\n  ::\n\n     fragment max\n     fragment max mtu\n\n  Enable internal datagram fragmentation so that no UDP datagrams are sent\n  which are larger than ``max`` bytes.\n\n  If the :code:`mtu` parameter is present the ``max`` parameter is\n  interpreted to include IP and UDP encapsulation overhead. The\n  :code:`mtu` parameter is introduced in OpenVPN version 2.6.0.\n\n  If the :code:`mtu` parameter is absent, the ``max`` parameter is\n  interpreted in the same way as the ``--link-mtu`` parameter, i.e.\n  the UDP packet size after encapsulation overhead has been added in,\n  but not including the UDP header itself.\n\n  The ``--fragment`` option only makes sense when you are using the UDP\n  protocol (``--proto udp``).\n\n  ``--fragment`` adds 4 bytes of overhead per datagram.\n\n  See the ``--mssfix`` option below for an important related option to\n  ``--fragment``.\n\n  It should also be noted that this option is not meant to replace UDP\n  fragmentation at the IP stack level. It is only meant as a last resort\n  when path MTU discovery is broken. Using this option is less efficient\n  than fixing path MTU discovery for your IP link and using native IP\n  fragmentation instead.\n\n  Having said that, there are circumstances where using OpenVPN's internal\n  fragmentation capability may be your only option, such as tunneling a\n  UDP multicast stream which requires fragmentation.\n\n--keepalive args\n  A helper directive designed to simplify the expression of ``--ping`` and\n  ``--ping-restart``.\n\n  Valid syntax:\n  ::\n\n     keepalive interval timeout\n\n  Send ping once every ``interval`` seconds, restart if ping is not received\n  for ``timeout`` seconds.\n\n  This option can be used on both client and server side, but it is enough\n  to add this on the server side as it will push appropriate ``--ping``\n  and ``--ping-restart`` options to the client. If used on both server and\n  client, the values pushed from server will override the client local\n  values.\n\n  The ``timeout`` argument will be twice as long on the server side. This\n  ensures that a timeout is detected on client side before the server side\n  drops the connection.\n\n  For example, ``--keepalive 10 60`` expands as follows:\n  ::\n\n     if mode server:\n         ping 10                    # Argument: interval\n         ping-restart 120           # Argument: timeout*2\n         push \"ping 10\"             # Argument: interval\n         push \"ping-restart 60\"     # Argument: timeout\n     else\n         ping 10                    # Argument: interval\n         ping-restart 60            # Argument: timeout\n\n--link-mtu n\n  **DEPRECATED** Sets an upper bound on the size of UDP packets which are sent between\n  OpenVPN peers. *It's best not to set this parameter unless you know what\n  you're doing.*\n\n  Due to variable header size of IP header (20 bytes for IPv4 and 40 bytes\n  for IPv6) and dynamically negotiated data channel cipher, this option\n  is not reliable. It is recommended to set tun-mtu with enough headroom\n  instead.\n\n--local args\n\n  Valid syntax:\n  ::\n\n     local host|* [port] [protocol]\n\n  Local host name or IP address and port for bind. If specified, OpenVPN will bind\n  to this address. If unspecified, OpenVPN will bind to all interfaces.\n  '*' can be used as hostname and means 'any host' (OpenVPN will listen on what\n  is returned by the OS).\n  On a client, or in point-to-point mode, this can only be specified once (1 socket).\n\n  On an OpenVPN setup running as ``--server``, this can be specified multiple times\n  to open multiple listening sockets on different addresses and/or different ports.\n  In order to specify multiple listen ports without specifying an address, use '*'\n  to signal \"use what the operating system gives you as default\", for\n  \"all IPv4 addresses\" use \"0.0.0.0\", for \"all IPv6 addresses\" use '::'.\n  ``--local`` implies ``--bind``.\n\n--lport port\n  Set default TCP/UDP port number. Cannot be used together with\n  ``--nobind`` option.  A port number of ``0`` is only honoured to\n  achieve \"bind() to a random assigned port number\" if a bind-to IP\n  address is specified with ``--local``.\n\n--mark value\n  Mark encrypted packets being sent with ``value``. The mark value can be\n  matched in policy routing and packetfilter rules. This option is only\n  supported in Linux and does nothing on other operating systems.\n\n--mode m\n  Set OpenVPN major mode. By default, OpenVPN runs in point-to-point mode\n  (:code:`p2p`). OpenVPN 2.0 introduces a new mode (:code:`server`) which\n  implements a multi-client server capability.\n\n--mssfix args\n\n  Valid syntax:\n  ::\n\n     mssfix max [mtu]\n\n     mssfix max [fixed]\n\n     mssfix\n\n  Announce to TCP sessions running over the tunnel that they should limit\n  their send packet sizes such that after OpenVPN has encapsulated them,\n  the resulting UDP packet size that OpenVPN sends to its peer will not\n  exceed ``max`` bytes. The default value is :code:`1492 mtu`. Use :code:`0`\n  as max to disable mssfix.\n\n  If the :code:`mtu` parameter is specified the ``max`` value is interpreted\n  as the resulting packet size of VPN packets including the IP and UDP header.\n  Support for the :code:`mtu` parameter was added with OpenVPN version 2.6.0.\n\n  If the :code:`mtu` parameter is not specified, the ``max`` parameter\n  is interpreted in the same way as the ``--link-mtu`` parameter, i.e.\n  the UDP packet size after encapsulation overhead has been added in, but\n  not including the UDP header itself. Resulting packet would be at most 28\n  bytes larger for IPv4 and 48 bytes for IPv6 (20/40 bytes for IP header and\n  8 bytes for UDP header). Default value of 1450 allows OpenVPN packets to be\n  transmitted over IPv4 on a link with MTU 1478 or higher without IP level\n  fragmentation (and 1498 for IPv6).\n\n  If the :code:`fixed` parameter is specified, OpenVPN will make no attempt\n  to calculate the VPN encapsulation overhead but instead will set the MSS to\n  limit the size of the payload IP packets to the specified number. IPv4 packets\n  will have the MSS value lowered to mssfix - 40 and IPv6 packets to mssfix - 60.\n\n  if ``--mssfix`` is specified is specified without any parameter it\n  inherits the parameters of ``--fragment`` if specified or uses the\n  default for ``--mssfix`` otherwise.\n\n  The ``--mssfix`` option only makes sense when you are using the UDP\n  protocol for OpenVPN peer-to-peer communication, i.e. ``--proto udp``.\n\n  ``--mssfix`` and ``--fragment`` can be ideally used together, where\n  ``--mssfix`` will try to keep TCP from needing packet fragmentation in\n  the first place, and if big packets come through anyhow (from protocols\n  other than TCP), ``--fragment`` will internally fragment them.\n\n  ``--max-packet-size``, ``--fragment``, and ``--mssfix`` are designed to\n  work around cases where Path MTU discovery is broken on the network path\n  between OpenVPN peers.\n\n  The usual symptom of such a breakdown is an OpenVPN connection which\n  successfully starts, but then stalls during active usage.\n\n  If ``--fragment`` and ``--mssfix`` are used together, ``--mssfix`` will\n  take its default ``max`` parameter from the ``--fragment max`` option.\n\n  Therefore, one could lower the maximum UDP packet size to 1300 (a good\n  first try for solving MTU-related connection problems) with the\n  following options:\n  ::\n\n     --tun-mtu 1500 --fragment 1300 --mssfix\n\n  If the ``max-packet-size size`` option is used in the configuration\n  it will also act as if ``mssfix size mtu`` was specified in the\n  configuration.\n\n--mtu-disc type\n  Should we do Path MTU discovery on TCP/UDP channel? Only supported on\n  OSes such as Linux that supports the necessary system call to set.\n\n  Valid types:\n\n  :code:`no`      Never send DF (Don't Fragment) frames\n\n  :code:`maybe`   Use per-route hints\n\n  :code:`yes`     Always DF (Don't Fragment)\n\n--mtu-test\n  To empirically measure MTU on connection startup, add the ``--mtu-test``\n  option to your configuration. OpenVPN will send ping packets of various\n  sizes to the remote peer and measure the largest packets which were\n  successfully received. The ``--mtu-test`` process normally takes about 3\n  minutes to complete.\n\n--nobind\n  Do not bind to local address and port. The IP stack will allocate a\n  dynamic port for returning packets. Since the value of the dynamic port\n  could not be known in advance by a peer, this option is only suitable\n  for peers which will be initiating connections by using the ``--remote``\n  option.\n\n--passtos\n  Set the TOS field of the tunnel packet to what the payload's TOS is.\n\n--ping n\n  Ping remote over the TCP/UDP control channel if no packets have been\n  sent for at least ``n`` seconds (specify ``--ping`` on both peers to\n  cause ping packets to be sent in both directions since OpenVPN ping\n  packets are not echoed like IP ping packets).\n\n  This option has two intended uses:\n\n  (1)  Compatibility with stateful firewalls. The periodic ping will ensure\n       that a stateful firewall rule which allows OpenVPN UDP packets to\n       pass will not time out.\n\n  (2)  To provide a basis for the remote to test the existence of its peer\n       using the ``--ping-exit`` option.\n\n  When using OpenVPN in server mode see also ``--keepalive``.\n\n--ping-exit n\n  Causes OpenVPN to exit after ``n`` seconds pass without reception of a\n  ping or other packet from remote. This option can be combined with\n  ``--inactive``, ``--ping`` and ``--ping-exit`` to create a two-tiered\n  inactivity disconnect.\n\n  For example,\n  ::\n\n      openvpn [options...] --inactive 3600 --ping 10 --ping-exit 60\n\n  when used on both peers will cause OpenVPN to exit within 60 seconds if\n  its peer disconnects, but will exit after one hour if no actual tunnel\n  data is exchanged.\n\n--ping-restart n\n  Similar to ``--ping-exit``, but trigger a :code:`SIGUSR1` restart after\n  ``n`` seconds pass without reception of a ping or other packet from\n  remote.\n\n  This option is useful in cases where the remote peer has a dynamic IP\n  address and a low-TTL DNS name is used to track the IP address using a\n  service such as https://www.nsupdate.info/ + a dynamic DNS client such as\n  ``ddclient``.\n\n  If the peer cannot be reached, a restart will be triggered, causing the\n  hostname used with ``--remote`` to be re-resolved (if ``--resolv-retry``\n  is also specified).\n\n  In server mode, ``--ping-restart``, ``--inactive`` or any other type of\n  internally generated signal will always be applied to individual client\n  instance objects, never to whole server itself. Note also in server mode\n  that any internally generated signal which would normally cause a\n  restart, will cause the deletion of the client instance object instead.\n\n  In client mode, the ``--ping-restart`` parameter is set to 120 seconds\n  by default. This default will hold until the client pulls a replacement\n  value from the server, based on the ``--keepalive`` setting in the\n  server configuration. To disable the 120 second default, set\n  ``--ping-restart 0`` on the client.\n\n  See the signals section below for more information on :code:`SIGUSR1`.\n\n  Note that the behavior of ``SIGUSR1`` can be modified by the\n  ``--persist-tun``, ``--persist-local-ip`` and\n  ``--persist-remote-ip`` options.\n\n  Also note that ``--ping-exit`` and ``--ping-restart`` are mutually\n  exclusive and cannot be used together.\n\n--ping-timer-rem\n  Run the ``--ping-exit`` / ``--ping-restart`` timer only if we have a\n  remote address. Use this option if you are starting the daemon in listen\n  mode (i.e. without an explicit ``--remote`` peer), and you don't want to\n  start clocking timeouts until a remote peer connects.\n\n--proto p\n  Use protocol ``p`` for communicating with remote host. ``p`` can be\n  :code:`udp`, :code:`tcp-client`, or :code:`tcp-server`. You can also\n  limit OpenVPN to use only IPv4 or only IPv6 by specifying ``p`` as\n  :code:`udp4`, :code:`tcp4-client`, :code:`tcp4-server` or :code:`udp6`,\n  :code:`tcp6-client`, :code:`tcp6-server`, respectively.\n\n  The default protocol is :code:`udp` when ``--proto`` is not specified.\n\n  For UDP operation, ``--proto udp`` should be specified on both peers.\n\n  For TCP operation, one peer must use ``--proto tcp-server`` and the\n  other must use ``--proto tcp-client``. A peer started with\n  :code:`tcp-server` will wait indefinitely for an incoming connection. A peer\n  started with :code:`tcp-client` will attempt to connect, and if that fails,\n  will sleep for 5 seconds (adjustable via the ``--connect-retry`` option)\n  and try again infinite or up to N retries (adjustable via the\n  ``--connect-retry-max`` option). Both TCP client and server will\n  simulate a SIGUSR1 restart signal if either side resets the connection.\n\n  OpenVPN is designed to operate optimally over UDP, but TCP capability is\n  provided for situations where UDP cannot be used. In comparison with\n  UDP, TCP will usually be somewhat less efficient and less robust when\n  used over unreliable or congested networks.\n\n  This article outlines some of problems with tunneling IP over TCP:\n  https://web.archive.org/web/20141025181658/http://sites.inka.de/sites/bigred/devel/tcp-tcp.html\n\n  There are certain cases, however, where using TCP may be advantageous\n  from a security and robustness perspective, such as tunneling non-IP or\n  application-level UDP protocols, or tunneling protocols which don't\n  possess a built-in reliability layer.\n\n--port port\n  TCP/UDP port number or port name for both local and remote (sets both\n  ``--lport`` and ``--rport`` options to given port). The current default\n  of 1194 represents the official IANA port number assignment for OpenVPN\n  and has been used since version 2.0-beta17. Previous versions used port\n  5000 as the default.\n\n--rport port\n  Set TCP/UDP port number or name used by the ``--remote`` option. The\n  port can also be set directly using the ``--remote`` option.\n\n--replay-window args\n  Modify the replay protection sliding-window size and time window.\n\n  Valid syntaxes::\n\n     replay-window n\n     replay-window n t\n\n  Use a replay protection sliding-window of size ``n`` and a time window\n  of ``t`` seconds.\n\n  By default ``n`` is :code:`64` (the IPSec default) and ``t`` is\n  :code:`15` seconds.\n\n  This option is only relevant in UDP mode, i.e. when either ``--proto\n  udp`` is specified, or no ``--proto`` option is specified.\n\n  When OpenVPN tunnels IP packets over UDP, there is the possibility that\n  packets might be dropped or delivered out of order. Because OpenVPN,\n  like IPSec, is emulating the physical network layer, it will accept an\n  out-of-order packet sequence, and will deliver such packets in the same\n  order they were received to the TCP/IP protocol stack, provided they\n  satisfy several constraints.\n\n  (a)   The packet cannot be a replay.\n\n  (b)   If a packet arrives out of order, it will only be accepted if\n        the difference between its sequence number and the highest sequence\n        number received so far is less than ``n``.\n\n  (c)   If a packet arrives out of order, it will only be accepted if it\n        arrives no later than ``t`` seconds after any packet containing a higher\n        sequence number.\n\n  If you are using a network link with a large pipeline (meaning that the\n  product of bandwidth and latency is high), you may want to use a larger\n  value for ``n``. Satellite links in particular often require this.\n\n  If you run OpenVPN at ``--verb 4``, you will see the message\n  \"PID_ERR replay-window backtrack occurred [x]\" every time the maximum sequence\n  number backtrack seen thus far increases. This can be used to calibrate\n  ``n``.\n\n  There is some controversy on the appropriate method of handling packet\n  reordering at the security layer.\n\n  Namely, to what extent should the security layer protect the\n  encapsulated protocol from attacks which masquerade as the kinds of\n  normal packet loss and reordering that occur over IP networks?\n\n  The IPSec and OpenVPN approach is to allow packet reordering within a\n  certain fixed sequence number window.\n\n  OpenVPN adds to the IPSec model by limiting the window size in time as\n  well as sequence space.\n\n  OpenVPN also adds TCP transport as an option (not offered by IPSec) in\n  which case OpenVPN can adopt a very strict attitude towards message\n  deletion and reordering: Don't allow it. Since TCP guarantees\n  reliability, any packet loss or reordering event can be assumed to be an\n  attack.\n\n  In this sense, it could be argued that TCP tunnel transport is preferred\n  when tunneling non-IP or UDP application protocols which might be\n  vulnerable to a message deletion or reordering attack which falls within\n  the normal operational parameters of IP networks.\n\n  So I would make the statement that one should never tunnel a non-IP\n  protocol or UDP application protocol over UDP, if the protocol might be\n  vulnerable to a message deletion or reordering attack that falls within\n  the normal operating parameters of what is to be expected from the\n  physical IP layer. The problem is easily fixed by simply using TCP as\n  the VPN transport layer.\n\n--replay-persist file\n  Persist replay-protection state across sessions using ``file`` to save\n  and reload the state.\n\n  This option will keep a disk copy of the current replay protection state\n  (i.e. the most recent packet timestamp and sequence number received from\n  the remote peer), so that if an OpenVPN session is stopped and\n  restarted, it will reject any replays of packets which were already\n  received by the prior session.\n\n  This option only makes sense when replay protection is enabled (the\n  default) and you are using TLS mode with ``--tls-auth``.\n\n--session-timeout n\n  Raises :code:`SIGTERM` for the client instance after ``n`` seconds since\n  the beginning of the session, forcing OpenVPN to disconnect.\n  In client mode, OpenVPN will disconnect and exit, while in server mode\n  all client sessions are terminated.\n\n  This option can also be specified in a client instance config file\n  using ``--client-config-dir`` or dynamically generated using a\n  ``--client-connect`` script. In these cases, only the related client\n  session is terminated.\n\n--socket-flags flags\n  Apply the given flags to the OpenVPN transport socket. Currently, only\n  :code:`TCP_NODELAY` is supported.\n\n  The :code:`TCP_NODELAY` socket flag is useful in TCP mode, and causes the\n  kernel to send tunnel packets immediately over the TCP connection without\n  trying to group several smaller packets into a larger packet.  This can\n  result in a considerably improvement in latency.\n\n  This option is pushable from server to client, and should be used on\n  both client and server for maximum effect.\n\n--tcp-nodelay\n  This macro sets the :code:`TCP_NODELAY` socket flag on the server as well\n  as pushes it to connecting clients. The :code:`TCP_NODELAY` flag disables\n  the Nagle algorithm on TCP sockets causing packets to be transmitted\n  immediately with low latency, rather than waiting a short period of time\n  in order to aggregate several packets into a larger containing packet.\n  In VPN applications over TCP, :code:`TCP_NODELAY` is generally a good\n  latency optimization.\n\n  The macro expands as follows:\n  ::\n\n     if mode server:\n         socket-flags TCP_NODELAY\n         push \"socket-flags TCP_NODELAY\"\n\n--max-packet-size size\n  This option will instruct OpenVPN to try to limit the maximum on-write packet\n  size by restricting the control channel packet size and setting ``--mssfix``.\n\n  OpenVPN will try to keep its control channel messages below this size but\n  due to some constraints in the protocol this is not always possible. If the\n  option is not set, the control packet maximum size defaults to 1250.\n  The control channel packet size will be restricted to values between\n  154 and 2048. The maximum packet size includes encapsulation overhead like\n  UDP and IP.\n\n  In terms of ``--mssfix`` it will expand to:\n  ::\n\n      mssfix size mtu\n\n  If you need to set ``--mssfix`` for data channel and control channel maximum\n  packet size independently, use ``--max-packet-size`` first, followed by a\n  ``--mssfix`` in the configuration.\n\n  In general the default size of 1250 should work almost universally apart\n  from specific corner cases, especially since IPv6 requires a MTU of 1280\n  or larger.\n"
  },
  {
    "path": "doc/man-sections/log-options.rst",
    "content": "Log options\n-----------\n\n--echo parms\n  Echo ``parms`` to log output.\n\n  Designed to be used to send messages to a controlling application which\n  is receiving the OpenVPN log output.\n\n--errors-to-stderr\n  Output errors to stderr instead of stdout unless log output is\n  redirected by one of the ``--log`` options.\n\n--log file\n  Output logging messages to ``file``, including output to stdout/stderr\n  which is generated by called scripts. If ``file`` already exists it will\n  be truncated. This option takes effect immediately when it is parsed in\n  the command line and will supersede syslog output if ``--daemon``\n  is also specified. This option is persistent over the entire\n  course of an OpenVPN instantiation and will not be reset by\n  :code:`SIGHUP`, :code:`SIGUSR1`, or ``--ping-restart``.\n\n  Note that on Windows, when OpenVPN is started as a service, logging\n  occurs by default without the need to specify this option.\n\n--log-append file\n  Append logging messages to ``file``.  If ``file`` does not exist, it will\n  be created. This option behaves exactly like ``--log`` except that it\n  appends to rather than truncating the log file.\n\n--machine-readable-output\n  Always write timestamps and message flags to log messages, even when\n  they otherwise would not be prefixed. In particular, this applies to log\n  messages sent to stdout.\n\n--mute n\n  Log at most ``n`` consecutive messages in the same category. This is\n  useful to limit repetitive logging of similar message types.\n\n--mute-replay-warnings\n  Silence the output of replay warnings, which are a common false alarm on\n  WiFi networks. This option preserves the security of the replay\n  protection code without the verbosity associated with warnings about\n  duplicate packets.\n\n--suppress-timestamps\n  Avoid writing timestamps to log messages, even when they otherwise would\n  be prepended. In particular, this applies to log messages sent to\n  stdout.\n\n--syslog progname\n  Direct log output to system logger, but do not become a daemon. See\n  ``--daemon`` directive above for description of ``progname`` parameter.\n\n--verb n\n  Set output verbosity to ``n`` (default :code:`1`). Each level shows all\n  info from the previous levels. Level :code:`3` is recommended if you want\n  a good summary of what's happening without being swamped by output.\n\n  :code:`0`\n      No output except fatal errors.\n\n  :code:`1` to :code:`4`\n      Normal usage range.\n\n  :code:`5`\n      Outputs :code:`R` and :code:`W` characters to the console for\n      each packet read and write, uppercase is used for TCP/UDP\n      packets and lowercase is used for TUN/TAP packets.\n\n  :code:`6` to :code:`11`\n      Debug info range (see :code:`errlevel.h` in the source code for\n      additional information on debug levels).\n"
  },
  {
    "path": "doc/man-sections/management-options.rst",
    "content": "Management Interface Options\n----------------------------\nOpenVPN provides a feature rich socket based management interface for both\nserver and client mode operations.\n\n--management args\n  Enable a management server on a ``socket-name`` Unix socket on those\n  platforms supporting it, or on a designated TCP port.\n\n  Valid syntaxes:\n  ::\n\n    management socket-name unix          #\n    management socket-name unix pw-file  # (recommended)\n    management IP port                   # (INSECURE)\n    management IP port pw-file           #\n\n  ``pw-file``, if specified, is a password file where the password must\n  be on first line. Instead of a filename it can use the keyword stdin\n  which will prompt the user for a password to use when OpenVPN is\n  starting.\n\n  For unix sockets, the default behaviour is to create a unix domain\n  socket that may be connected to by any process. Use the\n  ``--management-client-user`` and ``--management-client-group``\n  directives to restrict access.\n\n  The management interface provides a special mode where the TCP\n  management link can operate over the tunnel itself. To enable this mode,\n  set IP to ``tunnel``. Tunnel mode will cause the management interface to\n  listen for a TCP connection on the local VPN address of the TUN/TAP\n  interface.\n\n  ***BEWARE*** of enabling the management interface over TCP. In these cases\n  you should *ALWAYS* make use of ``pw-file`` to password protect the\n  management interface. Any user who can connect to this TCP ``IP:port``\n  will be able to manage and control (and interfere with) the OpenVPN\n  process. It is also strongly recommended to set IP to 127.0.0.1\n  (localhost) to restrict accessibility of the management server to local\n  clients.\n\n  While the management port is designed for programmatic control of\n  OpenVPN by other applications, it is possible to telnet to the port,\n  using a telnet client in \"raw\" mode. Once connected, type :code:`help`\n  for a list of commands.\n\n  For detailed documentation on the management interface, see the\n  *management-notes.txt* file in the management folder of the OpenVPN\n  source distribution.\n\n--management-client\n  Management interface will connect as a TCP/unix domain client to\n  ``IP:port`` specified by ``--management`` rather than listen as a TCP\n  server or on a unix domain socket.\n\n  If the client connection fails to connect or is disconnected, a SIGTERM\n  signal will be generated causing OpenVPN to quit.\n\n--management-client-auth\n  Gives management interface client the responsibility to authenticate\n  clients after their client certificate has been verified. See\n  :code:`management-notes.txt` in OpenVPN distribution for detailed notes.\n\n--management-client-group g\n  When the management interface is listening on a unix domain socket, only\n  allow connections from group ``g``.\n\n--management-client-user u\n  When the management interface is listening on a unix domain socket, only\n  allow connections from user ``u``.\n\n--management-external-cert certificate-hint\n  Allows usage for external certificate instead of ``--cert`` option\n  (client-only). ``certificate-hint`` is an arbitrary string which is\n  passed to a management interface client as an argument of\n  *NEED-CERTIFICATE* notification. Requires ``--management-external-key``.\n\n--management-external-key args\n  Allows usage for external private key file instead of ``--key`` option\n  (client-only).\n\n  Valid syntaxes:\n  ::\n\n     management-external-key\n     management-external-key nopadding\n     management-external-key pkcs1\n     management-external-key pss\n\n  or any combination like:\n  ::\n\n     management-external-key nopadding pkcs1\n     management-external-key pkcs1 pss\n\n  The optional parameters :code:`nopadding` :code:`pkcs1` and :code:`pss` signal\n  support for different padding algorithms. See\n  :code:`doc/mangement-notes.txt` for a complete description of this\n  feature.\n\n--management-forget-disconnect\n  Make OpenVPN forget passwords when management session disconnects.\n\n  This directive does not affect the ``--http-proxy`` username/password.\n  It is always cached.\n\n--management-hold\n  Start OpenVPN in a hibernating state, until a client of the management\n  interface explicitly starts it with the :code:`hold release` command.\n\n--management-log-cache n\n  Cache the most recent ``n`` lines of log file history for usage by the\n  management channel.\n\n--management-query-passwords\n  Query management channel for private key password and\n  ``--auth-user-pass`` username/password. Only query the management\n  channel for inputs which ordinarily would have been queried from the\n  console.\n\n--management-query-proxy\n  Query management channel for proxy server information for a specific\n  ``--remote`` (client-only).\n\n--management-query-remote\n  Allow management interface to override ``--remote`` directives\n  (client-only).\n\n--management-signal\n  Send SIGUSR1 signal to OpenVPN if management session disconnects. This\n  is useful when you wish to disconnect an OpenVPN session on user logoff.\n  For ``--management-client`` this option is not needed since a disconnect\n  will always generate a :code:`SIGTERM`.\n\n--management-up-down\n  Report tunnel up/down events to management interface.\n"
  },
  {
    "path": "doc/man-sections/network-config.rst",
    "content": "NETWORK CONFIGURATION\n=====================\n\nOpenVPN consists of two sides of network configuration.  One side is the\n*link* between the local and remote side, the other side is the *virtual\nnetwork adapter* (tun/tap device).\n\n.. include:: link-options.rst\n.. include:: vpn-network-options.rst\n.. include:: virtual-routing-and-forwarding.rst\n"
  },
  {
    "path": "doc/man-sections/pkcs11-options.rst",
    "content": "PKCS#11 / SmartCard options\n```````````````````````````\n\n--pkcs11-cert-private args\n  Set if access to certificate object should be performed after login.\n  Every provider has its own setting.\n\n  Valid syntaxes:\n  ::\n\n     pkcs11-cert-private 0\n     pkcs11-cert-private 1\n\n--pkcs11-id name\n  Specify the serialized certificate id to be used. The id can be gotten\n  by the standalone ``--show-pkcs11-ids`` option. See also the description\n  of ``--pkcs11-providers`` option.\n\n--pkcs11-id-management\n  Acquire PKCS#11 id from management interface. In this case a\n  :code:`NEED-STR 'pkcs11-id-request'` real-time message will be triggered,\n  application may use pkcs11-id-count command to retrieve available number of\n  certificates, and pkcs11-id-get command to retrieve certificate id and\n  certificate body.\n  See also the description of ``--pkcs11-providers`` option.\n\n--pkcs11-pin-cache seconds\n  Specify how many seconds the PIN can be cached, the default is until the\n  token is removed.\n\n--pkcs11-private-mode mode\n  Specify which method to use in order to perform private key operations.\n  A different mode can be specified for each provider. Mode is encoded as\n  hex number, and can be a mask one of the following:\n\n  :code:`0` (default)   Try to determine automatically.\n\n  :code:`1`             Use sign.\n\n  :code:`2`             Use sign recover.\n\n  :code:`4`             Use decrypt.\n\n  :code:`8`             Use unwrap.\n\n--pkcs11-protected-authentication args\n  Use PKCS#11 protected authentication path, useful for biometric and\n  external keypad devices. Every provider has its own setting.\n\n  Valid syntaxes:\n  ::\n\n     pkcs11-protected-authentication 0\n     pkcs11-protected-authentication 1\n\n--pkcs11-providers providers\n  Specify an RSA Security Inc. PKCS #11 Cryptographic Token Interface\n  (Cryptoki) providers to load. A space-separated list of one or more\n  provider library names may be specified. This option along with ``--pkcs11-id``\n  or ``pkcs11-id-management`` can be used instead of\n  ``--cert`` and ``--key`` or ``--pkcs12``.\n\n  If p11-kit is present on the system and was enabled during build, its\n  :code:`p11-kit-proxy.so` module will be loaded by default if either\n  the ``--pkcs11-id`` or ``--pkcs11-id-management`` options is present without\n  ``--pkcs11-providers``. If default loading is not enabled in the build and\n  no providers are specified, the former options will be ignored.\n\n--show-pkcs11-ids args\n  (Standalone) Show PKCS#11 token object list.\n\n  Valid syntax:\n  ::\n\n     show-pkcs11 [provider] [cert_private]\n\n  Specify ``cert_private`` as :code:`1` if certificates are stored as\n  private objects.\n\n  If *p11-kit* is present on the system, the ``provider`` argument is\n  optional; if omitted the default :code:`p11-kit-proxy.so` module will be\n  queried.\n\n  ``--verb`` option can be used BEFORE this option to produce debugging\n  information.\n"
  },
  {
    "path": "doc/man-sections/plugin-options.rst",
    "content": "Plug-in Interface Options\n-------------------------\n\nOpenVPN can be extended by loading external plug-in modules at runtime.  These\nplug-ins must be prebuilt and adhere to the OpenVPN Plug-In API.\n\n--plugin args\n  Loads an OpenVPN plug-in module.\n\n  Valid syntax:\n  ::\n\n     plugin module-name\n     plugin module-name \"arguments\"\n\n  The ``module-name`` needs to be the first\n  argument, indicating the plug-in to load.  The second argument is an\n  optional init string which will be passed directly to the plug-in.\n  If the init consists of multiple arguments it must be enclosed in\n  double-quotes (\\\").  Multiple plugin modules may be loaded into one\n  OpenVPN process.\n\n  The ``module-name`` argument can be just a filename or a filename\n  with a relative or absolute path. The format of the filename and path\n  defines if the plug-in will be loaded from a default plug-in directory\n  or outside this directory.\n  ::\n\n    --plugin path         Effective directory used\n    ===================== =============================\n     myplug.so            DEFAULT_DIR/myplug.so\n     subdir/myplug.so     DEFAULT_DIR/subdir/myplug.so\n     ./subdir/myplug.so   CWD/subdir/myplug.so\n     /usr/lib/my/plug.so  /usr/lib/my/plug.so\n\n\n  ``DEFAULT_DIR`` is replaced by the default plug-in directory, which is\n  configured at the build time of OpenVPN. ``CWD`` is the current directory\n  where OpenVPN was started or the directory OpenVPN have switched into\n  via the ``--cd`` option before the ``--plugin`` option.\n\n  For more information and examples on how to build OpenVPN plug-in\n  modules, see the README file in the ``plugin`` folder of the OpenVPN\n  source distribution.\n\n  If you are using an RPM install of OpenVPN, see\n  :code:`/usr/share/openvpn/plugin`. The documentation is in ``doc`` and\n  the actual plugin modules are in ``lib``.\n\n  Multiple plugin modules can be cascaded, and modules can be used in\n  tandem with scripts. The modules will be called by OpenVPN in the order\n  that they are declared in the config file. If both a plugin and script\n  are configured for the same callback, the script will be called last. If\n  the return code of the module/script controls an authentication function\n  (such as tls-verify, auth-user-pass-verify, or client-connect), then\n  every module and script must return success (:code:`0`) in order for the\n  connection to be authenticated.\n\n  **WARNING**:\n        Plug-ins may do deferred execution, meaning the plug-in will\n        return the control back to the main OpenVPN process and provide\n        the plug-in result later on via a different thread or process.\n        OpenVPN does **NOT** support multiple authentication plug-ins\n        **where more than one plugin** tries to do deferred authentication.\n        If this behaviour is detected, OpenVPN will shut down upon first\n        authentication.\n"
  },
  {
    "path": "doc/man-sections/protocol-options.rst",
    "content": "Protocol options\n----------------\nOptions in this section affect features available in the OpenVPN wire\nprotocol.  Many of these options also define the encryption options\nof the data channel in the OpenVPN wire protocol.  These options must be\nconfigured in a compatible way between both the local and remote side.\n\n--allow-compression mode\n  As described in the ``--compress`` option, compression is a potentially\n  dangerous option.  This option allows controlling the behaviour of\n  OpenVPN when compression is used and allowed.\n\n  The ``mode`` argument can be one of the following values:\n\n  :code:`asym`\n      OpenVPN will only *decompress incoming packets* but *not compress\n      outgoing packets*.  This also allows migrating to disable compression\n      when changing both server and client configurations to remove\n      compression at the same time is not a feasible option.\n\n  :code:`no`  (default)\n      OpenVPN will refuse any compression.  If data-channel offloading\n      is enabled, OpenVPN will additionally also refuse compression\n      framing (stub).\n\n  :code:`yes`\n      **DEPRECATED** This option is an alias for :code:`asym`. Previously\n      it did enable compression for outgoing packets, but OpenVPN never\n      compresses packets on send now.\n\n--auth alg\n  Authenticate data channel packets and (if enabled) ``tls-auth`` control\n  channel packets with HMAC using message digest algorithm ``alg``. (The\n  default is ``SHA1`` ). HMAC is a commonly used message authentication\n  algorithm (MAC) that uses a data string, a secure hash algorithm and a\n  key to produce a digital signature.\n\n  The OpenVPN data channel protocol uses encrypt-then-mac (i.e. first\n  encrypt a packet then HMAC the resulting ciphertext), which prevents\n  padding oracle attacks.\n\n  If an AEAD cipher mode (e.g. GCM) is chosen then the specified ``--auth``\n  algorithm is ignored for the data channel and the authentication method\n  of the AEAD cipher is used instead. Note that ``alg`` still specifies\n  the digest used for ``tls-auth``.\n\n  In static-key encryption mode, the HMAC key is included in the key file\n  generated by ``--genkey``. In TLS mode, the HMAC key is dynamically\n  generated and shared between peers via the TLS control channel. If\n  OpenVPN receives a packet with a bad HMAC it will drop the packet. HMAC\n  usually adds 16 or 20 bytes per packet. Set ``alg=none`` to disable\n  authentication.\n\n  For more information on HMAC see\n  https://tools.ietf.org/html/rfc2104\n\n--cipher alg\n  This option should not be used any longer in TLS mode and still\n  exists for two reasons:\n\n  * compatibility with old configurations still carrying it\n    around;\n\n  * allow users connecting to OpenVPN peers older than 2.6.0\n    to have ``--cipher`` configured the same way as the remote\n    counterpart. This can avoid MTU/frame size warnings.\n\n  Before 2.4.0, this option was used to select the cipher to be\n  configured on the data channel, however, later versions usually\n  ignored this directive in favour of a negotiated cipher.\n  Starting with 2.6.0, this option is always ignored in TLS mode\n  when it comes to configuring the cipher.\n\n  If you wish to specify the cipher to use on the data channel,\n  please see ``--data-ciphers`` (for regular negotiation) and\n  ``--data-ciphers-fallback`` (for a fallback option when the\n  negotiation cannot take place because the other peer is old or\n  has negotiation disabled).\n\n  To see ciphers that are available with OpenVPN, use the\n  ``--show-ciphers`` option.\n\n  Set ``alg`` to :code:`none` to disable encryption.\n\n--compress algorithm\n  **DEPRECATED** Enable a compression algorithm. Compression is generally\n  not recommended. VPN tunnels which use compression are susceptible to\n  the VORALCE attack vector. See also the :code:`migrate` parameter below.\n\n  The ``algorithm`` parameter may be :code:`lzo`, :code:`lz4`,\n  :code:`lz4-v2`, :code:`stub`, :code:`stub-v2`, :code:`migrate` or empty.\n  LZO and LZ4 are different compression algorithms, with LZ4 generally\n  offering the best performance with least CPU usage.\n\n  The :code:`lz4-v2` and :code:`stub-v2` variants implement a better\n  framing that does not add overhead when packets cannot be compressed. All\n  other variants always add one extra framing byte compared to no\n  compression framing.\n\n  Especially :code:`stub-v2` is essentially identical to no compression and\n  no compression framing as its header indicates IP version 5 in a tun setup\n  and can (ab)used to complete disable compression to clients. (See the\n  :code:`migrate` option below)\n\n  If the ``algorithm`` parameter is :code:`stub`, :code:`stub-v2` or empty,\n  compression will be turned off, but the packet framing for compression\n  will still be enabled, allowing a different setting to be pushed later.\n  Additionally, :code:`stub` and :code:`stub-v2` wil disable announcing\n  ``lzo`` and ``lz4`` compression support via *IV_* variables to the\n  server.\n\n  Note: the :code:`stub` (or empty) option is NOT compatible with the older\n  option ``--comp-lzo no``.\n\n  Using :code:`migrate` as compression algorithm enables a special migration mode.\n  It allows migration away from the ``--compress``/``--comp-lzo`` options to no compression.\n  This option sets the server to no compression mode and the server behaves identical to\n  a server without a compression option for all clients without a compression in their\n  config. However, if a client is detected that indicates that compression is used (via OCC),\n  the server will automatically add ``--push compress stub-v2`` to the client specific\n  configuration if supported by the client and otherwise switch to ``comp-lzo no``\n  and add ``--push comp-lzo`` to the client specific configuration.\n\n  ***Security Considerations***\n\n  Compression and encryption is a tricky combination. If an attacker knows\n  or is able to control (parts of) the plain-text of packets that contain\n  secrets, the attacker might be able to extract the secret if compression\n  is enabled. See e.g. the *CRIME* and *BREACH* attacks on TLS and\n  *VORACLE* on VPNs which also leverage to break encryption. If you are not\n  entirely sure that the above does not apply to your traffic, you are\n  advised to *not* enable compression.\n\n  For this reason compression support was removed from current versions\n  of OpenVPN. It will still decompress compressed packets received via\n  a VPN connection but it will never compress any outgoing packets.\n\n--comp-lzo mode\n  **DEPRECATED** Enable LZO compression algorithm.  Compression is\n  generally not recommended.  VPN tunnels which uses compression are\n  suspectible to the VORALCE attack vector.\n\n  Allows the other side of the connection to use LZO compression. Due\n  to difference in packet format this may add 1 additional byte per packet.\n  With current versions of OpenVPN no actual compression will happen.\n\n  ``mode`` may be :code:`yes`, :code:`no`, or :code:`adaptive`\n  but there is no actual change in behavior anymore.\n\n\n--comp-noadapt\n  **DEPRECATED** This option does not have any effect anymore since current\n  versions of OpenVPN never compress outgoing packets.\n\n--key-direction\n  Alternative way of specifying the optional direction parameter for the\n  ``--tls-auth`` option. Useful when using inline files\n  (See section on inline files).\n\n--data-ciphers cipher-list\n  Restrict the allowed ciphers to be negotiated to the ciphers in\n  ``cipher-list``. ``cipher-list`` is a colon-separated list of ciphers,\n  and defaults to :code:`AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305` when\n  Chacha20-Poly1305 is available and otherwise :code:`AES-256-GCM:AES-128-GCM`.\n\n  For servers, the first cipher from ``cipher-list`` that is also\n  supported by the client will be pushed to clients that support cipher\n  negotiation.\n\n  For more details see the chapter on `Data channel cipher negotiation`_.\n  *Especially* if you need to support clients with OpenVPN versions older\n  than 2.4!\n\n  Starting with OpenVPN 2.6 a cipher can be prefixed with a :code:`?` to mark\n  it as optional. This allows including ciphers in the list that may not be\n  available on all platforms.\n  E.g. :code:`AES-256-GCM:AES-128-GCM:?CHACHA20-POLY1305` would only enable\n  Chacha20-Poly1305 if the underlying SSL library (and its configuration)\n  supports it.\n\n  Starting with OpenVPN 2.7 the special keyword :code:`DEFAULT` can be used\n  in the string and is replaced by the default ciphers.  This can be used to\n  add an additional allowed cipher to the allowed ciphers, e.g.\n  :code:`DEFAULT:AES-192-CBC` to use the default ciphers but also allow\n  :code:`AES-192-CBC`.\n\n  Cipher negotiation is enabled in client-server mode only. I.e. if\n  ``--mode`` is set to ``server`` (server-side, implied by setting\n  ``--server`` ), or if ``--pull`` is specified (client-side, implied by\n  setting ``--client``).\n\n  If no common cipher is found during cipher negotiation, the connection\n  is terminated. To support old clients/old servers that do not provide any\n  cipher negotiation support see ``--data-ciphers-fallback``.\n\n  If ``--compat-mode`` is set to a version older than 2.5.0 the cipher\n  specified by ``--cipher`` will be appended to ``--data-ciphers`` if\n  not already present.\n\n  This list is restricted to be 127 chars long after conversion to OpenVPN\n  ciphers.\n\n  This option was called ``--ncp-ciphers`` in OpenVPN 2.4 but has been renamed\n  to ``--data-ciphers`` in OpenVPN 2.5 to more accurately reflect its meaning.\n\n--data-ciphers-fallback alg\n  Configure a cipher that is used to fall back to if we could not determine\n  which cipher the peer is willing to use.\n\n  This option should only be needed to\n  connect to peers that are running OpenVPN 2.3 or older versions, and\n  have been configured with ``--enable-small``\n  (typically used on routers or other embedded devices).\n\n\n--allow-deprecated-insecure-static-crypto\n   **DEPRECATED** This allow using OpenVPN without TLS. This is deprecated\n   and will be removed in OpenVPN 2.8.\n\n--tran-window n\n  Transition window -- our old key can live this many seconds after a new\n  a key renegotiation begins (default :code:`3600` seconds). This feature\n  allows for a graceful transition from old to new key, and removes the key\n  renegotiation sequence from the critical path of tunnel data forwarding.\n\n--force-tls-key-material-export\n  This option is only available in --mode server and forces to use\n  Keying Material Exporters (RFC 5705) for clients. This can be used to\n  simulate an environment where the cryptographic library does not support\n  the older method to generate data channel keys anymore. This option is\n  intended to be a test option and might be removed in a future OpenVPN\n  version without notice.\n"
  },
  {
    "path": "doc/man-sections/proxy-options.rst",
    "content": "--http-proxy args\n  Connect to remote host through an HTTP proxy.  This requires at least an\n  address ``server`` and ``port`` argument.  If HTTP Proxy-Authenticate\n  is required, a file name to an ``authfile`` file containing a username\n  and password on 2 lines can be given, or :code:`stdin` to prompt from\n  console. Its content can also be specified in the config file with the\n  ``--http-proxy-user-pass`` option (See `INLINE FILE SUPPORT`_).\n\n  The last optional argument is an ``auth-method`` which should be one\n  of :code:`none`, :code:`basic`.\n\n  HTTP Digest authentication is supported as well, but only via the\n  :code:`auto` or :code:`auto-nct` flags (below).  This must replace\n  the ``authfile`` argument.\n\n  The :code:`auto` flag causes OpenVPN to automatically determine the\n  ``auth-method`` and query stdin or the management interface for\n  username/password credentials, if required. This flag exists on OpenVPN\n  2.1 or higher.\n\n  The ``auto-nct`` flag (no clear-text auth) instructs OpenVPN to\n  automatically determine the authentication method, but to reject weak\n  authentication protocols such as HTTP Basic Authentication.\n\n  Examples:\n  ::\n\n     # no authentication\n     http-proxy proxy.example.net 3128\n     # basic authentication, load credentials from file\n     http-proxy proxy.example.net 3128 authfile.txt\n     # basic authentication, ask user for credentials\n     http-proxy proxy.example.net 3128 stdin\n     # determine which authentication is required, ask user for credentials\n     http-proxy proxy.example.net 3128 auto\n     # determine which authentication is required, but reject basic\n     http-proxy proxy.example.net 3128 auto-nct\n     # determine which authentication is required, but set credentials\n     http-proxy proxy.example.net 3128 auto\n     http-proxy-user-pass authfile.txt\n     # basic authentication, specify credentials inline\n     http-proxy proxy.example.net 3128 \"\" basic\n     <http-proxy-user-pass>\n     username\n     password\n     </http-proxy-user-pass>\n\n  Note that support for NTLMv1 proxies was removed with OpenVPN 2.7\n  and support for NTLMv2 proxies was removed with OpenVPN 2.8.\n\n--http-proxy-user-pass userpass\n  Overwrite the username/password information for ``--http-proxy``. If specified\n  as an inline option (see `INLINE FILE SUPPORT`_), it will be interpreted as\n  username/password separated by a newline. When specified on the command line\n  it is interpreted as a filename same as the third argument to ``--http-proxy``.\n\n  Example::\n\n    <http-proxy-user-pass>\n    username\n    password\n    </http-proxy-user-pass>\n\n--http-proxy-option args\n  Set extended HTTP proxy options. Requires an option ``type`` as argument\n  and an optional ``parameter`` to the type.  Repeat to set multiple\n  options.\n\n  :code:`VERSION` ``version``\n      Set HTTP version number to ``version`` (default :code:`1.0`).\n\n  :code:`AGENT` ``user-agent``\n      Set HTTP \"User-Agent\" string to ``user-agent``.\n\n  :code:`CUSTOM-HEADER` ``name`` ``content``\n      Adds the custom Header with ``name`` as name and ``content`` as\n      the content of the custom HTTP header.\n\n  Examples:\n  ::\n\n     http-proxy-option VERSION 1.1\n     http-proxy-option AGENT OpenVPN/2.4\n     http-proxy-option X-Proxy-Flag some-flags\n\n--socks-proxy args\n  Connect to remote host through a Socks5 proxy.  A required ``server``\n  argument is needed.  Optionally a ``port`` (default :code:`1080`) and\n  ``authfile`` can be given.  The ``authfile`` is a file containing a\n  username and password on 2 lines, or :code:`stdin` can be used to\n  prompt from console.\n"
  },
  {
    "path": "doc/man-sections/renegotiation.rst",
    "content": "Data Channel Renegotiation\n``````````````````````````\n\nWhen running OpenVPN in client/server mode, the data channel will use a\nseparate ephemeral encryption key which is rotated at regular intervals.\n\n--reneg-bytes n\n  Renegotiate data channel key after ``n`` bytes sent or received\n  (disabled by default with an exception, see below). OpenVPN allows the\n  lifetime of a key to be expressed as a number of bytes\n  encrypted/decrypted, a number of packets, or a number of seconds. A key\n  renegotiation will be forced if any of these three criteria are met by\n  either peer.\n\n  If using ciphers with cipher block sizes less than 128-bits,\n  ``--reneg-bytes`` is set to 64MB by default, unless it is explicitly\n  disabled by setting the value to :code:`0`, but this is\n  **HIGHLY DISCOURAGED** as this is designed to add some protection against\n  the SWEET32 attack vector. For more information see the ``--cipher``\n  option.\n\n  When data channel offload (DCO) is enabled, this option is ignored. DCO\n  does not support configurable renegotiation thresholds; automatic key\n  renegotiation mechanisms are sufficient for modern ciphers.\n\n--reneg-pkts n\n  Renegotiate data channel key after **n** packets sent and received\n  (disabled by default).\n\n  When data channel offload (DCO) is enabled, this option is ignored. DCO\n  does not support configurable renegotiation thresholds; automatic key\n  renegotiation mechanisms are sufficient for modern ciphers.\n\n--reneg-sec args\n  Renegotiate data channel key after at most ``max`` seconds\n  (default :code:`3600`) and at least ``min`` seconds (default is 90% of\n  ``max`` for servers, and equal to ``max`` for clients).\n  ::\n\n     reneg-sec max [min]\n\n  The effective ``--reneg-sec`` value used is per session\n  pseudo-uniform-randomized between ``min`` and ``max``.\n\n  With the default value of :code:`3600` this results in an effective per\n  session value in the range of :code:`3240` .. :code:`3600` seconds for\n  servers, or just 3600 for clients.\n\n  When using dual-factor authentication, note that this default value may\n  cause the end user to be challenged to reauthorize once per hour.\n\n  Also, keep in mind that this option can be used on both the client and\n  server, and whichever uses the lower value will be the one to trigger\n  the renegotiation. A common mistake is to set ``--reneg-sec`` to a\n  higher value on either the client or server, while the other side of the\n  connection is still using the default value of :code:`3600` seconds,\n  meaning that the renegotiation will still occur once per :code:`3600`\n  seconds. The solution is to increase --reneg-sec on both the client and\n  server, or set it to :code:`0` on one side of the connection (to\n  disable), and to your chosen value on the other side.\n"
  },
  {
    "path": "doc/man-sections/script-options.rst",
    "content": "SCRIPTING INTEGRATION\n=====================\n\nOpenVPN can execute external scripts in various phases of the lifetime of\nthe OpenVPN process.\n\n\nScript Order of Execution\n-------------------------\n\n#. ``--dns-updown``\n\n   Executed after TCP/UDP socket bind and TUN/TAP open, before ``--up``.\n\n#. ``--up``\n\n   Executed after TCP/UDP socket bind and TUN/TAP open, after ``--dns-updown``.\n\n#. ``--tls-verify``\n\n   Executed when we have a still untrusted remote peer.\n\n#. ``--ipchange``\n\n   Executed after connection authentication, or remote IP address change.\n\n#. ``--client-connect``\n\n   Executed in **--mode server** mode immediately after client\n   authentication.\n\n#. ``--route-up``\n\n   Executed after connection authentication, either immediately after, or\n   some number of seconds after as defined by the **--route-delay** option.\n\n#. ``--route-pre-down``\n\n   Executed right before the routes are removed.\n\n#. ``--client-disconnect``\n\n   Executed in ``--mode server`` mode on client instance shutdown.\n\n#. ``--dns-updown``\n\n   Executed before TCP/UDP and TUN/TAP close, before ``--down``.\n\n#. ``--down``\n\n   Executed after TCP/UDP and TUN/TAP close, after ``--dns-updown``.\n\n#. ``--learn-address``\n\n   Executed in ``--mode server`` mode whenever an IPv4 address/route or MAC\n   address is added to OpenVPN's internal routing table.\n\n#. ``--auth-user-pass-verify``\n\n   Executed in ``--mode server`` mode on new client connections, when the\n   client is still untrusted.\n\n#. ``--client-crresponse``\n\n    Execute in ``--mode server`` whenever a client sends a\n    :code:`CR_RESPONSE` message\n\nSCRIPT HOOKS\n------------\n\n--auth-user-pass-verify args\n  Require the client to provide a username/password (possibly in addition\n  to a client certificate) for authentication.\n\n  Valid syntax:\n  ::\n\n     auth-user-pass-verify cmd method\n\n  OpenVPN will run command ``cmd`` to validate the username/password\n  provided by the client.\n\n  ``cmd`` consists of a path to a script (or executable program), optionally\n  followed by arguments. The path and arguments may be single- or\n  double-quoted and/or escaped using a backslash, and should be separated\n  by one or more spaces.\n\n  If ``method`` is set to :code:`via-env`, OpenVPN will call ``cmd``\n  with the environmental variables :code:`username` and :code:`password`\n  set to the username/password strings provided by the client. *Beware*\n  that this method is insecure on some platforms which make the environment\n  of a process publicly visible to other unprivileged processes.\n\n  If ``method`` is set to :code:`via-file`, OpenVPN will write the username\n  and password to the first two lines of a temporary file. The filename\n  will be passed as an argument to ``cmd``, and the file will be\n  automatically deleted by OpenVPN after the script returns. The location\n  of the temporary file is controlled by the ``--tmp-dir`` option. For security,\n  consider setting it to a volatile storage medium such as :code:`/dev/shm` (if\n  available) to prevent the username/password file from touching the hard drive.\n\n  The script should examine the username and password, returning a success\n  exit code (:code:`0`) if the client's authentication request is to be\n  accepted, a failure code (:code:`1`) to reject the client, or a that\n  the authentication is deferred (:code:`2`). If the authentication is\n  deferred, the script must fork/start a background or another non-blocking\n  operation to continue the authentication in the background. When finshing\n  the authentication, a :code:`1` or :code:`0` must be written to the\n  file specified by the :code:`auth_control_file`.\n\n  If the file specified by :code:`auth_failed_reason_file` exists and has\n  non-empty content, the content of this file will be used as AUTH_FAILED\n  message. To avoid race conditions, this file should be written before\n  :code:`auth_control_file`.\n\n  This auth fail reason can be something simple like \"User has been permanently\n  disabled\" but there are also some special auth failed messages.\n\n  The ``TEMP`` message indicates that the authentication\n  temporarily failed and that the client should continue to retry to connect.\n  The server can optionally give a user readable message and hint the client a\n  behavior how to proceed. The keywords of the ``AUTH_FAILED,TEMP`` message\n  are comma separated keys/values and provide a hint to the client how to\n  proceed. Currently defined keywords are:\n\n  ``backoff`` :code:`s`\n        instructs the client to wait at least :code:`s` seconds before the next\n        connection attempt. If the client already uses a higher delay for\n        reconnection attempt, the delay will not be shortened.\n\n  ``advance addr``\n        Instructs the client to reconnect to the next (IP) address of the\n        current server.\n\n  ``advance remote``\n        Instructs the client to skip the remaining IP addresses of the current\n        server and instead connect to the next server specified in the\n        configuration file.\n\n  ``advance no``\n        Instructs the client to retry connecting to the same server again.\n\n  For example, the message ``TEMP[backoff 42,advance no]: No free IP addresses``\n  indicates that the VPN connection can currently not succeed and instructs\n  the client to retry in 42 seconds again.\n\n  When deferred authentication is in use, the script can also request\n  pending authentication by writing to the file specified by the\n  :code:`auth_pending_file`. The first line must be the timeout in\n  seconds, the required method on the second line (e.g. crtext) and\n  third line must be the EXTRA as documented in the\n  ``client-pending-auth`` section of ``doc/management.txt``.\n\n  This directive is designed to enable a plugin-style interface for\n  extending OpenVPN's authentication capabilities.\n\n  To protect against a client passing a maliciously formed username or\n  password string, the username string must consist only of these\n  characters: alphanumeric, underbar (':code:`_`'), dash (':code:`-`'),\n  dot (':code:`.`'), or at (':code:`@`'). The password string can consist\n  of any printable characters except for CR or LF. Any illegal characters\n  in either the username or password string will be converted to\n  underbar (':code:`_`').\n\n  Care must be taken by any user-defined scripts to avoid creating a\n  security vulnerability in the way that these strings are handled. Never\n  use these strings in such a way that they might be escaped or evaluated\n  by a shell interpreter.\n\n  For a sample script that performs PAM authentication, see\n  :code:`sample-scripts/auth-pam.pl` in the OpenVPN source distribution.\n\n--client-crresponse\n    Executed when the client sends a text based challenge response.\n\n    Valid syntax:\n    ::\n\n        client-crresponse cmd\n\n  OpenVPN will write the response of the client into a temporary file.\n  The filename will be passed as an argument to ``cmd``, and the file will\n  automatically deleted by OpenVPN after the script returns.\n\n  The response is passed as is from the client. The script needs to check\n  itself if the input is valid, e.g. if the input is valid base64 encoding.\n\n  The script can either directly write the result of the verification to\n  :code:`auth_control_file or further defer it. See ``--auth-user-pass-verify``\n  for details.\n\n  For a sample script that implement TOTP (RFC 6238) based two-factor\n  authentication, see :code:`sample-scripts/totpauth.py`.\n\n--client-connect cmd\n  Run command ``cmd`` on client connection.\n\n  ``cmd`` consists of a path to a script (or executable program), optionally\n  followed by arguments. The path and arguments may be single- or\n  double-quoted and/or escaped using a backslash, and should be separated\n  by one or more spaces.\n\n  The command is passed the common name and IP address of the\n  just-authenticated client as environmental variables (see environmental\n  variable section below). The command is also passed the pathname of a\n  freshly created temporary file as the last argument (after any arguments\n  specified in ``cmd`` ), to be used by the command to pass dynamically\n  generated config file directives back to OpenVPN.\n\n  If the script wants to generate a dynamic config file to be applied on\n  the server when the client connects, it should write it to the file\n  named by the last argument.\n\n  See the ``--client-config-dir`` option below for options which can be\n  legally used in a dynamically generated config file.\n\n  Note that the return value of ``script`` is significant. If ``script``\n  returns a non-zero error status, it will cause the client to be\n  disconnected.\n\n  If a ``--client-connect`` wants to defer the generating of the\n  configuration then the script needs to use the\n  :code:`client_connect_deferred_file` and\n  :code:`client_connect_config_file` environment variables, and write\n  status accordingly into these files.  See the `Environmental Variables`_\n  section for more details.\n\n--client-disconnect cmd\n  Like ``--client-connect`` but called on client instance shutdown. Will\n  not be called unless the ``--client-connect`` script and plugins (if\n  defined) were previously called on this instance with successful (0)\n  status returns.\n\n  The exception to this rule is if the ``--client-disconnect`` command or\n  plugins are cascaded, and at least one client-connect function\n  succeeded, then ALL of the client-disconnect functions for scripts and\n  plugins will be called on client instance object deletion, even in cases\n  where some of the related client-connect functions returned an error\n  status.\n\n  The ``--client-disconnect`` command is not passed any extra arguments\n  (only those arguments specified in cmd, if any).\n\n--dns-updown cmd\n  Run command ``cmd``, instead of the default DNS up/down command that comes\n  with openvpn. If ``cmd`` is ``disable`` the ``--dns-updown`` command is not run.\n\n  If you write your own command, please make sure to ignore ``--dns``\n  server profiles that cannot be applied. Port, DNSSEC and secure transport\n  settings need to be adhered to. If split DNS is not possible a full redirect\n  can be used as a fallback. If not all of the server addresses or search domains\n  can be configured, apply them in the order they are listed in.\n\n  Note that ``--dns-updown`` is not supported on all platforms. On Windows DNS\n  will always be set by the service. On Android DNS will be passed via management\n  interface.\n\n  Note that DNS-related ``--dhcp-option``\\ s might be converted so that they are\n  available to this hook if no ``--dns`` options exist. If any ``--dns server``\n  option is present, DNS-related ``--dhcp-option``\\ s will always be ignored.\n  If an ``--up`` script is defined, foreign_option env vars will be generated\n  from ``--dns`` options and passed to the script. The default ``--dns-updown``\n  command is not run if an ``--up`` script is defined. Both is done for backward\n  compatibility. In case you want to run the ``--dns-updown`` command even if\n  there is an ``--up`` defined, you can define a custom command or use ``force``\n  as ``cmd`` to run the default command. No DNS env vars will be passed to ``--up``\n  in this case.\n\n--down cmd\n  Run command ``cmd`` after TUN/TAP device close (post ``--user`` UID\n  change and/or ``--chroot`` ). ``cmd`` consists of a path to script (or\n  executable program), optionally followed by arguments. The path and\n  arguments may be single- or double-quoted and/or escaped using a\n  backslash, and should be separated by one or more spaces.\n\n  Called with the same parameters and environmental variables as the\n  ``--up`` option above.\n\n  Note that if you reduce privileges by using ``--user`` and/or\n  ``--group``, your ``--down`` script will also run at reduced privilege.\n\n--down-pre\n  Call ``--down`` cmd/script before, rather than after, TUN/TAP close.\n\n--ipchange cmd\n  Run command ``cmd`` when our remote ip-address is initially\n  authenticated or changes.\n\n  ``cmd`` consists of a path to a script (or executable program), optionally\n  followed by arguments. The path and arguments may be single- or\n  double-quoted and/or escaped using a backslash, and should be separated\n  by one or more spaces.\n\n  When ``cmd`` is executed two arguments are appended after any arguments\n  specified in ``cmd`` , as follows:\n  ::\n\n     cmd ip address port number\n\n  Don't use ``--ipchange`` in ``--mode server`` mode. Use a\n  ``--client-connect`` script instead.\n\n  See the `Environmental Variables`_ section below for additional\n  parameters passed as environmental variables.\n\n  If you are running in a dynamic IP address environment where the IP\n  addresses of either peer could change without notice, you can use this\n  script, for example, to edit the :code:`/etc/hosts` file with the current\n  address of the peer. The script will be run every time the remote peer\n  changes its IP address.\n\n  Similarly if *our* IP address changes due to DHCP, we should configure\n  our IP address change script (see man page for ``dhcpcd``\\(8)) to\n  deliver a ``SIGHUP`` or ``SIGUSR1`` signal to OpenVPN. OpenVPN will\n  then re-establish a connection with its most recently authenticated\n  peer on its new IP address.\n\n--learn-address cmd\n  Run command ``cmd`` to validate client virtual addresses or routes.\n\n  ``cmd`` consists of a path to a script (or executable program), optionally\n  followed by arguments. The path and arguments may be single- or\n  double-quoted and/or escaped using a backslash, and should be separated\n  by one or more spaces.\n\n  Three arguments will be appended to any arguments in ``cmd`` as follows:\n\n  :code:`$1` - [operation]\n      :code:`\"add\"`, :code:`\"update\"`, or :code:`\"delete\"` based on whether\n      or not the address is being added to, modified, or deleted from\n      OpenVPN's internal routing table.\n\n  :code:`$2` - [address]\n      The address being learned or unlearned. This can be an IPv4 address\n      such as :code:`\"198.162.10.14\"`, an IPv4 subnet such as\n      :code:`\"198.162.10.0/24\"`, or an ethernet MAC address (when\n      ``--dev tap`` is being used) such as :code:`\"00:FF:01:02:03:04\"`.\n\n  :code:`$3` - [common name]\n      The common name on the certificate associated with the client linked\n      to this address. Only present for :code:`\"add\"` or :code:`\"update\"`\n      operations, not :code:`\"delete\"`.\n\n  On :code:`\"add\"` or :code:`\"update\"` methods, if the script returns\n  a failure code (non-zero), OpenVPN will reject the address and will not\n  modify its internal routing table.\n\n  Normally, the ``cmd`` script will use the information provided above to\n  set appropriate firewall entries on the VPN TUN/TAP interface. Since\n  OpenVPN provides the association between virtual IP or MAC address and\n  the client's authenticated common name, it allows a user-defined script\n  to configure firewall access policies with regard to the client's\n  high-level common name, rather than the low level client virtual\n  addresses.\n\n--route-up cmd\n  Run command ``cmd`` after routes are added, subject to ``--route-delay``.\n\n  ``cmd`` consists of a path to a script (or executable program), optionally\n  followed by arguments. The path and arguments may be single- or\n  double-quoted and/or escaped using a backslash, and should be separated\n  by one or more spaces.\n\n  See the `Environmental Variables`_ section below for additional\n  parameters passed as environmental variables.\n\n--route-pre-down cmd\n  Run command ``cmd`` before routes are removed upon disconnection.\n\n  ``cmd`` consists of a path to a script (or executable program), optionally\n  followed by arguments. The path and arguments may be single- or\n  double-quoted and/or escaped using a backslash, and should be separated\n  by one or more spaces.\n\n  See the `Environmental Variables`_ section below for additional\n  parameters passed as environmental variables.\n\n--setenv args\n  Set a custom environmental variable :code:`name=value` to pass to script.\n\n  Valid syntaxes:\n  ::\n\n     setenv name value\n     setenv FORWARD_COMPATIBLE 1\n     setenv opt config_option\n\n  By setting :code:`FORWARD_COMPATIBLE` to :code:`1`, the config file\n  syntax checking is relaxed so that unknown directives will trigger a\n  warning but not a fatal error, on the assumption that a given unknown\n  directive might be valid in future OpenVPN versions.\n\n  This option should be used with caution, as there are good security\n  reasons for having OpenVPN fail if it detects problems in a config file.\n  Having said that, there are valid reasons for wanting new software\n  features to gracefully degrade when encountered by older software\n  versions.\n\n  It is also possible to tag a single directive so as not to trigger a\n  fatal error if the directive isn't recognized. To do this, prepend the\n  following before the directive: ``setenv opt``\n\n  Versions prior to OpenVPN 2.3.3 will always ignore options set with the\n  ``setenv opt`` directive.\n\n  See also ``--ignore-unknown-option``\n\n--setenv-safe args\n  Set a custom environmental variable :code:`OPENVPN_name` to :code:`value`\n  to pass to scripts.\n\n  Valid syntaxes:\n  ::\n\n     setenv-safe name value\n\n  This directive is designed to be pushed by the server to clients, and\n  the prepending of :code:`OPENVPN_` to the environmental variable is a\n  safety precaution to prevent a :code:`LD_PRELOAD` style attack from a\n  malicious or compromised server.\n\n--tls-verify cmd\n  Run command ``cmd`` to verify the X509 name of a pending TLS connection\n  that has otherwise passed all other tests of certification (except for\n  revocation via ``--crl-verify`` directive; the revocation test occurs\n  after the ``--tls-verify`` test).\n\n  ``cmd`` should return :code:`0` to allow the TLS handshake to proceed,\n  or :code:`1` to fail.\n\n  ``cmd`` consists of a path to a script (or executable program), optionally\n  followed by arguments. The path and arguments may be single- or\n  double-quoted and/or escaped using a backslash, and should be separated\n  by one or more spaces.\n\n  When ``cmd`` is executed two arguments are appended after any arguments\n  specified in ``cmd``, as follows:\n  ::\n\n     cmd certificate_depth subject\n\n  These arguments are, respectively, the current certificate depth and the\n  X509 subject distinguished name (dn) of the peer.\n\n  This feature is useful if the peer you want to trust has a certificate\n  which was signed by a certificate authority who also signed many other\n  certificates, where you don't necessarily want to trust all of them, but\n  rather be selective about which peer certificate you will accept. This\n  feature allows you to write a script which will test the X509 name on a\n  certificate and decide whether or not it should be accepted. For a\n  simple perl script which will test the common name field on the\n  certificate, see the file ``verify-cn`` in the OpenVPN distribution.\n\n  See the `Environmental Variables`_ section below for additional\n  parameters passed as environmental variables.\n\n--tls-export-cert dir\n  Adds an environment variable ``peer_cert`` when calling the\n  ``--tls-verify`` script or executing the OPENVPN_PLUGIN_TLS_VERIFY plugin\n  hook to verify the certificate.\n\n  The environment variable contains the path to a PEM encoded certificate\n  of the current peer certificate in the directory ``dir``.\n\n--up cmd\n  Run command ``cmd`` after successful TUN/TAP device open (pre ``--user``\n  UID change).\n\n  ``cmd`` consists of a path to a script (or executable program), optionally\n  followed by arguments. The path and arguments may be single- or\n  double-quoted and/or escaped using a backslash, and should be separated\n  by one or more spaces.\n\n  The up command is useful for specifying route commands which route IP\n  traffic destined for private subnets which exist at the other end of the\n  VPN connection into the tunnel.\n\n  For ``--dev tun`` execute as:\n  ::\n\n      cmd tun_dev tun_mtu 0 ifconfig_local_ip ifconfig_remote_ip [init | restart]\n\n  For ``--dev tap`` execute as:\n  ::\n\n       cmd tap_dev tap_mtu 0 ifconfig_local_ip ifconfig_netmask [init | restart]\n\n  See the `Environmental Variables`_ section below for additional\n  parameters passed as environmental variables.  The ``0`` argument\n  used to be ``link_mtu`` which is no longer passed to scripts - to\n  keep the argument order, it was replaced with ``0``.\n\n  Note that if ``cmd`` includes arguments, all OpenVPN-generated arguments\n  will be appended to them to build an argument list with which the\n  executable will be called.\n\n  Typically, ``cmd`` will run a script to add routes to the tunnel.\n\n  Normally the up script is called after the TUN/TAP device is opened. In\n  this context, the last command line parameter passed to the script will\n  be *init.* If the ``--up-restart`` option is also used, the up script\n  will be called for restarts as well. A restart is considered to be a\n  partial reinitialization of OpenVPN where the TUN/TAP instance is\n  preserved (the ``--persist-tun`` option will enable such preservation).\n  A restart can be generated by a SIGUSR1 signal, a ``--ping-restart``\n  timeout, or a connection reset when the TCP protocol is enabled with the\n  ``--proto`` option. If a restart occurs, and ``--up-restart`` has been\n  specified, the up script will be called with *restart* as the last\n  parameter.\n\n  *NOTE:*\n     On restart, OpenVPN will not pass the full set of environment\n     variables to the script. Namely, everything related to routing and\n     gateways will not be passed, as nothing needs to be done anyway - all\n     the routing setup is already in place. Additionally, the up-restart\n     script will run with the downgraded UID/GID settings (if configured).\n\n  The following standalone example shows how the ``--up`` script can be\n  called in both an initialization and restart context. (*NOTE:* for\n  security reasons, don't run the following example unless UDP port 9999\n  is blocked by your firewall. Also, the example will run indefinitely, so\n  you should abort with control-c).\n\n  ::\n\n      openvpn --dev tun --port 9999 --verb 4 --ping-restart 10 \\\n              --up 'echo up' --down 'echo down' --persist-tun  \\\n              --up-restart\n\n  Note that OpenVPN also provides the ``--ifconfig`` option to\n  automatically ifconfig the TUN device, eliminating the need to define an\n  ``--up`` script, unless you also want to configure routes in the\n  ``--up`` script.\n\n  If ``--ifconfig`` is also specified, OpenVPN will pass the ifconfig\n  local and remote endpoints on the command line to the ``--up`` script so\n  that they can be used to configure routes such as:\n\n  ::\n\n      route add -net 10.0.0.0 netmask 255.255.255.0 gw $5\n\n--up-delay\n  Delay TUN/TAP open and possible ``--up`` script execution until after\n  TCP/UDP connection establishment with peer.\n\n  In ``--proto udp`` mode, this option normally requires the use of\n  ``--ping`` to allow connection initiation to be sensed in the absence of\n  tunnel data, since UDP is a \"connectionless\" protocol.\n\n  On Windows, this option will delay the TAP-Win32 media state\n  transitioning to \"connected\" until connection establishment, i.e. the\n  receipt of the first authenticated packet from the peer.\n\n--up-restart\n  Enable the ``--up`` and ``--down`` scripts to be called for restarts as\n  well as initial program start. This option is described more fully above\n  in the ``--up`` option documentation.\n\nString Types and Remapping\n--------------------------\n\nIn certain cases, OpenVPN will perform remapping of characters in\nstrings. Essentially, any characters outside the set of permitted\ncharacters for each string type will be converted to underbar ('\\_').\n\n*Q: Why is string remapping necessary?*\n    It's an important security feature to prevent the malicious\n    coding of strings from untrusted sources to be passed as parameters to\n    scripts, saved in the environment, used as a common name, translated to\n    a filename, etc.\n\n*Q: Can string remapping be disabled?*\n    No.  The options ``--no-name-remapping`` and ``--compat-names`` have\n    been removed in 2.5 because they were considered too insecure.\n\nHere is a brief rundown of OpenVPN's current string types and the\npermitted character class for each string:\n\n*X509 Names*\n   Alphanumeric, underbar ('\\_'), dash ('-'), dot ('.'), at\n   ('@'), colon (':'), slash ('/'), and equal ('='). Alphanumeric is\n   defined as a character which will cause the C library isalnum() function\n   to return true.\n\n*Common Names*\n   Alphanumeric, underbar ('\\_'), dash ('-'), dot ('.'), and at ('@').\n\n*--auth-user-pass username*\n   Same as Common Name, with one exception:\n   The username is passed to the\n   :code:`OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY` plugin in its raw form,\n   without string remapping.\n\n*--auth-user-pass password*\n   Any \"printable\" character except CR or LF. Printable is defined to be\n   a character which will cause the C library isprint() function to\n   return true.\n\n*--client-config-dir filename as derived from common name or username*\n   Alphanumeric, underbar ('\\_'), dash ('-'), at ('@'), and dot ('.')\n   except for \".\" or \"..\" as standalone strings.\n\n*Environmental variable names*\n   Alphanumeric or underbar ('\\_').\n\n*Environmental variable values*\n   Any printable character.\n\nFor all cases, characters in a string which are not members of the legal\ncharacter class for that string type will be remapped to underbar\n('\\_').  \n\n\nEnvironmental Variables\n-----------------------\n\nOnce set, a variable is persisted indefinitely until it is reset by a\nnew value or a restart,\n\nIn server mode, environmental variables set by\nOpenVPN are scoped according to the client objects they are associated\nwith, so there should not be any issues with scripts having access to\nstale, previously set variables which refer to different client\ninstances.\n\n:code:`bytes_received`\n    Total number of bytes received from client during VPN session. Set prior\n    to execution of the ``--client-disconnect`` script.\n\n:code:`bytes_sent`\n    Total number of bytes sent to client during VPN session. Set prior to\n    execution of the ``--client-disconnect`` script.\n\n:code:`client_connect_config_file`\n    The path to the configuration file that should be written to by the\n    ``--client-connect`` script (optional, if per-session configuration\n    is desired).  This is the same file name as passed via command line\n    argument on the call to the ``--client-connect`` script.\n\n:code:`client_connect_deferred_file`\n    This file can be optionally written to in order to to communicate a\n    status code of the ``--client-connect`` script or plgin.  Only the\n    first character in the file is relevant.  It must be either :code:`1`\n    to indicate normal script execution, :code:`0` indicates an error (in\n    the same way that a non zero exit status does) or :code:`2` to indicate\n    that the script deferred returning the config file.\n\n    For deferred (background) handling, the script or plugin MUST write\n    :code:`2` to the file to indicate the deferral and then return with\n    exit code :code:`0` to signal ``deferred handler started OK``.\n\n    A background process or similar must then take care of writing the\n    configuration to the file indicated by the\n    :code:`client_connect_config_file` environment variable and when\n    finished, write the a :code:`1` to this file (or :code:`0` in case of\n    an error).\n\n    The absence of any character in the file when the script finishes\n    executing is interpreted the same as :code:`1`. This allows scripts\n    that are not written to support the defer mechanism to be used\n    unmodified.\n\n:code:`common_name`\n    The X509 common name of an authenticated client. Set prior to execution\n    of ``--client-connect``, ``--client-disconnect`` and\n    ``--auth-user-pass-verify`` scripts.\n\n:code:`config`\n    Name of first ``--config`` file. Set on program initiation and reset on\n    SIGHUP.\n\n\n:code:`daemon`\n    Set to \"1\" if the ``--daemon`` directive is specified, or \"0\" otherwise.\n    Set on program initiation and reset on SIGHUP.\n\n:code:`daemon_log_redirect`\n    Set to \"1\" if the ``--log`` or ``--log-append`` directives are\n    specified, or \"0\" otherwise. Set on program initiation and reset on\n    SIGHUP.\n\n:code:`dev`\n    The actual name of the TUN/TAP device, including a unit number if it\n    exists. Set prior to ``--up`` or ``--down`` script execution.\n\n:code:`dev_idx`\n    On Windows, the device index of the TUN/TAP adapter (to be used in\n    netsh.exe calls which sometimes just do not work right with interface\n    names). Set prior to ``--up`` or ``--down`` script execution.\n\n:code:`dns_*`\n    The ``--dns`` configuration options will be made available to ``--dns-updown``\n    execution through this set of environment variables. Variables appear\n    only if the corresponding option has a value assigned. For the semantics\n    of each individual variable, please refer to the documentation for ``--dns``.\n\n    ::\n\n       dns_search_domain_{n}\n       dns_server_{n}_address_{m}\n       dns_server_{n}_port_{m}\n       dns_server_{n}_resolve_domain_{m}\n       dns_server_{n}_dnssec\n       dns_server_{n}_transport\n       dns_server_{n}_sni\n\n:code:`foreign_option_{n}`\n    An option pushed via ``--push`` to a client which does not natively\n    support it, such as ``--dhcp-option`` on a non-Windows system, will be\n    recorded to this environmental variable sequence prior to ``--up``\n    script execution.\n\n:code:`ifconfig_ipv6_local`\n    The local VPN endpoint IPv6 address specified in the\n    ``--ifconfig-ipv6`` option (first parameter). Set prior to OpenVPN\n    calling the :code:`ifconfig` or code:`netsh` (windows version of\n    ifconfig) commands which normally occurs prior to ``--up`` script\n    execution.\n\n:code:`ifconfig_ipv6_netbits`\n    The prefix length of the IPv6 network on the VPN interface. Derived\n    from the /nnn parameter of the IPv6 address in the ``--ifconfig-ipv6``\n    option (first parameter). Set prior to OpenVPN calling the\n    :code:`ifconfig` or :code:`netsh` (windows version of ifconfig)\n    commands which normally occurs prior to ``--up`` script execution.\n\n:code:`ifconfig_ipv6_remote`\n    The remote VPN endpoint IPv6 address specified in the\n    ``--ifconfig-ipv6`` option (second parameter). Set prior to OpenVPN\n    calling the :code:`ifconfig` or :code:`netsh` (windows version of\n    ifconfig) commands which normally occurs prior to ``--up`` script\n    execution.\n\n:code:`ifconfig_local`\n    The local VPN endpoint IP address specified in the ``--ifconfig``\n    option (first parameter). Set prior to OpenVPN calling the\n    :code:`ifconfig` or :code:`netsh` (windows version of ifconfig)\n    commands which normally occurs prior to ``--up`` script execution.\n\n:code:`ifconfig_remote`\n    The remote VPN endpoint IP address specified in the ``--ifconfig``\n    option (second parameter) when ``--dev tun`` is used. Set prior to\n    OpenVPN calling the :code:`ifconfig` or :code:`netsh` (windows version\n    of ifconfig) commands which normally occurs prior to ``--up`` script\n    execution.\n\n:code:`ifconfig_netmask`\n    The subnet mask of the virtual ethernet segment that is specified as\n    the second parameter to ``--ifconfig`` when ``--dev tap`` is being\n    used. Set prior to OpenVPN calling the :code:`ifconfig` or\n    :code:`netsh` (windows version of ifconfig) commands which normally\n    occurs prior to ``--up`` script execution.\n\n:code:`ifconfig_pool_local_ip`\n    The local virtual IPv4 address for the TUN/TAP tunnel taken from an\n    ``--ifconfig-push`` directive if specified, or otherwise from the\n    ifconfig pool (controlled by the ``--ifconfig-pool`` config file\n    directive). Only set for ``--dev tun`` tunnels. This option is set on\n    the server prior to execution of the ``--client-connect`` and\n    ``--client-disconnect`` scripts.\n\n:code:`ifconfig_pool_local_ip6`\n    The local virtual IPv6 address for the TUN/TAP tunnel taken from an\n    ``--ifconfig-ipv6-push`` directive if specified, or otherwise from the\n    ifconfig pool (controlled by the ``--ifconfig-ipv6-pool`` config file\n    directive). Only set for ``--dev tun`` tunnels. This option is set on\n    the server prior to execution of the ``--client-connect`` and\n    ``--client-disconnect`` scripts.\n\n:code:`ifconfig_pool_netmask`\n    The virtual IPv4 netmask for the TUN/TAP tunnel taken from an\n    ``--ifconfig-push`` directive if specified, or otherwise from the\n    ifconfig pool (controlled by the ``--ifconfig-pool`` config file\n    directive). Only set for ``--dev tap`` tunnels. This option is set on\n    the server prior to execution of the ``--client-connect`` and\n    ``--client-disconnect`` scripts.\n\n:code:`ifconfig_pool_ip6_netbits`\n    The virtual IPv6 prefix length for the TUN/TAP tunnel taken from an\n    ``--ifconfig-ipv6-push`` directive if specified, or otherwise from the\n    ifconfig pool (controlled by the ``--ifconfig-ipv6-pool`` config file\n    directive). Only set for ``--dev tap`` tunnels. This option is set on\n    the server prior to execution of the ``--client-connect`` and\n    ``--client-disconnect`` scripts.\n\n:code:`ifconfig_pool_remote_ip`\n    The remote virtual IPv4 address for the TUN/TAP tunnel taken from an\n    ``--ifconfig-push`` directive if specified, or otherwise from the\n    ifconfig pool (controlled by the ``--ifconfig-pool`` config file\n    directive). This option is set on the server prior to execution of the\n    ``--client-connect`` and ``--client-disconnect`` scripts.\n\n:code:`ifconfig_pool_remote_ip6`\n    The remote virtual IPv6 address for the TUN/TAP tunnel taken from an\n    ``--ifconfig-ipv6-push`` directive if specified, or otherwise from the\n    ifconfig pool (controlled by the ``--ifconfig-ipv6-pool`` config file\n    directive). This option is set on the server prior to execution of the\n    ``--client-connect`` and ``--client-disconnect`` scripts.\n\n:code:`link_mtu`\n    *REMOVED* No longer passed to scripts since OpenVPN 2.6.0.  Used to be the\n    maximum packet size (not including the IP header) of tunnel data in\n    UDP tunnel transport mode.\n\n:code:`local`\n    The ``--local`` parameter. Set on program initiation and reset on\n    SIGHUP.\n\n:code:`local_port`\n    The local port number or name, specified by ``--port`` or ``--lport``.\n    Set on program initiation and reset on SIGHUP.\n\n:code:`password`\n    The password provided by a connecting client. Set prior to\n    ``--auth-user-pass-verify`` script execution only when the ``via-env``\n    modifier is specified, and deleted from the environment after the script\n    returns.\n\n:code:`peer_cert`\n    If the option ``--tls-export-cert`` is enabled, this option contains\n    the path to the current peer certificate to be verified in PEM format.\n    See also the argument certificate_depth to the ``--tls-verify`` command.\n\n:code:`proto`\n    The ``--proto`` parameter. Set on program initiation and reset on\n    SIGHUP.\n\n:code:`remote_{n}`\n    The ``--remote`` parameter. Set on program initiation and reset on\n    SIGHUP.\n\n:code:`remote_port_{n}`\n    The remote port number, specified by ``--port`` or ``--rport``. Set on\n    program initiation and reset on SIGHUP.\n\n:code:`route_net_gateway`\n    The pre-existing default IP gateway in the system routing table. Set\n    prior to ``--up`` script execution.\n\n:code:`route_vpn_gateway`\n    The default gateway used by ``--route`` options, as specified in either\n    the ``--route-gateway`` option or the second parameter to\n    ``--ifconfig`` when ``--dev tun`` is specified. Set prior to ``--up``\n    script execution.\n\n:code:`route_{parm}_{n}`\n    A set of variables which define each route to be added, and are set\n    prior to ``--up`` script execution.\n\n    ``parm`` will be one of :code:`network`, :code:`netmask\"`,\n    :code:`gateway`, or :code:`metric`.\n\n    ``n`` is the OpenVPN route number, starting from 1.\n\n    If the network or gateway are resolvable DNS names, their IP address\n    translations will be recorded rather than their names as denoted on the\n    command line or configuration file.\n\n:code:`route_ipv6_{parm}_{n}`\n    A set of variables which define each IPv6 route to be added, and are\n    set prior to ``--up`` script execution.\n\n    ``parm`` will be one of :code:`network`, :code:`gateway` or\n    :code:`metric`. ``route_ipv6_network_{n}`` contains :code:`netmask`\n    as :code:`/nnn`, unlike IPv4 where it is passed in a separate environment\n    variable.\n\n    ``n`` is the OpenVPN route number, starting from 1.\n\n    If the network or gateway are resolvable DNS names, their IP address\n    translations will be recorded rather than their names as denoted on the\n    command line or configuration file.\n\n:code:`route_redirect_gateway_ipv4`\n\n:code:`route_redirect_gateway_ipv6`\n    Set to :code:`1` if the corresponding default gateway should be redirected\n    into the tunnel, and to :code:`2` if also the local LAN segment should be\n    blocked (:code:`block-local`).  Not set otherwise.  Set prior to ``--up`` script\n    execution.\n\n:code:`script_context`\n    Set to \"init\" or \"restart\" prior to up/down script execution. For more\n    information, see documentation for ``--up``.\n\n:code:`script_type`\n    Prior to execution of any script, this variable is set to the type of\n    script being run. It can be one of the following: :code:`up`,\n    :code:`down`, :code:`ipchange`, :code:`route-up`, :code:`tls-verify`,\n    :code:`auth-user-pass-verify`, :code:`client-connect`,\n    :code:`client-disconnect` or :code:`learn-address`. Set prior to\n    execution of any script.\n\n:code:`signal`\n    The reason for exit or restart. Can be one of :code:`sigusr1`,\n    :code:`sighup`, :code:`sigterm`, :code:`sigint`, :code:`inactive`\n    (controlled by ``--inactive`` option), :code:`ping-exit` (controlled\n    by ``--ping-exit`` option), :code:`ping-restart` (controlled by\n    ``--ping-restart`` option), :code:`connection-reset` (triggered on TCP\n    connection reset), :code:`error` or :code:`unknown` (unknown signal).\n    This variable is set just prior to down script execution.\n\n:code:`time_ascii`\n    Client connection timestamp, formatted as a human-readable time string.\n    Set prior to execution of the ``--client-connect`` script.\n\n:code:`time_duration`\n    The duration (in seconds) of the client session which is now\n    disconnecting. Set prior to execution of the ``--client-disconnect``\n    script.\n\n:code:`time_unix`\n    Client connection timestamp, formatted as a unix integer date/time\n    value. Set prior to execution of the ``--client-connect`` script.\n\n:code:`tls_digest_{n}` / :code:`tls_digest_sha256_{n}`\n    Contains the certificate SHA1 / SHA256 fingerprint, where ``n`` is the\n    verification level. Only set for TLS connections. Set prior to execution\n    of ``--tls-verify`` script.\n\n:code:`tls_id_{n}`\n    A series of certificate fields from the remote peer, where ``n`` is the\n    verification level. Only set for TLS connections. Set prior to execution\n    of ``--tls-verify`` script.\n\n:code:`tls_serial_{n}`\n    The serial number of the certificate from the remote peer, where ``n``\n    is the verification level. Only set for TLS connections. Set prior to\n    execution of ``--tls-verify`` script. This is in the form of a decimal\n    string like \"933971680\", which is suitable for doing serial-based OCSP\n    queries (with OpenSSL, do not prepend \"0x\" to the string) If something\n    goes wrong while reading the value from the certificate it will be an\n    empty string, so your code should check that. See the\n    :code:`contrib/OCSP_check/OCSP_check.sh` script for an example.\n\n:code:`tls_serial_hex_{n}`\n    Like :code:`tls_serial_{n}`, but in hex form (e.g.\n    :code:`12:34:56:78:9A`).\n\n:code:`tun_mtu`\n    The MTU of the TUN/TAP device. Set prior to ``--up`` or ``--down``\n    script execution.\n\n:code:`trusted_ip` / :code:`trusted_ip6`)\n    Actual IP address of connecting client or peer which has been\n    authenticated. Set prior to execution of ``--ipchange``,\n    ``--client-connect`` and ``--client-disconnect`` scripts. If using ipv6\n    endpoints (udp6, tcp6), :code:`trusted_ip6` will be set instead.\n\n:code:`trusted_port`\n    Actual port number of connecting client or peer which has been\n    authenticated. Set prior to execution of ``--ipchange``,\n    ``--client-connect`` and ``--client-disconnect`` scripts.\n\n:code:`untrusted_ip` / :code:`untrusted_ip6`\n    Actual IP address of connecting client or peer which has not been\n    authenticated yet. Sometimes used to *nmap* the connecting host in a\n    ``--tls-verify`` script to ensure it is firewalled properly. Set prior\n    to execution of ``--tls-verify`` and ``--auth-user-pass-verify``\n    scripts. If using ipv6 endpoints (udp6, tcp6), :code:`untrusted_ip6`\n    will be set instead.\n\n:code:`untrusted_port`\n    Actual port number of connecting client or peer which has not been\n    authenticated yet. Set prior to execution of ``--tls-verify`` and\n    ``--auth-user-pass-verify`` scripts.\n\n:code:`username`\n    The username provided by a connecting client. Set prior to\n    ``--auth-user-pass-verify`` script execution only when the\n    :code:`via-env` modifier is specified.\n\n:code:`X509_{n}_{subject_field}`\n    An X509 subject field from the remote peer certificate, where ``n`` is\n    the verification level. Only set for TLS connections. Set prior to\n    execution of ``--tls-verify`` script. This variable is similar to\n    :code:`tls_id_{n}` except the component X509 subject fields are broken\n    out, and no string remapping occurs on these field values (except for\n    remapping of control characters to \":code:`_`\"). For example, the\n    following variables would be set on the OpenVPN server using the sample\n    client certificate in sample-keys (client.crt). Note that the\n    verification level is 0 for the client certificate and 1 for the CA\n    certificate.\n\n    You can use the ``--x509-track`` option to export more or less information\n    from the certificates.\n\n    ::\n\n       X509_0_emailAddress=me@myhost.mydomain\n       X509_0_CN=Test-Client\n       X509_0_O=OpenVPN-TEST\n       X509_0_ST=NA\n       X509_0_C=KG\n       X509_1_emailAddress=me@myhost.mydomain\n       X509_1_O=OpenVPN-TEST\n       X509_1_L=BISHKEK\n       X509_1_ST=NA\n       X509_1_C=KG\n"
  },
  {
    "path": "doc/man-sections/server-options.rst",
    "content": "Server Options\n--------------\nStarting with OpenVPN 2.0, a multi-client TCP/UDP server mode is\nsupported, and can be enabled with the ``--mode server`` option. In\nserver mode, OpenVPN will listen on a single port for incoming client\nconnections. All client connections will be routed through a single tun\nor tap interface. This mode is designed for scalability and should be\nable to support hundreds or even thousands of clients on sufficiently\nfast hardware. SSL/TLS authentication must be used in this mode.\n\n--auth-gen-token args\n  Returns an authentication token to successfully authenticated clients.\n\n  Valid syntax:\n  ::\n\n     auth-gen-token [lifetime] [renewal-time] [external-auth]\n\n  After successful user/password authentication, the OpenVPN server will\n  with this option generate a temporary authentication token and push that\n  to the client. On the following renegotiations, the OpenVPN client will pass\n  this token instead of the users password. On the server side the server\n  will do the token authentication internally and it will NOT do any\n  additional authentications against configured external user/password\n  authentication mechanisms.\n\n  The tokens implemented by this mechanism include an initial timestamp and\n  a renew timestamp and are secured by HMAC.\n\n  The ``lifetime`` argument defines how long the generated token is valid.\n  The lifetime is defined in seconds. If lifetime is not set or it is set\n  to :code:`0`, the token will never expire.\n\n  If ``renewal-time`` is not set it defaults to ``reneg-sec``.\n\n\n  The token will expire either after the configured ``lifetime`` of the\n  token is reached or after not being renewed for more than 2 \\*\n  ``renewal-time`` seconds. Clients will be sent renewed tokens on every TLS\n  renegotiation. If ``renewal-time`` is lower than ``reneg-sec`` the server\n  will push an  updated temporary authentication token every ``reneweal-time``\n  seconds. This is done to invalidate a token if a client is disconnected for a\n  sufficiently long time, while at the same time permitting much longer token\n  lifetimes for active clients.\n\n  This feature is useful for environments which are configured to use One\n  Time Passwords (OTP) as part of the user/password authentications and\n  that authentication mechanism does not implement any auth-token support.\n\n  When the :code:`external-auth` keyword is present the normal\n  authentication method will always be called even if auth-token succeeds.\n  Normally other authentications method are skipped if auth-token\n  verification succeeds or fails.\n\n  This option postpones this decision to the external authentication\n  methods and checks the validity of the account and do other checks.\n\n  In this mode the environment will have a ``session_id`` variable that\n  holds the session id from auth-gen-token. Also an environment variable\n  ``session_state`` is present. This variable indicates whether the\n  auth-token has succeeded or not. It can have the following values:\n\n  :code:`Initial`\n      No token from client.\n\n  :code:`Authenticated`\n      Token is valid and not expired.\n\n  :code:`Expired`\n      Token is valid but has expired.\n\n  :code:`Invalid`\n      Token is invalid (failed HMAC or wrong length)\n\n  :code:`AuthenticatedEmptyUser` / :code:`ExpiredEmptyUser`\n      The token is not valid with the username sent from the client but\n      would be valid (or expired) if we assume an empty username was\n      used instead.  These two cases are a workaround for behaviour in\n      OpenVPN 3.  If this workaround is not needed these two cases should\n      be handled in the same way as :code:`Invalid`.\n\n  **Warning:** Use this feature only if you want your authentication\n  method called on every verification. Since the external authentication\n  is called it needs to also indicate a success or failure of the\n  authentication. It is strongly recommended to return an authentication\n  failure in the case of the Invalid/Expired auth-token with the\n  external-auth option unless the client could authenticate in another\n  acceptable way (e.g. client certificate), otherwise returning success\n  will lead to authentication bypass (as does returning success on a wrong\n  password from a script).\n\n  **Note:** the username for ``--auth-gen-token`` can be overridden by\n  ``--override-username``. In this case the client will be pushed also the\n  ``--auth-token-user`` option and an auth token that is valid for that\n  username instead of the original username that the client authenticated\n  with.\n\n--auth-gen-token-secret file\n  Specifies a file that holds a secret for the HMAC used in\n  ``--auth-gen-token`` If ``file`` is not present OpenVPN will generate a\n  random secret on startup. This file should be used if auth-token should\n  validate after restarting a server or if client should be able to roam\n  between multiple OpenVPN servers with their auth-token.\n\n--auth-user-pass-optional\n  Allow connections by clients that do not specify a username/password.\n  Normally, when ``--auth-user-pass-verify`` or\n  ``--management-client-auth`` are specified (or an authentication plugin\n  module), the OpenVPN server daemon will require connecting clients to\n  specify a username and password. This option makes the submission of a\n  username/password by clients optional, passing the responsibility to the\n  user-defined authentication module/script to accept or deny the client\n  based on other factors (such as the setting of X509 certificate fields).\n  When this option is used, and a connecting client does not submit a\n  username/password, the user-defined authentication module/script will\n  see the username and password as being set to empty strings (\"\"). The\n  authentication module/script MUST have logic to detect this condition\n  and respond accordingly.\n\n--ccd-exclusive\n  Require, as a condition of authentication, that a connecting client has\n  a ``--client-config-dir`` file.\n\n--client-config-dir dir\n  Specify a directory ``dir`` for custom client config files. After a\n  connecting client has been authenticated, OpenVPN will look in this\n  directory for a file having the same name as the client's X509 common\n  name. If a matching file exists, it will be opened and parsed for\n  client-specific configuration options. If no matching file is found,\n  OpenVPN will instead try to open and parse a default file called\n  \"DEFAULT\", which may be provided but is not required. Note that the\n  configuration files must be readable by the OpenVPN process after it has\n  dropped it's root privileges.\n\n  This file can specify a fixed IP address for a given client using\n  ``--ifconfig-push``, as well as fixed subnets owned by the client using\n  ``--iroute``.\n\n  One of the useful properties of this option is that it allows client\n  configuration files to be conveniently created, edited, or removed while\n  the server is live, without needing to restart the server.\n\n  The following options are legal in a client-specific context: ``--push``,\n  ``--push-reset``, ``--push-remove``, ``--iroute``, ``--ifconfig-push``,\n  ``--vlan-pvid`` and ``--config``.\n\n  **Note:** OpenVPN uses the CN exactly as written in the certificate.\n  But since this is a file access the filesystem might interfere.\n  Importantly OpenVPN will consider two CNs that only differ in case as\n  different names but a case-insensitive filesystem (like you might\n  encounter on Windows or macOS) will treat them as the same. When you\n  generate your certificates make sure that the CNs are sufficiently\n  different to not cause issues. When trusting an external CA note that\n  this is a potential attack vector via maliciously generated\n  certificates that exploit this issue.\n\n--client-to-client\n  Because the OpenVPN server mode handles multiple clients through a\n  single tun or tap interface, it is effectively a router. The\n  ``--client-to-client`` flag tells OpenVPN to internally route\n  client-to-client traffic rather than pushing all client-originating\n  traffic to the TUN/TAP interface.\n\n  When this option is used, each client will \"see\" the other clients which\n  are currently connected. Otherwise, each client will only see the\n  server. Don't use this option if you want to firewall tunnel traffic\n  using custom, per-client rules.\n\n  Please note that when using data channel offload this option has no\n  effect. Packets are always sent to the tunnel interface and then\n  routed based on the system routing table.\n\n--disable\n  Disable a particular client (based on the common name) from connecting.\n  Don't use this option to disable a client due to key or password\n  compromise. Use a CRL (certificate revocation list) instead (see the\n  ``--crl-verify`` option).\n\n  This option must be associated with a specific client instance, which\n  means that it must be specified either in a client instance config file\n  using ``--client-config-dir`` or dynamically generated using a\n  ``--client-connect`` script.\n\n--connect-freq args\n  Allow a maximum of ``n`` new connections per ``sec`` seconds from\n  clients.\n\n  Valid syntax:\n  ::\n\n     connect-freq n sec\n\n  This is designed to contain DoS attacks which flood the server\n  with connection requests using certificates which will ultimately fail\n  to authenticate.\n\n  This limit applies after ``--connect-freq-initial`` and\n  only applies to client that have completed the three-way handshake\n  or client that use ``--tls-crypt-v2`` without cookie support\n  (``allow-noncookie`` argument to ``--tls-crypt-v2``).\n\n  This is an imperfect solution however, because in a real DoS scenario,\n  legitimate connections might also be refused.\n\n  For the best protection against DoS attacks in server mode, use\n  ``--proto udp`` and either ``--tls-auth`` or ``--tls-crypt``.\n\n--connect-freq-initial args\n  (UDP only) Allow a maximum of ``n`` initial connection packet responses\n  per ``sec`` seconds from the OpenVPN server to clients.\n\n  Valid syntax:\n  ::\n\n     connect-freq-initial n sec\n\n  OpenVPN starting at 2.6 is very efficient in responding to initial\n  connection packets. When not limiting the initial responses\n  an OpenVPN daemon can be abused in reflection attacks.\n  This option is designed to limit the rate OpenVPN will respond to initial\n  attacks.\n\n  Connection attempts that complete the initial three-way handshake\n  will not be counted against the limit. The default is to allow\n  100 initial connection per 10s.\n\n--duplicate-cn\n  Allow multiple clients with the same common name to concurrently\n  connect. In the absence of this option, OpenVPN will disconnect a client\n  instance upon connection of a new client having the same common name.\n\n--ifconfig-pool args\n  Set aside a pool of subnets to be dynamically allocated to connecting\n  clients, similar to a DHCP server.\n\n  Valid syntax:\n  ::\n\n     ifconfig-pool start-IP end-IP [netmask]\n\n  For tun-style tunnels, each client\n  will be given a /30 subnet (for interoperability with Windows clients).\n  For tap-style tunnels, individual addresses will be allocated, and the\n  optional ``netmask`` parameter will also be pushed to clients.\n\n--ifconfig-ipv6-pool args\n  Specify an IPv6 address pool for dynamic assignment to clients.\n\n  Valid args:\n  ::\n\n     ifconfig-ipv6-pool ipv6addr/bits\n\n  The pool starts at ``ipv6addr`` and matches the offset determined from\n  the start of the IPv4 pool.  If the host part of the given IPv6\n  address is ``0``, the pool starts at ``ipv6addr`` +1.\n\n--ifconfig-pool-persist args\n  Persist/unpersist ifconfig-pool data to ``file``, at ``seconds``\n  intervals (default :code:`600`), as well as on program startup and shutdown.\n\n  Valid syntax:\n  ::\n\n     ifconfig-pool-persist file [seconds]\n\n  The goal of this option is to provide a long-term association between\n  clients (denoted by their common name) and the virtual IP address\n  assigned to them from the ifconfig-pool. Maintaining a long-term\n  association is good for clients because it allows them to effectively\n  use the ``--persist-tun`` option.\n\n  ``file`` is a comma-delimited ASCII file, formatted as\n  :code:`<Common-Name>,<IP-address>`.\n\n  If ``seconds`` = :code:`0`, ``file`` will be treated as read-only. This\n  is useful if you would like to treat ``file`` as a configuration file.\n\n  Note that the entries in this file are treated by OpenVPN as\n  *suggestions* only, based on past associations between a common name and\n  IP address.  They do not guarantee that the given common name will always\n  receive the given IP address. If you want guaranteed assignment, use\n  ``--ifconfig-push``\n\n--ifconfig-push args\n  Push virtual IP endpoints for client tunnel, overriding the\n  ``--ifconfig-pool`` dynamic allocation.\n\n  Valid syntax:\n  ::\n\n     ifconfig-push local remote-netmask [alias]\n\n  The parameters ``local`` and ``remote-netmask`` are set according to the\n  ``--ifconfig`` directive which you want to execute on the client machine\n  to configure the remote end of the tunnel. Note that the parameters\n  ``local`` and ``remote-netmask`` are from the perspective of the client,\n  not the server. They may be DNS names rather than IP addresses, in which\n  case they will be resolved on the server at the time of client\n  connection.\n\n  The optional ``alias`` parameter may be used in cases where NAT causes\n  the client view of its local endpoint to differ from the server view. In\n  this case ``local/remote-netmask`` will refer to the server view while\n  ``alias/remote-netmask`` will refer to the client view.\n\n  This option must be associated with a specific client instance, which\n  means that it must be specified either in a client instance config file\n  using ``--client-config-dir`` or dynamically generated using a\n  ``--client-connect`` script.\n\n  Remember also to include a ``--route`` directive in the main OpenVPN\n  config file which encloses ``local``, so that the kernel will know to\n  route it to the server's TUN/TAP interface.\n\n  OpenVPN's internal client IP address selection algorithm works as\n  follows:\n\n  1.  Use ``--client-connect script`` generated file for static IP\n      (first choice).\n\n  2.  Use ``--client-config-dir`` file for static IP (next choice).\n\n  3.  Use ``--ifconfig-pool`` allocation for dynamic IP (last\n      choice).\n\n  When DCO is enabled and the IP is not in contained in the network specified\n  by ``--ifconfig``, OpenVPN will install a /32 host route for the ``local``\n  IP address.\n\n--ifconfig-ipv6-push args\n  for ``--client-config-dir`` per-client static IPv6 interface\n  configuration, see ``--client-config-dir`` and ``--ifconfig-push`` for\n  more details.\n\n  Valid syntax:\n  ::\n\n     ifconfig-ipv6-push ipv6addr/bits ipv6remote\n\n  When DCO is enabled and the IP is not in contained in the network specified\n  by ``--ifconfig-ipv6``, OpenVPN will install a /128 host route for the\n  ``ipv6addr`` IP address.\n\n--multihome [same-interface]\n  Configure a multi-homed UDP server. This option needs to be used when a\n  server has more than one IP address (e.g. multiple interfaces, or\n  secondary IP addresses), and is not using ``--local`` to force binding\n  to one specific address only. This option will add some extra lookups to\n  the packet path to ensure that the UDP reply packets are always sent\n  from the address that the client is talking to. This is not supported on\n  all platforms, and it adds more processing, so it's not enabled by\n  default.\n\n  *Notes:*\n    - This option is only relevant for UDP servers.\n    - Starting with 2.7.0, OpenVPN will ignore the incoming interface of\n      the packet, and leave selection of the outgoing interface to the\n      normal routing/policy mechanisms of the OS (\"set ipi_ifindex=0\").\n    - if the ``same-interface`` flag is added, OpenVPN will copy the\n      incoming interface index to the outgoing interface index,\n      trying to send the packet out over the same interface where it came\n      in on (= restoring earlier OpenVPN behaviour). This might not work\n      if there are no usable routes on that interface.\n    - the \\*BSD systems use a different API for IPv4 that does not provide\n      the interface index anyway (IP_RECVDSTADDR), so there the difference\n      applies only to IPv6.\n\n--iroute args\n  Generate an internal route to a specific client. The ``netmask``\n  parameter, if omitted, defaults to :code:`255.255.255.255`.\n\n  Valid syntax:\n  ::\n\n     iroute network [netmask]\n\n  This directive can be used to route a fixed subnet from the server to a\n  particular client, regardless of where the client is connecting from.\n  Remember that you must also add the route to the system routing table as\n  well (such as by using the ``--route`` directive). The reason why two\n  routes are needed is that the ``--route`` directive routes the packet\n  from the kernel to OpenVPN. Once in OpenVPN, the ``--iroute`` directive\n  routes to the specific client.\n\n  However, when using DCO, the ``--iroute`` directive is usually enough\n  for DCO to fully configure the routing table. The extra ``--route``\n  directive is required only if the expected behaviour is to route the\n  traffic for a specific network to the VPN interface also when the\n  responsible client is not connected (traffic will then be dropped).\n\n  This option must be specified either in a client instance config file\n  using ``--client-config-dir`` or dynamically generated using a\n  ``--client-connect`` script.\n\n  The ``--iroute`` directive also has an important interaction with\n  ``--push \"route ...\"``. ``--iroute`` essentially defines a subnet which\n  is owned by a particular client (we will call this client *A*). If you\n  would like other clients to be able to reach *A*'s subnet, you can use\n  ``--push \"route ...\"`` together with ``--client-to-client`` to effect\n  this. In order for all clients to see *A*'s subnet, OpenVPN must push\n  this route to all clients EXCEPT for *A*, since the subnet is already\n  owned by *A*. OpenVPN accomplishes this by not not pushing a route to\n  a client if it matches one of the client's iroutes.\n\n--iroute-ipv6 args\n  for ``--client-config-dir`` per-client static IPv6 route configuration,\n  see ``--iroute`` for more details how to setup and use this, and how\n  ``--iroute`` and ``--route`` interact.\n\n  Valid syntax:\n  ::\n\n     iroute-ipv6 ipv6addr/bits\n\n--max-clients n\n  Limit server to a maximum of ``n`` concurrent clients.\n\n--max-routes-per-client n\n  Allow a maximum of ``n`` internal routes per client (default\n  :code:`256`). This is designed to help contain DoS attacks where an\n  authenticated client floods the server with packets appearing to come\n  from many unique MAC addresses, forcing the server to deplete virtual\n  memory as its internal routing table expands. This directive can be used\n  in a ``--client-config-dir`` file or auto-generated by a\n  ``--client-connect`` script to override the global value for a particular\n  client.\n\n  Note that this directive affects OpenVPN's internal routing table, not\n  the kernel routing table.\n\n--override-username username\n  Sets the username of a connection to the specified username.  This username\n  will also be used by ``--auth-gen-token``. However, the overridden\n  username comes only into effect *after* the ``--client-config-dir`` has been\n  read and the ``--auth-user-pass-verify`` and ``--client-connect`` scripts\n  have been run.\n\n  Also ``--username-as-common-name`` will use the client provided username\n  as common-name. It is recommended to avoid the use of the\n  ``--override-username`` option if the option ``--username-as-common-name``\n  is being used.\n\n  The changed username will be picked up by the status output and also by\n  the ``--auth-gen-token`` option. It will also be pushed to the client\n  using ``--auth-token-user`` if ``--auth-gen-token`` is enabled.\n\n  Internally on all subsequent renegotiations the client provided username\n  will be replaced by the username provided by ``--override-username``.\n  If the client changes to a username that is different from both the initial\n  and the overridden username, the client will be rejected.\n\n  Special care should be taken that both the initial username of the client\n  and the overridden username are handled correctly when using\n  ``--override-username`` and the related options to avoid\n  authentication/authorisation bypasses.\n\n  This option is mainly intended for use cases that use certificates and\n  multi factor authentication and therefore do not provide a username that\n  can be used for ``--auth-gen-token`` to allow providing a username in\n  these scenarios.\n\n  If the ``--auth-token`` directive is pushed by another script/plugin or\n  management interface, consider also generating and pushing\n  ``--auth-token-user``.\n\n--port-share args\n  Share OpenVPN TCP with another service\n\n  Valid syntax:\n  ::\n\n     port-share host port [dir]\n\n  When run in TCP server mode, share the OpenVPN port with another\n  application, such as an HTTPS server. If OpenVPN senses a connection to\n  its port which is using a non-OpenVPN protocol, it will proxy the\n  connection to the server at ``host``:``port``. Currently only designed to\n  work with HTTP/HTTPS, though it would be theoretically possible to\n  extend to other protocols such as ssh.\n\n  ``dir`` specifies an optional directory where a temporary file with name\n  N containing content C will be dynamically generated for each proxy\n  connection, where C is the source IP:port of the client connection and N\n  is the source IP:port of the connection to the proxy receiver. This\n  directory can be used as a dictionary by the proxy receiver to determine\n  the origin of the connection. Each generated file will be automatically\n  deleted when the proxied connection is torn down.\n\n  Not implemented on Windows.\n\n--push option\n  Push a config file option back to the client for remote execution. Note\n  that ``option`` must be enclosed in double quotes (:code:`\"\"`). The\n  client must specify ``--pull`` in its config file. The set of options\n  which can be pushed is limited by both feasibility and security. Some\n  options such as those which would execute scripts are banned, since they\n  would effectively allow a compromised server to execute arbitrary code\n  on the client. Other options such as TLS or MTU parameters cannot be\n  pushed because the client needs to know them before the connection to the\n  server can be initiated.\n\n  This is a partial list of options which can currently be pushed:\n  ``--route``, ``--route-gateway``, ``--route-delay``,\n  ``--redirect-gateway``, ``--ip-win32``, ``--dhcp-option``, ``--dns``,\n  ``--inactive``, ``--ping``, ``--ping-exit``, ``--ping-restart``,\n  ``--setenv``, ``--auth-token``, ``--persist-tun``,\n  ``--echo``, ``--comp-lzo``, ``--socket-flags``, ``--sndbuf``,\n  ``--rcvbuf``, ``--session-timeout``\n\n  Note: using ``--push`` requires OpenVPN to run in ``--mode server`` (or\n  using of one of ``--server``, ``--server-bridge`` helper directives).\n\n--push-remove opt\n  Selectively remove all ``--push`` options matching \"opt\" from the option\n  list for a client. ``opt`` is matched as a substring against the whole\n  option string to-be-pushed to the client, so ``--push-remove route``\n  would remove all ``--push route ...`` and ``--push route-ipv6 ...``\n  statements, while ``--push-remove \"route-ipv6 2001:\"`` would only remove\n  IPv6 routes for :code:`2001:...` networks.\n\n  ``--push-remove`` can only be used in a client-specific context, like in\n  a ``--client-config-dir`` file, or ``--client-connect`` script or plugin\n  -- similar to ``--push-reset``, just more selective.\n\n  *NOTE*: to *change* an option, ``--push-remove`` can be used to first\n  remove the old value, and then add a new ``--push`` option with the new\n  value.\n\n  *NOTE 2*: due to implementation details, 'ifconfig' and 'ifconfig-ipv6'\n  can only be removed with an exact match on the option (\n  :code:`push-remove ifconfig`), no substring matching and no matching on\n  the IPv4/IPv6 address argument is possible.\n\n--push-reset\n  Don't inherit the global push list for a specific client instance.\n  Specify this option in a client-specific context such as with a\n  ``--client-config-dir`` configuration file. This option will ignore\n  ``--push`` options at the global config file level.\n\n  *NOTE*: ``--push-reset`` is very thorough: it will remove almost\n  all options from the list of to-be-pushed options.  In many cases,\n  some of these options will need to be re-configured afterwards -\n  specifically, ``--topology subnet`` and ``--route-gateway`` will get\n  lost and this will break client configs in many cases.  Thus, for most\n  purposes, ``--push-remove`` is better suited to selectively remove\n  push options for individual clients.\n\n--server args\n  A helper directive designed to simplify the configuration of OpenVPN's\n  server mode. This directive will set up an OpenVPN server which will\n  allocate addresses to clients out of the given network/netmask. The\n  server itself will take the :code:`.1` address of the given network for\n  use as the server-side endpoint of the local TUN/TAP interface. If the\n  optional :code:`nopool` flag is given, no dynamic IP address pool will\n  prepared for VPN clients.\n\n  Valid syntax:\n  ::\n\n      server network netmask [nopool]\n\n  For example, ``--server 10.8.0.0 255.255.255.0`` expands as follows:\n  ::\n\n     mode server\n     tls-server\n     push \"topology [topology]\"\n\n     if dev tun AND (topology == net30 OR topology == p2p):\n       ifconfig 10.8.0.1 10.8.0.2\n       if !nopool:\n         ifconfig-pool 10.8.0.4 10.8.0.251\n       route 10.8.0.0 255.255.255.0\n       if client-to-client:\n         push \"route 10.8.0.0 255.255.255.0\"\n       else if topology == net30:\n         push \"route 10.8.0.1\"\n\n     if dev tap OR (dev tun AND topology == subnet):\n       ifconfig 10.8.0.1 255.255.255.0\n       if !nopool:\n         ifconfig-pool 10.8.0.2 10.8.0.253 255.255.255.0\n       push \"route-gateway 10.8.0.1\"\n       if route-gateway unset:\n         route-gateway 10.8.0.2\n\n  Don't use ``--server`` if you are ethernet bridging. Use\n  ``--server-bridge`` instead.\n\n--server-bridge args\n  A helper directive similar to ``--server`` which is designed to simplify\n  the configuration of OpenVPN's server mode in ethernet bridging\n  configurations.\n\n  Valid syntaxes:\n  ::\n\n      server-bridge gateway netmask pool-start-IP pool-end-IP\n      server-bridge [nogw]\n\n  If ``--server-bridge`` is used without any parameters, it will enable a\n  DHCP-proxy mode, where connecting OpenVPN clients will receive an IP\n  address for their TAP adapter from the DHCP server running on the\n  OpenVPN server-side LAN. Note that only clients that support the binding\n  of a DHCP client with the TAP adapter (such as Windows) can support this\n  mode. The optional :code:`nogw` flag (advanced) indicates that gateway\n  information should not be pushed to the client.\n\n  To configure ethernet bridging, you must first use your OS's bridging\n  capability to bridge the TAP interface with the ethernet NIC interface.\n  For example, on Linux this is done with the :code:`brctl` tool, and with\n  Windows XP it is done in the Network Connections Panel by selecting the\n  ethernet and TAP adapters and right-clicking on \"Bridge Connections\".\n\n  Next you you must manually set the IP/netmask on the bridge interface.\n  The ``gateway`` and ``netmask`` parameters to ``--server-bridge`` can be\n  set to either the IP/netmask of the bridge interface, or the IP/netmask\n  of the default gateway/router on the bridged subnet.\n\n  Finally, set aside a IP range in the bridged subnet, denoted by\n  ``pool-start-IP`` and ``pool-end-IP``, for OpenVPN to allocate to\n  connecting clients.\n\n  For example, ``server-bridge 10.8.0.4 255.255.255.0 10.8.0.128\n  10.8.0.254`` expands as follows:\n  ::\n\n    mode server\n    tls-server\n\n    ifconfig-pool 10.8.0.128 10.8.0.254 255.255.255.0\n    push \"route-gateway 10.8.0.4\"\n\n  In another example, ``--server-bridge`` (without parameters) expands as\n  follows:\n  ::\n\n    mode server\n    tls-server\n\n    push \"route-gateway dhcp\"\n\n  Or ``--server-bridge nogw`` expands as follows:\n  ::\n\n    mode server\n    tls-server\n\n--server-ipv6 args\n  Convenience-function to enable a number of IPv6 related options at once,\n  namely ``--ifconfig-ipv6``, ``--ifconfig-ipv6-pool`` and\n  ``--push tun-ipv6``.\n\n  Valid syntax:\n  ::\n\n     server-ipv6 ipv6addr/bits\n\n  Pushing of the ``--tun-ipv6`` directive is done for older clients which\n  require an explicit ``--tun-ipv6`` in their configuration.\n\n--stale-routes-check args\n  Remove routes which haven't had activity for ``n`` seconds (i.e. the ageing\n  time).  This check is run every ``t`` seconds (i.e. check interval).\n\n  Valid syntax:\n  ::\n\n     stale-routes-check n [t]\n\n  If ``t`` is not present it defaults to ``n``.\n\n  This option helps to keep the dynamic routing table small. See also\n  ``--max-routes-per-client``\n\n--username-as-common-name\n  Use the authenticated username as the common-name, rather than the\n  common-name from the client certificate. Requires that some form of\n  ``--auth-user-pass`` verification is in effect. As the replacement happens\n  after ``--auth-user-pass`` verification, the verification script or\n  plugin will still receive the common-name from the certificate.\n\n  The common_name environment variable passed to scripts and plugins invoked\n  after authentication (e.g, client-connect script) and file names parsed in\n  client-config directory will match the username.\n\n--verify-client-cert mode\n  Specify whether the client is required to supply a valid certificate.\n\n  Possible ``mode`` options are:\n\n  :code:`none`\n      A client certificate is not required. the client needs to\n      authenticate using username/password only. Be aware that using this\n      directive is less secure than requiring certificates from all\n      clients.\n\n      If you use this directive, the entire responsibility of authentication\n      will rest on your ``--auth-user-pass-verify`` script, so keep in mind\n      that bugs in your script could potentially compromise the security of\n      your VPN.\n\n      ``--verify-client-cert none`` is functionally equivalent to\n      ``--client-cert-not-required``.\n\n  :code:`optional`\n      A client may present a certificate but it is not required to do so.\n      When using this directive, you should also use a\n      ``--auth-user-pass-verify`` script to ensure that clients are\n      authenticated using a certificate, a username and password, or\n      possibly even both.\n\n      Again, the entire responsibility of authentication will rest on your\n      ``--auth-user-pass-verify`` script, so keep in mind that bugs in your\n      script could potentially compromise the security of your VPN.\n\n  :code:`require`\n      This is the default option. A client is required to present a\n      certificate, otherwise VPN access is refused.\n\n  If you don't use this directive (or use ``--verify-client-cert require``)\n  but you also specify an ``--auth-user-pass-verify`` script, then OpenVPN\n  will perform double authentication. The client certificate verification\n  AND the ``--auth-user-pass-verify`` script will need to succeed in order\n  for a client to be authenticated and accepted onto the VPN.\n\n--vlan-tagging\n  Server-only option. Turns the OpenVPN server instance into a switch that\n  understands VLAN-tagging, based on IEEE 802.1Q.\n\n  The server TAP device and each of the connecting clients is seen as a\n  port of the switch. All client ports are in untagged mode and the server\n  TAP device is VLAN-tagged, untagged or accepts both, depending on the\n  ``--vlan-accept`` setting.\n\n  Ethernet frames with a prepended 802.1Q tag are called \"tagged\". If the\n  VLAN Identifier (VID) field in such a tag is non-zero, the frame is\n  called \"VLAN-tagged\". If the VID is zero, but the Priority Control Point\n  (PCP) field is non-zero, the frame is called \"prio-tagged\". If there is\n  no 802.1Q tag, the frame is \"untagged\".\n\n  Using the ``--vlan-pvid v`` option once per client (see\n  --client-config-dir), each port can be associated with a certain VID.\n  Packets can only be forwarded between ports having the same VID.\n  Therefore, clients with differing VIDs are completely separated from\n  one-another, even if ``--client-to-client`` is activated.\n\n  The packet filtering takes place in the OpenVPN server. Clients should\n  not have any VLAN tagging configuration applied.\n\n  The ``--vlan-tagging`` option is off by default. While turned off,\n  OpenVPN accepts any Ethernet frame and does not perform any special\n  processing for VLAN-tagged packets.\n\n  This option can only be activated in ``--dev tap mode``.\n\n--vlan-accept args\n  Configure the VLAN tagging policy for the server TAP device.\n\n  Valid syntax:\n  ::\n\n     vlan-accept  all|tagged|untagged\n\n  The following modes are available:\n\n  :code:`tagged`\n      Admit only VLAN-tagged frames. Only VLAN-tagged packets are accepted,\n      while untagged or priority-tagged packets are dropped when entering\n      the server TAP device.\n\n  :code:`untagged`\n      Admit only untagged and prio-tagged frames.  VLAN-tagged packets are\n      not accepted, while untagged or priority-tagged packets entering the\n      server TAP device are tagged with the value configured for the global\n      ``--vlan-pvid`` setting.\n\n  :code:`all` (default)\n      Admit all frames.  All packets are admitted and then treated like\n      untagged or tagged mode respectively.\n\n  *Note*:\n      Some vendors refer to switch ports running in :code:`tagged` mode\n      as \"trunk ports\" and switch ports running in :code:`untagged` mode\n      as \"access ports\".\n\n  Packets forwarded from clients to the server are VLAN-tagged with the\n  originating client's PVID, unless the VID matches the global\n  ``--vlan-pvid``, in which case the tag is removed.\n\n  If no *PVID* is configured for a given client (see --vlan-pvid) packets\n  are tagged with 1 by default.\n\n--vlan-pvid v\n  Specifies which VLAN identifier a \"port\" is associated with. Only valid\n  when ``--vlan-tagging`` is specified.\n\n  In the client context, the setting specifies which VLAN ID a client is\n  associated with. In the global context, the VLAN ID of the server TAP\n  device is set. The latter only makes sense for ``--vlan-accept\n  untagged`` and ``--vlan-accept all`` modes.\n\n  Valid values for ``v`` go from :code:`1` through to :code:`4094`. The\n  global value defaults to :code:`1`. If no ``--vlan-pvid`` is specified in\n  the client context, the global value is inherited.\n\n  In some switch implementations, the *PVID* is also referred to as \"Native\n  VLAN\".\n"
  },
  {
    "path": "doc/man-sections/signals.rst",
    "content": "SIGNALS\n=======\n\n:code:`SIGHUP`\n    Cause OpenVPN to close all TUN/TAP and network connections, restart,\n    re-read the configuration file (if any), and reopen TUN/TAP and network\n    connections.\n\n:code:`SIGUSR1`\n    Like :code:`SIGHUP`, except don't re-read configuration file, and\n    possibly don't close and reopen TUN/TAP device, re-read key files,\n    preserve local IP address/port, or preserve most recently authenticated\n    remote IP address/port based on ``--persist-tun``, ``--persist-local-ip``\n    and ``--persist-remote-ip`` options respectively (see above).\n\n    This signal may also be internally generated by a timeout condition,\n    governed by the ``--ping-restart`` option.\n\n    This signal, when combined with ``--persist-remote-ip``, may be sent\n    when the underlying parameters of the host's network interface change\n    such as when the host is a DHCP client and is assigned a new IP address.\n    See ``--ipchange`` for more information.\n\n:code:`SIGUSR2`\n    Causes OpenVPN to display its current statistics (to the syslog file if\n    ``--daemon`` is used, or stdout otherwise).\n\n:code:`SIGINT`, :code:`SIGTERM`\n    Causes OpenVPN to exit gracefully.\n"
  },
  {
    "path": "doc/man-sections/tls-options.rst",
    "content": "TLS Mode Options\n````````````````\n\nTLS mode is the most powerful crypto mode of OpenVPN in both security\nand flexibility. TLS mode works by establishing control and data\nchannels which are multiplexed over a single TCP/UDP port. OpenVPN\ninitiates a TLS session over the control channel and uses it to exchange\ncipher and HMAC keys to protect the data channel. TLS mode uses a robust\nreliability layer over the UDP connection for all control channel\ncommunication, while the data channel, over which encrypted tunnel data\npasses, is forwarded without any mediation. The result is the best of\nboth worlds: a fast data channel that forwards over UDP with only the\noverhead of encrypt, decrypt, and HMAC functions, and a control channel\nthat provides all of the security features of TLS, including\ncertificate-based authentication and Diffie Hellman forward secrecy.\n\nTo use TLS mode, each peer that runs OpenVPN should have its own local\ncertificate/key pair (``--cert`` and ``--key``), signed by the root\ncertificate which is specified in ``--ca``.\n\nWhen two OpenVPN peers connect, each presents its local certificate to\nthe other. Each peer will then check that its partner peer presented a\ncertificate which was signed by the master root certificate as specified\nin ``--ca``.\n\nIf that check on both peers succeeds, then the TLS negotiation will\nsucceed, both OpenVPN peers will exchange temporary session keys, and\nthe tunnel will begin passing data.\n\nThe OpenVPN project provides a set of scripts for managing RSA\ncertificates and keys: https://github.com/OpenVPN/easy-rsa\n\n--askpass file\n  Get certificate password from console or ``file`` before we daemonize.\n\n  Valid syntaxes:\n  ::\n\n     askpass\n     askpass file\n\n  For the extremely security conscious, it is possible to protect your\n  private key with a password. Of course this means that every time the\n  OpenVPN daemon is started you must be there to type the password. The\n  ``--askpass`` option allows you to start OpenVPN from the command line.\n  It will query you for a password before it daemonizes. To protect a\n  private key with a password you should omit the ``-nodes`` option when\n  you use the ``openssl`` command line tool to manage certificates and\n  private keys.\n\n  If ``file`` is specified, read the password from the first line of\n  ``file``. Keep in mind that storing your password in a file to a certain\n  extent invalidates the extra security provided by using an encrypted\n  key.\n\n--ca file\n  Certificate authority (CA) file in .pem format, also referred to as the\n  *root* certificate. This file can have multiple certificates in .pem\n  format, concatenated together. You can construct your own certificate\n  authority certificate and private key by using a command such as:\n  ::\n\n     openssl req -nodes -new -x509 -keyout ca.key -out ca.crt\n\n  Then edit your openssl.cnf file and edit the ``certificate`` variable to\n  point to your new root certificate ``ca.crt``.\n\n  For testing purposes only, the OpenVPN distribution includes a sample CA\n  certificate (ca.crt). Of course you should never use the test\n  certificates and test keys distributed with OpenVPN in a production\n  environment, since by virtue of the fact that they are distributed with\n  OpenVPN, they are totally insecure.\n\n--capath dir\n  Directory containing trusted certificates (CAs and CRLs). Not available\n  with mbed TLS.\n\n  CAs in the capath directory are expected to be named <hash>.<n>. CRLs\n  are expected to be named <hash>.r<n>. See the ``-CApath`` option of\n  ``openssl verify``, and the ``-hash`` option of ``openssl x509``,\n  ``openssl crl`` and ``X509_LOOKUP_hash_dir()``\\(3)\n  for more information.\n\n  Similar to the ``--crl-verify`` option, CRLs are not mandatory -\n  OpenVPN will log the usual warning in the logs if the relevant CRL is\n  missing, but the connection will be allowed.\n\n--cert file|uri\n  Local peer's signed certificate in .pem format or as a URI -- must be\n  signed by a certificate authority whose certificate is in ``--ca file``\n  in the peer configuration. URI is supported only when built with\n  OpenSSL 3.0 or later and any required providers are loaded. Types\n  of URIs supported and their syntax depends on providers. OpenSSL has\n  internal support for \"file:/absolute/path\" URI in which case the scheme\n  \"file:\" is optional, and any file format recognized by OpenSSL (e.g., PEM,\n  PKCS12) is supported. PKCS#11 URI (RFC 7512) is supported by pkcs11-provider.\n\n  Each peer in an OpenVPN link running in TLS mode should have its own certificate\n  and private key file. In addition, each certificate should have been\n  signed by the key of a certificate authority whose public key resides in\n  the ``--ca`` certificate authority file. You can easily make your own\n  certificate authority (see above) or pay money to use a commercial\n  service such as thawte.com (in which case you will be helping to finance\n  the world's second space tourist :). To generate a certificate, you can\n  use a command such as:\n  ::\n\n     openssl req -nodes -new -keyout mycert.key -out mycert.csr\n\n  If your certificate authority private key lives on another machine, copy\n  the certificate signing request (mycert.csr) to this other machine (this\n  can be done over an insecure channel such as email). Now sign the\n  certificate with a command such as:\n  ::\n\n     openssl ca -out mycert.crt -in mycert.csr\n\n  Now copy the certificate (mycert.crt) back to the peer which initially\n  generated the .csr file (this can be over a public medium). Note that\n  the ``openssl ca`` command reads the location of the certificate\n  authority key from its configuration file such as\n  :code:`/usr/share/ssl/openssl.cnf` -- note also that for certificate\n  authority functions, you must set up the files :code:`index.txt` (may be\n  empty) and :code:`serial` (initialize to :code:`01`).\n\n--crl-verify args\n  Check peer certificate against a Certificate Revocation List.\n\n  Valid syntax:\n  ::\n\n     crl-verify file/directory flag\n\n  Examples:\n  ::\n\n     crl-verify crl-file.pem\n     crl-verify /etc/openvpn/crls dir\n\n  A CRL (certificate revocation list) is used when a particular key is\n  compromised but when the overall PKI is still intact.\n\n  Suppose you had a PKI consisting of a CA, root certificate, and a number\n  of client certificates. Suppose a laptop computer containing a client\n  key and certificate was stolen. By adding the stolen certificate to the\n  CRL file, you could reject any connection which attempts to use it,\n  while preserving the overall integrity of the PKI.\n\n  The only time when it would be necessary to rebuild the entire PKI from\n  scratch would be if the root certificate key itself was compromised.\n\n  The option is not mandatory - if the relevant CRL is missing, OpenVPN\n  will log a warning in the logs - e.g.\n  ::\n\n     VERIFY WARNING: depth=0, unable to get certificate CRL\n\n  but the connection will be allowed.  If the optional :code:`dir` flag\n  is specified, enable a different mode where the ``crl-verify`` is\n  pointed at a directory containing files named as revoked serial numbers\n  (the files may be empty, the contents are never read). If a client\n  requests a connection, where the client certificate serial number\n  (decimal string) is the name of a file present in the directory, it will\n  be rejected.\n\n  *Note:*\n            As the crl file (or directory) is read every time a peer\n            connects, if you are dropping root privileges with\n            ``--user``, make sure that this user has sufficient\n            privileges to read the file.\n\n\n--dh file\n  File containing finite field Diffie Hellman parameters in .pem format (used\n  by ``--tls-server`` only).\n\n  Set ``file`` to :code:`none` to disable fine field Diffie Hellman\n  key exchange (and to only use ECDH or newer hybrid key agreement algorithms\n  like X25519MLKEM768 instead).\n  Note that this requires peers to be using an SSL library that supports\n  ECDH TLS cipher suites (e.g. OpenSSL 1.0.1+, or mbed TLS 2.0+). Starting\n  with 2.7.0, this is the same as not specifying ``--dh`` at all.\n\n  Diffie Hellman parameters can be generated using\n  ``openssl dhparam -out dh2048.pem 2048`` but it is recommended to\n  use ``none`` as finite field Diffie Hellman have been replaced\n  by more modern variants like ECDH.\n\n  Diffie Hellman parameters may be considered public.\n\n--ecdh-curve name\n  Specify the curve to use for elliptic curve Diffie Hellman. Available\n  curves can be listed with ``--show-curves``. The specified curve will\n  only be used for ECDH TLS-ciphers.\n\n  This option is not supported in mbed TLS builds of OpenVPN.\n\n--extra-certs file\n  Specify a ``file`` containing one or more PEM certs (concatenated\n  together) that complete the local certificate chain.\n\n  This option is useful for \"split\" CAs, where the CA for server certs is\n  different than the CA for client certs. Putting certs in this file\n  allows them to be used to complete the local certificate chain without\n  trusting them to verify the peer-submitted certificate, as would be the\n  case if the certs were placed in the ``ca`` file.\n\n--hand-window n\n  Handshake Window -- the TLS-based key exchange must finalize within\n  ``n`` seconds of handshake initiation by any peer (default :code:`60`\n  seconds). If the handshake fails we will attempt to reset our connection\n  with our peer and try again. Even in the event of handshake failure we\n  will still use our expiring key for up to ``--tran-window`` seconds to\n  maintain continuity of transmission of tunnel data.\n\n  The ``--hand-window`` parameter also controls the amount of time that\n  the OpenVPN client repeats the pull request until it times out.\n\n--key file|uri\n  Local peer's private key in .pem format or a URI. Use the private key\n  which was generated when you built your peer's certificate (see\n  ``--cert file`` above). URI is supported only when built with OpenSSL 3.0\n  or later and any required providers are loaded. (See ``--cert`` for more details).\n\n--ns-cert-type type\n  **DEPRECATED** The ``--remote-cert-tls`` option should be used instead.\n  The option is still available since it can't be silently ignored and needs\n  updates to certificates and configs on both sides of the connection.\n  However it should not be used for new clients or servers. It depends on the\n  deprecated ``nsCertType`` certificate field.\n\n  Might not work depending on the TLS library used.\n\n  Will be removed in a future release.\n\n--pkcs12 file\n  Specify a PKCS #12 file containing local private key, local certificate,\n  and root CA certificate. This option can be used instead of ``--ca``,\n  ``--cert``, and ``--key``.  Not available with mbed TLS.\n\n--remote-cert-eku oid\n  Require that peer certificate was signed with an explicit *extended key\n  usage*.\n\n  This is a useful security option for clients, to ensure that the host\n  they connect to is a designated server.\n\n  The extended key usage should be encoded in *oid notation*, or *OpenSSL\n  symbolic representation*.\n\n--remote-cert-ku key-usage\n  Require that peer certificate was signed with an explicit\n  ``key-usage``.\n\n  If present in the certificate, the :code:`keyUsage` value is validated by\n  the TLS library during the TLS handshake. Specifying this option without\n  arguments requires this extension to be present (so the TLS library will\n  verify it).\n\n  If ``key-usage`` is a list of usage bits, the :code:`keyUsage` field\n  must have *at least* the same bits set as the bits in *one of* the values\n  supplied in the ``key-usage`` list.\n\n  The ``key-usage`` values in the list must be encoded in hex, e.g.\n  ::\n\n     remote-cert-ku a0\n\n--remote-cert-tls type\n  Require that peer certificate was signed with an explicit *key usage*\n  and *extended key usage* based on RFC3280 TLS rules.\n\n  Valid syntaxes:\n  ::\n\n     remote-cert-tls server\n     remote-cert-tls client\n\n  This is a useful security option for clients, to ensure that the host\n  they connect to is a designated server. Or the other way around; for a\n  server to verify that only hosts with a client certificate can connect.\n\n  The ``--remote-cert-tls client`` option is equivalent to\n  ::\n\n     remote-cert-ku\n     remote-cert-eku \"TLS Web Client Authentication\"\n\n  The ``--remote-cert-tls server`` option is equivalent to\n  ::\n\n     remote-cert-ku\n     remote-cert-eku \"TLS Web Server Authentication\"\n\n  This is an important security precaution to protect against a\n  man-in-the-middle attack where an authorized client attempts to connect\n  to another client by impersonating the server. The attack is easily\n  prevented by having clients verify the server certificate using any one\n  of ``--remote-cert-tls``, ``--verify-x509-name``, ``--peer-fingerprint``\n  or ``--tls-verify``.\n\n--tls-auth args\n  Add an additional layer of HMAC authentication on top of the TLS control\n  channel to mitigate DoS attacks and attacks on the TLS stack.\n\n  Valid syntaxes:\n  ::\n\n     tls-auth file\n     tls-auth file 0\n     tls-auth file 1\n\n  In a nutshell, ``--tls-auth`` enables a kind of \"HMAC firewall\" on\n  OpenVPN's TCP/UDP port, where TLS control channel packets bearing an\n  incorrect HMAC signature can be dropped immediately without response.\n\n  ``file`` (required) is a file in OpenVPN static key format which can be\n  generated by ``--genkey``.\n\n  Older versions (up to OpenVPN 2.3) supported a freeform passphrase file.\n  This is no longer supported in newer versions (v2.4+).\n\n  The optional ``direction`` parameter enables the use of 2 distinct keys\n  (HMAC-send, HMAC-receive), so that each\n  data flow direction has a different HMAC key. This has a number of desirable\n  security properties including eliminating certain kinds of DoS and message\n  replay attacks.\n\n  When the ``direction`` parameter is omitted, the same key is used\n  bidirectionally.\n\n  The ``direction`` parameter should always be complementary on either\n  side of the connection, i.e. one side should use :code:`0` and the other\n  should use :code:`1`, or both sides should omit it altogether.\n\n  The ``direction`` parameter requires that ``file`` contains a 2048 bit\n  key. While pre-1.5 versions of OpenVPN generate 1024 bit key files, any\n  version of OpenVPN which supports the ``direction`` parameter, will also\n  support 2048 bit key file generation using the ``--genkey`` option.\n\n\n  ``--tls-auth`` is recommended when you are running OpenVPN in a mode\n  where it is listening for packets from any IP address, such as when\n  ``--remote`` is not specified, or ``--remote`` is specified with\n  ``--float``.\n\n  The rationale for this feature is as follows. TLS requires a\n  multi-packet exchange before it is able to authenticate a peer. During\n  this time before authentication, OpenVPN is allocating resources (memory\n  and CPU) to this potential peer. The potential peer is also exposing\n  many parts of OpenVPN and the OpenSSL library to the packets it is\n  sending. Most successful network attacks today seek to either exploit\n  bugs in programs (such as buffer overflow attacks) or force a program to\n  consume so many resources that it becomes unusable. Of course the first\n  line of defense is always to produce clean, well-audited code. OpenVPN\n  has been written with buffer overflow attack prevention as a top\n  priority. But as history has shown, many of the most widely used network\n  applications have, from time to time, fallen to buffer overflow attacks.\n\n  So as a second line of defense, OpenVPN offers this special layer of\n  authentication on top of the TLS control channel so that every packet on\n  the control channel is authenticated by an HMAC signature and a unique\n  ID for replay protection. This signature will also help protect against\n  DoS (Denial of Service) attacks. An important rule of thumb in reducing\n  vulnerability to DoS attacks is to minimize the amount of resources a\n  potential, but as yet unauthenticated, client is able to consume.\n\n  ``--tls-auth`` does this by signing every TLS control channel packet\n  with an HMAC signature, including packets which are sent before the TLS\n  level has had a chance to authenticate the peer. The result is that\n  packets without the correct signature can be dropped immediately upon\n  reception, before they have a chance to consume additional system\n  resources such as by initiating a TLS handshake. ``--tls-auth`` can be\n  strengthened by adding the ``--replay-persist`` option which will keep\n  OpenVPN's replay protection state in a file so that it is not lost\n  across restarts.\n\n  It should be emphasized that this feature is optional and that the key\n  file used with ``--tls-auth`` gives a peer nothing more than the power\n  to initiate a TLS handshake. It is not used to encrypt or authenticate\n  any tunnel data.\n\n  Use ``--tls-crypt`` instead if you want to use the key file to not only\n  authenticate, but also encrypt the TLS control channel.\n\n--tls-groups list\n    A list of allowable groups/curves in order of preference.\n\n    Set the allowed elliptic curves/groups for the TLS session.\n    These groups are allowed to be used in signatures and key exchange.\n\n    mbedTLS currently allows all known curves per default.\n\n    OpenSSL 1.1+ restricts the list per default to\n    ::\n\n      \"X25519:secp256r1:X448:secp521r1:secp384r1\".\n\n    If you use certificates that use non-standard curves, you\n    might need to add them here. If you do not force the ecdh curve\n    by using ``--ecdh-curve``, the groups for ecdh will also be picked\n    from this list.\n\n    OpenVPN maps the curve name ``secp256r1`` to ``prime256v1`` to allow\n    specifying the same tls-groups option for mbedTLS and OpenSSL.\n\n    Warning: this option not only affects elliptic curve certificates\n    but also the key exchange in TLS 1.3 and using this option improperly\n    will disable TLS 1.3.\n\n--tls-cert-profile profile\n  Set the allowed cryptographic algorithms for certificates according to\n  ``profile``.\n\n  The following profiles are supported:\n\n  :code:`insecure`\n      Identical for mbed TLS to :code:`legacy`\n\n  :code:`legacy` (default)\n      SHA1 and newer, RSA 2048-bit+, any elliptic curve.\n\n  :code:`preferred`\n      SHA2 and newer, RSA 2048-bit+, any elliptic curve.\n\n  :code:`suiteb`\n      SHA256/SHA384, ECDSA with P-256 or P-384.\n\n  This option is only fully supported for mbed TLS builds. OpenSSL builds\n  use the following approximation:\n\n  :code:`insecure`\n      sets \"security level 0\"\n\n  :code:`legacy` (default)\n      sets \"security level 1\"\n\n  :code:`preferred`\n      sets \"security level 2\"\n\n  :code:`suiteb`\n      sets \"security level 3\" and ``--tls-cipher \"SUITEB128\"``.\n\n  OpenVPN will migrate to 'preferred' as default in the future. Please\n  ensure that your keys already comply.\n\n*WARNING:* ``--tls-cipher``, ``--tls-ciphersuites`` and ``tls-groups``\n    These options are expert features, which - if used correctly - can\n    improve the security of your VPN connection. But it is also easy to\n    unwittingly use them to carefully align a gun with your foot, or just\n    break your connection. Use with care!\n\n--tls-cipher l\n  A list ``l`` of allowable TLS ciphers delimited by a colon (\":code:`:`\").\n\n  This setting can be used to ensure that certain cipher suites are used\n  (or not used) for the TLS connection. OpenVPN uses TLS to secure the\n  control channel, over which the keys that are used to protect the actual\n  VPN traffic are exchanged.\n\n  The supplied list of ciphers is (after potential OpenSSL/IANA name\n  translation) simply supplied to the crypto library. Please see the\n  OpenSSL and/or mbed TLS documentation for details on the cipher list\n  interpretation.\n\n  For OpenSSL, the ``--tls-cipher`` option is used for TLS 1.2 and below.\n\n  Use ``--show-tls`` to see a list of TLS ciphers supported by your crypto\n  library.\n\n  The default for ``--tls-cipher`` is to use mbed TLS's default cipher list\n  when using mbed TLS or\n  :code:`DEFAULT:!EXP:!LOW:!MEDIUM:!kDH:!kECDH:!DSS:!PSK:!SRP:!kRSA` when\n  using OpenSSL.\n\n--tls-ciphersuites l\n  Same as ``--tls-cipher`` but for TLS 1.3 and up. mbed TLS has no\n  TLS 1.3 support yet and only the ``--tls-cipher`` setting is used.\n\n  The default for ``--tls-ciphersuites`` is to use the crypto library's\n  default.\n\n--tls-client\n  Enable TLS and assume client role during TLS handshake.\n\n--tls-crypt keyfile\n  Encrypt and authenticate all control channel packets with the key from\n  ``keyfile``. (See ``--tls-auth`` for more background.)\n\n  Encrypting (and authenticating) control channel packets:\n\n  * provides more privacy by hiding the certificate used for the TLS\n    connection,\n\n  * makes it harder to identify OpenVPN traffic as such,\n\n  * provides \"poor-man's\" post-quantum security, against attackers who will\n    never know the pre-shared key (i.e. no forward secrecy).\n\n  In contrast to ``--tls-auth``, ``--tls-crypt`` does *not* require the\n  user to set ``--key-direction``.\n\n  **Security Considerations**\n\n  All peers use the same ``--tls-crypt`` pre-shared group key to\n  authenticate and encrypt control channel messages. To ensure that IV\n  collisions remain unlikely, this key should not be used to encrypt more\n  than 2^48 client-to-server or 2^48 server-to-client control channel\n  messages. A typical initial negotiation is about 10 packets in each\n  direction. Assuming both initial negotiation and renegotiations are at\n  most 2^16 (65536) packets (to be conservative), and (re)negotiations\n  happen each minute for each user (24/7), this limits the tls-crypt key\n  lifetime to 8171 years divided by the number of users. So a setup with\n  1000 users should rotate the key at least once each eight years. (And a\n  setup with 8000 users each year.)\n\n  If IV collisions were to occur, this could result in the security of\n  ``--tls-crypt`` degrading to the same security as using ``--tls-auth``.\n  That is, the control channel still benefits from the extra protection\n  against active man-in-the-middle-attacks and DoS attacks, but may no\n  longer offer extra privacy and post-quantum security on top of what TLS\n  itself offers.\n\n  For large setups or setups where clients are not trusted, consider using\n  ``--tls-crypt-v2`` instead. That uses per-client unique keys, and\n  thereby improves the bounds to 'rotate a client key at least once per\n  8000 years'.\n\n--tls-crypt-v2 keyfile\n\n  Valid syntax::\n\n     tls-crypt-v2 keyfile\n     tls-crypt-v2 keyfile force-cookie\n     tls-crypt-v2 keyfile allow-noncookie\n\n  Use client-specific tls-crypt keys.\n\n  For clients, ``keyfile`` is a client-specific tls-crypt key. Such a key\n  can be generated using the :code:`--genkey tls-crypt-v2-client` option.\n\n  For servers, ``keyfile`` is used to unwrap client-specific keys supplied\n  by the client during connection setup. This key must be the same as the\n  key used to generate the client-specific key (see :code:`--genkey\n  tls-crypt-v2-client`).\n\n  On servers, this option can be used together with the ``--tls-auth`` or\n  ``--tls-crypt`` option. In that case, the server will detect whether the\n  client is using client-specific keys, and automatically select the right\n  mode.\n\n  The optional parameters :code:`force-cookie` allows only tls-crypt-v2\n  clients that support a cookie based stateless three way handshake that\n  avoids replay attacks and state exhaustion on the server side (OpenVPN\n  2.6 and later). The option :code:`allow-noncookie` explicitly allows\n  older tls-crypt-v2 clients. The default is (currently)\n  :code:`allow-noncookie`.\n\n--tls-crypt-v2-verify cmd\n  Run command ``cmd`` to verify the metadata of the client-specific\n  tls-crypt-v2 key of a connecting client. This allows server\n  administrators to reject client connections, before exposing the TLS\n  stack (including the notoriously dangerous X.509 and ASN.1 stacks) to\n  the connecting client.\n\n  OpenVPN supplies the following environment variables to the command (and\n  only these variables. The normal environment variables available for\n  other scripts are NOT present):\n\n  * :code:`script_type` is set to :code:`tls-crypt-v2-verify`\n\n  * :code:`metadata_type` is set to :code:`0` if the metadata was user\n    supplied, or :code:`1` if it's a 64-bit unix timestamp representing\n    the key creation time.\n\n  * :code:`metadata_file` contains the filename of a temporary file that\n    contains the client metadata.\n\n  The command can reject the connection by exiting with a non-zero exit\n  code.\n\n--tls-crypt-v2-max-age n\n  Reject tls-crypt-v2 client keys that are older than n days or have\n  no timestamp.\n\n--tls-exit\n  Exit on TLS negotiation failure. This option can be useful when you only\n  want to make one attempt at connecting, e.g. in a test or monitoring script.\n  (OpenVPN's own test suite uses it this way.)\n\n--tls-server\n  Enable TLS and assume server role during TLS handshake. Note that\n  OpenVPN is designed as a peer-to-peer application. The designation of\n  client or server is only for the purpose of negotiating the TLS control\n  channel.\n\n--tls-timeout n\n  Packet retransmit timeout on TLS control channel if no acknowledgment\n  from remote within ``n`` seconds (default :code:`2`). When OpenVPN sends\n  a control packet to its peer, it will expect to receive an\n  acknowledgement within ``n`` seconds or it will retransmit the packet,\n  subject to a TCP-like exponential backoff algorithm. This parameter only\n  applies to control channel packets. Data channel packets (which carry\n  encrypted tunnel data) are never acknowledged, sequenced, or\n  retransmitted by OpenVPN because the higher level network protocols\n  running on top of the tunnel such as TCP expect this role to be left to\n  them.\n\n--tls-version-min args\n  Sets the minimum TLS version we will accept from the peer (default in\n  2.6.0 and later is \"1.2\").\n\n  Valid syntax:\n  ::\n\n     tls-version-min version ['or-highest']\n\n  Examples for version include :code:`1.0`, :code:`1.1`, or :code:`1.2`. If\n  :code:`or-highest` is specified and version is not recognized, we will\n  only accept the highest TLS version supported by the local SSL\n  implementation.\n\n--tls-version-max version\n  Set the maximum TLS version we will use (default is the highest version\n  supported). Examples for version include :code:`1.0`, :code:`1.1`, or\n  :code:`1.2`.\n\n--verify-hash args\n  **DEPRECATED** Specify SHA1 or SHA256 fingerprint for level-1 cert.\n\n  Valid syntax:\n  ::\n\n     verify-hash hash [algo]\n\n  The level-1 cert is the CA (or intermediate cert) that signs the leaf\n  certificate, and is one removed from the leaf certificate in the\n  direction of the root. When accepting a connection from a peer, the\n  level-1 cert fingerprint must match ``hash`` or certificate verification\n  will fail. Hash is specified as XX:XX:... For example:\n  ::\n\n     AD:B0:95:D8:09:C8:36:45:12:A9:89:C8:90:09:CB:13:72:A6:AD:16\n\n  The ``algo`` flag can be either :code:`SHA1` or :code:`SHA256`. If not\n  provided, it defaults to :code:`SHA1`.\n\n  This option can also be inlined\n  ::\n\n    <verify-hash>\n    00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff\n    11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00\n    </verify-hash>\n\nIf the option is inlined, ``algo`` is always :code:`SHA256`.\n\n--peer-fingerprint args\n   Specify a SHA256 fingerprint or list of SHA256 fingerprints to verify\n   the peer certificate against. The peer certificate must match one of the\n   fingerprint or certificate verification will fail. The option can also\n   be inlined\n\n  Valid syntax:\n  ::\n\n    peer-fingerprint AD:B0:95:D8:09:...\n\n  or inline:\n  ::\n\n    <peer-fingerprint>\n    00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff\n    11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00\n    </peer-fingerprint>\n\n  When the ``--peer-fingerprint`` option is used, specifying a CA with ``--ca`` or ``--capath`` is\n  optional. This allows the he ``--peer-fingerprint`` to be used as alternative to a PKI with\n  self-signed certificates for small setups. See the examples section for such a setup.\n\n--verify-x509-name args\n  Accept connections only if a host's X.509 name is equal to **name.** The\n  remote host must also pass all other tests of verification.\n\n  Valid syntax:\n  ::\n\n     verify-x509 name type\n\n  Which X.509 name is compared to ``name`` depends on the setting of type.\n  ``type`` can be :code:`subject` to match the complete subject DN\n  (default), :code:`name` to match a subject RDN or :code:`name-prefix` to\n  match a subject RDN prefix. Which RDN is verified as name depends on the\n  ``--x509-username-field`` option. But it defaults to the common name\n  (CN), e.g. a certificate with a subject DN\n  ::\n\n     C=KG, ST=NA, L=Bishkek, CN=Server-1\n\n  would be matched by:\n  ::\n\n     verify-x509-name 'C=KG, ST=NA, L=Bishkek, CN=Server-1'\n     verify-x509-name Server-1 name\n     verify-x509-name Server- name-prefix\n\n  The last example is useful if you want a client to only accept\n  connections to :code:`Server-1`, :code:`Server-2`, etc.\n\n  ``--verify-x509-name`` is a useful replacement for the ``--tls-verify``\n  option to verify the remote host, because ``--verify-x509-name`` works\n  in a ``--chroot`` environment without any dependencies.\n\n  Using a name prefix is a useful alternative to managing a CRL\n  (Certificate Revocation List) on the client, since it allows the client\n  to refuse all certificates except for those associated with designated\n  servers.\n\n  *NOTE:*\n      Test against a name prefix only when you are using OpenVPN\n      with a custom CA certificate that is under your control. Never use\n      this option with type :code:`name-prefix` when your client\n      certificates are signed by a third party, such as a commercial\n      web CA.\n\n--x509-track attribute\n  Save peer X509 **attribute** value in environment for use by plugins and\n  management interface. Prepend a :code:`+` to ``attribute`` to save values\n  from full cert chain. Otherwise the attribute will only be exported for\n  the leaf cert (i.e. depth :code:`0` of the cert chain). Values will be\n  encoded as :code:`X509_<depth>_<attribute>=<value>`. Multiple ``--x509-track``\n  options can be defined to track multiple attributes.\n\n  ``attribute`` can be any part of the X509 Subject field or any X509v3\n  extension (RFC 3280). X509v3 extensions might not be supported when\n  not using the default TLS backend library (OpenSSL). You can also\n  request the ``SHA1`` and ``SHA256`` fingerprints of the cert,\n  but that is always exported as :code:`tls_digest_{n}` and\n  :code:`tls_digest_sha256_{n}` anyway.\n\n  Note that by default **all** parts of the X509 Subject field are exported in\n  the environment for the whole cert chain. If you use ``--x509-track`` at least\n  once **only** the attributes specified by these options are exported.\n\n  Examples::\n\n    x509-track CN               # exports only X509_0_CN\n    x509-track +CN              # exports X509_{n}_CN for chain\n    x509-track basicConstraints # exports value of \"X509v3 Basic Constraints\"\n    x509-track SHA256           # exports SHA256 fingerprint\n\n--x509-username-field args\n  Fields in the X.509 certificate subject to be used as the username\n  (default :code:`CN`). If multiple fields are specified their values\n  will be concatenated into the one username using :code:`_` symbol\n  as a separator.\n\n  Valid syntax:\n  ::\n\n     x509-username-field [ext:]fieldname [[ext:]fieldname...]\n\n  Typically, this option is specified with **fieldname** arguments as\n  either of the following:\n  ::\n\n     x509-username-field emailAddress\n     x509-username-field 1.2.840.113549.1.9.1\n     x509-username-field ext:subjectAltName\n     x509-username-field CN serialNumber\n\n  The first two examples use the value of the :code:`emailAddress` attribute\n  in the certificate's Subject field as the username, where the first example\n  uses the name while the second example uses the oid. The third example\n  uses the :code:`ext:` prefix to signify that the X.509 extension\n  ``fieldname`` :code:`subjectAltName` be searched for an rfc822Name\n  (email) field to be used as the username. In cases where there are\n  multiple email addresses in :code:`ext:fieldname`, the last occurrence\n  is chosen. The last example uses the value of the :code:`CN` attribute\n  in the Subject field, combined with the :code:`_` separator and the\n  hexadecimal representation of the certificate's :code:`serialNumber`.\n\n  When this option is used, the ``--verify-x509-name`` option will match\n  against the chosen ``fieldname`` instead of the Common Name.\n\n  Only the :code:`subjectAltName` and :code:`issuerAltName` X.509\n  extensions and :code:`serialNumber` X.509 attribute are supported.\n\n  Non-compliant symbols are being replaced with the :code:`_` symbol, same as\n  the field separator, so concatenating multiple fields with such or :code:`_`\n  symbols can potentially lead to username collisions.\n"
  },
  {
    "path": "doc/man-sections/unsupported-options.rst",
    "content": "\nUNSUPPORTED OPTIONS\n===================\n\nOptions listed in this section have been removed from OpenVPN and are no\nlonger supported\n\n--client-cert-not-required\n  Removed in OpenVPN 2.5.  This should be replaced with\n  ``--verify-client-cert none``.\n\n--fast-io\n  Ignored since OpenVPN 2.7. This option became broken due to changes\n  to the event loop.\n\n--http-proxy-retry\n  Removed in OpenVPN 2.4.  All retries are controlled by ``--max-connect-retry``.\n\n--http-proxy-timeout\n  Removed in OpenVPN 2.4.  Connection timeout is controlled by\n  ``--connect-timeout``.\n\n--ifconfig-pool-linear\n  Removed in OpenVPN 2.5.  This should be replaced with ``--topology p2p``.\n\n--key-method\n  Removed in OpenVPN 2.5.  This option should not be used, as using the old\n  ``key-method`` weakens the VPN tunnel security.  The old ``key-method``\n  was also only needed when the remote side was older than OpenVPN 2.0.\n\n--management-client-pf\n  Removed in OpenVPN 2.6.  The built-in packet filtering (pf) functionality\n  has been removed.\n\n--max-routes\n  Removed in OpenVPN 2.4.  The limit was removed.\n\n--ncp-disable\n  Removed in OpenVPN 2.6.  This option mainly served a role as debug option\n  when NCP was first introduced.  It should no longer be necessary.\n\n--no-iv\n  Removed in OpenVPN 2.5.  This option should not be used as it weakens the\n  VPN tunnel security.  This has been a NOOP option since OpenVPN 2.4.\n\n--no-replay\n  Removed in OpenVPN 2.7.  This option should not be used as it weakens the\n  VPN tunnel security.  Previously we claimed to have removed this in\n  OpenVPN 2.5, but this wasn't actually the case.\n\n--prng\n  Removed in OpenVPN 2.6.  We now always use the PRNG of the SSL library.\n\n--persist-key\n  Ignored since OpenVPN 2.7. Keys are now always persisted across restarts.\n\n--opt-verify\n  Removed in OpenVPN 2.7.  This option does not make sense anymore as option\n  strings may not match due to the introduction of parameters negotiation.\n\n--socks-proxy-retry\n  Removed in OpenVPN 2.4.  All retries are controlled by ``--max-connect-retry``.\n\n--windows-driver\n  Removed in OpenVPN 2.7. OpenVPN will always use ovpn-dco as the default\n  driver on Windows. It will fall back to tap-windows6 if options are used\n  that are incompatible with ovpn-dco.\n\n--use-prediction-resistance\n  Removed in OpenVPN 2.8. This option caused the Mbed TLS 3 random number\n  generator to be reseeded on every call. It has been removed because this\n  is excessive.\n"
  },
  {
    "path": "doc/man-sections/virtual-routing-and-forwarding.rst",
    "content": "Virtual Routing and Forwarding\n------------------------------\n\nOptions in this section relates to configuration of virtual routing and\nforwarding in combination with the underlying operating system.\n\nAs of today this is only supported on Linux, a kernel >= 4.9 is\nrecommended.\n\nThis could come in handy when for example the external network should be\nonly used as a means to connect to some VPN endpoints and all regular\ntraffic should only be routed through any tunnel(s).  This could be\nachieved by setting up a VRF and configuring the interface connected to\nthe external network to be part of the VRF. The examples below will cover\nthis setup.\n\nAnother option would be to put the tun/tap interface into a VRF. This could\nbe done by an up-script which uses the :code:`ip link set` command shown\nbelow.\n\n\nVRF setup with iproute2\n```````````````````````\n\nCreate VRF :code:`vrf_external` and map it to routing table :code:`1023`\n\n::\n\n      ip link add vrf_external type vrf table 1023\n\nMove :code:`eth0` into :code:`vrf_external`\n\n::\n\n      ip link set master vrf_external dev eth0\n\nAny prefixes configured on :code:`eth0` will be moved from the :code`main`\nrouting table into routing table :code:`1023`\n\n\nVRF setup with ifupdown\n```````````````````````\n\nFor Debian based Distributions :code:`ifupdown2` provides an almost drop-in\nreplacement for :code:`ifupdown` including VRFs and other features.\nA configuration for an interface :code:`eth0` being part of VRF\ncode:`vrf_external` could look like this::\n\n      auto eth0\n      iface eth0\n          address 192.0.2.42/24\n          address 2001:db8:08:15::42/64\n          gateway 192.0.2.1\n          gateway 2001:db8:08:15::1\n          vrf vrf_external\n\n      auto vrf_external\n      iface vrf_external\n          vrf-table 1023\n\n\nOpenVPN configuration\n`````````````````````\nThe OpenVPN configuration needs to contain this line:\n\n::\n\n      bind-dev vrf_external\n\n\nFurther reading\n```````````````\n\nWikipedia has nice page one VRFs: https://en.wikipedia.org/wiki/Virtual_routing_and_forwarding\n\nThis talk from the Network Track of FrOSCon 2018 provides an overview about\nadvanced layer 2 and layer 3 features of Linux\n\n  - Slides: https://www.slideshare.net/BarbarossaTM/l2l3-fr-fortgeschrittene-helle-und-dunkle-magie-im-linuxnetzwerkstack\n  - Video (german): https://media.ccc.de/v/froscon2018-2247-l2\\_l3\\_fur\\_fortgeschrittene\\_-\\_helle\\_und\\_dunkle\\_magie\\_im\\_linux-netzwerkstack\n"
  },
  {
    "path": "doc/man-sections/vpn-network-options.rst",
    "content": "Virtual Network Adapter (VPN interface)\n---------------------------------------\n\nOptions in this section relates to configuration of the virtual tun/tap\nnetwork interface, including setting the VPN IP address and network\nrouting.\n\n--bind-dev device\n  (Linux only) Set ``device`` to bind the server socket to a\n  `Virtual Routing and Forwarding`_ device\n\n--block-ipv6\n  On the client, instead of sending IPv6 packets over the VPN tunnel, all\n  IPv6 packets are answered with an ICMPv6 no route host message. On the\n  server, all IPv6 packets from clients are answered with an ICMPv6 no\n  route to host message. This options is intended for cases when IPv6\n  should be blocked and other options are not available. ``--block-ipv6``\n  will use the remote IPv6 as source address of the ICMPv6 packets if set,\n  otherwise will use :code:`fe80::7` as source address.\n\n  For this option to make sense you actually have to route traffic to the\n  tun interface. The following example config block would send all IPv6\n  traffic to OpenVPN and answer all requests with no route to host,\n  effectively blocking IPv6 (to avoid IPv6 connections from dual-stacked\n  clients leaking around IPv4-only VPN services).\n\n  **Client config**\n    ::\n\n       --ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n       --redirect-gateway ipv6\n       --block-ipv6\n\n  **Server config**\n    Push a \"valid\" ipv6 config to the client and block on the server\n    ::\n\n       --push \"ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\"\n       --push \"redirect-gateway ipv6\"\n       --block-ipv6\n\n  Note: this option does not influence traffic sent from the server\n  towards the client (neither on the server nor on the client side).\n  This is not seen as necessary, as such traffic can be most easily\n  avoided by not configuring IPv6 on the server tun, or setting up a\n  server-side firewall rule.\n\n--dev device\n  TUN/TAP virtual network device which can be :code:`tunX`, :code:`tapX`,\n  :code:`null` or an arbitrary name string (:code:`X` can be omitted for\n  a dynamic device.)\n\n  See examples section below for an example on setting up a TUN device.\n\n  You must use either tun devices on both ends of the connection or tap\n  devices on both ends. You cannot mix them, as they represent different\n  underlying network layers:\n\n  :code:`tun`\n      devices encapsulate IPv4 or IPv6 (OSI Layer 3)\n\n  :code:`tap`\n      devices encapsulate Ethernet 802.3 (OSI Layer 2).\n\n  Valid syntaxes:\n  ::\n\n     dev tun2\n     dev tap4\n     dev ovpn\n\n  What happens if the device name is not :code:`tun` or :code:`tap` is\n  platform dependent.\n\n  On most platforms, :code:`tunN` (e.g. tun2, tun30) and :code:`tapN`\n  (e.g. tap3) will create a numbered tun/tap interface with the number\n  specified - this is useful if multiple OpenVPN instances are active,\n  and the instance-to-device mapping needs to be known.  Some platforms\n  do not support \"numbered tap\", so trying ``--dev tap3`` will fail.\n\n  Arbitrary device names (e.g. ``--dev tun-home``) will only work on\n  FreeBSD (with the DCO kernel driver for ``tun`` devices) and Linux\n  (for both ``tun`` and ``tap`` devices, DCO and tun/tap driver).\n\n  If such a device name starts with ``tun`` or ``tap`` (e.g. ``tun-home``),\n  OpenVPN will choose the right device type automatically.  Otherwise the\n  desired device type needs to be specified with ``--dev-type tun`` or\n  ``--dev-type tap``.\n\n  On Windows, only the names :code:`tun` and :code:`tap` are supported.\n  Selection among multiple installed drivers or driver instances is done\n  with ``--dev-node``.\n\n--dev-node node\n  This is a highly system dependent option to influence tun/tap driver\n  selection.\n\n  On Linux, tun/tap devices are created by accessing :code:`/dev/net/tun`,\n  and this device name can be changed using ``--dev-node ...``.\n\n  Under Mac OS X this option can be used to specify the default tun\n  implementation. Using ``--dev-node utun`` forces usage of the native\n  Darwin tun kernel support. Use ``--dev-node utunN`` to select a specific\n  utun instance. To force using the :code:`tun.kext` (:code:`/dev/tunX`)\n  use ``--dev-node tun``. When not specifying a ``--dev-node`` option\n  openvpn will first try to open utun, and fall back to tun.kext.\n\n  On Windows systems, select the TAP-Win32 adapter which is named ``node``\n  in the Network Connections Control Panel or the raw GUID of the adapter\n  enclosed by braces. The ``--show-adapters`` option under Windows can\n  also be used to enumerate all available TAP-Win32 adapters and will show\n  both the network connections control panel name and the GUID for each\n  TAP-Win32 adapter.\n\n  On other platforms, ``--dev-node node`` will influence the naming of the\n  created tun/tap device, if supported on that platform.  If OpenVPN cannot\n  figure out whether ``node`` is a TUN or TAP device based on the name,\n  you should also specify ``--dev-type tun`` or ``--dev-type tap``.\n\n  If ``node`` starts with the string ``unix:`` openvpn will treat the rest\n  of the argument as a program.\n  OpenVPN will start the program and create a temporary unix domain socket that\n  will be passed to the program together with the tun configuration as\n  environment variables.  The temporary unix domain socket  will be be passed\n  in the environment variable :code:`TUNTAP_SOCKET_FD`.\n\n  This ``unix:`` mode is designed mainly to use with the lwipovpn network\n  emulator (https://github.com/OpenVPN/lwipovpn).\n\n--dev-type device-type\n  Which device type are we using? ``device-type`` should be :code:`tun`\n  (OSI Layer 3) or :code:`tap` (OSI Layer 2). Use this option only if\n  the TUN/TAP device used with ``--dev`` does not begin with :code:`tun`\n  or :code:`tap`.\n\n--dhcp-option args\n  Set additional network parameters on supported platforms. May be specified\n  on the client or pushed from the server. On Windows these options are\n  handled by the ``tap-windows6`` driver by default or directly by OpenVPN\n  if dhcp is disabled. The ``OpenVPN for Android`` client also handles them internally.\n\n  On all other platforms these options are only saved in the client's\n  environment under the name :code:`foreign_option_{n}` before the\n  ``--up`` script is called. A plugin or an ``--up`` script must be used to\n  pick up and interpret these as required. Many Linux distributions include\n  such scripts and some third-party user interfaces such as tunnelblick also\n  come with scripts that process these options.\n\n  Valid syntax:\n  ::\n\n     dhcp-option type [parm]\n\n  :code:`DOMAIN` ``name``\n        Set Connection-specific DNS Suffix to :code:`name`.\n\n  :code:`ADAPTER_DOMAIN_SUFFIX` ``name``\n        Alias to :code:`DOMAIN`. This is a compatibility option, it\n        should not be used in new deployments.\n\n  :code:`DOMAIN-SEARCH` ``name``\n        Add :code:`name` to the domain search list.\n        Repeat this option to add more entries. Up to\n        10 domains are supported.\n\n  :code:`DNS` ``address``\n        Set primary domain name server IPv4 or IPv6 address.\n        Repeat this option to set secondary DNS server addresses.\n\n        Note: DNS IPv6 servers are currently set using netsh (the existing\n        DHCP code can only do IPv4 DHCP, and that protocol only permits\n        IPv4 addresses anywhere). The option will be put into the\n        environment, so an ``--up`` script could act upon it if needed.\n\n  :code:`WINS` ``address``\n        Set primary WINS server address (NetBIOS over TCP/IP Name Server).\n        Repeat this option to set secondary WINS server addresses.\n\n  :code:`NBDD` ``address``\n        Set primary NBDD server address (NetBIOS over TCP/IP Datagram\n        Distribution Server). Repeat this option to set secondary NBDD\n        server addresses.\n\n  :code:`NTP` ``address``\n        Set primary NTP server address (Network Time Protocol).\n        Repeat this option to set secondary NTP server addresses.\n\n  :code:`NBT` ``type``\n        Set NetBIOS over TCP/IP Node type. Possible options:\n\n        :code:`1`\n              b-node (broadcasts)\n\n        :code:`2`\n              p-node (point-to-point name queries to a WINS server)\n\n        :code:`4`\n              m-node (broadcast then query name server)\n\n        :code:`8`\n              h-node (query name server, then broadcast).\n\n  :code:`NBS` ``scope-id``\n        Set NetBIOS over TCP/IP Scope. A NetBIOS Scope ID provides an\n        extended naming service for the NetBIOS over TCP/IP (Known as NBT)\n        module. The primary purpose of a NetBIOS scope ID is to isolate\n        NetBIOS traffic on a single network to only those nodes with the\n        same NetBIOS scope ID. The NetBIOS scope ID is a character string\n        that is appended to the NetBIOS name. The NetBIOS scope ID on two\n        hosts must match, or the two hosts will not be able to communicate.\n        The NetBIOS Scope ID also allows computers to use the same computer\n        name, as they have different scope IDs. The Scope ID becomes a part\n        of the NetBIOS name, making the name unique. (This description of\n        NetBIOS scopes courtesy of NeonSurge@abyss.com)\n\n  :code:`DISABLE-NBT`\n        Disable Netbios-over-TCP/IP.\n\n  :code: `PROXY_HTTP` ``host`` ``port``\n        Sets a HTTP proxy that should be used when connected to the VPN.\n\n        This option currently only works on OpenVPN for Android and requires\n        Android 10 or later.\n\n--ifconfig args\n  Set TUN/TAP adapter parameters. It requires the *IP address* of the local\n  VPN endpoint. For TUN devices in point-to-point mode, the next argument\n  must be the VPN IP address of the remote VPN endpoint. For TAP devices,\n  or TUN devices used with ``--topology subnet``, the second argument\n  is the subnet mask of the virtual network segment which is being created\n  or connected to.\n\n  For TUN devices, which facilitate virtual point-to-point IP connections\n  (when used in ``--topology net30`` or ``p2p`` mode), the proper usage of\n  ``--ifconfig`` is to use two private IP addresses which are not a member\n  of any existing subnet which is in use. The IP addresses may be\n  consecutive and should have their order reversed on the remote peer.\n  After the VPN is established, by pinging ``rn``, you will be pinging\n  across the VPN.\n\n  For TAP devices, which provide the ability to create virtual ethernet\n  segments, or TUN devices in ``--topology subnet`` mode (which create\n  virtual \"multipoint networks\"), ``--ifconfig`` is used to set an IP\n  address and subnet mask just as a physical ethernet adapter would be\n  similarly configured. If you are attempting to connect to a remote\n  ethernet bridge, the IP address and subnet should be set to values which\n  would be valid on the bridged ethernet segment (note also that DHCP\n  can be used for the same purpose).\n\n  This option, while primarily a proxy for the ``ifconfig``\\(8) command,\n  is designed to simplify TUN/TAP tunnel configuration by providing a\n  standard interface to the different ifconfig implementations on\n  different platforms.\n\n  ``--ifconfig`` parameters which are IP addresses can also be specified\n  as a DNS or /etc/hosts file resolvable name.\n\n  For TAP devices, ``--ifconfig`` should not be used if the TAP interface\n  will be getting an IP address lease from a DHCP server.\n\n  Examples:\n  ::\n\n     # tun device in net30/p2p mode\n     ifconfig 10.8.0.2 10.8.0.1\n\n     # tun/tap device in subnet mode\n     ifconfig 10.8.0.2 255.255.255.0\n\n--ifconfig-ipv6 args\n  Configure an IPv6 address on the *tun* device.\n\n  Valid syntax:\n  ::\n\n     ifconfig-ipv6 ipv6addr/bits [ipv6remote]\n\n  The ``ipv6addr/bits`` argument is the IPv6 address to use. The\n  second parameter is used as route target for ``--route-ipv6`` if no\n  gateway is specified.\n\n  The ``--topology`` option has no influence with ``--ifconfig-ipv6``\n\n--ifconfig-noexec\n  Don't actually execute ifconfig/netsh commands, instead pass\n  ``--ifconfig`` parameters to scripts using environmental variables.\n\n--ifconfig-nowarn\n  Don't output an options consistency check warning if the ``--ifconfig``\n  option on this side of the connection doesn't match the remote side.\n  This is useful when you want to retain the overall benefits of the\n  options consistency check (also see ``--disable-occ`` option) while only\n  disabling the ifconfig component of the check.\n\n  For example, if you have a configuration where the local host uses\n  ``--ifconfig`` but the remote host does not, use ``--ifconfig-nowarn``\n  on the local host.\n\n  This option will also silence warnings about potential address conflicts\n  which occasionally annoy more experienced users by triggering \"false\n  positive\" warnings.\n\n--lladdr address\n  Specify the link layer address, more commonly known as the MAC address.\n  Only applied to TAP devices.\n\n--persist-tun\n  Don't close and reopen TUN/TAP device or run up/down scripts across\n  :code:`SIGUSR1` or ``--ping-restart`` restarts.\n\n  :code:`SIGUSR1` is a restart signal similar to :code:`SIGHUP`, but which\n  offers finer-grained control over reset options.\n\n  On Linux, this option can be useful when OpenVPN is not executed as\n  root and the CAP_NET_ADMIN has not been granted, because the process\n  would otherwise not be allowed to bring the interface down and back up.\n\n  Alongside the above, using ``--persist-tun`` allows the tunnel interface\n  to retain all IP/route settings, thus allowing the user to implement\n  any advanced traffic leaking protection (please note that for full\n  protection, extra route/firewall rules must be in place).\n\n--redirect-gateway flags\n  Automatically execute routing commands to cause all outgoing IP traffic\n  to be redirected over the VPN. This is a client-side option.\n\n  This option performs three steps:\n\n  (1)  Create a static route for the ``--remote`` address which\n       forwards to the pre-existing default gateway. This is done so that\n       ``(3)`` will not create a routing loop.\n\n  (2)  Delete the default gateway route.\n\n  (3)  Set the new default gateway to be the VPN endpoint address\n       (derived either from ``--route-gateway`` or the second parameter to\n       ``--ifconfig`` when ``--dev tun`` is specified).\n\n  When the tunnel is torn down, all of the above steps are reversed so\n  that the original default route is restored.\n\n  Option flags:\n\n  :code:`local`\n      Add the :code:`local` flag if both OpenVPN peers are directly\n      connected via a common subnet, such as with wireless. The\n      :code:`local` flag will cause step ``(1)`` above to be omitted.\n\n  :code:`autolocal`\n      Try to automatically determine whether to enable :code:`local`\n      flag above.\n\n  :code:`def1`\n      Use this flag to override the default gateway by using\n      :code:`0.0.0.0/1` and :code:`128.0.0.0/1` rather than\n      :code:`0.0.0.0/0`. This has the benefit of overriding but not\n      wiping out the original default gateway.\n\n  :code:`bypass-dhcp`\n      Add a direct route to the DHCP server (if it is non-local) which\n      bypasses the tunnel (Available on Windows clients, may not be\n      available on non-Windows clients).\n\n  :code:`bypass-dns`\n      Add a direct route to the DNS server(s) (if they are non-local)\n      which bypasses the tunnel (Available on Windows clients, may\n      not be available on non-Windows clients).\n\n  :code:`block-local`\n      Block access to local LAN when the tunnel is active, except for\n      the LAN gateway itself. This is accomplished by routing the local\n      LAN (except for the LAN gateway address) into the tunnel.\n      On Windows WFP filters are added in addition to the routes which\n      block access to resources not routed through the VPN adapter.\n      Push this flag to protect against TunnelCrack type of attacks\n      (see: https://tunnelcrack.mathyvanhoef.com/).\n\n  :code:`ipv6`\n      Redirect IPv6 routing into the tunnel. This works similar to\n      the :code:`def1` flag, that is, more specific IPv6 routes are added\n      (:code:`2000::/4`, :code:`3000::/4`), covering the whole IPv6\n      unicast space.\n\n  :code:`!ipv4`\n      Do not redirect IPv4 traffic - typically used in the flag pair\n      :code:`ipv6 !ipv4` to redirect IPv6-only.\n\n--redirect-private flags\n  Like ``--redirect-gateway``, but omit actually changing the default gateway.\n  Useful when pushing private subnets.\n\n--route-table id\n  Specify a default table id for use with --route.\n  By default, OpenVPN installs routes in the main routing\n  table of the operating system, but with this option,\n  a user defined routing table can be used instead.\n\n  (Supported on Linux only, on other platforms this is a no-op).\n\n--route args\n  Add route to routing table after connection is established. Multiple\n  routes can be specified. Routes will be automatically torn down in\n  reverse order prior to TUN/TAP device close.\n\n  Valid syntaxes:\n  ::\n\n      route network/IP\n      route network/IP netmask\n      route network/IP netmask gateway\n      route network/IP netmask gateway metric\n\n  This option is intended as a convenience proxy for the ``route``\\(8)\n  shell command, while at the same time providing portable semantics\n  across OpenVPN's platform space.\n\n  ``netmask``\n        defaults to :code:`255.255.255.255` when not given\n\n  ``gateway``\n        default taken from ``--route-gateway`` or the second\n        parameter to ``--ifconfig`` when ``--dev tun`` is specified.\n\n  ``metric``\n        default taken from ``--route-metric`` if set, otherwise :code:`0`.\n\n  The default can be specified by leaving an option blank or setting it to\n  :code:`default`.\n\n  The ``network`` and ``gateway`` parameters can also be specified as a\n  DNS or :code:`/etc/hosts` file resolvable name, or as one of three special\n  keywords:\n\n  :code:`vpn_gateway`\n      The remote VPN endpoint address (derived either from\n      ``--route-gateway`` or the second parameter to ``--ifconfig``\n      when ``--dev tun`` is specified).\n\n  :code:`net_gateway`\n      The pre-existing IP default gateway, read from the\n      routing table (not supported on all OSes).\n\n  :code:`remote_host`\n      The ``--remote`` address if OpenVPN is being run in\n      client mode, and is undefined in server mode.\n\n--route-delay args\n  Valid syntaxes:\n  ::\n\n       route-delay\n       route-delay n\n       route-delay n w\n\n  Delay ``n`` seconds (default :code:`0`) after connection establishment,\n  before adding routes. If ``n`` is :code:`0`, routes will be added\n  immediately upon connection establishment. If ``--route-delay`` is\n  omitted, routes will be added immediately after TUN/TAP device open and\n  ``--up`` script execution, before any ``--user`` or ``--group`` privilege\n  downgrade (or ``--chroot`` execution.)\n\n  This option is designed to be useful in scenarios where DHCP is used to\n  set tap adapter addresses. The delay will give the DHCP handshake time\n  to complete before routes are added.\n\n  On Windows, ``--route-delay`` tries to be more intelligent by waiting\n  ``w`` seconds (default :code:`30`) for the TAP-Win32 adapter\n  to come up before adding routes.\n\n--route-ipv6 args\n  Setup IPv6 routing in the system to send the specified IPv6 network into\n  OpenVPN's *tun*.\n\n  Valid syntaxes:\n  ::\n\n     route-ipv6 ipv6addr/bits\n     route-ipv6 ipv6addr/bits gateway\n     route-ipv6 ipv6addr/bits gateway metric\n\n  ``gateway``\n        Only used for IPv6 routes across *tap* devices,\n        and if missing, the ``ipv6remote`` field from ``--ifconfig-ipv6`` or\n        ``--route-ipv6-gateway`` is used.\n\n  ``metric``\n        default taken from ``--route-metric`` if set, otherwise :code:`0`.\n\n--route-gateway arg\n  Specify a default *gateway* for use with ``--route``.\n\n  If :code:`dhcp` is specified as the parameter, the gateway address will\n  be extracted from a DHCP negotiation with the OpenVPN server-side LAN.\n\n  Valid syntaxes:\n  ::\n\n      route-gateway gateway\n      route-gateway dhcp\n\n--route-ipv6-gateway gw\n  Specify a default gateway ``gw`` for use with ``--route-ipv6``.\n\n--route-metric m\n  Specify a default metric ``m`` for use with ``--route``.\n\n--route-noexec\n  Don't add or remove routes automatically. Instead pass routes to\n  ``--route-up`` script using environmental variables.\n\n--route-nopull\n  When used with ``--client`` or ``--pull``, accept options pushed by\n  server EXCEPT for routes, block-outside-dns and dhcp options like DNS\n  servers.\n\n  When used on the client, this option effectively bars the server from\n  adding routes to the client's routing table, however note that this\n  option still allows the server to set the TCP/IP properties of the\n  client's TUN/TAP interface.\n\n--topology mode\n  Configure virtual addressing topology when running in ``--dev tun``\n  mode. This directive has no meaning in ``--dev tap`` mode, which always\n  uses a :code:`subnet` topology.\n\n  If you set this directive on the server, the ``--server`` and\n  ``--server-bridge`` directives will automatically push your chosen\n  topology setting to clients as well. This directive can also be manually\n  pushed to clients. Like the ``--dev`` directive, this directive must\n  always be compatible between client and server.\n\n  ``mode`` can be one of:\n\n  :code:`subnet`\n    Use a subnet rather than a point-to-point topology by\n    configuring the tun interface with a local IP address and subnet mask,\n    similar to the topology used in ``--dev tap`` and ethernet bridging\n    mode. This mode allocates a single IP address per connecting client and\n    works on Windows as well. This is the default.\n\n  :code:`net30`\n    Use a point-to-point topology, by allocating one /30 subnet\n    per client. This is designed to allow point-to-point semantics when some\n    or all of the connecting clients might be Windows systems.\n\n  :code:`p2p`\n    Use a point-to-point topology where the remote endpoint of\n    the client's tun interface always points to the local endpoint of the\n    server's tun interface. This mode allocates a single IP address per\n    connecting client. Only use when none of the connecting clients are\n    Windows systems.\n\n  *Note:* Using ``--topology subnet`` changes the interpretation of the\n  arguments of ``--ifconfig`` to mean \"address netmask\", and not \"local\n  remote\".\n\n--tun-mtu args\n\n  Valid syntaxes:\n  ::\n\n      tun-mtu tun-mtu\n      tun-mtu tun-mtu occ-mtu\n\n  Take the TUN device MTU to be ``tun-mtu`` and derive the link MTU from it.\n  In most cases, you will probably want to leave this parameter set to\n  its default value.\n\n  The default for :code:`tun-mtu` is 1500.\n\n  The OCC MTU can be used to avoid warnings about mismatched MTU from\n  clients. If :code:`occ-mtu` is not specified, it will to default to the\n  tun-mtu.\n\n  The MTU (Maximum Transmission Units) is the maximum datagram size in\n  bytes that can be sent unfragmented over a particular network path.\n  OpenVPN requires that packets on the control and data channels be sent\n  unfragmented.\n\n  MTU problems often manifest themselves as connections which hang during\n  periods of active usage.\n\n  It's best to use the ``--fragment`` and/or ``--mssfix`` options to deal\n  with MTU sizing issues.\n\n  Note: Depending on the platform, the operating system allows one to receive\n  packets larger than ``tun-mtu`` (e.g. Linux and FreeBSD) but other platforms\n  (like macOS) limit received packets to the same size as the MTU.\n\n--tun-mtu-max maxmtu\n  This configures the maximum MTU size that a server can push to ``maxmtu``,\n  by configuring the internal buffers to allow at least this packet size.\n  The default for ``maxmtu`` is 1600. Currently, only increasing beyond 1600\n  is possible, and attempting to reduce max-mtu below 1600 will be ignored.\n\n--tun-mtu-extra n\n  Assume that the TUN/TAP device might return as many as ``n`` bytes more\n  than the ``--tun-mtu`` size on read. This parameter defaults to 0, which\n  is sufficient for most TUN devices. TAP devices may introduce additional\n  overhead in excess of the MTU size, and a setting of 32 is the default\n  when TAP devices are used. This parameter only controls internal OpenVPN\n  buffer sizing, so there is no transmission overhead associated with\n  using a larger value.\n\n\nTUN/TAP standalone operations\n-----------------------------\nThese two standalone operations will require ``--dev`` and optionally\n``--user`` and/or ``--group``.\n\n--mktun\n  (Standalone) Create a persistent tunnel on platforms which support them\n  such as Linux. Normally TUN/TAP tunnels exist only for the period of\n  time that an application has them open. This option takes advantage of\n  the TUN/TAP driver's ability to build persistent tunnels that live\n  through multiple instantiations of OpenVPN and die only when they are\n  deleted or the machine is rebooted.\n\n  One of the advantages of persistent tunnels is that they eliminate the\n  need for separate ``--up`` and ``--down`` scripts to run the appropriate\n  ``ifconfig``\\(8) and ``route``\\(8) commands. These commands can be\n  placed in the same shell script which starts or terminates an\n  OpenVPN session.\n\n  Another advantage is that open connections through the TUN/TAP-based\n  tunnel will not be reset if the OpenVPN peer restarts. This can be\n  useful to provide uninterrupted connectivity through the tunnel in the\n  event of a DHCP reset of the peer's public IP address (see the\n  ``--ipchange`` option above).\n\n  One disadvantage of persistent tunnels is that it is harder to\n  automatically configure their MTU value (see ``--link-mtu`` and\n  ``--tun-mtu`` above).\n\n  On some platforms such as Windows, TAP-Win32 tunnels are persistent by\n  default.\n\n--rmtun\n  (Standalone) Remove a persistent tunnel.\n"
  },
  {
    "path": "doc/man-sections/windows-options.rst",
    "content": "Windows-Specific Options\n-------------------------\n\nThese options are considered unknown on non-Windows platforms, resulting\nin fatal error (except ``--route-method``). You may want to use\n``--setenv opt`` or ``--ignore-unknown-option`` to ignore said error.\nNote that pushing unknown options from server does not trigger fatal\nerrors.\n\n--allow-nonadmin TAP-adapter\n  (Standalone) Set ``TAP-adapter`` to allow access from non-administrative\n  accounts. If ``TAP-adapter`` is omitted, all TAP adapters on the system\n  will be configured to allow non-admin access. The non-admin access\n  setting will only persist for the length of time that the TAP-Win32\n  device object and driver remain loaded, and will need to be re-enabled\n  after a reboot, or if the driver is unloaded and reloaded. This\n  directive can only be used by an administrator.\n\n--block-outside-dns\n  Block DNS servers on other network adapters to prevent DNS leaks. This\n  option prevents any application from accessing TCP or UDP port 53 except\n  one inside the tunnel. It uses Windows Filtering Platform (WFP) and\n  works on Windows Vista or later.\n\n--cryptoapicert select-string\n  *(Windows/OpenSSL Only)* Load the certificate and private key from the\n  Windows Certificate System Store.\n\n  Use this option instead of ``--cert`` and ``--key``.\n\n  This makes it possible to use any smart card, supported by Windows, but\n  also any kind of certificate, residing in the Cert Store, where you have\n  access to the private key. This option has been tested with a couple of\n  different smart cards (GemSAFE, Cryptoflex, and Swedish Post Office eID)\n  on the client side, and also an imported PKCS12 software certificate on\n  the server side.\n\n  To select a certificate, based on a substring search in the\n  certificate's subject:\n  ::\n\n     cryptoapicert \"SUBJ:Peter Runestig\"\n\n  To select a certificate, based on certificate's thumbprint (SHA1 hash):\n  ::\n\n     cryptoapicert \"THUMB:f6 49 24 41 01 b4 ...\"\n\n  The thumbprint hex string can easily be copy-and-pasted from the Windows\n  Certificate Store GUI. The embedded spaces in the hex string are optional.\n\n  To select a certificate based on a substring in certificate's\n  issuer name:\n  ::\n\n     cryptoapicert \"ISSUER:Sample CA\"\n\n  To select a certificate based on a certificate's template name or\n  OID of the template:\n  ::\n\n     cryptoapicert \"TMPL:Name of Template\"\n     cryptoapicert \"TMPL:1.3.6.1.4...\"\n\n  The first non-expired certificate found in the user's store or the\n  machine store that matches the select-string is used.\n\n--dhcp-release\n  Ask Windows to release the TAP adapter lease on shutdown. This option\n  has no effect now, as it is enabled by default starting with\n  OpenVPN 2.4.1.\n\n--dhcp-renew\n  Ask Windows to renew the TAP adapter lease on startup. This option is\n  normally unnecessary, as Windows automatically triggers a DHCP\n  renegotiation on the TAP adapter when it comes up, however if you set\n  the TAP-Win32 adapter Media Status property to \"Always Connected\", you\n  may need this flag.\n\n--ip-win32 method\n  When using ``--ifconfig`` on Windows, set the TAP-Win32 adapter IP\n  address and netmask using ``method``. Don't use this option unless you\n  are also using ``--ifconfig``.\n\n  :code:`manual`\n        Don't set the IP address or netmask automatically. Instead\n        output a message to the console telling the user to configure the\n        adapter manually and indicating the IP/netmask which OpenVPN\n        expects the adapter to be set to.\n\n  :code:`dynamic [offset] [lease-time]`\n        Automatically set the IP address and netmask by replying to DHCP\n        query messages generated by the kernel.  This mode is probably the\n        \"cleanest\" solution for setting the TCP/IP properties since it\n        uses the well-known DHCP protocol. There are, however, two\n        prerequisites for using this mode:\n\n        (1)  The TCP/IP properties for the TAP-Win32 adapter must be set\n             to \"Obtain an IP address automatically\", and\n\n        (2) OpenVPN needs to claim an IP address in the subnet for use\n            as the virtual DHCP server address.\n\n        By default in ``--dev tap`` mode, OpenVPN will take the normally\n        unused first address in the subnet. For example, if your subnet is\n        :code:`192.168.4.0 netmask 255.255.255.0`, then OpenVPN will take\n        the IP address :code:`192.168.4.0` to use as the virtual DHCP\n        server address.  In ``--dev tun`` mode, OpenVPN will cause the DHCP\n        server to masquerade as if it were coming from the remote endpoint.\n\n        The optional offset parameter is an integer which is > :code:`-256`\n        and < :code:`256` and which defaults to 0. If offset is positive,\n        the DHCP server will masquerade as the IP address at network\n        address + offset. If offset is negative, the DHCP server will\n        masquerade as the IP address at broadcast address + offset.\n\n        The Windows :code:`ipconfig /all` command can be used to show what\n        Windows thinks the DHCP server address is. OpenVPN will \"claim\"\n        this address, so make sure to use a free address. Having said that,\n        different OpenVPN instantiations, including different ends of\n        the same connection, can share the same virtual DHCP server\n        address.\n\n        The ``lease-time`` parameter controls the lease time of the DHCP\n        assignment given to the TAP-Win32 adapter, and is denoted in\n        seconds. Normally a very long lease time is preferred because it\n        prevents routes involving the TAP-Win32 adapter from being lost\n        when the system goes to sleep. The default lease time is one year.\n\n  :code:`netsh`\n        Automatically set the IP address and netmask using the Windows\n        command-line \"netsh\" command. This method appears to work correctly\n        on Windows XP but not Windows 2000.\n\n  :code:`ipapi`\n        Automatically set the IP address and netmask using the Windows IP\n        Helper API. This approach does not have ideal semantics, though\n        testing has indicated that it works okay in practice. If you use\n        this option, it is best to leave the TCP/IP properties for the\n        TAP-Win32 adapter in their default state, i.e. \"Obtain an IP\n        address automatically.\"\n\n  :code:`adaptive` (Default)\n        Try :code:`dynamic` method initially and fail over to :code:`netsh`\n        if the DHCP negotiation with the TAP-Win32 adapter does not succeed\n        in 20 seconds. Such failures have been known to occur when certain\n        third-party firewall packages installed on the client machine block\n        the DHCP negotiation used by the TAP-Win32 adapter. Note that if\n        the :code:`netsh` failover occurs, the TAP-Win32 adapter TCP/IP\n        properties will be reset from DHCP to static, and this will cause\n        future OpenVPN startups using the :code:`adaptive` mode to use\n        :code:`netsh` immediately, rather than trying :code:`dynamic` first.\n\n        To \"unstick\" the :code:`adaptive` mode from using :code:`netsh`,\n        run OpenVPN at least once using the :code:`dynamic` mode to restore\n        the TAP-Win32 adapter TCP/IP properties to a DHCP configuration.\n\n--pause-exit\n  Put up a \"press any key to continue\" message on the console prior to\n  OpenVPN program exit. This option is automatically used by the Windows\n  explorer when OpenVPN is run on a configuration file using the\n  right-click explorer menu.\n\n--register-dns\n  Run :code:`ipconfig /flushdns` and :code:`ipconfig /registerdns` on\n  connection initiation. This is known to kick Windows into recognizing\n  pushed DNS servers.\n\n--route-method m\n  Which method ``m`` to use for adding routes on Windows?\n\n  :code:`adaptive` (default)\n        Try IP helper API first. If that fails, fall back to the route.exe\n        shell command.\n\n  :code:`ipapi`\n        Use IP helper API.\n\n  :code:`exe`\n        Call the route.exe shell command.\n\n--service args\n  Should be used when OpenVPN is being automatically executed by another\n  program in such a context that no interaction with the user via display\n  or keyboard is possible.\n\n  Valid syntax:\n  ::\n\n     service exit-event [0|1]\n\n  In general, end-users should never need to explicitly use this option,\n  as it is automatically added by the OpenVPN service wrapper when a given\n  OpenVPN configuration is being run as a service.\n\n  ``exit-event`` is the name of a Windows global event object, and OpenVPN\n  will continuously monitor the state of this event object and exit when\n  it becomes signaled.\n\n  The second parameter indicates the initial state of ``exit-event`` and\n  normally defaults to 0.\n\n  Multiple OpenVPN processes can be simultaneously executed with the same\n  ``exit-event`` parameter. In any case, the controlling process can\n  signal ``exit-event``, causing all such OpenVPN processes to exit.\n\n  When executing an OpenVPN process using the ``--service`` directive,\n  OpenVPN will probably not have a console window to output status/error\n  messages, therefore it is useful to use ``--log`` or ``--log-append`` to\n  write these messages to a file.\n\n--show-adapters\n  (Standalone) Show available TAP-Win32 adapters which can be selected\n  using the ``--dev-node`` option. On non-Windows systems, the\n  ``ifconfig``\\(8) command provides similar functionality.\n\n--show-net\n  (Standalone) Show OpenVPN's view of the system routing table and network\n  adapter list.\n\n--show-net-up\n  Output OpenVPN's view of the system routing table and network adapter\n  list to the syslog or log file after the TUN/TAP adapter has been\n  brought up and any routes have been added.\n\n--show-valid-subnets\n  (Standalone) Show valid subnets for ``--dev tun`` emulation. Since the\n  TAP-Win32 driver exports an ethernet interface to Windows, and since TUN\n  devices are point-to-point in nature, it is necessary for the TAP-Win32\n  driver to impose certain constraints on TUN endpoint address selection.\n\n  Namely, the point-to-point endpoints used in TUN device emulation must\n  be the middle two addresses of a /30 subnet (netmask 255.255.255.252).\n\n--tap-sleep n\n  Cause OpenVPN to sleep for ``n`` seconds immediately after the TAP-Win32\n  adapter state is set to \"connected\".\n\n  This option is intended to be used to troubleshoot problems with the\n  ``--ifconfig`` and ``--ip-win32`` options, and is used to give the\n  TAP-Win32 adapter time to come up before Windows IP Helper API\n  operations are applied to it.\n\n--win-sys path\n  Set the Windows system directory pathname to use when looking for system\n  executables such as ``route.exe`` and ``netsh.exe``. By default, if this\n  directive is not specified, OpenVPN will use the SystemRoot environment\n  variable.\n\n  This option has changed behaviour since OpenVPN 2.3. Earlier you had to\n  define ``--win-sys env`` to use the SystemRoot environment variable,\n  otherwise it defaulted to :code:`C:\\\\WINDOWS`. It is not needed to use\n  the ``env`` keyword any more, and it will just be ignored. A warning is\n  logged when this is found in the configuration file.\n"
  },
  {
    "path": "doc/management-notes.txt",
    "content": "OpenVPN Management Interface Notes\n----------------------------------\n\nThe OpenVPN Management interface allows OpenVPN to\nbe administratively controlled from an external program via\na TCP or unix domain socket.\n\nThe interface has been specifically designed for developers\nwho would like to programmatically or remotely control\nan OpenVPN daemon, and can be used when OpenVPN is running\nas a client or server.\n\nThe management interface is implemented using a client/server TCP\nconnection or unix domain socket where OpenVPN will listen on a\nprovided IP address and port for incoming management interface client\nconnections.\n\nThe management protocol is currently cleartext without an explicit\nsecurity layer.  For this reason, it is recommended that the\nmanagement interface either listen on a unix domain socket,\nlocalhost (127.0.0.1), or on the local VPN address.  It's possible\nto remotely connect to the management interface over the VPN itself,\nthough some capabilities will be limited in this mode, such as the\nability to provide private key passwords.\n\nThe management interface is enabled in the OpenVPN\nconfiguration file using the following directive:\n\n--management\n\nSee the man page for documentation on this and related\ndirectives.\n\nOnce OpenVPN has started with the management layer enabled,\nyou can telnet to the management port (make sure to use\na telnet client which understands \"raw\" mode).\n\nOnce connected to the management port, you can use\nthe \"help\" command to list all commands.\n\nCOMMAND -- bytecount\n--------------------\n\nThe bytecount command is used to request real-time notification\nof OpenVPN bandwidth usage.\n\nCommand syntax:\n\n  bytecount n (where n > 0) -- set up automatic notification of\n                               bandwidth usage once every n seconds\n  bytecount 0 -- turn off bytecount notifications\n\nIf OpenVPN is running as a client, the bytecount notification\nwill look like this:\n\n  >BYTECOUNT:{BYTES_IN},{BYTES_OUT}\n\nBYTES_IN is the number of bytes that have been received from\nthe server and BYTES_OUT is the number of bytes that have been\nsent to the server.\n\nIf OpenVPN is running as a server, the bytecount notification\nwill look like this:\n\n  >BYTECOUNT_CLI:{CID},{BYTES_IN},{BYTES_OUT}\n \nCID is the Client ID, BYTES_IN is the number of bytes that have\nbeen received from the client and BYTES_OUT is the number of\nbytes that have been sent to the client.\n\nNote that when the bytecount command is used on the server, every\nconnected client will report its bandwidth numbers once every n\nseconds.\n\nWhen the client disconnects, the final bandwidth numbers will be\nplaced in the 'bytes_received' and 'bytes_sent' environmental variables\nas included in the >CLIENT:DISCONNECT notification.\n\nCOMMAND -- echo\n---------------\n\nThe echo capability is used to allow GUI-specific\nparameters to be either embedded in the OpenVPN config file\nor pushed to an OpenVPN client from a server.\n\nCommand examples:\n\n  echo on      -- turn on real-time notification of echo messages\n  echo all     -- print the current echo history list\n  echo off     -- turn off real-time notification of echo messages\n  echo on all  -- atomically enable real-time notification,\n                  plus show any messages in history buffer\n\nFor example, suppose you are developing a OpenVPN GUI and\nyou want to give the OpenVPN server the ability to ask\nthe GUI to forget any saved passwords.\n\nIn the OpenVPN server config file, add:\n\n  push \"echo forget-passwords\"\n\nWhen the OpenVPN client receives its pulled list of directives\nfrom the server, the \"echo forget-passwords\" directive will\nbe in the list, and it will cause the management interface\nto save the \"forget-passwords\" string in its list of echo\nparameters.\n\nThe management interface client can use \"echo all\" to output the full\nlist of echoed parameters, \"echo on\" to turn on real-time\nnotification of echoed parameters via the \">ECHO:\" prefix,\nor \"echo off\" to turn off real-time notification.\n\nWhen the GUI connects to the OpenVPN management socket, it\ncan issue an \"echo all\" command, which would produce output\nlike this:\n\n  1101519562,forget-passwords\n  END\n\nEssentially the echo command allowed us to pass parameters from\nthe OpenVPN server to the OpenVPN client, and then to the\nmanagement interface client (such as a GUI).  The large integer is the\nunix date/time when the echo parameter was received.\n\nIf the management interface client had issued the command \"echo on\",\nit would have enabled real-time notifications of echo\nparameters.  In this case, our \"forget-passwords\" message\nwould be output like this:\n\n  >ECHO:1101519562,forget-passwords\n\nLike the log command, the echo command can atomically show\nhistory while simultaneously activating real-time updates:\n\n  echo on all\n\nThe size of the echo buffer is currently hardcoded to 100\nmessages.\n\n\nGenerally speaking, the OpenVPN Core does not understand echo\nmessages at all (so a cooperating GUI and Server can use this\nmechanism for arbitrary information transport).\n\nThis said, a few echo commands have been agreed upon between the\ncommunity maintained OpenVPN Windows GUI and Tunnelblick for MacOS,\nand documentation of these can be found in doc/gui-notes.txt.\n\n\nCOMMAND -- exit, quit\n---------------------\n\nClose the management session, and resume listening on the\nmanagement port for connections from other clients. Currently,\nthe OpenVPN daemon can at most support a single management interface\nclient any one time.\n\nCOMMAND -- help\n---------------\n\nPrint a summary of commands.\n\nCOMMAND -- hold\n---------------\n\nThe hold command can be used to manipulate the hold flag,\nor release OpenVPN from a hold state.\n\nIf the hold flag is set on initial startup or\nrestart, OpenVPN will hibernate prior to initializing\nthe tunnel until the management interface receives\na \"hold release\" command.\n\nThe --management-hold directive of OpenVPN can be used\nto start OpenVPN with the hold flag set.\n\nThe hold flag setting is persistent and will not\nbe reset by restarts.\n\nOpenVPN will indicate that it is in a hold state by\nsending a real-time notification to the management interface\nclient, the parameter indicates how long OpenVPN would\nwait without UI (as influenced by connect-retry exponential\nbackoff). The UI needs to wait for releasing the hold if it\nwants similar behavior:\n\n  >HOLD:Waiting for hold release:10\n\nCommand examples:\n\n  hold         -- show current hold flag, 0=off, 1=on.\n  hold on      -- turn on hold flag so that future restarts\n                  will hold.\n  hold off     -- turn off hold flag so that future restarts will\n                  not hold.\n  hold release -- leave hold state and start OpenVPN, but\n                  do not alter the current hold flag setting.\n\nCOMMAND -- kill\n---------------\n\nIn server mode, kill a particular client instance.\n\nCommand examples:\n\n  kill Test-Client -- kill the client instance having a\n                      common name of \"Test-Client\".\n  kill tcp:1.2.3.4:4000 -- kill the client instance having a\n                           source address, port and proto of\n                           tcp:1.2.3.4:4000\n\n  Note that kill by address won't work for IPv6-connected\n  clients yet, so rely on kill by CN or CID instead.\n\nUse the \"status\" command to see which clients are connected.\n\nCOMMAND -- log\n--------------\n\nShow the OpenVPN log file.  Only the most recent n lines\nof the log file are cached by the management interface, where\nn is controlled by the OpenVPN --management-log-cache directive.\n\nCommand examples:\n\n  log on     -- Enable real-time output of log messages.\n  log all    -- Show currently cached log file history.\n  log on all -- Atomically show all currently cached log file\n                history then enable real-time notification of\n                new log file messages.\n  log off    -- Turn off real-time notification of log messages.\n  log 20     -- Show the most recent 20 lines of log file history.\n\nReal-time notification format:\n\nReal-time log messages begin with the \">LOG:\" prefix followed\nby the following comma-separated fields:\n\n  (a) unix integer date/time,\n  (b) zero or more message flags in a single string:\n      I -- informational\n      F -- fatal error\n      N -- non-fatal error\n      W -- warning\n      D -- debug, and\n  (c) message text.\n\nCOMMAND -- mute\n---------------\n\nChange the OpenVPN --mute parameter.  The mute parameter is\nused to silence repeating messages of the same message\ncategory.\n\nCommand examples:\n\n  mute 40 -- change the mute parameter to 40\n  mute    -- show the current mute setting\n\nCOMMAND -- net\n--------------\n\n(Windows Only) Produce output equivalent to the OpenVPN\n--show-net directive.  The output includes OpenVPN's view\nof the system network adapter list and routing table based\non information returned by the Windows IP helper API.\n\nCOMMAND -- pid\n--------------\n\nShows the process ID of the current OpenVPN process.\n\nCOMMAND -- password and username\n--------------------------------\n\n  The password command is used to pass passwords to OpenVPN.\n\n  If OpenVPN is run with the --management-query-passwords\n  directive, it will query the management interface for RSA\n  private key passwords and the --auth-user-pass\n  username/password.\n\n  When OpenVPN needs a password from the management interface,\n  it will produce a real-time \">PASSWORD:\" message.\n\n  Example 1:\n\n    >PASSWORD:Need 'Private Key' password\n\n  OpenVPN is indicating that it needs a password of type\n  \"Private Key\".\n\n  The management interface client should respond as follows:\n\n    password \"Private Key\" foo\n\n  Example 2:\n\n    >PASSWORD:Need 'Auth' username/password\n\n  OpenVPN needs a --auth-user-pass username and password.  The\n  management interface client should respond:\n\n    username \"Auth\" foo\n    password \"Auth\" bar\n\n  The username/password itself can be in quotes, and special\n  characters such as double quote or backslash must be escaped,\n  for example,\n\n    password \"Private Key\" \"foo\\\"bar\"\n\n  The escaping rules are the same as for the config file.\n  See the \"Command Parsing\" section below for more info.\n\n  The PASSWORD real-time message type can also be used to\n  indicate password or other types of authentication failure:\n\n  Example 3: The private key password is incorrect and OpenVPN\n  is exiting:\n\n    >PASSWORD:Verification Failed: 'Private Key'\n\n  Example 4: The --auth-user-pass username/password failed,\n  and OpenVPN will exit with a fatal error if '--auth-retry none'\n  (which is the default) is in effect:\n\n    >PASSWORD:Verification Failed: 'Auth'\n\n  Example 5: The --auth-user-pass username/password failed,\n  and the server provided a custom client-reason-text string\n  using the client-deny server-side management interface command.\n\n    >PASSWORD:Verification Failed: 'custom server-generated string'\n\n  Example 6: If server pushes --auth-token to the client, the OpenVPN\n  will produce a real-time PASSWORD message:\n\n    >PASSWORD:Auth-Token:foobar\n\n  Example 7: Static challenge/response:\n\n    >PASSWORD:Need 'Auth' username/password SC:1,Please enter token PIN\n\n  OpenVPN needs an --auth-user-pass username and password and the\n  response to a challenge. The user's response to \"Please enter\n  token PIN\" should be obtained and included in the management\n  interface client's response along with the username and password\n  formatted as described in the Challenge/Response Protocol section\n  below.\n\n  Example 8: Dynamic challenge/response:\n\n    >PASSWORD:Verification Failed: ['CRV1:R,E:Om01u7Fh4LrGBS7uh0SWmzwabUiGiW6l:Y3Ix:Please enter token PIN']\n\n  The previous --auth-user-pass username/password failed or is not\n  fully complete, and the server provided a custom\n  client-reason-text string indicating that a dynamic\n  challenge/response should occur the next time that a \"Need 'Auth'\n  username/password\" message is seen.\n\n  When the next \"Need 'Auth' username/password\" without a static\n  challenge is seen, the user's response to \"Please enter token PIN\"\n  should be obtained and included in the management interface client's\n  response along with the username and password formatted as described\n  in the Challenge/Response Protocol section below\n\nSee the \"Challenge/Response Protocol\" section below for more details\nabout examples 7 and 8, including how the management interface client\nshould respond.\n\nCOMMAND -- forget-passwords\n---------------------------\n\nThe forget-passwords command will cause the daemon to forget passwords\nentered during the session.\n\nCommand example:\n\n  forget-passwords -- forget passwords entered so far.\n\nCOMMAND -- signal\n-----------------\n\nThe signal command will send a signal to the OpenVPN daemon.\nThe signal can be one of SIGHUP, SIGTERM, SIGUSR1, or SIGUSR2.\n\nCommand example:\n\n  signal SIGUSR1 -- send a SIGUSR1 signal to daemon\n\nCOMMAND -- state\n----------------\n\nShow the current OpenVPN state, show state history, or\nenable real-time notification of state changes.\n\nThese are the OpenVPN states:\n\nCONNECTING    -- OpenVPN's initial state.\nWAIT          -- (Client only) Waiting for initial response\n                 from server.\nAUTH          -- (Client only) Authenticating with server.\nGET_CONFIG    -- (Client only) Downloading configuration options\n                 from server.\nASSIGN_IP     -- Assigning IP address to virtual network\n                 interface.\nADD_ROUTES    -- Adding routes to system.\nCONNECTED     -- Initialization Sequence Completed.\nRECONNECTING  -- A restart has occurred.\nEXITING       -- A graceful exit is in progress.\nRESOLVE       -- (Client only) DNS lookup\nTCP_CONNECT   -- (Client only) Connecting to TCP server\nAUTH_PENDING  -- (Client only) Authentication pending\n\nCommand examples:\n\n  state        -- Print current OpenVPN state.\n  state on     -- Enable real-time notification of state changes.\n  state off    -- Disable real-time notification of state changes.\n  state all    -- Print current state history.\n  state 3      -- Print the 3 most recent state transitions.\n  state on all -- Atomically show state history while at the\n                  same time enable real-time state notification\n\t\t  of future state transitions.\n\nThe output format consists of up to 9 comma-separated parameters:\n  (a) the integer unix date/time,\n  (b) the state name,\n  (c) optional descriptive string (used mostly on RECONNECTING\n      and EXITING to show the reason for the disconnect),\n  (d) optional TUN/TAP local IPv4 address\n  (e) optional address of remote server,\n  (f) optional port of remote server,\n  (g) optional local address,\n  (h) optional local port, and\n  (i) optional TUN/TAP local IPv6 address.\n\nFields (e)-(h) are shown for CONNECTED state,\n(d) and (i) are shown for ASSIGN_IP and CONNECTED states.\n\n(e) is available starting from OpenVPN 2.1\n(f)-(i) are available starting from OpenVPN 2.4\n\nFor AUTH_PENDING, if (c) is present, it would read\nas \"timeout number\" where number is the number of seconds\nbefore authentication will timeout. It is printed as an\nunsigned integer (%u).\n\nReal-time state notifications will have a \">STATE:\" prefix\nprepended to them.\n\nCOMMAND -- status\n-----------------\n\nShow current daemon status information, in the same format as\nthat produced by the OpenVPN --status directive.\n\nCommand examples:\n\nstatus   -- Show status information using the default status\n            format version.\n\nstatus 3 -- Show status information using the format of\n            --status-version 3.\n\nCOMMAND -- username\n-------------------\n\nSee the \"password\" section above.\n\nCOMMAND -- verb\n---------------\n\nChange the OpenVPN --verb parameter.  The verb parameter\ncontrols the output verbosity, and may range from 0 (no output)\nto 15 (maximum output).  See the OpenVPN man page for additional\ninfo on verbosity levels.\n\nCommand examples:\n\n  verb 4  -- change the verb parameter to 4\n  verb    -- show the current verb setting\n\nCOMMAND -- version\n------------------\n\nSet the version (integer) of Management Interface supported by the\nclient or show the current OpenVPN and Management Interface versions.\n\nCommand examples:\n  version 2  -- Change management version of client to 2 (default = 1)\n  version    -- Show the version of OpenVPN and its Management Interface\n\nNote: Until version 3, no response was generated when client sets its\nversion. This was fixed starting version 4: clients should expect\n\"SUCCESS: .. \" message only when setting the version to >= 4.\n\nMinimum client version required for certain features is listed below:\n    >PK_SIGN:[base64]           -- version 2 or greater\n    >PK_SIGN:[base64],[alg]     -- version 3 or greater\n\nCOMMAND -- auth-retry\n---------------------\n\nSet the --auth-retry setting to control how OpenVPN responds to\nusername/password authentication errors.  See the manual page\nfor more info.\n\nCommand examples:\n\n  auth-retry interact -- Don't exit when bad username/passwords are entered.\n                         Query for new input and retry.\n\nCOMMAND -- needok  (OpenVPN 2.1 or higher)\n------------------------------------------\n\nConfirm a \">NEED-OK\" real-time notification, normally used by\nOpenVPN to block while waiting for a specific user action.\n\nExample:\n\n  OpenVPN needs the user to insert a cryptographic token,\n  so it sends a real-time notification:\n\n    >NEED-OK:Need 'token-insertion-request' confirmation MSG:Please insert your cryptographic token\n\n  The management interface client, if it is a GUI, can flash a dialog\n  box containing the text after the \"MSG:\" marker to the user.\n  When the user acknowledges the dialog box,\n  the management interface client should issue either:\n\n     needok token-insertion-request ok\n  or\n     needok token-insertion-request cancel\n\nCOMMAND -- needstr  (OpenVPN 2.1 or higher)\n-------------------------------------------\n\nConfirm a \">NEED-STR\" real-time notification, normally used by\nOpenVPN to block while waiting for a specific user input.\n\nExample:\n\n  OpenVPN needs the user to specify some input, so it sends a\n  real-time notification:\n\n    >NEED-STR:Need 'name' input MSG:Please specify your name\n\n  The management interface client, if it is a GUI, can flash a dialog\n  box containing the text after the \"MSG:\" marker to the user.\n  When the user acknowledges the dialog box,\n  the management interface client should issue this command:\n\n     needstr name \"John\"\n\nCOMMAND -- pkcs11-id-count  (OpenVPN 2.1 or higher)\n---------------------------------------------------\n\nRetrieve available number of certificates.\n\nExample:\n\n     pkcs11-id-count\n     >PKCS11ID-COUNT:5\n\nCOMMAND -- pkcs11-id-get  (OpenVPN 2.1 or higher)\n-------------------------------------------------\n\nRetrieve certificate by index, the ID string should be provided\nas PKCS#11 identity, the blob is a base 64 encoded certificate.\n\nExample:\n\n     pkcs11-id-get 1\n     PKCS11ID-ENTRY:'1', ID:'<snip>', BLOB:'<snip>'\n\nCOMMAND -- client-auth  (OpenVPN 2.1 or higher)\n-----------------------------------------------\n\nAuthorize a \">CLIENT:CONNECT\" or \">CLIENT:REAUTH\" request and specify\n\"client-connect\" configuration directives in a subsequent text block.\n\nThe OpenVPN server should have been started with the\n--management-client-auth directive so that it will ask the management\ninterface to approve client connections.\n\n\n  client-auth {CID} {KID}\n  line_1\n  line_2\n  ...\n  line_n\n  END\n\nCID,KID -- client ID and Key ID.  See documentation for \">CLIENT:\"\nnotification for more info.\n\nline_1 to line_n -- client-connect configuration text block, as would be\nreturned by a --client-connect script.  The text block may be null, with\n\"END\" immediately following the \"client-auth\" line (using a null text\nblock is equivalent to using the client-auth-nt command).\n\nA client-connect configuration text block contains OpenVPN directives\nthat will be applied to the client instance object representing a newly\nconnected client.\n\nCOMMAND -- client-auth-nt  (OpenVPN 2.1 or higher)\n--------------------------------------------------\n\nAuthorize a \">CLIENT:CONNECT\" or \">CLIENT:REAUTH\" request without specifying\nclient-connect configuration text.\n\nThe OpenVPN server should have been started with the\n--management-client-auth directive so that it will ask the management\ninterface to approve client connections.\n\n  client-auth-nt {CID} {KID}\n\nCID,KID -- client ID and Key ID.  See documentation for \">CLIENT:\"\nnotification for more info.\n\nCOMMAND -- client-pending-auth  (OpenVPN 2.5 or higher)\n----------------------------------------------------\n\nInstruct OpenVPN server to send AUTH_PENDING and INFO_PRE message\nto signal a pending authenticating to the client. A pending auth means\nthat connecting requires extra authentication like a one time\npassword or doing a single sign on via web.\n\n    client-pending-auth {CID} {KID} {EXTRA} {TIMEOUT}\n\nThe server will send AUTH_PENDING and INFO_PRE,{EXTRA} to the client. If the\nclient supports accepting keywords to AUTH_PENDING (announced via IV_PROTO),\nTIMEOUT parameter will be also be announced to the client to allow it to modify\nits own timeout. The client is expected to inform the user that authentication\nis pending and display the extra information and also show the user the\nremaining time to complete the auth if applicable.\n\nReceiving an AUTH_PENDING message will make the client change its timeout to\nthe timeout proposed by the server, even if the timeout is shorter.\nIf the client does not receive a packet from the server for hand-window the\nconnection times out regardless of the timeout. This ensures that the connection\nstill times out relatively quickly in case of network problems. The client will\ncontinuously send PULL_REQUEST messages to the server until the timeout is reached.\nThis message also triggers an ACK message from the server that resets the\nhand-window based timeout.\n\nBoth client and server limit the maximum timeout to the smaller value of half the\n--tls-reneg minimum time and --hand-window time (defaults to 60s).\n\nFor the format of {EXTRA} see below. For OpenVPN server this is a stateless\noperation and needs to be followed by a client-deny/client-auth[-nt] command\n(that is the result of the out-of-band authentication).\n\nNote that the {KID} argument has been added in management version 5\nto specify the pending client key the authentication belongs to.\nThis ensures that the pending auth message is tied strictly to the\nauthentication session.\n\nBefore issuing a client-pending-auth to a client instead of a\nclient-auth/client-deny, the server should check the IV_SSO\nenvironment variable for whether the method is supported. Currently,\ndefined methods are crtext for challenge/response using text\n(e.g., TOTP), openurl (deprecated) and webauth for opening a URL in\nthe client to continue authentication. A client supporting webauth and\ncrtext would set\n\n    setenv IV_SSO webauth,crtext\n\nThe variable name IV_SSO is historic as AUTH_PENDING was first used\nto signal single sign on support. To keep compatibility with existing\nimplementations the name IV_SSO is kept in lieu of a better name.\n\nThe management interface of the client receives notification of\npending auth via\n\n>STATE:datetime,AUTH_PENDING,[timeout number],,,,,\n\nIf {EXTRA} is present the client is informed using INFOMSG\nnotification as\n\n>INFOMSG:{EXTRA}\n\nwhere {EXTRA} is formatted as received from the server.\nCurrently defined formats for {EXTRA} are detailed below.\n\nwebauth and openurl\n===================\nFor a web based extra authentication (like for\nSSO/SAML) {EXTRA} should be\n\n    OPEN_URL:url\n\nor\n\n    WEB_AUTH:flags:url\n\nThe OPEN_URL method is deprecated as it does not allow to send flags which\nproved to be needed to signal certain behaviour to the client.\n\nThe client should ask the user to open the URL to continue.\n\nThe space in a control message is limited, so this url should be kept\nshort to avoid issues. If a longer url is required a URL that redirects\nto the longer URL should be sent instead. The total length is limited to 1024\nbytes which includes the INFO_PRE:WEB_AUTH:flags.\n\nflags is a list of flags which are separated by commas. Currently defined\nflags are:\n\n- proxy     (see next pargraph)\n- hidden    start the webview in hidden mode (see openvpn3 webauth documentation)\n- external  Do not use an internal webview but use an external browser. Some\n            authentication providers refuse to work in an internal webview.\n\n\nA complete documentation how URLs should be handled on the client is available\nin the openvpn3 repository:\n\nhttps://github.com/OpenVPN/openvpn3/blob/master/doc/webauth.md\n\nwebauth with proxy\n==================\nThis is a variant of webauth that allows opening a url via an\nHTTP proxy. It could be used to avoid issues with OpenVPN connection's\npersist-tun that may cause the web server to be unreachable.\nThe client should announce proxy in its IV_SSO and parse the\nproxy flag in the WEB_AUTH message. The format of {EXTRA} in this case is\n\n    WEB_AUTH:proxy=<proxy>;<proxy_port>;<proxy_user_base64>;<proxy_password_base64>,flags:url\n\nThe proxy should be a literal IPv4 address or IPv6 address enclosed in [] to avoid\nambiguity in parsing. A literal IP address is preferred as DNS might not be\navailable when the client needs to open the url. The IP address will usually\nbe the address that client uses to connect to the VPN server. For dual-homed\nVPN servers, the server should respond with the same address that the client\nconnects to.\n\nThis address is also usually excluded from being redirected over the VPN\nby a host route. If the platform (like Android) uses another way of protecting\nthe VPN connection from routing loops, the client needs to also exclude the\nconnection to the proxy in the same manner.\n\nShould another IP be used, then the VPN configuration should include a route\nstatement to exclude that address from being routed over the VPN.\n\ncrtext\n=======\nThe format of {EXTRA} is similar to the already used two step authentication\ndescribed in Challenge/Response Protocol section of this document. Since\nmost of the fields are not necessary or can be inferred, only the <flags>\nand <challenge_text> fields are used:\n\n    CR_TEXT:<flags>:<challenge_text>\n\n<flags>: a series of optional, comma-separated flags:\n E : echo the response when the user types it.\n R : a response is required.\n\n<challenge_text>: the challenge text to be shown to the user.\n\nThe client should return the response to the crtext challenge\nusing the cr-response command.\n\nCOMMAND -- client-deny  (OpenVPN 2.1 or higher)\n-----------------------------------------------\n\nDeny a \">CLIENT:CONNECT\" or \">CLIENT:REAUTH\" request.\n\n  client-deny {CID} {KID} \"reason-text\" [\"client-reason-text\"]\n\nCID,KID -- client ID and Key ID.  See documentation for \">CLIENT:\"\nnotification for more info.\n\nreason-text: a human-readable message explaining why the authentication\nrequest was denied.  This message will be output to the OpenVPN log\nfile or syslog.\n\nclient-reason-text: a message that will be sent to the client as\npart of the AUTH_FAILED message.\n\nNote that client-deny denies a specific Key ID (pertaining to a\nTLS renegotiation).  A client-deny command issued in response to\nan initial TLS key negotiation (notified by \">CLIENT:CONNECT\") will\nterminate the client session after returning \"AUTH-FAILED\" to the client.\nOn the other hand, a client-deny command issued in response to\na TLS renegotiation (\">CLIENT:REAUTH\") will invalidate the renegotiated\nkey, however the TLS session associated with the currently active\nkey will continue to live for up to --tran-window seconds before\nexpiration.\n\nTo immediately kill a client session, use \"client-kill\".\n\nCOMMAND -- client-kill  (OpenVPN 2.1 or higher)\n-----------------------------------------------\n\nImmediately kill a client instance by CID.\n\n  client-kill {CID}\n\nCID -- client ID.  See documentation for \">CLIENT:\" notification for more\ninfo.\n\nCOMMAND -- remote-entry-count (OpenVPN 2.6+ management version > 3)\n-------------------------------------------------------------------\n\nRetrieve available number of remote host/port entries\n\nExample:\n\n  Management interface client sends:\n\n    remote-entry-count\n\n  OpenVPN daemon responds with\n\n  5\n  END\n\nCOMMAND -- remote-entry-get (OpenVPN 2.6+ management version > 3)\n------------------------------------------------------------------\n\n  remote-entry-get <start> [<end>]\n\nRetrieve remote entry (host, port, protocol, and status) for index\n<start> or indices from <start> to <end>-1. Alternatively\n<start> = \"all\" retrieves all remote entries. The index is 0-based.\nIf the entry is disabled due to protocol or proxy restrictions\n(i.e., ce->flag & CE_DISABLED == 1), the status is returned as \"disabled\",\notherwise it reads \"enabled\" without quotes.\n\nExample 1:\n\n  Management interface client sends:\n\n    remote-entry-get 1\n\n  OpenVPN daemon responds with\n\n    1,vpn.example.com,1194,udp,enabled\n    END\n\nExample 2:\n\n  Management interface client sends:\n\n    remote-entry-get 1 3\n\n  OpenVPN daemon responds with\n\n    1,vpn.example.com,1194,udp,enabled\n    2,vpn.example.net,443,tcp-client,disabled\n    END\n\nExample 3:\n  Management interface client sends:\n\n    remote-entry-get all\n\n  OpenVPN daemon with 3 connection entries responds with\n\n    0,vpn.example.com,1194,udp,enabled\n    1,vpn.example.com,443,tcp-client,enabled\n    2,vpn.example.net,443,udp,enabled\n    END\n\nCOMMAND -- remote  (OpenVPN AS 2.1.5/OpenVPN 2.3 or higher)\n--------------------------------------------\n\nProvide remote host/port in response to a >REMOTE notification\n(client only).  Requires that the --management-query-remote\ndirective is used.\n\n  remote ACTION [HOST PORT]\n\nThe \"remote\" command should only be given in response to a >REMOTE\nnotification.  For example, the following >REMOTE notification\nindicates that the client config file would ordinarily connect\nto vpn.example.com port 1194 (UDP):\n\n  >REMOTE:vpn.example.com,1194,udp\n\nNow, suppose we want to override the host and port, connecting\ninstead to vpn.otherexample.com port 1234.  After receiving\nthe above notification, use this command:\n\n  remote MOD vpn.otherexample.com 1234\n\nTo accept the same host and port as the client would ordinarily\nhave connected to, use this command:\n\n  remote ACCEPT\n\nTo skip the current connection entry and advance to the next one,\nuse this command:\n\n  remote SKIP\n\nStarting OpenVPN version 2.6 (management version > 3), skip\nmultiple remotes using:\n\n  remote SKIP n\n\nwhere n > 0 is the number of remotes to skip.\n\nCOMMAND -- proxy  (OpenVPN 2.3 or higher)\n--------------------------------------------\n\nProvide proxy server host/port and flags in response to a >PROXY\nnotification (client only).  Requires that the --management-query-proxy\ndirective is used.\n\n  proxy TYPE HOST PORT [\"nct\"]\n\nThe \"proxy\" command must only be given in response to a >PROXY\nnotification.  Use the \"nct\" flag if you only want to allow\nnon-cleartext auth with the proxy server.  The following >PROXY\nnotification indicates that the client config file would ordinarily\nconnect to the first --remote configured, vpn.example.com using TCP:\n\n  >PROXY:1,TCP,vpn.example.com\n\nNow, suppose we want to connect to the remote host using the proxy server\nproxy.intranet port 8080 with secure authentication only, if required.\nAfter receiving the above notification, use this command:\n\n  proxy HTTP proxy.intranet 8080 nct\n\nYou can also use the SOCKS keyword to pass a SOCKS server address, like:\n\n  proxy SOCKS fe00::1 1080\n\nTo accept connecting to the host and port directly, use this command:\n\n  proxy NONE\n\nCOMMAND -- cr-response (OpenVPN 2.5 or higher)\n-------------------------------------------------\nProvides support for sending responses to a challenge/response\nquery via INFOMSG,CR_TEXT (client-only). The response should\nbe base64 encoded:\n\n  cr-response SGFsbG8gV2VsdCE=\n\nThis command is intended to be used after the client receives a\nCR_TEXT challenge (see client-pending-auth section). The argument\nto cr-response is the base64 encoded answer to the challenge and\ndepends on the challenge itself. For a TOTP challenge this would be\na number encoded as base64; for a challenge like \"what day is it today?\"\nit would be a string encoded as base64.\n\nCOMMAND -- pk-sig (OpenVPN 2.5 or higher, management version > 1)\nCOMMAND -- rsa-sig (OpenVPN 2.3 or higher, management version <= 1)\n-----------------------------------------------------------------\nProvides support for external storage of the private key. Requires the\n--management-external-key option. This option can be used instead of \"key\"\nin client mode, and allows the client to run without the need to load the\nactual private key. When the SSL protocol needs to perform a sign\noperation, the data to be signed will be sent to the management interface\nvia a notification as follows:\n\n>PK_SIGN:[BASE64_DATA],[ALG] (if client announces support for management version > 2)\n>PK_SIGN:[BASE64_DATA] (if client announces support for management version > 1)\n>RSA_SIGN:[BASE64_DATA] (only older clients will be prompted like this)\n\nThe management interface client should then create an appropriate signature of\nthe (decoded) BASE64_DATA using the private key and return the SSL signature as\nfollows:\n\npk-sig (or rsa-sig)\n[BASE64_SIG_LINE]\n.\n.\n.\nEND\n\nBase 64 encoded output of RSA_private_encrypt for RSA or ECDSA_sign()\nfor EC using OpenSSL or mbedtls_pk_sign() using mbed TLS will provide a\ncorrect signature.\nThe rsa-sig interface expects PKCS1 padded signatures for RSA keys\n(RSA_PKCS1_PADDING). EC signatures are always unpadded.\n\nThis capability is intended to allow the use of arbitrary cryptographic\nservice providers with OpenVPN via the management interface.\n\nNew and updated clients are expected to use the version command to announce\na version > 1 and handle '>PK_SIGN' prompt and respond with 'pk-sig'.\n\nThe signature algorithm is indicated in the PK_SIGN request only if the\nmanagement client-version is > 2.  In particular, to support TLS1.3 and\nTLS1.2 using OpenSSL 1.1.1, unpadded signature support is required  and this\ncan be indicated in the signing request only if the client version is > 2\"\n\nThe currently defined padding algorithms are:\n\n - RSA_PKCS1_PADDING            -  PKCS1 padding and RSA signature\n - RSA_NO_PADDING               -  No padding may be added for the signature\n - ECDSA                        -  EC signature.\n - RSA_PKCS1_PSS_PADDING,params -  RSA signature with PSS padding\n\n   The params for PSS are specified as 'hashalg=name,saltlen=[max|digest]'.\n\n   The hashalg names are short common names such as SHA256, SHA224, etc.\n   PSS saltlen=\"digest\" means use the same size as the hash to sign, while\n   \"max\" indicates maximum possible saltlen which is\n   '(nbits-1)/8 - hlen - 2'. Here 'nbits' is the number of bits in the\n   key modulus and 'hlen' the size in octets of the hash.\n   (See: RFC 8017 sec 8.1.1 and 9.1.1)\n\n   In the case of PKCS1_PADDING, when the hash algorithm is not legacy\n   MD5-SHA1, the hash is encoded with DigestInfo header before presenting\n   to the management interface. This is identical to CKM_RSA_PKCS in Cryptoki\n   as well as what RSA_private_encrypt() in OpenSSL expects.\n\nCOMMAND -- certificate (OpenVPN 2.4 or higher)\n----------------------------------------------\nProvides support for external storage of the certificate. Requires the\n--management-external-cert option. This option can be used instead of \"cert\"\nin client mode. On SSL protocol initialization a notification will be sent\nto the management interface with a hint as follows:\n\n>NEED-CERTIFICATE:macosx-keychain:subject:o=OpenVPN-TEST\n\nThe management interface client should use the hint to obtain the specific\nSSL certificate and then return base 64 encoded certificate as follows:\n\ncertificate\n[BASE64_CERT_LINE]\n.\n.\n.\nEND\n\nThis capability is intended to allow the use of certificates\nstored outside of the filesystem (e.g. in Mac OS X Keychain)\nwith OpenVPN via the management interface.\n\nCOMMAND -- push-update-broad (OpenVPN 2.7 or higher)\n----------------------------------------------------\nSend a message to every connected client to update options at runtime.\nThe updatable options are: \"block-ipv6\", \"block-outside-dns\", \"dhcp-option\",\n\"dns\", \"ifconfig\", \"ifconfig-ipv6\", \"redirect-gateway\", \"redirect-private\",\n\"route\", \"route-gateway\", \"route-ipv6\", \"route-metric\", \"topology\",\n\"tun-mtu\", \"keepalive\". When a valid option is pushed, the receiving client will\ndelete every previous value and set new value, so the update of the option will\nnot be incremental even when theoretically possible (ex. with \"redirect-gateway\").\nThe '-' symbol in front of an option means the option should be removed.\nWhen an option is used with '-', it cannot take any parameter.\nThe '?' symbol in front of an option means the option's update is optional\nso if the client do not support it, that option will just be ignored without\nmaking fail the entire command. The '-' and '?' symbols can be used together.\n\nOption Format Ex.\n  `-?option`, `-option`, `?option parameters` are valid formats,\n  `?-option` is not a valid format.\n\nExample\n  push-update-broad \"route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400\"\n\nCOMMAND -- push-update-cid (OpenVPN 2.7 or higher)\n----------------------------------------------------\nSame as push-update-broad but you must target a single client using client id.\n\nExample\n  push-update-cid 42 \"route 10.10.10.1 255.255.255.255, -dns, ?tun-mtu 1400\"\n\nOUTPUT FORMAT\n-------------\n\n(1) Command success/failure indicated by \"SUCCESS: [text]\" or\n    \"ERROR: [text]\".\n\n(2) For commands which print multiple lines of output,\n    the last line will be \"END\".\n\n(3) Real-time messages will be in the form \">[source]:[text]\",\n    where source is \"CLIENT\", \"ECHO\", \"FATAL\", \"HOLD\", \"INFO\", \"LOG\",\n    \"NEED-OK\", \"PASSWORD\", or \"STATE\".\n\nREAL-TIME MESSAGE FORMAT\n------------------------\n\nThe OpenVPN management interface produces two kinds of\noutput: (a) output from a command, or (b) asynchronous,\nreal-time output which can be generated at any time.\n\nReal-time messages start with a '>' character in the first\ncolumn and are immediately followed by a type keyword\nindicating the type of real-time message.  The following\ntypes are currently defined:\n\nBYTECOUNT -- Real-time bandwidth usage notification, as enabled\n             by \"bytecount\" command when OpenVPN is running as\n             a client.\n\nBYTECOUNT_CLI -- Real-time bandwidth usage notification per-client,\n\t         as enabled by \"bytecount\" command when OpenVPN is\n                 running as a server.\n\nCLIENT   -- Notification of client connections and disconnections\n            on an OpenVPN server.  Enabled when OpenVPN is started\n            with the --management-client-auth option.  CLIENT\n            notifications may be multi-line.  See \"The CLIENT\n            notification\" section below for detailed info.\n\nECHO     -- Echo messages as controlled by the \"echo\" command.\n\nFATAL    -- A fatal error which is output to the log file just\n            prior to OpenVPN exiting.\n\nHOLD     -- Used to indicate that OpenVPN is in a holding state\n            and will not start until it receives a\n            \"hold release\" command.\n\nINFO     -- Informational messages such as the welcome message.\n\nLOG      -- Log message output as controlled by the \"log\" command.\n\nNEED-OK  -- OpenVPN needs the end user to do something, such as\n            insert a cryptographic token.  The \"needok\" command can\n            be used to tell OpenVPN to continue.\n\nNEED-STR -- OpenVPN needs information from end, such as\n            a certificate to use.  The \"needstr\" command can\n            be used to tell OpenVPN to continue.\n\nPASSWORD -- Used to tell the management interface client that OpenVPN\n            needs a password, also to indicate password\n            verification failure.\n\nSTATE    -- Shows the current OpenVPN state, as controlled\n            by the \"state\" command.\n\nINFOMSG  -- Authentication related info from server such as\n            CR_TEXT or OPEN_URL. See description under client-pending-auth\n\nThe CLIENT notification\n-----------------------\n\nThe \">CLIENT:\" notification is enabled by the --management-client-auth\nOpenVPN configuration directive that gives the management interface client\nthe responsibility to authenticate OpenVPN clients after their client\ncertificate has been verified.  CLIENT notifications may be multi-line, and\nthe sequentiality of a given CLIENT notification, its associated environmental\nvariables, and the terminating \">CLIENT:ENV,END\" line are guaranteed to be\natomic.\n\nCLIENT notification types:\n\n(1) Notify new client connection (\"CONNECT\") or existing client TLS session\n    renegotiation (\"REAUTH\").  Information about the client is provided\n    by a list of environmental variables which are documented in the OpenVPN\n    man page.  The environmental variables passed are equivalent to those\n    that would be passed to an --auth-user-pass-verify script.\n\n    >CLIENT:CONNECT|REAUTH,{CID},{KID}\n    >CLIENT:ENV,name1=val1\n    >CLIENT:ENV,name2=val2\n    >CLIENT:ENV,...\n    >CLIENT:ENV,END\n\n(2) Notify successful client authentication and session initiation.\n    Called after CONNECT.\n\n    >CLIENT:ESTABLISHED,{CID}\n    >CLIENT:ENV,name1=val1\n    >CLIENT:ENV,name2=val2\n    >CLIENT:ENV,...\n    >CLIENT:ENV,END\n\n(3) Notify existing client disconnection.  The environmental variables passed\n    are equivalent to those that would be passed to a --client-disconnect\n    script.\n\n    >CLIENT:DISCONNECT,{CID}\n    >CLIENT:ENV,name1=val1\n    >CLIENT:ENV,name2=val2\n    >CLIENT:ENV,...\n    >CLIENT:ENV,END\n\n(4) Notify that a particular virtual address or subnet\n    is now associated with a specific client.\n\n    >CLIENT:ADDRESS,{CID},{ADDR},{PRI}\n\n(5) Text based challenge/Response\n\n   >CLIENT:CR_RESPONSE,{CID},{KID},{response_base64}\n   >CLIENT:ENV,name1=val1\n   >CLIENT:ENV,name2=val2\n   >CLIENT:ENV,...\n   >CLIENT:ENV,END\n\n   Use of the cr-response command on the client side will trigger this\n   message on the server side.\n\n   CR_RESPONSE notification fulfills the same purpose as the\n   CRV1 response in the traditional challenge/response. See that section\n   below for more details. Since this uses the same cid as in the original\n   client-pending-auth challenge,  we do not include the username and opaque\n   session data in this notification. The string {response_base64} only contains\n   the actual response received from the client.\n\n   It is important to note that OpenVPN2 merely passes the authentication\n   information and does not do any further checks. (E.g. if a CR was issued\n   before or if multiple CR responses were sent from the client or if\n   data has a valid base64 encoding)\n\n   This interface should be be sufficient for almost all challenge/response\n   system that can be implemented with a single round and base64 encoding of the\n   response. Mechanisms that need multiple rounds or more complex answers\n   should implement a different response type than CR_RESPONSE.\n\n\nVariables:\n\nCID --  Client ID, numerical ID for each connecting client, sequence = 0,1,2,...\nKID --  Key ID, numerical ID for the key associated with a given client TLS session,\n        sequence = 0,1,2,...\nPRI --  Primary (1) or Secondary (0) VPN address/subnet.  All clients have at least\n        one primary IP address.  Secondary address/subnets are associated with\n        client-specific \"iroute\" directives.\nADDR -- IPv4 address/subnet in the form 1.2.3.4 or 1.2.3.0/255.255.255.0\n\nIn the unlikely scenario of an extremely long-running OpenVPN server,\nCID and KID should be assumed to recycle to 0 after (2^32)-1, however this\nrecycling behavior is guaranteed to be collision-free.\n\nCommand Parsing\n---------------\n\nThe management interface uses the same command line lexical analyzer\nas is used by the OpenVPN config file parser.\n\nWhitespace is a parameter separator.\n\nDouble quotation or single quotation characters (\"\", '') can be used\nto enclose parameters containing whitespace.\n\nBackslash-based shell escaping is performed, using the following\nmappings, when not in single quotations:\n\n\\\\       Maps to a single backslash character (\\).\n\\\"       Pass a literal doublequote character (\"), don't\n         interpret it as enclosing a parameter.\n\\[SPACE] Pass a literal space or tab character, don't\n         interpret it as a parameter delimiter.\n\nChallenge/Response Protocol\n---------------------------\n\nThe OpenVPN Challenge/Response Protocol allows an OpenVPN server to\ngenerate challenge questions that are shown to the user, and to see\nthe user's responses to those challenges.  Based on the responses, the\nserver can allow or deny access.\n\nThe protocol can be used to implement multi-factor authentication\nbecause the user must enter an additional piece of information,\nin addition to a username and password, to successfully authenticate.\nIn multi-factor authentication, this information is used to prove\nthat the user possesses a certain key-like device such as\ncryptographic token or a particular mobile phone.\n\nTwo variations on the challenge/response protocol are supported:\nthe \"static\" and \"dynamic\" protocols:\n\n * The static protocol uses OpenVPN's \"--static-challenge\" option.\n\n * The dynamic protocol does not involve special OpenVPN options\n   or actions. It is an agreement between the auth-user-pass\n   verification process on the server and the management interface\n   client to use custom strings that begin with \"['CRV1\" in\n   \"Verification Failed\" messages. (The \"[\" character and a matching\n   \"]\" character at the end of the message are added by the client\n   OpenVPN program, and are not present in the string generated by the\n   auth-user-pass verification process or in the string sent by the\n   server.)\n\nDynamic protocol:\n\nThe OpenVPN dynamic challenge/response protocol works by returning\na specially formatted error message after initial successful\nauthentication.  The error message has two purposes:\n\n 1. It causes OpenVPN to restart the connection attempt.\n\n 2. It contains information about the challenge, which should be used\n    to construct the response to the next authentication request (which\n    will occur after the restart).\n\nNotes:\n\n * '--auth-retry interact' must be in effect so that the\n   connection is restarted and credentials are requested again.\n\n * '--auth-retry none' (which is the default) will cause\n  OpenVPN to exit with a fatal error without retrying and the dynamic\n  challenge/response will never happen because \"Need 'Auth'\n  username/password\" will not be sent.\n\nThe error message is formatted as follows:\n\n   >PASSWORD:Verification Failed: 'Auth' ['CRV1:<flags>:<state_id>:<username_base64>:<challenge_text>']\n\n<flags>: a series of optional, comma-separated flags:\n E : echo the response when the user types it.\n R : a response is required.\n\n<state_id>: an opaque string that should be returned to the server\n          along with the response.\n\n<username_base64>: the username encoded as base 64.\n\n<challenge_text>: the challenge text to be shown to the user.\n\n<state_id> may not contain colon characters (\":\"), but <challenge_text>\nmay.\n\nExample challenge:\n\n  CRV1:R,E:Om01u7Fh4LrGBS7uh0SWmzwabUiGiW6l:Y3Ix:Please enter token PIN\n\nThe next time the username and password are requested with\n\n   >PASSWORD:Need 'Auth' username/password\n\nthe management interface client should display the challenge text and,\nif the R flag is specified, get a response from the user. The management\ninterface client should respond:\n\n   username \"Auth\" <username>\n   password \"Auth\" CRV1::<state_id>::<response_text>\n\nWhere <username> is the username decoded from <username_base64>,\n<state_id> is taken from the challenge request, and <response_text>\nis what the user entered in response to the challenge, which can be an\nempty string.  If the R flag is not present, <response_text> should\nbe an empty string.\n\n(As in all username/password responses described in the \"COMMAND --\npassword and username\" section above, the username and/or password\ncan be in quotes, and special characters such as double quotes or\nbackslashes must be escaped. See the \"Command Parsing\" section above\nfor more info.)\n\nExample response (suppose the user enters \"8675309\" for the token PIN):\n\n   username \"Auth\" cr1\n   password \"Auth\" CRV1::Om01u7Fh4LrGBS7uh0SWmzwabUiGiW6l::8675309\n\n(\"Y3Ix\" is the base 64 encoding of \"cr1\".)\n\nStatic protocol:\n\nThe static protocol differs from the dynamic protocol in that the\nchallenge question is sent to the management interface client in a\na username/password request, and the username, password, and\nresponse are delivered back to the server in response to that\nrequest.\n\nOpenVPN's --static-challenge option is used to provide the\nchallenge text to OpenVPN and indicate whether or not the response\nshould be echoed and how the response should be combined with the\npassword.\n\nWhen credentials are needed and the --static-challenge option is\nused, the management interface will send:\n\n  >PASSWORD:Need 'Auth' username/password SC:<flag>,<TEXT>\n\n  flag: an integer whose least significant bit is the ECHO flag and\n        the next significant bit is the FORMAT flag.\n        ECHO = (flag & 0x1) is 1 if response should be echoed, 0 to not echo\n        FORMAT = (flag & 0x2) is 1 if response should be concatenated with\n        password as plain text, 0 if response and password should be encoded\n        as described below. Thus flag could take values 0, 1, 2, or 3.\n  TEXT: challenge text that should be shown to the user to\n      facilitate their response\n\nFor example:\n\n  >PASSWORD:Need 'Auth' username/password SC:1,Please enter token PIN\n\nThe above notification indicates that OpenVPN needs a --auth-user-pass\nusername and password plus a response to a static challenge (\"Please\nenter token PIN\"). The \"1\" after the \"SC:\" indicates that the response\nshould be echoed.\n\nThe management interface client in this case should add the static\nchallenge text to the auth dialog followed by a field for the user to\nenter a response. If flag = 0 or 1 (i.e., FORMAT=0), the management\ninterface client should pack the password and response together into\nan encoded password and send:\n\n  username \"Auth\" <username>\n  password \"Auth\" \"SCRV1:<password_base64>:<response_base64>\"\n\nWhere <username> is the username entered by the user, <password_base64>\nis the base 64 encoding of the password entered by the user, and\n<response_base64> is the base 64 encoding of the response entered by\nthe user. The <password_base64> and/or the <response_base64> can be\nempty strings.\n\nIf flag = 2 or 3 (i.e., FORMAT=1), the client should simply concatenate\npassword and response with no separator and send:\n\n  username \"Auth\" <username>\n  password \"Auth\" \"<password><response>\"\n\n(As in all username/password responses described in the \"COMMAND --\npassword and username\" section above, the username can be in quotes,\nand special characters such as double quotes or backslashes must be\nescaped. See the \"Command Parsing\" section above for more info.)\n\nFor example, if user \"foo\" entered \"bar\" as the password and 8675309\nas the PIN, the following management interface commands should be\nissued if flag = 0 or 1 (i.e., FORMAT = 0):\n\n  username \"Auth\" foo\n  password \"Auth\" \"SCRV1:YmFy:ODY3NTMwOQ==\"\n\n  (\"YmFy\" is the base 64 encoding of \"bar\" and \"ODY3NTMwOQ==\" is the\n   base 64 encoding of \"8675309\".)\n\nor, if flag = 2 or 3 (i.e., FORMAT = 1):\n\n  username \"Auth\" foo\n  password \"Auth\" \"bar8675309\"\n"
  },
  {
    "path": "doc/openvpn-examples.5.rst",
    "content": "===============================\n openvpn examples\n===============================\n-------------------------\n Secure IP tunnel daemon\n-------------------------\n\n:Manual section: 5\n:Manual group: Configuration files\n\n\nINTRODUCTION\n============\n\nThis man page gives a few simple examples to create OpenVPN setups and configuration files.\n\n.. include:: man-sections/example-fingerprint.rst\n.. include:: man-sections/examples.rst\n"
  },
  {
    "path": "doc/openvpn.8.rst",
    "content": "=========\n openvpn\n=========\n-------------------------\n Secure IP tunnel daemon\n-------------------------\n\n:Manual section: 8\n:Manual group: System Manager's Manual\n\n\n\nSYNOPSIS\n========\n| ``openvpn`` [ options ... ]\n| ``openvpn``  ``--help``\n\n\n\nINTRODUCTION\n============\n\nOpenVPN is an open source VPN daemon by James Yonan. Because OpenVPN\ntries to be a universal VPN tool offering a great deal of flexibility,\nthere are a lot of options on this manual page. If you're new to\nOpenVPN, you might want to skip ahead to the examples section where you\nwill see how to construct simple VPNs on the command line without even\nneeding a configuration file.\n\nAlso note that there's more documentation and examples on the OpenVPN\nweb site: https://openvpn.net/\n\nAnd if you would like to see a shorter version of this manual, see the\nopenvpn usage message which can be obtained by running **openvpn**\nwithout any parameters.\n\n\n\nDESCRIPTION\n===========\n\nOpenVPN is a robust and highly flexible VPN daemon. OpenVPN supports\nSSL/TLS security, ethernet bridging, TCP or UDP tunnel transport through\nproxies or NAT, support for dynamic IP addresses and DHCP, scalability\nto hundreds or thousands of users, and portability to most major OS\nplatforms.\n\nOpenVPN is tightly bound to the OpenSSL library, and derives much of its\ncrypto capabilities from it.\n\nOpenVPN supports conventional encryption using a pre-shared secret key\n**(Static Key mode)** or public key security **(SSL/TLS mode)** using\nclient & server certificates. OpenVPN also supports non-encrypted\nTCP/UDP tunnels.\n\nOpenVPN is designed to work with the **TUN/TAP** virtual networking\ninterface that exists on most platforms.\n\nOverall, OpenVPN aims to offer many of the key features of IPSec but\nwith a relatively lightweight footprint.\n\n\n\nOPTIONS\n=======\n\nOpenVPN allows any option to be placed either on the command line or in\na configuration file. Though all command line options are preceded by a\ndouble-leading-dash (\"--\"), this prefix can be removed when an option is\nplaced in a configuration file.\n\n.. include:: man-sections/generic-options.rst\n.. include:: man-sections/log-options.rst\n.. include:: man-sections/protocol-options.rst\n.. include:: man-sections/client-options.rst\n.. include:: man-sections/server-options.rst\n.. include:: man-sections/encryption-options.rst\n.. include:: man-sections/cipher-negotiation.rst\n.. include:: man-sections/network-config.rst\n.. include:: man-sections/script-options.rst\n.. include:: man-sections/management-options.rst\n.. include:: man-sections/plugin-options.rst\n.. include:: man-sections/windows-options.rst\n.. include:: man-sections/advanced-options.rst\n.. include:: man-sections/unsupported-options.rst\n.. include:: man-sections/connection-profiles.rst\n.. include:: man-sections/inline-files.rst\n.. include:: man-sections/signals.rst\n\n\nFAQ\n===\n\nhttps://community.openvpn.net/openvpn/wiki/FAQ\n\n\n\nHOWTO\n=====\nThe manual ``openvpn-examples``\\(5) gives some examples, especially for\nsmall setups.\n\nFor a more comprehensive guide to setting up OpenVPN in a production\nsetting, see the OpenVPN HOWTO at\nhttps://openvpn.net/community-resources/how-to/\n\n\n\nPROTOCOL\n========\n\nAn ongoing effort to document the OpenVPN protocol can be found under\nhttps://github.com/openvpn/openvpn-rfc\n\n\nWEB\n===\n\nOpenVPN's web site is at https://community.openvpn.net/\n\nGo here to download the latest version of OpenVPN, subscribe to the\nmailing lists, read the mailing list archives, or browse the Git\nrepository.\n\n\n\nBUGS\n====\n\nReport all bugs to the OpenVPN team info@openvpn.net\n\n\n\nSEE ALSO\n========\n\n``openvpn-examples``\\(5),\n``dhcpcd``\\(8),\n``ifconfig``\\(8),\n``openssl``\\(1),\n``route``\\(8),\n``scp``\\(1)\n``ssh``\\(1)\n\n\n\nNOTES\n=====\n\nThis product includes software developed by the OpenSSL Project\n(https://www.openssl.org/)\n\nFor more information on the TLS protocol see:\nhttps://tools.ietf.org/html/rfc2246\n\nFor more information on the LZO real-time compression library see:\nhttps://www.oberhumer.com/opensource/lzo/\n\n\n\nCOPYRIGHT\n=========\n\nCopyright (C) 2002-2025 OpenVPN Inc This program is free software; you\ncan redistribute it and/or modify it under the terms of the GNU General\nPublic License version 2 as published by the Free Software Foundation.\n\nAUTHORS\n=======\n\nJames Yonan james@openvpn.net\n"
  },
  {
    "path": "doc/t_server_null.rst",
    "content": "Notes for the --dev null test suite\n===================================\n\nIntroduction\n------------\n\nThe *--dev null test suite* is primary targeted at testing client connections\nto the \"just compiled\" version of OpenVPN. The name is derived from \"null\"\ndevice type in OpenVPN. In particular, when *--dev null --ifconfig-noexec* is\nused in OpenVPN client configuration one does not need to run OpenVPN with root\nprivileges because interface, routing, etc. configuration is not done at all.\nThis is still enough to ensure that the OpenVPN client can connect to a server\ninstance.\n\nThe main features of the test suite:\n\n* Parallelized for fairly high performance\n* Mostly operating-system agnostic\n* Tested on Fedora Linux 38/39/40, FreeBSD 14, NetBSD 10.0 and OpenBSD 7.5\n* POSIX-compliant\n* Tested and known to work with Bash, Dash, Ksh, Yash and FreeBSD's default /bin/sh\n* Uses the sample certificates and keys\n* Supports running multiple servers and clients\n* Supports running servers directly as root and with sudo\n* Supports using different OpenVPN client versions\n\n  * The \"current\" (just compiled) version\n  * Any other OpenVPN versions that is present on the filesystem\n\n* Support testing for success as well as failure\n* Test cases (client configurations) and server setups (server configurations) are stored in a configuration file, i.e. data and code have been separated\n* Configuration file format is nearly identical to t_client.rc configuration\n* Supports a set of default tests, overriding default test settings and adding local tests\n* Supports client ping tests if ovpnlwip is available\n\nPrerequisites\n-------------\n\nRunning the test suite requires the following:\n\n* *bash* for running the tests\n* root-level privileges for launching the servers\n\n  * run as root\n  * a privilege escalation tool (sudo, doas, su) and the permission to become root\n\nIf you use \"doas\" you should enable nopass feature in */etc/doas.conf*. For\nexample to allow users in the *wheel* group to run commands without a password\nprompt::\n\n    permit nopass keepenv :wheel\n\nTechnical implementation\n------------------------\n\nThe test suite is completely parallelized to allow running a large number of\nserver and client combinations quickly.\n\nA normal test run looks like this:\n\n#. Server instances start\n#. Brief wait\n#. Client instances start\n#. ovpnlwip ping tests run\n#. Client instances stop\n#. Test results are collected\n#. Brief wait\n#. Server instances stop\n\nThe tests suite is launched via \"make check\":\n\n* make check\n\n  * t_server_null.sh\n\n    * t_server_null_server.sh\n\n      * Launches the compiled OpenVPN server instances as root (if necessary with sudo or su) in the background. The servers are killed using their management interface once all clients have exited.\n\n    * t_server_null_client.sh\n\n      * Waits until servers have launched. Then launch all clients, run ovpnlwip ping tests (if any), wait for clients to exit and then check test results by parsing the client log files. Each client kills itself after some delay using an \"--up\" script.\n\n\nConfiguration\n-------------\n\nThe test suite reads its configuration from two files:\n\n* *tests/t_server_null_defaults.rc:* default test configuration that should work on any system\n* *tests/t_server_null.rc:* a local configuration file; can be used to add additional tests or override settings from the default test configuration. Must be present or tests will be skipped, but can be an empty file.\n\nThe configuration syntax is very similar to *t_client.rc*. New server instances can be\ndefined like this::\n\n  SERVER_NAME_5=\"t_server_null_server-11195_udp\"\n  SERVER_MGMT_PORT_5=\"11195\"\n  SERVER_EXEC_5=\"${SERVER_EXEC}\"\n  SERVER_CONF_5=\"${SERVER_CONF_BASE} --lport 11195 --proto udp --management 127.0.0.1 ${SERVER_MGMT_PORT_5}\"\n\nIn this case the server instance identifier is **5**. Variables such as\n*SERVER_EXEC* and *SERVER_CONF_BASE* are defined in\n*t_server_null_defaults.rc*. To enable this server instance add it to the\nserver list::\n\n  TEST_SERVER_LIST=\"1 2 5\"\n\nThe client instances are added similarly::\n\n  TEST_NAME_9=\"t_server_null_client.sh-openvpn_current_udp_custom\"\n  SHOULD_PASS_9=\"yes\"\n  CLIENT_EXEC_9=\"${CLIENT_EXEC}\"\n  CLIENT_CONF_9=\"${CLIENT_CONF_BASE} --remote 127.0.0.1 1194 udp --proto udp\"\n\nIn this case the test identifier is **9**. *CLIENT_EXEC* and *CLIENT_CONF_BASE*\nare defined in *t_server_null_defaults.rc*. The variable *SHOULD_PASS*\ndetermines that this particular test is supposed to succeed and not fail.  To\nenable this client instance add it to the test list::\n\n  TEST_RUN_LIST=\"1 2 5 9\"\n\nClient ping tests that use ovpnlwip can be added similarly:\n\n  TEST_NAME_9L=\"t_server_null_client.sh-openvpn_current_udp_custom_lwip\"\n  SHOULD_PASS_9L=\"yes\"\n  CLIENT_EXEC_9L=\"${CLIENT_EXEC}\"\n  CLIENT_CONF_9L=\"${CLIENT_CONF_BASE_LWIP} --remote 127.0.0.1 1194 udp --proto udp\"\n\nNote that all ovpnlwip test names need to include a \"_lwip\" suffix: without it\nping tests won't get activated. Also note that the *tests* directory needs to\nhave the lwipovpn executable or ovpnlwip tests will get skipped. The ovpnlwip\nping tests get the IP addresses to ping from the \\*.ips files created by the\nlwip_client_up.sh script --up script.\n\nStress-testing the --dev null test suite\n----------------------------------------\n\nIt is very easy to introduce subtle, difficult to debug issues to the --dev\nnull tests when you make changes to it. These issues can be difficult to spot:\nbased on practical experience a bad change can make the test failure rate go\nfrom 0% (normal) to anywhere between 1% and 20%. You can spot these issues with\nthe provided stress-test script, *t_server_null_stress.sh*. It calls *make check*\nover and over again in a loop and when failures occur it saves the output under\n*tests/make-check*.\n\nTo follow the test flow on Linux you can run this while stress-testing::\n\n    watch -n 0.5 \"ps aux|grep -E '(openvpn|t_server_null_server.sh)'|grep -vE '(suppress|grep|tail)'\"\n\nRegarding privilege escalation\n------------------------------\n\nThe --dev null test servers need to be launched as root. Either run the tests\nas root directly, or configure a privilege escalation tool of your choice in\n*t_server_null.rc*. For example, to use sudo::\n\n    SUDO_EXEC=`which sudo`\n    RUN_SUDO=\"${SUDO_EXEC} -E\"\n\nIf you do stress-testing with *t_server_null_stress.sh* make sure your\nprivilege escalation authorization does not time out: if it does, then a\nreauthorization prompt will interrupt your tests.\n"
  },
  {
    "path": "doc/tests/authentication-plugins.md",
    "content": "# TESTING OF MULTIPLE AUTHENTICATION PLUG-INS\n\n\nOpenVPN 2.x can support loading and authenticating users through multiple\nplug-ins at the same time.  But it can only support a single plug-in doing\ndeferred authentication.  However, a plug-in supporting deferred\nauthentication may be accompanied by other authentication plug-ins **not**\ndoing deferred authentication.\n\nThis is a test script useful to test the various combinations and order of\nplug-in execution.\n\nThe configuration files are expected to be used from the root of the build\ndirectory.\n\nTo build the needed authentication plug-in, run:\n\n     make -C sample/sample-plugins\n\n\n## Test configs\n\n* Client config\n\n      verb 4\n      dev tun\n      client\n      remote x.x.x.x\n      ca sample/sample-keys/ca.crt\n      cert sample/sample-keys/client.crt\n      key sample/sample-keys/client.key\n      auth-user-pass\n\n* Base server config (`base-server.conf`)\n\n      verb 4\n      dev tun\n      server 10.8.0.0 255.255.255.0\n      dh none\n      ca sample/sample-keys/ca.crt\n      cert sample/sample-keys/server.crt\n      key sample/sample-keys/server.key\n\n\n## Test cases\n\n### Test: *sanity-1*\n\nThis tests the basic authentication with an instant answer.\n\n     config base-server.conf\n     plugin multi-auth.so S1.1 0 foo bar\n\n#### Expected results\n - Username/password `foo`/`bar`: **PASS**\n - Anything else: **FAIL**\n\n\n### Test: *sanity-2*\n\nThis is similar to `sanity-1`, but does the authentication\nthrough two plug-ins providing an instant reply.\n\n     config base-server.conf\n     plugin multi-auth.so S2.1 0 foo bar\n     plugin multi-auth.so S2.2 0 foo bar\n\n#### Expected results\n - Username/password `foo`/`bar`: **PASS**\n - Anything else: **FAIL**\n\n\n### Test: *sanity-3*\n\nThis is also similar to `sanity-1`, but uses deferred authentication\nwith a 1 second delay on the response.\n\n     plugin multi-auth.so S3.1 1000 foo bar\n\n#### Expected results\n - Username/password `foo`/`bar`: **PASS**\n - Anything else: **FAIL**\n\n\n### Test: *case-a*\n\nRuns two authentications, the first one deferred by 1 second and the\nsecond one providing an instant response.\n\n     plugin multi-auth.so A.1 1000 foo bar\n     plugin multi-auth.so A.2 0 foo bar\n\n#### Expected results\n - Username/password `foo`/`bar`: **PASS**\n - Anything else: **FAIL**\n\n\n### Test: *case-b*\n\nThis is similar to `case-a`, but the instant authentication response\nis provided first before the deferred authentication.\n\n     plugin multi-auth.so B.1 0 foo bar\n     plugin multi-auth.so B.2 1000 test pass\n\n#### Expected results\n - **Always FAIL**\n - This test should never pass, as each plug-in expects different\n   usernames and passwords.\n\n\n### Test: *case-c*\n\nThis is similar to the two prior tests, but the authentication result\nis returned instantly in both steps.\n\n     plugin multi-auth.so C.1 0 foo bar\n     plugin multi-auth.so C.2 0 foo2 bar2\n\n#### Expected results\n - **Always FAIL**\n - This test should never pass, as each plug-in expects different\n   usernames and passwords.\n\n\n### Test: *case-d*\n\nThis is similar to the `case-b` test, but the order of deferred\nand instant response is reversed.\n\n    plugin ./multi-auth.so D.1 2000 test pass\n    plugin ./multi-auth.so D.2 0 foo bar\n\n#### Expected results\n - **Always FAIL**\n - This test should never pass, as each plug-in expects different\n   usernames and passwords.\n\n\n### Test: *case-e*\n\nThis test case will run two deferred authentication plug-ins.  This is\n**not** supported by OpenVPN, and should therefore fail instantly.\n\n    plugin ./multi-auth.so E1 1000 test1 pass1\n    plugin ./multi-auth.so E2 2000 test2 pass2\n\n#### Expected results\n - The OpenVPN server process should stop running\n - An error about multiple deferred plug-ins being configured\n   should be seen in the server log.\n"
  },
  {
    "path": "doc/tls-crypt-v2.txt",
    "content": "Client-specific tls-crypt keys (--tls-crypt-v2)\n===============================================\n\nThis document describes the ``--tls-crypt-v2`` option, which enables OpenVPN\nto use client-specific ``--tls-crypt`` keys.\n\nRationale\n---------\n\n``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared\namong all clients and servers in an OpenVPN deployment.  If any client or\nserver is compromised, the attacker will have access to this shared key, and it\nwill no longer provide any security.  To reduce the risk of losing pre-shared\nkeys, ``tls-crypt-v2`` adds the ability to supply each client with a unique\ntls-crypt key.  This allows large organisations and VPN providers to profit\nfrom the same DoS and TLS stack protection that small deployments can already\nachieve using ``tls-auth`` or ``tls-crypt``.\n\nAlso, for ``tls-crypt``, even if all these peers succeed in keeping the key\nsecret, the key lifetime is limited to roughly 8000 years, divided by the\nnumber of clients (see the ``--tls-crypt`` section of the man page).  Using\nclient-specific keys, we lift this lifetime requirement to roughly 8000 years\nfor each client key (which \"Should Be Enough For Everybody (tm)\").\n\n\nIntroduction\n------------\n\n``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce\nclient-specific tls-crypt keys without introducing a lot of server-side state.\nThe client-specific key is encrypted using a server key.  The server key is the\nsame for all servers in a group.  When a client connects, it first sends the\nencrypted key to the server, such that the server can decrypt the key and all\nmessages can thereafter be encrypted using the client-specific key.\n\nA wrapped (encrypted and authenticated) client-specific key can also contain\nmetadata.  The metadata is wrapped together with the key, and can be used to\nallow servers to identify clients and/or key validity.  This allows the server\nto abort the connection immediately after receiving the first packet, rather\nthan performing an entire TLS handshake.  Aborting the connection this early\ngreatly improves the DoS resilience and reduces attack surface against\nmalicious clients that have the ``tls-crypt`` or ``tls-auth`` key.  This is\nparticularly relevant for large deployments (think lost key or disgruntled\nemployee) and VPN providers (clients are not trusted).\n\nTo allow for a smooth transition, ``tls-crypt-v2`` is designed such that a\nserver can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or\n``tls-auth``.  This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3\nopcode, that indicates that the client wants to use ``tls-crypt-v2`` for the\ncurrent connection.\n\nFor an exact specification and more details, read the Implementation section.\n\n\nImplementation\n--------------\n\nWhen setting up a tls-crypt-v2 group (similar to generating a tls-crypt or\ntls-auth key previously):\n\n1. Generate a tls-crypt-v2 server key using OpenVPN's ``--genkey tls-crypt-v2-server``.\n   This key contains 2 512-bit keys, of which we use:\n\n   * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke``\n   * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka``\n\n   This format is similar to the format for regular ``tls-crypt``/``tls-auth``\n   and data channel keys, which allows us to reuse code.\n\n2. Add the tls-crypt-v2 server key to all server configs\n   (``tls-crypt-v2 /path/to/server.key``)\n\n\nWhen provisioning a client, create a client-specific tls-crypt key:\n\n1. Generate 2048 bits client-specific key ``Kc`` using OpenVPN's ``--genkey tls-crypt-v2-client``\n\n2. Optionally generate metadata\n\n   The first byte of the metadata determines the type.  The initial\n   implementation supports the following types:\n\n   0x00 (USER):         User-defined free-form data.\n   0x01 (TIMESTAMP):    64-bit network order unix timestamp of key generation.\n\n   The timestamp can be used to reject too-old tls-crypt-v2 client keys.\n\n   User metadata could for example contain the users certificate serial, such\n   that the incoming connection can be verified against a CRL.\n\n   If no metadata is supplied during key generation, openvpn defaults to the\n   TIMESTAMP metadata type.\n\n3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant\n   SIV construction we use for tls-crypt:\n\n   ``len = len(WKc)`` (16 bit, network byte order)\n\n   ``T = HMAC-SHA256(Ka, len || Kc || metadata)``\n\n   ``IV = 128 most significant bits of T``\n\n   ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata) || len``\n\n   Note that the length of ``WKc`` can be computed before composing ``WKc``,\n   because the length of each component is known (and AES-256-CTR does not add\n   any padding).\n\n4. Create a tls-crypt-v2 client key: PEM-encode ``Kc || WKc`` and store in a\n   file, using the header ``-----BEGIN OpenVPN tls-crypt-v2 client key-----``\n   and the footer ``-----END OpenVPN tls-crypt-v2 client key-----``.  (The PEM\n   format is simple, and following PEM allows us to use the crypto lib function\n   for en/decoding.)\n\n5. Add the tls-crypt-v2 client key to the client config\n   (``tls-crypt-v2 /path/to/client-specific.key``)\n\n\nWhen setting up the openvpn connection:\n\n1. The client reads the tls-crypt-v2 key from its config, and:\n\n   1. loads ``Kc`` as its tls-crypt key,\n   2. stores ``WKc`` in memory for sending to the server.\n\n2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3\n   message, wraps it with tls-crypt using ``Kc`` as the key, and appends\n   ``WKc``.  (``WKc`` must not be encrypted, to prevent a chicken-and-egg\n   problem.)\n\n3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and\n\n   1. reads the WKc length field from the end of the message, and extracts WKc\n      from the message\n   2. unwraps ``WKc``\n   3. uses unwrapped ``Kc`` to verify the remaining\n      P_CONTROL_HARD_RESET_CLIENT_V3 message's (encryption and) authentication.\n\n   The message is dropped and no error response is sent when either 3.1, 3.2 or\n   3.3 fails (DoS protection).\n\n4. The server optionally checks if the client key contains a timestamp that is\n   below a maximum age configured with the --tls-crypt-v2-max-age option.\n\n5. Server optionally checks metadata using a --tls-crypt-v2-verify script\n\n   This allows early abort of connection, *before* we expose any of the\n   notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the\n   attack surface of the server.\n\n   The metadata is checked *after* the OpenVPN three-way handshake has\n   completed, to prevent DoS attacks.  (That is, once the client has proved to\n   the server that it possesses Kc, by authenticating a packet that contains the\n   session ID picked by the server.)\n\n   A server should not send back any error messages if metadata verification\n   fails, to reduce attack surface and maximize DoS resilience.\n\n6. Client and server use ``Kc`` for (un)wrapping any following control channel\n   messages.\n\n\nHMAC Cookie support\n-------------------\nTo avoid exhaustion attack and keeping state for connections that fail to\ncomplete the three-way handshake, the OpenVPN server will use its own session\nid as challenge that the client must repeat in the third packet of the\nhandshake. This introduces a problem. If the server does not keep the wrapped\nclient key from the initial packet, the server cannot decode the third packet.\nTherefore, tls-crypt-v2 in 2.6 allows resending the wrapped key in the third\npacket of the handshake with the P_CONTROL_WKC_V1 message. The modified\nhandshake is as follows (the rest of the handshake is unmodified):\n\n1. The client creates the P_CONTROL_HARD_RESET_CLIENT_V3 message as before\n   but indicates that it supports resending the wrapped key. This is done\n   by setting the packet id of the replay id to 0x0f000000. The first byte\n   indicates the early negotiation support and the next byte the flags.\n   All tls-crypt-v2 implementations that support early negotiation, must\n   also support resending the wrapped key. The flags byte is therefore\n   empty.\n\n2. The server responds with a P_CONTROL_HARD_RESET_V2 message. Instead of having\n   an empty payload like normally, the payload consists of TLV (type (uint16),\n   length (uint16), value) packets. TLV was chosen\n   to allow extensibility in the future. Currently only the following TLV is\n   defined:\n\n   flags - type 0x01, length 2.\n\n   Bit 1 indicates that the client needs to resend the WKc in the third packet.\n\n3. Instead of normal P_ACK_V1 or P_CONTROL_V1 packet, the client will send a\n   P_CONTROL_WKC_V1 packet. The P_CONTROL_WKC_V1 is identical to a normal\n   P_CONTROL_V1 packet but with the WKc appended.\n\n   Normally the first message of the client is either P_ACK_V1, directly\n   followed by a P_CONTROL_V1 message that contains the TLS Client Hello or\n   just a P_CONTROL_V1 message. Instead of a P_ACK_V1 message the client should\n   send a P_CONTROL_WKC_V1 message with an empty payload. This message must\n   also include an ACK for the P_CONTROL_HARD_RESET_V2 message.\n\n   When directly sending the TLS Client Hello message in the P_CONTROL_WKC_V1\n   message, the client must ensure that the resulting P_CONTROL_WKC_V1 message\n   with the appended WKc does not extend the control message length.\n\n\nConsiderations\n--------------\n\nTo allow for a smooth transition, the server implementation allows\n``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``.\nThis specification does not allow simultaneously using ``tls-crypt-v2`` and\nconnections without any control channel wrapping, because that would break DoS\nresilience.\n\nWKc includes a length field, so we leave the option for future extension of the\nP_CONTROL_HEAD_RESET_CLIENT_V3 message open.  (E.g. add payload to the reset to\nindicate low-level protocol features.)\n\n``tls-crypt-v2`` uses fixed crypto algorithms, because:\n\n * The crypto is used before we can do any negotiation, so the algorithms have\n   to be predefined.\n * The crypto primitives are chosen conservatively, making problems with these\n   primitives unlikely.\n * Making anything configurable adds complexity, both in implementation and\n   usage.  We should not add any more complexity than is absolutely necessary.\n\nPotential ``tls-crypt-v2`` risks:\n\n * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap)\n   than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth).\n * Flexible metadata allow mistakes\n   (So we should make it easy to do it right.  Provide tooling to create client\n   keys based on cert serial + CA fingerprint, provide script that uses CRL (if\n   available) to drop revoked keys.)\n"
  },
  {
    "path": "forked-test-driver",
    "content": "#! /bin/sh\n# test-driver - basic testsuite driver script.\n\nscriptversion=2018-03-07.03; # UTC\n\n# Copyright (C) 2011-2021 Free Software Foundation, Inc.\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation; either version 2, or (at your option)\n# any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\n# As a special exception to the GNU General Public License, if you\n# distribute this file as part of a program that contains a\n# configuration script generated by Autoconf, you may include it under\n# the same distribution terms that you use for the rest of that program.\n\n# This file is maintained in Automake, please report\n# bugs to <bug-automake@gnu.org> or send patches to\n# <automake-patches@gnu.org>.\n\n# Make unconditional expansion of undefined variables an error.  This\n# helps a lot in preventing typo-related bugs.\nset -u\n\nusage_error ()\n{\n  echo \"$0: $*\" >&2\n  print_usage >&2\n  exit 2\n}\n\nprint_usage ()\n{\n  cat <<END\nUsage:\n  test-driver --test-name NAME --log-file PATH --trs-file PATH\n              [--expect-failure {yes|no}] [--color-tests {yes|no}]\n              [--enable-hard-errors {yes|no}] [--]\n              TEST-SCRIPT [TEST-SCRIPT-ARGUMENTS]\n\nThe '--test-name', '--log-file' and '--trs-file' options are mandatory.\nSee the GNU Automake documentation for information.\nEND\n}\n\ntest_name= # Used for reporting.\nlog_file=  # Where to save the output of the test script.\ntrs_file=  # Where to save the metadata of the test run.\nexpect_failure=no\ncolor_tests=no\nenable_hard_errors=yes\nwhile test $# -gt 0; do\n  case $1 in\n  --help) print_usage; exit $?;;\n  --version) echo \"test-driver $scriptversion\"; exit $?;;\n  --test-name) test_name=$2; shift;;\n  --log-file) log_file=$2; shift;;\n  --trs-file) trs_file=$2; shift;;\n  --color-tests) color_tests=$2; shift;;\n  --expect-failure) expect_failure=$2; shift;;\n  --enable-hard-errors) enable_hard_errors=$2; shift;;\n  --) shift; break;;\n  -*) usage_error \"invalid option: '$1'\";;\n   *) break;;\n  esac\n  shift\ndone\n\nmissing_opts=\ntest x\"$test_name\" = x && missing_opts=\"$missing_opts --test-name\"\ntest x\"$log_file\"  = x && missing_opts=\"$missing_opts --log-file\"\ntest x\"$trs_file\"  = x && missing_opts=\"$missing_opts --trs-file\"\nif test x\"$missing_opts\" != x; then\n  usage_error \"the following mandatory options are missing:$missing_opts\"\nfi\n\nif test $# -eq 0; then\n  usage_error \"missing argument\"\nfi\n\nif test $color_tests = yes; then\n  # Keep this in sync with 'lib/am/check.am:$(am__tty_colors)'.\n  red='\u001b[0;31m' # Red.\n  grn='\u001b[0;32m' # Green.\n  lgn='\u001b[1;32m' # Light green.\n  blu='\u001b[1;34m' # Blue.\n  mgn='\u001b[0;35m' # Magenta.\n  std='\u001b[m'     # No color.\nelse\n  red= grn= lgn= blu= mgn= std=\nfi\n\ndo_exit='rm -f $log_file $trs_file; (exit $st); exit $st'\ntrap \"st=129; $do_exit\" 1\ntrap \"st=130; $do_exit\" 2\ntrap \"st=141; $do_exit\" 13\ntrap \"st=143; $do_exit\" 15\n\n# Test script is run here. We create the file first, then append to it,\n# to ameliorate tests themselves also writing to the log file. Our tests\n# don't, but others can (automake bug#35762).\n# OVPN changes:\n#  - add tee to see output of tests\n#  - needs portable pipefail mechanism\nestatusfile=\"${trs_file}.exit\"\n: >\"$log_file\"\n(\"$@\" 2>&1; estatus=$?; echo $estatus >\"$estatusfile\") | tee -a \"$log_file\"\nestatus=$(cat \"$estatusfile\")\nrm -f \"$estatusfile\"\n\nif test $enable_hard_errors = no && test $estatus -eq 99; then\n  tweaked_estatus=1\nelse\n  tweaked_estatus=$estatus\nfi\n\ncase $tweaked_estatus:$expect_failure in\n  0:yes) col=$red res=XPASS recheck=yes gcopy=yes;;\n  0:*)   col=$grn res=PASS  recheck=no  gcopy=no;;\n  77:*)  col=$blu res=SKIP  recheck=no  gcopy=yes;;\n  99:*)  col=$mgn res=ERROR recheck=yes gcopy=yes;;\n  *:yes) col=$lgn res=XFAIL recheck=no  gcopy=yes;;\n  *:*)   col=$red res=FAIL  recheck=yes gcopy=yes;;\nesac\n\n# Report the test outcome and exit status in the logs, so that one can\n# know whether the test passed or failed simply by looking at the '.log'\n# file, without the need of also peaking into the corresponding '.trs'\n# file (automake bug#11814).\necho \"$res $test_name (exit status: $estatus)\" >>\"$log_file\"\n\n# Report outcome to console.\necho \"${col}${res}${std}: $test_name\"\n\n# Register the test result, and other relevant metadata.\necho \":test-result: $res\" > $trs_file\necho \":global-test-result: $res\" >> $trs_file\necho \":recheck: $recheck\" >> $trs_file\necho \":copy-in-global-log: $gcopy\" >> $trs_file\n\n# Local Variables:\n# mode: shell-script\n# sh-indentation: 2\n# eval: (add-hook 'before-save-hook 'time-stamp)\n# time-stamp-start: \"scriptversion=\"\n# time-stamp-format: \"%:y-%02m-%02d.%02H\"\n# time-stamp-time-zone: \"UTC0\"\n# time-stamp-end: \"; # UTC\"\n# End:\n"
  },
  {
    "path": "include/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2006-2012 Alon Bar-Lev <alon.barlev@gmail.com>\n#\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in \\\n\t$(srcdir)/openvpn-plugin.h.in\n\ninclude_HEADERS = \\\n\topenvpn-plugin.h \\\n\topenvpn-msg.h\n"
  },
  {
    "path": "include/openvpn-msg.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2013-2026 Heiko Hund <heiko.hund@sophos.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef OPENVPN_MSG_H_\n#define OPENVPN_MSG_H_\n\n#include <windef.h>\n#include <ws2tcpip.h>\n\ntypedef enum\n{\n    msg_acknowledgement,\n    msg_add_address,\n    msg_del_address,\n    msg_add_route,\n    msg_del_route,\n    msg_add_dns_cfg,\n    msg_del_dns_cfg,\n    msg_add_nrpt_cfg,\n    msg_del_nrpt_cfg,\n    msg_add_nbt_cfg,\n    msg_del_nbt_cfg,\n    msg_flush_neighbors,\n    msg_add_wfp_block,\n    msg_del_wfp_block,\n    msg_register_dns,\n    msg_enable_dhcp,\n    deprecated_msg_register_ring_buffers,\n    msg_set_mtu,\n    msg_add_wins_cfg,\n    msg_del_wins_cfg,\n    msg_create_adapter\n} message_type_t;\n\ntypedef struct\n{\n    message_type_t type;\n    size_t size;\n    int message_id;\n} message_header_t;\n\ntypedef union\n{\n    struct in_addr ipv4;\n    struct in6_addr ipv6;\n} inet_address_t;\n\ntypedef struct\n{\n#define TUN_ADAPTER_INDEX_INVALID ((DWORD)-1)\n    DWORD index;\n    char name[256];\n} interface_t;\n\ntypedef enum\n{\n    wfp_block_local = 1 << 0,\n    wfp_block_dns = 1 << 1\n} wfp_block_flags_t;\n\ntypedef struct\n{\n    message_header_t header;\n    short family;\n    inet_address_t address;\n    int prefix_len;\n    interface_t iface;\n} address_message_t;\n\ntypedef struct\n{\n    message_header_t header;\n    short family;\n    inet_address_t prefix;\n    int prefix_len;\n    inet_address_t gateway;\n    interface_t iface;\n    int metric;\n} route_message_t;\n\ntypedef struct\n{\n    message_header_t header;\n    interface_t iface;\n    char domains[512];\n    short family;\n    unsigned int addr_len;\n    inet_address_t addr[4]; /* support up to 4 dns addresses */\n} dns_cfg_message_t;\n\n\ntypedef enum\n{\n    nrpt_dnssec\n} nrpt_flags_t;\n\n#define NRPT_ADDR_NUM  8  /* Max. number of addresses */\n#define NRPT_ADDR_SIZE 48 /* Max. address strlen + some */\ntypedef char nrpt_address_t[NRPT_ADDR_SIZE];\ntypedef struct\n{\n    message_header_t header;\n    interface_t iface;\n    nrpt_address_t addresses[NRPT_ADDR_NUM];\n    char resolve_domains[512]; /* double \\0 terminated */\n    char search_domains[512];\n    nrpt_flags_t flags;\n} nrpt_dns_cfg_message_t;\n\ntypedef struct\n{\n    message_header_t header;\n    interface_t iface;\n    unsigned int addr_len;\n    inet_address_t addr[4]; /* support up to 4 dns addresses */\n} wins_cfg_message_t;\n\ntypedef struct\n{\n    message_header_t header;\n    interface_t iface;\n    int disable_nbt;\n    int nbt_type;\n    char scope_id[256];\n    struct in_addr primary_nbns;\n    struct in_addr secondary_nbns;\n} nbt_cfg_message_t;\n\n/* TODO: NTP */\n\ntypedef struct\n{\n    message_header_t header;\n    short family;\n    interface_t iface;\n} flush_neighbors_message_t;\n\ntypedef struct\n{\n    message_header_t header;\n    int error_number;\n} ack_message_t;\n\ntypedef struct\n{\n    message_header_t header;\n    wfp_block_flags_t flags;\n    interface_t iface;\n} wfp_block_message_t;\n\ntypedef struct\n{\n    message_header_t header;\n    interface_t iface;\n} enable_dhcp_message_t;\n\ntypedef struct\n{\n    message_header_t header;\n    interface_t iface;\n    short family;\n    int mtu;\n} set_mtu_message_t;\n\ntypedef enum\n{\n    ADAPTER_TYPE_DCO,\n    ADAPTER_TYPE_TAP,\n} adapter_type_t;\n\ntypedef struct\n{\n    message_header_t header;\n    adapter_type_t adapter_type;\n} create_adapter_message_t;\n\n#endif /* ifndef OPENVPN_MSG_H_ */\n"
  },
  {
    "path": "include/openvpn-plugin.h.in",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef OPENVPN_PLUGIN_H_\n#define OPENVPN_PLUGIN_H_\n\n#define OPENVPN_PLUGIN_VERSION 3\n\n#ifdef ENABLE_CRYPTO_MBEDTLS\n#include <mbedtls/x509_crt.h>\n#ifndef __OPENVPN_X509_CERT_T_DECLARED\n#define __OPENVPN_X509_CERT_T_DECLARED\ntypedef mbedtls_x509_crt openvpn_x509_cert_t;\n#endif\n#else  /* ifdef ENABLE_CRYPTO_MBEDTLS */\n#include <openssl/x509.h>\n#ifndef __OPENVPN_X509_CERT_T_DECLARED\n#define __OPENVPN_X509_CERT_T_DECLARED\ntypedef X509 openvpn_x509_cert_t;\n#endif\n#endif\n\n#include <stdarg.h>\n#include <stddef.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* Provide some basic version information to plug-ins at OpenVPN compile time\n * This is will not be the complete version\n */\n#define OPENVPN_VERSION_MAJOR @OPENVPN_VERSION_MAJOR@\n#define OPENVPN_VERSION_MINOR @OPENVPN_VERSION_MINOR@\n#define OPENVPN_VERSION_PATCH \"@OPENVPN_VERSION_PATCH@\"\n\n/*\n * Plug-in types.  These types correspond to the set of script callbacks\n * supported by OpenVPN.\n *\n * This is the general call sequence to expect when running in server mode:\n *\n * Initial Server Startup:\n *\n * FUNC: openvpn_plugin_open_v1\n * FUNC: openvpn_plugin_client_constructor_v1 (this is the top-level \"generic\"\n *                                             client template)\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_UP\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_ROUTE_UP\n *\n * New Client Connection:\n *\n * FUNC: openvpn_plugin_client_constructor_v1\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_TLS_VERIFY (called once for every cert\n *                                                     in the server chain)\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_TLS_FINAL\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_IPCHANGE\n *\n * [If OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY returned OPENVPN_PLUGIN_FUNC_DEFERRED,\n * we don't proceed until authentication is verified via auth_control_file]\n *\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_CLIENT_CONNECT_V2\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_LEARN_ADDRESS\n *\n * The OPENVPN_PLUGIN_CLIENT_CRRESPONSE function is called when the client sends\n * the CR_RESPONSE message, this is *typically* after OPENVPN_PLUGIN_TLS_FINAL\n * but may also occur much later.\n *\n * [Client session ensues]\n *\n * For each \"TLS soft reset\", according to reneg-sec option (or similar):\n *\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_TLS_VERIFY (called once for every cert\n *                                                     in the server chain)\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_TLS_FINAL\n *\n * [If OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY returned OPENVPN_PLUGIN_FUNC_DEFERRED,\n * we expect that authentication is verified via auth_control_file within\n * the number of seconds defined by the \"hand-window\" option.  Data channel traffic\n * will continue to flow uninterrupted during this period.]\n *\n * [Client session continues]\n *\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_CLIENT_DISCONNECT\n * FUNC: openvpn_plugin_client_destructor_v1\n *\n * [ some time may pass ]\n *\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_LEARN_ADDRESS (this coincides with a\n *                                                            lazy free of initial\n *                                                            learned addr object)\n * Server Shutdown:\n *\n * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_DOWN\n * FUNC: openvpn_plugin_client_destructor_v1 (top-level \"generic\" client)\n * FUNC: openvpn_plugin_close_v1\n */\n#define OPENVPN_PLUGIN_UP                        0\n#define OPENVPN_PLUGIN_DOWN                      1\n#define OPENVPN_PLUGIN_ROUTE_UP                  2\n#define OPENVPN_PLUGIN_IPCHANGE                  3\n#define OPENVPN_PLUGIN_TLS_VERIFY                4\n#define OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY     5\n#define OPENVPN_PLUGIN_CLIENT_CONNECT            6\n#define OPENVPN_PLUGIN_CLIENT_DISCONNECT         7\n#define OPENVPN_PLUGIN_LEARN_ADDRESS             8\n#define OPENVPN_PLUGIN_CLIENT_CONNECT_V2         9\n#define OPENVPN_PLUGIN_TLS_FINAL                10\n/*#define OPENVPN_PLUGIN_ENABLE_PF                11 *REMOVED FEATURE* */\n#define OPENVPN_PLUGIN_ROUTE_PREDOWN            12\n#define OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER     13\n#define OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2  14\n#define OPENVPN_PLUGIN_CLIENT_CRRESPONSE        15\n#define OPENVPN_PLUGIN_N                        16\n\n/*\n * Build a mask out of a set of plug-in types.\n */\n#define OPENVPN_PLUGIN_MASK(x) (1<<(x))\n\n/*\n * A pointer to a plugin-defined object which contains\n * the object state.\n */\ntypedef void *openvpn_plugin_handle_t;\n\n/*\n * Return value for openvpn_plugin_func_v1 function\n */\n#define OPENVPN_PLUGIN_FUNC_SUCCESS  0\n#define OPENVPN_PLUGIN_FUNC_ERROR    1\n#define OPENVPN_PLUGIN_FUNC_DEFERRED 2\n\n/*\n * For Windows (needs to be modified for MSVC)\n */\n#if defined(_WIN32) && !defined(OPENVPN_PLUGIN_H)\n#define OPENVPN_EXPORT __declspec(dllexport)\n#else\n#define OPENVPN_EXPORT\n#endif\n\n/*\n * If OPENVPN_PLUGIN_H is defined, we know that we are being\n * included in an OpenVPN compile, rather than a plugin compile.\n */\n#ifdef OPENVPN_PLUGIN_H\n\n/*\n * We are compiling OpenVPN.\n */\n#define OPENVPN_PLUGIN_DEF        typedef\n#define OPENVPN_PLUGIN_FUNC(name) (*name)\n\n#else  /* ifdef OPENVPN_PLUGIN_H */\n\n/*\n * We are compiling plugin.\n */\n#define OPENVPN_PLUGIN_DEF        OPENVPN_EXPORT\n#define OPENVPN_PLUGIN_FUNC(name) name\n\n#endif\n\n/*\n * Used by openvpn_plugin_func to return structured\n * data.  The plugin should allocate all structure\n * instances, name strings, and value strings with\n * malloc, since OpenVPN will assume that it\n * can free the list by calling free() over the same.\n */\nstruct openvpn_plugin_string_list\n{\n    struct openvpn_plugin_string_list *next;\n    char *name;\n    char *value;\n};\n\n\n/* openvpn_plugin_{open,func}_v3() related structs */\n\n/**\n * Defines version of the v3 plugin argument structs\n *\n * Whenever one or more of these structs are modified, this constant\n * must be updated.  A changelog should be appended in this comment\n * as well, to make it easier to see what information is available\n * in the different versions.\n *\n * Version   Comment\n *    1      Initial plugin v3 structures providing the same API as\n *           the v2 plugin interface, X509 certificate information +\n *           a logging API for plug-ins.\n *\n *    2      Added ssl_api member in struct openvpn_plugin_args_open_in\n *           which identifies the SSL implementation OpenVPN is compiled\n *           against.\n *\n *    3      Added ovpn_version, ovpn_version_major, ovpn_version_minor\n *           and ovpn_version_patch to provide the runtime version of\n *           OpenVPN to plug-ins.\n *\n *    4      Exported secure_memzero() as plugin_secure_memzero()\n *\n *    5      Exported openvpn_base64_encode() as plugin_base64_encode()\n *           Exported openvpn_base64_decode() as plugin_base64_decode()\n */\n#define OPENVPN_PLUGINv3_STRUCTVER 5\n\n/**\n * Definitions needed for the plug-in callback functions.\n */\ntypedef enum\n{\n    PLOG_ERR    = (1 << 0),/* Error condition message */\n    PLOG_WARN   = (1 << 1),/* General warning message */\n    PLOG_NOTE   = (1 << 2),/* Informational message */\n    PLOG_DEBUG  = (1 << 3),/* Debug message, displayed if verb >= 7 */\n\n    PLOG_ERRNO  = (1 << 8),/* Add error description to message */\n    PLOG_NOMUTE = (1 << 9), /* Mute setting does not apply for message */\n\n} openvpn_plugin_log_flags_t;\n\n\n#ifdef __GNUC__\n#if __USE_MINGW_ANSI_STDIO\n#define _ovpn_chk_fmt(a, b) __attribute__ ((format(gnu_printf, (a), (b))))\n#else\n#define _ovpn_chk_fmt(a, b) __attribute__ ((format(__printf__, (a), (b))))\n#endif\n#else  /* ifdef __GNUC__ */\n#define _ovpn_chk_fmt(a, b)\n#endif\n\ntypedef void (*plugin_log_t)(openvpn_plugin_log_flags_t flags,\n                             const char *plugin_name,\n                             const char *format, ...) _ovpn_chk_fmt (3, 4);\n\ntypedef void (*plugin_vlog_t)(openvpn_plugin_log_flags_t flags,\n                              const char *plugin_name,\n                              const char *format,\n                              va_list arglist) _ovpn_chk_fmt (3, 0);\n#undef _ovpn_chk_fmt\n\n/**\n *  Export of secure_memzero() to be used inside plug-ins\n *\n *  @param data   Pointer to data to zeroise\n *  @param len    Length of data, in bytes\n *\n */\ntypedef void (*plugin_secure_memzero_t)(void *data, size_t len);\n\n/**\n *  Export of openvpn_base64_encode() to be used inside plug-ins\n *\n *  @param data   Pointer to data to BASE64 encode\n *  @param size   Length of data, in bytes\n *  @param *str   Pointer to the return buffer.  This needed memory is\n *                allocated by openvpn_base64_encode() and needs to be free()d\n *                after use.\n *\n *  @return int   Returns the length of the buffer created, or -1 on error.\n *\n */\ntypedef int (*plugin_base64_encode_t)(const void *data, int size, char **str);\n\n/**\n *  Export of openvpn_base64_decode() to be used inside plug-ins\n *\n *  @param str    Pointer to the BASE64 encoded data\n *  @param data   Pointer to the buffer where save the decoded data\n *  @param size   Size of the destination buffer\n *\n *  @return int   Returns the length of the decoded data, or -1 on error or\n *                if the destination buffer is too small.\n *\n */\ntypedef int (*plugin_base64_decode_t)(const char *str, void *data, int size);\n\n\n/**\n * Used by the openvpn_plugin_open_v3() function to pass callback\n * function pointers to the plug-in.\n *\n * plugin_log\n * plugin_vlog : Use these functions to add information to the OpenVPN log file.\n *               Messages will only be displayed if the plugin_name parameter\n *               is set. PLOG_DEBUG messages will only be displayed with plug-in\n *               debug log verbosity (at the time of writing that's verb >= 7).\n *\n * plugin_secure_memzero\n *             : Use this function to securely wipe sensitive information from\n *               memory.  This function is declared in a way that the compiler\n *               will not remove these function calls during the compiler\n *               optimization phase.\n */\nstruct openvpn_plugin_callbacks\n{\n    plugin_log_t plugin_log;\n    plugin_vlog_t plugin_vlog;\n    plugin_secure_memzero_t plugin_secure_memzero;\n    plugin_base64_encode_t plugin_base64_encode;\n    plugin_base64_decode_t plugin_base64_decode;\n};\n\n/**\n * Used by the openvpn_plugin_open_v3() function to indicate to the\n * plug-in what kind of SSL implementation OpenVPN uses.  This is\n * to avoid SEGV issues when OpenVPN is complied against mbed TLS\n * and the plug-in against OpenSSL.\n */\ntypedef enum {\n    SSLAPI_NONE,\n    SSLAPI_OPENSSL,\n    SSLAPI_MBEDTLS\n} ovpnSSLAPI;\n\n/**\n * Arguments used to transport variables to the plug-in.\n * The struct openvpn_plugin_args_open_in is only used\n * by the openvpn_plugin_open_v3() function.\n *\n * STRUCT MEMBERS\n *\n * type_mask : Set by OpenVPN to the logical OR of all script\n *             types which this version of OpenVPN supports.\n *\n * argv : a NULL-terminated array of options provided to the OpenVPN\n *        \"plug-in\" directive.  argv[0] is the dynamic library pathname.\n *\n * envp : a NULL-terminated array of OpenVPN-set environmental\n *        variables in \"name=value\" format.  Note that for security reasons,\n *        these variables are not actually written to the \"official\"\n *        environmental variable store of the process.\n *\n * callbacks : a pointer to the plug-in callback function struct.\n *\n */\nstruct openvpn_plugin_args_open_in\n{\n    const int type_mask;\n    const char **const argv;\n    const char **const envp;\n    struct openvpn_plugin_callbacks *callbacks;\n    const ovpnSSLAPI ssl_api;\n    const char *ovpn_version;\n    const unsigned int ovpn_version_major;\n    const unsigned int ovpn_version_minor;\n    const char *const ovpn_version_patch;\n};\n\n\n/**\n * Arguments used to transport variables from the plug-in back\n * to the OpenVPN process.  The struct openvpn_plugin_args_open_return\n * is only used by the openvpn_plugin_open_v3() function.\n *\n * STRUCT MEMBERS\n *\n * type_mask  : The plug-in should set this value to the logical OR of all script\n *              types which the plug-in wants to intercept.  For example, if the\n *              script wants to intercept the client-connect and client-disconnect\n *              script types:\n *\n *              type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT)\n *                         | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_DISCONNECT)\n *\n * handle :     Pointer to a global plug-in context, created by the plug-in.  This pointer\n *              is passed on to the other plug-in calls.\n *\n * return_list : used to return data back to OpenVPN.\n *\n */\nstruct openvpn_plugin_args_open_return\n{\n    int type_mask;\n    openvpn_plugin_handle_t handle;\n    struct openvpn_plugin_string_list **return_list;\n};\n\n/**\n * Arguments used to transport variables to and from the\n * plug-in.  The struct openvpn_plugin_args_func is only used\n * by the openvpn_plugin_func_v3() function.\n *\n * STRUCT MEMBERS:\n *\n * type : one of the PLUGIN_x types.\n *\n * argv : a NULL-terminated array of \"command line\" options which\n *        would normally be passed to the script.  argv[0] is the dynamic\n *        library pathname.\n *\n * envp : a NULL-terminated array of OpenVPN-set environmental\n *        variables in \"name=value\" format.  Note that for security reasons,\n *        these variables are not actually written to the \"official\"\n *        environmental variable store of the process.\n *\n * handle : Pointer to a global plug-in context, created by the plug-in's openvpn_plugin_open_v3().\n *\n * per_client_context : the per-client context pointer which was returned by\n *        openvpn_plugin_client_constructor_v1, if defined.\n *\n * current_cert_depth : Certificate depth of the certificate being passed over\n *\n * *current_cert : X509 Certificate object received from the client\n *\n */\nstruct openvpn_plugin_args_func_in\n{\n    const int type;\n    const char **const argv;\n    const char **const envp;\n    openvpn_plugin_handle_t handle;\n    void *per_client_context;\n    int current_cert_depth;\n    openvpn_x509_cert_t *current_cert;\n};\n\n\n/**\n * Arguments used to transport variables to and from the\n * plug-in.  The struct openvpn_plugin_args_func is only used\n * by the openvpn_plugin_func_v3() function.\n *\n * STRUCT MEMBERS:\n *\n * return_list : used to return data back to OpenVPN for further processing/usage by\n *               the OpenVPN executable.\n *\n */\nstruct openvpn_plugin_args_func_return\n{\n    struct openvpn_plugin_string_list **return_list;\n};\n\n/*\n * Multiple plugin modules can be cascaded, and modules can be\n * used in tandem with scripts.  The order of operation is that\n * the module func() functions are called in the order that\n * the modules were specified in the config file.  If a script\n * was specified as well, it will be called last.  If the\n * return code of the module/script controls an authentication\n * function (such as tls-verify or auth-user-pass-verify), then\n * every module and script must return success (0) in order for\n * the connection to be authenticated.\n *\n * Notes:\n *\n * Plugins which use a privilege-separation model (by forking in\n * their initialization function before the main OpenVPN process\n * downgrades root privileges and/or executes a chroot) must\n * daemonize after a fork if the \"daemon\" environmental variable is\n * set.  In addition, if the \"daemon_log_redirect\" variable is set,\n * the plugin should preserve stdout/stderr across the daemon()\n * syscall.  See the daemonize() function in plugin/auth-pam/auth-pam.c\n * for an example.\n */\n\n/*\n * Prototypes for functions which OpenVPN plug-ins must define.\n */\n\n/*\n * FUNCTION: openvpn_plugin_open_v2\n *\n * REQUIRED: YES\n *\n * Called on initial plug-in load.  OpenVPN will preserve plug-in state\n * across SIGUSR1 restarts but not across SIGHUP restarts.  A SIGHUP reset\n * will cause the plugin to be closed and reopened.\n *\n * ARGUMENTS\n *\n * *type_mask : Set by OpenVPN to the logical OR of all script\n *              types which this version of OpenVPN supports.  The plug-in\n *              should set this value to the logical OR of all script types\n *              which the plug-in wants to intercept.  For example, if the\n *              script wants to intercept the client-connect and\n *              client-disconnect script types:\n *\n *              *type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT)\n *                         | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_DISCONNECT)\n *\n * argv : a NULL-terminated array of options provided to the OpenVPN\n *        \"plug-in\" directive.  argv[0] is the dynamic library pathname.\n *\n * envp : a NULL-terminated array of OpenVPN-set environmental\n *        variables in \"name=value\" format.  Note that for security reasons,\n *        these variables are not actually written to the \"official\"\n *        environmental variable store of the process.\n *\n * return_list : used to return data back to OpenVPN.\n *\n * RETURN VALUE\n *\n * An openvpn_plugin_handle_t value on success, NULL on failure\n */\nOPENVPN_PLUGIN_DEF openvpn_plugin_handle_t OPENVPN_PLUGIN_FUNC(openvpn_plugin_open_v2)\n    (unsigned int *type_mask,\n    const char *argv[],\n    const char *envp[],\n    struct openvpn_plugin_string_list **return_list);\n\n/*\n * FUNCTION: openvpn_plugin_func_v2\n *\n * Called to perform the work of a given script type.\n *\n * REQUIRED: YES\n *\n * ARGUMENTS\n *\n * handle : the openvpn_plugin_handle_t value which was returned by\n *          openvpn_plugin_open.\n *\n * type : one of the PLUGIN_x types\n *\n * argv : a NULL-terminated array of \"command line\" options which\n *        would normally be passed to the script.  argv[0] is the dynamic\n *        library pathname.\n *\n * envp : a NULL-terminated array of OpenVPN-set environmental\n *        variables in \"name=value\" format.  Note that for security reasons,\n *        these variables are not actually written to the \"official\"\n *        environmental variable store of the process.\n *\n * per_client_context : the per-client context pointer which was returned by\n *        openvpn_plugin_client_constructor_v1, if defined.\n *\n * return_list : used to return data back to OpenVPN.\n *\n * RETURN VALUE\n *\n * OPENVPN_PLUGIN_FUNC_SUCCESS on success, OPENVPN_PLUGIN_FUNC_ERROR on failure\n *\n * In addition, OPENVPN_PLUGIN_FUNC_DEFERRED may be returned by\n * OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, OPENVPN_PLUGIN_CLIENT_CONNECT and\n * OPENVPN_PLUGIN_CLIENT_CONNECT_V2. This enables asynchronous\n * authentication or client connect  where the plugin (or one of its agents)\n * may indicate authentication success/failure or client configuration some\n * number of seconds after the return of the function handler.\n * For OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY and OPENVPN_PLUGIN_CLIENT_CONNECT\n * this is done by writing a single char to the file named by\n * auth_control_file/client_connect_deferred_file\n * in the environmental variable list (envp).\n *\n * Additionally the auth_pending_file can be written, which causes the openvpn\n * server to send a pending auth request to the client. See doc/management.txt\n * for more details on this authentication mechanism. The format of the\n * auth_pending_file is\n * line 1: timeout in seconds\n * line 2: Pending auth method the client needs to support (e.g. openurl)\n * line 3: EXTRA (e.g. WEBAUTH::http://www.example.com)\n *\n * In addition the OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER and\n * OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2 are called when OpenVPN tries to\n * get the deferred result. For a V2 call implementing this function is\n * required as information is not passed by files. For the normal version\n * the call is optional.\n *\n * first char of auth_control_file:\n * '0' -- indicates auth failure\n * '1' -- indicates auth success\n *\n * OpenVPN will delete the auth_control_file after it goes out of scope.\n *\n * See sample/sample-plugins/defer/multi-auth.c for an example on using\n * asynchronous authentication.\n */\nOPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_func_v2)\n    (openvpn_plugin_handle_t handle,\n    const int type,\n    const char *argv[],\n    const char *envp[],\n    void *per_client_context,\n    struct openvpn_plugin_string_list **return_list);\n\n\n/*\n * FUNCTION: openvpn_plugin_open_v3\n *\n * REQUIRED: YES\n *\n * Called on initial plug-in load.  OpenVPN will preserve plug-in state\n * across SIGUSR1 restarts but not across SIGHUP restarts.  A SIGHUP reset\n * will cause the plugin to be closed and reopened.\n *\n * ARGUMENTS\n *\n * version : fixed value, defines the API version of the OpenVPN plug-in API.  The plug-in\n *           should validate that this value is matching the OPENVPN_PLUGINv3_STRUCTVER\n *           value.\n *\n * arguments : Structure with all arguments available to the plug-in.\n *\n * retptr :    used to return data back to OpenVPN.\n *\n * RETURN VALUE\n *\n * OPENVPN_PLUGIN_FUNC_SUCCESS on success, OPENVPN_PLUGIN_FUNC_ERROR on failure\n */\nOPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_open_v3)\n    (const int version,\n    struct openvpn_plugin_args_open_in const *arguments,\n    struct openvpn_plugin_args_open_return *retptr);\n\n/*\n * FUNCTION: openvpn_plugin_func_v3\n *\n * Called to perform the work of a given script type.\n *\n * REQUIRED: YES\n *\n * ARGUMENTS\n *\n * version : fixed value, defines the API version of the OpenVPN plug-in API.  The plug-in\n *           should validate that this value is matching the OPENVPN_PLUGINv3_STRUCTVER\n *           value.\n *\n * arguments : Structure with all arguments available to the plug-in.\n *\n * retptr :    used to return data back to OpenVPN.\n *\n * RETURN VALUE\n *\n * OPENVPN_PLUGIN_FUNC_SUCCESS on success, OPENVPN_PLUGIN_FUNC_ERROR on failure\n *\n * In addition, OPENVPN_PLUGIN_FUNC_DEFERRED may be returned by\n * OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY.  This enables asynchronous\n * authentication where the plugin (or one of its agents) may indicate\n * authentication success/failure some number of seconds after the return\n * of the OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY handler by writing a single\n * char to the file named by auth_control_file in the environmental variable\n * list (envp).\n *\n * first char of auth_control_file:\n * '0' -- indicates auth failure\n * '1' -- indicates auth success\n *\n * OpenVPN will delete the auth_control_file after it goes out of scope.\n *\n * See sample/sample-plugins/defer/simple.c for an example on using\n * asynchronous authentication.\n */\nOPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_func_v3)\n    (const int version,\n    struct openvpn_plugin_args_func_in const *arguments,\n    struct openvpn_plugin_args_func_return *retptr);\n\n/*\n * FUNCTION: openvpn_plugin_close_v1\n *\n * REQUIRED: YES\n *\n * ARGUMENTS\n *\n * handle : the openvpn_plugin_handle_t value which was returned by\n *          openvpn_plugin_open.\n *\n * Called immediately prior to plug-in unload.\n */\nOPENVPN_PLUGIN_DEF void OPENVPN_PLUGIN_FUNC(openvpn_plugin_close_v1)\n    (openvpn_plugin_handle_t handle);\n\n/*\n * FUNCTION: openvpn_plugin_abort_v1\n *\n * REQUIRED: NO\n *\n * ARGUMENTS\n *\n * handle : the openvpn_plugin_handle_t value which was returned by\n *          openvpn_plugin_open.\n *\n * Called when OpenVPN is in the process of aborting due to a fatal error.\n * Will only be called on an open context returned by a prior successful\n * openvpn_plugin_open callback.\n */\nOPENVPN_PLUGIN_DEF void OPENVPN_PLUGIN_FUNC(openvpn_plugin_abort_v1)\n    (openvpn_plugin_handle_t handle);\n\n/*\n * FUNCTION: openvpn_plugin_client_constructor_v1\n *\n * Called to allocate a per-client memory region, which\n * is then passed to the openvpn_plugin_func_v2 function.\n * This function is called every time the OpenVPN server\n * constructs a client instance object, which normally\n * occurs when a session-initiating packet is received\n * by a new client, even before the client has authenticated.\n *\n * This function should allocate the private memory needed\n * by the plugin to track individual OpenVPN clients, and\n * return a void * to this memory region.\n *\n * REQUIRED: NO\n *\n * ARGUMENTS\n *\n * handle : the openvpn_plugin_handle_t value which was returned by\n *          openvpn_plugin_open.\n *\n * RETURN VALUE\n *\n * void * pointer to plugin's private per-client memory region, or NULL\n * if no memory region is required.\n */\nOPENVPN_PLUGIN_DEF void *OPENVPN_PLUGIN_FUNC(openvpn_plugin_client_constructor_v1)\n    (openvpn_plugin_handle_t handle);\n\n/*\n * FUNCTION: openvpn_plugin_client_destructor_v1\n *\n * This function is called on client instance object destruction.\n *\n * REQUIRED: NO\n *\n * ARGUMENTS\n *\n * handle : the openvpn_plugin_handle_t value which was returned by\n *          openvpn_plugin_open.\n *\n * per_client_context : the per-client context pointer which was returned by\n *        openvpn_plugin_client_constructor_v1, if defined.\n */\nOPENVPN_PLUGIN_DEF void OPENVPN_PLUGIN_FUNC(openvpn_plugin_client_destructor_v1)\n    (openvpn_plugin_handle_t handle, void *per_client_context);\n\n/*\n * FUNCTION: openvpn_plugin_select_initialization_point_v1\n *\n * Several different points exist in OpenVPN's initialization sequence where\n * the openvpn_plugin_open function can be called.  While the default is\n * OPENVPN_PLUGIN_INIT_PRE_DAEMON, this function can be used to select a\n * different initialization point.  For example, if your plugin needs to\n * return configuration parameters to OpenVPN, use\n * OPENVPN_PLUGIN_INIT_PRE_CONFIG_PARSE.\n *\n * REQUIRED: NO\n *\n * RETURN VALUE:\n *\n * An OPENVPN_PLUGIN_INIT_x value.\n */\n#define OPENVPN_PLUGIN_INIT_PRE_CONFIG_PARSE 1\n#define OPENVPN_PLUGIN_INIT_PRE_DAEMON       2 /* default */\n#define OPENVPN_PLUGIN_INIT_POST_DAEMON      3\n#define OPENVPN_PLUGIN_INIT_POST_UID_CHANGE  4\n\nOPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_select_initialization_point_v1)\n    (void);\n\n/*\n * FUNCTION: openvpn_plugin_min_version_required_v1\n *\n * This function is called by OpenVPN to query the minimum\n * plugin interface version number required by the plugin.\n *\n * REQUIRED: NO\n *\n * RETURN VALUE\n *\n * The minimum OpenVPN plugin interface version number necessary to support\n * this plugin.\n */\nOPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_min_version_required_v1)\n    (void);\n\n/*\n * Deprecated functions which are still supported for backward compatibility.\n */\n\nOPENVPN_PLUGIN_DEF openvpn_plugin_handle_t OPENVPN_PLUGIN_FUNC(openvpn_plugin_open_v1)\n    (unsigned int *type_mask,\n    const char *argv[],\n    const char *envp[]);\n\nOPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_func_v1)\n    (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[]);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* OPENVPN_PLUGIN_H_ */\n"
  },
  {
    "path": "ltrc.inc",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2008-2012 Alon Bar-Lev <alon.barlev@gmail.com>\n#\n# Required to build Windows resource file\n\nRCCOMPILE = $(RC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \\\n\t$(AM_CPPFLAGS) $(CPPFLAGS)\nLTRCCOMPILE = $(LIBTOOL) --mode=compile --tag=RC $(RCCOMPILE)\n\n.rc.lo:\n\t$(LTRCCOMPILE) -i \"$<\" -o \"$@\"\n\n.rc.o:\n\t$(RCCOMPILE) -i \"$<\" -o \"$@\"\n\n.mc.rc:\n\t$(WINDMC) \"$<\"\n"
  },
  {
    "path": "m4/.keep",
    "content": ""
  },
  {
    "path": "m4/ax_socklen_t.m4",
    "content": "dnl -- The following is base of curl's acinclude.m4 --\ndnl Check for socklen_t: historically on BSD it is an int, and in\ndnl POSIX 1g it is a type of its own, but some platforms use different\ndnl types for the argument to getsockopt, getpeername, etc.  So we\ndnl have to test to find something that will work.\nAC_DEFUN([AX_TYPE_SOCKLEN_T], [\n\tAC_CHECK_TYPE(\n\t\t[socklen_t],\n\t\t,\n\t\t[\n\t\t\tAS_VAR_PUSHDEF([VAR],[ax_cv_socklen_t_equiv])dnl\n\t\t\tAC_CACHE_CHECK(\n\t\t\t\t[for socklen_t equivalent],\n\t\t\t\t[VAR],\n\t\t\t\t[\n\t\t\t\t\t#AS_CASE is not supported on <autoconf-2.60\n\t\t\t\t\tcase \"${host}\" in\n\t\t\t\t\t*-mingw*) VAR=int ;;\n\t\t\t\t\t*)\n\t\t\t\t\t\t# Systems have either \"struct sockaddr *\" or\n\t\t\t\t\t\t# \"void *\" as the second argument to getpeername\n\t\t\t\t\t\tfor arg2 in \"struct sockaddr\" void; do\n\t\t\t\t\t\t\tfor t in int size_t unsigned long \"unsigned long\"; do\n\t\t\t\t\t\t\t\tAC_COMPILE_IFELSE(\n\t\t\t\t\t\t\t\t\t[AC_LANG_PROGRAM(\n\t\t\t\t\t\t\t\t\t\t[[\n#include <sys/types.h>\n#include <sys/socket.h>\nint getpeername (int, $arg2 *, $t *);\n\t\t\t\t\t\t\t\t\t\t]],\n\t\t\t\t\t\t\t\t\t\t[[\n$t len;\ngetpeername(0,0,&len);\n\t\t\t\t\t\t\t\t\t\t]]\n\t\t\t\t\t\t\t\t\t)],\n\t\t\t\t\t\t\t\t\t[VAR=\"$t\"; break]\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tdone\n\t\t\t\t\t\t\ttest -n \"$VAR\" && break\n\t\t\t\t\t\tdone\n\t\t\t\t\t\t;;\n\t\t\t\t\tesac\n\t\t\t\t]\n\t\t\t\tAS_VAR_IF(\n\t\t\t\t\t[VAR],\n\t\t\t\t\t[],\n\t\t\t\t\t[AC_MSG_ERROR([Cannot find a type to use in place of socklen_t])],\n\t\t\t\t\t[AC_DEFINE_UNQUOTED(\n\t\t\t\t\t\t[socklen_t],\n\t\t\t\t\t\t[$VAR],\n\t\t\t\t\t\t[type to use in place of socklen_t if not defined]\n\t\t\t\t\t)]\n\t\t\t\t)\n\t\t\t)\n\t\t],\n\t\t[[\n#include <sys/types.h>\n#ifdef _WIN32\n#include <ws2tcpip.h>\n#else\n#include <sys/socket.h>\n#endif\n\t\t]]\n\t)\n])\n"
  },
  {
    "path": "m4/pkg.m4",
    "content": "# pkg.m4 - Macros to locate and utilise pkg-config.   -*- Autoconf -*-\n# serial 12 (pkg-config-0.29.2)\n\ndnl Copyright © 2004 Scott James Remnant <scott@netsplit.com>.\ndnl Copyright © 2012-2015 Dan Nicholson <dbn.lists@gmail.com>\ndnl\ndnl This program is free software; you can redistribute it and/or modify\ndnl it under the terms of the GNU General Public License as published by\ndnl the Free Software Foundation; either version 2 of the License, or\ndnl (at your option) any later version.\ndnl\ndnl This program is distributed in the hope that it will be useful, but\ndnl WITHOUT ANY WARRANTY; without even the implied warranty of\ndnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\ndnl General Public License for more details.\ndnl\ndnl You should have received a copy of the GNU General Public License\ndnl along with this program; if not, write to the Free Software\ndnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA\ndnl 02111-1307, USA.\ndnl\ndnl As a special exception to the GNU General Public License, if you\ndnl distribute this file as part of a program that contains a\ndnl configuration script generated by Autoconf, you may include it under\ndnl the same distribution terms that you use for the rest of that\ndnl program.\n\ndnl PKG_PREREQ(MIN-VERSION)\ndnl -----------------------\ndnl Since: 0.29\ndnl\ndnl Verify that the version of the pkg-config macros are at least\ndnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's\ndnl installed version of pkg-config, this checks the developer's version\ndnl of pkg.m4 when generating configure.\ndnl\ndnl To ensure that this macro is defined, also add:\ndnl m4_ifndef([PKG_PREREQ],\ndnl     [m4_fatal([must install pkg-config 0.29 or later before running autoconf/autogen])])\ndnl\ndnl See the \"Since\" comment for each macro you use to see what version\ndnl of the macros you require.\nm4_defun([PKG_PREREQ],\n[m4_define([PKG_MACROS_VERSION], [0.29.2])\nm4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1,\n    [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])])\n])dnl PKG_PREREQ\n\ndnl PKG_PROG_PKG_CONFIG([MIN-VERSION])\ndnl ----------------------------------\ndnl Since: 0.16\ndnl\ndnl Search for the pkg-config tool and set the PKG_CONFIG variable to\ndnl first found in the path. Checks that the version of pkg-config found\ndnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is\ndnl used since that's the first version where most current features of\ndnl pkg-config existed.\nAC_DEFUN([PKG_PROG_PKG_CONFIG],\n[m4_pattern_forbid([^_?PKG_[A-Z_]+$])\nm4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$])\nm4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$])\nAC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])\nAC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path])\nAC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path])\n\nif test \"x$ac_cv_env_PKG_CONFIG_set\" != \"xset\"; then\n\tAC_PATH_TOOL([PKG_CONFIG], [pkg-config])\nfi\nif test -n \"$PKG_CONFIG\"; then\n\t_pkg_min_version=m4_default([$1], [0.9.0])\n\tAC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])\n\tif $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then\n\t\tAC_MSG_RESULT([yes])\n\telse\n\t\tAC_MSG_RESULT([no])\n\t\tPKG_CONFIG=\"\"\n\tfi\nfi[]dnl\n])dnl PKG_PROG_PKG_CONFIG\n\ndnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])\ndnl -------------------------------------------------------------------\ndnl Since: 0.18\ndnl\ndnl Check to see whether a particular set of modules exists. Similar to\ndnl PKG_CHECK_MODULES(), but does not set variables or print errors.\ndnl\ndnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG])\ndnl only at the first occurence in configure.ac, so if the first place\ndnl it's called might be skipped (such as if it is within an \"if\", you\ndnl have to call PKG_CHECK_EXISTS manually\nAC_DEFUN([PKG_CHECK_EXISTS],\n[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl\nif test -n \"$PKG_CONFIG\" && \\\n    AC_RUN_LOG([$PKG_CONFIG --exists --print-errors \"$1\"]); then\n  m4_default([$2], [:])\nm4_ifvaln([$3], [else\n  $3])dnl\nfi])\n\ndnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])\ndnl ---------------------------------------------\ndnl Internal wrapper calling pkg-config via PKG_CONFIG and setting\ndnl pkg_failed based on the result.\nm4_define([_PKG_CONFIG],\n[if test -n \"$$1\"; then\n    pkg_cv_[]$1=\"$$1\"\n elif test -n \"$PKG_CONFIG\"; then\n    PKG_CHECK_EXISTS([$3],\n                     [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 \"$3\" 2>/dev/null`\n\t\t      test \"x$?\" != \"x0\" && pkg_failed=yes ],\n\t\t     [pkg_failed=yes])\n else\n    pkg_failed=untried\nfi[]dnl\n])dnl _PKG_CONFIG\n\ndnl _PKG_SHORT_ERRORS_SUPPORTED\ndnl ---------------------------\ndnl Internal check to see if pkg-config supports short errors.\nAC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],\n[AC_REQUIRE([PKG_PROG_PKG_CONFIG])\nif $PKG_CONFIG --atleast-pkgconfig-version 0.20; then\n        _pkg_short_errors_supported=yes\nelse\n        _pkg_short_errors_supported=no\nfi[]dnl\n])dnl _PKG_SHORT_ERRORS_SUPPORTED\n\n\ndnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],\ndnl   [ACTION-IF-NOT-FOUND])\ndnl --------------------------------------------------------------\ndnl Since: 0.4.0\ndnl\ndnl Note that if there is a possibility the first call to\ndnl PKG_CHECK_MODULES might not happen, you should be sure to include an\ndnl explicit call to PKG_PROG_PKG_CONFIG in your configure.ac\nAC_DEFUN([PKG_CHECK_MODULES],\n[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl\nAC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl\nAC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl\n\npkg_failed=no\nAC_MSG_CHECKING([for $2])\n\n_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])\n_PKG_CONFIG([$1][_LIBS], [libs], [$2])\n\nm4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS\nand $1[]_LIBS to avoid the need to call pkg-config.\nSee the pkg-config man page for more details.])\n\nif test $pkg_failed = yes; then\n        AC_MSG_RESULT([no])\n        _PKG_SHORT_ERRORS_SUPPORTED\n        if test $_pkg_short_errors_supported = yes; then\n\t        $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs \"$2\" 2>&1`\n        else\n\t        $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs \"$2\" 2>&1`\n        fi\n\t# Put the nasty error message in config.log where it belongs\n\techo \"$$1[]_PKG_ERRORS\" >&AS_MESSAGE_LOG_FD\n\n\tm4_default([$4], [AC_MSG_ERROR(\n[Package requirements ($2) were not met:\n\n$$1_PKG_ERRORS\n\nConsider adjusting the PKG_CONFIG_PATH environment variable if you\ninstalled software in a non-standard prefix.\n\n_PKG_TEXT])[]dnl\n        ])\nelif test $pkg_failed = untried; then\n        AC_MSG_RESULT([no])\n\tm4_default([$4], [AC_MSG_FAILURE(\n[The pkg-config script could not be found or is too old.  Make sure it\nis in your PATH or set the PKG_CONFIG environment variable to the full\npath to pkg-config.\n\n_PKG_TEXT\n\nTo get pkg-config, see <https://www.freedesktop.org/wiki/Software/pkg-config/>.])[]dnl\n        ])\nelse\n\t$1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS\n\t$1[]_LIBS=$pkg_cv_[]$1[]_LIBS\n        AC_MSG_RESULT([yes])\n\t$3\nfi[]dnl\n])dnl PKG_CHECK_MODULES\n\n\ndnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],\ndnl   [ACTION-IF-NOT-FOUND])\ndnl ---------------------------------------------------------------------\ndnl Since: 0.29\ndnl\ndnl Checks for existence of MODULES and gathers its build flags with\ndnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags\ndnl and VARIABLE-PREFIX_LIBS from --libs.\ndnl\ndnl Note that if there is a possibility the first call to\ndnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to\ndnl include an explicit call to PKG_PROG_PKG_CONFIG in your\ndnl configure.ac.\nAC_DEFUN([PKG_CHECK_MODULES_STATIC],\n[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl\n_save_PKG_CONFIG=$PKG_CONFIG\nPKG_CONFIG=\"$PKG_CONFIG --static\"\nPKG_CHECK_MODULES($@)\nPKG_CONFIG=$_save_PKG_CONFIG[]dnl\n])dnl PKG_CHECK_MODULES_STATIC\n\n\ndnl PKG_INSTALLDIR([DIRECTORY])\ndnl -------------------------\ndnl Since: 0.27\ndnl\ndnl Substitutes the variable pkgconfigdir as the location where a module\ndnl should install pkg-config .pc files. By default the directory is\ndnl $libdir/pkgconfig, but the default can be changed by passing\ndnl DIRECTORY. The user can override through the --with-pkgconfigdir\ndnl parameter.\nAC_DEFUN([PKG_INSTALLDIR],\n[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])])\nm4_pushdef([pkg_description],\n    [pkg-config installation directory @<:@]pkg_default[@:>@])\nAC_ARG_WITH([pkgconfigdir],\n    [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],,\n    [with_pkgconfigdir=]pkg_default)\nAC_SUBST([pkgconfigdir], [$with_pkgconfigdir])\nm4_popdef([pkg_default])\nm4_popdef([pkg_description])\n])dnl PKG_INSTALLDIR\n\n\ndnl PKG_NOARCH_INSTALLDIR([DIRECTORY])\ndnl --------------------------------\ndnl Since: 0.27\ndnl\ndnl Substitutes the variable noarch_pkgconfigdir as the location where a\ndnl module should install arch-independent pkg-config .pc files. By\ndnl default the directory is $datadir/pkgconfig, but the default can be\ndnl changed by passing DIRECTORY. The user can override through the\ndnl --with-noarch-pkgconfigdir parameter.\nAC_DEFUN([PKG_NOARCH_INSTALLDIR],\n[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])])\nm4_pushdef([pkg_description],\n    [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@])\nAC_ARG_WITH([noarch-pkgconfigdir],\n    [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],,\n    [with_noarch_pkgconfigdir=]pkg_default)\nAC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir])\nm4_popdef([pkg_default])\nm4_popdef([pkg_description])\n])dnl PKG_NOARCH_INSTALLDIR\n\n\ndnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE,\ndnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])\ndnl -------------------------------------------\ndnl Since: 0.28\ndnl\ndnl Retrieves the value of the pkg-config variable for the given module.\nAC_DEFUN([PKG_CHECK_VAR],\n[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl\nAC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl\n\n_PKG_CONFIG([$1], [variable=\"][$3][\"], [$2])\nAS_VAR_COPY([$1], [pkg_cv_][$1])\n\nAS_VAR_IF([$1], [\"\"], [$5], [$4])dnl\n])dnl PKG_CHECK_VAR\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n    \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n    \"packageRules\": [\n        {\n            \"matchDepTypes\": [\n                \"action\"\n            ],\n            \"groupName\": \"github actions\",\n            \"pinDigests\": true,\n            \"extends\": [\n                \"schedule:monthly\"\n            ]\n        },\n        {\n            \"matchDepNames\": [\n                \"vcpkg\"\n            ],\n            \"extends\": [\n                \"schedule:monthly\"\n            ]\n        }\n    ],\n    \"customManagers\": [\n        {\n            \"customType\": \"regex\",\n            \"managerFilePatterns\": [\n                \"/^\\\\.github/workflows/.+\\\\.ya?ml$/\"\n            ],\n            \"matchStrings\": [\n                \"vcpkgGitCommitId:\\\\s*(?<currentDigest>.*?)\\\\n\"\n            ],\n            \"currentValueTemplate\": \"master\",\n            \"depNameTemplate\": \"vcpkg\",\n            \"packageNameTemplate\": \"https://github.com/microsoft/vcpkg\",\n            \"datasourceTemplate\": \"git-refs\"\n        },\n        {\n            \"customType\": \"regex\",\n            \"managerFilePatterns\": [\n                \"/^\\\\.github/workflows/.+\\\\.ya?ml$/\"\n            ],\n            \"matchStrings\": [\n                \"versioning=(?<versioning>.*?)\\\\n\\\\s*repository:\\\\s*(?<depName>.*?)\\\\n\\\\s*ref:\\\\s*(?<currentValue>.*?)\\\\n\"\n            ],\n            \"datasourceTemplate\": \"github-tags\"\n        }\n    ]\n}\n"
  },
  {
    "path": "sample/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2006-2012 Alon Bar-Lev <alon.barlev@gmail.com>\n#\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n\nDISTCLEANFILES = \\\n       $(builddir)/sample-plugins/Makefile\n\nEXTRA_DIST = \\\n\tsample-plugins \\\n\tsample-config-files \\\n\tsample-keys \\\n\tsample-scripts\n"
  },
  {
    "path": "sample/sample-config-files/README",
    "content": "Sample OpenVPN Configuration Files.\n\nThese files are part of the OpenVPN HOWTO\nwhich is located at:\n\nhttp://openvpn.net/howto.html\n\nSee also the openvpn-examples man page.\n"
  },
  {
    "path": "sample/sample-config-files/client.conf",
    "content": "##############################################\n# Sample client-side OpenVPN 2.6 config file #\n# for connecting to multi-client server.     #\n#                                            #\n# This configuration can be used by multiple #\n# clients, however each client should have   #\n# its own cert and key files.                #\n#                                            #\n# On Windows, you might want to rename this  #\n# file so it has a .ovpn extension           #\n##############################################\n\n# Specify that we are a client and that we\n# will be pulling certain config file directives\n# from the server.\nclient\n\n# Use the same setting as you are using on\n# the server.\n# On most systems, the VPN will not function\n# unless you partially or fully disable\n# the firewall for the TUN/TAP interface.\n;dev tap\ndev tun\n\n# Windows needs the TAP-Win32 adapter name\n# from the Network Connections panel\n# if you have more than one.  On XP SP2,\n# you may need to disable the firewall\n# for the TAP adapter.\n;dev-node MyTap\n\n# Are we connecting to a TCP or\n# UDP server?  Use the same setting as\n# on the server.\n;proto tcp\nproto udp\n\n# The hostname/IP and port of the server.\n# You can have multiple remote entries\n# to load balance between the servers.\nremote my-server-1 1194\n;remote my-server-2 1194\n\n# Choose a random host from the remote\n# list for load-balancing.  Otherwise\n# try hosts in the order specified.\n;remote-random\n\n# Keep trying indefinitely to resolve the\n# host name of the OpenVPN server.  Very useful\n# on machines which are not permanently connected\n# to the internet such as laptops.\nresolv-retry infinite\n\n# Most clients don't need to bind to\n# a specific local port number.\nnobind\n\n# Downgrade privileges after initialization (non-Windows only)\n;user openvpn\n;group openvpn\n\n# Try to preserve some state across restarts.\npersist-tun\n\n# If you are connecting through an\n# HTTP proxy to reach the actual OpenVPN\n# server, put the proxy server/IP and\n# port number here.  See the man page\n# if your proxy server requires\n# authentication.\n;http-proxy-retry # retry on connection failures\n;http-proxy [proxy server] [proxy port #]\n\n# Wireless networks often produce a lot\n# of duplicate packets.  Set this flag\n# to silence duplicate packet warnings.\n;mute-replay-warnings\n\n# SSL/TLS parms.\n# See the server config file for more\n# description.  It's best to use\n# a separate .crt/.key file pair\n# for each client.  A single ca\n# file can be used for all clients.\nca ca.crt\ncert client.crt\nkey client.key\n\n# Verify server certificate by checking that the\n# certificate has the correct key usage set.\n# This is an important precaution to protect against\n# a potential attack discussed here:\n#  http://openvpn.net/howto.html#mitm\n#\n# To use this feature, you will need to generate\n# your server certificates with the keyUsage set to\n#   digitalSignature, keyEncipherment\n# and the extendedKeyUsage to\n#   serverAuth\n# EasyRSA can do this for you.\nremote-cert-tls server\n\n# Allow to connect to really old OpenVPN versions\n# without AEAD support (OpenVPN 2.3.x or older)\n# This adds AES-256-CBC as fallback cipher and\n# keeps the modern ciphers as well.\n;data-ciphers AES-256-GCM:AES-128-GCM:?CHACHA20-POLY1305:AES-256-CBC\n\n# If a tls-auth key is used on the server\n# then every client must also have the key.\n;tls-auth ta.key 1\n\n# Set log file verbosity.\nverb 3\n\n# Silence repeating messages\n;mute 20\n"
  },
  {
    "path": "sample/sample-config-files/firewall.sh",
    "content": "#!/bin/sh\n\n# A Sample OpenVPN-aware firewall.\n\n# eth0 is connected to the internet.\n# eth1 is connected to a private subnet.\n\n# Change this subnet to correspond to your private\n# ethernet subnet.  Home will use HOME_NET/24 and\n# Office will use OFFICE_NET/24.\nPRIVATE=10.0.0.0/24\n\n# Loopback address\nLOOP=127.0.0.1\n\n# Delete old iptables rules\n# and temporarily block all traffic.\niptables -P OUTPUT DROP\niptables -P INPUT DROP\niptables -P FORWARD DROP\niptables -F\n\n# Set default policies\niptables -P OUTPUT ACCEPT\niptables -P INPUT DROP\niptables -P FORWARD DROP\n\n# Prevent external packets from using loopback addr\niptables -A INPUT -i eth0 -s $LOOP -j DROP\niptables -A FORWARD -i eth0 -s $LOOP -j DROP\niptables -A INPUT -i eth0 -d $LOOP -j DROP\niptables -A FORWARD -i eth0 -d $LOOP -j DROP\n\n# Anything coming from the Internet should have a real Internet address\niptables -A FORWARD -i eth0 -s 192.168.0.0/16 -j DROP\niptables -A FORWARD -i eth0 -s 172.16.0.0/12 -j DROP\niptables -A FORWARD -i eth0 -s 10.0.0.0/8 -j DROP\niptables -A INPUT -i eth0 -s 192.168.0.0/16 -j DROP\niptables -A INPUT -i eth0 -s 172.16.0.0/12 -j DROP\niptables -A INPUT -i eth0 -s 10.0.0.0/8 -j DROP\n\n# Block outgoing NetBios (if you have windows machines running\n# on the private subnet).  This will not affect any NetBios\n# traffic that flows over the VPN tunnel, but it will stop\n# local windows machines from broadcasting themselves to\n# the internet.\niptables -A FORWARD -p tcp --sport 137:139 -o eth0 -j DROP\niptables -A FORWARD -p udp --sport 137:139 -o eth0 -j DROP\niptables -A OUTPUT -p tcp --sport 137:139 -o eth0 -j DROP\niptables -A OUTPUT -p udp --sport 137:139 -o eth0 -j DROP\n\n# Check source address validity on packets going out to internet\niptables -A FORWARD ! -s $PRIVATE -i eth1 -j DROP\n\n# Allow local loopback\niptables -A INPUT -s $LOOP -j ACCEPT\niptables -A INPUT -d $LOOP -j ACCEPT\n\n# Allow incoming pings (can be disabled)\niptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT\n\n# Allow services such as www and ssh (can be disabled)\niptables -A INPUT -p tcp --dport http -j ACCEPT\niptables -A INPUT -p tcp --dport ssh -j ACCEPT\n\n# Allow incoming OpenVPN packets\n# Duplicate the line below for each\n# OpenVPN tunnel, changing --dport n\n# to match the OpenVPN UDP port.\n#\n# In OpenVPN, the port number is\n# controlled by the --port n option.\n# If you put this option in the config\n# file, you can remove the leading '--'\n#\n# If you taking the stateful firewall\n# approach (see the OpenVPN HOWTO),\n# then comment out the line below.\n\niptables -A INPUT -p udp --dport 1194 -j ACCEPT\n\n# Allow packets from TUN/TAP devices.\n# When OpenVPN is run in a secure mode,\n# it will authenticate packets prior\n# to their arriving on a tun or tap\n# interface.  Therefore, it is not\n# necessary to add any filters here,\n# unless you want to restrict the\n# type of packets which can flow over\n# the tunnel.\n\niptables -A INPUT -i tun+ -j ACCEPT\niptables -A FORWARD -i tun+ -j ACCEPT\niptables -A INPUT -i tap+ -j ACCEPT\niptables -A FORWARD -i tap+ -j ACCEPT\n\n# Allow packets from private subnets\niptables -A INPUT -i eth1 -j ACCEPT\niptables -A FORWARD -i eth1 -j ACCEPT\n\n# Keep state of connections from local machine and private subnets\niptables -A OUTPUT -m state --state NEW -o eth0 -j ACCEPT\niptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT\niptables -A FORWARD -m state --state NEW -o eth0 -j ACCEPT\niptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT\n\n# Masquerade local subnet\niptables -t nat -A POSTROUTING -s $PRIVATE -o eth0 -j MASQUERADE\n"
  },
  {
    "path": "sample/sample-config-files/loopback-client",
    "content": "# Perform a TLS loopback test -- client side.\n#\n# This test performs a TLS negotiation once every 10 seconds,\n# and will terminate after 2 minutes.\n#\n# From the root directory of the OpenVPN distribution,\n# after openvpn has been built, run:\n#\n#  ./openvpn --config sample-config-files/loopback-client  (In one window) \n#  ./openvpn --config sample-config-files/loopback-server  (Simultaneously in another window) \n#\n# this config file has the crypto material (cert, key, ..) \"inlined\",\n# while the \"server\" config has it as external reference - test both paths\n\nrport 16000\nlport 16001\nremote localhost\nlocal localhost\ndev null\nverb 3\nreneg-sec 10\ntls-client\nremote-cert-tls server\n#ca sample-keys/ca.crt\n<ca>\n-----BEGIN CERTIFICATE-----\nMIIGPjCCBCagAwIBAgIUb1C400ZucjRZvAAz3XyuEusnRgYwDQYJKoZIhvcNAQEL\nBQAwZjELMAkGA1UEBhMCS0cxCzAJBgNVBAgMAk5BMRAwDgYDVQQHDAdCSVNIS0VL\nMRUwEwYDVQQKDAxPcGVuVlBOLVRFU1QxITAfBgkqhkiG9w0BCQEWEm1lQG15aG9z\ndC5teWRvbWFpbjAeFw0yMzExMDcxMjIzMzlaFw0zMzExMDQxMjIzMzlaMGYxCzAJ\nBgNVBAYTAktHMQswCQYDVQQIDAJOQTEQMA4GA1UEBwwHQklTSEtFSzEVMBMGA1UE\nCgwMT3BlblZQTi1URVNUMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21h\naW4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCI+p/ZLGUHCANTTFaK\nnw+J3wi+ef2EKJ5WHt5PWMuBeaDpeU4Ghuaow8HlRPjG9lDRHtn+WQgZz9nUejYH\n+wtmN2BHwJAM4OeUVoB95tBrxd/VDCrdIvypVKldHsU3VkEbvPAl1jq68WVk+DXM\nFZqTUoafDK+irOvL7Z5j2gA3FDzRUQs0L+jCvRTl4omFSjSQwoBCoVXxNEAg9jgy\nlNWUHx+JHDB8dk+gEmDai20ggBWeAeThUU9dVZvwjv4E7zMRMx1skCRdWcyALJQf\nfjc9q6gnB9X9nPxXdWb/lYKcivJBmBRHLeirnUFL2S2IYRc2H0ZbX1d+WzDJV37+\nDKYy9ehltyHFiaXmZThJ2Kg/mAD55U3NCWNBXmQ0CvzhUh6QIQiOJNQHmK0qxgnc\nPOJeE4X55dv1nAGD/0fGeHTcuShzUoipCKAd1CZdXK2Ge3gZRH2WUvlQGd5JARd4\n3zbd2wXZX0h0e1/BWQVeXx/Cg6u31B5lll7B3rWeoZHvfV9DSC7e3IEOhgzG5cyA\nh+wrtlCszjiMreHSSYCQh9tlyK+ACOJUFtZFGdseBsMxRgXWtHr+ypW2iJI4KsEU\n/MNXr1Bqg7FGxIw0Oyc2zyzjgD9aq4CKEy64MYB1ZYf41Rbc2Z+pMx1MW9orsPp7\nqSp6SmpTk0RTHpH0O2wNC9F26wIDAQABo4HjMIHgMB0GA1UdDgQWBBRzsbjWipVr\nEuB0fMVXVZiUW6x4XjCBowYDVR0jBIGbMIGYgBRzsbjWipVrEuB0fMVXVZiUW6x4\nXqFqpGgwZjELMAkGA1UEBhMCS0cxCzAJBgNVBAgMAk5BMRAwDgYDVQQHDAdCSVNI\nS0VLMRUwEwYDVQQKDAxPcGVuVlBOLVRFU1QxITAfBgkqhkiG9w0BCQEWEm1lQG15\naG9zdC5teWRvbWFpboIUb1C400ZucjRZvAAz3XyuEusnRgYwDAYDVR0TBAUwAwEB\n/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBABqhFuSPgqplHQtFnWwQ\nTKfrElQJ07gF0eaBBijQVSm2MswB2xnWF/S2NRjIpw7k5ZlmZsAbCVcGMwqJOkfJ\nyX3Z7gK+yNrZehzNSOCkv+H79ExsS9/HETSqZxMevIIH7O0t/pACv20f85unBzhc\nx+980RzufuHK17sG3Z+z+d6i9XDhaZvV/gm6bWTXft1ufRzI5R48xWVAfJd1X9Ln\nbZmqF9Ye1GHxka1Xna9nOCgAuYYoGxq2VkUSIjlRCMaLCHlsWEn0JbRnQXPfBts6\n/yQBywcEekKRutCugn5bn625kAJHWGxcb0xIXj+Rqnp2++p33lbE4J09zfIkh5hV\nRvCSzaE0Z3Kly9237CV+DyAqzrBJq5HHN/AT6+xFd2yGPMPKH8hKbf3jIprexNEp\noG1XC/dsPFkPLUyeD++kVjzsLiDmYAn2x3Dco6cWD7FfEljb1pHkAp5CctU9TjZH\n21xcAsPbfS0vrDmj8zG7eTU+BtleL4AfxEVsMBzrUB6jSdUMpJ/hRtni4RxOHLmU\n0DqtHIqrDrC5Gb2KunNUIYqPp+80LSD1/Edo5Vr+k5AiFYCzZFXSab+6e4hEsLEV\nnQNMmcPVWATQ2najGfNftmhwQx9hU4gJaCw/rfhEmwIif5BxgG5VPUzy97T+GmOZ\nInB0RDylv3Lq3Hs8mBF4nRt7\n-----END CERTIFICATE-----\n</ca>\n#key sample-keys/client.key\n<key>\n-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDdrrIKQObP4cGi\nodKDLDGY4huyhUBnAPqrv8+dFNHGt2ODql+cFKDSTQQ6SpLmkkukhkAmQr2Dt/xJ\nt1bSyudwhRaPizvaR56LakrI5qjermstUiIMnc9nu30eZgVTi3yurdGmUl89nmso\nGFfZoUItwLBN2krwKaoCNIYCqq9nCQbtRSGOjPh1Vsfq6E+IjhyLW2gtsWal5MY2\n4nCN/u8Q8FL4U5a/flFw8j+uWIc40aNr9jhRmxbOZzWObXZjTWubfXfaVW8gsWZP\nmi2kczpSIYY886ZaZ+V9EPU2ViF+AyK9mOkYtD+ztQ3t1e9Ulm+dRmxvDrpLGvfM\n1OUkutKlAgMBAAECggEANwi9ron6QzWaqtNdva7lCT1o/uLR4EB/+s99rVOT2K+C\nhxdu8QK2Aj+YgxgsbA15tfiWSGldPywX9/0KEv7IgkioFy7Lxx7sn1PeCQ4qck3+\n0ZuIVHWBHhGPuFI/lEQWyg7g81eTyWpg0+1nMeI02cLyggFlhUXyrOV5N4REU2GW\nC0KBQFyVQJPrFszomK8qsHOu/gaGC1vOwgIID3cQ3iLKXkoHNmHO4hgbeSy+SfDP\nQ5C0xxKQa2RUz0nLbByuGtLYOsJmbjUMWjFXyjmwBsPCcvRmFRdnxFvlnzwGEH4M\nZKsw+49p1iJFyuCv7KJ/ILLJmoEuryjrSmdj3esIqQKBgQDwC24VBQLNmlug8rkG\nYWaRePsWRJylDlWIeHnfmGe27p7ytxOvGe6hnPu6nfg8nXHtruZCIhGya6qbuVmL\nvGrg94ia4MSpDVUgGiElXXQ/Pl7O9/lnSlIlxcBAgd8uggxIAzCeYI6c3r7AQcmY\njARMwYNCxJjz5nLctMe2MCs4LwKBgQDsatDXb3xr6jmflCUZa8Kx8SOgBWEZTEGz\nKEoCQWnF2fHUCy4Bwm8Imnws3iX0198TyxkVD2rP8oGwFj2SAVtI2L8Y/g5A05TA\nknfmVECvGp/MN266ZdCA8G/MKbk727TxyJs+4AseAi5p6cBULqZHsJaZE74qlcEl\n5gFQu35ZawKBgBBgRz9J2zoZmLyvMm48ANpVzZNkVOdxxeYMigv2AsVZHCDk2oPs\nmfoOkqHVmxTPjPExKGZEmr54V+hNyc0dqpD0ci5WvTPnQ/JvtektqfuSjrdB9ZLV\nYCtRhV8hPQ+YMaxMA2oankAXdh35nv44NybhYMoSTXj+NMHX13QXbytjAoGAdVKw\n3yixWzB6dinjm1Dx5rJfVos024QPWqRUzfe+UPROYUdHBpKB3YgktXNs7KuwRbdV\ndDEZdabIGyV+WpWXwnflpbZ2Rk95k3NcUw5ep0cUJBkiNxhNt58aK/xMs1rd2dsO\nx84RVkwI0oCw9FXOKOeGZOL6TVHR70fMQU86bY8CgYEAqg/1AD9lXzbR57zaR/br\nAIn0WWU2mnU7Dc4uhmQd9+JExqrplKKHrUp8eQEOW8nij6MbPYlpgkMdatvDOJqP\nWrYtwZsKXGhnalvbS3ye20HqpjYpBR7co3Q9KMaaDNoQe9HtjbT80GXpQEbJN2Iu\nADo3hPoX0yENIbKFccMuptM=\n-----END PRIVATE KEY-----\n</key>\n#cert sample-keys/client.crt\n<cert>\nCertificate:\n    Data:\n        Version: 3 (0x2)\n        Serial Number: 2 (0x2)\n        Signature Algorithm: sha256WithRSAEncryption\n        Issuer: C=KG, ST=NA, L=BISHKEK, O=OpenVPN-TEST/emailAddress=me@myhost.mydomain\n        Validity\n            Not Before: Nov  7 12:23:39 2023 GMT\n            Not After : Nov  4 12:23:39 2033 GMT\n        Subject: C=KG, ST=NA, O=OpenVPN-TEST, CN=Test-Client/emailAddress=me@myhost.mydomain\n        Subject Public Key Info:\n            Public Key Algorithm: rsaEncryption\n                Public-Key: (2048 bit)\n                Modulus:\n                    00:dd:ae:b2:0a:40:e6:cf:e1:c1:a2:a1:d2:83:2c:\n                    31:98:e2:1b:b2:85:40:67:00:fa:ab:bf:cf:9d:14:\n                    d1:c6:b7:63:83:aa:5f:9c:14:a0:d2:4d:04:3a:4a:\n                    92:e6:92:4b:a4:86:40:26:42:bd:83:b7:fc:49:b7:\n                    56:d2:ca:e7:70:85:16:8f:8b:3b:da:47:9e:8b:6a:\n                    4a:c8:e6:a8:de:ae:6b:2d:52:22:0c:9d:cf:67:bb:\n                    7d:1e:66:05:53:8b:7c:ae:ad:d1:a6:52:5f:3d:9e:\n                    6b:28:18:57:d9:a1:42:2d:c0:b0:4d:da:4a:f0:29:\n                    aa:02:34:86:02:aa:af:67:09:06:ed:45:21:8e:8c:\n                    f8:75:56:c7:ea:e8:4f:88:8e:1c:8b:5b:68:2d:b1:\n                    66:a5:e4:c6:36:e2:70:8d:fe:ef:10:f0:52:f8:53:\n                    96:bf:7e:51:70:f2:3f:ae:58:87:38:d1:a3:6b:f6:\n                    38:51:9b:16:ce:67:35:8e:6d:76:63:4d:6b:9b:7d:\n                    77:da:55:6f:20:b1:66:4f:9a:2d:a4:73:3a:52:21:\n                    86:3c:f3:a6:5a:67:e5:7d:10:f5:36:56:21:7e:03:\n                    22:bd:98:e9:18:b4:3f:b3:b5:0d:ed:d5:ef:54:96:\n                    6f:9d:46:6c:6f:0e:ba:4b:1a:f7:cc:d4:e5:24:ba:\n                    d2:a5\n                Exponent: 65537 (0x10001)\n        X509v3 extensions:\n            X509v3 Basic Constraints:\n                CA:FALSE\n            X509v3 Subject Key Identifier:\n                59:33:B9:2E:63:D1:17:A8:9F:BD:D8:CE:94:21:C5:41:C7:31:62:5D\n            X509v3 Authority Key Identifier:\n                keyid:73:B1:B8:D6:8A:95:6B:12:E0:74:7C:C5:57:55:98:94:5B:AC:78:5E\n                DirName:/C=KG/ST=NA/L=BISHKEK/O=OpenVPN-TEST/emailAddress=me@myhost.mydomain\n                serial:6F:50:B8:D3:46:6E:72:34:59:BC:00:33:DD:7C:AE:12:EB:27:46:06\n    Signature Algorithm: sha256WithRSAEncryption\n    Signature Value:\n        2a:9e:02:65:f4:3c:c0:37:88:f0:21:f9:fd:2e:7c:f4:8b:bb:\n        67:7d:f7:48:0c:98:f7:a1:46:4e:33:af:68:77:f4:53:03:09:\n        fd:4e:32:cb:0f:2c:f1:16:37:35:65:aa:68:79:16:a9:32:03:\n        d7:89:10:ef:ba:fd:e1:26:2c:60:7c:3b:42:60:68:47:cf:61:\n        88:00:77:e7:71:76:49:78:35:52:45:a4:31:7e:2b:e1:0a:c8:\n        ed:e1:a7:28:2f:23:a3:ce:ce:b5:99:6b:54:4d:df:d2:64:0a:\n        b7:c5:25:1e:d4:f7:a1:fd:4f:f3:12:d3:26:5f:3b:b2:93:93:\n        d1:8b:4b:4e:dc:d0:15:63:d1:77:36:75:34:76:37:59:ff:a0:\n        81:01:ec:b6:42:2f:bd:85:5d:d0:ef:ff:90:61:d6:91:b0:f5:\n        e6:94:66:7e:4c:20:06:c4:2e:0c:9b:9f:7f:89:f0:3e:8f:e5:\n        06:6c:81:75:a2:0b:c5:ac:44:f1:32:cc:57:90:a0:19:47:8c:\n        25:7a:d5:f1:61:1f:19:bf:4c:31:da:44:c1:30:91:e8:b5:cc:\n        e4:7e:20:55:0a:b9:dc:f3:5e:f5:7c:d1:0b:ee:71:c6:d6:38:\n        7e:85:7b:6c:cb:10:85:1e:6a:50:ab:c3:ae:f9:ff:96:4f:a3:\n        76:d6:fd:c0:f9:c7:9a:60:a8:8c:e5:9a:c5:a9:7b:63:11:ef:\n        7b:b9:9b:1f:63:51:a8:6d:2b:d6:f7:ef:51:bd:a8:32:9e:92:\n        aa:24:01:c9:e3:6a:c8:94:2e:d2:66:b2:c7:17:e5:06:53:9a:\n        bd:8a:19:8f:3a:51:7a:25:11:e5:e8:59:f7:1b:df:95:98:35:\n        c1:a6:74:15:6b:b1:2c:97:9b:fe:76:7e:56:20:4d:ee:07:8a:\n        b9:8b:bc:92:a9:19:81:28:91:4e:d2:9f:51:99:72:c0:12:76:\n        5b:c8:74:68:b5:9d:43:53:c1:af:39:b9:28:82:a0:0e:bb:ef:\n        21:d8:71:dd:02:af:dc:df:48:7b:39:21:7d:83:76:ea:e2:c7:\n        16:bb:d2:1a:1d:22:f6:4b:47:15:56:41:06:4d:39:1c:96:3f:\n        25:2d:83:8f:a4:a2:86:fa:0e:e9:45:9c:bf:26:40:e6:3e:9e:\n        d5:00:9f:ce:76:6f:df:cb:b2:85:b8:83:f2:ed:8b:b6:5a:68:\n        b5:c7:1b:ab:19:75:60:f3:5b:e7:5c:70:27:d9:1c:d8:24:f0:\n        2a:aa:2a:a6:98:77:d6:36:d9:02:35:a8:d3:2c:19:88:b8:0b:\n        d3:76:58:72:54:99:94:9a:ee:38:9b:8d:8e:10:48:cd:28:50:\n        31:b2:4b:d3:69:7b:91:b4\n-----BEGIN CERTIFICATE-----\nMIIFHzCCAwegAwIBAgIBAjANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJLRzEL\nMAkGA1UECAwCTkExEDAOBgNVBAcMB0JJU0hLRUsxFTATBgNVBAoMDE9wZW5WUE4t\nVEVTVDEhMB8GCSqGSIb3DQEJARYSbWVAbXlob3N0Lm15ZG9tYWluMB4XDTIzMTEw\nNzEyMjMzOVoXDTMzMTEwNDEyMjMzOVowajELMAkGA1UEBhMCS0cxCzAJBgNVBAgM\nAk5BMRUwEwYDVQQKDAxPcGVuVlBOLVRFU1QxFDASBgNVBAMMC1Rlc3QtQ2xpZW50\nMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW4wggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDdrrIKQObP4cGiodKDLDGY4huyhUBnAPqrv8+d\nFNHGt2ODql+cFKDSTQQ6SpLmkkukhkAmQr2Dt/xJt1bSyudwhRaPizvaR56LakrI\n5qjermstUiIMnc9nu30eZgVTi3yurdGmUl89nmsoGFfZoUItwLBN2krwKaoCNIYC\nqq9nCQbtRSGOjPh1Vsfq6E+IjhyLW2gtsWal5MY24nCN/u8Q8FL4U5a/flFw8j+u\nWIc40aNr9jhRmxbOZzWObXZjTWubfXfaVW8gsWZPmi2kczpSIYY886ZaZ+V9EPU2\nViF+AyK9mOkYtD+ztQ3t1e9Ulm+dRmxvDrpLGvfM1OUkutKlAgMBAAGjgdMwgdAw\nCQYDVR0TBAIwADAdBgNVHQ4EFgQUWTO5LmPRF6ifvdjOlCHFQccxYl0wgaMGA1Ud\nIwSBmzCBmIAUc7G41oqVaxLgdHzFV1WYlFuseF6haqRoMGYxCzAJBgNVBAYTAktH\nMQswCQYDVQQIDAJOQTEQMA4GA1UEBwwHQklTSEtFSzEVMBMGA1UECgwMT3BlblZQ\nTi1URVNUMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW6CFG9QuNNG\nbnI0WbwAM918rhLrJ0YGMA0GCSqGSIb3DQEBCwUAA4ICAQAqngJl9DzAN4jwIfn9\nLnz0i7tnffdIDJj3oUZOM69od/RTAwn9TjLLDyzxFjc1ZapoeRapMgPXiRDvuv3h\nJixgfDtCYGhHz2GIAHfncXZJeDVSRaQxfivhCsjt4acoLyOjzs61mWtUTd/SZAq3\nxSUe1Peh/U/zEtMmXzuyk5PRi0tO3NAVY9F3NnU0djdZ/6CBAey2Qi+9hV3Q7/+Q\nYdaRsPXmlGZ+TCAGxC4Mm59/ifA+j+UGbIF1ogvFrETxMsxXkKAZR4wletXxYR8Z\nv0wx2kTBMJHotczkfiBVCrnc8171fNEL7nHG1jh+hXtsyxCFHmpQq8Ou+f+WT6N2\n1v3A+ceaYKiM5ZrFqXtjEe97uZsfY1GobSvW9+9RvagynpKqJAHJ42rIlC7SZrLH\nF+UGU5q9ihmPOlF6JRHl6Fn3G9+VmDXBpnQVa7Esl5v+dn5WIE3uB4q5i7ySqRmB\nKJFO0p9RmXLAEnZbyHRotZ1DU8GvObkogqAOu+8h2HHdAq/c30h7OSF9g3bq4scW\nu9IaHSL2S0cVVkEGTTkclj8lLYOPpKKG+g7pRZy/JkDmPp7VAJ/Odm/fy7KFuIPy\n7Yu2Wmi1xxurGXVg81vnXHAn2RzYJPAqqiqmmHfWNtkCNajTLBmIuAvTdlhyVJmU\nmu44m42OEEjNKFAxskvTaXuRtA==\n-----END CERTIFICATE-----\n</cert>\n#tls-auth sample-keys/ta.key 1\nkey-direction 1\n<tls-auth>\n#\n# 2048 bit OpenVPN static key\n#\n-----BEGIN OpenVPN Static key V1-----\n21d94830510107f8753d3b6f3145e01d\ned37075115afcb0538ecdd8503ee9663\n7218c9ed38d908d594231d7d143c73da\n5055310f89d336da99c8b3dcb18909c7\n9dd44f540670ebc0f120beb7211e9683\n9cb542572c48bfa7ffaa9a22cb8304b7\n869b92f4442918e598745bb78ac8877f\n02b00a7cdef3f2446c130d39a7c45126\n9ef399fd6029cdfc80a7c604041312ab\n0a969bc906bdee6e6d707afdcbe8c7fb\n97beb66049c3d328340775025433ceba\n1e38008a826cf92443d903106199373b\ndadd9c2c735cf481e580db4e81b99f12\ne3f46b6159c687cd1b9e689f7712573c\n0f02735a45573dfb5cd55cf464942389\n2c7e91f439bdd7337a8ceebd302cfbfa\n-----END OpenVPN Static key V1-----\n</tls-auth>\ncipher AES-256-GCM\nping 1\ninactive 120 10000000\n"
  },
  {
    "path": "sample/sample-config-files/loopback-server",
    "content": "# Perform a TLS loopback test -- server side.\n#\n# This test performs a TLS negotiation once every 10 seconds,\n# and will terminate after 2 minutes.\n#\n# From the root directory of the OpenVPN distribution,\n# after openvpn has been built, run:\n#\n#  ./openvpn --config sample-config-files/loopback-client  (In one window) \n#  ./openvpn --config sample-config-files/loopback-server  (Simultaneously in another window) \n\nrport 16001\nlport 16000\nremote localhost\nlocal localhost\ndev null\nverb 3\nreneg-sec 10\ntls-server\ndh none\nca sample-keys/ca.crt\nkey sample-keys/server.key\ncert sample-keys/server.crt\ntls-auth sample-keys/ta.key 0\ncipher AES-256-GCM\nping 1\ninactive 120 10000000\n"
  },
  {
    "path": "sample/sample-config-files/openvpn-shutdown.sh",
    "content": "#!/bin/sh\n\n# stop all openvpn processes\n\nkillall -TERM openvpn\n"
  },
  {
    "path": "sample/sample-config-files/openvpn-startup.sh",
    "content": "#!/bin/sh\n\n# A sample OpenVPN startup script\n# for Linux.\n\n# openvpn config file directory\ndir=/etc/openvpn\n\n# load the firewall\n$dir/firewall.sh\n\n# load TUN/TAP kernel module\nmodprobe tun\n\n# enable IP forwarding\necho 1 > /proc/sys/net/ipv4/ip_forward\n\n# Invoke openvpn for each VPN tunnel\n# in daemon mode.  Alternatively,\n# you could remove \"--daemon\" from\n# the command line and add \"daemon\"\n# to the config file.\n#\n# Each tunnel should run on a separate\n# UDP port.  Use the \"port\" option\n# to control this.  Like all of\n# OpenVPN's options, you can\n# specify \"--port 8000\" on the command\n# line or \"port 8000\" in the config\n# file.\n\nopenvpn --cd $dir --daemon --config vpn1.conf\nopenvpn --cd $dir --daemon --config vpn2.conf\nopenvpn --cd $dir --daemon --config vpn2.conf\n"
  },
  {
    "path": "sample/sample-config-files/server.conf",
    "content": "#################################################\n# Sample OpenVPN 2.6 config file for            #\n# multi-client server.                          #\n#                                               #\n# This file is for the server side              #\n# of a many-clients <-> one-server              #\n# OpenVPN configuration.                        #\n#                                               #\n# OpenVPN also supports                         #\n# single-machine <-> single-machine             #\n# configurations (See the Examples page         #\n# on the web site for more info).               #\n#                                               #\n# This config should work on Windows            #\n# or Linux/BSD systems.  Remember on            #\n# Windows to quote pathnames and use            #\n# double backslashes, e.g.:                     #\n# \"C:\\\\Program Files\\\\OpenVPN\\\\config\\\\foo.key\" #\n#                                               #\n# Comments are preceded with '#' or ';'         #\n#################################################\n\n# Which local IP address should OpenVPN\n# listen on? (optional)\n;local a.b.c.d\n\n# Which TCP/UDP port should OpenVPN listen on?\n# If you want to run multiple OpenVPN instances\n# on the same machine, use a different port\n# number for each one.  You will need to\n# open up this port on your firewall.\nport 1194\n\n# TCP or UDP server?\n;proto tcp\nproto udp\n\n# \"dev tun\" will create a routed IP tunnel,\n# \"dev tap\" will create an ethernet tunnel.\n# Use \"dev tap0\" if you are ethernet bridging\n# and have precreated a tap0 virtual interface\n# and bridged it with your ethernet interface.\n# If you want to control access policies\n# over the VPN, you must create firewall\n# rules for the TUN/TAP interface.\n# On non-Windows systems, you can give\n# an explicit unit number, such as tun0.\n# On Windows, use \"dev-node\" for this.\n# On most systems, the VPN will not function\n# unless you partially or fully disable/open\n# the firewall for the TUN/TAP interface.\n;dev tap\ndev tun\n\n# Windows needs the TAP-Win32 adapter name\n# from the Network Connections panel if you\n# have more than one.\n# You may need to selectively disable the\n# Windows firewall for the TAP adapter.\n# Non-Windows systems usually don't need this.\n;dev-node MyTap\n\n# SSL/TLS root certificate (ca), certificate\n# (cert), and private key (key).  Each client\n# and the server must have their own cert and\n# key file.  The server and all clients will\n# use the same ca file.\n#\n# See the \"easy-rsa\" project at\n# https://github.com/OpenVPN/easy-rsa\n# for generating RSA certificates\n# and private keys.  Remember to use\n# a unique Common Name for the server\n# and each of the client certificates.\n#\n# Any X509 key management system can be used.\n# OpenVPN can also use a PKCS #12 formatted key file\n# (see \"pkcs12\" directive in man page).\n#\n# If you do not want to maintain a CA\n# and have a small number of clients\n# you can also use self-signed certificates\n# and use the peer-fingerprint option.\n# See openvpn-examples man page for a\n# configuration example.\nca ca.crt\ncert server.crt\nkey server.key  # This file should be kept secret\n\n# Allow to connect to really old OpenVPN versions\n# without AEAD support (OpenVPN 2.3.x or older)\n# This adds AES-256-CBC as fallback cipher and\n# keeps the modern ciphers as well.\n;data-ciphers AES-256-GCM:AES-128-GCM:?CHACHA20-POLY1305:AES-256-CBC\n\n# Network topology\n# Should be subnet (addressing via IP)\n# unless Windows clients v2.0.9 and lower have to\n# be supported (then net30, i.e. a /30 per client)\n# Defaults to net30 (not recommended)\ntopology subnet\n\n# Configure server mode and supply a VPN subnet\n# for OpenVPN to draw client addresses from.\n# The server will take 10.8.0.1 for itself,\n# the rest will be made available to clients.\n# Each client will be able to reach the server\n# on 10.8.0.1. Comment this line out if you are\n# ethernet bridging. See the man page for more info.\nserver 10.8.0.0 255.255.255.0\n\n# Maintain a record of client <-> virtual IP address\n# associations in this file.  If OpenVPN goes down or\n# is restarted, reconnecting clients can be assigned\n# the same virtual IP address from the pool that was\n# previously assigned.\nifconfig-pool-persist ipp.txt\n\n# Configure server mode for ethernet bridging.\n# You must first use your OS's bridging capability\n# to bridge the TAP interface with the ethernet\n# NIC interface.  Then you must manually set the\n# IP/netmask on the bridge interface, here we\n# assume 10.8.0.4/255.255.255.0.  Finally we\n# must set aside an IP range in this subnet\n# (start=10.8.0.50 end=10.8.0.100) to allocate\n# to connecting clients.  Leave this line commented\n# out unless you are ethernet bridging.\n;server-bridge 10.8.0.4 255.255.255.0 10.8.0.50 10.8.0.100\n\n# Configure server mode for ethernet bridging\n# using a DHCP-proxy, where clients talk\n# to the OpenVPN server-side DHCP server\n# to receive their IP address allocation\n# and DNS server addresses.  You must first use\n# your OS's bridging capability to bridge the TAP\n# interface with the ethernet NIC interface.\n# Note: this mode only works on clients (such as\n# Windows), where the client-side TAP adapter is\n# bound to a DHCP client.\n;server-bridge\n\n# Push routes to the client to allow it\n# to reach other private subnets behind\n# the server.  Remember that these\n# private subnets will also need\n# to know to route the OpenVPN client\n# address pool (10.8.0.0/255.255.255.0)\n# back to the OpenVPN server.\n;push \"route 192.168.10.0 255.255.255.0\"\n;push \"route 192.168.20.0 255.255.255.0\"\n\n# To assign specific IP addresses to specific\n# clients or if a connecting client has a private\n# subnet behind it that should also have VPN access,\n# use the subdirectory \"ccd\" for client-specific\n# configuration files (see man page for more info).\n\n# EXAMPLE: Suppose the client\n# having the certificate common name \"Thelonious\"\n# also has a small subnet behind his connecting\n# machine, such as 192.168.40.128/255.255.255.248.\n# First, uncomment out these lines:\n;client-config-dir ccd\n;route 192.168.40.128 255.255.255.248\n# Then create a file ccd/Thelonious with this line:\n#   iroute 192.168.40.128 255.255.255.248\n# This will allow Thelonious' private subnet to\n# access the VPN.  This example will only work\n# if you are routing, not bridging, i.e. you are\n# using \"dev tun\" and \"server\" directives.\n\n# EXAMPLE: Suppose you want to give\n# Thelonious a fixed VPN IP address of 10.9.0.1.\n# First uncomment out these lines:\n;client-config-dir ccd\n;route 10.9.0.0 255.255.255.252\n# Then add this line to ccd/Thelonious:\n#   ifconfig-push 10.9.0.1 10.9.0.2\n\n# Suppose that you want to enable different\n# firewall access policies for different groups\n# of clients.  There are two methods:\n# (1) Run multiple OpenVPN daemons, one for each\n#     group, and firewall the TUN/TAP interface\n#     for each group/daemon appropriately.\n# (2) (Advanced) Create a script to dynamically\n#     modify the firewall in response to access\n#     from different clients.  See man\n#     page for more info on learn-address script.\n;learn-address ./script\n\n# If enabled, this directive will configure\n# all clients to redirect their default\n# network gateway through the VPN, causing\n# all IP traffic such as web browsing and\n# DNS lookups to go through the VPN\n# (The OpenVPN server machine may need to NAT\n# or bridge the TUN/TAP interface to the internet\n# in order for this to work properly).\n;push \"redirect-gateway def1 bypass-dhcp\"\n\n# Certain Windows-specific network settings\n# can be pushed to clients, such as DNS\n# or WINS server addresses.  CAVEAT:\n# http://openvpn.net/faq.html#dhcpcaveats\n# The addresses below refer to the public\n# DNS servers provided by opendns.com.\n;push \"dhcp-option DNS 208.67.222.222\"\n;push \"dhcp-option DNS 208.67.220.220\"\n\n# Uncomment this directive to allow different\n# clients to be able to \"see\" each other.\n# By default, clients will only see the server.\n# To force clients to only see the server, you\n# will also need to appropriately firewall the\n# server's TUN/TAP interface.\n;client-to-client\n\n# Uncomment this directive if multiple clients\n# might connect with the same certificate/key\n# files or common names.  This is recommended\n# only for testing purposes.  For production use,\n# each client should have its own certificate/key\n# pair.\n#\n# IF YOU HAVE NOT GENERATED INDIVIDUAL\n# CERTIFICATE/KEY PAIRS FOR EACH CLIENT,\n# EACH HAVING ITS OWN UNIQUE \"COMMON NAME\",\n# UNCOMMENT THIS LINE.\n;duplicate-cn\n\n# The keepalive directive causes ping-like\n# messages to be sent back and forth over\n# the link so that each side knows when\n# the other side has gone down.\n# Ping every 10 seconds, assume that remote\n# peer is down if no ping received during\n# a 120 second time period.\nkeepalive 10 120\n\n# For extra security beyond that provided\n# by SSL/TLS, create an \"HMAC firewall\"\n# to help block DoS attacks and UDP port flooding.\n#\n# Generate with:\n#   openvpn --genkey tls-auth ta.key\n#\n# The server and each client must have\n# a copy of this key.\n# The second parameter should be '0'\n# on the server and '1' on the clients.\n;tls-auth ta.key 0 # This file is secret\n\n# The maximum number of concurrently connected\n# clients we want to allow.\n;max-clients 100\n\n# It's a good idea to reduce the OpenVPN\n# daemon's privileges after initialization.\n#\n# You can uncomment this on non-Windows\n# systems after creating a dedicated user.\n;user openvpn\n;group openvpn\n\n# The persist option will try to avoid\n# accessing certain resources on restart\n# that may no longer be accessible because\n# of the privilege downgrade.\npersist-tun\n\n# Output a short status file showing\n# current connections, truncated\n# and rewritten every minute.\nstatus openvpn-status.log\n\n# By default, log messages will go to the syslog (or\n# on Windows, if running as a service, they will go to\n# the \"\\Program Files\\OpenVPN\\log\" directory).\n# Use log or log-append to override this default.\n# \"log\" will truncate the log file on OpenVPN startup,\n# while \"log-append\" will append to it.  Use one\n# or the other (but not both).\n;log         openvpn.log\n;log-append  openvpn.log\n\n# Set the appropriate level of log\n# file verbosity.\n#\n# 0 is silent, except for fatal errors\n# 4 is reasonable for general usage\n# 5 and 6 can help to debug connection problems\n# 9 is extremely verbose\nverb 3\n\n# Silence repeating messages.  At most 20\n# sequential messages of the same message\n# category will be output to the log.\n;mute 20\n\n# Notify the client that when the server restarts so it\n# can automatically reconnect.\nexplicit-exit-notify 1\n"
  },
  {
    "path": "sample/sample-keys/README",
    "content": "Sample RSA and EC keys.\n\nRun ./gen-sample-keys.sh to generate fresh test keys.\n\nSee the examples section of the man page for usage examples.\n\nNOTE: THESE KEYS ARE FOR TESTING PURPOSES ONLY.\n      DON'T USE THEM FOR ANY REAL WORK BECAUSE\n      THEY ARE TOTALLY INSECURE!\n\nca.{crt,key}        -- sample CA key/cert\nserver.{crt,key}    -- sample server key/cert\nclient.{crt,key}    -- sample client key/cert\nclient-pass.key     -- sample client key with password-encrypted key\n                       password = \"password\"\nclient.p12          -- sample client pkcs12 bundle\n                       password = \"password\"\nclient-ec.{crt,key} -- sample elliptic curve client key/cert\nserver-ec.{crt,key} -- sample elliptic curve server key/cert\n"
  },
  {
    "path": "sample/sample-keys/ca.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIGPjCCBCagAwIBAgIUb1C400ZucjRZvAAz3XyuEusnRgYwDQYJKoZIhvcNAQEL\nBQAwZjELMAkGA1UEBhMCS0cxCzAJBgNVBAgMAk5BMRAwDgYDVQQHDAdCSVNIS0VL\nMRUwEwYDVQQKDAxPcGVuVlBOLVRFU1QxITAfBgkqhkiG9w0BCQEWEm1lQG15aG9z\ndC5teWRvbWFpbjAeFw0yMzExMDcxMjIzMzlaFw0zMzExMDQxMjIzMzlaMGYxCzAJ\nBgNVBAYTAktHMQswCQYDVQQIDAJOQTEQMA4GA1UEBwwHQklTSEtFSzEVMBMGA1UE\nCgwMT3BlblZQTi1URVNUMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21h\naW4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCI+p/ZLGUHCANTTFaK\nnw+J3wi+ef2EKJ5WHt5PWMuBeaDpeU4Ghuaow8HlRPjG9lDRHtn+WQgZz9nUejYH\n+wtmN2BHwJAM4OeUVoB95tBrxd/VDCrdIvypVKldHsU3VkEbvPAl1jq68WVk+DXM\nFZqTUoafDK+irOvL7Z5j2gA3FDzRUQs0L+jCvRTl4omFSjSQwoBCoVXxNEAg9jgy\nlNWUHx+JHDB8dk+gEmDai20ggBWeAeThUU9dVZvwjv4E7zMRMx1skCRdWcyALJQf\nfjc9q6gnB9X9nPxXdWb/lYKcivJBmBRHLeirnUFL2S2IYRc2H0ZbX1d+WzDJV37+\nDKYy9ehltyHFiaXmZThJ2Kg/mAD55U3NCWNBXmQ0CvzhUh6QIQiOJNQHmK0qxgnc\nPOJeE4X55dv1nAGD/0fGeHTcuShzUoipCKAd1CZdXK2Ge3gZRH2WUvlQGd5JARd4\n3zbd2wXZX0h0e1/BWQVeXx/Cg6u31B5lll7B3rWeoZHvfV9DSC7e3IEOhgzG5cyA\nh+wrtlCszjiMreHSSYCQh9tlyK+ACOJUFtZFGdseBsMxRgXWtHr+ypW2iJI4KsEU\n/MNXr1Bqg7FGxIw0Oyc2zyzjgD9aq4CKEy64MYB1ZYf41Rbc2Z+pMx1MW9orsPp7\nqSp6SmpTk0RTHpH0O2wNC9F26wIDAQABo4HjMIHgMB0GA1UdDgQWBBRzsbjWipVr\nEuB0fMVXVZiUW6x4XjCBowYDVR0jBIGbMIGYgBRzsbjWipVrEuB0fMVXVZiUW6x4\nXqFqpGgwZjELMAkGA1UEBhMCS0cxCzAJBgNVBAgMAk5BMRAwDgYDVQQHDAdCSVNI\nS0VLMRUwEwYDVQQKDAxPcGVuVlBOLVRFU1QxITAfBgkqhkiG9w0BCQEWEm1lQG15\naG9zdC5teWRvbWFpboIUb1C400ZucjRZvAAz3XyuEusnRgYwDAYDVR0TBAUwAwEB\n/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBABqhFuSPgqplHQtFnWwQ\nTKfrElQJ07gF0eaBBijQVSm2MswB2xnWF/S2NRjIpw7k5ZlmZsAbCVcGMwqJOkfJ\nyX3Z7gK+yNrZehzNSOCkv+H79ExsS9/HETSqZxMevIIH7O0t/pACv20f85unBzhc\nx+980RzufuHK17sG3Z+z+d6i9XDhaZvV/gm6bWTXft1ufRzI5R48xWVAfJd1X9Ln\nbZmqF9Ye1GHxka1Xna9nOCgAuYYoGxq2VkUSIjlRCMaLCHlsWEn0JbRnQXPfBts6\n/yQBywcEekKRutCugn5bn625kAJHWGxcb0xIXj+Rqnp2++p33lbE4J09zfIkh5hV\nRvCSzaE0Z3Kly9237CV+DyAqzrBJq5HHN/AT6+xFd2yGPMPKH8hKbf3jIprexNEp\noG1XC/dsPFkPLUyeD++kVjzsLiDmYAn2x3Dco6cWD7FfEljb1pHkAp5CctU9TjZH\n21xcAsPbfS0vrDmj8zG7eTU+BtleL4AfxEVsMBzrUB6jSdUMpJ/hRtni4RxOHLmU\n0DqtHIqrDrC5Gb2KunNUIYqPp+80LSD1/Edo5Vr+k5AiFYCzZFXSab+6e4hEsLEV\nnQNMmcPVWATQ2najGfNftmhwQx9hU4gJaCw/rfhEmwIif5BxgG5VPUzy97T+GmOZ\nInB0RDylv3Lq3Hs8mBF4nRt7\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "sample/sample-keys/ca.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCI+p/ZLGUHCANT\nTFaKnw+J3wi+ef2EKJ5WHt5PWMuBeaDpeU4Ghuaow8HlRPjG9lDRHtn+WQgZz9nU\nejYH+wtmN2BHwJAM4OeUVoB95tBrxd/VDCrdIvypVKldHsU3VkEbvPAl1jq68WVk\n+DXMFZqTUoafDK+irOvL7Z5j2gA3FDzRUQs0L+jCvRTl4omFSjSQwoBCoVXxNEAg\n9jgylNWUHx+JHDB8dk+gEmDai20ggBWeAeThUU9dVZvwjv4E7zMRMx1skCRdWcyA\nLJQffjc9q6gnB9X9nPxXdWb/lYKcivJBmBRHLeirnUFL2S2IYRc2H0ZbX1d+WzDJ\nV37+DKYy9ehltyHFiaXmZThJ2Kg/mAD55U3NCWNBXmQ0CvzhUh6QIQiOJNQHmK0q\nxgncPOJeE4X55dv1nAGD/0fGeHTcuShzUoipCKAd1CZdXK2Ge3gZRH2WUvlQGd5J\nARd43zbd2wXZX0h0e1/BWQVeXx/Cg6u31B5lll7B3rWeoZHvfV9DSC7e3IEOhgzG\n5cyAh+wrtlCszjiMreHSSYCQh9tlyK+ACOJUFtZFGdseBsMxRgXWtHr+ypW2iJI4\nKsEU/MNXr1Bqg7FGxIw0Oyc2zyzjgD9aq4CKEy64MYB1ZYf41Rbc2Z+pMx1MW9or\nsPp7qSp6SmpTk0RTHpH0O2wNC9F26wIDAQABAoIB/03LuNT2nmo+NwOYGuzjQUeM\neKd/vDIWWORoKm69wvHaQ/wFCr37Fc+ovMDD616N8j10d1ql5T4HCwfesdEnXljD\nk+RU68wT1OvdJ5Yj84w0mQ0c1TtXFhsVsChiL8htEtC04vK1RphRt0s8GQDkANI/\nSTXwz2Pe32sG4Q/iTcO3EkzBwVEfDQxkf4CyPNRVIZMVu+sOSoSfFB2/TXxfTrgA\niVsZGgS+i1+a2p0OuzXb2cl+mrv+8g2Czj0pSgDURU5QRwDGi+CuuorN7xx6i+Uw\nkhNH7X3SbnE9lAU2PF0KchMSUy6gp6YTiExPBXj80+fib4Wd+CdY2S1K9rNgOGG7\n4yOU9vbkgt4r+cXFu/NvG2GBMw5/Dqn7tFu+nLxC98/IrgFbsFPMwD/vS4IWYw+a\nCSy7Ed3FfPNlvE7Q2VoDOVpJoUAJZWrLFOisMSCSSq9Zfxc45Usz+hzg85nysywD\n5FS6LvGEdXJu0FTUHrBMBmcbYpdyVrY7qeQ2k4imC9+AKt+MuswJ+ofBLkxhgWlN\nNAaGOFdDKszDjYYgLEszL6M5Uwk+iBWfhPB0kuAqMCfljWwMVz6Apg6kjF4vk6rF\nObvlXAcchk7SuxHJFgRLGWw5WzPXxmK7StrDLpWiiWqnqf7LKpnclKlan8ML5vo4\nswwfDO6Q9Jw07fPoJfUCggEBALvAanX7Z8Dbc86uYh08myZPn4GHrz7qc1ouvV5T\nQsQ4iWEvVomPLtw57iSWX5x2hot7XyEZETOj+RHxmRsNHIewWk0+iAKAe9fw2FJD\n8DDki585G6HOsw/rN00xWymT47cLyESlYIq3eJYuZld8tCmg7Nfoe83CiRZXChlQ\n1TZWQlWxPR/Ykv6fitZrLfByl8JrVUNwr3rBMeegXF5d6tZ8FB89BiyOkdDrO8fz\nZIACG60+6QN9pghcCz8tDJKqYyhcd/Z4LNmBqMeCyX1LJ8Ig0paXF1B/iV9Cxsti\naV/m+nLea9HGy3RlFe5NgRqDaqctv/Aq4NkmGgvB5Bbx678CggEBALrFdzVKeOjl\nvlkA5f95eVWmpr0DK5/r2ZM+i8zTCBhzxlFDL3pC2hmVk41bE4O9RNrzCwgqO8Aa\nGtwA7iqT5B1dmYhypt62iK1ZZce+l93JLLInTP3UCCFVzwC/akrcRHQdyKh9MyP7\ntAgdTaM49xlaakiems0KxpR2RQ2dzQMPUDxqiD20bcIErIVk1+1mk2l6MiqTAHHx\nfK/WtULQGSAPwHJmhrGKKwUPJIChcDGJxMtrnpD5tcZpjF3W6+xZbyqcgYMwbpOn\ncALvKzfA0Cg0/oAOdcrVJU+iQyXUDIN57ezwAU/oyGVOofIVQn4xVBaH3F/JtAAG\nTM+WygbP79UCggEBAKsWQ+0PExSi5XzJW47Y02it1ePrCL6EVmkvflCd/pFgE5AD\n2w+u8jysbV3ZyXaCa0hfO+ilNw+ftC+twJ7t67mZ8i/Bc58UBcZZKkaMsitbl/+X\nwp5IBNPUu6gT+caBhVgf3HbxXHALkE8KKSg/8sycYDa/G1H8m39IAWPgTOoe4IPF\n5rVGXWy5ZYLOWCZrxe7cb+3smXt64Ub40jML0htxJcTxjta7dBS0xt0F5ebgBOhy\nE1OjA9FKTtVa78IWkhUNbiOijvwFMw/bFlCeU7SKxFuFgzFPhpbP+ucK3osNp9tU\n41tdk7iVBM8KwUKvzlhZUDZCXHKETee432gpO3ECggEAJGJZcbE7UquG5FHPfHBO\nmcfoTYPzmKjabtvNYi5uMk1DggsjkZ66XCeOYggvCgfyBPE54fJQR4EOYHNx8itz\nUeEtCq7DITnP8G0s7beMYDFTmrUbQ4tttgjAVbX0X/b/AtvWfjQ9pTHghYAn4rcz\nM+YwNEtpfq4ttzg/BYMLMCBokgxy1Ap1I0nDzgyyH9ZOu0qJwU9307qmfp7GGujt\nLBjFdcPRU37GGKs1gjVw5MWg57vkXPu4VJm1NYar2RQnGtb4R/VEZVFF+dxbv/W4\n10xTk+C9Q7E4HoZOrGzdrzMujWzH5KhFea7Sz5UiqfC0H9uBq8tgXGzdw8btPlx9\nrQKCAQEApnc3/WwS4fsbbZKjycvZu102sZfDRx1lYPpylUyEKm9iSq97miuC5/bO\nJ3HkK9e+uye3klB3lYnNHKjeFEDoq8DJJ33M/pyY9BuowOZtLJcGu5Krq42FFpaZ\nHIEcZWMwDPaNLunZAXkGpqdw7GPNivSrzy20iJgJLEVXwr0krT5UMVRKo5Xsq/P1\nrxJ78psVCsbOHvVgUfN6fHPf1I+EyLB+Dipr3qPNU1Aty0OCdI+2BeT90ovZiKvu\ndBnuWQOR7HlBimgHsF4Gb9Akjoix6SJKbm/E9GvLfUYbiIkARc99QC3G6h17PGiF\nC2j6oHefg+K1iyTA4LCTAkHWax2ggg==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "sample/sample-keys/client-ec.crt",
    "content": "Certificate:\n    Data:\n        Version: 3 (0x2)\n        Serial Number: 7 (0x7)\n        Signature Algorithm: sha256WithRSAEncryption\n        Issuer: C=KG, ST=NA, L=BISHKEK, O=OpenVPN-TEST/emailAddress=me@myhost.mydomain\n        Validity\n            Not Before: Nov  7 12:23:40 2023 GMT\n            Not After : Nov  4 12:23:40 2033 GMT\n        Subject: C=KG, ST=NA, O=OpenVPN-TEST, CN=Test-Client-EC/emailAddress=me@myhost.mydomain\n        Subject Public Key Info:\n            Public Key Algorithm: id-ecPublicKey\n                Public-Key: (256 bit)\n                pub:\n                    04:25:bd:3e:da:c5:cd:35:c0:44:d5:82:11:77:7a:\n                    24:12:1e:40:53:7a:ff:0d:0c:67:05:94:ce:5d:44:\n                    26:51:9b:0c:57:b1:38:30:9d:bd:13:03:59:12:0e:\n                    c8:35:5c:ca:b6:d1:81:41:9d:ac:9f:ec:2b:58:07:\n                    29:6d:d3:5f:5c\n                ASN1 OID: secp256k1\n        X509v3 extensions:\n            X509v3 Basic Constraints:\n                CA:FALSE\n            X509v3 Subject Key Identifier:\n                D4:76:DB:EC:D0:11:63:0E:FE:BA:4E:10:76:22:07:D7:99:02:DE:F6\n            X509v3 Authority Key Identifier:\n                keyid:73:B1:B8:D6:8A:95:6B:12:E0:74:7C:C5:57:55:98:94:5B:AC:78:5E\n                DirName:/C=KG/ST=NA/L=BISHKEK/O=OpenVPN-TEST/emailAddress=me@myhost.mydomain\n                serial:6F:50:B8:D3:46:6E:72:34:59:BC:00:33:DD:7C:AE:12:EB:27:46:06\n    Signature Algorithm: sha256WithRSAEncryption\n    Signature Value:\n        72:fd:18:d4:c2:0f:ba:6f:94:f2:f9:26:8b:93:fb:d5:99:df:\n        f7:aa:e6:27:f2:89:86:ff:6d:0a:24:ea:ae:d4:68:7b:08:38:\n        8a:7a:f9:a2:4d:e5:fe:2e:e1:bb:09:8c:2d:df:85:7b:01:dd:\n        58:4b:15:2a:db:49:10:ab:f1:78:49:fb:94:b3:31:e3:09:e0:\n        63:3c:d0:f2:34:18:de:37:0a:2d:d3:02:d5:ae:05:49:57:e7:\n        47:d0:70:3f:f1:35:28:82:79:00:b3:c8:45:00:86:77:d7:68:\n        63:d2:3d:8b:ef:a9:f8:99:97:fe:d0:0a:98:cb:7a:7b:73:28:\n        77:f4:bb:cf:1c:63:7e:64:60:87:f7:51:68:e9:7a:90:70:d8:\n        a0:e2:c6:88:70:62:2c:49:ac:ba:8c:2e:c5:d7:c9:42:8b:44:\n        cc:ae:f3:40:79:1c:99:09:2c:4c:24:89:55:41:ce:c6:52:a9:\n        a3:b7:4c:e6:75:63:4f:b6:70:84:1b:3e:56:f5:42:5f:b1:50:\n        46:eb:33:41:28:f8:30:f6:f9:f9:c0:5d:9b:a4:af:8e:03:c8:\n        3e:88:66:04:2e:5b:ec:50:36:a8:d1:9f:8e:e0:59:40:bb:f8:\n        ff:45:7d:40:2e:6d:f0:e8:84:5b:db:7e:0d:88:b3:a2:f6:34:\n        5b:b9:a1:1d:a0:fa:85:78:3b:9b:b3:0b:6c:f1:03:06:9c:f1:\n        e3:ba:64:a3:5c:d8:c8:d5:73:4a:3f:4d:83:aa:e8:c4:ce:dd:\n        92:23:b2:c8:ab:e5:39:93:d9:d7:ca:70:c2:ff:8f:71:40:f6:\n        c4:89:4a:72:0b:2a:7a:20:15:5b:a4:e9:75:a0:df:93:2b:7d:\n        1a:54:39:2c:80:4f:21:32:5f:9f:d8:96:08:2f:dc:e2:45:1f:\n        96:e9:31:84:90:2e:1d:07:92:56:a8:22:49:25:1b:bf:47:d5:\n        fa:34:e9:cc:7c:b2:18:ca:5e:d6:76:5e:b6:19:72:c0:10:d6:\n        c2:c6:f1:03:d4:0e:62:28:d8:56:e1:08:3a:f4:54:8f:7b:0d:\n        a5:62:53:8a:72:7b:2f:fa:80:8a:3a:54:4d:11:5c:58:7e:fc:\n        15:30:9b:fe:ef:35:a1:00:c0:15:0f:47:14:af:09:9f:1e:dd:\n        7a:ed:ea:2b:c8:a1:51:26:a3:d1:25:8c:31:1b:41:30:27:ca:\n        e8:3f:00:2b:83:8f:b4:f8:11:30:71:b8:4c:d8:af:48:88:aa:\n        e5:96:3e:f8:01:a9:17:b6:f2:09:27:d0:e9:b3:b3:89:b2:0f:\n        f7:c5:78:b3:b2:e1:26:a2:78:2b:4c:9d:99:57:4f:7e:fa:fe:\n        9b:ae:6f:c4:6a:b1:7c:d0\n-----BEGIN CERTIFICATE-----\nMIIEVDCCAjygAwIBAgIBBzANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJLRzEL\nMAkGA1UECAwCTkExEDAOBgNVBAcMB0JJU0hLRUsxFTATBgNVBAoMDE9wZW5WUE4t\nVEVTVDEhMB8GCSqGSIb3DQEJARYSbWVAbXlob3N0Lm15ZG9tYWluMB4XDTIzMTEw\nNzEyMjM0MFoXDTMzMTEwNDEyMjM0MFowbTELMAkGA1UEBhMCS0cxCzAJBgNVBAgM\nAk5BMRUwEwYDVQQKDAxPcGVuVlBOLVRFU1QxFzAVBgNVBAMMDlRlc3QtQ2xpZW50\nLUVDMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW4wVjAQBgcqhkjO\nPQIBBgUrgQQACgNCAAQlvT7axc01wETVghF3eiQSHkBTev8NDGcFlM5dRCZRmwxX\nsTgwnb0TA1kSDsg1XMq20YFBnayf7CtYBylt019co4HTMIHQMAkGA1UdEwQCMAAw\nHQYDVR0OBBYEFNR22+zQEWMO/rpOEHYiB9eZAt72MIGjBgNVHSMEgZswgZiAFHOx\nuNaKlWsS4HR8xVdVmJRbrHheoWqkaDBmMQswCQYDVQQGEwJLRzELMAkGA1UECAwC\nTkExEDAOBgNVBAcMB0JJU0hLRUsxFTATBgNVBAoMDE9wZW5WUE4tVEVTVDEhMB8G\nCSqGSIb3DQEJARYSbWVAbXlob3N0Lm15ZG9tYWlughRvULjTRm5yNFm8ADPdfK4S\n6ydGBjANBgkqhkiG9w0BAQsFAAOCAgEAcv0Y1MIPum+U8vkmi5P71Znf96rmJ/KJ\nhv9tCiTqrtRoewg4inr5ok3l/i7huwmMLd+FewHdWEsVKttJEKvxeEn7lLMx4wng\nYzzQ8jQY3jcKLdMC1a4FSVfnR9BwP/E1KIJ5ALPIRQCGd9doY9I9i++p+JmX/tAK\nmMt6e3Mod/S7zxxjfmRgh/dRaOl6kHDYoOLGiHBiLEmsuowuxdfJQotEzK7zQHkc\nmQksTCSJVUHOxlKpo7dM5nVjT7ZwhBs+VvVCX7FQRuszQSj4MPb5+cBdm6SvjgPI\nPohmBC5b7FA2qNGfjuBZQLv4/0V9QC5t8OiEW9t+DYizovY0W7mhHaD6hXg7m7ML\nbPEDBpzx47pko1zYyNVzSj9Ng6roxM7dkiOyyKvlOZPZ18pwwv+PcUD2xIlKcgsq\neiAVW6TpdaDfkyt9GlQ5LIBPITJfn9iWCC/c4kUflukxhJAuHQeSVqgiSSUbv0fV\n+jTpzHyyGMpe1nZethlywBDWwsbxA9QOYijYVuEIOvRUj3sNpWJTinJ7L/qAijpU\nTRFcWH78FTCb/u81oQDAFQ9HFK8Jnx7deu3qK8ihUSaj0SWMMRtBMCfK6D8AK4OP\ntPgRMHG4TNivSIiq5ZY++AGpF7byCSfQ6bOzibIP98V4s7LhJqJ4K0ydmVdPfvr+\nm65vxGqxfNA=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "sample/sample-keys/client-ec.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQggBG28jKEqUG3n/wcnvcr\nh2VP5dXkRChxqLw3ydT+HpGhRANCAAQlvT7axc01wETVghF3eiQSHkBTev8NDGcF\nlM5dRCZRmwxXsTgwnb0TA1kSDsg1XMq20YFBnayf7CtYBylt019c\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "sample/sample-keys/client-pass.key",
    "content": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIVDt6h9I/tNsCAggA\nMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDK/DTdm5la+nBeb36XV2oxBIIE\n0Elt0ly1Cwc1o6EHRs3rFT3EYqm9DMVrcQgquI0qdbbUMtAsDmMFmW9TI7wedCDR\nEy3olanxK2dwz9qupSmtH25j4dFtPVxfF0moND6I4cQTmzBzTOjkK4LzgMucWfFL\n+J/GQxJsh0npaEu2t7HSpbKDR4uWcRiPkjxc7gFRPJH8NF51ySnF7htUODh/lmsM\nmRbdD5asKzIvrOJSVWBs7RLtj8GRCHttOLMq6ib3O0/8WvBDPEVXfPJMH8JRNxJc\nwoz6CSOPoI7yd9tKnRf0YGOuPiFWc1J1LTqgvWxVuwaUGrlwRZQ3nMnKK9jfXIGr\nFmhkHYFqWX2tpYy1nI9i7qYqG0MdWTmf/Gng1/YA5jTDW3dpcCnm5bd2eyzgw0qG\nPFnjjdVlJnEKZe5phTzrffzKWW8oOBDRww63RtgnNykipPK2V+Wq3RHQ3Oach8ZB\n0RqyLCG1wFLN9qA3TmvmPDDLsksLj9LiCstqo6FyHrvy1NFsCVlVqeVcOay1VCT9\nApFHa5SRaW5PxTSUKfses1eIjB48Z+yplJ+6sIkv4jrTcXyjrJSmZA8GU1jVvO08\nO2W4PGLX3C4B6iIel2eZMyG2EHM24kIAH4Dqx+GDZBhSuRBwhTN7+c9nX3fmVs3t\ncTe1uPOYu73W9zHLOSIRkO8WKcyoTzf5FQqfVhVRLmb1Z0pA+qVQps6g7DyL1/da\nzwHYgdAk0wSK20JYlXOz+7lYUsg/o4sFKTYseHVQIhXyEfUIE5gBxTEltCc+FBlI\nq0wLW5axVFJZx1uaEV0/mAOLSkL8QEKd5VOlV+mT7sDk38AdyoBbk2rmmn4SeYB5\ntmAzNC1d8aTAANo51bvt9BL3gzzvAduwuzl/3kYGsd7ASnrYZYDMwxtObR3Ltj12\nJq+Uv9lknmsbuhNWY/rXE0eQT2sT7PIW4Y3HqxzVlA3TeWc6ug7GLbabQMfeFPct\nOouOgj74jIvqBRYzLvyAdLKBuDadSVvCpxJddgS9mc3Ne53YPKtT8tPSuPzDVLRp\nrMQyHKh+C9HCEozDGAjzLbr/icE1PfmxDfKbl99C5bRG2WlSL3VNxcuRr7o09LRK\nY2k/zE0WzQtgiNaV9MOykcf3NBgRhIYwpH+O1oT2kxlorAWJbh3FyFZUxZlPr+we\ndZSBXtrZ/6aevm76f/qsHvjqC3MfHbQ5544Z5lEvPGke2w7du7Vcu7141Oghzl0a\nqw1gCok/CKy4iWoTS8sfnaKB5eXhk9KFHN/ALHztDQlq2qQ6O2KEIndHzd3IAspB\nNgEFW+UmSankwA5QnDCoyqgvnybaCJwRcsk189PJYOUQMKrvwzdYWQJIkA/XZDGq\n3TF9+bm7hJifD4nOMI0RYU5kROPLR4nKUTkRVOaMEdV8jTCWzjPaffiYKk8IDVhy\nzVnKpuuiPBU6mZKIlBwMAEwUdFSUZ8huRCoa8UGqyukJmYR5JSxJVwtqwtCqHsXd\n2nujp0MvGdJy7V/9TIocKCbJOgubuOYt3F+tp78fUYY0P0TAVIa94Be/P5B+tzKN\n/EjT+mv6RP6YnFSKSGC8CKTolPa2rKJBH+UpaHdFdbKifmY+snIMe2wzYlI62gFj\nuJc7ZHyi4MMbzdWSLblOP+KUhn0qKBJAS12cgOVWP5bb\n-----END ENCRYPTED PRIVATE KEY-----\n"
  },
  {
    "path": "sample/sample-keys/client.crt",
    "content": "Certificate:\n    Data:\n        Version: 3 (0x2)\n        Serial Number: 2 (0x2)\n        Signature Algorithm: sha256WithRSAEncryption\n        Issuer: C=KG, ST=NA, L=BISHKEK, O=OpenVPN-TEST/emailAddress=me@myhost.mydomain\n        Validity\n            Not Before: Nov  7 12:23:39 2023 GMT\n            Not After : Nov  4 12:23:39 2033 GMT\n        Subject: C=KG, ST=NA, O=OpenVPN-TEST, CN=Test-Client/emailAddress=me@myhost.mydomain\n        Subject Public Key Info:\n            Public Key Algorithm: rsaEncryption\n                Public-Key: (2048 bit)\n                Modulus:\n                    00:dd:ae:b2:0a:40:e6:cf:e1:c1:a2:a1:d2:83:2c:\n                    31:98:e2:1b:b2:85:40:67:00:fa:ab:bf:cf:9d:14:\n                    d1:c6:b7:63:83:aa:5f:9c:14:a0:d2:4d:04:3a:4a:\n                    92:e6:92:4b:a4:86:40:26:42:bd:83:b7:fc:49:b7:\n                    56:d2:ca:e7:70:85:16:8f:8b:3b:da:47:9e:8b:6a:\n                    4a:c8:e6:a8:de:ae:6b:2d:52:22:0c:9d:cf:67:bb:\n                    7d:1e:66:05:53:8b:7c:ae:ad:d1:a6:52:5f:3d:9e:\n                    6b:28:18:57:d9:a1:42:2d:c0:b0:4d:da:4a:f0:29:\n                    aa:02:34:86:02:aa:af:67:09:06:ed:45:21:8e:8c:\n                    f8:75:56:c7:ea:e8:4f:88:8e:1c:8b:5b:68:2d:b1:\n                    66:a5:e4:c6:36:e2:70:8d:fe:ef:10:f0:52:f8:53:\n                    96:bf:7e:51:70:f2:3f:ae:58:87:38:d1:a3:6b:f6:\n                    38:51:9b:16:ce:67:35:8e:6d:76:63:4d:6b:9b:7d:\n                    77:da:55:6f:20:b1:66:4f:9a:2d:a4:73:3a:52:21:\n                    86:3c:f3:a6:5a:67:e5:7d:10:f5:36:56:21:7e:03:\n                    22:bd:98:e9:18:b4:3f:b3:b5:0d:ed:d5:ef:54:96:\n                    6f:9d:46:6c:6f:0e:ba:4b:1a:f7:cc:d4:e5:24:ba:\n                    d2:a5\n                Exponent: 65537 (0x10001)\n        X509v3 extensions:\n            X509v3 Basic Constraints: \n                CA:FALSE\n            X509v3 Subject Key Identifier: \n                59:33:B9:2E:63:D1:17:A8:9F:BD:D8:CE:94:21:C5:41:C7:31:62:5D\n            X509v3 Authority Key Identifier: \n                keyid:73:B1:B8:D6:8A:95:6B:12:E0:74:7C:C5:57:55:98:94:5B:AC:78:5E\n                DirName:/C=KG/ST=NA/L=BISHKEK/O=OpenVPN-TEST/emailAddress=me@myhost.mydomain\n                serial:6F:50:B8:D3:46:6E:72:34:59:BC:00:33:DD:7C:AE:12:EB:27:46:06\n    Signature Algorithm: sha256WithRSAEncryption\n    Signature Value:\n        2a:9e:02:65:f4:3c:c0:37:88:f0:21:f9:fd:2e:7c:f4:8b:bb:\n        67:7d:f7:48:0c:98:f7:a1:46:4e:33:af:68:77:f4:53:03:09:\n        fd:4e:32:cb:0f:2c:f1:16:37:35:65:aa:68:79:16:a9:32:03:\n        d7:89:10:ef:ba:fd:e1:26:2c:60:7c:3b:42:60:68:47:cf:61:\n        88:00:77:e7:71:76:49:78:35:52:45:a4:31:7e:2b:e1:0a:c8:\n        ed:e1:a7:28:2f:23:a3:ce:ce:b5:99:6b:54:4d:df:d2:64:0a:\n        b7:c5:25:1e:d4:f7:a1:fd:4f:f3:12:d3:26:5f:3b:b2:93:93:\n        d1:8b:4b:4e:dc:d0:15:63:d1:77:36:75:34:76:37:59:ff:a0:\n        81:01:ec:b6:42:2f:bd:85:5d:d0:ef:ff:90:61:d6:91:b0:f5:\n        e6:94:66:7e:4c:20:06:c4:2e:0c:9b:9f:7f:89:f0:3e:8f:e5:\n        06:6c:81:75:a2:0b:c5:ac:44:f1:32:cc:57:90:a0:19:47:8c:\n        25:7a:d5:f1:61:1f:19:bf:4c:31:da:44:c1:30:91:e8:b5:cc:\n        e4:7e:20:55:0a:b9:dc:f3:5e:f5:7c:d1:0b:ee:71:c6:d6:38:\n        7e:85:7b:6c:cb:10:85:1e:6a:50:ab:c3:ae:f9:ff:96:4f:a3:\n        76:d6:fd:c0:f9:c7:9a:60:a8:8c:e5:9a:c5:a9:7b:63:11:ef:\n        7b:b9:9b:1f:63:51:a8:6d:2b:d6:f7:ef:51:bd:a8:32:9e:92:\n        aa:24:01:c9:e3:6a:c8:94:2e:d2:66:b2:c7:17:e5:06:53:9a:\n        bd:8a:19:8f:3a:51:7a:25:11:e5:e8:59:f7:1b:df:95:98:35:\n        c1:a6:74:15:6b:b1:2c:97:9b:fe:76:7e:56:20:4d:ee:07:8a:\n        b9:8b:bc:92:a9:19:81:28:91:4e:d2:9f:51:99:72:c0:12:76:\n        5b:c8:74:68:b5:9d:43:53:c1:af:39:b9:28:82:a0:0e:bb:ef:\n        21:d8:71:dd:02:af:dc:df:48:7b:39:21:7d:83:76:ea:e2:c7:\n        16:bb:d2:1a:1d:22:f6:4b:47:15:56:41:06:4d:39:1c:96:3f:\n        25:2d:83:8f:a4:a2:86:fa:0e:e9:45:9c:bf:26:40:e6:3e:9e:\n        d5:00:9f:ce:76:6f:df:cb:b2:85:b8:83:f2:ed:8b:b6:5a:68:\n        b5:c7:1b:ab:19:75:60:f3:5b:e7:5c:70:27:d9:1c:d8:24:f0:\n        2a:aa:2a:a6:98:77:d6:36:d9:02:35:a8:d3:2c:19:88:b8:0b:\n        d3:76:58:72:54:99:94:9a:ee:38:9b:8d:8e:10:48:cd:28:50:\n        31:b2:4b:d3:69:7b:91:b4\n-----BEGIN CERTIFICATE-----\nMIIFHzCCAwegAwIBAgIBAjANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJLRzEL\nMAkGA1UECAwCTkExEDAOBgNVBAcMB0JJU0hLRUsxFTATBgNVBAoMDE9wZW5WUE4t\nVEVTVDEhMB8GCSqGSIb3DQEJARYSbWVAbXlob3N0Lm15ZG9tYWluMB4XDTIzMTEw\nNzEyMjMzOVoXDTMzMTEwNDEyMjMzOVowajELMAkGA1UEBhMCS0cxCzAJBgNVBAgM\nAk5BMRUwEwYDVQQKDAxPcGVuVlBOLVRFU1QxFDASBgNVBAMMC1Rlc3QtQ2xpZW50\nMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW4wggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDdrrIKQObP4cGiodKDLDGY4huyhUBnAPqrv8+d\nFNHGt2ODql+cFKDSTQQ6SpLmkkukhkAmQr2Dt/xJt1bSyudwhRaPizvaR56LakrI\n5qjermstUiIMnc9nu30eZgVTi3yurdGmUl89nmsoGFfZoUItwLBN2krwKaoCNIYC\nqq9nCQbtRSGOjPh1Vsfq6E+IjhyLW2gtsWal5MY24nCN/u8Q8FL4U5a/flFw8j+u\nWIc40aNr9jhRmxbOZzWObXZjTWubfXfaVW8gsWZPmi2kczpSIYY886ZaZ+V9EPU2\nViF+AyK9mOkYtD+ztQ3t1e9Ulm+dRmxvDrpLGvfM1OUkutKlAgMBAAGjgdMwgdAw\nCQYDVR0TBAIwADAdBgNVHQ4EFgQUWTO5LmPRF6ifvdjOlCHFQccxYl0wgaMGA1Ud\nIwSBmzCBmIAUc7G41oqVaxLgdHzFV1WYlFuseF6haqRoMGYxCzAJBgNVBAYTAktH\nMQswCQYDVQQIDAJOQTEQMA4GA1UEBwwHQklTSEtFSzEVMBMGA1UECgwMT3BlblZQ\nTi1URVNUMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW6CFG9QuNNG\nbnI0WbwAM918rhLrJ0YGMA0GCSqGSIb3DQEBCwUAA4ICAQAqngJl9DzAN4jwIfn9\nLnz0i7tnffdIDJj3oUZOM69od/RTAwn9TjLLDyzxFjc1ZapoeRapMgPXiRDvuv3h\nJixgfDtCYGhHz2GIAHfncXZJeDVSRaQxfivhCsjt4acoLyOjzs61mWtUTd/SZAq3\nxSUe1Peh/U/zEtMmXzuyk5PRi0tO3NAVY9F3NnU0djdZ/6CBAey2Qi+9hV3Q7/+Q\nYdaRsPXmlGZ+TCAGxC4Mm59/ifA+j+UGbIF1ogvFrETxMsxXkKAZR4wletXxYR8Z\nv0wx2kTBMJHotczkfiBVCrnc8171fNEL7nHG1jh+hXtsyxCFHmpQq8Ou+f+WT6N2\n1v3A+ceaYKiM5ZrFqXtjEe97uZsfY1GobSvW9+9RvagynpKqJAHJ42rIlC7SZrLH\nF+UGU5q9ihmPOlF6JRHl6Fn3G9+VmDXBpnQVa7Esl5v+dn5WIE3uB4q5i7ySqRmB\nKJFO0p9RmXLAEnZbyHRotZ1DU8GvObkogqAOu+8h2HHdAq/c30h7OSF9g3bq4scW\nu9IaHSL2S0cVVkEGTTkclj8lLYOPpKKG+g7pRZy/JkDmPp7VAJ/Odm/fy7KFuIPy\n7Yu2Wmi1xxurGXVg81vnXHAn2RzYJPAqqiqmmHfWNtkCNajTLBmIuAvTdlhyVJmU\nmu44m42OEEjNKFAxskvTaXuRtA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "sample/sample-keys/client.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDdrrIKQObP4cGi\nodKDLDGY4huyhUBnAPqrv8+dFNHGt2ODql+cFKDSTQQ6SpLmkkukhkAmQr2Dt/xJ\nt1bSyudwhRaPizvaR56LakrI5qjermstUiIMnc9nu30eZgVTi3yurdGmUl89nmso\nGFfZoUItwLBN2krwKaoCNIYCqq9nCQbtRSGOjPh1Vsfq6E+IjhyLW2gtsWal5MY2\n4nCN/u8Q8FL4U5a/flFw8j+uWIc40aNr9jhRmxbOZzWObXZjTWubfXfaVW8gsWZP\nmi2kczpSIYY886ZaZ+V9EPU2ViF+AyK9mOkYtD+ztQ3t1e9Ulm+dRmxvDrpLGvfM\n1OUkutKlAgMBAAECggEANwi9ron6QzWaqtNdva7lCT1o/uLR4EB/+s99rVOT2K+C\nhxdu8QK2Aj+YgxgsbA15tfiWSGldPywX9/0KEv7IgkioFy7Lxx7sn1PeCQ4qck3+\n0ZuIVHWBHhGPuFI/lEQWyg7g81eTyWpg0+1nMeI02cLyggFlhUXyrOV5N4REU2GW\nC0KBQFyVQJPrFszomK8qsHOu/gaGC1vOwgIID3cQ3iLKXkoHNmHO4hgbeSy+SfDP\nQ5C0xxKQa2RUz0nLbByuGtLYOsJmbjUMWjFXyjmwBsPCcvRmFRdnxFvlnzwGEH4M\nZKsw+49p1iJFyuCv7KJ/ILLJmoEuryjrSmdj3esIqQKBgQDwC24VBQLNmlug8rkG\nYWaRePsWRJylDlWIeHnfmGe27p7ytxOvGe6hnPu6nfg8nXHtruZCIhGya6qbuVmL\nvGrg94ia4MSpDVUgGiElXXQ/Pl7O9/lnSlIlxcBAgd8uggxIAzCeYI6c3r7AQcmY\njARMwYNCxJjz5nLctMe2MCs4LwKBgQDsatDXb3xr6jmflCUZa8Kx8SOgBWEZTEGz\nKEoCQWnF2fHUCy4Bwm8Imnws3iX0198TyxkVD2rP8oGwFj2SAVtI2L8Y/g5A05TA\nknfmVECvGp/MN266ZdCA8G/MKbk727TxyJs+4AseAi5p6cBULqZHsJaZE74qlcEl\n5gFQu35ZawKBgBBgRz9J2zoZmLyvMm48ANpVzZNkVOdxxeYMigv2AsVZHCDk2oPs\nmfoOkqHVmxTPjPExKGZEmr54V+hNyc0dqpD0ci5WvTPnQ/JvtektqfuSjrdB9ZLV\nYCtRhV8hPQ+YMaxMA2oankAXdh35nv44NybhYMoSTXj+NMHX13QXbytjAoGAdVKw\n3yixWzB6dinjm1Dx5rJfVos024QPWqRUzfe+UPROYUdHBpKB3YgktXNs7KuwRbdV\ndDEZdabIGyV+WpWXwnflpbZ2Rk95k3NcUw5ep0cUJBkiNxhNt58aK/xMs1rd2dsO\nx84RVkwI0oCw9FXOKOeGZOL6TVHR70fMQU86bY8CgYEAqg/1AD9lXzbR57zaR/br\nAIn0WWU2mnU7Dc4uhmQd9+JExqrplKKHrUp8eQEOW8nij6MbPYlpgkMdatvDOJqP\nWrYtwZsKXGhnalvbS3ye20HqpjYpBR7co3Q9KMaaDNoQe9HtjbT80GXpQEbJN2Iu\nADo3hPoX0yENIbKFccMuptM=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "sample/sample-keys/ffdhe2048.pem",
    "content": "-----BEGIN DH PARAMETERS-----\nMIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a\n87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7\nYdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi\n7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD\nssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==\n-----END DH PARAMETERS-----\n"
  },
  {
    "path": "sample/sample-keys/gen-sample-keys.sh",
    "content": "#!/bin/sh\n#\n# Run this script to set up a test CA, and test key-certificate pair for a\n# server, and various clients.\n#\n# Copyright (C) 2014-2026 Steffan Karger <steffan@karger.me>\nset -eu\n\ncommand -v openssl >/dev/null 2>&1 || { echo >&2 \"Unable to find openssl. Please make sure openssl is installed and in your path.\"; exit 1; }\n\nif [ ! -f openssl.cnf ]\nthen\n    echo \"Please run this script from the sample directory\"\n    exit 1\nfi\n\n# Generate static key for tls-auth (or static key mode)\ntop_builddir=\"${top_builddir:-$(dirname ${0})/../..}\"\n${top_builddir}/src/openvpn/openvpn --genkey tls-auth ta.key\n\n# Create required directories and files\nmkdir -p sample-ca\nrm -f sample-ca/index.txt\ntouch sample-ca/index.txt\necho \"01\" > sample-ca/serial\n\n# Generate CA key and cert\nopenssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 \\\n    -extensions easyrsa_ca -keyout sample-ca/ca.key -out sample-ca/ca.crt \\\n    -subj \"/C=KG/ST=NA/L=BISHKEK/O=OpenVPN-TEST/emailAddress=me@myhost.mydomain\" \\\n    -config openssl.cnf\n\n# Create server key and cert\nopenssl req -new -nodes -config openssl.cnf -extensions server \\\n    -keyout sample-ca/server.key -out sample-ca/server.csr \\\n    -subj \"/C=KG/ST=NA/O=OpenVPN-TEST/CN=Test-Server/emailAddress=me@myhost.mydomain\"\nopenssl ca -batch -config openssl.cnf -extensions server \\\n    -out sample-ca/server.crt -in sample-ca/server.csr\n\n# Create client key and cert\nopenssl req -new -nodes -config openssl.cnf \\\n    -keyout sample-ca/client.key -out sample-ca/client.csr \\\n    -subj \"/C=KG/ST=NA/O=OpenVPN-TEST/CN=Test-Client/emailAddress=me@myhost.mydomain\"\nopenssl ca -batch -config openssl.cnf \\\n    -out sample-ca/client.crt -in sample-ca/client.csr\n\n# Create password protected key file\nopenssl rsa -aes256 -passout pass:password \\\n    -in sample-ca/client.key -out sample-ca/client-pass.key\n\n# Create pkcs#12 client bundle\nopenssl pkcs12 -export -nodes -password pass:password \\\n    -out sample-ca/client.p12 -inkey sample-ca/client.key \\\n    -in sample-ca/client.crt -certfile sample-ca/ca.crt\n\n# Create a client cert, revoke it, generate CRL\nopenssl req -new -nodes -config openssl.cnf \\\n    -keyout sample-ca/client-revoked.key -out sample-ca/client-revoked.csr \\\n    -subj \"/C=KG/ST=NA/O=OpenVPN-TEST/CN=client-revoked/emailAddress=me@myhost.mydomain\"\nopenssl ca -batch -config openssl.cnf \\\n    -out sample-ca/client-revoked.crt -in sample-ca/client-revoked.csr\nopenssl ca -config openssl.cnf -revoke sample-ca/client-revoked.crt\nopenssl ca -config openssl.cnf -gencrl -out sample-ca/ca.crl\n\n# Create DSA server and client cert (signed by 'regular' RSA CA)\nopenssl dsaparam -out sample-ca/dsaparams.pem 2048\n\nopenssl req -new -newkey dsa:sample-ca/dsaparams.pem -nodes -config openssl.cnf \\\n    -extensions server \\\n    -keyout sample-ca/server-dsa.key -out sample-ca/server-dsa.csr \\\n    -subj \"/C=KG/ST=NA/O=OpenVPN-TEST/CN=Test-Server-DSA/emailAddress=me@myhost.mydomain\"\nopenssl ca -batch -config openssl.cnf -extensions server \\\n    -out sample-ca/server-dsa.crt -in sample-ca/server-dsa.csr\n\nopenssl req -new -newkey dsa:sample-ca/dsaparams.pem -nodes -config openssl.cnf \\\n    -keyout sample-ca/client-dsa.key -out sample-ca/client-dsa.csr \\\n    -subj \"/C=KG/ST=NA/O=OpenVPN-TEST/CN=Test-Client-DSA/emailAddress=me@myhost.mydomain\"\nopenssl ca -batch -config openssl.cnf \\\n    -out sample-ca/client-dsa.crt -in sample-ca/client-dsa.csr\n\n# Create EC server and client cert (signed by 'regular' RSA CA)\nopenssl ecparam -out sample-ca/secp256k1.pem -name secp256k1\n\nopenssl req -new -newkey ec:sample-ca/secp256k1.pem -nodes -config openssl.cnf \\\n    -extensions server \\\n    -keyout sample-ca/server-ec.key -out sample-ca/server-ec.csr \\\n    -subj \"/C=KG/ST=NA/O=OpenVPN-TEST/CN=Test-Server-EC/emailAddress=me@myhost.mydomain\"\nopenssl ca -batch -config openssl.cnf -extensions server \\\n    -out sample-ca/server-ec.crt -in sample-ca/server-ec.csr\n\nopenssl req -new -newkey ec:sample-ca/secp256k1.pem -nodes -config openssl.cnf \\\n    -keyout sample-ca/client-ec.key -out sample-ca/client-ec.csr \\\n    -subj \"/C=KG/ST=NA/O=OpenVPN-TEST/CN=Test-Client-EC/emailAddress=me@myhost.mydomain\"\nopenssl ca -batch -config openssl.cnf \\\n    -out sample-ca/client-ec.crt -in sample-ca/client-ec.csr\n\n# Generate DH parameters\nopenssl dhparam -out dh2048.pem 2048\n\n# Copy keys and certs to working directory\ncp sample-ca/*.key .\ncp sample-ca/*.crt .\ncp sample-ca/*.p12 .\ncp sample-ca/*.crl .\n"
  },
  {
    "path": "sample/sample-keys/openssl.cnf",
    "content": "# Heavily borrowed from EasyRSA 3, for use with OpenSSL 1.0.*\n\n####################################################################\n[ ca ]\ndefault_ca\t= CA_default\t\t# The default ca section\n\n####################################################################\n[ CA_default ]\n\ndir\t\t= sample-ca\t\t# Where everything is kept\ncerts\t\t= $dir\t\t\t# Where the issued certs are kept\ncrl_dir\t\t= $dir\t\t\t# Where the issued crl are kept\ndatabase\t= $dir/index.txt\t# database index file.\nnew_certs_dir\t= $dir\t\t\t# default place for new certs.\n\ncertificate\t= $dir/ca.crt\t \t# The CA certificate\nserial\t\t= $dir/serial \t\t# The current serial number\ncrl\t\t= $dir/crl.pem \t\t# The current CRL\nprivate_key\t= $dir/ca.key\t\t# The private key\nRANDFILE\t= $dir/.rand\t\t# private random number file\n\nx509_extensions\t= basic_exts\t\t# The extensions to add to the cert\n\n# This allows a V2 CRL. Ancient browsers don't like it, but anything Easy-RSA\n# is designed for will. In return, we get the Issuer attached to CRLs.\ncrl_extensions\t= crl_ext\n\ndefault_days\t= 3650\t\t\t# how long to certify for\ndefault_crl_days= 30\t\t\t# how long before next CRL\ndefault_md\t= sha256\t\t# use public key default MD\npreserve\t= no\t\t\t# keep passed DN ordering\n\n# A few difference way of specifying how similar the request should look\n# For type CA, the listed attributes must be the same, and the optional\n# and supplied fields are just that :-)\npolicy\t\t= policy_anything\n\n# For the 'anything' policy, which defines allowed DN fields\n[ policy_anything ]\ncountryName\t\t= optional\nstateOrProvinceName\t= optional\nlocalityName\t\t= optional\norganizationName\t= optional\norganizationalUnitName\t= optional\ncommonName\t\t= supplied\nname\t\t\t= optional\nemailAddress\t\t= optional\n\n####################################################################\n# Easy-RSA request handling\n# We key off $DN_MODE to determine how to format the DN\n[ req ]\ndefault_bits\t\t= 2048\ndefault_keyfile\t\t= privkey.pem\ndefault_md\t\t= sha256\ndistinguished_name\t= cn_only\nx509_extensions\t\t= easyrsa_ca\t# The extensions to add to the self signed cert\n\n# A placeholder to handle the $EXTRA_EXTS feature:\n#%EXTRA_EXTS%\t# Do NOT remove or change this line as $EXTRA_EXTS support requires it\n\n####################################################################\n# Easy-RSA DN (Subject) handling\n\n# Easy-RSA DN for cn_only support:\n[ cn_only ]\ncommonName\t\t= Common Name (eg: your user, host, or server name)\ncommonName_max\t\t= 64\ncommonName_default\t= changeme\n\n# Easy-RSA DN for org support:\n[ org ]\ncountryName\t\t\t= Country Name (2 letter code)\ncountryName_default\t\t= KG\ncountryName_min\t\t\t= 2\ncountryName_max\t\t\t= 2\n\nstateOrProvinceName\t\t= State or Province Name (full name)\nstateOrProvinceName_default\t= NA\n\nlocalityName\t\t\t= Locality Name (eg, city)\nlocalityName_default\t\t= BISHKEK\n\n0.organizationName\t\t= Organization Name (eg, company)\n0.organizationName_default\t= OpenVPN-TEST\n\norganizationalUnitName\t\t= Organizational Unit Name (eg, section)\norganizationalUnitName_default\t=\n\ncommonName\t\t\t= Common Name (eg: your user, host, or server name)\ncommonName_max\t\t\t= 64\ncommonName_default\t\t=\n\nemailAddress\t\t\t= Email Address\nemailAddress_default\t\t= me@myhost.mydomain\nemailAddress_max\t\t= 64\n\n####################################################################\n\n[ basic_exts ]\nbasicConstraints\t= CA:FALSE\nsubjectKeyIdentifier\t= hash\nauthorityKeyIdentifier\t= keyid,issuer:always\n\n# The Easy-RSA CA extensions\n[ easyrsa_ca ]\n\n# PKIX recommendations:\n\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid:always,issuer:always\n\n# This could be marked critical, but it's nice to support reading by any\n# broken clients who attempt to do so.\nbasicConstraints = CA:true\n\n# Limit key usage to CA tasks. If you really want to use the generated pair as\n# a self-signed cert, comment this out.\nkeyUsage = cRLSign, keyCertSign\n\n# CRL extensions.\n[ crl_ext ]\n\n# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.\n\n# issuerAltName=issuer:copy\nauthorityKeyIdentifier=keyid:always,issuer:always\n\n\n# Server extensions.\n[ server ]\n\nbasicConstraints       = CA:FALSE\nnsCertType             = server\nnsComment              = \"OpenSSL Generated Server Certificate\"\nsubjectKeyIdentifier   = hash\nauthorityKeyIdentifier = keyid,issuer:always\nextendedKeyUsage       = serverAuth\nkeyUsage               = digitalSignature, keyEncipherment\n"
  },
  {
    "path": "sample/sample-keys/server-ec.crt",
    "content": "Certificate:\n    Data:\n        Version: 3 (0x2)\n        Serial Number: 6 (0x6)\n        Signature Algorithm: sha256WithRSAEncryption\n        Issuer: C=KG, ST=NA, L=BISHKEK, O=OpenVPN-TEST/emailAddress=me@myhost.mydomain\n        Validity\n            Not Before: Nov  7 12:23:40 2023 GMT\n            Not After : Nov  4 12:23:40 2033 GMT\n        Subject: C=KG, ST=NA, O=OpenVPN-TEST, CN=Test-Server-EC/emailAddress=me@myhost.mydomain\n        Subject Public Key Info:\n            Public Key Algorithm: id-ecPublicKey\n                Public-Key: (256 bit)\n                pub:\n                    04:d6:37:3e:63:63:00:c8:48:ad:12:01:53:e8:72:\n                    4b:b5:50:66:fc:8f:9a:a5:ea:93:cf:94:7e:9d:75:\n                    e7:9b:c5:7e:08:6f:7e:e5:b4:b6:e7:c4:f1:41:a8:\n                    49:0d:f1:e8:7c:11:40:ae:a0:f3:e0:e4:f4:8d:d4:\n                    15:47:38:55:fd\n                ASN1 OID: secp256k1\n        X509v3 extensions:\n            X509v3 Basic Constraints:\n                CA:FALSE\n            Netscape Cert Type:\n                SSL Server\n            Netscape Comment:\n                OpenSSL Generated Server Certificate\n            X509v3 Subject Key Identifier:\n                F8:8F:75:E8:88:59:99:F2:4B:B1:0E:FC:51:52:6E:DD:2E:C9:13:90\n            X509v3 Authority Key Identifier:\n                keyid:73:B1:B8:D6:8A:95:6B:12:E0:74:7C:C5:57:55:98:94:5B:AC:78:5E\n                DirName:/C=KG/ST=NA/L=BISHKEK/O=OpenVPN-TEST/emailAddress=me@myhost.mydomain\n                serial:6F:50:B8:D3:46:6E:72:34:59:BC:00:33:DD:7C:AE:12:EB:27:46:06\n            X509v3 Extended Key Usage:\n                TLS Web Server Authentication\n            X509v3 Key Usage:\n                Digital Signature, Key Encipherment\n    Signature Algorithm: sha256WithRSAEncryption\n    Signature Value:\n        72:9d:c1:ea:43:a5:fb:9f:5b:e0:35:98:c3:77:c2:84:9e:e9:\n        2c:9d:ab:6b:eb:dc:de:b2:9c:fa:38:2a:95:95:ca:35:1b:e7:\n        b4:c2:ab:72:ea:f3:8e:6e:c4:3c:98:cd:88:3d:a4:7a:92:0c:\n        83:25:e2:e0:46:c9:e8:ed:4f:35:21:0c:cd:f0:16:87:0c:cc:\n        a3:97:cf:5b:ef:1d:ce:59:78:2c:36:83:c3:59:60:79:f8:4f:\n        19:7b:19:d8:c3:03:d6:bc:33:be:c2:72:d7:0f:f8:82:de:a3:\n        e6:03:87:5e:0d:e7:9d:87:38:15:77:65:97:2d:4e:7e:d0:47:\n        99:44:f4:3a:6d:b0:f1:6d:93:2e:b4:8a:d2:38:a9:1e:00:ea:\n        68:27:2d:d8:4a:99:f0:5f:a6:f5:7d:f0:57:60:5a:f7:5d:92:\n        a4:ab:30:86:a8:5d:ac:6a:dc:4a:73:6b:5e:77:a9:b9:39:cb:\n        60:3c:b9:ff:d7:b3:81:5d:8e:6a:ef:c6:17:ea:0a:65:a3:9d:\n        1b:ff:1c:73:5c:6a:bd:9c:bf:b8:81:bc:11:2f:8b:0d:0e:80:\n        40:5c:e0:10:33:02:35:e7:8c:d8:73:38:03:b3:41:f3:45:95:\n        57:35:5c:d5:6a:3f:c6:04:79:aa:4a:1c:6d:ab:a9:35:d6:fc:\n        02:64:33:b4:d8:27:18:ff:8b:97:47:96:c9:ff:2f:93:50:26:\n        7b:3c:84:03:6d:e1:56:44:49:12:45:50:16:de:23:b5:9e:07:\n        22:2b:51:78:3c:c4:9d:64:20:7c:c3:eb:af:33:54:5f:f9:35:\n        bd:bc:91:39:cc:50:16:c2:8e:60:4e:46:9c:af:17:fb:a0:c8:\n        6f:0a:e2:50:8b:a5:a9:f4:8f:f4:fa:d4:c9:a7:73:42:0c:00:\n        6d:37:f6:3c:5d:36:8b:ef:a7:bc:d4:af:77:72:f8:c5:71:15:\n        7d:de:74:0f:ec:4c:ce:d6:4d:70:b2:64:38:cb:96:41:c2:02:\n        45:22:62:dd:9d:d2:1c:71:cd:4b:c5:92:34:8a:26:b9:b1:8f:\n        50:85:0c:40:f1:61:68:dd:af:22:1b:d3:3a:78:fc:4f:9d:c0:\n        05:ba:02:7c:15:5b:9c:4f:c8:b9:b9:14:24:fb:1c:2f:16:9f:\n        24:e6:d0:f2:a5:6b:34:c5:69:84:0a:dc:ff:90:22:c6:45:d2:\n        0b:bf:20:28:7c:52:ee:a1:00:78:e9:18:cc:11:44:06:bb:15:\n        6d:8b:39:2c:37:69:ac:2d:86:4c:ef:8c:c7:00:0a:55:c3:5a:\n        53:53:b8:46:56:3a:87:d1:93:33:20:9a:a6:75:5f:0d:23:f5:\n        40:87:e8:cf:74:b1:2a:b4\n-----BEGIN CERTIFICATE-----\nMIIEwDCCAqigAwIBAgIBBjANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJLRzEL\nMAkGA1UECAwCTkExEDAOBgNVBAcMB0JJU0hLRUsxFTATBgNVBAoMDE9wZW5WUE4t\nVEVTVDEhMB8GCSqGSIb3DQEJARYSbWVAbXlob3N0Lm15ZG9tYWluMB4XDTIzMTEw\nNzEyMjM0MFoXDTMzMTEwNDEyMjM0MFowbTELMAkGA1UEBhMCS0cxCzAJBgNVBAgM\nAk5BMRUwEwYDVQQKDAxPcGVuVlBOLVRFU1QxFzAVBgNVBAMMDlRlc3QtU2VydmVy\nLUVDMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW4wVjAQBgcqhkjO\nPQIBBgUrgQQACgNCAATWNz5jYwDISK0SAVPocku1UGb8j5ql6pPPlH6ddeebxX4I\nb37ltLbnxPFBqEkN8eh8EUCuoPPg5PSN1BVHOFX9o4IBPjCCATowCQYDVR0TBAIw\nADARBglghkgBhvhCAQEEBAMCBkAwMwYJYIZIAYb4QgENBCYWJE9wZW5TU0wgR2Vu\nZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU+I916IhZmfJLsQ78\nUVJu3S7JE5AwgaMGA1UdIwSBmzCBmIAUc7G41oqVaxLgdHzFV1WYlFuseF6haqRo\nMGYxCzAJBgNVBAYTAktHMQswCQYDVQQIDAJOQTEQMA4GA1UEBwwHQklTSEtFSzEV\nMBMGA1UECgwMT3BlblZQTi1URVNUMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3Qu\nbXlkb21haW6CFG9QuNNGbnI0WbwAM918rhLrJ0YGMBMGA1UdJQQMMAoGCCsGAQUF\nBwMBMAsGA1UdDwQEAwIFoDANBgkqhkiG9w0BAQsFAAOCAgEAcp3B6kOl+59b4DWY\nw3fChJ7pLJ2ra+vc3rKc+jgqlZXKNRvntMKrcurzjm7EPJjNiD2kepIMgyXi4EbJ\n6O1PNSEMzfAWhwzMo5fPW+8dzll4LDaDw1lgefhPGXsZ2MMD1rwzvsJy1w/4gt6j\n5gOHXg3nnYc4FXdlly1OftBHmUT0Om2w8W2TLrSK0jipHgDqaCct2EqZ8F+m9X3w\nV2Ba912SpKswhqhdrGrcSnNrXnepuTnLYDy5/9ezgV2Oau/GF+oKZaOdG/8cc1xq\nvZy/uIG8ES+LDQ6AQFzgEDMCNeeM2HM4A7NB80WVVzVc1Wo/xgR5qkocbaupNdb8\nAmQztNgnGP+Ll0eWyf8vk1AmezyEA23hVkRJEkVQFt4jtZ4HIitReDzEnWQgfMPr\nrzNUX/k1vbyROcxQFsKOYE5GnK8X+6DIbwriUIulqfSP9PrUyadzQgwAbTf2PF02\ni++nvNSvd3L4xXEVfd50D+xMztZNcLJkOMuWQcICRSJi3Z3SHHHNS8WSNIomubGP\nUIUMQPFhaN2vIhvTOnj8T53ABboCfBVbnE/IubkUJPscLxafJObQ8qVrNMVphArc\n/5AixkXSC78gKHxS7qEAeOkYzBFEBrsVbYs5LDdprC2GTO+MxwAKVcNaU1O4RlY6\nh9GTMyCapnVfDSP1QIfoz3SxKrQ=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "sample/sample-keys/server-ec.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQghKHFa1jQGnTwZbFNJoJv\nRABNN9RrBuBkrXPCwOdUnt6hRANCAATWNz5jYwDISK0SAVPocku1UGb8j5ql6pPP\nlH6ddeebxX4Ib37ltLbnxPFBqEkN8eh8EUCuoPPg5PSN1BVHOFX9\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "sample/sample-keys/server.crt",
    "content": "Certificate:\n    Data:\n        Version: 3 (0x2)\n        Serial Number: 1 (0x1)\n        Signature Algorithm: sha256WithRSAEncryption\n        Issuer: C=KG, ST=NA, L=BISHKEK, O=OpenVPN-TEST/emailAddress=me@myhost.mydomain\n        Validity\n            Not Before: Nov  7 12:23:39 2023 GMT\n            Not After : Nov  4 12:23:39 2033 GMT\n        Subject: C=KG, ST=NA, O=OpenVPN-TEST, CN=Test-Server/emailAddress=me@myhost.mydomain\n        Subject Public Key Info:\n            Public Key Algorithm: rsaEncryption\n                Public-Key: (2048 bit)\n                Modulus:\n                    00:af:93:ce:9d:86:87:c4:8a:bb:38:6f:50:16:9b:\n                    29:70:da:5a:bd:b3:4c:5a:03:b8:e1:94:f5:3f:4b:\n                    3f:1b:05:ea:77:9e:34:59:01:99:de:81:e2:87:3a:\n                    d4:05:18:40:26:7f:a3:e9:82:52:bc:32:84:32:b9:\n                    3c:61:1f:68:5a:89:01:17:21:ec:b9:33:5b:96:33:\n                    16:91:0f:36:af:c3:0f:68:10:44:ea:e6:f9:00:35:\n                    13:61:3d:e7:a0:b1:4b:91:31:b8:11:02:a0:98:cd:\n                    fd:aa:e7:53:6c:31:05:87:36:56:c5:e4:8c:12:96:\n                    d6:f0:c4:5a:a7:0d:96:5f:f6:7a:95:ad:58:e5:6d:\n                    86:54:75:ea:da:aa:fd:1d:0c:38:19:6a:a6:24:c6:\n                    25:60:73:c4:a9:86:51:af:f6:52:45:48:f1:96:16:\n                    8e:19:ff:3f:ce:7b:d1:96:f6:2c:75:12:16:90:27:\n                    78:27:09:0a:77:a0:d8:6e:64:b0:09:94:7c:95:81:\n                    76:a7:c3:be:7d:5a:0c:5a:e4:2d:d2:15:6d:00:bb:\n                    83:a6:ac:35:dc:1e:f7:f5:67:ac:2f:70:07:fd:94:\n                    d9:b1:da:f4:8f:64:67:92:f1:f1:a8:72:27:dd:5c:\n                    d4:f1:38:ab:76:b8:4e:38:26:d4:4c:d9:87:4c:42:\n                    63:d5\n                Exponent: 65537 (0x10001)\n        X509v3 extensions:\n            X509v3 Basic Constraints: \n                CA:FALSE\n            Netscape Cert Type: \n                SSL Server\n            Netscape Comment: \n                OpenSSL Generated Server Certificate\n            X509v3 Subject Key Identifier: \n                18:B9:E7:B1:3E:D2:87:C4:78:2C:0D:D9:BB:7E:BE:68:B3:FC:6A:2B\n            X509v3 Authority Key Identifier: \n                keyid:73:B1:B8:D6:8A:95:6B:12:E0:74:7C:C5:57:55:98:94:5B:AC:78:5E\n                DirName:/C=KG/ST=NA/L=BISHKEK/O=OpenVPN-TEST/emailAddress=me@myhost.mydomain\n                serial:6F:50:B8:D3:46:6E:72:34:59:BC:00:33:DD:7C:AE:12:EB:27:46:06\n            X509v3 Extended Key Usage:\n                TLS Web Server Authentication\n            X509v3 Key Usage:\n                Digital Signature, Key Encipherment\n    Signature Algorithm: sha256WithRSAEncryption\n    Signature Value:\n        1d:e9:04:bc:77:22:d9:70:59:aa:d2:f4:4b:5b:8c:8c:6d:b8:\n        7d:0d:aa:0f:db:75:11:23:72:3a:95:34:33:63:95:16:f1:04:\n        61:95:8e:3f:36:4d:b7:28:a6:f2:ed:c8:89:8f:7f:05:65:83:\n        13:5d:42:ea:2c:1d:a8:79:25:ec:7c:19:6f:51:f2:b0:d0:19:\n        6a:db:14:ae:e4:69:91:d8:47:78:5a:d2:06:ce:fd:8f:d5:1d:\n        78:ae:86:2e:5a:f4:ef:db:05:3d:fc:12:9f:fb:76:60:60:bc:\n        2a:a0:89:50:ea:d8:1b:89:aa:5b:f5:3b:e7:af:3f:dc:ae:6e:\n        bd:5c:7e:63:52:2e:c9:6d:8f:e2:a0:fe:5d:ab:b1:dc:09:39:\n        3b:14:a0:ee:8a:a1:7d:ce:00:a2:9f:8a:b9:f2:67:71:e1:40:\n        9e:d7:c8:92:8f:a2:38:e5:8f:bc:5b:00:ab:92:2f:c5:21:83:\n        05:c7:ff:7a:84:39:99:e7:00:cb:28:2e:51:b8:e8:3e:90:84:\n        f2:d3:6a:67:b3:74:fd:e6:3f:53:b5:4a:08:6f:ed:0f:c2:81:\n        9a:eb:13:26:c1:15:1d:f3:21:51:39:56:76:55:8c:6d:79:6b:\n        5e:19:46:f2:19:2c:47:4f:2d:53:39:45:b5:50:6e:c4:1a:b6:\n        0e:9a:04:92:e9:7b:9d:d5:d7:2d:f3:30:5d:04:ce:24:93:75:\n        5c:35:51:77:e7:74:dd:97:05:bd:06:8a:a2:b2:8e:6c:74:e5:\n        9e:13:10:7e:37:b2:47:72:a0:be:b3:2f:ec:61:09:28:76:b8:\n        a1:85:28:ae:32:a7:b5:57:86:2c:d9:cd:26:f7:47:cc:92:48:\n        7d:06:ce:30:db:bc:23:fe:88:9c:75:50:7c:c0:f1:96:53:54:\n        34:b7:0c:a4:3a:66:12:ea:51:7f:ad:c7:4e:ed:98:8f:3d:c7:\n        ba:29:cd:4b:e9:e0:ce:54:a3:b0:51:d7:00:26:bb:b4:86:f6:\n        d0:76:51:9d:53:cb:52:94:e0:36:a6:9f:10:cb:79:92:4c:17:\n        cf:f2:9e:66:75:06:96:38:c1:f8:7c:22:1b:8e:53:01:bc:af:\n        86:7f:e0:02:f1:14:e2:cb:4b:94:f5:a7:c4:e3:d5:39:83:18:\n        2d:aa:ff:82:b4:da:0a:1b:5d:72:66:0d:c3:a6:7a:8a:2d:89:\n        db:e7:ea:2f:2a:ec:eb:4c:0a:2c:b1:41:1c:8d:7c:cb:78:6a:\n        a7:c5:e7:0b:7a:bf:44:de:24:02:72:da:88:77:40:5e:13:b0:\n        55:28:b5:31:1a:f9:43:79:2c:a1:fa:7d:9a:c8:7a:fe:c2:27:\n        e4:47:02:40:b6:d2:3d:35\n-----BEGIN CERTIFICATE-----\nMIIFizCCA3OgAwIBAgIBATANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJLRzEL\nMAkGA1UECAwCTkExEDAOBgNVBAcMB0JJU0hLRUsxFTATBgNVBAoMDE9wZW5WUE4t\nVEVTVDEhMB8GCSqGSIb3DQEJARYSbWVAbXlob3N0Lm15ZG9tYWluMB4XDTIzMTEw\nNzEyMjMzOVoXDTMzMTEwNDEyMjMzOVowajELMAkGA1UEBhMCS0cxCzAJBgNVBAgM\nAk5BMRUwEwYDVQQKDAxPcGVuVlBOLVRFU1QxFDASBgNVBAMMC1Rlc3QtU2VydmVy\nMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW4wggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQCvk86dhofEirs4b1AWmylw2lq9s0xaA7jhlPU/\nSz8bBep3njRZAZnegeKHOtQFGEAmf6PpglK8MoQyuTxhH2haiQEXIey5M1uWMxaR\nDzavww9oEETq5vkANRNhPeegsUuRMbgRAqCYzf2q51NsMQWHNlbF5IwSltbwxFqn\nDZZf9nqVrVjlbYZUderaqv0dDDgZaqYkxiVgc8SphlGv9lJFSPGWFo4Z/z/Oe9GW\n9ix1EhaQJ3gnCQp3oNhuZLAJlHyVgXanw759Wgxa5C3SFW0Au4OmrDXcHvf1Z6wv\ncAf9lNmx2vSPZGeS8fGocifdXNTxOKt2uE44JtRM2YdMQmPVAgMBAAGjggE+MIIB\nOjAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDAzBglghkgBhvhCAQ0EJhYk\nT3BlblNTTCBHZW5lcmF0ZWQgU2VydmVyIENlcnRpZmljYXRlMB0GA1UdDgQWBBQY\nueexPtKHxHgsDdm7fr5os/xqKzCBowYDVR0jBIGbMIGYgBRzsbjWipVrEuB0fMVX\nVZiUW6x4XqFqpGgwZjELMAkGA1UEBhMCS0cxCzAJBgNVBAgMAk5BMRAwDgYDVQQH\nDAdCSVNIS0VLMRUwEwYDVQQKDAxPcGVuVlBOLVRFU1QxITAfBgkqhkiG9w0BCQEW\nEm1lQG15aG9zdC5teWRvbWFpboIUb1C400ZucjRZvAAz3XyuEusnRgYwEwYDVR0l\nBAwwCgYIKwYBBQUHAwEwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4ICAQAd\n6QS8dyLZcFmq0vRLW4yMbbh9DaoP23URI3I6lTQzY5UW8QRhlY4/Nk23KKby7ciJ\nj38FZYMTXULqLB2oeSXsfBlvUfKw0Blq2xSu5GmR2Ed4WtIGzv2P1R14roYuWvTv\n2wU9/BKf+3ZgYLwqoIlQ6tgbiapb9Tvnrz/crm69XH5jUi7JbY/ioP5dq7HcCTk7\nFKDuiqF9zgCin4q58mdx4UCe18iSj6I45Y+8WwCrki/FIYMFx/96hDmZ5wDLKC5R\nuOg+kITy02pns3T95j9TtUoIb+0PwoGa6xMmwRUd8yFROVZ2VYxteWteGUbyGSxH\nTy1TOUW1UG7EGrYOmgSS6Xud1dct8zBdBM4kk3VcNVF353TdlwW9Boqiso5sdOWe\nExB+N7JHcqC+sy/sYQkodrihhSiuMqe1V4Ys2c0m90fMkkh9Bs4w27wj/oicdVB8\nwPGWU1Q0twykOmYS6lF/rcdO7ZiPPce6Kc1L6eDOVKOwUdcAJru0hvbQdlGdU8tS\nlOA2pp8Qy3mSTBfP8p5mdQaWOMH4fCIbjlMBvK+Gf+AC8RTiy0uU9afE49U5gxgt\nqv+CtNoKG11yZg3DpnqKLYnb5+ovKuzrTAossUEcjXzLeGqnxecLer9E3iQCctqI\nd0BeE7BVKLUxGvlDeSyh+n2ayHr+wifkRwJAttI9NQ==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "sample/sample-keys/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCvk86dhofEirs4\nb1AWmylw2lq9s0xaA7jhlPU/Sz8bBep3njRZAZnegeKHOtQFGEAmf6PpglK8MoQy\nuTxhH2haiQEXIey5M1uWMxaRDzavww9oEETq5vkANRNhPeegsUuRMbgRAqCYzf2q\n51NsMQWHNlbF5IwSltbwxFqnDZZf9nqVrVjlbYZUderaqv0dDDgZaqYkxiVgc8Sp\nhlGv9lJFSPGWFo4Z/z/Oe9GW9ix1EhaQJ3gnCQp3oNhuZLAJlHyVgXanw759Wgxa\n5C3SFW0Au4OmrDXcHvf1Z6wvcAf9lNmx2vSPZGeS8fGocifdXNTxOKt2uE44JtRM\n2YdMQmPVAgMBAAECggEAGe0W0rv4IFWRVRawGiZ0oBgeKL+TxAGjXewZABekYeEl\nwN647CEIZOAuYGRCGoknhTJ5NGnsvaLX/TAiclT+RnV5673ersTz/oyHWzQxPGhD\n8MyLi1mVOyqbNKi8zfBgMDh0mE5trc1SaoIYwh5wVTROQTqA/zMML3v5xuEta03u\nBMsNWMzm/fFXRvO6ydxdpFZkQJUeNvsGizrAhtFqsm8Cba9f/yEduyAdj2DpkG8A\nH8KmBQcAFstWX1hcC92V8qlf8RwA1o5TN82Nu2dwa+xkCTsOFK8uVE8lBkjB3C0O\nH4fGlwJ4BLUZPxIYaefn38LIQr8hZ9ITstmM2+EFoQKBgQDGD1CTdbAfGv9vBSle\ncinxflcgXOpr6XdGWZZz5VPvdE91fMgnwHOnGVZI0pI5xxO3FvrujjZ2yzTu+yme\nMG5YWjMraqdWZ0speJK7/nxIPNK+frCvVzY2sA/STgUEni2XnNkiC7w0VXWnT/xP\nrmCuJeJ211eF2bd4rrldeg9ApwKBgQDi8MiBDJFRxlP9xCTPVdTixN88Fy7JBFJE\nuZNtKeLkg2ce7bvNc9QOePXCM9Fn0NOuBTLf4SMkfFybyDKn7BTznwA0Yz2muyaK\nrzmGeGP+gzw5MQk6nzk8NIzdMYr3G9ockrMTYBNPVoiwhbshlVWNg3Qvic7cXDoB\nQ1bXfrurIwKBgQDFUDiLz3E4a+MRrWi7SKz0g1M1UJvSCfLjyRiUOWFXat5GQ5v7\nzkTpsdo+DlnS6buAaYpv4onr6yG++8VIbSNhLetQU56F+73rgM1eMHeMV9v0H67R\n3+aIsPnyH/vrz9HH+2BuBJbo5EKj/pF0qFp05BUrI/lzxaR8vES7FYDgfQKBgQDF\n+zWQj7w/UPx5SKKsVr7wTrxJmhfwulpjJlqdQ4tzu8c8zj2m0UPQlGoiUD6BiUcC\na/qkIa8c53mLVi4LHQRyPOZazbE9Qcwv9QoEbAcgRLFHW6YnhDzUbyvs1IndZmjz\nwG+Fma1+64k4JpLIi5UlbebwihLzX2ojK/IY8bEbbQKBgQC81tY7mRPAYnl5QmIQ\nYLqvQyHf/a2bVY+3XNyLF6tWngCOyt8z4Dy3pTRVI2KMVXL9+zPWuJdabwwVlWJs\n9CzR9SqYkaPP3mlbZXWt5X10OiyNU+kcCvTRNZ10OUr8XJ0tHRIuJxgBGoXdWxSF\n6uIa5Vvw9DOMFGnbugLbWuMYjQ==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "sample/sample-keys/ta.key",
    "content": "#\n# 2048 bit OpenVPN static key\n#\n-----BEGIN OpenVPN Static key V1-----\n21d94830510107f8753d3b6f3145e01d\ned37075115afcb0538ecdd8503ee9663\n7218c9ed38d908d594231d7d143c73da\n5055310f89d336da99c8b3dcb18909c7\n9dd44f540670ebc0f120beb7211e9683\n9cb542572c48bfa7ffaa9a22cb8304b7\n869b92f4442918e598745bb78ac8877f\n02b00a7cdef3f2446c130d39a7c45126\n9ef399fd6029cdfc80a7c604041312ab\n0a969bc906bdee6e6d707afdcbe8c7fb\n97beb66049c3d328340775025433ceba\n1e38008a826cf92443d903106199373b\ndadd9c2c735cf481e580db4e81b99f12\ne3f46b6159c687cd1b9e689f7712573c\n0f02735a45573dfb5cd55cf464942389\n2c7e91f439bdd7337a8ceebd302cfbfa\n-----END OpenVPN Static key V1-----\n"
  },
  {
    "path": "sample/sample-plugins/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n\nAM_CPPFLAGS = -I$(top_srcdir) -I$(top_builddir) \\\n\t-I$(top_srcdir)/include -I$(top_builddir)/include\n\n# We don't want automake to pull in libtool for building these\n# sample-plugins.  Even though this breaks the conceptual ideas\n# around autoconf/automake/libtools ... these sample plug-ins\n# are just sample code, not to be installed or distributed outside\n# of the source tarball.  Not even built by default, by design.\n#\n# We only add this as a simple and convenient way to build all\n# these plug-ins with the same build parameters as the rest\n# of the OpenVPN code.\n#\n# All the plugins which will be built are processed in this\n# separate Makefile, which disconnects everything just enough\n# to achieve our goal.\ninclude Makefile.plugins\n\n\ndist-hook :\n\tmake -f Makefile.plugins clean\n"
  },
  {
    "path": "sample/sample-plugins/Makefile.plugins",
    "content": "#  SPDX-License-Identifier: GPL-2.0-only\n#\n#  Copyright (C) 2020-2026 OpenVPN Inc <sales@openvpn.net>\n#\n\n#\n# Plug-ins to build - listed entries should not carry any extensions\n#\nPLUGINS = \\\n\tdefer/multi-auth \\\n\tkeying-material-exporter-demo/keyingmaterialexporter \\\n\tlog/log log/log_v3 \\\n\tsimple/base64 \\\n\tsimple/simple \\\n\tclient-connect/sample-client-connect\n\n# All the plugins to build - rewritten with .so extension\nall : $(foreach var, $(PLUGINS), $(var).so)\n\n# Do not automatically remove object files\n# This is a special Make setting, to avoid adding an implicit\n# 'rm' command on object files - due to the .c.o/%.so rules below\n.PRECIOUS: %.o\n\n# Compile step\n.c.o :\n\ttest -d `dirname $@` || $(MKDIR_P) `dirname $@`; \\\n\t$(CC) -c -o $@ $(CFLAGS) $(AM_CPPFLAGS) -fPIC $<\n\n# Link step\n%.so : %.o\n\t$(CC) $(LDFLAGS) -shared -fPIC -o $@ $<\n\n# Clean up all build object and shared object files\nclean :\n\trm -f $(foreach var, $(PLUGINS), $(var).o) \\\n\t$(foreach var, $(PLUGINS), $(var).so)\n"
  },
  {
    "path": "sample/sample-plugins/README",
    "content": "OpenVPN plug-in examples.\n\nExamples provided:\n\n* authentication and logging\nsimple/simple.c -- using the --auth-user-pass-verify callback, verify\n                   that the username/password is \"foo\"/\"bar\".\ndefer/multi-auth.c\n                -- using the --auth-user-pass-verify callback,\n                   test deferred authentication. Can be used to test multiple\n                   authentication plugins in the same server config.\nlog/log.c       -- Extended variant of simple/simple.c which adds more\n                   logging of what is happening inside the plug-in\nlog/log_v3.c    -- A variant of log/log.c, which makes use of the\n                   OpenVPN plug-in v3 API.  This will also log even more\n                   information related to certificates in use.\n\n* client-connect (and logging)\nclient-connect/sample-client-connect -- demonstrate how to use the\n                   CLIENT_CONNECT and CLIENT_CONNECT_V2 hooks to achieve\n                   \"per client configuration / logging / ...\" actions,\n                   both in synchronous and async/deferred mode\n\n* cryptography related\nsimple/base64.c -- Example using the OpenVPN exported base64 encode/decode\n                   functions\nkeying-material-exporter-demo/keyingmaterialexporter.c\n                -- Example based on TLS Keying Material Exporters over HTTP [RFC-5705]\n                   (openvpn/doc/keying-material-exporter.txt).  For more details, see\n                   keying-material-exporter-demo/README\n\n\nTo build on *BSD/Linux platforms (requires GNU Make):\n\n   gmake                   (builds a default set of plug-ins)\n   gmake simple/simple.so\n\nTo build on Windows platform (MinGW):\n\n   cd simple; ./winbuild simple.so\n\nTo use in OpenVPN, add to config file:\n\n  plugin simple.so (Linux/BSD/etc.)\n  plugin simple.dll\n"
  },
  {
    "path": "sample/sample-plugins/client-connect/README",
    "content": "OpenVPN plugin examples.\n\nExamples provided:\n\nsample-client-connect.c\n\n  - hook to all plugin hooks that openvpn offers\n  - log which hook got called\n  - on CLIENT_CONNECT or CLIENT_CONNECT_V2 set some config variables\n    (controlled by \"setenv plugin_cc_config ...\" and \"plugin_cc2_config\"\n    in openvpn's config)\n\n  - if the environment variable UV_WANT_CC_FAIL is set, fail\n  - if the environment variable UV_WANT_CC_DISABLE is set, reject (\"disable\")\n  - if the environment variable UV_WANT_CC_ASYNC is set, go to\n    asynchronous/deferred mode on CLIENT_CONNECT, and sleep for\n    ${UV_WANT_CC_ASYNC} seconds\n\n  - if the environment variable UV_WANT_CC2_FAIL is set, fail CC2\n  - if the environment variable UV_WANT_CC2_DISABLE is set, reject (\"disable\")\n  - if the environment variable UV_WANT_CC2_ASYNC is set, go to\n    asynchronous/deferred mode on CLIENT_CONNECT_V2, and sleep for\n    ${UV_WANT_CC2_ASYNC} seconds\n\n    (this can be client-controlled with --setenv UV_WANT_CC_ASYNC nnn\n     etc. --> for easy testing server code paths)\n\nTo build for unixy platforms (not very sophisticated right now, needs gmake):\n\n  .../sample-plugins$ gmake client-connect/sample-client-connect.so\n\n(This plugin has not been tested on Windows, and might not even work due\nto its use of fork() and wait().  Let us know if it does or needs patches)\n\n\nTo use in OpenVPN, add to config file:\n\n  plugin sample-client-connect.so (Linux/BSD/etc.)\n"
  },
  {
    "path": "sample/sample-plugins/client-connect/sample-client-connect.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * This file implements a simple OpenVPN plugin module which\n * will log the calls made, and send back some config statements\n * when called on the CLIENT_CONNECT and CLIENT_CONNECT_V2 hooks.\n *\n * it can be asked to fail or go to async/deferred mode by setting\n * environment variables (UV_WANT_CC_FAIL, UV_WANT_CC_ASYNC,\n * UV_WANT_CC2_ASYNC) - mostly used as a testing vehicle for the\n * server side code to handle these cases\n *\n * See the README file for build instructions and env control variables.\n */\n\n/* strdup() might need special defines to be visible in <string.h> */\n#include \"config.h\"\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <sys/wait.h>\n\n#include \"openvpn-plugin.h\"\n\n/* Pointers to functions exported from openvpn */\nstatic plugin_log_t plugin_log = NULL;\nstatic plugin_secure_memzero_t plugin_secure_memzero = NULL;\nstatic plugin_base64_decode_t plugin_base64_decode = NULL;\n\n/* module name for plugin_log() */\nstatic char *MODULE = \"sample-cc\";\n\n/*\n * Our context, where we keep our state.\n */\n\nstruct plugin_context\n{\n    int verb; /* logging verbosity */\n};\n\n/* this is used for the CLIENT_CONNECT_V2 async/deferred handler\n *\n * the \"CLIENT_CONNECT_V2\" handler puts per-client information into\n * this, and the \"CLIENT_CONNECT_DEFER_V2\" handler looks at it to see\n * if it's time yet to succeed/fail\n */\nstruct plugin_per_client_context\n{\n    time_t sleep_until; /* wakeup time (time() + sleep) */\n    bool want_fail;\n    bool want_disable;\n    const char *client_config;\n};\n\n/*\n * Given an environmental variable name, search\n * the envp array for its value, returning it\n * if found or NULL otherwise.\n */\nstatic const char *\nget_env(const char *name, const char *envp[])\n{\n    if (envp)\n    {\n        const size_t namelen = strlen(name);\n        for (int i = 0; envp[i]; ++i)\n        {\n            if (!strncmp(envp[i], name, namelen))\n            {\n                const char *cp = envp[i] + namelen;\n                if (*cp == '=')\n                {\n                    return cp + 1;\n                }\n            }\n        }\n    }\n    return NULL;\n}\n\n\nstatic int\natoi_null0(const char *str)\n{\n    if (str)\n    {\n        return atoi(str);\n    }\n    else\n    {\n        return 0;\n    }\n}\n\n/* use v3 functions so we can use openvpn's logging and base64 etc. */\nOPENVPN_EXPORT int\nopenvpn_plugin_open_v3(const int v3structver, struct openvpn_plugin_args_open_in const *args,\n                       struct openvpn_plugin_args_open_return *ret)\n{\n    /* const char **argv = args->argv; */ /* command line arguments (unused) */\n    const char **envp = args->envp;       /* environment variables */\n\n    /* Check API compatibility -- struct version 5 or higher needed */\n    if (v3structver < 5)\n    {\n        fprintf(stderr,\n                \"sample-client-connect: this plugin is incompatible with the running version of OpenVPN\\n\");\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /*\n     * Allocate our context\n     */\n    struct plugin_context *context = calloc(1, sizeof(struct plugin_context));\n    if (!context)\n    {\n        goto error;\n    }\n\n    /*\n     * Intercept just about everything...\n     */\n    ret->type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_UP)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_DOWN)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_ROUTE_UP)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_IPCHANGE)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_VERIFY)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT_V2)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_DISCONNECT)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_LEARN_ADDRESS)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_FINAL);\n\n    /* Save global pointers to functions exported from openvpn */\n    plugin_log = args->callbacks->plugin_log;\n    plugin_secure_memzero = args->callbacks->plugin_secure_memzero;\n    plugin_base64_decode = args->callbacks->plugin_base64_decode;\n\n    /*\n     * Get verbosity level from environment\n     */\n    context->verb = atoi_null0(get_env(\"verb\", envp));\n\n    ret->handle = (openvpn_plugin_handle_t *)context;\n    plugin_log(PLOG_NOTE, MODULE, \"initialization succeeded\");\n    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n\nerror:\n    free(context);\n    return OPENVPN_PLUGIN_FUNC_ERROR;\n}\n\n\n/* there are two possible interfaces for an openvpn plugin how\n * to be called on \"client connect\", which primarily differ in the\n * way config options are handed back to the client instance\n * (see openvpn/multi.c, multi_client_connect_call_plugin_{v1,v2}())\n *\n * OPENVPN_PLUGIN_CLIENT_CONNECT\n *   openvpn creates a temp file and passes the name to the plugin\n *    (via argv[1] variable, argv[0] is the name of the plugin)\n *   the plugin can write config statements to that file, and openvpn\n *    reads it in like a \"ccd/$cn\" per-client config file\n *\n * OPENVPN_PLUGIN_CLIENT_CONNECT_V2\n *   the caller passes in a pointer to an \"openvpn_plugin_string_list\"\n *   (openvpn-plugin.h), which is a linked list of (name,value) pairs\n *\n *   we fill in one node with name=\"config\" and value=\"our config\"\n *\n *   both \"l\" and \"l->name\" and \"l->value\" are malloc()ed by the plugin\n *   and free()ed by the caller (openvpn_plugin_string_list_free())\n */\n\n/* helper function to write actual \"here are your options\" file,\n * called from sync and sync handler\n */\nint\nwrite_cc_options_file(const char *name, const char **envp)\n{\n    if (!name)\n    {\n        return OPENVPN_PLUGIN_FUNC_SUCCESS;\n    }\n\n    FILE *fp = fopen(name, \"w\");\n    if (!fp)\n    {\n        plugin_log(PLOG_ERR, MODULE, \"fopen('%s') failed\", name);\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /* config to-be-sent can come from \"setenv plugin_cc_config\" in openvpn */\n    const char *p = get_env(\"plugin_cc_config\", envp);\n    if (p)\n    {\n        fprintf(fp, \"%s\\n\", p);\n    }\n\n    /* some generic config snippets so we know it worked */\n    fprintf(fp, \"push \\\"echo sample-cc plugin 1 called\\\"\\n\");\n\n    /* if the caller wants, reject client by means of \"disable\" option */\n    if (get_env(\"UV_WANT_CC_DISABLE\", envp))\n    {\n        plugin_log(PLOG_NOTE, MODULE, \"env has UV_WANT_CC_DISABLE, reject\");\n        fprintf(fp, \"disable\\n\");\n    }\n    fclose(fp);\n\n    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n}\n\nint\ncc_handle_deferred_v1(int seconds, const char *name, const char **envp)\n{\n    const char *ccd_file = get_env(\"client_connect_deferred_file\", envp);\n    if (!ccd_file)\n    {\n        plugin_log(PLOG_NOTE, MODULE,\n                   \"env has UV_WANT_CC_ASYNC=%d, but \"\n                   \"'client_connect_deferred_file' not set -> fail\",\n                   seconds);\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /* the CLIENT_CONNECT (v1) API is a bit tricky to work with, because\n     * completition can be signalled both by the \"deferred_file\" and by\n     * the new ...CLIENT_CONNECT_DEFER API - which is optional.\n     *\n     * For OpenVPN to be able to differenciate, we must create the file\n     * right away if we want to use that for signalling.\n     */\n    int fd = open(ccd_file, O_WRONLY);\n    if (fd < 0)\n    {\n        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"open('%s') failed\", ccd_file);\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    if (write(fd, \"2\", 1) != 1)\n    {\n        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"write to '%s' failed\", ccd_file);\n        close(fd);\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n    close(fd);\n\n    /* we do not want to complicate our lives with having to wait()\n     * for child processes (so they are not zombiefied) *and* we MUST NOT\n     * fiddle with signal handlers (= shared with openvpn main), so\n     * we use double-fork() trick.\n     */\n\n    /* fork, sleep, succeed/fail according to env vars */\n    pid_t p1 = fork();\n    if (p1 < 0) /* Fork failed */\n    {\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n    if (p1 > 0) /* parent process */\n    {\n        waitpid(p1, NULL, 0);\n        return OPENVPN_PLUGIN_FUNC_DEFERRED;\n    }\n\n    /* first gen child process, fork() again and exit() right away */\n    pid_t p2 = fork();\n    if (p2 < 0)\n    {\n        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"BACKGROUND: fork(2) failed\");\n        exit(1);\n    }\n    if (p2 > 0) /* new parent: exit right away */\n    {\n        exit(0);\n    }\n\n    /* (grand-)child process\n     *  - never call \"return\" now (would mess up openvpn)\n     *  - return status is communicated by file\n     *  - then exit()\n     */\n\n    /* do mighty complicated work that will really take time here... */\n    plugin_log(PLOG_NOTE, MODULE, \"in async/deferred handler, sleep(%d)\", seconds);\n    sleep((unsigned int)seconds);\n\n    /* write config options to openvpn */\n    int ret = write_cc_options_file(name, envp);\n\n    /* by setting \"UV_WANT_CC_FAIL\" we can be triggered to fail */\n    const char *p = get_env(\"UV_WANT_CC_FAIL\", envp);\n    if (p)\n    {\n        plugin_log(PLOG_NOTE, MODULE, \"env has UV_WANT_CC_FAIL=%s -> fail\", p);\n        ret = OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /* now signal success/failure state to openvpn */\n    fd = open(ccd_file, O_WRONLY);\n    if (fd < 0)\n    {\n        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"open('%s') failed\", ccd_file);\n        exit(1);\n    }\n\n    plugin_log(PLOG_NOTE, MODULE, \"cc_handle_deferred_v1: done, signalling %s\",\n               (ret == OPENVPN_PLUGIN_FUNC_SUCCESS) ? \"success\" : \"fail\");\n\n    if (write(fd, (ret == OPENVPN_PLUGIN_FUNC_SUCCESS) ? \"1\" : \"0\", 1) != 1)\n    {\n        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"write to '%s' failed\", ccd_file);\n    }\n    close(fd);\n\n    exit(0);\n}\n\nint\nopenvpn_plugin_client_connect(struct plugin_context *context, const char **argv, const char **envp)\n{\n    /* log environment variables handed to us by OpenVPN, but\n     * only if \"setenv verb\" is 3 or higher (arbitrary number)\n     */\n    if (context->verb >= 3)\n    {\n        for (int i = 0; argv[i]; i++)\n        {\n            plugin_log(PLOG_NOTE, MODULE, \"per-client argv: %s\", argv[i]);\n        }\n        for (int i = 0; envp[i]; i++)\n        {\n            plugin_log(PLOG_NOTE, MODULE, \"per-client env: %s\", envp[i]);\n        }\n    }\n\n    /* by setting \"UV_WANT_CC_ASYNC\" we go to async/deferred mode */\n    const char *p = get_env(\"UV_WANT_CC_ASYNC\", envp);\n    if (p)\n    {\n        /* the return value will usually be OPENVPN_PLUGIN_FUNC_DEFERRED\n         * (\"I will do my job in the background, check the status file!\")\n         * but depending on env setup it might be \"..._ERRROR\"\n         */\n        return cc_handle_deferred_v1(atoi(p), argv[1], envp);\n    }\n\n    /* -- this is synchronous mode (openvpn waits for us) -- */\n\n    /* by setting \"UV_WANT_CC_FAIL\" we can be triggered to fail */\n    p = get_env(\"UV_WANT_CC_FAIL\", envp);\n    if (p)\n    {\n        plugin_log(PLOG_NOTE, MODULE, \"env has UV_WANT_CC_FAIL=%s -> fail\", p);\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /* does the caller want options?  give them some */\n    int ret = write_cc_options_file(argv[1], envp);\n\n    return ret;\n}\n\nint\nopenvpn_plugin_client_connect_v2(struct plugin_context *context,\n                                 struct plugin_per_client_context *pcc, const char **envp,\n                                 struct openvpn_plugin_string_list **return_list)\n{\n    /* by setting \"UV_WANT_CC2_ASYNC\" we go to async/deferred mode */\n    const char *want_async = get_env(\"UV_WANT_CC2_ASYNC\", envp);\n    const char *want_fail = get_env(\"UV_WANT_CC2_FAIL\", envp);\n    const char *want_disable = get_env(\"UV_WANT_CC2_DISABLE\", envp);\n\n    /* config to push towards client - can be controlled by OpenVPN\n     * config (\"setenv plugin_cc2_config ...\") - mostly useful in a\n     * regression test environment to push stuff like routes which are\n     * then verified by t_client ping tests\n     */\n    const char *client_config = get_env(\"plugin_cc2_config\", envp);\n    if (!client_config)\n    {\n        /* pick something meaningless which can be verified in client log */\n        client_config = \"push \\\"setenv CC2 MOOH\\\"\\n\";\n    }\n\n    if (want_async)\n    {\n        /* we do no really useful work here, so we just tell the\n         * \"CLIENT_CONNECT_DEFER_V2\" handler that it should sleep\n         * and then \"do things\" via the per-client-context\n         */\n        pcc->sleep_until = time(NULL) + atoi(want_async);\n        pcc->want_fail = (want_fail != NULL);\n        pcc->want_disable = (want_disable != NULL);\n        pcc->client_config = client_config;\n        plugin_log(PLOG_NOTE, MODULE, \"env has UV_WANT_CC2_ASYNC=%s -> set up deferred handler\",\n                   want_async);\n        return OPENVPN_PLUGIN_FUNC_DEFERRED;\n    }\n\n    /* by setting \"UV_WANT_CC2_FAIL\" we can be triggered to fail here */\n    if (want_fail)\n    {\n        plugin_log(PLOG_NOTE, MODULE, \"env has UV_WANT_CC2_FAIL=%s -> fail\", want_fail);\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    struct openvpn_plugin_string_list *rl = calloc(1, sizeof(struct openvpn_plugin_string_list));\n    if (!rl)\n    {\n        plugin_log(PLOG_ERR, MODULE, \"malloc(return_list) failed\");\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n    rl->name = strdup(\"config\");\n    if (want_disable)\n    {\n        plugin_log(PLOG_NOTE, MODULE, \"env has UV_WANT_CC2_DISABLE, reject\");\n        rl->value = strdup(\"disable\\n\");\n    }\n    else\n    {\n        rl->value = strdup(client_config);\n    }\n\n    if (!rl->name || !rl->value)\n    {\n        plugin_log(PLOG_ERR, MODULE, \"malloc(return_list->xx) failed\");\n        free(rl->name);\n        free(rl->value);\n        free(rl);\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    *return_list = rl;\n\n    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n}\n\nint\nopenvpn_plugin_client_connect_defer_v2(struct plugin_context *context,\n                                       struct plugin_per_client_context *pcc,\n                                       struct openvpn_plugin_string_list **return_list)\n{\n    time_t time_left = pcc->sleep_until - time(NULL);\n    plugin_log(PLOG_NOTE, MODULE, \"defer_v2: seconds left=%d\", (int)time_left);\n\n    /* not yet due? */\n    if (time_left > 0)\n    {\n        return OPENVPN_PLUGIN_FUNC_DEFERRED;\n    }\n\n    /* client wants fail? */\n    if (pcc->want_fail)\n    {\n        plugin_log(PLOG_NOTE, MODULE, \"env has UV_WANT_CC2_FAIL -> fail\");\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /* fill in RL according to with-disable / without-disable */\n\n    /* TODO: unify this with non-deferred case */\n    struct openvpn_plugin_string_list *rl = calloc(1, sizeof(struct openvpn_plugin_string_list));\n    if (!rl)\n    {\n        plugin_log(PLOG_ERR, MODULE, \"malloc(return_list) failed\");\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n    rl->name = strdup(\"config\");\n    if (pcc->want_disable)\n    {\n        plugin_log(PLOG_NOTE, MODULE, \"env has UV_WANT_CC2_DISABLE, reject\");\n        rl->value = strdup(\"disable\\n\");\n    }\n    else\n    {\n        rl->value = strdup(pcc->client_config);\n    }\n\n    if (!rl->name || !rl->value)\n    {\n        plugin_log(PLOG_ERR, MODULE, \"malloc(return_list->xx) failed\");\n        free(rl->name);\n        free(rl->value);\n        free(rl);\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    *return_list = rl;\n\n    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n}\n\nOPENVPN_EXPORT int\nopenvpn_plugin_func_v2(openvpn_plugin_handle_t handle, const int type, const char *argv[],\n                       const char *envp[], void *per_client_context,\n                       struct openvpn_plugin_string_list **return_list)\n{\n    struct plugin_context *context = (struct plugin_context *)handle;\n    struct plugin_per_client_context *pcc = (struct plugin_per_client_context *)per_client_context;\n\n    /* for most functions, we just \"don't do anything\" but log the\n     * event received (so one can follow it in the log and understand\n     * the sequence of events).  CONNECT and CONNECT_V2 are handled\n     */\n    switch (type)\n    {\n        case OPENVPN_PLUGIN_UP:\n            plugin_log(PLOG_NOTE, MODULE, \"OPENVPN_PLUGIN_UP\");\n            break;\n\n        case OPENVPN_PLUGIN_DOWN:\n            plugin_log(PLOG_NOTE, MODULE, \"OPENVPN_PLUGIN_DOWN\");\n            break;\n\n        case OPENVPN_PLUGIN_ROUTE_UP:\n            plugin_log(PLOG_NOTE, MODULE, \"OPENVPN_PLUGIN_ROUTE_UP\");\n            break;\n\n        case OPENVPN_PLUGIN_IPCHANGE:\n            plugin_log(PLOG_NOTE, MODULE, \"OPENVPN_PLUGIN_IPCHANGE\");\n            break;\n\n        case OPENVPN_PLUGIN_TLS_VERIFY:\n            plugin_log(PLOG_NOTE, MODULE, \"OPENVPN_PLUGIN_TLS_VERIFY\");\n            break;\n\n        case OPENVPN_PLUGIN_CLIENT_CONNECT:\n            plugin_log(PLOG_NOTE, MODULE, \"OPENVPN_PLUGIN_CLIENT_CONNECT\");\n            return openvpn_plugin_client_connect(context, argv, envp);\n\n        case OPENVPN_PLUGIN_CLIENT_CONNECT_V2:\n            plugin_log(PLOG_NOTE, MODULE, \"OPENVPN_PLUGIN_CLIENT_CONNECT_V2\");\n            return openvpn_plugin_client_connect_v2(context, pcc, envp, return_list);\n\n        case OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2:\n            plugin_log(PLOG_NOTE, MODULE, \"OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2\");\n            return openvpn_plugin_client_connect_defer_v2(context, pcc, return_list);\n\n        case OPENVPN_PLUGIN_CLIENT_DISCONNECT:\n            plugin_log(PLOG_NOTE, MODULE, \"OPENVPN_PLUGIN_CLIENT_DISCONNECT\");\n            break;\n\n        case OPENVPN_PLUGIN_LEARN_ADDRESS:\n            plugin_log(PLOG_NOTE, MODULE, \"OPENVPN_PLUGIN_LEARN_ADDRESS\");\n            break;\n\n        case OPENVPN_PLUGIN_TLS_FINAL:\n            plugin_log(PLOG_NOTE, MODULE, \"OPENVPN_PLUGIN_TLS_FINAL\");\n            break;\n\n        default:\n            plugin_log(PLOG_NOTE, MODULE, \"OPENVPN_PLUGIN_? type=%d\\n\", type);\n    }\n    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n}\n\nOPENVPN_EXPORT void *\nopenvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)\n{\n    printf(\"FUNC: openvpn_plugin_client_constructor_v1\\n\");\n    return calloc(1, sizeof(struct plugin_per_client_context));\n}\n\nOPENVPN_EXPORT void\nopenvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *per_client_context)\n{\n    printf(\"FUNC: openvpn_plugin_client_destructor_v1\\n\");\n    free(per_client_context);\n}\n\nOPENVPN_EXPORT void\nopenvpn_plugin_close_v1(openvpn_plugin_handle_t handle)\n{\n    struct plugin_context *context = (struct plugin_context *)handle;\n    printf(\"FUNC: openvpn_plugin_close_v1\\n\");\n    free(context);\n}\n"
  },
  {
    "path": "sample/sample-plugins/defer/multi-auth.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * This file implements a simple OpenVPN plugin module which\n * can do either an instant authentication or a deferred auth.\n * The purpose of this plug-in is to test multiple auth plugins\n * in the same configuration file\n *\n * Plugin arguments:\n *\n *   multi-auth.so LOG_ID  DEFER_TIME  USERNAME  PASSWORD\n *\n * LOG_ID is just an ID string used to separate auth results in the log\n * DEFER_TIME is the time to defer the auth. Set to 0 to return immediately\n * USERNAME is the username for a valid authentication\n * PASSWORD is the password for a valid authentication\n *\n * The DEFER_TIME time unit is in ms.\n *\n * Sample usage:\n *\n * plugin multi-auth.so MA_1 0 foo bar  # Instant reply user:foo pass:bar\n * plugin multi-auth.so MA_2 5000 fux bax # Defer 5 sec, user:fux pass: bax\n *\n */\n#include \"config.h\"\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <unistd.h>\n#include <stdbool.h>\n#include <fcntl.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n\n#include \"openvpn-plugin.h\"\n\nstatic char *MODULE = \"multi-auth\";\n\n/*\n * Our context, where we keep our state.\n */\n\nstruct plugin_context\n{\n    int test_deferred_auth;\n    char *authid;\n    char *test_valid_user;\n    char *test_valid_pass;\n};\n\n/* local wrapping of the log function, to add more details */\nstatic plugin_vlog_t _plugin_vlog_func = NULL;\nstatic void\nplog(const struct plugin_context *ctx, int flags, char *fmt, ...)\n{\n    char logid[129];\n\n    if (ctx && ctx->authid)\n    {\n        snprintf(logid, 128, \"%s[%s]\", MODULE, ctx->authid);\n    }\n    else\n    {\n        snprintf(logid, 128, \"%s\", MODULE);\n    }\n\n    va_list arglist;\n    va_start(arglist, fmt);\n    _plugin_vlog_func(flags, logid, fmt, arglist);\n    va_end(arglist);\n}\n\n\n/*\n * Constants indicating minimum API and struct versions by the functions\n * in this plugin.  Consult openvpn-plugin.h, look for:\n * OPENVPN_PLUGIN_VERSION and OPENVPN_PLUGINv3_STRUCTVER\n *\n * Strictly speaking, this sample code only requires plugin_log, a feature\n * of structver version 1.  However, '1' lines up with ancient versions\n * of openvpn that are past end-of-support.  As such, we are requiring\n * structver '5' here to indicate a desire for modern openvpn, rather\n * than a need for any particular feature found in structver beyond '1'.\n */\n#define OPENVPN_PLUGIN_VERSION_MIN   3\n#define OPENVPN_PLUGIN_STRUCTVER_MIN 5\n\n\nstruct plugin_per_client_context\n{\n    int n_calls;\n    bool generated_pf_file;\n};\n\n\n/*\n * Given an environmental variable name, search\n * the envp array for its value, returning it\n * if found or NULL otherwise.\n */\nstatic const char *\nget_env(const char *name, const char *envp[])\n{\n    if (envp)\n    {\n        const size_t namelen = strlen(name);\n        for (int i = 0; envp[i]; ++i)\n        {\n            if (!strncmp(envp[i], name, namelen))\n            {\n                const char *cp = envp[i] + namelen;\n                if (*cp == '=')\n                {\n                    return cp + 1;\n                }\n            }\n        }\n    }\n    return NULL;\n}\n\n/* used for safe printf of possible NULL strings */\nstatic const char *\nnp(const char *str)\n{\n    if (str)\n    {\n        return str;\n    }\n    else\n    {\n        return \"[NULL]\";\n    }\n}\n\nstatic int\natoi_null0(const char *str)\n{\n    if (str)\n    {\n        return atoi(str);\n    }\n    else\n    {\n        return 0;\n    }\n}\n\n/* Require a minimum OpenVPN Plugin API */\nOPENVPN_EXPORT int\nopenvpn_plugin_min_version_required_v1(void)\n{\n    return OPENVPN_PLUGIN_VERSION_MIN;\n}\n\n/* use v3 functions so we can use openvpn's logging and base64 etc. */\nOPENVPN_EXPORT int\nopenvpn_plugin_open_v3(const int v3structver, struct openvpn_plugin_args_open_in const *args,\n                       struct openvpn_plugin_args_open_return *ret)\n{\n    if (v3structver < OPENVPN_PLUGIN_STRUCTVER_MIN)\n    {\n        fprintf(stderr, \"%s: this plugin is incompatible with the running version of OpenVPN\\n\",\n                MODULE);\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /* Save global pointers to functions exported from openvpn */\n    _plugin_vlog_func = args->callbacks->plugin_vlog;\n\n    plog(NULL, PLOG_NOTE, \"FUNC: openvpn_plugin_open_v3\");\n\n    /*\n     * Allocate our context\n     */\n    struct plugin_context *context = NULL;\n    context = (struct plugin_context *)calloc(1, sizeof(struct plugin_context));\n    if (!context)\n    {\n        goto error;\n    }\n\n    /* simple module argument parsing */\n    if ((args->argv[4]) && !args->argv[5])\n    {\n        context->authid = strdup(args->argv[1]);\n        if (!context->authid)\n        {\n            plog(context, PLOG_ERR, \"Out of memory\");\n            goto error;\n        }\n        context->test_deferred_auth = atoi_null0(args->argv[2]);\n        context->test_valid_user = strdup(args->argv[3]);\n        if (!context->test_valid_user)\n        {\n            plog(context, PLOG_ERR, \"Out of memory\");\n            goto error;\n        }\n        context->test_valid_pass = strdup(args->argv[4]);\n        if (!context->test_valid_pass)\n        {\n            plog(context, PLOG_ERR, \"Out of memory\");\n            goto error;\n        }\n    }\n    else\n    {\n        plog(context, PLOG_ERR, \"Too many arguments provided\");\n        goto error;\n    }\n\n    if (context->test_deferred_auth > 0)\n    {\n        plog(context, PLOG_NOTE, \"TEST_DEFERRED_AUTH %d\", context->test_deferred_auth);\n    }\n\n    /*\n     * Which callbacks to intercept.\n     */\n    ret->type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY);\n    ret->handle = (openvpn_plugin_handle_t *)context;\n\n    plog(context, PLOG_NOTE, \"initialization succeeded\");\n    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n\nerror:\n    plog(context, PLOG_NOTE, \"initialization failed\");\n    if (context)\n    {\n        free(context);\n    }\n    return OPENVPN_PLUGIN_FUNC_ERROR;\n}\n\nstatic bool\ndo_auth_user_pass(struct plugin_context *context, const char *username, const char *password)\n{\n    plog(context, PLOG_NOTE, \"expect_user=%s, received_user=%s, expect_passw=%s, received_passw=%s\",\n         np(context->test_valid_user), np(username), np(context->test_valid_pass), np(password));\n\n    if (context->test_valid_user && context->test_valid_pass)\n    {\n        if ((strcmp(context->test_valid_user, username) != 0)\n            || (strcmp(context->test_valid_pass, password) != 0))\n        {\n            plog(context, PLOG_ERR, \"User/Password auth result: FAIL\");\n            return false;\n        }\n        else\n        {\n            plog(context, PLOG_NOTE, \"User/Password auth result: PASS\");\n            return true;\n        }\n    }\n    return false;\n}\n\n\nstatic int\nauth_user_pass_verify(struct plugin_context *context, struct plugin_per_client_context *pcc,\n                      const char *argv[], const char *envp[])\n{\n    /* get username/password from envp string array */\n    const char *username = get_env(\"username\", envp);\n    const char *password = get_env(\"password\", envp);\n\n    if (!context->test_deferred_auth)\n    {\n        plog(context, PLOG_NOTE, \"Direct authentication\");\n        return do_auth_user_pass(context, username, password) ? OPENVPN_PLUGIN_FUNC_SUCCESS\n                                                              : OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /* get auth_control_file filename from envp string array*/\n    const char *auth_control_file = get_env(\"auth_control_file\", envp);\n    plog(context, PLOG_NOTE, \"auth_control_file=%s\", auth_control_file);\n\n    /* Authenticate asynchronously in n seconds */\n    if (!auth_control_file)\n    {\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /* we do not want to complicate our lives with having to wait()\n     * for child processes (so they are not zombiefied) *and* we MUST NOT\n     * fiddle with signal handlers (= shared with openvpn main), so\n     * we use double-fork() trick.\n     */\n\n    /* fork, sleep, succeed (no \"real\" auth done = always succeed) */\n    pid_t p1 = fork();\n    if (p1 < 0) /* Fork failed */\n    {\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n    if (p1 > 0) /* parent process */\n    {\n        waitpid(p1, NULL, 0);\n        return OPENVPN_PLUGIN_FUNC_DEFERRED;\n    }\n\n    /* first gen child process, fork() again and exit() right away */\n    pid_t p2 = fork();\n    if (p2 < 0)\n    {\n        plog(context, PLOG_ERR | PLOG_ERRNO, \"BACKGROUND: fork(2) failed\");\n        exit(1);\n    }\n\n    if (p2 != 0) /* new parent: exit right away */\n    {\n        exit(0);\n    }\n\n    /* (grand-)child process\n     *  - never call \"return\" now (would mess up openvpn)\n     *  - return status is communicated by file\n     *  - then exit()\n     */\n\n    /* do mighty complicated work that will really take time here... */\n    useconds_t wait_time = (useconds_t)context->test_deferred_auth * 1000;\n    plog(context, PLOG_NOTE, \"in async/deferred handler, usleep(%u)\", wait_time);\n    usleep(wait_time);\n\n    /* now signal success state to openvpn */\n    int fd = open(auth_control_file, O_WRONLY);\n    if (fd < 0)\n    {\n        plog(context, PLOG_ERR | PLOG_ERRNO, \"open('%s') failed\", auth_control_file);\n        exit(1);\n    }\n\n    char result[2] = \"0\\0\";\n    if (do_auth_user_pass(context, username, password))\n    {\n        result[0] = '1';\n    }\n\n    if (write(fd, result, 1) != 1)\n    {\n        plog(context, PLOG_ERR | PLOG_ERRNO, \"write to '%s' failed\", auth_control_file);\n    }\n    close(fd);\n\n    exit(0);\n}\n\n\nOPENVPN_EXPORT int\nopenvpn_plugin_func_v3(const int v3structver, struct openvpn_plugin_args_func_in const *args,\n                       struct openvpn_plugin_args_func_return *ret)\n{\n    if (v3structver < OPENVPN_PLUGIN_STRUCTVER_MIN)\n    {\n        fprintf(stderr, \"%s: this plugin is incompatible with the running version of OpenVPN\\n\",\n                MODULE);\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n    const char **argv = args->argv;\n    const char **envp = args->envp;\n    struct plugin_context *context = (struct plugin_context *)args->handle;\n    struct plugin_per_client_context *pcc =\n        (struct plugin_per_client_context *)args->per_client_context;\n    switch (args->type)\n    {\n        case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY:\n            plog(context, PLOG_NOTE, \"OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY\");\n            return auth_user_pass_verify(context, pcc, argv, envp);\n\n        default:\n            plog(context, PLOG_NOTE, \"OPENVPN_PLUGIN_?\");\n            return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n}\n\nOPENVPN_EXPORT void *\nopenvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)\n{\n    struct plugin_context *context = (struct plugin_context *)handle;\n    plog(context, PLOG_NOTE, \"FUNC: openvpn_plugin_client_constructor_v1\");\n    return calloc(1, sizeof(struct plugin_per_client_context));\n}\n\nOPENVPN_EXPORT void\nopenvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *per_client_context)\n{\n    struct plugin_context *context = (struct plugin_context *)handle;\n    plog(context, PLOG_NOTE, \"FUNC: openvpn_plugin_client_destructor_v1\");\n    free(per_client_context);\n}\n\nOPENVPN_EXPORT void\nopenvpn_plugin_close_v1(openvpn_plugin_handle_t handle)\n{\n    struct plugin_context *context = (struct plugin_context *)handle;\n    plog(context, PLOG_NOTE, \"FUNC: openvpn_plugin_close_v1\");\n    free(context);\n}\n"
  },
  {
    "path": "sample/sample-plugins/defer/winbuild",
    "content": "#\n# Build an OpenVPN plugin module on Windows/MinGW.\n# The argument should be the base name of the C source file\n# (without the .c).\n#\n\n# This directory is where we will look for openvpn-plugin.h\nINCLUDE=\"-I../../../build\"\n\nCC_FLAGS=\"-O2 -Wall\"\n\ngcc -DBUILD_DLL $CC_FLAGS $INCLUDE -c $1.c\ngcc --disable-stdcall-fixup -mdll -DBUILD_DLL -o junk.tmp -Wl,--base-file,base.tmp $1.o\nrm junk.tmp\ndlltool --dllname $1.dll --base-file base.tmp --output-exp temp.exp --input-def $1.def\nrm base.tmp\ngcc --enable-stdcall-fixup -mdll -DBUILD_DLL -o $1.dll $1.o -Wl,temp.exp\nrm temp.exp\n"
  },
  {
    "path": "sample/sample-plugins/keying-material-exporter-demo/README",
    "content": "OpenVPN plugin examples.                            Daniel Kubec <niel@rtfm.cz>\n\nExamples provided:\n\nkeyingmaterialexporter.c   -- Example based on TLS Keying Material Exporters over HTTP [RFC-5705]\n                              (openvpn/doc/keying-material-exporter.txt)\n\nThis example demonstrates authenticating a user over HTTP who have already\nestablished an OpenVPN connecting using the --keying-material-exporter\nfeature.\n\nRequires:\nOpenVPN RFC-5705 Support, OpenSSL >= 1.0.1\n\nFiles:\n  http-server.py -- Example HTTP Server listen  0.0.0.0:8080\n  http-client.py -- Example HTTP Client connect 10.8.0.1:8080 [GET /$SESSIONID]\n\n  server.ovpn    -- Example HTTP SSO VPN Server configuration\n  client.ovpn    -- Example HTTP SSO VPN Client configuration\n\n  keyingmaterialexporter.c,\n  keyingmaterialexporter.so  -- Example OpenVPN Client and Server plugin\n\nTo build:\n  ./build keyingmaterialexporter\n\nTo use in OpenVPN:\n\nEnter openvpn/sample/sample-plugins/keyingmaterialexporter directory\nand in separate terminals, start these four processes:\n\n$ openvpn --config ./server.ovpn\n$ openvpn --config ./client.ovpn\n$ ./http-server.py\n$ ./http-client.py\n\nTest:\n\nopenvpn --config ./server.ovpn\n##############################\n\nPLUGIN SSO: app session created\nPLUGIN_CALL: POST ./keyingmaterialexporter.so/PLUGIN_TLS_VERIFY status=0\nPLUGIN SSO: app session key:  a5885abc84d361803f58ede1ef9c0adf99e720cd\nPLUGIN SSO: app session file: /tmp/openvpn_sso_a5885abc84d361803f58ede1ef9c0adf99e720cd\nPLUGIN SSO: app session user: Test-Client\n\nopenvpn --config ./client.ovpn\n##############################\nPLUGIN SSO: app session created\nPLUGIN_CALL: POST ./keyingmaterialexporter.so/PLUGIN_TLS_VERIFY status=0\nPLUGIN SSO: app session key:  a5885abc84d361803f58ede1ef9c0adf99e720cd\nPLUGIN SSO: app session file: /tmp/openvpn_sso_user\nPLUGIN_CALL: POST ./keyingmaterialexporter.so/PLUGIN_TLS_FINAL status=0\n\nHTTP_SERVER:\nhttp-server.py\n################\nhttp server started\nsession file: /tmp/openvpn_sso_a5885abc84d361803f58ede1ef9c0adf99e720cd\n10.8.0.1 - - [02/Apr/2015 15:03:33] \"GET /a5885abc84d361803f58ede1ef9c0adf99e720cd HTTP/1.1\" 200 -\nsession user: Test-Client\nsession key:  a5885abc84d361803f58ede1ef9c0adf99e720cd\n\nHTTP_SERVER:\nhttp-client.py\n<html><body><h1>Greetings Test-Client. You are authorized</h1></body></html>\n"
  },
  {
    "path": "sample/sample-plugins/keying-material-exporter-demo/client.ovpn",
    "content": "tls-client\npull\n\nkeying-material-exporter \"EXPORTER_SSO_TEST\" 16\nreneg-sec 0\n\nca     ../../sample-keys/ca.crt\ncert   ../../sample-keys/client.crt\nkey    ../../sample-keys/client.key\n\nplugin ./keyingmaterialexporter.so\n\nremote 127.0.0.1 1194\nproto udp\ndev tun\nnobind\n\nverb 4\n"
  },
  {
    "path": "sample/sample-plugins/keying-material-exporter-demo/http-client.py",
    "content": "#!/usr/bin/python\nimport sys\nimport os\nimport httplib\n\nf = '/tmp/openvpn_sso_user'\nwith open (f, \"r\") as myfile:\n\tsession_key = myfile.read().replace('\\n', '')\n\nconn = httplib.HTTPConnection(\"10.8.0.1:8080\")\nconn.request(\"GET\", \"/\" + session_key)\nr1 = conn.getresponse()\n\nif r1.status == 200:\n\tbody = r1.read().rstrip()\n\tprint body\nelif r1.status == 404:\n\tprint \"Authentication failed\"\nelse:\n\tprint r1.status, r1.reason\n"
  },
  {
    "path": "sample/sample-plugins/keying-material-exporter-demo/http-server.py",
    "content": "#!/usr/bin/python\nfrom BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer\nimport os\n\nclass ExampleHTTPRequestHandler(BaseHTTPRequestHandler):\n\n\tdef do_GET(self):\n\t\tsession_key = os.path.basename(self.path)\n\t\tfile = '/tmp/openvpn_sso_' + session_key\n\t\tprint 'session file: ' + file\n\t\ttry:\n\t\t\tf = open(file)\n\t\t\t#send code 200 response\n\t\t\tself.send_response(200)\n\t\t\t#send header first\n\t\t\tself.send_header('Content-type','text-html')\n\t\t\tself.end_headers()\n\t\t\t#send file content to client\n\t\t\tuser = f.read().rstrip()\n\t\t\tprint 'session user: ' + user\n\t\t\tprint 'session key:  ' + session_key\n\t\t\tself.wfile.write('<html><body><h1>Greetings ' + user \\\n\t\t\t\t\t+ '. You are authorized' \\\n\t\t\t\t\t'</h1>' \\\n\t\t\t\t\t'</body></html>')\n\t\t\tf.close()\n\t\t\treturn\n\t\texcept IOError:\n\t\t\tself.send_error(404, 'authentication failed')\n\ndef run():\n\t#ip and port of servr\n\t#by default http server port is 80\n\tserver_address = ('0.0.0.0', 8080)\n\thttpd = HTTPServer(server_address, ExampleHTTPRequestHandler)\n\tprint('http server started')\n\thttpd.serve_forever()\n\tprint('http server stopped')\n\nif __name__ == '__main__':\n\trun()\n"
  },
  {
    "path": "sample/sample-plugins/keying-material-exporter-demo/keyingmaterialexporter.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * This file implements a Sample (HTTP) SSO OpenVPN plugin module\n *\n * See the README file for build instructions.\n */\n\n#include <stdio.h>\n#include <string.h>\n#include <strings.h>\n#include <stdlib.h>\n\n#include \"openvpn-plugin.h\"\n\n#ifndef MAXPATH\n#define MAXPATH 1024\n#endif\n\n#define ovpn_err(fmt, ...)  plugin->log(PLOG_ERR, \"SSO\", fmt, ##__VA_ARGS__)\n#define ovpn_dbg(fmt, ...)  plugin->log(PLOG_DEBUG, \"SSO\", fmt, ##__VA_ARGS__)\n#define ovpn_note(fmt, ...) plugin->log(PLOG_NOTE, \"SSO\", fmt, ##__VA_ARGS__)\n\nenum endpoint\n{\n    CLIENT = 1,\n    SERVER = 2\n};\n\nstruct plugin\n{\n    plugin_log_t log;\n    enum endpoint type;\n    int mask;\n};\n\nstruct session\n{\n    char user[48];\n    char key[48];\n};\n\n/*\n * Given an environmental variable name, search\n * the envp array for its value, returning it\n * if found or NULL otherwise.\n */\n\nstatic const char *\nget_env(const char *name, const char *envp[])\n{\n    if (envp)\n    {\n        const size_t namelen = strlen(name);\n        for (int i = 0; envp[i]; ++i)\n        {\n            if (!strncmp(envp[i], name, namelen))\n            {\n                const char *cp = envp[i] + namelen;\n                if (*cp == '=')\n                {\n                    return cp + 1;\n                }\n            }\n        }\n    }\n    return NULL;\n}\n\nOPENVPN_EXPORT int\nopenvpn_plugin_open_v3(const int version, struct openvpn_plugin_args_open_in const *args,\n                       struct openvpn_plugin_args_open_return *rv)\n{\n    struct plugin *plugin = calloc(1, sizeof(*plugin));\n\n    if (plugin == NULL)\n    {\n        printf(\"PLUGIN: allocating memory for context failed\\n\");\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    plugin->type = get_env(\"remote_1\", args->envp) ? CLIENT : SERVER;\n    plugin->log = args->callbacks->plugin_log;\n\n    plugin->mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_FINAL);\n    plugin->mask |= OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_VERIFY);\n\n    ovpn_note(\"vpn endpoint type=%s\", plugin->type == CLIENT ? \"client\" : \"server\");\n\n    rv->type_mask = plugin->mask;\n    rv->handle = (void *)plugin;\n\n    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n}\n\nstatic void\nsession_user_set(struct session *sess, X509 *x509)\n{\n    int fn_nid;\n    ASN1_OBJECT *fn;\n    ASN1_STRING *val;\n    X509_NAME *x509_name;\n    X509_NAME_ENTRY *ent;\n    const char *objbuf;\n\n    x509_name = X509_get_subject_name(x509);\n    int i, n = X509_NAME_entry_count(x509_name);\n    for (i = 0; i < n; ++i)\n    {\n        if (!(ent = X509_NAME_get_entry(x509_name, i)))\n        {\n            continue;\n        }\n        if (!(fn = X509_NAME_ENTRY_get_object(ent)))\n        {\n            continue;\n        }\n        if (!(val = X509_NAME_ENTRY_get_data(ent)))\n        {\n            continue;\n        }\n        if ((fn_nid = OBJ_obj2nid(fn)) == NID_undef)\n        {\n            continue;\n        }\n        if (!(objbuf = OBJ_nid2sn(fn_nid)))\n        {\n            continue;\n        }\n        unsigned char *buf = NULL;\n        if (ASN1_STRING_to_UTF8(&buf, val) < 0)\n        {\n            continue;\n        }\n\n        if (!strncasecmp(objbuf, \"CN\", 2))\n        {\n            strncpy(sess->user, (char *)buf, sizeof(sess->user) - 1);\n        }\n\n        OPENSSL_free(buf);\n    }\n}\n\nstatic int\ntls_verify(struct openvpn_plugin_args_func_in const *args)\n{\n    struct plugin *plugin = (struct plugin *)args->handle;\n    struct session *sess = (struct session *)args->per_client_context;\n\n    /* we store cert subject for the server end point only */\n    if (plugin->type != SERVER)\n    {\n        return OPENVPN_PLUGIN_FUNC_SUCCESS;\n    }\n\n    if (!args->current_cert)\n    {\n        ovpn_err(\"this example plugin requires client certificate\");\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    session_user_set(sess, args->current_cert);\n\n    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n}\n\nstatic void\nfile_store(char *file, char *content)\n{\n    FILE *f;\n    if (!(f = fopen(file, \"w+\")))\n    {\n        return;\n    }\n\n    fprintf(f, \"%s\", content);\n    fclose(f);\n}\n\nstatic void\nserver_store(struct openvpn_plugin_args_func_in const *args)\n{\n    struct plugin *plugin = (struct plugin *)args->handle;\n    struct session *sess = (struct session *)args->per_client_context;\n\n    char file[MAXPATH];\n    snprintf(file, sizeof(file) - 1, \"/tmp/openvpn_sso_%s\", sess->key);\n    ovpn_note(\"app session file: %s\", file);\n    file_store(file, sess->user);\n}\n\nstatic void\nclient_store(struct openvpn_plugin_args_func_in const *args)\n{\n    struct plugin *plugin = (struct plugin *)args->handle;\n    struct session *sess = (struct session *)args->per_client_context;\n\n    char *file = \"/tmp/openvpn_sso_user\";\n    ovpn_note(\"app session file: %s\", file);\n    file_store(file, sess->key);\n}\n\nstatic int\ntls_final(struct openvpn_plugin_args_func_in const *args,\n          struct openvpn_plugin_args_func_return *rv)\n{\n    struct plugin *plugin = (struct plugin *)args->handle;\n    struct session *sess = (struct session *)args->per_client_context;\n\n    const char *key;\n    if (!(key = get_env(\"exported_keying_material\", args->envp)))\n    {\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    strncpy(sess->key, key, sizeof(sess->key) - 1);\n    ovpn_note(\"app session key:  %s\", sess->key);\n\n    switch (plugin->type)\n    {\n        case SERVER:\n            server_store(args);\n            break;\n\n        case CLIENT:\n            client_store(args);\n            return OPENVPN_PLUGIN_FUNC_SUCCESS;\n    }\n\n    ovpn_note(\"app session user: %s\", sess->user);\n    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n}\n\nOPENVPN_EXPORT int\nopenvpn_plugin_func_v3(const int version, struct openvpn_plugin_args_func_in const *args,\n                       struct openvpn_plugin_args_func_return *rv)\n{\n    switch (args->type)\n    {\n        case OPENVPN_PLUGIN_TLS_VERIFY:\n            return tls_verify(args);\n\n        case OPENVPN_PLUGIN_TLS_FINAL:\n            return tls_final(args, rv);\n    }\n    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n}\n\nOPENVPN_EXPORT void *\nopenvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)\n{\n    struct plugin *plugin = (struct plugin *)handle;\n    struct session *sess = calloc(1, sizeof(*sess));\n\n    ovpn_note(\"app session created\");\n\n    return (void *)sess;\n}\n\nOPENVPN_EXPORT void\nopenvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *ctx)\n{\n    struct plugin *plugin = (struct plugin *)handle;\n    struct session *sess = (struct session *)ctx;\n\n    ovpn_note(\"app session key: %s\", sess->key);\n    ovpn_note(\"app session destroyed\");\n\n    free(sess);\n}\n\nOPENVPN_EXPORT void\nopenvpn_plugin_close_v1(openvpn_plugin_handle_t handle)\n{\n    struct plugin *plugin = (struct plugin *)handle;\n    free(plugin);\n}\n"
  },
  {
    "path": "sample/sample-plugins/keying-material-exporter-demo/server.ovpn",
    "content": "tls-server\nreneg-sec 0\n\nkeying-material-exporter \"EXPORTER_SSO_TEST\" 16\nduplicate-cn\n\nplugin ./keyingmaterialexporter.so\nca     ../../sample-keys/ca.crt\ncert   ../../sample-keys/server.crt\nkey    ../../sample-keys/server.key\ndh     none\n\nserver 10.8.0.0 255.255.255.0\nport 1194\nproto udp\ndev tun\n\nverb 4\n"
  },
  {
    "path": "sample/sample-plugins/log/log.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * This plugin is similar to simple.c, except it also logs extra information\n * to stdout for every plugin method called by OpenVPN.\n *\n * See the README file for build instructions.\n */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include \"openvpn-plugin.h\"\n\n/*\n * Our context, where we keep our state.\n */\nstruct plugin_context\n{\n    const char *username;\n    const char *password;\n};\n\n/*\n * Given an environmental variable name, search\n * the envp array for its value, returning it\n * if found or NULL otherwise.\n */\nstatic const char *\nget_env(const char *name, const char *envp[])\n{\n    if (envp)\n    {\n        const size_t namelen = strlen(name);\n        for (int i = 0; envp[i]; ++i)\n        {\n            if (!strncmp(envp[i], name, namelen))\n            {\n                const char *cp = envp[i] + namelen;\n                if (*cp == '=')\n                {\n                    return cp + 1;\n                }\n            }\n        }\n    }\n    return NULL;\n}\n\nOPENVPN_EXPORT openvpn_plugin_handle_t\nopenvpn_plugin_open_v1(unsigned int *type_mask, const char *argv[], const char *envp[])\n{\n    struct plugin_context *context;\n\n    /*\n     * Allocate our context\n     */\n    context = (struct plugin_context *)calloc(1, sizeof(struct plugin_context));\n    if (context == NULL)\n    {\n        printf(\"PLUGIN: allocating memory for context failed\\n\");\n        return NULL;\n    }\n\n    /*\n     * Set the username/password we will require.\n     */\n    context->username = \"foo\";\n    context->password = \"bar\";\n\n    /*\n     * Which callbacks to intercept.\n     */\n    *type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_UP) | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_DOWN)\n                 | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_ROUTE_UP)\n                 | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_IPCHANGE)\n                 | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_VERIFY)\n                 | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)\n                 | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT_V2)\n                 | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_DISCONNECT)\n                 | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_LEARN_ADDRESS)\n                 | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_FINAL);\n\n    return (openvpn_plugin_handle_t)context;\n}\n\nvoid\nshow(const int type, const char *argv[], const char *envp[])\n{\n    size_t i;\n    switch (type)\n    {\n        case OPENVPN_PLUGIN_UP:\n            printf(\"OPENVPN_PLUGIN_UP\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_DOWN:\n            printf(\"OPENVPN_PLUGIN_DOWN\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_ROUTE_UP:\n            printf(\"OPENVPN_PLUGIN_ROUTE_UP\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_IPCHANGE:\n            printf(\"OPENVPN_PLUGIN_IPCHANGE\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_TLS_VERIFY:\n            printf(\"OPENVPN_PLUGIN_TLS_VERIFY\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY:\n            printf(\"OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_CLIENT_CONNECT_V2:\n            printf(\"OPENVPN_PLUGIN_CLIENT_CONNECT_V2\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_CLIENT_DISCONNECT:\n            printf(\"OPENVPN_PLUGIN_CLIENT_DISCONNECT\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_LEARN_ADDRESS:\n            printf(\"OPENVPN_PLUGIN_LEARN_ADDRESS\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_TLS_FINAL:\n            printf(\"OPENVPN_PLUGIN_TLS_FINAL\\n\");\n            break;\n\n        default:\n            printf(\"OPENVPN_PLUGIN_?\\n\");\n            break;\n    }\n\n    printf(\"ARGV\\n\");\n    for (i = 0; argv[i] != NULL; ++i)\n    {\n        printf(\"%d '%s'\\n\", (int)i, argv[i]);\n    }\n\n    printf(\"ENVP\\n\");\n    for (i = 0; envp[i] != NULL; ++i)\n    {\n        printf(\"%d '%s'\\n\", (int)i, envp[i]);\n    }\n}\n\nOPENVPN_EXPORT int\nopenvpn_plugin_func_v1(openvpn_plugin_handle_t handle, const int type, const char *argv[],\n                       const char *envp[])\n{\n    struct plugin_context *context = (struct plugin_context *)handle;\n\n    show(type, argv, envp);\n\n    /* check entered username/password against what we require */\n    if (type == OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)\n    {\n        /* get username/password from envp string array */\n        const char *username = get_env(\"username\", envp);\n        const char *password = get_env(\"password\", envp);\n\n        if (username && !strcmp(username, context->username) && password\n            && !strcmp(password, context->password))\n        {\n            return OPENVPN_PLUGIN_FUNC_SUCCESS;\n        }\n        else\n        {\n            return OPENVPN_PLUGIN_FUNC_ERROR;\n        }\n    }\n    else\n    {\n        return OPENVPN_PLUGIN_FUNC_SUCCESS;\n    }\n}\n\nOPENVPN_EXPORT void\nopenvpn_plugin_close_v1(openvpn_plugin_handle_t handle)\n{\n    struct plugin_context *context = (struct plugin_context *)handle;\n    free(context);\n}\n"
  },
  {
    "path": "sample/sample-plugins/log/log_v3.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 David Sommerseth <dazo@eurephia.org>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * This plugin is similar to simple.c, except it also logs extra information\n * to stdout for every plugin method called by OpenVPN.  The only difference\n * between this (log_v3.c) and log.c is that this module uses the v3 plug-in\n * API.\n *\n * See the README file for build instructions.\n */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include \"openvpn-plugin.h\"\n\n/*\n * Our context, where we keep our state.\n */\nstruct plugin_context\n{\n    const char *username;\n    const char *password;\n};\n\n/*\n * Given an environmental variable name, search\n * the envp array for its value, returning it\n * if found or NULL otherwise.\n */\nstatic const char *\nget_env(const char *name, const char *envp[])\n{\n    if (envp)\n    {\n        const size_t namelen = strlen(name);\n        for (int i = 0; envp[i]; ++i)\n        {\n            if (!strncmp(envp[i], name, namelen))\n            {\n                const char *cp = envp[i] + namelen;\n                if (*cp == '=')\n                {\n                    return cp + 1;\n                }\n            }\n        }\n    }\n    return NULL;\n}\n\nOPENVPN_EXPORT int\nopenvpn_plugin_open_v3(const int v3structver, struct openvpn_plugin_args_open_in const *args,\n                       struct openvpn_plugin_args_open_return *ret)\n{\n    struct plugin_context *context = NULL;\n\n    /* Check that we are API compatible */\n    if (v3structver != OPENVPN_PLUGINv3_STRUCTVER)\n    {\n        printf(\"log_v3: ** ERROR ** Incompatible plug-in interface between this plug-in and OpenVPN\\n\");\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    if (args->ssl_api != SSLAPI_OPENSSL)\n    {\n        printf(\"This plug-in can only be used against OpenVPN with OpenSSL\\n\");\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /* Print some version information about the OpenVPN process using this plug-in */\n    printf(\"log_v3: OpenVPN %s  (Major: %i, Minor: %i, Patch: %s)\\n\", args->ovpn_version,\n           args->ovpn_version_major, args->ovpn_version_minor, args->ovpn_version_patch);\n\n    /*  Which callbacks to intercept.  */\n    ret->type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_UP)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_DOWN)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_ROUTE_UP)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_IPCHANGE)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_VERIFY)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT_V2)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_DISCONNECT)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_LEARN_ADDRESS)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_FINAL);\n\n\n    /* Allocate our context */\n    context = (struct plugin_context *)calloc(1, sizeof(struct plugin_context));\n    if (context == NULL)\n    {\n        printf(\"PLUGIN: allocating memory for context failed\\n\");\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /* Set the username/password we will require. */\n    context->username = \"foo\";\n    context->password = \"bar\";\n\n    /* Point the global context handle to our newly created context */\n    ret->handle = (void *)context;\n\n    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n}\n\nvoid\nshow(const int type, const char *argv[], const char *envp[])\n{\n    size_t i;\n    switch (type)\n    {\n        case OPENVPN_PLUGIN_UP:\n            printf(\"OPENVPN_PLUGIN_UP\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_DOWN:\n            printf(\"OPENVPN_PLUGIN_DOWN\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_ROUTE_UP:\n            printf(\"OPENVPN_PLUGIN_ROUTE_UP\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_IPCHANGE:\n            printf(\"OPENVPN_PLUGIN_IPCHANGE\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_TLS_VERIFY:\n            printf(\"OPENVPN_PLUGIN_TLS_VERIFY\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY:\n            printf(\"OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_CLIENT_CONNECT_V2:\n            printf(\"OPENVPN_PLUGIN_CLIENT_CONNECT_V2\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_CLIENT_DISCONNECT:\n            printf(\"OPENVPN_PLUGIN_CLIENT_DISCONNECT\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_LEARN_ADDRESS:\n            printf(\"OPENVPN_PLUGIN_LEARN_ADDRESS\\n\");\n            break;\n\n        case OPENVPN_PLUGIN_TLS_FINAL:\n            printf(\"OPENVPN_PLUGIN_TLS_FINAL\\n\");\n            break;\n\n        default:\n            printf(\"OPENVPN_PLUGIN_?\\n\");\n            break;\n    }\n\n    printf(\"ARGV\\n\");\n    for (i = 0; argv[i] != NULL; ++i)\n    {\n        printf(\"%d '%s'\\n\", (int)i, argv[i]);\n    }\n\n    printf(\"ENVP\\n\");\n    for (i = 0; envp[i] != NULL; ++i)\n    {\n        printf(\"%d '%s'\\n\", (int)i, envp[i]);\n    }\n}\n\nstatic void\nx509_print_info(X509 *x509crt)\n{\n    int i, n;\n    int fn_nid;\n    ASN1_OBJECT *fn;\n    ASN1_STRING *val;\n    X509_NAME *x509_name;\n    X509_NAME_ENTRY *ent;\n    const char *objbuf;\n    unsigned char *buf = NULL;\n\n    x509_name = X509_get_subject_name(x509crt);\n    n = X509_NAME_entry_count(x509_name);\n    for (i = 0; i < n; ++i)\n    {\n        ent = X509_NAME_get_entry(x509_name, i);\n        if (!ent)\n        {\n            continue;\n        }\n        fn = X509_NAME_ENTRY_get_object(ent);\n        if (!fn)\n        {\n            continue;\n        }\n        val = X509_NAME_ENTRY_get_data(ent);\n        if (!val)\n        {\n            continue;\n        }\n        fn_nid = OBJ_obj2nid(fn);\n        if (fn_nid == NID_undef)\n        {\n            continue;\n        }\n        objbuf = OBJ_nid2sn(fn_nid);\n        if (!objbuf)\n        {\n            continue;\n        }\n        if (ASN1_STRING_to_UTF8(&buf, val) < 0)\n        {\n            continue;\n        }\n\n        printf(\"X509 %s: %s\\n\", objbuf, (char *)buf);\n        OPENSSL_free(buf);\n    }\n}\n\n\nOPENVPN_EXPORT int\nopenvpn_plugin_func_v3(const int version, struct openvpn_plugin_args_func_in const *args,\n                       struct openvpn_plugin_args_func_return *retptr)\n{\n    struct plugin_context *context = (struct plugin_context *)args->handle;\n\n    printf(\"\\nopenvpn_plugin_func_v3() :::::>> \");\n    show(args->type, args->argv, args->envp);\n\n    /* Dump some X509 information if we're in the TLS_VERIFY phase */\n    if ((args->type == OPENVPN_PLUGIN_TLS_VERIFY) && args->current_cert)\n    {\n        printf(\"---- X509 Subject information ----\\n\");\n        printf(\"Certificate depth: %i\\n\", args->current_cert_depth);\n        x509_print_info(args->current_cert);\n        printf(\"----------------------------------\\n\");\n    }\n\n    /* check entered username/password against what we require */\n    if (args->type == OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)\n    {\n        /* get username/password from envp string array */\n        const char *username = get_env(\"username\", args->envp);\n        const char *password = get_env(\"password\", args->envp);\n\n        if (username && !strcmp(username, context->username) && password\n            && !strcmp(password, context->password))\n        {\n            return OPENVPN_PLUGIN_FUNC_SUCCESS;\n        }\n        else\n        {\n            return OPENVPN_PLUGIN_FUNC_ERROR;\n        }\n    }\n    else\n    {\n        return OPENVPN_PLUGIN_FUNC_SUCCESS;\n    }\n}\n\nOPENVPN_EXPORT void\nopenvpn_plugin_close_v1(openvpn_plugin_handle_t handle)\n{\n    struct plugin_context *context = (struct plugin_context *)handle;\n    free(context);\n}\n"
  },
  {
    "path": "sample/sample-plugins/log/winbuild",
    "content": "#\n# Build an OpenVPN plugin module on Windows/MinGW.\n# The argument should be the base name of the C source file\n# (without the .c).\n#\n\n# This directory is where we will look for openvpn-plugin.h\nINCLUDE=\"-I../../../include\"\n\nCC_FLAGS=\"-O2 -Wall\"\n\ngcc -DBUILD_DLL $CC_FLAGS $INCLUDE -c $1.c\ngcc --disable-stdcall-fixup -mdll -DBUILD_DLL -o junk.tmp -Wl,--base-file,base.tmp $1.o\nrm junk.tmp\ndlltool --dllname $1.dll --base-file base.tmp --output-exp temp.exp --input-def $1.def\nrm base.tmp\ngcc --enable-stdcall-fixup -mdll -DBUILD_DLL -o $1.dll $1.o -Wl,temp.exp\nrm temp.exp\n"
  },
  {
    "path": "sample/sample-plugins/simple/base64.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2017-2026 David Sommerseth <davids@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include \"openvpn-plugin.h\"\n\n#define PLUGIN_NAME \"base64.c\"\n\n/* Exported plug-in v3 API functions */\n/** Pointer to the OpenVPN log function.  See plugin_log() */\nplugin_log_t ovpn_log = NULL;\n/** Pointer to the OpenVPN vlog function. See plugin_vlog() */\nplugin_vlog_t ovpn_vlog = NULL;\n/** Pointer to the openvpn_base64_encode () function */\nplugin_base64_encode_t ovpn_base64_encode = NULL;\n/** Pointer to the openvpn_base64_decode () function */\nplugin_base64_decode_t ovpn_base64_decode = NULL;\n\n/**\n * Search the environment pointer for a specific env var name\n *\n * PLEASE NOTE! The result is not valid outside the local\n * scope of the calling function.  Once the calling function\n * returns, any returned pointers are invalid.\n *\n * @param name  String containing the env.var name to search for\n * @param envp  String array pointer to the environment variables\n *\n * @return Returns a pointer to the value in the environment variable\n *         table on successful match.  Otherwise NULL is returned\n *\n */\nstatic const char *\nget_env(const char *name, const char *envp[])\n{\n    if (envp)\n    {\n        const size_t namelen = strlen(name);\n        for (int i = 0; envp[i]; ++i)\n        {\n            if (!strncmp(envp[i], name, namelen))\n            {\n                const char *cp = envp[i] + namelen;\n                if (*cp == '=')\n                {\n                    return cp + 1;\n                }\n            }\n        }\n    }\n    return NULL;\n}\n\n\n/**\n * This function is called when OpenVPN loads the plug-in.\n * The purpose is to initialize the plug-in and tell OpenVPN\n * which plug-in hooks this plug-in wants to be involved in\n *\n * For the arguments, see the include/openvpn-plugin.h file\n * for details on the function parameters\n *\n * @param v3structver  An integer containing the API version of\n *                     the plug-in structs OpenVPN uses\n * @param args         A pointer to the argument struct for\n *                     information and features provided by\n *                     OpenVPN to the plug-in\n * @param ret          A pointer to the struct OpenVPN uses to\n *                     receive information back from the plug-in\n *\n * @return Must return OPENVPN_PLUGIN_FUNC_SUCCESS when everything\n *         completed successfully.  Otherwise it must be returned\n *         OPENVPN_PLUGIN_FUNC_ERROR, which will stop OpenVPN\n *         from running\n *\n */\nOPENVPN_EXPORT int\nopenvpn_plugin_open_v3(const int v3structver, struct openvpn_plugin_args_open_in const *args,\n                       struct openvpn_plugin_args_open_return *ret)\n{\n    /* Check that we are API compatible */\n    if (v3structver != OPENVPN_PLUGINv3_STRUCTVER)\n    {\n        printf(\"base64.c: ** ERROR ** Incompatible plug-in interface between this plug-in and OpenVPN\\n\");\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /*  Which callbacks to intercept.  */\n    ret->type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_VERIFY)\n                     | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT_V2);\n\n    /* we don't need a plug-in context in this example, but OpenVPN expects \"something\" */\n    ret->handle = calloc(1, 1);\n\n    /* Hook into the exported functions from OpenVPN */\n    ovpn_log = args->callbacks->plugin_log;\n    ovpn_vlog = args->callbacks->plugin_vlog;\n    ovpn_base64_encode = args->callbacks->plugin_base64_encode;\n    ovpn_base64_decode = args->callbacks->plugin_base64_decode;\n\n    /* Print some version information about the OpenVPN process using this plug-in */\n    ovpn_log(PLOG_NOTE, PLUGIN_NAME, \"OpenVPN %s  (Major: %i, Minor: %i, Patch: %s)\\n\",\n             args->ovpn_version, args->ovpn_version_major, args->ovpn_version_minor,\n             args->ovpn_version_patch);\n\n    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n}\n\n\n/**\n * This function is called by OpenVPN each time the OpenVPN reaches\n * a point where plug-in calls should happen.  It only happens for those\n * plug-in hooks enabled in openvpn_plugin_open_v3().\n *\n * For the arguments, see the include/openvpn-plugin.h file\n * for details on the function parameters\n *\n * @param handle   Pointer to the plug-in global context buffer, which\n *                 need to be released by this function\n * @param type     Type of the hook\n * @param argv     String array pointer to arguments for the hook\n * @param envp     String array pointer to current environment variables\n *\n * @return  Must return OPENVPN_PLUGIN_FUNC_SUCCESS or\n *          OPENVPN_PLUGIN_FUNC_DEFERRED on success.  Otherwise it\n *          should return OPENVPN_FUNC_ERROR, which will stop and reject\n *          the client session from progressing.\n *\n */\n\nOPENVPN_EXPORT int\nopenvpn_plugin_func_v1(openvpn_plugin_handle_t handle, const int type, const char *argv[],\n                       const char *envp[])\n{\n    if (type != OPENVPN_PLUGIN_TLS_VERIFY && type != OPENVPN_PLUGIN_CLIENT_CONNECT_V2)\n    {\n        ovpn_log(PLOG_ERR, PLUGIN_NAME, \"Unsupported plug-in hook call attempted\");\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /* get username/password from envp string array */\n    const char *clcert_cn = get_env(\"X509_0_CN\", envp);\n    if (!clcert_cn)\n    {\n        /* Ignore certificate checks not being a client certificate */\n        return OPENVPN_PLUGIN_FUNC_SUCCESS;\n    }\n\n    /* test the BASE64 encode function */\n    char *buf = NULL;\n    int r = ovpn_base64_encode(clcert_cn, (int)strlen(clcert_cn), &buf);\n    ovpn_log(PLOG_NOTE, PLUGIN_NAME, \"BASE64 encoded '%s' (return value %i):  '%s'\", clcert_cn, r,\n             buf);\n\n    /* test the BASE64 decode function */\n    char buf2[256] = { 0 };\n    r = ovpn_base64_decode(buf, &buf2, 255);\n    ovpn_log(PLOG_NOTE, PLUGIN_NAME, \"BASE64 decoded '%s' (return value %i):  '%s'\", buf, r, buf2);\n\n    /* Verify the result, and free the buffer allocated by ovpn_base64_encode() */\n    r = strcmp(clcert_cn, buf2);\n    free(buf);\n\n    return (r == 0) ? OPENVPN_PLUGIN_FUNC_SUCCESS : OPENVPN_PLUGIN_FUNC_ERROR;\n}\n\n\n/**\n * This cleans up the last part of the plug-in, allows it to\n * shut down cleanly and release the plug-in global context buffer\n *\n * @param handle   Pointer to the plug-in global context buffer, which\n *                 need to be released by this function\n */\nOPENVPN_EXPORT void\nopenvpn_plugin_close_v1(openvpn_plugin_handle_t handle)\n{\n    struct plugin_context *context = (struct plugin_context *)handle;\n    free(context);\n}\n"
  },
  {
    "path": "sample/sample-plugins/simple/simple.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * This file implements a simple OpenVPN plugin module which\n * will examine the username/password provided by a client,\n * and make an accept/deny determination.  Will run\n * on Windows or *nix.\n *\n * See the README file for build instructions.\n */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include \"openvpn-plugin.h\"\n\n/*\n * Our context, where we keep our state.\n */\nstruct plugin_context\n{\n    const char *username;\n    const char *password;\n};\n\n/*\n * Given an environmental variable name, search\n * the envp array for its value, returning it\n * if found or NULL otherwise.\n */\nstatic const char *\nget_env(const char *name, const char *envp[])\n{\n    if (envp)\n    {\n        const size_t namelen = strlen(name);\n        for (int i = 0; envp[i]; ++i)\n        {\n            if (!strncmp(envp[i], name, namelen))\n            {\n                const char *cp = envp[i] + namelen;\n                if (*cp == '=')\n                {\n                    return cp + 1;\n                }\n            }\n        }\n    }\n    return NULL;\n}\n\nOPENVPN_EXPORT openvpn_plugin_handle_t\nopenvpn_plugin_open_v1(unsigned int *type_mask, const char *argv[], const char *envp[])\n{\n    struct plugin_context *context;\n\n    /*\n     * Allocate our context\n     */\n    context = (struct plugin_context *)calloc(1, sizeof(struct plugin_context));\n    if (context == NULL)\n    {\n        printf(\"PLUGIN: allocating memory for context failed\\n\");\n        return NULL;\n    }\n\n    /*\n     * Set the username/password we will require.\n     */\n    context->username = \"foo\";\n    context->password = \"bar\";\n\n    /*\n     * We are only interested in intercepting the\n     * --auth-user-pass-verify callback.\n     */\n    *type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY);\n\n    return (openvpn_plugin_handle_t)context;\n}\n\nOPENVPN_EXPORT int\nopenvpn_plugin_func_v1(openvpn_plugin_handle_t handle, const int type, const char *argv[],\n                       const char *envp[])\n{\n    struct plugin_context *context = (struct plugin_context *)handle;\n\n    /* get username/password from envp string array */\n    const char *username = get_env(\"username\", envp);\n    const char *password = get_env(\"password\", envp);\n\n    /* check entered username/password against what we require */\n    if (username && !strcmp(username, context->username) && password\n        && !strcmp(password, context->password))\n    {\n        return OPENVPN_PLUGIN_FUNC_SUCCESS;\n    }\n    else\n    {\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n}\n\nOPENVPN_EXPORT void\nopenvpn_plugin_close_v1(openvpn_plugin_handle_t handle)\n{\n    struct plugin_context *context = (struct plugin_context *)handle;\n    free(context);\n}\n"
  },
  {
    "path": "sample/sample-plugins/simple/simple.def",
    "content": "LIBRARY   OpenVPN_PLUGIN_SAMPLE\nDESCRIPTION \"Sample OpenVPN plug-in module.\"\nEXPORTS\n   openvpn_plugin_open_v1   @1\n   openvpn_plugin_func_v1   @2\n   openvpn_plugin_close_v1  @3\n"
  },
  {
    "path": "sample/sample-plugins/simple/winbuild",
    "content": "#\n# Build an OpenVPN plugin module on Windows/MinGW.\n# The argument should be the base name of the C source file\n# (without the .c).\n#\n\n# This directory is where we will look for openvpn-plugin.h\nINCLUDE=\"-I../../../include\"\n\nCC_FLAGS=\"-O2 -Wall\"\n\ngcc -DBUILD_DLL $CC_FLAGS $INCLUDE -c $1.c\ngcc --disable-stdcall-fixup -mdll -DBUILD_DLL -o junk.tmp -Wl,--base-file,base.tmp $1.o\nrm junk.tmp\ndlltool --dllname $1.dll --base-file base.tmp --output-exp temp.exp --input-def $1.def\nrm base.tmp\ngcc --enable-stdcall-fixup -mdll -DBUILD_DLL -o $1.dll $1.o -Wl,temp.exp\nrm temp.exp\n"
  },
  {
    "path": "sample/sample-scripts/auth-pam.pl",
    "content": "#!/usr/bin/perl -t\n\n# OpenVPN PAM AUTHENTICATON\n#   This script can be used to add PAM-based authentication\n#   to OpenVPN 2.0.  The OpenVPN client must provide\n#   a username/password, using the --auth-user-pass directive.\n#   The OpenVPN server should specify --auth-user-pass-verify\n#   with this script as the argument and the 'via-file' method\n#   specified.  The server can also optionally specify\n#   --client-cert-not-required and/or --username-as-common-name.\n\n# SCRIPT OPERATION\n#   Return success or failure status based on whether or not a\n#   given username/password authenticates using PAM.\n#   Caller should write username/password as two lines in a file\n#   which is passed to this script as a command line argument.\n\n# CAVEATS\n#   * Requires Authen::PAM module, which may also\n#     require the pam-devel package.\n#   * May need to be run as root in order to\n#     access username/password file.\n\n# NOTES\n#   * This script is provided mostly as a demonstration of the\n#     --auth-user-pass-verify script capability in OpenVPN.\n#     For real world usage, see the auth-pam module in the plugin\n#     folder.\n\nuse Authen::PAM;\nuse POSIX;\n\n# This \"conversation function\" will pass\n# $password to PAM when it asks for it.\n\nsub my_conv_func {\n    my @res;\n    while ( @_ ) {\n        my $code = shift;\n        my $msg = shift;\n        my $ans = \"\";\n\n        $ans = $password if $msg =~ /[Pp]assword/;\n\n        push @res, (PAM_SUCCESS(),$ans);\n    }\n    push @res, PAM_SUCCESS();\n    return @res;\n}\n\n# Identify service type to PAM\n$service = \"login\";\n\n# Get username/password from file\n\nif ($ARG = shift @ARGV) {\n    if (!open (UPFILE, \"<$ARG\")) {\n\tprint \"Could not open username/password file: $ARG\\n\";\n\texit 1;\n    }\n} else {\n    print \"No username/password file specified on command line\\n\";\n    exit 1;\n}\n\n$username = <UPFILE>;\n$password = <UPFILE>;\n\nif (!$username || !$password) {\n    print \"Username/password not found in file: $ARG\\n\";\n    exit 1;\n}\n\nchomp $username;\nchomp $password;\n\nclose (UPFILE);\n\n# Initialize PAM object\n\nif (!ref($pamh = new Authen::PAM($service, $username, \\&my_conv_func))) {\n    print \"Authen::PAM init failed\\n\";\n    exit 1;\n}\n\n# Authenticate with PAM\n\n$res = $pamh->pam_authenticate;\n\n# Return success or failure\n\nif ($res == PAM_SUCCESS()) {\n    exit 0;\n} else {\n    print \"Auth '$username' failed, PAM said: \", $pamh->pam_strerror($res), \"\\n\";\n    exit 1;\n}\n"
  },
  {
    "path": "sample/sample-scripts/bridge-start",
    "content": "#!/bin/sh\n\n#################################\n# Set up Ethernet bridge on Linux\n# Requires: bridge-utils\n#################################\n\n# Define Bridge Interface\nbr=\"br0\"\n\n# Define list of TAP interfaces to be bridged,\n# for example tap=\"tap0 tap1 tap2\".\ntap=\"tap0\"\n\n# Define physical ethernet interface to be bridged\n# with TAP interface(s) above.\neth=\"eth0\"\neth_ip=\"192.168.8.4\"\neth_netmask=\"255.255.255.0\"\neth_broadcast=\"192.168.8.255\"\n\nfor t in $tap; do\n    openvpn --mktun --dev $t\ndone\n\nbrctl addbr $br\nbrctl addif $br $eth\n\nfor t in $tap; do\n    brctl addif $br $t\ndone\n\nfor t in $tap; do\n    ifconfig $t 0.0.0.0 promisc up\ndone\n\nifconfig $eth 0.0.0.0 promisc up\n\nifconfig $br $eth_ip netmask $eth_netmask broadcast $eth_broadcast\n"
  },
  {
    "path": "sample/sample-scripts/bridge-stop",
    "content": "#!/bin/sh\n\n####################################\n# Tear Down Ethernet bridge on Linux\n####################################\n\n# Define Bridge Interface\nbr=\"br0\"\n\n# Define list of TAP interfaces to be bridged together\ntap=\"tap0\"\n\nifconfig $br down\nbrctl delbr $br\n\nfor t in $tap; do\n    openvpn --rmtun --dev $t\ndone\n"
  },
  {
    "path": "sample/sample-scripts/totpauth.py",
    "content": "#! /usr/bin/python3\n# Copyright (c) 2021 OpenVPN Inc <sales@openvpn.net>\n# Copyright (c) 2021 Arne Schwabe <arne@rfc2549.org>\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in all\n# copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport sys\nimport os\nfrom base64 import standard_b64decode\n\nimport pyotp\n\n# Example script demonstrating how to use the auth-pending API in\n# OpenVPN. This script is provided under MIT license to allow easy\n# modification for other purposes.\n#\n# This needs support of crtext support on the client (e.g. OpenVPN for Android)\n# See also the management-notes.txt file for more information about the auth pending\n# protocol\n#\n# To use this script add the following lines in the openvpn config\n\n# client-crresponse /path/to/totpauth.py\n# auth-user-pass-verify /path/to/totpauth.py via-file\n# auth-user-pass-optional\n# auth-gen-token\n\n# Note that this script does NOT verify username/password\n# It is only meant for querying additional 2FA when certificates are\n# used to authenticate\n\n\n# For this demo script we hardcode the TOTP secrets in a simple dictionary.\nsecrets = {\"Test-Client\": \"OS6JDNRK2BNUPQVX\",\n           \"Client-2\": \"IXWEMP7SK2QWSHTG\"}\n\n\ndef main():\n    # Get common name and script type from environment\n    script_type = os.environ['script_type']\n    cn = os.environ['common_name']\n\n    if script_type == 'user-pass-verify':\n        # signal text based challenge response\n        if cn in secrets:\n            extra = \"CR_TEXT:E,R:Please enter your TOTP code!\"\n            write_auth_pending(300, 'crtext', extra)\n\n            # Signal authentication being deferred\n            sys.exit(2)\n        else:\n            # For unknown CN we report failure. Change to 0\n            # to allow CNs without secret to auth without 2FA\n            sys.exit(1)\n\n    elif script_type == 'client-crresponse':\n        response = None\n\n        # Read the crresponse from the argument file\n        # and convert it into text. A failure because of bad user\n        # input (e.g. invalid base64) will make the script throw\n        # an error and make OpenVPN return AUTH_FAILED\n        with open(sys.argv[1], 'r') as crinput:\n            response = crinput.read()\n            response = standard_b64decode(response)\n            response = response.decode().strip()\n\n        if cn not in secrets:\n            write_auth_control(1)\n            return\n\n        totp = pyotp.TOTP(secrets[cn])\n\n        # Check if the code is valid (and also allow code +/-1)\n        if totp.verify(response, valid_window=1):\n            write_auth_control(1)\n        else:\n            write_auth_control(0)\n    else:\n        print(f\"Unknown script type {script_type}\")\n        sys.exit(1)\n\n\ndef write_auth_control(status):\n    with open(os.environ['auth_control_file'], 'w') as auth_control:\n        auth_control.write(\"%d\" % status)\n\n\ndef write_auth_pending(timeout, method, extra):\n    with open(os.environ['auth_pending_file'], 'w') as auth_pending:\n        auth_pending.write(\"%d\\n%s\\n%s\" % (timeout, method, extra))\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "sample/sample-scripts/ucn.pl",
    "content": "#!/usr/bin/perl -t\n\n# OpenVPN --auth-user-pass-verify script.\n# Only authenticate if username equals common_name.\n# In OpenVPN config file:\n#   auth-user-pass-verify ./ucn.pl via-env\n\n$username = $ENV{'username'};\n$common_name = $ENV{'common_name'};\n\nexit !(length($username) > 0 && length($common_name) > 0 && $username eq $common_name);\n"
  },
  {
    "path": "sample/sample-scripts/verify-cn",
    "content": "#!/usr/bin/perl\n\n# verify-cn -- a sample OpenVPN tls-verify script\n#\n# Return 0 if cn matches the common name component of\n# subject, 1 otherwise.\n#\n# For example in OpenVPN, you could use the directive:\n#\n#   tls-verify \"./verify-cn /etc/openvpn/allowed_clients\"\n#\n# This would cause the connection to be dropped unless\n# the client common name is listed on a line in the\n# allowed_clients file.\n\ndie \"usage: verify-cn cnfile certificate_depth subject\" if (@ARGV != 3);\n\n# Parse out arguments:\n#   cnfile -- The file containing the list of common names, one per\n#             line, which the client is required to have,\n#             taken from the argument to the tls-verify directive\n#             in the OpenVPN config file.\n#             The file can have blank lines and comment lines that begin\n#             with the # character.\n#   depth  -- The current certificate chain depth.  In a typical\n#             bi-level chain, the root certificate will be at level\n#             1 and the client certificate will be at level 0.\n#             This script will be called separately for each level.\n#   x509   -- the X509 subject string as extracted by OpenVPN from\n#             the client's provided certificate.\n($cnfile, $depth, $x509) = @ARGV;\n\nif ($depth == 0) {\n    # If depth is zero, we know that this is the final\n    # certificate in the chain (i.e. the client certificate),\n    # and the one we are interested in examining.\n    # If so, parse out the common name substring in\n    # the X509 subject string.\n\n    if ($x509 =~ / CN=([^,]+)/) {\n        $cn = $1;\n\t# Accept the connection if the X509 common name\n\t# string matches the passed cn argument.\n\topen(FH, '<', $cnfile) or exit 1; # can't open, nobody authenticates!\n        while (defined($line = <FH>)) {\n\t    if ($line !~ /^[[:space:]]*(#|$)/o) {\n\t\tchop($line);\n\t\tif ($line eq $cn) {\n\t\t    exit 0;\n\t\t}\n\t    }\n\t}\n\tclose(FH);\n    }\n\n    # Authentication failed -- Either we could not parse\n    # the X509 subject string, or the common name in the\n    # subject string didn't match the passed cn argument.\n    exit 1;\n}\n\n# If depth is nonzero, tell OpenVPN to continue processing\n# the certificate chain.\nexit 0;\n"
  },
  {
    "path": "src/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2006-2012 Alon Bar-Lev <alon.barlev@gmail.com>\n#\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n\nSUBDIRS = compat openvpn openvpnmsica openvpnserv plugins tapctl\n"
  },
  {
    "path": "src/compat/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2006-2012 Alon Bar-Lev <alon.barlev@gmail.com>\n#\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n\nnoinst_LTLIBRARIES = libcompat.la\n\nlibcompat_la_SOURCES = \\\n\tcompat.h \\\n\tcompat-dirname.c \\\n\tcompat-basename.c \\\n\tcompat-gettimeofday.c \\\n\tcompat-daemon.c \\\n\tcompat-strsep.c"
  },
  {
    "path": "src/compat/compat-basename.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2011 - David Sommerseth <davids@redhat.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#ifndef HAVE_BASENAME\n\n#include \"compat.h\"\n#include <string.h>\n\n/* Modified version based on glibc-2.14.1 by Roland McGrath <roland@gnu.org>\n * This version is extended to handle both / and \\ in path names\n */\nchar *\nbasename(char *filename)\n{\n    char *p = strrchr(filename, '/');\n    if (!p)\n    {\n        /* If NULL, check for \\ instead ... might be Windows a path */\n        p = strrchr(filename, '\\\\');\n    }\n    return p ? p + 1 : (char *)filename;\n}\n\n#endif /* HAVE_BASENAME */\n"
  },
  {
    "path": "src/compat/compat-daemon.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2011 - David Sommerseth <davids@redhat.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#ifndef HAVE_DAEMON\n\n#ifdef HAVE_UNISTD_H\n#include <unistd.h>\n#endif\n\n#include <stdlib.h>\n\n#ifdef HAVE_SYS_TYPES_H\n#include <sys/types.h>\n#endif\n\n#ifdef HAVE_SYS_STAT_H\n#include <sys/stat.h>\n#endif\n\n#ifdef HAVE_FCNTL_H\n#include <fcntl.h>\n#endif\n\n#include <errno.h>\n\nint\ndaemon(int nochdir, int noclose)\n{\n#if defined(HAVE_FORK) && defined(HAVE_SETSID)\n    switch (fork())\n    {\n        case -1:\n            return (-1);\n\n        case 0:\n            break;\n\n        default:\n            exit(0);\n    }\n\n    if (setsid() == -1)\n    {\n        return (-1);\n    }\n\n    if (!nochdir)\n    {\n        if (chdir(\"/\") == -1)\n        {\n            return (-1);\n        }\n    }\n\n    if (!noclose)\n    {\n#if defined(HAVE_DUP) && defined(HAVE_DUP2)\n        int fd;\n        if ((fd = open(\"/dev/null\", O_RDWR, 0)) != -1)\n        {\n            dup2(fd, 0);\n            dup2(fd, 1);\n            dup2(fd, 2);\n            if (fd > 2)\n            {\n                close(fd);\n            }\n        }\n#endif\n    }\n\n    return 0;\n#else  /* if defined(HAVE_FORK) && defined(HAVE_SETSID) */\n    (void)nochdir;\n    (void)noclose;\n    errno = EFAULT;\n    return -1;\n#endif /* if defined(HAVE_FORK) && defined(HAVE_SETSID) */\n}\n\n#endif /* ifndef HAVE_DAEMON */\n"
  },
  {
    "path": "src/compat/compat-dirname.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2011 - David Sommerseth <davids@redhat.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n\n#ifndef HAVE_DIRNAME\n\n#include \"compat.h\"\n#include <string.h>\n\n/* Unoptimised version of glibc memrchr().\n * This is considered fast enough, as only this compat\n * version of dirname() depends on it.\n */\nstatic const char *\n__memrchr(const char *str, int c, size_t n)\n{\n    const char *end = str;\n\n    end += n - 1; /* Go to the end of the string */\n    while (end >= str)\n    {\n        if (c == *end)\n        {\n            return end;\n        }\n        else\n        {\n            end--;\n        }\n    }\n    return NULL;\n}\n\n/* Modified version based on glibc-2.14.1 by Ulrich Drepper <drepper@akkadia.org>\n * This version is extended to handle both / and \\ in path names.\n */\nchar *\ndirname(char *path)\n{\n    static const char dot[] = \".\";\n    char *last_slash;\n    char separator = '/';\n\n    /* Find last '/'.  */\n    last_slash = path != NULL ? strrchr(path, '/') : NULL;\n    /* If NULL, check for \\ instead ... might be Windows a path */\n    if (!last_slash)\n    {\n        last_slash = path != NULL ? strrchr(path, '\\\\') : NULL;\n        separator = last_slash ? '\\\\' : '/'; /* Change the separator if \\ was found */\n    }\n\n    if (last_slash != NULL && last_slash != path && last_slash[1] == '\\0')\n    {\n        /* Determine whether all remaining characters are slashes.  */\n        char *runp;\n\n        for (runp = last_slash; runp != path; --runp)\n        {\n            if (runp[-1] != separator)\n            {\n                break;\n            }\n        }\n\n        /* The '/' is the last character, we have to look further.  */\n        if (runp != path)\n        {\n            last_slash = (char *)__memrchr(path, separator, runp - path);\n        }\n    }\n\n    if (last_slash != NULL)\n    {\n        /* Determine whether all remaining characters are slashes.  */\n        char *runp;\n\n        for (runp = last_slash; runp != path; --runp)\n        {\n            if (runp[-1] != separator)\n            {\n                break;\n            }\n        }\n\n        /* Terminate the path.  */\n        if (runp == path)\n        {\n            /* The last slash is the first character in the string.  We have to\n             * return \"/\".  As a special case we have to return \"//\" if there\n             * are exactly two slashes at the beginning of the string.  See\n             * XBD 4.10 Path Name Resolution for more information.  */\n            if (last_slash == path + 1)\n            {\n                ++last_slash;\n            }\n            else\n            {\n                last_slash = path + 1;\n            }\n        }\n        else\n        {\n            last_slash = runp;\n        }\n\n        last_slash[0] = '\\0';\n    }\n    else\n    {\n        /* This assignment is ill-designed but the XPG specs require to\n         * return a string containing \".\" in any case no directory part is\n         * found and so a static and constant string is required.  */\n        path = (char *)dot;\n    }\n\n    return path;\n}\n\n#endif /* HAVE_DIRNAME */\n"
  },
  {
    "path": "src/compat/compat-gettimeofday.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#ifndef HAVE_GETTIMEOFDAY\n\n#include \"compat.h\"\n\n#ifdef _WIN32\n/*\n * NOTICE: mingw has much faster gettimeofday!\n * autoconf will set HAVE_GETTIMEOFDAY\n */\n\n#include <windows.h>\n#include <time.h>\n\nstatic time_t gtc_base = 0;\nstatic DWORD gtc_last = 0;\nstatic time_t last_sec = 0;\nstatic unsigned int last_msec = 0;\nstatic int bt_last = 0;\n\nstatic void\ngettimeofday_calibrate(void)\n{\n    const time_t t = time(NULL);\n    const DWORD gtc = GetTickCount();\n    gtc_base = t - gtc / 1000;\n    gtc_last = gtc;\n}\n\n/*\n * Rewritten by JY for OpenVPN 2.1, after I realized that\n * QueryPerformanceCounter takes nearly 2 orders of magnitude\n * more processor cycles than GetTickCount.\n */\nint\ngettimeofday(struct timeval *tv, void *tz)\n{\n    const DWORD gtc = GetTickCount();\n    int bt = 0;\n    time_t sec;\n    unsigned int msec;\n    const int backtrack_hold_seconds = 10;\n\n    (void)tz;\n\n    /* recalibrate at the dreaded 49.7 day mark */\n    if (!gtc_base || gtc < gtc_last)\n    {\n        gettimeofday_calibrate();\n    }\n    gtc_last = gtc;\n\n    sec = gtc_base + gtc / 1000;\n    msec = gtc % 1000;\n\n    if (sec == last_sec)\n    {\n        if (msec < last_msec)\n        {\n            msec = last_msec;\n            bt = 1;\n        }\n    }\n    else if (sec < last_sec)\n    {\n        /* We try to dampen out backtracks of less than backtrack_hold_seconds.\n         * Larger backtracks will be passed through and dealt with by the\n         * TIME_BACKTRACK_PROTECTION code */\n        if (sec > last_sec - backtrack_hold_seconds)\n        {\n            sec = last_sec;\n            msec = last_msec;\n        }\n        bt = 1;\n    }\n\n    last_sec = sec;\n    tv->tv_sec = (long)sec;\n    tv->tv_usec = (last_msec = msec) * 1000;\n\n    if (bt && !bt_last)\n    {\n        gettimeofday_calibrate();\n    }\n    bt_last = bt;\n\n    return 0;\n}\n\n#else /* ifdef _WIN32 */\n\n#include <time.h>\n\nint\ngettimeofday(struct timeval *tv, void *tz)\n{\n    (void)tz;\n    tv->tv_sec = time(NULL);\n    tv->tv_usec = 0;\n    return 0;\n}\n\n#endif /* _WIN32 */\n\n#endif /* HAVE_GETTIMEOFDAY */\n"
  },
  {
    "path": "src/compat/compat-strsep.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2019-2026 Arne Schwabe <arne@rfc2549.org>\n *  Copyright (C) 1992-2019 Free Software Foundation, Inc.\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#ifndef HAVE_STRSEP\n#include <string.h>\n\n/*\n * Modified version based on the glibc\n */\nchar *\nstrsep(char **stringp, const char *delim)\n{\n    char *begin, *end;\n    begin = *stringp;\n    if (begin == NULL)\n    {\n        return NULL;\n    }\n    /* Find the end of the token.  */\n    end = begin + strcspn(begin, delim);\n    if (*end)\n    {\n        /* Terminate the token and set *STRINGP past NUL character.  */\n        *end++ = '\\0';\n        *stringp = end;\n    }\n    else\n    {\n        /* No more delimiters; this is the last token.  */\n        *stringp = NULL;\n    }\n    return begin;\n}\n#endif /* ifndef HAVE_STRSEP */\n"
  },
  {
    "path": "src/compat/compat.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2011 - David Sommerseth <davids@redhat.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef COMPAT_H\n#define COMPAT_H\n\n#ifdef _WIN32\n#include <winsock2.h>\n#include <ws2tcpip.h>\n#endif\n\n#ifdef HAVE_SYS_TIME_H\n#include <sys/time.h>\n#endif\n\n#ifdef HAVE_SYS_SOCKET_H\n#include <sys/socket.h>\n#endif\n\n#ifndef HAVE_DIRNAME\nchar *dirname(char *str);\n\n#endif /* HAVE_DIRNAME */\n\n#ifndef HAVE_BASENAME\nchar *basename(char *str);\n\n#endif /* HAVE_BASENAME */\n\n#ifndef HAVE_GETTIMEOFDAY\nint gettimeofday(struct timeval *tv, void *tz);\n\n#endif\n\n#ifndef HAVE_DAEMON\nint daemon(int nochdir, int noclose);\n\n#endif\n\n#ifndef HAVE_STRSEP\nchar *strsep(char **stringp, const char *delim);\n\n#endif\n\n#endif /* COMPAT_H */\n"
  },
  {
    "path": "src/openvpn/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2006-2012 Alon Bar-Lev <alon.barlev@gmail.com>\n#\n\ninclude $(top_srcdir)/ltrc.inc\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n\nEXTRA_DIST = \\\n\topenvpn.manifest\n\nAM_CPPFLAGS = \\\n\t-I$(top_srcdir)/include \\\n\t-I$(top_srcdir)/src/compat\n\nAM_CFLAGS = \\\n\t$(TAP_CFLAGS) \\\n\t$(OPTIONAL_CRYPTO_CFLAGS) \\\n\t$(OPTIONAL_LIBCAPNG_CFLAGS) \\\n\t$(OPTIONAL_LIBNL_GENL_CFLAGS) \\\n\t$(OPTIONAL_LZO_CFLAGS) \\\n\t$(OPTIONAL_LZ4_CFLAGS) \\\n\t$(OPTIONAL_SYSTEMD_CFLAGS) \\\n\t$(OPTIONAL_PKCS11_HELPER_CFLAGS) \\\n\t$(OPTIONAL_INOTIFY_CFLAGS) \\\n\t-DPLUGIN_LIBDIR=\\\"${plugindir}\\\" \\\n\t-DDEFAULT_DNS_UPDOWN=\\\"${scriptdir}/dns-updown\\\"\n\nif WIN32\n# we want unicode entry point but not the macro\nAM_CFLAGS += -municode -UUNICODE\nendif\n\nsbin_PROGRAMS = openvpn\n\nopenvpn_SOURCES = \\\n\targv.c argv.h \\\n\tauth_token.c auth_token.h \\\n\tbase64.c base64.h \\\n\tbasic.h \\\n\tbuffer.c buffer.h \\\n\tcirc_list.h \\\n\tclinat.c clinat.h \\\n\tcommon.h \\\n\tcomp.c comp.h compstub.c \\\n\tcomp-lz4.c comp-lz4.h \\\n\tcrypto.c crypto.h crypto_backend.h \\\n\tcrypto_openssl.c crypto_openssl.h \\\n\tcrypto_mbedtls_legacy.c crypto_mbedtls_legacy.h \\\n\tcrypto_mbedtls.c crypto_mbedtls.h \\\n\tcrypto_epoch.c crypto_epoch.h \\\n\tdco.c dco.h dco_internal.h \\\n\tdco_freebsd.c dco_freebsd.h \\\n\tdco_linux.c dco_linux.h \\\n\tdco_win.c dco_win.h \\\n\tdhcp.c dhcp.h \\\n\tdns.c dns.h \\\n\tdomain_helper.h \\\n\tenv_set.c env_set.h \\\n\terrlevel.h \\\n\terror.c error.h \\\n\tevent.c event.h \\\n\tfdmisc.c fdmisc.h \\\n\tforward.c forward.h \\\n\tfragment.c fragment.h \\\n\tgremlin.c gremlin.h \\\n\thelper.c helper.h \\\n\thttpdigest.c httpdigest.h \\\n\tlladdr.c lladdr.h \\\n\tinit.c init.h \\\n\tinteger.h \\\n\tinterval.c interval.h \\\n\tlist.c list.h \\\n\tlzo.c lzo.h \\\n\tmanage.c manage.h \\\n\tmbuf.c mbuf.h \\\n\tmemdbg.h \\\n\tmisc.c misc.h \\\n\tovpn_dco_freebsd.h \\\n\tovpn_dco_linux.h \\\n\tovpn_dco_win.h \\\n\tplatform.c platform.h \\\n\tconsole.c console.h console_builtin.c console_systemd.c \\\n\tmbedtls_compat.h \\\n\tmroute.c mroute.h \\\n\tmss.c mss.h \\\n\tmtcp.c mtcp.h \\\n\tmtu.c mtu.h \\\n\tmudp.c mudp.h \\\n\tmulti.c multi.h \\\n\tmulti_io.c multi_io.h \\\n\tnetworking_freebsd.c \\\n\tnetworking_iproute2.c networking_iproute2.h \\\n\tnetworking_sitnl.c networking_sitnl.h \\\n\tnetworking.h \\\n\tocc.c occ.h \\\n\topenssl_compat.h \\\n\tpkcs11.c pkcs11.h pkcs11_backend.h \\\n\tpkcs11_openssl.c \\\n\tpkcs11_mbedtls.c \\\n\topenvpn.c openvpn.h \\\n\toptions.c options.h \\\n\toptions_util.c options_util.h \\\n\toptions_parse.c \\\n\totime.c otime.h \\\n\tpacket_id.c packet_id.h \\\n\tping.c ping.h \\\n\tplugin.c plugin.h \\\n\tpool.c pool.h \\\n\tproto.c proto.h \\\n\tproxy.c proxy.h \\\n\tps.c ps.h \\\n\tpush.c push_util.c push.h \\\n\tpushlist.h \\\n\treflect_filter.c reflect_filter.h \\\n\treliable.c reliable.h \\\n\troute.c route.h \\\n\trun_command.c run_command.h \\\n\tschedule.c schedule.h \\\n\tsession_id.c session_id.h \\\n\tshaper.c shaper.h \\\n\tsig.c sig.h \\\n\tsocket.c socket.h \\\n\tsocket_util.c socket_util.h \\\n\tsocks.c socks.h \\\n\tssl.c ssl.h  ssl_backend.h \\\n\tssl_openssl.c ssl_openssl.h \\\n\tssl_mbedtls.c ssl_mbedtls.h \\\n\tssl_ncp.c ssl_ncp.h \\\n\tssl_pkt.c ssl_pkt.h \\\n\tssl_util.c ssl_util.h \\\n\tssl_common.h \\\n\tssl_verify.c ssl_verify.h ssl_verify_backend.h \\\n\tssl_verify_openssl.c ssl_verify_openssl.h \\\n\tssl_verify_mbedtls.c ssl_verify_mbedtls.h \\\n\tstatus.c status.h \\\n\tsyshead.h \\\n\ttls_crypt.c tls_crypt.h \\\n\ttun.c tun.h \\\n\ttun_afunix.c tun_afunix.h \\\n\tvlan.c vlan.h \\\n\txkey_provider.c xkey_common.h \\\n\txkey_helper.c \\\n\twin32.h win32.c \\\n\twin32-util.h win32-util.c \\\n\tcryptoapi.h cryptoapi.c\nopenvpn_LDADD = \\\n\t$(top_builddir)/src/compat/libcompat.la \\\n\t$(SOCKETS_LIBS) \\\n\t$(OPTIONAL_LIBCAPNG_LIBS) \\\n\t$(OPTIONAL_LIBNL_GENL_LIBS) \\\n\t$(OPTIONAL_LZO_LIBS) \\\n\t$(OPTIONAL_LZ4_LIBS) \\\n\t$(OPTIONAL_PKCS11_HELPER_LIBS) \\\n\t$(OPTIONAL_CRYPTO_LIBS) \\\n\t$(OPTIONAL_SELINUX_LIBS) \\\n\t$(OPTIONAL_SYSTEMD_LIBS) \\\n\t$(OPTIONAL_DL_LIBS) \\\n\t$(OPTIONAL_INOTIFY_LIBS)\nif WIN32\nopenvpn_SOURCES += openvpn_win32_resources.rc wfp_block.c wfp_block.h\nopenvpn_LDADD += -lgdi32 -lws2_32 -lwininet -lcrypt32 -liphlpapi -lwinmm -lfwpuclnt -lrpcrt4 -lncrypt -lsetupapi -lbcrypt\nendif\n"
  },
  {
    "path": "src/openvpn/argv.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n *\n *\n *  A printf-like function (that only recognizes a subset of standard printf\n *  format operators) that prints arguments to an argv list instead\n *  of a standard string.  This is used to build up argv arrays for passing\n *  to execve.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"argv.h\"\n#include \"integer.h\"\n#include \"env_set.h\"\n#include \"options.h\"\n\n/**\n *  Resizes the list of arguments struct argv can carry.  This resize\n *  operation will only increase the size, never decrease the size.\n *\n *  @param a       Valid pointer to a struct argv to resize\n *  @param newcap  size_t with the new size of the argument list.\n */\nstatic void\nargv_extend(struct argv *a, const size_t newcap)\n{\n    if (newcap > a->capacity)\n    {\n        char **newargv;\n        size_t i;\n        ALLOC_ARRAY_CLEAR_GC(newargv, char *, newcap, &a->gc);\n        for (i = 0; i < a->argc; ++i)\n        {\n            newargv[i] = a->argv[i];\n        }\n        a->argv = newargv;\n        a->capacity = newcap;\n    }\n}\n\n/**\n *  Initialise an already allocated struct argv.\n *  It is expected that the input argument is a valid pointer.\n *\n *  @param a  Pointer to a struct argv to initialise\n */\nstatic void\nargv_init(struct argv *a)\n{\n    a->capacity = 0;\n    a->argc = 0;\n    a->argv = NULL;\n    a->gc = gc_new();\n    argv_extend(a, 8);\n}\n\n/**\n *  Allocates a new struct argv and ensures it is initialised.\n *  Note that it does not return a pointer, but a struct argv directly.\n *\n *  @returns Returns an initialised and empty struct argv.\n */\nstruct argv\nargv_new(void)\n{\n    struct argv ret;\n    argv_init(&ret);\n    return ret;\n}\n\n/**\n *  Frees all memory allocations allocated by the struct argv\n *  related functions.\n *\n *  @param a  Valid pointer to a struct argv to release memory from\n */\nvoid\nargv_free(struct argv *a)\n{\n    gc_free(&a->gc);\n}\n\n/**\n *  Resets the struct argv to an initial state.  No memory buffers\n *  will be released by this call.\n *\n *  @param a      Valid pointer to a struct argv to resize\n */\nstatic void\nargv_reset(struct argv *a)\n{\n    if (a->argc)\n    {\n        size_t i;\n        for (i = 0; i < a->argc; ++i)\n        {\n            a->argv[i] = NULL;\n        }\n        a->argc = 0;\n    }\n}\n\n/**\n *  Extends an existing struct argv to carry minimum 'add' number\n *  of new arguments.  This builds on argv_extend(), which ensures the\n *  new size will only be higher than the current capacity.\n *\n *  The new size is also calculated based on the result of adjust_power_of_2().\n *  This approach ensures that the list does grow bulks and only when the\n *  current limit is reached.\n *\n *  @param a    Valid pointer to the struct argv to extend\n *  @param add  size_t with the number of elements to add.\n *\n */\nstatic void\nargv_grow(struct argv *a, const size_t add)\n{\n    const size_t newargc = a->argc + add + 1;\n    ASSERT(newargc > a->argc);\n    argv_extend(a, adjust_power_of_2(newargc));\n}\n\n/**\n *  Appends a string to to the list of arguments stored in a struct argv\n *  This will ensure the list size in struct argv has the needed capacity to\n *  store the value.\n *\n *  @param a    struct argv where to append the new string value\n *  @param str  Pointer to string to append.  The provided string *MUST* have\n *              been malloc()ed or NULL.\n */\nstatic void\nargv_append(struct argv *a, char *str)\n{\n    argv_grow(a, 1);\n    a->argv[a->argc++] = str;\n}\n\n/**\n *  Clones a struct argv with all the contents to a new allocated struct argv.\n *  If 'headroom' is larger than 0, it will create a head-room in front of the\n *  values being copied from the source input.\n *\n *\n *  @param source    Valid pointer to the source struct argv to clone.  It may\n *                   be NULL.\n *  @param headroom  Number of slots to leave empty in front of the slots\n *                   copied from the source.\n *\n *  @returns Returns a new struct argv containing a copy of the source\n *           struct argv, with the given headroom in front of the copy.\n *\n */\nstatic struct argv\nargv_clone(const struct argv *source, const size_t headroom)\n{\n    struct argv r;\n    argv_init(&r);\n\n    for (size_t i = 0; i < headroom; ++i)\n    {\n        argv_append(&r, NULL);\n    }\n    if (source)\n    {\n        for (size_t i = 0; i < source->argc; ++i)\n        {\n            argv_append(&r, string_alloc(source->argv[i], &r.gc));\n        }\n    }\n    return r;\n}\n\n/**\n *  Inserts an argument string in front of all other argument slots.\n *\n *  @param  a     Valid pointer to the struct argv to insert the argument into\n *  @param  head  Pointer to the string with the argument to insert\n *\n *  @returns Returns a new struct argv with the inserted argument in front\n */\nstruct argv\nargv_insert_head(const struct argv *a, const char *head)\n{\n    struct argv r;\n    r = argv_clone(a, 1);\n    r.argv[0] = string_alloc(head, &r.gc);\n    return r;\n}\n\n/**\n *  Generate a single string with all the arguments in a struct argv\n *  concatenated.\n *\n *  @param a     Valid pointer to the struct argv with the arguments to list\n *  @param gc    Pointer to a struct gc_arena managed buffer\n *  @param flags Flags passed to the print_argv() function.\n *\n *  @returns Returns a string generated by print_argv() with all the arguments\n *           concatenated.  If the argument count is 0, it will return an empty\n *           string.  The return string is allocated in the gc_arena managed\n *           buffer.  If the gc_arena pointer is NULL, the returned string\n *           must be free()d explicitly to avoid memory leaks.\n */\nconst char *\nargv_str(const struct argv *a, struct gc_arena *gc, const unsigned int flags)\n{\n    return print_argv((const char **)a->argv, gc, flags);\n}\n\n/**\n *  Write the arguments stored in a struct argv via the msg() command.\n *\n *  @param msglevel  Integer with the message level used by msg().\n *  @param a         Valid pointer to the struct argv with the arguments to write.\n */\nvoid\nargv_msg(const msglvl_t msglevel, const struct argv *a)\n{\n    struct gc_arena gc = gc_new();\n    msg(msglevel, \"%s\", argv_str(a, &gc, 0));\n    gc_free(&gc);\n}\n\n/**\n *  Similar to argv_msg() but prefixes the messages being written with a\n *  given string.\n *\n *  @param msglevel  Integer with the message level used by msg().\n *  @param a         Valid pointer to the struct argv with the arguments to write\n *  @param prefix    Valid pointer to the prefix string\n *\n */\nvoid\nargv_msg_prefix(const msglvl_t msglevel, const struct argv *a, const char *prefix)\n{\n    struct gc_arena gc = gc_new();\n    msg(msglevel, \"%s: %s\", prefix, argv_str(a, &gc, 0));\n    gc_free(&gc);\n}\n\n/**\n *  Prepares argv format string for further processing\n *\n *  Individual argument must be separated by space. Ignores leading and\n *  trailing spaces.  Consecutive spaces count as one. Returns prepared\n *  format string, with space replaced by delim and adds the number of\n *  arguments to the count parameter.\n *\n *  @param format  Pointer to a the format string to process\n *  @param delim   Char with the delimiter to use\n *  @param count   size_t pointer used to return the number of\n *                 tokens (argument slots) found in the format string.\n *  @param gc      Pointer to a gc_arena managed buffer.\n *\n *  @returns Returns a parsed format string, together with the\n *           number of tokens parts found (via *count).  The result string\n *           is allocated within the gc_arena managed buffer.  If the\n *           gc_arena pointer is NULL, the returned string must be explicitly\n *           free()d to avoid memory leaks.\n */\nstatic char *\nargv_prep_format(const char *format, const char delim, size_t *count, struct gc_arena *gc)\n{\n    if (format == NULL)\n    {\n        return NULL;\n    }\n\n    bool in_token = false;\n    char *f = gc_malloc(strlen(format) + 1, true, gc);\n    for (size_t i = 0, j = 0; i < strlen(format); i++)\n    {\n        if (format[i] == ' ')\n        {\n            in_token = false;\n            continue;\n        }\n\n        if (!in_token)\n        {\n            (*count)++;\n\n            /*\n             * We don't add any delimiter to the output string if\n             * the string is empty; the resulting format string\n             * will never start with a delimiter.\n             */\n            if (j > 0) /* Has anything been written to the output string? */\n            {\n                f[j++] = delim;\n            }\n        }\n\n        f[j++] = format[i];\n        in_token = true;\n    }\n\n    return f;\n}\n\n/**\n *  Create a struct argv based on a format string\n *\n *  Instead of parsing the format string ourselves place delimiters via\n *  argv_prep_format() before we let libc's printf() do the parsing.\n *  Then split the resulting string at the injected delimiters.\n *\n *  @param argres  Valid pointer to a struct argv where the resulting parsed\n *                 arguments, based on the format string.\n *  @param format  Char string with a printf() compliant format string\n *  @param arglist A va_list with the arguments to be consumed by the format\n *                 string\n *\n *  @returns Returns true if the parsing and processing was successfully.  If\n *           the resulting number of arguments does not match the expected\n *           number of arguments (based on the format string), it is\n *           considered a failure, which returns false.  This can happen if\n *           the ASCII Group Separator (GS - 0x1D) is put into the arguments\n *           list or format string.\n */\nstatic bool\nargv_printf_arglist(struct argv *argres, const char *format, va_list arglist)\n{\n    const char delim = 0x1D; /* ASCII Group Separator (GS) */\n    bool res = false;\n\n    /*\n     * Prepare a format string which will be used by vsnprintf() later on.\n     *\n     * This means all space separators in the input format string will be\n     * replaced by the GS (0x1D), so we can split this up again after the\n     * the vsnprintf() call into individual arguments again which will be\n     * saved in the struct argv.\n     *\n     */\n    size_t argc = argres->argc;\n    char *f = argv_prep_format(format, delim, &argc, &argres->gc);\n    if (f == NULL)\n    {\n        goto out;\n    }\n\n    /*\n     * Determine minimum buffer size.\n     *\n     * With C99, vsnprintf(NULL, 0, ...) will return the number of bytes\n     * it would have written, had the buffer been large enough.\n     */\n    va_list tmplist;\n    va_copy(tmplist, arglist);\n    int len = vsnprintf(NULL, 0, f, tmplist);\n    va_end(tmplist);\n    if (len < 0)\n    {\n        goto out;\n    }\n\n    /*\n     *  Do the actual vsnprintf() operation, which expands the format\n     *  string with the provided arguments.\n     */\n    int size = len + 1;\n    char *buf = gc_malloc(size, false, &argres->gc);\n    len = vsnprintf(buf, size, f, arglist);\n    if (len < 0 || len >= size)\n    {\n        goto out;\n    }\n\n    /*\n     * Split the string at the GS (0x1D) delimiters and put each elemen\n     * into the struct argv being returned to the caller.\n     */\n    char *end = strchr(buf, delim);\n    while (end)\n    {\n        *end = '\\0';\n        argv_append(argres, buf);\n        buf = end + 1;\n        end = strchr(buf, delim);\n    }\n    argv_append(argres, buf);\n\n    if (argres->argc != argc)\n    {\n        /* Someone snuck in a GS (0x1D), fail gracefully */\n        argv_reset(argres);\n        goto out;\n    }\n    res = true;\n\nout:\n    return res;\n}\n\n/**\n *  printf() variant which populates a struct argv.  It processes the\n *  format string with the provided arguments.  For each space separator found\n *  in the format string, a new argument will be added to the resulting\n *  struct argv.\n *\n *  This will always reset and ensure the result is based on a pristine\n *  struct argv.\n *\n *  @param argres  Valid pointer to a struct argv where the result will be put.\n *  @param format  printf() compliant format string.\n *\n *  @returns Returns true if the parsing was successful.  See\n *           argv_printf_arglist() for more details.  The parsed result will\n *           be put into argres.\n */\nbool\nargv_printf(struct argv *argres, const char *format, ...)\n{\n    va_list arglist;\n    va_start(arglist, format);\n\n    argv_reset(argres);\n    bool res = argv_printf_arglist(argres, format, arglist);\n    va_end(arglist);\n    return res;\n}\n\n/**\n *  printf() inspired argv concatenation.  Adds arguments to an existing\n *  struct argv and populets the argument slots based on the printf() based\n *  format string.\n *\n *  @param argres  Valid pointer to a struct argv where the result will be put.\n *  @param format  printf() compliant format string.\n *\n *  @returns Returns true if the parsing was successful.  See\n *           argv_printf_arglist() for more details.  The parsed result will\n *           be put into argres.\n */\nbool\nargv_printf_cat(struct argv *argres, const char *format, ...)\n{\n    va_list arglist;\n    va_start(arglist, format);\n    bool res = argv_printf_arglist(argres, format, arglist);\n    va_end(arglist);\n    return res;\n}\n\n/**\n *  Parses a command string, tokenizes it and puts each element into a separate\n *  struct argv argument slot.\n *\n *  @param argres  Valid pointer to a struct argv where the parsed result\n *                 will be found.\n *  @param cmdstr  Char based string to parse\n *\n */\nvoid\nargv_parse_cmd(struct argv *argres, const char *cmdstr)\n{\n    argv_reset(argres);\n\n    char *parms[MAX_PARMS + 1] = { 0 };\n    int nparms =\n        parse_line(cmdstr, parms, MAX_PARMS, \"SCRIPT-ARGV\", 0, D_ARGV_PARSE_CMD, &argres->gc);\n    if (nparms)\n    {\n        int i;\n        for (i = 0; i < nparms; ++i)\n        {\n            argv_append(argres, parms[i]);\n        }\n    }\n    else\n    {\n        argv_append(argres, string_alloc(cmdstr, &argres->gc));\n    }\n}\n"
  },
  {
    "path": "src/openvpn/argv.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n *\n *\n *  A printf-like function (that only recognizes a subset of standard printf\n *  format operators) that prints arguments to an argv list instead\n *  of a standard string.  This is used to build up argv arrays for passing\n *  to execve.\n */\n\n#ifndef ARGV_H\n#define ARGV_H\n\n#include \"buffer.h\"\n\nstruct argv\n{\n    struct gc_arena gc;\n    size_t capacity;\n    size_t argc;\n    char **argv;\n};\n\nstruct argv argv_new(void);\n\nvoid argv_free(struct argv *a);\n\nconst char *argv_str(const struct argv *a, struct gc_arena *gc, const unsigned int flags);\n\nstruct argv argv_insert_head(const struct argv *a, const char *head);\n\nvoid argv_msg(const msglvl_t msglevel, const struct argv *a);\n\nvoid argv_msg_prefix(const msglvl_t msglevel, const struct argv *a, const char *prefix);\n\nvoid argv_parse_cmd(struct argv *a, const char *s);\n\nbool argv_printf(struct argv *a, const char *format, ...)\n#ifdef __GNUC__\n#if __USE_MINGW_ANSI_STDIO\n    __attribute__((format(gnu_printf, 2, 3)))\n#else\n    __attribute__((format(__printf__, 2, 3)))\n#endif\n#endif\n    ;\n\nbool argv_printf_cat(struct argv *a, const char *format, ...)\n#ifdef __GNUC__\n#if __USE_MINGW_ANSI_STDIO\n    __attribute__((format(gnu_printf, 2, 3)))\n#else\n    __attribute__((format(__printf__, 2, 3)))\n#endif\n#endif\n    ;\n\n#endif /* ifndef ARGV_H */\n"
  },
  {
    "path": "src/openvpn/auth_token.c",
    "content": "#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"base64.h\"\n#include \"buffer.h\"\n#include \"crypto.h\"\n#include \"openvpn.h\"\n#include \"ssl_common.h\"\n#include \"auth_token.h\"\n#include \"push.h\"\n#include \"integer.h\"\n#include \"ssl.h\"\n#include \"ssl_verify.h\"\n#include <inttypes.h>\n\nconst char *auth_token_pem_name = \"OpenVPN auth-token server key\";\n\n#define AUTH_TOKEN_SESSION_ID_LEN        12\n#define AUTH_TOKEN_SESSION_ID_BASE64_LEN (AUTH_TOKEN_SESSION_ID_LEN * 8 / 6)\n\n#if AUTH_TOKEN_SESSION_ID_LEN % 3\n#error AUTH_TOKEN_SESSION_ID_LEN needs to be multiple a 3\n#endif\n\n/* Size of the data of the token (not b64 encoded and without prefix) */\n#define TOKEN_DATA_LEN (2 * sizeof(int64_t) + AUTH_TOKEN_SESSION_ID_LEN + 32)\n\nstatic struct key_type\nauth_token_kt(void)\n{\n    return create_kt(\"none\", \"SHA256\", \"auth-gen-token\");\n}\n\nvoid\nadd_session_token_env(struct tls_session *session, struct tls_multi *multi,\n                      const struct user_pass *up)\n{\n    if (!multi->opt.auth_token_generate)\n    {\n        return;\n    }\n\n    int auth_token_state_flags = session->key[KS_PRIMARY].auth_token_state_flags;\n\n    const char *state;\n\n    if (!is_auth_token(up->password))\n    {\n        state = \"Initial\";\n    }\n    else if (auth_token_state_flags & AUTH_TOKEN_HMAC_OK)\n    {\n        switch (auth_token_state_flags & (AUTH_TOKEN_VALID_EMPTYUSER | AUTH_TOKEN_EXPIRED))\n        {\n            case 0:\n                state = \"Authenticated\";\n                break;\n\n            case AUTH_TOKEN_EXPIRED:\n                state = \"Expired\";\n                break;\n\n            case AUTH_TOKEN_VALID_EMPTYUSER:\n                state = \"AuthenticatedEmptyUser\";\n                break;\n\n            case AUTH_TOKEN_VALID_EMPTYUSER | AUTH_TOKEN_EXPIRED:\n                state = \"ExpiredEmptyUser\";\n                break;\n\n            default:\n                /* Silence compiler warning, all four possible combinations are covered */\n                ASSERT(0);\n        }\n    }\n    else\n    {\n        state = \"Invalid\";\n    }\n\n    setenv_str(session->opt->es, \"session_state\", state);\n\n    /* We had a valid session id before */\n    const char *session_id_source;\n    if (auth_token_state_flags & AUTH_TOKEN_HMAC_OK\n        && !(auth_token_state_flags & AUTH_TOKEN_EXPIRED))\n    {\n        session_id_source = up->password;\n    }\n    else\n    {\n        /*\n         * No session before, generate a new session token for the new session\n         */\n        if (!multi->auth_token_initial)\n        {\n            generate_auth_token(up, multi);\n        }\n        session_id_source = multi->auth_token_initial;\n    }\n    /*\n     * In the auth-token the auth token is already base64 encoded\n     * and being a multiple of 4 ensure that it a multiple of bytes\n     * in the encoding\n     */\n\n    char session_id[AUTH_TOKEN_SESSION_ID_LEN * 2] = { 0 };\n    memcpy(session_id, session_id_source + strlen(SESSION_ID_PREFIX),\n           AUTH_TOKEN_SESSION_ID_LEN * 8 / 6);\n\n    setenv_str(session->opt->es, \"session_id\", session_id);\n}\n\nvoid\nauth_token_write_server_key_file(const char *filename)\n{\n    write_pem_key_file(filename, auth_token_pem_name);\n}\n\nvoid\nauth_token_init_secret(struct key_ctx *key_ctx, const char *key_file, bool key_inline)\n{\n    struct key_type kt = auth_token_kt();\n\n    struct buffer server_secret_key = alloc_buf(2048);\n\n    bool key_loaded = false;\n    if (key_file)\n    {\n        key_loaded =\n            read_pem_key_file(&server_secret_key, auth_token_pem_name, key_file, key_inline);\n    }\n    else\n    {\n        key_loaded = generate_ephemeral_key(&server_secret_key, auth_token_pem_name);\n    }\n\n    if (!key_loaded)\n    {\n        msg(M_FATAL, \"ERROR: Cannot load auth-token secret\");\n    }\n\n    struct key key;\n\n    if (!buf_read(&server_secret_key, &key, sizeof(key)))\n    {\n        msg(M_FATAL, \"ERROR: not enough data in auth-token secret\");\n    }\n\n    struct key_parameters key_params;\n    key_parameters_from_key(&key_params, &key);\n    init_key_ctx(key_ctx, &key_params, &kt, false, \"auth-token secret\");\n\n    free_buf(&server_secret_key);\n}\n\nvoid\ngenerate_auth_token(const struct user_pass *up, struct tls_multi *multi)\n{\n    struct gc_arena gc = gc_new();\n\n    int64_t timestamp = htonll((uint64_t)now);\n    int64_t initial_timestamp = timestamp;\n\n    hmac_ctx_t *ctx = multi->opt.auth_token_key.hmac;\n    ASSERT(hmac_ctx_size(ctx) == 256 / 8);\n\n    uint8_t sessid[AUTH_TOKEN_SESSION_ID_LEN];\n\n    if (multi->auth_token_initial)\n    {\n        /* Just enough space to fit 8 bytes+ 1 extra to decode a non-padded\n         * base64 string (multiple of 3 bytes). 9 bytes => 12 bytes base64\n         * bytes\n         */\n        char old_tstamp_decode[9];\n\n        /* Make a copy of the string to not modify multi->auth_token_initial */\n        char *initial_token_copy = string_alloc(multi->auth_token_initial, &gc);\n\n        char *old_sessid = initial_token_copy + strlen(SESSION_ID_PREFIX);\n        char *old_tstamp_initial = old_sessid + AUTH_TOKEN_SESSION_ID_LEN * 8 / 6;\n\n        /*\n         * We null terminate the old token just after the session ID to let\n         * our base64 decode function only decode the session ID\n         */\n        old_tstamp_initial[12] = '\\0';\n        ASSERT(openvpn_base64_decode(old_tstamp_initial, old_tstamp_decode, 9) == 9);\n\n        memcpy(&initial_timestamp, &old_tstamp_decode, sizeof(initial_timestamp));\n\n        old_tstamp_initial[0] = '\\0';\n        ASSERT(openvpn_base64_decode(old_sessid, sessid, AUTH_TOKEN_SESSION_ID_LEN)\n               == AUTH_TOKEN_SESSION_ID_LEN);\n    }\n    else if (!rand_bytes(sessid, AUTH_TOKEN_SESSION_ID_LEN))\n    {\n        msg(M_FATAL, \"Failed to get enough randomness for \"\n                     \"authentication token\");\n    }\n\n    /* Calculate the HMAC */\n    /* We enforce up->username to be \\0 terminated in ssl.c.. Allowing username\n     * with \\0 in them is asking for troubles in so many ways anyway that we\n     * ignore that corner case here\n     */\n    uint8_t hmac_output[256 / 8];\n\n    hmac_ctx_reset(ctx);\n\n    /*\n     * If the token was only valid for the empty user, also generate\n     * a new token with the empty username since we do not want to loose\n     * the information that the username cannot be trusted\n     */\n    struct key_state *ks = &multi->session[TM_ACTIVE].key[KS_PRIMARY];\n    if (ks->auth_token_state_flags & AUTH_TOKEN_VALID_EMPTYUSER)\n    {\n        hmac_ctx_update(ctx, (const uint8_t *)\"\", 0);\n    }\n    else\n    {\n        hmac_ctx_update(ctx, (uint8_t *)up->username, (int)strlen(up->username));\n    }\n    hmac_ctx_update(ctx, sessid, AUTH_TOKEN_SESSION_ID_LEN);\n    hmac_ctx_update(ctx, (uint8_t *)&initial_timestamp, sizeof(initial_timestamp));\n    hmac_ctx_update(ctx, (uint8_t *)&timestamp, sizeof(timestamp));\n    hmac_ctx_final(ctx, hmac_output);\n\n    /* Construct the unencoded session token */\n    struct buffer token =\n        alloc_buf_gc(2 * sizeof(uint64_t) + AUTH_TOKEN_SESSION_ID_LEN + 256 / 8, &gc);\n\n    ASSERT(buf_write(&token, sessid, sizeof(sessid)));\n    ASSERT(buf_write(&token, &initial_timestamp, sizeof(initial_timestamp)));\n    ASSERT(buf_write(&token, &timestamp, sizeof(timestamp)));\n    ASSERT(buf_write(&token, hmac_output, sizeof(hmac_output)));\n\n    char *b64output = NULL;\n    openvpn_base64_encode(BPTR(&token), BLEN(&token), &b64output);\n\n    struct buffer session_token =\n        alloc_buf_gc(strlen(SESSION_ID_PREFIX) + strlen(b64output) + 1, &gc);\n\n    ASSERT(buf_write(&session_token, SESSION_ID_PREFIX, strlen(SESSION_ID_PREFIX)));\n    ASSERT(buf_write(&session_token, b64output, (int)strlen(b64output)));\n    ASSERT(buf_write_u8(&session_token, 0));\n\n    free(b64output);\n\n    /* free the auth-token if defined, we will replace it with a new one */\n    free(multi->auth_token);\n    multi->auth_token = strdup((char *)BPTR(&session_token));\n\n    dmsg(D_SHOW_KEYS, \"Generated token for client: %s (%s)\", multi->auth_token, up->username);\n\n    if (!multi->auth_token_initial)\n    {\n        /*\n         * Save the initial auth token to continue using the same session ID\n         * and timestamp in updates\n         */\n        multi->auth_token_initial = strdup(multi->auth_token);\n    }\n\n    gc_free(&gc);\n}\n\n\nstatic bool\ncheck_hmac_token(hmac_ctx_t *ctx, const uint8_t *b64decoded, const char *username)\n{\n    ASSERT(hmac_ctx_size(ctx) == 256 / 8);\n\n    uint8_t hmac_output[256 / 8];\n\n    hmac_ctx_reset(ctx);\n    hmac_ctx_update(ctx, (uint8_t *)username, (int)strlen(username));\n    hmac_ctx_update(ctx, b64decoded, TOKEN_DATA_LEN - 256 / 8);\n    hmac_ctx_final(ctx, hmac_output);\n\n    const uint8_t *hmac = b64decoded + TOKEN_DATA_LEN - 256 / 8;\n    return memcmp_constant_time(&hmac_output, hmac, 32) == 0;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\nunsigned int\nverify_auth_token(struct user_pass *up, struct tls_multi *multi, struct tls_session *session)\n{\n    /*\n     * Base64 is <= input and input is < USER_PASS_LEN, so using USER_PASS_LEN\n     * is safe here but a bit overkill\n     */\n    ASSERT(up && !up->protected);\n    uint8_t b64decoded[USER_PASS_LEN];\n    int decoded_len =\n        openvpn_base64_decode(up->password + strlen(SESSION_ID_PREFIX), b64decoded, USER_PASS_LEN);\n\n    /*\n     * Ensure that the decoded data is the size of the\n     * timestamp + hmac + session id\n     */\n    if (decoded_len != TOKEN_DATA_LEN)\n    {\n        msg(M_WARN, \"ERROR: --auth-token wrong size (%d!=%d)\", decoded_len, (int)TOKEN_DATA_LEN);\n        return 0;\n    }\n\n    unsigned int ret = 0;\n\n    const uint8_t *sessid = b64decoded;\n    const uint8_t *tstamp_initial = sessid + AUTH_TOKEN_SESSION_ID_LEN;\n    const uint8_t *tstamp = tstamp_initial + sizeof(int64_t);\n\n    /* tstamp, tstamp_initial might not be aligned to an uint64, use memcpy\n     * to avoid unaligned access */\n    uint64_t timestamp = 0, timestamp_initial = 0;\n    memcpy(&timestamp, tstamp, sizeof(uint64_t));\n    timestamp = ntohll(timestamp);\n\n    memcpy(&timestamp_initial, tstamp_initial, sizeof(uint64_t));\n    timestamp_initial = ntohll(timestamp_initial);\n\n    hmac_ctx_t *ctx = multi->opt.auth_token_key.hmac;\n    if (check_hmac_token(ctx, b64decoded, up->username))\n    {\n        ret |= AUTH_TOKEN_HMAC_OK;\n    }\n    else if (check_hmac_token(ctx, b64decoded, \"\"))\n    {\n        ret |= AUTH_TOKEN_HMAC_OK;\n        ret |= AUTH_TOKEN_VALID_EMPTYUSER;\n        /* overwrite the username of the client with the empty one */\n        strcpy(up->username, \"\");\n    }\n    else\n    {\n        msg(M_WARN, \"--auth-gen-token: HMAC on token from client failed (%s)\", up->username);\n        return 0;\n    }\n\n    /* Accept session tokens only if their timestamp is in the acceptable range\n     * for renegotiations */\n    bool in_renegotiation_time =\n        now >= timestamp && now < timestamp + 2 * session->opt->auth_token_renewal;\n\n    if (!in_renegotiation_time)\n    {\n        msg(M_WARN, \"Timestamp (%\" PRIu64 \") of auth-token is out of the renewal window\",\n            timestamp);\n        ret |= AUTH_TOKEN_EXPIRED;\n    }\n\n    /* Sanity check the initial timestamp */\n    if (timestamp < timestamp_initial)\n    {\n        msg(M_WARN,\n            \"Initial timestamp (%\" PRIu64 \") in token from client earlier than \"\n            \"current timestamp %\" PRIu64 \". Broken/unsynchronised clock?\",\n            timestamp_initial, timestamp);\n        ret |= AUTH_TOKEN_EXPIRED;\n    }\n\n    if (multi->opt.auth_token_lifetime && now > timestamp_initial + multi->opt.auth_token_lifetime)\n    {\n        ret |= AUTH_TOKEN_EXPIRED;\n    }\n\n    if (ret & AUTH_TOKEN_EXPIRED)\n    {\n        /* Tell client that the session token is expired */\n        auth_set_client_reason(multi, \"SESSION: token expired\");\n        msg(M_INFO, \"--auth-gen-token: auth-token from client expired\");\n    }\n\n    /* Check that we do have the same session ID in the token as in our stored\n     * auth-token to ensure that it did not change.\n     * This also compares the prefix and session part of the\n     * tokens, which should be identical if the session ID stayed the same */\n    if (multi->auth_token_initial\n        && memcmp_constant_time(multi->auth_token_initial, up->password,\n                                strlen(SESSION_ID_PREFIX) + AUTH_TOKEN_SESSION_ID_BASE64_LEN))\n    {\n        msg(M_WARN, \"--auth-gen-token: session id in token changed (Rejecting \"\n                    \"token.\");\n        ret = 0;\n    }\n    return ret;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nvoid\nwipe_auth_token(struct tls_multi *multi)\n{\n    if (multi)\n    {\n        if (multi->auth_token)\n        {\n            secure_memzero(multi->auth_token, strlen(multi->auth_token));\n            free(multi->auth_token);\n        }\n        if (multi->auth_token_initial)\n        {\n            secure_memzero(multi->auth_token_initial, strlen(multi->auth_token_initial));\n            free(multi->auth_token_initial);\n        }\n        multi->auth_token = NULL;\n        multi->auth_token_initial = NULL;\n    }\n}\n\nvoid\ncheck_send_auth_token(struct context *c)\n{\n    struct tls_multi *multi = c->c2.tls_multi;\n    struct tls_session *session = &multi->session[TM_ACTIVE];\n\n    if (get_primary_key(multi)->state < S_GENERATED_KEYS\n        || get_primary_key(multi)->authenticated != KS_AUTH_TRUE)\n    {\n        /* the currently active session is still in renegotiation or another\n         * not fully authorized state. We are either very close to a\n         * renegotiation or have deauthorized the client. In both cases\n         * we just ignore the request to send another token\n         */\n        return;\n    }\n\n    if (!multi->auth_token_initial)\n    {\n        msg(D_SHOW_KEYS, \"initial auth-token not generated yet, skipping \"\n                         \"auth-token renewal.\");\n        return;\n    }\n\n    if (!multi->locked_username)\n    {\n        msg(D_SHOW_KEYS, \"username not locked, skipping auth-token renewal.\");\n        return;\n    }\n\n    struct user_pass up;\n    CLEAR(up);\n    strncpynt(up.username, multi->locked_username, sizeof(up.username));\n\n    generate_auth_token(&up, multi);\n\n    resend_auth_token_renegotiation(multi, session);\n}\n\nvoid\nresend_auth_token_renegotiation(struct tls_multi *multi, struct tls_session *session)\n{\n    /*\n     * Auth token already sent to client, update auth-token on client.\n     * The initial auth-token is sent as part of the push message, for this\n     * update we need to schedule an extra push message.\n     *\n     * Otherwise, the auth-token get pushed out as part of the \"normal\"\n     * push-reply\n     */\n    if (multi->auth_token_initial)\n    {\n        /*\n         * We do not explicitly reschedule the sending of the\n         * control message here. This might delay this reply\n         * a few seconds but this message is not time critical\n         */\n        send_push_reply_auth_token(multi);\n    }\n}\n"
  },
  {
    "path": "src/openvpn/auth_token.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n#ifndef AUTH_TOKEN_H\n#define AUTH_TOKEN_H\n\n/**\n * Generate an auth token based on username and timestamp\n *\n * The idea of auth token is to be stateless, so that we can verify use it\n * even after we have forgotten about it or server has been restarted.\n *\n * To achieve this even though we cannot trust the client we use HMAC\n * to be able to verify the information.\n *\n * Format of the auth-token (before base64 encode)\n *\n * session id(12 bytes)|uint64 timestamp (8 bytes)|\n * uint64 timestamp (8 bytes)|sha256-hmac(32 bytes)\n *\n * The first timestamp is the time the token was initially created and is used to\n * determine the maximum renewable time of the token. We always include this even\n * if tokens do not expire (this value is not used) to keep the code cleaner.\n *\n * The second timestamp is the time the token was renewed/regenerated and is used\n * to determine if this token has been renewed in the acceptable time range\n * (2 * renegotiation timeout)\n *\n * The session id is a random string of 12 byte (or 16 in base64) that is not\n * used by OpenVPN itself but kept intact so that external logging/management\n * can track the session multiple reconnects/servers. It is deliberately chosen\n * be a multiple of 3 bytes to have a base64 encoding without padding.\n *\n * The hmac is calculated over the username concatenated with the\n * raw auth-token bytes to include authentication of the username in the token\n *\n * We encode the auth-token with base64 and then prepend \"SESS_ID_\" before\n * sending it to the client.\n *\n * This function will free() an existing multi->auth_token and keep the\n * existing initial timestamp and session id contained in that token.\n */\nvoid generate_auth_token(const struct user_pass *up, struct tls_multi *multi);\n\n/**\n * Verifies the auth token to be in the format that generate_auth_token\n * create and checks if the token is valid.\n *\n */\nunsigned verify_auth_token(struct user_pass *up, struct tls_multi *multi,\n                           struct tls_session *session);\n\n\n/**\n * Loads an HMAC secret from a file or if no file is present generates a\n * epheremal secret for the run time of the server and stores it into ctx\n */\nvoid auth_token_init_secret(struct key_ctx *key_ctx, const char *key_file, bool key_inline);\n\n\n/**\n * Generate a auth-token server secret key, and write to file.\n *\n * @param filename          Filename of the server key file to create.\n */\nvoid auth_token_write_server_key_file(const char *filename);\n\n\n/**\n * Put the session id, and auth token status into the environment\n * if auth-token is enabled\n *\n */\nvoid add_session_token_env(struct tls_session *session, struct tls_multi *multi,\n                           const struct user_pass *up);\n\n/**\n * Wipes the authentication token out of the memory, frees and cleans up\n * related buffers and flags\n *\n *  @param multi  Pointer to a multi object holding the auth_token variables\n */\nvoid wipe_auth_token(struct tls_multi *multi);\n\n/**\n * The prefix given to auth tokens start with, this prefix is special\n * cased to not show up in log files in OpenVPN 2 and 3\n *\n * We also prefix this with _AT_ to only act on auth token generated by us.\n */\n#define SESSION_ID_PREFIX \"SESS_ID_AT_\"\n\n/**\n * Return if the password string has the format of a password.\n *\n * This function will always read as many bytes as SESSION_ID_PREFIX is longer\n * the caller needs ensure that password memory is at least that long (true for\n * calling with struct user_pass)\n * @param password\n * @return whether the password string starts with the session token prefix\n */\nstatic inline bool\nis_auth_token(const char *password)\n{\n    return (memcmp_constant_time(SESSION_ID_PREFIX, password, strlen(SESSION_ID_PREFIX)) == 0);\n}\n/**\n * Checks if a client should be sent a new auth token to update its\n * current auth-token\n * @param multi     Pointer the multi object of the TLS session\n * @param session   Pointer to the TLS session itself\n */\nvoid resend_auth_token_renegotiation(struct tls_multi *multi, struct tls_session *session);\n\n\n/**\n * Checks if the timer to resend the auth-token has expired and if a new\n * auth-token should be send to the client and triggers the resending\n */\nvoid check_send_auth_token(struct context *c);\n\n#endif /* AUTH_TOKEN_H */\n"
  },
  {
    "path": "src/openvpn/base64.c",
    "content": "/*\n * Copyright (c) 1995-2001 Kungliga Tekniska Högskolan\n * (Royal Institute of Technology, Stockholm, Sweden).\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the Institute nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"base64.h\"\n\n#include \"memdbg.h\"\n\nstatic char base64_chars[] = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n/*\n * base64 encode input data of length size to malloced\n * buffer which is returned as *str.  Returns string\n * length of *str.\n */\nint\nopenvpn_base64_encode(const void *data, int size, char **str)\n{\n    if (size < 0)\n    {\n        return -1;\n    }\n    size_t out_size = (size_t)size * 4 / 3 + 4;\n    if (out_size > INT_MAX)\n    {\n        return -1;\n    }\n    char *p = (char *)malloc(out_size);\n    char *start = p;\n    if (p == NULL)\n    {\n        return -1;\n    }\n    const unsigned char *q = (const unsigned char *)data;\n    for (int i = 0; i < size;)\n    {\n        unsigned int c = q[i++];\n        c <<= 8;\n        if (i < size)\n        {\n            c += q[i];\n        }\n        i++;\n        c <<= 8;\n        if (i < size)\n        {\n            c += q[i];\n        }\n        i++;\n        p[0] = base64_chars[(c & 0x00fc0000) >> 18];\n        p[1] = base64_chars[(c & 0x0003f000) >> 12];\n        p[2] = base64_chars[(c & 0x00000fc0) >> 6];\n        p[3] = base64_chars[(c & 0x0000003f) >> 0];\n        if (i > size)\n        {\n            p[3] = '=';\n        }\n        if (i > size + 1)\n        {\n            p[2] = '=';\n        }\n        p += 4;\n    }\n    *p = 0;\n    *str = start;\n    return (int)strlen(start);\n}\n\nstatic int\npos(char c)\n{\n    for (char *p = base64_chars; *p; p++)\n    {\n        if (*p == c)\n        {\n            return (int)(p - base64_chars);\n        }\n    }\n    return -1;\n}\n\n#define DECODE_ERROR 0xffffffff\n\nstatic unsigned int\ntoken_decode(const char *token)\n{\n    unsigned int val = 0;\n    unsigned int marker = 0;\n    if (!token[0] || !token[1] || !token[2] || !token[3])\n    {\n        return DECODE_ERROR;\n    }\n    for (unsigned int i = 0; i < 4; i++)\n    {\n        val <<= 6;\n        if (token[i] == '=')\n        {\n            marker++;\n        }\n        else if (marker > 0)\n        {\n            return DECODE_ERROR;\n        }\n        else\n        {\n            int char_pos = pos(token[i]);\n            if (unlikely(char_pos < 0)) /* caller should check */\n            {\n                return DECODE_ERROR;\n            }\n            val += (unsigned int)char_pos;\n        }\n    }\n    if (marker > 2)\n    {\n        return DECODE_ERROR;\n    }\n    return (marker << 24) | val;\n}\n/*\n * Decode base64 str, outputting data to buffer\n * at data of length size.  Return length of\n * decoded data written or -1 on error or overflow.\n */\nint\nopenvpn_base64_decode(const char *str, void *data, int size)\n{\n    const char *p;\n    unsigned char *q;\n    unsigned char *e = NULL;\n\n    q = data;\n    if (size >= 0)\n    {\n        e = q + size;\n    }\n    for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4)\n    {\n        unsigned int val = token_decode(p);\n        unsigned int marker = (val >> 24) & 0xff;\n        if (val == DECODE_ERROR)\n        {\n            return -1;\n        }\n        if (e && q >= e)\n        {\n            return -1;\n        }\n        *q++ = (val >> 16) & 0xff;\n        if (marker < 2)\n        {\n            if (e && q >= e)\n            {\n                return -1;\n            }\n            *q++ = (val >> 8) & 0xff;\n        }\n        if (marker < 1)\n        {\n            if (e && q >= e)\n            {\n                return -1;\n            }\n            *q++ = val & 0xff;\n        }\n    }\n    return (int)(q - (unsigned char *)data);\n}\n"
  },
  {
    "path": "src/openvpn/base64.h",
    "content": "/*\n * Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Högskolan\n * (Royal Institute of Technology, Stockholm, Sweden).\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the Institute nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#ifndef _BASE64_H_\n#define _BASE64_H_\n\n/** Compute resulting base64 length.  6 bits per byte, padded to 4 bytes. */\n#define OPENVPN_BASE64_LENGTH(binary_length) ((((8 * binary_length) / 6) + 3) & ~3)\n\n/** Compute the maximal number of bytes encoded in a base64 string. */\n#define OPENVPN_BASE64_DECODED_LENGTH(base64_length) ((base64_length / 4) * 3)\n\nint openvpn_base64_encode(const void *data, int size, char **str);\n\nint openvpn_base64_decode(const char *str, void *data, int size);\n\n#endif\n"
  },
  {
    "path": "src/openvpn/basic.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef BASIC_H\n#define BASIC_H\n\n#define BOOL_CAST(x) ((x) ? (true) : (false))\n\n/* size of an array */\n#define SIZE(x) (sizeof(x) / sizeof(x[0]))\n\n/* clear an object (may be optimized away, use secure_memzero() to erase secrets) */\n#define CLEAR(x) memset(&(x), 0, sizeof(x))\n\n#define IPV4_NETMASK_HOST 0xffffffffU\n\n#endif\n"
  },
  {
    "path": "src/openvpn/buffer.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"common.h\"\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"mtu.h\"\n#include \"misc.h\"\n\n#include \"memdbg.h\"\n\n#include <wchar.h>\n\nsize_t\narray_mult_safe(const size_t m1, const size_t m2, const size_t extra)\n{\n    const size_t limit = ALLOC_SIZE_MAX;\n    unsigned long long res =\n        (unsigned long long)m1 * (unsigned long long)m2 + (unsigned long long)extra;\n    if (unlikely(m1 > limit) || unlikely(m2 > limit) || unlikely(extra > limit)\n        || unlikely(res > (unsigned long long)limit))\n    {\n        msg(M_FATAL, \"attempted allocation of excessively large array\");\n    }\n    return (size_t)res;\n}\n\nvoid\nbuf_size_error(const size_t size)\n{\n    msg(M_FATAL, \"fatal buffer size error, size=%lu\", (unsigned long)size);\n}\n\nstruct buffer\n#ifdef DMALLOC\nalloc_buf_debug(size_t size, const char *file, int line)\n#else\nalloc_buf(size_t size)\n#endif\n{\n    struct buffer buf;\n\n    if (!buf_size_valid(size))\n    {\n        buf_size_error(size);\n    }\n    buf.capacity = (int)size;\n    buf.offset = 0;\n    buf.len = 0;\n#ifdef DMALLOC\n    buf.data = openvpn_dmalloc(file, line, size);\n#else\n    buf.data = calloc(1, size);\n#endif\n    check_malloc_return(buf.data);\n\n    return buf;\n}\n\nstruct buffer\n#ifdef DMALLOC\nalloc_buf_gc_debug(size_t size, struct gc_arena *gc, const char *file, int line)\n#else\nalloc_buf_gc(size_t size, struct gc_arena *gc)\n#endif\n{\n    struct buffer buf;\n    if (!buf_size_valid(size))\n    {\n        buf_size_error(size);\n    }\n    buf.capacity = (int)size;\n    buf.offset = 0;\n    buf.len = 0;\n#ifdef DMALLOC\n    buf.data = (uint8_t *)gc_malloc_debug(size, false, gc, file, line);\n#else\n    buf.data = (uint8_t *)gc_malloc(size, false, gc);\n#endif\n    if (size)\n    {\n        *buf.data = 0;\n    }\n    return buf;\n}\n\nstruct buffer\n#ifdef DMALLOC\nclone_buf_debug(const struct buffer *buf, const char *file, int line)\n#else\nclone_buf(const struct buffer *buf)\n#endif\n{\n    struct buffer ret;\n    ret.capacity = buf->capacity;\n    ret.offset = buf->offset;\n    ret.len = buf->len;\n#ifdef DMALLOC\n    ret.data = (uint8_t *)openvpn_dmalloc(file, line, buf->capacity);\n#else\n    ret.data = (uint8_t *)malloc(buf->capacity);\n#endif\n    check_malloc_return(ret.data);\n    memcpy(BPTR(&ret), BPTR(buf), BLENZ(buf));\n    return ret;\n}\n\n#ifdef BUF_INIT_TRACKING\n\nbool\nbuf_init_debug(struct buffer *buf, int offset, const char *file, int line)\n{\n    buf->debug_file = file;\n    buf->debug_line = line;\n    return buf_init_dowork(buf, offset);\n}\n\nstatic inline int\nbuf_debug_line(const struct buffer *buf)\n{\n    return buf->debug_line;\n}\n\nstatic const char *\nbuf_debug_file(const struct buffer *buf)\n{\n    return buf->debug_file;\n}\n\n#else /* ifdef BUF_INIT_TRACKING */\n\n#define buf_debug_line(buf) 0\n#define buf_debug_file(buf) \"[UNDEF]\"\n\n#endif /* ifdef BUF_INIT_TRACKING */\n\nvoid\nbuf_clear(struct buffer *buf)\n{\n    if (buf->capacity > 0)\n    {\n        secure_memzero(buf->data, buf->capacity);\n    }\n    buf->len = 0;\n    buf->offset = 0;\n}\n\nbool\nbuf_assign(struct buffer *dest, const struct buffer *src)\n{\n    if (!buf_init(dest, src->offset))\n    {\n        return false;\n    }\n    return buf_write(dest, BPTR(src), BLENZ(src));\n}\n\nvoid\nfree_buf(struct buffer *buf)\n{\n    free(buf->data);\n    CLEAR(*buf);\n}\n\nstatic void\nfree_buf_gc(struct buffer *buf, struct gc_arena *gc)\n{\n    if (gc)\n    {\n        struct gc_entry **e = &gc->list;\n\n        while (*e)\n        {\n            /* check if this object is the one we want to delete */\n            if ((uint8_t *)(*e + 1) == buf->data)\n            {\n                struct gc_entry *to_delete = *e;\n\n                /* remove element from linked list and free it */\n                *e = (*e)->next;\n                free(to_delete);\n\n                break;\n            }\n\n            e = &(*e)->next;\n        }\n    }\n\n    CLEAR(*buf);\n}\n\n/*\n * Return a buffer for write that is a subset of another buffer\n */\nstruct buffer\nbuf_sub(struct buffer *buf, int size, bool prepend)\n{\n    struct buffer ret;\n    uint8_t *data;\n\n    CLEAR(ret);\n    data = prepend ? buf_prepend(buf, size) : buf_write_alloc(buf, size);\n    if (data)\n    {\n        ret.capacity = size;\n        ret.data = data;\n    }\n    return ret;\n}\n\n/*\n * printf append to a buffer with overflow check\n */\nbool\nbuf_printf(struct buffer *buf, const char *format, ...)\n{\n    int ret = false;\n    if (buf_defined(buf))\n    {\n        va_list arglist;\n        uint8_t *ptr = BEND(buf);\n        int cap = buf_forward_capacity(buf);\n\n        if (cap > 0)\n        {\n            int stat;\n            va_start(arglist, format);\n            stat = vsnprintf((char *)ptr, cap, format, arglist);\n            va_end(arglist);\n            *(buf->data + buf->capacity - 1) = 0; /* windows vsnprintf needs this */\n            buf->len += (int)strlen((char *)ptr);\n            if (stat >= 0 && stat < cap)\n            {\n                ret = true;\n            }\n        }\n    }\n    return ret;\n}\n\nbool\nbuf_puts(struct buffer *buf, const char *str)\n{\n    int ret = false;\n    uint8_t *ptr = BEND(buf);\n    int cap = buf_forward_capacity(buf);\n    if (cap > 0)\n    {\n        strncpynt((char *)ptr, str, cap);\n        *(buf->data + buf->capacity - 1) = 0; /* windows vsnprintf needs this */\n        buf->len += (int)strlen((char *)ptr);\n        ret = true;\n    }\n    return ret;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\n/*\n * write a string to the end of a buffer that was\n * truncated by buf_printf\n */\nvoid\nbuf_catrunc(struct buffer *buf, const char *str)\n{\n    if (buf_forward_capacity(buf) <= 1)\n    {\n        size_t len = strlen(str) + 1;\n        if (len < buf_forward_capacity_total(buf))\n        {\n            memcpy(buf->data + buf->capacity - len, str, len);\n        }\n    }\n}\n\nbool\nbuffer_write_file(const char *filename, const struct buffer *buf)\n{\n    bool ret = false;\n    int fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR);\n    if (fd == -1)\n    {\n        msg(M_ERRNO, \"Cannot open file '%s' for write\", filename);\n        return false;\n    }\n\n    const ssize_t size = write(fd, BPTR(buf), (unsigned int)BLEN(buf));\n    if (size != BLEN(buf))\n    {\n        msg(M_ERRNO, \"Write error on file '%s'\", filename);\n        goto cleanup;\n    }\n\n    ret = true;\ncleanup:\n    if (close(fd) < 0)\n    {\n        msg(M_ERRNO, \"Close error on file %s\", filename);\n        ret = false;\n    }\n    return ret;\n}\n\n/*\n * Garbage collection\n */\n\nvoid *\n#ifdef DMALLOC\ngc_malloc_debug(size_t size, bool clear, struct gc_arena *a, const char *file, int line)\n#else\ngc_malloc(size_t size, bool clear, struct gc_arena *a)\n#endif\n{\n    void *ret;\n    if (a)\n    {\n        struct gc_entry *e;\n#ifdef DMALLOC\n        e = (struct gc_entry *)openvpn_dmalloc(file, line, size + sizeof(struct gc_entry));\n#else\n        e = (struct gc_entry *)malloc(size + sizeof(struct gc_entry));\n#endif\n        check_malloc_return(e);\n        ret = (char *)e + sizeof(struct gc_entry);\n        e->next = a->list;\n        a->list = e;\n    }\n    else\n    {\n#ifdef DMALLOC\n        ret = openvpn_dmalloc(file, line, size);\n#else\n        ret = malloc(size);\n#endif\n        check_malloc_return(ret);\n    }\n#ifndef ZERO_BUFFER_ON_ALLOC\n    if (clear)\n#endif\n        memset(ret, 0, size);\n    return ret;\n}\n\nvoid *\ngc_realloc(void *ptr, size_t size, struct gc_arena *a)\n{\n    void *ret = realloc(ptr, size);\n    check_malloc_return(ret);\n    if (a)\n    {\n        if (ptr && ptr != ret)\n        {\n            /* find the old entry and modify it if realloc changed\n             * the pointer */\n            struct gc_entry_special *e = NULL;\n            for (e = a->list_special; e != NULL; e = e->next)\n            {\n                if (e->addr == ptr)\n                {\n                    break;\n                }\n            }\n            ASSERT(e);\n            ASSERT(e->addr == ptr);\n            e->addr = ret;\n        }\n        else if (!ptr)\n        {\n            /* sets e->addr to newptr */\n            gc_addspecial(ret, free, a);\n        }\n    }\n\n    return ret;\n}\n\nvoid\nx_gc_free(struct gc_arena *a)\n{\n    struct gc_entry *e;\n    e = a->list;\n    a->list = NULL;\n\n    while (e != NULL)\n    {\n        struct gc_entry *next = e->next;\n        free(e);\n        e = next;\n    }\n}\n\n/*\n * Functions to handle special objects in gc_entries\n */\n\nvoid\nx_gc_freespecial(struct gc_arena *a)\n{\n    struct gc_entry_special *e;\n    e = a->list_special;\n    a->list_special = NULL;\n\n    while (e != NULL)\n    {\n        struct gc_entry_special *next = e->next;\n        e->free_fnc(e->addr);\n        free(e);\n        e = next;\n    }\n}\n\nvoid\ngc_addspecial(void *addr, void (*free_function)(void *), struct gc_arena *a)\n{\n    ASSERT(a);\n    struct gc_entry_special *e;\n#ifdef DMALLOC\n    e = (struct gc_entry_special *)openvpn_dmalloc(file, line, sizeof(struct gc_entry_special));\n#else\n    e = (struct gc_entry_special *)malloc(sizeof(struct gc_entry_special));\n#endif\n    check_malloc_return(e);\n    e->free_fnc = free_function;\n    e->addr = addr;\n\n    e->next = a->list_special;\n    a->list_special = e;\n}\n\n\n/*\n * Transfer src arena to dest, resetting src to an empty arena.\n */\nvoid\ngc_transfer(struct gc_arena *dest, struct gc_arena *src)\n{\n    if (dest && src)\n    {\n        struct gc_entry *e = src->list;\n        if (e)\n        {\n            while (e->next != NULL)\n            {\n                e = e->next;\n            }\n            e->next = dest->list;\n            dest->list = src->list;\n            src->list = NULL;\n        }\n    }\n}\n\n/*\n * Hex dump -- Output a binary buffer to a hex string and return it.\n */\n\nchar *\nformat_hex_ex(const uint8_t *data, size_t size, size_t maxoutput, unsigned int space_break_flags,\n              const char *separator, struct gc_arena *gc)\n{\n    const size_t bytes_per_hexblock = space_break_flags & FHE_SPACE_BREAK_MASK;\n    const size_t separator_len = separator ? strlen(separator) : 0;\n    const size_t out_len = maxoutput > 0\n                               ? maxoutput\n                               : ((size * 2) + ((size / bytes_per_hexblock) * separator_len) + 2);\n\n    struct buffer out = alloc_buf_gc(out_len, gc);\n    for (size_t i = 0; i < size; ++i)\n    {\n        if (separator && i && !(i % bytes_per_hexblock))\n        {\n            buf_printf(&out, \"%s\", separator);\n        }\n        if (space_break_flags & FHE_CAPS)\n        {\n            buf_printf(&out, \"%02X\", data[i]);\n        }\n        else\n        {\n            buf_printf(&out, \"%02x\", data[i]);\n        }\n    }\n    buf_catrunc(&out, \"[more...]\");\n    return (char *)out.data;\n}\n\n/*\n * remove specific trailing character\n */\n\nvoid\nbuf_rmtail(struct buffer *buf, uint8_t remove)\n{\n    uint8_t *cp = BLAST(buf);\n    if (cp && *cp == remove)\n    {\n        *cp = '\\0';\n        --buf->len;\n    }\n}\n\n/*\n * force a null termination even it requires\n * truncation of the last char.\n */\nvoid\nbuf_null_terminate(struct buffer *buf)\n{\n    char *last = (char *)BLAST(buf);\n    if (last && *last == '\\0') /* already terminated? */\n    {\n        return;\n    }\n\n    if (!buf_safe(buf, 1)) /* make space for trailing null */\n    {\n        buf_inc_len(buf, -1);\n    }\n\n    buf_write_u8(buf, 0);\n}\n\n/*\n * Remove trailing \\r and \\n chars and ensure\n * null termination.\n */\nvoid\nbuf_chomp(struct buffer *buf)\n{\n    while (true)\n    {\n        char *last = (char *)BLAST(buf);\n        if (!last)\n        {\n            break;\n        }\n        if (char_class((unsigned char)*last, CC_CRLF | CC_NULL))\n        {\n            if (!buf_inc_len(buf, -1))\n            {\n                break;\n            }\n        }\n        else\n        {\n            break;\n        }\n    }\n    buf_null_terminate(buf);\n}\n\nconst char *\nskip_leading_whitespace(const char *str)\n{\n    while (*str)\n    {\n        const char c = *str;\n        if (!(c == ' ' || c == '\\t'))\n        {\n            break;\n        }\n        ++str;\n    }\n    return str;\n}\n\n/*\n * like buf_null_terminate, but operate on strings\n */\nvoid\nstring_null_terminate(char *str, int len, int capacity)\n{\n    ASSERT(len >= 0 && len <= capacity && capacity > 0);\n    if (len < capacity)\n    {\n        *(str + len) = '\\0';\n    }\n    else if (len == capacity)\n    {\n        *(str + len - 1) = '\\0';\n    }\n}\n\n/*\n * Remove trailing \\r and \\n chars.\n */\nvoid\nchomp(char *str)\n{\n    rm_trailing_chars(str, \"\\r\\n\");\n}\n\n/*\n * Remove trailing chars\n */\nvoid\nrm_trailing_chars(char *str, const char *what_to_delete)\n{\n    bool modified;\n    do\n    {\n        const size_t len = strlen(str);\n        modified = false;\n        if (len > 0)\n        {\n            char *cp = str + (len - 1);\n            if (strchr(what_to_delete, *cp) != NULL)\n            {\n                *cp = '\\0';\n                modified = true;\n            }\n        }\n    } while (modified);\n}\n\n/*\n * Allocate a string\n */\nchar *\n#ifdef DMALLOC\nstring_alloc_debug(const char *str, struct gc_arena *gc, const char *file, int line)\n#else\nstring_alloc(const char *str, struct gc_arena *gc)\n#endif\n{\n    if (str)\n    {\n        const size_t n = strlen(str) + 1;\n        char *ret;\n\n        if (gc)\n        {\n#ifdef DMALLOC\n            ret = (char *)gc_malloc_debug(n, false, gc, file, line);\n#else\n            ret = (char *)gc_malloc(n, false, gc);\n#endif\n        }\n        else\n        {\n            /* If there are no garbage collector available, it's expected\n             * that the caller cleans up afterwards.  This is coherent with the\n             * earlier behaviour when gc_malloc() would be called with gc == NULL\n             */\n#ifdef DMALLOC\n            ret = openvpn_dmalloc(file, line, n);\n#else\n            ret = calloc(1, n);\n#endif\n            check_malloc_return(ret);\n        }\n        memcpy(ret, str, n);\n        return ret;\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\n/*\n * Erase all characters in a string\n */\nvoid\nstring_clear(char *str)\n{\n    if (str)\n    {\n        secure_memzero(str, strlen(str));\n    }\n}\n\n/*\n * Return the length of a string array\n */\nint\nstring_array_len(const char **array)\n{\n    int i = 0;\n    if (array)\n    {\n        while (array[i])\n        {\n            ++i;\n        }\n    }\n    return i;\n}\n\nchar *\nprint_argv(const char **p, struct gc_arena *gc, const unsigned int flags)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n    int i = 0;\n    for (;;)\n    {\n        const char *cp = *p++;\n        if (!cp)\n        {\n            break;\n        }\n        if (i)\n        {\n            buf_printf(&out, \" \");\n        }\n        if (flags & PA_BRACKET)\n        {\n            buf_printf(&out, \"[%s]\", cp);\n        }\n        else\n        {\n            buf_printf(&out, \"%s\", cp);\n        }\n        ++i;\n    }\n    return BSTR(&out);\n}\n\n/*\n * Allocate a string inside a buffer\n */\nstruct buffer\n#ifdef DMALLOC\nstring_alloc_buf_debug(const char *str, struct gc_arena *gc, const char *file, int line)\n#else\nstring_alloc_buf(const char *str, struct gc_arena *gc)\n#endif\n{\n    struct buffer buf;\n\n    ASSERT(str);\n\n#ifdef DMALLOC\n    buf_set_read(&buf, (uint8_t *)string_alloc_debug(str, gc, file, line), strlen(str) + 1);\n#else\n    buf_set_read(&buf, (uint8_t *)string_alloc(str, gc), strlen(str) + 1);\n#endif\n\n    if (buf.len > 0) /* Don't count trailing '\\0' as part of length */\n    {\n        --buf.len;\n    }\n\n    return buf;\n}\n\n/*\n * String comparison\n */\n\nbool\nbuf_string_match_head_str(const struct buffer *src, const char *match)\n{\n    const size_t size = strlen(match);\n    if (size > src->len)\n    {\n        return false;\n    }\n    return memcmp(BPTR(src), match, size) == 0;\n}\n\nbool\nbuf_string_compare_advance(struct buffer *src, const char *match)\n{\n    if (buf_string_match_head_str(src, match))\n    {\n        buf_advance(src, strlen(match));\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\nint\nbuf_substring_len(const struct buffer *buf, int delim)\n{\n    int i = 0;\n    struct buffer tmp = *buf;\n    int c;\n\n    while ((c = buf_read_u8(&tmp)) >= 0)\n    {\n        ++i;\n        if (c == delim)\n        {\n            return i;\n        }\n    }\n    return -1;\n}\n\n/*\n * String parsing\n */\n\nbool\nbuf_parse(struct buffer *buf, const int delim, char *line, const int size)\n{\n    bool eol = false;\n    int n = 0;\n    int c;\n\n    ASSERT(size > 0);\n\n    do\n    {\n        c = buf_peek_u8(buf);\n        if (c < 0)\n        {\n            eol = true;\n            line[n] = 0;\n            break;\n        }\n        if (c == delim)\n        {\n            buf_advance(buf, 1);\n            line[n] = 0;\n            break;\n        }\n        if (n >= (size - 1))\n        {\n            break;\n        }\n        buf_advance(buf, 1);\n        line[n++] = (char)c;\n    } while (c);\n\n    line[size - 1] = '\\0';\n    return !(eol && !strlen(line));\n}\n\n/*\n * Print a string which might be NULL\n */\nconst char *\nnp(const char *str)\n{\n    if (str)\n    {\n        return str;\n    }\n    else\n    {\n        return \"[NULL]\";\n    }\n}\n\n/*\n * Classify and mutate strings based on character types.\n */\n\n/* Note 1: This functions depends on getting an unsigned\n   char. Both the is*() functions and our own checks expect it\n   this way.\n   Note 2: For CC_PRINT we just accept everything >= 32, so\n   if we ingest non-ASCII UTF-8 we will classify it as\n   printable since it will be >= 128. Other encodings are\n   not officially supported.\n*/\nbool\nchar_class(const unsigned char c, const unsigned int flags)\n{\n    if (!flags)\n    {\n        return false;\n    }\n    if (flags & CC_ANY)\n    {\n        return true;\n    }\n\n    if ((flags & CC_NULL) && c == '\\0')\n    {\n        return true;\n    }\n\n    if ((flags & CC_ALNUM) && isalnum(c))\n    {\n        return true;\n    }\n    if ((flags & CC_ALPHA) && isalpha(c))\n    {\n        return true;\n    }\n    if ((flags & CC_ASCII) && isascii(c))\n    {\n        return true;\n    }\n    if ((flags & CC_CNTRL) && iscntrl(c))\n    {\n        return true;\n    }\n    if ((flags & CC_DIGIT) && isdigit(c))\n    {\n        return true;\n    }\n    /* allow ascii non-control and UTF-8, consider DEL to be a control */\n    if ((flags & CC_PRINT) && (c >= 32 && c != 127))\n    {\n        return true;\n    }\n    if ((flags & CC_PUNCT) && ispunct(c))\n    {\n        return true;\n    }\n    if ((flags & CC_SPACE) && isspace(c))\n    {\n        return true;\n    }\n    if ((flags & CC_XDIGIT) && isxdigit(c))\n    {\n        return true;\n    }\n\n    if ((flags & CC_BLANK) && (c == ' ' || c == '\\t'))\n    {\n        return true;\n    }\n    if ((flags & CC_NEWLINE) && c == '\\n')\n    {\n        return true;\n    }\n    if ((flags & CC_CR) && c == '\\r')\n    {\n        return true;\n    }\n\n    if ((flags & CC_BACKSLASH) && c == '\\\\')\n    {\n        return true;\n    }\n    if ((flags & CC_UNDERBAR) && c == '_')\n    {\n        return true;\n    }\n    if ((flags & CC_DASH) && c == '-')\n    {\n        return true;\n    }\n    if ((flags & CC_DOT) && c == '.')\n    {\n        return true;\n    }\n    if ((flags & CC_COMMA) && c == ',')\n    {\n        return true;\n    }\n    if ((flags & CC_COLON) && c == ':')\n    {\n        return true;\n    }\n    if ((flags & CC_SLASH) && c == '/')\n    {\n        return true;\n    }\n    if ((flags & CC_SINGLE_QUOTE) && c == '\\'')\n    {\n        return true;\n    }\n    if ((flags & CC_DOUBLE_QUOTE) && c == '\\\"')\n    {\n        return true;\n    }\n    if ((flags & CC_REVERSE_QUOTE) && c == '`')\n    {\n        return true;\n    }\n    if ((flags & CC_AT) && c == '@')\n    {\n        return true;\n    }\n    if ((flags & CC_EQUAL) && c == '=')\n    {\n        return true;\n    }\n    if ((flags & CC_LESS_THAN) && c == '<')\n    {\n        return true;\n    }\n    if ((flags & CC_GREATER_THAN) && c == '>')\n    {\n        return true;\n    }\n    if ((flags & CC_PIPE) && c == '|')\n    {\n        return true;\n    }\n    if ((flags & CC_QUESTION_MARK) && c == '?')\n    {\n        return true;\n    }\n    if ((flags & CC_ASTERISK) && c == '*')\n    {\n        return true;\n    }\n\n    return false;\n}\n\nstatic inline bool\nchar_inc_exc(const char c, const unsigned int inclusive, const unsigned int exclusive)\n{\n    return char_class((unsigned char)c, inclusive)\n           && !char_class((unsigned char)c, exclusive);\n}\n\nbool\nstring_class(const char *str, const unsigned int inclusive, const unsigned int exclusive)\n{\n    char c;\n    ASSERT(str);\n    while ((c = *str++))\n    {\n        if (!char_inc_exc(c, inclusive, exclusive))\n        {\n            return false;\n        }\n    }\n    return true;\n}\n\n/*\n * Modify string in place.\n * Guaranteed to not increase string length.\n */\nbool\nstring_mod(char *str, const unsigned int inclusive, const unsigned int exclusive,\n           const char replace)\n{\n    const char *in = str;\n    bool ret = true;\n\n    ASSERT(str);\n\n    while (true)\n    {\n        char c = *in++;\n        if (c)\n        {\n            if (!char_inc_exc(c, inclusive, exclusive))\n            {\n                c = replace;\n                ret = false;\n            }\n            if (c)\n            {\n                *str++ = c;\n            }\n        }\n        else\n        {\n            *str = '\\0';\n            break;\n        }\n    }\n    return ret;\n}\n\nbool\nstring_check_buf(struct buffer *buf, const unsigned int inclusive, const unsigned int exclusive)\n{\n    ASSERT(buf);\n\n    for (int i = 0; i < BLEN(buf); i++)\n    {\n        char c = BSTR(buf)[i];\n\n        if (!char_inc_exc(c, inclusive, exclusive))\n        {\n            return false;\n        }\n    }\n    return true;\n}\n\nconst char *\nstring_mod_const(const char *str, const unsigned int inclusive, const unsigned int exclusive,\n                 const char replace, struct gc_arena *gc)\n{\n    if (str)\n    {\n        char *buf = string_alloc(str, gc);\n        string_mod(buf, inclusive, exclusive, replace);\n        return buf;\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\nvoid\nstring_replace_leading(char *str, const char match, const char replace)\n{\n    ASSERT(match != '\\0');\n    while (*str)\n    {\n        if (*str == match)\n        {\n            *str = replace;\n        }\n        else\n        {\n            break;\n        }\n        ++str;\n    }\n}\n\nbool\nchecked_snprintf(char *str, size_t size, const char *format, ...)\n{\n    va_list arglist;\n    va_start(arglist, format);\n    ASSERT(size < INT_MAX);\n    int len = vsnprintf(str, size, format, arglist);\n    va_end(arglist);\n    return (len >= 0 && len < (ssize_t)size);\n}\n\n#ifdef VERIFY_ALIGNMENT\nvoid\nvalign4(const struct buffer *buf, const char *file, const int line)\n{\n    if (buf && buf->len)\n    {\n        msglvl_t msglevel = D_ALIGN_DEBUG;\n        const unsigned int u = (unsigned int)BPTR(buf);\n\n        if (u & (PAYLOAD_ALIGN - 1))\n        {\n            msglevel = D_ALIGN_ERRORS;\n        }\n\n        msg(msglevel, \"%sAlignment at %s/%d ptr=\" ptr_format \" OLC=%d/%d/%d I=%s/%d\",\n            (msglevel == D_ALIGN_ERRORS) ? \"ERROR: \" : \"\", file, line, (ptr_type)buf->data,\n            buf->offset, buf->len, buf->capacity, buf_debug_file(buf), buf_debug_line(buf));\n    }\n}\n#endif /* ifdef VERIFY_ALIGNMENT */\n\n/*\n * struct buffer_list\n */\nstruct buffer_list *\nbuffer_list_new(void)\n{\n    struct buffer_list *ret;\n    ALLOC_OBJ_CLEAR(ret, struct buffer_list);\n    ret->size = 0;\n    return ret;\n}\n\nvoid\nbuffer_list_free(struct buffer_list *ol)\n{\n    if (ol)\n    {\n        buffer_list_reset(ol);\n        free(ol);\n    }\n}\n\nbool\nbuffer_list_defined(const struct buffer_list *ol)\n{\n    return ol && ol->head != NULL;\n}\n\nvoid\nbuffer_list_reset(struct buffer_list *ol)\n{\n    struct buffer_entry *e = ol->head;\n    while (e)\n    {\n        struct buffer_entry *next = e->next;\n        free_buf(&e->buf);\n        free(e);\n        e = next;\n    }\n    ol->head = ol->tail = NULL;\n    ol->size = 0;\n}\n\nvoid\nbuffer_list_push(struct buffer_list *ol, const char *str)\n{\n    if (str)\n    {\n        const size_t len = strlen((const char *)str);\n        struct buffer_entry *e = buffer_list_push_data(ol, str, len + 1);\n        if (e)\n        {\n            e->buf.len = (int)len; /* Don't count trailing '\\0' as part of length */\n        }\n    }\n}\n\nstruct buffer_entry *\nbuffer_list_push_data(struct buffer_list *ol, const void *data, size_t size)\n{\n    struct buffer_entry *e = NULL;\n    if (data)\n    {\n        ALLOC_OBJ_CLEAR(e, struct buffer_entry);\n\n        ++ol->size;\n        if (ol->tail)\n        {\n            ASSERT(ol->head);\n            ol->tail->next = e;\n        }\n        else\n        {\n            ASSERT(!ol->head);\n            ol->head = e;\n        }\n        e->buf = alloc_buf(size);\n        memcpy(e->buf.data, data, size);\n        e->buf.len = (int)size;\n        ol->tail = e;\n    }\n    return e;\n}\n\nstruct buffer *\nbuffer_list_peek(struct buffer_list *ol)\n{\n    if (ol && ol->head)\n    {\n        return &ol->head->buf;\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\nvoid\nbuffer_list_aggregate_separator(struct buffer_list *bl, const size_t max_len, const char *sep)\n{\n    const size_t sep_len = strlen(sep);\n    struct buffer_entry *more = bl->head;\n    size_t size = 0;\n    int count = 0;\n    for (; more; ++count)\n    {\n        size_t extra_len = BLENZ(&more->buf) + sep_len;\n        if (size + extra_len > max_len)\n        {\n            break;\n        }\n\n        size += extra_len;\n        more = more->next;\n    }\n\n    if (count >= 2)\n    {\n        struct buffer_entry *f;\n        ALLOC_OBJ_CLEAR(f, struct buffer_entry);\n        f->buf = alloc_buf(size + 1); /* prevent 0-byte malloc */\n\n        struct buffer_entry *e = bl->head;\n        for (size_t i = 0; e && i < count; ++i)\n        {\n            struct buffer_entry *next = e->next;\n            buf_copy(&f->buf, &e->buf);\n            buf_write(&f->buf, sep, sep_len);\n            free_buf(&e->buf);\n            free(e);\n            e = next;\n        }\n        bl->head = f;\n        bl->size -= count - 1;\n        f->next = more;\n        if (!more)\n        {\n            bl->tail = f;\n        }\n    }\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nvoid\nbuffer_list_aggregate(struct buffer_list *bl, const size_t max)\n{\n    buffer_list_aggregate_separator(bl, max, \"\");\n}\n\nvoid\nbuffer_list_pop(struct buffer_list *ol)\n{\n    if (ol && ol->head)\n    {\n        struct buffer_entry *e = ol->head->next;\n        free_buf(&ol->head->buf);\n        free(ol->head);\n        ol->head = e;\n        --ol->size;\n        if (!e)\n        {\n            ol->tail = NULL;\n        }\n    }\n}\n\nvoid\nbuffer_list_advance(struct buffer_list *ol, ssize_t n)\n{\n    if (ol->head)\n    {\n        struct buffer *buf = &ol->head->buf;\n        ASSERT(buf_advance(buf, n));\n        if (!BLEN(buf))\n        {\n            buffer_list_pop(ol);\n        }\n    }\n}\n\nstruct buffer_list *\nbuffer_list_file(const char *fn, int max_line_len)\n{\n    FILE *fp = platform_fopen(fn, \"r\");\n    struct buffer_list *bl = NULL;\n\n    if (fp)\n    {\n        char *line = (char *)malloc(max_line_len);\n        if (line)\n        {\n            bl = buffer_list_new();\n            while (fgets(line, max_line_len, fp) != NULL)\n            {\n                buffer_list_push(bl, line);\n            }\n            free(line);\n        }\n        fclose(fp);\n    }\n    return bl;\n}\n\nstruct buffer\nbuffer_read_from_file(const char *filename, struct gc_arena *gc)\n{\n    struct buffer ret = { 0 };\n\n    platform_stat_t file_stat = { 0 };\n    if (platform_stat(filename, &file_stat) < 0)\n    {\n        return ret;\n    }\n\n    FILE *fp = platform_fopen(filename, \"r\");\n    if (!fp)\n    {\n        return ret;\n    }\n\n    const size_t size = file_stat.st_size;\n    ret = alloc_buf_gc(size + 1, gc); /* space for trailing \\0 */\n    size_t read_size = fread(BPTR(&ret), 1, size, fp);\n    if (read_size == 0)\n    {\n        free_buf_gc(&ret, gc);\n        goto cleanup;\n    }\n    ASSERT(buf_inc_len(&ret, (int)read_size));\n    buf_null_terminate(&ret);\n\ncleanup:\n    fclose(fp);\n    return ret;\n}\n"
  },
  {
    "path": "src/openvpn/buffer.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef BUFFER_H\n#define BUFFER_H\n\n#include \"basic.h\"\n#include \"error.h\"\n\n#define BUF_SIZE_MAX 1000000\n\n/*\n * Define verify_align function, otherwise\n * it will be a noop.\n */\n/* #define VERIFY_ALIGNMENT */\n\n/*\n * Keep track of source file/line of buf_init calls\n */\n#ifdef VERIFY_ALIGNMENT\n#define BUF_INIT_TRACKING\n#endif\n\n/**************************************************************************/\n/**\n * Wrapper structure for dynamically allocated memory.\n *\n * The actual content stored in a \\c buffer structure starts at the memory\n * location \\c buffer.data \\c + \\c buffer.offset, and has a length of \\c\n * buffer.len bytes.  This, together with the space available before and\n * after the content, is represented in the pseudocode below:\n * @code\n * uint8_t *content_start    = buffer.data + buffer.offset;\n * uint8_t *content_end      = buffer.data + buffer.offset + buffer.len;\n * int      prepend_capacity = buffer.offset;\n * int      append_capacity  = buffer.capacity - (buffer.offset + buffer.len);\n * @endcode\n */\nstruct buffer\n{\n    int capacity;  /**< Size in bytes of memory allocated by\n                    *   \\c malloc(). */\n    int offset;    /**< Offset in bytes of the actual content\n                    *   within the allocated memory. */\n    int len;       /**< Length in bytes of the actual content\n                    *   within the allocated memory. */\n    uint8_t *data; /**< Pointer to the allocated memory. */\n\n#ifdef BUF_INIT_TRACKING\n    const char *debug_file;\n    int debug_line;\n#endif\n};\n\n\n/**************************************************************************/\n/**\n * Garbage collection entry for one dynamically allocated block of memory.\n *\n * This structure represents one link in the linked list contained in a \\c\n * gc_arena structure.  Each time the \\c gc_malloc() function is called,\n * it allocates \\c sizeof(gc_entry) + the requested number of bytes.  The\n * \\c gc_entry is then stored as a header in front of the memory address\n * returned to the caller.\n */\nstruct gc_entry\n{\n    struct gc_entry *next; /**< Pointer to the next item in the\n                            *   linked list. */\n};\n\n/**\n * Garbage collection entry for a specially allocated structure that needs\n * a custom free function to be freed like struct addrinfo\n *\n */\nstruct gc_entry_special\n{\n    struct gc_entry_special *next;\n    void (*free_fnc)(void *);\n    void *addr;\n};\n\n\n/**\n * Garbage collection arena used to keep track of dynamically allocated\n * memory.\n *\n * This structure contains a linked list of \\c gc_entry structures.  When\n * a block of memory is allocated using the \\c gc_malloc() function, the\n * allocation is registered in the function's \\c gc_arena argument.  All\n * the dynamically allocated memory registered in a \\c gc_arena can be\n * freed using the \\c gc_free() function.\n */\nstruct gc_arena\n{\n    struct gc_entry *list; /**< First element of the linked list of\n                            *   \\c gc_entry structures. */\n    struct gc_entry_special *list_special;\n};\n\n\n#define BPTR(buf)  (buf_bptr(buf))\n#define BEND(buf)  (buf_bend(buf))\n#define BLAST(buf) (buf_blast(buf))\n#define BLEN(buf)  (buf_len(buf))\n#define BLENZ(buf) ((size_t)buf_len(buf))\n#define BDEF(buf)  (buf_defined(buf))\n#define BSTR(buf)  (buf_str(buf))\n#define BCAP(buf)  (buf_forward_capacity(buf))\n\nvoid buf_clear(struct buffer *buf);\n\nvoid free_buf(struct buffer *buf);\n\nbool buf_assign(struct buffer *dest, const struct buffer *src);\n\nvoid string_clear(char *str);\n\nint string_array_len(const char **array);\n\nsize_t array_mult_safe(const size_t m1, const size_t m2, const size_t extra);\n\n#define PA_BRACKET (1 << 0)\nchar *print_argv(const char **p, struct gc_arena *gc, const unsigned int flags);\n\nvoid buf_size_error(const size_t size);\n\n/* for dmalloc debugging */\n\n#ifdef DMALLOC\n\n#define alloc_buf(size)               alloc_buf_debug(size, __FILE__, __LINE__)\n#define alloc_buf_gc(size, gc)        alloc_buf_gc_debug(size, gc, __FILE__, __LINE__);\n#define clone_buf(buf)                clone_buf_debug(buf, __FILE__, __LINE__);\n#define gc_malloc(size, clear, arena) gc_malloc_debug(size, clear, arena, __FILE__, __LINE__)\n#define string_alloc(str, gc)         string_alloc_debug(str, gc, __FILE__, __LINE__)\n#define string_alloc_buf(str, gc)     string_alloc_buf_debug(str, gc, __FILE__, __LINE__)\n\nstruct buffer alloc_buf_debug(size_t size, const char *file, int line);\n\nstruct buffer alloc_buf_gc_debug(size_t size, struct gc_arena *gc, const char *file, int line);\n\nstruct buffer clone_buf_debug(const struct buffer *buf, const char *file, int line);\n\nvoid *gc_malloc_debug(size_t size, bool clear, struct gc_arena *a, const char *file, int line);\n\nchar *string_alloc_debug(const char *str, struct gc_arena *gc, const char *file, int line);\n\nstruct buffer string_alloc_buf_debug(const char *str, struct gc_arena *gc, const char *file,\n                                     int line);\n\n#else  /* ifdef DMALLOC */\n\nstruct buffer alloc_buf(size_t size);\n\nstruct buffer alloc_buf_gc(size_t size,\n                           struct gc_arena *gc); /* allocate buffer with garbage collection */\n\nstruct buffer clone_buf(const struct buffer *buf);\n\nvoid *gc_malloc(size_t size, bool clear, struct gc_arena *a);\n\nchar *string_alloc(const char *str, struct gc_arena *gc);\n\nstruct buffer string_alloc_buf(const char *str, struct gc_arena *gc);\n\n#endif /* ifdef DMALLOC */\n\nvoid gc_addspecial(void *addr, void (*free_function)(void *), struct gc_arena *a);\n\n/**\n * allows to realloc a pointer previously allocated by gc_malloc or gc_realloc\n *\n * @note only use this function on pointers returned by gc_malloc or re_alloc\n *       with the same gc_arena\n *\n * @param ptr   Pointer of the previously allocated memory\n * @param size  New size\n * @param a     gc_arena to use\n * @return      new pointer\n */\nvoid *gc_realloc(void *ptr, size_t size, struct gc_arena *a);\n\n#ifdef BUF_INIT_TRACKING\n#define buf_init(buf, offset) buf_init_debug(buf, offset, __FILE__, __LINE__)\nbool buf_init_debug(struct buffer *buf, int offset, const char *file, int line);\n\n#else\n#define buf_init(buf, offset) buf_init_dowork(buf, offset)\n#endif\n\n\n/* inline functions */\nstatic inline void\ngc_freeaddrinfo_callback(void *addr)\n{\n    freeaddrinfo((struct addrinfo *)addr);\n}\n\n/** Return an empty struct buffer */\nstatic inline struct buffer\nclear_buf(void)\n{\n    return (struct buffer){ 0 };\n}\n\nstatic inline bool\nbuf_defined(const struct buffer *buf)\n{\n    return buf->data != NULL;\n}\n\nstatic inline bool\nbuf_valid(const struct buffer *buf)\n{\n    return likely(buf->data != NULL) && likely(buf->len >= 0);\n}\n\nstatic inline uint8_t *\nbuf_bptr(const struct buffer *buf)\n{\n    if (buf_valid(buf))\n    {\n        return buf->data + buf->offset;\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\nstatic int\nbuf_len(const struct buffer *buf)\n{\n    if (buf_valid(buf))\n    {\n        return buf->len;\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nstatic inline uint8_t *\nbuf_bend(const struct buffer *buf)\n{\n    return buf_bptr(buf) + buf_len(buf);\n}\n\nstatic inline uint8_t *\nbuf_blast(const struct buffer *buf)\n{\n    if (buf_len(buf) > 0)\n    {\n        return buf_bptr(buf) + buf_len(buf) - 1;\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\nstatic inline bool\nbuf_size_valid(const size_t size)\n{\n    return likely(size < BUF_SIZE_MAX);\n}\n\nstatic inline bool\nbuf_size_valid_signed(const int size)\n{\n    return likely(size >= -BUF_SIZE_MAX) && likely(size < BUF_SIZE_MAX);\n}\n\nstatic inline char *\nbuf_str(const struct buffer *buf)\n{\n    return (char *)buf_bptr(buf);\n}\n\nstatic inline void\nbuf_reset(struct buffer *buf)\n{\n    buf->capacity = 0;\n    buf->offset = 0;\n    buf->len = 0;\n    buf->data = NULL;\n}\n\nstatic inline void\nbuf_reset_len(struct buffer *buf)\n{\n    buf->len = 0;\n    buf->offset = 0;\n}\n\nstatic inline bool\nbuf_init_dowork(struct buffer *buf, int offset)\n{\n    if (offset < 0 || offset > buf->capacity || buf->data == NULL)\n    {\n        return false;\n    }\n    buf->len = 0;\n    buf->offset = offset;\n    return true;\n}\n\nstatic inline void\nbuf_set_write(struct buffer *buf, uint8_t *data, int size)\n{\n    if (!buf_size_valid(size))\n    {\n        buf_size_error(size);\n    }\n    buf->len = 0;\n    buf->offset = 0;\n    buf->capacity = size;\n    buf->data = data;\n    if (size > 0 && data)\n    {\n        *data = 0;\n    }\n}\n\nstatic inline void\nbuf_set_read(struct buffer *buf, const uint8_t *data, size_t size)\n{\n    if (!buf_size_valid(size))\n    {\n        buf_size_error(size);\n    }\n    buf->len = buf->capacity = (int)size;\n    buf->offset = 0;\n    buf->data = (uint8_t *)data;\n}\n\n/* Like strncpy but makes sure dest is always null terminated */\nstatic inline void\nstrncpynt(char *dest, const char *src, size_t maxlen)\n{\n    if (maxlen > 0)\n    {\n        strncpy(dest, src, maxlen - 1);\n        dest[maxlen - 1] = 0;\n    }\n}\n\n/* return true if string contains at least one numerical digit */\nstatic inline bool\nhas_digit(const char *src)\n{\n    char c;\n    while ((c = *src++))\n    {\n        if (isdigit(c))\n        {\n            return true;\n        }\n    }\n    return false;\n}\n\n/**\n * Securely zeroise memory.\n *\n * This code and description are based on code supplied by Zhaomo Yang, of the\n * University of California, San Diego (which was released into the public\n * domain).\n *\n * The secure_memzero function attempts to ensure that an optimizing compiler\n * does not remove the intended operation if cleared memory is not accessed\n * again by the program. This code has been tested under Clang 3.9.0 and GCC\n * 6.2 with optimization flags -O, -Os, -O0, -O1, -O2, and -O3 on\n * Ubuntu 16.04.1 LTS; under Clang 3.9.0 with optimization flags -O, -Os,\n * -O0, -O1, -O2, and -O3 on FreeBSD 10.2-RELEASE; under Microsoft Visual Studio\n * 2015 with optimization flags /O1, /O2 and /Ox on Windows 10.\n *\n * Theory of operation:\n *\n * 1. On Windows, use the SecureZeroMemory which ensures that data is\n *    overwritten.\n * 2. Under GCC or Clang, use a memory barrier, which forces the preceding\n *    memset to be carried out. The overhead of a memory barrier is usually\n *    negligible.\n * 3. If none of the above are available, use the volatile pointer\n *    technique to zero memory one byte at a time.\n *\n * @param data  Pointer to data to zeroise.\n * @param len   Length of data, in bytes.\n */\nstatic inline void\nsecure_memzero(void *data, size_t len)\n{\n#if defined(_WIN32)\n    SecureZeroMemory(data, len);\n#elif defined(__GNUC__) || defined(__clang__)\n    memset(data, 0, len);\n    __asm__ __volatile__(\"\" : : \"r\"(data) : \"memory\");\n#else\n    volatile char *p = (volatile char *)data;\n    while (len--)\n    {\n        *p++ = 0;\n    }\n#endif\n}\n\n/*\n * printf append to a buffer with overflow check,\n * due to usage of vsnprintf, it will leave space for\n * a final null character and thus use only\n * capacity - 1\n */\nbool buf_printf(struct buffer *buf, const char *format, ...)\n#ifdef __GNUC__\n#if __USE_MINGW_ANSI_STDIO\n    __attribute__((format(gnu_printf, 2, 3)))\n#else\n    __attribute__((format(__printf__, 2, 3)))\n#endif\n#endif\n    ;\n\n/*\n * puts append to a buffer with overflow check\n */\nbool buf_puts(struct buffer *buf, const char *str);\n\n\n/*\n * remove/add trailing characters\n */\n\nvoid buf_null_terminate(struct buffer *buf);\n\nvoid buf_chomp(struct buffer *buf);\n\nvoid buf_rmtail(struct buffer *buf, uint8_t remove);\n\n/*\n * non-buffer string functions\n */\nvoid chomp(char *str);\n\nvoid rm_trailing_chars(char *str, const char *what_to_delete);\n\nconst char *skip_leading_whitespace(const char *str);\n\nvoid string_null_terminate(char *str, int len, int capacity);\n\n/**\n * Write buffer contents to file.\n *\n * @param filename  The filename to write the buffer to.\n * @param buf       The buffer to write to the file.\n *\n * @return true on success, false otherwise.\n */\nbool buffer_write_file(const char *filename, const struct buffer *buf);\n\n/*\n * write a string to the end of a buffer that was\n * truncated by buf_printf\n */\nvoid buf_catrunc(struct buffer *buf, const char *str);\n\n/*\n * Parse a string based on a given delimiter char\n */\nbool buf_parse(struct buffer *buf, const int delim, char *line, const int size);\n\n/*\n * Hex dump -- Output a binary buffer to a hex string and return it.\n */\n#define FHE_SPACE_BREAK_MASK 0xFF  /* space_break parameter in lower 8 bits */\n#define FHE_CAPS             0x100 /* output hex in caps */\nchar *format_hex_ex(const uint8_t *data, size_t size, size_t maxoutput, unsigned int space_break_flags,\n                    const char *separator, struct gc_arena *gc);\n\nstatic inline char *\nformat_hex(const uint8_t *data, size_t size, size_t maxoutput, struct gc_arena *gc)\n{\n    return format_hex_ex(data, size, maxoutput, 4, \" \", gc);\n}\n\n/*\n * Return a buffer that is a subset of another buffer.\n */\nstruct buffer buf_sub(struct buffer *buf, int size, bool prepend);\n\n/*\n * Check if sufficient space to append to buffer.\n */\n\nstatic inline bool\nbuf_safe(const struct buffer *buf, size_t len)\n{\n    return buf_valid(buf) && buf_size_valid(len)\n           && buf->offset + buf->len + (int)len <= buf->capacity;\n}\n\nstatic inline bool\nbuf_safe_bidir(const struct buffer *buf, int len)\n{\n    if (buf_valid(buf) && buf_size_valid_signed(len))\n    {\n        int newlen = buf->len + len;\n        return newlen >= 0 && buf->offset + newlen <= buf->capacity;\n    }\n    else\n    {\n        return false;\n    }\n}\n\nstatic inline int\nbuf_forward_capacity(const struct buffer *buf)\n{\n    if (buf_valid(buf))\n    {\n        int ret = buf->capacity - (buf->offset + buf->len);\n        if (ret < 0)\n        {\n            ret = 0;\n        }\n        return ret;\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nstatic inline int\nbuf_forward_capacity_total(const struct buffer *buf)\n{\n    if (buf_valid(buf))\n    {\n        int ret = buf->capacity - buf->offset;\n        if (ret < 0)\n        {\n            ret = 0;\n        }\n        return ret;\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nstatic inline int\nbuf_reverse_capacity(const struct buffer *buf)\n{\n    if (buf_valid(buf))\n    {\n        return buf->offset;\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nstatic inline bool\nbuf_inc_len(struct buffer *buf, int inc)\n{\n    if (!buf_safe_bidir(buf, inc))\n    {\n        return false;\n    }\n    buf->len += inc;\n    return true;\n}\n\n/*\n * Make space to prepend to a buffer.\n * Return NULL if no space.\n */\n\nstatic inline uint8_t *\nbuf_prepend(struct buffer *buf, ssize_t size)\n{\n    if (!buf_valid(buf) || size < 0 || size > buf->offset)\n    {\n        return NULL;\n    }\n    buf->offset -= (int)size;\n    buf->len += (int)size;\n    return BPTR(buf);\n}\n\nstatic inline bool\nbuf_advance(struct buffer *buf, ssize_t size)\n{\n    if (!buf_valid(buf) || size < 0 || buf->len < size)\n    {\n        return false;\n    }\n    buf->offset += (int)size;\n    buf->len -= (int)size;\n    return true;\n}\n\n/*\n * Return a pointer to allocated space inside a buffer.\n * Return NULL if no space.\n */\n\nstatic inline uint8_t *\nbuf_write_alloc(struct buffer *buf, size_t size)\n{\n    uint8_t *ret;\n    if (!buf_safe(buf, size))\n    {\n        return NULL;\n    }\n    ret = BPTR(buf) + buf->len;\n    buf->len += (int)size;\n    return ret;\n}\n\nstatic inline uint8_t *\nbuf_read_alloc(struct buffer *buf, int size)\n{\n    uint8_t *ret;\n    if (size < 0 || buf->len < size)\n    {\n        return NULL;\n    }\n    ret = BPTR(buf);\n    buf->offset += size;\n    buf->len -= size;\n    return ret;\n}\n\nstatic inline bool\nbuf_write(struct buffer *dest, const void *src, size_t size)\n{\n    uint8_t *cp = buf_write_alloc(dest, size);\n    if (!cp)\n    {\n        return false;\n    }\n    memcpy(cp, src, size);\n    return true;\n}\n\nstatic inline bool\nbuf_write_prepend(struct buffer *dest, const void *src, int size)\n{\n    uint8_t *cp = buf_prepend(dest, size);\n    if (!cp)\n    {\n        return false;\n    }\n    memcpy(cp, src, size);\n    return true;\n}\n\nstatic inline bool\nbuf_write_u8(struct buffer *dest, uint8_t data)\n{\n    return buf_write(dest, &data, sizeof(uint8_t));\n}\n\nstatic inline bool\nbuf_write_u16(struct buffer *dest, uint16_t data)\n{\n    uint16_t u16 = htons(data);\n    return buf_write(dest, &u16, sizeof(uint16_t));\n}\n\nstatic inline bool\nbuf_write_u32(struct buffer *dest, uint32_t data)\n{\n    uint32_t u32 = htonl(data);\n    return buf_write(dest, &u32, sizeof(uint32_t));\n}\n\nstatic inline bool\nbuf_copy(struct buffer *dest, const struct buffer *src)\n{\n    return buf_write(dest, BPTR(src), BLENZ(src));\n}\n\nstatic inline bool\nbuf_copy_n(struct buffer *dest, struct buffer *src, int n)\n{\n    uint8_t *cp = buf_read_alloc(src, n);\n    if (!cp)\n    {\n        return false;\n    }\n    return buf_write(dest, cp, n);\n}\n\nstatic inline bool\nbuf_copy_range(struct buffer *dest, int dest_index, const struct buffer *src, int src_index,\n               int src_len)\n{\n    if (src_index < 0 || src_len < 0 || src_index + src_len > src->len || dest_index < 0\n        || dest->offset + dest_index + src_len > dest->capacity)\n    {\n        return false;\n    }\n    memcpy(dest->data + dest->offset + dest_index, src->data + src->offset + src_index, src_len);\n    if (dest_index + src_len > dest->len)\n    {\n        dest->len = dest_index + src_len;\n    }\n    return true;\n}\n\n/* truncate src to len, copy excess data beyond len to dest */\nstatic inline bool\nbuf_copy_excess(struct buffer *dest, struct buffer *src, int len)\n{\n    if (len < 0)\n    {\n        return false;\n    }\n    if (src->len > len)\n    {\n        struct buffer b = *src;\n        src->len = len;\n        if (!buf_advance(&b, len))\n        {\n            return false;\n        }\n        return buf_copy(dest, &b);\n    }\n    else\n    {\n        return true;\n    }\n}\n\nstatic inline bool\nbuf_read(struct buffer *src, void *dest, int size)\n{\n    uint8_t *cp = buf_read_alloc(src, size);\n    if (!cp)\n    {\n        return false;\n    }\n    memcpy(dest, cp, size);\n    return true;\n}\n\nstatic inline int\nbuf_peek_u8(struct buffer *buf)\n{\n    int ret;\n    if (BLEN(buf) < 1)\n    {\n        return -1;\n    }\n    ret = *BPTR(buf);\n    return ret;\n}\n\nstatic inline int\nbuf_read_u8(struct buffer *buf)\n{\n    int ret = buf_peek_u8(buf);\n    if (ret >= 0)\n    {\n        buf_advance(buf, 1);\n    }\n    return ret;\n}\n\nstatic inline int\nbuf_read_u16(struct buffer *buf)\n{\n    uint16_t ret;\n    if (!buf_read(buf, &ret, sizeof(uint16_t)))\n    {\n        return -1;\n    }\n    return ntohs(ret);\n}\n\nstatic inline uint32_t\nbuf_read_u32(struct buffer *buf, bool *good)\n{\n    uint32_t ret;\n    if (!buf_read(buf, &ret, sizeof(uint32_t)))\n    {\n        if (good)\n        {\n            *good = false;\n        }\n        return 0;\n    }\n    else\n    {\n        if (good)\n        {\n            *good = true;\n        }\n        return ntohl(ret);\n    }\n}\n\n/** Return true if buffer contents are equal */\nstatic inline bool\nbuf_equal(const struct buffer *a, const struct buffer *b)\n{\n    return BLEN(a) == BLEN(b) && 0 == memcmp(BPTR(a), BPTR(b), BLENZ(a));\n}\n\n/**\n * Compare src buffer contents with match.\n * *NOT* constant time. Do not use when comparing HMACs.\n */\nstatic inline bool\nbuf_string_match(const struct buffer *src, const void *match, int size)\n{\n    if (size != src->len)\n    {\n        return false;\n    }\n    return memcmp(BPTR(src), match, size) == 0;\n}\n\n/**\n * Compare first size bytes of src buffer contents with match.\n * *NOT* constant time. Do not use when comparing HMACs.\n */\nstatic inline bool\nbuf_string_match_head(const struct buffer *src, const void *match, int size)\n{\n    if (size < 0 || size > src->len)\n    {\n        return false;\n    }\n    return memcmp(BPTR(src), match, size) == 0;\n}\n\nbool buf_string_match_head_str(const struct buffer *src, const char *match);\n\nbool buf_string_compare_advance(struct buffer *src, const char *match);\n\nint buf_substring_len(const struct buffer *buf, int delim);\n\n/*\n * Print a string which might be NULL\n */\nconst char *np(const char *str);\n\n/* character classes */\n\n#define CC_ANY  (1 << 0)           /**< any character */\n#define CC_NULL (1 << 1)           /**< null character \\0 */\n\n#define CC_ALNUM  (1 << 2)         /**< alphanumeric isalnum() */\n#define CC_ALPHA  (1 << 3)         /**< alphabetic isalpha() */\n#define CC_ASCII  (1 << 4)         /**< ASCII character */\n#define CC_CNTRL  (1 << 5)         /**< control character iscntrl() */\n#define CC_DIGIT  (1 << 6)         /**< digit isdigit() */\n#define CC_PRINT  (1 << 7)         /**< printable (>= 32, != 127) */\n#define CC_PUNCT  (1 << 8)         /**< punctuation ispunct() */\n#define CC_SPACE  (1 << 9)         /**< whitespace isspace() */\n#define CC_XDIGIT (1 << 10)        /**< hex digit isxdigit() */\n\n#define CC_BLANK   (1 << 11)       /**< space or tab */\n#define CC_NEWLINE (1 << 12)       /**< newline */\n#define CC_CR      (1 << 13)       /**< carriage return */\n\n#define CC_BACKSLASH     (1 << 14) /**< backslash */\n#define CC_UNDERBAR      (1 << 15) /**< underscore */\n#define CC_DASH          (1 << 16) /**< dash */\n#define CC_DOT           (1 << 17) /**< dot */\n#define CC_COMMA         (1 << 18) /**< comma */\n#define CC_COLON         (1 << 19) /**< colon */\n#define CC_SLASH         (1 << 20) /**< slash */\n#define CC_SINGLE_QUOTE  (1 << 21) /**< single quote */\n#define CC_DOUBLE_QUOTE  (1 << 22) /**< double quote */\n#define CC_REVERSE_QUOTE (1 << 23) /**< reverse quote */\n#define CC_AT            (1 << 24) /**< at sign */\n#define CC_EQUAL         (1 << 25) /**< equal sign */\n#define CC_LESS_THAN     (1 << 26) /**< less than sign */\n#define CC_GREATER_THAN  (1 << 27) /**< greater than sign */\n#define CC_PIPE          (1 << 28) /**< pipe */\n#define CC_QUESTION_MARK (1 << 29) /**< question mark */\n#define CC_ASTERISK      (1 << 30) /**< asterisk */\n\n/* macro classes */\n#define CC_NAME (CC_ALNUM | CC_UNDERBAR) /**< alphanumeric plus underscore */\n#define CC_CRLF (CC_CR | CC_NEWLINE)     /**< carriage return or newline */\n\nbool char_class(const unsigned char c, const unsigned int flags);\n\nbool string_class(const char *str, const unsigned int inclusive, const unsigned int exclusive);\n\n/**\n * Modifies a string in place by replacing certain classes of characters of it with a specified\n * character.\n *\n * Guaranteed to not increase the length of the string.\n * If replace is 0, characters are skipped instead of replaced.\n *\n * @param str The string to be modified.\n * @param inclusive The character classes not to be replaced.\n * @param exclusive Character classes to be replaced even if they are also in inclusive.\n * @param replace The character to replace the specified character classes with.\n * @return True if the string was not modified, false otherwise.\n */\nbool string_mod(char *str, const unsigned int inclusive, const unsigned int exclusive,\n                const char replace);\n\n\n/**\n * Check a buffer if it only consists of allowed characters.\n *\n * @param buf The buffer to be checked.\n * @param inclusive The character classes that are allowed.\n * @param exclusive Character classes that are not allowed even if they are also in inclusive.\n * @return True if the string consists only of allowed characters, false otherwise.\n */\nbool string_check_buf(struct buffer *buf, const unsigned int inclusive,\n                      const unsigned int exclusive);\n\n/**\n * Returns a copy of a string with certain classes of characters of it replaced with a specified\n * character.\n *\n * If replace is 0, characters are skipped instead of replaced.\n *\n * @param str       The input string to be modified.\n * @param inclusive Character classes not to be replaced.\n * @param exclusive Character classes to be replaced even if they are also in inclusive.\n * @param replace   The character to replace the specified character classes with.\n * @param gc        The garbage collector arena to allocate memory from.\n *\n * @return The modified string with characters replaced within the specified range.\n */\nconst char *string_mod_const(const char *str, const unsigned int inclusive,\n                             const unsigned int exclusive, const char replace, struct gc_arena *gc);\n\nvoid string_replace_leading(char *str, const char match, const char replace);\n\n/** Return true iff str starts with prefix */\nstatic inline bool\nstrprefix(const char *str, const char *prefix)\n{\n    return 0 == strncmp(str, prefix, strlen(prefix));\n}\n\n/**\n * Like snprintf() but returns an boolean.\n *\n * To check the return value of snprintf() one needs to\n * do multiple comparisons of the \\p size parameter\n * against the return value. Doesn't get prettier by\n * them being different types with different signedness\n * and size.\n *\n * So this function allows to wrap all of that into one\n * boolean return value.\n *\n * @return true if snprintf() was successful and not truncated.\n */\nbool checked_snprintf(char *str, size_t size, const char *format, ...)\n#ifdef __GNUC__\n#if __USE_MINGW_ANSI_STDIO\n    __attribute__((format(gnu_printf, 3, 4)))\n#else\n    __attribute__((format(__printf__, 3, 4)))\n#endif\n#endif\n    ;\n\n/*\n * Verify that a pointer is correctly aligned\n */\n#ifdef VERIFY_ALIGNMENT\nvoid valign4(const struct buffer *buf, const char *file, const int line);\n\n#define verify_align_4(ptr) valign4(buf, __FILE__, __LINE__)\n#else\n#define verify_align_4(ptr)\n#endif\n\n/*\n * Very basic garbage collection, mostly for routines that return\n * char ptrs to malloced strings.\n */\n\nvoid gc_transfer(struct gc_arena *dest, struct gc_arena *src);\n\nvoid x_gc_free(struct gc_arena *a);\n\nvoid x_gc_freespecial(struct gc_arena *a);\n\nstatic inline bool\ngc_defined(struct gc_arena *a)\n{\n    return a->list != NULL;\n}\n\nstatic inline void\ngc_init(struct gc_arena *a)\n{\n    a->list = NULL;\n    a->list_special = NULL;\n}\n\nstatic inline void\ngc_detach(struct gc_arena *a)\n{\n    gc_init(a);\n}\n\nstatic inline struct gc_arena\ngc_new(void)\n{\n    struct gc_arena ret;\n    gc_init(&ret);\n    return ret;\n}\n\nstatic inline void\ngc_free(struct gc_arena *a)\n{\n    if (a->list)\n    {\n        x_gc_free(a);\n    }\n    if (a->list_special)\n    {\n        x_gc_freespecial(a);\n    }\n}\n\nstatic inline void\ngc_reset(struct gc_arena *a)\n{\n    gc_free(a);\n}\n\n/*\n * Allocate memory to hold a structure\n */\n\n/* When allocating arrays make sure we do not use a excessive amount\n * of memory.\n */\n#if UINTPTR_MAX <= UINT32_MAX\n/* 1 GB on 32bit systems, they usually can only allocate 2 GB for the\n * whole process.\n */\n#define ALLOC_SIZE_MAX (1u << 30)\n#else\n#define ALLOC_SIZE_MAX ((size_t)1 << 32) /* 4 GB */\n#endif\n\n#define ALLOC_OBJ(dptr, type)                                       \\\n    {                                                               \\\n        check_malloc_return((dptr) = (type *)malloc(sizeof(type))); \\\n    }\n\n#define ALLOC_OBJ_CLEAR(dptr, type)      \\\n    {                                    \\\n        ALLOC_OBJ(dptr, type);           \\\n        memset((dptr), 0, sizeof(type)); \\\n    }\n\n#define ALLOC_ARRAY(dptr, type, n)                                                           \\\n    {                                                                                        \\\n        check_malloc_return((dptr) = (type *)malloc(array_mult_safe(sizeof(type), (n), 0))); \\\n    }\n\n#define ALLOC_ARRAY_GC(dptr, type, n, gc)                                               \\\n    {                                                                                   \\\n        (dptr) = (type *)gc_malloc(array_mult_safe(sizeof(type), (n), 0), false, (gc)); \\\n    }\n\n#define ALLOC_ARRAY_CLEAR(dptr, type, n)                            \\\n    {                                                               \\\n        ALLOC_ARRAY(dptr, type, n);                                 \\\n        memset((dptr), 0, (array_mult_safe(sizeof(type), (n), 0))); \\\n    }\n\n#define ALLOC_ARRAY_CLEAR_GC(dptr, type, n, gc)                                        \\\n    {                                                                                  \\\n        (dptr) = (type *)gc_malloc(array_mult_safe(sizeof(type), (n), 0), true, (gc)); \\\n    }\n\n#define ALLOC_VAR_ARRAY_CLEAR_GC(dptr, type, atype, n, gc)                                         \\\n    {                                                                                              \\\n        (dptr) = (type *)gc_malloc(array_mult_safe(sizeof(atype), (n), sizeof(type)), true, (gc)); \\\n    }\n\n#define ALLOC_OBJ_GC(dptr, type, gc)                           \\\n    {                                                          \\\n        (dptr) = (type *)gc_malloc(sizeof(type), false, (gc)); \\\n    }\n\n#define ALLOC_OBJ_CLEAR_GC(dptr, type, gc)                    \\\n    {                                                         \\\n        (dptr) = (type *)gc_malloc(sizeof(type), true, (gc)); \\\n    }\n\nstatic inline void\ncheck_malloc_return(void *p)\n{\n    if (!p)\n    {\n        out_of_memory();\n    }\n}\n\n/*\n * Manage lists of buffers\n */\nstruct buffer_entry\n{\n    struct buffer buf;\n    struct buffer_entry *next;\n};\n\nstruct buffer_list\n{\n    struct buffer_entry *head; /* next item to pop/peek */\n    struct buffer_entry *tail; /* last item pushed */\n    int size;                  /* current number of entries */\n    int max_size;              /* maximum size list should grow to */\n};\n\n/**\n * Allocate an empty buffer list of capacity \\c max_size.\n *\n * @return the new list\n */\nstruct buffer_list *buffer_list_new(void);\n\n/**\n * Frees a buffer list and all the buffers in it.\n *\n * @param ol    the list to free\n */\nvoid buffer_list_free(struct buffer_list *ol);\n\n/**\n * Checks if the list is valid and non-empty\n *\n * @param ol    the list to check\n *\n * @return true iff \\c ol is not NULL and contains at least one buffer\n */\nbool buffer_list_defined(const struct buffer_list *ol);\n\n/**\n * Empty the list \\c ol and frees all the contained buffers\n *\n * @param ol    the list to reset\n */\nvoid buffer_list_reset(struct buffer_list *ol);\n\n/**\n * Allocates and appends a new buffer containing \\c str as data to \\c ol\n *\n * @param ol    the list to append the new buffer to\n * @param str   the string to copy into the new buffer\n */\nvoid buffer_list_push(struct buffer_list *ol, const char *str);\n\n/**\n * Allocates and appends a new buffer containing \\c data of length \\c size.\n *\n * @param ol    the list to append the new buffer to\n * @param data  the data to copy into the new buffer\n * @param size  the length of \\c data to copy into the buffer\n *\n * @return the new buffer\n */\nstruct buffer_entry *buffer_list_push_data(struct buffer_list *ol, const void *data, size_t size);\n\n/**\n * Retrieve the head buffer\n *\n * @param ol    the list to retrieve the buffer from\n *\n * @return a pointer to the head buffer or NULL if the list is empty\n */\nstruct buffer *buffer_list_peek(struct buffer_list *ol);\n\nvoid buffer_list_advance(struct buffer_list *ol, ssize_t n);\n\nvoid buffer_list_pop(struct buffer_list *ol);\n\n/**\n * Aggregates as many buffers as possible from \\c bl in a new buffer of maximum\n * length \\c max_len .\n * All the aggregated buffers are removed from the list and replaced by the new\n * one, followed by any additional (non-aggregated) data.\n *\n * @param bl    the list of buffer to aggregate\n * @param max   the maximum length of the aggregated buffer\n */\nvoid buffer_list_aggregate(struct buffer_list *bl, const size_t max);\n\n/**\n * Aggregates as many buffers as possible from \\c bl in a new buffer\n * of maximum length \\c max_len . \\c sep is written after\n * each copied buffer (also after the last one). All the aggregated buffers are\n * removed from the list and replaced by the new one, followed by any additional\n * (non-aggregated) data.\n * Nothing happens if \\c max_len is not enough to aggregate at least 2 buffers.\n *\n * @param bl        the list of buffer to aggregate\n * @param max_len   the maximum length of the aggregated buffer\n * @param sep       the separator to put between buffers during aggregation\n */\nvoid buffer_list_aggregate_separator(struct buffer_list *bl, const size_t max_len, const char *sep);\n\nstruct buffer_list *buffer_list_file(const char *fn, int max_line_len);\n\n/**\n * buffer_read_from_file - copy the content of a file into a buffer\n *\n * @param filename  path to the file to read\n * @param gc        the garbage collector to use when allocating the buffer. It\n *                  is passed to alloc_buf_gc() and therefore can be NULL.\n *\n * @return the buffer storing the file content or an invalid buffer in case of\n * error\n */\nstruct buffer buffer_read_from_file(const char *filename, struct gc_arena *gc);\n\n#endif /* BUFFER_H */\n"
  },
  {
    "path": "src/openvpn/circ_list.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef CIRC_LIST_H\n#define CIRC_LIST_H\n\n#include \"basic.h\"\n#include \"integer.h\"\n#include \"error.h\"\n\n#define CIRC_LIST(name, type) \\\n    struct name               \\\n    {                         \\\n        int x_head;           \\\n        int x_size;           \\\n        int x_cap;            \\\n        int x_sizeof;         \\\n        type x_list[];        \\\n    }\n\n#define CIRC_LIST_PUSH(obj, item)                                    \\\n    {                                                                \\\n        (obj)->x_head = modulo_add((obj)->x_head, -1, (obj)->x_cap); \\\n        (obj)->x_list[(obj)->x_head] = (item);                       \\\n        (obj)->x_size = min_int((obj)->x_size + 1, (obj)->x_cap);    \\\n    }\n\n#define CIRC_LIST_SIZE(obj) ((obj)->x_size)\n\n#define CIRC_LIST_INDEX(obj, index)                                                     \\\n    modulo_add((obj)->x_head, index_verify((index), (obj)->x_size, __FILE__, __LINE__), \\\n               (obj)->x_cap)\n\n#define CIRC_LIST_ITEM(obj, index) ((obj)->x_list[CIRC_LIST_INDEX((obj), (index))])\n\n#define CIRC_LIST_RESET(obj) \\\n    {                        \\\n        (obj)->x_head = 0;   \\\n        (obj)->x_size = 0;   \\\n    }\n\n#define CIRC_LIST_ALLOC(dest, list_type, size)                                 \\\n    {                                                                          \\\n        const int so = sizeof(list_type) + sizeof((dest)->x_list[0]) * (size); \\\n        (dest) = (list_type *)malloc(so);                                      \\\n        check_malloc_return(dest);                                             \\\n        memset((dest), 0, so);                                                 \\\n        (dest)->x_cap = size;                                                  \\\n        (dest)->x_sizeof = so;                                                 \\\n    }\n\n#define CIRC_LIST_FREE(dest) free(dest)\n\n#endif /* ifndef CIRC_LIST_H */\n"
  },
  {
    "path": "src/openvpn/clinat.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"clinat.h\"\n#include \"proto.h\"\n#include \"socket_util.h\"\n#include \"memdbg.h\"\n\nstatic bool\nadd_entry(struct client_nat_option_list *dest, const struct client_nat_entry *e)\n{\n    if (dest->n >= MAX_CLIENT_NAT)\n    {\n        msg(M_WARN, \"WARNING: client-nat table overflow (max %d entries)\", MAX_CLIENT_NAT);\n        return false;\n    }\n    else\n    {\n        dest->entries[dest->n++] = *e;\n        return true;\n    }\n}\n\nvoid\nprint_client_nat_list(const struct client_nat_option_list *list, msglvl_t msglevel)\n{\n    struct gc_arena gc = gc_new();\n    int i;\n\n    msg(msglevel, \"*** CNAT list\");\n    if (list)\n    {\n        for (i = 0; i < list->n; ++i)\n        {\n            const struct client_nat_entry *e = &list->entries[i];\n            msg(msglevel, \"  CNAT[%d] t=%d %s/%s/%s\", i, e->type,\n                print_in_addr_t(e->network, IA_NET_ORDER, &gc),\n                print_in_addr_t(e->netmask, IA_NET_ORDER, &gc),\n                print_in_addr_t(e->foreign_network, IA_NET_ORDER, &gc));\n        }\n    }\n    gc_free(&gc);\n}\n\nstruct client_nat_option_list *\nnew_client_nat_list(struct gc_arena *gc)\n{\n    struct client_nat_option_list *ret;\n    ALLOC_OBJ_CLEAR_GC(ret, struct client_nat_option_list, gc);\n    return ret;\n}\n\nstruct client_nat_option_list *\nclone_client_nat_option_list(const struct client_nat_option_list *src, struct gc_arena *gc)\n{\n    struct client_nat_option_list *ret;\n    ALLOC_OBJ_GC(ret, struct client_nat_option_list, gc);\n    *ret = *src;\n    return ret;\n}\n\nvoid\ncopy_client_nat_option_list(struct client_nat_option_list *dest,\n                            const struct client_nat_option_list *src)\n{\n    int i;\n    for (i = 0; i < src->n; ++i)\n    {\n        if (!add_entry(dest, &src->entries[i]))\n        {\n            break;\n        }\n    }\n}\n\nvoid\nadd_client_nat_to_option_list(struct client_nat_option_list *dest, const char *type,\n                              const char *network, const char *netmask, const char *foreign_network,\n                              msglvl_t msglevel)\n{\n    struct client_nat_entry e;\n    bool ok;\n\n    if (!strcmp(type, \"snat\"))\n    {\n        e.type = CN_SNAT;\n    }\n    else if (!strcmp(type, \"dnat\"))\n    {\n        e.type = CN_DNAT;\n    }\n    else\n    {\n        msg(msglevel, \"client-nat: type must be 'snat' or 'dnat'\");\n        return;\n    }\n\n    e.network = getaddr(0, network, 0, &ok, NULL);\n    if (!ok)\n    {\n        msg(msglevel, \"client-nat: bad network: %s\", network);\n        return;\n    }\n    e.netmask = getaddr(0, netmask, 0, &ok, NULL);\n    if (!ok)\n    {\n        msg(msglevel, \"client-nat: bad netmask: %s\", netmask);\n        return;\n    }\n    e.foreign_network = getaddr(0, foreign_network, 0, &ok, NULL);\n    if (!ok)\n    {\n        msg(msglevel, \"client-nat: bad foreign network: %s\", foreign_network);\n        return;\n    }\n\n    add_entry(dest, &e);\n}\n\n#if 0\nstatic void\nprint_checksum(struct openvpn_iphdr *iph, const char *prefix)\n{\n    uint16_t *sptr;\n    unsigned int sum = 0;\n    int i = 0;\n    for (sptr = (uint16_t *)iph; (uint8_t *)sptr < (uint8_t *)iph + sizeof(struct openvpn_iphdr); sptr++)\n    {\n        i += 1;\n        sum += *sptr;\n    }\n    msg(M_INFO, \"** CKSUM[%d] %s %08x\", i, prefix, sum);\n}\n#endif\n\nstatic void\nprint_pkt(struct openvpn_iphdr *iph, const char *prefix, const int direction, const msglvl_t msglevel)\n{\n    struct gc_arena gc = gc_new();\n\n    char *dirstr = \"???\";\n    if (direction == CN_OUTGOING)\n    {\n        dirstr = \"OUT\";\n    }\n    else if (direction == CN_INCOMING)\n    {\n        dirstr = \"IN\";\n    }\n\n    msg(msglevel, \"** CNAT %s %s %s -> %s\", dirstr, prefix,\n        print_in_addr_t(iph->saddr, IA_NET_ORDER, &gc),\n        print_in_addr_t(iph->daddr, IA_NET_ORDER, &gc));\n\n    gc_free(&gc);\n}\n\nvoid\nclient_nat_transform(const struct client_nat_option_list *list, struct buffer *ipbuf,\n                     const int direction)\n{\n    struct ip_tcp_udp_hdr *h = (struct ip_tcp_udp_hdr *)BPTR(ipbuf);\n    int32_t accumulate = 0;\n    unsigned int alog = 0;\n\n    if (check_debug_level(D_CLIENT_NAT))\n    {\n        print_pkt(&h->ip, \"BEFORE\", direction, D_CLIENT_NAT);\n    }\n\n    for (int i = 0; i < list->n; ++i)\n    {\n        uint32_t addr, *addr_ptr;\n        const uint32_t *from, *to;\n        unsigned int amask;\n        const struct client_nat_entry *e = &list->entries[i]; /* current NAT rule */\n        if (e->type ^ direction)\n        {\n            addr = *(addr_ptr = &h->ip.daddr);\n            amask = 2;\n        }\n        else\n        {\n            addr = *(addr_ptr = &h->ip.saddr);\n            amask = 1;\n        }\n        if (direction)\n        {\n            from = &e->foreign_network;\n            to = &e->network;\n        }\n        else\n        {\n            from = &e->network;\n            to = &e->foreign_network;\n        }\n\n        if (((addr & e->netmask) == *from) && !(amask & alog))\n        {\n            /* pre-adjust IP checksum */\n            ADD_CHECKSUM_32(accumulate, addr);\n\n            /* do NAT transform */\n            addr = (addr & ~e->netmask) | *to;\n\n            /* post-adjust IP checksum */\n            SUB_CHECKSUM_32(accumulate, addr);\n\n            /* write the modified address to packet */\n            *addr_ptr = addr;\n\n            /* mark as modified */\n            alog |= amask;\n        }\n    }\n    if (alog)\n    {\n        if (check_debug_level(D_CLIENT_NAT))\n        {\n            print_pkt(&h->ip, \"AFTER\", direction, D_CLIENT_NAT);\n        }\n\n        ADJUST_CHECKSUM(accumulate, h->ip.check);\n\n        if (h->ip.protocol == OPENVPN_IPPROTO_TCP)\n        {\n            if (BLENZ(ipbuf) >= sizeof(struct openvpn_iphdr) + sizeof(struct openvpn_tcphdr))\n            {\n                ADJUST_CHECKSUM(accumulate, h->u.tcp.check);\n            }\n        }\n        else if (h->ip.protocol == OPENVPN_IPPROTO_UDP)\n        {\n            if (BLENZ(ipbuf) >= sizeof(struct openvpn_iphdr) + sizeof(struct openvpn_udphdr))\n            {\n                ADJUST_CHECKSUM(accumulate, h->u.udp.check);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/openvpn/clinat.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#if !defined(CLINAT_H)\n#define CLINAT_H\n\n#include \"buffer.h\"\n\n#define MAX_CLIENT_NAT 64\n\n#define CN_OUTGOING 0\n#define CN_INCOMING 1\n\nstruct client_nat_entry\n{\n#define CN_SNAT 0\n#define CN_DNAT 1\n    int type;\n    in_addr_t network;\n    in_addr_t netmask;\n    in_addr_t foreign_network;\n};\n\nstruct client_nat_option_list\n{\n    int n;\n    struct client_nat_entry entries[MAX_CLIENT_NAT];\n};\n\nstruct client_nat_option_list *new_client_nat_list(struct gc_arena *gc);\n\nstruct client_nat_option_list *clone_client_nat_option_list(\n    const struct client_nat_option_list *src, struct gc_arena *gc);\n\nvoid copy_client_nat_option_list(struct client_nat_option_list *dest,\n                                 const struct client_nat_option_list *src);\n\nvoid print_client_nat_list(const struct client_nat_option_list *list, msglvl_t msglevel);\n\nvoid add_client_nat_to_option_list(struct client_nat_option_list *dest, const char *type,\n                                   const char *network, const char *netmask,\n                                   const char *foreign_network, msglvl_t msglevel);\n\nvoid client_nat_transform(const struct client_nat_option_list *list, struct buffer *ipbuf,\n                          const int direction);\n\n#endif /* if !defined(CLINAT_H) */\n"
  },
  {
    "path": "src/openvpn/common.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef COMMON_H\n#define COMMON_H\n\n#include <stdint.h>\n\n/*\n * Statistics counters and associated printf format.\n */\ntypedef uint64_t counter_type;\n#define counter_format \"%\" PRIu64\n\n/*\n * Time intervals\n */\ntypedef int interval_t;\n\n/*\n * Used as an upper bound for timeouts.\n */\n#define BIG_TIMEOUT (60 * 60 * 24 * 7) /* one week (in seconds) */\n\n/*\n * Printf formats for special types\n */\n#ifdef _WIN64\n#define ptr_format \"0x%016\" PRIx64\n#else\n#define ptr_format \"0x%08lx\"\n#endif\n#define fragment_header_format \"0x%08x\"\n\n/* these are used to cast the arguments\n * and MUST match the formats above */\n#ifdef _WIN64\ntypedef unsigned long long ptr_type;\n#else\ntypedef unsigned long ptr_type;\n#endif\n\n/* the --client-config-dir default file */\n#define CCD_DEFAULT \"DEFAULT\"\n\n/*\n * This parameter controls the TLS channel buffer size and the\n * maximum size of a single TLS message (cleartext).\n * This parameter must be >= PUSH_BUNDLE_SIZE\n */\n#define TLS_CHANNEL_BUF_SIZE 2048\n\n/* TLS control buffer minimum size\n *\n * A control frame might have IPv6 header (40 byte),\n * UDP (8 byte), opcode (1), session id (8),\n * ACK array with 4 ACKs in non-ACK_V1 packets (25 bytes)\n * tls-crypt(56) or tls-auth(up to 72). To allow secure\n * renegotiation (dynamic tls-crypt), we set this minimum\n * to 154, which only allows 16 byte of payload and should\n * be considered an absolute minimum and not a good value to\n * set\n */\n#define TLS_CHANNEL_MTU_MIN 154\n\n/*\n * This parameter controls the maximum size of a bundle\n * of pushed options.\n */\n#define PUSH_BUNDLE_SIZE 1024\n\n/*\n * In how many seconds does client re-send PUSH_REQUEST if we haven't yet received a reply\n */\n#define PUSH_REQUEST_INTERVAL 5\n\n/*\n * Script security warning\n */\n#define SCRIPT_SECURITY_WARNING \\\n    \"WARNING: External program may not be called unless '--script-security 2' or higher is enabled. See --help text or man page for detailed info.\"\n\n#endif /* ifndef COMMON_H */\n"
  },
  {
    "path": "src/openvpn/comp-lz4.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2013-2026 Gert Doering <gert@greenie.muc.de>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if defined(ENABLE_LZ4)\n#include <lz4.h>\n\n#include \"comp.h\"\n#include \"error.h\"\n\n#include \"memdbg.h\"\n\n\nstatic void\nlz4_compress_init(struct compress_context *compctx)\n{\n    msg(D_INIT_MEDIUM, \"LZ4 compression initializing\");\n    ASSERT(compctx->flags & COMP_F_SWAP);\n}\n\nstatic void\nlz4v2_compress_init(struct compress_context *compctx)\n{\n    msg(D_INIT_MEDIUM, \"LZ4v2 compression initializing\");\n}\n\nstatic void\nlz4_compress_uninit(struct compress_context *compctx)\n{\n}\n\n/* Doesn't do any actual compression anymore */\nstatic void\nlz4_compress(struct buffer *buf, struct buffer work, struct compress_context *compctx,\n             const struct frame *frame)\n{\n    if (buf->len <= 0)\n    {\n        return;\n    }\n\n    uint8_t comp_head_byte = NO_COMPRESS_BYTE_SWAP;\n    uint8_t *head = BPTR(buf);\n    uint8_t *tail = BEND(buf);\n    ASSERT(buf_safe(buf, 1));\n    ++buf->len;\n\n    /* move head byte of payload to tail */\n    *tail = *head;\n    *head = comp_head_byte;\n}\n\n/* Doesn't do any actual compression anymore */\nstatic void\nlz4v2_compress(struct buffer *buf, struct buffer work, struct compress_context *compctx,\n               const struct frame *frame)\n{\n    if (buf->len <= 0)\n    {\n        return;\n    }\n\n    compv2_escape_data_ifneeded(buf);\n}\n\nstatic void\ndo_lz4_decompress(int zlen_max, struct buffer *work, struct buffer *buf,\n                  struct compress_context *compctx)\n{\n    ASSERT(buf_safe(work, zlen_max));\n    int uncomp_len = LZ4_decompress_safe((const char *)BPTR(buf), (char *)BPTR(work), BLEN(buf),\n                                         zlen_max);\n    if (uncomp_len <= 0)\n    {\n        dmsg(D_COMP_ERRORS, \"LZ4 decompression error: %d\", uncomp_len);\n        buf->len = 0;\n        return;\n    }\n\n    ASSERT(buf_safe(work, uncomp_len));\n    work->len = uncomp_len;\n\n    dmsg(D_COMP, \"LZ4 decompress %d -> %d\", buf->len, work->len);\n    compctx->pre_decompress += buf->len;\n    compctx->post_decompress += work->len;\n\n    *buf = *work;\n}\n\nstatic void\nlz4_decompress(struct buffer *buf, struct buffer work, struct compress_context *compctx,\n               const struct frame *frame)\n{\n    int zlen_max = frame->buf.payload_size;\n    uint8_t c; /* flag indicating whether or not our peer compressed */\n\n    if (buf->len <= 0)\n    {\n        return;\n    }\n\n    ASSERT(buf_init(&work, frame->buf.headroom));\n\n    /* do unframing/swap (assumes buf->len > 0) */\n    {\n        uint8_t *head = BPTR(buf);\n        c = *head;\n        --buf->len;\n        *head = *BEND(buf);\n    }\n\n    if (c == LZ4_COMPRESS_BYTE) /* packet was compressed */\n    {\n        do_lz4_decompress(zlen_max, &work, buf, compctx);\n    }\n    else if (c == NO_COMPRESS_BYTE_SWAP) /* packet was not compressed */\n    {\n        /* nothing to do */\n    }\n    else\n    {\n        dmsg(D_COMP_ERRORS, \"Bad LZ4 decompression header byte: %d\", c);\n        buf->len = 0;\n    }\n}\n\nstatic void\nlz4v2_decompress(struct buffer *buf, struct buffer work, struct compress_context *compctx,\n                 const struct frame *frame)\n{\n    int zlen_max = frame->buf.payload_size;\n    uint8_t c; /* flag indicating whether or not our peer compressed */\n\n    if (buf->len <= 0)\n    {\n        return;\n    }\n\n    ASSERT(buf_init(&work, frame->buf.headroom));\n\n    /* do unframing/swap (assumes buf->len > 0) */\n    uint8_t *head = BPTR(buf);\n    c = *head;\n\n    /* Not compressed */\n    if (c != COMP_ALGV2_INDICATOR_BYTE)\n    {\n        return;\n    }\n\n    /* Packet to short to make sense */\n    if (buf->len <= 1)\n    {\n        buf->len = 0;\n        return;\n    }\n\n    c = head[1];\n    if (c == COMP_ALGV2_LZ4_BYTE) /* packet was compressed */\n    {\n        buf_advance(buf, 2);\n        do_lz4_decompress(zlen_max, &work, buf, compctx);\n    }\n    else if (c == COMP_ALGV2_UNCOMPRESSED_BYTE)\n    {\n        buf_advance(buf, 2);\n    }\n    else\n    {\n        dmsg(D_COMP_ERRORS, \"Bad LZ4v2 decompression header byte: %d\", c);\n        buf->len = 0;\n    }\n}\n\nconst struct compress_alg lz4_alg = { \"lz4\", lz4_compress_init, lz4_compress_uninit, lz4_compress,\n                                      lz4_decompress };\n\nconst struct compress_alg lz4v2_alg = { \"lz4v2\", lz4v2_compress_init, lz4_compress_uninit,\n                                        lz4v2_compress, lz4v2_decompress };\n#endif /* ENABLE_LZ4 */\n"
  },
  {
    "path": "src/openvpn/comp-lz4.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2013-2026 Gert Doering <gert@greenie.muc.de>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef OPENVPN_COMP_LZ4_H\n#define OPENVPN_COMP_LZ4_H\n\n#if defined(ENABLE_LZ4)\n\n#include \"buffer.h\"\n\nextern const struct compress_alg lz4_alg;\nextern const struct compress_alg lz4v2_alg;\n\nstruct lz4_workspace\n{\n    int dummy;\n};\n\n#endif /* ENABLE_LZ4 */\n#endif\n"
  },
  {
    "path": "src/openvpn/comp.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"comp.h\"\n#include \"error.h\"\n\n#ifdef USE_COMP\n\n#include \"otime.h\"\n\n#include \"memdbg.h\"\n\nstruct compress_context *\ncomp_init(const struct compress_options *opt)\n{\n    struct compress_context *compctx = NULL;\n    switch (opt->alg)\n    {\n        case COMP_ALG_STUB:\n            ALLOC_OBJ_CLEAR(compctx, struct compress_context);\n            compctx->flags = opt->flags;\n            compctx->alg = comp_stub_alg;\n            break;\n\n        case COMP_ALGV2_UNCOMPRESSED:\n            ALLOC_OBJ_CLEAR(compctx, struct compress_context);\n            compctx->flags = opt->flags;\n            compctx->alg = compv2_stub_alg;\n            break;\n\n#ifdef ENABLE_LZO\n        case COMP_ALG_LZO:\n            ALLOC_OBJ_CLEAR(compctx, struct compress_context);\n            compctx->flags = opt->flags;\n            compctx->alg = lzo_alg;\n            break;\n\n#endif\n#ifdef ENABLE_LZ4\n        case COMP_ALG_LZ4:\n            ALLOC_OBJ_CLEAR(compctx, struct compress_context);\n            compctx->flags = opt->flags;\n            compctx->alg = lz4_alg;\n            break;\n\n        case COMP_ALGV2_LZ4:\n            ALLOC_OBJ_CLEAR(compctx, struct compress_context);\n            compctx->flags = opt->flags;\n            compctx->alg = lz4v2_alg;\n            break;\n#endif\n    }\n    if (compctx)\n    {\n        (*compctx->alg.compress_init)(compctx);\n    }\n\n    return compctx;\n}\n\n/* In the v2 compression schemes, an uncompressed packet has\n * has no opcode in front, unless the first byte is 0x50. In this\n * case the packet needs to be escaped */\nvoid\ncompv2_escape_data_ifneeded(struct buffer *buf)\n{\n    uint8_t *head = BPTR(buf);\n    if (head[0] != COMP_ALGV2_INDICATOR_BYTE)\n    {\n        return;\n    }\n\n    /* Header is 0x50 */\n    ASSERT(buf_prepend(buf, 2));\n\n    head = BPTR(buf);\n    head[0] = COMP_ALGV2_INDICATOR_BYTE;\n    head[1] = COMP_ALGV2_UNCOMPRESSED;\n}\n\n\nvoid\ncomp_uninit(struct compress_context *compctx)\n{\n    if (compctx)\n    {\n        (*compctx->alg.compress_uninit)(compctx);\n        free(compctx);\n    }\n}\n\nvoid\ncomp_print_stats(const struct compress_context *compctx, struct status_output *so)\n{\n    if (compctx)\n    {\n        status_printf(so, \"pre-compress bytes,\" counter_format, compctx->pre_compress);\n        status_printf(so, \"post-compress bytes,\" counter_format, compctx->post_compress);\n        status_printf(so, \"pre-decompress bytes,\" counter_format, compctx->pre_decompress);\n        status_printf(so, \"post-decompress bytes,\" counter_format, compctx->post_decompress);\n    }\n}\n\n/*\n * Tell our peer which compression algorithms we support.\n */\nvoid\ncomp_generate_peer_info_string(const struct compress_options *opt, struct buffer *out)\n{\n    if (!opt || opt->flags & COMP_F_ALLOW_NOCOMP_ONLY)\n    {\n        return;\n    }\n\n    bool lzo_avail = false;\n    if (!(opt->flags & COMP_F_ADVERTISE_STUBS_ONLY))\n    {\n#if defined(ENABLE_LZ4)\n        buf_printf(out, \"IV_LZ4=1\\n\");\n        buf_printf(out, \"IV_LZ4v2=1\\n\");\n#endif\n#if defined(ENABLE_LZO)\n        buf_printf(out, \"IV_LZO=1\\n\");\n        lzo_avail = true;\n#endif\n    }\n    if (!lzo_avail)\n    {\n        buf_printf(out, \"IV_LZO_STUB=1\\n\");\n    }\n    buf_printf(out, \"IV_COMP_STUB=1\\n\");\n    buf_printf(out, \"IV_COMP_STUBv2=1\\n\");\n}\n#endif /* USE_COMP */\n\nbool\ncheck_compression_settings_valid(struct compress_options *info, msglvl_t msglevel)\n{\n    /*\n     * We also allow comp-stub-v2 here as it technically allows escaping of\n     * weird mac address and IPv5 protocol but practically always is used\n     * as an way to disable all framing.\n     */\n    if (info->alg != COMP_ALGV2_UNCOMPRESSED && info->alg != COMP_ALG_UNDEF\n        && (info->flags & COMP_F_ALLOW_NOCOMP_ONLY))\n    {\n#ifdef USE_COMP\n        msg(msglevel, \"Compression or compression stub framing is not allowed \"\n                      \"since data-channel offloading is enabled.\");\n#else\n        msg(msglevel, \"Compression or compression stub framing is not allowed \"\n                      \"since OpenVPN was built without compression support.\");\n#endif\n        return false;\n    }\n\n    if ((info->flags & COMP_F_ALLOW_STUB_ONLY) && comp_non_stub_enabled(info))\n    {\n        msg(msglevel, \"Compression is not allowed since allow-compression is \"\n                      \"set to 'stub-only'\");\n        return false;\n    }\n#ifndef ENABLE_LZ4\n    if (info->alg == COMP_ALGV2_LZ4 || info->alg == COMP_ALG_LZ4)\n    {\n        msg(msglevel, \"OpenVPN is compiled without LZ4 support. Requested \"\n                      \"compression cannot be enabled.\");\n        return false;\n    }\n#endif\n#ifndef ENABLE_LZO\n    if (info->alg == COMP_ALG_LZO)\n    {\n        msg(msglevel, \"OpenVPN is compiled without LZO support. Requested \"\n                      \"compression cannot be enabled.\");\n        return false;\n    }\n#endif\n    return true;\n}\n"
  },
  {
    "path": "src/openvpn/comp.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * Generic compression support.  Currently we support\n * LZO 2 and LZ4.\n */\n#ifndef OPENVPN_COMP_H\n#define OPENVPN_COMP_H\n\n/* We always parse all compression options, so we include these defines/struct\n * outside of the USE_COMP define */\n\n/* Compression flags */\n/* Removed\n #define COMP_F_ADAPTIVE             (1u<<0) / * COMP_ALG_LZO only * /\n #define COMP_F_ALLOW_COMPRESS       (1u<<1) / * not only incoming is compressed but also outgoing *\n /\n */\n/** initial command byte is swapped with last byte in buffer to preserve payload alignment */\n#define COMP_F_SWAP                 (1u << 2)\n/** tell server that we only support compression stubs */\n#define COMP_F_ADVERTISE_STUBS_ONLY (1u << 3)\n/** Only accept stub compression, even with COMP_F_ADVERTISE_STUBS_ONLY\n * we still accept other compressions to be pushed */\n#define COMP_F_ALLOW_STUB_ONLY      (1u << 4)\n/** push stub-v2 or comp-lzo no when we see a client with comp-lzo in occ */\n#define COMP_F_MIGRATE              (1u << 5)\n/** Compression was explicitly set to allow asymetric compression */\n#define COMP_F_ALLOW_ASYM           (1u << 6)\n/** Do not allow compression framing (breaks DCO) */\n#define COMP_F_ALLOW_NOCOMP_ONLY    (1u << 7)\n\n/* algorithms */\n#define COMP_ALG_UNDEF  0\n/** support compression command byte and framing without actual compression */\n#define COMP_ALG_STUB   1\n#define COMP_ALG_LZO    2 /**< LZO algorithm */\n#define COMP_ALG_SNAPPY 3 /**< Snappy algorithm (no longer supported) */\n#define COMP_ALG_LZ4    4 /**< LZ4 algorithm */\n\n\n/* algorithm v2 */\n#define COMP_ALGV2_UNCOMPRESSED 10\n#define COMP_ALGV2_LZ4          11\n/*\n #define COMP_ALGV2_LZO     12\n #define COMP_ALGV2_SNAPPY   13\n */\n\n/*\n * Information that basically identifies a compression\n * algorithm and related flags.\n */\nstruct compress_options\n{\n    int alg;\n    unsigned int flags;\n};\n\nstatic inline bool\ncomp_non_stub_enabled(const struct compress_options *info)\n{\n    return info->alg != COMP_ALGV2_UNCOMPRESSED && info->alg != COMP_ALG_STUB\n           && info->alg != COMP_ALG_UNDEF;\n}\n\n#include \"error.h\"\n\n/**\n * Checks if the compression settings are valid. Takes into account the\n * flags of allow-compression and also the whether algorithms are compiled\n * in\n */\nbool check_compression_settings_valid(struct compress_options *info, msglvl_t msglevel);\n\n#ifdef USE_COMP\n#include \"buffer.h\"\n#include \"mtu.h\"\n#include \"common.h\"\n#include \"status.h\"\n\n/*\n * Length of prepended prefix on compressed packets\n */\n#define COMP_PREFIX_LEN 1\n\n/*\n * Prefix bytes\n */\n\n/* V1 on wire codes */\n/* Initial command byte to tell our peer if we compressed */\n#define LZO_COMPRESS_BYTE     0x66\n#define LZ4_COMPRESS_BYTE     0x69\n#define NO_COMPRESS_BYTE      0xFA\n/** to maintain payload alignment, replace this byte with last byte of packet */\n#define NO_COMPRESS_BYTE_SWAP 0xFB\n\n/* V2 on wire code */\n#define COMP_ALGV2_INDICATOR_BYTE    0x50\n#define COMP_ALGV2_UNCOMPRESSED_BYTE 0\n#define COMP_ALGV2_LZ4_BYTE          1\n#define COMP_ALGV2_LZO_BYTE          2\n#define COMP_ALGV2_SNAPPY_BYTE       3\n\n/*\n * Compress worst case size expansion (for any algorithm)\n *\n * LZO:    len + len/8 + 128 + 3\n * Snappy: len + len/6 + 32\n * LZ4:    len + len/255 + 16  (LZ4_COMPRESSBOUND(len))\n */\n#define COMP_EXTRA_BUFFER(len) ((len) / 6 + 128 + 3 + COMP_PREFIX_LEN)\n\n/*\n * Don't try to compress any packet smaller than this.\n */\n#define COMPRESS_THRESHOLD 100\n\n/* Forward declaration of compression context */\nstruct compress_context;\n\n/*\n * Virtual methods and other static info for each compression algorithm\n */\nstruct compress_alg\n{\n    const char *name;\n    void (*compress_init)(struct compress_context *compctx);\n    void (*compress_uninit)(struct compress_context *compctx);\n    void (*compress)(struct buffer *buf, struct buffer work, struct compress_context *compctx,\n                     const struct frame *frame);\n\n    void (*decompress)(struct buffer *buf, struct buffer work, struct compress_context *compctx,\n                       const struct frame *frame);\n};\n\n/*\n * Headers for each compression implementation\n */\n#ifdef ENABLE_LZO\n#include \"lzo.h\"\n#endif\n\n#ifdef ENABLE_LZ4\n#include \"comp-lz4.h\"\n#endif\n\n/*\n * Workspace union of all supported compression algorithms\n */\nunion compress_workspace_union\n{\n#ifdef ENABLE_LZO\n    struct lzo_compress_workspace lzo;\n#endif\n#ifdef ENABLE_LZ4\n    struct lz4_workspace lz4;\n#endif\n};\n\n/*\n * Context for active compression session\n */\nstruct compress_context\n{\n    unsigned int flags;\n    struct compress_alg alg;\n    union compress_workspace_union wu;\n\n    /* statistics */\n    counter_type pre_decompress;\n    counter_type post_decompress;\n    counter_type pre_compress;\n    counter_type post_compress;\n};\n\nextern const struct compress_alg comp_stub_alg;\nextern const struct compress_alg compv2_stub_alg;\n\nstruct compress_context *comp_init(const struct compress_options *opt);\n\nvoid comp_uninit(struct compress_context *compctx);\n\nvoid comp_print_stats(const struct compress_context *compctx, struct status_output *so);\n\nvoid comp_generate_peer_info_string(const struct compress_options *opt, struct buffer *out);\n\nvoid compv2_escape_data_ifneeded(struct buffer *buf);\n\nstatic inline bool\ncomp_enabled(const struct compress_options *info)\n{\n    return info->alg != COMP_ALG_UNDEF;\n}\n#endif /* USE_COMP */\n#endif /* ifndef OPENVPN_COMP_H */\n"
  },
  {
    "path": "src/openvpn/compstub.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if defined(USE_COMP)\n\n#include \"comp.h\"\n#include \"error.h\"\n#include \"otime.h\"\n\n#include \"memdbg.h\"\n\nstatic void\nstub_compress_init(struct compress_context *compctx)\n{\n}\n\nstatic void\nstub_compress_uninit(struct compress_context *compctx)\n{\n}\n\nstatic void\nstub_compress(struct buffer *buf, struct buffer work, struct compress_context *compctx,\n              const struct frame *frame)\n{\n    if (buf->len <= 0)\n    {\n        return;\n    }\n    if (compctx->flags & COMP_F_SWAP)\n    {\n        uint8_t *head = BPTR(buf);\n        uint8_t *tail = BEND(buf);\n        ASSERT(buf_safe(buf, 1));\n        ++buf->len;\n\n        /* move head byte of payload to tail */\n        *tail = *head;\n        *head = NO_COMPRESS_BYTE_SWAP;\n    }\n    else\n    {\n        uint8_t *header = buf_prepend(buf, 1);\n        *header = NO_COMPRESS_BYTE;\n    }\n}\n\nstatic void\nstub_decompress(struct buffer *buf, struct buffer work, struct compress_context *compctx,\n                const struct frame *frame)\n{\n    uint8_t c;\n    if (buf->len <= 0)\n    {\n        return;\n    }\n    if (compctx->flags & COMP_F_SWAP)\n    {\n        uint8_t *head = BPTR(buf);\n        c = *head;\n        --buf->len;\n        *head = *BEND(buf);\n        if (c != NO_COMPRESS_BYTE_SWAP)\n        {\n            dmsg(D_COMP_ERRORS, \"Bad compression stub (swap) decompression header byte: %d\", c);\n            buf->len = 0;\n        }\n    }\n    else\n    {\n        c = *BPTR(buf);\n        ASSERT(buf_advance(buf, 1));\n        if (c != NO_COMPRESS_BYTE)\n        {\n            dmsg(D_COMP_ERRORS, \"Bad compression stub decompression header byte: %d\", c);\n            buf->len = 0;\n        }\n    }\n}\n\n\nstatic void\nstubv2_compress(struct buffer *buf, struct buffer work, struct compress_context *compctx,\n                const struct frame *frame)\n{\n    if (buf->len <= 0)\n    {\n        return;\n    }\n\n    compv2_escape_data_ifneeded(buf);\n}\n\nstatic void\nstubv2_decompress(struct buffer *buf, struct buffer work, struct compress_context *compctx,\n                  const struct frame *frame)\n{\n    if (buf->len <= 0)\n    {\n        return;\n    }\n\n    uint8_t *head = BPTR(buf);\n\n    /* no compression or packet to short*/\n    if (head[0] != COMP_ALGV2_INDICATOR_BYTE)\n    {\n        return;\n    }\n\n    /* compression header (0x50) is present */\n    buf_advance(buf, 1);\n\n    /* Packet buffer too short (only 1 byte) */\n    if (buf->len <= 0)\n    {\n        return;\n    }\n\n    head = BPTR(buf);\n    buf_advance(buf, 1);\n\n    if (head[0] != COMP_ALGV2_UNCOMPRESSED_BYTE)\n    {\n        dmsg(D_COMP_ERRORS, \"Bad compression stubv2 decompression header byte: %d\", *head);\n        buf->len = 0;\n        return;\n    }\n}\n\nconst struct compress_alg compv2_stub_alg = { \"stubv2\", stub_compress_init, stub_compress_uninit,\n                                              stubv2_compress, stubv2_decompress };\n\nconst struct compress_alg comp_stub_alg = { \"stub\", stub_compress_init, stub_compress_uninit,\n                                            stub_compress, stub_decompress };\n#endif /* USE_STUB */\n"
  },
  {
    "path": "src/openvpn/console.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2014-2015 David Sommerseth <davids@redhat.com>\n *  Copyright (C) 2016-2026 David Sommerseth <davids@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n#include \"console.h\"\n#include \"error.h\"\n#include \"buffer.h\"\n#include \"misc.h\"\n\n#ifdef ENABLE_SYSTEMD\n#include <systemd/sd-daemon.h>\n#endif\n\n\nstruct _query_user query_user[QUERY_USER_NUMSLOTS]; /* GLOBAL */\n\n\nvoid\nquery_user_clear(void)\n{\n    int i;\n\n    for (i = 0; i < QUERY_USER_NUMSLOTS; i++)\n    {\n        CLEAR(query_user[i]);\n    }\n}\n\n\nvoid\nquery_user_add(char *prompt, char *resp, int resp_len, bool echo)\n{\n    int i;\n\n    /* Ensure input is sane.  All these must be present otherwise it is\n     * a programming error.\n     */\n    ASSERT(prompt != NULL && resp_len > 0 && resp != NULL);\n\n    /* Seek to the last unused slot */\n    for (i = 0; i < QUERY_USER_NUMSLOTS; i++)\n    {\n        if (query_user[i].prompt == NULL)\n        {\n            break;\n        }\n    }\n    ASSERT(i < QUERY_USER_NUMSLOTS); /* Unlikely, but we want to panic if it happens */\n\n    /* Save the information needed for the user interaction */\n    query_user[i].prompt = prompt;\n    query_user[i].response = resp;\n    query_user[i].response_len = resp_len;\n    query_user[i].echo = echo;\n}\n"
  },
  {
    "path": "src/openvpn/console.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2014-2015 David Sommerseth <davids@redhat.com>\n *  Copyright (C) 2016-2026 David Sommerseth <davids@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef CONSOLE_H\n#define CONSOLE_H\n\n#include \"basic.h\"\n\n/**\n *  Configuration setup for declaring what kind of information to ask a user for\n */\nstruct _query_user\n{\n    char *prompt;     /**< Prompt to present to the user */\n    char *response;   /**< The user's response */\n    int response_len; /**< Length the of the user response */\n    bool echo;        /**< True: The user should see what is being typed, otherwise mask it */\n};\n\n#define QUERY_USER_NUMSLOTS 10\nextern struct _query_user query_user[]; /**< Global variable, declared in console.c */\n\n/**\n * Wipes all data put into all of the query_user structs\n *\n */\nvoid query_user_clear(void);\n\n\n/**\n * Adds an item to ask the user for\n *\n * @param prompt     Prompt to display to the user\n * @param resp       String containing the user response\n * @param resp_len   Length of the response string\n * @param echo       Should the user input be echoed to the user?  If False, input will be masked\n *\n */\nvoid query_user_add(char *prompt, char *resp, int resp_len, bool echo);\n\n\n/**\n * Loop through configured query_user slots, using the built-in method for\n * querying the user.\n * This method uses the console/TTY directly.\n *\n * @return True if executing all the defined steps completed successfully\n */\nbool query_user_exec_builtin(void);\n\n\n#if defined(ENABLE_SYSTEMD)\n/**\n * Loop through configured query_user slots, using the systemd method for\n * querying the user.\n * If systemd is not running it will fall back to use\n * query_user_exec_builtin() instead.\n *\n * @return True if executing all the defined steps completed successfully\n */\nbool query_user_exec_systemd(void);\n\n/**\n * Loop through configured query_user slots, using the compiled method for\n * querying the user.\n *\n * @return True if executing all the defined steps completed successfully\n */\nstatic inline bool\nquery_user_exec(void)\n{\n    return query_user_exec_systemd();\n}\n\n#else  /* ENABLE_SYSTEMD not defined */\n/**\n * Wrapper function enabling query_user_exec() if no alternative methods have\n * been enabled\n *\n */\nstatic inline bool\nquery_user_exec(void)\n{\n    return query_user_exec_builtin();\n}\n#endif /* defined(ENABLE_SYSTEMD) */\n\n\n/**\n * A plain \"make Gert happy\" wrapper.  Same arguments as query_user_add()\n *\n * FIXME/TODO: Remove this when refactoring the complete user query process\n *             to be called at start-up initialization of OpenVPN.\n *\n */\nstatic inline bool\nquery_user_SINGLE(char *prompt, char *resp, int resp_len, bool echo)\n{\n    query_user_clear();\n    query_user_add(prompt, resp, resp_len, echo);\n    return query_user_exec();\n}\n\n#endif /* ifndef CONSOLE_H */\n"
  },
  {
    "path": "src/openvpn/console_builtin.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2014-2015  David Sommerseth <davids@redhat.com>\n *  Copyright (C) 2016-2026 David Sommerseth <davids@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n *  These functions covers handing user input/output using the default consoles\n *\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n#include \"console.h\"\n#include \"error.h\"\n#include \"buffer.h\"\n#include \"misc.h\"\n\n#ifdef HAVE_TERMIOS_H\n#include <termios.h>\n#endif\n\n#ifdef _WIN32\n\n#include \"win32.h\"\n\n/**\n * Get input from a Windows console.\n *\n * @param prompt    Prompt to display to the user\n * @param echo      Should the user input be displayed in the console\n * @param input     Pointer to the buffer the user input will be saved\n * @param capacity  Size of the buffer for the user input\n *\n * @return Return false on input error, or if service\n *         exit event is signaled.\n */\nstatic bool\nget_console_input_win32(const char *prompt, const bool echo, char *input, const int capacity)\n{\n    ASSERT(prompt);\n    ASSERT(input);\n    ASSERT(capacity > 0);\n\n    input[0] = '\\0';\n\n    HANDLE in = GetStdHandle(STD_INPUT_HANDLE);\n    int orig_stderr = get_orig_stderr(); /* guaranteed to be always valid */\n    if ((in == INVALID_HANDLE_VALUE) || win32_service_interrupt(&win32_signal)\n        || (_write(orig_stderr, prompt, (unsigned int)strlen(prompt)) == -1))\n    {\n        msg(M_WARN | M_ERRNO, \"get_console_input_win32(): unexpected error\");\n        return false;\n    }\n\n    bool is_console = (GetFileType(in) == FILE_TYPE_CHAR);\n    DWORD flags_save = 0;\n    int status = 0;\n    WCHAR *winput;\n\n    if (is_console)\n    {\n        if (GetConsoleMode(in, &flags_save))\n        {\n            DWORD flags = ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;\n            if (echo)\n            {\n                flags |= ENABLE_ECHO_INPUT;\n            }\n            SetConsoleMode(in, flags);\n        }\n        else\n        {\n            is_console = 0;\n        }\n    }\n\n    DWORD len = 0;\n\n    if (is_console)\n    {\n        winput = malloc(capacity * sizeof(WCHAR));\n        if (winput == NULL)\n        {\n            return false;\n        }\n\n        status = ReadConsoleW(in, winput, capacity, &len, NULL);\n        WideCharToMultiByte(CP_UTF8, 0, winput, len, input, capacity, NULL, NULL);\n        free(winput);\n    }\n    else\n    {\n        status = ReadFile(in, input, capacity, &len, NULL);\n    }\n\n    string_null_terminate(input, (int)len, capacity);\n    chomp(input);\n\n    if (!echo)\n    {\n        _write(orig_stderr, \"\\r\\n\", 2);\n    }\n    if (is_console)\n    {\n        SetConsoleMode(in, flags_save);\n    }\n    if (status && !win32_service_interrupt(&win32_signal))\n    {\n        return true;\n    }\n\n    return false;\n}\n\n#endif /* _WIN32 */\n\n\n#ifdef HAVE_TERMIOS_H\n\n/**\n * Open the current console TTY for read/write operations\n *\n * @params write   If true, the user wants to write to the console\n *                 otherwise read from the console\n *\n * @returns Returns a FILE pointer to either the TTY in read or write mode\n *          or stdin/stderr, depending on the write flag\n *\n */\nstatic FILE *\nopen_tty(const bool write)\n{\n    FILE *ret;\n    ret = fopen(\"/dev/tty\", write ? \"w\" : \"r\");\n    if (!ret)\n    {\n        ret = write ? stderr : stdin;\n    }\n    return ret;\n}\n\n/**\n * Closes the TTY FILE pointer, but only if it is not a stdin/stderr FILE object.\n *\n * @param fp     FILE pointer to close\n *\n */\nstatic void\nclose_tty(FILE *fp)\n{\n    if (fp != stderr && fp != stdin)\n    {\n        fclose(fp);\n    }\n}\n\n#endif /* HAVE_TERMIOS_H */\n\n\n/**\n *  Core function for getting input from console\n *\n *  @param prompt    The prompt to present to the user\n *  @param echo      Should the user see what is being typed\n *  @param input     Pointer to the buffer used to save the user input\n *  @param capacity  Size of the input buffer\n *\n *  @returns Returns True if user input was gathered\n */\nstatic bool\nget_console_input(const char *prompt, const bool echo, char *input, const int capacity)\n{\n    bool ret = false;\n    ASSERT(prompt);\n    ASSERT(input);\n    ASSERT(capacity > 0);\n    input[0] = '\\0';\n\n#if defined(_WIN32)\n    return get_console_input_win32(prompt, echo, input, capacity);\n#elif defined(HAVE_TERMIOS_H)\n    bool restore_tty = false;\n    struct termios tty_tmp, tty_save;\n\n    /* did we --daemon'ize before asking for passwords?\n     * (in which case neither stdin or stderr are connected to a tty and\n     * /dev/tty can not be open()ed anymore)\n     */\n    if (!isatty(0) && !isatty(2))\n    {\n        int fd = open(\"/dev/tty\", O_RDWR);\n        if (fd < 0)\n        {\n            msg(M_FATAL,\n                \"neither stdin nor stderr are a tty device and you have neither a \"\n                \"controlling tty nor systemd - can't ask for '%s'.  If you used --daemon, \"\n                \"you need to use --askpass to make passphrase-protected keys work, and you \"\n                \"can not use --auth-nocache.\",\n                prompt);\n        }\n        close(fd);\n    }\n\n    FILE *fp = open_tty(true);\n    fprintf(fp, \"%s\", prompt);\n    fflush(fp);\n    close_tty(fp);\n\n    fp = open_tty(false);\n\n    if (!echo && (tcgetattr(fileno(fp), &tty_tmp) == 0))\n    {\n        tty_save = tty_tmp;\n        tty_tmp.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ISIG);\n        restore_tty = (tcsetattr(fileno(fp), TCSAFLUSH, &tty_tmp) == 0);\n    }\n\n    if (fgets(input, capacity, fp) != NULL)\n    {\n        chomp(input);\n        ret = true;\n    }\n\n    if (restore_tty)\n    {\n        if (tcsetattr(fileno(fp), TCSAFLUSH, &tty_save) == -1)\n        {\n            msg(M_WARN | M_ERRNO, \"tcsetattr() failed to restore tty settings\");\n        }\n\n        /* Echo the non-echoed newline */\n        close_tty(fp);\n        fp = open_tty(true);\n        fprintf(fp, \"\\n\");\n        fflush(fp);\n    }\n\n    close_tty(fp);\n#else  /* if defined(_WIN32) */\n    msg(M_FATAL, \"Sorry, but I can't get console input on this OS (%s)\", prompt);\n#endif /* if defined(_WIN32) */\n    return ret;\n}\n\n/**\n * @copydoc query_user_exec()\n *\n * Default method for querying user using default stdin/stdout on a console.\n * This needs to be available as a backup interface for the alternative\n * implementations in case they cannot query through their implementation\n * specific methods.\n *\n * If no alternative implementation is declared, a wrapper in console.h will ensure\n * query_user_exec() will call this function instead.\n *\n */\nbool\nquery_user_exec_builtin(void)\n{\n    bool ret = true; /* Presume everything goes okay */\n    int i;\n\n    /* Loop through configured query_user slots */\n    for (i = 0; i < QUERY_USER_NUMSLOTS && query_user[i].response != NULL; i++)\n    {\n        if (!get_console_input(query_user[i].prompt, query_user[i].echo, query_user[i].response,\n                               query_user[i].response_len))\n        {\n            /* Force the final result state to failed on failure */\n            ret = false;\n        }\n    }\n\n    return ret;\n}\n"
  },
  {
    "path": "src/openvpn/console_systemd.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2014-2015 David Sommerseth <davids@redhat.com>\n *  Copyright (C) 2016      David Sommerseth <dazo@privateinternetaccess.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Alternative method to query for user input, using systemd\n *\n */\n\n#include \"config.h\"\n\n#ifdef ENABLE_SYSTEMD\n#include \"syshead.h\"\n#include \"console.h\"\n#include \"misc.h\"\n#include \"run_command.h\"\n\n#include <systemd/sd-daemon.h>\n\n/*\n * is systemd running\n */\n\nstatic bool\ncheck_systemd_running(void)\n{\n    struct stat c;\n\n    /* We simply test whether the systemd cgroup hierarchy is\n     * mounted, as well as the systemd-ask-password executable\n     * being available */\n\n    return (sd_booted() > 0) && (stat(SYSTEMD_ASK_PASSWORD_PATH, &c) == 0);\n}\n\nstatic bool\nget_console_input_systemd(const char *prompt, const bool echo, char *input, const int capacity)\n{\n    int std_out;\n    bool ret = false;\n    struct argv argv = argv_new();\n\n    argv_printf(&argv, SYSTEMD_ASK_PASSWORD_PATH);\n    if (echo)\n    {\n        argv_printf_cat(&argv, \"--echo\");\n    }\n    argv_printf_cat(&argv, \"--icon network-vpn\");\n    argv_printf_cat(&argv, \"--timeout=0\");\n    argv_printf_cat(&argv, \"%s\", prompt);\n\n    if ((std_out = openvpn_popen(&argv, NULL)) < 0)\n    {\n        return false;\n    }\n    memset(input, 0, capacity);\n    if (read(std_out, input, capacity - 1) != 0)\n    {\n        chomp(input);\n        ret = true;\n    }\n    close(std_out);\n\n    argv_free(&argv);\n\n    return ret;\n}\n\n/**\n *  Systemd aware implementation of query_user_exec().  If systemd is not running\n *  it will fall back to use query_user_exec_builtin() instead.\n *\n */\nbool\nquery_user_exec_systemd(void)\n{\n    bool ret = true; /* Presume everything goes okay */\n    int i;\n\n    /* If systemd is not available, use the default built-in mechanism */\n    if (!check_systemd_running())\n    {\n        return query_user_exec_builtin();\n    }\n\n    /* Loop through the complete query setup and when needed, collect the information */\n    for (i = 0; i < QUERY_USER_NUMSLOTS && query_user[i].response != NULL; i++)\n    {\n        if (!get_console_input_systemd(query_user[i].prompt, query_user[i].echo,\n                                       query_user[i].response, query_user[i].response_len))\n        {\n            /* Force the final result state to failed on failure */\n            ret = false;\n        }\n    }\n\n    return ret;\n}\n\n#endif /* ENABLE_SYSTEMD */\n"
  },
  {
    "path": "src/openvpn/crypto.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <inttypes.h>\n\n#include \"syshead.h\"\n#include <string.h>\n\n#include \"crypto.h\"\n#include \"crypto_epoch.h\"\n#include \"packet_id.h\"\n#include \"error.h\"\n#include \"integer.h\"\n#include \"platform.h\"\n\n#include \"memdbg.h\"\n\n/*\n * Encryption and Compression Routines.\n *\n * On entry, buf contains the input data and length.\n * On exit, it should be set to the output data and length.\n *\n * If buf->len is <= 0 we should return\n * If buf->len is set to 0 on exit it tells the caller to ignore the packet.\n *\n * work is a workspace buffer we are given of size BUF_SIZE.\n * work may be used to return output data, or the input buffer\n * may be modified and returned as output.  If output data is\n * returned in work, the data should start after buf.headroom bytes\n * of padding to leave room for downstream routines to prepend.\n *\n * Up to a total of buf.headroom bytes may be prepended to the input buf\n * by all routines (encryption, decryption, compression, and decompression).\n *\n * Note that the buf_prepend return will assert if we try to\n * make a header bigger than buf.headroom.  This should not\n * happen unless the frame parameters are wrong.\n */\n\nstatic void\nopenvpn_encrypt_aead(struct buffer *buf, struct buffer work, struct crypto_options *opt)\n{\n    struct gc_arena gc;\n    int outlen = 0;\n    const bool use_epoch_data_format = opt->flags & CO_EPOCH_DATA_KEY_FORMAT;\n\n    if (use_epoch_data_format)\n    {\n        epoch_check_send_iterate(opt);\n    }\n\n    const struct key_ctx *ctx = &opt->key_ctx_bi.encrypt;\n    uint8_t *mac_out = NULL;\n    const int mac_len = OPENVPN_AEAD_TAG_LENGTH;\n\n    /* IV, packet-ID and implicit IV required for this mode. */\n    ASSERT(ctx->cipher);\n    ASSERT(packet_id_initialized(&opt->packet_id));\n\n    gc_init(&gc);\n\n    /* Prepare IV */\n    {\n        struct buffer iv_buffer;\n        uint8_t iv[OPENVPN_MAX_IV_LENGTH] = { 0 };\n        const unsigned int iv_len = cipher_ctx_iv_length(ctx->cipher);\n\n        ASSERT(iv_len >= OPENVPN_AEAD_MIN_IV_LEN && iv_len <= OPENVPN_MAX_IV_LENGTH);\n\n        buf_set_write(&iv_buffer, iv, iv_len);\n\n        /* IV starts with packet id to make the IV unique for packet */\n        if (use_epoch_data_format)\n        {\n            /* Note this does not check aead_usage_limit but can overstep it by\n             * a few extra blocks in one extra write. This is not affecting the\n             * security margin as these extra blocks are on a completely\n             * different order of magnitude than the security margin.\n             * The next iteration/call to epoch_check_send_iterate will\n             * iterate the epoch\n             */\n            if (!packet_id_write_epoch(&opt->packet_id.send, ctx->epoch, &iv_buffer))\n            {\n                msg(D_CRYPT_ERRORS, \"ENCRYPT ERROR: packet ID roll over\");\n                goto err;\n            }\n        }\n        else\n        {\n            if (!packet_id_write(&opt->packet_id.send, &iv_buffer, false, false))\n            {\n                msg(D_CRYPT_ERRORS, \"ENCRYPT ERROR: packet ID roll over\");\n                goto err;\n            }\n        }\n        /* Write packet id part of IV to work buffer */\n        ASSERT(buf_write(&work, iv, BLENZ(&iv_buffer)));\n\n        /* This generates the IV by XORing the implicit part of the IV\n         * with the packet id already written to the iv buffer */\n        for (unsigned int i = 0; i < iv_len; i++)\n        {\n            iv[i] = iv[i] ^ ctx->implicit_iv[i];\n        }\n\n        dmsg(D_PACKET_CONTENT, \"ENCRYPT IV: %s\", format_hex(iv, iv_len, 0, &gc));\n\n        /* Init cipher_ctx with IV.  key & keylen are already initialized */\n        ASSERT(cipher_ctx_reset(ctx->cipher, iv));\n    }\n\n    dmsg(D_PACKET_CONTENT, \"ENCRYPT FROM: %s\", format_hex(BPTR(buf), BLEN(buf), 80, &gc));\n\n    /* Buffer overflow check */\n    if (!buf_safe(&work, buf->len + mac_len + cipher_ctx_block_size(ctx->cipher)))\n    {\n        msg(D_CRYPT_ERRORS, \"ENCRYPT: buffer size error, bc=%d bo=%d bl=%d wc=%d wo=%d wl=%d\",\n            buf->capacity, buf->offset, buf->len, work.capacity, work.offset, work.len);\n        goto err;\n    }\n\n    /* For AEAD ciphers, authenticate Additional Data, including opcode */\n    ASSERT(cipher_ctx_update_ad(ctx->cipher, BPTR(&work), BLEN(&work)));\n    dmsg(D_PACKET_CONTENT, \"ENCRYPT AD: %s\", format_hex(BPTR(&work), BLEN(&work), 0, &gc));\n\n    if (!use_epoch_data_format)\n    {\n        /* Reserve space for authentication tag */\n        mac_out = buf_write_alloc(&work, mac_len);\n        ASSERT(mac_out);\n    }\n\n    /* Encrypt packet ID, payload */\n    ASSERT(cipher_ctx_update(ctx->cipher, BEND(&work), &outlen, BPTR(buf), BLEN(buf)));\n    ASSERT(buf_inc_len(&work, outlen));\n\n    /* Flush the encryption buffer */\n    ASSERT(cipher_ctx_final(ctx->cipher, BEND(&work), &outlen));\n    ASSERT(buf_inc_len(&work, outlen));\n\n    /* update number of plaintext blocks encrypted. Use the (x + (n-1))/n trick\n     * to round up the result to the number of blocks used */\n    const int blocksize = AEAD_LIMIT_BLOCKSIZE;\n    opt->key_ctx_bi.encrypt.plaintext_blocks += (BLEN(&work) + (blocksize - 1)) / blocksize;\n\n    /* if the tag is at end the end, allocate it now */\n    if (use_epoch_data_format)\n    {\n        /* Reserve space for authentication tag */\n        mac_out = buf_write_alloc(&work, mac_len);\n        ASSERT(mac_out);\n    }\n\n    /* Write authentication tag */\n    ASSERT(cipher_ctx_get_tag(ctx->cipher, mac_out, mac_len));\n\n    *buf = work;\n\n    dmsg(D_PACKET_CONTENT, \"ENCRYPT TO: %s\", format_hex(BPTR(buf), BLEN(buf), 80, &gc));\n\n    gc_free(&gc);\n    return;\n\nerr:\n    crypto_clear_error();\n    buf->len = 0;\n    gc_free(&gc);\n    return;\n}\n\nstatic void\nopenvpn_encrypt_v1(struct buffer *buf, struct buffer work, struct crypto_options *opt)\n{\n    struct gc_arena gc;\n    gc_init(&gc);\n\n    if (buf->len > 0 && opt)\n    {\n        const struct key_ctx *ctx = &opt->key_ctx_bi.encrypt;\n        uint8_t *mac_out = NULL;\n        const uint8_t *hmac_start = NULL;\n\n        /* Do Encrypt from buf -> work */\n        if (ctx->cipher)\n        {\n            uint8_t iv_buf[OPENVPN_MAX_IV_LENGTH] = { 0 };\n            const unsigned int iv_size = cipher_ctx_iv_length(ctx->cipher);\n\n            /* Reserve space for HMAC */\n            if (ctx->hmac)\n            {\n                mac_out = buf_write_alloc(&work, hmac_ctx_size(ctx->hmac));\n                ASSERT(mac_out);\n                hmac_start = BEND(&work);\n            }\n\n            if (cipher_ctx_mode_cbc(ctx->cipher))\n            {\n                /* generate pseudo-random IV */\n                prng_bytes(iv_buf, iv_size);\n\n                /* Put packet ID in plaintext buffer */\n                if (packet_id_initialized(&opt->packet_id)\n                    && !packet_id_write(&opt->packet_id.send, buf,\n                                        opt->flags & CO_PACKET_ID_LONG_FORM, true))\n                {\n                    msg(D_CRYPT_ERRORS, \"ENCRYPT ERROR: packet ID roll over\");\n                    goto err;\n                }\n            }\n            else if (cipher_ctx_mode_ofb_cfb(ctx->cipher))\n            {\n                struct buffer b;\n\n                /* packet-ID required for this mode. */\n                ASSERT(packet_id_initialized(&opt->packet_id));\n\n                buf_set_write(&b, iv_buf, iv_size);\n                ASSERT(packet_id_write(&opt->packet_id.send, &b, true, false));\n            }\n            else /* We only support CBC, CFB, or OFB modes right now */\n            {\n                ASSERT(0);\n            }\n\n            /* write the pseudo-randomly IV (CBC)/packet ID (OFB/CFB) */\n            ASSERT(buf_write(&work, iv_buf, iv_size));\n            dmsg(D_PACKET_CONTENT, \"ENCRYPT IV: %s\", format_hex(iv_buf, iv_size, 0, &gc));\n\n            dmsg(D_PACKET_CONTENT, \"ENCRYPT FROM: %s\", format_hex(BPTR(buf), BLEN(buf), 80, &gc));\n\n            /* cipher_ctx was already initialized with key & keylen */\n            ASSERT(cipher_ctx_reset(ctx->cipher, iv_buf));\n\n            /* Buffer overflow check */\n            if (!buf_safe(&work, buf->len + cipher_ctx_block_size(ctx->cipher)))\n            {\n                msg(D_CRYPT_ERRORS,\n                    \"ENCRYPT: buffer size error, bc=%d bo=%d bl=%d wc=%d wo=%d wl=%d cbs=%d\",\n                    buf->capacity, buf->offset, buf->len, work.capacity, work.offset, work.len,\n                    cipher_ctx_block_size(ctx->cipher));\n                goto err;\n            }\n\n            /* Encrypt packet ID, payload */\n            int outlen;\n            ASSERT(cipher_ctx_update(ctx->cipher, BEND(&work), &outlen, BPTR(buf), BLEN(buf)));\n            ASSERT(buf_inc_len(&work, outlen));\n\n            /* Flush the encryption buffer */\n            ASSERT(cipher_ctx_final(ctx->cipher, BEND(&work), &outlen));\n            ASSERT(buf_inc_len(&work, outlen));\n\n            /* For all CBC mode ciphers, check the last block is complete */\n            ASSERT(cipher_ctx_mode(ctx->cipher) != OPENVPN_MODE_CBC || outlen == (int)iv_size);\n        }\n        else /* No Encryption */\n        {\n            if (packet_id_initialized(&opt->packet_id)\n                && !packet_id_write(&opt->packet_id.send, buf, opt->flags & CO_PACKET_ID_LONG_FORM,\n                                    true))\n            {\n                msg(D_CRYPT_ERRORS, \"ENCRYPT ERROR: packet ID roll over\");\n                goto err;\n            }\n            if (ctx->hmac)\n            {\n                hmac_start = BPTR(buf);\n                ASSERT(mac_out = buf_prepend(buf, hmac_ctx_size(ctx->hmac)));\n            }\n            if (BLEN(&work))\n            {\n                buf_write_prepend(buf, BPTR(&work), BLEN(&work));\n            }\n            work = *buf;\n        }\n\n        /* HMAC the ciphertext (or plaintext if !cipher) */\n        if (ctx->hmac)\n        {\n            hmac_ctx_reset(ctx->hmac);\n            hmac_ctx_update(ctx->hmac, hmac_start, (int)(BEND(&work) - hmac_start));\n            hmac_ctx_final(ctx->hmac, mac_out);\n            dmsg(D_PACKET_CONTENT, \"ENCRYPT HMAC: %s\",\n                 format_hex(mac_out, hmac_ctx_size(ctx->hmac), 80, &gc));\n        }\n\n        *buf = work;\n\n        dmsg(D_PACKET_CONTENT, \"ENCRYPT TO: %s\", format_hex(BPTR(&work), BLEN(&work), 80, &gc));\n    }\n\n    gc_free(&gc);\n    return;\n\nerr:\n    crypto_clear_error();\n    buf->len = 0;\n    gc_free(&gc);\n    return;\n}\n\nvoid\nopenvpn_encrypt(struct buffer *buf, struct buffer work, struct crypto_options *opt)\n{\n    if (buf->len > 0 && opt)\n    {\n        if (cipher_ctx_mode_aead(opt->key_ctx_bi.encrypt.cipher))\n        {\n            openvpn_encrypt_aead(buf, work, opt);\n        }\n        else\n        {\n            openvpn_encrypt_v1(buf, work, opt);\n        }\n    }\n}\n\nuint64_t\ncipher_get_aead_limits(const char *ciphername)\n{\n    if (!cipher_kt_mode_aead(ciphername))\n    {\n        return 0;\n    }\n\n    if (cipher_kt_name(ciphername) == cipher_kt_name(\"CHACHA20-POLY1305\"))\n    {\n        return 0;\n    }\n\n    /* Assume all other ciphers require the limit */\n\n    /* We focus here on the equation\n     *\n     *       q + s <= p^(1/2) * 2^(129/2) - 1\n     *\n     * as is the one that is limiting us.\n     *\n     *  With p = 2^-57 this becomes\n     *\n     *      q + s <= (2^36 - 1)\n     *\n     */\n    uint64_t rs = (1ull << 36) - 1;\n\n    return rs;\n}\n\nbool\ncrypto_check_replay(struct crypto_options *opt, const struct packet_id_net *pin, uint16_t epoch,\n                    const char *error_prefix, struct gc_arena *gc)\n{\n    bool ret = false;\n    struct packet_id_rec *recv;\n\n    if (epoch == 0 || opt->key_ctx_bi.decrypt.epoch == epoch)\n    {\n        recv = &opt->packet_id.rec;\n    }\n    else if (epoch == opt->epoch_retiring_data_receive_key.epoch)\n    {\n        recv = &opt->epoch_retiring_key_pid_recv;\n    }\n    else\n    {\n        /* We have an epoch that is neither current or old recv key but\n         * is authenticated, ie we need to move to a new current recv key */\n        msg(D_GENKEY,\n            \"Received data packet with new epoch %d. Updating \"\n            \"receive key\",\n            epoch);\n        epoch_replace_update_recv_key(opt, epoch);\n        recv = &opt->packet_id.rec;\n    }\n\n    packet_id_reap_test(recv);\n    if (packet_id_test(recv, pin))\n    {\n        packet_id_add(recv, pin);\n        if (opt->pid_persist && (opt->flags & CO_PACKET_ID_LONG_FORM))\n        {\n            packet_id_persist_save_obj(opt->pid_persist, &opt->packet_id);\n        }\n        ret = true;\n    }\n    else\n    {\n        if (!(opt->flags & CO_MUTE_REPLAY_WARNINGS))\n        {\n            msg(D_REPLAY_ERRORS,\n                \"%s: bad packet ID (may be a replay): %s -- \"\n                \"see the man page entry for --replay-window for \"\n                \"more info or silence this warning with --mute-replay-warnings\",\n                error_prefix, packet_id_net_print(pin, true, gc));\n        }\n    }\n    return ret;\n}\n\n/**\n * Unwrap (authenticate, decrypt and check replay protection) AEAD-mode data\n * channel packets.\n *\n * Set buf->len to 0 and return false on decrypt error.\n *\n * On success, buf is set to point to plaintext, true is returned.\n */\nstatic bool\nopenvpn_decrypt_aead(struct buffer *buf, struct buffer work, struct crypto_options *opt,\n                     const struct frame *frame, const uint8_t *ad_start)\n{\n    static const char error_prefix[] = \"AEAD Decrypt error\";\n    struct packet_id_net pin = { 0 };\n    struct gc_arena gc;\n    gc_init(&gc);\n\n    struct key_ctx *ctx = &opt->key_ctx_bi.decrypt;\n    const bool use_epoch_data_format = opt->flags & CO_EPOCH_DATA_KEY_FORMAT;\n    if (!use_epoch_data_format && cipher_decrypt_verify_fail_exceeded(ctx))\n    {\n        CRYPT_DROP(\"Decryption failed verification limit reached.\");\n    }\n\n    const int tag_size = OPENVPN_AEAD_TAG_LENGTH;\n\n\n    ASSERT(opt);\n    ASSERT(frame);\n    ASSERT(buf->len > 0);\n    ASSERT(ctx->cipher);\n\n    dmsg(D_PACKET_CONTENT, \"DECRYPT FROM: %s\", format_hex(BPTR(buf), BLEN(buf), 80, &gc));\n\n    ASSERT(ad_start >= buf->data && ad_start <= BPTR(buf));\n\n    ASSERT(buf_init(&work, frame->buf.headroom));\n\n    /* IV and Packet ID required for this mode */\n    ASSERT(packet_id_initialized(&opt->packet_id));\n\n    /* Ensure that the packet size is long enough */\n    int min_packet_len = packet_id_size(false) + tag_size + 1;\n\n    if (use_epoch_data_format)\n    {\n        min_packet_len += sizeof(uint32_t);\n    }\n\n    if (buf->len < min_packet_len)\n    {\n        CRYPT_ERROR(\"missing IV info, missing tag or no payload\");\n    }\n\n    uint16_t epoch = 0;\n    /* Combine IV from explicit part from packet and implicit part from context */\n    {\n        uint8_t iv[OPENVPN_MAX_IV_LENGTH] = { 0 };\n        const unsigned int iv_len = cipher_ctx_iv_length(ctx->cipher);\n\n        /* Read packet id. For epoch data format also lookup the epoch key\n         * to be able to use the implicit IV of the correct decryption key */\n        if (use_epoch_data_format)\n        {\n            /* packet ID format is 16 bit epoch + 48 per epoch packet-counter */\n            const size_t packet_iv_len = sizeof(uint64_t);\n\n            /* copy the epoch-counter part into the IV */\n            memcpy(iv, BPTR(buf), packet_iv_len);\n\n            epoch = packet_id_read_epoch(&pin, buf);\n            if (epoch == 0)\n            {\n                CRYPT_ERROR(\"error reading packet-id\");\n            }\n            ctx = epoch_lookup_decrypt_key(opt, epoch);\n            if (!ctx)\n            {\n                CRYPT_ERROR(\"data packet with unknown epoch\");\n            }\n            else if (cipher_decrypt_verify_fail_exceeded(ctx))\n            {\n                CRYPT_DROP(\"Decryption failed verification limit reached\");\n            }\n        }\n        else\n        {\n            const size_t packet_iv_len = packet_id_size(false);\n            /* Packet ID form is a 32 bit packet counter */\n            memcpy(iv, BPTR(buf), packet_iv_len);\n            if (!packet_id_read(&pin, buf, false))\n            {\n                CRYPT_ERROR(\"error reading packet-id\");\n            }\n        }\n\n        /* This generates the IV by XORing the implicit part of the IV\n         * with the packet id already written to the iv buffer */\n        for (unsigned int i = 0; i < iv_len; i++)\n        {\n            iv[i] = iv[i] ^ ctx->implicit_iv[i];\n        }\n\n        dmsg(D_PACKET_CONTENT, \"DECRYPT IV: %s\", format_hex(iv, iv_len, 0, &gc));\n\n        /* Load IV, ctx->cipher was already initialized with key & keylen */\n        if (!cipher_ctx_reset(ctx->cipher, iv))\n        {\n            CRYPT_ERROR(\"cipher init failed\");\n        }\n    }\n\n    const int ad_size = (int)(BPTR(buf) - ad_start);\n\n    uint8_t *tag_ptr = NULL;\n    int data_len = 0;\n\n    if (use_epoch_data_format)\n    {\n        data_len = BLEN(buf) - tag_size;\n        tag_ptr = BPTR(buf) + data_len;\n    }\n    else\n    {\n        tag_ptr = BPTR(buf);\n        ASSERT(buf_advance(buf, tag_size));\n        data_len = BLEN(buf);\n    }\n\n    dmsg(D_PACKET_CONTENT, \"DECRYPT MAC: %s\", format_hex(tag_ptr, tag_size, 0, &gc));\n    dmsg(D_PACKET_CONTENT, \"DECRYPT FROM: %s\", format_hex(BPTR(buf), BLEN(buf), 0, &gc));\n\n    /* Buffer overflow check (should never fail) */\n    if (!buf_safe(&work, buf->len + cipher_ctx_block_size(ctx->cipher)))\n    {\n        CRYPT_ERROR(\"potential buffer overflow\");\n    }\n\n    /* feed in tag and the authenticated data */\n    ASSERT(cipher_ctx_update_ad(ctx->cipher, ad_start, ad_size));\n    dmsg(D_PACKET_CONTENT, \"DECRYPT AD: %s\", format_hex(ad_start, ad_size, 0, &gc));\n\n    /* Decrypt and authenticate packet */\n    int outlen;\n    if (!cipher_ctx_update(ctx->cipher, BPTR(&work), &outlen, BPTR(buf), data_len))\n    {\n        CRYPT_ERROR(\"packet decryption failed\");\n    }\n\n    ASSERT(buf_inc_len(&work, outlen));\n    if (!cipher_ctx_final_check_tag(ctx->cipher, BPTR(&work) + outlen, &outlen, tag_ptr, tag_size))\n    {\n        ctx->failed_verifications++;\n        CRYPT_DROP(\"packet tag authentication failed\");\n    }\n    ASSERT(buf_inc_len(&work, outlen));\n\n    dmsg(D_PACKET_CONTENT, \"DECRYPT TO: %s\", format_hex(BPTR(&work), BLEN(&work), 80, &gc));\n\n    if (!crypto_check_replay(opt, &pin, epoch, error_prefix, &gc))\n    {\n        goto error_exit;\n    }\n\n    /* update number of plaintext blocks decrypted. Use the (x + (n-1))/n trick\n     * to round up the result to the number of blocks used. */\n    const int blocksize = AEAD_LIMIT_BLOCKSIZE;\n    opt->key_ctx_bi.decrypt.plaintext_blocks += (BLEN(&work) + (blocksize - 1)) / blocksize;\n\n    *buf = work;\n\n    gc_free(&gc);\n    return true;\n\nerror_exit:\n    crypto_clear_error();\n    buf->len = 0;\n    gc_free(&gc);\n    return false;\n}\n\n/*\n * Unwrap (authenticate, decrypt and check replay protection) CBC, OFB or CFB\n * mode data channel packets.\n *\n * Set buf->len to 0 and return false on decrypt error.\n *\n * On success, buf is set to point to plaintext, true is returned.\n */\nstatic bool\nopenvpn_decrypt_v1(struct buffer *buf, struct buffer work, struct crypto_options *opt,\n                   const struct frame *frame)\n{\n    static const char error_prefix[] = \"Authenticate/Decrypt packet error\";\n    struct gc_arena gc;\n    gc_init(&gc);\n\n    if (buf->len > 0 && opt)\n    {\n        const struct key_ctx *ctx = &opt->key_ctx_bi.decrypt;\n        struct packet_id_net pin;\n        bool have_pin = false;\n\n        dmsg(D_PACKET_CONTENT, \"DECRYPT FROM: %s\", format_hex(BPTR(buf), BLEN(buf), 80, &gc));\n\n        /* Verify the HMAC */\n        if (ctx->hmac)\n        {\n            int hmac_len;\n            uint8_t local_hmac[MAX_HMAC_KEY_LENGTH]; /* HMAC of ciphertext computed locally */\n\n            hmac_ctx_reset(ctx->hmac);\n\n            /* Assume the length of the input HMAC */\n            hmac_len = hmac_ctx_size(ctx->hmac);\n\n            /* Authentication fails if insufficient data in packet for HMAC */\n            if (buf->len < hmac_len)\n            {\n                CRYPT_ERROR(\"missing authentication info\");\n            }\n\n            hmac_ctx_update(ctx->hmac, BPTR(buf) + hmac_len, BLEN(buf) - hmac_len);\n            hmac_ctx_final(ctx->hmac, local_hmac);\n\n            /* Compare locally computed HMAC with packet HMAC */\n            if (memcmp_constant_time(local_hmac, BPTR(buf), hmac_len))\n            {\n                CRYPT_DROP(\"packet HMAC authentication failed\");\n            }\n\n            ASSERT(buf_advance(buf, hmac_len));\n        }\n\n        /* Decrypt packet ID + payload */\n\n        if (ctx->cipher)\n        {\n            const unsigned int iv_size = cipher_ctx_iv_length(ctx->cipher);\n            uint8_t iv_buf[OPENVPN_MAX_IV_LENGTH] = { 0 };\n            int outlen;\n\n            /* initialize work buffer with buf.headroom bytes of prepend capacity */\n            ASSERT(buf_init(&work, frame->buf.headroom));\n\n            /* read the IV from the packet */\n            if (buf->len < (int)iv_size)\n            {\n                CRYPT_ERROR(\"missing IV info\");\n            }\n            memcpy(iv_buf, BPTR(buf), iv_size);\n            ASSERT(buf_advance(buf, iv_size));\n            dmsg(D_PACKET_CONTENT, \"DECRYPT IV: %s\", format_hex(iv_buf, iv_size, 0, &gc));\n\n            if (buf->len < 1)\n            {\n                CRYPT_ERROR(\"missing payload\");\n            }\n\n            /* ctx->cipher was already initialized with key & keylen */\n            if (!cipher_ctx_reset(ctx->cipher, iv_buf))\n            {\n                CRYPT_ERROR(\"decrypt initialization failed\");\n            }\n\n            /* Buffer overflow check (should never happen) */\n            if (!buf_safe(&work, buf->len + cipher_ctx_block_size(ctx->cipher)))\n            {\n                CRYPT_ERROR(\"packet too big to decrypt\");\n            }\n\n            /* Decrypt packet ID, payload */\n            if (!cipher_ctx_update(ctx->cipher, BPTR(&work), &outlen, BPTR(buf), BLEN(buf)))\n            {\n                CRYPT_ERROR(\"packet decryption failed\");\n            }\n            ASSERT(buf_inc_len(&work, outlen));\n\n            /* Flush the decryption buffer */\n            if (!cipher_ctx_final(ctx->cipher, BPTR(&work) + outlen, &outlen))\n            {\n                CRYPT_DROP(\"packet authentication failed, dropping.\");\n            }\n            ASSERT(buf_inc_len(&work, outlen));\n\n            dmsg(D_PACKET_CONTENT, \"DECRYPT TO: %s\", format_hex(BPTR(&work), BLEN(&work), 80, &gc));\n\n            /* Get packet ID from plaintext buffer or IV, depending on cipher mode */\n            {\n                if (cipher_ctx_mode_cbc(ctx->cipher))\n                {\n                    if (packet_id_initialized(&opt->packet_id))\n                    {\n                        if (!packet_id_read(&pin, &work,\n                                            BOOL_CAST(opt->flags & CO_PACKET_ID_LONG_FORM)))\n                        {\n                            CRYPT_ERROR(\"error reading CBC packet-id\");\n                        }\n                        have_pin = true;\n                    }\n                }\n                else if (cipher_ctx_mode_ofb_cfb(ctx->cipher))\n                {\n                    struct buffer b;\n\n                    /* packet-ID required for this mode. */\n                    ASSERT(packet_id_initialized(&opt->packet_id));\n\n                    buf_set_read(&b, iv_buf, iv_size);\n                    if (!packet_id_read(&pin, &b, true))\n                    {\n                        CRYPT_ERROR(\"error reading CFB/OFB packet-id\");\n                    }\n                    have_pin = true;\n                }\n                else /* We only support CBC, CFB, or OFB modes right now */\n                {\n                    ASSERT(0);\n                }\n            }\n        }\n        else\n        {\n            work = *buf;\n            if (packet_id_initialized(&opt->packet_id))\n            {\n                if (!packet_id_read(&pin, &work, BOOL_CAST(opt->flags & CO_PACKET_ID_LONG_FORM)))\n                {\n                    CRYPT_ERROR(\"error reading packet-id\");\n                }\n                have_pin = !BOOL_CAST(opt->flags & CO_IGNORE_PACKET_ID);\n            }\n        }\n\n        if (have_pin && !crypto_check_replay(opt, &pin, 0, error_prefix, &gc))\n        {\n            goto error_exit;\n        }\n        *buf = work;\n    }\n\n    gc_free(&gc);\n    return true;\n\nerror_exit:\n    crypto_clear_error();\n    buf->len = 0;\n    gc_free(&gc);\n    return false;\n}\n\n\nbool\nopenvpn_decrypt(struct buffer *buf, struct buffer work, struct crypto_options *opt,\n                const struct frame *frame, const uint8_t *ad_start)\n{\n    bool ret = false;\n\n    if (buf->len > 0 && opt)\n    {\n        if (cipher_ctx_mode_aead(opt->key_ctx_bi.decrypt.cipher))\n        {\n            ret = openvpn_decrypt_aead(buf, work, opt, frame, ad_start);\n        }\n        else\n        {\n            ret = openvpn_decrypt_v1(buf, work, opt, frame);\n        }\n    }\n    else\n    {\n        ret = true;\n    }\n    return ret;\n}\n\nsize_t\ncalculate_crypto_overhead(const struct key_type *kt, unsigned int pkt_id_size, bool occ)\n{\n    size_t crypto_overhead = 0;\n\n    if (!cipher_kt_mode_cbc(kt->cipher))\n    {\n        /* In CBC mode, the packet id is part of the payload size/overhead */\n        crypto_overhead += pkt_id_size;\n    }\n\n    if (cipher_kt_mode_aead(kt->cipher))\n    {\n        /* For AEAD ciphers, we basically use a stream cipher/CTR for\n         * the encryption, so no overhead apart from the extra bytes\n         * we add */\n        crypto_overhead += cipher_kt_tag_size(kt->cipher);\n\n        if (occ)\n        {\n            /* the frame calculation of old clients adds these to the link-mtu\n             * even though they are not part of the actual packet */\n            crypto_overhead += cipher_kt_iv_size(kt->cipher);\n            crypto_overhead += cipher_kt_block_size(kt->cipher);\n        }\n    }\n    else\n    {\n        if (cipher_defined(kt->cipher))\n        {\n            /* CBC, OFB or CFB mode */\n            if (occ)\n            {\n                crypto_overhead += cipher_kt_block_size(kt->cipher);\n            }\n            /* IV is always added (no-iv has been removed a while ago) */\n            crypto_overhead += cipher_kt_iv_size(kt->cipher);\n        }\n        if (md_defined(kt->digest))\n        {\n            crypto_overhead += md_kt_size(kt->digest);\n        }\n    }\n\n    return crypto_overhead;\n}\n\nunsigned int\ncrypto_max_overhead(void)\n{\n    return packet_id_size(true) + OPENVPN_MAX_IV_LENGTH + OPENVPN_MAX_CIPHER_BLOCK_SIZE\n           + max_int(OPENVPN_MAX_HMAC_SIZE, OPENVPN_AEAD_TAG_LENGTH);\n}\n\nstatic void\nwarn_insecure_key_type(const char *ciphername)\n{\n    if (cipher_kt_insecure(ciphername))\n    {\n        msg(M_WARN,\n            \"WARNING: INSECURE cipher (%s) with block size less than 128\"\n            \" bit (%u bit).  This allows attacks like SWEET32.  Mitigate by \"\n            \"using a --cipher with a larger block size (e.g. AES-256-CBC). \"\n            \"Support for these insecure ciphers will be removed in \"\n            \"OpenVPN 2.8.\",\n            ciphername, cipher_kt_block_size(ciphername) * 8);\n    }\n}\n\n/*\n * Build a struct key_type.\n */\nvoid\ninit_key_type(struct key_type *kt, const char *ciphername, const char *authname, bool tls_mode,\n              bool warn)\n{\n    bool aead_cipher = false;\n\n    ASSERT(ciphername);\n    ASSERT(authname);\n\n    CLEAR(*kt);\n    kt->cipher = ciphername;\n    if (strcmp(ciphername, \"none\") != 0)\n    {\n        if (!cipher_valid(ciphername))\n        {\n            msg(M_FATAL, \"Cipher %s not supported\", ciphername);\n        }\n\n        /* check legal cipher mode */\n        aead_cipher = cipher_kt_mode_aead(kt->cipher);\n        if (!(cipher_kt_mode_cbc(kt->cipher) || (tls_mode && aead_cipher)\n#ifdef ENABLE_OFB_CFB_MODE\n              || (tls_mode && cipher_kt_mode_ofb_cfb(kt->cipher))\n#endif\n                  ))\n        {\n            msg(M_FATAL, \"Cipher '%s' mode not supported\", ciphername);\n        }\n\n        if (OPENVPN_MAX_CIPHER_BLOCK_SIZE < cipher_kt_block_size(kt->cipher))\n        {\n            msg(M_FATAL, \"Cipher '%s' not allowed: block size too big.\", ciphername);\n        }\n        if (warn)\n        {\n            warn_insecure_key_type(ciphername);\n        }\n    }\n    else\n    {\n        if (warn)\n        {\n            msg(M_WARN, \"******* WARNING *******: '--cipher none' was specified. \"\n                        \"This means NO encryption will be performed and tunnelled \"\n                        \"data WILL be transmitted in clear text over the network! \"\n                        \"PLEASE DO RECONSIDER THIS SETTING!\");\n        }\n    }\n    kt->digest = authname;\n    if (strcmp(authname, \"none\") != 0)\n    {\n        if (aead_cipher) /* Ignore auth for AEAD ciphers */\n        {\n            kt->digest = \"none\";\n        }\n        else\n        {\n            int hmac_length = md_kt_size(kt->digest);\n\n            if (OPENVPN_MAX_HMAC_SIZE < hmac_length)\n            {\n                msg(M_FATAL, \"HMAC '%s' not allowed: digest size too big.\", authname);\n            }\n        }\n    }\n    else if (!aead_cipher)\n    {\n        if (warn)\n        {\n            msg(M_WARN, \"******* WARNING *******: '--auth none' was specified. \"\n                        \"This means no authentication will be performed on received \"\n                        \"packets, meaning you CANNOT trust that the data received by \"\n                        \"the remote side have NOT been manipulated. \"\n                        \"PLEASE DO RECONSIDER THIS SETTING!\");\n        }\n    }\n}\n\n/**\n * Update the implicit IV for a key_ctx based on TLS session ids and cipher\n * used.\n *\n * Note that the implicit IV is based on the HMAC key of the \\c key parameter,\n * but only in AEAD modes where the HMAC key is not used for an actual HMAC.\n *\n * @param ctx                   Encrypt/decrypt key context\n * @param key                   key parameters holding the key and hmac/\n *                              implicit iv used to calculate implicit IV\n */\nstatic void\nkey_ctx_update_implicit_iv(struct key_ctx *ctx, const struct key_parameters *key)\n{\n    /* Only use implicit IV in AEAD cipher mode, where HMAC key is not used */\n    if (cipher_ctx_mode_aead(ctx->cipher))\n    {\n        size_t impl_iv_len = 0;\n        size_t impl_iv_offset = 0;\n        ASSERT(cipher_ctx_iv_length(ctx->cipher) >= OPENVPN_AEAD_MIN_IV_LEN);\n\n        /* Epoch keys use XOR of full IV length with the packet id to generate\n         * IVs. Old data format uses concatenation instead (XOR with 0 for the\n         * first 4 bytes (sizeof(packet_id_type) */\n        if (key->epoch)\n        {\n            impl_iv_len = cipher_ctx_iv_length(ctx->cipher);\n            impl_iv_offset = 0;\n        }\n        else\n        {\n            impl_iv_len = cipher_ctx_iv_length(ctx->cipher) - sizeof(packet_id_type);\n            impl_iv_offset = sizeof(packet_id_type);\n        }\n        ASSERT(impl_iv_offset + impl_iv_len <= OPENVPN_MAX_IV_LENGTH);\n        ASSERT(impl_iv_len <= MAX_HMAC_KEY_LENGTH);\n        ASSERT(impl_iv_len <= key->hmac_size);\n        CLEAR(ctx->implicit_iv);\n        memcpy(ctx->implicit_iv + impl_iv_offset, key->hmac, impl_iv_len);\n    }\n}\n\n/* given a key and key_type, build a key_ctx */\nvoid\ninit_key_ctx(struct key_ctx *ctx, const struct key_parameters *key, const struct key_type *kt,\n             int enc, const char *prefix)\n{\n    struct gc_arena gc = gc_new();\n    CLEAR(*ctx);\n    if (cipher_defined(kt->cipher))\n    {\n        ASSERT(key->cipher_size >= cipher_kt_key_size(kt->cipher));\n        ctx->cipher = cipher_ctx_new();\n        cipher_ctx_init(ctx->cipher, key->cipher, kt->cipher, enc);\n\n        const char *ciphername = cipher_kt_name(kt->cipher);\n        msg(D_CIPHER_INIT, \"%s: Cipher '%s' initialized with %u bit key\", prefix, ciphername,\n            cipher_kt_key_size(kt->cipher) * 8);\n\n        dmsg(D_SHOW_KEYS, \"%s: CIPHER KEY: %s\", prefix,\n             format_hex(key->cipher, cipher_kt_key_size(kt->cipher), 0, &gc));\n        dmsg(D_CRYPTO_DEBUG, \"%s: CIPHER block_size=%u iv_size=%u\", prefix,\n             cipher_kt_block_size(kt->cipher), cipher_kt_iv_size(kt->cipher));\n        warn_insecure_key_type(ciphername);\n    }\n\n    if (md_defined(kt->digest))\n    {\n        ASSERT(key->hmac_size >= md_kt_size(kt->digest));\n        ctx->hmac = hmac_ctx_new();\n        hmac_ctx_init(ctx->hmac, key->hmac, kt->digest);\n\n        msg(D_CIPHER_INIT, \"%s: Using %d bit message hash '%s' for HMAC authentication\", prefix,\n            md_kt_size(kt->digest) * 8, md_kt_name(kt->digest));\n\n        dmsg(D_SHOW_KEYS, \"%s: HMAC KEY: %s\", prefix,\n             format_hex(key->hmac, md_kt_size(kt->digest), 0, &gc));\n\n        dmsg(D_CRYPTO_DEBUG, \"%s: HMAC size=%d block_size=%d\", prefix, md_kt_size(kt->digest),\n             hmac_ctx_size(ctx->hmac));\n    }\n    ctx->epoch = key->epoch;\n    gc_free(&gc);\n}\n\nvoid\ninit_key_bi_ctx_send(struct key_ctx *ctx, const struct key_parameters *key_params,\n                     const struct key_type *kt, const char *name)\n{\n    char log_prefix[128] = { 0 };\n\n    snprintf(log_prefix, sizeof(log_prefix), \"Outgoing %s\", name);\n    init_key_ctx(ctx, key_params, kt, OPENVPN_OP_ENCRYPT, log_prefix);\n    key_ctx_update_implicit_iv(ctx, key_params);\n    ctx->epoch = key_params->epoch;\n}\n\nvoid\ninit_key_bi_ctx_recv(struct key_ctx *ctx, const struct key_parameters *key_params,\n                     const struct key_type *kt, const char *name)\n{\n    char log_prefix[128] = { 0 };\n\n    snprintf(log_prefix, sizeof(log_prefix), \"Incoming %s\", name);\n    init_key_ctx(ctx, key_params, kt, OPENVPN_OP_DECRYPT, log_prefix);\n    key_ctx_update_implicit_iv(ctx, key_params);\n    ctx->epoch = key_params->epoch;\n}\n\nvoid\ninit_key_ctx_bi(struct key_ctx_bi *ctx, const struct key2 *key2, int key_direction,\n                const struct key_type *kt, const char *name)\n{\n    struct key_direction_state kds;\n\n    key_direction_state_init(&kds, key_direction);\n\n    struct key_parameters send_key;\n    struct key_parameters recv_key;\n\n    key_parameters_from_key(&send_key, &key2->keys[kds.out_key]);\n    key_parameters_from_key(&recv_key, &key2->keys[kds.in_key]);\n\n    init_key_bi_ctx_send(&ctx->encrypt, &send_key, kt, name);\n    init_key_bi_ctx_recv(&ctx->decrypt, &recv_key, kt, name);\n    ctx->initialized = true;\n}\n\nvoid\nfree_key_ctx(struct key_ctx *ctx)\n{\n    if (ctx->cipher)\n    {\n        cipher_ctx_free(ctx->cipher);\n        ctx->cipher = NULL;\n    }\n    if (ctx->hmac)\n    {\n        hmac_ctx_cleanup(ctx->hmac);\n        hmac_ctx_free(ctx->hmac);\n        ctx->hmac = NULL;\n    }\n    CLEAR(ctx->implicit_iv);\n    ctx->plaintext_blocks = 0;\n    ctx->epoch = 0;\n}\n\nvoid\nfree_key_ctx_bi(struct key_ctx_bi *ctx)\n{\n    free_key_ctx(&ctx->encrypt);\n    free_key_ctx(&ctx->decrypt);\n    ctx->initialized = false;\n}\n\nstatic bool\nkey_is_zero(struct key *key, const struct key_type *kt)\n{\n    size_t cipher_length = cipher_kt_key_size(kt->cipher);\n    for (size_t i = 0; i < cipher_length; ++i)\n    {\n        if (key->cipher[i])\n        {\n            return false;\n        }\n    }\n    msg(D_CRYPT_ERRORS, \"CRYPTO INFO: WARNING: zero key detected\");\n    return true;\n}\n\n/*\n * Make sure that cipher key is a valid key for current key_type.\n */\nbool\ncheck_key(struct key *key, const struct key_type *kt)\n{\n    if (cipher_defined(kt->cipher))\n    {\n        /*\n         * Check for zero key\n         */\n        if (key_is_zero(key, kt))\n        {\n            return false;\n        }\n    }\n    return true;\n}\n\n/*\n * Generate a random key.\n */\nstatic void\ngenerate_key_random(struct key *key)\n{\n    int cipher_len = MAX_CIPHER_KEY_LENGTH;\n    int hmac_len = MAX_HMAC_KEY_LENGTH;\n\n    struct gc_arena gc = gc_new();\n\n    CLEAR(*key);\n    if (!rand_bytes(key->cipher, cipher_len) || !rand_bytes(key->hmac, hmac_len))\n    {\n        msg(M_FATAL, \"ERROR: Random number generator cannot obtain entropy for key generation\");\n    }\n\n    dmsg(D_SHOW_KEY_SOURCE, \"Cipher source entropy: %s\",\n         format_hex(key->cipher, cipher_len, 0, &gc));\n    dmsg(D_SHOW_KEY_SOURCE, \"HMAC source entropy: %s\", format_hex(key->hmac, hmac_len, 0, &gc));\n\n    gc_free(&gc);\n}\n\nstatic void\nkey_print(const struct key *key, const struct key_type *kt, const char *prefix)\n{\n    struct gc_arena gc = gc_new();\n    dmsg(D_SHOW_KEY_SOURCE, \"%s (cipher, %s, %u bits): %s\", prefix, cipher_kt_name(kt->cipher),\n         cipher_kt_key_size(kt->cipher) * 8,\n         format_hex(key->cipher, cipher_kt_key_size(kt->cipher), 0, &gc));\n    dmsg(D_SHOW_KEY_SOURCE, \"%s (hmac, %s, %d bits): %s\", prefix, md_kt_name(kt->digest),\n         md_kt_size(kt->digest) * 8, format_hex(key->hmac, md_kt_size(kt->digest), 0, &gc));\n    gc_free(&gc);\n}\n/**\n * Prints the keys in a key2 structure.\n */\nvoid\nkey2_print(const struct key2 *k, const struct key_type *kt, const char *prefix0,\n           const char *prefix1)\n{\n    ASSERT(k->n == 2);\n    key_print(&k->keys[0], kt, prefix0);\n    key_print(&k->keys[1], kt, prefix1);\n}\n\nvoid\nkey_parameters_from_key(struct key_parameters *key_params, const struct key *key)\n{\n    CLEAR(*key_params);\n    memcpy(key_params->cipher, key->cipher, MAX_CIPHER_KEY_LENGTH);\n    key_params->cipher_size = MAX_CIPHER_KEY_LENGTH;\n    memcpy(key_params->hmac, key->hmac, MAX_HMAC_KEY_LENGTH);\n    key_params->hmac_size = MAX_HMAC_KEY_LENGTH;\n}\n\nvoid\ntest_crypto(struct crypto_options *co, struct frame *frame)\n{\n    int i, j;\n    struct gc_arena gc = gc_new();\n    struct buffer src = alloc_buf_gc(frame->buf.payload_size, &gc);\n    struct buffer work = alloc_buf_gc(BUF_SIZE(frame), &gc);\n    struct buffer encrypt_workspace = alloc_buf_gc(BUF_SIZE(frame), &gc);\n    struct buffer decrypt_workspace = alloc_buf_gc(BUF_SIZE(frame), &gc);\n    struct buffer buf = clear_buf();\n    void *buf_p;\n\n    /* init work */\n    ASSERT(buf_init(&work, frame->buf.headroom));\n\n    /* init implicit IV */\n    {\n        cipher_ctx_t *cipher = co->key_ctx_bi.encrypt.cipher;\n        if (cipher_ctx_mode_aead(cipher))\n        {\n            ASSERT(cipher_ctx_iv_length(cipher) <= OPENVPN_MAX_IV_LENGTH);\n            ASSERT(cipher_ctx_iv_length(cipher) >= OPENVPN_AEAD_MIN_IV_LEN);\n\n            /* Generate dummy implicit IV */\n            ASSERT(rand_bytes(co->key_ctx_bi.encrypt.implicit_iv, OPENVPN_MAX_IV_LENGTH));\n\n            memcpy(co->key_ctx_bi.decrypt.implicit_iv, co->key_ctx_bi.encrypt.implicit_iv,\n                   OPENVPN_MAX_IV_LENGTH);\n        }\n    }\n\n    msg(M_INFO, \"Entering \" PACKAGE_NAME \" crypto self-test mode.\");\n    for (i = 1; i <= frame->buf.payload_size; ++i)\n    {\n        update_time();\n\n        msg(M_INFO, \"TESTING ENCRYPT/DECRYPT of packet length=%d\", i);\n\n        /*\n         * Load src with random data.\n         */\n        ASSERT(buf_init(&src, 0));\n        ASSERT(i <= src.capacity);\n        src.len = i;\n        ASSERT(rand_bytes(BPTR(&src), BLEN(&src)));\n\n        /* copy source to input buf */\n        buf = work;\n        buf_p = buf_write_alloc(&buf, BLENZ(&src));\n        ASSERT(buf_p);\n        memcpy(buf_p, BPTR(&src), BLENZ(&src));\n\n        /* initialize work buffer with buf.headroom bytes of prepend capacity */\n        ASSERT(buf_init(&encrypt_workspace, frame->buf.headroom));\n\n        /* encrypt */\n        openvpn_encrypt(&buf, encrypt_workspace, co);\n\n        /* decrypt */\n        openvpn_decrypt(&buf, decrypt_workspace, co, frame, BPTR(&buf));\n\n        /* compare */\n        if (buf.len != src.len)\n        {\n            msg(M_FATAL, \"SELF TEST FAILED, src.len=%d buf.len=%d\", src.len, buf.len);\n        }\n        for (j = 0; j < i; ++j)\n        {\n            const uint8_t in = *(BPTR(&src) + j);\n            const uint8_t out = *(BPTR(&buf) + j);\n            if (in != out)\n            {\n                msg(M_FATAL, \"SELF TEST FAILED, pos=%d in=%d out=%d\", j, in, out);\n            }\n        }\n    }\n    msg(M_INFO, PACKAGE_NAME \" crypto self-test mode SUCCEEDED.\");\n    gc_free(&gc);\n}\n\nconst char *\nprint_key_filename(const char *str, bool is_inline)\n{\n    if (is_inline)\n    {\n        return \"[[INLINE]]\";\n    }\n\n    return np(str);\n}\n\nvoid\ncrypto_read_openvpn_key(const struct key_type *key_type, struct key_ctx_bi *ctx,\n                        const char *key_file, bool key_inline, const int key_direction,\n                        const char *key_name, const char *opt_name, struct key2 *keydata)\n{\n    struct key2 key2;\n    struct key_direction_state kds;\n    unsigned int flags = RKF_MUST_SUCCEED;\n\n    if (key_inline)\n    {\n        flags |= RKF_INLINE;\n    }\n    read_key_file(&key2, key_file, flags);\n\n    if (key2.n != 2)\n    {\n        msg(M_ERR,\n            \"File '%s' does not have OpenVPN Static Key format.  Using \"\n            \"free-form passphrase file is not supported anymore.\",\n            print_key_filename(key_file, key_inline));\n    }\n\n    /* check for and fix highly unlikely key problems */\n    verify_fix_key2(&key2, key_type, key_file);\n\n    /* handle key direction */\n    key_direction_state_init(&kds, key_direction);\n    must_have_n_keys(key_file, opt_name, &key2, kds.need_keys);\n\n    /* initialize key in both directions */\n    init_key_ctx_bi(ctx, &key2, key_direction, key_type, key_name);\n    if (keydata)\n    {\n        *keydata = key2;\n    }\n    secure_memzero(&key2, sizeof(key2));\n}\n\nvoid\ngenerate_test_crypto_random_key(const struct key_type *key_type, struct key_ctx_bi *ctx,\n                                const char *key_name)\n{\n    struct key2 key2;\n    key2.n = 2;\n    generate_key_random(&key2.keys[0]);\n    generate_key_random(&key2.keys[1]);\n    init_key_ctx_bi(ctx, &key2, KEY_DIRECTION_BIDIRECTIONAL, key_type, key_name);\n}\n\n\n/* header and footer for static key file */\nstatic const char static_key_head[] = \"-----BEGIN OpenVPN Static key V1-----\";\nstatic const char static_key_foot[] = \"-----END OpenVPN Static key V1-----\";\n\nstatic const char printable_char_fmt[] =\n    \"Non-Hex character ('%c') found at line %d in key file '%s' (%d/%d/%d bytes found/min/max)\";\n\nstatic const char unprintable_char_fmt[] =\n    \"Non-Hex, unprintable character (0x%02x) found at line %d in key file '%s' (%d/%d/%d bytes found/min/max)\";\n\n/* read key from file */\n\nvoid\nread_key_file(struct key2 *key2, const char *file, const unsigned int flags)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer in;\n    int size;\n    uint8_t hex_byte[3] = { 0, 0, 0 };\n\n    /* parse info */\n    const unsigned char *cp;\n    int hb_index = 0;\n    int line_num = 1;\n    int line_index = 0;\n    int match = 0;\n\n    /* output */\n    uint8_t *out = (uint8_t *)&key2->keys;\n    const int keylen = sizeof(key2->keys);\n    int count = 0;\n\n    /* parse states */\n#define PARSE_INITIAL       0\n#define PARSE_HEAD          1\n#define PARSE_DATA          2\n#define PARSE_DATA_COMPLETE 3\n#define PARSE_FOOT          4\n#define PARSE_FINISHED      5\n    int state = PARSE_INITIAL;\n\n    /* constants */\n    const int hlen = (int)strlen(static_key_head);\n    const int flen = (int)strlen(static_key_foot);\n    const int onekeylen = sizeof(key2->keys[0]);\n\n    CLEAR(*key2);\n\n    /*\n     * Key can be provided as a filename in 'file' or if RKF_INLINE\n     * is set, the actual key data itself in ascii form.\n     */\n    if (flags & RKF_INLINE) /* 'file' is a string containing ascii representation of key */\n    {\n        size_t buf_size = strlen(file) + 1;\n        ASSERT(buf_size <= INT_MAX);\n        size = (int)buf_size;\n        buf_set_read(&in, (const uint8_t *)file, size);\n    }\n    else /* 'file' is a filename which refers to a file containing the ascii key */\n    {\n        in = buffer_read_from_file(file, &gc);\n        if (!buf_valid(&in))\n        {\n            msg(M_FATAL, \"Read error on key file ('%s')\", file);\n        }\n\n        size = in.len;\n    }\n\n    cp = (unsigned char *)in.data;\n    while (size > 0)\n    {\n        const unsigned char c = *cp;\n\n#if 0\n        msg(M_INFO, \"char='%c'[%d] s=%d ln=%d li=%d m=%d c=%d\",\n            c, (int)c, state, line_num, line_index, match, count);\n#endif\n\n        if (c == '\\n')\n        {\n            line_index = match = 0;\n            ++line_num;\n        }\n        else\n        {\n            /* first char of new line */\n            if (!line_index)\n            {\n                /* first char of line after header line? */\n                if (state == PARSE_HEAD)\n                {\n                    state = PARSE_DATA;\n                }\n\n                /* first char of footer */\n                if ((state == PARSE_DATA || state == PARSE_DATA_COMPLETE) && c == '-')\n                {\n                    state = PARSE_FOOT;\n                }\n            }\n\n            /* compare read chars with header line */\n            if (state == PARSE_INITIAL)\n            {\n                if (line_index < hlen && c == static_key_head[line_index])\n                {\n                    if (++match == hlen)\n                    {\n                        state = PARSE_HEAD;\n                    }\n                }\n            }\n\n            /* compare read chars with footer line */\n            if (state == PARSE_FOOT)\n            {\n                if (line_index < flen && c == static_key_foot[line_index])\n                {\n                    if (++match == flen)\n                    {\n                        state = PARSE_FINISHED;\n                    }\n                }\n            }\n\n            /* reading key */\n            if (state == PARSE_DATA)\n            {\n                if (isxdigit(c))\n                {\n                    ASSERT(hb_index >= 0 && hb_index < 2);\n                    hex_byte[hb_index++] = c;\n                    if (hb_index == 2)\n                    {\n                        uint8_t u;\n                        ASSERT(sscanf((const char *)hex_byte, \"%\" SCNx8, &u) == 1);\n                        *out++ = u;\n                        hb_index = 0;\n                        if (++count == keylen)\n                        {\n                            state = PARSE_DATA_COMPLETE;\n                        }\n                    }\n                }\n                else if (isspace(c))\n                {\n                    /* ignore white space characters */\n                }\n                else\n                {\n                    msg(M_FATAL, (isprint(c) ? printable_char_fmt : unprintable_char_fmt), c,\n                        line_num, print_key_filename(file, flags & RKF_INLINE), count, onekeylen,\n                        keylen);\n                }\n            }\n            ++line_index;\n        }\n        ++cp;\n        --size;\n    }\n\n    /*\n     * Normally we will read either 1 or 2 keys from file.\n     */\n    key2->n = count / onekeylen;\n\n    ASSERT(key2->n >= 0 && key2->n <= (int)SIZE(key2->keys));\n\n    if (flags & RKF_MUST_SUCCEED)\n    {\n        if (!key2->n)\n        {\n            msg(M_FATAL,\n                \"Insufficient key material or header text not found in file '%s' (%d/%d/%d bytes found/min/max)\",\n                print_key_filename(file, flags & RKF_INLINE), count, onekeylen, keylen);\n        }\n\n        if (state != PARSE_FINISHED)\n        {\n            msg(M_FATAL, \"Footer text not found in file '%s' (%d/%d/%d bytes found/min/max)\",\n                print_key_filename(file, flags & RKF_INLINE), count, onekeylen, keylen);\n        }\n    }\n\n    /* zero file read buffer if not an inline file */\n    if (!(flags & RKF_INLINE))\n    {\n        buf_clear(&in);\n    }\n\n#if 0\n    /* DEBUGGING */\n    {\n        int i;\n        printf(\"KEY READ, n=%d\\n\", key2->n);\n        for (i = 0; i < (int) SIZE(key2->keys); ++i)\n        {\n            /* format key as ascii */\n            const char *fmt = format_hex_ex((const uint8_t *)&key2->keys[i],\n                                            sizeof(key2->keys[i]),\n                                            0,\n                                            16,\n                                            \"\\n\",\n                                            &gc);\n            printf(\"[%d]\\n%s\\n\\n\", i, fmt);\n        }\n    }\n#endif\n\n    /* pop our garbage collection level */\n    gc_free(&gc);\n}\n\nint\nwrite_key_file(const int nkeys, const char *filename)\n{\n    struct gc_arena gc = gc_new();\n\n    int nbits = nkeys * sizeof(struct key) * 8;\n\n    /* must be large enough to hold full key file */\n    struct buffer out = alloc_buf_gc(2048, &gc);\n\n    /* how to format the ascii file representation of key */\n    const int bytes_per_line = 16;\n\n    /* write header */\n    buf_printf(&out, \"#\\n# %d bit OpenVPN static key\\n#\\n\", nbits);\n    buf_printf(&out, \"%s\\n\", static_key_head);\n\n    for (int i = 0; i < nkeys; ++i)\n    {\n        struct key key;\n        char *fmt;\n\n        /* generate random bits */\n        generate_key_random(&key);\n\n        /* format key as ascii */\n        fmt = format_hex_ex((const uint8_t *)&key, sizeof(key), 0, bytes_per_line, \"\\n\", &gc);\n\n        /* write to holding buffer */\n        buf_printf(&out, \"%s\\n\", fmt);\n\n        /* zero memory which held key component (will be freed by GC) */\n        secure_memzero(fmt, strlen(fmt));\n        secure_memzero(&key, sizeof(key));\n    }\n\n    buf_printf(&out, \"%s\\n\", static_key_foot);\n\n    /* write key file to stdout if no filename given */\n    if (!filename || strcmp(filename, \"\") == 0)\n    {\n        printf(\"%.*s\\n\", BLEN(&out), BPTR(&out));\n    }\n    /* write key file, now formatted in out, to file */\n    else if (!buffer_write_file(filename, &out))\n    {\n        nbits = -1;\n    }\n\n    /* zero memory which held file content (memory will be freed by GC) */\n    buf_clear(&out);\n\n    /* pop our garbage collection level */\n    gc_free(&gc);\n\n    return nbits;\n}\n\nvoid\nmust_have_n_keys(const char *filename, const char *option, const struct key2 *key2, int n)\n{\n    if (key2->n < n)\n    {\n#ifdef ENABLE_SMALL\n        msg(M_FATAL,\n            \"Key file '%s' used in --%s contains insufficient key material [keys found=%d required=%d]\",\n            filename, option, key2->n, n);\n#else\n        msg(M_FATAL,\n            \"Key file '%s' used in --%s contains insufficient key material [keys found=%d required=%d] -- try generating a new key file with '\" PACKAGE\n            \" --genkey secret [file]', or use the existing key file in bidirectional mode by specifying --%s without a key direction parameter\",\n            filename, option, key2->n, n, option);\n#endif\n    }\n}\n\nint\nascii2keydirection(msglvl_t msglevel, const char *str)\n{\n    if (!str)\n    {\n        return KEY_DIRECTION_BIDIRECTIONAL;\n    }\n    else if (!strcmp(str, \"0\"))\n    {\n        return KEY_DIRECTION_NORMAL;\n    }\n    else if (!strcmp(str, \"1\"))\n    {\n        return KEY_DIRECTION_INVERSE;\n    }\n    else\n    {\n        msg(msglevel, \"Unknown key direction '%s' -- must be '0' or '1'\", str);\n        return -1;\n    }\n    return KEY_DIRECTION_BIDIRECTIONAL; /* NOTREACHED */\n}\n\nconst char *\nkeydirection2ascii(int kd, bool remote, bool humanreadable)\n{\n    if (kd == KEY_DIRECTION_BIDIRECTIONAL)\n    {\n        if (humanreadable)\n        {\n            return \"not set\";\n        }\n        else\n        {\n            return NULL;\n        }\n    }\n    else if (kd == KEY_DIRECTION_NORMAL)\n    {\n        return remote ? \"1\" : \"0\";\n    }\n    else if (kd == KEY_DIRECTION_INVERSE)\n    {\n        return remote ? \"0\" : \"1\";\n    }\n    else\n    {\n        ASSERT(0);\n    }\n    return NULL; /* NOTREACHED */\n}\n\nvoid\nkey_direction_state_init(struct key_direction_state *kds, int key_direction)\n{\n    CLEAR(*kds);\n    switch (key_direction)\n    {\n        case KEY_DIRECTION_NORMAL:\n            kds->out_key = 0;\n            kds->in_key = 1;\n            kds->need_keys = 2;\n            break;\n\n        case KEY_DIRECTION_INVERSE:\n            kds->out_key = 1;\n            kds->in_key = 0;\n            kds->need_keys = 2;\n            break;\n\n        case KEY_DIRECTION_BIDIRECTIONAL:\n            kds->out_key = 0;\n            kds->in_key = 0;\n            kds->need_keys = 1;\n            break;\n\n        default:\n            ASSERT(0);\n    }\n}\n\nvoid\nverify_fix_key2(struct key2 *key2, const struct key_type *kt, const char *shared_secret_file)\n{\n    int i;\n\n    for (i = 0; i < key2->n; ++i)\n    {\n        /* This should be a very improbable failure */\n        if (!check_key(&key2->keys[i], kt))\n        {\n            msg(M_FATAL, \"Key #%d in '%s' is bad.  Try making a new key with --genkey.\", i + 1,\n                shared_secret_file);\n        }\n    }\n}\n\nvoid\nprng_bytes(uint8_t *output, int len)\n{\n    ASSERT(rand_bytes(output, len));\n}\n\n/* an analogue to the random() function, but use prng_bytes */\nlong int\nget_random(void)\n{\n    long int l;\n    prng_bytes((unsigned char *)&l, sizeof(l));\n    if (l < 0)\n    {\n        l = -l;\n    }\n    return l;\n}\n\nvoid\nprint_cipher(const char *ciphername)\n{\n    printf(\"%s  (%u bit key, \", cipher_kt_name(ciphername), cipher_kt_key_size(ciphername) * 8);\n\n    if (cipher_kt_block_size(ciphername) == 1)\n    {\n        printf(\"stream cipher\");\n    }\n    else\n    {\n        printf(\"%u bit block\", cipher_kt_block_size(ciphername) * 8);\n    }\n\n    if (!cipher_kt_mode_cbc(ciphername))\n    {\n        printf(\", TLS client/server mode only\");\n    }\n\n    const char *reason;\n    if (!cipher_valid_reason(ciphername, &reason))\n    {\n        printf(\", %s\", reason);\n    }\n\n    printf(\")\\n\");\n}\n\nstatic const cipher_name_pair *\nget_cipher_name_pair(const char *cipher_name)\n{\n    const cipher_name_pair *pair;\n    size_t i = 0;\n\n    /* Search for a cipher name translation */\n    for (; i < cipher_name_translation_table_count; i++)\n    {\n        pair = &cipher_name_translation_table[i];\n        if (0 == strcmp(cipher_name, pair->openvpn_name)\n            || 0 == strcmp(cipher_name, pair->lib_name))\n        {\n            return pair;\n        }\n    }\n\n    /* Nothing found, return null */\n    return NULL;\n}\n\nconst char *\ntranslate_cipher_name_from_openvpn(const char *cipher_name)\n{\n    const cipher_name_pair *pair = get_cipher_name_pair(cipher_name);\n\n    if (NULL == pair)\n    {\n        return cipher_name;\n    }\n\n    return pair->lib_name;\n}\n\nconst char *\ntranslate_cipher_name_to_openvpn(const char *cipher_name)\n{\n    const cipher_name_pair *pair = get_cipher_name_pair(cipher_name);\n\n    if (NULL == pair)\n    {\n        return cipher_name;\n    }\n\n    return pair->openvpn_name;\n}\n\nvoid\nwrite_pem_key_file(const char *filename, const char *pem_name)\n{\n    struct gc_arena gc = gc_new();\n    struct key server_key = { 0 };\n    struct buffer server_key_buf = clear_buf();\n    struct buffer server_key_pem = clear_buf();\n\n    if (!rand_bytes((void *)&server_key, sizeof(server_key)))\n    {\n        msg(M_NONFATAL, \"ERROR: could not generate random key\");\n        goto cleanup;\n    }\n    buf_set_read(&server_key_buf, (void *)&server_key, sizeof(server_key));\n    if (!crypto_pem_encode(pem_name, &server_key_pem, &server_key_buf, &gc))\n    {\n        msg(M_WARN, \"ERROR: could not PEM-encode key\");\n        goto cleanup;\n    }\n\n    if (!filename || strcmp(filename, \"\") == 0)\n    {\n        printf(\"%.*s\", BLEN(&server_key_pem), BPTR(&server_key_pem));\n    }\n    else if (!buffer_write_file(filename, &server_key_pem))\n    {\n        msg(M_ERR, \"ERROR: could not write key file\");\n        goto cleanup;\n    }\n\ncleanup:\n    secure_memzero(&server_key, sizeof(server_key));\n    buf_clear(&server_key_pem);\n    gc_free(&gc);\n    return;\n}\n\nbool\ngenerate_ephemeral_key(struct buffer *key, const char *key_name)\n{\n    const int len = BCAP(key);\n\n    msg(M_INFO, \"Using random %s.\", key_name);\n\n    if (!rand_bytes(BEND(key), len))\n    {\n        msg(M_WARN, \"ERROR: could not generate random key\");\n        return false;\n    }\n\n    buf_inc_len(key, len);\n\n    return true;\n}\n\nbool\nread_pem_key_file(struct buffer *key, const char *pem_name, const char *key_file, bool key_inline)\n{\n    bool ret = false;\n    struct buffer key_pem = { 0 };\n    struct gc_arena gc = gc_new();\n\n    if (!key_inline)\n    {\n        key_pem = buffer_read_from_file(key_file, &gc);\n        if (!buf_valid(&key_pem))\n        {\n            msg(M_WARN, \"ERROR: failed to read %s file (%s)\", pem_name, key_file);\n            goto cleanup;\n        }\n    }\n    else\n    {\n        buf_set_read(&key_pem, (const void *)key_file, strlen(key_file) + 1);\n    }\n\n    if (!crypto_pem_decode(pem_name, key, &key_pem))\n    {\n        msg(M_WARN, \"ERROR: %s pem decode failed\", pem_name);\n        goto cleanup;\n    }\n\n    ret = true;\ncleanup:\n    if (!key_inline)\n    {\n        buf_clear(&key_pem);\n    }\n    gc_free(&gc);\n    return ret;\n}\n\nbool\ncheck_tls_prf_working(void)\n{\n    /* Modern TLS libraries might no longer support the TLS 1.0 PRF with\n     * MD5+SHA1. This allows us to establish connections only\n     * with other 2.6.0+ OpenVPN peers.\n     * Do a simple dummy test here to see if it works. */\n    const char *seed = \"tls1-prf-test\";\n    const char *secret = \"tls1-prf-test-secret\";\n    uint8_t out[8];\n    uint8_t expected_out[] = { 'q', 'D', 0xfe, '%', '@', 's', 'u', 0x95 };\n\n    int ret = ssl_tls1_PRF((uint8_t *)seed, strlen(seed), (uint8_t *)secret,\n                           strlen(secret), out, sizeof(out));\n\n    return (ret && memcmp(out, expected_out, sizeof(out)) == 0);\n}\n"
  },
  {
    "path": "src/openvpn/crypto.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Data Channel Cryptography Module\n *\n * @addtogroup data_crypto Data Channel Crypto module\n *\n * @par Crypto packet formats\n * The Data Channel Crypto module supports a number of crypto modes and\n * configurable options. The actual packet format depends on these options. A\n * Data Channel packet can consist of:\n *  - \\b Opcode, one byte specifying the packet type (see @ref network_protocol\n *    \"Network protocol\").\n *  - \\b Peer-id, if using the v2 data channel packet format (see @ref\n *    network_protocol \"Network protocol\").\n *  - \\b HMAC, covering the ciphertext IV + ciphertext. The HMAC size depends\n *    on the \\c \\-\\-auth option. If \\c \\-\\-auth \\c none is specified, there is no\n *    HMAC at all.\n *  - \\b Ciphertext \\b IV. The IV size depends on the \\c \\-\\-cipher option.\n *  - \\b Packet \\b ID, a 32-bit incrementing packet counter that provides replay\n *    protection.\n *  - \\b Timestamp, a 32-bit timestamp of the current time.\n *  - \\b Payload, the plain text network packet to be encrypted (unless\n *    encryption is disabled by using \\c \\-\\-cipher \\c none). The payload might\n *    already be compressed (see @ref compression \"Compression module\").\n *\n * @par\n * This section does not discuss the opcode and peer-id, since those do not\n * depend on the data channel crypto. See @ref network_protocol\n * \"Network protocol\" for more information on those.\n *\n * @par\n * \\e Legenda \\n\n * <tt>[ xxx ]</tt> = unprotected \\n\n * <tt>[ - xxx - ]</tt> = authenticated \\n\n * <tt>[ * xxx * ]</tt> = encrypted and authenticated\n *\n * @par\n * <b>CBC data channel cypto format</b> \\n\n * In CBC mode, both TLS-mode and static key mode are supported. The IV\n * consists of random bits to provide unpredictable IVs. \\n\n * <i>CBC IV format:</i> \\n\n * <tt> [ - random - ] </tt> \\n\n * <i>CBC data channel crypto format in TLS-mode:</i> \\n\n * <tt> [ HMAC ] [ - IV - ] [ * packet ID * ] [ * packet payload * ] </tt> \\n\n * <i>CBC data channel crypto format in static key mode:</i> \\n\n * <tt> [ HMAC ] [ - IV - ] [ * packet ID * ] [ * timestamp * ]\n * [ * packet payload * ] </tt>\n *\n * @par\n * <b>CFB/OFB data channel crypto format</b> \\n\n * CFB and OFB modes are only supported in TLS mode. In these modes, the IV\n * consists of the packet counter and a timestamp. If the IV is more than 8\n * bytes long, the remaining space is filled with zeroes. The packet counter may\n * not roll over within a single TLS sessions. This results in a unique IV for\n * each packet, as required by the CFB and OFB cipher modes.\n *\n * @par\n * <i>CFB/OFB IV format:</i> \\n\n * <tt>   [ - packet ID - ] [ - timestamp - ] [ - opt: zero-padding - ] </tt>\\n\n * <i>CFB/OFB data channel crypto format:</i> \\n\n * <tt>   [ HMAC ] [ - IV - ] [ * packet payload * ] </tt>\n *\n * @par\n * <b>GCM data channel crypto format</b> \\n\n * GCM modes are only supported in TLS mode.  In these modes, the IV consists of\n * the 32-bit packet counter followed by data from the HMAC key.  The HMAC key\n * can be used as IV, since in GCM and CCM modes the HMAC key is not used for\n * the HMAC.  The packet counter may not roll over within a single TLS sessions.\n * This results in a unique IV for each packet, as required by GCM.\n *\n * @par\n * The HMAC key data is pre-shared during the connection setup, and thus can be\n * omitted in on-the-wire packets, saving 8 bytes per packet (for GCM and CCM).\n *\n * @par\n * In GCM mode, P_DATA_V2 headers (the opcode and peer-id) are also\n * authenticated as Additional Data.\n *\n * @par\n * <i>GCM IV format:</i> \\n\n * <tt>   [ - packet ID - ] [ - HMAC key data - ] </tt>\\n\n * <i>P_DATA_V1 GCM data channel crypto format:</i> \\n\n * <tt>   [ opcode ] [ - packet ID - ] [ TAG ] [ * packet payload * ] </tt>\n * <i>P_DATA_V2 GCM data channel crypto format:</i> \\n\n * <tt>   [ - opcode/peer-id - ] [ - packet ID - ] [ TAG ] [ * packet payload * ] </tt>\n *\n * @par\n * <b>No-crypto data channel format</b> \\n\n * In no-crypto mode (\\c \\-\\-cipher \\c none is specified), both TLS-mode and\n * static key mode are supported. No encryption will be performed on the packet,\n * but packets can still be authenticated. This mode does not require an IV.\\n\n * <i>No-crypto data channel crypto format in TLS-mode:</i> \\n\n * <tt> [ HMAC ] [ - packet ID - ] [ - packet payload - ] </tt> \\n\n * <i>No-crypto data channel crypto format in static key mode:</i> \\n\n * <tt> [ HMAC ] [ - packet ID - ] [ - timestamp - ] [ - packet payload - ] </tt>\n *\n */\n\n#ifndef CRYPTO_H\n#define CRYPTO_H\n\n#include \"crypto_backend.h\"\n#include \"basic.h\"\n#include \"buffer.h\"\n#include \"packet_id.h\"\n#include \"mtu.h\"\n\n/** Wrapper struct to pass around SHA256 digests */\nstruct sha256_digest\n{\n    uint8_t digest[SHA256_DIGEST_LENGTH];\n};\n\n/*\n * Defines a key type and key length for both cipher and HMAC.\n */\nstruct key_type\n{\n    const char *cipher; /**< const name of the cipher */\n    const char *digest; /**< Message digest static parameters */\n};\n\n/**\n * Container for unidirectional cipher and HMAC %key material.\n * @ingroup control_processor. This is used as a wire format/file format\n * key, so it cannot be changed to add fields or change the length of fields\n */\nstruct key\n{\n    uint8_t cipher[MAX_CIPHER_KEY_LENGTH];\n    /**< %Key material for cipher operations. */\n    uint8_t hmac[MAX_HMAC_KEY_LENGTH];\n    /**< %Key material for HMAC operations. */\n};\n\n/** internal structure similar to struct key that holds key information\n * but is not represented on wire and can be changed/extended\n */\nstruct key_parameters\n{\n    /** %Key material for cipher operations. */\n    uint8_t cipher[MAX_CIPHER_KEY_LENGTH];\n\n    /** Number of bytes set in the cipher key material */\n    unsigned int cipher_size;\n\n    /** %Key material for HMAC operations. */\n    uint8_t hmac[MAX_HMAC_KEY_LENGTH];\n\n    /** Number of bytes set in the HMac key material */\n    unsigned int hmac_size;\n\n    /** the epoch of the key. Only defined/non zero if key parameters\n     * represent a data channel epoch key parameters.\n     * Other uses of this struct leave this zero. */\n    uint16_t epoch;\n};\n\n/**\n * Converts a struct key representation into a struct key_parameters\n * representation.\n *\n * @param key_params    destination for the converted struct\n * @param key           source of the conversion\n */\nvoid key_parameters_from_key(struct key_parameters *key_params, const struct key *key);\n\nstruct epoch_key\n{\n    uint8_t epoch_key[SHA256_DIGEST_LENGTH];\n    uint16_t epoch;\n};\n\n/**\n * Container for one set of cipher and/or HMAC contexts.\n * @ingroup control_processor\n */\nstruct key_ctx\n{\n    cipher_ctx_t *cipher; /**< Generic cipher %context. */\n    hmac_ctx_t *hmac;     /**< Generic HMAC %context. */\n    /**\n     * This implicit IV will be always XORed with the packet id that is sent on\n     * the wire to get the IV. For the common AEAD ciphers of AES-GCM and\n     * Chacha20-Poly1305, the length of the IV is 12 bytes (96 bits).\n     *\n     * For non-epoch 32bit packet id AEAD format we set the first 32\n     * bits of implicit_iv to 0.\n     * Xor with the packet id in this case works as concatenation:\n     * after xor the lower 32 bit of the IV are the packet id and\n     * the rest of the IV is from the implicit IV.\n     */\n    uint8_t implicit_iv[OPENVPN_MAX_IV_LENGTH];\n    /**< The implicit part of the IV */\n    size_t implicit_iv_len; /**< The length of implicit_iv */\n    /** Counter for the number of plaintext block encrypted using this cipher\n     * with the current key in number of 128 bit blocks (only used for\n     * AEAD ciphers) */\n    uint64_t plaintext_blocks;\n    /** number of failed verification using this cipher */\n    uint64_t failed_verifications;\n    /** OpenVPN data channel epoch, this variable holds the\n     *  epoch number this key belongs to. Note that epoch 0 is not used\n     *  and epoch is always non-zero for epoch key contexts */\n    uint16_t epoch;\n};\n\n#define KEY_DIRECTION_BIDIRECTIONAL 0 /* same keys for both directions */\n#define KEY_DIRECTION_NORMAL        1 /* encrypt with keys[0], decrypt with keys[1] */\n#define KEY_DIRECTION_INVERSE       2 /* encrypt with keys[1], decrypt with keys[0] */\n\n/**\n * Container for bidirectional cipher and HMAC %key material.\n * @ingroup control_processor\n */\nstruct key2\n{\n    int n;              /**< The number of \\c key objects stored\n                         *   in the \\c key2.keys array. */\n    struct key keys[2]; /**< Two unidirectional sets of %key\n                         *   material. The first key is the client\n                         *   (encrypts) to server (decrypts), the\n                         *   second the server to client key. */\n};\n\n/**\n * %Key ordering of the \\c key2.keys array.\n * @ingroup control_processor\n *\n * This structure takes care of correct ordering when using unidirectional\n * or bidirectional %key material, and allows the same shared secret %key\n * file to be loaded in the same way by client and server by having one of\n * the hosts use an reversed ordering.\n */\nstruct key_direction_state\n{\n    int out_key;   /**< Index into the \\c key2.keys array for\n                    *   the sending direction. */\n    int in_key;    /**< Index into the \\c key2.keys array for\n                    *   the receiving direction. */\n    int need_keys; /**< The number of key objects necessary\n                    *   to support both sending and\n                    *   receiving.\n                    *\n                    *   This will be 1 if the same keys are\n                    *   used in both directions, or 2 if\n                    *   there are two sets of unidirectional\n                    *   keys. */\n};\n\n/**\n * Container for two sets of OpenSSL cipher and/or HMAC contexts for both\n * sending and receiving directions.\n * @ingroup control_processor\n */\nstruct key_ctx_bi\n{\n    struct key_ctx encrypt; /**< Cipher and/or HMAC contexts for sending\n                             *   direction. */\n    struct key_ctx decrypt; /**< cipher and/or HMAC contexts for\n                             *   receiving direction. */\n    bool initialized;\n};\n\n/**\n * Security parameter state for processing data channel packets.\n * @ingroup data_crypto\n */\nstruct crypto_options\n{\n    struct key_ctx_bi key_ctx_bi;\n    /**< OpenSSL cipher and HMAC contexts for\n     *   both sending and receiving\n     *   directions. */\n\n    /** last epoch_key used for generation of the current send data keys.\n     * As invariant, the epoch of epoch_key_send is always kept >= the epoch of\n     * key_ctx_bi.decrypt.epoch\n     */\n    struct epoch_key epoch_key_send;\n\n    /** epoch_key used for the highest receive epoch keys */\n    struct epoch_key epoch_key_recv;\n\n    /** the key_type that is used to generate the epoch keys */\n    struct key_type epoch_key_type;\n\n    /** The limit for AEAD cipher, this is the sum of packets + blocks\n     * that are allowed to be used. Will switch to a new epoch if this\n     * limit is reached.\n     */\n    uint64_t aead_usage_limit;\n\n    /** Keeps the future epoch data keys for decryption. The current one\n     * that is expected to be used is stored in key_ctx_bi.\n     *\n     * for encryption keys this is not needed as we only need the current\n     * and move to another key by iteration and we never need to go back\n     * to an older key.\n     */\n    struct key_ctx *epoch_data_keys_future;\n\n    /** number of keys stored in \\c epoch_data_keys_future */\n    uint16_t epoch_data_keys_future_count;\n\n    /** The old key before the sender switched to a new epoch data key */\n    struct key_ctx epoch_retiring_data_receive_key;\n    struct packet_id_rec epoch_retiring_key_pid_recv;\n\n    struct packet_id packet_id; /**< Current packet ID state for both\n                                 *   sending and receiving directions.\n                                 *\n                                 *   This contains the packet id that is\n                                 *   used for replay protection.\n                                 *\n                                 *   The packet id also used as the IV\n                                 *   for AEAD/OFB/CFG ciphers.\n                                 */\n    struct packet_id_persist *pid_persist;\n    /**< Persistent packet ID state for\n     *   keeping state between successive\n     *   OpenVPN process startups. */\n\n#define CO_PACKET_ID_LONG_FORM         (1u << 0)\n    /**< Bit-flag indicating whether to use\n     *   OpenVPN's long packet ID format. */\n#define CO_IGNORE_PACKET_ID            (1u << 1)\n    /**< Bit-flag indicating whether to ignore\n     *   the packet ID of a received packet.\n     *   This flag is used during processing\n     *   of the first packet received from a\n     *   client. */\n#define CO_MUTE_REPLAY_WARNINGS        (1u << 2)\n    /**< Bit-flag indicating not to display\n     *   replay warnings. */\n#define CO_USE_TLS_KEY_MATERIAL_EXPORT (1u << 3)\n    /**< Bit-flag indicating that data channel key derivation\n     * is done using TLS keying material export [RFC5705]\n     */\n#define CO_RESEND_WKC                  (1u << 4)\n    /**< Bit-flag indicating that the client is expected to\n     * resend the wrapped client key with the 2nd packet (packet-id 1)\n     * like with the HARD_RESET_CLIENT_V3 packet */\n#define CO_FORCE_TLSCRYPTV2_COOKIE     (1u << 5)\n    /**< Bit-flag indicating that we do not allow clients that do\n     * not support resending the wrapped client key (WKc) with the\n     * third packet of the three-way handshake */\n#define CO_USE_CC_EXIT_NOTIFY          (1u << 6)\n    /**< Bit-flag indicating that explicit exit notifies should be\n     * sent via the control channel instead of using an OCC message\n     */\n#define CO_USE_DYNAMIC_TLS_CRYPT       (1u << 7)\n    /**< Bit-flag indicating that renegotiations are using tls-crypt\n     *   with a TLS-EKM derived key.\n     */\n#define CO_EPOCH_DATA_KEY_FORMAT       (1u << 8)\n    /**< Bit-flag indicating the epoch the data format. This format\n     * has the AEAD tag at the end of the packet and is using a longer\n     * 64-bit packet id that is split into a 16 bit epoch and 48 bit\n     * epoch counter\n     */\n\n    unsigned int flags; /**< Bit-flags determining behavior of\n                         *   security operation functions. */\n};\n\n#define CRYPT_ERROR_EXIT(flags, format)          \\\n    do                                           \\\n    {                                            \\\n        msg(flags, \"%s: \" format, error_prefix); \\\n        goto error_exit;                         \\\n    } while (false)\n\n#define CRYPT_ERROR(format) CRYPT_ERROR_EXIT(D_CRYPT_ERRORS, format)\n#define CRYPT_DROP(format)  CRYPT_ERROR_EXIT(D_MULTI_DROPPED, format)\n\n/**\n * Minimal IV length for AEAD mode ciphers (in bytes):\n * 4-byte packet id + 8 bytes implicit IV.\n */\n#define OPENVPN_AEAD_MIN_IV_LEN (sizeof(packet_id_type) + 8)\n\n#define RKF_MUST_SUCCEED (1 << 0)\n#define RKF_INLINE       (1 << 1)\nvoid read_key_file(struct key2 *key2, const char *file, const unsigned int flags);\n\n/**\n * Write nkeys 1024-bits keys to file.\n *\n * @returns number of random bits written, or -1 on failure.\n */\nint write_key_file(const int nkeys, const char *filename);\n\nbool check_key(struct key *key, const struct key_type *kt);\n\n/**\n * Initialize a key_type structure with.\n *\n * @param kt          The struct key_type to initialize\n * @param ciphername  The name of the cipher to use\n * @param authname    The name of the HMAC digest to use\n * @param tls_mode    Specifies whether we are running in TLS mode, which allows\n *                    more ciphers than static key mode.\n * @param warn        Print warnings when null cipher / auth is used.\n */\nvoid init_key_type(struct key_type *kt, const char *ciphername, const char *authname, bool tls_mode,\n                   bool warn);\n\n/*\n * Key context functions\n */\n\nvoid init_key_ctx(struct key_ctx *ctx, const struct key_parameters *key, const struct key_type *kt,\n                  int enc, const char *prefix);\n\nvoid init_key_bi_ctx_send(struct key_ctx *ctx, const struct key_parameters *key,\n                          const struct key_type *kt, const char *name);\n\nvoid init_key_bi_ctx_recv(struct key_ctx *ctx, const struct key_parameters *key,\n                          const struct key_type *kt, const char *name);\n\nvoid free_key_ctx(struct key_ctx *ctx);\n\nvoid init_key_ctx_bi(struct key_ctx_bi *ctx, const struct key2 *key2, int key_direction,\n                     const struct key_type *kt, const char *name);\n\nvoid free_key_ctx_bi(struct key_ctx_bi *ctx);\n\n\n/**************************************************************************/\n/** @name Functions for performing security operations on data channel packets\n *  @{ */\n\n/**\n * Encrypt and HMAC sign a packet so that it can be sent as a data channel\n * VPN tunnel packet to a remote OpenVPN peer.\n * @ingroup data_crypto\n *\n * This function handles encryption and HMAC signing of a data channel\n * packet before it is sent to its remote OpenVPN peer.  It receives the\n * necessary security parameters in the \\a opt argument, which should have\n * been set to the correct values by the \\c tls_pre_encrypt() function.\n *\n * This function calls the \\c EVP_Cipher* and \\c HMAC_* functions of the\n * OpenSSL library to perform the actual security operations.\n *\n * If an error occurs during processing, then the \\a buf %buffer is set to\n * empty.\n *\n * @param[in,out] buf  - The %buffer containing the packet on which to\n *                       perform security operations.\n * @param work         - An initialized working %buffer.\n * @param opt          - The security parameter state for this VPN tunnel.\n *\n * @note On return, the \\a buf argument\n *     will point to the resulting %buffer.  This %buffer will either\n *     contain the processed packet ready for sending, or be empty if an\n *     error occurred.\n */\nvoid openvpn_encrypt(struct buffer *buf, struct buffer work, struct crypto_options *opt);\n\n\n/**\n * HMAC verify and decrypt a data channel packet received from a remote\n * OpenVPN peer.\n * @ingroup data_crypto\n *\n * This function handles authenticating and decrypting a data channel\n * packet received from a remote OpenVPN peer.  It receives the necessary\n * security parameters in the \\a opt argument, which should have been set\n * to the correct values by the \\c tls_pre_decrypt() function.\n *\n * This function calls the \\c EVP_Cipher* and \\c HMAC_* functions of the\n * OpenSSL library to perform the actual security operations.\n *\n * If an error occurs during processing, then the \\a buf %buffer is set to\n * empty.\n *\n * @param buf          - The %buffer containing the packet received from a\n *                       remote OpenVPN peer on which to perform security\n *                       operations.\n * @param work         - A working %buffer.\n * @param opt          - The security parameter state for this VPN tunnel.\n * @param frame        - The packet geometry parameters for this VPN\n *                       tunnel.\n * @param ad_start     - A pointer into buf, indicating from where to start\n *                       authenticating additional data (AEAD mode only).\n *\n * @return\n * @li True, if the packet was authenticated and decrypted successfully.\n * @li False, if an error occurred. \\n On return, the \\a buf argument will\n *     point to the resulting %buffer.  This %buffer will either contain\n *     the plaintext packet ready for further processing, or be empty if\n *     an error occurred.\n */\nbool openvpn_decrypt(struct buffer *buf, struct buffer work, struct crypto_options *opt,\n                     const struct frame *frame, const uint8_t *ad_start);\n\n/** @} name Functions for performing security operations on data channel packets */\n\n/**\n * Check packet ID for replay, and perform replay administration.\n *\n * @param opt   Crypto options for this packet, contains replay state.\n * @param pin   Packet ID read from packet.\n * @param epoch Epoch read from packet or 0 when epoch is not used.\n * @param error_prefix  Prefix to use when printing error messages.\n * @param gc    Garbage collector to use.\n *\n * @return true if packet ID is validated to be not a replay, false otherwise.\n */\nbool crypto_check_replay(struct crypto_options *opt, const struct packet_id_net *pin,\n                         uint16_t epoch, const char *error_prefix, struct gc_arena *gc);\n\n\n/** Calculate the maximum overhead that our encryption has\n * on a packet. This does not include needed additional buffer size\n *\n * This does NOT include the padding and rounding of CBC size\n * as the users (mssfix/fragment) of this function need to adjust for\n * this and add it themselves.\n *\n * @param kt            Struct with the crypto algorithm to use\n * @param pkt_id_size   Size of the packet id\n * @param occ           if true calculates the overhead for crypto in the same\n *                      incorrect way as all previous OpenVPN versions did, to\n *                      end up with identical numbers for OCC compatibility\n */\nsize_t calculate_crypto_overhead(const struct key_type *kt, unsigned int pkt_id_size,\n                                 bool occ);\n\n/** Return the worst-case OpenVPN crypto overhead (in bytes) */\nunsigned int crypto_max_overhead(void);\n\n/**\n * Generate a server key with enough randomness to fill a key struct\n * and write to file.\n *\n * @param filename          Filename of the server key file to create.\n * @param key_name          The name to use in the PEM header/footer.\n */\nvoid write_pem_key_file(const char *filename, const char *key_name);\n\n/**\n * Generate ephermal key material into the key structure\n *\n * @param key           the key structure that will hold the key material\n * @param pem_name      the name used for logging\n * @return              true if key generation was successful\n */\nbool generate_ephemeral_key(struct buffer *key, const char *pem_name);\n\n/**\n * Read key material from a PEM encoded files into the key structure\n * @param key           the key structure that will hold the key material\n * @param pem_name      the name used in the pem encoding start/end lines\n * @param key_file      name of the file to read or the key itself if\n *                      key_inline is true\n * @param key_inline    True if key_file contains an inline key, False\n *                      otherwise.\n * @return              true if reading into key was successful\n */\nbool read_pem_key_file(struct buffer *key, const char *pem_name, const char *key_file,\n                       bool key_inline);\n\n/*\n * Message digest-based pseudo random number generator.\n *\n * If the PRNG was initialised with a certain message digest, uses the digest\n * to calculate the next random number, and prevent depletion of the entropy\n * pool.\n *\n * This PRNG is aimed at IV generation and similar miscellaneous tasks. Use\n * \\c rand_bytes() for higher-assurance functionality.\n *\n * Retrieves len bytes of pseudo random data, and places it in output.\n *\n * @param output        Output buffer\n * @param len           Length of the output buffer\n */\nvoid prng_bytes(uint8_t *output, int len);\n\n/* an analogue to the random() function, but use prng_bytes */\nlong int get_random(void);\n\n/** Print a cipher list entry */\nvoid print_cipher(const char *cipher);\n\nvoid test_crypto(struct crypto_options *co, struct frame *f);\n\n\n/* key direction functions */\n\nvoid key_direction_state_init(struct key_direction_state *kds, int key_direction);\n\nvoid verify_fix_key2(struct key2 *key2, const struct key_type *kt, const char *shared_secret_file);\n\nvoid must_have_n_keys(const char *filename, const char *option, const struct key2 *key2, int n);\n\nint ascii2keydirection(msglvl_t msglevel, const char *str);\n\nconst char *keydirection2ascii(int kd, bool remote, bool humanreadable);\n\n/* print keys */\nvoid key2_print(const struct key2 *k, const struct key_type *kt, const char *prefix0,\n                const char *prefix1);\n\nvoid crypto_read_openvpn_key(const struct key_type *key_type, struct key_ctx_bi *ctx,\n                             const char *key_file, bool key_inline, const int key_direction,\n                             const char *key_name, const char *opt_name, struct key2 *keydata);\n\n/**\n * Generate a random key and initialise ctx to be used the in the crypto random\n * test\n */\nvoid generate_test_crypto_random_key(const struct key_type *key_type, struct key_ctx_bi *ctx,\n                                     const char *key_name);\n\n/*\n * Inline functions\n */\n\n/**\n * As memcmp(), but constant-time.\n * Returns 0 when data is equal, non-zero otherwise.\n */\nint memcmp_constant_time(const void *a, const void *b, size_t size);\n\nstatic inline bool\nkey_ctx_bi_defined(const struct key_ctx_bi *key)\n{\n    return key->encrypt.cipher || key->encrypt.hmac || key->decrypt.cipher || key->decrypt.hmac;\n}\n\n/**\n * To be used when printing a string that may contain inline data.\n *\n * If \"is_inline\" is true, return the inline tag.\n * If \"is_inline\" is false and \"str\" is not NULL, return \"str\".\n * Return the constant string \"[NULL]\" otherwise.\n *\n * @param str       the original string to return when is_inline is false\n * @param is_inline true when str contains an inline data of some sort\n */\nconst char *print_key_filename(const char *str, bool is_inline);\n\n/**\n * Creates and validates an instance of struct key_type with the provided\n * algs.\n *\n * @param cipher    the cipher algorithm to use (must be a string literal)\n * @param md        the digest algorithm to use (must be a string literal)\n * @param optname   the name of the option requiring the key_type object\n *\n * @return          the initialized key_type instance\n */\nstatic inline struct key_type\ncreate_kt(const char *cipher, const char *md, const char *optname)\n{\n    struct key_type kt;\n    kt.cipher = cipher;\n    kt.digest = md;\n\n    if (cipher_defined(kt.cipher) && !cipher_valid(kt.cipher))\n    {\n        msg(M_WARN, \"ERROR: --%s requires %s support.\", optname, kt.cipher);\n        return (struct key_type){ 0 };\n    }\n    if (md_defined(kt.digest) && !md_valid(kt.digest))\n    {\n        msg(M_WARN, \"ERROR: --%s requires %s support.\", optname, kt.digest);\n        return (struct key_type){ 0 };\n    }\n\n    return kt;\n}\n\n/**\n * Check if the cipher is an AEAD cipher and needs to be limited to a certain\n * number of number of blocks + packets. Return 0 if ciphername is not an AEAD\n * cipher or no limit (e.g. Chacha20-Poly1305) is needed. (Or the limit is\n * larger than 2^64)\n *\n * For reference see the OpenVPN RFC draft and\n * https://www.ietf.org/archive/id/draft-irtf-cfrg-aead-limits-08.html\n */\nuint64_t cipher_get_aead_limits(const char *ciphername);\n\n/**\n * Check if the number of failed decryption is over the acceptable limit.\n */\nstatic inline bool\ncipher_decrypt_verify_fail_exceeded(const struct key_ctx *ctx)\n{\n    /* Use 2**36, same as DTLS 1.3. Strictly speaking this only guarantees\n     * the security margin for packets up to 2^10 blocks (16384 bytes)\n     * but we accept slightly lower security bound for the edge\n     * of Chacha20-Poly1305 and packets over 16k as MTUs over 16k are\n     * extremely rarely used */\n    return ctx->failed_verifications > (1ull << 36);\n}\n\n/**\n * Check if the number of failed decryption is approaching the limit and we\n * should try to move to a new key\n */\nstatic inline bool\ncipher_decrypt_verify_fail_warn(const struct key_ctx *ctx)\n{\n    /* Use 2**35, half the amount after which we refuse to decrypt */\n    return ctx->failed_verifications > (1ull << 35);\n}\n\n\n/**\n * Blocksize used for the AEAD limit caluclation\n *\n * Since cipher_ctx_block_size() is not reliable and will return 1 in many\n * cases use a hardcoded blocksize instead */\n#define AEAD_LIMIT_BLOCKSIZE 16\n\n/**\n * Checks if the current TLS library supports the TLS 1.0 PRF with MD5+SHA1\n * that OpenVPN uses when TLS Keying Material Export is not available.\n *\n * @return  true if supported, false otherwise.\n */\nbool check_tls_prf_working(void);\n\n/**\n * Checks if the usage limit for an AEAD cipher is reached\n *\n * This method abstracts the calculation to make the calling function easier\n * to read.\n */\nstatic inline bool\naead_usage_limit_reached(const uint64_t limit, const struct key_ctx *key_ctx, int64_t higest_pid)\n{\n    /* This is the  q + s <=  p^(1/2) * 2^(129/2) - 1 calculation where\n     * q is the number of protected messages (highest_pid)\n     * s Total plaintext length in all messages (in blocks) */\n    return (limit > 0 && key_ctx->plaintext_blocks + (uint64_t)higest_pid > limit);\n}\n\n#endif /* CRYPTO_H */\n"
  },
  {
    "path": "src/openvpn/crypto_backend.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Data Channel Cryptography SSL library-specific backend interface\n */\n\n#ifndef CRYPTO_BACKEND_H_\n#define CRYPTO_BACKEND_H_\n\n#ifdef ENABLE_CRYPTO_OPENSSL\n#include \"crypto_openssl.h\"\n#endif\n\n#ifdef ENABLE_CRYPTO_MBEDTLS\n#include <mbedtls/version.h>\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n#include \"crypto_mbedtls_legacy.h\"\n#else\n#include \"crypto_mbedtls.h\"\n#endif\n#endif\n\n#include \"basic.h\"\n#include \"buffer.h\"\n\n/* TLS uses a tag of 128 bits, let's do the same for OpenVPN */\n#define OPENVPN_AEAD_TAG_LENGTH 16\n\n/* Maximum cipher block size (bytes) */\n#define OPENVPN_MAX_CIPHER_BLOCK_SIZE 32\n\n/* Maximum HMAC digest size (bytes) */\n#define OPENVPN_MAX_HMAC_SIZE 64\n\n/** Types referencing specific message digest hashing algorithms */\ntypedef enum\n{\n    MD_SHA1,\n    MD_SHA256\n} hash_algo_type;\n\n/** Struct used in cipher name translation table */\ntypedef struct\n{\n    const char *openvpn_name; /**< Cipher name used by OpenVPN */\n    const char *lib_name;     /**< Cipher name used by crypto library */\n} cipher_name_pair;\n\n/** Cipher name translation table */\nextern const cipher_name_pair cipher_name_translation_table[];\nextern const size_t cipher_name_translation_table_count;\n\n/*\n * This routine should have additional OpenSSL crypto library initialisations\n * used by both crypto and ssl components of OpenVPN.\n */\nvoid crypto_init_lib(void);\n\nvoid crypto_uninit_lib(void);\n\nvoid crypto_clear_error(void);\n\n/*\n * Initialise the given named crypto engine.\n */\nvoid crypto_init_lib_engine(const char *engine_name);\n\n\n/**\n * Load the given (OpenSSL) providers\n * @param provider name of providers to load\n * @return reference to the loaded provider\n */\nprovider_t *crypto_load_provider(const char *provider);\n\n/**\n * Unloads the given (OpenSSL) provider\n * @param provname  name of the provider to unload\n * @param provider  pointer to the provider to unload\n */\nvoid crypto_unload_provider(const char *provname, provider_t *provider);\n\n#ifdef DMALLOC\n/*\n * OpenSSL memory debugging.  If dmalloc debugging is enabled, tell\n * OpenSSL to use our private malloc/realloc/free functions so that\n * we can dispatch them to dmalloc.\n */\nvoid crypto_init_dmalloc(void);\n\n#endif /* DMALLOC */\n\nvoid show_available_ciphers(void);\n\nvoid show_available_digests(void);\n\nvoid show_available_engines(void);\n\n/**\n * Encode binary data as PEM.\n *\n * @param name      The name to use in the PEM header/footer.\n * @param dst       Destination buffer for PEM-encoded data.  Must be a valid\n *                  pointer to an uninitialized buffer structure.  Iff this\n *                  function returns true, the buffer will contain memory\n *                  allocated through the supplied gc.\n * @param src       Source buffer.\n * @param gc        The garbage collector to use when allocating memory for dst.\n *\n * @return true iff PEM encode succeeded.\n */\nbool crypto_pem_encode(const char *name, struct buffer *dst, const struct buffer *src,\n                       struct gc_arena *gc);\n\n/**\n * Decode a PEM buffer to binary data.\n *\n * @param name      The name expected in the PEM header/footer.\n * @param dst       Destination buffer for decoded data.\n * @param src       Source buffer (PEM data).\n *\n * @return true iff PEM decode succeeded.\n */\nbool crypto_pem_decode(const char *name, struct buffer *dst, const struct buffer *src);\n\n/*\n *\n * Random number functions, used in cases where we want\n * reasonably strong cryptographic random number generation\n * without depleting our entropy pool.  Used for random\n * IV values and a number of other miscellaneous tasks.\n *\n */\n\n/**\n * Wrapper for secure random number generator. Retrieves len bytes of random\n * data, and places it in output.\n *\n * @param output        Output buffer\n * @param len           Length of the output buffer, in bytes\n *\n * @return              \\c 1 on success, \\c 0 on failure\n */\nint rand_bytes(uint8_t *output, int len);\n\n/*\n *\n * Generic cipher key type functions\n *\n */\n/*\n * Max size in bytes of any cipher key that might conceivably be used.\n *\n * This value is checked at compile time in crypto.c to make sure\n * it is always at least EVP_MAX_KEY_LENGTH.\n *\n * We define our own value, since this parameter\n * is used to control the size of static key files.\n * If the OpenSSL library increases EVP_MAX_KEY_LENGTH,\n * we don't want our key files to be suddenly rendered\n * unusable.\n */\n#define MAX_CIPHER_KEY_LENGTH 64\n\n/**\n * Returns if the cipher is valid, based on the given cipher name and provides a\n * reason if invalid.\n *\n * @param ciphername    Name of the cipher to check for validity (e.g.\n *                      \\c AES-128-CBC). Will be translated to the library name\n *                      from the openvpn config name if needed.\n * @param reason        Pointer where a static string indicating the reason\n *                      for rejecting the cipher should be stored. It is set to\n *                      NULL if the cipher is valid.\n *\n * @return              if the cipher is valid\n */\nbool cipher_valid_reason(const char *ciphername, const char **reason);\n\n/**\n * Returns if the cipher is valid, based on the given cipher name.\n *\n * @param ciphername    Name of the cipher to check for validity (e.g.\n *                      \\c AES-128-CBC). Will be translated to the library name\n *                      from the openvpn config name if needed.\n *\n * @return              if the cipher is valid\n */\nstatic inline bool\ncipher_valid(const char *ciphername)\n{\n    const char *reason;\n    return cipher_valid_reason(ciphername, &reason);\n}\n\n/**\n * Checks if the cipher is defined and is not the null (none) cipher\n *\n * @param ciphername    Name of the cipher to check if it is defined, may not\n *                      be NULL\n * @return              The cipher is defined and not the null (none) cipher\n */\nstatic inline bool\ncipher_defined(const char *ciphername)\n{\n    ASSERT(ciphername);\n    return strcmp(ciphername, \"none\") != 0;\n}\n\n/**\n * Retrieve a normalised string describing the cipher (e.g. \\c AES-128-CBC).\n * The returned name is normalised to the OpenVPN config name in case the\n * name differs from the name used by the crypto library.\n *\n * Returns [null-cipher] in case the ciphername is none. NULL if the cipher\n * is not valid.\n *\n * @param ciphername     Name of the cipher\n *\n * @return a statically allocated string describing the cipher.\n */\nconst char *cipher_kt_name(const char *ciphername);\n\n/**\n * Returns the size of keys used by the cipher, in bytes. If the cipher has a\n * variable key size, return the default key size.\n *\n * @param ciphername    Cipher name to lookup\n *\n * @return              (Default) size of keys used by the cipher, in bytes.\n */\nunsigned int cipher_kt_key_size(const char *ciphername);\n\n/**\n * Returns the size of the IV used by the cipher, in bytes, or 0 if no IV is\n * used.\n *\n * @param ciphername    cipher name to lookup\n *\n * @return              Size of the IV, in bytes, or 0 if the cipher does not\n *                      use an IV.\n */\nunsigned int cipher_kt_iv_size(const char *ciphername);\n\n/**\n * Returns the block size of the cipher, in bytes.\n *\n * @param ciphername    cipher name\n *\n * @return              Block size, in bytes.\n */\nunsigned int cipher_kt_block_size(const char *ciphername);\n\n/**\n * Returns the MAC tag size of the cipher, in bytes.\n *\n * @param ciphername    Name of the cipher\n *\n * @return              Tag size in bytes, or 0 if the tag size could not be\n *                      determined.\n */\nunsigned int cipher_kt_tag_size(const char *ciphername);\n\n/**\n * Returns true if we consider this cipher to be insecure.\n */\nbool cipher_kt_insecure(const char *ciphername);\n\n\n/**\n * Check if the supplied cipher is a supported CBC mode cipher.\n *\n * @param ciphername    cipher name\n *\n * @return              true iff the cipher is a CBC mode cipher.\n */\nbool cipher_kt_mode_cbc(const char *ciphername);\n\n/**\n * Check if the supplied cipher is a supported OFB or CFB mode cipher.\n *\n * @param ciphername    cipher name\n *\n * @return              true iff the cipher is a OFB or CFB mode cipher.\n */\nbool cipher_kt_mode_ofb_cfb(const char *ciphername);\n\n/**\n * Check if the supplied cipher is a supported AEAD mode cipher.\n *\n * @param ciphername    name of the cipher\n *\n * @return              true iff the cipher is a AEAD mode cipher.\n */\nbool cipher_kt_mode_aead(const char *ciphername);\n\n\n/**\n *\n * Generic cipher functions\n *\n */\n\n/**\n * Allocate a new cipher context\n *\n * @return              a new cipher context\n */\ncipher_ctx_t *cipher_ctx_new(void);\n\n/**\n * Cleanup and free a cipher context\n *\n * @param ctx           Cipher context.\n */\nvoid cipher_ctx_free(cipher_ctx_t *ctx);\n\n/**\n * Initialise a cipher context, based on the given key and key type.\n *\n * @param ctx           Cipher context. May not be NULL\n * @param key           Buffer containing the key to use\n * @param ciphername    Ciphername of the cipher to use\n * @param enc           Whether to encrypt or decrypt (either\n *                      \\c OPENVPN_OP_ENCRYPT or \\c OPENVPN_OP_DECRYPT).\n */\nvoid cipher_ctx_init(cipher_ctx_t *ctx, const uint8_t *key, const char *ciphername,\n                     crypto_operation_t enc);\n\n/**\n * Returns the size of the IV used by the cipher, in bytes, or 0 if no IV is\n * used.\n *\n * @param ctx           The cipher's context\n *\n * @return              Size of the IV, in bytes, or \\c 0 if the cipher does not\n *                      use an IV.\n */\nunsigned int cipher_ctx_iv_length(const cipher_ctx_t *ctx);\n\n/**\n * Gets the computed message authenticated code (MAC) tag for this cipher.\n *\n * @param ctx           The cipher's context\n * @param tag           The buffer to write computed tag in.\n * @param tag_len       The tag buffer size, in bytes.\n */\nint cipher_ctx_get_tag(cipher_ctx_t *ctx, uint8_t *tag, int tag_len);\n\n/**\n * Returns the block size of the cipher, in bytes.\n *\n * @param ctx           The cipher's context\n *\n * @return              Block size, in bytes, or 0 if ctx was NULL.\n */\nunsigned int cipher_ctx_block_size(const cipher_ctx_t *ctx);\n\n/**\n * Returns the mode that the cipher runs in.\n *\n * @param ctx           Cipher's context. May not be NULL.\n *\n * @return              Cipher mode, either \\c OPENVPN_MODE_CBC, \\c\n *                      OPENVPN_MODE_OFB or \\c OPENVPN_MODE_CFB\n */\nint cipher_ctx_mode(const cipher_ctx_t *ctx);\n\n/**\n * Check if the supplied cipher is a supported CBC mode cipher.\n *\n * @param ctx           Cipher's context. May not be NULL.\n *\n * @return              true iff the cipher is a CBC mode cipher.\n */\nbool cipher_ctx_mode_cbc(const cipher_ctx_t *ctx);\n\n/**\n * Check if the supplied cipher is a supported OFB or CFB mode cipher.\n *\n * @param ctx           Cipher's context. May not be NULL.\n *\n * @return              true iff the cipher is a OFB or CFB mode cipher.\n */\nbool cipher_ctx_mode_ofb_cfb(const cipher_ctx_t *ctx);\n\n/**\n * Check if the supplied cipher is a supported AEAD mode cipher.\n *\n * @param ctx           Cipher's context. May not be NULL.\n *\n * @return              true iff the cipher is a AEAD mode cipher.\n */\nbool cipher_ctx_mode_aead(const cipher_ctx_t *ctx);\n\n/**\n * Resets the given cipher context, setting the IV to the specified value.\n * Preserves the associated key information.\n *\n * @param ctx           Cipher's context. May not be NULL.\n * @param iv_buf        The IV to use.\n *\n * @return              \\c 0 on failure, \\c 1 on success.\n */\nint cipher_ctx_reset(cipher_ctx_t *ctx, const uint8_t *iv_buf);\n\n/**\n * Updates the given cipher context, providing additional data (AD) for\n * authenticated encryption with additional data (AEAD) cipher modes.\n *\n * @param ctx           Cipher's context. May not be NULL.\n * @param src           Source buffer\n * @param src_len       Length of the source buffer, in bytes\n *\n * @return              \\c 0 on failure, \\c 1 on success.\n */\nint cipher_ctx_update_ad(cipher_ctx_t *ctx, const uint8_t *src, int src_len);\n\n/**\n * Updates the given cipher context, encrypting data in the source buffer, and\n * placing any complete blocks in the destination buffer.\n *\n * Note that if a complete block cannot be written, data is cached in the\n * context, and emitted at a later call to \\c cipher_ctx_update, or by a call\n * to \\c cipher_ctx_final(). This implies that dst should have enough room for\n * src_len + \\c cipher_ctx_block_size().\n *\n * @param ctx           Cipher's context. May not be NULL.\n * @param dst           Destination buffer\n * @param dst_len       Length of the destination buffer, in bytes\n * @param src           Source buffer\n * @param src_len       Length of the source buffer, in bytes\n *\n * @return              \\c 0 on failure, \\c 1 on success.\n */\nint cipher_ctx_update(cipher_ctx_t *ctx, uint8_t *dst, int *dst_len, uint8_t *src, int src_len);\n\n/**\n * Pads the final cipher block using PKCS padding, and output to the destination\n * buffer.\n *\n * @param ctx           Cipher's context. May not be NULL.\n * @param dst           Destination buffer\n * @param dst_len       Length of the destination buffer, in bytes\n *\n * @return              \\c 0 on failure, \\c 1 on success.\n */\nint cipher_ctx_final(cipher_ctx_t *ctx, uint8_t *dst, int *dst_len);\n\n/**\n * Like \\c cipher_ctx_final, but check the computed authentication tag against\n * the supplied (expected) tag. This function reports failure when the tags\n * don't match.\n *\n * @param ctx           Cipher's context. May not be NULL.\n * @param dst           Destination buffer.\n * @param dst_len       Length of the destination buffer, in bytes.\n * @param tag           The expected authentication tag.\n * @param tag_len       The length of tag, in bytes.\n *\n * @return              \\c 0 on failure, \\c 1 on success.\n */\nint cipher_ctx_final_check_tag(cipher_ctx_t *ctx, uint8_t *dst, int *dst_len, uint8_t *tag,\n                               size_t tag_len);\n\n\n/*\n *\n * Generic message digest information functions\n *\n */\n\n/*\n * Max size in bytes of any HMAC key that might conceivably be used.\n *\n * This value is checked at compile time in crypto.c to make sure\n * it is always at least EVP_MAX_MD_SIZE.  We define our own value\n * for the same reason as above.\n */\n#define MAX_HMAC_KEY_LENGTH 64\n\n/**\n * Checks if the cipher is defined and is not the null (none) cipher\n *\n * @param mdname    Name of the digest\n * @return\n */\nstatic inline bool\nmd_defined(const char *mdname)\n{\n    return strcmp(mdname, \"none\") != 0;\n}\n\n\n/**\n * Return if a message digest parameters is valid given the name of the digest.\n *\n * @param digest        Name of the digest to verify, e.g. \\c MD5).\n *\n * @return              Whether a digest of the given name is available\n */\nbool md_valid(const char *digest);\n\n/**\n * Retrieve a string describing the digest digest (e.g. \\c SHA1).\n *\n * @param mdname        Message digest name\n *\n * @return              Statically allocated string describing the message\n *                      digest.\n */\nconst char *md_kt_name(const char *mdname);\n\n/**\n * Returns the size of the message digest, in bytes.\n *\n * @param mdname        Message digest name\n *\n * @return              Message digest size, in bytes, or 0 if ctx was NULL.\n */\nunsigned char md_kt_size(const char *mdname);\n\n\n/*\n *\n * Generic message digest functions\n *\n */\n\n/**\n * Calculates the message digest for the given buffer.\n *\n * @param mdname        message digest name\n * @param src           Buffer to digest. May not be NULL.\n * @param src_len       The length of the incoming buffer.\n * @param dst           Buffer to write the message digest to. May not be NULL.\n *\n * @return              true on success, false on failure\n */\nbool md_full(const char *mdname, const uint8_t *src, size_t src_len, uint8_t *dst);\n\n/*\n * Allocate a new message digest context\n *\n * @return              a new zeroed MD context\n */\nmd_ctx_t *md_ctx_new(void);\n\n/*\n * Free an existing, non-null message digest context\n *\n * @param ctx           Message digest context\n */\nvoid md_ctx_free(md_ctx_t *ctx);\n\n/**\n * Initialises the given message digest context.\n *\n * @param ctx           Message digest context\n * @param mdname        Message digest name\n */\nvoid md_ctx_init(md_ctx_t *ctx, const char *mdname);\n\n/*\n * Free the given message digest context.\n *\n * @param ctx           Message digest context\n */\nvoid md_ctx_cleanup(md_ctx_t *ctx);\n\n/*\n * Returns the size of the message digest output by the given context\n *\n * @param ctx           Message digest context.\n *\n * @return              Size of the message digest, or \\0 if ctx is NULL.\n */\nint md_ctx_size(const md_ctx_t *ctx);\n\n/*\n * Process the given data for use in the message digest.\n *\n * @param ctx           Message digest context. May not be NULL.\n * @param src           Buffer to digest. May not be NULL.\n * @param src_len       The length of the incoming buffer.\n */\nvoid md_ctx_update(md_ctx_t *ctx, const uint8_t *src, size_t src_len);\n\n/*\n * Output the message digest to the given buffer.\n *\n * @param ctx           Message digest context. May not be NULL.\n * @param dst           Buffer to write the message digest to. May not be NULL.\n */\nvoid md_ctx_final(md_ctx_t *ctx, uint8_t *dst);\n\n\n/*\n *\n * Generic HMAC functions\n *\n */\n\n/*\n * Create a new HMAC context\n *\n * @return              A new HMAC context\n */\nhmac_ctx_t *hmac_ctx_new(void);\n\n/*\n * Free an existing HMAC context\n *\n * @param  ctx           HMAC context to free\n */\nvoid hmac_ctx_free(hmac_ctx_t *ctx);\n\n/*\n * Initialises the given HMAC context, using the given digest\n * and key.\n *\n * @param ctx           HMAC context to initialise\n * @param key           The key to use for the HMAC\n * @param mdname        message digest name\n *\n */\nvoid hmac_ctx_init(hmac_ctx_t *ctx, const uint8_t *key, const char *mdname);\n\n\n/*\n * Free the given HMAC context.\n *\n * @param ctx           HMAC context\n */\nvoid hmac_ctx_cleanup(hmac_ctx_t *ctx);\n\n/*\n * Returns the size of the HMAC output by the given HMAC Context\n *\n * @param ctx           HMAC context.\n *\n * @return              Size of the HMAC, or \\0 if ctx is NULL.\n */\nint hmac_ctx_size(hmac_ctx_t *ctx);\n\n/*\n * Resets the given HMAC context, preserving the associated key information\n *\n * @param ctx           HMAC context. May not be NULL.\n */\nvoid hmac_ctx_reset(hmac_ctx_t *ctx);\n\n/*\n * Process the given data for use in the HMAC.\n *\n * @param ctx           HMAC context. May not be NULL.\n * @param src           The buffer to HMAC. May not be NULL.\n * @param src_len       The length of the incoming buffer.\n */\nvoid hmac_ctx_update(hmac_ctx_t *ctx, const uint8_t *src, int src_len);\n\n/*\n * Output the HMAC to the given buffer.\n *\n * @param ctx           HMAC context. May not be NULL.\n * @param dst           buffer to write the HMAC to. May not be NULL.\n */\nvoid hmac_ctx_final(hmac_ctx_t *ctx, uint8_t *dst);\n\n/**\n * Translate an OpenVPN cipher name to a crypto library cipher name.\n *\n * @param cipher_name   An OpenVPN cipher name\n *\n * @return              The corresponding crypto library cipher name, or NULL\n *                      if no matching cipher name was found.\n */\nconst char *translate_cipher_name_from_openvpn(const char *cipher_name);\n\n/**\n * Translate a crypto library cipher name to an OpenVPN cipher name.\n *\n * @param cipher_name   A crypto library cipher name\n *\n * @return              The corresponding OpenVPN cipher name, or NULL if no\n *                      matching cipher name was found.\n */\nconst char *translate_cipher_name_to_openvpn(const char *cipher_name);\n\n\n/**\n * Calculates the TLS 1.0-1.1 PRF function. For the exact specification of the\n * function definition see the TLS RFCs like RFC 4346.\n *\n * @param seed          seed to use\n * @param seed_len      length of the seed\n * @param secret        secret to use\n * @param secret_len    length of the secret\n * @param output        output destination\n * @param output_len    length of output/number of bytes to generate\n *\n * @return              true if successful, false on any error\n */\nbool ssl_tls1_PRF(const uint8_t *seed, size_t seed_len, const uint8_t *secret, size_t secret_len,\n                  uint8_t *output, size_t output_len);\n\n#endif /* CRYPTO_BACKEND_H_ */\n"
  },
  {
    "path": "src/openvpn/crypto_epoch.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2024-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2024-2026 Arne Schwabe <arne@rfc2549.org>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <inttypes.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include \"crypto_backend.h\"\n#include \"packet_id.h\"\n#include \"crypto.h\"\n#include \"crypto_epoch.h\"\n#include \"buffer.h\"\n#include \"integer.h\"\n\nvoid\novpn_hkdf_expand(const uint8_t *secret, const uint8_t *info, size_t info_len,\n                 uint8_t *out, size_t out_len)\n{\n    hmac_ctx_t *hmac_ctx = hmac_ctx_new();\n    hmac_ctx_init(hmac_ctx, secret, \"SHA256\");\n\n    ASSERT(info_len <= INT_MAX);\n    const unsigned int digest_size = SHA256_DIGEST_LENGTH;\n\n    /* T(0) = empty string */\n    uint8_t t_prev[SHA256_DIGEST_LENGTH];\n    unsigned int t_prev_len = 0;\n\n    for (uint8_t block = 1; (block - 1) * digest_size < out_len; block++)\n    {\n        hmac_ctx_reset(hmac_ctx);\n\n        /* calculate T(block) */\n        hmac_ctx_update(hmac_ctx, t_prev, t_prev_len);\n        hmac_ctx_update(hmac_ctx, info, (int)info_len);\n        hmac_ctx_update(hmac_ctx, &block, 1);\n        hmac_ctx_final(hmac_ctx, t_prev);\n        t_prev_len = digest_size;\n\n        /* Copy a full hmac output or remaining bytes */\n        size_t out_offset = (block - 1) * digest_size;\n        size_t copylen = min_size(digest_size, out_len - out_offset);\n\n        memcpy(out + out_offset, t_prev, copylen);\n    }\n    hmac_ctx_cleanup(hmac_ctx);\n    hmac_ctx_free(hmac_ctx);\n}\n\nbool\novpn_expand_label(const uint8_t *secret, size_t secret_len, const uint8_t *label, size_t label_len,\n                  const uint8_t *context, size_t context_len, uint8_t *out, size_t out_len)\n{\n    if (secret_len != 32 || label_len > 250 || context_len > 255 || label_len < 1)\n    {\n        /* Our current implementation is not a general purpose one\n         * and assumes that the secret size matches the size of the\n         * hash (SHA256) key. Also label length and context length\n         * need need to be in range */\n        return false;\n    }\n    ASSERT(out_len <= UINT16_MAX);\n\n    struct gc_arena gc = gc_new();\n    /* 2 byte for the outlen encoded as uint16, 5 bytes for \"ovpn \",\n     * 1 byte for context len byte and 1 byte for label len byte */\n    const uint8_t *label_prefix = (const uint8_t *)(\"ovpn \");\n    uint8_t prefix_len = 5;\n\n    size_t hkdf_label_len = 2 + prefix_len + 1 + label_len + 1 + context_len;\n    struct buffer hkdf_label = alloc_buf_gc(hkdf_label_len, &gc);\n\n    buf_write_u16(&hkdf_label, (uint16_t)out_len);\n    buf_write_u8(&hkdf_label, prefix_len + (uint8_t)label_len);\n    buf_write(&hkdf_label, label_prefix, prefix_len);\n    buf_write(&hkdf_label, label, label_len);\n\n    buf_write_u8(&hkdf_label, (uint8_t)context_len);\n    if (context_len > 0)\n    {\n        buf_write(&hkdf_label, context, context_len);\n    }\n\n    ASSERT(buf_len(&hkdf_label) == (int)hkdf_label_len);\n\n    ovpn_hkdf_expand(secret, buf_bptr(&hkdf_label), hkdf_label_len, out, out_len);\n\n    gc_free(&gc);\n    return true;\n}\n\n/**\n * Iterates the epoch key to make it E_n+1, ie increase the epoch by one\n * and derive the new key material accordingly\n * @param epoch_key epoch key to iterate\n */\nstatic void\nepoch_key_iterate(struct epoch_key *epoch_key)\n{\n    struct epoch_key new_epoch_key = { 0 };\n    new_epoch_key.epoch = epoch_key->epoch + 1;\n    const uint8_t epoch_update_label[] = \"datakey upd\";\n    /* length of the array without extra \\0 byte from the string */\n    const size_t epoch_update_label_len = sizeof(epoch_update_label) - 1;\n\n    /* E_N+1 = OVPN-Expand-Label(E_N, \"datakey upd\", \"\", 32) */\n    ovpn_expand_label(epoch_key->epoch_key, sizeof(epoch_key->epoch_key), epoch_update_label,\n                      epoch_update_label_len, NULL, 0, new_epoch_key.epoch_key,\n                      sizeof(new_epoch_key.epoch_key));\n    *epoch_key = new_epoch_key;\n}\n\nvoid\nepoch_data_key_derive(struct key_parameters *key, const struct epoch_key *epoch_key,\n                      const struct key_type *kt)\n{\n    key->hmac_size = cipher_kt_iv_size(kt->cipher);\n    key->cipher_size = cipher_kt_key_size(kt->cipher);\n\n    /* Generate data key from epoch key:\n     * K_i = OVPN-Expand-Label(E_i, \"data_key\", \"\", key_size)\n     * implicit_iv = OVPN-Expand-Label(E_i, \"data_iv\", \"\", implicit_iv_len)\n     */\n\n    const uint8_t epoch_data_key_label[] = \"data_key\";\n    /* length of the array without extra \\0 byte from the string */\n    const size_t epoch_data_key_label_len = sizeof(epoch_data_key_label) - 1;\n\n    ovpn_expand_label(epoch_key->epoch_key, sizeof(epoch_key->epoch_key), epoch_data_key_label,\n                      epoch_data_key_label_len, NULL, 0, (uint8_t *)(&key->cipher),\n                      key->cipher_size);\n\n    const uint8_t epoch_data_iv_label[] = \"data_iv\";\n    /* length of the array without extra \\0 byte from the string */\n    const size_t epoch_data_iv_label_len = sizeof(epoch_data_iv_label) - 1;\n\n    ovpn_expand_label(epoch_key->epoch_key, sizeof(epoch_key->epoch_key), epoch_data_iv_label,\n                      epoch_data_iv_label_len, NULL, 0, (uint8_t *)(&key->hmac), key->hmac_size);\n    key->epoch = epoch_key->epoch;\n}\n\nstatic void\nepoch_init_send_key_ctx(struct crypto_options *co)\n{\n    /* Ensure that we are NEVER regenerating the same key that has already\n     * been generated. Since we also reset the packet ID counter this would be\n     * catastrophic as we would do IV reuse which breaks ciphers like AES-GCM */\n    ASSERT(co->key_ctx_bi.encrypt.epoch != co->epoch_key_send.epoch);\n    char name[32] = { 0 };\n    snprintf(name, sizeof(name), \"Epoch Data key %\" PRIu16, co->epoch_key_send.epoch);\n\n    struct key_parameters send_key = { 0 };\n\n    epoch_data_key_derive(&send_key, &co->epoch_key_send, &co->epoch_key_type);\n\n    init_key_bi_ctx_send(&co->key_ctx_bi.encrypt, &send_key, &co->epoch_key_type, name);\n    reset_packet_id_send(&co->packet_id.send);\n    CLEAR(send_key);\n}\n\n\nstatic void\nepoch_init_recv_key(struct key_ctx *ctx, struct crypto_options *co)\n{\n    struct key_parameters recv_key = { 0 };\n    epoch_data_key_derive(&recv_key, &co->epoch_key_recv, &co->epoch_key_type);\n\n    char name[32];\n\n    snprintf(name, sizeof(name), \"Epoch Data key %\" PRIu16, co->epoch_key_recv.epoch);\n\n    init_key_bi_ctx_recv(ctx, &recv_key, &co->epoch_key_type, name);\n    CLEAR(recv_key);\n}\n\nvoid\nepoch_generate_future_receive_keys(struct crypto_options *co)\n{\n    /* We want the future receive keys to start just after the epoch of\n     * the currently used decryption key. */\n    ASSERT(co->key_ctx_bi.initialized);\n    uint16_t current_decrypt_epoch = co->key_ctx_bi.decrypt.epoch;\n\n    /* Either we have not generated any future keys yet (first initialisation)\n     * or the last index is the same as our current epoch key\n     * (last generated receive epoch key should match the epoch key).\n     *\n     * The last generated key might have been moved to the decrypt key already.\n     */\n    struct key_ctx *highest_future_key =\n        &co->epoch_data_keys_future[co->epoch_data_keys_future_count - 1];\n\n    ASSERT(co->epoch_key_recv.epoch == 1 || highest_future_key->epoch == co->epoch_key_recv.epoch\n           || current_decrypt_epoch == co->epoch_key_recv.epoch);\n\n    /* free the keys that are not used anymore */\n    for (uint16_t i = 0; i < co->epoch_data_keys_future_count; i++)\n    {\n        /* Keys in future keys are always epoch > 1 if initialised */\n        if (co->epoch_data_keys_future[i].epoch > 0\n            && co->epoch_data_keys_future[i].epoch < current_decrypt_epoch)\n        {\n            /* Key is old, free it */\n            free_key_ctx(&co->epoch_data_keys_future[i]);\n        }\n    }\n\n    /* The current epoch_key_recv is the higest currently generated key */\n    uint16_t current_highest_key = co->epoch_key_recv.epoch;\n\n    /* The current highest generated epoch key should be either the last\n     * key in the future key array or been already moved the current decrypt\n     * key (and then all future keys need to be generated)*/\n    ASSERT(current_highest_key == highest_future_key->epoch\n           || current_highest_key == current_decrypt_epoch);\n\n    /* Calculate the number of keys that need to be generated,\n     * if no keys have been generated assume only the first key is defined */\n    uint16_t desired_highest_key = current_decrypt_epoch + co->epoch_data_keys_future_count;\n    uint16_t num_keys_generate = desired_highest_key - current_highest_key;\n\n    /* Move the old keys out of the way so the order of keys stays strictly\n     * monotonic and consecutive. */\n    /* first check that the destination we are going to overwrite is freed */\n    for (uint16_t i = 0; i < num_keys_generate; i++)\n    {\n        ASSERT(co->epoch_data_keys_future[i].epoch == 0);\n    }\n    memmove(co->epoch_data_keys_future, co->epoch_data_keys_future + num_keys_generate,\n            (co->epoch_data_keys_future_count - num_keys_generate) * sizeof(struct key_ctx));\n\n    /* Clear and regenerate the array elements at the end */\n    for (uint16_t i = co->epoch_data_keys_future_count - num_keys_generate;\n         i < co->epoch_data_keys_future_count; i++)\n    {\n        CLEAR(co->epoch_data_keys_future[i]);\n        epoch_key_iterate(&co->epoch_key_recv);\n\n        epoch_init_recv_key(&co->epoch_data_keys_future[i], co);\n    }\n\n    /* Assert that all keys are initialised */\n    for (uint16_t i = 0; i < co->epoch_data_keys_future_count; i++)\n    {\n        ASSERT(co->epoch_data_keys_future[i].epoch > 0);\n    }\n}\n\nvoid\nepoch_iterate_send_key(struct crypto_options *co)\n{\n    ASSERT(co->epoch_key_send.epoch < UINT16_MAX);\n    epoch_key_iterate(&co->epoch_key_send);\n    free_key_ctx(&co->key_ctx_bi.encrypt);\n    epoch_init_send_key_ctx(co);\n}\n\nvoid\nepoch_replace_update_recv_key(struct crypto_options *co, uint16_t new_epoch)\n{\n    /* Find the key of the new epoch in future keys */\n    uint16_t fki;\n    for (fki = 0; fki < co->epoch_data_keys_future_count; fki++)\n    {\n        if (co->epoch_data_keys_future[fki].epoch == new_epoch)\n        {\n            break;\n        }\n    }\n    /* we should only ever be called when we successfully decrypted/authenticated\n     * a packet from a peer, ie the epoch recv key *MUST* be in that\n     * array */\n    ASSERT(fki < co->epoch_data_keys_future_count);\n    ASSERT(co->epoch_data_keys_future[fki].epoch == new_epoch);\n\n    struct key_ctx *new_ctx = &co->epoch_data_keys_future[fki];\n\n    /* Check if the new recv key epoch is higher than the send key epoch. If\n     * yes we will replace the send key as well */\n    if (co->key_ctx_bi.encrypt.epoch < new_epoch)\n    {\n        free_key_ctx(&co->key_ctx_bi.encrypt);\n\n        /* Update the epoch_key for send to match the current key being used.\n         * This is a bit of extra work but since we are a maximum of 16\n         * keys behind, a maximum 16 HMAC invocations are a small price to\n         * pay for not keeping all the old epoch keys around in future_keys\n         * array */\n        while (co->epoch_key_send.epoch < new_epoch)\n        {\n            epoch_key_iterate(&co->epoch_key_send);\n        }\n        epoch_init_send_key_ctx(co);\n    }\n\n    /* Replace receive key */\n    free_key_ctx(&co->epoch_retiring_data_receive_key);\n    co->epoch_retiring_data_receive_key = co->key_ctx_bi.decrypt;\n    packet_id_move_recv(&co->epoch_retiring_key_pid_recv, &co->packet_id.rec);\n\n    co->key_ctx_bi.decrypt = *new_ctx;\n\n    /* Zero the old location instead to of free_key_ctx since we moved the keys\n     * and do not want to free the pointers in the old place */\n    memset(new_ctx, 0, sizeof(struct key_ctx));\n\n    /* Generate new future keys */\n    epoch_generate_future_receive_keys(co);\n}\n\nvoid\nfree_epoch_key_ctx(struct crypto_options *co)\n{\n    for (uint16_t i = 0; i < co->epoch_data_keys_future_count; i++)\n    {\n        free_key_ctx(&co->epoch_data_keys_future[i]);\n    }\n\n    free(co->epoch_data_keys_future);\n    free_key_ctx(&co->epoch_retiring_data_receive_key);\n    free(co->epoch_retiring_key_pid_recv.seq_list);\n    CLEAR(co->epoch_key_recv);\n    CLEAR(co->epoch_key_send);\n}\n\nvoid\nepoch_init_key_ctx(struct crypto_options *co, const struct key_type *key_type,\n                   const struct epoch_key *e1_send, const struct epoch_key *e1_recv,\n                   uint16_t future_key_count)\n{\n    ASSERT(e1_send->epoch == 1 && e1_recv->epoch == 1);\n    co->epoch_key_recv = *e1_recv;\n    co->epoch_key_send = *e1_send;\n\n    co->epoch_key_type = *key_type;\n    co->aead_usage_limit = cipher_get_aead_limits(key_type->cipher);\n\n    epoch_init_send_key_ctx(co);\n    epoch_init_recv_key(&co->key_ctx_bi.decrypt, co);\n    co->key_ctx_bi.initialized = true;\n\n    co->epoch_data_keys_future_count = future_key_count;\n    ALLOC_ARRAY_CLEAR(co->epoch_data_keys_future, struct key_ctx, co->epoch_data_keys_future_count);\n    epoch_generate_future_receive_keys(co);\n}\n\nstruct key_ctx *\nepoch_lookup_decrypt_key(struct crypto_options *opt, uint16_t epoch)\n{\n    /* Current decrypt key is the most likely one */\n    if (opt->key_ctx_bi.decrypt.epoch == epoch)\n    {\n        return &opt->key_ctx_bi.decrypt;\n    }\n    else if (opt->epoch_retiring_data_receive_key.epoch\n             && opt->epoch_retiring_data_receive_key.epoch == epoch)\n    {\n        return &opt->epoch_retiring_data_receive_key;\n    }\n    else if (epoch > opt->key_ctx_bi.decrypt.epoch\n             && epoch <= opt->key_ctx_bi.decrypt.epoch + opt->epoch_data_keys_future_count)\n    {\n        /* Key in the range of future keys */\n        int index = epoch - (opt->key_ctx_bi.decrypt.epoch + 1);\n\n        /* If we have reached the edge of the valid keys we do not return\n         * the key anymore since regenerating the new keys would move us\n         * over the window of valid keys and would need all kind of\n         * special casing, so we stop returning the key in this case */\n        if (epoch > (UINT16_MAX - opt->epoch_data_keys_future_count - 1))\n        {\n            return NULL;\n        }\n        else\n        {\n            return &opt->epoch_data_keys_future[index];\n        }\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\n\nvoid\nepoch_check_send_iterate(struct crypto_options *opt)\n{\n    if (opt->epoch_key_send.epoch == UINT16_MAX)\n    {\n        /* limit of epoch keys reached, cannot move to a newer key anymore */\n        return;\n    }\n    if (opt->aead_usage_limit)\n    {\n        if (aead_usage_limit_reached(opt->aead_usage_limit, &opt->key_ctx_bi.encrypt,\n                                     opt->packet_id.send.id))\n        {\n            /* Send key limit reached */\n            epoch_iterate_send_key(opt);\n        }\n        /* draft 8 of the aead usage limit still had but draft 9 complete\n         * dropped this statement:\n         *\n         *    In particular, in two-party communication, one participant cannot\n         *    regard apparent overuse of a key by other participants as\n         *    being in error, when it could be that the other participant has\n         *    better information about bounds.\n         *\n         * OpenVPN 2.x implements a compromise of not regarding this as an\n         * error but still accepting packets of the usage limit but tries to\n         * push the peer to a new epoch key by increasing our own key epoch\n         *\n         * Also try to push the sender to use a new key if we are the\n         * decryption fail warn limit.\n         * */\n        else if (opt->key_ctx_bi.encrypt.epoch == opt->key_ctx_bi.decrypt.epoch\n                 && (aead_usage_limit_reached(opt->aead_usage_limit, &opt->key_ctx_bi.decrypt,\n                                              opt->packet_id.rec.id)\n                     || cipher_decrypt_verify_fail_warn(&opt->key_ctx_bi.decrypt)))\n        {\n            /* Receive key limit reached. Increase our own send key to signal\n             * that we want to use a new epoch. Peer should then also move its\n             * key but is not required to do this */\n            epoch_iterate_send_key(opt);\n        }\n    }\n\n    if (opt->packet_id.send.id == PACKET_ID_EPOCH_MAX)\n    {\n        epoch_iterate_send_key(opt);\n    }\n}\n"
  },
  {
    "path": "src/openvpn/crypto_epoch.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2024-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2024-2026 Arne Schwabe <arne@rfc2549.org>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef CRYPTO_EPOCH_H\n#define CRYPTO_EPOCH_H\n\n/**\n * Implementation of the RFC5869 HKDF-Expand function with the following\n * restrictions\n *\n *  - secret is assumed to be always 32 bytes\n *  - HASH is always SHA256\n *\n *  @param secret   the input keying material (HMAC key)\n *  @param info     context and application specific information\n *  @param info_len length of the info string\n *  @param out      output keying material\n *  @param out_len  length of output keying material\n */\nvoid ovpn_hkdf_expand(const uint8_t *secret, const uint8_t *info, size_t info_len,\n                      uint8_t *out, size_t out_len);\n\n/**\n * Variant of the RFC 8446 TLS 1.3  HKDF-Expand-Label function with the\n * following differences/restrictions:\n *  - secret must 32 bytes in length\n *  - label prefix is \"ovpn \" instead of \"tls13 \"\n *  - HASH is always SHA256\n *\n * @param secret        Input secret\n * @param secret_len    length of the input secret\n * @param label         Label for the exported key material\n * @param label_len     length of the label\n * @param context       optional context\n * @param context_len   length of the context\n * @param out      output keying material\n * @param out_len  length of output keying material\n * @return\n */\nbool ovpn_expand_label(const uint8_t *secret, size_t secret_len, const uint8_t *label,\n                       size_t label_len, const uint8_t *context, size_t context_len,\n                       uint8_t *out, size_t out_len);\n\n/**\n * Generate a data channel key pair from the epoch key\n * @param key           Destination for the generated data key\n * @param epoch_key     Epoch key to be used\n * @param kt            Cipher information to generate the data channel key for\n */\nvoid epoch_data_key_derive(struct key_parameters *key, const struct epoch_key *epoch_key,\n                           const struct key_type *kt);\n\n/**\n * Generates and fills the epoch_data_keys_future with next valid\n * future keys in crypto_options using the epoch of the key in\n * crypto_options.key_ctx_bi.decrypt as starting point\n *\n * This assume that the normal key_ctx_bi and epoch keys have been already\n * setup.\n *\n * This method is also called if crypto_options.key_ctx_bi.decrypt is changed.\n * The method will then change the future keys in epoch_data_keys_future to\n * free the ones that are older than the crypto_options.key_ctx_bi.decrypt and\n * generate the keys from the newer epoch.\n */\nvoid epoch_generate_future_receive_keys(struct crypto_options *co);\n\n\n/** This is called when the peer uses a new send key that is not the default\n * key. This function ensures the following:\n * - recv key matches the epoch index provided\n * - send key epoch is equal or higher than recv_key epoch\n *\n * @param co        crypto_options to update\n * @param new_epoch the new epoch to use for the receive key\n */\nvoid epoch_replace_update_recv_key(struct crypto_options *co, uint16_t new_epoch);\n\n/**\n * Updates the send key and send_epoch_key in cryptio_options->key_ctx_bi to\n * use the next epoch */\nvoid epoch_iterate_send_key(struct crypto_options *co);\n\n/**\n * Frees the extra data structures used by epoch keys in \\c crypto_options\n */\nvoid free_epoch_key_ctx(struct crypto_options *co);\n\n/**\n * Initialises data channel keys and internal structures for epoch data keys\n * using the provided E0 epoch key\n *\n * @param co                The crypto option struct to initialise the epoch\n *                          related fields\n * @param key_type          The parameter of what encryption cipher to use when\n *                          initialising the epoch related fields\n * @param e1_send           The E1 send epoch key derived by TLS-EKM\n * @param e1_recv           The E1 receive epoch key derived by TLS-EKM\n * @param future_key_count  the number of future epoch keys that should be\n *                          considered valid when receiving data from the peer\n */\nvoid epoch_init_key_ctx(struct crypto_options *co, const struct key_type *key_type,\n                        const struct epoch_key *e1_send, const struct epoch_key *e1_recv,\n                        uint16_t future_key_count);\n\n/**\n * Using an epoch, this function will try to retrieve a decryption\n * key context that matches that epoch from the \\c opt argument\n * @param opt       crypto_options to use to find the decrypt key\n * @param epoch     epoch of the key to lookup\n * @return          the key context with\n */\nstruct key_ctx *epoch_lookup_decrypt_key(struct crypto_options *opt, uint16_t epoch);\n\n/**\n * Checks if we need to iterate the send epoch key. This needs to be in one\n * of the following condition\n *  - max epoch counter reached\n *  - send key aead usage limit reached (for AES-GCM and similar ciphers)\n *  - recv key usage limit reached\n */\nvoid epoch_check_send_iterate(struct crypto_options *opt);\n\n\n#endif /* ifndef CRYPTO_EPOCH_H */\n"
  },
  {
    "path": "src/openvpn/crypto_mbedtls.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Data Channel Cryptography backend interface using the TF-PSA-Crypto library\n * part of Mbed TLS 4.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if defined(ENABLE_CRYPTO_MBEDTLS)\n#include <mbedtls/version.h>\n\n#if MBEDTLS_VERSION_NUMBER >= 0x04000000\n\n#include \"errlevel.h\"\n#include \"basic.h\"\n#include \"buffer.h\"\n#include \"crypto.h\"\n#include \"integer.h\"\n#include \"crypto_backend.h\"\n#include \"crypto_mbedtls.h\"\n#include \"otime.h\"\n#include \"misc.h\"\n\n#include <psa/crypto.h>\n#include <psa/crypto_config.h>\n#include <mbedtls/constant_time.h>\n#include <mbedtls/error.h>\n#include <mbedtls/pem.h>\n\n/*\n *\n * Hardware engine support. Allows loading/unloading of engines.\n *\n */\n\nvoid\ncrypto_init_lib_engine(const char *engine_name)\n{\n    msg(M_WARN, \"Note: mbed TLS hardware crypto engine functionality is not \"\n                \"available\");\n}\n\nprovider_t *\ncrypto_load_provider(const char *provider)\n{\n    if (provider)\n    {\n        msg(M_WARN, \"Note: mbed TLS provider functionality is not available\");\n    }\n    return NULL;\n}\n\nvoid\ncrypto_unload_provider(const char *provname, provider_t *provider)\n{\n}\n\n/* The library doesn't support looking up algorithms by string anymore, so here\n * is a lookup table. */\nstatic const cipher_info_t cipher_info_table[] = {\n/* TODO: Complete the table. */\n\n/* AES */\n#if PSA_WANT_KEY_TYPE_AES\n#if PSA_WANT_ALG_GCM\n    { \"AES-128-GCM\", PSA_KEY_TYPE_AES, PSA_ALG_GCM, 128 / 8, 96 / 8, 128 / 8 },\n    { \"AES-192-GCM\", PSA_KEY_TYPE_AES, PSA_ALG_GCM, 192 / 8, 96 / 8, 128 / 8 },\n    { \"AES-256-GCM\", PSA_KEY_TYPE_AES, PSA_ALG_GCM, 256 / 8, 96 / 8, 128 / 8 },\n#endif /* PSA_WANT_ALG_GCM */\n#if PSA_WANT_ALG_CBC_PKCS7\n    { \"AES-128-CBC\", PSA_KEY_TYPE_AES, PSA_ALG_CBC_PKCS7, 128 / 8, 128 / 8, 128 / 8 },\n    { \"AES-192-CBC\", PSA_KEY_TYPE_AES, PSA_ALG_CBC_PKCS7, 192 / 8, 128 / 8, 128 / 8 },\n    { \"AES-256-CBC\", PSA_KEY_TYPE_AES, PSA_ALG_CBC_PKCS7, 256 / 8, 128 / 8, 128 / 8 },\n#endif /* PSA_WANT_ALG_CBC_PKCS7 */\n#if PSA_WANT_ALG_CTR\n    { \"AES-128-CTR\", PSA_KEY_TYPE_AES, PSA_ALG_CTR, 128 / 8, 128 / 8, 128 / 8 },\n    { \"AES-192-CTR\", PSA_KEY_TYPE_AES, PSA_ALG_CTR, 192 / 8, 128 / 8, 128 / 8 },\n    { \"AES-256-CTR\", PSA_KEY_TYPE_AES, PSA_ALG_CTR, 256 / 8, 128 / 8, 128 / 8 },\n#endif /* PSA_WANT_ALG_CTR */\n#endif /* PSA_WANT_KEY_TYPE_AES */\n\n/* Chacha-Poly */\n#if PSA_WANT_KEY_TYPE_CHACHA20 && PSA_WANT_ALG_CHACHA20_POLY1305\n    { \"CHACHA20-POLY1305\", PSA_KEY_TYPE_CHACHA20, PSA_ALG_CHACHA20_POLY1305, 256 / 8, 96 / 8, 1 },\n#endif\n};\nstatic const size_t cipher_info_table_entries = sizeof(cipher_info_table) / sizeof(cipher_info_t);\n\nstatic const cipher_info_t *\ncipher_get(const char *ciphername)\n{\n    for (size_t i = 0; i < cipher_info_table_entries; i++)\n    {\n        if (strcmp(ciphername, cipher_info_table[i].name) == 0)\n        {\n            return &cipher_info_table[i];\n        }\n    }\n    return NULL;\n}\n\n/* Because Mbed TLS 4 doesn't support looking up algorithms by string, there's\n * nothing to translate. */\nconst cipher_name_pair cipher_name_translation_table[] = {};\nconst size_t cipher_name_translation_table_count =\n    sizeof(cipher_name_translation_table) / sizeof(cipher_name_pair);\n\nint\nrand_bytes(uint8_t *output, int len)\n{\n    if (len < 0)\n    {\n        return 0;\n    }\n    psa_status_t result = psa_generate_random(output, (size_t)len);\n    return result == PSA_SUCCESS;\n}\n\nbool\ncipher_valid_reason(const char *ciphername, const char **reason)\n{\n    ASSERT(reason);\n\n    const cipher_info_t *cipher_info = cipher_get(ciphername);\n\n    if (cipher_info == NULL)\n    {\n        msg(D_LOW, \"Cipher algorithm '%s' not found\", ciphername);\n        *reason = \"disabled because unknown\";\n        return false;\n    }\n\n    if (cipher_info->key_bytes > MAX_CIPHER_KEY_LENGTH)\n    {\n        msg(D_LOW,\n            \"Cipher algorithm '%s' uses a default key size (%u bytes) \"\n            \"which is larger than \" PACKAGE_NAME \"'s current maximum key size \"\n            \"(%d bytes)\",\n            ciphername, cipher_info->key_bytes, MAX_CIPHER_KEY_LENGTH);\n        *reason = \"disabled due to key size too large\";\n        return false;\n    }\n\n    *reason = NULL;\n    return true;\n}\n\nconst char *\ncipher_kt_name(const char *ciphername)\n{\n    const cipher_info_t *cipher_info = cipher_get(ciphername);\n    if (cipher_info == NULL)\n    {\n        return \"[null-cipher]\";\n    }\n    return cipher_info->name;\n}\n\nunsigned int\ncipher_kt_key_size(const char *ciphername)\n{\n    const cipher_info_t *cipher_info = cipher_get(ciphername);\n    if (cipher_info == NULL)\n    {\n        return 0;\n    }\n    return cipher_info->key_bytes;\n}\n\nunsigned int\ncipher_kt_iv_size(const char *ciphername)\n{\n    const cipher_info_t *cipher_info = cipher_get(ciphername);\n\n    if (cipher_info == NULL)\n    {\n        return 0;\n    }\n    return cipher_info->iv_bytes;\n}\n\nunsigned int\ncipher_kt_block_size(const char *ciphername)\n{\n    const cipher_info_t *cipher_info = cipher_get(ciphername);\n    if (cipher_info == NULL)\n    {\n        return 0;\n    }\n    return cipher_info->block_size;\n}\n\nunsigned int\ncipher_kt_tag_size(const char *ciphername)\n{\n    if (cipher_kt_mode_aead(ciphername))\n    {\n        return OPENVPN_AEAD_TAG_LENGTH;\n    }\n    return 0;\n}\n\nbool\ncipher_kt_insecure(const char *ciphername)\n{\n    const cipher_info_t *cipher_info = cipher_get(ciphername);\n    if (cipher_info == NULL)\n    {\n        return true;\n    }\n\n    return !(cipher_info->block_size >= 128 / 8\n             || cipher_info->psa_alg == PSA_ALG_CHACHA20_POLY1305);\n}\n\nbool\ncipher_kt_mode_cbc(const char *ciphername)\n{\n    const cipher_info_t *cipher_info = cipher_get(ciphername);\n    if (cipher_info == NULL)\n    {\n        return false;\n    }\n    return cipher_info->psa_alg == PSA_ALG_CBC_PKCS7;\n}\n\nbool\ncipher_kt_mode_ofb_cfb(const char *ciphername)\n{\n    const cipher_info_t *cipher_info = cipher_get(ciphername);\n    if (cipher_info == NULL)\n    {\n        return false;\n    }\n    return cipher_info->psa_alg == PSA_ALG_OFB || cipher_info->psa_alg == PSA_ALG_CFB;\n}\n\nbool\ncipher_kt_mode_aead(const char *ciphername)\n{\n    const cipher_info_t *cipher_info = cipher_get(ciphername);\n    if (cipher_info == NULL)\n    {\n        return false;\n    }\n    return cipher_info->psa_alg == PSA_ALG_GCM || cipher_info->psa_alg == PSA_ALG_CHACHA20_POLY1305;\n}\n\ncipher_ctx_t *\ncipher_ctx_new(void)\n{\n    cipher_ctx_t *ctx;\n    /* Initializing the object with zeros ensures that it is always safe to call\n     * cipher_ctx_free. */\n    ALLOC_OBJ_CLEAR(ctx, cipher_ctx_t);\n    return ctx;\n}\n\nvoid\ncipher_ctx_free(cipher_ctx_t *ctx)\n{\n    if (cipher_ctx_mode_aead(ctx))\n    {\n        ASSERT(psa_aead_abort(&ctx->operation.aead) == PSA_SUCCESS);\n    }\n    else\n    {\n        ASSERT(psa_cipher_abort(&ctx->operation.cipher) == PSA_SUCCESS);\n    }\n    ASSERT(psa_destroy_key(ctx->key) == PSA_SUCCESS);\n    free(ctx);\n}\n\nvoid\ncipher_ctx_init(cipher_ctx_t *ctx, const uint8_t *key, const char *ciphername,\n                crypto_operation_t enc)\n{\n    ASSERT(ciphername != NULL && ctx != NULL);\n    CLEAR(*ctx);\n\n    ctx->cipher_info = cipher_get(ciphername);\n    ASSERT(ctx->cipher_info != NULL);\n\n    psa_set_key_type(&ctx->key_attributes, ctx->cipher_info->psa_key_type);\n    psa_set_key_algorithm(&ctx->key_attributes, ctx->cipher_info->psa_alg);\n    psa_set_key_bits(&ctx->key_attributes, ctx->cipher_info->key_bytes * 8);\n    psa_set_key_usage_flags(&ctx->key_attributes,\n                            enc == OPENVPN_OP_ENCRYPT ? PSA_KEY_USAGE_ENCRYPT : PSA_KEY_USAGE_DECRYPT);\n\n    if (psa_import_key(&ctx->key_attributes, key, ctx->cipher_info->key_bytes, &ctx->key) != PSA_SUCCESS)\n    {\n        msg(M_FATAL, \"psa_import_key failed\");\n    }\n\n    /* make sure we used a big enough key */\n    ASSERT(psa_get_key_bits(&ctx->key_attributes) == (8 * ctx->cipher_info->key_bytes));\n}\n\nunsigned int\ncipher_ctx_iv_length(const cipher_ctx_t *ctx)\n{\n    return ctx->cipher_info->iv_bytes;\n}\n\nint\ncipher_ctx_get_tag(cipher_ctx_t *ctx, uint8_t *tag, int tag_len)\n{\n    if (!ctx->aead_finished || tag_len < OPENVPN_AEAD_TAG_LENGTH)\n    {\n        return 0;\n    }\n\n    memcpy(tag, ctx->tag, OPENVPN_AEAD_TAG_LENGTH);\n    return 1;\n}\n\nunsigned int\ncipher_ctx_block_size(const cipher_ctx_t *ctx)\n{\n    return ctx->cipher_info->block_size;\n}\n\nint\ncipher_ctx_mode(const cipher_ctx_t *ctx)\n{\n    ASSERT(ctx != NULL);\n    return (int)psa_get_key_algorithm(&ctx->key_attributes);\n}\n\nbool\ncipher_ctx_mode_cbc(const cipher_ctx_t *ctx)\n{\n    return ctx != NULL && cipher_ctx_mode(ctx) == OPENVPN_MODE_CBC;\n}\n\nbool\ncipher_ctx_mode_ofb_cfb(const cipher_ctx_t *ctx)\n{\n    if (ctx == NULL)\n    {\n        return false;\n    }\n    int mode = cipher_ctx_mode(ctx);\n    return mode == OPENVPN_MODE_OFB || mode == OPENVPN_MODE_CFB;\n}\n\nbool\ncipher_ctx_mode_aead(const cipher_ctx_t *ctx)\n{\n    if (ctx == NULL)\n    {\n        return false;\n    }\n    int mode = cipher_ctx_mode(ctx);\n    return mode == (int)PSA_ALG_GCM || mode == (int)PSA_ALG_CHACHA20_POLY1305;\n}\n\nstatic int\ncipher_ctx_direction(const cipher_ctx_t *ctx)\n{\n    psa_key_usage_t key_usage = psa_get_key_usage_flags(&ctx->key_attributes);\n    if (key_usage & PSA_KEY_USAGE_ENCRYPT)\n    {\n        return OPENVPN_OP_ENCRYPT;\n    }\n    else if (key_usage & PSA_KEY_USAGE_DECRYPT)\n    {\n        return OPENVPN_OP_DECRYPT;\n    }\n    else\n    {\n        return -1;\n    }\n}\n\nint\ncipher_ctx_reset(cipher_ctx_t *ctx, const uint8_t *iv_buf)\n{\n    psa_status_t status = 0;\n\n    if (cipher_ctx_mode_aead(ctx))\n    {\n        if (psa_aead_abort(&ctx->operation.aead) != PSA_SUCCESS)\n        {\n            return 0;\n        }\n\n        if (cipher_ctx_direction(ctx) == OPENVPN_OP_ENCRYPT)\n        {\n            status = psa_aead_encrypt_setup(&ctx->operation.aead, ctx->key, ctx->cipher_info->psa_alg);\n        }\n        else if (cipher_ctx_direction(ctx) == OPENVPN_OP_DECRYPT)\n        {\n            status = psa_aead_decrypt_setup(&ctx->operation.aead, ctx->key, ctx->cipher_info->psa_alg);\n        }\n        else\n        {\n            return 0;\n        }\n\n        if (status != PSA_SUCCESS)\n        {\n            return 0;\n        }\n\n        status = psa_aead_set_nonce(&ctx->operation.aead, iv_buf, ctx->cipher_info->iv_bytes);\n        if (status != PSA_SUCCESS)\n        {\n            return 0;\n        }\n    }\n    else\n    {\n        if (psa_cipher_abort(&ctx->operation.cipher) != PSA_SUCCESS)\n        {\n            return 0;\n        }\n\n        if (cipher_ctx_direction(ctx) == OPENVPN_OP_ENCRYPT)\n        {\n            status = psa_cipher_encrypt_setup(&ctx->operation.cipher, ctx->key, ctx->cipher_info->psa_alg);\n        }\n        else if (cipher_ctx_direction(ctx) == OPENVPN_OP_DECRYPT)\n        {\n            status = psa_cipher_decrypt_setup(&ctx->operation.cipher, ctx->key, ctx->cipher_info->psa_alg);\n        }\n        else\n        {\n            return 0;\n        }\n\n        if (status != PSA_SUCCESS)\n        {\n            return 0;\n        }\n\n        status = psa_cipher_set_iv(&ctx->operation.cipher, iv_buf, ctx->cipher_info->iv_bytes);\n        if (status != PSA_SUCCESS)\n        {\n            return 0;\n        }\n    }\n\n    return 1;\n}\n\n/* We rely on the caller to ensure that the destination buffer has enough room,\n * but Mbed TLS always wants a size for the destination buffer. This function\n * calculates the minimum necessary size for a given cipher and input length.\n *\n * This funcion assumes that src_len has been checked to be >= 0. */\nstatic size_t\nneeded_dst_size(const cipher_ctx_t *ctx, int src_len)\n{\n    int mode = cipher_ctx_mode(ctx);\n    if (mode == PSA_ALG_CTR || mode == PSA_ALG_GCM || mode == PSA_ALG_CHACHA20_POLY1305)\n    {\n        /* These algorithms are based on a keystream, so the input and output\n         * length are always equal. */\n        return (size_t)src_len;\n    }\n    else\n    {\n        /* These algorithms are block-based. The number of output blocks that are\n         * produced is at most 1 + src_len / block_size. */\n        size_t block_size = (size_t)cipher_ctx_block_size(ctx);\n        size_t max_blocks = 1 + (size_t)src_len / block_size;\n        return max_blocks * block_size;\n    }\n}\n\nint\ncipher_ctx_update_ad(cipher_ctx_t *ctx, const uint8_t *src, int src_len)\n{\n    if (src_len < 0 || !cipher_ctx_mode_aead(ctx))\n    {\n        return 0;\n    }\n\n    if (psa_aead_update_ad(&ctx->operation.aead, src, (size_t)src_len) != PSA_SUCCESS)\n    {\n        return 0;\n    }\n    return 1;\n}\n\nint\ncipher_ctx_update(cipher_ctx_t *ctx, uint8_t *dst, int *dst_len, uint8_t *src, int src_len)\n{\n    if (src_len < 0)\n    {\n        return 0;\n    }\n\n    size_t dst_size = needed_dst_size(ctx, src_len);\n    size_t psa_output_len = 0;\n    psa_status_t status = 0;\n\n    if (cipher_ctx_mode_aead(ctx))\n    {\n        status = psa_aead_update(&ctx->operation.aead, src, (size_t)src_len, dst, dst_size, &psa_output_len);\n    }\n    else\n    {\n        status = psa_cipher_update(&ctx->operation.cipher, src, (size_t)src_len, dst, dst_size, &psa_output_len);\n    }\n\n    if (status != PSA_SUCCESS)\n    {\n        return 0;\n    }\n\n    if (psa_output_len > INT_MAX)\n    {\n        return 0;\n    }\n    *dst_len = (int)psa_output_len;\n\n    return 1;\n}\n\nint\ncipher_ctx_final(cipher_ctx_t *ctx, uint8_t *dst, int *dst_len)\n{\n    size_t dst_size = needed_dst_size(ctx, 0);\n    size_t psa_output_len = 0;\n    psa_status_t status = 0;\n\n    if (cipher_ctx_mode_aead(ctx))\n    {\n        size_t actual_tag_size = 0;\n        status = psa_aead_finish(&ctx->operation.aead,\n                                 dst,\n                                 dst_size,\n                                 &psa_output_len,\n                                 ctx->tag,\n                                 (size_t)OPENVPN_AEAD_TAG_LENGTH,\n                                 &actual_tag_size);\n        if (status != PSA_SUCCESS || psa_output_len > (size_t)INT_MAX || actual_tag_size != (size_t)OPENVPN_AEAD_TAG_LENGTH)\n        {\n            return 0;\n        }\n        ctx->aead_finished = true;\n    }\n    else\n    {\n        status = psa_cipher_finish(&ctx->operation.cipher, dst, dst_size, &psa_output_len);\n        if (status != PSA_SUCCESS || psa_output_len > (size_t)INT_MAX)\n        {\n            return 0;\n        }\n    }\n\n    *dst_len = (int)psa_output_len;\n\n    return 1;\n}\n\nint\ncipher_ctx_final_check_tag(cipher_ctx_t *ctx, uint8_t *dst, int *dst_len, uint8_t *tag, size_t tag_len)\n{\n    if (cipher_ctx_direction(ctx) != OPENVPN_OP_DECRYPT || !cipher_ctx_mode_aead(ctx))\n    {\n        return 0;\n    }\n\n    size_t psa_output_len = 0;\n    psa_status_t status = 0;\n\n    status = psa_aead_verify(&ctx->operation.aead, dst, 0, &psa_output_len, tag, tag_len);\n    if (status != PSA_SUCCESS || psa_output_len > (size_t)INT_MAX)\n    {\n        return 0;\n    }\n    *dst_len = (int)psa_output_len;\n\n    return 1;\n}\n\nstatic const md_info_t md_info_table[] = {\n#if defined(PSA_WANT_ALG_MD5)\n    { \"MD5\", PSA_ALG_MD5 },\n#endif\n#if defined(PSA_WANT_ALG_SHA_1)\n    { \"SHA1\", PSA_ALG_SHA_1 },\n#endif\n#if defined(PSA_WANT_ALG_SHA_224)\n    { \"SHA224\", PSA_ALG_SHA_224 },\n#endif\n#if defined(PSA_WANT_ALG_SHA_256)\n    { \"SHA256\", PSA_ALG_SHA_256 },\n#endif\n#if defined(PSA_WANT_ALG_SHA_384)\n    { \"SHA384\", PSA_ALG_SHA_384 },\n#endif\n#if defined(PSA_WANT_ALG_SHA_512)\n    { \"SHA512\", PSA_ALG_SHA_512 },\n#endif\n#if defined(PSA_WANT_ALG_SHA3_224)\n    { \"SHA3-224\", PSA_ALG_SHA3_224 },\n#endif\n#if defined(PSA_WANT_ALG_SHA3_256)\n    { \"SHA3-256\", PSA_ALG_SHA3_256 },\n#endif\n#if defined(PSA_WANT_ALG_SHA3_384)\n    { \"SHA3-384\", PSA_ALG_SHA3_384 },\n#endif\n#if defined(PSA_WANT_ALG_SHA3_512)\n    { \"SHA3-512\", PSA_ALG_SHA3_512 },\n#endif\n};\nconst size_t md_info_table_entries = sizeof(md_info_table) / sizeof(md_info_t);\n\nstatic const md_info_t *\nmd_get(const char *digest_name)\n{\n    for (size_t i = 0; i < md_info_table_entries; i++)\n    {\n        if (strcmp(digest_name, md_info_table[i].name) == 0)\n        {\n            return &md_info_table[i];\n        }\n    }\n    return NULL;\n}\n\nbool\nmd_valid(const char *digest)\n{\n    const md_info_t *md = md_get(digest);\n    return md != NULL;\n}\n\nconst char *\nmd_kt_name(const char *mdname)\n{\n    if (strcmp(\"none\", mdname) == 0)\n    {\n        return \"[null-digest]\";\n    }\n    const md_info_t *md = md_get(mdname);\n    if (md == NULL)\n    {\n        return NULL;\n    }\n    return md->name;\n}\n\nunsigned char\nmd_kt_size(const char *mdname)\n{\n    if (strcmp(\"none\", mdname) == 0)\n    {\n        return 0;\n    }\n    const md_info_t *md_info = md_get(mdname);\n    if (md_info == NULL)\n    {\n        return 0;\n    }\n    return (unsigned char)PSA_HASH_LENGTH(md_info->psa_alg);\n}\n\nmd_ctx_t *\nmd_ctx_new(void)\n{\n    md_ctx_t *ctx;\n    ALLOC_OBJ_CLEAR(ctx, md_ctx_t);\n    return ctx;\n}\n\nbool\nmd_full(const char *mdname, const uint8_t *src, size_t src_len, uint8_t *dst)\n{\n    const md_info_t *md = md_get(mdname);\n    if (md == NULL)\n    {\n        return false;\n    }\n\n    /* We depend on the caller to ensure that dst has enough room for the hash,\n     * so we just tell PSA that it can hold the appropriate amount of bytes. */\n    size_t dst_size = PSA_HASH_LENGTH(md->psa_alg);\n    size_t hash_length = 0;\n\n    psa_status_t status = psa_hash_compute(md->psa_alg, src, src_len, dst, dst_size, &hash_length);\n    if (status != PSA_SUCCESS || hash_length != dst_size)\n    {\n        return false;\n    }\n    return true;\n}\n\nvoid\nmd_ctx_free(md_ctx_t *ctx)\n{\n    free(ctx);\n}\n\nvoid\nmd_ctx_init(md_ctx_t *ctx, const char *mdname)\n{\n    const md_info_t *md_info = md_get(mdname);\n    ASSERT(ctx != NULL && md_info != NULL);\n\n    ctx->md_info = md_info;\n    ASSERT(psa_hash_setup(&ctx->operation, md_info->psa_alg) == PSA_SUCCESS);\n}\n\nvoid\nmd_ctx_cleanup(md_ctx_t *ctx)\n{\n    ASSERT(psa_hash_abort(&ctx->operation) == PSA_SUCCESS);\n}\n\nint\nmd_ctx_size(const md_ctx_t *ctx)\n{\n    if (ctx == NULL)\n    {\n        return 0;\n    }\n    return (int)PSA_HASH_LENGTH(ctx->md_info->psa_alg);\n}\n\nvoid\nmd_ctx_update(md_ctx_t *ctx, const uint8_t *src, size_t src_len)\n{\n    ASSERT(psa_hash_update(&ctx->operation, src, src_len) == PSA_SUCCESS);\n}\n\nvoid\nmd_ctx_final(md_ctx_t *ctx, uint8_t *dst)\n{\n    /* We depend on the caller to ensure that dst has enough room for the hash,\n     * so we just tell PSA that it can hold the appropriate amount of bytes. */\n    size_t dst_size = PSA_HASH_LENGTH(ctx->md_info->psa_alg);\n    size_t hash_length = 0;\n\n    ASSERT(psa_hash_finish(&ctx->operation, dst, dst_size, &hash_length) == PSA_SUCCESS);\n    ASSERT(hash_length == dst_size);\n}\n\nhmac_ctx_t *\nhmac_ctx_new(void)\n{\n    hmac_ctx_t *ctx;\n    ALLOC_OBJ_CLEAR(ctx, hmac_ctx_t);\n    return ctx;\n}\n\nvoid\nhmac_ctx_free(hmac_ctx_t *ctx)\n{\n    free(ctx);\n}\n\nstatic void\nhmac_ctx_init_with_arbitrary_key_length(hmac_ctx_t *ctx, const uint8_t *key, size_t key_len, const md_info_t *md_info)\n{\n    ctx->md_info = md_info;\n    psa_set_key_type(&ctx->key_attributes, PSA_KEY_TYPE_HMAC);\n    psa_set_key_algorithm(&ctx->key_attributes, PSA_ALG_HMAC(md_info->psa_alg));\n    psa_set_key_usage_flags(&ctx->key_attributes, PSA_KEY_USAGE_SIGN_MESSAGE);\n\n    if (psa_import_key(&ctx->key_attributes, key, key_len, &ctx->key) != PSA_SUCCESS)\n    {\n        msg(M_FATAL, \"psa_import_key failed\");\n    }\n\n    ASSERT(psa_mac_sign_setup(&ctx->operation, ctx->key, PSA_ALG_HMAC(md_info->psa_alg)) == PSA_SUCCESS);\n}\n\nvoid\nhmac_ctx_init(hmac_ctx_t *ctx, const uint8_t *key, const char *mdname)\n{\n    const md_info_t *md_info = md_get(mdname);\n    ASSERT(ctx != NULL && key != NULL && md_info != NULL);\n\n    hmac_ctx_init_with_arbitrary_key_length(ctx, key, PSA_HASH_LENGTH(md_info->psa_alg), md_info);\n}\n\nvoid\nhmac_ctx_cleanup(hmac_ctx_t *ctx)\n{\n    ASSERT(psa_mac_abort(&ctx->operation) == PSA_SUCCESS);\n    ASSERT(psa_destroy_key(ctx->key) == PSA_SUCCESS);\n}\n\nint\nhmac_ctx_size(hmac_ctx_t *ctx)\n{\n    return (int)PSA_HASH_LENGTH(ctx->md_info->psa_alg);\n}\n\nvoid\nhmac_ctx_reset(hmac_ctx_t *ctx)\n{\n    ASSERT(psa_mac_abort(&ctx->operation) == PSA_SUCCESS);\n    ASSERT(psa_mac_sign_setup(&ctx->operation, ctx->key, PSA_ALG_HMAC(ctx->md_info->psa_alg)) == PSA_SUCCESS);\n}\n\nvoid\nhmac_ctx_update(hmac_ctx_t *ctx, const uint8_t *src, int src_len)\n{\n    ASSERT(src_len >= 0);\n    ASSERT(psa_mac_update(&ctx->operation, src, (size_t)src_len) == PSA_SUCCESS);\n}\n\nvoid\nhmac_ctx_final(hmac_ctx_t *ctx, uint8_t *dst)\n{\n    /* We depend on the caller to ensure that dst has enough room for the hash,\n     * so we just tell PSA that it can hold the appropriate amount of bytes. */\n    size_t dst_size = PSA_HASH_LENGTH(ctx->md_info->psa_alg);\n    size_t hmac_length = 0;\n\n    ASSERT(psa_mac_sign_finish(&ctx->operation, dst, dst_size, &hmac_length) == PSA_SUCCESS);\n    ASSERT(hmac_length == dst_size);\n}\n\n/*\n * Generate the hash required by for the \\c tls1_PRF function.\n *\n * @param md_kt         Message digest to use\n * @param sec           Secret to base the hash on\n * @param sec_len       Length of the secret\n * @param seed          Seed to hash\n * @param seed_len      Length of the seed\n * @param out           Output buffer\n * @param olen          Length of the output buffer\n */\nstatic void\ntls1_P_hash(const md_info_t *md_info, const uint8_t *sec, size_t sec_len, const uint8_t *seed,\n            int seed_len, uint8_t *out, size_t olen)\n{\n    struct gc_arena gc = gc_new();\n    uint8_t A1[MAX_HMAC_KEY_LENGTH];\n\n#ifdef ENABLE_DEBUG\n    /* used by the D_SHOW_KEY_SOURCE, guarded with ENABLE_DEBUG to avoid unused\n     * variables warnings if compiled with --enable-small */\n    const size_t olen_orig = olen;\n    const uint8_t *out_orig = out;\n#endif\n\n    hmac_ctx_t *ctx = hmac_ctx_new();\n    hmac_ctx_t *ctx_tmp = hmac_ctx_new();\n\n    dmsg(D_SHOW_KEY_SOURCE, \"tls1_P_hash sec: %s\", format_hex(sec, sec_len, 0, &gc));\n    dmsg(D_SHOW_KEY_SOURCE, \"tls1_P_hash seed: %s\", format_hex(seed, seed_len, 0, &gc));\n\n    unsigned int chunk = (unsigned int)PSA_HASH_LENGTH(md_info->psa_alg);\n    unsigned int A1_len = (unsigned int)PSA_HASH_LENGTH(md_info->psa_alg);\n\n    /* This is the only place where we init an HMAC with a key that is not\n     * equal to its size, therefore we init the hmac ctx manually here */\n    hmac_ctx_init_with_arbitrary_key_length(ctx, sec, sec_len, md_info);\n    hmac_ctx_init_with_arbitrary_key_length(ctx_tmp, sec, sec_len, md_info);\n\n    hmac_ctx_update(ctx, seed, seed_len);\n    hmac_ctx_final(ctx, A1);\n\n    for (;;)\n    {\n        hmac_ctx_reset(ctx);\n        hmac_ctx_reset(ctx_tmp);\n        hmac_ctx_update(ctx, A1, A1_len);\n        hmac_ctx_update(ctx_tmp, A1, A1_len);\n        hmac_ctx_update(ctx, seed, (int)seed_len);\n\n        if (olen > chunk)\n        {\n            hmac_ctx_final(ctx, out);\n            out += chunk;\n            olen -= chunk;\n            hmac_ctx_final(ctx_tmp, A1); /* calc the next A1 value */\n        }\n        else                             /* last one */\n        {\n            hmac_ctx_final(ctx, A1);\n            memcpy(out, A1, olen);\n            break;\n        }\n    }\n    hmac_ctx_cleanup(ctx);\n    hmac_ctx_free(ctx);\n    hmac_ctx_cleanup(ctx_tmp);\n    hmac_ctx_free(ctx_tmp);\n    secure_memzero(A1, sizeof(A1));\n\n    dmsg(D_SHOW_KEY_SOURCE, \"tls1_P_hash out: %s\", format_hex(out_orig, olen_orig, 0, &gc));\n    gc_free(&gc);\n}\n\n/*\n * Use the TLS PRF function for generating data channel keys.\n * This code is based on the OpenSSL library.\n *\n * TLS generates keys as such:\n *\n * master_secret[48] = PRF(pre_master_secret[48], \"master secret\",\n *                         ClientHello.random[32] + ServerHello.random[32])\n *\n * key_block[] = PRF(SecurityParameters.master_secret[48],\n *                 \"key expansion\",\n *                 SecurityParameters.server_random[32] +\n *                 SecurityParameters.client_random[32]);\n *\n * Notes:\n *\n * (1) key_block contains a full set of 4 keys.\n * (2) The pre-master secret is generated by the client.\n */\nbool\nssl_tls1_PRF(const uint8_t *label, size_t label_len, const uint8_t *sec, size_t slen, uint8_t *out1,\n             size_t olen)\n{\n    const md_info_t *md5 = md_get(\"MD5\");\n    const md_info_t *sha1 = md_get(\"SHA1\");\n\n    if (label_len > (size_t)INT_MAX)\n    {\n        return false;\n    }\n\n    struct gc_arena gc = gc_new();\n\n    uint8_t *out2 = (uint8_t *)gc_malloc(olen, false, &gc);\n\n    size_t len = slen / 2;\n    const uint8_t *S1 = sec;\n    const uint8_t *S2 = &(sec[len]);\n    len += (slen & 1); /* add for odd, make longer */\n\n    tls1_P_hash(md5, S1, len, label, (int)label_len, out1, olen);\n    tls1_P_hash(sha1, S2, len, label, (int)label_len, out2, olen);\n\n    for (size_t i = 0; i < olen; i++)\n    {\n        out1[i] ^= out2[i];\n    }\n\n    secure_memzero(out2, olen);\n\n    dmsg(D_SHOW_KEY_SOURCE, \"tls1_PRF out[%zu]: %s\", olen, format_hex(out1, olen, 0, &gc));\n\n    gc_free(&gc);\n    return true;\n}\n\nvoid\ncrypto_init_lib(void)\n{\n}\n\nvoid\ncrypto_uninit_lib(void)\n{\n}\n\nvoid\ncrypto_clear_error(void)\n{\n}\n\nbool\nmbed_log_err(unsigned int flags, int errval, const char *prefix)\n{\n    if (0 != errval)\n    {\n        char errstr[256];\n        mbedtls_strerror(errval, errstr, sizeof(errstr));\n\n        if (NULL == prefix)\n        {\n            prefix = \"mbed TLS error\";\n        }\n        msg(flags, \"%s: %s\", prefix, errstr);\n    }\n\n    return 0 == errval;\n}\n\nbool\nmbed_log_func_line(unsigned int flags, int errval, const char *func, int line)\n{\n    char prefix[256];\n\n    if (!checked_snprintf(prefix, sizeof(prefix), \"%s:%d\", func, line))\n    {\n        return mbed_log_err(flags, errval, func);\n    }\n\n    return mbed_log_err(flags, errval, prefix);\n}\n\nint\nmemcmp_constant_time(const void *a, const void *b, size_t size)\n{\n    return mbedtls_ct_memcmp(a, b, size);\n}\n\nvoid\nshow_available_ciphers(void)\n{\n    /* Mbed TLS 4 does not currently have a mechanism to discover available\n     * ciphers. We instead print out the ciphers from cipher_info_table. */\n\n#ifndef ENABLE_SMALL\n    printf(\"The following ciphers and cipher modes are available for use\\n\"\n           \"with \" PACKAGE_NAME \".  Each cipher shown below may be used as a\\n\"\n           \"parameter to the --data-ciphers (or --cipher) option.  Using a\\n\"\n           \"GCM or CBC mode is recommended.  In static key mode only CBC\\n\"\n           \"mode is allowed.\\n\\n\");\n#endif\n\n    for (size_t i = 0; i < cipher_info_table_entries; i++)\n    {\n        const cipher_info_t *info = &cipher_info_table[i];\n        const char *name = info->name;\n        if (!cipher_kt_insecure(name) && (cipher_kt_mode_aead(name) || cipher_kt_mode_cbc(name)))\n        {\n            print_cipher(name);\n        }\n    }\n\n    printf(\"\\nThe following ciphers have a block size of less than 128 bits, \\n\"\n           \"and are therefore deprecated.  Do not use unless you have to.\\n\\n\");\n    for (size_t i = 0; i < cipher_info_table_entries; i++)\n    {\n        const cipher_info_t *info = &cipher_info_table[i];\n        const char *name = info->name;\n        if (cipher_kt_insecure(name) && (cipher_kt_mode_aead(name) || cipher_kt_mode_cbc(name)))\n        {\n            print_cipher(name);\n        }\n    }\n    printf(\"\\n\");\n}\n\nvoid\nshow_available_digests(void)\n{\n    /* Mbed TLS 4 does not currently have a mechanism to discover available\n     * message digests. We instead print out the digests from md_info_table. */\n\n#ifndef ENABLE_SMALL\n    printf(\"The following message digests are available for use with\\n\" PACKAGE_NAME\n           \".  A message digest is used in conjunction with\\n\"\n           \"the HMAC function, to authenticate received packets.\\n\"\n           \"You can specify a message digest as parameter to\\n\"\n           \"the --auth option.\\n\\n\");\n#endif\n\n    for (size_t i = 0; i < md_info_table_entries; i++)\n    {\n        const md_info_t *info = &md_info_table[i];\n        printf(\"%s %d bit default key\\n\", info->name,\n               (unsigned char)PSA_HASH_LENGTH(info->psa_alg) * 8);\n    }\n    printf(\"\\n\");\n}\n\nvoid\nshow_available_engines(void)\n{\n    printf(\"Sorry, mbed TLS hardware crypto engine functionality is not \"\n           \"available\\n\");\n}\n\nbool\ncrypto_pem_encode(const char *name, struct buffer *dst, const struct buffer *src,\n                  struct gc_arena *gc)\n{\n    /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */\n    char header[1000 + 1] = { 0 };\n    char footer[1000 + 1] = { 0 };\n\n    if (!checked_snprintf(header, sizeof(header), \"-----BEGIN %s-----\\n\", name))\n    {\n        return false;\n    }\n    if (!checked_snprintf(footer, sizeof(footer), \"-----END %s-----\\n\", name))\n    {\n        return false;\n    }\n\n    size_t out_len = 0;\n    if (MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL\n        != mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src), NULL, 0, &out_len))\n    {\n        return false;\n    }\n\n    /* We set the size buf to out_len-1 to NOT include the 0 byte that\n     * mbedtls_pem_write_buffer in its length calculation */\n    *dst = alloc_buf_gc(out_len, gc);\n    if (!mbed_ok(mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src), BPTR(dst),\n                                          BCAP(dst), &out_len))\n        || !(out_len < INT_MAX && out_len > 1)\n        || !buf_inc_len(dst, (int)out_len - 1))\n    {\n        CLEAR(*dst);\n        return false;\n    }\n\n    return true;\n}\n\nbool\ncrypto_pem_decode(const char *name, struct buffer *dst, const struct buffer *src)\n{\n    /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */\n    char header[1000 + 1] = { 0 };\n    char footer[1000 + 1] = { 0 };\n\n    if (!checked_snprintf(header, sizeof(header), \"-----BEGIN %s-----\", name))\n    {\n        return false;\n    }\n    if (!checked_snprintf(footer, sizeof(footer), \"-----END %s-----\", name))\n    {\n        return false;\n    }\n\n    /* mbed TLS requires the src to be null-terminated */\n    /* allocate a new buffer to avoid modifying the src buffer */\n    struct gc_arena gc = gc_new();\n    struct buffer input = alloc_buf_gc(BLEN(src) + 1, &gc);\n    buf_copy(&input, src);\n    buf_null_terminate(&input);\n\n    size_t use_len = 0;\n    mbedtls_pem_context ctx = { 0 };\n    bool ret =\n        mbed_ok(mbedtls_pem_read_buffer(&ctx, header, footer, BPTR(&input), NULL, 0, &use_len));\n    size_t buf_size = 0;\n    const unsigned char *buf = mbedtls_pem_get_buffer(&ctx, &buf_size);\n    if (ret && !buf_write(dst, buf, buf_size))\n    {\n        ret = false;\n        msg(M_WARN, \"PEM decode error: destination buffer too small\");\n    }\n\n    mbedtls_pem_free(&ctx);\n    gc_free(&gc);\n    return ret;\n}\n\n#endif /* MBEDTLS_VERSION_NUMBER >= 0x04000000 */\n#endif /* defined(ENABLE_CRYPTO_MBEDTLS) */\n"
  },
  {
    "path": "src/openvpn/crypto_mbedtls.h",
    "content": "\n/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Data Channel Cryptography backend interface using the TF-PSA-Crypto library\n * part of Mbed TLS 4.\n */\n\n#ifndef CRYPTO_MBEDTLS4_H_\n#define CRYPTO_MBEDTLS4_H_\n\n#include <psa/crypto.h>\n\n#include \"integer.h\"\n\n/** Maximum length of an IV */\n#define OPENVPN_MAX_IV_LENGTH 16\n\n/** Cipher is in CBC mode */\n#define OPENVPN_MODE_CBC PSA_ALG_CBC_PKCS7\n\n/** Cipher is in OFB mode */\n#define OPENVPN_MODE_OFB PSA_ALG_OFB\n\n/** Cipher is in CFB mode */\n#define OPENVPN_MODE_CFB PSA_ALG_CFB\n\n/** Cipher is in GCM mode */\n#define OPENVPN_MODE_GCM PSA_ALG_GCM\n\ntypedef int crypto_operation_t;\n\n/** Cipher should encrypt */\n#define OPENVPN_OP_ENCRYPT 0\n\n/** Cipher should decrypt */\n#define OPENVPN_OP_DECRYPT 1\n\n#define MD4_DIGEST_LENGTH    16\n#define MD5_DIGEST_LENGTH    16\n#define SHA_DIGEST_LENGTH    20\n#define SHA256_DIGEST_LENGTH 32\n\ntypedef void provider_t;\n\ntypedef struct cipher_info\n{\n    const char *name;\n    psa_key_type_t psa_key_type;\n    psa_algorithm_t psa_alg;\n    unsigned int key_bytes;\n    unsigned int iv_bytes;\n    unsigned int block_size;\n} cipher_info_t;\n\ntypedef union psa_cipher_or_aead_operation\n{\n    psa_cipher_operation_t cipher;\n    psa_aead_operation_t aead;\n} cipher_operation_t;\n\ntypedef struct cipher_ctx\n{\n    mbedtls_svc_key_id_t key;\n    psa_key_attributes_t key_attributes;\n    const cipher_info_t *cipher_info;\n    bool aead_finished;\n    cipher_operation_t operation;\n    uint8_t tag[16];\n} cipher_ctx_t;\n\ntypedef struct md_info\n{\n    const char *name;\n    psa_algorithm_t psa_alg;\n} md_info_t;\n\ntypedef struct md_ctx\n{\n    const md_info_t *md_info;\n    psa_hash_operation_t operation;\n} md_ctx_t;\n\ntypedef struct hmac_ctx\n{\n    mbedtls_svc_key_id_t key;\n    psa_key_attributes_t key_attributes;\n    const md_info_t *md_info;\n    psa_mac_operation_t operation;\n} hmac_ctx_t;\n\n/**\n * Log the supplied mbed TLS error, prefixed by supplied prefix.\n *\n * @param flags         Flags to indicate error type and priority.\n * @param errval        mbed TLS error code.\n * @param prefix        Prefix to mbed TLS error message.\n *\n * @returns true if no errors are detected, false otherwise.\n */\nbool mbed_log_err(unsigned int flags, int errval, const char *prefix);\n\n/**\n * Log the supplied mbed TLS error, prefixed by function name and line number.\n *\n * @param flags         Flags to indicate error type and priority.\n * @param errval        mbed TLS error code.\n * @param func          Function name where error was reported.\n * @param line          Line number where error was reported.\n *\n * @returns true if no errors are detected, false otherwise.\n */\nbool mbed_log_func_line(unsigned int flags, int errval, const char *func, int line);\n\n/** Wraps mbed_log_func_line() to prevent function calls for non-errors */\nstatic inline bool\nmbed_log_func_line_lite(unsigned int flags, int errval, const char *func, int line)\n{\n    if (errval)\n    {\n        return mbed_log_func_line(flags, errval, func, line);\n    }\n    return true;\n}\n\n/**\n * Check errval and log on error.\n *\n * Convenience wrapper to put around mbed TLS library calls, e.g.\n *   if (!mbed_ok (mbedtls_ssl_func())) return 0;\n * or\n *   ASSERT (mbed_ok (mbedtls_ssl_func()));\n *\n * @param errval        mbed TLS error code to convert to error message.\n *\n * @returns true if no errors are detected, false otherwise.\n * TODO: The log function has been removed, do something about it?\n */\n#define mbed_ok(errval) mbed_log_func_line_lite(D_CRYPT_ERRORS, errval, __func__, __LINE__)\n\n#endif /* CRYPTO_MBEDTLS4_H_ */\n"
  },
  {
    "path": "src/openvpn/crypto_mbedtls_legacy.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Data Channel Cryptography mbed TLS-specific backend interface,\n * for mbed TLS 3.X versions.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if defined(ENABLE_CRYPTO_MBEDTLS)\n#include <mbedtls/version.h>\n\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n\n#include \"errlevel.h\"\n#include \"basic.h\"\n#include \"buffer.h\"\n#include \"crypto.h\"\n#include \"integer.h\"\n#include \"crypto_backend.h\"\n#include \"otime.h\"\n#include \"misc.h\"\n\n#include <mbedtls/base64.h>\n#include <mbedtls/des.h>\n#include <mbedtls/error.h>\n#include <mbedtls/md5.h>\n#include <mbedtls/cipher.h>\n#include <mbedtls/pem.h>\n\n#include <mbedtls/entropy.h>\n#include <mbedtls/ssl.h>\n\n\n/*\n *\n * Hardware engine support. Allows loading/unloading of engines.\n *\n */\n\nvoid\ncrypto_init_lib_engine(const char *engine_name)\n{\n    msg(M_WARN, \"Note: mbed TLS hardware crypto engine functionality is not \"\n                \"available\");\n}\n\nprovider_t *\ncrypto_load_provider(const char *provider)\n{\n    if (provider)\n    {\n        msg(M_WARN, \"Note: mbed TLS provider functionality is not available\");\n    }\n    return NULL;\n}\n\nvoid\ncrypto_unload_provider(const char *provname, provider_t *provider)\n{\n}\n\n/*\n *\n * Functions related to the core crypto library\n *\n */\n\nvoid\ncrypto_init_lib(void)\n{\n}\n\nvoid\ncrypto_uninit_lib(void)\n{\n}\n\nvoid\ncrypto_clear_error(void)\n{\n}\n\nbool\nmbed_log_err(unsigned int flags, int errval, const char *prefix)\n{\n    if (0 != errval)\n    {\n        char errstr[256];\n        mbedtls_strerror(errval, errstr, sizeof(errstr));\n\n        if (NULL == prefix)\n        {\n            prefix = \"mbed TLS error\";\n        }\n        msg(flags, \"%s: %s\", prefix, errstr);\n    }\n\n    return 0 == errval;\n}\n\nbool\nmbed_log_func_line(unsigned int flags, int errval, const char *func, int line)\n{\n    char prefix[256];\n\n    if (!checked_snprintf(prefix, sizeof(prefix), \"%s:%d\", func, line))\n    {\n        return mbed_log_err(flags, errval, func);\n    }\n\n    return mbed_log_err(flags, errval, prefix);\n}\n\n\n#ifdef DMALLOC\nvoid\ncrypto_init_dmalloc(void)\n{\n    msg(M_ERR, \"Error: dmalloc support is not available for mbed TLS.\");\n}\n#endif /* DMALLOC */\n\nconst cipher_name_pair cipher_name_translation_table[] = {\n    { \"BF-CBC\", \"BLOWFISH-CBC\" },\n    { \"BF-CFB\", \"BLOWFISH-CFB64\" },\n    { \"CAMELLIA-128-CFB\", \"CAMELLIA-128-CFB128\" },\n    { \"CAMELLIA-192-CFB\", \"CAMELLIA-192-CFB128\" },\n    { \"CAMELLIA-256-CFB\", \"CAMELLIA-256-CFB128\" }\n};\nconst size_t cipher_name_translation_table_count =\n    sizeof(cipher_name_translation_table) / sizeof(*cipher_name_translation_table);\n\nvoid\nshow_available_ciphers(void)\n{\n    const int *ciphers = mbedtls_cipher_list();\n\n#ifndef ENABLE_SMALL\n    printf(\"The following ciphers and cipher modes are available for use\\n\"\n           \"with \" PACKAGE_NAME \".  Each cipher shown below may be used as a\\n\"\n           \"parameter to the --data-ciphers (or --cipher) option.  Using a\\n\"\n           \"GCM or CBC mode is recommended.  In static key mode only CBC\\n\"\n           \"mode is allowed.\\n\\n\");\n#endif\n\n    while (*ciphers != 0)\n    {\n        const mbedtls_cipher_info_t *info = mbedtls_cipher_info_from_type(*ciphers);\n        const char *name = mbedtls_cipher_info_get_name(info);\n        if (info && name && !cipher_kt_insecure(name)\n            && (cipher_kt_mode_aead(name) || cipher_kt_mode_cbc(name)))\n        {\n            print_cipher(name);\n        }\n        ciphers++;\n    }\n\n    printf(\"\\nThe following ciphers have a block size of less than 128 bits, \\n\"\n           \"and are therefore deprecated.  Do not use unless you have to.\\n\\n\");\n    ciphers = mbedtls_cipher_list();\n    while (*ciphers != 0)\n    {\n        const mbedtls_cipher_info_t *info = mbedtls_cipher_info_from_type(*ciphers);\n        const char *name = mbedtls_cipher_info_get_name(info);\n        if (info && name && cipher_kt_insecure(name)\n            && (cipher_kt_mode_aead(name) || cipher_kt_mode_cbc(name)))\n        {\n            print_cipher(name);\n        }\n        ciphers++;\n    }\n    printf(\"\\n\");\n}\n\nvoid\nshow_available_digests(void)\n{\n    const int *digests = mbedtls_md_list();\n\n#ifndef ENABLE_SMALL\n    printf(\"The following message digests are available for use with\\n\" PACKAGE_NAME\n           \".  A message digest is used in conjunction with\\n\"\n           \"the HMAC function, to authenticate received packets.\\n\"\n           \"You can specify a message digest as parameter to\\n\"\n           \"the --auth option.\\n\\n\");\n#endif\n\n    while (*digests != 0)\n    {\n        const mbedtls_md_info_t *info = mbedtls_md_info_from_type(*digests);\n\n        if (info)\n        {\n            printf(\"%s %d bit default key\\n\", mbedtls_md_get_name(info),\n                   mbedtls_md_get_size(info) * 8);\n        }\n        digests++;\n    }\n    printf(\"\\n\");\n}\n\nvoid\nshow_available_engines(void)\n{\n    printf(\"Sorry, mbed TLS hardware crypto engine functionality is not \"\n           \"available\\n\");\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\nbool\ncrypto_pem_encode(const char *name, struct buffer *dst, const struct buffer *src,\n                  struct gc_arena *gc)\n{\n    /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */\n    char header[1000 + 1] = { 0 };\n    char footer[1000 + 1] = { 0 };\n\n    if (!checked_snprintf(header, sizeof(header), \"-----BEGIN %s-----\\n\", name))\n    {\n        return false;\n    }\n    if (!checked_snprintf(footer, sizeof(footer), \"-----END %s-----\\n\", name))\n    {\n        return false;\n    }\n\n    size_t out_len = 0;\n    if (MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL\n        != mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src), NULL, 0, &out_len))\n    {\n        return false;\n    }\n\n    /* We set the size buf to out_len-1 to NOT include the 0 byte that\n     * mbedtls_pem_write_buffer in its length calculation */\n    *dst = alloc_buf_gc(out_len, gc);\n    if (!mbed_ok(mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src), BPTR(dst),\n                                          BCAP(dst), &out_len))\n        || !buf_inc_len(dst, out_len - 1))\n    {\n        CLEAR(*dst);\n        return false;\n    }\n\n    return true;\n}\n\nbool\ncrypto_pem_decode(const char *name, struct buffer *dst, const struct buffer *src)\n{\n    /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */\n    char header[1000 + 1] = { 0 };\n    char footer[1000 + 1] = { 0 };\n\n    if (!checked_snprintf(header, sizeof(header), \"-----BEGIN %s-----\", name))\n    {\n        return false;\n    }\n    if (!checked_snprintf(footer, sizeof(footer), \"-----END %s-----\", name))\n    {\n        return false;\n    }\n\n    /* mbed TLS requires the src to be null-terminated */\n    /* allocate a new buffer to avoid modifying the src buffer */\n    struct gc_arena gc = gc_new();\n    struct buffer input = alloc_buf_gc(BLEN(src) + 1, &gc);\n    buf_copy(&input, src);\n    buf_null_terminate(&input);\n\n    size_t use_len = 0;\n    mbedtls_pem_context ctx = { 0 };\n    bool ret =\n        mbed_ok(mbedtls_pem_read_buffer(&ctx, header, footer, BPTR(&input), NULL, 0, &use_len));\n    size_t buf_size = 0;\n    const unsigned char *buf = mbedtls_pem_get_buffer(&ctx, &buf_size);\n    if (ret && !buf_write(dst, buf, buf_size))\n    {\n        ret = false;\n        msg(M_WARN, \"PEM decode error: destination buffer too small\");\n    }\n\n    mbedtls_pem_free(&ctx);\n    gc_free(&gc);\n    return ret;\n}\n\n/*\n *\n * Random number functions, used in cases where we want\n * reasonably strong cryptographic random number generation\n * without depleting our entropy pool.  Used for random\n * IV values and a number of other miscellaneous tasks.\n *\n */\n\n/*\n * Initialise the given ctr_drbg context, using a personalisation string and an\n * entropy gathering function.\n */\nmbedtls_ctr_drbg_context *\nrand_ctx_get(void)\n{\n    static mbedtls_entropy_context ec = { 0 };\n    static mbedtls_ctr_drbg_context cd_ctx = { 0 };\n    static bool rand_initialised = false;\n\n    if (!rand_initialised)\n    {\n        struct gc_arena gc = gc_new();\n        struct buffer pers_string = alloc_buf_gc(100, &gc);\n\n        /*\n         * Personalisation string, should be as unique as possible (see NIST\n         * 800-90 section 8.7.1). We have very little information at this stage.\n         * Include Program Name, memory address of the context and PID.\n         */\n        buf_printf(&pers_string, \"OpenVPN %0u %p %s\", platform_getpid(), &cd_ctx,\n                   time_string(0, 0, 0, &gc));\n\n        /* Initialise mbed TLS RNG, and built-in entropy sources */\n        mbedtls_entropy_init(&ec);\n\n        mbedtls_ctr_drbg_init(&cd_ctx);\n        if (!mbed_ok(mbedtls_ctr_drbg_seed(&cd_ctx, mbedtls_entropy_func, &ec, BPTR(&pers_string),\n                                           BLEN(&pers_string))))\n        {\n            msg(M_FATAL, \"Failed to initialize random generator\");\n        }\n\n        gc_free(&gc);\n        rand_initialised = true;\n    }\n\n    return &cd_ctx;\n}\n\nint\nrand_bytes(uint8_t *output, int len)\n{\n    mbedtls_ctr_drbg_context *rng_ctx = rand_ctx_get();\n\n    while (len > 0)\n    {\n        const size_t blen = min_int(len, MBEDTLS_CTR_DRBG_MAX_REQUEST);\n        if (0 != mbedtls_ctr_drbg_random(rng_ctx, output, blen))\n        {\n            return 0;\n        }\n\n        output += blen;\n        len -= blen;\n    }\n\n    return 1;\n}\n\n/*\n *\n * Generic cipher key type functions\n *\n */\nstatic const mbedtls_cipher_info_t *\ncipher_get(const char *ciphername)\n{\n    ASSERT(ciphername);\n\n    const mbedtls_cipher_info_t *cipher = NULL;\n\n    ciphername = translate_cipher_name_from_openvpn(ciphername);\n    cipher = mbedtls_cipher_info_from_string(ciphername);\n    return cipher;\n}\n\nbool\ncipher_valid_reason(const char *ciphername, const char **reason)\n{\n    ASSERT(reason);\n\n    const mbedtls_cipher_info_t *cipher = cipher_get(ciphername);\n\n    if (NULL == cipher)\n    {\n        msg(D_LOW, \"Cipher algorithm '%s' not found\", ciphername);\n        *reason = \"disabled because unknown\";\n        return false;\n    }\n\n    const size_t key_bytelen = mbedtls_cipher_info_get_key_bitlen(cipher) / 8;\n    if (key_bytelen > MAX_CIPHER_KEY_LENGTH)\n    {\n        msg(D_LOW,\n            \"Cipher algorithm '%s' uses a default key size (%zu bytes) \"\n            \"which is larger than \" PACKAGE_NAME \"'s current maximum key size \"\n            \"(%d bytes)\",\n            ciphername, key_bytelen, MAX_CIPHER_KEY_LENGTH);\n        *reason = \"disabled due to key size too large\";\n        return false;\n    }\n\n    *reason = NULL;\n    return true;\n}\n\nconst char *\ncipher_kt_name(const char *ciphername)\n{\n    const mbedtls_cipher_info_t *cipher_kt = cipher_get(ciphername);\n    if (NULL == cipher_kt)\n    {\n        return \"[null-cipher]\";\n    }\n\n    return translate_cipher_name_to_openvpn(mbedtls_cipher_info_get_name(cipher_kt));\n}\n\nunsigned int\ncipher_kt_key_size(const char *ciphername)\n{\n    const mbedtls_cipher_info_t *cipher_kt = cipher_get(ciphername);\n\n    if (NULL == cipher_kt)\n    {\n        return 0;\n    }\n\n    return mbedtls_cipher_info_get_key_bitlen(cipher_kt) / 8;\n}\n\nunsigned int\ncipher_kt_iv_size(const char *ciphername)\n{\n    const mbedtls_cipher_info_t *cipher_kt = cipher_get(ciphername);\n\n    if (NULL == cipher_kt)\n    {\n        return 0;\n    }\n    return mbedtls_cipher_info_get_iv_size(cipher_kt);\n}\n\nunsigned int\ncipher_kt_block_size(const char *ciphername)\n{\n    const mbedtls_cipher_info_t *cipher_kt = cipher_get(ciphername);\n    if (NULL == cipher_kt)\n    {\n        return 0;\n    }\n    return mbedtls_cipher_info_get_block_size(cipher_kt);\n}\n\nunsigned int\ncipher_kt_tag_size(const char *ciphername)\n{\n    if (cipher_kt_mode_aead(ciphername))\n    {\n        return OPENVPN_AEAD_TAG_LENGTH;\n    }\n    return 0;\n}\n\nbool\ncipher_kt_insecure(const char *ciphername)\n{\n    const mbedtls_cipher_info_t *cipher_kt = cipher_get(ciphername);\n    if (!cipher_kt)\n    {\n        return true;\n    }\n\n    return !(cipher_kt_block_size(ciphername) >= 128 / 8\n#ifdef MBEDTLS_CHACHAPOLY_C\n             || mbedtls_cipher_info_get_type(cipher_kt) == MBEDTLS_CIPHER_CHACHA20_POLY1305\n#endif\n    );\n}\n\nstatic mbedtls_cipher_mode_t\ncipher_kt_mode(const mbedtls_cipher_info_t *cipher_kt)\n{\n    ASSERT(NULL != cipher_kt);\n    return mbedtls_cipher_info_get_mode(cipher_kt);\n}\n\nbool\ncipher_kt_mode_cbc(const char *ciphername)\n{\n    const mbedtls_cipher_info_t *cipher = cipher_get(ciphername);\n    return cipher && cipher_kt_mode(cipher) == OPENVPN_MODE_CBC;\n}\n\nbool\ncipher_kt_mode_ofb_cfb(const char *ciphername)\n{\n    const mbedtls_cipher_info_t *cipher = cipher_get(ciphername);\n    return cipher\n           && (cipher_kt_mode(cipher) == OPENVPN_MODE_OFB\n               || cipher_kt_mode(cipher) == OPENVPN_MODE_CFB);\n}\n\nbool\ncipher_kt_mode_aead(const char *ciphername)\n{\n    const mbedtls_cipher_info_t *cipher = cipher_get(ciphername);\n    return cipher\n           && (cipher_kt_mode(cipher) == OPENVPN_MODE_GCM\n#ifdef MBEDTLS_CHACHAPOLY_C\n               || cipher_kt_mode(cipher) == MBEDTLS_MODE_CHACHAPOLY\n#endif\n           );\n}\n\n\n/*\n *\n * Generic cipher context functions\n *\n */\n\nmbedtls_cipher_context_t *\ncipher_ctx_new(void)\n{\n    mbedtls_cipher_context_t *ctx;\n    ALLOC_OBJ(ctx, mbedtls_cipher_context_t);\n    return ctx;\n}\n\nvoid\ncipher_ctx_free(mbedtls_cipher_context_t *ctx)\n{\n    mbedtls_cipher_free(ctx);\n    free(ctx);\n}\n\nvoid\ncipher_ctx_init(mbedtls_cipher_context_t *ctx, const uint8_t *key, const char *ciphername,\n                crypto_operation_t enc)\n{\n    ASSERT(NULL != ciphername && NULL != ctx);\n    CLEAR(*ctx);\n\n    const mbedtls_cipher_info_t *kt = cipher_get(ciphername);\n    ASSERT(kt);\n    size_t key_bitlen = mbedtls_cipher_info_get_key_bitlen(kt);\n\n    if (!mbed_ok(mbedtls_cipher_setup(ctx, kt)))\n    {\n        msg(M_FATAL, \"mbed TLS cipher context init #1\");\n    }\n\n    if (!mbed_ok(mbedtls_cipher_setkey(ctx, key, (int)key_bitlen, enc)))\n    {\n        msg(M_FATAL, \"mbed TLS cipher set key\");\n    }\n\n    if (mbedtls_cipher_info_get_mode(kt) == MBEDTLS_MODE_CBC)\n    {\n        if (!mbed_ok(mbedtls_cipher_set_padding_mode(ctx, MBEDTLS_PADDING_PKCS7)))\n        {\n            msg(M_FATAL, \"mbed TLS cipher set padding mode\");\n        }\n    }\n\n    /* make sure we used a big enough key */\n    ASSERT((size_t)mbedtls_cipher_get_key_bitlen(ctx) <= key_bitlen);\n}\n\nunsigned int\ncipher_ctx_iv_length(const mbedtls_cipher_context_t *ctx)\n{\n    return mbedtls_cipher_get_iv_size(ctx);\n}\n\nint\ncipher_ctx_get_tag(cipher_ctx_t *ctx, uint8_t *tag, int tag_len)\n{\n    if (tag_len < 0)\n    {\n        return 0;\n    }\n\n    if (!mbed_ok(mbedtls_cipher_write_tag(ctx, (unsigned char *)tag, tag_len)))\n    {\n        return 0;\n    }\n\n    return 1;\n}\n\nunsigned int\ncipher_ctx_block_size(const mbedtls_cipher_context_t *ctx)\n{\n    return mbedtls_cipher_get_block_size(ctx);\n}\n\nint\ncipher_ctx_mode(const mbedtls_cipher_context_t *ctx)\n{\n    ASSERT(NULL != ctx);\n\n    return mbedtls_cipher_get_cipher_mode(ctx);\n}\n\nbool\ncipher_ctx_mode_cbc(const cipher_ctx_t *ctx)\n{\n    return ctx && cipher_ctx_mode(ctx) == OPENVPN_MODE_CBC;\n}\n\n\nbool\ncipher_ctx_mode_ofb_cfb(const cipher_ctx_t *ctx)\n{\n    return ctx\n           && (cipher_ctx_mode(ctx) == OPENVPN_MODE_OFB\n               || cipher_ctx_mode(ctx) == OPENVPN_MODE_CFB);\n}\n\nbool\ncipher_ctx_mode_aead(const cipher_ctx_t *ctx)\n{\n    return ctx\n           && (cipher_ctx_mode(ctx) == OPENVPN_MODE_GCM\n#ifdef MBEDTLS_CHACHAPOLY_C\n               || cipher_ctx_mode(ctx) == MBEDTLS_MODE_CHACHAPOLY\n#endif\n           );\n}\n\nint\ncipher_ctx_reset(mbedtls_cipher_context_t *ctx, const uint8_t *iv_buf)\n{\n    if (!mbed_ok(mbedtls_cipher_reset(ctx)))\n    {\n        return 0;\n    }\n\n    if (!mbed_ok(mbedtls_cipher_set_iv(ctx, iv_buf, (size_t)mbedtls_cipher_get_iv_size(ctx))))\n    {\n        return 0;\n    }\n\n    return 1;\n}\n\nint\ncipher_ctx_update_ad(cipher_ctx_t *ctx, const uint8_t *src, int src_len)\n{\n    if (src_len < 0)\n    {\n        return 0;\n    }\n\n    if (!mbed_ok(mbedtls_cipher_update_ad(ctx, src, src_len)))\n    {\n        return 0;\n    }\n\n    return 1;\n}\n\nint\ncipher_ctx_update(mbedtls_cipher_context_t *ctx, uint8_t *dst, int *dst_len, uint8_t *src,\n                  int src_len)\n{\n    size_t s_dst_len = *dst_len;\n\n    if (!mbed_ok(mbedtls_cipher_update(ctx, src, (size_t)src_len, dst, &s_dst_len)))\n    {\n        return 0;\n    }\n\n    *dst_len = s_dst_len;\n\n    return 1;\n}\n\nint\ncipher_ctx_final(mbedtls_cipher_context_t *ctx, uint8_t *dst, int *dst_len)\n{\n    size_t s_dst_len = *dst_len;\n\n    if (!mbed_ok(mbedtls_cipher_finish(ctx, dst, &s_dst_len)))\n    {\n        return 0;\n    }\n\n    *dst_len = s_dst_len;\n\n    return 1;\n}\n\nint\ncipher_ctx_final_check_tag(mbedtls_cipher_context_t *ctx, uint8_t *dst, int *dst_len, uint8_t *tag,\n                           size_t tag_len)\n{\n    size_t olen = 0;\n\n    if (MBEDTLS_DECRYPT != mbedtls_cipher_get_operation(ctx))\n    {\n        return 0;\n    }\n\n    if (tag_len > SIZE_MAX)\n    {\n        return 0;\n    }\n\n    if (!mbed_ok(mbedtls_cipher_finish(ctx, dst, &olen)))\n    {\n        msg(D_CRYPT_ERRORS, \"%s: cipher_ctx_final() failed\", __func__);\n        return 0;\n    }\n\n    if (olen > INT_MAX)\n    {\n        return 0;\n    }\n    *dst_len = olen;\n\n    if (!mbed_ok(mbedtls_cipher_check_tag(ctx, (const unsigned char *)tag, tag_len)))\n    {\n        return 0;\n    }\n\n    return 1;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n/*\n *\n * Generic message digest information functions\n *\n */\n\n\nstatic const mbedtls_md_info_t *\nmd_get(const char *digest)\n{\n    const mbedtls_md_info_t *md = NULL;\n    ASSERT(digest);\n\n    md = mbedtls_md_info_from_string(digest);\n    if (!md)\n    {\n        msg(M_FATAL, \"Message hash algorithm '%s' not found\", digest);\n    }\n    if (mbedtls_md_get_size(md) > MAX_HMAC_KEY_LENGTH)\n    {\n        msg(M_FATAL,\n            \"Message hash algorithm '%s' uses a default hash size (%d bytes) which is larger than \" PACKAGE_NAME\n            \"'s current maximum hash size (%d bytes)\",\n            digest, mbedtls_md_get_size(md), MAX_HMAC_KEY_LENGTH);\n    }\n    return md;\n}\n\nbool\nmd_valid(const char *digest)\n{\n    const mbedtls_md_info_t *md = mbedtls_md_info_from_string(digest);\n    return md != NULL;\n}\n\nconst char *\nmd_kt_name(const char *mdname)\n{\n    if (!strcmp(\"none\", mdname))\n    {\n        return \"[null-digest]\";\n    }\n    const mbedtls_md_info_t *kt = md_get(mdname);\n    return mbedtls_md_get_name(kt);\n}\n\nunsigned char\nmd_kt_size(const char *mdname)\n{\n    if (!strcmp(\"none\", mdname))\n    {\n        return 0;\n    }\n    const mbedtls_md_info_t *kt = md_get(mdname);\n    return mbedtls_md_get_size(kt);\n}\n\n/*\n *\n * Generic message digest functions\n *\n */\n\nbool\nmd_full(const char *mdname, const uint8_t *src, size_t src_len, uint8_t *dst)\n{\n    const mbedtls_md_info_t *kt = md_get(mdname);\n    return 0 == mbedtls_md(kt, src, src_len, dst);\n}\n\nmbedtls_md_context_t *\nmd_ctx_new(void)\n{\n    mbedtls_md_context_t *ctx;\n    ALLOC_OBJ_CLEAR(ctx, mbedtls_md_context_t);\n    return ctx;\n}\n\nvoid\nmd_ctx_free(mbedtls_md_context_t *ctx)\n{\n    free(ctx);\n}\n\nvoid\nmd_ctx_init(mbedtls_md_context_t *ctx, const char *mdname)\n{\n    const mbedtls_md_info_t *kt = md_get(mdname);\n    ASSERT(NULL != ctx && NULL != kt);\n\n    mbedtls_md_init(ctx);\n    ASSERT(0 == mbedtls_md_setup(ctx, kt, 0));\n    ASSERT(0 == mbedtls_md_starts(ctx));\n}\n\nvoid\nmd_ctx_cleanup(mbedtls_md_context_t *ctx)\n{\n    mbedtls_md_free(ctx);\n}\n\nint\nmd_ctx_size(const mbedtls_md_context_t *ctx)\n{\n    if (NULL == ctx)\n    {\n        return 0;\n    }\n    return (int)mbedtls_md_get_size(mbedtls_md_info_from_ctx(ctx));\n}\n\nvoid\nmd_ctx_update(mbedtls_md_context_t *ctx, const uint8_t *src, size_t src_len)\n{\n    ASSERT(0 == mbedtls_md_update(ctx, src, src_len));\n}\n\nvoid\nmd_ctx_final(mbedtls_md_context_t *ctx, uint8_t *dst)\n{\n    ASSERT(0 == mbedtls_md_finish(ctx, dst));\n    mbedtls_md_free(ctx);\n}\n\n\n/*\n *\n * Generic HMAC functions\n *\n */\n\n\n/*\n * TODO: re-enable dmsg for crypto debug\n */\n\nmbedtls_md_context_t *\nhmac_ctx_new(void)\n{\n    mbedtls_md_context_t *ctx;\n    ALLOC_OBJ(ctx, mbedtls_md_context_t);\n    return ctx;\n}\n\nvoid\nhmac_ctx_free(mbedtls_md_context_t *ctx)\n{\n    free(ctx);\n}\n\nvoid\nhmac_ctx_init(mbedtls_md_context_t *ctx, const uint8_t *key, const char *mdname)\n{\n    const mbedtls_md_info_t *kt = md_get(mdname);\n    ASSERT(NULL != kt && NULL != ctx);\n\n    mbedtls_md_init(ctx);\n    int key_len = mbedtls_md_get_size(kt);\n    ASSERT(0 == mbedtls_md_setup(ctx, kt, 1));\n    ASSERT(0 == mbedtls_md_hmac_starts(ctx, key, key_len));\n\n    /* make sure we used a big enough key */\n    ASSERT(mbedtls_md_get_size(kt) <= key_len);\n}\n\nvoid\nhmac_ctx_cleanup(mbedtls_md_context_t *ctx)\n{\n    mbedtls_md_free(ctx);\n}\n\nint\nhmac_ctx_size(mbedtls_md_context_t *ctx)\n{\n    if (NULL == ctx)\n    {\n        return 0;\n    }\n    return mbedtls_md_get_size(mbedtls_md_info_from_ctx(ctx));\n}\n\nvoid\nhmac_ctx_reset(mbedtls_md_context_t *ctx)\n{\n    ASSERT(0 == mbedtls_md_hmac_reset(ctx));\n}\n\nvoid\nhmac_ctx_update(mbedtls_md_context_t *ctx, const uint8_t *src, int src_len)\n{\n    ASSERT(0 == mbedtls_md_hmac_update(ctx, src, src_len));\n}\n\nvoid\nhmac_ctx_final(mbedtls_md_context_t *ctx, uint8_t *dst)\n{\n    ASSERT(0 == mbedtls_md_hmac_finish(ctx, dst));\n}\n\nint\nmemcmp_constant_time(const void *a, const void *b, size_t size)\n{\n    /* mbed TLS has a no const time memcmp function that it exposes\n     * via its APIs like OpenSSL does with CRYPTO_memcmp\n     * Adapt the function that mbedtls itself uses in\n     * mbedtls_safer_memcmp as it considers that to be safe */\n    volatile const unsigned char *A = (volatile const unsigned char *)a;\n    volatile const unsigned char *B = (volatile const unsigned char *)b;\n    volatile unsigned char diff = 0;\n\n    for (size_t i = 0; i < size; i++)\n    {\n        unsigned char x = A[i], y = B[i];\n        diff |= x ^ y;\n    }\n\n    return diff;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\n/*\n * Generate the hash required by for the \\c tls1_PRF function.\n *\n * @param md_kt         Message digest to use\n * @param sec           Secret to base the hash on\n * @param sec_len       Length of the secret\n * @param seed          Seed to hash\n * @param seed_len      Length of the seed\n * @param out           Output buffer\n * @param olen          Length of the output buffer\n */\nstatic void\ntls1_P_hash(const mbedtls_md_info_t *md_kt, const uint8_t *sec, size_t sec_len, const uint8_t *seed,\n            size_t seed_len, uint8_t *out, size_t olen)\n{\n    struct gc_arena gc = gc_new();\n    uint8_t A1[MAX_HMAC_KEY_LENGTH];\n\n#ifdef ENABLE_DEBUG\n    /* used by the D_SHOW_KEY_SOURCE, guarded with ENABLE_DEBUG to avoid unused\n     * variables warnings if compiled with --enable-small */\n    const size_t olen_orig = olen;\n    const uint8_t *out_orig = out;\n#endif\n\n    hmac_ctx_t *ctx = hmac_ctx_new();\n    hmac_ctx_t *ctx_tmp = hmac_ctx_new();\n\n    dmsg(D_SHOW_KEY_SOURCE, \"tls1_P_hash sec: %s\", format_hex(sec, sec_len, 0, &gc));\n    dmsg(D_SHOW_KEY_SOURCE, \"tls1_P_hash seed: %s\", format_hex(seed, seed_len, 0, &gc));\n\n    unsigned int chunk = mbedtls_md_get_size(md_kt);\n    unsigned int A1_len = mbedtls_md_get_size(md_kt);\n\n    /* This is the only place where we init an HMAC with a key that is not\n     * equal to its size, therefore we init the hmac ctx manually here */\n    mbedtls_md_init(ctx);\n    ASSERT(0 == mbedtls_md_setup(ctx, md_kt, 1));\n    ASSERT(0 == mbedtls_md_hmac_starts(ctx, sec, sec_len));\n\n    mbedtls_md_init(ctx_tmp);\n    ASSERT(0 == mbedtls_md_setup(ctx_tmp, md_kt, 1));\n    ASSERT(0 == mbedtls_md_hmac_starts(ctx_tmp, sec, sec_len));\n\n    hmac_ctx_update(ctx, seed, seed_len);\n    hmac_ctx_final(ctx, A1);\n\n    for (;;)\n    {\n        hmac_ctx_reset(ctx);\n        hmac_ctx_reset(ctx_tmp);\n        hmac_ctx_update(ctx, A1, A1_len);\n        hmac_ctx_update(ctx_tmp, A1, A1_len);\n        hmac_ctx_update(ctx, seed, seed_len);\n\n        if (olen > chunk)\n        {\n            hmac_ctx_final(ctx, out);\n            out += chunk;\n            olen -= chunk;\n            hmac_ctx_final(ctx_tmp, A1); /* calc the next A1 value */\n        }\n        else                             /* last one */\n        {\n            hmac_ctx_final(ctx, A1);\n            memcpy(out, A1, olen);\n            break;\n        }\n    }\n    hmac_ctx_cleanup(ctx);\n    hmac_ctx_free(ctx);\n    hmac_ctx_cleanup(ctx_tmp);\n    hmac_ctx_free(ctx_tmp);\n    secure_memzero(A1, sizeof(A1));\n\n    dmsg(D_SHOW_KEY_SOURCE, \"tls1_P_hash out: %s\", format_hex(out_orig, olen_orig, 0, &gc));\n    gc_free(&gc);\n}\n\n/*\n * Use the TLS PRF function for generating data channel keys.\n * This code is based on the OpenSSL library.\n *\n * TLS generates keys as such:\n *\n * master_secret[48] = PRF(pre_master_secret[48], \"master secret\",\n *                         ClientHello.random[32] + ServerHello.random[32])\n *\n * key_block[] = PRF(SecurityParameters.master_secret[48],\n *                 \"key expansion\",\n *                 SecurityParameters.server_random[32] +\n *                 SecurityParameters.client_random[32]);\n *\n * Notes:\n *\n * (1) key_block contains a full set of 4 keys.\n * (2) The pre-master secret is generated by the client.\n */\nbool\nssl_tls1_PRF(const uint8_t *label, size_t label_len, const uint8_t *sec, size_t slen, uint8_t *out1,\n             size_t olen)\n{\n    struct gc_arena gc = gc_new();\n    const md_kt_t *md5 = md_get(\"MD5\");\n    const md_kt_t *sha1 = md_get(\"SHA1\");\n\n    uint8_t *out2 = (uint8_t *)gc_malloc(olen, false, &gc);\n\n    size_t len = slen / 2;\n    const uint8_t *S1 = sec;\n    const uint8_t *S2 = &(sec[len]);\n    len += (slen & 1); /* add for odd, make longer */\n\n    tls1_P_hash(md5, S1, len, label, label_len, out1, olen);\n    tls1_P_hash(sha1, S2, len, label, label_len, out2, olen);\n\n    for (size_t i = 0; i < olen; i++)\n    {\n        out1[i] ^= out2[i];\n    }\n\n    secure_memzero(out2, olen);\n\n    dmsg(D_SHOW_KEY_SOURCE, \"tls1_PRF out[%zu]: %s\", olen, format_hex(out1, olen, 0, &gc));\n\n    gc_free(&gc);\n    return true;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n#endif /* MBEDTLS_VERSION_NUMBER < 0x040000 */\n#endif /* ENABLE_CRYPTO_MBEDTLS */\n"
  },
  {
    "path": "src/openvpn/crypto_mbedtls_legacy.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Data Channel Cryptography mbed TLS-specific backend interface\n */\n\n#ifndef CRYPTO_MBEDTLS_H_\n#define CRYPTO_MBEDTLS_H_\n\n#include <stdbool.h>\n#include <mbedtls/cipher.h>\n#include <mbedtls/md.h>\n#include <mbedtls/ctr_drbg.h>\n\n/** Generic message digest key type %context. */\ntypedef mbedtls_md_info_t md_kt_t;\n\n/** Generic cipher %context. */\ntypedef mbedtls_cipher_context_t cipher_ctx_t;\n\n/** Generic message digest %context. */\ntypedef mbedtls_md_context_t md_ctx_t;\n\n/** Generic HMAC %context. */\ntypedef mbedtls_md_context_t hmac_ctx_t;\n\n/* Use a dummy type for the provider */\ntypedef void provider_t;\n\n/** Maximum length of an IV */\n#define OPENVPN_MAX_IV_LENGTH MBEDTLS_MAX_IV_LENGTH\n\n/** Cipher is in CBC mode */\n#define OPENVPN_MODE_CBC MBEDTLS_MODE_CBC\n\n/** Cipher is in OFB mode */\n#define OPENVPN_MODE_OFB MBEDTLS_MODE_OFB\n\n/** Cipher is in CFB mode */\n#define OPENVPN_MODE_CFB MBEDTLS_MODE_CFB\n\n/** Cipher is in GCM mode */\n#define OPENVPN_MODE_GCM MBEDTLS_MODE_GCM\n\ntypedef mbedtls_operation_t crypto_operation_t;\n\n/** Cipher should encrypt */\n#define OPENVPN_OP_ENCRYPT MBEDTLS_ENCRYPT\n\n/** Cipher should decrypt */\n#define OPENVPN_OP_DECRYPT MBEDTLS_DECRYPT\n\n#define MD4_DIGEST_LENGTH    16\n#define MD5_DIGEST_LENGTH    16\n#define SHA_DIGEST_LENGTH    20\n#define SHA256_DIGEST_LENGTH 32\n\n/**\n * Returns a singleton instance of the mbed TLS random number generator.\n *\n * For PolarSSL/mbed TLS 1.1+, this is the CTR_DRBG random number generator. If it\n * hasn't been initialised yet, the RNG will be initialised using the default\n * entropy sources. Aside from the default platform entropy sources, an\n * additional entropy source, the HAVEGE random number generator will also be\n * added. During initialisation, a personalisation string will be added based\n * on the time, the PID, and a pointer to the random context.\n */\nmbedtls_ctr_drbg_context *rand_ctx_get(void);\n\n/**\n * Log the supplied mbed TLS error, prefixed by supplied prefix.\n *\n * @param flags         Flags to indicate error type and priority.\n * @param errval        mbed TLS error code to convert to error message.\n * @param prefix        Prefix to mbed TLS error message.\n *\n * @returns true if no errors are detected, false otherwise.\n */\nbool mbed_log_err(unsigned int flags, int errval, const char *prefix);\n\n/**\n * Log the supplied mbed TLS error, prefixed by function name and line number.\n *\n * @param flags         Flags to indicate error type and priority.\n * @param errval        mbed TLS error code to convert to error message.\n * @param func          Function name where error was reported.\n * @param line          Line number where error was reported.\n *\n * @returns true if no errors are detected, false otherwise.\n */\nbool mbed_log_func_line(unsigned int flags, int errval, const char *func, int line);\n\n/** Wraps mbed_log_func_line() to prevent function calls for non-errors */\nstatic inline bool\nmbed_log_func_line_lite(unsigned int flags, int errval, const char *func, int line)\n{\n    if (errval)\n    {\n        return mbed_log_func_line(flags, errval, func, line);\n    }\n    return true;\n}\n\n/**\n * Check errval and log on error.\n *\n * Convenience wrapper to put around mbed TLS library calls, e.g.\n *   if (!mbed_ok (mbedtls_ssl_func())) return 0;\n * or\n *   ASSERT (mbed_ok (mbedtls_ssl_func()));\n *\n * @param errval        mbed TLS error code to convert to error message.\n *\n * @returns true if no errors are detected, false otherwise.\n */\n#define mbed_ok(errval) mbed_log_func_line_lite(D_CRYPT_ERRORS, errval, __func__, __LINE__)\n\n#endif /* CRYPTO_MBEDTLS_H_ */\n"
  },
  {
    "path": "src/openvpn/crypto_openssl.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Data Channel Cryptography OpenSSL-specific backend interface\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if defined(ENABLE_CRYPTO_OPENSSL)\n\n#include \"basic.h\"\n#include \"buffer.h\"\n#include \"integer.h\"\n#include \"crypto.h\"\n#include \"crypto_backend.h\"\n#include \"openssl_compat.h\"\n\n#include <openssl/conf.h>\n#include <openssl/des.h>\n#include <openssl/err.h>\n#include <openssl/evp.h>\n#include <openssl/objects.h>\n#include <openssl/rand.h>\n#include <openssl/ssl.h>\n\n#if !defined(LIBRESSL_VERSION_NUMBER)\n#include <openssl/kdf.h>\n#endif\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\n#include <openssl/provider.h>\n#include <openssl/core_names.h>\n#endif\n\n#if defined(_WIN32) && defined(OPENSSL_NO_EC)\n#error Windows build with OPENSSL_NO_EC: disabling EC key is not supported.\n#endif\n\n#ifdef _MSC_VER\n/* mute ossl3 deprecation warnings treated as errors in msvc */\n#pragma warning(disable : 4996)\n#endif\n\n/*\n * Check for key size creepage.\n */\n\n#if MAX_CIPHER_KEY_LENGTH < EVP_MAX_KEY_LENGTH\n#warning Some OpenSSL EVP ciphers now support key lengths greater than MAX_CIPHER_KEY_LENGTH -- consider increasing MAX_CIPHER_KEY_LENGTH\n#endif\n\n#if MAX_HMAC_KEY_LENGTH < EVP_MAX_MD_SIZE\n#warning Some OpenSSL HMAC message digests now support key lengths greater than MAX_HMAC_KEY_LENGTH -- consider increasing MAX_HMAC_KEY_LENGTH\n#endif\n\n#if HAVE_OPENSSL_ENGINE\n#include <openssl/ui.h>\n#include <openssl/engine.h>\n\nstatic bool engine_initialized = false; /* GLOBAL */\n\nstatic ENGINE *engine_persist = NULL;   /* GLOBAL */\n\n/* Try to load an engine in a shareable library */\nstatic ENGINE *\ntry_load_engine(const char *engine)\n{\n    ENGINE *e = ENGINE_by_id(\"dynamic\");\n    if (e)\n    {\n        if (!ENGINE_ctrl_cmd_string(e, \"SO_PATH\", engine, 0)\n            || !ENGINE_ctrl_cmd_string(e, \"LOAD\", NULL, 0))\n        {\n            ENGINE_free(e);\n            e = NULL;\n        }\n    }\n    return e;\n}\n\nstatic ENGINE *\nsetup_engine(const char *engine)\n{\n    ENGINE *e = NULL;\n\n    ENGINE_load_builtin_engines();\n\n    if (engine)\n    {\n        if (strcmp(engine, \"auto\") == 0)\n        {\n            msg(M_INFO, \"Initializing OpenSSL auto engine support\");\n            ENGINE_register_all_complete();\n            return NULL;\n        }\n        if ((e = ENGINE_by_id(engine)) == NULL && (e = try_load_engine(engine)) == NULL)\n        {\n            crypto_msg(M_FATAL, \"OpenSSL error: cannot load engine '%s'\", engine);\n        }\n\n        if (!ENGINE_set_default(e, ENGINE_METHOD_ALL))\n        {\n            crypto_msg(M_FATAL, \"OpenSSL error: ENGINE_set_default failed on engine '%s'\", engine);\n        }\n\n        msg(M_INFO, \"Initializing OpenSSL support for engine '%s'\", ENGINE_get_id(e));\n    }\n    return e;\n}\n\n#endif /* HAVE_OPENSSL_ENGINE */\n\nvoid\ncrypto_init_lib_engine(const char *engine_name)\n{\n#if HAVE_OPENSSL_ENGINE\n    if (!engine_initialized)\n    {\n        ASSERT(engine_name);\n        ASSERT(!engine_persist);\n        engine_persist = setup_engine(engine_name);\n        engine_initialized = true;\n    }\n#else /* if HAVE_OPENSSL_ENGINE */\n    msg(M_WARN, \"Note: OpenSSL hardware crypto engine functionality is not available\");\n#endif\n}\n\nprovider_t *\ncrypto_load_provider(const char *provider)\n{\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\n    /* Load providers into the default (NULL) library context */\n    OSSL_PROVIDER *prov = OSSL_PROVIDER_load(NULL, provider);\n    if (!prov)\n    {\n        crypto_msg(M_FATAL, \"failed to load provider '%s'\", provider);\n    }\n    return prov;\n#else /* OPENSSL_VERSION_NUMBER >= 0x30000000L */\n    msg(M_WARN, \"Note: OpenSSL provider functionality is not available\");\n    return NULL;\n#endif\n}\n\nvoid\ncrypto_unload_provider(const char *provname, provider_t *provider)\n{\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\n    if (!OSSL_PROVIDER_unload(provider))\n    {\n        crypto_msg(M_FATAL, \"failed to unload provider '%s'\", provname);\n    }\n#endif\n}\n\n/*\n *\n * Functions related to the core crypto library\n *\n */\n\nvoid\ncrypto_init_lib(void)\n{\n    OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL);\n    /*\n     * If you build the OpenSSL library and OpenVPN with\n     * CRYPTO_MDEBUG, you will get a listing of OpenSSL\n     * memory leaks on program termination.\n     */\n\n#ifdef CRYPTO_MDEBUG\n    CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);\n#endif\n}\n\nvoid\ncrypto_uninit_lib(void)\n{\n#ifdef CRYPTO_MDEBUG\n    FILE *fp = fopen(\"sdlog\", \"w\");\n    ASSERT(fp);\n    CRYPTO_mem_leaks_fp(fp);\n    fclose(fp);\n#endif\n\n#if HAVE_OPENSSL_ENGINE\n    if (engine_initialized)\n    {\n        ENGINE_cleanup();\n        engine_persist = NULL;\n        engine_initialized = false;\n    }\n#endif\n}\n\nvoid\ncrypto_clear_error(void)\n{\n    ERR_clear_error();\n}\n\nvoid\ncrypto_print_openssl_errors(const unsigned int flags)\n{\n    unsigned long err = 0;\n    int line, errflags;\n    const char *file, *data, *func;\n\n    while ((err = ERR_get_error_all(&file, &line, &func, &data, &errflags)) != 0)\n    {\n        if (!(errflags & ERR_TXT_STRING))\n        {\n            data = \"\";\n        }\n\n        /* Be more clear about frequently occurring \"no shared cipher\" error */\n        if (ERR_GET_REASON(err) == SSL_R_NO_SHARED_CIPHER)\n        {\n            msg(D_CRYPT_ERRORS, \"TLS error: The server has no TLS ciphersuites \"\n                                \"in common with the client. Your --tls-cipher setting might be \"\n                                \"too restrictive.\");\n        }\n        else if (ERR_GET_REASON(err) == SSL_R_UNSUPPORTED_PROTOCOL)\n        {\n            msg(D_CRYPT_ERRORS,\n                \"TLS error: Unsupported protocol. This typically \"\n                \"indicates that client and server have no common TLS version enabled. \"\n                \"This can be caused by mismatched tls-version-min and tls-version-max \"\n                \"options on client and server. \"\n                \"If your OpenVPN client is between v2.3.6 and v2.3.2 try adding \"\n                \"tls-version-min 1.0 to the client configuration to use TLS 1.0+ \"\n                \"instead of TLS 1.0 only\");\n        }\n\n        /* print file and line if verb >=8 */\n        if (!check_debug_level(D_TLS_DEBUG_MED))\n        {\n            msg(flags, \"OpenSSL: %s:%s\", ERR_error_string(err, NULL), data);\n        }\n        else\n        {\n            msg(flags, \"OpenSSL: %s:%s:%s:%d:%s\", ERR_error_string(err, NULL), data, file, line,\n                func);\n        }\n    }\n}\n\n\n/*\n *\n * OpenSSL memory debugging.  If dmalloc debugging is enabled, tell\n * OpenSSL to use our private malloc/realloc/free functions so that\n * we can dispatch them to dmalloc.\n *\n */\n\n#ifdef DMALLOC\nstatic void *\ncrypto_malloc(size_t size, const char *file, int line)\n{\n    return dmalloc_malloc(file, line, size, DMALLOC_FUNC_MALLOC, 0, 0);\n}\n\nstatic void *\ncrypto_realloc(void *ptr, size_t size, const char *file, int line)\n{\n    return dmalloc_realloc(file, line, ptr, size, DMALLOC_FUNC_REALLOC, 0);\n}\n\nstatic void\ncrypto_free(void *ptr)\n{\n    dmalloc_free(__FILE__, __LINE__, ptr, DMALLOC_FUNC_FREE);\n}\n\nvoid\ncrypto_init_dmalloc(void)\n{\n    CRYPTO_set_mem_ex_functions(crypto_malloc, crypto_realloc, crypto_free);\n}\n#endif /* DMALLOC */\n\nconst cipher_name_pair cipher_name_translation_table[] = {\n    { \"AES-128-GCM\", \"id-aes128-GCM\" },\n    { \"AES-192-GCM\", \"id-aes192-GCM\" },\n    { \"AES-256-GCM\", \"id-aes256-GCM\" },\n    { \"CHACHA20-POLY1305\", \"ChaCha20-Poly1305\" },\n};\nconst size_t cipher_name_translation_table_count =\n    sizeof(cipher_name_translation_table) / sizeof(*cipher_name_translation_table);\n\n\nstatic int\ncipher_name_cmp(const void *a, const void *b)\n{\n    const EVP_CIPHER *const *cipher_a = a;\n    const EVP_CIPHER *const *cipher_b = b;\n\n    return strcmp(EVP_CIPHER_get0_name(*cipher_a), EVP_CIPHER_get0_name(*cipher_b));\n}\n\nstruct collect_ciphers\n{\n    /* If we ever exceed this, we must be more selective */\n    const EVP_CIPHER *list[1000];\n    size_t num;\n};\n\nstatic void\ncollect_ciphers(EVP_CIPHER *cipher, void *list)\n{\n    if (!cipher)\n    {\n        return;\n    }\n    struct collect_ciphers *cipher_list = list;\n    if (cipher_list->num == SIZE(cipher_list->list))\n    {\n        msg(M_WARN, \"WARNING: Too many ciphers, not showing all\");\n        return;\n    }\n\n    const char *ciphername = EVP_CIPHER_get0_name(cipher);\n\n    if (ciphername\n        && (cipher_kt_mode_cbc(ciphername)\n#ifdef ENABLE_OFB_CFB_MODE\n            || cipher_kt_mode_ofb_cfb(ciphername)\n#endif\n            || cipher_kt_mode_aead(ciphername)))\n    {\n        cipher_list->list[cipher_list->num++] = cipher;\n    }\n}\n\nvoid\nshow_available_ciphers(void)\n{\n    struct collect_ciphers cipher_list = { 0 };\n\n#ifndef ENABLE_SMALL\n    printf(\"The following ciphers and cipher modes are available for use\\n\"\n           \"with \" PACKAGE_NAME \".  Each cipher shown below may be used as a\\n\"\n           \"parameter to the --data-ciphers (or --cipher) option. In static \\n\"\n           \"key mode only CBC mode is allowed.\\n\");\n    printf(\"See also openssl list -cipher-algorithms\\n\\n\");\n#endif\n\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\n    EVP_CIPHER_do_all_provided(NULL, collect_ciphers, &cipher_list);\n#else\n    for (int nid = 0; nid < 10000; ++nid)\n    {\n#if defined(LIBRESSL_VERSION_NUMBER)\n        /* OpenBSD/LibreSSL reimplemented EVP_get_cipherbyname and broke\n         * calling EVP_get_cipherbynid with an invalid nid in the process\n         * so that it would segfault. */\n        const EVP_CIPHER *cipher = NULL;\n        const char *name = OBJ_nid2sn(nid);\n        if (name)\n        {\n            cipher = EVP_get_cipherbyname(name);\n        }\n#else /* if defined(LIBRESSL_VERSION_NUMBER) */\n        const EVP_CIPHER *cipher = EVP_get_cipherbynid(nid);\n#endif\n        /* We cast the const away so we can keep the function prototype\n         * compatible with EVP_CIPHER_do_all_provided */\n        collect_ciphers((EVP_CIPHER *)cipher, &cipher_list);\n    }\n#endif\n\n    /* cast to non-const to prevent warning */\n    qsort((EVP_CIPHER *)cipher_list.list, cipher_list.num, sizeof(*cipher_list.list),\n          cipher_name_cmp);\n\n    for (size_t i = 0; i < cipher_list.num; i++)\n    {\n        if (!cipher_kt_insecure(EVP_CIPHER_get0_name(cipher_list.list[i])))\n        {\n            print_cipher(EVP_CIPHER_get0_name(cipher_list.list[i]));\n        }\n    }\n\n    printf(\"\\nThe following ciphers have a block size of less than 128 bits, \\n\"\n           \"and are therefore deprecated.  Do not use unless you have to.\\n\\n\");\n    for (size_t i = 0; i < cipher_list.num; i++)\n    {\n        if (cipher_kt_insecure(EVP_CIPHER_get0_name(cipher_list.list[i])))\n        {\n            print_cipher(EVP_CIPHER_get0_name(cipher_list.list[i]));\n        }\n    }\n    printf(\"\\n\");\n}\n\nvoid\nprint_digest(EVP_MD *digest, void *unused)\n{\n    printf(\"%s %d bit digest size\\n\", md_kt_name(EVP_MD_get0_name(digest)),\n           EVP_MD_size(digest) * 8);\n}\n\nvoid\nshow_available_digests(void)\n{\n#ifndef ENABLE_SMALL\n    printf(\"The following message digests are available for use with\\n\" PACKAGE_NAME\n           \".  A message digest is used in conjunction with\\n\"\n           \"the HMAC function, to authenticate received packets.\\n\"\n           \"You can specify a message digest as parameter to\\n\"\n           \"the --auth option.\\n\");\n    printf(\"See also openssl list -digest-algorithms\\n\\n\");\n#endif\n\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\n    EVP_MD_do_all_provided(NULL, print_digest, NULL);\n#else\n    for (int nid = 0; nid < 10000; ++nid)\n    {\n        /* OpenBSD/LibreSSL reimplemented EVP_get_digestbyname and broke\n         * calling EVP_get_digestbynid with an invalid nid in the process\n         * so that it would segfault. */\n#ifdef LIBRESSL_VERSION_NUMBER\n        const EVP_MD *digest = NULL;\n        const char *name = OBJ_nid2sn(nid);\n        if (name)\n        {\n            digest = EVP_get_digestbyname(name);\n        }\n#else  /* ifdef LIBRESSL_VERSION_NUMBER */\n        const EVP_MD *digest = EVP_get_digestbynid(nid);\n#endif\n        if (digest)\n        {\n            /* We cast the const away so we can keep the function prototype\n             * compatible with EVP_MD_do_all_provided */\n            print_digest((EVP_MD *)digest, NULL);\n        }\n    }\n#endif /* if OPENSSL_VERSION_NUMBER >= 0x30000000L */\n    printf(\"\\n\");\n}\n\nvoid\nshow_available_engines(void)\n{\n#if HAVE_OPENSSL_ENGINE /* Only defined for OpenSSL */\n    ENGINE *e;\n\n    printf(\"OpenSSL Crypto Engines\\n\\n\");\n\n    ENGINE_load_builtin_engines();\n\n    e = ENGINE_get_first();\n    while (e)\n    {\n        printf(\"%s [%s]\\n\", ENGINE_get_name(e), ENGINE_get_id(e));\n        e = ENGINE_get_next(e);\n    }\n    ENGINE_cleanup();\n#else /* if HAVE_OPENSSL_ENGINE */\n    printf(\"Sorry, OpenSSL hardware crypto engine functionality is not available.\\n\");\n#endif\n}\n\n\nbool\ncrypto_pem_encode(const char *name, struct buffer *dst, const struct buffer *src,\n                  struct gc_arena *gc)\n{\n    bool ret = false;\n    BIO *bio = BIO_new(BIO_s_mem());\n    if (!bio || !PEM_write_bio(bio, name, \"\", BPTR(src), BLEN(src)))\n    {\n        ret = false;\n        goto cleanup;\n    }\n\n    BUF_MEM *bptr;\n    BIO_get_mem_ptr(bio, &bptr);\n\n    *dst = alloc_buf_gc(bptr->length, gc);\n    ASSERT(buf_write(dst, bptr->data, bptr->length));\n\n    ret = true;\ncleanup:\n    if (!BIO_free(bio))\n    {\n        ret = false;\n    }\n\n    return ret;\n}\n\nbool\ncrypto_pem_decode(const char *name, struct buffer *dst, const struct buffer *src)\n{\n    bool ret = false;\n\n    BIO *bio = BIO_new_mem_buf((char *)BPTR(src), BLEN(src));\n    if (!bio)\n    {\n        crypto_msg(M_FATAL, \"Cannot open memory BIO for PEM decode\");\n    }\n\n    char *name_read = NULL;\n    char *header_read = NULL;\n    uint8_t *data_read = NULL;\n    long data_read_len = 0;\n    if (!PEM_read_bio(bio, &name_read, &header_read, &data_read, &data_read_len))\n    {\n        dmsg(D_CRYPT_ERRORS, \"%s: PEM decode failed\", __func__);\n        goto cleanup;\n    }\n\n    if (strcmp(name, name_read))\n    {\n        dmsg(D_CRYPT_ERRORS, \"%s: unexpected PEM name (got '%s', expected '%s')\", __func__,\n             name_read, name);\n        goto cleanup;\n    }\n\n    uint8_t *dst_data = buf_write_alloc(dst, data_read_len);\n    if (!dst_data)\n    {\n        dmsg(D_CRYPT_ERRORS, \"%s: dst too small (%i, needs %li)\", __func__, BCAP(dst),\n             data_read_len);\n        goto cleanup;\n    }\n    memcpy(dst_data, data_read, data_read_len);\n\n    ret = true;\ncleanup:\n    OPENSSL_free(name_read);\n    OPENSSL_free(header_read);\n    OPENSSL_free(data_read);\n    if (!BIO_free(bio))\n    {\n        ret = false;\n    }\n\n    return ret;\n}\n\n/*\n *\n * Random number functions, used in cases where we want\n * reasonably strong cryptographic random number generation\n * without depleting our entropy pool.  Used for random\n * IV values and a number of other miscellaneous tasks.\n *\n */\n\nint\nrand_bytes(uint8_t *output, int len)\n{\n    if (unlikely(1 != RAND_bytes(output, len)))\n    {\n        crypto_msg(D_CRYPT_ERRORS, \"RAND_bytes() failed\");\n        return 0;\n    }\n    return 1;\n}\n\n/*\n *\n * Generic cipher key type functions\n *\n */\n\nstatic evp_cipher_type *\ncipher_get(const char *ciphername)\n{\n    ASSERT(ciphername);\n\n    ciphername = translate_cipher_name_from_openvpn(ciphername);\n    return EVP_CIPHER_fetch(NULL, ciphername, NULL);\n}\n\nbool\ncipher_valid_reason(const char *ciphername, const char **reason)\n{\n    bool ret = false;\n    evp_cipher_type *cipher = cipher_get(ciphername);\n    if (!cipher)\n    {\n        crypto_msg(D_LOW, \"Cipher algorithm '%s' not found\", ciphername);\n        *reason = \"disabled because unknown\";\n        goto out;\n    }\n\n#ifdef OPENSSL_FIPS\n    /* Rhel 8/CentOS 8 have a patched OpenSSL version that return a cipher\n     * here that is actually not usable if in FIPS mode */\n\n    if (FIPS_mode() && !(EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_FIPS))\n    {\n        msg(D_LOW,\n            \"Cipher algorithm '%s' is known by OpenSSL library but \"\n            \"currently disabled by running in FIPS mode.\",\n            ciphername);\n        *reason = \"disabled by FIPS mode\";\n        goto out;\n    }\n#endif\n    if (EVP_CIPHER_key_length(cipher) > MAX_CIPHER_KEY_LENGTH)\n    {\n        msg(D_LOW,\n            \"Cipher algorithm '%s' uses a default key size (%d bytes) \"\n            \"which is larger than \" PACKAGE_NAME \"'s current maximum key size \"\n            \"(%d bytes)\",\n            ciphername, EVP_CIPHER_key_length(cipher), MAX_CIPHER_KEY_LENGTH);\n        *reason = \"disabled due to key size too large\";\n        goto out;\n    }\n\n    ret = true;\n    *reason = NULL;\nout:\n    EVP_CIPHER_free(cipher);\n    return ret;\n}\n\nconst char *\ncipher_kt_name(const char *ciphername)\n{\n    ASSERT(ciphername);\n    if (strcmp(\"none\", ciphername) == 0)\n    {\n        return \"[null-cipher]\";\n    }\n\n    evp_cipher_type *cipher_kt = cipher_get(ciphername);\n    if (!cipher_kt)\n    {\n        return NULL;\n    }\n\n    const char *name = EVP_CIPHER_name(cipher_kt);\n    EVP_CIPHER_free(cipher_kt);\n    return translate_cipher_name_to_openvpn(name);\n}\n\nunsigned int\ncipher_kt_key_size(const char *ciphername)\n{\n    evp_cipher_type *cipher = cipher_get(ciphername);\n    int size = EVP_CIPHER_key_length(cipher);\n    ASSERT(size >= 0);\n    EVP_CIPHER_free(cipher);\n    return size;\n}\n\nunsigned int\ncipher_kt_iv_size(const char *ciphername)\n{\n    evp_cipher_type *cipher = cipher_get(ciphername);\n    int ivsize = EVP_CIPHER_iv_length(cipher);\n    ASSERT(ivsize >= 0);\n    EVP_CIPHER_free(cipher);\n    return ivsize;\n}\n\nunsigned int\ncipher_kt_block_size(const char *ciphername)\n{\n    /*\n     * OpenSSL reports OFB/CFB/GCM cipher block sizes as '1 byte'.  To work\n     * around that, try to replace the mode with 'CBC' and return the block size\n     * reported for that cipher, if possible.  If that doesn't work, just return\n     * the value reported by OpenSSL.\n     */\n    char *name = NULL;\n    char *mode_str = NULL;\n    const char *orig_name = NULL;\n    evp_cipher_type *cbc_cipher = NULL;\n    evp_cipher_type *cipher = cipher_get(ciphername);\n    if (!cipher)\n    {\n        return 0;\n    }\n\n    int block_size = EVP_CIPHER_block_size(cipher);\n\n    orig_name = EVP_CIPHER_name(cipher);\n    if (!orig_name)\n    {\n        goto cleanup;\n    }\n\n    name = string_alloc(translate_cipher_name_to_openvpn(orig_name), NULL);\n    mode_str = strrchr(name, '-');\n    if (!mode_str || strlen(mode_str) < 4)\n    {\n        goto cleanup;\n    }\n\n    strcpy(mode_str, \"-CBC\");\n\n    cbc_cipher = EVP_CIPHER_fetch(NULL, translate_cipher_name_from_openvpn(name), NULL);\n    if (cbc_cipher)\n    {\n        block_size = EVP_CIPHER_block_size(cbc_cipher);\n    }\n\ncleanup:\n    EVP_CIPHER_free(cbc_cipher);\n    EVP_CIPHER_free(cipher);\n    free(name);\n    ASSERT(block_size >= 0);\n    return block_size;\n}\n\nunsigned int\ncipher_kt_tag_size(const char *ciphername)\n{\n    if (cipher_kt_mode_aead(ciphername))\n    {\n        return OPENVPN_AEAD_TAG_LENGTH;\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nbool\ncipher_kt_insecure(const char *ciphername)\n{\n    if (cipher_kt_block_size(ciphername) >= 128 / 8)\n    {\n        return false;\n    }\n#ifdef NID_chacha20_poly1305\n    evp_cipher_type *cipher = cipher_get(ciphername);\n    if (cipher)\n    {\n        bool ischachapoly = (EVP_CIPHER_nid(cipher) == NID_chacha20_poly1305);\n        EVP_CIPHER_free(cipher);\n        if (ischachapoly)\n        {\n            return false;\n        }\n    }\n#endif\n    return true;\n}\n\nint\ncipher_kt_mode(const EVP_CIPHER *cipher_kt)\n{\n    ASSERT(NULL != cipher_kt);\n    return EVP_CIPHER_mode(cipher_kt);\n}\n\nbool\ncipher_kt_mode_cbc(const char *ciphername)\n{\n    evp_cipher_type *cipher = cipher_get(ciphername);\n\n    bool ret = cipher\n               && (cipher_kt_mode(cipher) == OPENVPN_MODE_CBC\n    /* Exclude AEAD cipher modes, they require a different API */\n#ifdef EVP_CIPH_FLAG_CTS\n                   && !(EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_CTS)\n#endif\n                   && !(EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_AEAD_CIPHER)\n                   && !(EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_ENC_THEN_MAC));\n    EVP_CIPHER_free(cipher);\n    return ret;\n}\n\nbool\ncipher_kt_mode_ofb_cfb(const char *ciphername)\n{\n    evp_cipher_type *cipher = cipher_get(ciphername);\n    bool ofb_cfb = cipher\n                   && (cipher_kt_mode(cipher) == OPENVPN_MODE_OFB\n                       || cipher_kt_mode(cipher) == OPENVPN_MODE_CFB)\n                   /* Exclude AEAD cipher modes, they require a different API */\n                   && !(EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_AEAD_CIPHER);\n    EVP_CIPHER_free(cipher);\n    return ofb_cfb;\n}\n\nbool\ncipher_kt_mode_aead(const char *ciphername)\n{\n    bool isaead = false;\n\n    evp_cipher_type *cipher = cipher_get(ciphername);\n    if (cipher)\n    {\n        if (EVP_CIPHER_mode(cipher) == OPENVPN_MODE_GCM)\n        {\n            isaead = true;\n        }\n\n#ifdef NID_chacha20_poly1305\n        if (EVP_CIPHER_nid(cipher) == NID_chacha20_poly1305)\n        {\n            isaead = true;\n        }\n#endif\n    }\n\n    EVP_CIPHER_free(cipher);\n\n    return isaead;\n}\n\n/*\n *\n * Generic cipher context functions\n *\n */\n\ncipher_ctx_t *\ncipher_ctx_new(void)\n{\n    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();\n    check_malloc_return(ctx);\n    return ctx;\n}\n\nvoid\ncipher_ctx_free(EVP_CIPHER_CTX *ctx)\n{\n    EVP_CIPHER_CTX_free(ctx);\n}\n\nvoid\ncipher_ctx_init(EVP_CIPHER_CTX *ctx, const uint8_t *key, const char *ciphername,\n                crypto_operation_t enc)\n{\n    ASSERT(NULL != ciphername && NULL != ctx);\n    evp_cipher_type *kt = cipher_get(ciphername);\n\n    EVP_CIPHER_CTX_reset(ctx);\n    if (!EVP_CipherInit_ex(ctx, kt, NULL, key, NULL, enc))\n    {\n        crypto_msg(M_FATAL, \"EVP cipher init #2\");\n    }\n\n    /* make sure we used a big enough key */\n    ASSERT(EVP_CIPHER_CTX_key_length(ctx) <= EVP_CIPHER_key_length(kt));\n    EVP_CIPHER_free(kt);\n}\n\nunsigned int\ncipher_ctx_iv_length(const EVP_CIPHER_CTX *ctx)\n{\n    return EVP_CIPHER_CTX_iv_length(ctx);\n}\n\nint\ncipher_ctx_get_tag(EVP_CIPHER_CTX *ctx, uint8_t *tag_buf, int tag_size)\n{\n    return EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, tag_size, tag_buf);\n}\n\nunsigned int\ncipher_ctx_block_size(const EVP_CIPHER_CTX *ctx)\n{\n    return EVP_CIPHER_CTX_block_size(ctx);\n}\n\nint\ncipher_ctx_mode(const EVP_CIPHER_CTX *ctx)\n{\n    return EVP_CIPHER_CTX_mode(ctx);\n}\n\nbool\ncipher_ctx_mode_cbc(const cipher_ctx_t *ctx)\n{\n    if (!ctx)\n    {\n        return false;\n    }\n\n    unsigned long flags = EVP_CIPHER_CTX_flags(ctx);\n    int mode = EVP_CIPHER_CTX_mode(ctx);\n\n    return mode == EVP_CIPH_CBC_MODE\n    /* Exclude AEAD cipher modes, they require a different API */\n#ifdef EVP_CIPH_FLAG_CTS\n           && !(flags & EVP_CIPH_FLAG_CTS)\n#endif\n           && !(flags & EVP_CIPH_FLAG_AEAD_CIPHER);\n}\n\nbool\ncipher_ctx_mode_ofb_cfb(const cipher_ctx_t *ctx)\n{\n    if (!ctx)\n    {\n        return false;\n    }\n\n    int mode = EVP_CIPHER_CTX_get_mode(ctx);\n\n    return (mode == EVP_CIPH_OFB_MODE || mode == EVP_CIPH_CFB_MODE)\n           /* Exclude AEAD cipher modes, they require a different API */\n           && !(EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER);\n}\n\nbool\ncipher_ctx_mode_aead(const cipher_ctx_t *ctx)\n{\n    if (ctx)\n    {\n        unsigned long flags = EVP_CIPHER_CTX_flags(ctx);\n        if (flags & EVP_CIPH_FLAG_AEAD_CIPHER)\n        {\n            return true;\n        }\n\n#if defined(NID_chacha20_poly1305) && OPENSSL_VERSION_NUMBER < 0x30000000L\n        if (EVP_CIPHER_CTX_nid(ctx) == NID_chacha20_poly1305)\n        {\n            return true;\n        }\n#endif\n    }\n\n    return false;\n}\n\n\nint\ncipher_ctx_reset(EVP_CIPHER_CTX *ctx, const uint8_t *iv_buf)\n{\n    return EVP_CipherInit_ex(ctx, NULL, NULL, NULL, iv_buf, -1);\n}\n\nint\ncipher_ctx_update_ad(EVP_CIPHER_CTX *ctx, const uint8_t *src, int src_len)\n{\n    int len;\n    if (!EVP_CipherUpdate(ctx, NULL, &len, src, src_len))\n    {\n        crypto_msg(M_FATAL, \"%s: EVP_CipherUpdate() failed\", __func__);\n    }\n    return 1;\n}\n\nint\ncipher_ctx_update(EVP_CIPHER_CTX *ctx, uint8_t *dst, int *dst_len, uint8_t *src, int src_len)\n{\n    if (!EVP_CipherUpdate(ctx, dst, dst_len, src, src_len))\n    {\n        crypto_msg(M_FATAL, \"%s: EVP_CipherUpdate() failed\", __func__);\n    }\n    return 1;\n}\n\nint\ncipher_ctx_final(EVP_CIPHER_CTX *ctx, uint8_t *dst, int *dst_len)\n{\n    return EVP_CipherFinal(ctx, dst, dst_len);\n}\n\nint\ncipher_ctx_final_check_tag(EVP_CIPHER_CTX *ctx, uint8_t *dst, int *dst_len, uint8_t *tag,\n                           size_t tag_len)\n{\n    ASSERT(tag_len < INT_MAX);\n    if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, (int)tag_len, tag))\n    {\n        return 0;\n    }\n\n    return cipher_ctx_final(ctx, dst, dst_len);\n}\n\n/*\n *\n * Generic message digest information functions\n *\n */\n\n\nstatic evp_md_type *\nmd_get(const char *digest)\n{\n    evp_md_type *md = NULL;\n    ASSERT(digest);\n    md = EVP_MD_fetch(NULL, digest, NULL);\n    if (!md)\n    {\n        crypto_msg(M_FATAL, \"Message hash algorithm '%s' not found\", digest);\n    }\n    if (EVP_MD_size(md) > MAX_HMAC_KEY_LENGTH)\n    {\n        crypto_msg(M_FATAL,\n                   \"Message hash algorithm '%s' uses a default hash \"\n                   \"size (%d bytes) which is larger than \" PACKAGE_NAME \"'s current \"\n                   \"maximum hash size (%d bytes)\",\n                   digest, EVP_MD_size(md), MAX_HMAC_KEY_LENGTH);\n    }\n    return md;\n}\n\n\nbool\nmd_valid(const char *digest)\n{\n    evp_md_type *md = EVP_MD_fetch(NULL, digest, NULL);\n    bool valid = (md != NULL);\n    EVP_MD_free(md);\n    return valid;\n}\n\n\n/* Since we used the OpenSSL <=1.1 names as part of our OCC message, they\n * are now unfortunately part of our wire protocol.\n *\n * OpenSSL 3.0 will still accept the \"old\" names so we do not need to use\n * this translation table for forward lookup, only for returning the name\n * with md_kt_name() */\nconst cipher_name_pair digest_name_translation_table[] = {\n    { \"BLAKE2s256\", \"BLAKE2S-256\" },\n    { \"BLAKE2b512\", \"BLAKE2B-512\" },\n    { \"RIPEMD160\", \"RIPEMD-160\" },\n    { \"SHA224\", \"SHA2-224\" },\n    { \"SHA256\", \"SHA2-256\" },\n    { \"SHA384\", \"SHA2-384\" },\n    { \"SHA512\", \"SHA2-512\" },\n    { \"SHA512-224\", \"SHA2-512/224\" },\n    { \"SHA512-256\", \"SHA2-512/256\" },\n    { \"SHAKE128\", \"SHAKE-128\" },\n    { \"SHAKE256\", \"SHAKE-256\" },\n};\nconst size_t digest_name_translation_table_count =\n    sizeof(digest_name_translation_table) / sizeof(*digest_name_translation_table);\n\nconst char *\nmd_kt_name(const char *mdname)\n{\n    if (!strcmp(\"none\", mdname))\n    {\n        return \"[null-digest]\";\n    }\n    evp_md_type *kt = md_get(mdname);\n    const char *name = EVP_MD_get0_name(kt);\n\n    /* Search for a digest name translation */\n    for (size_t i = 0; i < digest_name_translation_table_count; i++)\n    {\n        const cipher_name_pair *pair = &digest_name_translation_table[i];\n        if (!strcmp(name, pair->lib_name))\n        {\n            name = pair->openvpn_name;\n        }\n    }\n\n    EVP_MD_free(kt);\n    return name;\n}\n\nunsigned char\nmd_kt_size(const char *mdname)\n{\n    if (!strcmp(\"none\", mdname))\n    {\n        return 0;\n    }\n    evp_md_type *kt = md_get(mdname);\n    unsigned char size = (unsigned char)EVP_MD_size(kt);\n    EVP_MD_free(kt);\n    return size;\n}\n\n\n/*\n *\n * Generic message digest functions\n *\n */\n\nbool\nmd_full(const char *mdname, const uint8_t *src, size_t src_len, uint8_t *dst)\n{\n    unsigned int in_md_len = 0;\n    evp_md_type *kt = md_get(mdname);\n\n    int ret = EVP_Digest(src, src_len, dst, &in_md_len, kt, NULL);\n    EVP_MD_free(kt);\n    return ret == 1;\n}\n\nEVP_MD_CTX *\nmd_ctx_new(void)\n{\n    EVP_MD_CTX *ctx = EVP_MD_CTX_new();\n    check_malloc_return(ctx);\n    return ctx;\n}\n\nvoid\nmd_ctx_free(EVP_MD_CTX *ctx)\n{\n    EVP_MD_CTX_free(ctx);\n}\n\nvoid\nmd_ctx_init(EVP_MD_CTX *ctx, const char *mdname)\n{\n    evp_md_type *kt = md_get(mdname);\n    ASSERT(NULL != ctx && NULL != kt);\n\n    EVP_MD_CTX_init(ctx);\n    if (!EVP_DigestInit(ctx, kt))\n    {\n        crypto_msg(M_FATAL, \"EVP_DigestInit failed\");\n    }\n    EVP_MD_free(kt);\n}\n\nvoid\nmd_ctx_cleanup(EVP_MD_CTX *ctx)\n{\n    EVP_MD_CTX_reset(ctx);\n}\n\nint\nmd_ctx_size(const EVP_MD_CTX *ctx)\n{\n    return EVP_MD_CTX_size(ctx);\n}\n\nvoid\nmd_ctx_update(EVP_MD_CTX *ctx, const uint8_t *src, size_t src_len)\n{\n    EVP_DigestUpdate(ctx, src, src_len);\n}\n\nvoid\nmd_ctx_final(EVP_MD_CTX *ctx, uint8_t *dst)\n{\n    unsigned int in_md_len = 0;\n\n    EVP_DigestFinal(ctx, dst, &in_md_len);\n}\n\n\n/*\n *\n * Generic HMAC functions\n *\n */\n#if OPENSSL_VERSION_NUMBER < 0x30000000L\nHMAC_CTX *\nhmac_ctx_new(void)\n{\n    HMAC_CTX *ctx = HMAC_CTX_new();\n    check_malloc_return(ctx);\n    return ctx;\n}\n\nvoid\nhmac_ctx_free(HMAC_CTX *ctx)\n{\n    HMAC_CTX_free(ctx);\n}\n\nvoid\nhmac_ctx_init(HMAC_CTX *ctx, const uint8_t *key, const char *mdname)\n{\n    evp_md_type *kt = md_get(mdname);\n    ASSERT(NULL != kt && NULL != ctx);\n\n    int key_len = EVP_MD_size(kt);\n    HMAC_CTX_reset(ctx);\n    if (!HMAC_Init_ex(ctx, key, key_len, kt, NULL))\n    {\n        crypto_msg(M_FATAL, \"HMAC_Init_ex failed\");\n    }\n\n    /* make sure we used a big enough key */\n    ASSERT((ssize_t)HMAC_size(ctx) <= key_len);\n}\n\nvoid\nhmac_ctx_cleanup(HMAC_CTX *ctx)\n{\n    HMAC_CTX_reset(ctx);\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\nint\nhmac_ctx_size(HMAC_CTX *ctx)\n{\n    return HMAC_size(ctx);\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nvoid\nhmac_ctx_reset(HMAC_CTX *ctx)\n{\n    if (!HMAC_Init_ex(ctx, NULL, 0, NULL, NULL))\n    {\n        crypto_msg(M_FATAL, \"HMAC_Init_ex failed\");\n    }\n}\n\nvoid\nhmac_ctx_update(HMAC_CTX *ctx, const uint8_t *src, int src_len)\n{\n    HMAC_Update(ctx, src, src_len);\n}\n\nvoid\nhmac_ctx_final(HMAC_CTX *ctx, uint8_t *dst)\n{\n    unsigned int in_hmac_len = 0;\n\n    HMAC_Final(ctx, dst, &in_hmac_len);\n}\n#else  /* if OPENSSL_VERSION_NUMBER < 0x30000000L */\nhmac_ctx_t *\nhmac_ctx_new(void)\n{\n    hmac_ctx_t *ctx;\n    ALLOC_OBJ_CLEAR(ctx, hmac_ctx_t);\n    EVP_MAC *hmac = EVP_MAC_fetch(NULL, \"HMAC\", NULL);\n    ctx->ctx = EVP_MAC_CTX_new(hmac);\n    check_malloc_return(ctx->ctx);\n\n    EVP_MAC_free(hmac);\n\n    return ctx;\n}\n\nvoid\nhmac_ctx_free(hmac_ctx_t *ctx)\n{\n    EVP_MAC_CTX_free(ctx->ctx);\n    secure_memzero(ctx, sizeof(hmac_ctx_t));\n    free(ctx);\n}\n\nvoid\nhmac_ctx_init(hmac_ctx_t *ctx, const uint8_t *key, const char *mdname)\n{\n    evp_md_type *kt = md_get(mdname);\n    ASSERT(NULL != kt && NULL != ctx && ctx->ctx != NULL);\n\n    /* We need to make a copy of the key since the OSSL parameters\n     * only reference it */\n    memcpy(ctx->key, key, (size_t)EVP_MD_size(kt));\n\n    /* Lookup/setting of parameters in OpenSSL 3.0 are string based\n     *\n     * The OSSL_PARAM_construct_utf8_string needs a non const str but this\n     * only used for lookup so we cast (as OpenSSL also does internally)\n     * the constness away here.\n     */\n    ctx->params[0] = OSSL_PARAM_construct_utf8_string(\"digest\", (char *)EVP_MD_get0_name(kt), 0);\n    ctx->params[1] = OSSL_PARAM_construct_octet_string(\"key\", ctx->key, (size_t)EVP_MD_size(kt));\n    ctx->params[2] = OSSL_PARAM_construct_end();\n\n    if (!EVP_MAC_init(ctx->ctx, NULL, 0, ctx->params))\n    {\n        crypto_msg(M_FATAL, \"EVP_MAC_init failed\");\n    }\n\n    EVP_MD_free(kt);\n}\n\nvoid\nhmac_ctx_cleanup(hmac_ctx_t *ctx)\n{\n    EVP_MAC_init(ctx->ctx, NULL, 0, NULL);\n}\n\nint\nhmac_ctx_size(hmac_ctx_t *ctx)\n{\n    return (int)EVP_MAC_CTX_get_mac_size(ctx->ctx);\n}\n\nvoid\nhmac_ctx_reset(hmac_ctx_t *ctx)\n{\n    /* The OpenSSL MAC API lacks a reset method and passing NULL as params\n     * does not reset it either, so use the params array to reinitialise it the\n     * same way as before */\n    if (!EVP_MAC_init(ctx->ctx, NULL, 0, ctx->params))\n    {\n        crypto_msg(M_FATAL, \"EVP_MAC_init failed\");\n    }\n}\n\nvoid\nhmac_ctx_update(hmac_ctx_t *ctx, const uint8_t *src, int src_len)\n{\n    EVP_MAC_update(ctx->ctx, src, src_len);\n}\n\nvoid\nhmac_ctx_final(hmac_ctx_t *ctx, uint8_t *dst)\n{\n    /* The calling code always gives us a buffer that has the size of our\n     * algorithm */\n    size_t in_hmac_len = EVP_MAC_CTX_get_mac_size(ctx->ctx);\n\n    EVP_MAC_final(ctx->ctx, dst, &in_hmac_len, in_hmac_len);\n}\n#endif /* if OPENSSL_VERSION_NUMBER < 0x30000000L */\n\nint\nmemcmp_constant_time(const void *a, const void *b, size_t size)\n{\n    return CRYPTO_memcmp(a, b, size);\n}\n#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) && !defined(LIBRESSL_VERSION_NUMBER)\nbool\nssl_tls1_PRF(const uint8_t *seed, size_t seed_len, const uint8_t *secret, size_t secret_len,\n             uint8_t *output, size_t output_len)\n{\n    bool ret = true;\n    EVP_KDF_CTX *kctx = NULL;\n\n\n    EVP_KDF *kdf = EVP_KDF_fetch(NULL, \"TLS1-PRF\", NULL);\n    if (!kdf)\n    {\n        goto err;\n    }\n\n    kctx = EVP_KDF_CTX_new(kdf);\n\n    if (!kctx)\n    {\n        goto err;\n    }\n\n    OSSL_PARAM params[4];\n\n    /* The OpenSSL APIs require us to cast the const aways even though the\n     * strings are never changed and only read */\n    params[0] =\n        OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, SN_md5_sha1, strlen(SN_md5_sha1));\n    params[1] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SECRET, (uint8_t *)secret,\n                                                  secret_len);\n    params[2] =\n        OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SEED, (uint8_t *)seed, seed_len);\n    params[3] = OSSL_PARAM_construct_end();\n\n    if (EVP_KDF_derive(kctx, output, output_len, params) <= 0)\n    {\n        crypto_msg(D_TLS_DEBUG_LOW, \"Generating TLS 1.0 PRF using \"\n                                    \"EVP_KDF_derive failed\");\n        goto err;\n    }\n\n    goto out;\n\nerr:\n    ret = false;\nout:\n    EVP_KDF_CTX_free(kctx);\n    EVP_KDF_free(kdf);\n\n    return ret;\n}\n#elif defined(OPENSSL_IS_AWSLC)\nbool\nssl_tls1_PRF(const uint8_t *label, size_t label_len, const uint8_t *sec, size_t slen, uint8_t *out1,\n             size_t olen)\n{\n    return CRYPTO_tls1_prf(EVP_md5_sha1(), out1, olen, sec, slen,\n                           (const char *)label, label_len, NULL, 0, NULL, 0);\n}\n#elif !defined(LIBRESSL_VERSION_NUMBER) && !defined(ENABLE_CRYPTO_WOLFSSL)\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\nbool\nssl_tls1_PRF(const uint8_t *seed, size_t seed_len, const uint8_t *secret, size_t secret_len,\n             uint8_t *output, size_t output_len)\n{\n    EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL);\n    if (!pctx)\n    {\n        return false;\n    }\n\n    bool ret = false;\n    if (!EVP_PKEY_derive_init(pctx))\n    {\n        goto out;\n    }\n\n    if (!EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_md5_sha1()))\n    {\n        goto out;\n    }\n\n    if (!EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, secret, secret_len))\n    {\n        goto out;\n    }\n\n    if (!EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed, seed_len))\n    {\n        goto out;\n    }\n\n    size_t out_len = output_len;\n    if (!EVP_PKEY_derive(pctx, output, &out_len))\n    {\n        goto out;\n    }\n    if (out_len != output_len)\n    {\n        goto out;\n    }\n    ret = true;\nout:\n    EVP_PKEY_CTX_free(pctx);\n    return ret;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n#else  /* if defined(LIBRESSL_VERSION_NUMBER) */\n/* LibreSSL and wolfSSL do not expose a TLS 1.0/1.1 PRF via the same APIs as\n * OpenSSL does. As result they will only be able to support\n * peers that support TLS EKM like when running with OpenSSL 3.x FIPS */\nbool\nssl_tls1_PRF(const uint8_t *label, size_t label_len, const uint8_t *sec, size_t slen, uint8_t *out1,\n             size_t olen)\n{\n    return false;\n}\n#endif /* if LIBRESSL_VERSION_NUMBER */\n#endif /* ENABLE_CRYPTO_OPENSSL */\n"
  },
  {
    "path": "src/openvpn/crypto_openssl.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Data Channel Cryptography OpenSSL-specific backend interface\n */\n\n#ifndef CRYPTO_OPENSSL_H_\n#define CRYPTO_OPENSSL_H_\n\n#include <openssl/evp.h>\n#include <openssl/hmac.h>\n#include <openssl/md5.h>\n#include <openssl/sha.h>\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\n#include <openssl/provider.h>\n#endif\n\n/** Generic cipher %context. */\ntypedef EVP_CIPHER_CTX cipher_ctx_t;\n\n/** Generic message digest %context. */\ntypedef EVP_MD_CTX md_ctx_t;\n\n/** Generic HMAC %context. */\n#if OPENSSL_VERSION_NUMBER < 0x30000000L\ntypedef HMAC_CTX hmac_ctx_t;\n\n/* Use a dummy type for the provider */\ntypedef void provider_t;\n#else\ntypedef struct\n{\n    OSSL_PARAM params[3];\n    uint8_t key[EVP_MAX_KEY_LENGTH];\n    EVP_MAC_CTX *ctx;\n} hmac_ctx_t;\n\ntypedef OSSL_PROVIDER provider_t;\n#endif\n\n/* In OpenSSL 3.0 the method that returns EVP_CIPHER, the cipher needs to be\n * freed afterwards, thus needing a non-const type. In constrast OpenSSL 1.1.1\n * and lower returns a const type, needing a const type */\n#if OPENSSL_VERSION_NUMBER < 0x30000000L\ntypedef const EVP_CIPHER evp_cipher_type;\ntypedef const EVP_MD evp_md_type;\n#else\ntypedef EVP_CIPHER evp_cipher_type;\ntypedef EVP_MD evp_md_type;\n#endif\n\n/** Maximum length of an IV */\n#define OPENVPN_MAX_IV_LENGTH EVP_MAX_IV_LENGTH\n\n/** Cipher is in CBC mode */\n#define OPENVPN_MODE_CBC EVP_CIPH_CBC_MODE\n\n/** Cipher is in OFB mode */\n#define OPENVPN_MODE_OFB EVP_CIPH_OFB_MODE\n\n/** Cipher is in CFB mode */\n#define OPENVPN_MODE_CFB EVP_CIPH_CFB_MODE\n\n/** Cipher is in GCM mode */\n#define OPENVPN_MODE_GCM EVP_CIPH_GCM_MODE\n\ntypedef int crypto_operation_t;\n\n/** Cipher should encrypt */\n#define OPENVPN_OP_ENCRYPT 1\n\n/** Cipher should decrypt */\n#define OPENVPN_OP_DECRYPT 0\n\n#define MD4_DIGEST_LENGTH 16\n\n/**\n * Retrieve any occurred OpenSSL errors and print those errors.\n *\n * Note that this function uses the not thread-safe OpenSSL error API.\n *\n * @param flags         Flags to indicate error type and priority.\n */\nvoid crypto_print_openssl_errors(const unsigned int flags);\n\n/**\n * Retrieve any OpenSSL errors, then print the supplied error message.\n *\n * This is just a convenience wrapper for often occurring situations.\n *\n * @param flags         Flags to indicate error type and priority.\n * @param ...           Format string and optional format arguments\n */\n#define crypto_msg(flags, ...)                        \\\n    do                                                \\\n    {                                                 \\\n        crypto_print_openssl_errors(nonfatal(flags)); \\\n        msg((flags), __VA_ARGS__);                    \\\n    } while (false)\n\n#endif /* CRYPTO_OPENSSL_H_ */\n"
  },
  {
    "path": "src/openvpn/cryptoapi.c",
    "content": "/*\n * Copyright (c) 2004 Peter 'Luna' Runestig <peter@runestig.com>\n * Copyright (c) 2018 Selva Nair <selva.nair@gmail.com>\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without modifi-\n * cation, are permitted provided that the following conditions are met:\n *\n *   o  Redistributions of source code must retain the above copyright notice,\n *      this list of conditions and the following disclaimer.\n *\n *   o  Redistributions in binary form must reproduce the above copyright no-\n *      tice, this list of conditions and the following disclaimer in the do-\n *      cumentation and/or other materials provided with the distribution.\n *\n *   o  The names of the contributors may not be used to endorse or promote\n *      products derived from this software without specific prior written\n *      permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LI-\n * ABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUEN-\n * TIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEV-\n * ER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABI-\n * LITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF\n * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#ifdef ENABLE_CRYPTOAPI\n\n#include <openssl/ssl.h>\n#include <openssl/evp.h>\n#include <openssl/err.h>\n#include <windows.h>\n#include <wincrypt.h>\n#include <ncrypt.h>\n#include <stdio.h>\n#include <ctype.h>\n\n#include \"buffer.h\"\n#include \"openssl_compat.h\"\n#include \"win32.h\"\n#include \"xkey_common.h\"\n#include \"crypto_openssl.h\"\n\n#ifndef HAVE_XKEY_PROVIDER\n\nint\nSSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)\n{\n    msg(M_NONFATAL, \"ERROR: this binary was built without cryptoapicert support\");\n    return 0;\n}\n\n#else  /* HAVE_XKEY_PROVIDER */\n\nstatic XKEY_EXTERNAL_SIGN_fn xkey_cng_sign;\n\ntypedef struct _CAPI_DATA\n{\n    const CERT_CONTEXT *cert_context;\n    HCRYPTPROV_OR_NCRYPT_KEY_HANDLE crypt_prov;\n    EVP_PKEY *pubkey;\n    DWORD key_spec;\n    BOOL free_crypt_prov;\n    int ref_count;\n} CAPI_DATA;\n\n/*\n * Translate OpenSSL hash OID to CNG algorithm name. Returns\n * \"UNKNOWN\" for unsupported algorithms and NULL for MD5+SHA1\n * mixed hash used in TLS 1.1 and earlier.\n */\nstatic const wchar_t *\ncng_hash_algo(int md_type)\n{\n    const wchar_t *alg = L\"UNKNOWN\";\n    switch (md_type)\n    {\n        case NID_md5:\n            alg = BCRYPT_MD5_ALGORITHM;\n            break;\n\n        case NID_sha1:\n            alg = BCRYPT_SHA1_ALGORITHM;\n            break;\n\n        case NID_sha256:\n            alg = BCRYPT_SHA256_ALGORITHM;\n            break;\n\n        case NID_sha384:\n            alg = BCRYPT_SHA384_ALGORITHM;\n            break;\n\n        case NID_sha512:\n            alg = BCRYPT_SHA512_ALGORITHM;\n            break;\n\n        case NID_md5_sha1:\n        case 0:\n            alg = NULL;\n            break;\n\n        default:\n            msg(M_WARN | M_INFO, \"cryptoapicert: Unknown hash type NID=0x%x\", md_type);\n            break;\n    }\n    return alg;\n}\n\nstatic void\nCAPI_DATA_free(CAPI_DATA *cd)\n{\n    if (!cd || cd->ref_count-- > 0)\n    {\n        return;\n    }\n    if (cd->free_crypt_prov && cd->crypt_prov)\n    {\n        if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)\n        {\n            NCryptFreeObject(cd->crypt_prov);\n        }\n        else\n        {\n            CryptReleaseContext(cd->crypt_prov, 0);\n        }\n    }\n    if (cd->cert_context)\n    {\n        CertFreeCertificateContext(cd->cert_context);\n    }\n    EVP_PKEY_free(cd->pubkey); /* passing NULL is okay */\n\n    free(cd);\n}\n\n/**\n * Parse a hex string with optional embedded spaces into\n * a byte array.\n * @param p         pointer to the input string\n * @param arr       on output contains the parsed bytes\n * @param capacity  capacity of the byte array arr\n * @returns the number of bytes parsed or 0 on error\n */\nstatic DWORD\nparse_hexstring(const char *p, unsigned char *arr, DWORD capacity)\n{\n    DWORD i = 0;\n    for (; *p && i < capacity; p += 2)\n    {\n        /* skip spaces */\n        while (*p == ' ')\n        {\n            p++;\n        }\n        if (!*p) /* ending with spaces is not an error */\n        {\n            break;\n        }\n\n        if (!isxdigit(p[0]) || !isxdigit(p[1]) || sscanf(p, \"%2hhx\", &arr[i++]) != 1)\n        {\n            return 0;\n        }\n    }\n    return i;\n}\n\nstatic void *\ndecode_object(struct gc_arena *gc, LPCSTR struct_type, const CRYPT_OBJID_BLOB *val, DWORD flags,\n              DWORD *cb)\n{\n    /* get byte count for decoding */\n    BYTE *buf;\n    if (!CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, struct_type, val->pbData,\n                           val->cbData, flags, NULL, cb))\n    {\n        return NULL;\n    }\n\n    /* do the actual decode */\n    buf = gc_malloc(*cb, false, gc);\n    if (!CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, struct_type, val->pbData,\n                           val->cbData, flags, buf, cb))\n    {\n        return NULL;\n    }\n\n    return buf;\n}\n\nstatic const CRYPT_OID_INFO *\nfind_oid(DWORD keytype, const void *key, DWORD groupid)\n{\n    const CRYPT_OID_INFO *info = NULL;\n\n    /* try proper resolve, also including AD */\n    info = CryptFindOIDInfo(keytype, (void *)key, groupid);\n\n    /* fall back to all groups if not found yet */\n    if (!info && groupid)\n    {\n        info = CryptFindOIDInfo(keytype, (void *)key, 0);\n    }\n\n    return info;\n}\n\nstatic bool\ntest_certificate_template(const char *cert_prop, const CERT_CONTEXT *cert_ctx)\n{\n    const CERT_INFO *info = cert_ctx->pCertInfo;\n    const CERT_EXTENSION *ext;\n    DWORD cbext;\n    void *pvext;\n    struct gc_arena gc = gc_new();\n    const WCHAR *tmpl_name = wide_string(cert_prop, &gc);\n\n    /* check for V2 extension (Windows 2003+) */\n    ext = CertFindExtension(szOID_CERTIFICATE_TEMPLATE, info->cExtension, info->rgExtension);\n    if (ext)\n    {\n        pvext = decode_object(&gc, X509_CERTIFICATE_TEMPLATE, &ext->Value, 0, &cbext);\n        if (pvext && cbext >= sizeof(CERT_TEMPLATE_EXT))\n        {\n            const CERT_TEMPLATE_EXT *cte = (const CERT_TEMPLATE_EXT *)pvext;\n            if (!stricmp(cert_prop, cte->pszObjId))\n            {\n                /* found direct OID match with certificate property specified */\n                gc_free(&gc);\n                return true;\n            }\n\n            const CRYPT_OID_INFO *tmpl_oid =\n                find_oid(CRYPT_OID_INFO_NAME_KEY, tmpl_name, CRYPT_TEMPLATE_OID_GROUP_ID);\n            if (tmpl_oid && !stricmp(tmpl_oid->pszOID, cte->pszObjId))\n            {\n                /* found OID match in extension against resolved key */\n                gc_free(&gc);\n                return true;\n            }\n        }\n    }\n\n    /* no extension found, exit */\n    gc_free(&gc);\n    return false;\n}\n\nstatic const CERT_CONTEXT *\nfind_certificate_in_store(const char *cert_prop, HCERTSTORE cert_store)\n{\n    /* Find, and use, the desired certificate from the store. The\n     * 'cert_prop' certificate search string can look like this:\n     * SUBJ:<certificate substring to match>\n     * THUMB:<certificate thumbprint hex value>, e.g.\n     *     THUMB:f6 49 24 41 01 b4 fb 44 0c ce f4 36 ae d0 c4 c9 df 7a b6 28\n     * TMPL:<template name or OID>\n     * The first matching certificate that has not expired is returned.\n     */\n    const CERT_CONTEXT *rv = NULL;\n    DWORD find_type;\n    const void *find_param;\n    unsigned char hash[255];\n    CRYPT_HASH_BLOB blob = { .cbData = 0, .pbData = hash };\n    struct gc_arena gc = gc_new();\n\n    if (!strncmp(cert_prop, \"SUBJ:\", 5))\n    {\n        /* skip the tag */\n        find_param = wide_string(cert_prop + 5, &gc);\n        find_type = CERT_FIND_SUBJECT_STR_W;\n    }\n    else if (!strncmp(cert_prop, \"ISSUER:\", 7))\n    {\n        find_param = wide_string(cert_prop + 7, &gc);\n        find_type = CERT_FIND_ISSUER_STR_W;\n    }\n    else if (!strncmp(cert_prop, \"THUMB:\", 6))\n    {\n        find_type = CERT_FIND_HASH;\n        find_param = &blob;\n\n        blob.cbData = parse_hexstring(cert_prop + 6, hash, sizeof(hash));\n        if (blob.cbData == 0)\n        {\n            msg(M_WARN | M_INFO, \"WARNING: cryptoapicert: error parsing <%s>.\", cert_prop);\n            goto out;\n        }\n    }\n    else if (!strncmp(cert_prop, \"TMPL:\", 5))\n    {\n        cert_prop += 5;\n        find_param = NULL;\n        find_type = CERT_FIND_HAS_PRIVATE_KEY;\n    }\n    else\n    {\n        msg(M_NONFATAL, \"Error in cryptoapicert: unsupported certificate specification <%s>\",\n            cert_prop);\n        goto out;\n    }\n\n    while (true)\n    {\n        int validity = 1;\n        /* this frees previous rv, if not NULL */\n        rv = CertFindCertificateInStore(cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0,\n                                        find_type, find_param, rv);\n        if (!rv)\n        {\n            break;\n        }\n        /* if searching by template name, check now if it matches */\n        if (find_type == CERT_FIND_HAS_PRIVATE_KEY && !test_certificate_template(cert_prop, rv))\n        {\n            continue;\n        }\n        validity = CertVerifyTimeValidity(NULL, rv->pCertInfo);\n        if (validity == 0)\n        {\n            break;\n        }\n        msg(M_WARN | M_INFO, \"WARNING: cryptoapicert: ignoring certificate in store %s.\",\n            validity < 0 ? \"not yet valid\" : \"that has expired\");\n    }\n\nout:\n    gc_free(&gc);\n    return rv;\n}\n\n/** Sign hash in tbs using EC key in cd and NCryptSignHash */\nstatic int\nxkey_cng_ec_sign(CAPI_DATA *cd, unsigned char *sig, size_t *siglen, const unsigned char *tbs,\n                 size_t tbslen)\n{\n    ASSERT(*siglen <= UINT_MAX);\n    ASSERT(tbslen <= UINT_MAX);\n    DWORD len = (DWORD)*siglen;\n\n    msg(D_LOW, \"Signing using NCryptSignHash with EC key\");\n\n    DWORD status = NCryptSignHash(cd->crypt_prov, NULL, (BYTE *)tbs, (DWORD)tbslen, sig, len, &len, 0);\n\n    if (status != ERROR_SUCCESS)\n    {\n        SetLastError(status);\n        msg(M_NONFATAL | M_ERRNO, \"Error in cryptoapicert: ECDSA signature using CNG failed.\");\n        return 0;\n    }\n\n    /* NCryptSignHash returns r|s -- convert to DER encoded buffer expected by OpenSSL */\n    int derlen = ecdsa_bin2der(sig, (int)len, *siglen);\n    if (derlen <= 0)\n    {\n        return 0;\n    }\n    *siglen = derlen;\n    return 1;\n}\n\n/** Sign hash in tbs using RSA key in cd and NCryptSignHash */\nstatic int\nxkey_cng_rsa_sign(CAPI_DATA *cd, unsigned char *sig, size_t *siglen, const unsigned char *tbs,\n                  size_t tbslen, XKEY_SIGALG sigalg)\n{\n    dmsg(D_LOW, \"In xkey_cng_rsa_sign\");\n\n    ASSERT(cd);\n    ASSERT(sig);\n    ASSERT(*siglen <= UINT_MAX);\n    ASSERT(tbs);\n    ASSERT(tbslen <= INT_MAX);\n\n    DWORD status = ERROR_SUCCESS;\n    DWORD len = 0;\n\n    const wchar_t *hashalg = cng_hash_algo(OBJ_sn2nid(sigalg.mdname));\n\n    if (hashalg && wcscmp(hashalg, L\"UNKNOWN\") == 0)\n    {\n        msg(M_NONFATAL, \"Error in cryptoapicert: Unknown hash name <%s>\", sigalg.mdname);\n        return 0;\n    }\n\n    if (!strcmp(sigalg.padmode, \"pkcs1\"))\n    {\n        msg(D_LOW, \"Signing using NCryptSignHash with PKCS1 padding: hashalg <%s>\", sigalg.mdname);\n\n        BCRYPT_PKCS1_PADDING_INFO padinfo = { hashalg };\n        status = NCryptSignHash(cd->crypt_prov, &padinfo, (BYTE *)tbs, (DWORD)tbslen, sig,\n                                (DWORD)*siglen, &len, BCRYPT_PAD_PKCS1);\n    }\n    else if (!strcmp(sigalg.padmode, \"pss\"))\n    {\n        int saltlen = (int)tbslen; /* digest size by default */\n        if (!strcmp(sigalg.saltlen, \"max\"))\n        {\n            saltlen = xkey_max_saltlen(EVP_PKEY_bits(cd->pubkey), saltlen);\n            if (saltlen < 0)\n            {\n                msg(M_NONFATAL, \"Error in cryptoapicert: invalid salt length (%d)\", saltlen);\n                return 0;\n            }\n        }\n\n        msg(D_LOW, \"Signing using NCryptSignHash with PSS padding: hashalg <%s>, saltlen <%d>\",\n            sigalg.mdname, saltlen);\n\n        /* cast is safe as saltlen >= 0 */\n        BCRYPT_PSS_PADDING_INFO padinfo = { hashalg, (DWORD)saltlen };\n        status = NCryptSignHash(cd->crypt_prov, &padinfo, (BYTE *)tbs, (DWORD)tbslen, sig,\n                                (DWORD)*siglen, &len, BCRYPT_PAD_PSS);\n    }\n    else\n    {\n        msg(M_NONFATAL, \"Error in cryptoapicert: Unsupported padding mode <%s>\", sigalg.padmode);\n        return 0;\n    }\n\n    if (status != ERROR_SUCCESS)\n    {\n        SetLastError(status);\n        msg(M_NONFATAL | M_ERRNO, \"Error in cryptoapicert: RSA signature using CNG failed.\");\n        return 0;\n    }\n\n    *siglen = len;\n    return (*siglen > 0);\n}\n\n/** Dispatch sign op to xkey_cng_<rsa/ec>_sign */\nstatic int\nxkey_cng_sign(void *handle, unsigned char *sig, size_t *siglen, const unsigned char *tbs,\n              size_t tbslen, XKEY_SIGALG sigalg)\n{\n    dmsg(D_LOW, \"In xkey_cng_sign\");\n\n    CAPI_DATA *cd = handle;\n    ASSERT(cd);\n    ASSERT(sig);\n    ASSERT(tbs);\n\n    unsigned char mdbuf[EVP_MAX_MD_SIZE];\n    size_t buflen = _countof(mdbuf);\n\n    /* compute digest if required */\n    if (!strcmp(sigalg.op, \"DigestSign\"))\n    {\n        if (!xkey_digest(tbs, tbslen, mdbuf, &buflen, sigalg.mdname))\n        {\n            return 0;\n        }\n        tbs = mdbuf;\n        tbslen = buflen;\n    }\n\n    if (!strcmp(sigalg.keytype, \"EC\"))\n    {\n        return xkey_cng_ec_sign(cd, sig, siglen, tbs, tbslen);\n    }\n    else if (!strcmp(sigalg.keytype, \"RSA\"))\n    {\n        return xkey_cng_rsa_sign(cd, sig, siglen, tbs, tbslen, sigalg);\n    }\n    else\n    {\n        return 0; /* Unknown keytype -- should not happen */\n    }\n}\n\nstatic char *\nget_cert_name(const CERT_CONTEXT *cc, struct gc_arena *gc)\n{\n    DWORD len = CertGetNameStringW(cc, CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, NULL, 0);\n    char *name = NULL;\n    if (len)\n    {\n        wchar_t *wname = gc_malloc(len * sizeof(wchar_t), false, gc);\n        if (!wname\n            || CertGetNameStringW(cc, CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, wname, len) == 0)\n        {\n            return NULL;\n        }\n        name = utf16to8(wname, gc);\n    }\n    return name;\n}\n\n/**\n * Load certificate matching 'cert_prop' from Windows cert store\n * into xkey provider and return pointers to X509 cert and private key.\n * Returns 1 on success, 0 on error.\n * Caller must free 'cert' and 'privkey' after use.\n */\nstatic int\nLoad_CryptoAPI_certificate(const char *cert_prop, X509 **cert, EVP_PKEY **privkey)\n{\n    HCERTSTORE cs;\n    CAPI_DATA *cd = calloc(1, sizeof(*cd));\n    struct gc_arena gc = gc_new();\n\n    if (cd == NULL)\n    {\n        msg(M_NONFATAL, \"Error in cryptoapicert: out of memory\");\n        goto err;\n    }\n    /* search CURRENT_USER first, then LOCAL_MACHINE */\n    cs = CertOpenStore((LPCSTR)CERT_STORE_PROV_SYSTEM, 0, 0,\n                       CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_OPEN_EXISTING_FLAG\n                           | CERT_STORE_READONLY_FLAG,\n                       L\"MY\");\n    if (cs == NULL)\n    {\n        msg(M_NONFATAL | M_ERRNO, \"Error in cryptoapicert: failed to open user certficate store\");\n        goto err;\n    }\n    cd->cert_context = find_certificate_in_store(cert_prop, cs);\n    CertCloseStore(cs, 0);\n    if (!cd->cert_context)\n    {\n        cs = CertOpenStore((LPCSTR)CERT_STORE_PROV_SYSTEM, 0, 0,\n                           CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_OPEN_EXISTING_FLAG\n                               | CERT_STORE_READONLY_FLAG,\n                           L\"MY\");\n        if (cs == NULL)\n        {\n            msg(M_NONFATAL | M_ERRNO,\n                \"Error in cryptoapicert: failed to open machine certficate store\");\n            goto err;\n        }\n        cd->cert_context = find_certificate_in_store(cert_prop, cs);\n        CertCloseStore(cs, 0);\n        if (cd->cert_context == NULL)\n        {\n            msg(M_NONFATAL, \"Error in cryptoapicert: certificate matching <%s> not found\",\n                cert_prop);\n            goto err;\n        }\n    }\n\n    /* try to log the \"name\" of the selected certificate */\n    char *cert_name = get_cert_name(cd->cert_context, &gc);\n    if (cert_name)\n    {\n        msg(D_LOW, \"cryptapicert: using certificate with name <%s>\", cert_name);\n    }\n\n    /* cert_context->pbCertEncoded is the cert X509 DER encoded. */\n    *cert = d2i_X509(NULL, (const unsigned char **)&cd->cert_context->pbCertEncoded,\n                     cd->cert_context->cbCertEncoded);\n    if (*cert == NULL)\n    {\n        msg(M_NONFATAL, \"Error in cryptoapicert: X509 certificate decode failed\");\n        goto err;\n    }\n\n    /* set up stuff to use the private key */\n    /* We support NCRYPT key handles only */\n    DWORD flags = CRYPT_ACQUIRE_COMPARE_KEY_FLAG | CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG;\n    if (!CryptAcquireCertificatePrivateKey(cd->cert_context, flags, NULL, &cd->crypt_prov,\n                                           &cd->key_spec, &cd->free_crypt_prov))\n    {\n        /* private key may be in a token not available, or incompatible with CNG */\n        msg(M_NONFATAL | M_ERRNO,\n            \"Error in cryptoapicert: failed to acquire key. Key not present or \"\n            \"is in a legacy token not supported by Windows CNG API\");\n        X509_free(*cert);\n        goto err;\n    }\n\n    /* the public key */\n    EVP_PKEY *pkey = X509_get_pubkey(*cert);\n    cd->pubkey = pkey; /* will be freed with cd */\n\n    *privkey = xkey_load_generic_key(tls_libctx, cd, pkey, xkey_cng_sign,\n                                     (XKEY_PRIVKEY_FREE_fn *)CAPI_DATA_free);\n    gc_free(&gc);\n    return 1; /* do not free cd -- its kept by xkey provider */\n\nerr:\n    CAPI_DATA_free(cd);\n    gc_free(&gc);\n    return 0;\n}\n\nint\nSSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)\n{\n    X509 *cert = NULL;\n    EVP_PKEY *privkey = NULL;\n    int ret = 0;\n\n    if (!Load_CryptoAPI_certificate(cert_prop, &cert, &privkey))\n    {\n        return ret;\n    }\n    if (SSL_CTX_use_certificate(ssl_ctx, cert) && SSL_CTX_use_PrivateKey(ssl_ctx, privkey))\n    {\n        crypto_print_openssl_errors(M_WARN);\n        ret = 1;\n    }\n\n    /* Always free cert and privkey even if retained by ssl_ctx as\n     * they are reference counted */\n    X509_free(cert);\n    EVP_PKEY_free(privkey);\n    return ret;\n}\n\n#endif /* HAVE_XKEY_PROVIDER */\n#endif /* _WIN32 */\n"
  },
  {
    "path": "src/openvpn/cryptoapi.h",
    "content": "#ifndef _CRYPTOAPI_H_\n#define _CRYPTOAPI_H_\n\nint SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop);\n\n\n#endif /* !_CRYPTOAPI_H_ */\n"
  },
  {
    "path": "src/openvpn/dco.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2021-2026 Arne Schwabe <arne@rfc2549.org>\n *  Copyright (C) 2021-2026 Antonio Quartulli <a@unstable.cc>\n *  Copyright (C) 2021-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#if defined(ENABLE_DCO)\n\n#include \"syshead.h\"\n#include \"crypto.h\"\n#include \"dco.h\"\n#include \"errlevel.h\"\n#include \"multi.h\"\n#include \"networking.h\"\n#include \"openvpn.h\"\n#include \"options.h\"\n#include \"ssl_common.h\"\n#include \"ssl_ncp.h\"\n#include \"tun.h\"\n#include \"tun_afunix.h\"\n\n#if defined(_WIN32)\n#include \"dco_win.h\"\n#endif\n\n#ifdef HAVE_LIBCAPNG\n#include <cap-ng.h>\n#endif\n\nstatic int\ndco_install_key(struct tls_multi *multi, struct key_state *ks, const uint8_t *encrypt_key,\n                const uint8_t *encrypt_iv, const uint8_t *decrypt_key, const uint8_t *decrypt_iv,\n                const char *ciphername)\n\n{\n    bool epoch = ks->crypto_options.flags & CO_EPOCH_DATA_KEY_FORMAT;\n    msg(D_DCO_DEBUG, \"%s: peer_id=%d keyid=%d epoch=%d, currently %d keys installed\", __func__,\n        multi->dco_peer_id, ks->key_id, multi->dco_keys_installed, epoch);\n\n    /* Install a key in the PRIMARY slot only when no other key exist.\n     * From that moment on, any new key will be installed in the SECONDARY\n     * slot and will be promoted to PRIMARY when userspace says so (a swap\n     * will be performed in that case)\n     */\n    dco_key_slot_t slot = OVPN_KEY_SLOT_PRIMARY;\n    if (multi->dco_keys_installed > 0)\n    {\n        slot = OVPN_KEY_SLOT_SECONDARY;\n    }\n\n    int ret = dco_new_key(multi->dco, multi->dco_peer_id, ks->key_id, slot, encrypt_key, encrypt_iv,\n                          decrypt_key, decrypt_iv, ciphername, epoch);\n    if ((ret == 0) && (multi->dco_keys_installed < 2))\n    {\n        multi->dco_keys_installed++;\n        ks->dco_status =\n            (slot == OVPN_KEY_SLOT_PRIMARY) ? DCO_INSTALLED_PRIMARY : DCO_INSTALLED_SECONDARY;\n    }\n\n    return ret;\n}\n\nint\ninit_key_dco_bi(struct tls_multi *multi, struct key_state *ks, const struct key2 *key2,\n                int key_direction, const char *ciphername, bool server)\n{\n    struct key_direction_state kds;\n    key_direction_state_init(&kds, key_direction);\n\n    return dco_install_key(multi, ks, key2->keys[kds.out_key].cipher, key2->keys[(int)server].hmac,\n                           key2->keys[kds.in_key].cipher, key2->keys[1 - (int)server].hmac,\n                           ciphername);\n}\n\n/**\n * Find a usable key that is not the primary (i.e. the secondary key)\n *\n * @param multi     The TLS struct to retrieve keys from\n * @param primary   The primary key that should be skipped during the scan\n *\n * @return          The secondary key or NULL if none could be found\n */\nstatic struct key_state *\ndco_get_secondary_key(struct tls_multi *multi, const struct key_state *primary)\n{\n    for (int i = 0; i < KEY_SCAN_SIZE; ++i)\n    {\n        struct key_state *ks = get_key_scan(multi, i);\n        struct key_ctx_bi *key = &ks->crypto_options.key_ctx_bi;\n\n        if (ks == primary)\n        {\n            continue;\n        }\n\n        if (ks->state >= S_GENERATED_KEYS && ks->authenticated == KS_AUTH_TRUE)\n        {\n            ASSERT(key->initialized);\n            return ks;\n        }\n    }\n\n    return NULL;\n}\n\nbool\ndco_update_keys(dco_context_t *dco, struct tls_multi *multi)\n{\n    /* this function checks if keys have to be swapped or erased, therefore it\n     * can't do much if we don't have any key installed\n     */\n    if (multi->dco_keys_installed == 0)\n    {\n        return true;\n    }\n\n    struct key_state *primary = tls_select_encryption_key(multi);\n    /* no primary key available -> no usable key exists, therefore we should\n     * tell DCO to simply wipe all keys\n     */\n    if (!primary)\n    {\n        msg(D_DCO, \"No encryption key found. Purging data channel keys\");\n\n        int ret = dco_del_key(dco, multi->dco_peer_id, OVPN_KEY_SLOT_PRIMARY);\n        if (ret < 0)\n        {\n            msg(D_DCO, \"Cannot delete primary key during wipe: %s (%d)\", strerror(-ret), ret);\n            return false;\n        }\n\n        ret = dco_del_key(dco, multi->dco_peer_id, OVPN_KEY_SLOT_SECONDARY);\n        if (ret < 0)\n        {\n            msg(D_DCO, \"Cannot delete secondary key during wipe: %s (%d)\", strerror(-ret), ret);\n            return false;\n        }\n\n        multi->dco_keys_installed = 0;\n        return true;\n    }\n\n    /* if we have a primary key, it must have been installed already (keys\n     * are installed upon generation in the TLS code)\n     */\n    ASSERT(primary->dco_status != DCO_NOT_INSTALLED);\n\n    struct key_state *secondary = dco_get_secondary_key(multi, primary);\n    /* if the current primary key was installed as secondary in DCO,\n     * this means we have promoted it since installation in DCO, and\n     * we now need to tell DCO to swap keys\n     */\n    if (primary->dco_status == DCO_INSTALLED_SECONDARY)\n    {\n        if (secondary)\n        {\n            msg(D_DCO_DEBUG,\n                \"Swapping primary and secondary keys to \"\n                \"primary-id=%d secondary-id=%d\",\n                primary->key_id, secondary->key_id);\n        }\n        else\n        {\n            msg(D_DCO_DEBUG,\n                \"Swapping primary and secondary keys to \"\n                \"primary-id=%d secondary-id=(to be deleted)\",\n                primary->key_id);\n        }\n\n        int ret = dco_swap_keys(dco, multi->dco_peer_id);\n        if (ret < 0)\n        {\n            msg(D_DCO, \"Cannot swap keys: %s (%d)\", strerror(-ret), ret);\n            return false;\n        }\n\n        primary->dco_status = DCO_INSTALLED_PRIMARY;\n        if (secondary)\n        {\n            ASSERT(secondary->dco_status == DCO_INSTALLED_PRIMARY);\n            secondary->dco_status = DCO_INSTALLED_SECONDARY;\n        }\n    }\n\n    /* if we have no secondary key anymore, inform DCO about it */\n    if (!secondary && multi->dco_keys_installed == 2)\n    {\n        int ret = dco_del_key(dco, multi->dco_peer_id, OVPN_KEY_SLOT_SECONDARY);\n        if (ret < 0)\n        {\n            msg(D_DCO, \"Cannot delete secondary key: %s (%d)\", strerror(-ret), ret);\n            return false;\n        }\n        multi->dco_keys_installed = 1;\n    }\n\n    /* all keys that are not installed are set to NOT installed. Include also\n     * keys that might even be considered as active keys to be sure*/\n    for (int i = 0; i < TM_SIZE; ++i)\n    {\n        for (int j = 0; j < KS_SIZE; j++)\n        {\n            struct key_state *ks = &multi->session[i].key[j];\n            if (ks != primary && ks != secondary)\n            {\n                ks->dco_status = DCO_NOT_INSTALLED;\n            }\n        }\n    }\n    return true;\n}\n\nstatic bool\ndco_check_option_ce(const struct connection_entry *ce, msglvl_t msglevel, int mode)\n{\n    if (ce->fragment)\n    {\n        msg(msglevel, \"Note: --fragment disables data channel offload.\");\n        return false;\n    }\n\n    if (ce->http_proxy_options)\n    {\n        msg(msglevel, \"Note: --http-proxy disables data channel offload.\");\n        return false;\n    }\n\n    if (ce->socks_proxy_server)\n    {\n        msg(msglevel, \"Note: --socks-proxy disables data channel offload.\");\n        return false;\n    }\n\n#if defined(TARGET_FREEBSD)\n    if (ce->local_list)\n    {\n        for (int i = 0; i < ce->local_list->len; i++)\n        {\n            if (!proto_is_dgram(ce->local_list->array[i]->proto))\n            {\n                msg(msglevel, \"NOTE: TCP transport disables data channel offload on FreeBSD.\");\n                return false;\n            }\n        }\n    }\n#endif\n\n#if defined(_WIN32)\n    if (!proto_is_udp(ce->local_list->array[0]->proto) && mode == MODE_SERVER)\n    {\n        msg(msglevel,\n            \"NOTE: TCP transport disables data channel offload on Windows in server mode.\");\n        return false;\n    }\n\n    if (!ce->remote && !dco_win_supports_multipeer())\n    {\n        msg(msglevel,\n            \"NOTE: --remote is not defined. This DCO version doesn't support multipeer. Disabling Data Channel Offload\");\n        return false;\n    }\n\n    if ((mode == MODE_SERVER) && (ce->local_list->len > 1))\n    {\n        msg(msglevel, \"NOTE: multiple --local options defined, disabling data channel offload\");\n        return false;\n    }\n#endif\n\n    return true;\n}\n\nbool\ndco_check_startup_option(msglvl_t msglevel, const struct options *o)\n{\n    /* check if no dev name was specified at all. In the case,\n     * later logic will most likely stop OpenVPN, so no need to\n     * print any message here.\n     */\n    if (!o->dev)\n    {\n        return false;\n    }\n\n    if (!o->tls_client && !o->tls_server)\n    {\n        msg(msglevel, \"No tls-client or tls-server option in configuration \"\n                      \"detected. Disabling data channel offload.\");\n        return false;\n    }\n\n    if (dev_type_enum(o->dev, o->dev_type) != DEV_TYPE_TUN)\n    {\n        msg(msglevel, \"Note: dev-type not tun, disabling data channel offload.\");\n        return false;\n    }\n\n    if (is_tun_afunix(o->dev_node))\n    {\n        msg(msglevel, \"Note: afunix tun type selected, disabling data channel offload\");\n        return false;\n    }\n\n    if (is_dev_type(o->dev, o->dev_type, \"null\"))\n    {\n        msg(msglevel, \"Note: null tun type selected, disabling data channel offload\");\n        return false;\n    }\n\n    if (o->connection_list)\n    {\n        const struct connection_list *l = o->connection_list;\n        for (int i = 0; i < l->len; ++i)\n        {\n            if (!dco_check_option_ce(l->array[i], msglevel, o->mode))\n            {\n                return false;\n            }\n        }\n    }\n    else\n    {\n        if (!dco_check_option_ce(&o->ce, msglevel, o->mode))\n        {\n            return false;\n        }\n    }\n\n#if defined(_WIN32)\n    if ((o->mode == MODE_SERVER) && !dco_win_supports_multipeer())\n    {\n        msg(msglevel,\n            \"--mode server is set. This DCO version doesn't support multipeer. Disabling Data Channel Offload\");\n        return false;\n    }\n\n    if ((o->mode == MODE_SERVER) && o->ce.local_list->len > 1)\n    {\n        msg(msglevel, \"multiple --local options defined, disabling data channel offload\");\n        return false;\n    }\n\n#elif defined(TARGET_LINUX)\n    /* if the device name is fixed, we need to check if an interface with this\n     * name already exists. IF it does, it must be a DCO interface, otherwise\n     * DCO has to be disabled in order to continue.\n     */\n    if (tun_name_is_fixed(o->dev))\n    {\n        char iftype[IFACE_TYPE_LEN_MAX];\n        /* we pass NULL as net_ctx because using DCO on Linux implies that we\n         * are using SITNL and the latter does not need any context. This way we\n         * don't need to have the net_ctx percolate all the way here\n         */\n        int ret = net_iface_type(NULL, o->dev, iftype);\n        if ((ret == 0) && (strcmp(iftype, \"ovpn-dco\") != 0))\n        {\n            msg(msglevel, \"Interface %s exists and is non-DCO. Disabling data channel offload\",\n                o->dev);\n            return false;\n        }\n        else if ((ret < 0) && (ret != -ENODEV))\n        {\n            msg(msglevel, \"Cannot retrieve type of device %s: %s (%d)\", o->dev, strerror(-ret),\n                ret);\n        }\n    }\n#endif /* if defined(_WIN32) */\n\n#if defined(HAVE_LIBCAPNG)\n    /* DCO can't operate without CAP_NET_ADMIN. To retain it when switching user\n     * we need CAP_SETPCAP. CAP_NET_ADMIN also needs to be part of the permitted set\n     * of capabilities in order to retain it.\n     */\n    if (o->username)\n    {\n        if (!capng_have_capability(CAPNG_EFFECTIVE, CAP_SETPCAP))\n        {\n            msg(msglevel, \"--user specified but lacking CAP_SETPCAP. \"\n                          \"Cannot retain CAP_NET_ADMIN. Disabling data channel offload\");\n            return false;\n        }\n        if (!capng_have_capability(CAPNG_PERMITTED, CAP_NET_ADMIN))\n        {\n            msg(msglevel, \"--user specified but not permitted to retain CAP_NET_ADMIN. \"\n                          \"Disabling data channel offload\");\n            return false;\n        }\n    }\n#endif /* if defined(HAVE_LIBCAPNG) */\n\n    if (o->mode == MODE_SERVER && o->topology != TOP_SUBNET)\n    {\n        msg(msglevel, \"Note: NOT using '--topology subnet' disables data channel offload.\");\n        return false;\n    }\n\n    if (o->management_flags & MF_QUERY_PROXY)\n    {\n        msg(msglevel, \"Note: --management-query-proxy disables data channel offload.\");\n        return false;\n    }\n\n    /* now that all options have been confirmed to be supported, check\n     * if DCO is truly available on the system\n     */\n    return dco_available(msglevel);\n}\n\nbool\ndco_check_option(msglvl_t msglevel, const struct options *o)\n{\n    /* At this point the ciphers have already been normalised */\n    if (o->enable_ncp_fallback\n        && !tls_item_in_cipher_list(o->ciphername, dco_get_supported_ciphers()))\n    {\n        msg(msglevel,\n            \"Note: --data-ciphers-fallback with cipher '%s' \"\n            \"disables data channel offload.\",\n            o->ciphername);\n        return false;\n    }\n\n#if defined(USE_COMP)\n    if (o->comp.alg != COMP_ALG_UNDEF || o->comp.flags & COMP_F_ALLOW_ASYM)\n    {\n        msg(msglevel,\n            \"Note: '--allow-compression' is not set to 'no', disabling data channel offload.\");\n\n        if (o->mode == MODE_SERVER && !(o->comp.flags & COMP_F_MIGRATE))\n        {\n            /* We can end up here from the multi.c call, only print the\n             * note if it is not already enabled */\n            msg(msglevel, \"Consider using the '--compress migrate' option.\");\n        }\n        return false;\n    }\n#endif\n\n    struct gc_arena gc = gc_new();\n    char *tmp_ciphers = string_alloc(o->ncp_ciphers, &gc);\n    const char *token;\n    while ((token = strsep(&tmp_ciphers, \":\")))\n    {\n        if (!tls_item_in_cipher_list(token, dco_get_supported_ciphers()))\n        {\n            msg(msglevel,\n                \"Note: cipher '%s' in --data-ciphers is not supported \"\n                \"by ovpn-dco, disabling data channel offload.\",\n                token);\n            gc_free(&gc);\n            return false;\n        }\n        /* FreeBSD supports none as cipher type but requires auth none to be\n         * be also enabled */\n        if (strcmp(token, \"none\") == 0 && strcmp(o->authname, \"none\") != 0)\n        {\n            msg(msglevel,\n                \"Note: cipher '%s' in --data-ciphers is only supported \"\n                \"with --auth=none by ovpn-dco, disabling data channel \"\n                \"offload.\",\n                token);\n            gc_free(&gc);\n            return false;\n        }\n    }\n    gc_free(&gc);\n\n    return true;\n}\n\nbool\ndco_check_pull_options(msglvl_t msglevel, const struct options *o)\n{\n    if (!o->use_peer_id)\n    {\n        msg(msglevel, \"OPTIONS IMPORT: Server did not request DATA_V2 packet \"\n                      \"format required for data channel offload\");\n        return false;\n    }\n    return true;\n}\n\nint\ndco_p2p_add_new_peer(struct context *c)\n{\n    if (!dco_enabled(&c->options))\n    {\n        return 0;\n    }\n\n    struct link_socket *sock = c->c2.link_sockets[0];\n\n    ASSERT(sock->info.connection_established);\n\n    struct sockaddr *remoteaddr = &sock->info.lsa->actual.dest.addr.sa;\n    struct tls_multi *multi = c->c2.tls_multi;\n#ifdef TARGET_FREEBSD\n    /* In Linux in P2P mode the kernel automatically removes an existing peer\n     * when adding a new peer. FreeBSD needs to explicitly be told to do that */\n    if (c->c2.tls_multi->dco_peer_id != -1)\n    {\n        dco_del_peer(&c->c1.tuntap->dco, c->c2.tls_multi->dco_peer_id);\n        c->c2.tls_multi->dco_peer_id = -1;\n    }\n#endif\n    int ret = dco_new_peer(&c->c1.tuntap->dco, multi->peer_id, sock->sd, NULL,\n                           proto_is_dgram(sock->info.proto) ? remoteaddr : NULL, NULL, NULL);\n    if (ret < 0)\n    {\n        return ret;\n    }\n\n    c->c2.tls_multi->dco_peer_id = multi->peer_id;\n\n    return 0;\n}\n\nvoid\ndco_remove_peer(struct context *c)\n{\n    if (!dco_enabled(&c->options))\n    {\n        return;\n    }\n\n    if (c->c1.tuntap && c->c2.tls_multi && c->c2.tls_multi->dco_peer_id != -1)\n    {\n        dco_del_peer(&c->c1.tuntap->dco, c->c2.tls_multi->dco_peer_id);\n        c->c2.tls_multi->dco_peer_id = -1;\n    }\n}\n\nstatic bool\ndco_multi_get_localaddr(struct multi_context *m, struct multi_instance *mi,\n                        struct sockaddr_storage *local)\n{\n#if ENABLE_IP_PKTINFO\n    struct context *c = &mi->context;\n\n    if (!proto_is_udp(c->c2.link_sockets[0]->info.proto)\n        || !(c->options.sockflags & SF_USE_IP_PKTINFO))\n    {\n        return false;\n    }\n\n    struct link_socket_actual *actual = &c->c2.link_socket_infos[0]->lsa->actual;\n\n    switch (actual->dest.addr.sa.sa_family)\n    {\n        case AF_INET:\n        {\n            struct sockaddr_in *sock_in4 = (struct sockaddr_in *)local;\n#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)\n            sock_in4->sin_addr = actual->pi.in4.ipi_spec_dst;\n#elif defined(IP_RECVDSTADDR)\n            sock_in4->sin_addr = actual->pi.in4;\n#else\n            /* source IP not available on this platform */\n            return false;\n#endif\n            sock_in4->sin_family = AF_INET;\n            break;\n        }\n\n        case AF_INET6:\n        {\n            struct sockaddr_in6 *sock_in6 = (struct sockaddr_in6 *)local;\n            sock_in6->sin6_addr = actual->pi.in6.ipi6_addr;\n            sock_in6->sin6_family = AF_INET6;\n            break;\n        }\n\n        default:\n            ASSERT(false);\n    }\n\n    return true;\n#else  /* if ENABLE_IP_PKTINFO */\n    return false;\n#endif /* if ENABLE_IP_PKTINFO */\n}\n\nint\ndco_multi_add_new_peer(struct multi_context *m, struct multi_instance *mi)\n{\n    struct context *c = &mi->context;\n\n    int peer_id = c->c2.tls_multi->peer_id;\n    struct sockaddr *remoteaddr, *localaddr = NULL;\n    struct sockaddr_storage local = { 0 };\n    const socket_descriptor_t sd = c->c2.link_sockets[0]->sd;\n\n\n    if (c->mode == CM_CHILD_TCP)\n    {\n        /* the remote address will be inferred from the TCP socket endpoint */\n        remoteaddr = NULL;\n    }\n    else\n    {\n        ASSERT(c->c2.link_socket_infos[0]->connection_established);\n        remoteaddr = &c->c2.link_socket_infos[0]->lsa->actual.dest.addr.sa;\n    }\n\n    /* In server mode we need to fetch the remote addresses from the push config */\n    struct in_addr vpn_ip4 = { 0 };\n    struct in_addr *vpn_addr4 = NULL;\n    if (c->c2.push_ifconfig_defined)\n    {\n        vpn_ip4.s_addr = htonl(c->c2.push_ifconfig_local);\n        vpn_addr4 = &vpn_ip4;\n    }\n\n    struct in6_addr *vpn_addr6 = NULL;\n    if (c->c2.push_ifconfig_ipv6_defined)\n    {\n        vpn_addr6 = &c->c2.push_ifconfig_ipv6_local;\n    }\n\n    if (dco_multi_get_localaddr(m, mi, &local))\n    {\n        localaddr = (struct sockaddr *)&local;\n    }\n\n    int ret =\n        dco_new_peer(&c->c1.tuntap->dco, peer_id, sd, localaddr, remoteaddr, vpn_addr4, vpn_addr6);\n    if (ret < 0)\n    {\n        return ret;\n    }\n\n    c->c2.tls_multi->dco_peer_id = peer_id;\n\n    return 0;\n}\n\nvoid\ndco_install_iroute(struct multi_context *m, struct multi_instance *mi, struct mroute_addr *addr)\n{\n#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32)\n    if (!dco_enabled(&m->top.options))\n    {\n        return;\n    }\n\n    int addrtype = (addr->type & MR_ADDR_MASK);\n\n    /* If we do not have local IP addr to install, skip the route */\n    if ((addrtype == MR_ADDR_IPV6 && !mi->context.c2.push_ifconfig_ipv6_defined)\n        || (addrtype == MR_ADDR_IPV4 && !mi->context.c2.push_ifconfig_defined))\n    {\n        return;\n    }\n\n#if defined(_WIN32)\n    if (addr->type & MR_ONLINK_DCO_ADDR)\n    {\n        /* Windows does not need these extra routes, so we ignore/skip them */\n        return;\n    }\n#endif\n\n    struct context *c = &mi->context;\n    if (addrtype == MR_ADDR_IPV6)\n    {\n#if defined(_WIN32)\n        dco_win_add_iroute_ipv6(&c->c1.tuntap->dco, addr->v6.addr, addr->netbits,\n                                c->c2.tls_multi->peer_id);\n#else\n        const struct in6_addr *gateway = &mi->context.c2.push_ifconfig_ipv6_local;\n        if (addr->type & MR_ONLINK_DCO_ADDR)\n        {\n            gateway = NULL;\n        }\n\n        net_route_v6_add(&m->top.net_ctx, &addr->v6.addr, addr->netbits,\n                         gateway, c->c1.tuntap->actual_name, 0,\n                         DCO_IROUTE_METRIC);\n#endif\n    }\n    else if (addrtype == MR_ADDR_IPV4)\n    {\n#if defined(_WIN32)\n        dco_win_add_iroute_ipv4(&c->c1.tuntap->dco, addr->v4.addr, addr->netbits,\n                                c->c2.tls_multi->peer_id);\n#else\n        in_addr_t dest = htonl(addr->v4.addr);\n        const in_addr_t *gateway = &mi->context.c2.push_ifconfig_local;\n        if (addr->type & MR_ONLINK_DCO_ADDR)\n        {\n            gateway = NULL;\n        }\n\n        net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, gateway,\n                         c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);\n#endif\n    }\n#endif /* if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32) */\n}\n\nvoid\ndco_delete_iroutes(struct multi_context *m, struct multi_instance *mi)\n{\n#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32)\n    if (!dco_enabled(&m->top.options))\n    {\n        return;\n    }\n    ASSERT(TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN);\n\n    struct context *c = &mi->context;\n\n    if (mi->context.c2.push_ifconfig_defined)\n    {\n        for (const struct iroute *ir = c->options.iroutes; ir; ir = ir->next)\n        {\n#if defined(_WIN32)\n            dco_win_del_iroute_ipv4(&c->c1.tuntap->dco, htonl(ir->network), ir->netbits);\n#else\n            net_route_v4_del(&m->top.net_ctx, &ir->network, ir->netbits,\n                             &mi->context.c2.push_ifconfig_local, c->c1.tuntap->actual_name, 0,\n                             DCO_IROUTE_METRIC);\n#endif\n        }\n\n#if !defined(_WIN32)\n        /* Check if we added a host route as the assigned client IP address was\n         * not in the on link scope defined by --ifconfig */\n        in_addr_t ifconfig_local = mi->context.c2.push_ifconfig_local;\n\n        if (multi_check_push_ifconfig_extra_route(mi, htonl(ifconfig_local)))\n        {\n            /* On windows we do not install these routes, so we also do not need to delete them */\n            net_route_v4_del(&m->top.net_ctx, &ifconfig_local,\n                             32, NULL, c->c1.tuntap->actual_name, 0,\n                             DCO_IROUTE_METRIC);\n        }\n#endif\n    }\n\n    if (mi->context.c2.push_ifconfig_ipv6_defined)\n    {\n        for (const struct iroute_ipv6 *ir6 = c->options.iroutes_ipv6; ir6; ir6 = ir6->next)\n        {\n#if defined(_WIN32)\n            dco_win_del_iroute_ipv6(&c->c1.tuntap->dco, ir6->network, ir6->netbits);\n#else\n            net_route_v6_del(&m->top.net_ctx, &ir6->network, ir6->netbits,\n                             &mi->context.c2.push_ifconfig_ipv6_local, c->c1.tuntap->actual_name, 0,\n                             DCO_IROUTE_METRIC);\n#endif\n        }\n\n        /* Checked if we added a host route as the assigned client IP address was\n         * outside the --ifconfig-ipv6 tun interface config */\n#if !defined(_WIN32)\n        struct in6_addr *dest = &mi->context.c2.push_ifconfig_ipv6_local;\n        if (multi_check_push_ifconfig_ipv6_extra_route(mi, dest))\n        {\n            /* On windows we do not install these routes, so we also do not need to delete them */\n            net_route_v6_del(&m->top.net_ctx, dest, 128, NULL,\n                             c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);\n        }\n#endif\n    }\n#endif /* if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32) */\n}\n\n#endif /* defined(ENABLE_DCO) */\n"
  },
  {
    "path": "src/openvpn/dco.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2021-2026 Arne Schwabe <arne@rfc2549.org>\n *  Copyright (C) 2021-2026 Antonio Quartulli <a@unstable.cc>\n *  Copyright (C) 2021-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n#ifndef DCO_H\n#define DCO_H\n\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"dco_internal.h\"\n#include \"networking.h\"\n\n/* forward declarations (including other headers leads to nasty include\n * order problems)\n */\nstruct event_set;\nstruct key2;\nstruct key_state;\nstruct multi_context;\nstruct multi_instance;\nstruct mroute_addr;\nstruct options;\nstruct tls_multi;\nstruct tuntap;\n\n#define DCO_IROUTE_METRIC  100\n#define DCO_DEFAULT_METRIC 200\n\n#if defined(ENABLE_DCO)\n\n/**\n * Check whether ovpn-dco is available on this platform (i.e. kernel support is\n * there)\n *\n * @param msglevel      level to print messages to\n * @return              true if ovpn-dco is available, false otherwise\n */\nbool dco_available(msglvl_t msglevel);\n\n\n/**\n * Return a human readable string representing the DCO version\n *\n * @param gc    the garbage collector to use for any dynamic allocation\n * @return      a pointer to the string (allocated via gc) containing the string\n */\nconst char *dco_version_string(struct gc_arena *gc);\n\n/**\n * Check whether the options struct has any option that is not supported by\n * our current dco implementation. If so print a warning at warning level\n * for the first conflicting option found and return false.\n *\n * @param msglevel  the msg level to use to print the warnings\n * @param o         the options struct that hold the options\n * @return          true if no conflict was detected, false otherwise\n */\nbool dco_check_option(msglvl_t msglevel, const struct options *o);\n\n/**\n * Check whether the options struct has any further option that is not supported\n * by our current dco implementation during early startup.\n * If so print a warning at warning level for the first conflicting option\n * found and return false.\n *\n * @param msglevel  the msg level to use to print the warnings\n * @param o         the options struct that hold the options\n * @return          true if no conflict was detected, false otherwise\n */\nbool dco_check_startup_option(msglvl_t msglevel, const struct options *o);\n\n/**\n * Check whether any of the options pushed by the server is not supported by\n * our current dco implementation. If so print a warning at warning level\n * for the first conflicting option found and return false.\n *\n * @param msglevel  the msg level to use to print the warnings\n * @param o         the options struct that hold the options\n * @return          true if no conflict was detected, false otherwise\n */\nbool dco_check_pull_options(msglvl_t msglevel, const struct options *o);\n\n/**\n * Initialize the DCO context\n *\n * @param c         the main instance context\n * @return          true on success, false otherwise\n */\nbool ovpn_dco_init(struct context *c);\n\n/**\n * Open/create a DCO interface\n *\n * @param tt        the tuntap context\n * @param ctx       the networking API context\n * @param dev       the name of the interface to create\n * @return          0 on success or a negative error code otherwise\n */\nint open_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev);\n\n/**\n * Close/destroy a DCO interface\n *\n * @param tt        the tuntap context\n * @param ctx       the networking API context\n */\nvoid close_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx);\n\n/**\n * Read and process data from the DCO communication channel\n * (i.e. a control packet)\n *\n * @param dco       the DCO context\n * @return          0 on success or a negative error code otherwise\n */\nint dco_read_and_process(dco_context_t *dco);\n\n/**\n * Install a DCO in the main event loop\n */\nvoid dco_event_set(dco_context_t *dco, struct event_set *es, void *arg);\n\n/**\n * Install the key material in DCO for the specified peer.\n * The key is installed in the primary slot when no other key was yet installed.\n * Any subsequent invocation will install the key in the secondary slot.\n *\n * @param multi     the TLS context of the current instance\n * @param ks        the state of the key being installed\n * @param key2      the container for the raw key material\n * @param key_direction the key direction to be used to extract the material\n * @param ciphername    the name of the cipher to use the key with\n * @param server    whether we are running on a server instance or not\n *\n * @return          0 on success or a negative error code otherwise\n */\nint init_key_dco_bi(struct tls_multi *multi, struct key_state *ks, const struct key2 *key2,\n                    int key_direction, const char *ciphername, bool server);\n\n/**\n * Possibly swap or wipe keys from DCO\n *\n * @param dco           DCO device context\n * @param multi         TLS multi instance\n *\n * @return              returns false if an error occurred that is not\n *                      recoverable and should reset the connection\n */\nbool dco_update_keys(dco_context_t *dco, struct tls_multi *multi);\n/**\n * Install a new peer in DCO - to be called by a CLIENT (or P2P) instance\n *\n * @param c         the main instance context\n * @return          0 on success or a negative error code otherwise\n */\nint dco_p2p_add_new_peer(struct context *c);\n\n/**\n * Modify DCO peer options. Special values are 0 (disable)\n * and -1 (do not touch).\n *\n * @param dco                DCO device context\n * @param peer_id            the ID of the peer to be modified\n * @param keepalive_interval keepalive interval in seconds\n * @param keepalive_timeout  keepalive timeout in seconds\n * @param mss                TCP MSS value\n *\n * @return                   0 on success or a negative error code otherwise\n */\nint dco_set_peer(dco_context_t *dco, unsigned int peerid, int keepalive_interval,\n                 int keepalive_timeout, int mss);\n\n/**\n * Remove a peer from DCO\n *\n * @param c         the main instance context of the peer to remove\n */\nvoid dco_remove_peer(struct context *c);\n\n/**\n * Install a new peer in DCO - to be called by a SERVER instance\n *\n * @param m         the server context\n * @param mi        the client instance\n * @return          0 on success or a negative error code otherwise\n */\nint dco_multi_add_new_peer(struct multi_context *m, struct multi_instance *mi);\n\n/**\n * Install an iroute in DCO, which means adding a route to the system routing\n * table. To be called by a SERVER instance only.\n *\n * @param m         the server context\n * @param mi        the client instance acting as nexthop for the route\n * @param addr      the route to add\n */\nvoid dco_install_iroute(struct multi_context *m, struct multi_instance *mi,\n                        struct mroute_addr *addr);\n\n/**\n * Remove all routes added through the specified client\n *\n * @param m         the server context\n * @param mi        the client instance for which routes have to be removed\n */\nvoid dco_delete_iroutes(struct multi_context *m, struct multi_instance *mi);\n\n/**\n * Update traffic statistics for all peers\n *\n * @param dco                   DCO device context\n * @param raise_sigusr1_on_err  whether to raise SIGUSR1 on error\n **/\nint dco_get_peer_stats_multi(dco_context_t *dco, const bool raise_sigusr1_on_err);\n\n/**\n * Update traffic statistics for single peer\n *\n * @param c                     instance context of the peer\n * @param raise_sigusr1_on_err  whether to raise SIGUSR1 on error\n **/\nint dco_get_peer_stats(struct context *c, const bool raise_sigusr1_on_err);\n\n/**\n * Retrieve the list of ciphers supported by the current platform\n *\n * @return                   list of colon-separated ciphers\n */\nconst char *dco_get_supported_ciphers(void);\n\n/**\n * Return whether the dco implementation supports the new protocol features of\n * a 64 bit packet counter and AEAD tag at the end.\n */\nbool\ndco_supports_epoch_data(struct context *c);\n#else  /* if defined(ENABLE_DCO) */\n\ntypedef void *dco_context_t;\n\nstatic inline bool\ndco_available(msglvl_t msglevel)\n{\n    return false;\n}\n\nstatic inline const char *\ndco_version_string(struct gc_arena *gc)\n{\n    return \"not-compiled\";\n}\n\nstatic inline bool\ndco_check_option(msglvl_t msglevel, const struct options *o)\n{\n    return false;\n}\n\nstatic inline bool\ndco_check_startup_option(msglvl_t msglevel, const struct options *o)\n{\n    return false;\n}\n\nstatic inline bool\ndco_check_pull_options(msglvl_t msglevel, const struct options *o)\n{\n    return false;\n}\n\nstatic inline bool\novpn_dco_init(struct context *c)\n{\n    return true;\n}\n\nstatic inline int\nopen_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev)\n{\n    return 0;\n}\n\nstatic inline void\nclose_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n}\n\nstatic inline int\ndco_read_and_process(dco_context_t *dco)\n{\n    ASSERT(false);\n    return 0;\n}\n\nstatic inline void\ndco_event_set(dco_context_t *dco, struct event_set *es, void *arg)\n{\n}\n\nstatic inline int\ninit_key_dco_bi(struct tls_multi *multi, struct key_state *ks, const struct key2 *key2,\n                int key_direction, const char *ciphername, bool server)\n{\n    return 0;\n}\n\nstatic inline bool\ndco_update_keys(dco_context_t *dco, struct tls_multi *multi)\n{\n    ASSERT(false);\n    return false;\n}\n\nstatic inline int\ndco_p2p_add_new_peer(struct context *c)\n{\n    return 0;\n}\n\nstatic inline int\ndco_set_peer(dco_context_t *dco, unsigned int peerid, int keepalive_interval, int keepalive_timeout,\n             int mss)\n{\n    return 0;\n}\n\nstatic inline void\ndco_remove_peer(struct context *c)\n{\n}\n\nstatic inline int\ndco_multi_add_new_peer(struct multi_context *m, struct multi_instance *mi)\n{\n    return 0;\n}\n\nstatic inline void\ndco_install_iroute(struct multi_context *m, struct multi_instance *mi, struct mroute_addr *addr)\n{\n}\n\nstatic inline void\ndco_delete_iroutes(struct multi_context *m, struct multi_instance *mi)\n{\n}\n\nstatic inline int\ndco_get_peer_stats_multi(dco_context_t *dco, const bool raise_sigusr1_on_err)\n{\n    return 0;\n}\n\nstatic inline int\ndco_get_peer_stats(struct context *c, const bool raise_sigusr1_on_err)\n{\n    return 0;\n}\n\nstatic inline const char *\ndco_get_supported_ciphers(void)\n{\n    return \"\";\n}\n\nstatic inline bool\ndco_supports_epoch_data(struct context *c)\n{\n    return false;\n}\n#endif /* defined(ENABLE_DCO) */\n#endif /* ifndef DCO_H */\n"
  },
  {
    "path": "src/openvpn/dco_freebsd.c",
    "content": "/*\n *  Interface to FreeBSD dco networking code\n *\n *  Copyright (C) 2022 Rubicon Communications, LLC (Netgate). All Rights Reserved.\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#if defined(ENABLE_DCO) && defined(TARGET_FREEBSD)\n\n#include \"syshead.h\"\n\n#include <sys/param.h>\n#include <sys/linker.h>\n#include <sys/nv.h>\n#include <sys/utsname.h>\n\n#include <netinet/in.h>\n\n#include \"dco_freebsd.h\"\n#include \"dco.h\"\n#include \"tun.h\"\n#include \"crypto.h\"\n#include \"multi.h\"\n#include \"ssl_common.h\"\n\nstatic nvlist_t *\nsockaddr_to_nvlist(const struct sockaddr *sa)\n{\n    nvlist_t *nvl = nvlist_create(0);\n\n    nvlist_add_number(nvl, \"af\", sa->sa_family);\n\n    switch (sa->sa_family)\n    {\n        case AF_INET:\n        {\n            const struct sockaddr_in *in = (const struct sockaddr_in *)sa;\n            nvlist_add_binary(nvl, \"address\", &in->sin_addr, sizeof(in->sin_addr));\n            nvlist_add_number(nvl, \"port\", in->sin_port);\n            break;\n        }\n\n        case AF_INET6:\n        {\n            const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *)sa;\n            nvlist_add_binary(nvl, \"address\", &in6->sin6_addr, sizeof(in6->sin6_addr));\n            nvlist_add_number(nvl, \"port\", in6->sin6_port);\n            nvlist_add_number(nvl, \"scopeid\", in6->sin6_scope_id);\n            break;\n        }\n\n        default:\n            ASSERT(0);\n    }\n\n    return (nvl);\n}\n\nstatic bool\nnvlist_to_sockaddr(const nvlist_t *nvl, struct sockaddr_storage *ss)\n{\n    if (!nvlist_exists_number(nvl, \"af\"))\n    {\n        return (false);\n    }\n    if (!nvlist_exists_binary(nvl, \"address\"))\n    {\n        return (false);\n    }\n    if (!nvlist_exists_number(nvl, \"port\"))\n    {\n        return (false);\n    }\n\n    ss->ss_family = (unsigned char)nvlist_get_number(nvl, \"af\");\n\n    switch (ss->ss_family)\n    {\n        case AF_INET:\n        {\n            struct sockaddr_in *in = (struct sockaddr_in *)ss;\n            const void *data;\n            size_t len;\n\n            in->sin_len = sizeof(*in);\n            data = nvlist_get_binary(nvl, \"address\", &len);\n            ASSERT(len == sizeof(in->sin_addr));\n            memcpy(&in->sin_addr, data, sizeof(in->sin_addr));\n            in->sin_port = (in_port_t)nvlist_get_number(nvl, \"port\");\n            break;\n        }\n\n        case AF_INET6:\n        {\n            struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)ss;\n            const void *data;\n            size_t len;\n\n            in6->sin6_len = sizeof(*in6);\n            data = nvlist_get_binary(nvl, \"address\", &len);\n            ASSERT(len == sizeof(in6->sin6_addr));\n            memcpy(&in6->sin6_addr, data, sizeof(in6->sin6_addr));\n            in6->sin6_port = (in_port_t)nvlist_get_number(nvl, \"port\");\n\n            if (nvlist_exists_number(nvl, \"scopeid\"))\n            {\n                in6->sin6_scope_id = (uint32_t)nvlist_get_number(nvl, \"scopeid\");\n            }\n            break;\n        }\n\n        default:\n            return (false);\n    }\n\n    return (true);\n}\n\nint\ndco_new_peer(dco_context_t *dco, unsigned int peerid, int sd, struct sockaddr *localaddr,\n             struct sockaddr *remoteaddr, struct in_addr *vpn_ipv4, struct in6_addr *vpn_ipv6)\n{\n    struct ifdrv drv;\n    nvlist_t *nvl, *local_nvl, *remote_nvl;\n    int ret;\n\n    nvl = nvlist_create(0);\n\n    msg(D_DCO_DEBUG, \"%s: peer-id %u, fd %d\", __func__, peerid, sd);\n\n    if (localaddr)\n    {\n        local_nvl = sockaddr_to_nvlist(localaddr);\n        nvlist_add_nvlist(nvl, \"local\", local_nvl);\n    }\n\n    if (remoteaddr)\n    {\n        remote_nvl = sockaddr_to_nvlist(remoteaddr);\n        nvlist_add_nvlist(nvl, \"remote\", remote_nvl);\n    }\n\n    if (vpn_ipv4)\n    {\n        nvlist_add_binary(nvl, \"vpn_ipv4\", &vpn_ipv4->s_addr, sizeof(vpn_ipv4->s_addr));\n    }\n\n    if (vpn_ipv6)\n    {\n        nvlist_add_binary(nvl, \"vpn_ipv6\", vpn_ipv6, sizeof(*vpn_ipv6));\n    }\n\n    nvlist_add_number(nvl, \"fd\", sd);\n    nvlist_add_number(nvl, \"peerid\", peerid);\n\n    CLEAR(drv);\n    snprintf(drv.ifd_name, IFNAMSIZ, \"%s\", dco->ifname);\n    drv.ifd_cmd = OVPN_NEW_PEER;\n    drv.ifd_data = nvlist_pack(nvl, &drv.ifd_len);\n\n    ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv);\n    if (ret)\n    {\n        msg(M_ERR, \"Failed to create new peer\");\n    }\n\n    free(drv.ifd_data);\n    if (localaddr)\n    {\n        nvlist_destroy(local_nvl);\n    }\n    if (remoteaddr)\n    {\n        nvlist_destroy(remote_nvl);\n    }\n    nvlist_destroy(nvl);\n\n    return ret;\n}\n\nstatic int\nopen_fd(dco_context_t *dco)\n{\n    int ret;\n\n    ret = pipe2(dco->pipefd, O_CLOEXEC | O_NONBLOCK);\n    if (ret != 0)\n    {\n        return -1;\n    }\n\n    dco->fd = socket(AF_LOCAL, SOCK_DGRAM | SOCK_CLOEXEC, 0);\n    if (dco->fd != -1)\n    {\n        dco->open = true;\n    }\n\n    return dco->fd;\n}\n\nstatic void\nclose_fd(dco_context_t *dco)\n{\n    close(dco->pipefd[0]);\n    close(dco->pipefd[1]);\n    close(dco->fd);\n}\n\nbool\novpn_dco_init(struct context *c)\n{\n    c->c1.tuntap->dco.c = c;\n\n    if (open_fd(&c->c1.tuntap->dco) < 0)\n    {\n        msg(M_ERR, \"Failed to open socket\");\n        return false;\n    }\n    return true;\n}\n\nstatic int\ndco_set_ifmode(dco_context_t *dco, int ifmode)\n{\n    struct ifdrv drv;\n    nvlist_t *nvl;\n    int ret;\n\n    nvl = nvlist_create(0);\n    nvlist_add_number(nvl, \"ifmode\", ifmode);\n\n    CLEAR(drv);\n    snprintf(drv.ifd_name, IFNAMSIZ, \"%s\", dco->ifname);\n    drv.ifd_cmd = OVPN_SET_IFMODE;\n    drv.ifd_data = nvlist_pack(nvl, &drv.ifd_len);\n\n    ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv);\n    if (ret)\n    {\n        msg(M_WARN | M_ERRNO, \"dco_set_ifmode: failed to set ifmode=%08x\", ifmode);\n    }\n\n    free(drv.ifd_data);\n    nvlist_destroy(nvl);\n\n    return ret;\n}\n\nstatic int\ncreate_interface(struct tuntap *tt, const char *dev)\n{\n    int ret;\n    struct ifreq ifr;\n\n    CLEAR(ifr);\n\n    /* Create ovpnx first, then rename it. */\n    snprintf(ifr.ifr_name, IFNAMSIZ, \"ovpn\");\n    ret = ioctl(tt->dco.fd, SIOCIFCREATE2, &ifr);\n    if (ret)\n    {\n        ret = -errno;\n        msg(M_WARN | M_ERRNO, \"Failed to create interface %s (SIOCIFCREATE2)\", ifr.ifr_name);\n        return ret;\n    }\n\n    /* Rename */\n    if (!strcmp(dev, \"tun\"))\n    {\n        ifr.ifr_data = \"ovpn\";\n    }\n    else\n    {\n        ifr.ifr_data = (char *)dev;\n    }\n\n    snprintf(tt->dco.ifname, IFNAMSIZ, \"%s\", ifr.ifr_data);\n\n    ret = ioctl(tt->dco.fd, SIOCSIFNAME, &ifr);\n    if (ret)\n    {\n        ret = -errno;\n        /* Delete the created interface again. */\n        (void)ioctl(tt->dco.fd, SIOCIFDESTROY, &ifr);\n        msg(M_WARN | M_ERRNO, \"Failed to create interface %s (SIOCSIFNAME)\", ifr.ifr_data);\n        return ret;\n    }\n\n    return 0;\n}\n\nstatic int\nremove_interface(struct tuntap *tt)\n{\n    int ret;\n    struct ifreq ifr;\n\n    CLEAR(ifr);\n    snprintf(ifr.ifr_name, IFNAMSIZ, \"%s\", tt->dco.ifname);\n\n    ret = ioctl(tt->dco.fd, SIOCIFDESTROY, &ifr);\n    if (ret)\n    {\n        msg(M_ERR, \"Failed to remove interface %s\", ifr.ifr_name);\n    }\n\n    tt->dco.ifname[0] = 0;\n\n    return ret;\n}\n\nint\nopen_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev)\n{\n    int ret = create_interface(tt, dev);\n\n    if (ret >= 0 || ret == -EEXIST)\n    {\n        /* see \"Interface Flags\" in ifnet(9) */\n        int i = IFF_POINTOPOINT | IFF_MULTICAST;\n        if (tt->topology == TOP_SUBNET)\n        {\n            i = IFF_BROADCAST | IFF_MULTICAST;\n        }\n        dco_set_ifmode(&tt->dco, i);\n    }\n\n    return ret;\n}\n\nvoid\nclose_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    remove_interface(tt);\n    close_fd(&tt->dco);\n}\n\nint\ndco_swap_keys(dco_context_t *dco, unsigned int peerid)\n{\n    struct ifdrv drv;\n    nvlist_t *nvl;\n    int ret;\n\n    msg(D_DCO_DEBUG, \"%s: peer-id %u\", __func__, peerid);\n\n    nvl = nvlist_create(0);\n    nvlist_add_number(nvl, \"peerid\", peerid);\n\n    CLEAR(drv);\n    snprintf(drv.ifd_name, IFNAMSIZ, \"%s\", dco->ifname);\n    drv.ifd_cmd = OVPN_SWAP_KEYS;\n    drv.ifd_data = nvlist_pack(nvl, &drv.ifd_len);\n\n    ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv);\n    if (ret)\n    {\n        msg(M_WARN | M_ERRNO, \"Failed to swap keys\");\n    }\n\n    free(drv.ifd_data);\n    nvlist_destroy(nvl);\n\n    return ret;\n}\n\nint\ndco_del_peer(dco_context_t *dco, unsigned int peerid)\n{\n    struct ifdrv drv;\n    nvlist_t *nvl;\n    int ret;\n\n    msg(D_DCO_DEBUG, \"%s: peer-id %u\", __func__, peerid);\n\n    nvl = nvlist_create(0);\n    nvlist_add_number(nvl, \"peerid\", peerid);\n\n    CLEAR(drv);\n    snprintf(drv.ifd_name, IFNAMSIZ, \"%s\", dco->ifname);\n    drv.ifd_cmd = OVPN_DEL_PEER;\n    drv.ifd_data = nvlist_pack(nvl, &drv.ifd_len);\n\n    ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv);\n    if (ret)\n    {\n        msg(M_WARN | M_ERRNO, \"Failed to delete peer\");\n    }\n\n    free(drv.ifd_data);\n    nvlist_destroy(nvl);\n\n    return ret;\n}\n\nint\ndco_del_key(dco_context_t *dco, unsigned int peerid, dco_key_slot_t slot)\n{\n    struct ifdrv drv;\n    nvlist_t *nvl;\n    int ret;\n\n    msg(D_DCO_DEBUG, \"%s: peer-id %u, slot %d\", __func__, peerid, slot);\n\n    nvl = nvlist_create(0);\n    nvlist_add_number(nvl, \"slot\", slot);\n    nvlist_add_number(nvl, \"peerid\", peerid);\n\n    CLEAR(drv);\n    snprintf(drv.ifd_name, IFNAMSIZ, \"%s\", dco->ifname);\n    drv.ifd_cmd = OVPN_DEL_KEY;\n    drv.ifd_data = nvlist_pack(nvl, &drv.ifd_len);\n\n    ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv);\n    if (ret)\n    {\n        msg(M_WARN | M_ERRNO, \"Failed to delete key\");\n    }\n\n    free(drv.ifd_data);\n    nvlist_destroy(nvl);\n\n    return ret;\n}\n\nstatic nvlist_t *\nkey_to_nvlist(const uint8_t *key, const uint8_t *implicit_iv, const char *ciphername)\n{\n    nvlist_t *nvl;\n    size_t key_len;\n\n    nvl = nvlist_create(0);\n\n    nvlist_add_string(nvl, \"cipher\", ciphername);\n\n    if (strcmp(ciphername, \"none\") != 0)\n    {\n        key_len = cipher_kt_key_size(ciphername);\n\n        nvlist_add_binary(nvl, \"key\", key, key_len);\n        nvlist_add_binary(nvl, \"iv\", implicit_iv, 8);\n    }\n\n    return (nvl);\n}\n\nstatic int\nstart_tun(dco_context_t *dco)\n{\n    struct ifdrv drv;\n    int ret;\n\n    CLEAR(drv);\n    snprintf(drv.ifd_name, IFNAMSIZ, \"%s\", dco->ifname);\n    drv.ifd_cmd = OVPN_START_VPN;\n\n    ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv);\n    if (ret)\n    {\n        msg(M_ERR, \"Failed to start vpn\");\n    }\n\n    return ret;\n}\n\nint\ndco_new_key(dco_context_t *dco, unsigned int peerid, int keyid, dco_key_slot_t slot,\n            const uint8_t *encrypt_key, const uint8_t *encrypt_iv, const uint8_t *decrypt_key,\n            const uint8_t *decrypt_iv, const char *ciphername, bool epoch)\n{\n    struct ifdrv drv;\n    nvlist_t *nvl, *encrypt_nvl, *decrypt_nvl;\n    int ret;\n\n    msg(D_DCO_DEBUG, \"%s: slot %d, key-id %d, peer-id %u, cipher %s, epoch %d\", __func__, slot, keyid, peerid,\n        ciphername, epoch);\n\n    nvl = nvlist_create(0);\n\n    nvlist_add_number(nvl, \"slot\", slot);\n    nvlist_add_number(nvl, \"keyid\", keyid);\n    nvlist_add_number(nvl, \"peerid\", peerid);\n\n    encrypt_nvl = key_to_nvlist(encrypt_key, encrypt_iv, ciphername);\n    decrypt_nvl = key_to_nvlist(decrypt_key, decrypt_iv, ciphername);\n\n    nvlist_add_nvlist(nvl, \"encrypt\", encrypt_nvl);\n    nvlist_add_nvlist(nvl, \"decrypt\", decrypt_nvl);\n\n    CLEAR(drv);\n    snprintf(drv.ifd_name, IFNAMSIZ, \"%s\", dco->ifname);\n    drv.ifd_cmd = OVPN_NEW_KEY;\n    drv.ifd_data = nvlist_pack(nvl, &drv.ifd_len);\n\n    ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv);\n    if (ret)\n    {\n        msg(M_ERR, \"Failed to set key\");\n    }\n    else\n    {\n        ret = start_tun(dco);\n    }\n\n    free(drv.ifd_data);\n    nvlist_destroy(encrypt_nvl);\n    nvlist_destroy(decrypt_nvl);\n    nvlist_destroy(nvl);\n\n    return ret;\n}\n\nint\ndco_set_peer(dco_context_t *dco, unsigned int peerid, int keepalive_interval, int keepalive_timeout,\n             int mss)\n{\n    struct ifdrv drv;\n    nvlist_t *nvl;\n    int ret;\n\n    msg(D_DCO_DEBUG, \"%s: peer-id %u, ping interval %d, ping timeout %d\", __func__, peerid,\n        keepalive_interval, keepalive_timeout);\n\n    nvl = nvlist_create(0);\n    nvlist_add_number(nvl, \"peerid\", peerid);\n    nvlist_add_number(nvl, \"interval\", keepalive_interval);\n    nvlist_add_number(nvl, \"timeout\", keepalive_timeout);\n\n    CLEAR(drv);\n    snprintf(drv.ifd_name, IFNAMSIZ, \"%s\", dco->ifname);\n    drv.ifd_cmd = OVPN_SET_PEER;\n    drv.ifd_data = nvlist_pack(nvl, &drv.ifd_len);\n\n    ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv);\n    if (ret)\n    {\n        msg(M_WARN | M_ERRNO, \"Failed to set keepalive\");\n    }\n\n    free(drv.ifd_data);\n    nvlist_destroy(nvl);\n\n    return ret;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\nstatic void\ndco_update_peer_stat(struct multi_context *m, uint32_t peerid, const nvlist_t *nvl)\n{\n    if (peerid >= m->max_clients || !m->instances[peerid])\n    {\n        msg(M_WARN, \"dco_update_peer_stat: invalid peer ID %u returned by kernel\", peerid);\n        return;\n    }\n\n    struct multi_instance *mi = m->instances[peerid];\n\n    mi->context.c2.dco_read_bytes = nvlist_get_number(nvl, \"in\");\n    mi->context.c2.dco_write_bytes = nvlist_get_number(nvl, \"out\");\n\n    msg(D_DCO_DEBUG, \"%s: peer-id %u, dco_read_bytes: \" counter_format \" dco_write_bytes: \" counter_format,\n        __func__, peerid, mi->context.c2.dco_read_bytes, mi->context.c2.dco_write_bytes);\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nint\ndco_read_and_process(dco_context_t *dco)\n{\n    struct ifdrv drv;\n    uint8_t buf[4096];\n    nvlist_t *nvl;\n    enum ovpn_notif_type type;\n    int ret;\n\n    /* Flush any pending data from the pipe. */\n    (void)read(dco->pipefd[1], buf, sizeof(buf));\n\n    CLEAR(drv);\n    snprintf(drv.ifd_name, IFNAMSIZ, \"%s\", dco->ifname);\n    drv.ifd_cmd = OVPN_GET_PKT;\n    drv.ifd_data = buf;\n    drv.ifd_len = sizeof(buf);\n\n    ret = ioctl(dco->fd, SIOCGDRVSPEC, &drv);\n    if (ret)\n    {\n        msg(M_WARN | M_ERRNO, \"Failed to read control packet\");\n        return -errno;\n    }\n\n    nvl = nvlist_unpack(buf, drv.ifd_len, 0);\n    if (!nvl)\n    {\n        msg(M_WARN, \"Failed to unpack nvlist\");\n        return -EINVAL;\n    }\n\n    /* dco_message_peer_id is signed int, because other parts of the\n     * code treat \"-1\" as \"this is a message not specific to one peer\"\n     */\n    dco->dco_message_peer_id = (int)nvlist_get_number(nvl, \"peerid\");\n\n    type = (enum ovpn_notif_type)nvlist_get_number(nvl, \"notification\");\n\n    switch (type)\n    {\n        case OVPN_NOTIF_DEL_PEER:\n            dco->dco_del_peer_reason = OVPN_DEL_PEER_REASON_EXPIRED;\n\n            if (nvlist_exists_number(nvl, \"del_reason\"))\n            {\n                uint32_t reason = (uint32_t)nvlist_get_number(nvl, \"del_reason\");\n                if (reason == OVPN_DEL_REASON_TIMEOUT)\n                {\n                    dco->dco_del_peer_reason = OVPN_DEL_PEER_REASON_EXPIRED;\n                }\n                else\n                {\n                    dco->dco_del_peer_reason = OVPN_DEL_PEER_REASON_USERSPACE;\n                }\n            }\n            msg(D_DCO_DEBUG, \"%s: received NOTIF_DEL_PEER for peer-id=%d, reason=%d\", __func__,\n                dco->dco_message_peer_id, dco->dco_del_peer_reason);\n\n            if (nvlist_exists_nvlist(nvl, \"bytes\"))\n            {\n                const nvlist_t *bytes = nvlist_get_nvlist(nvl, \"bytes\");\n\n                if (dco->c->mode == CM_TOP)\n                {\n                    dco_update_peer_stat(dco->c->multi, dco->dco_message_peer_id, bytes);\n                }\n                else\n                {\n                    dco->c->c2.dco_read_bytes = nvlist_get_number(bytes, \"in\");\n                    dco->c->c2.dco_write_bytes = nvlist_get_number(bytes, \"out\");\n                }\n            }\n\n            dco->dco_message_type = OVPN_CMD_DEL_PEER;\n            break;\n\n        case OVPN_NOTIF_ROTATE_KEY:\n            msg(D_DCO_DEBUG, \"%s: received NOTIF_ROTATE_KEY for peer-id=%d\", __func__,\n                dco->dco_message_peer_id);\n            dco->dco_message_type = OVPN_CMD_SWAP_KEYS;\n            break;\n\n        case OVPN_NOTIF_FLOAT:\n        {\n            const nvlist_t *address;\n\n            if (!nvlist_exists_nvlist(nvl, \"address\"))\n            {\n                msg(M_WARN, \"Float notification without address\");\n                break;\n            }\n\n            address = nvlist_get_nvlist(nvl, \"address\");\n            if (!nvlist_to_sockaddr(address, &dco->dco_float_peer_ss))\n            {\n                msg(M_WARN, \"Failed to parse float notification\");\n                break;\n            }\n            msg(D_DCO_DEBUG, \"%s: received NOTIF_FLOAT for peer-id=%d\", __func__,\n                dco->dco_message_peer_id);\n            dco->dco_message_type = OVPN_CMD_FLOAT_PEER;\n            break;\n        }\n\n        default:\n            msg(M_WARN, \"%s: unknown kernel notification %d\", __func__, type);\n            dco->dco_message_type = 0;\n            break;\n    }\n\n    nvlist_destroy(nvl);\n\n    if (dco->c->mode == CM_TOP)\n    {\n        multi_process_incoming_dco(dco);\n    }\n    else\n    {\n        process_incoming_dco(dco);\n    }\n\n    return 0;\n}\n\nbool\ndco_available(msglvl_t msglevel)\n{\n    struct if_clonereq ifcr;\n    char *buf = NULL;\n    int fd;\n    int ret;\n    bool available = false;\n\n    /* Attempt to load the module. Ignore errors, because it might already be\n     * loaded, or built into the kernel. */\n    (void)kldload(\"if_ovpn\");\n\n    fd = socket(AF_LOCAL, SOCK_DGRAM | SOCK_CLOEXEC, 0);\n    if (fd < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"%s: socket() failed, disabling data channel offload\", __func__);\n        return false;\n    }\n\n    CLEAR(ifcr);\n\n    /* List cloners and check if openvpn is there. That tells us if this kernel\n     * supports if_ovpn (i.e. DCO) or not. */\n    ret = ioctl(fd, SIOCIFGCLONERS, &ifcr);\n    if (ret != 0)\n    {\n        goto out;\n    }\n\n    buf = malloc(ifcr.ifcr_total * IFNAMSIZ);\n    if (!buf)\n    {\n        goto out;\n    }\n\n    ifcr.ifcr_count = ifcr.ifcr_total;\n    ifcr.ifcr_buffer = buf;\n    ret = ioctl(fd, SIOCIFGCLONERS, &ifcr);\n    if (ret != 0)\n    {\n        goto out;\n    }\n\n    for (int i = 0; i < ifcr.ifcr_total; i++)\n    {\n        if (strcmp(buf + (i * IFNAMSIZ), \"openvpn\") == 0)\n        {\n            available = true;\n            goto out;\n        }\n    }\n\nout:\n    free(buf);\n    close(fd);\n\n    return available;\n}\n\nconst char *\ndco_version_string(struct gc_arena *gc)\n{\n    struct utsname *uts;\n    ALLOC_OBJ_GC(uts, struct utsname, gc);\n\n    if (uname(uts) != 0)\n    {\n        return \"N/A\";\n    }\n\n    return uts->version;\n}\n\nvoid\ndco_event_set(dco_context_t *dco, struct event_set *es, void *arg)\n{\n    struct ifdrv drv;\n    nvlist_t *nvl;\n    uint8_t buf[128];\n    int ret;\n\n    if (!dco || !dco->open)\n    {\n        return;\n    }\n\n    CLEAR(drv);\n    snprintf(drv.ifd_name, IFNAMSIZ, \"%s\", dco->ifname);\n    drv.ifd_cmd = OVPN_POLL_PKT;\n    drv.ifd_len = sizeof(buf);\n    drv.ifd_data = buf;\n\n    ret = ioctl(dco->fd, SIOCGDRVSPEC, &drv);\n    if (ret)\n    {\n        msg(M_WARN | M_ERRNO, \"Failed to poll for packets\");\n        return;\n    }\n\n    nvl = nvlist_unpack(buf, drv.ifd_len, 0);\n    if (!nvl)\n    {\n        msg(M_WARN, \"Failed to unpack nvlist\");\n        return;\n    }\n\n    if (nvlist_get_number(nvl, \"pending\") > 0)\n    {\n        (void)write(dco->pipefd[0], \" \", 1);\n        event_ctl(es, dco->pipefd[1], EVENT_READ, arg);\n    }\n\n    nvlist_destroy(nvl);\n}\n\nint\ndco_get_peer_stats_multi(dco_context_t *dco, const bool raise_sigusr1_on_err)\n{\n    struct ifdrv drv;\n    uint8_t *buf = NULL;\n    size_t buf_size = 4096;\n    nvlist_t *nvl;\n    const nvlist_t *const *nvpeers;\n    size_t npeers;\n    int ret;\n\n    if (!dco || !dco->open)\n    {\n        return 0;\n    }\n\n    msg(D_DCO_DEBUG, __func__);\n\n    CLEAR(drv);\n    snprintf(drv.ifd_name, IFNAMSIZ, \"%s\", dco->ifname);\n    drv.ifd_cmd = OVPN_GET_PEER_STATS;\n\nretry:\n    buf = realloc(buf, buf_size);\n    drv.ifd_len = buf_size;\n    drv.ifd_data = buf;\n\n    ret = ioctl(dco->fd, SIOCGDRVSPEC, &drv);\n    if (ret && errno == ENOSPC)\n    {\n        buf_size *= 2;\n        goto retry;\n    }\n\n    if (ret)\n    {\n        free(buf);\n        msg(M_WARN | M_ERRNO, \"Failed to get peer stats\");\n        return -EINVAL;\n    }\n\n    nvl = nvlist_unpack(buf, drv.ifd_len, 0);\n    free(buf);\n    if (!nvl)\n    {\n        msg(M_WARN, \"Failed to unpack nvlist\");\n        return -EINVAL;\n    }\n\n    if (!nvlist_exists_nvlist_array(nvl, \"peers\"))\n    {\n        /* no peers */\n        nvlist_destroy(nvl);\n        return 0;\n    }\n\n    nvpeers = nvlist_get_nvlist_array(nvl, \"peers\", &npeers);\n    for (size_t i = 0; i < npeers; i++)\n    {\n        const nvlist_t *peer = nvpeers[i];\n        uint32_t peerid = (uint32_t)nvlist_get_number(peer, \"peerid\");\n        const nvlist_t *bytes = nvlist_get_nvlist(peer, \"bytes\");\n\n        /* we can end here in p2mp mode, or in p2p mode via\n         * the call to \"dco_get_peer_stat()\"\n         */\n        if (dco->c->mode == CM_TOP)\n        {\n            dco_update_peer_stat(dco->c->multi, peerid, bytes);\n        }\n        else\n        {\n            dco->c->c2.dco_read_bytes = nvlist_get_number(bytes, \"in\");\n            dco->c->c2.dco_write_bytes = nvlist_get_number(bytes, \"out\");\n        }\n    }\n\n    nvlist_destroy(nvl);\n    return 0;\n}\n\n/* get stats for a single peer\n * we can get here for \"the peer stats\" in p2p client mode, or by\n * being queried for a particular peer in p2mp mode, for --inactive\n */\nint\ndco_get_peer_stats(struct context *c, const bool raise_sigusr1_on_err)\n{\n    ASSERT(c->c2.tls_multi);\n    msg(D_DCO_DEBUG, \"%s: peer-id %d\", __func__, c->c2.tls_multi->dco_peer_id);\n\n    if (c->c2.tls_multi->dco_peer_id < 0)\n    {\n        return -EINVAL; /* DCO not active yet */\n    }\n\n    /* unfortunately, the FreeBSD kernel has no peer-specific query - so\n     * we just get all the stats - and if we're there anyway, we can save it\n     * for all peers, too...\n     */\n    return dco_get_peer_stats_multi(&c->c1.tuntap->dco, raise_sigusr1_on_err);\n}\n\nconst char *\ndco_get_supported_ciphers(void)\n{\n    return \"none:AES-256-GCM:AES-192-GCM:AES-128-GCM:CHACHA20-POLY1305\";\n}\n\nbool\ndco_supports_epoch_data(struct context *c)\n{\n    return false;\n}\n\n#endif /* defined(ENABLE_DCO) && defined(TARGET_FREEBSD) */\n"
  },
  {
    "path": "src/openvpn/dco_freebsd.h",
    "content": "/*\n *  Interface to FreeBSD dco networking code\n *\n *  Copyright (C) 2022 Rubicon Communications, LLC (Netgate). All Rights Reserved.\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n#ifndef DCO_FREEBSD_H\n#define DCO_FREEBSD_H\n\n#if defined(ENABLE_DCO) && defined(TARGET_FREEBSD)\n\n#include \"buffer.h\"\n#include \"event.h\"\n\n#include \"ovpn_dco_freebsd.h\"\n\n#define DCO_IROUTE_METRIC 100\n\ntypedef enum ovpn_key_slot dco_key_slot_t;\ntypedef enum ovpn_key_cipher dco_cipher_t;\n\nenum ovpn_message_type_t\n{\n    /* message type #0 is treated as magic number by process_incoming_dco() */\n    OVPN_CMD_NO_MESSAGE = 0,\n    OVPN_CMD_DEL_PEER,\n    OVPN_CMD_PACKET,\n    OVPN_CMD_SWAP_KEYS,\n    OVPN_CMD_FLOAT_PEER,\n};\n\nenum ovpn_del_reason_t\n{\n    OVPN_DEL_PEER_REASON_EXPIRED,\n    OVPN_DEL_PEER_REASON_TRANSPORT_ERROR,\n    OVPN_DEL_PEER_REASON_USERSPACE,\n    OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT,\n};\n\ntypedef struct dco_context\n{\n    bool open;\n    int fd;\n    int pipefd[2];\n\n    char ifname[IFNAMSIZ];\n\n    int dco_message_type;\n    int dco_message_peer_id;\n    int dco_del_peer_reason;\n    struct sockaddr_storage dco_float_peer_ss;\n\n    struct context *c;\n} dco_context_t;\n\n#endif /* defined(ENABLE_DCO) && defined(TARGET_FREEBSD) */\n#endif /* ifndef DCO_FREEBSD_H */\n"
  },
  {
    "path": "src/openvpn/dco_internal.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2022-2026 Antonio Quartulli <a@unstable.cc>\n *  Copyright (C) 2022-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n#ifndef DCO_INTERNAL_H\n#define DCO_INTERNAL_H\n\n#if defined(ENABLE_DCO)\n\n#include \"dco_freebsd.h\"\n#include \"dco_linux.h\"\n#include \"dco_win.h\"\n\n/**\n * This file contains the internal DCO API definition.\n * It is expected that this file is included only in dco.h.\n * The OpenVPN code should never directly include this file\n */\n\nstatic inline dco_cipher_t\ndco_get_cipher(const char *cipher)\n{\n    if (strcmp(cipher, \"AES-256-GCM\") == 0 || strcmp(cipher, \"AES-128-GCM\") == 0\n        || strcmp(cipher, \"AES-192-GCM\") == 0)\n    {\n        return OVPN_CIPHER_ALG_AES_GCM;\n    }\n    else if (strcmp(cipher, \"CHACHA20-POLY1305\") == 0)\n    {\n        return OVPN_CIPHER_ALG_CHACHA20_POLY1305;\n    }\n    else\n    {\n        msg(M_FATAL, \"DCO: provided unsupported cipher: %s\", cipher);\n    }\n}\n\n/**\n * The following are the DCO APIs used to control the driver.\n * They are implemented by dco_linux.c\n */\n\nint dco_new_peer(dco_context_t *dco, unsigned int peerid, socket_descriptor_t sd, struct sockaddr *localaddr,\n                 struct sockaddr *remoteaddr, struct in_addr *vpn_ipv4, struct in6_addr *vpn_ipv6);\n\nint dco_del_peer(dco_context_t *dco, unsigned int peerid);\n\nint dco_new_key(dco_context_t *dco, unsigned int peerid, int keyid, dco_key_slot_t slot,\n                const uint8_t *encrypt_key, const uint8_t *encrypt_iv, const uint8_t *decrypt_key,\n                const uint8_t *decrypt_iv, const char *ciphername, bool epoch);\n\nint dco_del_key(dco_context_t *dco, unsigned int peerid, dco_key_slot_t slot);\n\nint dco_swap_keys(dco_context_t *dco, unsigned int peerid);\n\n#endif /* defined(ENABLE_DCO) */\n#endif /* ifndef DCO_INTERNAL_H */\n"
  },
  {
    "path": "src/openvpn/dco_linux.c",
    "content": "/*\n *  Interface to linux dco networking code\n *\n *  Copyright (C) 2020-2026 Antonio Quartulli <a@unstable.cc>\n *  Copyright (C) 2020-2026 Arne Schwabe <arne@rfc2549.org>\n *  Copyright (C) 2020-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#if defined(ENABLE_DCO) && defined(TARGET_LINUX)\n\n#include \"syshead.h\"\n\n#include \"dco_linux.h\"\n#include \"errlevel.h\"\n#include \"buffer.h\"\n#include \"networking.h\"\n#include \"openvpn.h\"\n\n#include \"socket.h\"\n#include \"tun.h\"\n#include \"ssl.h\"\n#include \"fdmisc.h\"\n#include \"multi.h\"\n#include \"ssl_verify.h\"\n\n#include \"ovpn_dco_linux.h\"\n\n#include <netlink/socket.h>\n#include <netlink/netlink.h>\n#include <netlink/genl/genl.h>\n#include <netlink/genl/family.h>\n#include <netlink/genl/ctrl.h>\n\n/* When parsing multiple DEL_PEER notifications, openvpn tries to request stats\n * for each DEL_PEER message (see setenv_stats). This triggers a GET_PEER\n * request-reply while we are still parsing the rest of the initial\n * notifications, which can lead to NLE_BUSY or even NLE_NOMEM.\n *\n * This basic lock ensures we don't bite our own tail by issuing a dco_get_peer\n * while still busy receiving and parsing other messages.\n */\nstatic bool __is_locked = false;\n\n/* libnl < 3.5.0 does not set the NLA_F_NESTED on its own, therefore we\n * have to explicitly do it to prevent the kernel from failing upon\n * parsing of the message\n */\n#define nla_nest_start(_msg, _type) nla_nest_start(_msg, (_type) | NLA_F_NESTED)\n\nstatic int ovpn_get_mcast_id(dco_context_t *dco);\n\nvoid dco_check_key_ctx(const struct key_ctx_bi *key);\n\ntypedef int (*ovpn_nl_cb)(struct nl_msg *msg, void *arg);\n\n/**\n * @brief resolves the netlink ID for ovpn-dco\n *\n * This function queries the kernel via a netlink socket\n * whether the ovpn-dco netlink namespace is available\n *\n * This function can be used to determine if the kernel\n * supports DCO offloading.\n *\n * @return ID on success, negative error code on error\n */\nstatic int\nresolve_ovpn_netlink_id(msglvl_t msglevel)\n{\n    int ret;\n    struct nl_sock *nl_sock = nl_socket_alloc();\n\n    if (!nl_sock)\n    {\n        msg(msglevel, \"Allocating net link socket failed\");\n        return -ENOMEM;\n    }\n\n    ret = genl_connect(nl_sock);\n    if (ret)\n    {\n        msg(msglevel, \"Cannot connect to generic netlink: %s\", nl_geterror(ret));\n        goto err_sock;\n    }\n    set_cloexec(nl_socket_get_fd(nl_sock));\n\n    ret = genl_ctrl_resolve(nl_sock, OVPN_FAMILY_NAME);\n    if (ret < 0)\n    {\n        msg(msglevel, \"Cannot find ovpn_dco netlink component: %s\", nl_geterror(ret));\n    }\n\nerr_sock:\n    nl_socket_free(nl_sock);\n    return ret;\n}\n\nstatic struct nl_msg *\novpn_dco_nlmsg_create(dco_context_t *dco, uint8_t cmd)\n{\n    struct nl_msg *nl_msg = nlmsg_alloc();\n    if (!nl_msg)\n    {\n        msg(M_FATAL, \"cannot allocate netlink message\");\n        return NULL;\n    }\n\n    genlmsg_put(nl_msg, 0, 0, dco->ovpn_dco_id, 0, 0, cmd, 0);\n    NLA_PUT_U32(nl_msg, OVPN_A_IFINDEX, dco->ifindex);\n\n    return nl_msg;\nnla_put_failure:\n    nlmsg_free(nl_msg);\n    msg(M_INFO, \"cannot put into netlink message\");\n    return NULL;\n}\n\nstatic int\novpn_nl_recvmsgs(dco_context_t *dco, const char *prefix)\n{\n    __is_locked = true;\n    int ret = nl_recvmsgs(dco->nl_sock, dco->nl_cb);\n    __is_locked = false;\n\n    switch (ret)\n    {\n        case -NLE_INTR:\n            msg(M_WARN, \"%s: netlink received interrupt due to signal - ignoring\", prefix);\n            break;\n\n        case -NLE_NOMEM:\n            msg(M_FATAL, \"%s: netlink out of memory error\", prefix);\n            break;\n\n        case -NLE_AGAIN:\n            msg(M_WARN, \"%s: netlink reports blocking read - aborting wait\", prefix);\n            break;\n\n        case -NLE_NODEV:\n            msg(M_FATAL, \"%s: netlink reports device not found:\", prefix);\n            break;\n\n        case -NLE_OBJ_NOTFOUND:\n            msg(M_INFO, \"%s: netlink reports object not found, ovpn-dco unloaded?\", prefix);\n            break;\n\n        default:\n            if (ret)\n            {\n                msg(M_NONFATAL, \"%s: netlink reports error (%d): %s\", prefix, ret,\n                    nl_geterror(-ret));\n            }\n            break;\n    }\n\n    return ret;\n}\n\n/**\n * Send a prepared netlink message.\n *\n * The method will also free nl_msg\n * @param dco       The dco context to use\n * @param nl_msg    the message to use\n * @param prefix    A prefix to report in the error message to give the user context\n * @return          status of sending the message\n */\nstatic int\novpn_nl_msg_send(dco_context_t *dco, struct nl_msg *nl_msg, const char *prefix)\n{\n    dco->status = 1;\n\n    nl_send_auto(dco->nl_sock, nl_msg);\n\n    while (dco->status == 1)\n    {\n        ovpn_nl_recvmsgs(dco, prefix);\n    }\n\n    if (dco->status < 0)\n    {\n        msg(M_INFO, \"%s: failed to send netlink message: %s (%d)\", prefix, strerror(-dco->status),\n            dco->status);\n    }\n\n    return dco->status;\n}\n\nstruct sockaddr *\nmapped_v4_to_v6(struct sockaddr *sock, struct gc_arena *gc)\n{\n    struct sockaddr_in6 *sock6 = (struct sockaddr_in6 *)sock;\n    if (sock->sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sock6->sin6_addr))\n    {\n        struct sockaddr_in *sock4;\n        ALLOC_OBJ_CLEAR_GC(sock4, struct sockaddr_in, gc);\n        memcpy(&sock4->sin_addr, sock6->sin6_addr.s6_addr + 12, 4);\n        sock4->sin_port = sock6->sin6_port;\n        sock4->sin_family = AF_INET;\n        return (struct sockaddr *)sock4;\n    }\n    return sock;\n}\n\nint\ndco_new_peer(dco_context_t *dco, unsigned int peerid, int sd, struct sockaddr *localaddr,\n             struct sockaddr *remoteaddr, struct in_addr *vpn_ipv4, struct in6_addr *vpn_ipv6)\n{\n    struct gc_arena gc = gc_new();\n    const char *remotestr = \"[undefined]\";\n    if (remoteaddr)\n    {\n        remotestr = print_sockaddr(remoteaddr, &gc);\n    }\n    msg(D_DCO_DEBUG, \"%s: peer-id %d, fd %d, remote addr: %s\", __func__, peerid, sd, remotestr);\n\n    struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_PEER_NEW);\n    struct nlattr *attr = nla_nest_start(nl_msg, OVPN_A_PEER);\n    int ret = -EMSGSIZE;\n\n    NLA_PUT_U32(nl_msg, OVPN_A_PEER_ID, peerid);\n    NLA_PUT_U32(nl_msg, OVPN_A_PEER_SOCKET, sd);\n\n    /* Set the remote endpoint if defined (for UDP) */\n    if (remoteaddr)\n    {\n        remoteaddr = mapped_v4_to_v6(remoteaddr, &gc);\n\n        if (remoteaddr->sa_family == AF_INET)\n        {\n            NLA_PUT(nl_msg, OVPN_A_PEER_REMOTE_IPV4, sizeof(struct in_addr),\n                    &((struct sockaddr_in *)remoteaddr)->sin_addr);\n            NLA_PUT_U16(nl_msg, OVPN_A_PEER_REMOTE_PORT,\n                        ((struct sockaddr_in *)remoteaddr)->sin_port);\n        }\n        else if (remoteaddr->sa_family == AF_INET6)\n        {\n            NLA_PUT(nl_msg, OVPN_A_PEER_REMOTE_IPV6, sizeof(struct in6_addr),\n                    &((struct sockaddr_in6 *)remoteaddr)->sin6_addr);\n            NLA_PUT_U16(nl_msg, OVPN_A_PEER_REMOTE_PORT,\n                        ((struct sockaddr_in6 *)remoteaddr)->sin6_port);\n            NLA_PUT_U32(nl_msg, OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID,\n                        ((struct sockaddr_in6 *)remoteaddr)->sin6_scope_id);\n        }\n    }\n\n    if (localaddr)\n    {\n        localaddr = mapped_v4_to_v6(localaddr, &gc);\n        if (localaddr->sa_family == AF_INET)\n        {\n            NLA_PUT(nl_msg, OVPN_A_PEER_LOCAL_IPV4, sizeof(struct in_addr),\n                    &((struct sockaddr_in *)localaddr)->sin_addr);\n        }\n        else if (localaddr->sa_family == AF_INET6)\n        {\n            NLA_PUT(nl_msg, OVPN_A_PEER_LOCAL_IPV6, sizeof(struct in6_addr),\n                    &((struct sockaddr_in6 *)localaddr)->sin6_addr);\n        }\n    }\n\n    /* Set the primary VPN IP addresses of the peer */\n    if (vpn_ipv4)\n    {\n        NLA_PUT_U32(nl_msg, OVPN_A_PEER_VPN_IPV4, vpn_ipv4->s_addr);\n    }\n    if (vpn_ipv6)\n    {\n        NLA_PUT(nl_msg, OVPN_A_PEER_VPN_IPV6, sizeof(struct in6_addr), vpn_ipv6);\n    }\n    nla_nest_end(nl_msg, attr);\n\n    ret = ovpn_nl_msg_send(dco, nl_msg, __func__);\n\nnla_put_failure:\n    nlmsg_free(nl_msg);\n    gc_free(&gc);\n    return ret;\n}\n\nstatic int\novpn_nl_cb_finish(struct nl_msg(*msg) __attribute__((unused)), void *arg)\n{\n    int *status = arg;\n\n    *status = 0;\n    return NL_SKIP;\n}\n\n/* The following enum members exist in netlink.h since linux-6.1.\n * However, some distro we support still ship an old header, thus\n * failing the OpenVPN compilation.\n *\n * For the time being we add the needed defines manually.\n * We will drop this definition once we stop supporting those old\n * distros.\n *\n * @NLMSGERR_ATTR_MISS_TYPE: type of a missing required attribute,\n *  %NLMSGERR_ATTR_MISS_NEST will not be present if the attribute was\n *  missing at the message level\n * @NLMSGERR_ATTR_MISS_NEST: offset of the nest where attribute was missing\n */\nenum ovpn_nlmsgerr_attrs\n{\n    OVPN_NLMSGERR_ATTR_MISS_TYPE = 5,\n    OVPN_NLMSGERR_ATTR_MISS_NEST = 6,\n    OVPN_NLMSGERR_ATTR_MAX = 6,\n};\n\n/* This function is used as error callback on the netlink socket.\n * When something goes wrong and the kernel returns an error, this function is\n * invoked.\n *\n * We pass the error code to the user by means of a variable pointed by *arg\n * (supplied by the user when setting this callback) and we parse the kernel\n * reply to see if it contains a human-readable error. If found, it is printed.\n */\nstatic int\novpn_nl_cb_error(struct sockaddr_nl(*nla) __attribute__((unused)), struct nlmsgerr *err, void *arg)\n{\n    struct nlmsghdr *nlh = (struct nlmsghdr *)err - 1;\n    struct nlattr *tb_msg[OVPN_NLMSGERR_ATTR_MAX + 1];\n    int len = nlh->nlmsg_len;\n    struct nlattr *attrs;\n    int *ret = arg;\n    int ack_len = sizeof(*nlh) + sizeof(int) + sizeof(*nlh);\n\n    *ret = err->error;\n\n    if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))\n    {\n        return NL_STOP;\n    }\n\n    if (!(nlh->nlmsg_flags & NLM_F_CAPPED))\n    {\n        ack_len += err->msg.nlmsg_len - (int)sizeof(*nlh);\n    }\n\n    if (len <= ack_len)\n    {\n        return NL_STOP;\n    }\n\n    attrs = (void *)((unsigned char *)nlh + ack_len);\n    len -= ack_len;\n\n    nla_parse(tb_msg, OVPN_NLMSGERR_ATTR_MAX, attrs, len, NULL);\n    if (tb_msg[NLMSGERR_ATTR_MSG])\n    {\n        len = (int)strnlen((char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG]),\n                           nla_len(tb_msg[NLMSGERR_ATTR_MSG]));\n        msg(M_WARN, \"kernel error: %*s\", len, (char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG]));\n    }\n\n    if (tb_msg[OVPN_NLMSGERR_ATTR_MISS_NEST])\n    {\n        msg(M_WARN, \"kernel error: missing required nesting type %u\",\n            nla_get_u32(tb_msg[OVPN_NLMSGERR_ATTR_MISS_NEST]));\n    }\n\n    if (tb_msg[OVPN_NLMSGERR_ATTR_MISS_TYPE])\n    {\n        msg(M_WARN, \"kernel error: missing required attribute type %u\",\n            nla_get_u32(tb_msg[OVPN_NLMSGERR_ATTR_MISS_TYPE]));\n    }\n\n    return NL_STOP;\n}\n\nstatic void\novpn_dco_register(dco_context_t *dco)\n{\n    msg(D_DCO_DEBUG, __func__);\n    ovpn_get_mcast_id(dco);\n\n    if (dco->ovpn_dco_mcast_id < 0)\n    {\n        msg(M_FATAL, \"cannot get mcast group: %s\", nl_geterror(dco->ovpn_dco_mcast_id));\n    }\n\n    /* Register for ovpn-dco specific multicast messages that the kernel may\n     * send\n     */\n    int ret = nl_socket_add_membership(dco->nl_sock, dco->ovpn_dco_mcast_id);\n    if (ret)\n    {\n        msg(M_FATAL, \"%s: failed to join groups: %d\", __func__, ret);\n    }\n}\n\nstatic int ovpn_handle_msg(struct nl_msg *msg, void *arg);\n\nstatic void\novpn_dco_init_netlink(dco_context_t *dco)\n{\n    dco->ovpn_dco_id = resolve_ovpn_netlink_id(M_FATAL);\n\n    dco->nl_sock = nl_socket_alloc();\n\n    if (!dco->nl_sock)\n    {\n        msg(M_FATAL, \"Cannot create netlink socket\");\n    }\n\n    int ret = genl_connect(dco->nl_sock);\n    if (ret)\n    {\n        msg(M_FATAL, \"Cannot connect to generic netlink: %s\", nl_geterror(ret));\n    }\n\n    /* enable Extended ACK for detailed error reporting */\n    ret = 1;\n    setsockopt(nl_socket_get_fd(dco->nl_sock), SOL_NETLINK, NETLINK_EXT_ACK, &ret, sizeof(ret));\n\n    /* set close on exec and non-block on the netlink socket */\n    set_cloexec(nl_socket_get_fd(dco->nl_sock));\n    set_nonblock(nl_socket_get_fd(dco->nl_sock));\n\n    dco->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);\n    if (!dco->nl_cb)\n    {\n        msg(M_FATAL, \"failed to allocate netlink callback\");\n    }\n\n    nl_socket_set_cb(dco->nl_sock, dco->nl_cb);\n\n    dco->dco_message_peer_id = -1;\n    nl_cb_err(dco->nl_cb, NL_CB_CUSTOM, ovpn_nl_cb_error, &dco->status);\n    nl_cb_set(dco->nl_cb, NL_CB_FINISH, NL_CB_CUSTOM, ovpn_nl_cb_finish, &dco->status);\n    nl_cb_set(dco->nl_cb, NL_CB_ACK, NL_CB_CUSTOM, ovpn_nl_cb_finish, &dco->status);\n    nl_cb_set(dco->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, ovpn_handle_msg, dco);\n\n    ovpn_dco_register(dco);\n\n    /* The async PACKET messages confuse libnl and it will drop them with\n     * wrong sequence numbers (NLE_SEQ_MISMATCH), so disable libnl's sequence\n     * number check */\n    nl_socket_disable_seq_check(dco->nl_sock);\n\n    /* nl library sets the buffer size to 32k/32k by default which is sometimes\n     * overrun with very fast connecting/disconnecting clients.\n     * TODO: fix this in a better and more reliable way */\n    ASSERT(!nl_socket_set_buffer_size(dco->nl_sock, 1024 * 1024, 1024 * 1024));\n}\n\nbool\novpn_dco_init(struct context *c)\n{\n    dco_context_t *dco = &c->c1.tuntap->dco;\n\n    switch (c->mode)\n    {\n        case CM_TOP:\n            dco->ifmode = OVPN_MODE_MP;\n            break;\n\n        case CM_P2P:\n            dco->ifmode = OVPN_MODE_P2P;\n            break;\n\n        default:\n            ASSERT(false);\n    }\n\n    /* store pointer to context as it may be required by message\n     * parsing routines\n     */\n    dco->c = c;\n    ovpn_dco_init_netlink(dco);\n    return true;\n}\n\nstatic void\novpn_dco_uninit_netlink(dco_context_t *dco)\n{\n    nl_socket_free(dco->nl_sock);\n    dco->nl_sock = NULL;\n\n    /* Decrease reference count */\n    nl_cb_put(dco->nl_cb);\n\n    CLEAR(dco);\n}\n\nint\nopen_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev)\n{\n    msg(D_DCO_DEBUG, \"%s: %s\", __func__, dev);\n    ASSERT(tt->type == DEV_TYPE_TUN);\n\n    int ret = net_iface_new(ctx, dev, OVPN_FAMILY_NAME, &tt->dco);\n    if (ret < 0)\n    {\n        msg(D_DCO_DEBUG, \"Cannot create DCO interface %s: %d\", dev, ret);\n        return ret;\n    }\n\n    tt->dco.ifindex = if_nametoindex(dev);\n    if (!tt->dco.ifindex)\n    {\n        msg(M_FATAL, \"DCO: cannot retrieve ifindex for interface %s\", dev);\n    }\n\n    return 0;\n}\n\nvoid\nclose_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    msg(D_DCO_DEBUG, __func__);\n\n    net_iface_del(ctx, tt->actual_name);\n    ovpn_dco_uninit_netlink(&tt->dco);\n}\n\nint\ndco_swap_keys(dco_context_t *dco, unsigned int peerid)\n{\n    msg(D_DCO_DEBUG, \"%s: peer-id %d\", __func__, peerid);\n\n    struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_KEY_SWAP);\n    if (!nl_msg)\n    {\n        return -ENOMEM;\n    }\n\n    struct nlattr *attr = nla_nest_start(nl_msg, OVPN_A_KEYCONF);\n    int ret = -EMSGSIZE;\n    NLA_PUT_U32(nl_msg, OVPN_A_KEYCONF_PEER_ID, peerid);\n    nla_nest_end(nl_msg, attr);\n\n    ret = ovpn_nl_msg_send(dco, nl_msg, __func__);\n\nnla_put_failure:\n    nlmsg_free(nl_msg);\n    return ret;\n}\n\n\nint\ndco_del_peer(dco_context_t *dco, unsigned int peerid)\n{\n    msg(D_DCO_DEBUG | M_NOIPREFIX, \"%s: peer-id %d\", __func__, peerid);\n\n    struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_PEER_DEL);\n    if (!nl_msg)\n    {\n        return -ENOMEM;\n    }\n\n    struct nlattr *attr = nla_nest_start(nl_msg, OVPN_A_PEER);\n    int ret = -EMSGSIZE;\n    NLA_PUT_U32(nl_msg, OVPN_A_PEER_ID, peerid);\n    nla_nest_end(nl_msg, attr);\n\n    ret = ovpn_nl_msg_send(dco, nl_msg, __func__);\n\nnla_put_failure:\n    nlmsg_free(nl_msg);\n    return ret;\n}\n\n\nint\ndco_del_key(dco_context_t *dco, unsigned int peerid, dco_key_slot_t slot)\n{\n    int ret = -EMSGSIZE;\n    msg(D_DCO_DEBUG, \"%s: peer-id %d, slot %d\", __func__, peerid, slot);\n\n    struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_KEY_DEL);\n    if (!nl_msg)\n    {\n        return -ENOMEM;\n    }\n\n    struct nlattr *keyconf = nla_nest_start(nl_msg, OVPN_A_KEYCONF);\n    NLA_PUT_U32(nl_msg, OVPN_A_KEYCONF_PEER_ID, peerid);\n    NLA_PUT_U32(nl_msg, OVPN_A_KEYCONF_SLOT, slot);\n    nla_nest_end(nl_msg, keyconf);\n\n    ret = ovpn_nl_msg_send(dco, nl_msg, __func__);\n\nnla_put_failure:\n    nlmsg_free(nl_msg);\n    return ret;\n}\n\nint\ndco_new_key(dco_context_t *dco, unsigned int peerid, int keyid, dco_key_slot_t slot,\n            const uint8_t *encrypt_key, const uint8_t *encrypt_iv, const uint8_t *decrypt_key,\n            const uint8_t *decrypt_iv, const char *ciphername, bool epoch)\n{\n    msg(D_DCO_DEBUG, \"%s: slot %d, key-id %d, peer-id %d, cipher %s, epoch %d\", __func__, slot, keyid, peerid,\n        ciphername, epoch);\n\n    const size_t key_len = cipher_kt_key_size(ciphername);\n    ASSERT(key_len <= INT_MAX);\n    const int nonce_tail_len = 8;\n\n    struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_KEY_NEW);\n    if (!nl_msg)\n    {\n        return -ENOMEM;\n    }\n\n    dco_cipher_t dco_cipher = dco_get_cipher(ciphername);\n\n    int ret = -EMSGSIZE;\n\n    struct nlattr *key_conf = nla_nest_start(nl_msg, OVPN_A_KEYCONF);\n    NLA_PUT_U32(nl_msg, OVPN_A_KEYCONF_PEER_ID, peerid);\n    NLA_PUT_U32(nl_msg, OVPN_A_KEYCONF_SLOT, slot);\n    NLA_PUT_U32(nl_msg, OVPN_A_KEYCONF_KEY_ID, keyid);\n    NLA_PUT_U32(nl_msg, OVPN_A_KEYCONF_CIPHER_ALG, dco_cipher);\n\n    struct nlattr *key_enc = nla_nest_start(nl_msg, OVPN_A_KEYCONF_ENCRYPT_DIR);\n    if (dco_cipher != OVPN_CIPHER_ALG_NONE)\n    {\n        NLA_PUT(nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, (int)key_len, encrypt_key);\n        NLA_PUT(nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, nonce_tail_len, encrypt_iv);\n    }\n    nla_nest_end(nl_msg, key_enc);\n\n    struct nlattr *key_dec = nla_nest_start(nl_msg, OVPN_A_KEYCONF_DECRYPT_DIR);\n    if (dco_cipher != OVPN_CIPHER_ALG_NONE)\n    {\n        NLA_PUT(nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, (int)key_len, decrypt_key);\n        NLA_PUT(nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, nonce_tail_len, decrypt_iv);\n    }\n    nla_nest_end(nl_msg, key_dec);\n\n    nla_nest_end(nl_msg, key_conf);\n\n\n    ret = ovpn_nl_msg_send(dco, nl_msg, __func__);\n\nnla_put_failure:\n    nlmsg_free(nl_msg);\n    return ret;\n}\n\nint\ndco_set_peer(dco_context_t *dco, unsigned int peerid, int keepalive_interval, int keepalive_timeout,\n             int mss)\n{\n    msg(D_DCO_DEBUG, \"%s: peer-id %d, keepalive %d/%d, mss %d\", __func__, peerid,\n        keepalive_interval, keepalive_timeout, mss);\n\n    struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_PEER_SET);\n    if (!nl_msg)\n    {\n        return -ENOMEM;\n    }\n\n    struct nlattr *attr = nla_nest_start(nl_msg, OVPN_A_PEER);\n    int ret = -EMSGSIZE;\n    NLA_PUT_U32(nl_msg, OVPN_A_PEER_ID, peerid);\n    NLA_PUT_U32(nl_msg, OVPN_A_PEER_KEEPALIVE_INTERVAL, keepalive_interval);\n    NLA_PUT_U32(nl_msg, OVPN_A_PEER_KEEPALIVE_TIMEOUT, keepalive_timeout);\n    nla_nest_end(nl_msg, attr);\n\n    ret = ovpn_nl_msg_send(dco, nl_msg, __func__);\n\nnla_put_failure:\n    nlmsg_free(nl_msg);\n    return ret;\n}\n\n/* This function parses the reply provided by the kernel to the CTRL_CMD_GETFAMILY\n * message. We parse the reply and we retrieve the multicast group ID associated\n * with the \"ovpn-dco\" netlink family.\n *\n * The ID is later used to subscribe to the multicast group and be notified\n * about any multicast message sent by the ovpn-dco kernel module.\n */\nstatic int\nmcast_family_handler(struct nl_msg *msg, void *arg)\n{\n    dco_context_t *dco = arg;\n    struct nlattr *tb[CTRL_ATTR_MAX + 1];\n    struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));\n\n    nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL);\n\n    if (!tb[CTRL_ATTR_MCAST_GROUPS])\n    {\n        return NL_SKIP;\n    }\n\n    struct nlattr *mcgrp;\n    int rem_mcgrp;\n    nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp)\n    {\n        struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];\n\n        nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX, nla_data(mcgrp), nla_len(mcgrp), NULL);\n\n        if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] || !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID])\n        {\n            continue;\n        }\n\n        if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]), OVPN_MCGRP_PEERS,\n                    nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]))\n            != 0)\n        {\n            continue;\n        }\n        dco->ovpn_dco_mcast_id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);\n        break;\n    }\n\n    return NL_SKIP;\n}\n/**\n * Lookup the multicast id for OpenVPN. This method and its help method currently\n * hardcode the lookup to OVPN_FAMILY_NAME and OVPN_MCGRP_PEERS but\n * extended in the future if we need to lookup more than one mcast id.\n */\nstatic int\novpn_get_mcast_id(dco_context_t *dco)\n{\n    dco->ovpn_dco_mcast_id = -ENOENT;\n\n    /* Even though 'nlctrl' is a constant, there seem to be no library\n     * provided define for it */\n    dco->ctrlid = genl_ctrl_resolve(dco->nl_sock, \"nlctrl\");\n\n    struct nl_msg *nl_msg = nlmsg_alloc();\n    if (!nl_msg)\n    {\n        return -ENOMEM;\n    }\n\n    genlmsg_put(nl_msg, 0, 0, dco->ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);\n\n    int ret = -EMSGSIZE;\n    NLA_PUT_STRING(nl_msg, CTRL_ATTR_FAMILY_NAME, OVPN_FAMILY_NAME);\n\n    ret = ovpn_nl_msg_send(dco, nl_msg, __func__);\n\nnla_put_failure:\n    nlmsg_free(nl_msg);\n    return ret;\n}\n\nstatic bool\novpn_parse_float_addr(struct nlattr **attrs, struct sockaddr *out)\n{\n    if (!attrs[OVPN_A_PEER_REMOTE_PORT])\n    {\n        msg(D_DCO, \"ovpn-dco: no remote port in PEER_FLOAT_NTF message\");\n        return false;\n    }\n\n    if (attrs[OVPN_A_PEER_REMOTE_IPV4])\n    {\n        struct sockaddr_in *addr4 = (struct sockaddr_in *)out;\n        CLEAR(*addr4);\n        addr4->sin_family = AF_INET;\n        addr4->sin_port = nla_get_u16(attrs[OVPN_A_PEER_REMOTE_PORT]);\n        addr4->sin_addr.s_addr = nla_get_u32(attrs[OVPN_A_PEER_REMOTE_IPV4]);\n        return true;\n    }\n    else if (attrs[OVPN_A_PEER_REMOTE_IPV6]\n             && nla_len(attrs[OVPN_A_PEER_REMOTE_IPV6]) == sizeof(struct in6_addr))\n    {\n        struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)out;\n        CLEAR(*addr6);\n        addr6->sin6_family = AF_INET6;\n        addr6->sin6_port = nla_get_u16(attrs[OVPN_A_PEER_REMOTE_PORT]);\n        memcpy(&addr6->sin6_addr, nla_data(attrs[OVPN_A_PEER_REMOTE_IPV6]),\n               sizeof(addr6->sin6_addr));\n        if (attrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID])\n        {\n            addr6->sin6_scope_id = nla_get_u32(attrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]);\n        }\n        return true;\n    }\n\n    msg(D_DCO, \"ovpn-dco: no valid remote IP address in PEER_FLOAT_NTF message\");\n    return false;\n}\n\n/* libnl < 3.11.0 does not implement nla_get_uint() */\nstatic uint64_t\novpn_nla_get_uint(struct nlattr *attr)\n{\n    if (nla_len(attr) == sizeof(uint32_t))\n    {\n        return nla_get_u32(attr);\n    }\n    else\n    {\n        return nla_get_u64(attr);\n    }\n}\n\nstatic void\ndco_update_peer_stat(struct context_2 *c2, struct nlattr *tb[], uint32_t id)\n{\n    if (tb[OVPN_A_PEER_LINK_RX_BYTES])\n    {\n        c2->dco_read_bytes = ovpn_nla_get_uint(tb[OVPN_A_PEER_LINK_RX_BYTES]);\n        msg(D_DCO_DEBUG, \"%s / dco_read_bytes: \" counter_format, __func__, c2->dco_read_bytes);\n    }\n    else\n    {\n        msg(M_WARN, \"%s: no link RX bytes provided in reply for peer %u\", __func__, id);\n    }\n\n    if (tb[OVPN_A_PEER_LINK_TX_BYTES])\n    {\n        c2->dco_write_bytes = ovpn_nla_get_uint(tb[OVPN_A_PEER_LINK_TX_BYTES]);\n        msg(D_DCO_DEBUG, \"%s / dco_write_bytes: \" counter_format, __func__, c2->dco_write_bytes);\n    }\n    else\n    {\n        msg(M_WARN, \"%s: no link TX bytes provided in reply for peer %u\", __func__, id);\n    }\n\n    if (tb[OVPN_A_PEER_VPN_RX_BYTES])\n    {\n        c2->tun_read_bytes = ovpn_nla_get_uint(tb[OVPN_A_PEER_VPN_RX_BYTES]);\n        msg(D_DCO_DEBUG, \"%s / tun_read_bytes: \" counter_format, __func__, c2->tun_read_bytes);\n    }\n    else\n    {\n        msg(M_WARN, \"%s: no VPN RX bytes provided in reply for peer %u\", __func__, id);\n    }\n\n    if (tb[OVPN_A_PEER_VPN_TX_BYTES])\n    {\n        c2->tun_write_bytes = ovpn_nla_get_uint(tb[OVPN_A_PEER_VPN_TX_BYTES]);\n        msg(D_DCO_DEBUG, \"%s / tun_write_bytes: \" counter_format, __func__, c2->tun_write_bytes);\n    }\n    else\n    {\n        msg(M_WARN, \"%s: no VPN TX bytes provided in reply for peer %u\", __func__, id);\n    }\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\nstatic int\novpn_handle_peer(dco_context_t *dco, struct nlattr *attrs[])\n{\n    if (!attrs[OVPN_A_PEER])\n    {\n        msg(D_DCO_DEBUG, \"%s: malformed reply\", __func__);\n        return NL_SKIP;\n    }\n\n    struct nlattr *tb_peer[OVPN_A_PEER_MAX + 1];\n    nla_parse_nested(tb_peer, OVPN_A_PEER_MAX, attrs[OVPN_A_PEER], NULL);\n\n    if (!tb_peer[OVPN_A_PEER_ID])\n    {\n        msg(M_WARN, \"ovpn-dco: no peer-id provided in PEER_GET reply\");\n        return NL_SKIP;\n    }\n\n    uint32_t peer_id = nla_get_u32(tb_peer[OVPN_A_PEER_ID]);\n    struct context_2 *c2;\n\n    msg(D_DCO_DEBUG | M_NOIPREFIX, \"%s: parsing message for peer %u...\", __func__, peer_id);\n\n    if (dco->ifmode == OVPN_MODE_P2P)\n    {\n        c2 = &dco->c->c2;\n        if (c2->tls_multi->dco_peer_id != peer_id)\n        {\n            return NL_SKIP;\n        }\n    }\n    else\n    {\n        if (peer_id >= dco->c->multi->max_clients)\n        {\n            msg(M_WARN, \"%s: received out of bound peer_id %u (max=%u)\", __func__, peer_id,\n                dco->c->multi->max_clients);\n            return NL_SKIP;\n        }\n\n        struct multi_instance *mi = dco->c->multi->instances[peer_id];\n        if (!mi)\n        {\n            msg(M_WARN | M_NOIPREFIX, \"%s: received data for a non-existing peer %u\", __func__, peer_id);\n            return NL_SKIP;\n        }\n\n        c2 = &mi->context.c2;\n    }\n\n    dco_update_peer_stat(c2, tb_peer, peer_id);\n\n    return NL_OK;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nstatic bool\novpn_iface_check(dco_context_t *dco, struct nlattr *attrs[])\n{\n    /* we must know which interface this message is referring to in order to\n     * avoid mixing messages for other instances\n     */\n    if (!attrs[OVPN_A_IFINDEX])\n    {\n        msg(D_DCO, \"ovpn-dco: Received message without ifindex\");\n        return false;\n    }\n\n    uint32_t ifindex = nla_get_u32(attrs[OVPN_A_IFINDEX]);\n    if (ifindex != dco->ifindex)\n    {\n        msg(D_DCO_DEBUG, \"ovpn-dco: ignoring message for foreign ifindex %d\", ifindex);\n        return false;\n    }\n\n    return true;\n}\n\nstatic int\novpn_handle_peer_del_ntf(dco_context_t *dco, struct nlattr *attrs[])\n{\n    if (!ovpn_iface_check(dco, attrs))\n    {\n        return NL_STOP;\n    }\n\n    if (!attrs[OVPN_A_PEER])\n    {\n        msg(D_DCO | M_NOIPREFIX, \"ovpn-dco: no peer in PEER_DEL_NTF message\");\n        return NL_STOP;\n    }\n\n    struct nlattr *dp_attrs[OVPN_A_PEER_MAX + 1];\n    if (nla_parse_nested(dp_attrs, OVPN_A_PEER_MAX, attrs[OVPN_A_PEER], NULL))\n    {\n        msg(D_DCO | M_NOIPREFIX, \"ovpn-dco: can't parse peer in PEER_DEL_NTF messsage\");\n        return NL_STOP;\n    }\n\n    if (!dp_attrs[OVPN_A_PEER_DEL_REASON])\n    {\n        msg(D_DCO | M_NOIPREFIX, \"ovpn-dco: no reason in PEER_DEL_NTF message\");\n        return NL_STOP;\n    }\n    if (!dp_attrs[OVPN_A_PEER_ID])\n    {\n        msg(D_DCO | M_NOIPREFIX, \"ovpn-dco: no peer-id in PEER_DEL_NTF message\");\n        return NL_STOP;\n    }\n\n    int reason = nla_get_u32(dp_attrs[OVPN_A_PEER_DEL_REASON]);\n    unsigned int peerid = nla_get_u32(dp_attrs[OVPN_A_PEER_ID]);\n\n    msg(D_DCO_DEBUG | M_NOIPREFIX, \"ovpn-dco: received CMD_PEER_DEL_NTF, ifindex: %d, peer-id %u, reason: %d\",\n        dco->ifindex, peerid, reason);\n    dco->dco_message_peer_id = peerid;\n    dco->dco_del_peer_reason = reason;\n    dco->dco_message_type = OVPN_CMD_PEER_DEL_NTF;\n\n    return NL_OK;\n}\n\nstatic int\novpn_handle_peer_float_ntf(dco_context_t *dco, struct nlattr *attrs[])\n{\n    if (!ovpn_iface_check(dco, attrs))\n    {\n        return NL_STOP;\n    }\n\n    if (!attrs[OVPN_A_PEER])\n    {\n        msg(D_DCO, \"ovpn-dco: no peer in PEER_FLOAT_NTF message\");\n        return NL_STOP;\n    }\n\n    struct nlattr *fp_attrs[OVPN_A_PEER_MAX + 1];\n    if (nla_parse_nested(fp_attrs, OVPN_A_PEER_MAX, attrs[OVPN_A_PEER], NULL))\n    {\n        msg(D_DCO, \"ovpn-dco: can't parse peer in PEER_FLOAT_NTF messsage\");\n        return NL_STOP;\n    }\n\n    if (!fp_attrs[OVPN_A_PEER_ID])\n    {\n        msg(D_DCO, \"ovpn-dco: no peer-id in PEER_FLOAT_NTF message\");\n        return NL_STOP;\n    }\n    uint32_t peerid = nla_get_u32(fp_attrs[OVPN_A_PEER_ID]);\n\n    if (!ovpn_parse_float_addr(fp_attrs, (struct sockaddr *)&dco->dco_float_peer_ss))\n    {\n        return NL_STOP;\n    }\n\n    struct gc_arena gc = gc_new();\n    msg(D_DCO_DEBUG, \"ovpn-dco: received CMD_PEER_FLOAT_NTF, ifindex: %u, peer-id %u, address: %s\",\n        dco->ifindex, peerid, print_sockaddr((struct sockaddr *)&dco->dco_float_peer_ss, &gc));\n    dco->dco_message_peer_id = (int)peerid;\n    dco->dco_message_type = OVPN_CMD_PEER_FLOAT_NTF;\n\n    gc_free(&gc);\n\n    return NL_OK;\n}\n\nstatic int\novpn_handle_key_swap_ntf(dco_context_t *dco, struct nlattr *attrs[])\n{\n    if (!ovpn_iface_check(dco, attrs))\n    {\n        return NL_STOP;\n    }\n\n    if (!attrs[OVPN_A_KEYCONF])\n    {\n        msg(D_DCO, \"ovpn-dco: no keyconf in KEY_SWAP_NTF message\");\n        return NL_STOP;\n    }\n\n    struct nlattr *dp_attrs[OVPN_A_KEYCONF_MAX + 1];\n    if (nla_parse_nested(dp_attrs, OVPN_A_KEYCONF_MAX, attrs[OVPN_A_KEYCONF], NULL))\n    {\n        msg(D_DCO, \"ovpn-dco: can't parse keyconf in KEY_SWAP_NTF message\");\n        return NL_STOP;\n    }\n    if (!dp_attrs[OVPN_A_KEYCONF_PEER_ID])\n    {\n        msg(D_DCO, \"ovpn-dco: no peer-id in KEY_SWAP_NTF message\");\n        return NL_STOP;\n    }\n    if (!dp_attrs[OVPN_A_KEYCONF_KEY_ID])\n    {\n        msg(D_DCO, \"ovpn-dco: no key-id in KEY_SWAP_NTF message\");\n        return NL_STOP;\n    }\n\n    int key_id = nla_get_u16(dp_attrs[OVPN_A_KEYCONF_KEY_ID]);\n    unsigned int peer_id = nla_get_u32(dp_attrs[OVPN_A_KEYCONF_PEER_ID]);\n\n    msg(D_DCO_DEBUG, \"ovpn-dco: received CMD_KEY_SWAP_NTF, ifindex: %d, peer-id %u, key-id: %d\",\n        dco->ifindex, peer_id, key_id);\n    dco->dco_message_peer_id = peer_id;\n    dco->dco_message_key_id = key_id;\n    dco->dco_message_type = OVPN_CMD_KEY_SWAP_NTF;\n\n    return NL_OK;\n}\n\n/* This function parses any netlink message sent by ovpn-dco to userspace */\nstatic int\novpn_handle_msg(struct nl_msg *msg, void *arg)\n{\n    dco_context_t *dco = arg;\n\n    struct nlattr *attrs[OVPN_A_MAX + 1];\n    struct nlmsghdr *nlh = nlmsg_hdr(msg);\n    struct genlmsghdr *gnlh = genlmsg_hdr(nlh);\n\n    msg(D_DCO_DEBUG | M_NOIPREFIX, \"ovpn-dco: received netlink message type=%u cmd=%u flags=%#.4x\",\n        nlh->nlmsg_type, gnlh->cmd, nlh->nlmsg_flags);\n\n    /* if we get a message from the NLCTRL family, it means\n     * this is the reply to the mcast ID resolution request\n     * and we parse it accordingly.\n     */\n    if (nlh->nlmsg_type == dco->ctrlid)\n    {\n        msg(D_DCO_DEBUG, \"ovpn-dco: received CTRLID message\");\n        return mcast_family_handler(msg, dco);\n    }\n\n    if (!genlmsg_valid_hdr(nlh, 0))\n    {\n        msg(D_DCO, \"ovpn-dco: invalid header\");\n        return NL_STOP;\n    }\n\n    if (nla_parse(attrs, OVPN_A_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL))\n    {\n        msg(D_DCO, \"received bogus data from ovpn-dco\");\n        return NL_STOP;\n    }\n\n    /* based on the message type, we parse the subobject contained in the\n     * message, that stores the type-specific attributes.\n     *\n     * the \"dco\" object is then filled accordingly with the information\n     * retrieved from the message, so that *process_incoming_dco can react\n     * as need be.\n     */\n    int ret;\n    switch (gnlh->cmd)\n    {\n        case OVPN_CMD_PEER_GET:\n        {\n            /* return directly, there are no messages to pass to *process_incoming_dco() */\n            return ovpn_handle_peer(dco, attrs);\n        }\n\n        case OVPN_CMD_PEER_DEL_NTF:\n        {\n            ret = ovpn_handle_peer_del_ntf(dco, attrs);\n            break;\n        }\n\n        case OVPN_CMD_PEER_FLOAT_NTF:\n        {\n            ret = ovpn_handle_peer_float_ntf(dco, attrs);\n            break;\n        }\n\n        case OVPN_CMD_KEY_SWAP_NTF:\n        {\n            ret = ovpn_handle_key_swap_ntf(dco, attrs);\n            break;\n        }\n\n        default:\n            msg(D_DCO, \"ovpn-dco: received unknown command: %d\", gnlh->cmd);\n            dco->dco_message_type = 0;\n            return NL_STOP;\n    }\n\n    if (ret != NL_OK)\n    {\n        return ret;\n    }\n\n    if (dco->c->mode == CM_TOP)\n    {\n        multi_process_incoming_dco(dco);\n    }\n    else\n    {\n        process_incoming_dco(dco);\n    }\n\n    return NL_OK;\n}\n\nint\ndco_read_and_process(dco_context_t *dco)\n{\n    msg(D_DCO_DEBUG, __func__);\n\n    return ovpn_nl_recvmsgs(dco, __func__);\n}\n\nstatic int\ndco_get_peer(dco_context_t *dco, int peer_id, const bool raise_sigusr1_on_err)\n{\n    ASSERT(dco);\n\n    if (__is_locked)\n    {\n        msg(D_DCO_DEBUG, \"%s: cannot request peer stats while parsing other messages\", __func__);\n        return 0;\n    }\n\n    /* peer_id == -1 means \"dump all peers\", but this is allowed in MP mode only.\n     * If it happens in P2P mode it means that the DCO peer was deleted and we\n     * can simply bail out\n     */\n    if (peer_id == -1 && dco->ifmode == OVPN_MODE_P2P)\n    {\n        return 0;\n    }\n\n    msg(D_DCO_DEBUG | M_NOIPREFIX, \"%s: peer-id %d\", __func__, peer_id);\n\n    struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_PEER_GET);\n    struct nlattr *attr = nla_nest_start(nl_msg, OVPN_A_PEER);\n    int ret = -EMSGSIZE;\n\n    if (peer_id != -1)\n    {\n        NLA_PUT_U32(nl_msg, OVPN_A_PEER_ID, peer_id);\n    }\n    else\n    {\n        nlmsg_hdr(nl_msg)->nlmsg_flags |= NLM_F_DUMP;\n    }\n    nla_nest_end(nl_msg, attr);\n\n    ret = ovpn_nl_msg_send(dco, nl_msg, __func__);\n\nnla_put_failure:\n    nlmsg_free(nl_msg);\n\n    if (raise_sigusr1_on_err && ret < 0)\n    {\n        msg(M_WARN, \"Error retrieving DCO peer stats: the underlying DCO peer\"\n                    \"may have been deleted from the kernel without notifying \"\n                    \"userspace. Restarting the session\");\n        register_signal(dco->c->sig, SIGUSR1, \"dco peer stats error\");\n    }\n    return ret;\n}\n\nint\ndco_get_peer_stats(struct context *c, const bool raise_sigusr1_on_err)\n{\n    if (!c->c1.tuntap || c->c1.tuntap->dco.ifindex == 0)\n    {\n        return -1;\n    }\n\n    return dco_get_peer(&c->c1.tuntap->dco, c->c2.tls_multi->dco_peer_id, raise_sigusr1_on_err);\n}\n\nint\ndco_get_peer_stats_multi(dco_context_t *dco, const bool raise_sigusr1_on_err)\n{\n    return dco_get_peer(dco, -1, raise_sigusr1_on_err);\n}\n\nbool\ndco_available(msglvl_t msglevel)\n{\n    if (resolve_ovpn_netlink_id(D_DCO_DEBUG) < 0)\n    {\n        msg(msglevel, \"Note: Kernel support for ovpn-dco missing, disabling data channel offload.\");\n        return false;\n    }\n\n    return true;\n}\n\n/**\n * There's no version indicator in the ovpn in-tree module, so we return a\n * string containing info about the kernel version and release.\n */\nstatic const char *\ndco_version_string_in_tree(struct gc_arena *gc)\n{\n    struct buffer buf = alloc_buf_gc(256, gc);\n    struct utsname system;\n\n    if (uname(&system))\n    {\n        return \"ERR\";\n    }\n\n    buf_puts(&buf, system.release);\n    buf_puts(&buf, \" \");\n    buf_puts(&buf, system.version);\n    return BSTR(&buf);\n}\n\n/**\n * When the module is loaded, the backports version of ovpn has a version file\n * in sysfs. Read it and return the string.\n *\n * The caller is responsible for closing the file pointer.\n */\nstatic const char *\ndco_version_string_backports(FILE *fp, struct gc_arena *gc)\n{\n    char *str = gc_malloc(PATH_MAX, false, gc);\n\n    if (!fgets(str, PATH_MAX, fp))\n    {\n        return \"ERR\";\n    }\n\n    /* remove potential newline at the end of the string */\n    char *nl = strchr(str, '\\n');\n    if (nl)\n    {\n        *nl = '\\0';\n    }\n\n    return str;\n}\n\nconst char *\ndco_version_string(struct gc_arena *gc)\n{\n    const char *version;\n    struct stat sb;\n    FILE *fp;\n\n    if (stat(\"/sys/module/ovpn\", &sb) != 0 || !S_ISDIR(sb.st_mode))\n    {\n        return \"N/A\";\n    }\n\n    /* now that we know for sure that the module is loaded, if there's no\n     * version file it means we're dealing with the in-tree version, otherwise\n     * it's backports */\n    fp = fopen(\"/sys/module/ovpn/version\", \"r\");\n    if (!fp)\n    {\n        return dco_version_string_in_tree(gc);\n    }\n    version = dco_version_string_backports(fp, gc);\n\n    fclose(fp);\n    return version;\n}\n\nvoid\ndco_event_set(dco_context_t *dco, struct event_set *es, void *arg)\n{\n    if (dco && dco->nl_sock)\n    {\n        event_ctl(es, nl_socket_get_fd(dco->nl_sock), EVENT_READ, arg);\n    }\n}\n\nconst char *\ndco_get_supported_ciphers(void)\n{\n    return \"AES-128-GCM:AES-256-GCM:AES-192-GCM:CHACHA20-POLY1305\";\n}\n\nbool\ndco_supports_epoch_data(struct context *c)\n{\n    return false;\n}\n\n#endif /* defined(ENABLE_DCO) && defined(TARGET_LINUX) */\n"
  },
  {
    "path": "src/openvpn/dco_linux.h",
    "content": "/*\n *  Interface to linux dco networking code\n *\n *  Copyright (C) 2020-2026 Antonio Quartulli <a@unstable.cc>\n *  Copyright (C) 2020-2026 Arne Schwabe <arne@rfc2549.org>\n *  Copyright (C) 2020-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n#ifndef DCO_LINUX_H\n#define DCO_LINUX_H\n\n#if defined(ENABLE_DCO) && defined(TARGET_LINUX)\n\n#include \"event.h\"\n\n#include \"ovpn_dco_linux.h\"\n\n#include <netlink/socket.h>\n#include <netlink/netlink.h>\n\n/* Defines to avoid mismatching with other platforms */\n#define OVPN_CMD_DEL_PEER   OVPN_CMD_PEER_DEL_NTF\n#define OVPN_CMD_SWAP_KEYS  OVPN_CMD_KEY_SWAP_NTF\n#define OVPN_CMD_FLOAT_PEER OVPN_CMD_PEER_FLOAT_NTF\n\ntypedef enum ovpn_key_slot dco_key_slot_t;\ntypedef enum ovpn_cipher_alg dco_cipher_t;\n\n/* OVPN section */\n\n#ifndef IFLA_OVPN_MAX\n\nenum ovpn_mode\n{\n    OVPN_MODE_P2P,\n    OVPN_MODE_MP,\n};\n\nenum ovpn_ifla_attrs\n{\n    IFLA_OVPN_UNSPEC = 0,\n    IFLA_OVPN_MODE,\n\n    __IFLA_OVPN_MAX,\n};\n\n#define IFLA_OVPN_MAX (__IFLA_OVPN_MAX - 1)\n\n#endif /* ifndef IFLA_OVPN_MAX */\n\ntypedef struct\n{\n    struct nl_sock *nl_sock;\n    struct nl_cb *nl_cb;\n    int status;\n\n    struct context *c;\n    int ctrlid;\n\n    enum ovpn_mode ifmode;\n\n    int ovpn_dco_id;\n    int ovpn_dco_mcast_id;\n\n    unsigned int ifindex;\n\n    int dco_message_type;\n    int dco_message_peer_id;\n    int dco_message_key_id;\n    int dco_del_peer_reason;\n    struct sockaddr_storage dco_float_peer_ss;\n} dco_context_t;\n\n#endif /* defined(ENABLE_DCO) && defined(TARGET_LINUX) */\n#endif /* ifndef DCO_LINUX_H */\n"
  },
  {
    "path": "src/openvpn/dco_win.c",
    "content": "/*\n *  Interface to ovpn-win-dco networking code\n *\n *  Copyright (C) 2020-2026 Arne Schwabe <arne@rfc2549.org>\n *  Copyright (C) 2020-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#if defined(_WIN32)\n\n#include \"syshead.h\"\n\n#include \"dco.h\"\n#include \"forward.h\"\n#include \"tun.h\"\n#include \"crypto.h\"\n#include \"multi.h\"\n#include \"ssl_common.h\"\n#include \"openvpn.h\"\n\n#include <bcrypt.h>\n#include <winsock2.h>\n#include <ws2tcpip.h>\n\n#if defined(__MINGW32__)\nconst IN_ADDR in4addr_any = { 0 };\n#endif\n\n/* Sometimes IP Helper API, which we use for setting IP address etc,\n * complains that interface is not found. Give it some time to settle\n */\nstatic void\ndco_wait_ready(DWORD idx)\n{\n    for (int i = 0; i < 20; ++i)\n    {\n        MIB_IPINTERFACE_ROW row = { .InterfaceIndex = idx, .Family = AF_INET };\n        if (GetIpInterfaceEntry(&row) != ERROR_NOT_FOUND)\n        {\n            break;\n        }\n        msg(D_DCO_DEBUG, \"interface %ld not yet ready, retrying\", idx);\n        Sleep(50);\n    }\n}\n\n/**\n * Gets version of dco-win driver\n *\n * Fills Major/Minor/Patch fields in a passed OVPN_VERSION\n * struct. If version cannot be obtained, fields are set to 0.\n *\n * @param version pointer to OVPN_VERSION struct\n * @returns true if version has been obtained, false otherwise\n */\nstatic bool\ndco_get_version(OVPN_VERSION *version)\n{\n    CLEAR(*version);\n\n    bool res = false;\n\n    HANDLE h = CreateFile(\"\\\\\\\\.\\\\ovpn-dco-ver\", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);\n\n    if (h == INVALID_HANDLE_VALUE)\n    {\n        /* fallback to a \"normal\" device, this will fail if device is already in use */\n        h = CreateFile(\"\\\\\\\\.\\\\ovpn-dco\", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);\n    }\n\n    if (h == INVALID_HANDLE_VALUE)\n    {\n        goto done;\n    }\n\n    DWORD bytes_returned = 0;\n    if (!DeviceIoControl(h, OVPN_IOCTL_GET_VERSION, NULL, 0, version, sizeof(*version),\n                         &bytes_returned, NULL))\n    {\n        goto done;\n    }\n\n    res = true;\n\ndone:\n    if (h != INVALID_HANDLE_VALUE)\n    {\n        CloseHandle(h);\n    }\n\n    msg(D_DCO_DEBUG, \"dco version: %ld.%ld.%ld\", version->Major, version->Minor, version->Patch);\n\n    return res;\n}\n\n/**\n * @brief Initializes the DCO adapter in multipeer mode and sets it to \"connected\" state.\n *\n * Opens the DCO device, sets the adapter mode using `OVPN_IOCTL_SET_MODE`,\n * which transitions the adapter to the \"connected\" state, and waits for it to become ready.\n *\n * @param dco Pointer to the `dco_context_t` structure representing the DCO context.\n * @param dev_node Device node string for the DCO adapter.\n */\nvoid\novpn_dco_init_mp(dco_context_t *dco, const char *dev_node)\n{\n    ASSERT(dco->ifmode == DCO_MODE_UNINIT);\n    dco->ifmode = DCO_MODE_MP;\n\n    /* Use manual reset event so it remains signalled until\n     * explicitly reset. This way we won't lose notifications\n     */\n    dco->ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);\n    if (dco->ov.hEvent == NULL)\n    {\n        msg(M_ERR, \"Error: ovpn_dco_init: CreateEvent failed\");\n    }\n\n    dco->rwhandle.read = dco->ov.hEvent;\n\n    /* open DCO device */\n    struct gc_arena gc = gc_new();\n    const char *device_guid;\n    tun_open_device(dco->tt, dev_node, &device_guid, &gc);\n    gc_free(&gc);\n\n    /* set mp mode */\n    OVPN_MODE m = OVPN_MODE_MP;\n    DWORD bytes_returned = 0;\n    if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_SET_MODE, &m, sizeof(m), NULL, 0,\n                         &bytes_returned, NULL))\n    {\n        msg(M_ERR, \"DeviceIoControl(OVPN_IOCTL_SET_MODE) failed\");\n    }\n\n    dco_wait_ready(dco->tt->adapter_index);\n}\n\n/**\n * @brief Transitions the DCO adapter to the connected state in P2P mode.\n *\n * Sends `OVPN_IOCTL_START_VPN` to start the VPN and waits for the adapter\n * to become ready.\n *\n * @param tt Pointer to the `tuntap` structure representing the adapter.\n */\nvoid\ndco_p2p_start_vpn(struct tuntap *tt)\n{\n    DWORD bytes_returned = 0;\n    if (!DeviceIoControl(tt->hand, OVPN_IOCTL_START_VPN, NULL, 0, NULL, 0, &bytes_returned, NULL))\n    {\n        msg(M_ERR, \"DeviceIoControl(OVPN_IOCTL_START_VPN) failed\");\n    }\n\n    /* Sometimes IP Helper API, which we use for setting IP address etc,\n     * complains that interface is not found. Give it some time to settle\n     */\n    dco_wait_ready(tt->adapter_index);\n}\n\n\n/**\n * @brief Initializes DCO depends on `mode`\n *\n *  - for P2P it puts adapter in \"connected\" state. The peer should\n * be already added by dco_p2p_new_peer().\n *\n *  - for multipeer it opens DCO adapter and puts it into \"connected\"\n * state. The server socket should be initialized later by dco_mp_start_vpn().\n */\nbool\novpn_dco_init(struct context *c)\n{\n    dco_context_t *dco = &c->c1.tuntap->dco;\n\n    dco->c = c;\n\n    switch (c->mode)\n    {\n        case MODE_POINT_TO_POINT:\n            dco->ifmode = DCO_MODE_P2P;\n            dco_p2p_start_vpn(dco->tt);\n            break;\n\n        case MODE_SERVER:\n            ovpn_dco_init_mp(dco, c->options.dev_node);\n            break;\n\n        default:\n            ASSERT(false);\n    }\n\n    return true;\n}\n\nint\nopen_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev)\n{\n    ASSERT(0);\n    return 0;\n}\n\nstatic void\ndco_connect_wait(HANDLE handle, OVERLAPPED *ov, int timeout, struct signal_info *sig_info)\n{\n    volatile int *signal_received = &sig_info->signal_received;\n    /* GetOverlappedResultEx is available starting from Windows 8 */\n    typedef BOOL(WINAPI * get_overlapped_result_ex_t)(HANDLE, LPOVERLAPPED, LPDWORD, DWORD, BOOL);\n    get_overlapped_result_ex_t get_overlapped_result_ex =\n        (get_overlapped_result_ex_t)GetProcAddress(GetModuleHandle(\"Kernel32.dll\"),\n                                                   \"GetOverlappedResultEx\");\n\n    if (get_overlapped_result_ex == NULL)\n    {\n        msg(M_ERR, \"Failed to load GetOverlappedResult()\");\n    }\n\n    DWORD timeout_msec = timeout * 1000;\n    const int poll_interval_ms = 50;\n\n    while (timeout_msec > 0)\n    {\n        timeout_msec -= poll_interval_ms;\n\n        DWORD transferred;\n        if (get_overlapped_result_ex(handle, ov, &transferred, poll_interval_ms, FALSE) != 0)\n        {\n            /* TCP connection established by dco */\n            return;\n        }\n\n        DWORD err = GetLastError();\n        if ((err != WAIT_TIMEOUT) && (err != ERROR_IO_INCOMPLETE))\n        {\n            /* dco reported connection error */\n            msg(M_NONFATAL | M_ERRNO, \"dco connect error\");\n            register_signal(sig_info, SIGUSR1, \"dco-connect-error\");\n            return;\n        }\n\n        get_signal(signal_received);\n        if (*signal_received)\n        {\n            return;\n        }\n\n        management_sleep(0);\n    }\n\n    /* we end up here when timeout occurs in userspace */\n    msg(M_NONFATAL, \"dco connect timeout\");\n    register_signal(sig_info, SIGUSR1, \"dco-connect-timeout\");\n}\n\n/**\n * @brief Initializes and binds the kernel UDP transport socket for multipeer mode.\n *\n * Sends `OVPN_IOCTL_MP_START_VPN` to create a kernel-mode UDP socket, binds it to\n * the specified address, ready for incoming connections.\n *\n * @param handle Device handle for the DCO adapter.\n * @param sock Pointer to the `link_socket` structure containing socket information.\n */\nvoid\ndco_mp_start_vpn(HANDLE handle, struct link_socket *sock)\n{\n    msg(D_DCO_DEBUG, \"%s\", __func__);\n\n    int ai_family = sock->info.lsa->bind_local->ai_family;\n    struct addrinfo *local = sock->info.lsa->bind_local;\n    struct addrinfo *cur = NULL;\n\n    for (cur = local; cur; cur = cur->ai_next)\n    {\n        if (cur->ai_family == ai_family)\n        {\n            break;\n        }\n    }\n    if (!cur)\n    {\n        msg(M_FATAL, \"%s: Socket bind failed: Addr to bind has no %s record\", __func__,\n            addr_family_name(ai_family));\n    }\n\n    OVPN_MP_START_VPN in, out;\n    in.IPv6Only = sock->info.bind_ipv6_only ? 1 : 0;\n    if (ai_family == AF_INET)\n    {\n        memcpy(&in.ListenAddress.Addr4, cur->ai_addr, sizeof(struct sockaddr_in));\n    }\n    else\n    {\n        memcpy(&in.ListenAddress.Addr6, cur->ai_addr, sizeof(struct sockaddr_in6));\n    }\n\n    /* in multipeer mode control channel packets are prepended with remote peer's sockaddr */\n    sock->sockflags |= SF_PREPEND_SA;\n\n    DWORD bytes_returned = 0;\n    if (!DeviceIoControl(handle, OVPN_IOCTL_MP_START_VPN, &in, sizeof(in), &out, sizeof(out),\n                         &bytes_returned, NULL))\n    {\n        msg(M_ERR, \"DeviceIoControl(OVPN_IOCTL_MP_START_VPN) failed\");\n    }\n}\n\nvoid\ndco_p2p_new_peer(HANDLE handle, OVERLAPPED *ov, struct link_socket *sock,\n                 struct signal_info *sig_info)\n{\n    msg(D_DCO_DEBUG, \"%s\", __func__);\n\n    OVPN_NEW_PEER peer = { 0 };\n\n    struct addrinfo *remoteaddr = sock->info.lsa->current_remote;\n\n    struct sockaddr *local = NULL;\n    struct sockaddr *remote = remoteaddr->ai_addr;\n\n    if (remoteaddr->ai_protocol == IPPROTO_TCP || remoteaddr->ai_socktype == SOCK_STREAM)\n    {\n        peer.Proto = OVPN_PROTO_TCP;\n    }\n    else\n    {\n        peer.Proto = OVPN_PROTO_UDP;\n    }\n\n    if (sock->bind_local)\n    {\n        /* Use first local address with correct address family */\n        struct addrinfo *bind = sock->info.lsa->bind_local;\n        while (bind && !local)\n        {\n            if (bind->ai_family == remote->sa_family)\n            {\n                local = bind->ai_addr;\n            }\n            bind = bind->ai_next;\n        }\n    }\n\n    if (sock->bind_local && !local)\n    {\n        msg(M_FATAL, \"DCO: Socket bind failed: Address to bind lacks %s record\",\n            addr_family_name(remote->sa_family));\n    }\n\n    if (remote->sa_family == AF_INET6)\n    {\n        peer.Remote.Addr6 = *((SOCKADDR_IN6 *)(remoteaddr->ai_addr));\n        if (local)\n        {\n            peer.Local.Addr6 = *((SOCKADDR_IN6 *)local);\n        }\n        else\n        {\n            peer.Local.Addr6.sin6_addr = in6addr_any;\n            peer.Local.Addr6.sin6_port = 0;\n            peer.Local.Addr6.sin6_family = AF_INET6;\n        }\n    }\n    else if (remote->sa_family == AF_INET)\n    {\n        peer.Remote.Addr4 = *((SOCKADDR_IN *)(remoteaddr->ai_addr));\n        if (local)\n        {\n            peer.Local.Addr4 = *((SOCKADDR_IN *)local);\n        }\n        else\n        {\n            peer.Local.Addr4.sin_addr = in4addr_any;\n            peer.Local.Addr4.sin_port = 0;\n            peer.Local.Addr4.sin_family = AF_INET;\n        }\n    }\n    else\n    {\n        ASSERT(0);\n    }\n\n    CLEAR(*ov);\n    if (!DeviceIoControl(handle, OVPN_IOCTL_NEW_PEER, &peer, sizeof(peer), NULL, 0, NULL, ov))\n    {\n        DWORD err = GetLastError();\n        if (err != ERROR_IO_PENDING)\n        {\n            msg(M_ERR, \"DeviceIoControl(OVPN_IOCTL_NEW_PEER) failed\");\n        }\n        else\n        {\n            dco_connect_wait(handle, ov, get_server_poll_remaining_time(sock->server_poll_timeout),\n                             sig_info);\n        }\n    }\n}\n\nint\ndco_new_peer(dco_context_t *dco, unsigned int peerid, socket_descriptor_t sd, struct sockaddr *localaddr,\n             struct sockaddr *remoteaddr, struct in_addr *vpn_ipv4, struct in6_addr *vpn_ipv6)\n{\n    msg(D_DCO_DEBUG, \"%s: peer-id %d, fd \" SOCKET_PRINTF, __func__, peerid, sd);\n\n    if (dco->ifmode == DCO_MODE_P2P)\n    {\n        /* no-op for p2p */\n        return 0;\n    }\n\n    OVPN_MP_NEW_PEER newPeer = { 0 };\n\n    if (remoteaddr)\n    {\n        /* while the driver doesn't use the local address yet it requires its AF to be valid */\n        newPeer.Local.Addr4.sin_family = remoteaddr->sa_family;\n\n        if (remoteaddr->sa_family == AF_INET)\n        {\n            memcpy(&newPeer.Remote.Addr4, remoteaddr, sizeof(struct sockaddr_in));\n        }\n        else\n        {\n            memcpy(&newPeer.Remote.Addr6, remoteaddr, sizeof(struct sockaddr_in6));\n        }\n    }\n\n    if (vpn_ipv4)\n    {\n        newPeer.VpnAddr4 = *vpn_ipv4;\n    }\n\n    if (vpn_ipv6)\n    {\n        newPeer.VpnAddr6 = *vpn_ipv6;\n    }\n\n    newPeer.PeerId = peerid;\n\n    DWORD bytesReturned;\n    if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_MP_NEW_PEER, &newPeer, sizeof(newPeer), NULL, 0,\n                         &bytesReturned, NULL))\n    {\n        msg(M_ERR, \"DeviceIoControl(OVPN_IOCTL_MP_NEW_PEER) failed\");\n    }\n\n    return 0;\n}\n\nint\ndco_del_peer(dco_context_t *dco, unsigned int peerid)\n{\n    msg(D_DCO_DEBUG, \"%s: peer-id %d\", __func__, peerid);\n\n    OVPN_MP_DEL_PEER del_peer = { peerid };\n    VOID *buf = NULL;\n    DWORD len = 0;\n    DWORD ioctl = OVPN_IOCTL_DEL_PEER;\n\n    if (dco->ifmode == DCO_MODE_MP)\n    {\n        ioctl = OVPN_IOCTL_MP_DEL_PEER;\n        buf = &del_peer;\n        len = sizeof(del_peer);\n    }\n\n    DWORD bytes_returned = 0;\n    if (!DeviceIoControl(dco->tt->hand, ioctl, buf, len, NULL, 0, &bytes_returned, NULL))\n    {\n        msg(M_WARN | M_ERRNO, \"DeviceIoControl(OVPN_IOCTL_DEL_PEER) failed\");\n        return -1;\n    }\n    return 0;\n}\n\nint\ndco_set_peer(dco_context_t *dco, unsigned int peerid, int keepalive_interval, int keepalive_timeout,\n             int mss)\n{\n    msg(D_DCO_DEBUG, \"%s: peer-id %d, keepalive %d/%d, mss %d\", __func__, peerid,\n        keepalive_interval, keepalive_timeout, mss);\n\n    OVPN_MP_SET_PEER mp_peer = { peerid, keepalive_interval, keepalive_timeout, mss };\n    OVPN_SET_PEER peer = { keepalive_interval, keepalive_timeout, mss };\n    VOID *buf = NULL;\n    DWORD len = 0;\n    DWORD ioctl = (dco->ifmode == DCO_MODE_MP) ? OVPN_IOCTL_MP_SET_PEER : OVPN_IOCTL_SET_PEER;\n\n    if (dco->ifmode == DCO_MODE_MP)\n    {\n        buf = &mp_peer;\n        len = sizeof(OVPN_MP_SET_PEER);\n    }\n    else\n    {\n        buf = &peer;\n        len = sizeof(OVPN_SET_PEER);\n    }\n\n    DWORD bytes_returned = 0;\n    if (!DeviceIoControl(dco->tt->hand, ioctl, buf, len, NULL, 0, &bytes_returned, NULL))\n    {\n        msg(M_WARN | M_ERRNO, \"DeviceIoControl(OVPN_IOCTL_MP_SET_PEER) failed\");\n        return -1;\n    }\n\n    return 0;\n}\n\nint\ndco_new_key(dco_context_t *dco, unsigned int peerid, int keyid, dco_key_slot_t slot,\n            const uint8_t *encrypt_key, const uint8_t *encrypt_iv, const uint8_t *decrypt_key,\n            const uint8_t *decrypt_iv, const char *ciphername, bool epoch)\n{\n    msg(D_DCO_DEBUG, \"%s: slot %d, key-id %d, peer-id %d, cipher %s\", __func__, slot, keyid, peerid,\n        ciphername);\n\n    const int nonce_len = 8;\n    size_t key_len = cipher_kt_key_size(ciphername);\n    ASSERT(key_len <= 32);\n\n    OVPN_CRYPTO_DATA_V2 crypto_data;\n    ZeroMemory(&crypto_data, sizeof(crypto_data));\n\n    OVPN_CRYPTO_DATA *v1 = &crypto_data.V1;\n\n    v1->CipherAlg = dco_get_cipher(ciphername);\n    ASSERT(keyid >= 0 && keyid <= UCHAR_MAX);\n    v1->KeyId = (unsigned char)keyid;\n    v1->PeerId = peerid;\n    v1->KeySlot = slot;\n\n    /* for epoch we use key material as a seed, no as actual key */\n    CopyMemory(v1->Encrypt.Key, encrypt_key, epoch ? 32 : key_len);\n    v1->Encrypt.KeyLen = (unsigned char)key_len;\n    CopyMemory(v1->Encrypt.NonceTail, encrypt_iv, nonce_len);\n\n    CopyMemory(v1->Decrypt.Key, decrypt_key, epoch ? 32 : key_len);\n    v1->Decrypt.KeyLen = (unsigned char)key_len;\n    CopyMemory(v1->Decrypt.NonceTail, decrypt_iv, nonce_len);\n\n    ASSERT(v1->CipherAlg > 0);\n\n    DWORD ioctl = OVPN_IOCTL_NEW_KEY;\n    VOID *buf = &crypto_data.V1;\n    DWORD bufSize = sizeof(crypto_data.V1);\n    if (epoch)\n    {\n        ioctl = OVPN_IOCTL_NEW_KEY_V2;\n        crypto_data.CryptoOptions |= CRYPTO_OPTIONS_EPOCH;\n        buf = &crypto_data;\n        bufSize = sizeof(crypto_data);\n    }\n\n    DWORD bytes_returned = 0;\n\n    if (!DeviceIoControl(dco->tt->hand, ioctl, buf, bufSize, NULL, 0, &bytes_returned, NULL))\n    {\n        msg(M_ERR, \"DeviceIoControl(OVPN_IOCTL_NEW_KEY) failed\");\n        return -1;\n    }\n    return 0;\n}\n\nint\ndco_del_key(dco_context_t *dco, unsigned int peerid, dco_key_slot_t slot)\n{\n    msg(D_DCO, \"%s: peer-id %d, slot %d called but ignored\", __func__, peerid, slot);\n    /* FIXME: Implement in driver first */\n    return 0;\n}\n\nint\ndco_swap_keys(dco_context_t *dco, unsigned int peer_id)\n{\n    msg(D_DCO_DEBUG, \"%s: peer-id %d\", __func__, peer_id);\n\n    OVPN_MP_SWAP_KEYS swap = { peer_id };\n    DWORD ioctl = OVPN_IOCTL_SWAP_KEYS;\n    VOID *buf = NULL;\n    DWORD len = 0;\n\n    if (dco->ifmode == DCO_MODE_MP)\n    {\n        ioctl = OVPN_IOCTL_MP_SWAP_KEYS;\n        buf = &swap;\n        len = sizeof(swap);\n    }\n\n    DWORD bytes_returned = 0;\n    if (!DeviceIoControl(dco->tt->hand, ioctl, buf, len, NULL, 0, &bytes_returned, NULL))\n    {\n        msg(M_ERR, \"DeviceIoControl(OVPN_IOCTL_SWAP_KEYS) failed\");\n        return -1;\n    }\n    return 0;\n}\n\nbool\ndco_available(msglvl_t msglevel)\n{\n    /* try to open device by symbolic name */\n    HANDLE h = CreateFile(\"\\\\\\\\.\\\\ovpn-dco\", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,\n                          FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, NULL);\n\n    if (h != INVALID_HANDLE_VALUE)\n    {\n        CloseHandle(h);\n        return true;\n    }\n\n    DWORD err = GetLastError();\n    if (err == ERROR_ACCESS_DENIED)\n    {\n        /* this likely means that device exists but is already in use,\n         * don't bail out since later we try to open all existing dco\n         * devices and then bail out if all devices are in use\n         */\n        return true;\n    }\n\n    msg(msglevel, \"Note: ovpn-dco-win driver is missing, disabling data channel offload.\");\n    return false;\n}\n\nconst char *\ndco_version_string(struct gc_arena *gc)\n{\n    OVPN_VERSION version = { 0 };\n    if (dco_get_version(&version))\n    {\n        struct buffer out = alloc_buf_gc(256, gc);\n        buf_printf(&out, \"%ld.%ld.%ld\", version.Major, version.Minor, version.Patch);\n        return BSTR(&out);\n    }\n    else\n    {\n        return \"N/A\";\n    }\n}\n\n/**\n * @brief Handles successful completion of overlapped operation.\n *\n * We use overlapped I/O (Windows term for asynchronous I/O) to get\n * notifications from kernel to userspace. This gets the result of overlapped\n * operation and, in case of success, copies data from kernel-filled buffer\n * into userspace-provided dco context.\n *\n * @param dco Pointer to the dco context\n * @param queued true if operation was queued, false if it has completed immediately\n */\nstatic void\ndco_handle_overlapped_success(dco_context_t *dco, bool queued)\n{\n    DWORD bytes_read = 0;\n    BOOL res = GetOverlappedResult(dco->tt->hand, &dco->ov, &bytes_read, FALSE);\n    if (res)\n    {\n        msg(D_DCO_DEBUG, \"%s: completion%s success [%ld]\", __func__, queued ? \"\" : \" non-queued\",\n            bytes_read);\n\n        dco->dco_message_peer_id = dco->notif_buf.PeerId;\n        dco->dco_message_type = dco->notif_buf.Cmd;\n        dco->dco_del_peer_reason = dco->notif_buf.DelPeerReason;\n        dco->dco_float_peer_ss = dco->notif_buf.FloatAddress;\n    }\n    else\n    {\n        msg(D_DCO_DEBUG | M_ERRNO, \"%s: completion%s error\", __func__, queued ? \"\" : \" non-queued\");\n    }\n}\n\nint\ndco_read_and_process(dco_context_t *dco)\n{\n    if (dco->ifmode != DCO_MODE_MP)\n    {\n        ASSERT(false);\n    }\n\n    dco->dco_message_peer_id = -1;\n    dco->dco_message_type = 0;\n\n    switch (dco->iostate)\n    {\n        case IOSTATE_QUEUED:\n            dco_handle_overlapped_success(dco, true);\n\n            ASSERT(ResetEvent(dco->ov.hEvent));\n            dco->iostate = IOSTATE_INITIAL;\n\n            break;\n\n        case IOSTATE_IMMEDIATE_RETURN:\n            dco->iostate = IOSTATE_INITIAL;\n            ASSERT(ResetEvent(dco->ov.hEvent));\n\n            if (dco->ov_ret == ERROR_SUCCESS)\n            {\n                dco_handle_overlapped_success(dco, false);\n            }\n            else\n            {\n                SetLastError(dco->ov_ret);\n                msg(D_DCO_DEBUG | M_ERRNO, \"%s: completion non-queued error\", __func__);\n            }\n\n            break;\n    }\n\n    if (dco->c->mode == CM_TOP)\n    {\n        multi_process_incoming_dco(dco);\n    }\n    else\n    {\n        process_incoming_dco(dco);\n    }\n\n    return 0;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\nint\ndco_get_peer_stats_multi(dco_context_t *dco, const bool raise_sigusr1_on_err)\n{\n    struct gc_arena gc = gc_new();\n\n    int ret = 0;\n    struct tuntap *tt = dco->tt;\n\n    if (!tuntap_defined(tt))\n    {\n        ret = -1;\n        goto done;\n    }\n\n    OVPN_GET_PEER_STATS ps = {\n        .PeerId = -1\n    };\n\n    DWORD required_size = 0, bytes_returned = 0;\n    /* first, figure out buffer size */\n    if (!DeviceIoControl(tt->hand, OVPN_IOCTL_GET_PEER_STATS, &ps, sizeof(ps), &required_size, sizeof(DWORD), &bytes_returned, NULL))\n    {\n        if (GetLastError() == ERROR_MORE_DATA)\n        {\n            if (bytes_returned != sizeof(DWORD))\n            {\n                msg(M_WARN, \"%s: invalid bytes returned for size query (%lu, expected %zu)\", __func__, bytes_returned, sizeof(DWORD));\n                ret = -1;\n                goto done;\n            }\n            /* required_size now contains the size written by the driver */\n            if (required_size == 0)\n            {\n                ret = 0; /* no peers to process */\n                goto done;\n            }\n            if (required_size < sizeof(OVPN_PEER_STATS))\n            {\n                msg(M_WARN, \"%s: invalid required size %lu (minimum %zu)\", __func__, required_size, sizeof(OVPN_PEER_STATS));\n                ret = -1;\n                goto done;\n            }\n        }\n        else\n        {\n            msg(M_WARN | M_ERRNO, \"%s: failed to fetch required buffer size\", __func__);\n            ret = -1;\n            goto done;\n        }\n    }\n    else\n    {\n        /* unexpected success? */\n        if (bytes_returned == 0)\n        {\n            ret = 0; /* no peers to process */\n            goto done;\n        }\n\n        msg(M_WARN, \"%s: first DeviceIoControl call succeeded unexpectedly (%lu bytes returned)\", __func__, bytes_returned);\n        ret = -1;\n        goto done;\n    }\n\n\n    /* allocate the buffer and fetch stats */\n    OVPN_PEER_STATS *peer_stats = gc_malloc(required_size, true, &gc);\n    if (!peer_stats)\n    {\n        msg(M_WARN, \"%s: failed to allocate buffer of size %lu\", __func__, required_size);\n        ret = -1;\n        goto done;\n    }\n\n    if (!DeviceIoControl(tt->hand, OVPN_IOCTL_GET_PEER_STATS, &ps, sizeof(ps), peer_stats, required_size, &bytes_returned, NULL))\n    {\n        /* unlikely case when a peer has been added since fetching buffer size, not an error! */\n        if (GetLastError() == ERROR_MORE_DATA)\n        {\n            msg(M_WARN, \"%s: peer has been added, skip fetching stats\", __func__);\n            ret = 0;\n            goto done;\n        }\n\n        msg(M_WARN | M_ERRNO, \"%s: failed to fetch multipeer stats\", __func__);\n        ret = -1;\n        goto done;\n    }\n\n    /* iterate over stats and update peers */\n    for (size_t i = 0; i < bytes_returned / sizeof(OVPN_PEER_STATS); ++i)\n    {\n        OVPN_PEER_STATS *stat = &peer_stats[i];\n\n        if (stat->PeerId >= dco->c->multi->max_clients)\n        {\n            msg(M_WARN, \"%s: received out of bound peer_id %u (max=%u)\", __func__, stat->PeerId,\n                dco->c->multi->max_clients);\n            continue;\n        }\n\n        struct multi_instance *mi = dco->c->multi->instances[stat->PeerId];\n        if (!mi)\n        {\n            msg(M_WARN, \"%s: received data for a non-existing peer %u\", __func__, stat->PeerId);\n            continue;\n        }\n\n        /* update peer stats */\n        struct context_2 *c2 = &mi->context.c2;\n        c2->dco_read_bytes = stat->LinkRxBytes;\n        c2->dco_write_bytes = stat->LinkTxBytes;\n        c2->tun_read_bytes = stat->VpnRxBytes;\n        c2->tun_write_bytes = stat->VpnTxBytes;\n    }\n\ndone:\n    gc_free(&gc);\n\n    if (raise_sigusr1_on_err && ret < 0)\n    {\n        register_signal(dco->c->sig, SIGUSR1, \"dco peer stats error\");\n    }\n\n    return ret;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nint\ndco_get_peer_stats_fallback(struct context *c, const bool raise_sigusr1_on_err)\n{\n    struct tuntap *tt = c->c1.tuntap;\n\n    if (!tuntap_defined(tt))\n    {\n        return -1;\n    }\n\n    OVPN_STATS stats;\n    ZeroMemory(&stats, sizeof(OVPN_STATS));\n\n    DWORD bytes_returned = 0;\n    if (!DeviceIoControl(tt->hand, OVPN_IOCTL_GET_STATS, NULL, 0, &stats, sizeof(stats),\n                         &bytes_returned, NULL))\n    {\n        msg(M_WARN | M_ERRNO, \"DeviceIoControl(OVPN_IOCTL_GET_STATS) failed\");\n        return -1;\n    }\n\n    c->c2.dco_read_bytes = stats.TransportBytesReceived;\n    c->c2.dco_write_bytes = stats.TransportBytesSent;\n    c->c2.tun_read_bytes = stats.TunBytesReceived;\n    c->c2.tun_write_bytes = stats.TunBytesSent;\n\n    return 0;\n}\n\nint\ndco_get_peer_stats(struct context *c, const bool raise_sigusr1_on_err)\n{\n    struct tuntap *tt = c->c1.tuntap;\n\n    if (!tuntap_defined(tt))\n    {\n        return -1;\n    }\n\n    /* first, try a new ioctl */\n    OVPN_GET_PEER_STATS ps = { .PeerId = c->c2.tls_multi->dco_peer_id };\n\n    OVPN_PEER_STATS peer_stats = { 0 };\n    DWORD bytes_returned = 0;\n    if (!DeviceIoControl(tt->hand, OVPN_IOCTL_GET_PEER_STATS, &ps, sizeof(ps), &peer_stats, sizeof(peer_stats),\n                         &bytes_returned, NULL))\n    {\n        if (GetLastError() == ERROR_INVALID_FUNCTION)\n        {\n            /* are we using the old driver? */\n            return dco_get_peer_stats_fallback(c, raise_sigusr1_on_err);\n        }\n\n        msg(M_WARN | M_ERRNO, \"%s: DeviceIoControl(OVPN_IOCTL_GET_PEER_STATS) failed\", __func__);\n        return -1;\n    }\n\n    if (bytes_returned != sizeof(OVPN_PEER_STATS))\n    {\n        msg(M_WARN | M_ERRNO, \"%s: DeviceIoControl(OVPN_IOCTL_GET_PEER_STATS) returned invalid size\", __func__);\n        return -1;\n    }\n\n    c->c2.dco_read_bytes = peer_stats.LinkRxBytes;\n    c->c2.dco_write_bytes = peer_stats.LinkTxBytes;\n    c->c2.tun_read_bytes = peer_stats.VpnRxBytes;\n    c->c2.tun_write_bytes = peer_stats.VpnTxBytes;\n\n    return 0;\n}\n\nvoid\ndco_event_set(dco_context_t *dco, struct event_set *es, void *arg)\n{\n    if (dco->ifmode != DCO_MODE_MP)\n    {\n        /* mp only */\n        return;\n    }\n\n    event_ctl(es, &dco->rwhandle, EVENT_READ, arg);\n\n    if (dco->iostate == IOSTATE_INITIAL)\n    {\n        /* the overlapped IOCTL will signal this event on I/O completion */\n        ASSERT(ResetEvent(dco->ov.hEvent));\n\n        if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_NOTIFY_EVENT, NULL, 0, &dco->notif_buf,\n                             sizeof(dco->notif_buf), NULL, &dco->ov))\n        {\n            DWORD err = GetLastError();\n            if (err == ERROR_IO_PENDING) /* operation queued? */\n            {\n                dco->iostate = IOSTATE_QUEUED;\n                dco->ov_ret = ERROR_SUCCESS;\n\n                msg(D_DCO_DEBUG, \"%s: notify ioctl queued\", __func__);\n            }\n            else\n            {\n                /* error occured */\n                ASSERT(SetEvent(dco->ov.hEvent));\n                dco->iostate = IOSTATE_IMMEDIATE_RETURN;\n                dco->ov_ret = err;\n\n                msg(D_DCO_DEBUG | M_ERRNO, \"%s: notify ioctl error\", __func__);\n            }\n        }\n        else\n        {\n            ASSERT(SetEvent(dco->ov.hEvent));\n            dco->iostate = IOSTATE_IMMEDIATE_RETURN;\n            dco->ov_ret = ERROR_SUCCESS;\n\n            msg(D_DCO_DEBUG, \"%s: notify ioctl immediate return\", __func__);\n        }\n    }\n}\n\nconst char *\ndco_get_supported_ciphers(void)\n{\n    /*\n     * this API can be called either from user mode or kernel mode,\n     * which enables us to probe driver's chachapoly support\n     * (available starting from Windows 11)\n     */\n\n    BCRYPT_ALG_HANDLE h;\n    NTSTATUS status = BCryptOpenAlgorithmProvider(&h, L\"CHACHA20_POLY1305\", NULL, 0);\n    if (BCRYPT_SUCCESS(status))\n    {\n        BCryptCloseAlgorithmProvider(h, 0);\n        return \"AES-128-GCM:AES-256-GCM:AES-192-GCM:CHACHA20-POLY1305\";\n    }\n    else\n    {\n        return \"AES-128-GCM:AES-256-GCM:AES-192-GCM\";\n    }\n}\n\nbool\ndco_win_supports_multipeer(void)\n{\n    OVPN_VERSION ver = { 0 };\n    return dco_get_version(&ver) && ver.Major >= 2;\n}\n\nvoid\ndco_win_add_iroute_ipv4(dco_context_t *dco, in_addr_t dst, unsigned int netbits,\n                        unsigned int peer_id)\n{\n    struct gc_arena gc = gc_new();\n\n    OVPN_MP_IROUTE route = {\n        .Addr.Addr4.S_un.S_addr = dst, .Netbits = netbits, .PeerId = peer_id, .IPv6 = 0\n    };\n\n    msg(D_DCO_DEBUG, \"%s: %s/%d -> peer %d\", __func__, print_in_addr_t(dst, IA_NET_ORDER, &gc),\n        netbits, peer_id);\n\n    DWORD bytes_returned = 0;\n    if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_MP_ADD_IROUTE, &route, sizeof(route), NULL, 0,\n                         &bytes_returned, NULL))\n    {\n        msg(M_WARN | M_ERRNO, \"DeviceIoControl(OVPN_IOCTL_MP_ADD_IROUTE) failed\");\n    }\n\n    gc_free(&gc);\n}\n\nvoid\ndco_win_add_iroute_ipv6(dco_context_t *dco, struct in6_addr dst, unsigned int netbits,\n                        unsigned int peer_id)\n{\n    struct gc_arena gc = gc_new();\n\n    OVPN_MP_IROUTE route = { .Addr.Addr6 = dst, .Netbits = netbits, .PeerId = peer_id, .IPv6 = 1 };\n\n    msg(D_DCO_DEBUG, \"%s: %s/%d -> peer %d\", __func__, print_in6_addr(dst, IA_NET_ORDER, &gc),\n        netbits, peer_id);\n\n    DWORD bytes_returned = 0;\n    if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_MP_ADD_IROUTE, &route, sizeof(route), NULL, 0,\n                         &bytes_returned, NULL))\n    {\n        msg(M_WARN | M_ERRNO, \"DeviceIoControl(OVPN_IOCTL_MP_ADD_IROUTE) failed\");\n    }\n\n    gc_free(&gc);\n}\n\nvoid\ndco_win_del_iroute_ipv4(dco_context_t *dco, in_addr_t dst, unsigned int netbits)\n{\n    struct gc_arena gc = gc_new();\n\n    OVPN_MP_IROUTE route = {\n        .Addr.Addr4.S_un.S_addr = dst, .Netbits = netbits, .PeerId = -1, .IPv6 = 0\n    };\n\n    msg(D_DCO_DEBUG, \"%s: %s/%d\", __func__, print_in_addr_t(dst, IA_NET_ORDER, &gc), netbits);\n\n    DWORD bytes_returned = 0;\n    if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_MP_DEL_IROUTE, &route, sizeof(route), NULL, 0,\n                         &bytes_returned, NULL))\n    {\n        msg(M_WARN | M_ERRNO, \"DeviceIoControl(OVPN_IOCTL_MP_DEL_IROUTE) failed\");\n    }\n\n    gc_free(&gc);\n}\n\nvoid\ndco_win_del_iroute_ipv6(dco_context_t *dco, struct in6_addr dst, unsigned int netbits)\n{\n    struct gc_arena gc = gc_new();\n\n    OVPN_MP_IROUTE route = { .Addr.Addr6 = dst, .Netbits = netbits, .PeerId = -1, .IPv6 = 1 };\n\n    msg(D_DCO_DEBUG, \"%s: %s/%d\", __func__, print_in6_addr(dst, IA_NET_ORDER, &gc), netbits);\n\n    DWORD bytes_returned = 0;\n    if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_MP_DEL_IROUTE, &route, sizeof(route), NULL, 0,\n                         &bytes_returned, NULL))\n    {\n        msg(M_WARN | M_ERRNO, \"DeviceIoControl(OVPN_IOCTL_MP_DEL_IROUTE) failed\");\n    }\n\n    gc_free(&gc);\n}\n\nbool\ndco_supports_epoch_data(struct context *c)\n{\n    OVPN_VERSION ver = { 0 };\n    return dco_get_version(&ver) && ((ver.Major == 2 && ver.Minor >= 8) || (ver.Major > 2));\n}\n\n#endif /* defined(_WIN32) */\n"
  },
  {
    "path": "src/openvpn/dco_win.h",
    "content": "/*\n *  Interface to ovpn-win-dco networking code\n *\n *  Copyright (C) 2020-2026 Arne Schwabe <arne@rfc2549.org>\n *  Copyright (C) 2020-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef DCO_WIN_H\n#define DCO_WIN_H\n\n#if defined(ENABLE_DCO) && defined(_WIN32)\n\n#include <in6addr.h>\n\n#include \"buffer.h\"\n#include \"ovpn_dco_win.h\"\n#include \"sig.h\"\n\ntypedef OVPN_KEY_SLOT dco_key_slot_t;\ntypedef OVPN_CIPHER_ALG dco_cipher_t;\n\ntypedef enum\n{\n    DCO_MODE_UNINIT,\n    DCO_MODE_P2P,\n    DCO_MODE_MP\n} dco_mode_type;\n\nstruct dco_context\n{\n    struct tuntap *tt;\n    dco_mode_type ifmode;\n\n    OVPN_NOTIFY_EVENT notif_buf; /**< Buffer for incoming notifications. */\n    OVERLAPPED ov;               /**< Used by overlapped I/O for async IOCTL. */\n    int iostate;                 /**< State of overlapped I/O; see definitions in win32.h. */\n    struct rw_handle rwhandle;   /**< Used to hook async I/O to the OpenVPN event loop. */\n    int ov_ret;                  /**< Win32 error code for overlapped operation, 0 for success */\n\n    int dco_message_peer_id;\n    int dco_message_type;\n    int dco_del_peer_reason;\n    struct sockaddr_storage dco_float_peer_ss;\n\n    struct context *c;\n};\n\ntypedef struct dco_context dco_context_t;\n\nvoid dco_mp_start_vpn(HANDLE handle, struct link_socket *sock);\n\nvoid dco_p2p_new_peer(HANDLE handle, OVERLAPPED *ov, struct link_socket *sock,\n                      struct signal_info *sig_info);\n\nvoid dco_start_tun(struct tuntap *tt);\n\nbool dco_win_supports_multipeer(void);\n\nvoid dco_win_add_iroute_ipv4(dco_context_t *dco, in_addr_t dst, unsigned int netbits,\n                             unsigned int peer_id);\n\nvoid dco_win_add_iroute_ipv6(dco_context_t *dco, struct in6_addr dst, unsigned int netbits,\n                             unsigned int peer_id);\n\nvoid dco_win_del_iroute_ipv4(dco_context_t *dco, in_addr_t dst, unsigned int netbits);\n\nvoid dco_win_del_iroute_ipv6(dco_context_t *dco, struct in6_addr dst, unsigned int netbits);\n\n#else  /* if defined(ENABLE_DCO) && defined(_WIN32) */\n\nstatic inline void\ndco_start_tun(struct tuntap *tt)\n{\n    ASSERT(false);\n}\n\n#endif /* defined(_WIN32) */\n#endif /* ifndef DCO_H */\n"
  },
  {
    "path": "src/openvpn/dhcp.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"dhcp.h\"\n#include \"socket_util.h\"\n#include \"error.h\"\n\n#include \"memdbg.h\"\n\nstatic int\nget_dhcp_message_type(const struct dhcp *dhcp, const int optlen)\n{\n    const uint8_t *p = (uint8_t *)(dhcp + 1);\n    int i;\n\n    for (i = 0; i < optlen; ++i)\n    {\n        const uint8_t type = p[i];\n        const int room = optlen - i;\n        if (type == DHCP_END) /* didn't find what we were looking for */\n        {\n            return -1;\n        }\n        else if (type == DHCP_PAD) /* no-operation */\n        {\n        }\n        else if (type == DHCP_MSG_TYPE) /* what we are looking for */\n        {\n            if (room >= 3)\n            {\n                if (p[i + 1] == 1)   /* option length should be 1 */\n                {\n                    return p[i + 2]; /* return message type */\n                }\n            }\n            return -1;\n        }\n        else /* some other option */\n        {\n            if (room >= 2)\n            {\n                const int len = p[i + 1]; /* get option length */\n                i += (len + 1);           /* advance to next option */\n            }\n        }\n    }\n    return -1;\n}\n\nstatic in_addr_t\ndo_extract(struct dhcp *dhcp, int optlen)\n{\n    uint8_t *p = (uint8_t *)(dhcp + 1);\n    int i;\n    in_addr_t ret = 0;\n\n    for (i = 0; i < optlen;)\n    {\n        const uint8_t type = p[i];\n        const int room = optlen - i;\n        if (type == DHCP_END)\n        {\n            break;\n        }\n        else if (type == DHCP_PAD)\n        {\n            ++i;\n        }\n        else if (type == DHCP_ROUTER)\n        {\n            if (room >= 2)\n            {\n                const int len = p[i + 1]; /* get option length */\n                if (len <= (room - 2))\n                {\n                    /* get router IP address */\n                    if (!ret && len >= 4 && (len & 3) == 0)\n                    {\n                        memcpy(&ret, p + i + 2, 4);\n                        ret = ntohl(ret);\n                    }\n                    {\n                        /* delete the router option */\n                        uint8_t *dest = p + i;\n                        const int owlen = len + 2; /* len of data to overwrite */\n                        uint8_t *src = dest + owlen;\n                        uint8_t *end = p + optlen;\n                        const intptr_t movlen = end - src;\n                        if (movlen > 0)\n                        {\n                            memmove(dest, src, movlen);       /* overwrite router option */\n                        }\n                        memset(end - owlen, DHCP_PAD, owlen); /* pad tail */\n                    }\n                }\n                else\n                {\n                    break;\n                }\n            }\n            else\n            {\n                break;\n            }\n        }\n        else /* some other option */\n        {\n            if (room >= 2)\n            {\n                const int len = p[i + 1]; /* get option length */\n                i += (len + 2);           /* advance to next option */\n            }\n            else\n            {\n                break;\n            }\n        }\n    }\n    return ret;\n}\n\nin_addr_t\ndhcp_extract_router_msg(struct buffer *ipbuf)\n{\n    struct dhcp_full *df = (struct dhcp_full *)BPTR(ipbuf);\n    const int optlen =\n        BLEN(ipbuf)\n        - (int)(sizeof(struct openvpn_iphdr) + sizeof(struct openvpn_udphdr) + sizeof(struct dhcp));\n\n    if (optlen >= 0 && df->ip.protocol == OPENVPN_IPPROTO_UDP\n        && df->udp.source == htons(BOOTPS_PORT) && df->udp.dest == htons(BOOTPC_PORT)\n        && df->dhcp.op == BOOTREPLY)\n    {\n        const int message_type = get_dhcp_message_type(&df->dhcp, optlen);\n        if (message_type == DHCPACK || message_type == DHCPOFFER)\n        {\n            /* get the router IP address while padding out all DHCP router options */\n            const in_addr_t ret = do_extract(&df->dhcp, optlen);\n\n            /* recompute the UDP checksum */\n            df->udp.check = 0;\n            df->udp.check = htons(ip_checksum(\n                AF_INET, (uint8_t *)&df->udp,\n                sizeof(struct openvpn_udphdr) + sizeof(struct dhcp) + optlen,\n                (uint8_t *)&df->ip.saddr, (uint8_t *)&df->ip.daddr, OPENVPN_IPPROTO_UDP));\n\n            /* only return the extracted Router address if DHCPACK */\n            if (message_type == DHCPACK)\n            {\n                if (ret)\n                {\n                    struct gc_arena gc = gc_new();\n                    msg(D_ROUTE, \"Extracted DHCP router address: %s\", print_in_addr_t(ret, 0, &gc));\n                    gc_free(&gc);\n                }\n\n                return ret;\n            }\n        }\n    }\n    return 0;\n}\n\n#if defined(_WIN32) || defined(DHCP_UNIT_TEST)\n\n/*\n * Convert DHCP options from the command line / config file\n * into a raw DHCP-format options string.\n */\n\nstatic void\nwrite_dhcp_u8(struct buffer *buf, const uint8_t type, const uint8_t data, bool *error)\n{\n    if (!buf_safe(buf, 3))\n    {\n        *error = true;\n        msg(M_WARN, \"write_dhcp_u8: buffer overflow building DHCP options\");\n        return;\n    }\n    buf_write_u8(buf, type);\n    buf_write_u8(buf, 1);\n    buf_write_u8(buf, data);\n}\n\nstatic void\nwrite_dhcp_u32_array(struct buffer *buf, const uint8_t type, const uint32_t *data,\n                     const unsigned int len, bool *error)\n{\n    if (len > 0)\n    {\n        const size_t size = len * sizeof(uint32_t);\n\n        if (!buf_safe(buf, 2 + size))\n        {\n            *error = true;\n            msg(M_WARN, \"write_dhcp_u32_array: buffer overflow building DHCP options\");\n            return;\n        }\n        if (size < 1 || size > 255)\n        {\n            *error = true;\n            msg(M_WARN, \"write_dhcp_u32_array: size (%zu) must be > 0 and <= 255\", size);\n            return;\n        }\n        buf_write_u8(buf, type);\n        buf_write_u8(buf, (uint8_t)size);\n        for (unsigned int i = 0; i < len; ++i)\n        {\n            buf_write_u32(buf, data[i]);\n        }\n    }\n}\n\nstatic void\nwrite_dhcp_str(struct buffer *buf, const uint8_t type, const char *str, bool *error)\n{\n    const size_t len = strlen(str);\n    if (!buf_safe(buf, 2 + len))\n    {\n        *error = true;\n        msg(M_WARN, \"write_dhcp_str: buffer overflow building DHCP options\");\n        return;\n    }\n    if (len < 1 || len > 255)\n    {\n        *error = true;\n        msg(M_WARN, \"write_dhcp_str: string '%s' must be > 0 bytes and <= 255 bytes\", str);\n        return;\n    }\n    buf_write_u8(buf, type);\n    buf_write_u8(buf, (uint8_t)len);\n    buf_write(buf, str, len);\n}\n\n/*\n * RFC3397 states that multiple searchdomains are encoded as follows:\n *  - at start the length of the entire option is given\n *  - each subdomain is preceded by its length\n *  - each searchdomain is separated by a NUL character\n * e.g. if you want \"openvpn.net\" and \"duckduckgo.com\" then you end up with\n *  0x1D  0x7 openvpn 0x3 net 0x00 0x0A duckduckgo 0x3 com 0x00\n */\nstatic void\nwrite_dhcp_search_str(struct buffer *buf, const uint8_t type, const char *const *str_array,\n                      int array_len, bool *error)\n{\n    char tmp_buf[256];\n    size_t len = 0;\n    size_t label_length_pos;\n\n    for (int i = 0; i < array_len; i++)\n    {\n        const char *ptr = str_array[i];\n\n        if (strlen(ptr) + len + 1 > sizeof(tmp_buf))\n        {\n            *error = true;\n            msg(M_WARN, \"write_dhcp_search_str: temp buffer overflow building DHCP options\");\n            return;\n        }\n        /* Loop over all subdomains separated by a dot and replace the dot\n         * with the length of the subdomain */\n\n        /* label_length_pos points to the byte to be replaced by the length\n         * of the following domain label */\n        label_length_pos = len++;\n\n        while (true)\n        {\n            if (*ptr == '.' || *ptr == '\\0')\n            {\n                /* cast is protected by sizeof(tmp_buf) */\n                tmp_buf[label_length_pos] = (char)(len - label_length_pos - 1);\n                label_length_pos = len;\n                if (*ptr == '\\0')\n                {\n                    break;\n                }\n            }\n            tmp_buf[len++] = *ptr++;\n        }\n        /* And close off with an extra NUL char */\n        tmp_buf[len++] = 0;\n    }\n\n    if (!buf_safe(buf, 2 + len))\n    {\n        *error = true;\n        msg(M_WARN, \"write_search_dhcp_str: buffer overflow building DHCP options\");\n        return;\n    }\n    if (len > 255)\n    {\n        *error = true;\n        msg(M_WARN, \"write_dhcp_search_str: search domain string must be <= 255 bytes\");\n        return;\n    }\n\n    buf_write_u8(buf, type);\n    buf_write_u8(buf, (uint8_t)len);\n    buf_write(buf, tmp_buf, len);\n}\n\nbool\nbuild_dhcp_options_string(struct buffer *buf, const struct tuntap_options *o)\n{\n    bool error = false;\n    if (o->domain)\n    {\n        write_dhcp_str(buf, DHCP_DOMAIN_NAME, o->domain, &error);\n    }\n\n    if (o->netbios_scope)\n    {\n        write_dhcp_str(buf, DHCP_NETBIOS_SCOPE, o->netbios_scope, &error);\n    }\n\n    if (o->netbios_node_type)\n    {\n        write_dhcp_u8(buf, DHCP_NETBIOS_NODE_TYPE, o->netbios_node_type, &error);\n    }\n\n    write_dhcp_u32_array(buf, DHCP_DOMAIN_SERVER, (uint32_t *)o->dns, o->dns_len, &error);\n    write_dhcp_u32_array(buf, DHCP_NETBIOS_DOMAIN_SERVER, (uint32_t *)o->wins, o->wins_len, &error);\n    write_dhcp_u32_array(buf, DHCP_NTP_SERVER, (uint32_t *)o->ntp, o->ntp_len, &error);\n    write_dhcp_u32_array(buf, DHCP_NETBIOS_DIST_SERVER, (uint32_t *)o->nbdd, o->nbdd_len, &error);\n\n    if (o->domain_search_list_len > 0)\n    {\n        write_dhcp_search_str(buf, DHCP_DOMAIN_SEARCH, o->domain_search_list, o->domain_search_list_len, &error);\n    }\n\n    /* the MS DHCP server option 'Disable Netbios-over-TCP/IP\n     * is implemented as vendor option 001, value 002.\n     * A value of 001 means 'leave NBT alone' which is the default */\n    if (o->disable_nbt)\n    {\n        if (!buf_safe(buf, 8))\n        {\n            msg(M_WARN, \"build_dhcp_options_string: buffer overflow building DHCP options\");\n            return false;\n        }\n        buf_write_u8(buf, DHCP_VENDOR);\n        buf_write_u8(buf, 6); /* total length field */\n        buf_write_u8(buf, 0x001);\n        buf_write_u8(buf, 4); /* length of the vendor specified field */\n        buf_write_u32(buf, 0x002);\n    }\n    return !error;\n}\n\n#endif /* defined(_WIN32) */\n"
  },
  {
    "path": "src/openvpn/dhcp.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef DHCP_H\n#define DHCP_H\n\n#include \"common.h\"\n#include \"buffer.h\"\n#include \"proto.h\"\n\n#pragma pack(1)\n\n/* DHCP Option types */\n#define DHCP_PAD                   0\n#define DHCP_ROUTER                3\n#define DHCP_DOMAIN_SERVER         6\n#define DHCP_DOMAIN_NAME           15\n#define DHCP_NTP_SERVER            42\n#define DHCP_VENDOR                43\n#define DHCP_NETBIOS_DOMAIN_SERVER 44\n#define DHCP_NETBIOS_DIST_SERVER   45\n#define DHCP_NETBIOS_NODE_TYPE     46\n#define DHCP_NETBIOS_SCOPE         47\n#define DHCP_MSG_TYPE              53\n#define DHCP_DOMAIN_SEARCH         119\n#define DHCP_END                   255\n\n/* DHCP Messages types */\n#define DHCPDISCOVER 1\n#define DHCPOFFER    2\n#define DHCPREQUEST  3\n#define DHCPDECLINE  4\n#define DHCPACK      5\n#define DHCPNAK      6\n#define DHCPRELEASE  7\n#define DHCPINFORM   8\n\n/* DHCP UDP port numbers */\n#define BOOTPS_PORT 67\n#define BOOTPC_PORT 68\n\nstruct dhcp\n{\n#define BOOTREQUEST 1\n#define BOOTREPLY   2\n    uint8_t op;         /* message op */\n\n    uint8_t htype;      /* hardware address type (e.g. '1' = 10Mb Ethernet) */\n    uint8_t hlen;       /* hardware address length (e.g. '6' for 10Mb Ethernet) */\n    uint8_t hops;       /* client sets to 0, may be used by relay agents */\n    uint32_t xid;       /* transaction ID, chosen by client */\n    uint16_t secs;      /* seconds since request process began, set by client */\n    uint16_t flags;\n    uint32_t ciaddr;    /* client IP address, client sets if known */\n    uint32_t yiaddr;    /* 'your' IP address -- server's response to client */\n    uint32_t siaddr;    /* server IP address */\n    uint32_t giaddr;    /* relay agent IP address */\n    uint8_t chaddr[16]; /* client hardware address */\n    uint8_t sname[64];  /* optional server host name */\n    uint8_t file[128];  /* boot file name */\n    uint32_t magic;     /* must be 0x63825363 (network order) */\n};\n\nstruct dhcp_full\n{\n    struct openvpn_iphdr ip;\n    struct openvpn_udphdr udp;\n    struct dhcp dhcp;\n#define DHCP_OPTIONS_BUFFER_SIZE 256\n    uint8_t options[DHCP_OPTIONS_BUFFER_SIZE];\n};\n\n#pragma pack()\n\nin_addr_t dhcp_extract_router_msg(struct buffer *ipbuf);\n\n#if defined(_WIN32) || defined(DHCP_UNIT_TEST)\n#include \"tun.h\"\n\nbool build_dhcp_options_string(struct buffer *buf, const struct tuntap_options *o);\n#endif\n\n#endif /* ifndef DHCP_H */\n"
  },
  {
    "path": "src/openvpn/dns.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2022-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"dns.h\"\n#include \"socket_util.h\"\n#include \"options.h\"\n#include \"run_command.h\"\n#include \"domain_helper.h\"\n\n#ifdef _WIN32\n#include \"win32.h\"\n#include \"openvpn-msg.h\"\n#endif\n\n/**\n * Parses a string as port and stores it\n *\n * @param   port        Pointer to in_port_t where the port value is stored\n * @param   port_str    Port number as string\n * @return              True if parsing was successful\n */\nstatic bool\ndns_server_port_parse(in_port_t *port, const char *port_str)\n{\n    char *endptr;\n    errno = 0;\n    unsigned long tmp = strtoul(port_str, &endptr, 10);\n    if (errno || *endptr != '\\0' || tmp == 0 || tmp > UINT16_MAX)\n    {\n        return false;\n    }\n    *port = (in_port_t)tmp;\n    return true;\n}\n\nbool\ndns_server_addr_parse(struct dns_server *server, const char *addr)\n{\n    if (!addr)\n    {\n        return false;\n    }\n\n    char addrcopy[INET6_ADDRSTRLEN] = { 0 };\n    size_t copylen = 0;\n    in_port_t port = 0;\n    sa_family_t af;\n\n    const char *first_colon = strchr(addr, ':');\n    const char *last_colon = strrchr(addr, ':');\n\n    if (!first_colon || first_colon == last_colon)\n    {\n        /* IPv4 address with optional port, e.g. 1.2.3.4 or 1.2.3.4:853 */\n        if (last_colon)\n        {\n            if (last_colon == addr || !dns_server_port_parse(&port, last_colon + 1))\n            {\n                return false;\n            }\n            copylen = first_colon - addr;\n        }\n        af = AF_INET;\n    }\n    else\n    {\n        /* IPv6 address with optional port, e.g. ab::cd or [ab::cd]:853 */\n        if (addr[0] == '[')\n        {\n            addr += 1;\n            const char *bracket = last_colon - 1;\n            if (*bracket != ']' || bracket == addr || !dns_server_port_parse(&port, last_colon + 1))\n            {\n                return false;\n            }\n            copylen = bracket - addr;\n        }\n        af = AF_INET6;\n    }\n\n    /* Copy the address part into a temporary buffer and use that */\n    if (copylen)\n    {\n        if (copylen >= sizeof(addrcopy))\n        {\n            return false;\n        }\n        strncpy(addrcopy, addr, copylen);\n        addr = addrcopy;\n    }\n\n    struct addrinfo *ai = NULL;\n    if (openvpn_getaddrinfo(0, addr, NULL, 0, NULL, af, &ai) != 0)\n    {\n        return false;\n    }\n\n    if (server->addr_count >= SIZE(server->addr))\n    {\n        return false;\n    }\n\n    if (ai->ai_family == AF_INET)\n    {\n        struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr;\n        server->addr[server->addr_count].in.a4.s_addr = sin->sin_addr.s_addr;\n    }\n    else\n    {\n        struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ai->ai_addr;\n        server->addr[server->addr_count].in.a6 = sin6->sin6_addr;\n    }\n\n    server->addr[server->addr_count].family = af;\n    server->addr[server->addr_count].port = port;\n    server->addr_count += 1;\n\n    freeaddrinfo(ai);\n    return true;\n}\n\nbool\ndns_domain_list_append(struct dns_domain **entry, char **domains, struct gc_arena *gc)\n{\n    /* Fast forward to the end of the list */\n    while (*entry)\n    {\n        entry = &((*entry)->next);\n    }\n\n    /* Append all domains to the end of the list */\n    while (*domains)\n    {\n        char *domain = *domains++;\n        if (!validate_domain(domain))\n        {\n            return false;\n        }\n\n        ALLOC_OBJ_CLEAR_GC(*entry, struct dns_domain, gc);\n        struct dns_domain *new = *entry;\n        new->name = domain;\n        entry = &new->next;\n    }\n\n    return true;\n}\n\nbool\ndns_server_priority_parse(long *priority, const char *str, bool pulled)\n{\n    char *endptr;\n    const long min = pulled ? 0 : INT8_MIN;\n    const long max = INT8_MAX;\n    long prio = strtol(str, &endptr, 10);\n    if (*endptr != '\\0' || prio < min || prio > max)\n    {\n        return false;\n    }\n    *priority = prio;\n    return true;\n}\n\nstruct dns_server *\ndns_server_get(struct dns_server **entry, long priority, struct gc_arena *gc)\n{\n    struct dns_server *obj = *entry;\n    while (true)\n    {\n        if (!obj || obj->priority > priority)\n        {\n            ALLOC_OBJ_CLEAR_GC(*entry, struct dns_server, gc);\n            (*entry)->next = obj;\n            (*entry)->priority = priority;\n            return *entry;\n        }\n        else if (obj->priority == priority)\n        {\n            return obj;\n        }\n        entry = &obj->next;\n        obj = *entry;\n    }\n}\n\nbool\ndns_options_verify(msglvl_t msglevel, const struct dns_options *o)\n{\n    const struct dns_server *server = o->servers ? o->servers : o->servers_prepull;\n    while (server)\n    {\n        if (server->addr_count == 0)\n        {\n            msg(msglevel, \"ERROR: dns server %ld does not have an address assigned\",\n                server->priority);\n            return false;\n        }\n        server = server->next;\n    }\n    return true;\n}\n\nstatic struct dns_domain *\nclone_dns_domains(const struct dns_domain *domain, struct gc_arena *gc)\n{\n    struct dns_domain *new_list = NULL;\n    struct dns_domain **new_entry = &new_list;\n\n    while (domain)\n    {\n        ALLOC_OBJ_CLEAR_GC(*new_entry, struct dns_domain, gc);\n        struct dns_domain *new_domain = *new_entry;\n        *new_domain = *domain;\n        new_entry = &new_domain->next;\n        domain = domain->next;\n    }\n\n    return new_list;\n}\n\nstatic struct dns_server *\nclone_dns_servers(const struct dns_server *server, struct gc_arena *gc)\n{\n    struct dns_server *new_list = NULL;\n    struct dns_server **new_entry = &new_list;\n\n    while (server)\n    {\n        ALLOC_OBJ_CLEAR_GC(*new_entry, struct dns_server, gc);\n        struct dns_server *new_server = *new_entry;\n        *new_server = *server;\n        new_server->domains = clone_dns_domains(server->domains, gc);\n        new_entry = &new_server->next;\n        server = server->next;\n    }\n\n    return new_list;\n}\n\nstruct dns_options\nclone_dns_options(const struct dns_options *o, struct gc_arena *gc)\n{\n    struct dns_options clone;\n\n    memset(&clone, 0, sizeof(clone));\n    clone.search_domains = clone_dns_domains(o->search_domains, gc);\n    clone.servers = clone_dns_servers(o->servers, gc);\n    clone.servers_prepull = clone_dns_servers(o->servers_prepull, gc);\n    clone.updown = o->updown;\n    clone.updown_flags = o->updown_flags;\n    clone.from_dhcp = o->from_dhcp;\n\n    return clone;\n}\n\nvoid\ndns_options_preprocess_pull(struct dns_options *o)\n{\n    o->servers_prepull = o->servers;\n    o->servers = NULL;\n}\n\nvoid\ndns_options_postprocess_pull(struct dns_options *o)\n{\n    struct dns_server **entry = &o->servers;\n    struct dns_server *server = *entry;\n    struct dns_server *server_pp = o->servers_prepull;\n\n    while (server && server_pp)\n    {\n        if (server->priority > server_pp->priority)\n        {\n            /* Merge static server in front of pulled one */\n            struct dns_server *next_pp = server_pp->next;\n            server_pp->next = server;\n            *entry = server_pp;\n            server = *entry;\n            server_pp = next_pp;\n        }\n        else if (server->priority == server_pp->priority)\n        {\n            /* Pulled server overrides static one */\n            server_pp = server_pp->next;\n        }\n        entry = &server->next;\n        server = *entry;\n    }\n\n    /* Append remaining local servers */\n    if (server_pp)\n    {\n        *entry = server_pp;\n    }\n\n    o->servers_prepull = NULL;\n}\n\nstatic const char *\ndnssec_value(const enum dns_security dnssec)\n{\n    switch (dnssec)\n    {\n        case DNS_SECURITY_YES:\n            return \"yes\";\n\n        case DNS_SECURITY_OPTIONAL:\n            return \"optional\";\n\n        case DNS_SECURITY_NO:\n            return \"no\";\n\n        default:\n            return \"unset\";\n    }\n}\n\nstatic const char *\ntransport_value(const enum dns_server_transport transport)\n{\n    switch (transport)\n    {\n        case DNS_TRANSPORT_HTTPS:\n            return \"DoH\";\n\n        case DNS_TRANSPORT_TLS:\n            return \"DoT\";\n\n        case DNS_TRANSPORT_PLAIN:\n            return \"plain\";\n\n        default:\n            return \"unset\";\n    }\n}\n\n#ifdef _WIN32\n\nstatic void\nmake_domain_list(const char *what, const struct dns_domain *src, bool nrpt_domains, char *dst,\n                 size_t dst_size)\n{\n    /* NRPT domains need two \\0 at the end for REG_MULTI_SZ\n     * and a leading '.' added in front of the domain name */\n    size_t term_size = nrpt_domains ? 2 : 1;\n    size_t leading_dot = nrpt_domains ? 1 : 0;\n    size_t offset = 0;\n\n    memset(dst, 0, dst_size);\n\n    while (src)\n    {\n        size_t len = strlen(src->name);\n        if (offset + leading_dot + len + term_size > dst_size)\n        {\n            msg(M_WARN, \"WARNING: %s truncated\", what);\n            if (offset)\n            {\n                /* Remove trailing comma */\n                *(dst + offset - 1) = '\\0';\n            }\n            break;\n        }\n\n        if (leading_dot)\n        {\n            *(dst + offset++) = '.';\n        }\n        strncpy(dst + offset, src->name, len);\n        offset += len;\n\n        src = src->next;\n        if (src)\n        {\n            *(dst + offset++) = ',';\n        }\n    }\n}\n\nstatic void\nrun_up_down_service(bool add, const struct options *o, const struct tuntap *tt)\n{\n    const struct dns_server *server = o->dns_options.servers;\n    const struct dns_domain *search_domains = o->dns_options.search_domains;\n\n    while (true)\n    {\n        if (!server)\n        {\n            if (add)\n            {\n                msg(M_WARN, \"WARNING: setting DNS failed, no compatible server profile\");\n            }\n            return;\n        }\n\n        bool only_standard_server_ports = true;\n        for (size_t i = 0; i < NRPT_ADDR_NUM; ++i)\n        {\n            if (server->addr[i].port && server->addr[i].port != 53)\n            {\n                only_standard_server_ports = false;\n                break;\n            }\n        }\n        if ((server->transport == DNS_TRANSPORT_UNSET || server->transport == DNS_TRANSPORT_PLAIN)\n            && only_standard_server_ports)\n        {\n            break; /* found compatible server */\n        }\n\n        server = server->next;\n    }\n\n    ack_message_t ack;\n    nrpt_dns_cfg_message_t nrpt = {\n        .header = { (add ? msg_add_nrpt_cfg : msg_del_nrpt_cfg), sizeof(nrpt_dns_cfg_message_t),\n                    0 },\n        .iface = { .index = tt->adapter_index, .name = \"\" },\n        .flags = server->dnssec == DNS_SECURITY_NO ? 0 : nrpt_dnssec,\n    };\n    strncpynt(nrpt.iface.name, tt->actual_name, sizeof(nrpt.iface.name));\n\n    for (size_t i = 0; i < NRPT_ADDR_NUM; ++i)\n    {\n        if (server->addr[i].family == AF_UNSPEC)\n        {\n            /* No more addresses */\n            break;\n        }\n\n        if (inet_ntop(server->addr[i].family, &server->addr[i].in, nrpt.addresses[i],\n                      NRPT_ADDR_SIZE)\n            == NULL)\n        {\n            msg(M_WARN, \"WARNING: could not convert dns server address\");\n        }\n    }\n\n    make_domain_list(\"dns server resolve domains\", server->domains, true, nrpt.resolve_domains,\n                     sizeof(nrpt.resolve_domains));\n\n    make_domain_list(\"dns search domains\", search_domains, false, nrpt.search_domains,\n                     sizeof(nrpt.search_domains));\n\n    msg(D_LOW, \"%s NRPT DNS%s%s on '%s' (if_index = %lu) using service\",\n        (add ? \"Setting\" : \"Deleting\"), nrpt.resolve_domains[0] != 0 ? \", resolve domains\" : \"\",\n        nrpt.search_domains[0] != 0 ? \", search domains\" : \"\",\n        nrpt.iface.name, nrpt.iface.index);\n\n    send_msg_iservice(o->msg_channel, &nrpt, sizeof(nrpt), &ack, \"DNS\");\n}\n\n#else /* ifdef _WIN32 */\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\nstatic void\nsetenv_dns_option(struct env_set *es, const char *format, int i, int j, const char *value)\n{\n    char name[64];\n    bool name_ok = false;\n\n    if (j < 0)\n    {\n        name_ok = checked_snprintf(name, sizeof(name), format, i);\n    }\n    else\n    {\n        name_ok = checked_snprintf(name, sizeof(name), format, i, j);\n    }\n\n    if (!name_ok)\n    {\n        msg(M_WARN, \"WARNING: dns option setenv name buffer overflow\");\n    }\n\n    setenv_str(es, name, value);\n}\n\nstatic void\nsetenv_dns_options(const struct dns_options *o, struct env_set *es)\n{\n    struct gc_arena gc = gc_new();\n    const struct dns_server *s;\n    const struct dns_domain *d;\n    int i, j;\n\n    for (i = 1, d = o->search_domains; d != NULL; i++, d = d->next)\n    {\n        setenv_dns_option(es, \"dns_search_domain_%d\", i, -1, d->name);\n    }\n\n    for (i = 1, s = o->servers; s != NULL; i++, s = s->next)\n    {\n        for (j = 0; j < s->addr_count; ++j)\n        {\n            if (s->addr[j].family == AF_INET)\n            {\n                setenv_dns_option(es, \"dns_server_%d_address_%d\", i, j + 1,\n                                  print_in_addr_t(s->addr[j].in.a4.s_addr, IA_NET_ORDER, &gc));\n            }\n            else\n            {\n                setenv_dns_option(es, \"dns_server_%d_address_%d\", i, j + 1,\n                                  print_in6_addr(s->addr[j].in.a6, 0, &gc));\n            }\n            if (s->addr[j].port)\n            {\n                setenv_dns_option(es, \"dns_server_%d_port_%d\", i, j + 1,\n                                  print_in_port_t(s->addr[j].port, &gc));\n            }\n        }\n\n        if (s->domains)\n        {\n            for (j = 1, d = s->domains; d != NULL; j++, d = d->next)\n            {\n                setenv_dns_option(es, \"dns_server_%d_resolve_domain_%d\", i, j, d->name);\n            }\n        }\n\n        if (s->dnssec)\n        {\n            setenv_dns_option(es, \"dns_server_%d_dnssec\", i, -1, dnssec_value(s->dnssec));\n        }\n\n        if (s->transport)\n        {\n            setenv_dns_option(es, \"dns_server_%d_transport\", i, -1, transport_value(s->transport));\n        }\n        if (s->sni)\n        {\n            setenv_dns_option(es, \"dns_server_%d_sni\", i, -1, s->sni);\n        }\n    }\n\n    gc_free(&gc);\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nstatic void\nupdown_env_set(bool up, const struct dns_options *o, const struct tuntap *tt, struct env_set *es)\n{\n    setenv_str(es, \"dev\", tt->actual_name);\n    setenv_str(es, \"script_type\", up ? \"dns-up\" : \"dns-down\");\n    setenv_dns_options(o, es);\n}\n\nstatic int\ndo_run_up_down_command(bool up, const char *vars_file, const struct dns_options *o,\n                       const struct tuntap *tt)\n{\n    struct gc_arena gc = gc_new();\n    struct argv argv = argv_new();\n    struct env_set *es = env_set_create(&gc);\n\n    if (vars_file)\n    {\n        setenv_str(es, \"dns_vars_file\", vars_file);\n    }\n    else\n    {\n        updown_env_set(up, o, tt, es);\n    }\n\n    argv_printf(&argv, \"%s\", o->updown);\n    argv_msg(M_INFO, &argv);\n    int res;\n    if (dns_updown_user_set(o))\n    {\n        res = openvpn_run_script(&argv, es, S_EXITCODE, \"dns updown\");\n    }\n    else\n    {\n        res = openvpn_execve_check(&argv, es, S_EXITCODE, \"WARNING: Failed running dns updown\");\n    }\n    argv_free(&argv);\n    gc_free(&gc);\n    return res;\n}\n\nstatic bool\nrun_updown_runner(bool up, struct options *o, const struct tuntap *tt,\n                  struct dns_updown_runner_info *updown_runner)\n{\n    int dns_pipe_fd[2];\n    int ack_pipe_fd[2];\n    if (pipe(dns_pipe_fd) != 0 || pipe(ack_pipe_fd) != 0)\n    {\n        msg(M_WARN | M_ERRNO, \"run_dns_up_down: unable to create pipes\");\n        return false;\n    }\n    updown_runner->pid = fork();\n    if (updown_runner->pid == -1)\n    {\n        msg(M_WARN | M_ERRNO, \"run_dns_up_down: unable to fork\");\n        close(dns_pipe_fd[0]);\n        close(dns_pipe_fd[1]);\n        close(ack_pipe_fd[0]);\n        close(ack_pipe_fd[1]);\n        return false;\n    }\n    else if (updown_runner->pid > 0)\n    {\n        /* Parent process */\n        close(dns_pipe_fd[0]);\n        close(ack_pipe_fd[1]);\n        updown_runner->fds[0] = ack_pipe_fd[0];\n        updown_runner->fds[1] = dns_pipe_fd[1];\n    }\n    else\n    {\n        /* Script runner process, close unused FDs */\n        for (int fd = 3; fd < 100; ++fd)\n        {\n            if (fd != dns_pipe_fd[0] && fd != ack_pipe_fd[1])\n            {\n                close(fd);\n            }\n        }\n\n        /* Ignore signals */\n        signal(SIGINT, SIG_IGN);\n        signal(SIGHUP, SIG_IGN);\n        signal(SIGTERM, SIG_IGN);\n        signal(SIGUSR1, SIG_IGN);\n        signal(SIGUSR2, SIG_IGN);\n        signal(SIGPIPE, SIG_IGN);\n\n        while (1)\n        {\n            char path[PATH_MAX];\n\n            /* Block here until parent sends a path */\n            ssize_t rlen = read(dns_pipe_fd[0], &path, sizeof(path));\n            if (rlen < 1)\n            {\n                if (rlen == -1 && errno == EINTR)\n                {\n                    continue;\n                }\n                close(dns_pipe_fd[0]);\n                close(ack_pipe_fd[1]);\n                exit(0);\n            }\n\n            path[sizeof(path) - 1] = '\\0';\n            int res = do_run_up_down_command(up, path, &o->dns_options, tt);\n            platform_unlink(path);\n\n            /* Unblock parent process */\n            while (1)\n            {\n                ssize_t wlen = write(ack_pipe_fd[1], &res, sizeof(res));\n                if ((wlen == -1 && errno != EINTR) || wlen < (ssize_t)sizeof(res))\n                {\n                    /* Not much we can do about errors but exit */\n                    close(dns_pipe_fd[0]);\n                    close(ack_pipe_fd[1]);\n                    exit(0);\n                }\n                else if (wlen == sizeof(res))\n                {\n                    break;\n                }\n            }\n\n            up = !up; /* do the opposite next time */\n        }\n    }\n\n    return true;\n}\n\nstatic void\nrun_up_down_command(bool up, struct options *o, const struct tuntap *tt,\n                    struct dns_updown_runner_info *updown_runner)\n{\n    struct dns_options *dns = &o->dns_options;\n    if (!dns->updown || (o->up_script && !dns_updown_user_set(dns) && !dns_updown_forced(dns)))\n    {\n        return;\n    }\n\n    int status = -1;\n\n    if (!updown_runner->required)\n    {\n        /* Run dns updown directly */\n        status = do_run_up_down_command(up, NULL, dns, tt);\n    }\n    else\n    {\n        if (updown_runner->pid < 1)\n        {\n            /* Need to set up privilege preserving child first */\n            if (!run_updown_runner(up, o, tt, updown_runner))\n            {\n                return;\n            }\n        }\n\n        struct gc_arena gc = gc_new();\n        const char *dvf = platform_create_temp_file(o->tmp_dir, \"dvf\", &gc);\n        if (!dvf)\n        {\n            msg(M_ERR, \"could not create dns vars file\");\n            goto out_free;\n        }\n\n        struct env_set *es = env_set_create(&gc);\n        updown_env_set(up, &o->dns_options, tt, es);\n        env_set_write_file(dvf, es);\n\n        int wfd = updown_runner->fds[1];\n        ssize_t dvf_size = strlen(dvf) + 1;\n        while (1)\n        {\n            ssize_t len = write(wfd, dvf, dvf_size);\n            if (len < dvf_size)\n            {\n                if (len == -1 && errno == EINTR)\n                {\n                    continue;\n                }\n                msg(M_WARN | M_ERRNO, \"could not send dns vars filename\");\n            }\n            break;\n        }\n\n        int rfd = updown_runner->fds[0];\n        while (1)\n        {\n            ssize_t len = read(rfd, &status, sizeof(status));\n            if (len < (ssize_t)sizeof(status))\n            {\n                if (len == -1 && errno == EINTR)\n                {\n                    continue;\n                }\n                msg(M_WARN | M_ERRNO, \"could not receive dns updown status\");\n            }\n            break;\n        }\n\nout_free:\n        gc_free(&gc);\n    }\n\n    msg(M_INFO, \"dns %s command exited with status %d\", up ? \"up\" : \"down\", status);\n}\n\n#endif /* _WIN32 */\n\nvoid\nshow_dns_options(const struct dns_options *o)\n{\n    struct gc_arena gc = gc_new();\n\n    int i = 1;\n    struct dns_server *server = o->servers_prepull ? o->servers_prepull : o->servers;\n    while (server)\n    {\n        msg(D_SHOW_PARMS, \"  DNS server #%d:\", i++);\n\n        for (size_t j = 0; j < server->addr_count; ++j)\n        {\n            const char *addr;\n            const char *fmt_port;\n            if (server->addr[j].family == AF_INET)\n            {\n                addr = print_in_addr_t(server->addr[j].in.a4.s_addr, IA_NET_ORDER, &gc);\n                fmt_port = \"    address = %s:%s\";\n            }\n            else\n            {\n                addr = print_in6_addr(server->addr[j].in.a6, 0, &gc);\n                fmt_port = \"    address = [%s]:%s\";\n            }\n\n            if (server->addr[j].port)\n            {\n                const char *port = print_in_port_t(server->addr[j].port, &gc);\n                msg(D_SHOW_PARMS, fmt_port, addr, port);\n            }\n            else\n            {\n                msg(D_SHOW_PARMS, \"    address = %s\", addr);\n            }\n        }\n\n        if (server->dnssec)\n        {\n            msg(D_SHOW_PARMS, \"    dnssec = %s\", dnssec_value(server->dnssec));\n        }\n\n        if (server->transport)\n        {\n            msg(D_SHOW_PARMS, \"    transport = %s\", transport_value(server->transport));\n        }\n        if (server->sni)\n        {\n            msg(D_SHOW_PARMS, \"    sni = %s\", server->sni);\n        }\n\n        struct dns_domain *domain = server->domains;\n        if (domain)\n        {\n            msg(D_SHOW_PARMS, \"    resolve domains:\");\n            while (domain)\n            {\n                msg(D_SHOW_PARMS, \"      %s\", domain->name);\n                domain = domain->next;\n            }\n        }\n\n        server = server->next;\n    }\n\n    struct dns_domain *search_domain = o->search_domains;\n    if (search_domain)\n    {\n        msg(D_SHOW_PARMS, \"  DNS search domains:\");\n        while (search_domain)\n        {\n            msg(D_SHOW_PARMS, \"    %s\", search_domain->name);\n            search_domain = search_domain->next;\n        }\n    }\n\n    gc_free(&gc);\n}\n\nvoid\nrun_dns_up_down(bool up, struct options *o, const struct tuntap *tt,\n                struct dns_updown_runner_info *duri)\n{\n    if (!o->dns_options.servers)\n    {\n        return;\n    }\n#ifdef _WIN32\n    /* Don't use iservice in DHCP mode */\n    struct tuntap_options *tto = &o->tuntap_options;\n    if (tto->ip_win32_type == IPW32_SET_DHCP_MASQ || tto->ip_win32_type == IPW32_SET_ADAPTIVE)\n    {\n        return;\n    }\n#endif\n\n    /* Warn about adding servers of unsupported AF */\n    const struct dns_server *s = o->dns_options.servers;\n    while (up && s)\n    {\n        size_t bad_count = 0;\n        for (size_t i = 0; i < s->addr_count; ++i)\n        {\n            if ((s->addr[i].family == AF_INET6 && !tt->did_ifconfig_ipv6_setup)\n                || (s->addr[i].family == AF_INET && !tt->did_ifconfig_setup))\n            {\n                ++bad_count;\n            }\n        }\n        if (bad_count == s->addr_count)\n        {\n            msg(M_WARN,\n                \"DNS server %ld only has address(es) from a family \"\n                \"the tunnel is not configured for - it will not be reachable\",\n                s->priority);\n        }\n        else if (bad_count)\n        {\n            msg(M_WARN,\n                \"DNS server %ld has address(es) from a family \"\n                \"the tunnel is not configured for\",\n                s->priority);\n        }\n        s = s->next;\n    }\n\n#ifdef _WIN32\n    run_up_down_service(up, o, tt);\n#else\n    run_up_down_command(up, o, tt, duri);\n#endif /* ifdef _WIN32 */\n}\n"
  },
  {
    "path": "src/openvpn/dns.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2022-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef DNS_H\n#define DNS_H\n\n#include \"buffer.h\"\n#include \"env_set.h\"\n#include \"tun.h\"\n\nenum dns_security\n{\n    DNS_SECURITY_UNSET,\n    DNS_SECURITY_NO,\n    DNS_SECURITY_YES,\n    DNS_SECURITY_OPTIONAL\n};\n\nenum dns_server_transport\n{\n    DNS_TRANSPORT_UNSET,\n    DNS_TRANSPORT_PLAIN,\n    DNS_TRANSPORT_HTTPS,\n    DNS_TRANSPORT_TLS\n};\n\nenum dns_updown_flags\n{\n    DNS_UPDOWN_NO_FLAGS,\n    DNS_UPDOWN_USER_SET,\n    DNS_UPDOWN_FORCED\n};\n\nstruct dns_domain\n{\n    struct dns_domain *next;\n    const char *name;\n};\n\nstruct dns_server_addr\n{\n    union\n    {\n        struct in_addr a4;\n        struct in6_addr a6;\n    } in;\n    sa_family_t family;\n    in_port_t port;\n};\n\nstruct dns_server\n{\n    struct dns_server *next;\n    long priority;\n    size_t addr_count;\n    struct dns_server_addr addr[8];\n    struct dns_domain *domains;\n    enum dns_security dnssec;\n    enum dns_server_transport transport;\n    const char *sni;\n};\n\nstruct dns_updown_runner_info\n{\n    bool required;\n    int fds[2];\n#if !defined(_WIN32)\n    pid_t pid;\n#endif\n};\n\n#ifndef N_DHCP_ADDR\n#define N_DHCP_ADDR 4\n#endif\n\n#ifndef N_SEARCH_LIST_LEN\n#define N_SEARCH_LIST_LEN 10\n#endif\n\nstruct dhcp_options\n{\n    in_addr_t dns[N_DHCP_ADDR];\n    unsigned int dns_len;\n\n    struct in6_addr dns6[N_DHCP_ADDR];\n    unsigned int dns6_len;\n\n    const char *domain;\n    const char *domain_search_list[N_SEARCH_LIST_LEN];\n    unsigned int domain_search_list_len;\n};\n\nstruct dns_options\n{\n    struct dhcp_options from_dhcp;\n    struct dns_domain *search_domains;\n    struct dns_server *servers_prepull;\n    struct dns_server *servers;\n    struct gc_arena gc;\n    const char *updown;\n    enum dns_updown_flags updown_flags;\n};\n\n/**\n * Parses a string DNS server priority and validates it.\n *\n * @param   priority    Pointer to where the priority should be stored\n * @param   str         Priority string to parse\n * @param   pulled      Whether this was pulled from a server\n * @return              True if priority in string is valid\n */\nbool dns_server_priority_parse(long *priority, const char *str, bool pulled);\n\n/**\n * Find or create DNS server with priority in a linked list.\n * The list is ordered by priority.\n *\n * @param   entry       Address of the first list entry pointer\n * @param   priority    Priority of the DNS server to find / create\n * @param   gc          The gc new list items should be allocated in\n */\nstruct dns_server *dns_server_get(struct dns_server **entry, long priority, struct gc_arena *gc);\n\n/**\n * Appends safe DNS domain parameters to a linked list.\n *\n * @param   entry       Address of the first list entry pointer\n * @param   domains     Address of the first domain parameter\n * @param   gc          The gc the new list items should be allocated in\n * @return              True if domains were appended and don't contain invalid characters\n */\nbool dns_domain_list_append(struct dns_domain **entry, char **domains, struct gc_arena *gc);\n\n/**\n * Parses a string IPv4 or IPv6 address and optional colon separated port,\n * into a in_addr or in6_addr respectively plus a in_port_t port.\n *\n * @param   server      Pointer to DNS server the address is parsed for\n * @param   addr        Address as string\n * @return              True if parsing was successful\n */\nbool dns_server_addr_parse(struct dns_server *server, const char *addr);\n\n/**\n * Checks validity of DNS options\n *\n * @param   msglevel    The message level to log errors with\n * @param   o           Pointer to the DNS options to validate\n * @return              True if no error was found\n */\nbool dns_options_verify(msglvl_t msglevel, const struct dns_options *o);\n\n/**\n * Makes a deep copy of the passed DNS options.\n *\n * @param   o           Pointer to the DNS options to clone\n * @param   gc          Pointer to the gc_arena to use for the clone\n * @return              The dns_options clone\n */\nstruct dns_options clone_dns_options(const struct dns_options *o, struct gc_arena *gc);\n\n/**\n * Saves and resets the server options, so that pulled ones don't mix in.\n *\n * @param   o           Pointer to the DNS options to modify\n */\nvoid dns_options_preprocess_pull(struct dns_options *o);\n\n/**\n * Merges pulled DNS servers with static ones into an ordered list.\n *\n * @param   o           Pointer to the DNS options to modify\n */\nvoid dns_options_postprocess_pull(struct dns_options *o);\n\n/**\n * Invokes the action associated with bringing DNS up or down\n * @param   up          Boolean to set this call to \"up\" when true\n * @param   o           Pointer to the program options\n * @param   tt          Pointer to the connection's tuntap struct\n * @param   duri        Pointer to the updown runner info struct\n */\nvoid run_dns_up_down(bool up, struct options *o, const struct tuntap *tt,\n                     struct dns_updown_runner_info *duri);\n\n/**\n * Prints configured DNS options.\n *\n * @param   o           Pointer to the DNS options to print\n */\nvoid show_dns_options(const struct dns_options *o);\n\n/**\n * Returns whether dns-updown is user defined\n *\n * @param   o           Pointer to the DNS options struct\n */\nstatic inline bool\ndns_updown_user_set(const struct dns_options *o)\n{\n    return o->updown_flags == DNS_UPDOWN_USER_SET;\n}\n\n/**\n * Returns whether dns-updown is forced to run\n *\n * @param   o           Pointer to the DNS options struct\n */\nstatic inline bool\ndns_updown_forced(const struct dns_options *o)\n{\n    return o->updown_flags == DNS_UPDOWN_FORCED;\n}\n\n#endif /* ifndef DNS_H */\n"
  },
  {
    "path": "src/openvpn/domain_helper.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2025-2026 Lev Stipakov <lev@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, write to the Free Software Foundation, Inc.,\n *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n */\n\nstatic inline bool\nis_allowed_domain_ascii(unsigned char c)\n{\n    return (c >= 'A' && c <= 'Z')\n           || (c >= 'a' && c <= 'z')\n           || (c >= '0' && c <= '9')\n           || c == '.' || c == '-' || c == '_' || c >= 0x80;\n}\n\nstatic inline bool\nvalidate_domain(const char *domain)\n{\n    for (const char *ch = domain; *ch; ++ch)\n    {\n        if (!is_allowed_domain_ascii((unsigned char)*ch))\n        {\n            return false;\n        }\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/openvpn/env_set.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Technologies, Inc. <sales@openvpn.net>\n *  Copyright (C) 2014-2015 David Sommerseth <davids@redhat.com>\n *  Copyright (C) 2016-2026 David Sommerseth <davids@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"env_set.h\"\n\n#include \"run_command.h\"\n#include \"platform.h\"\n\n/*\n * Set environmental variable (int or string).\n *\n * On Posix, we use putenv for portability,\n * and put up with its painful semantics\n * that require all the support code below.\n */\n\n/* General-purpose environmental variable set functions */\n\nstatic char *\nconstruct_name_value(const char *name, const char *value, struct gc_arena *gc)\n{\n    struct buffer out;\n\n    ASSERT(name);\n    if (!value)\n    {\n        value = \"\";\n    }\n    out = alloc_buf_gc(strlen(name) + strlen(value) + 2, gc);\n    buf_printf(&out, \"%s=%s\", name, value);\n    return BSTR(&out);\n}\n\nstatic bool\nenv_string_equal(const char *s1, const char *s2)\n{\n    int c1, c2;\n    ASSERT(s1);\n    ASSERT(s2);\n\n    while (true)\n    {\n        c1 = *s1++;\n        c2 = *s2++;\n        if (c1 == '=')\n        {\n            c1 = 0;\n        }\n        if (c2 == '=')\n        {\n            c2 = 0;\n        }\n        if (!c1 && !c2)\n        {\n            return true;\n        }\n        if (c1 != c2)\n        {\n            break;\n        }\n    }\n    return false;\n}\n\nstatic bool\nremove_env_item(const char *str, const bool do_free, struct env_item **list)\n{\n    struct env_item *current, *prev;\n\n    ASSERT(str);\n    ASSERT(list);\n\n    for (current = *list, prev = NULL; current != NULL; current = current->next)\n    {\n        if (env_string_equal(current->string, str))\n        {\n            if (prev)\n            {\n                prev->next = current->next;\n            }\n            else\n            {\n                *list = current->next;\n            }\n            if (do_free)\n            {\n                secure_memzero(current->string, strlen(current->string));\n                free(current->string);\n                free(current);\n            }\n            return true;\n        }\n        prev = current;\n    }\n    return false;\n}\n\nstatic void\nadd_env_item(char *str, const bool do_alloc, struct env_item **list, struct gc_arena *gc)\n{\n    struct env_item *item;\n\n    ASSERT(str);\n    ASSERT(list);\n\n    ALLOC_OBJ_GC(item, struct env_item, gc);\n    item->string = do_alloc ? string_alloc(str, gc) : str;\n    item->next = *list;\n    *list = item;\n}\n\n/* struct env_set functions */\n\nstatic bool\nenv_set_del_nolock(struct env_set *es, const char *str)\n{\n    return remove_env_item(str, es->gc == NULL, &es->list);\n}\n\nstatic void\nenv_set_add_nolock(struct env_set *es, const char *str)\n{\n    remove_env_item(str, es->gc == NULL, &es->list);\n    add_env_item((char *)str, true, &es->list, es->gc);\n}\n\nstruct env_set *\nenv_set_create(struct gc_arena *gc)\n{\n    struct env_set *es;\n    ALLOC_OBJ_CLEAR_GC(es, struct env_set, gc);\n    es->list = NULL;\n    es->gc = gc;\n    return es;\n}\n\nvoid\nenv_set_destroy(struct env_set *es)\n{\n    if (es && es->gc == NULL)\n    {\n        struct env_item *e = es->list;\n        while (e)\n        {\n            struct env_item *next = e->next;\n            free(e->string);\n            free(e);\n            e = next;\n        }\n        free(es);\n    }\n}\n\nbool\nenv_set_del(struct env_set *es, const char *str)\n{\n    bool ret;\n    ASSERT(es);\n    ASSERT(str);\n    ret = env_set_del_nolock(es, str);\n    return ret;\n}\n\nvoid\nenv_set_add(struct env_set *es, const char *str)\n{\n    ASSERT(es);\n    ASSERT(str);\n    env_set_add_nolock(es, str);\n}\n\nconst char *\nenv_set_get(const struct env_set *es, const char *name)\n{\n    const struct env_item *item = es->list;\n    while (item && !env_string_equal(item->string, name))\n    {\n        item = item->next;\n    }\n    return item ? item->string : NULL;\n}\n\nvoid\nenv_set_print(msglvl_t msglevel, const struct env_set *es)\n{\n    if (check_debug_level(msglevel))\n    {\n        const struct env_item *e;\n        int i;\n\n        if (es)\n        {\n            e = es->list;\n            i = 0;\n\n            while (e)\n            {\n                if (env_safe_to_print(e->string))\n                {\n                    msg(msglevel, \"ENV [%d] '%s'\", i, e->string);\n                }\n                ++i;\n                e = e->next;\n            }\n        }\n    }\n}\n\nvoid\nenv_set_write_file(const char *path, const struct env_set *es)\n{\n    FILE *fp = platform_fopen(path, \"w\");\n    if (!fp)\n    {\n        msg(M_ERR, \"could not write env set to '%s'\", path);\n        return;\n    }\n\n    if (es)\n    {\n        const struct env_item *item = es->list;\n        while (item)\n        {\n            fputs(item->string, fp);\n            fputc('\\n', fp);\n            item = item->next;\n        }\n    }\n\n    fclose(fp);\n}\n\nvoid\nenv_set_inherit(struct env_set *es, const struct env_set *src)\n{\n    const struct env_item *e;\n\n    ASSERT(es);\n\n    if (src)\n    {\n        e = src->list;\n        while (e)\n        {\n            env_set_add_nolock(es, e->string);\n            e = e->next;\n        }\n    }\n}\n\n\n/* add/modify/delete environmental strings */\n\nvoid\nsetenv_counter(struct env_set *es, const char *name, counter_type value)\n{\n    char buf[64];\n    snprintf(buf, sizeof(buf), counter_format, value);\n    setenv_str(es, name, buf);\n}\n\nvoid\nsetenv_int(struct env_set *es, const char *name, int value)\n{\n    char buf[64];\n    snprintf(buf, sizeof(buf), \"%d\", value);\n    setenv_str(es, name, buf);\n}\n\nvoid\nsetenv_long_long(struct env_set *es, const char *name, long long value)\n{\n    char buf[64];\n    snprintf(buf, sizeof(buf), \"%\" PRIi64, (int64_t)value);\n    setenv_str(es, name, buf);\n}\n\nvoid\nsetenv_str(struct env_set *es, const char *name, const char *value)\n{\n    setenv_str_ex(es, name, value, CC_NAME, 0, 0, CC_PRINT, 0, 0);\n}\n\nvoid\nsetenv_str_safe(struct env_set *es, const char *name, const char *value)\n{\n    uint8_t b[64];\n    struct buffer buf;\n    buf_set_write(&buf, b, sizeof(b));\n    if (buf_printf(&buf, \"OPENVPN_%s\", name))\n    {\n        setenv_str(es, BSTR(&buf), value);\n    }\n    else\n    {\n        msg(M_WARN, \"setenv_str_safe: name overflow\");\n    }\n}\n\nvoid\nsetenv_str_incr(struct env_set *es, const char *name, const char *value)\n{\n    unsigned int counter = 1;\n    const size_t tmpname_len = strlen(name) + 5; /* 3 digits counter max */\n    char *tmpname = gc_malloc(tmpname_len, true, NULL);\n    strcpy(tmpname, name);\n    while (NULL != env_set_get(es, tmpname) && counter < 1000)\n    {\n        ASSERT(checked_snprintf(tmpname, tmpname_len, \"%s_%u\", name, counter));\n        counter++;\n    }\n    if (counter < 1000)\n    {\n        setenv_str(es, tmpname, value);\n    }\n    else\n    {\n        msg(D_TLS_DEBUG_MED, \"Too many same-name env variables, ignoring: %s\", name);\n    }\n    free(tmpname);\n}\n\nvoid\nsetenv_del(struct env_set *es, const char *name)\n{\n    ASSERT(name);\n    setenv_str(es, name, NULL);\n}\n\nvoid\nsetenv_str_ex(struct env_set *es, const char *name, const char *value,\n              const unsigned int name_include, const unsigned int name_exclude,\n              const char name_replace, const unsigned int value_include,\n              const unsigned int value_exclude, const char value_replace)\n{\n    struct gc_arena gc = gc_new();\n    const char *name_tmp;\n    const char *val_tmp = NULL;\n\n    ASSERT(name && strlen(name) > 1);\n\n    name_tmp = string_mod_const(name, name_include, name_exclude, name_replace, &gc);\n\n    if (value)\n    {\n        val_tmp = string_mod_const(value, value_include, value_exclude, value_replace, &gc);\n    }\n\n    ASSERT(es);\n\n    if (val_tmp)\n    {\n        const char *str = construct_name_value(name_tmp, val_tmp, &gc);\n        env_set_add(es, str);\n#if DEBUG_VERBOSE_SETENV\n        msg(M_INFO, \"SETENV_ES '%s'\", str);\n#endif\n    }\n    else\n    {\n        env_set_del(es, name_tmp);\n    }\n\n    gc_free(&gc);\n}\n\n/*\n * Setenv functions that append an integer index to the name\n */\nstatic const char *\nsetenv_format_indexed_name(const char *name, const int i, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(strlen(name) + 16, gc);\n    if (i >= 0)\n    {\n        buf_printf(&out, \"%s_%d\", name, i);\n    }\n    else\n    {\n        buf_printf(&out, \"%s\", name);\n    }\n    return BSTR(&out);\n}\n\nvoid\nsetenv_int_i(struct env_set *es, const char *name, const int value, const int i)\n{\n    struct gc_arena gc = gc_new();\n    const char *name_str = setenv_format_indexed_name(name, i, &gc);\n    setenv_int(es, name_str, value);\n    gc_free(&gc);\n}\n\nvoid\nsetenv_str_i(struct env_set *es, const char *name, const char *value, const int i)\n{\n    struct gc_arena gc = gc_new();\n    const char *name_str = setenv_format_indexed_name(name, i, &gc);\n    setenv_str(es, name_str, value);\n    gc_free(&gc);\n}\n\nbool\nenv_allowed(const char *str)\n{\n    return (script_security() >= SSEC_PW_ENV || !is_password_env_var(str));\n}\n\n/* Make arrays of strings */\n\nconst char **\nmake_env_array(const struct env_set *es, const bool check_allowed, struct gc_arena *gc)\n{\n    char **ret = NULL;\n    struct env_item *e = NULL;\n    int i = 0, n = 0;\n\n    /* figure length of es */\n    if (es)\n    {\n        for (e = es->list; e != NULL; e = e->next)\n        {\n            ++n;\n        }\n    }\n\n    /* alloc return array */\n    ALLOC_ARRAY_CLEAR_GC(ret, char *, n + 1, gc);\n\n    /* fill return array */\n    if (es)\n    {\n        i = 0;\n        for (e = es->list; e != NULL; e = e->next)\n        {\n            if (!check_allowed || env_allowed(e->string))\n            {\n                ASSERT(i < n);\n                ret[i++] = e->string;\n            }\n        }\n    }\n\n    ret[i] = NULL;\n    return (const char **)ret;\n}\n"
  },
  {
    "path": "src/openvpn/env_set.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Technologies, Inc. <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ENV_SET_H\n#define ENV_SET_H\n\n#include \"argv.h\"\n#include \"basic.h\"\n#include \"buffer.h\"\n#include \"common.h\"\n\n/*\n * Handle environmental variable lists\n */\n\nstruct env_item\n{\n    char *string;\n    struct env_item *next;\n};\n\nstruct env_set\n{\n    struct gc_arena *gc;\n    struct env_item *list;\n};\n\n/* set/delete environmental variable */\nvoid setenv_str_ex(struct env_set *es, const char *name, const char *value,\n                   const unsigned int name_include, const unsigned int name_exclude,\n                   const char name_replace, const unsigned int value_include,\n                   const unsigned int value_exclude, const char value_replace);\n\nvoid setenv_counter(struct env_set *es, const char *name, counter_type value);\n\nvoid setenv_int(struct env_set *es, const char *name, int value);\n\nvoid setenv_long_long(struct env_set *es, const char *name, long long value);\n\nvoid setenv_str(struct env_set *es, const char *name, const char *value);\n\nvoid setenv_str_safe(struct env_set *es, const char *name, const char *value);\n\nvoid setenv_del(struct env_set *es, const char *name);\n\n/**\n * Store the supplied name value pair in the env_set.  If the variable with the\n * supplied name  already exists, append _N to the name, starting at N=1.\n */\nvoid setenv_str_incr(struct env_set *es, const char *name, const char *value);\n\nvoid setenv_int_i(struct env_set *es, const char *name, const int value, const int i);\n\nvoid setenv_str_i(struct env_set *es, const char *name, const char *value, const int i);\n\n/* struct env_set functions */\n\nstruct env_set *env_set_create(struct gc_arena *gc);\n\nvoid env_set_destroy(struct env_set *es);\n\nbool env_set_del(struct env_set *es, const char *str);\n\nvoid env_set_add(struct env_set *es, const char *str);\n\nconst char *env_set_get(const struct env_set *es, const char *name);\n\nvoid env_set_print(msglvl_t msglevel, const struct env_set *es);\n\n/**\n * Write a struct env_set to a file. Each item on one line.\n *\n * @param path  The filepath to write to.\n * @param es    Pointer to the env_set to write.\n */\nvoid env_set_write_file(const char *path, const struct env_set *es);\n\nvoid env_set_inherit(struct env_set *es, const struct env_set *src);\n\n/* returns true if environmental variable name starts with 'password' */\nstatic inline bool\nis_password_env_var(const char *str)\n{\n    return (strncmp(str, \"password\", 8) == 0);\n}\n\n/* returns true if environmental variable safe to print to log */\nstatic inline bool\nenv_safe_to_print(const char *str)\n{\n#ifndef UNSAFE_DEBUG\n    if (is_password_env_var(str))\n    {\n        return false;\n    }\n#endif\n    return true;\n}\n\n/* returns true if environmental variable may be passed to an external program */\nbool env_allowed(const char *str);\n\nconst char **make_env_array(const struct env_set *es, const bool check_allowed,\n                            struct gc_arena *gc);\n\n#endif /* ifndef ENV_SET_H */\n"
  },
  {
    "path": "src/openvpn/errlevel.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ERRLEVEL_H\n#define ERRLEVEL_H\n\n#include \"error.h\"\n\n/*\n * Debug level at and above where we\n * display time to microsecond resolution.\n */\n#define DEBUG_LEVEL_USEC_TIME 4\n\n/*\n * In non-server modes, delay n milliseconds after certain kinds\n * of non-fatal network errors to avoid a barrage of errors.\n *\n * To disable all delays, set to 0.\n */\n#define P2P_ERROR_DELAY_MS 0\n\n/*\n * Enable D_LOG_RW\n */\n#define LOG_RW\n\n/*\n * Debugging levels for various kinds\n * of output.\n */\n\n#define M_VERB0 LOGLEV(0, 0, 0)                     /* Messages displayed even at --verb 0 (fatal errors only) */\n\n#define M_INFO LOGLEV(1, 0, 0)                      /* default informational messages */\n\n#define D_LINK_ERRORS    LOGLEV(1, 1, M_NONFATAL)   /* show link errors from main event loop */\n#define D_CRYPT_ERRORS   LOGLEV(1, 2, M_NONFATAL)   /* show errors from encrypt/decrypt */\n#define D_TLS_ERRORS     LOGLEV(1, 3, M_NONFATAL)   /* show TLS control channel errors */\n#define D_RESOLVE_ERRORS LOGLEV(1, 4, M_NONFATAL)   /* show hostname resolve errors */\n#define D_COMP_ERRORS    LOGLEV(1, 5, M_NONFATAL)   /* show compression errors */\n#define D_REPLAY_ERRORS  LOGLEV(1, 6, M_NONFATAL)   /* show packet replay errors */\n#define D_STREAM_ERRORS  LOGLEV(1, 7, M_NONFATAL)   /* TCP stream error requiring restart */\n#define D_IMPORT_ERRORS  LOGLEV(1, 8, M_NONFATAL)   /* show server import option errors */\n#define D_MULTI_ERRORS   LOGLEV(1, 9, M_NONFATAL)   /* show multi-client server errors */\n#define D_EVENT_ERRORS   LOGLEV(1, 10, M_NONFATAL)  /* show event.[ch] errors */\n#define D_PUSH_ERRORS    LOGLEV(1, 11, M_NONFATAL)  /* show push/pull errors */\n#define D_PID_PERSIST    LOGLEV(1, 12, M_NONFATAL)  /* show packet_id persist errors */\n#define D_FRAG_ERRORS    LOGLEV(1, 13, M_NONFATAL)  /* show fragmentation errors */\n#define D_ALIGN_ERRORS   LOGLEV(1, 14, M_NONFATAL)  /* show bad struct alignments */\n\n#define D_HANDSHAKE LOGLEV(2, 20, 0)                /* show data & control channel handshakes */\n#define D_CLOSE     LOGLEV(2, 22, 0)                /* show socket and TUN/TAP close */\n#define D_PROXY     LOGLEV(2, 24, 0)                /* show http proxy control packets */\n#define D_ARGV      LOGLEV(2, 25, 0)                /* show struct argv errors */\n\n#define D_TLS_DEBUG_LOW LOGLEV(3, 20, 0)            /* low frequency info from tls_session routines */\n#define D_GREMLIN       LOGLEV(3, 30, 0)            /* show simulated outage info from gremlin module */\n#define D_GENKEY        LOGLEV(3, 31, 0)            /* print message after key generation */\n#define D_ROUTE         LOGLEV(3, 0, 0)             /* show routes added and deleted (don't mute) */\n#define D_TUNTAP_INFO   LOGLEV(3, 32, 0)            /* show debugging info from TUN/TAP driver */\n#define D_RESTART       LOGLEV(3, 33, 0)            /* show certain restart messages */\n#define D_PUSH          LOGLEV(3, 34, 0)            /* show push/pull info */\n#define D_IFCONFIG_POOL LOGLEV(3, 35, 0)            /* show ifconfig pool info */\n#define D_AUTH          LOGLEV(3, 37, 0)            /* show user/pass auth info */\n#define D_MULTI_LOW     LOGLEV(3, 38, 0)            /* show point-to-multipoint low-freq debug info */\n#define D_PLUGIN        LOGLEV(3, 39, 0)            /* show plugin calls */\n#define D_MANAGEMENT    LOGLEV(3, 40, 0)            /* show --management info */\n#define D_SCHED_EXIT    LOGLEV(3, 41, 0)            /* show arming of scheduled exit */\n#define D_ROUTE_QUOTA   LOGLEV(3, 42, 0)            /* show route quota exceeded messages */\n#define D_OSBUF         LOGLEV(3, 43, 0)            /* show socket/tun/tap buffer sizes */\n#define D_PS_PROXY      LOGLEV(3, 44, 0)            /* messages related to --port-share option */\n#define D_IFCONFIG      LOGLEV(3, 0, 0)             /* show ifconfig info (don't mute) */\n#define D_DCO           LOGLEV(3, 0, 0)             /* show DCO related messages */\n\n#define D_SHOW_PARMS       LOGLEV(4, 50, 0)         /* show all parameters on program initiation */\n#define D_LOW              LOGLEV(4, 52, 0)         /* miscellaneous low-frequency debug info */\n#define D_DHCP_OPT         LOGLEV(4, 53, 0)         /* show DHCP options binary string */\n#define D_MBUF             LOGLEV(4, 54, 0)         /* mbuf.[ch] routines */\n#define D_PACKET_TRUNC_ERR LOGLEV(4, 55, 0)         /* PACKET_TRUNCATION_CHECK */\n#define D_MULTI_DROPPED    LOGLEV(4, 57, 0)         /* show point-to-multipoint packet drops */\n#define D_MULTI_MEDIUM     LOGLEV(4, 58, 0)         /* show medium frequency multi messages */\n#define D_X509_ATTR        LOGLEV(4, 59, 0)         /* show x509-track attributes on connection */\n#define D_INIT_MEDIUM      LOGLEV(4, 60, 0)         /* show medium frequency init messages */\n#define D_MTU_INFO         LOGLEV(4, 61, 0)         /* show terse MTU info */\n#define D_PID_DEBUG_LOW    LOGLEV(4, 63, 0)         /* show low-freq packet-id debugging info */\n#define D_PID_DEBUG_MEDIUM LOGLEV(4, 64, 0)         /* show medium-freq packet-id debugging info */\n#define D_CIPHER_INIT      LOGLEV(4, 65, 0)         /* show messages about cipher init */\n\n#define D_LOG_RW LOGLEV(5, 0, 0)                    /* Print 'R' or 'W' to stdout for read/write */\n\n#define D_RTNL          LOGLEV(6, 68, M_DEBUG)      /* show RTNL low level operations */\n#define D_LINK_RW       LOGLEV(6, 69, M_DEBUG)      /* show TCP/UDP reads/writes (terse) */\n#define D_TUN_RW        LOGLEV(6, 69, M_DEBUG)      /* show TUN/TAP reads/writes */\n#define D_TAP_WIN_DEBUG LOGLEV(6, 69, M_DEBUG)      /* show TAP-Windows driver debug info */\n#define D_CLIENT_NAT    LOGLEV(6, 69, M_DEBUG)      /* show client NAT debug info */\n#define D_XKEY          LOGLEV(6, 69, M_DEBUG)      /* show xkey-provider debug info */\n#define D_DCO_DEBUG     LOGLEV(6, 69, M_DEBUG)      /* show DCO related lowlevel debug messages */\n#define D_SIGNAL_DEBUG  LOGLEV(6, 69, M_DEBUG)      /* show signal related debug messages */\n\n#define D_SHOW_KEYS          LOGLEV(7, 70, M_DEBUG) /* show data channel encryption keys */\n#define D_SHOW_KEY_SOURCE    LOGLEV(7, 70, M_DEBUG) /* show data channel key source entropy */\n#define D_REL_LOW            LOGLEV(7, 70, M_DEBUG) /* show low frequency info from reliable layer */\n#define D_FRAG_DEBUG         LOGLEV(7, 70, M_DEBUG) /* show fragment debugging info */\n#define D_WIN32_IO_LOW       LOGLEV(7, 70, M_DEBUG) /* low freq win32 I/O debugging info */\n#define D_MTU_DEBUG          LOGLEV(7, 70, M_DEBUG) /* show MTU debugging info */\n#define D_MULTI_DEBUG        LOGLEV(7, 70, M_DEBUG) /* show medium-freq multi debugging info */\n#define D_MSS                LOGLEV(7, 70, M_DEBUG) /* show MSS adjustments */\n#define D_COMP_LOW           LOGLEV(7, 70, M_DEBUG) /* show adaptive compression state changes */\n#define D_CONNECTION_LIST    LOGLEV(7, 70, M_DEBUG) /* show <connection> list info */\n#define D_SCRIPT             LOGLEV(7, 70, M_DEBUG) /* show parms & env vars passed to scripts */\n#define D_SHOW_NET           LOGLEV(7, 70, M_DEBUG) /* show routing table and adapter list */\n#define D_ROUTE_DEBUG        LOGLEV(7, 70, M_DEBUG) /* show verbose route.[ch] output */\n#define D_TLS_STATE_ERRORS   LOGLEV(7, 70, M_DEBUG) /* no TLS state for client */\n#define D_SEMAPHORE_LOW      LOGLEV(7, 70, M_DEBUG) /* show Win32 semaphore waits (low freq) */\n#define D_SEMAPHORE          LOGLEV(7, 70, M_DEBUG) /* show Win32 semaphore waits */\n#define D_TEST_FILE          LOGLEV(7, 70, M_DEBUG) /* show test_file() calls */\n#define D_MANAGEMENT_DEBUG   LOGLEV(3, 70, M_DEBUG) /* show --management debug info */\n#define D_PLUGIN_DEBUG       LOGLEV(7, 70, M_DEBUG) /* show verbose plugin calls */\n#define D_SOCKET_DEBUG       LOGLEV(7, 70, M_DEBUG) /* show socket.[ch] debugging info */\n#define D_SHOW_PKCS11        LOGLEV(7, 70, M_DEBUG) /* show PKCS#11 actions */\n#define D_ALIGN_DEBUG        LOGLEV(7, 70, M_DEBUG) /* show verbose struct alignment info */\n#define D_PACKET_TRUNC_DEBUG LOGLEV(7, 70, M_DEBUG) /* PACKET_TRUNCATION_CHECK verbose */\n#define D_PING               LOGLEV(7, 70, M_DEBUG) /* PING send/receive messages */\n#define D_PS_PROXY_DEBUG     LOGLEV(7, 70, M_DEBUG) /* port share proxy debug */\n#define D_TLS_KEYSELECT      LOGLEV(7, 70, M_DEBUG) /* show key selection for data channel */\n#define D_ARGV_PARSE_CMD     LOGLEV(7, 70, M_DEBUG) /* show parse_line() errors in argv_parse_cmd */\n#define D_CRYPTO_DEBUG       LOGLEV(7, 70, M_DEBUG) /* show detailed info from crypto.c routines */\n#define D_PID_DEBUG          LOGLEV(7, 70, M_DEBUG) /* show packet-id debugging info */\n#define D_PUSH_DEBUG         LOGLEV(7, 73, M_DEBUG) /* show push/pull debugging info */\n#define D_SHOW_OCC           LOGLEV(7, 74, M_DEBUG) /* show options compatibility string */\n\n\n#define D_VLAN_DEBUG LOGLEV(7, 74, M_DEBUG)             /* show VLAN tagging/untagging debug info */\n\n#define D_HANDSHAKE_VERBOSE LOGLEV(8, 70, M_DEBUG)      /* show detailed description of handshake */\n#define D_TLS_DEBUG_MED     LOGLEV(8, 70, M_DEBUG)      /* limited info from tls_session routines */\n#define D_INTERVAL          LOGLEV(8, 70, M_DEBUG)      /* show interval.h debugging info */\n#define D_SCHEDULER         LOGLEV(8, 70, M_DEBUG)      /* show scheduler debugging info */\n#define D_GREMLIN_VERBOSE   LOGLEV(8, 70, M_DEBUG)      /* show verbose info from gremlin module */\n#define D_REL_DEBUG         LOGLEV(8, 70, M_DEBUG)      /* show detailed info from reliable routines */\n#define D_EVENT_WAIT        LOGLEV(8, 70, M_DEBUG)      /* show detailed info from event waits */\n#define D_MULTI_TCP         LOGLEV(8, 70, M_DEBUG)      /* show debug info from mtcp.c */\n\n#define D_TLS_DEBUG         LOGLEV(9, 70, M_DEBUG)      /* show detailed info from TLS routines */\n#define D_COMP              LOGLEV(9, 70, M_DEBUG)      /* show compression info */\n#define D_READ_WRITE        LOGLEV(9, 70, M_DEBUG)      /* show all tun/tcp/udp reads/writes/opens */\n#define D_PACKET_CONTENT    LOGLEV(9, 70, M_DEBUG)      /* show before/after encryption packet content */\n#define D_TLS_NO_SEND_KEY   LOGLEV(9, 70, M_DEBUG)      /* show when no data channel send-key exists */\n#define D_PID_PERSIST_DEBUG LOGLEV(9, 70, M_DEBUG)      /* show packet-id persist debugging info */\n#define D_LINK_RW_VERBOSE   LOGLEV(9, 70, M_DEBUG)      /* increase link reads/writes verbosity */\n#define D_STREAM_DEBUG      LOGLEV(9, 70, M_DEBUG)      /* show TCP stream debug info */\n#define D_WIN32_IO          LOGLEV(9, 70, M_DEBUG)      /* win32 I/O debugging info */\n#define D_PKCS11_DEBUG      LOGLEV(9, 70, M_DEBUG)      /* show PKCS#11 debugging */\n\n#define D_SHAPER_DEBUG LOGLEV(10, 70, M_DEBUG)          /* show traffic shaper info */\n\n#define D_REGISTRY     LOGLEV(11, 70, M_DEBUG)          /* win32 registry debugging info */\n#define D_OPENSSL_LOCK LOGLEV(11, 70, M_DEBUG)          /* show OpenSSL locks */\n\n/*#define D_THREAD_DEBUG       LOGLEV(4, 70, M_DEBUG)*/ /* show pthread debug information */\n\n#endif                                                  /* ifndef ERRLEVEL_H */\n"
  },
  {
    "path": "src/openvpn/error.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"error.h\"\n#include \"buffer.h\"\n#include \"init.h\"\n#include \"misc.h\"\n#include \"win32.h\"\n#include \"socket.h\"\n#include \"tun.h\"\n#include \"otime.h\"\n#include \"status.h\"\n#include \"integer.h\"\n#include \"ps.h\"\n\n#if SYSLOG_CAPABILITY\n#ifndef LOG_OPENVPN\n#define LOG_OPENVPN LOG_DAEMON\n#endif\n#endif\n\n/* Globals */\nmsglvl_t x_debug_level; /* GLOBAL */\n\n/* Mute state */\nstatic int mute_cutoff;        /* GLOBAL */\nstatic int mute_count;         /* GLOBAL */\nstatic msglvl_t mute_category; /* GLOBAL */\n\n/*\n * Output mode priorities are as follows:\n *\n *  (1) --log-x overrides everything\n *  (2) syslog is used if --daemon is defined and not --log-x\n *  (3) if OPENVPN_DEBUG_COMMAND_LINE is defined, output\n *      to constant logfile name.\n *  (4) Output to stdout.\n */\n\n/* If true, indicates that stdin/stdout/stderr\n * have been redirected due to --log */\nstatic bool std_redir; /* GLOBAL */\n\n/* Should messages be written to the syslog? */\nstatic bool use_syslog; /* GLOBAL */\n\n/* Should stdout/stderr be be parsable and always be prefixed with time\n * and message flags */\nstatic bool machine_readable_output; /* GLOBAL */\n\n/* Should timestamps be included on messages to stdout/stderr? */\nstatic bool suppress_timestamps; /* GLOBAL */\n\n/* The program name passed to syslog */\n#if SYSLOG_CAPABILITY\nstatic char *pgmname_syslog; /* GLOBAL */\n#endif\n\n/* If non-null, messages should be written here (used for debugging only) */\nstatic FILE *msgfp; /* GLOBAL */\n\n/* If true, we forked from main OpenVPN process */\nstatic bool forked; /* GLOBAL */\n\n/* our default output targets */\nstatic FILE *default_out; /* GLOBAL */\nstatic FILE *default_err; /* GLOBAL */\n\nvoid\nmsg_forked(void)\n{\n    forked = true;\n}\n\nbool\nset_debug_level(const int level, const unsigned int flags)\n{\n    if (level >= 0 && (unsigned int)level <= M_DEBUG_LEVEL)\n    {\n        x_debug_level = (msglvl_t)level;\n        return true;\n    }\n    else if (flags & SDL_CONSTRAIN)\n    {\n        x_debug_level = (msglvl_t)constrain_int(level, 0, M_DEBUG_LEVEL);\n        return true;\n    }\n    return false;\n}\n\nbool\nset_mute_cutoff(const int cutoff)\n{\n    if (cutoff >= 0)\n    {\n        mute_cutoff = cutoff;\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\nmsglvl_t\nget_debug_level(void)\n{\n    return x_debug_level;\n}\n\nint\nget_mute_cutoff(void)\n{\n    return mute_cutoff;\n}\n\nvoid\nset_suppress_timestamps(bool suppressed)\n{\n    suppress_timestamps = suppressed;\n}\n\nvoid\nset_machine_readable_output(bool parsable)\n{\n    machine_readable_output = parsable;\n}\n\nvoid\nerror_reset(void)\n{\n    use_syslog = std_redir = false;\n    suppress_timestamps = false;\n    machine_readable_output = false;\n    x_debug_level = 1;\n    mute_cutoff = 0;\n    mute_count = 0;\n    mute_category = 0;\n    default_out = OPENVPN_MSG_FP;\n    default_err = OPENVPN_MSG_FP;\n\n#ifdef OPENVPN_DEBUG_COMMAND_LINE\n    msgfp = fopen(OPENVPN_DEBUG_FILE, \"w\");\n    if (!msgfp)\n    {\n        openvpn_exit(OPENVPN_EXIT_STATUS_CANNOT_OPEN_DEBUG_FILE); /* exit point */\n    }\n#else\n    msgfp = NULL;\n#endif\n}\n\nvoid\nerrors_to_stderr(void)\n{\n    default_err = OPENVPN_ERROR_FP;\n}\n\n/*\n * Return a file to print messages to before syslog is opened.\n */\nFILE *\nmsg_fp(const msglvl_t flags)\n{\n    FILE *fp = msgfp;\n    if (!fp)\n    {\n        fp = (flags & (M_FATAL | M_USAGE_SMALL)) ? default_err : default_out;\n    }\n    if (!fp)\n    {\n        openvpn_exit(OPENVPN_EXIT_STATUS_CANNOT_OPEN_DEBUG_FILE); /* exit point */\n    }\n    return fp;\n}\n\n#define SWAP      \\\n    {             \\\n        tmp = m1; \\\n        m1 = m2;  \\\n        m2 = tmp; \\\n    }\n\nint x_msg_line_num; /* GLOBAL */\n\nvoid\nx_msg(const msglvl_t flags, const char *format, ...)\n{\n    va_list arglist;\n    va_start(arglist, format);\n    x_msg_va(flags, format, arglist);\n    va_end(arglist);\n}\n\nstatic const char *\nopenvpn_strerror(int err, bool crt_error, struct gc_arena *gc)\n{\n#ifdef _WIN32\n    if (!crt_error)\n    {\n        return strerror_win32(err, gc);\n    }\n#endif\n    return strerror(err);\n}\n\nvoid\nx_msg_va(const msglvl_t flags, const char *format, va_list arglist)\n{\n    struct gc_arena gc;\n#if SYSLOG_CAPABILITY\n    int level;\n#endif\n    char *m1;\n    char *m2;\n    char *tmp;\n    int e;\n    const char *prefix;\n    const char *prefix_sep;\n\n    void usage_small(void);\n\n    /* the macro has checked this otherwise */\n    if (!msg_test(flags))\n    {\n        return;\n    }\n\n    bool crt_error = false;\n    e = openvpn_errno_maybe_crt(&crt_error);\n\n    /*\n     * Apply muting filter.\n     */\n    /* the macro has checked this otherwise */\n    if (!dont_mute(flags))\n    {\n        return;\n    }\n\n    gc_init(&gc);\n\n    m1 = (char *)gc_malloc(ERR_BUF_SIZE, false, &gc);\n    m2 = (char *)gc_malloc(ERR_BUF_SIZE, false, &gc);\n\n    vsnprintf(m1, ERR_BUF_SIZE, format, arglist);\n    m1[ERR_BUF_SIZE - 1] = 0; /* windows vsnprintf needs this */\n\n    if ((flags & M_ERRNO) && e)\n    {\n        snprintf(m2, ERR_BUF_SIZE, \"%s: %s (errno=%d)\", m1, openvpn_strerror(e, crt_error, &gc), e);\n        SWAP;\n    }\n\n    if (flags & M_OPTERR)\n    {\n        snprintf(m2, ERR_BUF_SIZE, \"Options error: %s\", m1);\n        SWAP;\n    }\n\n#if SYSLOG_CAPABILITY\n    if (flags & (M_FATAL | M_NONFATAL | M_USAGE_SMALL))\n    {\n        level = LOG_ERR;\n    }\n    else if (flags & M_WARN)\n    {\n        level = LOG_WARNING;\n    }\n    else\n    {\n        level = LOG_NOTICE;\n    }\n#endif\n\n    /* set up client prefix */\n    if (flags & M_NOIPREFIX)\n    {\n        prefix = NULL;\n    }\n    else\n    {\n        prefix = msg_get_prefix();\n    }\n    prefix_sep = \" \";\n    if (!prefix)\n    {\n        prefix_sep = prefix = \"\";\n    }\n\n    /* virtual output capability used to copy output to management subsystem */\n    if (!forked)\n    {\n        const struct virtual_output *vo = msg_get_virtual_output();\n        if (vo)\n        {\n            snprintf(m2, ERR_BUF_SIZE, \"%s%s%s\", prefix, prefix_sep, m1);\n            virtual_output_print(vo, flags, m2);\n        }\n    }\n\n    if (!(flags & M_MSG_VIRT_OUT))\n    {\n        if (use_syslog && !std_redir && !forked)\n        {\n#if SYSLOG_CAPABILITY\n            syslog(level, \"%s%s%s\", prefix, prefix_sep, m1);\n#endif\n        }\n        else\n        {\n            FILE *fp = msg_fp(flags);\n            const bool show_usec = check_debug_level(DEBUG_LEVEL_USEC_TIME);\n\n            if (machine_readable_output)\n            {\n                struct timeval tv;\n                gettimeofday(&tv, NULL);\n\n                fprintf(fp, \"%\" PRIi64 \".%06ld %x %s%s%s%s\", (int64_t)tv.tv_sec, (long)tv.tv_usec,\n                        flags, prefix, prefix_sep, m1, \"\\n\");\n            }\n            else if ((flags & M_NOPREFIX) || suppress_timestamps)\n            {\n                fprintf(fp, \"%s%s%s%s\", prefix, prefix_sep, m1, (flags & M_NOLF) ? \"\" : \"\\n\");\n            }\n            else\n            {\n                fprintf(fp, \"%s %s%s%s%s\", time_string(0, 0, show_usec, &gc), prefix, prefix_sep,\n                        m1, (flags & M_NOLF) ? \"\" : \"\\n\");\n            }\n            fflush(fp);\n            ++x_msg_line_num;\n        }\n    }\n\n    if (flags & M_FATAL)\n    {\n        msg(M_INFO, \"Exiting due to fatal error\");\n    }\n\n    if (flags & M_FATAL)\n    {\n        openvpn_exit(OPENVPN_EXIT_STATUS_ERROR); /* exit point */\n    }\n    if (flags & M_USAGE_SMALL)\n    {\n        usage_small();\n    }\n\n    gc_free(&gc);\n}\n\n/*\n * Apply muting filter.\n */\nbool\ndont_mute(msglvl_t flags)\n{\n    bool ret = true;\n    if (mute_cutoff > 0 && !(flags & M_NOMUTE))\n    {\n        const msglvl_t mute_level = DECODE_MUTE_LEVEL(flags);\n        if (mute_level == mute_category)\n        {\n            if (mute_count == mute_cutoff)\n            {\n                msg(M_INFO | M_NOMUTE, \"NOTE: --mute triggered...\");\n            }\n            if (++mute_count > mute_cutoff)\n            {\n                ret = false;\n            }\n        }\n        else\n        {\n            const int suppressed = mute_count - mute_cutoff;\n            if (suppressed > 0)\n            {\n                msg(M_INFO | M_NOMUTE,\n                    \"%d variation(s) on previous %d message(s) suppressed by --mute\", suppressed,\n                    mute_cutoff);\n            }\n            mute_count = 1;\n            mute_category = mute_level;\n        }\n    }\n    return ret;\n}\n\nvoid\nassert_failed(const char *filename, int line, const char *condition)\n{\n    if (condition)\n    {\n        msg(M_FATAL, \"Assertion failed at %s:%d (%s)\", filename, line, condition);\n    }\n    else\n    {\n        msg(M_FATAL, \"Assertion failed at %s:%d\", filename, line);\n    }\n    _exit(1);\n}\n\n/*\n * Fail memory allocation.  Don't use msg() because it tries\n * to allocate memory as part of its operation.\n */\nvoid\nout_of_memory(void)\n{\n    fprintf(stderr, PACKAGE_NAME \": Out of Memory\\n\");\n    exit(1);\n}\n\nvoid\nopen_syslog(const char *pgmname, bool stdio_to_null)\n{\n#if SYSLOG_CAPABILITY\n    if (!msgfp && !std_redir)\n    {\n        if (!use_syslog)\n        {\n            pgmname_syslog = string_alloc(pgmname ? pgmname : PACKAGE, NULL);\n            openlog(pgmname_syslog, LOG_PID, LOG_OPENVPN);\n            use_syslog = true;\n\n            /* Better idea: somehow pipe stdout/stderr output to msg() */\n            if (stdio_to_null)\n            {\n                set_std_files_to_null(false);\n            }\n        }\n    }\n#else /* if SYSLOG_CAPABILITY */\n    msg(M_WARN,\n        \"Warning on use of --daemon: this operating system lacks daemon logging features, therefore when I become a daemon, I won't be able to log status or error messages\");\n#endif\n}\n\nvoid\nclose_syslog(void)\n{\n#if SYSLOG_CAPABILITY\n    if (use_syslog)\n    {\n        closelog();\n        use_syslog = false;\n        free(pgmname_syslog);\n        pgmname_syslog = NULL;\n    }\n#endif\n}\n\n#ifdef _WIN32\nstatic int orig_stderr;\n\nint\nget_orig_stderr(void)\n{\n    return orig_stderr ? orig_stderr : _fileno(stderr);\n}\n#endif\n\nvoid\nredirect_stdout_stderr(const char *file, bool append)\n{\n#if defined(_WIN32)\n    if (!std_redir)\n    {\n        struct gc_arena gc = gc_new();\n        HANDLE log_handle;\n        int log_fd;\n\n        SECURITY_ATTRIBUTES saAttr;\n        saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);\n        saAttr.bInheritHandle = TRUE;\n        saAttr.lpSecurityDescriptor = NULL;\n\n        log_handle = CreateFileW(wide_string(file, &gc), GENERIC_WRITE, FILE_SHARE_READ, &saAttr,\n                                 append ? OPEN_ALWAYS : CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);\n\n        gc_free(&gc);\n\n        if (log_handle == INVALID_HANDLE_VALUE)\n        {\n            msg(M_WARN | M_ERRNO, \"Warning: cannot open --log file: %s\", file);\n            return;\n        }\n\n        /* append to logfile? */\n        if (append)\n        {\n            if (SetFilePointer(log_handle, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER)\n            {\n                msg(M_ERR, \"Error: cannot seek to end of --log file: %s\", file);\n            }\n        }\n\n        /* save original stderr for password prompts */\n        orig_stderr = _dup(_fileno(stderr));\n        if (orig_stderr == -1)\n        {\n            msg(M_WARN | M_ERRNO,\n                \"Warning: cannot duplicate stderr, password prompts will appear in log file instead of console.\");\n            orig_stderr = _fileno(stderr);\n        }\n\n        /* direct stdout/stderr to point to log_handle */\n        log_fd = _open_osfhandle((intptr_t)log_handle, _O_TEXT);\n        if (log_fd == -1)\n        {\n            msg(M_ERR, \"Error: --log redirect failed due to _open_osfhandle failure\");\n        }\n\n        /* open log_handle as FILE stream */\n        ASSERT(msgfp == NULL);\n        msgfp = _fdopen(log_fd, \"wt\");\n        if (msgfp == NULL)\n        {\n            msg(M_ERR, \"Error: --log redirect failed due to _fdopen\");\n        }\n\n        /* redirect C-library stdout/stderr to log file */\n        if (_dup2(log_fd, 1) == -1 || _dup2(log_fd, 2) == -1)\n        {\n            msg(M_WARN, \"Error: --log redirect of stdout/stderr failed\");\n        }\n\n        std_redir = true;\n    }\n#elif defined(HAVE_DUP2)\n    if (!std_redir)\n    {\n        int out = open(file, O_CREAT | O_WRONLY | (append ? O_APPEND : O_TRUNC), S_IRUSR | S_IWUSR);\n\n        if (out < 0)\n        {\n            msg(M_WARN | M_ERRNO, \"Warning: Error redirecting stdout/stderr to --log file: %s\",\n                file);\n            return;\n        }\n\n        if (dup2(out, 1) == -1)\n        {\n            msg(M_ERR, \"--log file redirection error on stdout\");\n        }\n        if (dup2(out, 2) == -1)\n        {\n            msg(M_ERR, \"--log file redirection error on stderr\");\n        }\n\n        if (out > 2)\n        {\n            close(out);\n        }\n\n        std_redir = true;\n    }\n\n#else  /* if defined(_WIN32) */\n    msg(M_WARN,\n        \"WARNING: The --log option is not supported on this OS because it lacks the dup2 function\");\n#endif /* if defined(_WIN32) */\n}\n\n/*\n * Functions used to check return status\n * of I/O operations.\n */\n\nunsigned int x_cs_info_level;    /* GLOBAL */\nunsigned int x_cs_verbose_level; /* GLOBAL */\nunsigned int x_cs_err_delay_ms;  /* GLOBAL */\n\nvoid\nreset_check_status(void)\n{\n    x_cs_info_level = 0;\n    x_cs_verbose_level = 0;\n}\n\nvoid\nset_check_status(unsigned int info_level, unsigned int verbose_level)\n{\n    x_cs_info_level = info_level;\n    x_cs_verbose_level = verbose_level;\n}\n\n/*\n * Called after most socket or tun/tap operations, via the inline\n * function check_status().\n *\n * Decide if we should print an error message, and see if we can\n * extract any useful info from the error, such as a Path MTU hint\n * from the OS.\n */\nvoid\nx_check_status(ssize_t status, const char *description, struct link_socket *sock, struct tuntap *tt)\n{\n    const char *extended_msg = NULL;\n\n    bool crt_error = false;\n    int my_errno = openvpn_errno_maybe_crt(&crt_error);\n\n    msg(x_cs_verbose_level, \"%s %s returned %zd\",\n        sock ? proto2ascii(sock->info.proto, sock->info.af, true) : \"\", description, status);\n\n    if (status < 0)\n    {\n        struct gc_arena gc = gc_new();\n#if EXTENDED_SOCKET_ERROR_CAPABILITY\n        /* get extended socket error message and possible PMTU hint from OS */\n        if (sock)\n        {\n            int mtu;\n            extended_msg = format_extended_socket_error(sock->sd, &mtu, &gc);\n            if (mtu > 0 && sock->mtu != mtu)\n            {\n                sock->mtu = mtu;\n                sock->info.mtu_changed = true;\n            }\n        }\n#endif /* EXTENDED_SOCKET_ERROR_CAPABILITY */\n\n#ifdef _WIN32\n        /* get possible driver error from TAP-Windows driver */\n        if (tuntap_defined(tt))\n        {\n            extended_msg = tap_win_getinfo(tt, &gc);\n        }\n#endif\n\n        if (!ignore_sys_error(my_errno, crt_error))\n        {\n            if (extended_msg)\n            {\n                msg(x_cs_info_level, \"%s %s [%s]: %s (fd=\" SOCKET_PRINTF \",code=%d)\",\n                    description,\n                    sock ? proto2ascii(sock->info.proto, sock->info.af, true) : \"\",\n                    extended_msg,\n                    openvpn_strerror(my_errno, crt_error, &gc),\n                    sock ? sock->sd : SOCKET_UNDEFINED,\n                    my_errno);\n            }\n            else\n            {\n                msg(x_cs_info_level, \"%s %s: %s (fd=\" SOCKET_PRINTF \",code=%d)\",\n                    description,\n                    sock ? proto2ascii(sock->info.proto, sock->info.af, true) : \"\",\n                    openvpn_strerror(my_errno, crt_error, &gc),\n                    sock ? sock->sd : SOCKET_UNDEFINED,\n                    my_errno);\n            }\n\n            if (x_cs_err_delay_ms)\n            {\n                platform_sleep_milliseconds(x_cs_err_delay_ms);\n            }\n        }\n        gc_free(&gc);\n    }\n}\n\n/*\n * In multiclient mode, put a client-specific prefix\n * before each message.\n */\nconst char *x_msg_prefix; /* GLOBAL */\n\n/*\n * Allow MSG to be redirected through a virtual_output object\n */\n\nconst struct virtual_output *x_msg_virtual_output; /* GLOBAL */\n\n/*\n * Exiting.\n */\n\nvoid\nopenvpn_exit(const int status)\n{\n    if (!forked)\n    {\n        tun_abort();\n\n#ifdef _WIN32\n        uninit_win32();\n#endif\n        remove_pid_file();\n\n        close_syslog();\n\n#ifdef ENABLE_PLUGIN\n        plugin_abort();\n#endif\n\n#if PORT_SHARE\n        if (port_share)\n        {\n            port_share_abort(port_share);\n        }\n#endif\n\n#ifdef ABORT_ON_ERROR\n        if (status == OPENVPN_EXIT_STATUS_ERROR)\n        {\n            abort();\n        }\n#endif\n    }\n\n    exit(status);\n}\n\n/*\n * Translate msg flags into a string\n */\nconst char *\nmsg_flags_string(const msglvl_t flags, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(16, gc);\n    if (flags == M_INFO)\n    {\n        buf_printf(&out, \"I\");\n    }\n    if (flags & M_FATAL)\n    {\n        buf_printf(&out, \"F\");\n    }\n    if (flags & M_NONFATAL)\n    {\n        buf_printf(&out, \"N\");\n    }\n    if (flags & M_WARN)\n    {\n        buf_printf(&out, \"W\");\n    }\n    if (flags & M_DEBUG)\n    {\n        buf_printf(&out, \"D\");\n    }\n    return BSTR(&out);\n}\n\n#ifdef _WIN32\n\nconst char *\nstrerror_win32(DWORD errnum, struct gc_arena *gc)\n{\n    /*\n     * This code can be omitted, though often the Windows\n     * WSA error messages are less informative than the\n     * Posix equivalents.\n     */\n#if 1\n    switch (errnum)\n    {\n        /*\n         * When the TAP-Windows driver returns STATUS_UNSUCCESSFUL, this code\n         * gets returned to user space.\n         */\n        case ERROR_GEN_FAILURE:\n            return \"General failure (ERROR_GEN_FAILURE)\";\n\n        case ERROR_IO_PENDING:\n            return \"I/O Operation in progress (ERROR_IO_PENDING)\";\n\n        case WSA_IO_INCOMPLETE:\n            return \"I/O Operation in progress (WSA_IO_INCOMPLETE)\";\n\n        case WSAEINTR:\n            return \"Interrupted system call (WSAEINTR)\";\n\n        case WSAEBADF:\n            return \"Bad file number (WSAEBADF)\";\n\n        case WSAEACCES:\n            return \"Permission denied (WSAEACCES)\";\n\n        case WSAEFAULT:\n            return \"Bad address (WSAEFAULT)\";\n\n        case WSAEINVAL:\n            return \"Invalid argument (WSAEINVAL)\";\n\n        case WSAEMFILE:\n            return \"Too many open files (WSAEMFILE)\";\n\n        case WSAEWOULDBLOCK:\n            return \"Operation would block (WSAEWOULDBLOCK)\";\n\n        case WSAEINPROGRESS:\n            return \"Operation now in progress (WSAEINPROGRESS)\";\n\n        case WSAEALREADY:\n            return \"Operation already in progress (WSAEALREADY)\";\n\n        case WSAEDESTADDRREQ:\n            return \"Destination address required (WSAEDESTADDRREQ)\";\n\n        case WSAEMSGSIZE:\n            return \"Message too long (WSAEMSGSIZE)\";\n\n        case WSAEPROTOTYPE:\n            return \"Protocol wrong type for socket (WSAEPROTOTYPE)\";\n\n        case WSAENOPROTOOPT:\n            return \"Bad protocol option (WSAENOPROTOOPT)\";\n\n        case WSAEPROTONOSUPPORT:\n            return \"Protocol not supported (WSAEPROTONOSUPPORT)\";\n\n        case WSAESOCKTNOSUPPORT:\n            return \"Socket type not supported (WSAESOCKTNOSUPPORT)\";\n\n        case WSAEOPNOTSUPP:\n            return \"Operation not supported on socket (WSAEOPNOTSUPP)\";\n\n        case WSAEPFNOSUPPORT:\n            return \"Protocol family not supported (WSAEPFNOSUPPORT)\";\n\n        case WSAEAFNOSUPPORT:\n            return \"Address family not supported by protocol family (WSAEAFNOSUPPORT)\";\n\n        case WSAEADDRINUSE:\n            return \"Address already in use (WSAEADDRINUSE)\";\n\n        case WSAENETDOWN:\n            return \"Network is down (WSAENETDOWN)\";\n\n        case WSAENETUNREACH:\n            return \"Network is unreachable (WSAENETUNREACH)\";\n\n        case WSAENETRESET:\n            return \"Net dropped connection or reset (WSAENETRESET)\";\n\n        case WSAECONNABORTED:\n            return \"Software caused connection abort (WSAECONNABORTED)\";\n\n        case WSAECONNRESET:\n            return \"Connection reset by peer (WSAECONNRESET)\";\n\n        case WSAENOBUFS:\n            return \"No buffer space available (WSAENOBUFS)\";\n\n        case WSAEISCONN:\n            return \"Socket is already connected (WSAEISCONN)\";\n\n        case WSAENOTCONN:\n            return \"Socket is not connected (WSAENOTCONN)\";\n\n        case WSAETIMEDOUT:\n            return \"Connection timed out (WSAETIMEDOUT)\";\n\n        case WSAECONNREFUSED:\n            return \"Connection refused (WSAECONNREFUSED)\";\n\n        case WSAELOOP:\n            return \"Too many levels of symbolic links (WSAELOOP)\";\n\n        case WSAENAMETOOLONG:\n            return \"File name too long (WSAENAMETOOLONG)\";\n\n        case WSAEHOSTDOWN:\n            return \"Host is down (WSAEHOSTDOWN)\";\n\n        case WSAEHOSTUNREACH:\n            return \"No Route to Host (WSAEHOSTUNREACH)\";\n\n        case WSAENOTEMPTY:\n            return \"Directory not empty (WSAENOTEMPTY)\";\n\n        case WSAEPROCLIM:\n            return \"Too many processes (WSAEPROCLIM)\";\n\n        case WSAEUSERS:\n            return \"Too many users (WSAEUSERS)\";\n\n        case WSAEDQUOT:\n            return \"Disc Quota Exceeded (WSAEDQUOT)\";\n\n        case WSAESTALE:\n            return \"Stale NFS file handle (WSAESTALE)\";\n\n        case WSASYSNOTREADY:\n            return \"Network SubSystem is unavailable (WSASYSNOTREADY)\";\n\n        case WSAVERNOTSUPPORTED:\n            return \"WINSOCK DLL Version out of range (WSAVERNOTSUPPORTED)\";\n\n        case WSANOTINITIALISED:\n            return \"Successful WSASTARTUP not yet performed (WSANOTINITIALISED)\";\n\n        case WSAEREMOTE:\n            return \"Too many levels of remote in path (WSAEREMOTE)\";\n\n        case WSAHOST_NOT_FOUND:\n            return \"Host not found (WSAHOST_NOT_FOUND)\";\n\n        default:\n            break;\n    }\n#endif /* if 1 */\n\n    /* format a windows error message */\n    {\n        wchar_t wmessage[256];\n        char *message = NULL;\n        struct buffer out = alloc_buf_gc(256, gc);\n        const DWORD status =\n            FormatMessageW(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM\n                               | FORMAT_MESSAGE_ARGUMENT_ARRAY,\n                           NULL, errnum, 0, wmessage, SIZE(wmessage), NULL);\n        if (status)\n        {\n            message = utf16to8(wmessage, gc);\n        }\n        if (!status || !message)\n        {\n            buf_printf(&out, \"[Unknown Win32 Error]\");\n        }\n        else\n        {\n            char *cp;\n            for (cp = message; *cp != '\\0'; ++cp)\n            {\n                if (*cp == '\\n' || *cp == '\\r')\n                {\n                    *cp = ' ';\n                }\n            }\n\n            buf_printf(&out, \"%s\", message);\n        }\n\n        return BSTR(&out);\n    }\n}\n\n#endif /* ifdef _WIN32 */\n"
  },
  {
    "path": "src/openvpn/error.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ERROR_H\n#define ERROR_H\n\n#include \"basic.h\"\n#include \"syshead.h\"\n\n#include <assert.h>\n\n/* #define ABORT_ON_ERROR */\n\n#if defined(ENABLE_PKCS11) || defined(ENABLE_MANAGEMENT)\n#define ERR_BUF_SIZE 10240\n#else\n#define ERR_BUF_SIZE 1280\n#endif\n\nstruct gc_arena;\n\n/*\n * Where should messages be printed before syslog is opened?\n * Not used if OPENVPN_DEBUG_COMMAND_LINE is defined.\n */\n#define OPENVPN_MSG_FP   stdout\n#define OPENVPN_ERROR_FP stderr\n\n/*\n * Exit status codes\n */\n\n#define OPENVPN_EXIT_STATUS_GOOD                   0\n#define OPENVPN_EXIT_STATUS_ERROR                  1\n#define OPENVPN_EXIT_STATUS_USAGE                  1\n#define OPENVPN_EXIT_STATUS_CANNOT_OPEN_DEBUG_FILE 1\n\n/*\n * Special command line debugging mode.\n * If OPENVPN_DEBUG_COMMAND_LINE\n * is defined, contents of argc/argv will\n * be dumped to OPENVPN_DEBUG_FILE as well\n * as all other OpenVPN messages.\n */\n\n/* #define OPENVPN_DEBUG_COMMAND_LINE */\n#define OPENVPN_DEBUG_FILE PACKAGE \".log\"\n\n/* String and Error functions */\n\n#ifdef _WIN32\n#define openvpn_errno() GetLastError()\nconst char *strerror_win32(DWORD errnum, struct gc_arena *gc);\n#else\n#define openvpn_errno() errno\n#endif\n\ntypedef unsigned int msglvl_t;\n\n/*\n * These globals should not be accessed directly,\n * but rather through macros or inline functions defined below.\n */\nextern msglvl_t x_debug_level;\nextern int x_msg_line_num;\n\n/* msg() flags */\n\n#define M_DEBUG_LEVEL (0x0Fu) /* debug level mask */\n\n#define M_FATAL    (1u << 4)  /* exit program */\n#define M_NONFATAL (1u << 5)  /* non-fatal error */\n#define M_WARN     (1u << 6)  /* call syslog with LOG_WARNING */\n#define M_DEBUG    (1u << 7)\n\n#define M_ERRNO (1u << 8)         /* show errno description */\n\n#define M_NOMUTE       (1u << 11) /* don't do mute processing */\n#define M_NOPREFIX     (1u << 12) /* don't show date/time prefix */\n#define M_USAGE_SMALL  (1u << 13) /* fatal options error, call usage_small */\n#define M_MSG_VIRT_OUT (1u << 14) /* output message through msg_status_output callback */\n#define M_OPTERR       (1u << 15) /* print \"Options error:\" prefix */\n#define M_NOLF         (1u << 16) /* don't print new line */\n#define M_NOIPREFIX    (1u << 17) /* don't print instance prefix */\n\n/* flag combinations which are frequently used */\n#define M_ERR    (M_FATAL | M_ERRNO)\n#define M_USAGE  (M_USAGE_SMALL | M_NOPREFIX | M_OPTERR)\n#define M_CLIENT (M_MSG_VIRT_OUT | M_NOMUTE | M_NOIPREFIX)\n\n/*\n * Mute levels are designed to avoid large numbers of\n * mostly similar messages clogging the log file.\n *\n * A mute level of 0 is always printed.\n */\n#define MUTE_LEVEL_SHIFT 24\n#define MUTE_LEVEL_MASK  0xFFu\n\n#define ENCODE_MUTE_LEVEL(mute_level) (((mute_level) & MUTE_LEVEL_MASK) << MUTE_LEVEL_SHIFT)\n#define DECODE_MUTE_LEVEL(flags)      (((flags) >> MUTE_LEVEL_SHIFT) & MUTE_LEVEL_MASK)\n\n/*\n * log_level:  verbosity level n (--verb n) must be >= log_level to print.\n * mute_level: don't print more than n (--mute n) consecutive messages at\n *             a given mute level, or if 0 disable muting and print everything.\n *\n * Mask map:\n * Bits 0-3:   log level\n * Bits 4-23:  M_x flags\n * Bits 24-31: mute level\n */\n#define LOGLEV(log_level, mute_level, other) ((log_level) | ENCODE_MUTE_LEVEL(mute_level) | other)\n\n/*\n * If compiler supports variable arguments in macros, define\n * msg() as a macro for optimization win.\n */\n\n/** Check muting filter */\nbool dont_mute(msglvl_t flags);\n\n/* Macro to ensure (and teach static analysis tools) we exit on fatal errors */\n#define EXIT_FATAL(flags)      \\\n    do                         \\\n    {                          \\\n        if ((flags) & M_FATAL) \\\n        {                      \\\n            _exit(1);          \\\n        }                      \\\n    } while (false)\n\n#define msg(flags, ...)                  \\\n    do                                   \\\n    {                                    \\\n        if (msg_test(flags))             \\\n        {                                \\\n            x_msg((flags), __VA_ARGS__); \\\n        }                                \\\n        EXIT_FATAL(flags);               \\\n    } while (false)\n#ifdef ENABLE_DEBUG\n#define dmsg(flags, ...)                 \\\n    do                                   \\\n    {                                    \\\n        if (msg_test(flags))             \\\n        {                                \\\n            x_msg((flags), __VA_ARGS__); \\\n        }                                \\\n        EXIT_FATAL(flags);               \\\n    } while (false)\n#else\n#define dmsg(flags, ...)\n#endif\n\nvoid x_msg(const msglvl_t flags, const char *format, ...)\n#ifdef __GNUC__\n#if __USE_MINGW_ANSI_STDIO\n    __attribute__((format(gnu_printf, 2, 3)))\n#else\n    __attribute__((format(__printf__, 2, 3)))\n#endif\n#endif\n    ; /* should be called via msg above */\n\nvoid x_msg_va(const msglvl_t flags, const char *format, va_list arglist);\n\n/*\n * Function prototypes\n */\n\nvoid error_reset(void);\n\n/* route errors to stderr that would normally go to stdout */\nvoid errors_to_stderr(void);\n\nvoid set_suppress_timestamps(bool suppressed);\n\nvoid set_machine_readable_output(bool parsable);\n\n\n#define SDL_CONSTRAIN (1 << 0)\nbool set_debug_level(const int level, const unsigned int flags);\n\nbool set_mute_cutoff(const int cutoff);\n\nmsglvl_t get_debug_level(void);\n\nint get_mute_cutoff(void);\n\nconst char *msg_flags_string(const msglvl_t flags, struct gc_arena *gc);\n\n/*\n * File to print messages to before syslog is opened.\n */\nFILE *msg_fp(const msglvl_t flags);\n\n/* Fatal logic errors */\n#ifndef ENABLE_SMALL\n#define ASSERT(x)                                  \\\n    do                                             \\\n    {                                              \\\n        if (!(x))                                  \\\n        {                                          \\\n            assert_failed(__FILE__, __LINE__, #x); \\\n        }                                          \\\n    } while (false)\n#else\n#define ASSERT(x)                                    \\\n    do                                               \\\n    {                                                \\\n        if (!(x))                                    \\\n        {                                            \\\n            assert_failed(__FILE__, __LINE__, NULL); \\\n        }                                            \\\n    } while (false)\n#endif\n\n#ifdef _MSC_VER\n__declspec(noreturn)\n#endif\nvoid\nassert_failed(const char *filename, int line, const char *condition)\n#ifndef _MSC_VER\n    __attribute__((__noreturn__))\n#endif\n    ;\n\n/* Inline functions */\n\nstatic inline bool\ncheck_debug_level(msglvl_t level)\n{\n    return (level & M_DEBUG_LEVEL) <= x_debug_level;\n}\n\n/** Return true if flags represent an enabled, not muted log level */\nstatic inline bool\nmsg_test(msglvl_t flags)\n{\n    return check_debug_level(flags) && dont_mute(flags);\n}\n\n/* Call if we forked */\nvoid msg_forked(void);\n\n/* syslog output */\n\nvoid open_syslog(const char *pgmname, bool stdio_to_null);\n\nvoid close_syslog(void);\n\n/* log file output */\nvoid redirect_stdout_stderr(const char *file, bool append);\n\n#ifdef _WIN32\n/* get original stderr fd, even if redirected by --log/--log-append */\nint get_orig_stderr(void);\n\n#endif\n\n/* exit program */\nvoid openvpn_exit(const int status);\n\n/* exit program on out of memory error */\nvoid out_of_memory(void);\n\n/*\n * Check the return status of read/write routines.\n */\n\nstruct link_socket;\nstruct tuntap;\n\nextern unsigned int x_cs_info_level;\nextern unsigned int x_cs_verbose_level;\nextern unsigned int x_cs_err_delay_ms;\n\nvoid reset_check_status(void);\n\nvoid set_check_status(unsigned int info_level, unsigned int verbose_level);\n\nvoid x_check_status(ssize_t status, const char *description, struct link_socket *sock,\n                    struct tuntap *tt);\n\nstatic inline void\ncheck_status(ssize_t status, const char *description, struct link_socket *sock, struct tuntap *tt)\n{\n    if (status < 0 || check_debug_level(x_cs_verbose_level))\n    {\n        x_check_status(status, description, sock, tt);\n    }\n}\n\nstatic inline void\nset_check_status_error_delay(unsigned int milliseconds)\n{\n    x_cs_err_delay_ms = milliseconds;\n}\n\n/*\n * In multiclient mode, put a client-specific prefix\n * before each message.\n *\n * TODO: x_msg_prefix should be thread-local\n */\n\nextern const char *x_msg_prefix;\n\nstatic inline void\nmsg_set_prefix(const char *prefix)\n{\n    x_msg_prefix = prefix;\n}\n\nstatic inline const char *\nmsg_get_prefix(void)\n{\n    return x_msg_prefix;\n}\n\n/*\n * Allow MSG to be redirected through a virtual_output object\n */\n\nstruct virtual_output;\n\nextern const struct virtual_output *x_msg_virtual_output;\n\nstatic inline void\nmsg_set_virtual_output(const struct virtual_output *vo)\n{\n    x_msg_virtual_output = vo;\n}\n\nstatic inline const struct virtual_output *\nmsg_get_virtual_output(void)\n{\n    return x_msg_virtual_output;\n}\n\n/*\n * Return true if this is a system error\n * which can be safely ignored.\n */\nstatic inline bool\nignore_sys_error(const int err, bool crt_error)\n{\n#ifdef _WIN32\n    if (!crt_error && ((err == WSAEWOULDBLOCK || err == WSAEINVAL)))\n    {\n        return true;\n    }\n#else\n    crt_error = true;\n#endif\n\n    /* I/O operation pending */\n    if (crt_error && (err == EAGAIN))\n    {\n        return true;\n    }\n\n#if 0 /* if enabled, suppress ENOBUFS errors */\n#ifdef ENOBUFS\n    /* No buffer space available */\n    if (err == ENOBUFS)\n    {\n        return true;\n    }\n#endif\n#endif\n\n    return false;\n}\n\n/** Convert fatal errors to nonfatal, don't touch other errors */\nstatic inline msglvl_t\nnonfatal(const msglvl_t err)\n{\n    return err & M_FATAL ? (err ^ M_FATAL) | M_NONFATAL : err;\n}\n\nstatic inline int\nopenvpn_errno_maybe_crt(bool *crt_error)\n{\n    int err = 0;\n    *crt_error = false;\n#ifdef _WIN32\n    err = GetLastError();\n    if (err == ERROR_SUCCESS)\n    {\n        /* error is likely C runtime */\n        *crt_error = true;\n        err = errno;\n    }\n#else /* ifdef _WIN32 */\n    *crt_error = true;\n    err = errno;\n#endif\n    return err;\n}\n\n#include \"errlevel.h\"\n\n#endif /* ifndef ERROR_H */\n"
  },
  {
    "path": "src/openvpn/event.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"integer.h\"\n#include \"event.h\"\n#include \"fdmisc.h\"\n\n#if EPOLL\n#include <sys/epoll.h>\n#endif\n\n#include \"memdbg.h\"\n\n/*\n * Some OSes will prefer select() over poll()\n * when both are available.\n */\n#if defined(TARGET_DARWIN)\n#define SELECT_PREFERRED_OVER_POLL\n#endif\n\n/*\n * All non-windows OSes are assumed to have select()\n */\n#ifdef _WIN32\n#define SELECT 0\n#else\n#define SELECT 1\n#endif\n\n/*\n * This should be set to the highest file descriptor\n * which can be used in one of the FD_ macros.\n */\n#ifdef FD_SETSIZE\n#define SELECT_MAX_FDS FD_SETSIZE\n#else\n#define SELECT_MAX_FDS 256\n#endif\n\n/** Convert \\c timeval value (which is in seconds and microseconds)\n    to a value of milliseconds which is required by multiple polling\n    APIs.\n\n    @param tv \\c timeval to convert\n\n    @return Milliseconds to wait. Zero if \\p tv is zero.\n     Otherwise the return value is always greater than zero.\n*/\nstatic inline int\ntv_to_ms_timeout(const struct timeval *tv)\n{\n    if (tv->tv_sec == 0 && tv->tv_usec == 0)\n    {\n        return 0;\n    }\n    else\n    {\n        /* might overflow but not for practically useful numbers */\n        return max_int((int)(tv->tv_sec * 1000 + (tv->tv_usec + 500) / 1000), 1);\n    }\n}\n\n#ifdef _WIN32\n\nstruct we_set\n{\n    struct event_set_functions func;\n    bool fast;\n    HANDLE *events;\n    struct event_set_return *esr;\n    int n_events;\n    int capacity;\n};\n\nstatic inline void\nwe_set_event(struct we_set *wes, int i, event_t event, unsigned int rwflags, void *arg)\n{\n    ASSERT(i >= 0 && i < wes->capacity);\n\n    if (rwflags == EVENT_READ)\n    {\n        ASSERT(event->read != NULL);\n        wes->events[i] = event->read;\n    }\n    else if (rwflags == EVENT_WRITE)\n    {\n        ASSERT(event->write != NULL);\n        wes->events[i] = event->write;\n    }\n    else\n    {\n        msg(M_FATAL, \"fatal error in we_set_events: rwflags=%d\", rwflags);\n    }\n\n    wes->esr[i].rwflags = rwflags;\n    wes->esr[i].arg = arg;\n}\n\nstatic inline bool\nwe_append_event(struct we_set *wes, event_t event, unsigned int rwflags, void *arg)\n{\n    if (rwflags & EVENT_WRITE)\n    {\n        if (wes->n_events < wes->capacity)\n        {\n            we_set_event(wes, wes->n_events, event, EVENT_WRITE, arg);\n            ++wes->n_events;\n        }\n        else\n        {\n            return false;\n        }\n    }\n    if (rwflags & EVENT_READ)\n    {\n        if (wes->n_events < wes->capacity)\n        {\n            we_set_event(wes, wes->n_events, event, EVENT_READ, arg);\n            ++wes->n_events;\n        }\n        else\n        {\n            return false;\n        }\n    }\n    return true;\n}\n\nstatic void\nwe_del_event(struct we_set *wes, event_t event)\n{\n    int i, j = 0;\n    const int len = wes->n_events;\n\n    for (i = 0; i < len; ++i)\n    {\n        const HANDLE h = wes->events[i];\n        if (h == event->read || h == event->write)\n        {\n            --wes->n_events;\n        }\n        else\n        {\n            if (i != j)\n            {\n                wes->events[j] = wes->events[i];\n                wes->esr[j] = wes->esr[i];\n            }\n            ++j;\n        }\n    }\n}\n\nstatic void\nwe_del_index(struct we_set *wes, int index)\n{\n    int i;\n    ASSERT(index >= 0 && index < wes->n_events);\n    for (i = index; i < wes->n_events - 1; ++i)\n    {\n        wes->events[i] = wes->events[i + 1];\n        wes->esr[i] = wes->esr[i + 1];\n    }\n    --wes->n_events;\n}\n\nstatic void\nwe_get_rw_indices(struct we_set *wes, event_t event, int *ri, int *wi)\n{\n    int i;\n    *ri = *wi = -1;\n    for (i = 0; i < wes->n_events; ++i)\n    {\n        const HANDLE h = wes->events[i];\n        if (h == event->read)\n        {\n            ASSERT(*ri == -1);\n            *ri = i;\n        }\n        else if (h == event->write)\n        {\n            ASSERT(*wi == -1);\n            *wi = i;\n        }\n    }\n}\n\nstatic void\nwe_free(struct event_set *es)\n{\n    struct we_set *wes = (struct we_set *)es;\n    free(wes->events);\n    free(wes->esr);\n    free(wes);\n}\n\nstatic void\nwe_reset(struct event_set *es)\n{\n    struct we_set *wes = (struct we_set *)es;\n    ASSERT(wes->fast);\n    wes->n_events = 0;\n}\n\nstatic void\nwe_del(struct event_set *es, event_t event)\n{\n    struct we_set *wes = (struct we_set *)es;\n    ASSERT(!wes->fast);\n    we_del_event(wes, event);\n}\n\nstatic void\nwe_ctl(struct event_set *es, event_t event, unsigned int rwflags, void *arg)\n{\n    struct we_set *wes = (struct we_set *)es;\n\n    dmsg(D_EVENT_WAIT, \"WE_CTL n=%d ev=%p rwflags=0x%04x arg=\" ptr_format, wes->n_events, event,\n         rwflags, (ptr_type)arg);\n\n    if (wes->fast)\n    {\n        if (!we_append_event(wes, event, rwflags, arg))\n        {\n            goto err;\n        }\n    }\n    else\n    {\n        int ri, wi;\n        int one = -1;\n        int n = 0;\n\n        we_get_rw_indices(wes, event, &ri, &wi);\n        if (wi >= 0)\n        {\n            one = wi;\n            ++n;\n        }\n        if (ri >= 0)\n        {\n            one = ri;\n            ++n;\n        }\n        switch (rwflags)\n        {\n            case 0:\n                switch (n)\n                {\n                    case 0:\n                        break;\n\n                    case 1:\n                        we_del_index(wes, one);\n                        break;\n\n                    case 2:\n                        we_del_event(wes, event);\n                        break;\n\n                    default:\n                        ASSERT(0);\n                }\n                break;\n\n            case EVENT_READ:\n                switch (n)\n                {\n                    case 0:\n                        if (!we_append_event(wes, event, EVENT_READ, arg))\n                        {\n                            goto err;\n                        }\n                        break;\n\n                    case 1:\n                        we_set_event(wes, one, event, EVENT_READ, arg);\n                        break;\n\n                    case 2:\n                        we_del_index(wes, wi);\n                        break;\n\n                    default:\n                        ASSERT(0);\n                }\n                break;\n\n            case EVENT_WRITE:\n                switch (n)\n                {\n                    case 0:\n                        if (!we_append_event(wes, event, EVENT_WRITE, arg))\n                        {\n                            goto err;\n                        }\n                        break;\n\n                    case 1:\n                        we_set_event(wes, one, event, EVENT_WRITE, arg);\n                        break;\n\n                    case 2:\n                        we_del_index(wes, ri);\n                        break;\n\n                    default:\n                        ASSERT(0);\n                }\n                break;\n\n            case EVENT_READ | EVENT_WRITE:\n                switch (n)\n                {\n                    case 0:\n                        if (!we_append_event(wes, event, EVENT_READ | EVENT_WRITE, arg))\n                        {\n                            goto err;\n                        }\n                        break;\n\n                    case 1:\n                        if (ri == -1)\n                        {\n                            ASSERT(wi != -1);\n                            if (!we_append_event(wes, event, EVENT_READ, arg))\n                            {\n                                goto err;\n                            }\n                        }\n                        else if (wi == -1)\n                        {\n                            if (!we_append_event(wes, event, EVENT_WRITE, arg))\n                            {\n                                goto err;\n                            }\n                        }\n                        else\n                        {\n                            ASSERT(0);\n                        }\n                        break;\n\n                    case 2:\n                        break;\n\n                    default:\n                        ASSERT(0);\n                }\n                break;\n\n            default:\n                msg(M_FATAL, \"fatal error in we_ctl: rwflags=%d\", rwflags);\n        }\n    }\n    return;\n\nerr:\n    msg(D_EVENT_ERRORS,\n        \"Error: Windows resource limit WSA_MAXIMUM_WAIT_EVENTS (%d) has been exceeded\",\n        WSA_MAXIMUM_WAIT_EVENTS);\n}\n\nstatic int\nwe_wait(struct event_set *es, const struct timeval *tv, struct event_set_return *out, int outlen)\n{\n    struct we_set *wes = (struct we_set *)es;\n    const int timeout = tv_to_ms_timeout(tv);\n    DWORD status;\n\n    dmsg(D_EVENT_WAIT, \"WE_WAIT enter n=%d to=%d\", wes->n_events, timeout);\n\n#ifdef ENABLE_DEBUG\n    if (check_debug_level(D_EVENT_WAIT))\n    {\n        int i;\n        for (i = 0; i < wes->n_events; ++i)\n        {\n            dmsg(D_EVENT_WAIT, \"[%d] ev=%p rwflags=0x%04x arg=\" ptr_format, i, wes->events[i],\n                 wes->esr[i].rwflags, (ptr_type)wes->esr[i].arg);\n        }\n    }\n#endif\n\n    /* WSA_WAIT_EVENT_0 == 0 but the API documentation is written in a way\n       that doesn't guarantee that. So we make useless checks. */\n#if defined(__GNUC__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wtype-limits\"\n#endif\n\n    /*\n     * First poll our event list with 0 timeout\n     */\n    status = WSAWaitForMultipleEvents((DWORD)wes->n_events, wes->events, FALSE, (DWORD)0, FALSE);\n\n    /*\n     * If at least one event is already set, we must\n     * individually poll the whole list.\n     */\n    if (status >= WSA_WAIT_EVENT_0 && status < WSA_WAIT_EVENT_0 + (DWORD)wes->n_events)\n    {\n        int i;\n        int j = 0;\n        for (i = 0; i < wes->n_events; ++i)\n        {\n            if (j >= outlen)\n            {\n                break;\n            }\n            if (WaitForSingleObject(wes->events[i], 0) == WAIT_OBJECT_0)\n            {\n                *out = wes->esr[i];\n                dmsg(D_EVENT_WAIT, \"WE_WAIT leave [%d,%d] rwflags=0x%04x arg=\" ptr_format, i, j,\n                     out->rwflags, (ptr_type)out->arg);\n                ++j;\n                ++out;\n            }\n        }\n        return j;\n    }\n    else\n    {\n        /*\n         * If caller specified timeout > 0, we know at this point\n         * that no events are set, so wait only for the first event\n         * (or timeout) and return at most one event_set_return object.\n         *\n         * If caller specified timeout == 0, the second call to\n         * WSAWaitForMultipleEvents would be redundant -- just\n         * return 0 indicating timeout.\n         */\n        if (timeout > 0)\n        {\n            status = WSAWaitForMultipleEvents((DWORD)wes->n_events, wes->events, FALSE,\n                                              (DWORD)timeout, FALSE);\n        }\n\n        if (outlen >= 1 && status >= WSA_WAIT_EVENT_0\n            && status < WSA_WAIT_EVENT_0 + (DWORD)wes->n_events)\n        {\n            *out = wes->esr[status - WSA_WAIT_EVENT_0];\n            dmsg(D_EVENT_WAIT, \"WE_WAIT leave rwflags=0x%04x arg=\" ptr_format, out->rwflags,\n                 (ptr_type)out->arg);\n            return 1;\n        }\n        else if (status == WSA_WAIT_TIMEOUT)\n        {\n            return 0;\n        }\n        else\n        {\n            return -1;\n        }\n    }\n#if defined(__GNUC__)\n#pragma GCC diagnostic pop\n#endif\n}\n\nstatic struct event_set *\nwe_init(int *maxevents, unsigned int flags)\n{\n    struct we_set *wes;\n\n    dmsg(D_EVENT_WAIT, \"WE_INIT maxevents=%d flags=0x%08x\", *maxevents, flags);\n\n    ALLOC_OBJ_CLEAR(wes, struct we_set);\n\n    /* set dispatch functions */\n    wes->func.free = we_free;\n    wes->func.reset = we_reset;\n    wes->func.del = we_del;\n    wes->func.ctl = we_ctl;\n    wes->func.wait = we_wait;\n\n    if (flags & EVENT_METHOD_FAST)\n    {\n        wes->fast = true;\n    }\n    wes->n_events = 0;\n\n    /* Figure our event capacity */\n    ASSERT(*maxevents > 0);\n    wes->capacity = min_int(*maxevents * 2, WSA_MAXIMUM_WAIT_EVENTS);\n    *maxevents = min_int(*maxevents, WSA_MAXIMUM_WAIT_EVENTS);\n\n    /* Allocate space for Win32 event handles */\n    ALLOC_ARRAY_CLEAR(wes->events, HANDLE, wes->capacity);\n\n    /* Allocate space for event_set_return objects */\n    ALLOC_ARRAY_CLEAR(wes->esr, struct event_set_return, wes->capacity);\n\n    dmsg(D_EVENT_WAIT, \"WE_INIT maxevents=%d capacity=%d\", *maxevents, wes->capacity);\n\n    return (struct event_set *)wes;\n}\n\n#endif /* _WIN32 */\n\n#if EPOLL\n\nstruct ep_set\n{\n    struct event_set_functions func;\n    bool fast;\n    int epfd;\n    int maxevents;\n    struct epoll_event *events;\n};\n\nstatic void\nep_free(struct event_set *es)\n{\n    struct ep_set *eps = (struct ep_set *)es;\n    close(eps->epfd);\n    free(eps->events);\n    free(eps);\n}\n\nstatic void\nep_reset(struct event_set *es)\n{\n    const struct ep_set *eps = (struct ep_set *)es;\n    ASSERT(eps->fast);\n}\n\nstatic void\nep_del(struct event_set *es, event_t event)\n{\n    struct epoll_event ev;\n    struct ep_set *eps = (struct ep_set *)es;\n\n    dmsg(D_EVENT_WAIT, \"EP_DEL ev=%d\", (int)event);\n\n    ASSERT(!eps->fast);\n    CLEAR(ev);\n    if (epoll_ctl(eps->epfd, EPOLL_CTL_DEL, event, &ev) < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"EVENT: epoll_ctl EPOLL_CTL_DEL failed, sd=%d\", (int)event);\n    }\n}\n\nstatic void\nep_ctl(struct event_set *es, event_t event, unsigned int rwflags, void *arg)\n{\n    struct ep_set *eps = (struct ep_set *)es;\n    struct epoll_event ev;\n\n    CLEAR(ev);\n\n    ev.data.ptr = arg;\n    if (rwflags & EVENT_READ)\n    {\n        ev.events |= EPOLLIN;\n    }\n    if (rwflags & EVENT_WRITE)\n    {\n        ev.events |= EPOLLOUT;\n    }\n\n    dmsg(D_EVENT_WAIT, \"EP_CTL fd=%d rwflags=0x%04x ev=0x%08x arg=\" ptr_format, (int)event, rwflags,\n         (unsigned int)ev.events, (ptr_type)ev.data.ptr);\n\n    if (epoll_ctl(eps->epfd, EPOLL_CTL_MOD, event, &ev) < 0)\n    {\n        if (errno == ENOENT)\n        {\n            if (epoll_ctl(eps->epfd, EPOLL_CTL_ADD, event, &ev) < 0)\n            {\n                msg(M_ERR, \"EVENT: epoll_ctl EPOLL_CTL_ADD failed, sd=%d\", (int)event);\n            }\n        }\n        else\n        {\n            msg(M_ERR, \"EVENT: epoll_ctl EPOLL_CTL_MOD failed, sd=%d\", (int)event);\n        }\n    }\n}\n\nstatic int\nep_wait(struct event_set *es, const struct timeval *tv, struct event_set_return *out, int outlen)\n{\n    struct ep_set *eps = (struct ep_set *)es;\n    int stat;\n\n    if (outlen > eps->maxevents)\n    {\n        outlen = eps->maxevents;\n    }\n\n    stat = epoll_wait(eps->epfd, eps->events, outlen, tv_to_ms_timeout(tv));\n    ASSERT(stat <= outlen);\n\n    if (stat > 0)\n    {\n        int i;\n        const struct epoll_event *ev = eps->events;\n        struct event_set_return *esr = out;\n        for (i = 0; i < stat; ++i)\n        {\n            esr->rwflags = 0;\n            if (ev->events & (EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP))\n            {\n                esr->rwflags |= EVENT_READ;\n            }\n            if (ev->events & EPOLLOUT)\n            {\n                esr->rwflags |= EVENT_WRITE;\n            }\n            esr->arg = ev->data.ptr;\n            dmsg(D_EVENT_WAIT, \"EP_WAIT[%d] rwflags=0x%04x ev=0x%08x arg=\" ptr_format, i,\n                 esr->rwflags, ev->events, (ptr_type)ev->data.ptr);\n            ++ev;\n            ++esr;\n        }\n    }\n    return stat;\n}\n\nstatic struct event_set *\nep_init(int *maxevents, unsigned int flags)\n{\n    struct ep_set *eps;\n    int fd;\n\n    dmsg(D_EVENT_WAIT, \"EP_INIT maxevents=%d flags=0x%08x\", *maxevents, flags);\n\n    /* open epoll file descriptor */\n    fd = epoll_create(*maxevents);\n    if (fd < 0)\n    {\n        return NULL;\n    }\n\n    set_cloexec(fd);\n\n    ALLOC_OBJ_CLEAR(eps, struct ep_set);\n\n    /* set dispatch functions */\n    eps->func.free = ep_free;\n    eps->func.reset = ep_reset;\n    eps->func.del = ep_del;\n    eps->func.ctl = ep_ctl;\n    eps->func.wait = ep_wait;\n\n    /* fast method (\"sort of\") corresponds to epoll one-shot */\n    if (flags & EVENT_METHOD_FAST)\n    {\n        eps->fast = true;\n    }\n\n    /* allocate space for epoll_wait return */\n    ASSERT(*maxevents > 0);\n    eps->maxevents = *maxevents;\n    ALLOC_ARRAY_CLEAR(eps->events, struct epoll_event, eps->maxevents);\n\n    /* set epoll control fd */\n    eps->epfd = fd;\n\n    return (struct event_set *)eps;\n}\n#endif /* EPOLL */\n\n#if POLL\n\nstruct po_set\n{\n    struct event_set_functions func;\n    bool fast;\n    struct pollfd *events;\n    void **args;\n    int n_events;\n    int capacity;\n};\n\nstatic void\npo_free(struct event_set *es)\n{\n    struct po_set *pos = (struct po_set *)es;\n    free(pos->events);\n    free(pos->args);\n    free(pos);\n}\n\nstatic void\npo_reset(struct event_set *es)\n{\n    struct po_set *pos = (struct po_set *)es;\n    ASSERT(pos->fast);\n    pos->n_events = 0;\n}\n\nstatic void\npo_del(struct event_set *es, event_t event)\n{\n    struct po_set *pos = (struct po_set *)es;\n    int i;\n\n    dmsg(D_EVENT_WAIT, \"PO_DEL ev=%d\", (int)event);\n\n    ASSERT(!pos->fast);\n    for (i = 0; i < pos->n_events; ++i)\n    {\n        if (pos->events[i].fd == event)\n        {\n            int j;\n            for (j = i; j < pos->n_events - 1; ++j)\n            {\n                pos->events[j] = pos->events[j + 1];\n                pos->args[j] = pos->args[j + 1];\n            }\n            --pos->n_events;\n            break;\n        }\n    }\n}\n\nstatic inline void\npo_set_pollfd_events(struct pollfd *pfdp, unsigned int rwflags)\n{\n    pfdp->events = 0;\n    if (rwflags & EVENT_WRITE)\n    {\n        pfdp->events |= POLLOUT;\n    }\n    if (rwflags & EVENT_READ)\n    {\n        pfdp->events |= (POLLIN | POLLPRI);\n    }\n}\n\nstatic inline bool\npo_append_event(struct po_set *pos, event_t event, unsigned int rwflags, void *arg)\n{\n    if (pos->n_events < pos->capacity)\n    {\n        struct pollfd *pfdp = &pos->events[pos->n_events];\n        pfdp->fd = event;\n        pos->args[pos->n_events] = arg;\n        po_set_pollfd_events(pfdp, rwflags);\n        ++pos->n_events;\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\nstatic void\npo_ctl(struct event_set *es, event_t event, unsigned int rwflags, void *arg)\n{\n    struct po_set *pos = (struct po_set *)es;\n\n    dmsg(D_EVENT_WAIT, \"PO_CTL rwflags=0x%04x ev=%d arg=\" ptr_format, rwflags, (int)event,\n         (ptr_type)arg);\n\n    if (pos->fast)\n    {\n        if (!po_append_event(pos, event, rwflags, arg))\n        {\n            goto err;\n        }\n    }\n    else\n    {\n        int i;\n        for (i = 0; i < pos->n_events; ++i)\n        {\n            struct pollfd *pfdp = &pos->events[i];\n            if (pfdp->fd == event)\n            {\n                pos->args[i] = arg;\n                po_set_pollfd_events(pfdp, rwflags);\n                goto done;\n            }\n        }\n        if (!po_append_event(pos, event, rwflags, arg))\n        {\n            goto err;\n        }\n    }\n\ndone:\n    return;\n\nerr:\n    msg(D_EVENT_ERRORS, \"Error: poll: too many I/O wait events\");\n}\n\nstatic int\npo_wait(struct event_set *es, const struct timeval *tv, struct event_set_return *out, int outlen)\n{\n    struct po_set *pos = (struct po_set *)es;\n    int stat;\n\n    stat = poll(pos->events, pos->n_events, tv_to_ms_timeout(tv));\n\n    ASSERT(stat <= pos->n_events);\n\n    if (stat > 0)\n    {\n        int i, j = 0;\n        const struct pollfd *pfdp = pos->events;\n        for (i = 0; i < pos->n_events && j < outlen; ++i)\n        {\n            if (pfdp->revents & (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLOUT))\n            {\n                out->rwflags = 0;\n                if (pfdp->revents & (POLLIN | POLLPRI | POLLERR | POLLHUP))\n                {\n                    out->rwflags |= EVENT_READ;\n                }\n                if (pfdp->revents & POLLOUT)\n                {\n                    out->rwflags |= EVENT_WRITE;\n                }\n                out->arg = pos->args[i];\n                dmsg(D_EVENT_WAIT,\n                     \"PO_WAIT[%d,%d] fd=%d rev=0x%08x rwflags=0x%04x arg=\" ptr_format \" %s\", i, j,\n                     pfdp->fd, pfdp->revents, out->rwflags, (ptr_type)out->arg,\n                     pos->fast ? \"\" : \"[scalable]\");\n                ++out;\n                ++j;\n            }\n            else if (pfdp->revents)\n            {\n                msg(D_EVENT_ERRORS, \"Error: poll: unknown revents=0x%04x for fd=%d\",\n                    (unsigned int)pfdp->revents, pfdp->fd);\n            }\n            ++pfdp;\n        }\n        return j;\n    }\n    return stat;\n}\n\nstatic struct event_set *\npo_init(int *maxevents, unsigned int flags)\n{\n    struct po_set *pos;\n\n    dmsg(D_EVENT_WAIT, \"PO_INIT maxevents=%d flags=0x%08x\", *maxevents, flags);\n\n    ALLOC_OBJ_CLEAR(pos, struct po_set);\n\n    /* set dispatch functions */\n    pos->func.free = po_free;\n    pos->func.reset = po_reset;\n    pos->func.del = po_del;\n    pos->func.ctl = po_ctl;\n    pos->func.wait = po_wait;\n\n    if (flags & EVENT_METHOD_FAST)\n    {\n        pos->fast = true;\n    }\n\n    pos->n_events = 0;\n\n    /* Figure our event capacity */\n    ASSERT(*maxevents > 0);\n    pos->capacity = *maxevents;\n\n    /* Allocate space for pollfd structures to be passed to poll() */\n    ALLOC_ARRAY_CLEAR(pos->events, struct pollfd, pos->capacity);\n\n    /* Allocate space for event_set_return objects */\n    ALLOC_ARRAY_CLEAR(pos->args, void *, pos->capacity);\n\n    return (struct event_set *)pos;\n}\n#endif /* POLL */\n\n#if SELECT\n\nstruct se_set\n{\n    struct event_set_functions func;\n    bool fast;\n    fd_set readfds;\n    fd_set writefds;\n    void **args;  /* allocated to capacity size */\n    int maxfd;    /* largest fd seen so far, always < capacity */\n    int capacity; /* fixed largest fd + 1 */\n};\n\nstatic void\nse_free(struct event_set *es)\n{\n    struct se_set *ses = (struct se_set *)es;\n    free(ses->args);\n    free(ses);\n}\n\nstatic void\nse_reset(struct event_set *es)\n{\n    struct se_set *ses = (struct se_set *)es;\n    int i;\n    ASSERT(ses->fast);\n\n    dmsg(D_EVENT_WAIT, \"SE_RESET\");\n\n    FD_ZERO(&ses->readfds);\n    FD_ZERO(&ses->writefds);\n    for (i = 0; i <= ses->maxfd; ++i)\n    {\n        ses->args[i] = NULL;\n    }\n    ses->maxfd = -1;\n}\n\nstatic void\nse_del(struct event_set *es, event_t event)\n{\n    struct se_set *ses = (struct se_set *)es;\n    ASSERT(!ses->fast);\n\n    dmsg(D_EVENT_WAIT, \"SE_DEL ev=%d\", (int)event);\n\n    if (event >= 0 && event < ses->capacity)\n    {\n        FD_CLR(event, &ses->readfds);\n        FD_CLR(event, &ses->writefds);\n        ses->args[event] = NULL;\n    }\n    else\n    {\n        msg(D_EVENT_ERRORS, \"Error: select/se_del: too many I/O wait events\");\n    }\n    return;\n}\n\nstatic void\nse_ctl(struct event_set *es, event_t event, unsigned int rwflags, void *arg)\n{\n    struct se_set *ses = (struct se_set *)es;\n\n    dmsg(D_EVENT_WAIT, \"SE_CTL rwflags=0x%04x ev=%d fast=%d cap=%d maxfd=%d arg=\" ptr_format,\n         rwflags, (int)event, (int)ses->fast, ses->capacity, ses->maxfd, (ptr_type)arg);\n\n    if (event >= 0 && event < ses->capacity)\n    {\n        ses->maxfd = max_int(event, ses->maxfd);\n        ses->args[event] = arg;\n        if (ses->fast)\n        {\n            if (rwflags & EVENT_READ)\n            {\n                openvpn_fd_set(event, &ses->readfds);\n            }\n            if (rwflags & EVENT_WRITE)\n            {\n                openvpn_fd_set(event, &ses->writefds);\n            }\n        }\n        else\n        {\n            if (rwflags & EVENT_READ)\n            {\n                openvpn_fd_set(event, &ses->readfds);\n            }\n            else\n            {\n                FD_CLR(event, &ses->readfds);\n            }\n            if (rwflags & EVENT_WRITE)\n            {\n                openvpn_fd_set(event, &ses->writefds);\n            }\n            else\n            {\n                FD_CLR(event, &ses->writefds);\n            }\n        }\n    }\n    else\n    {\n        msg(D_EVENT_ERRORS, \"Error: select: too many I/O wait events, fd=%d cap=%d\", (int)event,\n            ses->capacity);\n    }\n}\n\nstatic int\nse_wait_return(struct se_set *ses, fd_set *read, fd_set *write, struct event_set_return *out,\n               int outlen)\n{\n    int i, j = 0;\n    for (i = 0; i <= ses->maxfd && j < outlen; ++i)\n    {\n        const bool r = FD_ISSET(i, read);\n        const bool w = FD_ISSET(i, write);\n        if (r || w)\n        {\n            out->rwflags = 0;\n            if (r)\n            {\n                out->rwflags |= EVENT_READ;\n            }\n            if (w)\n            {\n                out->rwflags |= EVENT_WRITE;\n            }\n            out->arg = ses->args[i];\n            dmsg(D_EVENT_WAIT, \"SE_WAIT[%d,%d] rwflags=0x%04x arg=\" ptr_format, i, j, out->rwflags,\n                 (ptr_type)out->arg);\n            ++out;\n            ++j;\n        }\n    }\n    return j;\n}\n\nstatic int\nse_wait_fast(struct event_set *es, const struct timeval *tv, struct event_set_return *out,\n             int outlen)\n{\n    struct se_set *ses = (struct se_set *)es;\n    struct timeval tv_tmp = *tv;\n    int stat;\n\n    dmsg(D_EVENT_WAIT, \"SE_WAIT_FAST maxfd=%d tv=%\" PRIi64 \"/%ld\", ses->maxfd,\n         (int64_t)tv_tmp.tv_sec, (long)tv_tmp.tv_usec);\n\n    stat = select(ses->maxfd + 1, &ses->readfds, &ses->writefds, NULL, &tv_tmp);\n\n    if (stat > 0)\n    {\n        stat = se_wait_return(ses, &ses->readfds, &ses->writefds, out, outlen);\n    }\n\n    return stat;\n}\n\nstatic int\nse_wait_scalable(struct event_set *es, const struct timeval *tv, struct event_set_return *out,\n                 int outlen)\n{\n    struct se_set *ses = (struct se_set *)es;\n    struct timeval tv_tmp = *tv;\n    fd_set read = ses->readfds;\n    fd_set write = ses->writefds;\n    int stat;\n\n    dmsg(D_EVENT_WAIT, \"SE_WAIT_SCALEABLE maxfd=%d tv=%\" PRIi64 \"/%ld\", ses->maxfd,\n         (int64_t)tv_tmp.tv_sec, (long)tv_tmp.tv_usec);\n\n    stat = select(ses->maxfd + 1, &read, &write, NULL, &tv_tmp);\n\n    if (stat > 0)\n    {\n        stat = se_wait_return(ses, &read, &write, out, outlen);\n    }\n\n    return stat;\n}\n\nstatic struct event_set *\nse_init(int *maxevents, unsigned int flags)\n{\n    struct se_set *ses;\n\n    dmsg(D_EVENT_WAIT, \"SE_INIT maxevents=%d flags=0x%08x\", *maxevents, flags);\n\n    ALLOC_OBJ_CLEAR(ses, struct se_set);\n\n    /* set dispatch functions */\n    ses->func.free = se_free;\n    ses->func.reset = se_reset;\n    ses->func.del = se_del;\n    ses->func.ctl = se_ctl;\n    ses->func.wait = se_wait_scalable;\n\n    if (flags & EVENT_METHOD_FAST)\n    {\n        ses->fast = true;\n        ses->func.wait = se_wait_fast;\n    }\n\n    /* Select needs to be passed this value + 1 */\n    ses->maxfd = -1;\n\n    /* Set our event capacity */\n    ASSERT(*maxevents > 0);\n    *maxevents = min_int(*maxevents, SELECT_MAX_FDS);\n    ses->capacity = SELECT_MAX_FDS;\n\n    /* Allocate space for event_set_return void * args */\n    ALLOC_ARRAY_CLEAR(ses->args, void *, ses->capacity);\n\n    return (struct event_set *)ses;\n}\n#endif /* SELECT */\n\nstatic struct event_set *\nevent_set_init_simple(int *maxevents, unsigned int flags)\n{\n    struct event_set *ret = NULL;\n#ifdef _WIN32\n    ret = we_init(maxevents, flags);\n#elif POLL && SELECT\n#if 0 /* Define to 1 if EVENT_METHOD_US_TIMEOUT should cause select to be favored over poll */\n    if (flags & EVENT_METHOD_US_TIMEOUT)\n    {\n        ret = se_init(maxevents, flags);\n    }\n#endif\n#ifdef SELECT_PREFERRED_OVER_POLL\n    if (!ret)\n    {\n        ret = se_init(maxevents, flags);\n    }\n    if (!ret)\n    {\n        ret = po_init(maxevents, flags);\n    }\n#else /* ifdef SELECT_PREFERRED_OVER_POLL */\n    if (!ret)\n    {\n        ret = po_init(maxevents, flags);\n    }\n    if (!ret)\n    {\n        ret = se_init(maxevents, flags);\n    }\n#endif\n#elif POLL\n    ret = po_init(maxevents, flags);\n#elif SELECT\n    ret = se_init(maxevents, flags);\n#else  /* ifdef _WIN32 */\n#error At least one of poll, select, or WSAWaitForMultipleEvents must be supported by the kernel\n#endif /* ifdef _WIN32 */\n    ASSERT(ret);\n    return ret;\n}\n\nstatic struct event_set *\nevent_set_init_scalable(int *maxevents, unsigned int flags)\n{\n    struct event_set *ret = NULL;\n#if EPOLL\n    ret = ep_init(maxevents, flags);\n    if (!ret)\n    {\n        msg(M_WARN, \"Note: sys_epoll API is unavailable, falling back to poll/select API\");\n        ret = event_set_init_simple(maxevents, flags);\n    }\n#else /* if EPOLL */\n    ret = event_set_init_simple(maxevents, flags);\n#endif\n    ASSERT(ret);\n    return ret;\n}\n\nstruct event_set *\nevent_set_init(int *maxevents, unsigned int flags)\n{\n    if (flags & EVENT_METHOD_FAST)\n    {\n        return event_set_init_simple(maxevents, flags);\n    }\n    else\n    {\n        return event_set_init_scalable(maxevents, flags);\n    }\n}\n"
  },
  {
    "path": "src/openvpn/event.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef EVENT_H\n#define EVENT_H\n\n#include \"win32.h\"\n#include \"sig.h\"\n\n/*\n * rwflags passed to event_ctl and returned by\n * struct event_set_return.\n */\n#define READ_SHIFT  0\n#define WRITE_SHIFT 1\n\n#define EVENT_UNDEF 4\n#define EVENT_READ  (1u << READ_SHIFT)\n#define EVENT_WRITE (1u << WRITE_SHIFT)\n\n/* event flags returned by io_wait.\n *\n * All these events are defined as bits in a bitfield.\n * Each event 'type' owns two bits in the bitfield: one for the READ\n * event and one for the WRITE event.\n *\n * For this reason, the specific event bit is calculated by adding\n * the event type identifier (always a multiple of 2, as defined\n * below) to 0 for READ and 1 for WRITE.\n *\n * E.g.\n * MANAGEMENT_SHIFT = 6;  <---- event type identifier\n * MANAGEMENT_READ = (1 << (6 + 0)),  <---- READ event\n * MANAGEMENT_WRITE = (1 << (6 + 1))  <---- WRITE event\n *\n * 'error' and 'file_close' are special and use read/write for different\n * signals.\n */\n\n#define SOCKET_SHIFT     0\n#define SOCKET_READ      (1 << (SOCKET_SHIFT + READ_SHIFT))\n#define SOCKET_WRITE     (1 << (SOCKET_SHIFT + WRITE_SHIFT))\n#define TUN_SHIFT        2\n#define TUN_READ         (1 << (TUN_SHIFT + READ_SHIFT))\n#define TUN_WRITE        (1 << (TUN_SHIFT + WRITE_SHIFT))\n#define ERR_SHIFT        4\n#define ES_ERROR         (1 << (ERR_SHIFT + READ_SHIFT))\n#define ES_TIMEOUT       (1 << (ERR_SHIFT + WRITE_SHIFT))\n#define MANAGEMENT_SHIFT 6\n#define MANAGEMENT_READ  (1 << (MANAGEMENT_SHIFT + READ_SHIFT))\n#define MANAGEMENT_WRITE (1 << (MANAGEMENT_SHIFT + WRITE_SHIFT))\n#define DCO_SHIFT        10\n#define DCO_READ         (1 << (DCO_SHIFT + READ_SHIFT))\n#define DCO_WRITE        (1 << (DCO_SHIFT + WRITE_SHIFT))\n\n/*\n * Initialization flags passed to event_set_init\n */\n#define EVENT_METHOD_US_TIMEOUT (1 << 0)\n#define EVENT_METHOD_FAST       (1 << 1)\n\n/*\n * The following constant is used as boundary between integer value\n * and real addresses when passing arguments to event handlers as (void *)\n */\n#define MULTI_N ((void *)16) /* upper bound on MTCP_x */\n\n#ifdef _WIN32\n\ntypedef const struct rw_handle *event_t;\n\n#define UNDEFINED_EVENT (NULL)\n\n#else /* ifdef _WIN32 */\n\ntypedef int event_t;\n\n#define UNDEFINED_EVENT (-1)\n\n#endif\n\nstruct event_set;\nstruct event_set_return;\n\nstruct event_set_functions\n{\n    void (*free)(struct event_set *es);\n    void (*reset)(struct event_set *es);\n    void (*del)(struct event_set *es, event_t event);\n    void (*ctl)(struct event_set *es, event_t event, unsigned int rwflags, void *arg);\n\n    /*\n     * Return status for wait:\n     * -1 on signal or error\n     * 0 on timeout\n     * length of event_set_return if at least 1 event is returned\n     */\n    int (*wait)(struct event_set *es, const struct timeval *tv, struct event_set_return *out,\n                int outlen);\n};\n\nstruct event_set_return\n{\n    unsigned int rwflags;\n    void *arg;\n};\n\nstruct event_set\n{\n    struct event_set_functions func;\n};\n\ntypedef enum\n{\n    EVENT_ARG_MULTI_INSTANCE = 0,\n    EVENT_ARG_LINK_SOCKET,\n} event_arg_t;\n\n/* generic event argument object to pass to event_ctl() */\nstruct event_arg\n{\n    event_arg_t type;\n    union\n    {\n        struct multi_instance *mi; /* if type = EVENT_ARG_MULTI_INSTANCE */\n        struct link_socket *sock;  /* if type = EVENT_ARG_LINK_SOCKET */\n    } u;\n};\n\n/*\n * maxevents on input:  desired max number of event_t descriptors\n *                      simultaneously set with event_ctl\n * maxevents on output: may be modified down, depending on limitations\n *                      of underlying API\n * flags:               EVENT_METHOD_x flags\n */\nstruct event_set *event_set_init(int *maxevents, unsigned int flags);\n\nstatic inline void\nevent_free(struct event_set *es)\n{\n    if (es)\n    {\n        (*es->func.free)(es);\n    }\n}\n\nstatic inline void\nevent_reset(struct event_set *es)\n{\n    (*es->func.reset)(es);\n}\n\nstatic inline void\nevent_del(struct event_set *es, event_t event)\n{\n    (*es->func.del)(es, event);\n}\n\nstatic inline void\nevent_ctl(struct event_set *es, event_t event, unsigned int rwflags, void *arg)\n{\n    (*es->func.ctl)(es, event, rwflags, arg);\n}\n\nstatic inline int\nevent_wait(struct event_set *es, const struct timeval *tv, struct event_set_return *out, int outlen)\n{\n    int ret;\n    ret = (*es->func.wait)(es, tv, out, outlen);\n    return ret;\n}\n\nstatic inline void\nevent_set_return_init(struct event_set_return *esr)\n{\n    esr->rwflags = 0;\n    esr->arg = NULL;\n}\n\n#ifdef _WIN32\n\nstatic inline void\nwait_signal(struct event_set *es, void *arg)\n{\n    if (HANDLE_DEFINED(win32_signal.in.read))\n    {\n        event_ctl(es, &win32_signal.in, EVENT_READ, arg);\n    }\n}\n\n#else /* ifdef _WIN32 */\n\nstatic inline void\nwait_signal(struct event_set *es, void *arg)\n{\n}\n\n#endif\n\n#endif /* ifndef EVENT_H */\n"
  },
  {
    "path": "src/openvpn/fdmisc.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"fdmisc.h\"\n#include \"error.h\"\n\n#include \"memdbg.h\"\n\n/* Set a file descriptor to non-blocking */\nbool\nset_nonblock_action(socket_descriptor_t fd)\n{\n#ifdef _WIN32\n    u_long arg = 1;\n    if (ioctlsocket(fd, FIONBIO, &arg))\n    {\n        return false;\n    }\n#else /* ifdef _WIN32 */\n    if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)\n    {\n        return false;\n    }\n#endif\n    return true;\n}\n\n/* Set a file descriptor to not be passed across execs */\nbool\nset_cloexec_action(socket_descriptor_t fd)\n{\n#ifndef _WIN32\n    if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0)\n    {\n        return false;\n    }\n#endif\n    return true;\n}\n\n/* Set a file descriptor to non-blocking */\nvoid\nset_nonblock(socket_descriptor_t fd)\n{\n    if (!set_nonblock_action(fd))\n    {\n        msg(M_ERR, \"Set socket to non-blocking mode failed\");\n    }\n}\n\n/* Set a file descriptor to not be passed across execs */\nvoid\nset_cloexec(socket_descriptor_t fd)\n{\n    if (!set_cloexec_action(fd))\n    {\n        msg(M_ERR, \"Set FD_CLOEXEC flag on file descriptor failed\");\n    }\n}\n"
  },
  {
    "path": "src/openvpn/fdmisc.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef FD_MISC_H\n#define FD_MISC_H\n\n#include \"basic.h\"\n#include \"error.h\"\n#include \"syshead.h\"\n\nbool set_nonblock_action(socket_descriptor_t fd);\n\nbool set_cloexec_action(socket_descriptor_t fd);\n\nvoid set_nonblock(socket_descriptor_t fd);\n\nvoid set_cloexec(socket_descriptor_t fd);\n\nstatic inline void\nopenvpn_fd_set(socket_descriptor_t fd, fd_set *setp)\n{\n#ifndef _WIN32 /* The Windows FD_SET() implementation does not overflow */\n    ASSERT(fd >= 0 && fd < FD_SETSIZE);\n#endif\n    FD_SET(fd, setp);\n}\n#undef FD_SET /* prevent direct use of FD_SET() */\n\n#endif        /* FD_MISC_H */\n"
  },
  {
    "path": "src/openvpn/forward.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"forward.h\"\n#include \"init.h\"\n#include \"push.h\"\n#include \"gremlin.h\"\n#include \"mss.h\"\n#include \"event.h\"\n#include \"occ.h\"\n#include \"otime.h\"\n#include \"ping.h\"\n#include \"ps.h\"\n#include \"dhcp.h\"\n#include \"common.h\"\n#include \"ssl_verify.h\"\n#include \"dco.h\"\n#include \"auth_token.h\"\n#include \"tun_afunix.h\"\n\n#include \"memdbg.h\"\n\ncounter_type link_read_bytes_global;  /* GLOBAL */\ncounter_type link_write_bytes_global; /* GLOBAL */\n\n/* show event wait debugging info */\n\n#ifdef ENABLE_DEBUG\n\nstatic const char *\nwait_status_string(struct context *c, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(64, gc);\n\n    buf_printf(&out, \"I/O WAIT %s|%s| %s\", tun_stat(c->c1.tuntap, EVENT_READ, gc),\n               tun_stat(c->c1.tuntap, EVENT_WRITE, gc), tv_string(&c->c2.timeval, gc));\n    for (int i = 0; i < c->c1.link_sockets_num; i++)\n    {\n        buf_printf(&out, \"\\n %s|%s\", socket_stat(c->c2.link_sockets[i], EVENT_READ, gc),\n                   socket_stat(c->c2.link_sockets[i], EVENT_WRITE, gc));\n    }\n    return BSTR(&out);\n}\n\nstatic void\nshow_wait_status(struct context *c)\n{\n    struct gc_arena gc = gc_new();\n    dmsg(D_EVENT_WAIT, \"%s\", wait_status_string(c, &gc));\n    gc_free(&gc);\n}\n\n#endif /* ifdef ENABLE_DEBUG */\n\nstatic void\ncheck_tls_errors_co(struct context *c)\n{\n    msg(D_STREAM_ERRORS, \"Fatal TLS error (check_tls_errors_co), restarting\");\n    register_signal(c->sig, c->c2.tls_exit_signal, \"tls-error\"); /* SOFT-SIGUSR1 -- TLS error */\n}\n\nstatic void\ncheck_tls_errors_nco(struct context *c)\n{\n    register_signal(c->sig, c->c2.tls_exit_signal, \"tls-error\"); /* SOFT-SIGUSR1 -- TLS error */\n}\n\n/*\n * TLS errors are fatal in TCP mode.\n * Also check for --tls-exit trigger.\n */\nstatic inline void\ncheck_tls_errors(struct context *c)\n{\n    if (c->c2.tls_multi && c->c2.tls_exit_signal)\n    {\n        if (link_socket_connection_oriented(c->c2.link_sockets[0]))\n        {\n            if (c->c2.tls_multi->n_soft_errors)\n            {\n                check_tls_errors_co(c);\n            }\n        }\n        else\n        {\n            if (c->c2.tls_multi->n_hard_errors)\n            {\n                check_tls_errors_nco(c);\n            }\n        }\n    }\n}\n\n/*\n * Set our wakeup to 0 seconds, so we will be rescheduled\n * immediately.\n */\nstatic inline void\ncontext_immediate_reschedule(struct context *c)\n{\n    c->c2.timeval.tv_sec = 0; /* ZERO-TIMEOUT */\n    c->c2.timeval.tv_usec = 0;\n}\n\nstatic inline void\ncontext_reschedule_sec(struct context *c, time_t sec)\n{\n    if (sec < 0)\n    {\n        sec = 0;\n    }\n    if (sec < c->c2.timeval.tv_sec)\n    {\n        c->c2.timeval.tv_sec = (tv_sec_t)sec;\n        c->c2.timeval.tv_usec = 0;\n    }\n}\n\nvoid\ncheck_dco_key_status(struct context *c)\n{\n    /* DCO context is not yet initialised or enabled */\n    if (!dco_enabled(&c->options))\n    {\n        return;\n    }\n\n    /* no active peer (p2p tls-server mode) */\n    if (c->c2.tls_multi->dco_peer_id == -1)\n    {\n        return;\n    }\n\n    if (!dco_update_keys(&c->c1.tuntap->dco, c->c2.tls_multi))\n    {\n        /* Something bad happened. Kill the connection to\n         * be able to recover. */\n        register_signal(c->sig, SIGUSR1, \"dco update keys error\");\n    }\n}\n\n/*\n * In TLS mode, let TLS level respond to any control-channel\n * packets which were received, or prepare any packets for\n * transmission.\n *\n * tmp_int is purely an optimization that allows us to call\n * tls_multi_process less frequently when there's not much\n * traffic on the control-channel.\n *\n */\nstatic void\ncheck_tls(struct context *c)\n{\n    interval_t wakeup = BIG_TIMEOUT;\n\n    if (interval_test(&c->c2.tmp_int))\n    {\n        const int tmp_status = tls_multi_process(\n            c->c2.tls_multi, &c->c2.to_link, &c->c2.to_link_addr, get_link_socket_info(c), &wakeup);\n\n        if (tmp_status == TLSMP_RECONNECT)\n        {\n            event_timeout_init(&c->c2.wait_for_connect, 1, now);\n            reset_coarse_timers(c);\n        }\n\n        if (tmp_status == TLSMP_ACTIVE || tmp_status == TLSMP_RECONNECT)\n        {\n            update_time();\n            interval_action(&c->c2.tmp_int);\n        }\n        else if (tmp_status == TLSMP_KILL)\n        {\n            if (c->options.mode == MODE_SERVER)\n            {\n                send_auth_failed(c, c->c2.tls_multi->client_reason);\n            }\n            else\n            {\n                register_signal(c->sig, SIGTERM, \"auth-control-exit\");\n            }\n        }\n\n        interval_future_trigger(&c->c2.tmp_int, wakeup);\n    }\n\n    interval_schedule_wakeup(&c->c2.tmp_int, &wakeup);\n\n    /*\n     * Our current code has no good hooks in the TLS machinery to update\n     * DCO keys. So we check the key status after the whole TLS machinery\n     * has been completed and potentially update them\n     *\n     * We have a hidden state transition from secondary to primary key based\n     * on ks->auth_deferred_expire that DCO needs to check that the normal\n     * TLS state engine does not check. So we call the \\c check_dco_key_status\n     * function even if tmp_status does not indicate that something has changed.\n     */\n    check_dco_key_status(c);\n\n    if (wakeup)\n    {\n        context_reschedule_sec(c, wakeup);\n    }\n}\n\nstatic void\nparse_incoming_control_channel_command(struct context *c, struct buffer *buf)\n{\n    if (buf_string_match_head_str(buf, \"AUTH_FAILED\"))\n    {\n        receive_auth_failed(c, buf);\n    }\n    else if (buf_string_match_head_str(buf, \"PUSH_\"))\n    {\n        incoming_push_message(c, buf);\n    }\n    else if (buf_string_match_head_str(buf, \"RESTART\"))\n    {\n        server_pushed_signal(c, buf, true, 7);\n    }\n    else if (buf_string_match_head_str(buf, \"HALT\"))\n    {\n        server_pushed_signal(c, buf, false, 4);\n    }\n    else if (buf_string_match_head_str(buf, \"INFO_PRE\"))\n    {\n        server_pushed_info(buf, 8);\n    }\n    else if (buf_string_match_head_str(buf, \"INFO\"))\n    {\n        server_pushed_info(buf, 4);\n    }\n    else if (buf_string_match_head_str(buf, \"CR_RESPONSE\"))\n    {\n        receive_cr_response(c, buf);\n    }\n    else if (buf_string_match_head_str(buf, \"AUTH_PENDING\"))\n    {\n        receive_auth_pending(c, buf);\n    }\n    else if (buf_string_match_head_str(buf, \"EXIT\"))\n    {\n        receive_exit_message(c);\n    }\n    else\n    {\n        msg(D_PUSH_ERRORS, \"WARNING: Received unknown control message: %s\", BSTR(buf));\n    }\n}\n\n/*\n * Handle incoming configuration\n * messages on the control channel.\n */\nstatic void\ncheck_incoming_control_channel(struct context *c)\n{\n    int len = tls_test_payload_len(c->c2.tls_multi);\n    /* We should only be called with len >0 */\n    ASSERT(len > 0);\n\n    struct gc_arena gc = gc_new();\n    struct buffer buf = alloc_buf_gc(len, &gc);\n    if (tls_rec_payload(c->c2.tls_multi, &buf))\n    {\n        while (BLEN(&buf) > 1)\n        {\n            struct buffer cmdbuf = extract_command_buffer(&buf, &gc);\n\n            if (cmdbuf.len > 0)\n            {\n                parse_incoming_control_channel_command(c, &cmdbuf);\n            }\n        }\n    }\n    else\n    {\n        msg(D_PUSH_ERRORS, \"WARNING: Receive control message failed\");\n    }\n\n    gc_free(&gc);\n}\n\n/*\n * Periodically resend PUSH_REQUEST until PUSH message received\n */\nstatic void\ncheck_push_request(struct context *c)\n{\n    send_push_request(c);\n\n    /* if no response to first push_request, retry at PUSH_REQUEST_INTERVAL second intervals */\n    event_timeout_modify_wakeup(&c->c2.push_request_interval, PUSH_REQUEST_INTERVAL);\n}\n\n/*\n * Things that need to happen immediately after connection initiation should go here.\n *\n * Options like --up-delay need to be triggered by this function which\n * checks for connection establishment.\n *\n * Note: The process_incoming_push_reply currently assumes that this function\n * only sets up the pull request timer when pull is enabled.\n */\nstatic void\ncheck_connection_established(struct context *c)\n{\n    if (connection_established(c))\n    {\n        /* if --pull was specified, send a push request to server */\n        if (c->c2.tls_multi && c->options.pull)\n        {\n#ifdef ENABLE_MANAGEMENT\n            if (management)\n            {\n                management_set_state(management, OPENVPN_STATE_GET_CONFIG, NULL, NULL, NULL, NULL,\n                                     NULL);\n            }\n#endif\n            /* fire up push request right away (already 1s delayed) */\n            /* We might receive a AUTH_PENDING request before we armed this\n             * timer. In that case we don't change the value */\n            if (c->c2.push_request_timeout < now)\n            {\n                c->c2.push_request_timeout = now + c->options.handshake_window;\n            }\n            event_timeout_init(&c->c2.push_request_interval, 0, now);\n            reset_coarse_timers(c);\n        }\n        else\n        {\n            if (!do_up(c, false, 0))\n            {\n                register_signal(c->sig, SIGUSR1, \"connection initialisation failed\");\n            }\n        }\n\n        event_timeout_clear(&c->c2.wait_for_connect);\n    }\n}\n\nbool\nsend_control_channel_string_dowork(struct tls_session *session, const char *str,\n                                   msglvl_t msglevel)\n{\n    struct gc_arena gc = gc_new();\n    bool stat;\n\n    ASSERT(session);\n    struct key_state *ks = &session->key[KS_PRIMARY];\n\n    /* buffered cleartext write onto TLS control channel */\n    stat = tls_send_payload(ks, (uint8_t *)str, strlen(str) + 1);\n\n    msg(msglevel, \"SENT CONTROL [%s]: '%s' (status=%d)\",\n        session->common_name ? session->common_name : \"UNDEF\", sanitize_control_message(str, &gc),\n        (int)stat);\n\n    gc_free(&gc);\n    return stat;\n}\n\nvoid\nreschedule_multi_process(struct context *c)\n{\n    interval_action(&c->c2.tmp_int);\n    context_immediate_reschedule(c); /* ZERO-TIMEOUT */\n}\n\nbool\nsend_control_channel_string(struct context *c, const char *str, msglvl_t msglevel)\n{\n    if (c->c2.tls_multi)\n    {\n        struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];\n        bool ret = send_control_channel_string_dowork(session, str, msglevel);\n        reschedule_multi_process(c);\n\n        return ret;\n    }\n    return true;\n}\n/*\n * Add routes.\n */\n\nstatic void\ncheck_add_routes_action(struct context *c, const bool errors)\n{\n    bool route_status = do_route(&c->options, c->c1.route_list, c->c1.route_ipv6_list, c->c1.tuntap,\n                                 c->plugins, c->c2.es, &c->net_ctx);\n\n    int flags = (errors ? ISC_ERRORS : 0);\n    flags |= (!route_status ? ISC_ROUTE_ERRORS : 0);\n\n    update_time();\n    event_timeout_clear(&c->c2.route_wakeup);\n    event_timeout_clear(&c->c2.route_wakeup_expire);\n    initialization_sequence_completed(c, flags); /* client/p2p --route-delay was defined */\n}\n\nstatic void\ncheck_add_routes(struct context *c)\n{\n    if (test_routes(c->c1.route_list, c->c1.tuntap))\n    {\n        check_add_routes_action(c, false);\n    }\n    else if (event_timeout_trigger(&c->c2.route_wakeup_expire, &c->c2.timeval, ETT_DEFAULT))\n    {\n        check_add_routes_action(c, true);\n    }\n    else\n    {\n        msg(D_ROUTE, \"Route: Waiting for TUN/TAP interface to come up...\");\n        if (c->c1.tuntap)\n        {\n            if (!tun_standby(c->c1.tuntap))\n            {\n                register_signal(c->sig, SIGHUP, \"ip-fail\");\n                c->persist.restart_sleep_seconds = 10;\n#ifdef _WIN32\n                show_routes(M_INFO | M_NOPREFIX);\n                show_adapters(M_INFO | M_NOPREFIX);\n#endif\n            }\n        }\n        update_time();\n        if (c->c2.route_wakeup.n != 1)\n        {\n            event_timeout_init(&c->c2.route_wakeup, 1, now);\n        }\n        event_timeout_reset(&c->c2.ping_rec_interval);\n    }\n}\n\n/*\n * Should we exit due to inactivity timeout?\n *\n * In the non-dco case, the timeout is reset via register_activity()\n * whenever there is sufficient activity on tun or link, so this function\n * is only ever called to raise the TERM signal.\n *\n * With DCO, OpenVPN does not see incoming or outgoing data packets anymore\n * and the logic needs to change - we permit the event to trigger and check\n * kernel DCO counters here, returning and rearming the timer if there was\n * sufficient traffic.\n *\n * NOTE: FreeBSD DCO does not supply \"tun bytes\" (= decrypted payload) today,\n * so \"dco bytes\" (encrypted bytes, including keepalives) is used instead\n */\nstatic void\ncheck_inactivity_timeout(struct context *c)\n{\n    if (dco_enabled(&c->options) && dco_get_peer_stats(c, true) == 0)\n    {\n#ifdef TARGET_FREEBSD\n        int64_t tot_bytes = c->c2.dco_read_bytes + c->c2.dco_write_bytes;\n#else\n        int64_t tot_bytes = c->c2.tun_read_bytes + c->c2.tun_write_bytes;\n#endif\n        int64_t new_bytes = tot_bytes - c->c2.inactivity_bytes;\n\n        if (new_bytes > c->options.inactivity_minimum_bytes)\n        {\n            c->c2.inactivity_bytes = tot_bytes;\n            event_timeout_reset(&c->c2.inactivity_interval);\n            return;\n        }\n    }\n\n    msg(M_INFO, \"Inactivity timeout (--inactive), exiting\");\n    register_signal(c->sig, SIGTERM, \"inactive\");\n}\n\nint\nget_server_poll_remaining_time(struct event_timeout *server_poll_timeout)\n{\n    update_time();\n    int remaining = event_timeout_remaining(server_poll_timeout);\n    return max_int(0, remaining);\n}\n\nstatic void\ncheck_server_poll_timeout(struct context *c)\n{\n    event_timeout_reset(&c->c2.server_poll_interval);\n    ASSERT(c->c2.tls_multi);\n    if (!tls_initial_packet_received(c->c2.tls_multi))\n    {\n        msg(M_INFO, \"Server poll timeout, restarting\");\n        register_signal(c->sig, SIGUSR1, \"server_poll\");\n        c->persist.restart_sleep_seconds = -1;\n    }\n}\n\n/*\n * Schedule a SIGTERM signal c->options.scheduled_exit_interval seconds from now.\n */\nbool\nschedule_exit(struct context *c)\n{\n    const int n_seconds = c->options.scheduled_exit_interval;\n    /* don't reschedule if already scheduled. */\n    if (event_timeout_defined(&c->c2.scheduled_exit))\n    {\n        return false;\n    }\n    tls_set_single_session(c->c2.tls_multi);\n    update_time();\n    reset_coarse_timers(c);\n    event_timeout_init(&c->c2.scheduled_exit, n_seconds, now);\n    c->c2.scheduled_exit_signal = SIGTERM;\n    msg(D_SCHED_EXIT, \"Delayed exit in %d seconds\", n_seconds);\n    return true;\n}\n\n/*\n * Scheduled exit?\n */\nstatic void\ncheck_scheduled_exit(struct context *c)\n{\n    register_signal(c->sig, c->c2.scheduled_exit_signal, \"delayed-exit\");\n}\n\n/*\n * Should we write timer-triggered status file.\n */\nstatic void\ncheck_status_file(struct context *c)\n{\n    if (c->c1.status_output)\n    {\n        print_status(c, c->c1.status_output);\n    }\n}\n\n#ifdef ENABLE_FRAGMENT\n/*\n * Should we deliver a datagram fragment to remote?\n * c is expected to be a single-link context (p2p or child)\n */\nstatic void\ncheck_fragment(struct context *c)\n{\n    struct link_socket_info *lsi = get_link_socket_info(c);\n\n    /* OS MTU Hint? */\n    if (lsi->mtu_changed && lsi->lsa)\n    {\n        frame_adjust_path_mtu(c);\n        lsi->mtu_changed = false;\n    }\n\n    if (fragment_outgoing_defined(c->c2.fragment))\n    {\n        if (!c->c2.to_link.len)\n        {\n            /* encrypt a fragment for output to TCP/UDP port */\n            ASSERT(fragment_ready_to_send(c->c2.fragment, &c->c2.buf, &c->c2.frame_fragment));\n            encrypt_sign(c, false);\n        }\n    }\n\n    fragment_housekeeping(c->c2.fragment, &c->c2.frame_fragment, &c->c2.timeval);\n}\n#endif /* ifdef ENABLE_FRAGMENT */\n\n/*\n * Buffer reallocation, for use with null encryption.\n */\nstatic inline void\nbuffer_turnover(const uint8_t *orig_buf, struct buffer *dest_stub, struct buffer *src_stub,\n                struct buffer *storage)\n{\n    if (orig_buf == src_stub->data && src_stub->data != storage->data)\n    {\n        buf_assign(storage, src_stub);\n        *dest_stub = *storage;\n    }\n    else\n    {\n        *dest_stub = *src_stub;\n    }\n}\n\n/*\n * Compress, fragment, encrypt and HMAC-sign an outgoing packet.\n * Input: c->c2.buf\n * Output: c->c2.to_link\n */\nvoid\nencrypt_sign(struct context *c, bool comp_frag)\n{\n    struct context_buffers *b = c->c2.buffers;\n    const uint8_t *orig_buf = c->c2.buf.data;\n    struct crypto_options *co = NULL;\n\n    if (dco_enabled(&c->options))\n    {\n        msg(M_WARN, \"Attempting to send data packet while data channel offload is in use. \"\n                    \"Dropping packet\");\n        c->c2.buf.len = 0;\n    }\n\n    /*\n     * Drop non-TLS outgoing packet if client-connect script/plugin\n     * has not yet succeeded. In non-TLS tls_multi mode is not defined\n     * and we always pass packets.\n     */\n    if (c->c2.tls_multi && c->c2.tls_multi->multi_state < CAS_CONNECT_DONE)\n    {\n        c->c2.buf.len = 0;\n    }\n\n    if (comp_frag)\n    {\n#ifdef USE_COMP\n        /* Compress the packet. */\n        if (c->c2.comp_context)\n        {\n            (*c->c2.comp_context->alg.compress)(&c->c2.buf, b->compress_buf, c->c2.comp_context,\n                                                &c->c2.frame);\n        }\n#endif\n#ifdef ENABLE_FRAGMENT\n        if (c->c2.fragment)\n        {\n            fragment_outgoing(c->c2.fragment, &c->c2.buf, &c->c2.frame_fragment);\n        }\n#endif\n    }\n\n    /* initialize work buffer with buf.headroom bytes of prepend capacity */\n    ASSERT(buf_init(&b->encrypt_buf, c->c2.frame.buf.headroom));\n\n    if (c->c2.tls_multi)\n    {\n        /* Get the key we will use to encrypt the packet. */\n        tls_pre_encrypt(c->c2.tls_multi, &c->c2.buf, &co);\n        /* If using P_DATA_V2, prepend the 1-byte opcode and 3-byte peer-id to the\n         * packet before openvpn_encrypt(), so we can authenticate the opcode too.\n         */\n        if (c->c2.buf.len > 0 && c->c2.tls_multi->use_peer_id)\n        {\n            tls_prepend_opcode_v2(c->c2.tls_multi, &b->encrypt_buf);\n        }\n    }\n    else\n    {\n        co = &c->c2.crypto_options;\n    }\n\n    /* Encrypt and authenticate the packet */\n    openvpn_encrypt(&c->c2.buf, b->encrypt_buf, co);\n\n    /* Do packet administration */\n    if (c->c2.tls_multi)\n    {\n        if (c->c2.buf.len > 0 && !c->c2.tls_multi->use_peer_id)\n        {\n            tls_prepend_opcode_v1(c->c2.tls_multi, &c->c2.buf);\n        }\n        tls_post_encrypt(c->c2.tls_multi, &c->c2.buf);\n    }\n\n    /*\n     * Get the address we will be sending the packet to.\n     */\n    link_socket_get_outgoing_addr(&c->c2.buf, get_link_socket_info(c), &c->c2.to_link_addr);\n\n    /* if null encryption, copy result to read_tun_buf */\n    buffer_turnover(orig_buf, &c->c2.to_link, &c->c2.buf, &b->read_tun_buf);\n}\n\n/*\n * Should we exit due to session timeout?\n */\nstatic void\ncheck_session_timeout(struct context *c)\n{\n    if (c->options.session_timeout\n        && event_timeout_trigger(&c->c2.session_interval, &c->c2.timeval, ETT_DEFAULT))\n    {\n        msg(M_INFO, \"Session timeout, exiting\");\n        register_signal(c->sig, SIGTERM, \"session-timeout\");\n    }\n}\n\n/*\n * Coarse timers work to 1 second resolution.\n */\nstatic void\nprocess_coarse_timers(struct context *c)\n{\n    /* flush current packet-id to file once per 60\n     * seconds if --replay-persist was specified */\n    if (packet_id_persist_enabled(&c->c1.pid_persist)\n        && event_timeout_trigger(&c->c2.packet_id_persist_interval, &c->c2.timeval, ETT_DEFAULT))\n    {\n        packet_id_persist_save(&c->c1.pid_persist);\n    }\n\n    /* Should we write timer-triggered status file */\n    if (c->c1.status_output\n        && event_timeout_trigger(&c->c1.status_output->et, &c->c2.timeval, ETT_DEFAULT))\n    {\n        check_status_file(c);\n    }\n\n    /* process connection establishment items */\n    if (event_timeout_trigger(&c->c2.wait_for_connect, &c->c2.timeval, ETT_DEFAULT))\n    {\n        check_connection_established(c);\n    }\n\n    /* see if we should send a push_request (option --pull) */\n    if (event_timeout_trigger(&c->c2.push_request_interval, &c->c2.timeval, ETT_DEFAULT))\n    {\n        check_push_request(c);\n    }\n\n    /* process --route options */\n    if (event_timeout_trigger(&c->c2.route_wakeup, &c->c2.timeval, ETT_DEFAULT))\n    {\n        check_add_routes(c);\n    }\n\n    /* check if we want to refresh the auth-token */\n    if (event_timeout_trigger(&c->c2.auth_token_renewal_interval, &c->c2.timeval, ETT_DEFAULT))\n    {\n        check_send_auth_token(c);\n    }\n\n    /* possibly exit due to --inactive */\n    if (c->options.inactivity_timeout\n        && event_timeout_trigger(&c->c2.inactivity_interval, &c->c2.timeval, ETT_DEFAULT))\n    {\n        check_inactivity_timeout(c);\n    }\n\n    if (c->sig->signal_received)\n    {\n        return;\n    }\n\n    /* kill session if time is over */\n    check_session_timeout(c);\n    if (c->sig->signal_received)\n    {\n        return;\n    }\n\n    /* restart if ping not received */\n    check_ping_restart(c);\n    if (c->sig->signal_received)\n    {\n        return;\n    }\n\n    if (c->c2.tls_multi)\n    {\n        if (c->options.ce.connect_timeout\n            && event_timeout_trigger(&c->c2.server_poll_interval, &c->c2.timeval, ETT_DEFAULT))\n        {\n            check_server_poll_timeout(c);\n        }\n        if (c->sig->signal_received)\n        {\n            return;\n        }\n        if (event_timeout_trigger(&c->c2.scheduled_exit, &c->c2.timeval, ETT_DEFAULT))\n        {\n            check_scheduled_exit(c);\n        }\n        if (c->sig->signal_received)\n        {\n            return;\n        }\n    }\n\n    /* Should we send an OCC_REQUEST message? */\n    check_send_occ_req(c);\n\n    /* Should we send an MTU load test? */\n    check_send_occ_load_test(c);\n\n    /* Should we send an OCC_EXIT message to remote? */\n    if (c->c2.explicit_exit_notification_time_wait)\n    {\n        process_explicit_exit_notification_timer_wakeup(c);\n    }\n\n    /* Should we ping the remote? */\n    check_ping_send(c);\n\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        management_check_bytecount_client(c, management, &c->c2.timeval);\n    }\n#endif /* ENABLE_MANAGEMENT */\n}\n\nstatic void\ncheck_coarse_timers(struct context *c)\n{\n    if (now < c->c2.coarse_timer_wakeup)\n    {\n        context_reschedule_sec(c, c->c2.coarse_timer_wakeup - now);\n        return;\n    }\n\n    const struct timeval save = c->c2.timeval;\n    c->c2.timeval.tv_sec = BIG_TIMEOUT;\n    c->c2.timeval.tv_usec = 0;\n    process_coarse_timers(c);\n    c->c2.coarse_timer_wakeup = now + c->c2.timeval.tv_sec;\n\n    dmsg(D_INTERVAL, \"TIMER: coarse timer wakeup %\" PRIi64 \" seconds\",\n         (int64_t)c->c2.timeval.tv_sec);\n\n    /* Is the coarse timeout NOT the earliest one? */\n    if (c->c2.timeval.tv_sec > save.tv_sec)\n    {\n        c->c2.timeval = save;\n    }\n}\n\nstatic void\ncheck_timeout_random_component_dowork(struct context *c)\n{\n    const int update_interval = 10; /* seconds */\n    c->c2.update_timeout_random_component = now + update_interval;\n    c->c2.timeout_random_component.tv_usec = (time_t)get_random() & 0x0003FFFF;\n    c->c2.timeout_random_component.tv_sec = 0;\n\n    dmsg(D_INTERVAL, \"RANDOM USEC=%ld\", (long)c->c2.timeout_random_component.tv_usec);\n}\n\nstatic inline void\ncheck_timeout_random_component(struct context *c)\n{\n    if (now >= c->c2.update_timeout_random_component)\n    {\n        check_timeout_random_component_dowork(c);\n    }\n    if (c->c2.timeval.tv_sec >= 1)\n    {\n        tv_add(&c->c2.timeval, &c->c2.timeout_random_component);\n    }\n}\n\n/*\n * Handle addition and removal of the 10-byte Socks5 header\n * in UDP packets.\n */\n\nstatic inline void\nsocks_postprocess_incoming_link(struct context *c, struct link_socket *sock)\n{\n    if (sock->socks_proxy && sock->info.proto == PROTO_UDP)\n    {\n        socks_process_incoming_udp(&c->c2.buf, &c->c2.from);\n    }\n}\n\nstatic inline void\nsocks_preprocess_outgoing_link(struct context *c, struct link_socket *sock,\n                               struct link_socket_actual **to_addr, int *size_delta)\n{\n    if (sock->socks_proxy && sock->info.proto == PROTO_UDP)\n    {\n        *size_delta += socks_process_outgoing_udp(&c->c2.to_link, c->c2.to_link_addr);\n        *to_addr = &sock->socks_relay;\n    }\n}\n\n/* undo effect of socks_preprocess_outgoing_link */\nstatic inline void\nlink_socket_write_post_size_adjust(int *size, int size_delta, struct buffer *buf)\n{\n    if (size_delta > 0 && *size > size_delta)\n    {\n        *size -= size_delta;\n        if (!buf_advance(buf, size_delta))\n        {\n            *size = 0;\n        }\n    }\n}\n\n/*\n * Output: c->c2.buf\n */\n\nvoid\nread_incoming_link(struct context *c, struct link_socket *sock)\n{\n    /*\n     * Set up for recvfrom call to read datagram\n     * sent to our TCP/UDP port.\n     */\n    int status;\n\n    /*ASSERT (!c->c2.to_tun.len);*/\n\n    c->c2.buf = c->c2.buffers->read_link_buf;\n    ASSERT(buf_init(&c->c2.buf, c->c2.frame.buf.headroom));\n\n    status = link_socket_read(sock, &c->c2.buf, &c->c2.from);\n\n    if (socket_connection_reset(sock, status))\n    {\n#if PORT_SHARE\n        if (port_share && socket_foreign_protocol_detected(sock))\n        {\n            const struct buffer *fbuf = socket_foreign_protocol_head(sock);\n            const int sd = socket_foreign_protocol_sd(sock);\n            port_share_redirect(port_share, fbuf, sd);\n            register_signal(c->sig, SIGTERM, \"port-share-redirect\");\n        }\n        else\n#endif\n        {\n            /* received a disconnect from a connection-oriented protocol */\n            if (event_timeout_defined(&c->c2.explicit_exit_notification_interval))\n            {\n                msg(D_STREAM_ERRORS,\n                    \"Connection reset during exit notification period, ignoring [%d]\", status);\n                management_sleep(1);\n            }\n            else\n            {\n                register_signal(c->sig, SIGUSR1,\n                                \"connection-reset\"); /* SOFT-SIGUSR1 -- TCP connection reset */\n                msg(D_STREAM_ERRORS, \"Connection reset, restarting [%d]\", status);\n            }\n        }\n        return;\n    }\n\n    /* check_status() call below resets last-error code */\n    bool dco_win_timeout = tuntap_is_dco_win_timeout(c->c1.tuntap, status);\n\n    /* check recvfrom status */\n    check_status(status, \"read\", sock, NULL);\n\n    if (dco_win_timeout)\n    {\n        trigger_ping_timeout_signal(c);\n    }\n\n    /* Remove socks header if applicable */\n    socks_postprocess_incoming_link(c, sock);\n}\n\nbool\nprocess_incoming_link_part1(struct context *c, struct link_socket_info *lsi, bool floated)\n{\n    struct gc_arena gc = gc_new();\n    bool decrypt_status = false;\n\n    if (c->c2.buf.len > 0)\n    {\n        c->c2.link_read_bytes += c->c2.buf.len;\n        link_read_bytes_global += c->c2.buf.len;\n        c->c2.original_recv_size = c->c2.buf.len;\n    }\n    else\n    {\n        c->c2.original_recv_size = 0;\n    }\n\n#ifdef ENABLE_DEBUG\n    /* take action to corrupt packet if we are in gremlin test mode */\n    if (c->options.gremlin)\n    {\n        if (!ask_gremlin(c->options.gremlin))\n        {\n            c->c2.buf.len = 0;\n        }\n        corrupt_gremlin(&c->c2.buf, c->options.gremlin);\n    }\n#endif\n\n    /* log incoming packet */\n#ifdef LOG_RW\n    if (c->c2.log_rw && c->c2.buf.len > 0)\n    {\n        fprintf(stderr, \"R\");\n    }\n#endif\n    msg(D_LINK_RW, \"%s READ [%d] from %s: %s\", proto2ascii(lsi->proto, lsi->af, true),\n        BLEN(&c->c2.buf), print_link_socket_actual(&c->c2.from, &gc), PROTO_DUMP(&c->c2.buf, &gc));\n\n    /*\n     * Good, non-zero length packet received.\n     * Commence multi-stage processing of packet,\n     * such as authenticate, decrypt, decompress.\n     * If any stage fails, it sets buf.len to 0 or -1,\n     * telling downstream stages to ignore the packet.\n     */\n    if (c->c2.buf.len > 0)\n    {\n        struct crypto_options *co = NULL;\n        const uint8_t *ad_start = NULL;\n        if (!link_socket_verify_incoming_addr(&c->c2.buf, lsi, &c->c2.from))\n        {\n            link_socket_bad_incoming_addr(&c->c2.buf, lsi, &c->c2.from);\n        }\n\n        if (c->c2.tls_multi)\n        {\n            uint8_t opcode = *BPTR(&c->c2.buf) >> P_OPCODE_SHIFT;\n\n            /*\n             * If DCO is enabled, the kernel drivers require that the\n             * other end only sends P_DATA_V2 packets. V1 are unknown\n             * to kernel and passed to userland, but we cannot handle them\n             * either because crypto context is missing - so drop the packet.\n             *\n             * This can only happen with particular old (2.4.0-2.4.4) servers.\n             */\n            if ((opcode == P_DATA_V1) && dco_enabled(&c->options))\n            {\n                msg(D_LINK_ERRORS, \"Data Channel Offload doesn't support DATA_V1 packets. \"\n                                   \"Upgrade your server to 2.4.5 or newer.\");\n                c->c2.buf.len = 0;\n            }\n\n            /*\n             * If tls_pre_decrypt returns true, it means the incoming\n             * packet was a good TLS control channel packet.  If so, TLS code\n             * will deal with the packet and set buf.len to 0 so downstream\n             * stages ignore it.\n             *\n             * If the packet is a data channel packet, tls_pre_decrypt\n             * will load crypto_options with the correct encryption key\n             * and return false.\n             */\n            if (tls_pre_decrypt(c->c2.tls_multi, &c->c2.from, &c->c2.buf, &co, floated, &ad_start))\n            {\n                interval_action(&c->c2.tmp_int);\n\n                /* reset packet received timer if TLS packet */\n                if (c->options.ping_rec_timeout)\n                {\n                    event_timeout_reset(&c->c2.ping_rec_interval);\n                }\n            }\n        }\n        else\n        {\n            co = &c->c2.crypto_options;\n        }\n\n        /*\n         * Drop non-TLS packet if client-connect script/plugin and cipher selection\n         * has not yet succeeded. In non-TLS mode tls_multi is not defined\n         * and we always pass packets.\n         */\n        if (c->c2.tls_multi && c->c2.tls_multi->multi_state < CAS_CONNECT_DONE)\n        {\n            c->c2.buf.len = 0;\n        }\n\n        /* authenticate and decrypt the incoming packet */\n        decrypt_status =\n            openvpn_decrypt(&c->c2.buf, c->c2.buffers->decrypt_buf, co, &c->c2.frame, ad_start);\n\n        if (!decrypt_status\n            /* on the instance context we have only one socket, so just check the first one */\n            && link_socket_connection_oriented(c->c2.link_sockets[0]))\n        {\n            /* decryption errors are fatal in TCP mode */\n            register_signal(c->sig, SIGUSR1,\n                            \"decryption-error\"); /* SOFT-SIGUSR1 -- decryption error in TCP mode */\n            msg(D_STREAM_ERRORS, \"Fatal decryption error (process_incoming_link), restarting\");\n        }\n    }\n    else\n    {\n        buf_reset(&c->c2.to_tun);\n    }\n    gc_free(&gc);\n\n    return decrypt_status;\n}\n\nvoid\nprocess_incoming_link_part2(struct context *c, struct link_socket_info *lsi,\n                            const uint8_t *orig_buf)\n{\n    if (c->c2.buf.len > 0)\n    {\n#ifdef ENABLE_FRAGMENT\n        if (c->c2.fragment)\n        {\n            fragment_incoming(c->c2.fragment, &c->c2.buf, &c->c2.frame_fragment);\n        }\n#endif\n\n#ifdef USE_COMP\n        /* decompress the incoming packet */\n        if (c->c2.comp_context)\n        {\n            (*c->c2.comp_context->alg.decompress)(&c->c2.buf, c->c2.buffers->decompress_buf,\n                                                  c->c2.comp_context, &c->c2.frame);\n        }\n#endif\n\n#ifdef PACKET_TRUNCATION_CHECK\n        /* if (c->c2.buf.len > 1) --c->c2.buf.len; */\n        ipv4_packet_size_verify(BPTR(&c->c2.buf), BLEN(&c->c2.buf), TUNNEL_TYPE(c->c1.tuntap),\n                                \"POST_DECRYPT\", &c->c2.n_trunc_post_decrypt);\n#endif\n\n        /*\n         * Set our \"official\" outgoing address, since\n         * if buf.len is non-zero, we know the packet\n         * authenticated.  In TLS mode we do nothing\n         * because TLS mode takes care of source address\n         * authentication.\n         *\n         * Also, update the persisted version of our packet-id.\n         */\n        if (!TLS_MODE(c) && c->c2.buf.len > 0)\n        {\n            link_socket_set_outgoing_addr(lsi, &c->c2.from, NULL, c->c2.es);\n        }\n\n        /* reset packet received timer */\n        if (c->options.ping_rec_timeout && c->c2.buf.len > 0)\n        {\n            event_timeout_reset(&c->c2.ping_rec_interval);\n        }\n\n        /* increment authenticated receive byte count */\n        if (c->c2.buf.len > 0)\n        {\n            c->c2.link_read_bytes_auth += c->c2.buf.len;\n            c->c2.max_recv_size_local =\n                max_int(c->c2.original_recv_size, c->c2.max_recv_size_local);\n        }\n\n        /* Did we just receive an openvpn ping packet? */\n        if (is_ping_msg(&c->c2.buf))\n        {\n            dmsg(D_PING, \"RECEIVED PING PACKET\");\n            c->c2.buf.len = 0; /* drop packet */\n        }\n\n        /* Did we just receive an OCC packet? */\n        if (is_occ_msg(&c->c2.buf))\n        {\n            process_received_occ_msg(c);\n        }\n\n        buffer_turnover(orig_buf, &c->c2.to_tun, &c->c2.buf, &c->c2.buffers->read_link_buf);\n\n        /* to_tun defined + unopened tuntap can cause deadlock */\n        if (!tuntap_defined(c->c1.tuntap))\n        {\n            c->c2.to_tun.len = 0;\n        }\n    }\n    else\n    {\n        buf_reset(&c->c2.to_tun);\n    }\n}\n\nstatic void\nprocess_incoming_link(struct context *c, struct link_socket *sock)\n{\n    struct link_socket_info *lsi = &sock->info;\n    const uint8_t *orig_buf = c->c2.buf.data;\n\n    process_incoming_link_part1(c, lsi, false);\n    process_incoming_link_part2(c, lsi, orig_buf);\n}\n\nvoid\nextract_dco_float_peer_addr(const sa_family_t socket_family, struct openvpn_sockaddr *out_osaddr,\n                            const struct sockaddr *float_sa)\n{\n    if (float_sa->sa_family == AF_INET)\n    {\n        struct sockaddr_in *float4 = (struct sockaddr_in *)float_sa;\n        /* DCO treats IPv4-mapped IPv6 addresses as pure IPv4. However, on a\n         * dual-stack socket, we need to preserve the mapping otherwise openvpn\n         * will not be able to find the peer by its transport address.\n         */\n        if (socket_family == AF_INET6)\n        {\n            out_osaddr->addr.in6.sin6_family = AF_INET6;\n            out_osaddr->addr.in6.sin6_port = float4->sin_port;\n\n            memset(&out_osaddr->addr.in6.sin6_addr.s6_addr, 0, 10);\n            out_osaddr->addr.in6.sin6_addr.s6_addr[10] = 0xff;\n            out_osaddr->addr.in6.sin6_addr.s6_addr[11] = 0xff;\n            memcpy(&out_osaddr->addr.in6.sin6_addr.s6_addr[12], &float4->sin_addr.s_addr,\n                   sizeof(in_addr_t));\n        }\n        else\n        {\n            memcpy(&out_osaddr->addr.in4, float4, sizeof(struct sockaddr_in));\n        }\n    }\n    else\n    {\n        struct sockaddr_in6 *float6 = (struct sockaddr_in6 *)float_sa;\n        memcpy(&out_osaddr->addr.in6, float6, sizeof(struct sockaddr_in6));\n    }\n}\n\nvoid\nprocess_incoming_dco(dco_context_t *dco)\n{\n#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD))\n    struct context *c = dco->c;\n\n    /* FreeBSD currently sends us removal notifcation with the old peer-id in\n     * p2p mode with the ping timeout reason, so ignore that one to not shoot\n     * ourselves in the foot and removing the just established session */\n    if (dco->dco_message_peer_id != c->c2.tls_multi->dco_peer_id)\n    {\n        msg(D_DCO_DEBUG,\n            \"%s: received message for mismatching peer-id %d, \"\n            \"expected %d\",\n            __func__, dco->dco_message_peer_id, c->c2.tls_multi->dco_peer_id);\n        return;\n    }\n\n    switch (dco->dco_message_type)\n    {\n        case OVPN_CMD_DEL_PEER:\n            /* peer is gone, unset ID to prevent more kernel calls */\n            c->c2.tls_multi->dco_peer_id = -1;\n            if (dco->dco_del_peer_reason == OVPN_DEL_PEER_REASON_EXPIRED)\n            {\n                msg(D_DCO_DEBUG,\n                    \"%s: received peer expired notification of for peer-id \"\n                    \"%d\",\n                    __func__, dco->dco_message_peer_id);\n                trigger_ping_timeout_signal(c);\n                return;\n            }\n            break;\n\n        case OVPN_CMD_SWAP_KEYS:\n            msg(D_DCO_DEBUG, \"%s: received key rotation notification for peer-id %d\", __func__,\n                dco->dco_message_peer_id);\n            tls_session_soft_reset(c->c2.tls_multi);\n            break;\n\n        default:\n            msg(D_DCO_DEBUG, \"%s: received message of type %u - ignoring\", __func__,\n                dco->dco_message_type);\n            return;\n    }\n\n#endif /* if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD)) */\n}\n\n/*\n * Output: c->c2.buf\n */\n\nvoid\nread_incoming_tun(struct context *c)\n{\n    /*\n     * Setup for read() call on TUN/TAP device.\n     */\n    /*ASSERT (!c->c2.to_link.len);*/\n\n    c->c2.buf = c->c2.buffers->read_tun_buf;\n\n#ifdef _WIN32\n    /* we cannot end up here when using dco */\n    ASSERT(!dco_enabled(&c->options));\n\n    sockethandle_t sh = { .is_handle = true, .h = c->c1.tuntap->hand, .prepend_sa = false };\n    sockethandle_finalize(sh, &c->c1.tuntap->reads, &c->c2.buf, NULL);\n#else  /* ifdef _WIN32 */\n    ASSERT(buf_init(&c->c2.buf, c->c2.frame.buf.headroom));\n    ASSERT(buf_safe(&c->c2.buf, c->c2.frame.buf.payload_size));\n    if (c->c1.tuntap->backend_driver == DRIVER_AFUNIX)\n    {\n        c->c2.buf.len =\n            (int)read_tun_afunix(c->c1.tuntap, BPTR(&c->c2.buf), c->c2.frame.buf.payload_size);\n    }\n    else\n    {\n        c->c2.buf.len = (int)read_tun(c->c1.tuntap, BPTR(&c->c2.buf), c->c2.frame.buf.payload_size);\n    }\n#endif /* ifdef _WIN32 */\n\n#ifdef PACKET_TRUNCATION_CHECK\n    ipv4_packet_size_verify(BPTR(&c->c2.buf), BLEN(&c->c2.buf), TUNNEL_TYPE(c->c1.tuntap),\n                            \"READ_TUN\", &c->c2.n_trunc_tun_read);\n#endif\n\n    /* Was TUN/TAP interface stopped? */\n    if (tuntap_stop(c->c2.buf.len))\n    {\n        register_signal(c->sig, SIGTERM, \"tun-stop\");\n        msg(M_INFO, \"TUN/TAP interface has been stopped, exiting\");\n        return;\n    }\n\n    /* Was TUN/TAP I/O operation aborted? */\n    if (tuntap_abort(c->c2.buf.len))\n    {\n        register_signal(c->sig, SIGHUP, \"tun-abort\");\n        c->persist.restart_sleep_seconds = 10;\n        msg(M_INFO, \"TUN/TAP I/O operation aborted, restarting\");\n        return;\n    }\n\n    /* Check the status return from read() */\n    check_status(c->c2.buf.len, \"read from TUN/TAP\", NULL, c->c1.tuntap);\n}\n\n/**\n * Drops UDP packets which OS decided to route via tun.\n *\n * On Windows and OS X when netwotk adapter is disabled or\n * disconnected, platform starts to use tun as external interface.\n * When packet is sent to tun, it comes to openvpn, encapsulated\n * and sent to routing table, which sends it again to tun.\n */\nstatic void\ndrop_if_recursive_routing(struct context *c, struct buffer *buf)\n{\n    if (c->c2.to_link_addr == NULL) /* no remote addr known */\n    {\n        return;\n    }\n\n    struct openvpn_sockaddr *link_addr = &c->c2.to_link_addr->dest;\n    struct link_socket_info *lsi = get_link_socket_info(c);\n\n    int ip_hdr_offset = 0;\n    int tun_ip_ver = get_tun_ip_ver(TUNNEL_TYPE(c->c1.tuntap), buf, &ip_hdr_offset);\n\n    if (tun_ip_ver == 4)\n    {\n        /* Ensure we can safely read the IPv4 header */\n        const int min_ip_header = ip_hdr_offset + sizeof(struct openvpn_iphdr);\n        if (BLEN(buf) < min_ip_header)\n        {\n            return;\n        }\n\n        struct openvpn_iphdr *pip = (struct openvpn_iphdr *)(BPTR(buf) + ip_hdr_offset);\n        const int ip_hlen = OPENVPN_IPH_GET_LEN(pip->version_len);\n        /* Reject malformed or truncated headers */\n        if (ip_hlen < (int)sizeof(struct openvpn_iphdr)\n            || BLENZ(buf) < ip_hdr_offset + ip_hlen + sizeof(uint16_t) * 2)\n        {\n            return;\n        }\n\n        /* skip ipv4 packets for ipv6 tun */\n        if (link_addr->addr.sa.sa_family != AF_INET)\n        {\n            return;\n        }\n\n        /* skip if tun protocol doesn't match link protocol */\n        if ((lsi->proto == PROTO_TCP && pip->protocol != OPENVPN_IPPROTO_TCP)\n            || (lsi->proto == PROTO_UDP && pip->protocol != OPENVPN_IPPROTO_UDP))\n        {\n            return;\n        }\n\n        /* drop packets with same dest addr and port as remote */\n        uint8_t *l4_hdr = (uint8_t *)pip + ip_hlen;\n\n        uint16_t link_port = ntohs(link_addr->addr.in4.sin_port);\n\n        /* TCP and UDP ports are at the same place in the header, and other protocols\n         * can not happen here due to the lsi->proto check above */\n        uint16_t src_port = ntohs(*(uint16_t *)l4_hdr);\n        uint16_t dst_port = ntohs(*(uint16_t *)(l4_hdr + sizeof(uint16_t)));\n        if ((memcmp(&link_addr->addr.in4.sin_addr.s_addr, &pip->daddr, sizeof(pip->daddr)) == 0) && (link_port == dst_port))\n        {\n            buf->len = 0;\n\n            struct gc_arena gc = gc_new();\n            msg(D_LOW, \"Recursive routing detected, packet dropped %s:%\" PRIu16 \" -> %s\",\n                print_in_addr_t(pip->saddr, IA_NET_ORDER, &gc),\n                src_port,\n                print_link_socket_actual(c->c2.to_link_addr, &gc));\n            gc_free(&gc);\n        }\n    }\n    else if (tun_ip_ver == 6)\n    {\n        /* make sure we got whole IPv6 header and TCP/UDP src/dst ports */\n        const int min_ipv6 = ip_hdr_offset + sizeof(struct openvpn_ipv6hdr) + sizeof(uint16_t) * 2;\n        if (BLEN(buf) < min_ipv6)\n        {\n            return;\n        }\n\n        /* skip ipv6 packets for ipv4 tun */\n        if (link_addr->addr.sa.sa_family != AF_INET6)\n        {\n            return;\n        }\n\n        struct openvpn_ipv6hdr *pip6 = (struct openvpn_ipv6hdr *)(BPTR(buf) + ip_hdr_offset);\n\n        /* skip if tun protocol doesn't match link protocol */\n        if ((lsi->proto == PROTO_TCP && pip6->nexthdr != OPENVPN_IPPROTO_TCP)\n            || (lsi->proto == PROTO_UDP && pip6->nexthdr != OPENVPN_IPPROTO_UDP))\n        {\n            return;\n        }\n\n        uint16_t link_port = ntohs(link_addr->addr.in6.sin6_port);\n\n        /* drop packets with same dest addr and port as remote */\n        uint8_t *l4_hdr = (uint8_t *)pip6 + sizeof(struct openvpn_ipv6hdr);\n        uint16_t src_port = ntohs(*(uint16_t *)l4_hdr);\n        uint16_t dst_port = ntohs(*(uint16_t *)(l4_hdr + sizeof(uint16_t)));\n        if ((OPENVPN_IN6_ARE_ADDR_EQUAL(&link_addr->addr.in6.sin6_addr, &pip6->daddr)) && (link_port == dst_port))\n        {\n            buf->len = 0;\n\n            struct gc_arena gc = gc_new();\n            msg(D_LOW, \"Recursive routing detected, packet dropped %s:%\" PRIu16 \" -> %s\",\n                print_in6_addr(pip6->saddr, IA_NET_ORDER, &gc),\n                src_port,\n                print_link_socket_actual(c->c2.to_link_addr, &gc));\n            gc_free(&gc);\n        }\n    }\n}\n\n/*\n * Input:  c->c2.buf\n * Output: c->c2.to_link\n */\n\nvoid\nprocess_incoming_tun(struct context *c, struct link_socket *out_sock)\n{\n    if (c->c2.buf.len > 0)\n    {\n        c->c2.tun_read_bytes += c->c2.buf.len;\n    }\n\n#ifdef LOG_RW\n    if (c->c2.log_rw && c->c2.buf.len > 0)\n    {\n        fprintf(stderr, \"r\");\n    }\n#endif\n\n    /* Show packet content */\n    dmsg(D_TUN_RW, \"TUN READ [%d]\", BLEN(&c->c2.buf));\n\n    if (c->c2.buf.len > 0)\n    {\n        if ((c->options.mode == MODE_POINT_TO_POINT) && (!c->options.allow_recursive_routing))\n        {\n            drop_if_recursive_routing(c, &c->c2.buf);\n        }\n        /*\n         * The --passtos and --mssfix options require\n         * us to examine the IP header (IPv4 or IPv6).\n         */\n        unsigned int flags =\n            PIPV4_PASSTOS | PIP_MSSFIX | PIPV4_CLIENT_NAT | PIPV6_ICMP_NOHOST_CLIENT;\n        process_ip_header(c, flags, &c->c2.buf, out_sock);\n\n#ifdef PACKET_TRUNCATION_CHECK\n        /* if (c->c2.buf.len > 1) --c->c2.buf.len; */\n        ipv4_packet_size_verify(BPTR(&c->c2.buf), BLEN(&c->c2.buf), TUNNEL_TYPE(c->c1.tuntap),\n                                \"PRE_ENCRYPT\", &c->c2.n_trunc_pre_encrypt);\n#endif\n    }\n    if (c->c2.buf.len > 0)\n    {\n        encrypt_sign(c, true);\n    }\n    else\n    {\n        buf_reset(&c->c2.to_link);\n    }\n}\n\n/**\n * Forges a IPv6 ICMP packet with a no route to host error code from the\n * IPv6 packet in buf and sends it directly back to the client via the tun\n * device when used on a client and via the link if used on the server.\n *\n * @param c         Tunnel context\n * @param buf       The buf containing the packet for which the icmp6\n *                  unreachable should be constructed.\n * @param client    Determines whether to the send packet back via tun or link\n */\nvoid\nipv6_send_icmp_unreachable(struct context *c, struct buffer *buf, bool client)\n{\n#define MAX_ICMPV6LEN 1280\n    struct openvpn_icmp6hdr icmp6out;\n    CLEAR(icmp6out);\n\n    /*\n     * Get a buffer to the ip packet, is_ipv6 automatically forwards\n     * the buffer to the ip packet\n     */\n    struct buffer inputipbuf = *buf;\n\n    is_ipv6(TUNNEL_TYPE(c->c1.tuntap), &inputipbuf);\n\n    if (BLEN(&inputipbuf) < (int)sizeof(struct openvpn_ipv6hdr))\n    {\n        return;\n    }\n\n    const struct openvpn_ipv6hdr *pip6 = (struct openvpn_ipv6hdr *)BPTR(&inputipbuf);\n\n    /* Copy version, traffic class, flow label from input packet */\n    struct openvpn_ipv6hdr pip6out = *pip6;\n\n    pip6out.version_prio = pip6->version_prio;\n    pip6out.daddr = pip6->saddr;\n\n    /*\n     * Use the IPv6 remote address if we have one, otherwise use a fake one\n     * using the remote address is preferred since it makes debugging and\n     * understanding where the ICMPv6 error originates easier\n     */\n    if (c->options.ifconfig_ipv6_remote)\n    {\n        inet_pton(AF_INET6, c->options.ifconfig_ipv6_remote, &pip6out.saddr);\n    }\n    else\n    {\n        inet_pton(AF_INET6, \"fe80::7\", &pip6out.saddr);\n    }\n\n    pip6out.nexthdr = OPENVPN_IPPROTO_ICMPV6;\n\n    /*\n     * The ICMPv6 unreachable code worked best in my (arne) tests with Windows,\n     * Linux and Android. Windows did not like the administratively prohibited\n     * return code (no fast fail)\n     */\n    icmp6out.icmp6_type = OPENVPN_ICMP6_DESTINATION_UNREACHABLE;\n    icmp6out.icmp6_code = OPENVPN_ICMP6_DU_NOROUTE;\n\n    const int icmpheader_len = sizeof(struct openvpn_ipv6hdr) + sizeof(struct openvpn_icmp6hdr);\n    int totalheader_len = icmpheader_len;\n\n    if (TUNNEL_TYPE(c->c1.tuntap) == DEV_TYPE_TAP)\n    {\n        totalheader_len += sizeof(struct openvpn_ethhdr);\n    }\n\n    /*\n     * Calculate size for payload, defined in the standard that the resulting\n     * frame should be <= 1280 and have as much as possible of the original\n     * packet\n     */\n    const int max_payload_size = min_int(MAX_ICMPV6LEN, c->c2.frame.tun_mtu - icmpheader_len);\n    const int payload_len = min_int(max_payload_size, BLEN(&inputipbuf));\n    const uint16_t icmp_len = (uint16_t)(sizeof(struct openvpn_icmp6hdr) + payload_len);\n\n    pip6out.payload_len = htons(icmp_len);\n\n    /* Construct the packet as outgoing packet back to the client */\n    struct buffer *outbuf;\n    if (client)\n    {\n        c->c2.to_tun = c->c2.buffers->aux_buf;\n        outbuf = &(c->c2.to_tun);\n    }\n    else\n    {\n        c->c2.to_link = c->c2.buffers->aux_buf;\n        outbuf = &(c->c2.to_link);\n    }\n    ASSERT(buf_init(outbuf, totalheader_len));\n\n    /* Fill the end of the buffer with original packet */\n    ASSERT(buf_safe(outbuf, payload_len));\n    ASSERT(buf_copy_n(outbuf, &inputipbuf, payload_len));\n\n    /* ICMP Header, copy into buffer to allow checksum calculation */\n    ASSERT(buf_write_prepend(outbuf, &icmp6out, sizeof(struct openvpn_icmp6hdr)));\n\n    /* Calculate checksum over the packet and write to header */\n\n    uint16_t new_csum =\n        ip_checksum(AF_INET6, BPTR(outbuf), BLEN(outbuf), (const uint8_t *)&pip6out.saddr,\n                    (uint8_t *)&pip6out.daddr, OPENVPN_IPPROTO_ICMPV6);\n    ((struct openvpn_icmp6hdr *)BPTR(outbuf))->icmp6_cksum = htons(new_csum);\n\n\n    /* IPv6 Header */\n    ASSERT(buf_write_prepend(outbuf, &pip6out, sizeof(struct openvpn_ipv6hdr)));\n\n    /*\n     * Tap mode, we also need to create an Ethernet header.\n     */\n    if (TUNNEL_TYPE(c->c1.tuntap) == DEV_TYPE_TAP)\n    {\n        if (BLEN(buf) < (int)sizeof(struct openvpn_ethhdr))\n        {\n            return;\n        }\n\n        const struct openvpn_ethhdr *orig_ethhdr = (struct openvpn_ethhdr *)BPTR(buf);\n\n        /* Copy frametype and reverse source/destination for the response */\n        struct openvpn_ethhdr ethhdr;\n        memcpy(ethhdr.source, orig_ethhdr->dest, OPENVPN_ETH_ALEN);\n        memcpy(ethhdr.dest, orig_ethhdr->source, OPENVPN_ETH_ALEN);\n        ethhdr.proto = htons(OPENVPN_ETH_P_IPV6);\n        ASSERT(buf_write_prepend(outbuf, &ethhdr, sizeof(struct openvpn_ethhdr)));\n    }\n#undef MAX_ICMPV6LEN\n}\n\nvoid\nprocess_ip_header(struct context *c, unsigned int flags, struct buffer *buf,\n                  struct link_socket *sock)\n{\n    if (!c->options.ce.mssfix)\n    {\n        flags &= ~PIP_MSSFIX;\n    }\n#if PASSTOS_CAPABILITY\n    if (!c->options.passtos)\n    {\n        flags &= ~PIPV4_PASSTOS;\n    }\n#endif\n    if (!c->options.client_nat)\n    {\n        flags &= ~PIPV4_CLIENT_NAT;\n    }\n    if (!c->options.route_gateway_via_dhcp)\n    {\n        flags &= ~PIPV4_EXTRACT_DHCP_ROUTER;\n    }\n    if (!c->options.block_ipv6)\n    {\n        flags &= ~(PIPV6_ICMP_NOHOST_CLIENT | PIPV6_ICMP_NOHOST_SERVER);\n    }\n\n    if (buf->len > 0)\n    {\n        struct buffer ipbuf = *buf;\n        if (is_ipv4(TUNNEL_TYPE(c->c1.tuntap), &ipbuf))\n        {\n#if PASSTOS_CAPABILITY\n            /* extract TOS from IP header */\n            if (flags & PIPV4_PASSTOS)\n            {\n                link_socket_extract_tos(sock, &ipbuf);\n            }\n#endif\n\n            /* possibly alter the TCP MSS */\n            if (flags & PIP_MSSFIX)\n            {\n                mss_fixup_ipv4(&ipbuf, c->c2.frame.mss_fix);\n            }\n\n            /* possibly do NAT on packet */\n            if ((flags & PIPV4_CLIENT_NAT) && c->options.client_nat)\n            {\n                const int direction = (flags & PIP_OUTGOING) ? CN_INCOMING : CN_OUTGOING;\n                client_nat_transform(c->options.client_nat, &ipbuf, direction);\n            }\n            /* possibly extract a DHCP router message */\n            if (flags & PIPV4_EXTRACT_DHCP_ROUTER)\n            {\n                const in_addr_t dhcp_router = dhcp_extract_router_msg(&ipbuf);\n                if (dhcp_router)\n                {\n                    route_list_add_vpn_gateway(c->c1.route_list, c->c2.es, dhcp_router);\n                }\n            }\n        }\n        else if (is_ipv6(TUNNEL_TYPE(c->c1.tuntap), &ipbuf))\n        {\n            /* possibly alter the TCP MSS */\n            if (flags & PIP_MSSFIX)\n            {\n                mss_fixup_ipv6(&ipbuf, c->c2.frame.mss_fix);\n            }\n            if (!(flags & PIP_OUTGOING)\n                && (flags & (PIPV6_ICMP_NOHOST_CLIENT | PIPV6_ICMP_NOHOST_SERVER)))\n            {\n                ipv6_send_icmp_unreachable(c, buf, (bool)(flags & PIPV6_ICMP_NOHOST_CLIENT));\n                /* Drop the IPv6 packet */\n                buf->len = 0;\n            }\n        }\n    }\n}\n\n/*\n * Input: c->c2.to_link\n */\n\nvoid\nprocess_outgoing_link(struct context *c, struct link_socket *sock)\n{\n    struct gc_arena gc = gc_new();\n    int error_code = 0;\n\n    if (c->c2.to_link.len > 0 && c->c2.to_link.len <= c->c2.frame.buf.payload_size)\n    {\n        /*\n         * Setup for call to send/sendto which will send\n         * packet to remote over the TCP/UDP port.\n         */\n        int size = 0;\n        ASSERT(link_socket_actual_defined(c->c2.to_link_addr));\n\n#ifdef ENABLE_DEBUG\n        /* In gremlin-test mode, we may choose to drop this packet */\n        if (!c->options.gremlin || ask_gremlin(c->options.gremlin))\n#endif\n        {\n            /*\n             * Let the traffic shaper know how many bytes\n             * we wrote.\n             */\n            if (c->options.shaper)\n            {\n                int overhead =\n                    datagram_overhead(c->c2.to_link_addr->dest.addr.sa.sa_family, sock->info.proto);\n                shaper_wrote_bytes(&c->c2.shaper, BLEN(&c->c2.to_link) + overhead);\n            }\n\n            /*\n             * Let the pinger know that we sent a packet.\n             */\n            if (c->options.ping_send_timeout)\n            {\n                event_timeout_reset(&c->c2.ping_send_interval);\n            }\n\n#if PASSTOS_CAPABILITY\n            /* Set TOS */\n            link_socket_set_tos(sock);\n#endif\n\n            /* Log packet send */\n#ifdef LOG_RW\n            if (c->c2.log_rw)\n            {\n                fprintf(stderr, \"W\");\n            }\n#endif\n            msg(D_LINK_RW, \"%s WRITE [%d] to %s: %s\",\n                proto2ascii(sock->info.proto, sock->info.af, true), BLEN(&c->c2.to_link),\n                print_link_socket_actual(c->c2.to_link_addr, &gc), PROTO_DUMP(&c->c2.to_link, &gc));\n\n            /* Packet send complexified by possible Socks5 usage */\n            {\n                struct link_socket_actual *to_addr = c->c2.to_link_addr;\n                int size_delta = 0;\n\n                /* If Socks5 over UDP, prepend header */\n                socks_preprocess_outgoing_link(c, sock, &to_addr, &size_delta);\n\n                /* Send packet */\n                size = (int)link_socket_write(sock, &c->c2.to_link, to_addr);\n\n                /* Undo effect of prepend */\n                link_socket_write_post_size_adjust(&size, size_delta, &c->c2.to_link);\n            }\n\n            if (size > 0)\n            {\n                c->c2.max_send_size_local = max_int(size, c->c2.max_send_size_local);\n                c->c2.link_write_bytes += size;\n                link_write_bytes_global += size;\n            }\n        }\n\n        /* Check return status */\n        error_code = openvpn_errno();\n        check_status(size, \"write\", sock, NULL);\n\n        if (size > 0)\n        {\n            /* Did we write a different size packet than we intended? */\n            if (size != BLEN(&c->c2.to_link))\n            {\n                msg(D_LINK_ERRORS,\n                    \"TCP/UDP packet was truncated/expanded on write to %s (tried=%d,actual=%d)\",\n                    print_link_socket_actual(c->c2.to_link_addr, &gc), BLEN(&c->c2.to_link), size);\n            }\n        }\n\n        /* if not a ping/control message, indicate activity regarding --inactive parameter */\n        if (c->c2.buf.len > 0)\n        {\n            register_activity(c, size);\n        }\n\n        /* for unreachable network and \"connecting\" state switch to the next host */\n\n        bool unreachable = error_code ==\n#ifdef _WIN32\n                           WSAENETUNREACH;\n#else\n                           ENETUNREACH;\n#endif\n        if (size < 0 && unreachable && c->c2.tls_multi\n            && !tls_initial_packet_received(c->c2.tls_multi)\n            && c->options.mode == MODE_POINT_TO_POINT)\n        {\n            msg(M_INFO, \"Network unreachable, restarting\");\n            register_signal(c->sig, SIGUSR1, \"network-unreachable\");\n        }\n    }\n    else\n    {\n        if (c->c2.to_link.len > 0)\n        {\n            msg(D_LINK_ERRORS, \"TCP/UDP packet too large on write to %s (tried=%d,max=%d)\",\n                print_link_socket_actual(c->c2.to_link_addr, &gc), c->c2.to_link.len,\n                c->c2.frame.buf.payload_size);\n        }\n    }\n\n    buf_reset(&c->c2.to_link);\n\n    gc_free(&gc);\n}\n\n/*\n * Input: c->c2.to_tun\n */\n\nvoid\nprocess_outgoing_tun(struct context *c, struct link_socket *in_sock)\n{\n    /*\n     * Set up for write() call to TUN/TAP\n     * device.\n     */\n    if (c->c2.to_tun.len <= 0)\n    {\n        return;\n    }\n\n    /*\n     * The --mssfix option requires\n     * us to examine the IP header (IPv4 or IPv6).\n     */\n    process_ip_header(c, PIP_MSSFIX | PIPV4_EXTRACT_DHCP_ROUTER | PIPV4_CLIENT_NAT | PIP_OUTGOING,\n                      &c->c2.to_tun, in_sock);\n\n    if (c->c2.to_tun.len <= c->c2.frame.buf.payload_size)\n    {\n        /*\n         * Write to TUN/TAP device.\n         */\n        ssize_t size;\n\n#ifdef LOG_RW\n        if (c->c2.log_rw)\n        {\n            fprintf(stderr, \"w\");\n        }\n#endif\n        dmsg(D_TUN_RW, \"TUN WRITE [%d]\", BLEN(&c->c2.to_tun));\n\n#ifdef PACKET_TRUNCATION_CHECK\n        ipv4_packet_size_verify(BPTR(&c->c2.to_tun), BLEN(&c->c2.to_tun), TUNNEL_TYPE(c->c1.tuntap),\n                                \"WRITE_TUN\", &c->c2.n_trunc_tun_write);\n#endif\n\n#ifdef _WIN32\n        size = tun_write_win32(c->c1.tuntap, &c->c2.to_tun);\n#else\n        if (c->c1.tuntap->backend_driver == DRIVER_AFUNIX)\n        {\n            size = write_tun_afunix(c->c1.tuntap, BPTR(&c->c2.to_tun), BLEN(&c->c2.to_tun));\n        }\n        else\n        {\n            size = write_tun(c->c1.tuntap, BPTR(&c->c2.to_tun), BLEN(&c->c2.to_tun));\n        }\n#endif\n\n        if (size > 0)\n        {\n            c->c2.tun_write_bytes += size;\n        }\n        check_status(size, \"write to TUN/TAP\", NULL, c->c1.tuntap);\n\n        /* check written packet size */\n        if (size > 0)\n        {\n            /* Did we write a different size packet than we intended? */\n            if (size != BLEN(&c->c2.to_tun))\n            {\n                msg(D_LINK_ERRORS,\n                    \"TUN/TAP packet was destructively fragmented on write to %s (tried=%d,actual=%zd)\",\n                    c->c1.tuntap->actual_name, BLEN(&c->c2.to_tun), size);\n            }\n\n            /* indicate activity regarding --inactive parameter */\n            register_activity(c, size);\n        }\n    }\n    else\n    {\n        /*\n         * This should never happen, probably indicates some kind\n         * of MTU mismatch.\n         */\n        msg(D_LINK_ERRORS, \"tun packet too large on write (tried=%d,max=%d)\", c->c2.to_tun.len,\n            c->c2.frame.buf.payload_size);\n    }\n\n    buf_reset(&c->c2.to_tun);\n}\n\nvoid\npre_select(struct context *c)\n{\n    /* make sure current time (now) is updated on function entry */\n\n    /*\n     * Start with an effectively infinite timeout, then let it\n     * reduce to a timeout that reflects the component which\n     * needs the earliest service.\n     */\n    c->c2.timeval.tv_sec = BIG_TIMEOUT;\n    c->c2.timeval.tv_usec = 0;\n\n#if defined(_WIN32)\n    if (check_debug_level(D_TAP_WIN_DEBUG))\n    {\n        c->c2.timeval.tv_sec = 1;\n        if (tuntap_defined(c->c1.tuntap))\n        {\n            tun_show_debug(c->c1.tuntap);\n        }\n    }\n#endif\n\n    /* check coarse timers? */\n    check_coarse_timers(c);\n    if (c->sig->signal_received)\n    {\n        return;\n    }\n\n    /* If tls is enabled, do tls control channel packet processing. */\n    if (c->c2.tls_multi)\n    {\n        check_tls(c);\n    }\n\n    /* In certain cases, TLS errors will require a restart */\n    check_tls_errors(c);\n    if (c->sig->signal_received)\n    {\n        return;\n    }\n\n    /* check for incoming control messages on the control channel like\n     * push request/reply/update, or authentication failure and 2FA messages */\n    if (tls_test_payload_len(c->c2.tls_multi) > 0)\n    {\n        check_incoming_control_channel(c);\n    }\n\n    /* Should we send an OCC message? */\n    check_send_occ_msg(c);\n\n#ifdef ENABLE_FRAGMENT\n    /* Should we deliver a datagram fragment to remote? */\n    if (c->c2.fragment)\n    {\n        check_fragment(c);\n    }\n#endif\n\n    /* Update random component of timeout */\n    check_timeout_random_component(c);\n}\n\nstatic void\nmulti_io_process_flags(struct context *c, struct event_set *es, const unsigned int flags,\n                       unsigned int *out_socket, unsigned int *out_tuntap)\n{\n    unsigned int socket = 0;\n    unsigned int tuntap = 0;\n    static uintptr_t tun_shift = TUN_SHIFT;\n    static uintptr_t err_shift = ERR_SHIFT;\n\n    /*\n     * Calculate the flags based on the provided 'flags' argument.\n     */\n    if ((c->options.mode != MODE_SERVER) && (flags & IOW_WAIT_SIGNAL))\n    {\n        wait_signal(es, (void *)err_shift);\n    }\n\n    if (flags & IOW_TO_LINK)\n    {\n        if (flags & IOW_SHAPER)\n        {\n            /*\n             * If sending this packet would put us over our traffic shaping\n             * quota, don't send -- instead compute the delay we must wait\n             * until it will be OK to send the packet.\n             */\n            int delay = 0;\n\n            /* set traffic shaping delay in microseconds */\n            if (c->options.shaper)\n            {\n                delay = max_int(delay, shaper_delay(&c->c2.shaper));\n            }\n\n            if (delay < 1000)\n            {\n                socket |= EVENT_WRITE;\n            }\n            else\n            {\n                shaper_soonest_event(&c->c2.timeval, delay);\n            }\n        }\n        else\n        {\n            socket |= EVENT_WRITE;\n        }\n    }\n    else if (!((flags & IOW_FRAG) && TO_LINK_FRAG(c)))\n    {\n        if (flags & IOW_READ_TUN)\n        {\n            tuntap |= EVENT_READ;\n        }\n    }\n\n    /*\n     * If outgoing data (for TUN/TAP device) pending, wait for ready-to-send status\n     * from device.  Otherwise, wait for incoming data on TCP/UDP port.\n     */\n    if (flags & IOW_TO_TUN)\n    {\n        tuntap |= EVENT_WRITE;\n    }\n    else\n    {\n        if (flags & IOW_READ_LINK)\n        {\n            socket |= EVENT_READ;\n        }\n    }\n\n    /*\n     * outgoing bcast buffer waiting to be sent?\n     */\n    if (flags & IOW_MBUF)\n    {\n        socket |= EVENT_WRITE;\n    }\n\n    /*\n     * Force wait on TUN input, even if also waiting on TCP/UDP output\n     */\n    if (flags & IOW_READ_TUN_FORCE)\n    {\n        tuntap |= EVENT_READ;\n    }\n\n    /*\n     * Configure event wait based on socket, tuntap flags.\n     * (for TCP server sockets this happens in\n     *  socket_set_listen_persistent()).\n     */\n    for (int i = 0; i < c->c1.link_sockets_num; i++)\n    {\n        if ((c->options.mode != MODE_SERVER) || (proto_is_dgram(c->c2.link_sockets[i]->info.proto)))\n        {\n            socket_set(c->c2.link_sockets[i], es, socket, &c->c2.link_sockets[i]->ev_arg, NULL);\n        }\n    }\n\n    tun_set(c->c1.tuntap, es, tuntap, (void *)tun_shift, NULL);\n\n    if (out_socket)\n    {\n        *out_socket = socket;\n    }\n\n    if (out_tuntap)\n    {\n        *out_tuntap = tuntap;\n    }\n}\n\n/*\n * Wait for I/O events.  Used for UDP sockets in\n * point-to-multipoint mode.\n */\n\nvoid\nget_io_flags_udp(struct context *c, struct multi_io *multi_io, const unsigned int flags)\n{\n    unsigned int out_socket;\n\n    multi_io_process_flags(c, multi_io->es, flags, &out_socket, NULL);\n    multi_io->udp_flags = (out_socket << SOCKET_SHIFT);\n}\n\n/*\n * This is the core I/O wait function, used for all I/O waits except\n * for the top-level server sockets.\n */\nvoid\nio_wait(struct context *c, const unsigned int flags)\n{\n    unsigned int out_socket;\n    unsigned int out_tuntap;\n    struct event_set_return esr[4];\n\n    /* These shifts all depend on EVENT_READ and EVENT_WRITE */\n    static uintptr_t socket_shift = SOCKET_SHIFT; /* depends on SOCKET_READ and SOCKET_WRITE */\n#ifdef ENABLE_MANAGEMENT\n    static uintptr_t management_shift =\n        MANAGEMENT_SHIFT; /* depends on MANAGEMENT_READ and MANAGEMENT_WRITE */\n#endif\n\n#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)\n    static uintptr_t dco_shift = DCO_SHIFT; /* Event from DCO linux kernel module */\n#endif\n\n    /*\n     * Decide what kind of events we want to wait for.\n     */\n    event_reset(c->c2.event_set);\n\n    multi_io_process_flags(c, c->c2.event_set, flags, &out_socket, &out_tuntap);\n\n#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)\n    if (c->c1.tuntap)\n    {\n        dco_event_set(&c->c1.tuntap->dco, c->c2.event_set, (void *)dco_shift);\n    }\n#endif\n\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        management_socket_set(management, c->c2.event_set, (void *)management_shift, NULL);\n    }\n#endif\n\n    /*\n     * Possible scenarios:\n     *  (1) tcp/udp port has data available to read\n     *  (2) tcp/udp port is ready to accept more data to write\n     *  (3) tun dev has data available to read\n     *  (4) tun dev is ready to accept more data to write\n     *  (5) we received a signal (handler sets signal_received)\n     *  (6) timeout (tv) expired\n     */\n\n    c->c2.event_set_status = ES_ERROR;\n\n    if (!c->sig->signal_received)\n    {\n        if (!(flags & IOW_CHECK_RESIDUAL) || !sockets_read_residual(c))\n        {\n            int status;\n\n#ifdef ENABLE_DEBUG\n            if (check_debug_level(D_EVENT_WAIT))\n            {\n                show_wait_status(c);\n            }\n#endif\n\n            /*\n             * Wait for something to happen.\n             */\n            status = event_wait(c->c2.event_set, &c->c2.timeval, esr, SIZE(esr));\n\n            check_status(status, \"event_wait\", NULL, NULL);\n\n            if (status > 0)\n            {\n                int i;\n                c->c2.event_set_status = 0;\n                for (i = 0; i < status; ++i)\n                {\n                    const struct event_set_return *e = &esr[i];\n                    uintptr_t shift;\n\n                    if (e->arg >= MULTI_N)\n                    {\n                        struct event_arg *ev_arg = (struct event_arg *)e->arg;\n                        if (ev_arg->type != EVENT_ARG_LINK_SOCKET)\n                        {\n                            c->c2.event_set_status = ES_ERROR;\n                            msg(D_LINK_ERRORS, \"io_work: non socket event delivered\");\n                            return;\n                        }\n\n                        shift = socket_shift;\n                    }\n                    else\n                    {\n                        shift = (uintptr_t)e->arg;\n                    }\n\n                    c->c2.event_set_status |= ((e->rwflags & 3) << shift);\n                }\n            }\n            else if (status == 0)\n            {\n                c->c2.event_set_status = ES_TIMEOUT;\n            }\n        }\n        else\n        {\n            c->c2.event_set_status = SOCKET_READ;\n        }\n    }\n\n    /* 'now' should always be a reasonably up-to-date timestamp */\n    update_time();\n\n    /* set signal_received if a signal was received */\n    if (c->c2.event_set_status & ES_ERROR)\n    {\n        get_signal(&c->sig->signal_received);\n    }\n\n    dmsg(D_EVENT_WAIT, \"I/O WAIT status=0x%04x\", c->c2.event_set_status);\n}\n\nvoid\nprocess_io(struct context *c, struct link_socket *sock)\n{\n    const unsigned int status = c->c2.event_set_status;\n\n#ifdef ENABLE_MANAGEMENT\n    if (status & (MANAGEMENT_READ | MANAGEMENT_WRITE))\n    {\n        ASSERT(management);\n        management_io(management);\n    }\n#endif\n\n    /* TCP/UDP port ready to accept write */\n    if (status & SOCKET_WRITE)\n    {\n        process_outgoing_link(c, sock);\n    }\n    /* TUN device ready to accept write */\n    else if (status & TUN_WRITE)\n    {\n        process_outgoing_tun(c, sock);\n    }\n    /* Incoming data on TCP/UDP port */\n    else if (status & SOCKET_READ)\n    {\n        read_incoming_link(c, sock);\n        if (!IS_SIG(c))\n        {\n            process_incoming_link(c, sock);\n        }\n    }\n    /* Incoming data on TUN device */\n    else if (status & TUN_READ)\n    {\n        read_incoming_tun(c);\n        if (!IS_SIG(c))\n        {\n            process_incoming_tun(c, sock);\n        }\n    }\n    else if (status & DCO_READ)\n    {\n        if (!IS_SIG(c))\n        {\n            dco_read_and_process(&c->c1.tuntap->dco);\n        }\n    }\n}\n"
  },
  {
    "path": "src/openvpn/forward.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n\n/**\n * @file\n * Interface functions to the internal and external multiplexers.\n */\n\n\n#ifndef FORWARD_H\n#define FORWARD_H\n\n/* the following macros must be defined before including any other header\n * file\n */\n\n#define TUN_OUT(c)  (BLEN(&(c)->c2.to_tun) > 0)\n#define LINK_OUT(c) (BLEN(&(c)->c2.to_link) > 0)\n#define ANY_OUT(c)  (TUN_OUT(c) || LINK_OUT(c))\n\n#ifdef ENABLE_FRAGMENT\n#define TO_LINK_FRAG(c) ((c)->c2.fragment && fragment_outgoing_defined((c)->c2.fragment))\n#else\n#define TO_LINK_FRAG(c) (false)\n#endif\n\n#define TO_LINK_DEF(c) (LINK_OUT(c) || TO_LINK_FRAG(c))\n\n#include \"openvpn.h\"\n#include \"occ.h\"\n#include \"ping.h\"\n#include \"multi_io.h\"\n\n#define IOW_TO_TUN         (1 << 0)\n#define IOW_TO_LINK        (1 << 1)\n#define IOW_READ_TUN       (1 << 2)\n#define IOW_READ_LINK      (1 << 3)\n#define IOW_SHAPER         (1 << 4)\n#define IOW_CHECK_RESIDUAL (1 << 5)\n#define IOW_FRAG           (1 << 6)\n#define IOW_MBUF           (1 << 7)\n#define IOW_READ_TUN_FORCE (1 << 8)\n#define IOW_WAIT_SIGNAL    (1 << 9)\n\n#define IOW_READ (IOW_READ_TUN | IOW_READ_LINK)\n\nextern counter_type link_read_bytes_global;\n\nextern counter_type link_write_bytes_global;\n\nvoid get_io_flags_udp(struct context *c, struct multi_io *multi_io, const unsigned int flags);\n\nvoid io_wait(struct context *c, const unsigned int flags);\n\nvoid pre_select(struct context *c);\n\nvoid process_io(struct context *c, struct link_socket *sock);\n\n\n/**********************************************************************/\n/**\n * Process a data channel packet that will be sent through a VPN tunnel.\n * @ingroup data_control\n *\n * This function controls the processing of a data channel packet which\n * will be sent through a VPN tunnel to a remote OpenVPN peer.  It's\n * general structure is as follows:\n * - Check that the client authentication has succeeded; if not, drop the\n *   packet.\n * - If the \\a comp_frag argument is true:\n *   - Call \\c lzo_compress() of the \\link compression Data Channel Compression\n *     module\\endlink to (possibly) compress the packet.\n *   - Call \\c fragment_outgoing() of the \\link fragmentation Data Channel Fragmentation\n *     module\\endlink to (possibly) fragment the packet.\n * - Activate the \\link data_crypto Data Channel Crypto module\\endlink to perform\n *   security operations on the packet.\n *   - Call \\c tls_pre_encrypt() to choose the appropriate security\n *     parameters for this packet.\n *   - Call \\c openvpn_encrypt() to encrypt and HMAC signed the packet.\n *   - Call \\c tls_post_encrypt() to prepend the one-byte OpenVPN header\n *     and do some TLS accounting.\n * - Place the resulting packet in \\c c->c2.to_link so that it can be sent\n *   over the external network interface to its remote destination by the\n *   \\link external_multiplexer External Multiplexer\\endlink.\n *\n * @param c - The context structure of the VPN tunnel associated with this\n *     packet.\n * @param comp_frag - Whether to do packet compression and fragmentation.\n *     This flag is set to true the first time a packet is processed.  If\n *     the packet then gets fragmented, this function will be called again\n *     once for each remaining fragment with this parameter set to false.\n */\nvoid encrypt_sign(struct context *c, bool comp_frag);\n\nint get_server_poll_remaining_time(struct event_timeout *server_poll_timeout);\n\n/**********************************************************************/\n/**\n * Read a packet from the external network interface.\n * @ingroup external_multiplexer\n *\n * The packet read from the external network interface is stored in \\c\n * c->c2.buf and its source address in \\c c->c2.from.  If an error\n * occurred, the length of \\c c->c2.buf will be 0.\n *\n * OpenVPN running as client or as UDP server only has a single external\n * network socket, so this function can be called with the single (client\n * mode) or top level (UDP server) context as its argument. OpenVPN\n * running as TCP server, on the other hand, has a network socket for each\n * active VPN tunnel.  In that case this function must be called with the\n * context associated with the appropriate VPN tunnel for which data is\n * available to be read.\n *\n * @param c    The context structure which contains the external\n *             network socket from which to read incoming packets.\n * @param sock   The socket where the packet can be read from.\n */\nvoid read_incoming_link(struct context *c, struct link_socket *sock);\n\n/**\n * Starts processing a packet read from the external network interface.\n * @ingroup external_multiplexer\n *\n * This function starts the processing of a data channel packet which\n * has come out of a VPN tunnel.  It's high-level structure is as follows:\n * - Verify that a nonzero length packet has been received from a valid\n *   source address for the given context \\a c.\n * - Call \\c tls_pre_decrypt(), which splits data channel and control\n *   channel packets:\n *   - If a data channel packet, the appropriate security parameters are\n *     loaded.\n *   - If a control channel packet, this function process is it and\n *     afterwards sets the packet's buffer length to 0, so that the data\n *     channel processing steps below will ignore it.\n * - Call \\c openvpn_decrypt() of the \\link data_crypto Data Channel\n *   Crypto module\\endlink to authenticate and decrypt the packet using\n *   the security parameters loaded by \\c tls_pre_decrypt() above.\n *\n * @param c - The context structure of the VPN tunnel associated with the\n *     packet.\n * @param lsi - link_socket_info obtained from context before processing.\n * @param floated - Flag indicates that peer has floated.\n *\n * @return true if packet is authenticated, false otherwise.\n */\nbool process_incoming_link_part1(struct context *c, struct link_socket_info *lsi, bool floated);\n\n/**\n * Continues processing a packet read from the external network interface.\n * @ingroup external_multiplexer\n *\n * This function continues the processing of a data channel packet which\n * has come out of a VPN tunnel. It must be called after\n * \\c process_incoming_link_part1() function.\n *\n * It's high-level structure is as follows:\n * - Call \\c fragment_incoming() of the \\link fragmentation Data Channel\n *   Fragmentation module\\endlink to reassemble the packet if it's\n *   fragmented.\n * - Call \\c lzo_decompress() of the \\link compression Data Channel\n *   Compression module\\endlink to decompress the packet if it's\n *   compressed.\n * - Place the resulting packet in \\c c->c2.to_tun so that it can be sent\n *   over the virtual tun/tap network interface to its local destination\n *   by the \\link internal_multiplexer Internal Multiplexer\\endlink.\n *\n * @param c - The context structure of the VPN tunnel associated with the\n *     packet.\n * @param lsi - link_socket_info obtained from context before processing.\n * @param orig_buf - Pointer to a buffer data.\n *\n */\nvoid process_incoming_link_part2(struct context *c, struct link_socket_info *lsi,\n                                 const uint8_t *orig_buf);\n\n/**\n * Transfers \\c float_sa data extracted from an incoming DCO\n * PEER_FLOAT_NTF to \\c out_osaddr for later processing.\n *\n * @param socket_family - The address family of the socket\n * @param out_osaddr - openvpn_sockaddr struct that will be filled the new\n *      address data\n * @param float_sa - The sockaddr struct containing the data received from the\n *      DCO notification\n */\nvoid extract_dco_float_peer_addr(sa_family_t socket_family, struct openvpn_sockaddr *out_osaddr,\n                                 const struct sockaddr *float_sa);\n\n/**\n * Process an incoming DCO message (from kernel space).\n *\n * @param dco - Pointer to the structure representing the DCO context.\n */\nvoid process_incoming_dco(dco_context_t *dco);\n\n/**\n * Write a packet to the external network interface.\n * @ingroup external_multiplexer\n *\n * This function writes the packet stored in \\c c->c2.to_link to the\n * external network device contained within \\c c->c1.link_socket.\n *\n * If an error occurs, it is logged and the packet is dropped.\n *\n * @param c   The context structure of the VPN tunnel associated with the\n *            packet.\n * @param sock  The socket to be used to send the packet.\n */\nvoid process_outgoing_link(struct context *c, struct link_socket *sock);\n\n\n/**************************************************************************/\n/**\n * Read a packet from the virtual tun/tap network interface.\n * @ingroup internal_multiplexer\n *\n * This function reads a packet from the virtual tun/tap network device \\c\n * c->c1.tuntap and stores it in \\c c->c2.buf.\n *\n * If an error occurs, it is logged and the packet is dropped.\n *\n * @param c - The context structure in which to store the received\n *     packet.\n */\nvoid read_incoming_tun(struct context *c);\n\n\n/**\n * Process a packet read from the virtual tun/tap network interface.\n * @ingroup internal_multiplexer\n *\n * This function calls \\c encrypt_sign() of the \\link data_control Data\n * Channel Control module\\endlink to process the packet.\n *\n * If an error occurs, it is logged and the packet is dropped.\n *\n * @param c       The context structure of the VPN tunnel associated with\n *                the packet.\n * @param out_sock  Socket that will be used to send out the packet.\n *\n */\nvoid process_incoming_tun(struct context *c, struct link_socket *out_sock);\n\n\n/**\n * Write a packet to the virtual tun/tap network interface.\n * @ingroup internal_multiplexer\n *\n * This function writes the packet stored in \\c c->c2.to_tun to the\n * virtual tun/tap network device \\c c->c1.tuntap.\n *\n * If an error occurs, it is logged and the packet is dropped.\n *\n * @param c      The context structure of the VPN tunnel associated\n *               with the packet.\n * @param in_sock  Socket where the packet was received.\n */\nvoid process_outgoing_tun(struct context *c, struct link_socket *in_sock);\n\n\n/**************************************************************************/\n\n/*\n * Send a string to remote over the TLS control channel.\n * Used for push/pull messages, passing username/password,\n * etc.\n * @param c          - The context structure of the VPN tunnel associated with\n *                     the packet.\n * @param str        - The message to be sent\n * @param msglevel   - Message level to use for logging\n */\nbool send_control_channel_string(struct context *c, const char *str, msglvl_t msglevel);\n\n/*\n * Send a string to remote over the TLS control channel.\n * Used for push/pull messages, auth pending and other clear text\n * control messages.\n *\n * This variant does not schedule the actual sending of the message\n * The caller needs to ensure that it is scheduled or call\n * send_control_channel_string\n *\n * @param session    - The session structure of the VPN tunnel associated\n *                     with the packet. The method will always use the\n *                     primary key (KS_PRIMARY) for sending the message\n * @param str        - The message to be sent\n * @param msglevel   - Message level to use for logging\n */\n\nbool send_control_channel_string_dowork(struct tls_session *session, const char *str,\n                                        msglvl_t msglevel);\n\n\n/**\n * Reschedule tls_multi_process.\n * NOTE: in multi-client mode, usually calling the function is\n * insufficient to reschedule the client instance object unless\n * multi_schedule_context_wakeup(m, mi) is also called.\n */\nvoid reschedule_multi_process(struct context *c);\n\n#define PIPV4_PASSTOS             (1u << 0)\n#define PIP_MSSFIX                (1u << 1) /* v4 and v6 */\n#define PIP_OUTGOING              (1u << 2)\n#define PIPV4_EXTRACT_DHCP_ROUTER (1u << 3)\n#define PIPV4_CLIENT_NAT          (1u << 4)\n#define PIPV6_ICMP_NOHOST_CLIENT  (1u << 5)\n#define PIPV6_ICMP_NOHOST_SERVER  (1u << 6)\n\n\nvoid process_ip_header(struct context *c, unsigned int flags, struct buffer *buf,\n                       struct link_socket *sock);\n\nbool schedule_exit(struct context *c);\n\nstatic inline struct link_socket_info *\nget_link_socket_info(struct context *c)\n{\n    if (c->c2.link_socket_infos)\n    {\n        return c->c2.link_socket_infos[0];\n    }\n    else\n    {\n        return &c->c2.link_sockets[0]->info;\n    }\n}\n\nstatic inline void\nregister_activity(struct context *c, const int64_t size)\n{\n    if (c->options.inactivity_timeout)\n    {\n        c->c2.inactivity_bytes += size;\n        if (c->c2.inactivity_bytes >= c->options.inactivity_minimum_bytes)\n        {\n            c->c2.inactivity_bytes = 0;\n            event_timeout_reset(&c->c2.inactivity_interval);\n        }\n    }\n}\n\n/*\n * Return the io_wait() flags appropriate for\n * a point-to-point tunnel.\n */\nstatic inline unsigned int\np2p_iow_flags(const struct context *c)\n{\n    unsigned int flags = (IOW_SHAPER | IOW_CHECK_RESIDUAL | IOW_FRAG | IOW_READ | IOW_WAIT_SIGNAL);\n    if (c->c2.to_link.len > 0)\n    {\n        flags |= IOW_TO_LINK;\n    }\n    if (c->c2.to_tun.len > 0)\n    {\n        flags |= IOW_TO_TUN;\n    }\n    return flags;\n}\n\n\nstatic inline bool\nconnection_established(struct context *c)\n{\n    if (c->c2.tls_multi)\n    {\n        return c->c2.tls_multi->multi_state >= CAS_WAITING_OPTIONS_IMPORT;\n    }\n    else\n    {\n        return get_link_socket_info(c)->connection_established;\n    }\n}\n\n#endif /* FORWARD_H */\n"
  },
  {
    "path": "src/openvpn/fragment.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#ifdef ENABLE_FRAGMENT\n\n#include \"crypto.h\"\n#include \"misc.h\"\n#include \"fragment.h\"\n#include \"integer.h\"\n#include \"memdbg.h\"\n\n#define FRAG_ERR(s) \\\n    {               \\\n        errmsg = s; \\\n        goto error; \\\n    }\n\nstatic void\nfragment_list_buf_init(struct fragment_list *list, const struct frame *frame)\n{\n    int i;\n    for (i = 0; i < N_FRAG_BUF; ++i)\n    {\n        list->fragments[i].buf = alloc_buf(BUF_SIZE(frame));\n    }\n}\n\nstatic void\nfragment_list_buf_free(struct fragment_list *list)\n{\n    int i;\n    for (i = 0; i < N_FRAG_BUF; ++i)\n    {\n        free_buf(&list->fragments[i].buf);\n    }\n}\n\n/*\n * Given a sequence ID number, get a fragment buffer.  Use a sliding window,\n * similar to packet_id code.\n */\nstatic struct fragment *\nfragment_list_get_buf(struct fragment_list *list, int seq_id)\n{\n    int diff;\n    if (abs(diff = modulo_subtract(seq_id, list->seq_id, N_SEQ_ID)) >= N_FRAG_BUF)\n    {\n        int i;\n        for (i = 0; i < N_FRAG_BUF; ++i)\n        {\n            list->fragments[i].defined = false;\n        }\n        list->index = 0;\n        list->seq_id = seq_id;\n        diff = 0;\n    }\n    while (diff > 0)\n    {\n        list->fragments[list->index = modulo_add(list->index, 1, N_FRAG_BUF)].defined = false;\n        list->seq_id = modulo_add(list->seq_id, 1, N_SEQ_ID);\n        --diff;\n    }\n    return &list->fragments[modulo_add(list->index, diff, N_FRAG_BUF)];\n}\n\nstruct fragment_master *\nfragment_init(struct frame *frame)\n{\n    struct fragment_master *ret;\n\n    /* code that initializes other parts of\n     * fragment_master assume an initial CLEAR */\n    ALLOC_OBJ_CLEAR(ret, struct fragment_master);\n\n    /*\n     * Outgoing sequence ID is randomized to reduce\n     * the probability of sequence number collisions\n     * when openvpn sessions are restarted.  This is\n     * not done out of any need for security, as all\n     * fragmentation control information resides\n     * inside of the encrypted/authenticated envelope.\n     */\n    ret->outgoing_seq_id = (int)get_random() & (N_SEQ_ID - 1);\n\n    event_timeout_init(&ret->wakeup, FRAG_WAKEUP_INTERVAL, now);\n\n    return ret;\n}\n\nvoid\nfragment_free(struct fragment_master *f)\n{\n    fragment_list_buf_free(&f->incoming);\n    free_buf(&f->outgoing);\n    free_buf(&f->outgoing_return);\n    free(f);\n}\n\nvoid\nfragment_frame_init(struct fragment_master *f, const struct frame *frame)\n{\n    fragment_list_buf_init(&f->incoming, frame);\n    f->outgoing = alloc_buf(BUF_SIZE(frame));\n    f->outgoing_return = alloc_buf(BUF_SIZE(frame));\n}\n\n/*\n * Accept an incoming datagram (which may be a fragment) from remote.\n * If the datagram is whole (i.e not a fragment), pass through.\n * If the datagram is a fragment, join with other fragments received so far.\n * If a fragment fully completes the datagram, return the datagram.\n */\nvoid\nfragment_incoming(struct fragment_master *f, struct buffer *buf, const struct frame *frame)\n{\n    const char *errmsg = NULL;\n    fragment_header_type flags = 0;\n    int frag_type = 0;\n\n    if (buf->len > 0)\n    {\n        /* get flags from packet head */\n        if (!buf_read(buf, &flags, sizeof(flags)))\n        {\n            FRAG_ERR(\"flags not found in packet\");\n        }\n        flags = ntoh_fragment_header_type(flags);\n\n        /* get fragment type from flags */\n        frag_type = ((flags >> FRAG_TYPE_SHIFT) & FRAG_TYPE_MASK);\n\n#if 0\n        /*\n         * If you want to extract FRAG_EXTRA_MASK/FRAG_EXTRA_SHIFT bits,\n         * do it here.\n         */\n        if (frag_type == FRAG_WHOLE || frag_type == FRAG_YES_NOTLAST)\n        {\n        }\n#endif\n\n        /* handle the fragment type */\n        if (frag_type == FRAG_WHOLE)\n        {\n            dmsg(D_FRAG_DEBUG, \"FRAG_IN buf->len=%d type=FRAG_WHOLE flags=\" fragment_header_format,\n                 buf->len, flags);\n\n            if (flags & (FRAG_SEQ_ID_MASK | FRAG_ID_MASK))\n            {\n                FRAG_ERR(\"spurious FRAG_WHOLE flags\");\n            }\n        }\n        else if (frag_type == FRAG_YES_NOTLAST || frag_type == FRAG_YES_LAST)\n        {\n            const int seq_id = ((flags >> FRAG_SEQ_ID_SHIFT) & FRAG_SEQ_ID_MASK);\n            const int n = ((flags >> FRAG_ID_SHIFT) & FRAG_ID_MASK);\n            const int size =\n                ((frag_type == FRAG_YES_LAST)\n                     ? (int)(((flags >> FRAG_SIZE_SHIFT) & FRAG_SIZE_MASK) << FRAG_SIZE_ROUND_SHIFT)\n                     : buf->len);\n\n            /* get the appropriate fragment buffer based on received seq_id */\n            struct fragment *frag = fragment_list_get_buf(&f->incoming, seq_id);\n\n            dmsg(\n                D_FRAG_DEBUG,\n                \"FRAG_IN len=%d type=%d seq_id=%d frag_id=%d size=%d flags=\" fragment_header_format,\n                buf->len, frag_type, seq_id, n, size, flags);\n\n            /* make sure that size is an even multiple of 1<<FRAG_SIZE_ROUND_SHIFT */\n            if (size & FRAG_SIZE_ROUND_MASK)\n            {\n                FRAG_ERR(\"bad fragment size\");\n            }\n\n            /* is this the first fragment for our sequence number? */\n            if (!frag->defined || frag->max_frag_size != size)\n            {\n                frag->defined = true;\n                frag->max_frag_size = size;\n                frag->map = 0;\n                ASSERT(buf_init(&frag->buf, frame->buf.headroom));\n            }\n\n            /* copy the data to fragment buffer */\n            if (!buf_copy_range(&frag->buf, n * size, buf, 0, buf->len))\n            {\n                FRAG_ERR(\"fragment buffer overflow\");\n            }\n\n            /* set elements in bit array to reflect which fragments have been received */\n            frag->map |= (((frag_type == FRAG_YES_LAST) ? FRAG_MAP_MASK : 1) << n);\n\n            /* update timestamp on partially built datagram */\n            frag->timestamp = now;\n\n            /* received full datagram? */\n            if ((frag->map & FRAG_MAP_MASK) == FRAG_MAP_MASK)\n            {\n                frag->defined = false;\n                *buf = frag->buf;\n            }\n            else\n            {\n                buf->len = 0;\n            }\n        }\n        else if (frag_type == FRAG_TEST)\n        {\n            FRAG_ERR(\"FRAG_TEST not implemented\");\n        }\n        else\n        {\n            FRAG_ERR(\"unknown fragment type\");\n        }\n    }\n\n    return;\n\nerror:\n    if (errmsg)\n    {\n        msg(D_FRAG_ERRORS, \"FRAG_IN error flags=\" fragment_header_format \": %s\", flags, errmsg);\n    }\n    buf->len = 0;\n    return;\n}\n\n/* pack fragment parms into a uint32_t and prepend to buffer */\nstatic void\nfragment_prepend_flags(struct buffer *buf, int type, int seq_id, int frag_id, int frag_size)\n{\n    fragment_header_type flags = ((type & FRAG_TYPE_MASK) << FRAG_TYPE_SHIFT)\n                                 | ((seq_id & FRAG_SEQ_ID_MASK) << FRAG_SEQ_ID_SHIFT)\n                                 | ((frag_id & FRAG_ID_MASK) << FRAG_ID_SHIFT);\n\n    if (type == FRAG_WHOLE || type == FRAG_YES_NOTLAST)\n    {\n        /*\n         * If you want to set FRAG_EXTRA_MASK/FRAG_EXTRA_SHIFT bits,\n         * do it here.\n         */\n        dmsg(\n            D_FRAG_DEBUG,\n            \"FRAG_OUT len=%d type=%d seq_id=%d frag_id=%d frag_size=%d flags=\" fragment_header_format,\n            buf->len, type, seq_id, frag_id, frag_size, flags);\n    }\n    else\n    {\n        flags |= (((frag_size >> FRAG_SIZE_ROUND_SHIFT) & FRAG_SIZE_MASK) << FRAG_SIZE_SHIFT);\n\n        dmsg(\n            D_FRAG_DEBUG,\n            \"FRAG_OUT len=%d type=%d seq_id=%d frag_id=%d frag_size=%d flags=\" fragment_header_format,\n            buf->len, type, seq_id, frag_id, frag_size, flags);\n    }\n\n    flags = hton_fragment_header_type(flags);\n    ASSERT(buf_write_prepend(buf, &flags, sizeof(flags)));\n}\n\n/*\n * Without changing the number of fragments, return a possibly smaller\n * max fragment size that will allow for the last fragment to be of\n * similar size as previous fragments.\n */\nstatic inline int\noptimal_fragment_size(int len, int max_frag_size)\n{\n    const int mfs_aligned = (max_frag_size & ~FRAG_SIZE_ROUND_MASK);\n    const int div = len / mfs_aligned;\n    const int mod = len % mfs_aligned;\n\n    if (div > 0 && mod > 0 && mod < mfs_aligned * 3 / 4)\n    {\n        return min_int(mfs_aligned,\n                       (max_frag_size - ((max_frag_size - mod) / (div + 1)) + FRAG_SIZE_ROUND_MASK)\n                           & ~FRAG_SIZE_ROUND_MASK);\n    }\n    else\n    {\n        return mfs_aligned;\n    }\n}\n\n/* process an outgoing datagram, possibly breaking it up into fragments */\nvoid\nfragment_outgoing(struct fragment_master *f, struct buffer *buf, const struct frame *frame)\n{\n    const char *errmsg = NULL;\n    if (buf->len > 0)\n    {\n        /* The outgoing buffer should be empty so we can put new data in it */\n        if (f->outgoing.len)\n        {\n            msg(D_FRAG_ERRORS, \"FRAG: outgoing buffer is not empty, len=[%d,%d]\", buf->len,\n                f->outgoing.len);\n        }\n        if (buf->len > frame->max_fragment_size) /* should we fragment? */\n        {\n            /*\n             * Send the datagram as a series of 2 or more fragments.\n             */\n            f->outgoing_frag_size = optimal_fragment_size(buf->len, frame->max_fragment_size);\n            if (buf->len > f->outgoing_frag_size * MAX_FRAGS)\n            {\n                FRAG_ERR(\"too many fragments would be required to send datagram\");\n            }\n            ASSERT(buf_init(&f->outgoing, frame->buf.headroom));\n            ASSERT(buf_copy(&f->outgoing, buf));\n            f->outgoing_seq_id = modulo_add(f->outgoing_seq_id, 1, N_SEQ_ID);\n            f->outgoing_frag_id = 0;\n            buf->len = 0;\n            ASSERT(fragment_ready_to_send(f, buf, frame));\n        }\n        else\n        {\n            /*\n             * Send the datagram whole.\n             */\n            fragment_prepend_flags(buf, FRAG_WHOLE, 0, 0, 0);\n        }\n    }\n    return;\n\nerror:\n    if (errmsg)\n    {\n        msg(D_FRAG_ERRORS, \"FRAG_OUT error, len=%d frag_size=%d MAX_FRAGS=%d: %s\", buf->len,\n            f->outgoing_frag_size, MAX_FRAGS, errmsg);\n    }\n    buf->len = 0;\n    return;\n}\n\n/* return true (and set buf) if we have an outgoing fragment which is ready to send */\nbool\nfragment_ready_to_send(struct fragment_master *f, struct buffer *buf, const struct frame *frame)\n{\n    if (fragment_outgoing_defined(f))\n    {\n        /* get fragment size, and determine if it is the last fragment */\n        int size = f->outgoing_frag_size;\n        int last = false;\n        if (f->outgoing.len <= size)\n        {\n            size = f->outgoing.len;\n            last = true;\n        }\n\n        /* initialize return buffer */\n        *buf = f->outgoing_return;\n        ASSERT(buf_init(buf, frame->buf.headroom));\n        ASSERT(buf_copy_n(buf, &f->outgoing, size));\n\n        /* fragment flags differ based on whether or not we are sending the last fragment */\n        fragment_prepend_flags(buf, last ? FRAG_YES_LAST : FRAG_YES_NOTLAST, f->outgoing_seq_id,\n                               f->outgoing_frag_id++, f->outgoing_frag_size);\n\n        ASSERT(!last\n               || !f->outgoing\n                       .len); /* outgoing buffer length should be zero after last fragment sent */\n\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\nstatic void\nfragment_ttl_reap(struct fragment_master *f)\n{\n    int i;\n    for (i = 0; i < N_FRAG_BUF; ++i)\n    {\n        struct fragment *frag = &f->incoming.fragments[i];\n        if (frag->defined && frag->timestamp + FRAG_TTL_SEC <= now)\n        {\n            msg(D_FRAG_ERRORS, \"FRAG TTL expired i=%d\", i);\n            frag->defined = false;\n        }\n    }\n}\n\n/* called every FRAG_WAKEUP_INTERVAL seconds */\nvoid\nfragment_wakeup(struct fragment_master *f, struct frame *frame)\n{\n    /* delete fragments with expired TTLs */\n    fragment_ttl_reap(f);\n}\n#endif /* ifdef ENABLE_FRAGMENT */\n"
  },
  {
    "path": "src/openvpn/fragment.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef FRAGMENT_H\n#define FRAGMENT_H\n\n/**\n * @file\n * Data Channel Fragmentation module header file.\n */\n\n\n#ifdef ENABLE_FRAGMENT\n\n/**\n * @addtogroup fragmentation\n * @{\n */\n\n\n#include \"common.h\"\n#include \"buffer.h\"\n#include \"interval.h\"\n#include \"mtu.h\"\n#include \"shaper.h\"\n#include \"error.h\"\n\n\n#define N_FRAG_BUF 25\n/**< Number of packet buffers for\n *   reassembling incoming fragmented\n *   packets. */\n\n#define FRAG_TTL_SEC 10\n/**< Time-to-live in seconds for a %fragment. */\n\n#define FRAG_WAKEUP_INTERVAL 5\n/**< Interval in seconds between calls to\n *   wakeup code. */\n\n/**************************************************************************/\n/**\n * Structure for reassembling one incoming fragmented packet.\n */\nstruct fragment\n{\n    bool defined;      /**< Whether reassembly is currently\n                        *   taking place in this structure. */\n\n    int max_frag_size; /**< Maximum size of each %fragment. */\n\n#define FRAG_MAP_MASK 0xFFFFFFFF\n    /**< Mask for reassembly map. */\n#define MAX_FRAGS     32 /**< Maximum number of fragments per packet. */\n    unsigned int map;\n    /**< Reassembly map for recording which\n     *   fragments have been received.\n     *\n     *   A bit array where each bit\n     *   corresponds to a %fragment.  A 1 bit\n     *   in element n means that the %fragment\n     *   n has been received.  Needs to have\n     *   at least \\c MAX_FRAGS bits. */\n\n    time_t timestamp;  /**< Timestamp for time-to-live purposes. */\n\n    struct buffer buf; /**< Buffer in which received datagrams\n                        *   are reassembled. */\n};\n\n\n/**\n * List of fragment structures for reassembling multiple incoming packets\n * concurrently.\n */\nstruct fragment_list\n{\n    /** Highest fragmentation sequence ID of\n     *  the packets currently being\n     *  reassembled. */\n    int seq_id;\n    /** Index of the packet being reassembled\n     *   with the highest fragmentation\n     *   sequence ID into the \\c\n     *   fragment_list.fragments array. */\n    int index;\n\n    /** Array of reassembly structures, each can contain one whole packet.\n     *\n     *  The fragmentation sequence IDs of the packets being reassembled in\n     *  this array are linearly increasing. \\c\n     *  fragment_list.fragments[fragment_list.index] has an ID of \\c\n     *  fragment_list.seq_id.  This means that one of these \\c fragment_list\n     *  structures can at any one time contain at most packets with the\n     *  fragmentation sequence IDs in the range \\c fragment_list.seq_id \\c -\n     *  \\c N_FRAG_BUF \\c + \\c 1 to \\c fragment_list.seq_id, inclusive.\n     */\n    struct fragment fragments[N_FRAG_BUF];\n};\n\n\n/**\n * Fragmentation and reassembly state for one VPN tunnel instance.\n *\n * This structure contains all the state necessary for sending and\n * receiving fragmented data channel packets associated with one VPN\n * tunnel.\n *\n * The fragmented packet currently being sent to a remote OpenVPN peer is\n * stored in \\c fragment_master.outgoing.  It is copied into that buffer\n * by the \\c fragment_outgoing() function and the remaining parts to be\n * sent can be retrieved by successive calls to \\c\n * fragment_ready_to_send().\n *\n * The received packets currently being reassembled are stored in the \\c\n * fragment_master.incoming array of \\c fragment structures.  The \\c\n * fragment_incoming() function adds newly received parts into this array\n * and returns the whole packets once reassembly is complete.\n */\nstruct fragment_master\n{\n    struct event_timeout wakeup; /**< Timeout structure used by the main\n                                  *   event loop to know when to do\n                                  *   fragmentation housekeeping. */\n#define N_SEQ_ID 256\n    /**< One more than the maximum fragment\n     *   sequence ID, above which the IDs wrap\n     *   to zero.  Should be a power of 2. */\n    int outgoing_seq_id;        /**< Fragment sequence ID of the current\n                                 *   fragmented packet waiting to be sent.\n                                 *\n                                 *   All parts of a fragmented packet\n                                 *   share the same sequence ID, so that\n                                 *   the remote OpenVPN peer can determine\n                                 *   which parts belong to which original\n                                 *   packet. */\n#define MAX_FRAG_PKT_SIZE 65536 /**< (Not used) Maximum packet size before fragmenting. */\n    int outgoing_frag_size;     /**< Size in bytes of each part to be\n                                 *   sent, except for the last part which\n                                 *   may be smaller.\n                                 *\n                                 *   This value is computed by the \\c\n                                 *   optimal_fragment_size() function. Its\n                                 *   value is sent to the remote peer in\n                                 *   the fragmentation header of the last\n                                 *   part (i.e. with %fragment type \\c\n                                 *   FRAG_YES_LAST) using the \\c\n                                 *   FRAG_SIZE_MASK and \\c FRAG_SIZE_SHIFT\n                                 *   bits. */\n    int outgoing_frag_id;       /**< The fragment ID of the next part to\n                                 *   be sent.  Must have a value between 0\n                                 *   and \\c MAX_FRAGS-1. */\n    struct buffer outgoing;     /**< Buffer containing the remaining parts\n                                 *   of the fragmented packet being sent. */\n    struct buffer outgoing_return;\n    /**< Buffer used by \\c\n     *   fragment_ready_to_send() to return a\n     *   part to send. */\n\n    struct fragment_list incoming;\n    /**< List of structures for reassembling\n     *   incoming packets. */\n};\n\n\n/**************************************************************************/\n/** @name Fragment header\n *  @todo Add description of %fragment header format.\n */\n/** @{ */ /*************************************/\n\ntypedef uint32_t fragment_header_type;\n/**< Fragmentation information is stored in a 32-bit packet header. */\n\n#define hton_fragment_header_type(x) htonl(x)\n/**< Convert a fragment_header_type from host to network order. */\n\n#define ntoh_fragment_header_type(x) ntohl(x)\n/**< Convert a \\c fragment_header_type from network to host order. */\n\n#define FRAG_TYPE_MASK  0x00000003 /**< Bit mask for %fragment type info. */\n#define FRAG_TYPE_SHIFT 0          /**< Bit shift for %fragment type info. */\n\n#define FRAG_WHOLE       0         /**< Fragment type indicating packet is whole. */\n#define FRAG_YES_NOTLAST 1\n/**< Fragment type indicating packet is part of a fragmented packet, but not\n *   the last part in the sequence. */\n#define FRAG_YES_LAST    2\n/**< Fragment type indicating packet is the last part in the sequence of parts. */\n#define FRAG_TEST        3\n/**< Fragment type not implemented yet.\n * In the future might be used as a control packet for establishing MTU size. */\n\n#define FRAG_SEQ_ID_MASK  0x000000ff /**< Bit mask for %fragment sequence ID. */\n#define FRAG_SEQ_ID_SHIFT 2          /**< Bit shift for %fragment sequence ID. */\n\n#define FRAG_ID_MASK  0x0000001f     /**< Bit mask for %fragment ID. */\n#define FRAG_ID_SHIFT 10             /**< Bit shift for %fragment ID. */\n\n\n/*\n * FRAG_SIZE  14 bits\n *\n * IF FRAG_YES_LAST (FRAG_SIZE):\n *   The max size of a %fragment.  If a %fragment is not the last %fragment in the packet,\n *   then the %fragment size is guaranteed to be equal to the max %fragment size.  Therefore,\n *   max_frag_size is only sent over the wire if FRAG_LAST is set.  Otherwise it is assumed\n *   to be the actual %fragment size received.\n */\n#define FRAG_SIZE_MASK        0x00003fff /**< Bit mask for %fragment size. */\n#define FRAG_SIZE_SHIFT       15         /**< Bit shift for %fragment size. */\n#define FRAG_SIZE_ROUND_SHIFT 2          /**< Bit shift for %fragment size rounding. */\n#define FRAG_SIZE_ROUND_MASK  ((1 << FRAG_SIZE_ROUND_SHIFT) - 1)\n/**< Bit mask for %fragment size rounding. */\n\n/*\n * FRAG_EXTRA 16 bits\n *\n * IF FRAG_WHOLE or FRAG_YES_NOTLAST, these 16 bits are available (not currently used)\n */\n#define FRAG_EXTRA_MASK  0x0000ffff /**< Bit mask for extra bits. */\n#define FRAG_EXTRA_SHIFT 15         /**< Bit shift for extra bits. */\n\n/** @} name Fragment header */      /********************************************/\n\n\n/**************************************************************************/\n/** @name Functions for initialization and cleanup */ /** @{ */ /************/\n\n/**\n * Allocate and initialize a \\c fragment_master structure.\n *\n * This function also modifies the \\a frame packet geometry parameters to\n * include space for the fragmentation header.\n *\n * @param frame        - The packet geometry parameters for this VPN\n *                       tunnel, modified by this function to include the\n *                       fragmentation header.\n *\n * @return A pointer to the new \\c fragment_master structure.\n */\nstruct fragment_master *fragment_init(struct frame *frame);\n\n\n/**\n * Allocate internal packet buffers for a \\c fragment_master structure.\n *\n * @param f            - The \\c fragment_master structure for which to\n *                       allocate the internal buffers.\n * @param frame        - The packet geometry parameters for this VPN\n *                       tunnel, used to determine how much memory to\n *                       allocate for each packet buffer.\n */\nvoid fragment_frame_init(struct fragment_master *f, const struct frame *frame);\n\n\n/**\n * Free a \\c fragment_master structure and its internal packet buffers.\n *\n * @param f            - The \\c fragment_master structure to free.\n */\nvoid fragment_free(struct fragment_master *f);\n\n/** @} name Functions for initialization and cleanup */ /*******************/\n\n\n/**************************************************************************/\n/** @name Functions for processing packets received from a remote OpenVPN peer */\n/** @{ */\n\n/**\n * Process an incoming packet, which may or may not be fragmented.\n *\n * This function inspects the fragmentation header of the incoming packet\n * and processes the packet accordingly. Depending on the %fragment type\n * bits (\\c FRAG_TYPE_MASK and \\c FRAG_TYPE_SHIFT) the packet is processed\n * in the following ways:\n *  - \\c FRAG_WHOLE: the packet is not fragmented, and this function does\n *    not modify its contents, except for removing the fragmentation\n *    header.\n *  - \\c FRAG_YES_NOTLAST or \\c FRAG_YES_LAST: the packet is part of a\n *    fragmented packet.  This function copies the packet into an internal\n *    reassembly buffer.  If the incoming part completes the packet being\n *    reassembled, the \\a buf argument is modified to point to the fully\n *    reassembled packet.  If, on the other hand, reassembly is not yet\n *    complete, then the \\a buf buffer is set to empty.\n *  - Any other value: error.\n *\n * If an error occurs during processing, an error message is logged and\n * the length of \\a buf is set to zero.\n *\n * @param f            - The \\c fragment_master structure for this VPN\n *                       tunnel.\n * @param[in,out] buf  - A pointer to the buffer structure containing the\n *                       incoming packet.  This pointer will have been\n *                       modified on return either to point to a\n *                       completely reassembled packet, or to have length\n *                       set to zero if reassembly is not yet complete.\n * @param frame        - The packet geometry parameters for this VPN\n *                       tunnel.\n *\n * @note On return the \\a buf argument buffer will be modified\n *     to communicate the result of the function.\n *     The buffer will have nonzero length if the incoming packet passed\n *     to this function was whole and unfragmented, or if it was the final\n *     part of a fragmented packet thereby completing reassembly.  On the\n *     other hand, the buffer will have a length of zero if the incoming\n *     packet was part of a fragmented packet and reassembly is not yet\n *     complete.  If an error occurs during processing, the buffer length\n *     is also set to zero.\n */\nvoid fragment_incoming(struct fragment_master *f, struct buffer *buf, const struct frame *frame);\n\n/** @} name Functions for processing packets received from a VPN tunnel */\n\n\n/**************************************************************************/\n/** @name Functions for processing packets to be sent to a remote OpenVPN peer */\n/** @{ */\n\n/**\n * Process an outgoing packet, which may or may not need to be fragmented.\n *\n * This function inspects the outgoing packet, determines whether it needs\n * to be fragmented, and processes it accordingly.\n *\n * Depending on the size of the outgoing packet and the packet geometry\n * parameters for the VPN tunnel, the packet will or will not be\n * fragmented.\n * @li Packet size is less than or equal to the maximum packet size for\n *     this VPN tunnel: fragmentation is not necessary.  The \\a buf\n *     argument points to a buffer containing the unmodified outgoing\n *     packet with a fragmentation header indicating the packet is whole\n *     (FRAG_WHOLE) prepended.\n * @li Packet size is greater than the maximum packet size for this VPN\n *     tunnel: fragmentation is necessary.  The original outgoing packet\n *     is copied into an internal buffer for fragmentation.  The \\a buf\n *     argument is modified to point to the first part of the fragmented\n *     packet. The remaining parts remain stored in the internal buffer,\n *     and can be retrieved using the \\c fragment_ready_to_send()\n *     function.\n *\n * If an error occurs during processing, an error message is logged and\n * the length of \\a buf is set to zero.\n *\n * @param f            - The \\c fragment_master structure for this VPN\n *                       tunnel.\n * @param[in,out] buf  - A pointer to the buffer structure containing the\n *                       outgoing packet.  This pointer will be modified\n *                       to point to a whole unfragmented packet or to the\n *                       first part of a fragmented packet on return.\n * @param frame        - The packet geometry parameters for this VPN\n *                       tunnel.\n *\n * @note On return the \\a buf argument buffer will be modified\n *     to communicate the result of the function.\n *     This buffer contains either the whole original outgoing packet if\n *     fragmentation was not necessary, or the first part of the\n *     fragmented outgoing packet if fragmentation was necessary. In both\n *     cases a fragmentation header will have been prepended to inform the\n *     remote peer how to handle the packet.\n */\nvoid fragment_outgoing(struct fragment_master *f, struct buffer *buf, const struct frame *frame);\n\n/**\n * Check whether outgoing fragments are ready to be send, and if so make\n * one available.\n *\n * This function checks whether the internal buffer for fragmenting\n * outgoing packets contains any unsent parts.  If it does not, meaning\n * there is nothing waiting to be sent, it returns false.  Otherwise there\n * are parts ready to be sent, and it returns true.  In that case it also\n * modifies the \\a buf argument to point to a buffer containing the next\n * part to be sent.\n *\n * @param f            - The \\a fragment_master structure for this VPN\n *                       tunnel.\n * @param buf          - A pointer to a buffer structure which on return,\n *                       if there are parts waiting to be sent, will point\n *                       to the next part to be sent.\n * @param frame        - The packet geometry parameters for this VPN\n *                       tunnel.\n *\n * @return\n * @li True, if an outgoing packet has been fragmented and not all parts\n *     have been sent yet.  In this case this function will modify the \\a\n *     buf argument to point to a buffer containing the next part to be\n *     sent.\n * @li False, if there are no outgoing fragmented parts waiting to be\n *     sent.\n */\nbool fragment_ready_to_send(struct fragment_master *f, struct buffer *buf,\n                            const struct frame *frame);\n\n/**\n * Check whether a \\c fragment_master structure contains fragments ready\n * to be sent.\n *\n * @param f            - The \\c fragment_master structure for this VPN\n *                       tunnel.\n *\n * @return\n * @li True, if there are one or more fragments ready to be sent.\n * @li False, otherwise.\n */\nstatic inline bool\nfragment_outgoing_defined(struct fragment_master *f)\n{\n    return f->outgoing.len > 0;\n}\n\n/** @} name Functions for processing packets going out through a VPN tunnel */\n\n\nvoid fragment_wakeup(struct fragment_master *f, struct frame *frame);\n\n\n/**************************************************************************/\n/** @name Functions for regular housekeeping */ /** @{ */ /******************/\n\n/**\n * Perform housekeeping of a \\c fragment_master structure.\n *\n * Housekeeping includes scanning incoming packet reassembly buffers for\n * packets which have not yet been reassembled completely but are already\n * older than their time-to-live.\n *\n * @param[in] f        The \\c fragment_master structure for this VPN\n *                     tunnel.\n * @param[in] frame    The packet geometry parameters for this VPN\n *                     tunnel.\n * @param[out] tv      Will be set to time for next housekeeping.\n */\nstatic inline void\nfragment_housekeeping(struct fragment_master *f, struct frame *frame, struct timeval *tv)\n{\n    if (event_timeout_trigger(&f->wakeup, tv, ETT_DEFAULT))\n    {\n        fragment_wakeup(f, frame);\n    }\n}\n\n/** @} name Functions for regular housekeeping */ /*************************/\n\n\n/** @} addtogroup fragmentation */ /****************************************/\n\n\n#endif /* ifdef ENABLE_FRAGMENT */\n#endif /* ifndef FRAGMENT_H */\n"
  },
  {
    "path": "src/openvpn/gremlin.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * Test protocol robustness by simulating dropped packets and\n * network outages when the --gremlin option is used.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#ifdef ENABLE_DEBUG\n\n#include \"error.h\"\n#include \"common.h\"\n#include \"crypto.h\"\n#include \"misc.h\"\n#include \"otime.h\"\n#include \"gremlin.h\"\n\n#include \"memdbg.h\"\n\n/*\n * Parameters for packet corruption and droppage.\n * Each parameter has 4 possible levels, 0 = disabled,\n * while 1, 2, and 3 are enumerated in the below arrays.\n * The parameter is a 2-bit field within the --gremlin\n * parameter.\n */\n\n/*\n * Probability that we will drop a packet is 1 / n\n */\nstatic const int drop_freq[] = { 500, 100, 50 };\n\n/*\n * Probability that we will corrupt a packet is 1 / n\n */\nstatic const int corrupt_freq[] = { 500, 100, 50 };\n\n/*\n * When network goes up, it will be up for between\n * UP_LOW and UP_HIGH seconds.\n */\nstatic const int up_low[] = { 60, 10, 5 };\nstatic const int up_high[] = { 600, 60, 10 };\n\n/*\n * When network goes down, it will be down for between\n * DOWN_LOW and DOWN_HIGH seconds.\n */\nstatic const int down_low[] = { 5, 10, 10 };\nstatic const int down_high[] = { 10, 60, 120 };\n\n/*\n * Packet flood levels:\n *  { number of packets, packet size }\n */\nstatic const struct packet_flood_parms packet_flood_data[] = { { 10, 100 },\n                                                               { 10, 1500 },\n                                                               { 100, 1500 } };\n\nstruct packet_flood_parms\nget_packet_flood_parms(int level)\n{\n    ASSERT(level > 0 && level < 4);\n    return packet_flood_data[level - 1];\n}\n\n/*\n * Return true with probability 1/n\n */\nstatic bool\nflip(int n)\n{\n    return (get_random() % n) == 0;\n}\n\n/*\n * Return uniformly distributed random number between\n * low and high.\n */\nstatic int\nroll(int low, int high)\n{\n    int ret;\n    ASSERT(low <= high);\n    ret = low + (int)(get_random() % (high - low + 1));\n    ASSERT(ret >= low && ret <= high);\n    return ret;\n}\n\nstatic bool initialized; /* GLOBAL */\nstatic bool up;          /* GLOBAL */\nstatic time_t next;      /* GLOBAL */\n\n/*\n * Return false if we should drop a packet.\n */\nbool\nask_gremlin(int flags)\n{\n    const int up_down_level = GREMLIN_UP_DOWN_LEVEL(flags);\n    const int drop_level = GREMLIN_DROP_LEVEL(flags);\n\n    if (!initialized)\n    {\n        initialized = true;\n\n        if (up_down_level)\n        {\n            up = false;\n        }\n        else\n        {\n            up = true;\n        }\n\n        next = now;\n    }\n\n    if (up_down_level) /* change up/down state? */\n    {\n        if (now >= next)\n        {\n            int delta;\n            if (up)\n            {\n                delta = roll(down_low[up_down_level - 1], down_high[up_down_level - 1]);\n                up = false;\n            }\n            else\n            {\n                delta = roll(up_low[up_down_level - 1], up_high[up_down_level - 1]);\n                up = true;\n            }\n\n            msg(D_GREMLIN, \"GREMLIN: CONNECTION GOING %s FOR %d SECONDS\", (up ? \"UP\" : \"DOWN\"),\n                delta);\n            next = now + delta;\n        }\n    }\n\n    if (drop_level)\n    {\n        if (up && flip(drop_freq[drop_level - 1]))\n        {\n            dmsg(D_GREMLIN_VERBOSE, \"GREMLIN: Random packet drop\");\n            return false;\n        }\n    }\n\n    return up;\n}\n\n/*\n * Possibly corrupt a packet.\n */\nvoid\ncorrupt_gremlin(struct buffer *buf, int flags)\n{\n    const int corrupt_level = GREMLIN_CORRUPT_LEVEL(flags);\n    if (corrupt_level)\n    {\n        if (flip(corrupt_freq[corrupt_level - 1]))\n        {\n            do\n            {\n                if (buf->len > 0)\n                {\n                    uint8_t r = (uint8_t)roll(0, 255);\n                    int method = roll(0, 5);\n\n                    switch (method)\n                    {\n                        case 0: /* corrupt the first byte */\n                            *BPTR(buf) = r;\n                            break;\n\n                        case 1: /* corrupt the last byte */\n                            *(BPTR(buf) + buf->len - 1) = r;\n                            break;\n\n                        case 2: /* corrupt a random byte */\n                            *(BPTR(buf) + roll(0, buf->len - 1)) = r;\n                            break;\n\n                        case 3: /* append a random byte */\n                            buf_write(buf, &r, 1);\n                            break;\n\n                        case 4: /* reduce length by 1 */\n                            --buf->len;\n                            break;\n\n                        case 5: /* reduce length by a random amount */\n                            buf->len -= roll(0, buf->len - 1);\n                            break;\n                    }\n                    dmsg(D_GREMLIN_VERBOSE, \"GREMLIN: Packet Corruption, method=%d\", method);\n                }\n                else\n                {\n                    break;\n                }\n            } while (flip(2)); /* a 50% chance we will corrupt again */\n        }\n    }\n}\n\n#endif /* ifdef ENABLE_DEBUG */\n"
  },
  {
    "path": "src/openvpn/gremlin.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef GREMLIN_H\n#define GREMLIN_H\n\n#ifdef ENABLE_DEBUG\n\n/*\n * Gremlin options, presented as bitmask argument to --gremlin directive\n */\n\n#define GREMLIN_CONNECTION_FLOOD_SHIFT (0)\n#define GREMLIN_CONNECTION_FLOOD_MASK  (0x07)\n\n#define GREMLIN_PACKET_FLOOD_SHIFT (3)\n#define GREMLIN_PACKET_FLOOD_MASK  (0x03)\n\n#define GREMLIN_CORRUPT_SHIFT (5)\n#define GREMLIN_CORRUPT_MASK  (0x03)\n\n#define GREMLIN_UP_DOWN_SHIFT (7)\n#define GREMLIN_UP_DOWN_MASK  (0x03)\n\n/* 512:1/500 1024:1/100 1536:1/50 */\n\n#define GREMLIN_DROP_SHIFT (9)\n#define GREMLIN_DROP_MASK  (0x03)\n\n/* extract gremlin parms */\n\n#define GREMLIN_CONNECTION_FLOOD_LEVEL(x) \\\n    (((x) >> GREMLIN_CONNECTION_FLOOD_SHIFT) & GREMLIN_CONNECTION_FLOOD_MASK)\n#define GREMLIN_PACKET_FLOOD_LEVEL(x) \\\n    (((x) >> GREMLIN_PACKET_FLOOD_SHIFT) & GREMLIN_PACKET_FLOOD_MASK)\n#define GREMLIN_CORRUPT_LEVEL(x) (((x) >> GREMLIN_CORRUPT_SHIFT) & GREMLIN_CORRUPT_MASK)\n#define GREMLIN_UP_DOWN_LEVEL(x) (((x) >> GREMLIN_UP_DOWN_SHIFT) & GREMLIN_UP_DOWN_MASK)\n#define GREMLIN_DROP_LEVEL(x)    (((x) >> GREMLIN_DROP_SHIFT) & GREMLIN_DROP_MASK)\n\n#include \"buffer.h\"\n\nstruct packet_flood_parms\n{\n    int n_packets;\n    int packet_size;\n};\n\nbool ask_gremlin(int flags);\n\nvoid corrupt_gremlin(struct buffer *buf, int flags);\n\nstruct packet_flood_parms get_packet_flood_parms(int level);\n\n#endif /* ifdef ENABLE_DEBUG */\n#endif /* ifndef GREMLIN_H */\n"
  },
  {
    "path": "src/openvpn/helper.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"forward.h\"\n#include \"helper.h\"\n#include \"pool.h\"\n#include \"push.h\"\n\n#include \"memdbg.h\"\n\n\nstatic const char *\nprint_netmask(int netbits, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(128, gc);\n    const in_addr_t netmask = netbits_to_netmask(netbits);\n\n    buf_printf(&out, \"%s (/%d)\", print_in_addr_t(netmask, 0, gc), netbits);\n\n    return BSTR(&out);\n}\n\nstatic const char *\nprint_opt_route_gateway(const in_addr_t route_gateway, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(128, gc);\n    ASSERT(route_gateway);\n    buf_printf(&out, \"route-gateway %s\", print_in_addr_t(route_gateway, 0, gc));\n    return BSTR(&out);\n}\n\nstatic const char *\nprint_opt_route_gateway_dhcp(struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(32, gc);\n    buf_printf(&out, \"route-gateway dhcp\");\n    return BSTR(&out);\n}\n\nstatic const char *\nprint_opt_route(const in_addr_t network, const in_addr_t netmask, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(128, gc);\n    ASSERT(network);\n\n    if (netmask)\n    {\n        buf_printf(&out, \"route %s %s\", print_in_addr_t(network, 0, gc),\n                   print_in_addr_t(netmask, 0, gc));\n    }\n    else\n    {\n        buf_printf(&out, \"route %s\", print_in_addr_t(network, 0, gc));\n    }\n\n    return BSTR(&out);\n}\n\nstatic const char *\nprint_opt_topology(const int topology, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(128, gc);\n\n    buf_printf(&out, \"topology %s\", print_topology(topology));\n\n    return BSTR(&out);\n}\n\nstatic const char *\nprint_str_int(const char *str, const int i, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(128, gc);\n    buf_printf(&out, \"%s %d\", str, i);\n    return BSTR(&out);\n}\n\nstatic const char *\nprint_str(const char *str, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(128, gc);\n    buf_printf(&out, \"%s\", str);\n    return BSTR(&out);\n}\n\nstatic void\nhelper_add_route(const in_addr_t network, const in_addr_t netmask, struct options *o)\n{\n    rol_check_alloc(o);\n    add_route_to_option_list(o->routes, print_in_addr_t(network, 0, &o->gc),\n                             print_in_addr_t(netmask, 0, &o->gc), NULL, NULL,\n                             o->route_default_table_id);\n}\n\nstatic void\nverify_common_subnet(const char *opt, const in_addr_t a, const in_addr_t b, const in_addr_t subnet)\n{\n    struct gc_arena gc = gc_new();\n    if ((a & subnet) != (b & subnet))\n    {\n        msg(M_USAGE, \"%s IP addresses %s and %s are not in the same %s subnet\", opt,\n            print_in_addr_t(a, 0, &gc), print_in_addr_t(b, 0, &gc),\n            print_in_addr_t(subnet, 0, &gc));\n    }\n    gc_free(&gc);\n}\n\n\n/**\n * Set --topology default depending on --mode\n */\nvoid\nhelper_setdefault_topology(struct options *o)\n{\n    if (o->topology != TOP_UNDEF)\n    {\n        return;\n    }\n    int dev = dev_type_enum(o->dev, o->dev_type);\n    if (dev != DEV_TYPE_TUN)\n    {\n        return;\n    }\n    if (o->mode == MODE_SERVER)\n    {\n        o->topology = TOP_SUBNET;\n    }\n    else\n    {\n        o->topology = TOP_NET30;\n    }\n}\n\n\n/*\n * Process server, server-bridge, and client helper\n * directives after the parameters themselves have been\n * parsed and placed in struct options.\n */\nvoid\nhelper_client_server(struct options *o)\n{\n    struct gc_arena gc = gc_new();\n\n    /*\n     * Get tun/tap/null device type\n     */\n    const int dev = dev_type_enum(o->dev, o->dev_type);\n\n    /*\n     *\n     * HELPER DIRECTIVE for IPv6\n     *\n     * server-ipv6 2001:db8::/64\n     *\n     * EXPANDS TO:\n     *\n     * tun-ipv6\n     * push \"tun-ipv6\"\n     * ifconfig-ipv6 2001:db8::1 2001:db8::2\n     * if !nopool:\n     *   ifconfig-ipv6-pool 2001:db8::1000/64\n     *\n     */\n    if (o->server_ipv6_defined)\n    {\n        if (o->client)\n        {\n            msg(M_USAGE, \"--server-ipv6 and --client cannot be used together\");\n        }\n\n        if (o->server_flags & SF_NOPOOL)\n        {\n            msg(M_USAGE, \"--server-ipv6 is incompatible with 'nopool' option\");\n        }\n        if (o->ifconfig_ipv6_pool_defined)\n        {\n            msg(M_USAGE,\n                \"--server-ipv6 already defines an ifconfig-ipv6-pool, so you can't also specify --ifconfig-pool explicitly\");\n        }\n\n        o->mode = MODE_SERVER;\n        o->tls_server = true;\n\n        /* local ifconfig is \"base address + 1\" and \"+2\" */\n        o->ifconfig_ipv6_local = print_in6_addr(add_in6_addr(o->server_network_ipv6, 1), 0, &o->gc);\n        o->ifconfig_ipv6_remote =\n            print_in6_addr(add_in6_addr(o->server_network_ipv6, 2), 0, &o->gc);\n        o->ifconfig_ipv6_netbits = o->server_netbits_ipv6;\n\n        /* basic sanity check */\n        ASSERT(o->server_netbits_ipv6 >= 64 && o->server_netbits_ipv6 <= 124);\n\n        o->ifconfig_ipv6_pool_defined = true;\n        /* For large enough pools we keep the original behaviour of adding\n         * 0x1000 when computing the base.\n         *\n         * Smaller pools can't get that far, therefore we just increase by 2\n         */\n        o->ifconfig_ipv6_pool_base =\n            add_in6_addr(o->server_network_ipv6, o->server_netbits_ipv6 < 112 ? 0x1000 : 2);\n        o->ifconfig_ipv6_pool_netbits = o->server_netbits_ipv6;\n\n        push_option(o, \"tun-ipv6\", M_USAGE);\n    }\n\n    /*\n     *\n     * HELPER DIRECTIVE:\n     *\n     * server 10.8.0.0 255.255.255.0\n     *\n     * EXPANDS TO:\n     *\n     * mode server\n     * tls-server\n     * push \"topology [topology]\"\n     *\n     * if tun AND (topology == net30 OR topology == p2p):\n     *   ifconfig 10.8.0.1 10.8.0.2\n     *   if !nopool:\n     *     ifconfig-pool 10.8.0.4 10.8.0.251\n     *   route 10.8.0.0 255.255.255.0\n     *   if client-to-client:\n     *     push \"route 10.8.0.0 255.255.255.0\"\n     *   else if topology == net30:\n     *     push \"route 10.8.0.1\"\n     *\n     * if tap OR (tun AND topology == subnet):\n     *   ifconfig 10.8.0.1 255.255.255.0\n     *   if !nopool:\n     *     ifconfig-pool 10.8.0.2 10.8.0.254 255.255.255.0\n     *   push \"route-gateway 10.8.0.1\"\n     *   if route-gateway unset:\n     *     route-gateway 10.8.0.2\n     */\n\n    if (o->server_defined)\n    {\n        int netbits = -2;\n        bool status = false;\n\n        if (o->client)\n        {\n            msg(M_USAGE, \"--server and --client cannot be used together\");\n        }\n\n        if (o->server_bridge_defined || o->server_bridge_proxy_dhcp)\n        {\n            msg(M_USAGE, \"--server and --server-bridge cannot be used together\");\n        }\n\n        if (o->shared_secret_file)\n        {\n            msg(M_USAGE,\n                \"--server and --secret cannot be used together (you must use SSL/TLS keys)\");\n        }\n\n        if (!(o->server_flags & SF_NOPOOL) && o->ifconfig_pool_defined)\n        {\n            msg(M_USAGE,\n                \"--server already defines an ifconfig-pool, so you can't also specify --ifconfig-pool explicitly\");\n        }\n\n        if (!(dev == DEV_TYPE_TAP || dev == DEV_TYPE_TUN))\n        {\n            msg(M_USAGE, \"--server directive only makes sense with --dev tun or --dev tap\");\n        }\n\n        status = netmask_to_netbits(o->server_network, o->server_netmask, &netbits);\n        if (!status)\n        {\n            msg(M_USAGE, \"--server directive network/netmask combination is invalid\");\n        }\n\n        if (netbits < 0)\n        {\n            msg(M_USAGE, \"--server directive netmask is invalid\");\n        }\n\n        if (netbits < IFCONFIG_POOL_MIN_NETBITS)\n        {\n            msg(M_USAGE,\n                \"--server directive netmask allows for too many host addresses (subnet must be %s or higher)\",\n                print_netmask(IFCONFIG_POOL_MIN_NETBITS, &gc));\n        }\n\n        if (dev == DEV_TYPE_TUN)\n        {\n            int pool_end_reserve = 4;\n\n            if (netbits > 29)\n            {\n                msg(M_USAGE,\n                    \"--server directive when used with --dev tun must define a subnet of %s or lower\",\n                    print_netmask(29, &gc));\n            }\n\n            if (netbits == 29)\n            {\n                pool_end_reserve = 0;\n            }\n\n            o->mode = MODE_SERVER;\n            o->tls_server = true;\n            /* Need to know topology now */\n            helper_setdefault_topology(o);\n\n            if (o->topology == TOP_NET30 || o->topology == TOP_P2P)\n            {\n                o->ifconfig_local = print_in_addr_t(o->server_network + 1, 0, &o->gc);\n                o->ifconfig_remote_netmask = print_in_addr_t(o->server_network + 2, 0, &o->gc);\n\n                if (!(o->server_flags & SF_NOPOOL))\n                {\n                    o->ifconfig_pool_defined = true;\n                    o->ifconfig_pool_start = o->server_network + 4;\n                    o->ifconfig_pool_end =\n                        (o->server_network | ~o->server_netmask) - pool_end_reserve;\n                    ifconfig_pool_verify_range(M_USAGE, o->ifconfig_pool_start,\n                                               o->ifconfig_pool_end);\n                }\n\n                helper_add_route(o->server_network, o->server_netmask, o);\n                if (o->enable_c2c)\n                {\n                    push_option(o, print_opt_route(o->server_network, o->server_netmask, &o->gc),\n                                M_USAGE);\n                }\n                else if (o->topology == TOP_NET30)\n                {\n                    push_option(o, print_opt_route(o->server_network + 1, 0, &o->gc), M_USAGE);\n                }\n            }\n            else if (o->topology == TOP_SUBNET)\n            {\n                o->ifconfig_local = print_in_addr_t(o->server_network + 1, 0, &o->gc);\n                o->ifconfig_remote_netmask = print_in_addr_t(o->server_netmask, 0, &o->gc);\n\n                if (!(o->server_flags & SF_NOPOOL))\n                {\n                    o->ifconfig_pool_defined = true;\n                    o->ifconfig_pool_start = o->server_network + 2;\n                    o->ifconfig_pool_end = (o->server_network | ~o->server_netmask) - 1;\n                    ifconfig_pool_verify_range(M_USAGE, o->ifconfig_pool_start,\n                                               o->ifconfig_pool_end);\n                }\n                o->ifconfig_pool_netmask = o->server_netmask;\n\n                push_option(o, print_opt_route_gateway(o->server_network + 1, &o->gc), M_USAGE);\n                if (!o->route_default_gateway)\n                {\n                    o->route_default_gateway = print_in_addr_t(o->server_network + 2, 0, &o->gc);\n                }\n            }\n            else\n            {\n                ASSERT(0);\n            }\n\n            push_option(o, print_opt_topology(o->topology, &o->gc), M_USAGE);\n\n            if (o->topology == TOP_NET30 && !(o->server_flags & SF_NOPOOL))\n            {\n                msg(M_WARN, \"WARNING: --topology net30 support for server \"\n                            \"configs with IPv4 pools will be removed in a future \"\n                            \"release. Please migrate to --topology subnet as soon \"\n                            \"as possible.\");\n            }\n        }\n        else if (dev == DEV_TYPE_TAP)\n        {\n            if (netbits > 30)\n            {\n                msg(M_USAGE,\n                    \"--server directive when used with --dev tap must define a subnet of %s or lower\",\n                    print_netmask(30, &gc));\n            }\n\n            o->mode = MODE_SERVER;\n            o->tls_server = true;\n            o->ifconfig_local = print_in_addr_t(o->server_network + 1, 0, &o->gc);\n            o->ifconfig_remote_netmask = print_in_addr_t(o->server_netmask, 0, &o->gc);\n\n            if (!(o->server_flags & SF_NOPOOL))\n            {\n                o->ifconfig_pool_defined = true;\n                o->ifconfig_pool_start = o->server_network + 2;\n                o->ifconfig_pool_end = (o->server_network | ~o->server_netmask) - 1;\n                ifconfig_pool_verify_range(M_USAGE, o->ifconfig_pool_start, o->ifconfig_pool_end);\n            }\n            o->ifconfig_pool_netmask = o->server_netmask;\n\n            push_option(o, print_opt_route_gateway(o->server_network + 1, &o->gc), M_USAGE);\n        }\n        else\n        {\n            ASSERT(0);\n        }\n\n        /* set push-ifconfig-constraint directive */\n        if ((dev == DEV_TYPE_TAP || o->topology == TOP_SUBNET))\n        {\n            o->push_ifconfig_constraint_defined = true;\n            o->push_ifconfig_constraint_network = o->server_network;\n            o->push_ifconfig_constraint_netmask = o->server_netmask;\n        }\n    }\n\n    /*\n     * HELPER DIRECTIVE:\n     *\n     * server-bridge 10.8.0.4 255.255.255.0 10.8.0.128 10.8.0.254\n     *\n     * EXPANDS TO:\n     *\n     * mode server\n     * tls-server\n     *\n     * ifconfig-pool 10.8.0.128 10.8.0.254 255.255.255.0\n     * push \"route-gateway 10.8.0.4\"\n     *\n     * OR\n     *\n     * server-bridge\n     *\n     * EXPANDS TO:\n     *\n     * mode server\n     * tls-server\n     *\n     * if !nogw:\n     *   push \"route-gateway dhcp\"\n     */\n    else if (o->server_bridge_defined || o->server_bridge_proxy_dhcp)\n    {\n        if (o->client)\n        {\n            msg(M_USAGE, \"--server-bridge and --client cannot be used together\");\n        }\n\n        if (!(o->server_flags & SF_NOPOOL) && o->ifconfig_pool_defined)\n        {\n            msg(M_USAGE,\n                \"--server-bridge already defines an ifconfig-pool, so you can't also specify --ifconfig-pool explicitly\");\n        }\n\n        if (o->shared_secret_file)\n        {\n            msg(M_USAGE,\n                \"--server-bridge and --secret cannot be used together (you must use SSL/TLS keys)\");\n        }\n\n        if (dev != DEV_TYPE_TAP)\n        {\n            msg(M_USAGE, \"--server-bridge directive only makes sense with --dev tap\");\n        }\n\n        if (o->server_bridge_defined)\n        {\n            verify_common_subnet(\"--server-bridge\", o->server_bridge_ip,\n                                 o->server_bridge_pool_start, o->server_bridge_netmask);\n            verify_common_subnet(\"--server-bridge\", o->server_bridge_pool_start,\n                                 o->server_bridge_pool_end, o->server_bridge_netmask);\n            verify_common_subnet(\"--server-bridge\", o->server_bridge_ip, o->server_bridge_pool_end,\n                                 o->server_bridge_netmask);\n        }\n\n        o->mode = MODE_SERVER;\n        o->tls_server = true;\n\n        if (o->server_bridge_defined)\n        {\n            o->ifconfig_pool_defined = true;\n            o->ifconfig_pool_start = o->server_bridge_pool_start;\n            o->ifconfig_pool_end = o->server_bridge_pool_end;\n            ifconfig_pool_verify_range(M_USAGE, o->ifconfig_pool_start, o->ifconfig_pool_end);\n            o->ifconfig_pool_netmask = o->server_bridge_netmask;\n            push_option(o, print_opt_route_gateway(o->server_bridge_ip, &o->gc), M_USAGE);\n        }\n        else if (o->server_bridge_proxy_dhcp && !(o->server_flags & SF_NO_PUSH_ROUTE_GATEWAY))\n        {\n            push_option(o, print_opt_route_gateway_dhcp(&o->gc), M_USAGE);\n        }\n    }\n\n    /*\n     * HELPER DIRECTIVE:\n     *\n     * client\n     *\n     * EXPANDS TO:\n     *\n     * pull\n     * tls-client\n     */\n    else if (o->client)\n    {\n        o->pull = true;\n        o->tls_client = true;\n    }\n\n    gc_free(&gc);\n}\n\n/*\n *\n * HELPER DIRECTIVE:\n *\n * keepalive 10 60\n *\n * EXPANDS TO:\n *\n * if mode server:\n *   ping 10\n *   ping-restart 120\n *   push \"ping 10\"\n *   push \"ping-restart 60\"\n * else\n *   ping 10\n *   ping-restart 60\n */\nvoid\nhelper_keepalive(struct options *o)\n{\n    if (o->keepalive_ping || o->keepalive_timeout)\n    {\n        /*\n         * Sanity checks.\n         */\n        if (o->keepalive_ping <= 0 || o->keepalive_timeout <= 0)\n        {\n            msg(M_USAGE, \"--keepalive parameters must be > 0\");\n        }\n        if (o->keepalive_ping * 2 > o->keepalive_timeout)\n        {\n            msg(M_USAGE,\n                \"the second parameter to --keepalive (restart timeout=%d) must be at least twice the value of the first parameter (ping interval=%d).  A ratio of 1:5 or 1:6 would be even better.  Recommended setting is --keepalive 10 60.\",\n                o->keepalive_timeout, o->keepalive_ping);\n        }\n        if (o->ping_send_timeout || o->ping_rec_timeout)\n        {\n            msg(M_USAGE,\n                \"--keepalive conflicts with --ping, --ping-exit, or --ping-restart.  If you use --keepalive, you don't need any of the other --ping directives.\");\n        }\n\n        /*\n         * Expand.\n         */\n        if (o->mode == MODE_POINT_TO_POINT)\n        {\n            o->ping_rec_timeout_action = PING_RESTART;\n            o->ping_send_timeout = o->keepalive_ping;\n            o->ping_rec_timeout = o->keepalive_timeout;\n        }\n        else if (o->mode == MODE_SERVER)\n        {\n            o->ping_rec_timeout_action = PING_RESTART;\n            o->ping_send_timeout = o->keepalive_ping;\n            o->ping_rec_timeout = o->keepalive_timeout * 2;\n            push_option(o, print_str_int(\"ping\", o->keepalive_ping, &o->gc), M_USAGE);\n            push_option(o, print_str_int(\"ping-restart\", o->keepalive_timeout, &o->gc), M_USAGE);\n        }\n        else\n        {\n            ASSERT(0);\n        }\n    }\n}\n\n/*\n *\n * HELPER DIRECTIVE:\n *\n * tcp-nodelay\n *\n * EXPANDS TO:\n *\n * if mode server:\n *   socket-flags TCP_NODELAY\n *   push \"socket-flags TCP_NODELAY\"\n */\nvoid\nhelper_tcp_nodelay(struct options *o)\n{\n    if (o->server_flags & SF_TCP_NODELAY_HELPER)\n    {\n        if (o->mode == MODE_SERVER)\n        {\n            o->sockflags |= SF_TCP_NODELAY;\n            push_option(o, print_str(\"socket-flags TCP_NODELAY\", &o->gc), M_USAGE);\n        }\n        else\n        {\n            o->sockflags |= SF_TCP_NODELAY;\n        }\n    }\n}\n"
  },
  {
    "path": "src/openvpn/helper.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * Process helper directives such as server, client, and keepalive.\n */\n\n#ifndef HELPER_H\n#define HELPER_H\n\n#include \"options.h\"\n\nvoid helper_setdefault_topology(struct options *o);\n\nvoid helper_keepalive(struct options *o);\n\nvoid helper_client_server(struct options *o);\n\nvoid helper_tcp_nodelay(struct options *o);\n\n#endif\n"
  },
  {
    "path": "src/openvpn/httpdigest.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if PROXY_DIGEST_AUTH\n\n#include \"crypto.h\"\n#include \"httpdigest.h\"\n\nstatic void\nCvtHex(IN HASH Bin, OUT HASHHEX Hex)\n{\n    unsigned short i;\n    unsigned char j;\n\n    for (i = 0; i < HASHLEN; i++)\n    {\n        j = (Bin[i] >> 4) & 0xf;\n        if (j <= 9)\n        {\n            Hex[i * 2] = (j + '0');\n        }\n        else\n        {\n            Hex[i * 2] = (unsigned char)(j + 'a' - 10);\n        }\n        j = Bin[i] & 0xf;\n        if (j <= 9)\n        {\n            Hex[i * 2 + 1] = (j + '0');\n        }\n        else\n        {\n            Hex[i * 2 + 1] = (unsigned char)(j + 'a' - 10);\n        }\n    }\n    Hex[HASHHEXLEN] = '\\0';\n}\n\n/* calculate H(A1) as per spec */\nvoid\nDigestCalcHA1(IN char *pszAlg, IN char *pszUserName, IN char *pszRealm, IN char *pszPassword,\n              IN char *pszNonce, IN char *pszCNonce, OUT HASHHEX SessionKey)\n{\n    HASH HA1;\n    md_ctx_t *md5_ctx = md_ctx_new();\n\n    md_ctx_init(md5_ctx, \"MD5\");\n    md_ctx_update(md5_ctx, (const uint8_t *)pszUserName, strlen(pszUserName));\n    md_ctx_update(md5_ctx, (const uint8_t *)\":\", 1);\n    md_ctx_update(md5_ctx, (const uint8_t *)pszRealm, strlen(pszRealm));\n    md_ctx_update(md5_ctx, (const uint8_t *)\":\", 1);\n    md_ctx_update(md5_ctx, (const uint8_t *)pszPassword, strlen(pszPassword));\n    md_ctx_final(md5_ctx, HA1);\n    if (pszAlg && strcasecmp(pszAlg, \"md5-sess\") == 0)\n    {\n        md_ctx_init(md5_ctx, \"MD5\");\n        md_ctx_update(md5_ctx, HA1, HASHLEN);\n        md_ctx_update(md5_ctx, (const uint8_t *)\":\", 1);\n        md_ctx_update(md5_ctx, (const uint8_t *)pszNonce, strlen(pszNonce));\n        md_ctx_update(md5_ctx, (const uint8_t *)\":\", 1);\n        md_ctx_update(md5_ctx, (const uint8_t *)pszCNonce, strlen(pszCNonce));\n        md_ctx_final(md5_ctx, HA1);\n    }\n    md_ctx_cleanup(md5_ctx);\n    md_ctx_free(md5_ctx);\n    CvtHex(HA1, SessionKey);\n}\n\n/* calculate request-digest/response-digest as per HTTP Digest spec */\nvoid\nDigestCalcResponse(IN HASHHEX HA1,         /* H(A1) */\n                   IN char *pszNonce,      /* nonce from server */\n                   IN char *pszNonceCount, /* 8 hex digits */\n                   IN char *pszCNonce,     /* client nonce */\n                   IN char *pszQop,        /* qop-value: \"\", \"auth\", \"auth-int\" */\n                   IN char *pszMethod,     /* method from the request */\n                   IN char *pszDigestUri,  /* requested URL */\n                   IN HASHHEX HEntity,     /* H(entity body) if qop=\"auth-int\" */\n                   OUT HASHHEX Response    /* request-digest or response-digest */\n)\n{\n    HASH HA2;\n    HASH RespHash;\n    HASHHEX HA2Hex;\n\n    md_ctx_t *md5_ctx = md_ctx_new();\n\n    /* calculate H(A2) */\n    md_ctx_init(md5_ctx, \"MD5\");\n    md_ctx_update(md5_ctx, (const uint8_t *)pszMethod, strlen(pszMethod));\n    md_ctx_update(md5_ctx, (const uint8_t *)\":\", 1);\n    md_ctx_update(md5_ctx, (const uint8_t *)pszDigestUri, strlen(pszDigestUri));\n    if (strcasecmp(pszQop, \"auth-int\") == 0)\n    {\n        md_ctx_update(md5_ctx, (const uint8_t *)\":\", 1);\n        md_ctx_update(md5_ctx, HEntity, HASHHEXLEN);\n    }\n    md_ctx_final(md5_ctx, HA2);\n    CvtHex(HA2, HA2Hex);\n\n    /* calculate response */\n    md_ctx_init(md5_ctx, \"MD5\");\n    md_ctx_update(md5_ctx, HA1, HASHHEXLEN);\n    md_ctx_update(md5_ctx, (const uint8_t *)\":\", 1);\n    md_ctx_update(md5_ctx, (const uint8_t *)pszNonce, strlen(pszNonce));\n    md_ctx_update(md5_ctx, (const uint8_t *)\":\", 1);\n    if (*pszQop)\n    {\n        md_ctx_update(md5_ctx, (const uint8_t *)pszNonceCount, strlen(pszNonceCount));\n        md_ctx_update(md5_ctx, (const uint8_t *)\":\", 1);\n        md_ctx_update(md5_ctx, (const uint8_t *)pszCNonce, strlen(pszCNonce));\n        md_ctx_update(md5_ctx, (const uint8_t *)\":\", 1);\n        md_ctx_update(md5_ctx, (const uint8_t *)pszQop, strlen(pszQop));\n        md_ctx_update(md5_ctx, (const uint8_t *)\":\", 1);\n    }\n    md_ctx_update(md5_ctx, HA2Hex, HASHHEXLEN);\n    md_ctx_final(md5_ctx, RespHash);\n    md_ctx_cleanup(md5_ctx);\n    md_ctx_free(md5_ctx);\n    CvtHex(RespHash, Response);\n}\n\n#endif /* if PROXY_DIGEST_AUTH */\n"
  },
  {
    "path": "src/openvpn/httpdigest.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#if PROXY_DIGEST_AUTH\n\n#define HASHLEN 16\ntypedef unsigned char HASH[HASHLEN];\n#define HASHHEXLEN 32\ntypedef unsigned char HASHHEX[HASHHEXLEN + 1];\n#undef IN\n#undef OUT\n#define IN const\n#define OUT\n\n/* calculate H(A1) as per HTTP Digest spec */\nvoid DigestCalcHA1(IN char *pszAlg, IN char *pszUserName, IN char *pszRealm, IN char *pszPassword,\n                   IN char *pszNonce, IN char *pszCNonce, OUT HASHHEX SessionKey);\n\n/* calculate request-digest/response-digest as per HTTP Digest spec */\nvoid DigestCalcResponse(IN HASHHEX HA1,         /* H(A1) */\n                        IN char *pszNonce,      /* nonce from server */\n                        IN char *pszNonceCount, /* 8 hex digits */\n                        IN char *pszCNonce,     /* client nonce */\n                        IN char *pszQop,        /* qop-value: \"\", \"auth\", \"auth-int\" */\n                        IN char *pszMethod,     /* method from the request */\n                        IN char *pszDigestUri,  /* requested URL */\n                        IN HASHHEX HEntity,     /* H(entity body) if qop=\"auth-int\" */\n                        OUT HASHHEX Response    /* request-digest or response-digest */\n);\n\n#endif /* if PROXY_DIGEST_AUTH */\n"
  },
  {
    "path": "src/openvpn/init.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#ifdef ENABLE_SYSTEMD\n#include <systemd/sd-daemon.h>\n#endif\n\n#include \"win32.h\"\n#include \"init.h\"\n#include \"run_command.h\"\n#include \"sig.h\"\n#include \"occ.h\"\n#include \"list.h\"\n#include \"otime.h\"\n#include \"pool.h\"\n#include \"gremlin.h\"\n#include \"occ.h\"\n#include \"pkcs11.h\"\n#include \"ps.h\"\n#include \"lladdr.h\"\n#include \"ping.h\"\n#include \"ssl_verify.h\"\n#include \"ssl_ncp.h\"\n#include \"tls_crypt.h\"\n#include \"forward.h\"\n#include \"auth_token.h\"\n#include \"mss.h\"\n#include \"mudp.h\"\n#include \"dco.h\"\n#include \"tun_afunix.h\"\n\n#include \"memdbg.h\"\n\n\nstatic struct context *static_context;  /* GLOBAL */\nstatic const char *saved_pid_file_name; /* GLOBAL */\n\n/*\n * Crypto initialization flags\n */\n#define CF_LOAD_PERSISTED_PACKET_ID (1 << 0)\n#define CF_INIT_TLS_MULTI           (1 << 1)\n#define CF_INIT_TLS_AUTH_STANDALONE (1 << 2)\n\nstatic void do_init_first_time(struct context *c);\n\nstatic bool do_deferred_p2p_ncp(struct context *c);\n\nstatic void\ncontext_clear(struct context *c)\n{\n    CLEAR(*c);\n}\n\nstatic void\ncontext_clear_1(struct context *c)\n{\n    CLEAR(c->c1);\n}\n\nvoid\ncontext_clear_2(struct context *c)\n{\n    CLEAR(c->c2);\n}\n\nvoid\ncontext_clear_all_except_first_time(struct context *c)\n{\n    const bool first_time_save = c->first_time;\n    const struct context_persist cpsave = c->persist;\n    context_clear(c);\n    c->first_time = first_time_save;\n    c->persist = cpsave;\n}\n\n/*\n * Pass tunnel endpoint and MTU parms to a user-supplied script.\n * Used to execute the up/down script/plugins.\n */\nstatic void\nrun_up_down(const char *command, const struct plugin_list *plugins, int plugin_type,\n            const char *arg,\n#ifdef _WIN32\n            DWORD adapter_index,\n#endif\n            const char *dev_type, int tun_mtu, const char *ifconfig_local,\n            const char *ifconfig_remote, const char *context, const char *signal_text,\n            const char *script_type, struct env_set *es)\n{\n    struct gc_arena gc = gc_new();\n\n    if (signal_text)\n    {\n        setenv_str(es, \"signal\", signal_text);\n    }\n    setenv_str(es, \"script_context\", context);\n    setenv_int(es, \"tun_mtu\", tun_mtu);\n    setenv_str(es, \"dev\", arg);\n    if (dev_type)\n    {\n        setenv_str(es, \"dev_type\", dev_type);\n    }\n#ifdef _WIN32\n    setenv_int(es, \"dev_idx\", adapter_index);\n#endif\n\n    if (!ifconfig_local)\n    {\n        ifconfig_local = \"\";\n    }\n    if (!ifconfig_remote)\n    {\n        ifconfig_remote = \"\";\n    }\n    if (!context)\n    {\n        context = \"\";\n    }\n\n    if (plugin_defined(plugins, plugin_type))\n    {\n        struct argv argv = argv_new();\n        ASSERT(arg);\n        argv_printf(&argv, \"%s %d 0 %s %s %s\", arg, tun_mtu, ifconfig_local, ifconfig_remote,\n                    context);\n\n        if (plugin_call(plugins, plugin_type, &argv, NULL, es) != OPENVPN_PLUGIN_FUNC_SUCCESS)\n        {\n            msg(M_FATAL, \"ERROR: up/down plugin call failed\");\n        }\n\n        argv_free(&argv);\n    }\n\n    if (command)\n    {\n        struct argv argv = argv_new();\n        ASSERT(arg);\n        setenv_str(es, \"script_type\", script_type);\n        argv_parse_cmd(&argv, command);\n        argv_printf_cat(&argv, \"%s %d 0 %s %s %s\", arg, tun_mtu, ifconfig_local, ifconfig_remote,\n                        context);\n        argv_msg(M_INFO, &argv);\n        openvpn_run_script(&argv, es, S_FATAL, \"--up/--down\");\n        argv_free(&argv);\n    }\n\n    gc_free(&gc);\n}\n\n/*\n * Should be called after options->ce is modified at the top\n * of a SIGUSR1 restart.\n */\nstatic void\nupdate_options_ce_post(struct options *options)\n{\n    /*\n     * In pull mode, we usually import --ping/--ping-restart parameters from\n     * the server.  However we should also set an initial default --ping-restart\n     * for the period of time before we pull the --ping-restart parameter\n     * from the server.\n     */\n    if (options->pull && options->ping_rec_timeout_action == PING_UNDEF\n        && proto_is_dgram(options->ce.proto))\n    {\n        options->ping_rec_timeout = PRE_PULL_INITIAL_PING_RESTART;\n        options->ping_rec_timeout_action = PING_RESTART;\n    }\n}\n\n#ifdef ENABLE_MANAGEMENT\nstatic bool\nmanagement_callback_proxy_cmd(void *arg, const char **p)\n{\n    struct context *c = arg;\n    struct connection_entry *ce = &c->options.ce;\n    struct gc_arena *gc = &c->c2.gc;\n    bool ret = false;\n\n    update_time();\n    if (streq(p[1], \"NONE\"))\n    {\n        ret = true;\n    }\n    else if (p[2] && p[3])\n    {\n        if (streq(p[1], \"HTTP\"))\n        {\n            struct http_proxy_options *ho;\n            if (ce->proto != PROTO_TCP && ce->proto != PROTO_TCP_CLIENT)\n            {\n                msg(M_WARN, \"HTTP proxy support only works for TCP based connections\");\n                return false;\n            }\n            ho = init_http_proxy_options_once(&ce->http_proxy_options, gc);\n            ho->server = string_alloc(p[2], gc);\n            ho->port = string_alloc(p[3], gc);\n            ho->auth_retry = (p[4] && streq(p[4], \"nct\") ? PAR_NCT : PAR_ALL);\n            ret = true;\n        }\n        else if (streq(p[1], \"SOCKS\"))\n        {\n            ce->socks_proxy_server = string_alloc(p[2], gc);\n            ce->socks_proxy_port = string_alloc(p[3], gc);\n            ret = true;\n        }\n    }\n    else\n    {\n        msg(M_WARN, \"Bad proxy command\");\n    }\n\n    ce->flags &= ~CE_MAN_QUERY_PROXY;\n\n    return ret;\n}\n\nstatic bool\nce_management_query_proxy(struct context *c)\n{\n    const struct connection_list *l = c->options.connection_list;\n    struct connection_entry *ce = &c->options.ce;\n    struct gc_arena gc;\n    bool ret = true;\n\n    update_time();\n    if (management)\n    {\n        gc = gc_new();\n        {\n            struct buffer out = alloc_buf_gc(256, &gc);\n            buf_printf(&out, \">PROXY:%u,%s,%s\", (l ? l->current : 0) + 1,\n                       (proto_is_udp(ce->proto) ? \"UDP\" : \"TCP\"), np(ce->remote));\n            management_notify_generic(management, BSTR(&out));\n            management->persist.special_state_msg = BSTR(&out);\n        }\n        ce->flags |= CE_MAN_QUERY_PROXY;\n        while (ce->flags & CE_MAN_QUERY_PROXY)\n        {\n            management_event_loop_n_seconds(management, 1);\n            if (IS_SIG(c))\n            {\n                ret = false;\n                break;\n            }\n        }\n        management->persist.special_state_msg = NULL;\n        gc_free(&gc);\n    }\n\n    return ret;\n}\n\n/**\n * This method sends a custom control channel message\n *\n * This will write the control message\n *\n *  command parm1,parm2,...\n *\n * to the control channel.\n *\n * @param arg           The context struct\n * @param command       The command being sent\n * @param parameters    the parameters to the command\n * @return              if sending was successful\n */\nstatic bool\nmanagement_callback_send_cc_message(void *arg, const char *command, const char *parameters)\n{\n    struct context *c = (struct context *)arg;\n    size_t len = strlen(command) + 1 + strlen(parameters) + 1;\n    if (len > PUSH_BUNDLE_SIZE)\n    {\n        return false;\n    }\n\n    struct gc_arena gc = gc_new();\n    struct buffer buf = alloc_buf_gc(len, &gc);\n    ASSERT(buf_printf(&buf, \"%s\", command));\n    if (parameters)\n    {\n        ASSERT(buf_printf(&buf, \",%s\", parameters));\n    }\n    bool status = send_control_channel_string(c, BSTR(&buf), D_PUSH);\n\n    gc_free(&gc);\n    return status;\n}\n\nstatic unsigned int\nmanagement_callback_remote_entry_count(void *arg)\n{\n    ASSERT(arg);\n    struct context *c = (struct context *)arg;\n    struct connection_list *l = c->options.connection_list;\n\n    return l->len;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\nstatic bool\nmanagement_callback_remote_entry_get(void *arg, unsigned int index, char **remote)\n{\n    ASSERT(arg);\n    ASSERT(remote);\n\n    struct context *c = (struct context *)arg;\n    struct connection_list *l = c->options.connection_list;\n    bool ret = true;\n\n    if (index < l->len)\n    {\n        struct connection_entry *ce = l->array[index];\n        const char *proto = proto2ascii(ce->proto, ce->af, false);\n        const char *status = (ce->flags & CE_DISABLED) ? \"disabled\" : \"enabled\";\n\n        /* space for output including 3 commas and a nul */\n        size_t len =\n            strlen(ce->remote) + strlen(ce->remote_port) + strlen(proto) + strlen(status) + 3 + 1;\n        char *out = malloc(len);\n        check_malloc_return(out);\n\n        snprintf(out, len, \"%s,%s,%s,%s\", ce->remote, ce->remote_port, proto, status);\n        *remote = out;\n    }\n    else\n    {\n        ret = false;\n        msg(M_WARN, \"Out of bounds index in management query for remote entry: index = %u\", index);\n    }\n\n    return ret;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nstatic bool\nmanagement_callback_remote_cmd(void *arg, const char **p)\n{\n    struct context *c = (struct context *)arg;\n    struct connection_entry *ce = &c->options.ce;\n    int ret = false;\n    if (p[1]\n        && ((ce->flags >> CE_MAN_QUERY_REMOTE_SHIFT) & CE_MAN_QUERY_REMOTE_MASK)\n               == CE_MAN_QUERY_REMOTE_QUERY)\n    {\n        unsigned int flags = 0;\n        if (!strcmp(p[1], \"ACCEPT\"))\n        {\n            flags = CE_MAN_QUERY_REMOTE_ACCEPT;\n            ret = true;\n        }\n        else if (!strcmp(p[1], \"SKIP\"))\n        {\n            flags = CE_MAN_QUERY_REMOTE_SKIP;\n            ret = true;\n            c->options.ce_advance_count = (p[2]) ? atoi(p[2]) : 1;\n        }\n        else if (!strcmp(p[1], \"MOD\") && p[2] && p[3])\n        {\n            if (strlen(p[2]) < RH_HOST_LEN && strlen(p[3]) < RH_PORT_LEN)\n            {\n                struct remote_host_store *rhs = c->options.rh_store;\n                if (!rhs)\n                {\n                    ALLOC_OBJ_CLEAR_GC(rhs, struct remote_host_store, &c->options.gc);\n                    c->options.rh_store = rhs;\n                }\n                strncpynt(rhs->host, p[2], RH_HOST_LEN);\n                strncpynt(rhs->port, p[3], RH_PORT_LEN);\n\n                ce->remote = rhs->host;\n                ce->remote_port = rhs->port;\n                flags = CE_MAN_QUERY_REMOTE_MOD;\n                ret = true;\n            }\n        }\n        if (ret)\n        {\n            ce->flags &= ~(CE_MAN_QUERY_REMOTE_MASK << CE_MAN_QUERY_REMOTE_SHIFT);\n            ce->flags |= ((flags & CE_MAN_QUERY_REMOTE_MASK) << CE_MAN_QUERY_REMOTE_SHIFT);\n        }\n    }\n    return ret;\n}\n\nstatic bool\nce_management_query_remote(struct context *c)\n{\n    struct gc_arena gc = gc_new();\n    volatile struct connection_entry *ce = &c->options.ce;\n    int ce_changed = true; /* presume the connection entry will be changed */\n\n    update_time();\n    if (management)\n    {\n        struct buffer out = alloc_buf_gc(256, &gc);\n\n        buf_printf(&out, \">REMOTE:%s,%s,%s\", np(ce->remote), ce->remote_port,\n                   proto2ascii(ce->proto, ce->af, false));\n        management_notify_generic(management, BSTR(&out));\n        management->persist.special_state_msg = BSTR(&out);\n\n        ce->flags &= ~(CE_MAN_QUERY_REMOTE_MASK << CE_MAN_QUERY_REMOTE_SHIFT);\n        ce->flags |= (CE_MAN_QUERY_REMOTE_QUERY << CE_MAN_QUERY_REMOTE_SHIFT);\n        while (((ce->flags >> CE_MAN_QUERY_REMOTE_SHIFT) & CE_MAN_QUERY_REMOTE_MASK)\n               == CE_MAN_QUERY_REMOTE_QUERY)\n        {\n            management_event_loop_n_seconds(management, 1);\n            if (IS_SIG(c))\n            {\n                ce_changed = false; /* connection entry have not been set */\n                break;\n            }\n        }\n        management->persist.special_state_msg = NULL;\n    }\n    gc_free(&gc);\n\n    if (ce_changed)\n    {\n        /* If it is likely a connection entry was modified,\n         * check what changed in the flags and that it was not skipped\n         */\n        const int flags = ((ce->flags >> CE_MAN_QUERY_REMOTE_SHIFT) & CE_MAN_QUERY_REMOTE_MASK);\n        ce_changed = (flags != CE_MAN_QUERY_REMOTE_SKIP);\n    }\n    return ce_changed;\n}\n#endif /* ENABLE_MANAGEMENT */\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\n/*\n * Initialize and possibly randomize the connection list.\n *\n * Applies the Fisher-Yates shuffle algorithm to ensure all permutations\n * are equally probable, thereby eliminating shuffling bias.\n *\n * The algorithm randomly selects an element from the unshuffled portion\n * and places it at position i. There's only one way to obtain each\n * permutation through these swaps. This guarantees that each permutation\n * occurs with equal probability in theory.\n */\nstatic void\ninit_connection_list(struct context *c)\n{\n    struct connection_list *l = c->options.connection_list;\n\n    l->current = -1;\n    if (c->options.remote_random)\n    {\n        int i;\n        for (i = l->len - 1; i > 0; --i)\n        {\n            const int j = get_random() % (i + 1);\n            if (i != j)\n            {\n                struct connection_entry *tmp;\n                tmp = l->array[i];\n                l->array[i] = l->array[j];\n                l->array[j] = tmp;\n            }\n        }\n    }\n}\n\n/*\n * Clear the remote address list\n */\nstatic void\nclear_remote_addrlist(struct link_socket_addr *lsa, bool free)\n{\n    if (lsa->remote_list && free)\n    {\n        freeaddrinfo(lsa->remote_list);\n    }\n    lsa->remote_list = NULL;\n    lsa->current_remote = NULL;\n}\n\n/*\n * Increment to next connection entry\n */\nstatic void\nnext_connection_entry(struct context *c)\n{\n    struct connection_list *l = c->options.connection_list;\n    bool ce_defined;\n    struct connection_entry *ce;\n    int n_cycles = 0;\n\n    do\n    {\n        ce_defined = true;\n        if (c->options.no_advance && l->current >= 0)\n        {\n            c->options.no_advance = false;\n        }\n        else\n        {\n            /* Check if there is another resolved address to try for\n             * the current connection */\n            if (c->c1.link_socket_addrs[0].current_remote\n                && c->c1.link_socket_addrs[0].current_remote->ai_next\n                && !c->options.advance_next_remote)\n            {\n                c->c1.link_socket_addrs[0].current_remote =\n                    c->c1.link_socket_addrs[0].current_remote->ai_next;\n            }\n            else\n            {\n                c->options.advance_next_remote = false;\n                /* FIXME (schwabe) fix the persist-remote-ip option for real,\n                 * this is broken probably ever since connection lists and multiple\n                 * remote existed\n                 */\n                if (!c->options.persist_remote_ip)\n                {\n                    /* Connection entry addrinfo objects might have been\n                     * resolved earlier but the entry itself might have been\n                     * skipped by management on the previous loop.\n                     * If so, clear the addrinfo objects as close_instance does\n                     */\n                    if (c->c1.link_socket_addrs[0].remote_list)\n                    {\n                        clear_remote_addrlist(&c->c1.link_socket_addrs[0],\n                                              !c->options.resolve_in_advance);\n                    }\n\n                    /* close_instance should have cleared the addrinfo objects */\n                    ASSERT(c->c1.link_socket_addrs[0].current_remote == NULL);\n                    ASSERT(c->c1.link_socket_addrs[0].remote_list == NULL);\n                }\n                else\n                {\n                    c->c1.link_socket_addrs[0].current_remote =\n                        c->c1.link_socket_addrs[0].remote_list;\n                }\n\n                int advance_count = 1;\n\n                /* If previous connection entry was skipped by management client\n                 * with a count to advance by, apply it.\n                 */\n                if (c->options.ce_advance_count > 0)\n                {\n                    advance_count = c->options.ce_advance_count;\n                }\n\n                /*\n                 * Increase the number of connection attempts\n                 * If this is connect-retry-max * size(l)\n                 * OpenVPN will quit\n                 */\n\n                c->options.unsuccessful_attempts += advance_count;\n                l->current += advance_count;\n\n                if (l->current >= l->len)\n                {\n                    l->current %= l->len;\n                    if (++n_cycles >= 2)\n                    {\n                        msg(M_FATAL, \"No usable connection profiles are present\");\n                    }\n                }\n            }\n        }\n\n        c->options.ce_advance_count = 1;\n        ce = l->array[l->current];\n\n        if (ce->flags & CE_DISABLED)\n        {\n            ce_defined = false;\n        }\n\n        c->options.ce = *ce;\n\n#ifdef ENABLE_MANAGEMENT\n        if (ce_defined && management && management_query_remote_enabled(management))\n        {\n            /* allow management interface to override connection entry details */\n            ce_defined = ce_management_query_remote(c);\n            if (IS_SIG(c))\n            {\n                break;\n            }\n        }\n        else if (ce_defined && management && management_query_proxy_enabled(management))\n        {\n            ce_defined = ce_management_query_proxy(c);\n            if (IS_SIG(c))\n            {\n                break;\n            }\n        }\n#endif\n    } while (!ce_defined);\n\n    /* Check if this connection attempt would bring us over the limit */\n    if (c->options.connect_retry_max > 0\n        && c->options.unsuccessful_attempts > (l->len * c->options.connect_retry_max))\n    {\n        msg(M_FATAL, \"All connections have been connect-retry-max (%d) times unsuccessful, exiting\",\n            c->options.connect_retry_max);\n    }\n    update_options_ce_post(&c->options);\n}\n\n/*\n * Query for private key and auth-user-pass username/passwords\n */\nvoid\ninit_query_passwords(const struct context *c)\n{\n    /* Certificate password input */\n    if (c->options.key_pass_file)\n    {\n        pem_password_setup(c->options.key_pass_file);\n    }\n\n    /* Auth user/pass input */\n    if (c->options.auth_user_pass_file)\n    {\n        enable_auth_user_pass();\n#ifdef ENABLE_MANAGEMENT\n        auth_user_pass_setup(c->options.auth_user_pass_file, c->options.auth_user_pass_file_inline,\n                             &c->options.sc_info);\n#else\n        auth_user_pass_setup(c->options.auth_user_pass_file, c->options.auth_user_pass_file_inline,\n                             NULL);\n#endif\n    }\n}\n\n/*\n * Initialize/Uninitialize HTTP or SOCKS proxy\n */\n\nstatic void\nuninit_proxy_dowork(struct context *c)\n{\n    if (c->c1.http_proxy_owned && c->c1.http_proxy)\n    {\n        http_proxy_close(c->c1.http_proxy);\n        c->c1.http_proxy = NULL;\n        c->c1.http_proxy_owned = false;\n    }\n    if (c->c1.socks_proxy_owned && c->c1.socks_proxy)\n    {\n        socks_proxy_close(c->c1.socks_proxy);\n        c->c1.socks_proxy = NULL;\n        c->c1.socks_proxy_owned = false;\n    }\n}\n\nstatic void\ninit_proxy_dowork(struct context *c)\n{\n    bool did_http = false;\n\n    uninit_proxy_dowork(c);\n\n    if (c->options.ce.http_proxy_options)\n    {\n        c->options.ce.http_proxy_options->first_time = c->first_time;\n\n        /* Possible HTTP proxy user/pass input */\n        c->c1.http_proxy = http_proxy_new(c->options.ce.http_proxy_options);\n        if (c->c1.http_proxy)\n        {\n            did_http = true;\n            c->c1.http_proxy_owned = true;\n        }\n    }\n\n    if (!did_http && c->options.ce.socks_proxy_server)\n    {\n        c->c1.socks_proxy =\n            socks_proxy_new(c->options.ce.socks_proxy_server, c->options.ce.socks_proxy_port,\n                            c->options.ce.socks_proxy_authfile);\n        if (c->c1.socks_proxy)\n        {\n            c->c1.socks_proxy_owned = true;\n        }\n    }\n}\n\nstatic void\ninit_proxy(struct context *c)\n{\n    init_proxy_dowork(c);\n}\n\nstatic void\nuninit_proxy(struct context *c)\n{\n    uninit_proxy_dowork(c);\n}\n\nstatic void\ndo_link_socket_addr_new(struct context *c)\n{\n    ALLOC_ARRAY_CLEAR_GC(c->c1.link_socket_addrs, struct link_socket_addr, c->c1.link_sockets_num,\n                         &c->gc);\n}\n\nvoid\ncontext_init_1(struct context *c)\n{\n    context_clear_1(c);\n\n    packet_id_persist_init(&c->c1.pid_persist);\n\n    init_connection_list(c);\n\n    c->c1.link_sockets_num = c->options.ce.local_list->len;\n\n    do_link_socket_addr_new(c);\n\n#if defined(ENABLE_PKCS11)\n    if (c->first_time)\n    {\n        int i;\n        pkcs11_initialize(true, c->options.pkcs11_pin_cache_period);\n        for (i = 0; i < MAX_PARMS && c->options.pkcs11_providers[i] != NULL; i++)\n        {\n            pkcs11_addProvider(\n                c->options.pkcs11_providers[i], c->options.pkcs11_protected_authentication[i],\n                c->options.pkcs11_private_mode[i], c->options.pkcs11_cert_private[i]);\n        }\n    }\n#endif\n\n#if 0 /* test get_user_pass with GET_USER_PASS_NEED_OK flag */\n    {\n        /*\n         * In the management interface, you can okay the request by entering \"needok token-insertion-request ok\"\n         */\n        struct user_pass up;\n        CLEAR(up);\n        strcpy(up.username, \"Please insert your cryptographic token\"); /* put the high-level message in up.username */\n        get_user_pass(&up, NULL, \"token-insertion-request\", GET_USER_PASS_MANAGEMENT|GET_USER_PASS_NEED_OK);\n        msg(M_INFO, \"RET:%s\", up.password); /* will return the third argument to management interface\n                                             * 'needok' command, usually 'ok' or 'cancel'. */\n    }\n#endif\n\n#ifdef ENABLE_SYSTEMD\n    /* We can report the PID via getpid() to systemd here as OpenVPN will not\n     * do any fork due to daemon() a future call.\n     * See possibly_become_daemon() [init.c] for more details.\n     */\n    sd_notifyf(0, \"READY=1\\nSTATUS=Pre-connection initialization successful\\nMAINPID=%lu\",\n               (unsigned long)getpid());\n#endif\n}\n\nvoid\ncontext_gc_free(struct context *c)\n{\n    gc_free(&c->c2.gc);\n    gc_free(&c->options.gc);\n    gc_free(&c->gc);\n}\n\n#if PORT_SHARE\n\nstatic void\nclose_port_share(void)\n{\n    if (port_share)\n    {\n        port_share_close(port_share);\n        port_share = NULL;\n    }\n}\n\nstatic void\ninit_port_share(struct context *c)\n{\n    if (!port_share && (c->options.port_share_host && c->options.port_share_port))\n    {\n        port_share =\n            port_share_open(c->options.port_share_host, c->options.port_share_port,\n                            c->c2.frame.buf.payload_size, c->options.port_share_journal_dir);\n        if (port_share == NULL)\n        {\n            msg(M_FATAL, \"Fatal error: Port sharing failed\");\n        }\n    }\n}\n\n#endif /* if PORT_SHARE */\n\n\nbool\ninit_static(void)\n{\n#if defined(DMALLOC)\n    crypto_init_dmalloc();\n#endif\n\n\n    /*\n     * Initialize random number seed.  random() is only used\n     * when \"weak\" random numbers are acceptable.\n     * SSL library routines are always used when cryptographically\n     * strong random numbers are required.\n     */\n    struct timeval tv;\n    if (!gettimeofday(&tv, NULL))\n    {\n        const unsigned int seed = (unsigned int)tv.tv_sec ^ tv.tv_usec;\n        srandom(seed);\n    }\n\n    error_reset();        /* initialize error.c */\n    reset_check_status(); /* initialize status check code in socket.c */\n\n#ifdef _WIN32\n    init_win32();\n#endif\n\n#ifdef OPENVPN_DEBUG_COMMAND_LINE\n    {\n        int i;\n        for (i = 0; i < argc; ++i)\n        {\n            msg(M_INFO, \"argv[%d] = '%s'\", i, argv[i]);\n        }\n    }\n#endif\n\n    update_time();\n\n    init_ssl_lib();\n\n#ifdef SCHEDULE_TEST\n    schedule_test();\n    return false;\n#endif\n\n#ifdef IFCONFIG_POOL_TEST\n    ifconfig_pool_test(0x0A010004, 0x0A0100FF);\n    return false;\n#endif\n\n#ifdef TIME_TEST\n    time_test();\n    return false;\n#endif\n\n#ifdef GEN_PATH_TEST\n    {\n        struct gc_arena gc = gc_new();\n        const char *fn = gen_path(\"foo\", \"bar\", &gc);\n        printf(\"%s\\n\", fn);\n        gc_free(&gc);\n    }\n    return false;\n#endif\n\n#ifdef STATUS_PRINTF_TEST\n    {\n        struct gc_arena gc = gc_new();\n        const char *tmp_file = platform_create_temp_file(\"/tmp\", \"foo\", &gc);\n        struct status_output *so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);\n        status_printf(so, \"%s\", \"foo\");\n        status_printf(so, \"%s\", \"bar\");\n        if (!status_close(so))\n        {\n            msg(M_WARN, \"STATUS_PRINTF_TEST: %s: write error\", tmp_file);\n        }\n        gc_free(&gc);\n    }\n    return false;\n#endif\n\n    return true;\n}\n\nvoid\nuninit_static(void)\n{\n    free_ssl_lib();\n\n#ifdef ENABLE_PKCS11\n    pkcs11_terminate();\n#endif\n\n#if PORT_SHARE\n    close_port_share();\n#endif\n\n#if defined(MEASURE_TLS_HANDSHAKE_STATS)\n    show_tls_performance_stats();\n#endif\n}\n\nvoid\ninit_verb_mute(struct context *c, unsigned int flags)\n{\n    if (flags & IVM_LEVEL_1)\n    {\n        /* set verbosity and mute levels */\n        set_check_status(D_LINK_ERRORS, D_READ_WRITE);\n        set_debug_level(c->options.verbosity, SDL_CONSTRAIN);\n        set_mute_cutoff(c->options.mute);\n    }\n\n    /* special D_LOG_RW mode */\n    if (flags & IVM_LEVEL_2)\n    {\n        c->c2.log_rw = (check_debug_level(D_LOG_RW) && !check_debug_level(D_LOG_RW + 1));\n    }\n}\n\n/*\n * Possibly set --dev based on --dev-node.\n * For example, if --dev-node /tmp/foo/tun, and --dev undefined,\n * set --dev to tun.\n */\nvoid\ninit_options_dev(struct options *options)\n{\n    if (!options->dev && options->dev_node)\n    {\n        /* POSIX basename() implementations may modify its arguments */\n        char *dev_node = string_alloc(options->dev_node, NULL);\n        options->dev = basename(dev_node);\n    }\n}\n\nbool\nprint_openssl_info(const struct options *options)\n{\n    /*\n     * OpenSSL info print mode?\n     */\n    if (options->show_ciphers || options->show_digests || options->show_engines\n        || options->show_tls_ciphers || options->show_curves)\n    {\n        if (options->show_ciphers)\n        {\n            show_available_ciphers();\n        }\n        if (options->show_digests)\n        {\n            show_available_digests();\n        }\n        if (options->show_engines)\n        {\n            show_available_engines();\n        }\n        if (options->show_tls_ciphers)\n        {\n            show_available_tls_ciphers(options->cipher_list, options->cipher_list_tls13,\n                                       options->tls_cert_profile);\n        }\n        if (options->show_curves)\n        {\n            show_available_curves();\n        }\n        return true;\n    }\n    return false;\n}\n\n/*\n * Static pre-shared key generation mode?\n */\nbool\ndo_genkey(const struct options *options)\n{\n    /* should we disable paging? */\n    if (options->mlock && (options->genkey))\n    {\n        platform_mlockall(true);\n    }\n\n    /*\n     * We do not want user to use --genkey with --secret. In the transistion\n     * phase we for secret.\n     */\n    if (options->genkey && options->genkey_type != GENKEY_SECRET && options->shared_secret_file)\n    {\n        msg(M_USAGE, \"Using --genkey type with --secret filename is \"\n                     \"not supported.  Use --genkey type filename instead.\");\n    }\n    if (options->genkey && options->genkey_type == GENKEY_SECRET)\n    {\n        int nbits_written;\n        const char *genkey_filename = options->genkey_filename;\n        if (options->shared_secret_file && options->genkey_filename)\n        {\n            msg(M_USAGE, \"You must provide a filename to either --genkey \"\n                         \"or --secret, not both\");\n        }\n\n        /*\n         * Copy filename from shared_secret_file to genkey_filename to support\n         * the old --genkey --secret foo.file syntax.\n         */\n        if (options->shared_secret_file)\n        {\n            msg(M_WARN, \"WARNING: Using --genkey --secret filename is \"\n                        \"DEPRECATED.  Use --genkey secret filename instead.\");\n            genkey_filename = options->shared_secret_file;\n        }\n\n        nbits_written = write_key_file(2, genkey_filename);\n        if (nbits_written < 0)\n        {\n            msg(M_FATAL, \"Failed to write key file\");\n        }\n\n        msg(D_GENKEY | M_NOPREFIX, \"Randomly generated %d bit key written to %s\", nbits_written,\n            options->shared_secret_file);\n        return true;\n    }\n    else if (options->genkey && options->genkey_type == GENKEY_TLS_CRYPTV2_SERVER)\n    {\n        tls_crypt_v2_write_server_key_file(options->genkey_filename);\n        return true;\n    }\n    else if (options->genkey && options->genkey_type == GENKEY_TLS_CRYPTV2_CLIENT)\n    {\n        if (!options->tls_crypt_v2_file)\n        {\n            msg(M_USAGE,\n                \"--genkey tls-crypt-v2-client requires a server key to be set via --tls-crypt-v2 to create a client key\");\n        }\n\n        tls_crypt_v2_write_client_key_file(options->genkey_filename, options->genkey_extra_data,\n                                           options->tls_crypt_v2_file,\n                                           options->tls_crypt_v2_file_inline);\n        return true;\n    }\n    else if (options->genkey && options->genkey_type == GENKEY_AUTH_TOKEN)\n    {\n        auth_token_write_server_key_file(options->genkey_filename);\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\n/*\n * Persistent TUN/TAP device management mode?\n */\nbool\ndo_persist_tuntap(struct options *options, openvpn_net_ctx_t *ctx)\n{\n    if (!options->persist_config)\n    {\n        return false;\n    }\n\n    /* sanity check on options for --mktun or --rmtun */\n    notnull(options->dev, \"TUN/TAP device (--dev)\");\n    if (options->ce.remote || options->ifconfig_local || options->ifconfig_remote_netmask\n        || options->shared_secret_file || options->tls_server || options->tls_client)\n    {\n        msg(M_FATAL | M_OPTERR,\n            \"options --mktun or --rmtun should only be used together with --dev\");\n    }\n\n#if defined(ENABLE_DCO)\n    if (dco_enabled(options))\n    {\n        /* creating a DCO interface via --mktun is not supported as it does not\n         * make much sense. Since DCO is enabled by default, people may run into\n         * this without knowing, therefore this case should be properly handled.\n         *\n         * Disable DCO if --mktun was provided and print a message to let\n         * user know.\n         */\n        if (dev_type_enum(options->dev, options->dev_type) == DEV_TYPE_TUN)\n        {\n            msg(M_WARN, \"Note: --mktun does not support DCO. Creating TUN interface.\");\n        }\n\n        options->disable_dco = true;\n    }\n#endif\n\n#ifdef ENABLE_FEATURE_TUN_PERSIST\n    tuncfg(options->dev, options->dev_type, options->dev_node, options->persist_mode,\n           options->username, options->groupname, &options->tuntap_options, ctx);\n    if (options->persist_mode && options->lladdr)\n    {\n        set_lladdr(ctx, options->dev, options->lladdr, NULL);\n    }\n    return true;\n#else /* ifdef ENABLE_FEATURE_TUN_PERSIST */\n    msg(M_FATAL | M_OPTERR,\n        \"options --mktun and --rmtun are not available on your operating \"\n        \"system.  Please check 'man tun' (or 'tap'), whether your system \"\n        \"supports using 'ifconfig %s create' / 'destroy' to create/remove \"\n        \"persistent tunnel interfaces.\",\n        options->dev);\n#endif\n    return false;\n}\n\n/*\n * Should we become a daemon?\n * Return true if we did it.\n */\nbool\npossibly_become_daemon(const struct options *options)\n{\n    bool ret = false;\n\n#ifdef ENABLE_SYSTEMD\n    /* return without forking if we are running from systemd */\n    if (sd_notify(0, \"READY=0\") > 0)\n    {\n        return ret;\n    }\n#endif\n\n    if (options->daemon)\n    {\n        /* Don't chdir immediately, but the end of the init sequence, if needed */\n\n#if defined(__APPLE__) && defined(__clang__)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n#endif\n        if (daemon(1, options->log) < 0)\n        {\n            msg(M_ERR, \"daemon() failed or unsupported\");\n        }\n#if defined(__APPLE__) && defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n        restore_signal_state();\n        if (options->log)\n        {\n            set_std_files_to_null(true);\n        }\n\n        ret = true;\n    }\n    return ret;\n}\n\n/*\n * Actually do UID/GID downgrade, chroot and SELinux context switching, if requested.\n */\nstatic void\ndo_uid_gid_chroot(struct context *c, bool no_delay)\n{\n    static const char why_not[] = \"will be delayed because of --client, --pull, or --up-delay\";\n    struct context_0 *c0 = c->c0;\n\n    if (c0 && !c0->uid_gid_chroot_set)\n    {\n        /* chroot if requested */\n        if (c->options.chroot_dir)\n        {\n            if (no_delay)\n            {\n                platform_chroot(c->options.chroot_dir);\n            }\n            else if (c->first_time)\n            {\n                msg(M_INFO, \"NOTE: chroot %s\", why_not);\n            }\n        }\n\n        /* set user and/or group if we want to setuid/setgid */\n        if (c0->uid_gid_specified)\n        {\n            if (no_delay)\n            {\n                platform_user_group_set(&c0->platform_state_user, &c0->platform_state_group, c);\n            }\n            else if (c->first_time)\n            {\n                msg(M_INFO, \"NOTE: UID/GID downgrade %s\", why_not);\n            }\n        }\n\n#ifdef ENABLE_SELINUX\n        /* Apply a SELinux context in order to restrict what OpenVPN can do\n         * to _only_ what it is supposed to do after initialization is complete\n         * (basically just network I/O operations). Doing it after chroot\n         * requires /proc to be mounted in the chroot (which is annoying indeed\n         * but doing it before requires more complex SELinux policies.\n         */\n        if (c->options.selinux_context)\n        {\n            if (no_delay)\n            {\n                if (-1 == setcon(c->options.selinux_context))\n                {\n                    msg(M_ERR, \"setcon to '%s' failed; is /proc accessible?\",\n                        c->options.selinux_context);\n                }\n                else\n                {\n                    msg(M_INFO, \"setcon to '%s' succeeded\", c->options.selinux_context);\n                }\n            }\n            else if (c->first_time)\n            {\n                msg(M_INFO, \"NOTE: setcon %s\", why_not);\n            }\n        }\n#endif\n\n        /* Privileges are going to be dropped by now (if requested), be sure\n         * to prevent any future privilege dropping attempts from now on.\n         */\n        if (no_delay)\n        {\n            c0->uid_gid_chroot_set = true;\n        }\n    }\n}\n\n/*\n * Return common name in a way that is formatted for\n * prepending to msg() output.\n */\nconst char *\nformat_common_name(struct context *c, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n    if (c->c2.tls_multi)\n    {\n        buf_printf(&out, \"[%s] \", tls_common_name(c->c2.tls_multi, false));\n    }\n    return BSTR(&out);\n}\n\nvoid\npre_setup(const struct options *options)\n{\n#ifdef _WIN32\n    if (options->exit_event_name)\n    {\n        win32_signal_open(&win32_signal, WSO_FORCE_SERVICE, options->exit_event_name,\n                          options->exit_event_initial_state);\n    }\n    else\n    {\n        win32_signal_open(&win32_signal, WSO_FORCE_CONSOLE, NULL, false);\n\n        /* put a title on the top window bar */\n        if (win32_signal.mode == WSO_MODE_CONSOLE)\n        {\n            window_title_save(&window_title);\n            window_title_generate(options->config);\n        }\n    }\n#endif /* ifdef _WIN32 */\n}\n\nvoid\nreset_coarse_timers(struct context *c)\n{\n    c->c2.coarse_timer_wakeup = 0;\n}\n\n/*\n * Initialise the server poll timeout timer\n * This timer is used in the http/socks proxy setup so it needs to be setup\n * before\n */\nstatic void\ndo_init_server_poll_timeout(struct context *c)\n{\n    update_time();\n    if (c->options.ce.connect_timeout)\n    {\n        event_timeout_init(&c->c2.server_poll_interval, c->options.ce.connect_timeout, now);\n    }\n}\n\n/*\n * Initialize timers\n */\nstatic void\ndo_init_timers(struct context *c, bool deferred)\n{\n    update_time();\n    reset_coarse_timers(c);\n\n    /* initialize inactivity timeout */\n    if (c->options.inactivity_timeout)\n    {\n        event_timeout_init(&c->c2.inactivity_interval, c->options.inactivity_timeout, now);\n    }\n\n    /* initialize inactivity timeout */\n    if (c->options.session_timeout)\n    {\n        event_timeout_init(&c->c2.session_interval, c->options.session_timeout, now);\n    }\n\n    /* initialize pings */\n    if (dco_enabled(&c->options))\n    {\n        /* The DCO kernel module will send the pings instead of user space */\n        event_timeout_clear(&c->c2.ping_rec_interval);\n        event_timeout_clear(&c->c2.ping_send_interval);\n    }\n    else\n    {\n        if (c->options.ping_send_timeout)\n        {\n            event_timeout_init(&c->c2.ping_send_interval, c->options.ping_send_timeout, 0);\n        }\n\n        if (c->options.ping_rec_timeout)\n        {\n            event_timeout_init(&c->c2.ping_rec_interval, c->options.ping_rec_timeout, now);\n        }\n    }\n\n    /* If the auth-token renewal interval is shorter than reneg-sec, arm\n     * \"auth-token renewal\" timer to send additional auth-token to update the\n     * token on the client more often.  If not, this happens automatically\n     * at renegotiation time, without needing an extra event.\n     */\n    if (c->options.auth_token_generate\n        && c->options.auth_token_renewal < c->options.renegotiate_seconds)\n    {\n        event_timeout_init(&c->c2.auth_token_renewal_interval, c->options.auth_token_renewal, now);\n    }\n\n    if (!deferred)\n    {\n        /* initialize connection establishment timer */\n        event_timeout_init(&c->c2.wait_for_connect, 1, now);\n\n        /* initialize occ timers */\n\n        if (c->options.occ && !TLS_MODE(c) && c->c2.options_string_local\n            && c->c2.options_string_remote)\n        {\n            event_timeout_init(&c->c2.occ_interval, OCC_INTERVAL_SECONDS, now);\n        }\n\n        if (c->options.mtu_test)\n        {\n            event_timeout_init(&c->c2.occ_mtu_load_test_interval, OCC_MTU_LOAD_INTERVAL_SECONDS,\n                               now);\n        }\n\n        /* initialize packet_id persistence timer */\n        if (c->options.packet_id_file)\n        {\n            event_timeout_init(&c->c2.packet_id_persist_interval, 60, now);\n        }\n\n        /* initialize tmp_int optimization that limits the number of times we call\n         * tls_multi_process in the main event loop */\n        interval_init(&c->c2.tmp_int, TLS_MULTI_HORIZON, TLS_MULTI_REFRESH);\n    }\n}\n\n/*\n * Initialize traffic shaper.\n */\nstatic void\ndo_init_traffic_shaper(struct context *c)\n{\n    /* initialize traffic shaper (i.e. transmit bandwidth limiter) */\n    if (c->options.shaper)\n    {\n        shaper_init(&c->c2.shaper, c->options.shaper);\n        shaper_msg(&c->c2.shaper);\n    }\n}\n\n/*\n * Allocate route list structures for IPv4 and IPv6\n * (we do this for IPv4 even if no --route option has been seen, as other\n * parts of OpenVPN might want to fill the route-list with info, e.g. DHCP)\n */\nstatic void\ndo_alloc_route_list(struct context *c)\n{\n    if (!c->c1.route_list)\n    {\n        ALLOC_OBJ_CLEAR_GC(c->c1.route_list, struct route_list, &c->gc);\n    }\n    if (c->options.routes_ipv6 && !c->c1.route_ipv6_list)\n    {\n        ALLOC_OBJ_CLEAR_GC(c->c1.route_ipv6_list, struct route_ipv6_list, &c->gc);\n    }\n}\n\n\n/*\n * Initialize the route list, resolving any DNS names in route\n * options and saving routes in the environment.\n */\nstatic void\ndo_init_route_list(const struct options *options, struct route_list *route_list,\n                   const struct link_socket_info *link_socket_info, struct env_set *es,\n                   openvpn_net_ctx_t *ctx)\n{\n    const char *gw = NULL;\n    int dev = dev_type_enum(options->dev, options->dev_type);\n    int metric = 0;\n\n    /* if DCO is enabled we have both regular routes and iroutes in the system\n     * routing table, and normal routes must have a higher metric for that to\n     * work so that iroutes are always matched first\n     */\n    if (dco_enabled(options))\n    {\n        metric = DCO_DEFAULT_METRIC;\n    }\n\n    if (dev == DEV_TYPE_TUN && (options->topology == TOP_NET30 || options->topology == TOP_P2P))\n    {\n        gw = options->ifconfig_remote_netmask;\n    }\n    if (options->route_default_gateway)\n    {\n        gw = options->route_default_gateway;\n    }\n    if (options->route_default_metric)\n    {\n        metric = options->route_default_metric;\n    }\n\n    if (init_route_list(route_list, options->routes, gw, metric,\n                        link_socket_current_remote(link_socket_info), es, ctx))\n    {\n        /* copy routes to environment */\n        setenv_routes(es, route_list);\n    }\n}\n\nstatic void\ndo_init_route_ipv6_list(const struct options *options, struct route_ipv6_list *route_ipv6_list,\n                        const struct link_socket_info *link_socket_info, struct env_set *es,\n                        openvpn_net_ctx_t *ctx)\n{\n    const char *gw = NULL;\n    int metric = -1; /* no metric set */\n\n    /* see explanation in do_init_route_list() */\n    if (dco_enabled(options))\n    {\n        metric = DCO_DEFAULT_METRIC;\n    }\n\n    gw = options->ifconfig_ipv6_remote; /* default GW = remote end */\n    if (options->route_ipv6_default_gateway)\n    {\n        gw = options->route_ipv6_default_gateway;\n    }\n\n    if (options->route_default_metric)\n    {\n        metric = options->route_default_metric;\n    }\n\n    /* redirect (IPv6) gateway to VPN?  if yes, add a few more specifics\n     */\n    if (options->routes_ipv6->flags & RG_REROUTE_GW && options->ifconfig_ipv6_local)\n    {\n        char *opt_list[] = { \"::/3\", \"2000::/4\", \"3000::/4\", \"fc00::/7\", NULL };\n        int i;\n\n        for (i = 0; opt_list[i]; i++)\n        {\n            add_route_ipv6_to_option_list(options->routes_ipv6,\n                                          string_alloc(opt_list[i], options->routes_ipv6->gc), NULL,\n                                          NULL, options->route_default_table_id);\n        }\n    }\n\n    if (init_route_ipv6_list(route_ipv6_list, options->routes_ipv6, gw, metric,\n                             link_socket_current_remote_ipv6(link_socket_info), es, ctx))\n    {\n        /* copy routes to environment */\n        setenv_routes_ipv6(es, route_ipv6_list);\n    }\n}\n\n\n/*\n * Called after all initialization has been completed.\n */\nvoid\ninitialization_sequence_completed(struct context *c, const unsigned int flags)\n{\n    static const char message[] = \"Initialization Sequence Completed\";\n\n    /* Reset the unsuccessful connection counter on complete initialisation */\n    c->options.unsuccessful_attempts = 0;\n\n    /* If we delayed UID/GID downgrade or chroot, do it now */\n    do_uid_gid_chroot(c, true);\n\n    /* Test if errors */\n    if (flags & ISC_ERRORS)\n    {\n#ifdef _WIN32\n        show_routes(M_INFO | M_NOPREFIX);\n        show_adapters(M_INFO | M_NOPREFIX);\n        msg(M_INFO, \"%s With Errors ( see http://openvpn.net/faq.html#dhcpclientserv )\", message);\n#else\n#ifdef ENABLE_SYSTEMD\n        sd_notifyf(0, \"STATUS=Failed to start up: %s With Errors\\nERRNO=1\", message);\n#endif\n        msg(M_INFO, \"%s With Errors\", message);\n#endif\n    }\n    else\n    {\n#ifdef ENABLE_SYSTEMD\n        sd_notifyf(0, \"STATUS=%s\", message);\n#endif\n        msg(M_INFO, \"%s\", message);\n    }\n\n    /* Flag that we initialized */\n    if ((flags & (ISC_ERRORS | ISC_SERVER)) == 0)\n    {\n        c->options.no_advance = true;\n    }\n\n#ifdef _WIN32\n    fork_register_dns_action(c->c1.tuntap);\n#endif\n\n#ifdef ENABLE_MANAGEMENT\n    /* Tell management interface that we initialized */\n    if (management)\n    {\n        in_addr_t *tun_local = NULL;\n        struct in6_addr *tun_local6 = NULL;\n        struct openvpn_sockaddr local, remote;\n        struct link_socket_actual *actual;\n        socklen_t sa_len = sizeof(local);\n        const char *detail = \"SUCCESS\";\n        if (flags & ISC_ERRORS)\n        {\n            detail = \"ERROR\";\n        }\n        /* Flag route error only on platforms where trivial \"already exists\" errors\n         * are filtered out. Currently this is the case on Windows or if usng netlink.\n         */\n#if defined(_WIN32) || defined(ENABLE_SITNL)\n        else if (flags & ISC_ROUTE_ERRORS)\n        {\n            detail = \"ROUTE_ERROR\";\n        }\n#endif\n\n        CLEAR(local);\n        actual = &get_link_socket_info(c)->lsa->actual;\n        remote = actual->dest;\n        getsockname(c->c2.link_sockets[0]->sd, &local.addr.sa, &sa_len);\n#if ENABLE_IP_PKTINFO\n        if (!addr_defined(&local))\n        {\n            switch (local.addr.sa.sa_family)\n            {\n                case AF_INET:\n#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)\n                    local.addr.in4.sin_addr = actual->pi.in4.ipi_spec_dst;\n#else\n                    local.addr.in4.sin_addr = actual->pi.in4;\n#endif\n                    break;\n\n                case AF_INET6:\n                    local.addr.in6.sin6_addr = actual->pi.in6.ipi6_addr;\n                    break;\n            }\n        }\n#endif\n\n        if (c->c1.tuntap)\n        {\n            tun_local = &c->c1.tuntap->local;\n            tun_local6 = &c->c1.tuntap->local_ipv6;\n        }\n        management_set_state(management, OPENVPN_STATE_CONNECTED, detail, tun_local, tun_local6,\n                             &local, &remote);\n        if (tun_local)\n        {\n            management_post_tunnel_open(management, *tun_local);\n        }\n    }\n#endif /* ifdef ENABLE_MANAGEMENT */\n}\n\n/**\n * Determine if external route commands should be executed based on\n * configured options and backend driver\n */\nstatic bool\nroute_noexec_enabled(const struct options *o, const struct tuntap *tt)\n{\n    return o->route_noexec || (tt && tt->backend_driver == DRIVER_AFUNIX)\n           || (tt && tt->backend_driver == DRIVER_NULL);\n}\n\n/*\n * Possibly add routes and/or call route-up script\n * based on options.\n */\nbool\ndo_route(const struct options *options, struct route_list *route_list,\n         struct route_ipv6_list *route_ipv6_list, const struct tuntap *tt,\n         const struct plugin_list *plugins, struct env_set *es, openvpn_net_ctx_t *ctx)\n{\n    bool ret = true;\n    if (!route_noexec_enabled(options, tt) && (route_list || route_ipv6_list))\n    {\n        ret = add_routes(route_list, route_ipv6_list, tt, ROUTE_OPTION_FLAGS(options), es, ctx);\n        setenv_int(es, \"redirect_gateway\", route_did_redirect_default_gateway(route_list));\n    }\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        management_up_down(management, \"UP\", es);\n    }\n#endif\n\n    if (plugin_defined(plugins, OPENVPN_PLUGIN_ROUTE_UP))\n    {\n        if (plugin_call(plugins, OPENVPN_PLUGIN_ROUTE_UP, NULL, NULL, es)\n            != OPENVPN_PLUGIN_FUNC_SUCCESS)\n        {\n            msg(M_WARN, \"WARNING: route-up plugin call failed\");\n        }\n    }\n\n    if (options->route_script)\n    {\n        struct argv argv = argv_new();\n        setenv_str(es, \"script_type\", \"route-up\");\n        argv_parse_cmd(&argv, options->route_script);\n        openvpn_run_script(&argv, es, 0, \"--route-up\");\n        argv_free(&argv);\n    }\n\n#ifdef _WIN32\n    if (options->show_net_up)\n    {\n        show_routes(M_INFO | M_NOPREFIX);\n        show_adapters(M_INFO | M_NOPREFIX);\n    }\n    else if (check_debug_level(D_SHOW_NET))\n    {\n        show_routes(D_SHOW_NET | M_NOPREFIX);\n        show_adapters(D_SHOW_NET | M_NOPREFIX);\n    }\n#endif\n    return ret;\n}\n\n/*\n * initialize tun/tap device object\n */\nstatic void\ndo_init_tun(struct context *c)\n{\n    c->c1.tuntap = init_tun(c->options.dev, c->options.dev_type, c->options.topology,\n                            c->options.ifconfig_local, c->options.ifconfig_remote_netmask,\n                            c->options.ifconfig_ipv6_local, c->options.ifconfig_ipv6_netbits,\n                            c->options.ifconfig_ipv6_remote, c->c1.link_socket_addrs[0].bind_local,\n                            c->c1.link_socket_addrs[0].remote_list, !c->options.ifconfig_nowarn,\n                            c->c2.es, &c->net_ctx, c->c1.tuntap);\n\n    if (is_tun_afunix(c->options.dev_node))\n    {\n        /* Using AF_UNIX trumps using DCO */\n        c->c1.tuntap->backend_driver = DRIVER_AFUNIX;\n    }\n    else if (is_dev_type(c->options.dev, c->options.dev_type, \"null\"))\n    {\n        c->c1.tuntap->backend_driver = DRIVER_NULL;\n    }\n#ifdef _WIN32\n    else\n    {\n        c->c1.tuntap->backend_driver = c->options.windows_driver;\n    }\n#else\n    else if (dco_enabled(&c->options))\n    {\n        c->c1.tuntap->backend_driver = DRIVER_DCO;\n    }\n    else\n    {\n        c->c1.tuntap->backend_driver = DRIVER_GENERIC_TUNTAP;\n    }\n#endif\n\n    init_tun_post(c->c1.tuntap, &c->c2.frame, &c->options.tuntap_options);\n\n    c->c1.tuntap_owned = true;\n}\n\n/*\n * Open tun/tap device, ifconfig, call up script, etc.\n */\n\n\nstatic bool\ncan_preserve_tun(struct tuntap *tt)\n{\n    if (tt && tt->backend_driver == DRIVER_AFUNIX)\n    {\n        return false;\n    }\n#ifdef TARGET_ANDROID\n    return false;\n#else\n    return is_tun_type_set(tt);\n#endif\n}\n\n/**\n * Add WFP filters to block traffic to local networks.\n * Depending on the configuration all or just DNS is filtered.\n * This functionality is only available on Windows on all other\n * systems this function is a noop.\n *\n * @param c pointer to the connection context\n */\nstatic void\nadd_wfp_block(struct context *c)\n{\n#if defined(_WIN32)\n    /* Fortify 'redirect-gateway block-local' with firewall rules? */\n    bool block_local = block_local_needed(c->c1.route_list);\n\n    if (c->options.block_outside_dns || block_local)\n    {\n        BOOL dns_only = !block_local;\n        if (!win_wfp_block(c->c1.tuntap->adapter_index, c->options.msg_channel, dns_only))\n        {\n            msg(M_FATAL, \"WFP: initialization failed\");\n        }\n    }\n#endif\n}\n\n/**\n * Remove any WFP block filters previously added.\n * This functionality is only available on Windows on all other\n * systems the function is a noop.\n *\n * @param c             pointer to the connection context\n * @param adapter_index the VPN adapter index\n */\nstatic void\ndel_wfp_block(struct context *c, unsigned long adapter_index)\n{\n#if defined(_WIN32)\n    if (c->options.block_outside_dns || block_local_needed(c->c1.route_list))\n    {\n        if (!win_wfp_uninit(adapter_index, c->options.msg_channel))\n        {\n            msg(M_FATAL, \"WFP: deinitialization failed\");\n        }\n    }\n#endif\n}\n\n/**\n * Determines if ifconfig execution should be disabled because of a\n * @param c\n * @return\n */\nstatic bool\nifconfig_noexec_enabled(const struct context *c)\n{\n    return c->options.ifconfig_noexec\n           || (c->c1.tuntap && c->c1.tuntap->backend_driver == DRIVER_AFUNIX)\n           || (c->c1.tuntap && c->c1.tuntap->backend_driver == DRIVER_NULL);\n}\n\nstatic void\nopen_tun_backend(struct context *c)\n{\n    struct tuntap *tt = c->c1.tuntap;\n\n    if (tt->backend_driver == DRIVER_NULL)\n    {\n        open_tun_null(c->c1.tuntap);\n    }\n    else if (tt->backend_driver == DRIVER_AFUNIX)\n    {\n        open_tun_afunix(&c->options, c->c2.frame.tun_mtu, tt, c->c2.es);\n    }\n    else\n    {\n        open_tun(c->options.dev, c->options.dev_type, c->options.dev_node, tt, &c->net_ctx);\n    }\n    msg(M_INFO, \"%s device [%s] opened\", print_tun_backend_driver(tt->backend_driver),\n        tt->actual_name);\n}\n\n\nstatic bool\ndo_open_tun(struct context *c, int *error_flags)\n{\n    struct gc_arena gc = gc_new();\n    bool ret = false;\n    *error_flags = 0;\n\n    if (!can_preserve_tun(c->c1.tuntap))\n    {\n#ifdef TARGET_ANDROID\n        /* If we emulate persist-tun on android we still have to open a new tun and\n         * then close the old */\n        int oldtunfd = -1;\n        if (c->c1.tuntap)\n        {\n            oldtunfd = c->c1.tuntap->fd;\n            free(c->c1.tuntap);\n            c->c1.tuntap = NULL;\n            c->c1.tuntap_owned = false;\n        }\n#endif\n\n        /* initialize (but do not open) tun/tap object, this also sets\n         * the backend driver type */\n        do_init_tun(c);\n\n        /* inherit the dco context from the tuntap object */\n        if (c->c2.tls_multi)\n        {\n            c->c2.tls_multi->dco = &c->c1.tuntap->dco;\n        }\n\n#ifdef _WIN32\n        /* store (hide) interactive service handle in tuntap_options */\n        c->c1.tuntap->options.msg_channel = c->options.msg_channel;\n        msg(D_ROUTE, \"interactive service msg_channel=%\" PRIuPTR, (intptr_t)c->options.msg_channel);\n#endif\n\n        /* allocate route list structure */\n        do_alloc_route_list(c);\n\n        /* parse and resolve the route option list */\n        ASSERT(c->c2.link_sockets[0]);\n        if (c->options.routes && c->c1.route_list)\n        {\n            do_init_route_list(&c->options, c->c1.route_list, &c->c2.link_sockets[0]->info,\n                               c->c2.es, &c->net_ctx);\n        }\n        if (c->options.routes_ipv6 && c->c1.route_ipv6_list)\n        {\n            do_init_route_ipv6_list(&c->options, c->c1.route_ipv6_list,\n                                    &c->c2.link_sockets[0]->info, c->c2.es, &c->net_ctx);\n        }\n\n        /* do ifconfig */\n        if (!ifconfig_noexec_enabled(c) && ifconfig_order(c->c1.tuntap) == IFCONFIG_BEFORE_TUN_OPEN)\n        {\n            /* guess actual tun/tap unit number that will be returned\n             * by open_tun */\n            const char *guess =\n                guess_tuntap_dev(c->options.dev, c->options.dev_type, c->options.dev_node, &gc);\n            do_ifconfig(c->c1.tuntap, guess, c->c2.frame.tun_mtu, c->c2.es, &c->net_ctx);\n        }\n\n        /* possibly add routes */\n        if (route_order(c->c1.tuntap) == ROUTE_BEFORE_TUN)\n        {\n            /* Ignore route_delay, would cause ROUTE_BEFORE_TUN to be ignored */\n            bool status = do_route(&c->options, c->c1.route_list, c->c1.route_ipv6_list,\n                                   c->c1.tuntap, c->plugins, c->c2.es, &c->net_ctx);\n            *error_flags |= (status ? 0 : ISC_ROUTE_ERRORS);\n        }\n#ifdef TARGET_ANDROID\n        /* Store the old fd inside the fd so open_tun can use it */\n        c->c1.tuntap->fd = oldtunfd;\n#endif\n\n        if (dco_enabled(&c->options))\n        {\n            ovpn_dco_init(c);\n        }\n\n        /* open the tun device */\n        open_tun_backend(c);\n\n        /* set the hardware address */\n        if (c->options.lladdr)\n        {\n            set_lladdr(&c->net_ctx, c->c1.tuntap->actual_name, c->options.lladdr, c->c2.es);\n        }\n\n        /* do ifconfig */\n        if (!ifconfig_noexec_enabled(c) && ifconfig_order(c->c1.tuntap) == IFCONFIG_AFTER_TUN_OPEN)\n        {\n            do_ifconfig(c->c1.tuntap, c->c1.tuntap->actual_name, c->c2.frame.tun_mtu, c->c2.es,\n                        &c->net_ctx);\n        }\n\n        run_dns_up_down(true, &c->options, c->c1.tuntap, &c->persist.duri);\n\n        /* run the up script */\n        run_up_down(c->options.up_script, c->plugins, OPENVPN_PLUGIN_UP, c->c1.tuntap->actual_name,\n#ifdef _WIN32\n                    c->c1.tuntap->adapter_index,\n#endif\n                    dev_type_string(c->options.dev, c->options.dev_type), c->c2.frame.tun_mtu,\n                    print_in_addr_t(c->c1.tuntap->local, IA_EMPTY_IF_UNDEF, &gc),\n                    print_in_addr_t(c->c1.tuntap->remote_netmask, IA_EMPTY_IF_UNDEF, &gc), \"init\",\n                    NULL, \"up\", c->c2.es);\n\n        add_wfp_block(c);\n\n        /* possibly add routes */\n        if ((route_order(c->c1.tuntap) == ROUTE_AFTER_TUN) && (!c->options.route_delay_defined))\n        {\n            bool status = do_route(&c->options, c->c1.route_list, c->c1.route_ipv6_list,\n                                   c->c1.tuntap, c->plugins, c->c2.es, &c->net_ctx);\n            *error_flags |= (status ? 0 : ISC_ROUTE_ERRORS);\n        }\n\n        ret = true;\n        static_context = c;\n    }\n    else\n    {\n        msg(M_INFO, \"Preserving previous TUN/TAP instance: %s\", c->c1.tuntap->actual_name);\n\n        /* explicitly set the ifconfig_* env vars */\n        do_ifconfig_setenv(c->c1.tuntap, c->c2.es);\n\n        run_dns_up_down(true, &c->options, c->c1.tuntap, &c->persist.duri);\n\n        /* run the up script if user specified --up-restart */\n        if (c->options.up_restart)\n        {\n            run_up_down(c->options.up_script, c->plugins, OPENVPN_PLUGIN_UP,\n                        c->c1.tuntap->actual_name,\n#ifdef _WIN32\n                        c->c1.tuntap->adapter_index,\n#endif\n                        dev_type_string(c->options.dev, c->options.dev_type), c->c2.frame.tun_mtu,\n                        print_in_addr_t(c->c1.tuntap->local, IA_EMPTY_IF_UNDEF, &gc),\n                        print_in_addr_t(c->c1.tuntap->remote_netmask, IA_EMPTY_IF_UNDEF, &gc),\n                        \"restart\", NULL, \"up\", c->c2.es);\n        }\n\n        add_wfp_block(c);\n    }\n    gc_free(&gc);\n    return ret;\n}\n\n/*\n * Close TUN/TAP device\n */\n\nstatic void\ndo_close_tun_simple(struct context *c)\n{\n    msg(D_CLOSE, \"Closing %s interface\", print_tun_backend_driver(c->c1.tuntap->backend_driver));\n\n    if (c->c1.tuntap)\n    {\n        if (!ifconfig_noexec_enabled(c))\n        {\n            undo_ifconfig(c->c1.tuntap, &c->net_ctx);\n        }\n        if (c->c1.tuntap->backend_driver == DRIVER_AFUNIX)\n        {\n            close_tun_afunix(c->c1.tuntap);\n        }\n        else if (c->c1.tuntap->backend_driver == DRIVER_NULL)\n        {\n            free(c->c1.tuntap->actual_name);\n            free(c->c1.tuntap);\n        }\n        else\n        {\n            close_tun(c->c1.tuntap, &c->net_ctx);\n        }\n        c->c1.tuntap = NULL;\n    }\n    c->c1.tuntap_owned = false;\n    CLEAR(c->c1.pulled_options_digest_save);\n}\n\nstatic void\ndo_close_tun(struct context *c, bool force)\n{\n    /* With dco-win we open tun handle in the very beginning.\n     * In case when tun wasn't opened - like we haven't connected,\n     * we still need to close tun handle\n     */\n    if (tuntap_is_dco_win(c->c1.tuntap) && !is_tun_type_set(c->c1.tuntap))\n    {\n        do_close_tun_simple(c);\n        return;\n    }\n\n    if (!c->c1.tuntap || !c->c1.tuntap_owned)\n    {\n        return;\n    }\n\n    struct gc_arena gc = gc_new();\n    const char *tuntap_actual = string_alloc(c->c1.tuntap->actual_name, &gc);\n    const in_addr_t local = c->c1.tuntap->local;\n    const in_addr_t remote_netmask = c->c1.tuntap->remote_netmask;\n    unsigned long adapter_index = 0;\n#ifdef _WIN32\n    adapter_index = c->c1.tuntap->adapter_index;\n#endif\n\n    run_dns_up_down(false, &c->options, c->c1.tuntap, &c->persist.duri);\n\n    if (force || !(c->sig->signal_received == SIGUSR1 && c->options.persist_tun))\n    {\n        static_context = NULL;\n\n#ifdef ENABLE_MANAGEMENT\n        /* tell management layer we are about to close the TUN/TAP device */\n        if (management)\n        {\n            management_pre_tunnel_close(management);\n            management_up_down(management, \"DOWN\", c->c2.es);\n        }\n#endif\n\n        /* delete any routes we added */\n        if (c->c1.route_list || c->c1.route_ipv6_list)\n        {\n            run_up_down(c->options.route_predown_script, c->plugins, OPENVPN_PLUGIN_ROUTE_PREDOWN,\n                        tuntap_actual,\n#ifdef _WIN32\n                        adapter_index,\n#endif\n                        NULL, c->c2.frame.tun_mtu, print_in_addr_t(local, IA_EMPTY_IF_UNDEF, &gc),\n                        print_in_addr_t(remote_netmask, IA_EMPTY_IF_UNDEF, &gc), \"init\",\n                        signal_description(c->sig->signal_received, c->sig->signal_text),\n                        \"route-pre-down\", c->c2.es);\n\n            delete_routes(c->c1.route_list, c->c1.route_ipv6_list, c->c1.tuntap,\n                          ROUTE_OPTION_FLAGS(&c->options), c->c2.es, &c->net_ctx);\n        }\n\n        /* actually close tun/tap device based on --down-pre flag */\n        if (!c->options.down_pre)\n        {\n            do_close_tun_simple(c);\n        }\n\n        /* Run the down script -- note that it will run at reduced\n         * privilege if, for example, \"--user\" was used. */\n        run_up_down(c->options.down_script, c->plugins, OPENVPN_PLUGIN_DOWN, tuntap_actual,\n#ifdef _WIN32\n                    adapter_index,\n#endif\n                    NULL, c->c2.frame.tun_mtu, print_in_addr_t(local, IA_EMPTY_IF_UNDEF, &gc),\n                    print_in_addr_t(remote_netmask, IA_EMPTY_IF_UNDEF, &gc), \"init\",\n                    signal_description(c->sig->signal_received, c->sig->signal_text), \"down\",\n                    c->c2.es);\n\n        del_wfp_block(c, adapter_index);\n\n        /* actually close tun/tap device based on --down-pre flag */\n        if (c->options.down_pre)\n        {\n            do_close_tun_simple(c);\n        }\n    }\n    else\n    {\n        /* run the down script on this restart if --up-restart was specified */\n        if (c->options.up_restart)\n        {\n            run_up_down(c->options.down_script, c->plugins, OPENVPN_PLUGIN_DOWN, tuntap_actual,\n#ifdef _WIN32\n                        adapter_index,\n#endif\n                        NULL, c->c2.frame.tun_mtu, print_in_addr_t(local, IA_EMPTY_IF_UNDEF, &gc),\n                        print_in_addr_t(remote_netmask, IA_EMPTY_IF_UNDEF, &gc), \"restart\",\n                        signal_description(c->sig->signal_received, c->sig->signal_text), \"down\",\n                        c->c2.es);\n        }\n\n        del_wfp_block(c, adapter_index);\n    }\n    gc_free(&gc);\n}\n\nvoid\ntun_abort(void)\n{\n    struct context *c = static_context;\n    if (c)\n    {\n        static_context = NULL;\n        do_close_tun(c, true);\n    }\n}\n\n/*\n * Handle delayed tun/tap interface bringup due to --up-delay or --pull\n */\n\n/**\n * Helper for do_up().  Take two option hashes and return true if they are not\n * equal, or either one is all-zeroes.\n */\nstatic bool\noptions_hash_changed_or_zero(const struct sha256_digest *a, const struct sha256_digest *b)\n{\n    const struct sha256_digest zero = { { 0 } };\n    return memcmp(a, b, sizeof(struct sha256_digest))\n           || !memcmp(a, &zero, sizeof(struct sha256_digest));\n}\n\n/**\n * Helper function for tls_print_deferred_options_results\n * Adds the \", \" delimitor if there already some data in the\n * buffer.\n */\nstatic void\nadd_delim_if_non_empty(struct buffer *buf, const char *header)\n{\n    if (buf_len(buf) > strlen(header))\n    {\n        buf_printf(buf, \", \");\n    }\n}\n\n\n/**\n * Prints the results of options imported for the data channel\n * @param c\n */\nstatic void\ntls_print_deferred_options_results(struct context *c)\n{\n    struct options *o = &c->options;\n\n    struct buffer out;\n    uint8_t line[1024] = { 0 };\n    buf_set_write(&out, line, sizeof(line));\n\n\n    if (cipher_kt_mode_aead(o->ciphername))\n    {\n        buf_printf(&out, \"Data Channel: cipher '%s'\", cipher_kt_name(o->ciphername));\n    }\n    else\n    {\n        buf_printf(&out, \"Data Channel: cipher '%s', auth '%s'\", cipher_kt_name(o->ciphername),\n                   md_kt_name(o->authname));\n    }\n\n    if (o->use_peer_id)\n    {\n        buf_printf(&out, \", peer-id: %d\", o->peer_id);\n    }\n\n#ifdef USE_COMP\n    if (c->c2.comp_context)\n    {\n        buf_printf(&out, \", compression: '%s'\", c->c2.comp_context->alg.name);\n    }\n#endif\n\n    msg(D_HANDSHAKE, \"%s\", BSTR(&out));\n\n    buf_clear(&out);\n\n    const char *header = \"Timers: \";\n\n    buf_printf(&out, \"%s\", header);\n\n    if (o->ping_send_timeout)\n    {\n        buf_printf(&out, \"ping %d\", o->ping_send_timeout);\n    }\n\n    if (o->ping_rec_timeout_action != PING_UNDEF)\n    {\n        /* yes unidirectional ping is possible .... */\n        add_delim_if_non_empty(&out, header);\n\n        if (o->ping_rec_timeout_action == PING_EXIT)\n        {\n            buf_printf(&out, \"ping-exit %d\", o->ping_rec_timeout);\n        }\n        else\n        {\n            buf_printf(&out, \"ping-restart %d\", o->ping_rec_timeout);\n        }\n    }\n\n    if (o->inactivity_timeout)\n    {\n        add_delim_if_non_empty(&out, header);\n\n        buf_printf(&out, \"inactive %d\", o->inactivity_timeout);\n        if (o->inactivity_minimum_bytes)\n        {\n            buf_printf(&out, \" %\" PRIu64, o->inactivity_minimum_bytes);\n        }\n    }\n\n    if (o->session_timeout)\n    {\n        add_delim_if_non_empty(&out, header);\n        buf_printf(&out, \"session-timeout %d\", o->session_timeout);\n    }\n\n    if (buf_len(&out) > strlen(header))\n    {\n        msg(D_HANDSHAKE, \"%s\", BSTR(&out));\n    }\n\n    buf_clear(&out);\n    header = \"Protocol options: \";\n    buf_printf(&out, \"%s\", header);\n\n    if (c->options.ce.explicit_exit_notification)\n    {\n        buf_printf(&out, \"explicit-exit-notify %d\", c->options.ce.explicit_exit_notification);\n    }\n    if (c->options.imported_protocol_flags)\n    {\n        add_delim_if_non_empty(&out, header);\n\n        buf_printf(&out, \"protocol-flags\");\n\n        if (o->imported_protocol_flags & CO_USE_CC_EXIT_NOTIFY)\n        {\n            buf_printf(&out, \" cc-exit\");\n        }\n        if (o->imported_protocol_flags & CO_USE_TLS_KEY_MATERIAL_EXPORT)\n        {\n            buf_printf(&out, \" tls-ekm\");\n        }\n        if (o->imported_protocol_flags & CO_USE_DYNAMIC_TLS_CRYPT)\n        {\n            buf_printf(&out, \" dyn-tls-crypt\");\n        }\n        if (o->imported_protocol_flags & CO_EPOCH_DATA_KEY_FORMAT)\n        {\n            buf_printf(&out, \" aead-epoch\");\n        }\n    }\n\n    if (buf_len(&out) > strlen(header))\n    {\n        msg(D_HANDSHAKE, \"%s\", BSTR(&out));\n    }\n}\n\n\n/**\n * This function is expected to be invoked after open_tun() was performed.\n *\n * This kind of behaviour is required by DCO, because the following operations\n * can be done only after the DCO device was created and the new peer was\n * properly added.\n */\nstatic bool\ndo_deferred_options_part2(struct context *c)\n{\n    struct frame *frame_fragment = NULL;\n#ifdef ENABLE_FRAGMENT\n    if (c->options.ce.fragment)\n    {\n        frame_fragment = &c->c2.frame_fragment;\n    }\n#endif\n\n    struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];\n    if (!tls_session_update_crypto_params(c->c2.tls_multi, session, &c->options, &c->c2.frame,\n                                          frame_fragment, get_link_socket_info(c),\n                                          &c->c1.tuntap->dco))\n    {\n        msg(D_TLS_ERRORS, \"OPTIONS ERROR: failed to import crypto options\");\n        return false;\n    }\n\n    return true;\n}\n\nbool\ndo_up(struct context *c, bool pulled_options, unsigned int option_types_found)\n{\n    int error_flags = 0;\n    if (!c->c2.do_up_ran)\n    {\n        reset_coarse_timers(c);\n\n        if (pulled_options)\n        {\n            if (!do_deferred_options(c, option_types_found, false))\n            {\n                msg(D_PUSH_ERRORS, \"ERROR: Failed to apply push options\");\n                return false;\n            }\n        }\n\n        /* if --up-delay specified, open tun, do ifconfig, and run up script now */\n        if (c->options.up_delay || PULL_DEFINED(&c->options))\n        {\n            c->c2.did_open_tun = do_open_tun(c, &error_flags);\n            update_time();\n\n            /*\n             * Was tun interface object persisted from previous restart iteration,\n             * and if so did pulled options string change from previous iteration?\n             */\n            if (!c->c2.did_open_tun && PULL_DEFINED(&c->options) && c->c1.tuntap\n                && options_hash_changed_or_zero(&c->c1.pulled_options_digest_save,\n                                                &c->c2.pulled_options_digest))\n            {\n                /* if so, close tun, delete routes, then reinitialize tun and add routes */\n                msg(M_INFO,\n                    \"NOTE: Pulled options changed on restart, will need to close and reopen TUN/TAP device.\");\n\n                bool tt_dco_win = tuntap_is_dco_win(c->c1.tuntap);\n                do_close_tun(c, true);\n\n                if (tt_dco_win)\n                {\n                    msg(M_NONFATAL, \"dco-win doesn't yet support reopening TUN device\");\n                    /* prevent link_socket_close() from closing handle with WinSock API */\n                    c->c2.link_sockets[0]->sd = SOCKET_UNDEFINED;\n                    return false;\n                }\n                else\n                {\n                    management_sleep(1);\n                    c->c2.did_open_tun = do_open_tun(c, &error_flags);\n                    update_time();\n                }\n            }\n        }\n    }\n\n    /* This part needs to be run in p2p mode (without pull) when the client\n     * reconnects to setup various things (like DCO and NCP cipher) that\n     * might have changed from the previous connection.\n     */\n    if (!c->c2.do_up_ran\n        || (c->c2.tls_multi && c->c2.tls_multi->multi_state == CAS_RECONNECT_PENDING))\n    {\n        if (c->mode == MODE_POINT_TO_POINT)\n        {\n            /* ovpn-dco requires adding the peer now, before any option can be set,\n             * but *after* having parsed the pushed peer-id in do_deferred_options()\n             */\n            int ret = dco_p2p_add_new_peer(c);\n            if (ret < 0)\n            {\n                msg(D_DCO, \"Cannot add peer to DCO: %s (%d)\", strerror(-ret), ret);\n                return false;\n            }\n        }\n\n        /* do_deferred_options_part2() and do_deferred_p2p_ncp() *must* be\n         * invoked after open_tun().\n         * This is required by DCO because we must have created the interface\n         * and added the peer before we can fiddle with the keys or any other\n         * data channel per-peer setting.\n         */\n        if (pulled_options)\n        {\n            if (!do_deferred_options_part2(c))\n            {\n                return false;\n            }\n        }\n        else\n        {\n            if (c->mode == MODE_POINT_TO_POINT)\n            {\n                if (!do_deferred_p2p_ncp(c))\n                {\n                    msg(D_TLS_ERRORS, \"ERROR: Failed to apply P2P negotiated protocol options\");\n                    return false;\n                }\n            }\n        }\n\n        if (c->c2.did_open_tun)\n        {\n            c->c1.pulled_options_digest_save = c->c2.pulled_options_digest;\n\n            /* if --route-delay was specified, start timer */\n            if ((route_order(c->c1.tuntap) == ROUTE_AFTER_TUN) && c->options.route_delay_defined)\n            {\n                event_timeout_init(&c->c2.route_wakeup, c->options.route_delay, now);\n                event_timeout_init(&c->c2.route_wakeup_expire,\n                                   c->options.route_delay + c->options.route_delay_window, now);\n                tun_standby_init(c->c1.tuntap);\n            }\n            else\n            {\n                /* client/p2p --route-delay undefined */\n                initialization_sequence_completed(c, error_flags);\n            }\n        }\n        else if (c->options.mode == MODE_POINT_TO_POINT)\n        {\n            /* client/p2p restart with --persist-tun */\n            initialization_sequence_completed(c, error_flags);\n        }\n\n        tls_print_deferred_options_results(c);\n\n        c->c2.do_up_ran = true;\n        if (c->c2.tls_multi)\n        {\n            c->c2.tls_multi->multi_state = CAS_CONNECT_DONE;\n        }\n    }\n    return true;\n}\n\nbool\ndo_update(struct context *c, unsigned int option_types_found)\n{\n    /* Not necessary since to receive the update the openvpn\n     * instance must be up and running but just in case\n     */\n    if (!c->c2.do_up_ran)\n    {\n        return false;\n    }\n\n    bool tt_dco_win = tuntap_is_dco_win(c->c1.tuntap);\n    if (tt_dco_win)\n    {\n        msg(M_NONFATAL, \"dco-win doesn't yet support reopening TUN device\");\n        return false;\n    }\n\n    if (!do_deferred_options(c, option_types_found, true))\n    {\n        msg(D_PUSH_ERRORS, \"ERROR: Failed to apply push options\");\n        return false;\n    }\n\n    do_close_tun(c, true);\n\n    management_sleep(1);\n    int error_flags = 0;\n    c->c2.did_open_tun = do_open_tun(c, &error_flags);\n    update_time();\n\n    if (c->c2.did_open_tun)\n    {\n        /* if --route-delay was specified, start timer */\n        if ((route_order(c->c1.tuntap) == ROUTE_AFTER_TUN) && c->options.route_delay_defined)\n        {\n            event_timeout_init(&c->c2.route_wakeup, c->options.route_delay, now);\n            event_timeout_init(&c->c2.route_wakeup_expire,\n                               c->options.route_delay + c->options.route_delay_window, now);\n            tun_standby_init(c->c1.tuntap);\n        }\n\n        initialization_sequence_completed(c, error_flags);\n    }\n\n    CLEAR(c->c1.pulled_options_digest_save);\n\n    return true;\n}\n\n/*\n * These are the option categories which will be accepted by pull.\n */\nunsigned int\npull_permission_mask(const struct context *c)\n{\n    unsigned int flags = OPT_P_UP | OPT_P_ROUTE_EXTRAS | OPT_P_SOCKBUF | OPT_P_SOCKFLAGS\n                         | OPT_P_SETENV | OPT_P_SHAPER | OPT_P_TIMER | OPT_P_COMP | OPT_P_PERSIST\n                         | OPT_P_MESSAGES | OPT_P_EXPLICIT_NOTIFY | OPT_P_ECHO | OPT_P_PULL_MODE\n                         | OPT_P_PEER_ID | OPT_P_NCP | OPT_P_PUSH_MTU;\n\n    if (!c->options.route_nopull)\n    {\n        flags |= (OPT_P_ROUTE | OPT_P_DHCPDNS);\n    }\n\n    return flags;\n}\n\nstatic bool\ndo_deferred_p2p_ncp(struct context *c)\n{\n    if (!c->c2.tls_multi)\n    {\n        return true;\n    }\n\n    c->options.use_peer_id = c->c2.tls_multi->use_peer_id;\n\n    struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];\n\n    const char *ncp_cipher =\n        get_p2p_ncp_cipher(session, c->c2.tls_multi->peer_info, &c->options.gc);\n\n    if (ncp_cipher)\n    {\n        c->options.ciphername = ncp_cipher;\n    }\n    else if (!c->options.enable_ncp_fallback)\n    {\n        msg(D_TLS_ERRORS, \"ERROR: failed to negotiate cipher with peer and \"\n                          \"--data-ciphers-fallback not enabled. No usable \"\n                          \"data channel cipher\");\n        return false;\n    }\n\n    struct frame *frame_fragment = NULL;\n#ifdef ENABLE_FRAGMENT\n    if (c->options.ce.fragment)\n    {\n        frame_fragment = &c->c2.frame_fragment;\n    }\n#endif\n\n    if (!tls_session_update_crypto_params(c->c2.tls_multi, session, &c->options, &c->c2.frame,\n                                          frame_fragment, get_link_socket_info(c),\n                                          &c->c1.tuntap->dco))\n    {\n        msg(D_TLS_ERRORS, \"ERROR: failed to set crypto cipher\");\n        return false;\n    }\n    return true;\n}\n\nbool\ndo_deferred_options(struct context *c, const unsigned int found, const bool is_update)\n{\n    if (found & OPT_P_MESSAGES)\n    {\n        init_verb_mute(c, IVM_LEVEL_1 | IVM_LEVEL_2);\n        msg(D_PUSH, \"OPTIONS IMPORT: --verb and/or --mute level changed\");\n    }\n    if (found & OPT_P_TIMER)\n    {\n        do_init_timers(c, true);\n        msg(D_PUSH_DEBUG, \"OPTIONS IMPORT: timers and/or timeouts modified\");\n    }\n\n    if (found & OPT_P_EXPLICIT_NOTIFY)\n    {\n        /* Client side, so just check the first link_socket */\n        if (!proto_is_udp(c->c2.link_sockets[0]->info.proto)\n            && c->options.ce.explicit_exit_notification)\n        {\n            msg(D_PUSH, \"OPTIONS IMPORT: --explicit-exit-notify can only be used with --proto udp\");\n            c->options.ce.explicit_exit_notification = 0;\n        }\n        else\n        {\n            msg(D_PUSH_DEBUG, \"OPTIONS IMPORT: explicit notify parm(s) modified\");\n        }\n    }\n\n    if (found & OPT_P_COMP)\n    {\n        if (!check_compression_settings_valid(&c->options.comp, D_PUSH_ERRORS))\n        {\n            msg(D_PUSH_ERRORS, \"OPTIONS ERROR: server pushed compression \"\n                               \"settings that are not allowed and will result \"\n                               \"in a non-working connection. \"\n                               \"See also allow-compression in the manual.\");\n            return false;\n        }\n#ifdef USE_COMP\n        msg(D_PUSH_DEBUG, \"OPTIONS IMPORT: compression parms modified\");\n        comp_uninit(c->c2.comp_context);\n        c->c2.comp_context = comp_init(&c->options.comp);\n#endif\n    }\n\n    if (found & OPT_P_SHAPER)\n    {\n        msg(D_PUSH, \"OPTIONS IMPORT: traffic shaper enabled\");\n        do_init_traffic_shaper(c);\n    }\n\n    if (found & OPT_P_SOCKBUF)\n    {\n        msg(D_PUSH, \"OPTIONS IMPORT: --sndbuf/--rcvbuf options modified\");\n\n        for (int i = 0; i < c->c1.link_sockets_num; i++)\n        {\n            link_socket_update_buffer_sizes(c->c2.link_sockets[i], c->options.rcvbuf,\n                                            c->options.sndbuf);\n        }\n    }\n\n    if (found & OPT_P_SOCKFLAGS)\n    {\n        msg(D_PUSH, \"OPTIONS IMPORT: --socket-flags option modified\");\n        for (int i = 0; i < c->c1.link_sockets_num; i++)\n        {\n            link_socket_update_flags(c->c2.link_sockets[i], c->options.sockflags);\n        }\n    }\n\n    if (found & OPT_P_PERSIST)\n    {\n        msg(D_PUSH, \"OPTIONS IMPORT: --persist options modified\");\n    }\n    if (found & OPT_P_UP)\n    {\n        msg(D_PUSH, \"OPTIONS IMPORT: --ifconfig/up options modified\");\n    }\n    if (found & OPT_P_ROUTE)\n    {\n        msg(D_PUSH, \"OPTIONS IMPORT: route options modified\");\n    }\n    if (found & OPT_P_ROUTE_EXTRAS)\n    {\n        msg(D_PUSH, \"OPTIONS IMPORT: route-related options modified\");\n    }\n    if (found & OPT_P_DHCPDNS)\n    {\n        msg(D_PUSH, \"OPTIONS IMPORT: --ip-win32 and/or --dhcp-option options modified\");\n    }\n    if (found & OPT_P_SETENV)\n    {\n        msg(D_PUSH, \"OPTIONS IMPORT: environment modified\");\n    }\n\n    if (found & OPT_P_PEER_ID)\n    {\n        msg(D_PUSH_DEBUG, \"OPTIONS IMPORT: peer-id set\");\n        c->c2.tls_multi->use_peer_id = true;\n        c->c2.tls_multi->peer_id = c->options.peer_id;\n    }\n\n    /* process (potentially) pushed options */\n    if (c->options.pull)\n    {\n        /* On PUSH_UPDATE, NCP related flags are never updated, and so the code\n         * would assume \"no cipher pushed = NCP failed\" - so, don't call it on\n         * updates */\n        if (!is_update && !check_pull_client_ncp(c, found))\n        {\n            return false;\n        }\n\n        /* Check if pushed options are compatible with DCO, if enabled */\n        if (dco_enabled(&c->options) && !dco_check_pull_options(D_PUSH_ERRORS, &c->options))\n        {\n            msg(D_PUSH_ERRORS, \"OPTIONS ERROR: pushed options are incompatible \"\n                               \"with data channel offload. Use --disable-dco to connect to \"\n                               \"this server\");\n            return false;\n        }\n    }\n\n    /* Ensure that for epoch data format is only enabled if also data v2\n     * is enabled */\n    bool epoch_data = c->options.imported_protocol_flags & CO_EPOCH_DATA_KEY_FORMAT;\n    bool datav2_enabled = c->options.use_peer_id && c->options.peer_id < MAX_PEER_ID;\n\n    if (epoch_data && !datav2_enabled)\n    {\n        msg(D_PUSH_ERRORS, \"OPTIONS ERROR: Epoch key data format tag requires \"\n                           \"data v2 (peer-id) to be enabled.\");\n        return false;\n    }\n\n\n    if (found & OPT_P_PUSH_MTU)\n    {\n        /* MTU has changed, check that the pushed MTU is small enough to\n         * be able to change it */\n        msg(D_PUSH, \"OPTIONS IMPORT: tun-mtu set to %d\", c->options.ce.tun_mtu);\n\n        struct frame *frame = &c->c2.frame;\n\n        if (c->options.ce.tun_mtu > frame->tun_max_mtu)\n        {\n            msg(D_PUSH_ERRORS,\n                \"Server-pushed tun-mtu is too large, please add \"\n                \"tun-mtu-max %d in the client configuration\",\n                c->options.ce.tun_mtu);\n        }\n        frame->tun_mtu = min_int(frame->tun_max_mtu, c->options.ce.tun_mtu);\n    }\n\n    return true;\n}\n\n/*\n * Possible hold on initialization, holdtime is the\n * time OpenVPN would wait without management\n */\nstatic bool\ndo_hold(int holdtime)\n{\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        /* block until management hold is released */\n        if (management_hold(management, holdtime))\n        {\n            return true;\n        }\n    }\n#endif\n    return false;\n}\n\n/*\n * Sleep before restart.\n */\nstatic void\nsocket_restart_pause(struct context *c)\n{\n    int sec = 2;\n    int backoff = 0;\n\n    switch (c->mode)\n    {\n        case CM_TOP:\n            sec = 1;\n            break;\n\n        case CM_CHILD_UDP:\n        case CM_CHILD_TCP:\n            sec = c->options.ce.connect_retry_seconds;\n            break;\n    }\n\n#ifdef ENABLE_DEBUG\n    if (GREMLIN_CONNECTION_FLOOD_LEVEL(c->options.gremlin))\n    {\n        sec = 0;\n    }\n#endif\n\n    if (auth_retry_get() == AR_NOINTERACT)\n    {\n        sec = 10;\n    }\n\n    /* Slow down reconnection after 5 retries per remote -- for TCP client or UDP tls-client only */\n    if (c->mode == CM_CHILD_TCP || (c->options.ce.proto == PROTO_UDP && c->options.tls_client))\n    {\n        backoff = (c->options.unsuccessful_attempts / c->options.connection_list->len) - 4;\n        if (backoff > 0)\n        {\n            /* sec is less than 2^16; we can left shift it by up to 15 bits without overflow */\n            sec = max_int(sec, 1) << min_int(backoff, 15);\n        }\n        if (c->options.server_backoff_time)\n        {\n            sec = max_int(sec, c->options.server_backoff_time);\n            c->options.server_backoff_time = 0;\n        }\n\n        if (sec > c->options.ce.connect_retry_seconds_max)\n        {\n            sec = c->options.ce.connect_retry_seconds_max;\n        }\n    }\n\n    if (c->persist.restart_sleep_seconds > 0 && c->persist.restart_sleep_seconds > sec)\n    {\n        sec = c->persist.restart_sleep_seconds;\n    }\n    else if (c->persist.restart_sleep_seconds == -1)\n    {\n        sec = 0;\n    }\n    c->persist.restart_sleep_seconds = 0;\n\n    /* do management hold on context restart, i.e. second, third, fourth, etc. initialization */\n    if (do_hold(sec))\n    {\n        sec = 0;\n    }\n\n    if (sec)\n    {\n        msg(D_RESTART, \"Restart pause, %d second(s)\", sec);\n        management_sleep(sec);\n    }\n}\n\n/*\n * Do a possible pause on context_2 initialization.\n */\nstatic void\ndo_startup_pause(struct context *c)\n{\n    if (!c->first_time)\n    {\n        socket_restart_pause(c);\n    }\n    else\n    {\n        do_hold(0); /* do management hold on first context initialization */\n    }\n}\n\nstatic size_t\nget_frame_mtu(struct context *c, const struct options *o)\n{\n    size_t mtu;\n\n    if (o->ce.link_mtu_defined)\n    {\n        ASSERT(o->ce.link_mtu_defined);\n        /* if we have a link mtu defined we calculate what the old code\n         * would have come up with as tun-mtu */\n        size_t overhead = frame_calculate_protocol_header_size(&c->c1.ks.key_type, o, true);\n        mtu = o->ce.link_mtu - overhead;\n    }\n    else\n    {\n        ASSERT(o->ce.tun_mtu_defined);\n        mtu = o->ce.tun_mtu;\n    }\n\n    if (mtu < TUN_MTU_MIN)\n    {\n        msg(M_WARN, \"TUN MTU value (%zu) must be at least %d\", mtu, TUN_MTU_MIN);\n        frame_print(&c->c2.frame, M_FATAL, \"MTU is too small\");\n    }\n    return mtu;\n}\n\n/*\n * Finalize MTU parameters based on command line or config file options.\n */\nstatic void\nframe_finalize_options(struct context *c, const struct options *o)\n{\n    if (!o)\n    {\n        o = &c->options;\n    }\n\n    struct frame *frame = &c->c2.frame;\n\n    frame->tun_mtu = get_frame_mtu(c, o);\n    frame->tun_max_mtu = o->ce.tun_mtu_max;\n\n    /* max mtu needs to be at least as large as the tun mtu */\n    frame->tun_max_mtu = max_int(frame->tun_mtu, frame->tun_max_mtu);\n\n    /* We always allow at least 1600 MTU packets to be received in our buffer\n     * space to allow server to push \"baby giant\" MTU sizes */\n    frame->tun_max_mtu = max_int(TUN_MTU_MAX_MIN, frame->tun_max_mtu);\n\n    size_t payload_size = frame->tun_max_mtu;\n\n    /* we need to be also large enough to hold larger control channel packets\n     * if configured */\n    payload_size = max_int(payload_size, o->ce.tls_mtu);\n\n    /* The extra tun needs to be added to the payload size */\n    if (o->ce.tun_mtu_defined)\n    {\n        payload_size += o->ce.tun_mtu_extra;\n    }\n\n    /* Add 32 byte of extra space in the buffer to account for small errors\n     * in the calculation */\n    payload_size += 32;\n\n\n    /* the space that is reserved before the payload to add extra headers to it\n     * we always reserve the space for the worst case */\n    size_t headroom = 0;\n\n    /* includes IV and packet ID */\n    headroom += crypto_max_overhead();\n\n    /* peer id + opcode */\n    headroom += 4;\n\n    /* socks proxy header */\n    headroom += SOCKS_UDPv4_HEADROOM;\n\n    /* compression header and fragment header (part of the encrypted payload) */\n    headroom += 1 + 1;\n\n    /* Round up headroom to the next multiple of 4 to ensure alignment */\n    headroom = (headroom + 3) & ~3;\n\n    /* Add the headroom to the payloadsize as a received (IP) packet can have\n     * all the extra headers in it */\n    payload_size += headroom;\n\n    /* the space after the payload, this needs some extra buffer space for\n     * encryption so headroom is probably too much but we do not really care\n     * the few extra bytes */\n    size_t tailroom = headroom;\n\n#ifdef USE_COMP\n    msg(D_MTU_DEBUG,\n        \"MTU: adding %zu buffer tailroom for compression for %zu \"\n        \"bytes of payload\",\n        COMP_EXTRA_BUFFER(payload_size), payload_size);\n    tailroom += COMP_EXTRA_BUFFER(payload_size);\n#endif\n\n    frame->buf.payload_size = payload_size;\n    frame->buf.headroom = headroom;\n    frame->buf.tailroom = tailroom;\n}\n\n/*\n * Free a key schedule, including OpenSSL components.\n */\nstatic void\nkey_schedule_free(struct key_schedule *ks, bool free_ssl_ctx)\n{\n    free_key_ctx_bi(&ks->static_key);\n    if (tls_ctx_initialised(ks->ssl_ctx) && free_ssl_ctx)\n    {\n        tls_ctx_free(ks->ssl_ctx);\n        free(ks->ssl_ctx);\n        free_key_ctx(&ks->auth_token_key);\n    }\n    CLEAR(*ks);\n}\n\nstatic void\ninit_crypto_pre(struct context *c, const unsigned int flags)\n{\n    if (c->options.engine)\n    {\n        crypto_init_lib_engine(c->options.engine);\n    }\n\n    if (flags & CF_LOAD_PERSISTED_PACKET_ID)\n    {\n        /* load a persisted packet-id for cross-session replay-protection */\n        if (c->options.packet_id_file)\n        {\n            packet_id_persist_load(&c->c1.pid_persist, c->options.packet_id_file);\n        }\n    }\n}\n\n\nstatic void\ndo_init_crypto_test(struct context *c)\n{\n    const struct options *options = &c->options;\n    ASSERT(options->test_crypto);\n\n    init_crypto_pre(c, 0);\n\n    c->c2.crypto_options.flags |= CO_PACKET_ID_LONG_FORM;\n\n    /* Initialize packet ID tracking */\n    packet_id_init(&c->c2.crypto_options.packet_id, options->replay_window, options->replay_time,\n                   \"STATIC\", 0);\n\n    ASSERT(!key_ctx_bi_defined(&c->c1.ks.static_key));\n\n    /* Init cipher and hash algorithm */\n    init_key_type(&c->c1.ks.key_type, options->ciphername, options->authname,\n                  options->test_crypto, true);\n\n    generate_test_crypto_random_key(&c->c1.ks.key_type, &c->c1.ks.static_key,\n                                    \"test crypto key\");\n\n    /* Get key schedule */\n    c->c2.crypto_options.key_ctx_bi = c->c1.ks.static_key;\n}\n\n/*\n * Static Key Mode (using a pre-shared key)\n */\nstatic void\ndo_init_crypto_static(struct context *c, const unsigned int flags)\n{\n    const struct options *options = &c->options;\n    ASSERT(options->shared_secret_file);\n\n    init_crypto_pre(c, flags);\n\n    /* Initialize flags */\n    if (c->options.mute_replay_warnings)\n    {\n        c->c2.crypto_options.flags |= CO_MUTE_REPLAY_WARNINGS;\n    }\n\n    /* Initialize packet ID tracking */\n    packet_id_init(&c->c2.crypto_options.packet_id, options->replay_window, options->replay_time,\n                   \"STATIC\", 0);\n    c->c2.crypto_options.pid_persist = &c->c1.pid_persist;\n    c->c2.crypto_options.flags |= CO_PACKET_ID_LONG_FORM;\n    packet_id_persist_load_obj(&c->c1.pid_persist, &c->c2.crypto_options.packet_id);\n\n    if (!key_ctx_bi_defined(&c->c1.ks.static_key))\n    {\n        /* Get cipher & hash algorithms */\n        init_key_type(&c->c1.ks.key_type, options->ciphername, options->authname,\n                      options->test_crypto, true);\n\n        /* Read cipher and hmac keys from shared secret file */\n        crypto_read_openvpn_key(&c->c1.ks.key_type, &c->c1.ks.static_key,\n                                options->shared_secret_file, options->shared_secret_file_inline,\n                                options->key_direction, \"Static Key Encryption\", \"secret\", NULL);\n    }\n    else\n    {\n        msg(M_INFO, \"Re-using pre-shared static key\");\n    }\n\n    /* Get key schedule */\n    c->c2.crypto_options.key_ctx_bi = c->c1.ks.static_key;\n}\n\n/*\n * Initialize the tls-auth/crypt key context\n */\nstatic void\ndo_init_tls_wrap_key(struct context *c)\n{\n    const struct options *options = &c->options;\n\n    /* TLS handshake authentication (--tls-auth) */\n    if (options->ce.tls_auth_file)\n    {\n        /* Initialize key_type for tls-auth with auth only */\n        CLEAR(c->c1.ks.tls_auth_key_type);\n        c->c1.ks.tls_auth_key_type.cipher = \"none\";\n        c->c1.ks.tls_auth_key_type.digest = options->authname;\n        if (!md_valid(options->authname))\n        {\n            msg(M_FATAL,\n                \"ERROR: tls-auth enabled, but no valid --auth \"\n                \"algorithm specified ('%s')\",\n                options->authname);\n        }\n\n        crypto_read_openvpn_key(&c->c1.ks.tls_auth_key_type, &c->c1.ks.tls_wrap_key,\n                                options->ce.tls_auth_file, options->ce.tls_auth_file_inline,\n                                options->ce.key_direction, \"Control Channel Authentication\",\n                                \"tls-auth\", &c->c1.ks.original_wrap_keydata);\n    }\n\n    /* TLS handshake encryption+authentication (--tls-crypt) */\n    if (options->ce.tls_crypt_file)\n    {\n        tls_crypt_init_key(&c->c1.ks.tls_wrap_key, &c->c1.ks.original_wrap_keydata,\n                           options->ce.tls_crypt_file, options->ce.tls_crypt_file_inline,\n                           options->tls_server);\n    }\n\n    /* tls-crypt with client-specific keys (--tls-crypt-v2) */\n    if (options->ce.tls_crypt_v2_file)\n    {\n        if (options->tls_server)\n        {\n            tls_crypt_v2_init_server_key(&c->c1.ks.tls_crypt_v2_server_key, true,\n                                         options->ce.tls_crypt_v2_file,\n                                         options->ce.tls_crypt_v2_file_inline);\n        }\n        else\n        {\n            tls_crypt_v2_init_client_key(&c->c1.ks.tls_wrap_key, &c->c1.ks.original_wrap_keydata,\n                                         &c->c1.ks.tls_crypt_v2_wkc, options->ce.tls_crypt_v2_file,\n                                         options->ce.tls_crypt_v2_file_inline);\n        }\n        /* We have to ensure that the loaded tls-crypt key is small enough\n         * to fit into the initial hard reset v3 packet */\n        int wkc_len = buf_len(&c->c1.ks.tls_crypt_v2_wkc);\n\n        /* empty ACK/message id, tls-crypt, Opcode, UDP, ipv6 */\n        int required_size = 5 + wkc_len + tls_crypt_buf_overhead() + 1 + 8 + 40;\n\n        if (required_size > c->options.ce.tls_mtu)\n        {\n            msg(M_WARN,\n                \"ERROR: tls-crypt-v2 client key too large to work with \"\n                \"requested --max-packet-size %d, requires at least \"\n                \"--max-packet-size %d. Packets will ignore requested \"\n                \"maximum packet size\",\n                c->options.ce.tls_mtu, required_size);\n        }\n    }\n}\n\n/*\n * Initialize the persistent component of OpenVPN's TLS mode,\n * which is preserved across SIGUSR1 resets.\n */\nstatic void\ndo_init_crypto_tls_c1(struct context *c)\n{\n    const struct options *options = &c->options;\n\n    if (!tls_ctx_initialised(c->c1.ks.ssl_ctx))\n    {\n        /*\n         * Initialize the OpenSSL library's global\n         * SSL context.\n         */\n        ASSERT(NULL == c->c1.ks.ssl_ctx);\n        c->c1.ks.ssl_ctx = init_ssl(options, c->c0 && c->c0->uid_gid_chroot_set);\n        if (!tls_ctx_initialised(c->c1.ks.ssl_ctx))\n        {\n            switch (auth_retry_get())\n            {\n                case AR_NONE:\n                    msg(M_FATAL, \"Error: private key password verification failed\");\n                    break;\n\n                case AR_INTERACT:\n                    ssl_purge_auth(false);\n                    /* Intentional [[fallthrough]]; */\n\n                case AR_NOINTERACT:\n                    /* SOFT-SIGUSR1 -- Password failure error */\n                    register_signal(c->sig, SIGUSR1, \"private-key-password-failure\");\n                    break;\n\n                default:\n                    ASSERT(0);\n            }\n            return;\n        }\n\n        /*\n         * BF-CBC is allowed to be used only when explicitly configured\n         * as NCP-fallback or when NCP has been disabled or explicitly\n         * allowed in the in ncp_ciphers list.\n         * In all other cases do not attempt to initialize BF-CBC as it\n         * may not even be supported by the underlying SSL library.\n         *\n         * Therefore, the key structure has to be initialized when:\n         * - any non-BF-CBC cipher was selected; or\n         * - BF-CBC is selected, NCP is enabled and fallback is enabled\n         *   (BF-CBC will be the fallback).\n         * - BF-CBC is in data-ciphers and we negotiate to use BF-CBC:\n         *   If the negotiated cipher and options->ciphername are the\n         *   same we do not reinit the cipher\n         *\n         * Note that BF-CBC will still be part of the OCC string to retain\n         * backwards compatibility with older clients.\n         */\n        const char *ciphername = options->ciphername;\n        if (streq(options->ciphername, \"BF-CBC\")\n            && !tls_item_in_cipher_list(\"BF-CBC\", options->ncp_ciphers)\n            && !options->enable_ncp_fallback)\n        {\n            ciphername = \"none\";\n        }\n\n        /* Do not warn if the cipher is used only in OCC */\n        bool warn = options->enable_ncp_fallback;\n        init_key_type(&c->c1.ks.key_type, ciphername, options->authname, true, warn);\n\n        /* initialize tls-auth/crypt/crypt-v2 key */\n        do_init_tls_wrap_key(c);\n\n        /* initialise auth-token crypto support */\n        if (c->options.auth_token_generate)\n        {\n            auth_token_init_secret(&c->c1.ks.auth_token_key, c->options.auth_token_secret_file,\n                                   c->options.auth_token_secret_file_inline);\n        }\n\n#if 0 /* was: #if ENABLE_INLINE_FILES --  Note that enabling this code will break restarts */\n        if (options->priv_key_file_inline)\n        {\n            string_clear(c->options.priv_key_file_inline);\n            c->options.priv_key_file_inline = NULL;\n        }\n#endif\n    }\n    else\n    {\n        msg(D_INIT_MEDIUM, \"Re-using SSL/TLS context\");\n\n        /*\n         * tls-auth/crypt key can be configured per connection block, therefore\n         * we must reload it as it may have changed\n         */\n        do_init_tls_wrap_key(c);\n    }\n}\n\nstatic void\ndo_init_crypto_tls(struct context *c, const unsigned int flags)\n{\n    const struct options *options = &c->options;\n    struct tls_options to;\n    bool packet_id_long_form;\n\n    ASSERT(options->tls_server || options->tls_client);\n    ASSERT(!options->test_crypto);\n\n    init_crypto_pre(c, flags);\n\n    /* Make sure we are either a TLS client or server but not both */\n    ASSERT(options->tls_server == !options->tls_client);\n\n    /* initialize persistent component */\n    do_init_crypto_tls_c1(c);\n    if (IS_SIG(c))\n    {\n        return;\n    }\n\n    /* In short form, unique datagram identifier is 32 bits, in long form 64 bits */\n    packet_id_long_form = cipher_kt_mode_ofb_cfb(c->c1.ks.key_type.cipher);\n\n    /* Set all command-line TLS-related options */\n    CLEAR(to);\n\n    if (options->mute_replay_warnings)\n    {\n        to.crypto_flags |= CO_MUTE_REPLAY_WARNINGS;\n    }\n\n    to.crypto_flags &= ~(CO_PACKET_ID_LONG_FORM);\n    if (packet_id_long_form)\n    {\n        to.crypto_flags |= CO_PACKET_ID_LONG_FORM;\n    }\n\n    to.ssl_ctx = c->c1.ks.ssl_ctx;\n    to.key_type = c->c1.ks.key_type;\n    to.server = options->tls_server;\n    to.replay_window = options->replay_window;\n    to.replay_time = options->replay_time;\n    to.config_ciphername = c->options.ciphername;\n    to.config_ncp_ciphers = c->options.ncp_ciphers;\n    to.transition_window = options->transition_window;\n    to.handshake_window = options->handshake_window;\n    to.packet_timeout = options->tls_timeout;\n    to.renegotiate_bytes = options->renegotiate_bytes;\n    to.renegotiate_packets = options->renegotiate_packets;\n    if (options->renegotiate_seconds_min < 0)\n    {\n        /* Add 10% jitter to reneg-sec by default (server side only) */\n        int auto_jitter = options->mode != MODE_SERVER\n                              ? 0\n                              : get_random() % max_int(options->renegotiate_seconds / 10, 1);\n        to.renegotiate_seconds = options->renegotiate_seconds - auto_jitter;\n    }\n    else\n    {\n        /* Add user-specified jitter to reneg-sec */\n        to.renegotiate_seconds =\n            options->renegotiate_seconds\n            - (get_random()\n               % max_int(options->renegotiate_seconds - options->renegotiate_seconds_min, 1));\n    }\n    to.single_session = options->single_session;\n    to.mode = options->mode;\n    to.pull = options->pull;\n    if (options->push_peer_info) /* all there is */\n    {\n        to.push_peer_info_detail = 3;\n    }\n    else if (options->pull) /* pull clients send some details */\n    {\n        to.push_peer_info_detail = 2;\n    }\n    else if (options->mode == MODE_SERVER) /* server: no peer info at all */\n    {\n        to.push_peer_info_detail = 0;\n    }\n    else /* default: minimal info to allow NCP in P2P mode */\n    {\n        to.push_peer_info_detail = 1;\n    }\n\n    /* Check if the DCO drivers support the epoch data format */\n    if (dco_enabled(options))\n    {\n        to.data_epoch_supported = dco_supports_epoch_data(c);\n    }\n    else\n    {\n        to.data_epoch_supported = true;\n    }\n\n    /* should we not xmit any packets until we get an initial\n     * response from client? */\n    if (to.server && c->mode == CM_CHILD_TCP)\n    {\n        to.xmit_hold = true;\n    }\n\n    to.verify_command = options->tls_verify;\n    to.verify_x509_type = (options->verify_x509_type & 0xff);\n    to.verify_x509_name = options->verify_x509_name;\n    to.crl_file = options->crl_file;\n    to.crl_file_inline = options->crl_file_inline;\n    to.ssl_flags = options->ssl_flags;\n    to.ns_cert_type = options->ns_cert_type;\n    memcpy(to.remote_cert_ku, options->remote_cert_ku, sizeof(to.remote_cert_ku));\n    to.remote_cert_eku = options->remote_cert_eku;\n    to.verify_hash = options->verify_hash;\n    to.verify_hash_algo = options->verify_hash_algo;\n    to.verify_hash_depth = options->verify_hash_depth;\n    to.verify_hash_no_ca = options->verify_hash_no_ca;\n    memcpy(to.x509_username_field, options->x509_username_field, sizeof(to.x509_username_field));\n    to.es = c->c2.es;\n    to.net_ctx = &c->net_ctx;\n\n#ifdef ENABLE_DEBUG\n    to.gremlin = c->options.gremlin;\n#endif\n\n    to.plugins = c->plugins;\n\n#ifdef ENABLE_MANAGEMENT\n    to.mda_context = &c->c2.mda_context;\n#endif\n\n    to.auth_user_pass_verify_script = options->auth_user_pass_verify_script;\n    to.auth_user_pass_verify_script_via_file = options->auth_user_pass_verify_script_via_file;\n    to.client_crresponse_script = options->client_crresponse_script;\n    to.tmp_dir = options->tmp_dir;\n    to.export_peer_cert_dir = options->tls_export_peer_cert_dir;\n    if (options->ccd_exclusive)\n    {\n        to.client_config_dir_exclusive = options->client_config_dir;\n    }\n    to.auth_user_pass_file = options->auth_user_pass_file;\n    to.auth_user_pass_file_inline = options->auth_user_pass_file_inline;\n    to.auth_token_generate = options->auth_token_generate;\n    to.auth_token_lifetime = options->auth_token_lifetime;\n    to.auth_token_renewal = options->auth_token_renewal;\n    to.auth_token_call_auth = options->auth_token_call_auth;\n    to.auth_token_key = c->c1.ks.auth_token_key;\n\n    to.x509_track = options->x509_track;\n\n#ifdef ENABLE_MANAGEMENT\n    to.sci = &options->sc_info;\n#endif\n\n#ifdef USE_COMP\n    to.comp_options = options->comp;\n#endif\n\n    if (options->keying_material_exporter_label)\n    {\n        to.ekm_size = options->keying_material_exporter_length;\n        if (to.ekm_size < 16 || to.ekm_size > 4095)\n        {\n            to.ekm_size = 0;\n        }\n\n        to.ekm_label = options->keying_material_exporter_label;\n        to.ekm_label_size = strlen(to.ekm_label);\n    }\n    else\n    {\n        to.ekm_size = 0;\n    }\n\n    /* TLS handshake authentication (--tls-auth) */\n    if (options->ce.tls_auth_file)\n    {\n        to.tls_wrap.mode = TLS_WRAP_AUTH;\n    }\n\n    /* TLS handshake encryption (--tls-crypt) */\n    if (options->ce.tls_crypt_file || (options->ce.tls_crypt_v2_file && options->tls_client))\n    {\n        to.tls_wrap.mode = TLS_WRAP_CRYPT;\n    }\n\n    if (to.tls_wrap.mode == TLS_WRAP_AUTH || to.tls_wrap.mode == TLS_WRAP_CRYPT)\n    {\n        to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key;\n        to.tls_wrap.opt.pid_persist = &c->c1.pid_persist;\n        to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM;\n        to.tls_wrap.original_wrap_keydata = c->c1.ks.original_wrap_keydata;\n    }\n\n    if (options->ce.tls_crypt_v2_file)\n    {\n        to.tls_crypt_v2 = true;\n        to.tls_wrap.tls_crypt_v2_wkc = &c->c1.ks.tls_crypt_v2_wkc;\n\n        if (options->tls_server)\n        {\n            to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;\n            to.tls_crypt_v2_verify_script = c->options.tls_crypt_v2_verify_script;\n            to.tls_crypt_v2_max_age = c->options.tls_crypt_v2_max_age;\n            if (options->ce.tls_crypt_v2_force_cookie)\n            {\n                to.tls_wrap.opt.flags |= CO_FORCE_TLSCRYPTV2_COOKIE;\n            }\n        }\n    }\n\n    /* let the TLS engine know if keys have to be installed in DCO or not */\n    to.dco_enabled = dco_enabled(options);\n\n    /*\n     * Initialize OpenVPN's master TLS-mode object.\n     */\n    if (flags & CF_INIT_TLS_MULTI)\n    {\n        c->c2.tls_multi = tls_multi_init(&to);\n        /* inherit the dco context from the tuntap object */\n        if (c->c1.tuntap)\n        {\n            c->c2.tls_multi->dco = &c->c1.tuntap->dco;\n        }\n    }\n\n    if (flags & CF_INIT_TLS_AUTH_STANDALONE)\n    {\n        c->c2.tls_auth_standalone = tls_auth_standalone_init(&to, &c->c2.gc);\n        c->c2.session_id_hmac = session_id_hmac_init();\n    }\n}\n\nstatic void\ndo_init_frame_tls(struct context *c)\n{\n    if (c->c2.tls_multi)\n    {\n        tls_multi_init_finalize(c->c2.tls_multi, c->options.ce.tls_mtu);\n        ASSERT(c->c2.tls_multi->opt.frame.buf.payload_size <= c->c2.frame.buf.payload_size);\n        frame_print(&c->c2.tls_multi->opt.frame, D_MTU_INFO, \"Control Channel MTU parms\");\n\n        /* Keep the max mtu also in the frame of tls multi so it can access\n         * it in push_peer_info */\n        c->c2.tls_multi->opt.frame.tun_max_mtu = c->c2.frame.tun_max_mtu;\n    }\n    if (c->c2.tls_auth_standalone)\n    {\n        tls_init_control_channel_frame_parameters(&c->c2.tls_auth_standalone->frame,\n                                                  c->options.ce.tls_mtu);\n        frame_print(&c->c2.tls_auth_standalone->frame, D_MTU_INFO, \"TLS-Auth MTU parms\");\n        c->c2.tls_auth_standalone->tls_wrap.work = alloc_buf_gc(BUF_SIZE(&c->c2.frame), &c->c2.gc);\n        c->c2.tls_auth_standalone->workbuf = alloc_buf_gc(BUF_SIZE(&c->c2.frame), &c->c2.gc);\n    }\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n/*\n * No encryption or authentication.\n */\nstatic void\ndo_init_crypto_none(struct context *c)\n{\n    ASSERT(!c->options.test_crypto);\n\n    /* Initialise key_type with auth/cipher \"none\", so the key_type struct is\n     * valid */\n    init_key_type(&c->c1.ks.key_type, \"none\", \"none\", c->options.test_crypto, true);\n\n    msg(M_WARN, \"******* WARNING *******: All encryption and authentication features \"\n                \"disabled -- All data will be tunnelled as clear text and will not be \"\n                \"protected against man-in-the-middle changes. \"\n                \"PLEASE DO RECONSIDER THIS CONFIGURATION!\");\n}\n\nstatic void\ndo_init_crypto(struct context *c, const unsigned int flags)\n{\n    if (c->options.shared_secret_file)\n    {\n        do_init_crypto_static(c, flags);\n    }\n    else if (c->options.tls_server || c->options.tls_client)\n    {\n        do_init_crypto_tls(c, flags);\n    }\n    else /* no encryption or authentication. */\n    {\n        do_init_crypto_none(c);\n    }\n}\n\nstatic void\ndo_init_frame(struct context *c)\n{\n    /*\n     * Adjust frame size based on the --tun-mtu-extra parameter.\n     */\n    if (c->options.ce.tun_mtu_extra_defined)\n    {\n        c->c2.frame.extra_tun += c->options.ce.tun_mtu_extra;\n    }\n\n    /*\n     * Fill in the blanks in the frame parameters structure,\n     * make sure values are rational, etc.\n     */\n    frame_finalize_options(c, NULL);\n\n\n#if defined(ENABLE_FRAGMENT)\n    /*\n     * MTU advisories\n     */\n    if (c->options.ce.fragment && c->options.mtu_test)\n    {\n        msg(M_WARN,\n            \"WARNING: using --fragment and --mtu-test together may produce an inaccurate MTU test result\");\n    }\n#endif\n\n#ifdef ENABLE_FRAGMENT\n    if (c->options.ce.fragment > 0 && c->options.ce.mssfix > c->options.ce.fragment)\n    {\n        msg(M_WARN,\n            \"WARNING: if you use --mssfix and --fragment, you should \"\n            \"set --fragment (%d) larger or equal than --mssfix (%d)\",\n            c->options.ce.fragment, c->options.ce.mssfix);\n    }\n    if (c->options.ce.fragment > 0 && c->options.ce.mssfix > 0\n        && c->options.ce.fragment_encap != c->options.ce.mssfix_encap)\n    {\n        msg(M_WARN, \"WARNING: if you use --mssfix and --fragment, you should \"\n                    \"use the \\\"mtu\\\" flag for both or none of of them.\");\n    }\n#endif\n}\n\nstatic void\ndo_option_warnings(struct context *c)\n{\n    const struct options *o = &c->options;\n\n    if (o->ping_send_timeout && !o->ping_rec_timeout)\n    {\n        msg(M_WARN, \"WARNING: --ping should normally be used with --ping-restart or --ping-exit\");\n    }\n\n    if (o->username || o->groupname || o->chroot_dir\n#ifdef ENABLE_SELINUX\n        || o->selinux_context\n#endif\n    )\n    {\n        if (!o->persist_tun)\n        {\n            msg(M_WARN,\n                \"WARNING: you are using user/group/chroot/setcon without persist-tun -- this may cause restarts to fail\");\n        }\n    }\n\n    if (o->chroot_dir && !(o->username && o->groupname))\n    {\n        msg(M_WARN,\n            \"WARNING: you are using chroot without specifying user and group -- this may cause the chroot jail to be insecure\");\n    }\n\n    if (o->pull && o->ifconfig_local && c->first_time)\n    {\n        msg(M_WARN,\n            \"WARNING: using --pull/--client and --ifconfig together is probably not what you want\");\n    }\n\n    if (o->server_bridge_defined || o->server_bridge_proxy_dhcp)\n    {\n        msg(M_WARN,\n            \"NOTE: when bridging your LAN adapter with the TAP adapter, note that the new bridge adapter will often take on its own IP address that is different from what the LAN adapter was previously set to\");\n    }\n\n    if (o->mode == MODE_SERVER)\n    {\n        if (o->duplicate_cn && o->client_config_dir)\n        {\n            msg(M_WARN,\n                \"WARNING: using --duplicate-cn and --client-config-dir together is probably not what you want\");\n        }\n        if (o->duplicate_cn && o->ifconfig_pool_persist_filename)\n        {\n            msg(M_WARN, \"WARNING: --ifconfig-pool-persist will not work with --duplicate-cn\");\n        }\n        if (!o->keepalive_ping || !o->keepalive_timeout)\n        {\n            msg(M_WARN, \"WARNING: --keepalive option is missing from server config\");\n        }\n    }\n\n    if (o->tls_server)\n    {\n        warn_on_use_of_common_subnets(&c->net_ctx);\n    }\n    if (o->tls_client && !o->tls_verify && o->verify_x509_type == VERIFY_X509_NONE\n        && !(o->ns_cert_type & NS_CERT_CHECK_SERVER) && !o->remote_cert_eku\n        && !(o->verify_hash_depth == 0 && o->verify_hash))\n    {\n        msg(M_WARN,\n            \"WARNING: No server certificate verification method has been enabled.  See http://openvpn.net/howto.html#mitm for more info.\");\n    }\n    if (o->ns_cert_type)\n    {\n        msg(M_WARN, \"WARNING: --ns-cert-type is DEPRECATED.  Use --remote-cert-tls instead.\");\n    }\n\n    /* If a script is used, print appropriate warnings */\n    if (o->user_script_used)\n    {\n        if (script_security() >= SSEC_SCRIPTS)\n        {\n            msg(M_WARN,\n                \"NOTE: the current --script-security setting may allow this configuration to call user-defined scripts\");\n        }\n        else if (script_security() >= SSEC_PW_ENV)\n        {\n            msg(M_WARN,\n                \"WARNING: the current --script-security setting may allow passwords to be passed to scripts via environmental variables\");\n        }\n        else\n        {\n            msg(M_WARN,\n                \"NOTE: starting with \" PACKAGE_NAME\n                \" 2.1, '--script-security 2' or higher is required to call user-defined scripts or executables\");\n        }\n    }\n}\n\nstruct context_buffers *\ninit_context_buffers(const struct frame *frame)\n{\n    struct context_buffers *b;\n\n    ALLOC_OBJ_CLEAR(b, struct context_buffers);\n\n    size_t buf_size = BUF_SIZE(frame);\n\n    b->read_link_buf = alloc_buf(buf_size);\n    b->read_tun_buf = alloc_buf(buf_size);\n\n    b->aux_buf = alloc_buf(buf_size);\n\n    b->encrypt_buf = alloc_buf(buf_size);\n    b->decrypt_buf = alloc_buf(buf_size);\n\n#ifdef USE_COMP\n    b->compress_buf = alloc_buf(buf_size);\n    b->decompress_buf = alloc_buf(buf_size);\n#endif\n\n    return b;\n}\n\nvoid\nfree_context_buffers(struct context_buffers *b)\n{\n    if (b)\n    {\n        free_buf(&b->read_link_buf);\n        free_buf(&b->read_tun_buf);\n        free_buf(&b->aux_buf);\n\n#ifdef USE_COMP\n        free_buf(&b->compress_buf);\n        free_buf(&b->decompress_buf);\n#endif\n\n        free_buf(&b->encrypt_buf);\n        free_buf(&b->decrypt_buf);\n\n        free(b);\n    }\n}\n\n/*\n * Now that we know all frame parameters, initialize\n * our buffers.\n */\nstatic void\ndo_init_buffers(struct context *c)\n{\n    c->c2.buffers = init_context_buffers(&c->c2.frame);\n    c->c2.buffers_owned = true;\n}\n\n#ifdef ENABLE_FRAGMENT\n/*\n * Fragmenting code has buffers to initialize\n * once frame parameters are known.\n */\nstatic void\ndo_init_fragment(struct context *c)\n{\n    ASSERT(c->options.ce.fragment);\n\n    /*\n     * Set frame parameter for fragment code.  This is necessary because\n     * the fragmentation code deals with payloads which have already been\n     * passed through the compression code.\n     */\n    c->c2.frame_fragment = c->c2.frame;\n\n    frame_calculate_dynamic(&c->c2.frame_fragment, &c->c1.ks.key_type, &c->options,\n                            get_link_socket_info(c));\n    fragment_frame_init(c->c2.fragment, &c->c2.frame_fragment);\n}\n#endif\n\n/*\n * Allocate our socket object.\n */\nstatic void\ndo_link_socket_new(struct context *c)\n{\n    ASSERT(!c->c2.link_sockets);\n\n    ALLOC_ARRAY_GC(c->c2.link_sockets, struct link_socket *, c->c1.link_sockets_num, &c->c2.gc);\n\n    for (int i = 0; i < c->c1.link_sockets_num; i++)\n    {\n        c->c2.link_sockets[i] = link_socket_new();\n    }\n    c->c2.link_socket_owned = true;\n}\n\n/*\n * bind TCP/UDP sockets\n */\nstatic void\ndo_init_socket_phase1(struct context *c)\n{\n    for (int i = 0; i < c->c1.link_sockets_num; i++)\n    {\n        int mode = LS_MODE_DEFAULT;\n\n        /* mode allows CM_CHILD_TCP\n         * instances to inherit acceptable fds\n         * from a top-level parent */\n        if (c->options.mode == MODE_SERVER)\n        {\n            /* initializing listening socket */\n            if (c->mode == CM_TOP)\n            {\n                mode = LS_MODE_TCP_LISTEN;\n            }\n            /* initializing socket to client */\n            else if (c->mode == CM_CHILD_TCP)\n            {\n                mode = LS_MODE_TCP_ACCEPT_FROM;\n            }\n        }\n\n        /* init each socket with its specific args */\n        link_socket_init_phase1(c, i, mode);\n    }\n}\n\n/*\n * finalize TCP/UDP sockets\n */\nstatic void\ndo_init_socket_phase2(struct context *c)\n{\n    for (int i = 0; i < c->c1.link_sockets_num; i++)\n    {\n        link_socket_init_phase2(c, c->c2.link_sockets[i]);\n    }\n}\n\n/*\n * Print MTU INFO\n */\nstatic void\ndo_print_data_channel_mtu_parms(struct context *c)\n{\n    frame_print(&c->c2.frame, D_MTU_INFO, \"Data Channel MTU parms\");\n#ifdef ENABLE_FRAGMENT\n    if (c->c2.fragment)\n    {\n        frame_print(&c->c2.frame_fragment, D_MTU_INFO, \"Fragmentation MTU parms\");\n    }\n#endif\n}\n\n/*\n * Get local and remote options compatibility strings.\n */\nstatic void\ndo_compute_occ_strings(struct context *c)\n{\n    struct gc_arena gc = gc_new();\n\n    c->c2.options_string_local =\n        options_string(&c->options, &c->c2.frame, c->c1.tuntap, &c->net_ctx, false, &gc);\n    c->c2.options_string_remote =\n        options_string(&c->options, &c->c2.frame, c->c1.tuntap, &c->net_ctx, true, &gc);\n\n    msg(D_SHOW_OCC, \"Local Options String (VER=%s): '%s'\",\n        options_string_version(c->c2.options_string_local, &gc), c->c2.options_string_local);\n    msg(D_SHOW_OCC, \"Expected Remote Options String (VER=%s): '%s'\",\n        options_string_version(c->c2.options_string_remote, &gc), c->c2.options_string_remote);\n\n    if (c->c2.tls_multi)\n    {\n        tls_multi_init_set_options(c->c2.tls_multi, c->c2.options_string_local,\n                                   c->c2.options_string_remote);\n    }\n\n    gc_free(&gc);\n}\n\n/*\n * These things can only be executed once per program instantiation.\n * Set up for possible UID/GID downgrade, but don't do it yet.\n * Daemonize if requested.\n */\nstatic void\ndo_init_first_time(struct context *c)\n{\n    if (c->first_time && !c->c0)\n    {\n        struct context_0 *c0;\n\n        ALLOC_OBJ_CLEAR_GC(c->c0, struct context_0, &c->gc);\n        c0 = c->c0;\n\n        /* get user and/or group that we want to setuid/setgid to,\n         * sets also platform_x_state */\n        bool group_defined = platform_group_get(c->options.groupname, &c0->platform_state_group);\n        bool user_defined = platform_user_get(c->options.username, &c0->platform_state_user);\n\n        c0->uid_gid_specified = user_defined || group_defined;\n\n        /* fork the dns script runner to preserve root? */\n        c->persist.duri.required = user_defined;\n\n        /* perform postponed chdir if --daemon */\n        if (c->did_we_daemonize && c->options.cd_dir == NULL)\n        {\n            platform_chdir(\"/\");\n        }\n\n        /* should we change scheduling priority? */\n        platform_nice(c->options.nice);\n    }\n}\n\n/*\n * free buffers\n */\nstatic void\ndo_close_free_buf(struct context *c)\n{\n    if (c->c2.buffers_owned)\n    {\n        free_context_buffers(c->c2.buffers);\n        c->c2.buffers = NULL;\n        c->c2.buffers_owned = false;\n    }\n}\n\n/*\n * close TLS\n */\nstatic void\ndo_close_tls(struct context *c)\n{\n    if (c->c2.tls_multi)\n    {\n        tls_multi_free(c->c2.tls_multi, true);\n        c->c2.tls_multi = NULL;\n    }\n\n    /* free options compatibility strings */\n    free(c->c2.options_string_local);\n    free(c->c2.options_string_remote);\n\n    c->c2.options_string_local = c->c2.options_string_remote = NULL;\n\n    if (c->c2.pulled_options_state)\n    {\n        md_ctx_cleanup(c->c2.pulled_options_state);\n        md_ctx_free(c->c2.pulled_options_state);\n    }\n\n    tls_auth_standalone_free(c->c2.tls_auth_standalone);\n}\n\n/*\n * Free key schedules\n */\nstatic void\ndo_close_free_key_schedule(struct context *c, bool free_ssl_ctx)\n{\n    /*\n     * always free the tls_auth/crypt key. The key will\n     * be reloaded from memory (pre-cached)\n     */\n    free_key_ctx(&c->c1.ks.tls_crypt_v2_server_key);\n    free_key_ctx_bi(&c->c1.ks.tls_wrap_key);\n    CLEAR(c->c1.ks.tls_wrap_key);\n    buf_clear(&c->c1.ks.tls_crypt_v2_wkc);\n    free_buf(&c->c1.ks.tls_crypt_v2_wkc);\n\n    if (!(c->sig->signal_received == SIGUSR1))\n    {\n        key_schedule_free(&c->c1.ks, free_ssl_ctx);\n    }\n}\n\n/*\n * Close TCP/UDP connection\n */\nstatic void\ndo_close_link_socket(struct context *c)\n{\n    if (c->c2.link_sockets && c->c2.link_socket_owned)\n    {\n        for (int i = 0; i < c->c1.link_sockets_num; i++)\n        {\n            /* in dco-win case, link socket is a tun handle which is\n             * closed in do_close_tun(). Set it to UNDEFINED so\n             * we won't use WinSock API to close it. */\n            if (tuntap_is_dco_win(c->c1.tuntap))\n            {\n                c->c2.link_sockets[i]->sd = SOCKET_UNDEFINED;\n            }\n\n            link_socket_close(c->c2.link_sockets[i]);\n        }\n        c->c2.link_sockets = NULL;\n    }\n\n\n    /* Preserve the resolved list of remote if the user request to or if we want\n     * reconnect to the same host again or there are still addresses that need\n     * to be tried */\n    if (!(c->sig->signal_received == SIGUSR1\n          && ((c->options.persist_remote_ip)\n              || (c->sig->source != SIG_SOURCE_HARD\n                  && ((c->c1.link_socket_addrs[0].current_remote\n                       && c->c1.link_socket_addrs[0].current_remote->ai_next)\n                      || c->options.no_advance)))))\n    {\n        clear_remote_addrlist(&c->c1.link_socket_addrs[0], !c->options.resolve_in_advance);\n    }\n\n    /* Clear the remote actual address when persist_remote_ip is not in use */\n    if (!(c->sig->signal_received == SIGUSR1 && c->options.persist_remote_ip))\n    {\n        for (int i = 0; i < c->c1.link_sockets_num; i++)\n        {\n            CLEAR(c->c1.link_socket_addrs[i].actual);\n        }\n    }\n\n    if (!(c->sig->signal_received == SIGUSR1 && c->options.persist_local_ip))\n    {\n        for (int i = 0; i < c->c1.link_sockets_num; i++)\n        {\n            if (c->c1.link_socket_addrs[i].bind_local && !c->options.resolve_in_advance)\n            {\n                freeaddrinfo(c->c1.link_socket_addrs[i].bind_local);\n            }\n\n            c->c1.link_socket_addrs[i].bind_local = NULL;\n        }\n    }\n}\n\n/*\n * Close packet-id persistence file\n */\nstatic void\ndo_close_packet_id(struct context *c)\n{\n    packet_id_free(&c->c2.crypto_options.packet_id);\n    packet_id_persist_save(&c->c1.pid_persist);\n    if (!(c->sig->signal_received == SIGUSR1))\n    {\n        packet_id_persist_close(&c->c1.pid_persist);\n    }\n}\n\n#ifdef ENABLE_FRAGMENT\n/*\n * Close fragmentation handler.\n */\nstatic void\ndo_close_fragment(struct context *c)\n{\n    if (c->c2.fragment)\n    {\n        fragment_free(c->c2.fragment);\n        c->c2.fragment = NULL;\n    }\n}\n#endif\n\n/*\n * Open and close our event objects.\n */\n\nstatic void\ndo_event_set_init(struct context *c, bool need_us_timeout)\n{\n    unsigned int flags = 0;\n\n    c->c2.event_set_max = BASE_N_EVENTS;\n\n    flags |= EVENT_METHOD_FAST;\n\n    if (need_us_timeout)\n    {\n        flags |= EVENT_METHOD_US_TIMEOUT;\n    }\n\n    c->c2.event_set = event_set_init(&c->c2.event_set_max, flags);\n    c->c2.event_set_owned = true;\n}\n\nstatic void\ndo_close_event_set(struct context *c)\n{\n    if (c->c2.event_set && c->c2.event_set_owned)\n    {\n        event_free(c->c2.event_set);\n        c->c2.event_set = NULL;\n        c->c2.event_set_owned = false;\n    }\n}\n\n/*\n * Open and close --status file\n */\n\nstatic void\ndo_open_status_output(struct context *c)\n{\n    if (!c->c1.status_output)\n    {\n        c->c1.status_output =\n            status_open(c->options.status_file, c->options.status_file_update_freq, -1, NULL,\n                        STATUS_OUTPUT_WRITE);\n        c->c1.status_output_owned = true;\n    }\n}\n\nstatic void\ndo_close_status_output(struct context *c)\n{\n    if (!(c->sig->signal_received == SIGUSR1))\n    {\n        if (c->c1.status_output_owned && c->c1.status_output)\n        {\n            status_close(c->c1.status_output);\n            c->c1.status_output = NULL;\n            c->c1.status_output_owned = false;\n        }\n    }\n}\n\n/*\n * Handle ifconfig-pool persistence object.\n */\nstatic void\ndo_open_ifconfig_pool_persist(struct context *c)\n{\n    if (!c->c1.ifconfig_pool_persist && c->options.ifconfig_pool_persist_filename)\n    {\n        c->c1.ifconfig_pool_persist =\n            ifconfig_pool_persist_init(c->options.ifconfig_pool_persist_filename,\n                                       c->options.ifconfig_pool_persist_refresh_freq);\n        c->c1.ifconfig_pool_persist_owned = true;\n    }\n}\n\nstatic void\ndo_close_ifconfig_pool_persist(struct context *c)\n{\n    if (!(c->sig->signal_received == SIGUSR1))\n    {\n        if (c->c1.ifconfig_pool_persist && c->c1.ifconfig_pool_persist_owned)\n        {\n            ifconfig_pool_persist_close(c->c1.ifconfig_pool_persist);\n            c->c1.ifconfig_pool_persist = NULL;\n            c->c1.ifconfig_pool_persist_owned = false;\n        }\n    }\n}\n\n/*\n * Inherit environmental variables\n */\n\nstatic void\ndo_inherit_env(struct context *c, const struct env_set *src)\n{\n    c->c2.es = env_set_create(NULL);\n    c->c2.es_owned = true;\n    env_set_inherit(c->c2.es, src);\n}\n\nstatic void\ndo_env_set_destroy(struct context *c)\n{\n    if (c->c2.es && c->c2.es_owned)\n    {\n        env_set_destroy(c->c2.es);\n        c->c2.es = NULL;\n        c->c2.es_owned = false;\n    }\n}\n\nstatic void\ndo_signal_on_tls_errors(struct context *c)\n{\n    if (c->options.tls_exit)\n    {\n        c->c2.tls_exit_signal = SIGTERM;\n    }\n    else\n    {\n        c->c2.tls_exit_signal = SIGUSR1;\n    }\n}\n\n#ifdef ENABLE_PLUGIN\n\nvoid\ninit_plugins(struct context *c)\n{\n    if (c->options.plugin_list && !c->plugins)\n    {\n        c->plugins = plugin_list_init(c->options.plugin_list);\n        c->plugins_owned = true;\n    }\n}\n\nvoid\nopen_plugins(struct context *c, const bool import_options, int init_point)\n{\n    if (c->plugins && c->plugins_owned)\n    {\n        if (import_options)\n        {\n            struct plugin_return pr, config;\n            plugin_return_init(&pr);\n            plugin_list_open(c->plugins, c->options.plugin_list, &pr, c->c2.es, init_point);\n            plugin_return_get_column(&pr, &config, \"config\");\n            if (plugin_return_defined(&config))\n            {\n                int i;\n                for (i = 0; i < config.n; ++i)\n                {\n                    unsigned int option_types_found = 0;\n                    if (config.list[i] && config.list[i]->value)\n                    {\n                        options_string_import(\n                            &c->options, config.list[i]->value, D_IMPORT_ERRORS | M_OPTERR,\n                            OPT_P_DEFAULT & ~OPT_P_PLUGIN, &option_types_found, c->es);\n                    }\n                }\n            }\n            plugin_return_free(&pr);\n        }\n        else\n        {\n            plugin_list_open(c->plugins, c->options.plugin_list, NULL, c->c2.es, init_point);\n        }\n    }\n}\n\nstatic void\ndo_close_plugins(struct context *c)\n{\n    if (c->plugins && c->plugins_owned && !(c->sig->signal_received == SIGUSR1))\n    {\n        plugin_list_close(c->plugins);\n        c->plugins = NULL;\n        c->plugins_owned = false;\n    }\n}\n\nstatic void\ndo_inherit_plugins(struct context *c, const struct context *src)\n{\n    if (!c->plugins && src->plugins)\n    {\n        c->plugins = plugin_list_inherit(src->plugins);\n        c->plugins_owned = true;\n    }\n}\n\n#endif /* ifdef ENABLE_PLUGIN */\n\n#ifdef ENABLE_MANAGEMENT\n\nstatic void\nmanagement_callback_status_p2p(void *arg, const int version, struct status_output *so)\n{\n    struct context *c = (struct context *)arg;\n    print_status(c, so);\n}\n\nvoid\nmanagement_show_net_callback(void *arg, const msglvl_t msglevel)\n{\n#ifdef _WIN32\n    show_routes(msglevel);\n    show_adapters(msglevel);\n    msg(msglevel, \"END\");\n#else\n    msg(msglevel, \"ERROR: Sorry, this command is currently only implemented on Windows\");\n#endif\n}\n\n#ifdef TARGET_ANDROID\nint\nmanagement_callback_network_change(void *arg, bool samenetwork)\n{\n    /* Check if the client should translate the network change to a SIGUSR1 to\n     * reestablish the connection or just reprotect the socket\n     *\n     * At the moment just assume that, for all settings that use pull (not\n     * --static) and are not using peer-id reestablishing the connection is\n     * required (unless the network is the same)\n     *\n     * The function returns -1 on invalid fd and -2 if the socket cannot be\n     * reused. On the -2 return value the man_network_change function triggers\n     * a SIGUSR1 to force a reconnect.\n     */\n\n    int socketfd = -1;\n    struct context *c = (struct context *)arg;\n    if (!c->c2.link_sockets || !c->c2.link_sockets[0])\n    {\n        return -1;\n    }\n    if (c->c2.link_sockets[0]->sd == SOCKET_UNDEFINED)\n    {\n        return -1;\n    }\n\n    /* On some newer Android handsets, changing to a different network\n     * often does not trigger a TCP reset but continue using the old\n     * connection (e.g. using mobile connection when WiFi becomes available */\n    struct link_socket_info *lsi = get_link_socket_info(c);\n    if (lsi && proto_is_tcp(lsi->proto) && !samenetwork)\n    {\n        return -2;\n    }\n\n    socketfd = c->c2.link_sockets[0]->sd;\n    if (!c->options.pull || c->c2.tls_multi->use_peer_id || samenetwork)\n    {\n        return socketfd;\n    }\n    else\n    {\n        return -2;\n    }\n}\n#endif /* ifdef TARGET_ANDROID */\n\n#endif /* ifdef ENABLE_MANAGEMENT */\n\nvoid\ninit_management_callback_p2p(struct context *c)\n{\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        struct management_callback cb;\n        CLEAR(cb);\n        cb.arg = c;\n        cb.status = management_callback_status_p2p;\n        cb.show_net = management_show_net_callback;\n        cb.proxy_cmd = management_callback_proxy_cmd;\n        cb.remote_cmd = management_callback_remote_cmd;\n        cb.send_cc_message = management_callback_send_cc_message;\n#ifdef TARGET_ANDROID\n        cb.network_change = management_callback_network_change;\n#endif\n        cb.remote_entry_count = management_callback_remote_entry_count;\n        cb.remote_entry_get = management_callback_remote_entry_get;\n        management_set_callback(management, &cb);\n    }\n#endif\n}\n\n#ifdef ENABLE_MANAGEMENT\n\nvoid\ninit_management(void)\n{\n    if (!management)\n    {\n        management = management_init();\n    }\n}\n\nbool\nopen_management(struct context *c)\n{\n    /* initialize management layer */\n    if (management)\n    {\n        if (c->options.management_addr)\n        {\n            unsigned int flags = c->options.management_flags;\n            if (c->options.mode == MODE_SERVER)\n            {\n                flags |= MF_SERVER;\n            }\n            if (management_open(\n                    management, c->options.management_addr, c->options.management_port,\n                    c->options.management_user_pass, c->options.management_client_user,\n                    c->options.management_client_group, c->options.management_log_history_cache,\n                    c->options.management_echo_buffer_size, c->options.management_state_buffer_size,\n                    c->options.remap_sigusr1, flags))\n            {\n                management_set_state(management, OPENVPN_STATE_CONNECTING, NULL, NULL, NULL, NULL,\n                                     NULL);\n            }\n\n            /* initial management hold, called early, before first context initialization */\n            do_hold(0);\n            if (IS_SIG(c))\n            {\n                msg(M_WARN, \"Signal received from management interface, exiting\");\n                return false;\n            }\n        }\n        else\n        {\n            close_management();\n        }\n    }\n    return true;\n}\n\nvoid\nclose_management(void)\n{\n    if (management)\n    {\n        management_close(management);\n        management = NULL;\n    }\n}\n\n#endif /* ifdef ENABLE_MANAGEMENT */\n\n\nvoid\nuninit_management_callback(void)\n{\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        management_clear_callback(management);\n    }\n#endif\n}\n\nvoid\npersist_client_stats(struct context *c)\n{\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        man_persist_client_stats(management, c);\n    }\n#endif\n}\n\n/*\n * Initialize a tunnel instance.\n */\nstatic void\ninit_instance(struct context *c, const struct env_set *env, const unsigned int flags)\n{\n    const struct options *options = &c->options;\n    const bool child = (c->mode == CM_CHILD_TCP || c->mode == CM_CHILD_UDP);\n\n    /* init garbage collection level */\n    gc_init(&c->c2.gc);\n\n    /* inherit environmental variables */\n    if (env)\n    {\n        do_inherit_env(c, env);\n    }\n\n    if (c->mode == CM_P2P)\n    {\n        init_management_callback_p2p(c);\n    }\n\n    /* possible sleep or management hold if restart */\n    if (c->mode == CM_P2P || c->mode == CM_TOP)\n    {\n        do_startup_pause(c);\n        if (IS_SIG(c))\n        {\n            goto sig;\n        }\n    }\n\n    if (c->options.resolve_in_advance)\n    {\n        do_preresolve(c);\n        if (IS_SIG(c))\n        {\n            goto sig;\n        }\n    }\n\n    /* Resets all values to the initial values from the config where needed */\n    pre_connect_restore(&c->options, &c->c2.gc);\n\n    /* map in current connection entry */\n    next_connection_entry(c);\n\n    /* should we disable paging? */\n    if (c->first_time && options->mlock)\n    {\n        platform_mlockall(true);\n    }\n\n    /* get passwords if undefined */\n    if (auth_retry_get() == AR_INTERACT)\n    {\n        init_query_passwords(c);\n    }\n\n    /* initialize context level 2 --verb/--mute parms */\n    init_verb_mute(c, IVM_LEVEL_2);\n\n    /* set error message delay for non-server modes */\n    if (c->mode == CM_P2P)\n    {\n        set_check_status_error_delay(P2P_ERROR_DELAY_MS);\n    }\n\n    /* warn about inconsistent options */\n    if (c->mode == CM_P2P || c->mode == CM_TOP)\n    {\n        do_option_warnings(c);\n    }\n\n#ifdef ENABLE_PLUGIN\n    /* initialize plugins */\n    if (c->mode == CM_P2P || c->mode == CM_TOP)\n    {\n        open_plugins(c, false, OPENVPN_PLUGIN_INIT_PRE_DAEMON);\n    }\n#endif\n\n    /* should we throw a signal on TLS errors? */\n    do_signal_on_tls_errors(c);\n\n    /* open --status file */\n    if (c->mode == CM_P2P || c->mode == CM_TOP)\n    {\n        do_open_status_output(c);\n    }\n\n    /* open --ifconfig-pool-persist file */\n    if (c->mode == CM_TOP)\n    {\n        do_open_ifconfig_pool_persist(c);\n    }\n\n    /* reset OCC state */\n    if (c->mode == CM_P2P || child)\n    {\n        c->c2.occ_op = occ_reset_op();\n    }\n\n    /* our wait-for-i/o objects, different for posix vs. win32 */\n    if (c->mode == CM_P2P || c->mode == CM_TOP)\n    {\n        do_event_set_init(c, SHAPER_DEFINED(&c->options));\n    }\n    else if (c->mode == CM_CHILD_TCP)\n    {\n        do_event_set_init(c, false);\n    }\n\n    /* initialize HTTP or SOCKS proxy object at scope level 2 */\n    init_proxy(c);\n\n    /* allocate our socket object */\n    if (c->mode == CM_P2P || c->mode == CM_TOP || c->mode == CM_CHILD_TCP)\n    {\n        do_link_socket_new(c);\n    }\n\n#ifdef ENABLE_FRAGMENT\n    /* initialize internal fragmentation object */\n    if (options->ce.fragment && (c->mode == CM_P2P || child))\n    {\n        c->c2.fragment = fragment_init(&c->c2.frame);\n    }\n#endif\n\n    /* init crypto layer */\n    {\n        unsigned int crypto_flags = 0;\n        if (c->mode == CM_TOP)\n        {\n            crypto_flags = CF_INIT_TLS_AUTH_STANDALONE;\n        }\n        else if (c->mode == CM_P2P)\n        {\n            crypto_flags = CF_LOAD_PERSISTED_PACKET_ID | CF_INIT_TLS_MULTI;\n        }\n        else if (child)\n        {\n            crypto_flags = CF_INIT_TLS_MULTI;\n        }\n        do_init_crypto(c, crypto_flags);\n        if (IS_SIG(c) && !child)\n        {\n            goto sig;\n        }\n    }\n\n#ifdef USE_COMP\n    /* initialize compression library. */\n    if (comp_enabled(&options->comp) && (c->mode == CM_P2P || child))\n    {\n        c->c2.comp_context = comp_init(&options->comp);\n    }\n#endif\n\n    /* initialize MTU variables */\n    do_init_frame(c);\n\n    /* initialize TLS MTU variables */\n    do_init_frame_tls(c);\n\n    /* init workspace buffers whose size is derived from frame size */\n    if (c->mode == CM_P2P || c->mode == CM_CHILD_TCP)\n    {\n        do_init_buffers(c);\n    }\n\n#ifdef ENABLE_FRAGMENT\n    /* initialize internal fragmentation capability with known frame size */\n    if (options->ce.fragment && (c->mode == CM_P2P || child))\n    {\n        do_init_fragment(c);\n    }\n#endif\n\n    /* bind the TCP/UDP socket */\n    if (c->mode == CM_P2P || c->mode == CM_TOP || c->mode == CM_CHILD_TCP)\n    {\n        do_init_socket_phase1(c);\n    }\n\n    /* initialize tun/tap device object,\n     * open tun/tap device, ifconfig, run up script, etc. */\n    if (!(options->up_delay || PULL_DEFINED(options)) && (c->mode == CM_P2P || c->mode == CM_TOP))\n    {\n        int error_flags = 0;\n        c->c2.did_open_tun = do_open_tun(c, &error_flags);\n    }\n\n    /* print MTU info */\n    do_print_data_channel_mtu_parms(c);\n\n    /* get local and remote options compatibility strings */\n    if (c->mode == CM_P2P || child)\n    {\n        do_compute_occ_strings(c);\n    }\n\n    /* initialize output speed limiter */\n    if (c->mode == CM_P2P)\n    {\n        do_init_traffic_shaper(c);\n    }\n\n    /* do one-time inits, and possibly become a daemon here */\n    do_init_first_time(c);\n\n#ifdef ENABLE_PLUGIN\n    /* initialize plugins */\n    if (c->mode == CM_P2P || c->mode == CM_TOP)\n    {\n        open_plugins(c, false, OPENVPN_PLUGIN_INIT_POST_DAEMON);\n    }\n#endif\n\n    /* initialise connect timeout timer */\n    do_init_server_poll_timeout(c);\n\n    /* finalize the TCP/UDP socket */\n    if (c->mode == CM_P2P || c->mode == CM_TOP || c->mode == CM_CHILD_TCP)\n    {\n        do_init_socket_phase2(c);\n\n\n        /* Update dynamic frame calculation as exact transport socket information\n         * (IP vs IPv6) may be only available after socket phase2 has finished.\n         * This is only needed for --static or no crypto, NCP will recalculate this\n         * in tls_session_update_crypto_params (P2MP) */\n        for (int i = 0; i < c->c1.link_sockets_num; i++)\n        {\n            frame_calculate_dynamic(&c->c2.frame, &c->c1.ks.key_type, &c->options,\n                                    &c->c2.link_sockets[i]->info);\n        }\n    }\n\n    /*\n     * Actually do UID/GID downgrade, and chroot, if requested.\n     * May be delayed by --client, --pull, or --up-delay.\n     */\n    do_uid_gid_chroot(c, c->c2.did_open_tun);\n\n    /* initialize timers */\n    if (c->mode == CM_P2P || child)\n    {\n        do_init_timers(c, false);\n    }\n\n#ifdef ENABLE_PLUGIN\n    /* initialize plugins */\n    if (c->mode == CM_P2P || c->mode == CM_TOP)\n    {\n        open_plugins(c, false, OPENVPN_PLUGIN_INIT_POST_UID_CHANGE);\n    }\n#endif\n\n#if PORT_SHARE\n    /* share OpenVPN port with foreign (such as HTTPS) server */\n    if (c->first_time && (c->mode == CM_P2P || c->mode == CM_TOP))\n    {\n        init_port_share(c);\n    }\n#endif\n\n    /* Check for signals */\n    if (IS_SIG(c))\n    {\n        goto sig;\n    }\n\n    return;\n\nsig:\n    if (!c->sig->signal_text)\n    {\n        c->sig->signal_text = \"init_instance\";\n    }\n    close_context(c, -1, flags);\n    return;\n}\n\n/*\n * Initialize a tunnel instance, handle pre and post-init\n * signal settings.\n */\nvoid\ninit_instance_handle_signals(struct context *c, const struct env_set *env, const unsigned int flags)\n{\n    pre_init_signal_catch();\n    init_instance(c, env, flags);\n    post_init_signal_catch();\n\n    /*\n     * This is done so that signals thrown during\n     * initialization can bring us back to\n     * a management hold.\n     */\n    if (IS_SIG(c))\n    {\n        remap_signal(c);\n        uninit_management_callback();\n    }\n}\n\n/*\n * Close a tunnel instance.\n */\nvoid\nclose_instance(struct context *c)\n{\n    /* close event objects */\n    do_close_event_set(c);\n\n    if (c->mode == CM_P2P || c->mode == CM_CHILD_TCP || c->mode == CM_CHILD_UDP\n        || c->mode == CM_TOP)\n    {\n#ifdef USE_COMP\n        if (c->c2.comp_context)\n        {\n            comp_uninit(c->c2.comp_context);\n            c->c2.comp_context = NULL;\n        }\n#endif\n\n        /* free buffers */\n        do_close_free_buf(c);\n\n        /* close peer for DCO if enabled, needs peer-id so must be done before\n         * closing TLS contexts */\n        dco_remove_peer(c);\n\n        /* close TLS */\n        do_close_tls(c);\n\n        /* free key schedules */\n        do_close_free_key_schedule(c, (c->mode == CM_P2P || c->mode == CM_TOP));\n\n        /* close TCP/UDP connection */\n        do_close_link_socket(c);\n\n        /* close TUN/TAP device */\n        do_close_tun(c, false);\n\n#ifdef ENABLE_MANAGEMENT\n        if (management)\n        {\n            management_notify_client_close(management, &c->c2.mda_context, NULL);\n        }\n#endif\n\n#ifdef ENABLE_PLUGIN\n        /* call plugin close functions and unload */\n        do_close_plugins(c);\n#endif\n\n        /* close packet-id persistence file */\n        do_close_packet_id(c);\n\n        /* close --status file */\n        do_close_status_output(c);\n\n#ifdef ENABLE_FRAGMENT\n        /* close fragmentation handler */\n        do_close_fragment(c);\n#endif\n\n        /* close --ifconfig-pool-persist obj */\n        do_close_ifconfig_pool_persist(c);\n\n        /* free up environmental variable store */\n        do_env_set_destroy(c);\n\n        /* close HTTP or SOCKS proxy */\n        uninit_proxy(c);\n\n        /* garbage collect */\n        gc_free(&c->c2.gc);\n    }\n}\n\nvoid\ninherit_context_child(struct context *dest, const struct context *src, struct link_socket *sock)\n{\n    CLEAR(*dest);\n\n    /* proto_is_dgram will ASSERT(0) if proto is invalid */\n    dest->mode = proto_is_dgram(sock->info.proto) ? CM_CHILD_UDP : CM_CHILD_TCP;\n\n    dest->gc = gc_new();\n\n    ALLOC_OBJ_CLEAR_GC(dest->sig, struct signal_info, &dest->gc);\n\n    /* c1 init */\n    packet_id_persist_init(&dest->c1.pid_persist);\n    dest->c1.link_sockets_num = 1;\n    do_link_socket_addr_new(dest);\n\n    dest->c1.ks.key_type = src->c1.ks.key_type;\n    /* inherit SSL context */\n    dest->c1.ks.ssl_ctx = src->c1.ks.ssl_ctx;\n    dest->c1.ks.tls_wrap_key = src->c1.ks.tls_wrap_key;\n    dest->c1.ks.tls_auth_key_type = src->c1.ks.tls_auth_key_type;\n    dest->c1.ks.tls_crypt_v2_server_key = src->c1.ks.tls_crypt_v2_server_key;\n    /* inherit pre-NCP ciphers */\n    dest->options.ciphername = src->options.ciphername;\n    dest->options.authname = src->options.authname;\n\n    /* inherit auth-token */\n    dest->c1.ks.auth_token_key = src->c1.ks.auth_token_key;\n\n    /* options */\n    dest->options = src->options;\n    dest->options.ce.proto = sock->info.proto;\n    options_detach(&dest->options);\n\n    dest->c2.event_set = src->c2.event_set;\n\n    if (dest->mode == CM_CHILD_TCP)\n    {\n        /*\n         * The CM_TOP context does the socket listen(),\n         * and the CM_CHILD_TCP context does the accept().\n         */\n        dest->c2.accept_from = sock;\n    }\n\n#ifdef ENABLE_PLUGIN\n    /* inherit plugins */\n    do_inherit_plugins(dest, src);\n#endif\n\n    /* context init */\n\n    /* inherit tun/tap interface object now as it may be required\n     * to initialize the DCO context in init_instance()\n     */\n    dest->c1.tuntap = src->c1.tuntap;\n\n    /* UDP inherits some extra things which TCP does not */\n    if (dest->mode == CM_CHILD_UDP)\n    {\n        ASSERT(!dest->c2.link_sockets);\n        ASSERT(dest->options.ce.local_list);\n\n        /* inherit buffers */\n        dest->c2.buffers = src->c2.buffers;\n\n        ALLOC_ARRAY_GC(dest->c2.link_sockets, struct link_socket *, 1, &dest->gc);\n\n        /* inherit parent link_socket and tuntap */\n        dest->c2.link_sockets[0] = sock;\n\n        ALLOC_ARRAY_GC(dest->c2.link_socket_infos, struct link_socket_info *, 1, &dest->gc);\n        ALLOC_OBJ_GC(dest->c2.link_socket_infos[0], struct link_socket_info, &dest->gc);\n        *dest->c2.link_socket_infos[0] = sock->info;\n\n        /* locally override some link_socket_info fields */\n        dest->c2.link_socket_infos[0]->lsa = &dest->c1.link_socket_addrs[0];\n        dest->c2.link_socket_infos[0]->connection_established = false;\n    }\n\n    init_instance(dest, src->c2.es, CC_NO_CLOSE | CC_USR1_TO_HUP);\n    if (IS_SIG(dest))\n    {\n        return;\n    }\n}\n\nvoid\ninherit_context_top(struct context *dest, const struct context *src)\n{\n    /* copy parent */\n    *dest = *src;\n\n    /*\n     * CM_TOP_CLONE will prevent close_instance from freeing or closing\n     * resources owned by the parent.\n     *\n     * Also note that CM_TOP_CLONE context objects are\n     * closed by multi_top_free in multi.c.\n     */\n    dest->mode = CM_TOP_CLONE;\n\n    dest->first_time = false;\n    dest->c0 = NULL;\n\n    options_detach(&dest->options);\n    gc_detach(&dest->gc);\n    gc_detach(&dest->c2.gc);\n\n    /* detach plugins */\n    dest->plugins_owned = false;\n\n    dest->c2.tls_multi = NULL;\n\n    /* detach c1 ownership */\n    dest->c1.tuntap_owned = false;\n    dest->c1.status_output_owned = false;\n    dest->c1.ifconfig_pool_persist_owned = false;\n\n    /* detach c2 ownership */\n    dest->c2.event_set_owned = false;\n    dest->c2.link_socket_owned = false;\n    dest->c2.buffers_owned = false;\n    dest->c2.es_owned = false;\n\n    dest->c2.event_set = NULL;\n    do_event_set_init(dest, false);\n\n#ifdef USE_COMP\n    dest->c2.comp_context = NULL;\n#endif\n}\n\nvoid\nclose_context(struct context *c, int sig, unsigned int flags)\n{\n    ASSERT(c);\n    ASSERT(c->sig);\n\n    if (sig >= 0)\n    {\n        register_signal(c->sig, sig, \"close_context\");\n    }\n\n    if (c->sig->signal_received == SIGUSR1)\n    {\n        if ((flags & CC_USR1_TO_HUP)\n            || (c->sig->source == SIG_SOURCE_HARD && (flags & CC_HARD_USR1_TO_HUP)))\n        {\n            register_signal(c->sig, SIGHUP, \"close_context usr1 to hup\");\n        }\n    }\n\n    if (!(flags & CC_NO_CLOSE))\n    {\n        close_instance(c);\n    }\n\n    if (flags & CC_GC_FREE)\n    {\n        context_gc_free(c);\n    }\n}\n\n/* Write our PID to a file */\nvoid\nwrite_pid_file(const char *filename, const char *chroot_dir)\n{\n    if (filename)\n    {\n        unsigned int pid = 0;\n        FILE *fp = platform_fopen(filename, \"w\");\n        if (!fp)\n        {\n            msg(M_ERR, \"Open error on pid file %s\", filename);\n            return;\n        }\n\n        pid = platform_getpid();\n        fprintf(fp, \"%u\\n\", pid);\n        if (fclose(fp))\n        {\n            msg(M_ERR, \"Close error on pid file %s\", filename);\n        }\n\n        /* remember file name so it can be deleted \"out of context\" later */\n        /* (the chroot case is more complex and not handled today) */\n        if (!chroot_dir)\n        {\n            saved_pid_file_name = strdup(filename);\n            if (!saved_pid_file_name)\n            {\n                msg(M_FATAL, \"Failed allocate memory saved_pid_file_name\");\n            }\n        }\n    }\n}\n\n/* remove PID file on exit, called from openvpn_exit() */\nvoid\nremove_pid_file(void)\n{\n    if (saved_pid_file_name)\n    {\n        platform_unlink(saved_pid_file_name);\n    }\n}\n\n\n/*\n * Do a loopback test\n * on the crypto subsystem.\n */\nvoid\ndo_test_crypto(struct context *c)\n{\n    /* print version number */\n    msg(M_INFO, \"%s\", title_string);\n    const struct options *options = &c->options;\n\n    ASSERT(options->test_crypto);\n    init_verb_mute(c, IVM_LEVEL_1);\n    context_init_1(c);\n    next_connection_entry(c);\n    do_init_crypto_test(c);\n\n    frame_finalize_options(c, options);\n\n    test_crypto(&c->c2.crypto_options, &c->c2.frame);\n\n    key_schedule_free(&c->c1.ks, true);\n    packet_id_free(&c->c2.crypto_options.packet_id);\n\n    context_gc_free(c);\n}\n"
  },
  {
    "path": "src/openvpn/init.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef INIT_H\n#define INIT_H\n\n#include \"openvpn.h\"\n\n/*\n * Baseline maximum number of events\n * to wait for.\n */\n#define BASE_N_EVENTS 5\n\nvoid context_clear_2(struct context *c);\n\nvoid context_init_1(struct context *c);\n\nvoid context_clear_all_except_first_time(struct context *c);\n\nbool init_static(void);\n\nvoid uninit_static(void);\n\n#define IVM_LEVEL_1 (1 << 0)\n#define IVM_LEVEL_2 (1 << 1)\nvoid init_verb_mute(struct context *c, unsigned int flags);\n\nvoid init_options_dev(struct options *options);\n\nbool print_openssl_info(const struct options *options);\n\nbool do_genkey(const struct options *options);\n\nbool do_persist_tuntap(struct options *options, openvpn_net_ctx_t *ctx);\n\nbool possibly_become_daemon(const struct options *options);\n\nvoid pre_setup(const struct options *options);\n\nvoid init_instance_handle_signals(struct context *c, const struct env_set *env,\n                                  const unsigned int flags);\n\n/**\n * Query for private key and auth-user-pass username/passwords.\n */\nvoid init_query_passwords(const struct context *c);\n\nbool do_route(const struct options *options, struct route_list *route_list,\n              struct route_ipv6_list *route_ipv6_list, const struct tuntap *tt,\n              const struct plugin_list *plugins, struct env_set *es, openvpn_net_ctx_t *ctx);\n\nvoid close_instance(struct context *c);\n\nvoid do_test_crypto(struct context *o);\n\nvoid context_gc_free(struct context *c);\n\nbool do_up(struct context *c, bool pulled_options, unsigned int option_types_found);\n\n/**\n * @brief A simplified version of the do_up() function. This function is called\n *        after receiving a successful PUSH_UPDATE message. It closes and reopens\n *        the TUN device to apply the updated options.\n *\n * @param c The context structure.\n * @param option_types_found The options found in the PUSH_UPDATE message.\n * @return true on success.\n * @return false on error.\n */\nbool do_update(struct context *c, unsigned int option_types_found);\n\nunsigned int pull_permission_mask(const struct context *c);\n\nconst char *format_common_name(struct context *c, struct gc_arena *gc);\n\nvoid reset_coarse_timers(struct context *c);\n\n/*\n * Handle non-tun-related pulled options.\n * Set `is_update` param to true to skip NCP check.\n */\nbool do_deferred_options(struct context *c, const unsigned int found, const bool is_update);\n\nvoid inherit_context_child(struct context *dest, const struct context *src,\n                           struct link_socket *sock);\n\nvoid inherit_context_top(struct context *dest, const struct context *src);\n\n#define CC_GC_FREE          (1 << 0)\n#define CC_USR1_TO_HUP      (1 << 1)\n#define CC_HARD_USR1_TO_HUP (1 << 2)\n#define CC_NO_CLOSE         (1 << 3)\n\nvoid close_context(struct context *c, int sig, unsigned int flags);\n\nstruct context_buffers *init_context_buffers(const struct frame *frame);\n\nvoid free_context_buffers(struct context_buffers *b);\n\n#define ISC_ERRORS       (1 << 0)\n#define ISC_SERVER       (1 << 1)\n#define ISC_ROUTE_ERRORS (1 << 2)\nvoid initialization_sequence_completed(struct context *c, const unsigned int flags);\n\n#ifdef ENABLE_MANAGEMENT\n\nvoid init_management(void);\n\nbool open_management(struct context *c);\n\nvoid close_management(void);\n\nvoid management_show_net_callback(void *arg, const msglvl_t msglevel);\n\n#endif\n\nvoid init_management_callback_p2p(struct context *c);\n\nvoid uninit_management_callback(void);\n\n#ifdef ENABLE_PLUGIN\nvoid init_plugins(struct context *c);\n\nvoid open_plugins(struct context *c, const bool import_options, int init_point);\n\n#endif\n\nvoid tun_abort(void);\n\nvoid write_pid_file(const char *filename, const char *chroot_dir);\n\nvoid remove_pid_file(void);\n\nvoid persist_client_stats(struct context *c);\n\n#endif /* ifndef INIT_H */\n"
  },
  {
    "path": "src/openvpn/integer.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef INTEGER_H\n#define INTEGER_H\n\n#include \"error.h\"\n\n#ifndef htonll\n#define htonll(x)    \\\n    ((1 == htonl(1)) \\\n         ? (x)       \\\n         : ((uint64_t)htonl((uint32_t)((x) & 0xFFFFFFFF)) << 32) | htonl((uint32_t)((x) >> 32)))\n#endif\n\n#ifndef ntohll\n#define ntohll(x)    \\\n    ((1 == ntohl(1)) \\\n         ? (x)       \\\n         : ((uint64_t)ntohl((uint32_t)((x) & 0xFFFFFFFF)) << 32) | ntohl((uint32_t)((x) >> 32)))\n#endif\n\nstatic inline int\nclamp_size_to_int(size_t size)\n{\n    ASSERT(size <= INT_MAX);\n    return (int)size;\n}\n\n/*\n * min/max functions\n */\nstatic inline unsigned int\nmax_uint(unsigned int x, unsigned int y)\n{\n    if (x > y)\n    {\n        return x;\n    }\n    else\n    {\n        return y;\n    }\n}\n\nstatic inline unsigned int\nmin_uint(unsigned int x, unsigned int y)\n{\n    if (x < y)\n    {\n        return x;\n    }\n    else\n    {\n        return y;\n    }\n}\n\nstatic inline size_t\nmin_size(size_t x, size_t y)\n{\n    if (x < y)\n    {\n        return x;\n    }\n    else\n    {\n        return y;\n    }\n}\n\nstatic inline int\nmax_int(int x, int y)\n{\n    if (x > y)\n    {\n        return x;\n    }\n    else\n    {\n        return y;\n    }\n}\n\nstatic inline int\nmin_int(int x, int y)\n{\n    if (x < y)\n    {\n        return x;\n    }\n    else\n    {\n        return y;\n    }\n}\n\nstatic inline int\nconstrain_int(int x, int min, int max)\n{\n    if (min > max)\n    {\n        return min;\n    }\n    if (x < min)\n    {\n        return min;\n    }\n    else if (x > max)\n    {\n        return max;\n    }\n    else\n    {\n        return x;\n    }\n}\n\nstatic inline unsigned int\nconstrain_uint(unsigned int x, unsigned int min, unsigned int max)\n{\n    if (min > max)\n    {\n        return min;\n    }\n    if (x < min)\n    {\n        return min;\n    }\n    else if (x > max)\n    {\n        return max;\n    }\n    else\n    {\n        return x;\n    }\n}\n\n/*\n * Functions used for circular buffer index arithmetic.\n */\n\n/*\n * Return x - y on a circle of circumference mod by shortest path.\n *\n * 0 <= x < mod\n * 0 <= y < mod\n */\nstatic inline int\nmodulo_subtract(int x, int y, int mod)\n{\n    const int d1 = x - y;\n    const int d2 = (x > y ? -mod : mod) + d1;\n    ASSERT(0 <= x && x < mod && 0 <= y && y < mod);\n    return abs(d1) > abs(d2) ? d2 : d1;\n}\n\n/*\n * Return x + y on a circle of circumference mod.\n *\n * 0 <= x < mod\n * -mod <= y <= mod\n */\nstatic inline int\nmodulo_add(int x, int y, int mod)\n{\n    int sum = x + y;\n    ASSERT(0 <= x && x < mod && -mod <= y && y <= mod);\n    if (sum >= mod)\n    {\n        sum -= mod;\n    }\n    if (sum < 0)\n    {\n        sum += mod;\n    }\n    return sum;\n}\n\n/*\n * Return the next largest power of 2\n * or u if u is a power of 2.\n */\nstatic inline size_t\nadjust_power_of_2(size_t u)\n{\n    size_t ret = 1;\n\n    while (ret < u)\n    {\n        ret <<= 1;\n        ASSERT(ret > 0);\n    }\n\n    return ret;\n}\n\nstatic inline int\nindex_verify(int index, int size, const char *file, int line)\n{\n    if (index < 0 || index >= size)\n    {\n        msg(M_FATAL, \"Assertion Failed: Array index=%d out of bounds for array size=%d in %s:%d\",\n            index, size, file, line);\n    }\n    return index;\n}\n\n/**\n * Rounds down num to the nearest multiple of multiple\n */\nstatic inline size_t\nround_down_size(size_t num, size_t multiple)\n{\n    return (num / multiple) * multiple;\n}\n\n#endif /* ifndef INTEGER_H */\n"
  },
  {
    "path": "src/openvpn/interval.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"interval.h\"\n\n#include \"memdbg.h\"\n\nvoid\ninterval_init(struct interval *top, int horizon, int refresh)\n{\n    CLEAR(*top);\n    top->refresh = refresh;\n    top->horizon = horizon;\n}\n\nbool\nevent_timeout_trigger(struct event_timeout *et, struct timeval *tv, const int et_const_retry)\n{\n    if (!et->defined)\n    {\n        return false;\n    }\n\n    bool ret = false;\n    interval_t wakeup = event_timeout_remaining(et);\n\n    if (wakeup <= 0)\n    {\n#if INTERVAL_DEBUG\n        dmsg(D_INTERVAL, \"EVENT event_timeout_trigger (%d) etcr=%d\", et->n, et_const_retry);\n#endif\n        if (et_const_retry < 0)\n        {\n            et->last = now;\n            wakeup = et->n;\n            ret = true;\n        }\n        else\n        {\n            wakeup = et_const_retry;\n        }\n    }\n\n    if (tv && wakeup < tv->tv_sec)\n    {\n#if INTERVAL_DEBUG\n        dmsg(D_INTERVAL, \"EVENT event_timeout_wakeup (%d/%d) etcr=%d\", (int)wakeup, et->n,\n             et_const_retry);\n#endif\n        tv->tv_sec = wakeup;\n        tv->tv_usec = 0;\n    }\n    return ret;\n}\n"
  },
  {
    "path": "src/openvpn/interval.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * The interval_ routines are designed to optimize the calling of a routine\n * (normally tls_multi_process()) which can be called less frequently\n * between triggers.\n */\n\n#ifndef INTERVAL_H\n#define INTERVAL_H\n\n#include \"otime.h\"\n\n#define INTERVAL_DEBUG 0\n\n/*\n * Designed to limit calls to expensive functions that need to be called\n * regularly.\n */\n\nstruct interval\n{\n    interval_t refresh;\n    interval_t horizon;\n    time_t future_trigger;\n    time_t last_action;\n    time_t last_test_true;\n};\n\nvoid interval_init(struct interval *top, int horizon, int refresh);\n\n/*\n * IF\n *   last_action less than horizon seconds ago\n *   OR last_test_true more than refresh seconds ago\n *   OR hit future_trigger\n * THEN\n *   return true\n * ELSE\n *   set wakeup to the number of seconds until a true return\n *   return false\n */\n\nstatic inline bool\ninterval_test(struct interval *top)\n{\n    bool trigger = false;\n    const time_t local_now = now;\n\n    if (top->future_trigger && local_now >= top->future_trigger)\n    {\n        trigger = true;\n        top->future_trigger = 0;\n    }\n\n    if (top->last_action + top->horizon > local_now\n        || top->last_test_true + top->refresh <= local_now || trigger)\n    {\n        top->last_test_true = local_now;\n#if INTERVAL_DEBUG\n        dmsg(D_INTERVAL, \"INTERVAL interval_test true\");\n#endif\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\nstatic inline void\ninterval_schedule_wakeup(struct interval *top, interval_t *wakeup)\n{\n    const time_t local_now = now;\n    interval_earliest_wakeup(wakeup, top->last_test_true + top->refresh, local_now);\n    interval_earliest_wakeup(wakeup, top->future_trigger, local_now);\n#if INTERVAL_DEBUG\n    dmsg(D_INTERVAL, \"INTERVAL interval_schedule wakeup=%d\", (int)*wakeup);\n#endif\n}\n\n/*\n * In wakeup seconds, interval_test will return true once.\n */\nstatic inline void\ninterval_future_trigger(struct interval *top, interval_t wakeup)\n{\n    if (wakeup)\n    {\n#if INTERVAL_DEBUG\n        dmsg(D_INTERVAL, \"INTERVAL interval_future_trigger %d\", (int)wakeup);\n#endif\n        top->future_trigger = now + wakeup;\n    }\n}\n\n/*\n * Once an action is triggered, interval_test will remain true for\n * horizon seconds.\n */\nstatic inline void\ninterval_action(struct interval *top)\n{\n#if INTERVAL_DEBUG\n    dmsg(D_INTERVAL, \"INTERVAL action\");\n#endif\n    top->last_action = now;\n}\n\n/*\n * Measure when n seconds beyond an event have elapsed\n */\n\nstruct event_timeout\n{\n    bool defined; /**< This timeout is active */\n    interval_t n; /**< periodic interval for periodic timeouts */\n    time_t last;  /**< time of last event */\n};\n\nstatic inline bool\nevent_timeout_defined(const struct event_timeout *et)\n{\n    return et->defined;\n}\n/**\n * Clears the timeout and reset all values to 0. Following timer checks will\n * not trigger.\n *\n * @param et    timer struct\n */\nstatic inline void\nevent_timeout_clear(struct event_timeout *et)\n{\n    et->defined = false;\n    et->n = 0;\n    et->last = 0;\n}\n\n\n/**\n * Initialises a timer struct. The timer will become true/trigger after\n * last + n seconds.\n *\n *\n * @param et            Timer struct\n * @param n             Interval of the timer for periodic timer. A negative\n *                      value for n will be interpreted as 0.\n * @param last          Sets the base time of the timer.\n */\nstatic inline void\nevent_timeout_init(struct event_timeout *et, interval_t n, const time_t last)\n{\n    et->defined = true;\n    et->n = (n >= 0) ? n : 0;\n    et->last = last;\n}\n\n/**\n * Resets a timer.\n *\n * Sets the last time the timer has been triggered for the calculation of the\n * next event.\n * @param et\n */\nstatic inline void\nevent_timeout_reset(struct event_timeout *et)\n{\n    if (et->defined)\n    {\n        et->last = now;\n    }\n}\n\n/**\n * Sets the interval n of a timeout.\n * @param et\n * @param n Interval value of the timer, negative values\n *          will be interpreted as 0\n *\n * @note you might need to call reset_coarse_timers after this\n */\nstatic inline void\nevent_timeout_modify_wakeup(struct event_timeout *et, interval_t n)\n{\n    if (et->defined)\n    {\n        et->n = (n >= 0) ? n : 0;\n    }\n}\n\n/**\n * Returns the time until the timeout should triggered, from \\c now.\n * This function does not check if the timeout is actually valid.\n */\nstatic inline interval_t\nevent_timeout_remaining(struct event_timeout *et)\n{\n    return (interval_t)((et->last + et->n) - now);\n}\n\n#define ETT_DEFAULT (-1)\n\n/**\n * This is the principal function for testing and triggering recurring\n * timers.\n *\n * If *et is not triggered, *tv is set to remaining time until the timeout if\n * not already lower:\n *\n *      *tv = minimum(*tv, event_timeout_remaining(*et))\n *\n * If *et triggers and et_const_retry is negative (ETT_DEFAULT is -1):\n *  - the function will return true\n *  - *et will be armed for the next event (et->last set to now).\n *  - *tv will be lowered to the event period (n) if larger than the\n *     period of the event (set to *et's next timeout)\n *  - *et will not changed (ie also not rearmed, stays armed)\n *\n * If *et triggers and et_const_retry >= 0, *tv will be lowered to et_const_try\n * if larger:\n *\n *    *tv = *minimum(*tv, et_const_retry)\n *\n * This is mainly useful if the trigger cannot yet be triggered for other\n * reasons and a backoff timeout should be returned if the timer is ready\n * to trigger.\n *\n *\n * @param et                the timeout to check\n * @param tv                will be set to timeout for next check for this\n *                          timeout unless already smaller.\n * @param et_const_retry    see above\n * @return                  if the timeout has triggered and event has been reset\n */\nbool event_timeout_trigger(struct event_timeout *et, struct timeval *tv, int et_const_retry);\n\n/*\n * Measure time intervals in microseconds\n */\n\n#define USEC_TIMER_MAX 60 /* maximum interval size in seconds */\n\n#define USEC_TIMER_MAX_USEC (USEC_TIMER_MAX * 1000000)\n\nstruct usec_timer\n{\n    struct timeval start;\n    struct timeval end;\n};\n\n#ifdef HAVE_GETTIMEOFDAY\n\nstatic inline void\nusec_timer_start(struct usec_timer *obj)\n{\n    CLEAR(*obj);\n    openvpn_gettimeofday(&obj->start, NULL);\n}\n\nstatic inline void\nusec_timer_end(struct usec_timer *obj)\n{\n    openvpn_gettimeofday(&obj->end, NULL);\n}\n\n#endif /* HAVE_GETTIMEOFDAY */\n\nstatic inline bool\nusec_timer_interval_defined(struct usec_timer *obj)\n{\n    return obj->start.tv_sec && obj->end.tv_sec;\n}\n\nstatic inline int\nusec_timer_interval(struct usec_timer *obj)\n{\n    return tv_subtract(&obj->end, &obj->start, USEC_TIMER_MAX);\n}\n\n#endif /* INTERVAL_H */\n"
  },
  {
    "path": "src/openvpn/list.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n\n#include \"integer.h\"\n#include \"list.h\"\n#include \"misc.h\"\n\n#include \"memdbg.h\"\n\nstruct hash *\nhash_init(const uint32_t n_buckets, const uint32_t iv,\n          uint32_t (*hash_function)(const void *key, uint32_t iv),\n          bool (*compare_function)(const void *key1, const void *key2))\n{\n    struct hash *h;\n\n    ASSERT(n_buckets > 0);\n    ALLOC_OBJ_CLEAR(h, struct hash);\n    h->n_buckets = (uint32_t)adjust_power_of_2(n_buckets);\n    h->mask = h->n_buckets - 1;\n    h->hash_function = hash_function;\n    h->compare_function = compare_function;\n    h->iv = iv;\n    ALLOC_ARRAY(h->buckets, struct hash_bucket, h->n_buckets);\n    for (uint32_t i = 0; i < h->n_buckets; ++i)\n    {\n        struct hash_bucket *b = &h->buckets[i];\n        b->list = NULL;\n    }\n    return h;\n}\n\nvoid\nhash_free(struct hash *hash)\n{\n    for (uint32_t i = 0; i < hash->n_buckets; ++i)\n    {\n        struct hash_bucket *b = &hash->buckets[i];\n        struct hash_element *he = b->list;\n\n        while (he)\n        {\n            struct hash_element *next = he->next;\n            free(he);\n            he = next;\n        }\n    }\n    free(hash->buckets);\n    free(hash);\n}\n\nstruct hash_element *\nhash_lookup_fast(struct hash *hash, struct hash_bucket *bucket, const void *key, uint32_t hv)\n{\n    struct hash_element *he;\n    struct hash_element *prev = NULL;\n\n    he = bucket->list;\n\n    while (he)\n    {\n        if (hv == he->hash_value && (*hash->compare_function)(key, he->key))\n        {\n            /* move to head of list */\n            if (prev)\n            {\n                prev->next = he->next;\n                he->next = bucket->list;\n                bucket->list = he;\n            }\n            return he;\n        }\n        prev = he;\n        he = he->next;\n    }\n\n    return NULL;\n}\n\nbool\nhash_remove_fast(struct hash *hash, struct hash_bucket *bucket, const void *key, uint32_t hv)\n{\n    struct hash_element *he;\n    struct hash_element *prev = NULL;\n\n    he = bucket->list;\n\n    while (he)\n    {\n        if (hv == he->hash_value && (*hash->compare_function)(key, he->key))\n        {\n            if (prev)\n            {\n                prev->next = he->next;\n            }\n            else\n            {\n                bucket->list = he->next;\n            }\n            free(he);\n            --hash->n_elements;\n            return true;\n        }\n        prev = he;\n        he = he->next;\n    }\n    return false;\n}\n\nbool\nhash_add(struct hash *hash, const void *key, void *value, bool replace)\n{\n    uint32_t hv;\n    struct hash_bucket *bucket;\n    struct hash_element *he;\n    bool ret = false;\n\n    hv = hash_value(hash, key);\n    bucket = &hash->buckets[hv & hash->mask];\n\n    if ((he = hash_lookup_fast(hash, bucket, key, hv))) /* already exists? */\n    {\n        if (replace)\n        {\n            he->value = value;\n            ret = true;\n        }\n    }\n    else\n    {\n        hash_add_fast(hash, bucket, key, hv, value);\n        ret = true;\n    }\n\n    return ret;\n}\n\nvoid\nhash_remove_by_value(struct hash *hash, void *value)\n{\n    struct hash_iterator hi;\n    struct hash_element *he;\n\n    hash_iterator_init(hash, &hi);\n    while ((he = hash_iterator_next(&hi)))\n    {\n        if (he->value == value)\n        {\n            hash_iterator_delete_element(&hi);\n        }\n    }\n    hash_iterator_free(&hi);\n}\n\nstatic void\nhash_remove_marked(struct hash *hash, struct hash_bucket *bucket)\n{\n    struct hash_element *prev = NULL;\n    struct hash_element *he = bucket->list;\n\n    while (he)\n    {\n        if (!he->key) /* marked? */\n        {\n            struct hash_element *newhe;\n            if (prev)\n            {\n                newhe = prev->next = he->next;\n            }\n            else\n            {\n                newhe = bucket->list = he->next;\n            }\n            free(he);\n            --hash->n_elements;\n            he = newhe;\n        }\n        else\n        {\n            prev = he;\n            he = he->next;\n        }\n    }\n}\n\nvoid\nhash_iterator_init_range(struct hash *hash, struct hash_iterator *hi, uint32_t start_bucket,\n                         uint32_t end_bucket)\n{\n    if (end_bucket > hash->n_buckets)\n    {\n        end_bucket = hash->n_buckets;\n    }\n\n    ASSERT(start_bucket <= end_bucket);\n\n    hi->hash = hash;\n    hi->elem = NULL;\n    hi->bucket = NULL;\n    hi->last = NULL;\n    hi->bucket_marked = false;\n    hi->bucket_index_start = start_bucket;\n    hi->bucket_index_end = end_bucket;\n    hi->bucket_index = hi->bucket_index_start - 1;\n}\n\nvoid\nhash_iterator_init(struct hash *hash, struct hash_iterator *hi)\n{\n    hash_iterator_init_range(hash, hi, 0, hash->n_buckets);\n}\n\nstatic inline void\nhash_iterator_lock(struct hash_iterator *hi, struct hash_bucket *b)\n{\n    hi->bucket = b;\n    hi->last = NULL;\n    hi->bucket_marked = false;\n}\n\nstatic inline void\nhash_iterator_unlock(struct hash_iterator *hi)\n{\n    if (hi->bucket)\n    {\n        if (hi->bucket_marked)\n        {\n            hash_remove_marked(hi->hash, hi->bucket);\n            hi->bucket_marked = false;\n        }\n        hi->bucket = NULL;\n        hi->last = NULL;\n    }\n}\n\nstatic inline void\nhash_iterator_advance(struct hash_iterator *hi)\n{\n    hi->last = hi->elem;\n    hi->elem = hi->elem->next;\n}\n\nvoid\nhash_iterator_free(struct hash_iterator *hi)\n{\n    hash_iterator_unlock(hi);\n}\n\nstruct hash_element *\nhash_iterator_next(struct hash_iterator *hi)\n{\n    struct hash_element *ret = NULL;\n    if (hi->elem)\n    {\n        ret = hi->elem;\n        hash_iterator_advance(hi);\n    }\n    else\n    {\n        while (++hi->bucket_index < hi->bucket_index_end)\n        {\n            struct hash_bucket *b;\n            hash_iterator_unlock(hi);\n            b = &hi->hash->buckets[hi->bucket_index];\n            if (b->list)\n            {\n                hash_iterator_lock(hi, b);\n                hi->elem = b->list;\n                if (hi->elem)\n                {\n                    ret = hi->elem;\n                    hash_iterator_advance(hi);\n                    break;\n                }\n            }\n        }\n    }\n    return ret;\n}\n\nvoid\nhash_iterator_delete_element(struct hash_iterator *hi)\n{\n    ASSERT(hi->last);\n    hi->last->key = NULL;\n    hi->bucket_marked = true;\n}\n\n\n/*\n * --------------------------------------------------------------------\n * hash() -- hash a variable-length key into a 32-bit value\n * k     : the key (the unaligned variable-length array of bytes)\n * len   : the length of the key, counting by bytes\n * level : can be any 4-byte value\n * Returns a 32-bit value.  Every bit of the key affects every bit of\n * the return value.  Every 1-bit and 2-bit delta achieves avalanche.\n * About 36+6len instructions.\n *\n * #define hashsize(n) ((uint32_t)1<<(n))\n * #define hashmask(n) (hashsize(n)-1)\n *\n * The best hash table sizes are powers of 2.  There is no need to do\n * mod a prime (mod is sooo slow!).  If you need less than 32 bits,\n * use a bitmask.  For example, if you need only 10 bits, do\n * h = (h & hashmask(10));\n * In which case, the hash table should have hashsize(10) elements.\n *\n * If you are hashing n strings (uint8_t **)k, do it like this:\n * for (i=0, h=0; i<n; ++i) h = hash( k[i], len[i], h);\n *\n * By Bob Jenkins, 1996.  bob_jenkins@burtleburtle.net.  You may use this\n * code any way you wish, private, educational, or commercial.  It's free.\n *\n * See https://burtleburtle.net/bob/hash/evahash.html\n * Use for hash table lookup, or anything where one collision in 2^32 is\n * acceptable.  Do NOT use for cryptographic purposes.\n *\n * --------------------------------------------------------------------\n *\n * mix -- mix 3 32-bit values reversibly.\n * For every delta with one or two bit set, and the deltas of all three\n * high bits or all three low bits, whether the original value of a,b,c\n * is almost all zero or is uniformly distributed,\n * If mix() is run forward or backward, at least 32 bits in a,b,c\n * have at least 1/4 probability of changing.\n * If mix() is run forward, every bit of c will change between 1/3 and\n * 2/3 of the time.  (Well, 22/100 and 78/100 for some 2-bit deltas.)\n * mix() was built out of 36 single-cycle latency instructions in a\n * structure that could supported 2x parallelism, like so:\n *    a -= b;\n *    a -= c; x = (c>>13);\n *    b -= c; a ^= x;\n *    b -= a; x = (a<<8);\n *    c -= a; b ^= x;\n *    c -= b; x = (b>>13);\n *    ...\n * Unfortunately, superscalar Pentiums and Sparcs can't take advantage\n * of that parallelism.  They've also turned some of those single-cycle\n * latency instructions into multi-cycle latency instructions.  Still,\n * this is the fastest good hash I could find.  There were about 2^^68\n * to choose from.  I only looked at a billion or so.\n *\n * James Yonan Notes:\n *\n * This function is faster than it looks, and appears to be\n * appropriate for our usage in OpenVPN which is primarily\n * for hash-table based address lookup (IPv4, IPv6, and Ethernet MAC).\n * NOTE: This function is never used for cryptographic purposes, only\n * to produce evenly-distributed indexes into hash tables.\n *\n * Benchmark results: 11.39 machine cycles per byte on a P2 266Mhz,\n *                   and 12.1 machine cycles per byte on a\n *                   2.2 Ghz P4 when hashing a 6 byte string.\n * --------------------------------------------------------------------\n */\n\n#define mix(a, b, c)    \\\n    {                   \\\n        a -= b;         \\\n        a -= c;         \\\n        a ^= (c >> 13); \\\n        b -= c;         \\\n        b -= a;         \\\n        b ^= (a << 8);  \\\n        c -= a;         \\\n        c -= b;         \\\n        c ^= (b >> 13); \\\n        a -= b;         \\\n        a -= c;         \\\n        a ^= (c >> 12); \\\n        b -= c;         \\\n        b -= a;         \\\n        b ^= (a << 16); \\\n        c -= a;         \\\n        c -= b;         \\\n        c ^= (b >> 5);  \\\n        a -= b;         \\\n        a -= c;         \\\n        a ^= (c >> 3);  \\\n        b -= c;         \\\n        b -= a;         \\\n        b ^= (a << 10); \\\n        c -= a;         \\\n        c -= b;         \\\n        c ^= (b >> 15); \\\n    }\n\nuint32_t\nhash_func(const uint8_t *k, uint32_t length, uint32_t initval)\n{\n    uint32_t a, b, c, len;\n\n    /* Set up the internal state */\n    len = length;\n    a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */\n    c = initval;        /* the previous hash value */\n\n    /*---------------------------------------- handle most of the key */\n    while (len >= 12)\n    {\n        a += (k[0] + ((uint32_t)k[1] << 8) + ((uint32_t)k[2] << 16) + ((uint32_t)k[3] << 24));\n        b += (k[4] + ((uint32_t)k[5] << 8) + ((uint32_t)k[6] << 16) + ((uint32_t)k[7] << 24));\n        c += (k[8] + ((uint32_t)k[9] << 8) + ((uint32_t)k[10] << 16) + ((uint32_t)k[11] << 24));\n        mix(a, b, c);\n        k += 12;\n        len -= 12;\n    }\n\n    /*------------------------------------- handle the last 11 bytes */\n    c += length;\n    switch (len) /* all the case statements fall through */\n    {\n        case 11:\n            c += ((uint32_t)k[10] << 24);\n            /* Intentional [[fallthrough]]; */\n\n        case 10:\n            c += ((uint32_t)k[9] << 16);\n            /* Intentional [[fallthrough]]; */\n\n        case 9:\n            c += ((uint32_t)k[8] << 8);\n        /* Intentional [[fallthrough]]; */\n\n        /* the first byte of c is reserved for the length */\n        case 8:\n            b += ((uint32_t)k[7] << 24);\n            /* Intentional [[fallthrough]]; */\n\n        case 7:\n            b += ((uint32_t)k[6] << 16);\n            /* Intentional [[fallthrough]]; */\n\n        case 6:\n            b += ((uint32_t)k[5] << 8);\n            /* Intentional [[fallthrough]]; */\n\n        case 5:\n            b += k[4];\n            /* Intentional [[fallthrough]]; */\n\n        case 4:\n            a += ((uint32_t)k[3] << 24);\n            /* Intentional [[fallthrough]]; */\n\n        case 3:\n            a += ((uint32_t)k[2] << 16);\n            /* Intentional [[fallthrough]]; */\n\n        case 2:\n            a += ((uint32_t)k[1] << 8);\n            /* Intentional [[fallthrough]]; */\n\n        case 1:\n            a += k[0];\n            /* case 0: nothing left to add */\n    }\n    mix(a, b, c);\n    /*-------------------------------------- report the result */\n    return c;\n}\n"
  },
  {
    "path": "src/openvpn/list.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef LIST_H\n#define LIST_H\n\n/*\n * This code is a fairly straightforward hash\n * table implementation using Bob Jenkins'\n * hash function.\n *\n * Hash tables are used in OpenVPN to keep track of\n * client instances over various key spaces.\n */\n\n\n#include \"basic.h\"\n#include \"buffer.h\"\n\nstruct hash_element\n{\n    void *value;\n    const void *key;\n    uint32_t hash_value;\n    struct hash_element *next;\n};\n\nstruct hash_bucket\n{\n    struct hash_element *list;\n};\n\nstruct hash\n{\n    uint32_t n_buckets;\n    uint32_t n_elements;\n    uint32_t mask;\n    uint32_t iv;\n    uint32_t (*hash_function)(const void *key, uint32_t iv);\n    bool (*compare_function)(const void *key1, const void *key2); /* return true if equal */\n    struct hash_bucket *buckets;\n};\n\nstruct hash *hash_init(const uint32_t n_buckets, const uint32_t iv,\n                       uint32_t (*hash_function)(const void *key, uint32_t iv),\n                       bool (*compare_function)(const void *key1, const void *key2));\n\nvoid hash_free(struct hash *hash);\n\nbool hash_add(struct hash *hash, const void *key, void *value, bool replace);\n\nstruct hash_element *hash_lookup_fast(struct hash *hash, struct hash_bucket *bucket,\n                                      const void *key, uint32_t hv);\n\nbool hash_remove_fast(struct hash *hash, struct hash_bucket *bucket, const void *key, uint32_t hv);\n\nvoid hash_remove_by_value(struct hash *hash, void *value);\n\nstruct hash_iterator\n{\n    struct hash *hash;\n    uint32_t bucket_index;\n    struct hash_bucket *bucket;\n    struct hash_element *elem;\n    struct hash_element *last;\n    bool bucket_marked;\n    uint32_t bucket_index_start;\n    uint32_t bucket_index_end;\n};\n\nvoid hash_iterator_init_range(struct hash *hash, struct hash_iterator *hi, uint32_t start_bucket,\n                              uint32_t end_bucket);\n\nvoid hash_iterator_init(struct hash *hash, struct hash_iterator *iter);\n\nstruct hash_element *hash_iterator_next(struct hash_iterator *hi);\n\nvoid hash_iterator_delete_element(struct hash_iterator *hi);\n\nvoid hash_iterator_free(struct hash_iterator *hi);\n\nuint32_t hash_func(const uint8_t *k, uint32_t length, uint32_t initval);\n\nstatic inline uint32_t\nhash_value(const struct hash *hash, const void *key)\n{\n    return (*hash->hash_function)(key, hash->iv);\n}\n\nstatic inline uint32_t\nhash_n_elements(const struct hash *hash)\n{\n    return hash->n_elements;\n}\n\nstatic inline uint32_t\nhash_n_buckets(const struct hash *hash)\n{\n    return hash->n_buckets;\n}\n\nstatic inline struct hash_bucket *\nhash_bucket(struct hash *hash, uint32_t hv)\n{\n    return &hash->buckets[hv & hash->mask];\n}\n\nstatic inline void *\nhash_lookup(struct hash *hash, const void *key)\n{\n    void *ret = NULL;\n    struct hash_element *he;\n    uint32_t hv = hash_value(hash, key);\n    struct hash_bucket *bucket = &hash->buckets[hv & hash->mask];\n\n    he = hash_lookup_fast(hash, bucket, key, hv);\n    if (he)\n    {\n        ret = he->value;\n    }\n\n    return ret;\n}\n\n/* NOTE: assumes that key is not a duplicate */\nstatic inline void\nhash_add_fast(struct hash *hash, struct hash_bucket *bucket, const void *key, uint32_t hv,\n              void *value)\n{\n    struct hash_element *he;\n\n    ALLOC_OBJ(he, struct hash_element);\n    he->value = value;\n    he->key = key;\n    he->hash_value = hv;\n    he->next = bucket->list;\n    bucket->list = he;\n    ++hash->n_elements;\n}\n\nstatic inline bool\nhash_remove(struct hash *hash, const void *key)\n{\n    uint32_t hv;\n    struct hash_bucket *bucket;\n    bool ret;\n\n    hv = hash_value(hash, key);\n    bucket = &hash->buckets[hv & hash->mask];\n    ret = hash_remove_fast(hash, bucket, key, hv);\n    return ret;\n}\n\n#endif /* LIST */\n"
  },
  {
    "path": "src/openvpn/lladdr.c",
    "content": "/*\n * Support routine for configuring link layer address\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n#include \"error.h\"\n#include \"misc.h\"\n#include \"run_command.h\"\n#include \"lladdr.h\"\n#include \"proto.h\"\n\nint\nset_lladdr(openvpn_net_ctx_t *ctx, const char *ifname, const char *lladdr, const struct env_set *es)\n{\n    int r;\n\n    if (!ifname || !lladdr)\n    {\n        return -1;\n    }\n\n#if defined(TARGET_LINUX)\n    uint8_t addr[OPENVPN_ETH_ALEN];\n\n    sscanf(lladdr, MAC_FMT, MAC_SCAN_ARG(addr));\n    r = (net_addr_ll_set(ctx, ifname, addr) == 0);\n#else /* if defined(TARGET_LINUX) */\n    struct argv argv = argv_new();\n#if defined(TARGET_SOLARIS)\n    argv_printf(&argv, \"%s %s ether %s\", IFCONFIG_PATH, ifname, lladdr);\n#elif defined(TARGET_OPENBSD)\n    argv_printf(&argv, \"%s %s lladdr %s\", IFCONFIG_PATH, ifname, lladdr);\n#elif defined(TARGET_DARWIN)\n    argv_printf(&argv, \"%s %s lladdr %s\", IFCONFIG_PATH, ifname, lladdr);\n#elif defined(TARGET_FREEBSD)\n    argv_printf(&argv, \"%s %s ether %s\", IFCONFIG_PATH, ifname, lladdr);\n#else  /* if defined(TARGET_SOLARIS) */\n    msg(M_WARN,\n        \"Sorry, but I don't know how to configure link layer addresses on this operating system.\");\n    return -1;\n#endif /* if defined(TARGET_SOLARIS) */\n    argv_msg(M_INFO, &argv);\n    r = openvpn_execve_check(&argv, es, M_WARN, \"ERROR: Unable to set link layer address.\");\n    argv_free(&argv);\n#endif /* if defined(TARGET_LINUX) */\n\n    if (r)\n    {\n        msg(M_INFO, \"TUN/TAP link layer address set to %s\", lladdr);\n    }\n\n    return r;\n}\n"
  },
  {
    "path": "src/openvpn/lladdr.h",
    "content": "/*\n * Support routine for configuring link layer address\n */\n\n#include \"misc.h\"\n#include \"networking.h\"\n\nint set_lladdr(openvpn_net_ctx_t *ctx, const char *ifname, const char *lladdr,\n               const struct env_set *es);\n"
  },
  {
    "path": "src/openvpn/lzo.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Data Channel Compression module function definitions.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if defined(ENABLE_LZO)\n\n#include \"comp.h\"\n#include \"error.h\"\n#include \"otime.h\"\n\n#include \"memdbg.h\"\n\n\nstatic void\nlzo_compress_init(struct compress_context *compctx)\n{\n    msg(D_INIT_MEDIUM, \"LZO compression initializing\");\n    ASSERT(!(compctx->flags & COMP_F_SWAP));\n    compctx->wu.lzo.wmem_size = LZO_WORKSPACE;\n\n    int lzo_status = lzo_init();\n    if (lzo_status != LZO_E_OK)\n    {\n        msg(M_FATAL, \"Cannot initialize LZO compression library (lzo_init() returns %d)\",\n            lzo_status);\n    }\n    compctx->wu.lzo.wmem = (lzo_voidp)malloc(compctx->wu.lzo.wmem_size);\n    check_malloc_return(compctx->wu.lzo.wmem);\n}\n\nstatic void\nlzo_compress_uninit(struct compress_context *compctx)\n{\n    free(compctx->wu.lzo.wmem);\n    compctx->wu.lzo.wmem = NULL;\n}\n\nstatic void\nlzo_compress(struct buffer *buf, struct buffer work, struct compress_context *compctx,\n             const struct frame *frame)\n{\n    uint8_t *header = buf_prepend(buf, 1);\n    *header = NO_COMPRESS_BYTE;\n}\n\nstatic void\nlzo_decompress(struct buffer *buf, struct buffer work, struct compress_context *compctx,\n               const struct frame *frame)\n{\n    lzo_uint zlen = frame->buf.payload_size;\n    uint8_t c; /* flag indicating whether or not our peer compressed */\n\n    if (buf->len <= 0)\n    {\n        return;\n    }\n\n    ASSERT(buf_init(&work, frame->buf.headroom));\n\n    c = *BPTR(buf);\n    ASSERT(buf_advance(buf, 1));\n\n    if (c == LZO_COMPRESS_BYTE) /* packet was compressed */\n    {\n        ASSERT(buf_safe(&work, zlen));\n        int err = LZO_DECOMPRESS(BPTR(buf), BLENZ(buf), BPTR(&work), &zlen, compctx->wu.lzo.wmem);\n        if (err != LZO_E_OK)\n        {\n            dmsg(D_COMP_ERRORS, \"LZO decompression error: %d\", err);\n            buf->len = 0;\n            return;\n        }\n\n        ASSERT(buf_safe(&work, zlen));\n        work.len = (int)zlen;\n\n        dmsg(D_COMP, \"LZO decompress %d -> %d\", buf->len, work.len);\n        compctx->pre_decompress += buf->len;\n        compctx->post_decompress += work.len;\n\n        *buf = work;\n    }\n    else if (c == NO_COMPRESS_BYTE) /* packet was not compressed */\n    {\n        /* nothing to do */\n    }\n    else\n    {\n        dmsg(D_COMP_ERRORS, \"Bad LZO decompression header byte: %d\", c);\n        buf->len = 0;\n    }\n}\n\nconst struct compress_alg lzo_alg = { \"lzo\", lzo_compress_init, lzo_compress_uninit, lzo_compress,\n                                      lzo_decompress };\n#endif /* ENABLE_LZO */\n"
  },
  {
    "path": "src/openvpn/lzo.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef OPENVPN_LZO_H\n#define OPENVPN_LZO_H\n\n\n/**\n * @file\n * Data Channel Compression module header file.\n */\n\n\n#if defined(ENABLE_LZO)\n\n/**\n * @addtogroup compression\n * @{\n */\n#if defined(HAVE_LZO_LZO1X_H)\n#include <lzo/lzo1x.h>\n#elif defined(HAVE_LZO1X_H)\n/* The lzo.h magic gets confused and still wants\n * to include lzo/lzoconf.h even if our include paths\n * are setup to include the paths without lzo/\n */\n#include <lzodefs.h>\n#include <lzoconf.h>\n#include <lzo1x.h>\n#endif\n\n#include \"buffer.h\"\n#include \"mtu.h\"\n#include \"common.h\"\n#include \"status.h\"\n\nextern const struct compress_alg lzo_alg;\n\n/**************************************************************************/\n/** @name LZO library interface defines */ /** @{ */ /***********************/\n#define LZO_COMPRESS   lzo1x_1_15_compress\n/**< LZO library compression function.\n *\n *   Use \\c lzo1x_1_15_compress because it\n *   is described as faster than the\n *   standard routine, although it does\n *   need a bit more memory. */\n#define LZO_WORKSPACE  LZO1X_1_15_MEM_COMPRESS\n/**< The size in bytes of the memory\n *   %buffer required by the LZO library\n *   compression algorithm. */\n#define LZO_DECOMPRESS lzo1x_decompress_safe\n/**< LZO library decompression function.\n *\n *   Use safe decompress because it\n *   includes checks for possible %buffer\n *   overflows. If speed is essential and\n *   you will always be using a MAC to\n *   verify the integrity of incoming\n *   packets, you might want to consider\n *   using the non-safe version. */\n/** @} name LZO library interface */ /**************************************/\n\n\n/**************************************************************************/\n/** @name Adaptive compression defines */ /** @{ */ /************************/\n#define AC_SAMP_SEC 2                               /**< Number of seconds in a sample period. */\n#define AC_MIN_BYTES                                                                      \\\n    1000                                            /**< Minimum number of bytes a sample \\\n                                                     *   period must contain for it to be \\\n                                                     *   evaluated. */\n#define AC_SAVE_PCT                                                                        \\\n    5                                               /**< Minimum size reduction percentage \\\n                                                     *   below which compression will be   \\\n                                                     *   turned off. */\n#define AC_OFF_SEC                                                                             \\\n    60                                              /**< Seconds to wait after compression has \\\n                                                     *   been turned off before retesting. */\n/** @} name Adaptive compression defines */         /*******************************/\n\n/**\n * Adaptive compression state.\n */\nstruct lzo_adaptive_compress\n{\n    bool compress_state;\n    time_t next;\n    int n_total;\n    int n_comp;\n};\n\n\n/**\n * State for the compression and decompression routines.\n *\n * This structure contains compression module state, such as whether\n * compression is enabled and the status of the adaptive compression\n * routines.  It also contains an allocated working buffer.\n *\n * One of these compression workspace structures is maintained for each\n * VPN tunnel.\n */\nstruct lzo_compress_workspace\n{\n    lzo_voidp wmem;\n    int wmem_size;\n    struct lzo_adaptive_compress ac;\n};\n\n/** @} addtogroup compression */\n\n\n#endif /* ENABLE_LZO && USE_COMP */\n#endif /* ifndef OPENVPN_LZO_H */\n"
  },
  {
    "path": "src/openvpn/manage.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#ifdef ENABLE_MANAGEMENT\n\n#include \"error.h\"\n#include \"fdmisc.h\"\n#include \"options.h\"\n#include \"sig.h\"\n#include \"event.h\"\n#include \"otime.h\"\n#include \"integer.h\"\n#include \"misc.h\"\n#include \"ssl.h\"\n#include \"common.h\"\n#include \"manage.h\"\n#include \"openvpn.h\"\n#include \"dco.h\"\n#include \"push.h\"\n#include \"multi.h\"\n\n#include \"memdbg.h\"\n\n#ifdef ENABLE_PKCS11\n#include \"pkcs11.h\"\n#endif\n\n#define MANAGEMENT_ECHO_PULL_INFO 0\n\n#if MANAGEMENT_ECHO_PULL_INFO\n#define MANAGEMENT_ECHO_FLAGS LOG_PRINT_INTVAL\n#else\n#define MANAGEMENT_ECHO_FLAGS 0\n#endif\n\n/* tag for blank username/password */\nstatic const char blank_up[] = \"[[BLANK]]\";\n\n/*\n * Management client versions indicating feature support in client.\n * Append new values as needed but do not change exisiting ones.\n */\nenum mcv\n{\n    MCV_DEFAULT = 1,\n    MCV_PKSIGN = 2,\n    MCV_PKSIGN_ALG = 3,\n};\n\nstruct management *management; /* GLOBAL */\n\n/* static forward declarations */\nstatic void man_output_standalone(struct management *man, volatile int *signal_received);\n\nstatic void man_reset_client_socket(struct management *man, const bool exiting);\n\nstatic void\nman_help(void)\n{\n    msg(M_CLIENT, \"Management Interface for %s\", title_string);\n    msg(M_CLIENT, \"Commands:\");\n    msg(M_CLIENT, \"auth-retry t           : Auth failure retry mode (none,interact,nointeract).\");\n    msg(M_CLIENT, \"bytecount n            : Show bytes in/out, update every n secs (0=off).\");\n    msg(M_CLIENT, \"echo [on|off] [N|all]  : Like log, but only show messages in echo buffer.\");\n    msg(M_CLIENT,\n        \"cr-response response   : Send a challenge response answer via CR_RESPONSE to server\");\n    msg(M_CLIENT, \"exit|quit              : Close management session.\");\n    msg(M_CLIENT, \"forget-passwords       : Forget passwords entered so far.\");\n    msg(M_CLIENT, \"help                   : Print this message.\");\n    msg(M_CLIENT, \"hold [on|off|release]  : Set/show hold flag to on/off state, or\");\n    msg(M_CLIENT, \"                         release current hold and start tunnel.\");\n    msg(M_CLIENT, \"kill cn                : Kill the client instance(s) having common name cn.\");\n    msg(M_CLIENT, \"kill IP:port           : Kill the client instance connecting from IP:port.\");\n    msg(M_CLIENT, \"load-stats             : Show global server load stats.\");\n    msg(M_CLIENT, \"log [on|off] [N|all]   : Turn on/off realtime log display\");\n    msg(M_CLIENT, \"                         + show last N lines or 'all' for entire history.\");\n    msg(M_CLIENT,\n        \"mute [n]               : Set log mute level to n, or show level if n is absent.\");\n    msg(M_CLIENT, \"needok type action     : Enter confirmation for NEED-OK request of 'type',\");\n    msg(M_CLIENT, \"                         where action = 'ok' or 'cancel'.\");\n    msg(M_CLIENT, \"needstr type action    : Enter confirmation for NEED-STR request of 'type',\");\n    msg(M_CLIENT, \"                         where action is reply string.\");\n    msg(M_CLIENT, \"net                    : (Windows only) Show network info and routing table.\");\n    msg(M_CLIENT, \"password type p        : Enter password p for a queried OpenVPN password.\");\n    msg(M_CLIENT, \"remote type [host port] : Override remote directive, type=ACCEPT|MOD|SKIP.\");\n    msg(M_CLIENT, \"remote-entry-count     : Get number of available remote entries.\");\n    msg(M_CLIENT, \"remote-entry-get  i|all [j]: Get remote entry at index = i to to j-1 or all.\");\n    msg(M_CLIENT, \"proxy type [host port flags] : Enter dynamic proxy server info.\");\n    msg(M_CLIENT, \"pid                    : Show process ID of the current OpenVPN process.\");\n#ifdef ENABLE_PKCS11\n    msg(M_CLIENT, \"pkcs11-id-count        : Get number of available PKCS#11 identities.\");\n    msg(M_CLIENT, \"pkcs11-id-get index    : Get PKCS#11 identity at index.\");\n#endif\n    msg(M_CLIENT, \"client-auth CID KID    : Authenticate client-id/key-id CID/KID (MULTILINE)\");\n    msg(M_CLIENT, \"client-auth-nt CID KID : Authenticate client-id/key-id CID/KID\");\n    msg(M_CLIENT,\n        \"client-deny CID KID R [CR] : Deny auth client-id/key-id CID/KID with log reason\");\n    msg(M_CLIENT, \"                             text R and optional client reason text CR\");\n    msg(M_CLIENT,\n        \"client-pending-auth CID KID MSG timeout : Instruct OpenVPN to send AUTH_PENDING and INFO_PRE msg\");\n    msg(M_CLIENT,\n        \"                                      to the client and wait for a final client-auth/client-deny\");\n    msg(M_CLIENT, \"client-kill CID [M]    : Kill client instance CID with message M (def=RESTART)\");\n    msg(M_CLIENT, \"env-filter [level]     : Set env-var filter level\");\n    msg(M_CLIENT, \"rsa-sig                : Enter a signature in response to >RSA_SIGN challenge\");\n    msg(M_CLIENT,\n        \"                         Enter signature base64 on subsequent lines followed by END\");\n    msg(M_CLIENT, \"pk-sig                 : Enter a signature in response to >PK_SIGN challenge\");\n    msg(M_CLIENT,\n        \"                         Enter signature base64 on subsequent lines followed by END\");\n    msg(M_CLIENT,\n        \"certificate            : Enter a client certificate in response to >NEED-CERT challenge\");\n    msg(M_CLIENT,\n        \"                         Enter certificate base64 on subsequent lines followed by END\");\n    msg(M_CLIENT, \"signal s               : Send signal s to daemon,\");\n    msg(M_CLIENT, \"                         s = SIGHUP|SIGTERM|SIGUSR1|SIGUSR2.\");\n    msg(M_CLIENT, \"state [on|off] [N|all] : Like log, but show state history.\");\n    msg(M_CLIENT, \"status [n]             : Show current daemon status info using format #n.\");\n    msg(M_CLIENT, \"test n                 : Produce n lines of output for testing/debugging.\");\n    msg(M_CLIENT, \"username type u        : Enter username u for a queried OpenVPN username.\");\n    msg(M_CLIENT, \"verb [n]               : Set log verbosity level to n, or show if n is absent.\");\n    msg(M_CLIENT, \"version [n]            : Set client's version to n or show current version of daemon.\");\n    msg(M_CLIENT, \"push-update-broad options : Broadcast a message to update the specified options.\");\n    msg(M_CLIENT, \"                            Ex. push-update-broad \\\"route something, -dns\\\"\");\n    msg(M_CLIENT, \"push-update-cid CID options : Send an update message to the client identified by CID.\");\n    msg(M_CLIENT, \"END\");\n}\n\nstatic const char *\nman_state_name(const int state)\n{\n    switch (state)\n    {\n        case OPENVPN_STATE_INITIAL:\n            return \"INITIAL\";\n\n        case OPENVPN_STATE_CONNECTING:\n            return \"CONNECTING\";\n\n        case OPENVPN_STATE_WAIT:\n            return \"WAIT\";\n\n        case OPENVPN_STATE_AUTH:\n            return \"AUTH\";\n\n        case OPENVPN_STATE_GET_CONFIG:\n            return \"GET_CONFIG\";\n\n        case OPENVPN_STATE_ASSIGN_IP:\n            return \"ASSIGN_IP\";\n\n        case OPENVPN_STATE_ADD_ROUTES:\n            return \"ADD_ROUTES\";\n\n        case OPENVPN_STATE_CONNECTED:\n            return \"CONNECTED\";\n\n        case OPENVPN_STATE_RECONNECTING:\n            return \"RECONNECTING\";\n\n        case OPENVPN_STATE_EXITING:\n            return \"EXITING\";\n\n        case OPENVPN_STATE_RESOLVE:\n            return \"RESOLVE\";\n\n        case OPENVPN_STATE_TCP_CONNECT:\n            return \"TCP_CONNECT\";\n\n        case OPENVPN_STATE_AUTH_PENDING:\n            return \"AUTH_PENDING\";\n\n        default:\n            return \"?\";\n    }\n}\n\nstatic void\nman_welcome(struct management *man)\n{\n    msg(M_CLIENT, \">INFO:OpenVPN Management Interface Version %d -- type 'help' for more info\",\n        MANAGEMENT_VERSION);\n    if (man->persist.special_state_msg)\n    {\n        msg(M_CLIENT, \"%s\", man->persist.special_state_msg);\n    }\n}\n\nstatic inline bool\nman_password_needed(struct management *man)\n{\n    return man->settings.up.defined && !man->connection.password_verified;\n}\n\nstatic void\nman_check_password(struct management *man, const char *line)\n{\n    if (man_password_needed(man))\n    {\n        /* This comparison is not fixed time but since strlen(time) is based on\n         * the attacker choice, it should not give any indication of the real\n         * password length, use + 1 to include the NUL byte that terminates the\n         * string*/\n        size_t compare_len =\n            min_size(strlen(line) + 1, sizeof(man->settings.up.password));\n        if (memcmp_constant_time(line, man->settings.up.password, compare_len) == 0)\n        {\n            man->connection.password_verified = true;\n            msg(M_CLIENT, \"SUCCESS: password is correct\");\n            man_welcome(man);\n        }\n        else\n        {\n            man->connection.password_verified = false;\n            msg(M_CLIENT, \"ERROR: bad password\");\n            if (++man->connection.password_tries >= MANAGEMENT_N_PASSWORD_RETRIES)\n            {\n                msg(M_WARN, \"MAN: client connection rejected after %d failed password attempts\",\n                    MANAGEMENT_N_PASSWORD_RETRIES);\n                man->connection.halt = true;\n            }\n        }\n    }\n}\n\nstatic void\nman_update_io_state(struct management *man)\n{\n    if (socket_defined(man->connection.sd_cli))\n    {\n        if (buffer_list_defined(man->connection.out))\n        {\n            man->connection.state = MS_CC_WAIT_WRITE;\n        }\n        else\n        {\n            man->connection.state = MS_CC_WAIT_READ;\n        }\n    }\n}\n\nstatic void\nman_output_list_push_finalize(struct management *man)\n{\n    if (management_connected(man))\n    {\n        man_update_io_state(man);\n        if (!man->persist.standalone_disabled)\n        {\n            volatile int signal_received = 0;\n            man_output_standalone(man, &signal_received);\n        }\n    }\n}\n\nstatic void\nman_output_list_push_str(struct management *man, const char *str)\n{\n    if (management_connected(man) && str)\n    {\n        buffer_list_push(man->connection.out, str);\n    }\n}\n\nstatic void\nman_output_list_push(struct management *man, const char *str)\n{\n    man_output_list_push_str(man, str);\n    man_output_list_push_finalize(man);\n}\n\nstatic void\nman_prompt(struct management *man)\n{\n    if (man_password_needed(man))\n    {\n        man_output_list_push(man, \"ENTER PASSWORD:\");\n    }\n#if 0 /* should we use prompt? */\n    else\n    {\n        man_output_list_push(man, \">\");\n    }\n#endif\n}\n\n\n/**\n * Small function to report the success or failure of a command to\n * the management interface\n */\nstatic void\nreport_command_status(const bool status, const char *command)\n{\n    if (status)\n    {\n        msg(M_CLIENT, \"SUCCESS: %s command succeeded\", command);\n    }\n    else\n    {\n        msg(M_CLIENT, \"ERROR: %s command failed\", command);\n    }\n}\n\nstatic void\nman_delete_unix_socket(struct management *man)\n{\n#if UNIX_SOCK_SUPPORT\n    if ((man->settings.flags & (MF_UNIX_SOCK | MF_CONNECT_AS_CLIENT)) == MF_UNIX_SOCK)\n    {\n        socket_delete_unix(&man->settings.local_unix);\n    }\n#endif\n}\n\nstatic void\nman_close_socket(struct management *man, const socket_descriptor_t sd)\n{\n#ifndef _WIN32\n    /*\n     * Windows doesn't need this because the ne32 event is permanently\n     * enabled at struct management scope.\n     */\n    if (man->persist.callback.delete_event)\n    {\n        (*man->persist.callback.delete_event)(man->persist.callback.arg, sd);\n    }\n#endif\n    openvpn_close_socket(sd);\n}\n\nstatic void\nvirtual_output_callback_func(void *arg, const unsigned int flags, const char *str)\n{\n    struct management *man = (struct management *)arg;\n    static int recursive_level = 0; /* GLOBAL */\n\n#define AF_DID_PUSH  (1 << 0)\n#define AF_DID_RESET (1 << 1)\n    if (recursive_level < 5) /* limit recursion */\n    {\n        struct gc_arena gc = gc_new();\n        struct log_entry e;\n        const char *out = NULL;\n        unsigned int action_flags = 0;\n\n        ++recursive_level;\n\n        CLEAR(e);\n        update_time();\n        e.timestamp = now;\n        e.u.msg_flags = flags;\n        e.string = str;\n\n        if (flags & M_FATAL)\n        {\n            man->persist.standalone_disabled = false;\n        }\n\n        if (flags != M_CLIENT)\n        {\n            log_history_add(man->persist.log, &e);\n        }\n\n        if (!man_password_needed(man))\n        {\n            if (flags == M_CLIENT)\n            {\n                out = log_entry_print(&e, LOG_PRINT_CRLF, &gc);\n            }\n            else if (man->connection.log_realtime)\n            {\n                out = log_entry_print(&e,\n                                      LOG_PRINT_INT_DATE | LOG_PRINT_MSG_FLAGS\n                                          | LOG_PRINT_LOG_PREFIX | LOG_PRINT_CRLF,\n                                      &gc);\n            }\n            if (out)\n            {\n                man_output_list_push_str(man, out);\n                action_flags |= AF_DID_PUSH;\n            }\n            if (flags & M_FATAL)\n            {\n                out = log_entry_print(&e, LOG_FATAL_NOTIFY | LOG_PRINT_CRLF, &gc);\n                if (out)\n                {\n                    man_output_list_push_str(man, out);\n                    action_flags |= (AF_DID_PUSH | AF_DID_RESET);\n                }\n            }\n        }\n\n        gc_free(&gc);\n\n        if (action_flags & AF_DID_PUSH)\n        {\n            man_output_list_push_finalize(man);\n        }\n        if (action_flags & AF_DID_RESET)\n        {\n            man_reset_client_socket(man, true);\n        }\n\n        --recursive_level;\n    }\n    else\n    {\n        /* cannot use msg here */\n        printf(\"virtual_output: message to management interface \"\n               \"dropped due to recursion: <%s>\\n\",\n               str);\n    }\n}\n\n/*\n * Given a signal, return the signal with possible remapping applied,\n * or -1 if the signal should be ignored.\n */\nstatic int\nman_mod_signal(const struct management *man, const int signum)\n{\n    const unsigned int flags = man->settings.mansig;\n    int s = signum;\n    if (s == SIGUSR1)\n    {\n        if (flags & MANSIG_MAP_USR1_TO_HUP)\n        {\n            s = SIGHUP;\n        }\n        if (flags & MANSIG_MAP_USR1_TO_TERM)\n        {\n            s = SIGTERM;\n        }\n    }\n    if (flags & MANSIG_IGNORE_USR1_HUP)\n    {\n        if (s == SIGHUP || s == SIGUSR1)\n        {\n            s = -1;\n        }\n    }\n    return s;\n}\n\nstatic void\nman_signal(struct management *man, const char *name)\n{\n    const int sig = parse_signal(name);\n    if (sig >= 0)\n    {\n        const int sig_mod = man_mod_signal(man, sig);\n        if (sig_mod >= 0)\n        {\n            throw_signal(sig_mod);\n            msg(M_CLIENT, \"SUCCESS: signal %s thrown\", signal_name(sig_mod, true));\n        }\n        else\n        {\n            msg(M_CLIENT, \"ERROR: signal '%s' is currently ignored\", name);\n            if (man->persist.special_state_msg)\n            {\n                msg(M_CLIENT, \"%s\", man->persist.special_state_msg);\n            }\n        }\n    }\n    else\n    {\n        msg(M_CLIENT, \"ERROR: signal '%s' is not a known signal type\", name);\n    }\n}\n\nstatic void\nman_command_unsupported(const char *command_name)\n{\n    msg(M_CLIENT, \"ERROR: The '%s' command is not supported by the current daemon mode\",\n        command_name);\n}\n\nstatic void\nman_status(struct management *man, const int version, struct status_output *so)\n{\n    if (man->persist.callback.status)\n    {\n        (*man->persist.callback.status)(man->persist.callback.arg, version, so);\n    }\n    else\n    {\n        man_command_unsupported(\"status\");\n    }\n}\n\nstatic void\nman_bytecount_stop(struct management *man)\n{\n    man->connection.bytecount_update_seconds = 0;\n    event_timeout_clear(&man->connection.bytecount_update_interval);\n}\n\nstatic void\nman_bytecount(struct management *man, const int update_seconds)\n{\n    if (update_seconds > 0)\n    {\n        man->connection.bytecount_update_seconds = update_seconds;\n        event_timeout_init(&man->connection.bytecount_update_interval,\n                           man->connection.bytecount_update_seconds, now);\n    }\n    else\n    {\n        man_bytecount_stop(man);\n    }\n\n    /* The newly received bytecount interval may be sooner than the existing\n     * coarse timer wakeup. Reset the timer to ensure it fires at the correct,\n     * earlier time.\n     */\n    if (man->persist.callback.arg)\n    {\n        struct context *c;\n\n        if (man->settings.flags & MF_SERVER)\n        {\n            struct multi_context *m = man->persist.callback.arg;\n            c = &m->top;\n        }\n        else\n        {\n            c = man->persist.callback.arg;\n        }\n\n        reset_coarse_timers(c);\n    }\n\n    msg(M_CLIENT, \"SUCCESS: bytecount interval changed\");\n}\n\nstatic void\nman_bytecount_output_client(counter_type bytes_in_total, counter_type bytes_out_total)\n{\n    char in[32];\n    char out[32];\n\n    /* do in a roundabout way to work around possible mingw or mingw-glibc bug */\n    snprintf(in, sizeof(in), counter_format, bytes_in_total);\n    snprintf(out, sizeof(out), counter_format, bytes_out_total);\n    msg(M_CLIENT, \">BYTECOUNT:%s,%s\", in, out);\n}\n\nstatic void\nman_bytecount_output_server(const counter_type bytes_in_total, const counter_type bytes_out_total,\n                            struct man_def_auth_context *mdac)\n{\n    char in[32];\n    char out[32];\n    /* do in a roundabout way to work around possible mingw or mingw-glibc bug */\n    snprintf(in, sizeof(in), counter_format, bytes_in_total);\n    snprintf(out, sizeof(out), counter_format, bytes_out_total);\n    msg(M_CLIENT, \">BYTECOUNT_CLI:%lu,%s,%s\", mdac->cid, in, out);\n}\n\nstatic void\nman_kill(struct management *man, const char *victim)\n{\n    struct gc_arena gc = gc_new();\n\n    if (man->persist.callback.kill_by_cn && man->persist.callback.kill_by_addr)\n    {\n        struct buffer buf;\n        char p1[128];\n        char p2[128];\n        char p3[128];\n        int n_killed;\n\n        buf_set_read(&buf, (uint8_t *)victim, strlen(victim) + 1);\n        buf_parse(&buf, ':', p1, sizeof(p1));\n        buf_parse(&buf, ':', p2, sizeof(p2));\n        buf_parse(&buf, ':', p3, sizeof(p3));\n\n        if (strlen(p1) && strlen(p2) && strlen(p3))\n        {\n            /* IP:port specified */\n            bool status;\n            const in_addr_t addr =\n                getaddr(GETADDR_HOST_ORDER | GETADDR_MSG_VIRT_OUT, p2, 0, &status, NULL);\n            if (status)\n            {\n                const int port = atoi(p3);\n                const uint8_t proto = (streq(p1, \"tcp\"))   ? PROTO_TCP_SERVER\n                                      : (streq(p1, \"udp\")) ? PROTO_UDP\n                                                           : PROTO_NONE;\n\n                if ((port > 0 && port <= UINT16_MAX) && (proto != PROTO_NONE))\n                {\n                    n_killed = (*man->persist.callback.kill_by_addr)(man->persist.callback.arg,\n                                                                     addr, (uint16_t)port, proto);\n                    if (n_killed > 0)\n                    {\n                        msg(M_CLIENT, \"SUCCESS: %d client(s) at address %s:%s:%d killed\", n_killed,\n                            proto2ascii(proto, AF_UNSPEC, false), print_in_addr_t(addr, 0, &gc),\n                            port);\n                    }\n                    else\n                    {\n                        msg(M_CLIENT, \"ERROR: client at address %s:%s:%d not found\",\n                            proto2ascii(proto, AF_UNSPEC, false), print_in_addr_t(addr, 0, &gc),\n                            port);\n                    }\n                }\n                else\n                {\n                    msg(M_CLIENT, \"ERROR: port number or protocol out of range: %s %s\", p3, p1);\n                }\n            }\n            else\n            {\n                msg(M_CLIENT, \"ERROR: error parsing IP address: %s\", p2);\n            }\n        }\n        else if (strlen(p1))\n        {\n            /* common name specified */\n            n_killed = (*man->persist.callback.kill_by_cn)(man->persist.callback.arg, p1);\n            if (n_killed > 0)\n            {\n                msg(M_CLIENT, \"SUCCESS: common name '%s' found, %d client(s) killed\", p1, n_killed);\n            }\n            else\n            {\n                msg(M_CLIENT, \"ERROR: common name '%s' not found\", p1);\n            }\n        }\n        else\n        {\n            msg(M_CLIENT, \"ERROR: kill parse\");\n        }\n    }\n    else\n    {\n        man_command_unsupported(\"kill\");\n    }\n\n    gc_free(&gc);\n}\n\n/*\n * General-purpose history command handler\n * for the log and echo commands.\n */\nstatic void\nman_history(struct management *man, const char *parm, const char *type, struct log_history *log,\n            bool *realtime, const unsigned int lep_flags)\n{\n    struct gc_arena gc = gc_new();\n    int n = 0;\n\n    if (streq(parm, \"on\"))\n    {\n        *realtime = true;\n        msg(M_CLIENT, \"SUCCESS: real-time %s notification set to ON\", type);\n    }\n    else if (streq(parm, \"off\"))\n    {\n        *realtime = false;\n        msg(M_CLIENT, \"SUCCESS: real-time %s notification set to OFF\", type);\n    }\n    else if (streq(parm, \"all\") || (n = atoi(parm)) > 0)\n    {\n        const int size = log_history_size(log);\n        const int start = (n ? n : size) - 1;\n        int i;\n\n        for (i = start; i >= 0; --i)\n        {\n            const struct log_entry *e = log_history_ref(log, i);\n            if (e)\n            {\n                const char *out = log_entry_print(e, lep_flags, &gc);\n                virtual_output_callback_func(man, M_CLIENT, out);\n            }\n        }\n        msg(M_CLIENT, \"END\");\n    }\n    else\n    {\n        msg(M_CLIENT, \"ERROR: %s parameter must be 'on' or 'off' or some number n or 'all'\", type);\n    }\n\n    gc_free(&gc);\n}\n\nstatic void\nman_log(struct management *man, const char *parm)\n{\n    man_history(man, parm, \"log\", man->persist.log, &man->connection.log_realtime,\n                LOG_PRINT_INT_DATE | LOG_PRINT_MSG_FLAGS);\n}\n\nstatic void\nman_echo(struct management *man, const char *parm)\n{\n    man_history(man, parm, \"echo\", man->persist.echo, &man->connection.echo_realtime,\n                LOG_PRINT_INT_DATE | MANAGEMENT_ECHO_FLAGS);\n}\n\nstatic void\nman_state(struct management *man, const char *parm)\n{\n    man_history(man, parm, \"state\", man->persist.state, &man->connection.state_realtime,\n                LOG_PRINT_INT_DATE | LOG_PRINT_STATE | LOG_PRINT_LOCAL_IP | LOG_PRINT_REMOTE_IP);\n}\n\nstatic void\nman_up_finalize(struct management *man)\n{\n    switch (man->connection.up_query_mode)\n    {\n        case UP_QUERY_USER_PASS:\n            if (!strlen(man->connection.up_query.username))\n            {\n                break;\n            }\n\n        /* fall through */\n        case UP_QUERY_PASS:\n        case UP_QUERY_NEED_OK:\n        case UP_QUERY_NEED_STR:\n            if (strlen(man->connection.up_query.password))\n            {\n                man->connection.up_query.defined = true;\n            }\n            break;\n\n        case UP_QUERY_DISABLED:\n            man->connection.up_query.defined = false;\n            break;\n\n        default:\n            ASSERT(0);\n    }\n}\n\nstatic void\nman_query_user_pass(struct management *man, const char *type, const char *string, const bool needed,\n                    const char *prompt, char *dest, int len)\n{\n    if (needed)\n    {\n        ASSERT(man->connection.up_query_type);\n        if (streq(man->connection.up_query_type, type))\n        {\n            strncpynt(dest, string, len);\n            man_up_finalize(man);\n            msg(M_CLIENT, \"SUCCESS: '%s' %s entered, but not yet verified\", type, prompt);\n        }\n        else\n        {\n            msg(M_CLIENT, \"ERROR: %s of type '%s' entered, but we need one of type '%s'\", prompt,\n                type, man->connection.up_query_type);\n        }\n    }\n    else\n    {\n        msg(M_CLIENT, \"ERROR: no %s is currently needed at this time\", prompt);\n    }\n}\n\nstatic void\nman_query_username(struct management *man, const char *type, const char *string)\n{\n    const bool needed =\n        ((man->connection.up_query_mode == UP_QUERY_USER_PASS) && man->connection.up_query_type);\n    man_query_user_pass(man, type, string, needed, \"username\", man->connection.up_query.username,\n                        USER_PASS_LEN);\n}\n\nstatic void\nman_query_password(struct management *man, const char *type, const char *string)\n{\n    const bool needed = ((man->connection.up_query_mode == UP_QUERY_PASS\n                          || man->connection.up_query_mode == UP_QUERY_USER_PASS)\n                         && man->connection.up_query_type);\n    if (!string[0]) /* allow blank passwords to be passed through using the blank_up tag */\n    {\n        string = blank_up;\n    }\n    man_query_user_pass(man, type, string, needed, \"password\", man->connection.up_query.password,\n                        USER_PASS_LEN);\n}\n\nstatic void\nman_query_need_ok(struct management *man, const char *type, const char *action)\n{\n    const bool needed =\n        ((man->connection.up_query_mode == UP_QUERY_NEED_OK) && man->connection.up_query_type);\n    man_query_user_pass(man, type, action, needed, \"needok-confirmation\",\n                        man->connection.up_query.password, USER_PASS_LEN);\n}\n\nstatic void\nman_query_need_str(struct management *man, const char *type, const char *action)\n{\n    const bool needed =\n        ((man->connection.up_query_mode == UP_QUERY_NEED_STR) && man->connection.up_query_type);\n    man_query_user_pass(man, type, action, needed, \"needstr-string\",\n                        man->connection.up_query.password, USER_PASS_LEN);\n}\n\nstatic void\nman_forget_passwords(struct management *man)\n{\n    ssl_purge_auth(false);\n    (void)ssl_clean_auth_token();\n    msg(M_CLIENT, \"SUCCESS: Passwords were forgotten\");\n}\n\nstatic void\nman_net(struct management *man)\n{\n    if (man->persist.callback.show_net)\n    {\n        (*man->persist.callback.show_net)(man->persist.callback.arg, M_CLIENT);\n    }\n    else\n    {\n        man_command_unsupported(\"net\");\n    }\n}\n\nstatic void\nman_send_cc_message(struct management *man, const char *message, const char *parameters)\n{\n    if (man->persist.callback.send_cc_message)\n    {\n        const bool status = (*man->persist.callback.send_cc_message)(man->persist.callback.arg,\n                                                                     message, parameters);\n        if (status)\n        {\n            msg(M_CLIENT, \"SUCCESS: command succeeded\");\n        }\n        else\n        {\n            msg(M_CLIENT, \"ERROR: command failed\");\n        }\n    }\n    else\n    {\n        man_command_unsupported(\"cr-repsonse\");\n    }\n}\n#ifdef ENABLE_PKCS11\n\nstatic void\nman_pkcs11_id_count(struct management *man)\n{\n    msg(M_CLIENT, \">PKCS11ID-COUNT:%d\", pkcs11_management_id_count());\n}\n\nstatic void\nman_pkcs11_id_get(struct management *man, const int index)\n{\n    char *id = NULL;\n    char *base64 = NULL;\n\n    if (pkcs11_management_id_get(index, &id, &base64))\n    {\n        msg(M_CLIENT, \">PKCS11ID-ENTRY:'%d', ID:'%s', BLOB:'%s'\", index, id, base64);\n    }\n    else\n    {\n        msg(M_CLIENT, \">PKCS11ID-ENTRY:'%d'\", index);\n    }\n\n    free(id);\n    free(base64);\n}\n\n#endif /* ifdef ENABLE_PKCS11 */\n\nstatic void\nman_remote_entry_count(struct management *man)\n{\n    unsigned count = 0;\n    if (man->persist.callback.remote_entry_count)\n    {\n        count = (*man->persist.callback.remote_entry_count)(man->persist.callback.arg);\n        msg(M_CLIENT, \"%u\", count);\n        msg(M_CLIENT, \"END\");\n    }\n    else\n    {\n        man_command_unsupported(\"remote-entry-count\");\n    }\n}\n\nstatic void\nman_remote_entry_get(struct management *man, const char *p1, const char *p2)\n{\n    ASSERT(p1);\n\n    if (man->persist.callback.remote_entry_get && man->persist.callback.remote_entry_count)\n    {\n        unsigned int count = (*man->persist.callback.remote_entry_count)(man->persist.callback.arg);\n\n        unsigned int from = (unsigned int)atoi(p1);\n        unsigned int to = p2 ? (unsigned int)atoi(p2) : from + 1;\n\n        if (!strcmp(p1, \"all\"))\n        {\n            from = 0;\n            to = count;\n        }\n\n        for (unsigned int i = from; i < min_uint(to, count); i++)\n        {\n            char *remote = NULL;\n            bool res =\n                (*man->persist.callback.remote_entry_get)(man->persist.callback.arg, i, &remote);\n            if (res && remote)\n            {\n                msg(M_CLIENT, \"%u,%s\", i, remote);\n            }\n            free(remote);\n        }\n        msg(M_CLIENT, \"END\");\n    }\n    else\n    {\n        man_command_unsupported(\"remote-entry-get\");\n    }\n}\n\nstatic void\nman_hold(struct management *man, const char *cmd)\n{\n    if (cmd)\n    {\n        if (streq(cmd, \"on\"))\n        {\n            man->settings.flags |= MF_HOLD;\n            msg(M_CLIENT, \"SUCCESS: hold flag set to ON\");\n        }\n        else if (streq(cmd, \"off\"))\n        {\n            man->settings.flags &= ~MF_HOLD;\n            msg(M_CLIENT, \"SUCCESS: hold flag set to OFF\");\n        }\n        else if (streq(cmd, \"release\"))\n        {\n            man->persist.hold_release = true;\n            msg(M_CLIENT, \"SUCCESS: hold release succeeded\");\n        }\n        else\n        {\n            msg(M_CLIENT, \"ERROR: bad hold command parameter\");\n        }\n    }\n    else\n    {\n        msg(M_CLIENT, \"SUCCESS: hold=%d\", BOOL_CAST(man->settings.flags & MF_HOLD));\n    }\n}\n\n#define IER_RESET 0\n#define IER_NEW   1\n\nstatic void\nin_extra_reset(struct man_connection *mc, const int mode)\n{\n    if (mc)\n    {\n        if (mode != IER_NEW)\n        {\n            mc->in_extra_cmd = IEC_UNDEF;\n            mc->in_extra_cid = 0;\n            mc->in_extra_kid = 0;\n        }\n        if (mc->in_extra)\n        {\n            buffer_list_free(mc->in_extra);\n            mc->in_extra = NULL;\n        }\n        if (mode == IER_NEW)\n        {\n            mc->in_extra = buffer_list_new();\n        }\n    }\n}\n\nstatic void\nin_extra_dispatch(struct management *man)\n{\n    switch (man->connection.in_extra_cmd)\n    {\n        case IEC_CLIENT_AUTH:\n            if (man->persist.callback.client_auth)\n            {\n                const bool status = (*man->persist.callback.client_auth)(\n                    man->persist.callback.arg, man->connection.in_extra_cid,\n                    man->connection.in_extra_kid, true, NULL, NULL, man->connection.in_extra);\n                man->connection.in_extra = NULL;\n                report_command_status(status, \"client-auth\");\n            }\n            else\n            {\n                man_command_unsupported(\"client-auth\");\n            }\n            break;\n\n        case IEC_PK_SIGN:\n            man->connection.ext_key_state = EKS_READY;\n            buffer_list_free(man->connection.ext_key_input);\n            man->connection.ext_key_input = man->connection.in_extra;\n            man->connection.in_extra = NULL;\n            return;\n\n        case IEC_CERTIFICATE:\n            man->connection.ext_cert_state = EKS_READY;\n            buffer_list_free(man->connection.ext_cert_input);\n            man->connection.ext_cert_input = man->connection.in_extra;\n            man->connection.in_extra = NULL;\n            return;\n    }\n    in_extra_reset(&man->connection, IER_RESET);\n}\n\nstatic bool\nparse_cid(const char *str, unsigned long *cid)\n{\n    if (sscanf(str, \"%lu\", cid) == 1)\n    {\n        return true;\n    }\n    else\n    {\n        msg(M_CLIENT, \"ERROR: cannot parse CID\");\n        return false;\n    }\n}\n\nstatic bool\nparse_uint(const char *str, const char *what, unsigned int *uint)\n{\n    if (sscanf(str, \"%u\", uint) == 1)\n    {\n        return true;\n    }\n    else\n    {\n        msg(M_CLIENT, \"ERROR: cannot parse %s\", what);\n        return false;\n    }\n}\n\n/**\n * Will send a notification to the client that succesful authentication\n * will require an additional step (web based SSO/2-factor auth/etc)\n *\n * @param man           The management interface struct\n * @param cid_str       The CID in string form\n * @param kid_str       The key ID in string form\n * @param extra         The string to be sent to the client containing\n *                      the information of the additional steps\n * @param timeout_str   The timeout value in string form\n */\nstatic void\nman_client_pending_auth(struct management *man, const char *cid_str, const char *kid_str,\n                        const char *extra, const char *timeout_str)\n{\n    unsigned long cid = 0;\n    unsigned int kid = 0;\n    unsigned int timeout = 0;\n    if (parse_cid(cid_str, &cid) && parse_uint(kid_str, \"KID\", &kid)\n        && parse_uint(timeout_str, \"TIMEOUT\", &timeout))\n    {\n        if (man->persist.callback.client_pending_auth)\n        {\n            bool ret = (*man->persist.callback.client_pending_auth)(man->persist.callback.arg, cid,\n                                                                    kid, extra, timeout);\n\n            if (ret)\n            {\n                msg(M_CLIENT, \"SUCCESS: client-pending-auth command succeeded\");\n            }\n            else\n            {\n                msg(M_CLIENT, \"ERROR: client-pending-auth command failed.\"\n                              \" Extra parameter might be too long\");\n            }\n        }\n        else\n        {\n            man_command_unsupported(\"client-pending-auth\");\n        }\n    }\n}\n\nstatic void\nman_client_auth(struct management *man, const char *cid_str, const char *kid_str, const bool extra)\n{\n    struct man_connection *mc = &man->connection;\n    mc->in_extra_cid = 0;\n    mc->in_extra_kid = 0;\n    if (parse_cid(cid_str, &mc->in_extra_cid) && parse_uint(kid_str, \"KID\", &mc->in_extra_kid))\n    {\n        mc->in_extra_cmd = IEC_CLIENT_AUTH;\n        in_extra_reset(mc, IER_NEW);\n        if (!extra)\n        {\n            in_extra_dispatch(man);\n        }\n    }\n}\n\nstatic void\nman_client_deny(struct management *man, const char *cid_str, const char *kid_str,\n                const char *reason, const char *client_reason)\n{\n    unsigned long cid = 0;\n    unsigned int kid = 0;\n    if (parse_cid(cid_str, &cid) && parse_uint(kid_str, \"KID\", &kid))\n    {\n        if (man->persist.callback.client_auth)\n        {\n            const bool status = (*man->persist.callback.client_auth)(\n                man->persist.callback.arg, cid, kid, false, reason, client_reason, NULL);\n            if (status)\n            {\n                msg(M_CLIENT, \"SUCCESS: client-deny command succeeded\");\n            }\n            else\n            {\n                msg(M_CLIENT, \"ERROR: client-deny command failed\");\n            }\n        }\n        else\n        {\n            man_command_unsupported(\"client-deny\");\n        }\n    }\n}\n\nstatic void\nman_client_kill(struct management *man, const char *cid_str, const char *kill_msg)\n{\n    unsigned long cid = 0;\n    if (parse_cid(cid_str, &cid))\n    {\n        if (man->persist.callback.kill_by_cid)\n        {\n            const bool status =\n                (*man->persist.callback.kill_by_cid)(man->persist.callback.arg, cid, kill_msg);\n            if (status)\n            {\n                msg(M_CLIENT, \"SUCCESS: client-kill command succeeded\");\n            }\n            else\n            {\n                msg(M_CLIENT, \"ERROR: client-kill command failed\");\n            }\n        }\n        else\n        {\n            man_command_unsupported(\"client-kill\");\n        }\n    }\n}\n\nstatic void\nman_client_n_clients(struct management *man)\n{\n    if (man->persist.callback.n_clients)\n    {\n        const int nclients = (*man->persist.callback.n_clients)(man->persist.callback.arg);\n        msg(M_CLIENT, \"SUCCESS: nclients=%d\", nclients);\n    }\n    else\n    {\n        man_command_unsupported(\"nclients\");\n    }\n}\n\nstatic void\nman_env_filter(struct management *man, const int level)\n{\n    man->connection.env_filter_level = level;\n    msg(M_CLIENT, \"SUCCESS: env_filter_level=%d\", level);\n}\n\n\nstatic void\nman_pk_sig(struct management *man, const char *cmd_name)\n{\n    struct man_connection *mc = &man->connection;\n    if (mc->ext_key_state == EKS_SOLICIT)\n    {\n        mc->ext_key_state = EKS_INPUT;\n        mc->in_extra_cmd = IEC_PK_SIGN;\n        in_extra_reset(mc, IER_NEW);\n    }\n    else\n    {\n        msg(M_CLIENT, \"ERROR: The %s command is not currently available\", cmd_name);\n    }\n}\n\nstatic void\nman_certificate(struct management *man)\n{\n    struct man_connection *mc = &man->connection;\n    if (mc->ext_cert_state == EKS_SOLICIT)\n    {\n        mc->ext_cert_state = EKS_INPUT;\n        mc->in_extra_cmd = IEC_CERTIFICATE;\n        in_extra_reset(mc, IER_NEW);\n    }\n    else\n    {\n        msg(M_CLIENT, \"ERROR: The certificate command is not currently available\");\n    }\n}\n\nstatic void\nman_load_stats(struct management *man)\n{\n    extern counter_type link_read_bytes_global;\n    extern counter_type link_write_bytes_global;\n    int nclients = 0;\n\n    if (man->persist.callback.n_clients)\n    {\n        nclients = (*man->persist.callback.n_clients)(man->persist.callback.arg);\n    }\n    msg(M_CLIENT, \"SUCCESS: nclients=%d,bytesin=\" counter_format \",bytesout=\" counter_format,\n        nclients, link_read_bytes_global, link_write_bytes_global);\n}\n\n#define MN_AT_LEAST (1 << 0)\n/**\n * Checks if the correct number of arguments to a management command are present\n * and otherwise prints an error and returns false.\n *\n * @param man       The management interface struct\n * @param p         pointer to the parameter array\n * @param n         number of arguments required\n * @param flags     if MN_AT_LEAST require at least n parameters and not exactly n\n * @return          Return whether p has n (or at least n) parameters\n */\nstatic bool\nman_need(struct management *man, const char **p, const int n, unsigned int flags)\n{\n    int i;\n    ASSERT(p[0]);\n    for (i = 1; i <= n; ++i)\n    {\n        if (!p[i])\n        {\n            msg(M_CLIENT, \"ERROR: the '%s' command requires %s%d parameter%s\", p[0],\n                (flags & MN_AT_LEAST) ? \"at least \" : \"\", n, n > 1 ? \"s\" : \"\");\n            return false;\n        }\n    }\n    return true;\n}\n\nstatic void\nman_proxy(struct management *man, const char **p)\n{\n    if (man->persist.callback.proxy_cmd)\n    {\n        const bool status = (*man->persist.callback.proxy_cmd)(man->persist.callback.arg, p);\n        report_command_status(status, \"proxy\");\n    }\n    else\n    {\n        man_command_unsupported(\"proxy\");\n    }\n}\n\nstatic void\nman_remote(struct management *man, const char **p)\n{\n    if (man->persist.callback.remote_cmd)\n    {\n        const bool status = (*man->persist.callback.remote_cmd)(man->persist.callback.arg, p);\n        report_command_status(status, \"remote\");\n    }\n    else\n    {\n        man_command_unsupported(\"remote\");\n    }\n}\n\n#ifdef TARGET_ANDROID\nstatic void\nman_network_change(struct management *man, bool samenetwork)\n{\n    /* Called to signal the OpenVPN that the network configuration has changed and\n     * the client should either float or reconnect.\n     *\n     * The code is currently only used by ics-openvpn\n     */\n    if (man->persist.callback.network_change)\n    {\n        int fd = (*man->persist.callback.network_change)(man->persist.callback.arg, samenetwork);\n        man->connection.fdtosend = fd;\n        msg(M_CLIENT, \"PROTECTFD: fd '%d' sent to be protected\", fd);\n        if (fd == -2)\n        {\n            man_signal(man, \"SIGUSR1\");\n        }\n    }\n}\n#endif\n\nstatic void\nset_client_version(struct management *man, const char *version)\n{\n    if (version)\n    {\n        man->connection.client_version = atoi(version);\n        /* Until MCV_PKSIGN_ALG, we missed to respond to this command. Acknowledge only if version is newer */\n        if (man->connection.client_version > MCV_PKSIGN_ALG)\n        {\n            msg(M_CLIENT, \"SUCCESS: Management client version set to %d\", man->connection.client_version);\n        }\n    }\n    else\n    {\n        msg(M_CLIENT, \"ERROR: Invalid value specified for management client version\");\n    }\n}\n\nstatic void\nman_push_update(struct management *man, const char **p, const push_update_type type)\n{\n    bool status = false;\n\n    if (type == UPT_BROADCAST)\n    {\n        if (!man->persist.callback.push_update_broadcast)\n        {\n            man_command_unsupported(\"push-update-broad\");\n            return;\n        }\n\n        status = (*man->persist.callback.push_update_broadcast)(man->persist.callback.arg, p[1]);\n    }\n    else if (type == UPT_BY_CID)\n    {\n        if (!man->persist.callback.push_update_by_cid)\n        {\n            man_command_unsupported(\"push-update-cid\");\n            return;\n        }\n\n        unsigned long cid = 0;\n\n        if (!parse_cid(p[1], &cid))\n        {\n            msg(M_CLIENT, \"ERROR: push-update-cid fail during cid parsing\");\n            return;\n        }\n\n        status = (*man->persist.callback.push_update_by_cid)(man->persist.callback.arg, cid, p[2]);\n    }\n\n    if (status)\n    {\n        msg(M_CLIENT, \"SUCCESS: push-update command succeeded\");\n        return;\n    }\n    msg(M_CLIENT, \"ERROR: push-update command failed\");\n}\n\nstatic void\nman_dispatch_command(struct management *man, struct status_output *so, const char **p,\n                     const int nparms)\n{\n    struct gc_arena gc = gc_new();\n\n    ASSERT(p[0]);\n    if (streq(p[0], \"exit\") || streq(p[0], \"quit\"))\n    {\n        man->connection.halt = true;\n        goto done;\n    }\n    else if (streq(p[0], \"help\"))\n    {\n        man_help();\n    }\n    else if (streq(p[0], \"version\") && p[1])\n    {\n        set_client_version(man, p[1]);\n    }\n    else if (streq(p[0], \"version\"))\n    {\n        msg(M_CLIENT, \"OpenVPN Version: %s\", title_string);\n        msg(M_CLIENT, \"Management Version: %d\", MANAGEMENT_VERSION);\n        msg(M_CLIENT, \"END\");\n    }\n    else if (streq(p[0], \"pid\"))\n    {\n        msg(M_CLIENT, \"SUCCESS: pid=%d\", platform_getpid());\n    }\n    else if (streq(p[0], \"nclients\"))\n    {\n        man_client_n_clients(man);\n    }\n    else if (streq(p[0], \"env-filter\"))\n    {\n        int level = 0;\n        if (p[1])\n        {\n            level = atoi(p[1]);\n        }\n        man_env_filter(man, level);\n    }\n    else if (streq(p[0], \"signal\"))\n    {\n        if (man_need(man, p, 1, 0))\n        {\n            man_signal(man, p[1]);\n        }\n    }\n#ifdef TARGET_ANDROID\n    else if (streq(p[0], \"network-change\"))\n    {\n        bool samenetwork = false;\n        if (p[1] && streq(p[1], \"samenetwork\"))\n        {\n            samenetwork = true;\n        }\n\n        man_network_change(man, samenetwork);\n    }\n#endif\n    else if (streq(p[0], \"load-stats\"))\n    {\n        man_load_stats(man);\n    }\n    else if (streq(p[0], \"status\"))\n    {\n        int version = 0;\n        if (p[1])\n        {\n            version = atoi(p[1]);\n        }\n        man_status(man, version, so);\n    }\n    else if (streq(p[0], \"kill\"))\n    {\n        if (man_need(man, p, 1, 0))\n        {\n            man_kill(man, p[1]);\n        }\n    }\n    else if (streq(p[0], \"verb\"))\n    {\n        if (p[1])\n        {\n            const int level = atoi(p[1]);\n            if (set_debug_level(level, 0))\n            {\n                msg(M_CLIENT, \"SUCCESS: verb level changed\");\n            }\n            else\n            {\n                msg(M_CLIENT, \"ERROR: verb level is out of range\");\n            }\n        }\n        else\n        {\n            msg(M_CLIENT, \"SUCCESS: verb=%u\", get_debug_level());\n        }\n    }\n    else if (streq(p[0], \"mute\"))\n    {\n        if (p[1])\n        {\n            const int level = atoi(p[1]);\n            if (set_mute_cutoff(level))\n            {\n                msg(M_CLIENT, \"SUCCESS: mute level changed\");\n            }\n            else\n            {\n                msg(M_CLIENT, \"ERROR: mute level is out of range\");\n            }\n        }\n        else\n        {\n            msg(M_CLIENT, \"SUCCESS: mute=%d\", get_mute_cutoff());\n        }\n    }\n    else if (streq(p[0], \"auth-retry\"))\n    {\n        if (p[1])\n        {\n            if (auth_retry_set(M_CLIENT, p[1]))\n            {\n                msg(M_CLIENT, \"SUCCESS: auth-retry parameter changed\");\n            }\n            else\n            {\n                msg(M_CLIENT, \"ERROR: bad auth-retry parameter\");\n            }\n        }\n        else\n        {\n            msg(M_CLIENT, \"SUCCESS: auth-retry=%s\", auth_retry_print());\n        }\n    }\n    else if (streq(p[0], \"state\"))\n    {\n        if (!p[1])\n        {\n            man_state(man, \"1\");\n        }\n        else\n        {\n            if (p[1])\n            {\n                man_state(man, p[1]);\n            }\n            if (p[2])\n            {\n                man_state(man, p[2]);\n            }\n        }\n    }\n    else if (streq(p[0], \"log\"))\n    {\n        if (man_need(man, p, 1, MN_AT_LEAST))\n        {\n            if (p[1])\n            {\n                man_log(man, p[1]);\n            }\n            if (p[2])\n            {\n                man_log(man, p[2]);\n            }\n        }\n    }\n    else if (streq(p[0], \"echo\"))\n    {\n        if (man_need(man, p, 1, MN_AT_LEAST))\n        {\n            if (p[1])\n            {\n                man_echo(man, p[1]);\n            }\n            if (p[2])\n            {\n                man_echo(man, p[2]);\n            }\n        }\n    }\n    else if (streq(p[0], \"username\"))\n    {\n        if (man_need(man, p, 2, 0))\n        {\n            man_query_username(man, p[1], p[2]);\n        }\n    }\n    else if (streq(p[0], \"password\"))\n    {\n        if (man_need(man, p, 2, 0))\n        {\n            man_query_password(man, p[1], p[2]);\n        }\n    }\n    else if (streq(p[0], \"forget-passwords\"))\n    {\n        man_forget_passwords(man);\n    }\n    else if (streq(p[0], \"needok\"))\n    {\n        if (man_need(man, p, 2, 0))\n        {\n            man_query_need_ok(man, p[1], p[2]);\n        }\n    }\n    else if (streq(p[0], \"needstr\"))\n    {\n        if (man_need(man, p, 2, 0))\n        {\n            man_query_need_str(man, p[1], p[2]);\n        }\n    }\n    else if (streq(p[0], \"cr-response\"))\n    {\n        if (man_need(man, p, 1, 0))\n        {\n            man_send_cc_message(man, \"CR_RESPONSE\", p[1]);\n        }\n    }\n    else if (streq(p[0], \"net\"))\n    {\n        man_net(man);\n    }\n    else if (streq(p[0], \"hold\"))\n    {\n        man_hold(man, p[1]);\n    }\n    else if (streq(p[0], \"bytecount\"))\n    {\n        if (man_need(man, p, 1, 0))\n        {\n            man_bytecount(man, atoi(p[1]));\n        }\n    }\n    else if (streq(p[0], \"client-kill\"))\n    {\n        if (man_need(man, p, 1, MN_AT_LEAST))\n        {\n            man_client_kill(man, p[1], p[2]);\n        }\n    }\n    else if (streq(p[0], \"client-deny\"))\n    {\n        if (man_need(man, p, 3, MN_AT_LEAST))\n        {\n            man_client_deny(man, p[1], p[2], p[3], p[4]);\n        }\n    }\n    else if (streq(p[0], \"client-auth-nt\"))\n    {\n        if (man_need(man, p, 2, 0))\n        {\n            man_client_auth(man, p[1], p[2], false);\n        }\n    }\n    else if (streq(p[0], \"client-auth\"))\n    {\n        if (man_need(man, p, 2, 0))\n        {\n            man_client_auth(man, p[1], p[2], true);\n        }\n    }\n    else if (streq(p[0], \"client-pending-auth\"))\n    {\n        if (man_need(man, p, 4, 0))\n        {\n            man_client_pending_auth(man, p[1], p[2], p[3], p[4]);\n        }\n    }\n    else if (streq(p[0], \"rsa-sig\"))\n    {\n        man_pk_sig(man, \"rsa-sig\");\n    }\n    else if (streq(p[0], \"pk-sig\"))\n    {\n        man_pk_sig(man, \"pk-sig\");\n    }\n    else if (streq(p[0], \"certificate\"))\n    {\n        man_certificate(man);\n    }\n#ifdef ENABLE_PKCS11\n    else if (streq(p[0], \"pkcs11-id-count\"))\n    {\n        man_pkcs11_id_count(man);\n    }\n    else if (streq(p[0], \"pkcs11-id-get\"))\n    {\n        if (man_need(man, p, 1, 0))\n        {\n            man_pkcs11_id_get(man, atoi(p[1]));\n        }\n    }\n#endif\n    else if (streq(p[0], \"remote-entry-count\"))\n    {\n        man_remote_entry_count(man);\n    }\n    else if (streq(p[0], \"remote-entry-get\"))\n    {\n        if (man_need(man, p, 1, MN_AT_LEAST))\n        {\n            man_remote_entry_get(man, p[1], p[2]);\n        }\n    }\n    else if (streq(p[0], \"proxy\"))\n    {\n        if (man_need(man, p, 1, MN_AT_LEAST))\n        {\n            man_proxy(man, p);\n        }\n    }\n    else if (streq(p[0], \"remote\"))\n    {\n        if (man_need(man, p, 1, MN_AT_LEAST))\n        {\n            man_remote(man, p);\n        }\n    }\n    else if (streq(p[0], \"push-update-broad\"))\n    {\n        if (man_need(man, p, 1, 0))\n        {\n            man_push_update(man, p, UPT_BROADCAST);\n        }\n    }\n    else if (streq(p[0], \"push-update-cid\"))\n    {\n        if (man_need(man, p, 2, 0))\n        {\n            man_push_update(man, p, UPT_BY_CID);\n        }\n    }\n#if 1\n    else if (streq(p[0], \"test\"))\n    {\n        if (man_need(man, p, 1, 0))\n        {\n            int i;\n            const int n = atoi(p[1]);\n            for (i = 0; i < n; ++i)\n            {\n                msg(M_CLIENT,\n                    \"[%d] The purpose of this command is to generate large amounts of output.\", i);\n            }\n        }\n    }\n#endif\n    else\n    {\n        msg(M_CLIENT, \"ERROR: unknown command [%s], enter 'help' for more options\", p[0]);\n    }\n\ndone:\n    gc_free(&gc);\n}\n\n#ifdef _WIN32\n\nstatic void\nman_start_ne32(struct management *man)\n{\n    switch (man->connection.state)\n    {\n        case MS_LISTEN:\n            net_event_win32_start(&man->connection.ne32, FD_ACCEPT, man->connection.sd_top);\n            break;\n\n        case MS_CC_WAIT_READ:\n        case MS_CC_WAIT_WRITE:\n            net_event_win32_start(&man->connection.ne32, FD_READ | FD_WRITE | FD_CLOSE,\n                                  man->connection.sd_cli);\n            break;\n\n        default:\n            ASSERT(0);\n    }\n}\n\nstatic void\nman_stop_ne32(struct management *man)\n{\n    net_event_win32_stop(&man->connection.ne32);\n}\n\n#endif /* ifdef _WIN32 */\n\nstatic void\nman_connection_settings_reset(struct management *man)\n{\n    man->connection.state_realtime = false;\n    man->connection.log_realtime = false;\n    man->connection.echo_realtime = false;\n    man->connection.bytecount_update_seconds = 0;\n    man->connection.password_verified = false;\n    man->connection.password_tries = 0;\n    man->connection.halt = false;\n    man->connection.state = MS_CC_WAIT_WRITE;\n}\n\nstatic void\nman_new_connection_post(struct management *man, const char *description)\n{\n    struct gc_arena gc = gc_new();\n\n    set_nonblock(man->connection.sd_cli);\n\n    man_connection_settings_reset(man);\n\n#ifdef _WIN32\n    man_start_ne32(man);\n#endif\n\n#if UNIX_SOCK_SUPPORT\n    if (man->settings.flags & MF_UNIX_SOCK)\n    {\n        msg(D_MANAGEMENT, \"MANAGEMENT: %s %s\", description,\n            sockaddr_unix_name(&man->settings.local_unix, \"NULL\"));\n    }\n    else\n#endif\n        if (man->settings.flags & MF_CONNECT_AS_CLIENT)\n    {\n        msg(D_MANAGEMENT, \"MANAGEMENT: %s %s\", description,\n            print_sockaddr(man->settings.local->ai_addr, &gc));\n    }\n    else\n    {\n        struct sockaddr_storage addr;\n        socklen_t addrlen = sizeof(addr);\n        if (!getpeername(man->connection.sd_cli, (struct sockaddr *)&addr, &addrlen))\n        {\n            msg(D_MANAGEMENT, \"MANAGEMENT: %s %s\", description,\n                print_sockaddr((struct sockaddr *)&addr, &gc));\n        }\n        else\n        {\n            msg(D_MANAGEMENT, \"MANAGEMENT: %s %s\", description, \"unknown\");\n        }\n    }\n\n    buffer_list_reset(man->connection.out);\n\n    if (!man_password_needed(man))\n    {\n        man_welcome(man);\n    }\n    man_prompt(man);\n    man_update_io_state(man);\n\n    gc_free(&gc);\n}\n\n#if UNIX_SOCK_SUPPORT\nstatic bool\nman_verify_unix_peer_uid_gid(struct management *man, const socket_descriptor_t sd)\n{\n    if (socket_defined(sd) && (man->settings.user.user_valid || man->settings.group.group_valid))\n    {\n        static const char err_prefix[] =\n            \"MANAGEMENT: unix domain socket client connection rejected --\";\n        uid_t uid;\n        gid_t gid;\n        if (unix_socket_get_peer_uid_gid(man->connection.sd_cli, &uid, &gid))\n        {\n            if (man->settings.user.user_valid && man->settings.user.uid != uid)\n            {\n                msg(D_MANAGEMENT,\n                    \"%s UID of socket peer (%d) doesn't match required value (%d) as given by --management-client-user\",\n                    err_prefix, uid, man->settings.user.uid);\n                return false;\n            }\n            if (man->settings.group.group_valid && man->settings.group.gid != gid)\n            {\n                msg(D_MANAGEMENT,\n                    \"%s GID of socket peer (%d) doesn't match required value (%d) as given by --management-client-group\",\n                    err_prefix, gid, man->settings.group.gid);\n                return false;\n            }\n        }\n        else\n        {\n            msg(D_MANAGEMENT, \"%s cannot get UID/GID of socket peer\", err_prefix);\n            return false;\n        }\n    }\n    return true;\n}\n#endif /* if UNIX_SOCK_SUPPORT */\n\nstatic void\nman_accept(struct management *man)\n{\n    struct link_socket_actual act;\n    CLEAR(act);\n\n    /*\n     * Accept the TCP or Unix domain socket client.\n     */\n#if UNIX_SOCK_SUPPORT\n    if (man->settings.flags & MF_UNIX_SOCK)\n    {\n        struct sockaddr_un remote;\n        man->connection.sd_cli = socket_accept_unix(man->connection.sd_top, &remote);\n        if (!man_verify_unix_peer_uid_gid(man, man->connection.sd_cli))\n        {\n            sd_close(&man->connection.sd_cli);\n        }\n    }\n    else\n#endif\n    {\n        man->connection.sd_cli = socket_do_accept(man->connection.sd_top, &act, false);\n    }\n\n    if (socket_defined(man->connection.sd_cli))\n    {\n        man->connection.remote = act.dest;\n\n        if (socket_defined(man->connection.sd_top))\n        {\n#ifdef _WIN32\n            man_stop_ne32(man);\n#endif\n        }\n\n        man_new_connection_post(man, \"Client connected from\");\n    }\n}\n\nstatic void\nman_listen(struct management *man)\n{\n    struct gc_arena gc = gc_new();\n\n    /*\n     * Initialize state\n     */\n    man->connection.state = MS_LISTEN;\n    man->connection.sd_cli = SOCKET_UNDEFINED;\n\n    /*\n     * Initialize listening socket\n     */\n    if (man->connection.sd_top == SOCKET_UNDEFINED)\n    {\n#if UNIX_SOCK_SUPPORT\n        if (man->settings.flags & MF_UNIX_SOCK)\n        {\n            man_delete_unix_socket(man);\n            man->connection.sd_top = create_socket_unix();\n            socket_bind_unix(man->connection.sd_top, &man->settings.local_unix, \"MANAGEMENT\");\n        }\n        else\n#endif\n        {\n            man->connection.sd_top = create_socket_tcp(man->settings.local);\n            socket_bind(man->connection.sd_top, man->settings.local, man->settings.local->ai_family,\n                        \"MANAGEMENT\", false);\n        }\n\n        /*\n         * Listen for connection\n         */\n        if (listen(man->connection.sd_top, 1))\n        {\n            msg(M_ERR, \"MANAGEMENT: listen() failed\");\n        }\n\n        /*\n         * Set misc socket properties\n         */\n        set_nonblock(man->connection.sd_top);\n\n#if UNIX_SOCK_SUPPORT\n        if (man->settings.flags & MF_UNIX_SOCK)\n        {\n            msg(D_MANAGEMENT, \"MANAGEMENT: unix domain socket listening on %s\",\n                sockaddr_unix_name(&man->settings.local_unix, \"NULL\"));\n        }\n        else\n#endif\n        {\n            const struct sockaddr *man_addr = man->settings.local->ai_addr;\n            struct sockaddr_storage addr;\n            socklen_t addrlen = sizeof(addr);\n            if (!getsockname(man->connection.sd_top, (struct sockaddr *)&addr, &addrlen))\n            {\n                man_addr = (struct sockaddr *)&addr;\n            }\n            else\n            {\n                msg(M_WARN | M_ERRNO, \"Failed to get the management socket address\");\n            }\n            msg(D_MANAGEMENT, \"MANAGEMENT: TCP Socket listening on %s\",\n                print_sockaddr(man_addr, &gc));\n        }\n    }\n\n#ifdef _WIN32\n    man_start_ne32(man);\n#endif\n\n    gc_free(&gc);\n}\n\nstatic void\nman_connect(struct management *man)\n{\n    struct gc_arena gc = gc_new();\n    int status;\n    int signal_received = 0;\n\n    /*\n     * Initialize state\n     */\n    man->connection.state = MS_INITIAL;\n    man->connection.sd_top = SOCKET_UNDEFINED;\n\n#if UNIX_SOCK_SUPPORT\n    if (man->settings.flags & MF_UNIX_SOCK)\n    {\n        man->connection.sd_cli = create_socket_unix();\n        status = socket_connect_unix(man->connection.sd_cli, &man->settings.local_unix);\n        if (!status && !man_verify_unix_peer_uid_gid(man, man->connection.sd_cli))\n        {\n#ifdef EPERM\n            status = EPERM;\n#else\n            status = 1;\n#endif\n            sd_close(&man->connection.sd_cli);\n        }\n    }\n    else\n#endif\n    {\n        man->connection.sd_cli = create_socket_tcp(man->settings.local);\n        status = openvpn_connect(man->connection.sd_cli, man->settings.local->ai_addr, 5,\n                                 &signal_received);\n    }\n\n    if (signal_received)\n    {\n        throw_signal(signal_received);\n        goto done;\n    }\n\n    if (status)\n    {\n#if UNIX_SOCK_SUPPORT\n        if (man->settings.flags & MF_UNIX_SOCK)\n        {\n            msg(D_LINK_ERRORS | M_ERRNO, \"MANAGEMENT: connect to unix socket %s failed\",\n                sockaddr_unix_name(&man->settings.local_unix, \"NULL\"));\n        }\n        else\n#endif\n        {\n            msg(D_LINK_ERRORS | M_ERRNO, \"MANAGEMENT: connect to %s failed\",\n                print_sockaddr(man->settings.local->ai_addr, &gc));\n        }\n        throw_signal_soft(SIGTERM, \"management-connect-failed\");\n        goto done;\n    }\n\n    man_new_connection_post(man, \"Connected to management server at\");\n\ndone:\n    gc_free(&gc);\n}\n\nstatic void\nman_reset_client_socket(struct management *man, const bool exiting)\n{\n    if (socket_defined(man->connection.sd_cli))\n    {\n        man_bytecount_stop(man);\n#ifdef _WIN32\n        man_stop_ne32(man);\n#endif\n        man_close_socket(man, man->connection.sd_cli);\n        man->connection.sd_cli = SOCKET_UNDEFINED;\n        man->connection.state = MS_INITIAL;\n        command_line_reset(man->connection.in);\n        buffer_list_reset(man->connection.out);\n        in_extra_reset(&man->connection, IER_RESET);\n        msg(D_MANAGEMENT, \"MANAGEMENT: Client disconnected\");\n    }\n    if (!exiting)\n    {\n        if (man->settings.flags & MF_FORGET_DISCONNECT && !man_password_needed(man))\n        {\n            msg(D_MANAGEMENT, \"MANAGEMENT: Reset authentication on disconnect\");\n            ssl_purge_auth(false);\n            (void)ssl_clean_auth_token();\n        }\n\n        if (man->settings.flags & MF_SIGNAL && !man_password_needed(man))\n        {\n            int mysig = man_mod_signal(man, SIGUSR1);\n            if (mysig >= 0)\n            {\n                msg(D_MANAGEMENT, \"MANAGEMENT: Triggering management signal\");\n                throw_signal_soft(mysig, \"management-disconnect\");\n            }\n        }\n\n        if (man->settings.flags & MF_CONNECT_AS_CLIENT)\n        {\n            msg(D_MANAGEMENT, \"MANAGEMENT: Triggering management exit\");\n            throw_signal_soft(SIGTERM, \"management-exit\");\n        }\n        else\n        {\n            man_listen(man);\n        }\n    }\n}\n\nstatic void\nman_process_command(struct management *man, const char *line)\n{\n    struct gc_arena gc = gc_new();\n    struct status_output *so;\n    int nparms;\n    char *parms[MAX_PARMS + 1];\n\n    CLEAR(parms);\n    so = status_open(NULL, 0, -1, &man->persist.vout, 0);\n    in_extra_reset(&man->connection, IER_RESET);\n\n    if (man_password_needed(man))\n    {\n        man_check_password(man, line);\n    }\n    else\n    {\n        nparms = parse_line(line, parms, MAX_PARMS, \"TCP\", 0, M_CLIENT, &gc);\n        if (parms[0] && streq(parms[0], \"password\"))\n        {\n            msg(D_MANAGEMENT_DEBUG, \"MANAGEMENT: CMD 'password [...]'\");\n        }\n        else if (!streq(line, \"load-stats\"))\n        {\n            msg(D_MANAGEMENT_DEBUG, \"MANAGEMENT: CMD '%s'\", line);\n        }\n\n#if 0\n        /* DEBUGGING -- print args */\n        {\n            int i;\n            for (i = 0; i < nparms; ++i)\n            {\n                msg(M_INFO, \"[%d] '%s'\", i, parms[i]);\n            }\n        }\n#endif\n\n        if (nparms > 0)\n        {\n            man_dispatch_command(man, so, (const char **)parms, nparms);\n        }\n    }\n\n    CLEAR(parms);\n    status_close(so);\n    gc_free(&gc);\n}\n\nstatic bool\nman_io_error(struct management *man, const char *prefix)\n{\n    bool crt_error = false;\n    int err = openvpn_errno_maybe_crt(&crt_error);\n\n    if (!ignore_sys_error(err, crt_error))\n    {\n        struct gc_arena gc = gc_new();\n        msg(D_MANAGEMENT, \"MANAGEMENT: TCP %s error: %s\", prefix, strerror(err));\n        gc_free(&gc);\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\n#ifdef TARGET_ANDROID\nstatic ssize_t\nman_send_with_fd(int fd, void *ptr, size_t nbytes, int flags, int sendfd)\n{\n    struct msghdr msg = { 0 };\n    struct iovec iov[1];\n\n    union\n    {\n        struct cmsghdr cm;\n        char control[CMSG_SPACE(sizeof(int))];\n    } control_un;\n    struct cmsghdr *cmptr;\n\n    msg.msg_control = control_un.control;\n    msg.msg_controllen = sizeof(control_un.control);\n\n    cmptr = CMSG_FIRSTHDR(&msg);\n    cmptr->cmsg_len = CMSG_LEN(sizeof(int));\n    cmptr->cmsg_level = SOL_SOCKET;\n    cmptr->cmsg_type = SCM_RIGHTS;\n    *((int *)CMSG_DATA(cmptr)) = sendfd;\n\n    msg.msg_name = NULL;\n    msg.msg_namelen = 0;\n\n    iov[0].iov_base = ptr;\n    iov[0].iov_len = nbytes;\n    msg.msg_iov = iov;\n    msg.msg_iovlen = 1;\n\n    return (sendmsg(fd, &msg, flags));\n}\n\nstatic ssize_t\nman_recv_with_fd(int fd, void *ptr, size_t nbytes, int flags, int *recvfd)\n{\n    struct msghdr msghdr = { 0 };\n    struct iovec iov[1];\n    ssize_t n;\n\n    union\n    {\n        struct cmsghdr cm;\n        char control[CMSG_SPACE(sizeof(int))];\n    } control_un;\n    struct cmsghdr *cmptr;\n\n    msghdr.msg_control = control_un.control;\n    msghdr.msg_controllen = sizeof(control_un.control);\n\n    msghdr.msg_name = NULL;\n    msghdr.msg_namelen = 0;\n\n    iov[0].iov_base = ptr;\n    iov[0].iov_len = nbytes;\n    msghdr.msg_iov = iov;\n    msghdr.msg_iovlen = 1;\n\n    if ((n = recvmsg(fd, &msghdr, flags)) <= 0)\n    {\n        return (n);\n    }\n\n    if ((cmptr = CMSG_FIRSTHDR(&msghdr)) != NULL && cmptr->cmsg_len == CMSG_LEN(sizeof(int)))\n    {\n        if (cmptr->cmsg_level != SOL_SOCKET)\n        {\n            msg(M_ERR, \"control level != SOL_SOCKET\");\n        }\n        if (cmptr->cmsg_type != SCM_RIGHTS)\n        {\n            msg(M_ERR, \"control type != SCM_RIGHTS\");\n        }\n        *recvfd = *((int *)CMSG_DATA(cmptr));\n    }\n    else\n    {\n        *recvfd = -1; /* descriptor was not passed */\n    }\n    return (n);\n}\n\n/*\n * The android control method will instruct the GUI part of openvpn to do\n * the route/ifconfig/open tun command.   See doc/android.txt for details.\n */\nbool\nmanagement_android_control(struct management *man, const char *command, const char *msg)\n{\n    if (!man)\n    {\n        msg(M_FATAL, \"Required management interface not available.\");\n    }\n    struct user_pass up;\n    CLEAR(up);\n    strncpy(up.username, msg, sizeof(up.username) - 1);\n\n    management_query_user_pass(management, &up, command, GET_USER_PASS_NEED_OK, (void *)0);\n    return strcmp(\"ok\", up.password) == 0;\n}\n\n/*\n * In Android 4.4 it is not possible to open a new tun device and then close the\n * old tun device without breaking the whole VPNService stack until the device\n * is rebooted. This management method ask the UI what method should be taken to\n * ensure the optimal solution for the situation\n */\nint\nmanagment_android_persisttun_action(struct management *man)\n{\n    struct user_pass up;\n    CLEAR(up);\n    strcpy(up.username, \"tunmethod\");\n    management_query_user_pass(management, &up, \"PERSIST_TUN_ACTION\", GET_USER_PASS_NEED_OK,\n                               (void *)0);\n    if (!strcmp(\"NOACTION\", up.password))\n    {\n        return ANDROID_KEEP_OLD_TUN;\n    }\n    else if (!strcmp(\"OPEN_BEFORE_CLOSE\", up.password))\n    {\n        return ANDROID_OPEN_BEFORE_CLOSE;\n    }\n    else\n    {\n        msg(M_ERR, \"Got unrecognised '%s' from management for PERSIST_TUN_ACTION query\",\n            up.password);\n    }\n\n    ASSERT(0);\n    return ANDROID_OPEN_BEFORE_CLOSE;\n}\n\n\n#endif /* ifdef TARGET_ANDROID */\n\nstatic ssize_t\nman_read(struct management *man)\n{\n    /*\n     * read command line from socket\n     */\n    unsigned char buf[256];\n    ssize_t len = 0;\n\n#ifdef TARGET_ANDROID\n    int fd;\n    len = man_recv_with_fd(man->connection.sd_cli, buf, sizeof(buf), MSG_NOSIGNAL, &fd);\n    if (fd >= 0)\n    {\n        man->connection.lastfdreceived = fd;\n    }\n#else /* ifdef TARGET_ANDROID */\n    len = recv(man->connection.sd_cli, (void *)buf, sizeof(buf), MSG_NOSIGNAL);\n#endif\n\n    if (len == 0)\n    {\n        man_reset_client_socket(man, false);\n    }\n    else if (len > 0)\n    {\n        bool processed_command = false;\n\n        ASSERT(len <= (ssize_t)sizeof(buf));\n        command_line_add(man->connection.in, buf, (size_t)len);\n\n        /*\n         * Reset output object\n         */\n        buffer_list_reset(man->connection.out);\n\n        /*\n         * process command line if complete\n         */\n        {\n            const char *line;\n            while ((line = command_line_get(man->connection.in)))\n            {\n                if (man->connection.in_extra)\n                {\n                    if (!strcmp(line, \"END\"))\n                    {\n                        in_extra_dispatch(man);\n                    }\n                    else\n                    {\n                        buffer_list_push(man->connection.in_extra, line);\n                    }\n                }\n                else\n                {\n                    man_process_command(man, (char *)line);\n                }\n                if (man->connection.halt)\n                {\n                    break;\n                }\n                command_line_next(man->connection.in);\n                processed_command = true;\n            }\n        }\n\n        /*\n         * Reset output state to MS_CC_WAIT_(READ|WRITE)\n         */\n        if (man->connection.halt)\n        {\n            man_reset_client_socket(man, false);\n            len = 0;\n        }\n        else\n        {\n            if (processed_command)\n            {\n                man_prompt(man);\n            }\n            man_update_io_state(man);\n        }\n    }\n    else /* len < 0 */\n    {\n        if (man_io_error(man, \"recv\"))\n        {\n            man_reset_client_socket(man, false);\n        }\n    }\n    return len;\n}\n\nstatic ssize_t\nman_write(struct management *man)\n{\n    const int size_hint = 1024;\n    ssize_t sent = 0;\n    const struct buffer *buf;\n\n    buffer_list_aggregate(man->connection.out, size_hint);\n    buf = buffer_list_peek(man->connection.out);\n    if (buf && BLEN(buf))\n    {\n        const int len = min_int(size_hint, BLEN(buf));\n#ifdef TARGET_ANDROID\n        if (man->connection.fdtosend > 0)\n        {\n            sent = man_send_with_fd(man->connection.sd_cli, BPTR(buf), len, MSG_NOSIGNAL,\n                                    man->connection.fdtosend);\n            man->connection.fdtosend = -1;\n        }\n        else\n#endif\n        {\n            sent = send(man->connection.sd_cli, (const void *)BPTR(buf), len, MSG_NOSIGNAL);\n        }\n        if (sent >= 0)\n        {\n            buffer_list_advance(man->connection.out, sent);\n        }\n        else if (sent < 0)\n        {\n            if (man_io_error(man, \"send\"))\n            {\n                man_reset_client_socket(man, false);\n            }\n        }\n    }\n\n    /*\n     * Reset output state to MS_CC_WAIT_(READ|WRITE)\n     */\n    man_update_io_state(man);\n\n    return sent;\n}\n\nstatic void\nman_connection_clear(struct man_connection *mc)\n{\n    CLEAR(*mc);\n\n    /* set initial state */\n    mc->state = MS_INITIAL;\n\n    /* clear socket descriptors */\n    mc->sd_top = SOCKET_UNDEFINED;\n    mc->sd_cli = SOCKET_UNDEFINED;\n}\n\nstatic void\nman_persist_init(struct management *man, const int log_history_cache, const int echo_buffer_size,\n                 const int state_buffer_size)\n{\n    struct man_persist *mp = &man->persist;\n    if (!mp->defined)\n    {\n        CLEAR(*mp);\n\n        /* initialize log history store */\n        mp->log = log_history_init(log_history_cache);\n\n        /*\n         * Initialize virtual output object, so that functions\n         * which write to a virtual_output object can be redirected\n         * here to the management object.\n         */\n        mp->vout.func = virtual_output_callback_func;\n        mp->vout.arg = man;\n        mp->vout.flags_default = M_CLIENT;\n        msg_set_virtual_output(&mp->vout);\n\n        /*\n         * Initialize --echo list\n         */\n        man->persist.echo = log_history_init(echo_buffer_size);\n\n        /*\n         * Initialize --state list\n         */\n        man->persist.state = log_history_init(state_buffer_size);\n\n        mp->defined = true;\n    }\n}\n\nstatic void\nman_persist_close(struct man_persist *mp)\n{\n    if (mp->log)\n    {\n        msg_set_virtual_output(NULL);\n        log_history_close(mp->log);\n    }\n\n    if (mp->echo)\n    {\n        log_history_close(mp->echo);\n    }\n\n    if (mp->state)\n    {\n        log_history_close(mp->state);\n    }\n\n    CLEAR(*mp);\n}\n\nstatic void\nman_settings_init(struct man_settings *ms, const char *addr, const char *port,\n                  const char *pass_file, const char *client_user, const char *client_group,\n                  const int log_history_cache, const int echo_buffer_size,\n                  const int state_buffer_size, const int remap_sigusr1, const unsigned int flags)\n{\n    if (!ms->defined)\n    {\n        CLEAR(*ms);\n\n        ms->flags = flags;\n\n        /*\n         * Get username/password\n         */\n        if (pass_file)\n        {\n            get_user_pass(&ms->up, pass_file, \"Management\", GET_USER_PASS_PASSWORD_ONLY);\n        }\n\n#if UNIX_SOCK_SUPPORT\n        /*\n         * lookup client UID/GID if specified\n         */\n        if (client_user)\n        {\n            ASSERT(platform_user_get(client_user, &ms->user));\n            msg(D_MANAGEMENT, \"MANAGEMENT: client_uid=%d\", ms->user.uid);\n        }\n        if (client_group)\n        {\n            ASSERT(platform_group_get(client_group, &ms->group));\n            msg(D_MANAGEMENT, \"MANAGEMENT: client_gid=%d\", ms->group.gid);\n        }\n\n        if (ms->flags & MF_UNIX_SOCK)\n        {\n            sockaddr_unix_init(&ms->local_unix, addr);\n        }\n        else\n#endif\n        {\n            /*\n             * Run management over tunnel, or\n             * separate channel?\n             */\n            if (streq(addr, \"tunnel\") && !(flags & MF_CONNECT_AS_CLIENT))\n            {\n                ms->management_over_tunnel = true;\n            }\n            else\n            {\n                int status;\n                int resolve_flags = GETADDR_RESOLVE | GETADDR_WARN_ON_SIGNAL | GETADDR_FATAL;\n\n                if (!(flags & MF_CONNECT_AS_CLIENT))\n                {\n                    resolve_flags |= GETADDR_PASSIVE;\n                }\n\n                status =\n                    openvpn_getaddrinfo(resolve_flags, addr, port, 0, NULL, AF_UNSPEC, &ms->local);\n                ASSERT(status == 0);\n            }\n        }\n\n        /*\n         * Log history and echo buffer may need to be resized\n         */\n        ms->log_history_cache = log_history_cache;\n        ms->echo_buffer_size = echo_buffer_size;\n        ms->state_buffer_size = state_buffer_size;\n\n        /*\n         * Set remap sigusr1 flags\n         */\n        if (remap_sigusr1 == SIGHUP)\n        {\n            ms->mansig |= MANSIG_MAP_USR1_TO_HUP;\n        }\n        else if (remap_sigusr1 == SIGTERM)\n        {\n            ms->mansig |= MANSIG_MAP_USR1_TO_TERM;\n        }\n\n        ms->defined = true;\n    }\n}\n\nstatic void\nman_settings_close(struct man_settings *ms)\n{\n    if (ms->local)\n    {\n        freeaddrinfo(ms->local);\n    }\n    CLEAR(*ms);\n}\n\n\nstatic void\nman_connection_init(struct management *man)\n{\n    if (man->connection.state == MS_INITIAL)\n    {\n#ifdef _WIN32\n        /*\n         * This object is a sort of TCP/IP helper\n         * for Windows.\n         */\n        net_event_win32_init(&man->connection.ne32);\n#endif\n\n        /*\n         * Allocate helper objects for command line input and\n         * command output from/to the socket.\n         */\n        man->connection.in = command_line_new(1024);\n        man->connection.out = buffer_list_new();\n\n        /*\n         * Initialize event set for standalone usage, when we are\n         * running outside of the primary event loop.\n         */\n        {\n            int maxevents = 1;\n            man->connection.es = event_set_init(&maxevents, EVENT_METHOD_FAST);\n        }\n\n        man->connection.client_version = MCV_DEFAULT; /* default version */\n\n        /*\n         * Listen/connect socket\n         */\n        if (man->settings.flags & MF_CONNECT_AS_CLIENT)\n        {\n            man_connect(man);\n        }\n        else\n        {\n            man_listen(man);\n        }\n    }\n}\n\nstatic void\nman_connection_close(struct management *man)\n{\n    struct man_connection *mc = &man->connection;\n\n    event_free(mc->es);\n#ifdef _WIN32\n    net_event_win32_close(&mc->ne32);\n#endif\n    if (socket_defined(mc->sd_top))\n    {\n        man_close_socket(man, mc->sd_top);\n        man_delete_unix_socket(man);\n    }\n    if (socket_defined(mc->sd_cli))\n    {\n        man_close_socket(man, mc->sd_cli);\n    }\n\n    command_line_free(mc->in);\n    buffer_list_free(mc->out);\n\n    event_timeout_clear(&mc->bytecount_update_interval);\n\n    in_extra_reset(&man->connection, IER_RESET);\n    buffer_list_free(mc->ext_key_input);\n    man_connection_clear(mc);\n}\n\nstruct management *\nmanagement_init(void)\n{\n    struct management *man;\n    ALLOC_OBJ_CLEAR(man, struct management);\n\n    man_persist_init(man, MANAGEMENT_LOG_HISTORY_INITIAL_SIZE, MANAGEMENT_ECHO_BUFFER_SIZE,\n                     MANAGEMENT_STATE_BUFFER_SIZE);\n\n    man_connection_clear(&man->connection);\n\n    return man;\n}\n\nbool\nmanagement_open(struct management *man, const char *addr, const char *port, const char *pass_file,\n                const char *client_user, const char *client_group, const int log_history_cache,\n                const int echo_buffer_size, const int state_buffer_size, const int remap_sigusr1,\n                const unsigned int flags)\n{\n    bool ret = false;\n\n    /*\n     * Save the settings only if they have not\n     * been saved before.\n     */\n    man_settings_init(&man->settings, addr, port, pass_file, client_user, client_group,\n                      log_history_cache, echo_buffer_size, state_buffer_size, remap_sigusr1, flags);\n\n    /*\n     * The log is initially sized to MANAGEMENT_LOG_HISTORY_INITIAL_SIZE,\n     * but may be changed here.  Ditto for echo and state buffers.\n     */\n    log_history_resize(man->persist.log, man->settings.log_history_cache);\n    log_history_resize(man->persist.echo, man->settings.echo_buffer_size);\n    log_history_resize(man->persist.state, man->settings.state_buffer_size);\n\n    /*\n     * If connection object is uninitialized and we are not doing\n     * over-the-tunnel management, then open (listening) connection.\n     */\n    if (man->connection.state == MS_INITIAL)\n    {\n        if (!man->settings.management_over_tunnel)\n        {\n            man_connection_init(man);\n            ret = true;\n        }\n    }\n\n    return ret;\n}\n\nvoid\nmanagement_close(struct management *man)\n{\n    man_output_list_push_finalize(man); /* flush output queue */\n    man_connection_close(man);\n    man_settings_close(&man->settings);\n    man_persist_close(&man->persist);\n    free(man);\n}\n\nvoid\nmanagement_set_callback(struct management *man, const struct management_callback *cb)\n{\n    man->persist.standalone_disabled = true;\n    man->persist.callback = *cb;\n}\n\nvoid\nmanagement_clear_callback(struct management *man)\n{\n    man->persist.standalone_disabled = false;\n    man->persist.hold_release = false;\n    CLEAR(man->persist.callback);\n    man_output_list_push_finalize(man); /* flush output queue */\n}\n\nvoid\nmanagement_set_state(struct management *man, const int state, const char *detail,\n                     const in_addr_t *tun_local_ip, const struct in6_addr *tun_local_ip6,\n                     const struct openvpn_sockaddr *local, const struct openvpn_sockaddr *remote)\n{\n    if (man->persist.state\n        && (!(man->settings.flags & MF_SERVER) || state < OPENVPN_STATE_CLIENT_BASE))\n    {\n        struct gc_arena gc = gc_new();\n        struct log_entry e;\n        const char *out = NULL;\n\n        update_time();\n        CLEAR(e);\n        e.timestamp = now;\n        e.u.state = state;\n        e.string = detail;\n        if (tun_local_ip)\n        {\n            e.local_ip = *tun_local_ip;\n        }\n        if (tun_local_ip6)\n        {\n            e.local_ip6 = *tun_local_ip6;\n        }\n        if (local)\n        {\n            e.local_sock = *local;\n        }\n        if (remote)\n        {\n            e.remote_sock = *remote;\n        }\n\n        log_history_add(man->persist.state, &e);\n\n        if (man->connection.state_realtime)\n        {\n            out = log_entry_print(&e,\n                                  LOG_PRINT_STATE_PREFIX | LOG_PRINT_INT_DATE | LOG_PRINT_STATE\n                                      | LOG_PRINT_LOCAL_IP | LOG_PRINT_REMOTE_IP | LOG_PRINT_CRLF\n                                      | LOG_ECHO_TO_LOG,\n                                  &gc);\n        }\n\n        if (out)\n        {\n            man_output_list_push(man, out);\n        }\n\n        gc_free(&gc);\n    }\n}\n\nstatic bool\nenv_filter_match(const char *env_str, const int env_filter_level)\n{\n    static const char *env_names[] = { \"username=\",\n                                       \"password=\",\n                                       \"X509_0_CN=\",\n                                       \"tls_serial_\",\n                                       \"untrusted_ip=\",\n                                       \"ifconfig_local=\",\n                                       \"ifconfig_netmask=\",\n                                       \"daemon_start_time=\",\n                                       \"daemon_pid=\",\n                                       \"dev=\",\n                                       \"ifconfig_pool_remote_ip=\",\n                                       \"ifconfig_pool_netmask=\",\n                                       \"time_duration=\",\n                                       \"bytes_sent=\",\n                                       \"bytes_received=\",\n                                       \"session_id=\",\n                                       \"session_state=\" };\n\n    if (env_filter_level == 0)\n    {\n        return true;\n    }\n    else if (env_filter_level <= 1 && !strncmp(env_str, \"X509_\", 5))\n    {\n        return true;\n    }\n    else if (env_filter_level <= 2)\n    {\n        size_t i;\n        for (i = 0; i < SIZE(env_names); ++i)\n        {\n            const char *en = env_names[i];\n            const size_t len = strlen(en);\n            if (!strncmp(env_str, en, len))\n            {\n                return true;\n            }\n        }\n        return false;\n    }\n    return false;\n}\n\nstatic void\nman_output_env(const struct env_set *es, const bool tail, const int env_filter_level,\n               const char *prefix)\n{\n    if (es)\n    {\n        struct env_item *e;\n        for (e = es->list; e != NULL; e = e->next)\n        {\n            if (e->string && (!env_filter_level || env_filter_match(e->string, env_filter_level)))\n            {\n                msg(M_CLIENT, \">%s:ENV,%s\", prefix, e->string);\n            }\n        }\n    }\n    if (tail)\n    {\n        msg(M_CLIENT, \">%s:ENV,END\", prefix);\n    }\n}\n\nstatic void\nman_output_extra_env(struct management *man, const char *prefix)\n{\n    struct gc_arena gc = gc_new();\n    struct env_set *es = env_set_create(&gc);\n    if (man->persist.callback.n_clients)\n    {\n        const int nclients = (*man->persist.callback.n_clients)(man->persist.callback.arg);\n        setenv_int(es, \"n_clients\", nclients);\n    }\n    man_output_env(es, false, man->connection.env_filter_level, prefix);\n    gc_free(&gc);\n}\n\nvoid\nmanagement_up_down(struct management *man, const char *updown, const struct env_set *es)\n{\n    if (man->settings.flags & MF_UP_DOWN)\n    {\n        msg(M_CLIENT, \">UPDOWN:%s\", updown);\n        man_output_env(es, true, 0, \"UPDOWN\");\n    }\n}\n\nvoid\nmanagement_notify(struct management *man, const char *severity, const char *type, const char *text)\n{\n    msg(M_CLIENT, \">NOTIFY:%s,%s,%s\", severity, type, text);\n}\n\nvoid\nmanagement_notify_generic(struct management *man, const char *str)\n{\n    msg(M_CLIENT, \"%s\", str);\n}\n\nstatic void\nman_output_peer_info_env(struct management *man, const struct man_def_auth_context *mdac)\n{\n    char line[256];\n    if (man->persist.callback.get_peer_info)\n    {\n        const char *peer_info =\n            (*man->persist.callback.get_peer_info)(man->persist.callback.arg, mdac->cid);\n        if (peer_info)\n        {\n            struct buffer buf;\n            buf_set_read(&buf, (const uint8_t *)peer_info, strlen(peer_info));\n            while (buf_parse(&buf, '\\n', line, sizeof(line)))\n            {\n                chomp(line);\n                if (validate_peer_info_line(line))\n                {\n                    msg(M_CLIENT, \">CLIENT:ENV,%s\", line);\n                }\n                else\n                {\n                    msg(D_MANAGEMENT, \"validation failed on peer_info line received from client\");\n                }\n            }\n        }\n    }\n}\n\nvoid\nmanagement_notify_client_needing_auth(struct management *management, const unsigned int mda_key_id,\n                                      struct man_def_auth_context *mdac, const struct env_set *es)\n{\n    if (!(mdac->flags & DAF_CONNECTION_CLOSED))\n    {\n        const char *mode = \"CONNECT\";\n        if (mdac->flags & DAF_CONNECTION_ESTABLISHED)\n        {\n            mode = \"REAUTH\";\n        }\n        msg(M_CLIENT, \">CLIENT:%s,%lu,%u\", mode, mdac->cid, mda_key_id);\n        man_output_extra_env(management, \"CLIENT\");\n        if (management->connection.env_filter_level > 0)\n        {\n            man_output_peer_info_env(management, mdac);\n        }\n        man_output_env(es, true, management->connection.env_filter_level, \"CLIENT\");\n        mdac->flags |= DAF_INITIAL_AUTH;\n    }\n}\n\nvoid\nmanagement_notify_client_cr_response(unsigned mda_key_id, const struct man_def_auth_context *mdac,\n                                     const struct env_set *es, const char *response)\n{\n    struct gc_arena gc;\n    if (management)\n    {\n        gc = gc_new();\n\n        msg(M_CLIENT, \">CLIENT:CR_RESPONSE,%lu,%u,%s\", mdac->cid, mda_key_id, response);\n        man_output_extra_env(management, \"CLIENT\");\n        if (management->connection.env_filter_level > 0)\n        {\n            man_output_peer_info_env(management, mdac);\n        }\n        man_output_env(es, true, management->connection.env_filter_level, \"CLIENT\");\n        gc_free(&gc);\n    }\n}\n\nvoid\nmanagement_connection_established(struct management *management, struct man_def_auth_context *mdac,\n                                  const struct env_set *es)\n{\n    mdac->flags |= DAF_CONNECTION_ESTABLISHED;\n    msg(M_CLIENT, \">CLIENT:ESTABLISHED,%lu\", mdac->cid);\n    man_output_extra_env(management, \"CLIENT\");\n    man_output_env(es, true, management->connection.env_filter_level, \"CLIENT\");\n}\n\nvoid\nmanagement_notify_client_close(struct management *management, struct man_def_auth_context *mdac,\n                               const struct env_set *es)\n{\n    if ((mdac->flags & DAF_INITIAL_AUTH) && !(mdac->flags & DAF_CONNECTION_CLOSED))\n    {\n        msg(M_CLIENT, \">CLIENT:DISCONNECT,%lu\", mdac->cid);\n        man_output_env(es, true, management->connection.env_filter_level, \"CLIENT\");\n        mdac->flags |= DAF_CONNECTION_CLOSED;\n    }\n}\n\nvoid\nmanagement_learn_addr(struct management *management, struct man_def_auth_context *mdac,\n                      const struct mroute_addr *addr, const bool primary)\n{\n    struct gc_arena gc = gc_new();\n    if ((mdac->flags & DAF_INITIAL_AUTH) && !(mdac->flags & DAF_CONNECTION_CLOSED))\n    {\n        msg(M_CLIENT, \">CLIENT:ADDRESS,%lu,%s,%d\", mdac->cid,\n            mroute_addr_print_ex(addr, MAPF_SUBNET, &gc), BOOL_CAST(primary));\n    }\n    gc_free(&gc);\n}\n\nvoid\nmanagement_echo(struct management *man, const char *string, const bool pull)\n{\n    if (man->persist.echo)\n    {\n        struct gc_arena gc = gc_new();\n        struct log_entry e;\n        const char *out = NULL;\n\n        update_time();\n        CLEAR(e);\n        e.timestamp = now;\n        e.string = string;\n        e.u.intval = BOOL_CAST(pull);\n\n        log_history_add(man->persist.echo, &e);\n\n        if (man->connection.echo_realtime)\n        {\n            out = log_entry_print(&e,\n                                  LOG_PRINT_INT_DATE | LOG_PRINT_ECHO_PREFIX | LOG_PRINT_CRLF\n                                      | MANAGEMENT_ECHO_FLAGS,\n                                  &gc);\n        }\n\n        if (out)\n        {\n            man_output_list_push(man, out);\n        }\n\n        gc_free(&gc);\n    }\n}\n\nvoid\nmanagement_post_tunnel_open(struct management *man, const in_addr_t tun_local_ip)\n{\n    /*\n     * If we are running management over the tunnel,\n     * this is the place to initialize the connection.\n     */\n    if (man->settings.management_over_tunnel && man->connection.state == MS_INITIAL)\n    {\n        /* listen on our local TUN/TAP IP address */\n        struct in_addr ia;\n        int ret;\n        char buf[INET_ADDRSTRLEN];\n\n        ia.s_addr = htonl(tun_local_ip);\n        inet_ntop(AF_INET, &ia, buf, sizeof(buf));\n        ret =\n            openvpn_getaddrinfo(GETADDR_PASSIVE, buf, NULL, 0, NULL, AF_INET, &man->settings.local);\n        ASSERT(ret == 0);\n        man_connection_init(man);\n    }\n}\n\nvoid\nmanagement_pre_tunnel_close(struct management *man)\n{\n    if (man->settings.management_over_tunnel)\n    {\n        man_connection_close(man);\n    }\n}\n\nvoid\nmanagement_auth_failure(struct management *man, const char *type, const char *reason)\n{\n    if (reason)\n    {\n        msg(M_CLIENT, \">PASSWORD:Verification Failed: '%s' ['%s']\", type, reason);\n    }\n    else\n    {\n        msg(M_CLIENT, \">PASSWORD:Verification Failed: '%s'\", type);\n    }\n}\n\nvoid\nmanagement_auth_token(struct management *man, const char *token)\n{\n    msg(M_CLIENT, \">PASSWORD:Auth-Token:%s\", token);\n}\n\nstatic inline bool\nman_persist_state(unsigned int *persistent, const int n)\n{\n    if (persistent)\n    {\n        if (*persistent == (unsigned int)n)\n        {\n            return false;\n        }\n        *persistent = n;\n    }\n    return true;\n}\n\n#ifdef _WIN32\n\nvoid\nmanagement_socket_set(struct management *man, struct event_set *es, void *arg,\n                      unsigned int *persistent)\n{\n    if (man->connection.state != MS_INITIAL)\n    {\n        event_t ev = net_event_win32_get_event(&man->connection.ne32);\n        net_event_win32_reset_write(&man->connection.ne32);\n\n        switch (man->connection.state)\n        {\n            case MS_LISTEN:\n                if (man_persist_state(persistent, 1))\n                {\n                    event_ctl(es, ev, EVENT_READ, arg);\n                }\n                break;\n\n            case MS_CC_WAIT_READ:\n                if (man_persist_state(persistent, 2))\n                {\n                    event_ctl(es, ev, EVENT_READ, arg);\n                }\n                break;\n\n            case MS_CC_WAIT_WRITE:\n                if (man_persist_state(persistent, 3))\n                {\n                    event_ctl(es, ev, EVENT_READ | EVENT_WRITE, arg);\n                }\n                break;\n\n            default:\n                ASSERT(0);\n        }\n    }\n}\n\nvoid\nmanagement_io(struct management *man)\n{\n    if (man->connection.state != MS_INITIAL)\n    {\n        long net_events;\n        net_event_win32_reset(&man->connection.ne32);\n        net_events = net_event_win32_get_event_mask(&man->connection.ne32);\n\n        if (net_events & FD_CLOSE)\n        {\n            man_reset_client_socket(man, false);\n        }\n        else\n        {\n            if (man->connection.state == MS_LISTEN)\n            {\n                if (net_events & FD_ACCEPT)\n                {\n                    man_accept(man);\n                    net_event_win32_clear_selected_events(&man->connection.ne32, FD_ACCEPT);\n                }\n            }\n            else if (man->connection.state == MS_CC_WAIT_READ\n                     || man->connection.state == MS_CC_WAIT_WRITE)\n            {\n                if (net_events & FD_READ)\n                {\n                    while (man_read(man) > 0)\n                    {\n                    }\n                    net_event_win32_clear_selected_events(&man->connection.ne32, FD_READ);\n                }\n\n                if (net_events & FD_WRITE)\n                {\n                    ssize_t status = man_write(man);\n                    if (status < 0 && WSAGetLastError() == WSAEWOULDBLOCK)\n                    {\n                        net_event_win32_clear_selected_events(&man->connection.ne32, FD_WRITE);\n                    }\n                }\n            }\n        }\n    }\n}\n\n#else  /* ifdef _WIN32 */\n\nvoid\nmanagement_socket_set(struct management *man, struct event_set *es, void *arg,\n                      unsigned int *persistent)\n{\n    switch (man->connection.state)\n    {\n        case MS_LISTEN:\n            if (man_persist_state(persistent, 1))\n            {\n                event_ctl(es, man->connection.sd_top, EVENT_READ, arg);\n            }\n            break;\n\n        case MS_CC_WAIT_READ:\n            if (man_persist_state(persistent, 2))\n            {\n                event_ctl(es, man->connection.sd_cli, EVENT_READ, arg);\n            }\n            break;\n\n        case MS_CC_WAIT_WRITE:\n            if (man_persist_state(persistent, 3))\n            {\n                event_ctl(es, man->connection.sd_cli, EVENT_WRITE, arg);\n            }\n            break;\n\n        case MS_INITIAL:\n            break;\n\n        default:\n            ASSERT(0);\n    }\n}\n\nvoid\nmanagement_io(struct management *man)\n{\n    switch (man->connection.state)\n    {\n        case MS_LISTEN:\n            man_accept(man);\n            break;\n\n        case MS_CC_WAIT_READ:\n            man_read(man);\n            break;\n\n        case MS_CC_WAIT_WRITE:\n            man_write(man);\n            break;\n\n        case MS_INITIAL:\n            break;\n\n        default:\n            ASSERT(0);\n    }\n}\n\n#endif /* ifdef _WIN32 */\n\nstatic inline bool\nman_standalone_ok(const struct management *man)\n{\n    return !man->settings.management_over_tunnel && man->connection.state != MS_INITIAL;\n}\n\nstatic bool\nman_check_for_signals(volatile int *signal_received)\n{\n    if (signal_received)\n    {\n        get_signal(signal_received);\n        if (*signal_received)\n        {\n            return true;\n        }\n    }\n    return false;\n}\n\n/*\n * Wait for socket I/O when outside primary event loop\n */\nstatic int\nman_block(struct management *man, volatile int *signal_received, const time_t expire)\n{\n    struct timeval tv;\n    struct event_set_return esr;\n    int status = -1;\n\n    if (man_standalone_ok(man))\n    {\n        /* expire time can be already overdue, for this case init zero\n         * timeout to avoid waiting first time and exit loop early with\n         * either obtained event or timeout.\n         */\n        tv.tv_usec = 0;\n        tv.tv_sec = 0;\n\n        while (true)\n        {\n            event_reset(man->connection.es);\n            management_socket_set(man, man->connection.es, NULL, NULL);\n            if (man_check_for_signals(signal_received))\n            {\n                status = -1;\n                break;\n            }\n            status = event_wait(man->connection.es, &tv, &esr, 1);\n            update_time();\n            if (man_check_for_signals(signal_received))\n            {\n                status = -1;\n                break;\n            }\n\n            if (status > 0)\n            {\n                break;\n            }\n            else if (expire && now >= expire)\n            {\n                /* set SIGINT signal if expiration time exceeded */\n                status = 0;\n                if (signal_received)\n                {\n                    *signal_received = SIGINT;\n                }\n                break;\n            }\n\n            /* wait one second more */\n            tv.tv_sec = 1;\n            tv.tv_usec = 0;\n        }\n    }\n    return status;\n}\n\n/*\n * Perform management socket output outside primary event loop\n */\nstatic void\nman_output_standalone(struct management *man, volatile int *signal_received)\n{\n    if (man_standalone_ok(man))\n    {\n        while (man->connection.state == MS_CC_WAIT_WRITE)\n        {\n            management_io(man);\n            if (man->connection.state == MS_CC_WAIT_WRITE)\n            {\n                man_block(man, signal_received, 0);\n            }\n            if (signal_received && *signal_received)\n            {\n                break;\n            }\n        }\n    }\n}\n\n/*\n * Process management event loop outside primary event loop\n */\nstatic int\nman_standalone_event_loop(struct management *man, volatile int *signal_received,\n                          const time_t expire)\n{\n    int status = -1;\n    if (man_standalone_ok(man))\n    {\n        status = man_block(man, signal_received, expire);\n        if (status > 0)\n        {\n            management_io(man);\n        }\n    }\n    return status;\n}\n\n#define MWCC_PASSWORD_WAIT (1 << 0)\n#define MWCC_HOLD_WAIT     (1 << 1)\n#define MWCC_OTHER_WAIT    (1 << 2)\n\n/*\n * Block until client connects\n */\nstatic void\nman_wait_for_client_connection(struct management *man, volatile int *signal_received,\n                               const time_t expire, unsigned int flags)\n{\n    ASSERT(man_standalone_ok(man));\n    if (man->connection.state == MS_LISTEN)\n    {\n        if (flags & MWCC_PASSWORD_WAIT)\n        {\n            msg(D_MANAGEMENT, \"Need password(s) from management interface, waiting...\");\n        }\n        if (flags & MWCC_HOLD_WAIT)\n        {\n            msg(D_MANAGEMENT, \"Need hold release from management interface, waiting...\");\n        }\n        if (flags & MWCC_OTHER_WAIT)\n        {\n            msg(D_MANAGEMENT, \"Need information from management interface, waiting...\");\n        }\n        do\n        {\n            man_standalone_event_loop(man, signal_received, expire);\n            if (signal_received && *signal_received)\n            {\n                break;\n            }\n        } while (man->connection.state == MS_LISTEN || man_password_needed(man));\n    }\n}\n\n/*\n * Process the management event loop for sec seconds\n */\nvoid\nmanagement_event_loop_n_seconds(struct management *man, int sec)\n{\n    if (man_standalone_ok(man))\n    {\n        volatile int signal_received = 0;\n        const bool standalone_disabled_save = man->persist.standalone_disabled;\n        time_t expire = 0;\n\n        /* This is so M_CLIENT messages will be correctly passed through msg() */\n        man->persist.standalone_disabled = false;\n\n        /* set expire time */\n        update_time();\n        if (sec >= 0)\n        {\n            expire = now + sec;\n        }\n\n        /* if no client connection, wait for one */\n        man_wait_for_client_connection(man, &signal_received, expire, 0);\n        if (signal_received)\n        {\n            return;\n        }\n\n        /* run command processing event loop */\n        do\n        {\n            man_standalone_event_loop(man, &signal_received, expire);\n            if (!signal_received)\n            {\n                man_check_for_signals(&signal_received);\n            }\n            if (signal_received)\n            {\n                return;\n            }\n            update_time();\n        } while (expire && expire > now);\n\n        /* revert state */\n        man->persist.standalone_disabled = standalone_disabled_save;\n    }\n    else if (sec > 0)\n    {\n        sleep(sec);\n    }\n}\n\n/*\n * Get a username/password from management channel in standalone mode.\n */\nbool\nmanagement_query_user_pass(struct management *man, struct user_pass *up, const char *type,\n                           const unsigned int flags, const char *static_challenge)\n{\n    struct gc_arena gc = gc_new();\n    bool ret = false;\n\n    if (man_standalone_ok(man))\n    {\n        volatile int signal_received = 0;\n        const bool standalone_disabled_save = man->persist.standalone_disabled;\n        struct buffer alert_msg = alloc_buf_gc(128, &gc);\n        const char *alert_type = NULL;\n        const char *prefix = NULL;\n        unsigned int up_query_mode = 0;\n        const char *sc = NULL;\n        ret = true;\n        /* This is so M_CLIENT messages will be correctly passed through msg() */\n        man->persist.standalone_disabled = false;\n        man->persist.special_state_msg = NULL;\n\n        CLEAR(man->connection.up_query);\n\n        if (flags & GET_USER_PASS_NEED_OK)\n        {\n            up_query_mode = UP_QUERY_NEED_OK;\n            prefix = \"NEED-OK\";\n            alert_type = \"confirmation\";\n        }\n        else if (flags & GET_USER_PASS_NEED_STR)\n        {\n            up_query_mode = UP_QUERY_NEED_STR;\n            prefix = \"NEED-STR\";\n            alert_type = \"string\";\n        }\n        else if (flags & GET_USER_PASS_PASSWORD_ONLY)\n        {\n            up_query_mode = UP_QUERY_PASS;\n            prefix = \"PASSWORD\";\n            alert_type = \"password\";\n        }\n        else\n        {\n            up_query_mode = UP_QUERY_USER_PASS;\n            prefix = \"PASSWORD\";\n            alert_type = \"username/password\";\n            if (static_challenge)\n            {\n                sc = static_challenge;\n            }\n        }\n        buf_printf(&alert_msg, \">%s:Need '%s' %s\", prefix, type, alert_type);\n\n        if (flags & (GET_USER_PASS_NEED_OK | GET_USER_PASS_NEED_STR))\n        {\n            buf_printf(&alert_msg, \" MSG:%s\", up->username);\n        }\n\n        if (sc)\n        {\n            buf_printf(&alert_msg, \" SC:%d,%s\",\n                       BOOL_CAST(flags & GET_USER_PASS_STATIC_CHALLENGE_ECHO)\n                           | (BOOL_CAST(flags & GET_USER_PASS_STATIC_CHALLENGE_CONCAT) << 1),\n                       sc);\n        }\n\n        man_wait_for_client_connection(man, &signal_received, 0, MWCC_PASSWORD_WAIT);\n        if (signal_received)\n        {\n            ret = false;\n        }\n\n        if (ret)\n        {\n            man->persist.special_state_msg = BSTR(&alert_msg);\n            msg(M_CLIENT, \"%s\", man->persist.special_state_msg);\n\n            /* tell command line parser which info we need */\n            man->connection.up_query_mode = up_query_mode;\n            man->connection.up_query_type = type;\n\n            /* run command processing event loop until we get our username/password/response */\n            do\n            {\n                man_standalone_event_loop(man, &signal_received, 0);\n                if (!signal_received)\n                {\n                    man_check_for_signals(&signal_received);\n                }\n                if (signal_received)\n                {\n                    ret = false;\n                    break;\n                }\n            } while (!man->connection.up_query.defined);\n        }\n\n        /* revert state */\n        man->connection.up_query_mode = UP_QUERY_DISABLED;\n        man->connection.up_query_type = NULL;\n        man->persist.standalone_disabled = standalone_disabled_save;\n        man->persist.special_state_msg = NULL;\n\n        /* pass through blank passwords */\n        if (!strcmp(man->connection.up_query.password, blank_up))\n        {\n            CLEAR(man->connection.up_query.password);\n        }\n\n        /*\n         * Transfer u/p to return object, zero any record\n         * we hold in the management object.\n         */\n        if (ret)\n        {\n            /* preserve caller's settings */\n            man->connection.up_query.nocache = up->nocache;\n            *up = man->connection.up_query;\n        }\n        secure_memzero(&man->connection.up_query, sizeof(man->connection.up_query));\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n\nstatic int\nmanagement_query_multiline(struct management *man, const char *b64_data, const char *prompt,\n                           const char *cmd, int *state, struct buffer_list **input)\n{\n    struct gc_arena gc = gc_new();\n    int ret = 0;\n    volatile int signal_received = 0;\n    struct buffer alert_msg = clear_buf();\n    const bool standalone_disabled_save = man->persist.standalone_disabled;\n    struct man_connection *mc = &man->connection;\n\n    if (man_standalone_ok(man))\n    {\n        /* This is so M_CLIENT messages will be correctly passed through msg() */\n        man->persist.standalone_disabled = false;\n        man->persist.special_state_msg = NULL;\n\n        *state = EKS_SOLICIT;\n\n        if (b64_data)\n        {\n            alert_msg = alloc_buf_gc(strlen(b64_data) + strlen(prompt) + 3, &gc);\n            buf_printf(&alert_msg, \">%s:%s\", prompt, b64_data);\n        }\n        else\n        {\n            alert_msg = alloc_buf_gc(strlen(prompt) + 3, &gc);\n            buf_printf(&alert_msg, \">%s\", prompt);\n        }\n\n        man_wait_for_client_connection(man, &signal_received, 0, MWCC_OTHER_WAIT);\n\n        if (signal_received)\n        {\n            goto done;\n        }\n\n        man->persist.special_state_msg = BSTR(&alert_msg);\n        msg(M_CLIENT, \"%s\", man->persist.special_state_msg);\n\n        /* run command processing event loop until we get our signature */\n        do\n        {\n            man_standalone_event_loop(man, &signal_received, 0);\n            if (!signal_received)\n            {\n                man_check_for_signals(&signal_received);\n            }\n            if (signal_received)\n            {\n                goto done;\n            }\n        } while (*state != EKS_READY);\n\n        ret = 1;\n    }\n\ndone:\n    if (*state == EKS_READY && ret)\n    {\n        msg(M_CLIENT, \"SUCCESS: %s command succeeded\", cmd);\n    }\n    else if (*state == EKS_INPUT || *state == EKS_READY)\n    {\n        msg(M_CLIENT, \"ERROR: %s command failed\", cmd);\n    }\n\n    /* revert state */\n    man->persist.standalone_disabled = standalone_disabled_save;\n    man->persist.special_state_msg = NULL;\n    in_extra_reset(mc, IER_RESET);\n    *state = EKS_UNDEF;\n\n    gc_free(&gc);\n    return ret;\n}\n\nstatic char *\n/* returns allocated base64 signature */\nmanagement_query_multiline_flatten_newline(struct management *man, const char *b64_data,\n                                           const char *prompt, const char *cmd, int *state,\n                                           struct buffer_list **input)\n{\n    int ok;\n    char *result = NULL;\n    struct buffer *buf;\n\n    ok = management_query_multiline(man, b64_data, prompt, cmd, state, input);\n    if (ok && buffer_list_defined(*input))\n    {\n        buffer_list_aggregate_separator(*input, 10000, \"\\n\");\n        buf = buffer_list_peek(*input);\n        if (buf && BLEN(buf) > 0)\n        {\n            result = (char *)malloc(BLENZ(buf) + 1);\n            check_malloc_return(result);\n            memcpy(result, buf->data, BLENZ(buf));\n            result[BLEN(buf)] = '\\0';\n        }\n    }\n\n    buffer_list_free(*input);\n    *input = NULL;\n\n    return result;\n}\n\nstatic char *\n/* returns allocated base64 signature */\nmanagement_query_multiline_flatten(struct management *man, const char *b64_data, const char *prompt,\n                                   const char *cmd, int *state, struct buffer_list **input)\n{\n    int ok;\n    char *result = NULL;\n    struct buffer *buf;\n\n    ok = management_query_multiline(man, b64_data, prompt, cmd, state, input);\n    if (ok && buffer_list_defined(*input))\n    {\n        buffer_list_aggregate(*input, 2048);\n        buf = buffer_list_peek(*input);\n        if (buf && BLEN(buf) > 0)\n        {\n            result = (char *)malloc(BLENZ(buf) + 1);\n            check_malloc_return(result);\n            memcpy(result, buf->data, BLENZ(buf));\n            result[BLEN(buf)] = '\\0';\n        }\n    }\n\n    buffer_list_free(*input);\n    *input = NULL;\n\n    return result;\n}\n\nchar *\n/* returns allocated base64 signature */\nmanagement_query_pk_sig(struct management *man, const char *b64_data, const char *algorithm)\n{\n    const char *prompt = \"PK_SIGN\";\n    const char *desc = \"pk-sign\";\n    struct buffer buf_data = alloc_buf(strlen(b64_data) + strlen(algorithm) + 20);\n\n    if (man->connection.client_version <= MCV_DEFAULT)\n    {\n        prompt = \"RSA_SIGN\";\n        desc = \"rsa-sign\";\n    }\n\n    buf_write(&buf_data, b64_data, (int)strlen(b64_data));\n    if (man->connection.client_version >= MCV_PKSIGN_ALG)\n    {\n        buf_write(&buf_data, \",\", (int)strlen(\",\"));\n        buf_write(&buf_data, algorithm, (int)strlen(algorithm));\n    }\n    char *ret = management_query_multiline_flatten(man, (char *)buf_bptr(&buf_data), prompt, desc,\n                                                   &man->connection.ext_key_state,\n                                                   &man->connection.ext_key_input);\n    free_buf(&buf_data);\n    return ret;\n}\n\nchar *\nmanagement_query_cert(struct management *man, const char *cert_name)\n{\n    const char prompt_1[] = \"NEED-CERTIFICATE:\";\n    struct buffer buf_prompt = alloc_buf(strlen(cert_name) + 20);\n    buf_write(&buf_prompt, prompt_1, strlen(prompt_1));\n    buf_write(&buf_prompt, cert_name, strlen(cert_name) + 1); /* +1 for \\0 */\n\n    char *result;\n    result = management_query_multiline_flatten_newline(\n        management, NULL, (char *)buf_bptr(&buf_prompt), \"certificate\",\n        &man->connection.ext_cert_state, &man->connection.ext_cert_input);\n    free_buf(&buf_prompt);\n    return result;\n}\n\n/*\n * Return true if management_hold() would block\n */\nbool\nmanagement_would_hold(struct management *man)\n{\n    return (man->settings.flags & MF_HOLD) && !man->persist.hold_release && man_standalone_ok(man);\n}\n\n/*\n * If the hold flag is enabled, hibernate until a management client releases the hold.\n * Return true if the caller should not sleep for an additional time interval.\n */\nbool\nmanagement_hold(struct management *man, int holdtime)\n{\n    if (management_would_hold(man))\n    {\n        volatile int signal_received = 0;\n        const bool standalone_disabled_save = man->persist.standalone_disabled;\n        struct gc_arena gc = gc_new();\n\n        man->persist.standalone_disabled =\n            false; /* This is so M_CLIENT messages will be correctly passed through msg() */\n        man->persist.special_state_msg = NULL;\n        man->settings.mansig |= MANSIG_IGNORE_USR1_HUP;\n\n        man_wait_for_client_connection(man, &signal_received, 0, MWCC_HOLD_WAIT);\n\n        if (!signal_received)\n        {\n            struct buffer out = alloc_buf_gc(128, &gc);\n            buf_printf(&out, \">HOLD:Waiting for hold release:%d\", holdtime);\n            man->persist.special_state_msg = BSTR(&out);\n            msg(M_CLIENT, \"%s\", man->persist.special_state_msg);\n\n            /* run command processing event loop until we get our username/password */\n            do\n            {\n                man_standalone_event_loop(man, &signal_received, 0);\n                if (!signal_received)\n                {\n                    man_check_for_signals(&signal_received);\n                }\n                if (signal_received)\n                {\n                    break;\n                }\n            } while (!man->persist.hold_release);\n        }\n\n        /* revert state */\n        man->persist.standalone_disabled = standalone_disabled_save;\n        man->persist.special_state_msg = NULL;\n        man->settings.mansig &= ~MANSIG_IGNORE_USR1_HUP;\n\n        gc_free(&gc);\n        return true;\n    }\n    return false;\n}\n\n/*\n * struct command_line\n */\n\nstruct command_line *\ncommand_line_new(const size_t buf_len)\n{\n    struct command_line *cl;\n    ALLOC_OBJ_CLEAR(cl, struct command_line);\n    cl->buf = alloc_buf(buf_len);\n    cl->residual = alloc_buf(buf_len);\n    return cl;\n}\n\nvoid\ncommand_line_reset(struct command_line *cl)\n{\n    buf_clear(&cl->buf);\n    buf_clear(&cl->residual);\n}\n\nvoid\ncommand_line_free(struct command_line *cl)\n{\n    if (!cl)\n    {\n        return;\n    }\n    command_line_reset(cl);\n    free_buf(&cl->buf);\n    free_buf(&cl->residual);\n    free(cl);\n}\n\nvoid\ncommand_line_add(struct command_line *cl, const unsigned char *buf, const size_t len)\n{\n    for (size_t i = 0; i < len; ++i)\n    {\n        if (buf[i] && char_class(buf[i], (CC_PRINT | CC_NEWLINE)))\n        {\n            if (!buf_write_u8(&cl->buf, buf[i]))\n            {\n                buf_clear(&cl->buf);\n            }\n        }\n    }\n}\n\nconst char *\ncommand_line_get(struct command_line *cl)\n{\n    const char *ret = NULL;\n\n    int i = buf_substring_len(&cl->buf, '\\n');\n    if (i >= 0)\n    {\n        buf_copy_excess(&cl->residual, &cl->buf, i);\n        buf_chomp(&cl->buf);\n        ret = BSTR(&cl->buf);\n    }\n    return ret;\n}\n\nvoid\ncommand_line_next(struct command_line *cl)\n{\n    buf_clear(&cl->buf);\n    buf_copy(&cl->buf, &cl->residual);\n    buf_clear(&cl->residual);\n}\n\n/*\n * struct log_entry\n */\n\nconst char *\nlog_entry_print(const struct log_entry *e, unsigned int flags, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(ERR_BUF_SIZE, gc);\n    if (flags & LOG_FATAL_NOTIFY)\n    {\n        buf_printf(&out, \">FATAL:\");\n    }\n    if (flags & LOG_PRINT_LOG_PREFIX)\n    {\n        buf_printf(&out, \">LOG:\");\n    }\n    if (flags & LOG_PRINT_ECHO_PREFIX)\n    {\n        buf_printf(&out, \">ECHO:\");\n    }\n    if (flags & LOG_PRINT_STATE_PREFIX)\n    {\n        buf_printf(&out, \">STATE:\");\n    }\n    if (flags & LOG_PRINT_INT_DATE)\n    {\n        buf_printf(&out, \"%u,\", (unsigned int)e->timestamp);\n    }\n    if (flags & LOG_PRINT_MSG_FLAGS)\n    {\n        buf_printf(&out, \"%s,\", msg_flags_string(e->u.msg_flags, gc));\n    }\n    if (flags & LOG_PRINT_STATE)\n    {\n        buf_printf(&out, \"%s,\", man_state_name(e->u.state));\n    }\n    if (flags & LOG_PRINT_INTVAL)\n    {\n        buf_printf(&out, \"%d,\", e->u.intval);\n    }\n    if (e->string)\n    {\n        buf_printf(&out, \"%s\", e->string);\n    }\n    if (flags & LOG_PRINT_LOCAL_IP)\n    {\n        buf_printf(&out, \",%s\", print_in_addr_t(e->local_ip, IA_EMPTY_IF_UNDEF, gc));\n    }\n    if (flags & LOG_PRINT_REMOTE_IP)\n    {\n        buf_printf(&out, \",%s\",\n                   (!addr_defined(&e->remote_sock)\n                        ? \",\"\n                        : print_sockaddr_ex(&e->remote_sock.addr.sa, \",\",\n                                            PS_DONT_SHOW_FAMILY | PS_SHOW_PORT, gc)));\n        buf_printf(&out, \",%s\",\n                   (!addr_defined(&e->local_sock)\n                        ? \",\"\n                        : print_sockaddr_ex(&e->local_sock.addr.sa, \",\",\n                                            PS_DONT_SHOW_FAMILY | PS_SHOW_PORT, gc)));\n    }\n    if (flags & LOG_PRINT_LOCAL_IP && !IN6_IS_ADDR_UNSPECIFIED(&e->local_ip6))\n    {\n        buf_printf(&out, \",%s\", print_in6_addr(e->local_ip6, IA_EMPTY_IF_UNDEF, gc));\n    }\n    if (flags & LOG_ECHO_TO_LOG)\n    {\n        msg(D_MANAGEMENT, \"MANAGEMENT: %s\", BSTR(&out));\n    }\n    if (flags & LOG_PRINT_CRLF)\n    {\n        buf_printf(&out, \"\\r\\n\");\n    }\n    return BSTR(&out);\n}\n\nstatic void\nlog_entry_free_contents(struct log_entry *e)\n{\n    /* Cast away constness of const char* */\n    free((char *)e->string);\n    CLEAR(*e);\n}\n\n/*\n * struct log_history\n */\n\nstatic inline int\nlog_index(const struct log_history *h, int i)\n{\n    return modulo_add(h->base, i, h->capacity);\n}\n\nstatic void\nlog_history_obj_init(struct log_history *h, int capacity)\n{\n    CLEAR(*h);\n    h->capacity = capacity;\n    ALLOC_ARRAY_CLEAR(h->array, struct log_entry, capacity);\n}\n\nstruct log_history *\nlog_history_init(const int capacity)\n{\n    struct log_history *h;\n    ASSERT(capacity > 0);\n    ALLOC_OBJ(h, struct log_history);\n    log_history_obj_init(h, capacity);\n    return h;\n}\n\nstatic void\nlog_history_free_contents(struct log_history *h)\n{\n    int i;\n    for (i = 0; i < h->size; ++i)\n    {\n        log_entry_free_contents(&h->array[log_index(h, i)]);\n    }\n    free(h->array);\n}\n\nvoid\nlog_history_close(struct log_history *h)\n{\n    log_history_free_contents(h);\n    free(h);\n}\n\nvoid\nlog_history_add(struct log_history *h, const struct log_entry *le)\n{\n    struct log_entry *e;\n    ASSERT(h->size >= 0 && h->size <= h->capacity);\n    if (h->size == h->capacity)\n    {\n        e = &h->array[h->base];\n        log_entry_free_contents(e);\n        h->base = log_index(h, 1);\n    }\n    else\n    {\n        e = &h->array[log_index(h, h->size)];\n        ++h->size;\n    }\n\n    *e = *le;\n    e->string = string_alloc(le->string, NULL);\n}\n\nvoid\nlog_history_resize(struct log_history *h, const int capacity)\n{\n    if (capacity != h->capacity)\n    {\n        struct log_history newlog;\n        int i;\n\n        ASSERT(capacity > 0);\n        log_history_obj_init(&newlog, capacity);\n\n        for (i = 0; i < h->size; ++i)\n        {\n            log_history_add(&newlog, &h->array[log_index(h, i)]);\n        }\n\n        log_history_free_contents(h);\n        *h = newlog;\n    }\n}\n\nconst struct log_entry *\nlog_history_ref(const struct log_history *h, const int index)\n{\n    if (index >= 0 && index < h->size)\n    {\n        return &h->array[log_index(h, (h->size - 1) - index)];\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\nvoid\nmanagement_sleep(const int n)\n{\n    if (n < 0)\n    {\n        return;\n    }\n    else if (management)\n    {\n        management_event_loop_n_seconds(management, n);\n    }\n    else\n    {\n#ifdef _WIN32\n        win32_sleep(n);\n#else\n        if (n > 0)\n        {\n            sleep(n);\n        }\n#endif\n    }\n}\n\nvoid\nmanagement_check_bytecount_client(struct context *c, struct management *man, struct timeval *timeval)\n{\n    if (man->persist.callback.flags & MCF_SERVER)\n    {\n        return;\n    }\n\n    if (event_timeout_trigger(&man->connection.bytecount_update_interval, timeval, ETT_DEFAULT))\n    {\n        if (dco_enabled(&c->options))\n        {\n            if (dco_get_peer_stats(c, true) < 0)\n            {\n                return;\n            }\n        }\n\n        man_bytecount_output_client(c->c2.dco_read_bytes + man->persist.bytes_in + c->c2.link_read_bytes,\n                                    c->c2.dco_write_bytes + man->persist.bytes_out + c->c2.link_write_bytes);\n    }\n}\n\nvoid\nmanagement_check_bytecount_server(struct multi_context *multi, struct timeval *timeval)\n{\n    if (!(management->persist.callback.flags & MCF_SERVER))\n    {\n        return;\n    }\n\n    if (event_timeout_trigger(&management->connection.bytecount_update_interval, timeval, ETT_DEFAULT))\n    {\n        /* fetch counters from dco */\n        if (dco_enabled(&multi->top.options))\n        {\n            if (dco_get_peer_stats_multi(&multi->top.c1.tuntap->dco, true) < 0)\n            {\n                return;\n            }\n        }\n\n        /* iterate over peers and report counters for each connected peer */\n        struct hash_iterator hi;\n        struct hash_element *he;\n        hash_iterator_init(multi->hash, &hi);\n        while ((he = hash_iterator_next(&hi)))\n        {\n            struct multi_instance *mi = (struct multi_instance *)he->value;\n            struct context_2 *c2 = &mi->context.c2;\n\n            if ((c2->mda_context.flags & (DAF_CONNECTION_ESTABLISHED | DAF_CONNECTION_CLOSED)) == DAF_CONNECTION_ESTABLISHED)\n            {\n                man_bytecount_output_server(c2->dco_read_bytes + c2->link_read_bytes, c2->dco_write_bytes + c2->link_write_bytes, &c2->mda_context);\n            }\n        }\n        hash_iterator_free(&hi);\n    }\n}\n\n/* context_2 stats are reset on reconnect. Since client expects stats\n * to be preserved across reconnects, we need to save context_2\n * stats before tearing the tunnel down.\n */\nvoid\nman_persist_client_stats(struct management *man, struct context *c)\n{\n    man->persist.bytes_in += c->c2.link_read_bytes;\n    man->persist.bytes_out += c->c2.link_write_bytes;\n\n    /* no need to raise SIGUSR1 on error since we are already closing the instance */\n    if (dco_enabled(&c->options) && (dco_get_peer_stats(c, false) == 0))\n    {\n        man->persist.bytes_in += c->c2.dco_read_bytes;\n        man->persist.bytes_out += c->c2.dco_write_bytes;\n    }\n}\n\n#else /* ifdef ENABLE_MANAGEMENT */\n\n#include \"win32.h\"\nvoid\nmanagement_sleep(const int n)\n{\n#ifdef _WIN32\n    win32_sleep(n);\n#else\n    if (n > 0)\n    {\n        sleep(n);\n    }\n#endif /* ifdef _WIN32 */\n}\n\n#endif /* ENABLE_MANAGEMENT */\n"
  },
  {
    "path": "src/openvpn/manage.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef MANAGE_H\n#define MANAGE_H\n\n/* management_open flags */\n#define MF_SERVER                 (1u << 0)\n#define MF_QUERY_PASSWORDS        (1u << 1)\n#define MF_HOLD                   (1u << 2)\n#define MF_SIGNAL                 (1u << 3)\n#define MF_FORGET_DISCONNECT      (1u << 4)\n#define MF_CONNECT_AS_CLIENT      (1u << 5)\n#define MF_CLIENT_AUTH            (1u << 6)\n/* #define MF_CLIENT_PF              (1u << 7) *REMOVED FEATURE* */\n#define MF_UNIX_SOCK              (1u << 8)\n#define MF_EXTERNAL_KEY           (1u << 9)\n#define MF_EXTERNAL_KEY_NOPADDING (1u << 10)\n#define MF_EXTERNAL_KEY_PKCS1PAD  (1u << 11)\n#define MF_UP_DOWN                (1u << 12)\n#define MF_QUERY_REMOTE           (1u << 13)\n#define MF_QUERY_PROXY            (1u << 14)\n#define MF_EXTERNAL_CERT          (1u << 15)\n#define MF_EXTERNAL_KEY_PSSPAD    (1u << 16)\n#define MF_EXTERNAL_KEY_DIGEST    (1u << 17)\n\n#ifdef ENABLE_MANAGEMENT\n\n#include \"misc.h\"\n#include \"event.h\"\n#include \"socket_util.h\"\n#include \"mroute.h\"\n\n#define MANAGEMENT_VERSION                  5\n#define MANAGEMENT_N_PASSWORD_RETRIES       3\n#define MANAGEMENT_LOG_HISTORY_INITIAL_SIZE 100\n#define MANAGEMENT_ECHO_BUFFER_SIZE         100\n#define MANAGEMENT_STATE_BUFFER_SIZE        100\n\n/*\n * Management-interface-based deferred authentication\n */\nstruct man_def_auth_context\n{\n    unsigned long cid;\n\n#define DAF_CONNECTION_ESTABLISHED (1u << 0)\n#define DAF_CONNECTION_CLOSED      (1u << 1)\n#define DAF_INITIAL_AUTH           (1u << 2)\n    unsigned int flags;\n\n    unsigned int mda_key_id_counter;\n};\n\n/*\n * Manage build-up of command line\n */\nstruct command_line\n{\n    struct buffer buf;\n    struct buffer residual;\n};\n\nstruct command_line *command_line_new(const size_t buf_len);\n\nvoid command_line_free(struct command_line *cl);\n\nvoid command_line_add(struct command_line *cl, const unsigned char *buf, const size_t len);\n\nconst char *command_line_get(struct command_line *cl);\n\nvoid command_line_reset(struct command_line *cl);\n\nvoid command_line_next(struct command_line *cl);\n\n/*\n * Manage log file history\n */\n\nunion log_entry_union\n{\n    unsigned int msg_flags;\n    int state;\n    int intval;\n};\n\nstruct log_entry\n{\n    time_t timestamp;\n    const char *string;\n    in_addr_t local_ip;\n    struct in6_addr local_ip6;\n    struct openvpn_sockaddr local_sock;\n    struct openvpn_sockaddr remote_sock;\n    union log_entry_union u;\n};\n\n#define LOG_PRINT_LOG_PREFIX   (1u << 0)\n#define LOG_PRINT_ECHO_PREFIX  (1u << 1)\n#define LOG_PRINT_STATE_PREFIX (1u << 2)\n\n#define LOG_PRINT_INT_DATE  (1u << 3)\n#define LOG_PRINT_MSG_FLAGS (1u << 4)\n#define LOG_PRINT_STATE     (1u << 5)\n#define LOG_PRINT_LOCAL_IP  (1u << 6)\n\n#define LOG_PRINT_CRLF   (1u << 7)\n#define LOG_FATAL_NOTIFY (1u << 8)\n\n#define LOG_PRINT_INTVAL (1u << 9)\n\n#define LOG_PRINT_REMOTE_IP (1u << 10)\n\n#define LOG_ECHO_TO_LOG (1u << 11)\n\nconst char *log_entry_print(const struct log_entry *e, unsigned int flags, struct gc_arena *gc);\n\nstruct log_history\n{\n    int base;\n    int size;\n    int capacity;\n    struct log_entry *array;\n};\n\nstruct log_history *log_history_init(const int capacity);\n\nvoid log_history_close(struct log_history *h);\n\nvoid log_history_add(struct log_history *h, const struct log_entry *le);\n\nvoid log_history_resize(struct log_history *h, const int capacity);\n\nconst struct log_entry *log_history_ref(const struct log_history *h, const int index);\n\nstatic inline int\nlog_history_size(const struct log_history *h)\n{\n    return h->size;\n}\n\nstatic inline int\nlog_history_capacity(const struct log_history *h)\n{\n    return h->capacity;\n}\n\n/*\n * Callbacks for 'status' and 'kill' commands.\n * Also for management-based deferred authentication and packet filter.\n */\nstruct management_callback\n{\n    void *arg;\n\n#define MCF_SERVER (1u << 0) /* is OpenVPN being run as a server? */\n    unsigned int flags;\n\n    void (*status)(void *arg, const int version, struct status_output *so);\n    void (*show_net)(void *arg, const msglvl_t msglevel);\n    int (*kill_by_cn)(void *arg, const char *common_name);\n    int (*kill_by_addr)(void *arg, const in_addr_t addr, const uint16_t port, const uint8_t proto);\n    void (*delete_event)(void *arg, event_t event);\n    int (*n_clients)(void *arg);\n    bool (*send_cc_message)(void *arg, const char *message, const char *parameter);\n    bool (*kill_by_cid)(void *arg, const unsigned long cid, const char *kill_msg);\n    bool (*client_auth)(void *arg, const unsigned long cid, const unsigned int mda_key_id,\n                        const bool auth, const char *reason, const char *client_reason,\n                        struct buffer_list *cc_config); /* ownership transferred */\n    bool (*client_pending_auth)(void *arg, const unsigned long cid, const unsigned int kid,\n                                const char *extra, unsigned int timeout);\n    char *(*get_peer_info)(void *arg, const unsigned long cid);\n    bool (*proxy_cmd)(void *arg, const char **p);\n    bool (*remote_cmd)(void *arg, const char **p);\n#ifdef TARGET_ANDROID\n    int (*network_change)(void *arg, bool samenetwork);\n#endif\n    unsigned int (*remote_entry_count)(void *arg);\n    bool (*remote_entry_get)(void *arg, unsigned int index, char **remote);\n    bool (*push_update_broadcast)(void *arg, const char *options);\n    bool (*push_update_by_cid)(void *arg, unsigned long cid, const char *options);\n};\n\n/*\n * Management object, split into three components:\n *\n * struct man_persist : Data elements which are persistent across\n *                      man_connection open and close.\n *\n * struct man_settings : management parameters.\n *\n * struct man_connection : created on socket binding and listen,\n *                         deleted on socket unbind, may\n *                         handle multiple sequential client\n *                         connections.\n */\n\nstruct man_persist\n{\n    bool defined;\n\n    struct log_history *log;\n    struct virtual_output vout;\n\n    bool standalone_disabled;\n    struct management_callback callback;\n\n    struct log_history *echo; /* saved --echo strings */\n    struct log_history *state;\n\n    bool hold_release;\n\n    const char *special_state_msg;\n\n    counter_type bytes_in;\n    counter_type bytes_out;\n};\n\nstruct man_settings\n{\n    bool defined;\n    unsigned int flags; /* MF_x flags */\n    struct addrinfo *local;\n#if UNIX_SOCK_SUPPORT\n    struct sockaddr_un local_unix;\n    struct platform_state_user user;\n    struct platform_state_group group;\n#endif\n    bool management_over_tunnel;\n    struct user_pass up;\n    int log_history_cache;\n    int echo_buffer_size;\n    int state_buffer_size;\n\n/* flags for handling the management interface \"signal\" command */\n#define MANSIG_IGNORE_USR1_HUP  (1u << 0)\n#define MANSIG_MAP_USR1_TO_HUP  (1u << 1)\n#define MANSIG_MAP_USR1_TO_TERM (1u << 2)\n    unsigned int mansig;\n};\n\n/* up_query modes */\n#define UP_QUERY_DISABLED  0\n#define UP_QUERY_USER_PASS 1\n#define UP_QUERY_PASS      2\n#define UP_QUERY_NEED_OK   3\n#define UP_QUERY_NEED_STR  4\n\n/* states */\n#define MS_INITIAL       0 /* all sockets are closed */\n#define MS_LISTEN        1 /* no client is connected */\n#define MS_CC_WAIT_READ  2 /* client is connected, waiting for read on socket */\n#define MS_CC_WAIT_WRITE 3 /* client is connected, waiting for ability to write to socket */\n\nstruct man_connection\n{\n    int state;\n\n    socket_descriptor_t sd_top;\n    socket_descriptor_t sd_cli;\n    struct openvpn_sockaddr remote;\n\n#ifdef _WIN32\n    struct net_event_win32 ne32;\n#endif\n\n    bool halt;\n    bool password_verified;\n    int password_tries;\n\n    struct command_line *in;\n    struct buffer_list *out;\n\n#define IEC_UNDEF       0\n#define IEC_CLIENT_AUTH 1\n/* #define IEC_CLIENT_PF   2 *REMOVED FEATURE* */\n#define IEC_RSA_SIGN    3\n#define IEC_CERTIFICATE 4\n#define IEC_PK_SIGN     5\n    int in_extra_cmd;\n    struct buffer_list *in_extra;\n    unsigned long in_extra_cid;\n    unsigned int in_extra_kid;\n#define EKS_UNDEF   0\n#define EKS_SOLICIT 1\n#define EKS_INPUT   2\n#define EKS_READY   3\n    int ext_key_state;\n    struct buffer_list *ext_key_input;\n    int ext_cert_state;\n    struct buffer_list *ext_cert_input;\n    struct event_set *es;\n    int env_filter_level;\n\n    bool state_realtime;\n    bool log_realtime;\n    bool echo_realtime;\n    int bytecount_update_seconds;\n    struct event_timeout bytecount_update_interval;\n\n    const char *up_query_type;\n    int up_query_mode;\n    struct user_pass up_query;\n\n#ifdef TARGET_ANDROID\n    int fdtosend;\n    int lastfdreceived;\n#endif\n    int client_version;\n};\n\nstruct management\n{\n    struct man_persist persist;\n    struct man_settings settings;\n    struct man_connection connection;\n};\n\nextern struct management *management;\n\nstruct user_pass;\n\nstruct management *management_init(void);\n\nbool management_open(struct management *man, const char *addr, const char *port,\n                     const char *pass_file, const char *client_user, const char *client_group,\n                     const int log_history_cache, const int echo_buffer_size,\n                     const int state_buffer_size, const int remap_sigusr1,\n                     const unsigned int flags);\n\nvoid management_close(struct management *man);\n\nvoid management_post_tunnel_open(struct management *man, const in_addr_t tun_local_ip);\n\nvoid management_pre_tunnel_close(struct management *man);\n\nvoid management_socket_set(struct management *man, struct event_set *es, void *arg,\n                           unsigned int *persistent);\n\nvoid management_io(struct management *man);\n\nvoid management_set_callback(struct management *man, const struct management_callback *cb);\n\nvoid management_clear_callback(struct management *man);\n\nbool management_query_user_pass(struct management *man, struct user_pass *up, const char *type,\n                                const unsigned int flags, const char *static_challenge);\n\n#ifdef TARGET_ANDROID\nbool management_android_control(struct management *man, const char *command, const char *msg);\n\n#define ANDROID_KEEP_OLD_TUN      1\n#define ANDROID_OPEN_BEFORE_CLOSE 2\nint managment_android_persisttun_action(struct management *man);\n\n#endif\n\nbool management_would_hold(struct management *man);\n\nbool management_hold(struct management *man, int holdtime);\n\nvoid management_event_loop_n_seconds(struct management *man, int sec);\n\nvoid management_up_down(struct management *man, const char *updown, const struct env_set *es);\n\nvoid management_notify(struct management *man, const char *severity, const char *type,\n                       const char *text);\n\nvoid management_notify_generic(struct management *man, const char *str);\n\nvoid management_notify_client_needing_auth(struct management *management,\n                                           const unsigned int auth_id,\n                                           struct man_def_auth_context *mdac,\n                                           const struct env_set *es);\n\nvoid management_connection_established(struct management *management,\n                                       struct man_def_auth_context *mdac, const struct env_set *es);\n\nvoid management_notify_client_close(struct management *management,\n                                    struct man_def_auth_context *mdac, const struct env_set *es);\n\nvoid management_learn_addr(struct management *management, struct man_def_auth_context *mdac,\n                           const struct mroute_addr *addr, const bool primary);\n\nvoid management_notify_client_cr_response(unsigned mda_key_id,\n                                          const struct man_def_auth_context *mdac,\n                                          const struct env_set *es, const char *response);\n\nchar *management_query_pk_sig(struct management *man, const char *b64_data, const char *algorithm);\n\nchar *management_query_cert(struct management *man, const char *cert_name);\n\nstatic inline bool\nmanagement_connected(const struct management *man)\n{\n    return man->connection.state == MS_CC_WAIT_READ || man->connection.state == MS_CC_WAIT_WRITE;\n}\n\nstatic inline bool\nmanagement_query_user_pass_enabled(const struct management *man)\n{\n    return BOOL_CAST(man->settings.flags & MF_QUERY_PASSWORDS);\n}\n\nstatic inline bool\nmanagement_query_remote_enabled(const struct management *man)\n{\n    return BOOL_CAST(man->settings.flags & MF_QUERY_REMOTE);\n}\n\nstatic inline bool\nmanagement_query_proxy_enabled(const struct management *man)\n{\n    return BOOL_CAST(man->settings.flags & MF_QUERY_PROXY);\n}\n\n\nstatic inline bool\nmanagement_enable_def_auth(const struct management *man)\n{\n    return man && BOOL_CAST(man->settings.flags & MF_CLIENT_AUTH);\n}\n\n/*\n * OpenVPN tells the management layer what state it's in\n */\n\n/* client/server states */\n#define OPENVPN_STATE_INITIAL      0 /* Initial, undefined state */\n#define OPENVPN_STATE_CONNECTING   1 /* Management interface has been initialized */\n#define OPENVPN_STATE_ASSIGN_IP    2 /* Assigning IP address to virtual network interface */\n#define OPENVPN_STATE_ADD_ROUTES   3 /* Adding routes to system */\n#define OPENVPN_STATE_CONNECTED    4 /* Initialization sequence completed */\n#define OPENVPN_STATE_RECONNECTING 5 /* Restart */\n#define OPENVPN_STATE_EXITING      6 /* Exit */\n\n/* client-only states */\n#define OPENVPN_STATE_WAIT        7  /* Waiting for initial response from server */\n#define OPENVPN_STATE_AUTH        8  /* Authenticating with server */\n#define OPENVPN_STATE_GET_CONFIG  9  /* Downloading configuration from server */\n#define OPENVPN_STATE_RESOLVE     10 /* DNS lookup */\n#define OPENVPN_STATE_TCP_CONNECT 11 /* Connecting to TCP server */\n#define OPENVPN_STATE_AUTH_PENDING                                   \\\n    12                               /* Waiting in auth-pending mode \\\n                                      * technically variant of GET_CONFIG */\n\n#define OPENVPN_STATE_CLIENT_BASE 7  /* Base index of client-only states */\n\nvoid management_set_state(struct management *man, const int state, const char *detail,\n                          const in_addr_t *tun_local_ip, const struct in6_addr *tun_local_ip6,\n                          const struct openvpn_sockaddr *local_addr,\n                          const struct openvpn_sockaddr *remote_addr);\n\n/*\n * The management object keeps track of OpenVPN --echo\n * parameters.\n */\nvoid management_echo(struct management *man, const char *string, const bool pull);\n\n/*\n * OpenVPN calls here to indicate a password failure\n */\n\nvoid management_auth_failure(struct management *man, const char *type, const char *reason);\n\n/*\n * Echo an authentication token to management interface\n */\nvoid management_auth_token(struct management *man, const char *token);\n\n/*\n * These functions drive the bytecount in/out counters.\n */\n\nvoid management_check_bytecount_client(struct context *c, struct management *man, struct timeval *timeval);\n\nvoid management_check_bytecount_server(struct multi_context *multi, struct timeval *timeval);\n\nvoid man_persist_client_stats(struct management *man, struct context *c);\n\n#endif /* ifdef ENABLE_MANAGEMENT */\n\n/**\n * A sleep function that services the management layer for n seconds rather\n * than doing nothing.\n */\nvoid management_sleep(const int n);\n\n#endif /* ifndef MANAGE_H */\n"
  },
  {
    "path": "src/openvpn/mbedtls_compat.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2023-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * mbedtls compatibility stub.\n * This file provides compatibility stubs to handle API differences between\n * different versions of Mbed TLS.\n */\n\n#ifndef MBEDTLS_COMPAT_H_\n#define MBEDTLS_COMPAT_H_\n\n#include \"syshead.h\"\n\n#include \"errlevel.h\"\n\n#include <mbedtls/asn1.h>\n#include <mbedtls/pk.h>\n#include <mbedtls/version.h>\n\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n#include <mbedtls/ctr_drbg.h>\n#include \"crypto_mbedtls_legacy.h\"\n#else\n#include <mbedtls/oid.h>\n#endif /* MBEDTLS_VERSION_NUMBER < 0x04000000 */\n\n#ifdef HAVE_PSA_CRYPTO_H\n#include <psa/crypto.h>\n#endif\n\nstatic inline void\nmbedtls_compat_psa_crypto_init(void)\n{\n#if defined(HAVE_PSA_CRYPTO_H) && defined(MBEDTLS_PSA_CRYPTO_C)\n    if (psa_crypto_init() != PSA_SUCCESS)\n    {\n        msg(M_FATAL, \"mbedtls: psa_crypto_init() failed\");\n    }\n#else\n    return;\n#endif\n}\n\n#if MBEDTLS_VERSION_NUMBER >= 0x04000000\ntypedef struct\n{\n    const char *name;\n    uint16_t tls_id;\n} mbedtls_ecp_curve_info;\n\nstatic inline int\nmbedtls_oid_get_attr_short_name(const mbedtls_asn1_buf *oid, const char **desc)\n{\n    /* The relevant OIDs all have equal length. */\n    if (oid->tag != MBEDTLS_ASN1_OID || oid->len != strlen(MBEDTLS_OID_AT_CN))\n    {\n        *desc = NULL;\n        return -1;\n    }\n\n    if (memcmp(oid->p, MBEDTLS_OID_AT_CN, oid->len) == 0)\n    {\n        *desc = \"CN\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_SUR_NAME, oid->len) == 0)\n    {\n        *desc = \"SN\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_SERIAL_NUMBER, oid->len) == 0)\n    {\n        *desc = \"serialNumber\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_COUNTRY, oid->len) == 0)\n    {\n        *desc = \"C\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_LOCALITY, oid->len) == 0)\n    {\n        *desc = \"L\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_STATE, oid->len) == 0)\n    {\n        *desc = \"ST\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_ORGANIZATION, oid->len) == 0)\n    {\n        *desc = \"O\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_ORG_UNIT, oid->len) == 0)\n    {\n        *desc = \"OU\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_TITLE, oid->len) == 0)\n    {\n        *desc = \"title\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_POSTAL_ADDRESS, oid->len) == 0)\n    {\n        *desc = \"postalAddress\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_POSTAL_CODE, oid->len) == 0)\n    {\n        *desc = \"postalCode\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_GIVEN_NAME, oid->len) == 0)\n    {\n        *desc = \"GN\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_INITIALS, oid->len) == 0)\n    {\n        *desc = \"initials\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_GENERATION_QUALIFIER, oid->len) == 0)\n    {\n        *desc = \"generationQualifier\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_UNIQUE_IDENTIFIER, oid->len) == 0)\n    {\n        *desc = \"uniqueIdentifier\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_DN_QUALIFIER, oid->len) == 0)\n    {\n        *desc = \"dnQualifier\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_AT_PSEUDONYM, oid->len) == 0)\n    {\n        *desc = \"pseudonym\";\n    }\n    else\n    {\n        *desc = NULL;\n        return -1;\n    }\n    return 0;\n}\n\nstatic inline int\nmbedtls_oid_get_extended_key_usage(const mbedtls_asn1_buf *oid, const char **desc)\n{\n    /* The relevant OIDs all have equal length. */\n    if (oid->tag != MBEDTLS_ASN1_OID || oid->len != strlen(MBEDTLS_OID_SERVER_AUTH))\n    {\n        *desc = NULL;\n        return -1;\n    }\n\n    if (memcmp(oid->p, MBEDTLS_OID_SERVER_AUTH, oid->len) == 0)\n    {\n        *desc = \"TLS Web Server Authentication\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_CLIENT_AUTH, oid->len) == 0)\n    {\n        *desc = \"TLS Web Client Authentication\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_CODE_SIGNING, oid->len) == 0)\n    {\n        *desc = \"Code Signing\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_EMAIL_PROTECTION, oid->len) == 0)\n    {\n        *desc = \"E-mail Protection\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_TIME_STAMPING, oid->len) == 0)\n    {\n        *desc = \"Time Stamping\";\n    }\n    else if (memcmp(oid->p, MBEDTLS_OID_OCSP_SIGNING, oid->len) == 0)\n    {\n        *desc = \"OCSP Signing\";\n    }\n    else\n    {\n        *desc = NULL;\n        return -1;\n    }\n\n    return 0;\n}\n#endif /* MBEDTLS_VERSION_NUMBER >= 0x04000000 */\n\n/* Some functions that operate on private keys use randomness to protect against\n * side channels. In Mbed TLS 4, they automatically use the RNG in the PSA\n * library, but in Mbed TLS 3, they require them as explicit arguments. */\nstatic inline int\nmbedtls_compat_pk_parse_key(mbedtls_pk_context *ctx,\n                            const unsigned char *key, size_t keylen,\n                            const unsigned char *pwd, size_t pwdlen)\n{\n#if MBEDTLS_VERSION_NUMBER >= 0x04000000\n    return mbedtls_pk_parse_key(ctx, key, keylen, pwd, pwdlen);\n#else\n    return mbedtls_pk_parse_key(ctx, key, keylen, pwd, pwdlen, mbedtls_ctr_drbg_random, rand_ctx_get());\n#endif /* MBEDTLS_VERSION_NUMBER < 0x04000000 */\n}\n\nstatic inline int\nmbedtls_compat_pk_parse_keyfile(mbedtls_pk_context *ctx, const char *path, const char *password)\n{\n#if MBEDTLS_VERSION_NUMBER >= 0x04000000\n    return mbedtls_pk_parse_keyfile(ctx, path, password);\n#else\n    return mbedtls_pk_parse_keyfile(ctx, path, password, mbedtls_ctr_drbg_random, rand_ctx_get());\n#endif /* MBEDTLS_VERSION_NUMBER < 0x04000000 */\n}\n\nstatic inline int\nmbedtls_compat_pk_check_pair(const mbedtls_pk_context *pub, const mbedtls_pk_context *prv)\n{\n#if MBEDTLS_VERSION_NUMBER >= 0x04000000\n    return mbedtls_pk_check_pair(pub, prv);\n#else\n    return mbedtls_pk_check_pair(pub, prv, mbedtls_ctr_drbg_random, rand_ctx_get());\n#endif /* MBEDTLS_VERSION_NUMBER < 0x04000000 */\n}\n\n#endif /* MBEDTLS_COMPAT_H_ */\n"
  },
  {
    "path": "src/openvpn/mbuf.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"integer.h\"\n#include \"misc.h\"\n#include \"mbuf.h\"\n\n#include \"memdbg.h\"\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\nstruct mbuf_set *\nmbuf_init(unsigned int size)\n{\n    ASSERT(size <= MBUF_SIZE_MAX);\n\n    struct mbuf_set *ret;\n    ALLOC_OBJ_CLEAR(ret, struct mbuf_set);\n    ret->capacity = adjust_power_of_2(size);\n    ALLOC_ARRAY(ret->array, struct mbuf_item, ret->capacity);\n    return ret;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nvoid\nmbuf_free(struct mbuf_set *ms)\n{\n    if (ms)\n    {\n        int i;\n        for (i = 0; i < (int)ms->len; ++i)\n        {\n            struct mbuf_item *item = &ms->array[MBUF_INDEX(ms->head, i, ms->capacity)];\n            mbuf_free_buf(item->buffer);\n        }\n        free(ms->array);\n        free(ms);\n    }\n}\n\nstruct mbuf_buffer *\nmbuf_alloc_buf(const struct buffer *buf)\n{\n    struct mbuf_buffer *ret;\n    ALLOC_OBJ(ret, struct mbuf_buffer);\n    ret->buf = clone_buf(buf);\n    ret->refcount = 1;\n    ret->flags = 0;\n    return ret;\n}\n\nvoid\nmbuf_free_buf(struct mbuf_buffer *mb)\n{\n    if (mb)\n    {\n        if (--mb->refcount <= 0)\n        {\n            free_buf(&mb->buf);\n            free(mb);\n        }\n    }\n}\n\nvoid\nmbuf_add_item(struct mbuf_set *ms, const struct mbuf_item *item)\n{\n    ASSERT(ms);\n    if (ms->len == ms->capacity)\n    {\n        struct mbuf_item rm;\n        ASSERT(mbuf_extract_item(ms, &rm));\n        mbuf_free_buf(rm.buffer);\n        msg(D_MULTI_DROPPED, \"MBUF: mbuf packet dropped\");\n    }\n\n    ASSERT(ms->len < ms->capacity);\n\n    ms->array[MBUF_INDEX(ms->head, ms->len, ms->capacity)] = *item;\n    if (++ms->len > ms->max_queued)\n    {\n        ms->max_queued = ms->len;\n    }\n    ++item->buffer->refcount;\n}\n\nbool\nmbuf_extract_item(struct mbuf_set *ms, struct mbuf_item *item)\n{\n    bool ret = false;\n    if (ms)\n    {\n        ASSERT(item);\n        while (ms->len)\n        {\n            *item = ms->array[ms->head];\n            ms->head = MBUF_INDEX(ms->head, 1, ms->capacity);\n            --ms->len;\n            if (item->instance) /* ignore dereferenced instances */\n            {\n                ret = true;\n                break;\n            }\n        }\n    }\n    return ret;\n}\n\nstruct multi_instance *\nmbuf_peek_dowork(struct mbuf_set *ms)\n{\n    struct multi_instance *ret = NULL;\n    if (ms)\n    {\n        int i;\n        for (i = 0; i < (int)ms->len; ++i)\n        {\n            struct mbuf_item *item = &ms->array[MBUF_INDEX(ms->head, i, ms->capacity)];\n            if (item->instance)\n            {\n                ret = item->instance;\n                break;\n            }\n        }\n    }\n    return ret;\n}\n\nvoid\nmbuf_dereference_instance(struct mbuf_set *ms, struct multi_instance *mi)\n{\n    if (ms)\n    {\n        int i;\n        for (i = 0; i < (int)ms->len; ++i)\n        {\n            struct mbuf_item *item = &ms->array[MBUF_INDEX(ms->head, i, ms->capacity)];\n            if (item->instance == mi)\n            {\n                mbuf_free_buf(item->buffer);\n                item->buffer = NULL;\n                item->instance = NULL;\n                msg(D_MBUF, \"MBUF: dereferenced queued packet\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/openvpn/mbuf.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef MBUF_H\n#define MBUF_H\n\n/*\n * Handle both multicast and broadcast functions.\n */\n\n/* define this to enable special test mode */\n/*#define MBUF_TEST*/\n\n#include \"basic.h\"\n#include \"buffer.h\"\n\nstruct multi_instance;\n\n#define MBUF_INDEX(head, offset, size) (((head) + (offset)) & ((size) - 1))\n/* limited by adjust_power_of_2 and array_mult_safe */\n#define MBUF_SIZE_MAX                  (ALLOC_SIZE_MAX / sizeof(struct mbuf_item))\n\nstruct mbuf_buffer\n{\n    struct buffer buf;\n    int refcount;\n\n#define MF_UNICAST (1 << 0)\n    unsigned int flags;\n};\n\nstruct mbuf_item\n{\n    struct mbuf_buffer *buffer;\n    struct multi_instance *instance;\n};\n\nstruct mbuf_set\n{\n    unsigned int head;\n    unsigned int len;\n    unsigned int capacity;\n    unsigned int max_queued;\n    struct mbuf_item *array;\n};\n\nstruct mbuf_set *mbuf_init(unsigned int size);\n\nvoid mbuf_free(struct mbuf_set *ms);\n\nstruct mbuf_buffer *mbuf_alloc_buf(const struct buffer *buf);\n\nvoid mbuf_free_buf(struct mbuf_buffer *mb);\n\nvoid mbuf_add_item(struct mbuf_set *ms, const struct mbuf_item *item);\n\nbool mbuf_extract_item(struct mbuf_set *ms, struct mbuf_item *item);\n\nvoid mbuf_dereference_instance(struct mbuf_set *ms, struct multi_instance *mi);\n\nstatic inline bool\nmbuf_defined(const struct mbuf_set *ms)\n{\n    return ms && ms->len;\n}\n\nstatic inline unsigned int\nmbuf_len(const struct mbuf_set *ms)\n{\n    return ms->len;\n}\n\nstatic inline int\nmbuf_maximum_queued(const struct mbuf_set *ms)\n{\n    return (int)ms->max_queued;\n}\n\nstruct multi_instance *mbuf_peek_dowork(struct mbuf_set *ms);\n\nstatic inline struct multi_instance *\nmbuf_peek(struct mbuf_set *ms)\n{\n    if (mbuf_defined(ms))\n    {\n        return mbuf_peek_dowork(ms);\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\n#endif /* ifndef MBUF_H */\n"
  },
  {
    "path": "src/openvpn/memdbg.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef MEMDBG_H\n#define MEMDBG_H\n\n/*\n * Valgrind debugging support.\n *\n * Valgrind is a great tool for debugging memory issues,\n * though it seems to generate a lot of warnings in OpenSSL\n * about uninitialized data. To silence these warnings,\n * I've put together a suppressions file\n * in debug/valgrind-suppress.\n *\n * Also, grep for VALGRIND_MAKE_READABLE in the OpenVPN source.\n * Because valgrind thinks that some of the data passed from\n * OpenSSL back to OpenVPN is tainted due to being sourced\n * from uninitialized data, we need to untaint it before use --\n * otherwise we will get a lot of useless warnings.\n *\n *   valgrind --tool=memcheck --error-limit=no --suppressions=debug/valgrind-suppress\n * --gen-suppressions=yes ./openvpn ...\n */\n\n#ifdef USE_VALGRIND\n\n#include <valgrind/memcheck.h>\n\n#define VALGRIND_MAKE_READABLE(addr, len)\n\n#else /* ifdef USE_VALGRIND */\n\n#define VALGRIND_MAKE_READABLE(addr, len)\n\n#endif\n\n#ifdef DMALLOC /* see ./configure options to enable */\n\n/*\n * See ./configure options to enable dmalloc\n * support for memory leak checking.\n *\n * The dmalloc package can be downloaded from:\n *\n *     https://dmalloc.com/\n *\n * When dmalloc is installed and enabled,\n * use this command prior to running openvpn:\n *\n *    dmalloc -l dlog -i 100 low -p log-unknown\n *\n * Also, put this in your .bashrc file:\n *\n *    function dmalloc { eval `command dmalloc -b $*`; }\n *\n * Or take a more low-level approach:\n *\n *    export DMALLOC_OPTIONS=\"debug=0x4e48503,inter=100,log=dlog\"\n *\n *  NOTE: When building dmalloc you need to add something\n *  like this to dmalloc's settings.h -- it will allocate a static\n *  buffer to be used as the malloc arena:\n *\n *  #define INTERNAL_MEMORY_SPACE (1024 * 1024 * 50)\n */\n\n#include <dmalloc.h>\n\n#define openvpn_dmalloc(file, line, size) \\\n    dmalloc_malloc((file), (line), (size), DMALLOC_FUNC_MALLOC, 0, 0)\n\n/*\n * This #define will put the line number of the log\n * file position where leaked memory was allocated instead\n * of the source code file and line number.  Make sure\n * to increase the size of dmalloc's info tables,\n * (MEMORY_TABLE_SIZE in settings.h)\n * otherwise it might get overwhelmed by the large\n * number of unique file/line combinations.\n */\n#if 0\n#undef malloc\n#define malloc(size) openvpn_dmalloc(\"logfile\", x_msg_line_num, (size))\n#endif\n\n#endif /* DMALLOC */\n\n/*\n * Force buffers to be zeroed after allocation.\n * For debugging only.\n */\n/*#define ZERO_BUFFER_ON_ALLOC*/\n\n#endif /* MEMDBG_H */\n"
  },
  {
    "path": "src/openvpn/misc.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2014-2015 David Sommerseth <davids@redhat.com>\n *  Copyright (C) 2016-2026 David Sommerseth <davids@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"buffer.h\"\n#include \"misc.h\"\n#include \"base64.h\"\n#include \"tun.h\"\n#include \"error.h\"\n#include \"otime.h\"\n#include \"plugin.h\"\n#include \"options.h\"\n#include \"manage.h\"\n#include \"crypto.h\"\n#include \"route.h\"\n#include \"console.h\"\n#include \"win32.h\"\n\n#include \"memdbg.h\"\n\n#ifdef ENABLE_IPROUTE\nconst char *iproute_path = IPROUTE_PATH; /* GLOBAL */\n#endif\n\n/*\n * Set standard file descriptors to /dev/null\n */\nvoid\nset_std_files_to_null(bool stdin_only)\n{\n#if defined(HAVE_DUP) && defined(HAVE_DUP2)\n    int fd;\n    if ((fd = open(\"/dev/null\", O_RDWR, 0)) != -1)\n    {\n        dup2(fd, 0);\n        if (!stdin_only)\n        {\n            dup2(fd, 1);\n            dup2(fd, 2);\n        }\n        if (fd > 2)\n        {\n            close(fd);\n        }\n    }\n#endif\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\n#ifdef ENABLE_MANAGEMENT\n/* Get username/password from the management interface */\nstatic bool\nauth_user_pass_mgmt(struct user_pass *up, const char *prefix, const unsigned int flags,\n                    const char *auth_challenge)\n{\n    const char *sc = NULL;\n\n    if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED)\n    {\n        management_auth_failure(management, prefix, \"previous auth credentials failed\");\n    }\n\n    if (auth_challenge && (flags & GET_USER_PASS_STATIC_CHALLENGE))\n    {\n        sc = auth_challenge;\n    }\n    if (!management_query_user_pass(management, up, prefix, flags, sc))\n    {\n        if ((flags & GET_USER_PASS_NOFATAL) != 0)\n        {\n            return false;\n        }\n        else\n        {\n            msg(M_FATAL,\n                \"ERROR: could not read %s username/password/ok/string from management interface\",\n                prefix);\n        }\n    }\n    return true;\n}\n\n/**\n * Parses an authentication challenge string and returns an auth_challenge_info structure.\n * The authentication challenge string should follow the dynamic challenge/response protocol.\n *\n * See doc/management-notes.txt for more info on the dynamic challenge/response protocol\n * implemented here.\n *\n * @param auth_challenge The authentication challenge string to parse. Can't be NULL.\n * @param gc             The gc_arena structure for memory allocation.\n *\n * @return               A pointer to the parsed auth_challenge_info structure, or NULL if parsing\n * fails.\n */\nstatic struct auth_challenge_info *\nparse_auth_challenge(const char *auth_challenge, struct gc_arena *gc)\n{\n    ASSERT(auth_challenge);\n\n    struct auth_challenge_info *ac;\n    const int len = strlen(auth_challenge);\n    char *work = (char *)gc_malloc(len + 1, false, gc);\n    char *cp;\n\n    struct buffer b;\n    buf_set_read(&b, (const uint8_t *)auth_challenge, len);\n\n    ALLOC_OBJ_CLEAR_GC(ac, struct auth_challenge_info, gc);\n\n    /* parse prefix */\n    if (!buf_parse(&b, ':', work, len))\n    {\n        return NULL;\n    }\n    if (strcmp(work, \"CRV1\"))\n    {\n        return NULL;\n    }\n\n    /* parse flags */\n    if (!buf_parse(&b, ':', work, len))\n    {\n        return NULL;\n    }\n    for (cp = work; *cp != '\\0'; ++cp)\n    {\n        const char c = *cp;\n        if (c == 'E')\n        {\n            ac->flags |= CR_ECHO;\n        }\n        else if (c == 'R')\n        {\n            ac->flags |= CR_RESPONSE;\n        }\n    }\n\n    /* parse state ID */\n    if (!buf_parse(&b, ':', work, len))\n    {\n        return NULL;\n    }\n    ac->state_id = string_alloc(work, gc);\n\n    /* parse user name */\n    if (!buf_parse(&b, ':', work, len))\n    {\n        return NULL;\n    }\n    ac->user = (char *)gc_malloc(strlen(work) + 1, true, gc);\n    openvpn_base64_decode(work, (void *)ac->user, -1);\n\n    /* parse challenge text */\n    ac->challenge_text = string_alloc(BSTR(&b), gc);\n\n    return ac;\n}\n\n#endif /* ifdef ENABLE_MANAGEMENT */\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n/*\n * Get and store a username/password\n */\n\nbool\nget_user_pass_cr(struct user_pass *up, const char *auth_file, const char *prefix,\n                 const unsigned int flags, const char *auth_challenge)\n{\n    struct gc_arena gc = gc_new();\n\n    if (!up->defined)\n    {\n        bool from_authfile = (auth_file && !streq(auth_file, \"stdin\"));\n        bool username_from_stdin = false;\n        bool password_from_stdin = false;\n        bool response_from_stdin = true;\n\n        unprotect_user_pass(up);\n        if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED)\n        {\n            msg(M_WARN, \"Note: previous '%s' credentials failed\", prefix);\n        }\n\n#ifdef ENABLE_MANAGEMENT\n        /*\n         * Get username/password from management interface?\n         */\n        if (management && (!from_authfile && (flags & GET_USER_PASS_MANAGEMENT))\n            && management_query_user_pass_enabled(management))\n        {\n            response_from_stdin = false;\n            if (!auth_user_pass_mgmt(up, prefix, flags, auth_challenge))\n            {\n                return false;\n            }\n        }\n        else\n#endif /* ifdef ENABLE_MANAGEMENT */\n            /*\n             * Get NEED_OK confirmation from the console\n             */\n            if (flags & GET_USER_PASS_NEED_OK)\n            {\n                struct buffer user_prompt = alloc_buf_gc(128, &gc);\n\n                buf_printf(&user_prompt, \"NEED-OK|%s|%s:\", prefix, up->username);\n                if (!query_user_SINGLE(BSTR(&user_prompt), up->password, USER_PASS_LEN, false))\n                {\n                    msg(M_FATAL, \"ERROR: could not read %s ok-confirmation from stdin\", prefix);\n                }\n\n                if (!strlen(up->password))\n                {\n                    strcpy(up->password, \"ok\");\n                }\n            }\n            else if (flags & GET_USER_PASS_INLINE_CREDS)\n            {\n                struct buffer buf;\n                buf_set_read(&buf, (uint8_t *)auth_file, strlen(auth_file) + 1);\n                if (!(flags & GET_USER_PASS_PASSWORD_ONLY))\n                {\n                    buf_parse(&buf, '\\n', up->username, USER_PASS_LEN);\n                }\n                buf_parse(&buf, '\\n', up->password, USER_PASS_LEN);\n\n                if (strlen(up->password) == 0)\n                {\n                    password_from_stdin = 1;\n                }\n            }\n            /*\n             * Read from auth file unless this is a dynamic challenge request.\n             */\n            else if (from_authfile && !(flags & GET_USER_PASS_DYNAMIC_CHALLENGE))\n            {\n                /*\n                 * Try to get username/password from a file.\n                 */\n                FILE *fp;\n                char password_buf[USER_PASS_LEN] = { '\\0' };\n\n                fp = platform_fopen(auth_file, \"r\");\n                if (!fp)\n                {\n                    msg(M_ERR, \"Error opening '%s' auth file: %s\", prefix, auth_file);\n                }\n\n                if ((flags & GET_USER_PASS_PASSWORD_ONLY) == 0)\n                {\n                    /* Read username first */\n                    if (fgets(up->username, USER_PASS_LEN, fp) == NULL)\n                    {\n                        msg(M_FATAL, \"Error reading username from %s authfile: %s\", prefix,\n                            auth_file);\n                    }\n                }\n                chomp(up->username);\n\n                if (fgets(password_buf, USER_PASS_LEN, fp) != NULL)\n                {\n                    chomp(password_buf);\n                }\n\n                if (flags & GET_USER_PASS_PASSWORD_ONLY && !password_buf[0])\n                {\n                    msg(M_FATAL, \"Error reading password from %s authfile: %s\", prefix, auth_file);\n                }\n\n                if (password_buf[0])\n                {\n                    strncpy(up->password, password_buf, USER_PASS_LEN);\n                }\n                /* The auth-file does not have the password: get both username\n                 * and password from the management interface if possible.\n                 * Otherwise set to read password from console.\n                 */\n#if defined(ENABLE_MANAGEMENT)\n                else if (management && (flags & GET_USER_PASS_MANAGEMENT)\n                         && management_query_user_pass_enabled(management))\n                {\n                    msg(D_LOW,\n                        \"No password found in %s authfile '%s'. Querying the management interface\",\n                        prefix, auth_file);\n                    if (!auth_user_pass_mgmt(up, prefix, flags, auth_challenge))\n                    {\n                        fclose(fp);\n                        return false;\n                    }\n                }\n#endif\n                else\n                {\n                    password_from_stdin = 1;\n                }\n\n                fclose(fp);\n\n                if (!(flags & GET_USER_PASS_PASSWORD_ONLY) && strlen(up->username) == 0)\n                {\n                    msg(M_FATAL, \"ERROR: username from %s authfile '%s' is empty\", prefix,\n                        auth_file);\n                }\n            }\n            else\n            {\n                username_from_stdin = true;\n                password_from_stdin = true;\n            }\n\n        /*\n         * Get username/password from standard input?\n         */\n        if (username_from_stdin || password_from_stdin || response_from_stdin)\n        {\n#ifdef ENABLE_MANAGEMENT\n            if (auth_challenge && (flags & GET_USER_PASS_DYNAMIC_CHALLENGE) && response_from_stdin)\n            {\n                struct auth_challenge_info *ac = parse_auth_challenge(auth_challenge, &gc);\n                if (ac)\n                {\n                    char *response = (char *)gc_malloc(USER_PASS_LEN, false, &gc);\n                    struct buffer packed_resp, challenge;\n\n                    challenge = alloc_buf_gc(14 + strlen(ac->challenge_text), &gc);\n                    buf_printf(&challenge, \"CHALLENGE: %s\", ac->challenge_text);\n                    buf_set_write(&packed_resp, (uint8_t *)up->password, USER_PASS_LEN);\n\n                    if (!query_user_SINGLE(BSTR(&challenge), response,\n                                           USER_PASS_LEN, BOOL_CAST(ac->flags & CR_ECHO)))\n                    {\n                        msg(M_FATAL, \"ERROR: could not read challenge response from stdin\");\n                    }\n                    strncpynt(up->username, ac->user, USER_PASS_LEN);\n                    buf_printf(&packed_resp, \"CRV1::%s::%s\", ac->state_id, response);\n                }\n                else\n                {\n                    msg(M_FATAL, \"ERROR: received malformed challenge request from server\");\n                }\n            }\n            else\n#endif /* ifdef ENABLE_MANAGEMENT */\n            {\n                struct buffer user_prompt = alloc_buf_gc(128, &gc);\n                struct buffer pass_prompt = alloc_buf_gc(128, &gc);\n\n                query_user_clear();\n                buf_printf(&user_prompt, \"Enter %s Username:\", prefix);\n                buf_printf(&pass_prompt, \"Enter %s Password:\", prefix);\n\n                if (username_from_stdin && !(flags & GET_USER_PASS_PASSWORD_ONLY))\n                {\n                    query_user_add(BSTR(&user_prompt), up->username, USER_PASS_LEN, true);\n                }\n\n                if (password_from_stdin)\n                {\n                    query_user_add(BSTR(&pass_prompt), up->password, USER_PASS_LEN, false);\n                }\n\n                if (!query_user_exec())\n                {\n                    msg(M_FATAL, \"ERROR: Failed retrieving username or password\");\n                }\n\n                if (!(flags & GET_USER_PASS_PASSWORD_ONLY))\n                {\n                    if (strlen(up->username) == 0)\n                    {\n                        msg(M_FATAL, \"ERROR: %s username is empty\", prefix);\n                    }\n                }\n\n#ifdef ENABLE_MANAGEMENT\n                if (auth_challenge && (flags & GET_USER_PASS_STATIC_CHALLENGE)\n                    && response_from_stdin)\n                {\n                    char *response = (char *)gc_malloc(USER_PASS_LEN, false, &gc);\n                    struct buffer packed_resp, challenge;\n                    char *pw64 = NULL, *resp64 = NULL;\n\n                    challenge = alloc_buf_gc(14 + strlen(auth_challenge), &gc);\n                    buf_printf(&challenge, \"CHALLENGE: %s\", auth_challenge);\n\n                    if (!query_user_SINGLE(BSTR(&challenge), response, USER_PASS_LEN,\n                                           BOOL_CAST(flags & GET_USER_PASS_STATIC_CHALLENGE_ECHO)))\n                    {\n                        msg(M_FATAL, \"ERROR: could not retrieve static challenge response\");\n                    }\n                    if (!(flags & GET_USER_PASS_STATIC_CHALLENGE_CONCAT))\n                    {\n                        if (openvpn_base64_encode(up->password, (int)strlen(up->password), &pw64) == -1\n                            || openvpn_base64_encode(response, (int)strlen(response), &resp64) == -1)\n                        {\n                            msg(M_FATAL, \"ERROR: could not base64-encode password/static_response\");\n                        }\n                        buf_set_write(&packed_resp, (uint8_t *)up->password, USER_PASS_LEN);\n                        buf_printf(&packed_resp, \"SCRV1:%s:%s\", pw64, resp64);\n                        string_clear(pw64);\n                        free(pw64);\n                        string_clear(resp64);\n                        free(resp64);\n                    }\n                    else\n                    {\n                        if (strlen(up->password) + strlen(response) >= USER_PASS_LEN)\n                        {\n                            msg(M_FATAL,\n                                \"ERROR: could not concatenate password/static_response: string too long\");\n                        }\n                        strncat(up->password, response, USER_PASS_LEN - strlen(up->password) - 1);\n                    }\n                }\n#endif /* ifdef ENABLE_MANAGEMENT */\n            }\n        }\n\n        string_mod(up->username, CC_PRINT, CC_CRLF, 0);\n        string_mod(up->password, CC_PRINT, CC_CRLF, 0);\n\n        up->defined = true;\n    }\n\n#if 0\n    msg(M_INFO, \"GET_USER_PASS %s u='%s' p='%s'\", prefix, up->username, up->password);\n#endif\n\n    gc_free(&gc);\n\n    return true;\n}\n\nvoid\npurge_user_pass(struct user_pass *up, const bool force)\n{\n    const bool nocache = up->nocache;\n    static bool warn_shown = false;\n    if (nocache || force)\n    {\n        secure_memzero(up, sizeof(*up));\n        up->nocache = nocache;\n    }\n    else\n    {\n        protect_user_pass(up);\n        /*\n         * don't show warning if the pass has been replaced by a token: this is an\n         * artificial \"auth-nocache\"\n         */\n        if (!warn_shown)\n        {\n            msg(M_WARN,\n                \"WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this\");\n            warn_shown = true;\n        }\n    }\n}\n\nvoid\nset_auth_token(struct user_pass *tk, const char *token)\n{\n    if (strlen(token))\n    {\n        unprotect_user_pass(tk);\n        strncpynt(tk->password, token, USER_PASS_LEN);\n        tk->token_defined = true;\n\n        /*\n         * If username already set, tk is fully defined.\n         */\n        if (strlen(tk->username))\n        {\n            tk->defined = true;\n        }\n        protect_user_pass(tk);\n    }\n}\n\nvoid\nset_auth_token_user(struct user_pass *tk, const char *username)\n{\n    if (strlen(username))\n    {\n        unprotect_user_pass(tk);\n        /* Clear the username before decoding to ensure no old material is left\n         * and also allow decoding to not use all space to ensure the last byte is\n         * always 0 */\n        CLEAR(tk->username);\n        int len = openvpn_base64_decode(username, tk->username, USER_PASS_LEN - 1);\n        tk->defined = len > 0;\n        if (!tk->defined)\n        {\n            msg(D_PUSH, \"Error decoding auth-token-username\");\n        }\n        protect_user_pass(tk);\n    }\n}\n\n\n/*\n * Process string received by untrusted peer before\n * printing to console or log file.\n *\n * Assumes that string has been null terminated.\n */\nconst char *\nsafe_print(const char *str, struct gc_arena *gc)\n{\n    return string_mod_const(str, CC_PRINT, CC_CRLF, '.', gc);\n}\n\nconst char **\nmake_arg_array(const char *first, const char *parms, struct gc_arena *gc)\n{\n    char **ret = NULL;\n    int base = 0;\n    const int max_parms = MAX_PARMS + 2;\n    int n = 0;\n\n    /* alloc return array */\n    ALLOC_ARRAY_CLEAR_GC(ret, char *, max_parms, gc);\n\n    /* process first parameter, if provided */\n    if (first)\n    {\n        ret[base++] = string_alloc(first, gc);\n    }\n\n    if (parms)\n    {\n        n = parse_line(parms, &ret[base], max_parms - base - 1, \"make_arg_array\", 0, M_WARN, gc);\n        ASSERT(n >= 0 && n + base + 1 <= max_parms);\n    }\n    ret[base + n] = NULL;\n\n    return (const char **)ret;\n}\n\nstatic const char **\nmake_inline_array(const char *str, struct gc_arena *gc)\n{\n    char line[OPTION_LINE_SIZE];\n    struct buffer buf;\n    int len = 0;\n    char **ret = NULL;\n    int i = 0;\n\n    buf_set_read(&buf, (const uint8_t *)str, strlen(str));\n    while (buf_parse(&buf, '\\n', line, sizeof(line)))\n    {\n        ++len;\n    }\n\n    /* alloc return array */\n    ALLOC_ARRAY_CLEAR_GC(ret, char *, len + 1, gc);\n\n    buf_set_read(&buf, (const uint8_t *)str, strlen(str));\n    while (buf_parse(&buf, '\\n', line, sizeof(line)))\n    {\n        chomp(line);\n        ASSERT(i < len);\n        ret[i] = string_alloc(skip_leading_whitespace(line), gc);\n        ++i;\n    }\n    ASSERT(i <= len);\n    ret[i] = NULL;\n    return (const char **)ret;\n}\n\nstatic const char **\nmake_arg_copy(char **p, struct gc_arena *gc)\n{\n    char **ret = NULL;\n    const int len = string_array_len((const char **)p);\n    const int max_parms = len + 1;\n    int i;\n\n    /* alloc return array */\n    ALLOC_ARRAY_CLEAR_GC(ret, char *, max_parms, gc);\n\n    for (i = 0; i < len; ++i)\n    {\n        ret[i] = p[i];\n    }\n\n    return (const char **)ret;\n}\n\nconst char **\nmake_extended_arg_array(char **p, bool is_inline, struct gc_arena *gc)\n{\n    const int argc = string_array_len((const char **)p);\n    if (is_inline)\n    {\n        return make_inline_array(p[0], gc);\n    }\n    else if (argc == 0)\n    {\n        return make_arg_array(NULL, NULL, gc);\n    }\n    else if (argc == 1)\n    {\n        return make_arg_array(p[0], NULL, gc);\n    }\n    else if (argc == 2)\n    {\n        return make_arg_array(p[0], p[1], gc);\n    }\n    else\n    {\n        return make_arg_copy(p, gc);\n    }\n}\n\n/*\n * Remove security-sensitive strings from control message\n * so that they will not be output to log file.\n */\nconst char *\nsanitize_control_message(const char *src, struct gc_arena *gc)\n{\n    char *ret = gc_malloc(strlen(src) + 1, false, gc);\n    char *dest = ret;\n    bool redact = false;\n    int skip = 0;\n\n    for (;;)\n    {\n        const char c = *src;\n        if (c == '\\0')\n        {\n            break;\n        }\n        if (c == 'S' && !strncmp(src, \"SESS_ID_\", 8))\n        {\n            skip = 7;\n            redact = true;\n        }\n        else if (c == 'e' && !strncmp(src, \"echo \", 5))\n        {\n            skip = 4;\n            redact = true;\n        }\n        else if (!check_debug_level(D_SHOW_KEYS) && (c == 'a' && !strncmp(src, \"auth-token \", 11)))\n        {\n            /* Unless --verb is 7 or higher (D_SHOW_KEYS), hide\n             * the auth-token value coming in the src string\n             */\n            skip = 10;\n            redact = true;\n        }\n\n        if (c == ',') /* end of redacted item? */\n        {\n            skip = 0;\n            redact = false;\n        }\n\n        if (redact)\n        {\n            if (skip > 0)\n            {\n                --skip;\n                *dest++ = c;\n            }\n        }\n        else\n        {\n            *dest++ = c;\n        }\n\n        ++src;\n    }\n    *dest = '\\0';\n    return ret;\n}\n\n/* helper to parse peer_info received from multi client, validate\n * (this is untrusted data) and put into environment\n */\nbool\nvalidate_peer_info_line(char *line)\n{\n    uint8_t c;\n    int state = 0;\n    while (*line)\n    {\n        c = *line;\n        switch (state)\n        {\n            case 0:\n            case 1:\n                if (c == '=' && state == 1)\n                {\n                    state = 2;\n                }\n                else if (isalnum(c) || c == '_')\n                {\n                    state = 1;\n                }\n                else\n                {\n                    return false;\n                }\n                /* Intentional [[fallthrough]]; */\n            case 2:\n                /* after the '=', replace non-printable or shell meta with '_' */\n                if (!isprint(c) || isspace(c) || c == '$' || c == '(' || c == '`')\n                {\n                    *line = '_';\n                }\n        }\n        line++;\n    }\n    return (state == 2);\n}\n\nvoid\noutput_peer_info_env(struct env_set *es, const char *peer_info)\n{\n    char line[256];\n    struct buffer buf;\n    buf_set_read(&buf, (const uint8_t *)peer_info, strlen(peer_info));\n    while (buf_parse(&buf, '\\n', line, sizeof(line)))\n    {\n        chomp(line);\n        if (validate_peer_info_line(line)\n            && (strncmp(line, \"IV_\", 3) == 0 || strncmp(line, \"UV_\", 3) == 0))\n        {\n            msg(M_INFO, \"peer info: %s\", line);\n            env_set_add(es, line);\n        }\n        else\n        {\n            msg(M_WARN, \"validation failed on peer_info line received from client\");\n        }\n    }\n}\n\nstruct buffer\nprepend_dir(const char *dir, const char *path, struct gc_arena *gc)\n{\n    size_t len = strlen(dir) + strlen(PATH_SEPARATOR_STR) + strlen(path) + 1;\n    struct buffer combined_path = alloc_buf_gc(len, gc);\n    buf_printf(&combined_path, \"%s%s%s\", dir, PATH_SEPARATOR_STR, path);\n    ASSERT(combined_path.len > 0);\n\n    return combined_path;\n}\n\nvoid\nprotect_user_pass(struct user_pass *up)\n{\n    if (up->protected)\n    {\n        return;\n    }\n#ifdef _WIN32\n    if (protect_buffer_win32(up->username, sizeof(up->username))\n        && protect_buffer_win32(up->password, sizeof(up->password)))\n    {\n        up->protected = true;\n    }\n    else\n    {\n        purge_user_pass(up, true);\n    }\n#endif\n}\n\nvoid\nunprotect_user_pass(struct user_pass *up)\n{\n    if (!up->protected)\n    {\n        return;\n    }\n#ifdef _WIN32\n    if (unprotect_buffer_win32(up->username, sizeof(up->username))\n        && unprotect_buffer_win32(up->password, sizeof(up->password)))\n    {\n        up->protected = false;\n    }\n    else\n    {\n        purge_user_pass(up, true);\n    }\n#endif\n}\n"
  },
  {
    "path": "src/openvpn/misc.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef MISC_H\n#define MISC_H\n\n#include \"argv.h\"\n#include \"basic.h\"\n#include \"common.h\"\n#include \"env_set.h\"\n#include \"integer.h\"\n#include \"buffer.h\"\n#include \"platform.h\"\n\n/* forward declarations */\nstruct plugin_list;\n\n\n/* Set standard file descriptors to /dev/null */\nvoid set_std_files_to_null(bool stdin_only);\n\n/* Make arrays of strings */\n\nconst char **make_arg_array(const char *first, const char *parms, struct gc_arena *gc);\n\nconst char **make_extended_arg_array(char **p, bool is_inline, struct gc_arena *gc);\n\n/*\n * Get and store a username/password\n */\n\nstruct user_pass\n{\n    bool defined;\n    /* For auth-token username and token can be set individually, so we\n     * use this second bool to track if the token (password) is defined */\n    bool token_defined;\n    bool nocache;\n    bool protected;\n\n/* max length of username/password */\n#ifdef ENABLE_PKCS11\n#define USER_PASS_LEN 4096\n#else\n#define USER_PASS_LEN 128\n#endif\n    /* Note that username and password are expected to be null-terminated */\n    char username[USER_PASS_LEN];\n    char password[USER_PASS_LEN];\n};\n\n#ifdef ENABLE_MANAGEMENT\n/*\n * Challenge response info on client as pushed by server.\n */\nstruct auth_challenge_info\n{\n#define CR_ECHO     (1 << 0) /* echo response when typed by user */\n#define CR_RESPONSE (1 << 1) /* response needed */\n    unsigned int flags;\n\n    const char *user;\n    const char *state_id;\n    const char *challenge_text;\n};\n\n/*\n * Challenge response info on client as pushed by server.\n */\nstruct static_challenge_info\n{\n#define SC_ECHO   (1 << 0) /* echo response when typed by user */\n#define SC_CONCAT (1 << 1) /* concatenate password and response and do not base64 encode */\n    unsigned int flags;\n\n    const char *challenge_text;\n};\n\n#else  /* ifdef ENABLE_MANAGEMENT */\nstruct auth_challenge_info\n{\n};\nstruct static_challenge_info\n{\n};\n#endif /* ifdef ENABLE_MANAGEMENT */\n\n/*\n * Flags for get_user_pass and management_query_user_pass\n */\n#define GET_USER_PASS_MANAGEMENT            (1 << 0)\n/* GET_USER_PASS_SENSITIVE     (1<<1)  not used anymore */\n#define GET_USER_PASS_PASSWORD_ONLY         (1 << 2)\n#define GET_USER_PASS_NEED_OK               (1 << 3)\n#define GET_USER_PASS_NOFATAL               (1 << 4)\n#define GET_USER_PASS_NEED_STR              (1 << 5)\n#define GET_USER_PASS_PREVIOUS_CREDS_FAILED (1 << 6)\n\n#define GET_USER_PASS_DYNAMIC_CHALLENGE     (1 << 7) /**< CRV1 protocol  -- dynamic challenge */\n#define GET_USER_PASS_STATIC_CHALLENGE      (1 << 8) /**< SCRV1 protocol -- static challenge */\n#define GET_USER_PASS_STATIC_CHALLENGE_ECHO (1 << 9) /**< SCRV1 protocol -- echo response */\n\n/** indicates that auth_file is actually inline creds */\n#define GET_USER_PASS_INLINE_CREDS            (1 << 10)\n/** indicates password and response should be concatenated */\n#define GET_USER_PASS_STATIC_CHALLENGE_CONCAT (1 << 11)\n\n/**\n * Retrieves the user credentials from various sources depending on the flags.\n *\n * @param up The user_pass structure to store the retrieved credentials.\n * @param auth_file The path to the authentication file. Might be NULL.\n * @param prefix The prefix to prepend to user prompts.\n * @param flags Additional flags to control the behavior of the function.\n * @param auth_challenge The authentication challenge string.\n * @return true if the user credentials were successfully retrieved, false otherwise.\n */\nbool get_user_pass_cr(struct user_pass *up, const char *auth_file, const char *prefix,\n                      const unsigned int flags, const char *auth_challenge);\n\n/**\n * Retrieves the user credentials from various sources depending on the flags.\n *\n * @param up The user_pass structure to store the retrieved credentials.\n * @param auth_file The path to the authentication file. Might be NULL.\n * @param prefix The prefix to prepend to user prompts.\n * @param flags Additional flags to control the behavior of the function.\n * @return true if the user credentials were successfully retrieved, false otherwise.\n */\nstatic inline bool\nget_user_pass(struct user_pass *up, const char *auth_file, const char *prefix,\n              const unsigned int flags)\n{\n    return get_user_pass_cr(up, auth_file, prefix, flags, NULL);\n}\n\nvoid purge_user_pass(struct user_pass *up, const bool force);\n\n/**\n * Sets the auth-token to token. The method will also purge up if\n * the auth-nocache option is active.\n *\n * @param tk        auth-token userpass to set\n * @param token     token to use as password for the auth-token\n *\n * @note    all parameters to this function must not be null.\n */\nvoid set_auth_token(struct user_pass *tk, const char *token);\n\n/**\n * Sets the auth-token username by base64 decoding the passed\n * username\n *\n * @param tk        auth-token userpass to set\n * @param username  base64 encoded username to set\n *\n * @note    all parameters to this function must not be null.\n */\nvoid set_auth_token_user(struct user_pass *tk, const char *username);\n\n/*\n * Process string received by untrusted peer before\n * printing to console or log file.\n * Assumes that string has been null terminated.\n */\nconst char *safe_print(const char *str, struct gc_arena *gc);\n\nconst char *sanitize_control_message(const char *str, struct gc_arena *gc);\n\n/*\n * /sbin/ip path, may be overridden\n */\n#ifdef ENABLE_IPROUTE\nextern const char *iproute_path;\n#endif\n\n/* helper to parse peer_info received from multi client, validate\n * (this is untrusted data) and put into environment */\nbool validate_peer_info_line(char *line);\n\nvoid output_peer_info_env(struct env_set *es, const char *peer_info);\n\n/**\n * Prepend a directory to a path.\n */\nstruct buffer prepend_dir(const char *dir, const char *path, struct gc_arena *gc);\n\n/**\n * Encrypt username and password buffers in user_pass\n */\nvoid protect_user_pass(struct user_pass *up);\n\n/**\n * Decrypt username and password buffers in user_pass\n */\nvoid unprotect_user_pass(struct user_pass *up);\n\n\n#define _STRINGIFY(S)       #S\n/* *INDENT-OFF* - uncrustify need to ignore this macro */\n#define MAC_FMT             _STRINGIFY(%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx)\n/* *INDENT-ON* */\n#define MAC_PRINT_ARG(_mac) _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5]\n#define MAC_SCAN_ARG(_mac)  &_mac[0], &_mac[1], &_mac[2], &_mac[3], &_mac[4], &_mac[5]\n\n#endif /* ifndef MISC_H */\n"
  },
  {
    "path": "src/openvpn/mroute.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n\n#include \"mroute.h\"\n#include \"proto.h\"\n#include \"error.h\"\n#include \"socket_util.h\"\n\n#include \"memdbg.h\"\n\nvoid\nmroute_addr_init(struct mroute_addr *addr)\n{\n    CLEAR(*addr);\n}\n\n/*\n * Ethernet multicast addresses.\n */\n\nstatic inline bool\nis_mac_mcast_addr(const uint8_t *mac)\n{\n    return (bool)(mac[0] & 1);\n}\n\nstatic inline bool\nis_mac_mcast_maddr(const struct mroute_addr *addr)\n{\n    return (addr->type & MR_ADDR_MASK) == MR_ADDR_ETHER && is_mac_mcast_addr(addr->ether.addr);\n}\n\n/*\n * Don't learn certain addresses.\n */\nbool\nmroute_learnable_address(const struct mroute_addr *addr, struct gc_arena *gc)\n{\n    int i;\n    bool all_zeros = true;\n    bool all_ones = true;\n\n    for (i = 0; i < addr->len; ++i)\n    {\n        int b = addr->raw_addr[i];\n        if (b != 0x00)\n        {\n            all_zeros = false;\n        }\n        if (b != 0xFF)\n        {\n            all_ones = false;\n        }\n    }\n\n    /* only networkss shorter than 8 bits are allowed to be all 0s. */\n    if (all_zeros && !((addr->type & MR_WITH_NETBITS) && (addr->netbits < 8)))\n    {\n        msg(D_MULTI_LOW, \"Can't learn %s: network is all 0s, but netbits >= 8\",\n            mroute_addr_print(addr, gc));\n        return false;\n    }\n\n    if (all_ones)\n    {\n        msg(D_MULTI_LOW, \"Can't learn %s: network is all 1s\", mroute_addr_print(addr, gc));\n        return false;\n    }\n\n    if (is_mac_mcast_maddr(addr))\n    {\n        msg(D_MULTI_LOW, \"Can't learn %s: network is a multicast address\",\n            mroute_addr_print(addr, gc));\n        return false;\n    }\n\n    return true;\n}\n\nstatic inline void\nmroute_get_in_addr_t(struct mroute_addr *ma, const in_addr_t src)\n{\n    if (ma)\n    {\n        ma->type = MR_ADDR_IPV4;\n        ma->netbits = 0;\n        ma->len = 4;\n        ma->v4.addr = src;\n    }\n}\n\nstatic inline void\nmroute_get_in6_addr(struct mroute_addr *ma, const struct in6_addr src)\n{\n    if (ma)\n    {\n        ma->type = MR_ADDR_IPV6;\n        ma->netbits = 0;\n        ma->len = 16;\n        ma->v6.addr = src;\n    }\n}\n\nstatic inline bool\nmroute_is_mcast(const in_addr_t addr)\n{\n    return ((addr & htonl(IP_MCAST_SUBNET_MASK)) == htonl(IP_MCAST_NETWORK));\n}\n\n/* RFC 4291, 2.7, \"binary 11111111 at the start of an address identifies\n *                 the address as being a multicast address\"\n */\nstatic inline bool\nmroute_is_mcast_ipv6(const struct in6_addr addr)\n{\n    return (addr.s6_addr[0] == 0xff);\n}\n\n\nunsigned int\nmroute_extract_addr_ip(struct mroute_addr *src, struct mroute_addr *dest, const struct buffer *buf)\n{\n    unsigned int ret = 0;\n    if (BLEN(buf) >= 1)\n    {\n        switch (OPENVPN_IPH_GET_VER(*BPTR(buf)))\n        {\n            case 4:\n                if (BLENZ(buf) >= sizeof(struct openvpn_iphdr))\n                {\n                    const struct openvpn_iphdr *ip = (const struct openvpn_iphdr *)BPTR(buf);\n\n                    mroute_get_in_addr_t(src, ip->saddr);\n                    mroute_get_in_addr_t(dest, ip->daddr);\n\n                    /* multicast packet? */\n                    if (mroute_is_mcast(ip->daddr))\n                    {\n                        ret |= MROUTE_EXTRACT_MCAST;\n                    }\n\n                    /* IGMP message? */\n                    if (ip->protocol == OPENVPN_IPPROTO_IGMP)\n                    {\n                        ret |= MROUTE_EXTRACT_IGMP;\n                    }\n\n                    ret |= MROUTE_EXTRACT_SUCCEEDED;\n                }\n                break;\n\n            case 6:\n                if (BLENZ(buf) >= sizeof(struct openvpn_ipv6hdr))\n                {\n                    const struct openvpn_ipv6hdr *ipv6 = (const struct openvpn_ipv6hdr *)BPTR(buf);\n#if 0 /* very basic debug */\n                    struct gc_arena gc = gc_new();\n                    msg( M_INFO, \"IPv6 packet! src=%s, dst=%s\",\n                         print_in6_addr( ipv6->saddr, 0, &gc ),\n                         print_in6_addr( ipv6->daddr, 0, &gc ));\n                    gc_free(&gc);\n#endif\n\n                    mroute_get_in6_addr(src, ipv6->saddr);\n                    mroute_get_in6_addr(dest, ipv6->daddr);\n\n                    if (mroute_is_mcast_ipv6(ipv6->daddr))\n                    {\n                        ret |= MROUTE_EXTRACT_MCAST;\n                    }\n\n                    ret |= MROUTE_EXTRACT_SUCCEEDED;\n                }\n                break;\n\n            default:\n                msg(M_WARN, \"IP packet with unknown IP version=%d seen\",\n                    OPENVPN_IPH_GET_VER(*BPTR(buf)));\n        }\n    }\n    return ret;\n}\n\nstatic void\nmroute_copy_ether_to_addr(struct mroute_addr *maddr, const uint8_t *ether_addr, uint16_t vid)\n{\n    maddr->type = MR_ADDR_ETHER;\n    maddr->netbits = 0;\n    maddr->len = OPENVPN_ETH_ALEN;\n    memcpy(maddr->ether.addr, ether_addr, OPENVPN_ETH_ALEN);\n    maddr->len += sizeof(vid);\n    maddr->ether.vid = vid;\n}\n\nunsigned int\nmroute_extract_addr_ether(struct mroute_addr *src, struct mroute_addr *dest, uint16_t vid,\n                          const struct buffer *buf)\n{\n    unsigned int ret = 0;\n    if (BLEN(buf) >= (int)sizeof(struct openvpn_ethhdr))\n    {\n        const struct openvpn_ethhdr *eth = (const struct openvpn_ethhdr *)BPTR(buf);\n        if (src)\n        {\n            mroute_copy_ether_to_addr(src, eth->source, vid);\n        }\n        if (dest)\n        {\n            mroute_copy_ether_to_addr(dest, eth->dest, vid);\n\n            /* ethernet broadcast/multicast packet? */\n            if (is_mac_mcast_addr(eth->dest))\n            {\n                ret |= MROUTE_EXTRACT_BCAST;\n            }\n        }\n\n        ret |= MROUTE_EXTRACT_SUCCEEDED;\n    }\n    return ret;\n}\n\n/*\n * Translate a struct openvpn_sockaddr (osaddr)\n * to a struct mroute_addr (addr).\n */\nbool\nmroute_extract_openvpn_sockaddr(struct mroute_addr *addr, const struct openvpn_sockaddr *osaddr,\n                                bool use_port)\n{\n    switch (osaddr->addr.sa.sa_family)\n    {\n        case AF_INET:\n        {\n            if (use_port)\n            {\n                addr->type = MR_ADDR_IPV4 | MR_WITH_PORT;\n                addr->netbits = 0;\n                addr->len = 6;\n                addr->v4.addr = osaddr->addr.in4.sin_addr.s_addr;\n                addr->v4.port = osaddr->addr.in4.sin_port;\n                if (addr->proto != PROTO_NONE)\n                {\n                    addr->type |= MR_WITH_PROTO;\n                }\n            }\n            else\n            {\n                addr->type = MR_ADDR_IPV4;\n                addr->netbits = 0;\n                addr->len = 4;\n                addr->v4.addr = osaddr->addr.in4.sin_addr.s_addr;\n            }\n            return true;\n        }\n\n        case AF_INET6:\n            if (use_port)\n            {\n                addr->type = MR_ADDR_IPV6 | MR_WITH_PORT;\n                addr->netbits = 0;\n                addr->len = 18;\n                addr->v6.addr = osaddr->addr.in6.sin6_addr;\n                addr->v6.port = osaddr->addr.in6.sin6_port;\n                if (addr->proto != PROTO_NONE)\n                {\n                    addr->type |= MR_WITH_PROTO;\n                }\n            }\n            else\n            {\n                addr->type = MR_ADDR_IPV6;\n                addr->netbits = 0;\n                addr->len = 16;\n                addr->v6.addr = osaddr->addr.in6.sin6_addr;\n            }\n            return true;\n    }\n    return false;\n}\n\n/*\n * Zero off the host bits in an address, leaving\n * only the network bits, using the netbits member of\n * struct mroute_addr as the controlling parameter.\n *\n * TODO: this is called for route-lookup for every yet-unhashed\n * destination address, so for lots of active net-iroutes, this\n * might benefit from some \"zeroize 32 bit at a time\" improvements\n */\nvoid\nmroute_addr_mask_host_bits(struct mroute_addr *ma)\n{\n    if ((ma->type & MR_ADDR_MASK) == MR_ADDR_IPV4)\n    {\n        in_addr_t addr = ntohl(ma->v4.addr);\n        addr &= netbits_to_netmask(ma->netbits);\n        ma->v4.addr = htonl(addr);\n    }\n    else if ((ma->type & MR_ADDR_MASK) == MR_ADDR_IPV6)\n    {\n        int byte = sizeof(ma->v6.addr) - 1; /* rightmost byte in address */\n        int bits_to_clear = 128 - ma->netbits;\n\n        while (byte >= 0 && bits_to_clear > 0)\n        {\n            if (bits_to_clear >= 8)\n            {\n                ma->v6.addr.s6_addr[byte--] = 0;\n                bits_to_clear -= 8;\n            }\n            else\n            {\n                ma->v6.addr.s6_addr[byte--] &= (uint8_t)(0xFF << bits_to_clear);\n                bits_to_clear = 0;\n            }\n        }\n        ASSERT(bits_to_clear == 0);\n    }\n    else\n    {\n        ASSERT(0);\n    }\n}\n\n/*\n * The mroute_addr hash function takes into account the\n * address type, number of bits in the network address,\n * and the actual address.\n */\nuint32_t\nmroute_addr_hash_function(const void *key, uint32_t iv)\n{\n    return hash_func(mroute_addr_hash_ptr((const struct mroute_addr *)key),\n                     mroute_addr_hash_len((const struct mroute_addr *)key), iv);\n}\n\nbool\nmroute_addr_compare_function(const void *key1, const void *key2)\n{\n    return mroute_addr_equal((const struct mroute_addr *)key1, (const struct mroute_addr *)key2);\n}\n\nconst char *\nmroute_addr_print(const struct mroute_addr *ma, struct gc_arena *gc)\n{\n    return mroute_addr_print_ex(ma, MAPF_IA_EMPTY_IF_UNDEF, gc);\n}\n\nconst char *\nmroute_addr_print_ex(const struct mroute_addr *ma, const unsigned int flags, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(64, gc);\n    if (ma)\n    {\n        struct mroute_addr maddr = *ma;\n\n        switch (maddr.type & MR_ADDR_MASK)\n        {\n            case MR_ADDR_ETHER:\n                buf_printf(&out, \"%s\",\n                           format_hex_ex(ma->ether.addr, sizeof(ma->ether.addr), 0, 1, \":\", gc));\n                buf_printf(&out, \"@%hu\", ma->ether.vid);\n                break;\n\n            case MR_ADDR_IPV4:\n            {\n                if ((flags & MAPF_SHOW_ARP) && (maddr.type & MR_ARP))\n                {\n                    buf_printf(&out, \"ARP/\");\n                }\n                if (maddr.type & MR_WITH_PROTO)\n                {\n                    buf_printf(&out, \"%s:\", proto2ascii(maddr.proto, AF_INET, false));\n                }\n                if (flags & MAPF_SHOW_FAMILY)\n                {\n                    buf_printf(&out, \"[AF_INET]\");\n                }\n                buf_printf(&out, \"%s\",\n                           print_in_addr_t(ntohl(maddr.v4.addr),\n                                           (flags & MAPF_IA_EMPTY_IF_UNDEF) ? IA_EMPTY_IF_UNDEF : 0,\n                                           gc));\n                if (maddr.type & MR_WITH_NETBITS)\n                {\n                    if (flags & MAPF_SUBNET)\n                    {\n                        const in_addr_t netmask = netbits_to_netmask(maddr.netbits);\n                        buf_printf(&out, \"/%s\", print_in_addr_t(netmask, 0, gc));\n                    }\n                    else\n                    {\n                        buf_printf(&out, \"/%d\", maddr.netbits);\n                    }\n                }\n                if (maddr.type & MR_WITH_PORT)\n                {\n                    buf_printf(&out, \":%d\", ntohs(maddr.v4.port));\n                }\n            }\n            break;\n\n            case MR_ADDR_IPV6:\n            {\n                if (maddr.type & MR_WITH_PROTO)\n                {\n                    buf_printf(&out, \"%s:\", proto2ascii(maddr.proto, AF_INET6, false));\n                }\n                if (flags & MAPF_SHOW_FAMILY)\n                {\n                    buf_printf(&out, \"[AF_INET6]\");\n                }\n                if (IN6_IS_ADDR_V4MAPPED(&maddr.v6.addr))\n                {\n                    buf_printf(&out, \"%s\",\n                               print_in_addr_t(maddr.v4mappedv6.addr, IA_NET_ORDER, gc));\n                }\n                else if (maddr.type & MR_WITH_PORT)\n                {\n                    buf_printf(&out, \"[%s]\", print_in6_addr(maddr.v6.addr, 0, gc));\n                }\n                else\n                {\n                    buf_printf(&out, \"%s\", print_in6_addr(maddr.v6.addr, 0, gc));\n                }\n                if (maddr.type & MR_WITH_PORT)\n                {\n                    buf_printf(&out, \":%d\", ntohs(maddr.v6.port));\n                }\n                if (maddr.type & MR_WITH_NETBITS)\n                {\n                    buf_printf(&out, \"/%d\", maddr.netbits);\n                }\n            }\n            break;\n\n            default:\n                buf_printf(&out, \"UNKNOWN\");\n                break;\n        }\n        return BSTR(&out);\n    }\n    else\n    {\n        return \"[NULL]\";\n    }\n}\n\n/*\n * mroute_helper's main job is keeping track of\n * currently used CIDR netlengths, so we don't\n * have to cycle through all 33.\n */\n\nstruct mroute_helper *\nmroute_helper_init(int ageable_ttl_secs)\n{\n    struct mroute_helper *mh;\n    ALLOC_OBJ_CLEAR(mh, struct mroute_helper);\n    mh->ageable_ttl_secs = ageable_ttl_secs;\n    return mh;\n}\n\nstatic void\nmroute_helper_regenerate(struct mroute_helper *mh)\n{\n    int i, j = 0;\n    for (i = MR_HELPER_NET_LEN - 1; i >= 0; --i)\n    {\n        if (mh->net_len_refcount[i] > 0)\n        {\n            mh->net_len[j++] = (uint8_t)i;\n        }\n    }\n    mh->n_net_len = j;\n\n#ifdef ENABLE_DEBUG\n    if (check_debug_level(D_MULTI_DEBUG))\n    {\n        struct gc_arena gc = gc_new();\n        struct buffer out = alloc_buf_gc(256, &gc);\n        buf_printf(&out, \"MROUTE CIDR netlen:\");\n        for (i = 0; i < mh->n_net_len; ++i)\n        {\n            buf_printf(&out, \" /%d\", mh->net_len[i]);\n        }\n        dmsg(D_MULTI_DEBUG, \"%s\", BSTR(&out));\n        gc_free(&gc);\n    }\n#endif\n}\n\nvoid\nmroute_helper_add_iroute46(struct mroute_helper *mh, int netbits)\n{\n    if (netbits >= 0)\n    {\n        ASSERT(netbits < MR_HELPER_NET_LEN);\n        ++mh->cache_generation;\n        ++mh->net_len_refcount[netbits];\n        if (mh->net_len_refcount[netbits] == 1)\n        {\n            mroute_helper_regenerate(mh);\n        }\n    }\n}\n\nvoid\nmroute_helper_del_iroute46(struct mroute_helper *mh, int netbits)\n{\n    if (netbits >= 0)\n    {\n        ASSERT(netbits < MR_HELPER_NET_LEN);\n        ++mh->cache_generation;\n        --mh->net_len_refcount[netbits];\n        ASSERT(mh->net_len_refcount[netbits] >= 0);\n        if (!mh->net_len_refcount[netbits])\n        {\n            mroute_helper_regenerate(mh);\n        }\n    }\n}\n\nvoid\nmroute_helper_free(struct mroute_helper *mh)\n{\n    free(mh);\n}\n"
  },
  {
    "path": "src/openvpn/mroute.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n\n#ifndef MROUTE_H\n#define MROUTE_H\n\n#include \"buffer.h\"\n#include \"list.h\"\n#include \"route.h\"\n\n#include <stddef.h>\n\n#define IP_MCAST_SUBNET_MASK ((in_addr_t)240 << 24)\n#define IP_MCAST_NETWORK     ((in_addr_t)224 << 24)\n\n/* Return status values for mroute_extract_addr_from_packet */\n\n#define MROUTE_EXTRACT_SUCCEEDED (1 << 0)\n#define MROUTE_EXTRACT_BCAST     (1 << 1)\n#define MROUTE_EXTRACT_MCAST     (1 << 2)\n#define MROUTE_EXTRACT_IGMP      (1 << 3)\n\n#define MROUTE_SEC_EXTRACT_SUCCEEDED (1 << (0 + MROUTE_SEC_SHIFT))\n#define MROUTE_SEC_EXTRACT_BCAST     (1 << (1 + MROUTE_SEC_SHIFT))\n#define MROUTE_SEC_EXTRACT_MCAST     (1 << (2 + MROUTE_SEC_SHIFT))\n#define MROUTE_SEC_EXTRACT_IGMP      (1 << (3 + MROUTE_SEC_SHIFT))\n\n#define MROUTE_SEC_SHIFT 4\n\n/*\n * Choose the largest address possible with\n * any of our supported types, which is IPv6\n * with port number.\n */\n#define MR_MAX_ADDR_LEN 20\n\n/*\n * Address Types\n */\n#define MR_ADDR_NONE  0\n#define MR_ADDR_ETHER 1\n#define MR_ADDR_IPV4  2\n#define MR_ADDR_IPV6  3\n#define MR_ADDR_MASK  3\n\n/* Address type mask indicating that port # is part of address */\n#define MR_WITH_PORT 4\n\n/* Address type mask indicating that netbits is part of address */\n#define MR_WITH_NETBITS 8\n\n/* Indicates than IPv4 addr was extracted from ARP packet */\n#define MR_ARP 16\n\n/* Address type mask indicating that proto # is part of address */\n#define MR_WITH_PROTO 32\n\n/* MRoute is an on link/scope address needed for DCO on Unix platforms */\n#define MR_ONLINK_DCO_ADDR 64\n\nstruct mroute_addr\n{\n    uint8_t len;     /* length of address */\n    uint8_t proto;\n    uint8_t type;    /* MR_ADDR/MR_WITH flags */\n    uint8_t netbits; /* number of bits in network part of address,\n                      * valid if MR_WITH_NETBITS is set */\n    union\n    {\n        uint8_t raw_addr[MR_MAX_ADDR_LEN]; /* actual address */\n        struct\n        {\n            uint8_t addr[OPENVPN_ETH_ALEN];\n            uint16_t vid;\n        } ether;\n        struct\n        {\n            in_addr_t addr; /* _network order_ IPv4 address */\n            in_port_t port; /* _network order_ TCP/UDP port */\n        } v4;\n        struct\n        {\n            struct in6_addr addr;\n            in_port_t port; /* _network order_ TCP/UDP port */\n        } v6;\n        struct\n        {\n            uint8_t prefix[12];\n            in_addr_t addr; /* _network order_ IPv4 address */\n        } v4mappedv6;\n    };\n};\n\n/* Double-check that struct packing works as expected */\nstatic_assert(offsetof(struct mroute_addr, v4.port) == offsetof(struct mroute_addr, v4) + 4,\n              \"Unexpected struct packing of v4\");\nstatic_assert(offsetof(struct mroute_addr, v6.port) == offsetof(struct mroute_addr, v6) + 16,\n              \"Unexpected struct packing of v6\");\nstatic_assert(offsetof(struct mroute_addr, v4mappedv6.addr)\n                  == offsetof(struct mroute_addr, v4mappedv6) + 12,\n              \"Unexpected struct packing of v4mappedv6\");\n\n/*\n * Number of bits in an address.  Should be raised for IPv6.\n */\n#define MR_HELPER_NET_LEN 129\n\n/*\n * Used to help maintain CIDR routing table.\n */\nstruct mroute_helper\n{\n    unsigned int cache_generation;           /* incremented when route added */\n    int ageable_ttl_secs;                    /* host route cache entry time-to-live*/\n    int n_net_len;                           /* length of net_len array */\n    uint8_t net_len[MR_HELPER_NET_LEN];      /* CIDR netlengths in descending order */\n    int net_len_refcount[MR_HELPER_NET_LEN]; /* refcount of each netlength */\n};\n\nstruct openvpn_sockaddr;\n\nbool mroute_extract_openvpn_sockaddr(struct mroute_addr *addr,\n                                     const struct openvpn_sockaddr *osaddr, bool use_port);\n\nbool mroute_learnable_address(const struct mroute_addr *addr, struct gc_arena *gc);\n\nuint32_t mroute_addr_hash_function(const void *key, uint32_t iv);\n\nbool mroute_addr_compare_function(const void *key1, const void *key2);\n\nvoid mroute_addr_init(struct mroute_addr *addr);\n\nconst char *mroute_addr_print(const struct mroute_addr *ma, struct gc_arena *gc);\n\n#define MAPF_SUBNET            (1 << 0)\n#define MAPF_IA_EMPTY_IF_UNDEF (1 << 1)\n#define MAPF_SHOW_ARP          (1 << 2)\n#define MAPF_SHOW_FAMILY       (1 << 3)\nconst char *mroute_addr_print_ex(const struct mroute_addr *ma, const unsigned int flags,\n                                 struct gc_arena *gc);\n\nvoid mroute_addr_mask_host_bits(struct mroute_addr *ma);\n\nstruct mroute_helper *mroute_helper_init(int ageable_ttl_secs);\n\nvoid mroute_helper_free(struct mroute_helper *mh);\n\nvoid mroute_helper_add_iroute46(struct mroute_helper *mh, int netbits);\n\nvoid mroute_helper_del_iroute46(struct mroute_helper *mh, int netbits);\n\nunsigned int mroute_extract_addr_ip(struct mroute_addr *src, struct mroute_addr *dest,\n                                    const struct buffer *buf);\n\nunsigned int mroute_extract_addr_ether(struct mroute_addr *src, struct mroute_addr *dest,\n                                       uint16_t vid, const struct buffer *buf);\n\n/*\n * Given a raw packet in buf, return the src and dest\n * addresses of the packet.\n */\nstatic inline unsigned int\nmroute_extract_addr_from_packet(struct mroute_addr *src, struct mroute_addr *dest, uint16_t vid,\n                                const struct buffer *buf, int tunnel_type)\n{\n    unsigned int ret = 0;\n    verify_align_4(buf);\n\n    /*\n     * Since we don't really need the protocol on vaddresses for internal VPN\n     * payload packets, make sure we have the same value to avoid hashing insert\n     * and search issues.\n     */\n    src->proto = 0;\n    dest->proto = src->proto;\n\n    if (tunnel_type == DEV_TYPE_TUN)\n    {\n        ret = mroute_extract_addr_ip(src, dest, buf);\n    }\n    else if (tunnel_type == DEV_TYPE_TAP)\n    {\n        ret = mroute_extract_addr_ether(src, dest, vid, buf);\n    }\n    return ret;\n}\n\nstatic inline bool\nmroute_addr_equal(const struct mroute_addr *a1, const struct mroute_addr *a2)\n{\n    if (a1->type != a2->type)\n    {\n        return false;\n    }\n    if (a1->proto != a2->proto)\n    {\n        return false;\n    }\n    if (a1->netbits != a2->netbits)\n    {\n        return false;\n    }\n    if (a1->len != a2->len)\n    {\n        return false;\n    }\n    return memcmp(a1->raw_addr, a2->raw_addr, a1->len) == 0;\n}\n\nstatic inline const uint8_t *\nmroute_addr_hash_ptr(const struct mroute_addr *a)\n{\n    /* NOTE: depends on ordering of struct mroute_addr */\n    return (uint8_t *)&a->proto;\n}\n\nstatic inline uint32_t\nmroute_addr_hash_len(const struct mroute_addr *a)\n{\n    return (uint32_t)a->len + 3;\n}\n\nstatic inline void\nmroute_extract_in_addr_t(struct mroute_addr *dest, const in_addr_t src)\n{\n    dest->type = MR_ADDR_IPV4;\n    dest->netbits = 0;\n    dest->len = 4;\n    dest->v4.addr = htonl(src);\n}\n\nstatic inline in_addr_t\nin_addr_t_from_mroute_addr(const struct mroute_addr *addr)\n{\n    if ((addr->type & MR_ADDR_MASK) == MR_ADDR_IPV4 && addr->netbits == 0 && addr->len == 4)\n    {\n        return ntohl(addr->v4.addr);\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nstatic inline void\nmroute_addr_reset(struct mroute_addr *ma)\n{\n    ma->len = 0;\n    ma->type = MR_ADDR_NONE;\n}\n\n#endif /* MROUTE_H */\n"
  },
  {
    "path": "src/openvpn/mss.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n#include \"error.h\"\n#include \"mss.h\"\n#include \"crypto.h\"\n#include \"ssl_common.h\"\n#include \"memdbg.h\"\n#include \"forward.h\"\n\n/*\n * Lower MSS on TCP SYN packets to fix MTU\n * problems which arise from protocol\n * encapsulation.\n */\n\n/*\n * IPv4 packet: find TCP header, check flags for \"SYN\"\n *              if yes, hand to mss_fixup_dowork()\n */\nvoid\nmss_fixup_ipv4(struct buffer *buf, uint16_t maxmss)\n{\n    const struct openvpn_iphdr *pip;\n    int hlen;\n\n    if (BLENZ(buf) < sizeof(struct openvpn_iphdr))\n    {\n        return;\n    }\n\n    verify_align_4(buf);\n    pip = (struct openvpn_iphdr *)BPTR(buf);\n\n    hlen = OPENVPN_IPH_GET_LEN(pip->version_len);\n\n    if (pip->protocol == OPENVPN_IPPROTO_TCP && ntohs(pip->tot_len) == BLEN(buf)\n        && (ntohs(pip->frag_off) & OPENVPN_IP_OFFMASK) == 0 && hlen <= BLEN(buf)\n        && BLEN(buf) - hlen >= (int)sizeof(struct openvpn_tcphdr))\n    {\n        struct buffer newbuf = *buf;\n        if (buf_advance(&newbuf, hlen))\n        {\n            struct openvpn_tcphdr *tc = (struct openvpn_tcphdr *)BPTR(&newbuf);\n            if (tc->flags & OPENVPN_TCPH_SYN_MASK)\n            {\n                mss_fixup_dowork(&newbuf, maxmss);\n            }\n        }\n    }\n}\n\n/*\n * IPv6 packet: find TCP header, check flags for \"SYN\"\n *              if yes, hand to mss_fixup_dowork()\n *              (IPv6 header structure is sufficiently different from IPv4...)\n */\nvoid\nmss_fixup_ipv6(struct buffer *buf, uint16_t maxmss)\n{\n    const struct openvpn_ipv6hdr *pip6;\n    struct buffer newbuf;\n\n    if (BLENZ(buf) < sizeof(struct openvpn_ipv6hdr))\n    {\n        return;\n    }\n\n    verify_align_4(buf);\n    pip6 = (struct openvpn_ipv6hdr *)BPTR(buf);\n\n    /* do we have the full IPv6 packet?\n     * \"payload_len\" does not include IPv6 header (+40 bytes)\n     */\n    if (BLEN(buf) != ntohs(pip6->payload_len) + 40)\n    {\n        return;\n    }\n\n    /* follow header chain until we reach final header, then check for TCP\n     *\n     * An IPv6 packet could, theoretically, have a chain of multiple headers\n     * before the final header (TCP, UDP, ...), so we'd need to walk that\n     * chain (see RFC 2460 and RFC 6564 for details).\n     *\n     * In practice, \"most typically used\" extension headers (AH, routing,\n     * fragment, mobility) are very unlikely to be seen inside an OpenVPN\n     * tun, so for now, we only handle the case of \"single next header = TCP\"\n     */\n    if (pip6->nexthdr != OPENVPN_IPPROTO_TCP)\n    {\n        return;\n    }\n\n    /* skip IPv6 header (40 bytes),\n     * verify remainder is large enough to contain a full TCP header\n     */\n    newbuf = *buf;\n    if (buf_advance(&newbuf, 40) && BLENZ(&newbuf) >= sizeof(struct openvpn_tcphdr))\n    {\n        struct openvpn_tcphdr *tc = (struct openvpn_tcphdr *)BPTR(&newbuf);\n        if (tc->flags & OPENVPN_TCPH_SYN_MASK)\n        {\n            mss_fixup_dowork(&newbuf, maxmss - 20);\n        }\n    }\n}\n\n/*\n * change TCP MSS option in SYN/SYN-ACK packets, if present\n * this is generic for IPv4 and IPv6, as the TCP header is the same\n */\n\nvoid\nmss_fixup_dowork(struct buffer *buf, uint16_t maxmss)\n{\n    int olen, optlen;\n    uint8_t *opt;\n\n    if (BLENZ(buf) < sizeof(struct openvpn_tcphdr))\n    {\n        return;\n    }\n\n    verify_align_4(buf);\n    struct openvpn_tcphdr *tc = (struct openvpn_tcphdr *)BPTR(buf);\n    int hlen = OPENVPN_TCPH_GET_DOFF(tc->doff_res);\n\n    /* Invalid header length or header without options. */\n    if (hlen <= (int)sizeof(struct openvpn_tcphdr) || hlen > BLEN(buf))\n    {\n        return;\n    }\n\n    for (olen = hlen - (int)sizeof(struct openvpn_tcphdr), opt = (uint8_t *)(tc + 1); olen > 1;\n         olen -= optlen, opt += optlen)\n    {\n        if (*opt == OPENVPN_TCPOPT_EOL)\n        {\n            break;\n        }\n        if (*opt == OPENVPN_TCPOPT_NOP)\n        {\n            optlen = 1;\n            continue;\n        }\n\n        optlen = *(opt + 1);\n        if (optlen <= 0 || optlen > olen)\n        {\n            break;\n        }\n        if (*opt == OPENVPN_TCPOPT_MAXSEG)\n        {\n            if (optlen != OPENVPN_TCPOLEN_MAXSEG)\n            {\n                continue;\n            }\n            uint16_t mssval = (uint16_t)(opt[2] << 8) + opt[3];\n            if (mssval > maxmss)\n            {\n                dmsg(D_MSS, \"MSS: %\" PRIu16 \" -> %\" PRIu16, mssval, maxmss);\n                opt[2] = (uint8_t)((maxmss >> 8) & 0xff);\n                opt[3] = (uint8_t)(maxmss & 0xff);\n                int32_t accumulate = htons(mssval);\n                accumulate -= htons(maxmss);\n                ADJUST_CHECKSUM(accumulate, tc->check);\n            }\n        }\n    }\n}\n\nstatic inline size_t\nadjust_payload_max_cbc(const struct key_type *kt, size_t target)\n{\n    if (!cipher_kt_mode_cbc(kt->cipher))\n    {\n        /* With stream ciphers (or block cipher in stream modes like CFB, AEAD)\n         * we can just use the target as is */\n        return target;\n    }\n    else\n    {\n        /* With CBC we need at least one extra byte for padding and then need\n         * to ensure that the resulting CBC ciphertext length, which is always\n         * a multiple of the block size, is not larger than the target value */\n        size_t block_size = cipher_kt_block_size(kt->cipher);\n        target = round_down_size(target, block_size);\n        return target - 1;\n    }\n}\n\nstatic size_t\nget_ip_encap_overhead(const struct options *options, const struct link_socket_info *lsi)\n{\n    /* Add the overhead of the encapsulating IP packets */\n    sa_family_t af;\n    int proto;\n\n    if (lsi && lsi->lsa)\n    {\n        af = lsi->lsa->actual.dest.addr.sa.sa_family;\n        proto = lsi->proto;\n    }\n    else\n    {\n        /* In the early init before the connection is established or we\n         * are in listen mode we can only make an educated guess\n         * from the af of the connection entry, in p2mp this will be\n         * later updated */\n        af = options->ce.af;\n        proto = options->ce.proto;\n    }\n    return datagram_overhead(af, proto);\n}\n\nstatic void\nframe_calculate_fragment(struct frame *frame, struct key_type *kt, const struct options *options,\n                         struct link_socket_info *lsi)\n{\n#if defined(ENABLE_FRAGMENT)\n    size_t overhead;\n\n    overhead = frame_calculate_protocol_header_size(kt, options, false);\n\n    if (options->ce.fragment_encap)\n    {\n        overhead += get_ip_encap_overhead(options, lsi);\n    }\n\n    size_t target = options->ce.fragment - overhead;\n    /* The 4 bytes of header that fragment adds itself. The other extra payload\n     * bytes (Ethernet header/compression) are handled by the fragment code\n     * just as part of the payload and therefore automatically taken into\n     * account if the packet needs to fragmented */\n    frame->max_fragment_size = clamp_size_to_int(adjust_payload_max_cbc(kt, target)) - 4;\n\n    if (cipher_kt_mode_cbc(kt->cipher))\n    {\n        /* The packet id gets added to *each* fragment in CBC mode, so we need\n         * to account for it */\n        frame->max_fragment_size -= calc_packet_id_size_dc(options, kt);\n    }\n#endif\n}\n\nstatic void\nframe_calculate_mssfix(struct frame *frame, struct key_type *kt, const struct options *options,\n                       struct link_socket_info *lsi)\n{\n    if (options->ce.mssfix_fixed)\n    {\n        /* we subtract IPv4 and TCP overhead here, mssfix method will add the\n         * extra 20 for IPv6 */\n        frame->mss_fix = (uint16_t)(options->ce.mssfix - (20 + 20));\n        return;\n    }\n\n    size_t overhead, payload_overhead;\n\n    overhead = frame_calculate_protocol_header_size(kt, options, false);\n\n    /* Calculate the number of bytes that the payload differs from the payload\n     * MTU. This are fragment/compression/ethernet headers */\n    payload_overhead = frame_calculate_payload_overhead(frame->extra_tun, options, kt);\n\n    /* We are in a \"liberal\" position with respect to MSS,\n     * i.e. we assume that MSS can be calculated from MTU\n     * by subtracting out only the IP and TCP header sizes\n     * without options.\n     *\n     * (RFC 879, section 7). */\n\n    if (options->ce.mssfix_encap)\n    {\n        /* Add the overhead of the encapsulating IP packets */\n        overhead += get_ip_encap_overhead(options, lsi);\n    }\n\n    /* Add 20 bytes for the IPv4 header and 20 byte for the TCP header of the\n     * payload, the mssfix method will add 20 extra if payload is IPv6 */\n    payload_overhead += 20 + 20;\n\n    /* Calculate the maximum MSS value from the max link layer size specified\n     * by ce.mssfix */\n\n    /* This is the target value our payload needs to be smaller */\n    size_t target = options->ce.mssfix - overhead;\n    frame->mss_fix = (uint16_t)(adjust_payload_max_cbc(kt, target) - payload_overhead);\n}\n\nvoid\nframe_calculate_dynamic(struct frame *frame, struct key_type *kt, const struct options *options,\n                        struct link_socket_info *lsi)\n{\n    if (options->ce.fragment > 0)\n    {\n        frame_calculate_fragment(frame, kt, options, lsi);\n    }\n\n    if (options->ce.mssfix > 0)\n    {\n        frame_calculate_mssfix(frame, kt, options, lsi);\n    }\n}\n\n/*\n * Adjust frame structure based on a Path MTU value given\n * to us by the OS.\n */\nvoid\nframe_adjust_path_mtu(struct context *c)\n{\n    struct link_socket_info *lsi = get_link_socket_info(c);\n    struct options *o = &c->options;\n\n    int pmtu = c->c2.link_sockets[0]->mtu;\n    sa_family_t af = lsi->lsa->actual.dest.addr.sa.sa_family;\n    int proto = lsi->proto;\n\n    int encap_overhead = datagram_overhead(af, proto);\n\n    /* check if mssfix and fragment need to be adjusted */\n    if (pmtu < o->ce.mssfix || (o->ce.mssfix_encap && pmtu < o->ce.mssfix + encap_overhead))\n    {\n        const char *mtustr = o->ce.mssfix_encap ? \" mtu\" : \"\";\n        msg(D_MTU_INFO,\n            \"Note adjusting 'mssfix %d%s' to 'mssfix %d mtu' \"\n            \"according to path MTU discovery\",\n            o->ce.mssfix, mtustr, pmtu);\n        o->ce.mssfix = pmtu;\n        o->ce.mssfix_encap = true;\n        frame_calculate_dynamic(&c->c2.frame, &c->c1.ks.key_type, o, lsi);\n    }\n\n#if defined(ENABLE_FRAGMENT)\n    if (pmtu < o->ce.fragment || (o->ce.fragment_encap && pmtu < o->ce.fragment + encap_overhead))\n    {\n        const char *mtustr = o->ce.fragment_encap ? \" mtu\" : \"\";\n        msg(D_MTU_INFO,\n            \"Note adjusting 'fragment %d%s' to 'fragment %d mtu' \"\n            \"according to path MTU discovery\",\n            o->ce.fragment, mtustr, pmtu);\n        o->ce.fragment = pmtu;\n        o->ce.fragment_encap = true;\n        frame_calculate_dynamic(&c->c2.frame_fragment, &c->c1.ks.key_type, o, lsi);\n    }\n#endif\n}\n"
  },
  {
    "path": "src/openvpn/mss.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef MSS_H\n#define MSS_H\n\n#include \"proto.h\"\n#include \"error.h\"\n#include \"mtu.h\"\n#include \"socket.h\"\n#include \"ssl_common.h\"\n\nvoid mss_fixup_ipv4(struct buffer *buf, uint16_t maxmss);\n\nvoid mss_fixup_ipv6(struct buffer *buf, uint16_t maxmss);\n\nvoid mss_fixup_dowork(struct buffer *buf, uint16_t maxmss);\n\n/** Set the --mssfix option. */\nvoid frame_calculate_dynamic(struct frame *frame, struct key_type *kt,\n                             const struct options *options, struct link_socket_info *lsi);\n\n/**\n * Checks and adjusts the fragment and mssfix value according to the\n * discovered path mtu value\n * @param c     context to adjust\n */\nvoid frame_adjust_path_mtu(struct context *c);\n\n#endif /* ifndef MSS_H */\n"
  },
  {
    "path": "src/openvpn/mtcp.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"multi.h\"\n#include \"forward.h\"\n#include \"mtcp.h\"\n#include \"multi_io.h\"\n\n#include \"memdbg.h\"\n\n#ifdef HAVE_SYS_INOTIFY_H\n#include <sys/inotify.h>\n#endif\n\nstruct ta_iow_flags\n{\n    unsigned int flags;\n    unsigned int ret;\n    unsigned int tun;\n    unsigned int sock;\n};\n\nstruct multi_instance *\nmulti_create_instance_tcp(struct multi_context *m, struct link_socket *sock)\n{\n    struct gc_arena gc = gc_new();\n    struct multi_instance *mi = NULL;\n    struct hash *hash = m->hash;\n\n    mi = multi_create_instance(m, NULL, sock);\n    if (mi)\n    {\n        mi->real.proto = sock->info.proto;\n        struct hash_element *he;\n        const uint32_t hv = hash_value(hash, &mi->real);\n        struct hash_bucket *bucket = hash_bucket(hash, hv);\n\n        multi_assign_peer_id(m, mi);\n\n        he = hash_lookup_fast(hash, bucket, &mi->real, hv);\n\n        if (he)\n        {\n            struct multi_instance *oldmi = (struct multi_instance *)he->value;\n            msg(D_MULTI_LOW,\n                \"MULTI TCP: new incoming client address matches existing client address -- new client takes precedence\");\n            oldmi->did_real_hash = false;\n            multi_close_instance(m, oldmi, false);\n            he->key = &mi->real;\n            he->value = mi;\n        }\n        else\n        {\n            hash_add_fast(hash, bucket, &mi->real, hv, mi);\n        }\n\n        mi->did_real_hash = true;\n    }\n\n#ifdef ENABLE_DEBUG\n    if (mi)\n    {\n        dmsg(D_MULTI_DEBUG, \"MULTI TCP: instance added: %s\", mroute_addr_print(&mi->real, &gc));\n    }\n    else\n    {\n        dmsg(D_MULTI_DEBUG, \"MULTI TCP: new client instance failed\");\n    }\n#endif\n\n    gc_free(&gc);\n    ASSERT(!(mi && mi->halt));\n    return mi;\n}\n\nbool\nmulti_tcp_instance_specific_init(struct multi_context *m, struct multi_instance *mi)\n{\n    /* buffer for queued TCP socket output packets */\n    mi->tcp_link_out_deferred = mbuf_init(m->top.options.n_bcast_buf);\n\n    ASSERT(mi->context.c2.link_sockets);\n    ASSERT(mi->context.c2.link_sockets[0]);\n    ASSERT(mi->context.c2.link_sockets[0]->info.lsa);\n    ASSERT(mi->context.c2.link_sockets[0]->mode == LS_MODE_TCP_ACCEPT_FROM);\n    ASSERT(mi->context.c2.link_sockets[0]->info.lsa->actual.dest.addr.sa.sa_family == AF_INET\n           || mi->context.c2.link_sockets[0]->info.lsa->actual.dest.addr.sa.sa_family == AF_INET6);\n    mi->real.proto = mi->context.c2.link_sockets[0]->info.proto;\n    if (!mroute_extract_openvpn_sockaddr(\n            &mi->real, &mi->context.c2.link_sockets[0]->info.lsa->actual.dest, true))\n    {\n        msg(D_MULTI_ERRORS, \"MULTI TCP: TCP client address is undefined\");\n        return false;\n    }\n    return true;\n}\n\nvoid\nmulti_tcp_instance_specific_free(struct multi_instance *mi)\n{\n    mbuf_free(mi->tcp_link_out_deferred);\n}\n\nvoid\nmulti_tcp_delete_event(struct multi_io *multi_io, event_t event)\n{\n    if (multi_io && multi_io->es)\n    {\n        event_del(multi_io->es, event);\n    }\n}\n\nvoid\nmulti_tcp_dereference_instance(struct multi_io *multi_io, struct multi_instance *mi)\n{\n    struct link_socket *sock = mi->context.c2.link_sockets[0];\n    if (sock && mi->socket_set_called)\n    {\n        event_del(multi_io->es, socket_event_handle(sock));\n        mi->socket_set_called = false;\n    }\n    multi_io->n_esr = 0;\n}\n\nbool\nmulti_tcp_process_outgoing_link_ready(struct multi_context *m, struct multi_instance *mi,\n                                      const unsigned int mpp_flags)\n{\n    struct mbuf_item item;\n    bool ret = true;\n    ASSERT(mi);\n\n    /* extract from queue */\n    if (mbuf_extract_item(mi->tcp_link_out_deferred, &item)) /* ciphertext IP packet */\n    {\n        dmsg(D_MULTI_TCP, \"MULTI TCP: transmitting previously deferred packet\");\n\n        ASSERT(mi == item.instance);\n        mi->context.c2.to_link = item.buffer->buf;\n        ret = multi_process_outgoing_link_dowork(m, mi, mpp_flags);\n        if (!ret)\n        {\n            mi = NULL;\n        }\n        mbuf_free_buf(item.buffer);\n    }\n    return ret;\n}\n\nbool\nmulti_tcp_process_outgoing_link(struct multi_context *m, bool defer, const unsigned int mpp_flags)\n{\n    struct multi_instance *mi = multi_process_outgoing_link_pre(m);\n    bool ret = true;\n\n    if (mi)\n    {\n        if ((defer && !proto_is_dgram(mi->context.c2.link_sockets[0]->info.proto))\n            || mbuf_defined(mi->tcp_link_out_deferred))\n        {\n            /* save to queue */\n            struct buffer *buf = &mi->context.c2.to_link;\n            if (BLEN(buf) > 0)\n            {\n                struct mbuf_buffer *mb = mbuf_alloc_buf(buf);\n                struct mbuf_item item;\n\n                set_prefix(mi);\n                dmsg(D_MULTI_TCP, \"MULTI TCP: queuing deferred packet\");\n                item.buffer = mb;\n                item.instance = mi;\n                mbuf_add_item(mi->tcp_link_out_deferred, &item);\n                mbuf_free_buf(mb);\n                buf_reset(buf);\n                ret = multi_process_post(m, mi, mpp_flags);\n                if (!ret)\n                {\n                    mi = NULL;\n                }\n                clear_prefix();\n            }\n        }\n        else\n        {\n            ret = multi_process_outgoing_link_dowork(m, mi, mpp_flags);\n            if (!ret)\n            {\n                mi = NULL;\n            }\n        }\n    }\n    return ret;\n}\n"
  },
  {
    "path": "src/openvpn/mtcp.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * TCP specific code for --mode server\n */\n\n#ifndef MTCP_H\n#define MTCP_H\n\n#include \"event.h\"\n\nstruct multi_context;\nstruct multi_instance;\nstruct context;\n\nvoid multi_tcp_dereference_instance(struct multi_io *multi_io, struct multi_instance *mi);\n\nbool multi_tcp_instance_specific_init(struct multi_context *m, struct multi_instance *mi);\n\nvoid multi_tcp_instance_specific_free(struct multi_instance *mi);\n\nbool multi_tcp_process_outgoing_link(struct multi_context *m, bool defer,\n                                     const unsigned int mpp_flags);\n\nbool multi_tcp_process_outgoing_link_ready(struct multi_context *m, struct multi_instance *mi,\n                                           const unsigned int mpp_flags);\n\nstruct multi_instance *multi_create_instance_tcp(struct multi_context *m, struct link_socket *sock);\n\nvoid multi_tcp_link_out_deferred(struct multi_context *m, struct multi_instance *mi);\n\nvoid multi_tcp_delete_event(struct multi_io *multi_io, event_t event);\n\n#endif /* ifndef MTCP_H */\n"
  },
  {
    "path": "src/openvpn/mtu.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"common.h\"\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"integer.h\"\n#include \"mtu.h\"\n#include \"options.h\"\n#include \"crypto.h\"\n\n#include \"memdbg.h\"\n\n/* allocate a buffer for socket or tun layer */\nvoid\nalloc_buf_sock_tun(struct buffer *buf, const struct frame *frame)\n{\n    /* allocate buffer for overlapped I/O */\n    *buf = alloc_buf(BUF_SIZE(frame));\n    ASSERT(buf_init(buf, frame->buf.headroom));\n    buf->len = frame->buf.payload_size;\n    ASSERT(buf_safe(buf, 0));\n}\n\nunsigned int\ncalc_packet_id_size_dc(const struct options *options, const struct key_type *kt)\n{\n    bool tlsmode = options->tls_server || options->tls_client;\n\n    bool packet_id_long_form = !tlsmode || cipher_kt_mode_ofb_cfb(kt->cipher);\n\n    return packet_id_size(packet_id_long_form);\n}\n\nsize_t\nframe_calculate_protocol_header_size(const struct key_type *kt, const struct options *options,\n                                     bool occ)\n{\n    /* Sum of all the overhead that reduces the usable packet size */\n    size_t header_size = 0;\n\n    bool tlsmode = options->tls_server || options->tls_client;\n\n    /* A socks proxy adds extra header to each packet\n     * (we only support Socks with IPv4, this value is different for IPv6) */\n    if (options->ce.socks_proxy_server && proto_is_udp(options->ce.proto))\n    {\n        header_size += SOCKS_UDPv4_HEADROOM;\n    }\n\n    /* TCP stream based packets have a 16 bit length field */\n    if (proto_is_tcp(options->ce.proto))\n    {\n        header_size += 2;\n    }\n\n    /* Add the opcode and peerid */\n    if (tlsmode)\n    {\n        header_size += options->use_peer_id ? 4 : 1;\n    }\n\n    unsigned int pkt_id_size = calc_packet_id_size_dc(options, kt);\n\n    /* For figuring out the crypto overhead, we need the size of the payload\n     * including all headers that also get encrypted as part of the payload */\n    header_size += calculate_crypto_overhead(kt, pkt_id_size, occ);\n    return header_size;\n}\n\n\nsize_t\nframe_calculate_payload_overhead(size_t extra_tun, const struct options *options,\n                                 const struct key_type *kt)\n{\n    size_t overhead = 0;\n\n    /* This is the overhead of tap device that is not included in the MTU itself\n     * i.e. Ethernet header that we still need to transmit as part of the\n     * payload, this is set to 0 by caller if not applicable */\n    overhead += extra_tun;\n\n#if defined(USE_COMP)\n    /* v1 Compression schemes add 1 byte header. V2 only adds a header when it\n     * does not increase the packet length. We ignore the unlikely escaping\n     * for tap here */\n    if (options->comp.alg == COMP_ALG_LZ4 || options->comp.alg == COMP_ALG_STUB\n        || options->comp.alg == COMP_ALG_LZO)\n    {\n        overhead += 1;\n    }\n#endif\n#if defined(ENABLE_FRAGMENT)\n    /* Add the size of the fragment header (uint32_t) */\n    if (options->ce.fragment)\n    {\n        overhead += 4;\n    }\n#endif\n\n    if (cipher_kt_mode_cbc(kt->cipher))\n    {\n        /* The packet id is part of the plain text payload instead of the\n         * cleartext protocol header and needs to be included in the payload\n         * overhead instead of the protocol header */\n        overhead += calc_packet_id_size_dc(options, kt);\n    }\n\n    return overhead;\n}\n\nsize_t\nframe_calculate_payload_size(const struct frame *frame, const struct options *options,\n                             const struct key_type *kt)\n{\n    size_t payload_size = options->ce.tun_mtu;\n    payload_size += frame_calculate_payload_overhead(frame->extra_tun, options, kt);\n    return payload_size;\n}\n\nsize_t\ncalc_options_string_link_mtu(const struct options *o, const struct frame *frame)\n{\n    struct key_type occ_kt;\n\n    /* neither --secret nor TLS mode */\n    if (!o->tls_client && !o->tls_server && !o->shared_secret_file)\n    {\n        init_key_type(&occ_kt, \"none\", \"none\", false, false);\n        return frame_calculate_payload_size(frame, o, &occ_kt);\n    }\n\n    /* o->ciphername might be BF-CBC even though the underlying SSL library\n     * does not support it. For this reason we workaround this corner case\n     * by pretending to have no encryption enabled and by manually adding\n     * the required packet overhead to the MTU computation.\n     */\n    const char *ciphername = o->ciphername;\n\n    size_t overhead = 0;\n\n    if (strcmp(o->ciphername, \"BF-CBC\") == 0)\n    {\n        /* none has no overhead, so use this to later add only --auth\n         * overhead */\n\n        /* overhead of BF-CBC: 64 bit block size, 64 bit IV size */\n        overhead += 64 / 8 + 64 / 8;\n        /* set ciphername to none, so its size does get added in the\n         * fake_kt and the cipher is not tried to be resolved */\n        ciphername = \"none\";\n    }\n\n    /* We pass tlsmode always true here since as we do not need to check if\n     * the ciphers are actually valid for non tls in occ calucation */\n    init_key_type(&occ_kt, ciphername, o->authname, true, false);\n\n    size_t payload = frame_calculate_payload_size(frame, o, &occ_kt);\n    overhead += frame_calculate_protocol_header_size(&occ_kt, o, true);\n\n    return payload + overhead;\n}\n\nvoid\nframe_print(const struct frame *frame, msglvl_t msglevel, const char *prefix)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer out = alloc_buf_gc(256, &gc);\n    if (prefix)\n    {\n        buf_printf(&out, \"%s \", prefix);\n    }\n    buf_printf(&out, \"[\");\n    buf_printf(&out, \" mss_fix:%\" PRIu16, frame->mss_fix);\n#ifdef ENABLE_FRAGMENT\n    buf_printf(&out, \" max_frag:%d\", frame->max_fragment_size);\n#endif\n    buf_printf(&out, \" tun_mtu:%d\", frame->tun_mtu);\n    buf_printf(&out, \" tun_max_mtu:%d\", frame->tun_max_mtu);\n    buf_printf(&out, \" headroom:%d\", frame->buf.headroom);\n    buf_printf(&out, \" payload:%d\", frame->buf.payload_size);\n    buf_printf(&out, \" tailroom:%d\", frame->buf.tailroom);\n    buf_printf(&out, \" ET:%d\", frame->extra_tun);\n    buf_printf(&out, \" ]\");\n\n    msg(msglevel, \"%s\", out.data);\n    gc_free(&gc);\n}\n\n#define MTUDISC_NOT_SUPPORTED_MSG \"--mtu-disc is not supported on this OS\"\n\nvoid\nset_mtu_discover_type(socket_descriptor_t sd, int mtu_type, sa_family_t proto_af)\n{\n    if (mtu_type >= 0)\n    {\n        switch (proto_af)\n        {\n#if defined(IP_MTU_DISCOVER)\n            case AF_INET:\n                if (setsockopt(sd, IPPROTO_IP, IP_MTU_DISCOVER, (void *)&mtu_type,\n                               sizeof(mtu_type)))\n                {\n                    msg(M_ERR, \"Error setting IP_MTU_DISCOVER type=%d on TCP/UDP socket\", mtu_type);\n                }\n                break;\n\n#endif\n#if defined(IPV6_MTU_DISCOVER)\n            case AF_INET6:\n                if (setsockopt(sd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, (void *)&mtu_type,\n                               sizeof(mtu_type)))\n                {\n                    msg(M_ERR, \"Error setting IPV6_MTU_DISCOVER type=%d on TCP6/UDP6 socket\",\n                        mtu_type);\n                }\n                break;\n\n#endif\n            default:\n                msg(M_FATAL, MTUDISC_NOT_SUPPORTED_MSG);\n                break;\n        }\n    }\n}\n\nint\ntranslate_mtu_discover_type_name(const char *name)\n{\n#if defined(IP_PMTUDISC_DONT) && defined(IP_PMTUDISC_WANT) && defined(IP_PMTUDISC_DO)\n    if (!strcmp(name, \"yes\"))\n    {\n        return IP_PMTUDISC_DO;\n    }\n    if (!strcmp(name, \"maybe\"))\n    {\n        return IP_PMTUDISC_WANT;\n    }\n    if (!strcmp(name, \"no\"))\n    {\n        return IP_PMTUDISC_DONT;\n    }\n    msg(M_FATAL, \"invalid --mtu-disc type: '%s' -- valid types are 'yes', 'maybe', or 'no'\", name);\n#else\n    msg(M_FATAL, MTUDISC_NOT_SUPPORTED_MSG);\n#endif\n    return -1; /* NOTREACHED */\n}\n\n#if EXTENDED_SOCKET_ERROR_CAPABILITY\n\n#include <linux/errqueue.h>\n\nstruct probehdr\n{\n    uint32_t ttl;\n    struct timeval tv;\n};\n\nconst char *\nformat_extended_socket_error(int fd, int *mtu, struct gc_arena *gc)\n{\n    struct probehdr rcvbuf;\n    struct iovec iov;\n    struct msghdr msg;\n    struct cmsghdr *cmsg;\n    struct sock_extended_err *e;\n    struct sockaddr_storage addr;\n    struct buffer out = alloc_buf_gc(256, gc);\n    char *cbuf = (char *)gc_malloc(256, false, gc);\n\n    *mtu = 0;\n\n    while (true)\n    {\n        memset(&rcvbuf, -1, sizeof(rcvbuf));\n        iov.iov_base = &rcvbuf;\n        iov.iov_len = sizeof(rcvbuf);\n        msg.msg_name = (uint8_t *)&addr;\n        msg.msg_namelen = sizeof(addr);\n        msg.msg_iov = &iov;\n        msg.msg_iovlen = 1;\n        msg.msg_flags = 0;\n        msg.msg_control = cbuf;\n        msg.msg_controllen = 256; /* size of cbuf */\n\n        ssize_t res = recvmsg(fd, &msg, MSG_ERRQUEUE);\n        if (res < 0)\n        {\n            goto exit;\n        }\n\n        e = NULL;\n\n        for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg))\n        {\n            if (cmsg->cmsg_level == SOL_IP)\n            {\n                if (cmsg->cmsg_type == IP_RECVERR)\n                {\n                    e = (struct sock_extended_err *)CMSG_DATA(cmsg);\n                }\n                else\n                {\n                    buf_printf(&out, \"CMSG=%d|\", cmsg->cmsg_type);\n                }\n            }\n            else if (cmsg->cmsg_level == IPPROTO_IPV6)\n            {\n                if (cmsg->cmsg_type == IPV6_RECVERR)\n                {\n                    e = (struct sock_extended_err *)CMSG_DATA(cmsg);\n                }\n                else\n                {\n                    buf_printf(&out, \"CMSG=%d|\", cmsg->cmsg_type);\n                }\n            }\n        }\n        if (e == NULL)\n        {\n            buf_printf(&out, \"NO-INFO|\");\n            goto exit;\n        }\n\n        switch (e->ee_errno)\n        {\n            case ETIMEDOUT:\n                buf_printf(&out, \"ETIMEDOUT|\");\n                break;\n\n            case EMSGSIZE:\n                buf_printf(&out, \"EMSGSIZE Path-MTU=%d|\", e->ee_info);\n                *mtu = e->ee_info;\n                break;\n\n            case ECONNREFUSED:\n                buf_printf(&out, \"ECONNREFUSED|\");\n                break;\n\n            case EPROTO:\n                buf_printf(&out, \"EPROTO|\");\n                break;\n\n            case EHOSTUNREACH:\n                buf_printf(&out, \"EHOSTUNREACH|\");\n                break;\n\n            case ENETUNREACH:\n                buf_printf(&out, \"ENETUNREACH|\");\n                break;\n\n            case EACCES:\n                buf_printf(&out, \"EACCES|\");\n                break;\n\n            default:\n                buf_printf(&out, \"UNKNOWN|\");\n                break;\n        }\n    }\n\nexit:\n    buf_rmtail(&out, '|');\n    return BSTR(&out);\n}\n\nvoid\nset_sock_extended_error_passing(int sd, sa_family_t proto_af)\n{\n    int on = 1;\n    /* see \"man 7 ip\" (on Linux)\n     * this works on IPv4 and IPv6(-dual-stack) sockets (v4-mapped)\n     */\n    if (setsockopt(sd, SOL_IP, IP_RECVERR, (void *)&on, sizeof(on)) != 0)\n    {\n        msg(M_WARN | M_ERRNO,\n            \"Note: enable extended error passing on TCP/UDP socket failed (IP_RECVERR)\");\n    }\n    /* see \"man 7 ipv6\" (on Linux)\n     * this only works on IPv6 sockets\n     */\n    if (proto_af == AF_INET6\n        && setsockopt(sd, IPPROTO_IPV6, IPV6_RECVERR, (void *)&on, sizeof(on)) != 0)\n    {\n        msg(M_WARN | M_ERRNO,\n            \"Note: enable extended error passing on TCP/UDP socket failed (IPV6_RECVERR)\");\n    }\n}\n\n#endif /* if EXTENDED_SOCKET_ERROR_CAPABILITY */\n"
  },
  {
    "path": "src/openvpn/mtu.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef MTU_H\n#define MTU_H\n\n#include \"buffer.h\"\n\n/*\n *\n * Packet manipulation routes such as encrypt, decrypt, compress, decompress\n * are passed a frame buffer that looks like this:\n *\n *    [extra_frame bytes] [mtu bytes] [extra_frame_bytes] [compression overflow bytes]\n *                         ^\n *                   Pointer passed to function points here so that routine\n *                   can make use of extra_frame bytes before pointer\n *                   to prepend headers, etc.\n *\n *    extra_frame bytes is large enough for all encryption related overhead.\n *\n *    mtu bytes will be the MTU size set in the ifconfig statement that configures\n *      the TUN or TAP device such as:\n *\n *      ifconfig $1 10.1.0.2 pointopoint 10.1.0.1 mtu 1450\n *\n *    Compression overflow bytes is the worst-case size expansion that would be\n *    expected if we tried to compress mtu + extra_frame bytes of incompressible data.\n */\n\n/*\n * Standard ethernet MTU\n */\n#define ETHERNET_MTU 1500\n\n/*\n * It is a fatal error if mtu is less than\n * this value for tun device.\n */\n#define TUN_MTU_MIN 100\n\n/*\n * Default MTU of network over which tunnel data will pass by TCP/UDP.\n */\n#define LINK_MTU_DEFAULT 1500\n\n/*\n * Default MTU of tunnel device.\n */\n#define TUN_MTU_DEFAULT 1500\n\n/*\n * Minimum maximum MTU\n */\n#define TUN_MTU_MAX_MIN 1600\n\n/*\n * MTU Defaults for TAP devices\n */\n#define TAP_MTU_EXTRA_DEFAULT 32\n\n/*\n * Default MSSFIX value, used for reducing TCP MTU size\n */\n#define MSSFIX_DEFAULT 1492\n\n/*\n * Default maximum size of control channel packets\n */\n#define TLS_MTU_DEFAULT 1250\n\n/*\n * Alignment of payload data such as IP packet or\n * ethernet frame.\n */\n#define PAYLOAD_ALIGN 4\n\n/*\n * How many bytes we prepend for a SOCKS UDP proxy.\n * This only handles IPv4 right now.\n */\n#define SOCKS_UDPv4_HEADROOM 10\n\n/**************************************************************************/\n/**\n * Packet geometry parameters.\n */\nstruct frame\n{\n    struct\n    {\n        /* This struct holds all the information about the buffers that are\n         * allocated to match this frame */\n        int payload_size; /**< the maximum size that a payload that our\n                           *   buffers can hold from either tun device\n                           *   or network link.\n                           */\n\n\n        int headroom; /**< the headroom in the buffer, this is choosen\n                       *   to allow all potential header to be added\n                       *   before the packet */\n\n        int tailroom; /**< the tailroom in the buffer. Chosen large\n                       *  enough to also accompany any extrea header\n                       *  or work space required by\n                       *  decryption/encryption or compression. */\n    } buf;\n\n    uint16_t mss_fix;      /**< The actual MSS value that should be\n                            *   written to the payload packets. This\n                            *   is the value for IPv4 TCP packets. For\n                            *   IPv6 packets another 20 bytes must\n                            *   be subtracted */\n\n    int max_fragment_size; /**< The maximum size of a fragment.\n                            * Fragmentation is done on the unencrypted\n                            * payload after (potential) compression. So\n                            * this value specifies the maximum payload\n                            * size that can be send in a single fragment\n                            */\n\n    int tun_mtu;           /**< the (user) configured tun-mtu. This is used\n                            *   in configuring the tun interface or\n                            *   in calculations that use the desired size\n                            *   of the payload in the buffer.\n                            *\n                            *   This variable is also used in control\n                            *   frame context to set the desired maximum\n                            *   control frame payload (although most of\n                            *   code ignores it)\n                            */\n    int tun_max_mtu;       /**< the maximum tun-mtu size the buffers are\n                            *   are sized for. This is the upper bound that\n                            *   a server can push as MTU */\n\n    int extra_tun;         /**< Maximum number of bytes in excess of\n                            *   the tun/tap MTU that might be read\n                            *   from or written to the virtual\n                            *   tun/tap network interface.\n                            *\n                            *   Only set with the option --tun-mtu-extra\n                            *   which defaults to 0 for tun and 32\n                            *   (\\c TAP_MTU_EXTRA_DEFAULT) for tap.\n                            *   */\n};\n\n/* Forward declarations, to prevent includes */\nstruct options;\n\n/*\n * Control buffer headroom allocations to allow for efficient prepending.\n */\n\n/*\n * Max size of a buffer used to build a packet for output to\n * the TCP/UDP port or to read a packet from a tap/tun device.\n *\n * Most of our code only prepends headers but compression needs the extra bytes\n * *after* the data as compressed data might end up larger than the original\n * data. Also crypto needs an extra block for encryption. Therefore tailroom is\n * larger than the headroom.\n */\n#define BUF_SIZE(f) ((f)->buf.headroom + (f)->buf.payload_size + (f)->buf.tailroom)\n\n/*\n * Function prototypes.\n */\n\nvoid frame_print(const struct frame *frame, msglvl_t msglevel, const char *prefix);\n\nvoid set_mtu_discover_type(socket_descriptor_t sd, int mtu_type, sa_family_t proto_af);\n\nint translate_mtu_discover_type_name(const char *name);\n\n/* forward declaration of key_type */\nstruct key_type;\n\n/**\n * Calculates the size of the payload according to tun-mtu and tap overhead. In\n * this context payload is identical to the size of the plaintext.\n * This also includes compression, fragmentation overhead, and packet id in CBC\n * mode if these options are used.\n *\n *\n * *  [IP][UDP][OPENVPN PROTOCOL HEADER][ **PAYLOAD incl compression header** ]\n */\nsize_t frame_calculate_payload_size(const struct frame *frame, const struct options *options,\n                                    const struct key_type *kt);\n\n/**\n * Calculates the size of the payload overhead according to tun-mtu and\n * tap overhead. This all the overhead that is considered part of the payload\n * itself. The compression and fragmentation header and extra header from tap\n * are considered part of this overhead that increases the payload larger than\n * tun-mtu.\n *\n * In CBC mode, the IV is part of the payload instead of part of the OpenVPN\n * protocol header and is included in the returned value.\n *\n * In this context payload is identical to the size of the plaintext and this\n * method can be also understand as number of bytes that are added to the\n * plaintext before encryption.\n *\n * *  [IP][UDP][OPENVPN PROTOCOL HEADER][ **PAYLOAD incl compression header** ]\n */\nsize_t frame_calculate_payload_overhead(size_t extra_tun, const struct options *options,\n                                        const struct key_type *kt);\n\n\n/**\n * Calculates the size of the OpenVPN protocol header. This includes\n * the crypto IV/tag/HMAC but does not include the IP encapsulation\n *\n *  This does NOT include the padding and rounding of CBC size\n *  as the users (mssfix/fragment) of this function need to adjust for\n *  this and add it themselves.\n *\n *  [IP][UDP][ **OPENVPN PROTOCOL HEADER**][PAYLOAD incl compression header]\n *\n * @param kt            the key_type to use to calculate the crypto overhead\n * @param options       the options struct to be used to calculate\n * @param occ           Use the calculation for the OCC link-mtu\n * @return              size of the overhead in bytes\n */\nsize_t frame_calculate_protocol_header_size(const struct key_type *kt,\n                                            const struct options *options, bool occ);\n\n/**\n * Calculate the link-mtu to advertise to our peer.  The actual value is not\n * relevant, because we will possibly perform data channel cipher negotiation\n * after this, but older clients will log warnings if we do not supply them the\n * value they expect.  This assumes that the traditional cipher/auth directives\n * in the config match the config of the peer.\n */\nsize_t calc_options_string_link_mtu(const struct options *options, const struct frame *frame);\n\n/**\n * Return the size of the packet ID size that is currently in use by cipher and\n * options for the data channel.\n */\nunsigned int calc_packet_id_size_dc(const struct options *options, const struct key_type *kt);\n\n/*\n * allocate a buffer for socket or tun layer\n */\nvoid alloc_buf_sock_tun(struct buffer *buf, const struct frame *frame);\n\n/*\n * EXTENDED_SOCKET_ERROR_CAPABILITY functions -- print extra error info\n * on socket errors, such as PMTU size.\n */\n\n#if EXTENDED_SOCKET_ERROR_CAPABILITY\n\nvoid set_sock_extended_error_passing(int sd, sa_family_t proto_af);\n\nconst char *format_extended_socket_error(int fd, int *mtu, struct gc_arena *gc);\n\n#endif\n\n#endif /* ifndef MTU_H */\n"
  },
  {
    "path": "src/openvpn/mudp.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"multi.h\"\n#include <inttypes.h>\n#include \"forward.h\"\n\n#include \"memdbg.h\"\n#include \"ssl_pkt.h\"\n\n#ifdef HAVE_SYS_INOTIFY_H\n#include <sys/inotify.h>\n#endif\n\nstatic void\nsend_hmac_reset_packet(struct multi_context *m, struct tls_pre_decrypt_state *state,\n                       struct tls_auth_standalone *tas, struct session_id *sid,\n                       bool request_resend_wkc)\n{\n    reset_packet_id_send(&state->tls_wrap_tmp.opt.packet_id.send);\n    state->tls_wrap_tmp.opt.packet_id.rec.initialized = true;\n    uint8_t header = 0 | (P_CONTROL_HARD_RESET_SERVER_V2 << P_OPCODE_SHIFT);\n    struct buffer buf = tls_reset_standalone(&state->tls_wrap_tmp, tas, sid,\n                                             &state->peer_session_id, header, request_resend_wkc);\n\n    struct context *c = &m->top;\n\n    /* dco-win server requires prepend with sockaddr, so preserve offset */\n    ASSERT(buf_init(&c->c2.buffers->aux_buf, buf.offset));\n\n    buf_copy(&c->c2.buffers->aux_buf, &buf);\n    m->hmac_reply = c->c2.buffers->aux_buf;\n    m->hmac_reply_dest = &m->top.c2.from;\n    msg(D_MULTI_DEBUG, \"Reset packet from client, sending HMAC based reset challenge\");\n}\n\n\n/* Returns true if this packet should create a new session */\nstatic bool\ndo_pre_decrypt_check(struct multi_context *m, struct tls_pre_decrypt_state *state,\n                     struct mroute_addr addr)\n{\n    ASSERT(m->top.c2.tls_auth_standalone);\n\n    enum first_packet_verdict verdict;\n\n    struct tls_auth_standalone *tas = m->top.c2.tls_auth_standalone;\n\n    verdict = tls_pre_decrypt_lite(tas, state, &m->top.c2.from, &m->top.c2.buf);\n\n    hmac_ctx_t *hmac = m->top.c2.session_id_hmac;\n    struct openvpn_sockaddr *from = &m->top.c2.from.dest;\n    int handwindow = m->top.options.handshake_window;\n\n    if (verdict == VERDICT_VALID_RESET_V3 || verdict == VERDICT_VALID_RESET_V2)\n    {\n        /* Check if we are still below our limit for sending out\n         * responses */\n        if (!reflect_filter_rate_limit_check(m->initial_rate_limiter))\n        {\n            return false;\n        }\n    }\n\n    if (verdict == VERDICT_VALID_RESET_V3)\n    {\n        /* Extract the packet id to check if it has the special format that\n         * indicates early negotiation support */\n        struct packet_id_net pin;\n        struct buffer tmp = m->top.c2.buf;\n        ASSERT(buf_advance(&tmp, 1 + SID_SIZE));\n        ASSERT(packet_id_read(&pin, &tmp, true));\n\n        /* The most significant byte is 0x0f if early negotiation is supported */\n        bool early_neg_support = ((pin.id & EARLY_NEG_MASK) & EARLY_NEG_START) == EARLY_NEG_START;\n\n        /* All clients that support early negotiation and tls-crypt are assumed\n         * to also support resending the WKc in the 2nd packet */\n        if (early_neg_support)\n        {\n            /* Calculate the session ID HMAC for our reply and create reset packet */\n            struct session_id sid =\n                calculate_session_id_hmac(state->peer_session_id, from, hmac, handwindow, 0);\n            send_hmac_reset_packet(m, state, tas, &sid, true);\n\n            return false;\n        }\n        else\n        {\n            /* For tls-crypt-v2 we need to keep the state of the first packet\n             * to store the unwrapped key if the client doesn't support resending\n             * the wrapped key. Unless the user specifically disallowed\n             * compatibility with such clients to avoid state exhaustion */\n            if (tas->tls_wrap.opt.flags & CO_FORCE_TLSCRYPTV2_COOKIE)\n            {\n                struct gc_arena gc = gc_new();\n                const char *peer = print_link_socket_actual(&m->top.c2.from, &gc);\n                msg(D_MULTI_DEBUG,\n                    \"tls-crypt-v2 force-cookie is enabled, \"\n                    \"ignoring connection attempt from old client (%s)\",\n                    peer);\n                gc_free(&gc);\n                return false;\n            }\n            else\n            {\n                return true;\n            }\n        }\n    }\n    else if (verdict == VERDICT_VALID_RESET_V2)\n    {\n        /* Calculate the session ID HMAC for our reply and create reset packet */\n        struct session_id sid =\n            calculate_session_id_hmac(state->peer_session_id, from, hmac, handwindow, 0);\n\n        send_hmac_reset_packet(m, state, tas, &sid, false);\n\n        /* We have a reply do not create a new session */\n        return false;\n    }\n    else if (verdict == VERDICT_VALID_CONTROL_V1 || verdict == VERDICT_VALID_ACK_V1\n             || verdict == VERDICT_VALID_WKC_V1)\n    {\n        /* ACK_V1 contains the peer id (our id) while CONTROL_V1 can but does not\n         * need to contain the peer id */\n        struct gc_arena gc = gc_new();\n\n        bool pkt_is_ack = (verdict == VERDICT_VALID_ACK_V1);\n        bool ret = check_session_hmac_and_pkt_id(state, from, hmac, handwindow, pkt_is_ack);\n\n        const char *peer = print_link_socket_actual(&m->top.c2.from, &gc);\n        uint8_t pkt_firstbyte = *BPTR(&m->top.c2.buf);\n        int op = pkt_firstbyte >> P_OPCODE_SHIFT;\n\n        if (!ret)\n        {\n            msg(D_MULTI_MEDIUM, \"Packet (%s) with invalid or missing SID from\"\n                                \" %s or wrong packet id\",\n                packet_opcode_name(op), peer);\n        }\n        else\n        {\n            msg(D_MULTI_DEBUG,\n                \"Valid packet (%s) with HMAC challenge from peer (%s), \"\n                \"accepting new connection.\",\n                packet_opcode_name(op), peer);\n        }\n        gc_free(&gc);\n\n        return ret;\n    }\n\n    /* VERDICT_INVALID */\n    return false;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\n/*\n * Get a client instance based on real address.  If\n * the instance doesn't exist, create it while\n * maintaining real address hash table atomicity.\n */\n\nstruct multi_instance *\nmulti_get_create_instance_udp(struct multi_context *m, bool *floated, struct link_socket *sock)\n{\n    struct gc_arena gc = gc_new();\n    struct mroute_addr real = { 0 };\n    struct multi_instance *mi = NULL;\n    struct hash *hash = m->hash;\n    real.proto = sock->info.proto;\n    m->hmac_reply_ls = sock;\n\n    if (mroute_extract_openvpn_sockaddr(&real, &m->top.c2.from.dest, true) && m->top.c2.buf.len > 0)\n    {\n        struct hash_element *he;\n        const uint32_t hv = hash_value(hash, &real);\n        struct hash_bucket *bucket = hash_bucket(hash, hv);\n        uint8_t *ptr = BPTR(&m->top.c2.buf);\n        uint8_t op = ptr[0] >> P_OPCODE_SHIFT;\n        bool v2 = (op == P_DATA_V2) && (m->top.c2.buf.len >= (1 + 3));\n        bool peer_id_disabled = false;\n\n        /* make sure buffer has enough length to read opcode (1 byte) and peer-id (3 bytes) */\n        if (v2)\n        {\n            uint32_t peer_id = ((uint32_t)ptr[1] << 16) | ((uint32_t)ptr[2] << 8) | ((uint32_t)ptr[3]);\n            peer_id_disabled = (peer_id == MAX_PEER_ID);\n\n            if (!peer_id_disabled && (peer_id < m->max_clients) && (m->instances[peer_id]))\n            {\n                /* Floating on TCP will never be possible, so ensure we only process\n                 * UDP clients */\n                if (m->instances[peer_id]->context.c2.link_sockets[0]->info.proto\n                    == sock->info.proto)\n                {\n                    mi = m->instances[peer_id];\n                    *floated = !link_socket_actual_match(&mi->context.c2.from, &m->top.c2.from);\n\n                    if (*floated)\n                    {\n                        /* reset prefix, since here we are not sure peer is the one it claims to be\n                         */\n                        ungenerate_prefix(mi);\n                        msg(D_MULTI_MEDIUM, \"Float requested for peer %\" PRIu32 \" to %s\", peer_id,\n                            mroute_addr_print(&real, &gc));\n                    }\n                }\n            }\n        }\n        if (!v2 || peer_id_disabled)\n        {\n            he = hash_lookup_fast(hash, bucket, &real, hv);\n            if (he)\n            {\n                mi = (struct multi_instance *)he->value;\n            }\n        }\n\n        /* we have no existing multi instance for this connection */\n        if (!mi)\n        {\n            struct tls_pre_decrypt_state state = { 0 };\n            if (m->deferred_shutdown_signal.signal_received)\n            {\n                msg(D_MULTI_ERRORS,\n                    \"MULTI: Connection attempt from %s ignored while server is \"\n                    \"shutting down\",\n                    mroute_addr_print(&real, &gc));\n            }\n            else if (do_pre_decrypt_check(m, &state, real))\n            {\n                /* This is an unknown session but with valid tls-auth/tls-crypt\n                 * (or no auth at all).  If this is the initial packet of a\n                 * session, we just send a reply with a HMAC session id and\n                 * do not generate a session slot */\n\n                if (frequency_limit_event_allowed(m->new_connection_limiter))\n                {\n                    /* a successful three-way handshake only counts against\n                     * connect-freq but not against connect-freq-initial */\n                    reflect_filter_rate_limit_decrease(m->initial_rate_limiter);\n\n                    mi = multi_create_instance(m, &real, sock);\n                    if (mi)\n                    {\n                        hash_add_fast(hash, bucket, &mi->real, hv, mi);\n                        mi->did_real_hash = true;\n                        multi_assign_peer_id(m, mi);\n\n                        /* If we have a session id already, ensure that the\n                         * state is using the same */\n                        if (session_id_defined(&state.server_session_id)\n                            && session_id_defined((&state.peer_session_id)))\n                        {\n                            mi->context.c2.tls_multi->n_sessions++;\n                            struct tls_session *session =\n                                &mi->context.c2.tls_multi->session[TM_INITIAL];\n                            session_skip_to_pre_start(session, &state, &m->top.c2.from);\n                        }\n                    }\n                }\n                else\n                {\n                    msg(D_MULTI_ERRORS,\n                        \"MULTI: Connection from %s would exceed new connection frequency limit as controlled by --connect-freq\",\n                        mroute_addr_print(&real, &gc));\n                }\n            }\n            free_tls_pre_decrypt_state(&state);\n        }\n\n#ifdef ENABLE_DEBUG\n        if (check_debug_level(D_MULTI_DEBUG))\n        {\n            const char *status = mi ? \"[ok]\" : \"[failed]\";\n\n            dmsg(D_MULTI_DEBUG, \"GET INST BY REAL: %s %s\", mroute_addr_print(&real, &gc), status);\n        }\n#endif\n    }\n\n    gc_free(&gc);\n    ASSERT(!(mi && mi->halt));\n    return mi;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n/*\n * Send a packet to UDP socket.\n */\nstatic inline void\nmulti_process_outgoing_link(struct multi_context *m, const unsigned int mpp_flags)\n{\n    struct multi_instance *mi = multi_process_outgoing_link_pre(m);\n    if (mi)\n    {\n        multi_process_outgoing_link_dowork(m, mi, mpp_flags);\n    }\n    if (m->hmac_reply_dest && m->hmac_reply.len > 0)\n    {\n        msg_set_prefix(\"Connection Attempt\");\n        m->top.c2.to_link = m->hmac_reply;\n        m->top.c2.to_link_addr = m->hmac_reply_dest;\n        process_outgoing_link(&m->top, m->hmac_reply_ls);\n        m->hmac_reply_ls = NULL;\n        m->hmac_reply_dest = NULL;\n    }\n}\n\n/*\n * Process a UDP socket event.\n */\nvoid\nmulti_process_io_udp(struct multi_context *m, struct link_socket *sock)\n{\n    const unsigned int status = m->multi_io->udp_flags;\n    const unsigned int mpp_flags = (MPP_PRE_SELECT | MPP_CLOSE_ON_SIGNAL);\n\n    /* UDP port ready to accept write */\n    if (status & SOCKET_WRITE)\n    {\n        multi_process_outgoing_link(m, mpp_flags);\n    }\n    /* Incoming data on UDP port */\n    else if (status & SOCKET_READ)\n    {\n        read_incoming_link(&m->top, sock);\n        if (!IS_SIG(&m->top))\n        {\n            multi_process_incoming_link(m, NULL, mpp_flags, sock);\n        }\n    }\n\n    m->multi_io->udp_flags = ES_ERROR;\n}\n\n/*\n * Return the io_wait() flags appropriate for\n * a point-to-multipoint tunnel.\n */\nunsigned int\np2mp_iow_flags(const struct multi_context *m)\n{\n    unsigned int flags = IOW_WAIT_SIGNAL;\n    if (m->pending)\n    {\n        if (TUN_OUT(&m->pending->context))\n        {\n            flags |= IOW_TO_TUN;\n        }\n        if (LINK_OUT(&m->pending->context))\n        {\n            flags |= IOW_TO_LINK;\n        }\n    }\n    else if (mbuf_defined(m->mbuf))\n    {\n        flags |= IOW_MBUF;\n    }\n    else if (m->hmac_reply_dest)\n    {\n        flags |= IOW_TO_LINK;\n    }\n    else\n    {\n        flags |= IOW_READ;\n    }\n    return flags;\n}\n"
  },
  {
    "path": "src/openvpn/mudp.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * UDP specific code for --mode server\n */\n\n#ifndef MUDP_H\n#define MUDP_H\n\nstruct context;\nstruct multi_context;\n\nunsigned int p2mp_iow_flags(const struct multi_context *m);\n\nvoid multi_process_io_udp(struct multi_context *m, struct link_socket *sock);\n/**************************************************************************/\n/**\n * Get, and if necessary create, the multi_instance associated with a\n * packet's source address.\n * @ingroup external_multiplexer\n *\n * This function extracts the source address of a recently read packet\n * from \\c m->top.c2.from and uses that source address as a hash key for\n * the hash table \\c m->hash.  If an entry exists, this function returns\n * it.  If no entry exists, this function handles its creation, and if\n * successful, returns the newly created instance.\n *\n * @param m            The single multi_context structure.\n * @param[out] floated Returns whether the client has floated.\n * @param sock         Listening socket where this instance is connecting to\n *\n * @return A pointer to a multi_instance if one already existed for the\n *     packet's source address or if one was a newly created successfully.\n *     NULL if one did not yet exist and a new one was not created.\n */\nstruct multi_instance *multi_get_create_instance_udp(struct multi_context *m, bool *floated,\n                                                     struct link_socket *sock);\n\n#endif /* ifndef MUDP_H */\n"
  },
  {
    "path": "src/openvpn/multi.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#ifdef HAVE_SYS_INOTIFY_H\n#include <sys/inotify.h>\n#define INOTIFY_EVENT_BUFFER_SIZE 16384\n#endif\n\n#include \"syshead.h\"\n\n#include \"forward.h\"\n#include \"multi.h\"\n#include \"push.h\"\n#include \"run_command.h\"\n#include \"otime.h\"\n#include \"gremlin.h\"\n#include \"ssl_verify.h\"\n#include \"ssl_ncp.h\"\n#include \"vlan.h\"\n#include \"auth_token.h\"\n#include \"route.h\"\n#include <inttypes.h>\n#include <string.h>\n\n#include \"memdbg.h\"\n\n\n#include \"crypto_backend.h\"\n#include \"ssl_util.h\"\n#include \"dco.h\"\n#include \"reflect_filter.h\"\n\n/*#define MULTI_DEBUG_EVENT_LOOP*/\n\n#ifdef MULTI_DEBUG_EVENT_LOOP\nstatic const char *\nid(struct multi_instance *mi)\n{\n    if (mi)\n    {\n        return tls_common_name(mi->context.c2.tls_multi, false);\n    }\n    else\n    {\n        return \"NULL\";\n    }\n}\n#endif\n\n#ifdef ENABLE_MANAGEMENT\nstatic void\nset_cc_config(struct multi_instance *mi, struct buffer_list *cc_config)\n{\n    buffer_list_free(mi->cc_config);\n    mi->cc_config = cc_config;\n}\n#endif\n\nstatic bool\nlearn_address_script(const struct multi_context *m, const struct multi_instance *mi, const char *op,\n                     const struct mroute_addr *addr)\n{\n    struct gc_arena gc = gc_new();\n    struct env_set *es;\n    bool ret = true;\n    struct plugin_list *plugins;\n\n    /* get environmental variable source */\n    if (mi && mi->context.c2.es)\n    {\n        es = mi->context.c2.es;\n    }\n    else\n    {\n        es = env_set_create(&gc);\n    }\n\n    /* get plugin source */\n    if (mi)\n    {\n        plugins = mi->context.plugins;\n    }\n    else\n    {\n        plugins = m->top.plugins;\n    }\n\n    if (plugin_defined(plugins, OPENVPN_PLUGIN_LEARN_ADDRESS))\n    {\n        struct argv argv = argv_new();\n        argv_printf(&argv, \"%s %s\", op, mroute_addr_print(addr, &gc));\n        if (mi)\n        {\n            argv_printf_cat(&argv, \"%s\", tls_common_name(mi->context.c2.tls_multi, false));\n        }\n        if (plugin_call(plugins, OPENVPN_PLUGIN_LEARN_ADDRESS, &argv, NULL, es)\n            != OPENVPN_PLUGIN_FUNC_SUCCESS)\n        {\n            msg(M_WARN, \"WARNING: learn-address plugin call failed\");\n            ret = false;\n        }\n        argv_free(&argv);\n    }\n\n    if (m->top.options.learn_address_script)\n    {\n        struct argv argv = argv_new();\n        setenv_str(es, \"script_type\", \"learn-address\");\n        argv_parse_cmd(&argv, m->top.options.learn_address_script);\n        argv_printf_cat(&argv, \"%s %s\", op, mroute_addr_print(addr, &gc));\n        if (mi)\n        {\n            argv_printf_cat(&argv, \"%s\", tls_common_name(mi->context.c2.tls_multi, false));\n        }\n        if (!openvpn_run_script(&argv, es, 0, \"--learn-address\"))\n        {\n            ret = false;\n        }\n        argv_free(&argv);\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n\nvoid\nmulti_ifconfig_pool_persist(struct multi_context *m, bool force)\n{\n    /* write pool data to file */\n    if (m->ifconfig_pool && m->top.c1.ifconfig_pool_persist\n        && (force || ifconfig_pool_write_trigger(m->top.c1.ifconfig_pool_persist)))\n    {\n        ifconfig_pool_write(m->top.c1.ifconfig_pool_persist, m->ifconfig_pool);\n    }\n}\n\nstatic void\nmulti_reap_range(const struct multi_context *m, uint32_t start_bucket, uint32_t end_bucket)\n{\n    struct gc_arena gc = gc_new();\n    struct hash_iterator hi;\n    struct hash_element *he;\n\n    dmsg(D_MULTI_DEBUG, \"MULTI: REAP range %d -> %d\", start_bucket, end_bucket);\n    hash_iterator_init_range(m->vhash, &hi, start_bucket, end_bucket);\n    while ((he = hash_iterator_next(&hi)) != NULL)\n    {\n        struct multi_route *r = (struct multi_route *)he->value;\n        if (!multi_route_defined(m, r))\n        {\n            dmsg(D_MULTI_DEBUG, \"MULTI: REAP DEL %s\", mroute_addr_print(&r->addr, &gc));\n            learn_address_script(m, NULL, \"delete\", &r->addr);\n            multi_route_del(r);\n            hash_iterator_delete_element(&hi);\n        }\n    }\n    hash_iterator_free(&hi);\n    gc_free(&gc);\n}\n\nstatic void\nmulti_reap_all(const struct multi_context *m)\n{\n    multi_reap_range(m, 0, hash_n_buckets(m->vhash));\n}\n\nstatic struct multi_reap *\nmulti_reap_new(uint32_t buckets_per_pass)\n{\n    struct multi_reap *mr;\n    ALLOC_OBJ(mr, struct multi_reap);\n    mr->bucket_base = 0;\n    mr->buckets_per_pass = buckets_per_pass;\n    mr->last_call = now;\n    return mr;\n}\n\nvoid\nmulti_reap_process_dowork(const struct multi_context *m)\n{\n    struct multi_reap *mr = m->reaper;\n    if (mr->bucket_base >= hash_n_buckets(m->vhash))\n    {\n        mr->bucket_base = 0;\n    }\n    multi_reap_range(m, mr->bucket_base, mr->bucket_base + mr->buckets_per_pass);\n    mr->bucket_base += mr->buckets_per_pass;\n    mr->last_call = now;\n}\n\nstatic void\nmulti_reap_free(struct multi_reap *mr)\n{\n    free(mr);\n}\n\n/*\n * How many buckets in vhash to reap per pass.\n */\nstatic uint32_t\nreap_buckets_per_pass(uint32_t n_buckets)\n{\n    return constrain_uint(n_buckets / REAP_DIVISOR, REAP_MIN, REAP_MAX);\n}\n\n#ifdef ENABLE_MANAGEMENT\n\nstatic uint32_t\ncid_hash_function(const void *key, uint32_t iv)\n{\n    const unsigned long *k = (const unsigned long *)key;\n    return (uint32_t)*k;\n}\n\nstatic bool\ncid_compare_function(const void *key1, const void *key2)\n{\n    const unsigned long *k1 = (const unsigned long *)key1;\n    const unsigned long *k2 = (const unsigned long *)key2;\n    return *k1 == *k2;\n}\n\n#endif\n\n#ifdef ENABLE_ASYNC_PUSH\nstatic uint32_t\n/*\n * inotify watcher descriptors are used as hash value\n */\nint_hash_function(const void *key, uint32_t iv)\n{\n    return (uint32_t)(uintptr_t)key;\n}\n\nstatic bool\nint_compare_function(const void *key1, const void *key2)\n{\n    return (unsigned long)key1 == (unsigned long)key2;\n}\n#endif\n\n/*\n * Main initialization function, init multi_context object.\n */\nstatic void\nmulti_init(struct context *t)\n{\n    struct multi_context *m = t->multi;\n    int dev = DEV_TYPE_UNDEF;\n\n    msg(D_MULTI_LOW, \"MULTI: multi_init called, r=%d v=%d\", t->options.real_hash_size,\n        t->options.virtual_hash_size);\n\n    /*\n     * Get tun/tap/null device type\n     */\n    dev = dev_type_enum(t->options.dev, t->options.dev_type);\n\n    /*\n     * Init our multi_context object.\n     */\n    CLEAR(*m);\n\n    /*\n     * Real address hash table (source port number is\n     * considered to be part of the address).  Used\n     * to determine which client sent an incoming packet\n     * which is seen on the TCP/UDP socket.\n     */\n    m->hash = hash_init(t->options.real_hash_size, (uint32_t)get_random(),\n                        mroute_addr_hash_function, mroute_addr_compare_function);\n\n    /*\n     * Virtual address hash table.  Used to determine\n     * which client to route a packet to.\n     */\n    m->vhash = hash_init(t->options.virtual_hash_size, (uint32_t)get_random(),\n                         mroute_addr_hash_function, mroute_addr_compare_function);\n\n    /*\n     * This hash table is a clone of m->hash but with a\n     * bucket size of one so that it can be used\n     * for fast iteration through the list.\n     */\n    m->iter = hash_init(1, (uint32_t)get_random(), mroute_addr_hash_function,\n                        mroute_addr_compare_function);\n\n#ifdef ENABLE_MANAGEMENT\n    m->cid_hash = hash_init(t->options.real_hash_size, 0, cid_hash_function, cid_compare_function);\n#endif\n\n#ifdef ENABLE_ASYNC_PUSH\n    /*\n     * Mapping between inotify watch descriptors and\n     * multi_instances.\n     */\n    m->inotify_watchers = hash_init(t->options.real_hash_size, (uint32_t)get_random(),\n                                    int_hash_function, int_compare_function);\n#endif\n\n    /*\n     * This is our scheduler, for time-based wakeup\n     * events.\n     */\n    m->schedule = schedule_init();\n\n    /*\n     * Limit frequency of incoming connections to control\n     * DoS.\n     */\n    m->new_connection_limiter = frequency_limit_init(t->options.cf_max, t->options.cf_per);\n    m->initial_rate_limiter =\n        initial_rate_limit_init(t->options.cf_initial_max, t->options.cf_initial_per);\n\n    /*\n     * Allocate broadcast/multicast buffer list\n     */\n    m->mbuf = mbuf_init(t->options.n_bcast_buf);\n\n    /*\n     * Different status file format options are available\n     */\n    m->status_file_version = t->options.status_file_version;\n\n    /*\n     * Possibly allocate an ifconfig pool, do it\n     * differently based on whether a tun or tap style\n     * tunnel.\n     */\n    if (t->options.ifconfig_pool_defined || t->options.ifconfig_ipv6_pool_defined)\n    {\n        int pool_type = IFCONFIG_POOL_INDIV;\n\n        if (dev == DEV_TYPE_TUN && t->options.topology == TOP_NET30)\n        {\n            pool_type = IFCONFIG_POOL_30NET;\n        }\n\n        m->ifconfig_pool = ifconfig_pool_init(\n            t->options.ifconfig_pool_defined, pool_type, t->options.ifconfig_pool_start,\n            t->options.ifconfig_pool_end, t->options.duplicate_cn,\n            t->options.ifconfig_ipv6_pool_defined, t->options.ifconfig_ipv6_pool_base,\n            t->options.ifconfig_ipv6_pool_netbits);\n\n        /* reload pool data from file */\n        if (t->c1.ifconfig_pool_persist)\n        {\n            ifconfig_pool_read(t->c1.ifconfig_pool_persist, m->ifconfig_pool);\n        }\n    }\n\n    /*\n     * Help us keep track of routing table.\n     */\n    m->route_helper = mroute_helper_init(MULTI_CACHE_ROUTE_TTL);\n\n    /*\n     * Initialize route and instance reaper.\n     */\n    m->reaper = multi_reap_new(reap_buckets_per_pass(t->options.virtual_hash_size));\n\n    /*\n     * Get local ifconfig address\n     */\n    CLEAR(m->local);\n    ASSERT(t->c1.tuntap);\n    mroute_extract_in_addr_t(&m->local, t->c1.tuntap->local);\n\n    /*\n     * Per-client limits\n     */\n    m->max_clients = t->options.max_clients;\n\n    m->instances = calloc(m->max_clients, sizeof(struct multi_instance *));\n\n    m->top.c2.event_set = t->c2.event_set;\n\n    /*\n     * Initialize multi-socket I/O wait object\n     */\n    m->multi_io = multi_io_init(m->max_clients);\n    m->tcp_queue_limit = t->options.tcp_queue_limit;\n\n    /*\n     * Allow client <-> client communication, without going through\n     * tun/tap interface and network stack?\n     */\n    m->enable_c2c = t->options.enable_c2c;\n\n    /* initialize stale routes check timer */\n    if (t->options.stale_routes_check_interval > 0)\n    {\n        msg(M_INFO,\n            \"Initializing stale route check timer to run every %i seconds and to removing routes with activity timeout older than %i seconds\",\n            t->options.stale_routes_check_interval, t->options.stale_routes_ageing_time);\n        event_timeout_init(&m->stale_routes_check_et, t->options.stale_routes_check_interval, 0);\n    }\n\n    m->deferred_shutdown_signal.signal_received = 0;\n}\n\nconst char *\nmulti_instance_string(const struct multi_instance *mi, bool null, struct gc_arena *gc)\n{\n    if (mi)\n    {\n        struct buffer out = alloc_buf_gc(MULTI_PREFIX_MAX_LENGTH, gc);\n        const char *cn = tls_common_name(mi->context.c2.tls_multi, true);\n\n        if (cn)\n        {\n            buf_printf(&out, \"%s/\", cn);\n        }\n        buf_printf(&out, \"%s\", mroute_addr_print(&mi->real, gc));\n        if (mi->context.c2.tls_multi && check_debug_level(D_DCO_DEBUG)\n            && dco_enabled(&mi->context.options))\n        {\n            buf_printf(&out, \" peer-id=%d\", mi->context.c2.tls_multi->peer_id);\n        }\n        return BSTR(&out);\n    }\n    else if (null)\n    {\n        return NULL;\n    }\n    else\n    {\n        return \"UNDEF\";\n    }\n}\n\nstatic void\ngenerate_prefix(struct multi_instance *mi)\n{\n    struct gc_arena gc = gc_new();\n    const char *prefix = multi_instance_string(mi, true, &gc);\n    if (prefix)\n    {\n        strncpynt(mi->msg_prefix, prefix, sizeof(mi->msg_prefix));\n    }\n    else\n    {\n        mi->msg_prefix[0] = '\\0';\n    }\n    set_prefix(mi);\n    gc_free(&gc);\n}\n\nvoid\nungenerate_prefix(struct multi_instance *mi)\n{\n    mi->msg_prefix[0] = '\\0';\n    set_prefix(mi);\n}\n\n/*\n * Tell the route helper about deleted iroutes so\n * that it can update its mask of currently used\n * CIDR netlengths.\n */\nstatic void\nmulti_del_iroutes(struct multi_context *m, struct multi_instance *mi)\n{\n    const struct iroute *ir;\n    const struct iroute_ipv6 *ir6;\n\n    dco_delete_iroutes(m, mi);\n\n    if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)\n    {\n        for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next)\n        {\n            mroute_helper_del_iroute46(m->route_helper, ir->netbits);\n        }\n\n        for (ir6 = mi->context.options.iroutes_ipv6; ir6 != NULL; ir6 = ir6->next)\n        {\n            mroute_helper_del_iroute46(m->route_helper, ir6->netbits);\n        }\n    }\n}\n\nstatic void\nsetenv_stats(struct multi_context *m, struct context *c)\n{\n    if (dco_enabled(&m->top.options))\n    {\n        if (dco_get_peer_stats_multi(&m->top.c1.tuntap->dco, false) < 0)\n        {\n            return;\n        }\n    }\n\n    setenv_counter(c->c2.es, \"bytes_received\", c->c2.link_read_bytes + c->c2.dco_read_bytes);\n    setenv_counter(c->c2.es, \"bytes_sent\", c->c2.link_write_bytes + c->c2.dco_write_bytes);\n}\n\nstatic void\nmulti_client_disconnect_setenv(struct multi_context *m, struct multi_instance *mi)\n{\n    /* setenv client real IP address */\n    setenv_trusted(mi->context.c2.es, get_link_socket_info(&mi->context));\n\n    /* setenv stats */\n    setenv_stats(m, &mi->context);\n\n    /* setenv connection duration */\n    setenv_long_long(mi->context.c2.es, \"time_duration\", now - mi->created);\n}\n\nstatic void\nmulti_client_disconnect_script(struct multi_context *m, struct multi_instance *mi)\n{\n    multi_client_disconnect_setenv(m, mi);\n\n    if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT))\n    {\n        if (plugin_call(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT, NULL, NULL,\n                        mi->context.c2.es)\n            != OPENVPN_PLUGIN_FUNC_SUCCESS)\n        {\n            msg(M_WARN, \"WARNING: client-disconnect plugin call failed\");\n        }\n    }\n\n    if (mi->context.options.client_disconnect_script)\n    {\n        struct argv argv = argv_new();\n        setenv_str(mi->context.c2.es, \"script_type\", \"client-disconnect\");\n        argv_parse_cmd(&argv, mi->context.options.client_disconnect_script);\n        openvpn_run_script(&argv, mi->context.c2.es, 0, \"--client-disconnect\");\n        argv_free(&argv);\n    }\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        management_notify_client_close(management, &mi->context.c2.mda_context, mi->context.c2.es);\n    }\n#endif\n}\n\nvoid\nmulti_close_instance(struct multi_context *m, struct multi_instance *mi, bool shutdown)\n{\n    ASSERT(!mi->halt);\n    mi->halt = true;\n    bool is_dgram = proto_is_dgram(mi->context.c2.link_sockets[0]->info.proto);\n\n    dmsg(D_MULTI_DEBUG, \"MULTI: multi_close_instance called\");\n\n    /* adjust current client connection count */\n    m->n_clients += mi->n_clients_delta;\n    mi->n_clients_delta = 0;\n\n    /* prevent dangling pointers */\n    if (m->pending == mi)\n    {\n        multi_set_pending(m, NULL);\n    }\n    if (m->earliest_wakeup == mi)\n    {\n        m->earliest_wakeup = NULL;\n    }\n\n    if (!shutdown)\n    {\n        if (mi->did_real_hash)\n        {\n            ASSERT(hash_remove(m->hash, &mi->real));\n        }\n        if (mi->did_iter)\n        {\n            ASSERT(hash_remove(m->iter, &mi->real));\n        }\n#ifdef ENABLE_MANAGEMENT\n        if (mi->did_cid_hash)\n        {\n            ASSERT(hash_remove(m->cid_hash, &mi->context.c2.mda_context.cid));\n        }\n#endif\n\n#ifdef ENABLE_ASYNC_PUSH\n        if (mi->inotify_watch != -1)\n        {\n            hash_remove(m->inotify_watchers, (void *)(uintptr_t)mi->inotify_watch);\n            mi->inotify_watch = -1;\n        }\n#endif\n\n        if (mi->context.c2.tls_multi->peer_id != MAX_PEER_ID)\n        {\n            m->instances[mi->context.c2.tls_multi->peer_id] = NULL;\n        }\n\n        schedule_remove_entry(m->schedule, (struct schedule_entry *)mi);\n\n        ifconfig_pool_release(m->ifconfig_pool, mi->vaddr_handle, false);\n\n        if (mi->did_iroutes)\n        {\n            multi_del_iroutes(m, mi);\n            mi->did_iroutes = false;\n        }\n\n        if (!is_dgram)\n        {\n            multi_tcp_dereference_instance(m->multi_io, mi);\n        }\n\n        mbuf_dereference_instance(m->mbuf, mi);\n    }\n\n#ifdef ENABLE_MANAGEMENT\n    set_cc_config(mi, NULL);\n#endif\n\n    if (mi->context.c2.tls_multi->multi_state >= CAS_CONNECT_DONE)\n    {\n        multi_client_disconnect_script(m, mi);\n    }\n\n    close_context(&mi->context, SIGTERM, CC_GC_FREE);\n\n    multi_tcp_instance_specific_free(mi);\n\n    ungenerate_prefix(mi);\n\n    /*\n     * Don't actually delete the instance memory allocation yet,\n     * because virtual routes may still point to it.  Let the\n     * vhash reaper deal with it.\n     */\n    multi_instance_dec_refcount(mi);\n}\n\n/*\n * Called on shutdown or restart.\n */\nstatic void\nmulti_uninit(struct multi_context *m)\n{\n    if (m->hash)\n    {\n        struct hash_iterator hi;\n        struct hash_element *he;\n\n        hash_iterator_init(m->iter, &hi);\n        while ((he = hash_iterator_next(&hi)))\n        {\n            struct multi_instance *mi = (struct multi_instance *)he->value;\n            mi->did_iter = false;\n            multi_close_instance(m, mi, true);\n        }\n        hash_iterator_free(&hi);\n\n        multi_reap_all(m);\n\n        hash_free(m->hash);\n        hash_free(m->vhash);\n        hash_free(m->iter);\n#ifdef ENABLE_MANAGEMENT\n        hash_free(m->cid_hash);\n#endif\n        m->hash = NULL;\n\n        free(m->instances);\n\n#ifdef ENABLE_ASYNC_PUSH\n        hash_free(m->inotify_watchers);\n        m->inotify_watchers = NULL;\n#endif\n\n        schedule_free(m->schedule);\n        mbuf_free(m->mbuf);\n        ifconfig_pool_free(m->ifconfig_pool);\n        frequency_limit_free(m->new_connection_limiter);\n        initial_rate_limit_free(m->initial_rate_limiter);\n        multi_reap_free(m->reaper);\n        mroute_helper_free(m->route_helper);\n        multi_io_free(m->multi_io);\n    }\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\n/*\n * Create a client instance object for a newly connected client.\n */\nstruct multi_instance *\nmulti_create_instance(struct multi_context *m, const struct mroute_addr *real,\n                      struct link_socket *sock)\n{\n    struct gc_arena gc = gc_new();\n    struct multi_instance *mi;\n\n    msg(D_MULTI_MEDIUM, \"MULTI: multi_create_instance called\");\n\n    ALLOC_OBJ_CLEAR(mi, struct multi_instance);\n\n    mi->gc = gc_new();\n    multi_instance_inc_refcount(mi);\n    mi->vaddr_handle = -1;\n    mi->created = now;\n    mroute_addr_init(&mi->real);\n\n    if (real)\n    {\n        mi->real = *real;\n        generate_prefix(mi);\n    }\n\n    inherit_context_child(&mi->context, &m->top, sock);\n    if (IS_SIG(&mi->context))\n    {\n        goto err;\n    }\n\n    mi->context.c2.tls_multi->multi_state = CAS_NOT_CONNECTED;\n\n    if (hash_n_elements(m->hash) >= m->max_clients)\n    {\n        msg(D_MULTI_ERRORS,\n            \"MULTI: new incoming connection would exceed maximum number of clients (%d)\",\n            m->max_clients);\n        goto err;\n    }\n\n    if (!real) /* TCP mode? */\n    {\n        if (!multi_tcp_instance_specific_init(m, mi))\n        {\n            goto err;\n        }\n        generate_prefix(mi);\n    }\n\n    if (!hash_add(m->iter, &mi->real, mi, false))\n    {\n        msg(D_MULTI_LOW, \"MULTI: unable to add real address [%s] to iterator hash table\",\n            mroute_addr_print(&mi->real, &gc));\n        goto err;\n    }\n    mi->did_iter = true;\n\n#ifdef ENABLE_MANAGEMENT\n    do\n    {\n        mi->context.c2.mda_context.cid = m->cid_counter++;\n    } while (!hash_add(m->cid_hash, &mi->context.c2.mda_context.cid, mi, false));\n    mi->did_cid_hash = true;\n#endif\n\n    mi->context.c2.push_request_received = false;\n#ifdef ENABLE_ASYNC_PUSH\n    mi->inotify_watch = -1;\n#endif\n\n    if (!multi_process_post(m, mi, MPP_PRE_SELECT))\n    {\n        msg(D_MULTI_ERRORS, \"MULTI: signal occurred during client instance initialization\");\n        goto err;\n    }\n\n    mi->ev_arg.type = EVENT_ARG_MULTI_INSTANCE;\n    mi->ev_arg.u.mi = mi;\n\n    gc_free(&gc);\n    return mi;\n\nerr:\n    multi_close_instance(m, mi, false);\n    gc_free(&gc);\n    return NULL;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n/*\n * Dump tables -- triggered by SIGUSR2.\n * If status file is defined, write to file.\n * If status file is NULL, write to syslog.\n */\nstatic void\nmulti_print_status(struct multi_context *m, struct status_output *so, const int version)\n{\n    if (m->hash)\n    {\n        struct gc_arena gc_top = gc_new();\n        struct hash_iterator hi;\n        const struct hash_element *he;\n\n        status_reset(so);\n\n        if (dco_enabled(&m->top.options))\n        {\n            if (dco_get_peer_stats_multi(&m->top.c1.tuntap->dco, true) < 0)\n            {\n                return;\n            }\n        }\n\n        if (version == 1)\n        {\n            /*\n             * Status file version 1\n             */\n            status_printf(so, \"OpenVPN CLIENT LIST\");\n            status_printf(so, \"Updated,%s\", time_string(0, 0, false, &gc_top));\n            status_printf(so, \"Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since\");\n            hash_iterator_init(m->hash, &hi);\n            while ((he = hash_iterator_next(&hi)))\n            {\n                struct gc_arena gc = gc_new();\n                const struct multi_instance *mi = (struct multi_instance *)he->value;\n\n                if (!mi->halt)\n                {\n                    status_printf(so, \"%s,%s,\" counter_format \",\" counter_format \",%s\",\n                                  tls_common_name(mi->context.c2.tls_multi, false),\n                                  mroute_addr_print(&mi->real, &gc),\n                                  mi->context.c2.link_read_bytes + mi->context.c2.dco_read_bytes,\n                                  mi->context.c2.link_write_bytes + mi->context.c2.dco_write_bytes,\n                                  time_string(mi->created, 0, false, &gc));\n                }\n                gc_free(&gc);\n            }\n            hash_iterator_free(&hi);\n\n            status_printf(so, \"ROUTING TABLE\");\n            status_printf(so, \"Virtual Address,Common Name,Real Address,Last Ref\");\n            hash_iterator_init(m->vhash, &hi);\n            while ((he = hash_iterator_next(&hi)))\n            {\n                struct gc_arena gc = gc_new();\n                const struct multi_route *route = (struct multi_route *)he->value;\n\n                if (multi_route_defined(m, route))\n                {\n                    const struct multi_instance *mi = route->instance;\n                    const struct mroute_addr *ma = &route->addr;\n                    char flags[2] = { 0, 0 };\n\n                    if (route->flags & MULTI_ROUTE_CACHE)\n                    {\n                        flags[0] = 'C';\n                    }\n                    status_printf(so, \"%s%s,%s,%s,%s\", mroute_addr_print(ma, &gc), flags,\n                                  tls_common_name(mi->context.c2.tls_multi, false),\n                                  mroute_addr_print(&mi->real, &gc),\n                                  time_string(route->last_reference, 0, false, &gc));\n                }\n                gc_free(&gc);\n            }\n            hash_iterator_free(&hi);\n\n            status_printf(so, \"GLOBAL STATS\");\n            if (m->mbuf)\n            {\n                status_printf(so, \"Max bcast/mcast queue length,%d\", mbuf_maximum_queued(m->mbuf));\n            }\n\n            status_printf(so, \"END\");\n        }\n        else if (version == 2 || version == 3)\n        {\n            const char sep = (version == 3) ? '\\t' : ',';\n\n            /*\n             * Status file version 2 and 3\n             */\n            status_printf(so, \"TITLE%c%s\", sep, title_string);\n            status_printf(so, \"TIME%c%s%c%u\", sep, time_string(now, 0, false, &gc_top), sep,\n                          (unsigned int)now);\n            status_printf(\n                so,\n                \"HEADER%cCLIENT_LIST%cCommon Name%cReal Address%cVirtual Address%cVirtual IPv6 Address%cBytes Received%cBytes Sent%cConnected Since%cConnected Since (time_t)%cUsername%cClient ID%cPeer ID%cData Channel Cipher\",\n                sep, sep, sep, sep, sep, sep, sep, sep, sep, sep, sep, sep, sep);\n            hash_iterator_init(m->hash, &hi);\n            while ((he = hash_iterator_next(&hi)))\n            {\n                struct gc_arena gc = gc_new();\n                const struct multi_instance *mi = (struct multi_instance *)he->value;\n\n                if (!mi->halt)\n                {\n                    status_printf(\n                        so,\n                        \"CLIENT_LIST%c%s%c%s%c%s%c%s%c\" counter_format \"%c\" counter_format\n                        \"%c%s%c%u%c%s%c\"\n#ifdef ENABLE_MANAGEMENT\n                        \"%lu\"\n#else\n                        \"\"\n#endif\n                        \"%c%\" PRIu32 \"%c%s\",\n                        sep, tls_common_name(mi->context.c2.tls_multi, false), sep,\n                        mroute_addr_print(&mi->real, &gc), sep,\n                        print_in_addr_t(mi->reporting_addr, IA_EMPTY_IF_UNDEF, &gc), sep,\n                        print_in6_addr(mi->reporting_addr_ipv6, IA_EMPTY_IF_UNDEF, &gc), sep,\n                        mi->context.c2.link_read_bytes + mi->context.c2.dco_read_bytes, sep,\n                        mi->context.c2.link_write_bytes + mi->context.c2.dco_write_bytes, sep,\n                        time_string(mi->created, 0, false, &gc), sep, (unsigned int)mi->created,\n                        sep, tls_username(mi->context.c2.tls_multi, false),\n#ifdef ENABLE_MANAGEMENT\n                        sep, mi->context.c2.mda_context.cid,\n#else\n                        sep,\n#endif\n                        sep,\n                        mi->context.c2.tls_multi ? mi->context.c2.tls_multi->peer_id : UINT32_MAX,\n                        sep, translate_cipher_name_to_openvpn(mi->context.options.ciphername));\n                }\n                gc_free(&gc);\n            }\n            hash_iterator_free(&hi);\n\n            status_printf(\n                so,\n                \"HEADER%cROUTING_TABLE%cVirtual Address%cCommon Name%cReal Address%cLast Ref%cLast Ref (time_t)\",\n                sep, sep, sep, sep, sep, sep);\n            hash_iterator_init(m->vhash, &hi);\n            while ((he = hash_iterator_next(&hi)))\n            {\n                struct gc_arena gc = gc_new();\n                const struct multi_route *route = (struct multi_route *)he->value;\n\n                if (multi_route_defined(m, route))\n                {\n                    const struct multi_instance *mi = route->instance;\n                    const struct mroute_addr *ma = &route->addr;\n                    char flags[2] = { 0, 0 };\n\n                    if (route->flags & MULTI_ROUTE_CACHE)\n                    {\n                        flags[0] = 'C';\n                    }\n                    status_printf(so, \"ROUTING_TABLE%c%s%s%c%s%c%s%c%s%c%u\", sep,\n                                  mroute_addr_print(ma, &gc), flags, sep,\n                                  tls_common_name(mi->context.c2.tls_multi, false), sep,\n                                  mroute_addr_print(&mi->real, &gc), sep,\n                                  time_string(route->last_reference, 0, false, &gc), sep,\n                                  (unsigned int)route->last_reference);\n                }\n                gc_free(&gc);\n            }\n            hash_iterator_free(&hi);\n\n            if (m->mbuf)\n            {\n                status_printf(so, \"GLOBAL_STATS%cMax bcast/mcast queue length%c%d\", sep, sep,\n                              mbuf_maximum_queued(m->mbuf));\n            }\n\n            status_printf(so, \"GLOBAL_STATS%cdco_enabled%c%d\", sep, sep,\n                          dco_enabled(&m->top.options));\n            status_printf(so, \"END\");\n        }\n        else\n        {\n            status_printf(so, \"ERROR: bad status format version number\");\n        }\n\n#ifdef PACKET_TRUNCATION_CHECK\n        {\n            status_printf(so, \"HEADER,ERRORS,Common Name,TUN Read Trunc,TUN Write Trunc,Pre-encrypt Trunc,Post-decrypt Trunc\");\n            hash_iterator_init(m->hash, &hi);\n            while ((he = hash_iterator_next(&hi)))\n            {\n                struct gc_arena gc = gc_new();\n                const struct multi_instance *mi = (struct multi_instance *)he->value;\n\n                if (!mi->halt)\n                {\n                    status_printf(so,\n                                  \"ERRORS,%s,\" counter_format \",\" counter_format \",\" counter_format\n                                  \",\" counter_format,\n                                  tls_common_name(mi->context.c2.tls_multi, false),\n                                  m->top.c2.n_trunc_tun_read, mi->context.c2.n_trunc_tun_write,\n                                  mi->context.c2.n_trunc_pre_encrypt,\n                                  mi->context.c2.n_trunc_post_decrypt);\n                }\n                gc_free(&gc);\n            }\n            hash_iterator_free(&hi);\n        }\n#endif /* ifdef PACKET_TRUNCATION_CHECK */\n\n        status_flush(so);\n        gc_free(&gc_top);\n    }\n\n#ifdef ENABLE_ASYNC_PUSH\n    if (m->inotify_watchers)\n    {\n        msg(D_MULTI_DEBUG, \"inotify watchers count: %d\", hash_n_elements(m->inotify_watchers));\n    }\n#endif\n}\n\n/*\n * Learn a virtual address or route.\n * The learn will fail if the learn address\n * script/plugin fails.  In this case the\n * return value may be != mi.\n * Return the instance which owns this route,\n * or NULL if none.\n */\nstatic struct multi_instance *\nmulti_learn_addr(struct multi_context *m, struct multi_instance *mi, const struct mroute_addr *addr,\n                 const unsigned int flags)\n{\n    struct hash_element *he;\n    const uint32_t hv = hash_value(m->vhash, addr);\n    struct hash_bucket *bucket = hash_bucket(m->vhash, hv);\n    struct multi_route *oldroute = NULL;\n    struct multi_instance *owner = NULL;\n    struct gc_arena gc = gc_new();\n\n    /* if route currently exists, get the instance which owns it */\n    he = hash_lookup_fast(m->vhash, bucket, addr, hv);\n    if (he)\n    {\n        oldroute = (struct multi_route *)he->value;\n    }\n    if (oldroute && multi_route_defined(m, oldroute))\n    {\n        owner = oldroute->instance;\n    }\n\n    /* do we need to add address to hash table? */\n    if ((!owner || owner != mi) && mroute_learnable_address(addr, &gc)\n        && !mroute_addr_equal(addr, &m->local))\n    {\n        struct multi_route *newroute;\n        bool learn_succeeded = false;\n\n        ALLOC_OBJ(newroute, struct multi_route);\n        newroute->addr = *addr;\n        newroute->instance = mi;\n        newroute->flags = flags;\n        newroute->last_reference = now;\n        newroute->cache_generation = 0;\n\n        /* The cache is invalidated when cache_generation is incremented */\n        if (flags & MULTI_ROUTE_CACHE)\n        {\n            newroute->cache_generation = m->route_helper->cache_generation;\n        }\n\n        if (oldroute) /* route already exists? */\n        {\n            if (route_quota_test(mi) && learn_address_script(m, mi, \"update\", &newroute->addr))\n            {\n                learn_succeeded = true;\n                owner = mi;\n                multi_instance_inc_refcount(mi);\n                route_quota_inc(mi);\n\n                /* delete old route */\n                multi_route_del(oldroute);\n\n                /* modify hash table entry, replacing old route */\n                he->key = &newroute->addr;\n                he->value = newroute;\n            }\n        }\n        else\n        {\n            if (route_quota_test(mi) && learn_address_script(m, mi, \"add\", &newroute->addr))\n            {\n                learn_succeeded = true;\n                owner = mi;\n                multi_instance_inc_refcount(mi);\n                route_quota_inc(mi);\n\n                /* add new route */\n                hash_add_fast(m->vhash, bucket, &newroute->addr, hv, newroute);\n            }\n        }\n\n        msg(D_MULTI_LOW, \"MULTI: Learn%s: %s -> %s\", learn_succeeded ? \"\" : \" FAILED\",\n            mroute_addr_print(&newroute->addr, &gc), multi_instance_string(mi, false, &gc));\n\n        if (!learn_succeeded)\n        {\n            free(newroute);\n        }\n    }\n    gc_free(&gc);\n\n    return owner;\n}\n\n/*\n * Get client instance based on virtual address.\n */\nstatic struct multi_instance *\nmulti_get_instance_by_virtual_addr(struct multi_context *m, const struct mroute_addr *addr,\n                                   bool cidr_routing)\n{\n    struct multi_route *route;\n    struct multi_instance *ret = NULL;\n\n    /* check for local address */\n    if (mroute_addr_equal(addr, &m->local))\n    {\n        return NULL;\n    }\n\n    route = (struct multi_route *)hash_lookup(m->vhash, addr);\n\n    /* does host route (possible cached) exist? */\n    if (route && multi_route_defined(m, route))\n    {\n        struct multi_instance *mi = route->instance;\n        route->last_reference = now;\n        ret = mi;\n    }\n    else if (cidr_routing) /* do we need to regenerate a host route cache entry? */\n    {\n        struct mroute_helper *rh = m->route_helper;\n        struct mroute_addr tryaddr;\n        int i;\n\n        /* cycle through each CIDR length */\n        for (i = 0; i < rh->n_net_len; ++i)\n        {\n            tryaddr = *addr;\n            tryaddr.type |= MR_WITH_NETBITS;\n            tryaddr.netbits = rh->net_len[i];\n            mroute_addr_mask_host_bits(&tryaddr);\n\n            /* look up a possible route with netbits netmask */\n            route = (struct multi_route *)hash_lookup(m->vhash, &tryaddr);\n\n            if (route && multi_route_defined(m, route))\n            {\n                /* found an applicable route, cache host route */\n                struct multi_instance *mi = route->instance;\n                multi_learn_addr(m, mi, addr, MULTI_ROUTE_CACHE | MULTI_ROUTE_AGEABLE);\n                ret = mi;\n                break;\n            }\n        }\n    }\n\n#ifdef ENABLE_DEBUG\n    if (check_debug_level(D_MULTI_DEBUG))\n    {\n        struct gc_arena gc = gc_new();\n        const char *addr_text = mroute_addr_print(addr, &gc);\n        if (ret)\n        {\n            dmsg(D_MULTI_DEBUG, \"GET INST BY VIRT: %s -> %s via %s\", addr_text,\n                 multi_instance_string(ret, false, &gc), mroute_addr_print(&route->addr, &gc));\n        }\n        else\n        {\n            dmsg(D_MULTI_DEBUG, \"GET INST BY VIRT: %s [failed]\", addr_text);\n        }\n        gc_free(&gc);\n    }\n#endif\n\n    ASSERT(!(ret && ret->halt));\n    return ret;\n}\n\n/*\n * Helper function to multi_learn_addr().\n */\nstatic struct multi_instance *\nmulti_learn_in_addr_t(struct multi_context *m, struct multi_instance *mi, in_addr_t a,\n                      int netbits, /* -1 if host route, otherwise # of network bits in address */\n                      bool primary)\n{\n    struct openvpn_sockaddr remote_si;\n    struct mroute_addr addr = { 0 };\n\n    CLEAR(remote_si);\n    remote_si.addr.in4.sin_family = AF_INET;\n    remote_si.addr.in4.sin_addr.s_addr = htonl(a);\n    addr.proto = 0;\n    ASSERT(mroute_extract_openvpn_sockaddr(&addr, &remote_si, false));\n\n    if (netbits >= 0)\n    {\n        addr.type |= MR_WITH_NETBITS;\n        addr.netbits = (uint8_t)netbits;\n    }\n\n    struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0);\n#ifdef ENABLE_MANAGEMENT\n    if (management && owner)\n    {\n        management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);\n    }\n#endif\n    if (primary && multi_check_push_ifconfig_extra_route(mi, addr.v4.addr))\n    {\n        /* \"primary\" is the VPN ifconfig address of the peer */\n        /* if it does not fall into the network defined by ifconfig_local\n         * we install this as extra onscope address on the interface  */\n        addr.netbits = 32;\n        addr.type |= MR_ONLINK_DCO_ADDR;\n\n        dco_install_iroute(m, mi, &addr);\n    }\n    else if (!primary)\n    {\n        ASSERT(netbits >= 0); /* DCO requires populated netbits */\n        dco_install_iroute(m, mi, &addr);\n    }\n\n    return owner;\n}\n\nstatic struct multi_instance *\nmulti_learn_in6_addr(struct multi_context *m, struct multi_instance *mi, struct in6_addr a6,\n                     int netbits, /* -1 if host route, otherwise # of network bits in address */\n                     bool primary)\n{\n    struct mroute_addr addr = { 0 };\n\n    addr.len = 16;\n    addr.type = MR_ADDR_IPV6;\n    addr.netbits = 0;\n    addr.v6.addr = a6;\n\n    if (netbits >= 0)\n    {\n        addr.type |= MR_WITH_NETBITS;\n        addr.netbits = (uint8_t)netbits;\n        mroute_addr_mask_host_bits(&addr);\n    }\n\n    struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0);\n#ifdef ENABLE_MANAGEMENT\n    if (management && owner)\n    {\n        management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);\n    }\n#endif\n    if (primary && multi_check_push_ifconfig_ipv6_extra_route(mi, &addr.v6.addr))\n    {\n        /* \"primary\" is the VPN ifconfig address of the peer */\n        /* if it does not fall into the network defined by ifconfig_local\n         * we install this as extra onscope address on the interface  */\n        addr.netbits = 128;\n        addr.type |= MR_ONLINK_DCO_ADDR;\n\n        dco_install_iroute(m, mi, &addr);\n    }\n    else if (!primary)\n    {\n        /* \"primary\" is the VPN ifconfig address of the peer and already\n         * known to DCO, so only install \"extra\" iroutes (primary = false)\n         */\n        ASSERT(netbits >= 0); /* DCO requires populated netbits */\n        dco_install_iroute(m, mi, &addr);\n    }\n\n    return owner;\n}\n\n/*\n * A new client has connected, add routes (server -> client)\n * to internal routing table.\n */\nstatic void\nmulti_add_iroutes(struct multi_context *m, struct multi_instance *mi)\n{\n    struct gc_arena gc = gc_new();\n    const struct iroute *ir;\n    const struct iroute_ipv6 *ir6;\n    if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)\n    {\n        mi->did_iroutes = true;\n        for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next)\n        {\n            if (ir->netbits >= 0)\n            {\n                msg(D_MULTI_LOW, \"MULTI: internal route %s/%d -> %s\",\n                    print_in_addr_t(ir->network, 0, &gc), ir->netbits,\n                    multi_instance_string(mi, false, &gc));\n            }\n            else\n            {\n                msg(D_MULTI_LOW, \"MULTI: internal route %s -> %s\",\n                    print_in_addr_t(ir->network, 0, &gc), multi_instance_string(mi, false, &gc));\n            }\n\n            mroute_helper_add_iroute46(m->route_helper, ir->netbits);\n\n            multi_learn_in_addr_t(m, mi, ir->network, ir->netbits, false);\n        }\n        for (ir6 = mi->context.options.iroutes_ipv6; ir6 != NULL; ir6 = ir6->next)\n        {\n            msg(D_MULTI_LOW, \"MULTI: internal route %s/%d -> %s\",\n                print_in6_addr(ir6->network, 0, &gc), ir6->netbits,\n                multi_instance_string(mi, false, &gc));\n\n            mroute_helper_add_iroute46(m->route_helper, ir6->netbits);\n\n            multi_learn_in6_addr(m, mi, ir6->network, ir6->netbits, false);\n        }\n    }\n    gc_free(&gc);\n}\n\n/*\n * Given an instance (new_mi), delete all other instances which use the\n * same common name.\n */\nstatic void\nmulti_delete_dup(struct multi_context *m, struct multi_instance *new_mi)\n{\n    if (new_mi)\n    {\n        const char *new_cn = tls_common_name(new_mi->context.c2.tls_multi, true);\n        if (new_cn)\n        {\n            struct hash_iterator hi;\n            struct hash_element *he;\n            int count = 0;\n\n            hash_iterator_init(m->iter, &hi);\n            while ((he = hash_iterator_next(&hi)))\n            {\n                struct multi_instance *mi = (struct multi_instance *)he->value;\n                if (mi != new_mi && !mi->halt)\n                {\n                    const char *cn = tls_common_name(mi->context.c2.tls_multi, true);\n                    if (cn && !strcmp(cn, new_cn))\n                    {\n                        mi->did_iter = false;\n                        multi_close_instance(m, mi, false);\n                        hash_iterator_delete_element(&hi);\n                        ++count;\n                    }\n                }\n            }\n            hash_iterator_free(&hi);\n\n            if (count)\n            {\n                msg(D_MULTI_LOW,\n                    \"MULTI: new connection by client '%s' will cause previous active sessions by this client to be dropped.  Remember to use the --duplicate-cn option if you want multiple clients using the same certificate or username to concurrently connect.\",\n                    new_cn);\n            }\n        }\n    }\n}\n\nstatic void\ncheck_stale_routes(struct multi_context *m)\n{\n    struct gc_arena gc = gc_new();\n    struct hash_iterator hi;\n    struct hash_element *he;\n\n    dmsg(D_MULTI_DEBUG, \"MULTI: Checking stale routes\");\n    hash_iterator_init_range(m->vhash, &hi, 0, hash_n_buckets(m->vhash));\n    while ((he = hash_iterator_next(&hi)) != NULL)\n    {\n        struct multi_route *r = (struct multi_route *)he->value;\n        if (multi_route_defined(m, r)\n            && difftime(now, r->last_reference) >= m->top.options.stale_routes_ageing_time)\n        {\n            dmsg(D_MULTI_DEBUG, \"MULTI: Deleting stale route for address '%s'\",\n                 mroute_addr_print(&r->addr, &gc));\n            learn_address_script(m, NULL, \"delete\", &r->addr);\n            multi_route_del(r);\n            hash_iterator_delete_element(&hi);\n        }\n    }\n    hash_iterator_free(&hi);\n    gc_free(&gc);\n}\n\n/*\n * Ensure that endpoint to be pushed to client\n * complies with --ifconfig-push-constraint directive.\n */\nstatic bool\nifconfig_push_constraint_satisfied(const struct context *c)\n{\n    const struct options *o = &c->options;\n    if (o->push_ifconfig_constraint_defined && c->c2.push_ifconfig_defined)\n    {\n        return (o->push_ifconfig_constraint_netmask & c->c2.push_ifconfig_local)\n               == o->push_ifconfig_constraint_network;\n    }\n    else\n    {\n        return true;\n    }\n}\n\n/*\n * Select a virtual address for a new client instance.\n * Use an --ifconfig-push directive, if given (static IP).\n * Otherwise use an --ifconfig-pool address (dynamic IP).\n */\nstatic void\nmulti_select_virtual_addr(struct multi_context *m, struct multi_instance *mi)\n{\n    struct gc_arena gc = gc_new();\n\n    /*\n     * If ifconfig addresses were set by dynamic config file,\n     * release pool addresses, otherwise keep them.\n     */\n    if (mi->context.options.push_ifconfig_defined)\n    {\n        /* ifconfig addresses were set statically,\n         * release dynamic allocation */\n        if (mi->vaddr_handle >= 0)\n        {\n            ifconfig_pool_release(m->ifconfig_pool, mi->vaddr_handle, true);\n            mi->vaddr_handle = -1;\n        }\n\n        mi->context.c2.push_ifconfig_defined = true;\n        mi->context.c2.push_ifconfig_local = mi->context.options.push_ifconfig_local;\n        mi->context.c2.push_ifconfig_remote_netmask =\n            mi->context.options.push_ifconfig_remote_netmask;\n        mi->context.c2.push_ifconfig_local_alias = mi->context.options.push_ifconfig_local_alias;\n\n        /* the current implementation does not allow \"static IPv4, pool IPv6\",\n         * (see below) so issue a warning if that happens - don't break the\n         * session, though, as we don't even know if this client WANTS IPv6\n         */\n        if (mi->context.options.ifconfig_ipv6_pool_defined\n            && !mi->context.options.push_ifconfig_ipv6_defined)\n        {\n            msg(M_INFO,\n                \"MULTI_sva: WARNING: if --ifconfig-push is used for IPv4, automatic IPv6 assignment from --ifconfig-ipv6-pool does not work.  Use --ifconfig-ipv6-push for IPv6 then.\");\n        }\n    }\n    else if (m->ifconfig_pool && mi->vaddr_handle < 0) /* otherwise, choose a pool address */\n    {\n        in_addr_t local = 0, remote = 0;\n        struct in6_addr remote_ipv6;\n        const char *cn = NULL;\n\n        if (!mi->context.options.duplicate_cn)\n        {\n            cn = tls_common_name(mi->context.c2.tls_multi, true);\n        }\n\n        CLEAR(remote_ipv6);\n        mi->vaddr_handle =\n            ifconfig_pool_acquire(m->ifconfig_pool, &local, &remote, &remote_ipv6, cn);\n        if (mi->vaddr_handle >= 0)\n        {\n            const int tunnel_type = TUNNEL_TYPE(mi->context.c1.tuntap);\n            const int tunnel_topology = TUNNEL_TOPOLOGY(mi->context.c1.tuntap);\n\n            msg(M_INFO, \"MULTI_sva: pool returned IPv4=%s, IPv6=%s\",\n                (mi->context.options.ifconfig_pool_defined ? print_in_addr_t(remote, 0, &gc)\n                                                           : \"(Not enabled)\"),\n                (mi->context.options.ifconfig_ipv6_pool_defined\n                     ? print_in6_addr(remote_ipv6, 0, &gc)\n                     : \"(Not enabled)\"));\n\n            if (mi->context.options.ifconfig_pool_defined)\n            {\n                /* set push_ifconfig_remote_netmask from pool ifconfig address(es) */\n                mi->context.c2.push_ifconfig_local = remote;\n                if (tunnel_type == DEV_TYPE_TAP\n                    || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET))\n                {\n                    mi->context.c2.push_ifconfig_remote_netmask =\n                        mi->context.options.ifconfig_pool_netmask;\n                    if (!mi->context.c2.push_ifconfig_remote_netmask)\n                    {\n                        mi->context.c2.push_ifconfig_remote_netmask =\n                            mi->context.c1.tuntap->remote_netmask;\n                    }\n                }\n                else if (tunnel_type == DEV_TYPE_TUN)\n                {\n                    if (tunnel_topology == TOP_P2P)\n                    {\n                        mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->local;\n                    }\n                    else if (tunnel_topology == TOP_NET30)\n                    {\n                        mi->context.c2.push_ifconfig_remote_netmask = local;\n                    }\n                }\n\n                if (mi->context.c2.push_ifconfig_remote_netmask)\n                {\n                    mi->context.c2.push_ifconfig_defined = true;\n                }\n                else\n                {\n                    msg(D_MULTI_ERRORS,\n                        \"MULTI: no --ifconfig-pool netmask parameter is available to push to %s\",\n                        multi_instance_string(mi, false, &gc));\n                }\n            }\n\n            if (mi->context.options.ifconfig_ipv6_pool_defined)\n            {\n                mi->context.c2.push_ifconfig_ipv6_local = remote_ipv6;\n                mi->context.c2.push_ifconfig_ipv6_remote = mi->context.c1.tuntap->local_ipv6;\n                mi->context.c2.push_ifconfig_ipv6_netbits =\n                    mi->context.options.ifconfig_ipv6_netbits;\n                mi->context.c2.push_ifconfig_ipv6_defined = true;\n            }\n        }\n        else\n        {\n            msg(D_MULTI_ERRORS, \"MULTI: no free --ifconfig-pool addresses are available\");\n        }\n    }\n\n    /* IPv6 push_ifconfig is a bit problematic - since IPv6 shares the\n     * pool handling with IPv4, the combination \"static IPv4, dynamic IPv6\"\n     * will fail (because no pool will be allocated in this case).\n     * OTOH, this doesn't make too much sense in reality - and the other\n     * way round (\"dynamic IPv4, static IPv6\") or \"both static\" makes sense\n     * -> and so it's implemented right now\n     */\n    if (mi->context.options.push_ifconfig_ipv6_defined)\n    {\n        mi->context.c2.push_ifconfig_ipv6_local = mi->context.options.push_ifconfig_ipv6_local;\n        mi->context.c2.push_ifconfig_ipv6_remote = mi->context.options.push_ifconfig_ipv6_remote;\n        mi->context.c2.push_ifconfig_ipv6_netbits = mi->context.options.push_ifconfig_ipv6_netbits;\n        mi->context.c2.push_ifconfig_ipv6_defined = true;\n\n        msg(M_INFO, \"MULTI_sva: push_ifconfig_ipv6 %s/%d\",\n            print_in6_addr(mi->context.c2.push_ifconfig_ipv6_local, 0, &gc),\n            mi->context.c2.push_ifconfig_ipv6_netbits);\n    }\n\n    gc_free(&gc);\n}\n\n/*\n * Set virtual address environmental variables.\n */\nstatic void\nmulti_set_virtual_addr_env(struct multi_instance *mi)\n{\n    setenv_del(mi->context.c2.es, \"ifconfig_pool_local_ip\");\n    setenv_del(mi->context.c2.es, \"ifconfig_pool_remote_ip\");\n    setenv_del(mi->context.c2.es, \"ifconfig_pool_netmask\");\n\n    if (mi->context.c2.push_ifconfig_defined)\n    {\n        const int tunnel_type = TUNNEL_TYPE(mi->context.c1.tuntap);\n        const int tunnel_topology = TUNNEL_TOPOLOGY(mi->context.c1.tuntap);\n\n        setenv_in_addr_t(mi->context.c2.es, \"ifconfig_pool_remote_ip\",\n                         mi->context.c2.push_ifconfig_local, SA_SET_IF_NONZERO);\n\n        if (tunnel_type == DEV_TYPE_TAP\n            || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET))\n        {\n            setenv_in_addr_t(mi->context.c2.es, \"ifconfig_pool_netmask\",\n                             mi->context.c2.push_ifconfig_remote_netmask, SA_SET_IF_NONZERO);\n        }\n        else if (tunnel_type == DEV_TYPE_TUN)\n        {\n            setenv_in_addr_t(mi->context.c2.es, \"ifconfig_pool_local_ip\",\n                             mi->context.c2.push_ifconfig_remote_netmask, SA_SET_IF_NONZERO);\n        }\n    }\n\n    setenv_del(mi->context.c2.es, \"ifconfig_pool_local_ip6\");\n    setenv_del(mi->context.c2.es, \"ifconfig_pool_remote_ip6\");\n    setenv_del(mi->context.c2.es, \"ifconfig_pool_ip6_netbits\");\n\n    if (mi->context.c2.push_ifconfig_ipv6_defined)\n    {\n        setenv_in6_addr(mi->context.c2.es, \"ifconfig_pool_remote\",\n                        &mi->context.c2.push_ifconfig_ipv6_local, SA_SET_IF_NONZERO);\n        setenv_in6_addr(mi->context.c2.es, \"ifconfig_pool_local\",\n                        &mi->context.c2.push_ifconfig_ipv6_remote, SA_SET_IF_NONZERO);\n        setenv_int(mi->context.c2.es, \"ifconfig_pool_ip6_netbits\",\n                   mi->context.c2.push_ifconfig_ipv6_netbits);\n    }\n}\n\n/*\n * Called after client-connect script is called\n */\nstatic void\nmulti_client_connect_post(struct multi_context *m, struct multi_instance *mi, const char *dc_file,\n                          unsigned int *option_types_found)\n{\n    /* Did script generate a dynamic config file? */\n    if (platform_test_file(dc_file))\n    {\n        options_server_import(&mi->context.options, dc_file, D_IMPORT_ERRORS | M_OPTERR,\n                              CLIENT_CONNECT_OPT_MASK, option_types_found, mi->context.c2.es);\n\n        /*\n         * If the --client-connect script generates a config file\n         * with an --ifconfig-push directive, it will override any\n         * --ifconfig-push directive from the --client-config-dir\n         * directory or any --ifconfig-pool dynamic address.\n         */\n        multi_select_virtual_addr(m, mi);\n        multi_set_virtual_addr_env(mi);\n    }\n}\n\n#ifdef ENABLE_PLUGIN\n\n/*\n * Called after client-connect plug-in is called\n */\nstatic void\nmulti_client_connect_post_plugin(struct multi_context *m, struct multi_instance *mi,\n                                 const struct plugin_return *pr, unsigned int *option_types_found)\n{\n    struct plugin_return config;\n\n    plugin_return_get_column(pr, &config, \"config\");\n\n    /* Did script generate a dynamic config file? */\n    if (plugin_return_defined(&config))\n    {\n        int i;\n        for (i = 0; i < config.n; ++i)\n        {\n            if (config.list[i] && config.list[i]->value)\n            {\n                options_string_import(&mi->context.options, config.list[i]->value,\n                                      D_IMPORT_ERRORS | M_OPTERR, CLIENT_CONNECT_OPT_MASK,\n                                      option_types_found, mi->context.c2.es);\n            }\n        }\n\n        /*\n         * If the --client-connect script generates a config file\n         * with an --ifconfig-push directive, it will override any\n         * --ifconfig-push directive from the --client-config-dir\n         * directory or any --ifconfig-pool dynamic address.\n         */\n        multi_select_virtual_addr(m, mi);\n        multi_set_virtual_addr_env(mi);\n    }\n}\n\n#endif /* ifdef ENABLE_PLUGIN */\n\n\n/*\n * Called to load management-derived client-connect config\n */\nenum client_connect_return\nmulti_client_connect_mda(struct multi_context *m, struct multi_instance *mi, bool deferred,\n                         unsigned int *option_types_found)\n{\n    /* We never return CC_RET_DEFERRED */\n    ASSERT(!deferred);\n    enum client_connect_return ret = CC_RET_SKIPPED;\n#ifdef ENABLE_MANAGEMENT\n    if (mi->cc_config)\n    {\n        struct buffer_entry *be;\n        for (be = mi->cc_config->head; be != NULL; be = be->next)\n        {\n            const char *opt = BSTR(&be->buf);\n            options_string_import(&mi->context.options, opt, D_IMPORT_ERRORS | M_OPTERR,\n                                  CLIENT_CONNECT_OPT_MASK, option_types_found, mi->context.c2.es);\n        }\n\n        /*\n         * If the --client-connect script generates a config file\n         * with an --ifconfig-push directive, it will override any\n         * --ifconfig-push directive from the --client-config-dir\n         * directory or any --ifconfig-pool dynamic address.\n         */\n        multi_select_virtual_addr(m, mi);\n        multi_set_virtual_addr_env(mi);\n\n        ret = CC_RET_SUCCEEDED;\n    }\n#endif /* ifdef ENABLE_MANAGEMENT */\n    return ret;\n}\n\nstatic void\nmulti_client_connect_setenv(struct multi_instance *mi)\n{\n    struct gc_arena gc = gc_new();\n\n    /* setenv incoming cert common name for script */\n    setenv_str(mi->context.c2.es, \"common_name\", tls_common_name(mi->context.c2.tls_multi, true));\n\n    /* setenv client real IP address */\n    setenv_trusted(mi->context.c2.es, get_link_socket_info(&mi->context));\n\n    /* setenv client virtual IP address */\n    multi_set_virtual_addr_env(mi);\n\n    /* setenv connection time */\n    {\n        const char *created_ascii = time_string(mi->created, 0, false, &gc);\n        setenv_str(mi->context.c2.es, \"time_ascii\", created_ascii);\n        setenv_long_long(mi->context.c2.es, \"time_unix\", mi->created);\n    }\n\n    gc_free(&gc);\n}\n\n/**\n * Calculates the options that depend on the client capabilities\n * based on local options and available peer info\n * - choosen cipher\n * - peer id\n */\nstatic bool\nmulti_client_set_protocol_options(struct context *c)\n{\n    struct tls_multi *tls_multi = c->c2.tls_multi;\n    const char *const peer_info = tls_multi->peer_info;\n    struct options *o = &c->options;\n\n\n    unsigned int proto = extract_iv_proto(peer_info);\n    if (proto & IV_PROTO_DATA_V2)\n    {\n        tls_multi->use_peer_id = true;\n        o->use_peer_id = true;\n    }\n    else if (dco_enabled(o))\n    {\n        msg(M_INFO, \"Client does not support DATA_V2. Data channel offloading \"\n                    \"requires DATA_V2. Dropping client.\");\n        auth_set_client_reason(tls_multi, \"Data channel negotiation \"\n                                          \"failed (missing DATA_V2)\");\n        return false;\n    }\n\n    /* Print a warning if we detect the client being in P2P mode and will\n     * not accept our pushed ciphers */\n    if (proto & IV_PROTO_NCP_P2P)\n    {\n        msg(M_WARN, \"Note: peer reports running in P2P mode (no --pull/--client \"\n                    \"option). It will not negotiate ciphers with this server. \"\n                    \"Expect this connection to fail.\");\n    }\n\n    if (proto & IV_PROTO_REQUEST_PUSH)\n    {\n        c->c2.push_request_received = true;\n    }\n\n    if (proto & IV_PROTO_TLS_KEY_EXPORT)\n    {\n        o->imported_protocol_flags |= CO_USE_TLS_KEY_MATERIAL_EXPORT;\n    }\n    else if (o->force_key_material_export)\n    {\n        msg(M_INFO, \"PUSH: client does not support TLS Keying Material \"\n                    \"Exporters but --force-tls-key-material-export is enabled.\");\n        auth_set_client_reason(tls_multi, \"Client incompatible with this \"\n                                          \"server. Keying Material Exporters (RFC 5705) \"\n                                          \"support missing. Upgrade to a client that \"\n                                          \"supports this feature (OpenVPN 2.6.0+).\");\n        return false;\n    }\n    if (proto & IV_PROTO_DYN_TLS_CRYPT)\n    {\n        o->imported_protocol_flags |= CO_USE_DYNAMIC_TLS_CRYPT;\n    }\n\n    if (proto & IV_PROTO_CC_EXIT_NOTIFY)\n    {\n        o->imported_protocol_flags |= CO_USE_CC_EXIT_NOTIFY;\n    }\n\n    /* Select cipher if client supports Negotiable Crypto Parameters */\n\n    /* if we have already created our key, we cannot *change* our own\n     * cipher -> so log the fact and push the \"what we have now\" cipher\n     * (so the client is always told what we expect it to use)\n     */\n    if (get_primary_key(tls_multi)->crypto_options.key_ctx_bi.initialized)\n    {\n        msg(M_INFO,\n            \"PUSH: client wants to negotiate cipher (NCP), but \"\n            \"server has already generated data channel keys, \"\n            \"re-sending previously negotiated cipher '%s'\",\n            o->ciphername);\n        return true;\n    }\n\n    /*\n     * Push the first cipher from --data-ciphers to the client that\n     * the client announces to be supporting.\n     */\n    char *push_cipher =\n        ncp_get_best_cipher(o->ncp_ciphers, peer_info, tls_multi->remote_ciphername, &o->gc);\n    if (push_cipher)\n    {\n        /* Enable epoch data key format if supported and AEAD cipher in use */\n        if (tls_multi->session[TM_ACTIVE].opt->data_epoch_supported && (proto & IV_PROTO_DATA_EPOCH)\n            && cipher_kt_mode_aead(push_cipher))\n        {\n            o->imported_protocol_flags |= CO_EPOCH_DATA_KEY_FORMAT;\n        }\n\n        o->ciphername = push_cipher;\n        return true;\n    }\n\n    /* NCP cipher negotiation failed. Try to figure out why exactly it\n     * failed and give good error messages and potentially do a fallback\n     * for non NCP clients */\n    struct gc_arena gc = gc_new();\n    bool ret = false;\n\n    const char *peer_ciphers = tls_peer_ncp_list(peer_info, &gc);\n    /* If we are in a situation where we know the client ciphers, there is no\n     * reason to fall back to a cipher that will not be accepted by the other\n     * side, in this situation we fail the auth*/\n    if (strlen(peer_ciphers) > 0)\n    {\n        msg(M_INFO,\n            \"PUSH: No common cipher between server and client. \"\n            \"Server data-ciphers: '%s'%s, client supported ciphers '%s'\",\n            o->ncp_ciphers_conf, ncp_expanded_ciphers(o, &gc), peer_ciphers);\n    }\n    else if (tls_multi->remote_ciphername)\n    {\n        msg(M_INFO,\n            \"PUSH: No common cipher between server and client. \"\n            \"Server data-ciphers: '%s'%s, client supports cipher '%s'\",\n            o->ncp_ciphers_conf, ncp_expanded_ciphers(o, &gc), tls_multi->remote_ciphername);\n    }\n    else\n    {\n        msg(M_INFO, \"PUSH: No NCP or OCC cipher data received from peer.\");\n\n        if (o->enable_ncp_fallback && !tls_multi->remote_ciphername)\n        {\n            msg(M_INFO,\n                \"Using data channel cipher '%s' since \"\n                \"--data-ciphers-fallback is set.\",\n                o->ciphername);\n            ret = true;\n        }\n        else\n        {\n            msg(M_INFO, \"Use --data-ciphers-fallback with the cipher the \"\n                        \"client is using if you want to allow the client to connect\");\n        }\n    }\n    if (!ret)\n    {\n        auth_set_client_reason(tls_multi, \"Data channel cipher negotiation \"\n                                          \"failed (no shared cipher)\");\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n\n/**\n * Delete the temporary file for the return value of client connect\n * It also removes it from client_connect_defer_state and environment\n */\nstatic void\nccs_delete_deferred_ret_file(struct multi_instance *mi)\n{\n    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);\n    if (!ccs->deferred_ret_file)\n    {\n        return;\n    }\n\n    setenv_del(mi->context.c2.es, \"client_connect_deferred_file\");\n    if (!platform_unlink(ccs->deferred_ret_file))\n    {\n        msg(D_MULTI_ERRORS, \"MULTI: problem deleting temporary file: %s\", ccs->deferred_ret_file);\n    }\n    free(ccs->deferred_ret_file);\n    ccs->deferred_ret_file = NULL;\n}\n\n/**\n * Create a temporary file for the return value of client connect\n * and puts it into the client_connect_defer_state and environment\n * as \"client_connect_deferred_file\"\n *\n * @return boolean value if creation was successful\n */\nstatic bool\nccs_gen_deferred_ret_file(struct multi_instance *mi)\n{\n    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);\n    struct gc_arena gc = gc_new();\n    const char *fn;\n\n    /* Delete file if it already exists */\n    ccs_delete_deferred_ret_file(mi);\n\n    fn = platform_create_temp_file(mi->context.options.tmp_dir, \"ccr\", &gc);\n    if (!fn)\n    {\n        gc_free(&gc);\n        return false;\n    }\n    ccs->deferred_ret_file = string_alloc(fn, NULL);\n\n    setenv_str(mi->context.c2.es, \"client_connect_deferred_file\", ccs->deferred_ret_file);\n\n    gc_free(&gc);\n    return true;\n}\n\n/**\n * Tests whether the deferred return value file exists and returns the\n * contained return value.\n *\n * @return CC_RET_SKIPPED if the file does not exist or is empty.\n *         CC_RET_DEFERRED, CC_RET_SUCCEEDED or CC_RET_FAILED depending on\n *         the value stored in the file.\n */\nstatic enum client_connect_return\nccs_test_deferred_ret_file(struct multi_instance *mi)\n{\n    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);\n    FILE *fp = fopen(ccs->deferred_ret_file, \"r\");\n    if (!fp)\n    {\n        return CC_RET_SKIPPED;\n    }\n\n    enum client_connect_return ret = CC_RET_SKIPPED;\n    const int c = fgetc(fp);\n    switch (c)\n    {\n        case '0':\n            ret = CC_RET_FAILED;\n            break;\n\n        case '1':\n            ret = CC_RET_SUCCEEDED;\n            break;\n\n        case '2':\n            ret = CC_RET_DEFERRED;\n            break;\n\n        case EOF:\n            if (feof(fp))\n            {\n                ret = CC_RET_SKIPPED;\n                break;\n            }\n\n        /* Not EOF but other error -> fall through to error state */\n        default:\n            /* We received an unknown/unexpected value.  Assume failure. */\n            msg(M_WARN, \"WARNING: Unknown/unexpected value in deferred \"\n                        \"client-connect resultfile\");\n            ret = CC_RET_FAILED;\n    }\n    fclose(fp);\n\n    return ret;\n}\n\n/**\n * Deletes the temporary file for the config directives of the  client connect\n * script and removes it into the client_connect_defer_state and environment\n *\n */\nstatic void\nccs_delete_config_file(struct multi_instance *mi)\n{\n    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);\n    if (ccs->config_file)\n    {\n        setenv_del(mi->context.c2.es, \"client_connect_config_file\");\n        if (!platform_unlink(ccs->config_file))\n        {\n            msg(D_MULTI_ERRORS, \"MULTI: problem deleting temporary file: %s\", ccs->config_file);\n        }\n        free(ccs->config_file);\n        ccs->config_file = NULL;\n    }\n}\n\n/**\n * Create a temporary file for the config directives of the  client connect\n * script and puts it into the client_connect_defer_state and environment\n * as \"client_connect_config_file\"\n *\n * @return boolean value if creation was successful\n */\nstatic bool\nccs_gen_config_file(struct multi_instance *mi)\n{\n    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);\n    struct gc_arena gc = gc_new();\n    const char *fn;\n\n    if (ccs->config_file)\n    {\n        ccs_delete_config_file(mi);\n    }\n\n    fn = platform_create_temp_file(mi->context.options.tmp_dir, \"cc\", &gc);\n    if (!fn)\n    {\n        gc_free(&gc);\n        return false;\n    }\n    ccs->config_file = string_alloc(fn, NULL);\n\n    setenv_str(mi->context.c2.es, \"client_connect_config_file\", ccs->config_file);\n\n    gc_free(&gc);\n    return true;\n}\n\nstatic enum client_connect_return\nmulti_client_connect_call_plugin_v1(struct multi_context *m, struct multi_instance *mi,\n                                    bool deferred, unsigned int *option_types_found)\n{\n    enum client_connect_return ret = CC_RET_SKIPPED;\n#ifdef ENABLE_PLUGIN\n    ASSERT(m);\n    ASSERT(mi);\n    ASSERT(option_types_found);\n    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);\n\n    /* deprecated callback, use a file for passing back return info */\n    if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT))\n    {\n        struct argv argv = argv_new();\n        int call;\n\n        if (!deferred)\n        {\n            call = OPENVPN_PLUGIN_CLIENT_CONNECT;\n            if (!ccs_gen_config_file(mi) || !ccs_gen_deferred_ret_file(mi))\n            {\n                ret = CC_RET_FAILED;\n                goto cleanup;\n            }\n        }\n        else\n        {\n            call = OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER;\n            /* the initial call should have created these files */\n            ASSERT(ccs->config_file);\n            ASSERT(ccs->deferred_ret_file);\n        }\n\n        argv_printf(&argv, \"%s\", ccs->config_file);\n        int plug_ret = plugin_call(mi->context.plugins, call, &argv, NULL, mi->context.c2.es);\n        if (plug_ret == OPENVPN_PLUGIN_FUNC_SUCCESS)\n        {\n            ret = CC_RET_SUCCEEDED;\n        }\n        else if (plug_ret == OPENVPN_PLUGIN_FUNC_DEFERRED)\n        {\n            ret = CC_RET_DEFERRED;\n            /**\n             * Contrary to the plugin v2 API, we do not demand a working\n             * deferred plugin as all return can be handled by the files\n             * and plugin_call return success if a plugin is not defined\n             */\n        }\n        else\n        {\n            msg(M_WARN, \"WARNING: client-connect plugin call failed\");\n            ret = CC_RET_FAILED;\n        }\n\n\n        /**\n         * plugin api v1 client connect async feature has both plugin and\n         * file return status, so in cases where the file has a code that\n         * demands override, we override our return code\n         */\n        int file_ret = ccs_test_deferred_ret_file(mi);\n\n        if (file_ret == CC_RET_FAILED)\n        {\n            ret = CC_RET_FAILED;\n        }\n        else if (ret == CC_RET_SUCCEEDED && file_ret == CC_RET_DEFERRED)\n        {\n            ret = CC_RET_DEFERRED;\n        }\n\n        /* if we still think we have succeeded, do postprocessing */\n        if (ret == CC_RET_SUCCEEDED)\n        {\n            multi_client_connect_post(m, mi, ccs->config_file, option_types_found);\n        }\ncleanup:\n        argv_free(&argv);\n\n        if (ret != CC_RET_DEFERRED)\n        {\n            ccs_delete_config_file(mi);\n            ccs_delete_deferred_ret_file(mi);\n        }\n    }\n#endif /* ifdef ENABLE_PLUGIN */\n    return ret;\n}\n\nstatic enum client_connect_return\nmulti_client_connect_call_plugin_v2(struct multi_context *m, struct multi_instance *mi,\n                                    bool deferred, unsigned int *option_types_found)\n{\n    enum client_connect_return ret = CC_RET_SKIPPED;\n#ifdef ENABLE_PLUGIN\n    ASSERT(m);\n    ASSERT(mi);\n    ASSERT(option_types_found);\n\n    int call = deferred ? OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2 : OPENVPN_PLUGIN_CLIENT_CONNECT_V2;\n    /* V2 callback, use a plugin_return struct for passing back return info */\n    if (plugin_defined(mi->context.plugins, call))\n    {\n        struct plugin_return pr;\n\n        plugin_return_init(&pr);\n\n        int plug_ret = plugin_call(mi->context.plugins, call, NULL, &pr, mi->context.c2.es);\n        if (plug_ret == OPENVPN_PLUGIN_FUNC_SUCCESS)\n        {\n            multi_client_connect_post_plugin(m, mi, &pr, option_types_found);\n            ret = CC_RET_SUCCEEDED;\n        }\n        else if (plug_ret == OPENVPN_PLUGIN_FUNC_DEFERRED)\n        {\n            ret = CC_RET_DEFERRED;\n            if (!(plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2)))\n            {\n                msg(M_WARN, \"A plugin that defers from the \"\n                            \"OPENVPN_PLUGIN_CLIENT_CONNECT_V2 call must also \"\n                            \"declare support for \"\n                            \"OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2\");\n                ret = CC_RET_FAILED;\n            }\n        }\n        else\n        {\n            msg(M_WARN, \"WARNING: client-connect-v2 plugin call failed\");\n            ret = CC_RET_FAILED;\n        }\n\n\n        plugin_return_free(&pr);\n    }\n#endif /* ifdef ENABLE_PLUGIN */\n    return ret;\n}\n\nstatic enum client_connect_return\nmulti_client_connect_script_deferred(struct multi_context *m, struct multi_instance *mi,\n                                     unsigned int *option_types_found)\n{\n    ASSERT(mi);\n    ASSERT(option_types_found);\n    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);\n    enum client_connect_return ret = CC_RET_SKIPPED;\n\n    ret = ccs_test_deferred_ret_file(mi);\n\n    if (ret == CC_RET_SKIPPED)\n    {\n        /*\n         * Skipped and deferred are equivalent in this context.\n         * skipped means that the called program has not yet\n         * written a return status implicitly needing more time\n         * while deferred is the explicit notification that it\n         * needs more time\n         */\n        ret = CC_RET_DEFERRED;\n    }\n\n    if (ret == CC_RET_SUCCEEDED)\n    {\n        ccs_delete_deferred_ret_file(mi);\n        multi_client_connect_post(m, mi, ccs->config_file, option_types_found);\n        ccs_delete_config_file(mi);\n    }\n    if (ret == CC_RET_FAILED)\n    {\n        msg(M_INFO, \"MULTI: deferred --client-connect script returned CC_RET_FAILED\");\n        ccs_delete_deferred_ret_file(mi);\n        ccs_delete_config_file(mi);\n    }\n    return ret;\n}\n\n/**\n * Runs the --client-connect script if one is defined.\n */\nstatic enum client_connect_return\nmulti_client_connect_call_script(struct multi_context *m, struct multi_instance *mi, bool deferred,\n                                 unsigned int *option_types_found)\n{\n    if (deferred)\n    {\n        return multi_client_connect_script_deferred(m, mi, option_types_found);\n    }\n    ASSERT(m);\n    ASSERT(mi);\n\n    enum client_connect_return ret = CC_RET_SKIPPED;\n    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);\n\n    if (mi->context.options.client_connect_script)\n    {\n        struct argv argv = argv_new();\n        struct gc_arena gc = gc_new();\n\n        setenv_str(mi->context.c2.es, \"script_type\", \"client-connect\");\n\n        if (!ccs_gen_config_file(mi) || !ccs_gen_deferred_ret_file(mi))\n        {\n            ret = CC_RET_FAILED;\n            goto cleanup;\n        }\n\n        argv_parse_cmd(&argv, mi->context.options.client_connect_script);\n        argv_printf_cat(&argv, \"%s\", ccs->config_file);\n\n        if (openvpn_run_script(&argv, mi->context.c2.es, 0, \"--client-connect\"))\n        {\n            if (ccs_test_deferred_ret_file(mi) == CC_RET_DEFERRED)\n            {\n                ret = CC_RET_DEFERRED;\n            }\n            else\n            {\n                multi_client_connect_post(m, mi, ccs->config_file, option_types_found);\n                ret = CC_RET_SUCCEEDED;\n            }\n        }\n        else\n        {\n            ret = CC_RET_FAILED;\n        }\ncleanup:\n        if (ret != CC_RET_DEFERRED)\n        {\n            ccs_delete_config_file(mi);\n            ccs_delete_deferred_ret_file(mi);\n        }\n        argv_free(&argv);\n        gc_free(&gc);\n    }\n    return ret;\n}\n\nstatic bool\nmulti_client_setup_dco_initial(struct multi_context *m, struct multi_instance *mi,\n                               struct gc_arena *gc)\n{\n    if (!dco_enabled(&mi->context.options))\n    {\n        /* DCO not enabled, nothing to do, return sucess */\n        return true;\n    }\n    int ret = dco_multi_add_new_peer(m, mi);\n    if (ret < 0)\n    {\n        msg(D_DCO, \"Cannot add peer to DCO for %s: %s (%d)\", multi_instance_string(mi, false, gc),\n            strerror(-ret), ret);\n        return false;\n    }\n\n    return true;\n}\n\n/**\n * Generates the data channel keys\n */\nstatic bool\nmulti_client_generate_tls_keys(struct context *c)\n{\n    struct frame *frame_fragment = NULL;\n#ifdef ENABLE_FRAGMENT\n    if (c->options.ce.fragment)\n    {\n        frame_fragment = &c->c2.frame_fragment;\n    }\n#endif\n    struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];\n    if (!tls_session_update_crypto_params(c->c2.tls_multi, session, &c->options, &c->c2.frame,\n                                          frame_fragment, get_link_socket_info(c),\n                                          &c->c1.tuntap->dco))\n    {\n        msg(D_TLS_ERRORS, \"TLS Error: initializing data channel failed\");\n        register_signal(c->sig, SIGUSR1, \"process-push-msg-failed\");\n        return false;\n    }\n\n    return true;\n}\n\nstatic void\nmulti_client_connect_late_setup(struct multi_context *m, struct multi_instance *mi,\n                                const unsigned int option_types_found)\n{\n    ASSERT(m);\n    ASSERT(mi);\n\n    struct gc_arena gc = gc_new();\n    /*\n     * Process sourced options.\n     */\n    do_deferred_options(&mi->context, option_types_found, false);\n\n    /*\n     * make sure we got ifconfig settings from somewhere\n     */\n    if (!mi->context.c2.push_ifconfig_defined)\n    {\n        msg(D_MULTI_ERRORS,\n            \"MULTI: no dynamic or static remote \"\n            \"--ifconfig address is available for %s\",\n            multi_instance_string(mi, false, &gc));\n    }\n\n    /*\n     * make sure that ifconfig settings comply with constraints\n     */\n    if (!ifconfig_push_constraint_satisfied(&mi->context))\n    {\n        const char *ifconfig_constraint_network =\n            print_in_addr_t(mi->context.options.push_ifconfig_constraint_network, 0, &gc);\n        const char *ifconfig_constraint_netmask =\n            print_in_addr_t(mi->context.options.push_ifconfig_constraint_netmask, 0, &gc);\n\n        /* JYFIXME -- this should cause the connection to fail */\n        msg(D_MULTI_ERRORS,\n            \"MULTI ERROR: primary virtual IP for %s (%s) \"\n            \"violates tunnel network/netmask constraint (%s/%s)\",\n            multi_instance_string(mi, false, &gc),\n            print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc),\n            ifconfig_constraint_network, ifconfig_constraint_netmask);\n    }\n\n    /* set our client's VPN endpoint for status reporting purposes */\n    mi->reporting_addr = mi->context.c2.push_ifconfig_local;\n    mi->reporting_addr_ipv6 = mi->context.c2.push_ifconfig_ipv6_local;\n\n    /* set context-level authentication flag */\n    mi->context.c2.tls_multi->multi_state = CAS_CONNECT_DONE;\n\n    /* Since dco-win maintains iroute routing table (subnet -> peer),\n     * peer must be added before iroutes. For other platforms it doesn't matter. */\n\n    /* authentication complete, calculate dynamic client specific options */\n    if (!multi_client_set_protocol_options(&mi->context))\n    {\n        mi->context.c2.tls_multi->multi_state = CAS_FAILED;\n    }\n    /* only continue if setting protocol options worked */\n    else if (!multi_client_setup_dco_initial(m, mi, &gc))\n    {\n        mi->context.c2.tls_multi->multi_state = CAS_FAILED;\n    }\n    /* Generate data channel keys only if setting protocol options\n     * and DCO initial setup has not failed */\n    else if (!multi_client_generate_tls_keys(&mi->context))\n    {\n        mi->context.c2.tls_multi->multi_state = CAS_FAILED;\n    }\n\n    /* dco peer has been added, it is now safe for Windows to add iroutes */\n\n    /*\n     * For routed tunnels, set up internal route to endpoint\n     * plus add all iroute routes.\n     */\n    if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)\n    {\n        if (mi->context.c2.push_ifconfig_defined)\n        {\n            multi_learn_in_addr_t(m, mi, mi->context.c2.push_ifconfig_local, -1, true);\n            msg(D_MULTI_LOW, \"MULTI: primary virtual IP for %s: %s\",\n                multi_instance_string(mi, false, &gc),\n                print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc));\n        }\n\n        if (mi->context.c2.push_ifconfig_ipv6_defined)\n        {\n            multi_learn_in6_addr(m, mi, mi->context.c2.push_ifconfig_ipv6_local, -1, true);\n            /* TODO: find out where addresses are \"unlearned\"!! */\n            const char *ifconfig_local_ipv6 =\n                print_in6_addr(mi->context.c2.push_ifconfig_ipv6_local, 0, &gc);\n            msg(D_MULTI_LOW, \"MULTI: primary virtual IPv6 for %s: %s\",\n                multi_instance_string(mi, false, &gc), ifconfig_local_ipv6);\n        }\n\n        /* add routes locally, pointing to new client, if\n         * --iroute options have been specified */\n        multi_add_iroutes(m, mi);\n\n        /*\n         * iroutes represent subnets which are \"owned\" by a particular\n         * client.  Therefore, do not actually push a route to a client\n         * if it matches one of the client's iroutes.\n         */\n        remove_iroutes_from_push_route_list(&mi->context.options);\n    }\n    else if (mi->context.options.iroutes)\n    {\n        msg(D_MULTI_ERRORS,\n            \"MULTI: --iroute options rejected for %s -- iroute \"\n            \"only works with tun-style tunnels\",\n            multi_instance_string(mi, false, &gc));\n    }\n\n    /* send push reply if ready */\n    if (mi->context.c2.push_request_received)\n    {\n        process_incoming_push_request(&mi->context);\n    }\n    gc_free(&gc);\n}\n\nstatic void\nmulti_client_connect_early_setup(struct multi_context *m, struct multi_instance *mi)\n{\n    ASSERT(mi->context.c1.tuntap);\n    /*\n     * lock down the common name and cert hashes so they can't change\n     * during future TLS renegotiations\n     */\n    tls_lock_common_name(mi->context.c2.tls_multi);\n    tls_lock_cert_hash_set(mi->context.c2.tls_multi);\n\n    /* generate a msg() prefix for this client instance */\n    generate_prefix(mi);\n\n    /* delete instances of previous clients with same common-name */\n    if (!mi->context.options.duplicate_cn)\n    {\n        multi_delete_dup(m, mi);\n    }\n\n    /* reset pool handle to null */\n    mi->vaddr_handle = -1;\n\n    /* do --client-connect setenvs */\n    multi_select_virtual_addr(m, mi);\n\n    multi_client_connect_setenv(mi);\n}\n\n/**\n *  Do the necessary modification for doing the compress migrate. This is\n *  implemented as a connect handler as it fits the modify config for a client\n *  paradigm and also is early enough in the chain to be overwritten by another\n *  ccd/script to do compression on a special client.\n */\nstatic enum client_connect_return\nmulti_client_connect_compress_migrate(struct multi_context *m, struct multi_instance *mi,\n                                      bool deferred, unsigned int *option_types_found)\n{\n#ifdef USE_COMP\n    struct options *o = &mi->context.options;\n    const char *const peer_info = mi->context.c2.tls_multi->peer_info;\n\n    if (o->comp.flags & COMP_F_MIGRATE && mi->context.c2.tls_multi->remote_usescomp)\n    {\n        if (peer_info && strstr(peer_info, \"IV_COMP_STUBv2=1\"))\n        {\n            push_option(o, \"compress stub-v2\", M_USAGE);\n        }\n        else\n        {\n            /* Client is old and does not support STUBv2 but since it\n             * announced comp-lzo via OCC we assume it uses comp-lzo, so\n             * switch to that and push the uncompressed variant. */\n            push_option(o, \"comp-lzo no\", M_USAGE);\n            o->comp.alg = COMP_ALG_STUB;\n            *option_types_found |= OPT_P_COMP;\n        }\n    }\n#endif\n    return CC_RET_SUCCEEDED;\n}\n\n/**\n * Try to source a dynamic config file from the\n * --client-config-dir directory.\n */\nstatic enum client_connect_return\nmulti_client_connect_source_ccd(struct multi_context *m, struct multi_instance *mi, bool deferred,\n                                unsigned int *option_types_found)\n{\n    /* Since we never return a CC_RET_DEFERRED, this indicates a serious\n     * problem */\n    ASSERT(!deferred);\n    enum client_connect_return ret = CC_RET_SKIPPED;\n    if (mi->context.options.client_config_dir)\n    {\n        struct gc_arena gc = gc_new();\n        const char *ccd_file = NULL;\n\n        const char *ccd_client =\n            platform_gen_path(mi->context.options.client_config_dir,\n                              tls_common_name(mi->context.c2.tls_multi, false), &gc);\n\n        const char *ccd_default =\n            platform_gen_path(mi->context.options.client_config_dir, CCD_DEFAULT, &gc);\n\n\n        /* try common-name file */\n        if (platform_test_file(ccd_client))\n        {\n            ccd_file = ccd_client;\n        }\n        /* try default file */\n        else if (platform_test_file(ccd_default))\n        {\n            ccd_file = ccd_default;\n        }\n\n        if (ccd_file)\n        {\n            options_server_import(&mi->context.options, ccd_file, D_IMPORT_ERRORS | M_OPTERR,\n                                  CLIENT_CONNECT_OPT_MASK, option_types_found, mi->context.c2.es);\n            /*\n             * Select a virtual address from either --ifconfig-push in\n             * --client-config-dir file or --ifconfig-pool.\n             */\n            multi_select_virtual_addr(m, mi);\n\n            multi_client_connect_setenv(mi);\n\n            ret = CC_RET_SUCCEEDED;\n        }\n        gc_free(&gc);\n    }\n    return ret;\n}\n\ntypedef enum client_connect_return (*multi_client_connect_handler)(\n    struct multi_context *m, struct multi_instance *mi, bool from_deferred,\n    unsigned int *option_types_found);\n\nstatic const multi_client_connect_handler client_connect_handlers[] = {\n    multi_client_connect_compress_migrate,\n    multi_client_connect_source_ccd,\n    multi_client_connect_call_plugin_v1,\n    multi_client_connect_call_plugin_v2,\n    multi_client_connect_call_script,\n    multi_client_connect_mda,\n    NULL,\n};\n\n/**\n * Overrides the locked username with the username of --override-username\n * @param mi the multi instance that should be modified.\n */\nstatic bool\noverride_locked_username(struct multi_instance *mi)\n{\n    struct tls_multi *multi = mi->context.c2.tls_multi;\n    struct options *options = &mi->context.options;\n    struct tls_session *session = &multi->session[TM_ACTIVE];\n\n    if (!multi->locked_username)\n    {\n        msg(D_MULTI_ERRORS, \"MULTI: Ignoring override-username as no \"\n                            \"user/password method is enabled. Enable \"\n                            \"--management-client-auth, --auth-user-pass-verify, or a \"\n                            \"plugin with user/password verify capability.\");\n        return false;\n    }\n\n    if (!multi->locked_original_username\n        && strcmp(multi->locked_username, options->override_username) != 0)\n    {\n        /* Check if the username length is acceptable */\n        if (!ssl_verify_username_length(session, options->override_username))\n        {\n            return false;\n        }\n\n        multi->locked_original_username = multi->locked_username;\n        multi->locked_username = strdup(options->override_username);\n\n        /* Override also the common name if username should be set as common\n         * name */\n        if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME))\n        {\n            set_common_name(session, multi->locked_username);\n            free(multi->locked_cn);\n            multi->locked_cn = NULL;\n            tls_lock_common_name(multi);\n        }\n\n        /* Regenerate the auth-token if enabled */\n        if (multi->auth_token_initial)\n        {\n            struct user_pass up;\n            CLEAR(up);\n            strncpynt(up.username, multi->locked_username, sizeof(up.username));\n\n            generate_auth_token(&up, multi);\n        }\n\n        msg(D_MULTI_LOW,\n            \"MULTI: Note, override-username changes username \"\n            \"from '%s' to '%s'\",\n            multi->locked_original_username, multi->locked_username);\n    }\n    return true;\n}\n/*\n * Called as soon as the SSL/TLS connection is authenticated.\n *\n * Will collect the client specific configuration from the different\n * sources like ccd files, connect plugins and management interface.\n *\n * This method starts with cas_context CAS_PENDING and will move the\n * state machine to either CAS_SUCCEEDED on success or\n * CAS_FAILED/CAS_PARTIAL on failure.\n *\n * Instance-specific directives to be processed (CLIENT_CONNECT_OPT_MASK)\n * include:\n *\n *   iroute start-ip end-ip\n *   ifconfig-push local remote-netmask\n *   push\n *\n *\n */\nstatic void\nmulti_connection_established(struct multi_context *m, struct multi_instance *mi)\n{\n    /* We are only called for the CAS_PENDING_x states, so we\n     * can ignore other states here */\n    bool from_deferred = (mi->context.c2.tls_multi->multi_state != CAS_PENDING);\n\n    int *cur_handler_index = &mi->client_connect_defer_state.cur_handler_index;\n    unsigned int *option_types_found = &mi->client_connect_defer_state.option_types_found;\n\n    /* We are called for the first time */\n    if (!from_deferred)\n    {\n        *cur_handler_index = 0;\n        *option_types_found = 0;\n        /* Initially we have no handler that has returned a result */\n        mi->context.c2.tls_multi->multi_state = CAS_PENDING_DEFERRED;\n\n        multi_client_connect_early_setup(m, mi);\n    }\n\n    bool cc_succeeded = true;\n\n    while (cc_succeeded && client_connect_handlers[*cur_handler_index] != NULL)\n    {\n        enum client_connect_return ret;\n        ret = client_connect_handlers[*cur_handler_index](m, mi, from_deferred, option_types_found);\n\n        from_deferred = false;\n\n        switch (ret)\n        {\n            case CC_RET_SUCCEEDED:\n                /*\n                 * Remember that we already had at least one handler\n                 * returning a result should we go to into deferred state\n                 */\n                mi->context.c2.tls_multi->multi_state = CAS_PENDING_DEFERRED_PARTIAL;\n                break;\n\n            case CC_RET_SKIPPED:\n                /*\n                 * Move on with the next handler without modifying any\n                 * other state\n                 */\n                break;\n\n            case CC_RET_DEFERRED:\n                /*\n                 * we already set multi_status to DEFERRED_RESULT or\n                 * DEFERRED_NO_RESULT. We just return\n                 * from the function as having multi_status\n                 */\n                return;\n\n            case CC_RET_FAILED:\n                /*\n                 * One handler failed. We abort the chain and set the final\n                 * result to failed\n                 */\n                cc_succeeded = false;\n                break;\n\n            default:\n                ASSERT(0);\n        }\n\n        /*\n         * Check for \"disable\" directive in client-config-dir file\n         * or config file generated by --client-connect script.\n         */\n        if (mi->context.options.disable)\n        {\n            msg(D_MULTI_ERRORS, \"MULTI: client has been rejected due to \"\n                                \"'disable' directive\");\n            cc_succeeded = false;\n        }\n\n        (*cur_handler_index)++;\n    }\n\n    if (mi->context.options.override_username)\n    {\n        if (!override_locked_username(mi))\n        {\n            cc_succeeded = false;\n        }\n    }\n\n    /* Check if we have forbidding options in the current mode */\n    if (dco_enabled(&mi->context.options)\n        && !dco_check_option(D_MULTI_ERRORS, &mi->context.options))\n    {\n        msg(D_MULTI_ERRORS, \"MULTI: client has been rejected due to incompatible DCO options\");\n        cc_succeeded = false;\n    }\n\n    if (!check_compression_settings_valid(&mi->context.options.comp, D_MULTI_ERRORS))\n    {\n        msg(D_MULTI_ERRORS, \"MULTI: client has been rejected due to invalid compression options\");\n        cc_succeeded = false;\n    }\n\n    if (cc_succeeded)\n    {\n        multi_client_connect_late_setup(m, mi, *option_types_found);\n    }\n    else\n    {\n        /* run the disconnect script if we had a connect script that\n         * did not fail */\n        if (mi->context.c2.tls_multi->multi_state == CAS_PENDING_DEFERRED_PARTIAL)\n        {\n            multi_client_disconnect_script(m, mi);\n        }\n\n        mi->context.c2.tls_multi->multi_state = CAS_FAILED;\n    }\n\n    /* increment number of current authenticated clients */\n    ++m->n_clients;\n    --mi->n_clients_delta;\n\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        management_connection_established(management, &mi->context.c2.mda_context,\n                                          mi->context.c2.es);\n    }\n#endif\n}\n\n#ifdef ENABLE_ASYNC_PUSH\n/*\n * Called when inotify event is fired, which happens when acf\n * or connect-status file is closed or deleted.\n * Continues authentication and sends push_reply\n * (or be deferred again by client-connect)\n */\nvoid\nmulti_process_file_closed(struct multi_context *m, const unsigned int mpp_flags)\n{\n    char buffer[INOTIFY_EVENT_BUFFER_SIZE];\n    ssize_t buffer_i = 0;\n    ssize_t r = read(m->top.c2.inotify_fd, buffer, INOTIFY_EVENT_BUFFER_SIZE);\n    if (r < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"MULTI: multi_process_file_closed error\");\n        return;\n    }\n\n    while (buffer_i < r)\n    {\n        /* parse inotify events */\n        struct inotify_event *pevent = (struct inotify_event *)&buffer[buffer_i];\n        size_t event_size = sizeof(struct inotify_event) + pevent->len;\n        buffer_i += event_size;\n\n        msg(D_MULTI_DEBUG, \"MULTI: modified fd %d, mask %d\", pevent->wd, pevent->mask);\n\n        struct multi_instance *mi =\n            hash_lookup(m->inotify_watchers, (void *)(uintptr_t)pevent->wd);\n\n        if (pevent->mask & IN_CLOSE_WRITE)\n        {\n            if (mi)\n            {\n                /* continue authentication, perform NCP negotiation and send push_reply */\n                multi_process_post(m, mi, mpp_flags);\n            }\n            else\n            {\n                msg(D_MULTI_ERRORS, \"MULTI: multi_instance not found!\");\n            }\n        }\n        else if (pevent->mask & IN_IGNORED)\n        {\n            /* this event is _always_ fired when watch is removed or file is deleted */\n            if (mi)\n            {\n                hash_remove(m->inotify_watchers, (void *)(uintptr_t)pevent->wd);\n                mi->inotify_watch = -1;\n            }\n        }\n        else\n        {\n            msg(D_MULTI_ERRORS, \"MULTI: unknown mask %d\", pevent->mask);\n        }\n    }\n}\n#endif /* ifdef ENABLE_ASYNC_PUSH */\n\n/*\n * Add a mbuf buffer to a particular\n * instance.\n */\nvoid\nmulti_add_mbuf(struct multi_context *m, struct multi_instance *mi, struct mbuf_buffer *mb)\n{\n    if (multi_output_queue_ready(m, mi))\n    {\n        struct mbuf_item item;\n        item.buffer = mb;\n        item.instance = mi;\n        mbuf_add_item(m->mbuf, &item);\n    }\n    else\n    {\n        msg(D_MULTI_DROPPED, \"MULTI: packet dropped due to output saturation (multi_add_mbuf)\");\n    }\n}\n\n/*\n * Add a packet to a client instance output queue.\n */\nstatic inline void\nmulti_unicast(struct multi_context *m, const struct buffer *buf, struct multi_instance *mi)\n{\n    struct mbuf_buffer *mb;\n\n    if (BLEN(buf) > 0)\n    {\n        mb = mbuf_alloc_buf(buf);\n        mb->flags = MF_UNICAST;\n        multi_add_mbuf(m, mi, mb);\n        mbuf_free_buf(mb);\n    }\n}\n\n/*\n * Broadcast a packet to all clients.\n */\nstatic void\nmulti_bcast(struct multi_context *m, const struct buffer *buf,\n            const struct multi_instance *sender_instance, uint16_t vid)\n{\n    struct hash_iterator hi;\n    struct hash_element *he;\n    struct multi_instance *mi;\n    struct mbuf_buffer *mb;\n\n    if (BLEN(buf) > 0)\n    {\n#ifdef MULTI_DEBUG_EVENT_LOOP\n        printf(\"BCAST len=%d\\n\", BLEN(buf));\n#endif\n        mb = mbuf_alloc_buf(buf);\n        hash_iterator_init(m->iter, &hi);\n\n        while ((he = hash_iterator_next(&hi)))\n        {\n            mi = (struct multi_instance *)he->value;\n            if (mi != sender_instance && !mi->halt)\n            {\n                if (vid != 0 && vid != mi->context.options.vlan_pvid)\n                {\n                    continue;\n                }\n                multi_add_mbuf(m, mi, mb);\n            }\n        }\n\n        hash_iterator_free(&hi);\n        mbuf_free_buf(mb);\n    }\n}\n\n/*\n * Given a time delta, indicating that we wish to be\n * awoken by the scheduler at time now + delta, figure\n * a sigma parameter (in microseconds) that represents\n * a sort of fuzz factor around delta, so that we're\n * really telling the scheduler to wake us up any time\n * between now + delta - sigma and now + delta + sigma.\n *\n * The sigma parameter helps the scheduler to run more efficiently.\n * Sigma should be no larger than TV_WITHIN_SIGMA_MAX_USEC\n */\nstatic inline unsigned int\ncompute_wakeup_sigma(const struct timeval *delta)\n{\n    ASSERT(delta->tv_sec >= 0);\n    ASSERT(delta->tv_usec >= 0);\n    if (delta->tv_sec < 1)\n    {\n        /* if < 1 sec, fuzz = # of microseconds / 8 */\n        return (unsigned int)(delta->tv_usec >> 3);\n    }\n    else\n    {\n        /* if < 10 minutes, fuzz = 13.1% of timeout */\n        if (delta->tv_sec < 600)\n        {\n            return (unsigned int)(delta->tv_sec << 17);\n        }\n        else\n        {\n            return 120 * 1000000; /* if >= 10 minutes, fuzz = 2 minutes */\n        }\n    }\n}\n\nstatic void\nmulti_schedule_context_wakeup(struct multi_context *m, struct multi_instance *mi)\n{\n    /* calculate an absolute wakeup time */\n    ASSERT(!openvpn_gettimeofday(&mi->wakeup, NULL));\n    tv_add(&mi->wakeup, &mi->context.c2.timeval);\n\n    /* tell scheduler to wake us up at some point in the future */\n    schedule_add_entry(m->schedule, (struct schedule_entry *)mi, &mi->wakeup,\n                       compute_wakeup_sigma(&mi->context.c2.timeval));\n}\n\n#if defined(ENABLE_ASYNC_PUSH)\nstatic void\nadd_inotify_file_watch(struct multi_context *m, struct multi_instance *mi, int inotify_fd,\n                       const char *file)\n{\n    /* watch acf file */\n    int watch_descriptor = inotify_add_watch(inotify_fd, file, IN_CLOSE_WRITE | IN_ONESHOT);\n    if (watch_descriptor >= 0)\n    {\n        if (mi->inotify_watch != -1)\n        {\n            hash_remove(m->inotify_watchers, (void *)(uintptr_t)mi->inotify_watch);\n        }\n        hash_add(m->inotify_watchers, (void *)(uintptr_t)watch_descriptor, mi, true);\n        mi->inotify_watch = watch_descriptor;\n    }\n    else\n    {\n        msg(M_NONFATAL | M_ERRNO, \"MULTI: inotify_add_watch error\");\n    }\n}\n#endif /* if defined(ENABLE_ASYNC_PUSH) */\n\n/*\n * Figure instance-specific timers, convert\n * earliest to absolute time in mi->wakeup,\n * call scheduler with our future wakeup time.\n *\n * Also close context on signal.\n */\nbool\nmulti_process_post(struct multi_context *m, struct multi_instance *mi, const unsigned int flags)\n{\n    bool ret = true;\n\n    if (!IS_SIG(&mi->context)\n        && ((flags & MPP_PRE_SELECT)))\n    {\n#if defined(ENABLE_ASYNC_PUSH)\n        bool was_unauthenticated = true;\n        struct key_state *ks = NULL;\n        if (mi->context.c2.tls_multi)\n        {\n            ks = &mi->context.c2.tls_multi->session[TM_ACTIVE].key[KS_PRIMARY];\n            was_unauthenticated = (ks->authenticated == KS_AUTH_FALSE);\n        }\n#endif\n\n        /* figure timeouts and fetch possible outgoing\n         * to_link packets (such as ping or TLS control) */\n        pre_select(&mi->context);\n\n#if defined(ENABLE_ASYNC_PUSH)\n        /*\n         * if we see the state transition from unauthenticated to deferred\n         * and an auth_control_file, we assume it got just added and add\n         * inotify watch to that file\n         */\n        if (ks && ks->plugin_auth.auth_control_file && was_unauthenticated\n            && (ks->authenticated == KS_AUTH_DEFERRED))\n        {\n            add_inotify_file_watch(m, mi, m->top.c2.inotify_fd, ks->plugin_auth.auth_control_file);\n        }\n        if (ks && ks->script_auth.auth_control_file && was_unauthenticated\n            && (ks->authenticated == KS_AUTH_DEFERRED))\n        {\n            add_inotify_file_watch(m, mi, m->top.c2.inotify_fd, ks->script_auth.auth_control_file);\n        }\n#endif\n\n        if (!IS_SIG(&mi->context))\n        {\n            /* connection is \"established\" when SSL/TLS key negotiation succeeds\n             * and (if specified) auth user/pass succeeds */\n\n            if (is_cas_pending(mi->context.c2.tls_multi->multi_state))\n            {\n                multi_connection_established(m, mi);\n            }\n#if defined(ENABLE_ASYNC_PUSH)\n            if (is_cas_pending(mi->context.c2.tls_multi->multi_state)\n                && mi->client_connect_defer_state.deferred_ret_file)\n            {\n                add_inotify_file_watch(m, mi, m->top.c2.inotify_fd,\n                                       mi->client_connect_defer_state.deferred_ret_file);\n            }\n#endif\n            /* tell scheduler to wake us up at some point in the future */\n            multi_schedule_context_wakeup(m, mi);\n        }\n    }\n\n    if (IS_SIG(&mi->context))\n    {\n        if (flags & MPP_CLOSE_ON_SIGNAL)\n        {\n            multi_close_instance_on_signal(m, mi);\n            ret = false;\n        }\n    }\n    else\n    {\n        /* continue to pend on output? */\n        multi_set_pending(m, ANY_OUT(&mi->context) ? mi : NULL);\n\n#ifdef MULTI_DEBUG_EVENT_LOOP\n        printf(\"POST %s[%d] to=%d lo=%d/%d w=%\" PRIi64 \"/%ld\\n\", id(mi), (int)(mi == m->pending),\n               mi ? mi->context.c2.to_tun.len : -1, mi ? mi->context.c2.to_link.len : -1,\n               (mi && mi->context.c2.fragment) ? mi->context.c2.fragment->outgoing.len : -1,\n               (int64_t)mi->context.c2.timeval.tv_sec, (long)mi->context.c2.timeval.tv_usec);\n#endif\n    }\n\n    if ((flags & MPP_RECORD_TOUCH) && m->mpp_touched)\n    {\n        *m->mpp_touched = mi;\n    }\n\n    return ret;\n}\n\n/**\n * Handles peer floating.\n *\n * If peer is floated to a taken address, either drops packet\n * (if peer that owns address has different CN) or disconnects\n * existing peer. Updates multi_instance with new address,\n * updates hashtables in multi_context.\n */\nstatic void\nmulti_process_float(struct multi_context *m, struct multi_instance *mi, struct link_socket *sock)\n{\n    struct mroute_addr real = { 0 };\n    struct hash *hash = m->hash;\n    struct gc_arena gc = gc_new();\n\n    if (mi->real.type & MR_WITH_PROTO)\n    {\n        real.type |= MR_WITH_PROTO;\n        real.proto = sock->info.proto;\n    }\n\n    if (!mroute_extract_openvpn_sockaddr(&real, &m->top.c2.from.dest, true))\n    {\n        goto done;\n    }\n\n    const uint32_t hv = hash_value(hash, &real);\n    struct hash_bucket *bucket = hash_bucket(hash, hv);\n\n    /* make sure that we don't float to an address taken by another client */\n    struct hash_element *he = hash_lookup_fast(hash, bucket, &real, hv);\n    if (he)\n    {\n        struct multi_instance *ex_mi = (struct multi_instance *)he->value;\n\n        struct tls_multi *m1 = mi->context.c2.tls_multi;\n        struct tls_multi *m2 = ex_mi->context.c2.tls_multi;\n\n        /* do not float if target address is taken by client with another cert */\n        if (!cert_hash_compare(m1->locked_cert_hash_set, m2->locked_cert_hash_set))\n        {\n            msg(D_MULTI_LOW, \"Disallow float to an address taken by another client %s\",\n                multi_instance_string(ex_mi, false, &gc));\n\n            mi->context.c2.buf.len = 0;\n\n            goto done;\n        }\n\n        /* It doesn't make sense to let a peer float to the address it already\n         * has, so we disallow it. This can happen if a DCO netlink notification\n         * gets lost and we miss a floating step.\n         */\n        if (m1->peer_id == m2->peer_id)\n        {\n            msg(M_WARN,\n                \"disallowing peer %\" PRIu32 \" (%s) from floating to \"\n                \"its own address (%s)\",\n                m1->peer_id, tls_common_name(mi->context.c2.tls_multi, false),\n                mroute_addr_print(&mi->real, &gc));\n            goto done;\n        }\n\n        msg(D_MULTI_LOW,\n            \"closing instance %s due to float collision with %s \"\n            \"using the same certificate\",\n            multi_instance_string(ex_mi, false, &gc), multi_instance_string(mi, false, &gc));\n        multi_close_instance(m, ex_mi, false);\n    }\n\n    msg(D_MULTI_MEDIUM, \"peer %\" PRIu32 \" (%s) floated from %s to %s\",\n        mi->context.c2.tls_multi->peer_id, tls_common_name(mi->context.c2.tls_multi, false),\n        mroute_addr_print_ex(&mi->real, MAPF_SHOW_FAMILY, &gc),\n        mroute_addr_print_ex(&real, MAPF_SHOW_FAMILY, &gc));\n\n    /* remove old address from hash table before changing address */\n    ASSERT(hash_remove(m->hash, &mi->real));\n    ASSERT(hash_remove(m->iter, &mi->real));\n\n    /* change external network address of the remote peer */\n    mi->real = real;\n    generate_prefix(mi);\n\n    mi->context.c2.from = m->top.c2.from;\n    mi->context.c2.to_link_addr = &mi->context.c2.from;\n\n    /* inherit parent link_socket and link_socket_info */\n    mi->context.c2.link_sockets[0] = sock;\n    mi->context.c2.link_socket_infos[0]->lsa->actual = m->top.c2.from;\n\n    tls_update_remote_addr(mi->context.c2.tls_multi, &mi->context.c2.from);\n\n    ASSERT(hash_add(m->hash, &mi->real, mi, false));\n    ASSERT(hash_add(m->iter, &mi->real, mi, false));\n\n#ifdef ENABLE_MANAGEMENT\n    ASSERT(hash_add(m->cid_hash, &mi->context.c2.mda_context.cid, mi, true));\n#endif\n\ndone:\n    gc_free(&gc);\n}\n\n/*\n * Called when an instance should be closed due to the\n * reception of a soft signal.\n */\nvoid\nmulti_close_instance_on_signal(struct multi_context *m, struct multi_instance *mi)\n{\n    remap_signal(&mi->context);\n    set_prefix(mi);\n    print_signal(mi->context.sig, \"client-instance\", D_MULTI_LOW);\n    clear_prefix();\n    multi_close_instance(m, mi, false);\n}\n\n#if (defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD))) \\\n    || defined(ENABLE_MANAGEMENT)\nstatic void\nmulti_signal_instance(struct multi_context *m, struct multi_instance *mi, const int sig)\n{\n    mi->context.sig->signal_received = sig;\n    multi_close_instance_on_signal(m, mi);\n}\n#endif\n\n#if defined(ENABLE_DCO)\nstatic void\nprocess_incoming_del_peer(struct multi_context *m, struct multi_instance *mi, dco_context_t *dco)\n{\n    const char *reason = \"ovpn-dco: unknown reason\";\n    switch (dco->dco_del_peer_reason)\n    {\n        case OVPN_DEL_PEER_REASON_EXPIRED:\n            reason = \"ovpn-dco: ping expired\";\n            break;\n\n        case OVPN_DEL_PEER_REASON_TRANSPORT_ERROR:\n            reason = \"ovpn-dco: transport error\";\n            break;\n\n        case OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT:\n            reason = \"ovpn-dco: transport disconnected\";\n            break;\n\n        case OVPN_DEL_PEER_REASON_USERSPACE:\n            /* We assume that is ourselves. Unfortunately, sometimes these\n             * events happen with enough delay that they can have an order of\n             *\n             * dco_del_peer x\n             * [new client connecting]\n             * dco_new_peer x\n             * event from dco_del_peer arrives.\n             *\n             * if we do not ignore this we get desynced with the kernel\n             * since we assume the peer-id is free again. The other way would\n             * be to send a dco_del_peer again\n             */\n            return;\n    }\n\n    /* When kernel already deleted the peer, the socket is no longer\n     * installed, and we do not need to clean up the state in the kernel */\n    mi->context.c2.tls_multi->dco_peer_id = -1;\n    mi->context.sig->signal_text = reason;\n    multi_signal_instance(m, mi, SIGTERM);\n}\n\nvoid\nmulti_process_incoming_dco(dco_context_t *dco)\n{\n    ASSERT(dco->c->multi);\n\n    struct multi_context *m = dco->c->multi;\n\n    int peer_id = dco->dco_message_peer_id;\n\n    /* no peer-specific message delivered -> nothing to process.\n     * bail out right away\n     */\n    if (peer_id < 0)\n    {\n        return;\n    }\n\n    if ((peer_id < m->max_clients) && (m->instances[peer_id]))\n    {\n        struct multi_instance *mi = m->instances[peer_id];\n        set_prefix(mi);\n        if (dco->dco_message_type == OVPN_CMD_DEL_PEER)\n        {\n            process_incoming_del_peer(m, mi, dco);\n        }\n        else if (dco->dco_message_type == OVPN_CMD_FLOAT_PEER)\n        {\n            ASSERT(mi->context.c2.link_sockets[0]);\n            extract_dco_float_peer_addr(mi->context.c2.link_sockets[0]->info.af,\n                                        &m->top.c2.from.dest,\n                                        (struct sockaddr *)&dco->dco_float_peer_ss);\n            multi_process_float(m, mi, mi->context.c2.link_sockets[0]);\n            CLEAR(dco->dco_float_peer_ss);\n        }\n        else if (dco->dco_message_type == OVPN_CMD_SWAP_KEYS)\n        {\n            tls_session_soft_reset(mi->context.c2.tls_multi);\n        }\n        clear_prefix();\n    }\n    else\n    {\n        msglvl_t msglevel = D_DCO;\n        if (dco->dco_message_type == OVPN_CMD_DEL_PEER\n            && dco->dco_del_peer_reason == OVPN_DEL_PEER_REASON_USERSPACE)\n        {\n            /* we receive OVPN_CMD_DEL_PEER message with reason USERSPACE\n             * after we kill the peer ourselves. This peer may have already\n             * been deleted, so we end up here.\n             * In this case, print the following debug message with DCO_DEBUG\n             * level only to avoid polluting the standard DCO level with this\n             * harmless event.\n             */\n            msglevel = D_DCO_DEBUG;\n        }\n        msg(msglevel,\n            \"Received DCO message for unknown peer-id: %d, \"\n            \"type %d, del_peer_reason %d\",\n            peer_id, dco->dco_message_type, dco->dco_del_peer_reason);\n    }\n}\n#endif /* if defined(ENABLE_DCO) */\n\n/*\n * Process packets in the TCP/UDP socket -> TUN/TAP interface direction,\n * i.e. client -> server direction.\n */\nbool\nmulti_process_incoming_link(struct multi_context *m, struct multi_instance *instance,\n                            const unsigned int mpp_flags, struct link_socket *sock)\n{\n    struct gc_arena gc = gc_new();\n\n    struct context *c;\n    struct mroute_addr src, dest;\n    unsigned int mroute_flags;\n    struct multi_instance *mi;\n    bool ret = true;\n    bool floated = false;\n\n    if (m->pending)\n    {\n        return true;\n    }\n\n    if (!instance)\n    {\n#ifdef MULTI_DEBUG_EVENT_LOOP\n        printf(\"TCP/UDP -> TUN [%d]\\n\", BLEN(&m->top.c2.buf));\n#endif\n        multi_set_pending(m, multi_get_create_instance_udp(m, &floated, sock));\n    }\n    else\n    {\n        multi_set_pending(m, instance);\n    }\n\n    if (m->pending)\n    {\n        set_prefix(m->pending);\n\n        /* get instance context */\n        c = &m->pending->context;\n\n        if (!instance)\n        {\n            /* transfer packet pointer from top-level context buffer to instance */\n            c->c2.buf = m->top.c2.buf;\n\n            /* transfer from-addr from top-level context buffer to instance */\n            if (!floated)\n            {\n                c->c2.from = m->top.c2.from;\n            }\n        }\n\n        if (BLEN(&c->c2.buf) > 0)\n        {\n            struct link_socket_info *lsi;\n            const uint8_t *orig_buf;\n\n            /* decrypt in instance context */\n\n            lsi = &sock->info;\n            orig_buf = c->c2.buf.data;\n            if (process_incoming_link_part1(c, lsi, floated))\n            {\n                /* nonzero length means that we have a valid, decrypted packed */\n                if (floated && c->c2.buf.len > 0)\n                {\n                    multi_process_float(m, m->pending, sock);\n                }\n\n                process_incoming_link_part2(c, lsi, orig_buf);\n            }\n\n            if (TUNNEL_TYPE(m->top.c1.tuntap) == DEV_TYPE_TUN)\n            {\n                /* extract packet source and dest addresses */\n                mroute_flags =\n                    mroute_extract_addr_from_packet(&src, &dest, 0, &c->c2.to_tun, DEV_TYPE_TUN);\n\n                /* drop packet if extract failed */\n                if (!(mroute_flags & MROUTE_EXTRACT_SUCCEEDED))\n                {\n                    c->c2.to_tun.len = 0;\n                }\n                /* make sure that source address is associated with this client */\n                else if (multi_get_instance_by_virtual_addr(m, &src, true) != m->pending)\n                {\n                    /* IPv6 link-local address (fe80::xxx)? */\n                    if ((src.type & MR_ADDR_MASK) == MR_ADDR_IPV6\n                        && IN6_IS_ADDR_LINKLOCAL(&src.v6.addr))\n                    {\n                        /* do nothing, for now.  TODO: add address learning */\n                    }\n                    else\n                    {\n                        msg(D_MULTI_DROPPED,\n                            \"MULTI: bad source address from client [%s], packet dropped\",\n                            mroute_addr_print(&src, &gc));\n                    }\n                    c->c2.to_tun.len = 0;\n                }\n                /* client-to-client communication enabled? */\n                else if (m->enable_c2c)\n                {\n                    /* multicast? */\n                    if (mroute_flags & MROUTE_EXTRACT_MCAST)\n                    {\n                        /* for now, treat multicast as broadcast */\n                        multi_bcast(m, &c->c2.to_tun, m->pending, 0);\n                    }\n                    else /* possible client to client routing */\n                    {\n                        ASSERT(!(mroute_flags & MROUTE_EXTRACT_BCAST));\n                        mi = multi_get_instance_by_virtual_addr(m, &dest, true);\n\n                        /* if dest addr is a known client, route to it */\n                        if (mi)\n                        {\n                            {\n                                multi_unicast(m, &c->c2.to_tun, mi);\n                                register_activity(c, BLEN(&c->c2.to_tun));\n                            }\n                            c->c2.to_tun.len = 0;\n                        }\n                    }\n                }\n            }\n            else if (TUNNEL_TYPE(m->top.c1.tuntap) == DEV_TYPE_TAP)\n            {\n                uint16_t vid = 0;\n\n                if (m->top.options.vlan_tagging)\n                {\n                    if (vlan_is_tagged(&c->c2.to_tun))\n                    {\n                        /* Drop VLAN-tagged frame. */\n                        msg(D_VLAN_DEBUG, \"dropping incoming VLAN-tagged frame\");\n                        c->c2.to_tun.len = 0;\n                    }\n                    else\n                    {\n                        vid = c->options.vlan_pvid;\n                    }\n                }\n                /* extract packet source and dest addresses */\n                mroute_flags =\n                    mroute_extract_addr_from_packet(&src, &dest, vid, &c->c2.to_tun, DEV_TYPE_TAP);\n\n                if (mroute_flags & MROUTE_EXTRACT_SUCCEEDED)\n                {\n                    if (multi_learn_addr(m, m->pending, &src, 0) == m->pending)\n                    {\n                        /* check for broadcast */\n                        if (m->enable_c2c)\n                        {\n                            if (mroute_flags & (MROUTE_EXTRACT_BCAST | MROUTE_EXTRACT_MCAST))\n                            {\n                                multi_bcast(m, &c->c2.to_tun, m->pending, vid);\n                            }\n                            else /* try client-to-client routing */\n                            {\n                                mi = multi_get_instance_by_virtual_addr(m, &dest, false);\n\n                                /* if dest addr is a known client, route to it */\n                                if (mi)\n                                {\n                                    multi_unicast(m, &c->c2.to_tun, mi);\n                                    register_activity(c, BLEN(&c->c2.to_tun));\n                                    c->c2.to_tun.len = 0;\n                                }\n                            }\n                        }\n                    }\n                    else\n                    {\n                        msg(D_MULTI_DROPPED,\n                            \"MULTI: bad source address from client [%s], packet dropped\",\n                            mroute_addr_print(&src, &gc));\n                        c->c2.to_tun.len = 0;\n                    }\n                }\n                else\n                {\n                    c->c2.to_tun.len = 0;\n                }\n            }\n        }\n\n        /* postprocess and set wakeup */\n        ret = multi_process_post(m, m->pending, mpp_flags);\n\n        clear_prefix();\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n\n/*\n * Process packets in the TUN/TAP interface -> TCP/UDP socket direction,\n * i.e. server -> client direction.\n */\nbool\nmulti_process_incoming_tun(struct multi_context *m, const unsigned int mpp_flags)\n{\n    bool ret = true;\n\n    if (BLEN(&m->top.c2.buf) > 0)\n    {\n        unsigned int mroute_flags;\n        struct mroute_addr src = { 0 }, dest = { 0 };\n        const int dev_type = TUNNEL_TYPE(m->top.c1.tuntap);\n        int16_t vid = 0;\n\n#ifdef MULTI_DEBUG_EVENT_LOOP\n        printf(\"TUN -> TCP/UDP [%d]\\n\", BLEN(&m->top.c2.buf));\n#endif\n\n        if (m->pending)\n        {\n            return true;\n        }\n\n        if (dev_type == DEV_TYPE_TAP && m->top.options.vlan_tagging)\n        {\n            vid = vlan_decapsulate(&m->top, &m->top.c2.buf);\n            if (vid < 0)\n            {\n                return false;\n            }\n        }\n\n        /*\n         * Route an incoming tun/tap packet to\n         * the appropriate multi_instance object.\n         */\n\n        mroute_flags = mroute_extract_addr_from_packet(&src, &dest, vid, &m->top.c2.buf, dev_type);\n\n        if (mroute_flags & MROUTE_EXTRACT_SUCCEEDED)\n        {\n            struct context *c;\n\n            /* broadcast or multicast dest addr? */\n            if (mroute_flags & (MROUTE_EXTRACT_BCAST | MROUTE_EXTRACT_MCAST))\n            {\n                /* for now, treat multicast as broadcast */\n                multi_bcast(m, &m->top.c2.buf, NULL, vid);\n            }\n            else\n            {\n                multi_set_pending(\n                    m, multi_get_instance_by_virtual_addr(m, &dest, dev_type == DEV_TYPE_TUN));\n\n                if (m->pending)\n                {\n                    /* get instance context */\n                    c = &m->pending->context;\n\n                    set_prefix(m->pending);\n\n                    {\n                        if (multi_output_queue_ready(m, m->pending))\n                        {\n                            /* transfer packet pointer from top-level context buffer to instance */\n                            c->c2.buf = m->top.c2.buf;\n                        }\n                        else\n                        {\n                            /* drop packet */\n                            msg(D_MULTI_DROPPED,\n                                \"MULTI: packet dropped due to output saturation (multi_process_incoming_tun)\");\n                            buf_reset_len(&c->c2.buf);\n                        }\n                    }\n\n                    /* encrypt in instance context */\n                    process_incoming_tun(c, c->c2.link_sockets[0]);\n\n                    /* postprocess and set wakeup */\n                    ret = multi_process_post(m, m->pending, mpp_flags);\n\n                    clear_prefix();\n                }\n            }\n        }\n    }\n    return ret;\n}\n\n/*\n * Process a possible client-to-client/bcast/mcast message in the\n * queue.\n */\nstruct multi_instance *\nmulti_get_queue(struct mbuf_set *ms)\n{\n    struct mbuf_item item;\n\n    if (mbuf_extract_item(ms, &item)) /* cleartext IP packet */\n    {\n        unsigned int pip_flags = PIPV4_PASSTOS | PIPV6_ICMP_NOHOST_SERVER;\n\n        set_prefix(item.instance);\n        item.instance->context.c2.buf = item.buffer->buf;\n        if (item.buffer->flags\n            & MF_UNICAST) /* --mssfix doesn't make sense for broadcast or multicast */\n        {\n            pip_flags |= PIP_MSSFIX;\n        }\n        process_ip_header(&item.instance->context, pip_flags, &item.instance->context.c2.buf,\n                          item.instance->context.c2.link_sockets[0]);\n        encrypt_sign(&item.instance->context, true);\n        mbuf_free_buf(item.buffer);\n\n        dmsg(D_MULTI_DEBUG, \"MULTI: C2C/MCAST/BCAST\");\n\n        clear_prefix();\n        return item.instance;\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\n/*\n * Called when an I/O wait times out.  Usually means that a particular\n * client instance object needs timer-based service.\n */\nbool\nmulti_process_timeout(struct multi_context *m, const unsigned int mpp_flags)\n{\n    bool ret = true;\n\n#ifdef MULTI_DEBUG_EVENT_LOOP\n    printf(\"%s -> TIMEOUT\\n\", id(m->earliest_wakeup));\n#endif\n\n    /* instance marked for wakeup? */\n    if (m->earliest_wakeup)\n    {\n        if (m->earliest_wakeup == (struct multi_instance *)&m->deferred_shutdown_signal)\n        {\n            schedule_remove_entry(m->schedule,\n                                  (struct schedule_entry *)&m->deferred_shutdown_signal);\n            throw_signal(m->deferred_shutdown_signal.signal_received);\n        }\n        else\n        {\n            set_prefix(m->earliest_wakeup);\n            ret = multi_process_post(m, m->earliest_wakeup, mpp_flags);\n            clear_prefix();\n        }\n        m->earliest_wakeup = NULL;\n    }\n    return ret;\n}\n\n/*\n * Drop a TUN/TAP outgoing packet..\n */\nvoid\nmulti_process_drop_outgoing_tun(struct multi_context *m, const unsigned int mpp_flags)\n{\n    struct multi_instance *mi = m->pending;\n\n    ASSERT(mi);\n\n    set_prefix(mi);\n\n    msg(D_MULTI_ERRORS, \"MULTI: Outgoing TUN queue full, dropped packet len=%d\",\n        mi->context.c2.to_tun.len);\n\n    buf_reset(&mi->context.c2.to_tun);\n\n    multi_process_post(m, mi, mpp_flags);\n    clear_prefix();\n}\n\n/*\n * Per-client route quota management\n */\n\nvoid\nroute_quota_exceeded(const struct multi_instance *mi)\n{\n    struct gc_arena gc = gc_new();\n    msg(D_ROUTE_QUOTA,\n        \"MULTI ROUTE: route quota (%d) exceeded for %s (see --max-routes-per-client option)\",\n        mi->context.options.max_routes_per_client, multi_instance_string(mi, false, &gc));\n    gc_free(&gc);\n}\n\n#ifdef ENABLE_DEBUG\n/*\n * Flood clients with random packets\n */\nstatic void\ngremlin_flood_clients(struct multi_context *m)\n{\n    const int level = GREMLIN_PACKET_FLOOD_LEVEL(m->top.options.gremlin);\n    if (level)\n    {\n        struct gc_arena gc = gc_new();\n        struct buffer buf = alloc_buf_gc(BUF_SIZE(&m->top.c2.frame), &gc);\n        struct packet_flood_parms parm = get_packet_flood_parms(level);\n        int i;\n\n        ASSERT(buf_init(&buf, m->top.c2.frame.buf.headroom));\n        parm.packet_size = min_int(parm.packet_size, m->top.c2.frame.buf.payload_size);\n\n        msg(D_GREMLIN, \"GREMLIN_FLOOD_CLIENTS: flooding clients with %d packets of size %d\",\n            parm.n_packets, parm.packet_size);\n\n        for (i = 0; i < parm.packet_size; ++i)\n        {\n            ASSERT(buf_write_u8(&buf, (uint8_t)(get_random() & 0xFF)));\n        }\n\n        for (i = 0; i < parm.n_packets; ++i)\n        {\n            multi_bcast(m, &buf, NULL, 0);\n        }\n\n        gc_free(&gc);\n    }\n}\n#endif /* ifdef ENABLE_DEBUG */\n\nstatic bool\nstale_route_check_trigger(struct multi_context *m)\n{\n    struct timeval null;\n    CLEAR(null);\n    return event_timeout_trigger(&m->stale_routes_check_et, &null, ETT_DEFAULT);\n}\n\n/*\n * Process timers in the top-level context\n */\nvoid\nmulti_process_per_second_timers_dowork(struct multi_context *m)\n{\n    /* possibly reap instances/routes in vhash */\n    multi_reap_process(m);\n\n    /* possibly print to status log */\n    if (m->top.c1.status_output)\n    {\n        if (status_trigger(m->top.c1.status_output))\n        {\n            multi_print_status(m, m->top.c1.status_output, m->status_file_version);\n        }\n    }\n\n    /* possibly flush ifconfig-pool file */\n    multi_ifconfig_pool_persist(m, false);\n\n#ifdef ENABLE_DEBUG\n    gremlin_flood_clients(m);\n#endif\n\n    /* Should we check for stale routes? */\n    if (m->top.options.stale_routes_check_interval && stale_route_check_trigger(m))\n    {\n        check_stale_routes(m);\n    }\n}\n\nstatic void\nmulti_top_init(struct context *top)\n{\n    inherit_context_top(&top->multi->top, top);\n    top->multi->top.c2.buffers = init_context_buffers(&top->c2.frame);\n}\n\nstatic void\nmulti_top_free(struct multi_context *m)\n{\n    close_context(&m->top, -1, CC_GC_FREE);\n    free_context_buffers(m->top.c2.buffers);\n}\n\nstatic bool\nis_exit_restart(int sig)\n{\n    return (sig == SIGUSR1 || sig == SIGTERM || sig == SIGHUP || sig == SIGINT);\n}\n\nstatic void\nmulti_push_restart_schedule_exit(struct multi_context *m, bool next_server)\n{\n    struct hash_iterator hi;\n    struct hash_element *he;\n\n    /* tell all clients to restart */\n    hash_iterator_init(m->iter, &hi);\n    while ((he = hash_iterator_next(&hi)))\n    {\n        struct multi_instance *mi = (struct multi_instance *)he->value;\n        if (!mi->halt && proto_is_dgram(mi->context.c2.link_sockets[0]->info.proto))\n        {\n            send_control_channel_string(&mi->context, next_server ? \"RESTART,[N]\" : \"RESTART\",\n                                        D_PUSH);\n            multi_schedule_context_wakeup(m, mi);\n        }\n    }\n    hash_iterator_free(&hi);\n\n    /* reschedule signal */\n    ASSERT(!openvpn_gettimeofday(&m->deferred_shutdown_signal.wakeup, NULL));\n    struct timeval tv = { .tv_sec = 2, .tv_usec = 0 };\n    tv_add(&m->deferred_shutdown_signal.wakeup, &tv);\n\n    m->deferred_shutdown_signal.signal_received = m->top.sig->signal_received;\n\n    schedule_add_entry(m->schedule, (struct schedule_entry *)&m->deferred_shutdown_signal,\n                       &m->deferred_shutdown_signal.wakeup,\n                       compute_wakeup_sigma(&tv));\n\n    signal_reset(m->top.sig, 0);\n}\n\n/*\n * Return true if event loop should break,\n * false if it should continue.\n */\nbool\nmulti_process_signal(struct multi_context *m)\n{\n    if (signal_reset(m->top.sig, SIGUSR2) == SIGUSR2)\n    {\n        struct status_output *so = status_open(NULL, 0, M_INFO, NULL, 0);\n        multi_print_status(m, so, m->status_file_version);\n        status_close(so);\n        return false;\n    }\n    else if (has_udp_in_local_list(&m->top.options) && is_exit_restart(m->top.sig->signal_received)\n             && (m->deferred_shutdown_signal.signal_received == 0)\n             && m->top.options.ce.explicit_exit_notification != 0)\n    {\n        multi_push_restart_schedule_exit(m, m->top.options.ce.explicit_exit_notification == 2);\n        return false;\n    }\n    return true;\n}\n\n/*\n * Management subsystem callbacks\n */\n#ifdef ENABLE_MANAGEMENT\n\nstatic void\nmanagement_callback_status(void *arg, const int version, struct status_output *so)\n{\n    struct multi_context *m = (struct multi_context *)arg;\n\n    if (!version)\n    {\n        multi_print_status(m, so, m->status_file_version);\n    }\n    else\n    {\n        multi_print_status(m, so, version);\n    }\n}\n\nstatic int\nmanagement_callback_n_clients(void *arg)\n{\n    struct multi_context *m = (struct multi_context *)arg;\n    return m->n_clients;\n}\n\nstatic int\nmanagement_callback_kill_by_cn(void *arg, const char *del_cn)\n{\n    struct multi_context *m = (struct multi_context *)arg;\n    struct hash_iterator hi;\n    struct hash_element *he;\n    int count = 0;\n\n    hash_iterator_init(m->iter, &hi);\n    while ((he = hash_iterator_next(&hi)))\n    {\n        struct multi_instance *mi = (struct multi_instance *)he->value;\n        if (!mi->halt)\n        {\n            const char *cn = tls_common_name(mi->context.c2.tls_multi, false);\n            if (cn && !strcmp(cn, del_cn))\n            {\n                multi_signal_instance(m, mi, SIGTERM);\n                ++count;\n            }\n        }\n    }\n    hash_iterator_free(&hi);\n    return count;\n}\n\nstatic int\nmanagement_callback_kill_by_addr(void *arg, const in_addr_t addr, const uint16_t port, const uint8_t proto)\n{\n    struct multi_context *m = (struct multi_context *)arg;\n    struct hash_iterator hi;\n    struct hash_element *he;\n    struct openvpn_sockaddr saddr;\n    struct mroute_addr maddr;\n    int count = 0;\n\n    CLEAR(saddr);\n    saddr.addr.in4.sin_family = AF_INET;\n    saddr.addr.in4.sin_addr.s_addr = htonl(addr);\n    saddr.addr.in4.sin_port = htons(port);\n    maddr.proto = proto;\n    if (mroute_extract_openvpn_sockaddr(&maddr, &saddr, true))\n    {\n        hash_iterator_init(m->iter, &hi);\n        while ((he = hash_iterator_next(&hi)))\n        {\n            struct multi_instance *mi = (struct multi_instance *)he->value;\n            if (!mi->halt && mroute_addr_equal(&maddr, &mi->real))\n            {\n                multi_signal_instance(m, mi, SIGTERM);\n                ++count;\n            }\n        }\n        hash_iterator_free(&hi);\n    }\n    return count;\n}\n\nstatic void\nmanagement_delete_event(void *arg, event_t event)\n{\n    struct multi_context *m = (struct multi_context *)arg;\n    if (m->multi_io)\n    {\n        multi_tcp_delete_event(m->multi_io, event);\n    }\n}\n\nstruct multi_instance *\nlookup_by_cid(struct multi_context *m, const unsigned long cid)\n{\n    if (m)\n    {\n        struct multi_instance *mi = (struct multi_instance *)hash_lookup(m->cid_hash, &cid);\n        if (mi && !mi->halt)\n        {\n            return mi;\n        }\n    }\n    return NULL;\n}\n\nstatic bool\nmanagement_kill_by_cid(void *arg, const unsigned long cid, const char *kill_msg)\n{\n    struct multi_context *m = (struct multi_context *)arg;\n    struct multi_instance *mi = lookup_by_cid(m, cid);\n    if (mi)\n    {\n        send_restart(&mi->context, kill_msg); /* was: multi_signal_instance (m, mi, SIGTERM); */\n        multi_schedule_context_wakeup(m, mi);\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\nstatic bool\nmanagement_client_pending_auth(void *arg, const unsigned long cid, const unsigned int mda_key_id,\n                               const char *extra, unsigned int timeout)\n{\n    struct multi_context *m = (struct multi_context *)arg;\n    struct multi_instance *mi = lookup_by_cid(m, cid);\n\n    if (mi)\n    {\n        struct tls_multi *multi = mi->context.c2.tls_multi;\n        struct tls_session *session;\n\n        if (multi->session[TM_INITIAL].key[KS_PRIMARY].mda_key_id == mda_key_id)\n        {\n            session = &multi->session[TM_INITIAL];\n        }\n        else if (multi->session[TM_ACTIVE].key[KS_PRIMARY].mda_key_id == mda_key_id)\n        {\n            session = &multi->session[TM_ACTIVE];\n        }\n        else\n        {\n            return false;\n        }\n\n        /* sends INFO_PRE and AUTH_PENDING messages to client */\n        bool ret = send_auth_pending_messages(multi, session, extra, timeout);\n        reschedule_multi_process(&mi->context);\n        multi_schedule_context_wakeup(m, mi);\n        return ret;\n    }\n    return false;\n}\n\n\nstatic bool\nmanagement_client_auth(void *arg, const unsigned long cid, const unsigned int mda_key_id,\n                       const bool auth, const char *reason, const char *client_reason,\n                       struct buffer_list *cc_config) /* ownership transferred */\n{\n    struct multi_context *m = (struct multi_context *)arg;\n    struct multi_instance *mi = lookup_by_cid(m, cid);\n    bool cc_config_owned = true;\n    bool ret = false;\n\n    if (mi)\n    {\n        ret = tls_authenticate_key(mi->context.c2.tls_multi, mda_key_id, auth, client_reason);\n        if (ret)\n        {\n            if (auth)\n            {\n                if (mi->context.c2.tls_multi->multi_state <= CAS_WAITING_AUTH)\n                {\n                    set_cc_config(mi, cc_config);\n                    cc_config_owned = false;\n                }\n            }\n            else if (reason)\n            {\n                msg(D_MULTI_LOW, \"MULTI: connection rejected: %s, CLI:%s\", reason,\n                    np(client_reason));\n            }\n        }\n    }\n    if (cc_config_owned && cc_config)\n    {\n        buffer_list_free(cc_config);\n    }\n    return ret;\n}\n\nstatic char *\nmanagement_get_peer_info(void *arg, const unsigned long cid)\n{\n    struct multi_context *m = (struct multi_context *)arg;\n    struct multi_instance *mi = lookup_by_cid(m, cid);\n    char *ret = NULL;\n\n    if (mi)\n    {\n        ret = mi->context.c2.tls_multi->peer_info;\n    }\n\n    return ret;\n}\n\n#endif /* ifdef ENABLE_MANAGEMENT */\n\n\nvoid\ninit_management_callback_multi(struct multi_context *m)\n{\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        struct management_callback cb;\n        CLEAR(cb);\n        cb.arg = m;\n        cb.flags = MCF_SERVER;\n        cb.status = management_callback_status;\n        cb.show_net = management_show_net_callback;\n        cb.kill_by_cn = management_callback_kill_by_cn;\n        cb.kill_by_addr = management_callback_kill_by_addr;\n        cb.delete_event = management_delete_event;\n        cb.n_clients = management_callback_n_clients;\n        cb.kill_by_cid = management_kill_by_cid;\n        cb.client_auth = management_client_auth;\n        cb.client_pending_auth = management_client_pending_auth;\n        cb.get_peer_info = management_get_peer_info;\n        cb.push_update_broadcast = management_callback_send_push_update_broadcast;\n        cb.push_update_by_cid = management_callback_send_push_update_by_cid;\n        management_set_callback(management, &cb);\n    }\n#endif /* ifdef ENABLE_MANAGEMENT */\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\nvoid\nmulti_assign_peer_id(struct multi_context *m, struct multi_instance *mi)\n{\n    /* max_clients must be less then max peer-id value */\n    ASSERT(m->max_clients < MAX_PEER_ID);\n\n    for (int i = 0; i < m->max_clients; ++i)\n    {\n        if (!m->instances[i])\n        {\n            mi->context.c2.tls_multi->peer_id = i;\n            m->instances[i] = mi;\n            break;\n        }\n    }\n\n    /* should not really end up here, since multi_create_instance returns null\n     * if amount of clients exceeds max_clients */\n    ASSERT(mi->context.c2.tls_multi->peer_id < m->max_clients);\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n/**\n * @brief Determines the earliest wakeup interval based on periodic operations.\n *\n * Updates the \\c timeval to reflect the next scheduled wakeup time.\n * Also sets \\c multi->earliest_wakeup to the instance with the earliest wakeup.\n *\n * @param multi     Pointer to the multi context\n * @param timeval   Pointer to the timeval structure to be updated with the\n *                  next wakeup time\n */\nstatic void\nmulti_get_timeout(struct multi_context *multi, struct timeval *timeval)\n{\n    multi_get_timeout_instance(multi, timeval);\n\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        management_check_bytecount_server(multi, timeval);\n    }\n#endif /* ENABLE_MANAGEMENT */\n}\n\n/**************************************************************************/\n/**\n * Main event loop for OpenVPN in point-to-multipoint server mode.\n * @ingroup eventloop\n *\n * @param multi context structure\n */\nstatic void\ntunnel_server_loop(struct multi_context *multi)\n{\n    int status;\n\n    while (true)\n    {\n        /* wait on tun/socket list */\n        multi_get_timeout(multi, &multi->top.c2.timeval);\n        status = multi_io_wait(multi);\n        MULTI_CHECK_SIG(multi);\n\n        /* check on status of coarse timers */\n        multi_process_per_second_timers(multi);\n\n        /* timeout? */\n        if (status > 0)\n        {\n            /* process the I/O which triggered select */\n            multi_io_process_io(multi);\n        }\n        else if (status == 0)\n        {\n            multi_io_action(multi, NULL, TA_TIMEOUT, false);\n        }\n\n        MULTI_CHECK_SIG(multi);\n    }\n}\n\n/*\n * Top level event loop.\n */\nvoid\ntunnel_server(struct context *top)\n{\n    ASSERT(top->options.mode == MODE_SERVER);\n\n    struct multi_context multi;\n\n    top->mode = CM_TOP;\n    top->multi = &multi;\n    context_clear_2(top);\n\n    /* initialize top-tunnel instance */\n    init_instance_handle_signals(top, top->es, CC_HARD_USR1_TO_HUP);\n    if (IS_SIG(top))\n    {\n        return;\n    }\n\n    /* initialize global multi_context object */\n    multi_init(top);\n\n    /* initialize our cloned top object */\n    multi_top_init(top);\n\n    /* initialize management interface */\n    init_management_callback_multi(&multi);\n\n    /* finished with initialization */\n    initialization_sequence_completed(top, ISC_SERVER); /* --mode server --proto tcp-server */\n\n#ifdef ENABLE_ASYNC_PUSH\n    multi.top.c2.inotify_fd = inotify_init();\n    if (multi.top.c2.inotify_fd < 0)\n    {\n        msg(D_MULTI_ERRORS | M_ERRNO, \"MULTI: inotify_init error\");\n    }\n#endif\n\n    tunnel_server_loop(&multi);\n\n#ifdef ENABLE_ASYNC_PUSH\n    msg(D_LOW, \"%s: close multi.top.c2.inotify_fd (%d)\",\n        __func__, multi.top.c2.inotify_fd);\n    close(multi.top.c2.inotify_fd);\n#endif\n\n    /* shut down management interface */\n    uninit_management_callback();\n\n    /* save ifconfig-pool */\n    multi_ifconfig_pool_persist(&multi, true);\n\n    /* tear down tunnel instance (unless --persist-tun) */\n    multi_uninit(&multi);\n    multi_top_free(&multi);\n    close_instance(top);\n}\n\n/* Searches for the address and deletes it if it is owned by the multi_instance */\nstatic void\nmulti_unlearn_addr(struct multi_context *m, struct multi_instance *mi, const struct mroute_addr *addr)\n{\n    struct hash_element *he;\n    const uint32_t hv = hash_value(m->vhash, addr);\n    struct hash_bucket *bucket = hash_bucket(m->vhash, hv);\n    struct multi_route *r = NULL;\n\n    /* if route currently exists, get the instance which owns it */\n    he = hash_lookup_fast(m->vhash, bucket, addr, hv);\n    if (he)\n    {\n        r = (struct multi_route *)he->value;\n    }\n\n    /* if the route does not exist or exists but is not owned by the current instance, return */\n    if (!r || r->instance != mi)\n    {\n        return;\n    }\n\n    struct gc_arena gc = gc_new();\n    msg(D_MULTI_LOW, \"MULTI: Unlearn: %s -> %s\", mroute_addr_print(&r->addr, &gc), multi_instance_string(mi, false, &gc));\n    learn_address_script(m, NULL, \"delete\", &r->addr);\n    hash_remove_by_value(m->vhash, r);\n    multi_route_del(r);\n\n    gc_free(&gc);\n}\n\n/**\n * @param m     The multi_context\n * @param mi    The multi_instance of the client we are updating\n * @param a     The new IPv4 address in network byte order\n */\nstatic void\nmulti_unlearn_in_addr_t(struct multi_context *m, struct multi_instance *mi, in_addr_t a)\n{\n    struct mroute_addr addr;\n    CLEAR(addr);\n\n    addr.type = MR_ADDR_IPV4;\n    addr.len = 4;\n    addr.v4.addr = a;\n\n    multi_unlearn_addr(m, mi, &addr);\n}\n\n/**\n * @param m     The multi_context\n * @param mi    The multi_instance of the client we are updating\n * @param a6    The new IPv6 address\n */\nstatic void\nmulti_unlearn_in6_addr(struct multi_context *m, struct multi_instance *mi, struct in6_addr a6)\n{\n    struct mroute_addr addr;\n    CLEAR(addr);\n\n    addr.type = MR_ADDR_IPV6;\n    addr.len = 16;\n    addr.v6.addr = a6;\n\n    multi_unlearn_addr(m, mi, &addr);\n}\n\n/* Function to unlearn previous ifconfig of a client in the server multi_context after a PUSH_UPDATE */\nvoid\nunlearn_ifconfig(struct multi_context *m, struct multi_instance *mi)\n{\n    in_addr_t old_addr = 0;\n    old_addr = htonl(mi->context.c2.push_ifconfig_local);\n    multi_unlearn_in_addr_t(m, mi, old_addr);\n    mi->context.c2.push_ifconfig_defined = false;\n    mi->context.c2.push_ifconfig_local = 0;\n    mi->reporting_addr = 0;\n}\n\n/* Function to unlearn previous ifconfig-ipv6 of a client in the server multi_context after a PUSH_UPDATE */\nvoid\nunlearn_ifconfig_ipv6(struct multi_context *m, struct multi_instance *mi)\n{\n    struct in6_addr old_addr6;\n    CLEAR(old_addr6);\n    old_addr6 = mi->context.c2.push_ifconfig_ipv6_local;\n    multi_unlearn_in6_addr(m, mi, old_addr6);\n    mi->context.c2.push_ifconfig_ipv6_defined = false;\n    CLEAR(mi->context.c2.push_ifconfig_ipv6_local);\n    CLEAR(mi->reporting_addr_ipv6);\n}\n\n/**\n * Update the vhash with new IP/IPv6 addresses in the multi_context when a\n * push-update message containing ifconfig/ifconfig-ipv6 options is sent\n * from the server.\n *\n * @param m         The multi_context\n * @param mi        The multi_instance of the client we are updating\n * @param new_ip    The new IPv4 address or NULL if no change\n * @param new_ipv6  The new IPv6 address or NULL if no change\n */\nvoid\nupdate_vhash(struct multi_context *m, struct multi_instance *mi, const char *new_ip, const char *new_ipv6)\n{\n    if (new_ip)\n    {\n        /* Remove old IP */\n        if (mi->context.c2.push_ifconfig_defined)\n        {\n            unlearn_ifconfig(m, mi);\n        }\n\n        /* Add new IP */\n        struct in_addr new_addr;\n        CLEAR(new_addr);\n        if (inet_pton(AF_INET, new_ip, &new_addr) == 1\n            && multi_learn_in_addr_t(m, mi, ntohl(new_addr.s_addr), -1, true))\n        {\n            mi->context.c2.push_ifconfig_defined = true;\n            mi->context.c2.push_ifconfig_local = ntohl(new_addr.s_addr);\n            /* set our client's VPN endpoint for status reporting purposes */\n            mi->reporting_addr = mi->context.c2.push_ifconfig_local;\n        }\n    }\n\n    if (new_ipv6)\n    {\n        /* Remove old IPv6 */\n        if (mi->context.c2.push_ifconfig_ipv6_defined)\n        {\n            unlearn_ifconfig_ipv6(m, mi);\n        }\n\n        /* Add new IPv6 */\n        struct in6_addr new_addr6;\n        CLEAR(new_addr6);\n        if (inet_pton(AF_INET6, new_ipv6, &new_addr6) == 1\n            && multi_learn_in6_addr(m, mi, new_addr6, -1, true))\n        {\n            mi->context.c2.push_ifconfig_ipv6_defined = true;\n            mi->context.c2.push_ifconfig_ipv6_local = new_addr6;\n            /* set our client's VPN endpoint for status reporting purposes */\n            mi->reporting_addr_ipv6 = mi->context.c2.push_ifconfig_ipv6_local;\n        }\n    }\n}\n\nbool\nmulti_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest)\n{\n    struct options *o = &mi->context.options;\n    in_addr_t local_addr, local_netmask;\n\n    if (!o->ifconfig_local || !o->ifconfig_remote_netmask)\n    {\n        /* If we do not have a local address, we just return false as\n         * this check doesn't make sense. */\n        return false;\n    }\n\n    /* if it falls into the network defined by ifconfig_local we assume\n     * it is already known to DCO and only install \"extra\" iroutes  */\n    inet_pton(AF_INET, o->ifconfig_local, &local_addr);\n    inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask);\n\n    return (local_addr & local_netmask) != (dest & local_netmask);\n}\n\nbool\nmulti_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi,\n                                           struct in6_addr *dest)\n{\n    struct options *o = &mi->context.options;\n\n    if (!o->ifconfig_ipv6_local || !o->ifconfig_ipv6_netbits)\n    {\n        /* If we do not have a local address, we just return false as\n         * this check doesn't make sense. */\n        return false;\n    }\n\n    /* if it falls into the network defined by ifconfig_local we assume\n     * it is already known to DCO and only install \"extra\" iroutes  */\n    struct in6_addr ifconfig_local;\n    if (inet_pton(AF_INET6, o->ifconfig_ipv6_local, &ifconfig_local) != 1)\n    {\n        return false;\n    }\n\n    return (!ipv6_net_contains_host(&ifconfig_local, o->ifconfig_ipv6_netbits,\n                                    dest));\n}\n"
  },
  {
    "path": "src/openvpn/multi.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Header file for server-mode related structures and functions.\n */\n\n#ifndef MULTI_H\n#define MULTI_H\n\n#include \"init.h\"\n#include \"forward.h\"\n#include \"mroute.h\"\n#include \"mbuf.h\"\n#include \"list.h\"\n#include \"schedule.h\"\n#include \"pool.h\"\n#include \"mudp.h\"\n#include \"mtcp.h\"\n#include \"multi_io.h\"\n#include \"vlan.h\"\n#include \"reflect_filter.h\"\n\n#define MULTI_PREFIX_MAX_LENGTH 256\n\n/*\n * Walk (don't run) through the routing table,\n * deleting old entries, and possibly multi_instance\n * structs as well which have been marked for deletion.\n */\nstruct multi_reap\n{\n    uint32_t bucket_base;\n    uint32_t buckets_per_pass;\n    time_t last_call;\n};\n\n\nstruct deferred_signal_schedule_entry\n{\n    struct schedule_entry se;\n    int signal_received;\n    struct timeval wakeup;\n};\n\n/**\n * Detached client connection state.  This is the state that is tracked while\n * the client connect hooks are executed.\n */\nstruct client_connect_defer_state\n{\n    /* Index of currently executed handler.  */\n    int cur_handler_index;\n    /* Remember which option classes where processed for delayed option\n     * handling. */\n    unsigned int option_types_found;\n\n    /**\n     * The temporary file name that contains the return status of the\n     * client-connect script if it exits with defer as status\n     */\n    char *deferred_ret_file;\n\n    /**\n     * The temporary file name that contains the config directives\n     * returned by the client-connect script\n     */\n    char *config_file;\n};\n\n/**\n * Server-mode state structure for one single VPN tunnel.\n *\n * This structure is used by OpenVPN processes running in server-mode to\n * store state information related to one single VPN tunnel.\n *\n * The @ref tunnel_state \"Structure of VPN tunnel state storage\" related\n * page describes the role the structure plays when OpenVPN is running in\n * server-mode.\n */\nstruct multi_instance\n{\n    struct schedule_entry se; /* this must be the first element of the structure,\n                               * We cast between this and schedule_entry so the\n                               * beginning of the struct must be identical */\n\n    struct event_arg ev_arg;  /**< this struct will store a pointer to either mi or\n                               * link_socket, depending on the event type, to keep\n                               * it accessible it's placed within the same struct\n                               * it points to. */\n\n    struct gc_arena gc;\n    bool halt;\n    int refcount;\n    int route_count;         /* number of routes (including cached routes) owned by this instance */\n    time_t created;          /**< Time at which a VPN tunnel instance\n                              *   was created.  This parameter is set\n                              *   by the \\c multi_create_instance()\n                              *   function. */\n    struct timeval wakeup;   /* absolute time */\n    struct mroute_addr real; /**< External network address of the\n                              *   remote peer. */\n    ifconfig_pool_handle vaddr_handle;\n    char msg_prefix[MULTI_PREFIX_MAX_LENGTH];\n\n    /* queued outgoing data in Server/TCP mode */\n    unsigned int tcp_rwflags;\n    struct mbuf_set *tcp_link_out_deferred;\n    bool socket_set_called;\n\n    in_addr_t reporting_addr;            /* IP address shown in status listing */\n    struct in6_addr reporting_addr_ipv6; /* IPv6 address in status listing */\n\n    bool did_real_hash;\n    bool did_iter;\n#ifdef ENABLE_MANAGEMENT\n    bool did_cid_hash;\n    struct buffer_list *cc_config;\n#endif\n    bool did_iroutes;\n    int n_clients_delta;    /* added to multi_context.n_clients when instance is closed */\n\n    struct context context; /**< The context structure storing state\n                             *   for this VPN tunnel. */\n    struct client_connect_defer_state client_connect_defer_state;\n#ifdef ENABLE_ASYNC_PUSH\n    int inotify_watch; /* watch descriptor for acf */\n#endif\n};\n\n\n/**\n * Main OpenVPN server state structure.\n *\n * This structure is used by OpenVPN processes running in server-mode to\n * store all the VPN tunnel and process-wide state.\n *\n * The @ref tunnel_state \"Structure of VPN tunnel state storage\" related\n * page describes the role the structure plays when OpenVPN is running in\n * server-mode.\n */\nstruct multi_context\n{\n    struct multi_instance **instances; /**< Array of multi_instances. An instance can be\n                                        * accessed using peer-id as an index. */\n\n    struct hash *hash;                 /**< VPN tunnel instances indexed by real\n                                        *   address of the remote peer. */\n    struct hash *vhash;                /**< VPN tunnel instances indexed by\n                                        *   virtual address of remote hosts. */\n    struct hash *iter;                 /**< VPN tunnel instances indexed by real\n                                        *   address of the remote peer, optimized\n                                        *   for iteration. */\n    struct schedule *schedule;\n    struct mbuf_set *mbuf;             /**< Set of buffers for passing data\n                                        *   channel packets between VPN tunnel\n                                        *   instances. */\n    struct multi_io *multi_io;         /**< I/O state and events tracker */\n    struct ifconfig_pool *ifconfig_pool;\n    struct frequency_limit *new_connection_limiter;\n    struct initial_packet_rate_limit *initial_rate_limiter;\n    struct mroute_helper *route_helper;\n    struct multi_reap *reaper;\n    struct mroute_addr local;\n    bool enable_c2c;\n    int max_clients;\n    int tcp_queue_limit;\n    int status_file_version;\n    int n_clients; /* current number of authenticated clients */\n\n#ifdef ENABLE_MANAGEMENT\n    struct hash *cid_hash;\n    unsigned long cid_counter;\n#endif\n\n    struct multi_instance *pending;\n    struct multi_instance *earliest_wakeup;\n    struct multi_instance **mpp_touched;\n    struct context_buffers *context_buffers;\n    time_t per_second_trigger;\n\n    struct context top; /**< Storage structure for process-wide\n                         *   configuration. */\n\n    struct buffer hmac_reply;\n    struct link_socket_actual *hmac_reply_dest;\n    struct link_socket *hmac_reply_ls;\n\n    /*\n     * Timer object for stale route check\n     */\n    struct event_timeout stale_routes_check_et;\n\n#ifdef ENABLE_ASYNC_PUSH\n    /* mapping between inotify watch descriptors and multi_instances */\n    struct hash *inotify_watchers;\n#endif\n\n    struct deferred_signal_schedule_entry deferred_shutdown_signal;\n};\n\n/**\n * Return values used by the client connect call-back functions.\n */\nenum client_connect_return\n{\n    CC_RET_FAILED,\n    CC_RET_SUCCEEDED,\n    CC_RET_DEFERRED,\n    CC_RET_SKIPPED\n};\n\n/*\n * Host route\n */\nstruct multi_route\n{\n    struct mroute_addr addr;\n    struct multi_instance *instance;\n\n#define MULTI_ROUTE_CACHE   (1 << 0)\n#define MULTI_ROUTE_AGEABLE (1 << 1)\n    unsigned int flags;\n\n    unsigned int cache_generation;\n    time_t last_reference;\n};\n\n\n/**************************************************************************/\n/**\n * Main event loop for OpenVPN in server mode.\n * @ingroup eventloop\n *\n * @param top          - Top-level context structure.\n */\nvoid tunnel_server(struct context *top);\n\n\nconst char *multi_instance_string(const struct multi_instance *mi, bool null, struct gc_arena *gc);\n\n/*\n * Called by mtcp.c, mudp.c, or other (to be written) protocol drivers\n */\n\nstruct multi_instance *multi_create_instance(struct multi_context *m,\n                                             const struct mroute_addr *real,\n                                             struct link_socket *sock);\n\nvoid multi_close_instance(struct multi_context *m, struct multi_instance *mi, bool shutdown);\n\nbool multi_process_timeout(struct multi_context *m, const unsigned int mpp_flags);\n\n#define MPP_PRE_SELECT      (1 << 0)\n#define MPP_CLOSE_ON_SIGNAL (1 << 1)\n#define MPP_RECORD_TOUCH    (1 << 2)\n\n\n/**************************************************************************/\n/**\n * Perform postprocessing of a VPN tunnel instance.\n *\n * After some VPN tunnel activity has taken place, the VPN tunnel's state\n * may need updating and some follow-up action may be required.  This\n * function controls the necessary postprocessing.  It is called by many\n * other functions that handle VPN tunnel related activity, such as \\c\n * multi_process_incoming_link(), \\c multi_process_outgoing_link(), \\c\n * multi_process_incoming_tun(), \\c multi_process_outgoing_tun(), and \\c\n * multi_process_timeout(), among others.\n *\n * @param m            - The single \\c multi_context structure.\n * @param mi           - The \\c multi_instance of the VPN tunnel to be\n *                       postprocessed.\n * @param flags        - Fast I/O optimization flags.\n *\n * @return\n *  - True, if the VPN tunnel instance \\a mi was not closed due to a\n *    signal during processing.\n *  - False, if the VPN tunnel instance \\a mi was closed.\n */\nbool multi_process_post(struct multi_context *m, struct multi_instance *mi,\n                        const unsigned int flags);\n\n/**\n * Process an incoming DCO message (from kernel space).\n *\n * @param dco - Pointer to the structure representing the DCO context.\n */\nvoid multi_process_incoming_dco(dco_context_t *dco);\n\n/**************************************************************************/\n/**\n * Demultiplex and process a packet received over the external network\n * interface.\n * @ingroup external_multiplexer\n *\n * This function determines which VPN tunnel instance the incoming packet\n * is associated with, and then calls \\c process_incoming_link() to handle\n * it.  Afterwards, if the packet is destined for a broadcast/multicast\n * address or a remote host reachable through a different VPN tunnel, this\n * function takes care of sending it they are.\n *\n * @note This function is only used by OpenVPN processes which are running\n *     in server mode, and can therefore sustain multiple active VPN\n *     tunnels.\n *\n * @param m            - The single \\c multi_context structure.\n * @param instance     - The VPN tunnel state structure associated with\n *                       the incoming packet, if known, as is the case\n *                       when using TCP transport. Otherwise NULL, as is\n *                       the case when using UDP transport.\n * @param mpp_flags    - Fast I/O optimization flags.\n * @param sock         - Socket where the packet was received.\n */\nbool multi_process_incoming_link(struct multi_context *m, struct multi_instance *instance,\n                                 const unsigned int mpp_flags, struct link_socket *sock);\n\n\n/**\n * Determine the destination VPN tunnel of a packet received over the\n * virtual tun/tap network interface and then process it accordingly.\n * @ingroup internal_multiplexer\n *\n * This function determines which VPN tunnel instance the packet is\n * destined for, and then calls \\c process_outgoing_tun() to handle it.\n *\n * @note This function is only used by OpenVPN processes which are running\n *     in server mode, and can therefore sustain multiple active VPN\n *     tunnels.\n *\n * @param m            - The single \\c multi_context structure.\n * @param mpp_flags    - Fast I/O optimization flags.\n */\nbool multi_process_incoming_tun(struct multi_context *m, const unsigned int mpp_flags);\n\n\nvoid multi_process_drop_outgoing_tun(struct multi_context *m, const unsigned int mpp_flags);\n\nstruct multi_instance *multi_get_queue(struct mbuf_set *ms);\n\nvoid multi_add_mbuf(struct multi_context *m, struct multi_instance *mi, struct mbuf_buffer *mb);\n\nvoid multi_ifconfig_pool_persist(struct multi_context *m, bool force);\n\nbool multi_process_signal(struct multi_context *m);\n\nvoid multi_close_instance_on_signal(struct multi_context *m, struct multi_instance *mi);\n\nvoid init_management_callback_multi(struct multi_context *m);\n\n#ifdef ENABLE_ASYNC_PUSH\n/**\n * Called when inotify event is fired, which happens when acf file is closed or deleted.\n * Continues authentication and sends push_repl\n *\n * @param m multi_context\n * @param mpp_flags\n */\nvoid multi_process_file_closed(struct multi_context *m, const unsigned int mpp_flags);\n\n#endif\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\n/*\n * Return true if our output queue is not full\n */\nstatic inline bool\nmulti_output_queue_ready(const struct multi_context *m, const struct multi_instance *mi)\n{\n    if (mi->tcp_link_out_deferred)\n    {\n        return mbuf_len(mi->tcp_link_out_deferred) <= m->tcp_queue_limit;\n    }\n    else\n    {\n        return true;\n    }\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n/*\n * Determine which instance has pending output\n * and prepare the output for sending in\n * the to_link buffer.\n */\nstatic inline struct multi_instance *\nmulti_process_outgoing_link_pre(struct multi_context *m)\n{\n    struct multi_instance *mi = NULL;\n\n    if (m->pending)\n    {\n        mi = m->pending;\n    }\n    else if (mbuf_defined(m->mbuf))\n    {\n        mi = multi_get_queue(m->mbuf);\n    }\n    return mi;\n}\n\n/*\n * Per-client route quota management\n */\n\nvoid route_quota_exceeded(const struct multi_instance *mi);\n\nstatic inline void\nroute_quota_inc(struct multi_instance *mi)\n{\n    ++mi->route_count;\n}\n\nstatic inline void\nroute_quota_dec(struct multi_instance *mi)\n{\n    --mi->route_count;\n}\n\n/* can we add a new route? */\nstatic inline bool\nroute_quota_test(const struct multi_instance *mi)\n{\n    if (mi->route_count >= mi->context.options.max_routes_per_client)\n    {\n        route_quota_exceeded(mi);\n        return false;\n    }\n    else\n    {\n        return true;\n    }\n}\n\n/*\n * Instance reference counting\n */\n\nstatic inline void\nmulti_instance_inc_refcount(struct multi_instance *mi)\n{\n    ++mi->refcount;\n}\n\nstatic inline void\nmulti_instance_dec_refcount(struct multi_instance *mi)\n{\n    if (--mi->refcount <= 0)\n    {\n        gc_free(&mi->gc);\n        free(mi);\n    }\n}\n\nstatic inline void\nmulti_route_del(struct multi_route *route)\n{\n    struct multi_instance *mi = route->instance;\n    route_quota_dec(mi);\n    multi_instance_dec_refcount(mi);\n    free(route);\n}\n\nstatic inline bool\nmulti_route_defined(const struct multi_context *m, const struct multi_route *r)\n{\n    if (r->instance->halt)\n    {\n        return false;\n    }\n    else if ((r->flags & MULTI_ROUTE_CACHE)\n             && r->cache_generation != m->route_helper->cache_generation)\n    {\n        return false;\n    }\n    else if ((r->flags & MULTI_ROUTE_AGEABLE)\n             && r->last_reference + m->route_helper->ageable_ttl_secs < now)\n    {\n        return false;\n    }\n    else\n    {\n        return true;\n    }\n}\n\n/*\n * Takes prefix away from multi_instance.\n */\nvoid ungenerate_prefix(struct multi_instance *mi);\n\n/*\n * Set a msg() function prefix with our current client instance ID.\n */\n\nstatic inline void\nset_prefix(struct multi_instance *mi)\n{\n#ifdef MULTI_DEBUG_EVENT_LOOP\n    if (mi->msg_prefix[0])\n    {\n        printf(\"[%s]\\n\", mi->msg_prefix);\n    }\n#endif\n    msg_set_prefix(mi->msg_prefix[0] ? mi->msg_prefix : NULL);\n}\n\nstatic inline void\nclear_prefix(void)\n{\n#ifdef MULTI_DEBUG_EVENT_LOOP\n    printf(\"[NULL]\\n\");\n#endif\n    msg_set_prefix(NULL);\n}\n\n/*\n * Instance Reaper\n *\n * Reaper constants.  The reaper is the process where the virtual address\n * and virtual route hash table is scanned for dead entries which are\n * then removed.  The hash table could potentially be quite large, so we\n * don't want to reap in a single pass.\n */\n\n#define REAP_MAX_WAKEUP 10   /* Do reap pass at least once per n seconds */\n#define REAP_DIVISOR    256  /* How many passes to cover whole hash table */\n#define REAP_MIN        16   /* Minimum number of buckets per pass */\n#define REAP_MAX        1024 /* Maximum number of buckets per pass */\n\n/*\n * Mark a cached host route for deletion after this\n * many seconds without any references.\n */\n#define MULTI_CACHE_ROUTE_TTL 60\n\nvoid multi_reap_process_dowork(const struct multi_context *m);\n\nvoid multi_process_per_second_timers_dowork(struct multi_context *m);\n\nstatic inline void\nmulti_reap_process(const struct multi_context *m)\n{\n    if (m->reaper->last_call != now)\n    {\n        multi_reap_process_dowork(m);\n    }\n}\n\nstatic inline void\nmulti_process_per_second_timers(struct multi_context *m)\n{\n    if (m->per_second_trigger != now)\n    {\n        multi_process_per_second_timers_dowork(m);\n        m->per_second_trigger = now;\n    }\n}\n\n/*\n * Updates \\c dest with the earliest timeout as a delta relative to the current\n * time and sets \\c m->earliest_wakeup to the \\c multi_instance with the\n * soonest scheduled wakeup.\n *\n * @param m     Pointer to the multi context\n * @param dest  Pointer to a timeval struct that will hold the earliest timeout\n *              delta.\n */\nstatic inline void\nmulti_get_timeout_instance(struct multi_context *m, struct timeval *dest)\n{\n    struct timeval tv, current;\n\n    CLEAR(tv);\n    m->earliest_wakeup = (struct multi_instance *)schedule_get_earliest_wakeup(m->schedule, &tv);\n    if (m->earliest_wakeup)\n    {\n        ASSERT(!openvpn_gettimeofday(&current, NULL));\n        tv_delta(dest, &current, &tv);\n        if (dest->tv_sec >= REAP_MAX_WAKEUP)\n        {\n            m->earliest_wakeup = NULL;\n            dest->tv_sec = REAP_MAX_WAKEUP;\n            dest->tv_usec = 0;\n        }\n    }\n    else\n    {\n        dest->tv_sec = REAP_MAX_WAKEUP;\n        dest->tv_usec = 0;\n    }\n}\n\n\n/**\n * Send a packet over the virtual tun/tap network interface to its locally\n * reachable destination.\n * @ingroup internal_multiplexer\n *\n * This function calls \\c process_outgoing_tun() to perform the actual\n * sending of the packet.  Afterwards, it calls \\c multi_process_post() to\n * perform server-mode postprocessing.\n *\n * @param m            - The single \\c multi_context structure.\n * @param mpp_flags    - Fast I/O optimization flags.\n *\n * @return\n *  - True, if the \\c multi_instance associated with the packet sent was\n *    not closed due to a signal during processing.\n *  - Falls, if the \\c multi_instance was closed.\n */\nstatic inline bool\nmulti_process_outgoing_tun(struct multi_context *m, const unsigned int mpp_flags)\n{\n    struct multi_instance *mi = m->pending;\n    bool ret = true;\n\n    ASSERT(mi);\n#ifdef MULTI_DEBUG_EVENT_LOOP\n    printf(\"%s -> TUN len=%d\\n\", id(mi), mi->context.c2.to_tun.len);\n#endif\n    set_prefix(mi);\n    vlan_process_outgoing_tun(m, mi);\n    process_outgoing_tun(&mi->context, mi->context.c2.link_sockets[0]);\n    ret = multi_process_post(m, mi, mpp_flags);\n    clear_prefix();\n    return ret;\n}\n\n#define CLIENT_CONNECT_OPT_MASK                                                            \\\n    (OPT_P_INSTANCE | OPT_P_INHERIT | OPT_P_PUSH | OPT_P_TIMER | OPT_P_CONFIG | OPT_P_ECHO \\\n     | OPT_P_COMP | OPT_P_SOCKFLAGS)\n\nstatic inline bool\nmulti_process_outgoing_link_dowork(struct multi_context *m, struct multi_instance *mi,\n                                   const unsigned int mpp_flags)\n{\n    bool ret = true;\n    set_prefix(mi);\n    process_outgoing_link(&mi->context, mi->context.c2.link_sockets[0]);\n    ret = multi_process_post(m, mi, mpp_flags);\n    clear_prefix();\n    return ret;\n}\n\n/**\n * Determines if the ifconfig_push_local address falls into the range of the local\n * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask)\n *\n * @param mi           The multi-instance to check this condition for\n * @param dest         The destination IP address to check\n *\n * @return Returns true if ifconfig_push is outside that range and requires an extra\n * route to be installed.\n */\nbool\nmulti_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest);\n\n/**\n * Determines if the ifconfig_ipv6_local address falls into the range of the local\n * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask)\n *\n * @param mi           The multi-instance to check this condition for\n * @param dest         The destination IPv6 address to check\n *\n * @return Returns true if ifconfig_push is outside that range and requires an extra\n * route to be installed.\n */\nbool\nmulti_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi,\n                                           struct in6_addr *dest);\n\n/*\n * Check for signals.\n */\n#define MULTI_CHECK_SIG(m) EVENT_LOOP_CHECK_SIGNAL(&(m)->top, multi_process_signal, (m))\n\nstatic inline void\nmulti_set_pending(struct multi_context *m, struct multi_instance *mi)\n{\n    m->pending = mi;\n}\n/**\n * Assigns a peer-id to a a client and adds the instance to the\n * the instances array of the \\c multi_context structure.\n *\n * @param m            - The single \\c multi_context structure.\n * @param mi           - The \\c multi_instance of the VPN tunnel to be\n *                       postprocessed.\n */\nvoid multi_assign_peer_id(struct multi_context *m, struct multi_instance *mi);\n\n#ifdef ENABLE_MANAGEMENT\nstruct multi_instance *\nlookup_by_cid(struct multi_context *m, const unsigned long cid);\n#endif\n\nvoid\nupdate_vhash(struct multi_context *m, struct multi_instance *mi, const char *new_ip, const char *new_ipv6);\nvoid unlearn_ifconfig(struct multi_context *m, struct multi_instance *mi);\nvoid unlearn_ifconfig_ipv6(struct multi_context *m, struct multi_instance *mi);\n\n#endif /* MULTI_H */\n"
  },
  {
    "path": "src/openvpn/multi_io.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"memdbg.h\"\n\n#include \"multi.h\"\n#include \"forward.h\"\n#include \"multi_io.h\"\n\n#ifdef HAVE_SYS_INOTIFY_H\n#include <sys/inotify.h>\n#endif\n\n/*\n * Special tags passed to event.[ch] functions\n */\n#define MULTI_IO_SOCKET           ((void *)1)\n#define MULTI_IO_TUN              ((void *)2)\n#define MULTI_IO_SIG              ((void *)3) /* Only on Windows */\n#define MULTI_IO_MANAGEMENT       ((void *)4)\n#define MULTI_IO_FILE_CLOSE_WRITE ((void *)5)\n#define MULTI_IO_DCO              ((void *)6)\n\nstruct ta_iow_flags\n{\n    unsigned int flags;\n    unsigned int ret;\n    unsigned int tun;\n    unsigned int sock;\n};\n\n#ifdef ENABLE_DEBUG\nstatic const char *\npract(int action)\n{\n    switch (action)\n    {\n        case TA_UNDEF:\n            return \"TA_UNDEF\";\n\n        case TA_SOCKET_READ:\n            return \"TA_SOCKET_READ\";\n\n        case TA_SOCKET_READ_RESIDUAL:\n            return \"TA_SOCKET_READ_RESIDUAL\";\n\n        case TA_SOCKET_WRITE:\n            return \"TA_SOCKET_WRITE\";\n\n        case TA_SOCKET_WRITE_READY:\n            return \"TA_SOCKET_WRITE_READY\";\n\n        case TA_SOCKET_WRITE_DEFERRED:\n            return \"TA_SOCKET_WRITE_DEFERRED\";\n\n        case TA_TUN_READ:\n            return \"TA_TUN_READ\";\n\n        case TA_TUN_WRITE:\n            return \"TA_TUN_WRITE\";\n\n        case TA_INITIAL:\n            return \"TA_INITIAL\";\n\n        case TA_TIMEOUT:\n            return \"TA_TIMEOUT\";\n\n        case TA_TUN_WRITE_TIMEOUT:\n            return \"TA_TUN_WRITE_TIMEOUT\";\n\n        default:\n            return \"?\";\n    }\n}\n#endif /* ENABLE_DEBUG */\n\nstatic inline struct context *\nmulti_get_context(struct multi_context *m, struct multi_instance *mi)\n{\n    if (mi)\n    {\n        return &mi->context;\n    }\n    else\n    {\n        return &m->top;\n    }\n}\n\nstruct multi_io *\nmulti_io_init(const int maxclients)\n{\n    struct multi_io *multi_io;\n\n    ASSERT(maxclients >= 1);\n\n    ALLOC_OBJ_CLEAR(multi_io, struct multi_io);\n    multi_io->maxevents = maxclients + BASE_N_EVENTS;\n    multi_io->es = event_set_init(&multi_io->maxevents, 0);\n    wait_signal(multi_io->es, MULTI_IO_SIG);\n    ALLOC_ARRAY(multi_io->esr, struct event_set_return, multi_io->maxevents);\n    msg(D_MULTI_LOW, \"MULTI IO: MULTI_IO INIT maxclients=%d maxevents=%d\", maxclients,\n        multi_io->maxevents);\n    return multi_io;\n}\n\nvoid\nmulti_io_set_global_rw_flags(struct multi_context *m, struct multi_instance *mi)\n{\n    if (!mi)\n    {\n        return;\n    }\n\n    mi->socket_set_called = true;\n    if (proto_is_dgram(mi->context.c2.link_sockets[0]->info.proto))\n    {\n        socket_set(mi->context.c2.link_sockets[0], m->multi_io->es, EVENT_READ,\n                   &mi->context.c2.link_sockets[0]->ev_arg, NULL);\n    }\n    else\n    {\n        socket_set(mi->context.c2.link_sockets[0], m->multi_io->es,\n                   mbuf_defined(mi->tcp_link_out_deferred) ? EVENT_WRITE : EVENT_READ, &mi->ev_arg,\n                   &mi->tcp_rwflags);\n    }\n}\n\nvoid\nmulti_io_free(struct multi_io *multi_io)\n{\n    if (multi_io)\n    {\n        event_free(multi_io->es);\n        free(multi_io->esr);\n        free(multi_io);\n    }\n}\n\nint\nmulti_io_wait(struct multi_context *m)\n{\n    int status, i;\n    unsigned int *persistent = &m->multi_io->tun_rwflags;\n\n    if (!tuntap_is_dco_win(m->top.c1.tuntap))\n    {\n        for (i = 0; i < m->top.c1.link_sockets_num; i++)\n        {\n            socket_set_listen_persistent(m->top.c2.link_sockets[i], m->multi_io->es,\n                                         &m->top.c2.link_sockets[i]->ev_arg);\n        }\n    }\n\n    if (has_udp_in_local_list(&m->top.options))\n    {\n        get_io_flags_udp(&m->top, m->multi_io, p2mp_iow_flags(m));\n    }\n\n    tun_set(m->top.c1.tuntap, m->multi_io->es, EVENT_READ, MULTI_IO_TUN, persistent);\n#if defined(ENABLE_DCO)\n    dco_event_set(&m->top.c1.tuntap->dco, m->multi_io->es, MULTI_IO_DCO);\n#endif\n\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        management_socket_set(management, m->multi_io->es, MULTI_IO_MANAGEMENT,\n                              &m->multi_io->management_persist_flags);\n    }\n#endif\n\n#ifdef ENABLE_ASYNC_PUSH\n    /* arm inotify watcher */\n    event_ctl(m->multi_io->es, m->top.c2.inotify_fd, EVENT_READ, MULTI_IO_FILE_CLOSE_WRITE);\n#endif\n\n    status =\n        event_wait(m->multi_io->es, &m->top.c2.timeval, m->multi_io->esr, m->multi_io->maxevents);\n    update_time();\n    m->multi_io->n_esr = 0;\n    if (status > 0)\n    {\n        m->multi_io->n_esr = status;\n    }\n    return status;\n}\n\nstatic int\nmulti_io_wait_lite(struct multi_context *m, struct multi_instance *mi, const int action,\n                   bool *tun_input_pending)\n{\n    struct context *c = multi_get_context(m, mi);\n    unsigned int looking_for = 0;\n\n    dmsg(D_MULTI_DEBUG, \"MULTI IO: multi_io_wait_lite a=%s mi=\" ptr_format, pract(action),\n         (ptr_type)mi);\n\n    tv_clear(&c->c2.timeval); /* ZERO-TIMEOUT */\n\n    switch (action)\n    {\n        case TA_TUN_READ:\n            looking_for = TUN_READ;\n            tun_input_pending = NULL;\n            io_wait(c, IOW_READ_TUN);\n            break;\n\n        case TA_SOCKET_READ:\n            looking_for = SOCKET_READ;\n            tun_input_pending = NULL;\n            io_wait(c, IOW_READ_LINK);\n            break;\n\n        case TA_TUN_WRITE:\n            looking_for = TUN_WRITE;\n            tun_input_pending = NULL;\n            /* For some reason, the Linux 2.2 TUN/TAP driver hits this timeout */\n            c->c2.timeval.tv_sec = 1;\n            io_wait(c, IOW_TO_TUN);\n            break;\n\n        case TA_SOCKET_WRITE:\n            looking_for = SOCKET_WRITE;\n            io_wait(c, IOW_TO_LINK | IOW_READ_TUN_FORCE);\n            break;\n\n        default:\n            msg(M_FATAL, \"MULTI IO: multi_io_wait_lite, unhandled action=%d\", action);\n    }\n\n    if (tun_input_pending && (c->c2.event_set_status & TUN_READ))\n    {\n        *tun_input_pending = true;\n    }\n\n    if (c->c2.event_set_status & looking_for)\n    {\n        return action;\n    }\n    else\n    {\n        switch (action)\n        {\n            /* MULTI PROTOCOL socket output buffer is full */\n            case TA_SOCKET_WRITE:\n                return TA_SOCKET_WRITE_DEFERRED;\n\n            /* TUN device timed out on accepting write */\n            case TA_TUN_WRITE:\n                return TA_TUN_WRITE_TIMEOUT;\n        }\n\n        return TA_UNDEF;\n    }\n}\n\nstatic struct multi_instance *\nmulti_io_dispatch(struct multi_context *m, struct multi_instance *mi, const int action)\n{\n    const unsigned int mpp_flags = MPP_PRE_SELECT | MPP_RECORD_TOUCH;\n    struct multi_instance *touched = mi;\n    m->mpp_touched = &touched;\n\n    dmsg(D_MULTI_DEBUG, \"MULTI IO: multi_io_dispatch a=%s mi=\" ptr_format, pract(action),\n         (ptr_type)mi);\n\n    switch (action)\n    {\n        case TA_TUN_READ:\n            read_incoming_tun(&m->top);\n            if (!IS_SIG(&m->top))\n            {\n                multi_process_incoming_tun(m, mpp_flags);\n            }\n            break;\n\n        case TA_SOCKET_READ:\n        case TA_SOCKET_READ_RESIDUAL:\n            ASSERT(mi);\n            ASSERT(mi->context.c2.link_sockets);\n            ASSERT(mi->context.c2.link_sockets[0]);\n            set_prefix(mi);\n            read_incoming_link(&mi->context, mi->context.c2.link_sockets[0]);\n            clear_prefix();\n            if (!IS_SIG(&mi->context))\n            {\n                multi_process_incoming_link(m, mi, mpp_flags, mi->context.c2.link_sockets[0]);\n                if (!IS_SIG(&mi->context))\n                {\n                    stream_buf_read_setup(mi->context.c2.link_sockets[0]);\n                }\n            }\n            break;\n\n        case TA_TIMEOUT:\n            multi_process_timeout(m, mpp_flags);\n            break;\n\n        case TA_TUN_WRITE:\n            multi_process_outgoing_tun(m, mpp_flags);\n            break;\n\n        case TA_TUN_WRITE_TIMEOUT:\n            multi_process_drop_outgoing_tun(m, mpp_flags);\n            break;\n\n        case TA_SOCKET_WRITE_READY:\n            ASSERT(mi);\n            multi_tcp_process_outgoing_link_ready(m, mi, mpp_flags);\n            break;\n\n        case TA_SOCKET_WRITE:\n            multi_tcp_process_outgoing_link(m, false, mpp_flags);\n            break;\n\n        case TA_SOCKET_WRITE_DEFERRED:\n            multi_tcp_process_outgoing_link(m, true, mpp_flags);\n            break;\n\n        case TA_INITIAL:\n            ASSERT(mi);\n            multi_io_set_global_rw_flags(m, mi);\n            multi_process_post(m, mi, mpp_flags);\n            break;\n\n        default:\n            msg(M_FATAL, \"MULTI IO: multi_io_dispatch, unhandled action=%d\", action);\n    }\n\n    m->mpp_touched = NULL;\n    return touched;\n}\n\nstatic int\nmulti_io_post(struct multi_context *m, struct multi_instance *mi, const int action)\n{\n    struct context *c = multi_get_context(m, mi);\n    int newaction = TA_UNDEF;\n\n#define MTP_NONE     0\n#define MTP_TUN_OUT  (1 << 0)\n#define MTP_LINK_OUT (1 << 1)\n    unsigned int flags = MTP_NONE;\n\n    if (TUN_OUT(c))\n    {\n        flags |= MTP_TUN_OUT;\n    }\n    if (LINK_OUT(c))\n    {\n        flags |= MTP_LINK_OUT;\n    }\n\n    switch (flags)\n    {\n        case MTP_TUN_OUT | MTP_LINK_OUT:\n        case MTP_TUN_OUT:\n            newaction = TA_TUN_WRITE;\n            break;\n\n        case MTP_LINK_OUT:\n            newaction = TA_SOCKET_WRITE;\n            break;\n\n        case MTP_NONE:\n            if (mi && sockets_read_residual(c))\n            {\n                newaction = TA_SOCKET_READ_RESIDUAL;\n            }\n            else\n            {\n                multi_io_set_global_rw_flags(m, mi);\n            }\n            break;\n\n        default:\n        {\n            struct gc_arena gc = gc_new();\n            msg(M_FATAL, \"MULTI IO: multi_io_post bad state, mi=%s flags=%d\",\n                multi_instance_string(mi, false, &gc), flags);\n            gc_free(&gc);\n            break;\n        }\n    }\n\n    dmsg(D_MULTI_DEBUG, \"MULTI IO: multi_io_post %s -> %s\", pract(action), pract(newaction));\n\n    return newaction;\n}\n\nvoid\nmulti_io_process_io(struct multi_context *m)\n{\n    struct multi_io *multi_io = m->multi_io;\n    int i;\n\n    for (i = 0; i < multi_io->n_esr; ++i)\n    {\n        struct event_set_return *e = &multi_io->esr[i];\n        struct event_arg *ev_arg = (struct event_arg *)e->arg;\n\n        /* incoming data for instance or listening socket? */\n        if (e->arg >= MULTI_N)\n        {\n            switch (ev_arg->type)\n            {\n                struct multi_instance *mi;\n\n                /* react to event on child instance */\n                case EVENT_ARG_MULTI_INSTANCE:\n                    if (!ev_arg->u.mi)\n                    {\n                        msg(D_MULTI_ERRORS, \"MULTI IO: multi_io_proc_io: null minstance\");\n                        break;\n                    }\n\n                    mi = ev_arg->u.mi;\n                    if (e->rwflags & EVENT_WRITE)\n                    {\n                        multi_io_action(m, mi, TA_SOCKET_WRITE_READY, false);\n                    }\n                    else if (e->rwflags & EVENT_READ)\n                    {\n                        multi_io_action(m, mi, TA_SOCKET_READ, false);\n                    }\n                    break;\n\n                case EVENT_ARG_LINK_SOCKET:\n                    if (!ev_arg->u.sock)\n                    {\n                        msg(D_MULTI_ERRORS, \"MULTI IO: multi_io_proc_io: null socket\");\n                        break;\n                    }\n                    /* new incoming TCP client attempting to connect? */\n                    if (!proto_is_dgram(ev_arg->u.sock->info.proto))\n                    {\n                        socket_reset_listen_persistent(ev_arg->u.sock);\n                        mi = multi_create_instance_tcp(m, ev_arg->u.sock);\n                    }\n                    else\n                    {\n                        multi_process_io_udp(m, ev_arg->u.sock);\n                        mi = m->pending;\n                    }\n                    /* monitor and/or handle events that are\n                     * triggered in succession by the first one\n                     * before returning to the main loop. */\n                    if (mi)\n                    {\n                        multi_io_action(m, mi, TA_INITIAL, false);\n                    }\n                    break;\n            }\n        }\n        else\n        {\n#ifdef ENABLE_MANAGEMENT\n            if (e->arg == MULTI_IO_MANAGEMENT)\n            {\n                ASSERT(management);\n                management_io(management);\n            }\n            else\n#endif\n                /* incoming data on TUN? */\n                if (e->arg == MULTI_IO_TUN)\n                {\n                    if (e->rwflags & EVENT_WRITE)\n                    {\n                        multi_io_action(m, NULL, TA_TUN_WRITE, false);\n                    }\n                    else if (e->rwflags & EVENT_READ)\n                    {\n                        multi_io_action(m, NULL, TA_TUN_READ, false);\n                    }\n                }\n\n#if defined(ENABLE_DCO)\n                /* incoming data on DCO? */\n                else if (e->arg == MULTI_IO_DCO)\n                {\n                    dco_read_and_process(&m->top.c1.tuntap->dco);\n                }\n#endif\n                /* signal received? */\n                else if (e->arg == MULTI_IO_SIG)\n                {\n                    get_signal(&m->top.sig->signal_received);\n                }\n#ifdef ENABLE_ASYNC_PUSH\n                else if (e->arg == MULTI_IO_FILE_CLOSE_WRITE)\n                {\n                    multi_process_file_closed(m, MPP_PRE_SELECT | MPP_RECORD_TOUCH);\n                }\n#endif\n        }\n        if (IS_SIG(&m->top))\n        {\n            break;\n        }\n    }\n    multi_io->n_esr = 0;\n\n    /*\n     * Process queued mbuf packets destined for TCP socket\n     */\n    {\n        struct multi_instance *mi;\n        while (!IS_SIG(&m->top) && (mi = mbuf_peek(m->mbuf)) != NULL)\n        {\n            multi_io_action(m, mi, TA_SOCKET_WRITE, true);\n        }\n    }\n}\n\nvoid\nmulti_io_action(struct multi_context *m, struct multi_instance *mi, int action, bool poll)\n{\n    bool tun_input_pending = false;\n\n    do\n    {\n        dmsg(D_MULTI_DEBUG, \"MULTI IO: multi_io_action a=%s p=%d\", pract(action), poll);\n\n        /*\n         * If TA_SOCKET_READ_RESIDUAL, it means we still have pending\n         * input packets which were read by a prior recv.\n         *\n         * Otherwise do a \"lite\" wait, which means we wait with 0 timeout\n         * on I/O events only related to the current instance, not\n         * the big list of events.\n         *\n         * On our first pass, poll will be false because we already know\n         * that input is available, and to call io_wait would be redundant.\n         */\n        if (poll && action != TA_SOCKET_READ_RESIDUAL)\n        {\n            const int orig_action = action;\n            action = multi_io_wait_lite(m, mi, action, &tun_input_pending);\n            if (action == TA_UNDEF)\n            {\n                msg(M_FATAL, \"MULTI IO: I/O wait required blocking in multi_io_action, action=%d\",\n                    orig_action);\n            }\n        }\n\n        /*\n         * Dispatch the action\n         */\n        struct multi_instance *touched = multi_io_dispatch(m, mi, action);\n\n        /*\n         * Signal received or connection\n         * reset by peer?\n         */\n        if (touched && IS_SIG(&touched->context))\n        {\n            if (mi == touched)\n            {\n                mi = NULL;\n            }\n            multi_close_instance_on_signal(m, touched);\n        }\n\n\n        /*\n         * If dispatch produced any pending output\n         * for a particular instance, point to\n         * that instance.\n         */\n        if (m->pending)\n        {\n            mi = m->pending;\n        }\n\n        /*\n         * Based on the effects of the action,\n         * such as generating pending output,\n         * possibly transition to a new action state.\n         */\n        action = multi_io_post(m, mi, action);\n\n        /*\n         * If we are finished processing the original action,\n         * check if we have any TUN input.  If so, transition\n         * our action state to processing this input.\n         */\n        if (tun_input_pending && action == TA_UNDEF)\n        {\n            action = TA_TUN_READ;\n            mi = NULL;\n            tun_input_pending = false;\n            poll = false;\n        }\n        else\n        {\n            poll = true;\n        }\n\n    } while (action != TA_UNDEF);\n}\n\nvoid\nmulti_io_delete_event(struct multi_io *multi_io, event_t event)\n{\n    if (multi_io && multi_io->es)\n    {\n        event_del(multi_io->es, event);\n    }\n}\n"
  },
  {
    "path": "src/openvpn/multi_io.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * Multi-protocol specific code for --mode server\n */\n\n#ifndef MULTI_IO_H\n#define MULTI_IO_H\n\n#include \"event.h\"\n\n/*\n * I/O processing States\n */\n\n#define TA_UNDEF                 0\n#define TA_SOCKET_READ           1\n#define TA_SOCKET_READ_RESIDUAL  2\n#define TA_SOCKET_WRITE          3\n#define TA_SOCKET_WRITE_READY    4\n#define TA_SOCKET_WRITE_DEFERRED 5\n#define TA_TUN_READ              6\n#define TA_TUN_WRITE             7\n#define TA_INITIAL               8\n#define TA_TIMEOUT               9\n#define TA_TUN_WRITE_TIMEOUT     10\n\n/*\n * I/O state and events tracker\n */\nstruct multi_io\n{\n    struct event_set *es;\n    struct event_set_return *esr;\n    int n_esr;\n    int maxevents;\n    unsigned int tun_rwflags;\n    unsigned int udp_flags;\n#ifdef ENABLE_MANAGEMENT\n    unsigned int management_persist_flags;\n#endif\n};\n\nstruct multi_io *multi_io_init(int maxclients);\n\nvoid multi_io_free(struct multi_io *multi_io);\n\nint multi_io_wait(struct multi_context *m);\n\nvoid multi_io_process_io(struct multi_context *m);\n\nvoid multi_io_set_global_rw_flags(struct multi_context *m, struct multi_instance *mi);\n\nvoid multi_io_action(struct multi_context *m, struct multi_instance *mi, int action, bool poll);\n\nvoid multi_io_delete_event(struct multi_io *multi_io, event_t event);\n\n#endif /* ifndef MULTI_IO_H */\n"
  },
  {
    "path": "src/openvpn/networking.h",
    "content": "/*\n *  Generic interface to platform specific networking code\n *\n *  Copyright (C) 2016-2026 Antonio Quartulli <a@unstable.cc>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef NETWORKING_H_\n#define NETWORKING_H_\n\n#include \"syshead.h\"\n\n#define IFACE_TYPE_LEN_MAX 64\n\nstruct context;\n\n#ifdef ENABLE_SITNL\n#include \"networking_sitnl.h\"\n#elif ENABLE_IPROUTE\n#include \"networking_iproute2.h\"\n#elif defined(TARGET_FREEBSD)\ntypedef void *openvpn_net_ctx_t;\ntypedef char openvpn_net_iface_t;\n#else  /* ifdef ENABLE_SITNL */\n/* define mock types to ensure code builds on any platform */\ntypedef void *openvpn_net_ctx_t;\ntypedef void *openvpn_net_iface_t;\n#endif /* ifdef ENABLE_SITNL */\n\n/* Only the iproute2 backend implements these functions,\n * the rest can rely on these stubs\n */\n#if !defined(ENABLE_IPROUTE)\nstatic inline int\nnet_ctx_init(struct context *c, openvpn_net_ctx_t *ctx)\n{\n    (void)c;\n    (void)ctx;\n\n    return 0;\n}\n\nstatic inline void\nnet_ctx_reset(openvpn_net_ctx_t *ctx)\n{\n    (void)ctx;\n}\n\nstatic inline void\nnet_ctx_free(openvpn_net_ctx_t *ctx)\n{\n    (void)ctx;\n}\n#endif /* !defined(ENABLE_IPROUTE) */\n\n#if defined(ENABLE_SITNL) || defined(ENABLE_IPROUTE)\n\n/**\n * Initialize the platform specific context object\n *\n * @param c         openvpn generic context\n * @param ctx       the implementation specific context to initialize\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_ctx_init(struct context *c, openvpn_net_ctx_t *ctx);\n\n/**\n * Release resources allocated by the internal garbage collector\n *\n * @param ctx       the implementation specific context\n */\nvoid net_ctx_reset(openvpn_net_ctx_t *ctx);\n\n/**\n * Release all resources allocated within the platform specific context object\n *\n * @param ctx       the implementation specific context to release\n */\nvoid net_ctx_free(openvpn_net_ctx_t *ctx);\n\n/**\n * Add a new interface\n *\n * @param ctx       the implementation specific context\n * @param iface     interface to create\n * @param type      string describing interface type\n * @param arg       extra data required by the specific type\n *\n * @return          0 on success, negative error code on error\n */\nint net_iface_new(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface, const char *type,\n                  void *arg);\n\n/**\n * Retrieve the interface type\n *\n * @param ctx       the implementation specific context\n * @param iface     interface to query\n * @param type      buffer where the type will be stored\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_iface_type(openvpn_net_ctx_t *ctx, const char *iface, char type[IFACE_TYPE_LEN_MAX]);\n\n/**\n * Remove an interface\n *\n * @param ctx       the implementation specific context\n * @param iface     interface to delete\n * @return int 0 on success, negative error code on error\n */\nint net_iface_del(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface);\n\n/**\n * Bring interface up or down.\n *\n * @param ctx       the implementation specific context\n * @param iface     the interface to modify\n * @param up        true if the interface has to be brought up, false otherwise\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_iface_up(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface, bool up);\n\n/**\n * Set the MTU for an interface\n *\n * @param ctx       the implementation specific context\n * @param iface     the interface to modify\n * @param mtru      the new MTU\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_iface_mtu_set(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface, uint32_t mtu);\n\n/**\n * Set the Link Layer (Ethernet) address of the TAP interface\n *\n * @param ctx       the implementation specific context\n * @param iface     the interface to modify\n * @param addr      the new address to set (expected ETH_ALEN bytes (6))\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_addr_ll_set(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface, uint8_t *addr);\n\n/**\n * Add an IPv4 address to an interface\n *\n * @param ctx       the implementation specific context\n * @param iface     the interface where the address has to be added\n * @param addr      the address to add\n * @param prefixlen the prefix length of the network associated with the address\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_addr_v4_add(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface, const in_addr_t *addr,\n                    int prefixlen);\n\n/**\n * Add an IPv6 address to an interface\n *\n * @param ctx       the implementation specific context\n * @param iface     the interface where the address has to be added\n * @param addr      the address to add\n * @param prefixlen the prefix length of the network associated with the address\n *\n * @return          0 on success, a negative error code otherwise\n */\n\nint net_addr_v6_add(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface,\n                    const struct in6_addr *addr, int prefixlen);\n\n/**\n * Remove an IPv4 from an interface\n *\n * @param ctx       the implementation specific context\n * @param iface     the interface to remove the address from\n * @param prefixlen the prefix length of the network associated with the address\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_addr_v4_del(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface, const in_addr_t *addr,\n                    int prefixlen);\n\n/**\n * Remove an IPv6 from an interface\n *\n * @param ctx       the implementation specific context\n * @param iface     the interface to remove the address from\n * @param prefixlen the prefix length of the network associated with the address\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_addr_v6_del(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface,\n                    const struct in6_addr *addr, int prefixlen);\n\n/**\n * Add a point-to-point IPv4 address to an interface\n *\n * @param ctx       the implementation specific context\n * @param iface     the interface where the address has to be added\n * @param local     the address to add\n * @param remote    the associated p-t-p remote address\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_addr_ptp_v4_add(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface,\n                        const in_addr_t *local, const in_addr_t *remote);\n\n/**\n * Remove a point-to-point IPv4 address from an interface\n *\n * @param ctx       the implementation specific context\n * @param iface     the interface to remove the address from\n * @param local     the address to remove\n * @param remote    the associated p-t-p remote address\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_addr_ptp_v4_del(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface,\n                        const in_addr_t *local, const in_addr_t *remote);\n\n#endif /* ENABLE_SITNL || ENABLE_IPROUTE */\n\n#if defined(ENABLE_SITNL) || defined(ENABLE_IPROUTE) || defined(TARGET_FREEBSD)\n/**\n * Add a route for an IPv4 address/network\n *\n * @param ctx       the implementation specific context\n * @param dst       the destination of the route\n * @param prefixlen the length of the prefix of the destination\n * @param gw        the gateway for this route\n * @param iface     the interface for this route (can be NULL)\n * @param table     the table to add this route to (if 0, will be added to the\n *                  main table)\n * @param metric    the metric associated with the route\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_route_v4_add(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen,\n                     const in_addr_t *gw, const openvpn_net_iface_t *iface, uint32_t table,\n                     int metric);\n\n/**\n * Add a route for an IPv6 address/network\n *\n * @param ctx       the implementation specific context\n * @param dst       the destination of the route\n * @param prefixlen the length of the prefix of the destination\n * @param gw        the gateway for this route\n * @param iface     the interface for this route (can be NULL)\n * @param table     the table to add this route to (if 0, will be added to the\n *                  main table)\n * @param metric    the metric associated with the route\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_route_v6_add(openvpn_net_ctx_t *ctx, const struct in6_addr *dst, int prefixlen,\n                     const struct in6_addr *gw, const openvpn_net_iface_t *iface, uint32_t table,\n                     int metric);\n\n/**\n * Delete a route for an IPv4 address/network\n *\n * @param ctx       the implementation specific context\n * @param dst       the destination of the route\n * @param prefixlen the length of the prefix of the destination\n * @param gw        the gateway for this route\n * @param iface     the interface for this route (can be NULL)\n * @param table     the table to add this route to (if 0, will be added to the\n *                  main table)\n * @param metric    the metric associated with the route\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_route_v4_del(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen,\n                     const in_addr_t *gw, const openvpn_net_iface_t *iface, uint32_t table,\n                     int metric);\n\n/**\n * Delete a route for an IPv6 address/network\n *\n * @param ctx       the implementation specific context\n * @param dst       the destination of the route\n * @param prefixlen the length of the prefix of the destination\n * @param gw        the gateway for this route\n * @param iface     the interface for this route (can be NULL)\n * @param table     the table to add this route to (if 0, will be added to the\n *                  main table)\n * @param metric    the metric associated with the route\n *\n * @return          0 on success, a negative error code otherwise\n */\nint net_route_v6_del(openvpn_net_ctx_t *ctx, const struct in6_addr *dst, int prefixlen,\n                     const struct in6_addr *gw, const openvpn_net_iface_t *iface, uint32_t table,\n                     int metric);\n\n#endif /* ENABLE_SITNL || ENABLE_IPROUTE || TARGET_FREEBSD */\n\n#if defined(ENABLE_SITNL) || defined(ENABLE_IPROUTE)\n\n/**\n * Retrieve the gateway and outgoing interface for the specified IPv4\n * address/network\n *\n * @param ctx           the implementation specific context\n * @param dst           The destination to lookup\n * @param best_gw       Location where the retrieved GW has to be stored\n * @param best_iface    Location where the retrieved interface has to be stored\n *\n * @return              0 on success, a negative error code otherwise\n */\nint net_route_v4_best_gw(openvpn_net_ctx_t *ctx, const in_addr_t *dst, in_addr_t *best_gw,\n                         openvpn_net_iface_t *best_iface);\n\n/**\n * Retrieve the gateway and outgoing interface for the specified IPv6\n * address/network\n *\n * @param ctx           the implementation specific context\n * @param dst           The destination to lookup\n * @param best_gw       Location where the retrieved GW has to be stored\n * @param best_iface    Location where the retrieved interface has to be stored\n *\n * @return              0 on success, a negative error code otherwise\n */\nint net_route_v6_best_gw(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,\n                         struct in6_addr *best_gw, openvpn_net_iface_t *best_iface);\n\n#endif /* ENABLE_SITNL || ENABLE_IPROUTE */\n\n#endif /* NETWORKING_H_ */\n"
  },
  {
    "path": "src/openvpn/networking_freebsd.c",
    "content": "#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n#include \"syshead.h\"\n#include \"errlevel.h\"\n#include \"run_command.h\"\n#include \"networking.h\"\n\n#if defined(TARGET_FREEBSD)\n\nstatic int\nnet_route_v4(const char *op, const in_addr_t *dst, int prefixlen, const in_addr_t *gw,\n             const char *iface, uint32_t table, int metric)\n{\n    char buf1[INET_ADDRSTRLEN], buf2[INET_ADDRSTRLEN];\n    in_addr_t _dst, _gw;\n    struct argv argv = argv_new();\n\n    ASSERT(gw || iface);\n\n    _dst = ntohl(*dst);\n\n    /* if we have a gateway (GW != NULL) install route to gateway IP\n     * if not, install \"connected\" route to interface\n     * (needed to make 'ifconfig-push IPs outside server subnet' work)\n     */\n    if (gw)\n    {\n        _gw = ntohl(*gw);\n        argv_printf(&argv, \"%s %s -net %s/%d %s -fib %d\", ROUTE_PATH, op,\n                    inet_ntop(AF_INET, &_dst, buf1, sizeof(buf1)), prefixlen,\n                    inet_ntop(AF_INET, &_gw, buf2, sizeof(buf2)), table);\n    }\n    else\n    {\n        argv_printf(&argv, \"%s %s -net %s/%d -iface %s -fib %d\", ROUTE_PATH, op,\n                    inet_ntop(AF_INET, &_dst, buf1, sizeof(buf1)), prefixlen,\n                    iface, table);\n    }\n\n    argv_msg(M_INFO, &argv);\n    bool status = openvpn_execve_check(&argv, NULL, 0, \"ERROR: FreeBSD route command failed\");\n\n    argv_free(&argv);\n\n    return (!status);\n}\n\nstatic int\nnet_route_v6(const char *op, const struct in6_addr *dst, int prefixlen, const struct in6_addr *gw,\n             const char *iface, uint32_t table, int metric)\n{\n    char buf1[INET6_ADDRSTRLEN], buf2[INET6_ADDRSTRLEN];\n    struct argv argv = argv_new();\n\n    ASSERT(gw || iface);\n\n    /* if we have a gateway (GW != NULL) install route to gateway IP\n     * if not, install \"connected\" route to interface\n     * (needed to make 'ifconfig-push IPs outside server subnet' work)\n     */\n    if (gw)\n    {\n        argv_printf(&argv, \"%s -6 %s -net %s/%d %s -fib %d\", ROUTE_PATH, op,\n                    inet_ntop(AF_INET6, dst, buf1, sizeof(buf1)), prefixlen,\n                    inet_ntop(AF_INET6, gw, buf2, sizeof(buf2)), table);\n    }\n    else\n    {\n        argv_printf(&argv, \"%s -6 %s -net %s/%d -iface %s -fib %d\", ROUTE_PATH, op,\n                    inet_ntop(AF_INET6, dst, buf1, sizeof(buf1)), prefixlen,\n                    iface, table);\n    }\n\n\n    argv_msg(M_INFO, &argv);\n    bool status = openvpn_execve_check(&argv, NULL, 0, \"ERROR: FreeBSD route command failed\");\n\n    argv_free(&argv);\n\n    return (!status);\n}\n\nint\nnet_route_v4_add(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen, const in_addr_t *gw,\n                 const char *iface, uint32_t table, int metric)\n{\n    return net_route_v4(\"add\", dst, prefixlen, gw, iface, table, metric);\n}\n\nint\nnet_route_v6_add(openvpn_net_ctx_t *ctx, const struct in6_addr *dst, int prefixlen,\n                 const struct in6_addr *gw, const char *iface, uint32_t table, int metric)\n{\n    return net_route_v6(\"add\", dst, prefixlen, gw, iface, table, metric);\n}\n\nint\nnet_route_v4_del(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen, const in_addr_t *gw,\n                 const char *iface, uint32_t table, int metric)\n{\n    return net_route_v4(\"del\", dst, prefixlen, gw, iface, table, metric);\n}\n\nint\nnet_route_v6_del(openvpn_net_ctx_t *ctx, const struct in6_addr *dst, int prefixlen,\n                 const struct in6_addr *gw, const char *iface, uint32_t table, int metric)\n{\n    return net_route_v6(\"del\", dst, prefixlen, gw, iface, table, metric);\n}\n\n#endif /* if defined(TARGET_FREEBSD) */\n"
  },
  {
    "path": "src/openvpn/networking_iproute2.c",
    "content": "/*\n *  Networking API implementation for iproute2\n *\n *  Copyright (C) 2018-2026 Antonio Quartulli <a@unstable.cc>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#if defined(TARGET_LINUX) && defined(ENABLE_IPROUTE)\n\n#include \"syshead.h\"\n\n#include \"argv.h\"\n#include \"networking.h\"\n#include \"misc.h\"\n#include \"openvpn.h\"\n#include \"run_command.h\"\n#include \"socket_util.h\"\n\n#include <stdbool.h>\n#include <netinet/in.h>\n\nint\nnet_ctx_init(struct context *c, openvpn_net_ctx_t *ctx)\n{\n    ctx->es = NULL;\n    if (c)\n    {\n        ctx->es = c->es;\n    }\n    ctx->gc = gc_new();\n\n    return 0;\n}\n\nvoid\nnet_ctx_reset(openvpn_net_ctx_t *ctx)\n{\n    gc_reset(&ctx->gc);\n}\n\nvoid\nnet_ctx_free(openvpn_net_ctx_t *ctx)\n{\n    gc_free(&ctx->gc);\n}\n\nint\nnet_iface_new(openvpn_net_ctx_t *ctx, const char *iface, const char *type, void *arg)\n{\n    struct argv argv = argv_new();\n\n    argv_printf(&argv, \"%s link add %s type %s\", iproute_path, iface, type);\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, ctx->es, S_FATAL, \"Linux ip link add failed\");\n\n    argv_free(&argv);\n\n    return 0;\n}\n\nint\nnet_iface_type(openvpn_net_ctx_t *ctx, const char *iface, char type[IFACE_TYPE_LEN_MAX])\n{\n    /* not supported by iproute2 */\n    msg(M_WARN, \"%s: operation not supported by iproute2 backend\", __func__);\n    return -1;\n}\n\nint\nnet_iface_del(openvpn_net_ctx_t *ctx, const char *iface)\n{\n    struct argv argv = argv_new();\n\n    argv_printf(&argv, \"%s link del %s\", iproute_path, iface);\n    openvpn_execve_check(&argv, ctx->es, 0, \"Linux ip link del failed\");\n\n    argv_free(&argv);\n\n    return 0;\n}\n\nint\nnet_iface_up(openvpn_net_ctx_t *ctx, const char *iface, bool up)\n{\n    struct argv argv = argv_new();\n\n    argv_printf(&argv, \"%s link set dev %s %s\", iproute_path, iface, up ? \"up\" : \"down\");\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, ctx->es, S_FATAL, \"Linux ip link set failed\");\n\n    argv_free(&argv);\n\n    return 0;\n}\n\nint\nnet_iface_mtu_set(openvpn_net_ctx_t *ctx, const char *iface, uint32_t mtu)\n{\n    struct argv argv = argv_new();\n\n    argv_printf(&argv, \"%s link set dev %s up mtu %d\", iproute_path, iface, mtu);\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, ctx->es, S_FATAL, \"Linux ip link set failed\");\n\n    argv_free(&argv);\n\n    return 0;\n}\n\nint\nnet_addr_ll_set(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface, uint8_t *addr)\n{\n    struct argv argv = argv_new();\n    int ret = 0;\n\n    argv_printf(&argv, \"%s link set addr \" MAC_FMT \" dev %s\", iproute_path, MAC_PRINT_ARG(addr),\n                iface);\n\n    argv_msg(M_INFO, &argv);\n    if (!openvpn_execve_check(&argv, ctx->es, 0, \"Linux ip link set addr failed\"))\n    {\n        ret = -1;\n    }\n\n    argv_free(&argv);\n\n    return ret;\n}\n\nint\nnet_addr_v4_add(openvpn_net_ctx_t *ctx, const char *iface, const in_addr_t *addr, int prefixlen)\n{\n    struct argv argv = argv_new();\n\n    const char *addr_str = print_in_addr_t(*addr, 0, &ctx->gc);\n\n    argv_printf(&argv, \"%s addr add dev %s %s/%d broadcast +\", iproute_path, iface, addr_str, prefixlen);\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, ctx->es, S_FATAL, \"Linux ip addr add failed\");\n\n    argv_free(&argv);\n\n    return 0;\n}\n\nint\nnet_addr_v6_add(openvpn_net_ctx_t *ctx, const char *iface, const struct in6_addr *addr,\n                int prefixlen)\n{\n    struct argv argv = argv_new();\n    char *addr_str = (char *)print_in6_addr(*addr, 0, &ctx->gc);\n\n    argv_printf(&argv, \"%s -6 addr add %s/%d dev %s\", iproute_path, addr_str, prefixlen, iface);\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, ctx->es, S_FATAL, \"Linux ip -6 addr add failed\");\n\n    argv_free(&argv);\n\n    return 0;\n}\n\nint\nnet_addr_v4_del(openvpn_net_ctx_t *ctx, const char *iface, const in_addr_t *addr, int prefixlen)\n{\n    struct argv argv = argv_new();\n    const char *addr_str = print_in_addr_t(*addr, 0, &ctx->gc);\n\n    argv_printf(&argv, \"%s addr del dev %s %s/%d\", iproute_path, iface, addr_str, prefixlen);\n\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, ctx->es, 0, \"Linux ip addr del failed\");\n\n    argv_free(&argv);\n\n    return 0;\n}\n\nint\nnet_addr_v6_del(openvpn_net_ctx_t *ctx, const char *iface, const struct in6_addr *addr,\n                int prefixlen)\n{\n    struct argv argv = argv_new();\n    char *addr_str = (char *)print_in6_addr(*addr, 0, &ctx->gc);\n\n    argv_printf(&argv, \"%s -6 addr del %s/%d dev %s\", iproute_path, addr_str, prefixlen, iface);\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, ctx->es, 0, \"Linux ip -6 addr del failed\");\n\n    argv_free(&argv);\n\n    return 0;\n}\n\nint\nnet_addr_ptp_v4_add(openvpn_net_ctx_t *ctx, const char *iface, const in_addr_t *local,\n                    const in_addr_t *remote)\n{\n    struct argv argv = argv_new();\n    const char *local_str = print_in_addr_t(*local, 0, &ctx->gc);\n    const char *remote_str = print_in_addr_t(*remote, 0, &ctx->gc);\n\n    argv_printf(&argv, \"%s addr add dev %s local %s peer %s\", iproute_path, iface, local_str,\n                remote_str);\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, ctx->es, S_FATAL, \"Linux ip addr add failed\");\n\n    argv_free(&argv);\n\n    return 0;\n}\n\nint\nnet_addr_ptp_v4_del(openvpn_net_ctx_t *ctx, const char *iface, const in_addr_t *local,\n                    const in_addr_t *remote)\n{\n    struct argv argv = argv_new();\n    const char *local_str = print_in_addr_t(*local, 0, &ctx->gc);\n    const char *remote_str = print_in_addr_t(*remote, 0, &ctx->gc);\n\n    argv_printf(&argv, \"%s addr del dev %s local %s peer %s\", iproute_path, iface, local_str,\n                remote_str);\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, ctx->es, 0, \"Linux ip addr del failed\");\n\n    argv_free(&argv);\n\n    return 0;\n}\n\nint\nnet_route_v4_add(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen, const in_addr_t *gw,\n                 const char *iface, uint32_t table, int metric)\n{\n    struct argv argv = argv_new();\n    const char *dst_str = print_in_addr_t(*dst, 0, &ctx->gc);\n    int ret = 0;\n\n    argv_printf(&argv, \"%s route add %s/%d\", iproute_path, dst_str, prefixlen);\n\n    if (metric > 0)\n    {\n        argv_printf_cat(&argv, \"metric %d\", metric);\n    }\n\n    if (iface)\n    {\n        argv_printf_cat(&argv, \"dev %s\", iface);\n    }\n\n    if (gw)\n    {\n        const char *gw_str = print_in_addr_t(*gw, 0, &ctx->gc);\n\n        argv_printf_cat(&argv, \"via %s\", gw_str);\n    }\n\n    argv_msg(D_ROUTE, &argv);\n    if (!openvpn_execve_check(&argv, ctx->es, 0, \"ERROR: Linux route add command failed\"))\n    {\n        ret = -1;\n    }\n\n    argv_free(&argv);\n\n    return ret;\n}\n\nint\nnet_route_v6_add(openvpn_net_ctx_t *ctx, const struct in6_addr *dst, int prefixlen,\n                 const struct in6_addr *gw, const char *iface, uint32_t table, int metric)\n{\n    struct argv argv = argv_new();\n    char *dst_str = (char *)print_in6_addr(*dst, 0, &ctx->gc);\n    int ret = 0;\n\n    argv_printf(&argv, \"%s -6 route add %s/%d dev %s\", iproute_path, dst_str, prefixlen, iface);\n\n    if (gw)\n    {\n        char *gw_str = (char *)print_in6_addr(*gw, 0, &ctx->gc);\n\n        argv_printf_cat(&argv, \"via %s\", gw_str);\n    }\n\n    if (metric > 0)\n    {\n        argv_printf_cat(&argv, \"metric %d\", metric);\n    }\n\n    argv_msg(D_ROUTE, &argv);\n    if (!openvpn_execve_check(&argv, ctx->es, 0, \"ERROR: Linux route -6 add command failed\"))\n    {\n        ret = -1;\n    }\n\n    argv_free(&argv);\n\n    return ret;\n}\n\nint\nnet_route_v4_del(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen, const in_addr_t *gw,\n                 const char *iface, uint32_t table, int metric)\n{\n    struct argv argv = argv_new();\n    const char *dst_str = print_in_addr_t(*dst, 0, &ctx->gc);\n    int ret = 0;\n\n    argv_printf(&argv, \"%s route del %s/%d\", iproute_path, dst_str, prefixlen);\n\n    if (metric > 0)\n    {\n        argv_printf_cat(&argv, \"metric %d\", metric);\n    }\n\n    argv_msg(D_ROUTE, &argv);\n    if (!openvpn_execve_check(&argv, ctx->es, 0, \"ERROR: Linux route delete command failed\"))\n    {\n        ret = -1;\n    }\n\n    argv_free(&argv);\n\n    return ret;\n}\n\nint\nnet_route_v6_del(openvpn_net_ctx_t *ctx, const struct in6_addr *dst, int prefixlen,\n                 const struct in6_addr *gw, const char *iface, uint32_t table, int metric)\n{\n    struct argv argv = argv_new();\n    char *dst_str = (char *)print_in6_addr(*dst, 0, &ctx->gc);\n    int ret = 0;\n\n    argv_printf(&argv, \"%s -6 route del %s/%d dev %s\", iproute_path, dst_str, prefixlen, iface);\n\n    if (gw)\n    {\n        char *gw_str = (char *)print_in6_addr(*gw, 0, &ctx->gc);\n\n        argv_printf_cat(&argv, \"via %s\", gw_str);\n    }\n\n    if (metric > 0)\n    {\n        argv_printf_cat(&argv, \"metric %d\", metric);\n    }\n\n    argv_msg(D_ROUTE, &argv);\n    if (!openvpn_execve_check(&argv, ctx->es, 0, \"ERROR: Linux route -6 del command failed\"))\n    {\n        ret = -1;\n    }\n\n    argv_free(&argv);\n\n    return ret;\n}\n\n/*\n * The following functions are not implemented in the iproute backend as it\n * uses the sitnl implementation from networking_sitnl.c.\n *\n * int\n * net_route_v4_best_gw(openvpn_net_ctx_t *ctx, const in_addr_t *dst,\n *                     in_addr_t *best_gw, char *best_iface)\n *\n * int\n * net_route_v6_best_gw(const struct in6_addr *dst,\n *                      struct in6_addr *best_gw, char *best_iface)\n */\n\n#endif /* ENABLE_IPROUTE && TARGET_LINUX */\n"
  },
  {
    "path": "src/openvpn/networking_iproute2.h",
    "content": "/*\n *  Generic interface to platform specific networking code\n *\n *  Copyright (C) 2016-2026 Antonio Quartulli <a@unstable.cc>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n\n#ifndef NETWORKING_IP_H_\n#define NETWORKING_IP_H_\n\n#include \"env_set.h\"\n\ntypedef char openvpn_net_iface_t;\n\nstruct openvpn_net_ctx\n{\n    struct env_set *es;\n    struct gc_arena gc;\n};\n\ntypedef struct openvpn_net_ctx openvpn_net_ctx_t;\n\n#endif /* NETWORKING_IP_H_ */\n"
  },
  {
    "path": "src/openvpn/networking_sitnl.c",
    "content": "/*\n *  Simplified Interface To NetLink\n *\n *  Copyright (C) 2016-2026 Antonio Quartulli <a@unstable.cc>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#ifdef TARGET_LINUX\n\n#include \"syshead.h\"\n\n#include \"dco.h\"\n#include \"errlevel.h\"\n#include \"fdmisc.h\"\n#include \"buffer.h\"\n#include \"misc.h\"\n#include \"networking.h\"\n#include \"proto.h\"\n#include \"route.h\"\n\n#include <errno.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <linux/netlink.h>\n#include <linux/rtnetlink.h>\n\n#define SNDBUF_SIZE (1024 * 2)\n#define RCVBUF_SIZE (1024 * 4)\n\n#define SITNL_ADDATTR(_msg, _max_size, _attr, _data, _size)          \\\n    {                                                                \\\n        if (sitnl_addattr(_msg, _max_size, _attr, _data, _size) < 0) \\\n        {                                                            \\\n            goto err;                                                \\\n        }                                                            \\\n    }\n\n#define SITNL_NEST(_msg, _max_size, _attr)              \\\n    ({                                                  \\\n        struct rtattr *_nest = sitnl_nlmsg_tail(_msg);  \\\n        SITNL_ADDATTR(_msg, _max_size, _attr, NULL, 0); \\\n        _nest;                                          \\\n    })\n\n#define SITNL_NEST_END(_msg, _nest)                                                        \\\n    {                                                                                      \\\n        _nest->rta_len = (unsigned short)((void *)sitnl_nlmsg_tail(_msg) - (void *)_nest); \\\n    }\n\n/* This function was originally implemented as a macro, but compiling with\n * gcc and -O3 was getting confused about the math and thus raising\n * security warnings on subsequent memcpy() calls.\n *\n * Converting the macro to a function was not enough, because gcc was still\n * inlining it and falling in the same math trap.\n *\n * The only way out to avoid any warning/error is to force the function to\n * not be inline'd.\n */\nstatic __attribute__((noinline)) void *\nsitnl_nlmsg_tail(const struct nlmsghdr *nlh)\n{\n    return (unsigned char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len);\n}\n\n/**\n * Generic address data structure used to pass addresses and prefixes as\n * argument to AF family agnostic functions\n */\ntypedef union\n{\n    in_addr_t ipv4;\n    struct in6_addr ipv6;\n} inet_address_t;\n\n/**\n * Link state request message\n */\nstruct sitnl_link_req\n{\n    struct nlmsghdr n;\n    struct ifinfomsg i;\n    char buf[256];\n};\n\n/**\n * Address request message\n */\nstruct sitnl_addr_req\n{\n    struct nlmsghdr n;\n    struct ifaddrmsg i;\n    char buf[256];\n};\n\n/**\n * Route request message\n */\nstruct sitnl_route_req\n{\n    struct nlmsghdr n;\n    struct rtmsg r;\n    char buf[256];\n};\n\ntypedef int (*sitnl_parse_reply_cb)(struct nlmsghdr *msg, void *arg);\n\n/**\n * Object returned by route request operation\n */\nstruct sitnl_route_data_cb\n{\n    unsigned int iface;\n    inet_address_t gw;\n};\n\n/**\n * Helper function used to easily add attributes to a rtnl message\n */\nstatic int\nsitnl_addattr(struct nlmsghdr *n, size_t maxlen, unsigned short type, const void *data, size_t alen)\n{\n    size_t len = RTA_LENGTH(alen);\n\n    if ((NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len)) > maxlen)\n    {\n        msg(M_WARN, \"%s: rtnl: message exceeded bound of %zu\", __func__, maxlen);\n        return -EMSGSIZE;\n    }\n\n    struct rtattr *rta = sitnl_nlmsg_tail(n);\n    rta->rta_type = type;\n    ASSERT(len <= USHRT_MAX);\n    rta->rta_len = (unsigned short)len;\n\n    if (!data)\n    {\n        memset(RTA_DATA(rta), 0, alen);\n    }\n    else\n    {\n        memcpy(RTA_DATA(rta), data, alen);\n    }\n\n    n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);\n\n    return 0;\n}\n\n/**\n * Open RTNL socket\n */\nstatic int\nsitnl_socket(void)\n{\n    int sndbuf = SNDBUF_SIZE;\n    int rcvbuf = RCVBUF_SIZE;\n    int fd;\n\n    fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);\n    if (fd < 0)\n    {\n        msg(M_WARN, \"%s: cannot open netlink socket\", __func__);\n        return fd;\n    }\n\n    /* set close on exec to avoid child processes access the socket */\n    set_cloexec(fd);\n\n    if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"%s: SO_SNDBUF\", __func__);\n        close(fd);\n        return -1;\n    }\n\n    if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"%s: SO_RCVBUF\", __func__);\n        close(fd);\n        return -1;\n    }\n\n    return fd;\n}\n\n/**\n * Bind socket to Netlink subsystem\n */\nstatic int\nsitnl_bind(int fd, uint32_t groups)\n{\n    socklen_t addr_len;\n    struct sockaddr_nl local;\n\n    CLEAR(local);\n\n    local.nl_family = AF_NETLINK;\n    local.nl_groups = groups;\n\n    if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"%s: cannot bind netlink socket\", __func__);\n        return -errno;\n    }\n\n    addr_len = sizeof(local);\n    if (getsockname(fd, (struct sockaddr *)&local, &addr_len) < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"%s: cannot getsockname\", __func__);\n        return -errno;\n    }\n\n    if (addr_len != sizeof(local))\n    {\n        msg(M_WARN, \"%s: wrong address length %d\", __func__, addr_len);\n        return -EINVAL;\n    }\n\n    if (local.nl_family != AF_NETLINK)\n    {\n        msg(M_WARN, \"%s: wrong address family %d\", __func__, local.nl_family);\n        return -EINVAL;\n    }\n\n    return 0;\n}\n\n/**\n * Send Netlink message and run callback on reply (if specified)\n */\nstatic int\nsitnl_send(struct nlmsghdr *payload, pid_t peer, unsigned int groups, sitnl_parse_reply_cb cb,\n           void *arg_cb)\n{\n    int fd, ret;\n    struct sockaddr_nl nladdr;\n    struct nlmsgerr *err;\n    struct nlmsghdr *h;\n    char buf[1024 * 16];\n    struct iovec iov = {\n        .iov_base = payload,\n        .iov_len = payload->nlmsg_len,\n    };\n    struct msghdr nlmsg = {\n        .msg_name = &nladdr,\n        .msg_namelen = sizeof(nladdr),\n        .msg_iov = &iov,\n        .msg_iovlen = 1,\n    };\n\n    CLEAR(nladdr);\n\n    nladdr.nl_family = AF_NETLINK;\n    nladdr.nl_pid = peer;\n    nladdr.nl_groups = groups;\n\n    /* NB: We currently do not verify seq and pid on answers.\n     * If we ever want to start with that we probably need to come up\n     * with something better than \"seconds since epoch\"...\n     */\n    payload->nlmsg_seq = (uint32_t)time(NULL);\n\n    /* no need to send reply */\n    if (!cb)\n    {\n        payload->nlmsg_flags |= NLM_F_ACK;\n    }\n\n    fd = sitnl_socket();\n    if (fd < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"%s: can't open rtnl socket\", __func__);\n        return -errno;\n    }\n\n    if (sitnl_bind(fd, 0) < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"%s: can't bind rtnl socket\", __func__);\n        ret = -errno;\n        goto out;\n    }\n\n    if (sendmsg(fd, &nlmsg, 0) < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"%s: rtnl: error on sendmsg()\", __func__);\n        ret = -errno;\n        goto out;\n    }\n\n    /* prepare buffer to store RTNL replies */\n    memset(buf, 0, sizeof(buf));\n    iov.iov_base = buf;\n\n    while (1)\n    {\n        /*\n         * iov_len is modified by recvmsg(), therefore has to be initialized before\n         * using it again\n         */\n        msg(D_RTNL, \"%s: checking for received messages\", __func__);\n        iov.iov_len = sizeof(buf);\n        ssize_t rcv_len = recvmsg(fd, &nlmsg, 0);\n        msg(D_RTNL, \"%s: rtnl: received %zd bytes\", __func__, rcv_len);\n        if (rcv_len < 0)\n        {\n            if ((errno == EINTR) || (errno == EAGAIN))\n            {\n                msg(D_RTNL, \"%s: interrupted call\", __func__);\n                continue;\n            }\n            msg(M_WARN | M_ERRNO, \"%s: rtnl: error on recvmsg()\", __func__);\n            ret = -errno;\n            goto out;\n        }\n\n        if (rcv_len == 0)\n        {\n            msg(M_WARN, \"%s: rtnl: socket reached unexpected EOF\", __func__);\n            ret = -EIO;\n            goto out;\n        }\n\n        if (nlmsg.msg_namelen != sizeof(nladdr))\n        {\n            msg(M_WARN, \"%s: sender address length: %u (expected %zu)\", __func__, nlmsg.msg_namelen,\n                sizeof(nladdr));\n            ret = -EIO;\n            goto out;\n        }\n\n        h = (struct nlmsghdr *)buf;\n        while (rcv_len >= (int)sizeof(*h))\n        {\n            uint32_t len = h->nlmsg_len;\n            ssize_t rem_len = len - sizeof(*h);\n\n            if ((rem_len < 0) || (len > rcv_len))\n            {\n                if (nlmsg.msg_flags & MSG_TRUNC)\n                {\n                    msg(M_WARN, \"%s: truncated message\", __func__);\n                    ret = -EIO;\n                    goto out;\n                }\n                msg(M_WARN, \"%s: malformed message: len=%u\", __func__, len);\n                ret = -EIO;\n                goto out;\n            }\n\n            /*            if (((int)nladdr.nl_pid != peer) || (h->nlmsg_pid != nladdr.nl_pid)\n             *               || (h->nlmsg_seq != seq))\n             *           {\n             *               rcv_len -= NLMSG_ALIGN(len);\n             *               h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));\n             *               msg(M_DEBUG, \"%s: skipping unrelated message. nl_pid:%d (peer:%d)\n             * nl_msg_pid:%d nl_seq:%d seq:%d\",\n             *                   __func__, (int)nladdr.nl_pid, peer, h->nlmsg_pid,\n             *                   h->nlmsg_seq, seq);\n             *               continue;\n             *           }\n             */\n\n            if (h->nlmsg_type == NLMSG_DONE)\n            {\n                ret = 0;\n                goto out;\n            }\n\n            if (h->nlmsg_type == NLMSG_ERROR)\n            {\n                err = (struct nlmsgerr *)NLMSG_DATA(h);\n                if (rem_len < (int)sizeof(struct nlmsgerr))\n                {\n                    msg(M_WARN, \"%s: ERROR truncated\", __func__);\n                    ret = -EIO;\n                }\n                else\n                {\n                    if (!err->error)\n                    {\n                        ret = 0;\n                        if (cb)\n                        {\n                            int r = cb(h, arg_cb);\n                            if (r <= 0)\n                            {\n                                ret = r;\n                            }\n                        }\n                    }\n                    else\n                    {\n                        msg(M_WARN, \"%s: rtnl: generic error (%d): %s\", __func__, err->error,\n                            strerror(-err->error));\n                        ret = err->error;\n                    }\n                }\n                goto out;\n            }\n\n            if (cb)\n            {\n                int r = cb(h, arg_cb);\n                if (r <= 0)\n                {\n                    ret = r;\n                    goto out;\n                }\n            }\n            else\n            {\n                msg(M_WARN, \"%s: RTNL: unexpected reply\", __func__);\n            }\n\n            rcv_len -= NLMSG_ALIGN(len);\n            h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));\n        }\n\n        if (nlmsg.msg_flags & MSG_TRUNC)\n        {\n            msg(M_WARN, \"%s: message truncated\", __func__);\n            continue;\n        }\n\n        if (rcv_len)\n        {\n            msg(M_WARN, \"%s: rtnl: %zd not parsed bytes\", __func__, rcv_len);\n            ret = -1;\n            goto out;\n        }\n    }\nout:\n    close(fd);\n\n    return ret;\n}\n\ntypedef struct\n{\n    size_t addr_size;\n    inet_address_t gw;\n    char iface[IFNAMSIZ];\n    bool default_only;\n    unsigned int table;\n} route_res_t;\n\nstatic int\nsitnl_route_save(struct nlmsghdr *n, void *arg)\n{\n    route_res_t *res = arg;\n    struct rtmsg *r = NLMSG_DATA(n);\n    struct rtattr *rta = RTM_RTA(r);\n    size_t len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*r));\n    unsigned int table, ifindex = 0;\n    void *gw = NULL;\n\n    /* filter-out non-zero dst prefixes */\n    if (res->default_only && r->rtm_dst_len != 0)\n    {\n        return 1;\n    }\n\n    /* route table, ignored with RTA_TABLE */\n    table = r->rtm_table;\n\n    while (RTA_OK(rta, len))\n    {\n        switch (rta->rta_type)\n        {\n            /* route interface */\n            case RTA_OIF:\n                ifindex = *(unsigned int *)RTA_DATA(rta);\n                break;\n\n            /* route prefix */\n            case RTA_DST:\n                break;\n\n            /* GW for the route */\n            case RTA_GATEWAY:\n                gw = RTA_DATA(rta);\n                break;\n\n            /* route table */\n            case RTA_TABLE:\n                table = *(unsigned int *)RTA_DATA(rta);\n                break;\n        }\n\n        rta = RTA_NEXT(rta, len);\n    }\n\n    /* filter out any route not coming from the selected table */\n    if (res->table && res->table != table)\n    {\n        return 1;\n    }\n\n    if (!if_indextoname(ifindex, res->iface))\n    {\n        msg(M_WARN | M_ERRNO, \"%s: rtnl: can't get ifname for index %d\", __func__, ifindex);\n        return -1;\n    }\n\n    if (gw)\n    {\n        memcpy(&res->gw, gw, res->addr_size);\n    }\n\n    return 0;\n}\n\nstatic int\nsitnl_route_best_gw(sa_family_t af_family, const inet_address_t *dst, void *best_gw,\n                    char *best_iface)\n{\n    struct sitnl_route_req req;\n    route_res_t res;\n    int ret = -EINVAL;\n\n    ASSERT(best_gw);\n    ASSERT(best_iface);\n\n    CLEAR(req);\n    CLEAR(res);\n\n    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.r));\n    req.n.nlmsg_type = RTM_GETROUTE;\n    req.n.nlmsg_flags = NLM_F_REQUEST;\n\n    ASSERT(af_family <= UCHAR_MAX);\n    req.r.rtm_family = (unsigned char)af_family;\n\n    switch (af_family)\n    {\n        case AF_INET:\n            res.addr_size = sizeof(in_addr_t);\n            /*\n             * kernel can't return 0.0.0.0/8 host route, dump all\n             * the routes and filter for 0.0.0.0/0 in cb()\n             */\n            if (!dst || !dst->ipv4)\n            {\n                req.n.nlmsg_flags |= NLM_F_DUMP;\n                res.default_only = true;\n                res.table = RT_TABLE_MAIN;\n            }\n            else\n            {\n                req.r.rtm_dst_len = 32;\n            }\n            break;\n\n        case AF_INET6:\n            res.addr_size = sizeof(struct in6_addr);\n            /* kernel can return ::/128 host route */\n            req.r.rtm_dst_len = 128;\n            break;\n\n        default:\n            /* unsupported */\n            return -EINVAL;\n    }\n\n    SITNL_ADDATTR(&req.n, sizeof(req), RTA_DST, dst, res.addr_size);\n\n    ret = sitnl_send(&req.n, 0, 0, sitnl_route_save, &res);\n    if (ret < 0)\n    {\n        goto err;\n    }\n\n    /* save result in output variables */\n    memcpy(best_gw, &res.gw, res.addr_size);\n    strncpy(best_iface, res.iface, IFNAMSIZ);\nerr:\n    return ret;\n}\n\n/* used by iproute2 implementation too */\nint\nnet_route_v6_best_gw(openvpn_net_ctx_t *ctx, const struct in6_addr *dst, struct in6_addr *best_gw,\n                     char *best_iface)\n{\n    inet_address_t dst_v6 = { 0 };\n    char buf[INET6_ADDRSTRLEN];\n    int ret;\n\n    if (dst)\n    {\n        dst_v6.ipv6 = *dst;\n    }\n\n    msg(D_ROUTE, \"%s query: dst %s\", __func__, inet_ntop(AF_INET6, &dst_v6.ipv6, buf, sizeof(buf)));\n\n    ret = sitnl_route_best_gw(AF_INET6, &dst_v6, best_gw, best_iface);\n    if (ret < 0)\n    {\n        return ret;\n    }\n\n    msg(D_ROUTE, \"%s result: via %s dev %s\", __func__,\n        inet_ntop(AF_INET6, best_gw, buf, sizeof(buf)), best_iface);\n\n    return ret;\n}\n\n/* used by iproute2 implementation too */\nint\nnet_route_v4_best_gw(openvpn_net_ctx_t *ctx, const in_addr_t *dst, in_addr_t *best_gw,\n                     char *best_iface)\n{\n    inet_address_t dst_v4 = { 0 };\n    char buf[INET_ADDRSTRLEN];\n    int ret;\n\n    if (dst)\n    {\n        dst_v4.ipv4 = htonl(*dst);\n    }\n\n    msg(D_ROUTE, \"%s query: dst %s\", __func__, inet_ntop(AF_INET, &dst_v4.ipv4, buf, sizeof(buf)));\n\n    ret = sitnl_route_best_gw(AF_INET, &dst_v4, best_gw, best_iface);\n    if (ret < 0)\n    {\n        return ret;\n    }\n\n    msg(D_ROUTE, \"%s result: via %s dev %s\", __func__,\n        inet_ntop(AF_INET, best_gw, buf, sizeof(buf)), best_iface);\n\n    /* result is expected in Host Order */\n    *best_gw = ntohl(*best_gw);\n\n    return ret;\n}\n\n#ifdef ENABLE_SITNL\n\nint\nnet_iface_up(openvpn_net_ctx_t *ctx, const char *iface, bool up)\n{\n    struct sitnl_link_req req;\n    int ifindex;\n\n    CLEAR(req);\n\n    if (!iface)\n    {\n        msg(M_WARN, \"%s: passed NULL interface\", __func__);\n        return -EINVAL;\n    }\n\n    ifindex = if_nametoindex(iface);\n    if (ifindex == 0)\n    {\n        msg(M_WARN, \"%s: rtnl: cannot get ifindex for %s: %s\", __func__, iface, strerror(errno));\n        return -ENOENT;\n    }\n\n    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));\n    req.n.nlmsg_flags = NLM_F_REQUEST;\n    req.n.nlmsg_type = RTM_NEWLINK;\n\n    req.i.ifi_family = AF_PACKET;\n    req.i.ifi_index = ifindex;\n    req.i.ifi_change |= IFF_UP;\n    if (up)\n    {\n        req.i.ifi_flags |= IFF_UP;\n    }\n    else\n    {\n        req.i.ifi_flags &= ~IFF_UP;\n    }\n\n    msg(M_INFO, \"%s: set %s %s\", __func__, iface, up ? \"up\" : \"down\");\n\n    return sitnl_send(&req.n, 0, 0, NULL, NULL);\n}\n\nint\nnet_iface_mtu_set(openvpn_net_ctx_t *ctx, const char *iface, uint32_t mtu)\n{\n    struct sitnl_link_req req;\n    int ifindex, ret = -1;\n\n    CLEAR(req);\n\n    ifindex = if_nametoindex(iface);\n    if (ifindex == 0)\n    {\n        msg(M_WARN | M_ERRNO, \"%s: rtnl: cannot get ifindex for %s\", __func__, iface);\n        return -1;\n    }\n\n    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));\n    req.n.nlmsg_flags = NLM_F_REQUEST;\n    req.n.nlmsg_type = RTM_NEWLINK;\n\n    req.i.ifi_family = AF_PACKET;\n    req.i.ifi_index = ifindex;\n\n    SITNL_ADDATTR(&req.n, sizeof(req), IFLA_MTU, &mtu, 4);\n\n    msg(M_INFO, \"%s: mtu %u for %s\", __func__, mtu, iface);\n\n    ret = sitnl_send(&req.n, 0, 0, NULL, NULL);\nerr:\n    return ret;\n}\n\nint\nnet_addr_ll_set(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface, uint8_t *addr)\n{\n    struct sitnl_link_req req;\n    int ifindex, ret = -1;\n\n    CLEAR(req);\n\n    ifindex = if_nametoindex(iface);\n    if (ifindex == 0)\n    {\n        msg(M_WARN | M_ERRNO, \"%s: rtnl: cannot get ifindex for %s\", __func__, iface);\n        return -1;\n    }\n\n    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));\n    req.n.nlmsg_flags = NLM_F_REQUEST;\n    req.n.nlmsg_type = RTM_NEWLINK;\n\n    req.i.ifi_family = AF_PACKET;\n    req.i.ifi_index = ifindex;\n\n    SITNL_ADDATTR(&req.n, sizeof(req), IFLA_ADDRESS, addr, OPENVPN_ETH_ALEN);\n\n    msg(M_INFO, \"%s: lladdr \" MAC_FMT \" for %s\", __func__, MAC_PRINT_ARG(addr), iface);\n\n    ret = sitnl_send(&req.n, 0, 0, NULL, NULL);\nerr:\n    return ret;\n}\n\nstatic int\nsitnl_addr_set(uint16_t cmd, uint16_t flags, int ifindex, sa_family_t af_family,\n               const inet_address_t *local, const inet_address_t *remote, int prefixlen)\n{\n    struct sitnl_addr_req req;\n    uint32_t size;\n    int ret = -EINVAL;\n\n    CLEAR(req);\n\n    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));\n    req.n.nlmsg_type = cmd;\n    req.n.nlmsg_flags = NLM_F_REQUEST | flags;\n\n    req.i.ifa_index = ifindex;\n    ASSERT(af_family <= UINT8_MAX);\n    req.i.ifa_family = (uint8_t)af_family;\n\n    switch (af_family)\n    {\n        case AF_INET:\n            size = sizeof(struct in_addr);\n            break;\n\n        case AF_INET6:\n            size = sizeof(struct in6_addr);\n            break;\n\n        default:\n            msg(M_WARN, \"%s: rtnl: unknown address family %d\", __func__, af_family);\n            return -EINVAL;\n    }\n\n    /* if no prefixlen has been specified, assume host address */\n    if (prefixlen == 0)\n    {\n        prefixlen = size * 8;\n    }\n    ASSERT(prefixlen <= UINT8_MAX);\n    req.i.ifa_prefixlen = (uint8_t)prefixlen;\n\n    if (remote)\n    {\n        SITNL_ADDATTR(&req.n, sizeof(req), IFA_ADDRESS, remote, size);\n    }\n\n    if (local)\n    {\n        SITNL_ADDATTR(&req.n, sizeof(req), IFA_LOCAL, local, size);\n    }\n\n    if (af_family == AF_INET && local && !remote && prefixlen <= 30)\n    {\n        inet_address_t broadcast = *local;\n        broadcast.ipv4 |= htonl(~netbits_to_netmask(prefixlen));\n        SITNL_ADDATTR(&req.n, sizeof(req), IFA_BROADCAST, &broadcast, size);\n    }\n\n    ret = sitnl_send(&req.n, 0, 0, NULL, NULL);\n    if (ret == -EEXIST)\n    {\n        ret = 0;\n    }\nerr:\n    return ret;\n}\n\nstatic int\nsitnl_addr_ptp_add(sa_family_t af_family, const char *iface, const inet_address_t *local,\n                   const inet_address_t *remote)\n{\n    int ifindex;\n\n    switch (af_family)\n    {\n        case AF_INET:\n        case AF_INET6:\n            break;\n\n        default:\n            return -EINVAL;\n    }\n\n    if (!iface)\n    {\n        msg(M_WARN, \"%s: passed NULL interface\", __func__);\n        return -EINVAL;\n    }\n\n    ifindex = if_nametoindex(iface);\n    if (ifindex == 0)\n    {\n        msg(M_WARN, \"%s: cannot get ifindex for %s: %s\", __func__, np(iface), strerror(errno));\n        return -ENOENT;\n    }\n\n    return sitnl_addr_set(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, ifindex, af_family, local,\n                          remote, 0);\n}\n\nstatic int\nsitnl_addr_ptp_del(sa_family_t af_family, const char *iface, const inet_address_t *local)\n{\n    int ifindex;\n\n    switch (af_family)\n    {\n        case AF_INET:\n        case AF_INET6:\n            break;\n\n        default:\n            return -EINVAL;\n    }\n\n    if (!iface)\n    {\n        msg(M_WARN, \"%s: passed NULL interface\", __func__);\n        return -EINVAL;\n    }\n\n    ifindex = if_nametoindex(iface);\n    if (ifindex == 0)\n    {\n        msg(M_WARN | M_ERRNO, \"%s: cannot get ifindex for %s\", __func__, iface);\n        return -ENOENT;\n    }\n\n    return sitnl_addr_set(RTM_DELADDR, 0, ifindex, af_family, local, NULL, 0);\n}\n\nstatic int\nsitnl_route_set(uint16_t cmd, uint16_t flags, int ifindex, sa_family_t af_family, const void *dst,\n                int prefixlen, const void *gw, enum rt_class_t table, int metric,\n                enum rt_scope_t scope, unsigned char protocol, unsigned char type)\n{\n    struct sitnl_route_req req;\n    int ret = -1, size;\n\n    CLEAR(req);\n\n    switch (af_family)\n    {\n        case AF_INET:\n            size = sizeof(in_addr_t);\n            break;\n\n        case AF_INET6:\n            size = sizeof(struct in6_addr);\n            break;\n\n        default:\n            return -EINVAL;\n    }\n\n    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.r));\n    req.n.nlmsg_type = cmd;\n    req.n.nlmsg_flags = NLM_F_REQUEST | flags;\n\n    ASSERT(af_family <= UCHAR_MAX);\n    req.r.rtm_family = (unsigned char)af_family;\n    req.r.rtm_scope = (unsigned char)scope;\n    req.r.rtm_protocol = protocol;\n    req.r.rtm_type = type;\n    ASSERT(prefixlen >= 0 && prefixlen <= UCHAR_MAX);\n    req.r.rtm_dst_len = (unsigned char)prefixlen;\n\n    if (table <= UCHAR_MAX)\n    {\n        req.r.rtm_table = (unsigned char)table;\n    }\n    else\n    {\n        req.r.rtm_table = RT_TABLE_UNSPEC;\n        SITNL_ADDATTR(&req.n, sizeof(req), RTA_TABLE, &table, 4);\n    }\n\n    if (dst)\n    {\n        SITNL_ADDATTR(&req.n, sizeof(req), RTA_DST, dst, size);\n    }\n\n    if (gw)\n    {\n        SITNL_ADDATTR(&req.n, sizeof(req), RTA_GATEWAY, gw, size);\n    }\n\n    if (ifindex > 0)\n    {\n        SITNL_ADDATTR(&req.n, sizeof(req), RTA_OIF, &ifindex, 4);\n    }\n\n    if (metric > 0)\n    {\n        SITNL_ADDATTR(&req.n, sizeof(req), RTA_PRIORITY, &metric, 4);\n    }\n\n    ret = sitnl_send(&req.n, 0, 0, NULL, NULL);\nerr:\n    return ret;\n}\n\nstatic int\nsitnl_addr_add(sa_family_t af_family, const char *iface, const inet_address_t *addr, int prefixlen)\n{\n    int ifindex;\n\n    switch (af_family)\n    {\n        case AF_INET:\n        case AF_INET6:\n            break;\n\n        default:\n            return -EINVAL;\n    }\n\n    if (!iface)\n    {\n        msg(M_WARN, \"%s: passed NULL interface\", __func__);\n        return -EINVAL;\n    }\n\n    ifindex = if_nametoindex(iface);\n    if (ifindex == 0)\n    {\n        msg(M_WARN | M_ERRNO, \"%s: rtnl: cannot get ifindex for %s\", __func__, iface);\n        return -ENOENT;\n    }\n\n    return sitnl_addr_set(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, ifindex, af_family, addr, NULL,\n                          prefixlen);\n}\n\nstatic int\nsitnl_addr_del(sa_family_t af_family, const char *iface, inet_address_t *addr, int prefixlen)\n{\n    int ifindex;\n\n    switch (af_family)\n    {\n        case AF_INET:\n        case AF_INET6:\n            break;\n\n        default:\n            return -EINVAL;\n    }\n\n    if (!iface)\n    {\n        msg(M_WARN, \"%s: passed NULL interface\", __func__);\n        return -EINVAL;\n    }\n\n    ifindex = if_nametoindex(iface);\n    if (ifindex == 0)\n    {\n        msg(M_WARN | M_ERRNO, \"%s: rtnl: cannot get ifindex for %s\", __func__, iface);\n        return -ENOENT;\n    }\n\n    return sitnl_addr_set(RTM_DELADDR, 0, ifindex, af_family, addr, NULL, prefixlen);\n}\n\nint\nnet_addr_v4_add(openvpn_net_ctx_t *ctx, const char *iface, const in_addr_t *addr, int prefixlen)\n{\n    inet_address_t addr_v4 = { 0 };\n    char buf[INET_ADDRSTRLEN];\n\n    if (!addr)\n    {\n        return -EINVAL;\n    }\n\n    addr_v4.ipv4 = htonl(*addr);\n\n    msg(M_INFO, \"%s: %s/%d dev %s\", __func__, inet_ntop(AF_INET, &addr_v4.ipv4, buf, sizeof(buf)),\n        prefixlen, iface);\n\n    return sitnl_addr_add(AF_INET, iface, &addr_v4, prefixlen);\n}\n\nint\nnet_addr_v6_add(openvpn_net_ctx_t *ctx, const char *iface, const struct in6_addr *addr,\n                int prefixlen)\n{\n    inet_address_t addr_v6 = { 0 };\n    char buf[INET6_ADDRSTRLEN];\n\n    if (!addr)\n    {\n        return -EINVAL;\n    }\n\n    addr_v6.ipv6 = *addr;\n\n    msg(M_INFO, \"%s: %s/%d dev %s\", __func__, inet_ntop(AF_INET6, &addr_v6.ipv6, buf, sizeof(buf)),\n        prefixlen, iface);\n\n    return sitnl_addr_add(AF_INET6, iface, &addr_v6, prefixlen);\n}\n\nint\nnet_addr_v4_del(openvpn_net_ctx_t *ctx, const char *iface, const in_addr_t *addr, int prefixlen)\n{\n    inet_address_t addr_v4 = { 0 };\n    char buf[INET_ADDRSTRLEN];\n\n    if (!addr)\n    {\n        return -EINVAL;\n    }\n\n    addr_v4.ipv4 = htonl(*addr);\n\n    msg(M_INFO, \"%s: %s dev %s\", __func__, inet_ntop(AF_INET, &addr_v4.ipv4, buf, sizeof(buf)),\n        iface);\n\n    return sitnl_addr_del(AF_INET, iface, &addr_v4, prefixlen);\n}\n\nint\nnet_addr_v6_del(openvpn_net_ctx_t *ctx, const char *iface, const struct in6_addr *addr,\n                int prefixlen)\n{\n    inet_address_t addr_v6 = { 0 };\n    char buf[INET6_ADDRSTRLEN];\n\n    if (!addr)\n    {\n        return -EINVAL;\n    }\n\n    addr_v6.ipv6 = *addr;\n\n    msg(M_INFO, \"%s: %s/%d dev %s\", __func__, inet_ntop(AF_INET6, &addr_v6.ipv6, buf, sizeof(buf)),\n        prefixlen, iface);\n\n    return sitnl_addr_del(AF_INET6, iface, &addr_v6, prefixlen);\n}\n\nint\nnet_addr_ptp_v4_add(openvpn_net_ctx_t *ctx, const char *iface, const in_addr_t *local,\n                    const in_addr_t *remote)\n{\n    inet_address_t local_v4 = { 0 };\n    inet_address_t remote_v4 = { 0 };\n    char buf1[INET_ADDRSTRLEN];\n    char buf2[INET_ADDRSTRLEN];\n\n    if (!local)\n    {\n        return -EINVAL;\n    }\n\n    local_v4.ipv4 = htonl(*local);\n\n    if (remote)\n    {\n        remote_v4.ipv4 = htonl(*remote);\n    }\n\n    msg(M_INFO, \"%s: %s peer %s dev %s\", __func__,\n        inet_ntop(AF_INET, &local_v4.ipv4, buf1, sizeof(buf1)),\n        inet_ntop(AF_INET, &remote_v4.ipv4, buf2, sizeof(buf2)), iface);\n\n    return sitnl_addr_ptp_add(AF_INET, iface, &local_v4, &remote_v4);\n}\n\nint\nnet_addr_ptp_v4_del(openvpn_net_ctx_t *ctx, const char *iface, const in_addr_t *local,\n                    const in_addr_t *remote)\n{\n    inet_address_t local_v4 = { 0 };\n    char buf[INET6_ADDRSTRLEN];\n\n\n    if (!local)\n    {\n        return -EINVAL;\n    }\n\n    local_v4.ipv4 = htonl(*local);\n\n    msg(M_INFO, \"%s: %s dev %s\", __func__, inet_ntop(AF_INET, &local_v4.ipv4, buf, sizeof(buf)),\n        iface);\n\n    return sitnl_addr_ptp_del(AF_INET, iface, &local_v4);\n}\n\nstatic int\nsitnl_route_add(const char *iface, sa_family_t af_family, const void *dst, int prefixlen,\n                const void *gw, uint32_t table, int metric)\n{\n    enum rt_scope_t scope = RT_SCOPE_UNIVERSE;\n    int ifindex = 0;\n\n    if (iface)\n    {\n        ifindex = if_nametoindex(iface);\n        if (ifindex == 0)\n        {\n            msg(M_WARN | M_ERRNO, \"%s: rtnl: can't get ifindex for %s\", __func__, iface);\n            return -ENOENT;\n        }\n    }\n\n    if (table == 0)\n    {\n        table = RT_TABLE_MAIN;\n    }\n\n    if (!gw && iface)\n    {\n        scope = RT_SCOPE_LINK;\n    }\n\n    return sitnl_route_set(RTM_NEWROUTE, NLM_F_CREATE, ifindex, af_family, dst, prefixlen, gw,\n                           table, metric, scope, RTPROT_BOOT, RTN_UNICAST);\n}\n\nint\nnet_route_v4_add(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen, const in_addr_t *gw,\n                 const char *iface, uint32_t table, int metric)\n{\n    in_addr_t *dst_ptr = NULL, *gw_ptr = NULL;\n    in_addr_t dst_be = 0, gw_be = 0;\n    char dst_str[INET_ADDRSTRLEN];\n    char gw_str[INET_ADDRSTRLEN];\n\n    if (dst)\n    {\n        dst_be = htonl(*dst);\n        dst_ptr = &dst_be;\n    }\n\n    if (gw)\n    {\n        gw_be = htonl(*gw);\n        gw_ptr = &gw_be;\n    }\n\n    msg(D_ROUTE, \"%s: %s/%d via %s dev %s table %d metric %d\", __func__,\n        inet_ntop(AF_INET, &dst_be, dst_str, sizeof(dst_str)), prefixlen,\n        inet_ntop(AF_INET, &gw_be, gw_str, sizeof(gw_str)), np(iface), table, metric);\n\n    return sitnl_route_add(iface, AF_INET, dst_ptr, prefixlen, gw_ptr, table, metric);\n}\n\nint\nnet_route_v6_add(openvpn_net_ctx_t *ctx, const struct in6_addr *dst, int prefixlen,\n                 const struct in6_addr *gw, const char *iface, uint32_t table, int metric)\n{\n    inet_address_t dst_v6 = { 0 };\n    inet_address_t gw_v6 = { 0 };\n    char dst_str[INET6_ADDRSTRLEN];\n    char gw_str[INET6_ADDRSTRLEN];\n\n    if (dst)\n    {\n        dst_v6.ipv6 = *dst;\n    }\n\n    if (gw)\n    {\n        gw_v6.ipv6 = *gw;\n    }\n\n    msg(D_ROUTE, \"%s: %s/%d via %s dev %s table %d metric %d\", __func__,\n        inet_ntop(AF_INET6, &dst_v6.ipv6, dst_str, sizeof(dst_str)), prefixlen,\n        inet_ntop(AF_INET6, &gw_v6.ipv6, gw_str, sizeof(gw_str)), np(iface), table, metric);\n\n    return sitnl_route_add(iface, AF_INET6, dst, prefixlen, gw, table, metric);\n}\n\nstatic int\nsitnl_route_del(const char *iface, sa_family_t af_family, inet_address_t *dst, int prefixlen,\n                inet_address_t *gw, uint32_t table, int metric)\n{\n    int ifindex = 0;\n\n    if (iface)\n    {\n        ifindex = if_nametoindex(iface);\n        if (ifindex == 0)\n        {\n            msg(M_WARN | M_ERRNO, \"%s: rtnl: can't get ifindex for %s\", __func__, iface);\n            return -ENOENT;\n        }\n    }\n\n    if (table == 0)\n    {\n        table = RT_TABLE_MAIN;\n    }\n\n    return sitnl_route_set(RTM_DELROUTE, 0, ifindex, af_family, dst, prefixlen, gw, table, metric,\n                           RT_SCOPE_NOWHERE, 0, 0);\n}\n\nint\nnet_route_v4_del(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen, const in_addr_t *gw,\n                 const char *iface, uint32_t table, int metric)\n{\n    inet_address_t dst_v4 = { 0 };\n    inet_address_t gw_v4 = { 0 };\n    char dst_str[INET_ADDRSTRLEN];\n    char gw_str[INET_ADDRSTRLEN];\n\n    if (dst)\n    {\n        dst_v4.ipv4 = htonl(*dst);\n    }\n\n    if (gw)\n    {\n        gw_v4.ipv4 = htonl(*gw);\n    }\n\n    msg(D_ROUTE, \"%s: %s/%d via %s dev %s table %d metric %d\", __func__,\n        inet_ntop(AF_INET, &dst_v4.ipv4, dst_str, sizeof(dst_str)), prefixlen,\n        inet_ntop(AF_INET, &gw_v4.ipv4, gw_str, sizeof(gw_str)), np(iface), table, metric);\n\n    return sitnl_route_del(iface, AF_INET, &dst_v4, prefixlen, &gw_v4, table, metric);\n}\n\nint\nnet_route_v6_del(openvpn_net_ctx_t *ctx, const struct in6_addr *dst, int prefixlen,\n                 const struct in6_addr *gw, const char *iface, uint32_t table, int metric)\n{\n    inet_address_t dst_v6 = { 0 };\n    inet_address_t gw_v6 = { 0 };\n    char dst_str[INET6_ADDRSTRLEN];\n    char gw_str[INET6_ADDRSTRLEN];\n\n    if (dst)\n    {\n        dst_v6.ipv6 = *dst;\n    }\n\n    if (gw)\n    {\n        gw_v6.ipv6 = *gw;\n    }\n\n    msg(D_ROUTE, \"%s: %s/%d via %s dev %s table %d metric %d\", __func__,\n        inet_ntop(AF_INET6, &dst_v6.ipv6, dst_str, sizeof(dst_str)), prefixlen,\n        inet_ntop(AF_INET6, &gw_v6.ipv6, gw_str, sizeof(gw_str)), np(iface), table, metric);\n\n    return sitnl_route_del(iface, AF_INET6, &dst_v6, prefixlen, &gw_v6, table, metric);\n}\n\n\nint\nnet_iface_new(openvpn_net_ctx_t *ctx, const char *iface, const char *type, void *arg)\n{\n    struct sitnl_link_req req = {};\n    int ret = -1;\n\n    ASSERT(iface);\n\n    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));\n    req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;\n    req.n.nlmsg_type = RTM_NEWLINK;\n\n    SITNL_ADDATTR(&req.n, sizeof(req), IFLA_IFNAME, iface, strlen(iface) + 1);\n\n    struct rtattr *linkinfo = SITNL_NEST(&req.n, sizeof(req), IFLA_LINKINFO);\n    SITNL_ADDATTR(&req.n, sizeof(req), IFLA_INFO_KIND, type, strlen(type) + 1);\n#if defined(ENABLE_DCO)\n    if (arg && (strcmp(type, OVPN_FAMILY_NAME) == 0))\n    {\n        dco_context_t *dco = arg;\n        struct rtattr *data = SITNL_NEST(&req.n, sizeof(req), IFLA_INFO_DATA);\n\n        /* the netlink format is uint8_t for this and using something\n         * other than uint8_t here (enum underlying type is undefined but\n         * commonly int) causes the values to be 0 when passed\n         * on big endian arch as we only take the (biggest endian) byte\n         * directly at the address\n         */\n        uint8_t ifmode = (uint8_t)dco->ifmode;\n        SITNL_ADDATTR(&req.n, sizeof(req), IFLA_OVPN_MODE, &ifmode, sizeof(uint8_t));\n        SITNL_NEST_END(&req.n, data);\n    }\n#endif\n    SITNL_NEST_END(&req.n, linkinfo);\n\n    req.i.ifi_family = AF_PACKET;\n\n    msg(D_ROUTE, \"%s: add %s type %s\", __func__, iface, type);\n\n    ret = sitnl_send(&req.n, 0, 0, NULL, NULL);\nerr:\n    return ret;\n}\n\nstatic int\nsitnl_parse_rtattr_flags(struct rtattr *tb[], size_t max, struct rtattr *rta, size_t len,\n                         unsigned short flags)\n{\n    unsigned short type;\n\n    memset(tb, 0, sizeof(struct rtattr *) * (max + 1));\n\n    while (RTA_OK(rta, len))\n    {\n        type = rta->rta_type & ~flags;\n\n        if ((type <= max) && (!tb[type]))\n        {\n            tb[type] = rta;\n        }\n\n        rta = RTA_NEXT(rta, len);\n    }\n\n    if (len)\n    {\n        msg(D_ROUTE, \"%s: %zu bytes not parsed! (rta_len=%u)\", __func__, len, rta->rta_len);\n    }\n\n    return 0;\n}\n\nstatic int\nsitnl_parse_rtattr(struct rtattr *tb[], size_t max, struct rtattr *rta, size_t len)\n{\n    return sitnl_parse_rtattr_flags(tb, max, rta, len, 0);\n}\n\n#define sitnl_parse_rtattr_nested(tb, max, rta) \\\n    (sitnl_parse_rtattr_flags(tb, max, RTA_DATA(rta), RTA_PAYLOAD(rta), NLA_F_NESTED))\n\nstatic int\nsitnl_type_save(struct nlmsghdr *n, void *arg)\n{\n    char *type = arg;\n    struct ifinfomsg *ifi = NLMSG_DATA(n);\n    struct rtattr *tb[IFLA_MAX + 1];\n    int ret;\n\n    ret = sitnl_parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n));\n    if (ret < 0)\n    {\n        return ret;\n    }\n\n    if (tb[IFLA_LINKINFO])\n    {\n        struct rtattr *tb_link[IFLA_INFO_MAX + 1];\n\n        ret = sitnl_parse_rtattr_nested(tb_link, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);\n        if (ret < 0)\n        {\n            return ret;\n        }\n\n        if (!tb_link[IFLA_INFO_KIND])\n        {\n            return -ENOENT;\n        }\n\n        strncpynt(type, RTA_DATA(tb_link[IFLA_INFO_KIND]), IFACE_TYPE_LEN_MAX);\n    }\n\n    return 0;\n}\n\nint\nnet_iface_type(openvpn_net_ctx_t *ctx, const char *iface, char type[IFACE_TYPE_LEN_MAX])\n{\n    struct sitnl_link_req req = {};\n    int ifindex = if_nametoindex(iface);\n\n    if (!ifindex)\n    {\n        return -errno;\n    }\n\n    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));\n    req.n.nlmsg_flags = NLM_F_REQUEST;\n    req.n.nlmsg_type = RTM_GETLINK;\n\n    req.i.ifi_family = AF_PACKET;\n    req.i.ifi_index = ifindex;\n\n    memset(type, 0, IFACE_TYPE_LEN_MAX);\n\n    int ret = sitnl_send(&req.n, 0, 0, sitnl_type_save, type);\n    if (ret < 0)\n    {\n        msg(D_ROUTE, \"%s: cannot retrieve iface %s: %s (%d)\", __func__, iface, strerror(-ret), ret);\n        return ret;\n    }\n\n    msg(D_ROUTE, \"%s: type of %s: %s\", __func__, iface, type);\n\n    return 0;\n}\n\nint\nnet_iface_del(openvpn_net_ctx_t *ctx, const char *iface)\n{\n    struct sitnl_link_req req = {};\n    int ifindex = if_nametoindex(iface);\n\n    if (!ifindex)\n    {\n        return -errno;\n    }\n\n    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));\n    req.n.nlmsg_flags = NLM_F_REQUEST;\n    req.n.nlmsg_type = RTM_DELLINK;\n\n    req.i.ifi_family = AF_PACKET;\n    req.i.ifi_index = ifindex;\n\n    msg(D_ROUTE, \"%s: delete %s\", __func__, iface);\n\n    return sitnl_send(&req.n, 0, 0, NULL, NULL);\n}\n\n#endif /* !ENABLE_SITNL */\n\n#endif /* TARGET_LINUX */\n"
  },
  {
    "path": "src/openvpn/networking_sitnl.h",
    "content": "/*\n *  Generic interface to platform specific networking code\n *\n *  Copyright (C) 2016-2026 Antonio Quartulli <a@unstable.cc>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n\n#ifndef NETWORKING_SITNL_H_\n#define NETWORKING_SITNL_H_\n\ntypedef char openvpn_net_iface_t;\ntypedef void *openvpn_net_ctx_t;\n\n#endif /* NETWORKING_SITNL_H_ */\n"
  },
  {
    "path": "src/openvpn/occ.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"occ.h\"\n#include \"forward.h\"\n#include \"memdbg.h\"\n\n\n/*\n * This random string identifies an OpenVPN\n * Configuration Control packet.\n * It should be of sufficient length and randomness\n * so as not to collide with other tunnel data.\n *\n * The OCC protocol is as follows:\n *\n * occ_magic -- (16 octets)\n *\n * type [OCC_REQUEST | OCC_REPLY] (1 octet)\n * null terminated options string if OCC_REPLY (variable)\n *\n * When encryption is used, the OCC packet\n * is encapsulated within the encrypted\n * envelope.\n *\n * OCC_STRING_SIZE must be set to sizeof (occ_magic)\n */\n\nconst uint8_t occ_magic[] = { 0x28, 0x7f, 0x34, 0x6b, 0xd4, 0xef, 0x7a, 0x81,\n                              0x2d, 0x56, 0xb8, 0xd3, 0xaf, 0xc5, 0x45, 0x9c };\n\nstatic const struct mtu_load_test mtu_load_test_sequence[] = {\n\n    { OCC_MTU_LOAD_REQUEST, -1000 },\n    { OCC_MTU_LOAD, -1000 },\n    { OCC_MTU_LOAD_REQUEST, -1000 },\n    { OCC_MTU_LOAD, -1000 },\n    { OCC_MTU_LOAD_REQUEST, -1000 },\n    { OCC_MTU_LOAD, -1000 },\n\n    { OCC_MTU_LOAD_REQUEST, -750 },\n    { OCC_MTU_LOAD, -750 },\n    { OCC_MTU_LOAD_REQUEST, -750 },\n    { OCC_MTU_LOAD, -750 },\n    { OCC_MTU_LOAD_REQUEST, -750 },\n    { OCC_MTU_LOAD, -750 },\n\n    { OCC_MTU_LOAD_REQUEST, -500 },\n    { OCC_MTU_LOAD, -500 },\n    { OCC_MTU_LOAD_REQUEST, -500 },\n    { OCC_MTU_LOAD, -500 },\n    { OCC_MTU_LOAD_REQUEST, -500 },\n    { OCC_MTU_LOAD, -500 },\n\n    { OCC_MTU_LOAD_REQUEST, -400 },\n    { OCC_MTU_LOAD, -400 },\n    { OCC_MTU_LOAD_REQUEST, -400 },\n    { OCC_MTU_LOAD, -400 },\n    { OCC_MTU_LOAD_REQUEST, -400 },\n    { OCC_MTU_LOAD, -400 },\n\n    { OCC_MTU_LOAD_REQUEST, -300 },\n    { OCC_MTU_LOAD, -300 },\n    { OCC_MTU_LOAD_REQUEST, -300 },\n    { OCC_MTU_LOAD, -300 },\n    { OCC_MTU_LOAD_REQUEST, -300 },\n    { OCC_MTU_LOAD, -300 },\n\n    { OCC_MTU_LOAD_REQUEST, -200 },\n    { OCC_MTU_LOAD, -200 },\n    { OCC_MTU_LOAD_REQUEST, -200 },\n    { OCC_MTU_LOAD, -200 },\n    { OCC_MTU_LOAD_REQUEST, -200 },\n    { OCC_MTU_LOAD, -200 },\n\n    { OCC_MTU_LOAD_REQUEST, -150 },\n    { OCC_MTU_LOAD, -150 },\n    { OCC_MTU_LOAD_REQUEST, -150 },\n    { OCC_MTU_LOAD, -150 },\n    { OCC_MTU_LOAD_REQUEST, -150 },\n    { OCC_MTU_LOAD, -150 },\n\n    { OCC_MTU_LOAD_REQUEST, -100 },\n    { OCC_MTU_LOAD, -100 },\n    { OCC_MTU_LOAD_REQUEST, -100 },\n    { OCC_MTU_LOAD, -100 },\n    { OCC_MTU_LOAD_REQUEST, -100 },\n    { OCC_MTU_LOAD, -100 },\n\n    { OCC_MTU_LOAD_REQUEST, -50 },\n    { OCC_MTU_LOAD, -50 },\n    { OCC_MTU_LOAD_REQUEST, -50 },\n    { OCC_MTU_LOAD, -50 },\n    { OCC_MTU_LOAD_REQUEST, -50 },\n    { OCC_MTU_LOAD, -50 },\n\n    { OCC_MTU_LOAD_REQUEST, 0 },\n    { OCC_MTU_LOAD, 0 },\n    { OCC_MTU_LOAD_REQUEST, 0 },\n    { OCC_MTU_LOAD, 0 },\n    { OCC_MTU_LOAD_REQUEST, 0 },\n    { OCC_MTU_LOAD, 0 },\n\n    { OCC_MTU_REQUEST, 0 },\n    { OCC_MTU_REQUEST, 0 },\n    { OCC_MTU_REQUEST, 0 },\n    { OCC_MTU_REQUEST, 0 },\n    { OCC_MTU_REQUEST, 0 },\n    { OCC_MTU_REQUEST, 0 },\n    { OCC_MTU_REQUEST, 0 },\n    { OCC_MTU_REQUEST, 0 },\n    { OCC_MTU_REQUEST, 0 },\n    { OCC_MTU_REQUEST, 0 },\n\n    { -1, 0 }\n};\n\nvoid\ncheck_send_occ_req_dowork(struct context *c)\n{\n    if (++c->c2.occ_n_tries >= OCC_N_TRIES)\n    {\n        if (c->options.ce.remote)\n        {\n            /*\n             * No OCC_REPLY from peer after repeated attempts.\n             * Give up.\n             */\n            msg(D_SHOW_OCC,\n                \"NOTE: failed to obtain options consistency info from peer -- \"\n                \"this could occur if the remote peer is running a version of \" PACKAGE_NAME\n                \" before 1.5-beta8 or if there is a network connectivity problem, and will not necessarily prevent \" PACKAGE_NAME\n                \" from running (\" counter_format \" bytes received from peer, \" counter_format\n                \" bytes authenticated data channel traffic) -- you can disable the options consistency \"\n                \"check with --disable-occ.\",\n                c->c2.link_read_bytes, c->c2.link_read_bytes_auth);\n        }\n        event_timeout_clear(&c->c2.occ_interval);\n    }\n    else\n    {\n        c->c2.occ_op = OCC_REQUEST;\n\n        /*\n         * If we don't hear back from peer, send another\n         * OCC_REQUEST in OCC_INTERVAL_SECONDS.\n         */\n        event_timeout_reset(&c->c2.occ_interval);\n    }\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\nvoid\ncheck_send_occ_load_test_dowork(struct context *c)\n{\n    if (connection_established(c))\n    {\n        const struct mtu_load_test *entry;\n\n        if (!c->c2.occ_mtu_load_n_tries)\n        {\n            msg(M_INFO,\n                \"NOTE: Beginning empirical MTU test -- results should be available in 3 to 4 minutes.\");\n        }\n\n        entry = &mtu_load_test_sequence[c->c2.occ_mtu_load_n_tries++];\n        if (entry->op >= 0)\n        {\n            c->c2.occ_op = entry->op;\n            size_t payload_size =\n                frame_calculate_payload_size(&c->c2.frame, &c->options, &c->c1.ks.key_type);\n            size_t header_size =\n                frame_calculate_protocol_header_size(&c->c1.ks.key_type, &c->options, false);\n\n            c->c2.occ_mtu_load_size = payload_size + header_size;\n        }\n        else\n        {\n            msg(M_INFO, \"NOTE: failed to empirically measure MTU (requires \" PACKAGE_NAME\n                        \" 1.5 or higher at other end of connection).\");\n            event_timeout_clear(&c->c2.occ_mtu_load_test_interval);\n            c->c2.occ_mtu_load_n_tries = 0;\n        }\n    }\n}\n\nvoid\ncheck_send_occ_msg_dowork(struct context *c)\n{\n    bool doit = false;\n\n    c->c2.buf = c->c2.buffers->aux_buf;\n    ASSERT(buf_init(&c->c2.buf, c->c2.frame.buf.headroom));\n    ASSERT(buf_safe(&c->c2.buf, c->c2.frame.buf.payload_size));\n    ASSERT(buf_write(&c->c2.buf, occ_magic, OCC_STRING_SIZE));\n\n    switch (c->c2.occ_op)\n    {\n        case OCC_REQUEST:\n            if (!buf_write_u8(&c->c2.buf, OCC_REQUEST))\n            {\n                break;\n            }\n            dmsg(D_PACKET_CONTENT, \"SENT OCC_REQUEST\");\n            doit = true;\n            break;\n\n        case OCC_REPLY:\n            if (!c->c2.options_string_local)\n            {\n                break;\n            }\n            if (!buf_write_u8(&c->c2.buf, OCC_REPLY))\n            {\n                break;\n            }\n            if (!buf_write(&c->c2.buf, c->c2.options_string_local,\n                           strlen(c->c2.options_string_local) + 1))\n            {\n                break;\n            }\n            dmsg(D_PACKET_CONTENT, \"SENT OCC_REPLY\");\n            doit = true;\n            break;\n\n        case OCC_MTU_REQUEST:\n            if (!buf_write_u8(&c->c2.buf, OCC_MTU_REQUEST))\n            {\n                break;\n            }\n            dmsg(D_PACKET_CONTENT, \"SENT OCC_MTU_REQUEST\");\n            doit = true;\n            break;\n\n        case OCC_MTU_REPLY:\n            if (!buf_write_u8(&c->c2.buf, OCC_MTU_REPLY))\n            {\n                break;\n            }\n            if (!buf_write_u16(&c->c2.buf, c->c2.max_recv_size_local))\n            {\n                break;\n            }\n            if (!buf_write_u16(&c->c2.buf, c->c2.max_send_size_local))\n            {\n                break;\n            }\n            dmsg(D_PACKET_CONTENT, \"SENT OCC_MTU_REPLY\");\n            doit = true;\n            break;\n\n        case OCC_MTU_LOAD_REQUEST:\n            if (!buf_write_u8(&c->c2.buf, OCC_MTU_LOAD_REQUEST))\n            {\n                break;\n            }\n            if (!buf_write_u16(&c->c2.buf, c->c2.occ_mtu_load_size))\n            {\n                break;\n            }\n            dmsg(D_PACKET_CONTENT, \"SENT OCC_MTU_LOAD_REQUEST\");\n            doit = true;\n            break;\n\n        case OCC_MTU_LOAD:\n        {\n            int need_to_add;\n\n            if (!buf_write_u8(&c->c2.buf, OCC_MTU_LOAD))\n            {\n                break;\n            }\n            size_t proto_hdr, payload_hdr;\n            const struct key_type *kt = &c->c1.ks.key_type;\n\n            /* OCC message have comp/fragment headers but not ethernet headers */\n            payload_hdr = frame_calculate_payload_overhead(0, &c->options, kt);\n\n            /* Since we do not know the payload size we just pass 0 as size here */\n            proto_hdr = frame_calculate_protocol_header_size(kt, &c->options, false);\n\n            need_to_add = min_int(c->c2.occ_mtu_load_size, c->c2.frame.buf.payload_size)\n                          - OCC_STRING_SIZE - sizeof(uint8_t) /* occ opcode */\n                          - payload_hdr - proto_hdr;\n\n            while (need_to_add > 0)\n            {\n                /*\n                 * Fill the load test packet with pseudo-random bytes.\n                 */\n                if (!buf_write_u8(&c->c2.buf, get_random() & 0xFF))\n                {\n                    break;\n                }\n                --need_to_add;\n            }\n            dmsg(D_PACKET_CONTENT, \"SENT OCC_MTU_LOAD min_int(%d,%d)-%d-%d-%d-%d) size=%d\",\n                 c->c2.occ_mtu_load_size, c->c2.frame.buf.payload_size, OCC_STRING_SIZE,\n                 (int)sizeof(uint8_t), (int)payload_hdr, (int)proto_hdr, BLEN(&c->c2.buf));\n            doit = true;\n        }\n        break;\n\n        case OCC_EXIT:\n            if (!buf_write_u8(&c->c2.buf, OCC_EXIT))\n            {\n                break;\n            }\n            dmsg(D_PACKET_CONTENT, \"SENT OCC_EXIT\");\n            doit = true;\n            break;\n    }\n\n    if (doit)\n    {\n        /*\n         * We will treat the packet like any other outgoing packet,\n         * compress, encrypt, sign, etc.\n         */\n        encrypt_sign(c, true);\n    }\n\n    c->c2.occ_op = -1;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nvoid\nprocess_received_occ_msg(struct context *c)\n{\n    ASSERT(buf_advance(&c->c2.buf, OCC_STRING_SIZE));\n    switch (buf_read_u8(&c->c2.buf))\n    {\n        case OCC_REQUEST:\n            dmsg(D_PACKET_CONTENT, \"RECEIVED OCC_REQUEST\");\n            c->c2.occ_op = OCC_REPLY;\n            break;\n\n        case OCC_MTU_REQUEST:\n            dmsg(D_PACKET_CONTENT, \"RECEIVED OCC_MTU_REQUEST\");\n            c->c2.occ_op = OCC_MTU_REPLY;\n            break;\n\n        case OCC_MTU_LOAD_REQUEST:\n            dmsg(D_PACKET_CONTENT, \"RECEIVED OCC_MTU_LOAD_REQUEST\");\n            c->c2.occ_mtu_load_size = buf_read_u16(&c->c2.buf);\n            if (c->c2.occ_mtu_load_size >= 0)\n            {\n                c->c2.occ_op = OCC_MTU_LOAD;\n            }\n            break;\n\n        case OCC_REPLY:\n            dmsg(D_PACKET_CONTENT, \"RECEIVED OCC_REPLY\");\n            if (c->options.occ && !TLS_MODE(c) && c->c2.options_string_remote)\n            {\n                if (!options_cmp_equal_safe((char *)BPTR(&c->c2.buf), c->c2.options_string_remote,\n                                            c->c2.buf.len))\n                {\n                    options_warning_safe((char *)BPTR(&c->c2.buf), c->c2.options_string_remote,\n                                         c->c2.buf.len);\n                }\n            }\n            event_timeout_clear(&c->c2.occ_interval);\n            break;\n\n        case OCC_MTU_REPLY:\n            dmsg(D_PACKET_CONTENT, \"RECEIVED OCC_MTU_REPLY\");\n            c->c2.max_recv_size_remote = buf_read_u16(&c->c2.buf);\n            c->c2.max_send_size_remote = buf_read_u16(&c->c2.buf);\n            if (c->options.mtu_test && c->c2.max_recv_size_remote > 0\n                && c->c2.max_send_size_remote > 0)\n            {\n                msg(M_INFO,\n                    \"NOTE: Empirical MTU test completed [Tried,Actual] local->remote=[%d,%d] remote->local=[%d,%d]\",\n                    c->c2.max_send_size_local, c->c2.max_recv_size_remote,\n                    c->c2.max_send_size_remote, c->c2.max_recv_size_local);\n                if (!c->options.ce.fragment && (proto_is_dgram(c->options.ce.proto))\n                    && c->c2.max_send_size_local > TUN_MTU_MIN\n                    && (c->c2.max_recv_size_remote < c->c2.max_send_size_local\n                        || c->c2.max_recv_size_local < c->c2.max_send_size_remote))\n                {\n                    msg(M_INFO,\n                        \"NOTE: This connection is unable to accommodate a UDP packet size of %d. Consider using --fragment or --mssfix options as a workaround.\",\n                        c->c2.max_send_size_local);\n                }\n            }\n            event_timeout_clear(&c->c2.occ_mtu_load_test_interval);\n            break;\n\n        case OCC_EXIT:\n            dmsg(D_STREAM_ERRORS, \"OCC exit message received by peer\");\n            register_signal(c->sig, SIGUSR1, \"remote-exit\");\n            break;\n    }\n    c->c2.buf.len = 0; /* don't pass packet on */\n}\n"
  },
  {
    "path": "src/openvpn/occ.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef OCC_H\n#define OCC_H\n\n#include \"forward.h\"\n\n/* OCC_STRING_SIZE must be set to sizeof (occ_magic) */\n#define OCC_STRING_SIZE 16\n\n/*\n * OCC (OpenVPN Configuration Control) protocol opcodes.\n */\n\n#define OCC_REQUEST 0 /* request options string from peer */\n#define OCC_REPLY   1 /* deliver options string to peer */\n\n/*\n * Send an OCC_REQUEST once every OCC_INTERVAL\n * seconds until a reply is received.\n *\n * If we haven't received a reply after\n * OCC_N_TRIES, give up.\n */\n#define OCC_INTERVAL_SECONDS 10\n#define OCC_N_TRIES          12\n\n/*\n * Other OCC protocol opcodes used to estimate the MTU empirically.\n */\n#define OCC_MTU_LOAD_REQUEST 2 /* Ask peer to send a big packet to us */\n#define OCC_MTU_LOAD         3 /* Send a big packet to peer */\n#define OCC_MTU_REQUEST                                           \\\n    4                          /* Ask peer to tell us the largest \\\n                                * packet it has received from us so far */\n#define OCC_MTU_REPLY 5        /* Send largest packet size to peer */\n\n/*\n * Process one command from mtu_load_test_sequence\n * once every n seconds, if --mtu-test is specified.\n */\n#define OCC_MTU_LOAD_INTERVAL_SECONDS 3\n\n/*\n * Send an exit message to remote.\n */\n#define OCC_EXIT 6\n\n/*\n * Used to conduct a load test command sequence\n * of UDP connection for empirical MTU measurement.\n */\nstruct mtu_load_test\n{\n    int op;    /* OCC opcode to send to peer */\n    int delta; /* determine packet size to send by using\n                * this delta against currently\n                * configured MTU */\n};\n\nextern const uint8_t occ_magic[];\n\nstatic inline bool\nis_occ_msg(const struct buffer *buf)\n{\n    return buf_string_match_head(buf, occ_magic, OCC_STRING_SIZE);\n}\n\nvoid process_received_occ_msg(struct context *c);\n\nvoid check_send_occ_req_dowork(struct context *c);\n\nvoid check_send_occ_load_test_dowork(struct context *c);\n\nvoid check_send_occ_msg_dowork(struct context *c);\n\n/*\n * Inline functions\n */\n\nstatic inline int\nocc_reset_op(void)\n{\n    return -1;\n}\n\n/*\n * Should we send an OCC_REQUEST message?\n */\nstatic inline void\ncheck_send_occ_req(struct context *c)\n{\n    if (event_timeout_defined(&c->c2.occ_interval)\n        && event_timeout_trigger(&c->c2.occ_interval, &c->c2.timeval,\n                                 (!TO_LINK_DEF(c) && c->c2.occ_op < 0) ? ETT_DEFAULT : 0))\n    {\n        check_send_occ_req_dowork(c);\n    }\n}\n\n/*\n * Should we send an MTU load test?\n */\nstatic inline void\ncheck_send_occ_load_test(struct context *c)\n{\n    if (event_timeout_defined(&c->c2.occ_mtu_load_test_interval)\n        && event_timeout_trigger(&c->c2.occ_mtu_load_test_interval, &c->c2.timeval,\n                                 (!TO_LINK_DEF(c) && c->c2.occ_op < 0) ? ETT_DEFAULT : 0))\n    {\n        check_send_occ_load_test_dowork(c);\n    }\n}\n\n/*\n * Should we send an OCC message?\n */\nstatic inline void\ncheck_send_occ_msg(struct context *c)\n{\n    if (c->c2.occ_op >= 0)\n    {\n        if (!TO_LINK_DEF(c))\n        {\n            check_send_occ_msg_dowork(c);\n        }\n        else\n        {\n            tv_clear(&c->c2.timeval); /* ZERO-TIMEOUT */\n        }\n    }\n}\n\n/**\n * Small helper function to determine if we should send the exit notification\n * via control channel.\n * @return control channel exit message should be used */\nstatic inline bool\ncc_exit_notify_enabled(struct context *c)\n{\n    /* Check if we have TLS active at all */\n    if (!c->c2.tls_multi)\n    {\n        return false;\n    }\n\n    const struct key_state *ks = get_primary_key(c->c2.tls_multi);\n    return (ks->crypto_options.flags & CO_USE_CC_EXIT_NOTIFY);\n}\n#endif /* ifndef OCC_H */\n"
  },
  {
    "path": "src/openvpn/openssl_compat.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * OpenSSL compatibility stub\n *\n * This file provide compatibility stubs for the OpenSSL libraries\n * prior to version 1.1. This version introduces many changes in the\n * library interface, including the fact that various objects and\n * structures are not fully opaque.\n */\n\n#ifndef OPENSSL_COMPAT_H_\n#define OPENSSL_COMPAT_H_\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"buffer.h\"\n\n#include <openssl/rsa.h>\n#include <openssl/ssl.h>\n#include <openssl/x509.h>\n#include <openssl/err.h>\n\n/* Functionality missing in 1.1.0 */\n#if OPENSSL_VERSION_NUMBER < 0x10101000L && !defined(ENABLE_CRYPTO_WOLFSSL)\n#define SSL_CTX_set1_groups SSL_CTX_set1_curves\n#endif\n\n/* Functionality missing in LibreSSL before 3.5 */\n#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3050000fL\n/**\n * Destroy a X509 object\n *\n * @param obj                X509 object\n */\nstatic inline void\nX509_OBJECT_free(X509_OBJECT *obj)\n{\n    if (obj)\n    {\n        X509_OBJECT_free_contents(obj);\n        OPENSSL_free(obj);\n    }\n}\n\n#define EVP_CTRL_AEAD_SET_TAG EVP_CTRL_GCM_SET_TAG\n#define EVP_CTRL_AEAD_GET_TAG EVP_CTRL_GCM_GET_TAG\n#endif\n\n#if defined(LIBRESSL_VERSION_NUMBER)\n#define RSA_F_RSA_OSSL_PRIVATE_ENCRYPT RSA_F_RSA_EAY_PRIVATE_ENCRYPT\n#endif\n\n#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3050400fL \\\n    || defined(OPENSSL_IS_AWSLC)\n#define SSL_get_peer_tmp_key SSL_get_server_tmp_key\n#endif\n\n/* Functionality missing in 1.1.1 */\n#if OPENSSL_VERSION_NUMBER < 0x30000000L && !defined(OPENSSL_NO_EC)\n\n/* Note that this is not a perfect emulation of the new function but\n * is good enough for our case of printing certificate details during\n * handshake */\nstatic inline int\nEVP_PKEY_get_group_name(EVP_PKEY *pkey, char *gname, size_t gname_sz, size_t *gname_len)\n{\n    const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey);\n    if (ec == NULL)\n    {\n        return 0;\n    }\n    const EC_GROUP *group = EC_KEY_get0_group(ec);\n    int nid = EC_GROUP_get_curve_name(group);\n\n    if (nid == 0)\n    {\n        return 0;\n    }\n    const char *curve = OBJ_nid2sn(nid);\n    if (!curve)\n    {\n        curve = \"(error fetching curve name)\";\n    }\n\n    strncpynt(gname, curve, gname_sz);\n\n    /* strncpynt ensures null termination so just strlen is fine here */\n    *gname_len = strlen(curve);\n    return 1;\n}\n#endif /* if OPENSSL_VERSION_NUMBER < 0x30000000L && !defined(OPENSSL_NO_EC) */\n\n#if OPENSSL_VERSION_NUMBER < 0x30000000L\n#define EVP_MD_get0_name        EVP_MD_name\n#define EVP_CIPHER_get0_name    EVP_CIPHER_name\n#define EVP_CIPHER_CTX_get_mode EVP_CIPHER_CTX_mode\n\n/** Reduce SSL_CTX_new_ex() to SSL_CTX_new() for OpenSSL < 3 */\n#define SSL_CTX_new_ex(libctx, propq, method) SSL_CTX_new((method))\n\n/* Some safe typedefs to avoid too many ifdefs */\ntypedef void OSSL_LIB_CTX;\ntypedef void OSSL_PROVIDER;\n\n/* Mimics the functions but only when the default context without\n * options is chosen */\nstatic inline const EVP_CIPHER *\nEVP_CIPHER_fetch(void *ctx, const char *algorithm, const char *properties)\n{\n    ASSERT(!ctx);\n    ASSERT(!properties);\n    return EVP_get_cipherbyname(algorithm);\n}\n\nstatic inline const EVP_MD *\nEVP_MD_fetch(void *ctx, const char *algorithm, const char *properties)\n{\n    ASSERT(!ctx);\n    ASSERT(!properties);\n    return EVP_get_digestbyname(algorithm);\n}\n\nstatic inline void\nEVP_CIPHER_free(const EVP_CIPHER *cipher)\n{\n    /* OpenSSL 1.1.1 and lower use only const EVP_CIPHER, nothing to free */\n}\n\nstatic inline void\nEVP_MD_free(const EVP_MD *md)\n{\n    /* OpenSSL 1.1.1 and lower use only const EVP_MD, nothing to free */\n}\n\nstatic inline unsigned long\nERR_get_error_all(const char **file, int *line, const char **func, const char **data, int *flags)\n{\n    static const char *empty = \"\";\n    *func = empty;\n    unsigned long err = ERR_get_error_line_data(file, line, data, flags);\n    return err;\n}\n\n#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */\n\n#if OPENSSL_VERSION_NUMBER < 0x30500000 \\\n    && (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER > 0x3050400fL)\nstatic inline int\nSSL_get0_peer_signature_name(SSL *ssl, const char **sigalg)\n{\n    int peer_sig_nid;\n    if (SSL_get_peer_signature_nid(ssl, &peer_sig_nid) && peer_sig_nid != NID_undef)\n    {\n        *sigalg = OBJ_nid2sn(peer_sig_nid);\n        return 1;\n    }\n    return 0;\n}\n#elif defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER <= 0x3050400fL\n/* The older LibreSSL version do not implement any variant of getting the peer\n * signature */\nstatic inline int\nSSL_get0_peer_signature_name(const SSL *ssl, const char **sigalg)\n{\n    *sigalg = NULL;\n    return 0;\n}\n#endif /* if OPENSSL_VERSION_NUMBER < 0x30500000 && (!defined(LIBRESSL_VERSION_NUMBER) || \\\n          LIBRESSL_VERSION_NUMBER > 0x3050400fL) */\n\n#if OPENSSL_VERSION_NUMBER < 0x30200000L && OPENSSL_VERSION_NUMBER >= 0x30000000L\nstatic inline const char *\nSSL_get0_group_name(SSL *s)\n{\n    int nid = (int)SSL_get_negotiated_group(s);\n    return SSL_group_to_name(s, nid);\n}\n#endif\n\n/* Introduced in OpenSSL 3.6.0 */\n#ifndef EVP_CIPH_FLAG_ENC_THEN_MAC\n#define EVP_CIPH_FLAG_ENC_THEN_MAC 0x10000000\n#endif\n\n#endif /* OPENSSL_COMPAT_H_ */\n"
  },
  {
    "path": "src/openvpn/openvpn.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"init.h\"\n#include \"forward.h\"\n#include \"multi.h\"\n#include \"win32.h\"\n#include \"platform.h\"\n#include \"string.h\"\n\n#include \"memdbg.h\"\n\n#define P2P_CHECK_SIG() EVENT_LOOP_CHECK_SIGNAL(c, process_signal_p2p, c);\n\nstatic bool\nprocess_signal_p2p(struct context *c)\n{\n    remap_signal(c);\n    return process_signal(c);\n}\n\n\n/**************************************************************************/\n/**\n * Main event loop for OpenVPN in client mode, where only one VPN tunnel\n * is active.\n * @ingroup eventloop\n *\n * @param c - The context structure of the single active VPN tunnel.\n */\nstatic void\ntunnel_point_to_point(struct context *c)\n{\n    context_clear_2(c);\n\n    /* set point-to-point mode */\n    c->mode = CM_P2P;\n    /* initialize tunnel instance, avoid SIGHUP when config is stdin since\n     * re-reading the config from stdin will not work */\n    bool stdin_config = c->options.config && (strcmp(c->options.config, \"stdin\") == 0);\n    init_instance_handle_signals(c, c->es, stdin_config ? 0 : CC_HARD_USR1_TO_HUP);\n    if (IS_SIG(c))\n    {\n        return;\n    }\n\n    /* main event loop */\n    while (true)\n    {\n        /* process timers, TLS, etc. */\n        pre_select(c);\n        P2P_CHECK_SIG();\n\n        /* set up and do the I/O wait */\n        io_wait(c, p2p_iow_flags(c));\n        P2P_CHECK_SIG();\n\n        /* timeout? */\n        if (c->c2.event_set_status == ES_TIMEOUT)\n        {\n            continue;\n        }\n\n        /* process the I/O which triggered select */\n        process_io(c, c->c2.link_sockets[0]);\n        P2P_CHECK_SIG();\n    }\n\n    persist_client_stats(c);\n\n    uninit_management_callback();\n\n    /* tear down tunnel instance (unless --persist-tun) */\n    close_instance(c);\n}\n\n#undef PROCESS_SIGNAL_P2P\n\nvoid\ninit_early(struct context *c)\n{\n    net_ctx_init(c, &c->net_ctx);\n\n    /* init verbosity and mute levels */\n    init_verb_mute(c, IVM_LEVEL_1);\n\n    /* Initialise OpenSSL provider, this needs to be initialised this\n     * early since option post-processing and also openssl info\n     * printing depends on it */\n    for (int j = 1; j < MAX_PARMS && c->options.providers.names[j]; j++)\n    {\n        c->options.providers.providers[j] = crypto_load_provider(c->options.providers.names[j]);\n    }\n}\n\nstatic void\nuninit_early(struct context *c)\n{\n    for (int j = 1; j < MAX_PARMS && c->options.providers.providers[j]; j++)\n    {\n        crypto_unload_provider(c->options.providers.names[j], c->options.providers.providers[j]);\n    }\n    net_ctx_free(&c->net_ctx);\n}\n\n\n/**************************************************************************/\n/**\n * OpenVPN's main init-run-cleanup loop.\n * @ingroup eventloop\n *\n * This function contains the two outer OpenVPN loops.  Its structure is\n * as follows:\n *  - Once-per-process initialization.\n *  - Outer loop, run at startup and then once per \\c SIGHUP:\n *    - Level 1 initialization\n *    - Inner loop, run at startup and then once per \\c SIGUSR1:\n *      - Call event loop function depending on client or server mode:\n *        - \\c tunnel_point_to_point()\n *        - \\c tunnel_server()\n *    - Level 1 cleanup\n *  - Once-per-process cleanup.\n *\n * @param argc - Commandline argument count.\n * @param argv - Commandline argument values.\n */\nstatic int\nopenvpn_main(int argc, char *argv[])\n{\n    struct context c;\n\n#if PEDANTIC\n    fprintf(stderr, \"Sorry, I was built with --enable-pedantic and I am incapable of doing any real work!\\n\");\n    return 1;\n#endif\n\n#ifdef _WIN32\n    SetConsoleOutputCP(CP_UTF8);\n#endif\n\n    CLEAR(c);\n\n    /* signify first time for components which can\n     * only be initialized once per program instantiation. */\n    c.first_time = true;\n\n    /* initialize program-wide statics */\n    if (init_static())\n    {\n        /*\n         * This loop is initially executed on startup and then\n         * once per SIGHUP.\n         */\n        do\n        {\n            /* enter pre-initialization mode with regard to signal handling */\n            pre_init_signal_catch();\n\n            /* zero context struct but leave first_time member alone */\n            context_clear_all_except_first_time(&c);\n\n            /* static signal info object */\n            c.sig = &siginfo_static;\n\n            /* initialize garbage collector scoped to context object */\n            gc_init(&c.gc);\n\n            /* initialize environmental variable store */\n            c.es = env_set_create(NULL);\n#ifdef _WIN32\n            set_win_sys_path_via_env(c.es);\n#endif\n\n#ifdef ENABLE_MANAGEMENT\n            /* initialize management subsystem */\n            init_management();\n#endif\n\n            /* initialize options to default state */\n            init_options(&c.options);\n\n            /* parse command line options, and read configuration file */\n            parse_argv(&c.options, argc, argv, M_USAGE, OPT_P_DEFAULT, NULL, c.es);\n\n#ifdef ENABLE_PLUGIN\n            /* plugins may contribute options configuration */\n            init_verb_mute(&c, IVM_LEVEL_1);\n            init_plugins(&c);\n            open_plugins(&c, true, OPENVPN_PLUGIN_INIT_PRE_CONFIG_PARSE);\n#endif\n\n            /* Early initialisation that need to happen before option\n             * post processing and other early startup but after parsing */\n            init_early(&c);\n\n            /* set dev options */\n            init_options_dev(&c.options);\n\n            /* openssl print info? */\n            if (print_openssl_info(&c.options))\n            {\n                break;\n            }\n\n            /* --genkey mode? */\n            if (do_genkey(&c.options))\n            {\n                break;\n            }\n\n            /* tun/tap persist command? */\n            if (do_persist_tuntap(&c.options, &c.net_ctx))\n            {\n                break;\n            }\n\n            /* sanity check on options */\n            options_postprocess(&c.options, c.es);\n\n            /* show all option settings */\n            show_settings(&c.options);\n\n            /* print version number */\n            msg(M_INFO, \"%s\", title_string);\n#ifdef _WIN32\n            show_windows_version(M_INFO);\n#endif\n            show_library_versions(M_INFO);\n\n            show_dco_version(M_INFO);\n\n            /* misc stuff */\n            pre_setup(&c.options);\n\n            /* test crypto? */\n            if (c.options.test_crypto)\n            {\n                do_test_crypto(&c);\n                break;\n            }\n\n            /* Query passwords before becoming a daemon if we don't use the\n             * management interface to get them. */\n            if (!(c.options.management_flags & MF_QUERY_PASSWORDS))\n            {\n                init_query_passwords(&c);\n            }\n\n            /* become a daemon if --daemon */\n            if (c.first_time)\n            {\n                c.did_we_daemonize = possibly_become_daemon(&c.options);\n                write_pid_file(c.options.writepid, c.options.chroot_dir);\n            }\n\n#ifdef ENABLE_MANAGEMENT\n            /* open management subsystem */\n            if (!open_management(&c))\n            {\n                break;\n            }\n            /* query for passwords through management interface, if needed */\n            if (c.options.management_flags & MF_QUERY_PASSWORDS)\n            {\n                init_query_passwords(&c);\n            }\n#endif\n\n            /* set certain options as environmental variables */\n            setenv_settings(c.es, &c.options);\n\n            /* finish context init */\n            context_init_1(&c);\n\n            do\n            {\n                /* run tunnel depending on mode */\n                switch (c.options.mode)\n                {\n                    case MODE_POINT_TO_POINT:\n                        tunnel_point_to_point(&c);\n                        break;\n\n                    case MODE_SERVER:\n                        tunnel_server(&c);\n                        break;\n\n                    default:\n                        ASSERT(0);\n                }\n\n                /* indicates first iteration -- has program-wide scope */\n                c.first_time = false;\n\n                /* any signals received? */\n                if (IS_SIG(&c))\n                {\n                    print_signal(c.sig, NULL, M_INFO);\n                }\n\n                /* pass restart status to management subsystem */\n                signal_restart_status(c.sig);\n            } while (signal_reset(c.sig, SIGUSR1) == SIGUSR1);\n\n            env_set_destroy(c.es);\n            uninit_options(&c.options);\n            gc_reset(&c.gc);\n            uninit_early(&c);\n        } while (signal_reset(c.sig, SIGHUP) == SIGHUP);\n    }\n\n    context_gc_free(&c);\n\n#ifdef ENABLE_MANAGEMENT\n    /* close management interface */\n    close_management();\n#endif\n\n    /* uninitialize program-wide statics */\n    uninit_static();\n\n    openvpn_exit(OPENVPN_EXIT_STATUS_GOOD); /* exit point */\n    return 0;                               /* NOTREACHED */\n}\n\n#ifdef _WIN32\nint\nwmain(int argc, wchar_t *wargv[])\n{\n    char **argv;\n    int ret;\n    int i;\n\n    if ((argv = calloc(argc + 1, sizeof(char *))) == NULL)\n    {\n        return 1;\n    }\n\n    for (i = 0; i < argc; i++)\n    {\n        int n = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, NULL, 0, NULL, NULL);\n        argv[i] = malloc(n);\n        WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i], n, NULL, NULL);\n    }\n\n    ret = openvpn_main(argc, argv);\n\n    for (i = 0; i < argc; i++)\n    {\n        free(argv[i]);\n    }\n    free(argv);\n\n    return ret;\n}\n#else  /* ifdef _WIN32 */\nint\nmain(int argc, char *argv[])\n{\n    return openvpn_main(argc, argv);\n}\n#endif /* ifdef _WIN32 */\n"
  },
  {
    "path": "src/openvpn/openvpn.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef OPENVPN_H\n#define OPENVPN_H\n\n#include \"buffer.h\"\n#include \"options.h\"\n#include \"socket.h\"\n#include \"crypto.h\"\n#include \"ssl.h\"\n#include \"packet_id.h\"\n#include \"comp.h\"\n#include \"tun.h\"\n#include \"interval.h\"\n#include \"status.h\"\n#include \"fragment.h\"\n#include \"shaper.h\"\n#include \"route.h\"\n#include \"proxy.h\"\n#include \"socks.h\"\n#include \"sig.h\"\n#include \"misc.h\"\n#include \"mbuf.h\"\n#include \"pool.h\"\n#include \"plugin.h\"\n#include \"manage.h\"\n#include \"dns.h\"\n\n/*\n * Our global key schedules, packaged thusly\n * to facilitate key persistence.\n */\n\nstruct key_schedule\n{\n    /* which cipher, HMAC digest, and key sizes are we using? */\n    struct key_type key_type;\n\n    /* pre-shared static key, read from a file */\n    struct key_ctx_bi static_key;\n\n    /* our global SSL context */\n    struct tls_root_ctx *ssl_ctx;\n\n    /* optional TLS control channel wrapping */\n    struct key_type tls_auth_key_type;\n    struct key_ctx_bi tls_wrap_key;\n    /** original tls-crypt key preserved to xored into the tls_crypt\n     * renegotiation key */\n    struct key2 original_wrap_keydata;\n    struct key_ctx tls_crypt_v2_server_key;\n    struct buffer tls_crypt_v2_wkc; /**< Wrapped client key */\n    struct key_ctx auth_token_key;\n};\n\n/*\n * struct packet_id_persist should be empty if we are not\n * building with crypto.\n */\n#ifndef PACKET_ID_H\nstruct packet_id_persist\n{\n    int dummy;\n};\nstatic inline void\npacket_id_persist_init(struct packet_id_persist *p)\n{\n}\n#endif\n\n/*\n * Packet processing buffers.\n */\nstruct context_buffers\n{\n    /* miscellaneous buffer, used by ping, occ, etc. */\n    struct buffer aux_buf;\n\n    /* workspace buffers used by crypto routines */\n    struct buffer encrypt_buf;\n    struct buffer decrypt_buf;\n\n    /* workspace buffers for compression */\n#ifdef USE_COMP\n    struct buffer compress_buf;\n    struct buffer decompress_buf;\n#endif\n\n    /*\n     * Buffers used to read from TUN device\n     * and TCP/UDP port.\n     */\n    struct buffer read_link_buf;\n    struct buffer read_tun_buf;\n};\n\n/*\n * always-persistent context variables\n */\nstruct context_persist\n{\n    int restart_sleep_seconds;\n    struct dns_updown_runner_info duri;\n};\n\n\n/**************************************************************************/\n/**\n * Level 0 %context containing information related to the OpenVPN process.\n *\n * Level 0 state is initialized once at program startup, and then remains\n * throughout the lifetime of the OpenVPN process.  This structure\n * contains information related to the process's PID, user, group, and\n * privileges.\n */\nstruct context_0\n{\n    /* workspace for --user/--group */\n    bool uid_gid_specified;\n    /* helper which tells us whether we should keep trying to drop privileges */\n    bool uid_gid_chroot_set;\n    struct platform_state_user platform_state_user;\n    struct platform_state_group platform_state_group;\n};\n\n\n/**\n * Level 1 %context containing state that persists across \\c SIGUSR1\n * restarts.\n *\n * Level 1 state is reset on \\c SIGHUP restarts.  This structure is\n * initialized for every iteration of the \\c main() function's outer \\c\n * SIGHUP loop, but persists over iteration of that function's inner \\c\n * SIGUSR1 loop.\n */\nstruct context_1\n{\n    int link_sockets_num;\n    struct link_socket_addr *link_socket_addrs;\n    /**< Local and remote addresses on the\n     *   external network. */\n\n    /* tunnel session keys */\n    struct key_schedule ks;\n\n    /* preresolved and cached host names */\n    struct cached_dns_entry *dns_cache;\n\n    /* persist crypto sequence number to/from file */\n    struct packet_id_persist pid_persist;\n\n    struct tuntap *tuntap; /**< Tun/tap virtual network interface. */\n    bool tuntap_owned;     /**< Whether the tun/tap interface should\n                            *   be cleaned up when this %context is\n                            *   cleaned up. */\n\n    struct route_list *route_list;\n    /**< List of routing information. See the\n     *   \\c --route command line option. */\n\n    /* list of --route-ipv6 directives */\n    struct route_ipv6_list *route_ipv6_list;\n\n    /* --status file */\n    struct status_output *status_output;\n    bool status_output_owned;\n\n    /* HTTP proxy object */\n    struct http_proxy_info *http_proxy;\n    bool http_proxy_owned;\n\n    /* SOCKS proxy object */\n    struct socks_proxy_info *socks_proxy;\n    bool socks_proxy_owned;\n\n    /* persist --ifconfig-pool db to file */\n    struct ifconfig_pool_persist *ifconfig_pool_persist;\n    bool ifconfig_pool_persist_owned;\n\n    /* if client mode, hash of option strings we pulled from server */\n    struct sha256_digest pulled_options_digest_save;\n    /**< Hash of option strings received from the\n     *   remote OpenVPN server.  Only used in\n     *   client-mode. */\n};\n\n\nstatic inline bool\nis_cas_pending(enum multi_status cas)\n{\n    return cas == CAS_PENDING || cas == CAS_PENDING_DEFERRED || cas == CAS_PENDING_DEFERRED_PARTIAL;\n}\n\n/**\n * Level 2 %context containing state that is reset on both \\c SIGHUP and\n * \\c SIGUSR1 restarts.\n *\n * This structure is initialized at the top of the \\c\n * tunnel_point_to_point() and \\c tunnel_server() \\c\n * functions.  In other words, it is reset for every\n * iteration of the \\c main() function's inner \\c SIGUSR1 loop.\n */\nstruct context_2\n{\n    struct gc_arena gc; /**< Garbage collection arena for\n                         *   allocations done in the level 2 scope\n                         *   of this context_2 structure. */\n\n    /* our global wait events */\n    struct event_set *event_set;\n    int event_set_max;\n    bool event_set_owned;\n\n    /* bitmask for event status. Check event.h for possible values */\n    unsigned int event_set_status;\n\n    struct link_socket **link_sockets;\n    struct link_socket_info **link_socket_infos;\n\n    bool link_socket_owned;\n\n    const struct link_socket *accept_from;   /* possibly do accept() on a parent link_socket */\n\n    struct link_socket_actual *to_link_addr; /* IP address of remote */\n    struct link_socket_actual from;          /* address of incoming datagram */\n\n    /* MTU frame parameters */\n    struct frame frame; /* Active frame parameters */\n\n#ifdef ENABLE_FRAGMENT\n    /* Object to handle advanced MTU negotiation and datagram fragmentation */\n    struct fragment_master *fragment;\n    struct frame frame_fragment;\n#endif\n\n    /*\n     * Traffic shaper object.\n     */\n    struct shaper shaper;\n\n    /*\n     * Statistics\n     */\n    counter_type tun_read_bytes;\n    counter_type tun_write_bytes;\n    counter_type link_read_bytes;\n    counter_type dco_read_bytes;\n    counter_type link_read_bytes_auth;\n    counter_type link_write_bytes;\n    counter_type dco_write_bytes;\n#ifdef PACKET_TRUNCATION_CHECK\n    counter_type n_trunc_tun_read;\n    counter_type n_trunc_tun_write;\n    counter_type n_trunc_pre_encrypt;\n    counter_type n_trunc_post_decrypt;\n#endif\n\n    /*\n     * Timer objects for ping and inactivity\n     * timeout features.\n     */\n    struct event_timeout wait_for_connect;\n    struct event_timeout ping_send_interval;\n    struct event_timeout ping_rec_interval;\n\n    /* --inactive */\n    struct event_timeout inactivity_interval;\n    int64_t inactivity_bytes;\n\n    struct event_timeout session_interval;\n\n    /* auth token renewal timer */\n    struct event_timeout auth_token_renewal_interval;\n\n    /* the option strings must match across peers */\n    char *options_string_local;\n    char *options_string_remote;\n\n    int occ_op; /* INIT to -1 */\n    int occ_n_tries;\n    struct event_timeout occ_interval;\n\n    /*\n     * Keep track of maximum packet size received so far\n     * (of authenticated packets).\n     */\n    int original_recv_size;   /* temporary */\n    int max_recv_size_local;  /* max packet size received */\n    int max_recv_size_remote; /* max packet size received by remote */\n    int max_send_size_local;  /* max packet size sent */\n    int max_send_size_remote; /* max packet size sent by remote */\n\n\n    /* remote wants us to send back a load test packet of this size */\n    int occ_mtu_load_size;\n\n    struct event_timeout occ_mtu_load_test_interval;\n    int occ_mtu_load_n_tries;\n\n    /*\n     * TLS-mode crypto objects.\n     */\n    struct tls_multi *tls_multi; /**< TLS state structure for this VPN\n                                  *   tunnel. */\n\n    struct tls_auth_standalone *tls_auth_standalone;\n    /**< TLS state structure required for the\n     *   initial authentication of a client's\n     *   connection attempt.  This structure\n     *   is used by the \\c\n     *   tls_pre_decrypt_lite() function when\n     *   it performs the HMAC firewall check\n     *   on the first connection packet\n     *   received from a new client.  See the\n     *   \\c --tls-auth commandline option. */\n\n\n    hmac_ctx_t *session_id_hmac;\n    /**< the HMAC we use to generate and verify our syn cookie like\n     * session ids from the server.\n     */\n\n    /* used to optimize calls to tls_multi_process */\n    struct interval tmp_int;\n\n    /* throw this signal on TLS errors */\n    int tls_exit_signal;\n\n    struct crypto_options crypto_options;\n    /**< Security parameters and crypto state\n     *   used by the \\link data_crypto Data\n     *   Channel Crypto module\\endlink to\n     *   process data channel packet. */\n\n    struct event_timeout packet_id_persist_interval;\n\n#ifdef USE_COMP\n    struct compress_context *comp_context;\n    /**< Compression context used by the\n     *   \\link compression Data Channel\n     *   Compression module\\endlink. */\n#endif\n\n    /*\n     * Buffers used for packet processing.\n     */\n    struct context_buffers *buffers;\n    bool buffers_owned; /* if true, we should free all buffers on close */\n\n    /*\n     * These buffers don't actually allocate storage, they are used\n     * as pointers to the allocated buffers in\n     * struct context_buffers.\n     */\n    struct buffer buf;\n    struct buffer to_tun;\n    struct buffer to_link;\n\n    /* should we print R|W|r|w to console on packet transfers? */\n    bool log_rw;\n\n    /* route stuff */\n    struct event_timeout route_wakeup;\n    struct event_timeout route_wakeup_expire;\n\n    /* did we open tun/tap dev during this cycle? */\n    bool did_open_tun;\n\n    /*\n     * Event loop info\n     */\n\n    /** Time to next event of timers and similar. This is used to determine\n     *  how long to wait on event wait (select/poll on link/tun read)\n     *  before this context wants to be serviced. */\n    struct timeval timeval;\n\n    /* next wakeup for processing coarse timers (>1 sec resolution) */\n    time_t coarse_timer_wakeup;\n\n    /* maintain a random delta to add to timeouts to avoid contexts\n     * waking up simultaneously */\n    time_t update_timeout_random_component;\n    struct timeval timeout_random_component;\n\n    /* Timer for everything up to the first packet from the *OpenVPN* server\n     * socks, http proxy, and tcp packets do not count */\n    struct event_timeout server_poll_interval;\n\n    /* indicates that the do_up_delay function has run */\n    bool do_up_ran;\n\n    /* indicates that we have received a SIGTERM when\n     * options->explicit_exit_notification is enabled,\n     * but we have not exited yet */\n    time_t explicit_exit_notification_time_wait;\n    struct event_timeout explicit_exit_notification_interval;\n\n    /* environmental variables to pass to scripts */\n    struct env_set *es;\n    bool es_owned;\n\n    /* --ifconfig endpoints to be pushed to client */\n    bool push_request_received;\n    bool push_ifconfig_defined;\n    time_t sent_push_reply_expiry;\n    in_addr_t push_ifconfig_local;\n    in_addr_t push_ifconfig_remote_netmask;\n    in_addr_t push_ifconfig_local_alias;\n\n    bool push_ifconfig_ipv6_defined;\n    struct in6_addr push_ifconfig_ipv6_local;\n    int push_ifconfig_ipv6_netbits;\n    struct in6_addr push_ifconfig_ipv6_remote;\n\n    struct event_timeout push_request_interval;\n    time_t push_request_timeout;\n\n    /* hash of pulled options, so we can compare when options change */\n    bool pulled_options_digest_init_done;\n    md_ctx_t *pulled_options_state;\n    struct sha256_digest pulled_options_digest;\n\n    struct event_timeout scheduled_exit;\n    int scheduled_exit_signal;\n\n    /* packet filter */\n\n#ifdef ENABLE_MANAGEMENT\n    struct man_def_auth_context mda_context;\n#endif\n\n#ifdef ENABLE_ASYNC_PUSH\n    int inotify_fd; /* descriptor for monitoring file changes */\n#endif\n};\n\n\n/**\n * Contains all state information for one tunnel.\n *\n * This structure represents one VPN tunnel.  It is used to store state\n * information related to a VPN tunnel, but also includes process-wide\n * data, such as configuration options.\n *\n * The @ref tunnel_state \"Structure of VPN tunnel state storage\" related\n * page describes how this structure is used in client-mode and\n * server-mode.\n */\nstruct context\n{\n    struct options options; /**< Options loaded from command line or\n                             *   configuration file. */\n\n    bool first_time;        /**< True on the first iteration of\n                             *   OpenVPN's main loop. */\n\n    /* context modes */\n#define CM_P2P       0           /* standalone point-to-point session or client */\n#define CM_TOP       1           /* top level of a multi-client or point-to-multipoint server */\n#define CM_TOP_CLONE 2           /* clone of a CM_TOP context for one thread */\n#define CM_CHILD_UDP 3           /* child context of a CM_TOP or CM_THREAD */\n#define CM_CHILD_TCP 4           /* child context of a CM_TOP or CM_THREAD */\n    int mode;                    /**< Role of this context within the\n                                  *   OpenVPN process.  Valid values are \\c\n                                  *   CM_P2P, \\c CM_TOP, \\c CM_TOP_CLONE,\n                                  *   \\c CM_CHILD_UDP, and \\c CM_CHILD_TCP. */\n\n    struct multi_context *multi; /**< Pointer to the main P2MP context.\n                                  *   Non-NULL only when mode == CM_TOP. */\n\n    struct gc_arena gc;          /**< Garbage collection arena for\n                                  *   allocations done in the scope of this\n                                  *   context structure. */\n\n    struct env_set *es;          /**< Set of environment variables. */\n\n    openvpn_net_ctx_t net_ctx;   /**< Networking API opaque context */\n\n    struct signal_info *sig;     /**< Internal error signaling object. */\n\n    struct plugin_list *plugins; /**< List of plug-ins. */\n    bool plugins_owned;          /**< Whether the plug-ins should be\n                                  *   cleaned up when this %context is\n                                  *   cleaned up. */\n\n    bool did_we_daemonize;       /**< Whether demonization has already\n                                  *   taken place. */\n\n    struct context_persist persist;\n    /**< Persistent %context. */\n    struct context_0 *c0; /**< Level 0 %context. */\n    struct context_1 c1;  /**< Level 1 %context. */\n    struct context_2 c2;  /**< Level 2 %context. */\n};\n\n/*\n * Check for a signal when inside an event loop\n */\n#define EVENT_LOOP_CHECK_SIGNAL(c, func, arg) \\\n    if (IS_SIG(c))                            \\\n    {                                         \\\n        const int brk = func(arg);            \\\n        if (brk)                              \\\n        {                                     \\\n            break;                            \\\n        }                                     \\\n        else                                  \\\n        {                                     \\\n            continue;                         \\\n        }                                     \\\n    }\n\n/*\n * Macros for referencing objects which may not\n * have been compiled in.\n */\n\n#define TLS_MODE(c)      ((c)->c2.tls_multi != NULL)\n#define PROTO_DUMP_FLAGS (check_debug_level(D_LINK_RW_VERBOSE) ? (PD_SHOW_DATA | PD_VERBOSE) : 0)\n#define PROTO_DUMP(buf, gc)                                                                   \\\n    protocol_dump(                                                                            \\\n        (buf),                                                                                \\\n        PROTO_DUMP_FLAGS | (c->c2.tls_multi ? PD_TLS : 0)                                     \\\n            | (c->options.tls_auth_file ? md_kt_size(c->c1.ks.key_type.digest) : 0)           \\\n            | (c->options.tls_crypt_file || c->options.tls_crypt_v2_file ? PD_TLS_CRYPT : 0), \\\n        gc)\n\n/* this represents \"disabled peer-id\" */\n#define MAX_PEER_ID 0xFFFFFF\n\n#endif /* ifndef OPENVPN_H */\n"
  },
  {
    "path": "src/openvpn/openvpn.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\" xmlns:asmv3=\"urn:schemas-microsoft-com:asm.v3\">\n    <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n        <application>\n            <!-- Windows 10 -->\n            <supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\"/>\n            <!-- Windows 8.1 -->\n            <supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\"/>\n            <!-- Windows 8 -->\n            <supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\"/>\n            <!-- Windows 7 -->\n            <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\"/>\n            <!-- Windows Vista -->\n            <supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\"/>\n        </application>\n    </compatibility>\n    <application>\n        <windowsSettings>\n            <activeCodePage xmlns=\"http://schemas.microsoft.com/SMI/2019/WindowsSettings\">UTF-8</activeCodePage>\n        </windowsSettings>\n    </application>\n    <trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n        <security>\n            <requestedPrivileges>\n                <!--\n                  UAC settings:\n                  - app should run at same integrity level as calling process\n                  - app does not need to manipulate windows belonging to\n                    higher-integrity-level processes\n                  -->\n                <requestedExecutionLevel\n                    level=\"asInvoker\"\n                    uiAccess=\"false\"\n                />\n            </requestedPrivileges>\n        </security>\n    </trustInfo>\n</assembly>\n"
  },
  {
    "path": "src/openvpn/openvpn_win32_resources.rc",
    "content": "#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n#include <winresrc.h>\n\n#pragma code_page(65001) /* UTF8 */\n\n1 RT_MANIFEST \"openvpn.manifest\"\n\nLANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL\n\nVS_VERSION_INFO VERSIONINFO\n FILEVERSION OPENVPN_VERSION_RESOURCE\n PRODUCTVERSION OPENVPN_VERSION_RESOURCE\n FILEFLAGSMASK 0x3fL\n#ifdef _DEBUG\n FILEFLAGS 0x1L\n#else\n FILEFLAGS 0x0L\n#endif\n FILEOS 0x40004L\n FILETYPE VFT_APP\n FILESUBTYPE 0x0L\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"040904b0\"\n        BEGIN\n            VALUE \"CompanyName\", \"The OpenVPN Project\"\n            VALUE \"FileDescription\", \"OpenVPN Daemon\"\n            VALUE \"FileVersion\", PACKAGE_VERSION \".0\"\n            VALUE \"InternalName\", \"OpenVPN\"\n            VALUE \"LegalCopyright\", \"Copyright © The OpenVPN Project\" \n            VALUE \"OriginalFilename\", \"openvpn.exe\"\n            VALUE \"ProductName\", \"OpenVPN\"\n            VALUE \"ProductVersion\", PACKAGE_VERSION \".0\"\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x409, 1200\n    END\nEND\n"
  },
  {
    "path": "src/openvpn/options.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2008-2026 David Sommerseth <dazo@eurephia.org>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * 2004-01-28: Added Socks5 proxy support\n *   (Christof Meerwald, https://cmeerw.org)\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n#ifdef HAVE_CONFIG_VERSION_H\n#include \"config-version.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"common.h\"\n#include \"run_command.h\"\n#include \"shaper.h\"\n#include \"crypto.h\"\n#include \"ssl.h\"\n#include \"ssl_ncp.h\"\n#include \"options.h\"\n#include \"misc.h\"\n#include \"socket_util.h\"\n#include \"packet_id.h\"\n#include \"pkcs11.h\"\n#include \"win32.h\"\n#include \"push.h\"\n#include \"pool.h\"\n#include \"proto.h\"\n#include \"helper.h\"\n#include \"manage.h\"\n#include \"forward.h\"\n#include \"ssl_verify.h\"\n#include \"platform.h\"\n#include \"xkey_common.h\"\n#include \"dco.h\"\n#include \"options_util.h\"\n#include \"tun_afunix.h\"\n#include \"domain_helper.h\"\n#include \"mbuf.h\"\n\n#include <ctype.h>\n\n#include \"memdbg.h\"\n#include \"options_util.h\"\n\nconst char title_string[] = PACKAGE_STRING\n#ifdef CONFIGURE_GIT_REVISION\n    \" [git:\" CONFIGURE_GIT_REVISION CONFIGURE_GIT_FLAGS \"]\"\n#endif\n    \" \" TARGET_ALIAS\n#if defined(ENABLE_CRYPTO_MBEDTLS)\n    \" [SSL (mbed TLS)]\"\n#elif defined(ENABLE_CRYPTO_OPENSSL)\n    \" [SSL (OpenSSL)]\"\n#else\n    \" [SSL]\"\n#endif /* defined(ENABLE_CRYPTO_MBEDTLS) */\n#ifdef USE_COMP\n#ifdef ENABLE_LZO\n    \" [LZO]\"\n#endif\n#ifdef ENABLE_LZ4\n    \" [LZ4]\"\n#endif\n#ifdef ENABLE_COMP_STUB\n    \" [COMP_STUB]\"\n#endif\n#endif /* USE_COMP */\n#if EPOLL\n    \" [EPOLL]\"\n#endif\n#ifdef PRODUCT_TAP_DEBUG\n    \" [TAPDBG]\"\n#endif\n#ifdef ENABLE_PKCS11\n    \" [PKCS11]\"\n#endif\n#if ENABLE_IP_PKTINFO\n#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)\n    \" [MH/PKTINFO]\"\n#elif defined(IP_RECVDSTADDR)\n    \" [MH/RECVDA]\"\n#endif\n#endif\n    \" [AEAD]\"\n#ifdef ENABLE_DCO\n    \" [DCO]\"\n#endif\n#ifdef CONFIGURE_GIT_REVISION\n    \" built on \" __DATE__\n#endif\n    ;\n\n#ifndef ENABLE_SMALL\n\nstatic const char usage_message[] =\n    \"%s\\n\"\n    \"\\n\"\n    \"General Options:\\n\"\n    \"--config file   : Read configuration options from file.\\n\"\n    \"--help          : Show options.\\n\"\n    \"--version       : Show copyright and version information.\\n\"\n    \"\\n\"\n    \"Tunnel Options:\\n\"\n    \"--local host|* [port]: Local host name or IP address and port for bind.\\n\"\n    \"                        If specified, OpenVPN will bindto this address. If unspecified,\\n\"\n    \"                        OpenVPN will bind to all interfaces. '*' can be used as hostname\\n\"\n    \"                        and means 'any host' (OpenVPN will listen on what is returned by the OS).\\n\"\n    \"                        On a client, or in point-to-point mode, this can only be specified once (1 socket).\\n\"\n    \"                        On an OpenVPN setup running as ``--server``, this can be specified multiple times\\n\"\n    \"                        to open multiple listening sockets on different addresses and/or different ports.\\n\"\n    \"                        In order to specify multiple listen ports without specifying an address, use '*'\\n\"\n    \"                        to signal 'use what the operating system gives you as default', for\\n\"\n    \"                        'all IPv4 addresses' use '0.0.0.0', for 'all IPv6 addresses' use '::'.\\n\"\n    \"                        ``--local`` implies ``--bind``.\\n\"\n    \"--remote host [port] : Remote host name or ip address.\\n\"\n    \"--remote-random : If multiple --remote options specified, choose one randomly.\\n\"\n    \"--remote-random-hostname : Add a random string to remote DNS name.\\n\"\n    \"--mode m        : Major mode, m = 'p2p' (default, point-to-point) or 'server'.\\n\"\n    \"--proto p       : Use protocol p for communicating with peer.\\n\"\n    \"                  p = udp (default), tcp-server, tcp-client\\n\"\n    \"                      udp4, tcp4-server, tcp4-client\\n\"\n    \"                      udp6, tcp6-server, tcp6-client\\n\"\n    \"--proto-force p : only consider protocol p in list of connection profiles.\\n\"\n    \"                  p = udp or tcp\\n\"\n    \"--connect-retry n [m] : For client, number of seconds to wait between\\n\"\n    \"                  connection retries (default=%d). On repeated retries\\n\"\n    \"                  the wait time is exponentially increased to a maximum of m\\n\"\n    \"                  (default=%d).\\n\"\n    \"--connect-retry-max n : Maximum connection attempt retries, default infinite.\\n\"\n    \"--http-proxy s p [up] [auth] : Connect to remote host\\n\"\n    \"                  through an HTTP proxy at address s and port p.\\n\"\n    \"                  If proxy authentication is required,\\n\"\n    \"                  up is a file containing username/password on 2 lines, or\\n\"\n    \"                  'stdin' to prompt from console.\\n\"\n    \"--http-proxy s p 'auto[-nct]' : Like the above directive, but automatically\\n\"\n    \"                  determine auth method and query for username/password\\n\"\n    \"                  if needed.  auto-nct disables weak proxy auth methods.\\n\"\n    \"--http-proxy-option type [parm] : Set extended HTTP proxy options.\\n\"\n    \"                                  Repeat to set multiple options.\\n\"\n    \"                  VERSION version (default=1.0)\\n\"\n    \"                  AGENT user-agent\\n\"\n    \"--socks-proxy s [p] [up] : Connect to remote host through a Socks5 proxy at\\n\"\n    \"                  address s and port p (default port = 1080).\\n\"\n    \"                  If proxy authentication is required,\\n\"\n    \"                  up is a file containing username/password on 2 lines, or\\n\"\n    \"                  'stdin' to prompt for console.\\n\"\n    \"--socks-proxy-retry : Retry indefinitely on Socks proxy errors.\\n\"\n    \"--resolv-retry n: If hostname resolve fails for --remote, retry\\n\"\n    \"                  resolve for n seconds before failing (disabled by default).\\n\"\n    \"                  Set n=\\\"infinite\\\" to retry indefinitely.\\n\"\n    \"--float         : Allow remote to change its IP address/port, such as through\\n\"\n    \"                  DHCP (this is the default if --remote is not used).\\n\"\n    \"--ipchange cmd  : Run command cmd on remote ip address initial\\n\"\n    \"                  setting or change -- execute as: cmd ip-address port#\\n\"\n    \"--port port     : TCP/UDP port # for both local and remote.\\n\"\n    \"--lport port    : TCP/UDP port # for local (default=%s). Implies --bind.\\n\"\n    \"--rport port    : TCP/UDP port # for remote (default=%s).\\n\"\n    \"--bind          : Bind to local address and port. (This is the default unless\\n\"\n    \"                  --proto tcp-client\"\n    \" or --http-proxy\"\n    \" or --socks-proxy\"\n    \" is used).\\n\"\n    \"--nobind        : Do not bind to local address and port.\\n\"\n    \"--dev tunX|tapX : tun/tap device (X can be omitted for dynamic device.\\n\"\n    \"--dev-type dt   : Which device type are we using? (dt = tun or tap) Use\\n\"\n    \"                  this option only if the tun/tap device used with --dev\\n\"\n    \"                  does not begin with \\\"tun\\\" or \\\"tap\\\".\\n\"\n    \"--dev-node node : Explicitly set the device node rather than using\\n\"\n    \"                  /dev/net/tun, /dev/tun, /dev/tap, etc.\\n\"\n#if defined(ENABLE_DCO)\n    \"--disable-dco   : Do not attempt using Data Channel Offload.\\n\"\n#endif\n    \"--lladdr hw     : Set the link layer address of the tap device.\\n\"\n    \"--topology t    : Set --dev tun topology: 'net30', 'p2p', or 'subnet'.\\n\"\n#ifdef ENABLE_IPROUTE\n    \"--iproute cmd   : Use this command instead of default \" IPROUTE_PATH \".\\n\"\n#endif\n    \"--ifconfig l rn : TUN: configure device to use IP address l as a local\\n\"\n    \"                  endpoint and rn as a remote endpoint.  l & rn should be\\n\"\n    \"                  swapped on the other peer.  l & rn must be private\\n\"\n    \"                  addresses outside of the subnets used by either peer.\\n\"\n    \"                  TAP: configure device to use IP address l as a local\\n\"\n    \"                  endpoint and rn as a subnet mask.\\n\"\n    \"--ifconfig-ipv6 l r : configure device to use IPv6 address l as local\\n\"\n    \"                      endpoint (as a /64) and r as remote endpoint\\n\"\n    \"--ifconfig-noexec : Don't actually execute ifconfig/netsh command, instead\\n\"\n    \"                    pass --ifconfig parms by environment to scripts.\\n\"\n    \"--ifconfig-nowarn : Don't warn if the --ifconfig option on this side of the\\n\"\n    \"                    connection doesn't match the remote side.\\n\"\n#ifdef TARGET_LINUX\n    \"--route-table table_id : Specify a custom routing table for use with --route(-ipv6).\\n\"\n    \"                           If not specified, the id of the default routing table will be used.\\n\"\n#endif\n    \"--route network [netmask] [gateway] [metric] :\\n\"\n    \"                  Add route to routing table after connection\\n\"\n    \"                  is established.  Multiple routes can be specified.\\n\"\n    \"                  netmask default: 255.255.255.255\\n\"\n    \"                  gateway default: taken from --route-gateway or --ifconfig\\n\"\n    \"                  Specify default by leaving blank or setting to \\\"default\\\".\\n\"\n    \"--route-ipv6 network/bits [gateway] [metric] :\\n\"\n    \"                  Add IPv6 route to routing table after connection\\n\"\n    \"                  is established.  Multiple routes can be specified.\\n\"\n    \"                  gateway default: taken from --route-ipv6-gateway or 'remote'\\n\"\n    \"                  in --ifconfig-ipv6\\n\"\n    \"--route-gateway gw|'dhcp' : Specify a default gateway for use with --route.\\n\"\n    \"--route-ipv6-gateway gw : Specify a default gateway for use with --route-ipv6.\\n\"\n    \"--route-metric m : Specify a default metric for use with --route.\\n\"\n    \"--route-delay n [w] : Delay n seconds after connection initiation before\\n\"\n    \"                  adding routes (may be 0).  If not specified, routes will\\n\"\n    \"                  be added immediately after tun/tap open.  On Windows, wait\\n\"\n    \"                  up to w seconds for TUN/TAP adapter to come up.\\n\"\n    \"--route-up cmd  : Run command cmd after routes are added.\\n\"\n    \"--route-pre-down cmd : Run command cmd before routes are removed.\\n\"\n    \"--route-noexec  : Don't add routes automatically.  Instead pass routes to\\n\"\n    \"                  --route-up script using environmental variables.\\n\"\n    \"--route-nopull  : When used with --client or --pull, accept options pushed\\n\"\n    \"                  by server EXCEPT for routes, dns, and dhcp options.\\n\"\n    \"--allow-pull-fqdn : Allow client to pull DNS names from server for\\n\"\n    \"                    --ifconfig, --route, and --route-gateway.\\n\"\n    \"--redirect-gateway [flags]: Automatically execute routing\\n\"\n    \"                  commands to redirect all outgoing IP traffic through the\\n\"\n    \"                  VPN.  Add 'local' flag if both \" PACKAGE_NAME \" servers are directly\\n\"\n    \"                  connected via a common subnet, such as with WiFi.\\n\"\n    \"                  Add 'def1' flag to set default route using using 0.0.0.0/1\\n\"\n    \"                  and 128.0.0.0/1 rather than 0.0.0.0/0.  Add 'bypass-dhcp'\\n\"\n    \"                  flag to add a direct route to DHCP server, bypassing tunnel.\\n\"\n    \"                  Add 'bypass-dns' flag to similarly bypass tunnel for DNS.\\n\"\n    \"--redirect-private [flags]: Like --redirect-gateway, but omit actually changing\\n\"\n    \"                  the default gateway.  Useful when pushing private subnets.\\n\"\n    \"--block-ipv6     : (Client) Instead sending IPv6 to the server generate\\n\"\n    \"                   ICMPv6 host unreachable messages on the client.\\n\"\n    \"                   (Server) Instead of forwarding IPv6 packets send\\n\"\n    \"                   ICMPv6 host unreachable packets to the client.\\n\"\n    \"--client-nat snat|dnat network netmask alias : on client add 1-to-1 NAT rule.\\n\"\n    \"--push-peer-info : (client only) push client info to server.\\n\"\n    \"--setenv name value : Set a custom environmental variable to pass to script.\\n\"\n    \"--setenv FORWARD_COMPATIBLE 1 : Relax config file syntax checking to allow\\n\"\n    \"                  directives for future OpenVPN versions to be ignored.\\n\"\n    \"--ignore-unknown-option opt1 opt2 ...: Relax config file syntax. Allow\\n\"\n    \"                  these options to be ignored when unknown\\n\"\n    \"--script-security level: Where level can be:\\n\"\n    \"                  0 -- strictly no calling of external programs\\n\"\n    \"                  1 -- (default) only call built-ins such as ifconfig\\n\"\n    \"                  2 -- allow calling of built-ins and scripts\\n\"\n    \"                  3 -- allow password to be passed to scripts via env\\n\"\n    \"--shaper n      : Restrict output to peer to n bytes per second.\\n\"\n    \"--keepalive n m : Helper option for setting timeouts in server mode.  Send\\n\"\n    \"                  ping once every n seconds, restart if ping not received\\n\"\n    \"                  for m seconds.\\n\"\n    \"--inactive n [bytes] : Exit after n seconds of activity on tun/tap device\\n\"\n    \"                  produces a combined in/out byte count < bytes.\\n\"\n    \"--session-timeout n: Limit connection time to n seconds.\\n\"\n    \"--ping-exit n   : Exit if n seconds pass without reception of remote ping.\\n\"\n    \"--ping-restart n: Restart if n seconds pass without reception of remote ping.\\n\"\n    \"--ping-timer-rem: Run the --ping-exit/--ping-restart timer only if we have a\\n\"\n    \"                  remote address.\\n\"\n    \"--ping n        : Ping remote once every n seconds over TCP/UDP port.\\n\"\n#if ENABLE_IP_PKTINFO\n    \"--multihome     : Configure a multi-homed UDP server.\\n\"\n#endif\n    \"--remap-usr1 s  : On SIGUSR1 signals, remap signal (s='SIGHUP' or 'SIGTERM').\\n\"\n    \"--persist-tun   : Keep tun/tap device open across SIGUSR1 or --ping-restart.\\n\"\n    \"--persist-remote-ip : Keep remote IP address across SIGUSR1 or --ping-restart.\\n\"\n    \"--persist-local-ip  : Keep local IP address across SIGUSR1 or --ping-restart.\\n\"\n#if PASSTOS_CAPABILITY\n    \"--passtos       : TOS passthrough (applies to IPv4 only).\\n\"\n#endif\n    \"--tun-mtu n     : Take the tun/tap device MTU to be n and derive the\\n\"\n    \"                  TCP/UDP MTU from it (default=%d).\\n\"\n    \"--tun-mtu-extra n : Assume that tun/tap device might return as many\\n\"\n    \"                  as n bytes more than the tun-mtu size on read\\n\"\n    \"                  (default TUN=0 TAP=%d).\\n\"\n    \"--tun-mtu-max n : Maximum pushable MTU (default and minimum=%d).\\n\"\n    \"--link-mtu n    : Take the TCP/UDP device MTU to be n and derive the tun MTU\\n\"\n    \"                  from it.\\n\"\n    \"--mtu-disc type : Should we do Path MTU discovery on TCP/UDP channel?\\n\"\n    \"                  'no'    -- Never send DF (Don't Fragment) frames\\n\"\n    \"                  'maybe' -- Use per-route hints\\n\"\n    \"                  'yes'   -- Always DF (Don't Fragment)\\n\"\n    \"--mtu-test      : Empirically measure and report MTU.\\n\"\n#ifdef ENABLE_FRAGMENT\n    \"--fragment max  : Enable internal datagram fragmentation so that no UDP\\n\"\n    \"                  datagrams are sent which are larger than max bytes.\\n\"\n    \"                  Adds 4 bytes of overhead per datagram.\\n\"\n#endif\n    \"--mssfix [n]    : Set upper bound on TCP MSS, default = tun-mtu size\\n\"\n    \"                  or --fragment max value, whichever is lower.\\n\"\n    \"--sndbuf size   : Set the TCP/UDP send buffer size.\\n\"\n    \"--rcvbuf size   : Set the TCP/UDP receive buffer size.\\n\"\n#if defined(TARGET_LINUX)\n    \"--mark value    : Mark encrypted packets being sent with value. The mark value\\n\"\n    \"                  can be matched in policy routing and packetfilter rules.\\n\"\n    \"--bind-dev dev  : Bind to the given device when making connection to a peer or\\n\"\n    \"                  listening for connections. This allows sending encrypted packets\\n\"\n    \"                  via a VRF present on the system.\\n\"\n#endif\n    \"--txqueuelen n  : Set the tun/tap TX queue length to n (Linux only).\\n\"\n    \"--mlock         : Disable Paging -- ensures key material and tunnel\\n\"\n    \"                  data will never be written to disk.\\n\"\n    \"--up cmd        : Run command cmd after successful tun device open.\\n\"\n    \"                  Execute as: cmd tun/tap-dev tun-mtu link-mtu \\\\\\n\"\n    \"                              ifconfig-local-ip ifconfig-remote-ip\\n\"\n    \"                  (pre --user or --group UID/GID change)\\n\"\n    \"--up-delay      : Delay tun/tap open and possible --up script execution\\n\"\n    \"                  until after TCP/UDP connection establishment with peer.\\n\"\n    \"--down cmd      : Run command cmd after tun device close.\\n\"\n    \"                  (post --user/--group UID/GID change and/or --chroot)\\n\"\n    \"                  (command parameters are same as --up option)\\n\"\n    \"--down-pre      : Run --down command before TUN/TAP close.\\n\"\n    \"--up-restart    : Run up/down commands for all restarts including those\\n\"\n    \"                  caused by --ping-restart or SIGUSR1\\n\"\n    \"--user user     : Set UID to user after initialization.\\n\"\n    \"--group group   : Set GID to group after initialization.\\n\"\n    \"--chroot dir    : Chroot to this directory after initialization.\\n\"\n#ifdef ENABLE_SELINUX\n    \"--setcon context: Apply this SELinux context after initialization.\\n\"\n#endif\n    \"--cd dir        : Change to this directory before initialization.\\n\"\n    \"--daemon [name] : Become a daemon after initialization.\\n\"\n    \"                  The optional 'name' parameter will be passed\\n\"\n    \"                  as the program name to the system logger.\\n\"\n    \"--syslog [name] : Output to syslog, but do not become a daemon.\\n\"\n    \"                  See --daemon above for a description of the 'name' parm.\\n\"\n    \"--log file      : Output log to file which is created/truncated on open.\\n\"\n    \"--log-append file : Append log to file, or create file if nonexistent.\\n\"\n    \"--suppress-timestamps : Don't log timestamps to stdout/stderr.\\n\"\n    \"--machine-readable-output : Always log timestamp, message flags to stdout/stderr.\\n\"\n    \"--writepid file : Write main process ID to file.\\n\"\n    \"--nice n        : Change process priority (>0 = lower, <0 = higher).\\n\"\n    \"--echo [parms ...] : Echo parameters to log output.\\n\"\n    \"--verb n        : Set output verbosity to n (default=%d):\\n\"\n    \"                  (Level 3 is recommended if you want a good summary\\n\"\n    \"                  of what's happening without being swamped by output).\\n\"\n    \"                : 0 -- no output except fatal errors\\n\"\n    \"                : 1 -- startup info + connection initiated messages +\\n\"\n    \"                       non-fatal encryption & net errors\\n\"\n    \"                : 2,3 -- show TLS negotiations & route info\\n\"\n    \"                : 4 -- show parameters\\n\"\n    \"                : 5 -- show 'RrWw' chars on console for each packet sent\\n\"\n    \"                       and received from TCP/UDP (caps) or tun/tap (lc)\\n\"\n    \"                : 6 to 11 -- debug messages of increasing verbosity\\n\"\n    \"--mute n        : Log at most n consecutive messages in the same category.\\n\"\n    \"--status file [n] : Write operational status to file every n seconds.\\n\"\n    \"--status-version [n] : Choose the status file format version number.\\n\"\n    \"                  Currently, n can be 1, 2, or 3 (default=1).\\n\"\n    \"--disable-occ   : (DEPRECATED) Disable options consistency check between peers.\\n\"\n#ifdef ENABLE_DEBUG\n    \"--gremlin mask  : Special stress testing mode (for debugging only).\\n\"\n#endif\n#if defined(USE_COMP)\n    \"--compress alg  : Use compression algorithm alg\\n\"\n    \"--allow-compression: Specify whether compression should be allowed\\n\"\n#if defined(ENABLE_LZO)\n    \"--comp-lzo      : Use LZO compression -- may add up to 1 byte per\\n\"\n    \"                  packet for incompressible data.\\n\"\n    \"--comp-noadapt  : Don't use adaptive compression when --comp-lzo\\n\"\n    \"                  is specified.\\n\"\n#endif\n#endif\n#ifdef ENABLE_MANAGEMENT\n    \"--management ip port [pass] : Enable a TCP server on ip:port to handle\\n\"\n    \"                  management functions.  pass is a password file\\n\"\n    \"                  or 'stdin' to prompt from console.\\n\"\n#if UNIX_SOCK_SUPPORT\n    \"                  To listen on a unix domain socket, specific the pathname\\n\"\n    \"                  in place of ip and use 'unix' as the port number.\\n\"\n#endif\n    \"--management-client : Management interface will connect as a TCP client to\\n\"\n    \"                      ip/port rather than listen as a TCP server.\\n\"\n    \"--management-query-passwords : Query management channel for private key\\n\"\n    \"                  and auth-user-pass passwords.\\n\"\n    \"--management-query-proxy : Query management channel for proxy information.\\n\"\n    \"--management-query-remote : Query management channel for --remote directive.\\n\"\n    \"--management-hold : Start \" PACKAGE_NAME \" in a hibernating state, until a client\\n\"\n    \"                    of the management interface explicitly starts it.\\n\"\n    \"--management-signal : Issue SIGUSR1 when management disconnect event occurs.\\n\"\n    \"--management-forget-disconnect : Forget passwords when management disconnect\\n\"\n    \"                                 event occurs.\\n\"\n    \"--management-up-down : Report tunnel up/down events to management interface.\\n\"\n    \"--management-log-cache n : Cache n lines of log file history for usage\\n\"\n    \"                  by the management channel.\\n\"\n#if UNIX_SOCK_SUPPORT\n    \"--management-client-user u  : When management interface is a unix socket, only\\n\"\n    \"                              allow connections from user u.\\n\"\n    \"--management-client-group g : When management interface is a unix socket, only\\n\"\n    \"                              allow connections from group g.\\n\"\n#endif\n    \"--management-client-auth : gives management interface client the responsibility\\n\"\n    \"                           to authenticate clients after their client certificate\\n\"\n    \"\t\t\t      has been verified.\\n\"\n#endif /* ifdef ENABLE_MANAGEMENT */\n#ifdef ENABLE_PLUGIN\n    \"--plugin m [str]: Load plug-in module m passing str as an argument\\n\"\n    \"                  to its initialization function.\\n\"\n#endif\n    \"--vlan-tagging  : Enable 802.1Q-based VLAN tagging.\\n\"\n    \"--vlan-accept tagged|untagged|all : Set VLAN tagging mode. Default is 'all'.\\n\"\n    \"--vlan-pvid v   : Sets the Port VLAN Identifier. Defaults to 1.\\n\"\n    \"\\n\"\n    \"Multi-Client Server options (when --mode server is used):\\n\"\n    \"--server network netmask : Helper option to easily configure server mode.\\n\"\n    \"--server-ipv6 network/bits : Configure IPv6 server mode.\\n\"\n    \"--server-bridge [IP netmask pool-start-IP pool-end-IP] : Helper option to\\n\"\n    \"                    easily configure ethernet bridging server mode.\\n\"\n    \"--push \\\"option\\\" : Push a config file option back to the peer for remote\\n\"\n    \"                  execution.  Peer must specify --pull in its config file.\\n\"\n    \"--push-reset    : Don't inherit global push list for specific\\n\"\n    \"                  client instance.\\n\"\n    \"--push-remove opt : Remove options matching 'opt' from the push list for\\n\"\n    \"                  a specific client instance.\\n\"\n    \"--ifconfig-pool start-IP end-IP [netmask] : Set aside a pool of subnets\\n\"\n    \"                  to be dynamically allocated to connecting clients.\\n\"\n    \"--ifconfig-pool-persist file [seconds] : Persist/unpersist ifconfig-pool\\n\"\n    \"                  data to file, at seconds intervals (default=600).\\n\"\n    \"                  If seconds=0, file will be treated as read-only.\\n\"\n    \"--ifconfig-ipv6-pool base-IP/bits : set aside an IPv6 network block\\n\"\n    \"                  to be dynamically allocated to connecting clients.\\n\"\n    \"--ifconfig-push local remote-netmask : Push an ifconfig option to remote,\\n\"\n    \"                  overrides --ifconfig-pool dynamic allocation.\\n\"\n    \"                  Only valid in a client-specific config file.\\n\"\n    \"--ifconfig-ipv6-push local/bits remote : Push an ifconfig-ipv6 option to\\n\"\n    \"                  remote, overrides --ifconfig-ipv6-pool allocation.\\n\"\n    \"                  Only valid in a client-specific config file.\\n\"\n    \"--iroute network [netmask] : Route subnet to client.\\n\"\n    \"--iroute-ipv6 network/bits : Route IPv6 subnet to client.\\n\"\n    \"                  Sets up internal routes only.\\n\"\n    \"                  Only valid in a client-specific config file.\\n\"\n    \"--disable       : Client is disabled.\\n\"\n    \"                  Only valid in a client-specific config file.\\n\"\n    \"--override-username: Overrides the client-specific username to be used.\\n\"\n    \"                  Only valid in a client-specific config file.\\n\"\n    \"--verify-client-cert [none|optional|require] : perform no, optional or\\n\"\n    \"                  mandatory client certificate verification.\\n\"\n    \"                  Default is to require the client to supply a certificate.\\n\"\n    \"--username-as-common-name  : For auth-user-pass authentication, use\\n\"\n    \"                  the authenticated username as the common name,\\n\"\n    \"                  rather than the common name from the client cert.\\n\"\n    \"--auth-user-pass-verify cmd method: Query client for username/password and\\n\"\n    \"                  run command cmd to verify.  If method='via-env', pass\\n\"\n    \"                  user/pass via environment, if method='via-file', pass\\n\"\n    \"                  user/pass via temporary file.\\n\"\n    \"--auth-gen-token  [lifetime] Generate a random authentication token which is pushed\\n\"\n    \"                  to each client, replacing the password.  Useful when\\n\"\n    \"                  OTP based two-factor auth mechanisms are in use and\\n\"\n    \"                  --reneg-* options are enabled. Optionally a lifetime in seconds\\n\"\n    \"                  for generated tokens can be set.\\n\"\n    \"--auth-user-pass-optional : Allow connections by clients that don't\\n\"\n    \"                  specify a username/password.\\n\"\n    \"--client-to-client : Internally route client-to-client traffic.\\n\"\n    \"--duplicate-cn  : Allow multiple clients with the same common name to\\n\"\n    \"                  concurrently connect.\\n\"\n    \"--client-connect cmd : Run command cmd on client connection.\\n\"\n    \"--client-disconnect cmd : Run command cmd on client disconnection.\\n\"\n    \"--client-config-dir dir : Directory for custom client config files.\\n\"\n    \"--ccd-exclusive : Refuse connection unless custom client config is found.\\n\"\n    \"--tmp-dir dir   : Temporary directory, used for --client-connect return file and plugin communication.\\n\"\n    \"--hash-size r v : Set the size of the real address hash table to r and the\\n\"\n    \"                  virtual address table to v.\\n\"\n    \"--bcast-buffers n : Allocate n broadcast buffers.\\n\"\n    \"--tcp-queue-limit n : Maximum number of queued TCP output packets.\\n\"\n    \"--tcp-nodelay   : Macro that sets TCP_NODELAY socket flag on the server\\n\"\n    \"                  as well as pushes it to connecting clients.\\n\"\n    \"--learn-address cmd : Run command cmd to validate client virtual addresses.\\n\"\n    \"--connect-freq n s : Allow a maximum of n new connections per s seconds.\\n\"\n    \"--connect-freq-initial n s : Allow a maximum of n replies for initial connections attempts per s seconds.\\n\"\n    \"--max-clients n : Allow a maximum of n simultaneously connected clients.\\n\"\n    \"--max-routes-per-client n : Allow a maximum of n internal routes per client.\\n\"\n    \"--stale-routes-check n [t] : Remove routes with a last activity timestamp\\n\"\n    \"                             older than n seconds. Run this check every t\\n\"\n    \"                             seconds (defaults to n).\\n\"\n    \"--explicit-exit-notify [n] : In UDP server mode send [RESTART] command on exit/restart to connected\\n\"\n    \"                             clients. n = 1 - reconnect to same server,\\n\"\n    \"                             2 - advance to next server, default=1.\\n\"\n#if PORT_SHARE\n    \"--port-share host port [dir] : When run in TCP mode, proxy incoming HTTPS\\n\"\n    \"                  sessions to a web server at host:port.  dir specifies an\\n\"\n    \"                  optional directory to write origin IP:port data.\\n\"\n#endif\n    \"\\n\"\n    \"Client options (when connecting to a multi-client server):\\n\"\n    \"--client         : Helper option to easily configure client mode.\\n\"\n    \"--auth-user-pass [up] : Authenticate with server using username/password.\\n\"\n    \"                  up is a file containing the username on the first line,\\n\"\n    \"                  and a password on the second. If either the password or both\\n\"\n    \"                  the username and the password are omitted OpenVPN will prompt\\n\"\n    \"                  for them from console.\\n\"\n    \"--pull           : Accept certain config file options from the peer as if they\\n\"\n    \"                  were part of the local config file.  Must be specified\\n\"\n    \"                  when connecting to a '--mode server' remote host.\\n\"\n    \"--pull-filter accept|ignore|reject t : Filter each option received from the\\n\"\n    \"                  server if it starts with the text t. The action flag accept,\\n\"\n    \"                  ignore or reject causes the option to be allowed, removed or\\n\"\n    \"                  rejected with error. May be specified multiple times, and\\n\"\n    \"                  each filter is applied in the order of appearance.\\n\"\n    \"--dns server <n> <option> <value> [value ...] : Configure option for DNS server #n\\n\"\n    \"                  Valid options are :\\n\"\n    \"                  address <addr[:port]> [addr[:port] ...] : server addresses 4/6\\n\"\n    \"                  resolve-domains <domain> [domain ...] : split domains\\n\"\n    \"                  dnssec <yes|no|optional> : option to use DNSSEC\\n\"\n    \"                  transport <DoH|DoT> : query server over HTTPS / TLS\\n\"\n    \"                  sni <domain> : DNS server name indication\\n\"\n    \"--dns search-domains <domain> [domain ...]:\\n\"\n    \"                  Add domains to DNS domain search list\\n\"\n    \"--dns-updown cmd|force|disable : Run cmd as user defined dns config command,\\n\"\n    \"                  force running the default script or disable running it.\\n\"\n    \"--auth-retry t  : How to handle auth failures.  Set t to\\n\"\n    \"                  none (default), interact, or nointeract.\\n\"\n    \"--static-challenge t e [<scrv1|concat>]: Enable static challenge/response protocol using\\n\"\n    \"                  challenge text t, with e indicating echo flag (0|1)\\n\"\n    \"                  and optional argument scrv1 or concat to use SCRV1 protocol or\"\n    \"                  concatenate response with password. Default is scrv1.\\n\"\n    \"--connect-timeout n : when polling possible remote servers to connect to\\n\"\n    \"                  in a round-robin fashion, spend no more than n seconds\\n\"\n    \"                  waiting for a response before trying the next server.\\n\"\n    \"--allow-recursive-routing : When this option is set, OpenVPN will not drop\\n\"\n    \"                  incoming tun packets with same destination as host.\\n\"\n    \"--explicit-exit-notify [n] : On exit/restart, send exit signal to\\n\"\n    \"                  server/remote. n = # of retries, default=1.\\n\"\n    \"\\n\"\n    \"Data Channel Encryption Options (must be compatible between peers):\\n\"\n    \"(These options are meaningful for both Static Key & TLS-mode)\\n\"\n    \"--auth alg      : Authenticate packets with HMAC using message\\n\"\n    \"                  digest algorithm alg (default=%s).\\n\"\n    \"                  (usually adds 16 or 20 bytes per packet)\\n\"\n    \"                  Set alg=none to disable authentication.\\n\"\n    \"--cipher alg    : Encrypt packets with cipher algorithm alg.\\n\"\n    \"                  You should usually use --data-ciphers instead.\\n\"\n    \"                  Set alg=none to disable encryption.\\n\"\n    \"--data-ciphers list : List of ciphers that are allowed to be negotiated.\\n\"\n#ifndef ENABLE_CRYPTO_MBEDTLS\n    \"--engine [name] : Enable OpenSSL hardware crypto engine functionality.\\n\"\n#endif\n    \"--mute-replay-warnings : Silence the output of replay warnings to log file.\\n\"\n    \"--replay-window n [t]  : Use a replay protection sliding window of size n\\n\"\n    \"                         and a time window of t seconds.\\n\"\n    \"                         Default n=%d t=%d\\n\"\n    \"--replay-persist file : Persist replay-protection state across sessions\\n\"\n    \"                  using file.\\n\"\n    \"--test-crypto   : Run a self-test of crypto features enabled.\\n\"\n    \"                  For debugging only.\\n\"\n    \"\\n\"\n    \"TLS Key Negotiation Options:\\n\"\n    \"(These options are meaningful only for TLS-mode)\\n\"\n    \"--tls-server    : Enable TLS and assume server role during TLS handshake.\\n\"\n    \"--tls-client    : Enable TLS and assume client role during TLS handshake.\\n\"\n    \"--ca file       : Certificate authority file in .pem format containing\\n\"\n    \"                  root certificate.\\n\"\n#ifndef ENABLE_CRYPTO_MBEDTLS\n    \"--capath dir    : A directory of trusted certificates (CAs\"\n    \" and CRLs).\\n\"\n#endif /* ENABLE_CRYPTO_MBEDTLS */\n    \"--dh file       : File containing Diffie Hellman parameters\\n\"\n    \"                  in .pem format (for --tls-server only).\\n\"\n    \"                  Use \\\"openssl dhparam -out dh1024.pem 1024\\\" to generate.\\n\"\n    \"--cert file     : Local certificate in .pem format or a URI -- must be signed\\n\"\n    \"                  by a Certificate Authority in --ca file used by the peer.\\n\"\n    \"--extra-certs file : one or more PEM certs that complete the cert chain.\\n\"\n    \"--key file      : Local private key in .pem format or a URI.\\n\"\n    \"--tls-version-min <version> ['or-highest'] : sets the minimum TLS version we\\n\"\n    \"    will accept from the peer.  If version is unrecognized and 'or-highest'\\n\"\n    \"    is specified, require max TLS version supported by SSL implementation.\\n\"\n    \"--tls-version-max <version> : sets the maximum TLS version we will use.\\n\"\n#ifndef ENABLE_CRYPTO_MBEDTLS\n    \"--pkcs12 file   : PKCS#12 file containing local private key, local certificate\\n\"\n    \"                  and optionally the root CA certificate.\\n\"\n    \"--x509-username-field : Field in x509 certificate containing the username.\\n\"\n    \"                        Default is CN in the Subject field.\\n\"\n#endif\n    \"--verify-hash hash [algo] : Specify fingerprint for level-1 certificate.\\n\"\n    \"                            Valid algo flags are SHA1 and SHA256. \\n\"\n#ifdef _WIN32\n    \"--cryptoapicert select-string : Load the certificate and private key from the\\n\"\n    \"                  Windows Certificate System Store.\\n\"\n#endif\n    \"--tls-cipher l  : A list l of allowable TLS ciphers separated by : (optional).\\n\"\n    \"--tls-ciphersuites l: A list of allowed TLS 1.3 cipher suites separated by : (optional)\\n\"\n    \"                : Use --show-tls to see a list of supported TLS ciphers (suites).\\n\"\n    \"--tls-cert-profile p : Set the allowed certificate crypto algorithm profile\\n\"\n    \"                  (default=legacy).\\n\"\n    \"--providers l   : A list l of OpenSSL providers to load.\\n\"\n    \"--tls-timeout n : Packet retransmit timeout on TLS control channel\\n\"\n    \"                  if no ACK from remote within n seconds (default=%d).\\n\"\n    \"--reneg-bytes n : Renegotiate data chan. key after n bytes sent and recvd.\\n\"\n    \"--reneg-pkts n  : Renegotiate data chan. key after n packets sent and recvd.\\n\"\n    \"--reneg-sec max [min] : Renegotiate data chan. key after at most max (default=%d)\\n\"\n    \"                  and at least min (defaults to 90%% of max on servers and equal\\n\"\n    \"                  to max on clients).\\n\"\n    \"--hand-window n : Data channel key exchange must finalize within n seconds\\n\"\n    \"                  of handshake initiation by any peer (default=%d).\\n\"\n    \"--tran-window n : Transition window -- old key can live this many seconds\\n\"\n    \"                  after new key renegotiation begins (default=%d).\\n\"\n    \"--single-session: Allow only one session (reset state on restart).\\n\"\n    \"--tls-exit      : Exit on TLS negotiation failure.\\n\"\n    \"--tls-auth f [d]: Add an additional layer of authentication on top of the TLS\\n\"\n    \"                  control channel to protect against attacks on the TLS stack\\n\"\n    \"                  and DoS attacks.\\n\"\n    \"                  f (required) is a shared-secret key file.\\n\"\n    \"                  The optional d parameter controls key directionality.\\n\"\n    \"--tls-crypt key : Add an additional layer of authenticated encryption on top\\n\"\n    \"                  of the TLS control channel to hide the TLS certificate,\\n\"\n    \"                  provide basic post-quantum security and protect against\\n\"\n    \"                  attacks on the TLS stack and DoS attacks.\\n\"\n    \"                  key (required) provides the pre-shared key file.\\n\"\n    \"--tls-crypt-v2 key : For clients: use key as a client-specific tls-crypt key.\\n\"\n    \"                  For servers: use key to decrypt client-specific keys.  For\\n\"\n    \"                  key generation (--genkey tls-crypt-v2-client): use key to\\n\"\n    \"                  encrypt generated client-specific key.  (See --tls-crypt.)\\n\"\n    \"--genkey tls-crypt-v2-client [keyfile] [base64 metadata]: Generate a\\n\"\n    \"                  fresh tls-crypt-v2 client key, and store to\\n\"\n    \"                  keyfile.  If supplied, include metadata in wrapped key.\\n\"\n    \"--genkey tls-crypt-v2-server [keyfile] [base64 metadata]: Generate a\\n\"\n    \"                  fresh tls-crypt-v2 server key, and store to keyfile\\n\"\n    \"--tls-crypt-v2-verify cmd : Run command cmd to verify the metadata of the\\n\"\n    \"                  client-supplied tls-crypt-v2 client key\\n\"\n    \"--tls-crypt-v2-max-age n : Only accept tls-crypt-v2 client keys that have a\\n\"\n    \"                  timestamp which is at most n days old.\\n\"\n    \"--askpass [file]: Get PEM password from controlling tty before we daemonize.\\n\"\n    \"--auth-nocache  : Don't cache --askpass or --auth-user-pass passwords.\\n\"\n    \"--crl-verify crl ['dir']: Check peer certificate against a CRL.\\n\"\n    \"--tls-verify cmd: Run command cmd to verify the X509 name of a\\n\"\n    \"                  pending TLS connection that has otherwise passed all other\\n\"\n    \"                  tests of certification.  cmd should return 0 to allow\\n\"\n    \"                  TLS handshake to proceed, or 1 to fail.  (cmd is\\n\"\n    \"                  executed as 'cmd certificate_depth subject')\\n\"\n    \"--verify-x509-name name: Accept connections only from a host with X509 subject\\n\"\n    \"                  DN name. The remote host must also pass all other tests\\n\"\n    \"                  of verification.\\n\"\n#ifndef ENABLE_CRYPTO_MBEDTLS\n    \"--ns-cert-type t: (DEPRECATED) Require that peer certificate was signed with \\n\"\n    \"                  an explicit nsCertType designation t = 'client' | 'server'.\\n\"\n#endif\n    \"--x509-track x  : Save peer X509 attribute x in environment for use by\\n\"\n    \"                  plugins and management interface.\\n\"\n    \"--keying-material-exporter label len : Save Exported Keying Material (RFC5705)\\n\"\n    \"                  of len bytes (min. 16 bytes) using label in environment for use by plugins.\\n\"\n    \"--remote-cert-ku v ... : Require that the peer certificate was signed with\\n\"\n    \"                  explicit key usage, you can specify more than one value.\\n\"\n    \"                  value should be given in hex format.\\n\"\n    \"--remote-cert-eku oid : Require that the peer certificate was signed with\\n\"\n    \"                  explicit extended key usage. Extended key usage can be encoded\\n\"\n    \"                  as an object identifier or OpenSSL string representation.\\n\"\n    \"--remote-cert-tls t: Require that peer certificate was signed with explicit\\n\"\n    \"                  key usage and extended key usage based on RFC3280 TLS rules.\\n\"\n    \"                  t = 'client' | 'server'.\\n\"\n#ifdef ENABLE_PKCS11\n    \"\\n\"\n    \"PKCS#11 Options:\\n\"\n    \"--pkcs11-providers provider ... : PKCS#11 provider to load.\\n\"\n    \"--pkcs11-protected-authentication [0|1] ... : Use PKCS#11 protected authentication\\n\"\n    \"                              path. Set for each provider.\\n\"\n    \"--pkcs11-private-mode hex ...   : PKCS#11 private key mode mask.\\n\"\n    \"                              0       : Try  to determine automatically (default).\\n\"\n    \"                              1       : Use Sign.\\n\"\n    \"                              2       : Use SignRecover.\\n\"\n    \"                              4       : Use Decrypt.\\n\"\n    \"                              8       : Use Unwrap.\\n\"\n    \"--pkcs11-cert-private [0|1] ... : Set if login should be performed before\\n\"\n    \"                                  certificate can be accessed. Set for each provider.\\n\"\n    \"--pkcs11-pin-cache seconds      : Number of seconds to cache PIN. The default is -1\\n\"\n    \"                                  cache until token is removed.\\n\"\n    \"--pkcs11-id-management          : Acquire identity from management interface.\\n\"\n    \"--pkcs11-id serialized-id 'id'  : Identity to use, get using standalone --show-pkcs11-ids\\n\"\n#endif /* ENABLE_PKCS11 */\n    \"\\n\"\n    \"SSL Library information:\\n\"\n    \"--show-ciphers  : Show cipher algorithms to use with --cipher option.\\n\"\n    \"--show-digests  : Show message digest algorithms to use with --auth option.\\n\"\n    \"--show-engines  : Show hardware crypto accelerator engines (if available).\\n\"\n    \"--show-tls      : Show all TLS ciphers (TLS used only as a control channel).\\n\"\n#ifdef _WIN32\n    \"\\n\"\n    \"Windows Specific:\\n\"\n    \"--win-sys path    : Pathname of Windows system directory. Default is the pathname\\n\"\n    \"                    from SystemRoot environment variable.\\n\"\n    \"--ip-win32 method : When using --ifconfig on Windows, set TAP-Windows adapter\\n\"\n    \"                    IP address using method = manual, netsh, ipapi,\\n\"\n    \"                    dynamic, or adaptive (default = adaptive).\\n\"\n    \"                    Dynamic method allows two optional parameters:\\n\"\n    \"                    offset: DHCP server address offset (> -256 and < 256).\\n\"\n    \"                            If 0, use network address, if >0, take nth\\n\"\n    \"                            address forward from network address, if <0,\\n\"\n    \"                            take nth address backward from broadcast\\n\"\n    \"                            address.\\n\"\n    \"                            Default is 0.\\n\"\n    \"                    lease-time: Lease time in seconds.\\n\"\n    \"                                Default is one year.\\n\"\n    \"--route-method    : Which method to use for adding routes on Windows?\\n\"\n    \"                    adaptive (default) -- Try ipapi then fall back to exe.\\n\"\n    \"                    ipapi -- Use IP helper API.\\n\"\n    \"                    exe -- Call the route.exe shell command.\\n\"\n    \"--dhcp-option type [parm] : Set extended TAP-Windows properties, must\\n\"\n    \"                    be used with --ip-win32 dynamic.  For options\\n\"\n    \"                    which allow multiple addresses,\\n\"\n    \"                    --dhcp-option must be repeated.\\n\"\n    \"                    DOMAIN name : Set DNS suffix\\n\"\n    \"                    DOMAIN-SEARCH entry : Add entry to DNS domain search list\\n\"\n    \"                    DNS addr    : Set domain name server address(es) (IPv4 and IPv6)\\n\"\n    \"                    NTP         : Set NTP server address(es)\\n\"\n    \"                    NBDD        : Set NBDD server address(es)\\n\"\n    \"                    WINS addr   : Set WINS server address(es)\\n\"\n    \"                    NBT type    : Set NetBIOS over TCP/IP Node type\\n\"\n    \"                                  1: B, 2: P, 4: M, 8: H\\n\"\n    \"                    NBS id      : Set NetBIOS scope ID\\n\"\n    \"                    DISABLE-NBT : Disable Netbios-over-TCP/IP.\\n\"\n    \"--dhcp-renew       : Ask Windows to renew the TAP adapter lease on startup.\\n\"\n    \"--dhcp-pre-release : Ask Windows to release the previous TAP adapter lease on\\n\"\n    \"                       startup.\\n\"\n    \"--register-dns  : Run ipconfig /flushdns and ipconfig /registerdns\\n\"\n    \"                  on connection initiation.\\n\"\n    \"--tap-sleep n   : Sleep for n seconds after TAP adapter open before\\n\"\n    \"                  attempting to set adapter properties.\\n\"\n    \"--pause-exit         : When run from a console window, pause before exiting.\\n\"\n    \"--service ex [0|1]   : For use when \" PACKAGE_NAME \" is being instantiated by a\\n\"\n    \"                       service, and should not be used directly by end-users.\\n\"\n    \"                       ex is the name of an event object which, when\\n\"\n    \"                       signaled, will cause \" PACKAGE_NAME \" to exit.  A second\\n\"\n    \"                       optional parameter controls the initial state of ex.\\n\"\n    \"--show-net-up   : Show \" PACKAGE_NAME \"'s view of routing table and net adapter list\\n\"\n    \"                  after TAP adapter is up and routes have been added.\\n\"\n    \"--block-outside-dns   : Block DNS on other network adapters to prevent DNS leaks\\n\"\n    \"Windows Standalone Options:\\n\"\n    \"\\n\"\n    \"--show-adapters : Show all TAP-Windows adapters.\\n\"\n    \"--show-net      : Show \" PACKAGE_NAME \"'s view of routing table and net adapter list.\\n\"\n    \"--show-valid-subnets : Show valid subnets for --dev tun emulation.\\n\"\n    \"--allow-nonadmin [TAP-adapter] : Allow \" PACKAGE_NAME \" running without admin privileges\\n\"\n    \"                                 to access TAP adapter.\\n\"\n#endif /* ifdef _WIN32 */\n    \"\\n\"\n    \"Generate a new key :\\n\"\n    \"--genkey tls-auth file   : Generate a new random key of type and write to file\\n\"\n    \"                         (for use with --tls-auth or --tls-crypt).\"\n#ifdef ENABLE_FEATURE_TUN_PERSIST\n    \"\\n\"\n    \"Tun/tap config mode:\\n\"\n    \"--mktun         : Create a persistent tunnel.\\n\"\n    \"--rmtun         : Remove a persistent tunnel.\\n\"\n    \"--dev tunX|tapX : tun/tap device\\n\"\n    \"--dev-type dt   : Device type.  See tunnel options above for details.\\n\"\n    \"--user user     : User to set privilege to.\\n\"\n    \"--group group   : Group to set privilege to.\\n\"\n#endif\n#ifdef ENABLE_PKCS11\n    \"\\n\"\n    \"PKCS#11 standalone options:\\n\"\n#ifdef DEFAULT_PKCS11_MODULE\n    \"--show-pkcs11-ids [provider] [cert_private] : Show PKCS#11 available ids.\\n\"\n#else\n    \"--show-pkcs11-ids provider [cert_private] : Show PKCS#11 available ids.\\n\"\n#endif\n    \"                                            --verb option can be added *BEFORE* this.\\n\"\n#endif /* ENABLE_PKCS11 */\n    \"\\n\"\n    \"General Standalone Options:\\n\"\n#ifdef ENABLE_DEBUG\n    \"--show-gateway [address]: Show info about gateway [to v4/v6 address].\\n\"\n#endif\n    ;\n\n#endif /* !ENABLE_SMALL */\n\n/*\n * This is where the options defaults go.\n * Any option not explicitly set here\n * will be set to 0.\n */\nvoid\ninit_options(struct options *o)\n{\n    CLEAR(*o);\n    gc_init(&o->gc);\n    gc_init(&o->dns_options.gc);\n\n    o->mode = MODE_POINT_TO_POINT;\n    o->topology = TOP_UNDEF;\n    o->ce.proto = PROTO_UDP;\n    o->ce.af = AF_UNSPEC;\n    o->ce.bind_ipv6_only = false;\n    o->ce.connect_retry_seconds = 1;\n    o->ce.connect_retry_seconds_max = 300;\n    o->ce.connect_timeout = 120;\n    o->connect_retry_max = 0;\n    o->ce.local_port = o->ce.remote_port = OPENVPN_PORT;\n    o->verbosity = 1;\n    o->status_file_update_freq = 60;\n    o->status_file_version = 1;\n    o->ce.bind_local = true;\n    o->ce.tun_mtu = TUN_MTU_DEFAULT;\n    o->ce.occ_mtu = 0;\n    o->ce.link_mtu = LINK_MTU_DEFAULT;\n    o->ce.tls_mtu = TLS_MTU_DEFAULT;\n    o->ce.mtu_discover_type = -1;\n    o->ce.mssfix = 0;\n    o->ce.mssfix_default = true;\n    o->ce.mssfix_encap = true;\n    o->route_default_table_id = 0;\n    o->route_delay_window = 30;\n    o->resolve_retry_seconds = RESOLV_RETRY_INFINITE;\n    o->resolve_in_advance = false;\n    o->proto_force = -1;\n    o->occ = true;\n#ifdef ENABLE_MANAGEMENT\n    o->management_log_history_cache = 250;\n    o->management_echo_buffer_size = 100;\n    o->management_state_buffer_size = 100;\n#endif\n#ifdef ENABLE_FEATURE_TUN_PERSIST\n    o->persist_mode = 1;\n#endif\n#ifdef _WIN32\n#if 0\n    o->tuntap_options.ip_win32_type = IPW32_SET_ADAPTIVE;\n#else\n    o->tuntap_options.ip_win32_type = IPW32_SET_DHCP_MASQ;\n#endif\n    o->tuntap_options.dhcp_lease_time = 31536000; /* one year */\n    /* use network address as internal DHCP server address */\n    o->tuntap_options.dhcp_masq_offset = 0;\n    o->route_method = ROUTE_METHOD_ADAPTIVE;\n    o->block_outside_dns = false;\n    o->windows_driver = WINDOWS_DRIVER_UNSPECIFIED;\n#endif\n    o->vlan_accept = VLAN_ALL;\n    o->vlan_pvid = 1;\n    o->real_hash_size = 256;\n    o->virtual_hash_size = 256;\n    o->n_bcast_buf = 256;\n    o->tcp_queue_limit = 64;\n    o->max_clients = 1024;\n    o->cf_initial_per = 10;\n    o->cf_initial_max = 100;\n    o->max_routes_per_client = 256;\n    o->stale_routes_check_interval = 0;\n    o->ifconfig_pool_persist_refresh_freq = 600;\n    o->scheduled_exit_interval = 5;\n    o->authname = \"SHA1\";\n    o->replay_window = DEFAULT_SEQ_BACKTRACK;\n    o->replay_time = DEFAULT_TIME_BACKTRACK;\n    o->key_direction = KEY_DIRECTION_BIDIRECTIONAL;\n    o->tls_timeout = 2;\n    o->renegotiate_bytes = -1;\n    o->renegotiate_seconds = 3600;\n    o->renegotiate_seconds_min = -1;\n    o->handshake_window = 60;\n    o->transition_window = 3600;\n    o->tls_cert_profile = NULL;\n    o->ecdh_curve = NULL;\n    o->x509_username_field[0] = X509_USERNAME_FIELD_DEFAULT;\n#ifdef ENABLE_PKCS11\n    o->pkcs11_pin_cache_period = -1;\n#endif /* ENABLE_PKCS11 */\n\n    /* P2MP server context features */\n    o->auth_token_generate = false;\n\n    /* Set default --tmp-dir */\n#ifdef _WIN32\n    /* On Windows, find temp dir via environment variables */\n    o->tmp_dir = win_get_tempdir();\n\n    if (!o->tmp_dir)\n    {\n        /* Error out if we can't find a valid temporary directory, which should\n         * be very unlikely. */\n        msg(M_USAGE, \"Could not find a suitable temporary directory.\"\n                     \" (GetTempPath() failed).  Consider using --tmp-dir\");\n    }\n#else  /* ifdef _WIN32 */\n    /* Non-windows platforms use $TMPDIR, and if not set, default to '/tmp' */\n    o->tmp_dir = getenv(\"TMPDIR\");\n    if (!o->tmp_dir)\n    {\n        o->tmp_dir = \"/tmp\";\n    }\n#endif /* _WIN32 */\n    o->allow_recursive_routing = false;\n\n#ifndef ENABLE_DCO\n    o->disable_dco = true;\n#endif /* ENABLE_DCO */\n\n#ifdef ENABLE_DNS_UPDOWN_BY_DEFAULT\n    o->dns_options.updown = DEFAULT_DNS_UPDOWN;\n#endif /* ENABLE_DNS_UPDOWN_BY_DEFAULT */\n}\n\nvoid\nuninit_options(struct options *o)\n{\n    if (o->connection_list)\n    {\n        CLEAR(*o->connection_list);\n    }\n    if (o->remote_list)\n    {\n        CLEAR(*o->remote_list);\n    }\n\n    gc_free(&o->gc);\n    gc_free(&o->dns_options.gc);\n}\n\n#ifndef ENABLE_SMALL\n\nstatic const char *\npull_filter_type_name(int type)\n{\n    if (type == PUF_TYPE_ACCEPT)\n    {\n        return \"accept\";\n    }\n    if (type == PUF_TYPE_IGNORE)\n    {\n        return \"ignore\";\n    }\n    if (type == PUF_TYPE_REJECT)\n    {\n        return \"reject\";\n    }\n    else\n    {\n        return \"???\";\n    }\n}\n\n#define SHOW_PARM(name, value, format) msg(D_SHOW_PARMS, \"  \" #name \" = \" format, (value))\n#define SHOW_STR(var)                  SHOW_PARM(var, (o->var ? o->var : \"[UNDEF]\"), \"'%s'\")\n#define SHOW_STR_INLINE(var) \\\n    SHOW_PARM(var, o->var##_inline ? \"[INLINE]\" : (o->var ? o->var : \"[UNDEF]\"), \"'%s'\")\n#define SHOW_INT(var)      SHOW_PARM(var, o->var, \"%d\")\n#define SHOW_UINT(var)     SHOW_PARM(var, o->var, \"%u\")\n#define SHOW_INT64(var)    SHOW_PARM(var, o->var, \"%\" PRIi64)\n#define SHOW_UNSIGNED(var) SHOW_PARM(var, o->var, \"0x%08x\")\n#define SHOW_BOOL(var)     SHOW_PARM(var, (o->var ? \"ENABLED\" : \"DISABLED\"), \"%s\");\n\n#endif /* ifndef ENABLE_SMALL */\n\nstatic void\nsetenv_connection_entry(struct env_set *es, const struct connection_entry *e, const int i)\n{\n    setenv_str_i(es, \"remote\", e->remote, i);\n    setenv_str_i(es, \"remote_port\", e->remote_port, i);\n\n    if (e->http_proxy_options)\n    {\n        setenv_str_i(es, \"http_proxy_server\", e->http_proxy_options->server, i);\n        setenv_str_i(es, \"http_proxy_port\", e->http_proxy_options->port, i);\n    }\n    if (e->socks_proxy_server)\n    {\n        setenv_str_i(es, \"socks_proxy_server\", e->socks_proxy_server, i);\n        setenv_str_i(es, \"socks_proxy_port\", e->socks_proxy_port, i);\n    }\n}\n\nstatic void\nsetenv_local_entry(struct env_set *es, const struct local_entry *e, const int i)\n{\n    setenv_str_i(es, \"proto\", proto2ascii(e->proto, AF_UNSPEC, false), i);\n    setenv_str_i(es, \"local\", e->local, i);\n    setenv_str_i(es, \"local_port\", e->port, i);\n}\n\nvoid\nsetenv_settings(struct env_set *es, const struct options *o)\n{\n    setenv_str(es, \"config\", o->config);\n    setenv_int(es, \"verb\", o->verbosity);\n    setenv_int(es, \"daemon\", o->daemon);\n    setenv_int(es, \"daemon_log_redirect\", o->log);\n    setenv_long_long(es, \"daemon_start_time\", time(NULL));\n    setenv_int(es, \"daemon_pid\", platform_getpid());\n\n    if (o->connection_list)\n    {\n        int i;\n        for (i = 0; i < o->connection_list->len; ++i)\n        {\n            setenv_connection_entry(es, o->connection_list->array[i], i + 1);\n        }\n    }\n    else\n    {\n        setenv_connection_entry(es, &o->ce, 1);\n    }\n\n    if (o->ce.local_list)\n    {\n        for (int i = 0; i < o->ce.local_list->len; i++)\n        {\n            setenv_local_entry(es, o->ce.local_list->array[i], i + 1);\n        }\n    }\n}\n\n#ifndef _WIN32\nstatic void\nsetenv_foreign_option(struct options *o, const char *option, const char *value, struct env_set *es)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer env_name = alloc_buf_gc(OPTION_PARM_SIZE, &gc);\n    struct buffer env_value = alloc_buf_gc(OPTION_PARM_SIZE, &gc);\n    bool good = true;\n\n    good &= buf_printf(&env_name, \"foreign_option_%d\", o->foreign_option_index + 1);\n    if (value)\n    {\n        good &= buf_printf(&env_value, \"dhcp-option %s %s\", option, value);\n    }\n    else\n    {\n        good &= buf_printf(&env_value, \"dhcp-option %s\", option);\n    }\n    if (good)\n    {\n        setenv_str(es, BSTR(&env_name), BSTR(&env_value));\n        ++o->foreign_option_index;\n    }\n    else\n    {\n        msg(M_WARN, \"foreign_option: name/value overflow\");\n    }\n    gc_free(&gc);\n}\n\nstatic void\ndelete_all_dhcp_fo(struct options *o, struct env_item **list)\n{\n    struct env_item *current, *prev;\n\n    ASSERT(list);\n\n    for (current = *list, prev = NULL; current != NULL; current = current->next)\n    {\n        char *tmp_value = NULL;\n        if (!strncmp(current->string, \"foreign_option_\", sizeof(\"foreign_option_\") - 1))\n        {\n            tmp_value = strchr(current->string, '=');\n            if (tmp_value && ++tmp_value)\n            {\n                if (!strncmp(tmp_value, \"dhcp-option \", sizeof(\"dhcp-option \") - 1))\n                {\n                    if (prev)\n                    {\n                        prev->next = current->next;\n                    }\n                    else\n                    {\n                        *list = current->next;\n                    }\n                    o->foreign_option_index--;\n                }\n            }\n        }\n        prev = current;\n    }\n}\n\n#endif /* ifndef _WIN32 */\n\nstatic in_addr_t\nget_ip_addr(const char *ip_string, msglvl_t msglevel, bool *error)\n{\n    unsigned int flags = GETADDR_HOST_ORDER;\n    bool succeeded = false;\n    in_addr_t ret;\n\n    if (msglevel & M_FATAL)\n    {\n        flags |= GETADDR_FATAL;\n    }\n\n    ret = getaddr(flags, ip_string, 0, &succeeded, NULL);\n    if (!succeeded && error)\n    {\n        *error = true;\n    }\n    return ret;\n}\n\n/**\n * Returns newly allocated string containing address part without \"/nn\".\n *\n * If gc != NULL, the allocated memory is registered in the supplied gc.\n */\nstatic char *\nget_ipv6_addr_no_netbits(const char *addr, struct gc_arena *gc)\n{\n    const char *end = strchr(addr, '/');\n    char *ret = NULL;\n    if (NULL == end)\n    {\n        ret = string_alloc(addr, gc);\n    }\n    else\n    {\n        size_t len = end - addr;\n        ret = gc_malloc(len + 1, true, gc);\n        memcpy(ret, addr, len);\n    }\n    return ret;\n}\n\nstatic bool\nipv6_addr_safe_hexplusbits(const char *ipv6_prefix_spec)\n{\n    return get_ipv6_addr(ipv6_prefix_spec, NULL, NULL, M_WARN);\n}\n\nstatic char *\nstring_substitute(const char *src, char from, char to, struct gc_arena *gc)\n{\n    char *ret = (char *)gc_malloc(strlen(src) + 1, true, gc);\n    char *dest = ret;\n    char c;\n\n    do\n    {\n        c = *src++;\n        if (c == from)\n        {\n            c = to;\n        }\n        *dest++ = c;\n    } while (c);\n    return ret;\n}\n\n/**\n * Parses a hexstring and checks if the string has the correct length. Return\n * a verify_hash_list containing the parsed hash string.\n *\n * @param   str         String to check/parse\n * @param   nbytes      Number of bytes expected in the hexstr (e.g. 20 for SHA1)\n * @param   msglevel    message level to use when printing warnings/errors\n * @param   gc          The returned object will be allocated in this gc\n */\nstatic struct verify_hash_list *\nparse_hash_fingerprint(const char *str, int nbytes, msglvl_t msglevel, struct gc_arena *gc)\n{\n    int i = 0;\n    const char *cp = str;\n\n    struct verify_hash_list *ret;\n    ALLOC_OBJ_CLEAR_GC(ret, struct verify_hash_list, gc);\n\n    char term = 0;\n    unsigned int byte;\n\n    while (*cp && i < nbytes)\n    {\n        /* valid segments consist of exactly two hex digits, then ':' or EOS */\n        if (!isxdigit(cp[0]) || !isxdigit(cp[1]) || (cp[2] != ':' && cp[2] != '\\0')\n            || sscanf(cp, \"%x\", &byte) != 1)\n        {\n            msg(msglevel, \"format error in hash fingerprint: %s\", str);\n            break;\n        }\n\n        ret->hash[i++] = (uint8_t)byte;\n\n        term = cp[2];\n        if (term == '\\0')\n        {\n            break;\n        }\n        cp += 3;\n    }\n    if (i < nbytes)\n    {\n        msg(msglevel, \"hash fingerprint is wrong length - expected %d bytes, got %d: %s\", nbytes, i,\n            str);\n    }\n    else if (term != '\\0')\n    {\n        msg(msglevel, \"hash fingerprint too long - expected only %d bytes: %s\", nbytes, str);\n    }\n    return ret;\n}\n\n/**\n * Parses a string consisting of multiple lines of hexstrings and checks if each\n * string has the correct length. Empty lines are ignored. Returns\n * a linked list of (possibly) multiple verify_hash_list objects.\n *\n * @param   str         String to check/parse\n * @param   nbytes      Number of bytes expected in the hexstring (e.g. 20 for SHA1)\n * @param   msglevel    message level to use when printing warnings/errors\n * @param   gc          The returned list items will be allocated in this gc\n */\nstatic struct verify_hash_list *\nparse_hash_fingerprint_multiline(const char *str, int nbytes, msglvl_t msglevel,\n                                 struct gc_arena *gc)\n{\n    struct gc_arena gc_temp = gc_new();\n    char *lines = string_alloc(str, &gc_temp);\n\n    struct verify_hash_list *ret = NULL;\n\n    const char *line;\n    while ((line = strsep(&lines, \"\\n\")))\n    {\n        /* ignore leading whitespace */\n        while (isspace(*line))\n        {\n            line++;\n        }\n        /* skip empty lines and comment lines */\n        if (strlen(line) == 0 || *line == '#' || *line == ';')\n        {\n            continue;\n        }\n\n        struct verify_hash_list *hash = parse_hash_fingerprint(line, nbytes, msglevel, gc);\n\n        if (!hash)\n        {\n            gc_free(&gc_temp);\n            return NULL;\n        }\n\n        hash->next = ret;\n        ret = hash;\n    }\n    gc_free(&gc_temp);\n\n    return ret;\n}\n#ifdef _WIN32\n\n#ifndef ENABLE_SMALL\n\nstatic void\nshow_dhcp_option_list(const char *name, const char *const *array, unsigned int len)\n{\n    for (unsigned int i = 0; i < len; ++i)\n    {\n        msg(D_SHOW_PARMS, \"  %s[%u] = %s\", name, i, array[i]);\n    }\n}\n\nstatic void\nshow_dhcp_option_addrs(const char *name, const in_addr_t *array, unsigned int len)\n{\n    struct gc_arena gc = gc_new();\n    for (unsigned int i = 0; i < len; ++i)\n    {\n        msg(D_SHOW_PARMS, \"  %s[%u] = %s\", name, i, print_in_addr_t(array[i], 0, &gc));\n    }\n    gc_free(&gc);\n}\n\nstatic void\nshow_tuntap_options(const struct tuntap_options *o)\n{\n    SHOW_BOOL(ip_win32_defined);\n    SHOW_INT(ip_win32_type);\n    SHOW_INT(dhcp_masq_offset);\n    SHOW_INT(dhcp_lease_time);\n    SHOW_INT(tap_sleep);\n    SHOW_UNSIGNED(dhcp_options);\n    SHOW_BOOL(dhcp_renew);\n    SHOW_BOOL(dhcp_pre_release);\n    SHOW_STR(domain);\n    SHOW_STR(netbios_scope);\n    SHOW_UNSIGNED(netbios_node_type);\n    SHOW_BOOL(disable_nbt);\n\n    show_dhcp_option_addrs(\"DNS\", o->dns, o->dns_len);\n    show_dhcp_option_addrs(\"WINS\", o->wins, o->wins_len);\n    show_dhcp_option_addrs(\"NTP\", o->ntp, o->ntp_len);\n    show_dhcp_option_addrs(\"NBDD\", o->nbdd, o->nbdd_len);\n    show_dhcp_option_list(\"DOMAIN-SEARCH\", o->domain_search_list, o->domain_search_list_len);\n}\n\n#endif /* ifndef ENABLE_SMALL */\n#endif /* ifdef _WIN32 */\n\nstatic void\ndhcp_option_dns6_parse(const char *parm, struct in6_addr *dns6_list, unsigned int *len, msglvl_t msglevel)\n{\n    struct in6_addr addr;\n    if (*len >= N_DHCP_ADDR)\n    {\n        msg(msglevel, \"--dhcp-option DNS: maximum of %u IPv6 dns servers can be specified\",\n            N_DHCP_ADDR);\n    }\n    else if (get_ipv6_addr(parm, &addr, NULL, msglevel))\n    {\n        dns6_list[(*len)++] = addr;\n    }\n}\nstatic void\ndhcp_option_address_parse(const char *name, const char *parm, in_addr_t *array, unsigned int *len,\n                          msglvl_t msglevel)\n{\n    if (*len >= N_DHCP_ADDR)\n    {\n        msg(msglevel, \"--dhcp-option %s: maximum of %u %s servers can be specified\", name,\n            N_DHCP_ADDR, name);\n    }\n    else\n    {\n        if (ip_addr_dotted_quad_safe(parm)) /* FQDN -- IP address only */\n        {\n            bool error = false;\n            const in_addr_t addr = get_ip_addr(parm, msglevel, &error);\n            if (!error)\n            {\n                array[(*len)++] = addr;\n            }\n        }\n        else\n        {\n            msg(msglevel, \"dhcp-option parameter %s '%s' must be an IP address\", name, parm);\n        }\n    }\n}\n\n#ifndef ENABLE_SMALL\nstatic const char *\nprint_vlan_accept(enum vlan_acceptable_frames mode)\n{\n    switch (mode)\n    {\n        case VLAN_ONLY_TAGGED:\n            return \"tagged\";\n\n        case VLAN_ONLY_UNTAGGED_OR_PRIORITY:\n            return \"untagged\";\n\n        case VLAN_ALL:\n            return \"all\";\n    }\n    return NULL;\n}\n\nstatic void\nshow_p2mp_parms(const struct options *o)\n{\n    struct gc_arena gc = gc_new();\n\n    msg(D_SHOW_PARMS, \"  server_network = %s\", print_in_addr_t(o->server_network, 0, &gc));\n    msg(D_SHOW_PARMS, \"  server_netmask = %s\", print_in_addr_t(o->server_netmask, 0, &gc));\n    msg(D_SHOW_PARMS, \"  server_network_ipv6 = %s\", print_in6_addr(o->server_network_ipv6, 0, &gc));\n    SHOW_INT(server_netbits_ipv6);\n    msg(D_SHOW_PARMS, \"  server_bridge_ip = %s\", print_in_addr_t(o->server_bridge_ip, 0, &gc));\n    msg(D_SHOW_PARMS, \"  server_bridge_netmask = %s\",\n        print_in_addr_t(o->server_bridge_netmask, 0, &gc));\n    msg(D_SHOW_PARMS, \"  server_bridge_pool_start = %s\",\n        print_in_addr_t(o->server_bridge_pool_start, 0, &gc));\n    msg(D_SHOW_PARMS, \"  server_bridge_pool_end = %s\",\n        print_in_addr_t(o->server_bridge_pool_end, 0, &gc));\n    if (o->push_list.head)\n    {\n        const struct push_entry *e = o->push_list.head;\n        while (e)\n        {\n            if (e->enable)\n            {\n                msg(D_SHOW_PARMS, \"  push_entry = '%s'\", e->option);\n            }\n            e = e->next;\n        }\n    }\n    SHOW_BOOL(ifconfig_pool_defined);\n    msg(D_SHOW_PARMS, \"  ifconfig_pool_start = %s\",\n        print_in_addr_t(o->ifconfig_pool_start, 0, &gc));\n    msg(D_SHOW_PARMS, \"  ifconfig_pool_end = %s\", print_in_addr_t(o->ifconfig_pool_end, 0, &gc));\n    msg(D_SHOW_PARMS, \"  ifconfig_pool_netmask = %s\",\n        print_in_addr_t(o->ifconfig_pool_netmask, 0, &gc));\n    SHOW_STR(ifconfig_pool_persist_filename);\n    SHOW_INT(ifconfig_pool_persist_refresh_freq);\n    SHOW_BOOL(ifconfig_ipv6_pool_defined);\n    msg(D_SHOW_PARMS, \"  ifconfig_ipv6_pool_base = %s\",\n        print_in6_addr(o->ifconfig_ipv6_pool_base, 0, &gc));\n    SHOW_INT(ifconfig_ipv6_pool_netbits);\n    SHOW_INT(n_bcast_buf);\n    SHOW_INT(tcp_queue_limit);\n    SHOW_INT(real_hash_size);\n    SHOW_INT(virtual_hash_size);\n    SHOW_STR(client_connect_script);\n    SHOW_STR(learn_address_script);\n    SHOW_STR(client_disconnect_script);\n    SHOW_STR(client_crresponse_script);\n    SHOW_STR(client_config_dir);\n    SHOW_BOOL(ccd_exclusive);\n    SHOW_STR(tmp_dir);\n    SHOW_BOOL(push_ifconfig_defined);\n    msg(D_SHOW_PARMS, \"  push_ifconfig_local = %s\",\n        print_in_addr_t(o->push_ifconfig_local, 0, &gc));\n    msg(D_SHOW_PARMS, \"  push_ifconfig_remote_netmask = %s\",\n        print_in_addr_t(o->push_ifconfig_remote_netmask, 0, &gc));\n    SHOW_BOOL(push_ifconfig_ipv6_defined);\n    msg(D_SHOW_PARMS, \"  push_ifconfig_ipv6_local = %s/%d\",\n        print_in6_addr(o->push_ifconfig_ipv6_local, 0, &gc), o->push_ifconfig_ipv6_netbits);\n    msg(D_SHOW_PARMS, \"  push_ifconfig_ipv6_remote = %s\",\n        print_in6_addr(o->push_ifconfig_ipv6_remote, 0, &gc));\n    SHOW_BOOL(enable_c2c);\n    SHOW_BOOL(duplicate_cn);\n    SHOW_INT(cf_max);\n    SHOW_INT(cf_per);\n    SHOW_INT(cf_initial_max);\n    SHOW_INT(cf_initial_per);\n    SHOW_INT(max_clients);\n    SHOW_INT(max_routes_per_client);\n    SHOW_STR(auth_user_pass_verify_script);\n    SHOW_BOOL(auth_user_pass_verify_script_via_file);\n    SHOW_BOOL(auth_token_generate);\n    SHOW_BOOL(force_key_material_export);\n    SHOW_INT(auth_token_lifetime);\n    SHOW_STR_INLINE(auth_token_secret_file);\n#if PORT_SHARE\n    SHOW_STR(port_share_host);\n    SHOW_STR(port_share_port);\n#endif\n    SHOW_BOOL(vlan_tagging);\n    msg(D_SHOW_PARMS, \"  vlan_accept = %s\", print_vlan_accept(o->vlan_accept));\n    SHOW_INT(vlan_pvid);\n\n    SHOW_BOOL(client);\n    SHOW_BOOL(pull);\n    SHOW_STR_INLINE(auth_user_pass_file);\n\n    gc_free(&gc);\n}\n\n#endif /* ! ENABLE_SMALL */\n\nstatic void\noption_iroute(struct options *o, const char *network_str, const char *netmask_str,\n              msglvl_t msglevel)\n{\n    struct iroute *ir;\n\n    ALLOC_OBJ_GC(ir, struct iroute, &o->gc);\n    ir->network = getaddr(GETADDR_HOST_ORDER, network_str, 0, NULL, NULL);\n    ir->netbits = 32; /* host route if no netmask given */\n\n    if (netmask_str)\n    {\n        const in_addr_t netmask = getaddr(GETADDR_HOST_ORDER, netmask_str, 0, NULL, NULL);\n        ir->netbits = netmask_to_netbits2(netmask);\n\n        if (ir->netbits < 0)\n        {\n            msg(msglevel, \"in --iroute %s %s : Bad network/subnet specification\", network_str,\n                netmask_str);\n            return;\n        }\n    }\n\n    ir->next = o->iroutes;\n    o->iroutes = ir;\n}\n\nstatic void\noption_iroute_ipv6(struct options *o, const char *prefix_str, msglvl_t msglevel)\n{\n    struct iroute_ipv6 *ir;\n\n    ALLOC_OBJ_GC(ir, struct iroute_ipv6, &o->gc);\n\n    if (!get_ipv6_addr(prefix_str, &ir->network, &ir->netbits, msglevel))\n    {\n        msg(msglevel, \"in --iroute-ipv6 %s: Bad IPv6 prefix specification\", prefix_str);\n        return;\n    }\n\n    ir->next = o->iroutes_ipv6;\n    o->iroutes_ipv6 = ir;\n}\n\n#ifndef ENABLE_SMALL\nstatic void\nshow_http_proxy_options(const struct http_proxy_options *o)\n{\n    int i;\n    msg(D_SHOW_PARMS, \"BEGIN http_proxy\");\n    SHOW_STR(server);\n    SHOW_STR(port);\n    SHOW_STR(auth_method_string);\n    SHOW_STR(auth_file);\n    SHOW_STR(auth_file_up);\n    SHOW_BOOL(inline_creds);\n    SHOW_BOOL(nocache);\n    SHOW_STR(http_version);\n    SHOW_STR(user_agent);\n    for (i = 0; i < MAX_CUSTOM_HTTP_HEADER && o->custom_headers[i].name; i++)\n    {\n        if (o->custom_headers[i].content)\n        {\n            msg(D_SHOW_PARMS, \"  custom_header[%d] = %s: %s\", i, o->custom_headers[i].name,\n                o->custom_headers[i].content);\n        }\n        else\n        {\n            msg(D_SHOW_PARMS, \"  custom_header[%d] = %s\", i, o->custom_headers[i].name);\n        }\n    }\n    msg(D_SHOW_PARMS, \"END http_proxy\");\n}\n#endif /* ifndef ENABLE_SMALL */\n\nvoid\noptions_detach(struct options *o)\n{\n    gc_detach(&o->gc);\n    o->routes = NULL;\n    o->client_nat = NULL;\n    clone_push_list(o);\n}\n\nvoid\nrol_check_alloc(struct options *options)\n{\n    if (!options->routes)\n    {\n        options->routes = new_route_option_list(&options->gc);\n    }\n}\n\nstatic void\nrol6_check_alloc(struct options *options)\n{\n    if (!options->routes_ipv6)\n    {\n        options->routes_ipv6 = new_route_ipv6_option_list(&options->gc);\n    }\n}\n\nstatic void\ncnol_check_alloc(struct options *options)\n{\n    if (!options->client_nat)\n    {\n        options->client_nat = new_client_nat_list(&options->gc);\n    }\n}\n\n#ifndef ENABLE_SMALL\nstatic void\nshow_connection_entry(const struct connection_entry *o)\n{\n    /* Display the global proto only in client mode or with no '--local'*/\n    if (o->local_list->len == 1)\n    {\n        msg(D_SHOW_PARMS, \"  proto = %s\", proto2ascii(o->proto, o->af, false));\n    }\n\n    msg(D_SHOW_PARMS, \"  Local Sockets:\");\n    for (int i = 0; i < o->local_list->len; i++)\n    {\n        msg(D_SHOW_PARMS, \"    [%s]:%s-%s\", o->local_list->array[i]->local,\n            o->local_list->array[i]->port,\n            proto2ascii(o->local_list->array[i]->proto, o->af, false));\n    }\n    SHOW_STR(remote);\n    SHOW_STR(remote_port);\n    SHOW_BOOL(remote_float);\n    SHOW_BOOL(bind_defined);\n    SHOW_BOOL(bind_local);\n    SHOW_BOOL(bind_ipv6_only);\n    SHOW_INT(connect_retry_seconds);\n    SHOW_INT(connect_timeout);\n\n    if (o->http_proxy_options)\n    {\n        show_http_proxy_options(o->http_proxy_options);\n    }\n    SHOW_STR(socks_proxy_server);\n    SHOW_STR(socks_proxy_port);\n    SHOW_INT(tun_mtu);\n    SHOW_BOOL(tun_mtu_defined);\n    SHOW_INT(link_mtu);\n    SHOW_BOOL(link_mtu_defined);\n    SHOW_INT(tun_mtu_extra);\n    SHOW_BOOL(tun_mtu_extra_defined);\n    SHOW_INT(tls_mtu);\n\n    SHOW_INT(mtu_discover_type);\n\n#ifdef ENABLE_FRAGMENT\n    SHOW_INT(fragment);\n#endif\n    SHOW_INT(mssfix);\n    SHOW_BOOL(mssfix_encap);\n    SHOW_BOOL(mssfix_fixed);\n\n    SHOW_INT(explicit_exit_notification);\n\n    SHOW_STR_INLINE(tls_auth_file);\n    SHOW_PARM(key_direction, keydirection2ascii(o->key_direction, false, true), \"%s\");\n    SHOW_STR_INLINE(tls_crypt_file);\n    SHOW_STR_INLINE(tls_crypt_v2_file);\n}\n\n\nstatic void\nshow_connection_entries(const struct options *o)\n{\n    if (o->connection_list)\n    {\n        const struct connection_list *l = o->connection_list;\n        int i;\n        for (i = 0; i < l->len; ++i)\n        {\n            msg(D_SHOW_PARMS, \"Connection profiles [%d]:\", i);\n            show_connection_entry(l->array[i]);\n        }\n    }\n    else\n    {\n        msg(D_SHOW_PARMS, \"Connection profiles [default]:\");\n        show_connection_entry(&o->ce);\n    }\n    msg(D_SHOW_PARMS, \"Connection profiles END\");\n}\n\nstatic void\nshow_pull_filter_list(const struct pull_filter_list *l)\n{\n    struct pull_filter *f;\n    if (!l)\n    {\n        return;\n    }\n\n    msg(D_SHOW_PARMS, \"  Pull filters:\");\n    for (f = l->head; f; f = f->next)\n    {\n        msg(D_SHOW_PARMS, \"    %s \\\"%s\\\"\", pull_filter_type_name(f->type), f->pattern);\n    }\n}\n\n#endif /* ifndef ENABLE_SMALL */\n\nvoid\nshow_settings(const struct options *o)\n{\n#ifndef ENABLE_SMALL\n    msg(D_SHOW_PARMS, \"Current Parameter Settings:\");\n\n    SHOW_STR(config);\n\n    SHOW_INT(mode);\n\n#ifdef ENABLE_FEATURE_TUN_PERSIST\n    SHOW_BOOL(persist_config);\n    SHOW_INT(persist_mode);\n#endif\n\n    SHOW_BOOL(show_ciphers);\n    SHOW_BOOL(show_digests);\n    SHOW_BOOL(show_engines);\n    SHOW_BOOL(genkey);\n    SHOW_STR(genkey_filename);\n    SHOW_STR(key_pass_file);\n    SHOW_BOOL(show_tls_ciphers);\n\n    SHOW_INT(connect_retry_max);\n    show_connection_entries(o);\n\n    SHOW_BOOL(remote_random);\n\n    SHOW_STR(ipchange);\n    SHOW_STR(dev);\n    SHOW_STR(dev_type);\n    SHOW_STR(dev_node);\n#if defined(ENABLE_DCO)\n    SHOW_BOOL(disable_dco);\n#endif\n    SHOW_STR(lladdr);\n    SHOW_INT(topology);\n    SHOW_STR(ifconfig_local);\n    SHOW_STR(ifconfig_remote_netmask);\n    SHOW_BOOL(ifconfig_noexec);\n    SHOW_BOOL(ifconfig_nowarn);\n    SHOW_STR(ifconfig_ipv6_local);\n    SHOW_INT(ifconfig_ipv6_netbits);\n    SHOW_STR(ifconfig_ipv6_remote);\n\n    SHOW_INT(shaper);\n    SHOW_INT(mtu_test);\n\n    SHOW_BOOL(mlock);\n\n    SHOW_INT(keepalive_ping);\n    SHOW_INT(keepalive_timeout);\n    SHOW_INT(inactivity_timeout);\n    SHOW_INT(session_timeout);\n    SHOW_INT64(inactivity_minimum_bytes);\n    SHOW_INT(ping_send_timeout);\n    SHOW_INT(ping_rec_timeout);\n    SHOW_INT(ping_rec_timeout_action);\n    SHOW_BOOL(ping_timer_remote);\n    SHOW_INT(remap_sigusr1);\n    SHOW_BOOL(persist_tun);\n    SHOW_BOOL(persist_local_ip);\n    SHOW_BOOL(persist_remote_ip);\n\n#if PASSTOS_CAPABILITY\n    SHOW_BOOL(passtos);\n#endif\n\n    SHOW_INT(resolve_retry_seconds);\n    SHOW_BOOL(resolve_in_advance);\n\n    SHOW_STR(username);\n    SHOW_STR(groupname);\n    SHOW_STR(chroot_dir);\n    SHOW_STR(cd_dir);\n#ifdef ENABLE_SELINUX\n    SHOW_STR(selinux_context);\n#endif\n    SHOW_STR(writepid);\n    SHOW_STR(up_script);\n    SHOW_STR(down_script);\n    SHOW_BOOL(down_pre);\n    SHOW_BOOL(up_restart);\n    SHOW_BOOL(up_delay);\n    SHOW_BOOL(daemon);\n    SHOW_BOOL(log);\n    SHOW_BOOL(suppress_timestamps);\n    SHOW_BOOL(machine_readable_output);\n    SHOW_INT(nice);\n    SHOW_INT(verbosity);\n    SHOW_INT(mute);\n#ifdef ENABLE_DEBUG\n    SHOW_INT(gremlin);\n#endif\n    SHOW_STR(status_file);\n    SHOW_INT(status_file_version);\n    SHOW_INT(status_file_update_freq);\n\n    SHOW_BOOL(occ);\n    SHOW_INT(rcvbuf);\n    SHOW_INT(sndbuf);\n#if defined(TARGET_LINUX)\n    SHOW_INT(mark);\n#endif\n    SHOW_INT(sockflags);\n\n    SHOW_INT(comp.alg);\n    SHOW_INT(comp.flags);\n\n    SHOW_STR(route_script);\n    SHOW_STR(route_default_gateway);\n    SHOW_INT(route_default_metric);\n    SHOW_INT(route_default_table_id);\n    SHOW_BOOL(route_noexec);\n    SHOW_INT(route_delay);\n    SHOW_INT(route_delay_window);\n    SHOW_BOOL(route_delay_defined);\n    SHOW_BOOL(route_nopull);\n    SHOW_BOOL(route_gateway_via_dhcp);\n    SHOW_BOOL(allow_pull_fqdn);\n    show_pull_filter_list(o->pull_filter_list);\n\n    if (o->routes)\n    {\n        print_route_options(o->routes, D_SHOW_PARMS);\n    }\n\n    if (o->client_nat)\n    {\n        print_client_nat_list(o->client_nat, D_SHOW_PARMS);\n    }\n\n    show_dns_options(&o->dns_options);\n\n#ifdef ENABLE_MANAGEMENT\n    SHOW_STR(management_addr);\n    SHOW_STR(management_port);\n    SHOW_STR(management_user_pass);\n    SHOW_INT(management_log_history_cache);\n    SHOW_INT(management_echo_buffer_size);\n    SHOW_STR(management_client_user);\n    SHOW_STR(management_client_group);\n    SHOW_INT(management_flags);\n#endif\n#ifdef ENABLE_PLUGIN\n    if (o->plugin_list)\n    {\n        plugin_option_list_print(o->plugin_list, D_SHOW_PARMS);\n    }\n#endif\n\n    SHOW_STR_INLINE(shared_secret_file);\n    SHOW_PARM(key_direction, keydirection2ascii(o->key_direction, false, true), \"%s\");\n    SHOW_STR(ciphername);\n    SHOW_STR(ncp_ciphers);\n    SHOW_STR(authname);\n#ifndef ENABLE_CRYPTO_MBEDTLS\n    SHOW_BOOL(engine);\n#endif /* ENABLE_CRYPTO_MBEDTLS */\n    SHOW_BOOL(mute_replay_warnings);\n    SHOW_INT(replay_window);\n    SHOW_INT(replay_time);\n    SHOW_STR(packet_id_file);\n    SHOW_BOOL(test_crypto);\n\n    SHOW_BOOL(tls_server);\n    SHOW_BOOL(tls_client);\n    SHOW_STR_INLINE(ca_file);\n    SHOW_STR(ca_path);\n    SHOW_STR_INLINE(dh_file);\n    if ((o->management_flags & MF_EXTERNAL_CERT))\n    {\n        SHOW_PARM(\"cert_file\", \"EXTERNAL_CERT\", \"%s\");\n    }\n    else\n    {\n        SHOW_STR_INLINE(cert_file);\n    }\n    SHOW_STR_INLINE(extra_certs_file);\n\n    if ((o->management_flags & MF_EXTERNAL_KEY))\n    {\n        SHOW_PARM(\"priv_key_file\", \"EXTERNAL_PRIVATE_KEY\", \"%s\");\n    }\n    else\n    {\n        SHOW_STR_INLINE(priv_key_file);\n    }\n#ifndef ENABLE_CRYPTO_MBEDTLS\n    SHOW_STR_INLINE(pkcs12_file);\n#endif\n#ifdef ENABLE_CRYPTOAPI\n    SHOW_STR(cryptoapi_cert);\n#endif\n    SHOW_STR(cipher_list);\n    SHOW_STR(cipher_list_tls13);\n    SHOW_STR(tls_cert_profile);\n    SHOW_STR(tls_verify);\n    SHOW_STR(tls_export_peer_cert_dir);\n    SHOW_INT(verify_x509_type);\n    SHOW_STR(verify_x509_name);\n    SHOW_STR_INLINE(crl_file);\n    SHOW_INT(ns_cert_type);\n    {\n        int i;\n        for (i = 0; i < MAX_PARMS; i++)\n        {\n            SHOW_INT(remote_cert_ku[i]);\n        }\n    }\n    SHOW_STR(remote_cert_eku);\n    if (o->verify_hash)\n    {\n        SHOW_INT(verify_hash_algo);\n        SHOW_INT(verify_hash_depth);\n        struct gc_arena gc = gc_new();\n        struct verify_hash_list *hl = o->verify_hash;\n        int digest_len =\n            (o->verify_hash_algo == MD_SHA1) ? SHA_DIGEST_LENGTH : SHA256_DIGEST_LENGTH;\n        while (hl)\n        {\n            char *s = format_hex_ex(hl->hash, digest_len, 0, 1, \":\", &gc);\n            SHOW_PARM(verify_hash, s, \"%s\");\n            hl = hl->next;\n        }\n        gc_free(&gc);\n    }\n    SHOW_INT(ssl_flags);\n\n    SHOW_INT(tls_timeout);\n\n    SHOW_INT64(renegotiate_bytes);\n    SHOW_INT64(renegotiate_packets);\n    SHOW_INT(renegotiate_seconds);\n\n    SHOW_INT(handshake_window);\n    SHOW_INT(transition_window);\n\n    SHOW_BOOL(single_session);\n    SHOW_BOOL(push_peer_info);\n    SHOW_BOOL(tls_exit);\n\n    SHOW_STR(tls_crypt_v2_metadata);\n\n#ifdef ENABLE_PKCS11\n    {\n        int i;\n        for (i = 0; i < MAX_PARMS && o->pkcs11_providers[i] != NULL; i++)\n        {\n            SHOW_PARM(pkcs11_providers, o->pkcs11_providers[i], \"%s\");\n        }\n    }\n    {\n        int i;\n        for (i = 0; i < MAX_PARMS; i++)\n        {\n            SHOW_PARM(pkcs11_protected_authentication,\n                      o->pkcs11_protected_authentication[i] ? \"ENABLED\" : \"DISABLED\", \"%s\");\n        }\n    }\n    {\n        int i;\n        for (i = 0; i < MAX_PARMS; i++)\n        {\n            SHOW_PARM(pkcs11_private_mode, o->pkcs11_private_mode[i], \"%08x\");\n        }\n    }\n    {\n        int i;\n        for (i = 0; i < MAX_PARMS; i++)\n        {\n            SHOW_PARM(pkcs11_cert_private, o->pkcs11_cert_private[i] ? \"ENABLED\" : \"DISABLED\",\n                      \"%s\");\n        }\n    }\n    SHOW_INT(pkcs11_pin_cache_period);\n    SHOW_STR(pkcs11_id);\n    SHOW_BOOL(pkcs11_id_management);\n#endif /* ENABLE_PKCS11 */\n\n    show_p2mp_parms(o);\n\n#ifdef _WIN32\n    SHOW_BOOL(show_net_up);\n    SHOW_INT(route_method);\n    SHOW_BOOL(block_outside_dns);\n    show_tuntap_options(&o->tuntap_options);\n#endif\n#endif /* ifndef ENABLE_SMALL */\n}\n\n#undef SHOW_PARM\n#undef SHOW_STR\n#undef SHOW_INT\n#undef SHOW_BOOL\n\n#ifdef ENABLE_MANAGEMENT\n\nstatic struct http_proxy_options *\nparse_http_proxy_override(const char *server, const char *port, const char *flags,\n                          struct gc_arena *gc)\n{\n    if (server && port)\n    {\n        struct http_proxy_options *ho;\n        ALLOC_OBJ_CLEAR_GC(ho, struct http_proxy_options, gc);\n        ho->server = string_alloc(server, gc);\n        ho->port = port;\n        if (flags && !strcmp(flags, \"nct\"))\n        {\n            ho->auth_retry = PAR_NCT;\n        }\n        else\n        {\n            ho->auth_retry = PAR_ALL;\n        }\n        ho->http_version = \"1.0\";\n        ho->user_agent = \"OpenVPN-Autoproxy/1.0\";\n        return ho;\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\nstatic void\noptions_postprocess_http_proxy_override(struct options *o)\n{\n    const struct connection_list *l = o->connection_list;\n    int i;\n    bool succeed = false;\n    for (i = 0; i < l->len; ++i)\n    {\n        struct connection_entry *ce = l->array[i];\n        if (ce->proto == PROTO_TCP_CLIENT || ce->proto == PROTO_TCP)\n        {\n            ce->http_proxy_options = o->http_proxy_override;\n            succeed = true;\n        }\n    }\n    if (succeed)\n    {\n        for (i = 0; i < l->len; ++i)\n        {\n            struct connection_entry *ce = l->array[i];\n            if (ce->proto == PROTO_UDP)\n            {\n                ce->flags |= CE_DISABLED;\n            }\n        }\n    }\n    else\n    {\n        msg(M_WARN,\n            \"Note: option http-proxy-override ignored because no TCP-based connection profiles are defined\");\n    }\n}\n\n#endif /* ifdef ENABLE_MANAGEMENT */\n\nstatic struct local_list *\nalloc_local_list_if_undef(struct connection_entry *ce, struct gc_arena *gc)\n{\n    if (!ce->local_list)\n    {\n        ALLOC_OBJ_CLEAR_GC(ce->local_list, struct local_list, gc);\n    }\n    return ce->local_list;\n}\n\nstatic struct local_entry *\nalloc_local_entry(struct connection_entry *ce, const msglvl_t msglevel, struct gc_arena *gc)\n{\n    struct local_list *l = alloc_local_list_if_undef(ce, gc);\n    struct local_entry *e;\n\n    if (l->len >= l->capacity)\n    {\n        const int new_cap = l->capacity + 1;\n        const size_t elem_size = sizeof(*l->array);\n\n        struct local_entry **new_array = gc_realloc(l->array, new_cap * elem_size, gc);\n        if (!new_array)\n        {\n            msg(msglevel,\n                \"Unable to process more local options: out of memory. Number of entries = %d\",\n                l->len);\n            return NULL;\n        }\n\n        l->array = new_array;\n        l->capacity = new_cap;\n    }\n\n    ALLOC_OBJ_CLEAR_GC(e, struct local_entry, gc);\n    e->proto = PROTO_NONE;\n    l->array[l->len++] = e;\n\n    return e;\n}\n\nstatic struct connection_list *\nalloc_connection_list_if_undef(struct options *options)\n{\n    if (!options->connection_list)\n    {\n        ALLOC_OBJ_CLEAR_GC(options->connection_list, struct connection_list, &options->gc);\n    }\n    return options->connection_list;\n}\n\nstatic struct connection_entry *\nalloc_connection_entry(struct options *options, const msglvl_t msglevel)\n{\n    struct connection_list *l = alloc_connection_list_if_undef(options);\n    struct connection_entry *e;\n\n    if (l->len == l->capacity)\n    {\n        int capacity = l->capacity + CONNECTION_LIST_SIZE;\n        struct connection_entry **ce =\n            gc_realloc(l->array, capacity * sizeof(struct connection_entry *), &options->gc);\n        if (ce == NULL)\n        {\n            msg(msglevel,\n                \"Unable to process more connection options: out of memory. Number of entries = %d\",\n                l->len);\n            return NULL;\n        }\n        l->array = ce;\n        l->capacity = capacity;\n    }\n    ALLOC_OBJ_GC(e, struct connection_entry, &options->gc);\n    l->array[l->len++] = e;\n    return e;\n}\n\nstatic struct remote_list *\nalloc_remote_list_if_undef(struct options *options)\n{\n    if (!options->remote_list)\n    {\n        ALLOC_OBJ_CLEAR_GC(options->remote_list, struct remote_list, &options->gc);\n    }\n    return options->remote_list;\n}\n\nstatic struct remote_entry *\nalloc_remote_entry(struct options *options, const msglvl_t msglevel)\n{\n    struct remote_list *l = alloc_remote_list_if_undef(options);\n    struct remote_entry *e;\n\n    if (l->len == l->capacity)\n    {\n        int capacity = l->capacity + CONNECTION_LIST_SIZE;\n        struct remote_entry **re =\n            gc_realloc(l->array, capacity * sizeof(struct remote_entry *), &options->gc);\n        if (re == NULL)\n        {\n            msg(msglevel,\n                \"Unable to process more remote options: out of memory. Number of entries = %d\",\n                l->len);\n            return NULL;\n        }\n        l->array = re;\n        l->capacity = capacity;\n    }\n    ALLOC_OBJ_GC(e, struct remote_entry, &options->gc);\n    l->array[l->len++] = e;\n    return e;\n}\n\nstatic struct pull_filter_list *\nalloc_pull_filter_list(struct options *o)\n{\n    if (!o->pull_filter_list)\n    {\n        ALLOC_OBJ_CLEAR_GC(o->pull_filter_list, struct pull_filter_list, &o->gc);\n    }\n    return o->pull_filter_list;\n}\n\nstatic struct pull_filter *\nalloc_pull_filter(struct options *o)\n{\n    struct pull_filter_list *l = alloc_pull_filter_list(o);\n    struct pull_filter *f;\n\n    ALLOC_OBJ_CLEAR_GC(f, struct pull_filter, &o->gc);\n    if (l->head)\n    {\n        ASSERT(l->tail);\n        l->tail->next = f;\n    }\n    else\n    {\n        ASSERT(!l->tail);\n        l->head = f;\n    }\n    l->tail = f;\n    return f;\n}\n\nstatic void\nconnection_entry_load_re(struct connection_entry *ce, const struct remote_entry *re)\n{\n    if (re->remote)\n    {\n        ce->remote = re->remote;\n    }\n    if (re->remote_port)\n    {\n        ce->remote_port = re->remote_port;\n    }\n    if (re->proto >= 0)\n    {\n        ce->proto = re->proto;\n    }\n    if (re->af > 0)\n    {\n        ce->af = re->af;\n    }\n}\n\nstatic void\nconnection_entry_preload_key(const char **key_file, bool *key_inline, struct gc_arena *gc)\n{\n    if (key_file && *key_file && !(*key_inline))\n    {\n        struct buffer in = buffer_read_from_file(*key_file, gc);\n        if (!buf_valid(&in))\n        {\n            msg(M_FATAL, \"Cannot pre-load keyfile (%s)\", *key_file);\n        }\n\n        *key_file = (const char *)in.data;\n        *key_inline = true;\n    }\n}\n\nstatic void\ncheck_ca_required(const struct options *options)\n{\n#ifdef ENABLE_CRYPTO_MBEDTLS\n    if (options->ca_path)\n    {\n        msg(M_USAGE, \"Parameter --capath cannot be used with the mbed TLS version of OpenVPN.\");\n    }\n#endif\n\n    if (options->verify_hash_no_ca || options->pkcs12_file || options->ca_file\n#ifndef ENABLE_CRYPTO_MBEDTLS\n        || options->ca_path\n#endif\n    )\n    {\n        return;\n    }\n\n    const char *const str = \"You must define CA file (--ca)\"\n#ifndef ENABLE_CRYPTO_MBEDTLS\n                            \" or CA path (--capath)\"\n#endif\n                            \" and/or peer fingerprint verification (--peer-fingerprint)\";\n    msg(M_USAGE, \"%s\", str);\n}\n\n#define MUST_BE_UNDEF(parm, parm_name)    \\\n    if (options->parm != defaults.parm)   \\\n    {                                     \\\n        msg(M_USAGE, use_err, parm_name); \\\n    }\n#define MUST_BE_FALSE(condition, parm_name) \\\n    if (condition)                          \\\n    {                                       \\\n        msg(M_USAGE, use_err, parm_name);   \\\n    }\n\nstatic void\noptions_postprocess_verify_ce(const struct options *options, const struct connection_entry *ce)\n{\n    struct options defaults;\n    int dev = DEV_TYPE_UNDEF;\n    bool pull = false;\n\n    init_options(&defaults);\n\n    if (!options->test_crypto)\n    {\n        notnull(options->dev, \"TUN/TAP device (--dev)\");\n    }\n\n    /*\n     * Get tun/tap/null device type\n     */\n    dev = dev_type_enum(options->dev, options->dev_type);\n\n    /*\n     * If \"proto tcp\" is specified, make sure we know whether it is\n     * tcp-client or tcp-server.\n     */\n    if (ce->proto == PROTO_TCP)\n    {\n        msg(M_USAGE, \"--proto tcp is ambiguous in this context. Please specify \"\n                     \"--proto tcp-server or --proto tcp-client\");\n    }\n\n    /*\n     * Sanity check on Client mode\n     */\n\n    if (options->mode != MODE_SERVER && ce->local_list->len > 1)\n    {\n        msg(M_USAGE, \"multiple --local statements only allowed in --server mode\");\n    }\n\n    if (options->lladdr && dev != DEV_TYPE_TAP)\n    {\n        msg(M_USAGE, \"--lladdr can only be used in --dev tap mode\");\n    }\n\n    /*\n     * Sanity check on MTU parameters\n     */\n    if (options->ce.tun_mtu_defined && options->ce.link_mtu_defined)\n    {\n        msg(M_USAGE, \"only one of --tun-mtu or --link-mtu may be defined\");\n    }\n\n    if (!proto_is_udp(ce->proto) && options->mtu_test)\n    {\n        msg(M_USAGE, \"--mtu-test only makes sense with --proto udp\");\n    }\n\n    /* will we be pulling options from server? */\n    pull = options->pull;\n\n    /*\n     * Sanity check on --local, --remote, and --ifconfig\n     */\n\n    if (string_defined_equal(ce->remote, options->ifconfig_local)\n        || string_defined_equal(ce->remote, options->ifconfig_remote_netmask))\n    {\n        msg(M_USAGE, \"--local and --remote addresses must be distinct from --ifconfig \"\n                     \"addresses\");\n    }\n\n    if (string_defined_equal(options->ifconfig_local, options->ifconfig_remote_netmask))\n    {\n        msg(M_USAGE, \"local and remote/netmask --ifconfig addresses must be different\");\n    }\n\n    if (ce->bind_defined && !ce->bind_local)\n    {\n        msg(M_USAGE, \"--bind and --nobind can't be used together\");\n    }\n\n    if (ce->local_port_defined && !ce->bind_local)\n    {\n        msg(M_USAGE, \"--lport and --nobind don't make sense when used together\");\n    }\n\n    if (!ce->remote && !ce->bind_local)\n    {\n        msg(M_USAGE, \"--nobind doesn't make sense unless used with --remote\");\n    }\n\n    for (int i = 0; i < ce->local_list->len; i++)\n    {\n        struct local_entry *le = ce->local_list->array[i];\n\n        if (proto_is_net(le->proto) && string_defined_equal(le->local, ce->remote)\n            && string_defined_equal(le->port, ce->remote_port))\n        {\n            msg(M_USAGE, \"--remote and one of the --local addresses are the same\");\n        }\n\n        if (string_defined_equal(le->local, options->ifconfig_local)\n            || string_defined_equal(le->local, options->ifconfig_remote_netmask))\n        {\n            msg(M_USAGE, \"--local addresses must be distinct from --ifconfig addresses\");\n        }\n\n        if (le->local && !ce->bind_local)\n        {\n            msg(M_USAGE, \"--local and --nobind don't make sense when used together\");\n        }\n    }\n\n    /*\n     * Check for consistency of management options\n     */\n#ifdef ENABLE_MANAGEMENT\n    if (!options->management_addr\n        && (options->management_flags\n            || options->management_log_history_cache != defaults.management_log_history_cache))\n    {\n        msg(M_USAGE,\n            \"--management is not specified, however one or more options which modify the behavior of --management were specified\");\n    }\n\n    if ((options->management_client_user || options->management_client_group)\n        && !(options->management_flags & MF_UNIX_SOCK))\n    {\n        msg(M_USAGE, \"--management-client-(user|group) can only be used on unix domain sockets\");\n    }\n\n    if (options->management_addr && !(options->management_flags & MF_UNIX_SOCK)\n        && (!options->management_user_pass))\n    {\n        msg(M_WARN, \"WARNING: Using --management on a TCP port WITHOUT \"\n                    \"passwords is STRONGLY discouraged and considered insecure\");\n    }\n\n#endif /* ifdef ENABLE_MANAGEMENT */\n\n#if !defined(HAVE_XKEY_PROVIDER)\n    if ((tls_version_max() >= TLS_VER_1_3) && (options->management_flags & MF_EXTERNAL_KEY)\n        && !(options->management_flags & (MF_EXTERNAL_KEY_NOPADDING)))\n    {\n        msg(M_FATAL, \"management-external-key with TLS 1.3 or later requires \"\n                     \"nopadding argument/support\");\n    }\n#endif\n    /*\n     * Windows-specific options.\n     */\n\n#ifdef _WIN32\n    if (dev == DEV_TYPE_TUN\n        && !(pull || (options->ifconfig_local && options->ifconfig_remote_netmask)))\n    {\n        msg(M_USAGE, \"On Windows, --ifconfig is required when --dev tun is used\");\n    }\n\n    if ((options->tuntap_options.ip_win32_defined)\n        && !(pull || (options->ifconfig_local && options->ifconfig_remote_netmask)))\n    {\n        msg(M_USAGE, \"On Windows, --ip-win32 doesn't make sense unless --ifconfig is also used\");\n    }\n\n    if (options->tuntap_options.dhcp_options & DHCP_OPTIONS_DHCP_REQUIRED)\n    {\n        const char *prefix = \"Some --dhcp-option or --dns options require DHCP server\";\n        if (options->windows_driver != WINDOWS_DRIVER_TAP_WINDOWS6)\n        {\n            msg(M_USAGE, \"%s, which is not supported by the selected %s driver\", prefix,\n                print_tun_backend_driver(options->windows_driver));\n        }\n        else if (options->tuntap_options.ip_win32_type != IPW32_SET_DHCP_MASQ\n                 && options->tuntap_options.ip_win32_type != IPW32_SET_ADAPTIVE)\n        {\n            msg(M_USAGE, \"%s, which requires --ip-win32 dynamic or adaptive\", prefix);\n        }\n    }\n#endif /* ifdef _WIN32 */\n\n    /*\n     * Check that protocol options make sense.\n     */\n\n#ifdef ENABLE_FRAGMENT\n    if (!proto_is_udp(ce->proto) && ce->fragment)\n    {\n        msg(M_USAGE, \"--fragment can only be used with --proto udp\");\n    }\n#endif\n\n    if (!ce->remote && ce->proto == PROTO_TCP_CLIENT)\n    {\n        msg(M_USAGE, \"--remote MUST be used in TCP Client mode\");\n    }\n\n    if ((ce->http_proxy_options) && ce->proto != PROTO_TCP_CLIENT)\n    {\n        msg(M_USAGE, \"--http-proxy MUST be used in TCP Client mode (i.e. --proto \"\n                     \"tcp-client)\");\n    }\n\n    if ((ce->http_proxy_options) && !ce->http_proxy_options->server)\n    {\n        msg(M_USAGE, \"--http-proxy not specified but other http proxy options present\");\n    }\n\n    if (ce->http_proxy_options && ce->socks_proxy_server)\n    {\n        msg(M_USAGE, \"--http-proxy can not be used together with --socks-proxy\");\n    }\n\n    if (ce->socks_proxy_server && ce->proto == PROTO_TCP_SERVER)\n    {\n        msg(M_USAGE, \"--socks-proxy can not be used in TCP Server mode\");\n    }\n\n    if (ce->proto == PROTO_TCP_SERVER && (options->connection_list->len > 1))\n    {\n        msg(M_USAGE, \"TCP server mode allows at most one --remote address\");\n    }\n\n    /*\n     * Check consistency of --mode server options.\n     */\n    if (options->mode == MODE_SERVER)\n    {\n        const char use_err[] = \"--%s cannot be used with --mode server.\";\n\n#define USAGE_VALID_SERVER_PROTOS            \\\n    \"--mode server currently only supports \" \\\n    \"--proto values of udp, tcp-server, tcp4-server, or tcp6-server\"\n#ifdef TARGET_ANDROID\n        msg(M_FATAL, \"--mode server not supported on Android\");\n#endif\n        if (!(dev == DEV_TYPE_TUN || dev == DEV_TYPE_TAP))\n        {\n            msg(M_USAGE, \"--mode server only works with --dev tun or --dev tap\");\n        }\n        MUST_BE_UNDEF(pull, \"pull\");\n        if (options->pull_filter_list)\n        {\n            msg(M_WARN, \"--pull-filter ignored for --mode server\");\n        }\n        if (!(proto_is_udp(ce->proto) || ce->proto == PROTO_TCP_SERVER))\n        {\n            msg(M_USAGE, USAGE_VALID_SERVER_PROTOS);\n        }\n#if PORT_SHARE\n        if ((options->port_share_host || options->port_share_port)\n            && (ce->proto != PROTO_TCP_SERVER))\n        {\n            msg(M_USAGE, \"--port-share only works in TCP server mode \"\n                         \"(--proto values of tcp-server, tcp4-server, or tcp6-server)\");\n        }\n#endif\n        if (!options->tls_server)\n        {\n            msg(M_USAGE, \"--mode server requires --tls-server\");\n        }\n        MUST_BE_FALSE(ce->remote, \"remote\");\n        MUST_BE_FALSE(!ce->bind_local, \"nobind\");\n        MUST_BE_FALSE(ce->http_proxy_options, \"http-proxy\");\n        MUST_BE_FALSE(ce->socks_proxy_server, \"socks-proxy\");\n        /* <connection> blocks force to have a remote embedded, so we check\n         * for the --remote and bail out if it is present\n         */\n        if (options->connection_list->len > 1 || options->connection_list->array[0]->remote)\n        {\n            msg(M_USAGE, \"<connection> cannot be used with --mode server\");\n        }\n\n        MUST_BE_UNDEF(shaper, \"shaper\");\n        if (options->ipchange)\n        {\n            msg(M_USAGE, \"--ipchange cannot be used with --mode server (use \"\n                         \"--client-connect instead)\");\n        }\n        if (!(proto_is_dgram(ce->proto) || ce->proto == PROTO_TCP_SERVER))\n        {\n            msg(M_USAGE, USAGE_VALID_SERVER_PROTOS);\n        }\n        if (!proto_is_udp(ce->proto) && (options->cf_max || options->cf_per))\n        {\n            msg(M_USAGE,\n                \"--connect-freq only works with --mode server --proto udp.  Try --max-clients instead.\");\n        }\n        if (!(dev == DEV_TYPE_TAP || (dev == DEV_TYPE_TUN && options->topology == TOP_SUBNET))\n            && options->ifconfig_pool_netmask)\n        {\n            msg(M_USAGE,\n                \"The third parameter to --ifconfig-pool (netmask) is only valid in --dev tap mode\");\n        }\n        if (options->routes && (options->routes->flags & RG_ENABLE))\n        {\n            msg(M_USAGE,\n                \"--redirect-gateway cannot be used with --mode server (however --push \\\"redirect-gateway\\\" is fine)\");\n        }\n        MUST_BE_UNDEF(route_delay_defined, \"route-delay\");\n        MUST_BE_UNDEF(up_delay, \"up-delay\");\n        if (!options->ifconfig_pool_defined && !options->ifconfig_ipv6_pool_defined\n            && options->ifconfig_pool_persist_filename)\n        {\n            msg(M_USAGE,\n                \"--ifconfig-pool-persist must be used with --ifconfig-pool or --ifconfig-ipv6-pool\");\n        }\n        if (options->ifconfig_ipv6_pool_defined && !options->ifconfig_ipv6_local)\n        {\n            msg(M_USAGE, \"--ifconfig-ipv6-pool needs --ifconfig-ipv6\");\n        }\n        MUST_BE_UNDEF(allow_recursive_routing, \"allow-recursive-routing\");\n        if (options->auth_user_pass_file)\n        {\n            msg(M_USAGE,\n                \"--auth-user-pass cannot be used with --mode server (it should be used on the client side only)\");\n        }\n        if (options->ccd_exclusive && !options->client_config_dir)\n        {\n            msg(M_USAGE, \"--ccd-exclusive must be used with --client-config-dir\");\n        }\n        if (options->auth_token_generate && !options->renegotiate_seconds)\n        {\n            msg(M_USAGE, \"--auth-gen-token needs a non-infinite \"\n                         \"--renegotiate_seconds setting\");\n        }\n        if (options->auth_token_generate && options->auth_token_renewal\n            && options->auth_token_renewal < 2 * options->handshake_window)\n        {\n            msg(M_USAGE,\n                \"--auth-gen-token renewal time needs to be at least \"\n                \" two times --hand-window (%d).\",\n                options->handshake_window);\n        }\n        if (!options->auth_user_pass_verify_script && !PLUGIN_OPTION_LIST(options)\n            && !MAN_CLIENT_AUTH_ENABLED(options))\n        {\n            const char *use_err =\n                \"--%s must be used with --management-client-auth, an --auth-user-pass-verify script, or plugin\";\n\n            MUST_BE_FALSE(options->ssl_flags\n                              & (SSLF_CLIENT_CERT_NOT_REQUIRED | SSLF_CLIENT_CERT_OPTIONAL),\n                          \"verify-client-cert none|optional\");\n            MUST_BE_FALSE(options->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME,\n                          \"username-as-common-name\");\n            MUST_BE_FALSE(options->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL,\n                          \"auth-user-pass-optional\");\n        }\n\n        if (options->vlan_tagging && dev != DEV_TYPE_TAP)\n        {\n            msg(M_USAGE, \"--vlan-tagging must be used with --dev tap\");\n        }\n        if (!options->vlan_tagging)\n        {\n            const char use_err[] = \"--%s requires --vlan-tagging\";\n            MUST_BE_UNDEF(vlan_accept, \"vlan-accept\");\n            MUST_BE_UNDEF(vlan_pvid, \"vlan-pvid\");\n        }\n    }\n    else\n    {\n        const char use_err[] = \"--%s requires --mode server\";\n        /*\n         * When not in server mode, err if parameters are\n         * specified which require --mode server.\n         */\n        MUST_BE_UNDEF(ifconfig_pool_defined, \"ifconfig-pool\");\n        MUST_BE_UNDEF(ifconfig_pool_persist_filename, \"ifconfig-pool-persist\");\n        MUST_BE_UNDEF(ifconfig_ipv6_pool_defined, \"ifconfig-ipv6-pool\");\n        MUST_BE_UNDEF(real_hash_size, \"hash-size\");\n        MUST_BE_UNDEF(virtual_hash_size, \"hash-size\");\n        MUST_BE_UNDEF(learn_address_script, \"learn-address\");\n        MUST_BE_UNDEF(client_connect_script, \"client-connect\");\n        MUST_BE_UNDEF(client_crresponse_script, \"client-crresponse\");\n        MUST_BE_UNDEF(client_disconnect_script, \"client-disconnect\");\n        MUST_BE_UNDEF(client_config_dir, \"client-config-dir\");\n        MUST_BE_UNDEF(ccd_exclusive, \"ccd-exclusive\");\n        MUST_BE_UNDEF(enable_c2c, \"client-to-client\");\n        MUST_BE_UNDEF(duplicate_cn, \"duplicate-cn\");\n        MUST_BE_UNDEF(cf_max, \"connect-freq\");\n        MUST_BE_UNDEF(cf_per, \"connect-freq\");\n        MUST_BE_FALSE(options->ssl_flags\n                          & (SSLF_CLIENT_CERT_NOT_REQUIRED | SSLF_CLIENT_CERT_OPTIONAL),\n                      \"verify-client-cert\");\n        MUST_BE_FALSE(options->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME, \"username-as-common-name\");\n        MUST_BE_FALSE(options->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL, \"auth-user-pass-optional\");\n        if (options->server_flags & SF_TCP_NODELAY_HELPER)\n        {\n            msg(M_WARN, \"WARNING: setting tcp-nodelay on the client side will not \"\n                        \"affect the server. To have TCP_NODELAY in both direction use \"\n                        \"tcp-nodelay in the server configuration instead.\");\n        }\n        MUST_BE_UNDEF(auth_user_pass_verify_script, \"auth-user-pass-verify\");\n        MUST_BE_UNDEF(auth_token_generate, \"auth-gen-token\");\n#if PORT_SHARE\n        if (options->port_share_host || options->port_share_port)\n        {\n            msg(M_USAGE,\n                \"--port-share requires TCP server mode (--mode server --proto tcp-server)\");\n        }\n#endif\n        MUST_BE_UNDEF(stale_routes_check_interval, \"stale-routes-check\");\n        MUST_BE_UNDEF(vlan_tagging, \"vlan-tagging\");\n        MUST_BE_UNDEF(vlan_accept, \"vlan-accept\");\n        MUST_BE_UNDEF(vlan_pvid, \"vlan-pvid\");\n        MUST_BE_UNDEF(force_key_material_export, \"force-key-material-export\");\n\n        if (options->push_list.head)\n        {\n            msg(M_WARN, \"Note: Using --push without --mode server is an \"\n                        \"unsupported configuration. Negotiation of OpenVPN \"\n                        \"features is expected to fail.\");\n        }\n    }\n\n    /*\n     * SSL/TLS mode sanity checks.\n     */\n    if (options->tls_server + options->tls_client + (options->shared_secret_file != NULL) > 1)\n    {\n        msg(M_USAGE, \"specify only one of --tls-server, --tls-client, or --secret\");\n    }\n\n    if (!options->tls_server && !options->tls_client && !options->test_crypto)\n    {\n        msglvl_t msglevel = M_USAGE;\n        if (options->allow_deprecated_insecure_static_crypto)\n        {\n            msglevel = M_INFO;\n        }\n\n        msg(msglevel, \"DEPRECATION: No tls-client or tls-server option in \"\n                      \"configuration detected. OpenVPN 2.8 will remove the \"\n                      \"functionality to run a VPN without TLS. \"\n                      \"See the examples section in the manual page for \"\n                      \"examples of a similar quick setup with peer-fingerprint. \"\n                      \"OpenVPN 2.7 allows using this configuration when using \"\n                      \"--allow-deprecated-insecure-static-crypto but you should move \"\n                      \"to a proper configuration using TLS as soon as possible.\");\n    }\n\n    if (options->ssl_flags & (SSLF_CLIENT_CERT_NOT_REQUIRED | SSLF_CLIENT_CERT_OPTIONAL))\n    {\n        msg(M_WARN, \"WARNING: POTENTIALLY DANGEROUS OPTION \"\n                    \"--verify-client-cert none|optional \"\n                    \"may accept clients which do not present a certificate\");\n    }\n\n    const unsigned int tls_version_max =\n        (options->ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT) & SSLF_TLS_VERSION_MAX_MASK;\n    const unsigned int tls_version_min =\n        (options->ssl_flags >> SSLF_TLS_VERSION_MIN_SHIFT) & SSLF_TLS_VERSION_MIN_MASK;\n\n    if (tls_version_max > 0 && tls_version_max < tls_version_min)\n    {\n        msg(M_USAGE, \"--tls-version-min bigger than --tls-version-max\");\n    }\n\n    if (options->tls_server || options->tls_client)\n    {\n        check_ca_required(options);\n#ifdef ENABLE_PKCS11\n        if (!options->pkcs11_providers[0] && options->pkcs11_id)\n        {\n            msg(M_WARN, \"Option pkcs11-id is ignored as no pkcs11-providers are specified\");\n        }\n        else if (!options->pkcs11_providers[0] && options->pkcs11_id_management)\n        {\n            msg(M_WARN,\n                \"Option pkcs11-id-management is ignored as no pkcs11-providers are specified\");\n        }\n\n        if (options->pkcs11_providers[0])\n        {\n            if (options->pkcs11_id_management && options->pkcs11_id != NULL)\n            {\n                msg(M_USAGE,\n                    \"Parameter --pkcs11-id cannot be used when --pkcs11-id-management is also specified.\");\n            }\n            if (!options->pkcs11_id_management && options->pkcs11_id == NULL)\n            {\n                msg(M_USAGE,\n                    \"Parameter --pkcs11-id or --pkcs11-id-management should be specified.\");\n            }\n            const char use_err[] =\n                \"Parameter --%s cannot be used when --pkcs11-provider is also specified.\";\n            MUST_BE_UNDEF(cert_file, \"cert\");\n            MUST_BE_UNDEF(priv_key_file, \"key\");\n            MUST_BE_UNDEF(pkcs12_file, \"pkcs12\");\n            MUST_BE_FALSE(options->management_flags & MF_EXTERNAL_KEY, \"management-external-key\");\n            MUST_BE_FALSE(options->management_flags & MF_EXTERNAL_CERT, \"management-external-cert\");\n#ifdef ENABLE_CRYPTOAPI\n            MUST_BE_UNDEF(cryptoapi_cert, \"cryptoapicert\");\n#endif\n        }\n        else\n#endif /* ifdef ENABLE_PKCS11 */\n#ifdef ENABLE_CRYPTOAPI\n            if (options->cryptoapi_cert)\n        {\n            const char use_err[] =\n                \"Parameter --%s cannot be used when --cryptoapicert is also specified.\";\n            MUST_BE_UNDEF(cert_file, \"cert\");\n            MUST_BE_UNDEF(priv_key_file, \"key\");\n            MUST_BE_UNDEF(pkcs12_file, \"pkcs12\");\n            MUST_BE_FALSE(options->management_flags & MF_EXTERNAL_KEY, \"management-external-key\");\n            MUST_BE_FALSE(options->management_flags & MF_EXTERNAL_CERT, \"management-external-cert\");\n        }\n        else\n#endif\n            if (options->pkcs12_file)\n        {\n#ifdef ENABLE_CRYPTO_MBEDTLS\n            msg(M_USAGE, \"Parameter --pkcs12 cannot be used with the mbed TLS version of OpenVPN.\");\n#else\n            const char use_err[] = \"Parameter --%s cannot be used when --pkcs12 is also specified.\";\n            MUST_BE_UNDEF(ca_path, \"capath\");\n            MUST_BE_UNDEF(cert_file, \"cert\");\n            MUST_BE_UNDEF(priv_key_file, \"key\");\n            MUST_BE_FALSE(options->management_flags & MF_EXTERNAL_KEY, \"management-external-key\");\n            MUST_BE_FALSE(options->management_flags & MF_EXTERNAL_CERT, \"management-external-cert\");\n#endif       /* ifdef ENABLE_CRYPTO_MBEDTLS */\n        }\n        else /* cert/key from none of pkcs11, pkcs12, cryptoapi */\n        {\n            if ((options->management_flags & MF_EXTERNAL_KEY) && options->priv_key_file)\n            {\n                msg(M_USAGE, \"--key and --management-external-key are mutually exclusive\");\n            }\n            if ((options->management_flags & MF_EXTERNAL_CERT))\n            {\n                if (options->cert_file)\n                {\n                    msg(M_USAGE, \"--cert and --management-external-cert are mutually exclusive\");\n                }\n                else if (!(options->management_flags & MF_EXTERNAL_KEY))\n                {\n                    msg(M_USAGE,\n                        \"--management-external-cert must be used with --management-external-key\");\n                }\n            }\n            if (pull)\n            {\n                const int sum =\n                    ((options->cert_file != NULL) || (options->management_flags & MF_EXTERNAL_CERT))\n                    + ((options->priv_key_file != NULL)\n                       || (options->management_flags & MF_EXTERNAL_KEY));\n\n                if (sum == 0)\n                {\n                    if (!options->auth_user_pass_file)\n                    {\n                        msg(M_USAGE, \"No client-side authentication method is \"\n                                     \"specified.  You must use either \"\n                                     \"--cert/--key, --pkcs12, or \"\n                                     \"--auth-user-pass\");\n                    }\n                }\n                else if (sum != 2)\n                {\n                    msg(M_USAGE, \"If you use one of --cert or --key, you must use them both\");\n                }\n            }\n            else\n            {\n                if (!(options->management_flags & MF_EXTERNAL_CERT))\n                {\n                    notnull(options->cert_file,\n                            \"certificate file (--cert) or PKCS#12 file (--pkcs12)\");\n                }\n                if (!(options->management_flags & MF_EXTERNAL_KEY))\n                {\n                    notnull(options->priv_key_file,\n                            \"private key file (--key) or PKCS#12 file (--pkcs12)\");\n                }\n            }\n        }\n        if (ce->tls_auth_file && ce->tls_crypt_file)\n        {\n            msg(M_USAGE, \"--tls-auth and --tls-crypt are mutually exclusive\");\n        }\n        if (options->tls_client && ce->tls_crypt_v2_file\n            && (ce->tls_auth_file || ce->tls_crypt_file))\n        {\n            msg(M_USAGE,\n                \"--tls-crypt-v2, --tls-auth and --tls-crypt are mutually exclusive in client mode\");\n        }\n    }\n    else\n    {\n        /*\n         * Make sure user doesn't specify any TLS options\n         * when in non-TLS mode.\n         */\n\n        const char use_err[] = \"Parameter %s can only be specified in TLS-mode, \"\n                               \"i.e. where --tls-server or --tls-client is also specified.\";\n\n        MUST_BE_UNDEF(ca_file, \"ca\");\n        MUST_BE_UNDEF(ca_path, \"capath\");\n        MUST_BE_UNDEF(dh_file, \"dh\");\n        MUST_BE_UNDEF(cert_file, \"cert\");\n        MUST_BE_UNDEF(priv_key_file, \"key\");\n#ifndef ENABLE_CRYPTO_MBEDTLS\n        MUST_BE_UNDEF(pkcs12_file, \"pkcs12\");\n#endif\n        MUST_BE_UNDEF(cipher_list, \"tls-cipher\");\n        MUST_BE_UNDEF(cipher_list_tls13, \"tls-ciphersuites\");\n        MUST_BE_UNDEF(tls_cert_profile, \"tls-cert-profile\");\n        MUST_BE_UNDEF(tls_verify, \"tls-verify\");\n        MUST_BE_UNDEF(tls_export_peer_cert_dir, \"tls-export-cert\");\n        MUST_BE_UNDEF(verify_x509_name, \"verify-x509-name\");\n        MUST_BE_UNDEF(tls_timeout, \"tls-timeout\");\n        MUST_BE_UNDEF(renegotiate_bytes, \"reneg-bytes\");\n        MUST_BE_UNDEF(renegotiate_packets, \"reneg-pkts\");\n        MUST_BE_UNDEF(renegotiate_seconds, \"reneg-sec\");\n        MUST_BE_UNDEF(handshake_window, \"hand-window\");\n        MUST_BE_UNDEF(transition_window, \"tran-window\");\n        MUST_BE_UNDEF(tls_auth_file, \"tls-auth\");\n        MUST_BE_UNDEF(tls_crypt_file, \"tls-crypt\");\n        MUST_BE_UNDEF(tls_crypt_v2_file, \"tls-crypt-v2\");\n        MUST_BE_UNDEF(single_session, \"single-session\");\n        MUST_BE_UNDEF(push_peer_info, \"push-peer-info\");\n        MUST_BE_UNDEF(tls_exit, \"tls-exit\");\n        MUST_BE_UNDEF(crl_file, \"crl-verify\");\n        MUST_BE_UNDEF(ns_cert_type, \"ns-cert-type\");\n        MUST_BE_UNDEF(remote_cert_ku[0], \"remote-cert-ku\");\n        MUST_BE_UNDEF(remote_cert_eku, \"remote-cert-eku\");\n#ifdef ENABLE_PKCS11\n        MUST_BE_UNDEF(pkcs11_providers[0], \"pkcs11-providers\");\n        MUST_BE_UNDEF(pkcs11_private_mode[0], \"pkcs11-private-mode\");\n        MUST_BE_UNDEF(pkcs11_id, \"pkcs11-id\");\n        MUST_BE_UNDEF(pkcs11_id_management, \"pkcs11-id-management\");\n#endif\n\n        if (pull)\n        {\n            msg(M_USAGE, use_err, \"--pull\");\n        }\n    }\n    if (options->auth_user_pass_file && !options->pull)\n    {\n        msg(M_USAGE, \"--auth-user-pass requires --pull\");\n    }\n\n    uninit_options(&defaults);\n}\n\n#undef MUST_BE_UNDEF\n#undef MUST_BE_FALSE\n\nstatic void\noptions_postprocess_mutate_ce(struct options *o, struct connection_entry *ce)\n{\n    const int dev = dev_type_enum(o->dev, o->dev_type);\n\n    if (o->server_defined || o->server_bridge_defined || o->server_bridge_proxy_dhcp)\n    {\n        if (ce->proto == PROTO_TCP)\n        {\n            ce->proto = PROTO_TCP_SERVER;\n            o->ce.proto = ce->proto;\n        }\n    }\n\n    if (o->mode != MODE_SERVER)\n    {\n        if (ce->proto == PROTO_TCP)\n        {\n            ce->proto = PROTO_TCP_CLIENT;\n            o->ce.proto = ce->proto;\n        }\n    }\n\n    /* an option is present that requires local bind to enabled */\n    bool need_bind = ce->local_port_defined || ce->bind_defined || ce->local_list;\n\n    /* socks proxy is enabled */\n    bool uses_socks = ce->proto == PROTO_UDP && ce->socks_proxy_server;\n\n    /* If binding is not forced by an explicit option and we have (at least)\n     * one of --tcp-client, --pull (or --client), or socks we do not bind\n     * locally to have \"normal\" IP client behaviour of a random source port */\n    if (!need_bind && (ce->proto == PROTO_TCP_CLIENT || uses_socks || o->pull))\n    {\n        ce->bind_local = false;\n    }\n\n    if (!ce->bind_local)\n    {\n        ce->local_port = NULL;\n    }\n\n    /* if protocol forcing is enabled, disable all protocols\n     * except for the forced one\n     */\n    if (o->proto_force >= 0 && o->proto_force != ce->proto)\n    {\n        ce->flags |= CE_DISABLED;\n    }\n\n    if (ce->http_proxy_options)\n    {\n        ce->http_proxy_options->nocache = ssl_get_auth_nocache();\n    }\n\n    /* our socks code is not fully IPv6 enabled yet (TCP works, UDP not)\n     * so fall back to IPv4-only (trac #1221)\n     */\n    if (ce->socks_proxy_server && proto_is_udp(ce->proto) && ce->af != AF_INET)\n    {\n        if (ce->af == AF_INET6)\n        {\n            msg(M_INFO, \"WARNING: '--proto udp6' is not compatible with \"\n                        \"'--socks-proxy' today.  Forcing IPv4 mode.\");\n        }\n        else\n        {\n            msg(M_INFO, \"NOTICE: dual-stack mode for '--proto udp' does not \"\n                        \"work correctly with '--socks-proxy' today.  Forcing IPv4.\");\n        }\n        ce->af = AF_INET;\n    }\n\n    /*\n     * Set MTU defaults\n     */\n    {\n        if (!ce->tun_mtu_defined && !ce->link_mtu_defined)\n        {\n            ce->tun_mtu_defined = true;\n        }\n        if ((dev == DEV_TYPE_TAP) && !ce->tun_mtu_extra_defined)\n        {\n            ce->tun_mtu_extra_defined = true;\n            ce->tun_mtu_extra = TAP_MTU_EXTRA_DEFAULT;\n        }\n    }\n\n    /*\n     * If --mssfix is supplied without a parameter or not specified at all,\n     * default it to --fragment value, if --fragment is specified and otherwise\n     * to the default if tun-mtu is 1500\n     */\n    if (o->ce.mssfix_default)\n    {\n#ifdef ENABLE_FRAGMENT\n        if (ce->fragment)\n        {\n            ce->mssfix = ce->fragment;\n        }\n        else\n#endif\n            if (ce->tun_mtu_defined)\n        {\n            if (o->ce.tun_mtu == TUN_MTU_DEFAULT)\n            {\n                /* We want to only set mssfix default value if we use a default\n                 * MTU Size, otherwise the different size of tun should either\n                 * already solve the problem or mssfix might artifically make the\n                 * payload packets smaller without mssfix 0 */\n                ce->mssfix = MSSFIX_DEFAULT;\n                ce->mssfix_encap = true;\n            }\n            else\n            {\n                /* We still apply the mssfix value but only adjust it to the\n                 * size of the tun interface. */\n                ce->mssfix = ce->tun_mtu;\n                ce->mssfix_fixed = true;\n            }\n        }\n    }\n\n    /*\n     * Set per-connection block tls-auth/crypt/crypto-v2 fields if undefined.\n     *\n     * At the end only one of these will be really set because the parser\n     * logic prevents configurations where more are set.\n     */\n    if (!ce->tls_auth_file && !ce->tls_crypt_file && !ce->tls_crypt_v2_file)\n    {\n        ce->tls_auth_file = o->tls_auth_file;\n        ce->tls_auth_file_inline = o->tls_auth_file_inline;\n        ce->key_direction = o->key_direction;\n\n        ce->tls_crypt_file = o->tls_crypt_file;\n        ce->tls_crypt_file_inline = o->tls_crypt_file_inline;\n\n        ce->tls_crypt_v2_file = o->tls_crypt_v2_file;\n        ce->tls_crypt_v2_file_inline = o->tls_crypt_v2_file_inline;\n    }\n\n    /* Pre-cache tls-auth/crypt(-v2) key file if\n     * keys were not already embedded in the config file.\n     */\n    connection_entry_preload_key(&ce->tls_auth_file, &ce->tls_auth_file_inline, &o->gc);\n    connection_entry_preload_key(&ce->tls_crypt_file, &ce->tls_crypt_file_inline, &o->gc);\n    connection_entry_preload_key(&ce->tls_crypt_v2_file, &ce->tls_crypt_v2_file_inline, &o->gc);\n\n\n    if (!proto_is_udp(ce->proto) && ce->explicit_exit_notification)\n    {\n        msg(M_WARN, \"NOTICE: --explicit-exit-notify ignored for --proto tcp\");\n        ce->explicit_exit_notification = 0;\n    }\n}\n\nstatic void\noptions_postprocess_mutate_le(struct connection_entry *ce, struct local_entry *le, int mode)\n{\n    /* use the global port if none is specified */\n    if (!le->port)\n    {\n        le->port = ce->local_port;\n    }\n    /* use the global proto if none is specified and\n     * allow proto bindings on server mode only */\n    if (!le->proto || mode == MODE_POINT_TO_POINT)\n    {\n        le->proto = ce->proto;\n    }\n}\n\n#ifdef _WIN32\n/* If iservice is in use, we need def1 method for redirect-gateway */\nstatic void\nremap_redirect_gateway_flags(struct options *opt)\n{\n    if (opt->routes && opt->route_method == ROUTE_METHOD_SERVICE\n        && opt->routes->flags & RG_REROUTE_GW && !(opt->routes->flags & RG_DEF1))\n    {\n        msg(M_INFO, \"Flag 'def1' added to --redirect-gateway (iservice is in use)\");\n        opt->routes->flags |= RG_DEF1;\n    }\n}\n#endif /* ifdef _WIN32 */\n\n/*\n * Save/Restore certain option defaults before --pull is applied.\n */\n\nstatic void\npre_connect_save(struct options *o)\n{\n    ALLOC_OBJ_CLEAR_GC(o->pre_connect, struct options_pre_connect, &o->gc);\n    o->pre_connect->tuntap_options = o->tuntap_options;\n    o->pre_connect->tuntap_options_defined = true;\n    o->pre_connect->foreign_option_index = o->foreign_option_index;\n\n    if (o->routes)\n    {\n        o->pre_connect->routes = clone_route_option_list(o->routes, &o->gc);\n        o->pre_connect->routes_defined = true;\n    }\n    if (o->routes_ipv6)\n    {\n        o->pre_connect->routes_ipv6 = clone_route_ipv6_option_list(o->routes_ipv6, &o->gc);\n        o->pre_connect->routes_ipv6_defined = true;\n    }\n    if (o->client_nat)\n    {\n        o->pre_connect->client_nat = clone_client_nat_option_list(o->client_nat, &o->gc);\n        o->pre_connect->client_nat_defined = true;\n    }\n\n    o->pre_connect->ifconfig_local = o->ifconfig_local;\n    o->pre_connect->ifconfig_ipv6_local = o->ifconfig_ipv6_local;\n\n    o->pre_connect->route_default_gateway = o->route_default_gateway;\n    o->pre_connect->route_ipv6_default_gateway = o->route_ipv6_default_gateway;\n\n    o->pre_connect->dns_options = clone_dns_options(&o->dns_options, &o->gc);\n\n    /* NCP related options that can be overwritten by a push */\n    o->pre_connect->ciphername = o->ciphername;\n    o->pre_connect->authname = o->authname;\n\n    /* Ping related options should be reset to the config values on reconnect */\n    o->pre_connect->ping_rec_timeout = o->ping_rec_timeout;\n    o->pre_connect->ping_rec_timeout_action = o->ping_rec_timeout_action;\n    o->pre_connect->ping_send_timeout = o->ping_send_timeout;\n\n    /* Miscellaneous Options */\n    o->pre_connect->comp = o->comp;\n}\n\nvoid\npre_connect_restore(struct options *o, struct gc_arena *gc)\n{\n    const struct options_pre_connect *pp = o->pre_connect;\n    if (pp)\n    {\n        CLEAR(o->tuntap_options);\n        if (pp->tuntap_options_defined)\n        {\n            o->tuntap_options = pp->tuntap_options;\n        }\n\n        if (pp->routes_defined)\n        {\n            rol_check_alloc(o);\n            copy_route_option_list(o->routes, pp->routes, gc);\n        }\n        else\n        {\n            o->routes = NULL;\n        }\n\n        if (pp->routes_ipv6_defined)\n        {\n            rol6_check_alloc(o);\n            copy_route_ipv6_option_list(o->routes_ipv6, pp->routes_ipv6, gc);\n        }\n        else\n        {\n            o->routes_ipv6 = NULL;\n        }\n\n        o->ifconfig_local = pp->ifconfig_local;\n        o->ifconfig_ipv6_local = pp->ifconfig_ipv6_local;\n\n        o->route_default_gateway = pp->route_default_gateway;\n        o->route_ipv6_default_gateway = pp->route_ipv6_default_gateway;\n\n        /* Free DNS options and reset them to pre-pull state */\n        gc_free(&o->dns_options.gc);\n        struct gc_arena dns_gc = gc_new();\n        o->dns_options = clone_dns_options(&pp->dns_options, &dns_gc);\n        o->dns_options.gc = dns_gc;\n\n        if (pp->client_nat_defined)\n        {\n            cnol_check_alloc(o);\n            copy_client_nat_option_list(o->client_nat, pp->client_nat);\n        }\n        else\n        {\n            o->client_nat = NULL;\n        }\n\n        o->foreign_option_index = pp->foreign_option_index;\n\n        o->ciphername = pp->ciphername;\n        o->authname = pp->authname;\n\n        o->ping_rec_timeout = pp->ping_rec_timeout;\n        o->ping_rec_timeout_action = pp->ping_rec_timeout_action;\n        o->ping_send_timeout = pp->ping_send_timeout;\n\n        /* Miscellaneous Options */\n        o->comp = pp->comp;\n    }\n\n    o->push_continuation = 0;\n    o->push_option_types_found = 0;\n    o->push_update_options_found = 0;\n    o->imported_protocol_flags = 0;\n}\n\nstatic void\noptions_postprocess_mutate_invariant(struct options *options)\n{\n#ifdef _WIN32\n    const int dev = dev_type_enum(options->dev, options->dev_type);\n\n    /* when using ovpn-dco, kernel doesn't send DHCP requests, so don't use it */\n    if ((options->windows_driver == DRIVER_DCO)\n        && (options->tuntap_options.ip_win32_type == IPW32_SET_DHCP_MASQ\n            || options->tuntap_options.ip_win32_type == IPW32_SET_ADAPTIVE))\n    {\n        options->tuntap_options.ip_win32_type = IPW32_SET_NETSH;\n    }\n\n    if ((dev == DEV_TYPE_TUN || dev == DEV_TYPE_TAP) && !options->route_delay_defined)\n    {\n        /* delay may only be necessary when we perform DHCP handshake */\n        const bool dhcp = (options->tuntap_options.ip_win32_type == IPW32_SET_DHCP_MASQ)\n                          || (options->tuntap_options.ip_win32_type == IPW32_SET_ADAPTIVE);\n        if ((options->mode == MODE_POINT_TO_POINT) && dhcp)\n        {\n            options->route_delay_defined = true;\n            options->route_delay = 5; /* Vista sometimes has a race without this */\n        }\n    }\n\n    if (options->ifconfig_noexec)\n    {\n        options->tuntap_options.ip_win32_type = IPW32_SET_MANUAL;\n        options->ifconfig_noexec = false;\n    }\n\n    remap_redirect_gateway_flags(options);\n\n    /*\n     * Check consistency of --mode server options.\n     */\n    if (options->mode == MODE_SERVER)\n    {\n        /*\n         * We need to explicitly set --tap-sleep because\n         * we do not schedule event timers in the top-level context.\n         */\n        options->tuntap_options.tap_sleep = 10;\n        if (options->route_delay_defined && options->route_delay)\n        {\n            options->tuntap_options.tap_sleep = options->route_delay;\n        }\n        options->route_delay_defined = false;\n    }\n#endif /* ifdef _WIN32 */\n\n#ifdef DEFAULT_PKCS11_MODULE\n    /* If p11-kit is present on the system then load its p11-kit-proxy.so\n     * by default if the user asks for PKCS#11 without otherwise specifying\n     * the module to use. */\n    if (!options->pkcs11_providers[0] && (options->pkcs11_id || options->pkcs11_id_management))\n    {\n        options->pkcs11_providers[0] = DEFAULT_PKCS11_MODULE;\n    }\n#endif\n}\n\nstatic void\noptions_postprocess_verify(const struct options *o)\n{\n    if (o->connection_list)\n    {\n        int i;\n        for (i = 0; i < o->connection_list->len; ++i)\n        {\n            options_postprocess_verify_ce(o, o->connection_list->array[i]);\n        }\n    }\n    else\n    {\n        options_postprocess_verify_ce(o, &o->ce);\n    }\n\n    dns_options_verify(M_FATAL, &o->dns_options);\n\n    if (dco_enabled(o))\n    {\n        if (o->enable_c2c)\n        {\n            msg(M_WARN, \"Note: --client-to-client has no effect when using data \"\n                        \"channel offload: packets are always sent to the VPN \"\n                        \"interface and then routed based on the system routing table\");\n        }\n\n        if (o->renegotiate_bytes > 0 || o->renegotiate_packets)\n        {\n            msg(M_WARN, \"Note: '--reneg-bytes' and '--reneg-pkts' are not supported \"\n                        \"by data channel offload; automatic key renegotiation \"\n                        \"mechanisms are sufficient for modern ciphers. \"\n                        \"Ignoring these options.\");\n        }\n    }\n}\n\nstatic void\noptions_postprocess_cipher(struct options *o)\n{\n    if (!o->pull && !(o->mode == MODE_SERVER))\n    {\n        /* If the cipher is not set, use the old default of BF-CBC. We will\n         * warn that this is deprecated on cipher initialisation, no need\n         * to warn here as well */\n        if (!o->ciphername)\n        {\n            o->ciphername = \"BF-CBC\";\n        }\n        else\n        {\n            o->enable_ncp_fallback = true;\n        }\n        return;\n    }\n\n    /* pull or P2MP mode */\n    if (!o->ciphername)\n    {\n        /* We still need to set the ciphername to BF-CBC since various other\n         * parts of OpenVPN assert that the ciphername is set */\n        o->ciphername = \"BF-CBC\";\n\n        msg(M_INFO,\n            \"Note: --cipher is not set. OpenVPN versions before 2.5 \"\n            \"defaulted to BF-CBC as fallback when cipher negotiation \"\n            \"failed in this case. If you need this fallback please add \"\n            \"'--data-ciphers-fallback BF-CBC' to your configuration \"\n            \"and/or add BF-CBC to --data-ciphers. E.g. \"\n            \"--data-ciphers %s:BF-CBC\",\n            o->ncp_ciphers_conf);\n    }\n    else if (!o->enable_ncp_fallback && !tls_item_in_cipher_list(o->ciphername, o->ncp_ciphers))\n    {\n        msg(M_WARN,\n            \"DEPRECATED OPTION: --cipher set to '%s' but missing in \"\n            \"--data-ciphers (%s). OpenVPN ignores --cipher for cipher \"\n            \"negotiations. \",\n            o->ciphername, o->ncp_ciphers_conf);\n    }\n}\n\n/**\n * The option --compat-mode is used to set up default settings to values\n * used on the specified openvpn version and earlier.\n *\n * This function is used in various \"default option\" paths to test if the\n * user requested compatibility with a version before the one specified\n * as argument. This way some default settings can be automatically\n * altered to guarantee compatibility with the version specified by the\n * user via --compat-mode.\n *\n * @param o         Options state\n * @param version   need compatibility with openvpn versions before the\n *                  one specified (20401 = before 2.4.1)\n * @return          whether compatibility should be enabled\n */\nstatic bool\nneed_compatibility_before(const struct options *o, unsigned int version)\n{\n    return o->backwards_compatible != 0 && o->backwards_compatible < version;\n}\n\n/**\n * Changes default values so that OpenVPN can be compatible with the user\n * specified version\n */\nstatic void\noptions_set_backwards_compatible_options(struct options *o)\n{\n    /* TLS min version is not set */\n    unsigned int tls_ver_min = (o->ssl_flags >> SSLF_TLS_VERSION_MIN_SHIFT) & SSLF_TLS_VERSION_MIN_MASK;\n    if (tls_ver_min == 0)\n    {\n        unsigned int tls_ver_max = (o->ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT) & SSLF_TLS_VERSION_MAX_MASK;\n        if (need_compatibility_before(o, 20307))\n        {\n            /* 2.3.6 and earlier have TLS 1.0 only, set minimum to TLS 1.0 */\n            o->ssl_flags |= (TLS_VER_1_0 << SSLF_TLS_VERSION_MIN_SHIFT);\n        }\n        else if (tls_ver_max == 0 || tls_ver_max >= TLS_VER_1_2)\n        {\n            /* Use TLS 1.2 as proper default */\n            o->ssl_flags |= (TLS_VER_1_2 << SSLF_TLS_VERSION_MIN_SHIFT);\n        }\n        else\n        {\n            /* Maximize the minimum version */\n            o->ssl_flags |= (tls_ver_max << SSLF_TLS_VERSION_MIN_SHIFT);\n        }\n    }\n\n    if (need_compatibility_before(o, 20400))\n    {\n        if (!o->ciphername)\n        {\n            /* If ciphername is not set default to BF-CBC when targeting these\n             * old versions that do not have NCP */\n            o->ciphername = \"BF-CBC\";\n        }\n        /* Versions < 2.4.0 additionally might be compiled with --enable-small and\n         * not have OCC strings required for \"poor man's NCP\" */\n        o->enable_ncp_fallback = true;\n    }\n\n    /* Versions < 2.5.0 do need --cipher in the list of accepted ciphers.\n     * Version 2.4 probably does not need it but NCP was not so\n     * good with 2.4 and ncp-disable might be more common on 2.4 peers.\n     * Only do this iff --cipher is set (explicitly or by compat mode\n     * < 2.4.0, see above). This is not 100% correct backwards compatible\n     * behaviour but 2.5 already behaved like this */\n    if (o->ciphername && need_compatibility_before(o, 20500)\n        && !tls_item_in_cipher_list(o->ciphername, o->ncp_ciphers))\n    {\n        append_cipher_to_ncp_list(o, o->ciphername);\n    }\n\n#ifdef USE_COMP\n    /* Compression is deprecated and we do not want to announce support for it\n     * by default anymore, additionally DCO breaks with compression.\n     *\n     * Disable compression by default starting with 2.6.0 if no other\n     * compression related option has been explicitly set */\n    if (!need_compatibility_before(o, 20600) && (o->comp.flags == 0))\n    {\n        if (!comp_non_stub_enabled(&o->comp))\n        {\n            o->comp.flags = COMP_F_ALLOW_STUB_ONLY | COMP_F_ADVERTISE_STUBS_ONLY;\n        }\n    }\n#else /* ifdef USE_COMP */\n    o->comp.flags = COMP_F_ALLOW_NOCOMP_ONLY;\n#endif\n}\n\nstatic void\noptions_process_mutate_prf(struct options *o)\n{\n    if (!check_tls_prf_working())\n    {\n        msg(D_TLS_ERRORS, \"Warning: TLS 1.0 PRF with MD5+SHA1 PRF is not \"\n                          \"supported by the TLS library. Your system does not support this \"\n                          \"calculation anymore or your security policy (e.g. FIPS 140-2) \"\n                          \"forbids it. Connections will only work with peers running \"\n                          \"OpenVPN 2.6.0 or higher)\");\n        if (o->mode == MODE_SERVER)\n        {\n            msg(M_WARN, \"Automatically enabling option \"\n                        \"--force-tls-key-material-export\");\n            o->force_key_material_export = true;\n        }\n    }\n}\n\n#if defined(_WIN32) || defined(TARGET_ANDROID)\n/**\n * @brief Postprocess DNS related settings\n *\n * Set TUN/TAP DNS options with values from either --dns\n * or --dhcp-option.\n *\n * @param o     pointer to the options struct\n */\nstatic void\ntuntap_options_postprocess_dns(struct options *o)\n{\n    struct dns_options *dns = &o->dns_options;\n    struct tuntap_options *tt = &o->tuntap_options;\n    if (!dns->servers)\n    {\n        /* Copy --dhcp-options to tuntap_options */\n        struct dhcp_options *dhcp = &dns->from_dhcp;\n        ASSERT(sizeof(dhcp->dns) == sizeof(tt->dns));\n        ASSERT(sizeof(dhcp->dns6) == sizeof(tt->dns6));\n        ASSERT(sizeof(dhcp->domain_search_list) == sizeof(tt->domain_search_list));\n\n        tt->domain = dhcp->domain;\n        tt->dns_len = dhcp->dns_len;\n        tt->dns6_len = dhcp->dns6_len;\n\n        memcpy(tt->dns, dhcp->dns, sizeof(tt->dns));\n        memcpy(tt->dns6, dhcp->dns6, sizeof(tt->dns6));\n\n        tt->domain_search_list_len = dhcp->domain_search_list_len;\n        for (size_t i = 0; i < SIZE(tt->domain_search_list); ++i)\n        {\n            tt->domain_search_list[i] = dhcp->domain_search_list[i];\n        }\n\n        return;\n    }\n\n#if defined(_WIN32)\n    if (tt->ip_win32_type != IPW32_SET_DHCP_MASQ && tt->ip_win32_type != IPW32_SET_ADAPTIVE)\n    {\n        return; /* Not in DHCP mode */\n    }\n#endif          /* if defined(_WIN32) */\n\n    /* Copy --dns options to tuntap_options */\n\n    const struct dns_domain *d = dns->search_domains;\n    if (d)\n    {\n        tt->domain_search_list_len = 0;\n    }\n\n    while (d && tt->domain_search_list_len + 1 < N_SEARCH_LIST_LEN)\n    {\n        tt->domain_search_list[tt->domain_search_list_len++] = d->name;\n        d = d->next;\n    }\n    if (d)\n    {\n        msg(M_WARN, \"WARNING: couldn't copy all --dns search-domains to TUN/TAP\");\n    }\n\n    tt->dns_len = 0;\n    tt->dns6_len = 0;\n\n    const struct dns_server *s = dns->servers;\n    while (s)\n    {\n        bool non_standard_server_port = false;\n        for (size_t i = 0; i < s->addr_count; ++i)\n        {\n            if (s->addr[i].port && s->addr[i].port != 53)\n            {\n                non_standard_server_port = true;\n                break;\n            }\n        }\n        if ((s->transport && s->transport != DNS_TRANSPORT_PLAIN)\n            || (s->dnssec && s->dnssec != DNS_SECURITY_NO) || non_standard_server_port)\n        {\n            /* Skip servers requiring unsupported config to be set */\n            s = s->next;\n        }\n        else\n        {\n            bool overflow = false;\n            for (size_t i = 0; i < s->addr_count; ++i)\n            {\n                if (s->addr[i].family == AF_INET && tt->dns_len + 1 < N_DHCP_ADDR)\n                {\n                    tt->dns[tt->dns_len++] = ntohl(s->addr[i].in.a4.s_addr);\n                }\n                else if (tt->dns6_len + 1 < N_DHCP_ADDR)\n                {\n                    tt->dns6[tt->dns6_len++] = s->addr[i].in.a6;\n                }\n                else\n                {\n                    overflow = true;\n                }\n            }\n            if (overflow)\n            {\n                msg(M_WARN, \"WARNING: couldn't copy all --dns server addresses to TUN/TAP\");\n            }\n            tt->dhcp_options |= DHCP_OPTIONS_DHCP_OPTIONAL;\n            return;\n        }\n    }\n}\n\n#else  /* if defined(_WIN32) || defined(TARGET_ANDROID) */\n\n/**\n * @brief Postprocess DNS related settings\n *\n * Discard existing --dhcp-options from the env if needed and possibly\n * replace them with values from --dns. If no --dns servers are set copy\n * the --dhcp-option values over for --dns-updown runs.\n *\n * @param o     pointer to the options struct\n * @param es    env set to modify potentially\n */\nstatic void\ndhcp_options_postprocess_dns(struct options *o, struct env_set *es)\n{\n    struct gc_arena gc = gc_new();\n    struct dns_options *dns = &o->dns_options;\n\n    if (is_tun_afunix(o->dev_node))\n    {\n        /* Disable running  dns-updown script with lwipovpn */\n        dns->updown_flags = DNS_UPDOWN_NO_FLAGS;\n        dns->updown = NULL;\n    }\n\n    if (dns->servers || dns_updown_user_set(dns) || dns_updown_forced(dns))\n    {\n        /* Clean up env from --dhcp-option DNS config */\n        struct buffer name = alloc_buf_gc(OPTION_PARM_SIZE, &gc);\n        struct buffer value = alloc_buf_gc(OPTION_PARM_SIZE, &gc);\n\n        const int fo_count = o->foreign_option_index;\n        o->foreign_option_index = 0;\n\n        for (int i = 1; i <= fo_count; ++i)\n        {\n            buf_clear(&name);\n            buf_printf(&name, \"foreign_option_%d\", i);\n            const char *env_str = env_set_get(es, BSTR(&name));\n            const char *item_val = strchr(env_str, '=') + 1;\n            buf_clear(&value);\n            buf_printf(&value, \"%s\", item_val);\n\n            /* Remove foreign option item from env set */\n            env_set_del(es, BSTR(&name));\n\n            item_val = BSTR(&value);\n            if (strncmp(item_val, \"dhcp-option \", 12) != 0\n                || (strncmp(item_val + 12, \"ADAPTER-DOMAIN-SUFFIX \", 22) != 0\n                    && strncmp(item_val + 12, \"DOMAIN-SEARCH \", 14) != 0\n                    && strncmp(item_val + 12, \"DOMAIN \", 7) != 0\n                    && strncmp(item_val + 12, \"DNS6 \", 5) != 0\n                    && strncmp(item_val + 12, \"DNS \", 4) != 0))\n            {\n                /* Re-set the item with potentially updated name */\n                buf_clear(&name);\n                buf_printf(&name, \"foreign_option_%d\", ++o->foreign_option_index);\n                setenv_str(es, BSTR(&name), BSTR(&value));\n            }\n        }\n    }\n\n    if (!dns->servers)\n    {\n        /* Copy --dhcp-options to dns_options */\n        struct dhcp_options *dhcp = &dns->from_dhcp;\n\n        if (dhcp->dns_len || dhcp->dns6_len)\n        {\n            struct dns_domain **entry = &dns->search_domains;\n            ALLOC_OBJ_CLEAR_GC(*entry, struct dns_domain, &dns->gc);\n            struct dns_domain *new = *entry;\n            new->name = dhcp->domain;\n            entry = &new->next;\n\n            for (unsigned int i = 0; i < dhcp->domain_search_list_len; ++i)\n            {\n                ALLOC_OBJ_CLEAR_GC(*entry, struct dns_domain, &dns->gc);\n                struct dns_domain *new = *entry;\n                new->name = dhcp->domain_search_list[i];\n                entry = &new->next;\n            }\n\n            struct dns_server *server = dns_server_get(&dns->servers, 0, &dns->gc);\n            const size_t max_addrs = SIZE(server->addr);\n            for (unsigned int i = 0; i < dhcp->dns_len && server->addr_count < max_addrs; ++i)\n            {\n                server->addr[server->addr_count].in.a4.s_addr = htonl(dhcp->dns[i]);\n                server->addr[server->addr_count].family = AF_INET;\n                server->addr_count += 1;\n            }\n            for (unsigned int i = 0; i < dhcp->dns6_len && server->addr_count < max_addrs; ++i)\n            {\n                server->addr[server->addr_count].in.a6 = dhcp->dns6[i];\n                server->addr[server->addr_count].family = AF_INET6;\n                server->addr_count += 1;\n            }\n        }\n    }\n    else if (o->up_script && !dns_updown_user_set(dns) && !dns_updown_forced(dns))\n    {\n        /* Set foreign option env vars from --dns config */\n        const struct dns_domain *d = dns->search_domains;\n        while (d)\n        {\n            setenv_foreign_option(o, \"DOMAIN\", d->name, es);\n            d = d->next;\n        }\n\n        const struct dns_server *s = dns->servers;\n        while (s)\n        {\n            bool non_standard_server_port = false;\n            for (size_t i = 0; i < s->addr_count; ++i)\n            {\n                if (s->addr[i].port && s->addr[i].port != 53)\n                {\n                    non_standard_server_port = true;\n                    break;\n                }\n            }\n            if ((s->transport && s->transport != DNS_TRANSPORT_PLAIN)\n                || (s->dnssec && s->dnssec != DNS_SECURITY_NO) || non_standard_server_port)\n            {\n                /* Skip servers requiring unsupported config to be set */\n                s = s->next;\n            }\n            else\n            {\n                for (size_t i = 0; i < s->addr_count; ++i)\n                {\n                    const char *option;\n                    const char *value;\n                    if (s->addr[i].family == AF_INET)\n                    {\n                        option = \"DNS\";\n                        value = print_in_addr_t(s->addr[i].in.a4.s_addr, IA_NET_ORDER, &gc);\n                    }\n                    else\n                    {\n                        option = \"DNS6\";\n                        value = print_in6_addr(s->addr[i].in.a6, 0, &gc);\n                    }\n                    setenv_foreign_option(o, option, value, es);\n                }\n                break;\n            }\n        }\n    }\n\n    gc_free(&gc);\n}\n#endif /* if defined(_WIN32) || defined(TARGET_ANDROID) */\n\nstatic void\noptions_postprocess_mutate(struct options *o, struct env_set *es)\n{\n    int i;\n    /*\n     * Process helper-type options which map to other, more complex\n     * sequences of options.\n     */\n    helper_client_server(o);\n    /* must be called after helpers that might set --mode */\n    helper_setdefault_topology(o);\n    helper_keepalive(o);\n    helper_tcp_nodelay(o);\n\n    options_postprocess_setdefault_ncpciphers(o);\n    options_set_backwards_compatible_options(o);\n    options_process_mutate_prf(o);\n\n    options_postprocess_cipher(o);\n    o->ncp_ciphers = mutate_ncp_cipher_list(o->ncp_ciphers, &o->gc);\n    if (o->ncp_ciphers == NULL)\n    {\n        msg(M_USAGE, \"--data-ciphers list contains unsupported ciphers or is too long.\");\n    }\n\n    if (o->remote_list && !o->connection_list)\n    {\n        /*\n         * Convert remotes into connection list\n         */\n        const struct remote_list *rl = o->remote_list;\n        for (i = 0; i < rl->len; ++i)\n        {\n            const struct remote_entry *re = rl->array[i];\n            struct connection_entry ce = o->ce;\n            struct connection_entry *ace;\n\n            ASSERT(re->remote);\n            connection_entry_load_re(&ce, re);\n            ace = alloc_connection_entry(o, M_USAGE);\n            ASSERT(ace);\n            *ace = ce;\n        }\n    }\n    else if (!o->remote_list && !o->connection_list)\n    {\n        struct connection_entry *ace;\n        ace = alloc_connection_entry(o, M_USAGE);\n        ASSERT(ace);\n        *ace = o->ce;\n    }\n\n    ASSERT(o->connection_list);\n    for (i = 0; i < o->connection_list->len; ++i)\n    {\n        options_postprocess_mutate_ce(o, o->connection_list->array[i]);\n    }\n\n    if (o->ce.local_list)\n    {\n        for (i = 0; i < o->ce.local_list->len; i++)\n        {\n            options_postprocess_mutate_le(&o->ce, o->ce.local_list->array[i], o->mode);\n        }\n\n        for (int i = 0; i < o->ce.local_list->len; i++)\n        {\n            if (o->ce.local_list->array[i]->proto == PROTO_TCP)\n            {\n                o->ce.local_list->array[i]->proto = PROTO_TCP_SERVER;\n            }\n            else if (o->ce.local_list->array[i]->proto == PROTO_NONE)\n            {\n                o->ce.local_list->array[i]->proto = o->ce.proto;\n            }\n        }\n    }\n    else\n    {\n        /* if no 'local' directive was specified, convert the global port\n         * setting to a listen entry */\n        struct local_entry *e = alloc_local_entry(&o->ce, M_USAGE, &o->gc);\n        ASSERT(e);\n        e->port = o->ce.local_port;\n        e->proto = o->ce.proto;\n    }\n\n    /* use the same listen list for every outgoing connection */\n    for (i = 0; i < o->connection_list->len; ++i)\n    {\n        o->connection_list->array[i]->local_list = o->ce.local_list;\n    }\n\n    if (o->tls_server)\n    {\n        if (o->dh_file && streq(o->dh_file, \"none\"))\n        {\n            o->dh_file = NULL;\n        }\n    }\n    else if (o->dh_file)\n    {\n        /* DH file is only meaningful in a tls-server context. */\n        msg(M_WARN, \"WARNING: Ignoring option 'dh' in tls-client mode, please only \"\n                    \"include this in your server configuration\");\n        o->dh_file = NULL;\n    }\n#if ENABLE_MANAGEMENT\n    if (o->http_proxy_override)\n    {\n        options_postprocess_http_proxy_override(o);\n    }\n#endif\n    if (!o->ca_file && !o->ca_path && o->verify_hash && o->verify_hash_depth == 0)\n    {\n        msg(M_INFO, \"Using certificate fingerprint to verify peer (no CA \"\n                    \"option set). \");\n        o->verify_hash_no_ca = true;\n    }\n\n    if (o->config && streq(o->config, \"stdin\") && o->remap_sigusr1 == SIGHUP)\n    {\n        msg(M_USAGE, \"Options 'config stdin' and 'remap-usr1 SIGHUP' are \"\n                     \"incompatible with each other.\");\n    }\n\n    if (dco_enabled(o))\n    {\n        /* check if any option should force disabling DCO */\n        o->disable_dco = !dco_check_option(D_DCO, o) || !dco_check_startup_option(D_DCO, o);\n    }\n#ifdef USE_COMP\n    if (dco_enabled(o))\n    {\n        o->comp.flags |= COMP_F_ALLOW_NOCOMP_ONLY;\n    }\n#endif\n\n#ifdef _WIN32\n    if (dco_enabled(o))\n    {\n        o->windows_driver = DRIVER_DCO;\n    }\n    else\n    {\n        o->windows_driver = WINDOWS_DRIVER_TAP_WINDOWS6;\n    }\n#else  /* _WIN32 */\n    if (dco_enabled(o) && o->dev_node)\n    {\n        msg(M_WARN, \"Note: ignoring --dev-node as it has no effect when using \"\n                    \"data channel offload\");\n        o->dev_node = NULL;\n    }\n#endif /* _WIN32 */\n\n    /* this depends on o->windows_driver, which is set above */\n    options_postprocess_mutate_invariant(o);\n\n    /* check that compression settings in the options are okay */\n    check_compression_settings_valid(&o->comp, M_USAGE);\n\n    /*\n     * Save certain parms before modifying options during connect, especially\n     * when using --pull\n     */\n    if (o->pull)\n    {\n        dns_options_preprocess_pull(&o->dns_options);\n    }\n    else\n    {\n#if defined(_WIN32) || defined(TARGET_ANDROID)\n        tuntap_options_postprocess_dns(o);\n#else\n        dhcp_options_postprocess_dns(o, es);\n#endif\n    }\n    if (o->auth_token_generate && !o->auth_token_renewal)\n    {\n        o->auth_token_renewal = o->renegotiate_seconds;\n    }\n    pre_connect_save(o);\n}\n\n/*\n *  Check file/directory sanity\n *\n */\n/* Expect people using the stripped down version to know what they do */\n#ifndef ENABLE_SMALL\n\n#define CHKACC_FILE       (1 << 0) /**< Check for a file/directory presence */\n#define CHKACC_DIRPATH    (1 << 1) /**< Check for directory presence where a file should reside */\n#define CHKACC_FILEXSTWR  (1 << 2) /**< If file exists, is it writable? */\n#define CHKACC_ACPTSTDIN  (1 << 3) /**< If filename is stdin, it's allowed and \"exists\" */\n#define CHKACC_PRIVATE    (1 << 4) /**< Warn if this (private) file is group/others accessible */\n#define CHKACC_ACCEPT_URI (1 << 5) /**< Do not check URIs, unless they start with file: */\n\nstatic bool\ncheck_file_access(const int type, const char *file, const int mode, const char *opt)\n{\n    int errcode = 0;\n\n    /* If no file configured, no errors to look for */\n    if (!file)\n    {\n        return false;\n    }\n\n    /* If stdin is allowed and the file name is 'stdin', then do no\n     * further checks as stdin is always available\n     */\n    if ((type & CHKACC_ACPTSTDIN) && streq(file, \"stdin\"))\n    {\n        return false;\n    }\n\n    /* file name is a URI if its first segment  has \":\" (i.e., before any \"/\")\n     * Then no checks done if CHKACC_ACCEPT_URI is set and the URI does not start with \"file:\"\n     */\n    if ((type & CHKACC_ACCEPT_URI) && strchr(file, ':'))\n    {\n        if (!strncmp(file, \"file:\", 5))\n        {\n            file += 5;\n        }\n        else if (!strchr(file, '/') || strchr(file, '/') > strchr(file, ':'))\n        {\n            return false;\n        }\n    }\n\n    /* Is the directory path leading to the given file accessible? */\n    if (type & CHKACC_DIRPATH)\n    {\n        char *fullpath =\n            string_alloc(file, NULL); /* POSIX dirname() implementation may modify its arguments */\n        char *dirpath = dirname(fullpath);\n\n        if (platform_access(dirpath, mode | X_OK) != 0)\n        {\n            errcode = errno;\n        }\n        free(fullpath);\n    }\n\n    /* Is the file itself accessible? */\n    if (!errcode && (type & CHKACC_FILE) && (platform_access(file, mode) != 0))\n    {\n        errcode = errno;\n    }\n\n    /* If the file exists and is accessible, is it writable? */\n    if (!errcode && (type & CHKACC_FILEXSTWR) && (platform_access(file, F_OK) == 0))\n    {\n        if (platform_access(file, W_OK) != 0)\n        {\n            errcode = errno;\n        }\n    }\n\n    /* Warn if a given private file is group/others accessible. */\n    if (type & CHKACC_PRIVATE)\n    {\n        platform_stat_t st;\n        if (platform_stat(file, &st))\n        {\n            msg(M_WARN | M_ERRNO, \"WARNING: cannot stat file '%s'\", file);\n        }\n#ifndef _WIN32\n        else\n        {\n            if (st.st_mode & (S_IRWXG | S_IRWXO))\n            {\n                msg(M_WARN, \"WARNING: file '%s' is group or others accessible\", file);\n            }\n        }\n#endif\n    }\n\n    /* Scream if an error is found */\n    if (errcode > 0)\n    {\n        msg(M_NOPREFIX | M_OPTERR | M_ERRNO, \"%s fails with '%s'\", opt, file);\n    }\n\n    /* Return true if an error occurred */\n    return (errcode != 0 ? true : false);\n}\n\n/* A wrapper for check_file_access() which also takes a chroot directory.\n * If chroot is NULL, behaviour is exactly the same as calling check_file_access() directly,\n * otherwise it will look for the file inside the given chroot directory instead.\n */\nstatic bool\ncheck_file_access_chroot(const char *chroot, const int type, const char *file, const int mode,\n                         const char *opt)\n{\n    bool ret = false;\n\n    /* If no file configured, no errors to look for */\n    if (!file)\n    {\n        return false;\n    }\n\n    /* If chroot is set, look for the file/directory inside the chroot */\n    if (chroot)\n    {\n        struct gc_arena gc = gc_new();\n        struct buffer chroot_file;\n\n        chroot_file = prepend_dir(chroot, file, &gc);\n        ret = check_file_access(type, BSTR(&chroot_file), mode, opt);\n        gc_free(&gc);\n    }\n    else\n    {\n        /* No chroot in play, just call core file check function */\n        ret = check_file_access(type, file, mode, opt);\n    }\n    return ret;\n}\n\n/**\n * A wrapper for check_file_access_chroot() that returns false immediately if\n * the file is inline (and therefore there is no access to check)\n */\nstatic bool\ncheck_file_access_chroot_inline(bool is_inline, const char *chroot, const int type,\n                                const char *file, const int mode, const char *opt)\n{\n    if (is_inline)\n    {\n        return false;\n    }\n\n    return check_file_access_chroot(chroot, type, file, mode, opt);\n}\n\n/**\n * A wrapper for check_file_access() that returns false immediately if the file\n * is inline (and therefore there is no access to check)\n */\nstatic bool\ncheck_file_access_inline(bool is_inline, const int type, const char *file, const int mode,\n                         const char *opt)\n{\n    if (is_inline)\n    {\n        return false;\n    }\n\n    return check_file_access(type, file, mode, opt);\n}\n\n/*\n * Verifies that the path in the \"command\" that comes after certain script options (e.g., --up) is a\n * valid file with appropriate permissions.\n *\n * \"command\" consists of a path, optionally followed by a space, which may be\n * followed by arbitrary arguments. It is NOT a full shell command line -- shell expansion is not\n * performed.\n *\n * The path and arguments in \"command\" may be single- or double-quoted or escaped.\n *\n * The path is extracted from \"command\", then check_file_access() is called to check it. The\n * arguments, if any, are ignored.\n *\n * Note that the type, mode, and opt arguments to this routine are the same as the corresponding\n * check_file_access() arguments.\n */\nstatic bool\ncheck_cmd_access(const char *command, const char *opt, const char *chroot)\n{\n    struct argv argv;\n    bool return_code;\n\n    /* If no command was set, there are no errors to look for */\n    if (!command)\n    {\n        return false;\n    }\n\n    /* Extract executable path and arguments */\n    argv = argv_new();\n    argv_parse_cmd(&argv, command);\n\n    /* if an executable is specified then check it; otherwise, complain */\n    if (argv.argv[0])\n    {\n        /* Scripts requires R_OK as well, but that might fail on binaries which\n         * only requires X_OK to function on Unix - a scenario not unlikely to\n         * be seen on suid binaries.\n         */\n        return_code = check_file_access_chroot(chroot, CHKACC_FILE, argv.argv[0], X_OK, opt);\n    }\n    else\n    {\n        msg(M_NOPREFIX | M_OPTERR, \"%s fails with '%s': No path to executable.\", opt, command);\n        return_code = true;\n    }\n\n    argv_free(&argv);\n\n    return return_code;\n}\n\n/*\n * Sanity check of all file/dir options.  Checks that file/dir\n * is accessible by OpenVPN\n */\nstatic void\noptions_postprocess_filechecks(struct options *options)\n{\n    bool errs = false;\n\n    /* ** SSL/TLS/crypto related files ** */\n    errs |= check_file_access_inline(options->dh_file_inline, CHKACC_FILE, options->dh_file, R_OK,\n                                     \"--dh\");\n\n    if (!options->verify_hash_no_ca)\n    {\n        errs |= check_file_access_inline(options->ca_file_inline, CHKACC_FILE, options->ca_file,\n                                         R_OK, \"--ca\");\n    }\n\n    errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE, options->ca_path, R_OK,\n                                     \"--capath\");\n\n    errs |= check_file_access_inline(options->cert_file_inline, CHKACC_FILE | CHKACC_ACCEPT_URI,\n                                     options->cert_file, R_OK, \"--cert\");\n\n    errs |= check_file_access_inline(options->extra_certs_file, CHKACC_FILE,\n                                     options->extra_certs_file, R_OK, \"--extra-certs\");\n\n    if (!(options->management_flags & MF_EXTERNAL_KEY))\n    {\n        errs |= check_file_access_inline(options->priv_key_file_inline,\n                                         CHKACC_FILE | CHKACC_PRIVATE | CHKACC_ACCEPT_URI,\n                                         options->priv_key_file, R_OK, \"--key\");\n    }\n\n    errs |= check_file_access_inline(options->pkcs12_file_inline, CHKACC_FILE | CHKACC_PRIVATE,\n                                     options->pkcs12_file, R_OK, \"--pkcs12\");\n\n    if (options->ssl_flags & SSLF_CRL_VERIFY_DIR)\n    {\n        errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE, options->crl_file,\n                                         R_OK | X_OK, \"--crl-verify directory\");\n    }\n    else\n    {\n        errs |=\n            check_file_access_chroot_inline(options->crl_file_inline, options->chroot_dir,\n                                            CHKACC_FILE, options->crl_file, R_OK, \"--crl-verify\");\n    }\n\n    if (options->tls_export_peer_cert_dir)\n    {\n        errs |=\n            check_file_access_chroot(options->chroot_dir, CHKACC_FILE,\n                                     options->tls_export_peer_cert_dir, W_OK, \"--tls-export-cert\");\n    }\n\n    ASSERT(options->connection_list);\n    for (int i = 0; i < options->connection_list->len; ++i)\n    {\n        struct connection_entry *ce = options->connection_list->array[i];\n\n        errs |= check_file_access_inline(ce->tls_auth_file_inline, CHKACC_FILE | CHKACC_PRIVATE,\n                                         ce->tls_auth_file, R_OK, \"--tls-auth\");\n        errs |= check_file_access_inline(ce->tls_crypt_file_inline, CHKACC_FILE | CHKACC_PRIVATE,\n                                         ce->tls_crypt_file, R_OK, \"--tls-crypt\");\n        errs |= check_file_access_inline(ce->tls_crypt_v2_file_inline, CHKACC_FILE | CHKACC_PRIVATE,\n                                         ce->tls_crypt_v2_file, R_OK, \"--tls-crypt-v2\");\n    }\n\n    errs |=\n        check_file_access_inline(options->shared_secret_file_inline, CHKACC_FILE | CHKACC_PRIVATE,\n                                 options->shared_secret_file, R_OK, \"--secret\");\n\n    errs |= check_file_access(CHKACC_DIRPATH | CHKACC_FILEXSTWR, options->packet_id_file,\n                              R_OK | W_OK, \"--replay-persist\");\n\n    /* ** Password files ** */\n    errs |= check_file_access(CHKACC_FILE | CHKACC_ACPTSTDIN | CHKACC_PRIVATE,\n                              options->key_pass_file, R_OK, \"--askpass\");\n#ifdef ENABLE_MANAGEMENT\n    errs |=\n        check_file_access(CHKACC_FILE | CHKACC_ACPTSTDIN | CHKACC_PRIVATE,\n                          options->management_user_pass, R_OK, \"--management user/password file\");\n#endif /* ENABLE_MANAGEMENT */\n    errs |= check_file_access_inline(options->auth_user_pass_file_inline,\n                                     CHKACC_FILE | CHKACC_ACPTSTDIN | CHKACC_PRIVATE,\n                                     options->auth_user_pass_file, R_OK, \"--auth-user-pass\");\n    /* ** System related ** */\n    errs |= check_file_access(CHKACC_FILE, options->chroot_dir, R_OK | X_OK, \"--chroot directory\");\n    errs |= check_file_access(CHKACC_DIRPATH | CHKACC_FILEXSTWR, options->writepid, R_OK | W_OK,\n                              \"--writepid\");\n\n    /* ** Log related ** */\n    errs |= check_file_access(CHKACC_DIRPATH | CHKACC_FILEXSTWR, options->status_file, R_OK | W_OK,\n                              \"--status\");\n\n    /* ** Config related ** */\n    errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE, options->client_config_dir,\n                                     R_OK | X_OK, \"--client-config-dir\");\n    errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE, options->tmp_dir,\n                                     R_OK | W_OK | X_OK, \"Temporary directory (--tmp-dir)\");\n\n    if (errs)\n    {\n        msg(M_USAGE, \"Please correct these errors.\");\n    }\n}\n#endif /* !ENABLE_SMALL */\n\n/*\n * Sanity check on options.\n * Also set some options based on other\n * options.\n */\nvoid\noptions_postprocess(struct options *options, struct env_set *es)\n{\n    options_postprocess_mutate(options, es);\n    options_postprocess_verify(options);\n#ifndef ENABLE_SMALL\n    options_postprocess_filechecks(options);\n#endif /* !ENABLE_SMALL */\n}\n\n/*\n * Sanity check on options after more options were pulled from server.\n * Also time to modify some options based on other options.\n */\nbool\noptions_postprocess_pull(struct options *o, struct env_set *es)\n{\n    bool success = dns_options_verify(D_PUSH_ERRORS, &o->dns_options);\n    if (success)\n    {\n        dns_options_postprocess_pull(&o->dns_options);\n#if defined(_WIN32) || defined(TARGET_ANDROID)\n        tuntap_options_postprocess_dns(o);\n#else\n        dhcp_options_postprocess_dns(o, es);\n#endif\n    }\n    return success;\n}\n\n/*\n * Build an options string to represent data channel encryption options.\n * This string must match exactly between peers.  The keysize is checked\n * separately by read_key().\n *\n * The following options must match on both peers:\n *\n * Tunnel options:\n *\n * --dev tun|tap [unit number need not match]\n * --dev-type tun|tap\n * --link-mtu\n * --udp-mtu\n * --tun-mtu\n * --proto udp\n * --proto tcp-client [matched with --proto tcp-server\n *                     on the other end of the connection]\n * --proto tcp-server [matched with --proto tcp-client on\n *                     the other end of the connection]\n * --tun-ipv6\n * --ifconfig x y [matched with --ifconfig y x on\n *                 the other end of the connection]\n *\n * --comp-lzo\n * --compress alg\n * --fragment\n *\n * Crypto Options:\n *\n * --cipher\n * --auth\n * --secret\n *\n * SSL Options:\n *\n * --tls-auth\n * --tls-client [matched with --tls-server on\n *               the other end of the connection]\n * --tls-server [matched with --tls-client on\n *               the other end of the connection]\n */\nchar *\noptions_string(const struct options *o, const struct frame *frame, struct tuntap *tt,\n               openvpn_net_ctx_t *ctx, bool remote, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf(OPTION_LINE_SIZE);\n    bool tt_local = false;\n\n    buf_printf(&out, \"V4\");\n\n    /*\n     * Tunnel Options\n     */\n\n    buf_printf(&out, \",dev-type %s\", dev_type_string(o->dev, o->dev_type));\n    /* the link-mtu that we send has only a meaning if have a fixed\n     * cipher (p2p) or have a fallback cipher configured for older non\n     * ncp clients. But not sending it will make even 2.4 complain\n     * about it being missing. So still send it. */\n    buf_printf(&out, \",link-mtu %u\", (unsigned int)calc_options_string_link_mtu(o, frame));\n\n    if (o->ce.occ_mtu != 0)\n    {\n        buf_printf(&out, \",tun-mtu %d\", o->ce.occ_mtu);\n    }\n    else\n    {\n        buf_printf(&out, \",tun-mtu %d\", frame->tun_mtu);\n    }\n\n    buf_printf(&out, \",proto %s\", proto_remote(o->ce.proto, remote));\n\n    bool p2p_nopull = o->mode == MODE_POINT_TO_POINT && !PULL_DEFINED(o);\n    /* send tun_ipv6 only in peer2peer mode - in client/server mode, it\n     * is usually pushed by the server, triggering a non-helpful warning\n     */\n    if (o->ifconfig_ipv6_local && p2p_nopull)\n    {\n        buf_printf(&out, \",tun-ipv6\");\n    }\n\n    /*\n     * Try to get ifconfig parameters into the options string.\n     * If tt is undefined, make a temporary instantiation.\n     */\n    if (!tt)\n    {\n        tt = init_tun(o->dev, o->dev_type, o->topology, o->ifconfig_local,\n                      o->ifconfig_remote_netmask, o->ifconfig_ipv6_local, o->ifconfig_ipv6_netbits,\n                      o->ifconfig_ipv6_remote, NULL, NULL, false, NULL, ctx, NULL);\n        if (tt)\n        {\n            tt_local = true;\n        }\n    }\n\n    if (tt && p2p_nopull)\n    {\n        const char *ios = ifconfig_options_string(tt, remote, o->ifconfig_nowarn, gc);\n        if (ios && strlen(ios))\n        {\n            buf_printf(&out, \",ifconfig %s\", ios);\n        }\n    }\n    if (tt_local)\n    {\n        free(tt);\n        tt = NULL;\n    }\n\n#ifdef USE_COMP\n    if (o->comp.alg != COMP_ALG_UNDEF)\n    {\n        buf_printf(&out, \",comp-lzo\"); /* for compatibility, this simply indicates that compression\n                                          context is active, not necessarily LZO per-se */\n    }\n#endif\n\n#ifdef ENABLE_FRAGMENT\n    if (o->ce.fragment)\n    {\n        buf_printf(&out, \",mtu-dynamic\");\n    }\n#endif\n\n#define TLS_CLIENT (o->tls_client)\n#define TLS_SERVER (o->tls_server)\n\n    /*\n     * Key direction\n     */\n    {\n        const char *kd = keydirection2ascii(o->key_direction, remote, false);\n        if (kd)\n        {\n            buf_printf(&out, \",keydir %s\", kd);\n        }\n    }\n\n    /*\n     * Crypto Options\n     */\n    if (o->shared_secret_file || TLS_CLIENT || TLS_SERVER)\n    {\n        struct key_type kt;\n\n        ASSERT((o->shared_secret_file != NULL) + (TLS_CLIENT == true) + (TLS_SERVER == true) <= 1);\n\n        /* Skip resolving BF-CBC to allow SSL libraries without BF-CBC\n         * to work here in the default configuration */\n        const char *ciphername = o->ciphername;\n        size_t keysize = 0;\n\n        if (strcmp(o->ciphername, \"BF-CBC\") == 0)\n        {\n            init_key_type(&kt, \"none\", o->authname, true, false);\n            keysize = 128;\n        }\n        else\n        {\n            init_key_type(&kt, o->ciphername, o->authname, true, false);\n            ciphername = cipher_kt_name(kt.cipher);\n            if (cipher_defined(o->ciphername))\n            {\n                keysize = cipher_kt_key_size(kt.cipher) * 8;\n            }\n        }\n        /* Only announce the cipher to our peer if we are willing to\n         * support it */\n        if (p2p_nopull || tls_item_in_cipher_list(ciphername, o->ncp_ciphers))\n        {\n            buf_printf(&out, \",cipher %s\", ciphername);\n        }\n        buf_printf(&out, \",auth %s\", md_kt_name(kt.digest));\n        buf_printf(&out, \",keysize %zu\", keysize);\n        if (o->shared_secret_file)\n        {\n            buf_printf(&out, \",secret\");\n        }\n    }\n\n    /*\n     * SSL Options\n     */\n    {\n        if (TLS_CLIENT || TLS_SERVER)\n        {\n            if (o->ce.tls_auth_file)\n            {\n                buf_printf(&out, \",tls-auth\");\n            }\n            /* Not adding tls-crypt here, because we won't reach this code if\n             * tls-auth/tls-crypt does not match.  Removing tls-auth here would\n             * break stuff, so leaving that in place. */\n\n            buf_printf(&out, \",key-method %d\", KEY_METHOD_2);\n        }\n\n        if (remote)\n        {\n            if (TLS_CLIENT)\n            {\n                buf_printf(&out, \",tls-server\");\n            }\n            else if (TLS_SERVER)\n            {\n                buf_printf(&out, \",tls-client\");\n            }\n        }\n        else\n        {\n            if (TLS_CLIENT)\n            {\n                buf_printf(&out, \",tls-client\");\n            }\n            else if (TLS_SERVER)\n            {\n                buf_printf(&out, \",tls-server\");\n            }\n        }\n    }\n\n#undef TLS_CLIENT\n#undef TLS_SERVER\n\n    return BSTR(&out);\n}\n\n/*\n * Compare option strings for equality.\n * If the first two chars of the strings differ, it means that\n * we are looking at different versions of the options string,\n * therefore don't compare them and return true.\n */\n\nbool\noptions_cmp_equal(char *actual, const char *expected)\n{\n    return options_cmp_equal_safe(actual, expected, strlen(actual) + 1);\n}\n\nvoid\noptions_warning(char *actual, const char *expected)\n{\n    options_warning_safe(actual, expected, strlen(actual) + 1);\n}\n\nstatic const char *\noptions_warning_extract_parm1(const char *option_string, struct gc_arena *gc_ret)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer b = string_alloc_buf(option_string, &gc);\n    char *p = gc_malloc(OPTION_PARM_SIZE, false, &gc);\n    const char *ret;\n\n    buf_parse(&b, ' ', p, OPTION_PARM_SIZE);\n    ret = string_alloc(p, gc_ret);\n    gc_free(&gc);\n    return ret;\n}\n\nstatic void\noptions_warning_safe_scan2(const msglvl_t msglevel, const int delim,\n                           const bool report_inconsistent, const char *p1,\n                           const struct buffer *b2_src, const char *b1_name,\n                           const char *b2_name)\n{\n    /* We will stop sending 'key-method', 'keydir', 'proto' and 'tls-auth' in\n     * OCC in a future version (because it's not useful). To reduce questions\n     * when interoperating, we no longer printing a warning about it.\n     */\n    if (strprefix(p1, \"key-method \") || strprefix(p1, \"keydir \") || strprefix(p1, \"proto \")\n        || streq(p1, \"tls-auth\") || strprefix(p1, \"tun-ipv6\") || strprefix(p1, \"cipher \"))\n    {\n        return;\n    }\n\n    if (strlen(p1) > 0)\n    {\n        struct gc_arena gc = gc_new();\n        struct buffer b2 = *b2_src;\n        const char *p1_prefix = options_warning_extract_parm1(p1, &gc);\n        char *p2 = gc_malloc(OPTION_PARM_SIZE, false, &gc);\n\n        while (buf_parse(&b2, delim, p2, OPTION_PARM_SIZE))\n        {\n            if (strlen(p2))\n            {\n                const char *p2_prefix = options_warning_extract_parm1(p2, &gc);\n\n                if (!strcmp(p1, p2))\n                {\n                    goto done;\n                }\n                if (!strcmp(p1_prefix, p2_prefix))\n                {\n                    if (report_inconsistent)\n                    {\n                        msg(msglevel, \"WARNING: '%s' is used inconsistently, %s='%s', %s='%s'\",\n                            safe_print(p1_prefix, &gc), b1_name, safe_print(p1, &gc), b2_name,\n                            safe_print(p2, &gc));\n                    }\n                    goto done;\n                }\n            }\n        }\n\n        msg(msglevel, \"WARNING: '%s' is present in %s config but missing in %s config, %s='%s'\",\n            safe_print(p1_prefix, &gc), b1_name, b2_name, b1_name, safe_print(p1, &gc));\n\ndone:\n        gc_free(&gc);\n    }\n}\n\nstatic void\noptions_warning_safe_scan1(const msglvl_t msglevel, const int delim,\n                           const bool report_inconsistent, const struct buffer *b1_src,\n                           const struct buffer *b2_src, const char *b1_name, const char *b2_name)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer b = *b1_src;\n    char *p = gc_malloc(OPTION_PARM_SIZE, true, &gc);\n\n    while (buf_parse(&b, delim, p, OPTION_PARM_SIZE))\n    {\n        options_warning_safe_scan2(msglevel, delim, report_inconsistent, p, b2_src, b1_name,\n                                   b2_name);\n    }\n\n    gc_free(&gc);\n}\n\nstatic void\noptions_warning_safe_ml(const msglvl_t msglevel, char *actual, const char *expected, size_t actual_n)\n{\n    struct gc_arena gc = gc_new();\n\n    if (actual_n > 0)\n    {\n        struct buffer local = alloc_buf_gc(OPTION_PARM_SIZE + 16, &gc);\n        struct buffer remote = alloc_buf_gc(OPTION_PARM_SIZE + 16, &gc);\n        actual[actual_n - 1] = 0;\n\n        buf_printf(&local, \"version %s\", expected);\n        buf_printf(&remote, \"version %s\", actual);\n\n        options_warning_safe_scan1(msglevel, ',', true, &local, &remote, \"local\", \"remote\");\n\n        options_warning_safe_scan1(msglevel, ',', false, &remote, &local, \"remote\", \"local\");\n    }\n\n    gc_free(&gc);\n}\n\nbool\noptions_cmp_equal_safe(char *actual, const char *expected, size_t actual_n)\n{\n    struct gc_arena gc = gc_new();\n    bool ret = true;\n\n    if (actual_n > 0)\n    {\n        actual[actual_n - 1] = 0;\n        if (strncmp(actual, expected, 2))\n        {\n            msg(D_SHOW_OCC, \"NOTE: Options consistency check may be skewed by version differences\");\n            options_warning_safe_ml(D_SHOW_OCC, actual, expected, actual_n);\n        }\n        else\n        {\n            ret = !strcmp(actual, expected);\n        }\n    }\n    gc_free(&gc);\n    return ret;\n}\n\nvoid\noptions_warning_safe(char *actual, const char *expected, size_t actual_n)\n{\n    options_warning_safe_ml(D_SHOW_OCC, actual, expected, actual_n);\n}\n\nconst char *\noptions_string_version(const char *s, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(4, gc);\n    strncpynt((char *)BPTR(&out), s, 3);\n    return BSTR(&out);\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\nchar *\noptions_string_extract_option(const char *options_string, const char *opt_name, struct gc_arena *gc)\n{\n    char *ret = NULL;\n    const size_t opt_name_len = strlen(opt_name);\n\n    const char *p = options_string;\n    while (p)\n    {\n        if (0 == strncmp(p, opt_name, opt_name_len) && strlen(p) > (opt_name_len + 1)\n            && p[opt_name_len] == ' ')\n        {\n            /* option found, extract value */\n            const char *start = &p[opt_name_len + 1];\n            const char *end = strchr(p, ',');\n            size_t val_len = end ? end - start : strlen(start);\n            ret = gc_malloc(val_len + 1, true, gc);\n            memcpy(ret, start, val_len);\n            break;\n        }\n        p = strchr(p, ',');\n        if (p)\n        {\n            p++; /* skip delimiter */\n        }\n    }\n    return ret;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n/*\n * parse/print topology coding\n */\n\nint\nparse_topology(const char *str, const msglvl_t msglevel)\n{\n    if (streq(str, \"net30\"))\n    {\n        return TOP_NET30;\n    }\n    else if (streq(str, \"p2p\"))\n    {\n        return TOP_P2P;\n    }\n    else if (streq(str, \"subnet\"))\n    {\n        return TOP_SUBNET;\n    }\n    else\n    {\n        msg(msglevel, \"--topology must be net30, p2p, or subnet\");\n        return TOP_UNDEF;\n    }\n}\n\nconst char *\nprint_topology(const int topology)\n{\n    switch (topology)\n    {\n        case TOP_UNDEF:\n            return \"undef\";\n\n        case TOP_NET30:\n            return \"net30\";\n\n        case TOP_P2P:\n            return \"p2p\";\n\n        case TOP_SUBNET:\n            return \"subnet\";\n\n        default:\n            return \"unknown\";\n    }\n}\n\n/*\n * Manage auth-retry variable\n */\n\nstatic int global_auth_retry; /* GLOBAL */\n\nint\nauth_retry_get(void)\n{\n    return global_auth_retry;\n}\n\nbool\nauth_retry_set(const msglvl_t msglevel, const char *option)\n{\n    if (streq(option, \"interact\"))\n    {\n        global_auth_retry = AR_INTERACT;\n    }\n    else if (streq(option, \"nointeract\"))\n    {\n        global_auth_retry = AR_NOINTERACT;\n    }\n    else if (streq(option, \"none\"))\n    {\n        global_auth_retry = AR_NONE;\n    }\n    else\n    {\n        msg(msglevel, \"--auth-retry method must be 'interact', 'nointeract', or 'none'\");\n        return false;\n    }\n    return true;\n}\n\nconst char *\nauth_retry_print(void)\n{\n    switch (global_auth_retry)\n    {\n        case AR_NONE:\n            return \"none\";\n\n        case AR_NOINTERACT:\n            return \"nointeract\";\n\n        case AR_INTERACT:\n            return \"interact\";\n\n        default:\n            return \"???\";\n    }\n}\n\n/*\n * Print the help message.\n */\nvoid\nusage(void)\n{\n    FILE *fp = msg_fp(0);\n\n#ifdef ENABLE_SMALL\n\n    fprintf(fp, \"Usage message not available\\n\");\n\n#else\n\n    struct options o;\n    init_options(&o);\n\n    fprintf(fp, usage_message, title_string, o.ce.connect_retry_seconds,\n            o.ce.connect_retry_seconds_max, o.ce.local_port, o.ce.remote_port, TUN_MTU_DEFAULT,\n            TAP_MTU_EXTRA_DEFAULT, TUN_MTU_MAX_MIN, o.verbosity, o.authname, o.replay_window,\n            o.replay_time, o.tls_timeout, o.renegotiate_seconds, o.handshake_window,\n            o.transition_window);\n    fflush(fp);\n\n#endif                                       /* ENABLE_SMALL */\n\n    openvpn_exit(OPENVPN_EXIT_STATUS_USAGE); /* exit point */\n}\n\nvoid\nusage_small(void)\n{\n    msg(M_WARN | M_NOPREFIX, \"Use --help for more information.\");\n    openvpn_exit(OPENVPN_EXIT_STATUS_USAGE); /* exit point */\n}\n\n#ifdef _WIN32\nvoid\nshow_windows_version(const unsigned int flags)\n{\n    struct gc_arena gc = gc_new();\n    msg(flags, \"Windows version: %s\", win32_version_string(&gc));\n    gc_free(&gc);\n}\n#endif\n\nvoid\nshow_dco_version(const unsigned int flags)\n{\n#ifdef ENABLE_DCO\n    struct gc_arena gc = gc_new();\n    msg(flags, \"DCO version: %s\", dco_version_string(&gc));\n    gc_free(&gc);\n#endif\n}\n\nvoid\nshow_library_versions(const unsigned int flags)\n{\n#ifdef ENABLE_LZO\n#define LZO_LIB_VER_STR \", LZO \", lzo_version_string()\n#else\n#define LZO_LIB_VER_STR \"\", \"\"\n#endif\n\n    msg(flags, \"library versions: %s%s%s\", get_ssl_library_version(), LZO_LIB_VER_STR);\n\n#undef LZO_LIB_VER_STR\n}\n\nstatic void\nusage_version(void)\n{\n    msg(M_INFO | M_NOPREFIX, \"%s\", title_string);\n    show_library_versions(M_INFO | M_NOPREFIX);\n#ifdef _WIN32\n    show_windows_version(M_INFO | M_NOPREFIX);\n#endif\n    show_dco_version(M_INFO | M_NOPREFIX);\n    msg(M_INFO | M_NOPREFIX, \"Originally developed by James Yonan\");\n    msg(M_INFO | M_NOPREFIX, \"Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\");\n#ifndef ENABLE_SMALL\n#ifdef CONFIGURE_DEFINES\n    msg(M_INFO | M_NOPREFIX, \"Compile time defines: %s\", CONFIGURE_DEFINES);\n#endif\n#ifdef CONFIGURE_SPECIAL_BUILD\n    msg(M_INFO | M_NOPREFIX, \"special build: %s\", CONFIGURE_SPECIAL_BUILD);\n#endif\n#endif\n    openvpn_exit(OPENVPN_EXIT_STATUS_GOOD);\n}\n\nvoid\nnotnull(const char *arg, const char *description)\n{\n    if (!arg)\n    {\n        msg(M_USAGE, \"You must define %s\", description);\n    }\n}\n\nbool\nstring_defined_equal(const char *s1, const char *s2)\n{\n    if (s1 && s2)\n    {\n        return !strcmp(s1, s2);\n    }\n    else\n    {\n        return false;\n    }\n}\n\n#if 0\nstatic void\nping_rec_err(msglvl_t msglevel)\n{\n    msg(msglevel, \"only one of --ping-exit or --ping-restart options may be specified\");\n}\n#endif\n\n#ifdef _WIN32 /* This function is only used when compiling on Windows */\nstatic unsigned int\natou(const char *str)\n{\n    unsigned int val = 0;\n    sscanf(str, \"%u\", &val);\n    return val;\n}\n#endif\n\n#define VERIFY_PERMISSION(mask)                                                               \\\n    {                                                                                         \\\n        if (!verify_permission(p[0], file, line, (mask), permission_mask, option_types_found, \\\n                               msglevel, options, is_inline))                                 \\\n        {                                                                                     \\\n            goto err;                                                                         \\\n        }                                                                                     \\\n    }\n\nstatic bool\nverify_permission(const char *name, const char *file, int line, const unsigned int type,\n                  const unsigned int allowed, unsigned int *found, const msglvl_t msglevel,\n                  struct options *options, bool is_inline)\n{\n    if (!(type & allowed))\n    {\n        msg(msglevel, \"option '%s' cannot be used in this context (%s)\", name, file);\n        return false;\n    }\n\n    if (is_inline && !(type & OPT_P_INLINE))\n    {\n        msg(msglevel, \"option '%s' is not expected to be inline (%s:%d)\", name, file, line);\n        return false;\n    }\n\n    if (found)\n    {\n        *found |= type;\n    }\n\n#ifndef ENABLE_SMALL\n    /* Check if this options is allowed in connection block,\n     * but we are currently not in a connection block\n     * unless this is a pushed option.\n     * Parsing a connection block uses a temporary options struct without\n     * connection_list\n     */\n\n    if ((type & OPT_P_CONNECTION) && options->connection_list && !(allowed & OPT_P_PULL_MODE))\n    {\n        if (file)\n        {\n            msg(M_WARN, \"Option '%s' in %s:%d is ignored by previous <connection> blocks \", name,\n                file, line);\n        }\n        else\n        {\n            msg(M_WARN, \"Option '%s' is ignored by previous <connection> blocks\", name);\n        }\n    }\n#endif\n    return true;\n}\n\n/*\n * Check that an option doesn't have too\n * many parameters.\n */\n\n#define NM_QUOTE_HINT (1 << 0)\n\nstatic bool\nno_more_than_n_args(const msglvl_t msglevel, char *p[], const int max, const unsigned int flags)\n{\n    const int len = string_array_len((const char **)p);\n\n    if (!len)\n    {\n        return false;\n    }\n\n    if (len > max)\n    {\n        msg(msglevel, \"the --%s directive should have at most %d parameter%s.%s\", p[0], max - 1,\n            max >= 3 ? \"s\" : \"\",\n            (flags & NM_QUOTE_HINT)\n                ? \"  To pass a list of arguments as one of the parameters, try enclosing them in double quotes (\\\"\\\").\"\n                : \"\");\n        return false;\n    }\n    else\n    {\n        return true;\n    }\n}\n\nstatic inline msglvl_t\nmsglevel_forward_compatible(struct options *options, const msglvl_t msglevel)\n{\n    return options->forward_compatible ? M_WARN : msglevel;\n}\n\n#define RESET_OPTION_ROUTES(option_ptr, field) \\\n    if (option_ptr)                            \\\n    {                                          \\\n        option_ptr->field = NULL;              \\\n        option_ptr->flags = 0;                 \\\n    }\n\nvoid\nremove_option(struct context *c, struct options *options, char *p[], bool is_inline,\n              const char *file, int line, const msglvl_t msglevel,\n              const unsigned int permission_mask, unsigned int *option_types_found,\n              struct env_set *es)\n{\n    msglvl_t msglevel_fc = msglevel_forward_compatible(options, msglevel);\n\n    if (streq(p[0], \"ifconfig\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_UP);\n        options->ifconfig_local = NULL;\n        options->ifconfig_remote_netmask = NULL;\n    }\n    else if (streq(p[0], \"ifconfig-ipv6\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_UP);\n        options->ifconfig_ipv6_local = NULL;\n        options->ifconfig_ipv6_netbits = 0;\n        options->ifconfig_ipv6_remote = NULL;\n    }\n    else if (streq(p[0], \"route\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE);\n        if (c->c1.route_list)\n        {\n            delete_routes_v4(c->c1.route_list, c->c1.tuntap, ROUTE_OPTION_FLAGS(&c->options), es,\n                             &c->net_ctx);\n            RESET_OPTION_ROUTES(options->routes, routes);\n        }\n    }\n    else if (streq(p[0], \"route-ipv6\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE);\n        if (c->c1.route_ipv6_list)\n        {\n            delete_routes_v6(c->c1.route_ipv6_list, c->c1.tuntap, ROUTE_OPTION_FLAGS(&c->options),\n                             es, &c->net_ctx);\n            RESET_OPTION_ROUTES(options->routes_ipv6, routes_ipv6);\n        }\n    }\n    else if (streq(p[0], \"route-gateway\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE_EXTRAS);\n        options->route_gateway_via_dhcp = false;\n        options->route_default_gateway = NULL;\n    }\n    else if (streq(p[0], \"route-metric\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE);\n        options->route_default_metric = 0;\n    }\n    else if (streq(p[0], \"push-continuation\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_PULL_MODE);\n        options->push_continuation = 0;\n    }\n    else if ((streq(p[0], \"redirect-gateway\") || streq(p[0], \"redirect-private\")) && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE);\n        if (options->routes)\n        {\n            options->routes->flags = 0;\n        }\n        if (options->routes_ipv6)\n        {\n            options->routes_ipv6->flags = 0;\n        }\n        env_set_del(es, \"route_redirect_gateway_ipv4\");\n        env_set_del(es, \"route_redirect_gateway_ipv6\");\n    }\n    else if (streq(p[0], \"dns\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_DHCPDNS);\n        gc_free(&options->dns_options.gc);\n        CLEAR(options->dns_options);\n    }\n    else if (streq(p[0], \"topology\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_UP);\n        options->topology = TOP_UNDEF;\n        helper_setdefault_topology(options);\n    }\n    else if (streq(p[0], \"tun-mtu\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_PUSH_MTU | OPT_P_CONNECTION);\n        options->ce.tun_mtu = TUN_MTU_DEFAULT;\n        options->ce.tun_mtu_defined = false;\n        options->ce.occ_mtu = 0;\n    }\n    else if (streq(p[0], \"block-ipv6\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE);\n        options->block_ipv6 = false;\n    }\n#if defined(_WIN32) || defined(TARGET_ANDROID)\n    else if (streq(p[0], \"dhcp-option\") && !p[1])\n    {\n        struct tuntap_options *o = &options->tuntap_options;\n        VERIFY_PERMISSION(OPT_P_DHCPDNS);\n\n        o->domain = NULL;\n        o->netbios_scope = NULL;\n        o->netbios_node_type = 0;\n        o->dns6_len = 0;\n        memset(o->dns6, 0, sizeof(o->dns6));\n        o->dns_len = 0;\n        memset(o->dns, 0, sizeof(o->dns));\n        o->wins_len = 0;\n        memset(o->wins, 0, sizeof(o->wins));\n        o->ntp_len = 0;\n        memset(o->ntp, 0, sizeof(o->ntp));\n        o->nbdd_len = 0;\n        memset(o->nbdd, 0, sizeof(o->nbdd));\n        while (o->domain_search_list_len-- > 0)\n        {\n            o->domain_search_list[o->domain_search_list_len] = NULL;\n        }\n        o->disable_nbt = 0;\n        o->dhcp_options = 0;\n#if defined(TARGET_ANDROID)\n        o->http_proxy_port = 0;\n        o->http_proxy = NULL;\n#endif\n    }\n#endif /* if defined(_WIN32) || defined(TARGET_ANDROID) */\n#ifdef _WIN32\n    else if (streq(p[0], \"block-outside-dns\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_DHCPDNS);\n        options->block_outside_dns = false;\n    }\n#else /* ifdef _WIN32 */\n    else if (streq(p[0], \"dhcp-option\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_DHCPDNS);\n        delete_all_dhcp_fo(options, &es->list);\n    }\n#endif\n    else\n    {\n        msglvl_t msglevel_unknown = msglevel_fc;\n        /* Check if an option is in --ignore-unknown-option and\n         * set warning level to non fatal */\n        for (int i = 0; options->ignore_unknown_option && options->ignore_unknown_option[i]; i++)\n        {\n            if (streq(p[0], options->ignore_unknown_option[i]))\n            {\n                msglevel_unknown = M_WARN;\n                break;\n            }\n        }\n        msg(msglevel_unknown,\n            \"Unrecognized option or missing or extra parameter(s) in %s:%d: -%s (%s)\", file, line,\n            p[0], PACKAGE_VERSION);\n    }\n    return;\nerr:\n    msg(msglevel, \"Error occurred trying to remove %s option\", p[0]);\n}\n\n\nstatic bool\ncheck_route_option(struct options *options, char *p[], const msglvl_t msglevel, bool pull_mode)\n{\n    rol_check_alloc(options);\n    if (pull_mode)\n    {\n        if (!ip_or_dns_addr_safe(p[1], options->allow_pull_fqdn)\n            && !is_special_addr(p[1])) /* FQDN -- may be DNS name */\n        {\n            msg(msglevel, \"route parameter network/IP '%s' must be a valid address\", p[1]);\n            return false;\n        }\n        if (p[2] && !ip_addr_dotted_quad_safe(p[2])) /* FQDN -- must be IP address */\n        {\n            msg(msglevel, \"route parameter netmask '%s' must be an IP address\", p[2]);\n            return false;\n        }\n        if (p[3] && !ip_or_dns_addr_safe(p[3], options->allow_pull_fqdn)\n            && !is_special_addr(p[3])) /* FQDN -- may be DNS name */\n        {\n            msg(msglevel, \"route parameter gateway '%s' must be a valid address\", p[3]);\n            return false;\n        }\n    }\n    return true;\n}\n\n\nstatic bool\ncheck_route6_option(struct options *options, char *p[], const msglvl_t msglevel, bool pull_mode)\n{\n    rol6_check_alloc(options);\n    if (pull_mode)\n    {\n        if (!ipv6_addr_safe_hexplusbits(p[1]))\n        {\n            msg(msglevel, \"route-ipv6 parameter network/IP '%s' must be a valid address\", p[1]);\n            return false;\n        }\n        if (p[2] && !ipv6_addr_safe(p[2]))\n        {\n            msg(msglevel, \"route-ipv6 parameter gateway '%s' must be a valid address\", p[2]);\n            return false;\n        }\n        /* p[3] is metric, if present */\n    }\n    return true;\n}\n\nstatic bool\ncheck_dns_option(struct options *options, char *p[], const msglvl_t msglevel, bool pull_mode)\n{\n    if (streq(p[1], \"search-domains\") && p[2])\n    {\n        if (!dns_domain_list_append(&options->dns_options.search_domains, &p[2],\n                                    &options->dns_options.gc))\n        {\n            msg(msglevel, \"--dns %s contain invalid characters\", p[1]);\n            return false;\n        }\n    }\n    else if (streq(p[1], \"server\") && p[2] && p[3] && p[4])\n    {\n        long priority;\n        if (!dns_server_priority_parse(&priority, p[2], pull_mode))\n        {\n            msg(msglevel, \"--dns server: invalid priority value '%s'\", p[2]);\n            return false;\n        }\n\n        struct dns_server *server =\n            dns_server_get(&options->dns_options.servers, priority, &options->dns_options.gc);\n\n        if (streq(p[3], \"address\") && p[4])\n        {\n            for (int i = 4; p[i]; ++i)\n            {\n                if (!dns_server_addr_parse(server, p[i]))\n                {\n                    msg(msglevel, \"--dns server %ld: malformed address or maximum exceeded '%s'\",\n                        priority, p[i]);\n                    return false;\n                }\n            }\n        }\n        else if (streq(p[3], \"resolve-domains\"))\n        {\n            if (!dns_domain_list_append(&server->domains, &p[4], &options->dns_options.gc))\n            {\n                msg(msglevel, \"--dns server %ld: %s contain invalid characters\", priority, p[3]);\n                return false;\n            }\n        }\n        else if (streq(p[3], \"dnssec\") && !p[5])\n        {\n            if (streq(p[4], \"yes\"))\n            {\n                server->dnssec = DNS_SECURITY_YES;\n            }\n            else if (streq(p[4], \"no\"))\n            {\n                server->dnssec = DNS_SECURITY_NO;\n            }\n            else if (streq(p[4], \"optional\"))\n            {\n                server->dnssec = DNS_SECURITY_OPTIONAL;\n            }\n            else\n            {\n                msg(msglevel, \"--dns server %ld: malformed dnssec value '%s'\", priority, p[4]);\n                return false;\n            }\n        }\n        else if (streq(p[3], \"transport\") && !p[5])\n        {\n            if (streq(p[4], \"plain\"))\n            {\n                server->transport = DNS_TRANSPORT_PLAIN;\n            }\n            else if (streq(p[4], \"DoH\"))\n            {\n                server->transport = DNS_TRANSPORT_HTTPS;\n            }\n            else if (streq(p[4], \"DoT\"))\n            {\n                server->transport = DNS_TRANSPORT_TLS;\n            }\n            else\n            {\n                msg(msglevel, \"--dns server %ld: malformed transport value '%s'\", priority, p[4]);\n                return false;\n            }\n        }\n        else if (streq(p[3], \"sni\") && !p[5])\n        {\n            if (!validate_domain(p[4]))\n            {\n                msg(msglevel, \"--dns server %ld: %s contains invalid characters\", priority, p[3]);\n                return false;\n            }\n            server->sni = p[4];\n        }\n        else\n        {\n            msg(msglevel,\n                \"--dns server %ld: unknown option type '%s' or missing or unknown parameter\",\n                priority, p[3]);\n            return false;\n        }\n    }\n    else\n    {\n        msg(msglevel, \"--dns: unknown option type '%s' or missing or unknown parameter\", p[1]);\n        return false;\n    }\n    return true;\n}\n\nvoid\nupdate_option(struct context *c, struct options *options, char *p[], bool is_inline,\n              const char *file, int line, const int level, const msglvl_t msglevel,\n              const unsigned int permission_mask, unsigned int *option_types_found,\n              struct env_set *es)\n{\n    const bool pull_mode = BOOL_CAST(permission_mask & OPT_P_PULL_MODE);\n    ASSERT(MAX_PARMS >= 7);\n\n    if (streq(p[0], \"route\") && p[1] && !p[5])\n    {\n        if (!(options->push_update_options_found & OPT_P_U_ROUTE))\n        {\n            VERIFY_PERMISSION(OPT_P_ROUTE);\n            if (!check_route_option(options, p, msglevel, pull_mode))\n            {\n                goto err;\n            }\n            if (c->c1.route_list)\n            {\n                delete_routes_v4(c->c1.route_list, c->c1.tuntap, ROUTE_OPTION_FLAGS(&c->options),\n                                 es, &c->net_ctx);\n                RESET_OPTION_ROUTES(options->routes, routes);\n            }\n            options->push_update_options_found |= OPT_P_U_ROUTE;\n        }\n    }\n    else if (streq(p[0], \"route-ipv6\") && p[1] && !p[4])\n    {\n        if (!(options->push_update_options_found & OPT_P_U_ROUTE6))\n        {\n            VERIFY_PERMISSION(OPT_P_ROUTE);\n            if (!check_route6_option(options, p, msglevel, pull_mode))\n            {\n                goto err;\n            }\n            if (c->c1.route_ipv6_list)\n            {\n                delete_routes_v6(c->c1.route_ipv6_list, c->c1.tuntap,\n                                 ROUTE_OPTION_FLAGS(&c->options), es, &c->net_ctx);\n                RESET_OPTION_ROUTES(options->routes_ipv6, routes_ipv6);\n            }\n            options->push_update_options_found |= OPT_P_U_ROUTE6;\n        }\n    }\n    else if (streq(p[0], \"redirect-gateway\") || streq(p[0], \"redirect-private\"))\n    {\n        if (!(options->push_update_options_found & OPT_P_U_REDIR_GATEWAY))\n        {\n            VERIFY_PERMISSION(OPT_P_ROUTE);\n            if (options->routes)\n            {\n                options->routes->flags = 0;\n            }\n            if (options->routes_ipv6)\n            {\n                options->routes_ipv6->flags = 0;\n            }\n            env_set_del(es, \"route_redirect_gateway_ipv4\");\n            env_set_del(es, \"route_redirect_gateway_ipv6\");\n            options->push_update_options_found |= OPT_P_U_REDIR_GATEWAY;\n        }\n    }\n    else if (streq(p[0], \"dns\") && p[1])\n    {\n        if (!(options->push_update_options_found & OPT_P_U_DNS))\n        {\n            VERIFY_PERMISSION(OPT_P_DHCPDNS);\n            if (!check_dns_option(options, p, msglevel, pull_mode))\n            {\n                goto err;\n            }\n            gc_free(&options->dns_options.gc);\n            CLEAR(options->dns_options);\n            options->push_update_options_found |= OPT_P_U_DNS;\n        }\n    }\n#if defined(_WIN32) || defined(TARGET_ANDROID)\n    else if (streq(p[0], \"dhcp-option\") && p[1] && !p[3])\n    {\n        if (!(options->push_update_options_found & OPT_P_U_DHCP))\n        {\n            struct tuntap_options *o = &options->tuntap_options;\n            VERIFY_PERMISSION(OPT_P_DHCPDNS);\n\n            o->domain = NULL;\n            o->netbios_scope = NULL;\n            o->netbios_node_type = 0;\n            o->dns6_len = 0;\n            CLEAR(o->dns6);\n            o->dns_len = 0;\n            CLEAR(o->dns);\n            o->wins_len = 0;\n            CLEAR(o->wins);\n            o->ntp_len = 0;\n            CLEAR(o->ntp);\n            o->nbdd_len = 0;\n            CLEAR(o->nbdd);\n            while (o->domain_search_list_len-- > 0)\n            {\n                o->domain_search_list[o->domain_search_list_len] = NULL;\n            }\n            o->disable_nbt = 0;\n            o->dhcp_options = 0;\n\n            CLEAR(options->dns_options.from_dhcp);\n#if defined(TARGET_ANDROID)\n            o->http_proxy_port = 0;\n            o->http_proxy = NULL;\n#endif\n            options->push_update_options_found |= OPT_P_U_DHCP;\n        }\n    }\n#else  /* if defined(_WIN32) || defined(TARGET_ANDROID) */\n    else if (streq(p[0], \"dhcp-option\") && p[1] && !p[3])\n    {\n        if (!(options->push_update_options_found & OPT_P_U_DHCP))\n        {\n            VERIFY_PERMISSION(OPT_P_DHCPDNS);\n            delete_all_dhcp_fo(options, &es->list);\n            options->push_update_options_found |= OPT_P_U_DHCP;\n        }\n    }\n#endif /* if defined(_WIN32) || defined(TARGET_ANDROID) */\n    add_option(options, p, is_inline, file, line, level, msglevel, permission_mask,\n               option_types_found, es);\n    return;\nerr:\n    msg(msglevel, \"Error occurred trying to update %s option\", p[0]);\n}\n\nstatic void\nset_user_script(struct options *options, const char **script, const char *new_script,\n                const char *type, bool in_chroot)\n{\n    if (*script)\n    {\n        msg(M_WARN,\n            \"Multiple --%s scripts defined.  \"\n            \"The previously configured script is overridden.\",\n            type);\n    }\n    *script = new_script;\n    options->user_script_used = true;\n\n#ifndef ENABLE_SMALL\n    {\n        char script_name[100];\n        snprintf(script_name, sizeof(script_name), \"--%s script\", type);\n\n        if (check_cmd_access(*script, script_name, (in_chroot ? options->chroot_dir : NULL)))\n        {\n            msg(M_USAGE, \"Please correct this error.\");\n        }\n    }\n#endif\n}\n\nstatic void\nshow_compression_warning(struct compress_options *info)\n{\n    if (comp_non_stub_enabled(info))\n    {\n        msg(M_WARN, \"WARNING: Compression for receiving enabled. \"\n                    \"Compression has been used in the past to break encryption. \"\n                    \"Compression support is deprecated and we recommend to disable \"\n                    \"it completely.\");\n    }\n}\n\nbool\nkey_is_external(const struct options *options)\n{\n    bool ret = false;\n    ret = ret || (options->management_flags & MF_EXTERNAL_KEY);\n#ifdef ENABLE_PKCS11\n    ret = ret || (options->pkcs11_providers[0] != NULL);\n#endif\n#ifdef ENABLE_CRYPTOAPI\n    ret = ret || options->cryptoapi_cert;\n#endif\n\n    return ret;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\nvoid\nadd_option(struct options *options, char *p[], bool is_inline, const char *file, int line,\n           const int level, const msglvl_t msglevel, const unsigned int permission_mask,\n           unsigned int *option_types_found, struct env_set *es)\n{\n    struct gc_arena gc = gc_new();\n    const bool pull_mode = BOOL_CAST(permission_mask & OPT_P_PULL_MODE);\n    msglvl_t msglevel_fc = msglevel_forward_compatible(options, msglevel);\n\n    ASSERT(MAX_PARMS >= 7);\n\n    /*\n     * If directive begins with \"setenv opt\" prefix, don't raise an error if\n     * directive is unrecognized.\n     */\n    if (streq(p[0], \"setenv\") && p[1] && streq(p[1], \"opt\") && !(permission_mask & OPT_P_PULL_MODE))\n    {\n        if (!p[2])\n        {\n            p[2] = \"setenv opt\"; /* will trigger an error that includes setenv opt */\n        }\n        p += 2;\n        msglevel_fc = M_WARN;\n    }\n\n    if (!file)\n    {\n        file = \"[CMD-LINE]\";\n        line = 1;\n    }\n    if (streq(p[0], \"help\"))\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        usage();\n        if (p[1])\n        {\n            msg(msglevel, \"--help does not accept any parameters\");\n            goto err;\n        }\n    }\n    if (streq(p[0], \"version\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        usage_version();\n    }\n    else if (streq(p[0], \"config\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_CONFIG);\n\n        /* save first config file only in options */\n        if (!options->config)\n        {\n            options->config = p[1];\n        }\n\n        read_config_file(options, p[1], level, file, line, msglevel, permission_mask,\n                         option_types_found, es);\n    }\n#if defined(ENABLE_DEBUG) && !defined(ENABLE_SMALL)\n    else if (streq(p[0], \"show-gateway\") && !p[2])\n    {\n        struct route_gateway_info rgi;\n        struct route_ipv6_gateway_info rgi6;\n        in_addr_t remote_ipv4 = 0;\n        struct in6_addr remote_ipv6 = IN6ADDR_ANY_INIT;\n        openvpn_net_ctx_t net_ctx;\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (p[1])\n        {\n            /* try parsing the argument as a v4 or v6 address - if\n             * possible, the output will show the exact route there, and\n             * \"the default route\" for the other protocol\n             */\n            remote_ipv4 = get_ip_addr(p[1], M_WARN, NULL);\n            get_ipv6_addr(p[1], &remote_ipv6, NULL, M_WARN);\n        }\n        net_ctx_init(NULL, &net_ctx);\n        get_default_gateway(&rgi, remote_ipv4, &net_ctx);\n        get_default_gateway_ipv6(&rgi6, &remote_ipv6, &net_ctx);\n        print_default_gateway(M_INFO, &rgi, &rgi6);\n        openvpn_exit(OPENVPN_EXIT_STATUS_GOOD); /* exit point */\n    }\n#endif\n    else if (streq(p[0], \"echo\") || streq(p[0], \"parameter\"))\n    {\n        struct buffer string = alloc_buf_gc(OPTION_PARM_SIZE, &gc);\n        int j;\n        bool good = true;\n\n        VERIFY_PERMISSION(OPT_P_ECHO);\n\n        for (j = 1; j < MAX_PARMS; ++j)\n        {\n            if (!p[j])\n            {\n                break;\n            }\n            if (j > 1)\n            {\n                good &= buf_printf(&string, \" \");\n            }\n            good &= buf_printf(&string, \"%s\", p[j]);\n        }\n        if (good)\n        {\n            /* only message-related ECHO are logged, since other ECHOs\n             * can potentially include security-sensitive strings */\n            if (p[1] && strncmp(p[1], \"msg\", 3) == 0)\n            {\n                msg(M_INFO, \"%s:%s\", pull_mode ? \"ECHO-PULL\" : \"ECHO\", BSTR(&string));\n            }\n#ifdef ENABLE_MANAGEMENT\n            if (management)\n            {\n                management_echo(management, BSTR(&string), pull_mode);\n            }\n#endif\n        }\n        else\n        {\n            msg(M_WARN, \"echo/parameter option overflow\");\n        }\n    }\n#ifdef ENABLE_MANAGEMENT\n    else if (streq(p[0], \"management\") && p[1] && p[2] && !p[4])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (streq(p[2], \"unix\"))\n        {\n#if UNIX_SOCK_SUPPORT\n            options->management_flags |= MF_UNIX_SOCK;\n#else\n            msg(msglevel, \"MANAGEMENT: this platform does not support unix domain sockets\");\n            goto err;\n#endif\n        }\n\n        options->management_addr = p[1];\n        options->management_port = p[2];\n        if (p[3])\n        {\n            options->management_user_pass = p[3];\n        }\n    }\n    else if (streq(p[0], \"management-client-user\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->management_client_user = p[1];\n    }\n    else if (streq(p[0], \"management-client-group\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->management_client_group = p[1];\n    }\n    else if (streq(p[0], \"management-query-passwords\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->management_flags |= MF_QUERY_PASSWORDS;\n    }\n    else if (streq(p[0], \"management-query-remote\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->management_flags |= MF_QUERY_REMOTE;\n    }\n    else if (streq(p[0], \"management-query-proxy\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->management_flags |= MF_QUERY_PROXY;\n    }\n    else if (streq(p[0], \"management-hold\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->management_flags |= MF_HOLD;\n    }\n    else if (streq(p[0], \"management-signal\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->management_flags |= MF_SIGNAL;\n    }\n    else if (streq(p[0], \"management-forget-disconnect\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->management_flags |= MF_FORGET_DISCONNECT;\n    }\n    else if (streq(p[0], \"management-up-down\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->management_flags |= MF_UP_DOWN;\n    }\n    else if (streq(p[0], \"management-client\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->management_flags |= MF_CONNECT_AS_CLIENT;\n    }\n    else if (streq(p[0], \"management-external-key\"))\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        for (int j = 1; j < MAX_PARMS && p[j] != NULL; ++j)\n        {\n            if (streq(p[j], \"nopadding\"))\n            {\n                options->management_flags |= MF_EXTERNAL_KEY_NOPADDING;\n            }\n            else if (streq(p[j], \"pkcs1\"))\n            {\n                options->management_flags |= MF_EXTERNAL_KEY_PKCS1PAD;\n            }\n            else if (streq(p[j], \"pss\"))\n            {\n                options->management_flags |= MF_EXTERNAL_KEY_PSSPAD;\n            }\n            else if (streq(p[j], \"digest\"))\n            {\n                options->management_flags |= MF_EXTERNAL_KEY_DIGEST;\n            }\n            else\n            {\n                msg(msglevel, \"Unknown management-external-key flag: %s\", p[j]);\n            }\n        }\n        /*\n         * When no option is present, assume that only PKCS1\n         * padding is supported\n         */\n        if (!(options->management_flags & (MF_EXTERNAL_KEY_NOPADDING | MF_EXTERNAL_KEY_PKCS1PAD)))\n        {\n            options->management_flags |= MF_EXTERNAL_KEY_PKCS1PAD;\n        }\n        options->management_flags |= MF_EXTERNAL_KEY;\n    }\n    else if (streq(p[0], \"management-external-cert\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->management_flags |= MF_EXTERNAL_CERT;\n        options->management_certificate = p[1];\n    }\n    else if (streq(p[0], \"management-client-auth\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->management_flags |= MF_CLIENT_AUTH;\n    }\n    else if (streq(p[0], \"management-log-cache\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (!atoi_constrained(p[1], &options->management_log_history_cache,\n                              p[0], 1, INT_MAX, msglevel))\n        {\n            goto err;\n        }\n    }\n#endif /* ifdef ENABLE_MANAGEMENT */\n#ifdef ENABLE_PLUGIN\n    else if (streq(p[0], \"plugin\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_PLUGIN);\n        if (!options->plugin_list)\n        {\n            options->plugin_list = plugin_option_list_new(&options->gc);\n        }\n        if (!plugin_option_list_add(options->plugin_list, &p[1], &options->gc))\n        {\n            msg(msglevel, \"plugin add failed: %s\", p[1]);\n            goto err;\n        }\n    }\n#endif\n    else if (streq(p[0], \"mode\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (streq(p[1], \"p2p\"))\n        {\n            options->mode = MODE_POINT_TO_POINT;\n        }\n        else if (streq(p[1], \"server\"))\n        {\n            options->mode = MODE_SERVER;\n        }\n        else\n        {\n            msg(msglevel, \"Bad --mode parameter: %s\", p[1]);\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"dev\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->dev = p[1];\n    }\n    else if (streq(p[0], \"dev-type\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->dev_type = p[1];\n    }\n#ifdef _WIN32\n    else if (streq(p[0], \"windows-driver\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        msg(M_WARN,\n            \"DEPRECATED OPTION: windows-driver: In OpenVPN 2.7, the default Windows driver is ovpn-dco. \"\n            \"If incompatible options are used, OpenVPN will fall back to tap-windows6. Wintun support has been removed.\");\n    }\n#endif\n    else if (streq(p[0], \"disable-dco\"))\n    {\n        options->disable_dco = true;\n    }\n    else if (streq(p[0], \"dev-node\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->dev_node = p[1];\n    }\n    else if (streq(p[0], \"lladdr\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_UP);\n        if (mac_addr_safe(p[1])) /* MAC address only */\n        {\n            options->lladdr = p[1];\n        }\n        else\n        {\n            msg(msglevel, \"lladdr parm '%s' must be a MAC address\", p[1]);\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"topology\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_UP);\n        options->topology = parse_topology(p[1], msglevel);\n    }\n    else if (streq(p[0], \"tun-ipv6\") && !p[1])\n    {\n        if (!pull_mode)\n        {\n            msg(M_WARN,\n                \"Note: option tun-ipv6 is ignored because modern operating systems do not need special IPv6 tun handling anymore.\");\n        }\n    }\n#ifdef ENABLE_IPROUTE\n    else if (streq(p[0], \"iproute\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        iproute_path = p[1];\n    }\n#endif\n    else if (streq(p[0], \"ifconfig\") && p[1] && p[2] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_UP);\n        if (ip_or_dns_addr_safe(p[1], options->allow_pull_fqdn)\n            && ip_or_dns_addr_safe(p[2], options->allow_pull_fqdn)) /* FQDN -- may be DNS name */\n        {\n            options->ifconfig_local = p[1];\n            options->ifconfig_remote_netmask = p[2];\n        }\n        else\n        {\n            msg(msglevel, \"ifconfig parms '%s' and '%s' must be valid addresses\", p[1], p[2]);\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"ifconfig-ipv6\") && p[1] && p[2] && !p[3])\n    {\n        unsigned int netbits;\n\n        VERIFY_PERMISSION(OPT_P_UP);\n        if (get_ipv6_addr(p[1], NULL, &netbits, msglevel) && ipv6_addr_safe(p[2]))\n        {\n            if (netbits < 64 || netbits > 124)\n            {\n                msg(msglevel, \"ifconfig-ipv6: /netbits must be between 64 and 124, not '/%d'\",\n                    netbits);\n                goto err;\n            }\n\n            options->ifconfig_ipv6_local = get_ipv6_addr_no_netbits(p[1], &options->gc);\n            options->ifconfig_ipv6_netbits = netbits;\n            options->ifconfig_ipv6_remote = p[2];\n        }\n        else\n        {\n            msg(msglevel, \"ifconfig-ipv6 parms '%s' and '%s' must be valid addresses\", p[1], p[2]);\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"ifconfig-noexec\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_UP);\n        options->ifconfig_noexec = true;\n    }\n    else if (streq(p[0], \"ifconfig-nowarn\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_UP);\n        options->ifconfig_nowarn = true;\n    }\n    else if (streq(p[0], \"local\") && p[1] && !p[4])\n    {\n        struct local_entry *e;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n\n        e = alloc_local_entry(&options->ce, M_USAGE, &options->gc);\n        ASSERT(e);\n\n        /* '*' is treated as 'ask the system to get some socket',\n         * therefore force binding on a particular address only when\n         * actually specified. */\n        if (strcmp(p[1], \"*\") != 0)\n        {\n            e->local = p[1];\n        }\n\n        if (p[2])\n        {\n            e->port = p[2];\n        }\n\n        if (p[3])\n        {\n            e->proto = ascii2proto(p[3]);\n        }\n    }\n    else if (streq(p[0], \"remote-random\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->remote_random = true;\n    }\n    else if (streq(p[0], \"connection\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);\n        if (is_inline)\n        {\n            struct options sub;\n            struct connection_entry *e;\n\n            init_options(&sub);\n            sub.ce = options->ce;\n            read_config_string(\"[CONNECTION-OPTIONS]\", &sub, p[1], msglevel, OPT_P_CONNECTION,\n                               option_types_found, es);\n            if (!sub.ce.remote)\n            {\n                msg(msglevel,\n                    \"Each 'connection' block must contain exactly one 'remote' directive\");\n                uninit_options(&sub);\n                goto err;\n            }\n\n            e = alloc_connection_entry(options, msglevel);\n            if (!e)\n            {\n                uninit_options(&sub);\n                goto err;\n            }\n            *e = sub.ce;\n            gc_transfer(&options->gc, &sub.gc);\n            uninit_options(&sub);\n        }\n    }\n    else if (streq(p[0], \"ignore-unknown-option\") && p[1])\n    {\n        int i;\n        int j;\n        int numignored = 0;\n        const char **ignore;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        /* Find out how many options to be ignored */\n        for (i = 1; p[i]; i++)\n        {\n            numignored++;\n        }\n\n        /* add number of options already ignored */\n        for (i = 0; options->ignore_unknown_option && options->ignore_unknown_option[i]; i++)\n        {\n            numignored++;\n        }\n\n        /* Allocate array */\n        ALLOC_ARRAY_GC(ignore, const char *, numignored + 1, &options->gc);\n        for (i = 0; options->ignore_unknown_option && options->ignore_unknown_option[i]; i++)\n        {\n            ignore[i] = options->ignore_unknown_option[i];\n        }\n\n        options->ignore_unknown_option = ignore;\n\n        for (j = 1; p[j]; j++)\n        {\n            /* Allow the user to specify ignore-unknown-option --opt too */\n            if (p[j][0] == '-' && p[j][1] == '-')\n            {\n                options->ignore_unknown_option[i] = (p[j] + 2);\n            }\n            else\n            {\n                options->ignore_unknown_option[i] = p[j];\n            }\n            i++;\n        }\n\n        options->ignore_unknown_option[i] = NULL;\n    }\n#if ENABLE_MANAGEMENT\n    else if (streq(p[0], \"http-proxy-override\") && p[1] && p[2] && !p[4])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->http_proxy_override = parse_http_proxy_override(p[1], p[2], p[3], &options->gc);\n        if (!options->http_proxy_override)\n        {\n            goto err;\n        }\n    }\n#endif\n    else if (streq(p[0], \"remote\") && p[1] && !p[4])\n    {\n        struct remote_entry re;\n        re.remote = re.remote_port = NULL;\n        re.proto = -1;\n        re.af = 0;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n        re.remote = p[1];\n        if (p[2])\n        {\n            re.remote_port = p[2];\n            if (p[3])\n            {\n                const int proto = ascii2proto(p[3]);\n                const sa_family_t af = ascii2af(p[3]);\n                if (proto < 0)\n                {\n                    msg(msglevel, \"remote: bad protocol associated with host %s: '%s'\", p[1], p[3]);\n                    goto err;\n                }\n                re.proto = proto;\n                re.af = af;\n            }\n        }\n        if (permission_mask & OPT_P_GENERAL)\n        {\n            struct remote_entry *e = alloc_remote_entry(options, msglevel);\n            if (!e)\n            {\n                goto err;\n            }\n            *e = re;\n        }\n        else if (permission_mask & OPT_P_CONNECTION)\n        {\n            connection_entry_load_re(&options->ce, &re);\n        }\n    }\n    else if (streq(p[0], \"resolv-retry\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (streq(p[1], \"infinite\"))\n        {\n            options->resolve_retry_seconds = RESOLV_RETRY_INFINITE;\n        }\n        else\n        {\n            options->resolve_retry_seconds = positive_atoi(p[1], msglevel);\n        }\n    }\n    else if ((streq(p[0], \"preresolve\") || streq(p[0], \"ip-remote-hint\")) && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->resolve_in_advance = true;\n        /* Note the ip-remote-hint and the argument p[1] are for\n         * backward compatibility */\n        if (p[1])\n        {\n            options->ip_remote_hint = p[1];\n        }\n    }\n    else if (streq(p[0], \"connect-retry\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n        options->ce.connect_retry_seconds = positive_atoi(p[1], msglevel);\n        /*\n         * Limit the base value of retry wait interval to 16 bits to avoid\n         * overflow when scaled up for exponential backoff\n         */\n        if (options->ce.connect_retry_seconds > 0xFFFF)\n        {\n            options->ce.connect_retry_seconds = 0xFFFF;\n            msg(M_WARN, \"connect retry wait interval truncated to %d\",\n                options->ce.connect_retry_seconds);\n        }\n\n        if (p[2])\n        {\n            options->ce.connect_retry_seconds_max =\n                max_int(positive_atoi(p[2], msglevel), options->ce.connect_retry_seconds);\n        }\n    }\n    else if ((streq(p[0], \"connect-timeout\") || streq(p[0], \"server-poll-timeout\")) && p[1]\n             && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n        options->ce.connect_timeout = positive_atoi(p[1], msglevel);\n    }\n    else if (streq(p[0], \"connect-retry-max\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n        options->connect_retry_max = positive_atoi(p[1], msglevel);\n    }\n    else if (streq(p[0], \"ipchange\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT))\n        {\n            goto err;\n        }\n        set_user_script(options, &options->ipchange,\n                        string_substitute(p[1], ',', ' ', &options->gc), \"ipchange\", true);\n    }\n    else if (streq(p[0], \"float\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n        options->ce.remote_float = true;\n    }\n#ifdef ENABLE_DEBUG\n    else if (streq(p[0], \"gremlin\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->gremlin = positive_atoi(p[1], msglevel);\n    }\n#endif\n    else if (streq(p[0], \"chroot\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->chroot_dir = p[1];\n    }\n    else if (streq(p[0], \"cd\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (platform_chdir(p[1]))\n        {\n            msg(M_ERR, \"cd to '%s' failed\", p[1]);\n            goto err;\n        }\n        options->cd_dir = p[1];\n    }\n#ifdef ENABLE_SELINUX\n    else if (streq(p[0], \"setcon\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->selinux_context = p[1];\n    }\n#endif\n    else if (streq(p[0], \"writepid\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->writepid = p[1];\n    }\n    else if (streq(p[0], \"up\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT))\n        {\n            goto err;\n        }\n        set_user_script(options, &options->up_script, p[1], \"up\", false);\n    }\n    else if (streq(p[0], \"down\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT))\n        {\n            goto err;\n        }\n        set_user_script(options, &options->down_script, p[1], \"down\", true);\n    }\n    else if (streq(p[0], \"down-pre\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->down_pre = true;\n    }\n    else if (streq(p[0], \"up-delay\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->up_delay = true;\n    }\n    else if (streq(p[0], \"up-restart\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->up_restart = true;\n    }\n    else if (streq(p[0], \"syslog\") && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        open_syslog(p[1], false);\n    }\n    else if (streq(p[0], \"daemon\") && !p[2])\n    {\n        bool didit = false;\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (!options->daemon)\n        {\n            options->daemon = didit = true;\n            open_syslog(p[1], false);\n        }\n        if (p[1])\n        {\n            if (!didit)\n            {\n                msg(M_WARN,\n                    \"WARNING: Multiple --daemon directives specified, ignoring --daemon %s. (Note that initscripts sometimes add their own --daemon directive.)\",\n                    p[1]);\n                goto err;\n            }\n        }\n    }\n    else if (streq(p[0], \"log\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->log = true;\n        redirect_stdout_stderr(p[1], false);\n    }\n    else if (streq(p[0], \"suppress-timestamps\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->suppress_timestamps = true;\n        set_suppress_timestamps(true);\n    }\n    else if (streq(p[0], \"machine-readable-output\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->machine_readable_output = true;\n        set_machine_readable_output(true);\n    }\n    else if (streq(p[0], \"log-append\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->log = true;\n        redirect_stdout_stderr(p[1], true);\n    }\n    else if (streq(p[0], \"mlock\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->mlock = true;\n    }\n#if ENABLE_IP_PKTINFO\n    else if (streq(p[0], \"multihome\") && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->sockflags |= SF_USE_IP_PKTINFO;\n        if (p[1] && streq(p[1], \"same-interface\"))\n        {\n            options->sockflags |= SF_PKTINFO_COPY_IIF;\n        }\n        else if (p[1])\n        {\n            msg(msglevel, \"Unknown parameter to --multihome: %s\", p[1]);\n        }\n    }\n#endif\n    else if (streq(p[0], \"verb\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_MESSAGES);\n        options->verbosity = positive_atoi(p[1], msglevel);\n        if (options->verbosity >= (D_TLS_DEBUG_MED & M_DEBUG_LEVEL))\n        {\n            /* We pass this flag to the SSL library to avoid\n             * mbed TLS always generating debug level logging */\n            options->ssl_flags |= SSLF_TLS_DEBUG_ENABLED;\n        }\n#if !defined(ENABLE_DEBUG) && !defined(ENABLE_SMALL)\n        /* Warn when a debug verbosity is supplied when built without debug support */\n        if (options->verbosity >= 7)\n        {\n            msg(M_WARN,\n                \"NOTE: debug verbosity (--verb %d) is enabled but this build lacks debug support.\",\n                options->verbosity);\n        }\n#endif\n    }\n    else if (streq(p[0], \"mute\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_MESSAGES);\n        options->mute = positive_atoi(p[1], msglevel);\n    }\n    else if (streq(p[0], \"errors-to-stderr\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_MESSAGES);\n        errors_to_stderr();\n    }\n    else if (streq(p[0], \"status\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->status_file = p[1];\n        if (p[2])\n        {\n            options->status_file_update_freq = positive_atoi(p[2], msglevel);\n        }\n    }\n    else if (streq(p[0], \"status-version\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (!atoi_constrained(p[1], &options->status_file_version, p[0], 1, 3, msglevel))\n        {\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"remap-usr1\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (streq(p[1], \"SIGHUP\"))\n        {\n            options->remap_sigusr1 = SIGHUP;\n        }\n        else if (streq(p[1], \"SIGTERM\"))\n        {\n            options->remap_sigusr1 = SIGTERM;\n        }\n        else\n        {\n            msg(msglevel, \"--remap-usr1 parm must be 'SIGHUP' or 'SIGTERM'\");\n            goto err;\n        }\n    }\n    else if ((streq(p[0], \"link-mtu\") || streq(p[0], \"udp-mtu\")) && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_MTU | OPT_P_CONNECTION);\n        options->ce.link_mtu = positive_atoi(p[1], msglevel);\n        options->ce.link_mtu_defined = true;\n    }\n    else if (streq(p[0], \"tun-mtu\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_PUSH_MTU | OPT_P_CONNECTION);\n        options->ce.tun_mtu = positive_atoi(p[1], msglevel);\n        options->ce.tun_mtu_defined = true;\n        if (p[2])\n        {\n            options->ce.occ_mtu = positive_atoi(p[2], msglevel);\n        }\n        else\n        {\n            options->ce.occ_mtu = 0;\n        }\n    }\n    else if (streq(p[0], \"tun-mtu-max\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_MTU | OPT_P_CONNECTION);\n        atoi_constrained(p[1], &options->ce.tun_mtu_max, p[0], TUN_MTU_MAX_MIN, 65536, msglevel);\n    }\n    else if (streq(p[0], \"tun-mtu-extra\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_MTU | OPT_P_CONNECTION);\n        if (atoi_constrained(p[1], &options->ce.tun_mtu_extra, p[0], 0, 65536, msglevel))\n        {\n            options->ce.tun_mtu_extra_defined = true;\n        }\n    }\n    else if (streq(p[0], \"max-packet-size\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_MTU | OPT_P_CONNECTION);\n        int maxmtu = positive_atoi(p[1], msglevel);\n        options->ce.tls_mtu = constrain_int(maxmtu, TLS_CHANNEL_MTU_MIN, TLS_CHANNEL_BUF_SIZE);\n\n        if (maxmtu < TLS_CHANNEL_MTU_MIN || maxmtu > TLS_CHANNEL_BUF_SIZE)\n        {\n            msg(M_WARN,\n                \"Note: max-packet-size value outside of allowed \"\n                \"control channel packet size (%d to %d), will use %d \"\n                \"instead.\",\n                TLS_CHANNEL_MTU_MIN, TLS_CHANNEL_BUF_SIZE, options->ce.tls_mtu);\n        }\n\n        /* also set mssfix maxmtu mtu */\n        options->ce.mssfix = maxmtu;\n        options->ce.mssfix_default = false;\n        options->ce.mssfix_encap = true;\n    }\n#ifdef ENABLE_FRAGMENT\n    else if (streq(p[0], \"mtu-dynamic\"))\n    {\n        VERIFY_PERMISSION(OPT_P_MTU | OPT_P_CONNECTION);\n        msg(msglevel, \"--mtu-dynamic has been replaced by --fragment\");\n        goto err;\n    }\n    else if (streq(p[0], \"fragment\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_MTU | OPT_P_CONNECTION);\n        if (!atoi_constrained(p[1], &options->ce.fragment, p[0], 68, INT_MAX, msglevel))\n        {\n            goto err;\n        }\n\n        if (p[2] && streq(p[2], \"mtu\"))\n        {\n            options->ce.fragment_encap = true;\n        }\n        else if (p[2])\n        {\n            msg(msglevel, \"Unknown parameter to --fragment: %s\", p[2]);\n        }\n    }\n#endif /* ifdef ENABLE_FRAGMENT */\n    else if (streq(p[0], \"mtu-disc\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_MTU | OPT_P_CONNECTION);\n        options->ce.mtu_discover_type = translate_mtu_discover_type_name(p[1]);\n    }\n    else if (streq(p[0], \"mtu-test\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->mtu_test = true;\n    }\n    else if (streq(p[0], \"nice\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_NICE);\n        options->nice = atoi_warn(p[1], msglevel);\n    }\n    else if (streq(p[0], \"rcvbuf\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_SOCKBUF);\n        options->rcvbuf = positive_atoi(p[1], msglevel);\n    }\n    else if (streq(p[0], \"sndbuf\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_SOCKBUF);\n        options->sndbuf = positive_atoi(p[1], msglevel);\n    }\n    else if (streq(p[0], \"mark\") && p[1] && !p[2])\n    {\n#if defined(TARGET_LINUX)\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->mark = atoi_warn(p[1], msglevel);\n#endif\n    }\n    else if (streq(p[0], \"socket-flags\"))\n    {\n        int j;\n        VERIFY_PERMISSION(OPT_P_SOCKFLAGS);\n        for (j = 1; j < MAX_PARMS && p[j]; ++j)\n        {\n            if (streq(p[j], \"TCP_NODELAY\"))\n            {\n                options->sockflags |= SF_TCP_NODELAY;\n            }\n            else\n            {\n                msg(msglevel, \"unknown socket flag: %s\", p[j]);\n            }\n        }\n    }\n#ifdef TARGET_LINUX\n    else if (streq(p[0], \"bind-dev\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SOCKFLAGS);\n        options->bind_dev = p[1];\n    }\n#endif\n    else if (streq(p[0], \"txqueuelen\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n#ifdef TARGET_LINUX\n        options->tuntap_options.txqueuelen = positive_atoi(p[1], msglevel);\n#else\n        msg(msglevel, \"--txqueuelen not supported on this OS\");\n        goto err;\n#endif\n    }\n    else if (streq(p[0], \"shaper\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_SHAPER);\n        if (!atoi_constrained(p[1], &options->shaper, p[0], SHAPER_MIN, SHAPER_MAX, msglevel))\n        {\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"port\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n        options->ce.local_port = options->ce.remote_port = p[1];\n    }\n    else if (streq(p[0], \"lport\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n\n        /* only trigger bind() if port is not 0 (or --local is used) */\n        if (!streq(p[1], \"0\"))\n        {\n            options->ce.local_port_defined = true;\n        }\n        options->ce.local_port = p[1];\n    }\n    else if (streq(p[0], \"rport\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n        options->ce.remote_port = p[1];\n    }\n    else if (streq(p[0], \"bind\") && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n        options->ce.bind_defined = true;\n        if (p[1] && streq(p[1], \"ipv6only\"))\n        {\n            options->ce.bind_ipv6_only = true;\n        }\n    }\n    else if (streq(p[0], \"nobind\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n        options->ce.bind_local = false;\n    }\n    else if (streq(p[0], \"fast-io\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        msg(M_WARN, \"DEPRECATED OPTION: --fast-io option ignored.\");\n    }\n    else if (streq(p[0], \"inactive\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_TIMER);\n        options->inactivity_timeout = positive_atoi(p[1], msglevel);\n        if (p[2])\n        {\n            positive_atoll(p[2], &options->inactivity_minimum_bytes, p[0], msglevel);\n            if (options->inactivity_minimum_bytes > INT_MAX)\n            {\n                msg(M_WARN,\n                    \"WARNING: '--inactive' with a 'bytes' value\"\n                    \" >2 Gbyte was silently ignored in older versions.  If \"\n                    \" your VPN exits unexpectedly with 'Inactivity timeout'\"\n                    \" in %d seconds, revisit this value.\",\n                    options->inactivity_timeout);\n            }\n        }\n    }\n    else if (streq(p[0], \"session-timeout\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_TIMER);\n        options->session_timeout = positive_atoi(p[1], msglevel);\n    }\n    else if (streq(p[0], \"proto\") && p[1] && !p[2])\n    {\n        int proto;\n        sa_family_t af;\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n        proto = ascii2proto(p[1]);\n        af = ascii2af(p[1]);\n        if (proto < 0)\n        {\n            msg(msglevel, \"Bad protocol: '%s'. Allowed protocols with --proto option: %s\", p[1],\n                proto2ascii_all(&gc));\n            goto err;\n        }\n        options->ce.proto = proto;\n        options->ce.af = af;\n    }\n    else if (streq(p[0], \"proto-force\") && p[1] && !p[2])\n    {\n        int proto_force;\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        proto_force = ascii2proto(p[1]);\n        if (proto_force < 0)\n        {\n            msg(msglevel, \"Bad --proto-force protocol: '%s'\", p[1]);\n            goto err;\n        }\n        options->proto_force = proto_force;\n    }\n    else if (streq(p[0], \"http-proxy\") && p[1] && !p[5])\n    {\n        struct http_proxy_options *ho;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n\n        {\n            if (!p[2])\n            {\n                msg(msglevel, \"http-proxy port number not defined\");\n                goto err;\n            }\n\n            ho = init_http_proxy_options_once(&options->ce.http_proxy_options, &options->gc);\n\n            ho->server = p[1];\n            ho->port = p[2];\n        }\n\n        if (p[3])\n        {\n            /* auto -- try to figure out proxy addr, port, and type automatically */\n            /* auto-nct -- disable proxy auth cleartext protocols (i.e. basic auth) */\n            if (streq(p[3], \"auto\"))\n            {\n                ho->auth_retry = PAR_ALL;\n            }\n            else if (streq(p[3], \"auto-nct\"))\n            {\n                ho->auth_retry = PAR_NCT;\n            }\n            else\n            {\n                ho->auth_method_string = \"basic\";\n                ho->auth_file = p[3];\n\n                if (p[4])\n                {\n                    ho->auth_method_string = p[4];\n                }\n            }\n        }\n        else\n        {\n            ho->auth_method_string = \"none\";\n        }\n    }\n    else if (streq(p[0], \"http-proxy-user-pass\") && p[1])\n    {\n        struct http_proxy_options *ho;\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);\n        ho = init_http_proxy_options_once(&options->ce.http_proxy_options, &options->gc);\n        ho->auth_file_up = p[1];\n        ho->inline_creds = is_inline;\n    }\n    else if (streq(p[0], \"http-proxy-retry\") || streq(p[0], \"socks-proxy-retry\") || streq(p[0], \"http-proxy-timeout\"))\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n        msg(M_WARN, \"DEPRECATED OPTION: %s option ignored.\", p[0]);\n    }\n    else if (streq(p[0], \"http-proxy-option\") && p[1] && !p[4])\n    {\n        struct http_proxy_options *ho;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n        ho = init_http_proxy_options_once(&options->ce.http_proxy_options, &options->gc);\n\n        if (streq(p[1], \"VERSION\") && p[2] && !p[3])\n        {\n            ho->http_version = p[2];\n        }\n        else if (streq(p[1], \"AGENT\") && p[2] && !p[3])\n        {\n            ho->user_agent = p[2];\n        }\n        else if ((streq(p[1], \"EXT1\") || streq(p[1], \"EXT2\") || streq(p[1], \"CUSTOM-HEADER\"))\n                 && p[2])\n        {\n            /* In the wild patched versions use both EXT1/2 and CUSTOM-HEADER\n             * with either two argument or one */\n\n            struct http_custom_header *custom_header = NULL;\n            int i;\n            /* Find the first free header */\n            for (i = 0; i < MAX_CUSTOM_HTTP_HEADER; i++)\n            {\n                if (!ho->custom_headers[i].name)\n                {\n                    custom_header = &ho->custom_headers[i];\n                    break;\n                }\n            }\n            if (!custom_header)\n            {\n                msg(msglevel, \"Cannot use more than %d http-proxy-option CUSTOM-HEADER : '%s'\",\n                    MAX_CUSTOM_HTTP_HEADER, p[1]);\n            }\n            else\n            {\n                /* We will save p[2] and p[3], the proxy code will detect if\n                 * p[3] is NULL */\n                custom_header->name = p[2];\n                custom_header->content = p[3];\n            }\n        }\n        else\n        {\n            msg(msglevel, \"Bad http-proxy-option or missing or extra parameter: '%s'\", p[1]);\n        }\n    }\n    else if (streq(p[0], \"socks-proxy\") && p[1] && !p[4])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n\n        if (p[2])\n        {\n            options->ce.socks_proxy_port = p[2];\n        }\n        else\n        {\n            options->ce.socks_proxy_port = \"1080\";\n        }\n        options->ce.socks_proxy_server = p[1];\n        options->ce.socks_proxy_authfile = p[3]; /* might be NULL */\n    }\n    else if (streq(p[0], \"keepalive\") && p[1] && p[2] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->keepalive_ping = atoi_warn(p[1], msglevel);\n        options->keepalive_timeout = atoi_warn(p[2], msglevel);\n    }\n    else if (streq(p[0], \"ping\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_TIMER);\n        options->ping_send_timeout = positive_atoi(p[1], msglevel);\n    }\n    else if (streq(p[0], \"ping-exit\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_TIMER);\n        options->ping_rec_timeout = positive_atoi(p[1], msglevel);\n        options->ping_rec_timeout_action = PING_EXIT;\n    }\n    else if (streq(p[0], \"ping-restart\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_TIMER);\n        options->ping_rec_timeout = positive_atoi(p[1], msglevel);\n        options->ping_rec_timeout_action = PING_RESTART;\n    }\n    else if (streq(p[0], \"ping-timer-rem\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_TIMER);\n        options->ping_timer_remote = true;\n    }\n    else if (streq(p[0], \"explicit-exit-notify\") && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION | OPT_P_EXPLICIT_NOTIFY);\n        if (p[1])\n        {\n            options->ce.explicit_exit_notification = positive_atoi(p[1], msglevel);\n        }\n        else\n        {\n            options->ce.explicit_exit_notification = 1;\n        }\n    }\n    else if (streq(p[0], \"persist-tun\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_PERSIST);\n        options->persist_tun = true;\n    }\n    else if (streq(p[0], \"persist-key\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_PERSIST);\n        msg(M_WARN, \"DEPRECATED OPTION: --persist-key option ignored. \"\n                    \"Keys are now always persisted across restarts. \");\n    }\n    else if (streq(p[0], \"persist-local-ip\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_PERSIST_IP);\n        options->persist_local_ip = true;\n    }\n    else if (streq(p[0], \"persist-remote-ip\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_PERSIST_IP);\n        options->persist_remote_ip = true;\n    }\n    else if (streq(p[0], \"client-nat\") && p[1] && p[2] && p[3] && p[4] && !p[5])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE);\n        cnol_check_alloc(options);\n        add_client_nat_to_option_list(options->client_nat, p[1], p[2], p[3], p[4], msglevel);\n    }\n    else if (streq(p[0], \"route-table\") && p[1] && !p[2])\n    {\n#ifndef ENABLE_SITNL\n        msg(M_WARN, \"NOTE: --route-table is supported only on Linux when SITNL is built-in\");\n#endif\n        VERIFY_PERMISSION(OPT_P_ROUTE_TABLE);\n        options->route_default_table_id = positive_atoi(p[1], msglevel);\n    }\n    else if (streq(p[0], \"route\") && p[1] && !p[5])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE);\n        if (!check_route_option(options, p, msglevel, pull_mode))\n        {\n            goto err;\n        }\n        add_route_to_option_list(options->routes, p[1], p[2], p[3], p[4],\n                                 options->route_default_table_id);\n    }\n    else if (streq(p[0], \"route-ipv6\") && p[1] && !p[4])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE);\n        if (!check_route6_option(options, p, msglevel, pull_mode))\n        {\n            goto err;\n        }\n        add_route_ipv6_to_option_list(options->routes_ipv6, p[1], p[2], p[3],\n                                      options->route_default_table_id);\n    }\n    else if (streq(p[0], \"max-routes\") && !p[2])\n    {\n        msg(M_WARN, \"DEPRECATED OPTION: --max-routes option ignored.\");\n    }\n    else if (streq(p[0], \"route-gateway\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE_EXTRAS);\n        if (streq(p[1], \"dhcp\"))\n        {\n            options->route_gateway_via_dhcp = true;\n        }\n        else\n        {\n            if (ip_or_dns_addr_safe(p[1], options->allow_pull_fqdn)\n                || is_special_addr(p[1])) /* FQDN -- may be DNS name */\n            {\n                options->route_default_gateway = p[1];\n            }\n            else\n            {\n                msg(msglevel, \"route-gateway parm '%s' must be a valid address\", p[1]);\n                goto err;\n            }\n        }\n    }\n    else if (streq(p[0], \"route-ipv6-gateway\") && p[1] && !p[2])\n    {\n        if (ipv6_addr_safe(p[1]))\n        {\n            options->route_ipv6_default_gateway = p[1];\n        }\n        else\n        {\n            msg(msglevel, \"route-ipv6-gateway parm '%s' must be a valid address\", p[1]);\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"route-metric\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE);\n        options->route_default_metric = positive_atoi(p[1], msglevel);\n    }\n    else if (streq(p[0], \"route-delay\") && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE_EXTRAS);\n        options->route_delay_defined = true;\n        if (p[1])\n        {\n            options->route_delay = positive_atoi(p[1], msglevel);\n            if (p[2])\n            {\n                options->route_delay_window = positive_atoi(p[2], msglevel);\n            }\n        }\n        else\n        {\n            options->route_delay = 0;\n        }\n    }\n    else if (streq(p[0], \"route-up\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT))\n        {\n            goto err;\n        }\n        set_user_script(options, &options->route_script, p[1], \"route-up\", false);\n    }\n    else if (streq(p[0], \"route-pre-down\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT))\n        {\n            goto err;\n        }\n        set_user_script(options, &options->route_predown_script, p[1], \"route-pre-down\", true);\n    }\n    else if (streq(p[0], \"route-noexec\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        options->route_noexec = true;\n    }\n    else if (streq(p[0], \"route-nopull\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->route_nopull = true;\n    }\n    else if (streq(p[0], \"pull-filter\") && p[1] && p[2] && !p[3])\n    {\n        struct pull_filter *f;\n        VERIFY_PERMISSION(OPT_P_GENERAL)\n        f = alloc_pull_filter(options);\n\n        if (strcmp(\"accept\", p[1]) == 0)\n        {\n            f->type = PUF_TYPE_ACCEPT;\n        }\n        else if (strcmp(\"ignore\", p[1]) == 0)\n        {\n            f->type = PUF_TYPE_IGNORE;\n        }\n        else if (strcmp(\"reject\", p[1]) == 0)\n        {\n            f->type = PUF_TYPE_REJECT;\n        }\n        else\n        {\n            msg(msglevel, \"Unknown --pull-filter type: %s\", p[1]);\n            goto err;\n        }\n        f->pattern = p[2];\n        f->size = strlen(p[2]);\n    }\n    else if (streq(p[0], \"allow-pull-fqdn\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->allow_pull_fqdn = true;\n    }\n    else if (streq(p[0], \"redirect-gateway\") || streq(p[0], \"redirect-private\"))\n    {\n        int j;\n        VERIFY_PERMISSION(OPT_P_ROUTE);\n        rol_check_alloc(options);\n\n        if (options->routes->flags & RG_ENABLE)\n        {\n            msg(M_WARN, \"WARNING: You have specified redirect-gateway and \"\n                        \"redirect-private at the same time (or the same option \"\n                        \"multiple times). This is not well supported and may lead to \"\n                        \"unexpected results\");\n        }\n\n        options->routes->flags |= RG_ENABLE;\n\n        if (streq(p[0], \"redirect-gateway\"))\n        {\n            options->routes->flags |= RG_REROUTE_GW;\n        }\n        for (j = 1; j < MAX_PARMS && p[j] != NULL; ++j)\n        {\n            if (streq(p[j], \"local\"))\n            {\n                options->routes->flags |= RG_LOCAL;\n            }\n            else if (streq(p[j], \"autolocal\"))\n            {\n                options->routes->flags |= RG_AUTO_LOCAL;\n            }\n            else if (streq(p[j], \"def1\"))\n            {\n                options->routes->flags |= RG_DEF1;\n            }\n            else if (streq(p[j], \"bypass-dhcp\"))\n            {\n                options->routes->flags |= RG_BYPASS_DHCP;\n            }\n            else if (streq(p[j], \"bypass-dns\"))\n            {\n                options->routes->flags |= RG_BYPASS_DNS;\n            }\n            else if (streq(p[j], \"block-local\"))\n            {\n                options->routes->flags |= RG_BLOCK_LOCAL;\n            }\n            else if (streq(p[j], \"ipv6\"))\n            {\n                rol6_check_alloc(options);\n                options->routes_ipv6->flags |= RG_REROUTE_GW;\n            }\n            else if (streq(p[j], \"!ipv4\"))\n            {\n                options->routes->flags &= ~(RG_REROUTE_GW | RG_ENABLE);\n            }\n            else\n            {\n                msg(msglevel, \"unknown --%s flag: %s\", p[0], p[j]);\n                goto err;\n            }\n        }\n        if (options->routes->flags & RG_REROUTE_GW)\n        {\n            setenv_int(es, \"route_redirect_gateway_ipv4\",\n                       options->routes->flags & RG_BLOCK_LOCAL ? 2 : 1);\n        }\n        if (options->routes_ipv6 && (options->routes_ipv6->flags & RG_REROUTE_GW))\n        {\n            setenv_int(es, \"route_redirect_gateway_ipv6\",\n                       options->routes->flags & RG_BLOCK_LOCAL ? 2 : 1);\n        }\n#ifdef _WIN32\n        /* we need this here to handle pushed --redirect-gateway */\n        remap_redirect_gateway_flags(options);\n#endif\n    }\n    else if (streq(p[0], \"block-ipv6\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE);\n        options->block_ipv6 = true;\n    }\n    else if (streq(p[0], \"remote-random-hostname\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->sockflags |= SF_HOST_RANDOMIZE;\n    }\n    else if (streq(p[0], \"setenv\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (streq(p[1], \"REMOTE_RANDOM_HOSTNAME\") && !p[2])\n        {\n            options->sockflags |= SF_HOST_RANDOMIZE;\n        }\n        else if (streq(p[1], \"GENERIC_CONFIG\"))\n        {\n            msg(msglevel, \"this is a generic configuration and cannot directly be used\");\n            goto err;\n        }\n        else if (streq(p[1], \"PUSH_PEER_INFO\") && !p[2])\n        {\n            options->push_peer_info = true;\n        }\n        else if (streq(p[1], \"SERVER_POLL_TIMEOUT\") && p[2])\n        {\n            options->ce.connect_timeout = positive_atoi(p[2], msglevel);\n        }\n        else\n        {\n            if (streq(p[1], \"FORWARD_COMPATIBLE\") && p[2] && streq(p[2], \"1\"))\n            {\n                options->forward_compatible = true;\n                msglevel_fc = msglevel_forward_compatible(options, msglevel);\n            }\n            setenv_str(es, p[1], p[2] ? p[2] : \"\");\n        }\n    }\n    else if (streq(p[0], \"compat-mode\") && p[1] && !p[3])\n    {\n        unsigned int major, minor, patch;\n        if (!(sscanf(p[1], \"%u.%u.%u\", &major, &minor, &patch) == 3))\n        {\n            msg(msglevel, \"cannot parse version number for --compat-mode: %s\", p[1]);\n            goto err;\n        }\n\n        options->backwards_compatible = major * 10000 + minor * 100 + patch;\n    }\n    else if (streq(p[0], \"setenv-safe\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_SETENV);\n        setenv_str_safe(es, p[1], p[2] ? p[2] : \"\");\n    }\n    else if (streq(p[0], \"script-security\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        int security;\n        if (atoi_constrained(p[1], &security, p[0], SSEC_NONE, SSEC_PW_ENV, msglevel))\n        {\n            script_security_set(security);\n        }\n    }\n    else if (streq(p[0], \"mssfix\") && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n        if (p[1])\n        {\n            int mssfix;\n            if (!atoi_constrained(p[1], &mssfix, p[0], 0, UINT16_MAX, msglevel))\n            {\n                goto err;\n            }\n            if (mssfix != 0 && mssfix < TLS_CHANNEL_MTU_MIN)\n            {\n                msg(msglevel, \"mssfix needs to be >= %d, not %d\", TLS_CHANNEL_MTU_MIN, mssfix);\n                goto err;\n            }\n\n            /* value specified, assume encapsulation is not\n             * included unless \"mtu\" follows later */\n            options->ce.mssfix = mssfix;\n            options->ce.mssfix_encap = false;\n            options->ce.mssfix_default = false;\n        }\n        else\n        {\n            /* Set MTU to default values */\n            options->ce.mssfix_default = true;\n            options->ce.mssfix_encap = true;\n            options->ce.mssfix_fixed = false;\n        }\n\n        if (p[2] && streq(p[2], \"mtu\"))\n        {\n            options->ce.mssfix_encap = true;\n        }\n        else if (p[2] && streq(p[2], \"fixed\"))\n        {\n            options->ce.mssfix_fixed = true;\n        }\n        else if (p[2])\n        {\n            msg(msglevel, \"Unknown parameter to --mssfix: %s\", p[2]);\n        }\n    }\n    else if (streq(p[0], \"disable-occ\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->occ = false;\n    }\n    else if (streq(p[0], \"server\") && p[1] && p[2] && !p[4])\n    {\n        const int lev = M_WARN;\n        bool error = false;\n        in_addr_t network, netmask;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        network = get_ip_addr(p[1], lev, &error);\n        netmask = get_ip_addr(p[2], lev, &error);\n        if (error || !network || !netmask)\n        {\n            msg(msglevel, \"error parsing --server parameters\");\n            goto err;\n        }\n        options->server_defined = true;\n        options->server_network = network;\n        options->server_netmask = netmask;\n\n        if (p[3])\n        {\n            if (streq(p[3], \"nopool\"))\n            {\n                options->server_flags |= SF_NOPOOL;\n            }\n            else\n            {\n                msg(msglevel, \"error parsing --server: %s is not a recognized flag\", p[3]);\n                goto err;\n            }\n        }\n    }\n    else if (streq(p[0], \"server-ipv6\") && p[1] && !p[2])\n    {\n        const int lev = M_WARN;\n        struct in6_addr network;\n        unsigned int netbits = 0;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (!get_ipv6_addr(p[1], &network, &netbits, lev))\n        {\n            msg(msglevel, \"error parsing --server-ipv6 parameter\");\n            goto err;\n        }\n        if (netbits < 64 || netbits > 124)\n        {\n            msg(msglevel, \"--server-ipv6 settings: network must be between /64 and /124 (not /%d)\",\n                netbits);\n\n            goto err;\n        }\n        options->server_ipv6_defined = true;\n        options->server_network_ipv6 = network;\n        options->server_netbits_ipv6 = netbits;\n    }\n    else if (streq(p[0], \"server-bridge\") && p[1] && p[2] && p[3] && p[4] && !p[5])\n    {\n        const int lev = M_WARN;\n        bool error = false;\n        in_addr_t ip, netmask, pool_start, pool_end;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        ip = get_ip_addr(p[1], lev, &error);\n        netmask = get_ip_addr(p[2], lev, &error);\n        pool_start = get_ip_addr(p[3], lev, &error);\n        pool_end = get_ip_addr(p[4], lev, &error);\n        if (error || !ip || !netmask || !pool_start || !pool_end)\n        {\n            msg(msglevel, \"error parsing --server-bridge parameters\");\n            goto err;\n        }\n        options->server_bridge_defined = true;\n        options->server_bridge_ip = ip;\n        options->server_bridge_netmask = netmask;\n        options->server_bridge_pool_start = pool_start;\n        options->server_bridge_pool_end = pool_end;\n    }\n    else if (streq(p[0], \"server-bridge\") && p[1] && streq(p[1], \"nogw\") && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->server_bridge_proxy_dhcp = true;\n        options->server_flags |= SF_NO_PUSH_ROUTE_GATEWAY;\n    }\n    else if (streq(p[0], \"server-bridge\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->server_bridge_proxy_dhcp = true;\n    }\n    else if (streq(p[0], \"push\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_PUSH);\n        push_options(options, &p[1], msglevel, &options->gc);\n    }\n    else if (streq(p[0], \"push-reset\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_INSTANCE);\n        push_reset(options);\n    }\n    else if (streq(p[0], \"push-remove\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_INSTANCE);\n        msg(D_PUSH, \"PUSH_REMOVE '%s'\", p[1]);\n        push_remove_option(options, p[1]);\n    }\n    else if (streq(p[0], \"ifconfig-pool\") && p[1] && p[2] && !p[4])\n    {\n        const int lev = M_WARN;\n        bool error = false;\n        in_addr_t start, end, netmask = 0;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        start = get_ip_addr(p[1], lev, &error);\n        end = get_ip_addr(p[2], lev, &error);\n        if (p[3])\n        {\n            netmask = get_ip_addr(p[3], lev, &error);\n        }\n        if (error)\n        {\n            msg(msglevel, \"error parsing --ifconfig-pool parameters\");\n            goto err;\n        }\n        if (!ifconfig_pool_verify_range(msglevel, start, end))\n        {\n            goto err;\n        }\n\n        options->ifconfig_pool_defined = true;\n        options->ifconfig_pool_start = start;\n        options->ifconfig_pool_end = end;\n        if (netmask)\n        {\n            options->ifconfig_pool_netmask = netmask;\n        }\n    }\n    else if (streq(p[0], \"ifconfig-pool-persist\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->ifconfig_pool_persist_filename = p[1];\n        if (p[2])\n        {\n            options->ifconfig_pool_persist_refresh_freq = positive_atoi(p[2], msglevel);\n        }\n    }\n    else if (streq(p[0], \"ifconfig-ipv6-pool\") && p[1] && !p[2])\n    {\n        const int lev = M_WARN;\n        struct in6_addr network;\n        unsigned int netbits = 0;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (!get_ipv6_addr(p[1], &network, &netbits, lev))\n        {\n            msg(msglevel, \"error parsing --ifconfig-ipv6-pool parameters\");\n            goto err;\n        }\n        if (netbits < 64 || netbits > 124)\n        {\n            msg(msglevel,\n                \"--ifconfig-ipv6-pool settings: network must be between /64 and /124 (not /%d)\",\n                netbits);\n            goto err;\n        }\n\n        options->ifconfig_ipv6_pool_defined = true;\n        options->ifconfig_ipv6_pool_base = network;\n        options->ifconfig_ipv6_pool_netbits = netbits;\n    }\n    else if (streq(p[0], \"hash-size\") && p[1] && p[2] && !p[3])\n    {\n        int real, virtual;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (!atoi_constrained(p[1], &real, \"hash-size real\", 1, INT_MAX, msglevel)\n            || !atoi_constrained(p[2], &virtual, \"hash-size virtual\", 1, INT_MAX, msglevel))\n        {\n            goto err;\n        }\n        options->real_hash_size = (uint32_t)real;\n        options->virtual_hash_size = (uint32_t)virtual;\n    }\n    else if (streq(p[0], \"connect-freq\") && p[1] && p[2] && !p[3])\n    {\n        int cf_max, cf_per;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (!atoi_constrained(p[1], &cf_max, \"connect-freq n\", 1, INT_MAX, msglevel)\n            || !atoi_constrained(p[2], &cf_per, \"connect-freq seconds\", 1, INT_MAX, msglevel))\n        {\n            goto err;\n        }\n        options->cf_max = cf_max;\n        options->cf_per = cf_per;\n    }\n    else if (streq(p[0], \"connect-freq-initial\") && p[1] && p[2] && !p[3])\n    {\n        int cf_max, cf_per;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (!atoi_constrained(p[1], &cf_max, \"connect-freq-initial n\", 1, INT_MAX, msglevel)\n            || !atoi_constrained(p[2], &cf_per, \"connect-freq-initial seconds\", 1, INT_MAX, msglevel))\n        {\n            goto err;\n        }\n        options->cf_initial_max = cf_max;\n        options->cf_initial_per = cf_per;\n    }\n    else if (streq(p[0], \"max-clients\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (!atoi_constrained(p[1], &options->max_clients, p[0], 1, MAX_PEER_ID, msglevel))\n        {\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"max-routes-per-client\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_INHERIT);\n        atoi_constrained(p[1], &options->max_routes_per_client, p[0], 1, INT_MAX, msglevel);\n    }\n    else if (streq(p[0], \"client-cert-not-required\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        msg(M_FATAL,\n            \"REMOVED OPTION: --client-cert-not-required, use '--verify-client-cert none' instead\");\n    }\n    else if (streq(p[0], \"verify-client-cert\") && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n\n        /* Reset any existing flags */\n        options->ssl_flags &= ~SSLF_CLIENT_CERT_OPTIONAL;\n        options->ssl_flags &= ~SSLF_CLIENT_CERT_NOT_REQUIRED;\n        if (p[1])\n        {\n            if (streq(p[1], \"none\"))\n            {\n                options->ssl_flags |= SSLF_CLIENT_CERT_NOT_REQUIRED;\n            }\n            else if (streq(p[1], \"optional\"))\n            {\n                options->ssl_flags |= SSLF_CLIENT_CERT_OPTIONAL;\n            }\n            else if (!streq(p[1], \"require\"))\n            {\n                msg(msglevel,\n                    \"parameter to --verify-client-cert must be 'none', 'optional' or 'require'\");\n                goto err;\n            }\n        }\n    }\n    else if (streq(p[0], \"username-as-common-name\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->ssl_flags |= SSLF_USERNAME_AS_COMMON_NAME;\n    }\n    else if (streq(p[0], \"auth-user-pass-optional\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->ssl_flags |= SSLF_AUTH_USER_PASS_OPTIONAL;\n    }\n    else if (streq(p[0], \"opt-verify\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        msg(M_INFO, \"DEPRECATED OPTION: --opt-verify was removed in OpenVPN 2.7.\");\n    }\n    else if (streq(p[0], \"auth-user-pass-verify\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        if (!no_more_than_n_args(msglevel, p, 3, NM_QUOTE_HINT))\n        {\n            goto err;\n        }\n        if (p[2])\n        {\n            if (streq(p[2], \"via-env\"))\n            {\n                options->auth_user_pass_verify_script_via_file = false;\n            }\n            else if (streq(p[2], \"via-file\"))\n            {\n                options->auth_user_pass_verify_script_via_file = true;\n            }\n            else\n            {\n                msg(msglevel,\n                    \"second parm to --auth-user-pass-verify must be 'via-env' or 'via-file'\");\n                goto err;\n            }\n        }\n        else\n        {\n            msg(msglevel,\n                \"--auth-user-pass-verify requires a second parameter ('via-env' or 'via-file')\");\n            goto err;\n        }\n        set_user_script(options, &options->auth_user_pass_verify_script, p[1],\n                        \"auth-user-pass-verify\", true);\n    }\n    else if (streq(p[0], \"auth-gen-token\"))\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->auth_token_generate = true;\n        options->auth_token_lifetime = p[1] ? positive_atoi(p[1], msglevel) : 0;\n\n        for (int i = 2; i < MAX_PARMS && p[i] != NULL; i++)\n        {\n            /* the second parameter can be the renewal time */\n            if (i == 2 && valid_integer(p[i], true))\n            {\n                options->auth_token_renewal = positive_atoi(p[i], msglevel);\n            }\n            else if (streq(p[i], \"external-auth\"))\n            {\n                options->auth_token_call_auth = true;\n            }\n            else\n            {\n                msg(msglevel, \"Invalid argument to auth-gen-token: %s (%d)\", p[i], i);\n            }\n        }\n    }\n    else if (streq(p[0], \"auth-gen-token-secret\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);\n        options->auth_token_secret_file = p[1];\n        options->auth_token_secret_file_inline = is_inline;\n    }\n    else if (streq(p[0], \"client-connect\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT))\n        {\n            goto err;\n        }\n        set_user_script(options, &options->client_connect_script, p[1], \"client-connect\", true);\n    }\n    else if (streq(p[0], \"client-crresponse\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT))\n        {\n            goto err;\n        }\n        set_user_script(options, &options->client_crresponse_script, p[1], \"client-crresponse\",\n                        true);\n    }\n    else if (streq(p[0], \"client-disconnect\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT))\n        {\n            goto err;\n        }\n        set_user_script(options, &options->client_disconnect_script, p[1], \"client-disconnect\",\n                        true);\n    }\n    else if (streq(p[0], \"learn-address\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT))\n        {\n            goto err;\n        }\n        set_user_script(options, &options->learn_address_script, p[1], \"learn-address\", true);\n    }\n    else if (streq(p[0], \"tmp-dir\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->tmp_dir = p[1];\n    }\n    else if (streq(p[0], \"client-config-dir\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->client_config_dir = p[1];\n    }\n    else if (streq(p[0], \"ccd-exclusive\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->ccd_exclusive = true;\n    }\n    else if (streq(p[0], \"bcast-buffers\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        atoi_constrained(p[1], &options->n_bcast_buf, p[0], 1, MBUF_SIZE_MAX, msglevel);\n    }\n    else if (streq(p[0], \"tcp-queue-limit\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        atoi_constrained(p[1], &options->tcp_queue_limit, p[0], 1, INT_MAX, msglevel);\n    }\n#if PORT_SHARE\n    else if (streq(p[0], \"port-share\") && p[1] && p[2] && !p[4])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->port_share_host = p[1];\n        options->port_share_port = p[2];\n        options->port_share_journal_dir = p[3];\n    }\n#endif\n    else if (streq(p[0], \"client-to-client\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->enable_c2c = true;\n    }\n    else if (streq(p[0], \"duplicate-cn\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->duplicate_cn = true;\n    }\n    else if (streq(p[0], \"iroute\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_INSTANCE);\n        option_iroute(options, p[1], p[2], msglevel);\n    }\n    else if (streq(p[0], \"iroute-ipv6\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_INSTANCE);\n        option_iroute_ipv6(options, p[1], msglevel);\n    }\n    else if (streq(p[0], \"ifconfig-push\") && p[1] && p[2] && !p[4])\n    {\n        in_addr_t local, remote_netmask;\n\n        VERIFY_PERMISSION(OPT_P_INSTANCE);\n        local = getaddr(GETADDR_HOST_ORDER | GETADDR_RESOLVE, p[1], 0, NULL, NULL);\n        remote_netmask = getaddr(GETADDR_HOST_ORDER | GETADDR_RESOLVE, p[2], 0, NULL, NULL);\n        if (local && remote_netmask)\n        {\n            options->push_ifconfig_defined = true;\n            options->push_ifconfig_local = local;\n            options->push_ifconfig_remote_netmask = remote_netmask;\n            if (p[3])\n            {\n                options->push_ifconfig_local_alias =\n                    getaddr(GETADDR_HOST_ORDER | GETADDR_RESOLVE, p[3], 0, NULL, NULL);\n            }\n        }\n        else\n        {\n            msg(msglevel, \"cannot parse --ifconfig-push addresses\");\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"ifconfig-push-constraint\") && p[1] && p[2] && !p[3])\n    {\n        in_addr_t network, netmask;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        network = getaddr(GETADDR_HOST_ORDER | GETADDR_RESOLVE, p[1], 0, NULL, NULL);\n        netmask = getaddr(GETADDR_HOST_ORDER, p[2], 0, NULL, NULL);\n        if (network && netmask)\n        {\n            options->push_ifconfig_constraint_defined = true;\n            options->push_ifconfig_constraint_network = network;\n            options->push_ifconfig_constraint_netmask = netmask;\n        }\n        else\n        {\n            msg(msglevel, \"cannot parse --ifconfig-push-constraint addresses\");\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"ifconfig-ipv6-push\") && p[1] && !p[3])\n    {\n        struct in6_addr local, remote;\n        unsigned int netbits;\n\n        VERIFY_PERMISSION(OPT_P_INSTANCE);\n\n        if (!get_ipv6_addr(p[1], &local, &netbits, msglevel))\n        {\n            msg(msglevel, \"cannot parse --ifconfig-ipv6-push addresses\");\n            goto err;\n        }\n\n        if (p[2])\n        {\n            if (!get_ipv6_addr(p[2], &remote, NULL, msglevel))\n            {\n                msg(msglevel, \"cannot parse --ifconfig-ipv6-push addresses\");\n                goto err;\n            }\n        }\n        else\n        {\n            if (!options->ifconfig_ipv6_local\n                || !get_ipv6_addr(options->ifconfig_ipv6_local, &remote, NULL, msglevel))\n            {\n                msg(msglevel,\n                    \"second argument to --ifconfig-ipv6-push missing and no global --ifconfig-ipv6 address set\");\n                goto err;\n            }\n        }\n\n        options->push_ifconfig_ipv6_defined = true;\n        options->push_ifconfig_ipv6_local = local;\n        options->push_ifconfig_ipv6_netbits = netbits;\n        options->push_ifconfig_ipv6_remote = remote;\n        options->push_ifconfig_ipv6_blocked = false;\n    }\n    else if (streq(p[0], \"disable\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_INSTANCE);\n        options->disable = true;\n    }\n    else if (streq(p[0], \"override-username\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_INSTANCE);\n        if (strlen(p[1]) > USER_PASS_LEN)\n        {\n            msg(msglevel,\n                \"override-username exceeds the maximum length of %d \"\n                \"characters\",\n                USER_PASS_LEN);\n\n            /* disable the connection since ignoring the request to\n             * set another username might cause serious problems */\n            options->disable = true;\n        }\n        else\n        {\n            options->override_username = p[1];\n        }\n    }\n    else if (streq(p[0], \"tcp-nodelay\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->server_flags |= SF_TCP_NODELAY_HELPER;\n    }\n    else if (streq(p[0], \"stale-routes-check\") && p[1] && !p[3])\n    {\n        int ageing_time, check_interval;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (!atoi_constrained(p[1], &ageing_time, \"stale-routes-check age\", 1, INT_MAX, msglevel))\n        {\n            goto err;\n        }\n\n        if (p[2])\n        {\n            if (!atoi_constrained(p[2], &check_interval,\n                                  \"stale-routes-check interval\", 1, INT_MAX, msglevel))\n            {\n                goto err;\n            }\n        }\n        else\n        {\n            check_interval = ageing_time;\n        }\n\n        options->stale_routes_ageing_time = ageing_time;\n        options->stale_routes_check_interval = check_interval;\n    }\n\n    else if (streq(p[0], \"client\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->client = true;\n    }\n    else if (streq(p[0], \"pull\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->pull = true;\n    }\n    else if (streq(p[0], \"push-continuation\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_PULL_MODE);\n        atoi_constrained(p[1], &options->push_continuation, p[0], 0, 2, msglevel);\n    }\n    else if (streq(p[0], \"auth-user-pass\") && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);\n        if (p[1])\n        {\n            options->auth_user_pass_file = p[1];\n            options->auth_user_pass_file_inline = is_inline;\n        }\n        else\n        {\n            options->auth_user_pass_file = \"stdin\";\n        }\n    }\n    else if (streq(p[0], \"auth-retry\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        auth_retry_set(msglevel, p[1]);\n    }\n#ifdef ENABLE_MANAGEMENT\n    else if (streq(p[0], \"static-challenge\") && p[1] && p[2] && !p[4])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->sc_info.challenge_text = p[1];\n        if (atoi_warn(p[2], msglevel))\n        {\n            options->sc_info.flags |= SC_ECHO;\n        }\n        if (p[3] && streq(p[3], \"concat\"))\n        {\n            options->sc_info.flags |= SC_CONCAT;\n        }\n        else if (p[3] && !streq(p[3], \"scrv1\"))\n        {\n            msg(msglevel, \"--static-challenge: unknown format indicator '%s'\", p[3]);\n            goto err;\n        }\n    }\n#endif\n    else if (streq(p[0], \"msg-channel\") && p[1])\n    {\n#ifdef _WIN32\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        HANDLE process = GetCurrentProcess();\n        HANDLE handle = (HANDLE)((intptr_t)atoll(p[1]));\n        if (!DuplicateHandle(process, handle, process, &options->msg_channel, 0, FALSE,\n                             DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS))\n        {\n            msg(msglevel, \"could not duplicate service pipe handle\");\n            goto err;\n        }\n        options->route_method = ROUTE_METHOD_SERVICE;\n#else /* ifdef _WIN32 */\n        msg(msglevel, \"--msg-channel is only supported on Windows\");\n        goto err;\n#endif\n    }\n#ifdef _WIN32\n    else if (streq(p[0], \"win-sys\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (streq(p[1], \"env\"))\n        {\n            msg(M_INFO, \"NOTE: --win-sys env is default from OpenVPN 2.3.\t \"\n                        \"This entry will now be ignored.  \"\n                        \"Please remove this entry from your configuration file.\");\n        }\n        else\n        {\n            set_win_sys_path(p[1], es);\n        }\n    }\n    else if (streq(p[0], \"route-method\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE_EXTRAS);\n        if (streq(p[1], \"adaptive\"))\n        {\n            options->route_method = ROUTE_METHOD_ADAPTIVE;\n        }\n        else if (streq(p[1], \"ipapi\"))\n        {\n            options->route_method = ROUTE_METHOD_IPAPI;\n        }\n        else if (streq(p[1], \"exe\"))\n        {\n            options->route_method = ROUTE_METHOD_EXE;\n        }\n        else\n        {\n            msg(msglevel, \"--route method must be 'adaptive', 'ipapi', or 'exe'\");\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"ip-win32\") && p[1] && !p[4])\n    {\n        const int index = ascii2ipset(p[1]);\n        struct tuntap_options *to = &options->tuntap_options;\n\n        VERIFY_PERMISSION(OPT_P_DHCPDNS);\n\n        if (index < 0)\n        {\n            msg(msglevel, \"Bad --ip-win32 method: '%s'.  Allowed methods: %s\", p[1],\n                ipset2ascii_all(&gc));\n            goto err;\n        }\n\n        if (index == IPW32_SET_ADAPTIVE)\n        {\n            options->route_delay_window = IPW32_SET_ADAPTIVE_DELAY_WINDOW;\n        }\n\n        if (index == IPW32_SET_DHCP_MASQ)\n        {\n            if (p[2])\n            {\n                if (!streq(p[2], \"default\"))\n                {\n                    int offset;\n\n                    if (!atoi_constrained(p[2], &offset, \"ip-win32 offset\", -256, 256, msglevel))\n                    {\n                        goto err;\n                    }\n                    to->dhcp_masq_custom_offset = true;\n                    to->dhcp_masq_offset = offset;\n                }\n\n                if (p[3])\n                {\n                    if (!atoi_constrained(p[3], &to->dhcp_lease_time,\n                                          \"ip-win32 lease time\", 30, INT_MAX, msglevel))\n                    {\n                        goto err;\n                    }\n                }\n            }\n        }\n        to->ip_win32_type = index;\n        to->ip_win32_defined = true;\n    }\n#endif /* ifdef _WIN32 */\n    else if (streq(p[0], \"dns-updown\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT))\n        {\n            goto err;\n        }\n        struct dns_options *dns = &options->dns_options;\n        if (streq(p[1], \"disable\"))\n        {\n            dns->updown = NULL;\n            dns->updown_flags = DNS_UPDOWN_NO_FLAGS;\n        }\n        else if (streq(p[1], \"force\"))\n        {\n            /* force dns-updown run, even if a --up script is defined */\n            if (!dns_updown_user_set(dns))\n            {\n                dns->updown = DEFAULT_DNS_UPDOWN;\n                dns->updown_flags = DNS_UPDOWN_FORCED;\n            }\n        }\n        else\n        {\n            if (dns->updown && streq(dns->updown, DEFAULT_DNS_UPDOWN))\n            {\n                /* Unset the default command to prevent warnings */\n                dns->updown = NULL;\n            }\n            set_user_script(options, &dns->updown, p[1], p[0], false);\n            dns->updown_flags = DNS_UPDOWN_USER_SET;\n        }\n    }\n    else if (streq(p[0], \"dns\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_DHCPDNS);\n        if (!check_dns_option(options, p, msglevel, pull_mode))\n        {\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"dhcp-option\") && p[1])\n    {\n        struct dhcp_options *dhcp = &options->dns_options.from_dhcp;\n#if defined(_WIN32) || defined(TARGET_ANDROID)\n        struct tuntap_options *o = &options->tuntap_options;\n#endif\n        VERIFY_PERMISSION(OPT_P_DHCPDNS);\n\n        bool dhcp_optional = false;\n\n        if ((streq(p[1], \"DOMAIN\") || streq(p[1], \"ADAPTER_DOMAIN_SUFFIX\")) && p[2] && !p[3])\n        {\n            if (!validate_domain(p[2]))\n            {\n                msg(msglevel, \"--dhcp-option %s contains invalid characters\", p[1]);\n                goto err;\n            }\n\n            dhcp->domain = p[2];\n            dhcp_optional = true;\n        }\n        else if (streq(p[1], \"DOMAIN-SEARCH\") && p[2] && !p[3])\n        {\n            if (!validate_domain(p[2]))\n            {\n                msg(msglevel, \"--dhcp-option %s contains invalid characters\", p[1]);\n                goto err;\n            }\n\n            if (dhcp->domain_search_list_len < N_SEARCH_LIST_LEN)\n            {\n                dhcp->domain_search_list[dhcp->domain_search_list_len++] = p[2];\n            }\n            else\n            {\n                msg(msglevel, \"--dhcp-option %s: maximum of %d search entries can be specified\",\n                    p[1], N_SEARCH_LIST_LEN);\n            }\n            dhcp_optional = true;\n        }\n        else if ((streq(p[1], \"DNS\") || streq(p[1], \"DNS6\")) && p[2] && !p[3]\n                 && (!strstr(p[2], \":\") || ipv6_addr_safe(p[2])))\n        {\n            if (strstr(p[2], \":\"))\n            {\n                dhcp_option_dns6_parse(p[2], dhcp->dns6, &dhcp->dns6_len, msglevel);\n            }\n            else\n            {\n                dhcp_option_address_parse(\"DNS\", p[2], dhcp->dns, &dhcp->dns_len, msglevel);\n                dhcp_optional = true;\n            }\n        }\n#if defined(_WIN32) || defined(TARGET_ANDROID)\n        else if (streq(p[1], \"NBS\") && p[2] && !p[3])\n        {\n            o->netbios_scope = p[2];\n            o->dhcp_options |= DHCP_OPTIONS_DHCP_REQUIRED;\n        }\n        else if (streq(p[1], \"NBT\") && p[2] && !p[3])\n        {\n            int t = atoi_warn(p[2], msglevel);\n            if (!(t == 1 || t == 2 || t == 4 || t == 8))\n            {\n                msg(msglevel, \"--dhcp-option NBT: parameter (%d) must be 1, 2, 4, or 8\", t);\n                goto err;\n            }\n            o->netbios_node_type = (uint8_t)t;\n            o->dhcp_options |= DHCP_OPTIONS_DHCP_REQUIRED;\n        }\n        else if (streq(p[1], \"WINS\") && p[2] && !p[3])\n        {\n            dhcp_option_address_parse(\"WINS\", p[2], o->wins, &o->wins_len, msglevel);\n            o->dhcp_options |= DHCP_OPTIONS_DHCP_OPTIONAL;\n        }\n        else if (streq(p[1], \"NTP\") && p[2] && !p[3])\n        {\n            dhcp_option_address_parse(\"NTP\", p[2], o->ntp, &o->ntp_len, msglevel);\n            o->dhcp_options |= DHCP_OPTIONS_DHCP_REQUIRED;\n        }\n        else if (streq(p[1], \"NBDD\") && p[2] && !p[3])\n        {\n            dhcp_option_address_parse(\"NBDD\", p[2], o->nbdd, &o->nbdd_len, msglevel);\n            o->dhcp_options |= DHCP_OPTIONS_DHCP_REQUIRED;\n        }\n        else if (streq(p[1], \"DISABLE-NBT\") && !p[2])\n        {\n            o->disable_nbt = 1;\n            o->dhcp_options |= DHCP_OPTIONS_DHCP_REQUIRED;\n        }\n#if defined(TARGET_ANDROID)\n        else if (streq(p[1], \"PROXY_HTTP\") && p[3] && !p[4])\n        {\n            o->http_proxy_port = positive_atoi(p[3], msglevel);\n            o->http_proxy = p[2];\n        }\n#endif\n        else\n        {\n            msg(msglevel, \"--dhcp-option: unknown option type '%s' or missing or unknown parameter\",\n                p[1]);\n            goto err;\n        }\n#else  /* if defined(_WIN32) || defined(TARGET_ANDROID) */\n        setenv_foreign_option(options, p[1], p[2], es);\n#endif /* if defined(_WIN32) || defined(TARGET_ANDROID) */\n\n        if (dhcp_optional)\n        {\n#if defined(_WIN32) || defined(TARGET_ANDROID)\n            o->dhcp_options |= DHCP_OPTIONS_DHCP_OPTIONAL;\n#endif\n        }\n    }\n#ifdef _WIN32\n    else if (streq(p[0], \"show-adapters\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        show_tap_win_adapters(M_INFO | M_NOPREFIX, M_WARN | M_NOPREFIX);\n        openvpn_exit(OPENVPN_EXIT_STATUS_GOOD); /* exit point */\n    }\n    else if (streq(p[0], \"show-net\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        show_routes(M_INFO | M_NOPREFIX);\n        show_adapters(M_INFO | M_NOPREFIX);\n        openvpn_exit(OPENVPN_EXIT_STATUS_GOOD); /* exit point */\n    }\n    else if (streq(p[0], \"show-net-up\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_UP);\n        options->show_net_up = true;\n    }\n    else if (streq(p[0], \"tap-sleep\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_DHCPDNS);\n        if (!atoi_constrained(p[1], &options->tuntap_options.tap_sleep, p[0], 0, 255, msglevel))\n        {\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"dhcp-renew\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_DHCPDNS);\n        options->tuntap_options.dhcp_renew = true;\n    }\n    else if (streq(p[0], \"dhcp-pre-release\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_DHCPDNS);\n        options->tuntap_options.dhcp_pre_release = true;\n        options->tuntap_options.dhcp_renew = true;\n    }\n    else if (streq(p[0], \"dhcp-release\") && !p[1])\n    {\n        msg(M_WARN, \"Obsolete option --dhcp-release detected. This is now on by default\");\n    }\n    else if (streq(p[0], \"dhcp-internal\") && p[1] && !p[2]) /* standalone method for internal use */\n    {\n        unsigned int adapter_index;\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        set_debug_level(options->verbosity, SDL_CONSTRAIN);\n        adapter_index = atou(p[1]);\n        sleep(options->tuntap_options.tap_sleep);\n        if (options->tuntap_options.dhcp_pre_release)\n        {\n            dhcp_release_by_adapter_index(adapter_index);\n        }\n        if (options->tuntap_options.dhcp_renew)\n        {\n            dhcp_renew_by_adapter_index(adapter_index);\n        }\n        openvpn_exit(OPENVPN_EXIT_STATUS_GOOD); /* exit point */\n    }\n    else if (streq(p[0], \"register-dns\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_DHCPDNS);\n        options->tuntap_options.register_dns = true;\n    }\n    else if (streq(p[0], \"block-outside-dns\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_DHCPDNS);\n        options->block_outside_dns = true;\n    }\n    else if (streq(p[0], \"rdns-internal\") && !p[1])\n    /* standalone method for internal use\n     *\n     * (if --register-dns is set, openvpn needs to call itself in a\n     *  sub-process to execute the required functions in a non-blocking\n     *  way, and uses --rdns-internal to signal that to itself)\n     */\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        set_debug_level(options->verbosity, SDL_CONSTRAIN);\n        if (options->tuntap_options.register_dns)\n        {\n            ipconfig_register_dns(NULL);\n        }\n        openvpn_exit(OPENVPN_EXIT_STATUS_GOOD); /* exit point */\n    }\n    else if (streq(p[0], \"show-valid-subnets\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        show_valid_win32_tun_subnets();\n        openvpn_exit(OPENVPN_EXIT_STATUS_GOOD); /* exit point */\n    }\n    else if (streq(p[0], \"pause-exit\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        set_pause_exit_win32();\n    }\n    else if (streq(p[0], \"service\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->exit_event_name = p[1];\n        if (p[2])\n        {\n            options->exit_event_initial_state = (atoi_warn(p[2], msglevel) != 0);\n        }\n    }\n    else if (streq(p[0], \"allow-nonadmin\") && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        tap_allow_nonadmin_access(p[1]);\n        openvpn_exit(OPENVPN_EXIT_STATUS_GOOD); /* exit point */\n    }\n    else if (streq(p[0], \"user\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        msg(M_WARN, \"NOTE: --user option is not implemented on Windows\");\n    }\n    else if (streq(p[0], \"group\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        msg(M_WARN, \"NOTE: --group option is not implemented on Windows\");\n    }\n#else  /* ifdef _WIN32 */\n    else if (streq(p[0], \"user\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->username = p[1];\n    }\n    else if (streq(p[0], \"group\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->groupname = p[1];\n    }\n    else if (streq(p[0], \"dhcp-option\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_DHCPDNS);\n        setenv_foreign_option(options, p[1], p[2], es);\n    }\n    else if (streq(p[0], \"route-method\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_ROUTE_EXTRAS);\n        /* ignore when pushed to non-Windows OS */\n    }\n#endif /* ifdef _WIN32 */\n#if PASSTOS_CAPABILITY\n    else if (streq(p[0], \"passtos\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->passtos = true;\n    }\n#endif\n    else if (streq(p[0], \"allow-compression\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n\n        if (streq(p[1], \"no\"))\n        {\n            options->comp.flags = COMP_F_ALLOW_STUB_ONLY | COMP_F_ADVERTISE_STUBS_ONLY;\n            if (comp_non_stub_enabled(&options->comp))\n            {\n                msg(msglevel, \"'--allow-compression no' conflicts with \"\n                              \" enabling compression\");\n            }\n        }\n        else if (options->comp.flags & COMP_F_ALLOW_STUB_ONLY)\n        {\n            /* Also printed on a push to hint at configuration problems */\n            msg(msglevel,\n                \"Cannot set allow-compression to '%s' \"\n                \"after set to 'no'\",\n                p[1]);\n            goto err;\n        }\n        else if (streq(p[1], \"asym\"))\n        {\n            options->comp.flags |= COMP_F_ALLOW_ASYM;\n        }\n        else if (streq(p[1], \"yes\"))\n        {\n            msg(M_WARN,\n                \"DEPRECATED OPTION: \\\"--allow-compression yes\\\" has been removed. \"\n                \"We will use \\\"asym\\\" mode instead. See the manual page for more information.\");\n\n            options->comp.flags |= COMP_F_ALLOW_ASYM;\n        }\n        else\n        {\n            msg(msglevel,\n                \"bad allow-compression option: %s -- \"\n                \"must be 'yes', 'no', or 'asym'\",\n                p[1]);\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"comp-lzo\") && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_COMP);\n\n        /* All lzo variants do not use swap */\n        options->comp.flags &= ~COMP_F_SWAP;\n        options->comp.alg = COMP_ALG_LZO;\n\n        if (p[1])\n        {\n            if (streq(p[1], \"no\"))\n            {\n                options->comp.alg = COMP_ALG_STUB;\n            }\n            /* There is no actual difference anymore between these variants.\n             * We never compress. On the server side we replace this with\n             * --compress migrate later anyway.\n             */\n            else if (!(streq(p[1], \"yes\") || streq(p[1], \"adaptive\")))\n            {\n                msg(msglevel, \"bad comp-lzo option: %s -- must be 'yes', 'no', or 'adaptive'\",\n                    p[1]);\n                goto err;\n            }\n        }\n        show_compression_warning(&options->comp);\n    }\n    else if (streq(p[0], \"comp-noadapt\") && !p[1])\n    {\n        /* NO-OP since we never compress anymore */\n    }\n    else if (streq(p[0], \"compress\") && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_COMP);\n        const char *alg = \"stub\";\n        if (p[1])\n        {\n            alg = p[1];\n        }\n\n        if (streq(alg, \"stub\"))\n        {\n            options->comp.alg = COMP_ALG_STUB;\n            options->comp.flags |= (COMP_F_SWAP | COMP_F_ADVERTISE_STUBS_ONLY);\n        }\n        else if (streq(alg, \"stub-v2\"))\n        {\n            options->comp.alg = COMP_ALGV2_UNCOMPRESSED;\n            options->comp.flags |= COMP_F_ADVERTISE_STUBS_ONLY;\n        }\n        else if (streq(alg, \"migrate\"))\n        {\n            options->comp.alg = COMP_ALG_UNDEF;\n            options->comp.flags = COMP_F_MIGRATE;\n        }\n        else if (streq(alg, \"lzo\"))\n        {\n            options->comp.alg = COMP_ALG_LZO;\n            options->comp.flags &= ~COMP_F_SWAP;\n        }\n        else if (streq(alg, \"lz4\"))\n        {\n            options->comp.alg = COMP_ALG_LZ4;\n            options->comp.flags |= COMP_F_SWAP;\n        }\n        else if (streq(alg, \"lz4-v2\"))\n        {\n            options->comp.alg = COMP_ALGV2_LZ4;\n        }\n        else\n        {\n            msg(msglevel, \"bad comp option: %s\", alg);\n            goto err;\n        }\n\n        show_compression_warning(&options->comp);\n    }\n    else if (streq(p[0], \"show-ciphers\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->show_ciphers = true;\n    }\n    else if (streq(p[0], \"show-digests\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->show_digests = true;\n    }\n    else if (streq(p[0], \"show-engines\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->show_engines = true;\n    }\n    else if (streq(p[0], \"key-direction\") && p[1] && !p[2])\n    {\n        int key_direction;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION);\n\n        key_direction = ascii2keydirection(msglevel, p[1]);\n        if (key_direction >= 0)\n        {\n            if (permission_mask & OPT_P_GENERAL)\n            {\n                options->key_direction = key_direction;\n            }\n            else if (permission_mask & OPT_P_CONNECTION)\n            {\n                options->ce.key_direction = key_direction;\n            }\n        }\n        else\n        {\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"secret\") && p[1] && !p[3])\n    {\n        msg(M_WARN, \"DEPRECATED OPTION: The option --secret is deprecated.\");\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);\n        options->shared_secret_file = p[1];\n        options->shared_secret_file_inline = is_inline;\n        if (!is_inline && p[2])\n        {\n            int key_direction;\n\n            key_direction = ascii2keydirection(msglevel, p[2]);\n            if (key_direction >= 0)\n            {\n                options->key_direction = key_direction;\n            }\n            else\n            {\n                goto err;\n            }\n        }\n    }\n    else if (streq(p[0], \"allow-deprecated-insecure-static-crypto\"))\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->allow_deprecated_insecure_static_crypto = true;\n    }\n    else if (streq(p[0], \"genkey\") && !p[4])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->genkey = true;\n        if (!p[1])\n        {\n            options->genkey_type = GENKEY_SECRET;\n        }\n        else\n        {\n            if (streq(p[1], \"secret\") || streq(p[1], \"tls-auth\") || streq(p[1], \"tls-crypt\"))\n            {\n                options->genkey_type = GENKEY_SECRET;\n            }\n            else if (streq(p[1], \"tls-crypt-v2-server\"))\n            {\n                options->genkey_type = GENKEY_TLS_CRYPTV2_SERVER;\n            }\n            else if (streq(p[1], \"tls-crypt-v2-client\"))\n            {\n                options->genkey_type = GENKEY_TLS_CRYPTV2_CLIENT;\n                if (p[3])\n                {\n                    options->genkey_extra_data = p[3];\n                }\n            }\n            else if (streq(p[1], \"auth-token\"))\n            {\n                options->genkey_type = GENKEY_AUTH_TOKEN;\n            }\n            else\n            {\n                msg(msglevel, \"unknown --genkey type: %s\", p[1]);\n            }\n        }\n        if (p[2])\n        {\n            options->genkey_filename = p[2];\n        }\n    }\n    else if (streq(p[0], \"auth\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->authname = p[1];\n    }\n    else if (streq(p[0], \"cipher\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_NCP | OPT_P_INSTANCE);\n        options->ciphername = p[1];\n    }\n    else if (streq(p[0], \"data-ciphers-fallback\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INSTANCE);\n        options->ciphername = p[1];\n        options->enable_ncp_fallback = true;\n    }\n    else if ((streq(p[0], \"data-ciphers\") || streq(p[0], \"ncp-ciphers\")) && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INSTANCE);\n        if (streq(p[0], \"ncp-ciphers\"))\n        {\n            msg(M_INFO, \"Note: Treating option '--ncp-ciphers' as \"\n                        \" '--data-ciphers' (renamed in OpenVPN 2.5).\");\n        }\n        options->ncp_ciphers = p[1];\n    }\n    else if (streq(p[0], \"key-derivation\") && p[1])\n    {\n        /* NCP only option that is pushed by the server to enable EKM,\n         * should not be used by normal users in config files*/\n        VERIFY_PERMISSION(OPT_P_NCP)\n        if (streq(p[1], \"tls-ekm\"))\n        {\n            options->imported_protocol_flags |= CO_USE_TLS_KEY_MATERIAL_EXPORT;\n        }\n        else\n        {\n            msg(msglevel, \"Unknown key-derivation method %s\", p[1]);\n        }\n    }\n    else if (streq(p[0], \"protocol-flags\") && p[1])\n    {\n        /* NCP only option that is pushed by the server to enable protocol\n         * features that are negotiated, should not be used by normal users\n         * in config files */\n        VERIFY_PERMISSION(OPT_P_NCP)\n        for (size_t j = 1; j < MAX_PARMS && p[j] != NULL; j++)\n        {\n            if (streq(p[j], \"cc-exit\"))\n            {\n                options->imported_protocol_flags |= CO_USE_CC_EXIT_NOTIFY;\n            }\n            else if (streq(p[j], \"tls-ekm\"))\n            {\n                options->imported_protocol_flags |= CO_USE_TLS_KEY_MATERIAL_EXPORT;\n            }\n            else if (streq(p[j], \"dyn-tls-crypt\"))\n            {\n                options->imported_protocol_flags |= CO_USE_DYNAMIC_TLS_CRYPT;\n            }\n            else if (streq(p[j], \"aead-epoch\"))\n            {\n                options->imported_protocol_flags |= CO_EPOCH_DATA_KEY_FORMAT;\n            }\n            else\n            {\n                msg(msglevel, \"Unknown protocol-flags flag: %s\", p[j]);\n            }\n        }\n    }\n    else if (streq(p[0], \"force-tls-key-material-export\"))\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->force_key_material_export = true;\n    }\n    else if (streq(p[0], \"prng\") && p[1] && !p[3])\n    {\n        msg(M_WARN, \"NOTICE: --prng option ignored (SSL library PRNG is used)\");\n    }\n    else if (streq(p[0], \"no-replay\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        /* always error out, this breaks the connection */\n        msg(M_FATAL, \"--no-replay was removed in OpenVPN 2.7. \"\n                     \"Update your configuration.\");\n    }\n    else if (streq(p[0], \"replay-window\") && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (p[1])\n        {\n            if (!atoi_constrained(p[1], &options->replay_window, \"replay-window windows size\",\n                                  MIN_SEQ_BACKTRACK, MAX_SEQ_BACKTRACK, msglevel))\n            {\n                goto err;\n            }\n\n            if (p[2])\n            {\n                if (!atoi_constrained(p[2], &options->replay_time, \"replay-window time window\",\n                                      MIN_TIME_BACKTRACK, MAX_TIME_BACKTRACK, msglevel))\n                {\n                    goto err;\n                }\n            }\n        }\n        else\n        {\n            msg(msglevel, \"replay-window option is missing window size parameter\");\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"mute-replay-warnings\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->mute_replay_warnings = true;\n    }\n    else if (streq(p[0], \"replay-persist\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->packet_id_file = p[1];\n    }\n    else if (streq(p[0], \"test-crypto\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->test_crypto = true;\n    }\n#ifndef ENABLE_CRYPTO_MBEDTLS\n    else if (streq(p[0], \"engine\") && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (p[1])\n        {\n            options->engine = p[1];\n        }\n        else\n        {\n            options->engine = \"auto\";\n        }\n    }\n#endif /* ENABLE_CRYPTO_MBEDTLS */\n    else if (streq(p[0], \"providers\") && p[1])\n    {\n        for (size_t j = 1; j < MAX_PARMS && p[j] != NULL; j++)\n        {\n            options->providers.names[j] = p[j];\n        }\n    }\n    else if (streq(p[0], \"show-tls\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->show_tls_ciphers = true;\n    }\n    else if ((streq(p[0], \"show-curves\") || streq(p[0], \"show-groups\")) && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->show_curves = true;\n    }\n    else if (streq(p[0], \"ecdh-curve\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        msg(M_WARN, \"Consider setting groups/curves preference with \"\n                    \"tls-groups instead of forcing a specific curve with \"\n                    \"ecdh-curve.\");\n        options->ecdh_curve = p[1];\n    }\n    else if (streq(p[0], \"tls-server\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->tls_server = true;\n    }\n    else if (streq(p[0], \"tls-client\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->tls_client = true;\n    }\n    else if (streq(p[0], \"ca\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);\n        options->ca_file = p[1];\n        options->ca_file_inline = is_inline;\n    }\n#ifndef ENABLE_CRYPTO_MBEDTLS\n    else if (streq(p[0], \"capath\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->ca_path = p[1];\n    }\n#endif /* ENABLE_CRYPTO_MBEDTLS */\n    else if (streq(p[0], \"dh\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);\n        options->dh_file = p[1];\n        options->dh_file_inline = is_inline;\n    }\n    else if (streq(p[0], \"cert\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);\n        options->cert_file = p[1];\n        options->cert_file_inline = is_inline;\n    }\n    else if (streq(p[0], \"extra-certs\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);\n        options->extra_certs_file = p[1];\n        options->extra_certs_file_inline = is_inline;\n    }\n    else if ((streq(p[0], \"verify-hash\") && p[1] && !p[3])\n             || (streq(p[0], \"peer-fingerprint\") && p[1] && !p[2]))\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);\n\n        int verify_hash_depth = 0;\n        if (streq(p[0], \"verify-hash\"))\n        {\n            msg(M_WARN, \"DEPRECATED OPTION: The option --verify-hash is deprecated. \"\n                        \"You should switch to the either use the level 1 certificate as \"\n                        \"--ca option, use --tls-verify or use --peer-fingerprint\");\n            /* verify level 1 cert, i.e. the CA that signed the leaf cert */\n            verify_hash_depth = 1;\n        }\n\n        options->verify_hash_algo = MD_SHA256;\n\n        int digest_len = SHA256_DIGEST_LENGTH;\n\n        if (options->verify_hash && options->verify_hash_depth != verify_hash_depth)\n        {\n            msg(msglevel,\n                \"ERROR: Setting %s not allowed. --verify-hash and\"\n                \" --peer-fingerprint are mutually exclusive\",\n                p[0]);\n            goto err;\n        }\n\n        if (streq(p[0], \"verify-hash\"))\n        {\n            if ((!p[2] && !is_inline) || (p[2] && streq(p[2], \"SHA1\")))\n            {\n                options->verify_hash_algo = MD_SHA1;\n                digest_len = SHA_DIGEST_LENGTH;\n            }\n            else if (p[2] && !streq(p[2], \"SHA256\"))\n            {\n                msg(msglevel,\n                    \"invalid or unsupported hashing algorithm: %s \"\n                    \"(only SHA1 and SHA256 are supported)\",\n                    p[2]);\n                goto err;\n            }\n        }\n\n        struct verify_hash_list *newlist;\n        newlist = parse_hash_fingerprint_multiline(p[1], digest_len, msglevel, &options->gc);\n\n        /* Append the new list to the end of our current list */\n        if (!options->verify_hash)\n        {\n            options->verify_hash = newlist;\n            options->verify_hash_depth = verify_hash_depth;\n        }\n        else\n        {\n            /* since both the old and new list can have multiple entries\n             * we need to go to the end of one of them to concatenate them  */\n            struct verify_hash_list *listend = options->verify_hash;\n            while (listend->next)\n            {\n                listend = listend->next;\n            }\n            listend->next = newlist;\n        }\n    }\n#if defined(ENABLE_CRYPTOAPI) && defined(HAVE_XKEY_PROVIDER)\n    else if (streq(p[0], \"cryptoapicert\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->cryptoapi_cert = p[1];\n    }\n#endif\n    else if (streq(p[0], \"key\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);\n        options->priv_key_file = p[1];\n        options->priv_key_file_inline = is_inline;\n    }\n    else if (streq(p[0], \"tls-version-min\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        int ver = tls_version_parse(p[1], p[2]);\n        if (ver == TLS_VER_BAD)\n        {\n            msg(msglevel, \"unknown tls-version-min parameter: %s\", p[1]);\n            goto err;\n        }\n\n#ifdef ENABLE_CRYPTO_MBEDTLS\n        if (ver < TLS_VER_1_2)\n        {\n            msg(M_WARN, \"--tls-version-min %s is not supported by mbedtls, using 1.2\", p[1]);\n            ver = TLS_VER_1_2;\n        }\n#endif\n\n        options->ssl_flags &= ~(SSLF_TLS_VERSION_MIN_MASK << SSLF_TLS_VERSION_MIN_SHIFT);\n        options->ssl_flags |= ((unsigned int)ver << SSLF_TLS_VERSION_MIN_SHIFT);\n    }\n    else if (streq(p[0], \"tls-version-max\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        int ver = tls_version_parse(p[1], NULL);\n        if (ver == TLS_VER_BAD)\n        {\n            msg(msglevel, \"unknown tls-version-max parameter: %s\", p[1]);\n            goto err;\n        }\n        options->ssl_flags &= ~(SSLF_TLS_VERSION_MAX_MASK << SSLF_TLS_VERSION_MAX_SHIFT);\n        options->ssl_flags |= ((unsigned int)ver << SSLF_TLS_VERSION_MAX_SHIFT);\n    }\n#ifndef ENABLE_CRYPTO_MBEDTLS\n    else if (streq(p[0], \"pkcs12\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);\n        options->pkcs12_file = p[1];\n        options->pkcs12_file_inline = is_inline;\n    }\n#endif /* ENABLE_CRYPTO_MBEDTLS */\n    else if (streq(p[0], \"askpass\") && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (p[1])\n        {\n            options->key_pass_file = p[1];\n        }\n        else\n        {\n            options->key_pass_file = \"stdin\";\n        }\n    }\n    else if (streq(p[0], \"auth-nocache\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        ssl_set_auth_nocache();\n    }\n    else if (streq(p[0], \"auth-token\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_ECHO);\n        ssl_set_auth_token(p[1]);\n#ifdef ENABLE_MANAGEMENT\n        if (management)\n        {\n            management_auth_token(management, p[1]);\n        }\n#endif\n    }\n    else if (streq(p[0], \"auth-token-user\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_ECHO);\n        ssl_set_auth_token_user(p[1]);\n    }\n    else if (streq(p[0], \"single-session\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->single_session = true;\n    }\n    else if (streq(p[0], \"push-peer-info\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->push_peer_info = true;\n    }\n    else if (streq(p[0], \"tls-exit\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->tls_exit = true;\n    }\n    else if (streq(p[0], \"tls-cipher\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->cipher_list = p[1];\n    }\n    else if (streq(p[0], \"tls-cert-profile\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->tls_cert_profile = p[1];\n    }\n    else if (streq(p[0], \"tls-ciphersuites\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->cipher_list_tls13 = p[1];\n    }\n    else if (streq(p[0], \"tls-groups\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->tls_groups = p[1];\n    }\n    else if (streq(p[0], \"crl-verify\") && p[1] && ((p[2] && streq(p[2], \"dir\")) || !p[2]))\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);\n        if (p[2] && streq(p[2], \"dir\"))\n        {\n            options->ssl_flags |= SSLF_CRL_VERIFY_DIR;\n        }\n        options->crl_file = p[1];\n        options->crl_file_inline = is_inline;\n    }\n    else if (streq(p[0], \"tls-verify\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT))\n        {\n            goto err;\n        }\n        set_user_script(options, &options->tls_verify,\n                        string_substitute(p[1], ',', ' ', &options->gc), \"tls-verify\", true);\n    }\n    else if (streq(p[0], \"tls-export-cert\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_SCRIPT);\n        options->tls_export_peer_cert_dir = p[1];\n    }\n    else if (streq(p[0], \"compat-names\"))\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        msg(msglevel, \"--compat-names was removed in OpenVPN 2.5. \"\n                      \"Update your configuration.\");\n        goto err;\n    }\n    else if (streq(p[0], \"no-name-remapping\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        msg(msglevel, \"--no-name-remapping was removed in OpenVPN 2.5. \"\n                      \"Update your configuration.\");\n        goto err;\n    }\n    else if (streq(p[0], \"verify-x509-name\") && p[1] && strlen(p[1]) && !p[3])\n    {\n        int type = VERIFY_X509_SUBJECT_DN;\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (p[2])\n        {\n            if (streq(p[2], \"subject\"))\n            {\n                type = VERIFY_X509_SUBJECT_DN;\n            }\n            else if (streq(p[2], \"name\"))\n            {\n                type = VERIFY_X509_SUBJECT_RDN;\n            }\n            else if (streq(p[2], \"name-prefix\"))\n            {\n                type = VERIFY_X509_SUBJECT_RDN_PREFIX;\n            }\n            else\n            {\n                msg(msglevel, \"unknown X.509 name type: %s\", p[2]);\n                goto err;\n            }\n        }\n        options->verify_x509_type = type;\n        options->verify_x509_name = p[1];\n    }\n    else if (streq(p[0], \"ns-cert-type\") && p[1] && !p[2])\n    {\n#ifdef ENABLE_CRYPTO_MBEDTLS\n        msg(msglevel, \"--ns-cert-type is not available with mbedtls.\");\n        goto err;\n#else\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (streq(p[1], \"server\"))\n        {\n            options->ns_cert_type = NS_CERT_CHECK_SERVER;\n        }\n        else if (streq(p[1], \"client\"))\n        {\n            options->ns_cert_type = NS_CERT_CHECK_CLIENT;\n        }\n        else\n        {\n            msg(msglevel, \"--ns-cert-type must be 'client' or 'server'\");\n            goto err;\n        }\n#endif /* ENABLE_CRYPTO_MBEDTLS */\n    }\n    else if (streq(p[0], \"remote-cert-ku\"))\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n\n        size_t j;\n        for (j = 1; j < MAX_PARMS && p[j] != NULL; ++j)\n        {\n            sscanf(p[j], \"%x\", &(options->remote_cert_ku[j - 1]));\n        }\n        if (j == 1)\n        {\n            /* No specific KU required, but require KU to be present */\n            options->remote_cert_ku[0] = OPENVPN_KU_REQUIRED;\n        }\n    }\n    else if (streq(p[0], \"remote-cert-eku\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->remote_cert_eku = p[1];\n    }\n    else if (streq(p[0], \"remote-cert-tls\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n\n        if (streq(p[1], \"server\"))\n        {\n            options->remote_cert_ku[0] = OPENVPN_KU_REQUIRED;\n            options->remote_cert_eku = \"TLS Web Server Authentication\";\n        }\n        else if (streq(p[1], \"client\"))\n        {\n            options->remote_cert_ku[0] = OPENVPN_KU_REQUIRED;\n            options->remote_cert_eku = \"TLS Web Client Authentication\";\n        }\n        else\n        {\n            msg(msglevel, \"--remote-cert-tls must be 'client' or 'server'\");\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"tls-timeout\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_TLS_PARMS);\n        options->tls_timeout = positive_atoi(p[1], msglevel);\n    }\n    else if (streq(p[0], \"reneg-bytes\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_TLS_PARMS);\n        if (!positive_atoll(p[1], &options->renegotiate_bytes, p[0], msglevel))\n        {\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"reneg-pkts\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_TLS_PARMS);\n        if (!positive_atoll(p[1], &options->renegotiate_packets, p[0], msglevel))\n        {\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"reneg-sec\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_TLS_PARMS);\n        options->renegotiate_seconds = positive_atoi(p[1], msglevel);\n        if (p[2])\n        {\n            options->renegotiate_seconds_min = positive_atoi(p[2], msglevel);\n        }\n    }\n    else if (streq(p[0], \"hand-window\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_TLS_PARMS);\n        options->handshake_window = positive_atoi(p[1], msglevel);\n    }\n    else if (streq(p[0], \"tran-window\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_TLS_PARMS);\n        options->transition_window = positive_atoi(p[1], msglevel);\n    }\n    else if (streq(p[0], \"tls-auth\") && p[1] && !p[3])\n    {\n        int key_direction = -1;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION | OPT_P_INLINE);\n\n        if (permission_mask & OPT_P_GENERAL)\n        {\n            options->tls_auth_file = p[1];\n            options->tls_auth_file_inline = is_inline;\n\n            if (!is_inline && p[2])\n            {\n                key_direction = ascii2keydirection(msglevel, p[2]);\n                if (key_direction < 0)\n                {\n                    goto err;\n                }\n                options->key_direction = key_direction;\n            }\n        }\n        else if (permission_mask & OPT_P_CONNECTION)\n        {\n            options->ce.tls_auth_file = p[1];\n            options->ce.tls_auth_file_inline = is_inline;\n            options->ce.key_direction = KEY_DIRECTION_BIDIRECTIONAL;\n\n            if (!is_inline && p[2])\n            {\n                key_direction = ascii2keydirection(msglevel, p[2]);\n                if (key_direction < 0)\n                {\n                    goto err;\n                }\n                options->ce.key_direction = key_direction;\n            }\n        }\n    }\n    else if (streq(p[0], \"tls-crypt\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION | OPT_P_INLINE);\n        if (permission_mask & OPT_P_GENERAL)\n        {\n            options->tls_crypt_file = p[1];\n            options->tls_crypt_file_inline = is_inline;\n        }\n        else if (permission_mask & OPT_P_CONNECTION)\n        {\n            options->ce.tls_crypt_file = p[1];\n            options->ce.tls_crypt_file_inline = is_inline;\n        }\n    }\n    else if (streq(p[0], \"tls-crypt-v2\") && p[1] && !p[3])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_CONNECTION | OPT_P_INLINE);\n        if (permission_mask & OPT_P_GENERAL)\n        {\n            options->tls_crypt_v2_file = p[1];\n            options->tls_crypt_v2_file_inline = is_inline;\n        }\n        else if (permission_mask & OPT_P_CONNECTION)\n        {\n            options->ce.tls_crypt_v2_file = p[1];\n            options->ce.tls_crypt_v2_file_inline = is_inline;\n        }\n\n        if (p[2] && streq(p[2], \"force-cookie\"))\n        {\n            options->ce.tls_crypt_v2_force_cookie = true;\n        }\n        else if (p[2] && streq(p[2], \"allow-noncookie\"))\n        {\n            options->ce.tls_crypt_v2_force_cookie = false;\n        }\n        else if (p[2])\n        {\n            msg(msglevel, \"Unsupported tls-crypt-v2 argument: %s\", p[2]);\n        }\n    }\n    else if (streq(p[0], \"tls-crypt-v2-verify\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->tls_crypt_v2_verify_script = p[1];\n    }\n    else if (streq(p[0], \"tls-crypt-v2-max-age\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (!atoi_constrained(p[1], &options->tls_crypt_v2_max_age, \"tls-crypt-v2-max-age\", 1, INT_MAX, msglevel))\n        {\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"x509-track\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        x509_track_add(&options->x509_track, p[1], msglevel, &options->gc);\n    }\n    else if (streq(p[0], \"x509-username-field\") && p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        for (size_t j = 1; j < MAX_PARMS && p[j] != NULL; ++j)\n        {\n            char *s = p[j];\n\n            if (strncmp(\"ext:\", s, 4) == 0 && !x509_username_field_ext_supported(s + 4))\n            {\n                msg(msglevel, \"Unsupported x509-username-field extension: %s\", s);\n            }\n            options->x509_username_field[j - 1] = p[j];\n        }\n    }\n#ifdef ENABLE_PKCS11\n    else if (streq(p[0], \"show-pkcs11-ids\") && !p[3])\n    {\n        char *provider = p[1];\n        bool cert_private = (p[2] == NULL ? false : (atoi_warn(p[2], msglevel) != 0));\n\n#ifdef DEFAULT_PKCS11_MODULE\n        if (!provider)\n        {\n            provider = DEFAULT_PKCS11_MODULE;\n        }\n        else if (!p[2])\n        {\n            char *endp = NULL;\n            long i = strtol(provider, &endp, 10);\n\n            if (*endp == 0)\n            {\n                /* There was one argument, and it was purely numeric.\n                 * Interpret it as the cert_private argument */\n                provider = DEFAULT_PKCS11_MODULE;\n                cert_private = i;\n            }\n        }\n#else  /* ifdef DEFAULT_PKCS11_MODULE */\n        if (!provider)\n        {\n            msg(msglevel, \"--show-pkcs11-ids requires a provider parameter\");\n            goto err;\n        }\n#endif /* ifdef DEFAULT_PKCS11_MODULE */\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n\n        set_debug_level(options->verbosity, SDL_CONSTRAIN);\n        show_pkcs11_ids(provider, cert_private);\n        openvpn_exit(OPENVPN_EXIT_STATUS_GOOD); /* exit point */\n    }\n    else if (streq(p[0], \"pkcs11-providers\") && p[1])\n    {\n        int j;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n\n        for (j = 1; j < MAX_PARMS && p[j] != NULL; ++j)\n        {\n            options->pkcs11_providers[j - 1] = p[j];\n        }\n    }\n    else if (streq(p[0], \"pkcs11-protected-authentication\"))\n    {\n        int j;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n\n        for (j = 1; j < MAX_PARMS && p[j] != NULL; ++j)\n        {\n            options->pkcs11_protected_authentication[j - 1] =\n                atoi_warn(p[j], msglevel) != 0 ? 1 : 0;\n        }\n    }\n    else if (streq(p[0], \"pkcs11-private-mode\") && p[1])\n    {\n        int j;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n\n        for (j = 1; j < MAX_PARMS && p[j] != NULL; ++j)\n        {\n            sscanf(p[j], \"%x\", &(options->pkcs11_private_mode[j - 1]));\n        }\n    }\n    else if (streq(p[0], \"pkcs11-cert-private\"))\n    {\n        int j;\n\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n\n        for (j = 1; j < MAX_PARMS && p[j] != NULL; ++j)\n        {\n            options->pkcs11_cert_private[j - 1] = (bool)(atoi_warn(p[j], msglevel));\n        }\n    }\n    else if (streq(p[0], \"pkcs11-pin-cache\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->pkcs11_pin_cache_period = positive_atoi(p[1], msglevel);\n    }\n    else if (streq(p[0], \"pkcs11-id\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->pkcs11_id = p[1];\n    }\n    else if (streq(p[0], \"pkcs11-id-management\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->pkcs11_id_management = true;\n    }\n#endif /* ifdef ENABLE_PKCS11 */\n    else if (streq(p[0], \"rmtun\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->persist_config = true;\n        options->persist_mode = 0;\n    }\n    else if (streq(p[0], \"mktun\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->persist_config = true;\n        options->persist_mode = 1;\n    }\n    else if (streq(p[0], \"peer-id\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_PEER_ID);\n        options->use_peer_id = true;\n        options->peer_id = atoi_warn(p[1], msglevel);\n    }\n    else if (streq(p[0], \"keying-material-exporter\") && p[1] && p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n\n        if (strncmp(p[1], \"EXPORTER\", 8))\n        {\n            msg(msglevel, \"Keying material exporter label must begin with \"\n                          \"\\\"EXPORTER\\\"\");\n            goto err;\n        }\n        if (streq(p[1], EXPORT_KEY_DATA_LABEL))\n        {\n            msg(msglevel,\n                \"Keying material exporter label must not be '\" EXPORT_KEY_DATA_LABEL \"'.\");\n        }\n\n        if (!atoi_constrained(p[2], &options->keying_material_exporter_length,\n                              p[0], 16, 4095, msglevel))\n        {\n            goto err;\n        }\n\n        options->keying_material_exporter_label = p[1];\n    }\n    else if (streq(p[0], \"allow-recursive-routing\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->allow_recursive_routing = true;\n    }\n    else if (streq(p[0], \"vlan-tagging\") && !p[1])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        options->vlan_tagging = true;\n    }\n    else if (streq(p[0], \"vlan-accept\") && p[1] && !p[2])\n    {\n        VERIFY_PERMISSION(OPT_P_GENERAL);\n        if (streq(p[1], \"tagged\"))\n        {\n            options->vlan_accept = VLAN_ONLY_TAGGED;\n        }\n        else if (streq(p[1], \"untagged\"))\n        {\n            options->vlan_accept = VLAN_ONLY_UNTAGGED_OR_PRIORITY;\n        }\n        else if (streq(p[1], \"all\"))\n        {\n            options->vlan_accept = VLAN_ALL;\n        }\n        else\n        {\n            msg(msglevel, \"--vlan-accept must be 'tagged', 'untagged' or 'all'\");\n            goto err;\n        }\n    }\n    else if (streq(p[0], \"vlan-pvid\") && p[1] && !p[2])\n    {\n        int vlan_pvid;\n        VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INSTANCE);\n        if (!atoi_constrained(p[1], &vlan_pvid, p[0],\n                              OPENVPN_8021Q_MIN_VID, OPENVPN_8021Q_MAX_VID, msglevel))\n        {\n            goto err;\n        }\n        options->vlan_pvid = (uint16_t)vlan_pvid;\n    }\n    else\n    {\n        int i;\n        msglvl_t msglevel_unknown = msglevel_fc;\n        /* Check if an option is in --ignore-unknown-option and\n         * set warning level to non fatal */\n        for (i = 0; options->ignore_unknown_option && options->ignore_unknown_option[i]; i++)\n        {\n            if (streq(p[0], options->ignore_unknown_option[i]))\n            {\n                msglevel_unknown = M_WARN;\n                break;\n            }\n        }\n        if (file)\n        {\n            msg(msglevel_unknown,\n                \"Unrecognized option or missing or extra parameter(s) in %s:%d: %s (%s)\", file,\n                line, p[0], PACKAGE_VERSION);\n        }\n        else\n        {\n            msg(msglevel_unknown, \"Unrecognized option or missing or extra parameter(s): --%s (%s)\",\n                p[0], PACKAGE_VERSION);\n        }\n    }\nerr:\n    gc_free(&gc);\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nbool\nhas_udp_in_local_list(const struct options *options)\n{\n    if (options->ce.local_list)\n    {\n        for (int i = 0; i < options->ce.local_list->len; i++)\n        {\n            if (proto_is_dgram(options->ce.local_list->array[i]->proto))\n            {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/openvpn/options.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * 2004-01-28: Added Socks5 proxy support\n *   (Christof Meerwald, https://cmeerw.org)\n */\n\n#ifndef OPTIONS_H\n#define OPTIONS_H\n\n#include \"basic.h\"\n#include \"common.h\"\n#include \"mtu.h\"\n#include \"route.h\"\n#include \"tun.h\"\n#include \"socket_util.h\"\n#include \"plugin.h\"\n#include \"manage.h\"\n#include \"proxy.h\"\n#include \"comp.h\"\n#include \"pushlist.h\"\n#include \"clinat.h\"\n#include \"crypto_backend.h\"\n#include \"dns.h\"\n\n\n/*\n * Maximum number of parameters associated with an option,\n * including the option name itself.\n */\n#define MAX_PARMS 16\n\n/*\n * Max size of options line and parameter.\n */\n#define OPTION_PARM_SIZE 256\n#define OPTION_LINE_SIZE 256\n\nextern const char title_string[];\n\n/* certain options are saved before --pull modifications are applied */\nstruct options_pre_connect\n{\n    bool tuntap_options_defined;\n    struct tuntap_options tuntap_options;\n\n    const char *ifconfig_local;\n    const char *ifconfig_ipv6_local;\n\n    bool routes_defined;\n    struct route_option_list *routes;\n\n    bool routes_ipv6_defined;\n    struct route_ipv6_option_list *routes_ipv6;\n\n    const char *route_default_gateway;\n    const char *route_ipv6_default_gateway;\n\n    bool client_nat_defined;\n    struct client_nat_option_list *client_nat;\n\n    struct dns_options dns_options;\n\n    const char *ciphername;\n    const char *authname;\n\n    int ping_send_timeout;\n    int ping_rec_timeout;\n    int ping_rec_timeout_action;\n\n    int foreign_option_index;\n    struct compress_options comp;\n};\n\n#if !defined(ENABLE_CRYPTO_OPENSSL) && !defined(ENABLE_CRYPTO_MBEDTLS)\n#error \"At least one of OpenSSL or mbed TLS needs to be defined.\"\n#endif\n\nstruct local_entry\n{\n    const char *local;\n    const char *port;\n    int proto;\n};\n\nstruct connection_entry\n{\n    struct local_list *local_list;\n    int proto;\n    sa_family_t af;\n    const char *local_port;\n    bool local_port_defined;\n    const char *remote_port;\n    const char *remote;\n    bool remote_float;\n    bool bind_defined;\n    bool bind_ipv6_only;\n    bool bind_local;\n    int connect_retry_seconds;\n    int connect_retry_seconds_max;\n    int connect_timeout;\n    struct http_proxy_options *http_proxy_options;\n    const char *socks_proxy_server;\n    const char *socks_proxy_port;\n    const char *socks_proxy_authfile;\n\n    int tun_mtu;          /* MTU of tun device */\n    int occ_mtu;          /* if non-null, this is the MTU we announce to peers in OCC */\n    int tun_mtu_max;      /* maximum MTU that can be pushed */\n\n    bool tun_mtu_defined; /* true if user overriding parm with command line option */\n    int tun_mtu_extra;\n    bool tun_mtu_extra_defined;\n    int link_mtu;          /* MTU of device over which tunnel packets pass via TCP/UDP */\n    bool link_mtu_defined; /* true if user overriding parm with command line option */\n    int tls_mtu;           /* Maximum MTU for the control channel messages */\n\n    /* Advanced MTU negotiation and datagram fragmentation options */\n    int mtu_discover_type;          /* used if OS supports setting Path MTU discovery options on socket */\n\n    int fragment;                   /* internal fragmentation size */\n    bool fragment_encap;            /* true if --fragment had the \"mtu\" parameter to\n                                     * include overhead from IP and TCP/UDP encapsulation */\n    int mssfix;                     /* Upper bound on TCP MSS */\n    bool mssfix_default;            /* true if --mssfix should use the default parameters */\n    bool mssfix_encap;              /* true if --mssfix had the \"mtu\" parameter to include\n                                     * overhead from IP and TCP/UDP encapsulation */\n    bool mssfix_fixed;              /* use the mssfix value without any encapsulation adjustments */\n\n    int explicit_exit_notification; /* Explicitly tell peer when we are exiting via OCC_EXIT or\n                                       [RESTART] message */\n\n#define CE_DISABLED                (1u << 0)\n#define CE_MAN_QUERY_PROXY         (1u << 1)\n#define CE_MAN_QUERY_REMOTE_UNDEF  0\n#define CE_MAN_QUERY_REMOTE_QUERY  1\n#define CE_MAN_QUERY_REMOTE_ACCEPT 2\n#define CE_MAN_QUERY_REMOTE_MOD    3\n#define CE_MAN_QUERY_REMOTE_SKIP   4\n#define CE_MAN_QUERY_REMOTE_MASK   (0x07u)\n#define CE_MAN_QUERY_REMOTE_SHIFT  (2)\n    unsigned int flags;\n\n    /* Shared secret used for TLS control channel authentication */\n    const char *tls_auth_file;\n    bool tls_auth_file_inline;\n    int key_direction;\n\n    /* Shared secret used for TLS control channel authenticated encryption */\n    const char *tls_crypt_file;\n    bool tls_crypt_file_inline;\n\n    /* Client-specific secret or server key used for TLS control channel\n     * authenticated encryption v2 */\n    const char *tls_crypt_v2_file;\n    bool tls_crypt_v2_file_inline;\n\n    /* Allow only client that support resending the wrapped client key */\n    bool tls_crypt_v2_force_cookie;\n};\n\nstruct remote_entry\n{\n    const char *remote;\n    const char *remote_port;\n    int proto;\n    sa_family_t af;\n};\n\n#define CONNECTION_LIST_SIZE 64\n\nstruct local_list\n{\n    int capacity;\n    int len;\n    struct local_entry **array;\n};\n\nstruct connection_list\n{\n    int capacity;\n    int len;\n    int current;\n    struct connection_entry **array;\n};\n\nstruct remote_list\n{\n    int capacity;\n    int len;\n    struct remote_entry **array;\n};\n\nstruct provider_list\n{\n    /* Names of the providers */\n    const char *names[MAX_PARMS];\n    /* Pointers to the loaded providers to unload them */\n    provider_t *providers[MAX_PARMS];\n};\n\nenum vlan_acceptable_frames\n{\n    VLAN_ONLY_TAGGED,\n    VLAN_ONLY_UNTAGGED_OR_PRIORITY,\n    VLAN_ALL,\n};\n\nstruct remote_host_store\n{\n#define RH_HOST_LEN 80\n    char host[RH_HOST_LEN];\n#define RH_PORT_LEN 20\n    char port[RH_PORT_LEN];\n};\n\nenum genkey_type\n{\n    GENKEY_SECRET,\n    GENKEY_TLS_CRYPTV2_CLIENT,\n    GENKEY_TLS_CRYPTV2_SERVER,\n    GENKEY_AUTH_TOKEN\n};\n\nstruct verify_hash_list\n{\n    /* We support SHA256 and SHA1 fingerpint. In the case of using the\n     * deprecated SHA1, only the first 20 bytes of each list item are used */\n    uint8_t hash[SHA256_DIGEST_LENGTH];\n    struct verify_hash_list *next;\n};\n\n/* Command line options */\nstruct options\n{\n    struct gc_arena gc;\n\n    /* first config file */\n    const char *config;\n\n    /* major mode */\n#define MODE_POINT_TO_POINT 0\n#define MODE_SERVER         1\n    int mode;\n\n    /* enable forward compatibility for post-2.1 features */\n    bool forward_compatible;\n    /** What version we should try to be compatible with as major * 10000 +\n     * minor * 100 + patch, e.g. 2.4.7 => 20407 */\n    unsigned int backwards_compatible;\n\n    /* list of options that should be ignored even if unknown */\n    const char **ignore_unknown_option;\n\n    /* persist parms */\n    bool persist_config;\n    int persist_mode;\n\n    const char *key_pass_file;\n    bool show_ciphers;\n    bool show_digests;\n    bool show_engines;\n    bool show_tls_ciphers;\n    bool show_curves;\n    bool genkey;\n    enum genkey_type genkey_type;\n    const char *genkey_filename;\n    const char *genkey_extra_data;\n\n    /* Networking parms */\n    int connect_retry_max;\n    struct connection_entry ce;\n    struct connection_list *connection_list;\n\n    struct remote_list *remote_list;\n    /* Do not advance the connection or remote addr list */\n    bool no_advance;\n    /* Advance directly to the next remote, skipping remaining addresses of the\n     * current remote */\n    bool advance_next_remote;\n    /* Counts the number of unsuccessful connection attempts */\n    unsigned int unsuccessful_attempts;\n    /* count of connection entries to advance by when no_advance is not set */\n    int ce_advance_count;\n    /* the server can suggest a backoff time to the client, it\n     * will still be capped by the max timeout between connections\n     * (300s by default) */\n    int server_backoff_time;\n\n#if ENABLE_MANAGEMENT\n    struct http_proxy_options *http_proxy_override;\n#endif\n\n    struct remote_host_store *rh_store;\n\n    struct dns_options dns_options;\n\n    bool remote_random;\n    const char *ipchange;\n    const char *dev;\n    const char *dev_type;\n    const char *dev_node;\n    const char *lladdr;\n    int topology; /* one of the TOP_x values from proto.h */\n    const char *ifconfig_local;\n    const char *ifconfig_remote_netmask;\n    const char *ifconfig_ipv6_local;\n    int ifconfig_ipv6_netbits;\n    const char *ifconfig_ipv6_remote;\n    bool ifconfig_noexec;\n    bool ifconfig_nowarn;\n    int shaper;\n\n    int proto_force;\n\n    bool mtu_test;\n\n    bool mlock;\n\n    int keepalive_ping; /* a proxy for ping/ping-restart */\n    int keepalive_timeout;\n\n    int inactivity_timeout; /* --inactive */\n    int64_t inactivity_minimum_bytes;\n\n    int session_timeout;    /* Force-kill session after n seconds */\n\n    int ping_send_timeout;  /* Send a TCP/UDP ping to remote every n seconds */\n    int ping_rec_timeout;   /* Expect a TCP/UDP ping from remote at least once every n seconds */\n    bool ping_timer_remote; /* Run ping timer only if we have a remote address */\n\n#define PING_UNDEF   0\n#define PING_EXIT    1\n#define PING_RESTART 2\n    int ping_rec_timeout_action; /* What action to take on ping_rec_timeout (exit or restart)? */\n\n    bool persist_tun;            /* Don't close/reopen TUN/TAP dev on SIGUSR1 or PING_RESTART */\n    bool persist_local_ip;       /* Don't re-resolve local address on SIGUSR1 or PING_RESTART */\n    bool persist_remote_ip;      /* Don't re-resolve remote address on SIGUSR1 or PING_RESTART */\n\n#if PASSTOS_CAPABILITY\n    bool passtos;\n#endif\n\n    int resolve_retry_seconds; /* If hostname resolve fails, retry for n seconds */\n    bool resolve_in_advance;\n    const char *ip_remote_hint;\n\n    struct tuntap_options tuntap_options;\n    /* DCO is disabled and should not be used as backend driver for the\n     * tun/tap device */\n    bool disable_dco;\n\n    /* Misc parms */\n    const char *username;\n    const char *groupname;\n    const char *chroot_dir;\n    const char *cd_dir;\n#ifdef ENABLE_SELINUX\n    char *selinux_context;\n#endif\n    const char *writepid;\n    const char *up_script;\n    const char *down_script;\n    bool user_script_used;\n    bool down_pre;\n    bool up_delay;\n    bool up_restart;\n    bool daemon;\n\n    int remap_sigusr1;\n\n    bool log;\n    bool suppress_timestamps;\n    bool machine_readable_output;\n    int nice;\n    int verbosity;\n    int mute;\n\n#ifdef ENABLE_DEBUG\n    int gremlin;\n#endif\n\n    const char *status_file;\n    int status_file_version;\n    int status_file_update_freq;\n\n    struct compress_options comp;\n\n    /* buffer sizes */\n    int rcvbuf;\n    int sndbuf;\n\n    /* mark value */\n    int mark;\n    char *bind_dev;\n\n    /* socket flags */\n    unsigned int sockflags;\n\n    /* route management */\n    const char *route_script;\n    const char *route_predown_script;\n    const char *route_default_gateway;\n    const char *route_ipv6_default_gateway;\n    int route_default_table_id;\n    int route_default_metric;\n    bool route_noexec;\n    int route_delay;\n    int route_delay_window;\n    bool route_delay_defined;\n    struct route_option_list *routes;\n    struct route_ipv6_option_list *routes_ipv6; /* IPv6 */\n    bool block_ipv6;\n    bool route_nopull;\n    bool route_gateway_via_dhcp;\n    bool allow_pull_fqdn; /* as a client, allow server to push a FQDN for certain parameters */\n    struct client_nat_option_list *client_nat;\n\n    /* Enable options consistency check between peers */\n    bool occ;\n\n#ifdef ENABLE_MANAGEMENT\n    const char *management_addr;\n    const char *management_port;\n    const char *management_user_pass;\n    int management_log_history_cache;\n    int management_echo_buffer_size;\n    int management_state_buffer_size;\n\n    const char *management_client_user;\n    const char *management_client_group;\n\n    const char *management_certificate;\n#endif\n    /* Mask of MF_ values of manage.h */\n    unsigned int management_flags;\n\n#ifdef ENABLE_PLUGIN\n    struct plugin_option_list *plugin_list;\n#endif\n\n    /* the tmp dir is for now only used in the P2P server context */\n    const char *tmp_dir;\n    bool server_defined;\n    in_addr_t server_network;\n    in_addr_t server_netmask;\n    bool server_ipv6_defined;            /* IPv6 */\n    struct in6_addr server_network_ipv6; /* IPv6 */\n    unsigned int server_netbits_ipv6;    /* IPv6 */\n\n#define SF_NOPOOL                (1 << 0)\n#define SF_TCP_NODELAY_HELPER    (1 << 1)\n#define SF_NO_PUSH_ROUTE_GATEWAY (1 << 2)\n    unsigned int server_flags;\n\n    bool server_bridge_proxy_dhcp;\n\n    bool server_bridge_defined;\n    in_addr_t server_bridge_ip;\n    in_addr_t server_bridge_netmask;\n    in_addr_t server_bridge_pool_start;\n    in_addr_t server_bridge_pool_end;\n\n    struct push_list push_list;\n    bool ifconfig_pool_defined;\n    in_addr_t ifconfig_pool_start;\n    in_addr_t ifconfig_pool_end;\n    in_addr_t ifconfig_pool_netmask;\n    const char *ifconfig_pool_persist_filename;\n    int ifconfig_pool_persist_refresh_freq;\n\n    bool ifconfig_ipv6_pool_defined;         /* IPv6 */\n    struct in6_addr ifconfig_ipv6_pool_base; /* IPv6 */\n    int ifconfig_ipv6_pool_netbits;          /* IPv6 */\n\n    uint32_t real_hash_size;\n    uint32_t virtual_hash_size;\n    const char *client_connect_script;\n    const char *client_disconnect_script;\n    const char *learn_address_script;\n    const char *client_crresponse_script;\n    const char *client_config_dir;\n    bool ccd_exclusive;\n    bool disable;\n    const char *override_username;\n    int n_bcast_buf;\n    int tcp_queue_limit;\n    struct iroute *iroutes;\n    struct iroute_ipv6 *iroutes_ipv6; /* IPv6 */\n    bool push_ifconfig_defined;\n    in_addr_t push_ifconfig_local;\n    in_addr_t push_ifconfig_remote_netmask;\n    in_addr_t push_ifconfig_local_alias;\n    bool push_ifconfig_constraint_defined;\n    in_addr_t push_ifconfig_constraint_network;\n    in_addr_t push_ifconfig_constraint_netmask;\n    bool push_ifconfig_ipv4_blocked;           /* IPv4 */\n    bool push_ifconfig_ipv6_defined;           /* IPv6 */\n    struct in6_addr push_ifconfig_ipv6_local;  /* IPv6 */\n    int push_ifconfig_ipv6_netbits;            /* IPv6 */\n    struct in6_addr push_ifconfig_ipv6_remote; /* IPv6 */\n    bool push_ifconfig_ipv6_blocked;           /* IPv6 */\n    bool enable_c2c;\n    bool duplicate_cn;\n\n    int cf_max;\n    int cf_per;\n\n    int cf_initial_max;\n    int cf_initial_per;\n\n    int max_clients;\n    int max_routes_per_client;\n    int stale_routes_check_interval;\n    int stale_routes_ageing_time;\n\n    const char *auth_user_pass_verify_script;\n    bool auth_user_pass_verify_script_via_file;\n    bool auth_token_generate;\n    bool auth_token_call_auth;\n    int auth_token_lifetime;\n    int auth_token_renewal;\n    const char *auth_token_secret_file;\n    bool auth_token_secret_file_inline;\n\n#if PORT_SHARE\n    char *port_share_host;\n    char *port_share_port;\n    const char *port_share_journal_dir;\n#endif\n\n    bool client;\n    bool pull; /* client pull of config options from server */\n    int push_continuation;\n    unsigned int push_option_types_found;\n    unsigned int push_update_options_found; /* tracks which option types have been reset in current PUSH_UPDATE sequence */\n    const char *auth_user_pass_file;\n    bool auth_user_pass_file_inline;\n    struct options_pre_connect *pre_connect;\n\n    int scheduled_exit_interval;\n\n#ifdef ENABLE_MANAGEMENT\n    struct static_challenge_info sc_info;\n#endif\n    /* Cipher parms */\n    const char *shared_secret_file;\n    bool shared_secret_file_inline;\n    bool allow_deprecated_insecure_static_crypto;\n    int key_direction;\n    const char *ciphername;\n    bool enable_ncp_fallback; /**< If defined fall back to\n                               * ciphername if NCP fails */\n    /** The original ncp_ciphers specified by the user in the configuration*/\n    const char *ncp_ciphers_conf;\n    const char *ncp_ciphers;\n    const char *authname;\n    const char *engine;\n    struct provider_list providers;\n    bool mute_replay_warnings;\n    int replay_window;\n    int replay_time;\n    const char *packet_id_file;\n    bool test_crypto;\n\n    /* TLS (control channel) parms */\n    bool tls_server;\n    bool tls_client;\n    const char *ca_file;\n    bool ca_file_inline;\n    const char *ca_path;\n    const char *dh_file;\n    bool dh_file_inline;\n    const char *cert_file;\n    bool cert_file_inline;\n    const char *extra_certs_file;\n    bool extra_certs_file_inline;\n    const char *priv_key_file;\n    bool priv_key_file_inline;\n    const char *pkcs12_file;\n    bool pkcs12_file_inline;\n    const char *cipher_list;\n    const char *cipher_list_tls13;\n    const char *tls_groups;\n    const char *tls_cert_profile;\n    const char *ecdh_curve;\n    const char *tls_verify;\n    const char *tls_export_peer_cert_dir;\n    int verify_x509_type;\n    const char *verify_x509_name;\n    const char *crl_file;\n    bool crl_file_inline;\n\n    int ns_cert_type; /* set to 0, NS_CERT_CHECK_SERVER, or NS_CERT_CHECK_CLIENT */\n    unsigned remote_cert_ku[MAX_PARMS];\n    const char *remote_cert_eku;\n    struct verify_hash_list *verify_hash;\n    hash_algo_type verify_hash_algo;\n    int verify_hash_depth;\n    bool verify_hash_no_ca;\n    unsigned int ssl_flags; /* set to SSLF_x flags from ssl.h */\n\n#ifdef ENABLE_PKCS11\n    const char *pkcs11_providers[MAX_PARMS];\n    unsigned pkcs11_private_mode[MAX_PARMS];\n    bool pkcs11_protected_authentication[MAX_PARMS];\n    bool pkcs11_cert_private[MAX_PARMS];\n    int pkcs11_pin_cache_period;\n    const char *pkcs11_id;\n    bool pkcs11_id_management;\n#endif\n\n#ifdef ENABLE_CRYPTOAPI\n    const char *cryptoapi_cert;\n#endif\n    /* Per-packet timeout on control channel */\n    int tls_timeout;\n\n    /* Data channel key renegotiation parameters */\n    int64_t renegotiate_bytes;\n    int64_t renegotiate_packets;\n    int renegotiate_seconds;\n    int renegotiate_seconds_min;\n\n    /* Data channel key handshake must finalize\n     * within n seconds of handshake initiation. */\n    int handshake_window;\n\n    /* Field list used to be the username in X509 cert. */\n    char *x509_username_field[MAX_PARMS];\n\n    /* Old key allowed to live n seconds after new key goes active */\n    int transition_window;\n\n    /* Shared secret used for TLS control channel authentication */\n    const char *tls_auth_file;\n    bool tls_auth_file_inline;\n\n    /* Shared secret used for TLS control channel authenticated encryption */\n    const char *tls_crypt_file;\n    bool tls_crypt_file_inline;\n\n    /* Client-specific secret or server key used for TLS control channel\n     * authenticated encryption v2 */\n    const char *tls_crypt_v2_file;\n    bool tls_crypt_v2_file_inline;\n\n    const char *tls_crypt_v2_metadata;\n\n    const char *tls_crypt_v2_verify_script;\n\n    int tls_crypt_v2_max_age;\n\n    /* Allow only one session */\n    bool single_session;\n\n    bool push_peer_info;\n\n    bool tls_exit;\n\n    const struct x509_track *x509_track;\n\n    /* special state parms */\n    int foreign_option_index;\n\n#ifdef _WIN32\n    HANDLE msg_channel;\n    const char *exit_event_name;\n    bool exit_event_initial_state;\n    bool show_net_up;\n    int route_method;\n    bool block_outside_dns;\n    enum tun_driver_type windows_driver;\n#endif\n\n    bool use_peer_id;\n    uint32_t peer_id;\n\n    /* Keying Material Exporters [RFC 5705] */\n    const char *keying_material_exporter_label;\n    int keying_material_exporter_length;\n    /* force using TLS key material export for data channel key generation */\n    bool force_key_material_export;\n\n    bool vlan_tagging;\n    enum vlan_acceptable_frames vlan_accept;\n    uint16_t vlan_pvid;\n\n    struct pull_filter_list *pull_filter_list;\n\n    /* Useful when packets sent by openvpn itself are not subject\n     * to the routing tables that would move packets into the tunnel. */\n    bool allow_recursive_routing;\n\n    /* data channel crypto flags set by push/pull. Reuses the CO_* crypto_flags */\n    unsigned int imported_protocol_flags;\n};\n\n#define streq(x, y) (!strcmp((x), (y)))\n\n/*\n * Option classes.\n */\n#define OPT_P_GENERAL         (1u << 0)\n#define OPT_P_UP              (1u << 1)\n#define OPT_P_ROUTE           (1u << 2)\n#define OPT_P_DHCPDNS         (1u << 3) /* includes ip windows options like */\n#define OPT_P_SCRIPT          (1u << 4)\n#define OPT_P_SETENV          (1u << 5)\n#define OPT_P_SHAPER          (1u << 6)\n#define OPT_P_TIMER           (1u << 7)\n#define OPT_P_PERSIST         (1u << 8)\n#define OPT_P_PERSIST_IP      (1u << 9)\n#define OPT_P_COMP            (1u << 10) /* TODO */\n#define OPT_P_MESSAGES        (1u << 11)\n#define OPT_P_NCP             (1u << 12) /**< Negotiable crypto parameters */\n#define OPT_P_TLS_PARMS       (1u << 13) /* TODO */\n#define OPT_P_MTU             (1u << 14) /* TODO */\n#define OPT_P_NICE            (1u << 15)\n#define OPT_P_PUSH            (1u << 16)\n#define OPT_P_INSTANCE        (1u << 17) /**< allowed in ccd, client-connect etc*/\n#define OPT_P_CONFIG          (1u << 18)\n#define OPT_P_EXPLICIT_NOTIFY (1u << 19)\n#define OPT_P_ECHO            (1u << 20)\n#define OPT_P_INHERIT         (1u << 21)\n#define OPT_P_ROUTE_EXTRAS    (1u << 22)\n#define OPT_P_PULL_MODE       (1u << 23)\n#define OPT_P_PLUGIN          (1u << 24)\n#define OPT_P_SOCKBUF         (1u << 25)\n#define OPT_P_SOCKFLAGS       (1u << 26)\n#define OPT_P_CONNECTION      (1u << 27)\n#define OPT_P_PEER_ID         (1u << 28)\n#define OPT_P_INLINE          (1u << 29)\n#define OPT_P_PUSH_MTU        (1u << 30)\n#define OPT_P_ROUTE_TABLE     (1u << 31)\n\n#define OPT_P_DEFAULT (~(OPT_P_INSTANCE | OPT_P_PULL_MODE))\n\n#define PULL_DEFINED(opt) ((opt)->pull)\n\n#ifdef _WIN32\n#define ROUTE_OPTION_FLAGS(o) ((o)->route_method & ROUTE_METHOD_MASK)\n#else\n#define ROUTE_OPTION_FLAGS(o) (0)\n#endif\n\n#define SHAPER_DEFINED(opt) ((opt)->shaper)\n\n#ifdef ENABLE_PLUGIN\n#define PLUGIN_OPTION_LIST(opt) ((opt)->plugin_list)\n#else\n#define PLUGIN_OPTION_LIST(opt) (NULL)\n#endif\n\n#ifdef ENABLE_MANAGEMENT\n#define MAN_CLIENT_AUTH_ENABLED(opt) ((opt)->management_flags & MF_CLIENT_AUTH)\n#else\n#define MAN_CLIENT_AUTH_ENABLED(opt) (false)\n#endif\n\n/*\n * some PUSH_UPDATE options\n */\n#define OPT_P_U_ROUTE         (1 << 0)\n#define OPT_P_U_ROUTE6        (1 << 1)\n#define OPT_P_U_DNS           (1 << 2)\n#define OPT_P_U_DHCP          (1 << 3)\n#define OPT_P_U_REDIR_GATEWAY (1 << 4)\n\nstruct pull_filter\n{\n#define PUF_TYPE_UNDEF  0 /**< undefined filter type */\n#define PUF_TYPE_ACCEPT 1 /**< filter type to accept a matching option */\n#define PUF_TYPE_IGNORE 2 /**< filter type to ignore a matching option */\n#define PUF_TYPE_REJECT 3 /**< filter type to reject and trigger SIGUSR1 */\n    int type;\n    size_t size;\n    char *pattern;\n    struct pull_filter *next;\n};\n\nstruct pull_filter_list\n{\n    struct pull_filter *head;\n    struct pull_filter *tail;\n};\n\nvoid add_option(struct options *options, char *p[], bool is_inline, const char *file,\n                int line, const int level, const msglvl_t msglevel,\n                const unsigned int permission_mask, unsigned int *option_types_found,\n                struct env_set *es);\n\n/**\n * @brief Resets options found in the PUSH_UPDATE message that are preceded by the `-` flag.\n *        This function is used in push-updates to reset specified options.\n *        The number of parameters `p` must always be 1. If the permission is verified,\n *        all related options are erased or reset to their default values.\n *        Upon successful permission verification (by VERIFY_PERMISSION()),\n *        `option_types_found` is filled with the flag corresponding to the option.\n *\n * @param c The context structure.\n * @param options A pointer to the options structure.\n * @param p An array of strings containing the options and their parameters.\n * @param is_inline A boolean indicating if the option is inline.\n * @param file The file where the function is called.\n * @param line The line number where the function is called.\n * @param msglevel The message level.\n * @param permission_mask The permission mask used by VERIFY_PERMISSION().\n * @param option_types_found A pointer to the variable where the flags corresponding to the options\n * found are stored.\n * @param es The environment set structure.\n */\nvoid remove_option(struct context *c, struct options *options, char *p[], bool is_inline,\n                   const char *file, int line, const msglvl_t msglevel,\n                   const unsigned int permission_mask, unsigned int *option_types_found,\n                   struct env_set *es);\n\n/**\n * @brief Processes an option to update. It first checks whether it has already\n *        received an option of the same type within the same update message.\n *        If the option has already been received, it calls add_option().\n *        Otherwise, it deletes all existing values related to that option before calling\n * add_option().\n *\n * @param c The context structure.\n * @param options A pointer to the options structure.\n * @param p An array of strings containing the options and their parameters.\n * @param is_inline A boolean indicating if the option is inline.\n * @param file The file where the function is called.\n * @param line The line number where the function is called.\n * @param level The level of the option.\n * @param msglevel The message level for logging.\n * @param permission_mask The permission mask used by VERIFY_PERMISSION().\n * @param option_types_found A pointer to the variable where the flags corresponding to the options\n * found are stored.\n * @param es The environment set structure.\n */\nvoid update_option(struct context *c, struct options *options, char *p[], bool is_inline,\n                   const char *file, int line, const int level, const msglvl_t msglevel,\n                   const unsigned int permission_mask, unsigned int *option_types_found,\n                   struct env_set *es);\n\nvoid parse_argv(struct options *options, const int argc, char *argv[], const msglvl_t msglevel,\n                const unsigned int permission_mask, unsigned int *option_types_found,\n                struct env_set *es);\n\nvoid read_config_file(struct options *options, const char *file, int level, const char *top_file,\n                      const int top_line, const msglvl_t msglevel,\n                      const unsigned int permission_mask, unsigned int *option_types_found,\n                      struct env_set *es);\n\nvoid read_config_string(const char *prefix, struct options *options, const char *config,\n                        const msglvl_t msglevel, const unsigned int permission_mask,\n                        unsigned int *option_types_found, struct env_set *es);\n\nvoid notnull(const char *arg, const char *description);\n\nvoid usage_small(void);\n\nvoid usage(void);\n\nvoid show_library_versions(const unsigned int flags);\n\n#ifdef _WIN32\nvoid show_windows_version(const unsigned int flags);\n\n#endif\n\nvoid show_dco_version(const unsigned int flags);\n\nvoid init_options(struct options *o);\n\nvoid uninit_options(struct options *o);\n\nvoid setenv_settings(struct env_set *es, const struct options *o);\n\nvoid show_settings(const struct options *o);\n\nbool string_defined_equal(const char *s1, const char *s2);\n\nconst char *options_string_version(const char *s, struct gc_arena *gc);\n\nchar *options_string(const struct options *o, const struct frame *frame, struct tuntap *tt,\n                     openvpn_net_ctx_t *ctx, bool remote, struct gc_arena *gc);\n\nbool options_cmp_equal_safe(char *actual, const char *expected, size_t actual_n);\n\nvoid options_warning_safe(char *actual, const char *expected, size_t actual_n);\n\nbool options_cmp_equal(char *actual, const char *expected);\n\nvoid options_warning(char *actual, const char *expected);\n\n/**\n * Given an OpenVPN options string, extract the value of an option.\n *\n * @param options_string        Zero-terminated, comma-separated options string\n * @param opt_name              The name of the option to extract\n * @param gc                    The gc to allocate the return value\n *\n * @return gc-allocated value of option with name opt_name if option was found,\n *         or NULL otherwise.\n */\nchar *options_string_extract_option(const char *options_string, const char *opt_name,\n                                    struct gc_arena *gc);\n\n\nvoid options_postprocess(struct options *options, struct env_set *es);\n\nbool options_postprocess_pull(struct options *o, struct env_set *es);\n\nvoid pre_connect_restore(struct options *o, struct gc_arena *gc);\n\nbool apply_push_options(struct context *c, struct options *options, struct buffer *buf,\n                        unsigned int permission_mask, unsigned int *option_types_found,\n                        struct env_set *es, bool is_update);\n\nvoid options_detach(struct options *o);\n\nvoid options_server_import(struct options *o, const char *filename, msglvl_t msglevel,\n                           unsigned int permission_mask, unsigned int *option_types_found,\n                           struct env_set *es);\n\nvoid pre_pull_default(struct options *o);\n\nvoid rol_check_alloc(struct options *options);\n\nint parse_line(const char *line, char *p[], const int n, const char *file, const int line_num,\n               msglvl_t msglevel, struct gc_arena *gc);\n\n/*\n * parse/print topology coding\n */\n\nint parse_topology(const char *str, const msglvl_t msglevel);\n\nconst char *print_topology(const int topology);\n\n/*\n * Manage auth-retry variable\n */\n\n#define AR_NONE       0\n#define AR_INTERACT   1\n#define AR_NOINTERACT 2\n\nint auth_retry_get(void);\n\nbool auth_retry_set(const msglvl_t msglevel, const char *option);\n\nconst char *auth_retry_print(void);\n\nvoid options_string_import(struct options *options, const char *config, const msglvl_t msglevel,\n                           const unsigned int permission_mask, unsigned int *option_types_found,\n                           struct env_set *es);\n\nbool key_is_external(const struct options *options);\n\nbool has_udp_in_local_list(const struct options *options);\n\n/**\n * Returns whether the current configuration has dco enabled.\n */\nstatic inline bool\ndco_enabled(const struct options *o)\n{\n#ifdef ENABLE_DCO\n    return !o->disable_dco;\n#else\n    return false;\n#endif /* ENABLE_DCO */\n}\n\n#endif /* ifndef OPTIONS_H */\n"
  },
  {
    "path": "src/openvpn/options_parse.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2008-2026 David Sommerseth <dazo@eurephia.org>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <string.h>\n\n#include \"options.h\"\n#include \"options_util.h\"\n#include \"push.h\"\n\nstatic void\nbypass_doubledash(char **p)\n{\n    if (strlen(*p) >= 3 && !strncmp(*p, \"--\", 2))\n    {\n        *p += 2;\n    }\n}\n\nstatic inline bool\nspace(char c)\n{\n    return c == '\\0' || isspace(c);\n}\n\nint\nparse_line(const char *line, char *p[], const int n, const char *file, const int line_num,\n           msglvl_t msglevel, struct gc_arena *gc)\n{\n    const int STATE_INITIAL = 0;\n    const int STATE_READING_QUOTED_PARM = 1;\n    const int STATE_READING_UNQUOTED_PARM = 2;\n    const int STATE_DONE = 3;\n    const int STATE_READING_SQUOTED_PARM = 4;\n\n    const char *error_prefix = \"\";\n\n    int ret = 0;\n    const char *c = line;\n    int state = STATE_INITIAL;\n    bool backslash = false;\n    char in, out;\n\n    char parm[OPTION_PARM_SIZE];\n    unsigned int parm_len = 0;\n\n    msglevel &= ~M_OPTERR;\n\n    if (msglevel & M_MSG_VIRT_OUT)\n    {\n        error_prefix = \"ERROR: \";\n    }\n\n    do\n    {\n        in = *c;\n        out = 0;\n\n        if (!backslash && in == '\\\\' && state != STATE_READING_SQUOTED_PARM)\n        {\n            backslash = true;\n        }\n        else\n        {\n            if (state == STATE_INITIAL)\n            {\n                if (!space(in))\n                {\n                    if (in == ';' || in == '#') /* comment */\n                    {\n                        break;\n                    }\n                    if (!backslash && in == '\\\"')\n                    {\n                        state = STATE_READING_QUOTED_PARM;\n                    }\n                    else if (!backslash && in == '\\'')\n                    {\n                        state = STATE_READING_SQUOTED_PARM;\n                    }\n                    else\n                    {\n                        out = in;\n                        state = STATE_READING_UNQUOTED_PARM;\n                    }\n                }\n            }\n            else if (state == STATE_READING_UNQUOTED_PARM)\n            {\n                if (!backslash && space(in))\n                {\n                    state = STATE_DONE;\n                }\n                else\n                {\n                    out = in;\n                }\n            }\n            else if (state == STATE_READING_QUOTED_PARM)\n            {\n                if (!backslash && in == '\\\"')\n                {\n                    state = STATE_DONE;\n                }\n                else\n                {\n                    out = in;\n                }\n            }\n            else if (state == STATE_READING_SQUOTED_PARM)\n            {\n                if (in == '\\'')\n                {\n                    state = STATE_DONE;\n                }\n                else\n                {\n                    out = in;\n                }\n            }\n            if (state == STATE_DONE)\n            {\n                /* ASSERT (parm_len > 0); */\n                p[ret] = gc_malloc(parm_len + 1, true, gc);\n                memcpy(p[ret], parm, parm_len);\n                p[ret][parm_len] = '\\0';\n                state = STATE_INITIAL;\n                parm_len = 0;\n                ++ret;\n            }\n\n            if (backslash && out)\n            {\n                if (!(out == '\\\\' || out == '\\\"' || space(out)))\n                {\n#ifdef ENABLE_SMALL\n                    msg(msglevel, \"%sOptions warning: Bad backslash ('\\\\') usage in %s:%d\",\n                        error_prefix, file, line_num);\n#else\n                    msg(msglevel,\n                        \"%sOptions warning: Bad backslash ('\\\\') usage in %s:%d: remember that backslashes are treated as shell-escapes and if you need to pass backslash characters as part of a Windows filename, you should use double backslashes such as \\\"c:\\\\\\\\\" PACKAGE\n                        \"\\\\\\\\static.key\\\"\",\n                        error_prefix, file, line_num);\n#endif\n                    return 0;\n                }\n            }\n            backslash = false;\n        }\n\n        /* store parameter character */\n        if (out)\n        {\n            if (parm_len >= SIZE(parm))\n            {\n                parm[SIZE(parm) - 1] = 0;\n                msg(msglevel, \"%sOptions error: Parameter at %s:%d is too long (%d chars max): %s\",\n                    error_prefix, file, line_num, (int)SIZE(parm), parm);\n                return 0;\n            }\n            parm[parm_len++] = out;\n        }\n\n        /* avoid overflow if too many parms in one config file line */\n        if (ret >= n)\n        {\n            break;\n        }\n\n    } while (*c++ != '\\0');\n\n    if (state == STATE_READING_QUOTED_PARM)\n    {\n        msg(msglevel, \"%sOptions error: No closing quotation (\\\") in %s:%d\", error_prefix, file,\n            line_num);\n        return 0;\n    }\n    if (state == STATE_READING_SQUOTED_PARM)\n    {\n        msg(msglevel, \"%sOptions error: No closing single quotation (\\') in %s:%d\", error_prefix,\n            file, line_num);\n        return 0;\n    }\n    if (state != STATE_INITIAL)\n    {\n        msg(msglevel, \"%sOptions error: Residual parse state (%d) in %s:%d\", error_prefix, state,\n            file, line_num);\n        return 0;\n    }\n#if 0\n    {\n        int i;\n        for (i = 0; i < ret; ++i)\n        {\n            msg(M_INFO|M_NOPREFIX, \"%s:%d ARG[%d] '%s'\", file, line_num, i, p[i]);\n        }\n    }\n#endif\n    return ret;\n}\n\nstruct in_src\n{\n#define IS_TYPE_FP  1\n#define IS_TYPE_BUF 2\n    int type;\n    union\n    {\n        FILE *fp;\n        struct buffer *multiline;\n    } u;\n};\n\nstatic bool\nin_src_get(const struct in_src *is, char *line, const int size)\n{\n    if (is->type == IS_TYPE_FP)\n    {\n        return BOOL_CAST(fgets(line, size, is->u.fp));\n    }\n    else if (is->type == IS_TYPE_BUF)\n    {\n        bool status = buf_parse(is->u.multiline, '\\n', line, size);\n        if ((int)strlen(line) + 1 < size)\n        {\n            strcat(line, \"\\n\");\n        }\n        return status;\n    }\n    else\n    {\n        ASSERT(0);\n        return false;\n    }\n}\n\nstatic char *\nread_inline_file(struct in_src *is, const char *close_tag, int *num_lines, struct gc_arena *gc)\n{\n    char line[OPTION_LINE_SIZE];\n    struct buffer buf = alloc_buf(8 * OPTION_LINE_SIZE);\n    char *ret;\n    bool endtagfound = false;\n\n    while (in_src_get(is, line, sizeof(line)))\n    {\n        (*num_lines)++;\n        char *line_ptr = line;\n        /* Remove leading spaces */\n        while (isspace(*line_ptr))\n        {\n            line_ptr++;\n        }\n        if (!strncmp(line_ptr, close_tag, strlen(close_tag)))\n        {\n            endtagfound = true;\n            break;\n        }\n        if (!buf_safe(&buf, strlen(line) + 1))\n        {\n            /* Increase buffer size */\n            struct buffer buf2 = alloc_buf(buf.capacity * 2);\n            ASSERT(buf_copy(&buf2, &buf));\n            buf_clear(&buf);\n            free_buf(&buf);\n            buf = buf2;\n        }\n        buf_printf(&buf, \"%s\", line);\n    }\n    if (!endtagfound)\n    {\n        msg(M_FATAL, \"ERROR: Endtag %s missing\", close_tag);\n    }\n    ret = string_alloc(BSTR(&buf), gc);\n    buf_clear(&buf);\n    free_buf(&buf);\n    secure_memzero(line, sizeof(line));\n    return ret;\n}\n\nstatic int\ncheck_inline_file(struct in_src *is, char *p[], struct gc_arena *gc)\n{\n    int num_inline_lines = 0;\n\n    if (p[0] && !p[1])\n    {\n        char *arg = p[0];\n        if (arg[0] == '<' && arg[strlen(arg) - 1] == '>')\n        {\n            struct buffer close_tag;\n\n            arg[strlen(arg) - 1] = '\\0';\n            p[0] = string_alloc(arg + 1, gc);\n            close_tag = alloc_buf(strlen(p[0]) + 4);\n            buf_printf(&close_tag, \"</%s>\", p[0]);\n            p[1] = read_inline_file(is, BSTR(&close_tag), &num_inline_lines, gc);\n            p[2] = NULL;\n            free_buf(&close_tag);\n        }\n    }\n    return num_inline_lines;\n}\n\nstatic int\ncheck_inline_file_via_fp(FILE *fp, char *p[], struct gc_arena *gc)\n{\n    struct in_src is;\n    is.type = IS_TYPE_FP;\n    is.u.fp = fp;\n    return check_inline_file(&is, p, gc);\n}\n\nstatic int\ncheck_inline_file_via_buf(struct buffer *multiline, char *p[], struct gc_arena *gc)\n{\n    struct in_src is;\n    is.type = IS_TYPE_BUF;\n    is.u.multiline = multiline;\n    return check_inline_file(&is, p, gc);\n}\n\nvoid\nread_config_file(struct options *options, const char *file, int level, const char *top_file,\n                 const int top_line, const msglvl_t msglevel,\n                 const unsigned int permission_mask, unsigned int *option_types_found,\n                 struct env_set *es)\n{\n    const int max_recursive_levels = 10;\n    FILE *fp;\n    int line_num;\n    char line[OPTION_LINE_SIZE + 1];\n    char *p[MAX_PARMS + 1];\n\n    ++level;\n    if (level <= max_recursive_levels)\n    {\n        if (streq(file, \"stdin\"))\n        {\n            fp = stdin;\n        }\n        else\n        {\n            fp = platform_fopen(file, \"r\");\n        }\n        if (fp)\n        {\n            line_num = 0;\n            while (fgets(line, sizeof(line), fp))\n            {\n                int offset = 0;\n                CLEAR(p);\n                ++line_num;\n                if (strlen(line) == OPTION_LINE_SIZE)\n                {\n                    msg(msglevel,\n                        \"In %s:%d: Maximum option line length (%d) exceeded, line starts with %s\",\n                        file, line_num, OPTION_LINE_SIZE, line);\n                }\n\n                /* Ignore UTF-8 BOM at start of stream */\n                if (line_num == 1 && strncmp(line, \"\\xEF\\xBB\\xBF\", 3) == 0)\n                {\n                    offset = 3;\n                }\n                if (parse_line(line + offset, p, SIZE(p) - 1, file, line_num, msglevel,\n                               &options->gc))\n                {\n                    bypass_doubledash(&p[0]);\n                    int lines_inline = check_inline_file_via_fp(fp, p, &options->gc);\n                    add_option(options, p, lines_inline, file, line_num, level, msglevel,\n                               permission_mask, option_types_found, es);\n                    line_num += lines_inline;\n                }\n            }\n            if (fp != stdin)\n            {\n                fclose(fp);\n            }\n        }\n        else\n        {\n            msg(msglevel, \"In %s:%d: Error opening configuration file: %s\", top_file, top_line,\n                file);\n        }\n    }\n    else\n    {\n        msg(msglevel,\n            \"In %s:%d: Maximum recursive include levels exceeded in include attempt of file %s -- probably you have a configuration file that tries to include itself.\",\n            top_file, top_line, file);\n    }\n    secure_memzero(line, sizeof(line));\n    CLEAR(p);\n}\n\nvoid\nread_config_string(const char *prefix, struct options *options, const char *config,\n                   const msglvl_t msglevel, const unsigned int permission_mask,\n                   unsigned int *option_types_found, struct env_set *es)\n{\n    char line[OPTION_LINE_SIZE];\n    struct buffer multiline;\n    int line_num = 0;\n\n    buf_set_read(&multiline, (uint8_t *)config, strlen(config));\n\n    while (buf_parse(&multiline, '\\n', line, sizeof(line)))\n    {\n        char *p[MAX_PARMS + 1];\n        CLEAR(p);\n        ++line_num;\n        if (parse_line(line, p, SIZE(p) - 1, prefix, line_num, msglevel, &options->gc))\n        {\n            bypass_doubledash(&p[0]);\n            int lines_inline = check_inline_file_via_buf(&multiline, p, &options->gc);\n            add_option(options, p, lines_inline, prefix, line_num, 0, msglevel, permission_mask,\n                       option_types_found, es);\n            line_num += lines_inline;\n        }\n        CLEAR(p);\n    }\n    secure_memzero(line, sizeof(line));\n}\n\nvoid\nparse_argv(struct options *options, const int argc, char *argv[], const msglvl_t msglevel,\n           const unsigned int permission_mask, unsigned int *option_types_found, struct env_set *es)\n{\n    /* usage message */\n    if (argc <= 1)\n    {\n        usage();\n    }\n\n    /* config filename specified only? */\n    if (argc == 2 && strncmp(argv[1], \"--\", 2))\n    {\n        char *p[MAX_PARMS + 1];\n        CLEAR(p);\n        p[0] = \"config\";\n        p[1] = argv[1];\n        add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found,\n                   es);\n    }\n    else\n    {\n        /* parse command line */\n        for (int i = 1; i < argc; ++i)\n        {\n            char *p[MAX_PARMS + 1];\n            CLEAR(p);\n            p[0] = argv[i];\n            if (strncmp(p[0], \"--\", 2))\n            {\n                msg(msglevel,\n                    \"I'm trying to parse \\\"%s\\\" as an --option parameter but I don't see a leading '--'\",\n                    p[0]);\n            }\n            else\n            {\n                p[0] += 2;\n            }\n\n            int j;\n            for (j = 1; j < MAX_PARMS; ++j)\n            {\n                if (i + j < argc)\n                {\n                    char *arg = argv[i + j];\n                    if (strncmp(arg, \"--\", 2))\n                    {\n                        p[j] = arg;\n                    }\n                    else\n                    {\n                        break;\n                    }\n                }\n            }\n            add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask, option_types_found,\n                       es);\n            i += j - 1;\n        }\n    }\n}\n\nbool\napply_push_options(struct context *c, struct options *options, struct buffer *buf,\n                   unsigned int permission_mask, unsigned int *option_types_found,\n                   struct env_set *es, bool is_update)\n{\n    char line[OPTION_PARM_SIZE];\n    int line_num = 0;\n    const char *file = \"[PUSH-OPTIONS]\";\n    const msglvl_t msglevel = D_PUSH_ERRORS | M_OPTERR;\n\n    while (buf_parse(buf, ',', line, sizeof(line)))\n    {\n        char *p[MAX_PARMS + 1];\n        CLEAR(p);\n        ++line_num;\n        unsigned int push_update_option_flags = 0;\n        int i = 0;\n\n        /* skip leading spaces matching the behaviour of parse_line */\n        while (isspace(line[i]))\n        {\n            i++;\n        }\n\n        /* If we are not in a 'PUSH_UPDATE' we just check `apply_pull_filter()`\n         * otherwise we must call `check_push_update_option_flags()` first\n         */\n        if ((is_update && !check_push_update_option_flags(line, &i, &push_update_option_flags))\n            || !apply_pull_filter(options, &line[i]))\n        {\n            /* In case we are in a `PUSH_UPDATE` and `check_push_update_option_flags()`\n             * or `apply_pull_filter()` fail but the option is flagged by `PUSH_OPT_OPTIONAL`,\n             * instead of restarting, we just ignore the option and we process the next one\n             */\n            if (push_update_option_flags & PUSH_OPT_OPTIONAL)\n            {\n                continue; /* Ignoring this option */\n            }\n            return false; /* Cause push/pull error and stop push processing */\n        }\n\n        if (parse_line(&line[i], p, SIZE(p) - 1, file, line_num, msglevel, &options->gc))\n        {\n            if (!is_update)\n            {\n                add_option(options, p, false, file, line_num, 0, msglevel, permission_mask,\n                           option_types_found, es);\n            }\n            else if (push_update_option_flags & PUSH_OPT_TO_REMOVE)\n            {\n                remove_option(c, options, p, false, file, line_num, msglevel, permission_mask,\n                              option_types_found, es);\n            }\n            else\n            {\n                /*\n                 * Use persistent push_update_options_found from options struct to track\n                 * which option types have been reset across continuation messages.\n                 * This prevents routes from being reset on each continuation message.\n                 */\n                update_option(c, options, p, false, file, line_num, 0, msglevel, permission_mask,\n                              option_types_found, es);\n            }\n        }\n    }\n    return true;\n}\n\nvoid\noptions_server_import(struct options *o, const char *filename, msglvl_t msglevel,\n                      unsigned int permission_mask, unsigned int *option_types_found,\n                      struct env_set *es)\n{\n    msg(D_PUSH, \"OPTIONS IMPORT: reading client specific options from: %s\", filename);\n    read_config_file(o, filename, 0, filename, 0, msglevel, permission_mask, option_types_found,\n                     es);\n}\n\nvoid\noptions_string_import(struct options *options, const char *config, const msglvl_t msglevel,\n                      const unsigned int permission_mask, unsigned int *option_types_found,\n                      struct env_set *es)\n{\n    read_config_string(\"[CONFIG-STRING]\", options, config, msglevel, permission_mask,\n                       option_types_found, es);\n}\n"
  },
  {
    "path": "src/openvpn/options_util.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"options_util.h\"\n\n#include \"push.h\"\n\nconst char *\nparse_auth_failed_temp(struct options *o, const char *reason)\n{\n    struct gc_arena gc = gc_new();\n\n    const char *message = reason;\n    char *m = string_alloc(reason, &gc);\n\n    /* Check if the message uses the TEMP[flags]: message format*/\n    char *endofflags = strstr(m, \"]\");\n\n    /* Temporary failure from the server */\n    if (m[0] == '[' && endofflags)\n    {\n        message = strstr(reason, \"]\") + 1;\n        /* null terminate the substring to only looks for flags between [ and ] */\n        *endofflags = '\\x00';\n        const char *token = strtok(m, \"[,\");\n        while (token)\n        {\n            if (!strncmp(token, \"backoff \", strlen(\"backoff \")))\n            {\n                if (sscanf(token, \"backoff %d\", &o->server_backoff_time) != 1)\n                {\n                    msg(D_PUSH, \"invalid AUTH_FAIL,TEMP flag: %s\", token);\n                    o->server_backoff_time = 0;\n                }\n            }\n            else if (!strncmp(token, \"advance \", strlen(\"advance \")))\n            {\n                token += strlen(\"advance \");\n                if (!strcmp(token, \"no\"))\n                {\n                    o->no_advance = true;\n                }\n                else if (!strcmp(token, \"remote\"))\n                {\n                    o->advance_next_remote = true;\n                    o->no_advance = false;\n                }\n                else if (!strcmp(token, \"addr\"))\n                {\n                    /* Go on to the next remote */\n                    o->no_advance = false;\n                }\n            }\n            else\n            {\n                msg(D_PUSH_ERRORS, \"WARNING: unknown AUTH_FAIL,TEMP flag: %s\", token);\n            }\n            token = strtok(NULL, \"[,\");\n        }\n    }\n\n    /* Look for the message in the original buffer to safely be\n     * able to return it */\n    if (!message || message[0] != ':')\n    {\n        message = \"\";\n    }\n    else\n    {\n        /* Skip the : at the beginning */\n        message += 1;\n    }\n    gc_free(&gc);\n    return message;\n}\n\nbool\nvalid_integer(const char *str, bool positive)\n{\n    char *endptr;\n    long long i = strtoll(str, &endptr, 10);\n\n    if (i < INT_MIN || (positive && i < 0) || *endptr != '\\0' || i > INT_MAX)\n    {\n        return false;\n    }\n    else\n    {\n        return true;\n    }\n}\n\nint\npositive_atoi(const char *str, msglvl_t msglevel)\n{\n    char *endptr;\n    long long i = strtoll(str, &endptr, 10);\n\n    if (i < 0 || *endptr != '\\0' || i > INT_MAX)\n    {\n        msg(msglevel, \"Cannot parse argument '%s' as non-negative integer\", str);\n        i = 0;\n    }\n\n    return (int)i;\n}\n\nbool\npositive_atoll(const char *str, int64_t *value, const char *name, msglvl_t msglevel)\n{\n    char *endptr;\n    long long ll = strtoll(str, &endptr, 10);\n\n    if (ll < 0 || *endptr != '\\0')\n    {\n        msg(msglevel, \"%s: Cannot parse '%s' as non-negative integer\", name, str);\n        return false;\n    }\n\n    *value = (int64_t)ll;\n    return true;\n}\n\nint\natoi_warn(const char *str, msglvl_t msglevel)\n{\n    char *endptr;\n    long long i = strtoll(str, &endptr, 10);\n\n    if (i < INT_MIN || *endptr != '\\0' || i > INT_MAX)\n    {\n        msg(msglevel, \"Cannot parse argument '%s' as integer\", str);\n        i = 0;\n    }\n\n    return (int)i;\n}\n\nbool\natoi_constrained(const char *str, int *value, const char *name, int min, int max, msglvl_t msglevel)\n{\n    ASSERT(min < max);\n\n    char *endptr;\n    long long i = strtoll(str, &endptr, 10);\n    if (i < INT_MIN || *endptr != '\\0' || i > INT_MAX)\n    {\n        msg(msglevel, \"%s: Cannot parse '%s' as integer\", name, str);\n        return false;\n    }\n    if (i < min || i > max)\n    {\n        if (max == INT_MAX) /* nicer message for common case */\n        {\n            msg(msglevel, \"%s: Must be an integer >= %d, not %lld\",\n                name, min, i);\n        }\n        else\n        {\n            msg(msglevel, \"%s: Must be an integer between %d and %d, not %lld\",\n                name, min, max, i);\n        }\n        return false;\n    }\n\n    *value = (int)i;\n    return true;\n}\n\nstatic const char *updatable_options[] = { \"block-ipv6\", \"block-outside-dns\",\n                                           \"dhcp-option\", \"dns\",\n                                           \"ifconfig\", \"ifconfig-ipv6\",\n                                           \"push-continuation\", \"redirect-gateway\",\n                                           \"redirect-private\", \"route\",\n                                           \"route-gateway\", \"route-ipv6\",\n                                           \"route-metric\", \"topology\",\n                                           \"tun-mtu\", \"keepalive\" };\n\nbool\ncheck_push_update_option_flags(char *line, int *i, unsigned int *flags)\n{\n    *flags = 0;\n    bool opt_is_updatable = false;\n    char c = line[*i];\n\n    /* We check for '?' and '-' and\n     * if they are present we skip them.\n     */\n    if (c == '-')\n    {\n        if (!(line)[*i + 1])\n        {\n            return false;\n        }\n        *flags |= PUSH_OPT_TO_REMOVE;\n        c = (line)[++(*i)];\n    }\n    if (c == '?')\n    {\n        if (!(line)[*i + 1] || (line)[*i + 1] == '-')\n        {\n            return false;\n        }\n        *flags |= PUSH_OPT_OPTIONAL;\n        c = (line)[++(*i)];\n    }\n\n    size_t len = strlen(&line[*i]);\n    int count = sizeof(updatable_options) / sizeof(char *);\n    for (int j = 0; j < count; ++j)\n    {\n        size_t opt_len = strlen(updatable_options[j]);\n        if (len < opt_len)\n        {\n            continue;\n        }\n        if (!strncmp(&line[*i], updatable_options[j], opt_len)\n            && (!line[*i + opt_len] || line[*i + opt_len] == ' '))\n        {\n            opt_is_updatable = true;\n            break;\n        }\n    }\n\n    if (!opt_is_updatable)\n    {\n        if (*flags & PUSH_OPT_OPTIONAL)\n        {\n            msg(D_PUSH, \"Pushed dispensable option is not updatable: '%s'. Ignoring.\", line);\n        }\n        else\n        {\n            msg(M_WARN, \"Pushed option is not updatable: '%s'.\", line);\n            return false;\n        }\n    }\n\n    return true;\n}\n\nbool\napply_pull_filter(const struct options *o, char *line)\n{\n    if (!o->pull_filter_list)\n    {\n        return true;\n    }\n\n    struct pull_filter *f;\n\n    for (f = o->pull_filter_list->head; f; f = f->next)\n    {\n        if (f->type == PUF_TYPE_ACCEPT && strncmp(line, f->pattern, f->size) == 0)\n        {\n            msg(D_LOW, \"Pushed option accepted by filter: '%s'\", line);\n            return true;\n        }\n        else if (f->type == PUF_TYPE_IGNORE && strncmp(line, f->pattern, f->size) == 0)\n        {\n            msg(D_PUSH, \"Pushed option removed by filter: '%s'\", line);\n            *line = '\\0';\n            return true;\n        }\n        else if (f->type == PUF_TYPE_REJECT && strncmp(line, f->pattern, f->size) == 0)\n        {\n            msg(M_WARN, \"Pushed option rejected by filter: '%s'.\", line);\n            return false;\n        }\n    }\n    return true;\n}\n"
  },
  {
    "path": "src/openvpn/options_util.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef OPTIONS_UTIL_H_\n#define OPTIONS_UTIL_H_\n\n#include \"options.h\"\n\nconst char *parse_auth_failed_temp(struct options *o, const char *reason);\n\n\n/** Checks if the string is a valid integer by checking if it can be\n *  converted to an integer */\nbool valid_integer(const char *str, bool positive);\n\n/**\n * Converts a str to a positive number if the string represents a postive\n * integer number. Otherwise print a warning with msglevel and return 0\n */\nint positive_atoi(const char *str, msglvl_t msglevel);\n\n/**\n * Converts a str to an integer if the string can be represented as an\n * integer number and is >= 0.\n * The integer is stored in \\p value.\n * On error, print a warning with \\p msglevel using \\p name. \\p value is\n * not changed on error.\n *\n * @return \\c true if the integer has been parsed and stored in value, \\c false otherwise\n */\nbool positive_atoll(const char *str, int64_t *value, const char *name, msglvl_t msglevel);\n\n/**\n * Converts a str to an integer if the string can be represented as an\n * integer number. Otherwise print a warning with \\p msglevel and return 0\n */\nint atoi_warn(const char *str, msglvl_t msglevel);\n\n/**\n * Converts a str to an integer if the string can be represented as an\n * integer number and is between \\p min and \\p max.\n * The integer is stored in \\p value.\n * On error, print a warning with \\p msglevel using \\p name. \\p value is\n * not changed on error.\n *\n * @return \\c true if the integer has been parsed and stored in value, \\c false otherwise\n */\nbool atoi_constrained(const char *str, int *value, const char *name, int min, int max,\n                      msglvl_t msglevel);\n\n/**\n * Filter an option line by all pull filters.\n *\n * If a match is found, the line is modified depending on\n * the filter type, and returns true. If the filter type is\n * reject, SIGUSR1 is triggered and the return value is false.\n * In that case the caller must end the push processing.\n */\nbool apply_pull_filter(const struct options *o, char *line);\n\n/**\n * @brief Checks the formatting and validity of options inside push-update messages.\n *\n * This function is used to validate and process options\n * in push-update messages. It performs the following checks:\n * - Determines if the options are updatable.\n * - Checks for the presence of the `-` flag, which indicates that the option\n *   should be removed.\n * - Checks for the `?` flag, which marks the option as optional and suppresses\n *   errors if the client cannot update it.\n * - Increase the value pointed by 'i' when we encounter the `'-'` and `'?'` flags\n *   after validating them and updating the appropriate flags in the `flags` variable.\n * - `-?option`, `-option`, `?option` are valid formats, `?-option` is not a valid format.\n * - If the flags and the option are not consecutive, the option is invalid:\n *   `- ?option`, `-? option`, `- option` are invalid formats.\n *\n * @param line A pointer to an option string. This string is the option being validated.\n * @param i A pointer to an integer that represents the current index in the `line` string.\n * @param flags A pointer where flags will be stored:\n *              - `PUSH_OPT_TO_REMOVE`: Set if the `-` flag is present.\n *              - `PUSH_OPT_OPTIONAL`: Set if the `?` flag is present.\n *\n * @return true if the flags and option combination are valid.\n * @return false if:\n *         - The `-` and `?` flags are not formatted correctly.\n *         - The `line` parameter is empty or `NULL`.\n *         - The `?` flag is absent and the option is not updatable.\n */\nbool check_push_update_option_flags(char *line, int *i, unsigned int *flags);\n\n#endif /* ifndef OPTIONS_UTIL_H_ */\n"
  },
  {
    "path": "src/openvpn/otime.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"otime.h\"\n\n#include \"memdbg.h\"\n\ntime_t now = 0;            /* GLOBAL */\n\nstatic time_t now_adj = 0; /* GLOBAL */\ntime_t now_usec = 0;       /* GLOBAL */\n\n/*\n * Try to filter out time instability caused by the system\n * clock backtracking or jumping forward.\n */\n\nvoid\nupdate_now(const time_t system_time)\n{\n    /* threshold at which to dampen forward jumps */\n    const int forward_threshold = 86400;\n    /* backward jump must be >= this many seconds before we adjust */\n    const int backward_trigger = 10;\n    time_t real_time = system_time + now_adj;\n\n    if (real_time > now)\n    {\n        const time_t overshoot = real_time - now - 1;\n        if (overshoot > forward_threshold && now_adj >= overshoot)\n        {\n            now_adj -= overshoot;\n            real_time -= overshoot;\n        }\n        now = real_time;\n    }\n    else if (real_time < now - backward_trigger)\n    {\n        now_adj += (now - real_time);\n    }\n}\n\nvoid\nupdate_now_usec(struct timeval *tv)\n{\n    const time_t last = now;\n    update_now(tv->tv_sec);\n    if (now > last || (now == last && tv->tv_usec > now_usec))\n    {\n        now_usec = tv->tv_usec;\n    }\n}\n\n/*\n * Return a numerical string describing a struct timeval.\n */\nconst char *\ntv_string(const struct timeval *tv, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(64, gc);\n    buf_printf(&out, \"[%\" PRIi64 \"/%ld]\", (int64_t)tv->tv_sec, (long)tv->tv_usec);\n    return BSTR(&out);\n}\n\n/*\n * Return an ascii string describing an absolute\n * date/time in a struct timeval.\n *\n */\nconst char *\ntv_string_abs(const struct timeval *tv, struct gc_arena *gc)\n{\n    return time_string(tv->tv_sec, tv->tv_usec, true, gc);\n}\n\n/* format a time_t as ascii, or use current time if 0 */\n\nconst char *\ntime_string(time_t t, tv_usec_t usec, bool show_usec, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(64, gc);\n    struct timeval tv;\n\n    if (!t)\n    {\n        gettimeofday(&tv, NULL);\n        t = tv.tv_sec;\n        usec = tv.tv_usec;\n    }\n\n    struct tm *tm = localtime(&t);\n\n    buf_printf(&out, \"%04d-%02d-%02d %02d:%02d:%02d\", tm->tm_year + 1900, tm->tm_mon + 1,\n               tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);\n\n    if (show_usec && usec)\n    {\n        buf_printf(&out, \" us=%ld\", (long)usec);\n    }\n\n    return BSTR(&out);\n}\n\n/*\n * Limit the frequency of an event stream.\n *\n * Used to control maximum rate of new\n * incoming connections.\n */\n\nstruct frequency_limit *\nfrequency_limit_init(int max, int per)\n{\n    struct frequency_limit *f;\n\n    ASSERT(max >= 0 && per >= 0);\n\n    ALLOC_OBJ(f, struct frequency_limit);\n    f->max = max;\n    f->per = per;\n    f->n = 0;\n    f->reset = 0;\n    return f;\n}\n\nvoid\nfrequency_limit_free(struct frequency_limit *f)\n{\n    free(f);\n}\n\nbool\nfrequency_limit_event_allowed(struct frequency_limit *f)\n{\n    if (f->per)\n    {\n        bool ret;\n        if (now >= f->reset + f->per)\n        {\n            f->reset = now;\n            f->n = 0;\n        }\n        ret = (++f->n <= f->max);\n        return ret;\n    }\n    else\n    {\n        return true;\n    }\n}\n\n#ifdef TIME_TEST\nvoid\ntime_test(void)\n{\n    struct timeval tv;\n    time_t t;\n    int i;\n    for (i = 0; i < 10000; ++i)\n    {\n        t = time(NULL);\n        gettimeofday(&tv, NULL);\n#if 1\n        msg(M_INFO, \"t=%\" PRIi64 \" s=%\" PRIi64 \" us=%ld\", (int64_t)t, (int64_t)tv.tv_sec,\n            (long)tv.tv_usec);\n#endif\n    }\n}\n#endif\n"
  },
  {
    "path": "src/openvpn/otime.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef OTIME_H\n#define OTIME_H\n\n#include \"common.h\"\n#include \"integer.h\"\n#include \"buffer.h\"\n\n#ifdef _WIN32\ntypedef long tv_sec_t;\ntypedef long tv_usec_t;\n#else\ntypedef time_t tv_sec_t;\ntypedef suseconds_t tv_usec_t;\n#endif\n\nstruct frequency_limit\n{\n    int max;\n    int per;\n    int n;\n    time_t reset;\n};\n\nstruct frequency_limit *frequency_limit_init(int max, int per);\n\nvoid frequency_limit_free(struct frequency_limit *f);\n\nbool frequency_limit_event_allowed(struct frequency_limit *f);\n\n/* format a time_t as ascii, or use current time if 0 */\nconst char *time_string(time_t t, tv_usec_t usec, bool show_usec, struct gc_arena *gc);\n\n/* struct timeval functions */\n\nconst char *tv_string(const struct timeval *tv, struct gc_arena *gc);\n\nconst char *tv_string_abs(const struct timeval *tv, struct gc_arena *gc);\n\nextern time_t now; /* updated frequently to time(NULL) */\n\nvoid time_test(void);\n\nvoid update_now(const time_t system_time);\n\nextern time_t now_usec;\nvoid update_now_usec(struct timeval *tv);\n\nstatic inline int\nopenvpn_gettimeofday(struct timeval *tv, void *tz)\n{\n    const int status = gettimeofday(tv, tz);\n    if (!status)\n    {\n        update_now_usec(tv);\n        tv->tv_sec = (tv_sec_t)now;\n        tv->tv_usec = (tv_usec_t)now_usec;\n    }\n    return status;\n}\n\nstatic inline void\nupdate_time(void)\n{\n#ifdef _WIN32\n    /* on _WIN32, gettimeofday is faster than time(NULL) */\n    struct timeval tv;\n    openvpn_gettimeofday(&tv, NULL);\n#else\n    update_now(time(NULL));\n    now_usec = 0;\n#endif\n}\n\nstatic inline time_t\nopenvpn_time(time_t *t)\n{\n    update_time();\n    if (t)\n    {\n        *t = now;\n    }\n    return now;\n}\n\nstatic inline void\ntv_clear(struct timeval *tv)\n{\n    tv->tv_sec = 0;\n    tv->tv_usec = 0;\n}\n\nstatic inline bool\ntv_defined(const struct timeval *tv)\n{\n    return tv->tv_sec > 0 && tv->tv_usec > 0;\n}\n\n/* return tv1 - tv2 in usec, constrained by max_seconds */\nstatic inline int\ntv_subtract(const struct timeval *tv1, const struct timeval *tv2, const int max_seconds)\n{\n    const int max_usec = max_seconds * 1000000;\n    const tv_sec_t sec_diff = tv1->tv_sec - tv2->tv_sec;\n\n    if (sec_diff > (max_seconds + 10))\n    {\n        return max_usec;\n    }\n    else if (sec_diff < -(max_seconds + 10))\n    {\n        return -max_usec;\n    }\n    const time_t complete_diff = sec_diff * 1000000 + (tv1->tv_usec - tv2->tv_usec);\n    return constrain_int((int)complete_diff, -max_usec, max_usec);\n}\n\nstatic inline void\ntv_add(struct timeval *dest, const struct timeval *src)\n{\n    dest->tv_sec += src->tv_sec;\n    dest->tv_usec += src->tv_usec;\n    dest->tv_sec += (dest->tv_usec >> 20);\n    dest->tv_usec &= 0x000FFFFF;\n    if (dest->tv_usec >= 1000000)\n    {\n        dest->tv_usec -= 1000000;\n        dest->tv_sec += 1;\n    }\n}\n\nstatic inline bool\ntv_lt(const struct timeval *t1, const struct timeval *t2)\n{\n    if (t1->tv_sec < t2->tv_sec)\n    {\n        return true;\n    }\n    else if (t1->tv_sec > t2->tv_sec)\n    {\n        return false;\n    }\n    else\n    {\n        return t1->tv_usec < t2->tv_usec;\n    }\n}\n\nstatic inline bool\ntv_le(const struct timeval *t1, const struct timeval *t2)\n{\n    if (t1->tv_sec < t2->tv_sec)\n    {\n        return true;\n    }\n    else if (t1->tv_sec > t2->tv_sec)\n    {\n        return false;\n    }\n    else\n    {\n        return t1->tv_usec <= t2->tv_usec;\n    }\n}\n\nstatic inline bool\ntv_ge(const struct timeval *t1, const struct timeval *t2)\n{\n    if (t1->tv_sec > t2->tv_sec)\n    {\n        return true;\n    }\n    else if (t1->tv_sec < t2->tv_sec)\n    {\n        return false;\n    }\n    else\n    {\n        return t1->tv_usec >= t2->tv_usec;\n    }\n}\n\nstatic inline bool\ntv_gt(const struct timeval *t1, const struct timeval *t2)\n{\n    if (t1->tv_sec > t2->tv_sec)\n    {\n        return true;\n    }\n    else if (t1->tv_sec < t2->tv_sec)\n    {\n        return false;\n    }\n    else\n    {\n        return t1->tv_usec > t2->tv_usec;\n    }\n}\n\nstatic inline bool\ntv_eq(const struct timeval *t1, const struct timeval *t2)\n{\n    return t1->tv_sec == t2->tv_sec && t1->tv_usec == t2->tv_usec;\n}\n\nstatic inline void\ntv_delta(struct timeval *dest, const struct timeval *t1, const struct timeval *t2)\n{\n    tv_sec_t sec = t2->tv_sec - t1->tv_sec;\n    tv_usec_t usec = t2->tv_usec - t1->tv_usec;\n\n    while (usec < 0)\n    {\n        usec += 1000000;\n        sec -= 1;\n    }\n\n    if (sec < 0)\n    {\n        usec = sec = 0;\n    }\n\n    dest->tv_sec = sec;\n    dest->tv_usec = usec;\n}\n\n#define TV_WITHIN_SIGMA_MAX_SEC  600\n#define TV_WITHIN_SIGMA_MAX_USEC (TV_WITHIN_SIGMA_MAX_SEC * 1000000)\n\n/*\n * Is t1 and t2 within sigma microseconds of each other?\n */\nstatic inline bool\ntv_within_sigma(const struct timeval *t1, const struct timeval *t2, unsigned int sigma)\n{\n    /* sigma should be less than 10 minutes */\n    const int delta = tv_subtract(t1, t2, TV_WITHIN_SIGMA_MAX_SEC);\n    return -(int)sigma <= delta && delta <= (int)sigma;\n}\n\n/*\n * Used to determine in how many seconds we should be\n * called again.\n */\nstatic inline void\ninterval_earliest_wakeup(interval_t *wakeup, time_t at, time_t current)\n{\n    if (at > current)\n    {\n        const interval_t delta = (interval_t)(at - current);\n        if (delta < *wakeup)\n        {\n            *wakeup = delta;\n        }\n        if (*wakeup < 0)\n        {\n            *wakeup = 0;\n        }\n    }\n}\n\n#endif /* ifndef OTIME_H */\n"
  },
  {
    "path": "src/openvpn/ovpn_dco_freebsd.h",
    "content": "/*-\n * SPDX-License-Identifier: BSD-2-Clause-FreeBSD\n *\n * Copyright (c) 2021 Rubicon Communications, LLC (Netgate)\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#ifndef _NET_IF_OVPN_H_\n#define _NET_IF_OVPN_H_\n\n#include <sys/types.h>\n#include <netinet/in.h>\n\n/* Maximum size of an ioctl request. */\n#define OVPN_MAX_REQUEST_SIZE 4096\n\nenum ovpn_notif_type\n{\n    OVPN_NOTIF_DEL_PEER,\n    OVPN_NOTIF_ROTATE_KEY,\n    OVPN_NOTIF_FLOAT,\n};\n\nenum ovpn_del_reason\n{\n    OVPN_DEL_REASON_REQUESTED = 0,\n    OVPN_DEL_REASON_TIMEOUT = 1\n};\n\nenum ovpn_key_slot\n{\n    OVPN_KEY_SLOT_PRIMARY = 0,\n    OVPN_KEY_SLOT_SECONDARY = 1\n};\n\nenum ovpn_key_cipher\n{\n    OVPN_CIPHER_ALG_NONE = 0,\n    OVPN_CIPHER_ALG_AES_GCM = 1,\n    OVPN_CIPHER_ALG_CHACHA20_POLY1305 = 2\n};\n\n#define OVPN_NEW_PEER       _IO('D', 1)\n#define OVPN_DEL_PEER       _IO('D', 2)\n#define OVPN_GET_STATS      _IO('D', 3)\n#define OVPN_NEW_KEY        _IO('D', 4)\n#define OVPN_SWAP_KEYS      _IO('D', 5)\n#define OVPN_DEL_KEY        _IO('D', 6)\n#define OVPN_SET_PEER       _IO('D', 7)\n#define OVPN_START_VPN      _IO('D', 8)\n#define OVPN_SEND_PKT       _IO('D', 9)\n#define OVPN_POLL_PKT       _IO('D', 10)\n#define OVPN_GET_PKT        _IO('D', 11)\n#define OVPN_SET_IFMODE     _IO('D', 12)\n#define OVPN_GET_PEER_STATS _IO('D', 13)\n\n#endif /* ifndef _NET_IF_OVPN_H_ */\n"
  },
  {
    "path": "src/openvpn/ovpn_dco_linux.h",
    "content": "/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */\n/* Do not edit directly, auto-generated from: */\n/*\tDocumentation/netlink/specs/ovpn.yaml */\n/* YNL-GEN uapi header */\n\n#ifndef _UAPI_LINUX_OVPN_H\n#define _UAPI_LINUX_OVPN_H\n\n#define OVPN_FAMILY_NAME\t\"ovpn\"\n#define OVPN_FAMILY_VERSION\t1\n\n#define OVPN_NONCE_TAIL_SIZE\t8\n\nenum ovpn_cipher_alg {\n\tOVPN_CIPHER_ALG_NONE,\n\tOVPN_CIPHER_ALG_AES_GCM,\n\tOVPN_CIPHER_ALG_CHACHA20_POLY1305,\n};\n\nenum ovpn_del_peer_reason {\n\tOVPN_DEL_PEER_REASON_TEARDOWN,\n\tOVPN_DEL_PEER_REASON_USERSPACE,\n\tOVPN_DEL_PEER_REASON_EXPIRED,\n\tOVPN_DEL_PEER_REASON_TRANSPORT_ERROR,\n\tOVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT,\n};\n\nenum ovpn_key_slot {\n\tOVPN_KEY_SLOT_PRIMARY,\n\tOVPN_KEY_SLOT_SECONDARY,\n};\n\nenum {\n\tOVPN_A_PEER_ID = 1,\n\tOVPN_A_PEER_REMOTE_IPV4,\n\tOVPN_A_PEER_REMOTE_IPV6,\n\tOVPN_A_PEER_REMOTE_IPV6_SCOPE_ID,\n\tOVPN_A_PEER_REMOTE_PORT,\n\tOVPN_A_PEER_SOCKET,\n\tOVPN_A_PEER_SOCKET_NETNSID,\n\tOVPN_A_PEER_VPN_IPV4,\n\tOVPN_A_PEER_VPN_IPV6,\n\tOVPN_A_PEER_LOCAL_IPV4,\n\tOVPN_A_PEER_LOCAL_IPV6,\n\tOVPN_A_PEER_LOCAL_PORT,\n\tOVPN_A_PEER_KEEPALIVE_INTERVAL,\n\tOVPN_A_PEER_KEEPALIVE_TIMEOUT,\n\tOVPN_A_PEER_DEL_REASON,\n\tOVPN_A_PEER_VPN_RX_BYTES,\n\tOVPN_A_PEER_VPN_TX_BYTES,\n\tOVPN_A_PEER_VPN_RX_PACKETS,\n\tOVPN_A_PEER_VPN_TX_PACKETS,\n\tOVPN_A_PEER_LINK_RX_BYTES,\n\tOVPN_A_PEER_LINK_TX_BYTES,\n\tOVPN_A_PEER_LINK_RX_PACKETS,\n\tOVPN_A_PEER_LINK_TX_PACKETS,\n\n\t__OVPN_A_PEER_MAX,\n\tOVPN_A_PEER_MAX = (__OVPN_A_PEER_MAX - 1)\n};\n\nenum {\n\tOVPN_A_KEYCONF_PEER_ID = 1,\n\tOVPN_A_KEYCONF_SLOT,\n\tOVPN_A_KEYCONF_KEY_ID,\n\tOVPN_A_KEYCONF_CIPHER_ALG,\n\tOVPN_A_KEYCONF_ENCRYPT_DIR,\n\tOVPN_A_KEYCONF_DECRYPT_DIR,\n\n\t__OVPN_A_KEYCONF_MAX,\n\tOVPN_A_KEYCONF_MAX = (__OVPN_A_KEYCONF_MAX - 1)\n};\n\nenum {\n\tOVPN_A_KEYDIR_CIPHER_KEY = 1,\n\tOVPN_A_KEYDIR_NONCE_TAIL,\n\n\t__OVPN_A_KEYDIR_MAX,\n\tOVPN_A_KEYDIR_MAX = (__OVPN_A_KEYDIR_MAX - 1)\n};\n\nenum {\n\tOVPN_A_IFINDEX = 1,\n\tOVPN_A_PEER,\n\tOVPN_A_KEYCONF,\n\n\t__OVPN_A_MAX,\n\tOVPN_A_MAX = (__OVPN_A_MAX - 1)\n};\n\nenum {\n\tOVPN_CMD_PEER_NEW = 1,\n\tOVPN_CMD_PEER_SET,\n\tOVPN_CMD_PEER_GET,\n\tOVPN_CMD_PEER_DEL,\n\tOVPN_CMD_PEER_DEL_NTF,\n\tOVPN_CMD_KEY_NEW,\n\tOVPN_CMD_KEY_GET,\n\tOVPN_CMD_KEY_SWAP,\n\tOVPN_CMD_KEY_SWAP_NTF,\n\tOVPN_CMD_KEY_DEL,\n\tOVPN_CMD_PEER_FLOAT_NTF,\n\n\t__OVPN_CMD_MAX,\n\tOVPN_CMD_MAX = (__OVPN_CMD_MAX - 1)\n};\n\n#define OVPN_MCGRP_PEERS\t\"peers\"\n\n#endif /* _UAPI_LINUX_OVPN_H */\n"
  },
  {
    "path": "src/openvpn/ovpn_dco_win.h",
    "content": "/*\n *  ovpn-dco-win OpenVPN protocol accelerator for Windows\n *\n *  Copyright (C) 2020-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  Author:\tLev Stipakov <lev@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n *\n *  This particular file (uapi.h) is also licensed using the MIT license (see COPYRIGHT.MIT).\n */\n\n#pragma once\n#ifndef _KERNEL_MODE\n#include <winsock2.h>\n#endif\n#include <ws2def.h>\n#include <ws2ipdef.h>\n\ntypedef enum {\n\tOVPN_PROTO_UDP,\n\tOVPN_PROTO_TCP\n} OVPN_PROTO;\n\ntypedef struct _OVPN_NEW_PEER {\n\tunion {\n\t\tSOCKADDR_IN Addr4;\n\t\tSOCKADDR_IN6 Addr6;\n\t} Local;\n\n\tunion {\n\t\tSOCKADDR_IN Addr4;\n\t\tSOCKADDR_IN6 Addr6;\n\t} Remote;\n\n\tOVPN_PROTO Proto;\n} OVPN_NEW_PEER, * POVPN_NEW_PEER;\n\ntypedef struct _OVPN_MP_NEW_PEER {\n    union {\n        SOCKADDR_IN Addr4;\n        SOCKADDR_IN6 Addr6;\n    } Local;\n\n    union {\n        SOCKADDR_IN Addr4;\n        SOCKADDR_IN6 Addr6;\n    } Remote;\n\n    IN_ADDR VpnAddr4;\n    IN6_ADDR VpnAddr6;\n\n    int PeerId;\n} OVPN_MP_NEW_PEER, * POVPN_MP_NEW_PEER;\n\ntypedef struct _OVPN_STATS {\n\tLONG LostInControlPackets;\n\tLONG LostOutControlPackets;\n\n\tLONG LostInDataPackets;\n\tLONG LostOutDataPackets;\n\n\tLONG ReceivedDataPackets;\n\tLONG ReceivedControlPackets;\n\n\tLONG SentControlPackets;\n\tLONG SentDataPackets;\n\n\tLONG64 TransportBytesSent;\n\tLONG64 TransportBytesReceived;\n\n\tLONG64 TunBytesSent;\n\tLONG64 TunBytesReceived;\n} OVPN_STATS, * POVPN_STATS;\n\ntypedef struct _OVPN_PEER_STATS {\n    int PeerId;\n    LONG64 LinkRxBytes;\n    LONG64 LinkTxBytes;\n    LONG64 VpnRxBytes;\n    LONG64 VpnTxBytes;\n} OVPN_PEER_STATS, * POVPN_PEER_STATS;\n\ntypedef enum _OVPN_KEY_SLOT {\n\tOVPN_KEY_SLOT_PRIMARY,\n\tOVPN_KEY_SLOT_SECONDARY\n} OVPN_KEY_SLOT;\n\ntypedef enum _OVPN_CIPHER_ALG {\n\tOVPN_CIPHER_ALG_NONE,\n\tOVPN_CIPHER_ALG_AES_GCM,\n\tOVPN_CIPHER_ALG_CHACHA20_POLY1305\n} OVPN_CIPHER_ALG;\n\ntypedef struct _OVPN_KEY_DIRECTION\n{\n\tunsigned char Key[32];\n\tunsigned char KeyLen; // 16/24/32 -> AES-128-GCM/AES-192-GCM/AES-256-GCM\n\tunsigned char NonceTail[8];\n} OVPN_KEY_DIRECTION;\n\ntypedef struct _OVPN_CRYPTO_DATA {\n\tOVPN_KEY_DIRECTION Encrypt;\n\tOVPN_KEY_DIRECTION Decrypt;\n\tOVPN_KEY_SLOT KeySlot;\n\tOVPN_CIPHER_ALG CipherAlg;\n\tunsigned char KeyId;\n\tint PeerId;\n} OVPN_CRYPTO_DATA, * POVPN_CRYPTO_DATA;\n\n#define CRYPTO_OPTIONS_EPOCH (1<<1)\n\ntypedef struct _OVPN_CRYPTO_DATA_V2 {\n    OVPN_CRYPTO_DATA V1;\n    UINT32 CryptoOptions;\n} OVPN_CRYPTO_DATA_V2, * POVPN_CRYPTO_DATA_V2;\n\ntypedef struct _OVPN_MP_SET_PEER {\n    int PeerId;\n    LONG KeepaliveInterval;\n    LONG KeepaliveTimeout;\n    LONG MSS;\n} OVPN_MP_SET_PEER, * POVPN_MP_SET_PEER;\n\ntypedef struct _OVPN_SET_PEER {\n    LONG KeepaliveInterval;\n    LONG KeepaliveTimeout;\n    LONG MSS;\n} OVPN_SET_PEER, * POVPN_SET_PEER;\n\ntypedef struct _OVPN_VERSION {\n    LONG Major;\n    LONG Minor;\n    LONG Patch;\n} OVPN_VERSION, * POVPN_VERSION;\n\ntypedef enum {\n    OVPN_MODE_P2P,\n    OVPN_MODE_MP\n} OVPN_MODE;\n\ntypedef struct _OVPN_SET_MODE {\n    OVPN_MODE Mode;\n} OVPN_SET_MODE, * POVPN_SET_MODE;\n\ntypedef struct _OVPN_MP_START_VPN {\n    union {\n        SOCKADDR_IN Addr4;\n        SOCKADDR_IN6 Addr6;\n    } ListenAddress;\n    int IPv6Only;\n} OVPN_MP_START_VPN, * POVPN_MP_START_VPN;\n\ntypedef enum {\n    OVPN_CMD_DEL_PEER,\n    OVPN_CMD_SWAP_KEYS,\n    OVPN_CMD_FLOAT_PEER\n} OVPN_NOTIFY_CMD;\n\ntypedef enum {\n    OVPN_DEL_PEER_REASON_TEARDOWN,\n    OVPN_DEL_PEER_REASON_USERSPACE,\n    OVPN_DEL_PEER_REASON_EXPIRED,\n    OVPN_DEL_PEER_REASON_TRANSPORT_ERROR,\n    OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT\n} OVPN_DEL_PEER_REASON;\n\ntypedef struct _OVPN_NOTIFY_EVENT {\n    OVPN_NOTIFY_CMD Cmd;\n    int PeerId;\n    OVPN_DEL_PEER_REASON DelPeerReason;\n    struct sockaddr_storage FloatAddress;\n} OVPN_NOTIFY_EVENT, * POVPN_NOTIFY_EVENT;\n\ntypedef struct _OVPN_MP_DEL_PEER {\n    int PeerId;\n} OVPN_MP_DEL_PEER, * POVPN_MP_DEL_PEER;\n\ntypedef struct _OVPN_MP_SWAP_KEYS {\n    int PeerId;\n} OVPN_MP_SWAP_KEYS, * POVPN_MP_SWAP_KEYS;\n\ntypedef struct _OVPN_MP_IROUTE {\n    union {\n        IN_ADDR Addr4;\n        IN6_ADDR Addr6;\n    } Addr;\n    int Netbits;\n    int PeerId;\n    int IPv6;\n} OVPN_MP_IROUTE, * POVPN_MP_IROUTE;\n\ntypedef struct _OVPN_GET_PEER_STATS {\n    int PeerId; // -1 for all peers stats\n} OVPN_GET_PEER_STATS, * POVPN_GET_PEER_STATS;\n\n#define OVPN_IOCTL_NEW_PEER     CTL_CODE(FILE_DEVICE_UNKNOWN, 1, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define OVPN_IOCTL_GET_STATS    CTL_CODE(FILE_DEVICE_UNKNOWN, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define OVPN_IOCTL_NEW_KEY      CTL_CODE(FILE_DEVICE_UNKNOWN, 3, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define OVPN_IOCTL_SWAP_KEYS    CTL_CODE(FILE_DEVICE_UNKNOWN, 4, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define OVPN_IOCTL_SET_PEER     CTL_CODE(FILE_DEVICE_UNKNOWN, 5, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define OVPN_IOCTL_START_VPN    CTL_CODE(FILE_DEVICE_UNKNOWN, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define OVPN_IOCTL_DEL_PEER     CTL_CODE(FILE_DEVICE_UNKNOWN, 7, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define OVPN_IOCTL_GET_VERSION  CTL_CODE(FILE_DEVICE_UNKNOWN, 8, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define OVPN_IOCTL_NEW_KEY_V2   CTL_CODE(FILE_DEVICE_UNKNOWN, 9, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define OVPN_IOCTL_SET_MODE     CTL_CODE(FILE_DEVICE_UNKNOWN, 10, METHOD_BUFFERED, FILE_ANY_ACCESS)\n\n#define OVPN_IOCTL_MP_START_VPN   CTL_CODE(FILE_DEVICE_UNKNOWN, 11, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define OVPN_IOCTL_MP_NEW_PEER    CTL_CODE(FILE_DEVICE_UNKNOWN, 12, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define OVPN_IOCTL_MP_SET_PEER    CTL_CODE(FILE_DEVICE_UNKNOWN, 13, METHOD_BUFFERED, FILE_ANY_ACCESS)\n\n#define OVPN_IOCTL_NOTIFY_EVENT   CTL_CODE(FILE_DEVICE_UNKNOWN, 14, METHOD_BUFFERED, FILE_ANY_ACCESS)\n\n#define OVPN_IOCTL_MP_DEL_PEER    CTL_CODE(FILE_DEVICE_UNKNOWN, 15, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define OVPN_IOCTL_MP_SWAP_KEYS   CTL_CODE(FILE_DEVICE_UNKNOWN, 16, METHOD_BUFFERED, FILE_ANY_ACCESS)\n\n#define OVPN_IOCTL_MP_ADD_IROUTE CTL_CODE(FILE_DEVICE_UNKNOWN, 17, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define OVPN_IOCTL_MP_DEL_IROUTE CTL_CODE(FILE_DEVICE_UNKNOWN, 18, METHOD_BUFFERED, FILE_ANY_ACCESS)\n\n#define OVPN_IOCTL_GET_PEER_STATS CTL_CODE(FILE_DEVICE_UNKNOWN, 19, METHOD_BUFFERED, FILE_ANY_ACCESS)\n"
  },
  {
    "path": "src/openvpn/packet_id.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * These routines are designed to catch replay attacks,\n * where a man-in-the-middle captures packets and then\n * attempts to replay them back later.\n *\n * We use the \"sliding-window\" algorithm, similar\n * to IPSec.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <stddef.h>\n\n#include \"packet_id.h\"\n#include \"misc.h\"\n#include \"integer.h\"\n\n#include \"memdbg.h\"\n\n/* #define PID_SIMULATE_BACKTRACK */\n\n/*\n * Special time_t value that indicates that\n * sequence number has expired.\n */\n#define SEQ_UNSEEN  ((time_t)0)\n#define SEQ_EXPIRED ((time_t)1)\n\n#ifdef ENABLE_DEBUG\nstatic void packet_id_debug_print(msglvl_t msglevel, const struct packet_id_rec *p,\n                                  const struct packet_id_net *pin, const char *message,\n                                  packet_id_print_type value);\n\n#endif /* ENABLE_DEBUG */\n\nstatic inline void\npacket_id_debug(msglvl_t msglevel, const struct packet_id_rec *p,\n                const struct packet_id_net *pin, const char *message, uint64_t value)\n{\n#ifdef ENABLE_DEBUG\n    if (unlikely(check_debug_level(msglevel)))\n    {\n        packet_id_debug_print(msglevel, p, pin, message, value);\n    }\n#endif\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\nstatic void\npacket_id_init_recv(struct packet_id_rec *rec, int seq_backtrack, int time_backtrack,\n                    const char *name, int unit)\n{\n    rec->name = name;\n    rec->unit = unit;\n    if (seq_backtrack)\n    {\n        ASSERT(MIN_SEQ_BACKTRACK <= seq_backtrack && seq_backtrack <= MAX_SEQ_BACKTRACK);\n        ASSERT(MIN_TIME_BACKTRACK <= time_backtrack && time_backtrack <= MAX_TIME_BACKTRACK);\n        CIRC_LIST_ALLOC(rec->seq_list, struct seq_list, seq_backtrack);\n        rec->seq_backtrack = seq_backtrack;\n        rec->time_backtrack = time_backtrack;\n    }\n    rec->initialized = true;\n}\nvoid\npacket_id_init(struct packet_id *p, int seq_backtrack, int time_backtrack, const char *name,\n               int unit)\n{\n    dmsg(D_PID_DEBUG, \"PID packet_id_init seq_backtrack=%d time_backtrack=%d\", seq_backtrack,\n         time_backtrack);\n\n    ASSERT(p);\n    CLEAR(*p);\n\n    packet_id_init_recv(&p->rec, seq_backtrack, time_backtrack, name, unit);\n}\n\nvoid\npacket_id_move_recv(struct packet_id_rec *dest, struct packet_id_rec *src)\n{\n    ASSERT(src);\n    ASSERT(dest);\n    /* clear free any old data in rec list */\n    free(dest->seq_list);\n    CLEAR(*dest);\n\n    /* Copy data to dest */\n    *dest = *src;\n\n    /* Reinitalise the source */\n    CLEAR(*src);\n    packet_id_init_recv(src, dest->seq_backtrack, dest->time_backtrack, dest->name, dest->unit);\n}\n\nvoid\npacket_id_free(struct packet_id *p)\n{\n    if (p)\n    {\n        dmsg(D_PID_DEBUG, \"PID packet_id_free\");\n        free(p->rec.seq_list);\n        CLEAR(*p);\n    }\n}\n\nvoid\npacket_id_add(struct packet_id_rec *p, const struct packet_id_net *pin)\n{\n    const time_t local_now = now;\n    if (p->seq_list)\n    {\n        int64_t diff;\n\n        /*\n         * If time value increases, start a new sequence list of number\n         * sequence for the new time point.\n         */\n        if (!CIRC_LIST_SIZE(p->seq_list) || pin->time > p->time\n            || (pin->id >= p->seq_backtrack && pin->id - p->seq_backtrack > p->id))\n        {\n            p->time = pin->time;\n            p->id = 0;\n            if (pin->id > p->seq_backtrack)\n            {\n                p->id = pin->id - p->seq_backtrack;\n            }\n            CIRC_LIST_RESET(p->seq_list);\n        }\n\n        while (p->id < pin->id\n#ifdef PID_SIMULATE_BACKTRACK\n               || (get_random() % 64) < 31\n#endif\n        )\n        {\n            CIRC_LIST_PUSH(p->seq_list, SEQ_UNSEEN);\n            ++p->id;\n        }\n\n        diff = p->id - pin->id;\n        if (diff < CIRC_LIST_SIZE(p->seq_list) && local_now > SEQ_EXPIRED)\n        {\n            CIRC_LIST_ITEM(p->seq_list, diff) = local_now;\n        }\n    }\n    else\n    {\n        p->time = pin->time;\n        p->id = pin->id;\n    }\n}\n\n/*\n * Expire sequence numbers which can no longer\n * be accepted because they would violate\n * time_backtrack.\n */\nvoid\npacket_id_reap(struct packet_id_rec *p)\n{\n    const time_t local_now = now;\n    if (p->time_backtrack)\n    {\n        bool expire = false;\n        for (int i = 0; i < CIRC_LIST_SIZE(p->seq_list); ++i)\n        {\n            const time_t t = CIRC_LIST_ITEM(p->seq_list, i);\n            if (t == SEQ_EXPIRED)\n            {\n                break;\n            }\n            if (!expire && t && t + p->time_backtrack < local_now)\n            {\n                expire = true;\n            }\n            if (expire)\n            {\n                CIRC_LIST_ITEM(p->seq_list, i) = SEQ_EXPIRED;\n            }\n        }\n    }\n    p->last_reap = local_now;\n}\n\n/*\n * Return true if packet id is ok, or false if\n * it is a replay.\n */\nbool\npacket_id_test(struct packet_id_rec *p, const struct packet_id_net *pin)\n{\n    uint64_t diff;\n\n    packet_id_debug(D_PID_DEBUG, p, pin, \"PID_TEST\", 0);\n\n    ASSERT(p->initialized);\n\n    if (!pin->id)\n    {\n        return false;\n    }\n\n    if (p->seq_backtrack)\n    {\n        /*\n         * In backtrack mode, we allow packet reordering subject\n         * to the seq_backtrack and time_backtrack constraints.\n         *\n         * This mode is used with UDP.\n         */\n        if (pin->time == p->time)\n        {\n            /* is packet-id greater than any one we've seen yet? */\n            if (pin->id > p->id)\n            {\n                return true;\n            }\n\n            /* check packet-id sliding window for original/replay status */\n            diff = p->id - pin->id;\n\n            /* keep track of maximum backtrack seen for debugging purposes */\n            if (diff > p->max_backtrack_stat)\n            {\n                p->max_backtrack_stat = diff;\n                packet_id_debug(D_PID_DEBUG_LOW, p, pin, \"PID_ERR replay-window backtrack occurred\",\n                                p->max_backtrack_stat);\n            }\n\n            if (diff >= (packet_id_type)CIRC_LIST_SIZE(p->seq_list))\n            {\n                packet_id_debug(D_PID_DEBUG_LOW, p, pin, \"PID_ERR large diff\", diff);\n                return false;\n            }\n\n            {\n                const time_t v = CIRC_LIST_ITEM(p->seq_list, diff);\n                if (v == 0)\n                {\n                    return true;\n                }\n                else\n                {\n                    /* raised from D_PID_DEBUG_LOW to reduce verbosity */\n                    packet_id_debug(D_PID_DEBUG_MEDIUM, p, pin, \"PID_ERR replay\", diff);\n                    return false;\n                }\n            }\n        }\n        else if (pin->time < p->time) /* if time goes back, reject */\n        {\n            packet_id_debug(D_PID_DEBUG_LOW, p, pin, \"PID_ERR time backtrack\", 0);\n            return false;\n        }\n        else /* time moved forward */\n        {\n            return true;\n        }\n    }\n    else\n    {\n        /*\n         * In non-backtrack mode, all sequence number series must\n         * begin at some number n > 0 and must increment linearly without gaps.\n         *\n         * This mode is used with TCP.\n         */\n        if (pin->time == p->time)\n        {\n            return !p->id || pin->id == p->id + 1;\n        }\n        else if (pin->time < p->time) /* if time goes back, reject */\n        {\n            return false;\n        }\n        else /* time moved forward */\n        {\n            return pin->id == 1;\n        }\n    }\n}\n\n/*\n * Read/write a packet ID to/from the buffer.  Short form is sequence number\n * only.  Long form is sequence number and timestamp.\n */\n\nbool\npacket_id_read(struct packet_id_net *pin, struct buffer *buf, bool long_form)\n{\n    packet_id_type net_id;\n    net_time_t net_time;\n\n    pin->id = 0;\n    pin->time = 0;\n\n    if (!buf_read(buf, &net_id, sizeof(net_id)))\n    {\n        return false;\n    }\n    pin->id = ntohpid(net_id);\n    if (long_form)\n    {\n        if (!buf_read(buf, &net_time, sizeof(net_time)))\n        {\n            return false;\n        }\n        pin->time = ntohtime(net_time);\n    }\n    return true;\n}\n\nstatic bool\npacket_id_send_update(struct packet_id_send *p, bool long_form)\n{\n    if (!p->time)\n    {\n        p->time = now;\n    }\n    if (p->id == PACKET_ID_MAX)\n    {\n        /* Packet ID only allowed to roll over if using long form and time has\n         * moved forward since last roll over.\n         */\n        if (!long_form || now <= p->time)\n        {\n            return false;\n        }\n        p->time = now;\n        p->id = 0;\n    }\n    p->id++;\n    return true;\n}\n\nstatic bool\npacket_id_send_update_epoch(struct packet_id_send *p)\n{\n    if (!p->time)\n    {\n        p->time = now;\n    }\n    if (p->id == PACKET_ID_EPOCH_MAX)\n    {\n        return false;\n    }\n    p->id++;\n    return true;\n}\n\nbool\npacket_id_write(struct packet_id_send *p, struct buffer *buf, bool long_form, bool prepend)\n{\n    if (!packet_id_send_update(p, long_form))\n    {\n        return false;\n    }\n\n    const packet_id_type net_id = htonpid(p->id);\n    const net_time_t net_time = htontime(p->time);\n    if (prepend)\n    {\n        if (long_form)\n        {\n            if (!buf_write_prepend(buf, &net_time, sizeof(net_time)))\n            {\n                return false;\n            }\n        }\n        if (!buf_write_prepend(buf, &net_id, sizeof(net_id)))\n        {\n            return false;\n        }\n    }\n    else\n    {\n        if (!buf_write(buf, &net_id, sizeof(net_id)))\n        {\n            return false;\n        }\n        if (long_form)\n        {\n            if (!buf_write(buf, &net_time, sizeof(net_time)))\n            {\n                return false;\n            }\n        }\n    }\n    return true;\n}\n\nconst char *\npacket_id_net_print(const struct packet_id_net *pin, bool print_timestamp, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n\n    buf_printf(&out, \"[ #\" packet_id_format, (packet_id_print_type)pin->id);\n    if (print_timestamp && pin->time)\n    {\n        buf_printf(&out, \" / time = (\" packet_id_format \") %s\", (packet_id_print_type)pin->time,\n                   time_string(pin->time, 0, false, gc));\n    }\n\n    buf_printf(&out, \" ]\");\n    return BSTR(&out);\n}\n\n/* initialize the packet_id_persist structure in a disabled state */\nvoid\npacket_id_persist_init(struct packet_id_persist *p)\n{\n    p->filename = NULL;\n    p->fd = -1;\n    p->time = p->time_last_written = 0;\n    p->id = p->id_last_written = 0;\n}\n\n/* close the file descriptor if it is open, and switch to disabled state */\nvoid\npacket_id_persist_close(struct packet_id_persist *p)\n{\n    if (packet_id_persist_enabled(p))\n    {\n        if (close(p->fd))\n        {\n            msg(D_PID_PERSIST | M_ERRNO, \"Close error on --replay-persist file %s\", p->filename);\n        }\n        packet_id_persist_init(p);\n    }\n}\n\n/* load persisted rec packet_id (time and id) only once from file, and set state to enabled */\nvoid\npacket_id_persist_load(struct packet_id_persist *p, const char *filename)\n{\n    struct gc_arena gc = gc_new();\n    if (!packet_id_persist_enabled(p))\n    {\n        /* open packet-id persist file for both read and write */\n        p->fd = platform_open(filename, O_CREAT | O_RDWR | O_BINARY, S_IRUSR | S_IWUSR);\n        if (p->fd == -1)\n        {\n            msg(D_PID_PERSIST | M_ERRNO, \"Cannot open --replay-persist file %s for read/write\",\n                filename);\n        }\n        else\n        {\n            struct packet_id_persist_file_image image;\n            ssize_t n;\n\n#if defined(HAVE_FLOCK) && defined(LOCK_EX) && defined(LOCK_NB)\n            if (flock(p->fd, LOCK_EX | LOCK_NB))\n            {\n                msg(M_ERR, \"Cannot obtain exclusive lock on --replay-persist file %s\", filename);\n            }\n#endif\n\n            p->filename = filename;\n            n = read(p->fd, &image, sizeof(image));\n            if (n == sizeof(image))\n            {\n                p->time = p->time_last_written = image.time;\n                p->id = p->id_last_written = image.id;\n                dmsg(D_PID_PERSIST_DEBUG, \"PID Persist Read from %s: %s\", p->filename,\n                     packet_id_persist_print(p, &gc));\n            }\n            else if (n == -1)\n            {\n                msg(D_PID_PERSIST | M_ERRNO, \"Read error on --replay-persist file %s\", p->filename);\n            }\n        }\n    }\n    gc_free(&gc);\n}\n\n/* save persisted rec packet_id (time and id) to file (only if enabled state) */\nvoid\npacket_id_persist_save(struct packet_id_persist *p)\n{\n    if (packet_id_persist_enabled(p) && p->time\n        && (p->time != p->time_last_written || p->id != p->id_last_written))\n    {\n        struct packet_id_persist_file_image image;\n        CLEAR(image);\n        ssize_t n;\n        off_t seek_ret;\n        struct gc_arena gc = gc_new();\n\n        image.time = p->time;\n        image.id = p->id;\n        seek_ret = lseek(p->fd, (off_t)0, SEEK_SET);\n        if (seek_ret == (off_t)0)\n        {\n            n = write(p->fd, &image, sizeof(image));\n            if (n == sizeof(image))\n            {\n                p->time_last_written = p->time;\n                p->id_last_written = p->id;\n                dmsg(D_PID_PERSIST_DEBUG, \"PID Persist Write to %s: %s\", p->filename,\n                     packet_id_persist_print(p, &gc));\n            }\n            else\n            {\n                msg(D_PID_PERSIST | M_ERRNO, \"Cannot write to --replay-persist file %s\",\n                    p->filename);\n            }\n        }\n        else\n        {\n            msg(D_PID_PERSIST | M_ERRNO, \"Cannot seek to beginning of --replay-persist file %s\",\n                p->filename);\n        }\n        gc_free(&gc);\n    }\n}\n\n/* transfer packet_id_persist -> packet_id */\nvoid\npacket_id_persist_load_obj(const struct packet_id_persist *p, struct packet_id *pid)\n{\n    if (p && pid && packet_id_persist_enabled(p) && p->time)\n    {\n        pid->rec.time = p->time;\n        pid->rec.id = p->id;\n    }\n}\n\nconst char *\npacket_id_persist_print(const struct packet_id_persist *p, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n\n    buf_printf(&out, \"[\");\n\n    if (packet_id_persist_enabled(p))\n    {\n        buf_printf(&out, \" #\" packet_id_format, (packet_id_print_type)p->id);\n        if (p->time)\n        {\n            buf_printf(&out, \" / time = (\" packet_id_format \") %s\", (packet_id_print_type)p->time,\n                       time_string(p->time, 0, false, gc));\n        }\n    }\n\n    buf_printf(&out, \" ]\");\n    return (char *)out.data;\n}\n\n#ifdef ENABLE_DEBUG\n\nstatic void\npacket_id_debug_print(msglvl_t msglevel, const struct packet_id_rec *p,\n                      const struct packet_id_net *pin,\n                      const char *message, packet_id_print_type value)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer out = alloc_buf_gc(256, &gc);\n    struct timeval tv;\n    const time_t prev_now = now;\n    const struct seq_list *sl = p->seq_list;\n    int i;\n\n    CLEAR(tv);\n    gettimeofday(&tv, NULL);\n\n    buf_printf(&out, \"%s [\" packet_id_format \"]\", message, value);\n    buf_printf(&out, \" [%s-%d] [\", p->name, p->unit);\n    for (i = 0; sl != NULL && i < sl->x_size; ++i)\n    {\n        char c;\n        time_t v;\n        int diff;\n\n        v = CIRC_LIST_ITEM(sl, i);\n        if (v == SEQ_UNSEEN)\n        {\n            c = '_';\n        }\n        else if (v == SEQ_EXPIRED)\n        {\n            c = 'E';\n        }\n        else\n        {\n            diff = (int)(prev_now - v);\n            if (diff < 0)\n            {\n                c = 'N';\n            }\n            else if (diff < 10)\n            {\n                c = (char)('0' + diff);\n            }\n            else\n            {\n                c = '>';\n            }\n        }\n        buf_printf(&out, \"%c\", c);\n    }\n    buf_printf(&out, \"] %\" PRIi64 \":\" packet_id_format, (int64_t)p->time, p->id);\n    if (pin)\n    {\n        buf_printf(&out, \" %\" PRIi64 \":\" packet_id_format, (int64_t)pin->time, pin->id);\n    }\n\n    buf_printf(&out, \" t=%\" PRIi64 \"[%d]\", (int64_t)prev_now, (int)(prev_now - tv.tv_sec));\n\n    buf_printf(&out, \" r=[%d,%\" PRIu64 \",%d,%\" PRIu64 \",%d]\", (int)(p->last_reap - tv.tv_sec),\n               p->seq_backtrack, p->time_backtrack, p->max_backtrack_stat, (int)p->initialized);\n    if (sl != NULL)\n    {\n        buf_printf(&out, \" sl=[%d,%d,%d,%d]\", sl->x_head, sl->x_size, sl->x_cap, sl->x_sizeof);\n    }\n\n\n    msg(msglevel, \"%s\", BSTR(&out));\n    gc_free(&gc);\n}\n\n#endif /* ifdef ENABLE_DEBUG */\n\nuint16_t\npacket_id_read_epoch(struct packet_id_net *pin, struct buffer *buf)\n{\n    uint64_t packet_id;\n\n\n    if (!buf_read(buf, &packet_id, sizeof(packet_id)))\n    {\n        return 0;\n    }\n\n    uint64_t id = ntohll(packet_id);\n    /* top most 16 bits */\n    uint16_t epoch = id >> 48;\n\n    pin->id = id & PACKET_ID_MASK;\n    return epoch;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nbool\npacket_id_write_epoch(struct packet_id_send *p, uint16_t epoch, struct buffer *buf)\n{\n    if (!packet_id_send_update_epoch(p))\n    {\n        return false;\n    }\n\n    /* Highest 16 bits of packet id is the epoch.\n     *\n     * The lower 48 bits are the per-epoch packet id counter. */\n    uint64_t net_id = ((uint64_t)epoch) << 48 | p->id;\n\n    /* convert to network order. This ensures that the highest bytes\n     * also become the first ones on the wire*/\n    net_id = htonll(net_id);\n\n    return buf_write(buf, &net_id, sizeof(net_id));\n}\n"
  },
  {
    "path": "src/openvpn/packet_id.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * These routines are designed to catch replay attacks,\n * where a man-in-the-middle captures packets and then\n * attempts to replay them back later.\n */\n\n#ifndef PACKET_ID_H\n#define PACKET_ID_H\n\n#include \"circ_list.h\"\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"otime.h\"\n\n/*\n * These are the types that members of a struct packet_id_net are converted\n * to for network transmission and for saving to a persistent file.\n *\n * Note: data epoch data uses a 64 bit packet ID\n * compromised of 16 bit epoch and 48 bit per-epoch packet counter.\n * These are ephemeral and are never saved to a file.\n */\ntypedef uint32_t packet_id_type;\n#define PACKET_ID_MAX       UINT32_MAX\n#define PACKET_ID_EPOCH_MAX 0x0000ffffffffffffull\n/** Mask of the bits that contain the 48-bit of the per-epoch packet\n * counter in the packet id*/\n#define PACKET_ID_MASK      0x0000ffffffffffffull\ntypedef uint32_t net_time_t;\n\n/*\n * In TLS mode, when a packet ID gets to this level,\n * start thinking about triggering a new\n * SSL/TLS handshake.\n */\n#define PACKET_ID_WRAP_TRIGGER 0xFF000000\n\n/* convert a packet_id_type from host to network order */\n#define htonpid(x) htonl(x)\n\n/* convert a packet_id_type from network to host order */\n#define ntohpid(x) ntohl(x)\n\n/* convert a time_t in host order to a net_time_t in network order */\n#define htontime(x) htonl((net_time_t)x)\n\n/* convert a net_time_t in network order to a time_t in host order */\n#define ntohtime(x) ((time_t)ntohl(x))\n\n\n/*\n * Printf formats for special types\n */\n#define packet_id_format \"%\" PRIu64\ntypedef uint64_t packet_id_print_type;\n\n/*\n * Maximum allowed backtrack in\n * sequence number due to packets arriving\n * out of order.\n */\n#define MIN_SEQ_BACKTRACK     0\n#define MAX_SEQ_BACKTRACK     65536\n#define DEFAULT_SEQ_BACKTRACK 64\n\n/*\n * Maximum allowed backtrack in\n * seconds due to packets arriving\n * out of order.\n */\n#define MIN_TIME_BACKTRACK     0\n#define MAX_TIME_BACKTRACK     600\n#define DEFAULT_TIME_BACKTRACK 15\n\n/*\n * Do a reap pass through the sequence number\n * array once every n seconds in order to\n * expire sequence numbers which can no longer\n * be accepted because they would violate\n * TIME_BACKTRACK.\n */\n#define SEQ_REAP_INTERVAL 5\n\nCIRC_LIST(seq_list, time_t);\n\n/*\n * This is the data structure we keep on the receiving side,\n * to check that no packet-id (i.e. sequence number + optional timestamp)\n * is accepted more than once.\n */\nstruct packet_id_rec\n{\n    time_t last_reap;            /* last call of packet_id_reap */\n    time_t time;                 /* highest time stamp received */\n    uint64_t id;                 /* highest sequence number received */\n    uint64_t seq_backtrack;      /* set from --replay-window */\n    int time_backtrack;          /* set from --replay-window */\n    uint64_t max_backtrack_stat; /* maximum backtrack seen so far */\n    bool initialized;            /* true if packet_id_init was called */\n    struct seq_list *seq_list;   /* packet-id \"memory\" */\n    const char *name;\n    int unit;\n};\n\n/*\n * file to facilitate cross-session persistence\n * of time/id\n */\nstruct packet_id_persist\n{\n    const char *filename;\n    int fd;\n    time_t time;       /* time stamp */\n    packet_id_type id; /* sequence number */\n    time_t time_last_written;\n    packet_id_type id_last_written;\n};\n\nstruct packet_id_persist_file_image\n{\n    time_t time;       /* time stamp */\n    packet_id_type id; /* sequence number */\n};\n\n/*\n * Keep a record of our current packet-id state\n * on the sending side.\n */\nstruct packet_id_send\n{\n    uint64_t id;\n    time_t time;\n};\n\n/*\n * Communicate packet-id over the wire.\n * A short packet-id is just a 32 bit\n * sequence number.  A long packet-id\n * includes a timestamp as well.\n *\n * An epoch packet-id is a 16 bit epoch\n * counter plus a 48 per-epoch packet-id.\n *\n *\n * Long packet-ids are used as IVs for\n * CFB/OFB ciphers and for control channel\n * messages.\n *\n * This data structure is always sent\n * over the net in network byte order,\n * by calling htonpid, ntohpid,\n * htontime, and ntohtime on the\n * data elements to change them\n * to and from standard sizes.\n *\n * In addition, time is converted to\n * a net_time_t before sending,\n * since openvpn always\n * uses a 32-bit time_t but some\n * 64 bit platforms use a\n * 64 bit time_t.\n */\n\n/**\n * Data structure for describing the packet id that is received/send to the\n * network. This struct does not match the on wire format.\n */\nstruct packet_id_net\n{\n    /* converted to packet_id_type on non-epoch data ids, does not contain\n     * the epoch but is a flat id */\n    uint64_t id;\n    time_t time; /* converted to net_time_t before transmission */\n};\n\nstruct packet_id\n{\n    struct packet_id_send send;\n    struct packet_id_rec rec;\n};\n\nvoid packet_id_init(struct packet_id *p, int seq_backtrack, int time_backtrack, const char *name,\n                    int unit);\n\nvoid packet_id_free(struct packet_id *p);\n\n/**\n * Move the packet id recv structure from \\c src to \\c dest. \\c src will\n * be reinitialised. \\c dest will be freed before the move.\n */\nvoid packet_id_move_recv(struct packet_id_rec *dest, struct packet_id_rec *src);\n\n/* should we accept an incoming packet id ? */\nbool packet_id_test(struct packet_id_rec *p, const struct packet_id_net *pin);\n\n/* change our current state to reflect an accepted packet id */\nvoid packet_id_add(struct packet_id_rec *p, const struct packet_id_net *pin);\n\n/* expire TIME_BACKTRACK sequence numbers */\nvoid packet_id_reap(struct packet_id_rec *p);\n\n/*\n * packet ID persistence\n */\n\n/* initialize the packet_id_persist structure in a disabled state */\nvoid packet_id_persist_init(struct packet_id_persist *p);\n\n/* close the file descriptor if it is open, and switch to disabled state */\nvoid packet_id_persist_close(struct packet_id_persist *p);\n\n/* load persisted rec packet_id (time and id) only once from file, and set state to enabled */\nvoid packet_id_persist_load(struct packet_id_persist *p, const char *filename);\n\n/* save persisted rec packet_id (time and id) to file (only if enabled state) */\nvoid packet_id_persist_save(struct packet_id_persist *p);\n\n/* transfer packet_id_persist -> packet_id */\nvoid packet_id_persist_load_obj(const struct packet_id_persist *p, struct packet_id *pid);\n\n/* return an ascii string representing a packet_id_persist object */\nconst char *packet_id_persist_print(const struct packet_id_persist *p, struct gc_arena *gc);\n\n/*\n * Read/write a packet ID to/from the buffer.  Short form is sequence number\n * only.  Long form is sequence number and timestamp.\n */\n\nbool packet_id_read(struct packet_id_net *pin, struct buffer *buf, bool long_form);\n\n/**\n * Write a packet ID to buf, and update the packet ID state.\n *\n * @param p             Packet ID state.\n * @param buf           Buffer to write the packet ID too\n * @param long_form     If true, also update and write time_t to buf\n * @param prepend       If true, prepend to buffer, otherwise append.\n *\n * @return true if successful, false otherwise.\n */\nbool packet_id_write(struct packet_id_send *p, struct buffer *buf, bool long_form, bool prepend);\n\n/*\n * Inline functions.\n */\n\n/** Is this struct packet_id initialized? */\nstatic inline bool\npacket_id_initialized(const struct packet_id *pid)\n{\n    return pid->rec.initialized;\n}\n\n/* are we in enabled state? */\nstatic inline bool\npacket_id_persist_enabled(const struct packet_id_persist *p)\n{\n    return p->fd >= 0;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\n/* transfer packet_id -> packet_id_persist */\nstatic inline void\npacket_id_persist_save_obj(struct packet_id_persist *p, const struct packet_id *pid)\n{\n    if (packet_id_persist_enabled(p) && pid->rec.time)\n    {\n        p->time = pid->rec.time;\n        p->id = pid->rec.id;\n    }\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n/**\n * Reset the current send packet id to its initial state.\n * Use very carefully (e.g. in the standalone reset packet context) to\n * avoid sending more than one packet with the same packet id (that is not\n * also a resend like the reset packet)\n *\n * @param p the packet structure to modify\n */\nstatic inline void\nreset_packet_id_send(struct packet_id_send *p)\n{\n    p->time = 0;\n    p->id = 0;\n}\n\nconst char *packet_id_net_print(const struct packet_id_net *pin, bool print_timestamp,\n                                struct gc_arena *gc);\n\nstatic inline int\npacket_id_size(bool long_form)\n{\n    return sizeof(packet_id_type) + (long_form ? sizeof(net_time_t) : 0);\n}\n\nstatic inline bool\npacket_id_close_to_wrapping(const struct packet_id_send *p)\n{\n    return p->id >= PACKET_ID_WRAP_TRIGGER;\n}\n\nstatic inline void\npacket_id_reap_test(struct packet_id_rec *p)\n{\n    if (p->last_reap + SEQ_REAP_INTERVAL <= now)\n    {\n        packet_id_reap(p);\n    }\n}\n\n/**\n * Writes the packet ID containing both the epoch and the packet id to the\n * buffer specified by buf.\n * @param p         packet id send structure to use for the packet id\n * @param epoch     epoch to write to the packet\n * @param buf       buffer to write the packet id/epoch to\n * @return          false if the packet id space is exhausted and cannot be written\n */\nbool packet_id_write_epoch(struct packet_id_send *p, uint16_t epoch, struct buffer *buf);\n\n/**\n * Reads the packet ID containing both the epoch and the per-epoch counter\n * from the buf.  Will return 0 as epoch id if there is any error.\n * @param p       packet_id struct to populate with the on-wire counter\n * @param buf     buffer to read the packet id from.\n * @return        0 for an error/invalid id, epoch otherwise\n */\nuint16_t packet_id_read_epoch(struct packet_id_net *p, struct buffer *buf);\n\n#endif /* PACKET_ID_H */\n"
  },
  {
    "path": "src/openvpn/ping.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"ping.h\"\n\n#include \"memdbg.h\"\n\n\n/*\n * This random string identifies an OpenVPN ping packet.\n * It should be of sufficient length and randomness\n * so as not to collide with other tunnel data.\n *\n * PING_STRING_SIZE must be sizeof (ping_string)\n */\nconst uint8_t ping_string[] = { 0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb,\n                                0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48 };\n\nvoid\ntrigger_ping_timeout_signal(struct context *c)\n{\n    struct gc_arena gc = gc_new();\n    switch (c->options.ping_rec_timeout_action)\n    {\n        case PING_EXIT:\n            msg(M_INFO, \"%sInactivity timeout (--ping-exit), exiting\", format_common_name(c, &gc));\n            register_signal(c->sig, SIGTERM, \"ping-exit\");\n            break;\n\n        case PING_RESTART:\n            msg(M_INFO, \"%sInactivity timeout (--ping-restart), restarting\",\n                format_common_name(c, &gc));\n            register_signal(c->sig, SIGUSR1, \"ping-restart\");\n            break;\n\n        default:\n            ASSERT(0);\n    }\n    gc_free(&gc);\n}\n\n/*\n * Should we ping the remote?\n */\nvoid\ncheck_ping_send_dowork(struct context *c)\n{\n    c->c2.buf = c->c2.buffers->aux_buf;\n    ASSERT(buf_init(&c->c2.buf, c->c2.frame.buf.headroom));\n    ASSERT(buf_safe(&c->c2.buf, c->c2.frame.buf.payload_size));\n    ASSERT(buf_write(&c->c2.buf, ping_string, sizeof(ping_string)));\n\n    /*\n     * We will treat the ping like any other outgoing packet,\n     * encrypt, sign, etc.\n     */\n    encrypt_sign(c, true);\n    /* Set length to 0, so it won't be counted as activity */\n    c->c2.buf.len = 0;\n    dmsg(D_PING, \"SENT PING\");\n}\n"
  },
  {
    "path": "src/openvpn/ping.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef PING_H\n#define PING_H\n\n#include \"init.h\"\n#include \"forward.h\"\n\n/*\n * Initial default --ping-restart before --pull\n */\n#define PRE_PULL_INITIAL_PING_RESTART 120 /* in seconds */\n\nextern const uint8_t ping_string[];\n\n/* PING_STRING_SIZE must be sizeof (ping_string) */\n#define PING_STRING_SIZE 16\n\nstatic inline bool\nis_ping_msg(const struct buffer *buf)\n{\n    return buf_string_match(buf, ping_string, PING_STRING_SIZE);\n}\n\n/**\n * Trigger the correct signal on a --ping timeout\n * depending if --ping-exit is set (SIGTERM) or not\n * (SIGUSR1)\n */\nvoid trigger_ping_timeout_signal(struct context *c);\n\nvoid check_ping_send_dowork(struct context *c);\n\n/*\n * Should we exit or restart due to ping (or other authenticated packet)\n * not received in n seconds?\n */\nstatic inline void\ncheck_ping_restart(struct context *c)\n{\n    if (c->options.ping_rec_timeout\n        && event_timeout_trigger(&c->c2.ping_rec_interval, &c->c2.timeval,\n                                 (!c->options.ping_timer_remote\n                                  || link_socket_actual_defined(&c->c1.link_socket_addrs[0].actual))\n                                     ? ETT_DEFAULT\n                                     : 15))\n    {\n        trigger_ping_timeout_signal(c);\n    }\n}\n\n/*\n * Should we ping the remote?\n */\nstatic inline void\ncheck_ping_send(struct context *c)\n{\n    if (c->options.ping_send_timeout\n        && event_timeout_trigger(&c->c2.ping_send_interval, &c->c2.timeval,\n                                 !TO_LINK_DEF(c) ? ETT_DEFAULT : 1))\n    {\n        check_ping_send_dowork(c);\n    }\n}\n\n#endif /* ifndef PING_H */\n"
  },
  {
    "path": "src/openvpn/pkcs11.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if defined(ENABLE_PKCS11)\n\n#include <pkcs11-helper-1.0/pkcs11h-certificate.h>\n#include \"basic.h\"\n#include \"error.h\"\n#include \"manage.h\"\n#include \"base64.h\"\n#include \"pkcs11.h\"\n#include \"misc.h\"\n#include \"otime.h\"\n#include \"console.h\"\n#include \"pkcs11_backend.h\"\n\nstatic time_t\n__mytime(void)\n{\n    return openvpn_time(NULL);\n}\n\n#if !defined(_WIN32)\nstatic int\n__mygettimeofday(struct timeval *tv)\n{\n    return gettimeofday(tv, NULL);\n}\n#endif\n\nstatic void\n__mysleep(unsigned long usec)\n{\n#if defined(_WIN32)\n    Sleep(usec / 1000);\n#else\n    if (usec > UINT_MAX)\n    {\n        usec = UINT_MAX;\n    }\n    usleep((useconds_t)usec);\n#endif\n}\n\n\nstatic pkcs11h_engine_system_t s_pkcs11h_sys_engine = { malloc, free, __mytime, __mysleep,\n#if defined(_WIN32)\n                                                        NULL\n#else\n                                                        __mygettimeofday\n#endif\n};\n\nstatic msglvl_t\n_pkcs11_msg_pkcs112openvpn(const unsigned flags)\n{\n    msglvl_t openvpn_flags;\n\n    switch (flags)\n    {\n        case PKCS11H_LOG_DEBUG2:\n            openvpn_flags = D_PKCS11_DEBUG;\n            break;\n\n        case PKCS11H_LOG_DEBUG1:\n            openvpn_flags = D_SHOW_PKCS11;\n            break;\n\n        case PKCS11H_LOG_INFO:\n            openvpn_flags = M_INFO;\n            break;\n\n        case PKCS11H_LOG_WARN:\n            openvpn_flags = M_WARN;\n            break;\n\n        case PKCS11H_LOG_ERROR:\n            openvpn_flags = M_FATAL;\n            break;\n\n        default:\n            openvpn_flags = M_FATAL;\n            break;\n    }\n\n#if defined(ENABLE_PKCS11_FORCE_DEBUG)\n    openvpn_flags = M_INFO;\n#endif\n\n    return openvpn_flags;\n}\n\nstatic unsigned\n_pkcs11_msg_openvpn2pkcs11(const msglvl_t flags)\n{\n    unsigned pkcs11_flags;\n\n    if ((flags & D_PKCS11_DEBUG) != 0)\n    {\n        pkcs11_flags = PKCS11H_LOG_DEBUG2;\n    }\n    else if ((flags & D_SHOW_PKCS11) != 0)\n    {\n        pkcs11_flags = PKCS11H_LOG_DEBUG1;\n    }\n    else if ((flags & M_INFO) != 0)\n    {\n        pkcs11_flags = PKCS11H_LOG_INFO;\n    }\n    else if ((flags & M_WARN) != 0)\n    {\n        pkcs11_flags = PKCS11H_LOG_WARN;\n    }\n    else if ((flags & M_FATAL) != 0)\n    {\n        pkcs11_flags = PKCS11H_LOG_ERROR;\n    }\n    else\n    {\n        pkcs11_flags = PKCS11H_LOG_ERROR;\n    }\n\n#if defined(ENABLE_PKCS11_FORCE_DEBUG)\n    pkcs11_flags = PKCS11H_LOG_DEBUG2;\n#endif\n\n    return pkcs11_flags;\n}\n\nstatic void\n_pkcs11_openvpn_log(void *const global_data, unsigned flags, const char *const szFormat,\n                    va_list args)\n{\n    char Buffer[10 * 1024];\n\n    (void)global_data;\n\n    vsnprintf(Buffer, sizeof(Buffer), szFormat, args);\n    Buffer[sizeof(Buffer) - 1] = 0;\n\n    msg(_pkcs11_msg_pkcs112openvpn(flags), \"%s\", Buffer);\n}\n\nstatic PKCS11H_BOOL\n_pkcs11_openvpn_token_prompt(void *const global_data, void *const user_data,\n                             const pkcs11h_token_id_t token, const unsigned retry)\n{\n    struct user_pass token_resp;\n\n    (void)global_data;\n    (void)user_data;\n    (void)retry;\n\n    ASSERT(token != NULL);\n\n    CLEAR(token_resp);\n    token_resp.defined = false;\n    token_resp.nocache = true;\n    snprintf(token_resp.username, sizeof(token_resp.username), \"Please insert %s token\",\n             token->label);\n\n    if (!get_user_pass(&token_resp, NULL, \"token-insertion-request\",\n                       GET_USER_PASS_MANAGEMENT | GET_USER_PASS_NEED_OK | GET_USER_PASS_NOFATAL))\n    {\n        return false;\n    }\n    else\n    {\n        return strcmp(token_resp.password, \"ok\") == 0;\n    }\n}\n\nstatic PKCS11H_BOOL\n_pkcs11_openvpn_pin_prompt(void *const global_data, void *const user_data,\n                           const pkcs11h_token_id_t token, const unsigned retry, char *const pin,\n                           const size_t pin_max)\n{\n    struct user_pass token_pass;\n    char prompt[1024];\n    CLEAR(token_pass);\n\n    (void)global_data;\n    (void)user_data;\n    (void)retry;\n\n    ASSERT(token != NULL);\n\n    snprintf(prompt, sizeof(prompt), \"%s token\", token->label);\n\n    token_pass.defined = false;\n    token_pass.nocache = true;\n\n    if (!get_user_pass(&token_pass, NULL, prompt,\n                       GET_USER_PASS_MANAGEMENT | GET_USER_PASS_PASSWORD_ONLY\n                           | GET_USER_PASS_NOFATAL))\n    {\n        return false;\n    }\n    else\n    {\n        strncpynt(pin, token_pass.password, pin_max);\n        purge_user_pass(&token_pass, true);\n\n        if (strlen(pin) == 0)\n        {\n            return false;\n        }\n        else\n        {\n            return true;\n        }\n    }\n}\n\nbool\npkcs11_initialize(const bool protected_auth, const int nPINCachePeriod)\n{\n    CK_RV rv = CKR_FUNCTION_FAILED;\n\n    dmsg(D_PKCS11_DEBUG, \"PKCS#11: pkcs11_initialize - entered\");\n\n    if ((rv = pkcs11h_engine_setSystem(&s_pkcs11h_sys_engine)) != CKR_OK)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot initialize system engine %ld-'%s'\", rv,\n            pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_initialize()) != CKR_OK)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot initialize %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_setLogHook(_pkcs11_openvpn_log, NULL)) != CKR_OK)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot set hooks %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    pkcs11h_setLogLevel(_pkcs11_msg_openvpn2pkcs11(get_debug_level()));\n\n    if ((rv = pkcs11h_setForkMode(FALSE)) != CKR_OK)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot set fork mode %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_setTokenPromptHook(_pkcs11_openvpn_token_prompt, NULL)) != CKR_OK)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot set hooks %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_setPINPromptHook(_pkcs11_openvpn_pin_prompt, NULL)) != CKR_OK)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot set hooks %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_setProtectedAuthentication(protected_auth)) != CKR_OK)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot set protected authentication mode %ld-'%s'\", rv,\n            pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_setPINCachePeriod(nPINCachePeriod)) != CKR_OK)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot set Pcache period %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    rv = CKR_OK;\n\ncleanup:\n    dmsg(D_PKCS11_DEBUG, \"PKCS#11: pkcs11_initialize - return %ld-'%s'\", rv,\n         pkcs11h_getMessage(rv));\n\n    return rv == CKR_OK;\n}\n\nvoid\npkcs11_terminate(void)\n{\n    dmsg(D_PKCS11_DEBUG, \"PKCS#11: pkcs11_terminate - entered\");\n\n    pkcs11h_terminate();\n\n    dmsg(D_PKCS11_DEBUG, \"PKCS#11: pkcs11_terminate - return\");\n}\n\nbool\npkcs11_addProvider(const char *const provider, const bool protected_auth,\n                   const unsigned private_mode, const bool cert_private)\n{\n    CK_RV rv = CKR_OK;\n\n    ASSERT(provider != NULL);\n\n    dmsg(D_PKCS11_DEBUG, \"PKCS#11: pkcs11_addProvider - entered - provider='%s', private_mode=%08x\",\n         provider, private_mode);\n\n    msg(M_INFO, \"PKCS#11: Adding PKCS#11 provider '%s'\", provider);\n\n#if PKCS11H_VERSION >= ((1 << 16) | (28 << 8) | (0 << 0))\n    if ((rv = pkcs11h_registerProvider(provider)) != CKR_OK)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot register provider '%s' %ld-'%s'\", provider, rv,\n            pkcs11h_getMessage(rv));\n    }\n    else\n    {\n        PKCS11H_BOOL allow_protected_auth = protected_auth;\n        PKCS11H_BOOL cert_is_private = cert_private;\n\n        rv = pkcs11h_setProviderProperty(provider, PKCS11H_PROVIDER_PROPERTY_LOCATION, provider,\n                                         strlen(provider) + 1);\n\n        if (rv == CKR_OK)\n        {\n            rv = pkcs11h_setProviderProperty(provider,\n                                             PKCS11H_PROVIDER_PROPERTY_ALLOW_PROTECTED_AUTH,\n                                             &allow_protected_auth, sizeof(allow_protected_auth));\n        }\n        if (rv == CKR_OK)\n        {\n            rv = pkcs11h_setProviderProperty(provider, PKCS11H_PROVIDER_PROPERTY_MASK_PRIVATE_MODE,\n                                             &private_mode, sizeof(private_mode));\n        }\n        if (rv == CKR_OK)\n        {\n            rv = pkcs11h_setProviderProperty(provider, PKCS11H_PROVIDER_PROPERTY_CERT_IS_PRIVATE,\n                                             &cert_is_private, sizeof(cert_is_private));\n        }\n#if defined(WIN32) && defined(PKCS11H_PROVIDER_PROPERTY_LOADER_FLAGS)\n        if (rv == CKR_OK && platform_absolute_pathname(provider))\n        {\n            unsigned loader_flags =\n                LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR;\n            rv = pkcs11h_setProviderProperty(provider, PKCS11H_PROVIDER_PROPERTY_LOADER_FLAGS,\n                                             &loader_flags, sizeof(loader_flags));\n        }\n#endif\n\n        if (rv != CKR_OK || (rv = pkcs11h_initializeProvider(provider)) != CKR_OK)\n        {\n            msg(M_WARN, \"PKCS#11: Cannot initialize provider '%s' %ld-'%s'\", provider, rv,\n                pkcs11h_getMessage(rv));\n            pkcs11h_removeProvider(provider);\n        }\n    }\n#else  /* if PKCS11H_VERSION >= ((1<<16) | (28<<8) | (0<<0)) */\n    if ((rv = pkcs11h_addProvider(provider, provider, protected_auth, private_mode,\n                                  PKCS11H_SLOTEVENT_METHOD_AUTO, 0, cert_private))\n        != CKR_OK)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot initialize provider '%s' %ld-'%s'\", provider, rv,\n            pkcs11h_getMessage(rv));\n    }\n#endif /* if PKCS11H_VERSION >= ((1<<16) | (28<<8) | (0<<0)) */\n\n    dmsg(D_PKCS11_DEBUG, \"PKCS#11: pkcs11_addProvider - return rv=%ld-'%s'\", rv,\n         pkcs11h_getMessage(rv));\n\n    return rv == CKR_OK;\n}\n\nint\npkcs11_logout(void)\n{\n    return pkcs11h_logout() == CKR_OK;\n}\n\nint\npkcs11_management_id_count(void)\n{\n    pkcs11h_certificate_id_list_t id_list = NULL;\n    pkcs11h_certificate_id_list_t t = NULL;\n    CK_RV rv = CKR_OK;\n    int count = 0;\n\n    dmsg(D_PKCS11_DEBUG, \"PKCS#11: pkcs11_management_id_count - entered\");\n\n    if ((rv = pkcs11h_certificate_enumCertificateIds(PKCS11H_ENUM_METHOD_CACHE_EXIST, NULL,\n                                                     PKCS11H_PROMPT_MASK_ALLOW_ALL, NULL, &id_list))\n        != CKR_OK)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot get certificate list %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    for (count = 0, t = id_list; t != NULL; t = t->next)\n    {\n        count++;\n    }\n\ncleanup:\n\n    pkcs11h_certificate_freeCertificateIdList(id_list);\n    id_list = NULL;\n\n    dmsg(D_PKCS11_DEBUG, \"PKCS#11: pkcs11_management_id_count - return count=%d\", count);\n\n    return count;\n}\n\nbool\npkcs11_management_id_get(const int index, char **id, char **base64)\n{\n    pkcs11h_certificate_id_list_t id_list = NULL;\n    pkcs11h_certificate_id_list_t entry = NULL;\n    pkcs11h_certificate_t certificate = NULL;\n    CK_RV rv = CKR_OK;\n    unsigned char *certificate_blob = NULL;\n    size_t certificate_blob_size = 0;\n    size_t max;\n    char *internal_id = NULL;\n    char *internal_base64 = NULL;\n    int count = 0;\n    bool success = false;\n\n    ASSERT(id != NULL);\n    ASSERT(base64 != NULL);\n\n    dmsg(D_PKCS11_DEBUG, \"PKCS#11: pkcs11_management_id_get - entered index=%d\", index);\n\n    *id = NULL;\n    *base64 = NULL;\n\n    if ((rv = pkcs11h_certificate_enumCertificateIds(PKCS11H_ENUM_METHOD_CACHE_EXIST, NULL,\n                                                     PKCS11H_PROMPT_MASK_ALLOW_ALL, NULL, &id_list))\n        != CKR_OK)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot get certificate list %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    entry = id_list;\n    count = 0;\n    while (entry != NULL && count != index)\n    {\n        count++;\n        entry = entry->next;\n    }\n\n    if (entry == NULL)\n    {\n        dmsg(D_PKCS11_DEBUG, \"PKCS#11: pkcs11_management_id_get - no certificate at index=%d\",\n             index);\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_certificate_serializeCertificateId(NULL, &max, entry->certificate_id))\n        != CKR_OK)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot serialize certificate id %ld-'%s'\", rv,\n            pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if ((internal_id = (char *)malloc(max)) == NULL)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot allocate memory\");\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_certificate_serializeCertificateId(internal_id, &max, entry->certificate_id))\n        != CKR_OK)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot serialize certificate id %ld-'%s'\", rv,\n            pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_certificate_create(entry->certificate_id, NULL, PKCS11H_PROMPT_MASK_ALLOW_ALL,\n                                         PKCS11H_PIN_CACHE_INFINITE, &certificate))\n        != CKR_OK)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot get certificate %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_certificate_getCertificateBlob(certificate, NULL, &certificate_blob_size))\n        != CKR_OK)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot get certificate blob %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if ((certificate_blob = (unsigned char *)malloc(certificate_blob_size)) == NULL)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot allocate memory\");\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_certificate_getCertificateBlob(certificate, certificate_blob,\n                                                     &certificate_blob_size))\n        != CKR_OK)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot get certificate blob %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if (certificate_blob_size > INT_MAX)\n    {\n        msg(M_WARN, \"PKCS#11: Invalid certificate size %zu\", certificate_blob_size);\n        goto cleanup;\n    }\n\n    if (openvpn_base64_encode(certificate_blob, (int)certificate_blob_size, &internal_base64) == -1)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot encode certificate\");\n        goto cleanup;\n    }\n\n    *id = internal_id;\n    internal_id = NULL;\n    *base64 = internal_base64;\n    internal_base64 = NULL;\n    success = true;\n\ncleanup:\n\n    pkcs11h_certificate_freeCertificateIdList(id_list);\n    id_list = NULL;\n\n    pkcs11h_certificate_freeCertificate(certificate);\n    certificate = NULL;\n\n    free(internal_id);\n    internal_id = NULL;\n\n    free(internal_base64);\n    internal_base64 = NULL;\n\n    free(certificate_blob);\n    certificate_blob = NULL;\n\n    dmsg(D_PKCS11_DEBUG, \"PKCS#11: pkcs11_management_id_get - return success=%d, id='%s'\",\n         success ? 1 : 0, *id);\n\n    return success;\n}\n\nint\ntls_ctx_use_pkcs11(struct tls_root_ctx *const ssl_ctx, bool pkcs11_id_management,\n                   const char *const pkcs11_id)\n{\n    pkcs11h_certificate_id_t certificate_id = NULL;\n    pkcs11h_certificate_t certificate = NULL;\n    CK_RV rv = CKR_OK;\n\n    bool ok = false;\n\n    ASSERT(ssl_ctx != NULL);\n    ASSERT(pkcs11_id_management || pkcs11_id != NULL);\n\n    dmsg(\n        D_PKCS11_DEBUG,\n        \"PKCS#11: tls_ctx_use_pkcs11 - entered - ssl_ctx=%p, pkcs11_id_management=%d, pkcs11_id='%s'\",\n        (void *)ssl_ctx, pkcs11_id_management ? 1 : 0, pkcs11_id);\n\n    if (pkcs11_id_management)\n    {\n        struct user_pass id_resp;\n\n        CLEAR(id_resp);\n\n        id_resp.defined = false;\n        id_resp.nocache = true;\n        snprintf(id_resp.username, sizeof(id_resp.username), \"Please specify PKCS#11 id to use\");\n\n        if (!get_user_pass(&id_resp, NULL, \"pkcs11-id-request\",\n                           GET_USER_PASS_MANAGEMENT | GET_USER_PASS_NEED_STR\n                               | GET_USER_PASS_NOFATAL))\n        {\n            goto cleanup;\n        }\n\n        if ((rv = pkcs11h_certificate_deserializeCertificateId(&certificate_id, id_resp.password))\n            != CKR_OK)\n        {\n            msg(M_WARN, \"PKCS#11: Cannot deserialize id %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n            goto cleanup;\n        }\n    }\n    else\n    {\n        if ((rv = pkcs11h_certificate_deserializeCertificateId(&certificate_id, pkcs11_id))\n            != CKR_OK)\n        {\n            msg(M_WARN, \"PKCS#11: Cannot deserialize id %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n            goto cleanup;\n        }\n    }\n\n    if ((rv = pkcs11h_certificate_create(certificate_id, NULL, PKCS11H_PROMPT_MASK_ALLOW_ALL,\n                                         PKCS11H_PIN_CACHE_INFINITE, &certificate))\n        != CKR_OK)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot get certificate %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if ((pkcs11_init_tls_session(certificate, ssl_ctx)))\n    {\n        /* Handled by SSL context free */\n        certificate = NULL;\n        goto cleanup;\n    }\n\n    /* Handled by SSL context free */\n    certificate = NULL;\n    ok = true;\n\ncleanup:\n    if (certificate != NULL)\n    {\n        pkcs11h_certificate_freeCertificate(certificate);\n        certificate = NULL;\n    }\n\n    if (certificate_id != NULL)\n    {\n        pkcs11h_certificate_freeCertificateId(certificate_id);\n        certificate_id = NULL;\n    }\n\n    dmsg(D_PKCS11_DEBUG, \"PKCS#11: tls_ctx_use_pkcs11 - return ok=%d, rv=%ld\", ok ? 1 : 0, rv);\n\n    return ok ? 1 : 0;\n}\n\nstatic PKCS11H_BOOL\n_pkcs11_openvpn_show_pkcs11_ids_pin_prompt(void *const global_data, void *const user_data,\n                                           const pkcs11h_token_id_t token, const unsigned retry,\n                                           char *const pin, const size_t pin_max)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer pass_prompt = alloc_buf_gc(128, &gc);\n\n    (void)global_data;\n    (void)user_data;\n    (void)retry;\n\n    ASSERT(token != NULL);\n\n    buf_printf(&pass_prompt, \"Please enter '%s' token PIN or 'cancel': \", token->display);\n    if (!query_user_SINGLE(BSTR(&pass_prompt), pin, (int)pin_max, false))\n    {\n        msg(M_FATAL, \"Could not retrieve the PIN\");\n    }\n\n    gc_free(&gc);\n\n    if (!strcmp(pin, \"cancel\"))\n    {\n        return FALSE;\n    }\n    else\n    {\n        return TRUE;\n    }\n}\n\nvoid\nshow_pkcs11_ids(const char *const provider, bool cert_private)\n{\n    struct gc_arena gc = gc_new();\n    pkcs11h_certificate_id_list_t user_certificates = NULL;\n    pkcs11h_certificate_id_list_t current = NULL;\n    CK_RV rv = CKR_FUNCTION_FAILED;\n\n    if ((rv = pkcs11h_initialize()) != CKR_OK)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot initialize %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_setLogHook(_pkcs11_openvpn_log, NULL)) != CKR_OK)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot set hooks %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    pkcs11h_setLogLevel(_pkcs11_msg_openvpn2pkcs11(get_debug_level()));\n\n    if ((rv = pkcs11h_setProtectedAuthentication(TRUE)) != CKR_OK)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot set protected authentication %ld-'%s'\", rv,\n            pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_setPINPromptHook(_pkcs11_openvpn_show_pkcs11_ids_pin_prompt, NULL)) != CKR_OK)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot set PIN hook %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    if (!pkcs11_addProvider(provider, TRUE, 0, cert_private ? TRUE : FALSE))\n    {\n        msg(M_FATAL, \"Failed to add PKCS#11 provider '%s\", provider);\n        goto cleanup;\n    }\n\n    if ((rv = pkcs11h_certificate_enumCertificateIds(PKCS11H_ENUM_METHOD_CACHE_EXIST, NULL,\n                                                     PKCS11H_PROMPT_MASK_ALLOW_ALL, NULL,\n                                                     &user_certificates))\n        != CKR_OK)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot enumerate certificates %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n        goto cleanup;\n    }\n\n    msg(M_INFO | M_NOPREFIX | M_NOLF,\n        (\"\\n\"\n         \"The following objects are available for use.\\n\"\n         \"Each object shown below may be used as parameter to\\n\"\n         \"--pkcs11-id option please remember to use single quote mark.\\n\"));\n    for (current = user_certificates; current != NULL; current = current->next)\n    {\n        pkcs11h_certificate_t certificate = NULL;\n        char *dn = NULL;\n        char serial[1024] = { 0 };\n        char *ser = NULL;\n        size_t ser_len = 0;\n\n        if ((rv = pkcs11h_certificate_serializeCertificateId(NULL, &ser_len,\n                                                             current->certificate_id))\n            != CKR_OK)\n        {\n            msg(M_FATAL, \"PKCS#11: Cannot serialize certificate %ld-'%s'\", rv,\n                pkcs11h_getMessage(rv));\n            goto cleanup1;\n        }\n\n        if (rv == CKR_OK && (ser = (char *)malloc(ser_len)) == NULL)\n        {\n            msg(M_FATAL, \"PKCS#11: Cannot allocate memory\");\n            goto cleanup1;\n        }\n\n        if ((rv =\n                 pkcs11h_certificate_serializeCertificateId(ser, &ser_len, current->certificate_id))\n            != CKR_OK)\n        {\n            msg(M_FATAL, \"PKCS#11: Cannot serialize certificate %ld-'%s'\", rv,\n                pkcs11h_getMessage(rv));\n            goto cleanup1;\n        }\n\n        if ((rv = pkcs11h_certificate_create(current->certificate_id, NULL,\n                                             PKCS11H_PROMPT_MASK_ALLOW_ALL,\n                                             PKCS11H_PIN_CACHE_INFINITE, &certificate)))\n        {\n            msg(M_FATAL, \"PKCS#11: Cannot create certificate %ld-'%s'\", rv, pkcs11h_getMessage(rv));\n            goto cleanup1;\n        }\n\n        if ((dn = pkcs11_certificate_dn(certificate, &gc)) == NULL)\n        {\n            goto cleanup1;\n        }\n\n        if ((pkcs11_certificate_serial(certificate, serial, sizeof(serial))))\n        {\n            goto cleanup1;\n        }\n\n        msg(M_INFO | M_NOPREFIX | M_NOLF,\n            (\"\\n\"\n             \"Certificate\\n\"\n             \"       DN:             %s\\n\"\n             \"       Serial:         %s\\n\"\n             \"       Serialized id:  %s\\n\"),\n            dn, serial, ser);\n\ncleanup1:\n\n        if (certificate != NULL)\n        {\n            pkcs11h_certificate_freeCertificate(certificate);\n            certificate = NULL;\n        }\n\n        free(ser);\n        ser = NULL;\n    }\n\ncleanup:\n    pkcs11h_certificate_freeCertificateIdList(user_certificates);\n    user_certificates = NULL;\n\n    pkcs11h_terminate();\n    gc_free(&gc);\n}\n#endif /* ENABLE_PKCS11 */\n"
  },
  {
    "path": "src/openvpn/pkcs11.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef OPENVPN_PKCS11_H\n#define OPENVPN_PKCS11_H\n\n#if defined(ENABLE_PKCS11)\n\n#include \"ssl_common.h\"\n\nbool pkcs11_initialize(const bool fProtectedAuthentication, const int nPINCachePeriod);\n\nvoid pkcs11_terminate(void);\n\nbool pkcs11_addProvider(const char *const provider, const bool fProtectedAuthentication,\n                        const unsigned private_mode, const bool fCertIsPrivate);\n\nint pkcs11_logout(void);\n\nint pkcs11_management_id_count(void);\n\nbool pkcs11_management_id_get(const int index, char **id, char **base64);\n\nint tls_ctx_use_pkcs11(struct tls_root_ctx *const ssl_ctx, bool pkcs11_id_management,\n                       const char *const pkcs11_id);\n\nvoid show_pkcs11_ids(const char *const provider, bool cert_private);\n\n#endif /* ENABLE_PKCS11 */\n\n#endif /* OPENVPN_PKCS11H_H */\n"
  },
  {
    "path": "src/openvpn/pkcs11_backend.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * PKCS #11 SSL library-specific backend\n */\n\n#ifndef PKCS11_BACKEND_H_\n#define PKCS11_BACKEND_H_\n\n#include \"syshead.h\"\n\n#if defined(ENABLE_PKCS11)\n\n#include \"ssl_common.h\"\n\n#include <pkcs11-helper-1.0/pkcs11h-certificate.h>\n\n/**\n * Retrieve PKCS #11 Certificate's DN in a printable format.\n *\n * @param certificate   The PKCS #11 helper certificate object\n * @param gc            Garbage collection pool to allocate memory in\n *\n * @return              Certificate's DN on success, NULL on failure\n */\nchar *pkcs11_certificate_dn(pkcs11h_certificate_t certificate, struct gc_arena *gc);\n\n/**\n * Retrieve PKCS #11 Certificate's serial number in a printable format.\n *\n * @param certificate   The PKCS #11 helper certificate object\n * @param serial        Buffer that the certificate's serial will be placed in.\n * @param serial_len    Size of said buffer.\n *\n * @return              1 on failure, 0 on success\n */\nint pkcs11_certificate_serial(pkcs11h_certificate_t certificate, char *serial, size_t serial_len);\n\n/**\n * Load PKCS #11 Certificate's information into the given TLS context\n *\n * @param certificate   The PKCS #11 helper certificate object\n * @param ssl_ctx       TLS context to use.\n *\n * @return              1 on failure, 0 on success\n */\nint pkcs11_init_tls_session(pkcs11h_certificate_t certificate, struct tls_root_ctx *const ssl_ctx);\n\n#endif /* defined(ENABLE_PKCS11) */\n#endif /* PKCS11_BACKEND_H_ */\n"
  },
  {
    "path": "src/openvpn/pkcs11_mbedtls.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * PKCS #11 mbed TLS backend\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if defined(ENABLE_PKCS11) && defined(ENABLE_CRYPTO_MBEDTLS)\n\n#include \"errlevel.h\"\n#include \"pkcs11_backend.h\"\n#include \"ssl_verify_backend.h\"\n#include <mbedtls/x509.h>\n\nstatic bool\npkcs11_get_x509_cert(pkcs11h_certificate_t pkcs11_cert, mbedtls_x509_crt *cert)\n{\n    /* We set a maximum size for certificates so that the PKCS provider cannot crash OpenVPN by\n     * making it try to allocate 2^64 bytes. The maximum of 100.000 bytes is picked as a round\n     * number that easily accomodates the currently standardized quantum-safe signature algorithms.\n     * It is twice the size of a SLH-DSA (aka SPHINCS+) signature plus public key.\n     *\n     * However, there are additional digital signature schemes currently on the NIST on-ramp\n     * (e.g., some parameter settings for LESS) that have even larger public keys or signatures, so\n     * if those ever see use on smartcards, we will need to increase this number. */\n    const size_t max_cert_size = 100000;\n\n    unsigned char *cert_blob = NULL;\n    size_t cert_blob_size = 0;\n    bool ret = false;\n\n    if (pkcs11h_certificate_getCertificateBlob(pkcs11_cert, NULL, &cert_blob_size) != CKR_OK)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot retrieve certificate object size\");\n        goto cleanup;\n    }\n\n    if (cert_blob_size > max_cert_size)\n    {\n        msg(M_WARN, \"PKCS#11: Certificate too large: %lu bytes, maximum is %lu\", cert_blob_size, max_cert_size);\n        goto cleanup;\n    }\n\n    check_malloc_return((cert_blob = calloc(1, cert_blob_size)));\n    if (pkcs11h_certificate_getCertificateBlob(pkcs11_cert, cert_blob, &cert_blob_size) != CKR_OK)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot retrieve certificate object\");\n        goto cleanup;\n    }\n\n    if (!mbed_ok(mbedtls_x509_crt_parse(cert, cert_blob, cert_blob_size)))\n    {\n        msg(M_WARN, \"PKCS#11: Could not parse certificate\");\n        goto cleanup;\n    }\n\n    ret = true;\ncleanup:\n    free(cert_blob);\n    return ret;\n}\n\nstatic bool\npkcs11_sign(void *pkcs11_cert, const void *src, size_t src_len, void *dst, size_t dst_len)\n{\n    return CKR_OK\n           == pkcs11h_certificate_signAny(pkcs11_cert, CKM_RSA_PKCS, src, src_len, dst, &dst_len);\n}\n\nint\npkcs11_init_tls_session(pkcs11h_certificate_t certificate, struct tls_root_ctx *const ssl_ctx)\n{\n    ASSERT(NULL != ssl_ctx);\n\n    ssl_ctx->pkcs11_cert = certificate;\n\n    ALLOC_OBJ_CLEAR(ssl_ctx->crt_chain, mbedtls_x509_crt);\n    if (!pkcs11_get_x509_cert(certificate, ssl_ctx->crt_chain))\n    {\n        msg(M_WARN, \"PKCS#11: Cannot initialize certificate\");\n        return 1;\n    }\n\n    if (tls_ctx_use_external_signing_func(ssl_ctx, pkcs11_sign, certificate))\n    {\n        msg(M_WARN, \"PKCS#11: Cannot register signing function\");\n        return 1;\n    }\n\n    return 0;\n}\n\nchar *\npkcs11_certificate_dn(pkcs11h_certificate_t cert, struct gc_arena *gc)\n{\n    char *ret = NULL;\n    mbedtls_x509_crt mbed_crt = { 0 };\n\n    if (!pkcs11_get_x509_cert(cert, &mbed_crt))\n    {\n        msg(M_WARN, \"PKCS#11: Cannot retrieve mbed TLS certificate object\");\n        goto cleanup;\n    }\n\n    if (!(ret = x509_get_subject(&mbed_crt, gc)))\n    {\n        msg(M_WARN, \"PKCS#11: mbed TLS cannot parse subject\");\n        goto cleanup;\n    }\n\ncleanup:\n    mbedtls_x509_crt_free(&mbed_crt);\n\n    return ret;\n}\n\nint\npkcs11_certificate_serial(pkcs11h_certificate_t cert, char *serial, size_t serial_len)\n{\n    int ret = 1;\n    mbedtls_x509_crt mbed_crt = { 0 };\n\n    if (!pkcs11_get_x509_cert(cert, &mbed_crt))\n    {\n        msg(M_WARN, \"PKCS#11: Cannot retrieve mbed TLS certificate object\");\n        goto cleanup;\n    }\n\n    if (mbedtls_x509_serial_gets(serial, serial_len, &mbed_crt.serial) < 0)\n    {\n        msg(M_WARN, \"PKCS#11: mbed TLS cannot parse serial\");\n        goto cleanup;\n    }\n\n    ret = 0;\ncleanup:\n    mbedtls_x509_crt_free(&mbed_crt);\n\n    return ret;\n}\n#endif /* defined(ENABLE_PKCS11) && defined(ENABLE_CRYPTO_MBEDTLS) */\n"
  },
  {
    "path": "src/openvpn/pkcs11_openssl.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * PKCS #11 OpenSSL backend\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if defined(ENABLE_PKCS11) && defined(ENABLE_CRYPTO_OPENSSL)\n\n#include \"errlevel.h\"\n#include \"pkcs11_backend.h\"\n#include \"ssl_verify.h\"\n#include \"xkey_common.h\"\n#include <pkcs11-helper-1.0/pkcs11h-openssl.h>\n\n#ifdef HAVE_XKEY_PROVIDER\nstatic XKEY_EXTERNAL_SIGN_fn xkey_pkcs11h_sign;\n\n#if PKCS11H_VERSION > ((1 << 16) | (27 << 8)) /* version > 1.27 */\n\n/* Table linking OpenSSL digest NID with CKM and CKG constants in PKCS#11 */\n#define MD_TYPE(n) { NID_sha##n, CKM_SHA##n, CKG_MGF1_SHA##n }\nstatic const struct\n{\n    int nid;\n    unsigned long ckm_id;\n    unsigned long mgf_id;\n} mdtypes[] = { MD_TYPE(224),\n                MD_TYPE(256),\n                MD_TYPE(384),\n                MD_TYPE(512),\n                { NID_sha1, CKM_SHA_1, CKG_MGF1_SHA1 }, /* SHA_1 naming is an oddity */\n                { NID_undef, 0, 0 } };\n\n/* From sigalg, derive parameters for pss signature and fill in  pss_params.\n * Its of type CK_RSA_PKCS_PSS_PARAMS struct with three fields to be filled in:\n * {enum hashAlg, enum mgf, ulong sLen}\n * where hashAlg is CKM_SHA256 etc., mgf is CKG_MGF1_SHA256 etc.\n */\nstatic int\nset_pss_params(CK_RSA_PKCS_PSS_PARAMS *pss_params, XKEY_SIGALG sigalg, pkcs11h_certificate_t cert)\n{\n    int ret = 0;\n    X509 *x509 = NULL;\n    EVP_PKEY *pubkey = NULL;\n\n    if ((x509 = pkcs11h_openssl_getX509(cert)) == NULL || (pubkey = X509_get0_pubkey(x509)) == NULL)\n    {\n        msg(M_WARN, \"PKCS#11: Unable get public key\");\n        goto cleanup;\n    }\n\n    /* map mdname to CKM and CKG constants for hash and mgf algorithms */\n    int i = 0;\n    int nid = OBJ_sn2nid(sigalg.mdname);\n    while (mdtypes[i].nid != NID_undef && mdtypes[i].nid != nid)\n    {\n        i++;\n    }\n    pss_params->hashAlg = mdtypes[i].ckm_id;\n    pss_params->mgf = mdtypes[i].mgf_id;\n\n    /* determine salt length */\n    const EVP_MD *md = EVP_get_digestbyname(sigalg.mdname);\n    if (!md)\n    {\n        msg(M_WARN,\n            \"WARN: set_pss_params: EVP_get_digestbyname returned NULL \"\n            \"for mdname = <%s>\",\n            sigalg.mdname);\n        goto cleanup;\n    }\n    int mdsize = EVP_MD_get_size(md);\n\n    int saltlen = -1;\n    if (!strcmp(sigalg.saltlen, \"digest\")) /* same as digest size */\n    {\n        saltlen = mdsize;\n    }\n    else if (!strcmp(sigalg.saltlen, \"max\")) /* maximum possible value */\n    {\n        saltlen = xkey_max_saltlen(EVP_PKEY_get_bits(pubkey), mdsize);\n    }\n\n    if (saltlen < 0 || pss_params->hashAlg == 0)\n    {\n        msg(M_WARN,\n            \"WARN: invalid RSA_PKCS1_PSS parameters: saltlen = <%s> \"\n            \"mdname = <%s>.\",\n            sigalg.saltlen, sigalg.mdname);\n        goto cleanup;\n    }\n    pss_params->sLen = (unsigned long)saltlen; /* saltlen >= 0 at this point */\n\n    msg(D_XKEY, \"set_pss_params: sLen = %lu, hashAlg = %lu, mgf = %lu\", pss_params->sLen,\n        pss_params->hashAlg, pss_params->mgf);\n\n    ret = 1;\n\ncleanup:\n    if (x509)\n    {\n        X509_free(x509);\n    }\n    return ret;\n}\n\n#else  /* if PKCS11H_VERSION > ((1<<16) | (27<<8)) */\n\n/* Make set_pss_params a no-op that always succeeds */\n#define set_pss_params(...) (1)\n\n/* Use a wrapper for pkcs11h_certificate_signAny_ex() for versions < 1.28\n * where its not available.\n * We just call pkcs11h_certificate_signAny() unless the padding\n * is PSS in which case we return an error.\n */\nstatic CK_RV\npkcs11h_certificate_signAny_ex(const pkcs11h_certificate_t cert, const CK_MECHANISM *mech,\n                               const unsigned char *tbs, size_t tbslen, unsigned char *sig,\n                               size_t *siglen)\n{\n    if (mech->mechanism == CKM_RSA_PKCS_PSS)\n    {\n        msg(M_NONFATAL, \"PKCS#11: Error: PSS padding is not supported by \"\n                        \"this version of pkcs11-helper library.\");\n        return CKR_MECHANISM_INVALID;\n    }\n    return pkcs11h_certificate_signAny(cert, mech->mechanism, tbs, tbslen, sig, siglen);\n}\n#endif /* PKCS11H_VERSION > 1.27 */\n\n/**\n * Sign op called from xkey provider\n *\n * We support ECDSA, RSA_NO_PADDING, RSA_PKCS1_PADDING, RSA_PKCS_PSS_PADDING\n */\nstatic int\nxkey_pkcs11h_sign(void *handle, unsigned char *sig, size_t *siglen, const unsigned char *tbs,\n                  size_t tbslen, XKEY_SIGALG sigalg)\n{\n    pkcs11h_certificate_t cert = handle;\n    CK_MECHANISM mech = { CKM_RSA_PKCS, NULL, 0 }; /* default value */\n    CK_RSA_PKCS_PSS_PARAMS pss_params = { 0 };\n\n    unsigned char buf[EVP_MAX_MD_SIZE];\n    size_t buflen = 0;\n    size_t siglen_max = *siglen;\n\n    unsigned char enc[EVP_MAX_MD_SIZE + 32]; /* 32 bytes enough for DigestInfo header */\n    size_t enc_len = sizeof(enc);\n\n    if (!strcmp(sigalg.op, \"DigestSign\"))\n    {\n        msg(D_XKEY, \"xkey_pkcs11h_sign: computing digest\");\n        if (xkey_digest(tbs, tbslen, buf, &buflen, sigalg.mdname))\n        {\n            tbs = buf;\n            tbslen = (size_t)buflen;\n            sigalg.op = \"Sign\";\n        }\n        else\n        {\n            return 0;\n        }\n    }\n\n    if (!strcmp(sigalg.keytype, \"EC\"))\n    {\n        msg(D_XKEY, \"xkey_pkcs11h_sign: signing with EC key\");\n        mech.mechanism = CKM_ECDSA;\n    }\n    else if (!strcmp(sigalg.keytype, \"RSA\"))\n    {\n        msg(D_XKEY, \"xkey_pkcs11h_sign: signing with RSA key: padmode = %s\", sigalg.padmode);\n        if (!strcmp(sigalg.padmode, \"none\"))\n        {\n            mech.mechanism = CKM_RSA_X_509;\n        }\n        else if (!strcmp(sigalg.padmode, \"pss\"))\n        {\n            mech.mechanism = CKM_RSA_PKCS_PSS;\n\n            if (!set_pss_params(&pss_params, sigalg, cert))\n            {\n                return 0;\n            }\n\n            mech.pParameter = &pss_params;\n            mech.ulParameterLen = sizeof(pss_params);\n        }\n        else if (!strcmp(sigalg.padmode, \"pkcs1\"))\n        {\n            /* CMA_RSA_PKCS needs pkcs1 encoded digest */\n\n            if (!encode_pkcs1(enc, &enc_len, sigalg.mdname, tbs, tbslen))\n            {\n                return 0;\n            }\n            tbs = enc;\n            tbslen = enc_len;\n        }\n        else /* should not happen */\n        {\n            msg(M_WARN, \"PKCS#11: Unknown padmode <%s>\", sigalg.padmode);\n        }\n    }\n    else\n    {\n        ASSERT(0); /* coding error -- we couldnt have created any such key */\n    }\n\n    if (CKR_OK != pkcs11h_certificate_signAny_ex(cert, &mech, tbs, tbslen, sig, siglen))\n    {\n        return 0;\n    }\n    if (strcmp(sigalg.keytype, \"EC\"))\n    {\n        return 1;\n    }\n\n    /* For EC keys, pkcs11 returns signature as r|s: convert to der encoded */\n    int derlen = ecdsa_bin2der(sig, (int)*siglen, siglen_max);\n\n    if (derlen <= 0)\n    {\n        return 0;\n    }\n    *siglen = derlen;\n\n    return 1;\n}\n\n/* wrapper for handle free */\nstatic void\nxkey_handle_free(void *handle)\n{\n    pkcs11h_certificate_freeCertificate(handle);\n}\n\n\n/**\n * Load certificate and public key from pkcs11h to SSL_CTX\n * through xkey provider.\n *\n * @param certificate          pkcs11h certificate object\n * @param ctx                  OpenVPN root tls context\n *\n * @returns                    1 on success, 0 on error to match\n *                             other xkey_load_.. routines\n */\nstatic int\nxkey_load_from_pkcs11h(pkcs11h_certificate_t certificate, struct tls_root_ctx *const ctx)\n{\n    int ret = 0;\n\n    X509 *x509 = pkcs11h_openssl_getX509(certificate);\n    if (!x509)\n    {\n        msg(M_WARN, \"PKCS#11: Unable get x509 certificate object\");\n        return 0;\n    }\n\n    EVP_PKEY *pubkey = X509_get0_pubkey(x509);\n\n    XKEY_PRIVKEY_FREE_fn *free_op = xkey_handle_free; /* it calls pkcs11h_..._freeCertificate() */\n    XKEY_EXTERNAL_SIGN_fn *sign_op = xkey_pkcs11h_sign;\n\n    EVP_PKEY *pkey = xkey_load_generic_key(tls_libctx, certificate, pubkey, sign_op, free_op);\n    if (!pkey)\n    {\n        msg(M_WARN, \"PKCS#11: Failed to load private key into xkey provider\");\n        goto cleanup;\n    }\n    /* provider took ownership of the pkcs11h certificate object -- do not free below */\n    certificate = NULL;\n\n    if (!SSL_CTX_use_cert_and_key(ctx->ctx, x509, pkey, NULL, 0))\n    {\n        crypto_print_openssl_errors(M_WARN);\n        msg(M_FATAL, \"PKCS#11: Failed to set cert and private key for OpenSSL\");\n        goto cleanup;\n    }\n    ret = 1;\n\ncleanup:\n    if (x509)\n    {\n        X509_free(x509);\n    }\n    if (pkey)\n    {\n        EVP_PKEY_free(pkey);\n    }\n    if (certificate)\n    {\n        pkcs11h_certificate_freeCertificate(certificate);\n    }\n    return ret;\n}\n#endif /* HAVE_XKEY_PROVIDER */\n\nint\npkcs11_init_tls_session(pkcs11h_certificate_t certificate, struct tls_root_ctx *const ssl_ctx)\n{\n#ifdef HAVE_XKEY_PROVIDER\n    return (xkey_load_from_pkcs11h(certificate, ssl_ctx) == 0); /* inverts the return value */\n#else\n    int ret = 1;\n\n    X509 *x509 = NULL;\n    EVP_PKEY *evp = NULL;\n    pkcs11h_openssl_session_t openssl_session = NULL;\n\n    if ((openssl_session = pkcs11h_openssl_createSession(certificate)) == NULL)\n    {\n        msg(M_WARN, \"PKCS#11: Cannot initialize openssl session\");\n        goto cleanup;\n    }\n\n    /*\n     * Will be released by openssl_session\n     */\n    certificate = NULL;\n\n    if ((evp = pkcs11h_openssl_session_getEVP(openssl_session)) == NULL)\n    {\n        msg(M_WARN, \"PKCS#11: Unable get evp object\");\n        goto cleanup;\n    }\n\n    if ((x509 = pkcs11h_openssl_session_getX509(openssl_session)) == NULL)\n    {\n        msg(M_WARN, \"PKCS#11: Unable get certificate object\");\n        goto cleanup;\n    }\n\n    if (!SSL_CTX_use_PrivateKey(ssl_ctx->ctx, evp))\n    {\n        msg(M_WARN, \"PKCS#11: Cannot set private key for openssl\");\n        goto cleanup;\n    }\n\n    if (!SSL_CTX_use_certificate(ssl_ctx->ctx, x509))\n    {\n        crypto_print_openssl_errors(M_WARN);\n        msg(M_FATAL, \"PKCS#11: Cannot set certificate for openssl\");\n        goto cleanup;\n    }\n    ret = 0;\n\ncleanup:\n    /*\n     * Certificate freeing is usually handled by openssl_session.\n     * If something went wrong, creating the session we have to do it manually.\n     */\n    if (certificate != NULL)\n    {\n        pkcs11h_certificate_freeCertificate(certificate);\n        certificate = NULL;\n    }\n\n    /*\n     * openssl objects have reference\n     * count, so release them\n     */\n    X509_free(x509);\n    x509 = NULL;\n\n    EVP_PKEY_free(evp);\n    evp = NULL;\n\n    if (openssl_session != NULL)\n    {\n        pkcs11h_openssl_freeSession(openssl_session);\n        openssl_session = NULL;\n    }\n    return ret;\n#endif                                                          /* ifdef HAVE_XKEY_PROVIDER */\n}\n\nchar *\npkcs11_certificate_dn(pkcs11h_certificate_t certificate, struct gc_arena *gc)\n{\n    X509 *x509 = NULL;\n\n    char *dn = NULL;\n\n    if ((x509 = pkcs11h_openssl_getX509(certificate)) == NULL)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot get X509\");\n        goto cleanup;\n    }\n\n    dn = x509_get_subject(x509, gc);\n\ncleanup:\n    X509_free(x509);\n    x509 = NULL;\n\n    return dn;\n}\n\nint\npkcs11_certificate_serial(pkcs11h_certificate_t certificate, char *serial, size_t serial_len)\n{\n    X509 *x509 = NULL;\n    BIO *bio = NULL;\n    int ret = 1;\n\n    if ((x509 = pkcs11h_openssl_getX509(certificate)) == NULL)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot get X509\");\n        goto cleanup;\n    }\n\n    if ((bio = BIO_new(BIO_s_mem())) == NULL)\n    {\n        msg(M_FATAL, \"PKCS#11: Cannot create BIO\");\n        goto cleanup;\n    }\n\n    i2a_ASN1_INTEGER(bio, X509_get_serialNumber(x509));\n    ASSERT(serial_len <= INT_MAX);\n    int n = BIO_read(bio, serial, (int)serial_len - 1);\n\n    if (n < 0)\n    {\n        serial[0] = '\\x0';\n    }\n    else\n    {\n        serial[n] = 0;\n    }\n\n    ret = 0;\n\ncleanup:\n    X509_free(x509);\n    x509 = NULL;\n\n    return ret;\n}\n\n#endif /* defined(ENABLE_PKCS11) && defined(ENABLE_OPENSSL) */\n"
  },
  {
    "path": "src/openvpn/platform.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"openvpn.h\"\n#include \"options.h\"\n\n#include \"buffer.h\"\n#include \"crypto.h\"\n#include \"error.h\"\n#include \"misc.h\"\n#include \"win32.h\"\n\n#include \"memdbg.h\"\n\n#include \"platform.h\"\n\n#ifdef _WIN32\n#include <direct.h>\n#endif\n\n#ifdef HAVE_LIBCAPNG\n#include <cap-ng.h>\n#include <sys/prctl.h>\n#endif\n\n/* Redefine the top level directory of the filesystem\n * to restrict access to files for security */\nvoid\nplatform_chroot(const char *path)\n{\n    if (path)\n    {\n#ifdef HAVE_CHROOT\n        const char *top = \"/\";\n        if (chroot(path))\n        {\n            msg(M_ERR, \"chroot to '%s' failed\", path);\n        }\n        if (platform_chdir(top))\n        {\n            msg(M_ERR, \"cd to '%s' failed\", top);\n        }\n        msg(M_INFO, \"chroot to '%s' and cd to '%s' succeeded\", path, top);\n#else /* ifdef HAVE_CHROOT */\n        msg(M_FATAL,\n            \"Sorry but I can't chroot to '%s' because this operating system doesn't appear to support the chroot() system call\",\n            path);\n#endif\n    }\n}\n\n/* Get/Set UID of process */\n\nbool\nplatform_user_get(const char *username, struct platform_state_user *state)\n{\n    CLEAR(*state);\n    if (username)\n    {\n#if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID)\n        const struct passwd *pw = getpwnam(username);\n        if (!pw)\n        {\n            msg(M_ERR, \"failed to find UID for user %s\", username);\n        }\n        else\n        {\n            state->uid = pw->pw_uid;\n            state->user_valid = true;\n        }\n        state->username = username;\n#else /* if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID) */\n        msg(M_FATAL,\n            \"cannot get UID for user %s -- platform lacks getpwname() or setuid() system calls\",\n            username);\n#endif\n    }\n    return state->user_valid;\n}\n\nstatic void\nplatform_user_set(const struct platform_state_user *state)\n{\n#if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID)\n    if (state->username && state->user_valid)\n    {\n        if (setuid(state->uid))\n        {\n            msg(M_ERR, \"setuid('%s') failed\", state->username);\n        }\n        msg(M_INFO, \"UID set to %s\", state->username);\n    }\n#endif\n}\n\n/* Get/Set GID of process */\n\nbool\nplatform_group_get(const char *groupname, struct platform_state_group *state)\n{\n    CLEAR(*state);\n    if (groupname)\n    {\n#if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID)\n        const struct group *gr = getgrnam(groupname);\n        if (!gr)\n        {\n            msg(M_ERR, \"failed to find GID for group %s\", groupname);\n        }\n        else\n        {\n            state->gid = gr->gr_gid;\n            state->group_valid = true;\n        }\n        state->groupname = groupname;\n#else /* if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID) */\n        msg(M_FATAL,\n            \"cannot get GID for group %s -- platform lacks getgrnam() or setgid() system calls\",\n            groupname);\n#endif\n    }\n    return state->group_valid;\n}\n\nstatic void\nplatform_group_set(const struct platform_state_group *state)\n{\n#if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID)\n    if (state->groupname && state->group_valid)\n    {\n        if (setgid(state->gid))\n        {\n            msg(M_ERR, \"setgid('%s') failed\", state->groupname);\n        }\n        msg(M_INFO, \"GID set to %s\", state->groupname);\n#ifdef HAVE_SETGROUPS\n        {\n            gid_t gr_list[1];\n            gr_list[0] = state->gid;\n            if (setgroups(1, gr_list))\n            {\n                msg(M_ERR, \"setgroups('%s') failed\", state->groupname);\n            }\n        }\n#endif\n    }\n#endif\n}\n\n/*\n * Determine if we need to retain process capabilities. DCO and SITNL need it.\n * Enforce it for DCO, but only try and soft-fail for SITNL to keep backwards compat.\n *\n * Returns the tri-state expected by platform_user_group_set.\n * -1: try to keep caps, but continue if impossible\n *  0: don't keep caps\n *  1: keep caps, fail hard if impossible\n */\nstatic int\nneed_keep_caps(struct context *c)\n{\n    if (!c)\n    {\n        return -1;\n    }\n\n    if (dco_enabled(&c->options))\n    {\n#ifdef TARGET_LINUX\n        /* DCO on Linux does not work at all without CAP_NET_ADMIN */\n        return 1;\n#else\n        /* Windows/BSD/... has no equivalent capability mechanism */\n        return -1;\n#endif\n    }\n\n#ifdef ENABLE_SITNL\n    return -1;\n#else\n    return 0;\n#endif\n}\n\n/* Set user and group, retaining neccesary capabilities required by the platform.\n *\n * The keep_caps argument has 3 possible states:\n *  >0: Retain capabilities, and fail hard on failure to do so.\n * ==0: Don't attempt to retain any capabilities, just sitch user/group.\n *  <0: Try to retain capabilities, but continue on failure.\n */\nvoid\nplatform_user_group_set(const struct platform_state_user *user_state,\n                        const struct platform_state_group *group_state, struct context *c)\n{\n    int keep_caps = need_keep_caps(c);\n    unsigned int err_flags = (keep_caps > 0) ? M_FATAL : M_NONFATAL;\n#ifdef HAVE_LIBCAPNG\n    int new_gid = -1, new_uid = -1;\n    int res;\n\n    if (keep_caps == 0)\n    {\n        goto fallback;\n    }\n\n    /*\n     * new_uid/new_gid defaults to -1, which will not make\n     * libcap-ng change the UID/GID unless configured\n     */\n    if (group_state->groupname && group_state->group_valid)\n    {\n        new_gid = (int)group_state->gid;\n    }\n    if (user_state->username && user_state->user_valid)\n    {\n        new_uid = (int)user_state->uid;\n    }\n\n    /* Prepare capabilities before dropping UID/GID */\n    capng_clear(CAPNG_SELECT_BOTH);\n    res = capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, CAP_NET_ADMIN);\n    if (res < 0)\n    {\n        msg(err_flags, \"capng_update(CAP_NET_ADMIN) failed: %d\", res);\n        goto fallback;\n    }\n\n    /* Change to new UID/GID.\n     * capng_change_id() internally calls capng_apply() to apply prepared capabilities.\n     */\n    res = capng_change_id(new_uid, new_gid, CAPNG_DROP_SUPP_GRP);\n    if (res == -4 || res == -6)\n    {\n        /* -4 and -6 mean failure of setuid/gid respectively.\n         * There is no point for us to continue if those failed. */\n        msg(M_ERR, \"capng_change_id('%s','%s') failed: %d\", user_state->username,\n            group_state->groupname, res);\n    }\n    else if (res == -3)\n    {\n        msg(M_NONFATAL | M_ERRNO, \"capng_change_id() failed applying capabilities\");\n        msg(err_flags, \"NOTE: previous error likely due to missing capability CAP_SETPCAP.\");\n        goto fallback;\n    }\n    else if (res < 0)\n    {\n        msg(err_flags | M_ERRNO, \"capng_change_id('%s','%s') failed retaining capabilities: %d\",\n            user_state->username, group_state->groupname, res);\n        goto fallback;\n    }\n\n    if (new_uid >= 0)\n    {\n        msg(M_INFO, \"UID set to %s\", user_state->username);\n    }\n    if (new_gid >= 0)\n    {\n        msg(M_INFO, \"GID set to %s\", group_state->groupname);\n    }\n\n    msg(M_INFO, \"Capabilities retained: CAP_NET_ADMIN\");\n    return;\n\nfallback:\n    /* capng_change_id() can leave this flag clobbered on failure\n     * This is working around a bug in libcap-ng, which can leave the flag set\n     * on failure: https://github.com/stevegrubb/libcap-ng/issues/33 */\n    if (prctl(PR_GET_KEEPCAPS) && prctl(PR_SET_KEEPCAPS, 0) < 0)\n    {\n        msg(M_ERR, \"Clearing KEEPCAPS flag failed\");\n    }\n#endif /* HAVE_LIBCAPNG */\n\n    if (keep_caps)\n    {\n        msg(err_flags, \"Unable to retain capabilities\");\n    }\n\n    platform_group_set(group_state);\n    platform_user_set(user_state);\n}\n\n/* Change process priority */\nvoid\nplatform_nice(int niceval)\n{\n    if (niceval)\n    {\n#ifdef HAVE_NICE\n        errno = 0;\n        if (nice(niceval) < 0 && errno != 0)\n        {\n            msg(M_WARN | M_ERRNO, \"WARNING: nice %d failed\", niceval);\n        }\n        else\n        {\n            msg(M_INFO, \"nice %d succeeded\", niceval);\n        }\n#else /* ifdef HAVE_NICE */\n        msg(M_WARN, \"WARNING: nice %d failed (function not implemented)\", niceval);\n#endif\n    }\n}\n\n/* Get current PID */\nunsigned int\nplatform_getpid(void)\n{\n#ifdef _WIN32\n    return (unsigned int)GetCurrentProcessId();\n#else\n    return (unsigned int)getpid();\n#endif\n}\n\n/* Disable paging */\nvoid\nplatform_mlockall(bool print_msg)\n{\n#ifdef HAVE_MLOCKALL\n\n#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_MEMLOCK)\n#define MIN_LOCKED_MEM_MB 100\n    struct rlimit rl;\n    if (getrlimit(RLIMIT_MEMLOCK, &rl) < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"WARNING: getrlimit(RLIMIT_MEMLOCK) failed\");\n    }\n    else\n    {\n        msg(M_INFO, \"mlock: MEMLOCK limit: soft=%ld KB, hard=%ld KB\",\n            ((long int)rl.rlim_cur) / 1024, ((long int)rl.rlim_max) / 1024);\n        if (rl.rlim_cur < MIN_LOCKED_MEM_MB * 1024 * 1024)\n        {\n            msg(M_INFO, \"mlock: RLIMIT_MEMLOCK < %d MB, increase limit\", MIN_LOCKED_MEM_MB);\n            rl.rlim_cur = MIN_LOCKED_MEM_MB * 1024 * 1024;\n            if (rl.rlim_max < rl.rlim_cur)\n            {\n                rl.rlim_max = rl.rlim_cur;\n            }\n            if (setrlimit(RLIMIT_MEMLOCK, &rl) < 0)\n            {\n                msg(M_FATAL | M_ERRNO, \"ERROR: setrlimit() failed\");\n            }\n        }\n    }\n#endif /* if defined(HAVE_GETRLIMIT) && defined(RLIMIT_MEMLOCK) */\n\n    if (mlockall(MCL_CURRENT | MCL_FUTURE))\n    {\n        msg(M_WARN | M_ERRNO, \"WARNING: mlockall call failed\");\n    }\n    else if (print_msg)\n    {\n        msg(M_INFO, \"mlockall call succeeded\");\n    }\n#else  /* ifdef HAVE_MLOCKALL */\n    msg(M_WARN, \"WARNING: mlockall call failed (function not implemented)\");\n#endif /* ifdef HAVE_MLOCKALL */\n}\n\n/*\n * Wrapper for chdir library function\n */\nint\nplatform_chdir(const char *dir)\n{\n#ifdef _WIN32\n    int res;\n    struct gc_arena gc = gc_new();\n    res = _wchdir(wide_string(dir, &gc));\n    gc_free(&gc);\n    return res;\n#else /* ifdef _WIN32 */\n#ifdef HAVE_CHDIR\n    return chdir(dir);\n#else /* ifdef HAVE_CHDIR */\n    return -1;\n#endif\n#endif\n}\n\n/*\n * convert execve() return into a success/failure value\n */\nbool\nplatform_system_ok(int stat)\n{\n#ifdef _WIN32\n    return stat == 0;\n#else\n    return stat != -1 && WIFEXITED(stat) && WEXITSTATUS(stat) == 0;\n#endif\n}\n\n#ifdef _WIN32\nint\nplatform_ret_code(int stat)\n{\n    if (stat >= 0 && stat < 255)\n    {\n        return stat;\n    }\n    else\n    {\n        return -1;\n    }\n}\n#else  /* ifdef _WIN32 */\nint\nplatform_ret_code(int stat)\n{\n    if (!WIFEXITED(stat) || stat == -1)\n    {\n        return -1;\n    }\n\n    int status = WEXITSTATUS(stat);\n    if (status >= 0 && status < 255)\n    {\n        return status;\n    }\n    else\n    {\n        return -1;\n    }\n}\n#endif /* ifdef _WIN32 */\n\nint\nplatform_access(const char *path, int mode)\n{\n#ifdef _WIN32\n    struct gc_arena gc = gc_new();\n    int ret = _waccess(wide_string(path, &gc), mode & ~X_OK);\n    gc_free(&gc);\n    return ret;\n#else\n    return access(path, mode);\n#endif\n}\n\n/*\n * Go to sleep for n milliseconds.\n */\nvoid\nplatform_sleep_milliseconds(unsigned int n)\n{\n#ifdef _WIN32\n    Sleep(n);\n#else\n    struct timeval tv;\n    tv.tv_sec = n / 1000;\n    tv.tv_usec = (n % 1000) * 1000;\n    select(0, NULL, NULL, NULL, &tv);\n#endif\n}\n\n/* delete a file, return true if succeeded */\nbool\nplatform_unlink(const char *filename)\n{\n#if defined(_WIN32)\n    struct gc_arena gc = gc_new();\n    BOOL ret = DeleteFileW(wide_string(filename, &gc));\n    gc_free(&gc);\n    return (ret != 0);\n#else\n    return (unlink(filename) == 0);\n#endif\n}\n\nFILE *\nplatform_fopen(const char *path, const char *mode)\n{\n#ifdef _WIN32\n    struct gc_arena gc = gc_new();\n    FILE *f = _wfopen(wide_string(path, &gc), wide_string(mode, &gc));\n    gc_free(&gc);\n    return f;\n#else\n    return fopen(path, mode);\n#endif\n}\n\nint\nplatform_open(const char *path, int flags, int mode)\n{\n#ifdef _WIN32\n    struct gc_arena gc = gc_new();\n    int fd = _wopen(wide_string(path, &gc), flags, mode);\n    gc_free(&gc);\n    return fd;\n#else\n    return open(path, flags, mode);\n#endif\n}\n\nint\nplatform_stat(const char *path, platform_stat_t *buf)\n{\n#ifdef _WIN32\n    struct gc_arena gc = gc_new();\n    int res = _wstat(wide_string(path, &gc), buf);\n    gc_free(&gc);\n    return res;\n#else\n    return stat(path, buf);\n#endif\n}\n\n/* create a temporary filename in directory */\nconst char *\nplatform_create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)\n{\n    int fd;\n    const char *retfname = NULL;\n    unsigned int attempts = 0;\n    char fname[256] = { 0 };\n    const char *fname_fmt = PACKAGE \"_%.*s_%08lx%08lx.tmp\";\n    const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8));\n\n    while (attempts < 6)\n    {\n        ++attempts;\n\n        if (!checked_snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len, prefix,\n                              (unsigned long)get_random(), (unsigned long)get_random()))\n        {\n            msg(M_WARN, \"ERROR: temporary filename too long\");\n            return NULL;\n        }\n\n        retfname = platform_gen_path(directory, fname, gc);\n        if (!retfname)\n        {\n            msg(M_WARN, \"Failed to create temporary filename and path\");\n            return NULL;\n        }\n\n        /* Atomically create the file.  Errors out if the file already\n         * exists.  */\n        fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);\n        if (fd != -1)\n        {\n            close(fd);\n            return retfname;\n        }\n        else if (fd == -1 && errno != EEXIST)\n        {\n            /* Something else went wrong, no need to retry.  */\n            msg(M_WARN | M_ERRNO, \"Could not create temporary file '%s'\", retfname);\n            return NULL;\n        }\n    }\n\n    msg(M_WARN, \"Failed to create temporary file after %i attempts\", attempts);\n    return NULL;\n}\n\n/*\n * Put a directory and filename together.\n */\nconst char *\nplatform_gen_path(const char *directory, const char *filename, struct gc_arena *gc)\n{\n#ifdef _WIN32\n    const int CC_PATH_RESERVED = CC_LESS_THAN | CC_GREATER_THAN | CC_COLON | CC_DOUBLE_QUOTE\n                                 | CC_SLASH | CC_BACKSLASH | CC_PIPE | CC_QUESTION_MARK\n                                 | CC_ASTERISK;\n#else\n    const int CC_PATH_RESERVED = CC_SLASH;\n#endif\n\n    if (!gc)\n    {\n        return NULL; /* Would leak memory otherwise */\n    }\n\n    const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc);\n\n    if (safe_filename && strcmp(safe_filename, \".\") && strcmp(safe_filename, \"..\")\n#ifdef _WIN32\n        && win_safe_filename(safe_filename)\n#endif\n    )\n    {\n        const size_t outsize = strlen(safe_filename) + (directory ? strlen(directory) : 0) + 16;\n        struct buffer out = alloc_buf_gc(outsize, gc);\n        char dirsep[2];\n\n        dirsep[0] = PATH_SEPARATOR;\n        dirsep[1] = '\\0';\n\n        if (directory)\n        {\n            buf_printf(&out, \"%s%s\", directory, dirsep);\n        }\n        buf_printf(&out, \"%s\", safe_filename);\n\n        return BSTR(&out);\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\nbool\nplatform_absolute_pathname(const char *pathname)\n{\n    if (pathname)\n    {\n        const int c = pathname[0];\n#ifdef _WIN32\n        return c == '\\\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\\\');\n#else\n        return c == '/';\n#endif\n    }\n    else\n    {\n        return false;\n    }\n}\n\n/* return true if filename can be opened for read */\nbool\nplatform_test_file(const char *filename)\n{\n    bool ret = false;\n    if (filename)\n    {\n        FILE *fp = platform_fopen(filename, \"r\");\n        if (fp)\n        {\n            fclose(fp);\n            ret = true;\n        }\n        else\n        {\n            if (errno == EACCES)\n            {\n                msg(M_WARN | M_ERRNO, \"Could not access file '%s'\", filename);\n            }\n        }\n    }\n\n    dmsg(D_TEST_FILE, \"TEST FILE '%s' [%d]\", filename ? filename : \"UNDEF\", ret);\n\n    return ret;\n}\n"
  },
  {
    "path": "src/openvpn/platform.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef PLATFORM_H\n#define PLATFORM_H\n\n#ifdef HAVE_SYS_TYPES_H\n#include <sys/types.h>\n#endif\n\n#ifdef HAVE_SYS_STAT_H\n#include <sys/stat.h>\n#endif\n\n#ifdef HAVE_UNISTD_H\n#include <unistd.h>\n#endif\n\n#ifdef HAVE_PWD_H\n#include <pwd.h>\n#endif\n\n#ifdef HAVE_GRP_H\n#include <grp.h>\n#endif\n\n#ifdef HAVE_STDIO_H\n#include <stdio.h>\n#endif\n\n#ifdef HAVE_GETRLIMIT\n#include <sys/resource.h>\n#endif\n\n#include \"basic.h\"\n#include \"buffer.h\"\n\n/* forward declared to avoid large amounts of extra includes */\nstruct context;\n\n/* Get/Set UID of process */\n\nstruct platform_state_user\n{\n#if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID)\n    const char *username;\n    uid_t uid;\n#endif\n    bool user_valid;\n};\n\n/* Get/Set GID of process */\n\nstruct platform_state_group\n{\n#if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID)\n    const char *groupname;\n    gid_t gid;\n#endif\n    bool group_valid;\n};\n\nbool platform_user_get(const char *username, struct platform_state_user *state);\n\nbool platform_group_get(const char *groupname, struct platform_state_group *state);\n\nvoid platform_user_group_set(const struct platform_state_user *user_state,\n                             const struct platform_state_group *group_state, struct context *c);\n\n\nvoid platform_chroot(const char *path);\n\nvoid platform_nice(int niceval);\n\nunsigned int platform_getpid(void);\n\nvoid platform_mlockall(bool print_msg); /* Disable paging */\n\nint platform_chdir(const char *dir);\n\n/** interpret the status code returned by execve() */\nbool platform_system_ok(int stat);\n\n/** Return an exit code if valid and between 0 and 255, -1 otherwise */\nint platform_ret_code(int stat);\n\nint platform_access(const char *path, int mode);\n\nvoid platform_sleep_milliseconds(unsigned int n);\n\n/* delete a file, return true if succeeded */\nbool platform_unlink(const char *filename);\n\nFILE *platform_fopen(const char *path, const char *mode);\n\nint platform_open(const char *path, int flags, int mode);\n\n#ifdef _WIN32\ntypedef struct _stat platform_stat_t;\n#else\ntypedef struct stat platform_stat_t;\n#endif\nint platform_stat(const char *path, platform_stat_t *buf);\n\n/**\n * Create a temporary file in directory, returns the filename of the created\n * file.\n */\nconst char *platform_create_temp_file(const char *directory, const char *prefix,\n                                      struct gc_arena *gc);\n\n/** Put a directory and filename together. */\nconst char *platform_gen_path(const char *directory, const char *filename, struct gc_arena *gc);\n\n/** Return true if pathname is absolute. */\nbool platform_absolute_pathname(const char *pathname);\n\n/** Return true if filename can be opened for read. */\nbool platform_test_file(const char *filename);\n\n#endif /* ifndef PLATFORM_H */\n"
  },
  {
    "path": "src/openvpn/plugin.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n#ifdef HAVE_CONFIG_VERSION_H\n#include \"config-version.h\"\n#endif\n\n#include \"syshead.h\"\n\n#ifdef ENABLE_PLUGIN\n\n#ifdef HAVE_DLFCN_H\n#include <dlfcn.h>\n#endif\n\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"misc.h\"\n#include \"plugin.h\"\n#include \"ssl_backend.h\"\n#include \"base64.h\"\n#include \"win32.h\"\n#include \"memdbg.h\"\n\n#define PLUGIN_SYMBOL_REQUIRED (1 << 0)\n\n/* used only for program aborts */\nstatic struct plugin_common *static_plugin_common = NULL; /* GLOBAL */\n\nstatic void\nplugin_show_string_array(msglvl_t msglevel, const char *name, const char *array[])\n{\n    int i;\n    for (i = 0; array[i]; ++i)\n    {\n        if (env_safe_to_print(array[i]))\n        {\n            msg(msglevel, \"%s[%d] = '%s'\", name, i, array[i]);\n        }\n    }\n}\n\nstatic void\nplugin_show_args_env(msglvl_t msglevel, const char *argv[], const char *envp[])\n{\n    if (check_debug_level(msglevel))\n    {\n        plugin_show_string_array(msglevel, \"ARGV\", argv);\n        plugin_show_string_array(msglevel, \"ENVP\", envp);\n    }\n}\n\nstatic const char *\nplugin_type_name(const int type)\n{\n    switch (type)\n    {\n        case OPENVPN_PLUGIN_UP:\n            return \"PLUGIN_UP\";\n\n        case OPENVPN_PLUGIN_DOWN:\n            return \"PLUGIN_DOWN\";\n\n        case OPENVPN_PLUGIN_ROUTE_UP:\n            return \"PLUGIN_ROUTE_UP\";\n\n        case OPENVPN_PLUGIN_IPCHANGE:\n            return \"PLUGIN_IPCHANGE\";\n\n        case OPENVPN_PLUGIN_TLS_VERIFY:\n            return \"PLUGIN_TLS_VERIFY\";\n\n        case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY:\n            return \"PLUGIN_AUTH_USER_PASS_VERIFY\";\n\n        case OPENVPN_PLUGIN_CLIENT_CONNECT:\n            return \"PLUGIN_CLIENT_CONNECT\";\n\n        case OPENVPN_PLUGIN_CLIENT_CONNECT_V2:\n            return \"PLUGIN_CLIENT_CONNECT_V2\";\n\n        case OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER:\n            return \"PLUGIN_CLIENT_CONNECT_DEFER\";\n\n        case OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2:\n            return \"PLUGIN_CLIENT_CONNECT_DEFER_V2\";\n\n        case OPENVPN_PLUGIN_CLIENT_DISCONNECT:\n            return \"PLUGIN_CLIENT_DISCONNECT\";\n\n        case OPENVPN_PLUGIN_LEARN_ADDRESS:\n            return \"PLUGIN_LEARN_ADDRESS\";\n\n        case OPENVPN_PLUGIN_TLS_FINAL:\n            return \"PLUGIN_TLS_FINAL\";\n\n        case OPENVPN_PLUGIN_ROUTE_PREDOWN:\n            return \"PLUGIN_ROUTE_PREDOWN\";\n\n        case OPENVPN_PLUGIN_CLIENT_CRRESPONSE:\n            return \"PLUGIN_CRRESPONSE\";\n\n        default:\n            return \"PLUGIN_???\";\n    }\n}\n\nstatic const char *\nplugin_mask_string(const unsigned int type_mask, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n    bool first = true;\n    int i;\n\n    for (i = 0; i < OPENVPN_PLUGIN_N; ++i)\n    {\n        if (OPENVPN_PLUGIN_MASK(i) & type_mask)\n        {\n            if (!first)\n            {\n                buf_printf(&out, \"|\");\n            }\n            buf_printf(&out, \"%s\", plugin_type_name(i));\n            first = false;\n        }\n    }\n    return BSTR(&out);\n}\n\nstatic inline unsigned int\nplugin_supported_types(void)\n{\n    return ((1 << OPENVPN_PLUGIN_N) - 1);\n}\n\nstruct plugin_option_list *\nplugin_option_list_new(struct gc_arena *gc)\n{\n    struct plugin_option_list *ret;\n    ALLOC_OBJ_CLEAR_GC(ret, struct plugin_option_list, gc);\n    return ret;\n}\n\nbool\nplugin_option_list_add(struct plugin_option_list *list, char **p, struct gc_arena *gc)\n{\n    if (list->n < MAX_PLUGINS)\n    {\n        struct plugin_option *o = &list->plugins[list->n++];\n        o->argv = make_extended_arg_array(p, false, gc);\n        if (o->argv[0])\n        {\n            o->so_pathname = o->argv[0];\n        }\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\n#ifndef ENABLE_SMALL\nvoid\nplugin_option_list_print(const struct plugin_option_list *list, msglvl_t msglevel)\n{\n    int i;\n    struct gc_arena gc = gc_new();\n\n    for (i = 0; i < list->n; ++i)\n    {\n        const struct plugin_option *o = &list->plugins[i];\n        msg(msglevel, \"  plugin[%d] %s '%s'\", i, o->so_pathname,\n            print_argv(o->argv, &gc, PA_BRACKET));\n    }\n\n    gc_free(&gc);\n}\n#endif\n\n#ifndef _WIN32\n\nstatic void\nlibdl_resolve_symbol(void *handle, void **dest, const char *symbol, const char *plugin_name,\n                     const unsigned int flags)\n{\n    *dest = dlsym(handle, symbol);\n    if ((flags & PLUGIN_SYMBOL_REQUIRED) && !*dest)\n    {\n        msg(M_FATAL, \"PLUGIN: could not find required symbol '%s' in plugin shared object %s: %s\",\n            symbol, plugin_name, dlerror());\n    }\n}\n\n#else  /* ifndef _WIN32 */\n\nstatic void\ndll_resolve_symbol(HMODULE module, void **dest, const char *symbol, const char *plugin_name,\n                   const unsigned int flags)\n{\n    *dest = GetProcAddress(module, symbol);\n    if ((flags & PLUGIN_SYMBOL_REQUIRED) && !*dest)\n    {\n        msg(M_FATAL, \"PLUGIN: could not find required symbol '%s' in plugin DLL %s\", symbol,\n            plugin_name);\n    }\n}\n\n#endif /* ifndef _WIN32 */\n\nstatic void\nplugin_init_item(struct plugin *p, const struct plugin_option *o)\n{\n    struct gc_arena gc = gc_new();\n    bool rel = false;\n\n    p->so_pathname = o->so_pathname;\n    p->plugin_type_mask = plugin_supported_types();\n\n#ifndef _WIN32\n\n    p->handle = NULL;\n\n    /* If the plug-in filename is not an absolute path,\n     * or beginning with '.', it should use the PLUGIN_LIBDIR\n     * as the base directory for loading the plug-in.\n     *\n     * This means the following scenarios are loaded from these places:\n     *    --plugin fancyplug.so              -> $PLUGIN_LIBDIR/fancyplug.so\n     *    --plugin my/fancyplug.so           -> $PLUGIN_LIBDIR/my/fancyplug.so\n     *    --plugin ./fancyplug.so            -> $CWD/fancyplug.so\n     *    --plugin /usr/lib/my/fancyplug.so  -> /usr/lib/my/fancyplug.so\n     *\n     * Please note that $CWD means the directory OpenVPN is either started from\n     * or the directory OpenVPN have changed into using --cd before --plugin\n     * was parsed.\n     *\n     */\n    if (!platform_absolute_pathname(p->so_pathname) && p->so_pathname[0] != '.')\n    {\n        char full[PATH_MAX];\n\n        snprintf(full, sizeof(full), \"%s/%s\", PLUGIN_LIBDIR, p->so_pathname);\n        p->handle = dlopen(full, RTLD_NOW);\n    }\n    else\n    {\n        rel = !platform_absolute_pathname(p->so_pathname);\n        p->handle = dlopen(p->so_pathname, RTLD_NOW);\n    }\n    if (!p->handle)\n    {\n        msg(M_ERR, \"PLUGIN_INIT: could not load plugin shared object %s: %s\", p->so_pathname,\n            dlerror());\n    }\n\n#define PLUGIN_SYM(var, name, flags) \\\n    libdl_resolve_symbol(p->handle, (void *)&p->var, name, p->so_pathname, flags)\n\n#else /* ifndef _WIN32 */\n\n    WCHAR *wpath = wide_string(p->so_pathname, &gc);\n    WCHAR normalized_plugin_path[MAX_PATH] = { 0 };\n    /* Normalize the plugin path, converting any relative paths to absolute paths. */\n    if (!GetFullPathNameW(wpath, MAX_PATH, normalized_plugin_path, NULL))\n    {\n        msg(M_ERR, \"PLUGIN_INIT: could not load plugin DLL: %ls. Failed to normalize plugin path.\",\n            wpath);\n    }\n\n    if (!plugin_in_trusted_dir(normalized_plugin_path))\n    {\n        msg(M_FATAL,\n            \"PLUGIN_INIT: could not load plugin DLL: %ls. The DLL is not in a trusted directory.\",\n            normalized_plugin_path);\n    }\n\n    p->module = LoadLibraryW(normalized_plugin_path);\n    if (!p->module)\n    {\n        msg(M_ERR, \"PLUGIN_INIT: could not load plugin DLL: %ls\", normalized_plugin_path);\n    }\n\n#define PLUGIN_SYM(var, name, flags) \\\n    dll_resolve_symbol(p->module, (void *)&p->var, name, p->so_pathname, flags)\n\n#endif /* ifndef _WIN32 */\n\n    PLUGIN_SYM(open1, \"openvpn_plugin_open_v1\", 0);\n    PLUGIN_SYM(open2, \"openvpn_plugin_open_v2\", 0);\n    PLUGIN_SYM(open3, \"openvpn_plugin_open_v3\", 0);\n    PLUGIN_SYM(func1, \"openvpn_plugin_func_v1\", 0);\n    PLUGIN_SYM(func2, \"openvpn_plugin_func_v2\", 0);\n    PLUGIN_SYM(func3, \"openvpn_plugin_func_v3\", 0);\n    PLUGIN_SYM(close, \"openvpn_plugin_close_v1\", PLUGIN_SYMBOL_REQUIRED);\n    PLUGIN_SYM(abort, \"openvpn_plugin_abort_v1\", 0);\n    PLUGIN_SYM(client_constructor, \"openvpn_plugin_client_constructor_v1\", 0);\n    PLUGIN_SYM(client_destructor, \"openvpn_plugin_client_destructor_v1\", 0);\n    PLUGIN_SYM(min_version_required, \"openvpn_plugin_min_version_required_v1\", 0);\n    PLUGIN_SYM(initialization_point, \"openvpn_plugin_select_initialization_point_v1\", 0);\n\n    if (!p->open1 && !p->open2 && !p->open3)\n    {\n        msg(M_FATAL, \"PLUGIN: symbol openvpn_plugin_open_vX is undefined in plugin: %s\",\n            p->so_pathname);\n    }\n\n    if (!p->func1 && !p->func2 && !p->func3)\n    {\n        msg(M_FATAL, \"PLUGIN: symbol openvpn_plugin_func_vX is undefined in plugin: %s\",\n            p->so_pathname);\n    }\n\n    /*\n     * Verify that we are sufficiently up-to-date to handle the plugin\n     */\n    if (p->min_version_required)\n    {\n        const int plugin_needs_version = (*p->min_version_required)();\n        if (plugin_needs_version > OPENVPN_PLUGIN_VERSION)\n        {\n            msg(M_FATAL,\n                \"PLUGIN_INIT: plugin needs interface version %d, but this version of OpenVPN only supports version %d: %s\",\n                plugin_needs_version, OPENVPN_PLUGIN_VERSION, p->so_pathname);\n        }\n    }\n\n    if (p->initialization_point)\n    {\n        p->requested_initialization_point = (*p->initialization_point)();\n    }\n    else\n    {\n        p->requested_initialization_point = OPENVPN_PLUGIN_INIT_PRE_DAEMON;\n    }\n\n    if (rel)\n    {\n        msg(M_WARN,\n            \"WARNING: plugin '%s' specified by a relative pathname -- using an absolute pathname would be more secure\",\n            p->so_pathname);\n    }\n\n    p->initialized = true;\n\n    gc_free(&gc);\n}\n\nstatic void\nplugin_vlog(openvpn_plugin_log_flags_t flags, const char *name, const char *format, va_list arglist)\n{\n    unsigned int msg_flags = 0;\n\n    if (!format)\n    {\n        return;\n    }\n\n    if (!name || name[0] == '\\0')\n    {\n        msg(D_PLUGIN_DEBUG, \"PLUGIN: suppressed log message from plugin with unknown name\");\n        return;\n    }\n\n    if (flags & PLOG_ERR)\n    {\n        msg_flags = M_INFO | M_NONFATAL;\n    }\n    else if (flags & PLOG_WARN)\n    {\n        msg_flags = M_INFO | M_WARN;\n    }\n    else if (flags & PLOG_NOTE)\n    {\n        msg_flags = M_INFO;\n    }\n    else if (flags & PLOG_DEBUG)\n    {\n        msg_flags = D_PLUGIN_DEBUG;\n    }\n\n    if (flags & PLOG_ERRNO)\n    {\n        msg_flags |= M_ERRNO;\n    }\n    if (flags & PLOG_NOMUTE)\n    {\n        msg_flags |= M_NOMUTE;\n    }\n\n    if (msg_test(msg_flags))\n    {\n        struct gc_arena gc;\n        char *msg_fmt;\n\n        /* Never add instance prefix; not thread safe */\n        msg_flags |= M_NOIPREFIX;\n\n        gc_init(&gc);\n        msg_fmt = gc_malloc(ERR_BUF_SIZE, false, &gc);\n        snprintf(msg_fmt, ERR_BUF_SIZE, \"PLUGIN %s: %s\", name, format);\n        x_msg_va(msg_flags, msg_fmt, arglist);\n\n        gc_free(&gc);\n    }\n}\n\nstatic void\nplugin_log(openvpn_plugin_log_flags_t flags, const char *name, const char *format, ...)\n{\n    va_list arglist;\n    va_start(arglist, format);\n    plugin_vlog(flags, name, format, arglist);\n    va_end(arglist);\n}\n\nstatic struct openvpn_plugin_callbacks callbacks = {\n    plugin_log,\n    plugin_vlog,\n    secure_memzero,        /* plugin_secure_memzero */\n    openvpn_base64_encode, /* plugin_base64_encode */\n    openvpn_base64_decode, /* plugin_base64_decode */\n};\n\n\n/* Provide a wrapper macro for a version patch level string to plug-ins.\n * This is located here purely to not make the code too messy with #ifndef\n * inside a struct declaration\n */\n#ifndef CONFIGURE_GIT_REVISION\n#define _OPENVPN_PATCH_LEVEL OPENVPN_VERSION_PATCH\n#else\n#define _OPENVPN_PATCH_LEVEL \"git:\" CONFIGURE_GIT_REVISION CONFIGURE_GIT_FLAGS\n#endif\n\nstatic void\nplugin_open_item(struct plugin *p, const struct plugin_option *o,\n                 struct openvpn_plugin_string_list **retlist, const char **envp,\n                 const int init_point)\n{\n    ASSERT(p->initialized);\n\n    /* clear return list */\n    if (retlist)\n    {\n        *retlist = NULL;\n    }\n\n    if (!p->plugin_handle && init_point == p->requested_initialization_point)\n    {\n        struct gc_arena gc = gc_new();\n\n        dmsg(D_PLUGIN_DEBUG, \"PLUGIN_INIT: PRE\");\n        plugin_show_args_env(D_PLUGIN_DEBUG, o->argv, envp);\n\n        /*\n         * Call the plugin initialization\n         */\n        if (p->open3)\n        {\n            struct openvpn_plugin_args_open_in args = { p->plugin_type_mask,\n                                                        (const char **const)o->argv,\n                                                        (const char **const)envp,\n                                                        &callbacks,\n                                                        SSLAPI,\n                                                        PACKAGE_VERSION,\n                                                        OPENVPN_VERSION_MAJOR,\n                                                        OPENVPN_VERSION_MINOR,\n                                                        _OPENVPN_PATCH_LEVEL };\n            struct openvpn_plugin_args_open_return retargs;\n\n            CLEAR(retargs);\n            retargs.return_list = retlist;\n            if ((*p->open3)(OPENVPN_PLUGINv3_STRUCTVER, &args, &retargs)\n                == OPENVPN_PLUGIN_FUNC_SUCCESS)\n            {\n                p->plugin_type_mask = retargs.type_mask;\n                p->plugin_handle = retargs.handle;\n            }\n            else\n            {\n                p->plugin_handle = NULL;\n            }\n        }\n        else if (p->open2)\n        {\n            p->plugin_handle = (*p->open2)(&p->plugin_type_mask, o->argv, envp, retlist);\n        }\n        else if (p->open1)\n        {\n            p->plugin_handle = (*p->open1)(&p->plugin_type_mask, o->argv, envp);\n        }\n        else\n        {\n            ASSERT(0);\n        }\n\n        msg(D_PLUGIN, \"PLUGIN_INIT: POST %s '%s' intercepted=%s %s\", p->so_pathname,\n            print_argv(o->argv, &gc, PA_BRACKET), plugin_mask_string(p->plugin_type_mask, &gc),\n            (retlist && *retlist) ? \"[RETLIST]\" : \"\");\n\n        if ((p->plugin_type_mask | plugin_supported_types()) != plugin_supported_types())\n        {\n            msg(M_FATAL,\n                \"PLUGIN_INIT: plugin %s expressed interest in unsupported plugin types: [want=0x%08x, have=0x%08x]\",\n                p->so_pathname, p->plugin_type_mask, plugin_supported_types());\n        }\n\n        if (p->plugin_handle == NULL)\n        {\n            msg(M_FATAL, \"PLUGIN_INIT: plugin initialization function failed: %s\", p->so_pathname);\n        }\n\n        gc_free(&gc);\n    }\n}\n\nstatic int\nplugin_call_item(const struct plugin *p, void *per_client_context, const int type,\n                 const struct argv *av, struct openvpn_plugin_string_list **retlist,\n                 const char **envp, int certdepth, openvpn_x509_cert_t *current_cert)\n{\n    int status = OPENVPN_PLUGIN_FUNC_SUCCESS;\n\n    /* clear return list */\n    if (retlist)\n    {\n        *retlist = NULL;\n    }\n\n    if (p->plugin_handle && (p->plugin_type_mask & OPENVPN_PLUGIN_MASK(type)))\n    {\n        struct gc_arena gc = gc_new();\n        struct argv a = argv_insert_head(av, p->so_pathname);\n\n        dmsg(D_PLUGIN_DEBUG, \"PLUGIN_CALL: PRE type=%s\", plugin_type_name(type));\n        plugin_show_args_env(D_PLUGIN_DEBUG, (const char **)a.argv, envp);\n\n        /*\n         * Call the plugin work function\n         */\n        if (p->func3)\n        {\n            struct openvpn_plugin_args_func_in args = { type,\n                                                        (const char **const)a.argv,\n                                                        (const char **const)envp,\n                                                        p->plugin_handle,\n                                                        per_client_context,\n                                                        (current_cert ? certdepth : -1),\n                                                        current_cert };\n\n            struct openvpn_plugin_args_func_return retargs;\n\n            CLEAR(retargs);\n            retargs.return_list = retlist;\n            status = (*p->func3)(OPENVPN_PLUGINv3_STRUCTVER, &args, &retargs);\n        }\n        else if (p->func2)\n        {\n            status = (*p->func2)(p->plugin_handle, type, (const char **)a.argv, envp,\n                                 per_client_context, retlist);\n        }\n        else if (p->func1)\n        {\n            status = (*p->func1)(p->plugin_handle, type, (const char **)a.argv, envp);\n        }\n        else\n        {\n            ASSERT(0);\n        }\n\n        msg(D_PLUGIN, \"PLUGIN_CALL: POST %s/%s status=%d\", p->so_pathname, plugin_type_name(type),\n            status);\n\n        if (status == OPENVPN_PLUGIN_FUNC_ERROR)\n        {\n            msg(M_WARN, \"PLUGIN_CALL: plugin function %s failed with status %d: %s\",\n                plugin_type_name(type), status, p->so_pathname);\n        }\n\n        argv_free(&a);\n        gc_free(&gc);\n    }\n    return status;\n}\n\nstatic void\nplugin_close_item(struct plugin *p)\n{\n    if (p->initialized)\n    {\n        msg(D_PLUGIN, \"PLUGIN_CLOSE: %s\", p->so_pathname);\n\n        /*\n         * Call the plugin close function\n         */\n        if (p->plugin_handle)\n        {\n            (*p->close)(p->plugin_handle);\n        }\n\n#ifndef _WIN32\n        if (dlclose(p->handle))\n        {\n            msg(M_WARN, \"PLUGIN_CLOSE: dlclose() failed on plugin: %s\", p->so_pathname);\n        }\n#elif defined(_WIN32)\n        if (!FreeLibrary(p->module))\n        {\n            msg(M_WARN, \"PLUGIN_CLOSE: FreeLibrary() failed on plugin: %s\", p->so_pathname);\n        }\n#endif\n\n        p->initialized = false;\n    }\n}\n\nstatic void\nplugin_abort_item(const struct plugin *p)\n{\n    /*\n     * Call the plugin abort function\n     */\n    if (p->abort)\n    {\n        (*p->abort)(p->plugin_handle);\n    }\n}\n\nstatic void\nplugin_per_client_init(const struct plugin_common *pc, struct plugin_per_client *cli,\n                       const int init_point)\n{\n    const int n = pc->n;\n    int i;\n\n    for (i = 0; i < n; ++i)\n    {\n        const struct plugin *p = &pc->plugins[i];\n        if (p->plugin_handle && (init_point < 0 || init_point == p->requested_initialization_point)\n            && p->client_constructor)\n        {\n            cli->per_client_context[i] = (*p->client_constructor)(p->plugin_handle);\n        }\n    }\n}\n\nstatic void\nplugin_per_client_destroy(const struct plugin_common *pc, struct plugin_per_client *cli)\n{\n    const int n = pc->n;\n    int i;\n\n    for (i = 0; i < n; ++i)\n    {\n        const struct plugin *p = &pc->plugins[i];\n        void *cc = cli->per_client_context[i];\n\n        if (p->client_destructor && cc)\n        {\n            (*p->client_destructor)(p->plugin_handle, cc);\n        }\n    }\n    CLEAR(*cli);\n}\n\nstruct plugin_list *\nplugin_list_inherit(const struct plugin_list *src)\n{\n    struct plugin_list *pl;\n    ALLOC_OBJ_CLEAR(pl, struct plugin_list);\n    pl->common = src->common;\n    ASSERT(pl->common);\n    plugin_per_client_init(pl->common, &pl->per_client, -1);\n    return pl;\n}\n\nstatic struct plugin_common *\nplugin_common_init(const struct plugin_option_list *list)\n{\n    int i;\n    struct plugin_common *pc;\n\n    ALLOC_OBJ_CLEAR(pc, struct plugin_common);\n\n    for (i = 0; i < list->n; ++i)\n    {\n        plugin_init_item(&pc->plugins[i], &list->plugins[i]);\n        pc->n = i + 1;\n    }\n\n    static_plugin_common = pc;\n    return pc;\n}\n\nstatic void\nplugin_common_open(struct plugin_common *pc, const struct plugin_option_list *list,\n                   struct plugin_return *pr, const struct env_set *es, const int init_point)\n{\n    struct gc_arena gc = gc_new();\n    int i;\n    const char **envp;\n\n    envp = make_env_array(es, false, &gc);\n\n    if (pr)\n    {\n        plugin_return_init(pr);\n    }\n\n    for (i = 0; i < pc->n; ++i)\n    {\n        plugin_open_item(&pc->plugins[i], &list->plugins[i], pr ? &pr->list[i] : NULL, envp,\n                         init_point);\n    }\n\n    if (pr)\n    {\n        pr->n = i;\n    }\n\n    gc_free(&gc);\n}\n\nstatic void\nplugin_common_close(struct plugin_common *pc)\n{\n    static_plugin_common = NULL;\n    if (pc)\n    {\n        int i;\n\n        for (i = 0; i < pc->n; ++i)\n        {\n            plugin_close_item(&pc->plugins[i]);\n        }\n        free(pc);\n    }\n}\n\nstruct plugin_list *\nplugin_list_init(const struct plugin_option_list *list)\n{\n    struct plugin_list *pl;\n    ALLOC_OBJ_CLEAR(pl, struct plugin_list);\n    pl->common = plugin_common_init(list);\n    pl->common_owned = true;\n    return pl;\n}\n\nvoid\nplugin_list_open(struct plugin_list *pl, const struct plugin_option_list *list,\n                 struct plugin_return *pr, const struct env_set *es, const int init_point)\n{\n    plugin_common_open(pl->common, list, pr, es, init_point);\n    plugin_per_client_init(pl->common, &pl->per_client, init_point);\n}\n\nint\nplugin_call_ssl(const struct plugin_list *pl, const int type, const struct argv *av,\n                struct plugin_return *pr, struct env_set *es, int certdepth,\n                openvpn_x509_cert_t *current_cert)\n{\n    if (pr)\n    {\n        plugin_return_init(pr);\n    }\n\n    if (plugin_defined(pl, type))\n    {\n        struct gc_arena gc = gc_new();\n        int i;\n        const char **envp;\n        const int n = plugin_n(pl);\n        bool error = false;\n        bool deferred_auth_done = false;\n\n        setenv_del(es, \"script_type\");\n        envp = make_env_array(es, false, &gc);\n\n        for (i = 0; i < n; ++i)\n        {\n            const int status =\n                plugin_call_item(&pl->common->plugins[i], pl->per_client.per_client_context[i],\n                                 type, av, pr ? &pr->list[i] : NULL, envp, certdepth, current_cert);\n            switch (status)\n            {\n                case OPENVPN_PLUGIN_FUNC_SUCCESS:\n                    break;\n\n                case OPENVPN_PLUGIN_FUNC_DEFERRED:\n                    if ((type == OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY) && deferred_auth_done)\n                    {\n                        /*\n                         * Do not allow deferred auth if a deferred auth has\n                         * already been started.  This should allow a single\n                         * deferred auth call to happen, with one or more\n                         * auth calls with an instant authentication result.\n                         *\n                         * The plug-in API is not designed for multiple\n                         * deferred authentications to happen, as the\n                         * auth_control_file file will be shared across all\n                         * the plug-ins.\n                         *\n                         * Since this is considered a critical configuration\n                         * error, we bail out and exit the OpenVPN process.\n                         */\n                        error = true;\n                        msg(M_FATAL, \"Exiting due to multiple authentication plug-ins \"\n                                     \"performing deferred authentication.  Only one \"\n                                     \"authentication plug-in doing deferred auth is \"\n                                     \"allowed.  Ignoring the result and stopping now, \"\n                                     \"the current authentication result is not to be \"\n                                     \"trusted.\");\n                        break;\n                    }\n                    deferred_auth_done = true;\n                    break;\n\n                default:\n                    error = true;\n                    break;\n            }\n        }\n\n        if (pr)\n        {\n            pr->n = i;\n        }\n\n        gc_free(&gc);\n\n        if (error)\n        {\n            return OPENVPN_PLUGIN_FUNC_ERROR;\n        }\n        else if (deferred_auth_done)\n        {\n            return OPENVPN_PLUGIN_FUNC_DEFERRED;\n        }\n    }\n\n    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n}\n\nvoid\nplugin_list_close(struct plugin_list *pl)\n{\n    if (pl)\n    {\n        if (pl->common)\n        {\n            plugin_per_client_destroy(pl->common, &pl->per_client);\n\n            if (pl->common_owned)\n            {\n                plugin_common_close(pl->common);\n            }\n        }\n\n        free(pl);\n    }\n}\n\nvoid\nplugin_abort(void)\n{\n    struct plugin_common *pc = static_plugin_common;\n    static_plugin_common = NULL;\n    if (pc)\n    {\n        int i;\n\n        for (i = 0; i < pc->n; ++i)\n        {\n            plugin_abort_item(&pc->plugins[i]);\n        }\n    }\n}\n\nbool\nplugin_defined(const struct plugin_list *pl, const int type)\n{\n    bool ret = false;\n\n    if (pl)\n    {\n        const struct plugin_common *pc = pl->common;\n\n        if (pc)\n        {\n            int i;\n            const unsigned int mask = OPENVPN_PLUGIN_MASK(type);\n            for (i = 0; i < pc->n; ++i)\n            {\n                if (pc->plugins[i].plugin_type_mask & mask)\n                {\n                    ret = true;\n                    break;\n                }\n            }\n        }\n    }\n    return ret;\n}\n\n/*\n * Plugin return functions\n */\n\nstatic void\nopenvpn_plugin_string_list_item_free(struct openvpn_plugin_string_list *l)\n{\n    if (l)\n    {\n        free(l->name);\n        string_clear(l->value);\n        free(l->value);\n        free(l);\n    }\n}\n\nstatic void\nopenvpn_plugin_string_list_free(struct openvpn_plugin_string_list *l)\n{\n    struct openvpn_plugin_string_list *next;\n    while (l)\n    {\n        next = l->next;\n        openvpn_plugin_string_list_item_free(l);\n        l = next;\n    }\n}\n\nstatic struct openvpn_plugin_string_list *\nopenvpn_plugin_string_list_find(struct openvpn_plugin_string_list *l, const char *name)\n{\n    while (l)\n    {\n        if (!strcmp(l->name, name))\n        {\n            return l;\n        }\n        l = l->next;\n    }\n    return NULL;\n}\n\nvoid\nplugin_return_get_column(const struct plugin_return *src, struct plugin_return *dest,\n                         const char *colname)\n{\n    int i;\n\n    dest->n = 0;\n    for (i = 0; i < src->n; ++i)\n    {\n        dest->list[i] = openvpn_plugin_string_list_find(src->list[i], colname);\n    }\n    dest->n = i;\n}\n\nvoid\nplugin_return_free(struct plugin_return *pr)\n{\n    int i;\n    for (i = 0; i < pr->n; ++i)\n    {\n        openvpn_plugin_string_list_free(pr->list[i]);\n    }\n    pr->n = 0;\n}\n\n#ifdef ENABLE_DEBUG\nvoid\nplugin_return_print(const msglvl_t msglevel, const char *prefix, const struct plugin_return *pr)\n{\n    int i;\n    msg(msglevel, \"PLUGIN_RETURN_PRINT %s\", prefix);\n    for (i = 0; i < pr->n; ++i)\n    {\n        struct openvpn_plugin_string_list *l = pr->list[i];\n        int count = 0;\n\n        msg(msglevel, \"PLUGIN #%d (%s)\", i, prefix);\n        while (l)\n        {\n            msg(msglevel, \"[%d] '%s' -> '%s'\", ++count, l->name, l->value);\n            l = l->next;\n        }\n    }\n}\n#endif /* ifdef ENABLE_DEBUG */\n#endif /* ENABLE_PLUGIN */\n"
  },
  {
    "path": "src/openvpn/plugin.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * plug-in support, using dynamically loaded libraries\n */\n\n#ifndef OPENVPN_PLUGIN_H\n#define OPENVPN_PLUGIN_H\n\n#ifdef ENABLE_CRYPTO_OPENSSL\n#include \"ssl_verify_openssl.h\"\n#endif\n#ifdef ENABLE_CRYPTO_MBEDTLS\n#include \"ssl_verify_mbedtls.h\"\n#endif\n#include \"openvpn-plugin.h\"\n\n#ifdef ENABLE_PLUGIN\n\n#include \"misc.h\"\n\n#define MAX_PLUGINS 16\n\nstruct plugin_option\n{\n    const char *so_pathname;\n    const char **argv;\n};\n\nstruct plugin_option_list\n{\n    int n;\n    struct plugin_option plugins[MAX_PLUGINS];\n};\n\nstruct plugin\n{\n    bool initialized;\n    const char *so_pathname;\n    unsigned int plugin_type_mask;\n    int requested_initialization_point;\n\n#ifndef _WIN32\n    void *handle;\n#else\n    HMODULE module;\n#endif\n\n    openvpn_plugin_open_v1 open1;\n    openvpn_plugin_open_v2 open2;\n    openvpn_plugin_open_v3 open3;\n    openvpn_plugin_func_v1 func1;\n    openvpn_plugin_func_v2 func2;\n    openvpn_plugin_func_v3 func3;\n    openvpn_plugin_close_v1 close;\n    openvpn_plugin_abort_v1 abort;\n    openvpn_plugin_client_constructor_v1 client_constructor;\n    openvpn_plugin_client_destructor_v1 client_destructor;\n    openvpn_plugin_min_version_required_v1 min_version_required;\n    openvpn_plugin_select_initialization_point_v1 initialization_point;\n\n    openvpn_plugin_handle_t plugin_handle;\n};\n\nstruct plugin_per_client\n{\n    void *per_client_context[MAX_PLUGINS];\n};\n\nstruct plugin_common\n{\n    int n;\n    struct plugin plugins[MAX_PLUGINS];\n};\n\nstruct plugin_list\n{\n    struct plugin_per_client per_client;\n    struct plugin_common *common;\n    bool common_owned;\n};\n\nstruct plugin_return\n{\n    int n;\n    struct openvpn_plugin_string_list *list[MAX_PLUGINS];\n};\n\nstruct plugin_option_list *plugin_option_list_new(struct gc_arena *gc);\n\nbool plugin_option_list_add(struct plugin_option_list *list, char **p, struct gc_arena *gc);\n\n#ifndef ENABLE_SMALL\nvoid plugin_option_list_print(const struct plugin_option_list *list, msglvl_t msglevel);\n\n#endif\n\nstruct plugin_list *plugin_list_init(const struct plugin_option_list *list);\n\nvoid plugin_list_open(struct plugin_list *pl, const struct plugin_option_list *list,\n                      struct plugin_return *pr, const struct env_set *es, const int init_point);\n\nstruct plugin_list *plugin_list_inherit(const struct plugin_list *src);\n\nint plugin_call_ssl(const struct plugin_list *pl, const int type, const struct argv *av,\n                    struct plugin_return *pr, struct env_set *es, int current_cert_depth,\n                    openvpn_x509_cert_t *current_cert);\n\nvoid plugin_list_close(struct plugin_list *pl);\n\nbool plugin_defined(const struct plugin_list *pl, const int type);\n\nvoid plugin_return_get_column(const struct plugin_return *src, struct plugin_return *dest,\n                              const char *colname);\n\nvoid plugin_return_free(struct plugin_return *pr);\n\n#ifdef ENABLE_DEBUG\nvoid plugin_return_print(const msglvl_t msglevel, const char *prefix, const struct plugin_return *pr);\n\n#endif\n\nstatic inline int\nplugin_n(const struct plugin_list *pl)\n{\n    if (pl && pl->common)\n    {\n        return pl->common->n;\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nstatic inline bool\nplugin_return_defined(const struct plugin_return *pr)\n{\n    return pr->n >= 0;\n}\n\nstatic inline void\nplugin_return_init(struct plugin_return *pr)\n{\n    pr->n = 0;\n}\n\n#else  /* ifdef ENABLE_PLUGIN */\nstruct plugin_list\n{\n    int dummy;\n};\nstruct plugin_return\n{\n    int dummy;\n};\n\nstatic inline bool\nplugin_defined(const struct plugin_list *pl, const int type)\n{\n    return false;\n}\n\nstatic inline int\nplugin_call_ssl(const struct plugin_list *pl, const int type, const struct argv *av,\n                struct plugin_return *pr, struct env_set *es, int current_cert_depth,\n                openvpn_x509_cert_t *current_cert)\n{\n    return 0;\n}\n\n#endif /* ENABLE_PLUGIN */\n\nstatic inline int\nplugin_call(const struct plugin_list *pl, const int type, const struct argv *av,\n            struct plugin_return *pr, struct env_set *es)\n{\n    return plugin_call_ssl(pl, type, av, pr, es, -1, NULL);\n}\n\nvoid plugin_abort(void);\n\n#endif /* OPENVPN_PLUGIN_H */\n"
  },
  {
    "path": "src/openvpn/pool.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"pool.h\"\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"socket_util.h\"\n#include \"otime.h\"\n\n#include \"memdbg.h\"\n\nstatic void\nifconfig_pool_entry_free(struct ifconfig_pool_entry *ipe, bool hard)\n{\n    ipe->in_use = false;\n    if (hard && ipe->common_name)\n    {\n        free(ipe->common_name);\n        ipe->common_name = NULL;\n    }\n    if (hard)\n    {\n        ipe->last_release = 0;\n    }\n    else\n    {\n        ipe->last_release = now;\n    }\n}\n\nstatic int\nifconfig_pool_find(struct ifconfig_pool *pool, const char *common_name)\n{\n    int i;\n    time_t earliest_release = 0;\n    int previous_usage = -1;\n    int new_usage = -1;\n\n    for (i = 0; i < pool->size; ++i)\n    {\n        struct ifconfig_pool_entry *ipe = &pool->list[i];\n        if (!ipe->in_use)\n        {\n            /*\n             * If duplicate_cn mode, take first available IP address\n             */\n            if (pool->duplicate_cn)\n            {\n                new_usage = i;\n                break;\n            }\n\n            /*\n             * Keep track of the unused IP address entry which\n             * was released earliest.\n             */\n            if ((new_usage == -1 || ipe->last_release < earliest_release) && !ipe->fixed)\n            {\n                earliest_release = ipe->last_release;\n                new_usage = i;\n            }\n\n            /*\n             * Keep track of a possible allocation to us\n             * from an earlier session.\n             */\n            if (previous_usage < 0 && common_name && ipe->common_name\n                && !strcmp(common_name, ipe->common_name))\n            {\n                previous_usage = i;\n            }\n        }\n    }\n\n    if (previous_usage >= 0)\n    {\n        return previous_usage;\n    }\n\n    if (new_usage >= 0)\n    {\n        return new_usage;\n    }\n\n    return -1;\n}\n\n/*\n * Verify start/end range\n */\nbool\nifconfig_pool_verify_range(const msglvl_t msglevel, const in_addr_t start, const in_addr_t end)\n{\n    struct gc_arena gc = gc_new();\n    bool ret = true;\n\n    if (start > end)\n    {\n        msg(msglevel, \"--ifconfig-pool start IP [%s] is greater than end IP [%s]\",\n            print_in_addr_t(start, 0, &gc), print_in_addr_t(end, 0, &gc));\n        ret = false;\n    }\n    if (end - start >= IFCONFIG_POOL_MAX)\n    {\n        msg(msglevel,\n            \"--ifconfig-pool address range is too large [%s -> %s].  Current maximum is %d addresses, as defined by IFCONFIG_POOL_MAX variable.\",\n            print_in_addr_t(start, 0, &gc), print_in_addr_t(end, 0, &gc), IFCONFIG_POOL_MAX);\n        ret = false;\n    }\n    gc_free(&gc);\n    return ret;\n}\n\nstruct ifconfig_pool *\nifconfig_pool_init(const bool ipv4_pool, enum pool_type type, in_addr_t start, in_addr_t end,\n                   const bool duplicate_cn, const bool ipv6_pool, const struct in6_addr ipv6_base,\n                   const int ipv6_netbits)\n{\n    struct gc_arena gc = gc_new();\n    struct ifconfig_pool *pool = NULL;\n    int pool_ipv4_size = -1, pool_ipv6_size = -1;\n\n    ASSERT(start <= end && end - start < IFCONFIG_POOL_MAX);\n    ALLOC_OBJ_CLEAR(pool, struct ifconfig_pool);\n\n    pool->duplicate_cn = duplicate_cn;\n\n    pool->ipv4.enabled = ipv4_pool;\n\n    if (pool->ipv4.enabled)\n    {\n        pool->ipv4.type = type;\n        switch (pool->ipv4.type)\n        {\n            case IFCONFIG_POOL_30NET:\n                pool->ipv4.base = start & ~3u;\n                pool_ipv4_size = (((end | 3) + 1) - pool->ipv4.base) >> 2;\n                break;\n\n            case IFCONFIG_POOL_INDIV:\n                pool->ipv4.base = start;\n                pool_ipv4_size = end - start + 1;\n                break;\n\n            default:\n                ASSERT(0);\n        }\n\n        if (pool_ipv4_size < 2)\n        {\n            msg(M_FATAL, \"IPv4 pool size is too small (%d), must be at least 2\", pool_ipv4_size);\n        }\n\n        msg(D_IFCONFIG_POOL, \"IFCONFIG POOL IPv4: base=%s size=%d\",\n            print_in_addr_t(pool->ipv4.base, 0, &gc), pool_ipv4_size);\n\n        pool->size = pool_ipv4_size;\n    }\n\n    /* IPv6 pools are always \"INDIV\" type */\n    pool->ipv6.enabled = ipv6_pool;\n\n    if (pool->ipv6.enabled)\n    {\n        /* the host portion of the address will always be contained in the last\n         * 4 bytes, therefore we can just extract that and use it as base in\n         * integer form\n         */\n        uint32_t base = (ipv6_base.s6_addr[12] << 24) | (ipv6_base.s6_addr[13] << 16)\n                        | (ipv6_base.s6_addr[14] << 8) | ipv6_base.s6_addr[15];\n        /* some bits of the last 4 bytes may still be part of the network\n         * portion of the address, therefore we need to set them to 0\n         */\n        if ((128 - ipv6_netbits) < 32)\n        {\n            /* extract only the bits that are really part of the host portion of\n             * the address.\n             *\n             * Example: if we have netbits=31, the first bit has to be zero'd,\n             * the following operation first computes mask=0x3fffff and then\n             * uses mask to extract the wanted bits from base\n             */\n            uint32_t mask = (1 << (128 - ipv6_netbits)) - 1;\n            base &= mask;\n        }\n\n        pool->ipv6.base = ipv6_base;\n\n        /* if a pool starts at a base address that has all-zero in the\n         * host part, that first IPv6 address must not be assigned to\n         * clients because it is not usable (subnet anycast address).\n         * Start with 1, then.\n         *\n         * NOTE: this will also (mis-)fire for something like\n         *    ifconfig-ipv6-pool 2001:db8:0:1:1234::0/64\n         * as we only check the rightmost 32 bits of the host part.  So be it.\n         */\n        if (base == 0)\n        {\n            msg(D_IFCONFIG_POOL, \"IFCONFIG POOL IPv6: incrementing pool start \"\n                                 \"to avoid ::0 assignment\");\n            base++;\n            pool->ipv6.base.s6_addr[15]++;\n        }\n\n        pool_ipv6_size =\n            ipv6_netbits >= 112 ? (1 << (128 - ipv6_netbits)) - base : IFCONFIG_POOL_MAX;\n\n        if (pool_ipv6_size < 2)\n        {\n            msg(M_FATAL, \"IPv6 pool size is too small (%d), must be at least 2\", pool_ipv6_size);\n        }\n\n        msg(D_IFCONFIG_POOL, \"IFCONFIG POOL IPv6: base=%s size=%d netbits=%d\",\n            print_in6_addr(pool->ipv6.base, 0, &gc), pool_ipv6_size, ipv6_netbits);\n\n        /* if there is no v4 pool, or the v6 pool is smaller, use\n         * v6 pool size as \"unified pool size\"\n         */\n        if (pool->size <= 0 || pool_ipv6_size < pool->size)\n        {\n            pool->size = pool_ipv6_size;\n        }\n    }\n\n    if (pool->ipv4.enabled && pool->ipv6.enabled)\n    {\n        if (pool_ipv4_size < pool_ipv6_size)\n        {\n            msg(M_INFO,\n                \"NOTE: IPv4 pool size is %d, IPv6 pool size is %d. \"\n                \"IPv4 pool size limits the number of clients that can be \"\n                \"served from the pool\",\n                pool_ipv4_size, pool_ipv6_size);\n        }\n        else if (pool_ipv4_size > pool_ipv6_size)\n        {\n            msg(M_WARN,\n                \"WARNING: IPv4 pool size is %d, IPv6 pool size is %d. \"\n                \"IPv6 pool size limits the number of clients that can be \"\n                \"served from the pool. This is likely a MISTAKE - please check \"\n                \"your configuration\",\n                pool_ipv4_size, pool_ipv6_size);\n        }\n    }\n\n    ASSERT(pool->size > 0);\n\n    ALLOC_ARRAY_CLEAR(pool->list, struct ifconfig_pool_entry, pool->size);\n\n    gc_free(&gc);\n    return pool;\n}\n\nvoid\nifconfig_pool_free(struct ifconfig_pool *pool)\n{\n    if (pool)\n    {\n        int i;\n\n        for (i = 0; i < pool->size; ++i)\n        {\n            ifconfig_pool_entry_free(&pool->list[i], true);\n        }\n        free(pool->list);\n        free(pool);\n    }\n}\n\nifconfig_pool_handle\nifconfig_pool_acquire(struct ifconfig_pool *pool, in_addr_t *local, in_addr_t *remote,\n                      struct in6_addr *remote_ipv6, const char *common_name)\n{\n    int i;\n\n    i = ifconfig_pool_find(pool, common_name);\n    if (i >= 0)\n    {\n        struct ifconfig_pool_entry *ipe = &pool->list[i];\n        ASSERT(!ipe->in_use);\n        ifconfig_pool_entry_free(ipe, true);\n        ipe->in_use = true;\n        if (common_name)\n        {\n            ipe->common_name = string_alloc(common_name, NULL);\n        }\n\n        if (pool->ipv4.enabled && local && remote)\n        {\n            switch (pool->ipv4.type)\n            {\n                case IFCONFIG_POOL_30NET:\n                {\n                    in_addr_t b = pool->ipv4.base + (i << 2);\n                    *local = b + 1;\n                    *remote = b + 2;\n                    break;\n                }\n\n                case IFCONFIG_POOL_INDIV:\n                {\n                    in_addr_t b = pool->ipv4.base + i;\n                    *local = 0;\n                    *remote = b;\n                    break;\n                }\n\n                default:\n                    ASSERT(0);\n            }\n        }\n\n        /* IPv6 pools are always INDIV (--linear) */\n        if (pool->ipv6.enabled && remote_ipv6)\n        {\n            *remote_ipv6 = add_in6_addr(pool->ipv6.base, i);\n        }\n    }\n    return i;\n}\n\nbool\nifconfig_pool_release(struct ifconfig_pool *pool, ifconfig_pool_handle hand, const bool hard)\n{\n    bool ret = false;\n\n    if (pool && hand >= 0 && hand < pool->size)\n    {\n        ifconfig_pool_entry_free(&pool->list[hand], hard);\n        ret = true;\n    }\n    return ret;\n}\n\n/*\n * private access functions\n */\n\n/* currently handling IPv4 logic only */\nstatic ifconfig_pool_handle\nifconfig_pool_ip_base_to_handle(const struct ifconfig_pool *pool, const in_addr_t addr)\n{\n    ifconfig_pool_handle ret = -1;\n\n    switch (pool->ipv4.type)\n    {\n        case IFCONFIG_POOL_30NET:\n        {\n            ret = (addr - pool->ipv4.base) >> 2;\n            break;\n        }\n\n        case IFCONFIG_POOL_INDIV:\n        {\n            ret = (addr - pool->ipv4.base);\n            break;\n        }\n\n        default:\n            ASSERT(0);\n    }\n\n    if (ret < 0 || ret >= pool->size)\n    {\n        ret = -1;\n    }\n\n    return ret;\n}\n\nstatic ifconfig_pool_handle\nifconfig_pool_ipv6_base_to_handle(const struct ifconfig_pool *pool, const struct in6_addr *in_addr)\n{\n    ifconfig_pool_handle ret;\n    uint32_t base, addr;\n\n    /* IPv6 pool is always IFCONFIG_POOL_INDIV.\n     *\n     * We assume the offset can't be larger than 2^32-1, therefore we compute\n     * the difference only among the last 4 bytes like if they were two 32bit\n     * long integers. The rest of the address must match.\n     */\n    for (int i = 0; i < (12); i++)\n    {\n        if (pool->ipv6.base.s6_addr[i] != in_addr->s6_addr[i])\n        {\n            return -1;\n        }\n    }\n\n    base = (pool->ipv6.base.s6_addr[12] << 24) | (pool->ipv6.base.s6_addr[13] << 16)\n           | (pool->ipv6.base.s6_addr[14] << 8) | pool->ipv6.base.s6_addr[15];\n\n    addr = (in_addr->s6_addr[12] << 24) | (in_addr->s6_addr[13] << 16) | (in_addr->s6_addr[14] << 8)\n           | in_addr->s6_addr[15];\n\n    ret = addr - base;\n    if (ret < 0 || ret >= pool->size)\n    {\n        ret = -1;\n    }\n\n    return ret;\n}\n\nstatic in_addr_t\nifconfig_pool_handle_to_ip_base(const struct ifconfig_pool *pool, ifconfig_pool_handle hand)\n{\n    in_addr_t ret = 0;\n\n    if (pool->ipv4.enabled && hand >= 0 && hand < pool->size)\n    {\n        switch (pool->ipv4.type)\n        {\n            case IFCONFIG_POOL_30NET:\n            {\n                ret = pool->ipv4.base + (hand << 2);\n                break;\n            }\n\n            case IFCONFIG_POOL_INDIV:\n            {\n                ret = pool->ipv4.base + hand;\n                break;\n            }\n\n            default:\n                ASSERT(0);\n        }\n    }\n\n    return ret;\n}\n\nstatic struct in6_addr\nifconfig_pool_handle_to_ipv6_base(const struct ifconfig_pool *pool, ifconfig_pool_handle hand)\n{\n    struct in6_addr ret = IN6ADDR_ANY_INIT;\n\n    /* IPv6 pools are always INDIV (--linear) */\n    if (pool->ipv6.enabled && hand >= 0 && hand < pool->size)\n    {\n        ret = add_in6_addr(pool->ipv6.base, hand);\n    }\n    return ret;\n}\n\nstatic void\nifconfig_pool_set(struct ifconfig_pool *pool, const char *cn, ifconfig_pool_handle h,\n                  const bool fixed)\n{\n    struct ifconfig_pool_entry *e = &pool->list[h];\n    ifconfig_pool_entry_free(e, true);\n    e->in_use = false;\n    e->common_name = string_alloc(cn, NULL);\n    e->last_release = now;\n    e->fixed = fixed;\n}\n\nstatic void\nifconfig_pool_list(const struct ifconfig_pool *pool, struct status_output *out)\n{\n    if (pool && out)\n    {\n        struct gc_arena gc = gc_new();\n        int i;\n\n        for (i = 0; i < pool->size; ++i)\n        {\n            const struct ifconfig_pool_entry *e = &pool->list[i];\n            struct in6_addr ip6;\n            in_addr_t ip;\n            const char *ip6_str = \"\";\n            const char *ip_str = \"\";\n\n            if (e->common_name)\n            {\n                if (pool->ipv4.enabled)\n                {\n                    ip = ifconfig_pool_handle_to_ip_base(pool, i);\n                    ip_str = print_in_addr_t(ip, 0, &gc);\n                }\n\n                if (pool->ipv6.enabled)\n                {\n                    ip6 = ifconfig_pool_handle_to_ipv6_base(pool, i);\n                    ip6_str = print_in6_addr(ip6, 0, &gc);\n                }\n\n                status_printf(out, \"%s,%s,%s\", e->common_name, ip_str, ip6_str);\n            }\n        }\n        gc_free(&gc);\n    }\n}\n\nstatic void\nifconfig_pool_msg(const struct ifconfig_pool *pool, int msglevel)\n{\n    struct status_output *so = status_open(NULL, 0, msglevel, NULL, 0);\n    ASSERT(so);\n    status_printf(so, \"IFCONFIG POOL LIST\");\n    ifconfig_pool_list(pool, so);\n    status_close(so);\n}\n\n/*\n * Deal with reading/writing the ifconfig pool database to a file\n */\n\nstruct ifconfig_pool_persist *\nifconfig_pool_persist_init(const char *filename, int refresh_freq)\n{\n    struct ifconfig_pool_persist *ret;\n\n    ASSERT(filename);\n\n    ALLOC_OBJ_CLEAR(ret, struct ifconfig_pool_persist);\n    if (refresh_freq > 0)\n    {\n        ret->fixed = false;\n        ret->file =\n            status_open(filename, refresh_freq, -1, NULL, STATUS_OUTPUT_READ | STATUS_OUTPUT_WRITE);\n    }\n    else\n    {\n        ret->fixed = true;\n        ret->file = status_open(filename, 0, -1, NULL, STATUS_OUTPUT_READ);\n    }\n    return ret;\n}\n\nvoid\nifconfig_pool_persist_close(struct ifconfig_pool_persist *persist)\n{\n    if (persist)\n    {\n        if (persist->file)\n        {\n            status_close(persist->file);\n        }\n        free(persist);\n    }\n}\n\nbool\nifconfig_pool_write_trigger(struct ifconfig_pool_persist *persist)\n{\n    if (persist->file)\n    {\n        return status_trigger(persist->file);\n    }\n    else\n    {\n        return false;\n    }\n}\n\nvoid\nifconfig_pool_read(struct ifconfig_pool_persist *persist, struct ifconfig_pool *pool)\n{\n    const int buf_size = 128;\n\n    update_time();\n\n    if (persist && persist->file && pool)\n    {\n        struct gc_arena gc = gc_new();\n        struct buffer in = alloc_buf_gc(256, &gc);\n        char *cn_buf, *ip_buf, *ip6_buf;\n\n        ALLOC_ARRAY_CLEAR_GC(cn_buf, char, buf_size, &gc);\n        ALLOC_ARRAY_CLEAR_GC(ip_buf, char, buf_size, &gc);\n        ALLOC_ARRAY_CLEAR_GC(ip6_buf, char, buf_size, &gc);\n\n        while (true)\n        {\n            ASSERT(buf_init(&in, 0));\n            if (!status_read(persist->file, &in))\n            {\n                break;\n            }\n            if (!BLEN(&in))\n            {\n                continue;\n            }\n\n            int c = *BSTR(&in);\n            if (c == '#' || c == ';')\n            {\n                continue;\n            }\n\n            msg(M_INFO, \"ifconfig_pool_read(), in='%s'\", BSTR(&in));\n\n            /* The expected format of a line is: \"CN,IP4,IP6\".\n             *\n             * IP4 or IP6 may be empty when respectively no v4 or v6 pool\n             * was previously specified.\n             *\n             * This means that accepted strings can be:\n             * - CN,IP4,IP6\n             * - CN,IP4\n             * - CN,,IP6\n             */\n            if (!buf_parse(&in, ',', cn_buf, buf_size) || !buf_parse(&in, ',', ip_buf, buf_size))\n            {\n                continue;\n            }\n\n            ifconfig_pool_handle h = -1, h6 = -1;\n\n            if (strlen(ip_buf) > 0)\n            {\n                bool v4_ok = true;\n                in_addr_t addr = getaddr(GETADDR_HOST_ORDER, ip_buf, 0, &v4_ok, NULL);\n\n                if (!v4_ok)\n                {\n                    msg(M_WARN, \"pool: invalid IPv4 (%s) for CN=%s\", ip_buf, cn_buf);\n                }\n                else\n                {\n                    h = ifconfig_pool_ip_base_to_handle(pool, addr);\n                    if (h < 0)\n                    {\n                        msg(M_WARN, \"pool: IPv4 (%s) out of pool range for CN=%s\", ip_buf, cn_buf);\n                    }\n                }\n            }\n\n            if (buf_parse(&in, ',', ip6_buf, buf_size) && strlen(ip6_buf) > 0)\n            {\n                struct in6_addr addr6;\n\n                if (!get_ipv6_addr(ip6_buf, &addr6, NULL, M_WARN))\n                {\n                    msg(M_WARN, \"pool: invalid IPv6 (%s) for CN=%s\", ip6_buf, cn_buf);\n                }\n                else\n                {\n                    h6 = ifconfig_pool_ipv6_base_to_handle(pool, &addr6);\n                    if (h6 < 0)\n                    {\n                        msg(M_WARN, \"pool: IPv6 (%s) out of pool range for CN=%s\", ip6_buf, cn_buf);\n                    }\n\n                    /* Rely on IPv6 if no IPv4 was provided or the one provided\n                     * was not valid\n                     */\n                    if (h < 0)\n                    {\n                        h = h6;\n                    }\n                }\n            }\n\n            /* at the moment IPv4 and IPv6 share the same pool, therefore offsets\n             * have to match for the same client.\n             *\n             * If offsets differ we use the IPv4, therefore warn the user about this.\n             */\n            if ((h6 >= 0) && (h != h6))\n            {\n                msg(M_WARN, \"pool: IPv4 (%s) and IPv6 (%s) have different offsets! Relying on IPv4\",\n                    ip_buf, ip6_buf);\n            }\n\n            /* if at least one among v4 and v6 was properly parsed, attempt\n             * setting an handle for this client\n             */\n            if (h >= 0)\n            {\n                msg(M_INFO, \"succeeded -> ifconfig_pool_set(hand=%d)\", h);\n                ifconfig_pool_set(pool, cn_buf, h, persist->fixed);\n            }\n        }\n\n        ifconfig_pool_msg(pool, D_IFCONFIG_POOL);\n\n        gc_free(&gc);\n    }\n}\n\nvoid\nifconfig_pool_write(struct ifconfig_pool_persist *persist, const struct ifconfig_pool *pool)\n{\n    if (persist && persist->file && (status_rw_flags(persist->file) & STATUS_OUTPUT_WRITE) && pool)\n    {\n        status_reset(persist->file);\n        ifconfig_pool_list(pool, persist->file);\n        status_flush(persist->file);\n    }\n}\n\n/*\n * TESTING ONLY\n */\n\n#ifdef IFCONFIG_POOL_TEST\n\n#define DUP_CN\n\nvoid\nifconfig_pool_test(in_addr_t start, in_addr_t end)\n{\n    struct gc_arena gc = gc_new();\n    struct ifconfig_pool *p = ifconfig_pool_init(IFCONFIG_POOL_30NET, start, end);\n    /*struct ifconfig_pool *p = ifconfig_pool_init (IFCONFIG_POOL_INDIV, start, end);*/\n    ifconfig_pool_handle array[256];\n    int i;\n\n    CLEAR(array);\n\n    msg(M_INFO | M_NOPREFIX, \"************ 1\");\n    for (i = 0; i < (int)SIZE(array); ++i)\n    {\n        char *cn;\n        ifconfig_pool_handle h;\n        in_addr_t local, remote;\n        char buf[256];\n        snprintf(buf, sizeof(buf), \"common-name-%d\", i);\n#ifdef DUP_CN\n        cn = NULL;\n#else\n        cn = buf;\n#endif\n        h = ifconfig_pool_acquire(p, &local, &remote, NULL, cn);\n        if (h < 0)\n        {\n            break;\n        }\n        msg(M_INFO | M_NOPREFIX, \"IFCONFIG_POOL TEST pass 1: l=%s r=%s cn=%s\",\n            print_in_addr_t(local, 0, &gc), print_in_addr_t(remote, 0, &gc), cn);\n        array[i] = h;\n    }\n\n    msg(M_INFO | M_NOPREFIX, \"************* 2\");\n    for (i = (int)SIZE(array) / 16; i < (int)SIZE(array) / 8; ++i)\n    {\n        msg(M_INFO, \"Attempt to release %d cn=%s\", array[i], p->list[i].common_name);\n        if (!ifconfig_pool_release(p, array[i]))\n        {\n            break;\n        }\n        msg(M_INFO, \"Succeeded\");\n    }\n\n    CLEAR(array);\n\n    msg(M_INFO | M_NOPREFIX, \"**************** 3\");\n    for (i = 0; i < (int)SIZE(array); ++i)\n    {\n        char *cn;\n        ifconfig_pool_handle h;\n        in_addr_t local, remote;\n        char buf[256];\n        snprintf(buf, sizeof(buf), \"common-name-%d\", i + 24);\n#ifdef DUP_CN\n        cn = NULL;\n#else\n        cn = buf;\n#endif\n        h = ifconfig_pool_acquire(p, &local, &remote, NULL, cn);\n        if (h < 0)\n        {\n            break;\n        }\n        msg(M_INFO | M_NOPREFIX, \"IFCONFIG_POOL TEST pass 3: l=%s r=%s cn=%s\",\n            print_in_addr_t(local, 0, &gc), print_in_addr_t(remote, 0, &gc), cn);\n        array[i] = h;\n    }\n\n    ifconfig_pool_free(p);\n    gc_free(&gc);\n}\n\n#endif /* ifdef IFCONFIG_POOL_TEST */\n"
  },
  {
    "path": "src/openvpn/pool.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef POOL_H\n#define POOL_H\n\n/*#define IFCONFIG_POOL_TEST*/\n\n#include \"basic.h\"\n#include \"status.h\"\n\n#define IFCONFIG_POOL_MAX         65536\n#define IFCONFIG_POOL_MIN_NETBITS 16\n\nenum pool_type\n{\n    IFCONFIG_POOL_30NET,\n    IFCONFIG_POOL_INDIV\n};\n\nstruct ifconfig_pool_entry\n{\n    bool in_use;\n    char *common_name;\n    time_t last_release;\n    bool fixed;\n};\n\nstruct ifconfig_pool\n{\n    bool duplicate_cn;\n    struct\n    {\n        bool enabled;\n        enum pool_type type;\n        in_addr_t base;\n    } ipv4;\n    struct\n    {\n        bool enabled;\n        struct in6_addr base;\n    } ipv6;\n    int size;\n    struct ifconfig_pool_entry *list;\n};\n\nstruct ifconfig_pool_persist\n{\n    struct status_output *file;\n    bool fixed;\n};\n\ntypedef int ifconfig_pool_handle;\n\nstruct ifconfig_pool *ifconfig_pool_init(const bool ipv4_pool, enum pool_type type, in_addr_t start,\n                                         in_addr_t end, const bool duplicate_cn,\n                                         const bool ipv6_pool, const struct in6_addr ipv6_base,\n                                         const int ipv6_netbits);\n\nvoid ifconfig_pool_free(struct ifconfig_pool *pool);\n\nbool ifconfig_pool_verify_range(const msglvl_t msglevel, const in_addr_t start, const in_addr_t end);\n\nifconfig_pool_handle ifconfig_pool_acquire(struct ifconfig_pool *pool, in_addr_t *local,\n                                           in_addr_t *remote, struct in6_addr *remote_ipv6,\n                                           const char *common_name);\n\nbool ifconfig_pool_release(struct ifconfig_pool *pool, ifconfig_pool_handle hand, const bool hard);\n\nstruct ifconfig_pool_persist *ifconfig_pool_persist_init(const char *filename, int refresh_freq);\n\nvoid ifconfig_pool_persist_close(struct ifconfig_pool_persist *persist);\n\nbool ifconfig_pool_write_trigger(struct ifconfig_pool_persist *persist);\n\nvoid ifconfig_pool_read(struct ifconfig_pool_persist *persist, struct ifconfig_pool *pool);\n\nvoid ifconfig_pool_write(struct ifconfig_pool_persist *persist, const struct ifconfig_pool *pool);\n\n#ifdef IFCONFIG_POOL_TEST\nvoid ifconfig_pool_test(in_addr_t start, in_addr_t end);\n\n#endif\n\n#endif /* ifndef POOL_H */\n"
  },
  {
    "path": "src/openvpn/proto.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"proto.h\"\n#include \"error.h\"\n\n#include \"memdbg.h\"\n\n/*\n * If raw tunnel packet is IPv<X>, return true and increment\n * buffer offset to start of IP header.\n */\nstatic bool\nis_ipv_X(int tunnel_type, struct buffer *buf, int ip_ver)\n{\n    int offset;\n    uint16_t proto;\n    const struct openvpn_iphdr *ih;\n\n    verify_align_4(buf);\n    if (tunnel_type == DEV_TYPE_TUN)\n    {\n        if (BLENZ(buf) < sizeof(struct openvpn_iphdr))\n        {\n            return false;\n        }\n        offset = 0;\n    }\n    else if (tunnel_type == DEV_TYPE_TAP)\n    {\n        const struct openvpn_ethhdr *eh;\n        if (BLENZ(buf) < sizeof(struct openvpn_ethhdr) + sizeof(struct openvpn_iphdr))\n        {\n            return false;\n        }\n        eh = (const struct openvpn_ethhdr *)BPTR(buf);\n\n        /* start by assuming this is a standard Eth fram */\n        proto = eh->proto;\n        offset = sizeof(struct openvpn_ethhdr);\n\n        /* if this is a 802.1q frame, parse the header using the according\n         * format\n         */\n        if (proto == htons(OPENVPN_ETH_P_8021Q))\n        {\n            const struct openvpn_8021qhdr *evh;\n            if (BLENZ(buf) < sizeof(struct openvpn_ethhdr) + sizeof(struct openvpn_iphdr))\n            {\n                return false;\n            }\n\n            evh = (const struct openvpn_8021qhdr *)BPTR(buf);\n\n            proto = evh->proto;\n            offset = sizeof(struct openvpn_8021qhdr);\n        }\n\n        if (ntohs(proto) != (ip_ver == 6 ? OPENVPN_ETH_P_IPV6 : OPENVPN_ETH_P_IPV4))\n        {\n            return false;\n        }\n    }\n    else\n    {\n        return false;\n    }\n\n    ih = (const struct openvpn_iphdr *)(BPTR(buf) + offset);\n\n    /* IP version is stored in the same bits for IPv4 or IPv6 header */\n    if (OPENVPN_IPH_GET_VER(ih->version_len) == ip_ver)\n    {\n        return buf_advance(buf, offset);\n    }\n    else\n    {\n        return false;\n    }\n}\n\nbool\nis_ipv4(int tunnel_type, struct buffer *buf)\n{\n    return is_ipv_X(tunnel_type, buf, 4);\n}\nbool\nis_ipv6(int tunnel_type, struct buffer *buf)\n{\n    return is_ipv_X(tunnel_type, buf, 6);\n}\n\n\nuint16_t\nip_checksum(const sa_family_t af, const uint8_t *payload, const int len_payload,\n            const uint8_t *src_addr, const uint8_t *dest_addr, const int proto)\n{\n    uint32_t sum = 0;\n    int addr_len = (af == AF_INET) ? 4 : 16;\n\n    /*\n     * make 16 bit words out of every two adjacent 8 bit words and  */\n    /* calculate the sum of all 16 bit words\n     */\n    for (int i = 0; i < len_payload; i += 2)\n    {\n        sum += (uint16_t)(((payload[i] << 8) & 0xFF00)\n                          + ((i + 1 < len_payload) ? (payload[i + 1] & 0xFF) : 0));\n    }\n\n    /*\n     * add the pseudo header which contains the IP source and destination\n     * addresses\n     */\n    for (int i = 0; i < addr_len; i += 2)\n    {\n        sum += (uint16_t)((src_addr[i] << 8) & 0xFF00) + (src_addr[i + 1] & 0xFF);\n    }\n    for (int i = 0; i < addr_len; i += 2)\n    {\n        sum += (uint16_t)((dest_addr[i] << 8) & 0xFF00) + (dest_addr[i + 1] & 0xFF);\n    }\n\n    /* the length of the payload */\n    sum += (uint16_t)len_payload;\n\n    /* The next header or proto field*/\n    sum += (uint16_t)proto;\n\n    /*\n     * keep only the last 16 bits of the 32 bit calculated sum and add\n     * the carries\n     */\n    while (sum >> 16)\n    {\n        sum = (sum & 0xFFFF) + (sum >> 16);\n    }\n\n    /* Take the one's complement of sum */\n    return ((uint16_t)~sum);\n}\n\n#ifdef PACKET_TRUNCATION_CHECK\n\nvoid\nipv4_packet_size_verify(const uint8_t *data, const int size, const int tunnel_type,\n                        const char *prefix, counter_type *errors)\n{\n    if (size > 0)\n    {\n        struct buffer buf;\n\n        buf_set_read(&buf, data, size);\n\n        if (is_ipv4(tunnel_type, &buf))\n        {\n            const struct openvpn_iphdr *pip;\n            int hlen;\n            int totlen;\n            const char *msgstr = \"PACKET SIZE INFO\";\n            msglvl_t msglevel = D_PACKET_TRUNC_DEBUG;\n\n            if (BLENZ(&buf) < sizeof(struct openvpn_iphdr))\n            {\n                return;\n            }\n\n            verify_align_4(&buf);\n            pip = (struct openvpn_iphdr *)BPTR(&buf);\n\n            hlen = OPENVPN_IPH_GET_LEN(pip->version_len);\n            totlen = ntohs(pip->tot_len);\n\n            if (BLEN(&buf) != totlen)\n            {\n                msgstr = \"PACKET TRUNCATION ERROR\";\n                msglevel = D_PACKET_TRUNC_ERR;\n                if (errors)\n                {\n                    ++(*errors);\n                }\n            }\n\n            msg(msglevel, \"%s %s: size=%d totlen=%d hlen=%d errcount=\" counter_format, msgstr,\n                prefix, BLEN(&buf), totlen, hlen, errors ? *errors : (counter_type)0);\n        }\n    }\n}\n\n#endif /* ifdef PACKET_TRUNCATION_CHECK */\n"
  },
  {
    "path": "src/openvpn/proto.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef PROTO_H\n#define PROTO_H\n\n#include \"common.h\"\n#include \"buffer.h\"\n\n#pragma pack(1)\n\n/*\n * Tunnel types\n */\n#define DEV_TYPE_UNDEF 0\n#define DEV_TYPE_TUN   2 /* point-to-point IP tunnel */\n#define DEV_TYPE_TAP   3 /* ethernet (802.3) tunnel */\n\n/* TUN topologies */\n\n#define TOP_UNDEF  0\n#define TOP_NET30  1\n#define TOP_P2P    2\n#define TOP_SUBNET 3\n\n/*\n * IP and Ethernet protocol structs.  For portability,\n * OpenVPN needs its own definitions of these structs, and\n * names have been adjusted to avoid collisions with\n * native structs.\n */\n\n#define OPENVPN_ETH_ALEN 6 /* ethernet address length */\nstruct openvpn_ethhdr\n{\n    uint8_t dest[OPENVPN_ETH_ALEN];   /* destination ethernet addr */\n    uint8_t source[OPENVPN_ETH_ALEN]; /* source ethernet addr   */\n\n#define OPENVPN_ETH_P_IPV4  0x0800    /* IPv4 protocol */\n#define OPENVPN_ETH_P_IPV6  0x86DD    /* IPv6 protocol */\n#define OPENVPN_ETH_P_ARP   0x0806    /* ARP protocol */\n#define OPENVPN_ETH_P_8021Q 0x8100    /* 802.1Q protocol */\n    uint16_t proto;                   /* packet type ID field */\n};\n\nstruct openvpn_8021qhdr\n{\n    uint8_t dest[OPENVPN_ETH_ALEN];          /* destination ethernet addr */\n    uint8_t source[OPENVPN_ETH_ALEN];        /* source ethernet addr */\n\n    uint16_t tpid;                           /* 802.1Q Tag Protocol Identifier */\n#define OPENVPN_8021Q_MASK_PCP htons(0xE000) /* mask PCP out of pcp_cfi_vid */\n#define OPENVPN_8021Q_MASK_CFI htons(0x1000) /* mask CFI out of pcp_cfi_vid */\n#define OPENVPN_8021Q_MASK_VID htons(0x0FFF) /* mask VID out of pcp_cfi_vid */\n    uint16_t pcp_cfi_vid;                    /* bit fields, see IEEE 802.1Q */\n    uint16_t proto;                          /* contained packet type ID field */\n};\n\n/*\n * Size difference between a regular Ethernet II header and an Ethernet II\n * header with additional IEEE 802.1Q tagging.\n */\n#define SIZE_ETH_TO_8021Q_HDR (sizeof(struct openvpn_8021qhdr) - sizeof(struct openvpn_ethhdr))\n\n/** Version of IN6_ARE_ADDR_EQUAL that is guaranteed to work for\n *  unaligned access. E.g. Linux uses 32bit compares which are\n *  not safe if the struct is unaligned. */\n#define OPENVPN_IN6_ARE_ADDR_EQUAL(a, b) (memcmp(a, b, sizeof(struct in6_addr)) == 0)\n\nstruct openvpn_iphdr\n{\n#define OPENVPN_IPH_GET_VER(v) (((v) >> 4) & 0x0F)\n#define OPENVPN_IPH_GET_LEN(v) (((v) & 0x0F) << 2)\n    uint8_t version_len;\n\n    uint8_t tos;\n    uint16_t tot_len;\n    uint16_t id;\n\n#define OPENVPN_IP_OFFMASK 0x1fff\n    uint16_t frag_off;\n\n    uint8_t ttl;\n\n#define OPENVPN_IPPROTO_IGMP   2  /* IGMP protocol */\n#define OPENVPN_IPPROTO_TCP    6  /* TCP protocol */\n#define OPENVPN_IPPROTO_UDP    17 /* UDP protocol */\n#define OPENVPN_IPPROTO_ICMPV6 58 /* ICMPV6 protocol */\n    uint8_t protocol;\n\n    uint16_t check;\n    uint32_t saddr;\n    uint32_t daddr;\n    /*The options start here. */\n};\n\n/*\n * IPv6 header\n */\nstruct openvpn_ipv6hdr\n{\n    uint8_t version_prio;\n    uint8_t flow_lbl[3];\n    uint16_t payload_len;\n    uint8_t nexthdr;\n    uint8_t hop_limit;\n\n    struct in6_addr saddr;\n    struct in6_addr daddr;\n};\n\n/*\n * ICMPv6 header\n */\nstruct openvpn_icmp6hdr\n{\n#define OPENVPN_ICMP6_DESTINATION_UNREACHABLE 1\n#define OPENVPN_ND_ROUTER_SOLICIT             133\n#define OPENVPN_ND_ROUTER_ADVERT              134\n#define OPENVPN_ND_NEIGHBOR_SOLICIT           135\n#define OPENVPN_ND_NEIGHBOR_ADVERT            136\n#define OPENVPN_ND_INVERSE_SOLICIT            141\n#define OPENVPN_ND_INVERSE_ADVERT             142\n    uint8_t icmp6_type;\n#define OPENVPN_ICMP6_DU_NOROUTE                 0\n#define OPENVPN_ICMP6_DU_COMMUNICATION_PROHIBTED 1\n    uint8_t icmp6_code;\n    uint16_t icmp6_cksum;\n    uint8_t icmp6_dataun[4];\n};\n\n/*\n * UDP header\n */\nstruct openvpn_udphdr\n{\n    uint16_t source;\n    uint16_t dest;\n    uint16_t len;\n    uint16_t check;\n};\n\n/*\n * TCP header, per RFC 793.\n */\nstruct openvpn_tcphdr\n{\n    uint16_t source;  /* source port */\n    uint16_t dest;    /* destination port */\n    uint32_t seq;     /* sequence number */\n    uint32_t ack_seq; /* acknowledgement number */\n\n#define OPENVPN_TCPH_GET_DOFF(d) (((d) & 0xF0) >> 2)\n    uint8_t doff_res;\n\n#define OPENVPN_TCPH_FIN_MASK (1 << 0)\n#define OPENVPN_TCPH_SYN_MASK (1 << 1)\n#define OPENVPN_TCPH_RST_MASK (1 << 2)\n#define OPENVPN_TCPH_PSH_MASK (1 << 3)\n#define OPENVPN_TCPH_ACK_MASK (1 << 4)\n#define OPENVPN_TCPH_URG_MASK (1 << 5)\n#define OPENVPN_TCPH_ECE_MASK (1 << 6)\n#define OPENVPN_TCPH_CWR_MASK (1 << 7)\n    uint8_t flags;\n\n    uint16_t window;\n    uint16_t check;\n    uint16_t urg_ptr;\n};\n\n#define OPENVPN_TCPOPT_EOL     0\n#define OPENVPN_TCPOPT_NOP     1\n#define OPENVPN_TCPOPT_MAXSEG  2\n#define OPENVPN_TCPOLEN_MAXSEG 4\n\nstruct ip_tcp_udp_hdr\n{\n    struct openvpn_iphdr ip;\n    union\n    {\n        struct openvpn_tcphdr tcp;\n        struct openvpn_udphdr udp;\n    } u;\n};\n\n#pragma pack()\n\n/*\n * The following macro is used to update an\n * internet checksum.  \"acc\" is a 32-bit\n * accumulation of all the changes to the\n * checksum (adding in old 16-bit words and\n * subtracting out new words), and \"cksum\"\n * is the checksum value to be updated.\n */\n#define ADJUST_CHECKSUM(acc, cksum)                \\\n    {                                              \\\n        int32_t _acc = acc;                        \\\n        _acc += (cksum);                           \\\n        if (_acc < 0)                              \\\n        {                                          \\\n            _acc = -_acc;                          \\\n            _acc = (_acc >> 16) + (_acc & 0xffff); \\\n            _acc += _acc >> 16;                    \\\n            (cksum) = (uint16_t)~_acc;             \\\n        }                                          \\\n        else                                       \\\n        {                                          \\\n            _acc = (_acc >> 16) + (_acc & 0xffff); \\\n            _acc += _acc >> 16;                    \\\n            (cksum) = (uint16_t)_acc;              \\\n        }                                          \\\n    }\n\n#define ADD_CHECKSUM_32(acc, u32)         \\\n    {                                     \\\n        acc += (int32_t)((u32) & 0xffff); \\\n        acc += (int32_t)((u32) >> 16);    \\\n    }\n\n#define SUB_CHECKSUM_32(acc, u32)         \\\n    {                                     \\\n        acc -= (int32_t)((u32) & 0xffff); \\\n        acc -= (int32_t)((u32) >> 16);    \\\n    }\n\n/*\n * This returns an ip protocol version of packet inside tun\n * and offset of IP header (via parameter).\n */\nstatic inline int\nget_tun_ip_ver(int tunnel_type, struct buffer *buf, int *ip_hdr_offset)\n{\n    int ip_ver = -1;\n\n    /* for tun get ip version from ip header */\n    if (tunnel_type == DEV_TYPE_TUN)\n    {\n        *ip_hdr_offset = 0;\n        if (likely(BLEN(buf) >= (int)sizeof(struct openvpn_iphdr)))\n        {\n            ip_ver = OPENVPN_IPH_GET_VER(*BPTR(buf));\n        }\n    }\n    else if (tunnel_type == DEV_TYPE_TAP)\n    {\n        *ip_hdr_offset = (int)(sizeof(struct openvpn_ethhdr));\n        /* for tap get ip version from eth header */\n        if (likely(BLEN(buf) >= *ip_hdr_offset))\n        {\n            const struct openvpn_ethhdr *eh = (const struct openvpn_ethhdr *)BPTR(buf);\n            uint16_t proto = ntohs(eh->proto);\n            if (proto == OPENVPN_ETH_P_IPV6)\n            {\n                ip_ver = 6;\n            }\n            else if (proto == OPENVPN_ETH_P_IPV4)\n            {\n                ip_ver = 4;\n            }\n        }\n    }\n\n    return ip_ver;\n}\n\n/*\n * If raw tunnel packet is IPv4 or IPv6, return true and increment\n * buffer offset to start of IP header.\n */\nbool is_ipv4(int tunnel_type, struct buffer *buf);\n\nbool is_ipv6(int tunnel_type, struct buffer *buf);\n\n/**\n *  Calculates an IP or IPv6 checksum with a pseudo header as required by\n *  TCP, UDP and ICMPv6\n *\n * @param af            - Address family for which the checksum is calculated\n *                        AF_INET or AF_INET6\n * @param payload       - the TCP, ICMPv6 or UDP packet\n * @param len_payload   - length of payload\n * @param src_addr      - Source address of the packet\n * @param dest_addr     - Destination address of the packet\n * @param proto next    - header or IP protocol of the packet\n * @return The calculated checksum in host order\n */\nuint16_t ip_checksum(const sa_family_t af, const uint8_t *payload, const int len_payload,\n                     const uint8_t *src_addr, const uint8_t *dest_addr, const int proto);\n\n#ifdef PACKET_TRUNCATION_CHECK\nvoid ipv4_packet_size_verify(const uint8_t *data, const int size, const int tunnel_type,\n                             const char *prefix, counter_type *errors);\n\n#endif\n\n#define OPENVPN_8021Q_MIN_VID 1\n#define OPENVPN_8021Q_MAX_VID 4094\n\n#endif /* ifndef PROTO_H */\n"
  },
  {
    "path": "src/openvpn/proxy.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"common.h\"\n#include \"misc.h\"\n#include \"crypto.h\"\n#include \"win32.h\"\n#include \"socket.h\"\n#include \"fdmisc.h\"\n#include \"proxy.h\"\n#include \"base64.h\"\n#include \"httpdigest.h\"\n#include \"memdbg.h\"\n#include \"forward.h\"\n\n#define UP_TYPE_PROXY \"HTTP Proxy\"\n\nstruct http_proxy_options *\ninit_http_proxy_options_once(struct http_proxy_options **hpo, struct gc_arena *gc)\n{\n    if (!*hpo)\n    {\n        ALLOC_OBJ_CLEAR_GC(*hpo, struct http_proxy_options, gc);\n        /* http proxy defaults */\n        (*hpo)->http_version = \"1.0\";\n    }\n    return *hpo;\n}\n\n\n/* cached proxy username/password */\nstatic struct user_pass static_proxy_user_pass;\n\nbool\nproxy_recv_char(uint8_t *c, const char *name, socket_descriptor_t sd,\n                struct timeval *timeout, volatile int *signal_received)\n{\n    fd_set reads;\n    FD_ZERO(&reads);\n    openvpn_fd_set(sd, &reads);\n\n    const int status = openvpn_select(sd + 1, &reads, NULL, NULL, timeout);\n\n    get_signal(signal_received);\n    if (*signal_received)\n    {\n        return false;\n    }\n\n    /* timeout? */\n    if (status == 0)\n    {\n        msg(D_LINK_ERRORS | M_ERRNO, \"%s: TCP port read timeout expired\", name);\n        return false;\n    }\n\n    /* error */\n    if (status < 0)\n    {\n        msg(D_LINK_ERRORS | M_ERRNO, \"%s: TCP port read failed on select()\", name);\n        return false;\n    }\n\n    /* read single char */\n    const ssize_t size = recv(sd, (void *)c, 1, MSG_NOSIGNAL);\n\n    /* error? */\n    if (size != 1)\n    {\n        msg(D_LINK_ERRORS | M_ERRNO, \"%s: TCP port read failed on recv()\", name);\n        return false;\n    }\n\n    return true;\n}\n\n\nstatic bool\nrecv_line(socket_descriptor_t sd, char *buf, int len, const int timeout_sec, const bool verbose,\n          struct buffer *lookahead, volatile int *signal_received)\n{\n    struct buffer la;\n    int lastc = 0;\n\n    CLEAR(la);\n    if (lookahead)\n    {\n        la = *lookahead;\n    }\n\n    while (true)\n    {\n        struct timeval tv;\n        uint8_t c;\n\n        if (buf_defined(&la))\n        {\n            ASSERT(buf_init(&la, 0));\n        }\n\n        tv.tv_sec = timeout_sec;\n        tv.tv_usec = 0;\n\n        if (!proxy_recv_char(&c, \"recv_line\", sd, &tv, signal_received))\n        {\n            return false;\n        }\n\n#if 0\n        if (isprint(c))\n        {\n            msg(M_INFO, \"PROXY: read '%c' (%d)\", c, (int)c);\n        }\n        else\n        {\n            msg(M_INFO, \"PROXY: read (%d)\", (int)c);\n        }\n#endif\n\n        /* store char in buffer */\n        if (len > 1)\n        {\n            *buf++ = c;\n            --len;\n        }\n\n        /* also store char in lookahead buffer */\n        if (buf_defined(&la))\n        {\n            buf_write_u8(&la, c);\n            if (!isprint(c) && !isspace(c)) /* not ascii? */\n            {\n                if (verbose)\n                {\n                    msg(D_LINK_ERRORS | M_ERRNO,\n                        \"recv_line: Non-ASCII character (%d) read on recv()\", (int)c);\n                }\n                *lookahead = la;\n                return false;\n            }\n        }\n\n        /* end of line? */\n        if (lastc == '\\r' && c == '\\n')\n        {\n            break;\n        }\n\n        lastc = c;\n    }\n\n    /* append trailing null */\n    if (len > 0)\n    {\n        *buf++ = '\\0';\n    }\n\n    return true;\n}\n\nbool\nproxy_send(socket_descriptor_t sd, const void *buf, size_t buf_len)\n{\n    const ssize_t size = openvpn_send(sd, buf, buf_len, MSG_NOSIGNAL);\n    if (size != (ssize_t)buf_len)\n    {\n        msg(D_LINK_ERRORS | M_ERRNO, \"proxy_send: TCP port write failed on send()\");\n        return false;\n    }\n    return true;\n}\n\nstatic bool\nsend_line_crlf(socket_descriptor_t sd, const char *src)\n{\n    bool ret;\n\n    struct buffer buf = alloc_buf(strlen(src) + 2);\n    ASSERT(buf_write(&buf, src, strlen(src)));\n    ASSERT(buf_write(&buf, \"\\r\\n\", 2));\n    ret = proxy_send(sd, BSTR(&buf), BLEN(&buf));\n    free_buf(&buf);\n    return ret;\n}\n\nstatic bool\nsend_crlf(socket_descriptor_t sd)\n{\n    return send_line_crlf(sd, \"\");\n}\n\nuint8_t *\nmake_base64_string2(const uint8_t *str, int src_len, struct gc_arena *gc)\n{\n    uint8_t *ret = NULL;\n    char *b64out = NULL;\n    ASSERT(openvpn_base64_encode((const void *)str, src_len, &b64out) >= 0);\n    ret = (uint8_t *)string_alloc(b64out, gc);\n    free(b64out);\n    return ret;\n}\n\nuint8_t *\nmake_base64_string(const uint8_t *str, struct gc_arena *gc)\n{\n    return make_base64_string2(str, (int)strlen((const char *)str), gc);\n}\n\nstatic const char *\nusername_password_as_base64(const struct http_proxy_info *p, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(strlen(p->up.username) + strlen(p->up.password) + 2, gc);\n    ASSERT(strlen(p->up.username) > 0);\n    buf_printf(&out, \"%s:%s\", p->up.username, p->up.password);\n    char *ret = (char *)make_base64_string((const uint8_t *)BSTR(&out), gc);\n    secure_memzero(BSTR(&out), out.len);\n    return ret;\n}\n\nstatic void\nclear_user_pass_http(void)\n{\n    purge_user_pass(&static_proxy_user_pass, true);\n}\n\nstatic void\nget_user_pass_http(struct http_proxy_info *p, const bool force)\n{\n    /*\n     * in case of forced (re)load, make sure the static storage is set as\n     * undefined, otherwise get_user_pass() won't try to load any credential\n     */\n    if (force)\n    {\n        clear_user_pass_http();\n    }\n\n    if (!static_proxy_user_pass.defined)\n    {\n        unsigned int flags = GET_USER_PASS_MANAGEMENT;\n        const char *auth_file = p->options.auth_file;\n        if (p->options.auth_file_up)\n        {\n            auth_file = p->options.auth_file_up;\n        }\n        if (p->queried_creds && !static_proxy_user_pass.nocache)\n        {\n            flags |= GET_USER_PASS_PREVIOUS_CREDS_FAILED;\n        }\n        if (p->options.inline_creds)\n        {\n            flags |= GET_USER_PASS_INLINE_CREDS;\n        }\n        get_user_pass(&static_proxy_user_pass, auth_file, UP_TYPE_PROXY, flags);\n        static_proxy_user_pass.nocache = p->options.nocache;\n        protect_user_pass(&static_proxy_user_pass);\n    }\n\n    /*\n     * Using cached credentials\n     */\n    p->queried_creds = true;\n    p->up = static_proxy_user_pass; /* this is a copy of protected memory */\n}\n\n#if 0\n/* function only used in #if 0 debug statement */\nstatic void\ndump_residual(socket_descriptor_t sd,\n              int timeout,\n              volatile int *signal_received)\n{\n    char buf[256];\n    while (true)\n    {\n        if (!recv_line(sd, buf, sizeof(buf), timeout, true, NULL, signal_received))\n        {\n            return;\n        }\n        chomp(buf);\n        msg(D_PROXY, \"PROXY HEADER: '%s'\", buf);\n    }\n}\n#endif\n\n/*\n * Extract the Proxy-Authenticate header from the stream.\n * Consumes all headers.\n */\nstatic int\nget_proxy_authenticate(socket_descriptor_t sd, int timeout, char **data,\n                       volatile int *signal_received)\n{\n    char buf[256];\n    int ret = HTTP_AUTH_NONE;\n    while (true)\n    {\n        if (!recv_line(sd, buf, sizeof(buf), timeout, true, NULL, signal_received))\n        {\n            free(*data);\n            *data = NULL;\n            return HTTP_AUTH_NONE;\n        }\n        chomp(buf);\n        if (!strlen(buf))\n        {\n            return ret;\n        }\n        if (ret == HTTP_AUTH_NONE && !strncmp(buf, \"Proxy-Authenticate: \", 20))\n        {\n            if (!strncmp(buf + 20, \"Basic \", 6))\n            {\n                msg(D_PROXY, \"PROXY AUTH BASIC: '%s'\", buf);\n                *data = string_alloc(buf + 26, NULL);\n                ret = HTTP_AUTH_BASIC;\n            }\n#if PROXY_DIGEST_AUTH\n            else if (!strncmp(buf + 20, \"Digest \", 7))\n            {\n                msg(D_PROXY, \"PROXY AUTH DIGEST: '%s'\", buf);\n                *data = string_alloc(buf + 27, NULL);\n                ret = HTTP_AUTH_DIGEST;\n            }\n#endif\n        }\n    }\n}\n\nstatic void\nstore_proxy_authenticate(struct http_proxy_info *p, char *data)\n{\n    free(p->proxy_authenticate);\n    p->proxy_authenticate = data;\n}\n\n/*\n * Parse out key/value pairs from Proxy-Authenticate string.\n * Return true on success, or false on parse failure.\n */\nstatic bool\nget_key_value(const char *str,                                         /* source string */\n              char *key,                                               /* key stored here */\n              char *value,                                             /* value stored here */\n              int max_key_len, int max_value_len, const char **endptr) /* next search position */\n{\n    int c;\n    bool starts_with_quote = false;\n    bool escape = false;\n\n    for (c = max_key_len - 1; (*str && (*str != '=') && c--);)\n    {\n        *key++ = *str++;\n    }\n    *key = '\\0';\n\n    if ('=' != *str++)\n    {\n        /* no key/value found */\n        return false;\n    }\n\n    if ('\\\"' == *str)\n    {\n        /* quoted string */\n        str++;\n        starts_with_quote = true;\n    }\n\n    for (c = max_value_len - 1; *str && c--; str++)\n    {\n        switch (*str)\n        {\n            case '\\\\':\n                if (!escape)\n                {\n                    /* possibly the start of an escaped quote */\n                    escape = true;\n                    *value++ = '\\\\'; /* even though this is an escape character, we still\n                                      * store it as-is in the target buffer */\n                    continue;\n                }\n                break;\n\n            case ',':\n                if (!starts_with_quote)\n                {\n                    /* this signals the end of the value if we didn't get a starting quote\n                     * and then we do \"sloppy\" parsing */\n                    c = 0; /* the end */\n                    continue;\n                }\n                break;\n\n            case '\\r':\n            case '\\n':\n                /* end of string */\n                c = 0;\n                continue;\n\n            case '\\\"':\n                if (!escape && starts_with_quote)\n                {\n                    /* end of string */\n                    c = 0;\n                    continue;\n                }\n                break;\n        }\n        escape = false;\n        *value++ = *str;\n    }\n    *value = '\\0';\n\n    *endptr = str;\n\n    return true; /* success */\n}\n\nstatic char *\nget_pa_var(const char *key, const char *pa, struct gc_arena *gc)\n{\n    char k[64];\n    char v[256];\n    const char *content = pa;\n\n    while (true)\n    {\n        const int status = get_key_value(content, k, v, sizeof(k), sizeof(v), &content);\n        if (status)\n        {\n            if (!strcmp(key, k))\n            {\n                return string_alloc(v, gc);\n            }\n        }\n        else\n        {\n            return NULL;\n        }\n\n        /* advance to start of next key */\n        if (*content == ',')\n        {\n            ++content;\n        }\n        while (*content && isspace(*content))\n        {\n            ++content;\n        }\n    }\n}\n\nstruct http_proxy_info *\nhttp_proxy_new(const struct http_proxy_options *o)\n{\n    struct http_proxy_info *p;\n\n    if (!o || !o->server)\n    {\n        msg(M_FATAL, \"HTTP_PROXY: server not specified\");\n    }\n\n    ASSERT(o->port);\n\n    ALLOC_OBJ_CLEAR(p, struct http_proxy_info);\n    p->options = *o;\n\n    /* parse authentication method */\n    p->auth_method = HTTP_AUTH_NONE;\n    if (o->auth_method_string)\n    {\n        if (!strcmp(o->auth_method_string, \"none\"))\n        {\n            p->auth_method = HTTP_AUTH_NONE;\n        }\n        else if (!strcmp(o->auth_method_string, \"basic\"))\n        {\n            p->auth_method = HTTP_AUTH_BASIC;\n        }\n        else\n        {\n            msg(M_FATAL, \"ERROR: unknown HTTP authentication method: '%s'\", o->auth_method_string);\n        }\n    }\n\n    /* When basic authentication is requested, get credentials now.\n     * In case of \"auto\" negotiation credentials will be retrieved later once\n     * we know whether we need any. */\n    if (p->auth_method == HTTP_AUTH_BASIC)\n    {\n        get_user_pass_http(p, p->options.first_time);\n    }\n\n    p->defined = true;\n    return p;\n}\n\nvoid\nhttp_proxy_close(struct http_proxy_info *hp)\n{\n    free(hp);\n}\n\nstatic bool\nadd_proxy_headers(struct http_proxy_info *p, socket_descriptor_t sd, /* already open to proxy */\n                  const char *host                                   /* openvpn server remote */\n)\n{\n    char buf[512];\n    int i;\n    bool host_header_sent = false;\n\n    /*\n     * Send custom headers if provided\n     * If content is NULL the whole header is in name\n     * Also remember if we already sent a Host: header\n     */\n    for (i = 0; i < MAX_CUSTOM_HTTP_HEADER && p->options.custom_headers[i].name; i++)\n    {\n        if (p->options.custom_headers[i].content)\n        {\n            snprintf(buf, sizeof(buf), \"%s: %s\", p->options.custom_headers[i].name,\n                     p->options.custom_headers[i].content);\n            if (!strcasecmp(p->options.custom_headers[i].name, \"Host\"))\n            {\n                host_header_sent = true;\n            }\n        }\n        else\n        {\n            snprintf(buf, sizeof(buf), \"%s\", p->options.custom_headers[i].name);\n            if (!strncasecmp(p->options.custom_headers[i].name, \"Host:\", 5))\n            {\n                host_header_sent = true;\n            }\n        }\n\n        msg(D_PROXY, \"Send to HTTP proxy: '%s'\", buf);\n        if (!send_line_crlf(sd, buf))\n        {\n            return false;\n        }\n    }\n\n    if (!host_header_sent)\n    {\n        snprintf(buf, sizeof(buf), \"Host: %s\", host);\n        msg(D_PROXY, \"Send to HTTP proxy: '%s'\", buf);\n        if (!send_line_crlf(sd, buf))\n        {\n            return false;\n        }\n    }\n\n    /* send User-Agent string if provided */\n    if (p->options.user_agent)\n    {\n        snprintf(buf, sizeof(buf), \"User-Agent: %s\", p->options.user_agent);\n        msg(D_PROXY, \"Send to HTTP proxy: '%s'\", buf);\n        if (!send_line_crlf(sd, buf))\n        {\n            return false;\n        }\n    }\n\n    return true;\n}\n\n\nbool\nestablish_http_proxy_passthru(struct http_proxy_info *p,\n                              socket_descriptor_t sd, /* already open to proxy */\n                              const char *host,       /* openvpn server remote */\n                              const char *port,       /* openvpn server port */\n                              struct event_timeout *server_poll_timeout, struct buffer *lookahead,\n                              struct signal_info *sig_info)\n{\n    struct gc_arena gc = gc_new();\n    char buf[512];\n    int status;\n    int nparms;\n    bool ret = false;\n    bool processed = false;\n    volatile int *signal_received = &sig_info->signal_received;\n\n    /* get user/pass if not previously given */\n    if (p->auth_method == HTTP_AUTH_BASIC || p->auth_method == HTTP_AUTH_DIGEST)\n    {\n        get_user_pass_http(p, false);\n\n        if (p->up.nocache)\n        {\n            clear_user_pass_http();\n        }\n        unprotect_user_pass(&p->up);\n    }\n\n    /* are we being called again after getting the digest server nonce in the previous transaction?\n     */\n    if (p->auth_method == HTTP_AUTH_DIGEST && p->proxy_authenticate)\n    {\n        nparms = 1;\n        status = 407;\n    }\n    else\n    {\n        /* format HTTP CONNECT message */\n        snprintf(buf, sizeof(buf), \"CONNECT %s:%s HTTP/%s\", host, port, p->options.http_version);\n\n        msg(D_PROXY, \"Send to HTTP proxy: '%s'\", buf);\n\n        /* send HTTP CONNECT message to proxy */\n        if (!send_line_crlf(sd, buf))\n        {\n            goto error;\n        }\n\n        if (!add_proxy_headers(p, sd, host))\n        {\n            goto error;\n        }\n\n        /* auth specified? */\n        switch (p->auth_method)\n        {\n            case HTTP_AUTH_NONE:\n                break;\n\n            case HTTP_AUTH_BASIC:\n                snprintf(buf, sizeof(buf), \"Proxy-Authorization: Basic %s\",\n                         username_password_as_base64(p, &gc));\n                msg(D_PROXY, \"Attempting Basic Proxy-Authorization\");\n                dmsg(D_SHOW_KEYS, \"Send to HTTP proxy: '%s'\", buf);\n                if (!send_line_crlf(sd, buf))\n                {\n                    goto error;\n                }\n                break;\n\n            default:\n                ASSERT(0);\n        }\n\n        /* clear any sensitive content in buf */\n        secure_memzero(buf, sizeof(buf));\n\n        /* send empty CR, LF */\n        if (!send_crlf(sd))\n        {\n            goto error;\n        }\n\n        /* receive reply from proxy */\n        if (!recv_line(sd, buf, sizeof(buf), get_server_poll_remaining_time(server_poll_timeout),\n                       true, NULL, signal_received))\n        {\n            goto error;\n        }\n\n        /* remove trailing CR, LF */\n        chomp(buf);\n\n        msg(D_PROXY, \"HTTP proxy returned: '%s'\", buf);\n\n        /* parse return string */\n        nparms = sscanf(buf, \"%*s %d\", &status);\n    }\n\n    /* check for a \"407 Proxy Authentication Required\" response */\n    while (nparms >= 1 && status == 407)\n    {\n        msg(D_PROXY, \"Proxy requires authentication\");\n\n        if (p->auth_method == HTTP_AUTH_BASIC && !processed)\n        {\n            processed = true;\n        }\n#if PROXY_DIGEST_AUTH\n        else if (p->auth_method == HTTP_AUTH_DIGEST && !processed)\n        {\n            char *pa = p->proxy_authenticate;\n            const int method = p->auth_method;\n            ASSERT(pa);\n\n            if (method == HTTP_AUTH_DIGEST)\n            {\n                const char *http_method = \"CONNECT\";\n                const char *nonce_count = \"00000001\";\n                const char *qop = \"auth\";\n                const char *username = p->up.username;\n                const char *password = p->up.password;\n                char *opaque_kv = \"\";\n                char uri[128];\n                uint8_t cnonce_raw[8];\n                uint8_t *cnonce;\n                HASHHEX session_key;\n                HASHHEX response;\n\n                const char *realm = get_pa_var(\"realm\", pa, &gc);\n                const char *nonce = get_pa_var(\"nonce\", pa, &gc);\n                const char *algor = get_pa_var(\"algorithm\", pa, &gc);\n                const char *opaque = get_pa_var(\"opaque\", pa, &gc);\n\n                if (!realm || !nonce)\n                {\n                    msg(D_LINK_ERRORS, \"HTTP proxy: digest auth failed, malformed response \"\n                                       \"from server: realm= or nonce= missing\");\n                    goto error;\n                }\n\n                /* generate a client nonce */\n                ASSERT(rand_bytes(cnonce_raw, sizeof(cnonce_raw)));\n                cnonce = make_base64_string2(cnonce_raw, sizeof(cnonce_raw), &gc);\n\n\n                /* build the digest response */\n                snprintf(uri, sizeof(uri), \"%s:%s\", host, port);\n\n                if (opaque)\n                {\n                    const size_t len = strlen(opaque) + 16;\n                    opaque_kv = gc_malloc(len, false, &gc);\n                    snprintf(opaque_kv, len, \", opaque=\\\"%s\\\"\", opaque);\n                }\n\n                DigestCalcHA1(algor, username, realm, password, nonce, (char *)cnonce, session_key);\n                DigestCalcResponse(session_key, nonce, nonce_count, (char *)cnonce, qop,\n                                   http_method, uri, NULL, response);\n\n                /* format HTTP CONNECT message */\n                snprintf(buf, sizeof(buf), \"%s %s HTTP/%s\", http_method, uri,\n                         p->options.http_version);\n\n                msg(D_PROXY, \"Send to HTTP proxy: '%s'\", buf);\n\n                /* send HTTP CONNECT message to proxy */\n                if (!send_line_crlf(sd, buf))\n                {\n                    goto error;\n                }\n\n                /* send HOST etc, */\n                if (!add_proxy_headers(p, sd, host))\n                {\n                    goto error;\n                }\n\n                /* send digest response */\n                if (!checked_snprintf(\n                        buf, sizeof(buf),\n                        \"Proxy-Authorization: Digest username=\\\"%s\\\", realm=\\\"%s\\\", nonce=\\\"%s\\\", uri=\\\"%s\\\", qop=%s, nc=%s, cnonce=\\\"%s\\\", response=\\\"%s\\\"%s\",\n                        username, realm, nonce, uri, qop, nonce_count, cnonce, response, opaque_kv))\n                {\n                    goto error;\n                }\n\n                msg(D_PROXY, \"Send to HTTP proxy: '%s'\", buf);\n                if (!send_line_crlf(sd, buf))\n                {\n                    goto error;\n                }\n                if (!send_crlf(sd))\n                {\n                    goto error;\n                }\n                /* clear any sensitive content in buf */\n                secure_memzero(buf, sizeof(buf));\n\n                /* receive reply from proxy */\n                if (!recv_line(sd, buf, sizeof(buf),\n                               get_server_poll_remaining_time(server_poll_timeout), true, NULL,\n                               signal_received))\n                {\n                    goto error;\n                }\n\n                /* remove trailing CR, LF */\n                chomp(buf);\n\n                msg(D_PROXY, \"HTTP proxy returned: '%s'\", buf);\n\n                /* parse return string */\n                nparms = sscanf(buf, \"%*s %d\", &status);\n                processed = true;\n            }\n            else\n            {\n                msg(D_PROXY, \"HTTP proxy: digest method not supported\");\n                goto error;\n            }\n        }\n#endif /* if PROXY_DIGEST_AUTH */\n        else if (p->options.auth_retry)\n        {\n            /* figure out what kind of authentication the proxy needs */\n            char *pa = NULL;\n            const int method = get_proxy_authenticate(\n                sd, get_server_poll_remaining_time(server_poll_timeout), &pa, signal_received);\n            if (method != HTTP_AUTH_NONE)\n            {\n                if (pa)\n                {\n                    msg(D_PROXY, \"HTTP proxy authenticate '%s'\", pa);\n                }\n                if (p->options.auth_retry == PAR_NCT && method == HTTP_AUTH_BASIC)\n                {\n                    msg(D_PROXY,\n                        \"HTTP proxy: support for basic auth and other cleartext proxy auth methods is disabled\");\n                    free(pa);\n                    goto error;\n                }\n                p->auth_method = method;\n                store_proxy_authenticate(p, pa);\n                ret = true;\n                goto done;\n            }\n            else\n            {\n                msg(D_PROXY,\n                    \"HTTP proxy: do not recognize the authentication method required by proxy\");\n                free(pa);\n                goto error;\n            }\n        }\n        else\n        {\n            if (!processed)\n            {\n                msg(D_PROXY, \"HTTP proxy: no support for proxy authentication method\");\n            }\n            goto error;\n        }\n    }\n\n    /* check return code, success = 200 */\n    if (nparms < 1 || status != 200)\n    {\n        msg(D_LINK_ERRORS, \"HTTP proxy returned bad status\");\n#if 0\n        /* DEBUGGING -- show a multi-line HTTP error response */\n        dump_residual(sd, get_server_poll_remaining_time(server_poll_timeout), signal_received);\n#endif\n        goto error;\n    }\n\n    /* SUCCESS */\n\n    /* receive line from proxy and discard */\n    if (!recv_line(sd, NULL, 0, get_server_poll_remaining_time(server_poll_timeout), true, NULL,\n                   signal_received))\n    {\n        goto error;\n    }\n\n    /*\n     * Toss out any extraneous chars, but don't throw away the\n     * start of the OpenVPN data stream (put it in lookahead).\n     */\n    while (recv_line(sd, NULL, 0, 2, false, lookahead, signal_received))\n    {\n    }\n\n    /* reset queried_creds so that we don't think that the next creds request is due to an auth\n     * error */\n    p->queried_creds = false;\n\n#if 0\n    if (lookahead && BLEN(lookahead))\n    {\n        msg(M_INFO, \"HTTP PROXY: lookahead: %s\", format_hex(BPTR(lookahead), BLEN(lookahead), 0));\n    }\n#endif\n\ndone:\n    purge_user_pass(&p->up, true);\n    gc_free(&gc);\n    return ret;\n\nerror:\n    purge_user_pass(&p->up, true);\n    register_signal(sig_info, SIGUSR1, \"HTTP proxy error\"); /* SOFT-SIGUSR1 -- HTTP proxy error */\n    gc_free(&gc);\n    return ret;\n}\n"
  },
  {
    "path": "src/openvpn/proxy.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef PROXY_H\n#define PROXY_H\n\n#include \"buffer.h\"\n#include \"misc.h\"\n\n/* HTTP CONNECT authentication methods */\n#define HTTP_AUTH_NONE   0\n#define HTTP_AUTH_BASIC  1\n#define HTTP_AUTH_DIGEST 2\n/* #define HTTP_AUTH_NTLM   3 removed in OpenVPN 2.7 */\n/* #define HTTP_AUTH_NTLM2  4 removed in OpenVPN 2.8 */\n#define HTTP_AUTH_N      5 /* number of HTTP_AUTH methods */\n\nstruct http_custom_header\n{\n    const char *name;\n    const char *content;\n};\n\n#define MAX_CUSTOM_HTTP_HEADER 10\nstruct http_proxy_options\n{\n    const char *server;\n    const char *port;\n\n#define PAR_NO  0 /* don't support any auth retries */\n#define PAR_ALL 1 /* allow all proxy auth protocols */\n#define PAR_NCT 2 /* disable cleartext proxy auth protocols */\n    int auth_retry;\n\n    const char *auth_method_string;\n    const char *auth_file;\n    const char *auth_file_up; /* specified with --http-proxy-user-pass */\n    const char *http_version;\n    const char *user_agent;\n    struct http_custom_header custom_headers[MAX_CUSTOM_HTTP_HEADER];\n    bool inline_creds; /* auth_file_up is inline credentials */\n    bool first_time;   /* indicates if we need to wipe user creds at the first iteration of the main\n                          loop */\n    bool nocache;\n};\n\nstruct http_proxy_info\n{\n    bool defined;\n    int auth_method;\n    struct http_proxy_options options;\n    struct user_pass up;\n    char *proxy_authenticate;\n    bool queried_creds;\n};\n\nstruct http_proxy_options *init_http_proxy_options_once(struct http_proxy_options **hpo,\n                                                        struct gc_arena *gc);\n\nstruct http_proxy_info *http_proxy_new(const struct http_proxy_options *o);\n\nvoid http_proxy_close(struct http_proxy_info *hp);\n\nbool proxy_recv_char(uint8_t *c, const char *name, socket_descriptor_t sd,\n                     struct timeval *timeout, volatile int *signal_received);\n\nbool proxy_send(socket_descriptor_t sd, const void *buf, size_t buf_len);\n\nbool establish_http_proxy_passthru(struct http_proxy_info *p,\n                                   socket_descriptor_t sd, /* already open to proxy */\n                                   const char *host,       /* openvpn server remote */\n                                   const char *port,       /* openvpn server port */\n                                   struct event_timeout *server_poll_timeout,\n                                   struct buffer *lookahead, struct signal_info *sig_info);\n\nuint8_t *make_base64_string2(const uint8_t *str, int str_len, struct gc_arena *gc);\n\nuint8_t *make_base64_string(const uint8_t *str, struct gc_arena *gc);\n\n#endif /* PROXY_H */\n"
  },
  {
    "path": "src/openvpn/ps.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if PORT_SHARE\n\n#include \"event.h\"\n#include \"socket.h\"\n#include \"fdmisc.h\"\n#include \"crypto.h\"\n#include \"ps.h\"\n\n#include \"memdbg.h\"\n\nstruct port_share *port_share = NULL; /* GLOBAL */\n\n/* size of i/o buffers */\n#define PROXY_CONNECTION_BUFFER_SIZE 1500\n\n/* Command codes for foreground -> background communication */\n#define COMMAND_REDIRECT 10\n#define COMMAND_EXIT     11\n\n/* Response codes for background -> foreground communication */\n#define RESPONSE_INIT_SUCCEEDED 20\n#define RESPONSE_INIT_FAILED    21\n\n/*\n * Return values for proxy_connection_io functions\n */\n\n#define IOSTAT_EAGAIN_ON_READ  0 /* recv returned EAGAIN */\n#define IOSTAT_EAGAIN_ON_WRITE 1 /* send returned EAGAIN */\n#define IOSTAT_READ_ERROR      2 /* the other end of our read socket (pc) was closed */\n#define IOSTAT_WRITE_ERROR     3 /* the other end of our write socket (pc->counterpart) was closed */\n#define IOSTAT_GOOD            4 /* nothing to report */\n\n/*\n * A foreign (non-OpenVPN) connection we are proxying,\n * usually HTTPS\n */\nstruct proxy_connection\n{\n    bool defined;\n    struct proxy_connection *next;\n    struct proxy_connection *counterpart;\n    struct buffer buf;\n    bool buffer_initial;\n    unsigned int rwflags;\n    int sd;\n    char *jfn;\n};\n\n#if 0\nstatic const char *\nheadc(const struct buffer *buf)\n{\n    static char foo[16];\n    strncpy(foo, BSTR(buf), 15);\n    foo[15] = 0;\n    return foo;\n}\n#endif\n\nstatic inline void\nclose_socket_if_defined(const socket_descriptor_t sd)\n{\n    if (socket_defined(sd))\n    {\n        openvpn_close_socket(sd);\n    }\n}\n\n/*\n * Close most of parent's fds.\n * Keep stdin/stdout/stderr, plus one\n * other fd which is presumed to be\n * our pipe back to parent.\n * Admittedly, a bit of a kludge,\n * but posix doesn't give us a kind\n * of FD_CLOEXEC which will stop\n * fds from crossing a fork().\n */\nstatic void\nclose_fds_except(int keep)\n{\n    socket_descriptor_t i;\n    closelog();\n    for (i = 3; i <= 100; ++i)\n    {\n        if (i != keep)\n        {\n            openvpn_close_socket(i);\n        }\n    }\n}\n\n/*\n * Usually we ignore signals, because our parent will\n * deal with them.\n */\nstatic void\nset_signals(void)\n{\n    signal(SIGTERM, SIG_DFL);\n\n    signal(SIGINT, SIG_IGN);\n    signal(SIGHUP, SIG_IGN);\n    signal(SIGUSR1, SIG_IGN);\n    signal(SIGUSR2, SIG_IGN);\n    signal(SIGPIPE, SIG_IGN);\n}\n\n/*\n * Socket read/write functions.\n */\n\nstatic int\nrecv_control(const socket_descriptor_t fd)\n{\n    unsigned char c;\n    const ssize_t size = read(fd, &c, sizeof(c));\n    if (size == sizeof(c))\n    {\n        return c;\n    }\n    else\n    {\n        return -1;\n    }\n}\n\nstatic int\nsend_control(const socket_descriptor_t fd, int code)\n{\n    unsigned char c = (unsigned char)code;\n    const ssize_t size = write(fd, &c, sizeof(c));\n    if (size == sizeof(c))\n    {\n        return (int)size;\n    }\n    else\n    {\n        return -1;\n    }\n}\n\nstatic int\ncmsg_size(void)\n{\n    return CMSG_SPACE(sizeof(socket_descriptor_t));\n}\n\n/*\n * Send a command (char), data (head), and a file descriptor (sd_send) to a local process\n * over unix socket sd.  Unfortunately, there's no portable way to send file descriptors\n * to other processes, so this code, as well as its analog (control_message_from_parent below),\n * is Linux-specific. This function runs in the context of the main process and is used to\n * send commands, data, and file descriptors to the background process.\n */\nstatic void\nport_share_sendmsg(const socket_descriptor_t sd, const char command, const struct buffer *head,\n                   const socket_descriptor_t sd_send)\n{\n    if (socket_defined(sd))\n    {\n        struct msghdr mesg;\n        struct cmsghdr *h;\n        struct iovec iov[2];\n        socket_descriptor_t sd_null[2] = { SOCKET_UNDEFINED, SOCKET_UNDEFINED };\n        char cmd;\n        ssize_t status;\n\n        dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE: sendmsg sd=%d len=%d\", (int)sd_send,\n             head ? BLEN(head) : -1);\n\n        CLEAR(mesg);\n\n        cmd = command;\n\n        iov[0].iov_base = &cmd;\n        iov[0].iov_len = sizeof(cmd);\n        mesg.msg_iovlen = 1;\n\n        if (head)\n        {\n            iov[1].iov_base = BPTR(head);\n            iov[1].iov_len = BLENZ(head);\n            mesg.msg_iovlen = 2;\n        }\n\n        mesg.msg_iov = iov;\n\n        mesg.msg_controllen = cmsg_size();\n        mesg.msg_control = (char *)malloc(mesg.msg_controllen);\n        check_malloc_return(mesg.msg_control);\n        mesg.msg_flags = 0;\n\n        h = CMSG_FIRSTHDR(&mesg);\n        h->cmsg_level = SOL_SOCKET;\n        h->cmsg_type = SCM_RIGHTS;\n        h->cmsg_len = CMSG_LEN(sizeof(socket_descriptor_t));\n\n        if (socket_defined(sd_send))\n        {\n            memcpy(CMSG_DATA(h), &sd_send, sizeof(sd_send));\n        }\n        else\n        {\n            socketpair(PF_UNIX, SOCK_DGRAM, 0, sd_null);\n            memcpy(CMSG_DATA(h), &sd_null[0], sizeof(sd_null[0]));\n        }\n\n        status = sendmsg(sd, &mesg, MSG_NOSIGNAL);\n        if (status == -1)\n        {\n            msg(M_WARN | M_ERRNO,\n                \"PORT SHARE: sendmsg failed -- unable to communicate with background process (%d,%d,%d,%d)\",\n                sd, sd_send, sd_null[0], sd_null[1]);\n        }\n\n        close_socket_if_defined(sd_null[0]);\n        close_socket_if_defined(sd_null[1]);\n        free(mesg.msg_control);\n    }\n}\n\nstatic void\nproxy_entry_close_sd(struct proxy_connection *pc, struct event_set *es)\n{\n    if (pc->defined && socket_defined(pc->sd))\n    {\n        dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE PROXY: delete sd=%d\", (int)pc->sd);\n        if (es)\n        {\n            event_del(es, pc->sd);\n        }\n        openvpn_close_socket(pc->sd);\n        pc->sd = SOCKET_UNDEFINED;\n    }\n}\n\n/*\n * Mark a proxy entry and its counterpart for close.\n */\nstatic void\nproxy_entry_mark_for_close(struct proxy_connection *pc, struct event_set *es)\n{\n    if (pc->defined)\n    {\n        struct proxy_connection *cp = pc->counterpart;\n        proxy_entry_close_sd(pc, es);\n        free_buf(&pc->buf);\n        pc->buffer_initial = false;\n        pc->rwflags = 0;\n        pc->defined = false;\n        if (pc->jfn)\n        {\n            unlink(pc->jfn);\n            free(pc->jfn);\n            pc->jfn = NULL;\n        }\n        if (cp && cp->defined && cp->counterpart == pc)\n        {\n            proxy_entry_mark_for_close(cp, es);\n        }\n    }\n}\n\n/*\n * Run through the proxy entry list and delete all entries marked\n * for close.\n */\nstatic void\nproxy_list_housekeeping(struct proxy_connection **list)\n{\n    if (list)\n    {\n        struct proxy_connection *prev = NULL;\n        struct proxy_connection *pc = *list;\n\n        while (pc)\n        {\n            struct proxy_connection *next = pc->next;\n            if (!pc->defined)\n            {\n                free(pc);\n                if (prev)\n                {\n                    prev->next = next;\n                }\n                else\n                {\n                    *list = next;\n                }\n            }\n            else\n            {\n                prev = pc;\n            }\n            pc = next;\n        }\n    }\n}\n\n/*\n * Record IP/port of client in filesystem, so that server receiving\n * the proxy can determine true client origin.\n */\nstatic void\njournal_add(const char *journal_dir, struct proxy_connection *pc, struct proxy_connection *cp)\n{\n    struct openvpn_sockaddr from, to;\n\n    socklen_t slen = sizeof(from.addr);\n    socklen_t dlen = sizeof(to.addr);\n    if (!getpeername(pc->sd, (struct sockaddr *)&from.addr.sa, &slen)\n        && !getsockname(cp->sd, (struct sockaddr *)&to.addr.sa, &dlen))\n    {\n        struct gc_arena gc = gc_new();\n        const char *f = print_openvpn_sockaddr(&from, &gc);\n        const char *t = print_openvpn_sockaddr(&to, &gc);\n        size_t fnlen = strlen(journal_dir) + strlen(t) + 2;\n        char *jfn = (char *)malloc(fnlen);\n        check_malloc_return(jfn);\n        snprintf(jfn, fnlen, \"%s/%s\", journal_dir, t);\n        dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE PROXY: client origin %s -> %s\", jfn, f);\n        int fd = platform_open(jfn, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP);\n        if (fd != -1)\n        {\n            ssize_t write_len = strlen(f);\n            if (write(fd, f, write_len) != write_len)\n            {\n                msg(M_WARN, \"PORT SHARE: writing to journal file (%s) failed\", jfn);\n            }\n            close(fd);\n            cp->jfn = jfn;\n        }\n        else\n        {\n            msg(M_WARN | M_ERRNO, \"PORT SHARE: unable to write journal file in %s\", jfn);\n            free(jfn);\n        }\n        gc_free(&gc);\n    }\n}\n\n/*\n * Cleanup function, on proxy process exit.\n */\nstatic void\nproxy_list_close(struct proxy_connection **list)\n{\n    if (list)\n    {\n        struct proxy_connection *pc = *list;\n        while (pc)\n        {\n            proxy_entry_mark_for_close(pc, NULL);\n            pc = pc->next;\n        }\n        proxy_list_housekeeping(list);\n    }\n}\n\nstatic inline void\nproxy_connection_io_requeue(struct proxy_connection *pc, const unsigned int rwflags_new,\n                            struct event_set *es)\n{\n    if (socket_defined(pc->sd) && pc->rwflags != rwflags_new)\n    {\n        /*dmsg (D_PS_PROXY_DEBUG, \"PORT SHARE PROXY: requeue[%d] rwflags=%u\", (int)pc->sd,\n         * rwflags_new);*/\n        event_ctl(es, pc->sd, rwflags_new, (void *)pc);\n        pc->rwflags = rwflags_new;\n    }\n}\n\n/*\n * Create a new pair of proxy_connection entries, one for each\n * socket file descriptor involved in the proxy.  We are given\n * the client fd, and we should derive our own server fd by connecting\n * to the server given by server_addr/server_port.  Return true\n * on success and false on failure to connect to server.\n */\nstatic bool\nproxy_entry_new(struct proxy_connection **list, struct event_set *es,\n                const struct openvpn_sockaddr server_addr, const socket_descriptor_t sd_client,\n                struct buffer *initial_data, const char *journal_dir)\n{\n    socket_descriptor_t sd_server;\n    int status;\n    struct proxy_connection *pc;\n    struct proxy_connection *cp;\n\n    /* connect to port share server */\n    if ((sd_server = socket(server_addr.addr.sa.sa_family, SOCK_STREAM, IPPROTO_TCP)) < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"PORT SHARE PROXY: cannot create socket\");\n        return false;\n    }\n    status = openvpn_connect(sd_server, &server_addr.addr.sa, 5, NULL);\n    if (status)\n    {\n        msg(M_WARN, \"PORT SHARE PROXY: connect to port-share server failed\");\n        openvpn_close_socket(sd_server);\n        return false;\n    }\n    dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE PROXY: connect to port-share server succeeded\");\n\n    set_nonblock(sd_client);\n    set_nonblock(sd_server);\n\n    /* allocate 2 new proxy_connection objects */\n    ALLOC_OBJ_CLEAR(pc, struct proxy_connection);\n    ALLOC_OBJ_CLEAR(cp, struct proxy_connection);\n\n    /* client object */\n    pc->defined = true;\n    pc->next = cp;\n    pc->counterpart = cp;\n    pc->buf = *initial_data;\n    pc->buffer_initial = true;\n    pc->rwflags = EVENT_UNDEF;\n    pc->sd = sd_client;\n\n    /* server object */\n    cp->defined = true;\n    cp->next = *list;\n    cp->counterpart = pc;\n    cp->buf = alloc_buf(PROXY_CONNECTION_BUFFER_SIZE);\n    cp->buffer_initial = false;\n    cp->rwflags = EVENT_UNDEF;\n    cp->sd = sd_server;\n\n    /* add to list */\n    *list = pc;\n\n    /* add journal entry */\n    if (journal_dir)\n    {\n        journal_add(journal_dir, pc, cp);\n    }\n\n    dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE PROXY: NEW CONNECTION [c=%d s=%d]\", (int)sd_client,\n         (int)sd_server);\n\n    /* set initial i/o states */\n    proxy_connection_io_requeue(pc, EVENT_READ, es);\n    proxy_connection_io_requeue(cp, EVENT_READ | EVENT_WRITE, es);\n\n    return true;\n}\n\n/*\n * This function runs in the context of the background proxy process.\n * Receive a control message from the parent (sent by the port_share_sendmsg\n * function above) and act on it.  Return false if the proxy process should\n * exit, true otherwise.\n */\nstatic bool\ncontrol_message_from_parent(const socket_descriptor_t sd_control, struct proxy_connection **list,\n                            struct event_set *es, const struct openvpn_sockaddr server_addr,\n                            const int max_initial_buf, const char *journal_dir)\n{\n    /* this buffer needs to be large enough to handle the largest buffer\n     * that might be returned by the link_socket_read call in read_incoming_link. */\n    struct buffer buf = alloc_buf(max_initial_buf);\n\n    struct msghdr mesg;\n    struct cmsghdr *h;\n    struct iovec iov[2];\n    char command = 0;\n    ssize_t status;\n    int ret = true;\n\n    CLEAR(mesg);\n\n    iov[0].iov_base = &command;\n    iov[0].iov_len = sizeof(command);\n    iov[1].iov_base = BPTR(&buf);\n    iov[1].iov_len = BCAP(&buf);\n    mesg.msg_iov = iov;\n    mesg.msg_iovlen = 2;\n\n    mesg.msg_controllen = cmsg_size();\n    mesg.msg_control = (char *)malloc(mesg.msg_controllen);\n    check_malloc_return(mesg.msg_control);\n    mesg.msg_flags = 0;\n\n    h = CMSG_FIRSTHDR(&mesg);\n    h->cmsg_len = CMSG_LEN(sizeof(socket_descriptor_t));\n    h->cmsg_level = SOL_SOCKET;\n    h->cmsg_type = SCM_RIGHTS;\n    static const socket_descriptor_t socket_undefined = SOCKET_UNDEFINED;\n    memcpy(CMSG_DATA(h), &socket_undefined, sizeof(socket_undefined));\n\n    status = recvmsg(sd_control, &mesg, MSG_NOSIGNAL);\n    if (status != -1)\n    {\n        if (h == NULL || h->cmsg_len != CMSG_LEN(sizeof(socket_descriptor_t))\n            || h->cmsg_level != SOL_SOCKET || h->cmsg_type != SCM_RIGHTS)\n        {\n            msg(M_WARN, \"PORT SHARE PROXY: received unknown message\");\n        }\n        else\n        {\n            socket_descriptor_t received_fd;\n            memcpy(&received_fd, CMSG_DATA(h), sizeof(received_fd));\n            dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE PROXY: RECEIVED sd=%d\", (int)received_fd);\n\n            if (status >= 2 && command == COMMAND_REDIRECT)\n            {\n                buf.len = (int)status - 1;\n                if (proxy_entry_new(list, es, server_addr, received_fd, &buf, journal_dir))\n                {\n                    CLEAR(buf); /* we gave the buffer to proxy_entry_new */\n                }\n                else\n                {\n                    openvpn_close_socket(received_fd);\n                }\n            }\n            else if (status >= 1 && command == COMMAND_EXIT)\n            {\n                dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE PROXY: RECEIVED COMMAND_EXIT\");\n                openvpn_close_socket(received_fd); /* null socket */\n                ret = false;\n            }\n        }\n    }\n    free(mesg.msg_control);\n    free_buf(&buf);\n    return ret;\n}\n\nstatic int\nproxy_connection_io_recv(struct proxy_connection *pc)\n{\n    /* recv data from socket */\n    const ssize_t status = recv(pc->sd, BPTR(&pc->buf), BCAP(&pc->buf), MSG_NOSIGNAL);\n    if (status < 0)\n    {\n        return (errno == EAGAIN) ? IOSTAT_EAGAIN_ON_READ : IOSTAT_READ_ERROR;\n    }\n    else\n    {\n        if (!status)\n        {\n            return IOSTAT_READ_ERROR;\n        }\n        dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE PROXY: read[%d] %zd\", (int)pc->sd, status);\n        pc->buf.len = (int)status;\n    }\n    return IOSTAT_GOOD;\n}\n\nstatic int\nproxy_connection_io_send(struct proxy_connection *pc, int *bytes_sent)\n{\n    const socket_descriptor_t sd = pc->counterpart->sd;\n    const ssize_t status = send(sd, BPTR(&pc->buf), BLENZ(&pc->buf), MSG_NOSIGNAL);\n\n    if (status < 0)\n    {\n        const int e = errno;\n        return (e == EAGAIN) ? IOSTAT_EAGAIN_ON_WRITE : IOSTAT_WRITE_ERROR;\n    }\n    else\n    {\n        *bytes_sent += (int)status;\n        if (status != pc->buf.len)\n        {\n            dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE PROXY: partial write[%d], tried=%d got=%zd\", (int)sd,\n                 pc->buf.len, status);\n            buf_advance(&pc->buf, status);\n            return IOSTAT_EAGAIN_ON_WRITE;\n        }\n        else\n        {\n            dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE PROXY: wrote[%d] %zd\", (int)sd, status);\n            pc->buf.len = 0;\n            pc->buf.offset = 0;\n        }\n    }\n\n    /* realloc send buffer after initial send */\n    if (pc->buffer_initial)\n    {\n        free_buf(&pc->buf);\n        pc->buf = alloc_buf(PROXY_CONNECTION_BUFFER_SIZE);\n        pc->buffer_initial = false;\n    }\n    return IOSTAT_GOOD;\n}\n\n/*\n * Forward data from pc to pc->counterpart.\n */\n\nstatic int\nproxy_connection_io_xfer(struct proxy_connection *pc, const int max_transfer)\n{\n    int transferred = 0;\n    while (transferred < max_transfer)\n    {\n        if (!BLEN(&pc->buf))\n        {\n            const int status = proxy_connection_io_recv(pc);\n            if (status != IOSTAT_GOOD)\n            {\n                return status;\n            }\n        }\n\n        if (BLEN(&pc->buf))\n        {\n            const int status = proxy_connection_io_send(pc, &transferred);\n            if (status != IOSTAT_GOOD)\n            {\n                return status;\n            }\n        }\n    }\n    return IOSTAT_EAGAIN_ON_READ;\n}\n\n/*\n * Decide how the receipt of an EAGAIN status should affect our next IO queueing.\n */\nstatic bool\nproxy_connection_io_status(const int status, unsigned int *rwflags_pc, unsigned int *rwflags_cp)\n{\n    switch (status)\n    {\n        case IOSTAT_EAGAIN_ON_READ:\n            *rwflags_pc |= EVENT_READ;\n            *rwflags_cp &= ~EVENT_WRITE;\n            return true;\n\n        case IOSTAT_EAGAIN_ON_WRITE:\n            *rwflags_pc &= ~EVENT_READ;\n            *rwflags_cp |= EVENT_WRITE;\n            return true;\n\n        case IOSTAT_READ_ERROR:\n            return false;\n\n        case IOSTAT_WRITE_ERROR:\n            return false;\n\n        default:\n            msg(M_FATAL, \"PORT SHARE PROXY: unexpected status=%d\", status);\n    }\n    return false; /* NOTREACHED */\n}\n\n/*\n * Dispatch function for forwarding data between the two socket fds involved\n * in the proxied connection.\n */\nstatic int\nproxy_connection_io_dispatch(struct proxy_connection *pc, const unsigned int rwflags,\n                             struct event_set *es)\n{\n    const int max_transfer_per_iteration = 10000;\n    struct proxy_connection *cp = pc->counterpart;\n    unsigned int rwflags_pc = pc->rwflags;\n    unsigned int rwflags_cp = cp->rwflags;\n\n    ASSERT(pc->defined && cp->defined && cp->counterpart == pc);\n\n    if (rwflags & EVENT_READ)\n    {\n        const int status = proxy_connection_io_xfer(pc, max_transfer_per_iteration);\n        if (!proxy_connection_io_status(status, &rwflags_pc, &rwflags_cp))\n        {\n            goto bad;\n        }\n    }\n    if (rwflags & EVENT_WRITE)\n    {\n        const int status = proxy_connection_io_xfer(cp, max_transfer_per_iteration);\n        if (!proxy_connection_io_status(status, &rwflags_cp, &rwflags_pc))\n        {\n            goto bad;\n        }\n    }\n    proxy_connection_io_requeue(pc, rwflags_pc, es);\n    proxy_connection_io_requeue(cp, rwflags_cp, es);\n\n    return true;\n\nbad:\n    proxy_entry_mark_for_close(pc, es);\n    return false;\n}\n\n/*\n * This is the main function for the port share proxy background process.\n */\nstatic void\nport_share_proxy(const struct openvpn_sockaddr hostaddr, const socket_descriptor_t sd_control,\n                 const int max_initial_buf, const char *journal_dir)\n{\n    if (send_control(sd_control, RESPONSE_INIT_SUCCEEDED) >= 0)\n    {\n        void *sd_control_marker = (void *)1;\n        int maxevents = 256;\n        struct event_set *es;\n        struct event_set_return esr[64];\n        struct proxy_connection *list = NULL;\n        time_t last_housekeeping = 0;\n\n        msg(D_PS_PROXY, \"PORT SHARE PROXY: proxy starting\");\n\n        es = event_set_init(&maxevents, 0);\n        event_ctl(es, sd_control, EVENT_READ, sd_control_marker);\n        while (true)\n        {\n            int n_events;\n            struct timeval tv;\n            time_t current;\n\n            tv.tv_sec = 10;\n            tv.tv_usec = 0;\n            n_events = event_wait(es, &tv, esr, SIZE(esr));\n            /*dmsg (D_PS_PROXY_DEBUG, \"PORT SHARE PROXY: event_wait returned %d\", n_events);*/\n            current = time(NULL);\n            if (n_events > 0)\n            {\n                int i;\n                for (i = 0; i < n_events; ++i)\n                {\n                    const struct event_set_return *e = &esr[i];\n                    if (e->arg == sd_control_marker)\n                    {\n                        if (!control_message_from_parent(sd_control, &list, es, hostaddr,\n                                                         max_initial_buf, journal_dir))\n                        {\n                            goto done;\n                        }\n                    }\n                    else\n                    {\n                        struct proxy_connection *pc = (struct proxy_connection *)e->arg;\n                        if (pc->defined)\n                        {\n                            proxy_connection_io_dispatch(pc, e->rwflags, es);\n                        }\n                    }\n                }\n            }\n            else if (n_events < 0)\n            {\n                dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE PROXY: event_wait failed\");\n            }\n            if (current > last_housekeeping)\n            {\n                proxy_list_housekeeping(&list);\n                last_housekeeping = current;\n            }\n        }\n\ndone:\n        proxy_list_close(&list);\n        event_free(es);\n    }\n    msg(M_INFO, \"PORT SHARE PROXY: proxy exiting\");\n}\n\n/*\n * Called from the main OpenVPN process to enable the port\n * share proxy.\n */\nstruct port_share *\nport_share_open(const char *host, const char *port, const int max_initial_buf,\n                const char *journal_dir)\n{\n    socket_descriptor_t fd[2];\n    struct openvpn_sockaddr hostaddr;\n    struct port_share *ps;\n\n    ALLOC_OBJ_CLEAR(ps, struct port_share);\n    ps->foreground_fd = -1;\n    ps->background_pid = -1;\n\n    /*\n     * Get host's IP address\n     */\n    struct addrinfo *ai;\n    int status = openvpn_getaddrinfo(GETADDR_RESOLVE | GETADDR_FATAL, host, port,\n                                     0, NULL, AF_UNSPEC, &ai);\n    ASSERT(status == 0);\n    ASSERT(sizeof(hostaddr.addr) >= ai->ai_addrlen);\n    memcpy(&hostaddr.addr.sa, ai->ai_addr, ai->ai_addrlen);\n    freeaddrinfo(ai);\n\n    if (msg_test(D_PS_PROXY_DEBUG))\n    {\n        struct gc_arena gc = gc_new();\n        dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE PROXY: receiver will be %s\",\n             print_openvpn_sockaddr(&hostaddr, &gc));\n        gc_free(&gc);\n    }\n\n    /*\n     * Make a socket for foreground and background processes\n     * to communicate.\n     */\n    if (socketpair(PF_UNIX, SOCK_DGRAM, 0, fd) == -1)\n    {\n        msg(M_WARN | M_ERRNO, \"PORT SHARE: socketpair call failed\");\n        goto error;\n    }\n\n    /*\n     * Fork off background proxy process.\n     */\n    pid_t pid = fork();\n\n    if (pid < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"PORT SHARE: fork failed\");\n        goto error;\n    }\n    else if (pid)\n    {\n        /*\n         * Foreground Process\n         */\n\n        ps->background_pid = pid;\n\n        /* close our copy of child's socket */\n        openvpn_close_socket(fd[1]);\n\n        /* don't let future subprocesses inherit child socket */\n        set_cloexec(fd[0]);\n\n        /* wait for background child process to initialize */\n        int status = recv_control(fd[0]);\n        if (status == RESPONSE_INIT_SUCCEEDED)\n        {\n            /* note that this will cause possible EAGAIN when writing to\n             * control socket if proxy process is backlogged */\n            set_nonblock(fd[0]);\n\n            ps->foreground_fd = fd[0];\n            return ps;\n        }\n        else\n        {\n            msg(M_ERR, \"PORT SHARE: unexpected init recv_control status=%d\", status);\n        }\n    }\n    else\n    {\n        /*\n         * Background Process\n         */\n\n        /* Ignore most signals (the parent will receive them) */\n        set_signals();\n\n        /* Let msg know that we forked */\n        msg_forked();\n\n#ifdef ENABLE_MANAGEMENT\n        /* Don't interact with management interface */\n        management = NULL;\n#endif\n\n        /* close all parent fds except our socket back to parent */\n        close_fds_except(fd[1]);\n\n        /* no blocking on control channel back to parent */\n        set_nonblock(fd[1]);\n\n        /* execute the event loop */\n        port_share_proxy(hostaddr, fd[1], max_initial_buf, journal_dir);\n\n        openvpn_close_socket(fd[1]);\n\n        exit(0);\n        return NULL; /* NOTREACHED */\n    }\n\nerror:\n    port_share_close(ps);\n    return NULL;\n}\n\nvoid\nport_share_close(struct port_share *ps)\n{\n    if (ps)\n    {\n        if (ps->foreground_fd >= 0)\n        {\n            /* tell background process to exit */\n            port_share_sendmsg(ps->foreground_fd, COMMAND_EXIT, NULL, SOCKET_UNDEFINED);\n\n            /* wait for background process to exit */\n            dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE: waiting for background process to exit\");\n            if (ps->background_pid > 0)\n            {\n                waitpid(ps->background_pid, NULL, 0);\n            }\n            dmsg(D_PS_PROXY_DEBUG, \"PORT SHARE: background process exited\");\n\n            openvpn_close_socket(ps->foreground_fd);\n            ps->foreground_fd = -1;\n        }\n\n        free(ps);\n    }\n}\n\nvoid\nport_share_abort(struct port_share *ps)\n{\n    if (ps)\n    {\n        /* tell background process to exit */\n        if (ps->foreground_fd >= 0)\n        {\n            send_control(ps->foreground_fd, COMMAND_EXIT);\n            openvpn_close_socket(ps->foreground_fd);\n            ps->foreground_fd = -1;\n        }\n    }\n}\n\n/*\n * Given either the first 2 or 3 bytes of an initial client -> server\n * data payload, return true if the protocol is that of an OpenVPN\n * client attempting to connect with an OpenVPN server.\n */\nbool\nis_openvpn_protocol(const struct buffer *buf)\n{\n    const unsigned char *p = (const unsigned char *)BSTR(buf);\n    const int len = BLEN(buf);\n    if (len >= 3)\n    {\n        int plen = (p[0] << 8) | p[1];\n\n        if (p[2] == (P_CONTROL_HARD_RESET_CLIENT_V3 << P_OPCODE_SHIFT))\n        {\n            /* WKc is at least 290 byte (not including metadata):\n             *\n             * 16 bit len + 256 bit HMAC + 2048 bit Kc = 2320 bit\n             *\n             * This is increased by the normal length of client handshake +\n             * tls-crypt overhead (32)\n             *\n             * For metadata tls-crypt-v2.txt does not explicitly specify\n             * an upper limit but we also have TLS_CRYPT_V2_MAX_WKC_LEN\n             * as 1024 bytes. We err on the safe side with 255 extra overhead\n             *\n             * We don't do the 2 byte check for tls-crypt-v2 because it is very\n             * unrealistic to have only 2 bytes available.\n             */\n            return (plen >= 336 && plen < (1024 + 255));\n        }\n        else\n        {\n            /* For non tls-crypt2 we assume the packet length to valid between\n             * 14 and 255 */\n            return plen >= 14 && plen <= 255\n                   && (p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2 << P_OPCODE_SHIFT));\n        }\n    }\n    else if (len >= 2)\n    {\n        int plen = (p[0] << 8) | p[1];\n        return plen >= 14 && plen <= 255;\n    }\n    else\n    {\n        return true;\n    }\n}\n\n/*\n * Called from the foreground process.  Send a message to the background process that it\n * should proxy the TCP client on sd to the host/port defined in the initial port_share_open\n * call.\n */\nvoid\nport_share_redirect(struct port_share *ps, const struct buffer *head, socket_descriptor_t sd)\n{\n    if (ps)\n    {\n        port_share_sendmsg(ps->foreground_fd, COMMAND_REDIRECT, head, sd);\n    }\n}\n\n#endif /* if PORT_SHARE */\n"
  },
  {
    "path": "src/openvpn/ps.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef PS_H\n#define PS_H\n\n#if PORT_SHARE\n\n#include \"basic.h\"\n#include \"buffer.h\"\n#include \"ssl.h\"\n\nstruct port_share\n{\n    /* Foreground's socket to background process */\n    socket_descriptor_t foreground_fd;\n\n    /* Process ID of background process */\n    pid_t background_pid;\n};\n\nextern struct port_share *port_share;\n\nstruct port_share *port_share_open(const char *host, const char *port, const int max_initial_buf,\n                                   const char *journal_dir);\n\nvoid port_share_close(struct port_share *ps);\n\nvoid port_share_abort(struct port_share *ps);\n\nbool is_openvpn_protocol(const struct buffer *buf);\n\nvoid port_share_redirect(struct port_share *ps, const struct buffer *head, socket_descriptor_t sd);\n\n#endif /* if PORT_SHARE */\n#endif /* ifndef PS_H */\n"
  },
  {
    "path": "src/openvpn/push.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"push.h\"\n#include \"options.h\"\n#include \"crypto.h\"\n#include \"ssl.h\"\n#include \"ssl_verify.h\"\n#include \"ssl_ncp.h\"\n#include \"manage.h\"\n\n#include \"memdbg.h\"\n#include \"ssl_util.h\"\n#include \"options_util.h\"\n\n/*\n * Auth username/password\n *\n * Client received an authentication failed message from server.\n * Runs on client.\n */\nvoid\nreceive_auth_failed(struct context *c, const struct buffer *buffer)\n{\n    msg(M_VERB0, \"AUTH: Received control message: %s\", BSTR(buffer));\n    c->options.no_advance = true;\n\n    if (!c->options.pull)\n    {\n        return;\n    }\n\n    struct buffer buf = *buffer;\n\n    /* If the AUTH_FAIL message ends with a , it is an extended message that\n     * contains further flags */\n    bool authfail_extended = buf_string_compare_advance(&buf, \"AUTH_FAILED,\");\n\n    const char *reason = NULL;\n    if (authfail_extended && BLEN(&buf))\n    {\n        reason = BSTR(&buf);\n    }\n\n    if (authfail_extended && buf_string_match_head_str(&buf, \"TEMP\"))\n    {\n        parse_auth_failed_temp(&c->options, reason + strlen(\"TEMP\"));\n        register_signal(c->sig, SIGUSR1, \"auth-temp-failure (server temporary reject)\");\n    }\n\n    /* Before checking how to react on AUTH_FAILED, first check if the\n     * failed auth might be the result of an expired auth-token.\n     * Note that a server restart will trigger a generic AUTH_FAILED\n     * instead an AUTH_FAILED,SESSION so handle all AUTH_FAILED message\n     * identical for this scenario */\n    else if (ssl_clean_auth_token())\n    {\n        /* SOFT-SIGUSR1 -- Auth failure error */\n        register_signal(c->sig, SIGUSR1, \"auth-failure (auth-token)\");\n        c->options.no_advance = true;\n    }\n    else\n    {\n        switch (auth_retry_get())\n        {\n            case AR_NONE:\n                /* SOFT-SIGTERM -- Auth failure error */\n                register_signal(c->sig, SIGTERM, \"auth-failure\");\n                break;\n\n            case AR_INTERACT:\n                ssl_purge_auth(false);\n                /* Intentional [[fallthrough]]; */\n\n            case AR_NOINTERACT:\n                /* SOFT-SIGTUSR1 -- Auth failure error */\n                register_signal(c->sig, SIGUSR1, \"auth-failure\");\n                break;\n\n            default:\n                ASSERT(0);\n        }\n    }\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        management_auth_failure(management, UP_TYPE_AUTH, reason);\n    }\n    /*\n     * Save the dynamic-challenge text even when management is defined\n     */\n    if (authfail_extended && buf_string_match_head_str(&buf, \"CRV1:\") && BLEN(&buf))\n    {\n        ssl_put_auth_challenge(BSTR(&buf));\n    }\n#endif /* ifdef ENABLE_MANAGEMENT */\n}\n\n/*\n * Act on received restart message from server\n */\nvoid\nserver_pushed_signal(struct context *c, const struct buffer *buffer, const bool restart,\n                     const int adv)\n{\n    if (c->options.pull)\n    {\n        struct buffer buf = *buffer;\n        const char *m = \"\";\n        if (buf_advance(&buf, adv) && buf_read_u8(&buf) == ',' && BLEN(&buf))\n        {\n            m = BSTR(&buf);\n        }\n\n        /* preserve cached passwords? */\n        /* advance to next server? */\n        {\n            bool purge = true;\n\n            if (m[0] == '[')\n            {\n                int i;\n                for (i = 1; m[i] != '\\0' && m[i] != ']'; ++i)\n                {\n                    if (m[i] == 'P')\n                    {\n                        purge = false;\n                    }\n                    else if (m[i] == 'N')\n                    {\n                        /* next server? */\n                        c->options.no_advance = false;\n                    }\n                }\n            }\n            if (purge)\n            {\n                ssl_purge_auth(true);\n            }\n        }\n\n        if (restart)\n        {\n            msg(D_STREAM_ERRORS, \"Connection reset command was pushed by server ('%s')\", m);\n            /* SOFT-SIGUSR1 -- server-pushed connection reset */\n            register_signal(c->sig, SIGUSR1, \"server-pushed-connection-reset\");\n        }\n        else\n        {\n            msg(D_STREAM_ERRORS, \"Halt command was pushed by server ('%s')\", m);\n            /* SOFT-SIGTERM -- server-pushed halt */\n            register_signal(c->sig, SIGTERM, \"server-pushed-halt\");\n        }\n#ifdef ENABLE_MANAGEMENT\n        if (management)\n        {\n            management_notify(management, \"info\", c->sig->signal_text, m);\n        }\n#endif\n    }\n}\n\nvoid\nreceive_exit_message(struct context *c)\n{\n    dmsg(D_STREAM_ERRORS, \"CC-EEN exit message received by peer\");\n    /* With control channel exit notification, we want to give the session\n     * enough time to handle retransmits and acknowledgment, so that eventual\n     * retries from the client to resend the exit or ACKs will not trigger\n     * a new session (we already forgot the session but the packet's HMAC\n     * is still valid).  This could happen for the entire period that the\n     * HMAC timeslot is still valid, but waiting five seconds here does not\n     * hurt much, takes care of the retransmits, and is easier code-wise.\n     *\n     * This does not affect OCC exit since the HMAC session code will\n     * ignore DATA packets\n     * */\n    if (c->options.mode == MODE_SERVER)\n    {\n        if (!schedule_exit(c))\n        {\n            /* Return early when we don't need to notify management */\n            return;\n        }\n    }\n    else\n    {\n        register_signal(c->sig, SIGUSR1, \"remote-exit\");\n    }\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        management_notify(management, \"info\", \"remote-exit\", \"EXIT\");\n    }\n#endif\n}\n\n\nvoid\nserver_pushed_info(const struct buffer *buffer, const int adv)\n{\n    const char *m = \"\";\n    struct buffer buf = *buffer;\n\n    if (buf_advance(&buf, adv) && buf_read_u8(&buf) == ',' && BLEN(&buf))\n    {\n        m = BSTR(&buf);\n    }\n\n#ifdef ENABLE_MANAGEMENT\n    struct gc_arena gc;\n    if (management)\n    {\n        gc = gc_new();\n\n        /*\n         * We use >INFOMSG here instead of plain >INFO since INFO is used to\n         * for management greeting and we don't want to confuse the client\n         */\n        struct buffer out = alloc_buf_gc(256, &gc);\n        if (buf_printf(&out, \">%s:%s\", \"INFOMSG\", m))\n        {\n            management_notify_generic(management, BSTR(&out));\n        }\n        else\n        {\n            msg(D_PUSH_ERRORS,\n                \"WARNING: Received INFO command is too long, won't notify management client.\");\n        }\n\n        gc_free(&gc);\n    }\n#endif\n    msg(D_PUSH, \"Info command was pushed by server ('%s')\", m);\n}\n\nvoid\nreceive_cr_response(struct context *c, const struct buffer *buffer)\n{\n    struct buffer buf = *buffer;\n    const char *m = \"\";\n\n    if (buf_advance(&buf, 11) && buf_read_u8(&buf) == ',' && BLEN(&buf))\n    {\n        m = BSTR(&buf);\n    }\n#ifdef ENABLE_MANAGEMENT\n    struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];\n    struct man_def_auth_context *mda = session->opt->mda_context;\n    struct env_set *es = session->opt->es;\n    unsigned int mda_key_id = get_primary_key(c->c2.tls_multi)->mda_key_id;\n\n    management_notify_client_cr_response(mda_key_id, mda, es, m);\n#endif\n#if ENABLE_PLUGIN\n    verify_crresponse_plugin(c->c2.tls_multi, m);\n#endif\n    verify_crresponse_script(c->c2.tls_multi, m);\n    msg(D_PUSH, \"CR response was sent by client ('%s')\", m);\n}\n\n/**\n * Parse the keyword for the AUTH_PENDING request\n * @param buffer                buffer containing the keywords, the buffer's\n *                              content will be modified by this function\n * @param server_timeout        timeout pushed by the server or unchanged\n *                              if the server does not push a timeout\n */\nstatic void\nparse_auth_pending_keywords(const struct buffer *buffer, unsigned int *server_timeout)\n{\n    struct buffer buf = *buffer;\n\n    /* does the buffer start with \"AUTH_PENDING,\" ? */\n    if (!buf_advance(&buf, strlen(\"AUTH_PENDING\")) || !(buf_read_u8(&buf) == ',') || !BLEN(&buf))\n    {\n#ifdef ENABLE_MANAGEMENT\n        if (management)\n        {\n            management_set_state(management, OPENVPN_STATE_AUTH_PENDING, \"\", NULL, NULL, NULL,\n                                 NULL);\n        }\n#endif\n\n        return;\n    }\n\n    /* parse the keywords in the same way that push options are parsed */\n    char line[OPTION_LINE_SIZE];\n\n#ifdef ENABLE_MANAGEMENT\n    /* Need to do the management notification with the keywords before\n     * buf_parse is called, as it will insert \\0 bytes into the buffer */\n    if (management)\n    {\n        management_set_state(management, OPENVPN_STATE_AUTH_PENDING, BSTR(&buf), NULL, NULL, NULL,\n                             NULL);\n    }\n#endif\n\n    while (buf_parse(&buf, ',', line, sizeof(line)))\n    {\n        if (sscanf(line, \"timeout %u\", server_timeout) != 1)\n        {\n            msg(D_PUSH, \"ignoring AUTH_PENDING parameter: %s\", line);\n        }\n    }\n}\n\nvoid\nreceive_auth_pending(struct context *c, const struct buffer *buffer)\n{\n    if (!c->options.pull)\n    {\n        return;\n    }\n\n    /* Cap the increase at the maximum time we are willing stay in the\n     * pending authentication state */\n    unsigned int max_timeout =\n        max_uint(c->options.renegotiate_seconds / 2, c->options.handshake_window);\n\n    /* try to parse parameter keywords, default to hand-winow timeout if the\n     * server does not supply a timeout */\n    unsigned int server_timeout = c->options.handshake_window;\n    parse_auth_pending_keywords(buffer, &server_timeout);\n\n    msg(D_PUSH,\n        \"AUTH_PENDING received, extending handshake timeout from %us \"\n        \"to %us\",\n        c->options.handshake_window, min_uint(max_timeout, server_timeout));\n\n    const struct key_state *ks = get_primary_key(c->c2.tls_multi);\n    c->c2.push_request_timeout = ks->established + min_uint(max_timeout, server_timeout);\n}\n\n/**\n * Add an option to the given push list by providing a format string.\n *\n * The string added to the push options is allocated in o->gc, so the caller\n * does not have to preserve anything.\n *\n * @param gc        GC arena where options are allocated\n * @param push_list Push list containing options\n * @param msglevel  The message level to use when printing errors\n * @param fmt       Format string for the option\n * @param ...       Format string arguments\n *\n * @return true on success, false on failure.\n */\nstatic bool push_option_fmt(struct gc_arena *gc, struct push_list *push_list,\n                            msglvl_t msglevel, const char *fmt, ...)\n#ifdef __GNUC__\n#if __USE_MINGW_ANSI_STDIO\n    __attribute__((format(gnu_printf, 4, 5)))\n#else\n    __attribute__((format(__printf__, 4, 5)))\n#endif\n#endif\n    ;\n\n/*\n * Send auth failed message from server to client.\n *\n * Does nothing if an exit is already scheduled\n */\nvoid\nsend_auth_failed(struct context *c, const char *client_reason)\n{\n    if (!schedule_exit(c))\n    {\n        msg(D_TLS_DEBUG, \"exit already scheduled for context\");\n        return;\n    }\n\n    struct gc_arena gc = gc_new();\n    static const char auth_failed[] = \"AUTH_FAILED\";\n    size_t len;\n\n    len = (client_reason ? strlen(client_reason) + 1 : 0) + sizeof(auth_failed);\n    if (len > PUSH_BUNDLE_SIZE)\n    {\n        len = PUSH_BUNDLE_SIZE;\n    }\n\n    {\n        struct buffer buf = alloc_buf_gc(len, &gc);\n        buf_printf(&buf, auth_failed);\n        if (client_reason)\n        {\n            buf_printf(&buf, \",%s\", client_reason);\n        }\n\n        /* We kill the whole session, send the AUTH_FAILED to any TLS session\n         * that might be active */\n        send_control_channel_string_dowork(&c->c2.tls_multi->session[TM_INITIAL], BSTR(&buf),\n                                           D_PUSH);\n        send_control_channel_string_dowork(&c->c2.tls_multi->session[TM_ACTIVE], BSTR(&buf),\n                                           D_PUSH);\n\n        reschedule_multi_process(c);\n    }\n\n    gc_free(&gc);\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\nbool\nsend_auth_pending_messages(struct tls_multi *tls_multi, struct tls_session *session,\n                           const char *extra, unsigned int timeout)\n{\n    struct key_state *ks = &session->key[KS_PRIMARY];\n\n    static const char info_pre[] = \"INFO_PRE,\";\n\n    const char *const peer_info = tls_multi->peer_info;\n    unsigned int proto = extract_iv_proto(peer_info);\n\n\n    /* Calculate the maximum timeout and subtract the time we already waited */\n    unsigned int max_timeout =\n        max_uint(tls_multi->opt.renegotiate_seconds / 2, tls_multi->opt.handshake_window);\n    max_timeout = max_timeout - (now - ks->initial);\n    timeout = min_uint(max_timeout, timeout);\n\n    struct gc_arena gc = gc_new();\n    if ((proto & IV_PROTO_AUTH_PENDING_KW) == 0)\n    {\n        send_control_channel_string_dowork(session, \"AUTH_PENDING\", D_PUSH);\n    }\n    else\n    {\n        static const char auth_pre[] = \"AUTH_PENDING,timeout \";\n        /* Assume a worst case of 8 byte uint64 in decimal which */\n        /* needs 20 bytes */\n        size_t len = 20 + 1 + sizeof(auth_pre);\n        struct buffer buf = alloc_buf_gc(len, &gc);\n        buf_printf(&buf, auth_pre);\n        buf_printf(&buf, \"%u\", timeout);\n        send_control_channel_string_dowork(session, BSTR(&buf), D_PUSH);\n    }\n\n    size_t len = strlen(extra) + 1 + sizeof(info_pre);\n    if (len > PUSH_BUNDLE_SIZE)\n    {\n        gc_free(&gc);\n        return false;\n    }\n\n    struct buffer buf = alloc_buf_gc(len, &gc);\n    buf_printf(&buf, info_pre);\n    buf_printf(&buf, \"%s\", extra);\n    send_control_channel_string_dowork(session, BSTR(&buf), D_PUSH);\n\n    ks->auth_deferred_expire = now + timeout;\n\n    gc_free(&gc);\n    return true;\n}\n\n/*\n * Send restart message from server to client.\n */\nvoid\nsend_restart(struct context *c, const char *kill_msg)\n{\n    schedule_exit(c);\n    send_control_channel_string(c, kill_msg ? kill_msg : \"RESTART\", D_PUSH);\n}\n\n/*\n * Push/Pull\n */\n\nvoid\nincoming_push_message(struct context *c, const struct buffer *buffer)\n{\n    struct gc_arena gc = gc_new();\n    unsigned int option_types_found = 0;\n\n    msg(D_PUSH, \"PUSH: Received control message: '%s'\",\n        sanitize_control_message(BSTR(buffer), &gc));\n\n    int status = process_incoming_push_msg(c, buffer, c->options.pull, pull_permission_mask(c),\n                                           &option_types_found);\n\n    if (status == PUSH_MSG_ERROR)\n    {\n        msg(D_PUSH_ERRORS, \"WARNING: Received bad push/pull message: %s\",\n            sanitize_control_message(BSTR(buffer), &gc));\n    }\n    else if (status == PUSH_MSG_REPLY || status == PUSH_MSG_UPDATE\n             || status == PUSH_MSG_CONTINUATION)\n    {\n        c->options.push_option_types_found |= option_types_found;\n\n        /* delay bringing tun/tap up until --push parms received from remote */\n        if (status == PUSH_MSG_REPLY || status == PUSH_MSG_UPDATE)\n        {\n            if (!options_postprocess_pull(&c->options, c->c2.es))\n            {\n                goto error;\n            }\n            if (status == PUSH_MSG_REPLY)\n            {\n                if (!do_up(c, true, c->options.push_option_types_found))\n                {\n                    msg(D_PUSH_ERRORS, \"Failed to open tun/tap interface\");\n                    goto error;\n                }\n            }\n            else\n            {\n                /* Clear push_update_options_found for next PUSH_UPDATE sequence */\n                c->options.push_update_options_found = 0;\n\n                /* Use accumulated option_types_found for the entire PUSH_UPDATE sequence */\n                if (!c->options.push_option_types_found)\n                {\n                    msg(M_WARN, \"No updatable options found in incoming PUSH_UPDATE message\");\n                }\n                else if (!do_update(c, c->options.push_option_types_found))\n                {\n                    msg(D_PUSH_ERRORS, \"Failed to update options\");\n                    goto error;\n                }\n            }\n        }\n        event_timeout_clear(&c->c2.push_request_interval);\n        event_timeout_clear(&c->c2.wait_for_connect);\n    }\n\n    goto cleanup;\n\nerror:\n    register_signal(c->sig, SIGUSR1, \"process-push-msg-failed\");\ncleanup:\n    gc_free(&gc);\n}\n\nbool\nsend_push_request(struct context *c)\n{\n    const struct key_state *ks = get_primary_key(c->c2.tls_multi);\n\n    /* We timeout here under two conditions:\n     * a) we reached the hard limit of push_request_timeout\n     * b) we have not seen anything from the server in hand_window time\n     *\n     * for non auth-pending scenario, push_request_timeout is the same as\n     * hand_window timeout. For b) every PUSH_REQUEST is a acknowledged by\n     * the server by a P_ACK_V1 packet that reset the keepalive timer\n     */\n\n    if (c->c2.push_request_timeout > now\n        && (now - ks->peer_last_packet) < c->options.handshake_window)\n    {\n        return send_control_channel_string(c, \"PUSH_REQUEST\", D_PUSH);\n    }\n    else\n    {\n        msg(D_STREAM_ERRORS, \"No reply from server to push requests in %ds\",\n            (int)(now - ks->established));\n        /* SOFT-SIGUSR1 -- server-pushed connection reset */\n        register_signal(c->sig, SIGUSR1, \"no-push-reply\");\n        return false;\n    }\n}\n\n/**\n * Prepare push option for auth-token\n * @param tls_multi     tls multi context of VPN tunnel\n * @param gc            gc arena for allocating push options\n * @param push_list     push list to where options are added\n */\nvoid\nprepare_auth_token_push_reply(struct tls_multi *tls_multi, struct gc_arena *gc,\n                              struct push_list *push_list)\n{\n    /*\n     * If server uses --auth-gen-token and we have an auth token\n     * to send to the client\n     */\n    if (tls_multi->auth_token)\n    {\n        push_option_fmt(gc, push_list, M_USAGE, \"auth-token %s\", tls_multi->auth_token);\n\n        char *base64user = NULL;\n        int ret = openvpn_base64_encode(tls_multi->locked_username,\n                                        (int)strlen(tls_multi->locked_username), &base64user);\n        if (ret < USER_PASS_LEN && ret > 0)\n        {\n            push_option_fmt(gc, push_list, M_USAGE, \"auth-token-user %s\", base64user);\n        }\n        free(base64user);\n    }\n}\n\n/**\n * Prepare push options, based on local options\n *\n * @param c             context structure storing data for VPN tunnel\n * @param gc            gc arena for allocating push options\n * @param push_list     push list to where options are added\n *\n * @return true on success, false on failure.\n */\nbool\nprepare_push_reply(struct context *c, struct gc_arena *gc, struct push_list *push_list)\n{\n    struct tls_multi *tls_multi = c->c2.tls_multi;\n    struct options *o = &c->options;\n\n    /* ipv6 */\n    if (c->c2.push_ifconfig_ipv6_defined && !o->push_ifconfig_ipv6_blocked)\n    {\n        push_option_fmt(gc, push_list, M_USAGE, \"ifconfig-ipv6 %s/%d %s\",\n                        print_in6_addr(c->c2.push_ifconfig_ipv6_local, 0, gc),\n                        c->c2.push_ifconfig_ipv6_netbits,\n                        print_in6_addr(c->c2.push_ifconfig_ipv6_remote, 0, gc));\n    }\n\n    /* ipv4 */\n    if (c->c2.push_ifconfig_defined && c->c2.push_ifconfig_local\n        && c->c2.push_ifconfig_remote_netmask && !o->push_ifconfig_ipv4_blocked)\n    {\n        in_addr_t ifconfig_local = c->c2.push_ifconfig_local;\n        if (c->c2.push_ifconfig_local_alias)\n        {\n            ifconfig_local = c->c2.push_ifconfig_local_alias;\n        }\n        push_option_fmt(gc, push_list, M_USAGE, \"ifconfig %s %s\",\n                        print_in_addr_t(ifconfig_local, 0, gc),\n                        print_in_addr_t(c->c2.push_ifconfig_remote_netmask, 0, gc));\n    }\n\n    if (tls_multi->use_peer_id)\n    {\n        push_option_fmt(gc, push_list, M_USAGE, \"peer-id %d\", tls_multi->peer_id);\n    }\n    /*\n     * If server uses --auth-gen-token and we have an auth token\n     * to send to the client\n     */\n    prepare_auth_token_push_reply(tls_multi, gc, push_list);\n\n    /*\n     * Push the selected cipher, at this point the cipher has been\n     * already negotiated and been fixed.\n     *\n     * We avoid pushing the cipher to clients not supporting NCP\n     * to avoid error messages in their logs\n     */\n    if (tls_peer_supports_ncp(c->c2.tls_multi->peer_info))\n    {\n        push_option_fmt(gc, push_list, M_USAGE, \"cipher %s\", o->ciphername);\n    }\n\n    struct buffer proto_flags = alloc_buf_gc(128, gc);\n\n    if (o->imported_protocol_flags & CO_USE_CC_EXIT_NOTIFY)\n    {\n        buf_printf(&proto_flags, \" cc-exit\");\n\n        /* if the cc exit flag is supported, pushing tls-ekm via protocol-flags\n         * is also supported */\n        if (o->imported_protocol_flags & CO_USE_TLS_KEY_MATERIAL_EXPORT)\n        {\n            buf_printf(&proto_flags, \" tls-ekm\");\n        }\n    }\n    else if (o->imported_protocol_flags & CO_USE_TLS_KEY_MATERIAL_EXPORT)\n    {\n        push_option_fmt(gc, push_list, M_USAGE, \"key-derivation tls-ekm\");\n    }\n\n    if (o->imported_protocol_flags & CO_USE_DYNAMIC_TLS_CRYPT)\n    {\n        buf_printf(&proto_flags, \" dyn-tls-crypt\");\n    }\n\n    if (o->imported_protocol_flags & CO_EPOCH_DATA_KEY_FORMAT)\n    {\n        buf_printf(&proto_flags, \" aead-epoch\");\n    }\n\n    if (buf_len(&proto_flags) > 0)\n    {\n        push_option_fmt(gc, push_list, M_USAGE, \"protocol-flags%s\", buf_str(&proto_flags));\n    }\n\n    /* Push our mtu to the peer if it supports pushable MTUs */\n    int client_max_mtu = 0;\n    const char *iv_mtu = extract_var_peer_info(tls_multi->peer_info, \"IV_MTU=\", gc);\n\n    if (iv_mtu && sscanf(iv_mtu, \"%d\", &client_max_mtu) == 1)\n    {\n        push_option_fmt(gc, push_list, M_USAGE, \"tun-mtu %d\", o->ce.tun_mtu);\n        if (client_max_mtu < o->ce.tun_mtu)\n        {\n            msg(M_WARN,\n                \"Warning: reported maximum MTU from client (%d) is lower \"\n                \"than MTU used on the server (%d). Add tun-mtu-max %d \"\n                \"to client configuration.\",\n                client_max_mtu, o->ce.tun_mtu, o->ce.tun_mtu);\n        }\n    }\n\n    return true;\n}\n\nstatic bool\nsend_push_options(struct context *c, struct buffer *buf, struct push_list *push_list, int safe_cap,\n                  bool *push_sent, bool *multi_push)\n{\n    struct push_entry *e = push_list->head;\n\n    while (e)\n    {\n        if (e->enable)\n        {\n            const int l = strlen(e->option);\n            if (BLEN(buf) + l >= safe_cap)\n            {\n                buf_printf(buf, \",push-continuation 2\");\n                {\n                    const bool status = send_control_channel_string(c, BSTR(buf), D_PUSH);\n                    if (!status)\n                    {\n                        return false;\n                    }\n                    *push_sent = true;\n                    *multi_push = true;\n                    buf_reset_len(buf);\n                    buf_printf(buf, \"%s\", push_reply_cmd);\n                }\n            }\n            if (BLEN(buf) + l >= safe_cap)\n            {\n                msg(M_WARN, \"--push option is too long\");\n                return false;\n            }\n            buf_printf(buf, \",%s\", e->option);\n        }\n        e = e->next;\n    }\n    return true;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nvoid\nsend_push_reply_auth_token(struct tls_multi *multi)\n{\n    struct gc_arena gc = gc_new();\n    struct push_list push_list = { 0 };\n    struct tls_session *session = &multi->session[TM_ACTIVE];\n\n    prepare_auth_token_push_reply(multi, &gc, &push_list);\n\n    /* prepare auth token should always add the auth-token option */\n    struct push_entry *e = push_list.head;\n    ASSERT(e && e->enable);\n\n    /* Construct a mimimal control channel push reply message */\n    struct buffer buf = alloc_buf_gc(PUSH_BUNDLE_SIZE, &gc);\n    buf_printf(&buf, \"%s,%s\", push_reply_cmd, e->option);\n    send_control_channel_string_dowork(session, BSTR(&buf), D_PUSH);\n    gc_free(&gc);\n}\n\nbool\nsend_push_reply(struct context *c, struct push_list *per_client_push_list)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer buf = alloc_buf_gc(PUSH_BUNDLE_SIZE, &gc);\n    bool multi_push = false;\n    const int extra = 84; /* extra space for possible trailing ifconfig and push-continuation */\n    const int safe_cap = BCAP(&buf) - extra;\n    bool push_sent = false;\n\n    buf_printf(&buf, \"%s\", push_reply_cmd);\n\n    /* send options which are common to all clients */\n    if (!send_push_options(c, &buf, &c->options.push_list, safe_cap, &push_sent, &multi_push))\n    {\n        goto fail;\n    }\n\n    /* send client-specific options */\n    if (!send_push_options(c, &buf, per_client_push_list, safe_cap, &push_sent, &multi_push))\n    {\n        goto fail;\n    }\n\n    if (multi_push)\n    {\n        buf_printf(&buf, \",push-continuation 1\");\n    }\n\n    if (BLENZ(&buf) >= sizeof(push_reply_cmd))\n    {\n        const bool status = send_control_channel_string(c, BSTR(&buf), D_PUSH);\n        if (!status)\n        {\n            goto fail;\n        }\n        push_sent = true;\n    }\n\n    /* If nothing have been pushed, send an empty push,\n     * as the client is expecting a response\n     */\n    if (!push_sent)\n    {\n        bool status = false;\n\n        buf_reset_len(&buf);\n        buf_printf(&buf, \"%s\", push_reply_cmd);\n        status = send_control_channel_string(c, BSTR(&buf), D_PUSH);\n        if (!status)\n        {\n            goto fail;\n        }\n    }\n\n    gc_free(&gc);\n    return true;\n\nfail:\n    gc_free(&gc);\n    return false;\n}\n\nstatic void\npush_option_ex(struct gc_arena *gc, struct push_list *push_list, const char *opt, bool enable,\n               msglvl_t msglevel)\n{\n    if (!string_class(opt, CC_ANY, CC_COMMA))\n    {\n        msg(msglevel, \"PUSH OPTION FAILED (illegal comma (',') in string): '%s'\", opt);\n    }\n    else\n    {\n        struct push_entry *e;\n        ALLOC_OBJ_CLEAR_GC(e, struct push_entry, gc);\n        e->enable = true;\n        e->option = opt;\n        if (push_list->head)\n        {\n            ASSERT(push_list->tail);\n            push_list->tail->next = e;\n            push_list->tail = e;\n        }\n        else\n        {\n            ASSERT(!push_list->tail);\n            push_list->head = e;\n            push_list->tail = e;\n        }\n    }\n}\n\nvoid\npush_option(struct options *o, const char *opt, msglvl_t msglevel)\n{\n    push_option_ex(&o->gc, &o->push_list, opt, true, msglevel);\n}\n\nvoid\nclone_push_list(struct options *o)\n{\n    if (o->push_list.head)\n    {\n        const struct push_entry *e = o->push_list.head;\n        push_reset(o);\n        while (e)\n        {\n            push_option_ex(&o->gc, &o->push_list, string_alloc(e->option, &o->gc), true, M_FATAL);\n            e = e->next;\n        }\n    }\n}\n\nvoid\npush_options(struct options *o, char **p, msglvl_t msglevel, struct gc_arena *gc)\n{\n    const char **argv = make_extended_arg_array(p, false, gc);\n    char *opt = print_argv(argv, gc, 0);\n    push_option(o, opt, msglevel);\n}\n\nstatic bool\npush_option_fmt(struct gc_arena *gc, struct push_list *push_list,\n                msglvl_t msglevel, const char *format, ...)\n{\n    va_list arglist;\n    char tmp[256] = { 0 };\n    int len;\n    va_start(arglist, format);\n    len = vsnprintf(tmp, sizeof(tmp), format, arglist);\n    va_end(arglist);\n    if (len < 0 || len >= (int)sizeof(tmp))\n    {\n        return false;\n    }\n    push_option_ex(gc, push_list, string_alloc(tmp, gc), true, msglevel);\n    return true;\n}\n\nvoid\npush_reset(struct options *o)\n{\n    CLEAR(o->push_list);\n}\n\nvoid\npush_remove_option(struct options *o, const char *p)\n{\n    msg(D_PUSH_DEBUG, \"PUSH_REMOVE searching for: '%s'\", p);\n\n    /* ifconfig is special, as not part of the push list */\n    if (streq(p, \"ifconfig\"))\n    {\n        o->push_ifconfig_ipv4_blocked = true;\n        return;\n    }\n\n    /* ifconfig-ipv6 is special, as not part of the push list */\n    if (streq(p, \"ifconfig-ipv6\"))\n    {\n        o->push_ifconfig_ipv6_blocked = true;\n        return;\n    }\n\n    if (o && o->push_list.head)\n    {\n        struct push_entry *e = o->push_list.head;\n\n        /* cycle through the push list */\n        while (e)\n        {\n            if (e->enable && strncmp(e->option, p, strlen(p)) == 0)\n            {\n                msg(D_PUSH_DEBUG, \"PUSH_REMOVE removing: '%s'\", e->option);\n                e->enable = false;\n            }\n\n            e = e->next;\n        }\n    }\n}\n\nint\nprocess_incoming_push_request(struct context *c)\n{\n    int ret = PUSH_MSG_ERROR;\n\n\n    if (tls_authentication_status(c->c2.tls_multi) == TLS_AUTHENTICATION_FAILED\n        || c->c2.tls_multi->multi_state == CAS_FAILED)\n    {\n        const char *client_reason = tls_client_reason(c->c2.tls_multi);\n        send_auth_failed(c, client_reason);\n        ret = PUSH_MSG_AUTH_FAILURE;\n    }\n    else if (tls_authentication_status(c->c2.tls_multi) == TLS_AUTHENTICATION_SUCCEEDED\n             && c->c2.tls_multi->multi_state >= CAS_CONNECT_DONE)\n    {\n        time_t now;\n\n        openvpn_time(&now);\n        if (c->c2.sent_push_reply_expiry > now)\n        {\n            ret = PUSH_MSG_ALREADY_REPLIED;\n        }\n        else\n        {\n            /* per-client push options - peer-id, cipher, ifconfig, ipv6-ifconfig */\n            struct push_list push_list = { 0 };\n            struct gc_arena gc = gc_new();\n\n            if (prepare_push_reply(c, &gc, &push_list) && send_push_reply(c, &push_list))\n            {\n                ret = PUSH_MSG_REQUEST;\n                c->c2.sent_push_reply_expiry = now + 30;\n            }\n            gc_free(&gc);\n        }\n    }\n    else\n    {\n        ret = PUSH_MSG_REQUEST_DEFERRED;\n    }\n\n    return ret;\n}\n\nstatic void\npush_update_digest(md_ctx_t *ctx, struct buffer *buf, const struct options *opt)\n{\n    char line[OPTION_PARM_SIZE];\n    while (buf_parse(buf, ',', line, sizeof(line)))\n    {\n        /* peer-id and auth-token might change on restart and this should not\n         * trigger reopening tun\n         * Also other options that only affect the control channel should\n         * not trigger a reopen of the tun device\n         */\n        if (strprefix(line, \"peer-id \")\n            || strprefix(line, \"auth-token \")\n            || strprefix(line, \"auth-token-user\")\n            || strprefix(line, \"protocol-flags \")\n            || strprefix(line, \"key-derivation \")\n            || strprefix(line, \"explicit-exit-notify \")\n            || strprefix(line, \"ping \")\n            || strprefix(line, \"ping-restart \")\n            || strprefix(line, \"ping-timer \"))\n        {\n            continue;\n        }\n        /* tun reopen only needed if cipher change can change tun MTU */\n        if (strprefix(line, \"cipher \") && opt->ce.tun_mtu_defined)\n        {\n            continue;\n        }\n        md_ctx_update(ctx, (const uint8_t *)line, strlen(line) + 1);\n    }\n}\n\nstatic int\nprocess_incoming_push_reply(struct context *c, unsigned int permission_mask,\n                            unsigned int *option_types_found, struct buffer *buf)\n{\n    int ret = PUSH_MSG_ERROR;\n    const int ch = buf_read_u8(buf);\n    if (ch == ',')\n    {\n        struct buffer buf_orig = (*buf);\n        if (!c->c2.pulled_options_digest_init_done)\n        {\n            c->c2.pulled_options_state = md_ctx_new();\n            md_ctx_init(c->c2.pulled_options_state, \"SHA256\");\n            c->c2.pulled_options_digest_init_done = true;\n        }\n        if (apply_push_options(c, &c->options, buf, permission_mask, option_types_found, c->c2.es,\n                               false))\n        {\n            push_update_digest(c->c2.pulled_options_state, &buf_orig, &c->options);\n            switch (c->options.push_continuation)\n            {\n                case 0:\n                case 1:\n                    md_ctx_final(c->c2.pulled_options_state, c->c2.pulled_options_digest.digest);\n                    md_ctx_cleanup(c->c2.pulled_options_state);\n                    md_ctx_free(c->c2.pulled_options_state);\n                    c->c2.pulled_options_state = NULL;\n                    c->c2.pulled_options_digest_init_done = false;\n                    ret = PUSH_MSG_REPLY;\n                    break;\n\n                case 2:\n                    ret = PUSH_MSG_CONTINUATION;\n                    break;\n            }\n        }\n        else\n        {\n            throw_signal_soft(SIGUSR1, \"Offending option received from server\");\n        }\n    }\n    else if (ch == '\\0')\n    {\n        ret = PUSH_MSG_REPLY;\n    }\n    /* show_settings (&c->options); */\n    return ret;\n}\n\nint\nprocess_incoming_push_msg(struct context *c, const struct buffer *buffer,\n                          bool honor_received_options, unsigned int permission_mask,\n                          unsigned int *option_types_found)\n{\n    struct buffer buf = *buffer;\n\n    if (buf_string_compare_advance(&buf, \"PUSH_REQUEST\"))\n    {\n        c->c2.push_request_received = true;\n        return process_incoming_push_request(c);\n    }\n    else if (honor_received_options && buf_string_compare_advance(&buf, push_reply_cmd))\n    {\n        return process_incoming_push_reply(c, permission_mask, option_types_found, &buf);\n    }\n    else if (honor_received_options && buf_string_compare_advance(&buf, push_update_cmd))\n    {\n        if (dco_enabled(&c->options))\n        {\n            msg(M_WARN, \"WARN: PUSH_UPDATE messages cannot currently be processed in client mode while DCO is enabled, ignoring.\"\n                        \" To be able to process PUSH_UPDATE messages, be sure to use the --disable-dco option.\");\n            return PUSH_MSG_ERROR;\n        }\n        return process_push_update(c, &c->options, permission_mask, option_types_found, &buf, false);\n    }\n    else\n    {\n        return PUSH_MSG_ERROR;\n    }\n}\n\n\n/*\n * Remove iroutes from the push_list.\n */\nvoid\nremove_iroutes_from_push_route_list(struct options *o)\n{\n    if (o && o->push_list.head && (o->iroutes || o->iroutes_ipv6))\n    {\n        struct gc_arena gc = gc_new();\n        struct push_entry *e = o->push_list.head;\n\n        /* cycle through the push list */\n        while (e)\n        {\n            char *p[MAX_PARMS + 1];\n            bool enable = true;\n\n            /* parse the push item */\n            CLEAR(p);\n            if (e->enable\n                && parse_line(e->option, p, SIZE(p) - 1, \"[PUSH_ROUTE_REMOVE]\", 1, D_ROUTE_DEBUG,\n                              &gc))\n            {\n                /* is the push item a route directive? */\n                if (p[0] && !strcmp(p[0], \"route\") && !p[3] && o->iroutes)\n                {\n                    /* get route parameters */\n                    bool status1, status2;\n                    const in_addr_t network = getaddr(GETADDR_HOST_ORDER, p[1], 0, &status1, NULL);\n                    const in_addr_t netmask = getaddr(\n                        GETADDR_HOST_ORDER, p[2] ? p[2] : \"255.255.255.255\", 0, &status2, NULL);\n\n                    /* did route parameters parse correctly? */\n                    if (status1 && status2)\n                    {\n                        const struct iroute *ir;\n\n                        /* does route match an iroute? */\n                        for (ir = o->iroutes; ir != NULL; ir = ir->next)\n                        {\n                            if (network == ir->network\n                                && netmask\n                                       == netbits_to_netmask(ir->netbits >= 0 ? ir->netbits : 32))\n                            {\n                                enable = false;\n                                break;\n                            }\n                        }\n                    }\n                }\n                else if (p[0] && !strcmp(p[0], \"route-ipv6\") && !p[2] && o->iroutes_ipv6)\n                {\n                    /* get route parameters */\n                    struct in6_addr network;\n                    unsigned int netbits;\n\n                    /* parse route-ipv6 arguments */\n                    if (get_ipv6_addr(p[1], &network, &netbits, D_ROUTE_DEBUG))\n                    {\n                        struct iroute_ipv6 *ir;\n\n                        /* does this route-ipv6 match an iroute-ipv6? */\n                        for (ir = o->iroutes_ipv6; ir != NULL; ir = ir->next)\n                        {\n                            if (!memcmp(&network, &ir->network, sizeof(network))\n                                && netbits == ir->netbits)\n                            {\n                                enable = false;\n                                break;\n                            }\n                        }\n                    }\n                }\n\n                /* should we copy the push item? */\n                e->enable = enable;\n                if (!enable)\n                {\n                    msg(D_PUSH, \"REMOVE PUSH ROUTE: '%s'\", e->option);\n                }\n            }\n\n            e = e->next;\n        }\n\n        gc_free(&gc);\n    }\n}\n"
  },
  {
    "path": "src/openvpn/push.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef PUSH_H\n#define PUSH_H\n\n#include \"forward.h\"\n\n#define PUSH_MSG_ERROR            0\n#define PUSH_MSG_REQUEST          1\n#define PUSH_MSG_REPLY            2\n#define PUSH_MSG_REQUEST_DEFERRED 3\n#define PUSH_MSG_AUTH_FAILURE     4\n#define PUSH_MSG_CONTINUATION     5\n#define PUSH_MSG_ALREADY_REPLIED  6\n#define PUSH_MSG_UPDATE           7\n\n#define push_reply_cmd  \"PUSH_REPLY\"\n#define push_update_cmd \"PUSH_UPDATE\"\n\n/* Push-update options flags */\n#define PUSH_OPT_TO_REMOVE (1 << 0)\n#define PUSH_OPT_OPTIONAL  (1 << 1)\n\n#ifdef ENABLE_MANAGEMENT\n/* Push-update message sender modes */\ntypedef enum\n{\n    UPT_BROADCAST = 0,\n    UPT_BY_CID = 1\n} push_update_type;\n#endif\n\nint process_incoming_push_request(struct context *c);\n\n/**\n * @brief Handles the receiving of a push-update message and applies updates to the specified\n * options.\n *\n * This function processes a push-update message, validating its content and applying updates\n * to the options specified in the message. It also handles split messages if the complete\n * message has not yet been received.\n *\n * @param c The context for the operation.\n * @param o The options structure to be updated with the received push options.\n * @param permission_mask The permission mask specifying which options are allowed to be pulled.\n * @param option_types_found A pointer to a variable that will be filled with the types of options\n *                           found in the message.\n * @param buf A buffer containing the received message.\n * @param msg_sender A boolean indicating if the message is being processed on the client (false)\n *                   or on the server (true).\n *\n * @return\n * - `PUSH_MSG_UPDATE`: The message was processed successfully, and the updates were applied.\n * - `PUSH_MSG_CONTINUATION`: The message is a fragment of a larger message, and the program is\n *                            waiting for the final part.\n * - `PUSH_MSG_ERROR`: An error occurred during message processing, or the message is invalid.\n */\n\nint process_push_update(struct context *c, struct options *o, unsigned int permission_mask,\n                        unsigned int *option_types_found, struct buffer *buf, bool msg_sender);\n\nint process_incoming_push_msg(struct context *c, const struct buffer *buffer,\n                              bool honor_received_options, unsigned int permission_mask,\n                              unsigned int *option_types_found);\n\nbool send_push_request(struct context *c);\n\nvoid receive_auth_failed(struct context *c, const struct buffer *buffer);\n\nvoid server_pushed_signal(struct context *c, const struct buffer *buffer, const bool restart,\n                          const int adv);\n\nvoid receive_exit_message(struct context *c);\n\nvoid server_pushed_info(const struct buffer *buffer, const int adv);\n\nvoid receive_cr_response(struct context *c, const struct buffer *buffer);\n\nvoid incoming_push_message(struct context *c, const struct buffer *buffer);\n\nvoid clone_push_list(struct options *o);\n\nvoid push_option(struct options *o, const char *opt, msglvl_t msglevel);\n\nvoid push_options(struct options *o, char **p, msglvl_t msglevel, struct gc_arena *gc);\n\nvoid push_reset(struct options *o);\n\nvoid push_remove_option(struct options *o, const char *p);\n\nvoid remove_iroutes_from_push_route_list(struct options *o);\n\nvoid send_auth_failed(struct context *c, const char *client_reason);\n\n/**\n * Sends the auth pending control messages to a client. See\n * doc/management-notes.txt under client-pending-auth for\n * more details on message format\n */\nbool send_auth_pending_messages(struct tls_multi *tls_multi, struct tls_session *session,\n                                const char *extra, unsigned int timeout);\n\nvoid send_restart(struct context *c, const char *kill_msg);\n\n/**\n * Sends a push reply message only containin the auth-token to update\n * the auth-token on the client. Always pushes to the active session\n *\n * @param multi    - The \\c tls_multi structure belonging to the instance\n *                   to push to\n */\nvoid send_push_reply_auth_token(struct tls_multi *multi);\n\n/**\n * Parses an AUTH_PENDING message and if in pull mode extends the timeout\n *\n * @param c             The context struct\n * @param buffer        Buffer containing the control message with AUTH_PENDING\n */\nvoid receive_auth_pending(struct context *c, const struct buffer *buffer);\n\n#ifdef ENABLE_MANAGEMENT\nbool management_callback_send_push_update_broadcast(void *arg, const char *options);\n\nbool management_callback_send_push_update_by_cid(void *arg, unsigned long cid, const char *options);\n\n#endif /* ifdef ENABLE_MANAGEMENT*/\n\n#endif /* ifndef PUSH_H */\n"
  },
  {
    "path": "src/openvpn/push_util.c",
    "content": "#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"push.h\"\n#include \"buffer.h\"\n\n#ifdef ENABLE_MANAGEMENT\n#include \"multi.h\"\n#include \"ssl_util.h\"\n#endif\n\nint\nprocess_push_update(struct context *c, struct options *o, unsigned int permission_mask,\n                    unsigned int *option_types_found, struct buffer *buf, bool msg_sender)\n{\n    int ret = PUSH_MSG_ERROR;\n    const int ch = buf_read_u8(buf);\n    if (ch == ',')\n    {\n        if (apply_push_options(c, o, buf, permission_mask, option_types_found, c->c2.es,\n                               true))\n        {\n            switch (o->push_continuation)\n            {\n                case 0:\n                case 1:\n                    ret = PUSH_MSG_UPDATE;\n                    break;\n\n                case 2:\n                    ret = PUSH_MSG_CONTINUATION;\n                    break;\n            }\n        }\n        else if (!msg_sender)\n        {\n            throw_signal_soft(SIGUSR1, \"Offending option received from server\");\n        }\n    }\n    else if (ch == '\\0')\n    {\n        ret = PUSH_MSG_UPDATE;\n    }\n\n    return ret;\n}\n\n#ifdef ENABLE_MANAGEMENT\n/**\n * Return index of last `,` or `0` if it didn't find any.\n * If there is a comma at index `0` it's an error anyway\n */\nstatic size_t\nfind_first_comma_of_next_bundle(const char *str, size_t ix)\n{\n    while (ix > 0)\n    {\n        if (str[ix] == ',')\n        {\n            return ix;\n        }\n        ix--;\n    }\n    return 0;\n}\n\n/* Allocate memory and assemble the final message */\nstatic struct buffer\nforge_msg(const char *src, const char *continuation, struct gc_arena *gc)\n{\n    size_t src_len = strlen(src);\n    size_t con_len = continuation ? strlen(continuation) : 0;\n    struct buffer buf = alloc_buf_gc(src_len + sizeof(push_update_cmd) + con_len + 2, gc);\n\n    buf_printf(&buf, \"%s,%s%s\", push_update_cmd, src, continuation ? continuation : \"\");\n\n    return buf;\n}\n\nstatic char *\ngc_strdup(const char *src, struct gc_arena *gc)\n{\n    char *ret = gc_malloc((strlen(src) + 1) * sizeof(char), true, gc);\n\n    strcpy(ret, src);\n    return ret;\n}\n\n/* It split the messagge (if necessary) and fill msgs with the message chunks.\n * Return `false` on failure an `true` on success.\n */\nstatic bool\nmessage_splitter(const char *s, struct buffer_list *msgs, struct gc_arena *gc, const size_t safe_cap)\n{\n    if (!s || !*s)\n    {\n        return false;\n    }\n\n    char *str = gc_strdup(s, gc);\n    size_t i = 0;\n\n    while (*str)\n    {\n        /* + ',' - '/0' */\n        if (strlen(str) > safe_cap)\n        {\n            size_t ci = find_first_comma_of_next_bundle(str, safe_cap);\n            if (!ci)\n            {\n                /* if no commas were found go to fail, do not send any message */\n                return false;\n            }\n            str[ci] = '\\0';\n            /* copy from i to (ci -1) */\n            struct buffer tmp = forge_msg(str, \",push-continuation 2\", gc);\n            buffer_list_push(msgs, BSTR(&tmp));\n            i = ci + 1;\n        }\n        else\n        {\n            if (msgs->head)\n            {\n                struct buffer tmp = forge_msg(str, \",push-continuation 1\", gc);\n                buffer_list_push(msgs, BSTR(&tmp));\n            }\n            else\n            {\n                struct buffer tmp = forge_msg(str, NULL, gc);\n                buffer_list_push(msgs, BSTR(&tmp));\n            }\n            i = strlen(str);\n        }\n        str = &str[i];\n    }\n    return true;\n}\n\n/* send the message(s) prepared to one single client */\nstatic bool\nsend_single_push_update(struct multi_context *m, struct multi_instance *mi, struct buffer_list *msgs)\n{\n    if (!msgs->head)\n    {\n        return false;\n    }\n\n    unsigned int option_types_found = 0;\n    struct context *c = &mi->context;\n    struct options o;\n    CLEAR(o);\n\n    /* Set canary values to detect ifconfig options in push-update messages.\n     * These placeholder strings will be overwritten to NULL by the option\n     * parser if -ifconfig or -ifconfig-ipv6 options are present in the\n     * push-update.\n     */\n    const char *canary = \"canary\";\n    o.ifconfig_local = canary;\n    o.ifconfig_ipv6_local = canary;\n\n    struct buffer_entry *e = msgs->head;\n    while (e)\n    {\n        if (!send_control_channel_string(c, BSTR(&e->buf), D_PUSH))\n        {\n            return false;\n        }\n\n        /* After sending the control message, we parse it, miming the behavior\n         * of `process_incoming_push_msg()` and we fill an empty `options` struct\n         * with the new options. If an `ifconfig_local` or `ifconfig_ipv6_local`\n         * options is found we update the vhash accordingly, so that the pushed\n         * ifconfig/ifconfig-ipv6 options can actually work.\n         * If we don't do that, packets arriving from the client with the\n         * new address will be rejected and packets for the new address\n         * will not be routed towards the client.\n         * Using `buf_string_compare_advance()` we mimic the behavior\n         * inside `process_incoming_push_msg()`. However, we don't need\n         * to check the return value here because we just want to `advance`,\n         * meaning we skip the `push_update_cmd' we added earlier.\n         * Also we need to make a temporary copy so we can buf_advance()\n         * without modifying original buffer.\n         */\n        struct buffer tmp_msg = e->buf;\n        buf_string_compare_advance(&tmp_msg, push_update_cmd);\n        unsigned int permission_mask = pull_permission_mask(c);\n        if (process_push_update(c, &o, permission_mask, &option_types_found, &tmp_msg, true) == PUSH_MSG_ERROR)\n        {\n            msg(M_WARN, \"Failed to process push update message sent to client ID: %u\", c->c2.tls_multi->peer_id);\n        }\n        e = e->next;\n    }\n\n    if (option_types_found & OPT_P_UP)\n    {\n        /* -ifconfig */\n        if (!o.ifconfig_local && mi->context.c2.push_ifconfig_defined)\n        {\n            unlearn_ifconfig(m, mi);\n        }\n        /* -ifconfig-ipv6 */\n        if (!o.ifconfig_ipv6_local && mi->context.c2.push_ifconfig_ipv6_defined)\n        {\n            unlearn_ifconfig_ipv6(m, mi);\n        }\n\n        if (o.ifconfig_local && !strcmp(o.ifconfig_local, canary))\n        {\n            o.ifconfig_local = NULL;\n        }\n        if (o.ifconfig_ipv6_local && !strcmp(o.ifconfig_ipv6_local, canary))\n        {\n            o.ifconfig_ipv6_local = NULL;\n        }\n\n        /* new ifconfig or new ifconfig-ipv6 */\n        update_vhash(m, mi, o.ifconfig_local, o.ifconfig_ipv6_local);\n    }\n\n    return true;\n}\n\n/* Return true if the client supports push-update */\nstatic bool\nsupport_push_update(struct multi_instance *mi)\n{\n    ASSERT(mi->context.c2.tls_multi);\n    const unsigned int iv_proto_peer = extract_iv_proto(mi->context.c2.tls_multi->peer_info);\n    if (!(iv_proto_peer & IV_PROTO_PUSH_UPDATE))\n    {\n        return false;\n    }\n\n    return true;\n}\n\n/**\n * @brief A function to send a PUSH_UPDATE control message from server to client(s).\n *\n * @param m the multi_context, contains all the clients connected to this server.\n * @param target the target to which to send the message. It should be:\n * `NULL` if `type == UPT_BROADCAST`,\n * a `mroute_addr *` if `type == UPT_BY_ADDR`,\n * a `char *` if `type == UPT_BY_CN`,\n * an `unsigned long *` if `type == UPT_BY_CID`.\n * @param msg a string containing the options to send.\n * @param type the way to address the message (broadcast, by cid, by cn, by address).\n * @param push_bundle_size the maximum size of a bundle of pushed option. Just use PUSH_BUNDLE_SIZE macro.\n * @return The number of clients to which the message was sent. Might return < 0 in case of error.\n */\nstatic int\nsend_push_update(struct multi_context *m, const void *target, const char *msg, const push_update_type type, const size_t push_bundle_size)\n{\n    if (dco_enabled(&m->top.options))\n    {\n        msg(M_WARN, \"WARN: PUSH_UPDATE messages cannot currently be sent while DCO is enabled.\"\n                    \" To send a PUSH_UPDATE message, be sure to use the --disable-dco option.\");\n        return 0;\n    }\n\n    if (!msg || !*msg || !m || (!target && type != UPT_BROADCAST))\n    {\n        return -EINVAL;\n    }\n\n    struct gc_arena gc = gc_new();\n    /* extra space for possible trailing ifconfig and push-continuation */\n    const size_t extra = 84 + sizeof(push_update_cmd);\n    /* push_bundle_size is the maximum size of a message, so if the message\n     * we want to send exceeds that size we have to split it into smaller messages */\n    ASSERT(push_bundle_size > extra);\n    const size_t safe_cap = push_bundle_size - extra;\n    struct buffer_list *msgs = buffer_list_new();\n\n    if (!message_splitter(msg, msgs, &gc, safe_cap))\n    {\n        buffer_list_free(msgs);\n        gc_free(&gc);\n        return -EINVAL;\n    }\n\n    if (type == UPT_BY_CID)\n    {\n        struct multi_instance *mi = lookup_by_cid(m, *((unsigned long *)target));\n\n        if (!mi)\n        {\n            buffer_list_free(msgs);\n            gc_free(&gc);\n            return -ENOENT;\n        }\n\n        if (!support_push_update(mi))\n        {\n            msg(M_CLIENT, \"PUSH_UPDATE: not sending message to unsupported peer with ID: %u\", mi->context.c2.tls_multi->peer_id);\n            buffer_list_free(msgs);\n            gc_free(&gc);\n            return 0;\n        }\n\n        if (!mi->halt\n            && send_single_push_update(m, mi, msgs))\n        {\n            buffer_list_free(msgs);\n            gc_free(&gc);\n            return 1;\n        }\n        else\n        {\n            buffer_list_free(msgs);\n            gc_free(&gc);\n            return 0;\n        }\n    }\n\n    int count = 0;\n    struct hash_iterator hi;\n    const struct hash_element *he;\n\n    hash_iterator_init(m->iter, &hi);\n    while ((he = hash_iterator_next(&hi)))\n    {\n        struct multi_instance *curr_mi = he->value;\n\n        if (curr_mi->halt || !support_push_update(curr_mi))\n        {\n            continue;\n        }\n\n        /* Type is UPT_BROADCAST so we update every client */\n        if (!send_single_push_update(m, curr_mi, msgs))\n        {\n            msg(M_CLIENT, \"ERROR: Peer ID: %u has not been updated\", curr_mi->context.c2.tls_multi->peer_id);\n            continue;\n        }\n        count++;\n    }\n\n    hash_iterator_free(&hi);\n    buffer_list_free(msgs);\n    gc_free(&gc);\n    return count;\n}\n\n#define RETURN_UPDATE_STATUS(n_sent)                                  \\\n    do                                                                \\\n    {                                                                 \\\n        if ((n_sent) > 0)                                             \\\n        {                                                             \\\n            msg(M_CLIENT, \"SUCCESS: %d client(s) updated\", (n_sent)); \\\n            return true;                                              \\\n        }                                                             \\\n        else                                                          \\\n        {                                                             \\\n            msg(M_CLIENT, \"ERROR: no client updated\");                \\\n            return false;                                             \\\n        }                                                             \\\n    } while (0)\n\nbool\nmanagement_callback_send_push_update_broadcast(void *arg, const char *options)\n{\n    int n_sent = send_push_update(arg, NULL, options, UPT_BROADCAST, PUSH_BUNDLE_SIZE);\n\n    RETURN_UPDATE_STATUS(n_sent);\n}\n\nbool\nmanagement_callback_send_push_update_by_cid(void *arg, unsigned long cid, const char *options)\n{\n    int n_sent = send_push_update(arg, &cid, options, UPT_BY_CID, PUSH_BUNDLE_SIZE);\n\n    RETURN_UPDATE_STATUS(n_sent);\n}\n#endif /* ifdef ENABLE_MANAGEMENT */\n"
  },
  {
    "path": "src/openvpn/pushlist.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#if !defined(PUSHLIST_H)\n#define PUSHLIST_H\n\n/* parameters to be pushed to peer */\n\nstruct push_entry\n{\n    struct push_entry *next;\n    bool enable;\n    const char *option;\n};\n\nstruct push_list\n{\n    struct push_entry *head;\n    struct push_entry *tail;\n};\n\n#endif /* if !defined(PUSHLIST_H) */\n"
  },
  {
    "path": "src/openvpn/reflect_filter.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2022-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n\n#include <stdint.h>\n#include <stddef.h>\n#include <stdlib.h>\n#include <string.h>\n#include <stdbool.h>\n#include <memory.h>\n\n#include \"crypto.h\"\n#include \"reflect_filter.h\"\n\n\nbool\nreflect_filter_rate_limit_check(struct initial_packet_rate_limit *irl)\n{\n    if (now > irl->last_period_reset + irl->period_length)\n    {\n        int64_t dropped = irl->curr_period_counter - irl->max_per_period;\n        if (dropped > 0)\n        {\n            msg(D_TLS_DEBUG_LOW,\n                \"Dropped %\" PRId64 \" initial handshake packets\"\n                \" due to --connect-freq-initial %\" PRId64 \" %d\",\n                dropped, irl->max_per_period, irl->period_length);\n        }\n        irl->last_period_reset = now;\n        irl->curr_period_counter = 0;\n        irl->warning_displayed = false;\n    }\n\n    irl->curr_period_counter++;\n\n    bool over_limit = irl->curr_period_counter > irl->max_per_period;\n\n    if (over_limit && !irl->warning_displayed)\n    {\n        msg(M_WARN,\n            \"Note: --connect-freq-initial %\" PRId64 \" %d rate limit \"\n            \"exceeded, dropping initial handshake packets for the next %d \"\n            \"seconds\",\n            irl->max_per_period, irl->period_length,\n            (int)(irl->last_period_reset + irl->period_length - now));\n        irl->warning_displayed = true;\n    }\n    return !over_limit;\n}\n\nvoid\nreflect_filter_rate_limit_decrease(struct initial_packet_rate_limit *irl)\n{\n    if (irl->curr_period_counter > 0)\n    {\n        irl->curr_period_counter--;\n    }\n}\n\n\nstruct initial_packet_rate_limit *\ninitial_rate_limit_init(int max_per_period, int period_length)\n{\n    struct initial_packet_rate_limit *irl;\n\n\n    ALLOC_OBJ(irl, struct initial_packet_rate_limit);\n\n    irl->max_per_period = max_per_period;\n    irl->period_length = period_length;\n    irl->curr_period_counter = 0;\n    irl->last_period_reset = 0;\n\n    return irl;\n}\n\nvoid\ninitial_rate_limit_free(struct initial_packet_rate_limit *irl)\n{\n    free(irl);\n}\n"
  },
  {
    "path": "src/openvpn/reflect_filter.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2022-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n#ifndef REFLECT_FILTER_H\n#define REFLECT_FILTER_H\n\n#include <limits.h>\n\n/** struct that handles all the rate limiting logic for initial\n * responses */\nstruct initial_packet_rate_limit\n{\n    /** This is a hard limit for packets per seconds. */\n    int64_t max_per_period;\n\n    /** period length in seconds */\n    int period_length;\n\n    /** Number of packets in the current period. We use int64_t here\n     * to avoid any potiential issues with overflow */\n    int64_t curr_period_counter;\n\n    /* Last time we reset our timer */\n    time_t last_period_reset;\n\n    /* we want to warn once per period that packets are being started to\n     * be dropped */\n    bool warning_displayed;\n};\n\n\n/**\n * checks if the connection is still allowed to connect under the rate\n * limit. This also increases the internal counter at the same time\n */\nbool reflect_filter_rate_limit_check(struct initial_packet_rate_limit *irl);\n\n/**\n * decreases the counter of initial packets seen, so connections that\n * successfully completed the three-way handshake do not count against\n * the counter of initial connection attempts\n */\nvoid reflect_filter_rate_limit_decrease(struct initial_packet_rate_limit *irl);\n\n/**\n * allocate and initialize the initial-packet rate limiter structure\n */\nstruct initial_packet_rate_limit *initial_rate_limit_init(int max_per_period, int period_length);\n\n/**\n * free the initial-packet rate limiter structure\n */\nvoid initial_rate_limit_free(struct initial_packet_rate_limit *irl);\n#endif /* ifndef REFLECT_FILTER_H */\n"
  },
  {
    "path": "src/openvpn/reliable.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * These routines implement a reliability layer on top of UDP,\n * so that SSL/TLS can be run over UDP.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"common.h\"\n#include \"reliable.h\"\n\n#include \"memdbg.h\"\n\n/* calculates test - base while allowing for base or test wraparound. test is\n * assumed to be higher than base */\nstatic inline packet_id_type\nsubtract_pid(const packet_id_type test, const packet_id_type base)\n{\n    return test - base;\n}\n\n/*\n * verify that test - base < extent while allowing for base or test wraparound\n */\nstatic inline bool\nreliable_pid_in_range1(const packet_id_type test, const packet_id_type base,\n                       const unsigned int extent)\n{\n    return subtract_pid(test, base) < extent;\n}\n\n/*\n * verify that test < base + extent while allowing for base or test wraparound\n */\nstatic inline bool\nreliable_pid_in_range2(const packet_id_type test, const packet_id_type base,\n                       const unsigned int extent)\n{\n    if (base + extent >= base)\n    {\n        if (test < base + extent)\n        {\n            return true;\n        }\n    }\n    else\n    {\n        if ((test + 0x80000000u) < (base + 0x80000000u) + extent)\n        {\n            return true;\n        }\n    }\n\n    return false;\n}\n\n/*\n * verify that p1 < p2  while allowing for p1 or p2 wraparound\n */\nstatic inline bool\nreliable_pid_min(const packet_id_type p1, const packet_id_type p2)\n{\n    return !reliable_pid_in_range1(p1, p2, 0x80000000u);\n}\n\n/* check if a particular packet_id is present in ack */\nstatic inline bool\nreliable_ack_packet_id_present(struct reliable_ack *ack, packet_id_type pid)\n{\n    for (int i = 0; i < ack->len; ++i)\n    {\n        if (ack->packet_id[i] == pid)\n        {\n            return true;\n        }\n    }\n    return false;\n}\n\n/* get a packet_id from buf */\nbool\nreliable_ack_read_packet_id(struct buffer *buf, packet_id_type *pid)\n{\n    packet_id_type net_pid;\n\n    if (buf_read(buf, &net_pid, sizeof(net_pid)))\n    {\n        *pid = ntohpid(net_pid);\n        dmsg(D_REL_DEBUG, \"ACK read ID \" packet_id_format \" (buf->len=%d)\",\n             (packet_id_print_type)*pid, buf->len);\n        return true;\n    }\n\n    dmsg(D_REL_LOW, \"ACK read ID FAILED (buf->len=%d)\", buf->len);\n    return false;\n}\n\n/* acknowledge a packet_id by adding it to a struct reliable_ack */\nbool\nreliable_ack_acknowledge_packet_id(struct reliable_ack *ack, packet_id_type pid)\n{\n    if (!reliable_ack_packet_id_present(ack, pid) && ack->len < RELIABLE_ACK_SIZE)\n    {\n        ack->packet_id[ack->len++] = pid;\n        dmsg(D_REL_DEBUG, \"ACK acknowledge ID \" packet_id_format \" (ack->len=%d)\",\n             (packet_id_print_type)pid, ack->len);\n        return true;\n    }\n\n    dmsg(D_REL_LOW, \"ACK acknowledge ID \" packet_id_format \" FAILED (ack->len=%d)\",\n         (packet_id_print_type)pid, ack->len);\n    return false;\n}\n\n\nbool\nreliable_ack_read(struct reliable_ack *ack, struct buffer *buf, const struct session_id *sid)\n{\n    struct session_id session_id_remote;\n\n    if (!reliable_ack_parse(buf, ack, &session_id_remote))\n    {\n        return false;\n    }\n\n    if (ack->len >= 1\n        && (!session_id_defined(&session_id_remote) || !session_id_equal(&session_id_remote, sid)))\n    {\n        struct gc_arena gc = gc_new();\n        dmsg(D_REL_LOW, \"ACK read BAD SESSION-ID FROM REMOTE, local=%s, remote=%s\",\n             session_id_print(sid, &gc), session_id_print(&session_id_remote, &gc));\n        gc_free(&gc);\n        return false;\n    }\n    return true;\n}\n\nbool\nreliable_ack_parse(struct buffer *buf, struct reliable_ack *ack,\n                   struct session_id *session_id_remote)\n{\n    uint8_t count;\n    ack->len = 0;\n\n    if (!buf_read(buf, &count, sizeof(count)))\n    {\n        return false;\n    }\n    for (int i = 0; i < count; ++i)\n    {\n        packet_id_type net_pid;\n        if (!buf_read(buf, &net_pid, sizeof(net_pid)))\n        {\n            return false;\n        }\n        if (ack->len >= RELIABLE_ACK_SIZE)\n        {\n            return false;\n        }\n        packet_id_type pid = ntohpid(net_pid);\n        ack->packet_id[ack->len++] = pid;\n    }\n    if (count)\n    {\n        if (!session_id_read(session_id_remote, buf))\n        {\n            return false;\n        }\n    }\n    return true;\n}\n\n/**\n * Copies the first n acks from \\c ack to \\c ack_mru\n */\nvoid\ncopy_acks_to_mru(struct reliable_ack *ack, struct reliable_ack *ack_mru, int n)\n{\n    ASSERT(ack->len >= n);\n    /* This loop is backward to ensure the same order as in ack */\n    for (int i = n - 1; i >= 0; i--)\n    {\n        packet_id_type id = ack->packet_id[i];\n\n        /* Handle special case of ack_mru empty */\n        if (ack_mru->len == 0)\n        {\n            ack_mru->len = 1;\n            ack_mru->packet_id[0] = id;\n        }\n\n        bool idfound = false;\n\n        /* Move all existing entries one to the right */\n        packet_id_type move = id;\n\n        for (int j = 0; j < ack_mru->len; j++)\n        {\n            packet_id_type tmp = ack_mru->packet_id[j];\n            ack_mru->packet_id[j] = move;\n            move = tmp;\n\n            if (move == id)\n            {\n                idfound = true;\n                break;\n            }\n        }\n\n        if (!idfound && ack_mru->len < RELIABLE_ACK_SIZE)\n        {\n            ack_mru->packet_id[ack_mru->len] = move;\n            ack_mru->len++;\n        }\n    }\n}\n\n/* write a packet ID acknowledgement record to buf, */\n/* removing all acknowledged entries from ack */\nbool\nreliable_ack_write(struct reliable_ack *ack, struct reliable_ack *ack_mru, struct buffer *buf,\n                   const struct session_id *sid, int max, bool prepend)\n{\n    int i, j, n;\n    struct buffer sub;\n\n    n = ack->len;\n    if (n > max)\n    {\n        n = max;\n    }\n\n    copy_acks_to_mru(ack, ack_mru, n);\n\n    /* Number of acks we can resend that still fit into the packet */\n    uint8_t total_acks = (uint8_t)min_int(max, ack_mru->len);\n\n    sub = buf_sub(buf, (int)ACK_SIZE(total_acks), prepend);\n    if (!BDEF(&sub))\n    {\n        goto error;\n    }\n    ASSERT(buf_write_u8(&sub, total_acks));\n\n    /* Write the actual acks to the packets. Since we copied the acks that\n     * are going out now already to the front of ack_mru we can fetch all\n     * acks from ack_mru */\n    for (i = 0; i < total_acks; ++i)\n    {\n        packet_id_type pid = ack_mru->packet_id[i];\n        packet_id_type net_pid = htonpid(pid);\n        ASSERT(buf_write(&sub, &net_pid, sizeof(net_pid)));\n        dmsg(D_REL_DEBUG, \"ACK write ID \" packet_id_format \" (ack->len=%d, n=%d)\",\n             (packet_id_print_type)pid, ack->len, n);\n    }\n    if (total_acks)\n    {\n        ASSERT(session_id_defined(sid));\n        ASSERT(session_id_write(sid, &sub));\n    }\n    if (n)\n    {\n        for (i = 0, j = n; j < ack->len;)\n        {\n            ack->packet_id[i++] = ack->packet_id[j++];\n        }\n        ack->len = i;\n    }\n\n    return true;\n\nerror:\n    return false;\n}\n\n/* print a reliable ACK record coming off the wire */\nconst char *\nreliable_ack_print(struct buffer *buf, bool verbose, struct gc_arena *gc)\n{\n    uint8_t n_ack;\n    struct buffer out = alloc_buf_gc(256, gc);\n\n    buf_printf(&out, \"[\");\n    if (!buf_read(buf, &n_ack, sizeof(n_ack)))\n    {\n        goto done;\n    }\n    for (int i = 0; i < n_ack; ++i)\n    {\n        packet_id_type pid;\n        if (!buf_read(buf, &pid, sizeof(pid)))\n        {\n            goto done;\n        }\n        pid = ntohpid(pid);\n        buf_printf(&out, \" \" packet_id_format, (packet_id_print_type)pid);\n    }\n    if (n_ack)\n    {\n        struct session_id sid_ack;\n        if (!session_id_read(&sid_ack, buf))\n        {\n            goto done;\n        }\n        if (verbose)\n        {\n            buf_printf(&out, \" sid=%s\", session_id_print(&sid_ack, gc));\n        }\n    }\n\ndone:\n    buf_printf(&out, \" ]\");\n    return BSTR(&out);\n}\n\n/*\n * struct reliable member functions.\n */\n\nvoid\nreliable_init(struct reliable *rel, int buf_size, int offset, int array_size, bool hold)\n{\n    CLEAR(*rel);\n    ASSERT(array_size > 0 && array_size <= RELIABLE_CAPACITY);\n    rel->hold = hold;\n    rel->size = array_size;\n    rel->offset = offset;\n    for (int i = 0; i < rel->size; ++i)\n    {\n        struct reliable_entry *e = &rel->array[i];\n        e->buf = alloc_buf(buf_size);\n        ASSERT(buf_init(&e->buf, offset));\n    }\n}\n\nvoid\nreliable_free(struct reliable *rel)\n{\n    if (!rel)\n    {\n        return;\n    }\n    for (int i = 0; i < rel->size; ++i)\n    {\n        struct reliable_entry *e = &rel->array[i];\n        free_buf(&e->buf);\n    }\n    free(rel);\n}\n\n/* no active buffers? */\nbool\nreliable_empty(const struct reliable *rel)\n{\n    for (int i = 0; i < rel->size; ++i)\n    {\n        const struct reliable_entry *e = &rel->array[i];\n        if (e->active)\n        {\n            return false;\n        }\n    }\n    return true;\n}\n\n/* del acknowledged items from send buf */\nvoid\nreliable_send_purge(struct reliable *rel, const struct reliable_ack *ack)\n{\n    for (int i = 0; i < ack->len; ++i)\n    {\n        packet_id_type pid = ack->packet_id[i];\n        for (int j = 0; j < rel->size; ++j)\n        {\n            struct reliable_entry *e = &rel->array[j];\n            if (e->active && e->packet_id == pid)\n            {\n                dmsg(D_REL_DEBUG,\n                     \"ACK received for pid \" packet_id_format \", deleting from send buffer\",\n                     (packet_id_print_type)pid);\n#if 0\n                /* DEBUGGING -- how close were we timing out on ACK failure and resending? */\n                {\n                    if (e->next_try)\n                    {\n                        const interval_t wake = e->next_try - now;\n                        msg(M_INFO, \"ACK \" packet_id_format \", wake=%d\", pid, wake);\n                    }\n                }\n#endif\n                e->active = false;\n            }\n            else if (e->active && e->packet_id < pid)\n            {\n                /* We have received an ACK for a packet with a higher PID. Either\n                 * we have received ACKs out of or order or the packet has been\n                 * lost. We count the number of ACKs to determine if we should\n                 * resend it early. */\n                e->n_acks++;\n            }\n        }\n    }\n}\n\n#ifdef ENABLE_DEBUG\n/* print the current sequence of active packet IDs */\nstatic const char *\nreliable_print_ids(const struct reliable *rel, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n\n    buf_printf(&out, \"[\" packet_id_format \"]\", (packet_id_print_type)rel->packet_id);\n    for (int i = 0; i < rel->size; ++i)\n    {\n        const struct reliable_entry *e = &rel->array[i];\n        if (e->active)\n        {\n            buf_printf(&out, \" \" packet_id_format, (packet_id_print_type)e->packet_id);\n        }\n    }\n    return BSTR(&out);\n}\n#endif /* ENABLE_DEBUG */\n\n/* true if at least one free buffer available */\nbool\nreliable_can_get(const struct reliable *rel)\n{\n    for (int i = 0; i < rel->size; ++i)\n    {\n        const struct reliable_entry *e = &rel->array[i];\n        if (!e->active)\n        {\n            return true;\n        }\n    }\n    struct gc_arena gc = gc_new();\n    dmsg(D_REL_LOW, \"ACK no free receive buffer available: %s\", reliable_print_ids(rel, &gc));\n    gc_free(&gc);\n    return false;\n}\n\n/* make sure that incoming packet ID isn't a replay */\nbool\nreliable_not_replay(const struct reliable *rel, packet_id_type id)\n{\n    struct gc_arena gc = gc_new();\n    if (reliable_pid_min(id, rel->packet_id))\n    {\n        goto bad;\n    }\n    for (int i = 0; i < rel->size; ++i)\n    {\n        const struct reliable_entry *e = &rel->array[i];\n        if (e->active && e->packet_id == id)\n        {\n            goto bad;\n        }\n    }\n    gc_free(&gc);\n    return true;\n\nbad:\n    dmsg(D_REL_DEBUG, \"ACK \" packet_id_format \" is a replay: %s\", (packet_id_print_type)id,\n         reliable_print_ids(rel, &gc));\n    gc_free(&gc);\n    return false;\n}\n\n/* make sure that incoming packet ID won't deadlock the receive buffer */\nbool\nreliable_wont_break_sequentiality(const struct reliable *rel, packet_id_type id)\n{\n    const int ret = reliable_pid_in_range2(id, rel->packet_id, rel->size);\n\n    if (!ret)\n    {\n        struct gc_arena gc = gc_new();\n        dmsg(D_REL_LOW, \"ACK \" packet_id_format \" breaks sequentiality: %s\",\n             (packet_id_print_type)id, reliable_print_ids(rel, &gc));\n        gc_free(&gc);\n    }\n\n    dmsg(D_REL_DEBUG, \"ACK RWBS rel->size=%d rel->packet_id=%08x id=%08x ret=%d\", rel->size,\n         rel->packet_id, id, ret);\n    return ret;\n}\n\n/* grab a free buffer */\nstruct buffer *\nreliable_get_buf(struct reliable *rel)\n{\n    for (int i = 0; i < rel->size; ++i)\n    {\n        struct reliable_entry *e = &rel->array[i];\n        if (!e->active)\n        {\n            ASSERT(buf_init(&e->buf, rel->offset));\n            return &e->buf;\n        }\n    }\n    return NULL;\n}\n\nint\nreliable_get_num_output_sequenced_available(struct reliable *rel)\n{\n    packet_id_type min_id = 0;\n    bool min_id_defined = false;\n\n    /* find minimum active packet_id */\n    for (int i = 0; i < rel->size; ++i)\n    {\n        const struct reliable_entry *e = &rel->array[i];\n        if (e->active)\n        {\n            if (!min_id_defined || reliable_pid_min(e->packet_id, min_id))\n            {\n                min_id_defined = true;\n                min_id = e->packet_id;\n            }\n        }\n    }\n\n    int ret = rel->size;\n    if (min_id_defined)\n    {\n        ret -= subtract_pid(rel->packet_id, min_id);\n    }\n    return ret;\n}\n\n/* grab a free buffer, fail if buffer clogged by unacknowledged low packet IDs */\nstruct buffer *\nreliable_get_buf_output_sequenced(struct reliable *rel)\n{\n    packet_id_type min_id = 0;\n    bool min_id_defined = false;\n    struct buffer *ret = NULL;\n\n    /* find minimum active packet_id */\n    for (int i = 0; i < rel->size; ++i)\n    {\n        const struct reliable_entry *e = &rel->array[i];\n        if (e->active)\n        {\n            if (!min_id_defined || reliable_pid_min(e->packet_id, min_id))\n            {\n                min_id_defined = true;\n                min_id = e->packet_id;\n            }\n        }\n    }\n\n    if (!min_id_defined || reliable_pid_in_range1(rel->packet_id, min_id, rel->size))\n    {\n        ret = reliable_get_buf(rel);\n    }\n    else\n    {\n        struct gc_arena gc = gc_new();\n        dmsg(D_REL_LOW, \"ACK output sequence broken: %s\", reliable_print_ids(rel, &gc));\n        gc_free(&gc);\n    }\n    return ret;\n}\n\n/* get active buffer for next sequentially increasing key ID */\nstruct reliable_entry *\nreliable_get_entry_sequenced(struct reliable *rel)\n{\n    for (int i = 0; i < rel->size; ++i)\n    {\n        struct reliable_entry *e = &rel->array[i];\n        if (e->active && e->packet_id == rel->packet_id)\n        {\n            return e;\n        }\n    }\n    return NULL;\n}\n\n/* return true if reliable_send would return a non-NULL result */\nbool\nreliable_can_send(const struct reliable *rel)\n{\n    struct gc_arena gc = gc_new();\n    int n_active = 0, n_current = 0;\n    for (int i = 0; i < rel->size; ++i)\n    {\n        const struct reliable_entry *e = &rel->array[i];\n        if (e->active)\n        {\n            ++n_active;\n            if (now >= e->next_try || e->n_acks >= N_ACK_RETRANSMIT)\n            {\n                ++n_current;\n            }\n        }\n    }\n    (void)n_active; /* dmsg might not generate code */\n    dmsg(D_REL_DEBUG, \"ACK reliable_can_send active=%d current=%d : %s\", n_active, n_current,\n         reliable_print_ids(rel, &gc));\n\n    gc_free(&gc);\n    return n_current > 0 && !rel->hold;\n}\n\n/* return next buffer to send to remote */\nstruct buffer *\nreliable_send(struct reliable *rel, int *opcode)\n{\n    struct reliable_entry *best = NULL;\n    const time_t local_now = now;\n\n    for (int i = 0; i < rel->size; ++i)\n    {\n        struct reliable_entry *e = &rel->array[i];\n\n        /* If N_ACK_RETRANSMIT later packets have received ACKs, we assume\n         * that the packet was lost and resend it even if the timeout has\n         * not expired yet. */\n        if (e->active && (e->n_acks >= N_ACK_RETRANSMIT || local_now >= e->next_try))\n        {\n            if (!best || reliable_pid_min(e->packet_id, best->packet_id))\n            {\n                best = e;\n            }\n        }\n    }\n    if (best)\n    {\n        /* exponential backoff */\n        best->next_try = local_now + best->timeout;\n        best->timeout *= 2;\n        best->n_acks = 0;\n        *opcode = best->opcode;\n        dmsg(D_REL_DEBUG, \"ACK reliable_send ID \" packet_id_format \" (size=%d to=%d)\",\n             (packet_id_print_type)best->packet_id, best->buf.len,\n             (int)(best->next_try - local_now));\n        return &best->buf;\n    }\n    return NULL;\n}\n\n/* schedule all pending packets for immediate retransmit */\nvoid\nreliable_schedule_now(struct reliable *rel)\n{\n    dmsg(D_REL_DEBUG, \"ACK reliable_schedule_now\");\n    rel->hold = false;\n    for (int i = 0; i < rel->size; ++i)\n    {\n        struct reliable_entry *e = &rel->array[i];\n        if (e->active)\n        {\n            e->next_try = now;\n            e->timeout = rel->initial_timeout;\n        }\n    }\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\n/* in how many seconds should we wake up to check for timeout */\n/* if we return BIG_TIMEOUT, nothing to wait for */\ninterval_t\nreliable_send_timeout(const struct reliable *rel)\n{\n    struct gc_arena gc = gc_new();\n    interval_t ret = BIG_TIMEOUT;\n    const time_t local_now = now;\n\n    for (int i = 0; i < rel->size; ++i)\n    {\n        const struct reliable_entry *e = &rel->array[i];\n        if (e->active)\n        {\n            if (e->next_try <= local_now)\n            {\n                ret = 0;\n                break;\n            }\n            else\n            {\n                ret = min_int(ret, e->next_try - local_now);\n            }\n        }\n    }\n\n    dmsg(D_REL_DEBUG, \"ACK reliable_send_timeout %d %s\", (int)ret, reliable_print_ids(rel, &gc));\n\n    gc_free(&gc);\n    return ret;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n/*\n * Enable an incoming buffer previously returned by a get function as active.\n */\n\nvoid\nreliable_mark_active_incoming(struct reliable *rel, struct buffer *buf, packet_id_type pid,\n                              int opcode)\n{\n    for (int i = 0; i < rel->size; ++i)\n    {\n        struct reliable_entry *e = &rel->array[i];\n        if (buf == &e->buf)\n        {\n            e->active = true;\n\n            /* packets may not arrive in sequential order */\n            e->packet_id = pid;\n\n            /* check for replay */\n            ASSERT(!reliable_pid_min(pid, rel->packet_id));\n\n            e->opcode = opcode;\n            e->next_try = 0;\n            e->timeout = 0;\n            e->n_acks = 0;\n            dmsg(D_REL_DEBUG, \"ACK mark active incoming ID \" packet_id_format,\n                 (packet_id_print_type)e->packet_id);\n            return;\n        }\n    }\n    ASSERT(0); /* buf not found in rel */\n}\n\n/*\n * Enable an outgoing buffer previously returned by a get function as active.\n */\n\nvoid\nreliable_mark_active_outgoing(struct reliable *rel, struct buffer *buf, int opcode)\n{\n    for (int i = 0; i < rel->size; ++i)\n    {\n        struct reliable_entry *e = &rel->array[i];\n        if (buf == &e->buf)\n        {\n            /* Write mode, increment packet_id (i.e. sequence number)\n             * linearly and prepend id to packet */\n            packet_id_type net_pid;\n            e->packet_id = rel->packet_id++;\n            net_pid = htonpid(e->packet_id);\n            ASSERT(buf_write_prepend(buf, &net_pid, sizeof(net_pid)));\n            e->active = true;\n            e->opcode = opcode;\n            e->next_try = 0;\n            e->timeout = rel->initial_timeout;\n            dmsg(D_REL_DEBUG, \"ACK mark active outgoing ID \" packet_id_format,\n                 (packet_id_print_type)e->packet_id);\n            return;\n        }\n    }\n    ASSERT(0); /* buf not found in rel */\n}\n\n/* delete a buffer previously activated by reliable_mark_active() */\nvoid\nreliable_mark_deleted(struct reliable *rel, struct buffer *buf)\n{\n    for (int i = 0; i < rel->size; ++i)\n    {\n        struct reliable_entry *e = &rel->array[i];\n        if (buf == &e->buf)\n        {\n            e->active = false;\n            rel->packet_id = e->packet_id + 1;\n            return;\n        }\n    }\n    ASSERT(0);\n}\n\n#if 0\n\nvoid\nreliable_ack_debug_print(const struct reliable_ack *ack, char *desc)\n{\n    printf(\"********* struct reliable_ack %s\\n\", desc);\n    for (int i = 0; i < ack->len; ++i)\n    {\n        printf(\"  %d: \" packet_id_format \"\\n\", i, (packet_id_print_type) ack->packet_id[i]);\n    }\n}\n\nvoid\nreliable_debug_print(const struct reliable *rel, char *desc)\n{\n    update_time();\n\n    printf(\"********* struct reliable %s\\n\", desc);\n    printf(\"  initial_timeout=%d\\n\", (int)rel->initial_timeout);\n    printf(\"  packet_id=\" packet_id_format \"\\n\", rel->packet_id);\n    printf(\"  now=%\" PRIi64 \"\\n\", (int64_t)now);\n    for (int i = 0; i < rel->size; ++i)\n    {\n        const struct reliable_entry *e = &rel->array[i];\n        if (e->active)\n        {\n            printf(\"  %d: packet_id=\" packet_id_format \" len=%d\", i, e->packet_id, e->buf.len);\n            printf(\" next_try=%\" PRIi64, (int64_t)e->next_try);\n            printf(\"\\n\");\n        }\n    }\n}\n\n#endif /* if 0 */\n"
  },
  {
    "path": "src/openvpn/reliable.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n\n/**\n * @file\n * Reliability Layer module header file.\n */\n\n\n#ifndef RELIABLE_H\n#define RELIABLE_H\n\n#include \"basic.h\"\n#include \"buffer.h\"\n#include \"packet_id.h\"\n#include \"session_id.h\"\n#include \"mtu.h\"\n\n/** @addtogroup reliable\n *  @{ */\n\n\n#define RELIABLE_ACK_SIZE                       \\\n    8 /**< The maximum number of packet IDs     \\\n       *   waiting to be acknowledged which can \\\n       *   be stored in one \\c reliable_ack     \\\n       *   structure. */\n\n#define RELIABLE_CAPACITY                      \\\n    12 /**< The maximum number of packets that \\\n        *   the reliability layer for one VPN  \\\n        *   tunnel in one direction can store. */\n\n#define N_ACK_RETRANSMIT                      \\\n    3 /**< We retry sending a packet early if \\\n       *   this many later packets have been  \\\n       *   ACKed. */\n\n/**\n * The acknowledgment structure in which packet IDs are stored for later\n * acknowledgment.\n */\nstruct reliable_ack\n{\n    int len;\n    packet_id_type packet_id[RELIABLE_ACK_SIZE];\n};\n\n/* The size of the ACK header */\n#define ACK_SIZE(n) (sizeof(uint8_t) + ((n) ? SID_SIZE : 0) + sizeof(packet_id_type) * (n))\n\n/**\n * The structure in which the reliability layer stores a single incoming\n * or outgoing packet.\n */\nstruct reliable_entry\n{\n    bool active;\n    interval_t timeout;\n    time_t next_try;\n    packet_id_type packet_id;\n    size_t n_acks; /* Number of acks received for packets with higher PID.\n                    * Used for fast retransmission when there were at least\n                    * N_ACK_RETRANSMIT. */\n    int opcode;\n    struct buffer buf;\n};\n\n/**\n * The reliability layer storage structure for one VPN tunnel's control\n * channel in one direction.\n */\nstruct reliable\n{\n    int size;\n    interval_t initial_timeout;\n    packet_id_type packet_id;\n    int offset; /**< Offset of the bufs in the reliable_entry array */\n    bool hold;  /* don't xmit until reliable_schedule_now is called */\n    struct reliable_entry array[RELIABLE_CAPACITY];\n};\n\n\n/**************************************************************************/\n/** @name Functions for processing incoming acknowledgments\n *  @{ */\n\n/**\n * Read an acknowledgment record from a received packet.\n *\n * This function reads the packet ID acknowledgment record from the packet\n * contained in \\a buf.  If the record contains acknowledgments, these are\n * stored in \\a ack.  This function also compares the packet's session ID\n * with the expected session ID \\a sid, which should be equal.\n *\n * @param ack The acknowledgment structure in which received\n *     acknowledgments are to be stored.\n * @param buf The buffer containing the packet.\n * @param sid The expected session ID to compare to the session ID in\n *     the packet.\n *\n * @return\n * @li True, if processing was successful.\n * @li False, if an error occurs during processing.\n */\nbool reliable_ack_read(struct reliable_ack *ack, struct buffer *buf, const struct session_id *sid);\n\n\n/**\n * Parse an acknowledgment record from a received packet.\n *\n * This function parses the packet ID acknowledgment record from the packet\n * contained in \\a buf.  If the record contains acknowledgments, these are\n * stored in \\a ack.  This function also extracts packet's session ID\n * and returns it in \\a session_id_remote\n *\n * @param ack The acknowledgment structure in which received\n *     acknowledgments are to be stored.\n * @param buf The buffer containing the packet.\n * @param session_id_remote The parsed remote session id. This field is\n *                          is only filled if ack->len >= 1\n * @return\n * @li True, if processing was successful.\n * @li False, if an error occurs during processing.\n */\nbool reliable_ack_parse(struct buffer *buf, struct reliable_ack *ack,\n                        struct session_id *session_id_remote);\n\n/**\n * Remove acknowledged packets from a reliable structure.\n *\n * @param rel The reliable structure storing sent packets.\n * @param ack The acknowledgment structure containing received\n *     acknowledgments.\n */\nvoid reliable_send_purge(struct reliable *rel, const struct reliable_ack *ack);\n\n/** @} name Functions for processing incoming acknowledgments */\n\n\n/**************************************************************************/\n/** @name Functions for processing outgoing acknowledgments\n *  @{ */\n\n/**\n * Check whether an acknowledgment structure contains any\n *     packet IDs to be acknowledged.\n *\n * @param ack The acknowledgment structure to check.\n *\n * @return\n * @li True, if the acknowledgment structure is empty.\n * @li False, if there are packet IDs to be acknowledged.\n */\nstatic inline bool\nreliable_ack_empty(struct reliable_ack *ack)\n{\n    return !ack->len;\n}\n\n/**\n * Returns the number of packets that need to be acked.\n *\n * @param ack The acknowledgment structure to check.\n *\n * @returns the number of outstanding acks\n */\nstatic inline int\nreliable_ack_outstanding(struct reliable_ack *ack)\n{\n    return ack->len;\n}\n\n\n/**\n * Write a packet ID acknowledgment record to a buffer.\n *\n * @param ack The acknowledgment structure containing packet IDs to be\n *     acknowledged.\n * @param ack_mru List of packets we have acknowledged before. Packets from\n *                \\c ack will be moved here and if there is space in our\n *                ack structure we will fill it with packets from this\n * @param buf The buffer into which the acknowledgment record will be\n *     written.\n * @param sid The session ID of the VPN tunnel associated with the\n *     packet IDs to be acknowledged.\n * @param max The maximum number of acknowledgments to be written in\n *     the record.\n * @param prepend If true, prepend the acknowledgment record in the\n *     buffer; if false, write into the buffer's current position.\n *\n * @return\n * @li True, if processing was successful.\n * @li False, if an error occurs during processing.\n */\nbool reliable_ack_write(struct reliable_ack *ack, struct reliable_ack *ack_mru, struct buffer *buf,\n                        const struct session_id *sid, int max, bool prepend);\n\n/** @} name Functions for processing outgoing acknowledgments */\n\n\n/**************************************************************************/\n/** @name Functions for initialization and cleanup\n *  @{ */\n\n/**\n * Initialize a reliable structure.\n *\n * @param rel The reliable structure to initialize.\n * @param buf_size The size of the buffers in which packets will be\n *     stored.\n * @param offset The size of reserved space at the beginning of the\n *     buffers to allow efficient header prepending.\n * @param array_size The number of packets that this reliable\n *     structure can store simultaneously.\n * @param hold description\n */\nvoid reliable_init(struct reliable *rel, int buf_size, int offset, int array_size, bool hold);\n\n/**\n * Free allocated memory associated with a reliable structure and the pointer\n * itself.\n * Does nothing if rel is NULL.\n *\n * @param rel The reliable structured to clean up.\n */\nvoid reliable_free(struct reliable *rel);\n\n/** @} name Functions for initialization and cleanup */\n\n\n/**************************************************************************/\n/** @name Functions for inserting incoming packets\n *  @{ */\n\n/**\n * Check whether a reliable structure has any free buffers\n *     available for use.\n *\n * @param rel The reliable structure to check.\n *\n * @return\n * @li True, if at least one buffer is available for use.\n * @li False, if all the buffers are active.\n */\nbool reliable_can_get(const struct reliable *rel);\n\n/**\n * Check that a received packet's ID is not a replay.\n *\n * @param rel The reliable structure for handling this VPN tunnel's\n *     received packets.\n * @param id The packet ID of the received packet.\n *\n * @return\n * @li True, if the packet ID is not a replay.\n * @li False, if the packet ID is a replay.\n */\nbool reliable_not_replay(const struct reliable *rel, packet_id_type id);\n\n/**\n * Check that a received packet's ID can safely be stored in\n *     the reliable structure's processing window.\n *\n * This function checks the difference between the received packet's ID\n * and the lowest non-acknowledged packet ID in the given reliable\n * structure.  If that difference is larger than the total number of\n * packets which can be stored, then this packet cannot be stored safely,\n * because the reliable structure could possibly fill up without leaving\n * room for all intervening packets.  In that case, this received packet\n * could break the reliable structure's sequentiality, and must therefore\n * be discarded.\n *\n * @param rel The reliable structure for handling this VPN tunnel's\n *     received packets.\n * @param id The packet ID of the received packet.\n *\n * @return\n * @li True, if the packet can safely be stored.\n * @li False, if the packet does not fit safely in the reliable\n *     structure's processing window.\n */\nbool reliable_wont_break_sequentiality(const struct reliable *rel, packet_id_type id);\n\n/**\n * Read the packet ID of a received packet.\n *\n * @param buf The buffer containing the received packet.\n * @param pid A pointer where the packet's packet ID will be written.\n *\n * @return\n * @li True, if processing was successful.\n * @li False, if an error occurs during processing.\n */\nbool reliable_ack_read_packet_id(struct buffer *buf, packet_id_type *pid);\n\n/**\n * Get the buffer of a free %reliable entry in which to store a\n *     packet.\n *\n * @param rel The reliable structure in which to search for a free\n *     entry.\n *\n * @return A pointer to a buffer of a free entry in the \\a rel\n *     reliable structure.  If there are no free entries available, this\n *     function returns NULL.\n */\nstruct buffer *reliable_get_buf(struct reliable *rel);\n\n/**\n * Mark the %reliable entry associated with the given buffer as active\n * incoming.\n *\n * @param rel The reliable structure associated with this packet.\n * @param buf The buffer into which the packet has been copied.\n * @param pid The packet's packet ID.\n * @param opcode The packet's opcode.\n */\nvoid reliable_mark_active_incoming(struct reliable *rel, struct buffer *buf, packet_id_type pid,\n                                   int opcode);\n\n/**\n * Record a packet ID for later acknowledgment.\n *\n * @param ack The acknowledgment structure which stores this VPN\n *     tunnel's packet IDs for later acknowledgment.\n * @param pid The packet ID of the received packet which should be\n *     acknowledged.\n *\n * @return\n * @li True, if the packet ID was added to \\a ack.\n * @li False, if the packet ID was already present in \\a ack or \\a ack\n *     has no free space to store any more packet IDs.\n */\nbool reliable_ack_acknowledge_packet_id(struct reliable_ack *ack, packet_id_type pid);\n\n/** @} name Functions for inserting incoming packets */\n\n\n/**************************************************************************/\n/** @name Functions for extracting incoming packets\n *  @{ */\n\n/**\n * Get the buffer of the next sequential and active entry.\n *\n * @param rel The reliable structure from which to retrieve the\n *     buffer.\n *\n * @return A pointer to the entry with the next sequential key ID.\n *     If no such entry is present, this function  returns NULL.\n */\nstruct reliable_entry *reliable_get_entry_sequenced(struct reliable *rel);\n\n\n/**\n * Copies the first n acks from \\c ack to \\c ack_mru\n *\n * @param ack The reliable structure to copy the acks from\n * @param ack_mru The reliable structure to insert the acks into\n * @param n The number of ACKS to copy\n */\nvoid copy_acks_to_mru(struct reliable_ack *ack, struct reliable_ack *ack_mru, int n);\n\n\n/**\n * Remove an entry from a reliable structure.\n *\n * @param rel The reliable structure associated with the given buffer.\n * @param buf The buffer of the reliable entry which is to be removed.\n */\nvoid reliable_mark_deleted(struct reliable *rel, struct buffer *buf);\n\n/** @} name Functions for extracting incoming packets */\n\n\n/**************************************************************************/\n/** @name Functions for inserting outgoing packets\n *  @{ */\n\n/**\n * Get the buffer of free reliable entry and check whether the\n *     outgoing acknowledgment sequence is still okay.\n *\n * @param rel The reliable structure in which to search for a free\n *     entry.\n *\n * @return A pointer to a buffer of a free entry in the \\a rel\n *     reliable structure.  If there are no free entries available, this\n *     function returns NULL.  If the outgoing acknowledgment sequence is\n *     broken, this function also returns NULL.\n */\nstruct buffer *reliable_get_buf_output_sequenced(struct reliable *rel);\n\n\n/**\n * Counts the number of free buffers in output that can be potentially used\n * for sending\n *\n *  @param rel The reliable structure in which to search for a free\n *     entry.\n *\n *  @return the number of buffer that are available for sending without\n *             breaking ack sequence\n * */\nint reliable_get_num_output_sequenced_available(struct reliable *rel);\n\n/**\n * Mark the reliable entry associated with the given buffer as\n *     active outgoing.\n *\n * @param rel The reliable structure for handling this VPN tunnel's\n *     outgoing packets.\n * @param buf The buffer previously returned by \\c\n *     reliable_get_buf_output_sequenced() into which the packet has been\n *     copied.\n * @param opcode The packet's opcode.\n */\nvoid reliable_mark_active_outgoing(struct reliable *rel, struct buffer *buf, int opcode);\n\n/** @} name Functions for inserting outgoing packets */\n\n\n/**************************************************************************/\n/** @name Functions for extracting outgoing packets\n *  @{ */\n\n/**\n * Check whether a reliable structure has any active entries\n *     ready to be (re)sent.\n *\n * @param rel The reliable structure to check.\n *\n * @return\n * @li True, if there are active entries ready to be (re)sent\n *     president.\n * @li False, if there are no active entries, or the active entries\n *     are not yet ready for resending.\n */\nbool reliable_can_send(const struct reliable *rel);\n\n/**\n * Get the next packet to send to the remote peer.\n *\n * This function looks for the active entry ready for (re)sending with the\n * lowest packet ID, and returns the buffer associated with it.  This\n * function also resets the timeout after which that entry will become\n * ready for resending again.\n *\n * @param rel The reliable structure to check.\n * @param opcode A pointer to an integer in which this function will\n *     store the opcode of the next packet to be sent.\n *\n * @return A pointer to the buffer of the next entry to be sent, or\n *     NULL if there are no entries ready for (re)sending present in the\n *     reliable structure.  If a valid pointer is returned, then \\a opcode\n *     will point to the opcode of that packet.\n */\nstruct buffer *reliable_send(struct reliable *rel, int *opcode);\n\n/** @} name Functions for extracting outgoing packets */\n\n\n/**************************************************************************/\n/** @name Miscellaneous functions\n *  @{ */\n\n/**\n * Check whether a reliable structure is empty.\n *\n * @param rel The reliable structure to check.\n *\n * @return\n * @li True, if there are no active entries in the given reliable\n *     structure.\n * @li False, if there is at least one active entry present.\n */\nbool reliable_empty(const struct reliable *rel);\n\n/**\n * Determined how many seconds until the earliest resend should\n *     be attempted.\n *\n * @param rel The reliable structured to check.\n *\n * @return The interval in seconds until the earliest resend attempt\n *     of the outgoing packets stored in the \\a rel reliable structure. If\n *     the next time for attempting resending of one or more packets has\n *     already passed, this function will return 0.\n */\ninterval_t reliable_send_timeout(const struct reliable *rel);\n\n/**\n * Reschedule all entries of a reliable structure to be ready\n *     for (re)sending immediately.\n *\n * @param rel The reliable structure of which the entries should be\n *     modified.\n */\nvoid reliable_schedule_now(struct reliable *rel);\n\nvoid reliable_debug_print(const struct reliable *rel, char *desc);\n\n/* set sending timeout (after this time we send again until ACK) */\nstatic inline void\nreliable_set_timeout(struct reliable *rel, interval_t timeout)\n{\n    rel->initial_timeout = timeout;\n}\n\n/* print a reliable ACK record coming off the wire */\nconst char *reliable_ack_print(struct buffer *buf, bool verbose, struct gc_arena *gc);\n\nvoid reliable_ack_debug_print(const struct reliable_ack *ack, char *desc);\n\n/** @} name Miscellaneous functions */\n\n\n/** @} addtogroup reliable */\n\n\n#endif /* RELIABLE_H */\n"
  },
  {
    "path": "src/openvpn/ring_buffer.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *                2019 Lev Stipakov <lev@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, write to the Free Software Foundation, Inc.,\n *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n */\n\n#ifdef _WIN32\n#ifndef OPENVPN_RING_BUFFER_H\n#define OPENVPN_RING_BUFFER_H\n\n#include <windows.h>\n#include <winioctl.h>\n\n#include <stdint.h>\n#include <stdbool.h>\n\n/*\n * Values below are taken from Wireguard Windows client\n * https://github.com/WireGuard/wireguard-go/blob/master/tun/wintun/ring_windows.go#L14\n */\n#define WINTUN_RING_CAPACITY       0x800000\n#define WINTUN_RING_TRAILING_BYTES 0x10000\n#define WINTUN_MAX_PACKET_SIZE     0xffff\n#define WINTUN_PACKET_ALIGN        4\n\n#define TUN_IOCTL_REGISTER_RINGS \\\n    CTL_CODE(51820U, 0x970U, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)\n\n/**\n * Wintun ring buffer\n * See https://github.com/WireGuard/wintun#ring-layout\n */\nstruct tun_ring\n{\n    volatile ULONG head;\n    volatile ULONG tail;\n    volatile LONG alertable;\n    UCHAR data[WINTUN_RING_CAPACITY + WINTUN_RING_TRAILING_BYTES];\n};\n\n/**\n * Struct for ring buffers registration\n * See https://github.com/WireGuard/wintun#registering-rings\n */\nstruct tun_register_rings\n{\n    struct\n    {\n        ULONG ring_size;\n        struct tun_ring *ring;\n        HANDLE tail_moved;\n    } send, receive;\n};\n\nstruct TUN_PACKET_HEADER\n{\n    uint32_t size;\n};\n\nstruct TUN_PACKET\n{\n    uint32_t size;\n    UCHAR data[WINTUN_MAX_PACKET_SIZE];\n};\n\n/**\n * Registers ring buffers used to exchange data between\n * userspace openvpn process and wintun kernel driver,\n * see https://github.com/WireGuard/wintun#registering-rings\n *\n * @param device              handle to opened wintun device\n * @param send_ring           pointer to send ring\n * @param receive_ring        pointer to receive ring\n * @param send_tail_moved     event set by wintun to signal openvpn\n *                            that data is available for reading in send ring\n * @param receive_tail_moved  event set by openvpn to signal wintun\n *                            that data has been written to receive ring\n * @return                    true if registration is successful, false otherwise - use\n * GetLastError()\n */\nstatic inline bool\nregister_ring_buffers(HANDLE device, struct tun_ring *send_ring, struct tun_ring *receive_ring,\n                      HANDLE send_tail_moved, HANDLE receive_tail_moved)\n{\n    struct tun_register_rings rr;\n    BOOL res;\n    DWORD bytes_returned;\n\n    ZeroMemory(&rr, sizeof(rr));\n\n    rr.send.ring = send_ring;\n    rr.send.ring_size = sizeof(struct tun_ring);\n    rr.send.tail_moved = send_tail_moved;\n\n    rr.receive.ring = receive_ring;\n    rr.receive.ring_size = sizeof(struct tun_ring);\n    rr.receive.tail_moved = receive_tail_moved;\n\n    res = DeviceIoControl(device, TUN_IOCTL_REGISTER_RINGS, &rr, sizeof(rr), NULL, 0,\n                          &bytes_returned, NULL);\n\n    return res != FALSE;\n}\n\n#endif /* ifndef OPENVPN_RING_BUFFER_H */\n#endif /* ifdef _WIN32 */\n"
  },
  {
    "path": "src/openvpn/route.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * Support routines for adding/deleting network routes.\n */\n#include <stddef.h>\n#include <stdbool.h>\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"common.h\"\n#include \"error.h\"\n#include \"route.h\"\n#include \"run_command.h\"\n#include \"socket.h\"\n#include \"manage.h\"\n#include \"win32.h\"\n#include \"options.h\"\n#include \"networking.h\"\n#include \"integer.h\"\n\n#include \"memdbg.h\"\n\n#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)\n#include <linux/rtnetlink.h> /* RTM_GETROUTE etc. */\n#endif\n\n#if defined(TARGET_NETBSD)\n#include <net/route.h> /* RT_ROUNDUP(), RT_ADVANCE() */\n#endif\n\n#ifdef _WIN32\n#include \"openvpn-msg.h\"\n\n#define METRIC_NOT_USED ((DWORD)-1)\nstatic int add_route_service(const struct route_ipv4 *, const struct tuntap *);\n\nstatic bool del_route_service(const struct route_ipv4 *, const struct tuntap *);\n\nstatic int add_route_ipv6_service(const struct route_ipv6 *, const struct tuntap *);\n\nstatic bool del_route_ipv6_service(const struct route_ipv6 *, const struct tuntap *);\n\nstatic int route_ipv6_ipapi(bool add, const struct route_ipv6 *, const struct tuntap *);\n\nstatic int add_route_ipapi(const struct route_ipv4 *r, const struct tuntap *tt,\n                           DWORD adapter_index);\n\nstatic bool del_route_ipapi(const struct route_ipv4 *r, const struct tuntap *tt);\n\n\n#endif\n\nstatic void delete_route(struct route_ipv4 *r, const struct tuntap *tt, unsigned int flags,\n                         const struct route_gateway_info *rgi, const struct env_set *es,\n                         openvpn_net_ctx_t *ctx);\n\nstatic void get_bypass_addresses(struct route_bypass *rb, const unsigned int flags);\n\n#ifdef ENABLE_DEBUG\n\nstatic void\nprint_bypass_addresses(const struct route_bypass *rb)\n{\n    struct gc_arena gc = gc_new();\n    int i;\n    for (i = 0; i < rb->n_bypass; ++i)\n    {\n        msg(D_ROUTE, \"ROUTE: bypass_host_route[%d]=%s\", i, print_in_addr_t(rb->bypass[i], 0, &gc));\n    }\n    gc_free(&gc);\n}\n\n#endif\n\n/* Route addition return status codes */\n#define RTA_ERROR   0 /* route addition failed */\n#define RTA_SUCCESS 1 /* route addition succeeded */\n#define RTA_EEXIST  2 /* route not added as it already exists */\n\n#ifndef TARGET_ANDROID\nstatic bool\nadd_bypass_address(struct route_bypass *rb, const in_addr_t a)\n{\n    int i;\n    for (i = 0; i < rb->n_bypass; ++i)\n    {\n        if (a == rb->bypass[i]) /* avoid duplicates */\n        {\n            return true;\n        }\n    }\n    if (rb->n_bypass < N_ROUTE_BYPASS)\n    {\n        rb->bypass[rb->n_bypass++] = a;\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n#endif\n\nstruct route_option_list *\nnew_route_option_list(struct gc_arena *a)\n{\n    struct route_option_list *ret;\n    ALLOC_OBJ_CLEAR_GC(ret, struct route_option_list, a);\n    ret->gc = a;\n    return ret;\n}\n\nstruct route_ipv6_option_list *\nnew_route_ipv6_option_list(struct gc_arena *a)\n{\n    struct route_ipv6_option_list *ret;\n    ALLOC_OBJ_CLEAR_GC(ret, struct route_ipv6_option_list, a);\n    ret->gc = a;\n    return ret;\n}\n\n/*\n * NOTE: structs are cloned/copied shallow by design.\n * The routes list from src will stay intact since it is allocated using\n * the options->gc. The cloned/copied lists will share this common tail\n * to avoid copying the data around between pulls. Pulled routes use\n * the c2->gc so they get freed immediately after a reconnect.\n */\nstruct route_option_list *\nclone_route_option_list(const struct route_option_list *src, struct gc_arena *a)\n{\n    struct route_option_list *ret;\n    ALLOC_OBJ_GC(ret, struct route_option_list, a);\n    *ret = *src;\n    return ret;\n}\n\nstruct route_ipv6_option_list *\nclone_route_ipv6_option_list(const struct route_ipv6_option_list *src, struct gc_arena *a)\n{\n    struct route_ipv6_option_list *ret;\n    ALLOC_OBJ_GC(ret, struct route_ipv6_option_list, a);\n    *ret = *src;\n    return ret;\n}\n\nvoid\ncopy_route_option_list(struct route_option_list *dest, const struct route_option_list *src,\n                       struct gc_arena *a)\n{\n    *dest = *src;\n    dest->gc = a;\n}\n\nvoid\ncopy_route_ipv6_option_list(struct route_ipv6_option_list *dest,\n                            const struct route_ipv6_option_list *src, struct gc_arena *a)\n{\n    *dest = *src;\n    dest->gc = a;\n}\n\nstatic const char *\nroute_string(const struct route_ipv4 *r, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n    buf_printf(&out, \"ROUTE network %s netmask %s gateway %s\", print_in_addr_t(r->network, 0, gc),\n               print_in_addr_t(r->netmask, 0, gc), print_in_addr_t(r->gateway, 0, gc));\n    if (r->flags & RT_METRIC_DEFINED)\n    {\n        buf_printf(&out, \" metric %d\", r->metric);\n    }\n    return BSTR(&out);\n}\n\nstatic bool\nis_route_parm_defined(const char *parm)\n{\n    if (!parm)\n    {\n        return false;\n    }\n    if (!strcmp(parm, \"default\"))\n    {\n        return false;\n    }\n    return true;\n}\n\nstatic void\nsetenv_route_addr(struct env_set *es, const char *key, const in_addr_t addr, int i)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer name = alloc_buf_gc(256, &gc);\n    if (i >= 0)\n    {\n        buf_printf(&name, \"route_%s_%d\", key, i);\n    }\n    else\n    {\n        buf_printf(&name, \"route_%s\", key);\n    }\n    setenv_str(es, BSTR(&name), print_in_addr_t(addr, 0, &gc));\n    gc_free(&gc);\n}\n\nstatic bool\nget_special_addr(const struct route_list *rl, const char *string, in_addr_t *out, bool *status)\n{\n    if (status)\n    {\n        *status = true;\n    }\n    if (!strcmp(string, \"vpn_gateway\"))\n    {\n        if (rl)\n        {\n            if (rl->spec.flags & RTSA_REMOTE_ENDPOINT)\n            {\n                *out = rl->spec.remote_endpoint;\n            }\n            else\n            {\n                msg(M_INFO, PACKAGE_NAME \" ROUTE: vpn_gateway undefined\");\n                if (status)\n                {\n                    *status = false;\n                }\n            }\n        }\n        return true;\n    }\n    else if (!strcmp(string, \"net_gateway\"))\n    {\n        if (rl)\n        {\n            if (rl->ngi.flags & RGI_ADDR_DEFINED)\n            {\n                *out = rl->ngi.gateway.addr;\n            }\n            else\n            {\n                msg(M_INFO, PACKAGE_NAME\n                    \" ROUTE: net_gateway undefined -- unable to get default gateway from system\");\n                if (status)\n                {\n                    *status = false;\n                }\n            }\n        }\n        return true;\n    }\n    else if (!strcmp(string, \"remote_host\"))\n    {\n        if (rl)\n        {\n            if (rl->spec.flags & RTSA_REMOTE_HOST)\n            {\n                *out = rl->spec.remote_host;\n            }\n            else\n            {\n                msg(M_INFO, PACKAGE_NAME \" ROUTE: remote_host undefined\");\n                if (status)\n                {\n                    *status = false;\n                }\n            }\n        }\n        return true;\n    }\n    return false;\n}\n\nbool\nis_special_addr(const char *addr_str)\n{\n    if (addr_str)\n    {\n        return get_special_addr(NULL, addr_str, NULL, NULL);\n    }\n    else\n    {\n        return false;\n    }\n}\n\nstatic bool\ninit_route(struct route_ipv4 *r, struct addrinfo **network_list, const struct route_option *ro,\n           const struct route_list *rl)\n{\n    const in_addr_t default_netmask = IPV4_NETMASK_HOST;\n    bool status;\n    int ret;\n    struct in_addr special = { 0 };\n\n    CLEAR(*r);\n    r->option = ro;\n\n    /* network */\n    if (!is_route_parm_defined(ro->network))\n    {\n        goto fail;\n    }\n\n    /* get_special_addr replaces specialaddr with a special ip addr\n     * like gw. getaddrinfo is called to convert a a addrinfo struct */\n\n    if (get_special_addr(rl, ro->network, (in_addr_t *)&special.s_addr, &status))\n    {\n        if (!status)\n        {\n            goto fail;\n        }\n        special.s_addr = htonl(special.s_addr);\n        char buf[INET_ADDRSTRLEN];\n        inet_ntop(AF_INET, &special, buf, sizeof(buf));\n        ret = openvpn_getaddrinfo(0, buf, NULL, 0, NULL, AF_INET, network_list);\n    }\n    else\n    {\n        ret = openvpn_getaddrinfo(GETADDR_RESOLVE | GETADDR_WARN_ON_SIGNAL, ro->network, NULL, 0,\n                                  NULL, AF_INET, network_list);\n    }\n\n    status = (ret == 0);\n\n    if (!status)\n    {\n        goto fail;\n    }\n\n    /* netmask */\n\n    if (is_route_parm_defined(ro->netmask))\n    {\n        r->netmask =\n            getaddr(GETADDR_HOST_ORDER | GETADDR_WARN_ON_SIGNAL, ro->netmask, 0, &status, NULL);\n        if (!status)\n        {\n            goto fail;\n        }\n    }\n    else\n    {\n        r->netmask = default_netmask;\n    }\n\n    /* gateway */\n\n    if (is_route_parm_defined(ro->gateway))\n    {\n        if (!get_special_addr(rl, ro->gateway, &r->gateway, &status))\n        {\n            r->gateway = getaddr(GETADDR_RESOLVE | GETADDR_HOST_ORDER | GETADDR_WARN_ON_SIGNAL,\n                                 ro->gateway, 0, &status, NULL);\n        }\n        if (!status)\n        {\n            goto fail;\n        }\n    }\n    else\n    {\n        if (rl->spec.flags & RTSA_REMOTE_ENDPOINT)\n        {\n            r->gateway = rl->spec.remote_endpoint;\n        }\n        else\n        {\n            msg(M_WARN, PACKAGE_NAME\n                \" ROUTE: \" PACKAGE_NAME\n                \" needs a gateway parameter for a --route option and no default was specified by either --route-gateway or --ifconfig options\");\n            goto fail;\n        }\n    }\n\n    /* metric */\n\n    r->metric = 0;\n    if (is_route_parm_defined(ro->metric))\n    {\n        r->metric = atoi(ro->metric);\n        if (r->metric < 0)\n        {\n            msg(M_WARN, PACKAGE_NAME \" ROUTE: route metric for network %s (%s) must be >= 0\",\n                ro->network, ro->metric);\n            goto fail;\n        }\n        r->flags |= RT_METRIC_DEFINED;\n    }\n    else if (rl->spec.flags & RTSA_DEFAULT_METRIC)\n    {\n        r->metric = rl->spec.default_metric;\n        r->flags |= RT_METRIC_DEFINED;\n    }\n\n    r->flags |= RT_DEFINED;\n\n    /* routing table id */\n    r->table_id = ro->table_id;\n\n    return true;\n\nfail:\n    msg(M_WARN, PACKAGE_NAME \" ROUTE: failed to parse/resolve route for host/network: %s\",\n        ro->network);\n    return false;\n}\n\nstatic bool\ninit_route_ipv6(struct route_ipv6 *r6, const struct route_ipv6_option *r6o,\n                const struct route_ipv6_list *rl6)\n{\n    CLEAR(*r6);\n\n    if (!get_ipv6_addr(r6o->prefix, &r6->network, &r6->netbits, M_WARN))\n    {\n        goto fail;\n    }\n\n    /* gateway */\n    if (is_route_parm_defined(r6o->gateway))\n    {\n        if (inet_pton(AF_INET6, r6o->gateway, &r6->gateway) != 1)\n        {\n            msg(M_WARN, PACKAGE_NAME \"ROUTE6: cannot parse gateway spec '%s'\", r6o->gateway);\n        }\n    }\n    else if (rl6->spec_flags & RTSA_REMOTE_ENDPOINT)\n    {\n        r6->gateway = rl6->remote_endpoint_ipv6;\n    }\n\n    /* metric */\n\n    r6->metric = -1;\n    if (is_route_parm_defined(r6o->metric))\n    {\n        r6->metric = atoi(r6o->metric);\n        if (r6->metric < 0)\n        {\n            msg(M_WARN, PACKAGE_NAME \" ROUTE: route metric for network %s (%s) must be >= 0\",\n                r6o->prefix, r6o->metric);\n            goto fail;\n        }\n        r6->flags |= RT_METRIC_DEFINED;\n    }\n    else if (rl6->spec_flags & RTSA_DEFAULT_METRIC)\n    {\n        r6->metric = rl6->default_metric;\n        r6->flags |= RT_METRIC_DEFINED;\n    }\n\n    r6->flags |= RT_DEFINED;\n\n    /* routing table id */\n    r6->table_id = r6o->table_id;\n\n    return true;\n\nfail:\n    msg(M_WARN, PACKAGE_NAME \" ROUTE: failed to parse/resolve route for host/network: %s\",\n        r6o->prefix);\n    return false;\n}\n\nvoid\nadd_route_to_option_list(struct route_option_list *l, const char *network, const char *netmask,\n                         const char *gateway, const char *metric, int table_id)\n{\n    struct route_option *ro;\n    ALLOC_OBJ_GC(ro, struct route_option, l->gc);\n    ro->network = network;\n    ro->netmask = netmask;\n    ro->gateway = gateway;\n    ro->metric = metric;\n    ro->table_id = table_id;\n    ro->next = l->routes;\n    l->routes = ro;\n}\n\nvoid\nadd_route_ipv6_to_option_list(struct route_ipv6_option_list *l, const char *prefix,\n                              const char *gateway, const char *metric, int table_id)\n{\n    struct route_ipv6_option *ro;\n    ALLOC_OBJ_GC(ro, struct route_ipv6_option, l->gc);\n    ro->prefix = prefix;\n    ro->gateway = gateway;\n    ro->metric = metric;\n    ro->table_id = table_id;\n    ro->next = l->routes_ipv6;\n    l->routes_ipv6 = ro;\n}\n\nstatic void\nclear_route_list(struct route_list *rl)\n{\n    gc_free(&rl->gc);\n    CLEAR(*rl);\n}\n\nstatic void\nclear_route_ipv6_list(struct route_ipv6_list *rl6)\n{\n    gc_free(&rl6->gc);\n    CLEAR(*rl6);\n}\n\nvoid\nroute_list_add_vpn_gateway(struct route_list *rl, struct env_set *es, const in_addr_t addr)\n{\n    ASSERT(rl);\n    rl->spec.remote_endpoint = addr;\n    rl->spec.flags |= RTSA_REMOTE_ENDPOINT;\n    setenv_route_addr(es, \"vpn_gateway\", rl->spec.remote_endpoint, -1);\n}\n\nstatic void\nadd_block_local_item(struct route_list *rl, const struct route_gateway_address *gateway,\n                     in_addr_t target)\n{\n    if (rl->rgi.gateway.netmask < 0xFFFFFFFF)\n    {\n        struct route_ipv4 *r1, *r2;\n        unsigned int l2;\n\n        ALLOC_OBJ_GC(r1, struct route_ipv4, &rl->gc);\n        ALLOC_OBJ_GC(r2, struct route_ipv4, &rl->gc);\n\n        /* split a route into two smaller blocking routes, and direct them to target */\n        l2 = ((~gateway->netmask) + 1) >> 1;\n        r1->flags = RT_DEFINED;\n        r1->gateway = target;\n        r1->network = gateway->addr & gateway->netmask;\n        r1->netmask = ~(l2 - 1);\n        r1->next = rl->routes;\n        rl->routes = r1;\n\n        *r2 = *r1;\n        r2->network += l2;\n        r2->next = rl->routes;\n        rl->routes = r2;\n    }\n}\n\nstatic void\nadd_block_local_routes(struct route_list *rl)\n{\n#ifndef TARGET_ANDROID\n    /* add bypass for gateway addr */\n    add_bypass_address(&rl->spec.bypass, rl->rgi.gateway.addr);\n#endif\n\n    /* block access to local subnet */\n    add_block_local_item(rl, &rl->rgi.gateway, rl->spec.remote_endpoint);\n\n    /* process additional subnets on gateway interface */\n    for (int i = 0; i < rl->rgi.n_addrs; ++i)\n    {\n        const struct route_gateway_address *gwa = &rl->rgi.addrs[i];\n        /* omit the add/subnet in &rl->rgi which we processed above */\n        if (!((rl->rgi.gateway.addr & rl->rgi.gateway.netmask) == (gwa->addr & gwa->netmask)\n              && rl->rgi.gateway.netmask == gwa->netmask))\n        {\n            add_block_local_item(rl, gwa, rl->spec.remote_endpoint);\n        }\n    }\n}\n\nbool\nblock_local_needed(const struct route_list *rl)\n{\n    const unsigned int rgi_needed = (RGI_ADDR_DEFINED | RGI_NETMASK_DEFINED);\n    return (rl->flags & RG_BLOCK_LOCAL) && (rl->rgi.flags & rgi_needed) == rgi_needed\n           && (rl->spec.flags & RTSA_REMOTE_ENDPOINT) && rl->spec.remote_host_local != TLA_LOCAL;\n}\n\nbool\ninit_route_list(struct route_list *rl, const struct route_option_list *opt,\n                const char *remote_endpoint, int default_metric, in_addr_t remote_host,\n                struct env_set *es, openvpn_net_ctx_t *ctx)\n{\n    struct gc_arena gc = gc_new();\n    bool ret = true;\n\n    clear_route_list(rl);\n\n    rl->flags = opt->flags;\n\n    if (remote_host != IPV4_INVALID_ADDR)\n    {\n        rl->spec.remote_host = remote_host;\n        rl->spec.flags |= RTSA_REMOTE_HOST;\n    }\n\n    if (default_metric)\n    {\n        rl->spec.default_metric = default_metric;\n        rl->spec.flags |= RTSA_DEFAULT_METRIC;\n    }\n\n    get_default_gateway(&rl->ngi, INADDR_ANY, ctx);\n    if (rl->ngi.flags & RGI_ADDR_DEFINED)\n    {\n        setenv_route_addr(es, \"net_gateway\", rl->ngi.gateway.addr, -1);\n#if defined(ENABLE_DEBUG) && !defined(ENABLE_SMALL)\n        print_default_gateway(D_ROUTE, &rl->rgi, NULL);\n#endif\n    }\n    else\n    {\n        dmsg(D_ROUTE, \"ROUTE: default_gateway=UNDEF\");\n    }\n\n    get_default_gateway(&rl->rgi, remote_host != IPV4_INVALID_ADDR ? remote_host : INADDR_ANY, ctx);\n\n    if (rl->spec.flags & RTSA_REMOTE_HOST)\n    {\n        rl->spec.remote_host_local = test_local_addr(remote_host, &rl->rgi);\n    }\n\n    if (is_route_parm_defined(remote_endpoint))\n    {\n        bool defined = false;\n        rl->spec.remote_endpoint =\n            getaddr(GETADDR_RESOLVE | GETADDR_HOST_ORDER | GETADDR_WARN_ON_SIGNAL, remote_endpoint,\n                    0, &defined, NULL);\n\n        if (defined)\n        {\n            setenv_route_addr(es, \"vpn_gateway\", rl->spec.remote_endpoint, -1);\n            rl->spec.flags |= RTSA_REMOTE_ENDPOINT;\n        }\n        else\n        {\n            msg(M_WARN, PACKAGE_NAME \" ROUTE: failed to parse/resolve default gateway: %s\",\n                remote_endpoint);\n            ret = false;\n        }\n    }\n\n    if (rl->flags & RG_ENABLE)\n    {\n        if (block_local_needed(rl))\n        {\n            add_block_local_routes(rl);\n        }\n        get_bypass_addresses(&rl->spec.bypass, rl->flags);\n#ifdef ENABLE_DEBUG\n        print_bypass_addresses(&rl->spec.bypass);\n#endif\n    }\n\n    /* parse the routes from opt to rl */\n    {\n        struct route_option *ro;\n        for (ro = opt->routes; ro; ro = ro->next)\n        {\n            struct addrinfo *netlist = NULL;\n            struct route_ipv4 r;\n\n            if (!init_route(&r, &netlist, ro, rl))\n            {\n                ret = false;\n            }\n            else\n            {\n                struct addrinfo *curele;\n                for (curele = netlist; curele; curele = curele->ai_next)\n                {\n                    struct route_ipv4 *new;\n                    ALLOC_OBJ_GC(new, struct route_ipv4, &rl->gc);\n                    *new = r;\n                    new->network = ntohl(((struct sockaddr_in *)curele->ai_addr)->sin_addr.s_addr);\n                    new->next = rl->routes;\n                    rl->routes = new;\n                }\n            }\n            if (netlist)\n            {\n                gc_addspecial(netlist, &gc_freeaddrinfo_callback, &gc);\n            }\n        }\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n\nbool\nipv6_net_contains_host(const struct in6_addr *network, unsigned int bits, const struct in6_addr *host)\n{\n    /* not the most beautiful implementation in the world, but portable and\n     * \"good enough\" */\n    if (bits > 128)\n    {\n        return false;\n    }\n\n    int i;\n    for (i = 0; bits >= 8; i++, bits -= 8)\n    {\n        if (network->s6_addr[i] != host->s6_addr[i])\n        {\n            return false;\n        }\n    }\n\n    if (bits == 0)\n    {\n        return true;\n    }\n\n    unsigned int mask = 0xff << (8 - bits);\n\n    if ((network->s6_addr[i] & mask) == (host->s6_addr[i] & mask))\n    {\n        return true;\n    }\n\n    return false;\n}\n\nbool\ninit_route_ipv6_list(struct route_ipv6_list *rl6, const struct route_ipv6_option_list *opt6,\n                     const char *remote_endpoint, int default_metric,\n                     const struct in6_addr *remote_host_ipv6, struct env_set *es,\n                     openvpn_net_ctx_t *ctx)\n{\n    struct gc_arena gc = gc_new();\n    bool ret = true;\n    bool need_remote_ipv6_route;\n\n    clear_route_ipv6_list(rl6);\n\n    rl6->flags = opt6->flags;\n\n    if (remote_host_ipv6)\n    {\n        rl6->remote_host_ipv6 = *remote_host_ipv6;\n        rl6->spec_flags |= RTSA_REMOTE_HOST;\n    }\n\n    if (default_metric >= 0)\n    {\n        rl6->default_metric = default_metric;\n        rl6->spec_flags |= RTSA_DEFAULT_METRIC;\n    }\n\n    msg(D_ROUTE, \"GDG6: remote_host_ipv6=%s\",\n        remote_host_ipv6 ? print_in6_addr(*remote_host_ipv6, 0, &gc) : \"n/a\");\n\n    get_default_gateway_ipv6(&rl6->ngi6, NULL, ctx);\n    if (rl6->ngi6.flags & RGI_ADDR_DEFINED)\n    {\n        setenv_str(es, \"net_gateway_ipv6\", print_in6_addr(rl6->ngi6.gateway.addr_ipv6, 0, &gc));\n#if defined(ENABLE_DEBUG) && !defined(ENABLE_SMALL)\n        print_default_gateway(D_ROUTE, NULL, &rl6->rgi6);\n#endif\n    }\n    else\n    {\n        dmsg(D_ROUTE, \"ROUTE6: default_gateway=UNDEF\");\n    }\n\n    get_default_gateway_ipv6(&rl6->rgi6, remote_host_ipv6, ctx);\n\n    if (is_route_parm_defined(remote_endpoint))\n    {\n        if (inet_pton(AF_INET6, remote_endpoint, &rl6->remote_endpoint_ipv6) == 1)\n        {\n            rl6->spec_flags |= RTSA_REMOTE_ENDPOINT;\n        }\n        else\n        {\n            msg(M_WARN, PACKAGE_NAME \" ROUTE: failed to parse/resolve VPN endpoint: %s\",\n                remote_endpoint);\n            ret = false;\n        }\n    }\n\n    /* parse the routes from opt6 to rl6\n     * discovering potential overlaps with remote_host_ipv6 in the process\n     */\n    need_remote_ipv6_route = false;\n\n    {\n        struct route_ipv6_option *ro6;\n        for (ro6 = opt6->routes_ipv6; ro6; ro6 = ro6->next)\n        {\n            struct route_ipv6 *r6;\n            ALLOC_OBJ_GC(r6, struct route_ipv6, &rl6->gc);\n            if (!init_route_ipv6(r6, ro6, rl6))\n            {\n                ret = false;\n            }\n            else\n            {\n                r6->next = rl6->routes_ipv6;\n                rl6->routes_ipv6 = r6;\n\n#ifndef TARGET_ANDROID\n                /* On Android the VPNService protect function call will take of\n                 * avoiding routing loops, so ignore this part and let\n                 * need_remote_ipv6_route always evaluate to false\n                 */\n                if (remote_host_ipv6\n                    && ipv6_net_contains_host(&r6->network, r6->netbits, remote_host_ipv6))\n                {\n                    need_remote_ipv6_route = true;\n                    msg(D_ROUTE,\n                        \"ROUTE6: %s/%d overlaps IPv6 remote %s, adding host route to VPN endpoint\",\n                        print_in6_addr(r6->network, 0, &gc), r6->netbits,\n                        print_in6_addr(*remote_host_ipv6, 0, &gc));\n                }\n#endif\n            }\n        }\n    }\n\n    /* add VPN server host route if needed */\n    if (need_remote_ipv6_route)\n    {\n        if ((rl6->rgi6.flags & (RGI_ADDR_DEFINED | RGI_IFACE_DEFINED))\n            == (RGI_ADDR_DEFINED | RGI_IFACE_DEFINED))\n        {\n            struct route_ipv6 *r6;\n            ALLOC_OBJ_CLEAR_GC(r6, struct route_ipv6, &rl6->gc);\n\n            r6->network = *remote_host_ipv6;\n            r6->netbits = 128;\n            if (!(rl6->rgi6.flags & RGI_ON_LINK))\n            {\n                r6->gateway = rl6->rgi6.gateway.addr_ipv6;\n            }\n            r6->metric = 1;\n#ifdef _WIN32\n            r6->adapter_index = rl6->rgi6.adapter_index;\n#else\n            r6->iface = rl6->rgi6.iface;\n#endif\n            r6->flags = RT_DEFINED | RT_METRIC_DEFINED;\n\n            r6->next = rl6->routes_ipv6;\n            rl6->routes_ipv6 = r6;\n        }\n        else\n        {\n            msg(M_WARN,\n                \"ROUTE6: IPv6 route overlaps with IPv6 remote address, but could not determine IPv6 gateway address + interface, expect failure\\n\");\n        }\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n\nstatic bool\nadd_route3(in_addr_t network, in_addr_t netmask, in_addr_t gateway, const struct tuntap *tt,\n           unsigned int flags, const struct route_gateway_info *rgi, const struct env_set *es,\n           openvpn_net_ctx_t *ctx)\n{\n    struct route_ipv4 r;\n    CLEAR(r);\n    r.flags = RT_DEFINED;\n    r.network = network;\n    r.netmask = netmask;\n    r.gateway = gateway;\n    return add_route(&r, tt, flags, rgi, es, ctx);\n}\n\nstatic void\ndel_route3(in_addr_t network, in_addr_t netmask, in_addr_t gateway, const struct tuntap *tt,\n           unsigned int flags, const struct route_gateway_info *rgi, const struct env_set *es,\n           openvpn_net_ctx_t *ctx)\n{\n    struct route_ipv4 r;\n    CLEAR(r);\n    r.flags = RT_DEFINED | RT_ADDED;\n    r.network = network;\n    r.netmask = netmask;\n    r.gateway = gateway;\n    delete_route(&r, tt, flags, rgi, es, ctx);\n}\n\nstatic bool\nadd_bypass_routes(struct route_bypass *rb, in_addr_t gateway, const struct tuntap *tt,\n                  unsigned int flags, const struct route_gateway_info *rgi,\n                  const struct env_set *es, openvpn_net_ctx_t *ctx)\n{\n    int ret = true;\n    for (int i = 0; i < rb->n_bypass; ++i)\n    {\n        if (rb->bypass[i])\n        {\n            ret = add_route3(rb->bypass[i], IPV4_NETMASK_HOST, gateway, tt, flags | ROUTE_REF_GW,\n                             rgi, es, ctx)\n                  && ret;\n        }\n    }\n    return ret;\n}\n\nstatic void\ndel_bypass_routes(struct route_bypass *rb, in_addr_t gateway, const struct tuntap *tt,\n                  unsigned int flags, const struct route_gateway_info *rgi,\n                  const struct env_set *es, openvpn_net_ctx_t *ctx)\n{\n    int i;\n    for (i = 0; i < rb->n_bypass; ++i)\n    {\n        if (rb->bypass[i])\n        {\n            del_route3(rb->bypass[i], IPV4_NETMASK_HOST, gateway, tt, flags | ROUTE_REF_GW, rgi, es,\n                       ctx);\n        }\n    }\n}\n\nstatic bool\nredirect_default_route_to_vpn(struct route_list *rl, const struct tuntap *tt, unsigned int flags,\n                              const struct env_set *es, openvpn_net_ctx_t *ctx)\n{\n    const char err[] = \"NOTE: unable to redirect IPv4 default gateway --\";\n    bool ret = true;\n\n    if (rl && rl->flags & RG_ENABLE)\n    {\n        bool local = rl->flags & RG_LOCAL;\n\n        if (!(rl->spec.flags & RTSA_REMOTE_ENDPOINT) && (rl->flags & RG_REROUTE_GW))\n        {\n            msg(M_WARN, \"%s VPN gateway parameter (--route-gateway or --ifconfig) is missing\", err);\n            ret = false;\n        }\n        /*\n         * check if a default route is defined, unless:\n         * - we are connecting to a remote host in our network\n         * - we are connecting to a non-IPv4 remote host (i.e. we use IPv6)\n         */\n        else if (!(rl->rgi.flags & RGI_ADDR_DEFINED) && !local\n                 && (rl->spec.flags & RTSA_REMOTE_HOST))\n        {\n            msg(M_WARN, \"%s Cannot read current default gateway from system\", err);\n            ret = false;\n        }\n        else\n        {\n#ifndef TARGET_ANDROID\n            if (rl->flags & RG_AUTO_LOCAL)\n            {\n                const int tla = rl->spec.remote_host_local;\n                if (tla == TLA_NONLOCAL)\n                {\n                    dmsg(D_ROUTE, \"ROUTE remote_host is NOT LOCAL\");\n                    local = false;\n                }\n                else if (tla == TLA_LOCAL)\n                {\n                    dmsg(D_ROUTE, \"ROUTE remote_host is LOCAL\");\n                    local = true;\n                }\n            }\n            if (!local)\n            {\n                /* route remote host to original default gateway */\n                /* if remote_host is not ipv4 (ie: ipv6), just skip\n                 * adding this special /32 route */\n                if ((rl->spec.flags & RTSA_REMOTE_HOST)\n                    && rl->spec.remote_host != IPV4_INVALID_ADDR)\n                {\n                    ret = add_route3(rl->spec.remote_host, IPV4_NETMASK_HOST, rl->rgi.gateway.addr,\n                                     tt, flags | ROUTE_REF_GW, &rl->rgi, es, ctx);\n                    if (ret)\n                    {\n                        rl->iflags |= RL_DID_LOCAL;\n                    }\n                }\n                else\n                {\n                    dmsg(D_ROUTE, \"ROUTE remote_host protocol differs from tunneled\");\n                }\n            }\n#endif /* ifndef TARGET_ANDROID */\n\n            /* route DHCP/DNS server traffic through original default gateway */\n            ret = add_bypass_routes(&rl->spec.bypass, rl->rgi.gateway.addr, tt, flags, &rl->rgi, es,\n                                    ctx)\n                  && ret;\n\n            if (rl->flags & RG_REROUTE_GW)\n            {\n                if (rl->flags & RG_DEF1)\n                {\n                    /* add new default route (1st component) */\n                    ret = add_route3(0x00000000, 0x80000000, rl->spec.remote_endpoint, tt, flags,\n                                     &rl->rgi, es, ctx)\n                          && ret;\n\n                    /* add new default route (2nd component) */\n                    ret = add_route3(0x80000000, 0x80000000, rl->spec.remote_endpoint, tt, flags,\n                                     &rl->rgi, es, ctx)\n                          && ret;\n                }\n                else\n                {\n                    /* don't try to remove the def route if it does not exist */\n                    if (rl->rgi.flags & RGI_ADDR_DEFINED)\n                    {\n                        /* delete default route */\n                        del_route3(0, 0, rl->rgi.gateway.addr, tt, flags | ROUTE_REF_GW, &rl->rgi,\n                                   es, ctx);\n                    }\n\n                    /* add new default route */\n                    ret = add_route3(0, 0, rl->spec.remote_endpoint, tt, flags, &rl->rgi, es, ctx)\n                          && ret;\n                }\n            }\n\n            /* set a flag so we can undo later */\n            rl->iflags |= RL_DID_REDIRECT_DEFAULT_GATEWAY;\n        }\n    }\n    return ret;\n}\n\nstatic void\nundo_redirect_default_route_to_vpn(struct route_list *rl, const struct tuntap *tt,\n                                   unsigned int flags, const struct env_set *es,\n                                   openvpn_net_ctx_t *ctx)\n{\n    if (rl && rl->iflags & RL_DID_REDIRECT_DEFAULT_GATEWAY)\n    {\n        /* delete remote host route */\n        if (rl->iflags & RL_DID_LOCAL)\n        {\n            del_route3(rl->spec.remote_host, IPV4_NETMASK_HOST, rl->rgi.gateway.addr, tt,\n                       flags | ROUTE_REF_GW, &rl->rgi, es, ctx);\n            rl->iflags &= ~RL_DID_LOCAL;\n        }\n\n        /* delete special DHCP/DNS bypass route */\n        del_bypass_routes(&rl->spec.bypass, rl->rgi.gateway.addr, tt, flags, &rl->rgi, es, ctx);\n\n        if (rl->flags & RG_REROUTE_GW)\n        {\n            if (rl->flags & RG_DEF1)\n            {\n                /* delete default route (1st component) */\n                del_route3(0x00000000, 0x80000000, rl->spec.remote_endpoint, tt, flags, &rl->rgi,\n                           es, ctx);\n\n                /* delete default route (2nd component) */\n                del_route3(0x80000000, 0x80000000, rl->spec.remote_endpoint, tt, flags, &rl->rgi,\n                           es, ctx);\n            }\n            else\n            {\n                /* delete default route */\n                del_route3(0, 0, rl->spec.remote_endpoint, tt, flags, &rl->rgi, es, ctx);\n                /* restore original default route if there was any */\n                if (rl->rgi.flags & RGI_ADDR_DEFINED)\n                {\n                    add_route3(0, 0, rl->rgi.gateway.addr, tt, flags | ROUTE_REF_GW, &rl->rgi, es,\n                               ctx);\n                }\n            }\n        }\n\n        rl->iflags &= ~RL_DID_REDIRECT_DEFAULT_GATEWAY;\n    }\n}\n\nbool\nadd_routes(struct route_list *rl, struct route_ipv6_list *rl6, const struct tuntap *tt,\n           unsigned int flags, const struct env_set *es, openvpn_net_ctx_t *ctx)\n{\n    bool ret = redirect_default_route_to_vpn(rl, tt, flags, es, ctx);\n    if (rl && !(rl->iflags & RL_ROUTES_ADDED))\n    {\n        struct route_ipv4 *r;\n\n        if (rl->routes && !tt->did_ifconfig_setup)\n        {\n            msg(M_INFO,\n                \"WARNING: OpenVPN was configured to add an IPv4 \"\n                \"route. However, no IPv4 has been configured for %s, \"\n                \"therefore the route installation may fail or may not work \"\n                \"as expected.\",\n                tt->actual_name);\n        }\n\n#ifdef ENABLE_MANAGEMENT\n        if (management && rl->routes)\n        {\n            management_set_state(management, OPENVPN_STATE_ADD_ROUTES, NULL, NULL, NULL, NULL,\n                                 NULL);\n        }\n#endif\n\n        for (r = rl->routes; r; r = r->next)\n        {\n            if (flags & ROUTE_DELETE_FIRST)\n            {\n                delete_route(r, tt, flags, &rl->rgi, es, ctx);\n            }\n            ret = add_route(r, tt, flags, &rl->rgi, es, ctx) && ret;\n        }\n        rl->iflags |= RL_ROUTES_ADDED;\n    }\n    if (rl6 && !(rl6->iflags & RL_ROUTES_ADDED))\n    {\n        struct route_ipv6 *r;\n\n        if (!tt->did_ifconfig_ipv6_setup)\n        {\n            msg(M_INFO,\n                \"WARNING: OpenVPN was configured to add an IPv6 \"\n                \"route. However, no IPv6 has been configured for %s, \"\n                \"therefore the route installation may fail or may not work \"\n                \"as expected.\",\n                tt->actual_name);\n        }\n\n        for (r = rl6->routes_ipv6; r; r = r->next)\n        {\n            if (flags & ROUTE_DELETE_FIRST)\n            {\n                delete_route_ipv6(r, tt, es, ctx);\n            }\n            ret = add_route_ipv6(r, tt, flags, es, ctx) && ret;\n        }\n        rl6->iflags |= RL_ROUTES_ADDED;\n    }\n\n    return ret;\n}\n\nvoid\ndelete_routes(struct route_list *rl, struct route_ipv6_list *rl6, const struct tuntap *tt,\n              unsigned int flags, const struct env_set *es, openvpn_net_ctx_t *ctx)\n{\n    delete_routes_v4(rl, tt, flags, es, ctx);\n    delete_routes_v6(rl6, tt, flags, es, ctx);\n}\n\nvoid\ndelete_routes_v4(struct route_list *rl, const struct tuntap *tt, unsigned int flags,\n                 const struct env_set *es, openvpn_net_ctx_t *ctx)\n{\n    if (rl && (rl->iflags & RL_ROUTES_ADDED))\n    {\n        struct route_ipv4 *r;\n        for (r = rl->routes; r; r = r->next)\n        {\n            delete_route(r, tt, flags, &rl->rgi, es, ctx);\n        }\n        rl->iflags &= ~RL_ROUTES_ADDED;\n    }\n\n    undo_redirect_default_route_to_vpn(rl, tt, flags, es, ctx);\n\n    if (rl)\n    {\n        clear_route_list(rl);\n    }\n}\n\nvoid\ndelete_routes_v6(struct route_ipv6_list *rl6, const struct tuntap *tt, unsigned int flags,\n                 const struct env_set *es, openvpn_net_ctx_t *ctx)\n{\n    if (rl6 && (rl6->iflags & RL_ROUTES_ADDED))\n    {\n        struct route_ipv6 *r6;\n        for (r6 = rl6->routes_ipv6; r6; r6 = r6->next)\n        {\n            delete_route_ipv6(r6, tt, es, ctx);\n        }\n        rl6->iflags &= ~RL_ROUTES_ADDED;\n    }\n\n    if (rl6)\n    {\n        clear_route_ipv6_list(rl6);\n    }\n}\n\n#ifndef ENABLE_SMALL\n\nstatic const char *\nshow_opt(const char *option)\n{\n    if (!option)\n    {\n        return \"default (not set)\";\n    }\n    else\n    {\n        return option;\n    }\n}\n\nstatic void\nprint_route_option(const struct route_option *ro, msglvl_t msglevel)\n{\n    msg(msglevel, \"  route %s/%s/%s/%s\", show_opt(ro->network), show_opt(ro->netmask),\n        show_opt(ro->gateway), show_opt(ro->metric));\n}\n\nvoid\nprint_route_options(const struct route_option_list *rol, msglvl_t msglevel)\n{\n    struct route_option *ro;\n    if (rol->flags & RG_ENABLE)\n    {\n        msg(msglevel, \"  [redirect_default_gateway local=%d]\", (rol->flags & RG_LOCAL) != 0);\n    }\n    for (ro = rol->routes; ro; ro = ro->next)\n    {\n        print_route_option(ro, msglevel);\n    }\n}\n\nvoid\nprint_default_gateway(const msglvl_t msglevel, const struct route_gateway_info *rgi,\n                      const struct route_ipv6_gateway_info *rgi6)\n{\n    struct gc_arena gc = gc_new();\n    if (rgi && (rgi->flags & RGI_ADDR_DEFINED))\n    {\n        struct buffer out = alloc_buf_gc(256, &gc);\n        buf_printf(&out, \"ROUTE_GATEWAY\");\n        if (rgi->flags & RGI_ON_LINK)\n        {\n            buf_printf(&out, \" ON_LINK\");\n        }\n        else\n        {\n            buf_printf(&out, \" %s\", print_in_addr_t(rgi->gateway.addr, 0, &gc));\n        }\n        if (rgi->flags & RGI_NETMASK_DEFINED)\n        {\n            buf_printf(&out, \"/%s\", print_in_addr_t(rgi->gateway.netmask, 0, &gc));\n        }\n#ifdef _WIN32\n        if (rgi->flags & RGI_IFACE_DEFINED)\n        {\n            buf_printf(&out, \" I=%lu\", rgi->adapter_index);\n        }\n#else\n        if (rgi->flags & RGI_IFACE_DEFINED)\n        {\n            buf_printf(&out, \" IFACE=%s\", rgi->iface);\n        }\n#endif\n        if (rgi->flags & RGI_HWADDR_DEFINED)\n        {\n            buf_printf(&out, \" HWADDR=%s\", format_hex_ex(rgi->hwaddr, 6, 0, 1, \":\", &gc));\n        }\n        msg(msglevel, \"%s\", BSTR(&out));\n    }\n\n    if (rgi6 && (rgi6->flags & RGI_ADDR_DEFINED))\n    {\n        struct buffer out = alloc_buf_gc(256, &gc);\n        buf_printf(&out, \"ROUTE6_GATEWAY\");\n        buf_printf(&out, \" %s\", print_in6_addr(rgi6->gateway.addr_ipv6, 0, &gc));\n        if (rgi6->flags & RGI_ON_LINK)\n        {\n            buf_printf(&out, \" ON_LINK\");\n        }\n        if (rgi6->flags & RGI_NETMASK_DEFINED)\n        {\n            buf_printf(&out, \"/%d\", rgi6->gateway.netbits_ipv6);\n        }\n#ifdef _WIN32\n        if (rgi6->flags & RGI_IFACE_DEFINED)\n        {\n            buf_printf(&out, \" I=%lu\", rgi6->adapter_index);\n        }\n#else\n        if (rgi6->flags & RGI_IFACE_DEFINED)\n        {\n            buf_printf(&out, \" IFACE=%s\", rgi6->iface);\n        }\n#endif\n        if (rgi6->flags & RGI_HWADDR_DEFINED)\n        {\n            buf_printf(&out, \" HWADDR=%s\", format_hex_ex(rgi6->hwaddr, 6, 0, 1, \":\", &gc));\n        }\n        msg(msglevel, \"%s\", BSTR(&out));\n    }\n    gc_free(&gc);\n}\n\n#endif /* ifndef ENABLE_SMALL */\n\nstatic void\nprint_route(const struct route_ipv4 *r, msglvl_t msglevel)\n{\n    struct gc_arena gc = gc_new();\n    if (r->flags & RT_DEFINED)\n    {\n        msg(msglevel, \"%s\", route_string(r, &gc));\n    }\n    gc_free(&gc);\n}\n\nvoid\nprint_routes(const struct route_list *rl, msglvl_t msglevel)\n{\n    struct route_ipv4 *r;\n    for (r = rl->routes; r; r = r->next)\n    {\n        print_route(r, msglevel);\n    }\n}\n\nstatic void\nsetenv_route(struct env_set *es, const struct route_ipv4 *r, int i)\n{\n    struct gc_arena gc = gc_new();\n    if (r->flags & RT_DEFINED)\n    {\n        setenv_route_addr(es, \"network\", r->network, i);\n        setenv_route_addr(es, \"netmask\", r->netmask, i);\n        setenv_route_addr(es, \"gateway\", r->gateway, i);\n\n        if (r->flags & RT_METRIC_DEFINED)\n        {\n            struct buffer name = alloc_buf_gc(256, &gc);\n            buf_printf(&name, \"route_metric_%d\", i);\n            setenv_int(es, BSTR(&name), r->metric);\n        }\n    }\n    gc_free(&gc);\n}\n\nvoid\nsetenv_routes(struct env_set *es, const struct route_list *rl)\n{\n    int i = 1;\n    struct route_ipv4 *r;\n    for (r = rl->routes; r; r = r->next)\n    {\n        setenv_route(es, r, i++);\n    }\n}\n\nstatic void\nsetenv_route_ipv6(struct env_set *es, const struct route_ipv6 *r6, int i)\n{\n    struct gc_arena gc = gc_new();\n    if (r6->flags & RT_DEFINED)\n    {\n        struct buffer name1 = alloc_buf_gc(256, &gc);\n        struct buffer val = alloc_buf_gc(256, &gc);\n        struct buffer name2 = alloc_buf_gc(256, &gc);\n\n        buf_printf(&name1, \"route_ipv6_network_%d\", i);\n        buf_printf(&val, \"%s/%d\", print_in6_addr(r6->network, 0, &gc), r6->netbits);\n        setenv_str(es, BSTR(&name1), BSTR(&val));\n\n        buf_printf(&name2, \"route_ipv6_gateway_%d\", i);\n        setenv_str(es, BSTR(&name2), print_in6_addr(r6->gateway, 0, &gc));\n\n        if (r6->flags & RT_METRIC_DEFINED)\n        {\n            struct buffer name3 = alloc_buf_gc(256, &gc);\n            buf_printf(&name3, \"route_ipv6_metric_%d\", i);\n            setenv_int(es, BSTR(&name3), r6->metric);\n        }\n    }\n    gc_free(&gc);\n}\nvoid\nsetenv_routes_ipv6(struct env_set *es, const struct route_ipv6_list *rl6)\n{\n    int i = 1;\n    struct route_ipv6 *r6;\n    for (r6 = rl6->routes_ipv6; r6; r6 = r6->next)\n    {\n        setenv_route_ipv6(es, r6, i++);\n    }\n}\n\n/*\n * local_route() determines whether the gateway of a provided host\n * route is on the same interface that owns the default gateway.\n * It uses the data structure\n * returned by get_default_gateway() (struct route_gateway_info)\n * to determine this.  If the route is local, LR_MATCH is returned.\n * When adding routes into the kernel, if LR_MATCH is defined for\n * a given route, the route should explicitly reference the default\n * gateway interface as the route destination.  For example, here\n * is an example on Linux that uses LR_MATCH:\n *\n *   route add -net 10.10.0.1 netmask 255.255.255.255 dev eth0\n *\n * This capability is needed by the \"default-gateway block-local\"\n * directive, to allow client access to the local subnet to be\n * blocked but still allow access to the local default gateway.\n */\n\n/* local_route() return values */\n#define LR_NOMATCH 0 /* route is not local */\n#define LR_MATCH   1 /* route is local */\n#define LR_ERROR   2 /* caller should abort adding route */\n\nstatic int\nlocal_route(in_addr_t network, in_addr_t netmask, in_addr_t gateway,\n            const struct route_gateway_info *rgi)\n{\n    /* set LR_MATCH on local host routes */\n    const unsigned int rgi_needed = (RGI_ADDR_DEFINED | RGI_NETMASK_DEFINED | RGI_IFACE_DEFINED);\n    if (rgi && (rgi->flags & rgi_needed) == rgi_needed && gateway == rgi->gateway.addr\n        && netmask == 0xFFFFFFFF)\n    {\n        if (((network ^ rgi->gateway.addr) & rgi->gateway.netmask) == 0)\n        {\n            return LR_MATCH;\n        }\n        else\n        {\n            /* examine additional subnets on gateway interface */\n            for (int i = 0; i < rgi->n_addrs; ++i)\n            {\n                const struct route_gateway_address *gwa = &rgi->addrs[i];\n                if (((network ^ gwa->addr) & gwa->netmask) == 0)\n                {\n                    return LR_MATCH;\n                }\n            }\n        }\n    }\n    return LR_NOMATCH;\n}\n\n/* Return true if the \"on-link\" form of the route should be used.  This is when the gateway for\n * a route is specified as an interface rather than an address. */\n#if defined(TARGET_LINUX) || defined(_WIN32) || defined(TARGET_DARWIN)\nstatic inline bool\nis_on_link(const int is_local_route, const unsigned int flags, const struct route_gateway_info *rgi)\n{\n    return rgi\n           && (is_local_route == LR_MATCH\n               || ((flags & ROUTE_REF_GW) && (rgi->flags & RGI_ON_LINK)));\n}\n#endif\n\nbool\nadd_route(struct route_ipv4 *r, const struct tuntap *tt, unsigned int flags,\n          const struct route_gateway_info *rgi, /* may be NULL */\n          const struct env_set *es, openvpn_net_ctx_t *ctx)\n{\n    int status = 0;\n    int is_local_route;\n\n    if (!(r->flags & RT_DEFINED))\n    {\n        return true; /* no error */\n    }\n\n    struct argv argv = argv_new();\n    struct gc_arena gc = gc_new();\n\n#if !defined(TARGET_LINUX)\n    const char *network = print_in_addr_t(r->network, 0, &gc);\n#if !defined(TARGET_AIX)\n    const char *netmask = print_in_addr_t(r->netmask, 0, &gc);\n#endif\n    const char *gateway = print_in_addr_t(r->gateway, 0, &gc);\n#endif\n\n    is_local_route = local_route(r->network, r->netmask, r->gateway, rgi);\n    if (is_local_route == LR_ERROR)\n    {\n        goto done;\n    }\n\n#if defined(TARGET_LINUX)\n    const char *iface = NULL;\n    int metric = -1;\n\n    if (is_on_link(is_local_route, flags, rgi))\n    {\n        iface = rgi->iface;\n    }\n\n    if (r->flags & RT_METRIC_DEFINED)\n    {\n        metric = r->metric;\n    }\n\n\n    status = RTA_SUCCESS;\n    int ret = net_route_v4_add(ctx, &r->network, netmask_to_netbits2(r->netmask), &r->gateway,\n                               iface, r->table_id, metric);\n    if (ret == -EEXIST)\n    {\n        msg(D_ROUTE, \"NOTE: Linux route add command failed because route exists\");\n        status = RTA_EEXIST;\n    }\n    else if (ret < 0)\n    {\n        msg(M_WARN, \"ERROR: Linux route add command failed\");\n        status = RTA_ERROR;\n    }\n\n#elif defined(TARGET_ANDROID)\n    char out[128];\n\n    if (rgi)\n    {\n        snprintf(out, sizeof(out), \"%s %s %s dev %s\", network, netmask, gateway, rgi->iface);\n    }\n    else\n    {\n        snprintf(out, sizeof(out), \"%s %s %s\", network, netmask, gateway);\n    }\n    bool ret = management_android_control(management, \"ROUTE\", out);\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#elif defined(_WIN32)\n    {\n        DWORD ai = TUN_ADAPTER_INDEX_INVALID;\n        argv_printf(&argv, \"%s%s ADD %s MASK %s %s\", get_win_sys_path(), WIN_ROUTE_PATH_SUFFIX,\n                    network, netmask, gateway);\n        if (r->flags & RT_METRIC_DEFINED)\n        {\n            argv_printf_cat(&argv, \"METRIC %d\", r->metric);\n        }\n        if (is_on_link(is_local_route, flags, rgi))\n        {\n            ai = rgi->adapter_index;\n            argv_printf_cat(&argv, \"IF %lu\", ai);\n        }\n\n        argv_msg(D_ROUTE, &argv);\n\n        const char *method = \"service\";\n        if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_SERVICE)\n        {\n            status = add_route_service(r, tt);\n        }\n        else if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_IPAPI)\n        {\n            status = add_route_ipapi(r, tt, ai);\n            method = \"ipapi\";\n        }\n        else if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_EXE)\n        {\n            netcmd_semaphore_lock();\n            bool ret =\n                openvpn_execve_check(&argv, es, 0, \"ERROR: Windows route add command failed\");\n            status = ret ? RTA_SUCCESS : RTA_ERROR;\n            netcmd_semaphore_release();\n            method = \"route.exe\";\n        }\n        else if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_ADAPTIVE)\n        {\n            status = add_route_ipapi(r, tt, ai);\n            method = \"ipapi [adaptive]\";\n            if (status == RTA_ERROR)\n            {\n                msg(D_ROUTE, \"Route addition fallback to route.exe\");\n                netcmd_semaphore_lock();\n                bool ret = openvpn_execve_check(\n                    &argv, es, 0, \"ERROR: Windows route add command failed [adaptive]\");\n                status = ret ? RTA_SUCCESS : RTA_ERROR;\n                netcmd_semaphore_release();\n                method = \"route.exe\";\n            }\n        }\n        else\n        {\n            ASSERT(0);\n        }\n        if (status != RTA_ERROR) /* error is logged upstream */\n        {\n            msg(D_ROUTE, \"Route addition via %s %s\", method,\n                (status == RTA_SUCCESS) ? \"succeeded\" : \"failed because route exists\");\n        }\n    }\n\n#elif defined(TARGET_SOLARIS)\n\n    /* example: route add 192.0.2.32 -netmask 255.255.255.224 somegateway */\n\n    argv_printf(&argv, \"%s add\", ROUTE_PATH);\n\n    argv_printf_cat(&argv, \"%s -netmask %s %s\", network, netmask, gateway);\n\n    /* Solaris can only distinguish between \"metric 0\" == \"on-link on the\n     * interface where the IP address given is configured\" and \"metric > 0\"\n     * == \"use gateway specified\" (no finer-grained route metrics available)\n     *\n     * More recent versions of Solaris can also do \"-interface\", but that\n     * would break backwards compatibility with older versions for no gain.\n     */\n    if (r->flags & RT_METRIC_DEFINED)\n    {\n        argv_printf_cat(&argv, \"%d\", r->metric);\n    }\n\n    argv_msg(D_ROUTE, &argv);\n    bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: Solaris route add command failed\");\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#elif defined(TARGET_FREEBSD)\n\n    argv_printf(&argv, \"%s add\", ROUTE_PATH);\n\n#if 0\n    if (r->flags & RT_METRIC_DEFINED)\n    {\n        argv_printf_cat(&argv, \"-rtt %d\", r->metric);\n    }\n#endif\n\n    argv_printf_cat(&argv, \"-net %s %s %s\", network, gateway, netmask);\n\n    /* FIXME -- add on-link support for FreeBSD */\n\n    argv_msg(D_ROUTE, &argv);\n    bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: FreeBSD route add command failed\");\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#elif defined(TARGET_DRAGONFLY)\n\n    argv_printf(&argv, \"%s add\", ROUTE_PATH);\n\n#if 0\n    if (r->flags & RT_METRIC_DEFINED)\n    {\n        argv_printf_cat(&argv, \"-rtt %d\", r->metric);\n    }\n#endif\n\n    argv_printf_cat(&argv, \"-net %s %s %s\", network, gateway, netmask);\n\n    /* FIXME -- add on-link support for Dragonfly */\n\n    argv_msg(D_ROUTE, &argv);\n    bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: DragonFly route add command failed\");\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#elif defined(TARGET_DARWIN)\n\n    argv_printf(&argv, \"%s add\", ROUTE_PATH);\n\n#if 0\n    if (r->flags & RT_METRIC_DEFINED)\n    {\n        argv_printf_cat(&argv, \"-rtt %d\", r->metric);\n    }\n#endif\n\n    if (is_on_link(is_local_route, flags, rgi))\n    {\n        /* Mac OS X route syntax for ON_LINK:\n         * route add -cloning -net 10.10.0.1 -netmask 255.255.255.255 -interface en0 */\n        argv_printf_cat(&argv, \"-cloning -net %s -netmask %s -interface %s\", network, netmask,\n                        rgi->iface);\n    }\n    else\n    {\n        argv_printf_cat(&argv, \"-net %s %s %s\", network, gateway, netmask);\n    }\n\n    argv_msg(D_ROUTE, &argv);\n    bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: OS X route add command failed\");\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#elif defined(TARGET_OPENBSD) || defined(TARGET_NETBSD)\n\n    argv_printf(&argv, \"%s add\", ROUTE_PATH);\n\n#if 0\n    if (r->flags & RT_METRIC_DEFINED)\n    {\n        argv_printf_cat(&argv, \"-rtt %d\", r->metric);\n    }\n#endif\n\n    argv_printf_cat(&argv, \"-net %s %s -netmask %s\", network, gateway, netmask);\n\n    /* FIXME -- add on-link support for OpenBSD/NetBSD */\n\n    argv_msg(D_ROUTE, &argv);\n    bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: OpenBSD/NetBSD route add command failed\");\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#elif defined(TARGET_AIX)\n\n    {\n        int netbits = netmask_to_netbits2(r->netmask);\n        argv_printf(&argv, \"%s add -net %s/%d %s\", ROUTE_PATH, network, netbits, gateway);\n        argv_msg(D_ROUTE, &argv);\n        bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: AIX route add command failed\");\n        status = ret ? RTA_SUCCESS : RTA_ERROR;\n    }\n\n#elif defined(TARGET_HAIKU)\n\n    /* ex: route add /dev/net/ipro1000/0 0.0.0.0 gw 192.168.1.1 netmask 128.0.0.0 */\n    argv_printf(&argv, \"%s add %s inet %s gw %s netmask %s\", ROUTE_PATH, rgi->iface, network,\n                gateway, netmask);\n    argv_msg(D_ROUTE, &argv);\n    bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: Haiku inet route add command failed\");\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#else  /* if defined(TARGET_LINUX) */\n    msg(M_FATAL,\n        \"Sorry, but I don't know how to do 'route' commands on this operating system.  Try putting your routes in a --route-up script\");\n#endif /* if defined(TARGET_LINUX) */\n\ndone:\n    if (status == RTA_SUCCESS)\n    {\n        r->flags |= RT_ADDED;\n    }\n    else\n    {\n        r->flags &= ~RT_ADDED;\n    }\n    argv_free(&argv);\n    gc_free(&gc);\n    /* release resources potentially allocated during route setup */\n    net_ctx_reset(ctx);\n\n    return (status != RTA_ERROR);\n}\n\nvoid\nroute_ipv6_clear_host_bits(struct route_ipv6 *r6)\n{\n    /* clear host bit parts of route\n     * (needed if routes are specified improperly, or if we need to\n     * explicitly setup/clear the \"connected\" network routes on some OSes)\n     */\n    int byte = 15;\n    int bits_to_clear = 128 - r6->netbits;\n\n    while (byte >= 0 && bits_to_clear > 0)\n    {\n        if (bits_to_clear >= 8)\n        {\n            r6->network.s6_addr[byte--] = 0;\n            bits_to_clear -= 8;\n        }\n        else\n        {\n            r6->network.s6_addr[byte--] &= (0xff << bits_to_clear);\n            bits_to_clear = 0;\n        }\n    }\n}\n\nbool\nadd_route_ipv6(struct route_ipv6 *r6, const struct tuntap *tt, unsigned int flags,\n               const struct env_set *es, openvpn_net_ctx_t *ctx)\n{\n    int status = 0;\n    bool gateway_needed = false;\n\n    if (!(r6->flags & RT_DEFINED))\n    {\n        return true; /* no error */\n    }\n\n    struct argv argv = argv_new();\n    struct gc_arena gc = gc_new();\n\n#ifndef _WIN32\n    const char *device = tt->actual_name;\n    if (r6->iface != NULL) /* vpn server special route */\n    {\n        device = r6->iface;\n        if (!IN6_IS_ADDR_UNSPECIFIED(&r6->gateway))\n        {\n            gateway_needed = true;\n        }\n    }\n#endif\n\n    route_ipv6_clear_host_bits(r6);\n    const char *network = print_in6_addr(r6->network, 0, &gc);\n    const char *gateway = print_in6_addr(r6->gateway, 0, &gc);\n\n#if defined(TARGET_DARWIN) || defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY) \\\n    || defined(TARGET_OPENBSD) || defined(TARGET_NETBSD)\n\n    /* the BSD platforms cannot specify gateway and interface independently,\n     * but for link-local destinations, we MUST specify the interface, so\n     * we build a combined \"$gateway%$interface\" gateway string\n     */\n    if (r6->iface != NULL && gateway_needed\n        && IN6_IS_ADDR_LINKLOCAL(&r6->gateway)) /* fe80::...%intf */\n    {\n        size_t len = strlen(gateway) + 1 + strlen(r6->iface) + 1;\n        char *tmp = gc_malloc(len, true, &gc);\n        snprintf(tmp, len, \"%s%%%s\", gateway, r6->iface);\n        gateway = tmp;\n    }\n#endif\n\n#ifndef _WIN32\n    msg(D_ROUTE, \"add_route_ipv6(%s/%d -> %s metric %d) dev %s\", network, r6->netbits, gateway,\n        r6->metric, device);\n#else\n    msg(D_ROUTE, \"add_route_ipv6(%s/%d -> %s metric %d) IF %lu\", network, r6->netbits, gateway,\n        r6->metric, r6->adapter_index ? r6->adapter_index : tt->adapter_index);\n#endif\n\n    /*\n     * Filter out routes which are essentially no-ops\n     * (not currently done for IPv6)\n     */\n\n    /* On \"tun\" interface, we never set a gateway if the operating system\n     * can do \"route to interface\" - it does not add value, as the target\n     * dev already fully qualifies the route destination on point-to-point\n     * interfaces.   OTOH, on \"tap\" interface, we must always set the\n     * gateway unless the route is to be an on-link network\n     */\n    if (tt->type == DEV_TYPE_TAP && !((r6->flags & RT_METRIC_DEFINED) && r6->metric == 0))\n    {\n        gateway_needed = true;\n    }\n\n    if (gateway_needed && IN6_IS_ADDR_UNSPECIFIED(&r6->gateway))\n    {\n        msg(M_WARN,\n            \"ROUTE6 WARNING: \" PACKAGE_NAME \" needs a gateway \"\n            \"parameter for a --route-ipv6 option and no default was set via \"\n            \"--ifconfig-ipv6 or --route-ipv6-gateway option.  Not installing \"\n            \"IPv6 route to %s/%d.\",\n            network, r6->netbits);\n        status = 0;\n        goto done;\n    }\n\n#if defined(TARGET_LINUX)\n    int metric = -1;\n    if ((r6->flags & RT_METRIC_DEFINED) && (r6->metric > 0))\n    {\n        metric = r6->metric;\n    }\n\n    status = RTA_SUCCESS;\n    int ret = net_route_v6_add(ctx, &r6->network, r6->netbits, gateway_needed ? &r6->gateway : NULL,\n                               device, r6->table_id, metric);\n    if (ret == -EEXIST)\n    {\n        msg(D_ROUTE, \"NOTE: Linux route add command failed because route exists\");\n        status = RTA_EEXIST;\n    }\n    else if (ret < 0)\n    {\n        msg(M_WARN, \"ERROR: Linux route add command failed\");\n        status = RTA_ERROR;\n    }\n\n#elif defined(TARGET_ANDROID)\n    char out[64];\n\n    snprintf(out, sizeof(out), \"%s/%d %s\", network, r6->netbits, device);\n\n    status = management_android_control(management, \"ROUTE6\", out);\n\n#elif defined(_WIN32)\n\n    if (tt->options.msg_channel)\n    {\n        status = add_route_ipv6_service(r6, tt);\n    }\n    else\n    {\n        status = route_ipv6_ipapi(true, r6, tt);\n    }\n#elif defined(TARGET_SOLARIS)\n\n    /* example: route add -inet6 2001:db8::/32 somegateway 0 */\n\n    /* for some reason, routes to tun/tap do not work for me unless I set\n     * \"metric 0\" - otherwise, the routes will be nicely installed, but\n     * packets will just disappear somewhere.  So we always use \"0\" now,\n     * unless the route points to \"gateway on other interface\"...\n     *\n     * (Note: OpenSolaris can not specify host%interface gateways, so we just\n     * use the GW addresses - it seems to still work for fe80:: addresses,\n     * however this is done internally.  NUD maybe?)\n     */\n    argv_printf(&argv, \"%s add -inet6 %s/%d %s\", ROUTE_PATH, network, r6->netbits, gateway);\n\n    /* on tun (not tap), not \"elsewhere\"? -> metric 0 */\n    if (tt->type == DEV_TYPE_TUN && !r6->iface)\n    {\n        argv_printf_cat(&argv, \"0\");\n    }\n\n    argv_msg(D_ROUTE, &argv);\n    bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: Solaris route add -inet6 command failed\");\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#elif defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY)\n\n    argv_printf(&argv, \"%s add -inet6 %s/%d\", ROUTE_PATH, network, r6->netbits);\n\n    if (gateway_needed)\n    {\n        argv_printf_cat(&argv, \"%s\", gateway);\n    }\n    else\n    {\n        argv_printf_cat(&argv, \"-iface %s\", device);\n    }\n\n    argv_msg(D_ROUTE, &argv);\n    bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: *BSD route add -inet6 command failed\");\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#elif defined(TARGET_DARWIN)\n\n    argv_printf(&argv, \"%s add -inet6 %s -prefixlen %d\", ROUTE_PATH, network, r6->netbits);\n\n    if (gateway_needed)\n    {\n        argv_printf_cat(&argv, \"%s\", gateway);\n    }\n    else\n    {\n        argv_printf_cat(&argv, \"-iface %s\", device);\n    }\n\n    argv_msg(D_ROUTE, &argv);\n    bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: MacOS X route add -inet6 command failed\");\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#elif defined(TARGET_OPENBSD)\n\n    argv_printf(&argv, \"%s add -inet6 %s -prefixlen %d %s\", ROUTE_PATH, network, r6->netbits,\n                gateway);\n\n    argv_msg(D_ROUTE, &argv);\n    bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: OpenBSD route add -inet6 command failed\");\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#elif defined(TARGET_NETBSD)\n\n    argv_printf(&argv, \"%s add -inet6 %s/%d %s\", ROUTE_PATH, network, r6->netbits, gateway);\n\n    argv_msg(D_ROUTE, &argv);\n    bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: NetBSD route add -inet6 command failed\");\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#elif defined(TARGET_AIX)\n\n    argv_printf(&argv, \"%s add -inet6 %s/%d %s\", ROUTE_PATH, network, r6->netbits, gateway);\n    argv_msg(D_ROUTE, &argv);\n    bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: AIX route add command failed\");\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#elif defined(TARGET_HAIKU)\n\n    /* ex: route add /dev/net/ipro1000/0 inet6 :: gw beef::cafe prefixlen 64 */\n    argv_printf(&argv, \"%s add %s inet6 %s gw %s prefixlen %d\", ROUTE_PATH, r6->iface, network,\n                gateway, r6->netbits);\n    argv_msg(D_ROUTE, &argv);\n    bool ret = openvpn_execve_check(&argv, es, 0, \"ERROR: Haiku inet6 route add command failed\");\n    status = ret ? RTA_SUCCESS : RTA_ERROR;\n\n#else  /* if defined(TARGET_LINUX) */\n    msg(M_FATAL,\n        \"Sorry, but I don't know how to do 'route ipv6' commands on this operating system.  Try putting your routes in a --route-up script\");\n#endif /* if defined(TARGET_LINUX) */\n\ndone:\n    if (status == RTA_SUCCESS)\n    {\n        r6->flags |= RT_ADDED;\n    }\n    else\n    {\n        r6->flags &= ~RT_ADDED;\n    }\n    argv_free(&argv);\n    gc_free(&gc);\n    /* release resources potentially allocated during route setup */\n    net_ctx_reset(ctx);\n\n    return (status != RTA_ERROR);\n}\n\nstatic void\ndelete_route(struct route_ipv4 *r, const struct tuntap *tt, unsigned int flags,\n             const struct route_gateway_info *rgi, const struct env_set *es, openvpn_net_ctx_t *ctx)\n{\n#if !defined(TARGET_LINUX)\n    const char *network;\n#if !defined(TARGET_AIX)\n    const char *netmask;\n#endif\n    const char *gateway;\n#else /* if !defined(TARGET_LINUX) */\n    int metric;\n#endif\n    int is_local_route;\n\n    if ((r->flags & (RT_DEFINED | RT_ADDED)) != (RT_DEFINED | RT_ADDED))\n    {\n        return;\n    }\n\n    struct gc_arena gc = gc_new();\n    struct argv argv = argv_new();\n\n#if !defined(TARGET_LINUX)\n    network = print_in_addr_t(r->network, 0, &gc);\n#if !defined(TARGET_AIX)\n    netmask = print_in_addr_t(r->netmask, 0, &gc);\n#endif\n    gateway = print_in_addr_t(r->gateway, 0, &gc);\n#endif\n\n    is_local_route = local_route(r->network, r->netmask, r->gateway, rgi);\n    if (is_local_route == LR_ERROR)\n    {\n        goto done;\n    }\n\n#if defined(TARGET_LINUX)\n    metric = -1;\n    if (r->flags & RT_METRIC_DEFINED)\n    {\n        metric = r->metric;\n    }\n\n    if (net_route_v4_del(ctx, &r->network, netmask_to_netbits2(r->netmask), &r->gateway, NULL,\n                         r->table_id, metric)\n        < 0)\n    {\n        msg(M_WARN, \"ERROR: Linux route delete command failed\");\n    }\n#elif defined(_WIN32)\n\n    argv_printf(&argv, \"%s%s DELETE %s MASK %s %s\", get_win_sys_path(), WIN_ROUTE_PATH_SUFFIX,\n                network, netmask, gateway);\n\n    argv_msg(D_ROUTE, &argv);\n\n    if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_SERVICE)\n    {\n        const bool status = del_route_service(r, tt);\n        msg(D_ROUTE, \"Route deletion via service %s\", status ? \"succeeded\" : \"failed\");\n    }\n    else if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_IPAPI)\n    {\n        const bool status = del_route_ipapi(r, tt);\n        msg(D_ROUTE, \"Route deletion via IPAPI %s\", status ? \"succeeded\" : \"failed\");\n    }\n    else if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_EXE)\n    {\n        netcmd_semaphore_lock();\n        openvpn_execve_check(&argv, es, 0, \"ERROR: Windows route delete command failed\");\n        netcmd_semaphore_release();\n    }\n    else if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_ADAPTIVE)\n    {\n        const bool status = del_route_ipapi(r, tt);\n        msg(D_ROUTE, \"Route deletion via IPAPI %s [adaptive]\", status ? \"succeeded\" : \"failed\");\n        if (!status)\n        {\n            msg(D_ROUTE, \"Route deletion fallback to route.exe\");\n            netcmd_semaphore_lock();\n            openvpn_execve_check(&argv, es, 0,\n                                 \"ERROR: Windows route delete command failed [adaptive]\");\n            netcmd_semaphore_release();\n        }\n    }\n    else\n    {\n        ASSERT(0);\n    }\n\n#elif defined(TARGET_SOLARIS)\n\n    argv_printf(&argv, \"%s delete %s -netmask %s %s\", ROUTE_PATH, network, netmask, gateway);\n\n    argv_msg(D_ROUTE, &argv);\n    openvpn_execve_check(&argv, es, 0, \"ERROR: Solaris route delete command failed\");\n\n#elif defined(TARGET_FREEBSD)\n\n    argv_printf(&argv, \"%s delete -net %s %s %s\", ROUTE_PATH, network, gateway, netmask);\n\n    argv_msg(D_ROUTE, &argv);\n    openvpn_execve_check(&argv, es, 0, \"ERROR: FreeBSD route delete command failed\");\n\n#elif defined(TARGET_DRAGONFLY)\n\n    argv_printf(&argv, \"%s delete -net %s %s %s\", ROUTE_PATH, network, gateway, netmask);\n\n    argv_msg(D_ROUTE, &argv);\n    openvpn_execve_check(&argv, es, 0, \"ERROR: DragonFly route delete command failed\");\n\n#elif defined(TARGET_DARWIN)\n\n    if (is_on_link(is_local_route, flags, rgi))\n    {\n        argv_printf(&argv, \"%s delete -cloning -net %s -netmask %s -interface %s\", ROUTE_PATH,\n                    network, netmask, rgi->iface);\n    }\n    else\n    {\n        argv_printf(&argv, \"%s delete -net %s %s %s\", ROUTE_PATH, network, gateway, netmask);\n    }\n\n    argv_msg(D_ROUTE, &argv);\n    openvpn_execve_check(&argv, es, 0, \"ERROR: OS X route delete command failed\");\n\n#elif defined(TARGET_OPENBSD) || defined(TARGET_NETBSD)\n\n    argv_printf(&argv, \"%s delete -net %s %s -netmask %s\", ROUTE_PATH, network, gateway, netmask);\n\n    argv_msg(D_ROUTE, &argv);\n    openvpn_execve_check(&argv, es, 0, \"ERROR: OpenBSD/NetBSD route delete command failed\");\n\n#elif defined(TARGET_ANDROID)\n    /* Avoids the unused variables warnings that all other platforms use\n     * by adding them to the error message. */\n    msg(D_ROUTE_DEBUG, \"Deleting routes on Android is not possible/not \"\n                       \"needed. The VpnService API allows routes to be set \"\n                       \"on connect only and will clean up automatically. \"\n                       \"Tried to delete route %s netmask %s gateway %s\",\n        network, netmask, gateway);\n#elif defined(TARGET_AIX)\n\n    {\n        int netbits = netmask_to_netbits2(r->netmask);\n        argv_printf(&argv, \"%s delete -net %s/%d %s\", ROUTE_PATH, network, netbits, gateway);\n        argv_msg(D_ROUTE, &argv);\n        openvpn_execve_check(&argv, es, 0, \"ERROR: AIX route delete command failed\");\n    }\n\n#elif defined(TARGET_HAIKU)\n\n    /* ex: route delete /dev/net/ipro1000/0 inet 192.168.0.0 gw 192.168.1.1 netmask 255.255.0.0 */\n    argv_printf(&argv, \"%s delete %s inet %s gw %s netmask %s\", ROUTE_PATH, rgi->iface, network,\n                gateway, netmask);\n    argv_msg(D_ROUTE, &argv);\n    openvpn_execve_check(&argv, es, 0, \"ERROR: Haiku inet route delete command failed\");\n\n#else  /* if defined(TARGET_LINUX) */\n    msg(M_FATAL,\n        \"Sorry, but I don't know how to do 'route' commands on this operating system.  Try putting your routes in a --route-up script\");\n#endif /* if defined(TARGET_LINUX) */\n\ndone:\n    r->flags &= ~RT_ADDED;\n    argv_free(&argv);\n    gc_free(&gc);\n    /* release resources potentially allocated during route cleanup */\n    net_ctx_reset(ctx);\n}\n\nvoid\ndelete_route_ipv6(const struct route_ipv6 *r6, const struct tuntap *tt, const struct env_set *es,\n                  openvpn_net_ctx_t *ctx)\n{\n    const char *network;\n\n    if ((r6->flags & (RT_DEFINED | RT_ADDED)) != (RT_DEFINED | RT_ADDED))\n    {\n        return;\n    }\n\n#if !defined(_WIN32)\n#if !defined(TARGET_LINUX)\n    const char *gateway;\n#endif\n#if !defined(TARGET_SOLARIS)\n    bool gateway_needed = false;\n    const char *device = tt->actual_name;\n    if (r6->iface != NULL) /* vpn server special route */\n    {\n        device = r6->iface;\n        gateway_needed = true;\n    }\n    (void)device; /* unused on some platforms */\n\n    /* if we used a gateway on \"add route\", we also need to specify it on\n     * delete, otherwise some OSes will refuse to delete the route\n     */\n    if (tt->type == DEV_TYPE_TAP && !((r6->flags & RT_METRIC_DEFINED) && r6->metric == 0))\n    {\n        gateway_needed = true;\n    }\n#endif\n#endif\n\n    struct gc_arena gc = gc_new();\n    struct argv argv = argv_new();\n\n    network = print_in6_addr(r6->network, 0, &gc);\n#if !defined(TARGET_LINUX) && !defined(_WIN32)\n    gateway = print_in6_addr(r6->gateway, 0, &gc);\n#endif\n\n#if defined(TARGET_DARWIN) || defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY) \\\n    || defined(TARGET_OPENBSD) || defined(TARGET_NETBSD)\n\n    /* the BSD platforms cannot specify gateway and interface independently,\n     * but for link-local destinations, we MUST specify the interface, so\n     * we build a combined \"$gateway%$interface\" gateway string\n     */\n    if (r6->iface != NULL && gateway_needed\n        && IN6_IS_ADDR_LINKLOCAL(&r6->gateway)) /* fe80::...%intf */\n    {\n        size_t len = strlen(gateway) + 1 + strlen(r6->iface) + 1;\n        char *tmp = gc_malloc(len, true, &gc);\n        snprintf(tmp, len, \"%s%%%s\", gateway, r6->iface);\n        gateway = tmp;\n    }\n#endif\n\n    msg(D_ROUTE, \"delete_route_ipv6(%s/%d)\", network, r6->netbits);\n\n#if defined(TARGET_LINUX)\n    int metric = -1;\n    if ((r6->flags & RT_METRIC_DEFINED) && (r6->metric > 0))\n    {\n        metric = r6->metric;\n    }\n\n    if (net_route_v6_del(ctx, &r6->network, r6->netbits, gateway_needed ? &r6->gateway : NULL,\n                         device, r6->table_id, metric)\n        < 0)\n    {\n        msg(M_WARN, \"ERROR: Linux route v6 delete command failed\");\n    }\n\n#elif defined(_WIN32)\n\n    if (tt->options.msg_channel)\n    {\n        del_route_ipv6_service(r6, tt);\n    }\n    else\n    {\n        route_ipv6_ipapi(false, r6, tt);\n    }\n#elif defined(TARGET_SOLARIS)\n\n    /* example: route delete -inet6 2001:db8::/32 somegateway */\n\n    argv_printf(&argv, \"%s delete -inet6 %s/%d %s\", ROUTE_PATH, network, r6->netbits, gateway);\n\n    argv_msg(D_ROUTE, &argv);\n    openvpn_execve_check(&argv, es, 0, \"ERROR: Solaris route delete -inet6 command failed\");\n\n#elif defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY)\n\n    argv_printf(&argv, \"%s delete -inet6 %s/%d\", ROUTE_PATH, network, r6->netbits);\n\n    if (gateway_needed)\n    {\n        argv_printf_cat(&argv, \"%s\", gateway);\n    }\n    else\n    {\n        argv_printf_cat(&argv, \"-iface %s\", device);\n    }\n\n    argv_msg(D_ROUTE, &argv);\n    openvpn_execve_check(&argv, es, 0, \"ERROR: *BSD route delete -inet6 command failed\");\n\n#elif defined(TARGET_DARWIN)\n\n    argv_printf(&argv, \"%s delete -inet6 %s -prefixlen %d\", ROUTE_PATH, network, r6->netbits);\n\n    if (gateway_needed)\n    {\n        argv_printf_cat(&argv, \"%s\", gateway);\n    }\n    else\n    {\n        argv_printf_cat(&argv, \"-iface %s\", device);\n    }\n\n    argv_msg(D_ROUTE, &argv);\n    openvpn_execve_check(&argv, es, 0, \"ERROR: MacOS X route delete -inet6 command failed\");\n\n#elif defined(TARGET_OPENBSD)\n\n    argv_printf(&argv, \"%s delete -inet6 %s -prefixlen %d %s\", ROUTE_PATH, network, r6->netbits,\n                gateway);\n\n    argv_msg(D_ROUTE, &argv);\n    openvpn_execve_check(&argv, es, 0, \"ERROR: OpenBSD route delete -inet6 command failed\");\n\n#elif defined(TARGET_NETBSD)\n\n    argv_printf(&argv, \"%s delete -inet6 %s/%d %s\", ROUTE_PATH, network, r6->netbits, gateway);\n\n    argv_msg(D_ROUTE, &argv);\n    openvpn_execve_check(&argv, es, 0, \"ERROR: NetBSD route delete -inet6 command failed\");\n\n#elif defined(TARGET_AIX)\n\n    argv_printf(&argv, \"%s delete -inet6 %s/%d %s\", ROUTE_PATH, network, r6->netbits, gateway);\n    argv_msg(D_ROUTE, &argv);\n    openvpn_execve_check(&argv, es, 0, \"ERROR: AIX route add command failed\");\n\n#elif defined(TARGET_ANDROID)\n    msg(D_ROUTE_DEBUG, \"Deleting routes on Android is not possible/not \"\n                       \"needed. The VpnService API allows routes to be set \"\n                       \"on connect only and will clean up automatically. \"\n                       \"Tried to delete %s gateway %s\",\n        network,\n        gateway_needed ? gateway : \"(not needed)\");\n#elif defined(TARGET_HAIKU)\n\n    /* ex: route delete /dev/net/ipro1000/0 inet6 :: gw beef::cafe prefixlen 64 */\n    argv_printf(&argv, \"%s delete %s inet6 %s gw %s prefixlen %d\", ROUTE_PATH, r6->iface, network,\n                gateway, r6->netbits);\n    argv_msg(D_ROUTE, &argv);\n    openvpn_execve_check(&argv, es, 0, \"ERROR: Haiku inet6 route delete command failed\");\n\n#else  /* if defined(TARGET_LINUX) */\n    msg(M_FATAL,\n        \"Sorry, but I don't know how to do 'route ipv6' commands on this operating system.  Try putting your routes in a --route-down script\");\n#endif /* if defined(TARGET_LINUX) */\n\n    argv_free(&argv);\n    gc_free(&gc);\n    /* release resources potentially allocated during route cleanup */\n    net_ctx_reset(ctx);\n}\n\n/*\n * The --redirect-gateway option requires OS-specific code below\n * to get the current default gateway.\n */\n\n#if defined(_WIN32)\n\nstatic const MIB_IPFORWARDTABLE *\nget_windows_routing_table(struct gc_arena *gc)\n{\n    ULONG size = 0;\n    PMIB_IPFORWARDTABLE rt = NULL;\n    DWORD status;\n\n    status = GetIpForwardTable(NULL, &size, TRUE);\n    if (status == ERROR_INSUFFICIENT_BUFFER)\n    {\n        rt = (PMIB_IPFORWARDTABLE)gc_malloc(size, false, gc);\n        status = GetIpForwardTable(rt, &size, TRUE);\n        if (status != NO_ERROR)\n        {\n            msg(D_ROUTE, \"NOTE: GetIpForwardTable returned error: %s (code=%lu)\",\n                strerror_win32(status, gc), status);\n            rt = NULL;\n        }\n    }\n    return rt;\n}\n\nstatic int\ntest_route(const IP_ADAPTER_INFO *adapters, const in_addr_t gateway, DWORD *index)\n{\n    int count = 0;\n    DWORD i = adapter_index_of_ip(adapters, gateway, &count, NULL);\n    if (index)\n    {\n        *index = i;\n    }\n    return count;\n}\n\nstatic void\ntest_route_helper(bool *ret, int *count, int *good, int *ambig, const IP_ADAPTER_INFO *adapters,\n                  const in_addr_t gateway)\n{\n    int c;\n\n    ++*count;\n    c = test_route(adapters, gateway, NULL);\n    if (c == 0)\n    {\n        *ret = false;\n    }\n    else\n    {\n        ++*good;\n    }\n    if (c > 1)\n    {\n        ++*ambig;\n    }\n}\n\n/*\n * If we tried to add routes now, would we succeed?\n */\nbool\ntest_routes(const struct route_list *rl, const struct tuntap *tt)\n{\n    struct gc_arena gc = gc_new();\n    const IP_ADAPTER_INFO *adapters = get_adapter_info_list(&gc);\n    bool ret = false;\n    int count = 0;\n    int good = 0;\n    int ambig = 0;\n    int len = -1;\n    bool adapter_up = false;\n\n    if (is_adapter_up(tt, adapters))\n    {\n        ret = true;\n        adapter_up = true;\n\n        /* we do this test only if we have IPv4 routes to install, and if\n         * the tun/tap interface has seen IPv4 ifconfig - because if we\n         * have no IPv4, the check will always fail, failing tun init\n         */\n        if (rl && tt->did_ifconfig_setup)\n        {\n            struct route_ipv4 *r;\n            for (r = rl->routes, len = 0; r; r = r->next, ++len)\n            {\n                test_route_helper(&ret, &count, &good, &ambig, adapters, r->gateway);\n            }\n\n            if ((rl->flags & RG_ENABLE) && (rl->spec.flags & RTSA_REMOTE_ENDPOINT))\n            {\n                test_route_helper(&ret, &count, &good, &ambig, adapters, rl->spec.remote_endpoint);\n            }\n        }\n    }\n\n    msg(D_ROUTE, \"TEST ROUTES: %d/%d succeeded len=%d ret=%u a=%d u/d=%s\",\n        good, count, len, ret, ambig, adapter_up ? \"up\" : \"down\");\n\n    gc_free(&gc);\n    return ret;\n}\n\nstatic const MIB_IPFORWARDROW *\nget_default_gateway_row(const MIB_IPFORWARDTABLE *routes)\n{\n    struct gc_arena gc = gc_new();\n    DWORD lowest_metric = MAXDWORD;\n    const MIB_IPFORWARDROW *ret = NULL;\n    int best = -1;\n\n    if (routes)\n    {\n        for (DWORD i = 0; i < routes->dwNumEntries; ++i)\n        {\n            const MIB_IPFORWARDROW *row = &routes->table[i];\n            const in_addr_t net = ntohl(row->dwForwardDest);\n            const in_addr_t mask = ntohl(row->dwForwardMask);\n            const DWORD index = row->dwForwardIfIndex;\n            const DWORD metric = row->dwForwardMetric1;\n\n            dmsg(D_ROUTE_DEBUG, \"GDGR: route[%lu] %s/%s i=%lu m=%lu\", i,\n                 print_in_addr_t((in_addr_t)net, 0, &gc), print_in_addr_t((in_addr_t)mask, 0, &gc),\n                 index, metric);\n\n            if (!net && !mask && metric < lowest_metric)\n            {\n                ret = row;\n                lowest_metric = metric;\n                best = i;\n            }\n        }\n    }\n\n    dmsg(D_ROUTE_DEBUG, \"GDGR: best=%d lm=%lu\", best, lowest_metric);\n\n    gc_free(&gc);\n    return ret;\n}\n\n/**\n * @brief Determines the best route to a destination for both IPv4 and IPv6.\n *\n * Uses `GetBestInterfaceEx` and `GetBestRoute2` to find the optimal route\n * and network interface for the specified destination address.\n *\n * @param gc Pointer to struct gc_arena for internal string allocation.\n * @param dest The destination IP address (IPv4 or IPv6).\n * @param best_route Pointer to a `MIB_IPFORWARD_ROW2` structure to store the best route.\n * @return DWORD `NO_ERROR` on success, or an error code.\n */\nstatic DWORD\nget_best_route(struct gc_arena *gc, SOCKADDR_INET *dest, MIB_IPFORWARD_ROW2 *best_route)\n{\n    DWORD best_if_index;\n    DWORD status;\n\n    CLEAR(*best_route);\n\n    /* get the best interface index to reach dest */\n    status = GetBestInterfaceEx((struct sockaddr *)dest, &best_if_index);\n    if (status != NO_ERROR)\n    {\n        msg(D_ROUTE, \"NOTE: GetBestInterfaceEx returned error: %s (code=%lu)\",\n            strerror_win32(status, gc), status);\n        goto done;\n    }\n\n    msg(D_ROUTE_DEBUG, \"GetBestInterfaceEx() returned if=%lu\", best_if_index);\n\n    /* get the routing information (such as NextHop) for the destination and interface */\n    NET_LUID luid;\n    CLEAR(luid);\n    SOCKADDR_INET best_src;\n    CLEAR(best_src);\n    status = GetBestRoute2(&luid, best_if_index, NULL, dest, 0, best_route, &best_src);\n    if (status != NO_ERROR)\n    {\n        msg(D_ROUTE, \"NOTE: GetIpForwardEntry2 returned error: %s (code=%lu)\",\n            strerror_win32(status, gc), status);\n        goto done;\n    }\n\ndone:\n    return status;\n}\n\nvoid\nget_default_gateway(struct route_gateway_info *rgi, in_addr_t dest, openvpn_net_ctx_t *ctx)\n{\n    CLEAR(*rgi);\n\n    struct gc_arena gc = gc_new();\n\n    /* convert in_addr_t into SOCKADDR_INET */\n    SOCKADDR_INET sa;\n    CLEAR(sa);\n    sa.si_family = AF_INET;\n    sa.Ipv4.sin_addr.s_addr = htonl(dest);\n\n    /* get the best route to the destination */\n    MIB_IPFORWARD_ROW2 best_route;\n    CLEAR(best_route);\n    DWORD status = get_best_route(&gc, &sa, &best_route);\n    if (status != NO_ERROR)\n    {\n        goto done;\n    }\n\n    rgi->flags = RGI_ADDR_DEFINED | RGI_IFACE_DEFINED;\n    rgi->gateway.addr = ntohl(best_route.NextHop.Ipv4.sin_addr.S_un.S_addr);\n    rgi->adapter_index = best_route.InterfaceIndex;\n\n    if (rgi->gateway.addr == INADDR_ANY)\n    {\n        rgi->flags |= RGI_ON_LINK;\n    }\n\n    /* get netmask and MAC address */\n    const IP_ADAPTER_INFO *adapters = get_adapter_info_list(&gc);\n    const IP_ADAPTER_INFO *ai = get_adapter(adapters, rgi->adapter_index);\n    if (ai)\n    {\n        memcpy(rgi->hwaddr, ai->Address, 6);\n        rgi->flags |= RGI_HWADDR_DEFINED;\n\n        /* get netmask for non-onlink routes */\n        in_addr_t nm = inet_addr(ai->IpAddressList.IpMask.String);\n        if (!(rgi->flags & RGI_ON_LINK) && (nm != INADDR_NONE))\n        {\n            rgi->gateway.netmask = ntohl(nm);\n            rgi->flags |= RGI_NETMASK_DEFINED;\n        }\n    }\n\ndone:\n    gc_free(&gc);\n}\n\nstatic DWORD\nwindows_route_find_if_index(const struct route_ipv4 *r, const struct tuntap *tt)\n{\n    struct gc_arena gc = gc_new();\n    DWORD ret = TUN_ADAPTER_INDEX_INVALID;\n    int count = 0;\n    const IP_ADAPTER_INFO *adapters = get_adapter_info_list(&gc);\n    const IP_ADAPTER_INFO *tun_adapter = get_tun_adapter(tt, adapters);\n    bool on_tun = false;\n\n    /* first test on tun interface */\n    if (is_ip_in_adapter_subnet(tun_adapter, r->gateway, NULL))\n    {\n        ret = tun_adapter->Index;\n        count = 1;\n        on_tun = true;\n    }\n    else /* test on other interfaces */\n    {\n        count = test_route(adapters, r->gateway, &ret);\n    }\n\n    if (count == 0)\n    {\n        msg(M_WARN, \"Warning: route gateway is not reachable on any active network adapters: %s\",\n            print_in_addr_t(r->gateway, 0, &gc));\n        ret = TUN_ADAPTER_INDEX_INVALID;\n    }\n    else if (count > 1)\n    {\n        msg(M_WARN, \"Warning: route gateway is ambiguous: %s (%d matches)\",\n            print_in_addr_t(r->gateway, 0, &gc), count);\n    }\n\n    dmsg(D_ROUTE_DEBUG, \"DEBUG: route find if: on_tun=%d count=%d index=%lu\",\n         on_tun, count, ret);\n\n    gc_free(&gc);\n    return ret;\n}\n\n/* IPv6 implementation using GetBestRoute2()\n * https://msdn.microsoft.com/en-us/library/windows/desktop/aa365922(v=vs.85).aspx\n * https://msdn.microsoft.com/en-us/library/windows/desktop/aa814411(v=vs.85).aspx\n */\nvoid\nget_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi6, const struct in6_addr *dest,\n                         openvpn_net_ctx_t *ctx)\n{\n    struct gc_arena gc = gc_new();\n    CLEAR(*rgi6);\n\n    SOCKADDR_INET DestinationAddress;\n    CLEAR(DestinationAddress);\n    DestinationAddress.si_family = AF_INET6;\n    if (dest)\n    {\n        DestinationAddress.Ipv6.sin6_addr = *dest;\n    }\n\n    MIB_IPFORWARD_ROW2 BestRoute;\n    CLEAR(BestRoute);\n    DWORD status = get_best_route(&gc, &DestinationAddress, &BestRoute);\n\n    if (status != NO_ERROR)\n    {\n        goto done;\n    }\n\n    msg(D_ROUTE, \"GDG6: II=%lu DP=%s/%d NH=%s\", BestRoute.InterfaceIndex,\n        print_in6_addr(BestRoute.DestinationPrefix.Prefix.Ipv6.sin6_addr, 0, &gc),\n        BestRoute.DestinationPrefix.PrefixLength,\n        print_in6_addr(BestRoute.NextHop.Ipv6.sin6_addr, 0, &gc));\n    msg(D_ROUTE, \"GDG6: Metric=%lu, Loopback=%u, AA=%u, I=%u\", BestRoute.Metric,\n        BestRoute.Loopback, BestRoute.AutoconfigureAddress, BestRoute.Immortal);\n\n    rgi6->gateway.addr_ipv6 = BestRoute.NextHop.Ipv6.sin6_addr;\n    rgi6->adapter_index = BestRoute.InterfaceIndex;\n    rgi6->flags |= RGI_ADDR_DEFINED | RGI_IFACE_DEFINED;\n\n    /* on-link is signalled by receiving an empty (::) NextHop */\n    if (IN6_IS_ADDR_UNSPECIFIED(&BestRoute.NextHop.Ipv6.sin6_addr))\n    {\n        rgi6->flags |= RGI_ON_LINK;\n    }\n\ndone:\n    gc_free(&gc);\n}\n\n/* Returns RTA_SUCCESS on success, RTA_EEXIST if route exists, RTA_ERROR on error */\nstatic int\nadd_route_ipapi(const struct route_ipv4 *r, const struct tuntap *tt, DWORD adapter_index)\n{\n    struct gc_arena gc = gc_new();\n    int ret = RTA_ERROR;\n    DWORD status;\n    const DWORD if_index = (adapter_index == TUN_ADAPTER_INDEX_INVALID)\n                               ? windows_route_find_if_index(r, tt)\n                               : adapter_index;\n\n    if (if_index != TUN_ADAPTER_INDEX_INVALID)\n    {\n        MIB_IPFORWARDROW fr;\n        CLEAR(fr);\n        fr.dwForwardDest = htonl(r->network);\n        fr.dwForwardMask = htonl(r->netmask);\n        fr.dwForwardPolicy = 0;\n        fr.dwForwardNextHop = htonl(r->gateway);\n        fr.dwForwardIfIndex = if_index;\n        fr.dwForwardType = 4;  /* the next hop is not the final dest */\n        fr.dwForwardProto = 3; /* PROTO_IP_NETMGMT */\n        fr.dwForwardAge = 0;\n        fr.dwForwardNextHopAS = 0;\n        fr.dwForwardMetric1 = (r->flags & RT_METRIC_DEFINED) ? r->metric : 1;\n        fr.dwForwardMetric2 = METRIC_NOT_USED;\n        fr.dwForwardMetric3 = METRIC_NOT_USED;\n        fr.dwForwardMetric4 = METRIC_NOT_USED;\n        fr.dwForwardMetric5 = METRIC_NOT_USED;\n\n        if ((r->network & r->netmask) != r->network)\n        {\n            msg(M_WARN, \"Warning: address %s is not a network address in relation to netmask %s\",\n                print_in_addr_t(r->network, 0, &gc), print_in_addr_t(r->netmask, 0, &gc));\n        }\n\n        status = CreateIpForwardEntry(&fr);\n\n        if (status == NO_ERROR)\n        {\n            ret = RTA_SUCCESS;\n        }\n        else if (status == ERROR_OBJECT_ALREADY_EXISTS)\n        {\n            ret = RTA_EEXIST;\n        }\n        else\n        {\n            /* failed, try increasing the metric to work around Vista issue */\n            /* iteratively retry higher metrics up to this limit */\n            const DWORD forward_metric_limit = 2048;\n\n            for (; fr.dwForwardMetric1 <= forward_metric_limit; ++fr.dwForwardMetric1)\n            {\n                /* try a different forward type=3 (\"the next hop is the final dest\") in addition\n                 * to 4.\n                 * --redirect-gateway over RRAS seems to need this. */\n                for (fr.dwForwardType = 4; fr.dwForwardType >= 3; --fr.dwForwardType)\n                {\n                    status = CreateIpForwardEntry(&fr);\n                    if (status == NO_ERROR)\n                    {\n                        msg(D_ROUTE,\n                            \"ROUTE: CreateIpForwardEntry succeeded with dwForwardMetric1=%lu and dwForwardType=%lu\",\n                            fr.dwForwardMetric1, fr.dwForwardType);\n                        ret = RTA_SUCCESS;\n                        goto doublebreak;\n                    }\n                    else if (status != ERROR_BAD_ARGUMENTS)\n                    {\n                        goto doublebreak;\n                    }\n                }\n            }\n\ndoublebreak:\n            if (status != NO_ERROR)\n            {\n                if (status == ERROR_OBJECT_ALREADY_EXISTS)\n                {\n                    ret = RTA_EEXIST;\n                }\n                else\n                {\n                    msg(M_WARN,\n                        \"ERROR: route addition failed using CreateIpForwardEntry: \"\n                        \"%s [status=%lu if_index=%lu]\",\n                        strerror_win32(status, &gc), status, if_index);\n                }\n            }\n        }\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n\nstatic bool\ndel_route_ipapi(const struct route_ipv4 *r, const struct tuntap *tt)\n{\n    struct gc_arena gc = gc_new();\n    bool ret = false;\n    DWORD status;\n    const DWORD if_index = windows_route_find_if_index(r, tt);\n\n    if (if_index != TUN_ADAPTER_INDEX_INVALID)\n    {\n        MIB_IPFORWARDROW fr;\n        CLEAR(fr);\n\n        fr.dwForwardDest = htonl(r->network);\n        fr.dwForwardMask = htonl(r->netmask);\n        fr.dwForwardPolicy = 0;\n        fr.dwForwardNextHop = htonl(r->gateway);\n        fr.dwForwardIfIndex = if_index;\n\n        status = DeleteIpForwardEntry(&fr);\n\n        if (status == NO_ERROR)\n        {\n            ret = true;\n        }\n        else\n        {\n            msg(M_WARN, \"ERROR: route deletion failed using DeleteIpForwardEntry: %s\",\n                strerror_win32(status, &gc));\n        }\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n\n/* Returns RTA_SUCCESS on success, RTA_EEXIST if route exists, RTA_ERROR on error */\nstatic int\ndo_route_service(const bool add, const route_message_t *rt, const DWORD size, HANDLE pipe)\n{\n    int ret = RTA_ERROR;\n    ack_message_t ack;\n    struct gc_arena gc = gc_new();\n\n    if (!send_msg_iservice(pipe, rt, size, &ack, \"ROUTE\"))\n    {\n        goto out;\n    }\n\n    if (ack.error_number != NO_ERROR)\n    {\n        ret = (ack.error_number == ERROR_OBJECT_ALREADY_EXISTS) ? RTA_EEXIST : RTA_ERROR;\n        if (ret == RTA_ERROR)\n        {\n            msg(M_WARN, \"ERROR: route %s failed using service: %s [status=%u if_index=%lu]\",\n                (add ? \"addition\" : \"deletion\"), strerror_win32(ack.error_number, &gc),\n                ack.error_number, rt->iface.index);\n        }\n        goto out;\n    }\n\n    ret = RTA_SUCCESS;\n\nout:\n    gc_free(&gc);\n    return ret;\n}\n\n/* Returns RTA_SUCCESS on success, RTA_EEXIST if route exists, RTA_ERROR on error */\nstatic int\ndo_route_ipv4_service(const bool add, const struct route_ipv4 *r, const struct tuntap *tt)\n{\n    DWORD if_index = windows_route_find_if_index(r, tt);\n    if (if_index == TUN_ADAPTER_INDEX_INVALID)\n    {\n        return RTA_ERROR;\n    }\n\n    route_message_t msg = { .header = { (add ? msg_add_route : msg_del_route),\n                                        sizeof(route_message_t), 0 },\n                            .family = AF_INET,\n                            .prefix.ipv4.s_addr = htonl(r->network),\n                            .gateway.ipv4.s_addr = htonl(r->gateway),\n                            .iface = { .index = if_index, .name = \"\" },\n                            .metric = (r->flags & RT_METRIC_DEFINED ? r->metric : -1) };\n\n    netmask_to_netbits(r->network, r->netmask, &msg.prefix_len);\n    if (msg.prefix_len == -1)\n    {\n        msg.prefix_len = 32;\n    }\n\n    return do_route_service(add, &msg, sizeof(msg), tt->options.msg_channel);\n}\n\n/* Add or delete an ipv6 route\n * Returns RTA_SUCCESS on success, RTA_EEXIST if route exists, RTA_ERROR on error\n */\nstatic int\nroute_ipv6_ipapi(const bool add, const struct route_ipv6 *r, const struct tuntap *tt)\n{\n    DWORD err;\n    int ret = RTA_ERROR;\n    PMIB_IPFORWARD_ROW2 fwd_row;\n    struct gc_arena gc = gc_new();\n\n    fwd_row = gc_malloc(sizeof(*fwd_row), true, &gc);\n\n    fwd_row->ValidLifetime = 0xffffffff;\n    fwd_row->PreferredLifetime = 0xffffffff;\n    fwd_row->Protocol = MIB_IPPROTO_NETMGMT;\n    fwd_row->Metric = ((r->flags & RT_METRIC_DEFINED) ? r->metric : -1);\n    fwd_row->DestinationPrefix.Prefix.si_family = AF_INET6;\n    fwd_row->DestinationPrefix.Prefix.Ipv6.sin6_addr = r->network;\n    fwd_row->DestinationPrefix.PrefixLength = (UINT8)r->netbits;\n    fwd_row->NextHop.si_family = AF_INET6;\n    fwd_row->NextHop.Ipv6.sin6_addr = r->gateway;\n    fwd_row->InterfaceIndex = r->adapter_index ? r->adapter_index : tt->adapter_index;\n\n    /* In TUN mode we use a special link-local address as the next hop.\n     * The tapdrvr knows about it and will answer neighbor discovery packets.\n     * (only do this for routes actually using the tun/tap device)\n     */\n    if (tt->type == DEV_TYPE_TUN && !r->adapter_index)\n    {\n        inet_pton(AF_INET6, \"fe80::8\", &fwd_row->NextHop.Ipv6.sin6_addr);\n    }\n\n    /* Use LUID if interface index not available */\n    if (fwd_row->InterfaceIndex == TUN_ADAPTER_INDEX_INVALID && strlen(tt->actual_name))\n    {\n        NET_LUID luid;\n        err = ConvertInterfaceAliasToLuid(wide_string(tt->actual_name, &gc), &luid);\n        if (err != NO_ERROR)\n        {\n            goto out;\n        }\n        fwd_row->InterfaceLuid = luid;\n        fwd_row->InterfaceIndex = 0;\n    }\n\n    if (add)\n    {\n        err = CreateIpForwardEntry2(fwd_row);\n    }\n    else\n    {\n        err = DeleteIpForwardEntry2(fwd_row);\n    }\n\nout:\n    if (err != NO_ERROR)\n    {\n        ret = (err == ERROR_OBJECT_ALREADY_EXISTS) ? RTA_EEXIST : RTA_ERROR;\n        if (ret == RTA_ERROR)\n        {\n            msg(M_WARN, \"ERROR: route %s failed using ipapi: %s [status=%lu if_index=%lu]\",\n                (add ? \"addition\" : \"deletion\"), strerror_win32(err, &gc), err,\n                fwd_row->InterfaceIndex);\n        }\n        else if (add)\n        {\n            msg(D_ROUTE, \"IPv6 route addition using ipapi failed because route exists\");\n        }\n    }\n    else\n    {\n        msg(D_ROUTE, \"IPv6 route %s using ipapi\", add ? \"added\" : \"deleted\");\n        ret = RTA_SUCCESS;\n    }\n    gc_free(&gc);\n    return ret;\n}\n\n/* Returns RTA_SUCCESS on success, RTA_EEXIST if route exists, RTA_ERROR on error */\nstatic int\ndo_route_ipv6_service(const bool add, const struct route_ipv6 *r, const struct tuntap *tt)\n{\n    int status;\n    route_message_t msg = { .header = { (add ? msg_add_route : msg_del_route),\n                                        sizeof(route_message_t), 0 },\n                            .family = AF_INET6,\n                            .prefix.ipv6 = r->network,\n                            .prefix_len = r->netbits,\n                            .gateway.ipv6 = r->gateway,\n                            .iface = { .index = tt->adapter_index, .name = \"\" },\n                            .metric = ((r->flags & RT_METRIC_DEFINED) ? r->metric : -1) };\n\n    if (r->adapter_index) /* vpn server special route */\n    {\n        msg.iface.index = r->adapter_index;\n    }\n\n    /* In TUN mode we use a special link-local address as the next hop.\n     * The tapdrvr knows about it and will answer neighbor discovery packets.\n     * (only do this for routes actually using the tun/tap device)\n     */\n    if (tt->type == DEV_TYPE_TUN && msg.iface.index == tt->adapter_index)\n    {\n        inet_pton(AF_INET6, \"fe80::8\", &msg.gateway.ipv6);\n    }\n\n    if (msg.iface.index == TUN_ADAPTER_INDEX_INVALID)\n    {\n        strncpy(msg.iface.name, tt->actual_name, sizeof(msg.iface.name));\n        msg.iface.name[sizeof(msg.iface.name) - 1] = '\\0';\n    }\n\n    status = do_route_service(add, &msg, sizeof(msg), tt->options.msg_channel);\n    if (status != RTA_ERROR)\n    {\n        msg(D_ROUTE, \"IPv6 route %s via service %s\", add ? \"addition\" : \"deletion\",\n            (status == RTA_SUCCESS) ? \"succeeded\" : \"failed because route exists\");\n    }\n    return status;\n}\n\n/* Returns RTA_SUCCESS on success, RTA_EEXIST if route exists, RTA_ERROR on error */\nstatic int\nadd_route_service(const struct route_ipv4 *r, const struct tuntap *tt)\n{\n    return do_route_ipv4_service(true, r, tt);\n}\n\nstatic bool\ndel_route_service(const struct route_ipv4 *r, const struct tuntap *tt)\n{\n    return do_route_ipv4_service(false, r, tt);\n}\n\n/* Returns RTA_SUCCESS on success, RTA_EEXIST if route exists, RTA_ERROR on error */\nstatic int\nadd_route_ipv6_service(const struct route_ipv6 *r, const struct tuntap *tt)\n{\n    return do_route_ipv6_service(true, r, tt);\n}\n\nstatic bool\ndel_route_ipv6_service(const struct route_ipv6 *r, const struct tuntap *tt)\n{\n    return do_route_ipv6_service(false, r, tt);\n}\n\nstatic const char *\nformat_route_entry(const MIB_IPFORWARDROW *r, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n    buf_printf(&out, \"%s %s %s p=%lu i=%lu t=%lu pr=%lu a=%lu h=%lu m=%lu/%lu/%lu/%lu/%lu\",\n               print_in_addr_t(r->dwForwardDest, IA_NET_ORDER, gc),\n               print_in_addr_t(r->dwForwardMask, IA_NET_ORDER, gc),\n               print_in_addr_t(r->dwForwardNextHop, IA_NET_ORDER, gc), r->dwForwardPolicy,\n               r->dwForwardIfIndex, r->dwForwardType, r->dwForwardProto,\n               r->dwForwardAge, r->dwForwardNextHopAS, r->dwForwardMetric1,\n               r->dwForwardMetric2, r->dwForwardMetric3, r->dwForwardMetric4,\n               r->dwForwardMetric5);\n    return BSTR(&out);\n}\n\n/*\n * Show current routing table\n */\nvoid\nshow_routes(msglvl_t msglevel)\n{\n    struct gc_arena gc = gc_new();\n\n    const MIB_IPFORWARDTABLE *rt = get_windows_routing_table(&gc);\n\n    msg(msglevel, \"SYSTEM ROUTING TABLE\");\n    if (rt)\n    {\n        for (DWORD i = 0; i < rt->dwNumEntries; ++i)\n        {\n            msg(msglevel, \"%s\", format_route_entry(&rt->table[i], &gc));\n        }\n    }\n    gc_free(&gc);\n}\n\n#elif defined(TARGET_ANDROID)\n\nvoid\nget_default_gateway(struct route_gateway_info *rgi, in_addr_t dest, openvpn_net_ctx_t *ctx)\n{\n    /* Android, set some pseudo GW, addr is in host byte order,\n     * Determining the default GW on Android 5.0+ is non trivial\n     * and serves almost no purpose since OpenVPN only uses the\n     * default GW address to add routes for networks that should\n     * NOT be routed over the VPN. Using a well known address\n     * (127.'d'.'g'.'w') for the default GW make detecting\n     * these routes easier from the controlling app.\n     */\n    CLEAR(*rgi);\n\n    rgi->gateway.addr = 127 << 24 | 'd' << 16 | 'g' << 8 | 'w';\n    rgi->flags = RGI_ADDR_DEFINED | RGI_IFACE_DEFINED;\n    strcpy(rgi->iface, \"android-gw\");\n\n    /* Skip scanning/fetching interface from loopback interface we do\n     * normally on Linux.\n     * It always fails and \"ioctl(SIOCGIFCONF) failed\" confuses users\n     */\n}\n\nvoid\nget_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi6, const struct in6_addr *dest,\n                         openvpn_net_ctx_t *ctx)\n{\n    /* Same for ipv6 */\n\n    CLEAR(*rgi6);\n\n    /* Use a fake link-local address */\n    ASSERT(inet_pton(AF_INET6, \"fe80::ad\", &rgi6->addrs->addr_ipv6) == 1);\n    rgi6->addrs->netbits_ipv6 = 64;\n    rgi6->flags = RGI_ADDR_DEFINED | RGI_IFACE_DEFINED;\n    strcpy(rgi6->iface, \"android-gw\");\n}\n\n#elif defined(TARGET_LINUX)\n\nvoid\nget_default_gateway(struct route_gateway_info *rgi, in_addr_t dest, openvpn_net_ctx_t *ctx)\n{\n    struct gc_arena gc = gc_new();\n    int sd = -1;\n    char best_name[IFNAMSIZ];\n\n    CLEAR(*rgi);\n    CLEAR(best_name);\n\n    /* find best route to 'dest', get gateway IP addr + interface */\n    if (net_route_v4_best_gw(ctx, &dest, &rgi->gateway.addr, best_name) == 0)\n    {\n        rgi->flags |= RGI_ADDR_DEFINED;\n        if (!rgi->gateway.addr && best_name[0])\n        {\n            rgi->flags |= RGI_ON_LINK;\n        }\n    }\n\n    /* scan adapter list */\n    if (rgi->flags & RGI_ADDR_DEFINED)\n    {\n        struct ifreq *ifr, *ifend;\n        in_addr_t addr, netmask;\n        struct ifreq ifreq;\n        struct ifconf ifc;\n        struct ifreq ifs[20]; /* Maximum number of interfaces to scan */\n\n        if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)\n        {\n            msg(M_WARN, \"GDG: socket() failed\");\n            goto done;\n        }\n        ifc.ifc_len = sizeof(ifs);\n        ifc.ifc_req = ifs;\n        if (ioctl(sd, SIOCGIFCONF, &ifc) < 0)\n        {\n            msg(M_WARN, \"GDG: ioctl(SIOCGIFCONF) failed\");\n            goto done;\n        }\n\n        /* scan through interface list */\n        ifend = ifs + (ifc.ifc_len / sizeof(struct ifreq));\n        for (ifr = ifc.ifc_req; ifr < ifend; ifr++)\n        {\n            if (ifr->ifr_addr.sa_family == AF_INET)\n            {\n                /* get interface addr */\n                addr = ntohl(((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr.s_addr);\n\n                /* get interface name */\n                strncpynt(ifreq.ifr_name, ifr->ifr_name, sizeof(ifreq.ifr_name));\n\n                /* check that the interface is up */\n                if (ioctl(sd, SIOCGIFFLAGS, &ifreq) < 0)\n                {\n                    continue;\n                }\n                if (!(ifreq.ifr_flags & IFF_UP))\n                {\n                    continue;\n                }\n\n                if (rgi->flags & RGI_ON_LINK)\n                {\n                    /* check that interface name of current interface\n                     * matches interface name of best default route */\n                    if (strcmp(ifreq.ifr_name, best_name))\n                    {\n                        continue;\n                    }\n#if 0\n                    /* if point-to-point link, use remote addr as route gateway */\n                    if ((ifreq.ifr_flags & IFF_POINTOPOINT) && ioctl(sd, SIOCGIFDSTADDR, &ifreq) >= 0)\n                    {\n                        rgi->gateway.addr = ntohl(((struct sockaddr_in *) &ifreq.ifr_addr)->sin_addr.s_addr);\n                        if (rgi->gateway.addr)\n                        {\n                            rgi->flags &= ~RGI_ON_LINK;\n                        }\n                    }\n#endif\n                }\n                else\n                {\n                    /* get interface netmask */\n                    if (ioctl(sd, SIOCGIFNETMASK, &ifreq) < 0)\n                    {\n                        continue;\n                    }\n                    netmask = ntohl(((struct sockaddr_in *)&ifreq.ifr_addr)->sin_addr.s_addr);\n\n                    /* check that interface matches default route */\n                    if (((rgi->gateway.addr ^ addr) & netmask) != 0)\n                    {\n                        continue;\n                    }\n\n                    /* save netmask */\n                    rgi->gateway.netmask = netmask;\n                    rgi->flags |= RGI_NETMASK_DEFINED;\n                }\n\n                /* save iface name */\n                strncpynt(rgi->iface, ifreq.ifr_name, sizeof(rgi->iface));\n                rgi->flags |= RGI_IFACE_DEFINED;\n\n                /* now get the hardware address. */\n                memset(&ifreq.ifr_hwaddr, 0, sizeof(struct sockaddr));\n                if (ioctl(sd, SIOCGIFHWADDR, &ifreq) < 0)\n                {\n                    msg(M_WARN, \"GDG: SIOCGIFHWADDR(%s) failed\", ifreq.ifr_name);\n                    goto done;\n                }\n                memcpy(rgi->hwaddr, &ifreq.ifr_hwaddr.sa_data, 6);\n                rgi->flags |= RGI_HWADDR_DEFINED;\n\n                break;\n            }\n        }\n    }\n\ndone:\n    if (sd >= 0)\n    {\n        close(sd);\n    }\n    gc_free(&gc);\n}\n\n/* IPv6 implementation using netlink\n * https://www.linuxjournal.com/article/7356 - \"Kernel Korner - Why and How to Use Netlink Socket\"\n * netlink(3), netlink(7), rtnetlink(7)\n * https://www.virtualbox.org/svn/vbox/trunk/src/VBox/NetworkServices/NAT/\n */\nstruct rtreq\n{\n    struct nlmsghdr nh;\n    struct rtmsg rtm;\n    char attrbuf[512];\n};\n\nvoid\nget_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi6, const struct in6_addr *dest,\n                         openvpn_net_ctx_t *ctx)\n{\n    int flags;\n\n    CLEAR(*rgi6);\n\n    if (net_route_v6_best_gw(ctx, dest, &rgi6->gateway.addr_ipv6, rgi6->iface) == 0)\n    {\n        if (!IN6_IS_ADDR_UNSPECIFIED(&rgi6->gateway.addr_ipv6))\n        {\n            rgi6->flags |= RGI_ADDR_DEFINED;\n        }\n\n        if (strlen(rgi6->iface) > 0)\n        {\n            rgi6->flags |= RGI_IFACE_DEFINED;\n        }\n    }\n\n    /* if we have an interface but no gateway, the destination is on-link */\n    flags = rgi6->flags & (RGI_IFACE_DEFINED | RGI_ADDR_DEFINED);\n    if (flags == RGI_IFACE_DEFINED)\n    {\n        rgi6->flags |= (RGI_ADDR_DEFINED | RGI_ON_LINK);\n        if (dest)\n        {\n            rgi6->gateway.addr_ipv6 = *dest;\n        }\n    }\n}\n\n#elif defined(TARGET_DARWIN) || defined(TARGET_SOLARIS) || defined(TARGET_FREEBSD) \\\n    || defined(TARGET_DRAGONFLY) || defined(TARGET_OPENBSD) || defined(TARGET_NETBSD)\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <net/route.h>\n#include <net/if_dl.h>\n#if !defined(TARGET_SOLARIS)\n#include <ifaddrs.h>\n#endif\n\nstruct rtmsg\n{\n    struct rt_msghdr m_rtm;\n    char m_space[512];\n};\n\n/* the route socket code is identical for all 4 supported BSDs and for\n * MacOS X (Darwin), with one crucial difference: when going from\n * 32 bit to 64 bit, FreeBSD/OpenBSD increased the structure size but kept\n * source code compatibility by keeping the use of \"long\", while\n * MacOS X decided to keep binary compatibility by *changing* the API\n * to use \"uint32_t\", thus 32 bit on all OS X variants\n *\n * NetBSD does the MacOS way of \"fixed number of bits, no matter if\n * 32 or 64 bit OS\", but chose uint64_t.  For maximum portability, we\n * just use the OS RT_ROUNDUP() macro, which is guaranteed to be correct.\n *\n * We used to have a large amount of duplicate code here which really\n * differed only in this (long) vs. (uint32_t) - IMHO, worse than\n * having a combined block for all BSDs with this single #ifdef inside\n */\n\n#if defined(TARGET_DARWIN)\n#define ROUNDUP(a) ((a) > 0 ? (1 + (((a) - 1) | (sizeof(uint32_t) - 1))) : sizeof(uint32_t))\n#elif defined(TARGET_NETBSD)\n#define ROUNDUP(a) RT_ROUNDUP(a)\n#else\n#define ROUNDUP(a) ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))\n#endif\n\n#if defined(TARGET_SOLARIS)\n#define NEXTADDR(w, u)        \\\n    if (rtm_addrs & (w))      \\\n    {                         \\\n        size_t l = sizeof(u); \\\n        memmove(cp, &(u), l); \\\n        cp += ROUNDUP(l);     \\\n    }\n\n#define ADVANCE(x, n) (x += ROUNDUP(sizeof(struct sockaddr_in)))\n#else /* if defined(TARGET_SOLARIS) */\n#define NEXTADDR(w, u)                                \\\n    if (rtm_addrs & (w))                              \\\n    {                                                 \\\n        size_t l = ((struct sockaddr *)&(u))->sa_len; \\\n        memmove(cp, &(u), l);                         \\\n        cp += ROUNDUP(l);                             \\\n    }\n\n#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))\n#endif\n\n#define max(a, b) ((a) > (b) ? (a) : (b))\n\nvoid\nget_default_gateway(struct route_gateway_info *rgi, in_addr_t dest, openvpn_net_ctx_t *ctx)\n{\n    struct gc_arena gc = gc_new();\n    struct rtmsg m_rtmsg;\n    int sockfd = -1;\n    int rtm_addrs;\n    struct sockaddr so_dst, so_mask;\n    char *cp = m_rtmsg.m_space;\n    struct sockaddr *gate = NULL, *ifp = NULL, *sa;\n    struct rt_msghdr *rtm_aux;\n\n#define rtm m_rtmsg.m_rtm\n\n    CLEAR(*rgi);\n\n    /* setup data to send to routing socket */\n    const int pid = getpid();\n    int seq = 0;\n#ifdef TARGET_OPENBSD\n    rtm_addrs = RTA_DST | RTA_NETMASK; /* Kernel refuses RTA_IFP */\n#else\n    rtm_addrs = RTA_DST | RTA_NETMASK | RTA_IFP;\n#endif\n\n    bzero(&m_rtmsg, sizeof(m_rtmsg));\n    bzero(&so_dst, sizeof(so_dst));\n    bzero(&so_mask, sizeof(so_mask));\n    bzero(&rtm, sizeof(struct rt_msghdr));\n\n    rtm.rtm_type = RTM_GET;\n    rtm.rtm_flags = RTF_UP | RTF_GATEWAY;\n    rtm.rtm_version = RTM_VERSION;\n    rtm.rtm_seq = ++seq;\n#ifdef TARGET_OPENBSD\n    rtm.rtm_tableid = (u_short)getrtable();\n#endif\n    rtm.rtm_addrs = rtm_addrs;\n\n    so_dst.sa_family = AF_INET;\n    so_mask.sa_family = AF_INET;\n\n#ifndef TARGET_SOLARIS\n    so_dst.sa_len = sizeof(struct sockaddr_in);\n    so_mask.sa_len = sizeof(struct sockaddr_in);\n#endif\n\n    NEXTADDR(RTA_DST, so_dst);\n    NEXTADDR(RTA_NETMASK, so_mask);\n\n    /* sizeof(struct rt_msghdr) + padding */\n    rtm.rtm_msglen = (u_short)(cp - (char *)&m_rtmsg);\n\n    /* transact with routing socket */\n    sockfd = socket(PF_ROUTE, SOCK_RAW, 0);\n    if (sockfd < 0)\n    {\n        msg(M_WARN, \"GDG: socket #1 failed\");\n        goto done;\n    }\n    if (write(sockfd, (char *)&m_rtmsg, rtm.rtm_msglen) < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"GDG: problem writing to routing socket\");\n        goto done;\n    }\n    ssize_t ret;\n    do\n    {\n        ret = read(sockfd, (char *)&m_rtmsg, sizeof(m_rtmsg));\n    } while (ret > 0 && (rtm.rtm_seq != seq || rtm.rtm_pid != pid));\n    close(sockfd);\n    sockfd = -1;\n\n    /* extract return data from routing socket */\n    rtm_aux = &rtm;\n    cp = (char *)(rtm_aux + 1);\n    if (rtm_aux->rtm_addrs)\n    {\n        for (unsigned int i = 1; i; i <<= 1)\n        {\n            if (i & rtm_aux->rtm_addrs)\n            {\n                sa = (struct sockaddr *)cp;\n                if (i == RTA_GATEWAY)\n                {\n                    gate = sa;\n                }\n                else if (i == RTA_IFP)\n                {\n                    ifp = sa;\n                }\n                ADVANCE(cp, sa);\n            }\n        }\n    }\n    else\n    {\n        goto done;\n    }\n\n    /* get gateway addr and interface name */\n    if (gate != NULL)\n    {\n        /* get default gateway addr */\n        rgi->gateway.addr = ntohl(((struct sockaddr_in *)gate)->sin_addr.s_addr);\n        if (rgi->gateway.addr)\n        {\n            rgi->flags |= RGI_ADDR_DEFINED;\n        }\n\n        if (ifp)\n        {\n            /* get interface name */\n            const struct sockaddr_dl *adl = (struct sockaddr_dl *)ifp;\n            if (adl->sdl_nlen && adl->sdl_nlen < sizeof(rgi->iface))\n            {\n                memcpy(rgi->iface, adl->sdl_data, adl->sdl_nlen);\n                rgi->iface[adl->sdl_nlen] = '\\0';\n                rgi->flags |= RGI_IFACE_DEFINED;\n            }\n        }\n    }\n\n    /* get netmask of interface that owns default gateway */\n    if (rgi->flags & RGI_IFACE_DEFINED)\n    {\n        struct ifreq ifr;\n\n        sockfd = socket(AF_INET, SOCK_DGRAM, 0);\n        if (sockfd < 0)\n        {\n            msg(M_WARN, \"GDG: socket #2 failed\");\n            goto done;\n        }\n\n        CLEAR(ifr);\n        ifr.ifr_addr.sa_family = AF_INET;\n        strncpynt(ifr.ifr_name, rgi->iface, IFNAMSIZ);\n\n        if (ioctl(sockfd, SIOCGIFNETMASK, (char *)&ifr) < 0)\n        {\n            msg(M_WARN, \"GDG: ioctl #1 failed\");\n            goto done;\n        }\n        close(sockfd);\n        sockfd = -1;\n\n        rgi->gateway.netmask = ntohl(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr);\n        rgi->flags |= RGI_NETMASK_DEFINED;\n    }\n\n    /* try to read MAC addr associated with interface that owns default gateway */\n    if (rgi->flags & RGI_IFACE_DEFINED)\n    {\n#if defined(TARGET_SOLARIS)\n        /* OpenSolaris has getifaddrs(3), but it does not return AF_LINK */\n        sockfd = socket(AF_INET, SOCK_DGRAM, 0);\n        if (sockfd < 0)\n        {\n            msg(M_WARN, \"GDG: socket #3 failed\");\n            goto done;\n        }\n\n        struct ifreq ifreq = { 0 };\n\n        /* now get the hardware address. */\n        strncpynt(ifreq.ifr_name, rgi->iface, sizeof(ifreq.ifr_name));\n        if (ioctl(sockfd, SIOCGIFHWADDR, &ifreq) < 0)\n        {\n            msg(M_WARN, \"GDG: SIOCGIFHWADDR(%s) failed\", ifreq.ifr_name);\n        }\n        else\n        {\n            memcpy(rgi->hwaddr, &ifreq.ifr_addr.sa_data, 6);\n            rgi->flags |= RGI_HWADDR_DEFINED;\n        }\n#else  /* if defined(TARGET_SOLARIS) */\n        struct ifaddrs *ifap, *ifa;\n\n        if (getifaddrs(&ifap) != 0)\n        {\n            msg(M_WARN | M_ERRNO, \"GDG: getifaddrs() failed\");\n            goto done;\n        }\n\n        for (ifa = ifap; ifa; ifa = ifa->ifa_next)\n        {\n            if (ifa->ifa_addr != NULL && ifa->ifa_addr->sa_family == AF_LINK\n                && !strncmp(ifa->ifa_name, rgi->iface, IFNAMSIZ))\n            {\n                struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr;\n                memcpy(rgi->hwaddr, LLADDR(sdl), 6);\n                rgi->flags |= RGI_HWADDR_DEFINED;\n            }\n        }\n\n        freeifaddrs(ifap);\n#endif /* if defined(TARGET_SOLARIS) */\n    }\n\ndone:\n    if (sockfd >= 0)\n    {\n        close(sockfd);\n    }\n    gc_free(&gc);\n}\n\n/* BSD implementation using routing socket (as does IPv4)\n * (the code duplication is somewhat unavoidable if we want this to\n * work on OpenSolaris as well.  *sigh*)\n */\n\n/* Solaris has no length field - this is ugly, but less #ifdef in total\n */\n#if defined(TARGET_SOLARIS)\n#undef ADVANCE\n#define ADVANCE(x, n) (x += ROUNDUP(sizeof(struct sockaddr_in6)))\n#endif\n\nvoid\nget_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi6, const struct in6_addr *dest,\n                         openvpn_net_ctx_t *ctx)\n{\n    struct rtmsg m_rtmsg;\n    int sockfd = -1;\n    int rtm_addrs;\n    struct sockaddr_in6 so_dst, so_mask;\n    char *cp = m_rtmsg.m_space;\n    struct sockaddr *gate = NULL, *ifp = NULL, *sa;\n    struct rt_msghdr *rtm_aux;\n\n    CLEAR(*rgi6);\n\n    /* setup data to send to routing socket */\n    const int pid = getpid();\n    int seq = 0;\n#ifdef TARGET_OPENBSD\n    rtm_addrs = RTA_DST | RTA_NETMASK; /* Kernel refuses RTA_IFP */\n#else\n    rtm_addrs = RTA_DST | RTA_NETMASK | RTA_IFP;\n#endif\n\n    bzero(&m_rtmsg, sizeof(m_rtmsg));\n    bzero(&so_dst, sizeof(so_dst));\n    bzero(&so_mask, sizeof(so_mask));\n    bzero(&rtm, sizeof(struct rt_msghdr));\n\n    rtm.rtm_type = RTM_GET;\n    rtm.rtm_flags = RTF_UP;\n    rtm.rtm_version = RTM_VERSION;\n    rtm.rtm_seq = ++seq;\n#ifdef TARGET_OPENBSD\n    rtm.rtm_tableid = (u_short)getrtable();\n#endif\n\n    so_dst.sin6_family = AF_INET6;\n    so_mask.sin6_family = AF_INET6;\n\n    if (dest != NULL /* specific host? */\n        && !IN6_IS_ADDR_UNSPECIFIED(dest))\n    {\n        so_dst.sin6_addr = *dest;\n        /* :: needs /0 \"netmask\", host route wants \"no netmask */\n        rtm_addrs &= ~RTA_NETMASK;\n    }\n\n    rtm.rtm_addrs = rtm_addrs;\n\n#ifndef TARGET_SOLARIS\n    so_dst.sin6_len = sizeof(struct sockaddr_in6);\n    so_mask.sin6_len = sizeof(struct sockaddr_in6);\n#endif\n\n    NEXTADDR(RTA_DST, so_dst);\n    NEXTADDR(RTA_NETMASK, so_mask);\n\n    /* sizeof(struct rt_msghdr) + padding */\n    rtm.rtm_msglen = (u_short)(cp - (char *)&m_rtmsg);\n\n    /* transact with routing socket */\n    sockfd = socket(PF_ROUTE, SOCK_RAW, 0);\n    if (sockfd < 0)\n    {\n        msg(M_WARN, \"GDG6: socket #1 failed\");\n        goto done;\n    }\n    if (write(sockfd, (char *)&m_rtmsg, rtm.rtm_msglen) < 0)\n    {\n        msg(M_WARN | M_ERRNO, \"GDG6: problem writing to routing socket\");\n        goto done;\n    }\n    ssize_t ret;\n    do\n    {\n        ret = read(sockfd, (char *)&m_rtmsg, sizeof(m_rtmsg));\n    } while (ret > 0 && (rtm.rtm_seq != seq || rtm.rtm_pid != pid));\n\n    close(sockfd);\n    sockfd = -1;\n\n    /* extract return data from routing socket */\n    rtm_aux = &rtm;\n    cp = (char *)(rtm_aux + 1);\n    if (rtm_aux->rtm_addrs)\n    {\n        for (unsigned int i = 1; i; i <<= 1)\n        {\n            if (i & rtm_aux->rtm_addrs)\n            {\n                sa = (struct sockaddr *)cp;\n                if (i == RTA_GATEWAY)\n                {\n                    gate = sa;\n                }\n                else if (i == RTA_IFP)\n                {\n                    ifp = sa;\n                }\n                ADVANCE(cp, sa);\n            }\n        }\n    }\n    else\n    {\n        goto done;\n    }\n\n    /* get gateway addr and interface name */\n    if (gate != NULL)\n    {\n        struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)gate;\n        struct in6_addr gw = s6->sin6_addr;\n\n#ifndef TARGET_SOLARIS\n        /* You do not really want to know... from FreeBSD's route.c\n         * (KAME encodes the 16 bit scope_id in s6_addr[2] + [3],\n         * but for a correct link-local address these must be :0000: )\n         */\n        if (gate->sa_len == sizeof(struct sockaddr_in6) && IN6_IS_ADDR_LINKLOCAL(&gw))\n        {\n            gw.s6_addr[2] = gw.s6_addr[3] = 0;\n        }\n\n        if (gate->sa_len != sizeof(struct sockaddr_in6) || IN6_IS_ADDR_UNSPECIFIED(&gw))\n        {\n            rgi6->flags |= RGI_ON_LINK;\n        }\n        else\n#endif\n        {\n            rgi6->gateway.addr_ipv6 = gw;\n        }\n        rgi6->flags |= RGI_ADDR_DEFINED;\n\n        if (ifp)\n        {\n            /* get interface name */\n            const struct sockaddr_dl *adl = (struct sockaddr_dl *)ifp;\n            if (adl->sdl_nlen && adl->sdl_nlen < sizeof(rgi6->iface))\n            {\n                memcpy(rgi6->iface, adl->sdl_data, adl->sdl_nlen);\n                rgi6->flags |= RGI_IFACE_DEFINED;\n            }\n        }\n    }\n\ndone:\n    if (sockfd >= 0)\n    {\n        close(sockfd);\n    }\n}\n\n#undef max\n\n#elif defined(TARGET_HAIKU)\n\nvoid\nget_default_gateway(struct route_gateway_info *rgi, in_addr_t dest, openvpn_net_ctx_t *ctx)\n{\n    CLEAR(*rgi);\n\n    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);\n    if (sockfd < 0)\n    {\n        msg(M_ERRNO, \"%s: Error opening socket for AF_INET\", __func__);\n        return;\n    }\n\n    struct ifconf config;\n    config.ifc_len = sizeof(config.ifc_value);\n    if (ioctl(sockfd, SIOCGRTSIZE, &config, sizeof(struct ifconf)) < 0)\n    {\n        msg(M_ERRNO, \"%s: Error getting routing table size\", __func__);\n        return;\n    }\n\n    uint32 size = (uint32)config.ifc_value;\n    if (size == 0)\n    {\n        return;\n    }\n\n    void *buffer = malloc(size);\n    check_malloc_return(buffer);\n\n    config.ifc_len = size;\n    config.ifc_buf = buffer;\n    if (ioctl(sockfd, SIOCGRTTABLE, &config, sizeof(struct ifconf)) < 0)\n    {\n        free(buffer);\n        return;\n    }\n\n    struct ifreq *interface = (struct ifreq *)buffer;\n    struct ifreq *end = (struct ifreq *)((uint8 *)buffer + size);\n\n    while (interface < end)\n    {\n        struct route_entry route = interface->ifr_route;\n        if ((route.flags & RTF_GATEWAY) != 0 && (route.flags & RTF_DEFAULT) != 0)\n        {\n            rgi->gateway.addr = ntohl(((struct sockaddr_in *)route.gateway)->sin_addr.s_addr);\n            rgi->flags = RGI_ADDR_DEFINED | RGI_IFACE_DEFINED;\n            strncpy(rgi->iface, interface->ifr_name, sizeof(rgi->iface));\n        }\n\n        int32 address_size = 0;\n        if (route.destination != NULL)\n        {\n            address_size += route.destination->sa_len;\n        }\n        if (route.mask != NULL)\n        {\n            address_size += route.mask->sa_len;\n        }\n        if (route.gateway != NULL)\n        {\n            address_size += route.gateway->sa_len;\n        }\n\n        interface = (struct ifreq *)((addr_t)interface + IF_NAMESIZE + sizeof(struct route_entry)\n                                     + address_size);\n    }\n    free(buffer);\n}\n\nvoid\nget_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi6, const struct in6_addr *dest,\n                         openvpn_net_ctx_t *ctx)\n{\n    /* TODO: Same for ipv6 with AF_INET6 */\n    CLEAR(*rgi6);\n}\n\n#else  /* if defined(_WIN32) */\n\n/*\n * This is a platform-specific method that returns data about\n * the current default gateway.  Return data is placed into\n * a struct route_gateway_info object provided by caller.  The\n * implementation should CLEAR the structure before adding\n * data to it.\n *\n * Data returned includes:\n * 1. default gateway address (rgi->gateway.addr)\n * 2. netmask of interface that owns default gateway\n *    (rgi->gateway.netmask)\n * 3. hardware address (i.e. MAC address) of interface that owns\n *    default gateway (rgi->hwaddr)\n * 4. interface name (or adapter index on Windows) that owns default\n *    gateway (rgi->iface or rgi->adapter_index)\n * 5. an array of additional address/netmask pairs defined by\n *    interface that owns default gateway (rgi->addrs with length\n *    given in rgi->n_addrs)\n *\n * The flags RGI_x_DEFINED may be used to indicate which of the data\n * members were successfully returned (set in rgi->flags).  All of\n * the data members are optional, however certain OpenVPN functionality\n * may be disabled by missing items.\n */\nvoid\nget_default_gateway(struct route_gateway_info *rgi, in_addr_t dest, openvpn_net_ctx_t *ctx)\n{\n    CLEAR(*rgi);\n}\nvoid\nget_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi6, const struct in6_addr *dest,\n                         openvpn_net_ctx_t *ctx)\n{\n    msg(D_ROUTE, \"no support for get_default_gateway_ipv6() on this system\");\n    CLEAR(*rgi6);\n}\n\n#endif /* if defined(_WIN32) */\n\nbool\nnetmask_to_netbits(const in_addr_t network, const in_addr_t netmask, int *netbits)\n{\n    int i;\n    const int addrlen = sizeof(in_addr_t) * 8;\n\n    if ((network & netmask) == network)\n    {\n        for (i = 0; i <= addrlen; ++i)\n        {\n            in_addr_t mask = netbits_to_netmask(i);\n            if (mask == netmask)\n            {\n                if (i == addrlen)\n                {\n                    *netbits = -1;\n                }\n                else\n                {\n                    *netbits = i;\n                }\n                return true;\n            }\n        }\n    }\n    return false;\n}\n\n/* similar to netmask_to_netbits(), but don't mess with base address\n * etc., just convert to netbits - non-mappable masks are returned as \"-1\"\n */\nint\nnetmask_to_netbits2(in_addr_t netmask)\n{\n    int i;\n    const int addrlen = sizeof(in_addr_t) * 8;\n\n    for (i = 0; i <= addrlen; ++i)\n    {\n        in_addr_t mask = netbits_to_netmask(i);\n        if (mask == netmask)\n        {\n            return i;\n        }\n    }\n    return -1;\n}\n\n\n/*\n * get_bypass_addresses() is used by the redirect-gateway bypass-x\n * functions to build a route bypass to selected DHCP/DNS servers,\n * so that outgoing packets to these servers don't end up in the tunnel.\n */\n\n#if defined(_WIN32)\n\nstatic void\nadd_host_route_if_nonlocal(struct route_bypass *rb, const in_addr_t addr)\n{\n    if (test_local_addr(addr, NULL) == TLA_NONLOCAL && addr != 0 && addr != IPV4_NETMASK_HOST)\n    {\n        add_bypass_address(rb, addr);\n    }\n}\n\nstatic void\nadd_host_route_array(struct route_bypass *rb, const IP_ADDR_STRING *iplist)\n{\n    while (iplist)\n    {\n        bool succeed = false;\n        const in_addr_t ip =\n            getaddr(GETADDR_HOST_ORDER, iplist->IpAddress.String, 0, &succeed, NULL);\n        if (succeed)\n        {\n            add_host_route_if_nonlocal(rb, ip);\n        }\n        iplist = iplist->Next;\n    }\n}\n\nstatic void\nget_bypass_addresses(struct route_bypass *rb, const unsigned int flags)\n{\n    struct gc_arena gc = gc_new();\n    /*bool ret_bool = false;*/\n\n    /* get full routing table */\n    const MIB_IPFORWARDTABLE *routes = get_windows_routing_table(&gc);\n\n    /* get the route which represents the default gateway */\n    const MIB_IPFORWARDROW *row = get_default_gateway_row(routes);\n\n    if (row)\n    {\n        /* get the adapter which the default gateway is associated with */\n        const IP_ADAPTER_INFO *dgi = get_adapter_info(row->dwForwardIfIndex, &gc);\n\n        /* get extra adapter info, such as DNS addresses */\n        const IP_PER_ADAPTER_INFO *pai = get_per_adapter_info(row->dwForwardIfIndex, &gc);\n\n        /* Bypass DHCP server address */\n        if ((flags & RG_BYPASS_DHCP) && dgi && dgi->DhcpEnabled)\n        {\n            add_host_route_array(rb, &dgi->DhcpServer);\n        }\n\n        /* Bypass DNS server addresses */\n        if ((flags & RG_BYPASS_DNS) && pai)\n        {\n            add_host_route_array(rb, &pai->DnsServerList);\n        }\n    }\n\n    gc_free(&gc);\n}\n\n#else  /* if defined(_WIN32) */\n\nstatic void\nget_bypass_addresses(struct route_bypass *rb, const unsigned int flags) /* PLATFORM-SPECIFIC */\n{\n}\n\n#endif /* if defined(_WIN32) */\n\n/*\n * Test if addr is reachable via a local interface (return ILA_LOCAL),\n * or if it needs to be routed via the default gateway (return\n * ILA_NONLOCAL).  If the target platform doesn't implement this\n * function, return ILA_NOT_IMPLEMENTED.\n *\n * Used by redirect-gateway autolocal feature\n */\n\n#if defined(_WIN32)\n\nint\ntest_local_addr(const in_addr_t addr, const struct route_gateway_info *rgi)\n{\n    struct gc_arena gc = gc_new();\n    const in_addr_t nonlocal_netmask =\n        0x80000000L; /* routes with netmask <= to this are considered non-local */\n    int ret = TLA_NONLOCAL;\n\n    /* get full routing table */\n    const MIB_IPFORWARDTABLE *rt = get_windows_routing_table(&gc);\n    if (rt)\n    {\n        for (DWORD i = 0; i < rt->dwNumEntries; ++i)\n        {\n            const MIB_IPFORWARDROW *row = &rt->table[i];\n            const in_addr_t net = ntohl(row->dwForwardDest);\n            const in_addr_t mask = ntohl(row->dwForwardMask);\n            if (mask > nonlocal_netmask && (addr & mask) == net)\n            {\n                ret = TLA_LOCAL;\n                break;\n            }\n        }\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n\n#else  /* if defined(_WIN32) */\n\nint\ntest_local_addr(const in_addr_t addr, const struct route_gateway_info *rgi) /* PLATFORM-SPECIFIC */\n{\n    if (rgi)\n    {\n        if (local_route(addr, 0xFFFFFFFF, rgi->gateway.addr, rgi))\n        {\n            return TLA_LOCAL;\n        }\n        else\n        {\n            return TLA_NONLOCAL;\n        }\n    }\n    return TLA_NOT_IMPLEMENTED;\n}\n\n#endif /* if defined(_WIN32) */\n"
  },
  {
    "path": "src/openvpn/route.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * Support routines for adding/deleting network routes.\n */\n\n#ifndef ROUTE_H\n#define ROUTE_H\n\n#include \"basic.h\"\n#include \"tun.h\"\n#include \"misc.h\"\n#include \"networking.h\"\n\n#ifdef _WIN32\n/*\n * Windows route methods\n */\n#define ROUTE_METHOD_ADAPTIVE 0 /* try IP helper first then route.exe */\n#define ROUTE_METHOD_IPAPI    1 /* use IP helper API */\n#define ROUTE_METHOD_EXE      2 /* use route.exe */\n#define ROUTE_METHOD_SERVICE  3 /* use the privileged Windows service */\n#define ROUTE_METHOD_MASK     3\n#endif\n\n/*\n * Route add/delete flags (must stay clear of ROUTE_METHOD bits)\n */\n#define ROUTE_DELETE_FIRST (1 << 2)\n#define ROUTE_REF_GW       (1 << 3)\n\nstruct route_bypass\n{\n#define N_ROUTE_BYPASS 8\n    int n_bypass;\n    in_addr_t bypass[N_ROUTE_BYPASS];\n};\n\nstruct route_special_addr\n{\n    /* bits indicating which members below are defined */\n#define RTSA_REMOTE_ENDPOINT (1 << 0)\n#define RTSA_REMOTE_HOST     (1 << 1)\n#define RTSA_DEFAULT_METRIC  (1 << 2)\n    unsigned int flags;\n\n    in_addr_t remote_endpoint;\n    in_addr_t remote_host;\n    int remote_host_local; /* TLA_x value */\n    struct route_bypass bypass;\n    int table_id;\n    int default_metric;\n};\n\nstruct route_option\n{\n    struct route_option *next;\n    const char *network;\n    const char *netmask;\n    const char *gateway;\n    int table_id;\n    const char *metric;\n};\n\n/* redirect-gateway flags */\n#define RG_ENABLE      (1u << 0)\n#define RG_LOCAL       (1u << 1)\n#define RG_DEF1        (1u << 2)\n#define RG_BYPASS_DHCP (1u << 3)\n#define RG_BYPASS_DNS  (1u << 4)\n#define RG_REROUTE_GW  (1u << 5)\n#define RG_AUTO_LOCAL  (1u << 6)\n#define RG_BLOCK_LOCAL (1u << 7)\n\nstruct route_option_list\n{\n    unsigned int flags; /* RG_x flags */\n    struct route_option *routes;\n    struct gc_arena *gc;\n};\n\nstruct route_ipv6_option\n{\n    struct route_ipv6_option *next;\n    const char *prefix;  /* e.g. \"2001:db8:1::/64\" */\n    const char *gateway; /* e.g. \"2001:db8:0::2\" */\n    const char *metric;  /* e.g. \"5\" */\n    int table_id;\n};\n\nstruct route_ipv6_option_list\n{\n    unsigned int flags; /* RG_x flags, see route_option-list */\n    struct route_ipv6_option *routes_ipv6;\n    struct gc_arena *gc;\n};\n\nstruct route_ipv4\n{\n#define RT_DEFINED        (1u << 0)\n#define RT_ADDED          (1u << 1)\n#define RT_METRIC_DEFINED (1u << 2)\n    struct route_ipv4 *next;\n    unsigned int flags;\n    const struct route_option *option;\n    in_addr_t network;\n    in_addr_t netmask;\n    in_addr_t gateway;\n    int table_id;\n    int metric;\n};\n\nstruct route_ipv6\n{\n    struct route_ipv6 *next;\n    unsigned int flags; /* RT_ flags, see route_ipv4 */\n    struct in6_addr network;\n    unsigned int netbits;\n    struct in6_addr gateway;\n    int metric;\n    int table_id;\n    /* gateway interface */\n#ifdef _WIN32\n    DWORD adapter_index; /* interface or ~0 if undefined */\n#else\n    char *iface; /* interface name (null terminated) */\n#endif\n};\n\n\nstruct route_gateway_address\n{\n    in_addr_t addr;\n    in_addr_t netmask;\n};\n\nstruct route_gateway_info\n{\n#define RGI_ADDR_DEFINED    (1 << 0) /* set if gateway.addr defined */\n#define RGI_NETMASK_DEFINED (1 << 1) /* set if gateway.netmask defined */\n#define RGI_HWADDR_DEFINED  (1 << 2) /* set if hwaddr is defined */\n#define RGI_IFACE_DEFINED   (1 << 3) /* set if iface is defined */\n#define RGI_OVERFLOW        (1 << 4) /* set if more interface addresses than will fit in addrs */\n#define RGI_ON_LINK         (1 << 5)\n    unsigned int flags;\n\n    /* gateway interface */\n#ifdef _WIN32\n    DWORD adapter_index; /* interface or ~0 if undefined */\n#elif defined(TARGET_HAIKU)\n    char iface[PATH_MAX]; /* iface names are full /dev path with driver name */\n#else\n    char iface[16]; /* interface name (null terminated), may be empty */\n#endif\n\n    /* gateway interface hardware address */\n    uint8_t hwaddr[6];\n\n    /* gateway/router address */\n    struct route_gateway_address gateway;\n\n    /* address/netmask pairs bound to interface */\n#define RGI_N_ADDRESSES 8\n    int n_addrs;                                         /* len of addrs, may be 0 */\n    struct route_gateway_address addrs[RGI_N_ADDRESSES]; /* local addresses attached to iface */\n};\n\nstruct route_ipv6_gateway_address\n{\n    struct in6_addr addr_ipv6;\n    int netbits_ipv6;\n};\n\nstruct route_ipv6_gateway_info\n{\n    /* RGI_ flags used as in route_gateway_info */\n    unsigned int flags;\n\n    /* gateway interface */\n#ifdef _WIN32\n    DWORD adapter_index; /* interface or ~0 if undefined */\n#else\n    /* non linux platform don't have this constant defined */\n#ifndef IFNAMSIZ\n#if defined(TARGET_HAIKU)\n/* iface names are full /dev path with driver name */\n#define IFNAMSIZ PATH_MAX\n#else\n#define IFNAMSIZ 16\n#endif\n#endif\n    char iface[IFNAMSIZ]; /* interface name (null terminated), may be empty */\n#endif\n\n    /* gateway interface hardware address */\n    uint8_t hwaddr[6];\n\n    /* gateway/router address */\n    struct route_ipv6_gateway_address gateway;\n\n    /* address/netmask pairs bound to interface */\n#define RGI_N_ADDRESSES 8\n    int n_addrs;                /* len of addrs, may be 0 */\n    struct route_ipv6_gateway_address\n        addrs[RGI_N_ADDRESSES]; /* local addresses attached to iface */\n};\n\nstruct route_list\n{\n#define RL_DID_REDIRECT_DEFAULT_GATEWAY (1u << 0)\n#define RL_DID_LOCAL                    (1u << 1)\n#define RL_ROUTES_ADDED                 (1u << 2)\n    unsigned int iflags;\n\n    struct route_special_addr spec;\n    struct route_gateway_info rgi;\n    struct route_gateway_info ngi; /* net_gateway */\n    unsigned int flags;            /* RG_x flags */\n    struct route_ipv4 *routes;\n    struct gc_arena gc;\n};\n\nstruct route_ipv6_list\n{\n    unsigned int iflags;                  /* RL_ flags, see route_list */\n\n    unsigned int spec_flags;              /* RTSA_ flags, route_special_addr */\n    struct in6_addr remote_endpoint_ipv6; /* inside tun */\n    struct in6_addr remote_host_ipv6;     /* --remote address */\n    int default_metric;\n\n    struct route_ipv6_gateway_info rgi6;\n    struct route_ipv6_gateway_info ngi6; /* net_gateway_ipv6 */\n    unsigned int flags;                  /* RG_x flags, see route_option_list */\n    struct route_ipv6 *routes_ipv6;\n    struct gc_arena gc;\n};\n\n/* internal OpenVPN route */\nstruct iroute\n{\n    in_addr_t network;\n    int netbits;\n    struct iroute *next;\n};\n\nstruct iroute_ipv6\n{\n    struct in6_addr network;\n    unsigned int netbits;\n    struct iroute_ipv6 *next;\n};\n\n/**\n * Get the decision whether to block traffic to local networks while the VPN\n * is connected. This definitely returns false when not redirecting the gateway\n * or when the 'block-local' flag is not set. Also checks for other\n * prerequisites to redirect local networks into the tunnel.\n *\n * @param rl const pointer to the struct route_list to base the decision on.\n *\n * @return boolean indicating whether local traffic should be blocked.\n */\nbool block_local_needed(const struct route_list *rl);\n\nstruct route_option_list *new_route_option_list(struct gc_arena *a);\n\nstruct route_ipv6_option_list *new_route_ipv6_option_list(struct gc_arena *a);\n\nstruct route_option_list *clone_route_option_list(const struct route_option_list *src,\n                                                  struct gc_arena *a);\n\nstruct route_ipv6_option_list *clone_route_ipv6_option_list(\n    const struct route_ipv6_option_list *src, struct gc_arena *a);\n\nvoid copy_route_option_list(struct route_option_list *dest, const struct route_option_list *src,\n                            struct gc_arena *a);\n\nvoid copy_route_ipv6_option_list(struct route_ipv6_option_list *dest,\n                                 const struct route_ipv6_option_list *src, struct gc_arena *a);\n\nvoid route_ipv6_clear_host_bits(struct route_ipv6 *r6);\n\nbool add_route_ipv6(struct route_ipv6 *r, const struct tuntap *tt, unsigned int flags,\n                    const struct env_set *es, openvpn_net_ctx_t *ctx);\n\nvoid delete_route_ipv6(const struct route_ipv6 *r, const struct tuntap *tt,\n                       const struct env_set *es, openvpn_net_ctx_t *ctx);\n\nbool add_route(struct route_ipv4 *r, const struct tuntap *tt, unsigned int flags,\n               const struct route_gateway_info *rgi, const struct env_set *es,\n               openvpn_net_ctx_t *ctx);\n\nvoid add_route_to_option_list(struct route_option_list *l, const char *network, const char *netmask,\n                              const char *gateway, const char *metric, int table_id);\n\nvoid add_route_ipv6_to_option_list(struct route_ipv6_option_list *l, const char *prefix,\n                                   const char *gateway, const char *metric, int table_id);\n\nbool init_route_list(struct route_list *rl, const struct route_option_list *opt,\n                     const char *remote_endpoint, int default_metric, in_addr_t remote_host,\n                     struct env_set *es, openvpn_net_ctx_t *ctx);\n\nbool init_route_ipv6_list(struct route_ipv6_list *rl6, const struct route_ipv6_option_list *opt6,\n                          const char *remote_endpoint, int default_metric,\n                          const struct in6_addr *remote_host, struct env_set *es,\n                          openvpn_net_ctx_t *ctx);\n\nvoid route_list_add_vpn_gateway(struct route_list *rl, struct env_set *es, const in_addr_t addr);\n\nbool add_routes(struct route_list *rl, struct route_ipv6_list *rl6, const struct tuntap *tt,\n                unsigned int flags, const struct env_set *es, openvpn_net_ctx_t *ctx);\n\nvoid delete_routes(struct route_list *rl, struct route_ipv6_list *rl6, const struct tuntap *tt,\n                   unsigned int flags, const struct env_set *es, openvpn_net_ctx_t *ctx);\n\nvoid delete_routes_v4(struct route_list *rl, const struct tuntap *tt, unsigned int flags,\n                      const struct env_set *es, openvpn_net_ctx_t *ctx);\n\nvoid delete_routes_v6(struct route_ipv6_list *rl6, const struct tuntap *tt, unsigned int flags,\n                      const struct env_set *es, openvpn_net_ctx_t *ctx);\n\nvoid setenv_routes(struct env_set *es, const struct route_list *rl);\n\nvoid setenv_routes_ipv6(struct env_set *es, const struct route_ipv6_list *rl6);\n\nbool is_special_addr(const char *addr_str);\n\n/**\n * @brief Retrieves the best gateway for a given destination based on the routing table.\n *\n * @param rgi  Pointer to a struct to store the gateway information.\n * @param dest Destination IP address in host byte order.\n * @param ctx  Pointer to a platform-specific network context struct.\n */\nvoid get_default_gateway(struct route_gateway_info *rgi, in_addr_t dest, openvpn_net_ctx_t *ctx);\n\nvoid get_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi, const struct in6_addr *dest,\n                              openvpn_net_ctx_t *ctx);\n\nvoid print_default_gateway(const msglvl_t msglevel, const struct route_gateway_info *rgi,\n                           const struct route_ipv6_gateway_info *rgi6);\n\n/*\n * Test if addr is reachable via a local interface (return ILA_LOCAL),\n * or if it needs to be routed via the default gateway (return\n * ILA_NONLOCAL).  If the current platform doesn't implement this\n * function, return ILA_NOT_IMPLEMENTED.\n */\n#define TLA_NOT_IMPLEMENTED 0\n#define TLA_NONLOCAL        1\n#define TLA_LOCAL           2\nint test_local_addr(const in_addr_t addr, const struct route_gateway_info *rgi);\n\n#ifndef ENABLE_SMALL\nvoid print_route_options(const struct route_option_list *rol, msglvl_t msglevel);\n\n#endif\n\nvoid print_routes(const struct route_list *rl, msglvl_t msglevel);\n\n#ifdef _WIN32\n\nvoid show_routes(msglvl_t msglevel);\n\nbool test_routes(const struct route_list *rl, const struct tuntap *tt);\n\n#else /* ifdef _WIN32 */\nstatic inline bool\ntest_routes(const struct route_list *rl, const struct tuntap *tt)\n{\n    return true;\n}\n#endif\n\nbool netmask_to_netbits(const in_addr_t network, const in_addr_t netmask, int *netbits);\n\nint netmask_to_netbits2(in_addr_t netmask);\n\nstatic inline in_addr_t\nnetbits_to_netmask(const int netbits)\n{\n    const int addrlen = sizeof(in_addr_t) * 8;\n    in_addr_t mask = 0;\n    if (netbits > 0 && netbits <= addrlen)\n    {\n        mask = IPV4_NETMASK_HOST << (addrlen - netbits);\n    }\n    return mask;\n}\n\nstatic inline bool\nroute_list_vpn_gateway_needed(const struct route_list *rl)\n{\n    if (!rl)\n    {\n        return false;\n    }\n    else\n    {\n        return !(rl->spec.flags & RTSA_REMOTE_ENDPOINT);\n    }\n}\n\nstatic inline int\nroute_did_redirect_default_gateway(const struct route_list *rl)\n{\n    return rl && BOOL_CAST(rl->iflags & RL_DID_REDIRECT_DEFAULT_GATEWAY);\n}\n\n\n/**\n * check whether an IPv6 host address is covered by a given network/bits\n * @param network the network address\n * @param bits the network mask\n * @param host the host address to be checked if it is contained by the network\n *\n * @return true if the host address is covered by the network with the given\n *         network mask by bits\n */\nbool\nipv6_net_contains_host(const struct in6_addr *network, unsigned int bits, const struct in6_addr *host);\n\n#endif /* ifndef ROUTE_H */\n"
  },
  {
    "path": "src/openvpn/run_command.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Technologies, Inc. <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"platform.h\"\n#include \"win32.h\"\n\n#include \"memdbg.h\"\n\n#include \"run_command.h\"\n\n/* contains an SSEC_x value defined in platform.h */\nstatic int script_security_level = SSEC_BUILT_IN; /* GLOBAL */\n\nint\nscript_security(void)\n{\n    return script_security_level;\n}\n\nvoid\nscript_security_set(int level)\n{\n    script_security_level = level;\n}\n\n/*\n * Generate an error message based on the status code returned by openvpn_execve().\n */\nstatic const char *\nsystem_error_message(int stat, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n\n    switch (stat)\n    {\n        case OPENVPN_EXECVE_NOT_ALLOWED:\n            buf_printf(&out, \"disallowed by script-security setting\");\n            break;\n\n#ifdef _WIN32\n        case OPENVPN_EXECVE_ERROR:\n            buf_printf(&out, \"external program did not execute -- \");\n            /* fall through */\n\n        default:\n            buf_printf(&out, \"returned error code %d\", stat);\n            break;\n#else  /* ifdef _WIN32 */\n\n        case OPENVPN_EXECVE_ERROR:\n            buf_printf(&out, \"external program fork failed\");\n            break;\n\n        default:\n            if (!WIFEXITED(stat))\n            {\n                buf_printf(&out, \"external program did not exit normally\");\n            }\n            else\n            {\n                const int cmd_ret = WEXITSTATUS(stat);\n                if (!cmd_ret)\n                {\n                    buf_printf(&out, \"external program exited normally\");\n                }\n                else if (cmd_ret == OPENVPN_EXECVE_FAILURE)\n                {\n                    buf_printf(&out, \"could not execute external program\");\n                }\n                else\n                {\n                    buf_printf(&out, \"external program exited with error status: %d\", cmd_ret);\n                }\n            }\n            break;\n#endif /* ifdef _WIN32 */\n    }\n    return (const char *)out.data;\n}\n\n#ifndef WIN32\nbool\nopenvpn_waitpid_check(pid_t pid, const char *msg_prefix, msglvl_t msglevel)\n{\n    if (pid == 0)\n    {\n        return false;\n    }\n    int status;\n    pid_t pidret = waitpid(pid, &status, WNOHANG);\n    if (pidret != pid)\n    {\n        return true;\n    }\n\n    if (WIFEXITED(status))\n    {\n        int exitcode = WEXITSTATUS(status);\n\n        if (exitcode == OPENVPN_EXECVE_FAILURE)\n        {\n            msg(msglevel, \"%scould not execute external program (exit code 127)\", msg_prefix);\n        }\n        else\n        {\n            msg(msglevel, \"%sexternal program exited with error status: %d\", msg_prefix, exitcode);\n        }\n    }\n    else if (WIFSIGNALED(status))\n    {\n        msg(msglevel, \"%sexternal program received signal %d\", msg_prefix, WTERMSIG(status));\n    }\n\n    return false;\n}\n#endif /* ifndef WIN32 */\n\nbool\nopenvpn_execve_allowed(const unsigned int flags)\n{\n    if (flags & S_SCRIPT)\n    {\n        return script_security() >= SSEC_SCRIPTS;\n    }\n    else\n    {\n        return script_security() >= SSEC_BUILT_IN;\n    }\n}\n\n\n#ifndef _WIN32\n/*\n * Run execve() inside a fork().  Designed to replicate the semantics of system() but\n * in a safer way that doesn't require the invocation of a shell or the risks\n * associated with formatting and parsing a command line.\n * Returns the exit status of child, OPENVPN_EXECVE_NOT_ALLOWED if openvpn_execve_allowed()\n * returns false, or OPENVPN_EXECVE_ERROR on other errors.\n */\nint\nopenvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags)\n{\n    struct gc_arena gc = gc_new();\n    int ret = OPENVPN_EXECVE_ERROR;\n    static bool warn_shown = false;\n\n    if (a && a->argv[0])\n    {\n#if defined(ENABLE_FEATURE_EXECVE)\n        if (openvpn_execve_allowed(flags))\n        {\n            const char *cmd = a->argv[0];\n            char *const *argv = a->argv;\n            char *const *envp = (char *const *)make_env_array(es, true, &gc);\n            pid_t pid;\n\n            pid = fork();\n            if (pid == (pid_t)0) /* child side */\n            {\n                execve(cmd, argv, envp);\n                exit(OPENVPN_EXECVE_FAILURE);\n            }\n            else if (pid < (pid_t)0) /* fork failed */\n            {\n                msg(M_ERR, \"openvpn_execve: unable to fork\");\n            }\n            else if (flags & S_NOWAITPID)\n            {\n                ret = pid;\n            }\n            else /* parent side */\n            {\n                if (waitpid(pid, &ret, 0) != pid)\n                {\n                    ret = OPENVPN_EXECVE_ERROR;\n                }\n            }\n        }\n        else\n        {\n            ret = OPENVPN_EXECVE_NOT_ALLOWED;\n            if (!warn_shown && (script_security() < SSEC_SCRIPTS))\n            {\n                msg(M_WARN, SCRIPT_SECURITY_WARNING);\n                warn_shown = true;\n            }\n        }\n#else  /* if defined(ENABLE_FEATURE_EXECVE) */\n        msg(M_WARN, \"openvpn_execve: execve function not available\");\n#endif /* if defined(ENABLE_FEATURE_EXECVE) */\n    }\n    else\n    {\n        msg(M_FATAL, \"openvpn_execve: called with empty argv\");\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n#endif /* ifndef _WIN32 */\n\n/*\n * Wrapper around openvpn_execve\n */\nint\nopenvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags,\n                     const char *error_message)\n{\n    struct gc_arena gc = gc_new();\n    const int stat = openvpn_execve(a, es, flags);\n    int ret = false;\n\n    if (flags & S_EXITCODE)\n    {\n        ret = platform_ret_code(stat);\n        if (ret != -1)\n        {\n            goto done;\n        }\n    }\n    else if (flags & S_NOWAITPID && (stat > 0))\n    {\n        ret = stat;\n        goto done;\n    }\n    else if (platform_system_ok(stat))\n    {\n        ret = true;\n        goto done;\n    }\n    if (error_message)\n    {\n        msg(((flags & S_FATAL) ? M_FATAL : M_WARN), \"%s: %s\", error_message,\n            system_error_message(stat, &gc));\n    }\ndone:\n    gc_free(&gc);\n\n    return ret;\n}\n\n/*\n * Run execve() inside a fork(), duping stdout.  Designed to replicate the semantics of popen() but\n * in a safer way that doesn't require the invocation of a shell or the risks\n * associated with formatting and parsing a command line.\n */\nint\nopenvpn_popen(const struct argv *a, const struct env_set *es)\n{\n    struct gc_arena gc = gc_new();\n    int ret = -1;\n\n    if (a && a->argv[0])\n    {\n#if defined(ENABLE_FEATURE_EXECVE)\n        static bool warn_shown = false;\n        if (script_security() >= SSEC_BUILT_IN)\n        {\n            const char *cmd = a->argv[0];\n            char *const *argv = a->argv;\n            char *const *envp = (char *const *)make_env_array(es, true, &gc);\n            pid_t pid;\n            int pipe_stdout[2];\n\n            if (pipe(pipe_stdout) == 0)\n            {\n                pid = fork();\n                if (pid == (pid_t)0)       /* child side */\n                {\n                    close(pipe_stdout[0]); /* Close read end */\n                    dup2(pipe_stdout[1], 1);\n                    execve(cmd, argv, envp);\n                    exit(OPENVPN_EXECVE_FAILURE);\n                }\n                else if (pid > (pid_t)0) /* parent side */\n                {\n                    int status = 0;\n\n                    close(pipe_stdout[1]); /* Close write end */\n                    waitpid(pid, &status, 0);\n                    ret = pipe_stdout[0];\n                }\n                else /* fork failed */\n                {\n                    close(pipe_stdout[0]);\n                    close(pipe_stdout[1]);\n                    msg(M_ERR, \"openvpn_popen: unable to fork %s\", cmd);\n                }\n            }\n            else\n            {\n                msg(M_WARN, \"openvpn_popen: unable to create stdout pipe for %s\", cmd);\n                ret = -1;\n            }\n        }\n        else if (!warn_shown && (script_security() < SSEC_SCRIPTS))\n        {\n            msg(M_WARN, SCRIPT_SECURITY_WARNING);\n            warn_shown = true;\n        }\n#else  /* if defined(ENABLE_FEATURE_EXECVE) */\n        msg(M_WARN, \"openvpn_popen: execve function not available\");\n#endif /* if defined(ENABLE_FEATURE_EXECVE) */\n    }\n    else\n    {\n        msg(M_FATAL, \"openvpn_popen: called with empty argv\");\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n"
  },
  {
    "path": "src/openvpn/run_command.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Technologies, Inc. <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef RUN_COMMAND_H\n#define RUN_COMMAND_H\n\n#include \"basic.h\"\n#include \"env_set.h\"\n\n/* Script security */\n/** strictly no calling of external programs */\n#define SSEC_NONE     0\n/** only call built-in programs such as ifconfig, route, netsh, etc.*/\n#define SSEC_BUILT_IN 1\n/** allow calling of built-in programs and user-defined scripts */\n#define SSEC_SCRIPTS  2\n/** allow calling of built-in programs and user-defined scripts that may receive a password\n    as an environmental variable */\n#define SSEC_PW_ENV   3\n\n#define OPENVPN_EXECVE_ERROR       -1  /* generic error while forking to run an external program */\n#define OPENVPN_EXECVE_NOT_ALLOWED -2  /* external program not run due to script security */\n#define OPENVPN_EXECVE_FAILURE     127 /* exit code passed back from child when execve fails */\n\nint script_security(void);\n\nvoid script_security_set(int level);\n\n/* openvpn_execve flags */\n#define S_SCRIPT    (1 << 0)\n#define S_FATAL     (1 << 1)\n/** Instead of returning 1/0 for success/fail,\n * return exit code when between 0 and 255 and -1 otherwise */\n#define S_EXITCODE  (1 << 2)\n/** instead of waiting for child process to exit and report the status,\n * return the pid of the child process */\n#define S_NOWAITPID (1 << 3)\n\n/* wrapper around the execve() call */\nint openvpn_popen(const struct argv *a, const struct env_set *es);\n\nbool openvpn_execve_allowed(const unsigned int flags);\n\nint openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags,\n                         const char *error_message);\n\n\n#ifndef WIN32\n/** Checks if a running process is still running. This is mainly useful\n * for processes started with \\c S_NOWAITPID\n *\n * This function is currently not implemented for Windows as the helper\n * macros used by this function are not available.\n *\n * @param pid            pid of the process to be checked\n * @param msg_prefix     prefixed of the message that be printed\n * @param msglevel       msglevel of the messages to be printed\n * @return               true if the process is still running, false if\n *                       an error condition occurred\n */\nbool openvpn_waitpid_check(pid_t pid, const char *msg_prefix, msglvl_t msglevel);\n\n#endif\n\n/**\n * Will run a script and return the exit code of the script if between\n * 0 and 255, -1 otherwise\n */\nstatic inline int\nopenvpn_run_script(const struct argv *a, const struct env_set *es, const unsigned int flags,\n                   const char *hook)\n{\n    char msg[256];\n\n    snprintf(msg, sizeof(msg), \"WARNING: Failed running command (%s)\", hook);\n    return openvpn_execve_check(a, es, flags | S_SCRIPT, msg);\n}\n\n#endif /* ifndef RUN_COMMAND_H */\n"
  },
  {
    "path": "src/openvpn/schedule.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"buffer.h\"\n#include \"misc.h\"\n#include \"crypto.h\"\n#include \"schedule.h\"\n\n#include \"memdbg.h\"\n\n#ifdef SCHEDULE_TEST\n\nstruct status\n{\n    int sru;\n    int ins;\n    int coll;\n    int lsteps;\n};\n\nstatic struct status z;\n\n#endif\n\n#ifdef ENABLE_DEBUG\nstatic void\nschedule_entry_debug_info(const char *caller, const struct schedule_entry *e)\n{\n    struct gc_arena gc = gc_new();\n    if (e)\n    {\n        dmsg(D_SCHEDULER, \"SCHEDULE: %s wakeup=[%s] pri=%u\", caller, tv_string_abs(&e->tv, &gc),\n             e->pri);\n    }\n    else\n    {\n        dmsg(D_SCHEDULER, \"SCHEDULE: %s NULL\", caller);\n    }\n    gc_free(&gc);\n}\n#endif\n\nstatic inline void\nschedule_set_pri(struct schedule_entry *e)\n{\n    e->pri = (unsigned int)random();\n    if (e->pri < 1)\n    {\n        e->pri = 1;\n    }\n}\n\n/* This is the master key comparison routine.  A key is\n * simply a struct timeval containing the absolute time for\n * an event.  The unique treap priority (pri) is used to ensure\n * that keys do not collide.\n */\nstatic inline int\nschedule_entry_compare(const struct schedule_entry *e1, const struct schedule_entry *e2)\n{\n    if (e1->tv.tv_sec < e2->tv.tv_sec)\n    {\n        return -1;\n    }\n    else if (e1->tv.tv_sec > e2->tv.tv_sec)\n    {\n        return 1;\n    }\n    else\n    {\n        if (e1->tv.tv_usec < e2->tv.tv_usec)\n        {\n            return -1;\n        }\n        else if (e1->tv.tv_usec > e2->tv.tv_usec)\n        {\n            return 1;\n        }\n        else\n        {\n            if (e1->pri < e2->pri)\n            {\n                return -1;\n            }\n            else if (e1->pri > e2->pri)\n            {\n                return 1;\n            }\n            else\n            {\n                return 0;\n            }\n        }\n    }\n}\n\n/*\n * Detach a btree node from its parent\n */\nstatic inline void\nschedule_detach_parent(struct schedule *s, struct schedule_entry *e)\n{\n    if (e)\n    {\n        if (e->parent)\n        {\n            if (e->parent->lt == e)\n            {\n                e->parent->lt = NULL;\n            }\n            else if (e->parent->gt == e)\n            {\n                e->parent->gt = NULL;\n            }\n            else\n            {\n                /* parent <-> child linkage is corrupted */\n                ASSERT(0);\n            }\n            e->parent = NULL;\n        }\n        else\n        {\n            if (s->root == e) /* last element deleted, tree is empty */\n            {\n                s->root = NULL;\n            }\n        }\n    }\n}\n\n/*\n *\n * Given a binary search tree, move a node toward the root\n * while still maintaining the correct ordering relationships\n * within the tree.  This function is the workhorse\n * of the tree balancer.\n *\n * This code will break on key collisions, which shouldn't\n * happen because the treap priority is considered part of the key\n * and is guaranteed to be unique.\n */\nstatic void\nschedule_rotate_up(struct schedule *s, struct schedule_entry *e)\n{\n    if (e && e->parent)\n    {\n        struct schedule_entry *lt = e->lt;\n        struct schedule_entry *gt = e->gt;\n        struct schedule_entry *p = e->parent;\n        struct schedule_entry *gp = p->parent;\n\n        if (gp) /* if grandparent exists, modify its child link */\n        {\n            if (gp->gt == p)\n            {\n                gp->gt = e;\n            }\n            else if (gp->lt == p)\n            {\n                gp->lt = e;\n            }\n            else\n            {\n                ASSERT(0);\n            }\n        }\n        else /* no grandparent, now we are the root */\n        {\n            s->root = e;\n        }\n\n        /* grandparent is now our parent */\n        e->parent = gp;\n\n        /* parent is now our child */\n        p->parent = e;\n\n        /* reorient former parent's links\n         * to reflect new position in the tree */\n        if (p->gt == e)\n        {\n            e->lt = p;\n            p->gt = lt;\n            if (lt)\n            {\n                lt->parent = p;\n            }\n        }\n        else if (p->lt == e)\n        {\n            e->gt = p;\n            p->lt = gt;\n            if (gt)\n            {\n                gt->parent = p;\n            }\n        }\n        else\n        {\n            /* parent <-> child linkage is corrupted */\n            ASSERT(0);\n        }\n\n#ifdef SCHEDULE_TEST\n        ++z.sru;\n#endif\n    }\n}\n\n/*\n * This is the treap deletion algorithm:\n *\n * Rotate lesser-priority children up in the tree\n * until we are childless.  Then delete.\n */\nvoid\nschedule_remove_node(struct schedule *s, struct schedule_entry *e)\n{\n    while (e->lt || e->gt)\n    {\n        if (e->lt)\n        {\n            if (e->gt)\n            {\n                if (e->lt->pri < e->gt->pri)\n                {\n                    schedule_rotate_up(s, e->lt);\n                }\n                else\n                {\n                    schedule_rotate_up(s, e->gt);\n                }\n            }\n            else\n            {\n                schedule_rotate_up(s, e->lt);\n            }\n        }\n        else if (e->gt)\n        {\n            schedule_rotate_up(s, e->gt);\n        }\n    }\n\n    schedule_detach_parent(s, e);\n    e->pri = 0;\n}\n\n/*\n * Trivially add a node to a binary search tree without\n * regard for balance.\n */\nstatic void\nschedule_insert(struct schedule *s, struct schedule_entry *e)\n{\n    struct schedule_entry *c = s->root;\n    while (true)\n    {\n        const int comp = schedule_entry_compare(e, c);\n\n#ifdef SCHEDULE_TEST\n        ++z.ins;\n#endif\n\n        if (comp == -1)\n        {\n            if (c->lt)\n            {\n                c = c->lt;\n                continue;\n            }\n            else\n            {\n                c->lt = e;\n                e->parent = c;\n                break;\n            }\n        }\n        else if (comp == 1)\n        {\n            if (c->gt)\n            {\n                c = c->gt;\n                continue;\n            }\n            else\n            {\n                c->gt = e;\n                e->parent = c;\n                break;\n            }\n        }\n        else\n        {\n            /* rare key/priority collision -- no big deal,\n             * just choose another priority and retry */\n#ifdef SCHEDULE_TEST\n            ++z.coll;\n#endif\n            schedule_set_pri(e);\n            /* msg (M_INFO, \"PRI COLLISION pri=%u\", e->pri); */\n            c = s->root;\n            continue;\n        }\n    }\n}\n\n/*\n * Given an element, remove it from the btree if it's already\n * there and re-insert it based on its current key.\n */\nvoid\nschedule_add_modify(struct schedule *s, struct schedule_entry *e)\n{\n#ifdef ENABLE_DEBUG\n    if (check_debug_level(D_SCHEDULER))\n    {\n        schedule_entry_debug_info(\"schedule_add_modify\", e);\n    }\n#endif\n\n    /* already in tree, remove */\n    if (IN_TREE(e))\n    {\n        schedule_remove_node(s, e);\n    }\n\n    /* set random priority */\n    schedule_set_pri(e);\n\n    if (s->root)\n    {\n        schedule_insert(s, e); /* trivial insert into tree */\n    }\n    else\n    {\n        s->root = e; /* tree was empty, we are the first element */\n    }\n    /* This is the magic of the randomized treap algorithm which\n     * keeps the tree balanced.  Move the node up the tree until\n     * its own priority is greater than that of its parent */\n    while (e->parent && e->parent->pri > e->pri)\n    {\n        schedule_rotate_up(s, e);\n    }\n}\n\n/*\n * Find the earliest event to be scheduled\n */\nstruct schedule_entry *\nschedule_find_least(struct schedule_entry *e)\n{\n    if (e)\n    {\n        while (e->lt)\n        {\n#ifdef SCHEDULE_TEST\n            ++z.lsteps;\n#endif\n            e = e->lt;\n        }\n    }\n\n#ifdef ENABLE_DEBUG\n    if (check_debug_level(D_SCHEDULER))\n    {\n        schedule_entry_debug_info(\"schedule_find_least\", e);\n    }\n#endif\n\n    return e;\n}\n\n/*\n *  Public functions below this point\n */\n\nstruct schedule *\nschedule_init(void)\n{\n    struct schedule *s;\n\n    ALLOC_OBJ_CLEAR(s, struct schedule);\n    return s;\n}\n\nvoid\nschedule_free(struct schedule *s)\n{\n    free(s);\n}\n\nvoid\nschedule_remove_entry(struct schedule *s, struct schedule_entry *e)\n{\n    s->earliest_wakeup = NULL; /* invalidate cache */\n    schedule_remove_node(s, e);\n}\n\n/*\n *  Debug functions below this point\n */\n\n#ifdef SCHEDULE_TEST\n\nstatic inline struct schedule_entry *\nschedule_find_earliest_wakeup(struct schedule *s)\n{\n    return schedule_find_least(s->root);\n}\n\n/*\n * Recursively check that the treap (btree) is\n * internally consistent.\n */\nint\nschedule_debug_entry(const struct schedule_entry *e, int depth, int *count, struct timeval *least,\n                     const struct timeval *min, const struct timeval *max)\n{\n    struct gc_arena gc = gc_new();\n    int maxdepth = depth;\n    if (e)\n    {\n        int d;\n\n        ASSERT(e != e->lt);\n        ASSERT(e != e->gt);\n        ASSERT(e != e->parent);\n        ASSERT(!e->parent || e->parent != e->lt);\n        ASSERT(!e->parent || e->parent != e->gt);\n        ASSERT(!e->lt || e->lt != e->gt);\n\n        if (e->lt)\n        {\n            ASSERT(e->lt->parent == e);\n            ASSERT(schedule_entry_compare(e->lt, e) == -1);\n            ASSERT(e->lt->pri >= e->pri);\n        }\n\n        if (e->gt)\n        {\n            ASSERT(e->gt->parent == e);\n            ASSERT(schedule_entry_compare(e->gt, e));\n            ASSERT(e->gt->pri >= e->pri);\n        }\n\n        ASSERT(tv_le(min, &e->tv));\n        ASSERT(tv_le(&e->tv, max));\n\n        if (count)\n        {\n            ++(*count);\n        }\n\n        if (least && tv_lt(&e->tv, least))\n        {\n            *least = e->tv;\n        }\n\n        d = schedule_debug_entry(e->lt, depth + 1, count, least, min, &e->tv);\n        if (d > maxdepth)\n        {\n            maxdepth = d;\n        }\n\n        d = schedule_debug_entry(e->gt, depth + 1, count, least, &e->tv, max);\n        if (d > maxdepth)\n        {\n            maxdepth = d;\n        }\n    }\n    gc_free(&gc);\n    return maxdepth;\n}\n\nint\nschedule_debug(struct schedule *s, int *count, struct timeval *least)\n{\n    struct timeval min;\n    struct timeval max;\n\n    min.tv_sec = 0;\n    min.tv_usec = 0;\n    max.tv_sec = 0x7FFFFFFF;\n    max.tv_usec = 0x7FFFFFFF;\n\n    if (s->root)\n    {\n        ASSERT(s->root->parent == NULL);\n    }\n    return schedule_debug_entry(s->root, 0, count, least, &min, &max);\n}\n\n#if 1\n\nvoid\ntv_randomize(struct timeval *tv)\n{\n    tv->tv_sec += random() % 100;\n    tv->tv_usec = random() % 100;\n}\n\n#else  /* if 1 */\n\nvoid\ntv_randomize(struct timeval *tv)\n{\n    struct gc_arena gc = gc_new();\n    long int choice = get_random();\n    if ((choice & 0xFF) == 0)\n    {\n        tv->tv_usec += ((choice >> 8) & 0xFF);\n    }\n    else\n    {\n        prng_bytes((uint8_t *)tv, sizeof(struct timeval));\n    }\n    gc_free(&gc);\n}\n\n#endif /* if 1 */\n\nvoid\nschedule_verify(struct schedule *s)\n{\n    struct gc_arena gc = gc_new();\n    struct timeval least;\n    int count;\n    int maxlev;\n    struct schedule_entry *e;\n    const struct status zz = z;\n\n    least.tv_sec = least.tv_usec = 0x7FFFFFFF;\n\n    count = 0;\n\n    maxlev = schedule_debug(s, &count, &least);\n\n    e = schedule_find_earliest_wakeup(s);\n\n    if (e)\n    {\n        printf(\"Verification Phase  count=%d maxlev=%d sru=%d ins=%d coll=%d ls=%d l=%s\", count,\n               maxlev, zz.sru, zz.ins, zz.coll, zz.lsteps, tv_string(&e->tv, &gc));\n\n        if (!tv_eq(&least, &e->tv))\n        {\n            printf(\" [COMPUTED DIFFERENT MIN VALUES!]\");\n        }\n\n        printf(\"\\n\");\n    }\n\n    CLEAR(z);\n    gc_free(&gc);\n}\n\nvoid\nschedule_randomize_array(struct schedule_entry **array, int size)\n{\n    int i;\n    for (i = 0; i < size; ++i)\n    {\n        const int src = get_random() % size;\n        struct schedule_entry *tmp = array[i];\n        if (i != src)\n        {\n            array[i] = array[src];\n            array[src] = tmp;\n        }\n    }\n}\n\nvoid\nschedule_print_work(struct schedule_entry *e, int indent)\n{\n    struct gc_arena gc = gc_new();\n    int i;\n    for (i = 0; i < indent; ++i)\n    {\n        printf(\" \");\n    }\n    if (e)\n    {\n        printf(\"%s [%u] e=\" ptr_format \", p=\" ptr_format \" lt=\" ptr_format \" gt=\" ptr_format \"\\n\",\n               tv_string(&e->tv, &gc), e->pri, (ptr_type)e, (ptr_type)e->parent, (ptr_type)e->lt,\n               (ptr_type)e->gt);\n        schedule_print_work(e->lt, indent + 1);\n        schedule_print_work(e->gt, indent + 1);\n    }\n    else\n    {\n        printf(\"NULL\\n\");\n    }\n    gc_free(&gc);\n}\n\nvoid\nschedule_print(struct schedule *s)\n{\n    printf(\"*************************\\n\");\n    schedule_print_work(s->root, 0);\n}\n\nvoid\nschedule_test(void)\n{\n    struct gc_arena gc = gc_new();\n    int n = 1000;\n    int n_mod = 25;\n\n    int i, j;\n    struct schedule_entry **array;\n    struct schedule *s = schedule_init();\n    struct schedule_entry *e;\n\n    CLEAR(z);\n    ALLOC_ARRAY(array, struct schedule_entry *, n);\n\n    printf(\"Creation/Insertion Phase\\n\");\n\n    for (i = 0; i < n; ++i)\n    {\n        ALLOC_OBJ_CLEAR(array[i], struct schedule_entry);\n        tv_randomize(&array[i]->tv);\n        /*schedule_print (s);*/\n        /*schedule_verify (s);*/\n        schedule_add_modify(s, array[i]);\n    }\n\n    schedule_randomize_array(array, n);\n\n    /*schedule_print (s);*/\n    schedule_verify(s);\n\n    for (j = 1; j <= n_mod; ++j)\n    {\n        printf(\"Modification Phase Pass %d\\n\", j);\n\n        for (i = 0; i < n; ++i)\n        {\n            e = schedule_find_earliest_wakeup(s);\n            /*printf (\"BEFORE %s\\n\", tv_string (&e->tv, &gc));*/\n            tv_randomize(&e->tv);\n            /*printf (\"AFTER %s\\n\", tv_string (&e->tv, &gc));*/\n            schedule_add_modify(s, e);\n            /*schedule_verify (s);*/\n            /*schedule_print (s);*/\n        }\n        schedule_verify(s);\n        /*schedule_print (s);*/\n    }\n\n    /*printf (\"INS=%d\\n\", z.ins);*/\n\n    while ((e = schedule_find_earliest_wakeup(s)))\n    {\n        schedule_remove_node(s, e);\n        /*schedule_verify (s);*/\n    }\n    schedule_verify(s);\n\n    printf(\"S->ROOT is %s\\n\", s->root ? \"NOT NULL\" : \"NULL\");\n\n    for (i = 0; i < n; ++i)\n    {\n        free(array[i]);\n    }\n    free(array);\n    free(s);\n    gc_free(&gc);\n}\n\n#endif /* ifdef SCHEDULE_TEST */\n"
  },
  {
    "path": "src/openvpn/schedule.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef SCHEDULE_H\n#define SCHEDULE_H\n\n/*\n * This code implements an efficient scheduler using\n * a random treap binary tree.\n *\n * The scheduler is used by the server executive to\n * keep track of which instances need service at a\n * known time in the future.  Instances need to\n * schedule events for things such as sending\n * a ping or scheduling a TLS renegotiation.\n */\n\n/* define to enable a special test mode */\n/*#define SCHEDULE_TEST*/\n\n#include \"otime.h\"\n#include \"error.h\"\n\nstruct schedule_entry\n{\n    struct timeval tv;             /* wakeup time */\n    unsigned int pri;              /* random treap priority */\n    struct schedule_entry *parent; /* treap (btree) links */\n    struct schedule_entry *lt;\n    struct schedule_entry *gt;\n};\n\nstruct schedule\n{\n    struct schedule_entry *earliest_wakeup; /* cached earliest wakeup */\n    struct schedule_entry *root;            /* the root of the treap (btree) */\n};\n\n/* Public functions */\n\nstruct schedule *schedule_init(void);\n\nvoid schedule_free(struct schedule *s);\n\nvoid schedule_remove_entry(struct schedule *s, struct schedule_entry *e);\n\n#ifdef SCHEDULE_TEST\nvoid schedule_test(void);\n\n#endif\n\n/* Private Functions */\n\n/* is node already in tree? */\n#define IN_TREE(e) ((e)->pri)\n\nstruct schedule_entry *schedule_find_least(struct schedule_entry *e);\n\nvoid schedule_add_modify(struct schedule *s, struct schedule_entry *e);\n\nvoid schedule_remove_node(struct schedule *s, struct schedule_entry *e);\n\n/* Public inline functions */\n\n/**\n * Add a struct schedule_entry to the scheduler btree or\n * update an existing entry with a new wakeup time.\n *\n * @p sigma is only used when the entry is already present\n * in the schedule. If the originally scheduled time and the new\n * time are within @p sigma microseconds of each other then the\n * entry is not rescheduled and will occur at the original time.\n * When adding a new entry @p sigma will be ignored.\n *\n * @param s     scheduler tree\n * @param e     entry to add to the schedule\n * @param tv    wakeup time for the entry\n * @param sigma window size for the event in microseconds\n *\n * @note The caller should treat @p e as opaque data. Only\n * the scheduler functions should change the object. The\n * caller is expected to manage the memory for the object\n * and must only free it once it has been removed from the\n * schedule.\n */\nstatic inline void\nschedule_add_entry(struct schedule *s, struct schedule_entry *e, const struct timeval *tv,\n                   unsigned int sigma)\n{\n    if (!IN_TREE(e) || !sigma || !tv_within_sigma(tv, &e->tv, sigma))\n    {\n        e->tv = *tv;\n        schedule_add_modify(s, e);\n        s->earliest_wakeup = NULL; /* invalidate cache */\n    }\n}\n\n/*\n * Return the node with the earliest wakeup time.  If two\n * nodes have the exact same wakeup time, select based on\n * the random priority assigned to each node (the priority\n * is randomized every time an entry is re-added).\n */\nstatic inline struct schedule_entry *\nschedule_get_earliest_wakeup(struct schedule *s, struct timeval *wakeup)\n{\n    struct schedule_entry *ret;\n\n    /* cache result */\n    if (!s->earliest_wakeup)\n    {\n        s->earliest_wakeup = schedule_find_least(s->root);\n    }\n    ret = s->earliest_wakeup;\n    if (ret)\n    {\n        *wakeup = ret->tv;\n    }\n\n    return ret;\n}\n\n#endif /* ifndef SCHEDULE_H */\n"
  },
  {
    "path": "src/openvpn/session_id.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * Each session is identified by a random 8-byte session identifier.\n *\n * For efficiency, the session id is only transmitted over the control\n * channel (which only sees traffic occasionally when keys are being\n * negotiated).  The data channel sees a smaller version of the session-id --\n * it is called the key_id and is currently 2 bits long.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"error.h\"\n#include \"common.h\"\n#include \"crypto.h\"\n#include \"session_id.h\"\n\n#include \"memdbg.h\"\n\nconst struct session_id x_session_id_zero;\n\nvoid\nsession_id_random(struct session_id *sid)\n{\n    prng_bytes(sid->id, SID_SIZE);\n}\n\nconst char *\nsession_id_print(const struct session_id *sid, struct gc_arena *gc)\n{\n    return format_hex(sid->id, SID_SIZE, 0, gc);\n}\n"
  },
  {
    "path": "src/openvpn/session_id.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * Each session is identified by a random 8-byte session identifier.\n *\n * For efficiency, the session id is only transmitted over the control\n * channel (which only sees traffic occasionally when keys are being\n * negotiated).\n */\n\n#ifndef SESSION_ID_H\n#define SESSION_ID_H\n\n#include \"basic.h\"\n#include \"buffer.h\"\n\nstruct session_id\n{\n    uint8_t id[8];\n};\n\nextern const struct session_id x_session_id_zero;\n\n#define SID_SIZE (sizeof(x_session_id_zero.id))\n\nstatic inline bool\nsession_id_equal(const struct session_id *sid1, const struct session_id *sid2)\n{\n    return !memcmp(sid1->id, sid2->id, SID_SIZE);\n}\n\nstatic inline bool\nsession_id_defined(const struct session_id *sid1)\n{\n    return memcmp(sid1->id, &x_session_id_zero.id, SID_SIZE) != 0;\n}\n\nstatic inline bool\nsession_id_read(struct session_id *sid, struct buffer *buf)\n{\n    return buf_read(buf, sid->id, SID_SIZE);\n}\n\nstatic inline bool\nsession_id_write_prepend(const struct session_id *sid, struct buffer *buf)\n{\n    return buf_write_prepend(buf, sid->id, SID_SIZE);\n}\n\nstatic inline bool\nsession_id_write(const struct session_id *sid, struct buffer *buf)\n{\n    return buf_write(buf, sid->id, SID_SIZE);\n}\n\nvoid session_id_random(struct session_id *sid);\n\nconst char *session_id_print(const struct session_id *sid, struct gc_arena *gc);\n\n#endif /* SESSION_ID_H */\n"
  },
  {
    "path": "src/openvpn/shaper.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n#include \"shaper.h\"\n#include \"memdbg.h\"\n\n/*\n * We want to wake up in delay microseconds.  If timeval is larger\n * than delay, set timeval to delay.\n */\nbool\nshaper_soonest_event(struct timeval *tv, int delay)\n{\n    bool ret = false;\n    if (delay < 1000000)\n    {\n        if (tv->tv_sec)\n        {\n            tv->tv_sec = 0;\n            tv->tv_usec = delay;\n            ret = true;\n        }\n        else if (delay < tv->tv_usec)\n        {\n            tv->tv_usec = delay;\n            ret = true;\n        }\n    }\n    else\n    {\n        const int sec = delay / 1000000;\n        const int usec = delay % 1000000;\n\n        if (sec < tv->tv_sec)\n        {\n            tv->tv_sec = sec;\n            tv->tv_usec = usec;\n            ret = true;\n        }\n        else if (sec == tv->tv_sec)\n        {\n            if (usec < tv->tv_usec)\n            {\n                tv->tv_usec = usec;\n                ret = true;\n            }\n        }\n    }\n#ifdef SHAPER_DEBUG\n    dmsg(D_SHAPER_DEBUG, \"SHAPER shaper_soonest_event sec=%\" PRIi64 \" usec=%ld ret=%d\",\n         (int64_t)tv->tv_sec, (long)tv->tv_usec, (int)ret);\n#endif\n    return ret;\n}\n\nvoid\nshaper_reset_wakeup(struct shaper *s)\n{\n    CLEAR(s->wakeup);\n}\n\nvoid\nshaper_msg(struct shaper *s)\n{\n    msg(M_INFO, \"Output Traffic Shaping initialized at %d bytes per second\", s->bytes_per_second);\n}\n"
  },
  {
    "path": "src/openvpn/shaper.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef SHAPER_H\n#define SHAPER_H\n\n/*#define SHAPER_DEBUG*/\n\n#include \"basic.h\"\n#include \"integer.h\"\n#include \"misc.h\"\n#include \"error.h\"\n#include \"interval.h\"\n\n/*\n * A simple traffic shaper for\n * the output direction.\n */\n\n#define SHAPER_MIN 100 /* bytes per second */\n#define SHAPER_MAX 100000000\n\n#define SHAPER_MAX_TIMEOUT 10 /* seconds */\n\n#define SHAPER_USE_FP\n\nstruct shaper\n{\n    int bytes_per_second;\n    struct timeval wakeup;\n\n#ifdef SHAPER_USE_FP\n    double factor;\n#else\n    int factor;\n#endif\n};\n\nvoid shaper_msg(struct shaper *s);\n\nvoid shaper_reset_wakeup(struct shaper *s);\n\n/*\n * We want to wake up in delay microseconds.  If timeval is larger\n * than delay, set timeval to delay.\n */\nbool shaper_soonest_event(struct timeval *tv, int delay);\n\n/*\n * inline functions\n */\n\nstatic inline void\nshaper_reset(struct shaper *s, int bytes_per_second)\n{\n    s->bytes_per_second = constrain_int(bytes_per_second, SHAPER_MIN, SHAPER_MAX);\n\n#ifdef SHAPER_USE_FP\n    s->factor = 1000000.0 / (double)s->bytes_per_second;\n#else\n    s->factor = 1000000 / s->bytes_per_second;\n#endif\n}\n\nstatic inline void\nshaper_init(struct shaper *s, int bytes_per_second)\n{\n    shaper_reset(s, bytes_per_second);\n    shaper_reset_wakeup(s);\n}\n\n/*\n * Returns traffic shaping delay in microseconds relative to current\n * time, or 0 if no delay.\n */\nstatic inline int\nshaper_delay(struct shaper *s)\n{\n    struct timeval tv;\n    int delay = 0;\n\n    if (tv_defined(&s->wakeup))\n    {\n        ASSERT(!openvpn_gettimeofday(&tv, NULL));\n        delay = tv_subtract(&s->wakeup, &tv, SHAPER_MAX_TIMEOUT);\n#ifdef SHAPER_DEBUG\n        dmsg(D_SHAPER_DEBUG, \"SHAPER shaper_delay delay=%d\", delay);\n#endif\n    }\n\n    return delay > 0 ? delay : 0;\n}\n\n\n/*\n * We are about to send a datagram of nbytes bytes.\n *\n * Compute when we can send another datagram,\n * based on target throughput (s->bytes_per_second).\n */\nstatic inline void\nshaper_wrote_bytes(struct shaper *s, int nbytes)\n{\n    struct timeval tv;\n\n    /* compute delay in microseconds */\n    tv.tv_sec = 0;\n#ifdef SHAPER_USE_FP\n    tv.tv_usec =\n        min_int((int)((double)max_int(nbytes, 100) * s->factor), (SHAPER_MAX_TIMEOUT * 1000000));\n#else\n    tv.tv_usec = s->bytes_per_second\n                     ? min_int(max_int(nbytes, 100) * s->factor, (SHAPER_MAX_TIMEOUT * 1000000))\n                     : 0;\n#endif\n\n    if (tv.tv_usec)\n    {\n        ASSERT(!openvpn_gettimeofday(&s->wakeup, NULL));\n        tv_add(&s->wakeup, &tv);\n\n#ifdef SHAPER_DEBUG\n        dmsg(D_SHAPER_DEBUG,\n             \"SHAPER shaper_wrote_bytes bytes=%d delay=%ld sec=%\" PRIi64 \" usec=%ld\", nbytes,\n             (long)tv.tv_usec, (int64_t)s->wakeup.tv_sec, (long)s->wakeup.tv_usec);\n#endif\n    }\n}\n\n#if 0\n/*\n * Increase/Decrease bandwidth by a percentage.\n *\n * Return true if bandwidth changed.\n */\nstatic inline bool\nshaper_change_pct(struct shaper *s, int pct)\n{\n    const int orig_bandwidth = s->bytes_per_second;\n    const int new_bandwidth = orig_bandwidth + (orig_bandwidth * pct / 100);\n    ASSERT(s->bytes_per_second);\n    shaper_reset(s, new_bandwidth);\n    return s->bytes_per_second != orig_bandwidth;\n}\n#endif\n\n#endif /* ifndef SHAPER_H */\n"
  },
  {
    "path": "src/openvpn/sig.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2016-2026 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"win32.h\"\n#include \"init.h\"\n#include \"status.h\"\n#include \"sig.h\"\n#include \"occ.h\"\n#include \"manage.h\"\n#include \"openvpn.h\"\n\n#include \"memdbg.h\"\n\n/* Handle signals */\n\nstruct signal_info siginfo_static; /* GLOBAL */\n\nstruct signame\n{\n    int value;\n    int priority;\n    const char *upper;\n    const char *lower;\n};\n\nstatic const struct signame signames[] = { { SIGINT, 5, \"SIGINT\", \"sigint\" },\n                                           { SIGTERM, 4, \"SIGTERM\", \"sigterm\" },\n                                           { SIGHUP, 3, \"SIGHUP\", \"sighup\" },\n                                           { SIGUSR1, 2, \"SIGUSR1\", \"sigusr1\" },\n                                           { SIGUSR2, 1, \"SIGUSR2\", \"sigusr2\" } };\n\n/* mask for hard signals from management or windows */\nstatic unsigned long long ignored_hard_signals_mask;\n\nint\nparse_signal(const char *signame)\n{\n    int i;\n    for (i = 0; i < (int)SIZE(signames); ++i)\n    {\n        if (!strcmp(signame, signames[i].upper))\n        {\n            return signames[i].value;\n        }\n    }\n    return -1;\n}\n\nstatic int\nsignal_priority(int sig)\n{\n    for (size_t i = 0; i < SIZE(signames); ++i)\n    {\n        if (sig == signames[i].value)\n        {\n            return signames[i].priority;\n        }\n    }\n    return -1;\n}\n\nconst char *\nsignal_name(const int sig, const bool upper)\n{\n    int i;\n    for (i = 0; i < (int)SIZE(signames); ++i)\n    {\n        if (sig == signames[i].value)\n        {\n            return upper ? signames[i].upper : signames[i].lower;\n        }\n    }\n    return \"UNKNOWN\";\n}\n\nconst char *\nsignal_description(const int signum, const char *sigtext)\n{\n    if (sigtext)\n    {\n        return sigtext;\n    }\n    else\n    {\n        return signal_name(signum, false);\n    }\n}\n\n/**\n * Block (i.e., defer) all unix signals.\n * Used while directly modifying the volatile elements of\n * siginfo_static.\n */\nstatic inline void\nblock_async_signals(void)\n{\n#ifndef _WIN32\n    sigset_t all;\n    sigfillset(&all); /* all signals */\n    sigprocmask(SIG_BLOCK, &all, NULL);\n#endif\n}\n\n/**\n * Unblock all unix signals.\n */\nstatic inline void\nunblock_async_signals(void)\n{\n#ifndef _WIN32\n    sigset_t none;\n    sigemptyset(&none);\n    sigprocmask(SIG_SETMASK, &none, NULL);\n#endif\n}\n\n/**\n * Private function for registering a signal in the specified\n * signal_info struct. This could be the global siginfo_static\n * or a context specific signinfo struct.\n *\n * A signal is allowed to override an already registered\n * one only if it has a higher priority.\n * Returns true if the signal is set, false otherwise.\n *\n * Do not call any \"AS-unsafe\" functions such as printf from here\n * as this may be called from signal_handler().\n */\nstatic bool\ntry_throw_signal(struct signal_info *si, int signum, int source)\n{\n    bool ret = false;\n    if (signal_priority(signum) >= signal_priority(si->signal_received))\n    {\n        si->signal_received = signum;\n        si->source = source;\n        ret = true;\n    }\n    return ret;\n}\n\n/**\n * Throw a hard signal. Called from management and when windows\n * signals are received through ctrl-c, exit event etc.\n */\nvoid\nthrow_signal(const int signum)\n{\n    if (ignored_hard_signals_mask & (1LL << signum))\n    {\n        msg(D_SIGNAL_DEBUG, \"Signal %s is currently ignored\", signal_name(signum, true));\n        return;\n    }\n    block_async_signals();\n\n    if (!try_throw_signal(&siginfo_static, signum, SIG_SOURCE_HARD))\n    {\n        msg(D_SIGNAL_DEBUG, \"Ignoring %s when %s has been received\", signal_name(signum, true),\n            signal_name(siginfo_static.signal_received, true));\n    }\n    else\n    {\n        msg(D_SIGNAL_DEBUG, \"Throw signal (hard): %s \", signal_name(signum, true));\n    }\n\n    unblock_async_signals();\n}\n\n/**\n * Throw a soft global signal. Used to register internally generated signals\n * due to errors that require a restart or exit, or restart requests\n * received from the server. A textual description of the signal may\n * be provided.\n */\nvoid\nthrow_signal_soft(const int signum, const char *signal_text)\n{\n    block_async_signals();\n\n    if (try_throw_signal(&siginfo_static, signum, SIG_SOURCE_SOFT))\n    {\n        siginfo_static.signal_text = signal_text;\n        msg(D_SIGNAL_DEBUG, \"Throw signal (soft): %s (%s)\", signal_name(signum, true), signal_text);\n    }\n    else\n    {\n        msg(D_SIGNAL_DEBUG, \"Ignoring %s when %s has been received\", signal_name(signum, true),\n            signal_name(siginfo_static.signal_received, true));\n    }\n\n    unblock_async_signals();\n}\n\n/**\n * Register a soft signal in the signal_info struct si respecting priority.\n * si may be a pointer to the global siginfo_static or a context-specific\n * signal in a multi-instance or a temporary variable.\n */\nvoid\nregister_signal(struct signal_info *si, int signum, const char *signal_text)\n{\n    if (si == &siginfo_static) /* attempting to alter the global signal */\n    {\n        block_async_signals();\n    }\n\n    if (try_throw_signal(si, signum, SIG_SOURCE_SOFT))\n    {\n        si->signal_text = signal_text;\n        if (signal_text && strcmp(signal_text, \"connection-failed\") == 0)\n        {\n            si->source = SIG_SOURCE_CONNECTION_FAILED;\n        }\n        msg(D_SIGNAL_DEBUG | M_NOIPREFIX, \"register signal: %s (%s)\", signal_name(signum, true), signal_text);\n    }\n    else\n    {\n        msg(D_SIGNAL_DEBUG, \"Ignoring %s when %s has been received\", signal_name(signum, true),\n            signal_name(si->signal_received, true));\n    }\n\n    if (si == &siginfo_static)\n    {\n        unblock_async_signals();\n    }\n}\n\n/**\n * Clear the signal if its current value equals signum. If\n * signum is zero the signal is cleared independent of its current\n * value. Returns the current value of the signal.\n */\nint\nsignal_reset(struct signal_info *si, int signum)\n{\n    int sig_saved = 0;\n    if (si)\n    {\n        if (si == &siginfo_static) /* attempting to alter the global signal */\n        {\n            block_async_signals();\n        }\n\n        sig_saved = si->signal_received;\n        if (!signum || sig_saved == signum)\n        {\n            si->signal_received = 0;\n            si->signal_text = NULL;\n            si->source = SIG_SOURCE_SOFT;\n            msg(D_SIGNAL_DEBUG, \"signal_reset: signal %s is cleared\", signal_name(signum, true));\n        }\n\n        if (si == &siginfo_static)\n        {\n            unblock_async_signals();\n        }\n    }\n    return sig_saved;\n}\n\nvoid\nprint_signal(const struct signal_info *si, const char *title, msglvl_t msglevel)\n{\n    if (si)\n    {\n        const char *type = (si->signal_text ? si->signal_text : \"\");\n        const char *t = (title ? title : \"process\");\n        const char *hs = NULL;\n        switch (si->source)\n        {\n            case SIG_SOURCE_SOFT:\n                hs = \"soft\";\n                break;\n\n            case SIG_SOURCE_HARD:\n                hs = \"hard\";\n                break;\n\n            case SIG_SOURCE_CONNECTION_FAILED:\n                hs = \"connection failed(soft)\";\n                break;\n\n            default:\n                ASSERT(0);\n        }\n\n        switch (si->signal_received)\n        {\n            case SIGINT:\n            case SIGTERM:\n                msg(msglevel, \"%s[%s,%s] received, %s exiting\",\n                    signal_name(si->signal_received, true), hs, type, t);\n                break;\n\n            case SIGHUP:\n            case SIGUSR1:\n                msg(msglevel, \"%s[%s,%s] received, %s restarting\",\n                    signal_name(si->signal_received, true), hs, type, t);\n                break;\n\n            default:\n                msg(msglevel, \"Unknown signal %d [%s,%s] received by %s\", si->signal_received, hs,\n                    type, t);\n                break;\n        }\n    }\n    else\n    {\n        msg(msglevel, \"Unknown signal received\");\n    }\n}\n\n/*\n * Call management interface with restart info\n */\nvoid\nsignal_restart_status(const struct signal_info *si)\n{\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        int state = -1;\n        switch (si->signal_received)\n        {\n            case SIGINT:\n            case SIGTERM:\n                state = OPENVPN_STATE_EXITING;\n                break;\n\n            case SIGHUP:\n            case SIGUSR1:\n                state = OPENVPN_STATE_RECONNECTING;\n                break;\n        }\n\n        if (state >= 0)\n        {\n            management_set_state(management, state,\n                                 si->signal_text ? si->signal_text\n                                                 : signal_name(si->signal_received, true),\n                                 NULL, NULL, NULL, NULL);\n        }\n    }\n#endif /* ifdef ENABLE_MANAGEMENT */\n}\n\n#ifndef _WIN32\n/* normal signal handler, when we are in event loop */\nstatic void\nsignal_handler(const int signum)\n{\n    try_throw_signal(&siginfo_static, signum, SIG_SOURCE_HARD);\n}\n#endif\n\n/* set handlers for unix signals */\n\n#define SM_UNDEF     0\n#define SM_PRE_INIT  1\n#define SM_POST_INIT 2\nstatic int signal_mode; /* GLOBAL */\n\nvoid\npre_init_signal_catch(void)\n{\n#ifndef _WIN32\n    sigset_t block_mask;\n    struct sigaction sa;\n    CLEAR(sa);\n\n    sigfillset(&block_mask);  /* all signals */\n    sa.sa_handler = signal_handler;\n    sa.sa_mask = block_mask;  /* signals blocked inside the handler */\n    sa.sa_flags = SA_RESTART; /* match with the behaviour of signal() on Linux and BSD */\n\n    signal_mode = SM_PRE_INIT;\n    sigaction(SIGINT, &sa, NULL);\n    sigaction(SIGTERM, &sa, NULL);\n\n    sa.sa_handler = SIG_IGN;\n    sigaction(SIGHUP, &sa, NULL);\n    sigaction(SIGUSR1, &sa, NULL);\n    sigaction(SIGUSR2, &sa, NULL);\n    sigaction(SIGPIPE, &sa, NULL);\n#endif /* _WIN32 */\n    /* clear any pending signals of the ignored type */\n    signal_reset(&siginfo_static, SIGUSR1);\n    signal_reset(&siginfo_static, SIGUSR2);\n    signal_reset(&siginfo_static, SIGHUP);\n}\n\nvoid\npost_init_signal_catch(void)\n{\n#ifndef _WIN32\n    sigset_t block_mask;\n    struct sigaction sa;\n    CLEAR(sa);\n\n    sigfillset(&block_mask);  /* all signals */\n    sa.sa_handler = signal_handler;\n    sa.sa_mask = block_mask;  /* signals blocked inside the handler */\n    sa.sa_flags = SA_RESTART; /* match with the behaviour of signal() on Linux and BSD */\n\n    signal_mode = SM_POST_INIT;\n    sigaction(SIGINT, &sa, NULL);\n    sigaction(SIGTERM, &sa, NULL);\n    sigaction(SIGHUP, &sa, NULL);\n    sigaction(SIGUSR1, &sa, NULL);\n    sigaction(SIGUSR2, &sa, NULL);\n    sa.sa_handler = SIG_IGN;\n    sigaction(SIGPIPE, &sa, NULL);\n#endif /* _WIN32 */\n}\n\nvoid\nhalt_low_priority_signals(void)\n{\n#ifndef _WIN32\n    struct sigaction sa;\n    CLEAR(sa);\n    sa.sa_handler = SIG_IGN;\n    sigaction(SIGHUP, &sa, NULL);\n    sigaction(SIGUSR1, &sa, NULL);\n    sigaction(SIGUSR2, &sa, NULL);\n#endif /* _WIN32 */\n    ignored_hard_signals_mask = (1LL << SIGHUP) | (1LL << SIGUSR1) | (1LL << SIGUSR2);\n}\n\n/* called after daemonization to retain signal settings */\nvoid\nrestore_signal_state(void)\n{\n    if (signal_mode == SM_PRE_INIT)\n    {\n        pre_init_signal_catch();\n    }\n    else if (signal_mode == SM_POST_INIT)\n    {\n        post_init_signal_catch();\n    }\n}\n\n/*\n * Print statistics.\n *\n * Triggered by SIGUSR2 or F2 on Windows.\n */\nvoid\nprint_status(struct context *c, struct status_output *so)\n{\n    struct gc_arena gc = gc_new();\n\n    status_reset(so);\n\n    if (dco_enabled(&c->options))\n    {\n        if (dco_get_peer_stats(c, true) < 0)\n        {\n            return;\n        }\n    }\n\n    status_printf(so, \"OpenVPN STATISTICS\");\n    status_printf(so, \"Updated,%s\", time_string(0, 0, false, &gc));\n    status_printf(so, \"TUN/TAP read bytes,\" counter_format, c->c2.tun_read_bytes);\n    status_printf(so, \"TUN/TAP write bytes,\" counter_format, c->c2.tun_write_bytes);\n    status_printf(so, \"TCP/UDP read bytes,\" counter_format,\n                  c->c2.link_read_bytes + c->c2.dco_read_bytes);\n    status_printf(so, \"TCP/UDP write bytes,\" counter_format,\n                  c->c2.link_write_bytes + c->c2.dco_write_bytes);\n    status_printf(so, \"Auth read bytes,\" counter_format, c->c2.link_read_bytes_auth);\n#ifdef USE_COMP\n    if (c->c2.comp_context)\n    {\n        comp_print_stats(c->c2.comp_context, so);\n    }\n#endif\n#ifdef PACKET_TRUNCATION_CHECK\n    status_printf(so, \"TUN read truncations,\" counter_format, c->c2.n_trunc_tun_read);\n    status_printf(so, \"TUN write truncations,\" counter_format, c->c2.n_trunc_tun_write);\n    status_printf(so, \"Pre-encrypt truncations,\" counter_format, c->c2.n_trunc_pre_encrypt);\n    status_printf(so, \"Post-decrypt truncations,\" counter_format, c->c2.n_trunc_post_decrypt);\n#endif\n#ifdef _WIN32\n    if (tuntap_defined(c->c1.tuntap))\n    {\n        const char *extended_msg = tap_win_getinfo(c->c1.tuntap, &gc);\n        if (extended_msg)\n        {\n            status_printf(so, \"TAP-WIN32 driver status,\\\"%s\\\"\", extended_msg);\n        }\n    }\n#endif\n\n    status_printf(so, \"END\");\n    status_flush(so);\n    gc_free(&gc);\n}\n\n/*\n * Handle the triggering and time-wait of explicit\n * exit notification.\n */\nstatic void\nprocess_explicit_exit_notification_init(struct context *c)\n{\n    msg(M_INFO, \"SIGTERM received, sending exit notification to peer\");\n    /* init the timeout to send the OCC_EXIT messages if cc exit is not\n     * enabled and also to exit after waiting for retries of resending of\n     * exit messages */\n    event_timeout_init(&c->c2.explicit_exit_notification_interval, 1, 0);\n    reset_coarse_timers(c);\n\n    /* Windows exit event will continue trigering SIGTERM -- halt it */\n    halt_non_edge_triggered_signals();\n\n    /* Before resetting the signal, ensure hard low priority signals\n     * will be ignored during the exit notification period.\n     */\n    halt_low_priority_signals(); /* Set hard SIGUSR1/SIGHUP/SIGUSR2 to be ignored */\n    signal_reset(c->sig, 0);\n\n    c->c2.explicit_exit_notification_time_wait = now;\n\n    /* Check if we are in TLS mode and should send the notification via data\n     * channel */\n    if (cc_exit_notify_enabled(c))\n    {\n        send_control_channel_string(c, \"EXIT\", D_PUSH);\n    }\n}\n\nvoid\nprocess_explicit_exit_notification_timer_wakeup(struct context *c)\n{\n    if (event_timeout_trigger(&c->c2.explicit_exit_notification_interval, &c->c2.timeval,\n                              ETT_DEFAULT))\n    {\n        ASSERT(c->c2.explicit_exit_notification_time_wait\n               && c->options.ce.explicit_exit_notification);\n        if (now >= c->c2.explicit_exit_notification_time_wait\n                       + c->options.ce.explicit_exit_notification)\n        {\n            event_timeout_clear(&c->c2.explicit_exit_notification_interval);\n            register_signal(c->sig, SIGTERM, \"exit-with-notification\");\n        }\n        else if (!cc_exit_notify_enabled(c))\n        {\n            c->c2.occ_op = OCC_EXIT;\n        }\n    }\n}\n\n/*\n * Process signals\n */\n\nvoid\nremap_signal(struct context *c)\n{\n    if (c->sig->signal_received == SIGUSR1 && c->options.remap_sigusr1)\n    {\n        register_signal(c->sig, c->options.remap_sigusr1, c->sig->signal_text);\n    }\n}\n\nstatic void\nprocess_sigusr2(struct context *c)\n{\n    struct status_output *so = status_open(NULL, 0, M_INFO, NULL, 0);\n    print_status(c, so);\n    status_close(so);\n    signal_reset(c->sig, SIGUSR2);\n}\n\nstatic bool\nprocess_sigterm(struct context *c)\n{\n    bool ret = true;\n    if (c->options.ce.explicit_exit_notification && !c->c2.explicit_exit_notification_time_wait)\n    {\n        process_explicit_exit_notification_init(c);\n        ret = false;\n    }\n    return ret;\n}\n\n/**\n * If a soft restart signal is received during exit-notification, it\n * implies the event loop cannot continue: remap to SIGTERM to exit promptly.\n * Hard restart signals are ignored during exit notification wait.\n */\nstatic void\nremap_restart_signals(struct context *c)\n{\n    if ((c->sig->signal_received == SIGUSR1 || c->sig->signal_received == SIGHUP)\n        && event_timeout_defined(&c->c2.explicit_exit_notification_interval)\n        && c->sig->source != SIG_SOURCE_HARD)\n    {\n        msg(M_INFO, \"Converting soft %s received during exit notification to SIGTERM\",\n            signal_name(c->sig->signal_received, true));\n        register_signal(c->sig, SIGTERM, \"exit-with-notification\");\n    }\n}\n\nbool\nprocess_signal(struct context *c)\n{\n    bool ret = true;\n\n    remap_restart_signals(c);\n\n    if (c->sig->signal_received == SIGTERM || c->sig->signal_received == SIGINT)\n    {\n        ret = process_sigterm(c);\n    }\n    else if (c->sig->signal_received == SIGUSR2)\n    {\n        process_sigusr2(c);\n        ret = false;\n    }\n    return ret;\n}\n"
  },
  {
    "path": "src/openvpn/sig.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef SIG_H\n#define SIG_H\n\n#include \"status.h\"\n#include \"win32.h\"\n\n#define SIG_SOURCE_SOFT              0\n#define SIG_SOURCE_HARD              1\n/* CONNECTION_FAILED is also a \"soft\" status,\n * It is thrown if a connection attempt fails\n */\n#define SIG_SOURCE_CONNECTION_FAILED 2\n\n/*\n * Signal information, including signal code\n * and descriptive text.\n */\nstruct signal_info\n{\n    volatile int signal_received;\n    volatile int source;\n    const char *signal_text;\n};\n\n#define IS_SIG(c) ((c)->sig->signal_received)\n\nstruct context;\n\nextern struct signal_info siginfo_static;\n\nint parse_signal(const char *signame);\n\nconst char *signal_name(const int sig, const bool upper);\n\nconst char *signal_description(const int signum, const char *sigtext);\n\nvoid throw_signal(const int signum);\n\nvoid throw_signal_soft(const int signum, const char *signal_text);\n\nvoid pre_init_signal_catch(void);\n\nvoid post_init_signal_catch(void);\n\nvoid restore_signal_state(void);\n\nvoid print_signal(const struct signal_info *si, const char *title, msglvl_t msglevel);\n\nvoid print_status(struct context *c, struct status_output *so);\n\nvoid remap_signal(struct context *c);\n\nvoid signal_restart_status(const struct signal_info *si);\n\nbool process_signal(struct context *c);\n\nvoid register_signal(struct signal_info *si, int sig, const char *text);\n\nvoid process_explicit_exit_notification_timer_wakeup(struct context *c);\n\n/**\n * Clear the signal if its current value equals signum. If signum is\n * zero the signal is cleared independent of its current value.\n * @returns the current value of the signal.\n */\nint signal_reset(struct signal_info *si, int signum);\n\nstatic inline void\nhalt_non_edge_triggered_signals(void)\n{\n#ifdef _WIN32\n    win32_signal_close(&win32_signal);\n#endif\n}\n\n/**\n * Copy the global signal_received (if non-zero) to the passed-in argument sig.\n * As the former is volatile, do not assign if sig and &signal_received are the\n * same.  Even on windows signal_received is really volatile as it can change if\n * a ctrl-C or ctrl-break is delivered. So use the same logic as above.\n *\n * Also, on windows always call win32_signal_get to pickup any signals simulated by\n * key-board short cuts or the exit event.\n */\n\nstatic inline void\nget_signal(volatile int *sig)\n{\n#ifdef _WIN32\n    const int i = win32_signal_get(&win32_signal);\n#else\n    const int i = siginfo_static.signal_received;\n#endif\n    if (i && sig != &siginfo_static.signal_received)\n    {\n        *sig = i;\n    }\n}\n\n#endif /* ifndef SIG_H */\n"
  },
  {
    "path": "src/openvpn/siphash.h",
    "content": "/*\n * SipHash reference C implementation\n *\n * Copyright (c) 2012-2021 Jean-Philippe Aumasson\n * <jeanphilippe.aumasson@gmail.com>\n * Copyright (c) 2012-2014 Daniel J. Bernstein <djb@cr.yp.to>\n *\n * To the extent possible under law, the author(s) have dedicated all copyright\n * and related and neighboring rights to this software to the public domain\n * worldwide. This software is distributed without any warranty.\n *\n * You should have received a copy of the CC0 Public Domain Dedication along\n * with\n * this software. If not, see\n * <http://creativecommons.org/publicdomain/zero/1.0/>.\n */\n\n#ifndef SIPHASH_H\n#define SIPHASH_H\n\n#include <inttypes.h>\n\n/* siphash always uses 128-bit keys */\n#define SIPHASH_KEY_SIZE 16\n\nint siphash(const void *in, size_t inlen, const void *k, uint8_t *out,\n            size_t outlen);\n\n#endif\n"
  },
  {
    "path": "src/openvpn/siphash_reference.c",
    "content": "/*\n * SipHash reference C implementation\n *\n * Copyright 2012-2024 JP Aumasson\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n *\n */\n\n/* Note:  the reference implementation is also available under CC0 license\n * (dual licensed) we included the MIT license here since it is shorter */\n\n#include \"siphash.h\"\n#include <assert.h>\n#include <stddef.h>\n#include <stdint.h>\n\n/* default: SipHash-2-4 */\n#ifndef cROUNDS\n#define cROUNDS 2\n#endif\n#ifndef dROUNDS\n#define dROUNDS 4\n#endif\n\n#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))\n\n#define U32TO8_LE(p, v)            \\\n    (p)[0] = (uint8_t)((v));       \\\n    (p)[1] = (uint8_t)((v) >> 8);  \\\n    (p)[2] = (uint8_t)((v) >> 16); \\\n    (p)[3] = (uint8_t)((v) >> 24);\n\n#define U64TO8_LE(p, v)              \\\n    U32TO8_LE((p), (uint32_t)((v))); \\\n    U32TO8_LE((p) + 4, (uint32_t)((v) >> 32));\n\n#define U8TO64_LE(p)                                           \\\n    (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8)          \\\n     | ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) \\\n     | ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) \\\n     | ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56))\n\n#define SIPROUND           \\\n    do                     \\\n    {                      \\\n        v0 += v1;          \\\n        v1 = ROTL(v1, 13); \\\n        v1 ^= v0;          \\\n        v0 = ROTL(v0, 32); \\\n        v2 += v3;          \\\n        v3 = ROTL(v3, 16); \\\n        v3 ^= v2;          \\\n        v0 += v3;          \\\n        v3 = ROTL(v3, 21); \\\n        v3 ^= v0;          \\\n        v2 += v1;          \\\n        v1 = ROTL(v1, 17); \\\n        v1 ^= v2;          \\\n        v2 = ROTL(v2, 32); \\\n    } while (0)\n\n#ifdef DEBUG_SIPHASH\n#include <stdio.h>\n\n#define TRACE                                            \\\n    do                                                   \\\n    {                                                    \\\n        printf(\"(%3zu) v0 %016\" PRIx64 \"\\n\", inlen, v0); \\\n        printf(\"(%3zu) v1 %016\" PRIx64 \"\\n\", inlen, v1); \\\n        printf(\"(%3zu) v2 %016\" PRIx64 \"\\n\", inlen, v2); \\\n        printf(\"(%3zu) v3 %016\" PRIx64 \"\\n\", inlen, v3); \\\n    } while (0)\n#else /* ifdef DEBUG_SIPHASH */\n#define TRACE\n#endif\n\n/*\n *  Computes a SipHash value\n * in: pointer to input data (read-only)\n *  inlen: input data length in bytes (any size_t value)\n * k: pointer to the key data (read-only), must be 16 bytes\n * out: pointer to output data (write-only), outlen bytes must be allocated\n *  outlen: length of the output in bytes, must be 8 or 16\n */\nint\nsiphash(const void *in, const size_t inlen, const void *k, uint8_t *out,\n        const size_t outlen)\n{\n    const unsigned char *ni = (const unsigned char *)in;\n    const unsigned char *kk = (const unsigned char *)k;\n\n    assert((outlen == 8) || (outlen == 16));\n    uint64_t v0 = UINT64_C(0x736f6d6570736575);\n    uint64_t v1 = UINT64_C(0x646f72616e646f6d);\n    uint64_t v2 = UINT64_C(0x6c7967656e657261);\n    uint64_t v3 = UINT64_C(0x7465646279746573);\n    uint64_t k0 = U8TO64_LE(kk);\n    uint64_t k1 = U8TO64_LE(kk + 8);\n    uint64_t m;\n    int i;\n    const unsigned char *end = ni + inlen - (inlen % sizeof(uint64_t));\n    const int left = inlen & 7;\n    uint64_t b = ((uint64_t)inlen) << 56;\n    v3 ^= k1;\n    v2 ^= k0;\n    v1 ^= k1;\n    v0 ^= k0;\n\n    if (outlen == 16)\n    {\n        v1 ^= 0xee;\n    }\n\n    for (; ni != end; ni += 8)\n    {\n        m = U8TO64_LE(ni);\n        v3 ^= m;\n\n        TRACE;\n        for (i = 0; i < cROUNDS; ++i)\n        {\n            SIPROUND;\n        }\n\n        v0 ^= m;\n    }\n\n    switch (left)\n    {\n        case 7:\n            b |= ((uint64_t)ni[6]) << 48;\n\n        /* FALLTHRU */\n        case 6:\n            b |= ((uint64_t)ni[5]) << 40;\n\n        /* FALLTHRU */\n        case 5:\n            b |= ((uint64_t)ni[4]) << 32;\n\n        /* FALLTHRU */\n        case 4:\n            b |= ((uint64_t)ni[3]) << 24;\n\n        /* FALLTHRU */\n        case 3:\n            b |= ((uint64_t)ni[2]) << 16;\n\n        /* FALLTHRU */\n        case 2:\n            b |= ((uint64_t)ni[1]) << 8;\n\n        /* FALLTHRU */\n        case 1:\n            b |= ((uint64_t)ni[0]);\n            break;\n\n        case 0:\n            break;\n    }\n\n    v3 ^= b;\n\n    TRACE;\n    for (i = 0; i < cROUNDS; ++i)\n    {\n        SIPROUND;\n    }\n\n    v0 ^= b;\n\n    if (outlen == 16)\n    {\n        v2 ^= 0xee;\n    }\n    else\n    {\n        v2 ^= 0xff;\n    }\n\n    TRACE;\n    for (i = 0; i < dROUNDS; ++i)\n    {\n        SIPROUND;\n    }\n\n    b = v0 ^ v1 ^ v2 ^ v3;\n    U64TO8_LE(out, b);\n\n    if (outlen == 8)\n    {\n        return 0;\n    }\n\n    v1 ^= 0xdd;\n\n    TRACE;\n    for (i = 0; i < dROUNDS; ++i)\n    {\n        SIPROUND;\n    }\n\n    b = v0 ^ v1 ^ v2 ^ v3;\n    U64TO8_LE(out + 8, b);\n\n    return 0;\n}\n"
  },
  {
    "path": "src/openvpn/socket.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"socket.h\"\n#include \"fdmisc.h\"\n#include \"misc.h\"\n#include \"gremlin.h\"\n#include \"plugin.h\"\n#include \"ps.h\"\n#include \"run_command.h\"\n#include \"manage.h\"\n#include \"misc.h\"\n#include \"manage.h\"\n#include \"openvpn.h\"\n#include \"forward.h\"\n\n#include \"memdbg.h\"\n\nbool\nsockets_read_residual(const struct context *c)\n{\n    for (int i = 0; i < c->c1.link_sockets_num; i++)\n    {\n        if (c->c2.link_sockets[i]->stream_buf.residual_fully_formed)\n        {\n            return true;\n        }\n    }\n    return false;\n}\n\n/*\n * Convert sockflags/getaddr_flags into getaddr_flags\n */\nstatic unsigned int\nsf2gaf(const unsigned int getaddr_flags, const unsigned int sockflags)\n{\n    if (sockflags & SF_HOST_RANDOMIZE)\n    {\n        return getaddr_flags | GETADDR_RANDOMIZE;\n    }\n    else\n    {\n        return getaddr_flags;\n    }\n}\n\n/*\n * Functions related to the translation of DNS names to IP addresses.\n */\nstatic int\nget_addr_generic(sa_family_t af, unsigned int flags, const char *hostname, void *network,\n                 unsigned int *netbits, int resolve_retry_seconds, struct signal_info *sig_info,\n                 msglvl_t msglevel)\n{\n    char *endp, *sep, *var_host = NULL;\n    struct addrinfo *ai = NULL;\n    unsigned long bits;\n    uint8_t max_bits;\n    int ret = -1;\n\n    if (!hostname)\n    {\n        msg(M_NONFATAL, \"Can't resolve null hostname!\");\n        goto out;\n    }\n\n    /* assign family specific default values */\n    switch (af)\n    {\n        case AF_INET:\n            bits = 0;\n            max_bits = sizeof(in_addr_t) * 8;\n            break;\n\n        case AF_INET6:\n            bits = 64;\n            max_bits = sizeof(struct in6_addr) * 8;\n            break;\n\n        default:\n            msg(M_WARN, \"Unsupported AF family passed to getaddrinfo for %s (%d)\", hostname, af);\n            goto out;\n    }\n\n    /* we need to modify the hostname received as input, but we don't want to\n     * touch it directly as it might be a constant string.\n     *\n     * Therefore, we clone the string here and free it at the end of the\n     * function */\n    var_host = strdup(hostname);\n    if (!var_host)\n    {\n        msg(M_NONFATAL | M_ERRNO, \"Can't allocate hostname buffer for getaddrinfo\");\n        goto out;\n    }\n\n    /* check if this hostname has a /bits suffix */\n    sep = strchr(var_host, '/');\n    if (sep)\n    {\n        bits = strtoul(sep + 1, &endp, 10);\n        if ((*endp != '\\0') || (bits > max_bits))\n        {\n            msg(msglevel, \"IP prefix '%s': invalid '/bits' spec (%s)\", hostname, sep + 1);\n            goto out;\n        }\n        *sep = '\\0';\n    }\n\n    ret = openvpn_getaddrinfo(flags & ~GETADDR_HOST_ORDER, var_host, NULL, resolve_retry_seconds,\n                              sig_info, af, &ai);\n    if ((ret == 0) && network)\n    {\n        struct in6_addr *ip6;\n        in_addr_t *ip4;\n\n        if (af != ai->ai_family)\n        {\n            msg(msglevel, \"Can't parse %s as IPv%d address\", var_host, (af == AF_INET) ? 4 : 6);\n            ret = -1;\n            goto out;\n        }\n\n        switch (af)\n        {\n            case AF_INET:\n                ip4 = network;\n                *ip4 = ((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr;\n\n                if (flags & GETADDR_HOST_ORDER)\n                {\n                    *ip4 = ntohl(*ip4);\n                }\n                break;\n\n            case AF_INET6:\n                ip6 = network;\n                *ip6 = ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;\n                break;\n\n            default:\n                /* can't get here because 'af' was previously checked */\n                msg(M_WARN, \"Unsupported AF family for %s (%d)\", var_host, af);\n                goto out;\n        }\n    }\n\n    if (netbits)\n    {\n        *netbits = (unsigned int)bits;\n    }\n\n    /* restore '/' separator, if any */\n    if (sep)\n    {\n        *sep = '/';\n    }\nout:\n    if (ai)\n    {\n        freeaddrinfo(ai);\n    }\n    free(var_host);\n\n    return ret;\n}\n\nin_addr_t\ngetaddr(unsigned int flags, const char *hostname, int resolve_retry_seconds, bool *succeeded,\n        struct signal_info *sig_info)\n{\n    in_addr_t addr = { 0 };\n    int status;\n\n    status = get_addr_generic(AF_INET, flags, hostname, &addr, NULL, resolve_retry_seconds,\n                              sig_info, M_WARN);\n    if (status == 0)\n    {\n        if (succeeded)\n        {\n            *succeeded = true;\n        }\n        return addr;\n    }\n    else\n    {\n        if (succeeded)\n        {\n            *succeeded = false;\n        }\n        return 0;\n    }\n}\n\nbool\nget_ipv6_addr(const char *hostname, struct in6_addr *network, unsigned int *netbits,\n              msglvl_t msglevel)\n{\n    if (get_addr_generic(AF_INET6, GETADDR_RESOLVE, hostname, network, netbits, 0, NULL, msglevel)\n        < 0)\n    {\n        return false;\n    }\n\n    return true; /* parsing OK, values set */\n}\n\nstatic inline bool\nstreqnull(const char *a, const char *b)\n{\n    if (a == NULL && b == NULL)\n    {\n        return true;\n    }\n    else if (a == NULL || b == NULL)\n    {\n        return false;\n    }\n    else\n    {\n        return streq(a, b);\n    }\n}\n\n/*\n * get_cached_dns_entry return 0 on success and -1\n * otherwise. (like getaddrinfo)\n */\nstatic int\nget_cached_dns_entry(struct cached_dns_entry *dns_cache, const char *hostname, const char *servname,\n                     int ai_family, unsigned int resolve_flags, struct addrinfo **ai)\n{\n    struct cached_dns_entry *ph;\n    unsigned int flags;\n\n    /* Only use flags that are relevant for the structure */\n    flags = resolve_flags & GETADDR_CACHE_MASK;\n\n    for (ph = dns_cache; ph; ph = ph->next)\n    {\n        if (streqnull(ph->hostname, hostname) && streqnull(ph->servname, servname)\n            && ph->ai_family == ai_family && ph->flags == flags)\n        {\n            *ai = ph->ai;\n            return 0;\n        }\n    }\n    return -1;\n}\n\n\nstatic int\ndo_preresolve_host(struct context *c, const char *hostname, const char *servname, const int af,\n                   const unsigned int flags)\n{\n    struct addrinfo *ai;\n    int status;\n\n    if (get_cached_dns_entry(c->c1.dns_cache, hostname, servname, af, flags, &ai) == 0)\n    {\n        /* entry already cached, return success */\n        return 0;\n    }\n\n    status = openvpn_getaddrinfo(flags, hostname, servname, c->options.resolve_retry_seconds, NULL,\n                                 af, &ai);\n    if (status == 0)\n    {\n        struct cached_dns_entry *ph;\n\n        ALLOC_OBJ_CLEAR_GC(ph, struct cached_dns_entry, &c->gc);\n        ph->ai = ai;\n        ph->hostname = hostname;\n        ph->servname = servname;\n        ph->flags = flags & GETADDR_CACHE_MASK;\n\n        if (!c->c1.dns_cache)\n        {\n            c->c1.dns_cache = ph;\n        }\n        else\n        {\n            struct cached_dns_entry *prev = c->c1.dns_cache;\n            while (prev->next)\n            {\n                prev = prev->next;\n            }\n            prev->next = ph;\n        }\n\n        gc_addspecial(ai, &gc_freeaddrinfo_callback, &c->gc);\n    }\n    return status;\n}\n\nvoid\ndo_preresolve(struct context *c)\n{\n    struct connection_list *l = c->options.connection_list;\n    const unsigned int preresolve_flags = GETADDR_RESOLVE | GETADDR_UPDATE_MANAGEMENT_STATE\n                                          | GETADDR_MENTION_RESOLVE_RETRY | GETADDR_FATAL;\n\n\n    for (int i = 0; i < l->len; ++i)\n    {\n        int status;\n        const char *remote;\n        unsigned int flags = preresolve_flags;\n\n        struct connection_entry *ce = l->array[i];\n\n        if (proto_is_dgram(ce->proto))\n        {\n            flags |= GETADDR_DATAGRAM;\n        }\n\n        if (c->options.sockflags & SF_HOST_RANDOMIZE)\n        {\n            flags |= GETADDR_RANDOMIZE;\n        }\n\n        if (c->options.ip_remote_hint)\n        {\n            remote = c->options.ip_remote_hint;\n        }\n        else\n        {\n            remote = ce->remote;\n        }\n\n        /* HTTP remote hostname does not need to be resolved */\n        if (!ce->http_proxy_options)\n        {\n            status = do_preresolve_host(c, remote, ce->remote_port, ce->af, flags);\n            if (status != 0)\n            {\n                goto err;\n            }\n        }\n\n        /* Preresolve proxy */\n        if (ce->http_proxy_options)\n        {\n            status = do_preresolve_host(c, ce->http_proxy_options->server,\n                                        ce->http_proxy_options->port, ce->af, preresolve_flags);\n\n            if (status != 0)\n            {\n                goto err;\n            }\n        }\n\n        if (ce->socks_proxy_server)\n        {\n            status =\n                do_preresolve_host(c, ce->socks_proxy_server, ce->socks_proxy_port, ce->af, flags);\n            if (status != 0)\n            {\n                goto err;\n            }\n        }\n\n        if (ce->bind_local)\n        {\n            flags |= GETADDR_PASSIVE;\n            flags &= ~GETADDR_RANDOMIZE;\n\n            for (int j = 0; j < ce->local_list->len; j++)\n            {\n                struct local_entry *le = ce->local_list->array[j];\n\n                if (!le->local)\n                {\n                    continue;\n                }\n\n                status = do_preresolve_host(c, le->local, le->port, ce->af, flags);\n                if (status != 0)\n                {\n                    goto err;\n                }\n            }\n        }\n    }\n    return;\n\nerr:\n    throw_signal_soft(SIGHUP, \"Preresolving failed\");\n}\n\nstatic int\nsocket_get_sndbuf(socket_descriptor_t sd)\n{\n    int val;\n    socklen_t len = sizeof(val);\n\n    if (getsockopt(sd, SOL_SOCKET, SO_SNDBUF, (void *)&val, &len) == 0 && len == sizeof(val))\n    {\n        return val;\n    }\n    return 0;\n}\n\nstatic void\nsocket_set_sndbuf(socket_descriptor_t sd, int size)\n{\n    if (setsockopt(sd, SOL_SOCKET, SO_SNDBUF, (void *)&size, sizeof(size)) != 0)\n    {\n        msg(M_WARN, \"NOTE: setsockopt SO_SNDBUF=%d failed\", size);\n    }\n}\n\nstatic int\nsocket_get_rcvbuf(socket_descriptor_t sd)\n{\n    int val;\n    socklen_t len = sizeof(val);\n\n    if (getsockopt(sd, SOL_SOCKET, SO_RCVBUF, (void *)&val, &len) == 0 && len == sizeof(val))\n    {\n        return val;\n    }\n    return 0;\n}\n\nstatic void\nsocket_set_rcvbuf(socket_descriptor_t sd, int size)\n{\n    if (setsockopt(sd, SOL_SOCKET, SO_RCVBUF, (void *)&size, sizeof(size)) != 0)\n    {\n        msg(M_WARN, \"NOTE: setsockopt SO_RCVBUF=%d failed\", size);\n    }\n}\n\nvoid\nsocket_set_buffers(socket_descriptor_t fd, const struct socket_buffer_size *sbs, bool reduce_size)\n{\n    if (sbs)\n    {\n        const int sndbuf_old = socket_get_sndbuf(fd);\n        const int rcvbuf_old = socket_get_rcvbuf(fd);\n\n        if (sbs->sndbuf && (reduce_size || sndbuf_old < sbs->sndbuf))\n        {\n            socket_set_sndbuf(fd, sbs->sndbuf);\n        }\n\n        if (sbs->rcvbuf && (reduce_size || rcvbuf_old < sbs->rcvbuf))\n        {\n            socket_set_rcvbuf(fd, sbs->rcvbuf);\n        }\n\n        msg(D_OSBUF, \"Socket Buffers: R=[%d->%d] S=[%d->%d]\", rcvbuf_old, socket_get_rcvbuf(fd),\n            sndbuf_old, socket_get_sndbuf(fd));\n    }\n}\n\n/*\n * Set other socket options\n */\n\nstatic bool\nsocket_set_tcp_nodelay(socket_descriptor_t sd, int state)\n{\n#if defined(_WIN32) || (defined(IPPROTO_TCP) && defined(TCP_NODELAY))\n    if (setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, (void *)&state, sizeof(state)) != 0)\n    {\n        msg(M_WARN, \"NOTE: setsockopt TCP_NODELAY=%d failed\", state);\n        return false;\n    }\n    else\n    {\n        dmsg(D_OSBUF, \"Socket flags: TCP_NODELAY=%d succeeded\", state);\n        return true;\n    }\n#else /* if defined(_WIN32) || (defined(IPPROTO_TCP) && defined(TCP_NODELAY)) */\n    msg(M_WARN, \"NOTE: setsockopt TCP_NODELAY=%d failed (No kernel support)\", state);\n    return false;\n#endif\n}\n\nstatic inline void\nsocket_set_mark(socket_descriptor_t sd, int mark)\n{\n#if defined(TARGET_LINUX)\n    if (mark && setsockopt(sd, SOL_SOCKET, SO_MARK, (void *)&mark, sizeof(mark)) != 0)\n    {\n        msg(M_WARN, \"NOTE: setsockopt SO_MARK=%d failed\", mark);\n    }\n#endif\n}\n\nstatic bool\nsocket_set_flags(socket_descriptor_t sd, unsigned int sockflags)\n{\n    /* SF_TCP_NODELAY doesn't make sense for dco-win */\n    if ((sockflags & SF_TCP_NODELAY) && (!(sockflags & SF_DCO_WIN)))\n    {\n        return socket_set_tcp_nodelay(sd, 1);\n    }\n    else\n    {\n        return true;\n    }\n}\n\nbool\nlink_socket_update_flags(struct link_socket *sock, unsigned int sockflags)\n{\n    if (sock && socket_defined(sock->sd))\n    {\n        sock->sockflags |= sockflags;\n        return socket_set_flags(sock->sd, sock->sockflags);\n    }\n    else\n    {\n        return false;\n    }\n}\n\nvoid\nlink_socket_update_buffer_sizes(struct link_socket *sock, int rcvbuf, int sndbuf)\n{\n    if (sock && socket_defined(sock->sd))\n    {\n        sock->socket_buffer_sizes.sndbuf = sndbuf;\n        sock->socket_buffer_sizes.rcvbuf = rcvbuf;\n        socket_set_buffers(sock->sd, &sock->socket_buffer_sizes, true);\n    }\n}\n\n/*\n * SOCKET INITIALIZATION CODE.\n * Create a TCP/UDP socket\n */\n\nsocket_descriptor_t\ncreate_socket_tcp(struct addrinfo *addrinfo)\n{\n    socket_descriptor_t sd;\n\n    ASSERT(addrinfo);\n    ASSERT(addrinfo->ai_socktype == SOCK_STREAM);\n\n    if ((sd = socket(addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol))\n        == SOCKET_UNDEFINED)\n    {\n        msg(M_ERR, \"Cannot create TCP socket\");\n    }\n\n#ifndef _WIN32 /* using SO_REUSEADDR on Windows will cause bind to succeed on port conflicts! */\n    /* set SO_REUSEADDR on socket */\n    {\n        int on = 1;\n        if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) < 0)\n        {\n            msg(M_ERR, \"TCP: Cannot setsockopt SO_REUSEADDR on TCP socket\");\n        }\n    }\n#endif\n\n    /* set socket file descriptor to not pass across execs, so that\n     * scripts don't have access to it */\n    set_cloexec(sd);\n\n    return sd;\n}\n\nstatic socket_descriptor_t\ncreate_socket_udp(struct addrinfo *addrinfo, const unsigned int flags)\n{\n    socket_descriptor_t sd;\n\n    ASSERT(addrinfo);\n    ASSERT(addrinfo->ai_socktype == SOCK_DGRAM);\n\n    if ((sd = socket(addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol))\n        == SOCKET_UNDEFINED)\n    {\n        msg(M_ERR, \"UDP: Cannot create UDP/UDP6 socket\");\n    }\n#if ENABLE_IP_PKTINFO\n    else if (flags & SF_USE_IP_PKTINFO)\n    {\n        int pad = 1;\n        if (addrinfo->ai_family == AF_INET)\n        {\n#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)\n            if (setsockopt(sd, SOL_IP, IP_PKTINFO, (void *)&pad, sizeof(pad)) < 0)\n            {\n                msg(M_ERR, \"UDP: failed setsockopt for IP_PKTINFO\");\n            }\n#elif defined(IP_RECVDSTADDR)\n            if (setsockopt(sd, IPPROTO_IP, IP_RECVDSTADDR, (void *)&pad, sizeof(pad)) < 0)\n            {\n                msg(M_ERR, \"UDP: failed setsockopt for IP_RECVDSTADDR\");\n            }\n#else /* if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) */\n#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix syshead.h)\n#endif\n        }\n        else if (addrinfo->ai_family == AF_INET6)\n        {\n#ifndef IPV6_RECVPKTINFO /* Some older Darwin platforms require this */\n            if (setsockopt(sd, IPPROTO_IPV6, IPV6_PKTINFO, (void *)&pad, sizeof(pad)) < 0)\n#else\n            if (setsockopt(sd, IPPROTO_IPV6, IPV6_RECVPKTINFO, (void *)&pad, sizeof(pad)) < 0)\n#endif\n            {\n                msg(M_ERR, \"UDP: failed setsockopt for IPV6_RECVPKTINFO\");\n            }\n        }\n    }\n#endif /* if ENABLE_IP_PKTINFO */\n\n    /* set socket file descriptor to not pass across execs, so that\n     * scripts don't have access to it */\n    set_cloexec(sd);\n\n    return sd;\n}\n\nstatic void\nbind_local(struct link_socket *sock, const sa_family_t ai_family)\n{\n    /* bind to local address/port */\n    if (sock->bind_local)\n    {\n        if (sock->socks_proxy && sock->info.proto == PROTO_UDP)\n        {\n            socket_bind(sock->ctrl_sd, sock->info.lsa->bind_local, ai_family, \"SOCKS\", false);\n        }\n        else\n        {\n            socket_bind(sock->sd, sock->info.lsa->bind_local, ai_family, \"TCP/UDP\",\n                        sock->info.bind_ipv6_only);\n        }\n    }\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\nstatic void\ncreate_socket(struct link_socket *sock, struct addrinfo *addr)\n{\n    if (addr->ai_protocol == IPPROTO_UDP || addr->ai_socktype == SOCK_DGRAM)\n    {\n        sock->sd = create_socket_udp(addr, sock->sockflags);\n        sock->sockflags |= SF_GETADDRINFO_DGRAM;\n\n        /* Assume that control socket and data socket to the socks proxy\n         * are using the same IP family */\n        if (sock->socks_proxy)\n        {\n            /* Construct a temporary addrinfo to create the socket,\n             * currently resolve two remote addresses is not supported,\n             * TODO: Rewrite the whole resolve_remote */\n            struct addrinfo addrinfo_tmp = *addr;\n            addrinfo_tmp.ai_socktype = SOCK_STREAM;\n            addrinfo_tmp.ai_protocol = IPPROTO_TCP;\n            sock->ctrl_sd = create_socket_tcp(&addrinfo_tmp);\n        }\n    }\n    else if (addr->ai_protocol == IPPROTO_TCP || addr->ai_socktype == SOCK_STREAM)\n    {\n        sock->sd = create_socket_tcp(addr);\n    }\n    else\n    {\n        ASSERT(0);\n    }\n    /* Set af field of sock->info, so it always reflects the address family\n     * of the created socket */\n    sock->info.af = addr->ai_family;\n\n    /* set socket buffers based on --sndbuf and --rcvbuf options */\n    socket_set_buffers(sock->sd, &sock->socket_buffer_sizes, true);\n\n    /* set socket to --mark packets with given value */\n    socket_set_mark(sock->sd, sock->mark);\n\n#if defined(TARGET_LINUX)\n    if (sock->bind_dev)\n    {\n        msg(M_INFO, \"Using bind-dev %s\", sock->bind_dev);\n        if (setsockopt(sock->sd, SOL_SOCKET, SO_BINDTODEVICE, sock->bind_dev,\n                       strlen(sock->bind_dev) + 1)\n            != 0)\n        {\n            msg(M_WARN | M_ERRNO, \"WARN: setsockopt SO_BINDTODEVICE=%s failed\", sock->bind_dev);\n        }\n    }\n#endif\n\n    bind_local(sock, addr->ai_family);\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n#ifdef TARGET_ANDROID\nstatic void\nprotect_fd_nonlocal(int fd, const struct sockaddr *addr)\n{\n    if (!management)\n    {\n        msg(M_FATAL, \"Required management interface not available.\");\n    }\n\n    /* pass socket FD to management interface to pass on to VPNService API\n     * as \"protected socket\" (exempt from being routed into tunnel)\n     */\n    if (addr_local(addr))\n    {\n        msg(D_SOCKET_DEBUG, \"Address is local, not protecting socket fd %d\", fd);\n        return;\n    }\n\n    msg(D_SOCKET_DEBUG, \"Protecting socket fd %d\", fd);\n    management->connection.fdtosend = fd;\n    management_android_control(management, \"PROTECTFD\", __func__);\n}\n#endif\n\n/*\n * Functions used for establishing a TCP stream connection.\n */\nstatic void\nsocket_do_listen(socket_descriptor_t sd, const struct addrinfo *local, bool do_listen,\n                 bool do_set_nonblock)\n{\n    struct gc_arena gc = gc_new();\n    if (do_listen)\n    {\n        ASSERT(local);\n        msg(M_INFO, \"Listening for incoming TCP connection on %s\",\n            print_sockaddr(local->ai_addr, &gc));\n        if (listen(sd, 32))\n        {\n            msg(M_ERR, \"TCP: listen() failed\");\n        }\n    }\n\n    /* set socket to non-blocking mode */\n    if (do_set_nonblock)\n    {\n        set_nonblock(sd);\n    }\n\n    gc_free(&gc);\n}\n\nsocket_descriptor_t\nsocket_do_accept(socket_descriptor_t sd, struct link_socket_actual *act, const bool nowait)\n{\n    /* af_addr_size WILL return 0 in this case if AFs other than AF_INET\n     * are compiled because act is empty here.\n     * could use getsockname() to support later remote_len check\n     */\n    socklen_t remote_len_af = af_addr_size(act->dest.addr.sa.sa_family);\n    socklen_t remote_len = sizeof(act->dest.addr);\n    socket_descriptor_t new_sd = SOCKET_UNDEFINED;\n\n    CLEAR(*act);\n\n    if (nowait)\n    {\n        new_sd = getpeername(sd, &act->dest.addr.sa, &remote_len);\n\n        if (!socket_defined(new_sd))\n        {\n            msg(D_LINK_ERRORS | M_ERRNO, \"TCP: getpeername() failed\");\n        }\n        else\n        {\n            new_sd = sd;\n        }\n    }\n    else\n    {\n        new_sd = accept(sd, &act->dest.addr.sa, &remote_len);\n    }\n\n#if 0 /* For debugging only, test the effect of accept() failures */\n    {\n        static int foo = 0;\n        ++foo;\n        if (foo & 1)\n        {\n            new_sd = -1;\n        }\n    }\n#endif\n\n    if (!socket_defined(new_sd))\n    {\n        msg(D_LINK_ERRORS | M_ERRNO, \"TCP: accept(%d) failed\", (int)sd);\n    }\n    /* only valid if we have remote_len_af!=0 */\n    else if (remote_len_af && remote_len != remote_len_af)\n    {\n        msg(D_LINK_ERRORS,\n            \"TCP: Received strange incoming connection with unknown address length=%d\", remote_len);\n        openvpn_close_socket(new_sd);\n        new_sd = SOCKET_UNDEFINED;\n    }\n    else\n    {\n        /* set socket file descriptor to not pass across execs, so that\n         * scripts don't have access to it */\n        set_cloexec(new_sd);\n    }\n    return new_sd;\n}\n\nstatic void\ntcp_connection_established(const struct link_socket_actual *act)\n{\n    struct gc_arena gc = gc_new();\n    msg(M_INFO, \"TCP connection established with %s\", print_link_socket_actual(act, &gc));\n    gc_free(&gc);\n}\n\nstatic socket_descriptor_t\nsocket_listen_accept(socket_descriptor_t sd, struct link_socket_actual *act,\n                     const struct addrinfo *local, bool do_listen,\n                     bool nowait, volatile int *signal_received)\n{\n    struct gc_arena gc = gc_new();\n    socket_descriptor_t new_sd = SOCKET_UNDEFINED;\n\n    CLEAR(*act);\n    socket_do_listen(sd, local, do_listen, true);\n\n    while (true)\n    {\n        int status;\n        fd_set reads;\n        struct timeval tv;\n\n        FD_ZERO(&reads);\n        openvpn_fd_set(sd, &reads);\n        tv.tv_sec = 0;\n        tv.tv_usec = 0;\n\n        status = openvpn_select(sd + 1, &reads, NULL, NULL, &tv);\n\n        get_signal(signal_received);\n        if (*signal_received)\n        {\n            gc_free(&gc);\n            return sd;\n        }\n\n        if (status < 0)\n        {\n            msg(D_LINK_ERRORS | M_ERRNO, \"TCP: select() failed\");\n        }\n\n        if (status <= 0)\n        {\n            management_sleep(1);\n            continue;\n        }\n\n        new_sd = socket_do_accept(sd, act, nowait);\n\n        if (socket_defined(new_sd))\n        {\n            break;\n        }\n        management_sleep(1);\n    }\n\n    if (!nowait && openvpn_close_socket(sd))\n    {\n        msg(M_ERR, \"TCP: close socket failed (sd)\");\n    }\n\n    tcp_connection_established(act);\n\n    gc_free(&gc);\n    return new_sd;\n}\n\nvoid\nsocket_bind(socket_descriptor_t sd, struct addrinfo *local, int ai_family, const char *prefix,\n            bool ipv6only)\n{\n    struct gc_arena gc = gc_new();\n\n    /* FIXME (schwabe)\n     * getaddrinfo for the bind address might return multiple AF_INET/AF_INET6\n     * entries for the requested protocol.\n     * For example if an address has multiple A records\n     * What is the correct way to deal with it?\n     */\n\n    struct addrinfo *cur;\n\n    ASSERT(local);\n\n\n    /* find the first addrinfo with correct ai_family */\n    for (cur = local; cur; cur = cur->ai_next)\n    {\n        if (cur->ai_family == ai_family)\n        {\n            break;\n        }\n    }\n    if (!cur)\n    {\n        msg(M_FATAL, \"%s: Socket bind failed: Addr to bind has no %s record\", prefix,\n            addr_family_name(ai_family));\n    }\n\n    if (ai_family == AF_INET6)\n    {\n        int v6only = ipv6only ? 1 : 0; /* setsockopt must have an \"int\" */\n\n        msg(M_INFO, \"setsockopt(IPV6_V6ONLY=%d)\", v6only);\n        if (setsockopt(sd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&v6only, sizeof(v6only)))\n        {\n            msg(M_NONFATAL | M_ERRNO, \"Setting IPV6_V6ONLY=%d failed\", v6only);\n        }\n    }\n    if (openvpn_bind(sd, cur->ai_addr, cur->ai_addrlen))\n    {\n        msg(M_FATAL | M_ERRNO, \"%s: Socket bind failed on local address %s\", prefix,\n            print_sockaddr_ex(local->ai_addr, \":\", PS_SHOW_PORT, &gc));\n    }\n    gc_free(&gc);\n}\n\nint\nopenvpn_connect(socket_descriptor_t sd, const struct sockaddr *remote, int connect_timeout,\n                volatile int *signal_received)\n{\n    int status = 0;\n\n#ifdef TARGET_ANDROID\n    protect_fd_nonlocal(sd, remote);\n#endif\n    set_nonblock(sd);\n    status = connect(sd, remote, af_addr_size(remote->sa_family));\n    if (status)\n    {\n        status = openvpn_errno();\n    }\n    if (\n#ifdef _WIN32\n        status == WSAEWOULDBLOCK\n#else\n        status == EINPROGRESS\n#endif\n    )\n    {\n        while (true)\n        {\n#if POLL\n            struct pollfd fds[1];\n            fds[0].fd = sd;\n            fds[0].events = POLLOUT;\n            status = poll(fds, 1, (connect_timeout > 0) ? 1000 : 0);\n#else\n            fd_set writes;\n            struct timeval tv;\n\n            FD_ZERO(&writes);\n            openvpn_fd_set(sd, &writes);\n            tv.tv_sec = (connect_timeout > 0) ? 1 : 0;\n            tv.tv_usec = 0;\n\n            status = openvpn_select(sd + 1, NULL, &writes, NULL, &tv);\n#endif\n            if (signal_received)\n            {\n                get_signal(signal_received);\n                if (*signal_received)\n                {\n                    status = 0;\n                    break;\n                }\n            }\n            if (status < 0)\n            {\n                status = openvpn_errno();\n                break;\n            }\n            if (status <= 0)\n            {\n                if (--connect_timeout < 0)\n                {\n#ifdef _WIN32\n                    status = WSAETIMEDOUT;\n#else\n                    status = ETIMEDOUT;\n#endif\n                    break;\n                }\n                management_sleep(0);\n                continue;\n            }\n\n            /* got it */\n            {\n                int val = 0;\n                socklen_t len;\n\n                len = sizeof(val);\n                if (getsockopt(sd, SOL_SOCKET, SO_ERROR, (void *)&val, &len) == 0\n                    && len == sizeof(val))\n                {\n                    status = val;\n                }\n                else\n                {\n                    status = openvpn_errno();\n                }\n                break;\n            }\n        }\n    }\n\n    return status;\n}\n\nvoid\nset_actual_address(struct link_socket_actual *actual, struct addrinfo *ai)\n{\n    CLEAR(*actual);\n    ASSERT(ai);\n\n    if (ai->ai_family == AF_INET)\n    {\n        actual->dest.addr.in4 = *((struct sockaddr_in *)ai->ai_addr);\n    }\n    else if (ai->ai_family == AF_INET6)\n    {\n        actual->dest.addr.in6 = *((struct sockaddr_in6 *)ai->ai_addr);\n    }\n    else\n    {\n        ASSERT(0);\n    }\n}\n\nstatic void\nsocket_connect(socket_descriptor_t *sd, const struct sockaddr *dest, const int connect_timeout,\n               struct signal_info *sig_info)\n{\n    struct gc_arena gc = gc_new();\n    int status;\n\n    msg(M_INFO, \"Attempting to establish TCP connection with %s\", print_sockaddr(dest, &gc));\n\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        management_set_state(management, OPENVPN_STATE_TCP_CONNECT, NULL, NULL, NULL, NULL, NULL);\n    }\n#endif\n\n    /* Set the actual address */\n    status = openvpn_connect(*sd, dest, connect_timeout, &sig_info->signal_received);\n\n    get_signal(&sig_info->signal_received);\n    if (sig_info->signal_received)\n    {\n        goto done;\n    }\n\n    if (status)\n    {\n        msg(D_LINK_ERRORS, \"TCP: connect to %s failed: %s\", print_sockaddr(dest, &gc),\n            strerror(status));\n\n        openvpn_close_socket(*sd);\n        *sd = SOCKET_UNDEFINED;\n        register_signal(sig_info, SIGUSR1, \"connection-failed\");\n    }\n    else\n    {\n        msg(M_INFO, \"TCP connection established with %s\", print_sockaddr(dest, &gc));\n    }\n\ndone:\n    gc_free(&gc);\n}\n\n/*\n * Stream buffer handling prototypes -- stream_buf is a helper class\n * to assist in the packetization of stream transport protocols\n * such as TCP.\n */\n\nstatic void stream_buf_init(struct stream_buf *sb, struct buffer *buf, const unsigned int sockflags,\n                            const int proto);\n\nstatic void stream_buf_close(struct stream_buf *sb);\n\nstatic bool stream_buf_added(struct stream_buf *sb, int length_added);\n\n/* For stream protocols, allocate a buffer to build up packet.\n * Called after frame has been finalized. */\n\nstatic void\nsocket_frame_init(const struct frame *frame, struct link_socket *sock)\n{\n#ifdef _WIN32\n    overlapped_io_init(&sock->reads, frame, FALSE);\n    overlapped_io_init(&sock->writes, frame, TRUE);\n    sock->rw_handle.read = sock->reads.overlapped.hEvent;\n    sock->rw_handle.write = sock->writes.overlapped.hEvent;\n#endif\n\n    if (link_socket_connection_oriented(sock))\n    {\n#ifdef _WIN32\n        stream_buf_init(&sock->stream_buf, &sock->reads.buf_init, sock->sockflags,\n                        sock->info.proto);\n#else\n        alloc_buf_sock_tun(&sock->stream_buf_data, frame);\n\n        stream_buf_init(&sock->stream_buf, &sock->stream_buf_data, sock->sockflags,\n                        sock->info.proto);\n#endif\n    }\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\nstatic void\nresolve_bind_local(struct link_socket *sock, const sa_family_t af)\n{\n    struct gc_arena gc = gc_new();\n\n    /* resolve local address if undefined */\n    if (!sock->info.lsa->bind_local)\n    {\n        unsigned int flags = GETADDR_RESOLVE | GETADDR_WARN_ON_SIGNAL | GETADDR_FATAL | GETADDR_PASSIVE;\n        int status;\n\n        if (proto_is_dgram(sock->info.proto))\n        {\n            flags |= GETADDR_DATAGRAM;\n        }\n\n        /* will return AF_{INET|INET6}from local_host */\n        status = get_cached_dns_entry(sock->dns_cache, sock->local_host, sock->local_port, af,\n                                      flags, &sock->info.lsa->bind_local);\n\n        if (status)\n        {\n            status = openvpn_getaddrinfo(flags, sock->local_host, sock->local_port, 0, NULL, af,\n                                         &sock->info.lsa->bind_local);\n        }\n\n        if (status != 0)\n        {\n            msg(M_FATAL, \"getaddrinfo() failed for local \\\"%s:%s\\\": %s\", sock->local_host,\n                sock->local_port, gai_strerror(status));\n        }\n\n        /* the address family returned by openvpn_getaddrinfo() should be\n         * taken into consideration only if we really passed an hostname\n         * to resolve. Otherwise its value is not useful to us and may\n         * actually break our socket, i.e. when it returns AF_INET\n         * but our remote is v6 only.\n         */\n        if (sock->local_host)\n        {\n            /* the resolved 'local entry' might have a different family than\n             * what was globally configured\n             */\n            sock->info.af = sock->info.lsa->bind_local->ai_family;\n        }\n    }\n\n    gc_free(&gc);\n}\n\nstatic void\nresolve_remote(struct link_socket *sock, int phase, struct signal_info *sig_info)\n{\n    volatile int *signal_received = sig_info ? &sig_info->signal_received : NULL;\n    struct gc_arena gc = gc_new();\n\n    /* resolve remote address if undefined */\n    if (!sock->info.lsa->remote_list)\n    {\n        if (sock->remote_host)\n        {\n            unsigned int flags =\n                sf2gaf(GETADDR_RESOLVE | GETADDR_UPDATE_MANAGEMENT_STATE, sock->sockflags);\n            int retry = 0;\n            int status = -1;\n            struct addrinfo *ai;\n            if (proto_is_dgram(sock->info.proto))\n            {\n                flags |= GETADDR_DATAGRAM;\n            }\n\n            if (sock->resolve_retry_seconds == RESOLV_RETRY_INFINITE)\n            {\n                if (phase == 2)\n                {\n                    flags |= (GETADDR_TRY_ONCE | GETADDR_FATAL);\n                }\n                retry = 0;\n            }\n            else if (phase == 1)\n            {\n                if (sock->resolve_retry_seconds)\n                {\n                    retry = 0;\n                }\n                else\n                {\n                    flags |= (GETADDR_FATAL | GETADDR_MENTION_RESOLVE_RETRY);\n                    retry = 0;\n                }\n            }\n            else if (phase == 2)\n            {\n                if (sock->resolve_retry_seconds)\n                {\n                    flags |= GETADDR_FATAL;\n                    retry = sock->resolve_retry_seconds;\n                }\n                else\n                {\n                    ASSERT(0);\n                }\n            }\n            else\n            {\n                ASSERT(0);\n            }\n\n\n            status = get_cached_dns_entry(sock->dns_cache, sock->remote_host, sock->remote_port,\n                                          sock->info.af, flags, &ai);\n            if (status)\n            {\n                status = openvpn_getaddrinfo(flags, sock->remote_host, sock->remote_port, retry,\n                                             sig_info, sock->info.af, &ai);\n            }\n\n            if (status == 0)\n            {\n                sock->info.lsa->remote_list = ai;\n                sock->info.lsa->current_remote = ai;\n\n                dmsg(D_SOCKET_DEBUG, \"RESOLVE_REMOTE flags=0x%04x phase=%d rrs=%d sig=%d status=%d\",\n                     flags, phase, retry, signal_received ? *signal_received : -1, status);\n            }\n            if (signal_received && *signal_received)\n            {\n                goto done;\n            }\n            if (status != 0)\n            {\n                if (signal_received)\n                {\n                    /* potential overwrite of signal */\n                    register_signal(sig_info, SIGUSR1, \"socks-resolve-failure\");\n                }\n                goto done;\n            }\n        }\n    }\n\n    /* should we re-use previous active remote address? */\n    if (link_socket_actual_defined(&sock->info.lsa->actual))\n    {\n        msg(M_INFO, \"TCP/UDP: Preserving recently used remote address: %s\",\n            print_link_socket_actual(&sock->info.lsa->actual, &gc));\n    }\n    else\n    {\n        CLEAR(sock->info.lsa->actual);\n        if (sock->info.lsa->current_remote)\n        {\n            set_actual_address(&sock->info.lsa->actual, sock->info.lsa->current_remote);\n        }\n    }\n\ndone:\n    gc_free(&gc);\n}\n\n\nstruct link_socket *\nlink_socket_new(void)\n{\n    struct link_socket *sock;\n\n    ALLOC_OBJ_CLEAR(sock, struct link_socket);\n    sock->sd = SOCKET_UNDEFINED;\n    sock->ctrl_sd = SOCKET_UNDEFINED;\n    sock->ev_arg.type = EVENT_ARG_LINK_SOCKET;\n    sock->ev_arg.u.sock = sock;\n\n    return sock;\n}\n\nvoid\nlink_socket_init_phase1(struct context *c, int sock_index, int mode)\n{\n    struct link_socket *sock = c->c2.link_sockets[sock_index];\n    struct options *o = &c->options;\n    ASSERT(sock);\n\n    const char *host = o->ce.local_list->array[sock_index]->local;\n    const char *port = o->ce.local_list->array[sock_index]->port;\n    int proto = o->ce.local_list->array[sock_index]->proto;\n    const char *remote_host = o->ce.remote;\n    const char *remote_port = o->ce.remote_port;\n\n    if (remote_host)\n    {\n        proto = o->ce.proto;\n    }\n\n    if (c->mode == CM_CHILD_TCP || c->mode == CM_CHILD_UDP)\n    {\n        struct link_socket *tmp_sock = NULL;\n        if (c->mode == CM_CHILD_TCP)\n        {\n            tmp_sock = (struct link_socket *)c->c2.accept_from;\n        }\n        else if (c->mode == CM_CHILD_UDP)\n        {\n            tmp_sock = c->c2.link_sockets[0];\n        }\n\n        host = tmp_sock->local_host;\n        port = tmp_sock->local_port;\n        proto = tmp_sock->info.proto;\n    }\n\n    sock->local_host = host;\n    sock->local_port = port;\n    sock->remote_host = remote_host;\n    sock->remote_port = remote_port;\n    sock->dns_cache = c->c1.dns_cache;\n    sock->http_proxy = c->c1.http_proxy;\n    sock->socks_proxy = c->c1.socks_proxy;\n    sock->bind_local = o->ce.bind_local;\n    sock->resolve_retry_seconds = o->resolve_retry_seconds;\n    sock->mtu_discover_type = o->ce.mtu_discover_type;\n\n#ifdef ENABLE_DEBUG\n    sock->gremlin = o->gremlin;\n#endif\n\n    sock->socket_buffer_sizes.rcvbuf = o->rcvbuf;\n    sock->socket_buffer_sizes.sndbuf = o->sndbuf;\n\n    sock->sockflags = o->sockflags;\n\n#if PORT_SHARE\n    if (o->port_share_host && o->port_share_port)\n    {\n        sock->sockflags |= SF_PORT_SHARE;\n    }\n#endif\n\n    sock->mark = o->mark;\n    sock->bind_dev = o->bind_dev;\n    sock->info.proto = proto;\n    sock->info.af = o->ce.af;\n    sock->info.remote_float = o->ce.remote_float;\n    sock->info.lsa = &c->c1.link_socket_addrs[sock_index];\n    sock->info.bind_ipv6_only = o->ce.bind_ipv6_only;\n    sock->info.ipchange_command = o->ipchange;\n    sock->info.plugins = c->plugins;\n    sock->server_poll_timeout = &c->c2.server_poll_interval;\n\n    sock->mode = mode;\n    if (mode == LS_MODE_TCP_ACCEPT_FROM)\n    {\n        ASSERT(c->c2.accept_from);\n        ASSERT(sock->info.proto == PROTO_TCP_SERVER);\n        sock->sd = c->c2.accept_from->sd;\n        /* inherit (possibly guessed) info AF from parent context */\n        sock->info.af = c->c2.accept_from->info.af;\n    }\n\n    /* are we running in HTTP proxy mode? */\n    if (sock->http_proxy)\n    {\n        ASSERT(sock->info.proto == PROTO_TCP_CLIENT);\n\n        /* the proxy server */\n        sock->remote_host = c->c1.http_proxy->options.server;\n        sock->remote_port = c->c1.http_proxy->options.port;\n\n        /* the OpenVPN server we will use the proxy to connect to */\n        sock->proxy_dest_host = remote_host;\n        sock->proxy_dest_port = remote_port;\n    }\n    /* or in Socks proxy mode? */\n    else if (sock->socks_proxy)\n    {\n        /* the proxy server */\n        sock->remote_host = c->c1.socks_proxy->server;\n        sock->remote_port = c->c1.socks_proxy->port;\n\n        /* the OpenVPN server we will use the proxy to connect to */\n        sock->proxy_dest_host = remote_host;\n        sock->proxy_dest_port = remote_port;\n    }\n    else\n    {\n        sock->remote_host = remote_host;\n        sock->remote_port = remote_port;\n    }\n\n    /* bind behavior for TCP server vs. client */\n    if (sock->info.proto == PROTO_TCP_SERVER)\n    {\n        if (sock->mode == LS_MODE_TCP_ACCEPT_FROM)\n        {\n            sock->bind_local = false;\n        }\n        else\n        {\n            sock->bind_local = true;\n        }\n    }\n\n    if (mode != LS_MODE_TCP_ACCEPT_FROM)\n    {\n        if (sock->bind_local)\n        {\n            resolve_bind_local(sock, sock->info.af);\n        }\n        resolve_remote(sock, 1, NULL);\n    }\n}\n\nstatic void\nphase2_set_socket_flags(struct link_socket *sock)\n{\n    /* set misc socket parameters */\n    socket_set_flags(sock->sd, sock->sockflags);\n\n    /* set socket to non-blocking mode */\n    set_nonblock(sock->sd);\n\n    /* set Path MTU discovery options on the socket */\n    set_mtu_discover_type(sock->sd, sock->mtu_discover_type, sock->info.af);\n\n#if EXTENDED_SOCKET_ERROR_CAPABILITY\n    /* if the OS supports it, enable extended error passing on the socket */\n    set_sock_extended_error_passing(sock->sd, sock->info.af);\n#endif\n}\n\n\nstatic void\nlinksock_print_addr(struct link_socket *sock)\n{\n    struct gc_arena gc = gc_new();\n    const msglvl_t msglevel = (sock->mode == LS_MODE_TCP_ACCEPT_FROM) ? D_INIT_MEDIUM : M_INFO;\n\n    /* print local address */\n    if (sock->bind_local)\n    {\n        sa_family_t ai_family = sock->info.lsa->actual.dest.addr.sa.sa_family;\n        /* Socket is always bound on the first matching address,\n         * For bound sockets with no remote addr this is the element of\n         * the list */\n        struct addrinfo *cur;\n        for (cur = sock->info.lsa->bind_local; cur; cur = cur->ai_next)\n        {\n            if (!ai_family || ai_family == cur->ai_family)\n            {\n                break;\n            }\n        }\n        ASSERT(cur);\n        msg(msglevel, \"%s link local (bound): %s\",\n            proto2ascii(sock->info.proto, sock->info.af, true), print_sockaddr(cur->ai_addr, &gc));\n    }\n    else\n    {\n        msg(msglevel, \"%s link local: (not bound)\",\n            proto2ascii(sock->info.proto, sock->info.af, true));\n    }\n\n    /* print active remote address */\n    msg(msglevel, \"%s link remote: %s\", proto2ascii(sock->info.proto, sock->info.af, true),\n        print_link_socket_actual_ex(&sock->info.lsa->actual, \":\", PS_SHOW_PORT_IF_DEFINED, &gc));\n    gc_free(&gc);\n}\n\nstatic void\nphase2_tcp_server(struct link_socket *sock, struct signal_info *sig_info)\n{\n    ASSERT(sig_info);\n    volatile int *signal_received = &sig_info->signal_received;\n    switch (sock->mode)\n    {\n        case LS_MODE_DEFAULT:\n            sock->sd =\n                socket_listen_accept(sock->sd, &sock->info.lsa->actual,\n                                     sock->info.lsa->bind_local, true, false,\n                                     signal_received);\n            break;\n\n        case LS_MODE_TCP_LISTEN:\n            socket_do_listen(sock->sd, sock->info.lsa->bind_local, true, false);\n            break;\n\n        case LS_MODE_TCP_ACCEPT_FROM:\n            sock->sd = socket_do_accept(sock->sd, &sock->info.lsa->actual, false);\n            if (!socket_defined(sock->sd))\n            {\n                register_signal(sig_info, SIGTERM, \"socket-undefined\");\n                return;\n            }\n            tcp_connection_established(&sock->info.lsa->actual);\n            break;\n\n        default:\n            ASSERT(0);\n    }\n}\n\n\nstatic void\nphase2_tcp_client(struct link_socket *sock, struct signal_info *sig_info)\n{\n    bool proxy_retry = false;\n    do\n    {\n        socket_connect(&sock->sd, sock->info.lsa->current_remote->ai_addr,\n                       get_server_poll_remaining_time(sock->server_poll_timeout), sig_info);\n\n        if (sig_info->signal_received)\n        {\n            return;\n        }\n\n        if (sock->http_proxy)\n        {\n            proxy_retry = establish_http_proxy_passthru(\n                sock->http_proxy, sock->sd, sock->proxy_dest_host, sock->proxy_dest_port,\n                sock->server_poll_timeout, &sock->stream_buf.residual, sig_info);\n        }\n        else if (sock->socks_proxy)\n        {\n            establish_socks_proxy_passthru(sock->socks_proxy, sock->sd, sock->proxy_dest_host,\n                                           sock->proxy_dest_port, sock->server_poll_timeout,\n                                           sig_info);\n        }\n        if (proxy_retry)\n        {\n            openvpn_close_socket(sock->sd);\n            sock->sd = create_socket_tcp(sock->info.lsa->current_remote);\n        }\n\n    } while (proxy_retry);\n}\n\nstatic void\nphase2_socks_client(struct link_socket *sock, struct signal_info *sig_info)\n{\n    socket_connect(&sock->ctrl_sd, sock->info.lsa->current_remote->ai_addr,\n                   get_server_poll_remaining_time(sock->server_poll_timeout), sig_info);\n\n    if (sig_info->signal_received)\n    {\n        return;\n    }\n\n    establish_socks_proxy_udpassoc(sock->socks_proxy, sock->ctrl_sd, &sock->socks_relay.dest,\n                                   sock->server_poll_timeout, sig_info);\n\n    if (sig_info->signal_received)\n    {\n        return;\n    }\n\n    sock->remote_host = sock->proxy_dest_host;\n    sock->remote_port = sock->proxy_dest_port;\n\n    addr_zero_host(&sock->info.lsa->actual.dest);\n    if (sock->info.lsa->remote_list)\n    {\n        freeaddrinfo(sock->info.lsa->remote_list);\n        sock->info.lsa->current_remote = NULL;\n        sock->info.lsa->remote_list = NULL;\n    }\n\n    resolve_remote(sock, 1, sig_info);\n}\n\n#if defined(_WIN32)\nstatic void\ncreate_socket_dco_win(struct context *c, struct link_socket *sock, struct signal_info *sig_info)\n{\n    /* in P2P mode we must have remote resolved at this point */\n    struct addrinfo *remoteaddr = sock->info.lsa->current_remote;\n    if ((c->options.mode == MODE_POINT_TO_POINT) && (!remoteaddr))\n    {\n        return;\n    }\n\n    if (!c->c1.tuntap)\n    {\n        struct tuntap *tt;\n        ALLOC_OBJ_CLEAR(tt, struct tuntap);\n\n        tt->backend_driver = DRIVER_DCO;\n        tt->options.msg_channel = c->options.msg_channel;\n\n        const char *device_guid = NULL; /* not used */\n        tun_open_device(tt, c->options.dev_node, &device_guid, &c->gc);\n\n        /* Ensure we can \"safely\" cast the handle to a socket */\n        static_assert(sizeof(sock->sd) == sizeof(tt->hand), \"HANDLE and SOCKET size differs\");\n\n        c->c1.tuntap = tt;\n    }\n\n    if (c->options.mode == MODE_SERVER)\n    {\n        dco_mp_start_vpn(c->c1.tuntap->hand, sock);\n    }\n    else\n    {\n        dco_p2p_new_peer(c->c1.tuntap->hand, &c->c1.tuntap->dco_new_peer_ov, sock, sig_info);\n    }\n    sock->sockflags |= SF_DCO_WIN;\n\n    if (sig_info->signal_received)\n    {\n        return;\n    }\n\n    sock->sd = (SOCKET)c->c1.tuntap->hand;\n    linksock_print_addr(sock);\n}\n#endif /* if defined(_WIN32) */\n\n/* finalize socket initialization */\nvoid\nlink_socket_init_phase2(struct context *c, struct link_socket *sock)\n{\n    const struct frame *frame = &c->c2.frame;\n    struct signal_info *sig_info = c->sig;\n\n    struct signal_info sig_save = { 0 };\n\n    ASSERT(sock);\n    ASSERT(sig_info);\n\n    if (sig_info->signal_received)\n    {\n        sig_save = *sig_info;\n        sig_save.signal_received = signal_reset(sig_info, 0);\n    }\n\n    /* initialize buffers */\n    socket_frame_init(frame, sock);\n\n    /* Second chance to resolv/create socket */\n    resolve_remote(sock, 2, sig_info);\n\n    /* If a valid remote has been found, create the socket with its addrinfo */\n#if defined(_WIN32)\n    if (dco_enabled(&c->options))\n    {\n        create_socket_dco_win(c, sock, sig_info);\n        goto done;\n    }\n#endif\n    if (sock->info.lsa->current_remote)\n    {\n        create_socket(sock, sock->info.lsa->current_remote);\n    }\n\n    /* If socket has not already been created create it now */\n    if (sock->sd == SOCKET_UNDEFINED)\n    {\n        /* If we have no --remote and have still not figured out the\n         * protocol family to use we will use the first of the bind */\n\n        if (sock->bind_local && !sock->remote_host && sock->info.lsa->bind_local)\n        {\n            /* Warn if this is because neither v4 or v6 was specified\n             * and we should not connect a remote */\n            if (sock->info.af == AF_UNSPEC)\n            {\n                msg(M_WARN, \"Could not determine IPv4/IPv6 protocol. Using %s\",\n                    addr_family_name(sock->info.lsa->bind_local->ai_family));\n                sock->info.af = sock->info.lsa->bind_local->ai_family;\n            }\n            create_socket(sock, sock->info.lsa->bind_local);\n        }\n    }\n\n    /* Socket still undefined, give a warning and abort connection */\n    if (sock->sd == SOCKET_UNDEFINED)\n    {\n        msg(M_WARN, \"Could not determine IPv4/IPv6 protocol\");\n        register_signal(sig_info, SIGUSR1, \"Could not determine IPv4/IPv6 protocol\");\n        goto done;\n    }\n\n    if (sig_info->signal_received)\n    {\n        goto done;\n    }\n\n    if (sock->info.proto == PROTO_TCP_SERVER)\n    {\n        phase2_tcp_server(sock, sig_info);\n    }\n    else if (sock->info.proto == PROTO_TCP_CLIENT)\n    {\n        phase2_tcp_client(sock, sig_info);\n    }\n    else if (sock->info.proto == PROTO_UDP && sock->socks_proxy)\n    {\n        phase2_socks_client(sock, sig_info);\n    }\n#ifdef TARGET_ANDROID\n    if (sock->sd != -1)\n    {\n        protect_fd_nonlocal(sock->sd, &sock->info.lsa->actual.dest.addr.sa);\n    }\n#endif\n    if (sig_info->signal_received)\n    {\n        goto done;\n    }\n\n    phase2_set_socket_flags(sock);\n    linksock_print_addr(sock);\n\ndone:\n    if (sig_save.signal_received)\n    {\n        /* Always restore the saved signal -- register/throw_signal will handle priority */\n        if (sig_save.source == SIG_SOURCE_HARD && sig_info == &siginfo_static)\n        {\n            throw_signal(sig_save.signal_received);\n        }\n        else\n        {\n            register_signal(sig_info, sig_save.signal_received, sig_save.signal_text);\n        }\n    }\n}\n\nvoid\nlink_socket_close(struct link_socket *sock)\n{\n    if (sock)\n    {\n#ifdef ENABLE_DEBUG\n        const int gremlin = GREMLIN_CONNECTION_FLOOD_LEVEL(sock->gremlin);\n#else\n        const int gremlin = 0;\n#endif\n\n        if (socket_defined(sock->sd))\n        {\n#ifdef _WIN32\n            close_net_event_win32(&sock->listen_handle, sock->sd, 0);\n#endif\n            if (!gremlin)\n            {\n                msg(D_LOW, \"TCP/UDP: Closing socket\");\n                if (openvpn_close_socket(sock->sd))\n                {\n                    msg(M_WARN | M_ERRNO, \"TCP/UDP: Close Socket failed\");\n                }\n            }\n            sock->sd = SOCKET_UNDEFINED;\n#ifdef _WIN32\n            if (!gremlin)\n            {\n                overlapped_io_close(&sock->reads);\n                overlapped_io_close(&sock->writes);\n            }\n#endif\n        }\n\n        if (socket_defined(sock->ctrl_sd))\n        {\n            if (openvpn_close_socket(sock->ctrl_sd))\n            {\n                msg(M_WARN | M_ERRNO, \"TCP/UDP: Close Socket (ctrl_sd) failed\");\n            }\n            sock->ctrl_sd = SOCKET_UNDEFINED;\n        }\n\n        stream_buf_close(&sock->stream_buf);\n        free_buf(&sock->stream_buf_data);\n        if (!gremlin)\n        {\n            free(sock);\n        }\n    }\n}\n\nvoid\nsetenv_trusted(struct env_set *es, const struct link_socket_info *info)\n{\n    setenv_link_socket_actual(es, \"trusted\", &info->lsa->actual, SA_IP_PORT);\n}\n\nstatic void\nipchange_fmt(const bool include_cmd, struct argv *argv, const struct link_socket_info *info,\n             struct gc_arena *gc)\n{\n    const char *host = print_sockaddr_ex(&info->lsa->actual.dest.addr.sa, \" \", PS_SHOW_PORT, gc);\n    if (include_cmd)\n    {\n        argv_parse_cmd(argv, info->ipchange_command);\n        argv_printf_cat(argv, \"%s\", host);\n    }\n    else\n    {\n        argv_printf(argv, \"%s\", host);\n    }\n}\n\nvoid\nlink_socket_connection_initiated(struct link_socket_info *info,\n                                 const struct link_socket_actual *act, const char *common_name,\n                                 struct env_set *es)\n{\n    struct gc_arena gc = gc_new();\n\n    info->lsa->actual = *act; /* Note: skip this line for --force-dest */\n    setenv_trusted(es, info);\n    info->connection_established = true;\n\n    /* Print connection initiated message, with common name if available */\n    {\n        struct buffer out = alloc_buf_gc(256, &gc);\n        if (common_name)\n        {\n            buf_printf(&out, \"[%s] \", common_name);\n        }\n        buf_printf(&out, \"Peer Connection Initiated with %s\",\n                   print_link_socket_actual(&info->lsa->actual, &gc));\n        msg(M_INFO, \"%s\", BSTR(&out));\n    }\n\n    /* set environmental vars */\n    setenv_str(es, \"common_name\", common_name);\n\n    /* Process --ipchange plugin */\n    if (plugin_defined(info->plugins, OPENVPN_PLUGIN_IPCHANGE))\n    {\n        struct argv argv = argv_new();\n        ipchange_fmt(false, &argv, info, &gc);\n        if (plugin_call(info->plugins, OPENVPN_PLUGIN_IPCHANGE, &argv, NULL, es)\n            != OPENVPN_PLUGIN_FUNC_SUCCESS)\n        {\n            msg(M_WARN, \"WARNING: ipchange plugin call failed\");\n        }\n        argv_free(&argv);\n    }\n\n    /* Process --ipchange option */\n    if (info->ipchange_command)\n    {\n        struct argv argv = argv_new();\n        setenv_str(es, \"script_type\", \"ipchange\");\n        ipchange_fmt(true, &argv, info, &gc);\n        openvpn_run_script(&argv, es, 0, \"--ipchange\");\n        argv_free(&argv);\n    }\n\n    gc_free(&gc);\n}\n\nvoid\nlink_socket_bad_incoming_addr(struct buffer *buf, const struct link_socket_info *info,\n                              const struct link_socket_actual *from_addr)\n{\n    struct gc_arena gc = gc_new();\n    struct addrinfo *ai;\n\n    switch (from_addr->dest.addr.sa.sa_family)\n    {\n        case AF_INET:\n        case AF_INET6:\n            msg(D_LINK_ERRORS,\n                \"TCP/UDP: Incoming packet rejected from %s[%d], expected peer address: %s (allow this incoming source address/port by removing --remote or adding --float)\",\n                print_link_socket_actual(from_addr, &gc), (int)from_addr->dest.addr.sa.sa_family,\n                print_sockaddr_ex(info->lsa->remote_list->ai_addr, \":\", PS_SHOW_PORT, &gc));\n            /* print additional remote addresses */\n            for (ai = info->lsa->remote_list->ai_next; ai; ai = ai->ai_next)\n            {\n                msg(D_LINK_ERRORS, \"or from peer address: %s\",\n                    print_sockaddr_ex(ai->ai_addr, \":\", PS_SHOW_PORT, &gc));\n            }\n            break;\n    }\n    buf->len = 0;\n    gc_free(&gc);\n}\n\nvoid\nlink_socket_bad_outgoing_addr(void)\n{\n    dmsg(D_READ_WRITE, \"TCP/UDP: No outgoing address to send packet\");\n}\n\nin_addr_t\nlink_socket_current_remote(const struct link_socket_info *info)\n{\n    const struct link_socket_addr *lsa = info->lsa;\n\n    /*\n     * This logic supports \"redirect-gateway\" semantic, which\n     * makes sense only for PF_INET routes over PF_INET endpoints\n     *\n     * Maybe in the future consider PF_INET6 endpoints also ...\n     * by now just ignore it\n     *\n     * For --remote entries with multiple addresses this\n     * only return the actual endpoint we have successfully connected to\n     */\n    if (lsa->actual.dest.addr.sa.sa_family != AF_INET)\n    {\n        return IPV4_INVALID_ADDR;\n    }\n\n    if (link_socket_actual_defined(&lsa->actual))\n    {\n        return ntohl(lsa->actual.dest.addr.in4.sin_addr.s_addr);\n    }\n    else if (lsa->current_remote)\n    {\n        return ntohl(((struct sockaddr_in *)lsa->current_remote->ai_addr)->sin_addr.s_addr);\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nconst struct in6_addr *\nlink_socket_current_remote_ipv6(const struct link_socket_info *info)\n{\n    const struct link_socket_addr *lsa = info->lsa;\n\n    /* This logic supports \"redirect-gateway\" semantic,\n     * for PF_INET6 routes over PF_INET6 endpoints\n     *\n     * For --remote entries with multiple addresses this\n     * only return the actual endpoint we have successfully connected to\n     */\n    if (lsa->actual.dest.addr.sa.sa_family != AF_INET6)\n    {\n        return NULL;\n    }\n\n    if (link_socket_actual_defined(&lsa->actual))\n    {\n        return &(lsa->actual.dest.addr.in6.sin6_addr);\n    }\n    else if (lsa->current_remote)\n    {\n        return &(((struct sockaddr_in6 *)lsa->current_remote->ai_addr)->sin6_addr);\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\n/*\n * Return a status string describing socket state.\n */\nconst char *\nsocket_stat(const struct link_socket *s, unsigned int rwflags, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(64, gc);\n    if (s)\n    {\n        if (rwflags & EVENT_READ)\n        {\n            buf_printf(&out, \"S%s\", (s->rwflags_debug & EVENT_READ) ? \"R\" : \"r\");\n#ifdef _WIN32\n            buf_printf(&out, \"%s\", overlapped_io_state_ascii(&s->reads));\n#endif\n        }\n        if (rwflags & EVENT_WRITE)\n        {\n            buf_printf(&out, \"S%s\", (s->rwflags_debug & EVENT_WRITE) ? \"W\" : \"w\");\n#ifdef _WIN32\n            buf_printf(&out, \"%s\", overlapped_io_state_ascii(&s->writes));\n#endif\n        }\n    }\n    else\n    {\n        buf_printf(&out, \"S?\");\n    }\n    return BSTR(&out);\n}\n\n/*\n * Stream buffer functions, used to packetize a TCP\n * stream connection.\n */\n\n/**\n * resets the stream buffer to be set up for the next round of\n * reassembling a packet\n *\n * But still leaves the current packet in \\c sb->buf to be potentially\n * read.\n */\nstatic inline void\nstream_buf_reset(struct stream_buf *sb)\n{\n    dmsg(D_STREAM_DEBUG, \"STREAM: RESET\");\n    sb->residual_fully_formed = false;\n    sb->buf = sb->buf_init;\n    sb->len = -1;\n}\n\nstatic void\nstream_buf_init(struct stream_buf *sb, struct buffer *buf, const unsigned int sockflags,\n                const int proto)\n{\n    sb->buf_init = *buf;\n    sb->maxlen = sb->buf_init.len;\n    sb->buf_init.len = 0;\n    sb->residual = alloc_buf(sb->maxlen);\n    sb->error = false;\n#if PORT_SHARE\n    sb->port_share_state =\n        ((sockflags & SF_PORT_SHARE) && (proto == PROTO_TCP_SERVER)) ? PS_ENABLED : PS_DISABLED;\n#endif\n    stream_buf_reset(sb);\n\n    dmsg(D_STREAM_DEBUG, \"STREAM: INIT maxlen=%d\", sb->maxlen);\n}\n\n/**\n * Return a buffer that is backed by the same backend as sb->buf that\n * determines where the next read should be done by also having the\n * right offset into \\c sb->buf.\n * @param sb the stream buffer from which to construct the next buffer\n */\nstatic inline struct buffer\nstream_buf_get_next(struct stream_buf *sb)\n{\n    /* set up 'next' for next i/o read */\n    struct buffer next;\n    next = sb->buf;\n    next.offset = sb->buf.offset + sb->buf.len;\n    next.len = (sb->len >= 0 ? sb->len : sb->maxlen) - sb->buf.len;\n    dmsg(D_STREAM_DEBUG, \"STREAM: GET NEXT, buf=[%d,%d] next=[%d,%d] len=%d maxlen=%d\",\n         sb->buf.offset, sb->buf.len, next.offset, next.len, sb->len, sb->maxlen);\n    ASSERT(next.len > 0);\n    ASSERT(buf_safe(&sb->buf, next.len));\n    return next;\n}\n\n/**\n * Sets the parameter buf to the current buffer of \\c sb->buf.\n * This function assumes that caller already checked if the packet in \\c sb->buf\n * is fully assembled.\n *\n * @param sb    stream buffer to operate on\n * @param buf   buffer to point to the contents of buf\n */\nstatic inline void\nstream_buf_get_final(struct stream_buf *sb, struct buffer *buf)\n{\n    dmsg(D_STREAM_DEBUG, \"STREAM: GET FINAL len=%d\", buf_defined(&sb->buf) ? sb->buf.len : -1);\n    ASSERT(buf_defined(&sb->buf));\n    *buf = sb->buf;\n}\n\nbool\nstream_buf_read_setup_dowork(struct stream_buf *sb)\n{\n    if (sb->residual.len && !sb->residual_fully_formed)\n    {\n        ASSERT(buf_copy(&sb->buf, &sb->residual));\n        ASSERT(buf_init(&sb->residual, 0));\n        sb->residual_fully_formed = stream_buf_added(sb, 0);\n        dmsg(D_STREAM_DEBUG, \"STREAM: RESIDUAL FULLY FORMED [%s], len=%d\",\n             sb->residual_fully_formed ? \"YES\" : \"NO\", sb->residual.len);\n    }\n\n    return !sb->residual_fully_formed;\n}\n\n/**\n * This will determine if \\c sb->buf contains a full packet. It will also\n * move anything in \\c sb->buf beyond a full packet to \\c sb->residual.\n *\n * The first time the function is called with a valid buffer and port sharing\n * is enabled, the function will also determine if the buffer contains\n * OpenVPN protocol data and store the result in \\c sb->port_share_state.\n *\n * If a packet outside the allowed range is detected, the error state\n * on \\c sb is set.\n *\n * Since the buffer in \\c sb->buf is modified from the outside (via\n * \\c stream_buf_get_next) the parameter \\p length_added needs to be set\n * to the amount of bytes that have been written to this buffer. If the\n * buffer was not modified but should still be analysed and potentially\n * split to \\c sb->residual, the parameter \\p length_added should be 0.\n *\n * @param sb the stream buffer\n * @param length_added The length that has been added to \\c sb->buf\n * @return true if \\c sb->buf contains fully reassembled packet\n */\nstatic bool\nstream_buf_added(struct stream_buf *sb, int length_added)\n{\n    dmsg(D_STREAM_DEBUG, \"STREAM: ADD length_added=%d\", length_added);\n    if (length_added > 0)\n    {\n        sb->buf.len += length_added;\n    }\n\n    /* if length unknown, see if we can get the length prefix from\n     * the head of the buffer */\n    if (sb->len < 0 && sb->buf.len >= (int)sizeof(packet_size_type))\n    {\n        packet_size_type net_size;\n\n#if PORT_SHARE\n        if (sb->port_share_state == PS_ENABLED)\n        {\n            if (!is_openvpn_protocol(&sb->buf))\n            {\n                msg(D_PS_PROXY, \"Non-OpenVPN client protocol detected\");\n                sb->port_share_state = PS_FOREIGN;\n                sb->error = true;\n                return false;\n            }\n            else\n            {\n                sb->port_share_state = PS_DISABLED;\n            }\n        }\n#endif\n\n        ASSERT(buf_read(&sb->buf, &net_size, sizeof(net_size)));\n        sb->len = ntohps(net_size);\n\n        if (sb->len < 1 || sb->len > sb->maxlen)\n        {\n            msg(M_WARN,\n                \"WARNING: Bad encapsulated packet length from peer (%d), which must be > 0 and <= %d -- please ensure that --tun-mtu or --link-mtu is equal on both peers -- this condition could also indicate a possible active attack on the TCP link -- [Attempting restart...]\",\n                sb->len, sb->maxlen);\n            stream_buf_reset(sb);\n            sb->error = true;\n            return false;\n        }\n    }\n\n    /* is our incoming packet fully read? */\n    if (sb->len > 0 && sb->buf.len >= sb->len)\n    {\n        /* save any residual data that's part of the next packet */\n        ASSERT(buf_init(&sb->residual, 0));\n        if (sb->buf.len > sb->len)\n        {\n            ASSERT(buf_copy_excess(&sb->residual, &sb->buf, sb->len));\n        }\n        dmsg(D_STREAM_DEBUG, \"STREAM: ADD returned TRUE, buf_len=%d, residual_len=%d\",\n             BLEN(&sb->buf), BLEN(&sb->residual));\n        return true;\n    }\n    else\n    {\n        dmsg(D_STREAM_DEBUG, \"STREAM: ADD returned FALSE (have=%d need=%d)\", sb->buf.len, sb->len);\n        return false;\n    }\n}\n\nstatic void\nstream_buf_close(struct stream_buf *sb)\n{\n    free_buf(&sb->residual);\n}\n\n/*\n * The listen event is a special event whose sole purpose is\n * to tell us that there's a new incoming connection on a\n * TCP socket, for use in server mode.\n */\nevent_t\nsocket_listen_event_handle(struct link_socket *s)\n{\n#ifdef _WIN32\n    if (!defined_net_event_win32(&s->listen_handle))\n    {\n        init_net_event_win32(&s->listen_handle, FD_ACCEPT, s->sd, 0);\n    }\n    return &s->listen_handle;\n#else /* ifdef _WIN32 */\n    return s->sd;\n#endif\n}\n\n\n/*\n * Bad incoming address lengths that differ from what\n * we expect are considered to be fatal errors.\n */\nvoid\nbad_address_length(int actual, int expected)\n{\n    msg(M_FATAL,\n        \"ERROR: received strange incoming packet with an address length of %d -- we only accept address lengths of %d.\",\n        actual, expected);\n}\n\n/*\n * Socket Read Routines\n */\n\nint\nlink_socket_read_tcp(struct link_socket *sock, struct buffer *buf)\n{\n    int len = 0;\n\n    if (!sock->stream_buf.residual_fully_formed)\n    {\n        /* with Linux-DCO, we sometimes try to access a socket that is\n         * already installed in the kernel and has no valid file descriptor\n         * anymore.  This is a bug.\n         * Handle by resetting client instance instead of crashing.\n         */\n        if (sock->sd == SOCKET_UNDEFINED)\n        {\n            msg(M_INFO, \"BUG: link_socket_read_tcp(): sock->sd==-1, reset client instance\");\n            sock->stream_reset = true; /* reset client instance */\n            return buf->len = 0;       /* nothing to read */\n        }\n\n#ifdef _WIN32\n        sockethandle_t sh = { .s = sock->sd };\n        len = sockethandle_finalize(sh, &sock->reads, buf, NULL);\n#else\n        struct buffer frag = stream_buf_get_next(&sock->stream_buf);\n        len = recv(sock->sd, BPTR(&frag), BLENZ(&frag), MSG_NOSIGNAL);\n#endif\n\n        if (!len)\n        {\n            sock->stream_reset = true;\n        }\n        if (len <= 0)\n        {\n            return buf->len = len;\n        }\n    }\n\n    if (sock->stream_buf.residual_fully_formed\n        || stream_buf_added(&sock->stream_buf, len)) /* packet complete? */\n    {\n        stream_buf_get_final(&sock->stream_buf, buf);\n        stream_buf_reset(&sock->stream_buf);\n        return buf->len;\n    }\n    else\n    {\n        return buf->len = 0; /* no error, but packet is still incomplete */\n    }\n}\n\n#ifndef _WIN32\n\n#if ENABLE_IP_PKTINFO\n\n/* make the buffer large enough to handle ancillary socket data for\n * both IPv4 and IPv6 destination addresses, plus padding (see RFC 2292)\n */\n#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)\n#define PKTINFO_BUF_SIZE \\\n    max_int(CMSG_SPACE(sizeof(struct in6_pktinfo)), CMSG_SPACE(sizeof(struct in_pktinfo)))\n#else\n#define PKTINFO_BUF_SIZE \\\n    max_int(CMSG_SPACE(sizeof(struct in6_pktinfo)), CMSG_SPACE(sizeof(struct in_addr)))\n#endif\n\nstatic socklen_t\nlink_socket_read_udp_posix_recvmsg(struct link_socket *sock, struct buffer *buf,\n                                   struct link_socket_actual *from)\n{\n    struct iovec iov;\n    uint8_t pktinfo_buf[PKTINFO_BUF_SIZE];\n    struct msghdr mesg = { 0 };\n    socklen_t fromlen = sizeof(from->dest.addr);\n\n    ASSERT(sock->sd >= 0); /* can't happen */\n\n    iov.iov_base = BPTR(buf);\n    iov.iov_len = buf_forward_capacity_total(buf);\n    mesg.msg_iov = &iov;\n    mesg.msg_iovlen = 1;\n    mesg.msg_name = &from->dest.addr;\n    mesg.msg_namelen = fromlen;\n    mesg.msg_control = pktinfo_buf;\n    mesg.msg_controllen = sizeof pktinfo_buf;\n    buf->len = recvmsg(sock->sd, &mesg, 0);\n    if (buf->len >= 0)\n    {\n        struct cmsghdr *cmsg;\n        fromlen = mesg.msg_namelen;\n        cmsg = CMSG_FIRSTHDR(&mesg);\n        if (cmsg != NULL && CMSG_NXTHDR(&mesg, cmsg) == NULL\n#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)\n            && cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_PKTINFO\n            && cmsg->cmsg_len >= CMSG_LEN(sizeof(struct in_pktinfo)))\n#elif defined(IP_RECVDSTADDR)\n            && cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVDSTADDR\n            && cmsg->cmsg_len >= CMSG_LEN(sizeof(struct in_addr)))\n#else /* if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) */\n#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix syshead.h)\n#endif\n        {\n#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)\n            struct in_pktinfo *pkti = (struct in_pktinfo *)CMSG_DATA(cmsg);\n            from->pi.in4.ipi_ifindex =\n                (sock->sockflags & SF_PKTINFO_COPY_IIF) ? pkti->ipi_ifindex : 0;\n            from->pi.in4.ipi_spec_dst = pkti->ipi_spec_dst;\n#elif defined(IP_RECVDSTADDR)\n            from->pi.in4 = *(struct in_addr *)CMSG_DATA(cmsg);\n#else /* if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) */\n#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix syshead.h)\n#endif\n        }\n        else if (cmsg != NULL && CMSG_NXTHDR(&mesg, cmsg) == NULL\n                 && cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO\n                 && cmsg->cmsg_len >= CMSG_LEN(sizeof(struct in6_pktinfo)))\n        {\n            struct in6_pktinfo *pkti6 = (struct in6_pktinfo *)CMSG_DATA(cmsg);\n            from->pi.in6.ipi6_ifindex =\n                (sock->sockflags & SF_PKTINFO_COPY_IIF) ? pkti6->ipi6_ifindex : 0;\n            from->pi.in6.ipi6_addr = pkti6->ipi6_addr;\n        }\n        else if (cmsg != NULL)\n        {\n            msg(M_WARN,\n                \"CMSG received that cannot be parsed (cmsg_level=%d, cmsg_type=%d, cmsg=len=%d)\",\n                (int)cmsg->cmsg_level, (int)cmsg->cmsg_type, (int)cmsg->cmsg_len);\n        }\n    }\n\n    return fromlen;\n}\n#endif /* if ENABLE_IP_PKTINFO */\n\nint\nlink_socket_read_udp_posix(struct link_socket *sock, struct buffer *buf,\n                           struct link_socket_actual *from)\n{\n    socklen_t fromlen = sizeof(from->dest.addr);\n    socklen_t expectedlen = af_addr_size(sock->info.af);\n    addr_zero_host(&from->dest);\n\n    ASSERT(sock->sd >= 0); /* can't happen */\n\n#if ENABLE_IP_PKTINFO\n    /* Both PROTO_UDPv4 and PROTO_UDPv6 */\n    if (sock->info.proto == PROTO_UDP && sock->sockflags & SF_USE_IP_PKTINFO)\n    {\n        fromlen = link_socket_read_udp_posix_recvmsg(sock, buf, from);\n    }\n    else\n#endif\n    {\n        buf->len = recvfrom(sock->sd, BPTR(buf), buf_forward_capacity(buf), 0, &from->dest.addr.sa,\n                            &fromlen);\n    }\n    /* FIXME: won't do anything when sock->info.af == AF_UNSPEC */\n    if (buf->len >= 0 && expectedlen && fromlen != expectedlen)\n    {\n        bad_address_length(fromlen, expectedlen);\n    }\n    return buf->len;\n}\n\n#endif /* ifndef _WIN32 */\n\n/*\n * Socket Write Routines\n */\n\nssize_t\nlink_socket_write_tcp(struct link_socket *sock, struct buffer *buf, struct link_socket_actual *to)\n{\n    packet_size_type len = (packet_size_type)BLENZ(buf);\n    dmsg(D_STREAM_DEBUG, \"STREAM: WRITE %u offset=%d\", len, buf->offset);\n    ASSERT(len <= sock->stream_buf.maxlen);\n    len = htonps(len);\n    ASSERT(buf_write_prepend(buf, &len, sizeof(len)));\n#ifdef _WIN32\n    return link_socket_write_win32(sock, buf, to);\n#else\n    return link_socket_write_tcp_posix(sock, buf);\n#endif\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n#if ENABLE_IP_PKTINFO\n\nssize_t\nlink_socket_write_udp_posix_sendmsg(struct link_socket *sock, struct buffer *buf,\n                                    struct link_socket_actual *to)\n{\n    struct iovec iov;\n    struct msghdr mesg;\n    struct cmsghdr *cmsg;\n    uint8_t pktinfo_buf[PKTINFO_BUF_SIZE];\n\n    iov.iov_base = BPTR(buf);\n    iov.iov_len = BLENZ(buf);\n    mesg.msg_iov = &iov;\n    mesg.msg_iovlen = 1;\n    switch (to->dest.addr.sa.sa_family)\n    {\n        case AF_INET:\n        {\n            mesg.msg_name = &to->dest.addr.sa;\n            mesg.msg_namelen = sizeof(struct sockaddr_in);\n            mesg.msg_control = pktinfo_buf;\n            mesg.msg_flags = 0;\n#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)\n            mesg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));\n            cmsg = CMSG_FIRSTHDR(&mesg);\n            cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));\n            cmsg->cmsg_level = SOL_IP;\n            cmsg->cmsg_type = IP_PKTINFO;\n            {\n                struct in_pktinfo *pkti;\n                pkti = (struct in_pktinfo *)CMSG_DATA(cmsg);\n                pkti->ipi_ifindex = to->pi.in4.ipi_ifindex;\n                pkti->ipi_spec_dst = to->pi.in4.ipi_spec_dst;\n                pkti->ipi_addr.s_addr = 0;\n            }\n#elif defined(IP_RECVDSTADDR)\n            ASSERT(CMSG_SPACE(sizeof(struct in_addr)) <= sizeof(pktinfo_buf));\n            mesg.msg_controllen = CMSG_SPACE(sizeof(struct in_addr));\n            cmsg = CMSG_FIRSTHDR(&mesg);\n            cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));\n            cmsg->cmsg_level = IPPROTO_IP;\n            cmsg->cmsg_type = IP_RECVDSTADDR;\n            *(struct in_addr *)CMSG_DATA(cmsg) = to->pi.in4;\n#else  /* if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) */\n#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix syshead.h)\n#endif /* if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) */\n            break;\n        }\n\n        case AF_INET6:\n        {\n            struct in6_pktinfo *pkti6;\n            mesg.msg_name = &to->dest.addr.sa;\n            mesg.msg_namelen = sizeof(struct sockaddr_in6);\n\n            ASSERT(CMSG_SPACE(sizeof(struct in6_pktinfo)) <= sizeof(pktinfo_buf));\n            mesg.msg_control = pktinfo_buf;\n            mesg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));\n            mesg.msg_flags = 0;\n            cmsg = CMSG_FIRSTHDR(&mesg);\n            cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));\n            cmsg->cmsg_level = IPPROTO_IPV6;\n            cmsg->cmsg_type = IPV6_PKTINFO;\n\n            pkti6 = (struct in6_pktinfo *)CMSG_DATA(cmsg);\n            pkti6->ipi6_ifindex = to->pi.in6.ipi6_ifindex;\n            pkti6->ipi6_addr = to->pi.in6.ipi6_addr;\n            break;\n        }\n\n        default:\n            ASSERT(0);\n    }\n    return sendmsg(sock->sd, &mesg, 0);\n}\n\n#endif /* if ENABLE_IP_PKTINFO */\n\n/*\n * Win32 overlapped socket I/O functions.\n */\n\n#ifdef _WIN32\n\nstatic int\nsocket_get_last_error(const struct link_socket *sock)\n{\n    if (socket_is_dco_win(sock))\n    {\n        return GetLastError();\n    }\n\n    return WSAGetLastError();\n}\n\nint\nsocket_recv_queue(struct link_socket *sock, int maxsize)\n{\n    if (sock->reads.iostate == IOSTATE_INITIAL)\n    {\n        WSABUF wsabuf[1];\n        int status;\n\n        /* reset buf to its initial state */\n        if (proto_is_udp(sock->info.proto))\n        {\n            sock->reads.buf = sock->reads.buf_init;\n        }\n        else if (proto_is_tcp(sock->info.proto))\n        {\n            sock->reads.buf = stream_buf_get_next(&sock->stream_buf);\n        }\n        else\n        {\n            ASSERT(0);\n        }\n\n        /* Win32 docs say it's okay to allocate the wsabuf on the stack */\n        wsabuf[0].buf = BSTR(&sock->reads.buf);\n        /* make sure maxsize is sane */\n        ASSERT(maxsize <= BLEN(&sock->reads.buf));\n        wsabuf[0].len = maxsize ? maxsize : BLEN(&sock->reads.buf);\n\n        /* the overlapped read will signal this event on I/O completion */\n        ASSERT(ResetEvent(sock->reads.overlapped.hEvent));\n        sock->reads.flags = 0;\n\n        if (socket_is_dco_win(sock))\n        {\n            status = ReadFile((HANDLE)sock->sd, wsabuf[0].buf, wsabuf[0].len, &sock->reads.size,\n                              &sock->reads.overlapped);\n            /* Readfile status is inverted from WSARecv */\n            status = !status;\n        }\n        else if (proto_is_udp(sock->info.proto))\n        {\n            sock->reads.addr_defined = true;\n            sock->reads.addrlen = sizeof(sock->reads.addr6);\n            status = WSARecvFrom(sock->sd, wsabuf, 1, &sock->reads.size, &sock->reads.flags,\n                                 (struct sockaddr *)&sock->reads.addr, &sock->reads.addrlen,\n                                 &sock->reads.overlapped, NULL);\n        }\n        else if (proto_is_tcp(sock->info.proto))\n        {\n            sock->reads.addr_defined = false;\n            status = WSARecv(sock->sd, wsabuf, 1, &sock->reads.size, &sock->reads.flags,\n                             &sock->reads.overlapped, NULL);\n        }\n        else\n        {\n            status = 0;\n            ASSERT(0);\n        }\n\n        if (!status) /* operation completed immediately? */\n        {\n            /* FIXME: won't do anything when sock->info.af == AF_UNSPEC */\n            int af_len = af_addr_size(sock->info.af);\n            if (sock->reads.addr_defined && af_len && sock->reads.addrlen != af_len)\n            {\n                bad_address_length(sock->reads.addrlen, af_len);\n            }\n            sock->reads.iostate = IOSTATE_IMMEDIATE_RETURN;\n\n            /* since we got an immediate return, we must signal the event object ourselves */\n            ASSERT(SetEvent(sock->reads.overlapped.hEvent));\n            sock->reads.status = 0;\n\n            dmsg(D_WIN32_IO, \"WIN32 I/O: Socket Receive immediate return [%d,%d]\",\n                 (int)wsabuf[0].len, (int)sock->reads.size);\n        }\n        else\n        {\n            status = socket_get_last_error(sock);\n            if (status == WSA_IO_PENDING) /* operation queued? */\n            {\n                sock->reads.iostate = IOSTATE_QUEUED;\n                sock->reads.status = status;\n                dmsg(D_WIN32_IO, \"WIN32 I/O: Socket Receive queued [%d]\", (int)wsabuf[0].len);\n            }\n            else /* error occurred */\n            {\n                struct gc_arena gc = gc_new();\n                ASSERT(SetEvent(sock->reads.overlapped.hEvent));\n                sock->reads.iostate = IOSTATE_IMMEDIATE_RETURN;\n                sock->reads.status = status;\n                dmsg(D_WIN32_IO, \"WIN32 I/O: Socket Receive error [%d]: %s\", (int)wsabuf[0].len,\n                     strerror_win32(status, &gc));\n                gc_free(&gc);\n            }\n        }\n    }\n    return sock->reads.iostate;\n}\n\nint\nsocket_send_queue(struct link_socket *sock, struct buffer *buf, const struct link_socket_actual *to)\n{\n    if (sock->writes.iostate == IOSTATE_INITIAL)\n    {\n        WSABUF wsabuf[1];\n        int status;\n\n        /* make a private copy of buf */\n        sock->writes.buf = sock->writes.buf_init;\n        sock->writes.buf.len = 0;\n        ASSERT(buf_copy(&sock->writes.buf, buf));\n\n        /* Win32 docs say it's okay to allocate the wsabuf on the stack */\n        wsabuf[0].buf = BSTR(&sock->writes.buf);\n        wsabuf[0].len = BLEN(&sock->writes.buf);\n\n        /* the overlapped write will signal this event on I/O completion */\n        ASSERT(ResetEvent(sock->writes.overlapped.hEvent));\n        sock->writes.flags = 0;\n\n        if (socket_is_dco_win(sock))\n        {\n            status = WriteFile((HANDLE)sock->sd, wsabuf[0].buf, wsabuf[0].len, &sock->writes.size,\n                               &sock->writes.overlapped);\n\n            /* WriteFile status is inverted from WSASendTo */\n            status = !status;\n        }\n        else if (proto_is_udp(sock->info.proto))\n        {\n            /* set destination address for UDP writes */\n            sock->writes.addr_defined = true;\n            if (to->dest.addr.sa.sa_family == AF_INET6)\n            {\n                sock->writes.addr6 = to->dest.addr.in6;\n                sock->writes.addrlen = sizeof(sock->writes.addr6);\n            }\n            else\n            {\n                sock->writes.addr = to->dest.addr.in4;\n                sock->writes.addrlen = sizeof(sock->writes.addr);\n            }\n\n            status = WSASendTo(sock->sd, wsabuf, 1, &sock->writes.size, sock->writes.flags,\n                               (struct sockaddr *)&sock->writes.addr, sock->writes.addrlen,\n                               &sock->writes.overlapped, NULL);\n        }\n        else if (proto_is_tcp(sock->info.proto))\n        {\n            /* destination address for TCP writes was established on connection initiation */\n            sock->writes.addr_defined = false;\n\n            status = WSASend(sock->sd, wsabuf, 1, &sock->writes.size, sock->writes.flags,\n                             &sock->writes.overlapped, NULL);\n        }\n        else\n        {\n            status = 0;\n            ASSERT(0);\n        }\n\n        if (!status) /* operation completed immediately? */\n        {\n            sock->writes.iostate = IOSTATE_IMMEDIATE_RETURN;\n\n            /* since we got an immediate return, we must signal the event object ourselves */\n            ASSERT(SetEvent(sock->writes.overlapped.hEvent));\n\n            sock->writes.status = 0;\n\n            dmsg(D_WIN32_IO, \"WIN32 I/O: Socket Send immediate return [%d,%d]\", (int)wsabuf[0].len,\n                 (int)sock->writes.size);\n        }\n        else\n        {\n            status = socket_get_last_error(sock);\n            /* both status code have the identical value */\n            if (status == WSA_IO_PENDING || status == ERROR_IO_PENDING) /* operation queued? */\n            {\n                sock->writes.iostate = IOSTATE_QUEUED;\n                sock->writes.status = status;\n                dmsg(D_WIN32_IO, \"WIN32 I/O: Socket Send queued [%d]\", (int)wsabuf[0].len);\n            }\n            else /* error occurred */\n            {\n                struct gc_arena gc = gc_new();\n                ASSERT(SetEvent(sock->writes.overlapped.hEvent));\n                sock->writes.iostate = IOSTATE_IMMEDIATE_RETURN;\n                sock->writes.status = status;\n\n                dmsg(D_WIN32_IO, \"WIN32 I/O: Socket Send error [%d]: %s\", (int)wsabuf[0].len,\n                     strerror_win32(status, &gc));\n\n                gc_free(&gc);\n            }\n        }\n    }\n    return sock->writes.iostate;\n}\n\nvoid\nread_sockaddr_from_overlapped(struct overlapped_io *io, struct sockaddr *dst, int overlapped_ret)\n{\n    if (overlapped_ret >= 0 && io->addr_defined)\n    {\n        /* TODO(jjo): streamline this mess */\n        /* in this func we don't have relevant info about the PF_ of this\n         * endpoint, as link_socket_actual will be zero for the 1st received packet\n         *\n         * Test for inets PF_ possible sizes\n         */\n        switch (io->addrlen)\n        {\n            case sizeof(struct sockaddr_in):\n            case sizeof(struct sockaddr_in6):\n            /* TODO(jjo): for some reason (?) I'm getting 24,28 for AF_INET6\n             * under _WIN32*/\n            case sizeof(struct sockaddr_in6) - 4:\n                break;\n\n            default:\n                bad_address_length(io->addrlen, af_addr_size(io->addr.sin_family));\n        }\n\n        switch (io->addr.sin_family)\n        {\n            case AF_INET:\n                memcpy(dst, &io->addr, sizeof(struct sockaddr_in));\n                break;\n\n            case AF_INET6:\n                memcpy(dst, &io->addr6, sizeof(struct sockaddr_in6));\n                break;\n        }\n    }\n    else\n    {\n        CLEAR(*dst);\n    }\n}\n\n/**\n * @brief Extracts a sockaddr from a packet payload.\n *\n * Reads a sockaddr structure from the start of the packet buffer and writes it to `dst`.\n *\n * @param[in] buf Packet buffer containing the payload.\n * @param[out] dst Destination buffer for the extracted sockaddr.\n * @return Length of the extracted sockaddr\n */\nstatic int\nread_sockaddr_from_packet(struct buffer *buf, struct sockaddr *dst)\n{\n    int sa_len = 0;\n\n    const struct sockaddr *sa = (const struct sockaddr *)BPTR(buf);\n    switch (sa->sa_family)\n    {\n        case AF_INET:\n            sa_len = sizeof(struct sockaddr_in);\n            if (buf_len(buf) < sa_len)\n            {\n                msg(M_FATAL,\n                    \"ERROR: received incoming packet with too short length of %d -- must be at least %d.\",\n                    buf_len(buf), sa_len);\n            }\n            memcpy(dst, sa, sa_len);\n            buf_advance(buf, sa_len);\n            break;\n\n        case AF_INET6:\n            sa_len = sizeof(struct sockaddr_in6);\n            if (buf_len(buf) < sa_len)\n            {\n                msg(M_FATAL,\n                    \"ERROR: received incoming packet with too short length of %d -- must be at least %d.\",\n                    buf_len(buf), sa_len);\n            }\n            memcpy(dst, sa, sa_len);\n            buf_advance(buf, sa_len);\n            break;\n\n        default:\n            msg(M_FATAL, \"ERROR: received incoming packet with invalid address family %d.\",\n                sa->sa_family);\n    }\n\n    return sa_len;\n}\n\n/* Returns the number of bytes successfully read */\nint\nsockethandle_finalize(sockethandle_t sh, struct overlapped_io *io, struct buffer *buf,\n                      struct link_socket_actual *from)\n{\n    int ret = -1;\n    BOOL status;\n\n    switch (io->iostate)\n    {\n        case IOSTATE_QUEUED:\n            status = SocketHandleGetOverlappedResult(sh, io);\n            if (status)\n            {\n                /* successful return for a queued operation */\n                if (buf)\n                {\n                    *buf = io->buf;\n                }\n                ret = io->size;\n                io->iostate = IOSTATE_INITIAL;\n                ASSERT(ResetEvent(io->overlapped.hEvent));\n\n                dmsg(D_WIN32_IO, \"WIN32 I/O: Completion success [%d]\", ret);\n            }\n            else\n            {\n                /* error during a queued operation */\n                ret = -1;\n                if (SocketHandleGetLastError(sh) != ERROR_IO_INCOMPLETE)\n                {\n                    /* if no error (i.e. just not finished yet), then DON'T execute this code */\n                    io->iostate = IOSTATE_INITIAL;\n                    ASSERT(ResetEvent(io->overlapped.hEvent));\n                    msg(D_WIN32_IO | M_ERRNO, \"WIN32 I/O: Completion error\");\n                }\n            }\n            break;\n\n        case IOSTATE_IMMEDIATE_RETURN:\n            io->iostate = IOSTATE_INITIAL;\n            ASSERT(ResetEvent(io->overlapped.hEvent));\n            if (io->status)\n            {\n                /* error return for a non-queued operation */\n                SocketHandleSetLastError(sh, io->status);\n                ret = -1;\n                msg(D_WIN32_IO | M_ERRNO, \"WIN32 I/O: Completion non-queued error\");\n            }\n            else\n            {\n                /* successful return for a non-queued operation */\n                if (buf)\n                {\n                    *buf = io->buf;\n                }\n                ret = io->size;\n                dmsg(D_WIN32_IO, \"WIN32 I/O: Completion non-queued success [%d]\", ret);\n            }\n            break;\n\n        case IOSTATE_INITIAL: /* were we called without proper queueing? */\n            SocketHandleSetInvalError(sh);\n            ret = -1;\n            dmsg(D_WIN32_IO, \"WIN32 I/O: Completion BAD STATE\");\n            break;\n\n        default:\n            ASSERT(0);\n    }\n\n    if (from && ret > 0 && sh.is_handle && sh.prepend_sa)\n    {\n        ret -= read_sockaddr_from_packet(buf, &from->dest.addr.sa);\n    }\n\n    if (!sh.is_handle && from)\n    {\n        read_sockaddr_from_overlapped(io, &from->dest.addr.sa, ret);\n    }\n\n    if (buf)\n    {\n        buf->len = ret;\n    }\n    return ret;\n}\n\n#endif /* _WIN32 */\n\n/*\n * Socket event notification\n */\n\nunsigned int\nsocket_set(struct link_socket *s, struct event_set *es, unsigned int rwflags, void *arg,\n           unsigned int *persistent)\n{\n    if (s)\n    {\n        if ((rwflags & EVENT_READ) && !stream_buf_read_setup(s))\n        {\n            ASSERT(!persistent);\n            rwflags &= ~EVENT_READ;\n        }\n\n#ifdef _WIN32\n        if (rwflags & EVENT_READ)\n        {\n            socket_recv_queue(s, 0);\n        }\n#endif\n\n        /* if persistent is defined, call event_ctl only if rwflags has changed since last call */\n        if (!persistent || *persistent != rwflags)\n        {\n            event_ctl(es, socket_event_handle(s), rwflags, arg);\n            if (persistent)\n            {\n                *persistent = rwflags;\n            }\n        }\n\n        s->rwflags_debug = rwflags;\n    }\n    return rwflags;\n}\n\nvoid\nsd_close(socket_descriptor_t *sd)\n{\n    if (sd && socket_defined(*sd))\n    {\n        openvpn_close_socket(*sd);\n        *sd = SOCKET_UNDEFINED;\n    }\n}\n\n#if UNIX_SOCK_SUPPORT\n\n/*\n * code for unix domain sockets\n */\n\nconst char *\nsockaddr_unix_name(const struct sockaddr_un *local, const char *null)\n{\n    if (local && local->sun_family == PF_UNIX)\n    {\n        return local->sun_path;\n    }\n    else\n    {\n        return null;\n    }\n}\n\nsocket_descriptor_t\ncreate_socket_unix(void)\n{\n    socket_descriptor_t sd;\n\n    if ((sd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)\n    {\n        msg(M_ERR, \"Cannot create unix domain socket\");\n    }\n\n    /* set socket file descriptor to not pass across execs, so that\n     * scripts don't have access to it */\n    set_cloexec(sd);\n\n    return sd;\n}\n\nvoid\nsocket_bind_unix(socket_descriptor_t sd, struct sockaddr_un *local, const char *prefix)\n{\n    struct gc_arena gc = gc_new();\n    const mode_t orig_umask = umask(0);\n\n    if (bind(sd, (struct sockaddr *)local, sizeof(struct sockaddr_un)))\n    {\n        msg(M_FATAL | M_ERRNO, \"%s: Socket bind[%d] failed on unix domain socket %s\", prefix,\n            (int)sd, sockaddr_unix_name(local, \"NULL\"));\n    }\n\n    umask(orig_umask);\n    gc_free(&gc);\n}\n\nsocket_descriptor_t\nsocket_accept_unix(socket_descriptor_t sd, struct sockaddr_un *remote)\n{\n    socklen_t remote_len = sizeof(struct sockaddr_un);\n    socket_descriptor_t ret;\n\n    CLEAR(*remote);\n    ret = accept(sd, (struct sockaddr *)remote, &remote_len);\n    if (ret >= 0)\n    {\n        /* set socket file descriptor to not pass across execs, so that\n         * scripts don't have access to it */\n        set_cloexec(ret);\n    }\n    return ret;\n}\n\nint\nsocket_connect_unix(socket_descriptor_t sd, struct sockaddr_un *remote)\n{\n    int status = connect(sd, (struct sockaddr *)remote, sizeof(struct sockaddr_un));\n    if (status)\n    {\n        status = openvpn_errno();\n    }\n    return status;\n}\n\nvoid\nsockaddr_unix_init(struct sockaddr_un *local, const char *path)\n{\n    local->sun_family = PF_UNIX;\n    strncpynt(local->sun_path, path, sizeof(local->sun_path));\n}\n\nvoid\nsocket_delete_unix(const struct sockaddr_un *local)\n{\n    const char *name = sockaddr_unix_name(local, NULL);\n    if (name && strlen(name))\n    {\n        unlink(name);\n    }\n}\n\nbool\nunix_socket_get_peer_uid_gid(const socket_descriptor_t sd, uid_t *uid, gid_t *gid)\n{\n#ifdef HAVE_GETPEEREID\n    uid_t u;\n    gid_t g;\n    if (getpeereid(sd, &u, &g) == -1)\n    {\n        return false;\n    }\n    if (uid)\n    {\n        *uid = u;\n    }\n    if (gid)\n    {\n        *gid = g;\n    }\n    return true;\n#elif defined(SO_PEERCRED)\n    struct ucred peercred;\n    socklen_t so_len = sizeof(peercred);\n    if (getsockopt(sd, SOL_SOCKET, SO_PEERCRED, &peercred, &so_len) == -1)\n    {\n        return false;\n    }\n    if (uid)\n    {\n        *uid = peercred.uid;\n    }\n    if (gid)\n    {\n        *gid = peercred.gid;\n    }\n    return true;\n#else  /* ifdef HAVE_GETPEEREID */\n    return false;\n#endif /* ifdef HAVE_GETPEEREID */\n}\n\n#endif /* if UNIX_SOCK_SUPPORT */\n"
  },
  {
    "path": "src/openvpn/socket.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef SOCKET_H\n#define SOCKET_H\n\n#include \"buffer.h\"\n#include \"common.h\"\n#include \"error.h\"\n#include \"proto.h\"\n#include \"mtu.h\"\n#include \"win32.h\"\n#include \"event.h\"\n#include \"proxy.h\"\n#include \"socks.h\"\n#include \"misc.h\"\n#include \"tun.h\"\n#include \"socket_util.h\"\n\n/*\n * OpenVPN's default port number as assigned by IANA.\n */\n#define OPENVPN_PORT \"1194\"\n\n/*\n * Number of seconds that \"resolv-retry infinite\"\n * represents.\n */\n#define RESOLV_RETRY_INFINITE 1000000000\n\n/*\n * packet_size_type is used to communicate packet size\n * over the wire when stream oriented protocols are\n * being used\n */\n\ntypedef uint16_t packet_size_type;\n\n/* convert a packet_size_type from host to network order */\n#define htonps(x) htons(x)\n\n/* convert a packet_size_type from network to host order */\n#define ntohps(x) ntohs(x)\n\n/* struct to hold preresolved host names */\nstruct cached_dns_entry\n{\n    const char *hostname;\n    const char *servname;\n    int ai_family;\n    unsigned int flags;\n    struct addrinfo *ai;\n    struct cached_dns_entry *next;\n};\n\n/* IP addresses which are persistent across SIGUSR1s */\nstruct link_socket_addr\n{\n    struct addrinfo *bind_local;\n    struct addrinfo *remote_list;     /* complete remote list */\n    struct addrinfo *current_remote;  /* remote used in the\n                                       * current connection attempt */\n    struct link_socket_actual actual; /* reply to this address */\n};\n\nstruct link_socket_info\n{\n    struct link_socket_addr *lsa;\n    bool connection_established;\n    const char *ipchange_command;\n    const struct plugin_list *plugins;\n    bool remote_float;\n    uint8_t proto;   /* Protocol (PROTO_x defined below) */\n    sa_family_t af;  /* Address family like AF_INET, AF_INET6 or AF_UNSPEC*/\n    bool bind_ipv6_only;\n    int mtu_changed; /* Set to true when mtu value is changed */\n};\n\n/**\n * struct used to extract packets encapsulated in streams into a buffer,\n * in this case OpenVPN packets (data or control) embedded in a TCP stream.\n *\n * This struct is used to packetise the TCP stream into the\n * OpenVPN packet. Each OpenVPN packet has a two-byte header determining\n * the length of the packet.\n */\nstruct stream_buf\n{\n    /* Buffer to hold the initial buffer that will be used to reset buf */\n    struct buffer buf_init;\n\n    /** buffer holding the excess bytes that are not part of the\n     *  packet. */\n    struct buffer residual;\n\n    /** Maximum length of a packet that we accept */\n    int maxlen;\n\n    /** The buffer in buf contains a full packet without a header. Any\n     * extra data is in residual */\n    bool residual_fully_formed;\n\n    /** Holds the data of the current packet. This might be a partial packet */\n    struct buffer buf;\n\n    /** -1 if not yet known. Otherwise holds the length of the\n     *   packet. If >= 0, buf is already moved past the initial\n     *  size header */\n    int len;\n\n    /** if true, a fatal TCP error has occurred,\n     *  requiring that connection be restarted */\n    bool error;\n#if PORT_SHARE\n#define PS_DISABLED 0\n#define PS_ENABLED  1\n#define PS_FOREIGN  2\n    int port_share_state;\n#endif\n};\n\n/*\n * Used to set socket buffer sizes\n */\nstruct socket_buffer_size\n{\n    int rcvbuf;\n    int sndbuf;\n};\n\n/**\n * Sets the receive and send buffer sizes of a socket descriptor.\n *\n * @param fd            The socket to modify\n * @param sbs           new sizes.\n * @param reduce_size   apply the new size even if smaller than current one\n */\nvoid socket_set_buffers(socket_descriptor_t fd, const struct socket_buffer_size *sbs,\n                        bool reduce_size);\n\n/*\n * This is the main socket structure used by OpenVPN.  The SOCKET_\n * defines try to abstract away our implementation differences between\n * using sockets on Posix vs. Win32.\n */\nstruct link_socket\n{\n    struct link_socket_info info;\n\n    struct event_arg ev_arg; /**< this struct will store a pointer to either mi or\n                              * link_socket, depending on the event type, to keep\n                              * it accessible it's placed within the same struct\n                              * it points to. */\n\n    socket_descriptor_t sd;\n    socket_descriptor_t ctrl_sd; /* only used for UDP over Socks */\n\n#ifdef _WIN32\n    struct overlapped_io reads;\n    struct overlapped_io writes;\n    struct rw_handle rw_handle;\n    struct rw_handle listen_handle; /* For listening on TCP socket in server mode */\n#endif\n\n    /* used for printing status info only */\n    unsigned int rwflags_debug;\n\n    /* used for long-term queueing of pre-accepted socket listen */\n    bool listen_persistent_queued;\n\n    const char *remote_host;\n    const char *remote_port;\n    const char *local_host;\n    const char *local_port;\n    struct cached_dns_entry *dns_cache;\n    bool bind_local;\n\n#define LS_MODE_DEFAULT         0\n#define LS_MODE_TCP_LISTEN      1\n#define LS_MODE_TCP_ACCEPT_FROM 2\n    int mode;\n\n    int resolve_retry_seconds;\n    int mtu_discover_type;\n\n    struct socket_buffer_size socket_buffer_sizes;\n\n    int mtu; /* OS discovered MTU, or 0 if unknown */\n\n#define SF_USE_IP_PKTINFO    (1 << 0)\n#define SF_TCP_NODELAY       (1 << 1)\n#define SF_PORT_SHARE        (1 << 2)\n#define SF_HOST_RANDOMIZE    (1 << 3)\n#define SF_GETADDRINFO_DGRAM (1 << 4)\n#define SF_DCO_WIN           (1 << 5)\n#define SF_PREPEND_SA        (1 << 6)\n#define SF_PKTINFO_COPY_IIF  (1 << 7)\n    unsigned int sockflags;\n    int mark;\n    const char *bind_dev;\n\n    /* for stream sockets */\n    struct stream_buf stream_buf;\n    struct buffer stream_buf_data;\n    bool stream_reset;\n\n    /* HTTP proxy */\n    struct http_proxy_info *http_proxy;\n\n    /* Socks proxy */\n    struct socks_proxy_info *socks_proxy;\n    struct link_socket_actual socks_relay; /* Socks UDP relay address */\n\n    /* The OpenVPN server we will use the proxy to connect to */\n    const char *proxy_dest_host;\n    const char *proxy_dest_port;\n\n    /* Pointer to the server-poll to trigger the timeout in function which have\n     * their own loop instead of using the main oop */\n    struct event_timeout *server_poll_timeout;\n\n#if PASSTOS_CAPABILITY\n    /* used to get/set TOS. */\n#if defined(TARGET_LINUX)\n    uint8_t ptos;\n#else /* all the BSDs, Solaris, MacOS use plain \"int\" -> see \"man ip\" there */\n    int ptos;\n#endif\n    bool ptos_defined;\n#endif\n\n#ifdef ENABLE_DEBUG\n    int gremlin; /* --gremlin bits */\n#endif\n};\n\n/*\n * Some Posix/Win32 differences.\n */\n\n#ifndef MSG_NOSIGNAL\n#define MSG_NOSIGNAL 0\n#endif\n\n#ifdef _WIN32\n\n#define openvpn_close_socket(s) closesocket(s)\n\nint socket_recv_queue(struct link_socket *sock, int maxsize);\n\nint socket_send_queue(struct link_socket *sock, struct buffer *buf,\n                      const struct link_socket_actual *to);\n\ntypedef struct\n{\n    union\n    {\n        SOCKET s;\n        HANDLE h;\n    };\n    bool is_handle;\n    bool prepend_sa; /* are incoming packets prepended with sockaddr? */\n} sockethandle_t;\n\nint sockethandle_finalize(sockethandle_t sh, struct overlapped_io *io, struct buffer *buf,\n                          struct link_socket_actual *from);\n\nstatic inline BOOL\nSocketHandleGetOverlappedResult(sockethandle_t sh, struct overlapped_io *io)\n{\n    return sh.is_handle\n               ? GetOverlappedResult(sh.h, &io->overlapped, &io->size, FALSE)\n               : WSAGetOverlappedResult(sh.s, &io->overlapped, &io->size, FALSE, &io->flags);\n}\n\nstatic inline int\nSocketHandleGetLastError(sockethandle_t sh)\n{\n    return sh.is_handle ? (int)GetLastError() : WSAGetLastError();\n}\n\ninline static void\nSocketHandleSetLastError(sockethandle_t sh, DWORD err)\n{\n    sh.is_handle ? SetLastError(err) : WSASetLastError(err);\n}\n\nstatic inline void\nSocketHandleSetInvalError(sockethandle_t sh)\n{\n    sh.is_handle ? SetLastError(ERROR_INVALID_FUNCTION) : WSASetLastError(WSAEINVAL);\n}\n\n/* winsock(2).h uses slightly different types so to avoid conversion\n   errors we wrap these functions on Windows */\n\nstatic inline int\nopenvpn_select(socket_descriptor_t nfds, fd_set *readfds, fd_set *writefds,\n               fd_set *exceptfds, struct timeval *timeout)\n{\n    (void)nfds; /* first argument ignored on Windows */\n    return select(0, readfds, writefds, exceptfds, timeout);\n}\n\nstatic inline ssize_t\nopenvpn_send(socket_descriptor_t sockfd, const void *buf, size_t len, int flags)\n{\n    ASSERT(len <= INT_MAX);\n    return send(sockfd, buf, (int)len, flags);\n}\n\nstatic inline int\nopenvpn_bind(socket_descriptor_t sockfd, const struct sockaddr *addr, size_t addrlen)\n{\n    ASSERT(addrlen <= INT_MAX);\n    return bind(sockfd, addr, (int)addrlen);\n}\n\n#else /* ifdef _WIN32 */\n\n#define openvpn_close_socket(s) close(s)\n#define openvpn_select(nfds, readfds, writefds, exceptfds, timeout) \\\n    select(nfds, readfds, writefds, exceptfds, timeout)\n#define openvpn_send(sockfd, buf, len, flags) send(sockfd, buf, len, flags)\n#define openvpn_bind(sockfd, addr, addrlen)   bind(sockfd, addr, addrlen)\n\n#endif /* ifdef _WIN32 */\n\nstruct link_socket *link_socket_new(void);\n\nvoid socket_bind(socket_descriptor_t sd, struct addrinfo *local, int af_family, const char *prefix,\n                 bool ipv6only);\n\nint openvpn_connect(socket_descriptor_t sd, const struct sockaddr *remote, int connect_timeout,\n                    volatile int *signal_received);\n\n\n/*\n * Initialize link_socket object.\n */\nvoid link_socket_init_phase1(struct context *c, int sock_index, int mode);\n\nvoid link_socket_init_phase2(struct context *c, struct link_socket *sock);\n\nvoid do_preresolve(struct context *c);\n\nvoid link_socket_close(struct link_socket *sock);\n\nvoid sd_close(socket_descriptor_t *sd);\n\nvoid bad_address_length(int actual, int expected);\n\n/* IPV4_INVALID_ADDR: returned by link_socket_current_remote()\n * to ease redirect-gateway logic for ipv4 tunnels on ipv6 endpoints\n */\n#define IPV4_INVALID_ADDR 0xffffffff\nin_addr_t link_socket_current_remote(const struct link_socket_info *info);\n\nconst struct in6_addr *link_socket_current_remote_ipv6(const struct link_socket_info *info);\n\nvoid link_socket_connection_initiated(struct link_socket_info *info,\n                                      const struct link_socket_actual *addr,\n                                      const char *common_name, struct env_set *es);\n\nvoid link_socket_bad_incoming_addr(struct buffer *buf, const struct link_socket_info *info,\n                                   const struct link_socket_actual *from_addr);\n\nvoid set_actual_address(struct link_socket_actual *actual, struct addrinfo *ai);\n\nvoid link_socket_bad_outgoing_addr(void);\n\nvoid setenv_trusted(struct env_set *es, const struct link_socket_info *info);\n\nbool link_socket_update_flags(struct link_socket *sock, unsigned int sockflags);\n\nvoid link_socket_update_buffer_sizes(struct link_socket *sock, int rcvbuf, int sndbuf);\n\n/*\n * Low-level functions\n */\n\nsocket_descriptor_t create_socket_tcp(struct addrinfo *);\n\nsocket_descriptor_t socket_do_accept(socket_descriptor_t sd, struct link_socket_actual *act,\n                                     const bool nowait);\n\n#if UNIX_SOCK_SUPPORT\n\nsocket_descriptor_t create_socket_unix(void);\n\nvoid socket_bind_unix(socket_descriptor_t sd, struct sockaddr_un *local, const char *prefix);\n\nsocket_descriptor_t socket_accept_unix(socket_descriptor_t sd, struct sockaddr_un *remote);\n\nint socket_connect_unix(socket_descriptor_t sd, struct sockaddr_un *remote);\n\nvoid sockaddr_unix_init(struct sockaddr_un *local, const char *path);\n\nconst char *sockaddr_unix_name(const struct sockaddr_un *local, const char *null);\n\nvoid socket_delete_unix(const struct sockaddr_un *local);\n\nbool unix_socket_get_peer_uid_gid(const socket_descriptor_t sd, uid_t *uid, gid_t *gid);\n\n#endif /* if UNIX_SOCK_SUPPORT */\n\nstatic inline bool\nlink_socket_connection_oriented(const struct link_socket *sock)\n{\n    if (sock)\n    {\n        return link_socket_proto_connection_oriented(sock->info.proto);\n    }\n    else\n    {\n        return false;\n    }\n}\n\n#if PORT_SHARE\n\nstatic inline bool\nsocket_foreign_protocol_detected(const struct link_socket *sock)\n{\n    return link_socket_connection_oriented(sock) && sock->stream_buf.port_share_state == PS_FOREIGN;\n}\n\nstatic inline const struct buffer *\nsocket_foreign_protocol_head(const struct link_socket *sock)\n{\n    return &sock->stream_buf.buf;\n}\n\nstatic inline int\nsocket_foreign_protocol_sd(const struct link_socket *sock)\n{\n    return sock->sd;\n}\n\n#endif /* if PORT_SHARE */\n\nstatic inline bool\nsocket_connection_reset(const struct link_socket *sock, int status)\n{\n    if (link_socket_connection_oriented(sock))\n    {\n        if (sock->stream_reset || sock->stream_buf.error)\n        {\n            return true;\n        }\n        else if (status < 0)\n        {\n            const int err = openvpn_errno();\n#ifdef _WIN32\n            return err == WSAECONNRESET || err == WSAECONNABORTED\n                   || err == ERROR_CONNECTION_ABORTED;\n#else\n            return err == ECONNRESET;\n#endif\n        }\n    }\n    return false;\n}\n\nstatic inline bool\nlink_socket_verify_incoming_addr(struct buffer *buf, const struct link_socket_info *info,\n                                 const struct link_socket_actual *from_addr)\n{\n    if (buf->len > 0)\n    {\n        switch (from_addr->dest.addr.sa.sa_family)\n        {\n            case AF_INET6:\n            case AF_INET:\n                if (!link_socket_actual_defined(from_addr))\n                {\n                    return false;\n                }\n                if (info->remote_float || (!info->lsa->remote_list))\n                {\n                    return true;\n                }\n                if (addrlist_match_proto(&from_addr->dest, info->lsa->remote_list, info->proto))\n                {\n                    return true;\n                }\n        }\n    }\n    return false;\n}\n\nstatic inline void\nlink_socket_get_outgoing_addr(struct buffer *buf, const struct link_socket_info *info,\n                              struct link_socket_actual **act)\n{\n    if (buf->len > 0)\n    {\n        struct link_socket_addr *lsa = info->lsa;\n        if (link_socket_actual_defined(&lsa->actual))\n        {\n            *act = &lsa->actual;\n        }\n        else\n        {\n            link_socket_bad_outgoing_addr();\n            buf->len = 0;\n            *act = NULL;\n        }\n    }\n}\n\nstatic inline void\nlink_socket_set_outgoing_addr(struct link_socket_info *info, const struct link_socket_actual *act,\n                              const char *common_name, struct env_set *es)\n{\n    struct link_socket_addr *lsa = info->lsa;\n    if (\n        /* new or changed address? */\n        (!info->connection_established\n         || !addr_match_proto(&act->dest, &lsa->actual.dest, info->proto))\n        &&\n        /* address undef or address == remote or --float */\n        (info->remote_float\n         || (!lsa->remote_list || addrlist_match_proto(&act->dest, lsa->remote_list, info->proto))))\n    {\n        link_socket_connection_initiated(info, act, common_name, es);\n    }\n}\n\n/**\n * Will try to check if the buffers in stream form a\n * full packet. Will return true if further reads are\n * required and false otherwise. (full packet is ready)\n *\n * With UDP we always return true as there is no reassembly.\n *\n * @param sb    the stream buffer that should be worked on\n * @return      true if more reads are required.\n */\nbool stream_buf_read_setup_dowork(struct stream_buf *sb);\n\nstatic inline bool\nstream_buf_read_setup(struct link_socket *sock)\n{\n    if (link_socket_connection_oriented(sock))\n    {\n        return stream_buf_read_setup_dowork(&sock->stream_buf);\n    }\n    else\n    {\n        return true;\n    }\n}\n\n/**\n * Returns true if we are on Windows and this link is running on DCO-WIN.\n * This helper is used to enable DCO-WIN specific logic that is not relevant\n * to other platforms.\n */\nstatic inline bool\nsocket_is_dco_win(const struct link_socket *s)\n{\n    return s->sockflags & SF_DCO_WIN;\n}\n\n/*\n * Socket Read Routines\n */\n\nint link_socket_read_tcp(struct link_socket *sock, struct buffer *buf);\n\n#ifdef _WIN32\n\nstatic inline int\nlink_socket_read_udp_win32(struct link_socket *sock, struct buffer *buf,\n                           struct link_socket_actual *from)\n{\n    sockethandle_t sh = { .s = sock->sd };\n    if (socket_is_dco_win(sock))\n    {\n        *from = sock->info.lsa->actual;\n        sh.is_handle = true;\n        sh.prepend_sa = sock->sockflags & SF_PREPEND_SA;\n    }\n    return sockethandle_finalize(sh, &sock->reads, buf, from);\n}\n\n#else  /* ifdef _WIN32 */\n\nint link_socket_read_udp_posix(struct link_socket *sock, struct buffer *buf,\n                               struct link_socket_actual *from);\n\n#endif /* ifdef _WIN32 */\n\n/* read a TCP or UDP packet from link */\nstatic inline int\nlink_socket_read(struct link_socket *sock, struct buffer *buf, struct link_socket_actual *from)\n{\n    if (proto_is_udp(sock->info.proto) || socket_is_dco_win(sock))\n    /* unified UDPv4 and UDPv6, for DCO-WIN the kernel\n     * will strip the length header */\n    {\n        int res;\n\n#ifdef _WIN32\n        res = link_socket_read_udp_win32(sock, buf, from);\n#else\n        res = link_socket_read_udp_posix(sock, buf, from);\n#endif\n        return res;\n    }\n    else if (proto_is_tcp(sock->info.proto)) /* unified TCPv4 and TCPv6 */\n    {\n        /* from address was returned by accept */\n        from->dest = sock->info.lsa->actual.dest;\n        return link_socket_read_tcp(sock, buf);\n    }\n    else\n    {\n        ASSERT(0);\n        return -1; /* NOTREACHED */\n    }\n}\n\n/*\n * Socket Write routines\n */\n\nssize_t link_socket_write_tcp(struct link_socket *sock, struct buffer *buf,\n                              struct link_socket_actual *to);\n\n#ifdef _WIN32\n\nstatic inline int\nlink_socket_write_win32(struct link_socket *sock, struct buffer *buf, struct link_socket_actual *to)\n{\n    int err = 0;\n    int status = 0;\n    sockethandle_t sh = { .s = sock->sd, .is_handle = socket_is_dco_win(sock) };\n    if (overlapped_io_active(&sock->writes))\n    {\n        status = sockethandle_finalize(sh, &sock->writes, NULL, NULL);\n        if (status < 0)\n        {\n            err = SocketHandleGetLastError(sh);\n        }\n    }\n\n    /* dco-win mp requires control packets to be prepended with sockaddr */\n    if (sock->sockflags & SF_PREPEND_SA)\n    {\n        if (to->dest.addr.sa.sa_family == AF_INET)\n        {\n            buf_write_prepend(buf, &to->dest.addr.in4, sizeof(struct sockaddr_in));\n        }\n        else\n        {\n            buf_write_prepend(buf, &to->dest.addr.in6, sizeof(struct sockaddr_in6));\n        }\n    }\n\n    socket_send_queue(sock, buf, to);\n    if (status < 0)\n    {\n        SocketHandleSetLastError(sh, err);\n        return status;\n    }\n    else\n    {\n        return BLEN(buf);\n    }\n}\n\n#else /* ifdef _WIN32 */\n\nssize_t link_socket_write_udp_posix_sendmsg(struct link_socket *sock, struct buffer *buf,\n                                            struct link_socket_actual *to);\n\n\nstatic inline ssize_t\nlink_socket_write_udp_posix(struct link_socket *sock, struct buffer *buf,\n                            struct link_socket_actual *to)\n{\n#if ENABLE_IP_PKTINFO\n    if (proto_is_udp(sock->info.proto) && (sock->sockflags & SF_USE_IP_PKTINFO)\n        && addr_defined_ipi(to))\n    {\n        return link_socket_write_udp_posix_sendmsg(sock, buf, to);\n    }\n    else\n#endif\n        return sendto(sock->sd, BPTR(buf), BLENZ(buf), 0, (struct sockaddr *)&to->dest.addr.sa,\n                      (socklen_t)af_addr_size(to->dest.addr.sa.sa_family));\n}\n\nstatic inline ssize_t\nlink_socket_write_tcp_posix(struct link_socket *sock, struct buffer *buf)\n{\n    return send(sock->sd, BPTR(buf), BLENZ(buf), MSG_NOSIGNAL);\n}\n\n#endif /* ifdef _WIN32 */\n\nstatic inline ssize_t\nlink_socket_write_udp(struct link_socket *sock, struct buffer *buf, struct link_socket_actual *to)\n{\n#ifdef _WIN32\n    return link_socket_write_win32(sock, buf, to);\n#else\n    return link_socket_write_udp_posix(sock, buf, to);\n#endif\n}\n\n/* write a TCP or UDP packet to link */\nstatic inline ssize_t\nlink_socket_write(struct link_socket *sock, struct buffer *buf, struct link_socket_actual *to)\n{\n    if (proto_is_udp(sock->info.proto) || socket_is_dco_win(sock))\n    {\n        /* unified UDPv4, UDPv6 and DCO-WIN (driver adds length header) */\n        return link_socket_write_udp(sock, buf, to);\n    }\n    else if (proto_is_tcp(sock->info.proto)) /* unified TCPv4 and TCPv6 */\n    {\n        return link_socket_write_tcp(sock, buf, to);\n    }\n    else\n    {\n        ASSERT(0);\n        return -1; /* NOTREACHED */\n    }\n}\n\n#if PASSTOS_CAPABILITY\n\n/*\n * Extract TOS bits.  Assumes that ipbuf is a valid IPv4 packet.\n */\nstatic inline void\nlink_socket_extract_tos(struct link_socket *sock, const struct buffer *ipbuf)\n{\n    if (sock && ipbuf)\n    {\n        struct openvpn_iphdr *iph = (struct openvpn_iphdr *)BPTR(ipbuf);\n        sock->ptos = iph->tos;\n        sock->ptos_defined = true;\n    }\n}\n\n/*\n * Set socket properties to reflect TOS bits which were extracted\n * from tunnel packet.\n */\nstatic inline void\nlink_socket_set_tos(struct link_socket *sock)\n{\n    if (sock && sock->ptos_defined)\n    {\n        setsockopt(sock->sd, IPPROTO_IP, IP_TOS, (const void *)&sock->ptos, sizeof(sock->ptos));\n    }\n}\n\n#endif /* if PASSTOS_CAPABILITY */\n\n/*\n * Socket I/O wait functions\n */\n\n/*\n * Extends the pre-existing read residual logic\n * to all initialized sockets, ensuring the complete\n * packet is read.\n */\nbool sockets_read_residual(const struct context *c);\n\nstatic inline event_t\nsocket_event_handle(const struct link_socket *sock)\n{\n#ifdef _WIN32\n    return &sock->rw_handle;\n#else\n    return sock->sd;\n#endif\n}\n\nevent_t socket_listen_event_handle(struct link_socket *sock);\n\nunsigned int socket_set(struct link_socket *sock, struct event_set *es, unsigned int rwflags,\n                        void *arg, unsigned int *persistent);\n\nstatic inline void\nsocket_set_listen_persistent(struct link_socket *sock, struct event_set *es, void *arg)\n{\n    if (sock && !sock->listen_persistent_queued)\n    {\n        event_ctl(es, socket_listen_event_handle(sock), EVENT_READ, arg);\n        sock->listen_persistent_queued = true;\n    }\n}\n\nstatic inline void\nsocket_reset_listen_persistent(struct link_socket *sock)\n{\n#ifdef _WIN32\n    reset_net_event_win32(&sock->listen_handle, sock->sd);\n#endif\n}\n\nconst char *socket_stat(const struct link_socket *sock, unsigned int rwflags, struct gc_arena *gc);\n\n#endif /* SOCKET_H */\n"
  },
  {
    "path": "src/openvpn/socket_util.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"socket_util.h\"\n#include \"crypto.h\"\n#include \"manage.h\"\n\n/*\n * Format IP addresses in ascii\n */\n\nconst char *\nprint_sockaddr_ex(const struct sockaddr *sa, const char *separator, const unsigned int flags,\n                  struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(128, gc);\n    bool addr_is_defined = false;\n    char hostaddr[NI_MAXHOST] = \"\";\n    char servname[NI_MAXSERV] = \"\";\n    int status;\n\n    socklen_t salen = 0;\n    switch (sa->sa_family)\n    {\n        case AF_INET:\n            if (!(flags & PS_DONT_SHOW_FAMILY))\n            {\n                buf_puts(&out, \"[AF_INET]\");\n            }\n            salen = sizeof(struct sockaddr_in);\n            addr_is_defined = ((struct sockaddr_in *)sa)->sin_addr.s_addr != 0;\n            break;\n\n        case AF_INET6:\n            if (!(flags & PS_DONT_SHOW_FAMILY))\n            {\n                buf_puts(&out, \"[AF_INET6]\");\n            }\n            salen = sizeof(struct sockaddr_in6);\n            addr_is_defined = !IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)sa)->sin6_addr);\n            break;\n\n        case AF_UNSPEC:\n            if (!(flags & PS_DONT_SHOW_FAMILY))\n            {\n                return \"[AF_UNSPEC]\";\n            }\n            else\n            {\n                return \"\";\n            }\n\n        default:\n            ASSERT(0);\n    }\n\n    status = getnameinfo(sa, salen, hostaddr, sizeof(hostaddr), servname, sizeof(servname),\n                         NI_NUMERICHOST | NI_NUMERICSERV);\n\n    if (status != 0)\n    {\n        buf_printf(&out, \"[nameinfo() err: %s]\", gai_strerror(status));\n        return BSTR(&out);\n    }\n\n    if (!(flags & PS_DONT_SHOW_ADDR))\n    {\n        if (addr_is_defined)\n        {\n            buf_puts(&out, hostaddr);\n        }\n        else\n        {\n            buf_puts(&out, \"[undef]\");\n        }\n    }\n\n    if ((flags & PS_SHOW_PORT) || (flags & PS_SHOW_PORT_IF_DEFINED))\n    {\n        if (separator)\n        {\n            buf_puts(&out, separator);\n        }\n\n        buf_puts(&out, servname);\n    }\n\n    return BSTR(&out);\n}\n\nconst char *\nprint_link_socket_actual(const struct link_socket_actual *act, struct gc_arena *gc)\n{\n    return print_link_socket_actual_ex(act, \":\", PS_SHOW_PORT | PS_SHOW_PKTINFO, gc);\n}\n\n#ifndef IF_NAMESIZE\n#define IF_NAMESIZE 16\n#endif\n\nconst char *\nprint_link_socket_actual_ex(const struct link_socket_actual *act, const char *separator,\n                            const unsigned int flags, struct gc_arena *gc)\n{\n    if (act)\n    {\n        struct buffer out = alloc_buf_gc(128, gc);\n        buf_printf(&out, \"%s\", print_sockaddr_ex(&act->dest.addr.sa, separator, flags, gc));\n#if ENABLE_IP_PKTINFO\n        char ifname[IF_NAMESIZE] = \"[undef]\";\n\n        if ((flags & PS_SHOW_PKTINFO) && addr_defined_ipi(act))\n        {\n            switch (act->dest.addr.sa.sa_family)\n            {\n                case AF_INET:\n                {\n                    struct openvpn_sockaddr sa;\n                    CLEAR(sa);\n                    sa.addr.in4.sin_family = AF_INET;\n#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)\n                    sa.addr.in4.sin_addr = act->pi.in4.ipi_spec_dst;\n                    if_indextoname(act->pi.in4.ipi_ifindex, ifname);\n#elif defined(IP_RECVDSTADDR)\n                    sa.addr.in4.sin_addr = act->pi.in4;\n                    ifname[0] = 0;\n#else /* if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) */\n#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix syshead.h)\n#endif\n                    buf_printf(&out, \" (via %s%%%s)\",\n                               print_sockaddr_ex(&sa.addr.sa, separator, 0, gc), ifname);\n                }\n                break;\n\n                case AF_INET6:\n                {\n                    struct sockaddr_in6 sin6;\n                    char buf[INET6_ADDRSTRLEN] = \"[undef]\";\n                    CLEAR(sin6);\n                    sin6.sin6_family = AF_INET6;\n                    sin6.sin6_addr = act->pi.in6.ipi6_addr;\n                    if_indextoname(act->pi.in6.ipi6_ifindex, ifname);\n                    if (getnameinfo((struct sockaddr *)&sin6, sizeof(struct sockaddr_in6), buf,\n                                    sizeof(buf), NULL, 0, NI_NUMERICHOST)\n                        == 0)\n                    {\n                        buf_printf(&out, \" (via %s%%%s)\", buf, ifname);\n                    }\n                    else\n                    {\n                        buf_printf(&out, \" (via [getnameinfo() err]%%%s)\", ifname);\n                    }\n                }\n                break;\n            }\n        }\n#endif /* if ENABLE_IP_PKTINFO */\n        return BSTR(&out);\n    }\n    else\n    {\n        return \"[NULL]\";\n    }\n}\n\n/*\n * Convert an in_addr_t in host byte order\n * to an ascii dotted quad.\n */\nconst char *\nprint_in_addr_t(in_addr_t addr, unsigned int flags, struct gc_arena *gc)\n{\n    struct in_addr ia;\n    char *out = gc_malloc(INET_ADDRSTRLEN, true, gc);\n\n    if (addr || !(flags & IA_EMPTY_IF_UNDEF))\n    {\n        CLEAR(ia);\n        ia.s_addr = (flags & IA_NET_ORDER) ? addr : htonl(addr);\n\n        inet_ntop(AF_INET, &ia, out, INET_ADDRSTRLEN);\n    }\n    return out;\n}\n\n/*\n * Convert an in6_addr in host byte order\n * to an ascii representation of an IPv6 address\n */\nconst char *\nprint_in6_addr(struct in6_addr a6, unsigned int flags, struct gc_arena *gc)\n{\n    char *out = gc_malloc(INET6_ADDRSTRLEN, true, gc);\n\n    if (memcmp(&a6, &in6addr_any, sizeof(a6)) != 0 || !(flags & IA_EMPTY_IF_UNDEF))\n    {\n        inet_ntop(AF_INET6, &a6, out, INET6_ADDRSTRLEN);\n    }\n    return out;\n}\n\n/*\n * Convert an in_port_t in host byte order to a string\n */\nconst char *\nprint_in_port_t(in_port_t port, struct gc_arena *gc)\n{\n    struct buffer buffer = alloc_buf_gc(8, gc);\n    buf_printf(&buffer, \"%hu\", port);\n    return BSTR(&buffer);\n}\n\n/* add some offset to an ipv6 address\n * (add in steps of 8 bits, taking overflow into next round)\n */\nstruct in6_addr\nadd_in6_addr(struct in6_addr base, uint32_t add)\n{\n    for (int i = 15; i >= 0 && add > 0; i--)\n    {\n        register uint32_t carry;\n        register uint32_t h;\n\n        h = base.s6_addr[i];\n        base.s6_addr[i] = (h + add) & UINT8_MAX;\n\n        /* using explicit carry for the 8-bit additions will catch\n         * 8-bit and(!) 32-bit overruns nicely\n         */\n        carry = ((h & 0xff) + (add & 0xff)) >> 8;\n        add = (add >> 8) + carry;\n    }\n    return base;\n}\n\n/* set environmental variables for ip/port in *addr */\nvoid\nsetenv_sockaddr(struct env_set *es, const char *name_prefix, const struct openvpn_sockaddr *addr,\n                const unsigned int flags)\n{\n    char name_buf[256];\n\n    char buf[INET6_ADDRSTRLEN];\n    switch (addr->addr.sa.sa_family)\n    {\n        case AF_INET:\n            if (flags & SA_IP_PORT)\n            {\n                snprintf(name_buf, sizeof(name_buf), \"%s_ip\", name_prefix);\n            }\n            else\n            {\n                snprintf(name_buf, sizeof(name_buf), \"%s\", name_prefix);\n            }\n\n            inet_ntop(AF_INET, &addr->addr.in4.sin_addr, buf, sizeof(buf));\n            setenv_str(es, name_buf, buf);\n\n            if ((flags & SA_IP_PORT) && addr->addr.in4.sin_port)\n            {\n                snprintf(name_buf, sizeof(name_buf), \"%s_port\", name_prefix);\n                setenv_int(es, name_buf, ntohs(addr->addr.in4.sin_port));\n            }\n            break;\n\n        case AF_INET6:\n            if (IN6_IS_ADDR_V4MAPPED(&addr->addr.in6.sin6_addr))\n            {\n                struct in_addr ia;\n                memcpy(&ia.s_addr, &addr->addr.in6.sin6_addr.s6_addr[12], sizeof(ia.s_addr));\n                snprintf(name_buf, sizeof(name_buf), \"%s_ip\", name_prefix);\n                inet_ntop(AF_INET, &ia, buf, sizeof(buf));\n            }\n            else\n            {\n                snprintf(name_buf, sizeof(name_buf), \"%s_ip6\", name_prefix);\n                inet_ntop(AF_INET6, &addr->addr.in6.sin6_addr, buf, sizeof(buf));\n            }\n            setenv_str(es, name_buf, buf);\n\n            if ((flags & SA_IP_PORT) && addr->addr.in6.sin6_port)\n            {\n                snprintf(name_buf, sizeof(name_buf), \"%s_port\", name_prefix);\n                setenv_int(es, name_buf, ntohs(addr->addr.in6.sin6_port));\n            }\n            break;\n    }\n}\n\nvoid\nsetenv_in_addr_t(struct env_set *es, const char *name_prefix, in_addr_t addr,\n                 const unsigned int flags)\n{\n    if (addr || !(flags & SA_SET_IF_NONZERO))\n    {\n        struct openvpn_sockaddr si;\n        CLEAR(si);\n        si.addr.in4.sin_family = AF_INET;\n        si.addr.in4.sin_addr.s_addr = htonl(addr);\n        setenv_sockaddr(es, name_prefix, &si, flags);\n    }\n}\n\nvoid\nsetenv_in6_addr(struct env_set *es, const char *name_prefix, const struct in6_addr *addr,\n                const unsigned int flags)\n{\n    if (!IN6_IS_ADDR_UNSPECIFIED(addr) || !(flags & SA_SET_IF_NONZERO))\n    {\n        struct openvpn_sockaddr si;\n        CLEAR(si);\n        si.addr.in6.sin6_family = AF_INET6;\n        si.addr.in6.sin6_addr = *addr;\n        setenv_sockaddr(es, name_prefix, &si, flags);\n    }\n}\n\nvoid\nsetenv_link_socket_actual(struct env_set *es, const char *name_prefix,\n                          const struct link_socket_actual *act, const unsigned int flags)\n{\n    setenv_sockaddr(es, name_prefix, &act->dest, flags);\n}\n\n/*\n * Convert protocol names between index and ascii form.\n */\n\nstruct proto_names\n{\n    const char *short_form;\n    const char *display_form;\n    sa_family_t proto_af;\n    int proto;\n};\n\n/* Indexed by PROTO_x */\nstatic const struct proto_names proto_names[] = {\n    { \"proto-uninitialized\", \"proto-NONE\", AF_UNSPEC, PROTO_NONE },\n    /* try IPv4 and IPv6 (client), bind dual-stack (server) */\n    { \"udp\", \"UDP\", AF_UNSPEC, PROTO_UDP },\n    { \"tcp-server\", \"TCP_SERVER\", AF_UNSPEC, PROTO_TCP_SERVER },\n    { \"tcp-client\", \"TCP_CLIENT\", AF_UNSPEC, PROTO_TCP_CLIENT },\n    { \"tcp\", \"TCP\", AF_UNSPEC, PROTO_TCP },\n    /* force IPv4 */\n    { \"udp4\", \"UDPv4\", AF_INET, PROTO_UDP },\n    { \"tcp4-server\", \"TCPv4_SERVER\", AF_INET, PROTO_TCP_SERVER },\n    { \"tcp4-client\", \"TCPv4_CLIENT\", AF_INET, PROTO_TCP_CLIENT },\n    { \"tcp4\", \"TCPv4\", AF_INET, PROTO_TCP },\n    /* force IPv6 */\n    { \"udp6\", \"UDPv6\", AF_INET6, PROTO_UDP },\n    { \"tcp6-server\", \"TCPv6_SERVER\", AF_INET6, PROTO_TCP_SERVER },\n    { \"tcp6-client\", \"TCPv6_CLIENT\", AF_INET6, PROTO_TCP_CLIENT },\n    { \"tcp6\", \"TCPv6\", AF_INET6, PROTO_TCP },\n};\n\nint\nascii2proto(const char *proto_name)\n{\n    for (size_t i = 0; i < SIZE(proto_names); ++i)\n    {\n        if (!strcmp(proto_name, proto_names[i].short_form))\n        {\n            return proto_names[i].proto;\n        }\n    }\n    return -1;\n}\n\nsa_family_t\nascii2af(const char *proto_name)\n{\n    for (size_t i = 0; i < SIZE(proto_names); ++i)\n    {\n        if (!strcmp(proto_name, proto_names[i].short_form))\n        {\n            return proto_names[i].proto_af;\n        }\n    }\n    return 0;\n}\n\nconst char *\nproto2ascii(int proto, sa_family_t af, bool display_form)\n{\n    for (size_t i = 0; i < SIZE(proto_names); ++i)\n    {\n        if (proto_names[i].proto_af == af && proto_names[i].proto == proto)\n        {\n            if (display_form)\n            {\n                return proto_names[i].display_form;\n            }\n            else\n            {\n                return proto_names[i].short_form;\n            }\n        }\n    }\n\n    return \"[unknown protocol]\";\n}\n\nconst char *\nproto2ascii_all(struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n\n    for (size_t i = 0; i < SIZE(proto_names); ++i)\n    {\n        if (i)\n        {\n            buf_printf(&out, \" \");\n        }\n        buf_printf(&out, \"[%s]\", proto_names[i].short_form);\n    }\n    return BSTR(&out);\n}\n\nconst char *\naddr_family_name(int af)\n{\n    switch (af)\n    {\n        case AF_INET:\n            return \"AF_INET\";\n\n        case AF_INET6:\n            return \"AF_INET6\";\n    }\n    return \"AF_UNSPEC\";\n}\n\n/*\n * Given a local proto, return local proto\n * if !remote, or compatible remote proto\n * if remote.\n *\n * This is used for options compatibility\n * checking.\n *\n * IPv6 and IPv4 protocols are comptabile but OpenVPN\n * has always sent UDPv4, TCPv4 over the wire. Keep these\n * strings for backward compatibility\n */\nconst char *\nproto_remote(int proto, bool remote)\n{\n    ASSERT(proto >= 0 && proto < PROTO_N);\n    if (proto == PROTO_UDP)\n    {\n        return \"UDPv4\";\n    }\n\n    if ((remote && proto == PROTO_TCP_CLIENT) || (!remote && proto == PROTO_TCP_SERVER))\n    {\n        return \"TCPv4_SERVER\";\n    }\n    if ((remote && proto == PROTO_TCP_SERVER) || (!remote && proto == PROTO_TCP_CLIENT))\n    {\n        return \"TCPv4_CLIENT\";\n    }\n\n    ASSERT(0);\n    return \"\"; /* Make the compiler happy */\n}\n\n/**\n * Small helper function for openvpn_getaddrinfo to print the address\n * family when resolving fails\n */\nstatic const char *\ngetaddrinfo_addr_family_name(int af)\n{\n    switch (af)\n    {\n        case AF_INET:\n            return \"[AF_INET]\";\n\n        case AF_INET6:\n            return \"[AF_INET6]\";\n    }\n    return \"\";\n}\n\n/*\n * Prepend a random string to hostname to prevent DNS caching.\n * For example, foo.bar.gov would be modified to <random-chars>.foo.bar.gov.\n * Of course, this requires explicit support in the DNS server (wildcard).\n */\nstatic const char *\nhostname_randomize(const char *hostname, struct gc_arena *gc)\n{\n#define n_rnd_bytes 6\n\n    uint8_t rnd_bytes[n_rnd_bytes];\n    const char *rnd_str;\n    struct buffer hname = alloc_buf_gc(strlen(hostname) + sizeof(rnd_bytes) * 2 + 4, gc);\n\n    prng_bytes(rnd_bytes, sizeof(rnd_bytes));\n    rnd_str = format_hex_ex(rnd_bytes, sizeof(rnd_bytes), 40, 0, NULL, gc);\n    buf_printf(&hname, \"%s.%s\", rnd_str, hostname);\n    return BSTR(&hname);\n#undef n_rnd_bytes\n}\n\n/*\n * Translate IPv4/IPv6 addr or hostname into struct addrinfo\n * If resolve error, try again for resolve_retry_seconds seconds.\n */\nint\nopenvpn_getaddrinfo(unsigned int flags, const char *hostname, const char *servname,\n                    int resolve_retry_seconds, struct signal_info *sig_info, int ai_family,\n                    struct addrinfo **res)\n{\n    struct addrinfo hints;\n    int status;\n    struct signal_info sigrec = { 0 };\n    msglvl_t msglevel = (flags & GETADDR_FATAL) ? M_FATAL : D_RESOLVE_ERRORS;\n    struct gc_arena gc = gc_new();\n    const char *print_hostname;\n    const char *print_servname;\n\n    ASSERT(res);\n\n    ASSERT(hostname || servname);\n    ASSERT(!(flags & GETADDR_HOST_ORDER));\n\n    if (servname)\n    {\n        print_servname = servname;\n    }\n    else\n    {\n        print_servname = \"\";\n    }\n\n    if (flags & GETADDR_MSG_VIRT_OUT)\n    {\n        msglevel |= M_MSG_VIRT_OUT;\n    }\n\n    if ((flags & (GETADDR_FATAL_ON_SIGNAL | GETADDR_WARN_ON_SIGNAL)) && !sig_info)\n    {\n        sig_info = &sigrec;\n    }\n\n    /* try numeric ip addr first */\n    CLEAR(hints);\n    hints.ai_flags = AI_NUMERICHOST;\n\n    if (flags & GETADDR_PASSIVE)\n    {\n        hints.ai_flags |= AI_PASSIVE;\n    }\n\n    if (flags & GETADDR_DATAGRAM)\n    {\n        hints.ai_socktype = SOCK_DGRAM;\n    }\n    else\n    {\n        hints.ai_socktype = SOCK_STREAM;\n    }\n\n    /* if hostname is not set, we want to bind to 'ANY', with\n     * the correct address family - v4-only or v6/v6-dual-stack */\n    if (!hostname)\n    {\n        hints.ai_family = ai_family;\n    }\n\n    status = getaddrinfo(hostname, servname, &hints, res);\n\n    if (status != 0)                      /* parse as numeric address failed? */\n    {\n        const int fail_wait_interval = 5; /* seconds */\n        /* Add +4 to cause integer division rounding up (1 + 4) = 5, (0+4)/5=0 */\n        int resolve_retries =\n            (flags & GETADDR_TRY_ONCE) ? 1 : ((resolve_retry_seconds + 4) / fail_wait_interval);\n        const char *fmt;\n        msglvl_t level = 0;\n\n        /* this is not a numeric IP, therefore force resolution using the\n         * provided ai_family */\n        hints.ai_family = ai_family;\n\n        if (hostname && (flags & GETADDR_RANDOMIZE))\n        {\n            hostname = hostname_randomize(hostname, &gc);\n        }\n\n        if (hostname)\n        {\n            print_hostname = hostname;\n        }\n        else\n        {\n            print_hostname = \"undefined\";\n        }\n\n        fmt = \"RESOLVE: Cannot resolve host address: %s:%s%s (%s)\";\n        if ((flags & GETADDR_MENTION_RESOLVE_RETRY) && !resolve_retry_seconds)\n        {\n            fmt = \"RESOLVE: Cannot resolve host address: %s:%s%s (%s)\"\n                  \"(I would have retried this name query if you had \"\n                  \"specified the --resolv-retry option.)\";\n        }\n\n        if (!(flags & GETADDR_RESOLVE) || status == EAI_FAIL)\n        {\n            msg(msglevel, \"RESOLVE: Cannot parse IP address: %s:%s (%s)\", print_hostname,\n                print_servname, gai_strerror(status));\n            goto done;\n        }\n\n#ifdef ENABLE_MANAGEMENT\n        if (flags & GETADDR_UPDATE_MANAGEMENT_STATE)\n        {\n            if (management)\n            {\n                management_set_state(management, OPENVPN_STATE_RESOLVE, NULL, NULL, NULL, NULL,\n                                     NULL);\n            }\n        }\n#endif\n\n        /*\n         * Resolve hostname\n         */\n        while (true)\n        {\n#ifndef _WIN32\n            /* force resolv.conf reload */\n            res_init();\n#endif\n            /* try hostname lookup */\n            hints.ai_flags &= ~AI_NUMERICHOST;\n            dmsg(D_SOCKET_DEBUG, \"GETADDRINFO flags=0x%04x ai_family=%d ai_socktype=%d\", flags,\n                 hints.ai_family, hints.ai_socktype);\n            status = getaddrinfo(hostname, servname, &hints, res);\n\n            if (sig_info)\n            {\n                get_signal(&sig_info->signal_received);\n                if (sig_info->signal_received) /* were we interrupted by a signal? */\n                {\n                    /* why are we overwriting SIGUSR1 ? */\n                    if (signal_reset(sig_info, SIGUSR1) == SIGUSR1) /* ignore SIGUSR1 */\n                    {\n                        msg(level, \"RESOLVE: Ignored SIGUSR1 signal received during \"\n                                   \"DNS resolution attempt\");\n                    }\n                    else\n                    {\n                        /* turn success into failure (interrupted syscall) */\n                        if (0 == status)\n                        {\n                            ASSERT(res);\n                            freeaddrinfo(*res);\n                            *res = NULL;\n                            status = EAI_AGAIN; /* = temporary failure */\n                            errno = EINTR;\n                        }\n                        goto done;\n                    }\n                }\n            }\n\n            /* success? */\n            if (0 == status)\n            {\n                break;\n            }\n\n            /* resolve lookup failed, should we\n             * continue or fail? */\n            level = msglevel;\n            if (resolve_retries > 0)\n            {\n                level = D_RESOLVE_ERRORS;\n            }\n\n            msg(level, fmt, print_hostname, print_servname, getaddrinfo_addr_family_name(ai_family),\n                gai_strerror(status));\n\n            if (--resolve_retries <= 0)\n            {\n                goto done;\n            }\n\n            management_sleep(fail_wait_interval);\n        }\n\n        ASSERT(res);\n\n        /* hostname resolve succeeded */\n\n        /*\n         * Do not choose an IP Addresse by random or change the order *\n         * of IP addresses, doing so will break RFC 3484 address selection *\n         */\n    }\n    else\n    {\n        /* IP address parse succeeded */\n        if (flags & GETADDR_RANDOMIZE)\n        {\n            msg(M_WARN, \"WARNING: ignoring --remote-random-hostname because the \"\n                        \"hostname is an IP address\");\n        }\n    }\n\ndone:\n    if (sig_info && sig_info->signal_received)\n    {\n        msglvl_t level = 0;\n        if (flags & GETADDR_FATAL_ON_SIGNAL)\n        {\n            level = M_FATAL;\n        }\n        else if (flags & GETADDR_WARN_ON_SIGNAL)\n        {\n            level = M_WARN;\n        }\n        msg(level, \"RESOLVE: signal received during DNS resolution attempt\");\n    }\n\n    gc_free(&gc);\n    return status;\n}\n\n/*\n * We do our own inet_aton because the glibc function\n * isn't very good about error checking.\n */\nint\nopenvpn_inet_aton(const char *dotted_quad, struct in_addr *addr)\n{\n    unsigned int a, b, c, d;\n\n    CLEAR(*addr);\n    if (sscanf(dotted_quad, \"%u.%u.%u.%u\", &a, &b, &c, &d) == 4)\n    {\n        if (a < 256 && b < 256 && c < 256 && d < 256)\n        {\n            addr->s_addr = htonl(a << 24 | b << 16 | c << 8 | d);\n            return OIA_IP; /* good dotted quad */\n        }\n    }\n    if (string_class(dotted_quad, CC_DIGIT | CC_DOT, 0))\n    {\n        return OIA_ERROR; /* probably a badly formatted dotted quad */\n    }\n    else\n    {\n        return OIA_HOSTNAME; /* probably a hostname */\n    }\n}\n\nbool\nip_addr_dotted_quad_safe(const char *dotted_quad)\n{\n    /* verify non-NULL */\n    if (!dotted_quad)\n    {\n        return false;\n    }\n\n    /* verify length is within limits */\n    if (strlen(dotted_quad) > 15)\n    {\n        return false;\n    }\n\n    /* verify that all chars are either numeric or '.' and that no numeric\n     * substring is greater than 3 chars */\n    {\n        int nnum = 0;\n        const char *p = dotted_quad;\n        int c;\n\n        while ((c = *p++))\n        {\n            if (c >= '0' && c <= '9')\n            {\n                ++nnum;\n                if (nnum > 3)\n                {\n                    return false;\n                }\n            }\n            else if (c == '.')\n            {\n                nnum = 0;\n            }\n            else\n            {\n                return false;\n            }\n        }\n    }\n\n    /* verify that string will convert to IP address */\n    {\n        struct in_addr a;\n        return openvpn_inet_aton(dotted_quad, &a) == OIA_IP;\n    }\n}\n\nbool\nipv6_addr_safe(const char *ipv6_text_addr)\n{\n    /* verify non-NULL */\n    if (!ipv6_text_addr)\n    {\n        return false;\n    }\n\n    /* verify length is within limits */\n    if (strlen(ipv6_text_addr) > INET6_ADDRSTRLEN)\n    {\n        return false;\n    }\n\n    /* verify that string will convert to IPv6 address */\n    {\n        struct in6_addr a6;\n        return inet_pton(AF_INET6, ipv6_text_addr, &a6) == 1;\n    }\n}\n\nstatic bool\ndns_addr_safe(const char *addr)\n{\n    if (addr)\n    {\n        const size_t len = strlen(addr);\n        return len > 0 && len <= 255 && string_class(addr, CC_ALNUM | CC_DASH | CC_DOT, 0);\n    }\n    else\n    {\n        return false;\n    }\n}\n\nbool\nip_or_dns_addr_safe(const char *addr, const bool allow_fqdn)\n{\n    if (ip_addr_dotted_quad_safe(addr))\n    {\n        return true;\n    }\n    else if (allow_fqdn)\n    {\n        return dns_addr_safe(addr);\n    }\n    else\n    {\n        return false;\n    }\n}\n\nbool\nmac_addr_safe(const char *mac_addr)\n{\n    /* verify non-NULL */\n    if (!mac_addr)\n    {\n        return false;\n    }\n\n    /* verify length is within limits */\n    if (strlen(mac_addr) > 17)\n    {\n        return false;\n    }\n\n    /* verify that all chars are either alphanumeric or ':' and that no\n     * alphanumeric substring is greater than 2 chars */\n    {\n        int nnum = 0;\n        const char *p = mac_addr;\n        int c;\n\n        while ((c = *p++))\n        {\n            if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))\n            {\n                ++nnum;\n                if (nnum > 2)\n                {\n                    return false;\n                }\n            }\n            else if (c == ':')\n            {\n                nnum = 0;\n            }\n            else\n            {\n                return false;\n            }\n        }\n    }\n\n    /* error-checking is left to script invoked in lladdr.c */\n    return true;\n}\n"
  },
  {
    "path": "src/openvpn/socket_util.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef SOCKET_UTIL_H\n#define SOCKET_UTIL_H\n\n#include \"buffer.h\"\n#include \"env_set.h\"\n#include \"sig.h\"\n\n#define PS_SHOW_PORT_IF_DEFINED (1 << 0)\n#define PS_SHOW_PORT            (1 << 1)\n#define PS_SHOW_PKTINFO         (1 << 2)\n#define PS_DONT_SHOW_ADDR       (1 << 3)\n#define PS_DONT_SHOW_FAMILY     (1 << 4)\n\n/* OpenVPN sockaddr struct */\nstruct openvpn_sockaddr\n{\n    /*int dummy;*/ /* add offset to force a bug if sa not explicitly dereferenced */\n    union\n    {\n        struct sockaddr sa;\n        struct sockaddr_in in4;\n        struct sockaddr_in6 in6;\n    } addr;\n};\n\n/* actual address of remote, based on source address of received packets */\nstruct link_socket_actual\n{\n    /*int dummy;*/ /* add offset to force a bug if dest not explicitly dereferenced */\n\n    struct openvpn_sockaddr dest;\n#if ENABLE_IP_PKTINFO\n    union\n    {\n#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)\n        struct in_pktinfo in4;\n#elif defined(IP_RECVDSTADDR)\n        struct in_addr in4;\n#endif\n        struct in6_pktinfo in6;\n    } pi;\n#endif\n};\n\nconst char *print_sockaddr_ex(const struct sockaddr *addr, const char *separator,\n                              const unsigned int flags, struct gc_arena *gc);\n\nstatic inline const char *\nprint_openvpn_sockaddr(const struct openvpn_sockaddr *addr, struct gc_arena *gc)\n{\n    return print_sockaddr_ex(&addr->addr.sa, \":\", PS_SHOW_PORT, gc);\n}\n\nstatic inline const char *\nprint_sockaddr(const struct sockaddr *addr, struct gc_arena *gc)\n{\n    return print_sockaddr_ex(addr, \":\", PS_SHOW_PORT, gc);\n}\n\n\nconst char *print_link_socket_actual_ex(const struct link_socket_actual *act, const char *separator,\n                                        const unsigned int flags, struct gc_arena *gc);\n\nconst char *print_link_socket_actual(const struct link_socket_actual *act, struct gc_arena *gc);\n\n\n#define IA_EMPTY_IF_UNDEF (1 << 0)\n#define IA_NET_ORDER      (1 << 1)\nconst char *print_in_addr_t(in_addr_t addr, unsigned int flags, struct gc_arena *gc);\n\nconst char *print_in6_addr(struct in6_addr addr6, unsigned int flags, struct gc_arena *gc);\n\nconst char *print_in_port_t(in_port_t port, struct gc_arena *gc);\n\nstruct in6_addr add_in6_addr(struct in6_addr base, uint32_t add);\n\n#define SA_IP_PORT        (1 << 0)\n#define SA_SET_IF_NONZERO (1 << 1)\nvoid setenv_sockaddr(struct env_set *es, const char *name_prefix,\n                     const struct openvpn_sockaddr *addr, const unsigned int flags);\n\nvoid setenv_in_addr_t(struct env_set *es, const char *name_prefix, in_addr_t addr,\n                      const unsigned int flags);\n\nvoid setenv_in6_addr(struct env_set *es, const char *name_prefix, const struct in6_addr *addr,\n                     const unsigned int flags);\n\nvoid setenv_link_socket_actual(struct env_set *es, const char *name_prefix,\n                               const struct link_socket_actual *act, const unsigned int flags);\n\n/*\n * DNS resolution\n */\n\n#define GETADDR_RESOLVE                 (1u << 0)\n#define GETADDR_FATAL                   (1u << 1)\n#define GETADDR_HOST_ORDER              (1u << 2)\n#define GETADDR_MENTION_RESOLVE_RETRY   (1u << 3)\n#define GETADDR_FATAL_ON_SIGNAL         (1u << 4)\n#define GETADDR_WARN_ON_SIGNAL          (1u << 5)\n#define GETADDR_MSG_VIRT_OUT            (1u << 6)\n#define GETADDR_TRY_ONCE                (1u << 7)\n#define GETADDR_UPDATE_MANAGEMENT_STATE (1u << 8)\n#define GETADDR_RANDOMIZE               (1u << 9)\n#define GETADDR_PASSIVE                 (1u << 10)\n#define GETADDR_DATAGRAM                (1u << 11)\n\n#define GETADDR_CACHE_MASK (GETADDR_DATAGRAM | GETADDR_PASSIVE)\n\n/**\n * Translate an IPv4 addr or hostname from string form to in_addr_t\n *\n * In case of resolve error, it will try again for\n * resolve_retry_seconds seconds.\n */\nin_addr_t getaddr(unsigned int flags, const char *hostname, int resolve_retry_seconds,\n                  bool *succeeded, struct signal_info *sig_info);\n\n/**\n * Translate an IPv6 addr or hostname from string form to in6_addr\n */\nbool get_ipv6_addr(const char *hostname, struct in6_addr *network, unsigned int *netbits,\n                   msglvl_t msglevel);\n\nint openvpn_getaddrinfo(unsigned int flags, const char *hostname, const char *servname,\n                        int resolve_retry_seconds, struct signal_info *sig_info, int ai_family,\n                        struct addrinfo **res);\n\n/* return values of openvpn_inet_aton */\n#define OIA_HOSTNAME 0\n#define OIA_IP       1\n#define OIA_ERROR    -1\nint openvpn_inet_aton(const char *dotted_quad, struct in_addr *addr);\n\n/* integrity validation on pulled options */\nbool ip_addr_dotted_quad_safe(const char *dotted_quad);\n\nbool ip_or_dns_addr_safe(const char *addr, const bool allow_fqdn);\n\nbool mac_addr_safe(const char *mac_addr);\n\nbool ipv6_addr_safe(const char *ipv6_text_addr);\n\n/*\n * Transport protocol naming and other details.\n */\n\n/*\n * Use enum's instead of #define to allow for easier\n * optional proto support\n */\nenum proto_num\n{\n    PROTO_NONE, /* catch for uninitialized */\n    PROTO_UDP,\n    PROTO_TCP,\n    PROTO_TCP_SERVER,\n    PROTO_TCP_CLIENT,\n    PROTO_N\n};\n\nstatic inline bool\nproto_is_net(int proto)\n{\n    ASSERT(proto >= 0 && proto < PROTO_N);\n    return proto != PROTO_NONE;\n}\n\n/**\n * @brief Returns if the protocol being used is UDP\n */\nstatic inline bool\nproto_is_udp(int proto)\n{\n    ASSERT(proto >= 0 && proto < PROTO_N);\n    return proto == PROTO_UDP;\n}\n\n/**\n * @brief Return if the protocol is datagram (UDP)\n *\n */\nstatic inline bool\nproto_is_dgram(int proto)\n{\n    return proto_is_udp(proto);\n}\n\n/**\n * @brief returns if the proto is a TCP variant (tcp-server, tcp-client or tcp)\n */\nstatic inline bool\nproto_is_tcp(int proto)\n{\n    ASSERT(proto >= 0 && proto < PROTO_N);\n    return proto == PROTO_TCP_CLIENT || proto == PROTO_TCP_SERVER;\n}\n\nint ascii2proto(const char *proto_name);\n\nsa_family_t ascii2af(const char *proto_name);\n\nconst char *proto2ascii(int proto, sa_family_t af, bool display_form);\n\nconst char *proto2ascii_all(struct gc_arena *gc);\n\nconst char *proto_remote(int proto, bool remote);\n\nconst char *addr_family_name(int af);\n\nstatic inline bool\naddr_defined(const struct openvpn_sockaddr *addr)\n{\n    if (!addr)\n    {\n        return 0;\n    }\n    switch (addr->addr.sa.sa_family)\n    {\n        case AF_INET:\n            return addr->addr.in4.sin_addr.s_addr != 0;\n\n        case AF_INET6:\n            return !IN6_IS_ADDR_UNSPECIFIED(&addr->addr.in6.sin6_addr);\n\n        default:\n            return 0;\n    }\n}\n\nstatic inline bool\naddr_local(const struct sockaddr *addr)\n{\n    if (!addr)\n    {\n        return false;\n    }\n    switch (addr->sa_family)\n    {\n        case AF_INET:\n            return ((const struct sockaddr_in *)addr)->sin_addr.s_addr == htonl(INADDR_LOOPBACK);\n\n        case AF_INET6:\n            return IN6_IS_ADDR_LOOPBACK(&((const struct sockaddr_in6 *)addr)->sin6_addr);\n\n        default:\n            return false;\n    }\n}\n\n\nstatic inline bool\naddr_defined_ipi(const struct link_socket_actual *lsa)\n{\n#if ENABLE_IP_PKTINFO\n    if (!lsa)\n    {\n        return 0;\n    }\n    switch (lsa->dest.addr.sa.sa_family)\n    {\n#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)\n        case AF_INET:\n            return lsa->pi.in4.ipi_spec_dst.s_addr != 0;\n\n#elif defined(IP_RECVDSTADDR)\n        case AF_INET:\n            return lsa->pi.in4.s_addr != 0;\n\n#endif\n        case AF_INET6:\n            return !IN6_IS_ADDR_UNSPECIFIED(&lsa->pi.in6.ipi6_addr);\n\n        default:\n            return 0;\n    }\n#else /* if ENABLE_IP_PKTINFO */\n    ASSERT(0);\n#endif\n    return false;\n}\n\n/*\n * Overhead added to packets by various protocols.\n */\nstatic inline int\ndatagram_overhead(sa_family_t af, int proto)\n{\n    int overhead = 0;\n    overhead += (proto == PROTO_UDP) ? 8 : 20;\n    overhead += (af == AF_INET) ? 20 : 40;\n    return overhead;\n}\n\n/*\n * Misc inline functions\n */\n\nstatic inline bool\nlink_socket_proto_connection_oriented(int proto)\n{\n    return !proto_is_dgram(proto);\n}\n\nstatic inline bool\nlink_socket_actual_defined(const struct link_socket_actual *act)\n{\n    return act && addr_defined(&act->dest);\n}\n\nstatic inline bool\naddr_match(const struct openvpn_sockaddr *a1, const struct openvpn_sockaddr *a2)\n{\n    switch (a1->addr.sa.sa_family)\n    {\n        case AF_INET:\n            return a1->addr.in4.sin_addr.s_addr == a2->addr.in4.sin_addr.s_addr;\n\n        case AF_INET6:\n            return IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr, &a2->addr.in6.sin6_addr);\n    }\n    ASSERT(0);\n    return false;\n}\n\nstatic inline bool\naddrlist_match(const struct openvpn_sockaddr *a1, const struct addrinfo *addrlist)\n{\n    const struct addrinfo *curele;\n    for (curele = addrlist; curele; curele = curele->ai_next)\n    {\n        switch (a1->addr.sa.sa_family)\n        {\n            case AF_INET:\n                if (a1->addr.in4.sin_addr.s_addr\n                    == ((struct sockaddr_in *)curele->ai_addr)->sin_addr.s_addr)\n                {\n                    return true;\n                }\n                break;\n\n            case AF_INET6:\n                if (IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr,\n                                       &((struct sockaddr_in6 *)curele->ai_addr)->sin6_addr))\n                {\n                    return true;\n                }\n                break;\n\n            default:\n                ASSERT(0);\n        }\n    }\n    return false;\n}\n\nstatic inline bool\naddrlist_port_match(const struct openvpn_sockaddr *a1, const struct addrinfo *a2)\n{\n    const struct addrinfo *curele;\n    for (curele = a2; curele; curele = curele->ai_next)\n    {\n        switch (a1->addr.sa.sa_family)\n        {\n            case AF_INET:\n                if (curele->ai_family == AF_INET\n                    && a1->addr.in4.sin_addr.s_addr\n                           == ((struct sockaddr_in *)curele->ai_addr)->sin_addr.s_addr\n                    && a1->addr.in4.sin_port == ((struct sockaddr_in *)curele->ai_addr)->sin_port)\n                {\n                    return true;\n                }\n                break;\n\n            case AF_INET6:\n                if (curele->ai_family == AF_INET6\n                    && IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr,\n                                          &((struct sockaddr_in6 *)curele->ai_addr)->sin6_addr)\n                    && a1->addr.in6.sin6_port\n                           == ((struct sockaddr_in6 *)curele->ai_addr)->sin6_port)\n                {\n                    return true;\n                }\n                break;\n\n            default:\n                ASSERT(0);\n        }\n    }\n    return false;\n}\n\n\nstatic inline bool\naddr_port_match(const struct openvpn_sockaddr *a1, const struct openvpn_sockaddr *a2)\n{\n    switch (a1->addr.sa.sa_family)\n    {\n        case AF_INET:\n            return a1->addr.in4.sin_addr.s_addr == a2->addr.in4.sin_addr.s_addr\n                   && a1->addr.in4.sin_port == a2->addr.in4.sin_port;\n\n        case AF_INET6:\n            return IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr, &a2->addr.in6.sin6_addr)\n                   && a1->addr.in6.sin6_port == a2->addr.in6.sin6_port;\n    }\n    ASSERT(0);\n    return false;\n}\n\nstatic inline bool\naddr_match_proto(const struct openvpn_sockaddr *a1, const struct openvpn_sockaddr *a2,\n                 const int proto)\n{\n    return link_socket_proto_connection_oriented(proto) ? addr_match(a1, a2)\n                                                        : addr_port_match(a1, a2);\n}\n\n\nstatic inline bool\naddrlist_match_proto(const struct openvpn_sockaddr *a1, struct addrinfo *addr_list, const int proto)\n{\n    return link_socket_proto_connection_oriented(proto) ? addrlist_match(a1, addr_list)\n                                                        : addrlist_port_match(a1, addr_list);\n}\n\nstatic inline void\naddr_zero_host(struct openvpn_sockaddr *addr)\n{\n    switch (addr->addr.sa.sa_family)\n    {\n        case AF_INET:\n            addr->addr.in4.sin_addr.s_addr = 0;\n            break;\n\n        case AF_INET6:\n            memset(&addr->addr.in6.sin6_addr, 0, sizeof(struct in6_addr));\n            break;\n    }\n}\n\nstatic inline int\naf_addr_size(sa_family_t af)\n{\n    switch (af)\n    {\n        case AF_INET:\n            return sizeof(struct sockaddr_in);\n\n        case AF_INET6:\n            return sizeof(struct sockaddr_in6);\n\n        default:\n#if 0\n            /* could be called from socket_do_accept() with empty addr */\n            msg(M_ERR, \"Bad address family: %d\", af);\n            ASSERT(0);\n#endif\n            return 0;\n    }\n}\n\nstatic inline bool\nlink_socket_actual_match(const struct link_socket_actual *a1, const struct link_socket_actual *a2)\n{\n    return addr_port_match(&a1->dest, &a2->dest);\n}\n\n#endif\n"
  },
  {
    "path": "src/openvpn/socks.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * 2004-01-30: Added Socks5 proxy support, see RFC 1928\n *   (Christof Meerwald, https://cmeerw.org)\n *\n * 2010-10-10: Added Socks5 plain text authentication support (RFC 1929)\n *   (Pierre Bourdon <delroth@gmail.com>)\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"common.h\"\n#include \"misc.h\"\n#include \"win32.h\"\n#include \"socket.h\"\n#include \"fdmisc.h\"\n#include \"misc.h\"\n#include \"proxy.h\"\n#include \"forward.h\"\n\n#include \"memdbg.h\"\n\n#define UP_TYPE_SOCKS \"SOCKS Proxy\"\n\nstruct socks_proxy_info *\nsocks_proxy_new(const char *server, const char *port, const char *authfile)\n{\n    struct socks_proxy_info *p;\n\n    ALLOC_OBJ_CLEAR(p, struct socks_proxy_info);\n\n    ASSERT(server);\n    ASSERT(port);\n\n    strncpynt(p->server, server, sizeof(p->server));\n    p->port = port;\n\n    if (authfile)\n    {\n        strncpynt(p->authfile, authfile, sizeof(p->authfile));\n    }\n    else\n    {\n        p->authfile[0] = 0;\n    }\n\n    p->defined = true;\n\n    return p;\n}\n\nvoid\nsocks_proxy_close(struct socks_proxy_info *sp)\n{\n    free(sp);\n}\n\nstatic bool\nsocks_proxy_recv_char(uint8_t *c, const char *name, socket_descriptor_t sd,\n                      struct event_timeout *server_poll_timeout,\n                      volatile int *signal_received)\n{\n    fd_set reads;\n    FD_ZERO(&reads);\n    openvpn_fd_set(sd, &reads);\n\n    struct timeval tv;\n    tv.tv_sec = get_server_poll_remaining_time(server_poll_timeout);\n    tv.tv_usec = 0;\n\n    return proxy_recv_char(c, name, sd, &tv, signal_received);\n}\n\nstatic bool\nsocks_username_password_auth(struct socks_proxy_info *p, socket_descriptor_t sd,\n                             struct event_timeout *server_poll_timeout,\n                             volatile int *signal_received)\n{\n    char to_send[516];\n    struct user_pass creds;\n    bool ret = false;\n\n    CLEAR(creds);\n    if (!get_user_pass(&creds, p->authfile, UP_TYPE_SOCKS, GET_USER_PASS_MANAGEMENT))\n    {\n        msg(M_NONFATAL, \"SOCKS failed to get username/password.\");\n        goto cleanup;\n    }\n\n    if ((strlen(creds.username) > 255) || (strlen(creds.password) > 255))\n    {\n        msg(M_NONFATAL, \"SOCKS username and/or password exceeds 255 characters.  \"\n                        \"Authentication not possible.\");\n        goto cleanup;\n    }\n\n    ASSERT(checked_snprintf(to_send, sizeof(to_send), \"\\x01%c%s%c%s\",\n                            (int)strlen(creds.username), creds.username,\n                            (int)strlen(creds.password), creds.password));\n    if (!proxy_send(sd, to_send, strlen(to_send)))\n    {\n        goto cleanup;\n    }\n\n    int len = 0;\n    uint8_t buf[2];\n    while (len < 2)\n    {\n        uint8_t c;\n\n        if (!socks_proxy_recv_char(&c, __func__, sd, server_poll_timeout, signal_received))\n        {\n            goto cleanup;\n        }\n        /* store char in buffer */\n        buf[len++] = c;\n    }\n\n    /* VER = 5, SUCCESS = 0 --> auth success */\n    if (buf[0] != 5 || buf[1] != 0)\n    {\n        msg(D_LINK_ERRORS, \"socks_username_password_auth: server refused the authentication\");\n        goto cleanup;\n    }\n\n    ret = true;\ncleanup:\n    secure_memzero(&creds, sizeof(creds));\n    secure_memzero(to_send, sizeof(to_send));\n    return ret;\n}\n\nstatic bool\nsocks_handshake(struct socks_proxy_info *p, socket_descriptor_t sd,\n                struct event_timeout *server_poll_timeout, volatile int *signal_received)\n{\n    uint8_t buf[2];\n    int len = 0;\n\n    /* VER = 5, NMETHODS = 1, METHODS = [0 (no auth)] */\n    uint8_t method_sel[3] = { 0x05, 0x01, 0x00 };\n    if (p->authfile[0])\n    {\n        method_sel[2] = 0x02; /* METHODS = [2 (plain login)] */\n    }\n    if (!proxy_send(sd, method_sel, sizeof(method_sel)))\n    {\n        return false;\n    }\n\n    while (len < 2)\n    {\n        uint8_t c;\n\n        if (!socks_proxy_recv_char(&c, __func__, sd, server_poll_timeout, signal_received))\n        {\n            return false;\n        }\n        /* store char in buffer */\n        buf[len++] = c;\n    }\n\n    /* VER == 5 */\n    if (buf[0] != 5)\n    {\n        msg(D_LINK_ERRORS, \"socks_handshake: Socks proxy returned bad status\");\n        return false;\n    }\n\n    /* validate that the auth method returned is the one sent */\n    if (buf[1] != method_sel[2])\n    {\n        msg(D_LINK_ERRORS, \"socks_handshake: Socks proxy returned unexpected auth\");\n        return false;\n    }\n\n    /* select the appropriate authentication method */\n    switch (buf[1])\n    {\n        case 0: /* no authentication */\n            break;\n\n        case 2: /* login/password */\n            if (!p->authfile[0])\n            {\n                msg(D_LINK_ERRORS,\n                    \"socks_handshake: server asked for username/login auth but we were \"\n                    \"not provided any credentials\");\n                return false;\n            }\n\n            if (!socks_username_password_auth(p, sd, server_poll_timeout, signal_received))\n            {\n                return false;\n            }\n\n            break;\n\n        default: /* unknown auth method */\n            msg(D_LINK_ERRORS, \"socks_handshake: unknown SOCKS auth method\");\n            return false;\n    }\n\n    return true;\n}\n\nstatic bool\nrecv_socks_reply(socket_descriptor_t sd, struct openvpn_sockaddr *addr,\n                 struct event_timeout *server_poll_timeout, volatile int *signal_received)\n{\n    uint8_t atyp = 0;\n    int alen = 0;\n    int len = 0;\n    char buf[270]; /* 4 + alen(max 256) + 2 */\n\n    if (addr != NULL)\n    {\n        addr->addr.in4.sin_family = AF_INET;\n        addr->addr.in4.sin_addr.s_addr = htonl(INADDR_ANY);\n        addr->addr.in4.sin_port = htons(0);\n    }\n\n    while (len < 4 + alen + 2)\n    {\n        uint8_t c;\n\n        if (!socks_proxy_recv_char(&c, __func__, sd, server_poll_timeout, signal_received))\n        {\n            return false;\n        }\n\n        if (len == 3)\n        {\n            atyp = c;\n        }\n\n        if (len == 4)\n        {\n            switch (atyp)\n            {\n                case 1: /* IP V4 */\n                    alen = 4;\n                    break;\n\n                case 3: /* DOMAINNAME */\n                    /* RFC 1928, section 5: 1 byte length, <n> bytes name,\n                     * so the total \"address length\" is (length+1)\n                     */\n                    alen = c + 1;\n                    break;\n\n                case 4: /* IP V6 */\n                    alen = 16;\n                    break;\n\n                default:\n                    msg(D_LINK_ERRORS, \"recv_socks_reply: Socks proxy returned bad address type\");\n                    return false;\n            }\n        }\n\n        /* store char in buffer */\n        if (len < (int)sizeof(buf))\n        {\n            buf[len] = c;\n        }\n        ++len;\n    }\n\n    /* VER == 5 && REP == 0 (succeeded) */\n    if (buf[0] != 5 || buf[1] != 0)\n    {\n        msg(D_LINK_ERRORS, \"recv_socks_reply: Socks proxy returned bad reply\");\n        return false;\n    }\n\n    /* ATYP == 1 (IP V4 address) */\n    if (atyp == 1 && addr != NULL)\n    {\n        memcpy(&addr->addr.in4.sin_addr, buf + 4, sizeof(addr->addr.in4.sin_addr));\n        memcpy(&addr->addr.in4.sin_port, buf + 8, sizeof(addr->addr.in4.sin_port));\n        struct gc_arena gc = gc_new();\n        msg(M_INFO, \"SOCKS proxy wants us to send UDP to %s\", print_openvpn_sockaddr(addr, &gc));\n        gc_free(&gc);\n    }\n\n\n    return true;\n}\n\nstatic int\nport_from_servname(const char *servname)\n{\n    int port = 0;\n    port = atoi(servname);\n    if (port > 0 && port < 65536)\n    {\n        return port;\n    }\n\n    struct servent *service;\n    service = getservbyname(servname, NULL);\n    if (service)\n    {\n        return service->s_port;\n    }\n\n    return 0;\n}\n\nvoid\nestablish_socks_proxy_passthru(struct socks_proxy_info *p,\n                               socket_descriptor_t sd, /* already open to proxy */\n                               const char *host,       /* openvpn server remote */\n                               const char *servname,   /* openvpn server port */\n                               struct event_timeout *server_poll_timeout,\n                               struct signal_info *sig_info)\n{\n    char buf[270];\n    size_t len;\n\n    if (!socks_handshake(p, sd, server_poll_timeout, &sig_info->signal_received))\n    {\n        goto error;\n    }\n\n    /* format Socks CONNECT message */\n    buf[0] = '\\x05'; /* VER = 5 */\n    buf[1] = '\\x01'; /* CMD = 1 (CONNECT) */\n    buf[2] = '\\x00'; /* RSV */\n    buf[3] = '\\x03'; /* ATYP = 3 (DOMAINNAME) */\n\n    len = strlen(host);\n    len = (5 + len + 2 > sizeof(buf)) ? (sizeof(buf) - 5 - 2) : len;\n\n    buf[4] = (char)len;\n    memcpy(buf + 5, host, len);\n\n    int port = port_from_servname(servname);\n    if (port == 0)\n    {\n        msg(D_LINK_ERRORS, \"establish_socks_proxy_passthrough: Cannot convert %s to port number\",\n            servname);\n        goto error;\n    }\n\n    buf[5 + len] = (char)(port >> 8);\n    buf[5 + len + 1] = (char)(port & 0xff);\n\n    if (!proxy_send(sd, buf, 5 + len + 2))\n    {\n        goto error;\n    }\n\n    /* receive reply from Socks proxy and discard */\n    if (!recv_socks_reply(sd, NULL, server_poll_timeout, &sig_info->signal_received))\n    {\n        goto error;\n    }\n\n    return;\n\nerror:\n    /* SOFT-SIGUSR1 -- socks error */\n    register_signal(sig_info, SIGUSR1, \"socks-error\");\n    return;\n}\n\nvoid\nestablish_socks_proxy_udpassoc(struct socks_proxy_info *p,\n                               socket_descriptor_t ctrl_sd, /* already open to proxy */\n                               struct openvpn_sockaddr *relay_addr,\n                               struct event_timeout *server_poll_timeout,\n                               struct signal_info *sig_info)\n{\n    if (!socks_handshake(p, ctrl_sd, server_poll_timeout, &sig_info->signal_received))\n    {\n        goto error;\n    }\n\n    {\n        /* send Socks UDP ASSOCIATE message */\n        /* VER = 5, CMD = 3 (UDP ASSOCIATE), RSV = 0, ATYP = 1 (IP V4),\n         * BND.ADDR = 0, BND.PORT = 0 */\n        const ssize_t size =\n            send(ctrl_sd, \"\\x05\\x03\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\", 10, MSG_NOSIGNAL);\n        if (size != 10)\n        {\n            msg(D_LINK_ERRORS | M_ERRNO, \"%s: TCP port write failed on send()\", __func__);\n            goto error;\n        }\n    }\n\n    /* receive reply from Socks proxy */\n    CLEAR(*relay_addr);\n    if (!recv_socks_reply(ctrl_sd, relay_addr, server_poll_timeout, &sig_info->signal_received))\n    {\n        goto error;\n    }\n    if (!relay_addr->addr.in4.sin_addr.s_addr)\n    {\n        msg(D_LINK_ERRORS, \"%s: Socks proxy did not answer with IPv4 address\", __func__);\n        goto error;\n    }\n\n    return;\n\nerror:\n    /* SOFT-SIGUSR1 -- socks error */\n    register_signal(sig_info, SIGUSR1, \"socks-error\");\n    return;\n}\n\n/*\n * Remove the socks5 header from an incoming\n * UDP packet, setting *from to the source address.\n *\n * Run after UDP read.\n */\nvoid\nsocks_process_incoming_udp(struct buffer *buf, struct link_socket_actual *from)\n{\n    int atyp;\n\n    if (BLEN(buf) < SOCKS_UDPv4_HEADROOM)\n    {\n        goto error;\n    }\n\n    buf_read_u16(buf);\n    if (buf_read_u8(buf) != 0)\n    {\n        goto error;\n    }\n\n    atyp = buf_read_u8(buf);\n    if (atyp != 1) /* ATYP == 1 (IP V4) */\n    {\n        goto error;\n    }\n\n    buf_read(buf, &from->dest.addr.in4.sin_addr, sizeof(from->dest.addr.in4.sin_addr));\n    buf_read(buf, &from->dest.addr.in4.sin_port, sizeof(from->dest.addr.in4.sin_port));\n\n    return;\n\nerror:\n    buf->len = 0;\n}\n\n/*\n * Add a socks header prior to UDP write.\n * *to is the destination address.\n *\n * Run before UDP write.\n * Returns the size of the header.\n */\nint\nsocks_process_outgoing_udp(struct buffer *buf, const struct link_socket_actual *to)\n{\n    /*\n     * Get a subset buffer prepended to buf --\n     * we expect these bytes will be here because\n     * we always allocate space for these bytes\n     */\n    struct buffer head = buf_sub(buf, SOCKS_UDPv4_HEADROOM, true);\n\n    /* crash if not enough headroom in buf */\n    ASSERT(buf_defined(&head));\n\n    buf_write_u16(&head, 0);     /* RSV = 0 */\n    buf_write_u8(&head, 0);      /* FRAG = 0 */\n    buf_write_u8(&head, '\\x01'); /* ATYP = 1 (IP V4) */\n    buf_write(&head, &to->dest.addr.in4.sin_addr, sizeof(to->dest.addr.in4.sin_addr));\n    buf_write(&head, &to->dest.addr.in4.sin_port, sizeof(to->dest.addr.in4.sin_port));\n\n    return SOCKS_UDPv4_HEADROOM;\n}\n"
  },
  {
    "path": "src/openvpn/socks.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * 2004-01-30: Added Socks5 proxy support\n *   (Christof Meerwald, https://cmeerw.org)\n */\n\n#ifndef SOCKS_H\n#define SOCKS_H\n\n#include \"buffer.h\"\n\nstruct openvpn_sockaddr;\nstruct link_socket_actual;\n\nstruct socks_proxy_info\n{\n    bool defined;\n\n    char server[128];\n    const char *port;\n    char authfile[256];\n};\n\nstruct socks_proxy_info *socks_proxy_new(const char *server, const char *port,\n                                         const char *authfile);\n\nvoid socks_proxy_close(struct socks_proxy_info *sp);\n\nvoid establish_socks_proxy_passthru(struct socks_proxy_info *p,\n                                    socket_descriptor_t sd, /* already open to proxy */\n                                    const char *host,       /* openvpn server remote */\n                                    const char *servname,   /* openvpn server port */\n                                    struct event_timeout *server_poll_timeout,\n                                    struct signal_info *sig_info);\n\nvoid establish_socks_proxy_udpassoc(struct socks_proxy_info *p,\n                                    socket_descriptor_t ctrl_sd, /* already open to proxy */\n                                    struct openvpn_sockaddr *relay_addr,\n                                    struct event_timeout *server_poll_timeout,\n                                    struct signal_info *sig_info);\n\nvoid socks_process_incoming_udp(struct buffer *buf, struct link_socket_actual *from);\n\nint socks_process_outgoing_udp(struct buffer *buf, const struct link_socket_actual *to);\n\n#endif /* ifndef SOCKS_H */\n"
  },
  {
    "path": "src/openvpn/ssl.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *  Copyright (C) 2008-2026 David Sommerseth <dazo@eurephia.org>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel SSL/Data channel negotiation Module\n */\n\n/*\n * The routines in this file deal with dynamically negotiating\n * the data channel HMAC and cipher keys through a TLS session.\n *\n * Both the TLS session and the data channel are multiplexed\n * over the same TCP/UDP port.\n */\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n#include \"win32.h\"\n\n#include \"error.h\"\n#include \"common.h\"\n#include \"socket.h\"\n#include \"misc.h\"\n#include \"fdmisc.h\"\n#include \"interval.h\"\n#include \"status.h\"\n#include \"gremlin.h\"\n#include \"pkcs11.h\"\n#include \"route.h\"\n#include \"tls_crypt.h\"\n\n#include \"crypto_epoch.h\"\n#include \"ssl.h\"\n#include \"ssl_verify.h\"\n#include \"ssl_backend.h\"\n#include \"ssl_ncp.h\"\n#include \"ssl_util.h\"\n#include \"auth_token.h\"\n#include \"mss.h\"\n#include \"dco.h\"\n\n#include \"memdbg.h\"\n#include \"openvpn.h\"\n\n#ifdef MEASURE_TLS_HANDSHAKE_STATS\n\nstatic int tls_handshake_success; /* GLOBAL */\nstatic int tls_handshake_error;   /* GLOBAL */\nstatic int tls_packets_generated; /* GLOBAL */\nstatic int tls_packets_sent;      /* GLOBAL */\n\n#define INCR_SENT      ++tls_packets_sent\n#define INCR_GENERATED ++tls_packets_generated\n#define INCR_SUCCESS   ++tls_handshake_success\n#define INCR_ERROR     ++tls_handshake_error\n\nvoid\nshow_tls_performance_stats(void)\n{\n    msg(D_TLS_DEBUG_LOW, \"TLS Handshakes, success=%f%% (good=%d, bad=%d), retransmits=%f%%\",\n        (double)tls_handshake_success / (tls_handshake_success + tls_handshake_error) * 100.0,\n        tls_handshake_success, tls_handshake_error,\n        (double)(tls_packets_sent - tls_packets_generated) / tls_packets_generated * 100.0);\n}\n#else /* ifdef MEASURE_TLS_HANDSHAKE_STATS */\n\n#define INCR_SENT\n#define INCR_GENERATED\n#define INCR_SUCCESS\n#define INCR_ERROR\n\n#endif /* ifdef MEASURE_TLS_HANDSHAKE_STATS */\n\n/**\n * Limit the reneg_bytes value when using a small-block (<128 bytes) cipher.\n *\n * @param ciphername    The current cipher (may be NULL).\n * @param reneg_bytes   Pointer to the current reneg_bytes, updated if needed.\n *                      May *not* be NULL.\n */\nstatic void\ntls_limit_reneg_bytes(const char *ciphername, int64_t *reneg_bytes)\n{\n    if (cipher_kt_insecure(ciphername))\n    {\n        if (*reneg_bytes == -1) /* Not user-specified */\n        {\n            msg(M_WARN, \"WARNING: cipher with small block size in use, \"\n                        \"reducing reneg-bytes to 64MB to mitigate SWEET32 attacks.\");\n            *reneg_bytes = 64 * 1024 * 1024;\n        }\n    }\n}\n\nstatic uint64_t\ntls_get_limit_aead(const char *ciphername)\n{\n    uint64_t limit = cipher_get_aead_limits(ciphername);\n\n    if (limit == 0)\n    {\n        return 0;\n    }\n\n    /* set limit to 7/8 of the limit so the renegotiation can succeed before\n     * we go over the limit */\n    limit = limit / 8 * 7;\n\n    msg(D_SHOW_KEYS,\n        \"Note: AEAD cipher %s will trigger a renegotiation\"\n        \" at a sum of %\" PRIi64 \" blocks and packets.\",\n        ciphername, limit);\n    return limit;\n}\n\nvoid\ntls_init_control_channel_frame_parameters(struct frame *frame, int tls_mtu)\n{\n    /*\n     * frame->extra_frame is already initialized with tls_auth buffer requirements,\n     * if --tls-auth is enabled.\n     */\n\n    /* calculates the maximum overhead that control channel frames can have */\n    int overhead = 0;\n\n    /* Socks */\n    overhead += 10;\n\n    /* tls-auth and tls-crypt */\n    overhead += max_int(tls_crypt_buf_overhead(), packet_id_size(true) + OPENVPN_MAX_HMAC_SIZE);\n\n    /* TCP length field and opcode */\n    overhead += 3;\n\n    /* ACK array and remote SESSION ID (part of the ACK array) */\n    overhead += ACK_SIZE(RELIABLE_ACK_SIZE);\n\n    /* Previous OpenVPN version calculated the maximum size and buffer of a\n     * control frame depending on the overhead of the data channel frame\n     * overhead and limited its maximum size to 1250. Since control frames\n     * also need to fit into data channel buffer we have the same\n     * default of 1500 + 100 as data channel buffers have. Increasing\n     * control channel mtu beyond this limit also increases the data channel\n     * buffers */\n    frame->buf.payload_size = max_int(1500, tls_mtu) + 100;\n\n    frame->buf.headroom = overhead;\n    frame->buf.tailroom = overhead;\n\n    frame->tun_mtu = tls_mtu;\n\n    /* Ensure the tun-mtu stays in a valid range */\n    frame->tun_mtu = min_int(frame->tun_mtu, TLS_CHANNEL_BUF_SIZE);\n    frame->tun_mtu = max_int(frame->tun_mtu, TLS_CHANNEL_MTU_MIN);\n}\n\n/**\n * calculate the maximum overhead that control channel frames have\n * This includes header, op code and everything apart from the\n * payload itself. This method is a bit pessimistic and might give higher\n * overhead than we actually have */\nstatic size_t\ncalc_control_channel_frame_overhead(const struct tls_session *session)\n{\n    const struct key_state *ks = &session->key[KS_PRIMARY];\n    size_t overhead = 0;\n\n    /* opcode */\n    overhead += 1;\n\n    /* our own session id */\n    overhead += SID_SIZE;\n\n    /* ACK array and remote SESSION ID (part of the ACK array) */\n    int ackstosend = reliable_ack_outstanding(ks->rec_ack) + ks->lru_acks->len;\n    overhead += ACK_SIZE(min_int(ackstosend, CONTROL_SEND_ACK_MAX));\n\n    /* Message packet id */\n    overhead += sizeof(packet_id_type);\n\n    if (session->tls_wrap.mode == TLS_WRAP_CRYPT)\n    {\n        overhead += tls_crypt_buf_overhead();\n    }\n    else if (session->tls_wrap.mode == TLS_WRAP_AUTH)\n    {\n        overhead += hmac_ctx_size(session->tls_wrap.opt.key_ctx_bi.encrypt.hmac);\n        overhead += packet_id_size(true);\n    }\n\n    /* Add the typical UDP overhead for an IPv6 UDP packet. TCP+IPv6 has a\n     * larger overhead but the risk of a TCP connection getting dropped because\n     * we try to send a too large packet is basically zero */\n    overhead += datagram_overhead(session->untrusted_addr.dest.addr.sa.sa_family, PROTO_UDP);\n\n    return overhead;\n}\n\nvoid\ninit_ssl_lib(void)\n{\n    tls_init_lib();\n\n    crypto_init_lib();\n}\n\nvoid\nfree_ssl_lib(void)\n{\n    crypto_uninit_lib();\n\n    tls_free_lib();\n}\n\n/*\n * OpenSSL library calls pem_password_callback if the\n * private key is protected by a password.\n */\n\nstatic struct user_pass passbuf; /* GLOBAL */\n\nvoid\npem_password_setup(const char *auth_file)\n{\n    unprotect_user_pass(&passbuf);\n    if (!strlen(passbuf.password))\n    {\n        get_user_pass(&passbuf, auth_file, UP_TYPE_PRIVATE_KEY,\n                      GET_USER_PASS_MANAGEMENT | GET_USER_PASS_PASSWORD_ONLY);\n    }\n}\n\nint\npem_password_callback(char *buf, int size, int rwflag, void *u)\n{\n    if (buf)\n    {\n        /* prompt for password even if --askpass wasn't specified */\n        pem_password_setup(NULL);\n        ASSERT(!passbuf.protected);\n        strncpynt(buf, passbuf.password, (size_t)size);\n        purge_user_pass(&passbuf, false);\n\n        return (int)strlen(buf);\n    }\n    return 0;\n}\n\n/*\n * Auth username/password handling\n */\n\nstatic bool auth_user_pass_enabled;     /* GLOBAL */\nstatic struct user_pass auth_user_pass; /* GLOBAL */\nstatic struct user_pass auth_token;     /* GLOBAL */\n\n#ifdef ENABLE_MANAGEMENT\nstatic char *auth_challenge; /* GLOBAL */\n#endif\n\nvoid\nenable_auth_user_pass(void)\n{\n    auth_user_pass_enabled = true;\n}\n\nvoid\nauth_user_pass_setup(const char *auth_file, bool is_inline, const struct static_challenge_info *sci)\n{\n    unsigned int flags = GET_USER_PASS_MANAGEMENT;\n\n    if (is_inline)\n    {\n        flags |= GET_USER_PASS_INLINE_CREDS;\n    }\n\n    if (!auth_user_pass.defined && !auth_token.defined)\n    {\n        unprotect_user_pass(&auth_user_pass);\n#ifdef ENABLE_MANAGEMENT\n        if (auth_challenge) /* dynamic challenge/response */\n        {\n            flags |= GET_USER_PASS_DYNAMIC_CHALLENGE;\n            get_user_pass_cr(&auth_user_pass, auth_file, UP_TYPE_AUTH, flags, auth_challenge);\n        }\n        else if (sci) /* static challenge response */\n        {\n            flags |= GET_USER_PASS_STATIC_CHALLENGE;\n            if (sci->flags & SC_ECHO)\n            {\n                flags |= GET_USER_PASS_STATIC_CHALLENGE_ECHO;\n            }\n            if (sci->flags & SC_CONCAT)\n            {\n                flags |= GET_USER_PASS_STATIC_CHALLENGE_CONCAT;\n            }\n            get_user_pass_cr(&auth_user_pass, auth_file, UP_TYPE_AUTH, flags, sci->challenge_text);\n        }\n        else\n#endif /* ifdef ENABLE_MANAGEMENT */\n        {\n            get_user_pass(&auth_user_pass, auth_file, UP_TYPE_AUTH, flags);\n        }\n    }\n}\n\n/*\n * Disable password caching\n */\nvoid\nssl_set_auth_nocache(void)\n{\n    passbuf.nocache = true;\n    auth_user_pass.nocache = true;\n}\n\n/*\n * Get the password caching\n */\nbool\nssl_get_auth_nocache(void)\n{\n    return passbuf.nocache;\n}\n\n/*\n * Set an authentication token\n */\nvoid\nssl_set_auth_token(const char *token)\n{\n    set_auth_token(&auth_token, token);\n}\n\nvoid\nssl_set_auth_token_user(const char *username)\n{\n    set_auth_token_user(&auth_token, username);\n}\n\n/*\n * Cleans an auth token and checks if it was active\n */\nbool\nssl_clean_auth_token(void)\n{\n    bool wasdefined = auth_token.defined;\n    purge_user_pass(&auth_token, true);\n    return wasdefined;\n}\n\n/*\n * Forget private key password AND auth-user-pass username/password.\n */\nvoid\nssl_purge_auth(const bool auth_user_pass_only)\n{\n    if (!auth_user_pass_only)\n    {\n#ifdef ENABLE_PKCS11\n        pkcs11_logout();\n#endif\n        purge_user_pass(&passbuf, true);\n    }\n    purge_user_pass(&auth_user_pass, true);\n#ifdef ENABLE_MANAGEMENT\n    ssl_purge_auth_challenge();\n#endif\n}\n\n#ifdef ENABLE_MANAGEMENT\n\nvoid\nssl_purge_auth_challenge(void)\n{\n    free(auth_challenge);\n    auth_challenge = NULL;\n}\n\nvoid\nssl_put_auth_challenge(const char *cr_str)\n{\n    ssl_purge_auth_challenge();\n    auth_challenge = string_alloc(cr_str, NULL);\n}\n\n#endif\n\n/*\n * Parse a TLS version string, returning a TLS_VER_x constant.\n * If version string is not recognized and extra == \"or-highest\",\n * return tls_version_max().\n */\nint\ntls_version_parse(const char *vstr, const char *extra)\n{\n    const int max_version = tls_version_max();\n    if (!strcmp(vstr, \"1.0\") && TLS_VER_1_0 <= max_version)\n    {\n        return TLS_VER_1_0;\n    }\n    else if (!strcmp(vstr, \"1.1\") && TLS_VER_1_1 <= max_version)\n    {\n        return TLS_VER_1_1;\n    }\n    else if (!strcmp(vstr, \"1.2\") && TLS_VER_1_2 <= max_version)\n    {\n        return TLS_VER_1_2;\n    }\n    else if (!strcmp(vstr, \"1.3\") && TLS_VER_1_3 <= max_version)\n    {\n        return TLS_VER_1_3;\n    }\n    else if (extra && !strcmp(extra, \"or-highest\"))\n    {\n        return max_version;\n    }\n    else\n    {\n        return TLS_VER_BAD;\n    }\n}\n\n/**\n * Load (or possibly reload) the CRL file into the SSL context.\n * No reload is performed under the following conditions:\n * - the CRL file was passed inline\n * - the CRL file was not modified since the last (re)load\n *\n * @param ssl_ctx         The TLS context to use when reloading the CRL\n * @param crl_file        The file name to load the CRL from, or\n *                        or an array containing the inline CRL.\n * @param crl_file_inline True if crl_file is an inline CRL.\n */\nstatic void\ntls_ctx_reload_crl(struct tls_root_ctx *ssl_ctx, const char *crl_file, bool crl_file_inline)\n{\n    /* if something goes wrong with stat(), we'll store 0 as mtime */\n    platform_stat_t crl_stat = { 0 };\n\n    /*\n     * an inline CRL can't change at runtime, therefore there is no need to\n     * reload it. It will be reloaded upon config change + SIGHUP.\n     * Use always '1' as dummy timestamp in this case: it will trigger the\n     * first load, but will prevent any future reload.\n     */\n    if (crl_file_inline)\n    {\n        crl_stat.st_mtime = 1;\n    }\n    else if (platform_stat(crl_file, &crl_stat) < 0)\n    {\n        /* If crl_last_mtime is zero, the CRL file has not been read before. */\n        if (ssl_ctx->crl_last_mtime == 0)\n        {\n            msg(M_FATAL, \"ERROR: Failed to stat CRL file during initialization, exiting.\");\n        }\n        else\n        {\n            msg(M_WARN, \"WARNING: Failed to stat CRL file, not reloading CRL.\");\n        }\n        return;\n    }\n\n    /*\n     * Store the CRL if this is the first time or if the file was changed since\n     * the last load.\n     * Note: Windows does not support tv_nsec.\n     */\n    if ((ssl_ctx->crl_last_size == crl_stat.st_size)\n        && (ssl_ctx->crl_last_mtime == crl_stat.st_mtime))\n    {\n        return;\n    }\n\n    ssl_ctx->crl_last_mtime = crl_stat.st_mtime;\n    ssl_ctx->crl_last_size = crl_stat.st_size;\n    backend_tls_ctx_reload_crl(ssl_ctx, crl_file, crl_file_inline);\n}\n\n/*\n * Initialize SSL context.\n * All files are in PEM format.\n */\nstruct tls_root_ctx *\ninit_ssl(const struct options *options, bool in_chroot)\n{\n    tls_clear_error();\n\n    if (key_is_external(options))\n    {\n        load_xkey_provider();\n    }\n\n    struct tls_root_ctx *new_ctx;\n    ALLOC_OBJ_CLEAR(new_ctx, struct tls_root_ctx);\n\n    if (options->tls_server)\n    {\n        tls_ctx_server_new(new_ctx);\n\n        if (options->dh_file)\n        {\n            tls_ctx_load_dh_params(new_ctx, options->dh_file, options->dh_file_inline);\n        }\n    }\n    else /* if client */\n    {\n        tls_ctx_client_new(new_ctx);\n    }\n\n    /* Restrict allowed certificate crypto algorithms */\n    tls_ctx_set_cert_profile(new_ctx, options->tls_cert_profile);\n\n    /* Allowable ciphers */\n    /* Since @SECLEVEL also influences loading of certificates, set the\n     * cipher restrictions before loading certificates */\n    tls_ctx_restrict_ciphers(new_ctx, options->cipher_list);\n    tls_ctx_restrict_ciphers_tls13(new_ctx, options->cipher_list_tls13);\n\n    /* Set the allow groups/curves for TLS if we want to override them */\n    if (options->tls_groups)\n    {\n        tls_ctx_set_tls_groups(new_ctx, options->tls_groups);\n    }\n\n    if (!tls_ctx_set_options(new_ctx, options->ssl_flags))\n    {\n        goto err;\n    }\n\n    if (options->pkcs12_file)\n    {\n        if (0\n            != tls_ctx_load_pkcs12(new_ctx, options->pkcs12_file, options->pkcs12_file_inline,\n                                   !options->ca_file))\n        {\n            goto err;\n        }\n    }\n#ifdef ENABLE_PKCS11\n    else if (options->pkcs11_providers[0])\n    {\n        if (!tls_ctx_use_pkcs11(new_ctx, options->pkcs11_id_management, options->pkcs11_id))\n        {\n            msg(M_WARN, \"Cannot load certificate \\\"%s\\\" using PKCS#11 interface\",\n                options->pkcs11_id);\n            goto err;\n        }\n    }\n#endif\n#ifdef ENABLE_CRYPTOAPI\n    else if (options->cryptoapi_cert)\n    {\n        tls_ctx_load_cryptoapi(new_ctx, options->cryptoapi_cert);\n    }\n#endif\n#ifdef ENABLE_MANAGEMENT\n    else if (options->management_flags & MF_EXTERNAL_CERT)\n    {\n        char *cert = management_query_cert(management, options->management_certificate);\n        tls_ctx_load_cert_file(new_ctx, cert, true);\n        free(cert);\n    }\n#endif\n    else if (options->cert_file)\n    {\n        tls_ctx_load_cert_file(new_ctx, options->cert_file, options->cert_file_inline);\n    }\n\n    if (options->priv_key_file)\n    {\n        if (0\n            != tls_ctx_load_priv_file(new_ctx, options->priv_key_file,\n                                      options->priv_key_file_inline))\n        {\n            goto err;\n        }\n    }\n#ifdef ENABLE_MANAGEMENT\n    else if (options->management_flags & MF_EXTERNAL_KEY)\n    {\n        if (tls_ctx_use_management_external_key(new_ctx))\n        {\n            msg(M_WARN, \"Cannot initialize mamagement-external-key\");\n            goto err;\n        }\n    }\n#endif\n\n    if (options->ca_file || options->ca_path)\n    {\n        tls_ctx_load_ca(new_ctx, options->ca_file, options->ca_file_inline, options->ca_path,\n                        options->tls_server);\n    }\n\n    /* Load extra certificates that are part of our own certificate\n     * chain but shouldn't be included in the verify chain */\n    if (options->extra_certs_file)\n    {\n        tls_ctx_load_extra_certs(new_ctx, options->extra_certs_file,\n                                 options->extra_certs_file_inline);\n    }\n\n    /* Check certificate notBefore and notAfter */\n    tls_ctx_check_cert_time(new_ctx);\n\n    /* Read CRL */\n    if (options->crl_file && !(options->ssl_flags & SSLF_CRL_VERIFY_DIR))\n    {\n        /* If we're running with the chroot option, we may run init_ssl() before\n         * and after chroot-ing. We can use the crl_file path as-is if we're\n         * not going to chroot, or if we already are inside the chroot.\n         *\n         * If we're going to chroot later, we need to prefix the path of the\n         * chroot directory to crl_file.\n         */\n        if (!options->chroot_dir || in_chroot || options->crl_file_inline)\n        {\n            tls_ctx_reload_crl(new_ctx, options->crl_file, options->crl_file_inline);\n        }\n        else\n        {\n            struct gc_arena gc = gc_new();\n            struct buffer crl_file_buf = prepend_dir(options->chroot_dir, options->crl_file, &gc);\n            tls_ctx_reload_crl(new_ctx, BSTR(&crl_file_buf), options->crl_file_inline);\n            gc_free(&gc);\n        }\n    }\n\n    /* Once keys and cert are loaded, load ECDH parameters */\n    if (options->tls_server)\n    {\n        tls_ctx_load_ecdh_params(new_ctx, options->ecdh_curve);\n    }\n\n#ifdef ENABLE_CRYPTO_MBEDTLS\n    /* Personalise the random by mixing in the certificate */\n    tls_ctx_personalise_random(new_ctx);\n#endif\n\n    tls_clear_error();\n    return new_ctx;\n\nerr:\n    tls_clear_error();\n    tls_ctx_free(new_ctx);\n    free(new_ctx);\n    return NULL;\n}\n\n/*\n * Map internal constants to ascii names.\n */\nstatic const char *\nstate_name(int state)\n{\n    switch (state)\n    {\n        case S_UNDEF:\n            return \"S_UNDEF\";\n\n        case S_INITIAL:\n            return \"S_INITIAL\";\n\n        case S_PRE_START_SKIP:\n            return \"S_PRE_START_SKIP\";\n\n        case S_PRE_START:\n            return \"S_PRE_START\";\n\n        case S_START:\n            return \"S_START\";\n\n        case S_SENT_KEY:\n            return \"S_SENT_KEY\";\n\n        case S_GOT_KEY:\n            return \"S_GOT_KEY\";\n\n        case S_ACTIVE:\n            return \"S_ACTIVE\";\n\n        case S_ERROR:\n            return \"S_ERROR\";\n\n        case S_ERROR_PRE:\n            return \"S_ERROR_PRE\";\n\n        case S_GENERATED_KEYS:\n            return \"S_GENERATED_KEYS\";\n\n        default:\n            return \"S_???\";\n    }\n}\n\nstatic const char *\nks_auth_name(enum ks_auth_state auth)\n{\n    switch (auth)\n    {\n        case KS_AUTH_TRUE:\n            return \"KS_AUTH_TRUE\";\n\n        case KS_AUTH_DEFERRED:\n            return \"KS_AUTH_DEFERRED\";\n\n        case KS_AUTH_FALSE:\n            return \"KS_AUTH_FALSE\";\n\n        default:\n            return \"KS_????\";\n    }\n}\n\nstatic const char *\nsession_index_name(int index)\n{\n    switch (index)\n    {\n        case TM_ACTIVE:\n            return \"TM_ACTIVE\";\n\n        case TM_INITIAL:\n            return \"TM_INITIAL\";\n\n        case TM_LAME_DUCK:\n            return \"TM_LAME_DUCK\";\n\n        default:\n            return \"TM_???\";\n    }\n}\n\n/*\n * For debugging.\n */\nstatic const char *\nprint_key_id(struct tls_multi *multi, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n\n    for (int i = 0; i < KEY_SCAN_SIZE; ++i)\n    {\n        struct key_state *ks = get_key_scan(multi, i);\n        buf_printf(&out, \" [key#%d state=%s auth=%s id=%d sid=%s]\", i, state_name(ks->state),\n                   ks_auth_name(ks->authenticated), ks->key_id,\n                   session_id_print(&ks->session_id_remote, gc));\n    }\n\n    return BSTR(&out);\n}\n\nbool\nis_hard_reset_method2(int op)\n{\n    if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2\n        || op == P_CONTROL_HARD_RESET_CLIENT_V3)\n    {\n        return true;\n    }\n\n    return false;\n}\n\n/** @addtogroup control_processor\n *  @{ */\n\n/** @name Functions for initialization and cleanup of key_state structures\n *  @{ */\n\n/**\n * Initialize a \\c key_state structure.\n * @ingroup control_processor\n *\n * This function initializes a \\c key_state structure associated with a \\c\n * tls_session.  It sets up the structure's SSL-BIO, sets the object's \\c\n * key_state.state to \\c S_INITIAL, and sets the session ID and key ID two\n * appropriate values based on the \\c tls_session's internal state.  It\n * also initializes a new set of structures for the \\link reliable\n * Reliability Layer\\endlink.\n *\n * @param session      - A pointer to the \\c tls_session structure\n *                       associated with the \\a ks argument.\n * @param ks           - A pointer to the \\c key_state structure to be\n *                       initialized.  This structure should already have\n *                       been allocated before calling this function.\n */\nstatic void\nkey_state_init(struct tls_session *session, struct key_state *ks)\n{\n    update_time();\n\n    CLEAR(*ks);\n\n    /*\n     * Build TLS object that reads/writes ciphertext\n     * to/from memory BIOs.\n     */\n    key_state_ssl_init(&ks->ks_ssl, session->opt->ssl_ctx, session->opt->server, session);\n\n    /* Set control-channel initiation mode */\n    ks->initial_opcode = session->initial_opcode;\n    session->initial_opcode = P_CONTROL_SOFT_RESET_V1;\n    ks->state = S_INITIAL;\n    ks->key_id = session->key_id;\n\n    /*\n     * key_id increments to KEY_ID_MASK then recycles back to 1.\n     * This way you know that if key_id is 0, it is the first key.\n     */\n    ++session->key_id;\n    session->key_id &= P_KEY_ID_MASK;\n    if (!session->key_id)\n    {\n        session->key_id = 1;\n    }\n\n    /* allocate key source material object */\n    ALLOC_OBJ_CLEAR(ks->key_src, struct key_source2);\n\n    /* allocate reliability objects */\n    ALLOC_OBJ_CLEAR(ks->send_reliable, struct reliable);\n    ALLOC_OBJ_CLEAR(ks->rec_reliable, struct reliable);\n    ALLOC_OBJ_CLEAR(ks->rec_ack, struct reliable_ack);\n    ALLOC_OBJ_CLEAR(ks->lru_acks, struct reliable_ack);\n\n    /* allocate buffers */\n    ks->plaintext_read_buf = alloc_buf(TLS_CHANNEL_BUF_SIZE);\n    ks->plaintext_write_buf = alloc_buf(TLS_CHANNEL_BUF_SIZE);\n    ks->ack_write_buf = alloc_buf(BUF_SIZE(&session->opt->frame));\n    reliable_init(ks->send_reliable, BUF_SIZE(&session->opt->frame),\n                  session->opt->frame.buf.headroom, TLS_RELIABLE_N_SEND_BUFFERS,\n                  ks->key_id ? false : session->opt->xmit_hold);\n    reliable_init(ks->rec_reliable, BUF_SIZE(&session->opt->frame),\n                  session->opt->frame.buf.headroom, TLS_RELIABLE_N_REC_BUFFERS, false);\n    reliable_set_timeout(ks->send_reliable, session->opt->packet_timeout);\n\n    /* init packet ID tracker */\n    packet_id_init(&ks->crypto_options.packet_id, session->opt->replay_window,\n                   session->opt->replay_time, \"SSL\", ks->key_id);\n\n    ks->crypto_options.pid_persist = NULL;\n\n#ifdef ENABLE_MANAGEMENT\n    ks->mda_key_id = session->opt->mda_context->mda_key_id_counter++;\n#endif\n\n    /*\n     * Attempt CRL reload before TLS negotiation. Won't be performed if\n     * the file was not modified since the last reload. This affects\n     * all instances (all instances share the same context).\n     */\n    if (session->opt->crl_file && !(session->opt->ssl_flags & SSLF_CRL_VERIFY_DIR))\n    {\n        tls_ctx_reload_crl(session->opt->ssl_ctx, session->opt->crl_file,\n                           session->opt->crl_file_inline);\n    }\n}\n\n\n/**\n * Cleanup a \\c key_state structure.\n * @ingroup control_processor\n *\n * This function cleans up a \\c key_state structure.  It frees the\n * associated SSL-BIO, and the structures allocated for the \\link reliable\n * Reliability Layer\\endlink.\n *\n * @param ks           - A pointer to the \\c key_state structure to be\n *                       cleaned up.\n * @param clear        - Whether the memory allocated for the \\a ks object\n *                       should be overwritten with 0s.\n */\nstatic void\nkey_state_free(struct key_state *ks, bool clear)\n{\n    ks->state = S_UNDEF;\n\n    key_state_ssl_free(&ks->ks_ssl);\n\n    free_key_ctx_bi(&ks->crypto_options.key_ctx_bi);\n    free_epoch_key_ctx(&ks->crypto_options);\n    free_buf(&ks->plaintext_read_buf);\n    free_buf(&ks->plaintext_write_buf);\n    free_buf(&ks->ack_write_buf);\n    buffer_list_free(ks->paybuf);\n\n    reliable_free(ks->send_reliable);\n    reliable_free(ks->rec_reliable);\n\n    free(ks->rec_ack);\n    free(ks->lru_acks);\n    free(ks->key_src);\n\n    packet_id_free(&ks->crypto_options.packet_id);\n\n    key_state_rm_auth_control_files(&ks->plugin_auth);\n    key_state_rm_auth_control_files(&ks->script_auth);\n\n    if (clear)\n    {\n        secure_memzero(ks, sizeof(*ks));\n    }\n}\n\n/** @} name Functions for initialization and cleanup of key_state structures */\n\n/** @} addtogroup control_processor */\n\n\n/**\n * Returns whether or not the server should check for username/password\n *\n * @param session       The current TLS session\n *\n * @return              true if username and password verification is enabled,\n *                      false if not.\n */\nstatic inline bool\ntls_session_user_pass_enabled(struct tls_session *session)\n{\n    return (session->opt->auth_user_pass_verify_script\n            || plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)\n#ifdef ENABLE_MANAGEMENT\n            || management_enable_def_auth(management)\n#endif\n    );\n}\n\n\n/** @addtogroup control_processor\n *  @{ */\n\n/** @name Functions for initialization and cleanup of tls_session structures\n *  @{ */\n\n/**\n * Initialize a \\c tls_session structure.\n * @ingroup control_processor\n *\n * This function initializes a \\c tls_session structure.  This includes\n * generating a random session ID, and initializing the \\c KS_PRIMARY \\c\n * key_state in the \\c tls_session.key array.\n *\n * @param multi        - A pointer to the \\c tls_multi structure\n *                       associated with the \\a session argument.\n * @param session      - A pointer to the \\c tls_session structure to be\n *                       initialized.  This structure should already have\n *                       been allocated before calling this function.\n */\nstatic void\ntls_session_init(struct tls_multi *multi, struct tls_session *session)\n{\n    struct gc_arena gc = gc_new();\n\n    dmsg(D_TLS_DEBUG, \"TLS: tls_session_init: entry\");\n\n    CLEAR(*session);\n\n    /* Set options data to point to parent's option structure */\n    session->opt = &multi->opt;\n\n    /* Randomize session # if it is 0 */\n    while (!session_id_defined(&session->session_id))\n    {\n        session_id_random(&session->session_id);\n    }\n\n    /* Are we a TLS server or client? */\n    if (session->opt->server)\n    {\n        session->initial_opcode = P_CONTROL_HARD_RESET_SERVER_V2;\n    }\n    else\n    {\n        session->initial_opcode = session->opt->tls_crypt_v2 ? P_CONTROL_HARD_RESET_CLIENT_V3\n                                                             : P_CONTROL_HARD_RESET_CLIENT_V2;\n    }\n\n    /* Initialize control channel authentication parameters */\n    session->tls_wrap = session->opt->tls_wrap;\n    session->tls_wrap.work = alloc_buf(BUF_SIZE(&session->opt->frame));\n\n    /* initialize packet ID replay window for --tls-auth */\n    packet_id_init(&session->tls_wrap.opt.packet_id, session->opt->replay_window,\n                   session->opt->replay_time, \"TLS_WRAP\", session->key_id);\n\n    /* If we are using tls-crypt-v2 we manipulate the packet id to be (ab)used\n     * to indicate early protocol negotiation */\n    if (session->opt->tls_crypt_v2)\n    {\n        session->tls_wrap.opt.packet_id.send.time = now;\n        session->tls_wrap.opt.packet_id.send.id = EARLY_NEG_START;\n    }\n\n    /* load most recent packet-id to replay protect on --tls-auth */\n    packet_id_persist_load_obj(session->tls_wrap.opt.pid_persist, &session->tls_wrap.opt.packet_id);\n\n    key_state_init(session, &session->key[KS_PRIMARY]);\n\n    dmsg(D_TLS_DEBUG, \"TLS: tls_session_init: new session object, sid=%s\",\n         session_id_print(&session->session_id, &gc));\n\n    gc_free(&gc);\n}\n\n/**\n * Clean up a \\c tls_session structure.\n * @ingroup control_processor\n *\n * This function cleans up a \\c tls_session structure.  This includes\n * cleaning up all associated \\c key_state structures.\n *\n * @param session      - A pointer to the \\c tls_session structure to be\n *                       cleaned up.\n * @param clear        - Whether the memory allocated for the \\a session\n *                       object should be overwritten with 0s. This\n *                       implicitly sets many states to 0/false,\n *                       e.g. the validity of the keys in the structure\n *\n */\nstatic void\ntls_session_free(struct tls_session *session, bool clear)\n{\n    tls_wrap_free(&session->tls_wrap);\n    tls_wrap_free(&session->tls_wrap_reneg);\n\n    for (size_t i = 0; i < KS_SIZE; ++i)\n    {\n        /* we don't need clear=true for this call since\n         * the structs are part of session and get cleared\n         * as part of session */\n        key_state_free(&session->key[i], false);\n    }\n\n    free(session->common_name);\n\n    cert_hash_free(session->cert_hash_set);\n\n    if (clear)\n    {\n        secure_memzero(session, sizeof(*session));\n    }\n}\n\n/** @} name Functions for initialization and cleanup of tls_session structures */\n\n/** @} addtogroup control_processor */\n\n\nstatic void\nmove_session(struct tls_multi *multi, int dest, int src, bool reinit_src)\n{\n    msg(D_TLS_DEBUG_LOW, \"TLS: move_session: dest=%s src=%s reinit_src=%d\",\n        session_index_name(dest), session_index_name(src), reinit_src);\n    ASSERT(src != dest);\n    ASSERT(src >= 0 && src < TM_SIZE);\n    ASSERT(dest >= 0 && dest < TM_SIZE);\n    tls_session_free(&multi->session[dest], false);\n    multi->session[dest] = multi->session[src];\n\n    if (reinit_src)\n    {\n        tls_session_init(multi, &multi->session[src]);\n    }\n    else\n    {\n        secure_memzero(&multi->session[src], sizeof(multi->session[src]));\n    }\n\n    dmsg(D_TLS_DEBUG, \"TLS: move_session: exit\");\n}\n\nstatic void\nreset_session(struct tls_multi *multi, struct tls_session *session)\n{\n    tls_session_free(session, false);\n    tls_session_init(multi, session);\n}\n\n/*\n * Used to determine in how many seconds we should be\n * called again.\n */\nstatic inline void\ncompute_earliest_wakeup(interval_t *earliest, time_t seconds_from_now)\n{\n    if (seconds_from_now < *earliest)\n    {\n        *earliest = (interval_t)seconds_from_now;\n    }\n    if (*earliest < 0)\n    {\n        *earliest = 0;\n    }\n}\n\n/*\n * Return true if \"lame duck\" or retiring key has expired and can\n * no longer be used.\n */\nstatic inline bool\nlame_duck_must_die(const struct tls_session *session, interval_t *wakeup)\n{\n    const struct key_state *lame = &session->key[KS_LAME_DUCK];\n    if (lame->state >= S_INITIAL)\n    {\n        ASSERT(lame->must_die); /* a lame duck key must always have an expiration */\n        if (now < lame->must_die)\n        {\n            compute_earliest_wakeup(wakeup, lame->must_die - now);\n            return false;\n        }\n        else\n        {\n            return true;\n        }\n    }\n    else if (lame->state == S_ERROR)\n    {\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\nstruct tls_multi *\ntls_multi_init(struct tls_options *tls_options)\n{\n    struct tls_multi *ret;\n\n    ALLOC_OBJ_CLEAR(ret, struct tls_multi);\n\n    /* get command line derived options */\n    ret->opt = *tls_options;\n    ret->dco_peer_id = -1;\n    ret->peer_id = MAX_PEER_ID;\n\n    return ret;\n}\n\nvoid\ntls_multi_init_finalize(struct tls_multi *multi, int tls_mtu)\n{\n    tls_init_control_channel_frame_parameters(&multi->opt.frame, tls_mtu);\n    /* initialize the active and untrusted sessions */\n\n    tls_session_init(multi, &multi->session[TM_ACTIVE]);\n    tls_session_init(multi, &multi->session[TM_INITIAL]);\n}\n\n/*\n * Initialize and finalize a standalone tls-auth verification object.\n */\n\nstruct tls_auth_standalone *\ntls_auth_standalone_init(struct tls_options *tls_options, struct gc_arena *gc)\n{\n    struct tls_auth_standalone *tas;\n\n    ALLOC_OBJ_CLEAR_GC(tas, struct tls_auth_standalone, gc);\n\n    tas->tls_wrap = tls_options->tls_wrap;\n\n    /*\n     * Standalone tls-auth is in read-only mode with respect to TLS\n     * control channel state.  After we build a new client instance\n     * object, we will process this session-initiating packet for real.\n     */\n    tas->tls_wrap.opt.flags |= CO_IGNORE_PACKET_ID;\n\n    /* get initial frame parms, still need to finalize */\n    tas->frame = tls_options->frame;\n\n    packet_id_init(&tas->tls_wrap.opt.packet_id, tls_options->replay_window,\n                   tls_options->replay_time, \"TAS\", 0);\n\n    return tas;\n}\n\nvoid\ntls_auth_standalone_free(struct tls_auth_standalone *tas)\n{\n    if (!tas)\n    {\n        return;\n    }\n\n    packet_id_free(&tas->tls_wrap.opt.packet_id);\n}\n\n/*\n * Set local and remote option compatibility strings.\n * Used to verify compatibility of local and remote option\n * sets.\n */\nvoid\ntls_multi_init_set_options(struct tls_multi *multi, const char *local, const char *remote)\n{\n    /* initialize options string */\n    multi->opt.local_options = local;\n    multi->opt.remote_options = remote;\n}\n\n/*\n * Cleanup a tls_multi structure and free associated memory allocations.\n */\nvoid\ntls_multi_free(struct tls_multi *multi, bool clear)\n{\n    ASSERT(multi);\n\n    auth_set_client_reason(multi, NULL);\n\n    free(multi->peer_info);\n    free(multi->locked_cn);\n    free(multi->locked_username);\n    free(multi->locked_original_username);\n\n    cert_hash_free(multi->locked_cert_hash_set);\n\n    wipe_auth_token(multi);\n\n    free(multi->remote_ciphername);\n\n    for (int i = 0; i < TM_SIZE; ++i)\n    {\n        tls_session_free(&multi->session[i], false);\n    }\n\n    if (clear)\n    {\n        secure_memzero(multi, sizeof(*multi));\n    }\n\n    free(multi);\n}\n\n/*\n * For debugging, print contents of key_source2 structure.\n */\n\nstatic void\nkey_source_print(const struct key_source *k, const char *prefix)\n{\n    struct gc_arena gc = gc_new();\n\n    VALGRIND_MAKE_READABLE((void *)k->pre_master, sizeof(k->pre_master));\n    VALGRIND_MAKE_READABLE((void *)k->random1, sizeof(k->random1));\n    VALGRIND_MAKE_READABLE((void *)k->random2, sizeof(k->random2));\n\n    dmsg(D_SHOW_KEY_SOURCE, \"%s pre_master: %s\", prefix,\n         format_hex(k->pre_master, sizeof(k->pre_master), 0, &gc));\n    dmsg(D_SHOW_KEY_SOURCE, \"%s random1: %s\", prefix,\n         format_hex(k->random1, sizeof(k->random1), 0, &gc));\n    dmsg(D_SHOW_KEY_SOURCE, \"%s random2: %s\", prefix,\n         format_hex(k->random2, sizeof(k->random2), 0, &gc));\n\n    gc_free(&gc);\n}\n\nstatic void\nkey_source2_print(const struct key_source2 *k)\n{\n    key_source_print(&k->client, \"Client\");\n    key_source_print(&k->server, \"Server\");\n}\n\nstatic bool\nopenvpn_PRF(const uint8_t *secret, size_t secret_len, const char *label, const uint8_t *client_seed,\n            size_t client_seed_len, const uint8_t *server_seed, size_t server_seed_len,\n            const struct session_id *client_sid, const struct session_id *server_sid,\n            uint8_t *output, size_t output_len)\n{\n    /* concatenate seed components */\n\n    struct buffer seed =\n        alloc_buf(strlen(label) + client_seed_len + server_seed_len + SID_SIZE * 2);\n\n    ASSERT(buf_write(&seed, label, strlen(label)));\n    ASSERT(buf_write(&seed, client_seed, client_seed_len));\n    ASSERT(buf_write(&seed, server_seed, server_seed_len));\n\n    if (client_sid)\n    {\n        ASSERT(buf_write(&seed, client_sid->id, SID_SIZE));\n    }\n    if (server_sid)\n    {\n        ASSERT(buf_write(&seed, server_sid->id, SID_SIZE));\n    }\n\n    /* compute PRF */\n    bool ret = ssl_tls1_PRF(BPTR(&seed), BLENZ(&seed), secret, secret_len, output, output_len);\n\n    buf_clear(&seed);\n    free_buf(&seed);\n\n    VALGRIND_MAKE_READABLE((void *)output, output_len);\n    return ret;\n}\n\nstatic void\ninit_epoch_keys(struct key_state *ks, struct tls_multi *multi, const struct key_type *key_type,\n                bool server, struct key2 *key2)\n{\n    /* For now we hardcode this to be 16 for the software based data channel\n     * DCO based implementations/HW implementation might adjust this number\n     * based on their expected speed */\n    const uint8_t future_key_count = 16;\n\n    int key_direction = server ? KEY_DIRECTION_INVERSE : KEY_DIRECTION_NORMAL;\n    struct key_direction_state kds;\n    key_direction_state_init(&kds, key_direction);\n\n    struct crypto_options *co = &ks->crypto_options;\n\n    /* For the epoch key we use the first 32 bytes of key2 cipher keys\n     * for the  initial secret */\n    struct epoch_key e1_send = { 0 };\n    e1_send.epoch = 1;\n    memcpy(&e1_send.epoch_key, key2->keys[kds.out_key].cipher, sizeof(e1_send.epoch_key));\n\n    struct epoch_key e1_recv = { 0 };\n    e1_recv.epoch = 1;\n    memcpy(&e1_recv.epoch_key, key2->keys[kds.in_key].cipher, sizeof(e1_recv.epoch_key));\n\n    /* DCO implementations have two choices at this point.\n     *\n     * a) (more likely) they probably to pass E1 directly to kernel\n     * space at this point and do all the other key derivation in kernel\n     *\n     * b) They let userspace do the key derivation and pass all the individual\n     * keys to the DCO layer.\n     * */\n    epoch_init_key_ctx(co, key_type, &e1_send, &e1_recv, future_key_count);\n\n    secure_memzero(&e1_send, sizeof(e1_send));\n    secure_memzero(&e1_recv, sizeof(e1_recv));\n}\n\nstatic void\ninit_key_contexts(struct key_state *ks, struct tls_multi *multi, const struct key_type *key_type,\n                  bool server, struct key2 *key2, bool dco_enabled)\n{\n    struct key_ctx_bi *key = &ks->crypto_options.key_ctx_bi;\n\n    /* Initialize key contexts */\n    int key_direction = server ? KEY_DIRECTION_INVERSE : KEY_DIRECTION_NORMAL;\n\n    if (dco_enabled)\n    {\n        if (key->encrypt.hmac)\n        {\n            msg(M_FATAL, \"FATAL: DCO does not support --auth\");\n        }\n\n        int ret = init_key_dco_bi(multi, ks, key2, key_direction, key_type->cipher, server);\n        if (ret < 0)\n        {\n            msg(M_FATAL, \"Impossible to install key material in DCO: %s\", strerror(-ret));\n        }\n\n        /* encrypt/decrypt context are unused with DCO */\n        CLEAR(key->encrypt);\n        CLEAR(key->decrypt);\n        key->initialized = true;\n    }\n    else if (multi->opt.crypto_flags & CO_EPOCH_DATA_KEY_FORMAT)\n    {\n        if (!cipher_kt_mode_aead(key_type->cipher))\n        {\n            msg(M_FATAL,\n                \"AEAD cipher (currently %s) \"\n                \"required for epoch data format.\",\n                cipher_kt_name(key_type->cipher));\n        }\n        init_epoch_keys(ks, multi, key_type, server, key2);\n    }\n    else\n    {\n        init_key_ctx_bi(key, key2, key_direction, key_type, \"Data Channel\");\n    }\n}\n\nstatic bool\ngenerate_key_expansion_tls_export(struct tls_session *session, struct key2 *key2)\n{\n    if (!key_state_export_keying_material(session, EXPORT_KEY_DATA_LABEL,\n                                          strlen(EXPORT_KEY_DATA_LABEL), key2->keys,\n                                          sizeof(key2->keys)))\n    {\n        return false;\n    }\n    key2->n = 2;\n\n    return true;\n}\n\nstatic bool\ngenerate_key_expansion_openvpn_prf(const struct tls_session *session, struct key2 *key2)\n{\n    uint8_t master[48] = { 0 };\n\n    const struct key_state *ks = &session->key[KS_PRIMARY];\n    const struct key_source2 *key_src = ks->key_src;\n\n    const struct session_id *client_sid =\n        session->opt->server ? &ks->session_id_remote : &session->session_id;\n    const struct session_id *server_sid =\n        !session->opt->server ? &ks->session_id_remote : &session->session_id;\n\n    /* debugging print of source key material */\n    key_source2_print(key_src);\n\n    /* compute master secret */\n    if (!openvpn_PRF(key_src->client.pre_master, sizeof(key_src->client.pre_master),\n                     KEY_EXPANSION_ID \" master secret\", key_src->client.random1,\n                     sizeof(key_src->client.random1), key_src->server.random1,\n                     sizeof(key_src->server.random1), NULL, NULL, master, sizeof(master)))\n    {\n        return false;\n    }\n\n    /* compute key expansion */\n    if (!openvpn_PRF(master, sizeof(master), KEY_EXPANSION_ID \" key expansion\",\n                     key_src->client.random2, sizeof(key_src->client.random2),\n                     key_src->server.random2, sizeof(key_src->server.random2), client_sid,\n                     server_sid, (uint8_t *)key2->keys, sizeof(key2->keys)))\n    {\n        return false;\n    }\n    secure_memzero(&master, sizeof(master));\n\n    key2->n = 2;\n\n    return true;\n}\n\n/*\n * Using source entropy from local and remote hosts, mix into\n * master key.\n */\nstatic bool\ngenerate_key_expansion(struct tls_multi *multi, struct key_state *ks, struct tls_session *session)\n{\n    struct key_ctx_bi *key = &ks->crypto_options.key_ctx_bi;\n    bool ret = false;\n    struct key2 key2;\n\n    if (key->initialized)\n    {\n        msg(D_TLS_ERRORS, \"TLS Error: key already initialized\");\n        goto exit;\n    }\n\n    bool server = session->opt->server;\n\n    if (session->opt->crypto_flags & CO_USE_TLS_KEY_MATERIAL_EXPORT)\n    {\n        if (!generate_key_expansion_tls_export(session, &key2))\n        {\n            msg(D_TLS_ERRORS, \"TLS Error: Keying material export failed\");\n            goto exit;\n        }\n    }\n    else\n    {\n        if (!generate_key_expansion_openvpn_prf(session, &key2))\n        {\n            msg(D_TLS_ERRORS, \"TLS Error: PRF calculation failed. Your system \"\n                              \"might not support the old TLS 1.0 PRF calculation anymore or \"\n                              \"the policy does not allow it (e.g. running in FIPS mode). \"\n                              \"The peer did not announce support for the modern TLS Export \"\n                              \"feature that replaces the TLS 1.0 PRF (requires OpenVPN \"\n                              \"2.6.x or higher)\");\n            goto exit;\n        }\n    }\n\n    key2_print(&key2, &session->opt->key_type, \"Master Encrypt\", \"Master Decrypt\");\n\n    /* check for weak keys */\n    for (int i = 0; i < 2; ++i)\n    {\n        if (!check_key(&key2.keys[i], &session->opt->key_type))\n        {\n            msg(D_TLS_ERRORS, \"TLS Error: Bad dynamic key generated\");\n            goto exit;\n        }\n    }\n\n    init_key_contexts(ks, multi, &session->opt->key_type, server, &key2, session->opt->dco_enabled);\n    ret = true;\n\nexit:\n    secure_memzero(&key2, sizeof(key2));\n\n    return ret;\n}\n\n/**\n * Generate data channel keys for the supplied TLS session.\n *\n * This erases the source material used to generate the data channel keys, and\n * can thus be called only once per session.\n */\nbool\ntls_session_generate_data_channel_keys(struct tls_multi *multi, struct tls_session *session)\n{\n    bool ret = false;\n    struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */\n\n    if (ks->authenticated <= KS_AUTH_FALSE)\n    {\n        msg(D_TLS_ERRORS, \"TLS Error: key_state not authenticated\");\n        goto cleanup;\n    }\n\n    ks->crypto_options.flags = session->opt->crypto_flags;\n\n    if (!generate_key_expansion(multi, ks, session))\n    {\n        msg(D_TLS_ERRORS, \"TLS Error: generate_key_expansion failed\");\n        goto cleanup;\n    }\n    tls_limit_reneg_bytes(session->opt->key_type.cipher, &session->opt->renegotiate_bytes);\n\n    session->opt->aead_usage_limit = tls_get_limit_aead(session->opt->key_type.cipher);\n\n    /* set the state of the keys for the session to generated */\n    ks->state = S_GENERATED_KEYS;\n\n    ret = true;\ncleanup:\n    secure_memzero(ks->key_src, sizeof(*ks->key_src));\n    return ret;\n}\n\nbool\ntls_session_update_crypto_params_do_work(struct tls_multi *multi, struct tls_session *session,\n                                         struct options *options, struct frame *frame,\n                                         struct frame *frame_fragment, struct link_socket_info *lsi,\n                                         dco_context_t *dco)\n{\n    if (session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized)\n    {\n        /* keys already generated, nothing to do */\n        return true;\n    }\n\n    init_key_type(&session->opt->key_type, options->ciphername, options->authname, true, true);\n\n    bool packet_id_long_form = cipher_kt_mode_ofb_cfb(session->opt->key_type.cipher);\n    session->opt->crypto_flags &= ~(CO_PACKET_ID_LONG_FORM);\n    if (packet_id_long_form)\n    {\n        session->opt->crypto_flags |= CO_PACKET_ID_LONG_FORM;\n    }\n\n    frame_calculate_dynamic(frame, &session->opt->key_type, options, lsi);\n\n    frame_print(frame, D_MTU_INFO, \"Data Channel MTU parms\");\n\n    /*\n     * mssfix uses data channel framing, which at this point contains\n     * actual overhead. Fragmentation logic uses frame_fragment, which\n     * still contains worst case overhead. Replace it with actual overhead\n     * to prevent unneeded fragmentation.\n     */\n\n    if (frame_fragment)\n    {\n        frame_calculate_dynamic(frame_fragment, &session->opt->key_type, options, lsi);\n        frame_print(frame_fragment, D_MTU_INFO, \"Fragmentation MTU parms\");\n    }\n\n    if (session->key[KS_PRIMARY].key_id == 0\n        && session->opt->crypto_flags & CO_USE_DYNAMIC_TLS_CRYPT)\n    {\n        /* If dynamic tls-crypt has been negotiated, and we are on the\n         * first session (key_id = 0), generate a tls-crypt key for the\n         * following renegotiations */\n        if (!tls_session_generate_dynamic_tls_crypt_key(session))\n        {\n            return false;\n        }\n    }\n\n    if (dco_enabled(options))\n    {\n        /* dco_set_peer() must be called if either keepalive or\n         * mssfix are set to update in-kernel config */\n        if (options->ping_send_timeout || frame->mss_fix)\n        {\n            int ret = dco_set_peer(dco, multi->dco_peer_id, options->ping_send_timeout,\n                                   options->ping_rec_timeout, frame->mss_fix);\n            if (ret < 0)\n            {\n                msg(D_DCO, \"Cannot set DCO peer parameters for peer (id=%u): %s\",\n                    multi->dco_peer_id, strerror(-ret));\n                return false;\n            }\n        }\n    }\n    return tls_session_generate_data_channel_keys(multi, session);\n}\n\nbool\ntls_session_update_crypto_params(struct tls_multi *multi, struct tls_session *session,\n                                 struct options *options, struct frame *frame,\n                                 struct frame *frame_fragment, struct link_socket_info *lsi,\n                                 dco_context_t *dco)\n{\n    if (!check_session_cipher(session, options))\n    {\n        return false;\n    }\n\n    /* Import crypto settings that might be set by pull/push */\n    session->opt->crypto_flags |= options->imported_protocol_flags;\n\n    return tls_session_update_crypto_params_do_work(multi, session, options, frame, frame_fragment,\n                                                    lsi, dco);\n}\n\n\nstatic bool\nrandom_bytes_to_buf(struct buffer *buf, uint8_t *out, int outlen)\n{\n    if (!rand_bytes(out, outlen))\n    {\n        msg(M_FATAL,\n            \"ERROR: Random number generator cannot obtain entropy for key generation [SSL]\");\n    }\n    if (!buf_write(buf, out, outlen))\n    {\n        return false;\n    }\n    return true;\n}\n\nstatic bool\nkey_source2_randomize_write(struct key_source2 *k2, struct buffer *buf, bool server)\n{\n    struct key_source *k = &k2->client;\n    if (server)\n    {\n        k = &k2->server;\n    }\n\n    CLEAR(*k);\n\n    if (!server)\n    {\n        if (!random_bytes_to_buf(buf, k->pre_master, sizeof(k->pre_master)))\n        {\n            return false;\n        }\n    }\n\n    if (!random_bytes_to_buf(buf, k->random1, sizeof(k->random1)))\n    {\n        return false;\n    }\n    if (!random_bytes_to_buf(buf, k->random2, sizeof(k->random2)))\n    {\n        return false;\n    }\n\n    return true;\n}\n\nstatic int\nkey_source2_read(struct key_source2 *k2, struct buffer *buf, bool server)\n{\n    struct key_source *k = &k2->client;\n\n    if (!server)\n    {\n        k = &k2->server;\n    }\n\n    CLEAR(*k);\n\n    if (server)\n    {\n        if (!buf_read(buf, k->pre_master, sizeof(k->pre_master)))\n        {\n            return 0;\n        }\n    }\n\n    if (!buf_read(buf, k->random1, sizeof(k->random1)))\n    {\n        return 0;\n    }\n    if (!buf_read(buf, k->random2, sizeof(k->random2)))\n    {\n        return 0;\n    }\n\n    return 1;\n}\n\nstatic void\nflush_payload_buffer(struct key_state *ks)\n{\n    struct buffer *b;\n\n    while ((b = buffer_list_peek(ks->paybuf)))\n    {\n        key_state_write_plaintext_const(&ks->ks_ssl, b->data, b->len);\n        buffer_list_pop(ks->paybuf);\n    }\n}\n\n/*\n * Move the active key to the lame duck key and reinitialize the\n * active key.\n */\nstatic void\nkey_state_soft_reset(struct tls_session *session)\n{\n    struct key_state *ks = &session->key[KS_PRIMARY];        /* primary key */\n    struct key_state *ks_lame = &session->key[KS_LAME_DUCK]; /* retiring key */\n\n    ks->must_die = now + session->opt->transition_window;    /* remaining lifetime of old key */\n    key_state_free(ks_lame, false);\n    *ks_lame = *ks;\n\n    key_state_init(session, ks);\n    ks->session_id_remote = ks_lame->session_id_remote;\n    ks->remote_addr = ks_lame->remote_addr;\n}\n\nvoid\ntls_session_soft_reset(struct tls_multi *tls_multi)\n{\n    key_state_soft_reset(&tls_multi->session[TM_ACTIVE]);\n}\n\n/*\n * Read/write strings from/to a struct buffer with a u16 length prefix.\n */\n\nstatic bool\nwrite_empty_string(struct buffer *buf)\n{\n    if (!buf_write_u16(buf, 0))\n    {\n        return false;\n    }\n    return true;\n}\n\nstatic bool\nwrite_string(struct buffer *buf, const char *str, const int maxlen)\n{\n    const size_t len = strlen(str) + 1;\n    const size_t real_maxlen = (maxlen >= 0 && maxlen <= UINT16_MAX) ? (size_t)maxlen : UINT16_MAX;\n    if (len > real_maxlen)\n    {\n        return false;\n    }\n    if (!buf_write_u16(buf, (uint16_t)len))\n    {\n        return false;\n    }\n    if (!buf_write(buf, str, len))\n    {\n        return false;\n    }\n    return true;\n}\n\n/**\n * Read a string that is encoded as a 2 byte header with the length from the\n * buffer \\c buf. Will return the non-negative value if reading was successful.\n * The returned value will include the trailing 0 byte.\n *\n * If the message is over the capacity or could not be read\n * it will return the negative length that was in the\n * header and try to skip the string. If the string cannot be skipped, the\n * buf will stay at the current position or position + 2\n */\nstatic int\nread_string(struct buffer *buf, char *str, const unsigned int capacity)\n{\n    const int len = buf_read_u16(buf);\n    if (len < 1 || len > (int)capacity)\n    {\n        buf_advance(buf, len);\n\n        /* will also return 0 for a no string being present */\n        return -len;\n    }\n    if (!buf_read(buf, str, len))\n    {\n        return -len;\n    }\n    str[len - 1] = '\\0';\n    return len;\n}\n\nstatic char *\nread_string_alloc(struct buffer *buf)\n{\n    const int len = buf_read_u16(buf);\n    char *str;\n\n    if (len < 1)\n    {\n        return NULL;\n    }\n    str = (char *)malloc(len);\n    check_malloc_return(str);\n    if (!buf_read(buf, str, len))\n    {\n        free(str);\n        return NULL;\n    }\n    str[len - 1] = '\\0';\n    return str;\n}\n\n/**\n * Prepares the IV_ and UV_ variables that are part of the\n * exchange to signal the peer's capabilities. The amount\n * of variables is determined by session->opt->push_peer_info_detail\n *\n *     0     nothing. Used on a TLS P2MP server side to send no information\n *           to the client\n *     1     minimal info needed for NCP in P2P mode\n *     2     when --pull is enabled, the \"default\" set of variables\n *     3     all information including MAC address and library versions\n *\n * @param buf       the buffer to write these variables to\n * @param session   the TLS session object\n * @return          true if no error was encountered\n */\nstatic bool\npush_peer_info(struct buffer *buf, struct tls_session *session)\n{\n    struct gc_arena gc = gc_new();\n    bool ret = false;\n    struct buffer out = alloc_buf_gc(512 * 3, &gc);\n\n    if (session->opt->push_peer_info_detail > 1)\n    {\n        /* push version */\n        buf_printf(&out, \"IV_VER=%s\\n\", PACKAGE_VERSION);\n\n        /* push platform */\n#if defined(TARGET_LINUX)\n        buf_printf(&out, \"IV_PLAT=linux\\n\");\n#elif defined(TARGET_SOLARIS)\n        buf_printf(&out, \"IV_PLAT=solaris\\n\");\n#elif defined(TARGET_OPENBSD)\n        buf_printf(&out, \"IV_PLAT=openbsd\\n\");\n#elif defined(TARGET_DARWIN)\n        buf_printf(&out, \"IV_PLAT=mac\\n\");\n#elif defined(TARGET_NETBSD)\n        buf_printf(&out, \"IV_PLAT=netbsd\\n\");\n#elif defined(TARGET_FREEBSD)\n        buf_printf(&out, \"IV_PLAT=freebsd\\n\");\n#elif defined(TARGET_ANDROID)\n        buf_printf(&out, \"IV_PLAT=android\\n\");\n#elif defined(_WIN32)\n        buf_printf(&out, \"IV_PLAT=win\\n\");\n#endif\n        /* Announce that we do not require strict sequence numbers with\n         * TCP. (TCP non-linear) */\n        buf_printf(&out, \"IV_TCPNL=1\\n\");\n    }\n\n    /* These are the IV variable that are sent to peers in p2p mode */\n    if (session->opt->push_peer_info_detail > 0)\n    {\n        /* support for P_DATA_V2 */\n        int iv_proto = IV_PROTO_DATA_V2;\n\n        /* support for the latest --dns option */\n        iv_proto |= IV_PROTO_DNS_OPTION_V2;\n\n        /* support for exit notify via control channel */\n        iv_proto |= IV_PROTO_CC_EXIT_NOTIFY;\n\n        /* currently push-update is not supported when DCO is enabled */\n        if (!session->opt->dco_enabled)\n        {\n            /* support push-updates */\n            iv_proto |= IV_PROTO_PUSH_UPDATE;\n        }\n\n        if (session->opt->pull)\n        {\n            /* support for receiving push_reply before sending\n             * push request, also signal that the client wants\n             * to get push-reply messages without requiring a round\n             * trip for a push request message*/\n            iv_proto |= IV_PROTO_REQUEST_PUSH;\n\n            /* Support keywords in the AUTH_PENDING control message */\n            iv_proto |= IV_PROTO_AUTH_PENDING_KW;\n\n            /* support for AUTH_FAIL,TEMP control message */\n            iv_proto |= IV_PROTO_AUTH_FAIL_TEMP;\n\n            /* support for tun-mtu as part of the push message */\n            buf_printf(&out, \"IV_MTU=%d\\n\", session->opt->frame.tun_max_mtu);\n        }\n\n        /* support for Negotiable Crypto Parameters */\n        if (session->opt->mode == MODE_SERVER || session->opt->pull)\n        {\n            if (tls_item_in_cipher_list(\"AES-128-GCM\", session->opt->config_ncp_ciphers)\n                && tls_item_in_cipher_list(\"AES-256-GCM\", session->opt->config_ncp_ciphers))\n            {\n                buf_printf(&out, \"IV_NCP=2\\n\");\n            }\n        }\n        else\n        {\n            /* We are not using pull or p2mp server, instead do P2P NCP */\n            iv_proto |= IV_PROTO_NCP_P2P;\n        }\n\n        if (session->opt->data_epoch_supported)\n        {\n            iv_proto |= IV_PROTO_DATA_EPOCH;\n        }\n\n        buf_printf(&out, \"IV_CIPHERS=%s\\n\", session->opt->config_ncp_ciphers);\n\n        iv_proto |= IV_PROTO_TLS_KEY_EXPORT;\n        iv_proto |= IV_PROTO_DYN_TLS_CRYPT;\n\n        buf_printf(&out, \"IV_PROTO=%d\\n\", iv_proto);\n\n        if (session->opt->push_peer_info_detail > 1)\n        {\n            /* push compression status */\n#ifdef USE_COMP\n            comp_generate_peer_info_string(&session->opt->comp_options, &out);\n#endif\n        }\n\n        if (session->opt->push_peer_info_detail > 2)\n        {\n            /* push mac addr */\n            struct route_gateway_info rgi;\n            get_default_gateway(&rgi, 0, session->opt->net_ctx);\n            if (rgi.flags & RGI_HWADDR_DEFINED)\n            {\n                buf_printf(&out, \"IV_HWADDR=%s\\n\", format_hex_ex(rgi.hwaddr, 6, 0, 1, \":\", &gc));\n            }\n            buf_printf(&out, \"IV_SSL=%s\\n\", get_ssl_library_version());\n#if defined(_WIN32)\n            buf_printf(&out, \"IV_PLAT_VER=%s\\n\", win32_version_string(&gc));\n#else\n            struct utsname u;\n            uname(&u);\n            buf_printf(&out, \"IV_PLAT_VER=%s\\n\", u.release);\n#endif\n        }\n\n        if (session->opt->push_peer_info_detail > 1)\n        {\n            struct env_set *es = session->opt->es;\n            /* push env vars that begin with UV_, IV_PLAT_VER and IV_GUI_VER */\n            for (struct env_item *e = es->list; e != NULL; e = e->next)\n            {\n                if (e->string)\n                {\n                    if ((((strncmp(e->string, \"UV_\", 3) == 0\n                           || strncmp(e->string, \"IV_PLAT_VER=\", sizeof(\"IV_PLAT_VER=\") - 1) == 0)\n                          && session->opt->push_peer_info_detail > 2)\n                         || (strncmp(e->string, \"IV_GUI_VER=\", sizeof(\"IV_GUI_VER=\") - 1) == 0)\n                         || (strncmp(e->string, \"IV_SSO=\", sizeof(\"IV_SSO=\") - 1) == 0))\n                        && buf_safe(&out, strlen(e->string) + 1))\n                    {\n                        buf_printf(&out, \"%s\\n\", e->string);\n                    }\n                }\n            }\n        }\n\n        if (!write_string(buf, BSTR(&out), -1))\n        {\n            goto error;\n        }\n    }\n    else\n    {\n        if (!write_empty_string(buf)) /* no peer info */\n        {\n            goto error;\n        }\n    }\n    ret = true;\n\nerror:\n    gc_free(&gc);\n    return ret;\n}\n\n#ifdef USE_COMP\nstatic bool\nwrite_compat_local_options(struct buffer *buf, const char *options)\n{\n    struct gc_arena gc = gc_new();\n    const char *local_options = options_string_compat_lzo(options, &gc);\n    bool ret = write_string(buf, local_options, TLS_OPTIONS_LEN);\n    gc_free(&gc);\n    return ret;\n}\n#endif\n\n/**\n * Handle the writing of key data, peer-info, username/password, OCC\n * to the TLS control channel (cleartext).\n */\nstatic bool\nkey_method_2_write(struct buffer *buf, struct tls_multi *multi, struct tls_session *session)\n{\n    struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */\n\n    ASSERT(buf_init(buf, 0));\n\n    /* write a uint32 0 */\n    if (!buf_write_u32(buf, 0))\n    {\n        goto error;\n    }\n\n    /* write key_method + flags */\n    if (!buf_write_u8(buf, KEY_METHOD_2))\n    {\n        goto error;\n    }\n\n    /* write key source material */\n    if (!key_source2_randomize_write(ks->key_src, buf, session->opt->server))\n    {\n        goto error;\n    }\n\n    /* write options string */\n    {\n#ifdef USE_COMP\n        if (multi->remote_usescomp && session->opt->mode == MODE_SERVER\n            && multi->opt.comp_options.flags & COMP_F_MIGRATE)\n        {\n            if (!write_compat_local_options(buf, session->opt->local_options))\n            {\n                goto error;\n            }\n        }\n        else\n#endif\n            if (!write_string(buf, session->opt->local_options, TLS_OPTIONS_LEN))\n        {\n            goto error;\n        }\n    }\n\n    /* write username/password if specified or we are using a auth-token */\n    if (auth_user_pass_enabled || (auth_token.token_defined && auth_token.defined))\n    {\n#ifdef ENABLE_MANAGEMENT\n        auth_user_pass_setup(session->opt->auth_user_pass_file,\n                             session->opt->auth_user_pass_file_inline, session->opt->sci);\n#else\n        auth_user_pass_setup(session->opt->auth_user_pass_file,\n                             session->opt->auth_user_pass_file_inline, NULL);\n#endif\n        struct user_pass *up = &auth_user_pass;\n\n        /*\n         * If we have a valid auth-token, send that instead of real\n         * username/password\n         */\n        if (auth_token.token_defined && auth_token.defined)\n        {\n            up = &auth_token;\n        }\n        unprotect_user_pass(up);\n\n        if (!write_string(buf, up->username, -1))\n        {\n            goto error;\n        }\n        else if (!write_string(buf, up->password, -1))\n        {\n            goto error;\n        }\n        /* save username for auth-token which may get pushed later */\n        if (session->opt->pull && up != &auth_token)\n        {\n            unprotect_user_pass(&auth_token);\n            strncpynt(auth_token.username, up->username, USER_PASS_LEN);\n            protect_user_pass(&auth_token);\n        }\n        protect_user_pass(up);\n        /* respect auth-nocache */\n        purge_user_pass(&auth_user_pass, false);\n    }\n    else\n    {\n        if (!write_empty_string(buf)) /* no username */\n        {\n            goto error;\n        }\n        if (!write_empty_string(buf)) /* no password */\n        {\n            goto error;\n        }\n    }\n\n    if (!push_peer_info(buf, session))\n    {\n        goto error;\n    }\n\n    if (session->opt->server && session->opt->mode != MODE_SERVER && ks->key_id == 0)\n    {\n        /* tls-server option set and not P2MP server, so we\n         * are a P2P client running in tls-server mode */\n        p2p_mode_ncp(multi, session);\n    }\n\n    return true;\n\nerror:\n    msg(D_TLS_ERRORS, \"TLS Error: Key Method #2 write failed\");\n    secure_memzero(ks->key_src, sizeof(*ks->key_src));\n    return false;\n}\n\nstatic void\nexport_user_keying_material(struct tls_session *session)\n{\n    if (session->opt->ekm_size > 0)\n    {\n        const size_t size = session->opt->ekm_size;\n        struct gc_arena gc = gc_new();\n\n        unsigned char *ekm = gc_malloc(size, true, &gc);\n        if (key_state_export_keying_material(session, session->opt->ekm_label,\n                                             session->opt->ekm_label_size, ekm,\n                                             session->opt->ekm_size))\n        {\n            const size_t len = (size * 2) + 2;\n\n            const char *key = format_hex_ex(ekm, size, len, 0, NULL, &gc);\n            setenv_str(session->opt->es, \"exported_keying_material\", key);\n\n            dmsg(D_TLS_DEBUG_MED, \"%s: exported keying material: %s\", __func__, key);\n            secure_memzero(ekm, size);\n        }\n        else\n        {\n            msg(M_WARN, \"WARNING: Export keying material failed!\");\n            setenv_del(session->opt->es, \"exported_keying_material\");\n        }\n        gc_free(&gc);\n    }\n}\n\n/**\n * Handle reading key data, peer-info, username/password, OCC\n * from the TLS control channel (cleartext).\n */\nstatic bool\nkey_method_2_read(struct buffer *buf, struct tls_multi *multi, struct tls_session *session)\n{\n    struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */\n\n    struct gc_arena gc = gc_new();\n    char *options;\n    struct user_pass *up = NULL;\n\n    /* allocate temporary objects */\n    ALLOC_ARRAY_CLEAR_GC(options, char, TLS_OPTIONS_LEN, &gc);\n\n    /* discard leading uint32 */\n    if (!buf_advance(buf, 4))\n    {\n        msg(D_TLS_ERRORS, \"TLS ERROR: Plaintext buffer too short (%d bytes).\", buf->len);\n        goto error;\n    }\n\n    /* get key method */\n    int key_method_flags = buf_read_u8(buf);\n    if ((key_method_flags & KEY_METHOD_MASK) != 2)\n    {\n        msg(D_TLS_ERRORS, \"TLS ERROR: Unknown key_method/flags=%d received from remote host\",\n            key_method_flags);\n        goto error;\n    }\n\n    /* get key source material (not actual keys yet) */\n    if (!key_source2_read(ks->key_src, buf, session->opt->server))\n    {\n        msg(D_TLS_ERRORS,\n            \"TLS Error: Error reading remote data channel key source entropy from plaintext buffer\");\n        goto error;\n    }\n\n    /* get options */\n    if (read_string(buf, options, TLS_OPTIONS_LEN) < 0)\n    {\n        msg(D_TLS_ERRORS, \"TLS Error: Failed to read required OCC options string\");\n        goto error;\n    }\n\n    ks->authenticated = KS_AUTH_FALSE;\n\n    /* always extract username + password fields from buf, even if not\n     * authenticating for it, because otherwise we can't get at the\n     * peer_info data which follows behind\n     */\n    ALLOC_OBJ_CLEAR_GC(up, struct user_pass, &gc);\n    int username_len = read_string(buf, up->username, USER_PASS_LEN);\n    int password_len = read_string(buf, up->password, USER_PASS_LEN);\n\n    /* get peer info from control channel */\n    free(multi->peer_info);\n    multi->peer_info = read_string_alloc(buf);\n    if (multi->peer_info)\n    {\n        output_peer_info_env(session->opt->es, multi->peer_info);\n    }\n\n    free(multi->remote_ciphername);\n    multi->remote_ciphername = options_string_extract_option(options, \"cipher\", NULL);\n    multi->remote_usescomp = strstr(options, \",comp-lzo,\");\n\n    /* In OCC we send '[null-cipher]' instead 'none' */\n    if (multi->remote_ciphername && strcmp(multi->remote_ciphername, \"[null-cipher]\") == 0)\n    {\n        free(multi->remote_ciphername);\n        multi->remote_ciphername = string_alloc(\"none\", NULL);\n    }\n\n    if (username_len < 0 || password_len < 0)\n    {\n        msg(D_TLS_ERRORS, \"TLS Error: Username (%d) or password (%d) too long\", abs(username_len),\n            abs(password_len));\n        auth_set_client_reason(multi, \"Username or password is too long. \"\n                                      \"Maximum length is 128 bytes\");\n\n        /* treat the same as failed username/password and do not error\n         * out (goto error) to sent an AUTH_FAILED back to the client */\n        ks->authenticated = KS_AUTH_FALSE;\n    }\n    else if (tls_session_user_pass_enabled(session))\n    {\n        /* Perform username/password authentication */\n        if (!username_len || !password_len)\n        {\n            CLEAR(*up);\n            if (!(session->opt->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL))\n            {\n                msg(D_TLS_ERRORS, \"TLS Error: Auth Username/Password was not provided by peer\");\n                goto error;\n            }\n        }\n\n        verify_user_pass(up, multi, session);\n    }\n    else\n    {\n        /* Session verification should have occurred during TLS negotiation*/\n        if (!session->verified)\n        {\n            msg(D_TLS_ERRORS, \"TLS Error: Certificate verification failed (key-method 2)\");\n            goto error;\n        }\n        ks->authenticated = KS_AUTH_TRUE;\n    }\n\n    /* clear username and password from memory */\n    secure_memzero(up, sizeof(*up));\n\n    /* Perform final authentication checks */\n    if (ks->authenticated > KS_AUTH_FALSE)\n    {\n        verify_final_auth_checks(multi, session);\n    }\n\n    /* check options consistency */\n    if (!options_cmp_equal(options, session->opt->remote_options))\n    {\n        const char *remote_options = session->opt->remote_options;\n#ifdef USE_COMP\n        if (multi->opt.comp_options.flags & COMP_F_MIGRATE && multi->remote_usescomp)\n        {\n            msg(D_PUSH, \"Note: 'compress migrate' detected remote peer \"\n                        \"with compression enabled.\");\n            remote_options = options_string_compat_lzo(remote_options, &gc);\n        }\n#endif\n\n        options_warning(options, remote_options);\n    }\n\n    buf_clear(buf);\n\n    /*\n     * Call OPENVPN_PLUGIN_TLS_FINAL plugin if defined, for final\n     * veto opportunity over authentication decision.\n     */\n    if ((ks->authenticated > KS_AUTH_FALSE)\n        && plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_TLS_FINAL))\n    {\n        export_user_keying_material(session);\n\n        if (plugin_call(session->opt->plugins, OPENVPN_PLUGIN_TLS_FINAL, NULL, NULL,\n                        session->opt->es)\n            != OPENVPN_PLUGIN_FUNC_SUCCESS)\n        {\n            ks->authenticated = KS_AUTH_FALSE;\n        }\n\n        setenv_del(session->opt->es, \"exported_keying_material\");\n    }\n\n    if (!session->opt->server && !session->opt->pull && ks->key_id == 0)\n    {\n        /* We are a p2p tls-client without pull, enable common\n         * protocol options */\n        p2p_mode_ncp(multi, session);\n    }\n\n    gc_free(&gc);\n    return true;\n\nerror:\n    ks->authenticated = KS_AUTH_FALSE;\n    secure_memzero(ks->key_src, sizeof(*ks->key_src));\n    if (up)\n    {\n        secure_memzero(up, sizeof(*up));\n    }\n    buf_clear(buf);\n    gc_free(&gc);\n    return false;\n}\n\nstatic int\nauth_deferred_expire_window(const struct tls_options *o)\n{\n    int ret = o->handshake_window;\n    const int r2 = o->renegotiate_seconds / 2;\n\n    if (o->renegotiate_seconds && r2 < ret)\n    {\n        ret = r2;\n    }\n    return ret;\n}\n\n/**\n * Move the session from S_INITIAL to S_PRE_START. This will also generate\n * the initial message based on ks->initial_opcode\n *\n * @return if the state change was succesful\n */\nstatic bool\nsession_move_pre_start(const struct tls_session *session, struct key_state *ks,\n                       bool skip_initial_send)\n{\n    struct buffer *buf = reliable_get_buf_output_sequenced(ks->send_reliable);\n    if (!buf)\n    {\n        return false;\n    }\n\n    ks->initial = now;\n    ks->must_negotiate = now + session->opt->handshake_window;\n    ks->auth_deferred_expire = now + auth_deferred_expire_window(session->opt);\n\n    /* null buffer */\n    reliable_mark_active_outgoing(ks->send_reliable, buf, ks->initial_opcode);\n\n    /* If we want to skip sending the initial handshake packet we still generate\n     * it to increase internal counters etc. but immediately mark it as done */\n    if (skip_initial_send)\n    {\n        reliable_mark_deleted(ks->send_reliable, buf);\n    }\n    INCR_GENERATED;\n\n    ks->state = skip_initial_send ? S_PRE_START_SKIP : S_PRE_START;\n\n    struct gc_arena gc = gc_new();\n    dmsg(D_TLS_DEBUG, \"TLS: Initial Handshake, sid=%s\",\n         session_id_print(&session->session_id, &gc));\n    gc_free(&gc);\n\n#ifdef ENABLE_MANAGEMENT\n    if (management && ks->initial_opcode != P_CONTROL_SOFT_RESET_V1)\n    {\n        management_set_state(management, OPENVPN_STATE_WAIT, NULL, NULL, NULL, NULL, NULL);\n    }\n#endif\n    return true;\n}\n\n/**\n * Moves the key to state to S_ACTIVE and also advances the multi_state state\n * machine if this is the initial connection.\n */\nstatic void\nsession_move_active(struct tls_multi *multi, struct tls_session *session,\n                    struct link_socket_info *to_link_socket_info, struct key_state *ks)\n{\n    dmsg(D_TLS_DEBUG_MED, \"STATE S_ACTIVE\");\n\n    ks->established = now;\n    if (check_debug_level(D_HANDSHAKE))\n    {\n        print_details(&ks->ks_ssl, \"Control Channel:\");\n    }\n    ks->state = S_ACTIVE;\n    /* Cancel negotiation timeout */\n    ks->must_negotiate = 0;\n    INCR_SUCCESS;\n\n    /* Set outgoing address for data channel packets */\n    link_socket_set_outgoing_addr(to_link_socket_info, &ks->remote_addr, session->common_name,\n                                  session->opt->es);\n\n    /* Check if we need to advance the tls_multi state machine */\n    if (multi->multi_state == CAS_NOT_CONNECTED)\n    {\n        if (session->opt->mode == MODE_SERVER)\n        {\n            /* On a server we continue with running connect scripts next */\n            multi->multi_state = CAS_WAITING_AUTH;\n        }\n        else\n        {\n            /* Skip the connect script related states */\n            multi->multi_state = CAS_WAITING_OPTIONS_IMPORT;\n        }\n    }\n\n    /* Flush any payload packets that were buffered before our state transitioned to S_ACTIVE */\n    flush_payload_buffer(ks);\n\n#ifdef MEASURE_TLS_HANDSHAKE_STATS\n    show_tls_performance_stats();\n#endif\n}\n\nbool\nsession_skip_to_pre_start(struct tls_session *session, struct tls_pre_decrypt_state *state,\n                          struct link_socket_actual *from)\n{\n    struct key_state *ks = &session->key[KS_PRIMARY];\n    ks->session_id_remote = state->peer_session_id;\n    ks->remote_addr = *from;\n    session->session_id = state->server_session_id;\n    session->untrusted_addr = *from;\n    session->burst = true;\n\n    /* The OpenVPN protocol implicitly mandates that packet id always start\n     * from 0 in the RESET packets as OpenVPN 2.x will not allow gaps in the\n     * ids and starts always from 0. Since we skip/ignore one (RESET) packet\n     * in each direction, we need to set the ids to 1 */\n    ks->rec_reliable->packet_id = 1;\n    /* for ks->send_reliable->packet_id, session_move_pre_start moves the\n     * counter to 1 */\n    session->tls_wrap.opt.packet_id.send.id = 1;\n    return session_move_pre_start(session, ks, true);\n}\n\n/**\n * Parses the TLVs (type, length, value) in the early negotiation\n */\nstatic bool\nparse_early_negotiation_tlvs(struct buffer *buf, struct key_state *ks)\n{\n    while (buf->len > 0)\n    {\n        if (buf_len(buf) < 4)\n        {\n            goto error;\n        }\n        /* read type */\n        int type = buf_read_u16(buf);\n        int len = buf_read_u16(buf);\n        if (type < 0 || len < 0 || buf_len(buf) < len)\n        {\n            goto error;\n        }\n\n        switch (type)\n        {\n            case TLV_TYPE_EARLY_NEG_FLAGS:\n                if (len != sizeof(uint16_t))\n                {\n                    goto error;\n                }\n                int flags = buf_read_u16(buf);\n\n                if (flags & EARLY_NEG_FLAG_RESEND_WKC)\n                {\n                    ks->crypto_options.flags |= CO_RESEND_WKC;\n                }\n                break;\n\n            default:\n                /* Skip types we do not parse */\n                buf_advance(buf, len);\n        }\n    }\n    reliable_mark_deleted(ks->rec_reliable, buf);\n\n    return true;\nerror:\n    msg(D_TLS_ERRORS, \"TLS Error: Early negotiation malformed packet\");\n    return false;\n}\n\n/**\n * Read incoming ciphertext and passes it to the buffer of the SSL library.\n * Returns false if an error is encountered that should abort the session.\n */\nstatic bool\nread_incoming_tls_ciphertext(struct buffer *buf, struct key_state *ks, bool *continue_tls_process)\n{\n    int status = 0;\n    if (buf->len)\n    {\n        status = key_state_write_ciphertext(&ks->ks_ssl, buf);\n        if (status == -1)\n        {\n            msg(D_TLS_ERRORS, \"TLS Error: Incoming Ciphertext -> TLS object write error\");\n            return false;\n        }\n    }\n    else\n    {\n        status = 1;\n    }\n    if (status == 1)\n    {\n        reliable_mark_deleted(ks->rec_reliable, buf);\n        *continue_tls_process = true;\n        dmsg(D_TLS_DEBUG, \"Incoming Ciphertext -> TLS\");\n    }\n    return true;\n}\n\nstatic bool\ncontrol_packet_needs_wkc(const struct key_state *ks)\n{\n    return (ks->crypto_options.flags & CO_RESEND_WKC) && (ks->send_reliable->packet_id == 1);\n}\n\n\nstatic bool\nread_incoming_tls_plaintext(struct key_state *ks, struct buffer *buf, interval_t *wakeup,\n                            bool *continue_tls_process)\n{\n    ASSERT(buf_init(buf, 0));\n\n    int status = key_state_read_plaintext(&ks->ks_ssl, buf);\n\n    update_time();\n    if (status == -1)\n    {\n        msg(D_TLS_ERRORS, \"TLS Error: TLS object -> incoming plaintext read error\");\n        return false;\n    }\n    if (status == 1)\n    {\n        *continue_tls_process = true;\n        dmsg(D_TLS_DEBUG, \"TLS -> Incoming Plaintext\");\n\n        /* More data may be available, wake up again asap to check. */\n        *wakeup = 0;\n    }\n    return true;\n}\n\nstatic bool\nwrite_outgoing_tls_ciphertext(struct tls_session *session, bool *continue_tls_process)\n{\n    struct key_state *ks = &session->key[KS_PRIMARY];\n\n    int rel_avail = reliable_get_num_output_sequenced_available(ks->send_reliable);\n    if (rel_avail == 0)\n    {\n        return true;\n    }\n\n    /* We need to determine how much space is actually available in the control\n     * channel frame */\n    int max_pkt_len = min_int(TLS_CHANNEL_BUF_SIZE, session->opt->frame.tun_mtu);\n\n    /* Subtract overhead */\n    max_pkt_len -= (int)calc_control_channel_frame_overhead(session);\n\n    /* calculate total available length for outgoing tls ciphertext */\n    int maxlen = max_pkt_len * rel_avail;\n\n    /* Is first packet one that will have a WKC appended? */\n    if (control_packet_needs_wkc(ks))\n    {\n        maxlen -= buf_len(session->tls_wrap.tls_crypt_v2_wkc);\n    }\n\n    /* If we end up with a size that leaves no room for payload, ignore the\n     * constraints to still be to send a packet. This might have gone negative\n     * if we have a large wrapped client key. */\n    if (maxlen < 16)\n    {\n        msg(D_TLS_ERRORS,\n            \"Warning: --max-packet-size (%d) setting too low. \"\n            \"Sending minimum sized packet.\",\n            session->opt->frame.tun_mtu);\n        maxlen = 16;\n        /* We set the maximum length here to ensure a packet with a wrapped\n         * key can actually carry the 16 byte of payload */\n        max_pkt_len = TLS_CHANNEL_BUF_SIZE;\n    }\n\n    /* This seems a bit wasteful to allocate every time */\n    struct gc_arena gc = gc_new();\n    struct buffer tmp = alloc_buf_gc(maxlen, &gc);\n\n    int status = key_state_read_ciphertext(&ks->ks_ssl, &tmp);\n\n    if (status == -1)\n    {\n        msg(D_TLS_ERRORS, \"TLS Error: Ciphertext -> reliable TCP/UDP transport read error\");\n        gc_free(&gc);\n        return false;\n    }\n    if (status == 1)\n    {\n        /* Split the TLS ciphertext (TLS record) into multiple small packets\n         * that respect tls_mtu */\n        while (tmp.len > 0)\n        {\n            int len = max_pkt_len;\n            int opcode = P_CONTROL_V1;\n            if (control_packet_needs_wkc(ks))\n            {\n                opcode = P_CONTROL_WKC_V1;\n                len = max_int(0, len - buf_len(session->tls_wrap.tls_crypt_v2_wkc));\n            }\n            /* do not send more than available */\n            len = min_int(len, tmp.len);\n\n            struct buffer *buf = reliable_get_buf_output_sequenced(ks->send_reliable);\n            /* we assert here since we checked for its availability before */\n            ASSERT(buf);\n            buf_copy_n(buf, &tmp, len);\n\n            reliable_mark_active_outgoing(ks->send_reliable, buf, opcode);\n            INCR_GENERATED;\n            *continue_tls_process = true;\n        }\n        dmsg(D_TLS_DEBUG, \"Outgoing Ciphertext -> Reliable\");\n    }\n\n    gc_free(&gc);\n    return true;\n}\n\nstatic bool\ncheck_outgoing_ciphertext(struct key_state *ks, struct tls_session *session,\n                          bool *continue_tls_process)\n{\n    /* Outgoing Ciphertext to reliable buffer */\n    if (ks->state >= S_START)\n    {\n        struct buffer *buf = reliable_get_buf_output_sequenced(ks->send_reliable);\n        if (buf)\n        {\n            if (!write_outgoing_tls_ciphertext(session, continue_tls_process))\n            {\n                return false;\n            }\n        }\n    }\n    return true;\n}\n\nstatic bool\ntls_process_state(struct tls_multi *multi, struct tls_session *session, struct buffer *to_link,\n                  struct link_socket_actual **to_link_addr,\n                  struct link_socket_info *to_link_socket_info, interval_t *wakeup)\n{\n    /* This variable indicates if we should call this method\n     * again to process more incoming/outgoing TLS state/data\n     * We want to repeat this until we either determined that there\n     * is nothing more to process or that further processing\n     * should only be done after the outer loop (sending packets etc.)\n     * has run once more */\n    bool continue_tls_process = false;\n    struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */\n\n    /* Initial handshake */\n    if (ks->state == S_INITIAL)\n    {\n        continue_tls_process = session_move_pre_start(session, ks, false);\n    }\n\n    /* Are we timed out on receive? */\n    if (now >= ks->must_negotiate && ks->state >= S_UNDEF && ks->state < S_ACTIVE)\n    {\n        msg(D_TLS_ERRORS,\n            \"TLS Error: TLS key negotiation failed to occur within %d seconds (check your network connectivity)\",\n            session->opt->handshake_window);\n        goto error;\n    }\n\n    /* Check if the initial three-way Handshake is complete.\n     * We consider the handshake to be complete when our own initial\n     * packet has been successfully ACKed. */\n    if (ks->state == S_PRE_START && reliable_empty(ks->send_reliable))\n    {\n        ks->state = S_START;\n        continue_tls_process = true;\n\n        /* New connection, remove any old X509 env variables */\n        tls_x509_clear_env(session->opt->es);\n        dmsg(D_TLS_DEBUG_MED, \"STATE S_START\");\n    }\n\n    /* Wait for ACK */\n    if (((ks->state == S_GOT_KEY && !session->opt->server)\n         || (ks->state == S_SENT_KEY && session->opt->server))\n        && reliable_empty(ks->send_reliable))\n    {\n        session_move_active(multi, session, to_link_socket_info, ks);\n        continue_tls_process = true;\n    }\n\n    /* Reliable buffer to outgoing TCP/UDP (send up to CONTROL_SEND_ACK_MAX ACKs\n     * for previously received packets) */\n    if (!to_link->len && reliable_can_send(ks->send_reliable))\n    {\n        int opcode;\n\n        struct buffer *buf = reliable_send(ks->send_reliable, &opcode);\n        ASSERT(buf);\n        struct buffer b = *buf;\n        INCR_SENT;\n\n        write_control_auth(session, ks, &b, to_link_addr, opcode, CONTROL_SEND_ACK_MAX, true);\n        *to_link = b;\n        dmsg(D_TLS_DEBUG, \"Reliable -> TCP/UDP\");\n\n        /* This changed the state of the outgoing buffer. In order to avoid\n         * running this function again/further and invalidating the key_state\n         * buffer and accessing the buffer that is now in to_link after it being\n         * freed for a potential error, we shortcircuit exiting of the outer\n         * process here. */\n        return false;\n    }\n\n    if (ks->state == S_ERROR_PRE)\n    {\n        /* When we end up here, we had one last chance to send an outstanding\n         * packet that contained an alert. We do not ensure that this packet\n         * has been successfully delivered  (ie wait for the ACK etc)\n         * but rather stop processing now */\n        ks->state = S_ERROR;\n        return false;\n    }\n\n    /* Write incoming ciphertext to TLS object */\n    struct reliable_entry *entry = reliable_get_entry_sequenced(ks->rec_reliable);\n    if (entry)\n    {\n        /* The first packet from the peer (the reset packet) is special and\n         * contains early protocol negotiation */\n        if (entry->packet_id == 0 && is_hard_reset_method2(entry->opcode))\n        {\n            if (!parse_early_negotiation_tlvs(&entry->buf, ks))\n            {\n                goto error;\n            }\n        }\n        else\n        {\n            if (!read_incoming_tls_ciphertext(&entry->buf, ks, &continue_tls_process))\n            {\n                goto error;\n            }\n        }\n    }\n\n    /* Read incoming plaintext from TLS object */\n    struct buffer *buf = &ks->plaintext_read_buf;\n    if (!buf->len)\n    {\n        if (!read_incoming_tls_plaintext(ks, buf, wakeup, &continue_tls_process))\n        {\n            goto error;\n        }\n    }\n\n    /* Send Key */\n    buf = &ks->plaintext_write_buf;\n    if (!buf->len\n        && ((ks->state == S_START && !session->opt->server)\n            || (ks->state == S_GOT_KEY && session->opt->server)))\n    {\n        if (!key_method_2_write(buf, multi, session))\n        {\n            goto error;\n        }\n\n        continue_tls_process = true;\n        dmsg(D_TLS_DEBUG_MED, \"STATE S_SENT_KEY\");\n        ks->state = S_SENT_KEY;\n    }\n\n    /* Receive Key */\n    buf = &ks->plaintext_read_buf;\n    if (buf->len\n        && ((ks->state == S_SENT_KEY && !session->opt->server)\n            || (ks->state == S_START && session->opt->server)))\n    {\n        if (!key_method_2_read(buf, multi, session))\n        {\n            goto error;\n        }\n\n        continue_tls_process = true;\n        dmsg(D_TLS_DEBUG_MED, \"STATE S_GOT_KEY\");\n        ks->state = S_GOT_KEY;\n    }\n\n    /* Write outgoing plaintext to TLS object */\n    buf = &ks->plaintext_write_buf;\n    if (buf->len)\n    {\n        int status = key_state_write_plaintext(&ks->ks_ssl, buf);\n        if (status == -1)\n        {\n            msg(D_TLS_ERRORS, \"TLS ERROR: Outgoing Plaintext -> TLS object write error\");\n            goto error;\n        }\n        if (status == 1)\n        {\n            continue_tls_process = true;\n            dmsg(D_TLS_DEBUG, \"Outgoing Plaintext -> TLS\");\n        }\n    }\n    if (!check_outgoing_ciphertext(ks, session, &continue_tls_process))\n    {\n        goto error;\n    }\n\n    return continue_tls_process;\nerror:\n    tls_clear_error();\n\n    /* Shut down the TLS session but do a last read from the TLS\n     * object to be able to read potential TLS alerts */\n    key_state_ssl_shutdown(&ks->ks_ssl);\n    check_outgoing_ciphertext(ks, session, &continue_tls_process);\n\n    /* Put ourselves in the pre error state that will only send out the\n     * control channel packets but nothing else */\n    ks->state = S_ERROR_PRE;\n\n    msg(D_TLS_ERRORS, \"TLS Error: TLS handshake failed\");\n    INCR_ERROR;\n    return true;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\n/**\n * Determines if a renegotiation should be triggerred based on the various\n * factors that can trigger one\n */\nstatic bool\nshould_trigger_renegotiation(const struct tls_session *session, const struct key_state *ks)\n{\n    /* Time limit */\n    if (session->opt->renegotiate_seconds\n        && now >= ks->established + session->opt->renegotiate_seconds)\n    {\n        return true;\n    }\n\n    /* Byte limit */\n    if (session->opt->renegotiate_bytes > 0 && ks->n_bytes >= session->opt->renegotiate_bytes)\n    {\n        return true;\n    }\n\n    /* Packet limit */\n    if (session->opt->renegotiate_packets && ks->n_packets >= session->opt->renegotiate_packets)\n    {\n        return true;\n    }\n\n    /* epoch key id approaching the 16 bit limit */\n    if (ks->crypto_options.flags & CO_EPOCH_DATA_KEY_FORMAT)\n    {\n        /* We only need to check the send key as we always keep send\n         * key epoch >= recv key epoch in \\c epoch_replace_update_recv_key */\n        if (ks->crypto_options.epoch_key_send.epoch >= 0xF000)\n        {\n            return true;\n        }\n        else\n        {\n            return false;\n        }\n    }\n\n\n    /* Packet id approach the limit of the packet id */\n    if (packet_id_close_to_wrapping(&ks->crypto_options.packet_id.send))\n    {\n        return true;\n    }\n\n    /* Check the AEAD usage limit of cleartext blocks + packets.\n     *\n     *  Contrary to when epoch data mode is active, where only the sender side\n     *  checks the limit, here we check both receive and send limit since\n     *  we assume that only one side is aware of the limit.\n     *\n     *  Since if both sides were aware, then both sides will probably also\n     *  switch to use epoch data channel instead, so this code is not\n     *  in effect then.\n     *\n     * When epoch are in use the crypto layer will handle this internally\n     * with new epochs instead of triggering a renegotiation */\n    const struct key_ctx_bi *key_ctx_bi = &ks->crypto_options.key_ctx_bi;\n    const uint64_t usage_limit = session->opt->aead_usage_limit;\n\n    if (aead_usage_limit_reached(usage_limit, &key_ctx_bi->encrypt,\n                                 ks->crypto_options.packet_id.send.id)\n        || aead_usage_limit_reached(usage_limit, &key_ctx_bi->decrypt,\n                                    ks->crypto_options.packet_id.rec.id))\n    {\n        return true;\n    }\n\n    if (cipher_decrypt_verify_fail_warn(&key_ctx_bi->decrypt))\n    {\n        return true;\n    }\n\n    return false;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n/*\n * This is the primary routine for processing TLS stuff inside the\n * the main event loop.  When this routine exits\n * with non-error status, it will set *wakeup to the number of seconds\n * when it wants to be called again.\n *\n * Return value is true if we have placed a packet in *to_link which we\n * want to send to our peer.\n */\nstatic bool\ntls_process(struct tls_multi *multi, struct tls_session *session, struct buffer *to_link,\n            struct link_socket_actual **to_link_addr, struct link_socket_info *to_link_socket_info,\n            interval_t *wakeup)\n{\n    struct key_state *ks = &session->key[KS_PRIMARY];        /* primary key */\n    struct key_state *ks_lame = &session->key[KS_LAME_DUCK]; /* retiring key */\n\n    /* Make sure we were initialized and that we're not in an error state */\n    ASSERT(ks->state != S_UNDEF);\n    ASSERT(ks->state != S_ERROR);\n    ASSERT(session_id_defined(&session->session_id));\n\n    /* Should we trigger a soft reset? -- new key, keeps old key for a while */\n    if (ks->state >= S_GENERATED_KEYS && should_trigger_renegotiation(session, ks))\n    {\n        msg(D_TLS_DEBUG_LOW,\n            \"TLS: soft reset sec=%d/%d bytes=\" counter_format \"/%\" PRIi64 \" pkts=\" counter_format\n            \"/%\" PRIi64 \" aead_limit_send=%\" PRIu64 \"/%\" PRIu64 \" aead_limit_recv=%\" PRIu64\n            \"/%\" PRIu64,\n            (int)(now - ks->established), session->opt->renegotiate_seconds, ks->n_bytes,\n            session->opt->renegotiate_bytes, ks->n_packets, session->opt->renegotiate_packets,\n            ks->crypto_options.key_ctx_bi.encrypt.plaintext_blocks + ks->n_packets,\n            session->opt->aead_usage_limit,\n            ks->crypto_options.key_ctx_bi.decrypt.plaintext_blocks + ks->n_packets,\n            session->opt->aead_usage_limit);\n        key_state_soft_reset(session);\n    }\n\n    /* Kill lame duck key transition_window seconds after primary key negotiation */\n    if (lame_duck_must_die(session, wakeup))\n    {\n        key_state_free(ks_lame, true);\n        msg(D_TLS_DEBUG_LOW, \"TLS: tls_process: killed expiring key\");\n    }\n\n    bool continue_tls_process = true;\n    while (continue_tls_process)\n    {\n        update_time();\n\n        dmsg(D_TLS_DEBUG, \"TLS: tls_process: chg=%d ks=%s lame=%s to_link->len=%d wakeup=%d\",\n             continue_tls_process, state_name(ks->state), state_name(ks_lame->state), to_link->len,\n             *wakeup);\n        continue_tls_process =\n            tls_process_state(multi, session, to_link, to_link_addr, to_link_socket_info, wakeup);\n\n        if (ks->state == S_ERROR)\n        {\n            return false;\n        }\n    }\n\n    update_time();\n\n    /* We often send acks back to back to a following control packet. This\n     * normally does not create a problem (apart from an extra packet).\n     * However, with the P_CONTROL_WKC_V1 we need to ensure that the packet\n     * gets resent if not received by remote, so instead we use an empty\n     * control packet in this special case */\n\n    /* Send 1 or more ACKs (each received control packet gets one ACK) */\n    if (!to_link->len && !reliable_ack_empty(ks->rec_ack))\n    {\n        if (control_packet_needs_wkc(ks))\n        {\n            struct buffer *buf = reliable_get_buf_output_sequenced(ks->send_reliable);\n            if (!buf)\n            {\n                return false;\n            }\n\n            /* We do not write anything to the buffer, this way this will be\n             * an empty control packet that gets the ack piggybacked and\n             * also appended the wrapped client key since it has a WCK opcode */\n            reliable_mark_active_outgoing(ks->send_reliable, buf, P_CONTROL_WKC_V1);\n        }\n        else\n        {\n            struct buffer buf = ks->ack_write_buf;\n            ASSERT(buf_init(&buf, multi->opt.frame.buf.headroom));\n            write_control_auth(session, ks, &buf, to_link_addr, P_ACK_V1, RELIABLE_ACK_SIZE, false);\n            *to_link = buf;\n            dmsg(D_TLS_DEBUG, \"Dedicated ACK -> TCP/UDP\");\n        }\n    }\n\n    /* When should we wake up again? */\n    if (ks->state >= S_INITIAL || ks->state == S_ERROR_PRE)\n    {\n        compute_earliest_wakeup(wakeup, reliable_send_timeout(ks->send_reliable));\n\n        if (ks->must_negotiate)\n        {\n            compute_earliest_wakeup(wakeup, ks->must_negotiate - now);\n        }\n    }\n\n    if (ks->established && session->opt->renegotiate_seconds)\n    {\n        compute_earliest_wakeup(wakeup, ks->established + session->opt->renegotiate_seconds - now);\n    }\n\n    dmsg(D_TLS_DEBUG, \"TLS: tls_process: timeout set to %d\", *wakeup);\n\n    /* prevent event-loop spinning by setting minimum wakeup of 1 second */\n    if (*wakeup <= 0)\n    {\n        *wakeup = 1;\n\n        /* if we had something to send to remote, but to_link was busy,\n         * let caller know we need to be called again soon */\n        return true;\n    }\n\n    /* If any of the state changes resulted in the to_link buffer being\n     * set, we are also active */\n    if (to_link->len)\n    {\n        return true;\n    }\n\n    return false;\n}\n\n\n/**\n * This is a safe guard function to double check that a buffer from a session is\n * not used in a session to avoid a use after free.\n *\n * @param to_link\n * @param session\n */\nstatic void\ncheck_session_buf_not_used(struct buffer *to_link, struct tls_session *session)\n{\n    uint8_t *dataptr = to_link->data;\n    if (!dataptr)\n    {\n        return;\n    }\n\n    /* Checks buffers in tls_wrap */\n    if (session->tls_wrap.work.data == dataptr)\n    {\n        msg(M_INFO, \"Warning buffer of freed TLS session is \"\n                    \"still in use (tls_wrap.work.data)\");\n        goto used;\n    }\n\n    for (int i = 0; i < KS_SIZE; i++)\n    {\n        struct key_state *ks = &session->key[i];\n        if (ks->state == S_UNDEF)\n        {\n            continue;\n        }\n\n        /* we don't expect send_reliable to be NULL when state is\n         * not S_UNDEF, but people have reported crashes nonetheless,\n         * therefore we better catch this event, report and exit.\n         */\n        if (!ks->send_reliable)\n        {\n            msg(M_FATAL,\n                \"ERROR: session->key[%d]->send_reliable is NULL \"\n                \"while key state is %s. Exiting.\",\n                i, state_name(ks->state));\n        }\n\n        for (int j = 0; j < ks->send_reliable->size; j++)\n        {\n            if (ks->send_reliable->array[j].buf.data == dataptr)\n            {\n                msg(M_INFO,\n                    \"Warning buffer of freed TLS session is still in\"\n                    \" use (session->key[%d].send_reliable->array[%d])\",\n                    i, j);\n\n                goto used;\n            }\n        }\n    }\n    return;\n\nused:\n    to_link->len = 0;\n    to_link->data = 0;\n    /* for debugging, you can add an ASSERT(0); here to trigger an abort */\n}\n/*\n * Called by the top-level event loop.\n *\n * Basically decides if we should call tls_process for\n * the active or untrusted sessions.\n */\n\nint\ntls_multi_process(struct tls_multi *multi, struct buffer *to_link,\n                  struct link_socket_actual **to_link_addr,\n                  struct link_socket_info *to_link_socket_info, interval_t *wakeup)\n{\n    struct gc_arena gc = gc_new();\n    int active = TLSMP_INACTIVE;\n    bool error = false;\n\n    tls_clear_error();\n\n    /*\n     * Process each session object having state of S_INITIAL or greater,\n     * and which has a defined remote IP addr.\n     */\n\n    for (int i = 0; i < TM_SIZE; ++i)\n    {\n        struct tls_session *session = &multi->session[i];\n        struct key_state *ks = &session->key[KS_PRIMARY];\n        struct key_state *ks_lame = &session->key[KS_LAME_DUCK];\n\n        /* set initial remote address. This triggers connecting with that\n         * session. So we only do that if the TM_ACTIVE session is not\n         * established */\n        if (i == TM_INITIAL && ks->state == S_INITIAL && get_primary_key(multi)->state <= S_INITIAL\n            && link_socket_actual_defined(&to_link_socket_info->lsa->actual))\n        {\n            ks->remote_addr = to_link_socket_info->lsa->actual;\n        }\n\n        dmsg(D_TLS_DEBUG,\n             \"TLS: tls_multi_process: i=%d state=%s, mysid=%s, stored-sid=%s, stored-ip=%s\", i,\n             state_name(ks->state), session_id_print(&session->session_id, &gc),\n             session_id_print(&ks->session_id_remote, &gc),\n             print_link_socket_actual(&ks->remote_addr, &gc));\n\n        if ((ks->state >= S_INITIAL || ks->state == S_ERROR_PRE)\n            && link_socket_actual_defined(&ks->remote_addr))\n        {\n            struct link_socket_actual *tla = NULL;\n\n            update_time();\n\n            if (tls_process(multi, session, to_link, &tla, to_link_socket_info, wakeup))\n            {\n                active = TLSMP_ACTIVE;\n            }\n\n            /*\n             * If tls_process produced an outgoing packet,\n             * return the link_socket_actual object (which\n             * contains the outgoing address).\n             */\n            if (tla)\n            {\n                multi->to_link_addr = *tla;\n                *to_link_addr = &multi->to_link_addr;\n            }\n\n            /*\n             * If tls_process hits an error:\n             * (1) If the session has an unexpired lame duck key, preserve it.\n             * (2) Reinitialize the session.\n             * (3) Increment soft error count\n             */\n            if (ks->state == S_ERROR)\n            {\n                ++multi->n_soft_errors;\n\n                if (i == TM_ACTIVE || (i == TM_INITIAL && get_primary_key(multi)->state < S_ACTIVE))\n                {\n                    error = true;\n                }\n\n                if (i == TM_ACTIVE && ks_lame->state >= S_GENERATED_KEYS\n                    && !multi->opt.single_session)\n                {\n                    move_session(multi, TM_LAME_DUCK, TM_ACTIVE, true);\n                }\n                else\n                {\n                    check_session_buf_not_used(to_link, session);\n                    reset_session(multi, session);\n                }\n            }\n        }\n    }\n\n    update_time();\n\n    enum tls_auth_status tas = tls_authentication_status(multi);\n\n    /* If we have successfully authenticated and are still waiting for the authentication to finish\n     * move the state machine for the multi context forward */\n\n    if (multi->multi_state >= CAS_CONNECT_DONE)\n    {\n        /* Only generate keys for the TM_ACTIVE session. We defer generating\n         * keys for TM_INITIAL until we actually trust it.\n         * For TM_LAME_DUCK it makes no sense to generate new keys. */\n        struct tls_session *session = &multi->session[TM_ACTIVE];\n        struct key_state *ks = &session->key[KS_PRIMARY];\n\n        if (ks->state == S_ACTIVE && ks->authenticated == KS_AUTH_TRUE)\n        {\n            /* Session is now fully authenticated.\n             * tls_session_generate_data_channel_keys will move ks->state\n             * from S_ACTIVE to S_GENERATED_KEYS */\n            if (!tls_session_generate_data_channel_keys(multi, session))\n            {\n                msg(D_TLS_ERRORS, \"TLS Error: generate_key_expansion failed\");\n                ks->authenticated = KS_AUTH_FALSE;\n                key_state_ssl_shutdown(&ks->ks_ssl);\n                ks->state = S_ERROR_PRE;\n            }\n\n            /* Update auth token on the client if needed on renegotiation\n             * (key id !=0) */\n            if (session->key[KS_PRIMARY].key_id != 0)\n            {\n                resend_auth_token_renegotiation(multi, session);\n            }\n        }\n    }\n\n    if (multi->multi_state == CAS_WAITING_AUTH && tas == TLS_AUTHENTICATION_SUCCEEDED)\n    {\n        multi->multi_state = CAS_PENDING;\n    }\n\n    /*\n     * If lame duck session expires, kill it.\n     */\n    if (lame_duck_must_die(&multi->session[TM_LAME_DUCK], wakeup))\n    {\n        tls_session_free(&multi->session[TM_LAME_DUCK], true);\n        msg(D_TLS_DEBUG_LOW, \"TLS: tls_multi_process: killed expiring key\");\n    }\n\n    /*\n     * If untrusted session achieves TLS authentication,\n     * move it to active session, usurping any prior session.\n     *\n     * A semi-trusted session is one in which the certificate authentication\n     * succeeded (if cert verification is enabled) but the username/password\n     * verification failed.  A semi-trusted session can forward data on the\n     * TLS control channel but not on the tunnel channel.\n     */\n    if (TLS_AUTHENTICATED(multi, &multi->session[TM_INITIAL].key[KS_PRIMARY]))\n    {\n        move_session(multi, TM_ACTIVE, TM_INITIAL, true);\n        tas = tls_authentication_status(multi);\n        msg(D_TLS_DEBUG_LOW,\n            \"TLS: tls_multi_process: initial untrusted \"\n            \"session promoted to %strusted\",\n            tas == TLS_AUTHENTICATION_SUCCEEDED ? \"\" : \"semi-\");\n\n        if (multi->multi_state == CAS_CONNECT_DONE)\n        {\n            multi->multi_state = CAS_RECONNECT_PENDING;\n            active = TLSMP_RECONNECT;\n        }\n    }\n\n    /*\n     * A hard error means that TM_ACTIVE hit an S_ERROR state and that no\n     * other key state objects are S_ACTIVE or higher.\n     */\n    if (error)\n    {\n        for (int i = 0; i < KEY_SCAN_SIZE; ++i)\n        {\n            if (get_key_scan(multi, i)->state >= S_ACTIVE)\n            {\n                goto nohard;\n            }\n        }\n        ++multi->n_hard_errors;\n    }\nnohard:\n\n#ifdef ENABLE_DEBUG\n    /* DEBUGGING -- flood peer with repeating connection attempts */\n    {\n        const int throw_level = GREMLIN_CONNECTION_FLOOD_LEVEL(multi->opt.gremlin);\n        if (throw_level)\n        {\n            for (int i = 0; i < KEY_SCAN_SIZE; ++i)\n            {\n                if (get_key_scan(multi, i)->state >= throw_level)\n                {\n                    ++multi->n_hard_errors;\n                    ++multi->n_soft_errors;\n                }\n            }\n        }\n    }\n#endif\n\n    gc_free(&gc);\n\n    return (tas == TLS_AUTHENTICATION_FAILED) ? TLSMP_KILL : active;\n}\n\n/**\n * We have not found a matching key to decrypt data channel packet,\n * try to generate a sensible error message and print it\n */\nstatic void\nprint_key_id_not_found_reason(struct tls_multi *multi, const struct link_socket_actual *from,\n                              int key_id)\n{\n    struct gc_arena gc = gc_new();\n    const char *source = print_link_socket_actual(from, &gc);\n\n\n    for (int i = 0; i < KEY_SCAN_SIZE; ++i)\n    {\n        struct key_state *ks = get_key_scan(multi, i);\n        if (ks->key_id != key_id)\n        {\n            continue;\n        }\n\n        /* Our key state has been progressed far enough to be part of a valid\n         * session but has not generated keys. */\n        if (ks->state >= S_INITIAL && ks->state < S_GENERATED_KEYS)\n        {\n            msg(D_MULTI_DROPPED, \"Key %s [%d] not initialized (yet), dropping packet.\", source,\n                key_id);\n            gc_free(&gc);\n            return;\n        }\n        if (ks->state >= S_ACTIVE && ks->authenticated != KS_AUTH_TRUE)\n        {\n            msg(D_MULTI_DROPPED, \"Key %s [%d] not authorized%s, dropping packet.\", source, key_id,\n                (ks->authenticated == KS_AUTH_DEFERRED) ? \" (deferred)\" : \"\");\n            gc_free(&gc);\n            return;\n        }\n    }\n\n    msg(D_TLS_ERRORS,\n        \"TLS Error: local/remote TLS keys are out of sync: %s \"\n        \"(received key id: %d, known key ids: %s)\",\n        source, key_id, print_key_id(multi, &gc));\n    gc_free(&gc);\n}\n\n/**\n * Check the keyid of the an incoming data channel packet and\n * return the matching crypto parameters in \\c opt if found.\n * Also move the \\c buf to the start of the encrypted data, skipping\n * the opcode and peer id header and setting also set \\c ad_start for\n * AEAD ciphers to the start of the authenticated data.\n */\nstatic inline void\nhandle_data_channel_packet(struct tls_multi *multi, const struct link_socket_actual *from,\n                           struct buffer *buf, struct crypto_options **opt, bool floated,\n                           const uint8_t **ad_start)\n{\n    struct gc_arena gc = gc_new();\n\n    uint8_t c = *BPTR(buf);\n    int op = c >> P_OPCODE_SHIFT;\n    int key_id = c & P_KEY_ID_MASK;\n\n    for (int i = 0; i < KEY_SCAN_SIZE; ++i)\n    {\n        struct key_state *ks = get_key_scan(multi, i);\n\n        /*\n         * This is the basic test of TLS state compatibility between a local OpenVPN\n         * instance and its remote peer.\n         *\n         * If the test fails, it tells us that we are getting a packet from a source\n         * which claims reference to a prior negotiated TLS session, but the local\n         * OpenVPN instance has no memory of such a negotiation.\n         *\n         * It almost always occurs on UDP sessions when the passive side of the\n         * connection is restarted without the active side restarting as well (the\n         * passive side is the server which only listens for the connections, the\n         * active side is the client which initiates connections).\n         */\n        if (ks->state >= S_GENERATED_KEYS && key_id == ks->key_id\n            && ks->authenticated == KS_AUTH_TRUE\n            && (floated || link_socket_actual_match(from, &ks->remote_addr)))\n        {\n            ASSERT(ks->crypto_options.key_ctx_bi.initialized);\n            /* return appropriate data channel decrypt key in opt */\n            *opt = &ks->crypto_options;\n            if (op == P_DATA_V2)\n            {\n                *ad_start = BPTR(buf);\n            }\n            ASSERT(buf_advance(buf, 1));\n            if (op == P_DATA_V1)\n            {\n                *ad_start = BPTR(buf);\n            }\n            else if (op == P_DATA_V2)\n            {\n                if (buf->len < 4)\n                {\n                    msg(D_TLS_ERRORS,\n                        \"Protocol error: received P_DATA_V2 from %s but length is < 4\",\n                        print_link_socket_actual(from, &gc));\n                    ++multi->n_soft_errors;\n                    goto done;\n                }\n                ASSERT(buf_advance(buf, 3));\n            }\n\n            ++ks->n_packets;\n            ks->n_bytes += buf->len;\n            dmsg(D_TLS_KEYSELECT, \"TLS: tls_pre_decrypt, key_id=%d, IP=%s\", key_id,\n                 print_link_socket_actual(from, &gc));\n            gc_free(&gc);\n            return;\n        }\n    }\n\n    print_key_id_not_found_reason(multi, from, key_id);\n\ndone:\n    gc_free(&gc);\n    tls_clear_error();\n    buf->len = 0;\n    *opt = NULL;\n}\n\n/*\n *\n * When we are in TLS mode, this is the first routine which sees\n * an incoming packet.\n *\n * If it's a data packet, we set opt so that our caller can\n * decrypt it.  We also give our caller the appropriate decryption key.\n *\n * If it's a control packet, we authenticate it and process it,\n * possibly creating a new tls_session if it represents the\n * first packet of a new session.  For control packets, we will\n * also zero the size of *buf so that our caller ignores the\n * packet on our return.\n *\n * Note that openvpn only allows one active session at a time,\n * so a new session (once authenticated) will always usurp\n * an old session.\n *\n * Return true if input was an authenticated control channel\n * packet.\n *\n * If we are running in TLS thread mode, all public routines\n * below this point must be called with the L_TLS lock held.\n */\n\nbool\ntls_pre_decrypt(struct tls_multi *multi, const struct link_socket_actual *from, struct buffer *buf,\n                struct crypto_options **opt, bool floated, const uint8_t **ad_start)\n{\n    if (buf->len <= 0)\n    {\n        buf->len = 0;\n        *opt = NULL;\n        return false;\n    }\n\n    struct gc_arena gc = gc_new();\n    bool ret = false;\n\n    /* get opcode  */\n    uint8_t pkt_firstbyte = *BPTR(buf);\n    int op = pkt_firstbyte >> P_OPCODE_SHIFT;\n\n    if ((op == P_DATA_V1) || (op == P_DATA_V2))\n    {\n        handle_data_channel_packet(multi, from, buf, opt, floated, ad_start);\n        return false;\n    }\n\n    /* get key_id */\n    int key_id = pkt_firstbyte & P_KEY_ID_MASK;\n\n    /* control channel packet */\n    bool do_burst = false;\n    bool new_link = false;\n    struct session_id sid; /* remote session ID */\n\n    /* verify legal opcode */\n    if (op < P_FIRST_OPCODE || op > P_LAST_OPCODE)\n    {\n        if (op == P_CONTROL_HARD_RESET_CLIENT_V1 || op == P_CONTROL_HARD_RESET_SERVER_V1)\n        {\n            msg(D_TLS_ERRORS, \"Peer tried unsupported key-method 1\");\n        }\n        msg(D_TLS_ERRORS, \"TLS Error: unknown opcode received from %s op=%d\",\n            print_link_socket_actual(from, &gc), op);\n        goto error;\n    }\n\n    /* hard reset ? */\n    if (is_hard_reset_method2(op))\n    {\n        /* verify client -> server or server -> client connection */\n        if (((op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_CLIENT_V3)\n             && !multi->opt.server)\n            || ((op == P_CONTROL_HARD_RESET_SERVER_V2) && multi->opt.server))\n        {\n            msg(D_TLS_ERRORS,\n                \"TLS Error: client->client or server->server connection attempted from %s\",\n                print_link_socket_actual(from, &gc));\n            goto error;\n        }\n    }\n\n    /*\n     * Authenticate Packet\n     */\n    dmsg(D_TLS_DEBUG, \"TLS: control channel, op=%s, IP=%s\", packet_opcode_name(op),\n         print_link_socket_actual(from, &gc));\n\n    /* get remote session-id */\n    {\n        struct buffer tmp = *buf;\n        buf_advance(&tmp, 1);\n        if (!session_id_read(&sid, &tmp) || !session_id_defined(&sid))\n        {\n            msg(D_TLS_ERRORS, \"TLS Error: session-id not found in packet from %s\",\n                print_link_socket_actual(from, &gc));\n            goto error;\n        }\n    }\n\n    int i;\n    /* use session ID to match up packet with appropriate tls_session object */\n    for (i = 0; i < TM_SIZE; ++i)\n    {\n        struct tls_session *session = &multi->session[i];\n        struct key_state *ks = &session->key[KS_PRIMARY];\n\n        dmsg(\n            D_TLS_DEBUG,\n            \"TLS: initial packet test, i=%d state=%s, mysid=%s, rec-sid=%s, rec-ip=%s, stored-sid=%s, stored-ip=%s\",\n            i, state_name(ks->state), session_id_print(&session->session_id, &gc),\n            session_id_print(&sid, &gc), print_link_socket_actual(from, &gc),\n            session_id_print(&ks->session_id_remote, &gc),\n            print_link_socket_actual(&ks->remote_addr, &gc));\n\n        if (session_id_equal(&ks->session_id_remote, &sid))\n        /* found a match */\n        {\n            if (i == TM_LAME_DUCK)\n            {\n                msg(D_TLS_ERRORS, \"TLS ERROR: received control packet with stale session-id=%s\",\n                    session_id_print(&sid, &gc));\n                goto error;\n            }\n            dmsg(D_TLS_DEBUG, \"TLS: found match, session[%d], sid=%s\", i,\n                 session_id_print(&sid, &gc));\n            break;\n        }\n    }\n\n    /*\n     * Hard reset and session id does not match any session in\n     * multi->session: Possible initial packet. New sessions always start\n     * as TM_INITIAL\n     */\n    if (i == TM_SIZE && is_hard_reset_method2(op))\n    {\n        /*\n         * No match with existing sessions,\n         * probably a new session.\n         */\n        struct tls_session *session = &multi->session[TM_INITIAL];\n\n        /*\n         * If --single-session, don't allow any hard-reset connection request\n         * unless it is the first packet of the session.\n         */\n        if (multi->opt.single_session && multi->n_sessions)\n        {\n            msg(D_TLS_ERRORS,\n                \"TLS Error: Cannot accept new session request from %s due \"\n                \"to session context expire or --single-session\",\n                print_link_socket_actual(from, &gc));\n            goto error;\n        }\n\n        if (!read_control_auth(buf, tls_session_get_tls_wrap(session, key_id), from, session->opt,\n                               true))\n        {\n            goto error;\n        }\n\n#ifdef ENABLE_MANAGEMENT\n        if (management)\n        {\n            management_set_state(management, OPENVPN_STATE_AUTH, NULL, NULL, NULL, NULL, NULL);\n        }\n#endif\n\n        /*\n         * New session-initiating control packet is authenticated at this point,\n         * assuming that the --tls-auth command line option was used.\n         *\n         * Without --tls-auth, we leave authentication entirely up to TLS.\n         */\n        msg(D_TLS_DEBUG_LOW, \"TLS: Initial packet from %s, sid=%s\",\n            print_link_socket_actual(from, &gc), session_id_print(&sid, &gc));\n\n        do_burst = true;\n        new_link = true;\n        i = TM_INITIAL;\n        session->untrusted_addr = *from;\n    }\n    else\n    {\n        /*\n         * Packet must belong to an existing session.\n         */\n        if (i != TM_ACTIVE && i != TM_INITIAL)\n        {\n            msg(D_TLS_ERRORS, \"TLS Error: Unroutable control packet received from %s (si=%d op=%s)\",\n                print_link_socket_actual(from, &gc), i, packet_opcode_name(op));\n            goto error;\n        }\n\n        struct tls_session *session = &multi->session[i];\n        struct key_state *ks = &session->key[KS_PRIMARY];\n        /*\n         * Verify remote IP address\n         */\n        if (!new_link && !link_socket_actual_match(&ks->remote_addr, from))\n        {\n            msg(D_TLS_ERRORS, \"TLS Error: Received control packet from unexpected IP addr: %s\",\n                print_link_socket_actual(from, &gc));\n            goto error;\n        }\n\n        /*\n         * Remote is requesting a key renegotiation.  We only allow renegotiation\n         * when the previous session is fully established to avoid weird corner\n         * cases.\n         */\n        if (op == P_CONTROL_SOFT_RESET_V1 && ks->state >= S_GENERATED_KEYS)\n        {\n            if (!read_control_auth(buf, tls_session_get_tls_wrap(session, key_id), from,\n                                   session->opt, false))\n            {\n                goto error;\n            }\n\n            key_state_soft_reset(session);\n\n            dmsg(D_TLS_DEBUG, \"TLS: received P_CONTROL_SOFT_RESET_V1 s=%d sid=%s\", i,\n                 session_id_print(&sid, &gc));\n        }\n        else\n        {\n            bool initial_packet = false;\n            if (ks->state == S_PRE_START_SKIP)\n            {\n                /* When we are coming from the session_skip_to_pre_start\n                 * method, we allow this initial packet to setup the\n                 * tls-crypt-v2 peer specific key */\n                initial_packet = true;\n                ks->state = S_PRE_START;\n            }\n            /*\n             * Remote responding to our key renegotiation request?\n             */\n            if (op == P_CONTROL_SOFT_RESET_V1)\n            {\n                do_burst = true;\n            }\n\n            if (!read_control_auth(buf, tls_session_get_tls_wrap(session, key_id), from,\n                                   session->opt, initial_packet))\n            {\n                /* if an initial packet in read_control_auth, we rather\n                 * error out than anything else */\n                if (initial_packet)\n                {\n                    multi->n_hard_errors++;\n                }\n                goto error;\n            }\n\n            dmsg(D_TLS_DEBUG, \"TLS: received control channel packet s#=%d sid=%s\", i,\n                 session_id_print(&sid, &gc));\n        }\n    }\n\n    /*\n     * We have an authenticated control channel packet (if --tls-auth/tls-crypt\n     * or tls-crypt-v2 was set).\n     * Now pass to our reliability layer which deals with\n     * packet acknowledgements, retransmits, sequencing, etc.\n     */\n    struct tls_session *session = &multi->session[i];\n    struct key_state *ks = &session->key[KS_PRIMARY];\n\n    /* Make sure we were initialized and that we're not in an error state */\n    ASSERT(ks->state != S_UNDEF);\n    ASSERT(ks->state != S_ERROR);\n    ASSERT(session_id_defined(&session->session_id));\n\n    /* Let our caller know we processed a control channel packet */\n    ret = true;\n\n    /*\n     * Set our remote address and remote session_id\n     */\n    if (new_link)\n    {\n        ks->session_id_remote = sid;\n        ks->remote_addr = *from;\n        ++multi->n_sessions;\n    }\n    else if (!link_socket_actual_match(&ks->remote_addr, from))\n    {\n        msg(D_TLS_ERRORS,\n            \"TLS Error: Existing session control channel packet from unknown IP address: %s\",\n            print_link_socket_actual(from, &gc));\n        goto error;\n    }\n\n    /*\n     * Should we do a retransmit of all unacknowledged packets in\n     * the send buffer?  This improves the start-up efficiency of the\n     * initial key negotiation after the 2nd peer comes online.\n     */\n    if (do_burst && !session->burst)\n    {\n        reliable_schedule_now(ks->send_reliable);\n        session->burst = true;\n    }\n\n    /* Check key_id */\n    if (ks->key_id != key_id)\n    {\n        msg(D_TLS_ERRORS, \"TLS ERROR: local/remote key IDs out of sync (%d/%d) ID: %s\", ks->key_id,\n            key_id, print_key_id(multi, &gc));\n        goto error;\n    }\n\n    /*\n     * Process incoming ACKs for packets we can now\n     * delete from reliable send buffer\n     */\n    {\n        /* buffers all packet IDs to delete from send_reliable */\n        struct reliable_ack send_ack;\n\n        if (!reliable_ack_read(&send_ack, buf, &session->session_id))\n        {\n            msg(D_TLS_ERRORS, \"TLS Error: reading acknowledgement record from packet\");\n            goto error;\n        }\n        reliable_send_purge(ks->send_reliable, &send_ack);\n    }\n\n    if (op != P_ACK_V1 && reliable_can_get(ks->rec_reliable))\n    {\n        packet_id_type id;\n\n        /* Extract the packet ID from the packet */\n        if (reliable_ack_read_packet_id(buf, &id))\n        {\n            /* Avoid deadlock by rejecting packet that would de-sequentialize receive buffer */\n            if (reliable_wont_break_sequentiality(ks->rec_reliable, id))\n            {\n                if (reliable_not_replay(ks->rec_reliable, id))\n                {\n                    /* Save incoming ciphertext packet to reliable buffer */\n                    struct buffer *in = reliable_get_buf(ks->rec_reliable);\n                    ASSERT(in);\n                    if (!buf_copy(in, buf))\n                    {\n                        msg(D_MULTI_DROPPED, \"Incoming control channel packet too big, dropping.\");\n                        goto error;\n                    }\n                    reliable_mark_active_incoming(ks->rec_reliable, in, id, op);\n                }\n\n                /* Process outgoing acknowledgment for packet just received, even if it's a replay\n                 */\n                reliable_ack_acknowledge_packet_id(ks->rec_ack, id);\n            }\n        }\n    }\n    /* Remember that we received a valid control channel packet */\n    ks->peer_last_packet = now;\n\ndone:\n    buf->len = 0;\n    *opt = NULL;\n    gc_free(&gc);\n    return ret;\n\nerror:\n    ++multi->n_soft_errors;\n    tls_clear_error();\n    goto done;\n}\n\n\nstruct key_state *\ntls_select_encryption_key(struct tls_multi *multi)\n{\n    struct key_state *ks_select = NULL;\n    for (int i = 0; i < KEY_SCAN_SIZE; ++i)\n    {\n        struct key_state *ks = get_key_scan(multi, i);\n        if (ks->state >= S_GENERATED_KEYS && ks->authenticated == KS_AUTH_TRUE)\n        {\n            ASSERT(ks->crypto_options.key_ctx_bi.initialized);\n\n            if (!ks_select)\n            {\n                ks_select = ks;\n            }\n            if (now >= ks->auth_deferred_expire)\n            {\n                ks_select = ks;\n                break;\n            }\n        }\n    }\n    return ks_select;\n}\n\n\n/* Choose the key with which to encrypt a data packet */\nvoid\ntls_pre_encrypt(struct tls_multi *multi, struct buffer *buf, struct crypto_options **opt)\n{\n    multi->save_ks = NULL;\n    if (buf->len <= 0)\n    {\n        buf->len = 0;\n        *opt = NULL;\n        return;\n    }\n\n    struct key_state *ks_select = tls_select_encryption_key(multi);\n\n    if (ks_select)\n    {\n        *opt = &ks_select->crypto_options;\n        multi->save_ks = ks_select;\n        dmsg(D_TLS_KEYSELECT, \"TLS: tls_pre_encrypt: key_id=%d\", ks_select->key_id);\n        return;\n    }\n    else\n    {\n        struct gc_arena gc = gc_new();\n        dmsg(D_TLS_KEYSELECT, \"TLS Warning: no data channel send key available: %s\",\n             print_key_id(multi, &gc));\n        gc_free(&gc);\n\n        *opt = NULL;\n        buf->len = 0;\n    }\n}\n\nvoid\ntls_prepend_opcode_v1(const struct tls_multi *multi, struct buffer *buf)\n{\n    struct key_state *ks = multi->save_ks;\n\n    msg(D_TLS_DEBUG, __func__);\n\n    ASSERT(ks);\n    ASSERT(ks->key_id <= P_KEY_ID_MASK);\n\n    uint8_t op = (P_DATA_V1 << P_OPCODE_SHIFT) | (uint8_t)ks->key_id;\n    ASSERT(buf_write_prepend(buf, &op, 1));\n}\n\nvoid\ntls_prepend_opcode_v2(const struct tls_multi *multi, struct buffer *buf)\n{\n    struct key_state *ks = multi->save_ks;\n    uint32_t peer;\n\n    msg(D_TLS_DEBUG, __func__);\n\n    ASSERT(ks);\n\n    peer = htonl(((P_DATA_V2 << P_OPCODE_SHIFT) | ks->key_id) << 24 | (multi->peer_id & 0xFFFFFF));\n    ASSERT(buf_write_prepend(buf, &peer, 4));\n}\n\nvoid\ntls_post_encrypt(struct tls_multi *multi, struct buffer *buf)\n{\n    struct key_state *ks = multi->save_ks;\n    multi->save_ks = NULL;\n\n    if (buf->len > 0)\n    {\n        ASSERT(ks);\n\n        ++ks->n_packets;\n        ks->n_bytes += buf->len;\n    }\n}\n\n/*\n * Send a payload over the TLS control channel.\n * Called externally.\n */\n\nbool\ntls_send_payload(struct key_state *ks, const uint8_t *data, size_t size)\n{\n    bool ret = false;\n\n    tls_clear_error();\n\n    ASSERT(ks);\n\n    if (ks->state >= S_ACTIVE)\n    {\n        ASSERT(size <= INT_MAX);\n        if (key_state_write_plaintext_const(&ks->ks_ssl, data, (int)size) == 1)\n        {\n            ret = true;\n        }\n    }\n    else\n    {\n        if (!ks->paybuf)\n        {\n            ks->paybuf = buffer_list_new();\n        }\n        buffer_list_push_data(ks->paybuf, data, size);\n        ret = true;\n    }\n\n\n    tls_clear_error();\n\n    return ret;\n}\n\nbool\ntls_rec_payload(struct tls_multi *multi, struct buffer *buf)\n{\n    bool ret = false;\n\n    tls_clear_error();\n\n    ASSERT(multi);\n\n    struct key_state *ks = get_key_scan(multi, 0);\n\n    if (ks->state >= S_ACTIVE && BLEN(&ks->plaintext_read_buf))\n    {\n        if (buf_copy(buf, &ks->plaintext_read_buf))\n        {\n            ret = true;\n        }\n        ks->plaintext_read_buf.len = 0;\n    }\n\n    tls_clear_error();\n\n    return ret;\n}\n\nvoid\ntls_update_remote_addr(struct tls_multi *multi, const struct link_socket_actual *addr)\n{\n    struct gc_arena gc = gc_new();\n    for (int i = 0; i < TM_SIZE; ++i)\n    {\n        struct tls_session *session = &multi->session[i];\n\n        for (int j = 0; j < KS_SIZE; ++j)\n        {\n            struct key_state *ks = &session->key[j];\n\n            if (!link_socket_actual_defined(&ks->remote_addr)\n                || link_socket_actual_match(addr, &ks->remote_addr))\n            {\n                continue;\n            }\n\n            dmsg(D_TLS_KEYSELECT, \"TLS: tls_update_remote_addr from IP=%s to IP=%s\",\n                 print_link_socket_actual(&ks->remote_addr, &gc),\n                 print_link_socket_actual(addr, &gc));\n\n            ks->remote_addr = *addr;\n        }\n    }\n    gc_free(&gc);\n}\n\nvoid\nshow_available_tls_ciphers(const char *cipher_list, const char *cipher_list_tls13,\n                           const char *tls_cert_profile)\n{\n    printf(\"Available TLS Ciphers, listed in order of preference:\\n\");\n\n    if (tls_version_max() >= TLS_VER_1_3)\n    {\n        printf(\"\\nFor TLS 1.3 and newer (--tls-ciphersuites):\\n\\n\");\n        show_available_tls_ciphers_list(cipher_list_tls13, tls_cert_profile, true);\n    }\n\n    printf(\"\\nFor TLS 1.2 and older (--tls-cipher):\\n\\n\");\n    show_available_tls_ciphers_list(cipher_list, tls_cert_profile, false);\n\n    printf(\"\\n\"\n           \"Note: Whether a cipher suite in this list can actually work depends\\n\"\n           \"on the specific setup of both peers. See the man page entries of\\n\"\n           \"--tls-cipher and --show-tls for more details.\\n\\n\");\n}\n\n/*\n * Dump a human-readable rendition of an openvpn packet\n * into a garbage collectable string which is returned.\n */\nconst char *\nprotocol_dump(struct buffer *buffer, unsigned int flags, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n    struct buffer buf = *buffer;\n\n    uint8_t c;\n    int op;\n    int key_id;\n\n    int tls_auth_hmac_size = (flags & PD_TLS_AUTH_HMAC_SIZE_MASK);\n\n    if (buf.len <= 0)\n    {\n        buf_printf(&out, \"DATA UNDEF len=%d\", buf.len);\n        goto done;\n    }\n\n    if (!(flags & PD_TLS))\n    {\n        goto print_data;\n    }\n\n    /*\n     * Initial byte (opcode)\n     */\n    if (!buf_read(&buf, &c, sizeof(c)))\n    {\n        goto done;\n    }\n    op = (c >> P_OPCODE_SHIFT);\n    key_id = c & P_KEY_ID_MASK;\n    buf_printf(&out, \"%s kid=%d\", packet_opcode_name(op), key_id);\n\n    if ((op == P_DATA_V1) || (op == P_DATA_V2))\n    {\n        goto print_data;\n    }\n\n    /*\n     * Session ID\n     */\n    {\n        struct session_id sid;\n\n        if (!session_id_read(&sid, &buf))\n        {\n            goto done;\n        }\n        if (flags & PD_VERBOSE)\n        {\n            buf_printf(&out, \" sid=%s\", session_id_print(&sid, gc));\n        }\n    }\n\n    /*\n     * tls-auth hmac + packet_id\n     */\n    if (tls_auth_hmac_size)\n    {\n        struct packet_id_net pin;\n        uint8_t tls_auth_hmac[MAX_HMAC_KEY_LENGTH];\n\n        ASSERT(tls_auth_hmac_size <= MAX_HMAC_KEY_LENGTH);\n\n        if (!buf_read(&buf, tls_auth_hmac, tls_auth_hmac_size))\n        {\n            goto done;\n        }\n        if (flags & PD_VERBOSE)\n        {\n            buf_printf(&out, \" tls_hmac=%s\", format_hex(tls_auth_hmac, tls_auth_hmac_size, 0, gc));\n        }\n\n        if (!packet_id_read(&pin, &buf, true))\n        {\n            goto done;\n        }\n        buf_printf(&out, \" pid=%s\", packet_id_net_print(&pin, (flags & PD_VERBOSE), gc));\n    }\n    /*\n     * packet_id + tls-crypt hmac\n     */\n    if (flags & PD_TLS_CRYPT)\n    {\n        struct packet_id_net pin;\n        uint8_t tls_crypt_hmac[TLS_CRYPT_TAG_SIZE];\n\n        if (!packet_id_read(&pin, &buf, true))\n        {\n            goto done;\n        }\n        buf_printf(&out, \" pid=%s\", packet_id_net_print(&pin, (flags & PD_VERBOSE), gc));\n        if (!buf_read(&buf, tls_crypt_hmac, TLS_CRYPT_TAG_SIZE))\n        {\n            goto done;\n        }\n        if (flags & PD_VERBOSE)\n        {\n            buf_printf(&out, \" tls_crypt_hmac=%s\",\n                       format_hex(tls_crypt_hmac, TLS_CRYPT_TAG_SIZE, 0, gc));\n        }\n        /*\n         * Remainder is encrypted and optional wKc\n         */\n        goto done;\n    }\n\n    /*\n     * ACK list\n     */\n    buf_printf(&out, \" %s\", reliable_ack_print(&buf, (flags & PD_VERBOSE), gc));\n\n    if (op == P_ACK_V1)\n    {\n        goto print_data;\n    }\n\n    /*\n     * Packet ID\n     */\n    {\n        packet_id_type l;\n        if (!buf_read(&buf, &l, sizeof(l)))\n        {\n            goto done;\n        }\n        l = ntohpid(l);\n        buf_printf(&out, \" pid=\" packet_id_format, (packet_id_print_type)l);\n    }\n\nprint_data:\n    if (flags & PD_SHOW_DATA)\n    {\n        buf_printf(&out, \" DATA %s\", format_hex(BPTR(&buf), BLEN(&buf), 80, gc));\n    }\n    else\n    {\n        buf_printf(&out, \" DATA len=%d\", buf.len);\n    }\n\ndone:\n    return BSTR(&out);\n}\n"
  },
  {
    "path": "src/openvpn/ssl.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel SSL/Data channel negotiation module\n */\n\n#ifndef OPENVPN_SSL_H\n#define OPENVPN_SSL_H\n\n#include \"basic.h\"\n#include \"common.h\"\n#include \"crypto.h\"\n#include \"packet_id.h\"\n#include \"session_id.h\"\n#include \"reliable.h\"\n#include \"socket.h\"\n#include \"mtu.h\"\n#include \"options.h\"\n#include \"plugin.h\"\n\n#include \"ssl_common.h\"\n#include \"ssl_backend.h\"\n#include \"ssl_pkt.h\"\n#include \"tls_crypt.h\"\n\n/* Used in the TLS PRF function */\n#define KEY_EXPANSION_ID \"OpenVPN\"\n\n/*\n * Set the max number of acknowledgments that can \"hitch a ride\" on an outgoing\n * non-P_ACK_V1 control packet.\n */\n#define CONTROL_SEND_ACK_MAX 4\n\n/*\n * Various timeouts\n */\n#define TLS_MULTI_REFRESH 15 /* call tls_multi_process once every n seconds */\n#define TLS_MULTI_HORIZON                                                             \\\n    2                        /* call tls_multi_process frequently for n seconds after \\\n                              * every packet sent/received action */\n\n/*\n * Buffer sizes (also see mtu.h).\n */\n\n/* Maximum length of OCC options string passed as part of auth handshake */\n#define TLS_OPTIONS_LEN 512\n\n/* Definitions of the bits in the IV_PROTO bitfield\n *\n * In older OpenVPN versions this used in a comparison\n * IV_PROTO >= 2 to determine if DATA_V2 is supported.\n * Therefore any client announcing any of the flags must\n * also announce IV_PROTO_DATA_V2. We also treat bit 0\n * as reserved for this reason */\n\n/** Support P_DATA_V2 */\n#define IV_PROTO_DATA_V2 (1 << 1)\n\n/** Assume client will send a push request and server does not need\n * to wait for a push-request to send a push-reply */\n#define IV_PROTO_REQUEST_PUSH (1 << 2)\n\n/** Supports key derivation via TLS key material exporter [RFC5705] */\n#define IV_PROTO_TLS_KEY_EXPORT (1 << 3)\n\n/** Supports signaling keywords with AUTH_PENDING, e.g. timeout=xy */\n#define IV_PROTO_AUTH_PENDING_KW (1 << 4)\n\n/** Support doing NCP in P2P mode. This mode works by both peers looking at\n * each other's IV_ variables and deterministically deciding both on the same\n * result. */\n#define IV_PROTO_NCP_P2P (1 << 5)\n\n/** Supports the --dns option introduced in version 2.6. Not sent anymore. */\n#define IV_PROTO_DNS_OPTION (1 << 6)\n\n/** Support for explicit exit notify via control channel\n *  This also includes support for the protocol-flags pushed option */\n#define IV_PROTO_CC_EXIT_NOTIFY (1 << 7)\n\n/** Support for AUTH_FAIL,TEMP messages */\n#define IV_PROTO_AUTH_FAIL_TEMP (1 << 8)\n\n/** Support to dynamic tls-crypt (renegotiation with TLS-EKM derived tls-crypt key) */\n#define IV_PROTO_DYN_TLS_CRYPT (1 << 9)\n\n/** Support the extended packet id and epoch format for data channel packets */\n#define IV_PROTO_DATA_EPOCH (1 << 10)\n\n/** Supports the --dns option after all the incompatible changes */\n#define IV_PROTO_DNS_OPTION_V2 (1 << 11)\n\n/** Supports push-update */\n#define IV_PROTO_PUSH_UPDATE (1 << 12)\n\n/* Default field in X509 to be username */\n#define X509_USERNAME_FIELD_DEFAULT \"CN\"\n\n#define KEY_METHOD_2 2\n\n/* key method taken from lower 4 bits */\n#define KEY_METHOD_MASK 0x0F\n\n/*\n * Measure success rate of TLS handshakes, for debugging only\n */\n/* #define MEASURE_TLS_HANDSHAKE_STATS */\n\n/*\n * Prepare the SSL library for use\n */\nvoid init_ssl_lib(void);\n\n/*\n * Free any internal state that the SSL library might have\n */\nvoid free_ssl_lib(void);\n\n/**\n * Build master SSL context object that serves for the whole of OpenVPN\n * instantiation\n */\nstruct tls_root_ctx *init_ssl(const struct options *options, bool in_chroot);\n\n/** @addtogroup control_processor\n *  @{ */\n\n/** @name Functions for initialization and cleanup of tls_multi structures\n *  @{ */\n\n/**\n * Allocate and initialize a \\c tls_multi structure.\n * @ingroup control_processor\n *\n * This function allocates a new \\c tls_multi structure, and performs some\n * amount of initialization.  Afterwards, the \\c tls_multi_init_finalize()\n * function must be called to finalize the structure's initialization\n * process.\n *\n * @param tls_options  - The configuration options to be used for this VPN\n *                       tunnel.\n *\n * @return A newly allocated and initialized \\c tls_multi structure.\n */\nstruct tls_multi *tls_multi_init(struct tls_options *tls_options);\n\n/**\n * Finalize initialization of a \\c tls_multi structure.\n * @ingroup control_processor\n *\n * This function initializes the \\c TM_ACTIVE \\c tls_session, and in\n * server mode also the \\c TM_INITIAL \\c tls_session, associated with\n * this \\c tls_multi structure.  It also configures the control channel's\n * \\c frame structure based on the data channel's \\c frame given in\n * argument \\a frame.\n *\n * @param multi        - The \\c tls_multi structure of which to finalize\n *                       initialization.\n * @param tls_mtu      - maximum allowed size for control channel packets\n */\nvoid tls_multi_init_finalize(struct tls_multi *multi, int tls_mtu);\n\n/*\n * Initialize a standalone tls-auth verification object.\n */\nstruct tls_auth_standalone *tls_auth_standalone_init(struct tls_options *tls_options,\n                                                     struct gc_arena *gc);\n\n/**\n * Frees a standalone tls-auth verification object.\n * @param tas   the object to free. May be NULL.\n */\nvoid tls_auth_standalone_free(struct tls_auth_standalone *tas);\n\n/*\n * Setups the control channel frame size parameters from the data channel\n * parameters\n */\nvoid tls_init_control_channel_frame_parameters(struct frame *frame, int tls_mtu);\n\n/*\n * Set local and remote option compatibility strings.\n * Used to verify compatibility of local and remote option\n * sets.\n */\nvoid tls_multi_init_set_options(struct tls_multi *multi, const char *local, const char *remote);\n\n/**\n * Cleanup a \\c tls_multi structure and free associated memory\n * allocations.\n * @ingroup control_processor\n *\n * This function cleans up a \\c tls_multi structure.  This includes\n * cleaning up all associated \\c tls_session structures.\n *\n * @param multi        - The \\c tls_multi structure to clean up in free.\n * @param clear        - Whether the memory allocated for the \\a multi\n *                       object should be overwritten with 0s.\n */\nvoid tls_multi_free(struct tls_multi *multi, bool clear);\n\n/** @} name Functions for initialization and cleanup of tls_multi structures */\n\n/** @} addtogroup control_processor */\n\n#define TLSMP_INACTIVE  0\n#define TLSMP_ACTIVE    1\n#define TLSMP_KILL      2\n#define TLSMP_RECONNECT 3\n\n/*\n * Called by the top-level event loop.\n *\n * Basically decides if we should call tls_process for\n * the active or untrusted sessions.\n */\nint tls_multi_process(struct tls_multi *multi, struct buffer *to_link,\n                      struct link_socket_actual **to_link_addr,\n                      struct link_socket_info *to_link_socket_info, interval_t *wakeup);\n\n\n/**************************************************************************/\n/**\n * Determine whether an incoming packet is a data channel or control\n * channel packet, and process accordingly.\n * @ingroup external_multiplexer\n *\n * When OpenVPN is in TLS mode, this is the first function to process an\n * incoming packet.  It inspects the packet's one-byte header which\n * contains the packet's opcode and key ID.  Depending on the opcode, the\n * packet is processed as a data channel or as a control channel packet.\n *\n * @par Data channel packets\n *\n * If the opcode indicates the packet is a data channel packet, then the\n * packet's key ID is used to find the local TLS state it is associated\n * with.  This state is checked whether it is active, authenticated, and\n * its remote peer is the source of this packet.  If these checks passed,\n * the state's security parameters are loaded into the \\a opt crypto\n * options so that \\p openvpn_decrypt() can later use them to authenticate\n * and decrypt the packet.\n *\n * This function then returns false.  The \\a buf buffer has not been\n * modified, except for removing the header.\n *\n * @par Control channel packets\n *\n * If the opcode indicates the packet is a control channel packet, then\n * this function will process it based on its plaintext header. depending\n * on the packet's opcode and session ID this function determines if it is\n * destined for an active TLS session, or whether a new TLS session should\n * be started.  This function also initiates data channel session key\n * renegotiation if the received opcode requests that.\n *\n * If the incoming packet is destined for an active TLS session, then the\n * packet is inserted into the Reliability Layer and will be handled\n * later.\n *\n * @param[in]  multi    The TLS multi structure associated with the VPN tunnel\n *     of this packet.\n * @param[in]  from     The source address of the packet.\n * @param[in]  buf      buffer structure containing the incoming packet.\n * @param[out] opt      Returns a crypto options structure with the appropriate\n *     security parameters to handle the packet if it is a data channel packet.\n * @param[in] floated   Set whether the peer is allowed to have floated.\n * @param[out] ad_start Returns a pointer to the start of the authenticated data\n *     of this packet\n *\n * @return\n * @li True if the packet is a control channel packet that has been\n *     processed successfully.\n * @li False if the packet is a data channel packet, or if an error\n *     occurred during processing of a control channel packet.\n */\nbool tls_pre_decrypt(struct tls_multi *multi, const struct link_socket_actual *from,\n                     struct buffer *buf, struct crypto_options **opt, bool floated,\n                     const uint8_t **ad_start);\n\n\n/**************************************************************************/\n/** @name Functions for managing security parameter state for data channel packets\n *  @{ */\n\n\n/**\n * Choose the appropriate security parameters with which to process an\n * outgoing packet.\n * @ingroup data_crypto\n *\n * If no appropriate security parameters can be found, or if some other\n * error occurs, then the buffer is set to empty, and the parameters to a NULL\n * pointer.\n *\n * @param multi - The TLS state for this packet's destination VPN tunnel.\n * @param buf - The buffer containing the outgoing packet.\n * @param opt - Returns a crypto options structure with the security parameters.\n */\nvoid tls_pre_encrypt(struct tls_multi *multi, struct buffer *buf, struct crypto_options **opt);\n\n/**\n * Selects the primary encryption that should be used to encrypt data of an\n * outgoing packet.\n * @ingroup data_crypto\n *\n * If no key is found NULL is returned instead.\n *\n * @param multi - The TLS state for this packet's destination VPN tunnel.\n */\nstruct key_state *tls_select_encryption_key(struct tls_multi *multi);\n\n/**\n * Prepend a one-byte OpenVPN data channel P_DATA_V1 opcode to the packet.\n *\n * The opcode identifies the packet as a V1 data channel packet and gives the\n * low-permutation version of the key-id to the recipient, so it knows which\n * decrypt key to use.\n *\n * @param multi - The TLS state for this packet's destination VPN tunnel.\n * @param buf - The buffer to write the header to.\n *\n * @ingroup data_crypto\n */\nvoid tls_prepend_opcode_v1(const struct tls_multi *multi, struct buffer *buf);\n\n/**\n * Prepend an OpenVPN data channel P_DATA_V2 header to the packet.  The\n * P_DATA_V2 header consists of a 1-byte opcode, followed by a 3-byte peer-id.\n *\n * The opcode identifies the packet as a V2 data channel packet and gives the\n * low-permutation version of the key-id to the recipient, so it knows which\n * decrypt key to use.\n *\n * The peer-id is sent by clients to servers to help the server determine to\n * select the decrypt key when the client is roaming between addresses/ports.\n *\n * @param multi - The TLS state for this packet's destination VPN tunnel.\n * @param buf - The buffer to write the header to.\n *\n * @ingroup data_crypto\n */\nvoid tls_prepend_opcode_v2(const struct tls_multi *multi, struct buffer *buf);\n\n/**\n * Perform some accounting for the key state used.\n * @ingroup data_crypto\n *\n * @param multi - The TLS state for this packet's destination VPN tunnel.\n * @param buf - The buffer containing the outgoing packet.\n */\nvoid tls_post_encrypt(struct tls_multi *multi, struct buffer *buf);\n\n/** @} name Functions for managing security parameter state for data channel packets */\n\n/*\n * Setup private key file password. If auth_file is given, use the\n * credentials stored in the file.\n */\nvoid pem_password_setup(const char *auth_file);\n\n/* Enables the use of user/password authentication */\nvoid enable_auth_user_pass(void);\n\n/*\n * Setup authentication username and password. If auth_file is given, use the\n * credentials stored in the file, however, if is_inline is true then auth_file\n * contains the username/password inline.\n */\nvoid auth_user_pass_setup(const char *auth_file, bool is_inline,\n                          const struct static_challenge_info *sc_info);\n\n/*\n * Ensure that no caching is performed on authentication information\n */\nvoid ssl_set_auth_nocache(void);\n\n/*\n * Getter method for retrieving the auth-nocache option.\n */\nbool ssl_get_auth_nocache(void);\n\n/*\n * Purge any stored authentication information, both for key files and tunnel\n * authentication. If PCKS #11 is enabled, purge authentication for that too.\n * Note that auth_token is not cleared.\n */\nvoid ssl_purge_auth(const bool auth_user_pass_only);\n\nvoid ssl_set_auth_token(const char *token);\n\nvoid ssl_set_auth_token_user(const char *username);\n\nbool ssl_clean_auth_token(void);\n\n#ifdef ENABLE_MANAGEMENT\n\nvoid ssl_purge_auth_challenge(void);\n\nvoid ssl_put_auth_challenge(const char *cr_str);\n\n#endif\n\n/*\n * Send a payload over the TLS control channel\n */\nbool tls_send_payload(struct key_state *ks, const uint8_t *data, size_t size);\n\n/*\n * Receive a payload through the TLS control channel\n */\nbool tls_rec_payload(struct tls_multi *multi, struct buffer *buf);\n\n/**\n * Updates remote address in TLS sessions.\n *\n * @param multi - Tunnel to update\n * @param addr - new address\n */\nvoid tls_update_remote_addr(struct tls_multi *multi, const struct link_socket_actual *addr);\n\n/**\n * Update TLS session crypto parameters (cipher and auth) and derive data\n * channel keys based on the supplied options. Does nothing if keys are already\n * generated.\n *\n * @param multi           The TLS object for this instance.\n * @param session         The TLS session to update.\n * @param options         The options to use when updating session.\n * @param frame           The frame options for this session (frame overhead is\n *                        adjusted based on the selected cipher/auth).\n * @param frame_fragment  The fragment frame options.\n * @param lsi             link socket info to adjust MTU related options\n *                        depending on the current protocol\n * @param dco             The dco context to perform dco_set_peer()\n *                        whenever a crypto param update occurs.\n *\n * @return true if updating succeeded or keys are already generated, false otherwise.\n */\nbool tls_session_update_crypto_params(struct tls_multi *multi, struct tls_session *session,\n                                      struct options *options, struct frame *frame,\n                                      struct frame *frame_fragment, struct link_socket_info *lsi,\n                                      dco_context_t *dco);\n\n/*\n * inline functions\n */\n\n/** Free the elements of a tls_wrap_ctx structure */\nstatic inline void\ntls_wrap_free(struct tls_wrap_ctx *tls_wrap)\n{\n    if (packet_id_initialized(&tls_wrap->opt.packet_id))\n    {\n        packet_id_free(&tls_wrap->opt.packet_id);\n    }\n\n    if (tls_wrap->cleanup_key_ctx)\n    {\n        free_key_ctx_bi(&tls_wrap->opt.key_ctx_bi);\n    }\n\n    free_buf(&tls_wrap->tls_crypt_v2_metadata);\n    free_buf(&tls_wrap->work);\n    secure_memzero(&tls_wrap->original_wrap_keydata, sizeof(tls_wrap->original_wrap_keydata));\n}\n\nstatic inline bool\ntls_initial_packet_received(const struct tls_multi *multi)\n{\n    return multi->n_sessions > 0;\n}\n\nstatic inline int\ntls_test_payload_len(const struct tls_multi *multi)\n{\n    if (multi)\n    {\n        const struct key_state *ks = get_primary_key(multi);\n        if (ks->state >= S_ACTIVE)\n        {\n            return BLEN(&ks->plaintext_read_buf);\n        }\n    }\n    return 0;\n}\n\nstatic inline void\ntls_set_single_session(struct tls_multi *multi)\n{\n    if (multi)\n    {\n        multi->opt.single_session = true;\n    }\n}\n\n/*\n * protocol_dump() flags\n */\n#define PD_TLS_AUTH_HMAC_SIZE_MASK 0xFF\n#define PD_SHOW_DATA               (1 << 8)\n#define PD_TLS                     (1 << 9)\n#define PD_VERBOSE                 (1 << 10)\n#define PD_TLS_CRYPT               (1 << 11)\n\nconst char *protocol_dump(struct buffer *buffer, unsigned int flags, struct gc_arena *gc);\n\n/*\n * debugging code\n */\n\n#ifdef MEASURE_TLS_HANDSHAKE_STATS\nvoid show_tls_performance_stats(void);\n\n#endif\n\n/**\n * Given a key_method, return true if opcode represents the one of the\n * hard_reset op codes for key-method 2\n *\n */\nbool is_hard_reset_method2(int op);\n\n/*\n * Show the TLS ciphers that are available for us to use in the SSL\n * library with headers hinting their usage and warnings about usage.\n *\n * @param cipher_list       list of allowed TLS cipher, or NULL.\n * @param cipher_list_tls13 list of allowed TLS 1.3+ cipher, or NULL\n * @param tls_cert_profile  TLS certificate crypto profile name.\n */\nvoid show_available_tls_ciphers(const char *cipher_list, const char *cipher_list_tls13,\n                                const char *tls_cert_profile);\n\n\n/**\n * Generate data channel keys for the supplied TLS session.\n *\n * This erases the source material used to generate the data channel keys, and\n * can thus be called only once per session.\n */\nbool tls_session_generate_data_channel_keys(struct tls_multi *multi, struct tls_session *session);\n\nvoid tls_session_soft_reset(struct tls_multi *multi);\n\n/**\n * Load ovpn.xkey provider used for external key signing\n */\nvoid load_xkey_provider(void);\n\n/* Special method to skip the three way handshake RESET stages. This is\n * used by the HMAC code when seeing a packet that matches the previous\n * HMAC based stateless server state */\nbool session_skip_to_pre_start(struct tls_session *session, struct tls_pre_decrypt_state *state,\n                               struct link_socket_actual *from);\n\n#endif /* ifndef OPENVPN_SSL_H */\n"
  },
  {
    "path": "src/openvpn/ssl_backend.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel SSL library backend module\n */\n\n\n#ifndef SSL_BACKEND_H_\n#define SSL_BACKEND_H_\n\n#include \"buffer.h\"\n\n#ifdef ENABLE_CRYPTO_OPENSSL\n#include \"ssl_openssl.h\"\n#include \"ssl_verify_openssl.h\"\n#define SSLAPI SSLAPI_OPENSSL\n#endif\n#ifdef ENABLE_CRYPTO_MBEDTLS\n#include \"ssl_mbedtls.h\"\n#include \"ssl_verify_mbedtls.h\"\n#define SSLAPI SSLAPI_MBEDTLS\n#endif\n\n/* Ensure that SSLAPI got a sane value if SSL is disabled or unknown */\n#ifndef SSLAPI\n#define SSLAPI SSLAPI_NONE\n#endif\n\n/**\n *  prototype for struct tls_session from ssl_common.h\n */\nstruct tls_session;\n\n/*\n *\n * Functions implemented in ssl.c for use by the backend SSL library\n *\n */\n\n/**\n * Callback to retrieve the user's password\n *\n * @param buf           Buffer to return the password in\n * @param size          Size of the buffer\n * @param rwflag        Unused, needed for OpenSSL compatibility\n * @param u             Unused, needed for OpenSSL compatibility\n */\nint pem_password_callback(char *buf, int size, int rwflag, void *u);\n\n/*\n *\n * Functions used in ssl.c which must be implemented by the backend SSL library\n *\n */\n\n/**\n * Perform any static initialisation necessary by the library.\n * Called on OpenVPN initialisation\n */\nvoid tls_init_lib(void);\n\n/**\n * Free any global SSL library-specific data structures.\n */\nvoid tls_free_lib(void);\n\n/**\n * Clear the underlying SSL library's error state.\n */\nvoid tls_clear_error(void);\n\n/**\n * Parse a TLS version specifier\n *\n * @param vstr          The TLS version string\n * @param extra         An optional extra parameter, may be NULL\n *\n * @return              One of the TLS_VER_x constants or TLS_VER_BAD\n *                      if a parse error should be flagged.\n */\n#define TLS_VER_BAD    -1\n#define TLS_VER_UNSPEC 0 /* default */\n#define TLS_VER_1_0    1\n#define TLS_VER_1_1    2\n#define TLS_VER_1_2    3\n#define TLS_VER_1_3    4\nint tls_version_parse(const char *vstr, const char *extra);\n\n/**\n * Return the maximum TLS version (as a TLS_VER_x constant)\n * supported by current SSL implementation\n *\n * @return              One of the TLS_VER_x constants (but not TLS_VER_BAD).\n */\nint tls_version_max(void);\n\n/**\n * Initialise a library-specific TLS context for a server.\n *\n * @param ctx           TLS context to initialise\n */\nvoid tls_ctx_server_new(struct tls_root_ctx *ctx);\n\n/**\n * Initialises a library-specific TLS context for a client.\n *\n * @param ctx           TLS context to initialise\n */\nvoid tls_ctx_client_new(struct tls_root_ctx *ctx);\n\n/**\n * Frees the library-specific TLSv1 context\n *\n * @param ctx           TLS context to free\n */\nvoid tls_ctx_free(struct tls_root_ctx *ctx);\n\n/**\n * Checks whether the given TLS context is initialised\n *\n * @param ctx           TLS context to check\n *\n * @return      true if the context is initialised, false if not.\n */\nbool tls_ctx_initialised(struct tls_root_ctx *ctx);\n\n/**\n * Set any library specific options.\n *\n * Examples include disabling session caching, the password callback to use,\n * and session verification parameters.\n *\n * @param ctx           TLS context to set options on\n * @param ssl_flags     SSL flags to set\n *\n * @return true on success, false otherwise.\n */\nbool tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags);\n\n/**\n * Restrict the list of ciphers that can be used within the TLS context for TLS 1.2\n * and below\n *\n * @param ctx           TLS context to restrict, must be valid.\n * @param ciphers       String containing : delimited cipher names, or NULL to use\n *                                      sane defaults.\n */\nvoid tls_ctx_restrict_ciphers(struct tls_root_ctx *ctx, const char *ciphers);\n\n/**\n * Restrict the list of ciphers that can be used within the TLS context for TLS 1.3\n * and higher\n *\n * @param ctx           TLS context to restrict, must be valid.\n * @param ciphers       String containing : delimited cipher names, or NULL to use\n *                                      sane defaults.\n */\nvoid tls_ctx_restrict_ciphers_tls13(struct tls_root_ctx *ctx, const char *ciphers);\n\n/**\n * Set the TLS certificate profile.  The profile defines which crypto\n * algorithms may be used in the supplied certificate.\n *\n * @param ctx           TLS context to restrict, must be valid.\n * @param profile       The profile name ('preferred', 'legacy' or 'suiteb').\n *                      Defaults to 'preferred' if NULL.\n */\nvoid tls_ctx_set_cert_profile(struct tls_root_ctx *ctx, const char *profile);\n\n/**\n * Set the (elliptic curve) group allowed for signatures and\n * key exchange.\n *\n * @param ctx       TLS context to restrict, must be valid.\n * @param groups    List of groups that will be allowed, in priority,\n *                  separated by :\n */\nvoid tls_ctx_set_tls_groups(struct tls_root_ctx *ctx, const char *groups);\n\n/**\n * Check our certificate notBefore and notAfter fields, and warn if the cert is\n * either not yet valid or has expired.  Note that this is a non-fatal error,\n * since we compare against the system time, which might be incorrect.\n *\n * @param ctx           TLS context to get our certificate from.\n */\nvoid tls_ctx_check_cert_time(const struct tls_root_ctx *ctx);\n\n/**\n * Load Diffie Hellman Parameters, and load them into the library-specific\n * TLS context.\n *\n * @param ctx                   TLS context to use\n * @param dh_file               The file name to load the parameters from, or\n *                              a string containing the parameters in the case\n *                              of inline files.\n * @param dh_file_inline        True if dh_file is an inline file.\n */\nvoid tls_ctx_load_dh_params(struct tls_root_ctx *ctx, const char *dh_file, bool dh_file_inline);\n\n/**\n * Load Elliptic Curve Parameters, and load them into the library-specific\n * TLS context.\n *\n * @param ctx          TLS context to use\n * @param curve_name   The name of the elliptic curve to load.\n */\nvoid tls_ctx_load_ecdh_params(struct tls_root_ctx *ctx, const char *curve_name);\n\n/**\n * Load PKCS #12 file for key, cert and (optionally) CA certs, and add to\n * library-specific TLS context.\n *\n * @param ctx                   TLS context to use\n * @param pkcs12_file           The file name to load the information from, or\n *                              a string containing the information in the case\n *                              of inline files.\n * @param pkcs12_file_inline    True if pkcs12_file is an inline file.\n * @param load_ca_file          True if CAs from the file should be added to\n *                              the cert store and be trusted.\n *\n * @return                      1 if an error occurred, 0 if parsing was\n *                              successful.\n */\nint tls_ctx_load_pkcs12(struct tls_root_ctx *ctx, const char *pkcs12_file, bool pkcs12_file_inline,\n                        bool load_ca_file);\n\n/**\n * Use Windows cryptoapi for key and cert, and add to library-specific TLS\n * context.\n *\n * @param ctx                   TLS context to use\n * @param cryptoapi_cert       String representing the certificate to load.\n */\n#ifdef ENABLE_CRYPTOAPI\nvoid tls_ctx_load_cryptoapi(struct tls_root_ctx *ctx, const char *cryptoapi_cert);\n\n#endif /* _WIN32 */\n\n/**\n * Load certificate file into the given TLS context. If the given certificate\n * file contains a certificate chain, load the whole chain.\n *\n * @param ctx                   TLS context to use\n * @param cert_file             The file name to load the certificate from, or\n *                              a string containing the certificate in the case\n *                              of inline files.\n * @param cert_file_inline      True if cert_file is an inline file.\n */\nvoid tls_ctx_load_cert_file(struct tls_root_ctx *ctx, const char *cert_file, bool cert_file_inline);\n\n/**\n * Load private key file into the given TLS context.\n *\n * @param ctx                   TLS context to use\n * @param priv_key_file         The file name to load the private key from, or\n *                              a string containing the private key in the case\n *                              of inline files.\n * @param priv_key_file_inline  True if priv_key_file is an inline file\n *\n * @return                      1 if an error occurred, 0 if parsing was\n *                              successful.\n */\nint tls_ctx_load_priv_file(struct tls_root_ctx *ctx, const char *priv_key_file,\n                           bool priv_key_file_inline);\n\n#ifdef ENABLE_MANAGEMENT\n\n/**\n * Tell the management interface to load the given certificate and the external\n * private key matching the given certificate.\n *\n * @param ctx                   TLS context to use\n *\n * @return                      1 if an error occurred, 0 if successful.\n */\nint tls_ctx_use_management_external_key(struct tls_root_ctx *ctx);\n\n#endif /* ENABLE_MANAGEMENT */\n\n/**\n * Load certificate authority certificates from the given file or path.\n *\n * Note that not all SSL libraries support loading from a path.\n *\n * @param ctx                   TLS context to use\n * @param ca_file               The file name to load the CAs from, or\n *                              a string containing the CAs in the case of\n *                              inline files.\n * @param ca_file_inline        True if ca_file is an inline file\n * @param ca_path               The path to load the CAs from\n * @param tls_server            True if we are the server side of the TLS\n *                              connection and should use the CA for verifying\n *                              client certificates\n */\nvoid tls_ctx_load_ca(struct tls_root_ctx *ctx, const char *ca_file, bool ca_file_inline,\n                     const char *ca_path, bool tls_server);\n\n/**\n * Load extra certificate authority certificates from the given file or path.\n * These Load extra certificates that are part of our own certificate\n * chain but shouldn't be included in the verify chain.\n *\n *\n * @param ctx                           TLS context to use\n * @param extra_certs_file              The file name to load the certs from, or\n *                                      a string containing the certs in the\n *                                      case of inline files.\n * @param extra_certs_file_inline       True if extra_certs_file is an inline\n *                                      file.\n */\nvoid tls_ctx_load_extra_certs(struct tls_root_ctx *ctx, const char *extra_certs_file,\n                              bool extra_certs_file_inline);\n\n#ifdef ENABLE_CRYPTO_MBEDTLS\n/**\n * Add a personalisation string to the mbed TLS RNG, based on the certificate\n * loaded into the given context.\n *\n * @param ctx                   TLS context to use\n */\nvoid tls_ctx_personalise_random(struct tls_root_ctx *ctx);\n\n#endif\n\n/* **************************************\n *\n * Key-state specific functions\n *\n ***************************************/\n\n/**\n * Initialise the SSL channel part of the given key state. Settings will be\n * loaded from a previously initialised TLS context.\n *\n * @param ks_ssl        The SSL channel's state info to initialise\n * @param ssl_ctx       The TLS context to use when initialising the channel.\n * @param is_server     Initialise a server?\n * @param session       The session associated with the given key_state\n */\nvoid key_state_ssl_init(struct key_state_ssl *ks_ssl, const struct tls_root_ctx *ssl_ctx,\n                        bool is_server, struct tls_session *session);\n\n/**\n * Sets a TLS session to be shutdown state, so the TLS library will generate\n * a shutdown alert.\n */\nvoid key_state_ssl_shutdown(struct key_state_ssl *ks_ssl);\n\n/**\n * Free the SSL channel part of the given key state.\n *\n * @param ks_ssl        The SSL channel's state info to free\n */\nvoid key_state_ssl_free(struct key_state_ssl *ks_ssl);\n\n/**\n * Reload the Certificate Revocation List for the SSL channel\n *\n * @param ssl_ctx       The TLS context to use when reloading the CRL\n * @param crl_file      The file name to load the CRL from, or\n *                      an array containing the inline CRL.\n * @param crl_inline    True if crl_file is an inline CRL.\n */\nvoid backend_tls_ctx_reload_crl(struct tls_root_ctx *ssl_ctx, const char *crl_file,\n                                bool crl_inline);\n\n#define EXPORT_KEY_DATA_LABEL          \"EXPORTER-OpenVPN-datakeys\"\n#define EXPORT_P2P_PEERID_LABEL        \"EXPORTER-OpenVPN-p2p-peerid\"\n#define EXPORT_DYNAMIC_TLS_CRYPT_LABEL \"EXPORTER-OpenVPN-dynamic-tls-crypt\"\n/**\n * Keying Material Exporters [RFC 5705] allows additional keying material to be\n * derived from existing TLS channel. This exported keying material can then be\n * used for a variety of purposes.\n *\n * @param session      The session associated with the given key_state\n * @param label        The label to use when exporting the key\n * @param label_size   The size of the label to use when exporting the key\n * @param ekm          Buffer to return the exported key material in\n * @param ekm_size     The size of ekm, in bytes\n * @returns            true if exporting succeeded, false otherwise\n */\nbool key_state_export_keying_material(struct tls_session *session, const char *label,\n                                      size_t label_size, void *ekm, size_t ekm_size);\n\n/**************************************************************************/\n/** @addtogroup control_tls\n *  @{ */\n\n/** @name Functions for packets to be sent to a remote OpenVPN peer\n *  @{ */\n\n/**\n * Insert a plaintext buffer into the TLS module.\n *\n * After successfully processing the data, the data in \\a buf is zeroized,\n * its length set to zero, and a value of \\c 1 is returned.\n *\n * @param ks_ssl       - The security parameter state for this %key\n *                       session.\n * @param buf          - The plaintext message to process.\n *\n * @return The return value indicates whether the data was successfully\n *     processed:\n * - \\c 1: All the data was processed successfully.\n * - \\c 0: The data was not processed, this function should be called\n *   again later to retry.\n * - \\c -1: An error occurred.\n */\nint key_state_write_plaintext(struct key_state_ssl *ks_ssl, struct buffer *buf);\n\n/**\n * Insert plaintext data into the TLS module.\n *\n * @param ks_ssl       - The security parameter state for this %key\n *                       session.\n * @param data         - A pointer to the data to process.\n * @param len          - The length in bytes of the data to process.\n *\n * @return The return value indicates whether the data was successfully\n *     processed:\n * - \\c 1: All the data was processed successfully.\n * - \\c 0: The data was not processed, this function should be called\n *   again later to retry.\n * - \\c -1: An error occurred.\n */\nint key_state_write_plaintext_const(struct key_state_ssl *ks_ssl, const uint8_t *data, int len);\n\n/**\n * Extract ciphertext data from the TLS module.\n *\n * If the \\a buf buffer has a length other than zero, this function does\n * not perform any action and returns 0.\n *\n * @param ks_ssl       - The security parameter state for this %key\n *                       session.\n * @param buf          - A buffer in which to store the ciphertext.\n *\n * @return The return value indicates whether the data was successfully\n *     processed:\n * - \\c 1: Data was extracted successfully.\n * - \\c 0: No data was extracted, this function should be called again\n *   later to retry.\n * - \\c -1: An error occurred.\n */\nint key_state_read_ciphertext(struct key_state_ssl *ks_ssl, struct buffer *buf);\n\n\n/** @} name Functions for packets to be sent to a remote OpenVPN peer */\n\n\n/** @name Functions for packets received from a remote OpenVPN peer\n *  @{ */\n\n/**\n * Insert a ciphertext buffer into the TLS module.\n *\n * After successfully processing the data, the data in \\a buf is zeroized,\n * its length set to zero, and a value of \\c 1 is returned.\n *\n * @param ks_ssl       - The security parameter state for this %key\n *                       session.\n * @param buf          - The ciphertext message to process.\n *\n * @return The return value indicates whether the data was successfully\n *     processed:\n * - \\c 1: All the data was processed successfully.\n * - \\c 0: The data was not processed, this function should be called\n *   again later to retry.\n * - \\c -1: An error occurred.\n */\nint key_state_write_ciphertext(struct key_state_ssl *ks_ssl, struct buffer *buf);\n\n/**\n * Extract plaintext data from the TLS module.\n *\n * If the \\a buf buffer has a length other than zero, this function does\n * not perform any action and returns 0.\n *\n * @param ks_ssl       - The security parameter state for this %key\n *                       session.\n * @param buf          - A buffer in which to store the plaintext.\n *\n * @return The return value indicates whether the data was successfully\n *     processed:\n * - \\c 1: Data was extracted successfully.\n * - \\c 0: No data was extracted, this function should be called again\n *   later to retry.\n * - \\c -1: An error occurred.\n */\nint key_state_read_plaintext(struct key_state_ssl *ks_ssl, struct buffer *buf);\n\n/** @} name Functions for packets received from a remote OpenVPN peer */\n\n/** @} addtogroup control_tls */\n\n/* **************************************\n *\n * Information functions\n *\n * Print information for the end user.\n *\n ***************************************/\n\n/**\n * Print a one line summary of SSL/TLS session handshake.\n */\nvoid print_details(struct key_state_ssl *ks_ssl, const char *prefix);\n\n/**\n * Show the TLS ciphers that are available for us to use in the\n * library depending on the TLS version. This function prints\n * a list of ciphers without headers/footers.\n *\n * @param cipher_list       list of allowed TLS cipher, or NULL.\n * @param tls_cert_profile  TLS certificate crypto profile name.\n * @param tls13             Select if <=TLS1.2 or TLS1.3+ ciphers\n *                          should be shown\n */\nvoid show_available_tls_ciphers_list(const char *cipher_list, const char *tls_cert_profile,\n                                     bool tls13);\n\n/**\n * Show the available elliptic curves in the crypto library\n */\nvoid show_available_curves(void);\n\n/**\n * return a pointer to a static memory area containing the\n * name and version number of the SSL library in use\n */\nconst char *get_ssl_library_version(void);\n\n#endif /* SSL_BACKEND_H_ */\n"
  },
  {
    "path": "src/openvpn/ssl_common.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel Common Data Structures\n */\n\n#ifndef SSL_COMMON_H_\n#define SSL_COMMON_H_\n\n#include \"session_id.h\"\n#include \"socket_util.h\"\n#include \"packet_id.h\"\n#include \"crypto.h\"\n#include \"options.h\"\n\n#include \"ssl_backend.h\"\n\n/* passwords */\n#define UP_TYPE_AUTH        \"Auth\"\n#define UP_TYPE_PRIVATE_KEY \"Private Key\"\n\n/** @addtogroup control_processor\n *  @{ */\n/**\n * @name Control channel negotiation states\n *\n * These states represent the different phases of control channel\n * negotiation between OpenVPN peers.  OpenVPN servers and clients\n * progress through the states in a different order, because of their\n * different roles during exchange of random material.  The references to\n * the \\c key_source2 structure in the list below is only valid if %key\n * method 2 is being used.  See the \\link key_generation data channel key\n * generation\\endlink related page for more information.\n *\n * Clients follow this order:\n *   -# \\c S_INITIAL, ready to begin three-way handshake and control\n *      channel negotiation.\n *   -# \\c S_PRE_START, have started three-way handshake, waiting for\n *      acknowledgment from remote.\n *   -# \\c S_START, initial three-way handshake complete.\n *   -# \\c S_SENT_KEY, have sent local part of \\c key_source2 random\n *      material.\n *   -# \\c S_GOT_KEY, have received remote part of \\c key_source2 random\n *      material.\n *   -# \\c S_ACTIVE, control channel successfully established\n *   -# \\c S_GENERATED_KEYS, the data channel keys have been generated\n *\n * Servers follow the same order, except for \\c S_SENT_KEY and \\c\n * S_GOT_KEY being reversed, because the server first receives the\n * client's \\c key_source2 random material before generating and sending\n * its own.\n *\n * @{\n */\n/* clang-format off */\n#define S_ERROR         (-2)    /**< Error state.  */\n#define S_ERROR_PRE     (-1)    /**< Error state but try to send out alerts\n                                 *  before killing the keystore and moving\n                                 *  it to S_ERROR */\n#define S_UNDEF           0     /**< Undefined state, used after a \\c\n                                 *   key_state is cleaned up. */\n#define S_INITIAL         1     /**< Initial \\c key_state state after\n                                 *   initialization by \\c key_state_init()\n                                 *   before start of three-way handshake. */\n#define S_PRE_START_SKIP  2     /**< Waiting for the remote OpenVPN peer\n                                 *   to acknowledge during the initial\n                                 *   three-way handshake. */\n#define S_PRE_START       3     /**< Waiting for the remote OpenVPN peer\n                                 *   to acknowledge during the initial\n                                 *   three-way handshake. */\n#define S_START           4     /**< Three-way handshake is complete,\n                                 *   start of key exchange. */\n#define S_SENT_KEY        5     /**< Local OpenVPN process has sent its\n                                 *   part of the key material. */\n#define S_GOT_KEY         6     /**< Local OpenVPN process has received\n                                 *   the remote's part of the key\n                                 *   material. */\n#define S_ACTIVE          7     /**< Operational \\c key_state state\n                                 *   immediately after negotiation has\n                                 *   completed while still within the\n                                 *   handshake window.  Deferred auth and\n                                 *   client connect can still be pending. */\n#define S_GENERATED_KEYS  8     /**< The data channel keys have been generated\n                                 *  The TLS session is fully authenticated\n                                 *  when reaching this state. */\n/* clang-format on */\n/* Note that earlier versions also had a S_OP_NORMAL state that was\n * virtually identical with S_ACTIVE and the code still assumes everything\n * >= S_ACTIVE to be fully operational */\n/** @} name Control channel negotiation states */\n/** @} addtogroup control_processor */\n\n/**\n * Container for one half of random material to be used in %key method 2\n * \\ref key_generation \"data channel key generation\".\n * @ingroup control_processor\n */\nstruct key_source\n{\n    uint8_t pre_master[48]; /**< Random used for master secret\n                             *   generation, provided only by client\n                             *   OpenVPN peer. */\n    uint8_t random1[32];    /**< Seed used for master secret\n                             *   generation, provided by both client\n                             *   and server. */\n    uint8_t random2[32];    /**< Seed used for key expansion, provided\n                             *   by both client and server. */\n};\n\n\n/**\n * Container for both halves of random material to be used in %key method\n * 2 \\ref key_generation \"data channel key generation\".\n * @ingroup control_processor\n */\nstruct key_source2\n{\n    struct key_source client; /**< Random provided by client. */\n    struct key_source server; /**< Random provided by server. */\n};\n\n\n/**\n * This reflects the (server side) authentication state after the TLS\n * session has been established and key_method_2_read is called. If async auth\n * is enabled the state will first move to KS_AUTH_DEFERRED before eventually\n * being set to KS_AUTH_TRUE or KS_AUTH_FALSE\n * Only KS_AUTH_TRUE is fully authenticated\n */\nenum ks_auth_state\n{\n    KS_AUTH_FALSE,    /**< Key state is not authenticated  */\n    KS_AUTH_DEFERRED, /**< Key state authentication is being deferred,\n                       * by async auth */\n    KS_AUTH_TRUE      /**< Key state is authenticated. TLS and user/pass\n                       * succeeded. This includes AUTH_PENDING/OOB\n                       * authentication as those hold the\n                       * connection artificially in KS_AUTH_DEFERRED\n                       */\n};\n\nstruct auth_deferred_status\n{\n    char *auth_control_file;\n    char *auth_pending_file;\n    char *auth_failed_reason_file;\n    unsigned int auth_control_status;\n};\n\n/* key_state_test_auth_control_file return values, these specify the\n * current status of a deferred authentication */\nenum auth_deferred_result\n{\n    ACF_PENDING,   /**< deferred auth still pending */\n    ACF_SUCCEEDED, /**< deferred auth has suceeded */\n    ACF_DISABLED,  /**< deferred auth is not used */\n    ACF_FAILED     /**< deferred auth has failed */\n};\n\nenum dco_key_status\n{\n    DCO_NOT_INSTALLED,\n    DCO_INSTALLED_PRIMARY,\n    DCO_INSTALLED_SECONDARY\n};\n\n/**\n * Security parameter state of one TLS and data channel %key session.\n * @ingroup control_processor\n *\n * This structure represents one security parameter session between\n * OpenVPN peers.  It includes the control channel TLS state and the data\n * channel crypto state.  It also contains the reliability layer\n * structures used for control channel messages.\n *\n * A new \\c key_state structure is initialized for each hard or soft\n * reset.\n *\n * @see\n *  - This structure should be initialized using the \\c key_state_init()\n *    function.\n *  - This structure should be cleaned up using the \\c key_state_free()\n *    function.\n */\nstruct key_state\n{\n    int state;\n    /** The state of the auth-token sent from the client */\n    unsigned int auth_token_state_flags;\n\n    /**\n     * Key id for this key_state,  inherited from struct tls_session.\n     * @see tls_session::key_id.\n     */\n    int key_id;\n\n    /**\n     * Key id for this key_state, inherited from struct tls_session.\n     * @see tls_multi::peer_id.\n     */\n    uint32_t peer_id;\n\n    struct key_state_ssl ks_ssl;           /* contains SSL object and BIOs for the control channel */\n\n    time_t initial;                        /* when we created this session */\n    time_t established;                    /* when our state went S_ACTIVE */\n    time_t must_negotiate;                 /* key negotiation times out if not finished before this time */\n    time_t must_die;                       /* this object is destroyed at this time */\n    time_t peer_last_packet;               /* Last time we received a packet in this control session */\n\n    int initial_opcode;                    /* our initial P_ opcode */\n    struct session_id session_id_remote;   /* peer's random session ID */\n    struct link_socket_actual remote_addr; /* peer's IP addr */\n\n    struct crypto_options crypto_options;  /* data channel crypto options */\n\n    struct key_source2 *key_src;           /* source entropy for key expansion */\n\n    struct buffer plaintext_read_buf;\n    struct buffer plaintext_write_buf;\n    struct buffer ack_write_buf;\n\n    struct reliable *send_reliable; /* holds a copy of outgoing packets until ACK received */\n    struct reliable *rec_reliable;  /* order incoming ciphertext packets before we pass to TLS */\n    struct reliable_ack *rec_ack;   /* buffers all packet IDs we want to ACK back to sender */\n    struct reliable_ack *lru_acks;  /* keeps the most recently acked packages*/\n\n    /** Holds outgoing message for the control channel until ks->state reaches\n     * S_ACTIVE */\n    struct buffer_list *paybuf;\n    counter_type n_bytes;   /* how many bytes sent/recvd since last key exchange */\n    counter_type n_packets; /* how many packets sent/recvd since last key exchange */\n\n    /*\n     * If bad username/password, TLS connection will come up but 'authenticated' will be false.\n     */\n    enum ks_auth_state authenticated;\n    time_t auth_deferred_expire;\n\n#ifdef ENABLE_MANAGEMENT\n    unsigned int mda_key_id;\n    enum auth_deferred_result mda_status;\n#endif\n    time_t acf_last_mod;\n\n    struct auth_deferred_status plugin_auth;\n    struct auth_deferred_status script_auth;\n\n    enum dco_key_status dco_status;\n};\n\n/** Control channel wrapping (--tls-auth/--tls-crypt) context */\nstruct tls_wrap_ctx\n{\n    enum\n    {\n        TLS_WRAP_NONE = 0,                  /**< No control channel wrapping */\n        TLS_WRAP_AUTH,                      /**< Control channel authentication */\n        TLS_WRAP_CRYPT,                     /**< Control channel encryption and authentication */\n    } mode;                                 /**< Control channel wrapping mode */\n    struct crypto_options opt;              /**< Crypto state */\n    struct buffer work;                     /**< Work buffer (only for --tls-crypt) */\n    struct key_ctx tls_crypt_v2_server_key; /**< Decrypts client keys */\n    const struct buffer *tls_crypt_v2_wkc;  /**< Wrapped client key,\n                                             *   sent to server */\n    struct buffer tls_crypt_v2_metadata;    /**< Received from client */\n    bool cleanup_key_ctx;                   /**< opt.key_ctx_bi is owned by\n                                             *   this context */\n    /** original key data to be xored in to the key for dynamic tls-crypt.\n     *\n     * We keep the original key data to ensure that the newly generated key\n     * for the dynamic tls-crypt has the same level of quality by using\n     * xor with the original key. This gives us the same same entropy/randomness\n     * as the original tls-crypt key to ensure the post-quantum use case of\n     * tls-crypt still holds true\n     * */\n    struct key2 original_wrap_keydata;\n};\n\n/*\n * Our const options, obtained directly or derived from\n * command line options.\n */\nstruct tls_options\n{\n    /* our master TLS context from which all SSL objects are derived,\n     * this context is shared between all instances in p2pm with\n     * inherit_context_child. */\n    struct tls_root_ctx *ssl_ctx;\n\n    /* data channel cipher, hmac, and key lengths */\n    struct key_type key_type;\n\n    /* true if we are a TLS server, client otherwise */\n    bool server;\n\n    /* if true, don't xmit until first packet from peer is received */\n    bool xmit_hold;\n\n    /* local and remote options strings\n     * that must match between client and server */\n    const char *local_options;\n    const char *remote_options;\n\n    /* from command line */\n    bool single_session;\n    int mode;\n    bool pull;\n    /**\n     * The detail of info we push in peer info\n     *\n     * 0 - nothing at all, P2MP server only\n     * 1 - only the most basic information to negotiate cipher and features\n     *     for P2P NCP\n     * 2 - normal setting for clients\n     * 3 - full information including \"sensitive data\" like IV_HWADDR\n     *     enabled by --push-peer-info\n     */\n    int push_peer_info_detail;\n    int transition_window;\n    int handshake_window;\n    interval_t packet_timeout;\n    int64_t renegotiate_bytes;\n    int64_t renegotiate_packets;\n    /** limit for AEAD cipher when not running in epoch data key mode,\n     *  this is the sum of packets + blocks that are allowed to be used */\n    uint64_t aead_usage_limit;\n    interval_t renegotiate_seconds;\n\n    /* cert verification parms */\n    const char *verify_command;\n    int verify_x509_type;\n    const char *verify_x509_name;\n    const char *crl_file;\n    bool crl_file_inline;\n    int ns_cert_type;\n    unsigned remote_cert_ku[MAX_PARMS];\n    const char *remote_cert_eku;\n    struct verify_hash_list *verify_hash;\n    int verify_hash_depth;\n    bool verify_hash_no_ca;\n    hash_algo_type verify_hash_algo;\n    char *x509_username_field[MAX_PARMS];\n\n    /* struct crypto_option flags */\n    unsigned int crypto_flags;\n\n    int replay_window; /* --replay-window parm */\n    int replay_time;   /* --replay-window parm */\n\n    const char *config_ciphername;\n    const char *config_ncp_ciphers;\n\n    /** whether our underlying data channel supports new data channel\n     * features (epoch keys with AEAD tag at the end). This is always true\n     * for the internal implementation but can be false for DCO\n     * implementations */\n    bool data_epoch_supported;\n\n    bool tls_crypt_v2;\n    const char *tls_crypt_v2_verify_script;\n    int tls_crypt_v2_max_age;\n\n    /** TLS handshake wrapping state */\n    struct tls_wrap_ctx tls_wrap;\n\n    struct frame frame;\n\n    /* used for username/password authentication */\n    const char *auth_user_pass_verify_script;\n    const char *client_crresponse_script;\n    bool auth_user_pass_verify_script_via_file;\n    const char *tmp_dir;\n    const char *export_peer_cert_dir;\n    const char *auth_user_pass_file;\n    bool auth_user_pass_file_inline;\n\n    bool auth_token_generate;  /**< Generate auth-tokens on successful\n                                * user/pass auth,seet via\n                                * options->auth_token_generate. */\n    bool auth_token_call_auth; /**< always call normal authentication */\n    unsigned int auth_token_lifetime;\n    unsigned int auth_token_renewal;\n\n    struct key_ctx auth_token_key;\n\n    /* use the client-config-dir as a positive authenticator */\n    const char *client_config_dir_exclusive;\n\n    /* instance-wide environment variable set */\n    struct env_set *es;\n    openvpn_net_ctx_t *net_ctx;\n    const struct plugin_list *plugins;\n\n    /* compression parms */\n#ifdef USE_COMP\n    struct compress_options comp_options;\n#endif\n\n    /* configuration file SSL-related boolean and low-permutation options */\n#define SSLF_CLIENT_CERT_NOT_REQUIRED (1u << 0)\n#define SSLF_CLIENT_CERT_OPTIONAL     (1u << 1)\n#define SSLF_USERNAME_AS_COMMON_NAME  (1u << 2)\n#define SSLF_AUTH_USER_PASS_OPTIONAL  (1u << 3)\n/* (1u << 4) free for usage */\n#define SSLF_CRL_VERIFY_DIR           (1u << 5)\n#define SSLF_TLS_VERSION_MIN_SHIFT    6\n#define SSLF_TLS_VERSION_MIN_MASK     0xFu /* (uses bit positions 6 to 9) */\n#define SSLF_TLS_VERSION_MAX_SHIFT    10\n#define SSLF_TLS_VERSION_MAX_MASK     0xFu /* (uses bit positions 10 to 13) */\n#define SSLF_TLS_DEBUG_ENABLED        (1u << 14)\n    unsigned int ssl_flags;\n\n#ifdef ENABLE_MANAGEMENT\n    struct man_def_auth_context *mda_context;\n#endif\n\n    const struct x509_track *x509_track;\n\n#ifdef ENABLE_MANAGEMENT\n    const struct static_challenge_info *sci;\n#endif\n\n    /* --gremlin bits */\n    int gremlin;\n\n    /* Keying Material Exporter [RFC 5705] parameters */\n    const char *ekm_label;\n    size_t ekm_label_size;\n    size_t ekm_size;\n\n    bool dco_enabled; /**< Whether keys have to be installed in DCO or not */\n};\n\n/** @addtogroup control_processor\n *  @{ */\n/** @name Index of key_state objects within a tls_session structure\n *\n *  This is the index of \\c tls_session.key\n *\n *  @{ */\n#define KS_PRIMARY 0 /**< Primary %key state index. */\n#define KS_LAME_DUCK                                        \\\n    1                /**< %Key state index that will retire \\\n                      *   soon. */\n#define KS_SIZE 2    /**< Size of the \\c tls_session.key array. */\n/** @} name Index of key_state objects within a tls_session structure */\n/** @} addtogroup control_processor */\n\n/**\n * Security parameter state of a single session within a VPN tunnel.\n * @ingroup control_processor\n *\n * This structure represents an OpenVPN peer-to-peer control channel\n * session.\n *\n * A \\c tls_session remains over soft resets, but a new instance is\n * initialized for each hard reset.\n *\n * @see\n *  - This structure should be initialized using the \\c tls_session_init()\n *    function.\n *  - This structure should be cleaned up using the \\c tls_session_free()\n *    function.\n */\nstruct tls_session\n{\n    /* const options and config info */\n    struct tls_options *opt;\n\n    /* during hard reset used to control burst retransmit */\n    bool burst;\n\n    /* authenticate control packets */\n    struct tls_wrap_ctx tls_wrap;\n\n    /* Specific tls-crypt for renegotiations, if this is valid,\n     * tls_wrap_reneg.mode is TLS_WRAP_CRYPT, otherwise ignore it */\n    struct tls_wrap_ctx tls_wrap_reneg;\n\n    int initial_opcode;           /* our initial P_ opcode */\n    struct session_id session_id; /* our random session ID */\n\n    /**\n     * The current active key id, used to keep track of renegotiations.\n     * key_id increments with each soft reset to KEY_ID_MASK then recycles back\n     * to 1.  This way you know that if key_id is 0, it is the first key.\n     */\n    int key_id;\n\n    int verify_maxlevel;\n\n    char *common_name;\n\n    struct cert_hash_set *cert_hash_set;\n\n    bool verified; /* true if peer certificate was verified against CA */\n\n    /* not-yet-authenticated incoming client */\n    struct link_socket_actual untrusted_addr;\n\n    struct key_state key[KS_SIZE];\n};\n\n/** @addtogroup control_processor\n *  @{ */\n/** @name Index of tls_session objects within a tls_multi structure\n *\n *  This is the index of \\c tls_multi.session\n *\n *  Normally three tls_session objects are maintained by an active openvpn\n *  session.  The first is the current, TLS authenticated session, the\n *  second is used to process connection requests from a new client that\n *  would usurp the current session if successfully authenticated, and the\n *  third is used as a repository for a \"lame-duck\" %key in the event that\n *  the primary session resets due to error while the lame-duck %key still\n *  has time left before its expiration.  Lame duck keys are used to\n *  maintain the continuity of the data channel connection while a new %key\n *  is being negotiated.\n *\n *  @{ */\n#define TM_ACTIVE 0    /**< Active \\c tls_session. */\n#define TM_INITIAL                                           \\\n    1                  /**< As yet un-trusted \\c tls_session \\\n                        *   being negotiated. */\n#define TM_LAME_DUCK 2 /**< Old \\c tls_session. */\n#define TM_SIZE                                              \\\n    3                  /**< Size of the \\c tls_multi.session \\\n                        *   array. */\n/** @} name Index of tls_session objects within a tls_multi structure */\n/** @} addtogroup control_processor */\n\n\n/*\n * The number of keys we will scan on encrypt or decrypt.  The first\n * is the \"active\" key.  The second is the lame_duck or retiring key\n * associated with the active key's session ID.  The third is a detached\n * lame duck session that only occurs in situations where a key renegotiate\n * failed on the active key, but a lame duck key was still valid.  By\n * preserving the lame duck session, we can be assured of having a data\n * channel key available even when network conditions are so bad that\n * we can't negotiate a new key within the time allotted.\n */\n#define KEY_SCAN_SIZE 3\n\n\n/* multi state (originally client authentication state (=CAS))\n * CAS_NOT_CONNECTED must be 0 since non multi code paths still check\n * this variable but do not explicitly initialise it and depend\n * on zero initialisation */\n\n/* CAS_NOT_CONNECTED is the initial state for every context. When the *first*\n * tls_session reaches S_ACTIVE, this state machine moves to CAS_PENDING (server)\n * or CAS_CONNECT_DONE (client/p2p) as clients skip the stages associated with\n * connect scripts/plugins */\nenum multi_status\n{\n    CAS_NOT_CONNECTED,\n    CAS_WAITING_AUTH,             /**< Initial TLS connection established but deferred auth is not yet finished */\n    CAS_PENDING,                  /**< Options import (Connect script/plugin, ccd,...) */\n    CAS_PENDING_DEFERRED,         /**< Waiting on an async option import handler */\n    CAS_PENDING_DEFERRED_PARTIAL, /**< at least handler succeeded but another is still pending */\n    CAS_FAILED,                   /**< Option import failed or explicitly denied the client */\n    CAS_WAITING_OPTIONS_IMPORT,   /**< client with pull or p2p waiting for first time options import\n                                   */\n    /** session has already successful established (CAS_CONNECT_DONE) but has a\n     * reconnect and needs to redo some initialisation, this state is similar\n     * CAS_WAITING_OPTIONS_IMPORT but skips a few things. The normal connection\n     * skips this step. */\n    CAS_RECONNECT_PENDING,\n    CAS_CONNECT_DONE,\n};\n\n\n/**\n * Security parameter state for a single VPN tunnel.\n * @ingroup control_processor\n *\n * An active VPN tunnel running with TLS enabled has one \\c tls_multi\n * object, in which it stores all control channel and data channel\n * security parameter state.  This structure can contain multiple,\n * possibly simultaneously active, \\c tls_context objects to allow for\n * interruption-less transitions during session renegotiations.  Each \\c\n * tls_context represents one control channel session, which can span\n * multiple data channel security parameter sessions stored in \\c\n * key_state structures.\n */\nstruct tls_multi\n{\n    /* used to coordinate access between main thread and TLS thread */\n    /*MUTEX_PTR_DEFINE (mutex);*/\n\n    /* const options and config info */\n    struct tls_options opt;\n\n    /*\n     * used by tls_pre_encrypt to communicate the encrypt key\n     * to tls_post_encrypt()\n     */\n    struct key_state *save_ks; /* temporary pointer used between pre/post routines */\n\n    /*\n     * Used to return outgoing address from\n     * tls_multi_process.\n     */\n    struct link_socket_actual to_link_addr;\n\n    int n_sessions; /**< Number of sessions negotiated thus\n                     *   far. */\n    enum multi_status multi_state;\n\n    /*\n     * Number of errors.\n     */\n    int n_hard_errors; /* errors due to TLS negotiation failure */\n    int n_soft_errors; /* errors due to unrecognized or failed-to-authenticate incoming packets */\n\n    /**\n     * Our locked common name, username, and cert hashes\n     * (cannot change during the life of this tls_multi object)\n     */\n    char *locked_cn;\n\n    /** The locked username is the username we assume the client is using.\n     * Normally the username used for initial authentication unless\n     * overridden by --override-username */\n    char *locked_username;\n\n    /** The username that client initially used before being overridden\n     * by --override-user */\n    char *locked_original_username;\n\n    struct cert_hash_set *locked_cert_hash_set;\n\n    /**\n     * Time of last when we updated the cached state of\n     * tls_authentication_status deferred files */\n    time_t tas_cache_last_update;\n\n    /** The number of times we updated the cache */\n    unsigned int tas_cache_num_updates;\n\n    /** An error message to send to client on AUTH_FAILED */\n    char *client_reason;\n\n    /**\n     * A multi-line string of general-purpose info received from peer\n     * over control channel.\n     */\n    char *peer_info;\n    /**\n     * If server sends a generated auth-token,\n     * this is the token to use for future\n     * user/pass authentications in this session.\n     */\n    char *auth_token;\n    /**\n     * The first auth-token we sent to a client. We use this to remember\n     * the session ID and initial timestamp when generating new auth-token.\n     */\n    char *auth_token_initial;\n\n/** Auth-token sent from client has valid hmac */\n#define AUTH_TOKEN_HMAC_OK         (1 << 0)\n/** Auth-token sent from client has expired */\n#define AUTH_TOKEN_EXPIRED         (1 << 1)\n/**\n * Auth-token is only valid for an empty username\n * and not the username actually supplied from the client\n *\n * OpenVPN 3 clients sometimes wipes or replaces the username with a\n * username hint from their config.\n */\n#define AUTH_TOKEN_VALID_EMPTYUSER (1 << 2)\n\n    /* For P_DATA_V2 */\n    uint32_t peer_id;\n    bool use_peer_id;\n\n    char *remote_ciphername; /**< cipher specified in peer's config file */\n    bool remote_usescomp;    /**< remote announced comp-lzo in OCC string */\n\n    /*\n     * Our session objects.\n     */\n    /** Array of \\c tls_session objects\n     *  representing control channel\n     *  sessions with the remote peer. */\n    struct tls_session session[TM_SIZE];\n\n    /* Only used when DCO is used to remember how many keys we installed\n     * for this session */\n    int dco_keys_installed;\n    /**\n     * This is the handle that DCO uses to identify this session with the\n     * kernel.\n     *\n     * We keep this separate as the normal peer_id can change during\n     * p2p NCP and we need to track the id that is really used.\n     */\n    int dco_peer_id;\n\n    dco_context_t *dco;\n};\n\n/**  gets an item  of \\c key_state objects in the\n *   order they should be scanned by data\n *   channel modules. */\nstatic inline struct key_state *\nget_key_scan(struct tls_multi *multi, int index)\n{\n    switch (index)\n    {\n        case 0:\n            return &multi->session[TM_ACTIVE].key[KS_PRIMARY];\n\n        case 1:\n            return &multi->session[TM_ACTIVE].key[KS_LAME_DUCK];\n\n        case 2:\n            return &multi->session[TM_LAME_DUCK].key[KS_LAME_DUCK];\n\n        default:\n            ASSERT(false);\n            return NULL; /* NOTREACHED */\n    }\n}\n\n/**  gets an item  of \\c key_state objects in the\n *   order they should be scanned by data\n *   channel modules. */\nstatic inline const struct key_state *\nget_primary_key(const struct tls_multi *multi)\n{\n    return &multi->session[TM_ACTIVE].key[KS_PRIMARY];\n}\n\n#endif /* SSL_COMMON_H_ */\n"
  },
  {
    "path": "src/openvpn/ssl_mbedtls.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *  Copyright (C) 2006-2010, Brainspark B.V.\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel mbed TLS Backend\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if defined(ENABLE_CRYPTO_MBEDTLS)\n\n#include \"errlevel.h\"\n#include \"ssl_backend.h\"\n#include \"base64.h\"\n#include \"buffer.h\"\n#include \"misc.h\"\n#include \"manage.h\"\n#include \"mbedtls_compat.h\"\n#include \"pkcs11_backend.h\"\n#include \"ssl_common.h\"\n#include \"ssl_util.h\"\n\n#include \"ssl_verify_mbedtls.h\"\n#include <mbedtls/debug.h>\n#include <mbedtls/error.h>\n#include <mbedtls/net_sockets.h>\n#include <mbedtls/version.h>\n\n#include <mbedtls/oid.h>\n#include <mbedtls/pem.h>\n\nstatic const mbedtls_x509_crt_profile openvpn_x509_crt_profile_legacy = {\n    /* Hashes from SHA-1 and above */\n    MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA1) | MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_RIPEMD160)\n        | MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA224) | MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA256)\n        | MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA384) | MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA512),\n    0xFFFFFFF, /* Any PK alg    */\n    0xFFFFFFF, /* Any curve     */\n    1024,      /* RSA-1024 and larger */\n};\n\nstatic const mbedtls_x509_crt_profile openvpn_x509_crt_profile_preferred = {\n    /* SHA-2 and above */\n    MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA224) | MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA256)\n        | MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA384) | MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA512),\n    0xFFFFFFF, /* Any PK alg    */\n    0xFFFFFFF, /* Any curve     */\n    2048,      /* RSA-2048 and larger */\n};\n\n#define openvpn_x509_crt_profile_suiteb mbedtls_x509_crt_profile_suiteb;\n\nvoid\ntls_init_lib(void)\n{\n    mbedtls_compat_psa_crypto_init();\n}\n\nvoid\ntls_free_lib(void)\n{\n}\n\nvoid\ntls_ctx_server_new(struct tls_root_ctx *ctx)\n{\n    ASSERT(NULL != ctx);\n    CLEAR(*ctx);\n\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n    ALLOC_OBJ_CLEAR(ctx->dhm_ctx, mbedtls_dhm_context);\n#endif\n\n    ALLOC_OBJ_CLEAR(ctx->ca_chain, mbedtls_x509_crt);\n\n    ctx->endpoint = MBEDTLS_SSL_IS_SERVER;\n    ctx->initialised = true;\n}\n\nvoid\ntls_ctx_client_new(struct tls_root_ctx *ctx)\n{\n    ASSERT(NULL != ctx);\n    CLEAR(*ctx);\n\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n    ALLOC_OBJ_CLEAR(ctx->dhm_ctx, mbedtls_dhm_context);\n#endif\n    ALLOC_OBJ_CLEAR(ctx->ca_chain, mbedtls_x509_crt);\n\n    ctx->endpoint = MBEDTLS_SSL_IS_CLIENT;\n    ctx->initialised = true;\n}\n\nvoid\ntls_ctx_free(struct tls_root_ctx *ctx)\n{\n    if (ctx)\n    {\n        mbedtls_pk_free(ctx->priv_key);\n        free(ctx->priv_key);\n\n        mbedtls_x509_crt_free(ctx->ca_chain);\n        free(ctx->ca_chain);\n\n        mbedtls_x509_crt_free(ctx->crt_chain);\n        free(ctx->crt_chain);\n\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n        mbedtls_dhm_free(ctx->dhm_ctx);\n        free(ctx->dhm_ctx);\n#endif\n\n        mbedtls_x509_crl_free(ctx->crl);\n        free(ctx->crl);\n\n#if defined(ENABLE_PKCS11)\n        /* ...freeCertificate() can handle NULL ptrs, but if pkcs11 helper\n         * has not been initialized, it will ASSERT() - so, do not pass NULL\n         */\n        if (ctx->pkcs11_cert)\n        {\n            pkcs11h_certificate_freeCertificate(ctx->pkcs11_cert);\n        }\n#endif\n\n        free(ctx->allowed_ciphers);\n\n        free(ctx->groups);\n\n        CLEAR(*ctx);\n\n        ctx->initialised = false;\n    }\n}\n\nbool\ntls_ctx_initialised(struct tls_root_ctx *ctx)\n{\n    /* either this should be NULL or should be non-null and then have a\n     * valid TLS ctx inside as well */\n    ASSERT(NULL == ctx || ctx->initialised);\n    return ctx != NULL;\n}\n#if !defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT)\n/*\n * If we don't have mbedtls_ssl_export_keying_material(), we use\n * mbedtls_ssl_set_export_keys_cb() to obtain a copy of the TLS 1.2\n * master secret and compute the TLS-Exporter function ourselves.\n * Unfortunately, with TLS 1.3, there is no alternative to\n * mbedtls_ssl_export_keying_material().\n */\nvoid\nmbedtls_ssl_export_keys_cb(void *p_expkey, mbedtls_ssl_key_export_type type,\n                           const unsigned char *secret, size_t secret_len,\n                           const unsigned char client_random[32],\n                           const unsigned char server_random[32],\n                           mbedtls_tls_prf_types tls_prf_type)\n{\n    /* Since we can't get the TLS 1.3 exporter master secret, we ignore all key\n     * types except MBEDTLS_SSL_KEY_EXPORT_TLS12_MASTER_SECRET. */\n    if (type != MBEDTLS_SSL_KEY_EXPORT_TLS12_MASTER_SECRET)\n    {\n        return;\n    }\n\n    struct tls_session *session = p_expkey;\n    struct key_state_ssl *ks_ssl = &session->key[KS_PRIMARY].ks_ssl;\n    struct tls_key_cache *cache = &ks_ssl->tls_key_cache;\n\n    /* The TLS 1.2 master secret has a fixed size, so if secret_len has\n     * a different value, something is wrong with mbed TLS. */\n    if (secret_len != sizeof(cache->master_secret))\n    {\n        msg(M_FATAL, \"ERROR: Incorrect TLS 1.2 master secret length: Got %zu, expected %zu\",\n            secret_len, sizeof(cache->master_secret));\n    }\n\n    memcpy(cache->client_server_random, client_random, 32);\n    memcpy(cache->client_server_random + 32, server_random, 32);\n    memcpy(cache->master_secret, secret, sizeof(cache->master_secret));\n    cache->tls_prf_type = tls_prf_type;\n}\n#endif /* !defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT) */\n\n\nbool\nkey_state_export_keying_material(struct tls_session *session, const char *label, size_t label_size,\n                                 void *ekm, size_t ekm_size)\n{\n    ASSERT(strlen(label) == label_size);\n\n#if defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT)\n    /* Our version of mbed TLS has a built-in TLS-Exporter. */\n\n    mbedtls_ssl_context *ctx = session->key[KS_PRIMARY].ks_ssl.ctx;\n    if (mbed_ok(\n            mbedtls_ssl_export_keying_material(ctx, ekm, ekm_size, label, label_size, NULL, 0, 0)))\n    {\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n\n#else  /* defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT) */\n    struct tls_key_cache *cache = &session->key[KS_PRIMARY].ks_ssl.tls_key_cache;\n\n    /* If the type is NONE, we either have no cached secrets or\n     * there is no PRF, in both cases we cannot generate key material */\n    if (cache->tls_prf_type == MBEDTLS_SSL_TLS_PRF_NONE)\n    {\n        return false;\n    }\n\n    int ret = mbedtls_ssl_tls_prf(cache->tls_prf_type, cache->master_secret,\n                                  sizeof(cache->master_secret), label, cache->client_server_random,\n                                  sizeof(cache->client_server_random), ekm, ekm_size);\n\n    if (mbed_ok(ret))\n    {\n        return true;\n    }\n    else\n    {\n        secure_memzero(ekm, session->opt->ekm_size);\n        return false;\n    }\n#endif /* defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT) */\n}\n\nbool\ntls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)\n{\n    return true;\n}\n\nstatic const char *\ntls_translate_cipher_name(const char *cipher_name)\n{\n    const tls_cipher_name_pair *pair = tls_get_cipher_name_pair(cipher_name, strlen(cipher_name));\n\n    if (NULL == pair)\n    {\n        /* No translation found, return original */\n        return cipher_name;\n    }\n\n    if (0 != strcmp(cipher_name, pair->iana_name))\n    {\n        /* Deprecated name found, notify user */\n        msg(M_WARN, \"Deprecated cipher suite name '%s', please use IANA name '%s'\",\n            pair->openssl_name, pair->iana_name);\n    }\n\n    return pair->iana_name;\n}\n\nvoid\ntls_ctx_restrict_ciphers_tls13(struct tls_root_ctx *ctx, const char *ciphers)\n{\n    if (ciphers == NULL)\n    {\n        /* Nothing to do, return without warning message */\n        return;\n    }\n\n    msg(M_WARN,\n        \"mbed TLS does not support setting tls-ciphersuites. \"\n        \"Ignoring TLS 1.3 cipher list: %s\",\n        ciphers);\n}\n\nvoid\ntls_ctx_restrict_ciphers(struct tls_root_ctx *ctx, const char *ciphers)\n{\n    char *tmp_ciphers, *tmp_ciphers_orig, *token;\n\n    if (NULL == ciphers)\n    {\n        return; /* Nothing to do */\n    }\n\n    ASSERT(NULL != ctx);\n\n    /* Get number of ciphers */\n    int cipher_count = get_num_elements(ciphers, ':');\n\n    /* Allocate an array for them */\n    ALLOC_ARRAY_CLEAR(ctx->allowed_ciphers, int, cipher_count + 1)\n\n    /* Parse allowed ciphers, getting IDs */\n    int i = 0;\n    tmp_ciphers_orig = tmp_ciphers = string_alloc(ciphers, NULL);\n\n    token = strtok(tmp_ciphers, \":\");\n    while (token)\n    {\n        ctx->allowed_ciphers[i] = mbedtls_ssl_get_ciphersuite_id(tls_translate_cipher_name(token));\n        if (0 != ctx->allowed_ciphers[i])\n        {\n            i++;\n        }\n        token = strtok(NULL, \":\");\n    }\n    free(tmp_ciphers_orig);\n}\n\nvoid\ntls_ctx_set_cert_profile(struct tls_root_ctx *ctx, const char *profile)\n{\n    if (!profile || 0 == strcmp(profile, \"legacy\") || 0 == strcmp(profile, \"insecure\"))\n    {\n        ctx->cert_profile = openvpn_x509_crt_profile_legacy;\n    }\n    else if (0 == strcmp(profile, \"preferred\"))\n    {\n        ctx->cert_profile = openvpn_x509_crt_profile_preferred;\n    }\n    else if (0 == strcmp(profile, \"suiteb\"))\n    {\n        ctx->cert_profile = openvpn_x509_crt_profile_suiteb;\n    }\n    else\n    {\n        msg(M_FATAL, \"ERROR: Invalid cert profile: %s\", profile);\n    }\n}\n\n#if MBEDTLS_VERSION_NUMBER >= 0x04000000\nstatic const mbedtls_ecp_curve_info ecp_curve_info_table[] = {\n/* secp curves. */\n#if defined(PSA_WANT_ECC_SECP_R1_256)\n    { \"secp256r1\", MBEDTLS_SSL_IANA_TLS_GROUP_SECP256R1 },\n#endif\n#if defined(PSA_WANT_ECC_SECP_R1_384)\n    { \"secp384r1\", MBEDTLS_SSL_IANA_TLS_GROUP_SECP384R1 },\n#endif\n#if defined(PSA_WANT_ECC_SECP_R1_521)\n    { \"secp521r1\", MBEDTLS_SSL_IANA_TLS_GROUP_SECP521R1 },\n#endif\n\n/* Curve25519. */\n#if defined(PSA_WANT_ECC_MONTGOMERY_255)\n    { \"X25519\", MBEDTLS_SSL_IANA_TLS_GROUP_X25519 },\n#endif\n\n/* Curve448. */\n#if defined(PSA_WANT_ECC_MONTGOMERY_448)\n    { \"X448\", MBEDTLS_SSL_IANA_TLS_GROUP_X448 },\n#endif\n\n/* Brainpool curves. */\n#if defined(PSA_WANT_ECC_BRAINPOOL_P_R1_256)\n    { \"brainpoolP256r1\", MBEDTLS_SSL_IANA_TLS_GROUP_BP256R1 },\n#endif\n#if defined(PSA_WANT_ECC_BRAINPOOL_P_R1_384)\n    { \"brainpoolP384r1\", MBEDTLS_SSL_IANA_TLS_GROUP_BP384R1 },\n#endif\n#if defined(PSA_WANT_ECC_BRAINPOOL_P_R1_512)\n    { \"brainpoolP512r1\", MBEDTLS_SSL_IANA_TLS_GROUP_BP512R1 },\n#endif\n\n/* Named Diffie-Hellman groups. */\n#if defined(PSA_WANT_DH_RFC7919_2048)\n    { \"ffdhe2048\", MBEDTLS_SSL_IANA_TLS_GROUP_FFDHE2048 },\n#endif\n#if defined(PSA_WANT_DH_RFC7919_3072)\n    { \"ffdhe3072\", MBEDTLS_SSL_IANA_TLS_GROUP_FFDHE3072 },\n#endif\n#if defined(PSA_WANT_DH_RFC7919_4096)\n    { \"ffdhe4096\", MBEDTLS_SSL_IANA_TLS_GROUP_FFDHE4096 },\n#endif\n#if defined(PSA_WANT_DH_RFC7919_6144)\n    { \"ffdhe6144\", MBEDTLS_SSL_IANA_TLS_GROUP_FFDHE6144 },\n#endif\n#if defined(PSA_WANT_DH_RFC7919_8192)\n    { \"ffdhe8192\", MBEDTLS_SSL_IANA_TLS_GROUP_FFDHE8192 },\n#endif\n};\nstatic const size_t ecp_curve_info_table_items = sizeof(ecp_curve_info_table) / sizeof(mbedtls_ecp_curve_info);\n\nstatic const mbedtls_ecp_curve_info *\nmbedtls_ecp_curve_info_from_name(const char *name)\n{\n    for (size_t i = 0; i < ecp_curve_info_table_items; i++)\n    {\n        if (strcmp(name, ecp_curve_info_table[i].name) == 0)\n        {\n            return &ecp_curve_info_table[i];\n        }\n    }\n    return NULL;\n}\n#endif /* MBEDTLS_VERSION_NUMBER >= 0x04000000 */\n\nvoid\ntls_ctx_set_tls_groups(struct tls_root_ctx *ctx, const char *groups)\n{\n    ASSERT(ctx);\n    struct gc_arena gc = gc_new();\n\n    /* Get number of groups and allocate an array in ctx */\n    int groups_count = get_num_elements(groups, ':');\n    ALLOC_ARRAY_CLEAR(ctx->groups, uint16_t, groups_count + 1)\n\n    /* Parse allowed ciphers, getting IDs */\n    int i = 0;\n    char *tmp_groups = string_alloc(groups, &gc);\n\n    const char *token;\n    while ((token = strsep(&tmp_groups, \":\")))\n    {\n        const mbedtls_ecp_curve_info *ci = mbedtls_ecp_curve_info_from_name(token);\n        if (!ci)\n        {\n            msg(M_WARN, \"Warning unknown curve/group specified: %s\", token);\n        }\n        else\n        {\n            ctx->groups[i] = ci->tls_id;\n            i++;\n        }\n    }\n\n    /* Recent mbedtls versions state that the list of groups must be terminated\n     * with 0. Older versions state that it must be terminated with MBEDTLS_ECP_DP_NONE\n     * which is also 0, so this works either way. */\n    ctx->groups[i] = 0;\n\n    gc_free(&gc);\n}\n\n\nvoid\ntls_ctx_check_cert_time(const struct tls_root_ctx *ctx)\n{\n    ASSERT(ctx);\n    if (ctx->crt_chain == NULL)\n    {\n        return; /* Nothing to check if there is no certificate */\n    }\n\n    if (mbedtls_x509_time_is_future(&ctx->crt_chain->valid_from))\n    {\n        msg(M_WARN, \"WARNING: Your certificate is not yet valid!\");\n    }\n\n    if (mbedtls_x509_time_is_past(&ctx->crt_chain->valid_to))\n    {\n        msg(M_WARN, \"WARNING: Your certificate has expired!\");\n    }\n}\n\nvoid\ntls_ctx_load_dh_params(struct tls_root_ctx *ctx, const char *dh_file, bool dh_inline)\n{\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n    if (dh_inline)\n    {\n        if (!mbed_ok(mbedtls_dhm_parse_dhm(ctx->dhm_ctx, (const unsigned char *)dh_file,\n                                           strlen(dh_file) + 1)))\n        {\n            msg(M_FATAL, \"Cannot read inline DH parameters\");\n        }\n    }\n    else\n    {\n        if (!mbed_ok(mbedtls_dhm_parse_dhmfile(ctx->dhm_ctx, dh_file)))\n        {\n            msg(M_FATAL, \"Cannot read DH parameters from file %s\", dh_file);\n        }\n    }\n\n    msg(D_TLS_DEBUG_LOW, \"Diffie-Hellman initialized with \" counter_format \" bit key\",\n        (counter_type)mbedtls_dhm_get_bitlen(ctx->dhm_ctx));\n#else\n    if (strcmp(dh_file, \"none\") != 0)\n    {\n        msg(M_FATAL, \"Mbed TLS 4 only supports pre-defined Diffie-Hellman groups.\");\n    }\n#endif /* MBEDTLS_VERSION_NUMBER < 0x04000000 */\n}\n\nvoid\ntls_ctx_load_ecdh_params(struct tls_root_ctx *ctx, const char *curve_name)\n{\n    if (NULL != curve_name)\n    {\n        msg(M_WARN, \"WARNING: mbed TLS builds do not support specifying an \"\n                    \"ECDH curve with --ecdh-curve, using default curves. Use \"\n                    \"--tls-groups to specify curves.\");\n    }\n}\n\nint\ntls_ctx_load_pkcs12(struct tls_root_ctx *ctx, const char *pkcs12_file, bool pkcs12_file_inline,\n                    bool load_ca_file)\n{\n    msg(M_FATAL, \"PKCS #12 files not yet supported for mbed TLS.\");\n    return 0;\n}\n\n#ifdef ENABLE_CRYPTOAPI\nvoid\ntls_ctx_load_cryptoapi(struct tls_root_ctx *ctx, const char *cryptoapi_cert)\n{\n    msg(M_FATAL, \"Windows CryptoAPI not yet supported for mbed TLS.\");\n}\n#endif /* _WIN32 */\n\nvoid\ntls_ctx_load_cert_file(struct tls_root_ctx *ctx, const char *cert_file, bool cert_inline)\n{\n    ASSERT(NULL != ctx);\n\n    if (!ctx->crt_chain)\n    {\n        ALLOC_OBJ_CLEAR(ctx->crt_chain, mbedtls_x509_crt);\n    }\n\n    if (cert_inline)\n    {\n        if (!cert_file)\n        {\n            msg(M_FATAL, \"Cannot load inline certificate: NULL\");\n        }\n        if (!mbed_ok(mbedtls_x509_crt_parse(ctx->crt_chain, (const unsigned char *)cert_file,\n                                            strlen(cert_file) + 1)))\n        {\n            msg(M_FATAL, \"Cannot load inline certificate\");\n        }\n    }\n    else\n    {\n        if (!mbed_ok(mbedtls_x509_crt_parse_file(ctx->crt_chain, cert_file)))\n        {\n            msg(M_FATAL, \"Cannot load certificate file %s\", cert_file);\n        }\n    }\n}\n\nint\ntls_ctx_load_priv_file(struct tls_root_ctx *ctx, const char *priv_key_file, bool priv_key_inline)\n{\n    int status;\n    ASSERT(NULL != ctx);\n\n    if (!ctx->priv_key)\n    {\n        ALLOC_OBJ_CLEAR(ctx->priv_key, mbedtls_pk_context);\n    }\n\n    if (priv_key_inline)\n    {\n        status = mbedtls_compat_pk_parse_key(ctx->priv_key, (const unsigned char *)priv_key_file,\n                                             strlen(priv_key_file) + 1, NULL, 0);\n\n        if (MBEDTLS_ERR_PK_PASSWORD_REQUIRED == status)\n        {\n            char passbuf[512] = { 0 };\n            pem_password_callback(passbuf, 512, 0, NULL);\n            status = mbedtls_compat_pk_parse_key(\n                ctx->priv_key, (const unsigned char *)priv_key_file, strlen(priv_key_file) + 1,\n                (unsigned char *)passbuf, strlen(passbuf));\n        }\n    }\n    else\n    {\n        status = mbedtls_compat_pk_parse_keyfile(ctx->priv_key, priv_key_file, NULL);\n        if (MBEDTLS_ERR_PK_PASSWORD_REQUIRED == status)\n        {\n            char passbuf[512] = { 0 };\n            pem_password_callback(passbuf, 512, 0, NULL);\n            status = mbedtls_compat_pk_parse_keyfile(ctx->priv_key, priv_key_file, passbuf);\n        }\n    }\n    if (!mbed_ok(status))\n    {\n#ifdef ENABLE_MANAGEMENT\n        if (management && (MBEDTLS_ERR_PK_PASSWORD_MISMATCH == status))\n        {\n            management_auth_failure(management, UP_TYPE_PRIVATE_KEY, NULL);\n        }\n#endif\n        msg(M_WARN, \"Cannot load private key file %s\",\n            print_key_filename(priv_key_file, priv_key_inline));\n        return 1;\n    }\n\n    if (!mbed_ok(mbedtls_compat_pk_check_pair(&ctx->crt_chain->pk, ctx->priv_key)))\n    {\n        msg(M_WARN, \"Private key does not match the certificate\");\n        return 1;\n    }\n\n    return 0;\n}\n\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n/**\n * external_pkcs1_sign implements a mbed TLS rsa_sign_func callback, that uses\n * the management interface to request an RSA signature for the supplied hash.\n *\n * @param ctx_voidptr   Management external key context.\n * @param f_rng         (Unused)\n * @param p_rng         (Unused)\n * @param md_alg        Message digest ('hash') algorithm type.\n * @param hashlen       Length of hash (overridden by length specified by md_alg\n *                      if md_alg != MBEDTLS_MD_NONE).\n * @param hash          The digest ('hash') to sign. Should have a size\n *                      matching the length of md_alg (if != MBEDTLS_MD_NONE),\n *                      or hashlen otherwise.\n * @param sig           Buffer that returns the signature. Should be at least of\n *                      size ctx->signature_length.\n *\n * @return 0 on success, non-zero mbed TLS error code on failure.\n */\nstatic inline int\nexternal_pkcs1_sign(void *ctx_voidptr, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng,\n                    mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash,\n                    unsigned char *sig)\n{\n    struct external_context *const ctx = ctx_voidptr;\n    int rv;\n    size_t asn_len = 0, oid_size = 0;\n    const char *oid = NULL;\n\n    if (NULL == ctx)\n    {\n        return MBEDTLS_ERR_RSA_BAD_INPUT_DATA;\n    }\n\n    /*\n     * Support a wide range of hashes. TLSv1.1 and before only need SIG_RSA_RAW,\n     * but TLSv1.2 needs the full suite of hashes.\n     *\n     * This code has been taken from mbed TLS pkcs11_sign(), under the GPLv2.0+.\n     */\n    if (md_alg != MBEDTLS_MD_NONE)\n    {\n        const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(md_alg);\n        if (md_info == NULL)\n        {\n            return (MBEDTLS_ERR_RSA_BAD_INPUT_DATA);\n        }\n\n        if (!mbed_ok(mbedtls_oid_get_oid_by_md(md_alg, &oid, &oid_size)))\n        {\n            return (MBEDTLS_ERR_RSA_BAD_INPUT_DATA);\n        }\n\n        hashlen = mbedtls_md_get_size(md_info);\n        asn_len = 10 + oid_size;\n    }\n\n    if (ctx->signature_length < (asn_len + hashlen)\n        || (asn_len + hashlen) > UINT8_MAX)\n    {\n        return MBEDTLS_ERR_RSA_BAD_INPUT_DATA;\n    }\n\n    uint8_t *to_sign = NULL;\n    ALLOC_ARRAY_CLEAR(to_sign, uint8_t, asn_len + hashlen);\n    uint8_t *p = to_sign;\n    if (md_alg != MBEDTLS_MD_NONE)\n    {\n        /*\n         * DigestInfo ::= SEQUENCE {\n         *   digestAlgorithm DigestAlgorithmIdentifier,\n         *   digest Digest }\n         *\n         * DigestAlgorithmIdentifier ::= AlgorithmIdentifier\n         *\n         * Digest ::= OCTET STRING\n         */\n        *p++ = MBEDTLS_ASN1_SEQUENCE | MBEDTLS_ASN1_CONSTRUCTED;\n        *p++ = (uint8_t)(0x08 + oid_size + hashlen);\n        *p++ = MBEDTLS_ASN1_SEQUENCE | MBEDTLS_ASN1_CONSTRUCTED;\n        *p++ = (uint8_t)(0x04 + oid_size);\n        *p++ = MBEDTLS_ASN1_OID;\n        *p++ = (uint8_t)oid_size;\n        memcpy(p, oid, oid_size);\n        p += oid_size;\n        *p++ = MBEDTLS_ASN1_NULL;\n        *p++ = 0x00;\n        *p++ = MBEDTLS_ASN1_OCTET_STRING;\n        *p++ = (uint8_t)hashlen;\n\n        /* Double-check ASN length */\n        ASSERT(asn_len == (uintptr_t)(p - to_sign));\n    }\n\n    /* Copy the hash to be signed */\n    memcpy(p, hash, hashlen);\n\n    /* Call external signature function */\n    if (!ctx->sign(ctx->sign_ctx, to_sign, asn_len + hashlen, sig, ctx->signature_length))\n    {\n        rv = MBEDTLS_ERR_RSA_PRIVATE_FAILED;\n        goto done;\n    }\n\n    rv = 0;\n\ndone:\n    free(to_sign);\n    return rv;\n}\n\nstatic inline size_t\nexternal_key_len(void *vctx)\n{\n    struct external_context *const ctx = vctx;\n\n    return ctx->signature_length;\n}\n#endif /* MBEDTLS_VERSION_NUMBER < 0x04000000 */\n\nint\ntls_ctx_use_external_signing_func(struct tls_root_ctx *ctx, external_sign_func sign_func,\n                                  void *sign_ctx)\n{\n#if MBEDTLS_VERSION_NUMBER >= 0x04000000\n    msg(M_WARN, \"tls_ctx_use_external_signing_func is not implemented for Mbed TLS 4.\");\n    return 1;\n#else\n    ASSERT(NULL != ctx);\n\n    if (ctx->crt_chain == NULL)\n    {\n        msg(M_WARN, \"ERROR: external key requires a certificate.\");\n        return 1;\n    }\n\n    if (mbedtls_pk_get_type(&ctx->crt_chain->pk) != MBEDTLS_PK_RSA)\n    {\n        msg(M_WARN, \"ERROR: external key with mbed TLS requires a \"\n                    \"certificate with an RSA key.\");\n        return 1;\n    }\n\n    ctx->external_key.signature_length = mbedtls_pk_get_len(&ctx->crt_chain->pk);\n    ctx->external_key.sign = sign_func;\n    ctx->external_key.sign_ctx = sign_ctx;\n\n    ALLOC_OBJ_CLEAR(ctx->priv_key, mbedtls_pk_context);\n    if (!mbed_ok(mbedtls_pk_setup_rsa_alt(ctx->priv_key, &ctx->external_key, NULL,\n                                          external_pkcs1_sign, external_key_len)))\n    {\n        return 1;\n    }\n\n    return 0;\n#endif /* MBEDTLS_VERSION_NUMBER >= 0x04000000 */\n}\n\n#ifdef ENABLE_MANAGEMENT\n/** Query the management interface for a signature, see external_sign_func. */\nstatic bool\nmanagement_sign_func(void *sign_ctx, const void *src, size_t src_len, void *dst, size_t dst_len)\n{\n    bool ret = false;\n    char *src_b64 = NULL;\n    char *dst_b64 = NULL;\n\n    if (!management || (openvpn_base64_encode(src, (int)src_len, &src_b64) <= 0))\n    {\n        goto cleanup;\n    }\n\n    /*\n     * We only support RSA external keys and PKCS1 signatures at the moment\n     * in mbed TLS, so the signature parameter is hardcoded to this encoding\n     */\n    if (!(dst_b64 = management_query_pk_sig(management, src_b64, \"RSA_PKCS1_PADDING\")))\n    {\n        goto cleanup;\n    }\n\n    if (openvpn_base64_decode(dst_b64, dst, (int)dst_len) != (int)dst_len)\n    {\n        goto cleanup;\n    }\n\n    ret = true;\ncleanup:\n    free(src_b64);\n    free(dst_b64);\n\n    return ret;\n}\n\nint\ntls_ctx_use_management_external_key(struct tls_root_ctx *ctx)\n{\n    return tls_ctx_use_external_signing_func(ctx, management_sign_func, NULL);\n}\n\n#endif /* ifdef ENABLE_MANAGEMENT */\n\nvoid\ntls_ctx_load_ca(struct tls_root_ctx *ctx, const char *ca_file, bool ca_inline, const char *ca_path,\n                bool tls_server)\n{\n    if (ca_path)\n    {\n        msg(M_FATAL, \"ERROR: mbed TLS cannot handle the capath directive\");\n    }\n\n    if (ca_file && ca_inline)\n    {\n        if (!mbed_ok(mbedtls_x509_crt_parse(ctx->ca_chain, (const unsigned char *)ca_file,\n                                            strlen(ca_file) + 1)))\n        {\n            msg(M_FATAL, \"Cannot load inline CA certificates\");\n        }\n    }\n    else\n    {\n        /* Load CA file for verifying peer supplied certificate */\n        if (!mbed_ok(mbedtls_x509_crt_parse_file(ctx->ca_chain, ca_file)))\n        {\n            msg(M_FATAL, \"Cannot load CA certificate file %s\", ca_file);\n        }\n    }\n}\n\nvoid\ntls_ctx_load_extra_certs(struct tls_root_ctx *ctx, const char *extra_certs_file,\n                         bool extra_certs_inline)\n{\n    ASSERT(NULL != ctx);\n\n    if (!ctx->crt_chain)\n    {\n        ALLOC_OBJ_CLEAR(ctx->crt_chain, mbedtls_x509_crt);\n    }\n\n    if (extra_certs_inline)\n    {\n        if (!mbed_ok(mbedtls_x509_crt_parse(ctx->crt_chain, (const unsigned char *)extra_certs_file,\n                                            strlen(extra_certs_file) + 1)))\n        {\n            msg(M_FATAL, \"Cannot load inline extra-certs file\");\n        }\n    }\n    else\n    {\n        if (!mbed_ok(mbedtls_x509_crt_parse_file(ctx->crt_chain, extra_certs_file)))\n        {\n            msg(M_FATAL, \"Cannot load extra-certs file: %s\", extra_certs_file);\n        }\n    }\n}\n\n/* **************************************\n *\n * Key-state specific functions\n *\n ***************************************/\n\n/*\n * \"Endless buffer\"\n */\n\nstatic inline void\nbuf_free_entry(buffer_entry *entry)\n{\n    if (NULL != entry)\n    {\n        free(entry->data);\n        free(entry);\n    }\n}\n\nstatic void\nbuf_free_entries(endless_buffer *buf)\n{\n    while (buf->first_block)\n    {\n        buffer_entry *cur_block = buf->first_block;\n        buf->first_block = cur_block->next_block;\n        buf_free_entry(cur_block);\n    }\n    buf->last_block = NULL;\n}\n\nstatic int\nendless_buf_read(endless_buffer *in, unsigned char *out, size_t out_len)\n{\n    size_t read_len = 0;\n\n    ASSERT(out_len <= INT_MAX);\n\n    if (in->first_block == NULL)\n    {\n        return MBEDTLS_ERR_SSL_WANT_READ;\n    }\n\n    while (in->first_block != NULL && read_len < out_len)\n    {\n        size_t block_len = in->first_block->length - in->data_start;\n        if (block_len <= out_len - read_len)\n        {\n            buffer_entry *cur_entry = in->first_block;\n            memcpy(out + read_len, cur_entry->data + in->data_start, block_len);\n\n            read_len += block_len;\n\n            in->first_block = cur_entry->next_block;\n            in->data_start = 0;\n\n            if (in->first_block == NULL)\n            {\n                in->last_block = NULL;\n            }\n\n            buf_free_entry(cur_entry);\n        }\n        else\n        {\n            memcpy(out + read_len, in->first_block->data + in->data_start, out_len - read_len);\n            in->data_start += out_len - read_len;\n            read_len = out_len;\n        }\n    }\n\n    return (int)read_len;\n}\n\nstatic int\nendless_buf_write(endless_buffer *out, const unsigned char *in, size_t len)\n{\n    buffer_entry *new_block = malloc(sizeof(buffer_entry));\n    if (NULL == new_block)\n    {\n        return MBEDTLS_ERR_NET_SEND_FAILED;\n    }\n\n    new_block->data = malloc(len);\n    if (NULL == new_block->data)\n    {\n        free(new_block);\n        return MBEDTLS_ERR_NET_SEND_FAILED;\n    }\n\n    ASSERT(len <= INT_MAX);\n\n    new_block->length = len;\n    new_block->next_block = NULL;\n\n    memcpy(new_block->data, in, len);\n\n    if (NULL == out->first_block)\n    {\n        out->first_block = new_block;\n    }\n\n    if (NULL != out->last_block)\n    {\n        out->last_block->next_block = new_block;\n    }\n\n    out->last_block = new_block;\n\n    return (int)len;\n}\n\nstatic int\nssl_bio_read(void *ctx, unsigned char *out, size_t out_len)\n{\n    bio_ctx *my_ctx = (bio_ctx *)ctx;\n    return endless_buf_read(&my_ctx->in, out, out_len);\n}\n\nstatic int\nssl_bio_write(void *ctx, const unsigned char *in, size_t in_len)\n{\n    bio_ctx *my_ctx = (bio_ctx *)ctx;\n    return endless_buf_write(&my_ctx->out, in, in_len);\n}\n\nstatic void\nmy_debug(void *ctx, int level, const char *file, int line, const char *str)\n{\n    msglvl_t my_loglevel = (level < 3) ? D_TLS_DEBUG_MED : D_TLS_DEBUG;\n    msg(my_loglevel, \"mbed TLS msg (%s:%d): %s\", file, line, str);\n}\n\n/*\n * Further personalise the RNG using a hash of the public key\n */\nvoid\ntls_ctx_personalise_random(struct tls_root_ctx *ctx)\n{\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n    static char old_sha256_hash[32] = { 0 };\n    unsigned char sha256_hash[32] = { 0 };\n    mbedtls_ctr_drbg_context *cd_ctx = rand_ctx_get();\n\n    if (NULL != ctx->crt_chain)\n    {\n        mbedtls_x509_crt *cert = ctx->crt_chain;\n\n        if (!md_full(\"SHA256\", cert->tbs.p, cert->tbs.len, sha256_hash))\n        {\n            msg(M_WARN, \"WARNING: failed to personalise random\");\n        }\n\n        if (0 != memcmp(old_sha256_hash, sha256_hash, sizeof(sha256_hash)))\n        {\n            if (!mbed_ok(mbedtls_ctr_drbg_update(cd_ctx, sha256_hash, 32)))\n            {\n                msg(M_WARN, \"WARNING: failed to personalise random, could not update CTR_DRBG\");\n            }\n            memcpy(old_sha256_hash, sha256_hash, sizeof(old_sha256_hash));\n        }\n    }\n#endif /* MBEDTLS_VERSION_NUMBER < 0x040000 */\n}\n\nint\ntls_version_max(void)\n{\n    /* We need mbedtls_ssl_export_keying_material() to support TLS 1.3. */\n#if defined(MBEDTLS_SSL_PROTO_TLS1_3) && defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT)\n    return TLS_VER_1_3;\n#elif defined(MBEDTLS_SSL_PROTO_TLS1_2)\n    return TLS_VER_1_2;\n#else\n#error mbedtls is compiled without support for TLS 1.2 or 1.3\n#endif\n}\n\n/**\n * Convert an OpenVPN tls-version variable to mbed TLS format\n *\n * @param tls_ver       The tls-version variable to convert.\n *\n * @return Translated mbedTLS SSL version from OpenVPN TLS version.\n */\nmbedtls_ssl_protocol_version\ntls_version_to_ssl_version(int tls_ver)\n{\n    switch (tls_ver)\n    {\n#if defined(MBEDTLS_SSL_PROTO_TLS1_2)\n        case TLS_VER_1_2:\n            return MBEDTLS_SSL_VERSION_TLS1_2;\n#endif\n\n#if defined(MBEDTLS_SSL_PROTO_TLS1_3)\n        case TLS_VER_1_3:\n            return MBEDTLS_SSL_VERSION_TLS1_3;\n#endif\n\n        default:\n            msg(M_FATAL, \"%s: invalid or unsupported TLS version %d\", __func__, tls_ver);\n            return MBEDTLS_SSL_VERSION_UNKNOWN;\n    }\n}\n\nvoid\nbackend_tls_ctx_reload_crl(struct tls_root_ctx *ctx, const char *crl_file, bool crl_inline)\n{\n    ASSERT(crl_file);\n\n    if (ctx->crl == NULL)\n    {\n        ALLOC_OBJ_CLEAR(ctx->crl, mbedtls_x509_crl);\n    }\n    mbedtls_x509_crl_free(ctx->crl);\n\n    if (crl_inline)\n    {\n        if (!mbed_ok(mbedtls_x509_crl_parse(ctx->crl, (const unsigned char *)crl_file,\n                                            strlen(crl_file) + 1)))\n        {\n            msg(M_WARN, \"CRL: cannot parse inline CRL\");\n            goto err;\n        }\n    }\n    else\n    {\n        if (!mbed_ok(mbedtls_x509_crl_parse_file(ctx->crl, crl_file)))\n        {\n            msg(M_WARN, \"CRL: cannot read CRL from file %s\", crl_file);\n            goto err;\n        }\n    }\n    return;\n\nerr:\n    mbedtls_x509_crl_free(ctx->crl);\n}\n\nvoid\nkey_state_ssl_init(struct key_state_ssl *ks_ssl, const struct tls_root_ctx *ssl_ctx, bool is_server,\n                   struct tls_session *session)\n{\n    ASSERT(NULL != ssl_ctx);\n    ASSERT(ks_ssl);\n    CLEAR(*ks_ssl);\n\n    /* Initialise SSL config */\n    ALLOC_OBJ_CLEAR(ks_ssl->ssl_config, mbedtls_ssl_config);\n    mbedtls_ssl_config_init(ks_ssl->ssl_config);\n    mbedtls_ssl_config_defaults(ks_ssl->ssl_config, ssl_ctx->endpoint, MBEDTLS_SSL_TRANSPORT_STREAM,\n                                MBEDTLS_SSL_PRESET_DEFAULT);\n#ifdef MBEDTLS_DEBUG_C\n    /* We only want to have mbed TLS generate debug level logging when we would\n     * also display it.\n     * In fact mbed TLS 2.25.0 crashes generating debug log if Curve25591 is\n     * selected for DH (https://github.com/ARMmbed/mbedtls/issues/4208) */\n    if (session->opt->ssl_flags & SSLF_TLS_DEBUG_ENABLED)\n    {\n        mbedtls_debug_set_threshold(3);\n    }\n    else\n    {\n        mbedtls_debug_set_threshold(2);\n    }\n#endif\n    mbedtls_ssl_conf_dbg(ks_ssl->ssl_config, my_debug, NULL);\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n    mbedtls_ssl_conf_rng(ks_ssl->ssl_config, mbedtls_ctr_drbg_random, rand_ctx_get());\n#endif /* MBEDTLS_VERSION_NUMBER < 0x04000000 */\n\n    mbedtls_ssl_conf_cert_profile(ks_ssl->ssl_config, &ssl_ctx->cert_profile);\n\n    if (ssl_ctx->allowed_ciphers)\n    {\n        mbedtls_ssl_conf_ciphersuites(ks_ssl->ssl_config, ssl_ctx->allowed_ciphers);\n    }\n\n    if (ssl_ctx->groups)\n    {\n        mbedtls_ssl_conf_groups(ks_ssl->ssl_config, ssl_ctx->groups);\n    }\n\n    /* Disable TLS renegotiations if the mbedtls library supports that feature.\n     * OpenVPN's renegotiation creates new SSL sessions and does not depend on\n     * this feature and TLS renegotiations have been problematic in the past. */\n#if defined(MBEDTLS_SSL_RENEGOTIATION)\n    mbedtls_ssl_conf_renegotiation(ks_ssl->ssl_config, MBEDTLS_SSL_RENEGOTIATION_DISABLED);\n#endif /* MBEDTLS_SSL_RENEGOTIATION */\n\n    /* Disable record splitting (for now).  OpenVPN assumes records are sent\n     * unfragmented, and changing that will require thorough review and\n     * testing.  Since OpenVPN is not susceptible to BEAST, we can just\n     * disable record splitting as a quick fix. */\n#if defined(MBEDTLS_SSL_CBC_RECORD_SPLITTING)\n    mbedtls_ssl_conf_cbc_record_splitting(ks_ssl->ssl_config,\n                                          MBEDTLS_SSL_CBC_RECORD_SPLITTING_DISABLED);\n#endif /* MBEDTLS_SSL_CBC_RECORD_SPLITTING */\n\n    /* Initialise authentication information */\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n    if (is_server)\n    {\n        mbed_ok(mbedtls_ssl_conf_dh_param_ctx(ks_ssl->ssl_config, ssl_ctx->dhm_ctx));\n    }\n#endif\n\n    (void)mbed_ok(mbedtls_ssl_conf_own_cert(ks_ssl->ssl_config, ssl_ctx->crt_chain, ssl_ctx->priv_key));\n\n    /* Initialise SSL verification */\n    if (session->opt->ssl_flags & SSLF_CLIENT_CERT_OPTIONAL)\n    {\n        mbedtls_ssl_conf_authmode(ks_ssl->ssl_config, MBEDTLS_SSL_VERIFY_OPTIONAL);\n    }\n    else if (!(session->opt->ssl_flags & SSLF_CLIENT_CERT_NOT_REQUIRED))\n    {\n        mbedtls_ssl_conf_authmode(ks_ssl->ssl_config, MBEDTLS_SSL_VERIFY_REQUIRED);\n    }\n    mbedtls_ssl_conf_verify(ks_ssl->ssl_config, verify_callback, session);\n\n    /* TODO: mbed TLS does not currently support sending the CA chain to the client */\n    mbedtls_ssl_conf_ca_chain(ks_ssl->ssl_config, ssl_ctx->ca_chain, ssl_ctx->crl);\n\n    /* Initialize minimum TLS version */\n    {\n        const int configured_tls_version_min =\n            (session->opt->ssl_flags >> SSLF_TLS_VERSION_MIN_SHIFT) & SSLF_TLS_VERSION_MIN_MASK;\n\n        /* default to TLS 1.2 */\n        mbedtls_ssl_protocol_version version = MBEDTLS_SSL_VERSION_TLS1_2;\n\n        if (configured_tls_version_min > TLS_VER_UNSPEC)\n        {\n            version = tls_version_to_ssl_version(configured_tls_version_min);\n        }\n\n        mbedtls_ssl_conf_min_tls_version(ks_ssl->ssl_config, version);\n    }\n\n    /* Initialize maximum TLS version */\n    {\n        const int configured_tls_version_max =\n            (session->opt->ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT) & SSLF_TLS_VERSION_MAX_MASK;\n\n        mbedtls_ssl_protocol_version version = MBEDTLS_SSL_VERSION_UNKNOWN;\n\n        if (configured_tls_version_max > TLS_VER_UNSPEC)\n        {\n            version = tls_version_to_ssl_version(configured_tls_version_max);\n        }\n        else\n        {\n            /* Default to tls_version_max(). */\n            version = tls_version_to_ssl_version(tls_version_max());\n        }\n\n        mbedtls_ssl_conf_max_tls_version(ks_ssl->ssl_config, version);\n    }\n\n    /* Initialise SSL context */\n    ALLOC_OBJ_CLEAR(ks_ssl->ctx, mbedtls_ssl_context);\n    mbedtls_ssl_init(ks_ssl->ctx);\n    (void)mbed_ok(mbedtls_ssl_setup(ks_ssl->ctx, ks_ssl->ssl_config));\n    /* We do verification in our own callback depending on the\n     * exact configuration. We do not rely on the default hostname\n     * verification. */\n    ASSERT(mbed_ok(mbedtls_ssl_set_hostname(ks_ssl->ctx, NULL)));\n\n#if !defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT)\n    /* Initialize the keying material exporter callback. */\n    mbedtls_ssl_set_export_keys_cb(ks_ssl->ctx, mbedtls_ssl_export_keys_cb, session);\n#endif\n\n    /* Initialise BIOs */\n    ALLOC_OBJ_CLEAR(ks_ssl->bio_ctx, bio_ctx);\n    mbedtls_ssl_set_bio(ks_ssl->ctx, ks_ssl->bio_ctx, ssl_bio_write, ssl_bio_read, NULL);\n}\n\n\nvoid\nkey_state_ssl_shutdown(struct key_state_ssl *ks_ssl)\n{\n    mbedtls_ssl_send_alert_message(ks_ssl->ctx, MBEDTLS_SSL_ALERT_LEVEL_FATAL,\n                                   MBEDTLS_SSL_ALERT_MSG_CLOSE_NOTIFY);\n}\n\nvoid\nkey_state_ssl_free(struct key_state_ssl *ks_ssl)\n{\n    if (ks_ssl)\n    {\n        CLEAR(ks_ssl->tls_key_cache);\n\n        if (ks_ssl->ctx)\n        {\n            mbedtls_ssl_free(ks_ssl->ctx);\n            free(ks_ssl->ctx);\n        }\n        if (ks_ssl->ssl_config)\n        {\n            mbedtls_ssl_config_free(ks_ssl->ssl_config);\n            free(ks_ssl->ssl_config);\n        }\n        if (ks_ssl->bio_ctx)\n        {\n            buf_free_entries(&ks_ssl->bio_ctx->in);\n            buf_free_entries(&ks_ssl->bio_ctx->out);\n            free(ks_ssl->bio_ctx);\n        }\n        CLEAR(*ks_ssl);\n    }\n}\n\nint\nkey_state_write_plaintext(struct key_state_ssl *ks, struct buffer *buf)\n{\n    int retval = 0;\n\n    ASSERT(buf);\n\n    retval = key_state_write_plaintext_const(ks, BPTR(buf), BLEN(buf));\n\n    if (1 == retval)\n    {\n        memset(BPTR(buf), 0, BLEN(buf)); /* erase data just written */\n        buf->len = 0;\n    }\n\n    return retval;\n}\n\nint\nkey_state_write_plaintext_const(struct key_state_ssl *ks, const uint8_t *data, int len)\n{\n    int retval = 0;\n\n    ASSERT(NULL != ks);\n    ASSERT(len >= 0);\n\n    if (0 == len)\n    {\n        return 0;\n    }\n\n    ASSERT(data);\n\n    retval = mbedtls_ssl_write(ks->ctx, data, len);\n\n    if (retval < 0)\n    {\n        if (MBEDTLS_ERR_SSL_WANT_WRITE == retval || MBEDTLS_ERR_SSL_WANT_READ == retval)\n        {\n            return 0;\n        }\n        mbed_log_err(D_TLS_ERRORS, retval, \"TLS ERROR: write tls_write_plaintext_const error\");\n        return -1;\n    }\n\n    if (retval != len)\n    {\n        msg(D_TLS_ERRORS, \"TLS ERROR: write tls_write_plaintext_const incomplete %d/%d\", retval,\n            len);\n        return -1;\n    }\n\n    /* successful write */\n    dmsg(D_HANDSHAKE_VERBOSE, \"write tls_write_plaintext_const %d bytes\", retval);\n\n    return 1;\n}\n\nint\nkey_state_read_ciphertext(struct key_state_ssl *ks, struct buffer *buf)\n{\n    int retval = 0;\n    int len = 0;\n\n    ASSERT(NULL != ks);\n    ASSERT(buf);\n    ASSERT(buf->len >= 0);\n\n    if (buf->len)\n    {\n        return 0;\n    }\n\n    len = buf_forward_capacity(buf);\n\n    retval = endless_buf_read(&ks->bio_ctx->out, BPTR(buf), len);\n\n    /* Error during read, check for retry error */\n    if (retval < 0)\n    {\n        if (MBEDTLS_ERR_SSL_WANT_WRITE == retval || MBEDTLS_ERR_SSL_WANT_READ == retval)\n        {\n            return 0;\n        }\n        mbed_log_err(D_TLS_ERRORS, retval, \"TLS_ERROR: read tls_read_ciphertext error\");\n        buf->len = 0;\n        return -1;\n    }\n    /* Nothing read, try again */\n    if (0 == retval)\n    {\n        buf->len = 0;\n        return 0;\n    }\n\n    /* successful read */\n    dmsg(D_HANDSHAKE_VERBOSE, \"read tls_read_ciphertext %d bytes\", retval);\n    buf->len = retval;\n    return 1;\n}\n\nint\nkey_state_write_ciphertext(struct key_state_ssl *ks, struct buffer *buf)\n{\n    int retval = 0;\n\n    ASSERT(NULL != ks);\n    ASSERT(buf);\n    ASSERT(buf->len >= 0);\n\n    if (0 == buf->len)\n    {\n        return 0;\n    }\n\n    retval = endless_buf_write(&ks->bio_ctx->in, BPTR(buf), buf->len);\n\n    if (retval < 0)\n    {\n        if (MBEDTLS_ERR_SSL_WANT_WRITE == retval || MBEDTLS_ERR_SSL_WANT_READ == retval)\n        {\n            return 0;\n        }\n        mbed_log_err(D_TLS_ERRORS, retval, \"TLS ERROR: write tls_write_ciphertext error\");\n        return -1;\n    }\n\n    if (retval != buf->len)\n    {\n        msg(D_TLS_ERRORS, \"TLS ERROR: write tls_write_ciphertext incomplete %d/%d\", retval,\n            buf->len);\n        return -1;\n    }\n\n    /* successful write */\n    dmsg(D_HANDSHAKE_VERBOSE, \"write tls_write_ciphertext %d bytes\", retval);\n\n    memset(BPTR(buf), 0, BLEN(buf)); /* erase data just written */\n    buf->len = 0;\n\n    return 1;\n}\n\nint\nkey_state_read_plaintext(struct key_state_ssl *ks, struct buffer *buf)\n{\n    int retval = 0;\n    int len = 0;\n\n    ASSERT(NULL != ks);\n    ASSERT(buf);\n    ASSERT(buf->len >= 0);\n\n    if (buf->len)\n    {\n        return 0;\n    }\n\n    len = buf_forward_capacity(buf);\n\n    retval = mbedtls_ssl_read(ks->ctx, BPTR(buf), len);\n\n    /* Error during read, check for retry error */\n    if (retval < 0)\n    {\n        if (MBEDTLS_ERR_SSL_WANT_WRITE == retval || MBEDTLS_ERR_SSL_WANT_READ == retval\n            || MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET == retval)\n        {\n            return 0;\n        }\n        mbed_log_err(D_TLS_ERRORS, retval, \"TLS_ERROR: read tls_read_plaintext error\");\n        buf->len = 0;\n        return -1;\n    }\n    /* Nothing read, try again */\n    if (0 == retval)\n    {\n        buf->len = 0;\n        return 0;\n    }\n\n    /* successful read */\n    dmsg(D_HANDSHAKE_VERBOSE, \"read tls_read_plaintext %d bytes\", retval);\n    buf->len = retval;\n\n    return 1;\n}\n\n/* **************************************\n *\n * Information functions\n *\n * Print information for the end user.\n *\n ***************************************/\nvoid\nprint_details(struct key_state_ssl *ks_ssl, const char *prefix)\n{\n    const mbedtls_x509_crt *cert;\n    char s1[256];\n    char s2[256];\n\n    s1[0] = s2[0] = 0;\n    snprintf(s1, sizeof(s1), \"%s %s, cipher %s\", prefix, mbedtls_ssl_get_version(ks_ssl->ctx),\n             mbedtls_ssl_get_ciphersuite(ks_ssl->ctx));\n\n    cert = mbedtls_ssl_get_peer_cert(ks_ssl->ctx);\n    if (cert != NULL)\n    {\n        snprintf(s2, sizeof(s2), \", %u bit key\", (unsigned int)mbedtls_pk_get_bitlen(&cert->pk));\n    }\n\n    msg(D_HANDSHAKE, \"%s%s\", s1, s2);\n}\n\nvoid\nshow_available_tls_ciphers_list(const char *cipher_list, const char *tls_cert_profile, bool tls13)\n{\n    if (tls13)\n    {\n        /* mbed TLS has no TLS 1.3 support currently */\n        return;\n    }\n    struct tls_root_ctx tls_ctx;\n    const int *ciphers = mbedtls_ssl_list_ciphersuites();\n\n    tls_ctx_server_new(&tls_ctx);\n    tls_ctx_set_cert_profile(&tls_ctx, tls_cert_profile);\n    tls_ctx_restrict_ciphers(&tls_ctx, cipher_list);\n\n    if (tls_ctx.allowed_ciphers)\n    {\n        ciphers = tls_ctx.allowed_ciphers;\n    }\n\n    while (*ciphers != 0)\n    {\n        printf(\"%s\\n\", mbedtls_ssl_get_ciphersuite_name(*ciphers));\n        ciphers++;\n    }\n    tls_ctx_free(&tls_ctx);\n}\n\nvoid\nshow_available_curves(void)\n{\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n    const mbedtls_ecp_curve_info *pcurve = mbedtls_ecp_curve_list();\n\n    if (NULL == pcurve)\n    {\n        msg(M_FATAL, \"Cannot retrieve curve list from mbed TLS\");\n    }\n\n    /* Print curve list */\n    printf(\"Available Elliptic curves, listed in order of preference:\\n\\n\");\n    while (MBEDTLS_ECP_DP_NONE != pcurve->grp_id)\n    {\n        printf(\"%s\\n\", pcurve->name);\n        pcurve++;\n    }\n#else\n    printf(\"Available elliptic curves:\\n\\n\");\n    for (size_t i = 0; i < ecp_curve_info_table_items; i++)\n    {\n        printf(\"%s\\n\", ecp_curve_info_table[i].name);\n    }\n#endif /* MBEDTLS_VERSION_NUMBER < 0x04000000 */\n}\n\nconst char *\nget_ssl_library_version(void)\n{\n    static char mbedtls_version[30];\n    unsigned int pv = mbedtls_version_get_number();\n    snprintf(mbedtls_version, sizeof(mbedtls_version), \"mbed TLS %d.%d.%d\", (pv >> 24) & 0xff,\n             (pv >> 16) & 0xff, (pv >> 8) & 0xff);\n    return mbedtls_version;\n}\n\nvoid\nload_xkey_provider(void)\n{\n    return; /* no external key provider in mbedTLS build */\n}\n\n#endif /* defined(ENABLE_CRYPTO_MBEDTLS) */\n"
  },
  {
    "path": "src/openvpn/ssl_mbedtls.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel mbed TLS Backend\n */\n\n#ifndef SSL_MBEDTLS_H_\n#define SSL_MBEDTLS_H_\n\n#include \"syshead.h\"\n\n#include <mbedtls/ssl.h>\n#include <mbedtls/x509_crt.h>\n#include <mbedtls/version.h>\n\n#if defined(ENABLE_PKCS11)\n#include <pkcs11-helper-1.0/pkcs11h-certificate.h>\n#endif\n\ntypedef struct _buffer_entry buffer_entry;\n\nstruct _buffer_entry\n{\n    size_t length;\n    uint8_t *data;\n    buffer_entry *next_block;\n};\n\ntypedef struct\n{\n    size_t data_start;\n    buffer_entry *first_block;\n    buffer_entry *last_block;\n} endless_buffer;\n\ntypedef struct\n{\n    endless_buffer in;\n    endless_buffer out;\n} bio_ctx;\n\n/**\n * External signing function prototype.  A function pointer to a function\n * implementing this prototype is provided to\n * tls_ctx_use_external_signing_func().\n *\n * @param sign_ctx  The context for the signing function.\n * @param src       The data to be signed,\n * @param src_size  The length of src, in bytes.\n * @param dst       The destination buffer for the signature.\n * @param dst_size  The length of the destination buffer.\n *\n * @return true if signing succeeded, false otherwise.\n */\ntypedef bool (*external_sign_func)(void *sign_ctx, const void *src, size_t src_size, void *dst,\n                                   size_t dst_size);\n\n/** Context used by external_pkcs1_sign() */\nstruct external_context\n{\n    size_t signature_length;\n    external_sign_func sign;\n    void *sign_ctx;\n};\n\n#if !defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT)\n/**\n * struct to cache TLS secrets for keying material exporter (RFC 5705).\n * Not needed if the library itself implements the keying material exporter.\n *\n * The constants 64 and 48 are inherent to TLS 1.2. For TLS 1.3, it is not\n * possible to obtain the exporter master secret from mbed TLS. */\nstruct tls_key_cache\n{\n    unsigned char client_server_random[64];\n    mbedtls_tls_prf_types tls_prf_type;\n    unsigned char master_secret[48];\n};\n#else /* !defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT) */\nstruct tls_key_cache\n{\n};\n#endif\n\n/**\n * Structure that wraps the TLS context. Contents differ depending on the\n * SSL library used.\n *\n * Either \\c priv_key_pkcs11 or \\c priv_key must be filled in.\n */\nstruct tls_root_ctx\n{\n    bool initialised; /**< True if the context has been initialised */\n\n    int endpoint;     /**< Whether or not this is a server or a client */\n\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n    mbedtls_dhm_context *dhm_ctx;          /**< Diffie-Helmann-Merkle context */\n#endif\n    mbedtls_x509_crt *crt_chain;           /**< Local Certificate chain */\n    mbedtls_x509_crt *ca_chain;            /**< CA chain for remote verification */\n    mbedtls_pk_context *priv_key;          /**< Local private key */\n    mbedtls_x509_crl *crl;                 /**< Certificate Revocation List */\n    time_t crl_last_mtime;                 /**< CRL last modification time */\n    off_t crl_last_size;                   /**< size of last loaded CRL */\n#ifdef ENABLE_PKCS11\n    pkcs11h_certificate_t pkcs11_cert;     /**< PKCS11 certificate */\n#endif\n    struct external_context external_key;  /**< External key context */\n    int *allowed_ciphers;                  /**< List of allowed ciphers for this connection */\n    uint16_t *groups;                      /**< List of allowed groups for this connection */\n    mbedtls_x509_crt_profile cert_profile; /**< Allowed certificate types */\n};\n\nstruct key_state_ssl\n{\n    mbedtls_ssl_config *ssl_config; /**< mbedTLS global ssl config */\n    mbedtls_ssl_context *ctx;       /**< mbedTLS connection context */\n    bio_ctx *bio_ctx;\n\n    struct tls_key_cache tls_key_cache;\n};\n\n/**\n * Call the supplied signing function to create a TLS signature during the\n * TLS handshake.\n *\n * @param ctx                   TLS context to use.\n * @param sign_func             Signing function to call.\n * @param sign_ctx              Context for the sign function.\n *\n * @return                      0 if successful, 1 if an error occurred.\n */\nint tls_ctx_use_external_signing_func(struct tls_root_ctx *ctx, external_sign_func sign_func,\n                                      void *sign_ctx);\n\nstatic inline void\ntls_clear_error(void)\n{\n}\n#endif /* SSL_MBEDTLS_H_ */\n"
  },
  {
    "path": "src/openvpn/ssl_ncp.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *  Copyright (C) 2008-2026 David Sommerseth <dazo@eurephia.org>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel SSL/Data dynamic negotiation Module\n * This file is split from ssl.c to be able to unit test it.\n */\n\n/*\n * The routines in this file deal with dynamically negotiating\n * the data channel HMAC and cipher keys through a TLS session.\n *\n * Both the TLS session and the data channel are multiplexed\n * over the same TCP/UDP port.\n */\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <string.h>\n\n#include \"syshead.h\"\n#include \"win32.h\"\n\n#include \"error.h\"\n#include \"common.h\"\n\n#include \"ssl_ncp.h\"\n#include \"ssl_util.h\"\n#include \"openvpn.h\"\n\n/**\n * Return the Negotiable Crypto Parameters version advertised in the peer info\n * string, or 0 if none specified.\n */\nstatic int\ntls_peer_info_ncp_ver(const char *peer_info)\n{\n    const char *ncpstr = peer_info ? strstr(peer_info, \"IV_NCP=\") : NULL;\n    if (ncpstr)\n    {\n        int ncp = 0;\n        int r = sscanf(ncpstr, \"IV_NCP=%d\", &ncp);\n        if (r == 1)\n        {\n            return ncp;\n        }\n    }\n    return 0;\n}\n\n/**\n * Returns whether the client supports NCP either by\n * announcing IV_NCP>=2 or the IV_CIPHERS list\n */\nbool\ntls_peer_supports_ncp(const char *peer_info)\n{\n    if (!peer_info)\n    {\n        return false;\n    }\n    else if (tls_peer_info_ncp_ver(peer_info) >= 2 || strstr(peer_info, \"IV_CIPHERS=\"))\n    {\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\nchar *\nmutate_ncp_cipher_list(const char *list, struct gc_arena *gc)\n{\n    bool error_found = false;\n\n    struct buffer new_list = alloc_buf(MAX_NCP_CIPHERS_LENGTH);\n\n    char *const tmp_ciphers = string_alloc(list, NULL);\n    const char *token = strtok(tmp_ciphers, \":\");\n    while (token)\n    {\n        /*\n         * Going cipher_kt_name (and translate_cipher_name_from_openvpn/\n         * translate_cipher_name_to_openvpn) also normalises the cipher name,\n         * e.g. replacing AeS-128-gCm with AES-128-GCM\n         *\n         * ciphers that have ? in front of them are considered optional and\n         * OpenVPN will only warn if they are not found (and remove them from\n         * the list)\n         */\n        bool optional = false;\n        if (token[0] == '?')\n        {\n            token++;\n            optional = true;\n        }\n\n        const bool nonecipher = (strcmp(token, \"none\") == 0);\n        const char *optstr = optional ? \"optional \" : \"\";\n\n        if (nonecipher)\n        {\n            msg(M_WARN, \"WARNING: cipher 'none' specified for --data-ciphers. \"\n                        \"This allows negotiation of NO encryption and \"\n                        \"tunnelled data WILL then be transmitted in clear text \"\n                        \"over the network! \"\n                        \"PLEASE DO RECONSIDER THIS SETTING!\");\n        }\n        if (!nonecipher && !cipher_valid(token))\n        {\n            msg(M_WARN, \"Unsupported %scipher in --data-ciphers: %s\", optstr, token);\n            error_found = error_found || !optional;\n        }\n        else if (!nonecipher && !cipher_kt_mode_aead(token) && !cipher_kt_mode_cbc(token)\n                 && !cipher_kt_mode_ofb_cfb(token))\n        {\n            msg(M_WARN,\n                \"Unsupported %scipher algorithm '%s'. It does not use \"\n                \"CFB, OFB, CBC, or a supported AEAD mode\",\n                optstr, token);\n            error_found = error_found || !optional;\n        }\n        else\n        {\n            const char *ovpn_cipher_name = cipher_kt_name(token);\n            if (nonecipher)\n            {\n                /* NULL resolves to [null-cipher] but we need none for\n                 * data-ciphers */\n                ovpn_cipher_name = \"none\";\n            }\n\n            if (buf_len(&new_list) > 0)\n            {\n                /* The next if condition ensure there is always space for\n                 * a :\n                 */\n                buf_puts(&new_list, \":\");\n            }\n\n            /* Ensure buffer has capacity for cipher name + : + \\0 */\n            if (buf_forward_capacity(&new_list) < (int)strlen(ovpn_cipher_name) + 2)\n            {\n                msg(M_WARN, \"Length of --data-ciphers is over the \"\n                            \"limit of 127 chars\");\n                error_found = true;\n            }\n            else\n            {\n                buf_puts(&new_list, ovpn_cipher_name);\n            }\n        }\n        token = strtok(NULL, \":\");\n    }\n\n\n    char *ret = NULL;\n    if (!error_found && buf_len(&new_list) > 0)\n    {\n        buf_null_terminate(&new_list);\n        ret = string_alloc(buf_str(&new_list), gc);\n    }\n    free(tmp_ciphers);\n    free_buf(&new_list);\n\n    return ret;\n}\n\n\nvoid\nappend_cipher_to_ncp_list(struct options *o, const char *ciphername)\n{\n    /* Append the --cipher to ncp_ciphers to allow it in NCP */\n    size_t newlen = strlen(o->ncp_ciphers) + 1 + strlen(ciphername) + 1;\n    char *ncp_ciphers = gc_malloc(newlen, false, &o->gc);\n\n    ASSERT(checked_snprintf(ncp_ciphers, newlen, \"%s:%s\", o->ncp_ciphers, ciphername));\n    o->ncp_ciphers = ncp_ciphers;\n}\n\nbool\ntls_item_in_cipher_list(const char *item, const char *list)\n{\n    char *tmp_ciphers = string_alloc(list, NULL);\n    char *tmp_ciphers_orig = tmp_ciphers;\n\n    const char *token = strtok(tmp_ciphers, \":\");\n    while (token)\n    {\n        if (0 == strcmp(token, item))\n        {\n            break;\n        }\n        token = strtok(NULL, \":\");\n    }\n    free(tmp_ciphers_orig);\n\n    return token != NULL;\n}\nconst char *\ntls_peer_ncp_list(const char *peer_info, struct gc_arena *gc)\n{\n    /* Check if the peer sends the IV_CIPHERS list */\n    const char *iv_ciphers = extract_var_peer_info(peer_info, \"IV_CIPHERS=\", gc);\n    if (iv_ciphers)\n    {\n        return iv_ciphers;\n    }\n    else if (tls_peer_info_ncp_ver(peer_info) >= 2)\n    {\n        /* If the peer announces IV_NCP=2 then it supports the AES GCM\n         * ciphers */\n        return \"AES-256-GCM:AES-128-GCM\";\n    }\n    else\n    {\n        return \"\";\n    }\n}\n\nchar *\nncp_get_best_cipher(const char *server_list, const char *peer_info, const char *remote_cipher,\n                    struct gc_arena *gc)\n{\n    /*\n     * The gc of the parameter is tied to the VPN session, create a\n     * short lived gc arena that is only valid for the duration of\n     * this function\n     */\n\n    struct gc_arena gc_tmp = gc_new();\n\n    const char *peer_ncp_list = tls_peer_ncp_list(peer_info, &gc_tmp);\n\n    /* non-NCP clients without OCC?  \"assume nothing\" */\n    /* For client doing the newer version of NCP (that send IV_CIPHERS)\n     * we cannot assume that they will accept remote_cipher */\n    if (remote_cipher == NULL || (peer_info && strstr(peer_info, \"IV_CIPHERS=\")))\n    {\n        remote_cipher = \"\";\n    }\n\n    char *tmp_ciphers = string_alloc(server_list, &gc_tmp);\n\n    const char *token;\n    while ((token = strsep(&tmp_ciphers, \":\")))\n    {\n        if (tls_item_in_cipher_list(token, peer_ncp_list) || streq(token, remote_cipher))\n        {\n            break;\n        }\n    }\n\n    char *ret = NULL;\n    if (token != NULL)\n    {\n        ret = string_alloc(token, gc);\n    }\n\n    gc_free(&gc_tmp);\n    return ret;\n}\n\n/**\n * \"Poor man's NCP\": Use peer cipher if it is an allowed (NCP) cipher.\n * Allows non-NCP peers to upgrade their cipher individually.\n *\n * Returns true if we switched to the peer's cipher\n *\n * Make sure to call tls_session_update_crypto_params() after calling this\n * function.\n */\nstatic bool\ntls_poor_mans_ncp(struct options *o, const char *remote_ciphername)\n{\n    if (remote_ciphername && tls_item_in_cipher_list(remote_ciphername, o->ncp_ciphers))\n    {\n        o->ciphername = string_alloc(remote_ciphername, &o->gc);\n        msg(D_TLS_DEBUG_LOW, \"Using peer cipher '%s'\", o->ciphername);\n        return true;\n    }\n    return false;\n}\n\nbool\ncheck_pull_client_ncp(struct context *c, const unsigned int found)\n{\n    if (found & OPT_P_NCP)\n    {\n        msg(D_PUSH_DEBUG, \"OPTIONS IMPORT: data channel crypto options modified\");\n        return true;\n    }\n\n    /* If the server did not push a --cipher, we will switch to the\n     * remote cipher if it is in our ncp-ciphers list */\n    if (tls_poor_mans_ncp(&c->options, c->c2.tls_multi->remote_ciphername))\n    {\n        return true;\n    }\n\n    /* We could not figure out the peer's cipher but we have fallback\n     * enabled */\n    if (!c->c2.tls_multi->remote_ciphername && c->options.enable_ncp_fallback)\n    {\n        return true;\n    }\n\n    /* We failed negotiation, give appropriate error message */\n    if (c->c2.tls_multi->remote_ciphername)\n    {\n        msg(D_TLS_ERRORS,\n            \"OPTIONS ERROR: failed to negotiate \"\n            \"cipher with server.  Add the server's \"\n            \"cipher ('%s') to --data-ciphers (currently '%s'), e.g.\"\n            \"--data-ciphers %s:%s if you want to connect to this server.\",\n            c->c2.tls_multi->remote_ciphername, c->options.ncp_ciphers_conf,\n            c->options.ncp_ciphers_conf, c->c2.tls_multi->remote_ciphername);\n        return false;\n    }\n    else\n    {\n        msg(D_TLS_ERRORS, \"OPTIONS ERROR: failed to negotiate \"\n                          \"cipher with server. Configure \"\n                          \"--data-ciphers-fallback if you want to connect \"\n                          \"to this server.\");\n        return false;\n    }\n}\n\nconst char *\nget_p2p_ncp_cipher(struct tls_session *session, const char *peer_info, struct gc_arena *gc)\n{\n    /* we use a local gc arena to keep the temporary strings needed by strsep */\n    struct gc_arena gc_local = gc_new();\n    const char *peer_ciphers = extract_var_peer_info(peer_info, \"IV_CIPHERS=\", &gc_local);\n\n    if (!peer_ciphers)\n    {\n        gc_free(&gc_local);\n        return NULL;\n    }\n\n    const char *server_ciphers;\n    const char *client_ciphers;\n\n    if (session->opt->server)\n    {\n        server_ciphers = session->opt->config_ncp_ciphers;\n        client_ciphers = peer_ciphers;\n    }\n    else\n    {\n        client_ciphers = session->opt->config_ncp_ciphers;\n        server_ciphers = peer_ciphers;\n    }\n\n    /* Find the first common cipher from TLS server and TLS client. We\n     * use the preference of the server here to make it deterministic */\n    char *tmp_ciphers = string_alloc(server_ciphers, &gc_local);\n\n    const char *token;\n    while ((token = strsep(&tmp_ciphers, \":\")))\n    {\n        if (tls_item_in_cipher_list(token, client_ciphers))\n        {\n            break;\n        }\n    }\n\n    const char *ret = NULL;\n    if (token != NULL)\n    {\n        ret = string_alloc(token, gc);\n    }\n    gc_free(&gc_local);\n\n    return ret;\n}\n\nstatic void\np2p_ncp_set_options(struct tls_multi *multi, struct tls_session *session, const char *common_cipher)\n{\n    /* will return 0 if peer_info is null */\n    const unsigned int iv_proto_peer = extract_iv_proto(multi->peer_info);\n\n    /* The other peer does not support P2P NCP */\n    if (!(iv_proto_peer & IV_PROTO_NCP_P2P))\n    {\n        return;\n    }\n\n    if (iv_proto_peer & IV_PROTO_DATA_V2)\n    {\n        multi->use_peer_id = true;\n        multi->peer_id = 0x76706e; /* 'v' 'p' 'n' */\n    }\n\n    if (iv_proto_peer & IV_PROTO_CC_EXIT_NOTIFY)\n    {\n        session->opt->crypto_flags |= CO_USE_CC_EXIT_NOTIFY;\n    }\n\n    if (session->opt->data_epoch_supported && (iv_proto_peer & IV_PROTO_DATA_EPOCH) && common_cipher\n        && cipher_kt_mode_aead(common_cipher))\n    {\n        session->opt->crypto_flags |= CO_EPOCH_DATA_KEY_FORMAT;\n    }\n    else\n    {\n        /* The peer might have changed its ciphers options during reconnect,\n         * ensure we clear the flag if we previously had it enabled */\n        session->opt->crypto_flags &= ~CO_EPOCH_DATA_KEY_FORMAT;\n    }\n\n    if (iv_proto_peer & IV_PROTO_TLS_KEY_EXPORT)\n    {\n        session->opt->crypto_flags |= CO_USE_TLS_KEY_MATERIAL_EXPORT;\n\n        if (multi->use_peer_id)\n        {\n            /* Using a non hardcoded peer-id makes a tiny bit harder to\n             * fingerprint packets and also gives each connection a unique\n             * peer-id that can be useful for NAT tracking etc. */\n\n            uint8_t peerid[3];\n            if (!key_state_export_keying_material(session, EXPORT_P2P_PEERID_LABEL,\n                                                  strlen(EXPORT_P2P_PEERID_LABEL), &peerid, 3))\n            {\n                /* Non DCO setup might still work but also this should never\n                 * happen or very likely the TLS encryption key exporter will\n                 * also fail */\n                msg(M_NONFATAL, \"TLS key export for P2P peer id failed. \"\n                                \"Continuing anyway, expect problems\");\n            }\n            else\n            {\n                multi->peer_id = (peerid[0] << 16) + (peerid[1] << 8) + peerid[2];\n            }\n        }\n    }\n    if (iv_proto_peer & IV_PROTO_DYN_TLS_CRYPT)\n    {\n        session->opt->crypto_flags |= CO_USE_DYNAMIC_TLS_CRYPT;\n    }\n}\n\nvoid\np2p_mode_ncp(struct tls_multi *multi, struct tls_session *session)\n{\n    struct gc_arena gc = gc_new();\n\n    /* Query the common cipher here to log it as part of our message.\n     * We postpone switching the cipher to do_up */\n    const char *common_cipher = get_p2p_ncp_cipher(session, multi->peer_info, &gc);\n\n    /* Set the common options */\n    p2p_ncp_set_options(multi, session, common_cipher);\n\n    if (!common_cipher)\n    {\n        struct buffer out = alloc_buf_gc(128, &gc);\n        /* at this point we do not really know if our fallback is\n         * not enabled or if we use 'none' cipher as fallback, so\n         * keep this ambiguity here and print fallback-cipher: none\n         */\n\n        const char *fallback_name = \"none\";\n        const char *ciphername = session->opt->key_type.cipher;\n\n        if (cipher_defined(ciphername))\n        {\n            fallback_name = cipher_kt_name(ciphername);\n        }\n\n        buf_printf(&out, \"(not negotiated, fallback-cipher: %s)\", fallback_name);\n        common_cipher = BSTR(&out);\n    }\n\n    msg(D_TLS_DEBUG_LOW,\n        \"P2P mode NCP negotiation result: \"\n        \"TLS_export=%d, DATA_v2=%d, peer-id %d, epoch=%d, cipher=%s\",\n        (bool)(session->opt->crypto_flags & CO_USE_TLS_KEY_MATERIAL_EXPORT), multi->use_peer_id,\n        multi->peer_id, (bool)(session->opt->crypto_flags & CO_EPOCH_DATA_KEY_FORMAT),\n        common_cipher);\n\n    gc_free(&gc);\n}\n\n\nbool\ncheck_session_cipher(struct tls_session *session, struct options *options)\n{\n    bool cipher_allowed_as_fallback =\n        options->enable_ncp_fallback && streq(options->ciphername, session->opt->config_ciphername);\n\n    if (!session->opt->server && !cipher_allowed_as_fallback\n        && !tls_item_in_cipher_list(options->ciphername, options->ncp_ciphers))\n    {\n        struct gc_arena gc = gc_new();\n        msg(D_TLS_ERRORS, \"Error: negotiated cipher not allowed - %s not in %s%s\",\n            options->ciphername, options->ncp_ciphers_conf, ncp_expanded_ciphers(options, &gc));\n        /* undo cipher push, abort connection setup */\n        options->ciphername = session->opt->config_ciphername;\n        gc_free(&gc);\n        return false;\n    }\n    else\n    {\n        return true;\n    }\n}\n\n/**\n * Replaces the string DEFAULT with the string \\c replace.\n *\n * @param o         Options struct to modify and to use the gc from\n * @param replace   string used to replace the DEFAULT string\n */\nstatic void\nreplace_default_in_ncp_ciphers_option(struct options *o, const char *replace)\n{\n    const char *search = \"DEFAULT\";\n    const size_t ncp_ciphers_len = strlen(o->ncp_ciphers) + strlen(replace) - strlen(search) + 1;\n\n    uint8_t *ncp_ciphers = gc_malloc(ncp_ciphers_len, true, &o->gc);\n\n    struct buffer ncp_ciphers_buf;\n    ASSERT(ncp_ciphers_len <= INT_MAX);\n    buf_set_write(&ncp_ciphers_buf, ncp_ciphers, (int)ncp_ciphers_len);\n\n    const char *def = strstr(o->ncp_ciphers, search);\n\n    /* Copy everything before the DEFAULT string */\n    buf_write(&ncp_ciphers_buf, o->ncp_ciphers, def - o->ncp_ciphers);\n\n    /* copy the default string. */\n    buf_write(&ncp_ciphers_buf, replace, strlen(replace));\n\n    /* copy the rest of the ncp cipher string */\n    const char *after_default = def + strlen(search);\n    buf_write(&ncp_ciphers_buf, after_default, strlen(after_default));\n\n    o->ncp_ciphers = (char *)ncp_ciphers;\n}\n\n/**\n * Checks for availibility of Chacha20-Poly1305 and sets\n * the ncp_cipher to either AES-256-GCM:AES-128-GCM or\n * AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305.\n */\nvoid\noptions_postprocess_setdefault_ncpciphers(struct options *o)\n{\n    bool default_in_cipher_list =\n        o->ncp_ciphers && tls_item_in_cipher_list(\"DEFAULT\", o->ncp_ciphers);\n\n    /* preserve the values that the user put into the configuration */\n    o->ncp_ciphers_conf = o->ncp_ciphers;\n\n    /* check if crypto library supports chacha */\n    bool can_do_chacha = cipher_valid(\"CHACHA20-POLY1305\");\n\n    if (can_do_chacha && dco_enabled(o))\n    {\n        /* also make sure that dco supports chacha */\n        can_do_chacha = tls_item_in_cipher_list(\"CHACHA20-POLY1305\", dco_get_supported_ciphers());\n    }\n\n    const char *default_ciphers = \"AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305\";\n\n    if (!can_do_chacha)\n    {\n        default_ciphers = \"AES-256-GCM:AES-128-GCM\";\n    }\n\n    /* want to rather print DEFAULT instead of a manually set default list */\n    if (!o->ncp_ciphers_conf || !strcmp(default_ciphers, o->ncp_ciphers_conf))\n    {\n        o->ncp_ciphers = default_ciphers;\n        o->ncp_ciphers_conf = \"DEFAULT\";\n    }\n    else if (!default_in_cipher_list)\n    {\n        /* custom cipher list without DEFAULT string in it,\n         * nothing to replace/mutate */\n        return;\n    }\n    else\n    {\n        replace_default_in_ncp_ciphers_option(o, default_ciphers);\n    }\n}\n\nconst char *\nncp_expanded_ciphers(struct options *o, struct gc_arena *gc)\n{\n    if (!strcmp(o->ncp_ciphers, o->ncp_ciphers_conf))\n    {\n        /* expanded ciphers and user set ciphers are identical, no need to\n         * add an expanded version */\n        return \"\";\n    }\n\n    /* two extra brackets, one space, NUL byte */\n    struct buffer expanded_ciphers_buf = alloc_buf_gc(strlen(o->ncp_ciphers) + 4, gc);\n\n    buf_printf(&expanded_ciphers_buf, \" (%s)\", o->ncp_ciphers);\n    return BSTR(&expanded_ciphers_buf);\n}\n"
  },
  {
    "path": "src/openvpn/ssl_ncp.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel SSL/Data dynamic negotiation Module\n * This file is split from ssl.h to be able to unit test it.\n */\n\n#ifndef OPENVPN_SSL_NCP_H\n#define OPENVPN_SSL_NCP_H\n\n#include \"buffer.h\"\n#include \"options.h\"\n#include \"ssl_common.h\"\n\n/**\n * Returns whether the client supports NCP either by\n * announcing IV_NCP>=2 or the IV_CIPHERS list\n */\nbool tls_peer_supports_ncp(const char *peer_info);\n\n/* forward declaration to break include dependency loop */\nstruct context;\n\n/**\n * Checks whether the cipher negotiation is in an acceptable state\n * and we continue to connect or should abort.\n *\n * @return  Wether the client NCP process suceeded or failed\n */\nbool check_pull_client_ncp(struct context *c, unsigned int found);\n\n/**\n * Iterates through the ciphers in server_list and return the first\n * cipher that is also supported by the peer according to the IV_NCP\n * and IV_CIPHER values in peer_info.\n *\n * We also accept a cipher that is the remote cipher of the client for\n * \"Poor man's NCP\": Use peer cipher if it is an allowed (NCP) cipher.\n * Allows non-NCP peers to upgrade their cipher individually.\n *\n * Make sure to call tls_session_update_crypto_params() after calling this\n * function.\n *\n * @param server_list   Our own cipher list\n * @param peer_info     Peer information\n * @param remote_cipher Fallback cipher, ignored if peer sent \\c IV_CIPHERS\n * @param gc            gc arena that is used to allocate the returned string\n *\n * @returns NULL if no common cipher is available, otherwise the best common\n * cipher\n */\nchar *ncp_get_best_cipher(const char *server_list, const char *peer_info, const char *remote_cipher,\n                          struct gc_arena *gc);\n\n\n/**\n * Returns the support cipher list from the peer according to the IV_NCP\n * and IV_CIPHER values in peer_info.\n *\n * @returns Either a string containing the ncp list that is either static\n * or allocated via gc. If no information is available an empty string\n * (\"\") is returned.\n */\nconst char *tls_peer_ncp_list(const char *peer_info, struct gc_arena *gc);\n\n/**\n * Check whether the ciphers in the supplied list are supported.\n *\n * @param list          Colon-separated list of ciphers\n * @param gc            gc_arena to allocate the returned string\n *\n * @returns             colon separated string of normalised (via\n *                      translate_cipher_name_from_openvpn) and\n *                      zero terminated string iff all ciphers\n *                      in list are supported and the total length\n *                      is short than MAX_NCP_CIPHERS_LENGTH. NULL\n *                      otherwise.\n */\nchar *mutate_ncp_cipher_list(const char *list, struct gc_arena *gc);\n\n/**\n * Appends the cipher specified by the ciphernamer parameter to to\n * the o->ncp_ciphers list.\n * @param o             options struct to modify. Its gc is also used\n * @param ciphername    the ciphername to add\n */\nvoid append_cipher_to_ncp_list(struct options *o, const char *ciphername);\n\n/**\n * Return true iff item is present in the colon-separated zero-terminated\n * cipher list.\n */\nbool tls_item_in_cipher_list(const char *item, const char *list);\n\n/**\n * The maximum length of a ncp-cipher string that is accepted.\n *\n * Since this list needs to be pushed as IV_CIPHERS, we are conservative\n * about its length.\n */\n#define MAX_NCP_CIPHERS_LENGTH 127\n\n/**\n * Determines if there is common cipher of both peer by looking at the\n * IV_CIPHER peer info. In contrast of the server mode NCP that tries to\n * accomandate all kind of corner cases in P2P mode NCP only takes IV_CIPHER\n * into account and falls back to previous behaviour if this fails.\n */\nvoid p2p_mode_ncp(struct tls_multi *multi, struct tls_session *session);\n\n/**\n * Determines the best common cipher from both peers IV_CIPHER lists. The\n * first cipher from the tls-server that is also in the tls-client IV_CIPHER\n * list will be returned. If no common cipher can be found, both peer\n * will continue to use whatever cipher is their default and NULL will be\n * returned.\n *\n * @param session       tls_session\n * @param peer_info     peer info of the peer\n * @param gc            gc arena that will be used to allocate the returned cipher\n * @return              common cipher if one exist.\n */\nconst char *get_p2p_ncp_cipher(struct tls_session *session, const char *peer_info,\n                               struct gc_arena *gc);\n\n\n/**\n * Checks if the cipher is allowed, otherwise returns false and reset the\n * cipher to the config cipher.\n */\nbool check_session_cipher(struct tls_session *session, struct options *options);\n\n/**\n * Checks for availability of Chacha20-Poly1305 and sets\n * the ncp_cipher to either AES-256-GCM:AES-128-GCM or\n * AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305 if not set.\n *\n * If DEFAULT is in the ncp_cipher string, it will be replaced\n * by the default cipher string as defined above.\n */\nvoid options_postprocess_setdefault_ncpciphers(struct options *o);\n\n/** returns the o->ncp_ciphers in brackets, e.g.\n *  (AES-256-GCM:CHACHA20-POLY1305) if o->ncp_ciphers_conf\n *  and o->ncp_ciphers differ, otherwise an empty string\n *\n *  The returned string will be allocated in the passed \\c gc\n */\nconst char *ncp_expanded_ciphers(struct options *o, struct gc_arena *gc);\n#endif /* ifndef OPENVPN_SSL_NCP_H */\n"
  },
  {
    "path": "src/openvpn/ssl_openssl.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel OpenSSL Backend\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if defined(ENABLE_CRYPTO_OPENSSL)\n\n#include \"errlevel.h\"\n#include \"buffer.h\"\n#include \"misc.h\"\n#include \"manage.h\"\n#include \"memdbg.h\"\n#include \"ssl_backend.h\"\n#include \"ssl_common.h\"\n#include \"base64.h\"\n#include \"openssl_compat.h\"\n#include \"xkey_common.h\"\n\n#ifdef ENABLE_CRYPTOAPI\n#include \"cryptoapi.h\"\n#endif\n\n#include \"ssl_verify_openssl.h\"\n#include \"ssl_util.h\"\n\n#include <openssl/bn.h>\n#include <openssl/crypto.h>\n#include <openssl/dh.h>\n#include <openssl/dsa.h>\n#include <openssl/err.h>\n#include <openssl/pkcs12.h>\n#include <openssl/rsa.h>\n#include <openssl/x509.h>\n#include <openssl/ssl.h>\n#ifndef OPENSSL_NO_EC\n#include <openssl/ec.h>\n#endif\n\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\n#define HAVE_OPENSSL_STORE_API\n#include <openssl/ui.h>\n#include <openssl/store.h>\n#endif\n\n#if defined(_MSC_VER) && !defined(_M_ARM64)\n#include <openssl/applink.c>\n#endif\n\nOSSL_LIB_CTX *tls_libctx; /* Global */\n\nstatic void unload_xkey_provider(void);\n\n/*\n * Allocate space in SSL objects in which to store a struct tls_session\n * pointer back to parent.\n *\n */\n\nint mydata_index; /* GLOBAL */\n\nvoid\ntls_init_lib(void)\n{\n    mydata_index = SSL_get_ex_new_index(0, \"struct session *\", NULL, NULL, NULL);\n    ASSERT(mydata_index >= 0);\n}\n\nvoid\ntls_free_lib(void)\n{\n}\n\nvoid\ntls_ctx_server_new(struct tls_root_ctx *ctx)\n{\n    ASSERT(NULL != ctx);\n\n    ctx->ctx = SSL_CTX_new_ex(tls_libctx, NULL, SSLv23_server_method());\n\n    if (ctx->ctx == NULL)\n    {\n        crypto_msg(M_FATAL, \"SSL_CTX_new SSLv23_server_method\");\n    }\n    if (ERR_peek_error() != 0)\n    {\n        crypto_msg(M_WARN, \"Warning: TLS server context initialisation \"\n                           \"has warnings.\");\n    }\n}\n\nvoid\ntls_ctx_client_new(struct tls_root_ctx *ctx)\n{\n    ASSERT(NULL != ctx);\n\n    ctx->ctx = SSL_CTX_new_ex(tls_libctx, NULL, SSLv23_client_method());\n\n    if (ctx->ctx == NULL)\n    {\n        crypto_msg(M_FATAL, \"SSL_CTX_new SSLv23_client_method\");\n    }\n    if (ERR_peek_error() != 0)\n    {\n        crypto_msg(M_WARN, \"Warning: TLS client context initialisation \"\n                           \"has warnings.\");\n    }\n}\n\nvoid\ntls_ctx_free(struct tls_root_ctx *ctx)\n{\n    ASSERT(NULL != ctx);\n    SSL_CTX_free(ctx->ctx);\n    ctx->ctx = NULL;\n    unload_xkey_provider(); /* in case it is loaded */\n}\n\nbool\ntls_ctx_initialised(struct tls_root_ctx *ctx)\n{\n    /* either this should be NULL or should be non-null and then have a\n     * valid TLS ctx inside as well */\n    ASSERT(ctx == NULL || ctx->ctx != NULL);\n    return ctx != NULL;\n}\n\nbool\nkey_state_export_keying_material(struct tls_session *session, const char *label, size_t label_size,\n                                 void *ekm, size_t ekm_size)\n\n{\n    SSL *ssl = session->key[KS_PRIMARY].ks_ssl.ssl;\n\n    if (SSL_export_keying_material(ssl, ekm, ekm_size, label, label_size, NULL, 0, 0) == 1)\n    {\n        return true;\n    }\n    else\n    {\n        secure_memzero(ekm, ekm_size);\n        return false;\n    }\n}\n\n/*\n * Print debugging information on SSL/TLS session negotiation.\n */\n\n#ifndef INFO_CALLBACK_SSL_CONST\n#define INFO_CALLBACK_SSL_CONST const\n#endif\nstatic void\ninfo_callback(INFO_CALLBACK_SSL_CONST SSL *s, int where, int ret)\n{\n    if (where & SSL_CB_LOOP)\n    {\n        dmsg(D_HANDSHAKE_VERBOSE, \"SSL state (%s): %s\",\n             where & SSL_ST_CONNECT  ? \"connect\"\n             : where & SSL_ST_ACCEPT ? \"accept\"\n                                     : \"undefined\",\n             SSL_state_string_long(s));\n    }\n    else if (where & SSL_CB_ALERT)\n    {\n        dmsg(D_TLS_DEBUG_LOW, \"%s %s SSL alert: %s\", where & SSL_CB_READ ? \"Received\" : \"Sent\",\n             SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));\n    }\n}\n\n/*\n * Return maximum TLS version supported by local OpenSSL library.\n * Assume that presence of SSL_OP_NO_TLSvX macro indicates that\n * TLSvX is supported.\n */\nint\ntls_version_max(void)\n{\n#if defined(TLS1_3_VERSION)\n    /* If this is defined we can safely assume TLS 1.3 support */\n    return TLS_VER_1_3;\n#elif OPENSSL_VERSION_NUMBER >= 0x10100000L\n    /*\n     * If TLS_VER_1_3 is not defined, we were compiled against a version that\n     * did not support TLS 1.3.\n     *\n     * However, the library we are *linked* against might be OpenSSL 1.1.1\n     * and therefore supports TLS 1.3. This needs to be checked at runtime\n     * since we can be compiled against 1.1.0 and then the library can be\n     * upgraded to 1.1.1.\n     * We only need to check this for OpenSSL versions that can be\n     * upgraded to 1.1.1 without recompile (>= 1.1.0)\n     */\n    if (OpenSSL_version_num() >= 0x1010100fL)\n    {\n        return TLS_VER_1_3;\n    }\n    else\n    {\n        return TLS_VER_1_2;\n    }\n#elif defined(TLS1_2_VERSION) || defined(SSL_OP_NO_TLSv1_2)\n    return TLS_VER_1_2;\n#elif defined(TLS1_1_VERSION) || defined(SSL_OP_NO_TLSv1_1)\n    return TLS_VER_1_1;\n#else /* if defined(TLS1_3_VERSION) */\n    return TLS_VER_1_0;\n#endif\n}\n\n/** Convert internal version number to openssl version number */\nstatic uint16_t\nopenssl_tls_version(unsigned int ver)\n{\n    if (ver == TLS_VER_1_0)\n    {\n        return TLS1_VERSION;\n    }\n    else if (ver == TLS_VER_1_1)\n    {\n        return TLS1_1_VERSION;\n    }\n    else if (ver == TLS_VER_1_2)\n    {\n        return TLS1_2_VERSION;\n    }\n    else if (ver == TLS_VER_1_3)\n    {\n        /*\n         * Supporting the library upgraded to TLS1.3 without recompile\n         * is enough to support here with a simple constant that the same\n         * as in the TLS 1.3, so spec it is very unlikely that OpenSSL\n         * will change this constant\n         */\n#ifndef TLS1_3_VERSION\n        /*\n         * We do not want to define TLS_VER_1_3 if not defined\n         * since other parts of the code use the existance of this macro\n         * as proxy for TLS 1.3 support\n         */\n        return 0x0304;\n#else\n        return TLS1_3_VERSION;\n#endif\n    }\n    return 0;\n}\n\nstatic bool\ntls_ctx_set_tls_versions(struct tls_root_ctx *ctx, unsigned int ssl_flags)\n{\n    uint16_t tls_ver_min =\n        openssl_tls_version((ssl_flags >> SSLF_TLS_VERSION_MIN_SHIFT) & SSLF_TLS_VERSION_MIN_MASK);\n    uint16_t tls_ver_max =\n        openssl_tls_version((ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT) & SSLF_TLS_VERSION_MAX_MASK);\n\n    if (!tls_ver_min)\n    {\n        /* Enforce at least TLS 1.0 */\n        uint16_t cur_min = (uint16_t)SSL_CTX_get_min_proto_version(ctx->ctx);\n        tls_ver_min = cur_min < TLS1_VERSION ? TLS1_VERSION : cur_min;\n    }\n\n    if (!SSL_CTX_set_min_proto_version(ctx->ctx, tls_ver_min))\n    {\n        msg(D_TLS_ERRORS, \"%s: failed to set minimum TLS version\", __func__);\n        return false;\n    }\n\n    if (tls_ver_max && !SSL_CTX_set_max_proto_version(ctx->ctx, tls_ver_max))\n    {\n        msg(D_TLS_ERRORS, \"%s: failed to set maximum TLS version\", __func__);\n        return false;\n    }\n\n    return true;\n}\n\nbool\ntls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)\n{\n    ASSERT(NULL != ctx);\n\n    /* process SSL options */\n    uint64_t sslopt = SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET;\n#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE\n    sslopt |= SSL_OP_CIPHER_SERVER_PREFERENCE;\n#endif\n    sslopt |= SSL_OP_NO_COMPRESSION;\n    /* Disable TLS renegotiations. OpenVPN's renegotiation creates new SSL\n     * session and does not depend on this feature. And TLS renegotiations have\n     * been problematic in the past */\n#ifdef SSL_OP_NO_RENEGOTIATION\n    sslopt |= SSL_OP_NO_RENEGOTIATION;\n#endif\n\n    SSL_CTX_set_options(ctx->ctx, sslopt);\n\n    if (!tls_ctx_set_tls_versions(ctx, ssl_flags))\n    {\n        return false;\n    }\n\n#ifdef SSL_MODE_RELEASE_BUFFERS\n    SSL_CTX_set_mode(ctx->ctx, SSL_MODE_RELEASE_BUFFERS);\n#endif\n    SSL_CTX_set_session_cache_mode(ctx->ctx, SSL_SESS_CACHE_OFF);\n    SSL_CTX_set_default_passwd_cb(ctx->ctx, pem_password_callback);\n\n    /* Require peer certificate verification */\n    int verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;\n    if (ssl_flags & SSLF_CLIENT_CERT_NOT_REQUIRED)\n    {\n        verify_flags = 0;\n    }\n    else if (ssl_flags & SSLF_CLIENT_CERT_OPTIONAL)\n    {\n        verify_flags = SSL_VERIFY_PEER;\n    }\n    SSL_CTX_set_verify(ctx->ctx, verify_flags, verify_callback);\n\n    SSL_CTX_set_info_callback(ctx->ctx, info_callback);\n\n    return true;\n}\n\nstatic void\nconvert_tls_list_to_openssl(char *openssl_ciphers, size_t len, const char *ciphers)\n{\n    /* Parse supplied cipher list and pass on to OpenSSL */\n    size_t begin_of_cipher, end_of_cipher;\n\n    const char *current_cipher;\n    size_t current_cipher_len;\n\n    const tls_cipher_name_pair *cipher_pair;\n\n    size_t openssl_ciphers_len = 0;\n    openssl_ciphers[0] = '\\0';\n\n    /* Translate IANA cipher suite names to OpenSSL names */\n    begin_of_cipher = end_of_cipher = 0;\n    for (; begin_of_cipher < strlen(ciphers); begin_of_cipher = end_of_cipher)\n    {\n        end_of_cipher += strcspn(&ciphers[begin_of_cipher], \":\");\n        cipher_pair =\n            tls_get_cipher_name_pair(&ciphers[begin_of_cipher], end_of_cipher - begin_of_cipher);\n\n        if (NULL == cipher_pair)\n        {\n            /* No translation found, use original */\n            current_cipher = &ciphers[begin_of_cipher];\n            current_cipher_len = end_of_cipher - begin_of_cipher;\n\n            /* Issue warning on missing translation */\n            /* %.*s format specifier expects length of type int, so guarantee */\n            /* that length is small enough and cast to int. */\n            msg(D_LOW, \"No valid translation found for TLS cipher '%.*s'\",\n                constrain_int((int)current_cipher_len, 0, 256), current_cipher);\n        }\n        else\n        {\n            /* Use OpenSSL name */\n            current_cipher = cipher_pair->openssl_name;\n            current_cipher_len = strlen(current_cipher);\n\n            if (end_of_cipher - begin_of_cipher == current_cipher_len\n                && 0\n                       != memcmp(&ciphers[begin_of_cipher], cipher_pair->iana_name,\n                                 end_of_cipher - begin_of_cipher))\n            {\n                /* Non-IANA name used, show warning */\n                msg(M_WARN, \"Deprecated TLS cipher name '%s', please use IANA name '%s'\",\n                    cipher_pair->openssl_name, cipher_pair->iana_name);\n            }\n        }\n\n        /* Make sure new cipher name fits in cipher string */\n        if ((SIZE_MAX - openssl_ciphers_len) < current_cipher_len\n            || (len - 1) < (openssl_ciphers_len + current_cipher_len))\n        {\n            msg(M_FATAL, \"Failed to set restricted TLS cipher list, too long (>%d).\",\n                (int)(len - 1));\n        }\n\n        /* Concatenate cipher name to OpenSSL cipher string */\n        memcpy(&openssl_ciphers[openssl_ciphers_len], current_cipher, current_cipher_len);\n        openssl_ciphers_len += current_cipher_len;\n        openssl_ciphers[openssl_ciphers_len] = ':';\n        openssl_ciphers_len++;\n\n        end_of_cipher++;\n    }\n\n    if (openssl_ciphers_len > 0)\n    {\n        openssl_ciphers[openssl_ciphers_len - 1] = '\\0';\n    }\n}\n\nvoid\ntls_ctx_restrict_ciphers(struct tls_root_ctx *ctx, const char *ciphers)\n{\n    if (ciphers == NULL)\n    {\n        /* Use sane default TLS cipher list */\n        if (!SSL_CTX_set_cipher_list(\n                ctx->ctx,\n                /* Use openssl's default list as a basis */\n                \"DEFAULT\"\n                /* Disable export ciphers and openssl's 'low' and 'medium' ciphers */\n                \":!EXP:!LOW:!MEDIUM\"\n                /* Disable static (EC)DH keys (no forward secrecy) */\n                \":!kDH:!kECDH\"\n                /* Disable DSA private keys */\n                \":!DSS\"\n                /* Disable unsupported TLS modes */\n                \":!PSK:!SRP:!kRSA\"))\n        {\n            crypto_msg(M_FATAL, \"Failed to set default TLS cipher list.\");\n        }\n        return;\n    }\n\n    char openssl_ciphers[4096];\n    convert_tls_list_to_openssl(openssl_ciphers, sizeof(openssl_ciphers), ciphers);\n\n    ASSERT(NULL != ctx);\n\n    /* Set OpenSSL cipher list */\n    if (!SSL_CTX_set_cipher_list(ctx->ctx, openssl_ciphers))\n    {\n        crypto_msg(M_FATAL, \"Failed to set restricted TLS cipher list: %s\", openssl_ciphers);\n    }\n}\n\nstatic void\nconvert_tls13_list_to_openssl(char *openssl_ciphers, size_t len, const char *ciphers)\n{\n    /*\n     * OpenSSL (and official IANA) cipher names have _ in them. We\n     * historically used names with - in them. Silently convert names\n     * with - to names with _ to support both\n     */\n    if (strlen(ciphers) >= (len - 1))\n    {\n        msg(M_FATAL, \"Failed to set restricted TLS 1.3 cipher list, too long (>%d).\",\n            (int)(len - 1));\n    }\n\n    strncpy(openssl_ciphers, ciphers, len);\n\n    for (size_t i = 0; i < strlen(openssl_ciphers); i++)\n    {\n        if (openssl_ciphers[i] == '-')\n        {\n            openssl_ciphers[i] = '_';\n        }\n    }\n}\n\nvoid\ntls_ctx_restrict_ciphers_tls13(struct tls_root_ctx *ctx, const char *ciphers)\n{\n    if (ciphers == NULL)\n    {\n        /* default cipher list of OpenSSL 1.1.1 is sane, do not set own\n         * default as we do with tls-cipher */\n        return;\n    }\n\n#if !defined(TLS1_3_VERSION)\n    crypto_msg(M_WARN,\n               \"Not compiled with OpenSSL 1.1.1 or higher. \"\n               \"Ignoring TLS 1.3 only tls-ciphersuites '%s' setting.\",\n               ciphers);\n#else\n    ASSERT(NULL != ctx);\n\n    char openssl_ciphers[4096];\n    convert_tls13_list_to_openssl(openssl_ciphers, sizeof(openssl_ciphers), ciphers);\n\n    if (!SSL_CTX_set_ciphersuites(ctx->ctx, openssl_ciphers))\n    {\n        crypto_msg(M_FATAL, \"Failed to set restricted TLS 1.3 cipher list: %s\", openssl_ciphers);\n    }\n#endif\n}\n\nvoid\ntls_ctx_set_cert_profile(struct tls_root_ctx *ctx, const char *profile)\n{\n#if OPENSSL_VERSION_NUMBER > 0x10100000L \\\n    && (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER > 0x3060000fL)\n    /* OpenSSL does not have certificate profiles, but a complex set of\n     * callbacks that we could try to implement to achieve something similar.\n     * For now, use OpenSSL's security levels to achieve similar (but not equal)\n     * behaviour. */\n    if (!profile || 0 == strcmp(profile, \"legacy\"))\n    {\n        SSL_CTX_set_security_level(ctx->ctx, 1);\n    }\n    else if (0 == strcmp(profile, \"insecure\"))\n    {\n        SSL_CTX_set_security_level(ctx->ctx, 0);\n    }\n    else if (0 == strcmp(profile, \"preferred\"))\n    {\n        SSL_CTX_set_security_level(ctx->ctx, 2);\n    }\n    else if (0 == strcmp(profile, \"suiteb\"))\n    {\n        SSL_CTX_set_security_level(ctx->ctx, 3);\n        SSL_CTX_set_cipher_list(ctx->ctx, \"SUITEB128\");\n    }\n    else\n    {\n        msg(M_FATAL, \"ERROR: Invalid cert profile: %s\", profile);\n    }\n#else  /* if OPENSSL_VERSION_NUMBER > 0x10100000L */\n    if (profile)\n    {\n        msg(M_WARN,\n            \"WARNING: OpenSSL 1.1.0 and LibreSSL do not support \"\n            \"--tls-cert-profile, ignoring user-set profile: '%s'\",\n            profile);\n    }\n#endif /* if OPENSSL_VERSION_NUMBER > 0x10100000L */\n}\n\nvoid\ntls_ctx_set_tls_groups(struct tls_root_ctx *ctx, const char *groups)\n{\n    ASSERT(ctx);\n#if OPENSSL_VERSION_NUMBER < 0x30000000L && !defined(ENABLE_CRYPTO_WOLFSSL)\n    struct gc_arena gc = gc_new();\n    /* This method could be as easy as\n     *  SSL_CTX_set1_groups_list(ctx->ctx, groups)\n     * but OpenSSL (< 3.0) does not like the name secp256r1 for prime256v1\n     * This is one of the important curves.\n     * To support the same name for OpenSSL and mbedTLS, we do\n     * this dance.\n     * Also note that the code is wrong in the presence of OpenSSL3 providers.\n     */\n\n    int groups_count = get_num_elements(groups, ':');\n\n    int *glist;\n    /* Allocate an array for them */\n    ALLOC_ARRAY_CLEAR_GC(glist, int, groups_count, &gc);\n\n    /* Parse allowed ciphers, getting IDs */\n    int glistlen = 0;\n    char *tmp_groups = string_alloc(groups, &gc);\n\n    const char *token;\n    while ((token = strsep(&tmp_groups, \":\")))\n    {\n        if (streq(token, \"secp256r1\"))\n        {\n            token = \"prime256v1\";\n        }\n        int nid = OBJ_sn2nid(token);\n\n        if (nid == 0)\n        {\n            msg(M_WARN, \"Warning unknown curve/group specified: %s\", token);\n        }\n        else\n        {\n            glist[glistlen] = nid;\n            glistlen++;\n        }\n    }\n\n    if (!SSL_CTX_set1_groups(ctx->ctx, glist, glistlen))\n    {\n        crypto_msg(M_FATAL, \"Failed to set allowed TLS group list: %s\", groups);\n    }\n    gc_free(&gc);\n#else  /* if OPENSSL_VERSION_NUMBER < 0x30000000L */\n    if (!SSL_CTX_set1_groups_list(ctx->ctx, groups))\n    {\n        crypto_msg(M_FATAL, \"Failed to set allowed TLS group list: %s\", groups);\n    }\n#endif /* if OPENSSL_VERSION_NUMBER < 0x30000000L */\n}\n\nvoid\ntls_ctx_check_cert_time(const struct tls_root_ctx *ctx)\n{\n    int ret;\n    const X509 *cert;\n\n    ASSERT(ctx);\n\n    cert = SSL_CTX_get0_certificate(ctx->ctx);\n\n    if (cert == NULL)\n    {\n        return; /* Nothing to check if there is no certificate */\n    }\n\n    ret = X509_cmp_time(X509_get0_notBefore(cert), NULL);\n    if (ret == 0)\n    {\n        msg(D_TLS_DEBUG_MED, \"Failed to read certificate notBefore field.\");\n    }\n    if (ret > 0)\n    {\n        msg(M_WARN, \"WARNING: Your certificate is not yet valid!\");\n    }\n\n    ret = X509_cmp_time(X509_get0_notAfter(cert), NULL);\n    if (ret == 0)\n    {\n        msg(D_TLS_DEBUG_MED, \"Failed to read certificate notAfter field.\");\n    }\n    if (ret < 0)\n    {\n        msg(M_WARN, \"WARNING: Your certificate has expired!\");\n    }\n}\n\nvoid\ntls_ctx_load_dh_params(struct tls_root_ctx *ctx, const char *dh_file, bool dh_file_inline)\n{\n    BIO *bio;\n\n    ASSERT(NULL != ctx);\n\n    if (dh_file_inline)\n    {\n        if (!(bio = BIO_new_mem_buf((char *)dh_file, -1)))\n        {\n            crypto_msg(M_FATAL, \"Cannot open memory BIO for inline DH parameters\");\n        }\n    }\n    else\n    {\n        /* Get Diffie Hellman Parameters */\n        if (!(bio = BIO_new_file(dh_file, \"r\")))\n        {\n            crypto_msg(M_FATAL, \"Cannot open %s for DH parameters\", dh_file);\n        }\n    }\n\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\n    EVP_PKEY *dh = PEM_read_bio_Parameters(bio, NULL);\n    BIO_free(bio);\n\n    if (!dh)\n    {\n        crypto_msg(M_FATAL, \"Cannot load DH parameters from %s\",\n                   print_key_filename(dh_file, dh_file_inline));\n    }\n    if (!SSL_CTX_set0_tmp_dh_pkey(ctx->ctx, dh))\n    {\n        crypto_msg(M_FATAL, \"SSL_CTX_set0_tmp_dh_pkey\");\n    }\n\n    msg(D_TLS_DEBUG_LOW, \"Diffie-Hellman initialized with %d bit key\", 8 * EVP_PKEY_get_size(dh));\n#else  /* if OPENSSL_VERSION_NUMBER >= 0x30000000L */\n    DH *dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);\n    BIO_free(bio);\n\n    if (!dh)\n    {\n        crypto_msg(M_FATAL, \"Cannot load DH parameters from %s\",\n                   print_key_filename(dh_file, dh_file_inline));\n    }\n    if (!SSL_CTX_set_tmp_dh(ctx->ctx, dh))\n    {\n        crypto_msg(M_FATAL, \"SSL_CTX_set_tmp_dh\");\n    }\n\n    msg(D_TLS_DEBUG_LOW, \"Diffie-Hellman initialized with %d bit key\", 8 * DH_size(dh));\n\n    DH_free(dh);\n#endif /* if OPENSSL_VERSION_NUMBER >= 0x30000000L */\n}\n\nvoid\ntls_ctx_load_ecdh_params(struct tls_root_ctx *ctx, const char *curve_name)\n{\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\n    if (curve_name != NULL)\n    {\n        msg(M_WARN, \"WARNING: OpenSSL 3.0+ builds do not support specifying an \"\n                    \"ECDH curve with --ecdh-curve, using default curves. Use \"\n                    \"--tls-groups to specify groups.\");\n    }\n#elif !defined(OPENSSL_NO_EC)\n    int nid = NID_undef;\n    EC_KEY *ecdh = NULL;\n    const char *sname = NULL;\n\n    /* Generate a new ECDH key for each SSL session (for non-ephemeral ECDH) */\n    SSL_CTX_set_options(ctx->ctx, SSL_OP_SINGLE_ECDH_USE);\n\n    if (curve_name != NULL)\n    {\n        /* Use user supplied curve if given */\n        msg(D_TLS_DEBUG, \"Using user specified ECDH curve (%s)\", curve_name);\n        nid = OBJ_sn2nid(curve_name);\n    }\n    else\n    {\n        return;\n    }\n\n    /* Translate NID back to name , just for kicks */\n    sname = OBJ_nid2sn(nid);\n    if (sname == NULL)\n    {\n        sname = \"(Unknown)\";\n    }\n\n    /* Create new EC key and set as ECDH key */\n    if (NID_undef == nid || NULL == (ecdh = EC_KEY_new_by_curve_name(nid)))\n    {\n        /* Creating key failed, fall back on sane default */\n        ecdh = EC_KEY_new_by_curve_name(NID_secp384r1);\n        const char *source =\n            (NULL == curve_name) ? \"extract curve from certificate\" : \"use supplied curve\";\n        msg(D_TLS_DEBUG_LOW, \"Failed to %s (%s), using secp384r1 instead.\", source, sname);\n        sname = OBJ_nid2sn(NID_secp384r1);\n    }\n\n    if (!SSL_CTX_set_tmp_ecdh(ctx->ctx, ecdh))\n    {\n        crypto_msg(M_FATAL, \"SSL_CTX_set_tmp_ecdh: cannot add curve\");\n    }\n\n    msg(D_TLS_DEBUG_LOW, \"ECDH curve %s added\", sname);\n\n    EC_KEY_free(ecdh);\n#else  /* ifndef OPENSSL_NO_EC */\n    msg(D_LOW, \"Your OpenSSL library was built without elliptic curve support.\"\n               \" Skipping ECDH parameter loading.\");\n#endif /* OPENSSL_NO_EC */\n}\n\n#if defined(HAVE_OPENSSL_STORE_API)\n/**\n * A wrapper for password callback for use with OpenSSL UI_METHOD.\n * The callback is obtained using SSL_CTX_get_default_passwd_cb()\n * which is set to pem_password_callback() in tls_ctx_set_options().\n */\nstatic int\nui_reader(UI *ui, UI_STRING *uis)\n{\n    SSL_CTX *ctx = UI_get0_user_data(ui);\n\n    if (UI_get_string_type(uis) == UIT_PROMPT)\n    {\n        const char *prompt = UI_get0_output_string(uis);\n\n        /* If pkcs#11 Use custom prompt similar to pkcs11-helper */\n        if (strstr(prompt, \"PKCS#11\"))\n        {\n            struct user_pass up;\n            CLEAR(up);\n            get_user_pass(&up, NULL, \"PKCS#11 token\",\n                          GET_USER_PASS_MANAGEMENT | GET_USER_PASS_PASSWORD_ONLY);\n            UI_set_result(ui, uis, up.password);\n            purge_user_pass(&up, true);\n        }\n        else /* use our generic 'Private Key' passphrase callback */\n        {\n            char password[USER_PASS_LEN];\n            pem_password_cb *cb = SSL_CTX_get_default_passwd_cb(ctx);\n            void *d = SSL_CTX_get_default_passwd_cb_userdata(ctx);\n\n            cb(password, sizeof(password), 0, d);\n            UI_set_result(ui, uis, password);\n            secure_memzero(password, sizeof(password));\n        }\n\n        return 1;\n    }\n    return 0;\n}\n\nstatic void\nclear_ossl_store_error(OSSL_STORE_CTX *store_ctx)\n{\n    if (OSSL_STORE_error(store_ctx))\n    {\n        ERR_clear_error();\n    }\n}\n#endif /* defined(HAVE_OPENSSL_STORE_API) */\n\n/**\n * Load private key from OSSL_STORE URI or file\n * uri : URI of object or filename\n * ssl_ctx : SSL_CTX for UI prompt\n *\n * Return a pointer to the key or NULL if not found.\n * Caller must free the key after use.\n */\nstatic void *\nload_pkey_from_uri(const char *uri, SSL_CTX *ssl_ctx)\n{\n    EVP_PKEY *pkey = NULL;\n\n#if !defined(HAVE_OPENSSL_STORE_API)\n\n    /* Treat the uri as file name */\n    BIO *in = BIO_new_file(uri, \"r\");\n    if (!in)\n    {\n        return NULL;\n    }\n    pkey = PEM_read_bio_PrivateKey(in, NULL, SSL_CTX_get_default_passwd_cb(ssl_ctx),\n                                   SSL_CTX_get_default_passwd_cb_userdata(ssl_ctx));\n    BIO_free(in);\n\n#else  /* defined(HAVE_OPENSSL_STORE_API) */\n\n    OSSL_STORE_CTX *store_ctx = NULL;\n    OSSL_STORE_INFO *info = NULL;\n\n    UI_METHOD *ui_method = UI_create_method(\"openvpn\");\n    if (!ui_method)\n    {\n        msg(M_WARN, \"OpenSSL UI creation failed\");\n        return NULL;\n    }\n    UI_method_set_reader(ui_method, ui_reader);\n\n    store_ctx = OSSL_STORE_open_ex(uri, tls_libctx, NULL, ui_method, ssl_ctx, NULL, NULL, NULL);\n    if (!store_ctx)\n    {\n        goto end;\n    }\n    if (OSSL_STORE_expect(store_ctx, OSSL_STORE_INFO_PKEY) != 1)\n    {\n        goto end;\n    }\n    while (1)\n    {\n        info = OSSL_STORE_load(store_ctx);\n        if (info || OSSL_STORE_eof(store_ctx))\n        {\n            break;\n        }\n        /* OPENSSL_STORE_load can return error and still have usable objects to follow.\n         * ref: man OPENSSL_STORE_open\n         * Clear error and recurse through the file if info = NULL and eof not reached\n         */\n        clear_ossl_store_error(store_ctx);\n    }\n    if (!info)\n    {\n        goto end;\n    }\n    pkey = OSSL_STORE_INFO_get1_PKEY(info);\n    OSSL_STORE_INFO_free(info);\n    msg(D_TLS_DEBUG_MED, \"Found pkey in store using URI: %s\", uri);\n\nend:\n    OSSL_STORE_close(store_ctx);\n    UI_destroy_method(ui_method);\n\n#endif /* defined(HAVE_OPENSSL_STORE_API) */\n\n    return pkey;\n}\n\nint\ntls_ctx_load_pkcs12(struct tls_root_ctx *ctx, const char *pkcs12_file, bool pkcs12_file_inline,\n                    bool load_ca_file)\n{\n    FILE *fp;\n    EVP_PKEY *pkey;\n    X509 *cert;\n    STACK_OF(X509) *ca = NULL;\n    PKCS12 *p12;\n    int i;\n    char password[256];\n\n    ASSERT(NULL != ctx);\n\n    if (pkcs12_file_inline)\n    {\n        BIO *b64 = BIO_new(BIO_f_base64());\n        BIO *bio = BIO_new_mem_buf((void *)pkcs12_file, (int)strlen(pkcs12_file));\n        ASSERT(b64 && bio);\n        BIO_push(b64, bio);\n        p12 = d2i_PKCS12_bio(b64, NULL);\n        if (!p12)\n        {\n            crypto_msg(M_FATAL, \"Error reading inline PKCS#12 file\");\n        }\n        BIO_free(b64);\n        BIO_free(bio);\n    }\n    else\n    {\n        /* Load the PKCS #12 file */\n        if (!(fp = platform_fopen(pkcs12_file, \"rb\")))\n        {\n            crypto_msg(M_FATAL, \"Error opening file %s\", pkcs12_file);\n        }\n        p12 = d2i_PKCS12_fp(fp, NULL);\n        fclose(fp);\n        if (!p12)\n        {\n            crypto_msg(M_FATAL, \"Error reading PKCS#12 file %s\", pkcs12_file);\n        }\n    }\n\n    /* Parse the PKCS #12 file */\n    if (!PKCS12_parse(p12, \"\", &pkey, &cert, &ca))\n    {\n        pem_password_callback(password, sizeof(password) - 1, 0, NULL);\n        /* Reparse the PKCS #12 file with password */\n        ca = NULL;\n        if (!PKCS12_parse(p12, password, &pkey, &cert, &ca))\n        {\n            crypto_msg(M_WARN, \"Decoding PKCS12 failed. Probably wrong password \"\n                               \"or unsupported/legacy encryption\");\n#ifdef ENABLE_MANAGEMENT\n            if (management && (ERR_GET_REASON(ERR_peek_error()) == PKCS12_R_MAC_VERIFY_FAILURE))\n            {\n                management_auth_failure(management, UP_TYPE_PRIVATE_KEY, NULL);\n            }\n#endif\n            PKCS12_free(p12);\n            return 1;\n        }\n    }\n    PKCS12_free(p12);\n\n    /* Load Certificate */\n    if (!SSL_CTX_use_certificate(ctx->ctx, cert))\n    {\n        crypto_print_openssl_errors(M_WARN);\n        crypto_msg(M_FATAL, \"Cannot use certificate\");\n    }\n\n    /* Load Private Key */\n    if (!SSL_CTX_use_PrivateKey(ctx->ctx, pkey))\n    {\n        crypto_msg(M_FATAL, \"Cannot use private key\");\n    }\n\n    /* Check Private Key */\n    if (!SSL_CTX_check_private_key(ctx->ctx))\n    {\n        crypto_msg(M_FATAL, \"Private key does not match the certificate\");\n    }\n\n    /* Set Certificate Verification chain */\n    if (load_ca_file)\n    {\n        /* Add CAs from PKCS12 to the cert store and mark them as trusted.\n         * They're also used to fill in the chain of intermediate certs as\n         * necessary.\n         */\n        if (ca && sk_X509_num(ca))\n        {\n            for (i = 0; i < sk_X509_num(ca); i++)\n            {\n                X509_STORE *cert_store = SSL_CTX_get_cert_store(ctx->ctx);\n                if (!X509_STORE_add_cert(cert_store, sk_X509_value(ca, i)))\n                {\n                    crypto_msg(M_FATAL,\n                               \"Cannot add certificate to certificate chain (X509_STORE_add_cert)\");\n                }\n                if (!SSL_CTX_add_client_CA(ctx->ctx, sk_X509_value(ca, i)))\n                {\n                    crypto_msg(M_FATAL,\n                               \"Cannot add certificate to client CA list (SSL_CTX_add_client_CA)\");\n                }\n            }\n        }\n    }\n    else\n    {\n        /* If trusted CA certs were loaded from a PEM file, and we ignore the\n         * ones in PKCS12, do load PKCS12-provided certs to the client extra\n         * certs chain just in case they include intermediate CAs needed to\n         * prove my identity to the other end. This does not make them trusted.\n         */\n        if (ca && sk_X509_num(ca))\n        {\n            for (i = 0; i < sk_X509_num(ca); i++)\n            {\n                if (!SSL_CTX_add_extra_chain_cert(ctx->ctx, sk_X509_value(ca, i)))\n                {\n                    crypto_msg(\n                        M_FATAL,\n                        \"Cannot add extra certificate to chain (SSL_CTX_add_extra_chain_cert)\");\n                }\n            }\n        }\n    }\n    return 0;\n}\n\n#ifdef ENABLE_CRYPTOAPI\nvoid\ntls_ctx_load_cryptoapi(struct tls_root_ctx *ctx, const char *cryptoapi_cert)\n{\n    ASSERT(NULL != ctx);\n\n    /* Load Certificate and Private Key */\n    if (!SSL_CTX_use_CryptoAPI_certificate(ctx->ctx, cryptoapi_cert))\n    {\n        crypto_msg(M_FATAL, \"Cannot load certificate \\\"%s\\\" from Microsoft Certificate Store\",\n                   cryptoapi_cert);\n    }\n}\n#endif /* ENABLE_CRYPTOAPI */\n\nstatic void\ntls_ctx_add_extra_certs(struct tls_root_ctx *ctx, BIO *bio, bool optional)\n{\n    X509 *cert;\n    while (true)\n    {\n        cert = NULL;\n        if (!PEM_read_bio_X509(bio, &cert, NULL, NULL))\n        {\n            /*  a PEM_R_NO_START_LINE \"Error\" indicates that no certificate\n             *  is found in the buffer.  If loading more certificates is\n             *  optional, break without raising an error\n             */\n            if (optional && ERR_GET_REASON(ERR_peek_error()) == PEM_R_NO_START_LINE)\n            {\n                /* remove that error from error stack */\n                (void)ERR_get_error();\n                break;\n            }\n\n            /* Otherwise, bail out with error */\n            crypto_msg(M_FATAL, \"Error reading extra certificate\");\n        }\n        /* takes ownership of cert like a set1 method */\n        if (SSL_CTX_add_extra_chain_cert(ctx->ctx, cert) != 1)\n        {\n            crypto_msg(M_FATAL, \"Error adding extra certificate\");\n        }\n        /* We loaded at least one certificate, so loading more is optional */\n        optional = true;\n    }\n}\n\nstatic bool\ncert_uri_supported(void)\n{\n#if defined(HAVE_OPENSSL_STORE_API)\n    return 1;\n#else\n    return 0;\n#endif\n}\n\nstatic void\ntls_ctx_load_cert_uri(struct tls_root_ctx *tls_ctx, const char *uri)\n{\n#if defined(HAVE_OPENSSL_STORE_API)\n    X509 *x = NULL;\n    int ret = 0;\n    OSSL_STORE_CTX *store_ctx = NULL;\n    OSSL_STORE_INFO *info = NULL;\n\n    ASSERT(NULL != tls_ctx);\n\n    UI_METHOD *ui_method = UI_create_method(\"openvpn\");\n    if (!ui_method)\n    {\n        msg(M_WARN, \"OpenSSL UI method creation failed\");\n        goto end;\n    }\n    UI_method_set_reader(ui_method, ui_reader);\n\n    store_ctx =\n        OSSL_STORE_open_ex(uri, tls_libctx, NULL, ui_method, tls_ctx->ctx, NULL, NULL, NULL);\n    if (!store_ctx)\n    {\n        goto end;\n    }\n    if (OSSL_STORE_expect(store_ctx, OSSL_STORE_INFO_CERT) != 1)\n    {\n        goto end;\n    }\n\n    while (1)\n    {\n        info = OSSL_STORE_load(store_ctx);\n        if (info || OSSL_STORE_eof(store_ctx))\n        {\n            break;\n        }\n        /* OPENSSL_STORE_load can return error and still have usable objects to follow.\n         * ref: man OPENSSL_STORE_open\n         * Clear error and recurse through the file if info = NULL and eof not reached.\n         */\n        clear_ossl_store_error(store_ctx);\n    }\n    if (!info)\n    {\n        goto end;\n    }\n\n    x = OSSL_STORE_INFO_get0_CERT(info);\n    if (x == NULL)\n    {\n        goto end;\n    }\n    msg(D_TLS_DEBUG_MED, \"Found cert in store using URI: %s\", uri);\n\n    ret = SSL_CTX_use_certificate(tls_ctx->ctx, x);\n    if (!ret)\n    {\n        goto end;\n    }\n    OSSL_STORE_INFO_free(info);\n    info = NULL;\n\n    /* iterate through the store and add extra certificates if any to the chain */\n    while (!OSSL_STORE_eof(store_ctx))\n    {\n        info = OSSL_STORE_load(store_ctx);\n        if (!info)\n        {\n            clear_ossl_store_error(store_ctx);\n            continue;\n        }\n        x = OSSL_STORE_INFO_get1_CERT(info);\n        if (x && SSL_CTX_add_extra_chain_cert(tls_ctx->ctx, x) != 1)\n        {\n            X509_free(x);\n            crypto_msg(M_FATAL, \"Error adding extra certificate\");\n            break;\n        }\n        OSSL_STORE_INFO_free(info);\n        info = NULL;\n    }\n\nend:\n    if (!ret)\n    {\n        crypto_print_openssl_errors(M_WARN);\n        crypto_msg(M_FATAL, \"Cannot load certificate from URI <%s>\", uri);\n    }\n    else\n    {\n        crypto_print_openssl_errors(M_DEBUG);\n    }\n\n    UI_destroy_method(ui_method);\n    OSSL_STORE_INFO_free(info);\n    OSSL_STORE_close(store_ctx);\n#else  /* defined(HAVE_OPENSSL_STORE_API */\n    ASSERT(0);\n#endif /* defined(HAVE_OPENSSL_STORE_API */\n}\n\nstatic void\ntls_ctx_load_cert_pem_file(struct tls_root_ctx *ctx, const char *cert_file, bool cert_file_inline)\n{\n    BIO *in = NULL;\n    X509 *x = NULL;\n    int ret = 0;\n\n    ASSERT(NULL != ctx);\n\n    if (cert_file_inline)\n    {\n        in = BIO_new_mem_buf((char *)cert_file, -1);\n    }\n    else\n    {\n        in = BIO_new_file((char *)cert_file, \"r\");\n    }\n\n    if (in == NULL)\n    {\n        SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_SYS_LIB);\n        goto end;\n    }\n\n    x = PEM_read_bio_X509(in, NULL, SSL_CTX_get_default_passwd_cb(ctx->ctx),\n                          SSL_CTX_get_default_passwd_cb_userdata(ctx->ctx));\n    if (x == NULL)\n    {\n        SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_PEM_LIB);\n        goto end;\n    }\n\n    ret = SSL_CTX_use_certificate(ctx->ctx, x);\n    if (ret)\n    {\n        tls_ctx_add_extra_certs(ctx, in, true);\n    }\n\nend:\n    if (!ret)\n    {\n        crypto_print_openssl_errors(M_WARN);\n        if (cert_file_inline)\n        {\n            crypto_msg(M_FATAL, \"Cannot load inline certificate file\");\n        }\n        else\n        {\n            crypto_msg(M_FATAL, \"Cannot load certificate file %s\", cert_file);\n        }\n    }\n    else\n    {\n        crypto_print_openssl_errors(M_DEBUG);\n    }\n\n    BIO_free(in);\n    X509_free(x);\n}\n\nvoid\ntls_ctx_load_cert_file(struct tls_root_ctx *ctx, const char *cert_file, bool cert_file_inline)\n{\n    if (cert_uri_supported() && !cert_file_inline)\n    {\n        tls_ctx_load_cert_uri(ctx, cert_file);\n    }\n    else\n    {\n        tls_ctx_load_cert_pem_file(ctx, cert_file, cert_file_inline);\n    }\n}\n\nint\ntls_ctx_load_priv_file(struct tls_root_ctx *ctx, const char *priv_key_file,\n                       bool priv_key_file_inline)\n{\n    SSL_CTX *ssl_ctx = NULL;\n    BIO *in = NULL;\n    EVP_PKEY *pkey = NULL;\n    int ret = 1;\n\n    ASSERT(NULL != ctx);\n\n    ssl_ctx = ctx->ctx;\n\n    if (priv_key_file_inline)\n    {\n        in = BIO_new_mem_buf((char *)priv_key_file, -1);\n        if (in == NULL)\n        {\n            goto end;\n        }\n        pkey = PEM_read_bio_PrivateKey(in, NULL, SSL_CTX_get_default_passwd_cb(ctx->ctx),\n                                       SSL_CTX_get_default_passwd_cb_userdata(ctx->ctx));\n    }\n    else\n    {\n        pkey = load_pkey_from_uri(priv_key_file, ssl_ctx);\n    }\n\n    if (!pkey || !SSL_CTX_use_PrivateKey(ssl_ctx, pkey))\n    {\n#ifdef ENABLE_MANAGEMENT\n        if (management && (ERR_GET_REASON(ERR_peek_error()) == EVP_R_BAD_DECRYPT))\n        {\n            management_auth_failure(management, UP_TYPE_PRIVATE_KEY, NULL);\n        }\n#endif\n        crypto_msg(M_WARN, \"Cannot load private key file %s\",\n                   print_key_filename(priv_key_file, priv_key_file_inline));\n        goto end;\n    }\n\n    /* Check Private Key */\n    if (!SSL_CTX_check_private_key(ssl_ctx))\n    {\n        crypto_msg(M_FATAL, \"Private key does not match the certificate\");\n    }\n    ret = 0;\n\nend:\n    EVP_PKEY_free(pkey);\n    BIO_free(in);\n    return ret;\n}\n\nvoid\nbackend_tls_ctx_reload_crl(struct tls_root_ctx *ssl_ctx, const char *crl_file, bool crl_inline)\n{\n    BIO *in = NULL;\n\n    X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx->ctx);\n    if (!store)\n    {\n        crypto_msg(M_FATAL, \"Cannot get certificate store\");\n    }\n\n    /* Always start with a cleared CRL list, for that we\n     * we need to manually find the CRL object from the stack\n     * and remove it */\n    STACK_OF(X509_OBJECT) *objs = X509_STORE_get0_objects(store);\n    for (int i = 0; i < sk_X509_OBJECT_num(objs); i++)\n    {\n        X509_OBJECT *obj = sk_X509_OBJECT_value(objs, i);\n        ASSERT(obj);\n        if (X509_OBJECT_get_type(obj) == X509_LU_CRL)\n        {\n            sk_X509_OBJECT_delete(objs, i);\n            X509_OBJECT_free(obj);\n        }\n    }\n\n    X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);\n\n    if (crl_inline)\n    {\n        in = BIO_new_mem_buf((char *)crl_file, -1);\n    }\n    else\n    {\n        in = BIO_new_file(crl_file, \"r\");\n    }\n\n    if (in == NULL)\n    {\n        msg(M_WARN, \"CRL: cannot read: %s\", print_key_filename(crl_file, crl_inline));\n        goto end;\n    }\n\n    int num_crls_loaded = 0;\n    while (true)\n    {\n        X509_CRL *crl = PEM_read_bio_X509_CRL(in, NULL, NULL, NULL);\n        if (crl == NULL)\n        {\n            /*\n             * PEM_R_NO_START_LINE can be considered equivalent to EOF.\n             */\n            bool eof = ERR_GET_REASON(ERR_peek_error()) == PEM_R_NO_START_LINE;\n            /* but warn if no CRLs have been loaded */\n            if (num_crls_loaded > 0 && eof)\n            {\n                /* remove that error from error stack */\n                (void)ERR_get_error();\n                break;\n            }\n\n            crypto_msg(M_WARN, \"CRL: cannot read CRL from file %s\",\n                       print_key_filename(crl_file, crl_inline));\n            break;\n        }\n\n        if (!X509_STORE_add_crl(store, crl))\n        {\n            X509_CRL_free(crl);\n            crypto_msg(M_WARN, \"CRL: cannot add %s to store\",\n                       print_key_filename(crl_file, crl_inline));\n            break;\n        }\n        X509_CRL_free(crl);\n        num_crls_loaded++;\n    }\n    msg(M_INFO, \"CRL: loaded %d CRLs from file %s\", num_crls_loaded, crl_file);\nend:\n    BIO_free(in);\n}\n\n\n#if defined(ENABLE_MANAGEMENT) && !defined(HAVE_XKEY_PROVIDER)\n\n/* encrypt */\nstatic int\nrsa_pub_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)\n{\n    ASSERT(0);\n    return -1;\n}\n\n/* verify arbitrary data */\nstatic int\nrsa_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)\n{\n    ASSERT(0);\n    return -1;\n}\n\n/* decrypt */\nstatic int\nrsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)\n{\n    ASSERT(0);\n    return -1;\n}\n\n/* called at RSA_free */\nstatic int\nopenvpn_extkey_rsa_finish(RSA *rsa)\n{\n    /* meth was allocated in tls_ctx_use_management_external_key() ; since\n     * this function is called when the parent RSA object is destroyed,\n     * it is no longer used after this point so kill it. */\n    const RSA_METHOD *meth = RSA_get_method(rsa);\n    RSA_meth_free((RSA_METHOD *)meth);\n    return 1;\n}\n\n/*\n * Convert OpenSSL's constant to the strings used in the management\n * interface query\n */\nconst char *\nget_rsa_padding_name(const int padding)\n{\n    switch (padding)\n    {\n        case RSA_PKCS1_PADDING:\n            return \"RSA_PKCS1_PADDING\";\n\n        case RSA_NO_PADDING:\n            return \"RSA_NO_PADDING\";\n\n        default:\n            return \"UNKNOWN\";\n    }\n}\n\n/**\n * Pass the input hash in 'dgst' to management and get the signature back.\n *\n * @param dgst          hash to be signed\n * @param dgstlen       len of data in dgst\n * @param sig           On successful return signature is in sig.\n * @param siglen        length of buffer sig\n * @param algorithm     padding/hashing algorithm for the signature\n *\n * @return              signature length or -1 on error.\n */\nstatic int\nget_sig_from_man(const unsigned char *dgst, unsigned int dgstlen, unsigned char *sig,\n                 unsigned int siglen, const char *algorithm)\n{\n    char *in_b64 = NULL;\n    char *out_b64 = NULL;\n    int len = -1;\n\n    int bencret = openvpn_base64_encode(dgst, dgstlen, &in_b64);\n\n    if (management && bencret > 0)\n    {\n        out_b64 = management_query_pk_sig(management, in_b64, algorithm);\n    }\n    if (out_b64)\n    {\n        len = openvpn_base64_decode(out_b64, sig, siglen);\n    }\n\n    free(in_b64);\n    free(out_b64);\n    return len;\n}\n\n/* sign arbitrary data */\nstatic int\nrsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)\n{\n    int len = RSA_size(rsa);\n\n    if (padding != RSA_PKCS1_PADDING && padding != RSA_NO_PADDING)\n    {\n        RSAerr(RSA_F_RSA_OSSL_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);\n        return -1;\n    }\n\n    int ret = get_sig_from_man(from, flen, to, len, get_rsa_padding_name(padding));\n\n    return (ret == len) ? ret : -1;\n}\n\nstatic int\ntls_ctx_use_external_rsa_key(struct tls_root_ctx *ctx, EVP_PKEY *pkey)\n{\n    RSA *rsa = NULL;\n    RSA_METHOD *rsa_meth;\n\n    ASSERT(NULL != ctx);\n\n    const RSA *pub_rsa = EVP_PKEY_get0_RSA(pkey);\n    ASSERT(NULL != pub_rsa);\n\n    /* allocate custom RSA method object */\n    rsa_meth = RSA_meth_new(\"OpenVPN external private key RSA Method\", RSA_METHOD_FLAG_NO_CHECK);\n    check_malloc_return(rsa_meth);\n    RSA_meth_set_pub_enc(rsa_meth, rsa_pub_enc);\n    RSA_meth_set_pub_dec(rsa_meth, rsa_pub_dec);\n    RSA_meth_set_priv_enc(rsa_meth, rsa_priv_enc);\n    RSA_meth_set_priv_dec(rsa_meth, rsa_priv_dec);\n    RSA_meth_set_init(rsa_meth, NULL);\n    RSA_meth_set_finish(rsa_meth, openvpn_extkey_rsa_finish);\n    RSA_meth_set0_app_data(rsa_meth, NULL);\n\n    /* allocate RSA object */\n    rsa = RSA_new();\n    if (rsa == NULL)\n    {\n        SSLerr(SSL_F_SSL_USE_PRIVATEKEY, ERR_R_MALLOC_FAILURE);\n        goto err;\n    }\n\n    /* initialize RSA object */\n    const BIGNUM *n = NULL;\n    const BIGNUM *e = NULL;\n    RSA_get0_key(pub_rsa, &n, &e, NULL);\n    RSA_set0_key(rsa, BN_dup(n), BN_dup(e), NULL);\n    RSA_set_flags(rsa, RSA_flags(rsa) | RSA_FLAG_EXT_PKEY);\n    if (!RSA_set_method(rsa, rsa_meth))\n    {\n        RSA_meth_free(rsa_meth);\n        goto err;\n    }\n    /* from this point rsa_meth will get freed with rsa */\n\n    /* bind our custom RSA object to ssl_ctx */\n    if (!SSL_CTX_use_RSAPrivateKey(ctx->ctx, rsa))\n    {\n        goto err;\n    }\n\n    RSA_free(rsa); /* doesn't necessarily free, just decrements refcount */\n    return 1;\n\nerr:\n    if (rsa)\n    {\n        RSA_free(rsa);\n    }\n    else if (rsa_meth)\n    {\n        RSA_meth_free(rsa_meth);\n    }\n    return 0;\n}\n\n#if !defined(OPENSSL_NO_EC)\n\n/* called when EC_KEY is destroyed */\nstatic void\nopenvpn_extkey_ec_finish(EC_KEY *ec)\n{\n    /* release the method structure */\n    const EC_KEY_METHOD *ec_meth = EC_KEY_get_method(ec);\n    EC_KEY_METHOD_free((EC_KEY_METHOD *)ec_meth);\n}\n\n/* EC_KEY_METHOD callback: sign().\n * Sign the hash using EC key and return DER encoded signature in sig,\n * its length in siglen. Return value is 1 on success, 0 on error.\n */\nstatic int\necdsa_sign(int type, const unsigned char *dgst, int dgstlen, unsigned char *sig,\n           unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *ec)\n{\n    int capacity = ECDSA_size(ec);\n    /*\n     * ECDSA does not seem to have proper constants for paddings since\n     * there are only signatures without padding at the moment, use\n     * a generic ECDSA for the moment\n     */\n    int len = get_sig_from_man(dgst, dgstlen, sig, capacity, \"ECDSA\");\n\n    if (len > 0)\n    {\n        *siglen = len;\n        return 1;\n    }\n    return 0;\n}\n\n/* EC_KEY_METHOD callback: sign_setup(). We do no precomputations */\nstatic int\necdsa_sign_setup(EC_KEY *ec, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp)\n{\n    return 1;\n}\n\n/* EC_KEY_METHOD callback: sign_sig().\n * Sign the hash and return the result as a newly allocated ECDS_SIG\n * struct or NULL on error.\n */\nstatic ECDSA_SIG *\necdsa_sign_sig(const unsigned char *dgst, int dgstlen, const BIGNUM *in_kinv, const BIGNUM *in_r,\n               EC_KEY *ec)\n{\n    ECDSA_SIG *ecsig = NULL;\n    unsigned int len = ECDSA_size(ec);\n    struct gc_arena gc = gc_new();\n\n    unsigned char *buf = gc_malloc(len, false, &gc);\n    if (ecdsa_sign(0, dgst, dgstlen, buf, &len, NULL, NULL, ec) != 1)\n    {\n        goto out;\n    }\n    /* const char ** should be avoided: not up to us, so we cast our way through */\n    ecsig = d2i_ECDSA_SIG(NULL, (const unsigned char **)&buf, len);\n\nout:\n    gc_free(&gc);\n    return ecsig;\n}\n\nstatic int\ntls_ctx_use_external_ec_key(struct tls_root_ctx *ctx, EVP_PKEY *pkey)\n{\n    EC_KEY *ec = NULL;\n    EVP_PKEY *privkey = NULL;\n    EC_KEY_METHOD *ec_method;\n\n    ASSERT(ctx);\n\n    ec_method = EC_KEY_METHOD_new(EC_KEY_OpenSSL());\n    if (!ec_method)\n    {\n        goto err;\n    }\n\n    /* Among init methods, we only need the finish method */\n    EC_KEY_METHOD_set_init(ec_method, NULL, openvpn_extkey_ec_finish, NULL, NULL, NULL, NULL);\n#ifdef OPENSSL_IS_AWSLC\n    EC_KEY_METHOD_set_sign(ec_method, ecdsa_sign, NULL, ecdsa_sign_sig);\n#else\n    EC_KEY_METHOD_set_sign(ec_method, ecdsa_sign, ecdsa_sign_setup, ecdsa_sign_sig);\n#endif\n\n    ec = EC_KEY_dup(EVP_PKEY_get0_EC_KEY(pkey));\n    if (!ec)\n    {\n        EC_KEY_METHOD_free(ec_method);\n        goto err;\n    }\n    if (!EC_KEY_set_method(ec, ec_method))\n    {\n        EC_KEY_METHOD_free(ec_method);\n        goto err;\n    }\n    /* from this point ec_method will get freed when ec is freed */\n\n    privkey = EVP_PKEY_new();\n    if (!EVP_PKEY_assign_EC_KEY(privkey, ec))\n    {\n        goto err;\n    }\n    /* from this point ec will get freed when privkey is freed */\n\n    if (!SSL_CTX_use_PrivateKey(ctx->ctx, privkey))\n    {\n        ec = NULL; /* avoid double freeing it below */\n        goto err;\n    }\n\n    EVP_PKEY_free(privkey); /* this will down ref privkey and ec */\n    return 1;\n\nerr:\n    /* Reach here only when ec and privkey can be independenly freed */\n    EVP_PKEY_free(privkey);\n    EC_KEY_free(ec);\n    return 0;\n}\n#endif /* !defined(OPENSSL_NO_EC) */\n#endif /* ENABLE_MANAGEMENT && !HAVE_XKEY_PROVIDER */\n\n#ifdef ENABLE_MANAGEMENT\nint\ntls_ctx_use_management_external_key(struct tls_root_ctx *ctx)\n{\n    int ret = 1;\n\n    ASSERT(NULL != ctx);\n\n    X509 *cert = SSL_CTX_get0_certificate(ctx->ctx);\n\n    ASSERT(NULL != cert);\n\n    /* get the public key */\n    EVP_PKEY *pkey = X509_get0_pubkey(cert);\n    ASSERT(pkey); /* NULL before SSL_CTX_use_certificate() is called */\n\n#ifdef HAVE_XKEY_PROVIDER\n    EVP_PKEY *privkey = xkey_load_management_key(tls_libctx, pkey);\n    if (!privkey || !SSL_CTX_use_PrivateKey(ctx->ctx, privkey))\n    {\n        EVP_PKEY_free(privkey);\n        goto cleanup;\n    }\n    EVP_PKEY_free(privkey);\n#else  /* ifdef HAVE_XKEY_PROVIDER */\n#if OPENSSL_VERSION_NUMBER < 0x30000000L\n    if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA)\n#else  /* OPENSSL_VERSION_NUMBER < 0x30000000L */\n    if (EVP_PKEY_is_a(pkey, \"RSA\"))\n#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */\n    {\n        if (!tls_ctx_use_external_rsa_key(ctx, pkey))\n        {\n            goto cleanup;\n        }\n    }\n#if !defined(OPENSSL_NO_EC)\n#if OPENSSL_VERSION_NUMBER < 0x30000000L\n    else if (EVP_PKEY_id(pkey) == EVP_PKEY_EC)\n#else  /* OPENSSL_VERSION_NUMBER < 0x30000000L */\n    else if (EVP_PKEY_is_a(pkey, \"EC\"))\n#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */\n    {\n        if (!tls_ctx_use_external_ec_key(ctx, pkey))\n        {\n            goto cleanup;\n        }\n    }\n    else\n    {\n        crypto_msg(M_WARN, \"management-external-key requires an RSA or EC certificate\");\n        goto cleanup;\n    }\n#else  /* !defined(OPENSSL_NO_EC) */\n    else\n    {\n        crypto_msg(M_WARN, \"management-external-key requires an RSA certificate\");\n        goto cleanup;\n    }\n#endif /* !defined(OPENSSL_NO_EC) */\n\n#endif /* HAVE_XKEY_PROVIDER */\n\n    ret = 0;\ncleanup:\n    if (ret)\n    {\n        crypto_msg(M_FATAL, \"Cannot enable SSL external private key capability\");\n    }\n    return ret;\n}\n\n#endif /* ifdef ENABLE_MANAGEMENT */\n\nstatic int\nsk_x509_name_cmp(const X509_NAME *const *a, const X509_NAME *const *b)\n{\n    return X509_NAME_cmp(*a, *b);\n}\n\nvoid\ntls_ctx_load_ca(struct tls_root_ctx *ctx, const char *ca_file, bool ca_file_inline,\n                const char *ca_path, bool tls_server)\n{\n    STACK_OF(X509_INFO) *info_stack = NULL;\n    STACK_OF(X509_NAME) *cert_names = NULL;\n    X509_LOOKUP *lookup = NULL;\n    X509_STORE *store = NULL;\n    X509_NAME *xn = NULL;\n    BIO *in = NULL;\n    int i, added = 0, prev = 0;\n\n    ASSERT(NULL != ctx);\n\n    store = SSL_CTX_get_cert_store(ctx->ctx);\n    if (!store)\n    {\n        crypto_msg(M_FATAL, \"Cannot get certificate store\");\n    }\n\n    /* Try to add certificates and CRLs from ca_file */\n    if (ca_file)\n    {\n        if (ca_file_inline)\n        {\n            in = BIO_new_mem_buf((char *)ca_file, -1);\n        }\n        else\n        {\n            in = BIO_new_file(ca_file, \"r\");\n        }\n\n        if (in)\n        {\n            info_stack = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL);\n        }\n\n        if (info_stack)\n        {\n            for (i = 0; i < sk_X509_INFO_num(info_stack); i++)\n            {\n                X509_INFO *info = sk_X509_INFO_value(info_stack, i);\n                if (info->crl)\n                {\n                    X509_STORE_add_crl(store, info->crl);\n                }\n\n                if (tls_server && !info->x509)\n                {\n                    crypto_msg(M_FATAL, \"X509 name was missing in TLS mode\");\n                }\n\n                if (info->x509)\n                {\n                    X509_STORE_add_cert(store, info->x509);\n                    added++;\n\n                    if (!tls_server)\n                    {\n                        continue;\n                    }\n\n                    /* Use names of CAs as a client CA list */\n                    if (cert_names == NULL)\n                    {\n                        cert_names = sk_X509_NAME_new(sk_x509_name_cmp);\n                        if (!cert_names)\n                        {\n                            continue;\n                        }\n                    }\n\n                    xn = X509_get_subject_name(info->x509);\n                    if (!xn)\n                    {\n                        continue;\n                    }\n\n                    /* Don't add duplicate CA names */\n                    if (sk_X509_NAME_find(cert_names, xn) == -1)\n                    {\n                        xn = X509_NAME_dup(xn);\n                        if (!xn)\n                        {\n                            continue;\n                        }\n                        sk_X509_NAME_push(cert_names, xn);\n                    }\n                }\n\n                if (tls_server)\n                {\n                    int cnum = sk_X509_NAME_num(cert_names);\n                    if (cnum != (prev + 1))\n                    {\n                        crypto_msg(M_WARN,\n                                   \"Cannot load CA certificate file %s (entry %d did not validate)\",\n                                   print_key_filename(ca_file, ca_file_inline), added);\n                    }\n                    prev = cnum;\n                }\n            }\n            sk_X509_INFO_pop_free(info_stack, X509_INFO_free);\n        }\n        int cnum;\n        if (tls_server)\n        {\n            cnum = sk_X509_NAME_num(cert_names);\n            SSL_CTX_set_client_CA_list(ctx->ctx, cert_names);\n        }\n\n        if (!added)\n        {\n            crypto_msg(M_FATAL, \"Cannot load CA certificate file %s (no entries were read)\",\n                       print_key_filename(ca_file, ca_file_inline));\n        }\n\n        if (tls_server)\n        {\n            if (cnum != added)\n            {\n                crypto_msg(M_FATAL,\n                           \"Cannot load CA certificate file %s (only %d \"\n                           \"of %d entries were valid X509 names)\",\n                           print_key_filename(ca_file, ca_file_inline), cnum, added);\n            }\n        }\n\n        BIO_free(in);\n    }\n\n    /* Set a store for certs (CA & CRL) with a lookup on the \"capath\" hash directory */\n    if (ca_path)\n    {\n        lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());\n        if (lookup && X509_LOOKUP_add_dir(lookup, ca_path, X509_FILETYPE_PEM))\n        {\n            msg(M_WARN, \"WARNING: experimental option --capath %s\", ca_path);\n        }\n        else\n        {\n            crypto_msg(M_FATAL, \"Cannot add lookup at --capath %s\", ca_path);\n        }\n        X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);\n    }\n}\n\nvoid\ntls_ctx_load_extra_certs(struct tls_root_ctx *ctx, const char *extra_certs_file,\n                         bool extra_certs_file_inline)\n{\n    BIO *in;\n    if (extra_certs_file_inline)\n    {\n        in = BIO_new_mem_buf((char *)extra_certs_file, -1);\n    }\n    else\n    {\n        in = BIO_new_file(extra_certs_file, \"r\");\n    }\n\n    if (in == NULL)\n    {\n        crypto_msg(M_FATAL, \"Cannot load extra-certs file: %s\",\n                   print_key_filename(extra_certs_file, extra_certs_file_inline));\n    }\n    else\n    {\n        tls_ctx_add_extra_certs(ctx, in, false);\n    }\n\n    BIO_free(in);\n}\n\n/* **************************************\n *\n * Key-state specific functions\n *\n ***************************************/\n/*\n *\n * BIO functions\n *\n */\n\n#ifdef BIO_DEBUG\n\n#warning BIO_DEBUG defined\n\nstatic FILE *biofp;                           /* GLOBAL */\nstatic bool biofp_toggle;                     /* GLOBAL */\nstatic time_t biofp_last_open;                /* GLOBAL */\nstatic const int biofp_reopen_interval = 600; /* GLOBAL */\n\nstatic void\nclose_biofp(void)\n{\n    if (biofp)\n    {\n        ASSERT(!fclose(biofp));\n        biofp = NULL;\n    }\n}\n\nstatic void\nopen_biofp(void)\n{\n    const time_t current = time(NULL);\n    const pid_t pid = getpid();\n\n    if (biofp_last_open + biofp_reopen_interval < current)\n    {\n        close_biofp();\n    }\n    if (!biofp)\n    {\n        char fn[256];\n        snprintf(fn, sizeof(fn), \"bio/%d-%d.log\", pid, biofp_toggle);\n        biofp = fopen(fn, \"w\");\n        ASSERT(biofp);\n        biofp_last_open = time(NULL);\n        biofp_toggle ^= 1;\n    }\n}\n\nstatic void\nbio_debug_data(const char *mode, BIO *bio, const uint8_t *buf, int len, const char *desc)\n{\n    struct gc_arena gc = gc_new();\n    if (len > 0)\n    {\n        open_biofp();\n        fprintf(biofp, \"BIO_%s %s time=%\" PRIi64 \" bio=\" ptr_format \" len=%d data=%s\\n\", mode, desc,\n                (int64_t)time(NULL), (ptr_type)bio, len, format_hex(buf, len, 0, &gc));\n        fflush(biofp);\n    }\n    gc_free(&gc);\n}\n\nstatic void\nbio_debug_oc(const char *mode, BIO *bio)\n{\n    open_biofp();\n    fprintf(biofp, \"BIO %s time=%\" PRIi64 \" bio=\" ptr_format \"\\n\", mode, (int64_t)time(NULL),\n            (ptr_type)bio);\n    fflush(biofp);\n}\n\n#endif /* ifdef BIO_DEBUG */\n\n/*\n * Write to an OpenSSL BIO in non-blocking mode.\n */\nstatic int\nbio_write(BIO *bio, const uint8_t *data, int size, const char *desc)\n{\n    int i;\n    int ret = 0;\n    ASSERT(size >= 0);\n    if (size)\n    {\n        /*\n         * Free the L_TLS lock prior to calling BIO routines\n         * so that foreground thread can still call\n         * tls_pre_decrypt or tls_pre_encrypt,\n         * allowing tunnel packet forwarding to continue.\n         */\n#ifdef BIO_DEBUG\n        bio_debug_data(\"write\", bio, data, size, desc);\n#endif\n        i = BIO_write(bio, data, size);\n\n        if (i < 0)\n        {\n            if (!BIO_should_retry(bio))\n            {\n                crypto_msg(D_TLS_ERRORS, \"TLS ERROR: BIO write %s error\", desc);\n                ret = -1;\n                ERR_clear_error();\n            }\n        }\n        else if (i != size)\n        {\n            crypto_msg(D_TLS_ERRORS, \"TLS ERROR: BIO write %s incomplete %d/%d\", desc, i, size);\n            ret = -1;\n            ERR_clear_error();\n        }\n        else\n        { /* successful write */\n            dmsg(D_HANDSHAKE_VERBOSE, \"BIO write %s %d bytes\", desc, i);\n            ret = 1;\n        }\n    }\n    return ret;\n}\n\n/*\n * Inline functions for reading from and writing\n * to BIOs.\n */\n\nstatic void\nbio_write_post(const int status, struct buffer *buf)\n{\n    /* success status return from bio_write? */\n    if (status == 1)\n    {\n        memset(BPTR(buf), 0, BLENZ(buf)); /* erase data just written */\n        buf->len = 0;\n    }\n}\n\n/*\n * Read from an OpenSSL BIO in non-blocking mode.\n */\nstatic int\nbio_read(BIO *bio, struct buffer *buf, const char *desc)\n{\n    ASSERT(buf->len >= 0);\n    if (buf->len)\n    {\n        /* we only want to write empty buffers, ignore read request\n         * if the buffer is not empty */\n        return 0;\n    }\n    int len = buf_forward_capacity(buf);\n\n    /*\n     * BIO_read brackets most of the serious RSA\n     * key negotiation number crunching.\n     */\n    int i = BIO_read(bio, BPTR(buf), len);\n\n    VALGRIND_MAKE_READABLE((void *)&i, sizeof(i));\n\n#ifdef BIO_DEBUG\n    bio_debug_data(\"read\", bio, BPTR(buf), i, desc);\n#endif\n\n    int ret = 0;\n    if (i < 0)\n    {\n        if (!BIO_should_retry(bio))\n        {\n            crypto_msg(D_TLS_ERRORS, \"TLS_ERROR: BIO read %s error\", desc);\n            buf->len = 0;\n            ret = -1;\n            ERR_clear_error();\n        }\n    }\n    else if (!i)\n    {\n        buf->len = 0;\n    }\n    else\n    { /* successful read */\n        dmsg(D_HANDSHAKE_VERBOSE, \"BIO read %s %d bytes\", desc, i);\n        buf->len = i;\n        ret = 1;\n        VALGRIND_MAKE_READABLE((void *)BPTR(buf), BLEN(buf));\n    }\n    return ret;\n}\n\nvoid\nkey_state_ssl_init(struct key_state_ssl *ks_ssl, const struct tls_root_ctx *ssl_ctx, bool is_server,\n                   struct tls_session *session)\n{\n    ASSERT(NULL != ssl_ctx);\n    ASSERT(ks_ssl);\n    CLEAR(*ks_ssl);\n\n    ks_ssl->ssl = SSL_new(ssl_ctx->ctx);\n    if (!ks_ssl->ssl)\n    {\n        crypto_msg(M_FATAL, \"SSL_new failed\");\n    }\n\n    /* put session * in ssl object so we can access it\n     * from verify callback*/\n    SSL_set_ex_data(ks_ssl->ssl, mydata_index, session);\n\n    ASSERT((ks_ssl->ssl_bio = BIO_new(BIO_f_ssl())));\n    ASSERT((ks_ssl->ct_in = BIO_new(BIO_s_mem())));\n    ASSERT((ks_ssl->ct_out = BIO_new(BIO_s_mem())));\n\n#ifdef BIO_DEBUG\n    bio_debug_oc(\"open ssl_bio\", ks_ssl->ssl_bio);\n    bio_debug_oc(\"open ct_in\", ks_ssl->ct_in);\n    bio_debug_oc(\"open ct_out\", ks_ssl->ct_out);\n#endif\n\n    if (is_server)\n    {\n        SSL_set_accept_state(ks_ssl->ssl);\n    }\n    else\n    {\n        SSL_set_connect_state(ks_ssl->ssl);\n    }\n\n    SSL_set_bio(ks_ssl->ssl, ks_ssl->ct_in, ks_ssl->ct_out);\n    BIO_set_ssl(ks_ssl->ssl_bio, ks_ssl->ssl, BIO_NOCLOSE);\n}\n\nvoid\nkey_state_ssl_shutdown(struct key_state_ssl *ks_ssl)\n{\n    SSL_set_shutdown(ks_ssl->ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);\n}\n\nvoid\nkey_state_ssl_free(struct key_state_ssl *ks_ssl)\n{\n    if (ks_ssl->ssl)\n    {\n#ifdef BIO_DEBUG\n        bio_debug_oc(\"close ssl_bio\", ks_ssl->ssl_bio);\n        bio_debug_oc(\"close ct_in\", ks_ssl->ct_in);\n        bio_debug_oc(\"close ct_out\", ks_ssl->ct_out);\n#endif\n        BIO_free_all(ks_ssl->ssl_bio);\n        SSL_free(ks_ssl->ssl);\n    }\n}\n\nint\nkey_state_write_plaintext(struct key_state_ssl *ks_ssl, struct buffer *buf)\n{\n    int ret = 0;\n\n    ASSERT(NULL != ks_ssl);\n\n    ret = bio_write(ks_ssl->ssl_bio, BPTR(buf), BLEN(buf), \"tls_write_plaintext\");\n    bio_write_post(ret, buf);\n\n    return ret;\n}\n\nint\nkey_state_write_plaintext_const(struct key_state_ssl *ks_ssl, const uint8_t *data, int len)\n{\n    int ret = 0;\n\n    ASSERT(NULL != ks_ssl);\n\n    ret = bio_write(ks_ssl->ssl_bio, data, len, \"tls_write_plaintext_const\");\n\n    return ret;\n}\n\nint\nkey_state_read_ciphertext(struct key_state_ssl *ks_ssl, struct buffer *buf)\n{\n    int ret = 0;\n\n    ASSERT(NULL != ks_ssl);\n\n    ret = bio_read(ks_ssl->ct_out, buf, \"tls_read_ciphertext\");\n\n    return ret;\n}\n\nint\nkey_state_write_ciphertext(struct key_state_ssl *ks_ssl, struct buffer *buf)\n{\n    int ret = 0;\n\n    ASSERT(NULL != ks_ssl);\n\n    ret = bio_write(ks_ssl->ct_in, BPTR(buf), BLEN(buf), \"tls_write_ciphertext\");\n    bio_write_post(ret, buf);\n\n    return ret;\n}\n\nint\nkey_state_read_plaintext(struct key_state_ssl *ks_ssl, struct buffer *buf)\n{\n    int ret = 0;\n\n    ASSERT(NULL != ks_ssl);\n\n    ret = bio_read(ks_ssl->ssl_bio, buf, \"tls_read_plaintext\");\n\n    return ret;\n}\n\nstatic void\nprint_pkey_details(EVP_PKEY *pkey, char *buf, size_t buflen)\n{\n    const char *curve = \"\";\n    const char *type = \"(error getting type)\";\n\n    if (pkey == NULL)\n    {\n        buf[0] = 0;\n        return;\n    }\n\n    int typeid = EVP_PKEY_id(pkey);\n#if OPENSSL_VERSION_NUMBER < 0x30000000L\n    bool is_ec = typeid == EVP_PKEY_EC;\n#else\n    bool is_ec = EVP_PKEY_is_a(pkey, \"EC\");\n#endif\n\n#ifndef OPENSSL_NO_EC\n    char groupname[64];\n    if (is_ec)\n    {\n        size_t len;\n        if (EVP_PKEY_get_group_name(pkey, groupname, sizeof(groupname), &len))\n        {\n            curve = groupname;\n        }\n        else\n        {\n            curve = \"(error getting curve name)\";\n        }\n    }\n#endif\n    if (typeid != 0)\n    {\n#if OPENSSL_VERSION_NUMBER < 0x30000000L\n        type = OBJ_nid2sn(typeid);\n\n        /* OpenSSL reports rsaEncryption, dsaEncryption and\n         * id-ecPublicKey, map these values to nicer ones */\n        if (typeid == EVP_PKEY_RSA)\n        {\n            type = \"RSA\";\n        }\n        else if (typeid == EVP_PKEY_DSA)\n        {\n            type = \"DSA\";\n        }\n        else if (typeid == EVP_PKEY_EC)\n        {\n            /* EC gets the curve appended after the type */\n            type = \"EC, curve \";\n        }\n        else if (type == NULL)\n        {\n            type = \"unknown type\";\n        }\n#else  /* OpenSSL >= 3 */\n        type = EVP_PKEY_get0_type_name(pkey);\n        if (type == NULL)\n        {\n            type = \"(error getting public key type)\";\n        }\n#endif /* if OPENSSL_VERSION_NUMBER < 0x30000000L */\n    }\n\n    snprintf(buf, buflen, \"%d bits %s%s\", EVP_PKEY_bits(pkey), type, curve);\n}\n\n/**\n * Print human readable information about the certificate into buf\n * @param cert      the certificate being used\n * @param buf       output buffer\n * @param buflen    output buffer length\n */\nstatic void\nprint_cert_details(X509 *cert, char *buf, size_t buflen)\n{\n    EVP_PKEY *pkey = X509_get_pubkey(cert);\n    char pkeybuf[64] = { 0 };\n    print_pkey_details(pkey, pkeybuf, sizeof(pkeybuf));\n\n    char sig[128] = { 0 };\n    int signature_nid = X509_get_signature_nid(cert);\n    if (signature_nid != 0)\n    {\n        snprintf(sig, sizeof(sig), \", signature: %s\", OBJ_nid2sn(signature_nid));\n    }\n\n    snprintf(buf, buflen, \", peer certificate: %s%s\", pkeybuf, sig);\n\n    EVP_PKEY_free(pkey);\n}\n\nstatic void\nprint_server_tempkey(SSL *ssl, char *buf, size_t buflen)\n{\n    EVP_PKEY *pkey = NULL;\n    SSL_get_peer_tmp_key(ssl, &pkey);\n    if (!pkey)\n    {\n        return;\n    }\n\n    char pkeybuf[128] = { 0 };\n    print_pkey_details(pkey, pkeybuf, sizeof(pkeybuf));\n\n    snprintf(buf, buflen, \", peer temporary key: %s\", pkeybuf);\n\n    EVP_PKEY_free(pkey);\n}\n\n#if !defined(LIBRESSL_VERSION_NUMBER) \\\n    || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER >= 0x3090000fL)\n/**\n * Translate an OpenSSL NID into a more human readable name\n * @param nid\n * @return\n */\nstatic const char *\nget_sigtype(int nid)\n{\n    /* Fix a few OpenSSL names to be better understandable */\n    switch (nid)\n    {\n        case EVP_PKEY_RSA:\n            /* will otherwise say rsaEncryption */\n            return \"RSA\";\n\n        case EVP_PKEY_DSA:\n            /* dsaEncryption otherwise */\n            return \"DSA\";\n\n        case EVP_PKEY_EC:\n            /* will say id-ecPublicKey */\n            return \"ECDSA\";\n\n        case -1:\n            return \"(error getting name)\";\n\n        default:\n        {\n            const char *type = OBJ_nid2sn(nid);\n            if (!type)\n            {\n                /* This is unlikely to ever happen as OpenSSL is unlikely to\n                 * return an NID it cannot resolve itself but we silence\n                 * linter/code checkers here */\n                type = \"(error getting name, OBJ_nid2sn failed)\";\n            }\n            return type;\n        }\n    }\n}\n#endif /* ifndef LIBRESSL_VERSION_NUMBER */\n\n/**\n * Get the type of the signature that is used by the peer during the\n * TLS handshake\n */\nstatic void\nprint_peer_signature(SSL *ssl, char *buf, size_t buflen)\n{\n    int peer_sig_type_nid = NID_undef;\n    const char *peer_sig_unknown = \"unknown\";\n    const char *peer_sig = peer_sig_unknown;\n    const char *peer_sig_type = \"unknown type\";\n\n    const char *signame = NULL;\n    SSL_get0_peer_signature_name(ssl, &signame);\n    if (signame)\n    {\n        peer_sig = signame;\n    }\n\n#if !defined(LIBRESSL_VERSION_NUMBER) \\\n    || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER >= 0x3090000fL)\n    /* LibreSSL 3.7.x and 3.8.x implement this function but do not export it\n     * and fail linking with an unresolved symbol */\n    if (SSL_get_peer_signature_type_nid(ssl, &peer_sig_type_nid) && peer_sig_type_nid != NID_undef)\n    {\n        peer_sig_type = get_sigtype(peer_sig_type_nid);\n    }\n#endif\n\n    if (peer_sig == peer_sig_unknown && peer_sig_type_nid == NID_undef)\n    {\n        return;\n    }\n\n    snprintf(buf, buflen, \", peer signing digest/type: %s %s\", peer_sig, peer_sig_type);\n}\n\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\nvoid\nprint_tls_key_agreement_group(SSL *ssl, char *buf, size_t buflen)\n{\n    const char *groupname = SSL_get0_group_name(ssl);\n    if (!groupname)\n    {\n        snprintf(buf, buflen, \", key agreement: (error fetching group)\");\n    }\n    else\n    {\n        snprintf(buf, buflen, \", key agreement: %s\", groupname);\n    }\n}\n#endif\n\n/* **************************************\n *\n * Information functions\n *\n * Print information for the end user.\n *\n ***************************************/\nvoid\nprint_details(struct key_state_ssl *ks_ssl, const char *prefix)\n{\n    const SSL_CIPHER *ciph;\n    char s1[256];\n    char s2[256];\n    char s3[256];\n    char s4[256];\n    char s5[256];\n\n    s1[0] = s2[0] = s3[0] = s4[0] = s5[0] = 0;\n    ciph = SSL_get_current_cipher(ks_ssl->ssl);\n    snprintf(s1, sizeof(s1), \"%s %s, cipher %s %s\", prefix, SSL_get_version(ks_ssl->ssl),\n             SSL_CIPHER_get_version(ciph), SSL_CIPHER_get_name(ciph));\n    X509 *cert = SSL_get_peer_certificate(ks_ssl->ssl);\n\n    if (cert)\n    {\n        print_cert_details(cert, s2, sizeof(s2));\n        X509_free(cert);\n    }\n    print_server_tempkey(ks_ssl->ssl, s3, sizeof(s3));\n    print_peer_signature(ks_ssl->ssl, s4, sizeof(s4));\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\n    print_tls_key_agreement_group(ks_ssl->ssl, s5, sizeof(s5));\n#endif\n\n    msg(D_HANDSHAKE, \"%s%s%s%s%s\", s1, s2, s3, s4, s5);\n}\n\nvoid\nshow_available_tls_ciphers_list(const char *cipher_list, const char *tls_cert_profile, bool tls13)\n{\n    struct tls_root_ctx tls_ctx;\n\n    tls_ctx.ctx = SSL_CTX_new(SSLv23_method());\n    if (!tls_ctx.ctx)\n    {\n        crypto_msg(M_FATAL, \"Cannot create SSL_CTX object\");\n    }\n\n#if defined(TLS1_3_VERSION)\n    if (tls13)\n    {\n        SSL_CTX_set_min_proto_version(tls_ctx.ctx, TLS1_3_VERSION);\n        tls_ctx_restrict_ciphers_tls13(&tls_ctx, cipher_list);\n    }\n    else\n#endif\n    {\n        SSL_CTX_set_max_proto_version(tls_ctx.ctx, TLS1_2_VERSION);\n        tls_ctx_restrict_ciphers(&tls_ctx, cipher_list);\n    }\n\n    tls_ctx_set_cert_profile(&tls_ctx, tls_cert_profile);\n\n    SSL *ssl = SSL_new(tls_ctx.ctx);\n    if (!ssl)\n    {\n        crypto_msg(M_FATAL, \"Cannot create SSL object\");\n    }\n\n#if OPENSSL_VERSION_NUMBER < 0x1010000fL || defined(OPENSSL_IS_AWSLC) || defined(ENABLE_CRYPTO_WOLFSSL)\n    STACK_OF(SSL_CIPHER) *sk = SSL_get_ciphers(ssl);\n#else\n    STACK_OF(SSL_CIPHER) *sk = SSL_get1_supported_ciphers(ssl);\n#endif\n    for (int i = 0; i < sk_SSL_CIPHER_num(sk); i++)\n    {\n        const SSL_CIPHER *c = sk_SSL_CIPHER_value(sk, i);\n\n        const char *cipher_name = SSL_CIPHER_get_name(c);\n\n        const tls_cipher_name_pair *pair =\n            tls_get_cipher_name_pair(cipher_name, strlen(cipher_name));\n\n        if (tls13)\n        {\n            printf(\"%s\\n\", cipher_name);\n        }\n        else if (NULL == pair)\n        {\n            /* No translation found, print warning */\n            printf(\"%s (No IANA name known to OpenVPN, use OpenSSL name.)\\n\", cipher_name);\n        }\n        else\n        {\n            printf(\"%s\\n\", pair->iana_name);\n        }\n    }\n#if (OPENSSL_VERSION_NUMBER >= 0x1010000fL)\n    sk_SSL_CIPHER_free(sk);\n#endif\n    SSL_free(ssl);\n    SSL_CTX_free(tls_ctx.ctx);\n}\n\n/*\n * Show the Elliptic curves that are available for us to use\n * in the OpenSSL library.\n */\nvoid\nshow_available_curves(void)\n{\n    printf(\"Consider using 'openssl ecparam -list_curves' as alternative to running\\n\"\n           \"this command.\\n\"\n           \"Note this output does only list curves/groups that OpenSSL considers as\\n\"\n           \"builtin EC curves. It does not list additional curves nor X448 or X25519\\n\");\n#ifndef OPENSSL_NO_EC\n    EC_builtin_curve *curves = NULL;\n    size_t crv_len = 0;\n    size_t n = 0;\n\n    crv_len = EC_get_builtin_curves(NULL, 0);\n    ALLOC_ARRAY(curves, EC_builtin_curve, crv_len);\n    if (EC_get_builtin_curves(curves, crv_len))\n    {\n        printf(\"\\nAvailable Elliptic curves/groups:\\n\");\n        for (n = 0; n < crv_len; n++)\n        {\n            const char *sname;\n            sname = OBJ_nid2sn(curves[n].nid);\n            if (sname == NULL)\n            {\n                sname = \"\";\n            }\n\n            printf(\"%s\\n\", sname);\n        }\n    }\n    else\n    {\n        crypto_msg(M_FATAL, \"Cannot get list of builtin curves\");\n    }\n    free(curves);\n#else  /* ifndef OPENSSL_NO_EC */\n    msg(M_WARN, \"Your OpenSSL library was built without elliptic curve support. \"\n                \"No curves available.\");\n#endif /* ifndef OPENSSL_NO_EC */\n}\n\nconst char *\nget_ssl_library_version(void)\n{\n    return OpenSSL_version(OPENSSL_VERSION);\n}\n\n\n/** Some helper routines for provider load/unload */\n#ifdef HAVE_XKEY_PROVIDER\nstatic int\nprovider_load(OSSL_PROVIDER *prov, void *dest_libctx)\n{\n    const char *name = OSSL_PROVIDER_get0_name(prov);\n    OSSL_PROVIDER_load(dest_libctx, name);\n    return 1;\n}\n\nstatic int\nprovider_unload(OSSL_PROVIDER *prov, void *unused)\n{\n    (void)unused;\n    OSSL_PROVIDER_unload(prov);\n    return 1;\n}\n#endif /* HAVE_XKEY_PROVIDER */\n\n/**\n * Setup ovpn.xey provider for signing with external keys.\n * It is loaded into a custom library context so as not to pollute\n * the default context. Alternatively we could override any\n * system-wide property query set on the default context. But we\n * want to avoid that.\n */\nvoid\nload_xkey_provider(void)\n{\n#ifdef HAVE_XKEY_PROVIDER\n\n    /* Make a new library context for use in TLS context */\n    if (!tls_libctx)\n    {\n        tls_libctx = OSSL_LIB_CTX_new();\n        check_malloc_return(tls_libctx);\n\n        /* Load all providers in default LIBCTX into this libctx.\n         * OpenSSL has a child libctx functionality to automate this,\n         * but currently that is usable only from within providers.\n         * So we do something close to it manually here.\n         */\n        OSSL_PROVIDER_do_all(NULL, provider_load, tls_libctx);\n    }\n\n    if (!OSSL_PROVIDER_available(tls_libctx, \"ovpn.xkey\"))\n    {\n        OSSL_PROVIDER_add_builtin(tls_libctx, \"ovpn.xkey\", xkey_provider_init);\n        if (!OSSL_PROVIDER_load(tls_libctx, \"ovpn.xkey\"))\n        {\n            msg(M_NONFATAL, \"ERROR: failed loading external key provider: \"\n                            \"Signing with external keys will not work.\");\n        }\n    }\n\n    /* We only implement minimal functionality in ovpn.xkey, so we do not want\n     * methods in xkey to be picked unless absolutely required (i.e, when the key\n     * is external). Ensure this by setting a default propquery for the custom\n     * libctx that unprefers, but does not forbid, ovpn.xkey. See also man page\n     * of \"property\" in OpenSSL 3.0.\n     */\n    EVP_set_default_properties(tls_libctx, \"?provider!=ovpn.xkey\");\n\n#endif /* HAVE_XKEY_PROVIDER */\n}\n\n/**\n * Undo steps in load_xkey_provider\n */\nstatic void\nunload_xkey_provider(void)\n{\n#ifdef HAVE_XKEY_PROVIDER\n    if (tls_libctx)\n    {\n        OSSL_PROVIDER_do_all(tls_libctx, provider_unload, NULL);\n        OSSL_LIB_CTX_free(tls_libctx);\n    }\n#endif /* HAVE_XKEY_PROVIDER */\n    tls_libctx = NULL;\n}\n\n#endif /* defined(ENABLE_CRYPTO_OPENSSL) */\n"
  },
  {
    "path": "src/openvpn/ssl_openssl.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel OpenSSL Backend\n */\n\n#ifndef SSL_OPENSSL_H_\n#define SSL_OPENSSL_H_\n\n#include <openssl/ssl.h>\n#include <openssl/err.h>\n\n/**\n * Structure that wraps the TLS context. Contents differ depending on the\n * SSL library used.\n */\nstruct tls_root_ctx\n{\n    SSL_CTX *ctx;\n    time_t crl_last_mtime;\n    off_t crl_last_size;\n};\n\nstruct key_state_ssl\n{\n    SSL *ssl;     /* SSL object -- new obj created for each new key */\n    BIO *ssl_bio; /* read/write plaintext from here */\n    BIO *ct_in;   /* write ciphertext to here */\n    BIO *ct_out;  /* read ciphertext from here */\n};\n\n/**\n * Allocate space in SSL objects in which to store a struct tls_session\n * pointer back to parent.\n */\nextern int mydata_index; /* GLOBAL */\n\nstatic inline void\ntls_clear_error(void)\n{\n    ERR_clear_error();\n}\n\n#endif /* SSL_OPENSSL_H_ */\n"
  },
  {
    "path": "src/openvpn/ssl_pkt.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"ssl_util.h\"\n#include \"ssl_pkt.h\"\n#include \"ssl_common.h\"\n#include \"crypto.h\"\n#include \"session_id.h\"\n#include \"reliable.h\"\n#include \"tls_crypt.h\"\n\n/*\n * Dependent on hmac size, opcode size, and session_id size.\n * Will assert if too small.\n */\n#define SWAP_BUF_SIZE 256\n\n/**\n * Move a packet authentication HMAC + related fields to or from the front\n * of the buffer so it can be processed by encrypt/decrypt.\n *\n * Turning the on wire format that starts with the opcode to a format\n * that starts with the hmac\n * e.g. \"onwire\" [opcode, peer session id] [hmac, packet id] [remainder of packed]\n *\n *\n *    \"internal\" [hmac, packet id] [opcode, peer session id] [remainder of packet]\n *\n *  @param buf      the buffer the swap operation is executed on\n *  @param incoming determines the direction of the swap\n *  @param co       crypto options, determines the hmac to use in the swap\n *\n *  @return         if the swap was successful (buf was large enough)\n */\nstatic bool\nswap_hmac(struct buffer *buf, const struct crypto_options *co, bool incoming)\n{\n    ASSERT(co);\n\n    const struct key_ctx *ctx = (incoming ? &co->key_ctx_bi.decrypt : &co->key_ctx_bi.encrypt);\n    ASSERT(ctx->hmac);\n\n    {\n        /* hmac + packet_id (8 bytes) */\n        const int hmac_size = hmac_ctx_size(ctx->hmac) + packet_id_size(true);\n\n        /* opcode (1 byte) + session_id (8 bytes) */\n        const int osid_size = 1 + SID_SIZE;\n\n        int e1, e2;\n        uint8_t *b = BPTR(buf);\n        uint8_t buf1[SWAP_BUF_SIZE];\n        uint8_t buf2[SWAP_BUF_SIZE];\n\n        if (incoming)\n        {\n            e1 = osid_size;\n            e2 = hmac_size;\n        }\n        else\n        {\n            e1 = hmac_size;\n            e2 = osid_size;\n        }\n\n        ASSERT(e1 <= SWAP_BUF_SIZE && e2 <= SWAP_BUF_SIZE);\n\n        if (buf->len >= e1 + e2)\n        {\n            memcpy(buf1, b, e1);\n            memcpy(buf2, b + e1, e2);\n            memcpy(b, buf2, e2);\n            memcpy(b + e2, buf1, e1);\n            return true;\n        }\n        else\n        {\n            return false;\n        }\n    }\n}\n\n#undef SWAP_BUF_SIZE\n\n/**\n * Wraps a TLS control packet by adding tls-auth HMAC or tls-crypt(-v2)\n * encryption and opcode header including session id.\n *\n * @param ctx           tls wrapping context\n * @param header        first byte of the packet (opcode and key id)\n * @param buf           buffer to write the resulting packet to\n * @param session_id    session id to use as our session id\n */\nstatic void\ntls_wrap_control(struct tls_wrap_ctx *ctx, uint8_t header, struct buffer *buf,\n                 struct session_id *session_id)\n{\n    if (ctx->mode == TLS_WRAP_AUTH || ctx->mode == TLS_WRAP_NONE)\n    {\n        ASSERT(session_id_write_prepend(session_id, buf));\n        ASSERT(buf_write_prepend(buf, &header, sizeof(header)));\n    }\n    if (ctx->mode == TLS_WRAP_AUTH)\n    {\n        struct buffer null = clear_buf();\n\n        /* no encryption, only write hmac */\n        openvpn_encrypt(buf, null, &ctx->opt);\n        ASSERT(swap_hmac(buf, &ctx->opt, false));\n    }\n    else if (ctx->mode == TLS_WRAP_CRYPT)\n    {\n        ASSERT(buf_init(&ctx->work, buf->offset));\n        ASSERT(buf_write(&ctx->work, &header, sizeof(header)));\n        ASSERT(session_id_write(session_id, &ctx->work));\n        if (!tls_crypt_wrap(buf, &ctx->work, &ctx->opt))\n        {\n            buf->len = 0;\n            return;\n        }\n\n        if ((header >> P_OPCODE_SHIFT) == P_CONTROL_HARD_RESET_CLIENT_V3\n            || (header >> P_OPCODE_SHIFT) == P_CONTROL_WKC_V1)\n        {\n            if (!buf_copy(&ctx->work, ctx->tls_crypt_v2_wkc))\n            {\n                msg(D_TLS_ERRORS, \"Could not append tls-crypt-v2 client key\");\n                buf->len = 0;\n                return;\n            }\n        }\n\n        /* Don't change the original data in buf, it's used by the reliability\n         * layer to resend on failure. */\n        *buf = ctx->work;\n    }\n}\n\nvoid\nwrite_control_auth(struct tls_session *session, struct key_state *ks, struct buffer *buf,\n                   struct link_socket_actual **to_link_addr, int opcode, int max_ack,\n                   bool prepend_ack)\n{\n    ASSERT(ks->key_id >= 0 && ks->key_id <= P_KEY_ID_MASK);\n    ASSERT(opcode >= 0 && opcode <= P_LAST_OPCODE);\n    uint8_t header = (uint8_t)(ks->key_id | (opcode << P_OPCODE_SHIFT));\n\n    /* Workaround for Softether servers. Softether has a bug that it only\n     * allows 4 ACks in packets and drops packets if more ACKs are contained\n     * in a packet (see commit 37aa1ba5 in Softether) */\n    if (session->tls_wrap.mode == TLS_WRAP_NONE && !session->opt->server\n        && !(session->opt->crypto_flags & CO_USE_TLS_KEY_MATERIAL_EXPORT))\n    {\n        max_ack = min_int(max_ack, 4);\n    }\n\n    ASSERT(link_socket_actual_defined(&ks->remote_addr));\n    ASSERT(reliable_ack_write(ks->rec_ack, ks->lru_acks, buf, &ks->session_id_remote, max_ack,\n                              prepend_ack));\n\n    msg(D_TLS_DEBUG, \"%s(): %s\", __func__, packet_opcode_name(opcode));\n\n    tls_wrap_control(tls_session_get_tls_wrap(session, ks->key_id), header, buf,\n                     &session->session_id);\n\n    *to_link_addr = &ks->remote_addr;\n}\n\nbool\nread_control_auth(struct buffer *buf, struct tls_wrap_ctx *ctx,\n                  const struct link_socket_actual *from, const struct tls_options *opt,\n                  bool initial_packet)\n{\n    struct gc_arena gc = gc_new();\n    bool ret = false;\n\n    const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT;\n    if ((opcode == P_CONTROL_HARD_RESET_CLIENT_V3 || opcode == P_CONTROL_WKC_V1)\n        && !tls_crypt_v2_extract_client_key(buf, ctx, opt, initial_packet))\n    {\n        msg(D_TLS_ERRORS, \"TLS Error: can not extract tls-crypt-v2 client key from %s\",\n            print_link_socket_actual(from, &gc));\n        goto cleanup;\n    }\n\n    if (ctx->mode == TLS_WRAP_AUTH)\n    {\n        struct buffer null = clear_buf();\n\n        /* move the hmac record to the front of the packet */\n        if (!swap_hmac(buf, &ctx->opt, true))\n        {\n            msg(D_TLS_ERRORS, \"TLS Error: cannot locate HMAC in incoming packet from %s\",\n                print_link_socket_actual(from, &gc));\n            gc_free(&gc);\n            return false;\n        }\n\n        /* authenticate only (no decrypt) and remove the hmac record\n         * from the head of the buffer */\n        openvpn_decrypt(buf, null, &ctx->opt, NULL, BPTR(buf));\n        if (!buf->len)\n        {\n            msg(D_TLS_ERRORS, \"TLS Error: incoming packet authentication failed from %s\",\n                print_link_socket_actual(from, &gc));\n            goto cleanup;\n        }\n    }\n    else if (ctx->mode == TLS_WRAP_CRYPT)\n    {\n        struct buffer tmp = alloc_buf_gc(buf_forward_capacity_total(buf), &gc);\n        if (!tls_crypt_unwrap(buf, &tmp, &ctx->opt))\n        {\n            msg(D_TLS_ERRORS, \"TLS Error: tls-crypt unwrapping failed from %s\",\n                print_link_socket_actual(from, &gc));\n            goto cleanup;\n        }\n        ASSERT(buf_init(buf, buf->offset));\n        ASSERT(buf_copy(buf, &tmp));\n        buf_clear(&tmp);\n    }\n    else if (ctx->tls_crypt_v2_server_key.cipher)\n    {\n        /* If tls-crypt-v2 is enabled, require *some* wrapping */\n        msg(D_TLS_ERRORS, \"TLS Error: could not determine wrapping from %s\",\n            print_link_socket_actual(from, &gc));\n        /* TODO Do we want to support using tls-crypt-v2 and no control channel\n         * wrapping at all simultaneously?  That would allow server admins to\n         * upgrade clients one-by-one without running a second instance, but we\n         * should not enable it by default because it breaks DoS-protection.\n         * So, add something like --tls-crypt-v2-allow-insecure-fallback ? */\n        goto cleanup;\n    }\n\n    if (ctx->mode == TLS_WRAP_NONE || ctx->mode == TLS_WRAP_AUTH)\n    {\n        /* advance buffer pointer past opcode & session_id since our caller\n         * already read it */\n        buf_advance(buf, SID_SIZE + 1);\n    }\n\n    ret = true;\ncleanup:\n    gc_free(&gc);\n    return ret;\n}\n\nvoid\nfree_tls_pre_decrypt_state(struct tls_pre_decrypt_state *state)\n{\n    free_buf(&state->newbuf);\n    free_buf(&state->tls_wrap_tmp.tls_crypt_v2_metadata);\n    if (state->tls_wrap_tmp.cleanup_key_ctx)\n    {\n        free_key_ctx_bi(&state->tls_wrap_tmp.opt.key_ctx_bi);\n        secure_memzero(&state->tls_wrap_tmp.original_wrap_keydata, sizeof(state->tls_wrap_tmp.original_wrap_keydata));\n    }\n}\n\n/*\n * This function is similar to tls_pre_decrypt, except it is called\n * when we are in server mode and receive an initial incoming\n * packet.  Note that we don't modify any state in our parameter\n * objects except state.  The purpose is solely to\n * determine whether we should generate a client instance\n * object, in which case true is returned.\n *\n * This function is essentially the first-line HMAC firewall\n * on the UDP port listener in --mode server mode.\n */\nenum first_packet_verdict\ntls_pre_decrypt_lite(const struct tls_auth_standalone *tas, struct tls_pre_decrypt_state *state,\n                     const struct link_socket_actual *from, const struct buffer *buf)\n{\n    struct gc_arena gc = gc_new();\n    /* A packet needs to have at least an opcode and session id */\n    if (BLENZ(buf) < 1 + SID_SIZE)\n    {\n        dmsg(D_TLS_STATE_ERRORS, \"TLS State Error: Too short packet (length  %d) received from %s\",\n             buf->len, print_link_socket_actual(from, &gc));\n        goto error;\n    }\n\n    /* get opcode and key ID */\n    uint8_t pkt_firstbyte = *BPTR(buf);\n    int op = pkt_firstbyte >> P_OPCODE_SHIFT;\n    int key_id = pkt_firstbyte & P_KEY_ID_MASK;\n\n    /* this packet is from an as-yet untrusted source, so\n     * scrutinize carefully */\n\n    /* Allow only the reset packet or the first packet of the actual handshake. */\n    if (op != P_CONTROL_HARD_RESET_CLIENT_V2 && op != P_CONTROL_HARD_RESET_CLIENT_V3\n        && op != P_CONTROL_V1 && op != P_CONTROL_WKC_V1 && op != P_ACK_V1)\n    {\n        /*\n         * This can occur due to bogus data or DoS packets.\n         */\n        dmsg(D_TLS_STATE_ERRORS, \"TLS State Error: No TLS state for client %s, opcode=%d\",\n             print_link_socket_actual(from, &gc), op);\n        goto error;\n    }\n\n    if (key_id != 0)\n    {\n        dmsg(D_TLS_STATE_ERRORS,\n             \"TLS State Error: Unknown key ID (%d) received from %s -- 0 was expected\", key_id,\n             print_link_socket_actual(from, &gc));\n        goto error;\n    }\n\n    /* read peer session id, we do this at this point since\n     * read_control_auth will skip over it */\n    struct buffer tmp = *buf;\n    buf_advance(&tmp, 1);\n    if (!session_id_read(&state->peer_session_id, &tmp)\n        || !session_id_defined(&state->peer_session_id))\n    {\n        msg(D_TLS_ERRORS, \"TLS Error: session-id not found in packet from %s\",\n            print_link_socket_actual(from, &gc));\n        goto error;\n    }\n\n    state->newbuf = clone_buf(buf);\n    state->tls_wrap_tmp = tas->tls_wrap;\n\n    /* HMAC test and unwrapping the encrypted part of the control message\n     * into newbuf or just setting newbuf to point to the start of control\n     * message */\n    bool status = read_control_auth(&state->newbuf, &state->tls_wrap_tmp, from, NULL, true);\n\n    if (!status)\n    {\n        goto error;\n    }\n\n    /*\n     * At this point, if --tls-auth is being used, we know that\n     * the packet has passed the HMAC test, but we don't know if\n     * it is a replay yet.  We will attempt to defeat replays\n     * by not advancing to the S_START state until we\n     * receive an ACK from our first reply to the client\n     * that includes an HMAC of our randomly generated 64 bit\n     * session ID.\n     *\n     * On the other hand if --tls-auth is not being used, we\n     * will proceed to begin the TLS authentication\n     * handshake with only cursory integrity checks having\n     * been performed, since we will be leaving the task\n     * of authentication solely up to TLS.\n     */\n    gc_free(&gc);\n    if (op == P_CONTROL_V1)\n    {\n        return VERDICT_VALID_CONTROL_V1;\n    }\n    else if (op == P_ACK_V1)\n    {\n        return VERDICT_VALID_ACK_V1;\n    }\n    else if (op == P_CONTROL_HARD_RESET_CLIENT_V3)\n    {\n        return VERDICT_VALID_RESET_V3;\n    }\n    else if (op == P_CONTROL_WKC_V1)\n    {\n        return VERDICT_VALID_WKC_V1;\n    }\n    else\n    {\n        return VERDICT_VALID_RESET_V2;\n    }\n\nerror:\n    tls_clear_error();\n    gc_free(&gc);\n    return VERDICT_INVALID;\n}\n\n\nstruct buffer\ntls_reset_standalone(struct tls_wrap_ctx *ctx, struct tls_auth_standalone *tas,\n                     struct session_id *own_sid, struct session_id *remote_sid, uint8_t header,\n                     bool request_resend_wkc)\n{\n    /* Copy buffer here to point at the same data but allow tls_wrap_control\n     * to potentially change buf to point to another buffer without\n     * modifying the buffer in tas */\n    struct buffer buf = tas->workbuf;\n    ASSERT(buf_init(&buf, tas->frame.buf.headroom));\n\n    /* Reliable ACK structure */\n    /* Length of the ACK structure - 1 ACK */\n    buf_write_u8(&buf, 1);\n\n    /* ACKed packet - first packet's id is always 0 */\n    buf_write_u32(&buf, 0);\n\n    /* Remote session id */\n    buf_write(&buf, remote_sid->id, SID_SIZE);\n\n    /* Packet ID of our own packet: Our reset packet is always using\n     * packet id 0 since it is the first packet */\n    packet_id_type net_pid = htonpid(0);\n\n    ASSERT(buf_write(&buf, &net_pid, sizeof(net_pid)));\n\n    /* Add indication for tls-crypt-v2 to resend the WKc with the reply */\n    if (request_resend_wkc)\n    {\n        buf_write_u16(&buf, TLV_TYPE_EARLY_NEG_FLAGS); /* TYPE: flags */\n        buf_write_u16(&buf, sizeof(uint16_t));\n        buf_write_u16(&buf, EARLY_NEG_FLAG_RESEND_WKC);\n    }\n\n    /* Add tls-auth/tls-crypt wrapping, this might replace buf with\n     * ctx->work */\n    tls_wrap_control(ctx, header, &buf, own_sid);\n\n    return buf;\n}\n\nhmac_ctx_t *\nsession_id_hmac_init(void)\n{\n    /* We assume that SHA256 is always available */\n    ASSERT(md_valid(\"SHA256\"));\n    hmac_ctx_t *hmac_ctx = hmac_ctx_new();\n\n    uint8_t key[SHA256_DIGEST_LENGTH];\n    ASSERT(rand_bytes(key, sizeof(key)));\n\n    hmac_ctx_init(hmac_ctx, key, \"SHA256\");\n    return hmac_ctx;\n}\n\nstruct session_id\ncalculate_session_id_hmac(struct session_id client_sid, const struct openvpn_sockaddr *from,\n                          hmac_ctx_t *hmac, int handwindow, int offset)\n{\n    union\n    {\n        uint8_t hmac_result[SHA256_DIGEST_LENGTH];\n        struct session_id sid;\n    } result;\n\n    /* Get the valid time quantisation for our hmac,\n     * we divide time by handwindow/2 and allow the previous\n     * and future session time if specified by offset */\n    uint32_t session_id_time = ntohl((uint32_t)(now / ((handwindow + 1) / 2) + offset));\n\n    hmac_ctx_reset(hmac);\n    /* We do not care about endian here since it does not need to be\n     * portable */\n    hmac_ctx_update(hmac, (const uint8_t *)&session_id_time, sizeof(session_id_time));\n\n    /* add client IP and port */\n    switch (from->addr.sa.sa_family)\n    {\n        case AF_INET:\n            hmac_ctx_update(hmac, (const uint8_t *)&from->addr.in4, sizeof(struct sockaddr_in));\n            break;\n\n        case AF_INET6:\n            hmac_ctx_update(hmac, (const uint8_t *)&from->addr.in6, sizeof(struct sockaddr_in6));\n            break;\n    }\n\n    /* add session id of client */\n    hmac_ctx_update(hmac, client_sid.id, SID_SIZE);\n\n    hmac_ctx_final(hmac, result.hmac_result);\n\n    return result.sid;\n}\n\nbool\ncheck_session_hmac_and_pkt_id(struct tls_pre_decrypt_state *state,\n                              const struct openvpn_sockaddr *from,\n                              hmac_ctx_t *hmac,\n                              int handwindow,\n                              bool pkt_is_ack)\n{\n    if (!from)\n    {\n        return false;\n    }\n\n    struct buffer buf = state->newbuf;\n    struct reliable_ack ack;\n\n    if (!reliable_ack_parse(&buf, &ack, &state->server_session_id))\n    {\n        return false;\n    }\n\n    /* Check if the packet ID of the packet or ACKED packet  is <= 1 */\n    for (int i = 0; i < ack.len; i++)\n    {\n        /* This packet ACKs a packet that has a higher packet id than the\n         * ones expected in the three-way handshake, consider it as invalid\n         * for the session */\n        if (ack.packet_id[i] > 1)\n        {\n            return false;\n        }\n    }\n\n    if (!pkt_is_ack)\n    {\n        packet_id_type message_id;\n        /* Extract the packet ID from the packet */\n        if (!reliable_ack_read_packet_id(&buf, &message_id))\n        {\n            return false;\n        }\n\n        /* similar check. Anything larger than 1 is not considered part of the\n         * three-way handshake */\n        if (message_id > 1)\n        {\n            return false;\n        }\n    }\n\n\n    /* check adjacent timestamps too, the handwindow is split in 2 for the\n     * offset, so we check the current timeslot and the two before that */\n    for (int offset = -2; offset <= 0; offset++)\n    {\n        struct session_id expected_id =\n            calculate_session_id_hmac(state->peer_session_id, from, hmac, handwindow, offset);\n\n        if (memcmp_constant_time(&expected_id, &state->server_session_id, SID_SIZE) == 0)\n        {\n            return true;\n        }\n    }\n    return false;\n}\n\nstruct buffer\nextract_command_buffer(struct buffer *buf, struct gc_arena *gc)\n{\n    /* commands on the control channel are seperated by 0x00 bytes.\n     * cmdlen does not include the 0 byte of the string */\n    int cmdlen = (int)strnlen(BSTR(buf), BLENZ(buf));\n\n    if (cmdlen >= BLEN(buf))\n    {\n        buf_advance(buf, cmdlen);\n        /* Return empty buffer */\n        struct buffer empty = { 0 };\n        return empty;\n    }\n\n    /* include the NUL byte and ensure NUL termination */\n    cmdlen += 1;\n\n    /* Construct a buffer that only holds the current command and\n     * its closing NUL byte */\n    struct buffer cmdbuf = alloc_buf_gc(cmdlen, gc);\n    buf_write(&cmdbuf, BPTR(buf), cmdlen);\n\n    /* Remove \\r and \\n at the end of the buffer to avoid\n     * problems with scripts and other that add extra \\r and \\n */\n    buf_chomp(&cmdbuf);\n\n    /* check we have only printable characters or null byte in the\n     * command string and no newlines */\n    if (!string_check_buf(&cmdbuf, CC_PRINT | CC_NULL, CC_CRLF))\n    {\n        msg(D_PUSH_ERRORS, \"WARNING: Received control with invalid characters: %s\",\n            format_hex(BPTR(&cmdbuf), BLEN(&cmdbuf), 256, gc));\n        cmdbuf.len = 0;\n    }\n\n    buf_advance(buf, cmdlen);\n    return cmdbuf;\n}\n"
  },
  {
    "path": "src/openvpn/ssl_pkt.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * SSL control channel wrap/unwrap and decode functions.\n * This file (and its .c file) is designed to to be included in units/etc without\n * pulling in a lot of dependencies.\n */\n\n#ifndef SSL_PKT_H\n#define SSL_PKT_H\n\n#include \"buffer.h\"\n#include \"ssl_backend.h\"\n#include \"ssl_common.h\"\n\n/* packet opcode (high 5 bits) and key-id (low 3 bits) are combined in one byte */\n#define P_KEY_ID_MASK  0x07\n#define P_OPCODE_SHIFT 3\n\n/* packet opcodes -- the V1 is intended to allow protocol changes in the future */\n#define P_CONTROL_HARD_RESET_CLIENT_V1 1 /* initial key from client, forget previous state */\n#define P_CONTROL_HARD_RESET_SERVER_V1 2 /* initial key from server, forget previous state */\n#define P_CONTROL_SOFT_RESET_V1        3 /* new key, graceful transition from old to new key */\n#define P_CONTROL_V1                   4 /* control channel packet (usually TLS ciphertext) */\n#define P_ACK_V1                       5 /* acknowledgement for packets received */\n#define P_DATA_V1                      6 /* data channel packet */\n#define P_DATA_V2                      9 /* data channel packet with peer-id */\n\n/* indicates key_method >= 2 */\n#define P_CONTROL_HARD_RESET_CLIENT_V2 7 /* initial key from client, forget previous state */\n#define P_CONTROL_HARD_RESET_SERVER_V2 8 /* initial key from server, forget previous state */\n\n/* indicates key_method >= 2 and client-specific tls-crypt key */\n#define P_CONTROL_HARD_RESET_CLIENT_V3 10 /* initial key from client, forget previous state */\n\n/* Variant of P_CONTROL_V1 but with appended wrapped key\n * like P_CONTROL_HARD_RESET_CLIENT_V3 */\n#define P_CONTROL_WKC_V1 11\n\n/* define the range of legal opcodes\n * Since we do no longer support key-method 1 we consider\n * the v1 op codes invalid */\n#define P_FIRST_OPCODE 3\n#define P_LAST_OPCODE  11\n\n/*\n * Define number of buffers for send and receive in the reliability layer.\n */\n#define TLS_RELIABLE_N_SEND_BUFFERS 6 /* also window size for reliability layer */\n#define TLS_RELIABLE_N_REC_BUFFERS  12\n\n/*\n * Used in --mode server mode to check tls-auth signature on initial\n * packets received from new clients.\n */\nstruct tls_auth_standalone\n{\n    struct tls_wrap_ctx tls_wrap;\n    struct buffer workbuf;\n    struct frame frame;\n};\n\nenum first_packet_verdict\n{\n    /** This packet is a valid reset packet from the peer (all but tls-crypt-v2) */\n    VERDICT_VALID_RESET_V2,\n    /** This is a valid v3 reset (tls-crypt-v2) */\n    VERDICT_VALID_RESET_V3,\n    /** This packet is a valid control packet from the peer */\n    VERDICT_VALID_CONTROL_V1,\n    /** This packet is a valid ACK control packet from the peer,\n     * i.e. it has a valid session id hmac in it */\n    VERDICT_VALID_ACK_V1,\n    /** The packet is a valid control packet with appended wrapped client key */\n    VERDICT_VALID_WKC_V1,\n    /** the packet failed on of the various checks */\n    VERDICT_INVALID\n};\n\n/**\n * struct that stores the temporary data for the tls lite decrypt\n * functions\n */\nstruct tls_pre_decrypt_state\n{\n    struct tls_wrap_ctx tls_wrap_tmp;\n    struct buffer newbuf;\n    struct session_id peer_session_id;\n    struct session_id server_session_id;\n};\n\nvoid free_tls_pre_decrypt_state(struct tls_pre_decrypt_state *state);\n\n/**\n * Inspect an incoming packet for which no VPN tunnel is active, and\n * determine whether a new VPN tunnel should be created.\n * @ingroup data_crypto\n *\n * This function receives the initial incoming packet from a client that\n * wishes to establish a new VPN tunnel, and determines the packet is a\n * valid initial packet.  It is only used when OpenVPN is running in\n * server mode.\n *\n * The tests performed by this function are whether the packet's opcode is\n * correct for establishing a new VPN tunnel, whether its key ID is 0, and\n * whether its size is not too large.  This function also performs the\n * initial HMAC firewall test, if configured to do so.\n *\n * The incoming packet and the local VPN tunnel state are not modified by\n * this function.  Its sole purpose is to inspect the packet and determine\n * whether a new VPN tunnel should be created.  If so, that new VPN tunnel\n * instance will handle processing of the packet.\n *\n * This function is only used in the UDP p2mp server code path\n *\n * @param[in] tas    The standalone TLS authentication setting structure for\n *     this process.\n * @param[out] state The state struct to store information in.\n * @param[in] from   The source address of the packet.\n * @param[in] buf    buffer structure containing the incoming packet.\n *\n * @return\n * @li True if the packet is valid and a new VPN tunnel should be created\n *     for this client.\n * @li False if the packet is not valid, did not pass the HMAC firewall\n *     test, or some other error occurred.\n */\nenum first_packet_verdict tls_pre_decrypt_lite(const struct tls_auth_standalone *tas,\n                                               struct tls_pre_decrypt_state *state,\n                                               const struct link_socket_actual *from,\n                                               const struct buffer *buf);\n\n/* Creates an SHA256 HMAC context with a random key that is used for the\n * session id.\n *\n * We do not support loading this from a config file since continuing session\n * between restarts of OpenVPN has never been supported and that includes\n * early session setup.\n */\nhmac_ctx_t *session_id_hmac_init(void);\n\n/**\n * Calculates the HMAC based server session id based on a client session id\n * and socket addr.\n *\n * @param client_sid    session id of the client\n * @param from          link_socket from the client\n * @param hmac          the hmac context to use for the calculation\n * @param handwindow    the quantisation of the current time\n * @param offset        offset to 'now' to use\n * @return              the expected server session id\n */\nstruct session_id calculate_session_id_hmac(struct session_id client_sid,\n                                            const struct openvpn_sockaddr *from, hmac_ctx_t *hmac,\n                                            int handwindow, int offset);\n\n/**\n * Checks if a control packet has a correct HMAC server session id\n *\n * This will also consider packets that have a packet id higher\n * than 1 or ack packets higher than 1 to be invalid as they are\n * not part of the initial three way handshake of OpenVPN and should\n * not create a new connection.\n *\n * @param state         session information\n * @param from          link_socket from the client\n * @param hmac          the hmac context to use for the calculation\n * @param handwindow    the quantisation of the current time\n * @param pkt_is_ack    the packet being checked is a P_ACK_V1\n * @return              the expected server session id\n */\nbool check_session_hmac_and_pkt_id(struct tls_pre_decrypt_state *state, const struct openvpn_sockaddr *from,\n                                   hmac_ctx_t *hmac, int handwindow, bool pkt_is_ack);\n\n/*\n * Write a control channel authentication record.\n */\nvoid write_control_auth(struct tls_session *session, struct key_state *ks, struct buffer *buf,\n                        struct link_socket_actual **to_link_addr, int opcode, int max_ack,\n                        bool prepend_ack);\n\n\n/**\n * Read a control channel authentication record.\n * @param buf               buffer that holds the incoming packet\n * @param ctx               control channel security context\n * @param from              incoming link socket address\n * @param opt               tls options struct for the session\n * @param initial_packet    whether this is the initial packet for the connection\n * @return                  if the packet was successfully processed\n */\nbool read_control_auth(struct buffer *buf, struct tls_wrap_ctx *ctx,\n                       const struct link_socket_actual *from, const struct tls_options *opt,\n                       bool initial_packet);\n\n\n/**\n * This function creates a reset packet using the information\n * from the tls pre decrypt state.\n *\n */\nstruct buffer tls_reset_standalone(struct tls_wrap_ctx *ctx, struct tls_auth_standalone *tas,\n                                   struct session_id *own_sid, struct session_id *remote_sid,\n                                   uint8_t header, bool request_resend_wkc);\n\n\n/**\n * Extracts a control channel message from buf and adjusts the size of\n * buf after the message has been extracted\n * @param buf   The buffer the message should be extracted from\n * @param gc    gc_arena to be used for the returned buffer and displaying\n *              diagnostic messages\n * @return      A buffer with a control channel message or a buffer with\n *              with length 0 if there is no message or the message has\n *              invalid characters.\n */\nstruct buffer extract_command_buffer(struct buffer *buf, struct gc_arena *gc);\n\nstatic inline const char *\npacket_opcode_name(int op)\n{\n    switch (op)\n    {\n        case P_CONTROL_HARD_RESET_CLIENT_V1:\n            return \"P_CONTROL_HARD_RESET_CLIENT_V1\";\n\n        case P_CONTROL_HARD_RESET_SERVER_V1:\n            return \"P_CONTROL_HARD_RESET_SERVER_V1\";\n\n        case P_CONTROL_HARD_RESET_CLIENT_V2:\n            return \"P_CONTROL_HARD_RESET_CLIENT_V2\";\n\n        case P_CONTROL_HARD_RESET_SERVER_V2:\n            return \"P_CONTROL_HARD_RESET_SERVER_V2\";\n\n        case P_CONTROL_HARD_RESET_CLIENT_V3:\n            return \"P_CONTROL_HARD_RESET_CLIENT_V3\";\n\n        case P_CONTROL_SOFT_RESET_V1:\n            return \"P_CONTROL_SOFT_RESET_V1\";\n\n        case P_CONTROL_V1:\n            return \"P_CONTROL_V1\";\n\n        case P_CONTROL_WKC_V1:\n            return \"P_CONTROL_WKC_V1\";\n\n        case P_ACK_V1:\n            return \"P_ACK_V1\";\n\n        case P_DATA_V1:\n            return \"P_DATA_V1\";\n\n        case P_DATA_V2:\n            return \"P_DATA_V2\";\n\n        default:\n            return \"P_???\";\n    }\n}\n\n/**\n * Determines if the current session should use the renegotiation tls wrap\n * struct instead the normal one and returns it.\n *\n * @param session\n * @param key_id    key_id of the received/or to be send packet\n * @return\n */\nstatic inline struct tls_wrap_ctx *\ntls_session_get_tls_wrap(struct tls_session *session, int key_id)\n{\n    /* OpenVPN has the hardcoded assumption in its protocol that\n     * key-id 0 is always first session and renegotiations use key-id\n     * 1 to 7 and wrap around to 1 after that. So key-id > 0 is equivalent\n     * to \"this is a renegotiation\"\n     */\n    if (key_id > 0 && session->tls_wrap_reneg.mode == TLS_WRAP_CRYPT)\n    {\n        return &session->tls_wrap_reneg;\n    }\n    else\n    {\n        return &session->tls_wrap;\n    }\n}\n\n/* initial packet id (instead of 0) that indicates that the peer supports\n * early protocol negotiation. This will make the packet id turn a bit faster\n * but the network time part of the packet id takes care of that. And\n * this is also a rather theoretical scenario as it still needs more than\n * 2^31 control channel packets to happen */\n#define EARLY_NEG_MASK  0xff000000\n#define EARLY_NEG_START 0x0f000000\n\n\n/* Early negotiation that part of the server response in the RESET_V2 packet.\n * Since clients that announce early negotiation support will treat the payload\n * of reset packets special and parse it as TLV messages.\n * as TLV (type, length, value) */\n#define TLV_TYPE_EARLY_NEG_FLAGS  0x0001\n#define EARLY_NEG_FLAG_RESEND_WKC 0x0001\n#endif /* ifndef SSL_PKT_H */\n"
  },
  {
    "path": "src/openvpn/ssl_util.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"ssl_util.h\"\n\nchar *\nextract_var_peer_info(const char *peer_info, const char *var, struct gc_arena *gc)\n{\n    if (!peer_info)\n    {\n        return NULL;\n    }\n\n    const char *var_start = strstr(peer_info, var);\n    if (!var_start)\n    {\n        /* variable not found in peer info */\n        return NULL;\n    }\n\n    var_start += strlen(var);\n    const char *var_end = strstr(var_start, \"\\n\");\n    if (!var_end)\n    {\n        /* var is at end of the peer_info list and no '\\n' follows */\n        var_end = var_start + strlen(var_start);\n    }\n\n    char *var_value = string_alloc(var_start, gc);\n    /* NULL terminate the copy at the right position */\n    var_value[var_end - var_start] = '\\0';\n    return var_value;\n}\n\nunsigned int\nextract_iv_proto(const char *peer_info)\n{\n    const char *optstr = peer_info ? strstr(peer_info, \"IV_PROTO=\") : NULL;\n    if (optstr)\n    {\n        int proto = 0;\n        int r = sscanf(optstr, \"IV_PROTO=%d\", &proto);\n        if (r == 1 && proto > 0)\n        {\n            return proto;\n        }\n    }\n    return 0;\n}\n\nconst char *\noptions_string_compat_lzo(const char *options, struct gc_arena *gc)\n{\n    /* Example string without and with comp-lzo, i.e. input/output of this function */\n    /* w/o comp: 'V4,dev-type tun,link-mtu 1457,tun-mtu 1400,proto UDPv4,auth SHA1,keysize\n     * 128,key-method 2,tls-server' */\n    /* comp-lzo: 'V4,dev-type tun,link-mtu 1458,tun-mtu 1400,proto UDPv4,comp-lzo,auth SHA1,keysize\n     * 128,key-method 2,tls-server' */\n\n    /* Note: since this function is used only in a very limited scope it makes\n     * assumptions how the string looks. Since we locally generated the string\n     * we can make these assumptions */\n\n    /* Check that the link-mtu string is in options */\n    const char *tmp = strstr(options, \",link-mtu\");\n    if (!tmp)\n    {\n        return options;\n    }\n\n    /* Get old link_mtu size */\n    int link_mtu;\n    if (sscanf(tmp, \",link-mtu %d,\", &link_mtu) != 1 || link_mtu < 100 || link_mtu > 9900)\n    {\n        return options;\n    }\n\n    /* 1 byte for the possibility of 999 to 1000 and 1 byte for the null\n     * terminator */\n    struct buffer buf = alloc_buf_gc(strlen(options) + strlen(\",comp-lzo\") + 2, gc);\n\n    buf_write(&buf, options, (int)(tmp - options));\n\n    /* Increase link-mtu by one for the comp-lzo opcode */\n    buf_printf(&buf, \",link-mtu %d\", link_mtu + 1);\n\n    tmp += strlen(\",link-mtu \") + (link_mtu < 1000 ? 3 : 4);\n\n    buf_printf(&buf, \"%s,comp-lzo\", tmp);\n\n    return BSTR(&buf);\n}\n\n/**\n * SSL/TLS Cipher suite name translation table\n */\nstatic const tls_cipher_name_pair tls_cipher_name_translation_table[] = {\n    { \"ADH-SEED-SHA\", \"TLS-DH-anon-WITH-SEED-CBC-SHA\" },\n    { \"AES128-GCM-SHA256\", \"TLS-RSA-WITH-AES-128-GCM-SHA256\" },\n    { \"AES128-SHA256\", \"TLS-RSA-WITH-AES-128-CBC-SHA256\" },\n    { \"AES128-SHA\", \"TLS-RSA-WITH-AES-128-CBC-SHA\" },\n    { \"AES256-GCM-SHA384\", \"TLS-RSA-WITH-AES-256-GCM-SHA384\" },\n    { \"AES256-SHA256\", \"TLS-RSA-WITH-AES-256-CBC-SHA256\" },\n    { \"AES256-SHA\", \"TLS-RSA-WITH-AES-256-CBC-SHA\" },\n    { \"CAMELLIA128-SHA256\", \"TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256\" },\n    { \"CAMELLIA128-SHA\", \"TLS-RSA-WITH-CAMELLIA-128-CBC-SHA\" },\n    { \"CAMELLIA256-SHA256\", \"TLS-RSA-WITH-CAMELLIA-256-CBC-SHA256\" },\n    { \"CAMELLIA256-SHA\", \"TLS-RSA-WITH-CAMELLIA-256-CBC-SHA\" },\n    { \"DES-CBC3-SHA\", \"TLS-RSA-WITH-3DES-EDE-CBC-SHA\" },\n    { \"DES-CBC-SHA\", \"TLS-RSA-WITH-DES-CBC-SHA\" },\n    { \"DH-DSS-SEED-SHA\", \"TLS-DH-DSS-WITH-SEED-CBC-SHA\" },\n    { \"DHE-DSS-AES128-GCM-SHA256\", \"TLS-DHE-DSS-WITH-AES-128-GCM-SHA256\" },\n    { \"DHE-DSS-AES128-SHA256\", \"TLS-DHE-DSS-WITH-AES-128-CBC-SHA256\" },\n    { \"DHE-DSS-AES128-SHA\", \"TLS-DHE-DSS-WITH-AES-128-CBC-SHA\" },\n    { \"DHE-DSS-AES256-GCM-SHA384\", \"TLS-DHE-DSS-WITH-AES-256-GCM-SHA384\" },\n    { \"DHE-DSS-AES256-SHA256\", \"TLS-DHE-DSS-WITH-AES-256-CBC-SHA256\" },\n    { \"DHE-DSS-AES256-SHA\", \"TLS-DHE-DSS-WITH-AES-256-CBC-SHA\" },\n    { \"DHE-DSS-CAMELLIA128-SHA256\", \"TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA256\" },\n    { \"DHE-DSS-CAMELLIA128-SHA\", \"TLS-DHE-DSS-WITH-CAMELLIA-128-CBC-SHA\" },\n    { \"DHE-DSS-CAMELLIA256-SHA256\", \"TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA256\" },\n    { \"DHE-DSS-CAMELLIA256-SHA\", \"TLS-DHE-DSS-WITH-CAMELLIA-256-CBC-SHA\" },\n    { \"DHE-DSS-SEED-SHA\", \"TLS-DHE-DSS-WITH-SEED-CBC-SHA\" },\n    { \"DHE-RSA-AES128-GCM-SHA256\", \"TLS-DHE-RSA-WITH-AES-128-GCM-SHA256\" },\n    { \"DHE-RSA-AES128-SHA256\", \"TLS-DHE-RSA-WITH-AES-128-CBC-SHA256\" },\n    { \"DHE-RSA-AES128-SHA\", \"TLS-DHE-RSA-WITH-AES-128-CBC-SHA\" },\n    { \"DHE-RSA-AES256-GCM-SHA384\", \"TLS-DHE-RSA-WITH-AES-256-GCM-SHA384\" },\n    { \"DHE-RSA-AES256-SHA256\", \"TLS-DHE-RSA-WITH-AES-256-CBC-SHA256\" },\n    { \"DHE-RSA-AES256-SHA\", \"TLS-DHE-RSA-WITH-AES-256-CBC-SHA\" },\n    { \"DHE-RSA-CAMELLIA128-SHA256\", \"TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256\" },\n    { \"DHE-RSA-CAMELLIA128-SHA\", \"TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA\" },\n    { \"DHE-RSA-CAMELLIA256-SHA256\", \"TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256\" },\n    { \"DHE-RSA-CAMELLIA256-SHA\", \"TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA\" },\n    { \"DHE-RSA-CHACHA20-POLY1305\", \"TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256\" },\n    { \"DHE-RSA-SEED-SHA\", \"TLS-DHE-RSA-WITH-SEED-CBC-SHA\" },\n    { \"DH-RSA-SEED-SHA\", \"TLS-DH-RSA-WITH-SEED-CBC-SHA\" },\n    { \"ECDH-ECDSA-AES128-GCM-SHA256\", \"TLS-ECDH-ECDSA-WITH-AES-128-GCM-SHA256\" },\n    { \"ECDH-ECDSA-AES128-SHA256\", \"TLS-ECDH-ECDSA-WITH-AES-128-CBC-SHA256\" },\n    { \"ECDH-ECDSA-AES128-SHA\", \"TLS-ECDH-ECDSA-WITH-AES-128-CBC-SHA\" },\n    { \"ECDH-ECDSA-AES256-GCM-SHA384\", \"TLS-ECDH-ECDSA-WITH-AES-256-GCM-SHA384\" },\n    { \"ECDH-ECDSA-AES256-SHA256\", \"TLS-ECDH-ECDSA-WITH-AES-256-CBC-SHA256\" },\n    { \"ECDH-ECDSA-AES256-SHA384\", \"TLS-ECDH-ECDSA-WITH-AES-256-CBC-SHA384\" },\n    { \"ECDH-ECDSA-AES256-SHA\", \"TLS-ECDH-ECDSA-WITH-AES-256-CBC-SHA\" },\n    { \"ECDH-ECDSA-CAMELLIA128-SHA256\", \"TLS-ECDH-ECDSA-WITH-CAMELLIA-128-CBC-SHA256\" },\n    { \"ECDH-ECDSA-CAMELLIA128-SHA\", \"TLS-ECDH-ECDSA-WITH-CAMELLIA-128-CBC-SHA\" },\n    { \"ECDH-ECDSA-CAMELLIA256-SHA256\", \"TLS-ECDH-ECDSA-WITH-CAMELLIA-256-CBC-SHA256\" },\n    { \"ECDH-ECDSA-CAMELLIA256-SHA\", \"TLS-ECDH-ECDSA-WITH-CAMELLIA-256-CBC-SHA\" },\n    { \"ECDH-ECDSA-DES-CBC3-SHA\", \"TLS-ECDH-ECDSA-WITH-3DES-EDE-CBC-SHA\" },\n    { \"ECDH-ECDSA-DES-CBC-SHA\", \"TLS-ECDH-ECDSA-WITH-DES-CBC-SHA\" },\n    { \"ECDH-ECDSA-RC4-SHA\", \"TLS-ECDH-ECDSA-WITH-RC4-128-SHA\" },\n    { \"ECDHE-ECDSA-AES128-GCM-SHA256\", \"TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256\" },\n    { \"ECDHE-ECDSA-AES128-SHA256\", \"TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256\" },\n    { \"ECDHE-ECDSA-AES128-SHA384\", \"TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA384\" },\n    { \"ECDHE-ECDSA-AES128-SHA\", \"TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA\" },\n    { \"ECDHE-ECDSA-AES256-GCM-SHA384\", \"TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384\" },\n    { \"ECDHE-ECDSA-AES256-SHA256\", \"TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA256\" },\n    { \"ECDHE-ECDSA-AES256-SHA384\", \"TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384\" },\n    { \"ECDHE-ECDSA-AES256-SHA\", \"TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA\" },\n    { \"ECDHE-ECDSA-CAMELLIA128-SHA256\", \"TLS-ECDHE-ECDSA-WITH-CAMELLIA-128-CBC-SHA256\" },\n    { \"ECDHE-ECDSA-CAMELLIA128-SHA\", \"TLS-ECDHE-ECDSA-WITH-CAMELLIA-128-CBC-SHA\" },\n    { \"ECDHE-ECDSA-CAMELLIA256-SHA256\", \"TLS-ECDHE-ECDSA-WITH-CAMELLIA-256-CBC-SHA256\" },\n    { \"ECDHE-ECDSA-CAMELLIA256-SHA\", \"TLS-ECDHE-ECDSA-WITH-CAMELLIA-256-CBC-SHA\" },\n    { \"ECDHE-ECDSA-CHACHA20-POLY1305\", \"TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256\" },\n    { \"ECDHE-ECDSA-DES-CBC3-SHA\", \"TLS-ECDHE-ECDSA-WITH-3DES-EDE-CBC-SHA\" },\n    { \"ECDHE-ECDSA-DES-CBC-SHA\", \"TLS-ECDHE-ECDSA-WITH-DES-CBC-SHA\" },\n    { \"ECDHE-ECDSA-RC4-SHA\", \"TLS-ECDHE-ECDSA-WITH-RC4-128-SHA\" },\n    { \"ECDHE-RSA-AES128-GCM-SHA256\", \"TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256\" },\n    { \"ECDHE-RSA-AES128-SHA256\", \"TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256\" },\n    { \"ECDHE-RSA-AES128-SHA384\", \"TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA384\" },\n    { \"ECDHE-RSA-AES128-SHA\", \"TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA\" },\n    { \"ECDHE-RSA-AES256-GCM-SHA384\", \"TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384\" },\n    { \"ECDHE-RSA-AES256-SHA256\", \"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA256\" },\n    { \"ECDHE-RSA-AES256-SHA384\", \"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384\" },\n    { \"ECDHE-RSA-AES256-SHA\", \"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA\" },\n    { \"ECDHE-RSA-CAMELLIA128-SHA256\", \"TLS-ECDHE-RSA-WITH-CAMELLIA-128-CBC-SHA256\" },\n    { \"ECDHE-RSA-CAMELLIA128-SHA\", \"TLS-ECDHE-RSA-WITH-CAMELLIA-128-CBC-SHA\" },\n    { \"ECDHE-RSA-CAMELLIA256-SHA256\", \"TLS-ECDHE-RSA-WITH-CAMELLIA-256-CBC-SHA256\" },\n    { \"ECDHE-RSA-CAMELLIA256-SHA\", \"TLS-ECDHE-RSA-WITH-CAMELLIA-256-CBC-SHA\" },\n    { \"ECDHE-RSA-CHACHA20-POLY1305\", \"TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256\" },\n    { \"ECDHE-RSA-DES-CBC3-SHA\", \"TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA\" },\n    { \"ECDHE-RSA-DES-CBC-SHA\", \"TLS-ECDHE-RSA-WITH-DES-CBC-SHA\" },\n    { \"ECDHE-RSA-RC4-SHA\", \"TLS-ECDHE-RSA-WITH-RC4-128-SHA\" },\n    { \"ECDH-RSA-AES128-GCM-SHA256\", \"TLS-ECDH-RSA-WITH-AES-128-GCM-SHA256\" },\n    { \"ECDH-RSA-AES128-SHA256\", \"TLS-ECDH-RSA-WITH-AES-128-CBC-SHA256\" },\n    { \"ECDH-RSA-AES128-SHA384\", \"TLS-ECDH-RSA-WITH-AES-128-CBC-SHA384\" },\n    { \"ECDH-RSA-AES128-SHA\", \"TLS-ECDH-RSA-WITH-AES-128-CBC-SHA\" },\n    { \"ECDH-RSA-AES256-GCM-SHA384\", \"TLS-ECDH-RSA-WITH-AES-256-GCM-SHA384\" },\n    { \"ECDH-RSA-AES256-SHA256\", \"TLS-ECDH-RSA-WITH-AES-256-CBC-SHA256\" },\n    { \"ECDH-RSA-AES256-SHA384\", \"TLS-ECDH-RSA-WITH-AES-256-CBC-SHA384\" },\n    { \"ECDH-RSA-AES256-SHA\", \"TLS-ECDH-RSA-WITH-AES-256-CBC-SHA\" },\n    { \"ECDH-RSA-CAMELLIA128-SHA256\", \"TLS-ECDH-RSA-WITH-CAMELLIA-128-CBC-SHA256\" },\n    { \"ECDH-RSA-CAMELLIA128-SHA\", \"TLS-ECDH-RSA-WITH-CAMELLIA-128-CBC-SHA\" },\n    { \"ECDH-RSA-CAMELLIA256-SHA256\", \"TLS-ECDH-RSA-WITH-CAMELLIA-256-CBC-SHA256\" },\n    { \"ECDH-RSA-CAMELLIA256-SHA\", \"TLS-ECDH-RSA-WITH-CAMELLIA-256-CBC-SHA\" },\n    { \"ECDH-RSA-DES-CBC3-SHA\", \"TLS-ECDH-RSA-WITH-3DES-EDE-CBC-SHA\" },\n    { \"ECDH-RSA-DES-CBC-SHA\", \"TLS-ECDH-RSA-WITH-DES-CBC-SHA\" },\n    { \"ECDH-RSA-RC4-SHA\", \"TLS-ECDH-RSA-WITH-RC4-128-SHA\" },\n    { \"EDH-DSS-DES-CBC3-SHA\", \"TLS-DHE-DSS-WITH-3DES-EDE-CBC-SHA\" },\n    { \"EDH-DSS-DES-CBC-SHA\", \"TLS-DHE-DSS-WITH-DES-CBC-SHA\" },\n    { \"EDH-RSA-DES-CBC3-SHA\", \"TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA\" },\n    { \"EDH-RSA-DES-CBC-SHA\", \"TLS-DHE-RSA-WITH-DES-CBC-SHA\" },\n    { \"EXP-DES-CBC-SHA\", \"TLS-RSA-EXPORT-WITH-DES40-CBC-SHA\" },\n    { \"EXP-EDH-DSS-DES-CBC-SHA\", \"TLS-DH-DSS-EXPORT-WITH-DES40-CBC-SHA\" },\n    { \"EXP-EDH-RSA-DES-CBC-SHA\", \"TLS-DH-RSA-EXPORT-WITH-DES40-CBC-SHA\" },\n    { \"EXP-RC2-CBC-MD5\", \"TLS-RSA-EXPORT-WITH-RC2-CBC-40-MD5\" },\n    { \"EXP-RC4-MD5\", \"TLS-RSA-EXPORT-WITH-RC4-40-MD5\" },\n    { \"NULL-MD5\", \"TLS-RSA-WITH-NULL-MD5\" },\n    { \"NULL-SHA256\", \"TLS-RSA-WITH-NULL-SHA256\" },\n    { \"NULL-SHA\", \"TLS-RSA-WITH-NULL-SHA\" },\n    { \"PSK-3DES-EDE-CBC-SHA\", \"TLS-PSK-WITH-3DES-EDE-CBC-SHA\" },\n    { \"PSK-AES128-CBC-SHA\", \"TLS-PSK-WITH-AES-128-CBC-SHA\" },\n    { \"PSK-AES256-CBC-SHA\", \"TLS-PSK-WITH-AES-256-CBC-SHA\" },\n    { \"PSK-RC4-SHA\", \"TLS-PSK-WITH-RC4-128-SHA\" },\n    { \"RC4-MD5\", \"TLS-RSA-WITH-RC4-128-MD5\" },\n    { \"RC4-SHA\", \"TLS-RSA-WITH-RC4-128-SHA\" },\n    { \"SEED-SHA\", \"TLS-RSA-WITH-SEED-CBC-SHA\" },\n    { \"SRP-DSS-3DES-EDE-CBC-SHA\", \"TLS-SRP-SHA-DSS-WITH-3DES-EDE-CBC-SHA\" },\n    { \"SRP-DSS-AES-128-CBC-SHA\", \"TLS-SRP-SHA-DSS-WITH-AES-128-CBC-SHA\" },\n    { \"SRP-DSS-AES-256-CBC-SHA\", \"TLS-SRP-SHA-DSS-WITH-AES-256-CBC-SHA\" },\n    { \"SRP-RSA-3DES-EDE-CBC-SHA\", \"TLS-SRP-SHA-RSA-WITH-3DES-EDE-CBC-SHA\" },\n    { \"SRP-RSA-AES-128-CBC-SHA\", \"TLS-SRP-SHA-RSA-WITH-AES-128-CBC-SHA\" },\n    { \"SRP-RSA-AES-256-CBC-SHA\", \"TLS-SRP-SHA-RSA-WITH-AES-256-CBC-SHA\" },\n#ifdef ENABLE_CRYPTO_OPENSSL\n    /* OpenSSL-specific group names */\n    { \"DEFAULT\", \"DEFAULT\" },\n    { \"ALL\", \"ALL\" },\n    { \"HIGH\", \"HIGH\" },\n    { \"!HIGH\", \"!HIGH\" },\n    { \"MEDIUM\", \"MEDIUM\" },\n    { \"!MEDIUM\", \"!MEDIUM\" },\n    { \"LOW\", \"LOW\" },\n    { \"!LOW\", \"!LOW\" },\n    { \"ECDH\", \"ECDH\" },\n    { \"!ECDH\", \"!ECDH\" },\n    { \"ECDSA\", \"ECDSA\" },\n    { \"!ECDSA\", \"!ECDSA\" },\n    { \"EDH\", \"EDH\" },\n    { \"!EDH\", \"!EDH\" },\n    { \"EXP\", \"EXP\" },\n    { \"!EXP\", \"!EXP\" },\n    { \"RSA\", \"RSA\" },\n    { \"!RSA\", \"!RSA\" },\n    { \"kRSA\", \"kRSA\" },\n    { \"!kRSA\", \"!kRSA\" },\n    { \"SRP\", \"SRP\" },\n    { \"!SRP\", \"!SRP\" },\n#endif\n    { NULL, NULL }\n};\n\nconst tls_cipher_name_pair *\ntls_get_cipher_name_pair(const char *cipher_name, size_t len)\n{\n    const tls_cipher_name_pair *pair = tls_cipher_name_translation_table;\n\n    while (pair->openssl_name != NULL)\n    {\n        if ((strlen(pair->openssl_name) == len && 0 == memcmp(cipher_name, pair->openssl_name, len))\n            || (strlen(pair->iana_name) == len && 0 == memcmp(cipher_name, pair->iana_name, len)))\n        {\n            return pair;\n        }\n        pair++;\n    }\n\n    /* No entry found, return NULL */\n    return NULL;\n}\n\nint\nget_num_elements(const char *string, char delimiter)\n{\n    const size_t string_len = strlen(string);\n\n    ASSERT(0 != string_len);\n\n    int element_count = 1;\n    /* Get number of ciphers */\n    for (size_t i = 0; i < string_len; i++)\n    {\n        if (string[i] == delimiter)\n        {\n            element_count++;\n        }\n    }\n\n    return element_count;\n}\n"
  },
  {
    "path": "src/openvpn/ssl_util.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * SSL utility functions.\n * This file (and its .c file) is designed to to be included in units/etc\n * without pulling in a lot of dependencies.\n */\n\n#ifndef SSL_UTIL_H_\n#define SSL_UTIL_H_\n\n#include \"buffer.h\"\n\n/**\n * Extracts a variable from peer info, the returned string will be allocated\n * using the supplied gc_arena\n *\n * @param peer_info     The peer's peer_info\n * @param var           The variable *including* =, e.g. IV_CIPHERS=\n * @param gc            GC arena to allocate return value in\n *\n * @return  The content of the variable as NULL terminated string or NULL if the\n *          variable cannot be found.\n */\nchar *extract_var_peer_info(const char *peer_info, const char *var, struct gc_arena *gc);\n\n/**\n * Extracts the IV_PROTO variable and returns its value or 0\n * if it cannot be extracted.\n *\n * @param peer_info     peer info string to search for IV_PROTO\n */\nunsigned int extract_iv_proto(const char *peer_info);\n\n/**\n * Takes a locally produced OCC string for TLS server mode and modifies as\n * if the option comp-lzo was enabled. This is to send a client in\n * comp-lzo migrate mode the expected OCC string.\n *\n * Note: This function expects the string to be in the locally generated\n * format and does not accept arbitrary strings.\n *\n * @param options   the locally generated OCC string\n * @param gc        gc_arena to allocate the returned string in\n * @return          the modified string or options on error\n */\nconst char *options_string_compat_lzo(const char *options, struct gc_arena *gc);\n\n/**\n * Get a tls_cipher_name_pair containing OpenSSL and IANA names for supplied TLS cipher name\n *\n * @param cipher_name   Can be either OpenSSL or IANA cipher name\n * @return              tls_cipher_name_pair* if found, NULL otherwise\n */\ntypedef struct\n{\n    const char *openssl_name;\n    const char *iana_name;\n} tls_cipher_name_pair;\nconst tls_cipher_name_pair *tls_get_cipher_name_pair(const char *cipher_name, size_t len);\n\n/**\n * Returns the occurrences of 'delimiter' in a string +1\n * This is typically used to find out the number elements in a\n * cipher string or similar that is separated by : like\n *\n *   X25519:secp256r1:X448:secp512r1:secp384r1:brainpoolP384r1\n *\n * @param string        the string to work on\n * @param delimiter     the delimiter to count, typically ':'\n * @return              occrrences of delimiter + 1\n */\nint get_num_elements(const char *string, char delimiter);\n\n#endif /* ifndef SSL_UTIL_H_ */\n"
  },
  {
    "path": "src/openvpn/ssl_verify.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel Verification Module\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n#include <string.h>\n\n#include \"base64.h\"\n#include \"manage.h\"\n#include \"otime.h\"\n#include \"run_command.h\"\n#include \"ssl_verify.h\"\n#include \"ssl_verify_backend.h\"\n\n#ifdef ENABLE_CRYPTO_OPENSSL\n#include \"ssl_verify_openssl.h\"\n#endif\n#include \"auth_token.h\"\n#include \"push.h\"\n#include \"ssl_util.h\"\n\nstatic void\nstring_mod_remap_name(char *str)\n{\n    string_mod(str, CC_PRINT, CC_CRLF, '_');\n}\n\n/*\n * Export the untrusted IP address and port to the environment\n */\nstatic void\nsetenv_untrusted(struct tls_session *session)\n{\n    setenv_link_socket_actual(session->opt->es, \"untrusted\", &session->untrusted_addr, SA_IP_PORT);\n}\n\n/*\n * Remove authenticated state from all sessions in the given tunnel\n */\nstatic void\ntls_deauthenticate(struct tls_multi *multi)\n{\n    if (multi)\n    {\n        wipe_auth_token(multi);\n        for (int i = 0; i < TM_SIZE; ++i)\n        {\n            for (int j = 0; j < KS_SIZE; ++j)\n            {\n                multi->session[i].key[j].authenticated = KS_AUTH_FALSE;\n            }\n        }\n    }\n}\n\nvoid\nset_common_name(struct tls_session *session, const char *common_name)\n{\n    if (session->common_name)\n    {\n        free(session->common_name);\n        session->common_name = NULL;\n    }\n    if (common_name)\n    {\n        /* FIXME: Last alloc will never be freed */\n        session->common_name = string_alloc(common_name, NULL);\n    }\n    /* update common name in env */\n    setenv_str(session->opt->es, \"common_name\", common_name);\n}\n\n/*\n * Retrieve the common name for the given tunnel's active session. If the\n * common name is NULL or empty, return NULL if null is true, or \"UNDEF\" if\n * null is false.\n */\nconst char *\ntls_common_name(const struct tls_multi *multi, const bool null)\n{\n    const char *ret = NULL;\n    if (multi)\n    {\n        ret = multi->session[TM_ACTIVE].common_name;\n    }\n    if (ret && strlen(ret))\n    {\n        return ret;\n    }\n    else if (null)\n    {\n        return NULL;\n    }\n    else\n    {\n        return \"UNDEF\";\n    }\n}\n\n/*\n * Lock the common name for the given tunnel.\n */\nvoid\ntls_lock_common_name(struct tls_multi *multi)\n{\n    const char *cn = multi->session[TM_ACTIVE].common_name;\n    if (cn && !multi->locked_cn)\n    {\n        multi->locked_cn = string_alloc(cn, NULL);\n    }\n}\n\n/*\n * Lock the username for the given tunnel\n */\nstatic bool\ntls_lock_username(struct tls_multi *multi, const char *username)\n{\n    if (multi->locked_username)\n    {\n        /* If the username has been overridden, we accept both the original\n         * username and the changed username */\n        if (strcmp(username, multi->locked_username) != 0\n            && (!multi->locked_original_username\n                || strcmp(username, multi->locked_original_username) != 0))\n        {\n            msg(D_TLS_ERRORS,\n                \"TLS Auth Error: username attempted to change from '%s' to '%s' -- tunnel disabled\",\n                multi->locked_username, username);\n\n            /* disable the tunnel */\n            tls_deauthenticate(multi);\n            return false;\n        }\n    }\n    else\n    {\n        multi->locked_username = string_alloc(username, NULL);\n    }\n    return true;\n}\n\nconst char *\ntls_username(const struct tls_multi *multi, const bool null)\n{\n    const char *ret = NULL;\n    if (multi)\n    {\n        ret = multi->locked_username;\n    }\n    if (ret && strlen(ret))\n    {\n        return ret;\n    }\n    else if (null)\n    {\n        return NULL;\n    }\n    else\n    {\n        return \"UNDEF\";\n    }\n}\n\nvoid\ncert_hash_remember(struct tls_session *session, const int error_depth,\n                   const struct buffer *cert_hash)\n{\n    if (error_depth >= 0 && error_depth < MAX_CERT_DEPTH)\n    {\n        if (!session->cert_hash_set)\n        {\n            ALLOC_OBJ_CLEAR(session->cert_hash_set, struct cert_hash_set);\n        }\n        if (!session->cert_hash_set->ch[error_depth])\n        {\n            ALLOC_OBJ(session->cert_hash_set->ch[error_depth], struct cert_hash);\n        }\n\n        struct cert_hash *ch = session->cert_hash_set->ch[error_depth];\n        ASSERT(sizeof(ch->sha256_hash) == BLEN(cert_hash));\n        memcpy(ch->sha256_hash, BPTR(cert_hash), sizeof(ch->sha256_hash));\n    }\n}\n\nvoid\ncert_hash_free(struct cert_hash_set *chs)\n{\n    if (chs)\n    {\n        int i;\n        for (i = 0; i < MAX_CERT_DEPTH; ++i)\n        {\n            free(chs->ch[i]);\n        }\n        free(chs);\n    }\n}\n\nbool\ncert_hash_compare(const struct cert_hash_set *chs1, const struct cert_hash_set *chs2)\n{\n    if (chs1 && chs2)\n    {\n        int i;\n        for (i = 0; i < MAX_CERT_DEPTH; ++i)\n        {\n            const struct cert_hash *ch1 = chs1->ch[i];\n            const struct cert_hash *ch2 = chs2->ch[i];\n\n            if (!ch1 && !ch2)\n            {\n                continue;\n            }\n            else if (ch1 && ch2\n                     && !memcmp(ch1->sha256_hash, ch2->sha256_hash, sizeof(ch1->sha256_hash)))\n            {\n                continue;\n            }\n            else\n            {\n                return false;\n            }\n        }\n        return true;\n    }\n    else if (!chs1 && !chs2)\n    {\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\nstatic struct cert_hash_set *\ncert_hash_copy(const struct cert_hash_set *chs)\n{\n    struct cert_hash_set *dest = NULL;\n    if (chs)\n    {\n        int i;\n        ALLOC_OBJ_CLEAR(dest, struct cert_hash_set);\n        for (i = 0; i < MAX_CERT_DEPTH; ++i)\n        {\n            const struct cert_hash *ch = chs->ch[i];\n            if (ch)\n            {\n                ALLOC_OBJ(dest->ch[i], struct cert_hash);\n                memcpy(dest->ch[i]->sha256_hash, ch->sha256_hash, sizeof(dest->ch[i]->sha256_hash));\n            }\n        }\n    }\n    return dest;\n}\nvoid\ntls_lock_cert_hash_set(struct tls_multi *multi)\n{\n    const struct cert_hash_set *chs = multi->session[TM_ACTIVE].cert_hash_set;\n    if (chs && !multi->locked_cert_hash_set)\n    {\n        multi->locked_cert_hash_set = cert_hash_copy(chs);\n    }\n}\n\n/*\n * Returns the string associated with the given certificate type.\n */\nstatic const char *\nprint_nsCertType(int type)\n{\n    switch (type)\n    {\n        case NS_CERT_CHECK_SERVER:\n            return \"SERVER\";\n\n        case NS_CERT_CHECK_CLIENT:\n            return \"CLIENT\";\n\n        default:\n            return \"?\";\n    }\n}\n\n/*\n * Verify the peer's certificate fields.\n *\n * @param opt the tls options to verify against\n * @param peer_cert the peer's certificate\n * @param subject the peer's extracted subject name\n * @param subject the peer's extracted common name\n */\nstatic result_t\nverify_peer_cert(const struct tls_options *opt, openvpn_x509_cert_t *peer_cert, const char *subject,\n                 const char *common_name)\n{\n    /* verify certificate nsCertType */\n    if (opt->ns_cert_type != NS_CERT_CHECK_NONE)\n    {\n        if (SUCCESS == x509_verify_ns_cert_type(peer_cert, opt->ns_cert_type))\n        {\n            msg(D_HANDSHAKE, \"VERIFY OK: nsCertType=%s\", print_nsCertType(opt->ns_cert_type));\n        }\n        else\n        {\n            msg(D_HANDSHAKE, \"VERIFY nsCertType ERROR: %s, require nsCertType=%s\", subject,\n                print_nsCertType(opt->ns_cert_type));\n            return FAILURE; /* Reject connection */\n        }\n    }\n\n    /* verify certificate ku */\n    if (opt->remote_cert_ku[0] != 0)\n    {\n        if (SUCCESS == x509_verify_cert_ku(peer_cert, opt->remote_cert_ku, MAX_PARMS))\n        {\n            msg(D_HANDSHAKE, \"VERIFY KU OK\");\n        }\n        else\n        {\n            msg(D_HANDSHAKE, \"VERIFY KU ERROR\");\n            return FAILURE; /* Reject connection */\n        }\n    }\n\n    /* verify certificate eku */\n    if (opt->remote_cert_eku != NULL)\n    {\n        if (SUCCESS == x509_verify_cert_eku(peer_cert, opt->remote_cert_eku))\n        {\n            msg(D_HANDSHAKE, \"VERIFY EKU OK\");\n        }\n        else\n        {\n            msg(D_HANDSHAKE, \"VERIFY EKU ERROR\");\n            return FAILURE; /* Reject connection */\n        }\n    }\n\n    /* verify X509 name or username against --verify-x509-[user]name */\n    if (opt->verify_x509_type != VERIFY_X509_NONE)\n    {\n        if ((opt->verify_x509_type == VERIFY_X509_SUBJECT_DN\n             && strcmp(opt->verify_x509_name, subject) == 0)\n            || (opt->verify_x509_type == VERIFY_X509_SUBJECT_RDN\n                && strcmp(opt->verify_x509_name, common_name) == 0)\n            || (opt->verify_x509_type == VERIFY_X509_SUBJECT_RDN_PREFIX\n                && strncmp(opt->verify_x509_name, common_name, strlen(opt->verify_x509_name)) == 0))\n        {\n            msg(D_HANDSHAKE, \"VERIFY X509NAME OK: %s\", subject);\n        }\n        else\n        {\n            msg(D_HANDSHAKE, \"VERIFY X509NAME ERROR: %s, must be %s\", subject,\n                opt->verify_x509_name);\n            return FAILURE; /* Reject connection */\n        }\n    }\n\n    return SUCCESS;\n}\n\n/*\n * Export the subject, common_name, and raw certificate fields to the\n * environment for later verification by scripts and plugins.\n */\nstatic void\nverify_cert_set_env(struct env_set *es, openvpn_x509_cert_t *peer_cert, int cert_depth,\n                    const char *subject, const struct x509_track *x509_track)\n{\n    char envname[64];\n    char *serial = NULL;\n    struct gc_arena gc = gc_new();\n\n    /* Save X509 fields in environment */\n    if (x509_track)\n    {\n        x509_setenv_track(x509_track, es, cert_depth, peer_cert);\n    }\n    else\n    {\n        x509_setenv(es, cert_depth, peer_cert);\n    }\n\n    /* export subject name string as environmental variable */\n    snprintf(envname, sizeof(envname), \"tls_id_%d\", cert_depth);\n    setenv_str(es, envname, subject);\n\n    /* export X509 cert fingerprints */\n    {\n        struct buffer sha1 = x509_get_sha1_fingerprint(peer_cert, &gc);\n        struct buffer sha256 = x509_get_sha256_fingerprint(peer_cert, &gc);\n\n        snprintf(envname, sizeof(envname), \"tls_digest_%d\", cert_depth);\n        setenv_str(es, envname, format_hex_ex(BPTR(&sha1), BLEN(&sha1), 0, 1, \":\", &gc));\n\n        snprintf(envname, sizeof(envname), \"tls_digest_sha256_%d\", cert_depth);\n        setenv_str(es, envname, format_hex_ex(BPTR(&sha256), BLEN(&sha256), 0, 1, \":\", &gc));\n    }\n\n    /* export serial number as environmental variable */\n    serial = backend_x509_get_serial(peer_cert, &gc);\n    snprintf(envname, sizeof(envname), \"tls_serial_%d\", cert_depth);\n    setenv_str(es, envname, serial);\n\n    /* export serial number in hex as environmental variable */\n    serial = backend_x509_get_serial_hex(peer_cert, &gc);\n    snprintf(envname, sizeof(envname), \"tls_serial_hex_%d\", cert_depth);\n    setenv_str(es, envname, serial);\n\n    gc_free(&gc);\n}\n\n/**\n * Exports the certificate in \\c peer_cert into the environment and adds\n * the filname\n */\nstatic bool\nverify_cert_cert_export_env(struct env_set *es, openvpn_x509_cert_t *peer_cert,\n                            const char *pem_export_fname)\n{\n    /* export the path to the current certificate in pem file format */\n    setenv_str(es, \"peer_cert\", pem_export_fname);\n\n    return backend_x509_write_pem(peer_cert, pem_export_fname) == SUCCESS;\n}\n\nstatic void\nverify_cert_cert_delete_env(struct env_set *es, const char *pem_export_fname)\n{\n    env_set_del(es, \"peer_cert\");\n    if (pem_export_fname)\n    {\n        unlink(pem_export_fname);\n    }\n}\n\n/*\n * call --tls-verify plug-in(s)\n */\nstatic result_t\nverify_cert_call_plugin(const struct plugin_list *plugins, struct env_set *es, int cert_depth,\n                        openvpn_x509_cert_t *cert, char *subject)\n{\n    if (plugin_defined(plugins, OPENVPN_PLUGIN_TLS_VERIFY))\n    {\n        int ret;\n        struct argv argv = argv_new();\n\n        argv_printf(&argv, \"%d %s\", cert_depth, subject);\n\n        ret =\n            plugin_call_ssl(plugins, OPENVPN_PLUGIN_TLS_VERIFY, &argv, NULL, es, cert_depth, cert);\n\n        argv_free(&argv);\n\n        if (ret == OPENVPN_PLUGIN_FUNC_SUCCESS)\n        {\n            msg(D_HANDSHAKE, \"VERIFY PLUGIN OK: depth=%d, %s\", cert_depth, subject);\n        }\n        else\n        {\n            msg(D_HANDSHAKE, \"VERIFY PLUGIN ERROR: depth=%d, %s\", cert_depth, subject);\n            return FAILURE; /* Reject connection */\n        }\n    }\n    return SUCCESS;\n}\n\n/*\n * run --tls-verify script\n */\nstatic result_t\nverify_cert_call_command(const char *verify_command, struct env_set *es, int cert_depth,\n                         char *subject)\n{\n    int ret;\n    struct gc_arena gc = gc_new();\n    struct argv argv = argv_new();\n\n    setenv_str(es, \"script_type\", \"tls-verify\");\n\n    argv_parse_cmd(&argv, verify_command);\n    argv_printf_cat(&argv, \"%d %s\", cert_depth, subject);\n\n    argv_msg_prefix(D_TLS_DEBUG, &argv, \"TLS: executing verify command\");\n    ret = openvpn_run_script(&argv, es, 0, \"--tls-verify script\");\n\n    gc_free(&gc);\n    argv_free(&argv);\n\n    if (ret)\n    {\n        msg(D_HANDSHAKE, \"VERIFY SCRIPT OK: depth=%d, %s\", cert_depth, subject);\n        return SUCCESS;\n    }\n\n    msg(D_HANDSHAKE, \"VERIFY SCRIPT ERROR: depth=%d, %s\", cert_depth, subject);\n    return FAILURE; /* Reject connection */\n}\n\n/*\n * check peer cert against CRL directory\n */\nstatic result_t\nverify_check_crl_dir(const char *crl_dir, openvpn_x509_cert_t *cert, const char *subject,\n                     int cert_depth)\n{\n    result_t ret = FAILURE;\n    char fn[256];\n    int fd = -1;\n    struct gc_arena gc = gc_new();\n\n    char *serial = backend_x509_get_serial(cert, &gc);\n    if (!serial)\n    {\n        msg(D_HANDSHAKE, \"VERIFY CRL: depth=%d, %s, serial number is not available\", cert_depth,\n            subject);\n        goto cleanup;\n    }\n\n    if (!checked_snprintf(fn, sizeof(fn), \"%s%c%s\", crl_dir, PATH_SEPARATOR, serial))\n    {\n        msg(D_HANDSHAKE, \"VERIFY CRL: filename overflow\");\n        goto cleanup;\n    }\n    fd = platform_open(fn, O_RDONLY, 0);\n    if (fd >= 0)\n    {\n        msg(D_HANDSHAKE, \"VERIFY CRL: depth=%d, %s, serial=%s is revoked\", cert_depth, subject,\n            serial);\n        goto cleanup;\n    }\n\n    ret = SUCCESS;\n\ncleanup:\n\n    if (fd != -1)\n    {\n        close(fd);\n    }\n    gc_free(&gc);\n    return ret;\n}\n\nresult_t\nverify_cert(struct tls_session *session, openvpn_x509_cert_t *cert, int cert_depth)\n{\n    /* need to define these variables here so goto cleanup will always have\n     * them defined */\n    result_t ret = FAILURE;\n    struct gc_arena gc = gc_new();\n    const char *pem_export_fname = NULL;\n\n    const struct tls_options *opt = session->opt;\n    ASSERT(opt);\n\n    session->verified = false;\n\n    /* get the X509 name */\n    char *subject = x509_get_subject(cert, &gc);\n    if (!subject)\n    {\n        msg(D_TLS_ERRORS,\n            \"VERIFY ERROR: depth=%d, could not extract X509 \"\n            \"subject string from certificate\",\n            cert_depth);\n        goto cleanup;\n    }\n\n    /* enforce character class restrictions in X509 name */\n    string_mod_remap_name(subject);\n    string_replace_leading(subject, '-', '_');\n\n    /* extract the username (default is CN) */\n    struct buffer buf = alloc_buf_gc(256, &gc);\n    for (int i = 0; opt->x509_username_field[i] != NULL; i++)\n    {\n        char username[TLS_USERNAME_LEN + 1] = { 0 }; /* null-terminated */\n\n        if (SUCCESS\n            != backend_x509_get_username(username, sizeof(username), opt->x509_username_field[i],\n                                         cert))\n        {\n            if (!cert_depth)\n            {\n                msg(D_TLS_ERRORS,\n                    \"VERIFY ERROR: could not extract %s from X509 \"\n                    \"subject string ('%s') -- note that the field length is \"\n                    \"limited to %d characters\",\n                    opt->x509_username_field[i], subject, TLS_USERNAME_LEN);\n                goto cleanup;\n            }\n            break;\n        }\n        if (!buf_printf(&buf, i ? \"_%s\" : \"%s\", username))\n        {\n            if (!cert_depth)\n            {\n                msg(D_TLS_ERRORS,\n                    \"VERIFY ERROR: could not append %s from X509 \"\n                    \"certificate -- note that the username length is \"\n                    \"limited to %d characters\",\n                    opt->x509_username_field[i], buf.capacity - 1);\n                goto cleanup;\n            }\n            break;\n        }\n    }\n\n    char *common_name = BSTR(&buf);\n    if (!common_name)\n    {\n        msg(D_TLS_ERRORS,\n            \"VERIFY ERROR: depth=%d, could not extract X509 \"\n            \"username string from certificate\",\n            cert_depth);\n        goto cleanup;\n    }\n\n    /* enforce character class restrictions in common name */\n    string_mod_remap_name(common_name);\n\n    /* warn if cert chain is too deep */\n    if (cert_depth >= MAX_CERT_DEPTH)\n    {\n        msg(D_TLS_ERRORS,\n            \"TLS Error: Convoluted certificate chain detected with depth [%d] greater than %d\",\n            cert_depth, MAX_CERT_DEPTH);\n        goto cleanup; /* Reject connection */\n    }\n\n    if (cert_depth == opt->verify_hash_depth && opt->verify_hash)\n    {\n        struct buffer cert_fp = { 0 };\n\n        switch (opt->verify_hash_algo)\n        {\n            case MD_SHA1:\n                cert_fp = x509_get_sha1_fingerprint(cert, &gc);\n                break;\n\n            case MD_SHA256:\n                cert_fp = x509_get_sha256_fingerprint(cert, &gc);\n                break;\n\n            default:\n                /* This should normally not happen at all; the algorithm used\n                 * is parsed by add_option() [options.c] and set to a predefined\n                 * value in an enumerated type.  So if this unlikely scenario\n                 * happens, consider this a failure\n                 */\n                msg(M_WARN,\n                    \"Unexpected invalid algorithm used with \"\n                    \"--verify-hash (%i)\",\n                    opt->verify_hash_algo);\n                ret = FAILURE;\n                goto cleanup;\n        }\n\n        struct verify_hash_list *current_hash = opt->verify_hash;\n\n        while (current_hash)\n        {\n            if (memcmp_constant_time(BPTR(&cert_fp), current_hash->hash, BLENZ(&cert_fp)) == 0)\n            {\n                break;\n            }\n            current_hash = current_hash->next;\n        }\n\n        if (!current_hash)\n        {\n            const char *hex_fp = format_hex_ex(BPTR(&cert_fp), BLEN(&cert_fp), 0, 1, \":\", &gc);\n            msg(D_TLS_ERRORS,\n                \"TLS Error: --tls-verify/--peer-fingerprint \"\n                \"certificate hash verification failed. (got certificate \"\n                \"fingerprint: %s)\",\n                hex_fp);\n            goto cleanup;\n        }\n    }\n\n    /* save common name in session object */\n    if (cert_depth == 0)\n    {\n        set_common_name(session, common_name);\n    }\n\n    session->verify_maxlevel = max_int(session->verify_maxlevel, cert_depth);\n\n    if (opt->export_peer_cert_dir)\n    {\n        pem_export_fname = platform_create_temp_file(opt->export_peer_cert_dir, \"pef\", &gc);\n\n        if (!pem_export_fname || !verify_cert_cert_export_env(opt->es, cert, pem_export_fname))\n        {\n            msg(D_TLS_ERRORS,\n                \"TLS Error: Failed to export certificate for \"\n                \"--tls-export-cert in %s\",\n                opt->export_peer_cert_dir);\n            goto cleanup;\n        }\n    }\n    /* export certificate values to the environment */\n    verify_cert_set_env(opt->es, cert, cert_depth, subject, opt->x509_track);\n\n    /* export current untrusted IP */\n    setenv_untrusted(session);\n\n    /* If this is the peer's own certificate, verify it */\n    if (cert_depth == 0 && SUCCESS != verify_peer_cert(opt, cert, subject, common_name))\n    {\n        goto cleanup;\n    }\n\n    /* call --tls-verify plug-in(s), if registered */\n    if (SUCCESS != verify_cert_call_plugin(opt->plugins, opt->es, cert_depth, cert, subject))\n    {\n        goto cleanup;\n    }\n\n    /* run --tls-verify script */\n    if (opt->verify_command\n        && SUCCESS != verify_cert_call_command(opt->verify_command, opt->es, cert_depth, subject))\n    {\n        goto cleanup;\n    }\n\n    /* check peer cert against CRL */\n    if (opt->crl_file)\n    {\n        if (opt->ssl_flags & SSLF_CRL_VERIFY_DIR)\n        {\n            if (SUCCESS != verify_check_crl_dir(opt->crl_file, cert, subject, cert_depth))\n            {\n                goto cleanup;\n            }\n        }\n        else\n        {\n            if (tls_verify_crl_missing(opt))\n            {\n                msg(D_TLS_ERRORS, \"VERIFY ERROR: CRL not loaded\");\n                goto cleanup;\n            }\n        }\n    }\n\n    msg(D_HANDSHAKE, \"VERIFY OK: depth=%d, %s\", cert_depth, subject);\n    session->verified = true;\n    ret = SUCCESS;\n\ncleanup:\n    verify_cert_cert_delete_env(opt->es, pem_export_fname);\n    if (ret != SUCCESS)\n    {\n        tls_clear_error();         /* always? */\n        session->verified = false; /* double sure? */\n    }\n\n    gc_free(&gc);\n\n    return ret;\n}\n\n/* ***************************************************************************\n * Functions for the management of deferred authentication when using\n * user/password authentication.\n *************************************************************************** */\n\nvoid\nauth_set_client_reason(struct tls_multi *multi, const char *client_reason)\n{\n    free(multi->client_reason);\n    multi->client_reason = NULL;\n\n    if (client_reason && strlen(client_reason))\n    {\n        multi->client_reason = string_alloc(client_reason, NULL);\n    }\n}\n\n#ifdef ENABLE_MANAGEMENT\n\nstatic inline enum auth_deferred_result\nman_def_auth_test(const struct key_state *ks)\n{\n    if (management_enable_def_auth(management))\n    {\n        return ks->mda_status;\n    }\n    else\n    {\n        return ACF_DISABLED;\n    }\n}\n#endif /* ifdef ENABLE_MANAGEMENT */\n\n/**\n *  Removes auth_pending file from the file system\n *  and key_state structure\n */\nstatic void\nkey_state_rm_auth_pending_file(struct auth_deferred_status *ads)\n{\n    if (ads && ads->auth_pending_file)\n    {\n        platform_unlink(ads->auth_pending_file);\n        free(ads->auth_pending_file);\n        ads->auth_pending_file = NULL;\n    }\n}\n\n/**\n * Check peer_info if the client supports the requested pending auth method\n */\nstatic bool\ncheck_auth_pending_method(const char *peer_info, const char *method)\n{\n    struct gc_arena gc = gc_new();\n\n    char *iv_sso = extract_var_peer_info(peer_info, \"IV_SSO=\", &gc);\n    if (!iv_sso)\n    {\n        gc_free(&gc);\n        return false;\n    }\n\n    const char *client_method = strtok(iv_sso, \",\");\n    bool supported = false;\n\n    while (client_method)\n    {\n        if (0 == strcmp(client_method, method))\n        {\n            supported = true;\n            break;\n        }\n        client_method = strtok(NULL, \",\");\n    }\n\n    gc_free(&gc);\n    return supported;\n}\n\n/**\n *  Checks if the deferred state should also send auth pending\n *  request to the client. Also removes the auth_pending control file\n *\n *  @returns true   if file was either processed sucessfully or did not\n *                  exist at all\n *  @returns false  The file had an invlaid format or another error occured\n */\nstatic bool\nkey_state_check_auth_pending_file(struct auth_deferred_status *ads,\n                                  struct tls_multi *multi,\n                                  struct tls_session *session)\n{\n    bool ret = true;\n    if (ads->auth_pending_file)\n    {\n        struct buffer_list *lines = buffer_list_file(ads->auth_pending_file, 1024);\n        if (lines && lines->head)\n        {\n            /* Must have at least three lines. further lines are ignored for\n             * forward compatibility */\n            if (!lines->head || !lines->head->next || !lines->head->next->next)\n            {\n                msg(M_WARN, \"auth pending control file is not at least \"\n                            \"three lines long.\");\n                buffer_list_free(lines);\n                return false;\n            }\n            struct buffer *timeout_buf = &lines->head->buf;\n            struct buffer *iv_buf = &lines->head->next->buf;\n            struct buffer *extra_buf = &lines->head->next->next->buf;\n\n            /* Remove newline chars at the end of the lines */\n            buf_chomp(timeout_buf);\n            buf_chomp(iv_buf);\n            buf_chomp(extra_buf);\n\n            long timeout = strtol(BSTR(timeout_buf), NULL, 10);\n            if (timeout <= 0)\n            {\n                msg(M_WARN, \"could not parse auth pending file timeout\");\n                buffer_list_free(lines);\n                return false;\n            }\n\n            const char *pending_method = BSTR(iv_buf);\n            if (!check_auth_pending_method(multi->peer_info, pending_method))\n            {\n                char buf[128];\n                snprintf(buf, sizeof(buf),\n                         \"Authentication failed, required pending auth \"\n                         \"method '%s' not supported\",\n                         pending_method);\n                auth_set_client_reason(multi, buf);\n                msg(M_INFO,\n                    \"Client does not supported auth pending method '%s'\",\n                    pending_method);\n                ret = false;\n            }\n            else\n            {\n                send_auth_pending_messages(multi, session, BSTR(extra_buf),\n                                           (unsigned int)timeout);\n            }\n        }\n\n        buffer_list_free(lines);\n    }\n    key_state_rm_auth_pending_file(ads);\n    return ret;\n}\n\n/**\n *  Removes auth_pending and auth_control files from file system\n *  and key_state structure\n */\nvoid\nkey_state_rm_auth_control_files(struct auth_deferred_status *ads)\n{\n    if (ads->auth_control_file)\n    {\n        platform_unlink(ads->auth_control_file);\n        free(ads->auth_control_file);\n        ads->auth_control_file = NULL;\n    }\n    if (ads->auth_failed_reason_file)\n    {\n        platform_unlink(ads->auth_failed_reason_file);\n        free(ads->auth_failed_reason_file);\n        ads->auth_failed_reason_file = NULL;\n    }\n    key_state_rm_auth_pending_file(ads);\n}\n\n/**\n * Generates and creates the control files used for deferred authentification\n * in the temporary directory.\n *\n * @return  true if file creation was successful\n */\nstatic bool\nkey_state_gen_auth_control_files(struct auth_deferred_status *ads, const struct tls_options *opt)\n{\n    struct gc_arena gc = gc_new();\n\n    key_state_rm_auth_control_files(ads);\n    const char *acf = platform_create_temp_file(opt->tmp_dir, \"acf\", &gc);\n    const char *apf = platform_create_temp_file(opt->tmp_dir, \"apf\", &gc);\n    const char *afr = platform_create_temp_file(opt->tmp_dir, \"afr\", &gc);\n\n    if (acf && apf && afr)\n    {\n        ads->auth_control_file = string_alloc(acf, NULL);\n        ads->auth_pending_file = string_alloc(apf, NULL);\n        ads->auth_failed_reason_file = string_alloc(afr, NULL);\n\n        setenv_str(opt->es, \"auth_control_file\", ads->auth_control_file);\n        setenv_str(opt->es, \"auth_pending_file\", ads->auth_pending_file);\n        setenv_str(opt->es, \"auth_failed_reason_file\", ads->auth_failed_reason_file);\n    }\n\n    gc_free(&gc);\n    return (acf && apf && afr);\n}\n\n/**\n * Checks if the auth failed reason file has any content and if yes it will\n * be returned as string allocated in gc to the caller.\n */\nstatic char *\nkey_state_check_auth_failed_message_file(const struct auth_deferred_status *ads,\n                                         struct gc_arena *gc)\n{\n    char *ret = NULL;\n    if (ads->auth_failed_reason_file)\n    {\n        struct buffer reason = buffer_read_from_file(ads->auth_failed_reason_file, gc);\n\n        if (BLEN(&reason))\n        {\n            ret = BSTR(&reason);\n        }\n    }\n    return ret;\n}\n\n\n/**\n * Checks the auth control status from a file. The function will try\n * to read and update the cached status if the status is still pending\n * and the parameter cached is false.\n * The function returns the most recent known status.\n *\n * @param ads       deferred status control structure\n * @param cached    Return only cached status\n * @return          ACF_* as per enum\n */\nstatic enum auth_deferred_result\nkey_state_test_auth_control_file(struct auth_deferred_status *ads, bool cached)\n{\n    if (ads->auth_control_file)\n    {\n        unsigned int ret = ads->auth_control_status;\n        if (ret == ACF_PENDING && !cached)\n        {\n            FILE *fp = fopen(ads->auth_control_file, \"r\");\n            if (fp)\n            {\n                const int c = fgetc(fp);\n                if (c == '1')\n                {\n                    ret = ACF_SUCCEEDED;\n                }\n                else if (c == '0')\n                {\n                    ret = ACF_FAILED;\n                }\n                fclose(fp);\n                ads->auth_control_status = ret;\n            }\n        }\n        return ret;\n    }\n    return ACF_DISABLED;\n}\n\n/**\n * This method takes a key_state and if updates the state\n * of the key if it is deferred.\n * @param cached    If auth control files should be tried to be opened or th\n *                  cached results should be used\n * @param ks        The key_state to update\n */\nstatic void\nupdate_key_auth_status(bool cached, struct key_state *ks)\n{\n    if (ks->authenticated == KS_AUTH_FALSE)\n    {\n        return;\n    }\n    else\n    {\n        enum auth_deferred_result auth_plugin = ACF_DISABLED;\n        enum auth_deferred_result auth_script = ACF_DISABLED;\n        enum auth_deferred_result auth_man = ACF_DISABLED;\n        auth_plugin = key_state_test_auth_control_file(&ks->plugin_auth, cached);\n        auth_script = key_state_test_auth_control_file(&ks->script_auth, cached);\n#ifdef ENABLE_MANAGEMENT\n        auth_man = man_def_auth_test(ks);\n#endif\n        ASSERT(auth_plugin < 4 && auth_script < 4 && auth_man < 4);\n\n        if (auth_plugin == ACF_FAILED || auth_script == ACF_FAILED || auth_man == ACF_FAILED)\n        {\n            ks->authenticated = KS_AUTH_FALSE;\n            return;\n        }\n        else if (auth_plugin == ACF_PENDING || auth_script == ACF_PENDING\n                 || auth_man == ACF_PENDING)\n        {\n            if (now >= ks->auth_deferred_expire)\n            {\n                /* Window to authenticate the key has expired, mark\n                 * the key as unauthenticated */\n                ks->authenticated = KS_AUTH_FALSE;\n            }\n        }\n        else\n        {\n            /* all auth states (auth_plugin, auth_script, auth_man)\n             * are either ACF_DISABLED or ACF_SUCCEDED now, which\n             * translates to \"not checked\" or \"auth succeeded\"\n             */\n            ks->authenticated = KS_AUTH_TRUE;\n        }\n    }\n}\n\n\n/**\n * The minimum times to have passed to update the cache. Older versions\n * of OpenVPN had code path that did not do any caching, so we start\n * with no caching (0) here as well to have the same super quick initial\n * reaction.\n */\nstatic time_t cache_intervals[] = { 0, 0, 0, 0, 0, 1, 1, 2, 2, 4, 8 };\n\n/**\n * uses cache_intervals times to determine if we should update the\n * cache.\n */\nstatic bool\ntls_authentication_status_use_cache(struct tls_multi *multi)\n{\n    unsigned int idx = min_uint(multi->tas_cache_num_updates, SIZE(cache_intervals) - 1);\n    time_t latency = cache_intervals[idx];\n    return multi->tas_cache_last_update + latency >= now;\n}\n\nenum tls_auth_status\ntls_authentication_status(struct tls_multi *multi)\n{\n    bool deferred = false;\n\n    /* at least one valid key has successfully completed authentication */\n    bool success = false;\n\n    /* at least one key is enabled for decryption */\n    int active = 0;\n\n    /* at least one key already failed authentication */\n    bool failed_auth = false;\n\n    bool cached = tls_authentication_status_use_cache(multi);\n\n    for (int i = 0; i < KEY_SCAN_SIZE; ++i)\n    {\n        struct key_state *ks = get_key_scan(multi, i);\n        if (TLS_AUTHENTICATED(multi, ks))\n        {\n            active++;\n            update_key_auth_status(cached, ks);\n\n            if (ks->authenticated == KS_AUTH_FALSE)\n            {\n                failed_auth = true;\n            }\n            else if (ks->authenticated == KS_AUTH_DEFERRED)\n            {\n                deferred = true;\n            }\n            else if (ks->authenticated == KS_AUTH_TRUE)\n            {\n                success = true;\n            }\n        }\n    }\n\n    /* we did not rely on a cached result, remember the cache update time */\n    if (!cached)\n    {\n        multi->tas_cache_last_update = now;\n        multi->tas_cache_num_updates++;\n    }\n\n#if 0\n    dmsg(D_TLS_ERRORS, \"TAS: a=%d s=%d d=%d f=%d\", active, success, deferred, failed_auth);\n#endif\n    if (failed_auth)\n    {\n        struct gc_arena gc = gc_new();\n        const struct key_state *ks = get_primary_key(multi);\n        const char *plugin_message =\n            key_state_check_auth_failed_message_file(&ks->plugin_auth, &gc);\n        const char *script_message =\n            key_state_check_auth_failed_message_file(&ks->script_auth, &gc);\n\n        if (plugin_message)\n        {\n            auth_set_client_reason(multi, plugin_message);\n        }\n        if (script_message)\n        {\n            auth_set_client_reason(multi, script_message);\n        }\n\n        /* We have at least one session that failed authentication. There\n         * might be still another session with valid keys.\n         * Although our protocol allows keeping the VPN session alive\n         * with the other session (and we actually did that in earlier\n         * version, this behaviour is really strange from a user (admin)\n         * experience */\n        gc_free(&gc);\n        return TLS_AUTHENTICATION_FAILED;\n    }\n    else if (success)\n    {\n        return TLS_AUTHENTICATION_SUCCEEDED;\n    }\n    else if (active == 0 || deferred)\n    {\n        /* We have a deferred authentication and no currently active key\n         * (first auth, no renegotiation)  */\n        return TLS_AUTHENTICATION_DEFERRED;\n    }\n    else\n    {\n        /* at least one key is active but none is fully authenticated (!success)\n         * and all active are either failed authed or expired deferred auth */\n        return TLS_AUTHENTICATION_FAILED;\n    }\n}\n\n#ifdef ENABLE_MANAGEMENT\n/*\n * For deferred auth, this is where the management interface calls (on server)\n * to indicate auth failure/success.\n */\nbool\ntls_authenticate_key(struct tls_multi *multi, const unsigned int mda_key_id, const bool auth,\n                     const char *client_reason)\n{\n    bool ret = false;\n    if (multi)\n    {\n        int i;\n        auth_set_client_reason(multi, client_reason);\n        for (i = 0; i < KEY_SCAN_SIZE; ++i)\n        {\n            struct key_state *ks = get_key_scan(multi, i);\n            if (ks->mda_key_id == mda_key_id)\n            {\n                ks->mda_status = auth ? ACF_SUCCEEDED : ACF_FAILED;\n                ret = true;\n            }\n        }\n    }\n    return ret;\n}\n#endif /* ifdef ENABLE_MANAGEMENT */\n\n\n/* ****************************************************************************\n * Functions to verify username and password\n *\n * Authenticate a client using username/password.\n * Runs on server.\n *\n * If you want to add new authentication methods,\n * this is the place to start.\n *************************************************************************** */\n\n/**\n * Check if the script/plugin left a message in the auth failed message\n * file and relay it to the user */\nstatic void\ncheck_for_client_reason(struct tls_multi *multi, struct auth_deferred_status *status)\n{\n    struct gc_arena gc = gc_new();\n    const char *msg = key_state_check_auth_failed_message_file(status, &gc);\n    if (msg)\n    {\n        auth_set_client_reason(multi, msg);\n    }\n    gc_free(&gc);\n}\n/*\n * Verify the user name and password using a script\n */\nstatic int\nverify_user_pass_script(struct tls_session *session, struct tls_multi *multi,\n                        const struct user_pass *up)\n{\n    struct gc_arena gc = gc_new();\n    struct argv argv = argv_new();\n    const char *tmp_file = \"\";\n    int retval = OPENVPN_PLUGIN_FUNC_ERROR;\n    struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */\n\n    /* Set environmental variables prior to calling script */\n    setenv_str(session->opt->es, \"script_type\", \"user-pass-verify\");\n\n    /* format command line */\n    argv_parse_cmd(&argv, session->opt->auth_user_pass_verify_script);\n\n    if (session->opt->auth_user_pass_verify_script_via_file)\n    {\n        struct status_output *so;\n\n        tmp_file = platform_create_temp_file(session->opt->tmp_dir, \"up\", &gc);\n        if (tmp_file)\n        {\n            so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);\n            status_printf(so, \"%s\", up->username);\n            status_printf(so, \"%s\", up->password);\n            if (!status_close(so))\n            {\n                msg(D_TLS_ERRORS, \"TLS Auth Error: could not write username/password to file: %s\",\n                    tmp_file);\n                goto done;\n            }\n            /* pass temp file name to script */\n            argv_printf_cat(&argv, \"%s\", tmp_file);\n        }\n    }\n    else\n    {\n        /* username env is already set by set_verify_user_pass_env */\n        setenv_str(session->opt->es, \"password\", up->password);\n    }\n\n    /* pre-create files for deferred auth control */\n    if (!key_state_gen_auth_control_files(&ks->script_auth, session->opt))\n    {\n        msg(D_TLS_ERRORS,\n            \"TLS Auth Error (%s): \"\n            \"could not create deferred auth control file\",\n            __func__);\n        retval = OPENVPN_PLUGIN_FUNC_ERROR;\n        goto error;\n    }\n\n    /* call command */\n    int script_ret =\n        openvpn_run_script(&argv, session->opt->es, S_EXITCODE, \"--auth-user-pass-verify\");\n    switch (script_ret)\n    {\n        case 0:\n            retval = OPENVPN_PLUGIN_FUNC_SUCCESS;\n            break;\n\n        case 2:\n            retval = OPENVPN_PLUGIN_FUNC_DEFERRED;\n            break;\n\n        default:\n            check_for_client_reason(multi, &ks->script_auth);\n            retval = OPENVPN_PLUGIN_FUNC_ERROR;\n            break;\n    }\n    if (retval == OPENVPN_PLUGIN_FUNC_DEFERRED)\n    {\n        /* Check if we the plugin has written the pending auth control\n         * file and send the pending auth to the client */\n        if (!key_state_check_auth_pending_file(&ks->script_auth, multi, session))\n        {\n            retval = OPENVPN_PLUGIN_FUNC_ERROR;\n            key_state_rm_auth_control_files(&ks->script_auth);\n        }\n    }\n    else\n    {\n        /* purge auth control filename (and file itself) for non-deferred returns */\n        key_state_rm_auth_control_files(&ks->script_auth);\n    }\n\ndone:\n    if (tmp_file && strlen(tmp_file) > 0)\n    {\n        platform_unlink(tmp_file);\n    }\n\nerror:\n    if (!session->opt->auth_user_pass_verify_script_via_file)\n    {\n        setenv_del(session->opt->es, \"password\");\n    }\n\n    argv_free(&argv);\n    gc_free(&gc);\n    return retval;\n}\n\n#ifdef ENABLE_PLUGIN\nvoid\nverify_crresponse_plugin(struct tls_multi *multi, const char *cr_response)\n{\n    struct tls_session *session = &multi->session[TM_ACTIVE];\n    setenv_str(session->opt->es, \"crresponse\", cr_response);\n\n    plugin_call(session->opt->plugins, OPENVPN_PLUGIN_CLIENT_CRRESPONSE, NULL, NULL,\n                session->opt->es);\n\n    setenv_del(session->opt->es, \"crresponse\");\n}\n#endif\n\nvoid\nverify_crresponse_script(struct tls_multi *multi, const char *cr_response)\n{\n    struct tls_session *session = &multi->session[TM_ACTIVE];\n\n    if (!session->opt->client_crresponse_script)\n    {\n        return;\n    }\n    struct argv argv = argv_new();\n    struct gc_arena gc = gc_new();\n\n    setenv_str(session->opt->es, \"script_type\", \"client-crresponse\");\n\n    /* Since cr response might be sensitive, like a stupid way to query\n     * a password via 2FA, we pass it via file instead environment */\n    const char *tmp_file = platform_create_temp_file(session->opt->tmp_dir, \"cr\", &gc);\n    static const char *openerrmsg = \"TLS CR Response Error: could not write \"\n                                    \"crtext challenge response to file: %s\";\n\n    if (tmp_file)\n    {\n        struct status_output *so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);\n        status_printf(so, \"%s\", cr_response);\n        if (!status_close(so))\n        {\n            msg(D_TLS_ERRORS, openerrmsg, tmp_file);\n            tls_deauthenticate(multi);\n            goto done;\n        }\n    }\n    else\n    {\n        msg(D_TLS_ERRORS, openerrmsg, \"creating file failed\");\n        tls_deauthenticate(multi);\n        goto done;\n    }\n\n    argv_parse_cmd(&argv, session->opt->client_crresponse_script);\n    argv_printf_cat(&argv, \"%s\", tmp_file);\n\n\n    if (!openvpn_run_script(&argv, session->opt->es, 0, \"--client-crresponse\"))\n    {\n        tls_deauthenticate(multi);\n    }\ndone:\n    argv_free(&argv);\n    gc_free(&gc);\n}\n\n/*\n * Verify the username and password using a plugin\n */\nstatic int\nverify_user_pass_plugin(struct tls_session *session, struct tls_multi *multi,\n                        const struct user_pass *up)\n{\n    int retval = OPENVPN_PLUGIN_FUNC_ERROR;\n    struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */\n\n    /* set password in private env space */\n    setenv_str(session->opt->es, \"password\", up->password);\n\n    /* generate filename for deferred auth control file */\n    if (!key_state_gen_auth_control_files(&ks->plugin_auth, session->opt))\n    {\n        msg(D_TLS_ERRORS,\n            \"TLS Auth Error (%s): \"\n            \"could not create deferred auth control file\",\n            __func__);\n        return retval;\n    }\n\n    /* call command */\n    retval = plugin_call(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, NULL, NULL,\n                         session->opt->es);\n\n    if (retval == OPENVPN_PLUGIN_FUNC_DEFERRED)\n    {\n        /* Check if the plugin has written the pending auth control\n         * file and send the pending auth to the client */\n        if (!key_state_check_auth_pending_file(&ks->plugin_auth, multi, session))\n        {\n            retval = OPENVPN_PLUGIN_FUNC_ERROR;\n        }\n    }\n\n    if (retval == OPENVPN_PLUGIN_FUNC_ERROR)\n    {\n        check_for_client_reason(multi, &ks->plugin_auth);\n    }\n\n    if (retval != OPENVPN_PLUGIN_FUNC_DEFERRED)\n    {\n        /* purge auth control filename (and file itself) for non-deferred returns */\n        key_state_rm_auth_control_files(&ks->plugin_auth);\n    }\n\n    setenv_del(session->opt->es, \"password\");\n\n    return retval;\n}\n\n\n#ifdef ENABLE_MANAGEMENT\n/*\n * management deferred internal ssl_verify.c status codes\n */\n#define KMDA_ERROR   0\n#define KMDA_SUCCESS 1\n#define KMDA_UNDEF   2\n#define KMDA_DEF     3\n\nstatic int\nverify_user_pass_management(struct tls_session *session, const struct user_pass *up)\n{\n    int retval = KMDA_ERROR;\n    struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */\n\n    /* set username/password in private env space */\n    setenv_str(session->opt->es, \"password\", up->password);\n\n    if (management)\n    {\n        management_notify_client_needing_auth(management, ks->mda_key_id, session->opt->mda_context,\n                                              session->opt->es);\n    }\n\n    setenv_del(session->opt->es, \"password\");\n\n    retval = KMDA_SUCCESS;\n\n    return retval;\n}\n#endif /* ifdef ENABLE_MANAGEMENT */\n\nstatic bool\nset_verify_user_pass_env(struct user_pass *up, struct tls_multi *multi, struct tls_session *session)\n{\n    /* Is username defined? */\n    if ((session->opt->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL) || strlen(up->username))\n    {\n        setenv_str(session->opt->es, \"username\", up->username);\n\n        /* setenv incoming cert common name for script */\n        setenv_str(session->opt->es, \"common_name\", session->common_name);\n\n        /* setenv client real IP address */\n        setenv_untrusted(session);\n\n        /*\n         * if we are using auth-gen-token, send also the session id of auth gen token to\n         * allow the management to figure out if it is a new session or a continued one\n         */\n        add_session_token_env(session, multi, up);\n        return true;\n    }\n    else\n    {\n        msg(D_TLS_ERRORS, \"TLS Auth Error: peer provided a blank username\");\n        return false;\n    }\n}\n\nbool\nssl_verify_username_length(struct tls_session *session, const char *username)\n{\n    if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME)\n        && strlen(username) > TLS_USERNAME_LEN)\n    {\n        msg(D_TLS_ERRORS,\n            \"TLS Auth Error: --username-as-common name specified and \"\n            \"username is longer than the maximum permitted Common Name \"\n            \"length of %d characters\",\n            TLS_USERNAME_LEN);\n        return false;\n    }\n    else\n    {\n        return true;\n    }\n}\n\n/**\n * Main username/password verification entry point\n *\n * Will set session->ks[KS_PRIMARY].authenticated according to\n * result of the username/password verification\n */\nvoid\nverify_user_pass(struct user_pass *up, struct tls_multi *multi, struct tls_session *session)\n{\n    struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */\n\n    ASSERT(up && !up->protected);\n\n#ifdef ENABLE_MANAGEMENT\n    int man_def_auth = KMDA_UNDEF;\n\n    if (management_enable_def_auth(management))\n    {\n        man_def_auth = KMDA_DEF;\n    }\n#endif\n\n    /* enforce character class restrictions in username/password */\n    string_mod_remap_name(up->username);\n    string_mod(up->password, CC_PRINT, CC_CRLF, '_');\n\n    /*\n     * If auth token succeeds we skip the auth\n     * methods unless otherwise specified\n     */\n    bool skip_auth = false;\n\n    /* Replace username early if override-username is in effect but only\n     * if client is sending the original username */\n    if (multi->locked_original_username\n        && strncmp(up->username, multi->locked_original_username, sizeof(up->username)) == 0)\n    {\n        msg(D_MULTI_LOW,\n            \"TLS: Replacing client provided username '%s' with \"\n            \"username from override-user '%s'\",\n            up->username, multi->locked_username);\n        strncpy(up->username, multi->locked_username, sizeof(up->username));\n    }\n\n    /*\n     * If server is configured with --auth-gen-token and the client sends\n     * something that looks like an authentication token, this\n     * round will be done internally using the token instead of\n     * calling any external authentication modules.\n     */\n    if (session->opt->auth_token_generate && is_auth_token(up->password))\n    {\n        ks->auth_token_state_flags = verify_auth_token(up, multi, session);\n\n        /* If this is the first time we see an auth-token in this multi session,\n         * save it as initial auth token. This ensures using the\n         * same session ID and initial timestamp in new tokens */\n        if (!multi->auth_token_initial)\n        {\n            multi->auth_token_initial = strdup(up->password);\n        }\n\n        if (session->opt->auth_token_call_auth)\n        {\n            /*\n             * we do not care about the result here because it is\n             * the responsibility of the external authentication to\n             * decide what to do with the result\n             */\n        }\n        else if (ks->auth_token_state_flags == AUTH_TOKEN_HMAC_OK)\n        {\n            /*\n             * We do not want the EXPIRED or EMPTY USER flags here so check\n             * for equality with AUTH_TOKEN_HMAC_OK\n             */\n            msg(M_WARN,\n                \"TLS: Username/auth-token authentication \"\n                \"succeeded for username '%s'\",\n                up->username);\n            skip_auth = true;\n        }\n        else\n        {\n            wipe_auth_token(multi);\n            ks->authenticated = KS_AUTH_FALSE;\n            msg(M_WARN,\n                \"TLS: Username/auth-token authentication \"\n                \"failed for username '%s'\",\n                up->username);\n            return;\n        }\n    }\n\n    int plugin_status = OPENVPN_PLUGIN_FUNC_SUCCESS;\n    int script_status = OPENVPN_PLUGIN_FUNC_SUCCESS;\n    /* Set the environment variables used by all auth variants */\n    if (!set_verify_user_pass_env(up, multi, session))\n    {\n        skip_auth = true;\n        plugin_status = OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /* call plugin(s) and/or script */\n    if (!skip_auth)\n    {\n#ifdef ENABLE_MANAGEMENT\n        if (man_def_auth == KMDA_DEF)\n        {\n            man_def_auth = verify_user_pass_management(session, up);\n        }\n#endif\n        if (plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY))\n        {\n            plugin_status = verify_user_pass_plugin(session, multi, up);\n        }\n\n        if (session->opt->auth_user_pass_verify_script)\n        {\n            script_status = verify_user_pass_script(session, multi, up);\n        }\n    }\n\n    /* check sizing of username if it will become our common name */\n    if (!ssl_verify_username_length(session, up->username))\n    {\n        plugin_status = OPENVPN_PLUGIN_FUNC_ERROR;\n        script_status = OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /* auth succeeded? */\n    bool plugin_ok = plugin_status == OPENVPN_PLUGIN_FUNC_SUCCESS\n                     || plugin_status == OPENVPN_PLUGIN_FUNC_DEFERRED;\n\n    bool script_ok = script_status == OPENVPN_PLUGIN_FUNC_SUCCESS\n                     || script_status == OPENVPN_PLUGIN_FUNC_DEFERRED;\n\n    if (script_ok && plugin_ok && tls_lock_username(multi, up->username)\n#ifdef ENABLE_MANAGEMENT\n        && man_def_auth != KMDA_ERROR\n#endif\n    )\n    {\n        ks->authenticated = KS_AUTH_TRUE;\n        if (plugin_status == OPENVPN_PLUGIN_FUNC_DEFERRED\n            || script_status == OPENVPN_PLUGIN_FUNC_DEFERRED)\n        {\n            ks->authenticated = KS_AUTH_DEFERRED;\n        }\n#ifdef ENABLE_MANAGEMENT\n        if (man_def_auth != KMDA_UNDEF)\n        {\n            if (skip_auth)\n            {\n                ks->mda_status = ACF_DISABLED;\n            }\n            else\n            {\n                ks->authenticated = KS_AUTH_DEFERRED;\n            }\n        }\n#endif\n        if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME))\n        {\n            set_common_name(session, up->username);\n        }\n\n        if ((session->opt->auth_token_generate))\n        {\n            /*\n             * If we accepted a (not expired) token, i.e.\n             * initial auth via token on new connection, we need\n             * to store the auth-token in multi->auth_token, so\n             * the initial timestamp and session id can be extracted from it\n             */\n            if (!multi->auth_token && (ks->auth_token_state_flags & AUTH_TOKEN_HMAC_OK)\n                && !(ks->auth_token_state_flags & AUTH_TOKEN_EXPIRED))\n            {\n                multi->auth_token = strdup(up->password);\n            }\n\n            /*\n             * Server is configured with --auth-gen-token. Generate or renew\n             * the token.\n             */\n            generate_auth_token(up, multi);\n        }\n\n        msg(D_HANDSHAKE, \"TLS: Username/Password authentication %s for username '%s' %s\",\n            (ks->authenticated == KS_AUTH_DEFERRED) ? \"deferred\" : \"succeeded\", up->username,\n            (session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? \"[CN SET]\" : \"\");\n    }\n    else\n    {\n        ks->authenticated = KS_AUTH_FALSE;\n        msg(D_TLS_ERRORS, \"TLS Auth Error: Auth Username/Password verification failed for peer\");\n    }\n}\n\nvoid\nverify_final_auth_checks(struct tls_multi *multi, struct tls_session *session)\n{\n    struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */\n\n    /* While it shouldn't really happen, don't allow the common name to be NULL */\n    if (!session->common_name)\n    {\n        set_common_name(session, \"\");\n    }\n\n    /* Don't allow the CN to change once it's been locked */\n    if (ks->authenticated > KS_AUTH_FALSE && multi->locked_cn)\n    {\n        const char *cn = session->common_name;\n        if (cn && strcmp(cn, multi->locked_cn))\n        {\n            msg(D_TLS_ERRORS,\n                \"TLS Auth Error: TLS object CN attempted to change from '%s' to '%s' -- tunnel disabled\",\n                multi->locked_cn, cn);\n\n            /* change the common name back to its original value and disable the tunnel */\n            set_common_name(session, multi->locked_cn);\n            tls_deauthenticate(multi);\n        }\n    }\n\n    /* Don't allow the cert hashes to change once they have been locked */\n    if (ks->authenticated > KS_AUTH_FALSE && multi->locked_cert_hash_set)\n    {\n        const struct cert_hash_set *chs = session->cert_hash_set;\n        if (chs && !cert_hash_compare(chs, multi->locked_cert_hash_set))\n        {\n            msg(D_TLS_ERRORS,\n                \"TLS Auth Error: TLS object CN=%s client-provided SSL certs unexpectedly changed during mid-session reauth\",\n                session->common_name);\n\n            /* disable the tunnel */\n            tls_deauthenticate(multi);\n        }\n    }\n\n    /* verify --client-config-dir based authentication */\n    if (ks->authenticated > KS_AUTH_FALSE && session->opt->client_config_dir_exclusive)\n    {\n        struct gc_arena gc = gc_new();\n\n        const char *cn = session->common_name;\n        const char *path = platform_gen_path(session->opt->client_config_dir_exclusive, cn, &gc);\n        if (!cn || !strcmp(cn, CCD_DEFAULT) || !platform_test_file(path))\n        {\n            ks->authenticated = KS_AUTH_FALSE;\n            wipe_auth_token(multi);\n            msg(D_TLS_ERRORS,\n                \"TLS Auth Error: --client-config-dir authentication failed for common name '%s' file='%s'\",\n                session->common_name, path ? path : \"UNDEF\");\n        }\n\n        gc_free(&gc);\n    }\n}\n\nvoid\ntls_x509_clear_env(struct env_set *es)\n{\n    struct env_item *item = es->list;\n    while (item)\n    {\n        struct env_item *next = item->next;\n        if (item->string && 0 == strncmp(\"X509_\", item->string, strlen(\"X509_\")))\n        {\n            env_set_del(es, item->string);\n        }\n        item = next;\n    }\n}\n"
  },
  {
    "path": "src/openvpn/ssl_verify.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel Verification Module\n */\n\n#ifndef SSL_VERIFY_H_\n#define SSL_VERIFY_H_\n\n#include \"syshead.h\"\n#include \"misc.h\"\n#include \"ssl_common.h\"\n\n/* Include OpenSSL-specific code */\n#ifdef ENABLE_CRYPTO_OPENSSL\n#include \"ssl_verify_openssl.h\"\n#endif\n#ifdef ENABLE_CRYPTO_MBEDTLS\n#include \"ssl_verify_mbedtls.h\"\n#endif\n\n#include \"ssl_verify_backend.h\"\n\n/*\n * Keep track of certificate hashes at various depths\n */\n\n/** Maximum certificate depth we will allow */\n#define MAX_CERT_DEPTH 16\n\n/** Maximum length of common name */\n#define TLS_USERNAME_LEN 64\n\n/** Structure containing the hash for a single certificate */\nstruct cert_hash\n{\n    unsigned char sha256_hash[256 / 8];\n};\n\n/** Structure containing the hashes for a full certificate chain */\nstruct cert_hash_set\n{\n    struct cert_hash *ch[MAX_CERT_DEPTH]; /**< Array of certificate hashes */\n};\n\n#define VERIFY_X509_NONE               0\n#define VERIFY_X509_SUBJECT_DN         1\n#define VERIFY_X509_SUBJECT_RDN        2\n#define VERIFY_X509_SUBJECT_RDN_PREFIX 3\n\nenum tls_auth_status\n{\n    TLS_AUTHENTICATION_SUCCEEDED = 0,\n    TLS_AUTHENTICATION_FAILED = 1,\n    TLS_AUTHENTICATION_DEFERRED = 2\n};\n\n/**\n * Return current session authentication state of the tls_multi structure\n * This will return TLS_AUTHENTICATION_SUCCEEDED only if the session is\n * fully authenticated, i.e. VPN traffic is allowed over it.\n *\n * Checks the status of all active keys and checks if the deferred\n * authentication has succeeded.\n *\n * As a side effect this function will also transition ks->authenticated\n * from KS_AUTH_DEFERRED to KS_AUTH_FALSE/KS_AUTH_TRUE if the deferred\n * authentication has succeeded after last call.\n *\n * @param   multi       the tls_multi struct to operate on\n *\n * @return              Current authentication status of the tls_multi\n */\nenum tls_auth_status tls_authentication_status(struct tls_multi *multi);\n\n/** Check whether the \\a ks \\c key_state has finished the key exchange part\n *  of the OpenVPN hand shake. This is that the key_method_2read/write\n *  handshakes have been completed and certificate verification have\n *  been completed.\n *\n * connect/deferred auth might still pending. Also data-channel keys might\n * not have been created since they are delayed until PUSH_REPLY for NCP\n * clients.\n *\n *   @ingroup data_crypto\n *\n *   If true, it is safe to assume that this session has been authenticated\n *   by TLS.\n *\n *   @note This macro only works if S_SENT_KEY + 1 == S_GOT_KEY. */\n#define TLS_AUTHENTICATED(multi, ks) ((ks)->state >= (S_GOT_KEY - (multi)->opt.server))\n\n/**\n * Remove the given key state's auth deferred status auth control file,\n * if it exists.\n *\n * @param ads    The key state the remove the file for\n */\nvoid key_state_rm_auth_control_files(struct auth_deferred_status *ads);\n\n/**\n * Frees the given set of certificate hashes.\n *\n * @param chs   The certificate hash set to free.\n */\nvoid cert_hash_free(struct cert_hash_set *chs);\n\n/**\n * Locks the certificate hash set used in the given tunnel\n *\n * @param multi The tunnel to lock\n */\nvoid tls_lock_cert_hash_set(struct tls_multi *multi);\n\n/**\n * Locks the common name field for the given tunnel\n *\n * @param multi The tunnel to lock\n */\nvoid tls_lock_common_name(struct tls_multi *multi);\n\n/**\n * Returns the common name field for the given tunnel\n *\n * @param multi The tunnel to return the common name for\n * @param null  Whether null may be returned. If not, \"UNDEF\" will be returned.\n */\nconst char *tls_common_name(const struct tls_multi *multi, const bool null);\n\n\n/**\n * Sets the common name field for the given tunnel\n *\n * @param session       The session to set the common name for\n * @param common_name   The name to set the common name to\n */\nvoid set_common_name(struct tls_session *session, const char *common_name);\n\n/**\n * Returns the username field for the given tunnel\n *\n * @param multi The tunnel to return the username for\n * @param null  Whether null may be returned. If not, \"UNDEF\" will be returned.\n */\nconst char *tls_username(const struct tls_multi *multi, const bool null);\n\n/**\n * Compares certificates hashes, returns true if hashes are equal.\n *\n * @param chs1 cert 1 hash set\n * @param chs2 cert 2 hash set\n */\nbool cert_hash_compare(const struct cert_hash_set *chs1, const struct cert_hash_set *chs2);\n\n/**\n * Verify the given username and password, using either an external script, a\n * plugin, or the management interface.\n *\n * If authentication succeeds, the appropriate state is filled into the\n * session's primary key state's authenticated field. Authentication may also\n * be deferred, in which case the key state's auth_deferred field is filled in.\n *\n * @param up            The username and password to verify.\n * @param multi         The TLS multi structure to verify usernames against.\n * @param session       The current TLS session\n *\n */\nvoid verify_user_pass(struct user_pass *up, struct tls_multi *multi, struct tls_session *session);\n\n\n/**\n * Checks if the username length is valid to use.  This checks when\n * username-as-common-name is active if the username is shorter than\n * the maximum TLS common name length (64).\n *\n * It will also display an error message if the name is too long\n *\n * @param session       current TLS session\n * @param username      username to check\n * @return              true if name is under limit or username-as-common-name\n *                      is not active\n */\nbool ssl_verify_username_length(struct tls_session *session, const char *username);\n\n/**\n * Runs the --client-crresponse script if one is defined.\n *\n * As with the management interface the script is stateless in the sense that\n * it does not directly participate in the authentication but rather should set\n * the files for the deferred auth like the management commands.\n *\n */\nvoid verify_crresponse_script(struct tls_multi *multi, const char *cr_response);\n\n/**\n * Call the plugin OPENVPN_PLUGIN_CLIENT_CRRESPONSE.\n *\n * As with the management interface calling the plugin is stateless in the sense\n * that it does not directly participate in the authentication but rather\n * should set the files for the deferred auth like the management commands.\n */\nvoid verify_crresponse_plugin(struct tls_multi *multi, const char *cr_response);\n\n/**\n * Perform final authentication checks, including locking of the cn, the allowed\n * certificate hashes, and whether a client config entry exists in the\n * client config directory.\n *\n * @param multi         The TLS multi structure to verify locked structures.\n * @param session       The current TLS session\n *\n */\nvoid verify_final_auth_checks(struct tls_multi *multi, struct tls_session *session);\n\nstruct x509_track\n{\n    const struct x509_track *next;\n    const char *name;\n#define XT_FULL_CHAIN (1 << 0)\n    unsigned int flags;\n    int nid;\n};\n\n/*\n * Certificate checking for verify_nsCertType\n */\n/** Do not perform Netscape certificate type verification */\n#define NS_CERT_CHECK_NONE   (0)\n/** Do not perform Netscape certificate type verification */\n#define NS_CERT_CHECK_SERVER (1 << 0)\n/** Do not perform Netscape certificate type verification */\n#define NS_CERT_CHECK_CLIENT (1 << 1)\n\n/** Require keyUsage to be present in cert (0xFFFF is an invalid KU value) */\n#define OPENVPN_KU_REQUIRED (0xFFFF)\n\n/*\n * TODO: document\n */\n#ifdef ENABLE_MANAGEMENT\nbool tls_authenticate_key(struct tls_multi *multi, const unsigned int mda_key_id, const bool auth,\n                          const char *client_reason);\n\n#endif\n\n/**\n * Sets the reason why authentication of a client failed. This be will send to the client\n * when the AUTH_FAILED message is sent\n * An example would be \"SESSION: Token expired\"\n * @param multi             The multi tls struct\n * @param client_reason     The string to send to the client as part of AUTH_FAILED\n */\nvoid auth_set_client_reason(struct tls_multi *multi, const char *client_reason);\n\nstatic inline const char *\ntls_client_reason(struct tls_multi *multi)\n{\n    return multi->client_reason;\n}\n\n/** Remove any X509_ env variables from env_set es */\nvoid tls_x509_clear_env(struct env_set *es);\n\n#endif /* SSL_VERIFY_H_ */\n"
  },
  {
    "path": "src/openvpn/ssl_verify_backend.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel Verification Module library-specific backend interface\n */\n\n#ifndef SSL_VERIFY_BACKEND_H_\n#define SSL_VERIFY_BACKEND_H_\n\n/**\n * Result of verification function\n */\ntypedef enum\n{\n    SUCCESS = 0,\n    FAILURE = 1\n} result_t;\n\n/*\n * Backend support functions.\n *\n * The following functions are needed by the backend, but defined in the main\n * file.\n */\n\n/*\n * Verify certificate for the given session. Performs OpenVPN-specific\n * verification.\n *\n * This function must be called for every certificate in the certificate\n * chain during the certificate verification stage of the handshake.\n *\n * @param session       TLS Session associated with this tunnel\n * @param cert          Certificate to process\n * @param cert_depth    Depth of the current certificate\n *\n * @return              \\c SUCCESS if verification was successful, \\c FAILURE on failure.\n */\nresult_t verify_cert(struct tls_session *session, openvpn_x509_cert_t *cert, int cert_depth);\n\n/*\n * Remember the given certificate hash, allowing the certificate chain to be\n * locked between sessions.\n *\n * Must be called for every certificate in the verification chain, whether it\n * is valid or not.\n *\n * @param session       TLS Session associated with this tunnel\n * @param cert_depth    Depth of the current certificate\n * @param cert_hash     Hash of the current certificate\n */\nvoid cert_hash_remember(struct tls_session *session, const int cert_depth,\n                        const struct buffer *cert_hash);\n\n/*\n * Library-specific functions.\n *\n * The following functions must be implemented on a library-specific basis.\n */\n\n/*\n * Retrieve certificate's subject name.\n *\n * @param cert          Certificate to retrieve the subject from.\n * @param gc            Garbage collection arena to use when allocating string.\n *\n * @return              a string containing the subject\n */\nchar *x509_get_subject(openvpn_x509_cert_t *cert, struct gc_arena *gc);\n\n/**\n * Retrieve the certificate's SHA1 fingerprint.\n *\n * @param cert          Certificate to retrieve the fingerprint from.\n * @param gc            Garbage collection arena to use when allocating string.\n *\n * @return              a string containing the certificate fingerprint\n */\nstruct buffer x509_get_sha1_fingerprint(openvpn_x509_cert_t *cert, struct gc_arena *gc);\n\n/**\n * Retrieve the certificate's SHA256 fingerprint.\n *\n * @param cert          Certificate to retrieve the fingerprint from.\n * @param gc            Garbage collection arena to use when allocating string.\n *\n * @return              a string containing the certificate fingerprint\n */\nstruct buffer x509_get_sha256_fingerprint(openvpn_x509_cert_t *cert, struct gc_arena *gc);\n\n/*\n * Retrieve the certificate's username from the specified field.\n *\n * If the field is prepended with ext: is enabled,\n * it will be loaded from an X.509 extension\n *\n * @param cn                    Buffer to return the common name in.\n * @param cn_len                Length of the cn buffer.\n * @param x509_username_field   Name of the field to load from\n * @param cert                  Certificate to retrieve the common name from.\n *\n * @return              \\c FAILURE, \\c or SUCCESS\n */\nresult_t backend_x509_get_username(char *common_name, size_t cn_len, char *x509_username_field,\n                                   openvpn_x509_cert_t *peer_cert);\n\n/**\n * Return true iff the supplied extension field is supported by the\n * --x509-username-field option.\n */\nbool x509_username_field_ext_supported(const char *extname);\n\n/*\n * Return the certificate's serial number in decimal string representation.\n *\n * The serial number is returned as a string, since it might be a bignum.\n *\n * @param cert          Certificate to retrieve the serial number from.\n * @param gc            Garbage collection arena to use when allocating string.\n *\n * @return              String representation of the certificate's serial number\n *                      in decimal notation, or NULL on error.\n */\nchar *backend_x509_get_serial(openvpn_x509_cert_t *cert, struct gc_arena *gc);\n\n/*\n * Return the certificate's serial number in hex string representation.\n *\n * The serial number is returned as a string, since it might be a bignum.\n *\n * @param cert          Certificate to retrieve the serial number from.\n * @param gc            Garbage collection arena to use when allocating string.\n *\n * @return              String representation of the certificate's serial number\n *                      in hex notation, or NULL on error.\n */\nchar *backend_x509_get_serial_hex(openvpn_x509_cert_t *cert, struct gc_arena *gc);\n\n/*\n * Write the certificate to the file in PEM format.\n *\n *\n * @param cert          Certificate to serialise.\n *\n * @return              \\c FAILURE, \\c or SUCCESS\n */\nresult_t backend_x509_write_pem(openvpn_x509_cert_t *cert, const char *filename);\n\n/*\n * Save X509 fields to environment, using the naming convention:\n *\n * X509_{cert_depth}_{name}={value}\n *\n * @param es            Environment set to save variables in\n * @param cert_depth    Depth of the certificate\n * @param cert          Certificate to set the environment for\n */\nvoid x509_setenv(struct env_set *es, int cert_depth, openvpn_x509_cert_t *cert);\n\n/*\n * Start tracking the given attribute.\n *\n * The tracked attributes are stored in ll_head.\n *\n * @param ll_head       The x509_track to store tracked attributes in\n * @param name          Name of the attribute to track\n * @param msglevel      Message level for errors\n * @param gc            Garbage collection arena for temp data\n *\n */\nvoid x509_track_add(const struct x509_track **ll_head, const char *name,\n                    msglvl_t msglevel, struct gc_arena *gc);\n\n/*\n * Save X509 fields to environment, using the naming convention:\n *\n *  X509_{cert_depth}_{name}={value}\n *\n * This function differs from setenv_x509 below in the following ways:\n *\n * (1) Only explicitly named attributes in xt are saved, per usage\n *     of --x509-track program options.\n * (2) Only the level 0 cert info is saved unless the XT_FULL_CHAIN\n *     flag is set in xt->flags (corresponds with prepending a '+'\n *     to the name when specified by --x509-track program option).\n * (3) This function supports both X509 subject name fields as\n *     well as X509 V3 extensions.\n *\n * @param xt\n * @param es            Environment set to save variables in\n * @param cert_depth    Depth of the certificate\n * @param cert          Certificate to set the environment for\n */\nvoid x509_setenv_track(const struct x509_track *xt, struct env_set *es, const int depth,\n                       openvpn_x509_cert_t *x509);\n\n/*\n * Check X.509 Netscape certificate type field, if available.\n *\n * @param cert          Certificate to check.\n * @param usage         One of \\c NS_CERT_CHECK_CLIENT, \\c NS_CERT_CHECK_SERVER,\n *                      or \\c NS_CERT_CHECK_NONE.\n *\n * @return              \\c SUCCESS if NS_CERT_CHECK_NONE or if the certificate has\n *                      the expected bit set. \\c FAILURE if the certificate does\n *                      not have NS cert type verification or the wrong bit set.\n */\nresult_t x509_verify_ns_cert_type(openvpn_x509_cert_t *cert, const int usage);\n\n/*\n * Verify X.509 key usage extension field.\n *\n * @param cert          Certificate to check.\n * @param expected_ku   Array of valid key usage values\n * @param expected_len  Length of the key usage array\n *\n * @return              \\c SUCCESS if one of the key usage values matches, \\c FAILURE\n *                      if key usage is not enabled, or the values do not match.\n */\nresult_t x509_verify_cert_ku(openvpn_x509_cert_t *x509, const unsigned *const expected_ku,\n                             size_t expected_len);\n\n/*\n * Verify X.509 extended key usage extension field.\n *\n * @param cert          Certificate to check.\n * @param expected_oid  String representation of the expected Object ID. May be\n *                      either the string representation of the numeric OID\n *                      (e.g. \\c \"1.2.3.4\", or the descriptive string matching\n *                      the OID.\n *\n * @return              \\c SUCCESS if one of the expected OID matches one of the\n *                      extended key usage fields, \\c FAILURE if extended key\n *                      usage is not enabled, or the values do not match.\n */\nresult_t x509_verify_cert_eku(openvpn_x509_cert_t *x509, const char *const expected_oid);\n\n/**\n * Return true iff a CRL is configured, but is not loaded.  This can be caused\n * by e.g. a CRL parsing error, a missing CRL file or CRL file permission\n * errors.  (These conditions are checked upon startup, but the CRL might be\n * updated and reloaded during runtime.)\n */\nbool tls_verify_crl_missing(const struct tls_options *opt);\n\n#endif /* SSL_VERIFY_BACKEND_H_ */\n"
  },
  {
    "path": "src/openvpn/ssl_verify_mbedtls.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel Verification Module mbed TLS backend\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if defined(ENABLE_CRYPTO_MBEDTLS)\n\n#include <mbedtls/version.h>\n\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n#include \"crypto_mbedtls_legacy.h\"\n#include <mbedtls/bignum.h>\n#include <mbedtls/sha1.h>\n#else\n#include \"crypto_mbedtls.h\"\n#endif\n\n#include \"mbedtls_compat.h\"\n\n#include \"ssl_verify.h\"\n#include <mbedtls/asn1.h>\n#include <mbedtls/error.h>\n#include <mbedtls/oid.h>\n\n#define MAX_SUBJECT_LENGTH 256\n\nint\nverify_callback(void *session_obj, mbedtls_x509_crt *cert, int cert_depth, uint32_t *flags)\n{\n    struct tls_session *session = (struct tls_session *)session_obj;\n    struct gc_arena gc = gc_new();\n\n    ASSERT(cert);\n    ASSERT(session);\n\n    session->verified = false;\n\n    /* Remember certificate hash */\n    struct buffer cert_fingerprint = x509_get_sha256_fingerprint(cert, &gc);\n    cert_hash_remember(session, cert_depth, &cert_fingerprint);\n\n    if (session->opt->verify_hash_no_ca)\n    {\n        /*\n         * If we decide to verify the peer certificate based on the fingerprint\n         * we ignore wrong dates and the certificate not being trusted.\n         * Any other problem with the certificate (wrong key, bad cert,...)\n         * will still trigger an error.\n         * Clearing these flags relies on verify_cert will later rejecting a\n         * certificate that has no matching fingerprint.\n         */\n        uint32_t flags_ignore = MBEDTLS_X509_BADCERT_NOT_TRUSTED | MBEDTLS_X509_BADCERT_EXPIRED\n                                | MBEDTLS_X509_BADCERT_FUTURE;\n        *flags = *flags & ~flags_ignore;\n    }\n\n    /* did peer present cert which was signed by our root cert? */\n    if (*flags != 0)\n    {\n        int ret = 0;\n        char errstr[512] = { 0 };\n        char *subject = x509_get_subject(cert, &gc);\n        char *serial = backend_x509_get_serial(cert, &gc);\n\n        ret = mbedtls_x509_crt_verify_info(errstr, sizeof(errstr) - 1, \"\", *flags);\n        if (ret <= 0\n            && !checked_snprintf(errstr, sizeof(errstr), \"Could not retrieve error string, flags=%\" PRIx32, *flags))\n        {\n            errstr[0] = '\\0';\n        }\n        else\n        {\n            chomp(errstr);\n        }\n\n        if (subject)\n        {\n            msg(D_TLS_ERRORS, \"VERIFY ERROR: depth=%d, subject=%s, serial=%s: %s\", cert_depth,\n                subject, serial ? serial : \"<not available>\", errstr);\n        }\n        else\n        {\n            msg(D_TLS_ERRORS,\n                \"VERIFY ERROR: depth=%d, (could not extract X509 \"\n                \"subject string from certificate): %s\",\n                cert_depth, errstr);\n        }\n\n        /* Leave flags set to non-zero to indicate that the cert is not ok */\n    }\n    else if (SUCCESS != verify_cert(session, cert, cert_depth))\n    {\n        *flags |= MBEDTLS_X509_BADCERT_OTHER;\n    }\n\n    gc_free(&gc);\n\n    /*\n     * PolarSSL/mbed TLS-1.2.0+ expects 0 on anything except fatal errors.\n     */\n    return 0;\n}\n\n/* not supported for mbedTLS yet */\nbool\nx509_username_field_ext_supported(const char *fieldname)\n{\n    return false;\n}\n\nresult_t\nbackend_x509_get_username(char *cn, size_t cn_len, char *x509_username_field, mbedtls_x509_crt *cert)\n{\n    mbedtls_x509_name *name;\n\n    ASSERT(cn != NULL);\n\n    name = &cert->subject;\n\n    /* Find common name */\n    while (name != NULL)\n    {\n        if (0 == memcmp(name->oid.p, MBEDTLS_OID_AT_CN, MBEDTLS_OID_SIZE(MBEDTLS_OID_AT_CN)))\n        {\n            break;\n        }\n\n        name = name->next;\n    }\n\n    /* Not found, return an error if this is the peer's certificate */\n    if (name == NULL)\n    {\n        return FAILURE;\n    }\n\n    /* Found, extract CN */\n    if (cn_len > name->val.len)\n    {\n        memcpy(cn, name->val.p, name->val.len);\n        cn[name->val.len] = '\\0';\n    }\n    else\n    {\n        memcpy(cn, name->val.p, cn_len);\n        cn[cn_len - 1] = '\\0';\n    }\n\n    return SUCCESS;\n}\n\n#if MBEDTLS_VERSION_NUMBER >= 0x04000000\n/* Mbed TLS 4 has no function to print the certificate serial number and does\n * not expose the bignum functions anymore. So in order to write the serial\n * number as a decimal string, we implement bignum % 10 and bignum / 10. */\nstatic char\nbignum_mod_10(const uint8_t *bignum, size_t bignum_length)\n{\n    int result = 0;\n    for (size_t i = 0; i < bignum_length; i++)\n    {\n        result = (result * 256) % 10;\n        result = (result + bignum[i]) % 10;\n    }\n    return (char)result;\n}\n\n/* Divide bignum by 10 rounded down, in place. */\nstatic void\nbignum_div_10(uint8_t *bignum, size_t *bignum_length)\n{\n    /*\n     * Some intuition for the algorithm below:\n     *\n     * We want to calculate\n     *\n     *     (bignum[0] * 256^n + bignum[1] * 256^(n-1) + ... + bignum[n]) / 10.\n     *\n     * Let remainder = bignum[0] % 10 and carry = remainder * 256.\n     * Then we can write the above as\n     *\n     *     (bignum[0] / 10) * 256^n\n     *       + ((carry + bignum[1]) * 256^(n-1) + ... + bignum[n]) / 10.\n     *\n     * So now we have the first byte of our result. The second byte will be\n     * (carry + bignum[1]) / 10. Note that this fits into one byte because\n     * 0 <= remainder < 10. We calculate the next remainder and carry as\n     * remainder = (carry + bignum[1]) % 10 and carry = remainder * 256 and\n     * move on to the next byte until we are done.\n     */\n    size_t new_length = 0;\n    int carry = 0;\n    for (size_t i = 0; i < *bignum_length; i++)\n    {\n        uint8_t next_byte = (uint8_t)((bignum[i] + carry) / 10);\n        int remainder = (bignum[i] + carry) % 10;\n        carry = remainder * 256;\n\n        /* Write the byte unless it's a leading zero. */\n        if (new_length != 0 || next_byte != 0)\n        {\n            bignum[new_length++] = next_byte;\n        }\n    }\n    *bignum_length = new_length;\n}\n\n/* Write the decimal representation of bignum to out, if enough space is available.\n * Returns the number of bytes needed in out, or 0 on error. To calculate the\n * necessary buffer size, the function can be called with out = NULL. */\nstatic size_t\nwrite_bignum(char *out, size_t out_size, const uint8_t *bignum, size_t bignum_length)\n{\n    if (bignum_length == 0)\n    {\n        /* We want out to be \"0\". */\n        if (out != NULL)\n        {\n            if (out_size >= 2)\n            {\n                out[0] = '0';\n                out[1] = '\\0';\n            }\n            else if (out_size > 0)\n            {\n                out[0] = '\\0';\n            }\n        }\n        return 2;\n    }\n\n    uint8_t *bignum_copy = malloc(bignum_length);\n    if (bignum_copy == NULL)\n    {\n        return 0;\n    }\n    memcpy(bignum_copy, bignum, bignum_length);\n\n    size_t bytes_needed = 0;\n    size_t bytes_written = 0;\n    while (bignum_length > 0)\n    {\n        /* We're writing the digits in reverse order. We put them in the right order later. */\n        char digit = bignum_mod_10(bignum_copy, bignum_length);\n        if (out != NULL && bytes_written < out_size - 1)\n        {\n            out[bytes_written++] = '0' + (char)digit;\n        }\n        bytes_needed += 1;\n        bignum_div_10(bignum_copy, &bignum_length);\n    }\n\n    if (out != NULL)\n    {\n        if (bytes_written == bytes_needed)\n        {\n            /* We had space for all digits. Now reverse them. */\n            for (size_t i = 0; i < bytes_written / 2; i++)\n            {\n                char tmp = out[i];\n                out[i] = out[bytes_written - 1 - i];\n                out[bytes_written - 1 - i] = tmp;\n            }\n            out[bytes_written] = '\\0';\n        }\n        else if (out_size > 0)\n        {\n            out[0] = '\\0';\n        }\n    }\n    bytes_needed += 1;\n\n    free(bignum_copy);\n    return bytes_needed;\n}\n#endif /* MBEDTLS_VERSION_NUMBER >= 0x04000000 */\n\nchar *\nbackend_x509_get_serial(mbedtls_x509_crt *cert, struct gc_arena *gc)\n{\n    char *buf = NULL;\n    size_t buflen = 0;\n\n#if MBEDTLS_VERSION_NUMBER < 0x04000000\n    mbedtls_mpi serial_mpi = { 0 };\n\n    /* Transform asn1 integer serial into mbed TLS MPI */\n    mbedtls_mpi_init(&serial_mpi);\n    if (!mbed_ok(mbedtls_mpi_read_binary(&serial_mpi, cert->serial.p, cert->serial.len)))\n    {\n        msg(M_WARN, \"Failed to retrieve serial from certificate.\");\n        goto end;\n    }\n\n    /* Determine decimal representation length, allocate buffer */\n    mbedtls_mpi_write_string(&serial_mpi, 10, NULL, 0, &buflen);\n    buf = gc_malloc(buflen, true, gc);\n\n    /* Write MPI serial as decimal string into buffer */\n    if (!mbed_ok(mbedtls_mpi_write_string(&serial_mpi, 10, buf, buflen, &buflen)))\n    {\n        msg(M_WARN, \"Failed to write serial to string.\");\n        buf = NULL;\n        goto end;\n    }\n\nend:\n    mbedtls_mpi_free(&serial_mpi);\n    return buf;\n#else\n    buflen = write_bignum(NULL, 0, cert->serial.p, cert->serial.len);\n    if (buflen == 0)\n    {\n        msg(M_WARN, \"Failed to write serial to string.\");\n        return NULL;\n    }\n    buf = gc_malloc(buflen, true, gc);\n    if (write_bignum(buf, buflen, cert->serial.p, cert->serial.len) != buflen)\n    {\n        msg(M_WARN, \"Failed to write serial to string.\");\n        return NULL;\n    }\n    return buf;\n#endif /* MBEDTLS_VERSION_NUMBER < 0x04000000 */\n}\n\nchar *\nbackend_x509_get_serial_hex(mbedtls_x509_crt *cert, struct gc_arena *gc)\n{\n    char *buf = NULL;\n    size_t len = cert->serial.len * 3 + 1;\n\n    buf = gc_malloc(len, true, gc);\n\n    if (mbedtls_x509_serial_gets(buf, len - 1, &cert->serial) < 0)\n    {\n        buf = NULL;\n    }\n\n    return buf;\n}\n\nresult_t\nbackend_x509_write_pem(openvpn_x509_cert_t *cert, const char *filename)\n{\n    /* mbed TLS does not make it easy to write a certificate in PEM format.\n     * The only way is to directly access the DER encoded raw certificate\n     * and PEM encode it ourselves */\n\n    struct gc_arena gc = gc_new();\n    /* just do a very loose upper bound for the base64 based PEM encoding\n     * using 3 times the space for the base64 and 100 bytes for the\n     * headers and footer */\n    struct buffer pem = alloc_buf_gc(cert->raw.len * 3 + 100, &gc);\n\n    struct buffer der = { 0 };\n    buf_set_read(&der, cert->raw.p, cert->raw.len);\n\n    if (!crypto_pem_encode(\"CERTIFICATE\", &pem, &der, &gc))\n    {\n        goto err;\n    }\n\n    if (!buffer_write_file(filename, &pem))\n    {\n        goto err;\n    }\n\n    gc_free(&gc);\n    return SUCCESS;\nerr:\n    msg(D_TLS_DEBUG_LOW, \"Error writing X509 certificate to file %s\", filename);\n    gc_free(&gc);\n    return FAILURE;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif\n\nstatic struct buffer\nx509_get_fingerprint(const mbedtls_md_info_t *md_info, mbedtls_x509_crt *cert, struct gc_arena *gc)\n{\n    const size_t md_size = mbedtls_md_get_size(md_info);\n    struct buffer fingerprint = alloc_buf_gc(md_size, gc);\n    mbedtls_md(md_info, cert->raw.p, cert->raw.len, BPTR(&fingerprint));\n    ASSERT(buf_inc_len(&fingerprint, md_size));\n    return fingerprint;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nstruct buffer\nx509_get_sha1_fingerprint(mbedtls_x509_crt *cert, struct gc_arena *gc)\n{\n    return x509_get_fingerprint(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), cert, gc);\n}\n\nstruct buffer\nx509_get_sha256_fingerprint(mbedtls_x509_crt *cert, struct gc_arena *gc)\n{\n    return x509_get_fingerprint(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), cert, gc);\n}\n\nchar *\nx509_get_subject(mbedtls_x509_crt *cert, struct gc_arena *gc)\n{\n    char tmp_subject[MAX_SUBJECT_LENGTH] = { 0 };\n    char *subject = NULL;\n\n    int ret = 0;\n\n    ret = mbedtls_x509_dn_gets(tmp_subject, MAX_SUBJECT_LENGTH - 1, &cert->subject);\n    if (ret > 0)\n    {\n        /* Allocate the required space for the subject */\n        subject = string_alloc(tmp_subject, gc);\n    }\n\n    return subject;\n}\n\nstatic void\ndo_setenv_x509(struct env_set *es, const char *name, char *value, int depth)\n{\n    char *name_expand;\n    size_t name_expand_size;\n\n    string_mod(value, CC_ANY, CC_CRLF, '?');\n    msg(D_X509_ATTR, \"X509 ATTRIBUTE name='%s' value='%s' depth=%d\", name, value, depth);\n    name_expand_size = 64 + strlen(name);\n    name_expand = (char *)malloc(name_expand_size);\n    check_malloc_return(name_expand);\n    snprintf(name_expand, name_expand_size, \"X509_%d_%s\", depth, name);\n    setenv_str(es, name_expand, value);\n    free(name_expand);\n}\n\nstatic char *\nasn1_buf_to_c_string(const mbedtls_asn1_buf *orig, struct gc_arena *gc)\n{\n    size_t i;\n    char *val;\n\n    if (!(orig->tag == MBEDTLS_ASN1_UTF8_STRING || orig->tag == MBEDTLS_ASN1_PRINTABLE_STRING\n          || orig->tag == MBEDTLS_ASN1_IA5_STRING))\n    {\n        /* Only support C-string compatible types */\n        return string_alloc(\"ERROR: unsupported ASN.1 string type\", gc);\n    }\n\n    for (i = 0; i < orig->len; ++i)\n    {\n        if (orig->p[i] == '\\0')\n        {\n            return string_alloc(\"ERROR: embedded null value\", gc);\n        }\n    }\n    val = gc_malloc(orig->len + 1, false, gc);\n    memcpy(val, orig->p, orig->len);\n    val[orig->len] = '\\0';\n    return val;\n}\n\nstatic void\ndo_setenv_name(struct env_set *es, const struct x509_track *xt, const mbedtls_x509_crt *cert,\n               int depth, struct gc_arena *gc)\n{\n    const mbedtls_x509_name *xn;\n    for (xn = &cert->subject; xn != NULL; xn = xn->next)\n    {\n        const char *xn_short_name = NULL;\n        if (0 == mbedtls_oid_get_attr_short_name(&xn->oid, &xn_short_name)\n            && 0 == strcmp(xt->name, xn_short_name))\n        {\n            char *val_str = asn1_buf_to_c_string(&xn->val, gc);\n            do_setenv_x509(es, xt->name, val_str, depth);\n        }\n    }\n}\n\nvoid\nx509_track_add(const struct x509_track **ll_head, const char *name, msglvl_t msglevel,\n               struct gc_arena *gc)\n{\n    struct x509_track *xt;\n    ALLOC_OBJ_CLEAR_GC(xt, struct x509_track, gc);\n    if (*name == '+')\n    {\n        xt->flags |= XT_FULL_CHAIN;\n        ++name;\n    }\n    xt->name = name;\n    xt->next = *ll_head;\n    *ll_head = xt;\n}\n\nvoid\nx509_setenv_track(const struct x509_track *xt, struct env_set *es, const int depth,\n                  mbedtls_x509_crt *cert)\n{\n    struct gc_arena gc = gc_new();\n    while (xt)\n    {\n        if (depth == 0 || (xt->flags & XT_FULL_CHAIN))\n        {\n            if (0 == strcmp(xt->name, \"SHA1\") || 0 == strcmp(xt->name, \"SHA256\"))\n            {\n                /* Fingerprint is not part of X509 structure */\n                struct buffer cert_hash;\n                char *fingerprint;\n\n                if (0 == strcmp(xt->name, \"SHA1\"))\n                {\n                    cert_hash = x509_get_sha1_fingerprint(cert, &gc);\n                }\n                else\n                {\n                    cert_hash = x509_get_sha256_fingerprint(cert, &gc);\n                }\n\n                fingerprint =\n                    format_hex_ex(BPTR(&cert_hash), BLEN(&cert_hash), 0, 1 | FHE_CAPS, \":\", &gc);\n                do_setenv_x509(es, xt->name, fingerprint, depth);\n            }\n            else\n            {\n                do_setenv_name(es, xt, cert, depth, &gc);\n            }\n        }\n        xt = xt->next;\n    }\n    gc_free(&gc);\n}\n\n/*\n * Save X509 fields to environment, using the naming convention:\n *\n * X509_{cert_depth}_{name}={value}\n */\nvoid\nx509_setenv(struct env_set *es, int cert_depth, mbedtls_x509_crt *cert)\n{\n    unsigned char c;\n    const mbedtls_x509_name *name;\n    char s[128] = { 0 };\n\n    name = &cert->subject;\n\n    while (name != NULL)\n    {\n        char name_expand[64 + 8];\n        const char *shortname;\n\n        if (0 == mbedtls_oid_get_attr_short_name(&name->oid, &shortname))\n        {\n            snprintf(name_expand, sizeof(name_expand), \"X509_%d_%s\", cert_depth, shortname);\n        }\n        else\n        {\n            snprintf(name_expand, sizeof(name_expand), \"X509_%d_\\?\\?\", cert_depth);\n        }\n\n        size_t i;\n        for (i = 0; i < name->val.len; i++)\n        {\n            if (i >= sizeof(s) - 1)\n            {\n                break;\n            }\n\n            c = name->val.p[i];\n            if (c < 32 || c == 127 || (c > 128 && c < 160))\n            {\n                s[i] = '?';\n            }\n            else\n            {\n                s[i] = c;\n            }\n        }\n        s[i] = '\\0';\n\n        /* Check both strings, set environment variable */\n        string_mod(name_expand, CC_PRINT, CC_CRLF, '_');\n        string_mod((char *)s, CC_PRINT, CC_CRLF, '_');\n        setenv_str_incr(es, name_expand, (char *)s);\n\n        name = name->next;\n    }\n}\n\n/* Dummy function because Netscape certificate types are not supported in OpenVPN with mbedtls.\n * Returns SUCCESS if usage is NS_CERT_CHECK_NONE, FAILURE otherwise. */\nresult_t\nx509_verify_ns_cert_type(mbedtls_x509_crt *cert, const int usage)\n{\n    if (usage == NS_CERT_CHECK_NONE)\n    {\n        return SUCCESS;\n    }\n\n    return FAILURE;\n}\n\nresult_t\nx509_verify_cert_ku(mbedtls_x509_crt *cert, const unsigned int *const expected_ku, size_t expected_len)\n{\n    msg(D_HANDSHAKE, \"Validating certificate key usage\");\n\n    if (!mbedtls_x509_crt_has_ext_type(cert, MBEDTLS_X509_EXT_KEY_USAGE))\n    {\n        msg(D_TLS_ERRORS, \"ERROR: Certificate does not have key usage extension\");\n        return FAILURE;\n    }\n\n    if (expected_ku[0] == OPENVPN_KU_REQUIRED)\n    {\n        /* Extension required, value checked by TLS library */\n        return SUCCESS;\n    }\n\n    result_t fFound = FAILURE;\n    for (size_t i = 0; SUCCESS != fFound && i < expected_len; i++)\n    {\n        if (expected_ku[i] != 0 && 0 == mbedtls_x509_crt_check_key_usage(cert, expected_ku[i]))\n        {\n            fFound = SUCCESS;\n        }\n    }\n\n    if (fFound != SUCCESS)\n    {\n        msg(D_TLS_ERRORS, \"ERROR: Certificate has invalid key usage, expected one of:\");\n        for (size_t i = 0; i < expected_len && expected_ku[i]; i++)\n        {\n            msg(D_TLS_ERRORS, \" * %04x\", expected_ku[i]);\n        }\n    }\n\n    return fFound;\n}\n\nresult_t\nx509_verify_cert_eku(mbedtls_x509_crt *cert, const char *const expected_oid)\n{\n    result_t fFound = FAILURE;\n\n    if (!mbedtls_x509_crt_has_ext_type(cert, MBEDTLS_X509_EXT_EXTENDED_KEY_USAGE))\n    {\n        msg(D_HANDSHAKE, \"Certificate does not have extended key usage extension\");\n    }\n    else\n    {\n        mbedtls_x509_sequence *oid_seq = &(cert->ext_key_usage);\n\n        msg(D_HANDSHAKE, \"Validating certificate extended key usage\");\n        while (oid_seq != NULL)\n        {\n            mbedtls_x509_buf *oid = &oid_seq->buf;\n            char oid_num_str[1024];\n            const char *oid_str;\n\n            if (0 == mbedtls_oid_get_extended_key_usage(oid, &oid_str))\n            {\n                msg(D_HANDSHAKE, \"++ Certificate has EKU (str) %s, expects %s\", oid_str,\n                    expected_oid);\n                if (!strcmp(expected_oid, oid_str))\n                {\n                    fFound = SUCCESS;\n                    break;\n                }\n            }\n\n            if (0 < mbedtls_oid_get_numeric_string(oid_num_str, sizeof(oid_num_str), oid))\n            {\n                msg(D_HANDSHAKE, \"++ Certificate has EKU (oid) %s, expects %s\", oid_num_str,\n                    expected_oid);\n                if (!strcmp(expected_oid, oid_num_str))\n                {\n                    fFound = SUCCESS;\n                    break;\n                }\n            }\n            oid_seq = oid_seq->next;\n        }\n    }\n\n    return fFound;\n}\n\nbool\ntls_verify_crl_missing(const struct tls_options *opt)\n{\n    if (opt->crl_file && !(opt->ssl_flags & SSLF_CRL_VERIFY_DIR)\n        && (opt->ssl_ctx->crl == NULL || opt->ssl_ctx->crl->version == 0))\n    {\n        return true;\n    }\n    return false;\n}\n\n#endif /* #if defined(ENABLE_CRYPTO_MBEDTLS) */\n"
  },
  {
    "path": "src/openvpn/ssl_verify_mbedtls.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel Verification Module mbed TLS backend\n */\n\n#ifndef SSL_VERIFY_MBEDTLS_H_\n#define SSL_VERIFY_MBEDTLS_H_\n\n#include \"syshead.h\"\n#include <mbedtls/x509_crt.h>\n\n#ifndef __OPENVPN_X509_CERT_T_DECLARED\n#define __OPENVPN_X509_CERT_T_DECLARED\ntypedef mbedtls_x509_crt openvpn_x509_cert_t;\n#endif\n\n/** @name Function for authenticating a new connection from a remote OpenVPN peer\n *  @{ */\n\n/**\n * Verify that the remote OpenVPN peer's certificate allows setting up a\n * VPN tunnel.\n * @ingroup control_tls\n *\n * This callback function is called when a new TLS session is being setup to\n * determine whether the remote OpenVPN peer's certificate is allowed to\n * connect. It is called for once for every certificate in the chain. The\n * callback functionality is configured in the \\c key_state_ssl_init() function,\n * which calls the mbed TLS library's \\c mbedtls_ssl_conf_verify() function with\n * \\c verify_callback() as its callback argument.\n *\n * It checks *flags and registers the certificate hash. If these steps succeed,\n * it calls the \\c verify_cert() function, which performs OpenVPN-specific\n * verification.\n *\n * @param session_obj  - The OpenVPN \\c tls_session associated with this object,\n *                       as set during SSL session setup.\n * @param cert         - The certificate used by mbed TLS.\n * @param cert_depth   - The depth of the current certificate in the chain, with\n *                       0 being the actual certificate.\n * @param flags        - Whether the remote OpenVPN peer's certificate\n *                       passed verification.  A value of 0 means it\n *                       verified successfully, any other value means it\n *                       failed. \\c verify_callback() is considered to have\n *                       ok'ed this certificate if flags is 0 when it returns.\n *\n * @return The return value is 0 unless a fatal error occurred.\n */\nint verify_callback(void *session_obj, mbedtls_x509_crt *cert, int cert_depth, uint32_t *flags);\n\n/** @} name Function for authenticating a new connection from a remote OpenVPN peer */\n\n#endif /* SSL_VERIFY_MBEDTLS_H_ */\n"
  },
  {
    "path": "src/openvpn/ssl_verify_openssl.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel Verification Module OpenSSL implementation\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#if defined(ENABLE_CRYPTO_OPENSSL)\n\n#include \"ssl_verify_openssl.h\"\n\n#include \"error.h\"\n#include \"ssl_openssl.h\"\n#include \"ssl_verify.h\"\n#include \"ssl_verify_backend.h\"\n#include \"openssl_compat.h\"\n\n#include <openssl/bn.h>\n#include <openssl/err.h>\n#include <openssl/x509v3.h>\n\nint\nverify_callback(int preverify_ok, X509_STORE_CTX *ctx)\n{\n    int ret = 0;\n    struct tls_session *session;\n    SSL *ssl;\n    struct gc_arena gc = gc_new();\n\n    /* get the tls_session pointer */\n    ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());\n    ASSERT(ssl);\n    session = (struct tls_session *)SSL_get_ex_data(ssl, mydata_index);\n    ASSERT(session);\n\n    X509 *current_cert = X509_STORE_CTX_get_current_cert(ctx);\n    struct buffer cert_hash = x509_get_sha256_fingerprint(current_cert, &gc);\n    cert_hash_remember(session, X509_STORE_CTX_get_error_depth(ctx), &cert_hash);\n\n    /* did peer present cert which was signed by our root cert? */\n    if (!preverify_ok && !session->opt->verify_hash_no_ca)\n    {\n        /* get the X509 name */\n        char *subject = x509_get_subject(current_cert, &gc);\n        char *serial = backend_x509_get_serial(current_cert, &gc);\n\n        if (!subject)\n        {\n            subject = \"(Failed to retrieve certificate subject)\";\n        }\n\n        /* Log and ignore missing CRL errors */\n        if (X509_STORE_CTX_get_error(ctx) == X509_V_ERR_UNABLE_TO_GET_CRL)\n        {\n            msg(D_TLS_DEBUG_LOW, \"VERIFY WARNING: depth=%d, %s: %s\",\n                X509_STORE_CTX_get_error_depth(ctx),\n                X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx)), subject);\n            ret = 1;\n            goto cleanup;\n        }\n\n        /* Remote site specified a certificate, but it's not correct */\n        msg(D_TLS_ERRORS, \"VERIFY ERROR: depth=%d, error=%s: %s, serial=%s\",\n            X509_STORE_CTX_get_error_depth(ctx),\n            X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx)), subject,\n            serial ? serial : \"<not available>\");\n\n        ERR_clear_error();\n\n        session->verified = false;\n        goto cleanup;\n    }\n\n    if (SUCCESS != verify_cert(session, current_cert, X509_STORE_CTX_get_error_depth(ctx)))\n    {\n        goto cleanup;\n    }\n\n    ret = 1;\n\ncleanup:\n    gc_free(&gc);\n\n    return ret;\n}\n\nbool\nx509_username_field_ext_supported(const char *fieldname)\n{\n    int nid = OBJ_txt2nid(fieldname);\n    return nid == NID_subject_alt_name || nid == NID_issuer_alt_name;\n}\n\nstatic bool\nextract_x509_extension(X509 *cert, char *fieldname, char *out, size_t size)\n{\n    bool retval = false;\n\n    if (!x509_username_field_ext_supported(fieldname))\n    {\n        msg(D_TLS_ERRORS, \"ERROR: --x509-username-field 'ext:%s' not supported\", fieldname);\n        return false;\n    }\n\n    int nid = OBJ_txt2nid(fieldname);\n    GENERAL_NAMES *extensions = X509_get_ext_d2i(cert, nid, NULL, NULL);\n    if (extensions)\n    {\n        /* get amount of alternatives,\n         * RFC2459 claims there MUST be at least\n         * one, but we don't depend on it...\n         */\n\n        int numalts = sk_GENERAL_NAME_num(extensions);\n\n        /* loop through all alternatives */\n        for (int i = 0; i < numalts; i++)\n        {\n            /* get a handle to alternative name number i */\n            const GENERAL_NAME *name = sk_GENERAL_NAME_value(extensions, i);\n            char *buf = NULL;\n\n            switch (name->type)\n            {\n                case GEN_EMAIL:\n                    if (ASN1_STRING_to_UTF8((unsigned char **)&buf, name->d.rfc822Name) < 0)\n                    {\n                        continue;\n                    }\n                    if ((ssize_t)strlen(buf) != ASN1_STRING_length(name->d.rfc822Name))\n                    {\n                        msg(D_TLS_ERRORS, \"ASN1 ERROR: string contained terminating zero\");\n                        OPENSSL_free(buf);\n                    }\n                    else\n                    {\n                        strncpynt(out, buf, size);\n                        OPENSSL_free(buf);\n                        retval = true;\n                    }\n                    break;\n\n                default:\n                    msg(D_TLS_DEBUG, \"%s: ignoring general name field type %d\", __func__,\n                        name->type);\n                    break;\n            }\n        }\n        GENERAL_NAMES_free(extensions);\n    }\n    return retval;\n}\n\n/*\n * Extract a field from an X509 subject name.\n *\n * Example:\n *\n * /C=US/ST=CO/L=Denver/O=ORG/CN=First-CN/CN=Test-CA/Email=jim@yonan.net\n *\n * The common name is 'Test-CA'\n *\n * Return true on success, false on error (insufficient buffer size in 'out'\n * to contain result is grounds for error).\n */\nstatic result_t\nextract_x509_field_ssl(X509_NAME *x509, const char *field_name, char *out, size_t size)\n{\n    int lastpos = -1;\n    int tmp = -1;\n    X509_NAME_ENTRY *x509ne = NULL;\n    ASN1_STRING *asn1 = NULL;\n    unsigned char *buf = NULL;\n\n    ASN1_OBJECT *field_name_obj = OBJ_txt2obj(field_name, 0);\n    if (field_name_obj == NULL)\n    {\n        msg(D_TLS_ERRORS, \"Invalid X509 attribute name '%s'\", field_name);\n        return FAILURE;\n    }\n\n    ASSERT(size > 0);\n    *out = '\\0';\n    do\n    {\n        lastpos = tmp;\n        tmp = X509_NAME_get_index_by_OBJ(x509, field_name_obj, lastpos);\n    } while (tmp > -1);\n\n    ASN1_OBJECT_free(field_name_obj);\n\n    /* Nothing found */\n    if (lastpos == -1)\n    {\n        return FAILURE;\n    }\n\n    x509ne = X509_NAME_get_entry(x509, lastpos);\n    if (!x509ne)\n    {\n        return FAILURE;\n    }\n\n    asn1 = X509_NAME_ENTRY_get_data(x509ne);\n    if (!asn1)\n    {\n        return FAILURE;\n    }\n    if (ASN1_STRING_to_UTF8(&buf, asn1) < 0)\n    {\n        return FAILURE;\n    }\n\n    strncpynt(out, (char *)buf, size);\n\n    const result_t ret = (strlen((char *)buf) < size) ? SUCCESS : FAILURE;\n    OPENSSL_free(buf);\n    return ret;\n}\n\nresult_t\nbackend_x509_get_username(char *common_name, size_t cn_len, char *x509_username_field, X509 *peer_cert)\n{\n    if (strncmp(\"ext:\", x509_username_field, 4) == 0)\n    {\n        if (!extract_x509_extension(peer_cert, x509_username_field + 4, common_name, cn_len))\n        {\n            return FAILURE;\n        }\n    }\n    else if (strcmp(LN_serialNumber, x509_username_field) == 0)\n    {\n        ASN1_INTEGER *asn1_i = X509_get_serialNumber(peer_cert);\n        struct gc_arena gc = gc_new();\n        char *serial = format_hex_ex(asn1_i->data, asn1_i->length, 0, 1 | FHE_CAPS, NULL, &gc);\n\n        if (!serial || cn_len <= strlen(serial) + 2)\n        {\n            gc_free(&gc);\n            return FAILURE;\n        }\n        snprintf(common_name, cn_len, \"0x%s\", serial);\n        gc_free(&gc);\n    }\n    else\n    {\n        X509_NAME *x509_subject_name = X509_get_subject_name(peer_cert);\n        if (x509_subject_name == NULL)\n        {\n            msg(D_TLS_ERRORS, \"X509 subject name is NULL\");\n            return FAILURE;\n        }\n\n        if (FAILURE\n            == extract_x509_field_ssl(x509_subject_name, x509_username_field,\n                                      common_name, cn_len))\n        {\n            return FAILURE;\n        }\n    }\n\n    return SUCCESS;\n}\n\nchar *\nbackend_x509_get_serial(openvpn_x509_cert_t *cert, struct gc_arena *gc)\n{\n    ASN1_INTEGER *asn1_i;\n    BIGNUM *bignum;\n    char *openssl_serial, *serial;\n\n    asn1_i = X509_get_serialNumber(cert);\n    bignum = ASN1_INTEGER_to_BN(asn1_i, NULL);\n    openssl_serial = BN_bn2dec(bignum);\n\n    serial = string_alloc(openssl_serial, gc);\n\n    BN_free(bignum);\n    OPENSSL_free(openssl_serial);\n\n    return serial;\n}\n\nchar *\nbackend_x509_get_serial_hex(openvpn_x509_cert_t *cert, struct gc_arena *gc)\n{\n    const ASN1_INTEGER *asn1_i = X509_get_serialNumber(cert);\n\n    return format_hex_ex(asn1_i->data, asn1_i->length, 0, 1, \":\", gc);\n}\n\nresult_t\nbackend_x509_write_pem(openvpn_x509_cert_t *cert, const char *filename)\n{\n    BIO *out = BIO_new_file(filename, \"w\");\n    if (!out)\n    {\n        goto err;\n    }\n\n    if (!PEM_write_bio_X509(out, cert))\n    {\n        goto err;\n    }\n    BIO_free(out);\n\n    return SUCCESS;\nerr:\n    BIO_free(out);\n    crypto_msg(D_TLS_DEBUG_LOW, \"Error writing X509 certificate to file %s\", filename);\n    return FAILURE;\n}\n\nstruct buffer\nx509_get_sha1_fingerprint(X509 *cert, struct gc_arena *gc)\n{\n    const EVP_MD *sha1 = EVP_sha1();\n    struct buffer hash = alloc_buf_gc((size_t)EVP_MD_size(sha1), gc);\n    X509_digest(cert, EVP_sha1(), BPTR(&hash), NULL);\n    ASSERT(buf_inc_len(&hash, EVP_MD_size(sha1)));\n    return hash;\n}\n\nstruct buffer\nx509_get_sha256_fingerprint(X509 *cert, struct gc_arena *gc)\n{\n    const EVP_MD *sha256 = EVP_sha256();\n    struct buffer hash = alloc_buf_gc((size_t)EVP_MD_size(sha256), gc);\n    X509_digest(cert, EVP_sha256(), BPTR(&hash), NULL);\n    ASSERT(buf_inc_len(&hash, EVP_MD_size(sha256)));\n    return hash;\n}\n\nchar *\nx509_get_subject(X509 *cert, struct gc_arena *gc)\n{\n    BIO *subject_bio = NULL;\n    BUF_MEM *subject_mem;\n    char *subject = NULL;\n\n    subject_bio = BIO_new(BIO_s_mem());\n    if (subject_bio == NULL)\n    {\n        goto err;\n    }\n\n    X509_NAME_print_ex(subject_bio, X509_get_subject_name(cert), 0,\n                       XN_FLAG_SEP_CPLUS_SPC | XN_FLAG_FN_SN | ASN1_STRFLGS_UTF8_CONVERT\n                           | ASN1_STRFLGS_ESC_CTRL);\n\n    if (BIO_eof(subject_bio))\n    {\n        goto err;\n    }\n\n    BIO_get_mem_ptr(subject_bio, &subject_mem);\n\n    subject = gc_malloc(subject_mem->length + 1, false, gc);\n\n    memcpy(subject, subject_mem->data, subject_mem->length);\n    subject[subject_mem->length] = '\\0';\n\nerr:\n    BIO_free(subject_bio);\n    return subject;\n}\n\n\n/*\n * x509-track implementation -- save X509 fields to environment,\n * using the naming convention:\n *\n *  X509_{cert_depth}_{name}={value}\n *\n * This function differs from x509_setenv below in the following ways:\n *\n * (1) Only explicitly named attributes in xt are saved, per usage\n *     of \"x509-track\" program options.\n * (2) Only the level 0 cert info is saved unless the XT_FULL_CHAIN\n *     flag is set in xt->flags (corresponds with prepending a '+'\n *     to the name when specified by \"x509-track\" program option).\n * (3) This function supports both X509 subject name fields as\n *     well as X509 V3 extensions.\n * (4) This function can return the SHA1 fingerprint of a cert, e.g.\n *       x509-track \"+SHA1\"\n *     will return the SHA1 fingerprint for each certificate in the\n *     peer chain.\n */\n\nvoid\nx509_track_add(const struct x509_track **ll_head, const char *name, msglvl_t msglevel,\n               struct gc_arena *gc)\n{\n    struct x509_track *xt;\n    ALLOC_OBJ_CLEAR_GC(xt, struct x509_track, gc);\n    if (*name == '+')\n    {\n        xt->flags |= XT_FULL_CHAIN;\n        ++name;\n    }\n    xt->name = name;\n    xt->nid = OBJ_txt2nid(name);\n    if (xt->nid != NID_undef)\n    {\n        xt->next = *ll_head;\n        *ll_head = xt;\n    }\n    else\n    {\n        msg(msglevel, \"x509_track: no such attribute '%s'\", name);\n    }\n}\n\n/* worker method for setenv_x509_track */\nstatic void\ndo_setenv_x509(struct env_set *es, const char *name, char *value, int depth)\n{\n    char *name_expand;\n    size_t name_expand_size;\n\n    string_mod(value, CC_ANY, CC_CRLF, '?');\n    msg(D_X509_ATTR, \"X509 ATTRIBUTE name='%s' value='%s' depth=%d\", name, value, depth);\n    name_expand_size = 64 + strlen(name);\n    name_expand = (char *)malloc(name_expand_size);\n    check_malloc_return(name_expand);\n    snprintf(name_expand, name_expand_size, \"X509_%d_%s\", depth, name);\n    setenv_str(es, name_expand, value);\n    free(name_expand);\n}\n\nvoid\nx509_setenv_track(const struct x509_track *xt, struct env_set *es, const int depth, X509 *x509)\n{\n    struct gc_arena gc = gc_new();\n    X509_NAME *x509_name = X509_get_subject_name(x509);\n    const char nullc = '\\0';\n\n    while (xt)\n    {\n        if (depth == 0 || (xt->flags & XT_FULL_CHAIN))\n        {\n            switch (xt->nid)\n            {\n                case NID_sha1:\n                case NID_sha256:\n                {\n                    struct buffer fp_buf;\n                    char *fp_str = NULL;\n\n                    if (xt->nid == NID_sha1)\n                    {\n                        fp_buf = x509_get_sha1_fingerprint(x509, &gc);\n                    }\n                    else\n                    {\n                        fp_buf = x509_get_sha256_fingerprint(x509, &gc);\n                    }\n\n                    fp_str = format_hex_ex(BPTR(&fp_buf), BLEN(&fp_buf), 0, 1 | FHE_CAPS, \":\", &gc);\n                    do_setenv_x509(es, xt->name, fp_str, depth);\n                }\n                break;\n\n                default:\n                {\n                    int i = X509_NAME_get_index_by_NID(x509_name, xt->nid, -1);\n                    if (i >= 0)\n                    {\n                        X509_NAME_ENTRY *ent = X509_NAME_get_entry(x509_name, i);\n                        if (ent)\n                        {\n                            ASN1_STRING *val = X509_NAME_ENTRY_get_data(ent);\n                            unsigned char *buf = NULL;\n                            if (ASN1_STRING_to_UTF8(&buf, val) >= 0)\n                            {\n                                do_setenv_x509(es, xt->name, (char *)buf, depth);\n                                OPENSSL_free(buf);\n                            }\n                        }\n                    }\n                    else\n                    {\n                        i = X509_get_ext_by_NID(x509, xt->nid, -1);\n                        if (i >= 0)\n                        {\n                            X509_EXTENSION *ext = X509_get_ext(x509, i);\n                            if (ext)\n                            {\n                                BIO *bio = BIO_new(BIO_s_mem());\n                                if (bio)\n                                {\n                                    if (X509V3_EXT_print(bio, ext, 0, 0))\n                                    {\n                                        if (BIO_write(bio, &nullc, 1) == 1)\n                                        {\n                                            char *str;\n                                            BIO_get_mem_data(bio, &str);\n                                            do_setenv_x509(es, xt->name, str, depth);\n                                        }\n                                    }\n                                    BIO_free(bio);\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        xt = xt->next;\n    }\n    gc_free(&gc);\n}\n\n/*\n * Save X509 fields to environment, using the naming convention:\n *\n *  X509_{cert_depth}_{name}={value}\n */\nvoid\nx509_setenv(struct env_set *es, int cert_depth, openvpn_x509_cert_t *peer_cert)\n{\n    int i, n;\n    int fn_nid;\n    ASN1_OBJECT *fn;\n    ASN1_STRING *val;\n    X509_NAME_ENTRY *ent;\n    const char *objbuf;\n    unsigned char *buf = NULL;\n    char *name_expand;\n    size_t name_expand_size;\n    X509_NAME *x509 = X509_get_subject_name(peer_cert);\n\n    n = X509_NAME_entry_count(x509);\n    for (i = 0; i < n; ++i)\n    {\n        ent = X509_NAME_get_entry(x509, i);\n        if (!ent)\n        {\n            continue;\n        }\n        fn = X509_NAME_ENTRY_get_object(ent);\n        if (!fn)\n        {\n            continue;\n        }\n        val = X509_NAME_ENTRY_get_data(ent);\n        if (!val)\n        {\n            continue;\n        }\n        fn_nid = OBJ_obj2nid(fn);\n        if (fn_nid == NID_undef)\n        {\n            continue;\n        }\n        objbuf = OBJ_nid2sn(fn_nid);\n        if (!objbuf)\n        {\n            continue;\n        }\n        if (ASN1_STRING_to_UTF8(&buf, val) < 0)\n        {\n            continue;\n        }\n        name_expand_size = 64 + strlen(objbuf);\n        name_expand = (char *)malloc(name_expand_size);\n        check_malloc_return(name_expand);\n        snprintf(name_expand, name_expand_size, \"X509_%d_%s\", cert_depth, objbuf);\n        string_mod(name_expand, CC_PRINT, CC_CRLF, '_');\n        string_mod((char *)buf, CC_PRINT, CC_CRLF, '_');\n        setenv_str_incr(es, name_expand, (char *)buf);\n        free(name_expand);\n        OPENSSL_free(buf);\n    }\n}\n\nresult_t\nx509_verify_ns_cert_type(openvpn_x509_cert_t *peer_cert, const int usage)\n{\n    if (usage == NS_CERT_CHECK_NONE)\n    {\n        return SUCCESS;\n    }\n    if (usage == NS_CERT_CHECK_CLIENT)\n    {\n        /*\n         * Unfortunately, X509_check_purpose() does some weird thing that\n         * prevent it to take a const argument\n         */\n        result_t result =\n            X509_check_purpose(peer_cert, X509_PURPOSE_SSL_CLIENT, 0) ? SUCCESS : FAILURE;\n\n        /*\n         * old versions of OpenSSL allow us to make the less strict check we used to\n         * do. If this less strict check pass, warn user that this might not be the\n         * case when its distribution will update to OpenSSL 1.1\n         */\n        if (result == FAILURE)\n        {\n            ASN1_BIT_STRING *ns;\n            ns = X509_get_ext_d2i(peer_cert, NID_netscape_cert_type, NULL, NULL);\n            result = (ns && ns->length > 0 && (ns->data[0] & NS_SSL_CLIENT)) ? SUCCESS : FAILURE;\n            if (result == SUCCESS)\n            {\n                msg(M_WARN, \"X509: Certificate is a client certificate yet it's purpose \"\n                            \"cannot be verified (check may fail in the future)\");\n            }\n            ASN1_BIT_STRING_free(ns);\n        }\n        return result;\n    }\n    if (usage == NS_CERT_CHECK_SERVER)\n    {\n        /*\n         * Unfortunately, X509_check_purpose() does some weird thing that\n         * prevent it to take a const argument\n         */\n        result_t result =\n            X509_check_purpose(peer_cert, X509_PURPOSE_SSL_SERVER, 0) ? SUCCESS : FAILURE;\n\n        /*\n         * old versions of OpenSSL allow us to make the less strict check we used to\n         * do. If this less strict check pass, warn user that this might not be the\n         * case when its distribution will update to OpenSSL 1.1\n         */\n        if (result == FAILURE)\n        {\n            ASN1_BIT_STRING *ns;\n            ns = X509_get_ext_d2i(peer_cert, NID_netscape_cert_type, NULL, NULL);\n            result = (ns && ns->length > 0 && (ns->data[0] & NS_SSL_SERVER)) ? SUCCESS : FAILURE;\n            if (result == SUCCESS)\n            {\n                msg(M_WARN, \"X509: Certificate is a server certificate yet it's purpose \"\n                            \"cannot be verified (check may fail in the future)\");\n            }\n            ASN1_BIT_STRING_free(ns);\n        }\n        return result;\n    }\n\n    return FAILURE;\n}\n\nresult_t\nx509_verify_cert_ku(X509 *x509, const unsigned int *const expected_ku, size_t expected_len)\n{\n    ASN1_BIT_STRING *ku = X509_get_ext_d2i(x509, NID_key_usage, NULL, NULL);\n\n    if (ku == NULL)\n    {\n        msg(D_TLS_ERRORS, \"Certificate does not have key usage extension\");\n        return FAILURE;\n    }\n\n    if (expected_ku[0] == OPENVPN_KU_REQUIRED)\n    {\n        /* Extension required, value checked by TLS library */\n        ASN1_BIT_STRING_free(ku);\n        return SUCCESS;\n    }\n\n    unsigned int nku = 0;\n    for (int i = 0; i < 8; i++)\n    {\n        if (ASN1_BIT_STRING_get_bit(ku, i))\n        {\n            nku |= 1 << (7 - i);\n        }\n    }\n\n    /*\n     * Fixup if no LSB bits\n     */\n    if ((nku & 0xff) == 0)\n    {\n        nku >>= 8;\n    }\n\n    msg(D_HANDSHAKE, \"Validating certificate key usage\");\n    result_t fFound = FAILURE;\n    for (size_t i = 0; fFound != SUCCESS && i < expected_len; i++)\n    {\n        if (expected_ku[i] != 0 && (nku & expected_ku[i]) == expected_ku[i])\n        {\n            fFound = SUCCESS;\n        }\n    }\n\n    if (fFound != SUCCESS)\n    {\n        msg(D_TLS_ERRORS, \"ERROR: Certificate has key usage %04x, expected one of:\", nku);\n        for (size_t i = 0; i < expected_len && expected_ku[i]; i++)\n        {\n            msg(D_TLS_ERRORS, \" * %04x\", expected_ku[i]);\n        }\n    }\n\n    ASN1_BIT_STRING_free(ku);\n\n    return fFound;\n}\n\nresult_t\nx509_verify_cert_eku(X509 *x509, const char *const expected_oid)\n{\n    EXTENDED_KEY_USAGE *eku = NULL;\n    result_t fFound = FAILURE;\n\n    if ((eku = (EXTENDED_KEY_USAGE *)X509_get_ext_d2i(x509, NID_ext_key_usage, NULL, NULL)) == NULL)\n    {\n        msg(D_HANDSHAKE, \"Certificate does not have extended key usage extension\");\n    }\n    else\n    {\n        int i;\n\n        msg(D_HANDSHAKE, \"Validating certificate extended key usage\");\n        for (i = 0; SUCCESS != fFound && i < sk_ASN1_OBJECT_num(eku); i++)\n        {\n            ASN1_OBJECT *oid = sk_ASN1_OBJECT_value(eku, i);\n            char szOid[1024];\n\n            if (SUCCESS != fFound && OBJ_obj2txt(szOid, sizeof(szOid), oid, 0) != -1)\n            {\n                msg(D_HANDSHAKE, \"++ Certificate has EKU (str) %s, expects %s\", szOid,\n                    expected_oid);\n                if (!strcmp(expected_oid, szOid))\n                {\n                    fFound = SUCCESS;\n                }\n            }\n            if (SUCCESS != fFound && OBJ_obj2txt(szOid, sizeof(szOid), oid, 1) != -1)\n            {\n                msg(D_HANDSHAKE, \"++ Certificate has EKU (oid) %s, expects %s\", szOid,\n                    expected_oid);\n                if (!strcmp(expected_oid, szOid))\n                {\n                    fFound = SUCCESS;\n                }\n            }\n        }\n    }\n\n    if (eku != NULL)\n    {\n        sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free);\n    }\n\n    return fFound;\n}\n\nbool\ntls_verify_crl_missing(const struct tls_options *opt)\n{\n    if (!opt->crl_file || (opt->ssl_flags & SSLF_CRL_VERIFY_DIR))\n    {\n        return false;\n    }\n\n    X509_STORE *store = SSL_CTX_get_cert_store(opt->ssl_ctx->ctx);\n    if (!store)\n    {\n        crypto_msg(M_FATAL, \"Cannot get certificate store\");\n    }\n\n    STACK_OF(X509_OBJECT) *objs = X509_STORE_get0_objects(store);\n    for (int i = 0; i < sk_X509_OBJECT_num(objs); i++)\n    {\n        X509_OBJECT *obj = sk_X509_OBJECT_value(objs, i);\n        ASSERT(obj);\n        if (X509_OBJECT_get_type(obj) == X509_LU_CRL)\n        {\n            return false;\n        }\n    }\n    return true;\n}\n\n#endif /* defined(ENABLE_CRYPTO_OPENSSL) */\n"
  },
  {
    "path": "src/openvpn/ssl_verify_openssl.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @file\n * Control Channel Verification Module OpenSSL backend\n */\n\n\n#ifndef SSL_VERIFY_OPENSSL_H_\n#define SSL_VERIFY_OPENSSL_H_\n\n#include <openssl/x509.h>\n\n#ifndef __OPENVPN_X509_CERT_T_DECLARED\n#define __OPENVPN_X509_CERT_T_DECLARED\ntypedef X509 openvpn_x509_cert_t;\n#endif\n\n/** @name Function for authenticating a new connection from a remote OpenVPN peer\n *  @{ */\n\n/**\n * Verify that the remote OpenVPN peer's certificate allows setting up a\n * VPN tunnel.\n * @ingroup control_tls\n *\n * This callback function is called every time a new TLS session is being\n * setup to determine whether the remote OpenVPN peer's certificate is\n * allowed to connect. It is called for once for every certificate in the chain.\n * The callback functionality is configured in the \\c init_ssl() function, which\n * calls the OpenSSL library's \\c SSL_CTX_set_verify() function with \\c\n * verify_callback() as its callback argument.\n *\n * It checks preverify_ok, and registers the certificate hash. If these steps\n * succeed, it calls the \\c verify_cert() function, which performs\n * OpenVPN-specific verification.\n *\n * @param preverify_ok - Whether the remote OpenVPN peer's certificate\n *                       past verification.  A value of 1 means it\n *                       verified successfully, 0 means it failed.\n * @param ctx          - The complete context used by the OpenSSL library\n *                       to verify the certificate chain.\n *\n * @return The return value indicates whether the supplied certificate is\n *     allowed to set up a VPN tunnel.  The following values can be\n *     returned:\n *      - \\c 0: failure, this certificate is not allowed to connect.\n *      - \\c 1: success, this certificate is allowed to connect.\n */\nint verify_callback(int preverify_ok, X509_STORE_CTX *ctx);\n\n/** @} name Function for authenticating a new connection from a remote OpenVPN peer */\n\n#endif /* SSL_VERIFY_OPENSSL_H_ */\n"
  },
  {
    "path": "src/openvpn/status.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"status.h\"\n#include \"misc.h\"\n#include \"fdmisc.h\"\n\n#include \"memdbg.h\"\n\n/*\n * printf-style interface for outputting status info\n */\n\nstatic const char *\nprint_status_mode(unsigned int flags)\n{\n    switch (flags)\n    {\n        case STATUS_OUTPUT_WRITE:\n            return \"WRITE\";\n\n        case STATUS_OUTPUT_READ:\n            return \"READ\";\n\n        case STATUS_OUTPUT_READ | STATUS_OUTPUT_WRITE:\n            return \"READ/WRITE\";\n\n        default:\n            return \"UNDEF\";\n    }\n}\n\nstruct status_output *\nstatus_open(const char *filename, const int refresh_freq, const int msglevel,\n            const struct virtual_output *vout, const unsigned int flags)\n{\n    struct status_output *so = NULL;\n    if (filename || msglevel >= 0 || vout)\n    {\n        ALLOC_OBJ_CLEAR(so, struct status_output);\n        so->flags = flags;\n        so->msglevel = msglevel;\n        so->vout = vout;\n        so->fd = -1;\n        buf_reset(&so->read_buf);\n        event_timeout_clear(&so->et);\n        if (filename)\n        {\n            switch (so->flags)\n            {\n                case STATUS_OUTPUT_WRITE:\n                    so->fd =\n                        platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR);\n                    break;\n\n                case STATUS_OUTPUT_READ:\n                    so->fd = platform_open(filename, O_RDONLY, S_IRUSR | S_IWUSR);\n                    break;\n\n                case STATUS_OUTPUT_READ | STATUS_OUTPUT_WRITE:\n                    so->fd = platform_open(filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);\n                    break;\n\n                default:\n                    ASSERT(0);\n            }\n            if (so->fd >= 0)\n            {\n                so->filename = string_alloc(filename, NULL);\n                set_cloexec(so->fd);\n\n                /* allocate read buffer */\n                if (so->flags & STATUS_OUTPUT_READ)\n                {\n                    so->read_buf = alloc_buf(512);\n                }\n            }\n            else\n            {\n                msg(M_WARN, \"Note: cannot open %s for %s\", filename, print_status_mode(so->flags));\n                so->errors = true;\n            }\n        }\n        else\n        {\n            so->flags = STATUS_OUTPUT_WRITE;\n        }\n\n        if ((so->flags & STATUS_OUTPUT_WRITE) && refresh_freq > 0)\n        {\n            event_timeout_init(&so->et, refresh_freq, 0);\n        }\n    }\n    return so;\n}\n\nbool\nstatus_trigger(struct status_output *so)\n{\n    if (so)\n    {\n        struct timeval null;\n        CLEAR(null);\n        return event_timeout_trigger(&so->et, &null, ETT_DEFAULT);\n    }\n    else\n    {\n        return false;\n    }\n}\n\nvoid\nstatus_reset(struct status_output *so)\n{\n    if (so && so->fd >= 0)\n    {\n        lseek(so->fd, (off_t)0, SEEK_SET);\n    }\n}\n\nvoid\nstatus_flush(struct status_output *so)\n{\n    if (so && so->fd >= 0 && (so->flags & STATUS_OUTPUT_WRITE))\n    {\n#if defined(HAVE_FTRUNCATE)\n        {\n            const off_t off = lseek(so->fd, (off_t)0, SEEK_CUR);\n            if (ftruncate(so->fd, off) != 0)\n            {\n                msg(M_WARN | M_ERRNO, \"Failed to truncate status file\");\n            }\n        }\n#elif defined(HAVE_CHSIZE)\n        {\n            const long off = (long)lseek(so->fd, (off_t)0, SEEK_CUR);\n            chsize(so->fd, off);\n        }\n#else /* if defined(HAVE_FTRUNCATE) */\n#warning both ftruncate and chsize functions appear to be missing from this OS\n#endif\n\n        /* clear read buffer */\n        if (buf_defined(&so->read_buf))\n        {\n            ASSERT(buf_init(&so->read_buf, 0));\n        }\n    }\n}\n\n/* return false if error occurred */\nbool\nstatus_close(struct status_output *so)\n{\n    bool ret = true;\n    if (so)\n    {\n        if (so->errors)\n        {\n            ret = false;\n        }\n        if (so->fd >= 0)\n        {\n            if (close(so->fd) < 0)\n            {\n                ret = false;\n            }\n        }\n        free(so->filename);\n\n        if (buf_defined(&so->read_buf))\n        {\n            free_buf(&so->read_buf);\n        }\n        free(so);\n    }\n    else\n    {\n        ret = false;\n    }\n    return ret;\n}\n\n#define STATUS_PRINTF_MAXLEN 512\n\nvoid\nstatus_printf(struct status_output *so, const char *format, ...)\n{\n    if (so && (so->flags & STATUS_OUTPUT_WRITE))\n    {\n        char buf[STATUS_PRINTF_MAXLEN + 2]; /* leave extra bytes for CR, LF */\n        va_list arglist;\n        int stat;\n\n        va_start(arglist, format);\n        stat = vsnprintf(buf, STATUS_PRINTF_MAXLEN, format, arglist);\n        va_end(arglist);\n        buf[STATUS_PRINTF_MAXLEN - 1] = 0;\n\n        if (stat < 0 || stat >= STATUS_PRINTF_MAXLEN)\n        {\n            so->errors = true;\n        }\n\n        if (so->msglevel >= 0 && !so->errors)\n        {\n            msg((msglvl_t)so->msglevel, \"%s\", buf);\n        }\n\n        if (so->fd >= 0 && !so->errors)\n        {\n            strcat(buf, \"\\n\");\n            ssize_t len = strlen(buf);\n            if (len > 0)\n            {\n                if (write(so->fd, buf, (unsigned int)len) != len)\n                {\n                    so->errors = true;\n                }\n            }\n        }\n\n        if (so->vout && !so->errors)\n        {\n            chomp(buf);\n            (*so->vout->func)(so->vout->arg, so->vout->flags_default, buf);\n        }\n    }\n}\n\nbool\nstatus_read(struct status_output *so, struct buffer *buf)\n{\n    bool ret = false;\n\n    if (so && so->fd >= 0 && (so->flags & STATUS_OUTPUT_READ))\n    {\n        ASSERT(buf_defined(&so->read_buf));\n        ASSERT(buf_defined(buf));\n        while (true)\n        {\n            const int c = buf_read_u8(&so->read_buf);\n\n            /* read more of file into buffer */\n            if (c == -1)\n            {\n                ASSERT(buf_init(&so->read_buf, 0));\n                ssize_t len = read(so->fd, BPTR(&so->read_buf), BCAP(&so->read_buf));\n                if (len <= 0)\n                {\n                    break;\n                }\n\n                ASSERT(buf_inc_len(&so->read_buf, (int)len));\n                continue;\n            }\n\n            ret = true;\n\n            if (c == '\\r')\n            {\n                continue;\n            }\n\n            if (c == '\\n')\n            {\n                break;\n            }\n\n            buf_write_u8(buf, (uint8_t)c);\n        }\n\n        buf_null_terminate(buf);\n    }\n\n    return ret;\n}\n"
  },
  {
    "path": "src/openvpn/status.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef STATUS_H\n#define STATUS_H\n\n#include \"interval.h\"\n\n/*\n * virtual function interface for status output\n */\nstruct virtual_output\n{\n    void *arg;\n    unsigned int flags_default;\n    void (*func)(void *arg, const unsigned int flags, const char *str);\n};\n\nstatic inline void\nvirtual_output_print(const struct virtual_output *vo, const unsigned int flags, const char *str)\n{\n    (*vo->func)(vo->arg, flags, str);\n}\n\n/*\n * printf-style interface for inputting/outputting status info\n */\n\nstruct status_output\n{\n#define STATUS_OUTPUT_READ  (1 << 0)\n#define STATUS_OUTPUT_WRITE (1 << 1)\n    unsigned int flags;\n\n    char *filename;\n    int fd;\n    /* NB: -1 is used to indicate that output should only go to the file */\n    int msglevel;\n    const struct virtual_output *vout;\n\n    struct buffer read_buf;\n\n    struct event_timeout et;\n\n    bool errors;\n};\n\nstruct status_output *status_open(const char *filename, const int refresh_freq, const int msglevel,\n                                  const struct virtual_output *vout, const unsigned int flags);\n\nbool status_trigger(struct status_output *so);\n\nvoid status_reset(struct status_output *so);\n\nvoid status_flush(struct status_output *so);\n\nbool status_close(struct status_output *so);\n\nvoid status_printf(struct status_output *so, const char *format, ...)\n#ifdef __GNUC__\n#if __USE_MINGW_ANSI_STDIO\n    __attribute__((format(gnu_printf, 2, 3)))\n#else\n    __attribute__((format(__printf__, 2, 3)))\n#endif\n#endif\n    ;\n\nbool status_read(struct status_output *so, struct buffer *buf);\n\nstatic inline unsigned int\nstatus_rw_flags(const struct status_output *so)\n{\n    if (so)\n    {\n        return so->flags;\n    }\n    else\n    {\n        return 0;\n    }\n}\n\n#endif /* ifndef STATUS_H */\n"
  },
  {
    "path": "src/openvpn/syshead.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef SYSHEAD_H\n#define SYSHEAD_H\n\n#include \"compat.h\"\n#include <stdbool.h>\n\n/* branch prediction hints */\n#if defined(__GNUC__)\n#define likely(x)   __builtin_expect((x), 1)\n#define unlikely(x) __builtin_expect((x), 0)\n#else\n#define likely(x)   (x)\n#define unlikely(x) (x)\n#endif\n\n#ifdef _WIN32\n#include <windows.h>\n#include <winsock2.h>\n#include <tlhelp32.h>\n#define sleep(x) Sleep((x) * 1000)\n#define random   rand\n#define srandom  srand\n#endif\n\n/* if inttypes.h is included this breaks rc.exe when using the ClangCL\n * Toolchain as it pulls in a inttypes.h variant for clang that rc.exe does\n * not understand (#include_next preprocessor directive) */\n#if defined(_WIN32) && !defined(RC_INVOKED)\n#include <inttypes.h>\ntypedef uint32_t in_addr_t;\ntypedef uint16_t in_port_t;\n\n#define SIGHUP  1\n#define SIGINT  2\n#define SIGUSR1 10\n#define SIGUSR2 12\n#define SIGTERM 15\n#endif\n\n#if defined(_MSC_VER) && !defined(RC_INVOKED)\n#include <BaseTsd.h>\ntypedef SSIZE_T ssize_t;\n#define strncasecmp strnicmp\n#define strcasecmp  _stricmp\n\n#define S_IRUSR _S_IREAD\n#define S_IWUSR _S_IWRITE\n#define R_OK    4\n#define W_OK    2\n#define X_OK    1\n#define F_OK    0\n#endif\n\n#if defined(_MSC_VER) && !defined(__clang__) /* Microsoft compiler */\n#define __func__ __FUNCTION__\n#define __attribute__(x)\n#endif\n\n#if defined(__APPLE__)\n#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1070\n#define __APPLE_USE_RFC_3542 1\n#endif\n#endif\n\n#ifdef HAVE_SYS_TYPES_H\n#include <sys/types.h>\n#endif\n\n#ifdef HAVE_SYS_WAIT_H\n#include <sys/wait.h>\n#endif\n\n#ifndef _WIN32\n#ifndef WEXITSTATUS\n#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)\n#endif\n#ifndef WIFEXITED\n#define WIFEXITED(stat_val) (((stat_val) & 255) == 0)\n#endif\n#endif\n\n#ifdef HAVE_SYS_TIME_H\n#include <sys/time.h>\n#endif\n\n#include <time.h>\n\n#ifdef HAVE_SYS_SOCKET_H\n#include <sys/socket.h>\n#endif\n\n#ifdef HAVE_SYS_UN_H\n#include <sys/un.h>\n#endif\n\n#ifdef HAVE_SYS_IOCTL_H\n#include <sys/ioctl.h>\n#endif\n\n#ifdef HAVE_SYS_STAT_H\n#include <sys/stat.h>\n#endif\n\n#ifdef HAVE_FCNTL_H\n#include <fcntl.h>\n#endif\n\n#ifdef HAVE_SYS_FILE_H\n#include <sys/file.h>\n#endif\n\n/* These headers belong to C99 and should be always be present */\n#include <stdlib.h>\n#include <inttypes.h>\n#include <stdint.h>\n#include <stdarg.h>\n#include <signal.h>\n#include <limits.h>\n#include <stdio.h>\n#include <ctype.h>\n#include <errno.h>\n\n#ifdef HAVE_UNISTD_H\n#include <unistd.h>\n#endif\n\n#ifdef HAVE_ERR_H\n#include <err.h>\n#endif\n\n#ifdef HAVE_SYSLOG_H\n#include <syslog.h>\n#endif\n\n#ifdef HAVE_PWD_H\n#include <pwd.h>\n#endif\n\n#ifdef HAVE_GRP_H\n#include <grp.h>\n#endif\n\n#ifdef HAVE_NETDB_H\n#include <netdb.h>\n#endif\n\n#ifdef HAVE_NETINET_IN_H\n#include <netinet/in.h>\n#endif\n\n#ifdef HAVE_RESOLV_H\n#include <resolv.h>\n#endif\n\n#ifdef HAVE_POLL_H\n#include <poll.h>\n#endif\n\n#ifdef ENABLE_SELINUX\n#include <selinux/selinux.h>\n#endif\n\n#if defined(HAVE_LIBGEN_H)\n#include <libgen.h>\n#endif\n\n#ifdef TARGET_SOLARIS\n#ifdef HAVE_STRINGS_H\n#include <strings.h>\n#endif\n#else\n#include <string.h>\n#endif\n\n#if defined(TARGET_HAIKU)\n#include <SupportDefs.h> /* uint32, etc */\n#include <net/if.h>      /* ifconf etc */\n#include <sys/sockio.h>  /* SIOCGRTTABLE, etc */\n#endif                   /* TARGET_HAIKU */\n\n#ifdef HAVE_ARPA_INET_H\n#include <arpa/inet.h>\n#endif\n\n#ifdef HAVE_NET_IF_H\n#include <net/if.h>\n#endif\n\n#ifdef TARGET_NETBSD\n#include <net/if_tap.h>\n#endif\n\n#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)\n\n#define EXTENDED_SOCKET_ERROR_CAPABILITY 1\n\n#ifdef TARGET_LINUX\n#define ENABLE_FEATURE_TUN_PERSIST\n#endif\n\n#include <linux/if_tun.h>\n#include <linux/sockios.h>\n\n#ifdef HAVE_NETINET_IP_H\n#include <netinet/ip.h>\n#endif\n\n#ifdef HAVE_NETINET_TCP_H\n#include <netinet/tcp.h>\n#endif\n\n#endif /* TARGET_LINUX */\n\n#ifdef TARGET_SOLARIS\n\n#ifdef HAVE_STROPTS_H\n#include <stropts.h>\n#undef S_ERROR\n#endif\n\n#ifdef HAVE_NET_IF_TUN_H\n#include <net/if_tun.h>\n#endif\n\n#ifdef HAVE_SYS_SOCKIO_H\n#include <sys/sockio.h>\n#endif\n\n#ifdef HAVE_NETINET_IP_H\n#include <netinet/ip.h>\n#endif\n\n#ifdef HAVE_NETINET_TCP_H\n#include <netinet/tcp.h>\n#endif\n\n#endif /* TARGET_SOLARIS */\n\n#ifdef TARGET_OPENBSD\n\n#ifdef HAVE_SYS_UIO_H\n#include <sys/uio.h>\n#endif\n\n#ifdef HAVE_NETINET_IP_H\n#include <netinet/ip.h>\n#endif\n\n#ifdef HAVE_NETINET_TCP_H\n#include <netinet/tcp.h>\n#endif\n\n#ifdef HAVE_NET_IF_TUN_H\n#include <net/if_tun.h>\n#endif\n\n#endif /* TARGET_OPENBSD */\n\n#ifdef TARGET_FREEBSD\n\n#ifdef HAVE_SYS_UIO_H\n#include <sys/uio.h>\n#endif\n\n#ifdef HAVE_NETINET_IP_H\n#include <netinet/ip.h>\n#endif\n\n#ifdef HAVE_NETINET_TCP_H\n#include <netinet/tcp.h>\n#endif\n\n#ifdef HAVE_NET_IF_TUN_H\n#include <net/if_tun.h>\n#endif\n\n#endif /* TARGET_FREEBSD */\n\n#ifdef TARGET_NETBSD\n\n#ifdef HAVE_NET_IF_TUN_H\n#include <net/if_tun.h>\n#endif\n\n#ifdef HAVE_NETINET_TCP_H\n#include <netinet/tcp.h>\n#endif\n\n#endif /* TARGET_NETBSD */\n\n#ifdef TARGET_DRAGONFLY\n\n#ifdef HAVE_SYS_UIO_H\n#include <sys/uio.h>\n#endif\n\n#ifdef HAVE_NETINET_IP_H\n#include <netinet/ip.h>\n#endif\n\n#ifdef HAVE_NET_TUN_IF_TUN_H\n#include <net/tun/if_tun.h>\n#endif\n\n#endif /* TARGET_DRAGONFLY */\n\n#ifdef TARGET_DARWIN\n\n#ifdef HAVE_NETINET_TCP_H\n#include <netinet/tcp.h>\n#endif\n\n#endif /* TARGET_DARWIN */\n\n#ifdef _WIN32\n/* Missing declarations for MinGW 32. */\n#if defined(__MINGW32__)\ntypedef int MIB_TCP_STATE;\n#endif\n#include <naptypes.h>\n#include <ntddndis.h>\n#include <iphlpapi.h>\n#include <wininet.h>\n#include <shellapi.h>\n#include <io.h>\n\n/* The following two headers are needed of PF_INET6 */\n#include <winsock2.h>\n#include <ws2tcpip.h>\n#endif\n\n#ifdef HAVE_SYS_MMAN_H\n#ifdef TARGET_DARWIN\n#define _P1003_1B_VISIBLE\n#endif /* TARGET_DARWIN */\n#include <sys/mman.h>\n#endif\n\n#ifndef _WIN32\n#include <sys/utsname.h>\n#endif\n\n/*\n * Pedantic mode is meant to accomplish lint-style program checking,\n * not to build a working executable.\n */\n#ifdef PEDANTIC\n#undef HAVE_CPP_VARARG_MACRO_GCC\n#undef HAVE_CPP_VARARG_MACRO_ISO\n#undef inline\n#define inline\n#endif\n\n/*\n * Do we have the capability to support the --passtos option?\n */\n#if defined(IPPROTO_IP) && defined(IP_TOS)\n#define PASSTOS_CAPABILITY 1\n#else\n#define PASSTOS_CAPABILITY 0\n#endif\n\n/*\n * Does this platform support linux-style IP_PKTINFO\n * or bsd-style IP_RECVDSTADDR ?\n */\n#if ((defined(HAVE_IN_PKTINFO) && defined(IP_PKTINFO)) || defined(IP_RECVDSTADDR)) \\\n    && defined(HAVE_MSGHDR) && defined(HAVE_CMSGHDR) && defined(CMSG_FIRSTHDR)     \\\n    && defined(CMSG_NXTHDR) && defined(HAVE_RECVMSG) && defined(HAVE_SENDMSG)\n#define ENABLE_IP_PKTINFO 1\n#else\n#define ENABLE_IP_PKTINFO 0\n#endif\n\n/*\n * Does this platform define SOL_IP\n * or only bsd-style IPPROTO_IP ?\n */\n#ifndef SOL_IP\n#define SOL_IP IPPROTO_IP\n#endif\n\n/*\n * Define type sa_family_t if it isn't defined in the socket headers\n */\n#ifndef HAVE_SA_FAMILY_T\ntypedef unsigned short sa_family_t;\n#endif\n\n/*\n * Do we have a syslog capability?\n */\n#if defined(HAVE_OPENLOG) && defined(HAVE_SYSLOG)\n#define SYSLOG_CAPABILITY 1\n#else\n#define SYSLOG_CAPABILITY 0\n#endif\n\n/*\n * Does this OS draw a distinction between binary and ascii files?\n */\n#ifndef O_BINARY\n#define O_BINARY 0\n#endif\n\n/*\n * Directory separation char\n */\n#ifdef _WIN32\n#define PATH_SEPARATOR     '\\\\'\n#define PATH_SEPARATOR_STR \"\\\\\"\n#else\n#define PATH_SEPARATOR     '/'\n#define PATH_SEPARATOR_STR \"/\"\n#endif\n\n/*\n * Our socket descriptor type.\n */\n#ifdef _WIN32\n#define SOCKET_UNDEFINED (INVALID_SOCKET)\n#define SOCKET_PRINTF    \"%\" PRIxPTR\ntypedef SOCKET socket_descriptor_t;\n#else\n#define SOCKET_UNDEFINED (-1)\n#define SOCKET_PRINTF    \"%d\"\ntypedef int socket_descriptor_t;\n#endif\n\nstatic inline int\nsocket_defined(const socket_descriptor_t sd)\n{\n    return sd != SOCKET_UNDEFINED;\n}\n\n/*\n * Should we enable the use of execve() for calling subprocesses,\n * instead of system()?\n */\n#if defined(HAVE_EXECVE) && defined(HAVE_FORK)\n#define ENABLE_FEATURE_EXECVE\n#endif\n\n/*\n * HTTPS port sharing capability\n */\n#if defined(ENABLE_PORT_SHARE) && defined(SCM_RIGHTS) && defined(HAVE_MSGHDR)  \\\n    && defined(HAVE_CMSGHDR) && defined(CMSG_FIRSTHDR) && defined(CMSG_NXTHDR) \\\n    && defined(HAVE_RECVMSG) && defined(HAVE_SENDMSG)\n#define PORT_SHARE 1\n#else\n#define PORT_SHARE 0\n#endif\n\n/*\n * Do we support Unix domain sockets?\n */\n#if defined(PF_UNIX) && !defined(_WIN32)\n#define UNIX_SOCK_SUPPORT 1\n#else\n#define UNIX_SOCK_SUPPORT 0\n#endif\n\n/*\n * Should we include proxy digest auth functionality\n */\n#define PROXY_DIGEST_AUTH 1\n\n/*\n * Do we have CryptoAPI capability?\n */\n#if defined(_WIN32) && defined(ENABLE_CRYPTO_OPENSSL) && !defined(ENABLE_CRYPTO_WOLFSSL)\n#define ENABLE_CRYPTOAPI\n#endif\n\n/*\n * Is poll available on this platform?\n * (Note: on win32 select is faster than poll and we avoid\n * using poll there)\n */\n#if defined(HAVE_POLL_H) || !defined(_WIN32)\n#define POLL 1\n#else\n#define POLL 0\n#endif\n\n/*\n * Is epoll available on this platform?\n */\n#if defined(HAVE_EPOLL_CREATE) && defined(HAVE_SYS_EPOLL_H)\n#define EPOLL 1\n#else\n#define EPOLL 0\n#endif\n\n/*\n * Compression support\n */\n#if defined(ENABLE_LZO) || defined(ENABLE_LZ4) || defined(ENABLE_COMP_STUB)\n#define USE_COMP\n#endif\n\n#ifdef _MSC_VER\n#ifndef PATH_MAX\n#define PATH_MAX MAX_PATH\n#endif\n#endif\n\n#endif /* ifndef SYSHEAD_H */\n"
  },
  {
    "path": "src/openvpn/tls_crypt.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"argv.h\"\n#include \"base64.h\"\n#include \"crypto.h\"\n#include \"integer.h\"\n#include \"platform.h\"\n#include \"run_command.h\"\n#include \"session_id.h\"\n#include \"ssl.h\"\n\n#include \"tls_crypt.h\"\n\nconst char *tls_crypt_v2_cli_pem_name = \"OpenVPN tls-crypt-v2 client key\";\nconst char *tls_crypt_v2_srv_pem_name = \"OpenVPN tls-crypt-v2 server key\";\n\n/** Metadata contains user-specified data */\nstatic const uint8_t TLS_CRYPT_METADATA_TYPE_USER = 0x00;\n/** Metadata contains a 64-bit unix timestamp in network byte order */\nstatic const uint8_t TLS_CRYPT_METADATA_TYPE_TIMESTAMP = 0x01;\n\nstatic struct key_type\ntls_crypt_kt(void)\n{\n    return create_kt(\"AES-256-CTR\", \"SHA256\", \"tls-crypt\");\n}\n\nint\ntls_crypt_buf_overhead(void)\n{\n    return packet_id_size(true) + TLS_CRYPT_TAG_SIZE + TLS_CRYPT_BLOCK_SIZE;\n}\n\nvoid\ntls_crypt_init_key(struct key_ctx_bi *key, struct key2 *keydata, const char *key_file,\n                   bool key_inline, bool tls_server)\n{\n    const int key_direction = tls_server ? KEY_DIRECTION_NORMAL : KEY_DIRECTION_INVERSE;\n    struct key_type kt = tls_crypt_kt();\n    if (!kt.cipher || !kt.digest)\n    {\n        msg(M_FATAL, \"ERROR: --tls-crypt not supported\");\n    }\n    crypto_read_openvpn_key(&kt, key, key_file, key_inline, key_direction,\n                            \"Control Channel Encryption\", \"tls-crypt\", keydata);\n}\n\n/**\n * Will produce key = key XOR other\n */\nstatic void\nxor_key2(struct key2 *key, const struct key2 *other)\n{\n    ASSERT(key->n == 2 && other->n == 2);\n    for (int k = 0; k < 2; k++)\n    {\n        for (int j = 0; j < MAX_CIPHER_KEY_LENGTH; j++)\n        {\n            key->keys[k].cipher[j] = key->keys[k].cipher[j] ^ other->keys[k].cipher[j];\n        }\n\n        for (int j = 0; j < MAX_HMAC_KEY_LENGTH; j++)\n        {\n            key->keys[k].hmac[j] = key->keys[k].hmac[j] ^ other->keys[k].hmac[j];\n        }\n    }\n}\n\nbool\ntls_session_generate_dynamic_tls_crypt_key(struct tls_session *session)\n{\n    struct key2 rengokeys;\n    if (!key_state_export_keying_material(session, EXPORT_DYNAMIC_TLS_CRYPT_LABEL,\n                                          strlen(EXPORT_DYNAMIC_TLS_CRYPT_LABEL), rengokeys.keys,\n                                          sizeof(rengokeys.keys)))\n    {\n        return false;\n    }\n    rengokeys.n = 2;\n\n    session->tls_wrap_reneg.opt = session->tls_wrap.opt;\n    session->tls_wrap_reneg.mode = TLS_WRAP_CRYPT;\n    session->tls_wrap_reneg.cleanup_key_ctx = true;\n    session->tls_wrap_reneg.work = alloc_buf(BUF_SIZE(&session->opt->frame));\n    session->tls_wrap_reneg.opt.pid_persist = NULL;\n\n    packet_id_init(&session->tls_wrap_reneg.opt.packet_id, session->opt->replay_window,\n                   session->opt->replay_time, \"TLS_WRAP_RENEG\", session->key_id);\n\n    if (session->tls_wrap.mode == TLS_WRAP_CRYPT || session->tls_wrap.mode == TLS_WRAP_AUTH)\n    {\n        xor_key2(&rengokeys, &session->tls_wrap.original_wrap_keydata);\n    }\n\n    const int key_direction = session->opt->server ? KEY_DIRECTION_NORMAL : KEY_DIRECTION_INVERSE;\n\n    struct key_direction_state kds;\n    key_direction_state_init(&kds, key_direction);\n\n    struct key_type kt = tls_crypt_kt();\n\n    init_key_ctx_bi(&session->tls_wrap_reneg.opt.key_ctx_bi, &rengokeys, key_direction, &kt,\n                    \"dynamic tls-crypt\");\n    secure_memzero(&rengokeys, sizeof(rengokeys));\n\n    return true;\n}\n\n\nbool\ntls_crypt_wrap(const struct buffer *src, struct buffer *dst, struct crypto_options *opt)\n{\n    const struct key_ctx *ctx = &opt->key_ctx_bi.encrypt;\n    struct gc_arena gc;\n\n    /* IV, packet-ID and implicit IV required for this mode. */\n    ASSERT(ctx->cipher);\n    ASSERT(ctx->hmac);\n    ASSERT(packet_id_initialized(&opt->packet_id));\n    ASSERT(hmac_ctx_size(ctx->hmac) == 256 / 8);\n\n    gc_init(&gc);\n\n    dmsg(D_PACKET_CONTENT, \"TLS-CRYPT WRAP FROM: %s\", format_hex(BPTR(src), BLEN(src), 80, &gc));\n\n    /* Get packet ID */\n    if (!packet_id_write(&opt->packet_id.send, dst, true, false))\n    {\n        msg(D_CRYPT_ERRORS, \"TLS-CRYPT ERROR: packet ID roll over.\");\n        goto err;\n    }\n\n    dmsg(D_PACKET_CONTENT, \"TLS-CRYPT WRAP AD: %s\", format_hex(BPTR(dst), BLEN(dst), 0, &gc));\n\n    /* Buffer overflow check */\n    if (!buf_safe(dst, BLENZ(src) + TLS_CRYPT_BLOCK_SIZE + TLS_CRYPT_TAG_SIZE))\n    {\n        msg(D_CRYPT_ERRORS,\n            \"TLS-CRYPT WRAP: buffer size error, \"\n            \"sc=%d so=%d sl=%d dc=%d do=%d dl=%d\",\n            src->capacity, src->offset, src->len, dst->capacity, dst->offset, dst->len);\n        goto err;\n    }\n\n    /* Calculate auth tag and synthetic IV */\n    {\n        uint8_t *tag = NULL;\n        hmac_ctx_reset(ctx->hmac);\n        hmac_ctx_update(ctx->hmac, BPTR(dst), BLEN(dst));\n        hmac_ctx_update(ctx->hmac, BPTR(src), BLEN(src));\n\n        ASSERT(tag = buf_write_alloc(dst, TLS_CRYPT_TAG_SIZE));\n        hmac_ctx_final(ctx->hmac, tag);\n\n        dmsg(D_PACKET_CONTENT, \"TLS-CRYPT WRAP TAG: %s\",\n             format_hex(tag, TLS_CRYPT_TAG_SIZE, 0, &gc));\n\n        /* Use the 128 most significant bits of the tag as IV */\n        ASSERT(cipher_ctx_reset(ctx->cipher, tag));\n    }\n\n    /* Encrypt src */\n    {\n        int outlen = 0;\n        ASSERT(cipher_ctx_update(ctx->cipher, BEND(dst), &outlen, BPTR(src), BLEN(src)));\n        ASSERT(buf_inc_len(dst, outlen));\n        ASSERT(cipher_ctx_final(ctx->cipher, BPTR(dst), &outlen));\n        ASSERT(buf_inc_len(dst, outlen));\n    }\n\n    dmsg(D_PACKET_CONTENT, \"TLS-CRYPT WRAP TO: %s\", format_hex(BPTR(dst), BLEN(dst), 80, &gc));\n\n    gc_free(&gc);\n    return true;\n\nerr:\n    crypto_clear_error();\n    dst->len = 0;\n    gc_free(&gc);\n    return false;\n}\n\nbool\ntls_crypt_unwrap(const struct buffer *src, struct buffer *dst, struct crypto_options *opt)\n{\n    static const char error_prefix[] = \"tls-crypt unwrap error\";\n    const struct key_ctx *ctx = &opt->key_ctx_bi.decrypt;\n    struct gc_arena gc;\n\n    gc_init(&gc);\n\n    ASSERT(opt);\n    ASSERT(src->len > 0);\n    ASSERT(ctx->cipher);\n    ASSERT(packet_id_initialized(&opt->packet_id) || (opt->flags & CO_IGNORE_PACKET_ID));\n\n    dmsg(D_PACKET_CONTENT, \"TLS-CRYPT UNWRAP FROM: %s\", format_hex(BPTR(src), BLEN(src), 80, &gc));\n\n    if (BLENZ(src) < TLS_CRYPT_OFF_CT)\n    {\n        CRYPT_ERROR(\"packet too short\");\n    }\n\n    /* Decrypt cipher text */\n    {\n        int outlen = 0;\n\n        /* Buffer overflow check (should never fail) */\n        if (!buf_safe(dst, BLENZ(src) - TLS_CRYPT_OFF_CT + TLS_CRYPT_BLOCK_SIZE))\n        {\n            CRYPT_ERROR(\"potential buffer overflow\");\n        }\n\n        if (!cipher_ctx_reset(ctx->cipher, BPTR(src) + TLS_CRYPT_OFF_TAG))\n        {\n            CRYPT_ERROR(\"cipher reset failed\");\n        }\n        if (!cipher_ctx_update(ctx->cipher, BPTR(dst), &outlen, BPTR(src) + TLS_CRYPT_OFF_CT,\n                               BLEN(src) - (int)TLS_CRYPT_OFF_CT))\n        {\n            CRYPT_ERROR(\"cipher update failed\");\n        }\n        ASSERT(buf_inc_len(dst, outlen));\n        if (!cipher_ctx_final(ctx->cipher, BPTR(dst), &outlen))\n        {\n            CRYPT_ERROR(\"cipher final failed\");\n        }\n        ASSERT(buf_inc_len(dst, outlen));\n    }\n\n    /* Check authentication */\n    {\n        const uint8_t *tag = BPTR(src) + TLS_CRYPT_OFF_TAG;\n        uint8_t tag_check[TLS_CRYPT_TAG_SIZE] = { 0 };\n\n        dmsg(D_PACKET_CONTENT, \"TLS-CRYPT UNWRAP AD: %s\",\n             format_hex(BPTR(src), TLS_CRYPT_OFF_TAG, 0, &gc));\n        dmsg(D_PACKET_CONTENT, \"TLS-CRYPT UNWRAP TO: %s\",\n             format_hex(BPTR(dst), BLEN(dst), 80, &gc));\n\n        hmac_ctx_reset(ctx->hmac);\n        hmac_ctx_update(ctx->hmac, BPTR(src), TLS_CRYPT_OFF_TAG);\n        hmac_ctx_update(ctx->hmac, BPTR(dst), BLEN(dst));\n        hmac_ctx_final(ctx->hmac, tag_check);\n\n        if (memcmp_constant_time(tag, tag_check, sizeof(tag_check)))\n        {\n            dmsg(D_CRYPTO_DEBUG, \"tag      : %s\", format_hex(tag, sizeof(tag_check), 0, &gc));\n            dmsg(D_CRYPTO_DEBUG, \"tag_check: %s\", format_hex(tag_check, sizeof(tag_check), 0, &gc));\n            CRYPT_ERROR(\"packet authentication failed\");\n        }\n    }\n\n    /* Check replay */\n    if (!(opt->flags & CO_IGNORE_PACKET_ID))\n    {\n        struct packet_id_net pin;\n        struct buffer tmp = *src;\n        ASSERT(buf_advance(&tmp, TLS_CRYPT_OFF_PID));\n        ASSERT(packet_id_read(&pin, &tmp, true));\n        if (!crypto_check_replay(opt, &pin, 0, error_prefix, &gc))\n        {\n            CRYPT_ERROR(\"packet replay\");\n        }\n    }\n\n    gc_free(&gc);\n    return true;\n\nerror_exit:\n    crypto_clear_error();\n    dst->len = 0;\n    gc_free(&gc);\n    return false;\n}\n\nstatic inline void\ntls_crypt_v2_load_client_key(struct key_ctx_bi *key, const struct key2 *key2, bool tls_server)\n{\n    const int key_direction = tls_server ? KEY_DIRECTION_NORMAL : KEY_DIRECTION_INVERSE;\n    struct key_type kt = tls_crypt_kt();\n    if (!kt.cipher || !kt.digest)\n    {\n        msg(M_FATAL, \"ERROR: --tls-crypt-v2 not supported\");\n    }\n    init_key_ctx_bi(key, key2, key_direction, &kt, \"Control Channel Encryption\");\n}\n\nvoid\ntls_crypt_v2_init_client_key(struct key_ctx_bi *key, struct key2 *original_key,\n                             struct buffer *wkc_buf, const char *key_file, bool key_inline)\n{\n    struct buffer client_key = alloc_buf(TLS_CRYPT_V2_CLIENT_KEY_LEN + TLS_CRYPT_V2_MAX_WKC_LEN);\n\n    if (!read_pem_key_file(&client_key, tls_crypt_v2_cli_pem_name, key_file, key_inline))\n    {\n        msg(M_FATAL, \"ERROR: invalid tls-crypt-v2 client key format\");\n    }\n\n    struct key2 key2 = { .n = 2 };\n    if (!buf_read(&client_key, &key2.keys, sizeof(key2.keys)))\n    {\n        msg(M_FATAL, \"ERROR: not enough data in tls-crypt-v2 client key\");\n    }\n\n    tls_crypt_v2_load_client_key(key, &key2, false);\n    *original_key = key2;\n\n    *wkc_buf = client_key;\n}\n\nvoid\ntls_crypt_v2_init_server_key(struct key_ctx *key_ctx, bool encrypt, const char *key_file,\n                             bool key_inline)\n{\n    struct key srv_key;\n    struct buffer srv_key_buf;\n\n    buf_set_write(&srv_key_buf, (void *)&srv_key, sizeof(srv_key));\n    if (!read_pem_key_file(&srv_key_buf, tls_crypt_v2_srv_pem_name, key_file, key_inline))\n    {\n        msg(M_FATAL, \"ERROR: invalid tls-crypt-v2 server key format\");\n    }\n\n    struct key_type kt = tls_crypt_kt();\n    if (!kt.cipher || !kt.digest)\n    {\n        msg(M_FATAL, \"ERROR: --tls-crypt-v2 not supported\");\n    }\n    struct key_parameters srv_key_params;\n\n    key_parameters_from_key(&srv_key_params, &srv_key);\n\n    init_key_ctx(key_ctx, &srv_key_params, &kt, encrypt, \"tls-crypt-v2 server key\");\n    secure_memzero(&srv_key, sizeof(srv_key));\n}\n\nstatic bool\ntls_crypt_v2_wrap_client_key(struct buffer *wkc, const struct key2 *src_key,\n                             const struct buffer *src_metadata, struct key_ctx *server_key,\n                             struct gc_arena *gc)\n{\n    cipher_ctx_t *cipher_ctx = server_key->cipher;\n    struct buffer work =\n        alloc_buf_gc(TLS_CRYPT_V2_MAX_WKC_LEN + cipher_ctx_block_size(cipher_ctx), gc);\n\n    /* Calculate auth tag and synthetic IV */\n    uint8_t *tag = buf_write_alloc(&work, TLS_CRYPT_TAG_SIZE);\n    if (!tag)\n    {\n        msg(M_WARN, \"ERROR: could not write tag\");\n        return false;\n    }\n    const int data_len = BLEN(src_metadata) + sizeof(src_key->keys) + sizeof(uint16_t);\n    const int tagged_len = data_len + TLS_CRYPT_TAG_SIZE;\n    const uint16_t net_len = htons((uint16_t)tagged_len);\n    hmac_ctx_t *hmac_ctx = server_key->hmac;\n    hmac_ctx_reset(hmac_ctx);\n    hmac_ctx_update(hmac_ctx, (void *)&net_len, sizeof(net_len));\n    hmac_ctx_update(hmac_ctx, (void *)src_key->keys, sizeof(src_key->keys));\n    hmac_ctx_update(hmac_ctx, BPTR(src_metadata), BLEN(src_metadata));\n    hmac_ctx_final(hmac_ctx, tag);\n\n    dmsg(D_CRYPTO_DEBUG, \"TLS-CRYPT WRAP TAG: %s\", format_hex(tag, TLS_CRYPT_TAG_SIZE, 0, gc));\n\n    /* Use the 128 most significant bits of the tag as IV */\n    ASSERT(cipher_ctx_reset(cipher_ctx, tag));\n\n    /* Overflow check (OpenSSL requires an extra block in the dst buffer) */\n    const int padded_len = data_len + cipher_ctx_block_size(cipher_ctx);\n    if (buf_forward_capacity(&work) < padded_len)\n    {\n        msg(M_WARN, \"ERROR: could not crypt: insufficient space in dst\");\n        return false;\n    }\n\n    /* Encrypt */\n    int outlen = 0;\n    ASSERT(cipher_ctx_update(cipher_ctx, BEND(&work), &outlen, (void *)src_key->keys,\n                             sizeof(src_key->keys)));\n    ASSERT(buf_inc_len(&work, outlen));\n    ASSERT(cipher_ctx_update(cipher_ctx, BEND(&work), &outlen, BPTR(src_metadata),\n                             BLEN(src_metadata)));\n    ASSERT(buf_inc_len(&work, outlen));\n    ASSERT(cipher_ctx_final(cipher_ctx, BEND(&work), &outlen));\n    ASSERT(buf_inc_len(&work, outlen));\n    ASSERT(buf_write(&work, &net_len, sizeof(net_len)));\n\n    return buf_copy(wkc, &work);\n}\n\nstatic bool\ntls_crypt_v2_unwrap_client_key(struct key2 *client_key, struct buffer *metadata,\n                               struct buffer wrapped_client_key, struct key_ctx *server_key)\n{\n    const char *error_prefix = __func__;\n    bool ret = false;\n    struct gc_arena gc = gc_new();\n    /* The crypto API requires one extra cipher block of buffer head room when\n     * decrypting, which nicely matches the tag size of WKc.  So\n     * TLS_CRYPT_V2_MAX_WKC_LEN is always large enough for the plaintext. */\n    uint8_t plaintext_buf_data[TLS_CRYPT_V2_MAX_WKC_LEN] = { 0 };\n    struct buffer plaintext = { 0 };\n\n    dmsg(D_TLS_DEBUG_MED, \"%s: unwrapping client key (len=%d): %s\", __func__,\n         BLEN(&wrapped_client_key),\n         format_hex(BPTR(&wrapped_client_key), BLEN(&wrapped_client_key), 0, &gc));\n\n    if (TLS_CRYPT_V2_MAX_WKC_LEN < BLEN(&wrapped_client_key))\n    {\n        CRYPT_ERROR(\"wrapped client key too big\");\n    }\n\n    /* Decrypt client key and metadata */\n    uint16_t net_len = 0;\n    const uint8_t *tag = BPTR(&wrapped_client_key);\n\n    if (BLENZ(&wrapped_client_key) < sizeof(net_len))\n    {\n        CRYPT_ERROR(\"failed to read length\");\n    }\n    memcpy(&net_len, BEND(&wrapped_client_key) - sizeof(net_len), sizeof(net_len));\n\n    if (ntohs(net_len) != BLEN(&wrapped_client_key))\n    {\n        dmsg(D_TLS_DEBUG_LOW, \"%s: net_len=%u, BLEN=%i\", __func__, ntohs(net_len),\n             BLEN(&wrapped_client_key));\n        CRYPT_ERROR(\"invalid length\");\n    }\n\n    buf_inc_len(&wrapped_client_key, -(int)sizeof(net_len));\n\n    if (!buf_advance(&wrapped_client_key, TLS_CRYPT_TAG_SIZE))\n    {\n        CRYPT_ERROR(\"failed to read tag\");\n    }\n\n    if (!cipher_ctx_reset(server_key->cipher, tag))\n    {\n        CRYPT_ERROR(\"failed to initialize IV\");\n    }\n    buf_set_write(&plaintext, plaintext_buf_data, sizeof(plaintext_buf_data));\n    int outlen = 0;\n    if (!cipher_ctx_update(server_key->cipher, BPTR(&plaintext), &outlen, BPTR(&wrapped_client_key),\n                           BLEN(&wrapped_client_key)))\n    {\n        CRYPT_ERROR(\"could not decrypt client key\");\n    }\n    ASSERT(buf_inc_len(&plaintext, outlen));\n\n    if (!cipher_ctx_final(server_key->cipher, BEND(&plaintext), &outlen))\n    {\n        CRYPT_ERROR(\"cipher final failed\");\n    }\n    ASSERT(buf_inc_len(&plaintext, outlen));\n\n    /* Check authentication */\n    uint8_t tag_check[TLS_CRYPT_TAG_SIZE] = { 0 };\n    hmac_ctx_reset(server_key->hmac);\n    hmac_ctx_update(server_key->hmac, (void *)&net_len, sizeof(net_len));\n    hmac_ctx_update(server_key->hmac, BPTR(&plaintext), BLEN(&plaintext));\n    hmac_ctx_final(server_key->hmac, tag_check);\n\n    if (memcmp_constant_time(tag, tag_check, sizeof(tag_check)))\n    {\n        dmsg(D_CRYPTO_DEBUG, \"tag      : %s\", format_hex(tag, sizeof(tag_check), 0, &gc));\n        dmsg(D_CRYPTO_DEBUG, \"tag_check: %s\", format_hex(tag_check, sizeof(tag_check), 0, &gc));\n        CRYPT_ERROR(\"client key authentication error\");\n        msg(D_TLS_DEBUG_LOW, \"This might be a client-key that was generated for \"\n                             \"a different tls-crypt-v2 server key)\");\n    }\n\n    if (BLENZ(&plaintext) < sizeof(client_key->keys))\n    {\n        CRYPT_ERROR(\"failed to read client key\");\n    }\n    memcpy(&client_key->keys, BPTR(&plaintext), sizeof(client_key->keys));\n    ASSERT(buf_advance(&plaintext, sizeof(client_key->keys)));\n    client_key->n = 2;\n\n    if (!buf_copy(metadata, &plaintext))\n    {\n        CRYPT_ERROR(\"metadata too large for supplied buffer\");\n    }\n\n    ret = true;\nerror_exit:\n    if (!ret)\n    {\n        secure_memzero(client_key, sizeof(*client_key));\n    }\n    buf_clear(&plaintext);\n    gc_free(&gc);\n    return ret;\n}\n\nstatic bool\ntls_crypt_v2_check_client_key_age(const struct tls_wrap_ctx *ctx, int max_days)\n{\n    if (BLENZ(&ctx->tls_crypt_v2_metadata) < 1 + sizeof(int64_t))\n    {\n        msg(M_WARN, \"ERROR: Client key metadata is too small to contain a timestamp.\");\n        return false;\n    }\n\n    const uint8_t *metadata = ctx->tls_crypt_v2_metadata.data;\n    if (*metadata != TLS_CRYPT_METADATA_TYPE_TIMESTAMP)\n    {\n        msg(M_WARN, \"ERROR: Client key does not have a timestamp.\");\n        return false;\n    }\n\n    int64_t timestamp;\n    memcpy(&timestamp, metadata + 1, sizeof(int64_t));\n    timestamp = (int64_t)ntohll((uint64_t)timestamp);\n    int64_t max_age_in_seconds = (int64_t)max_days * 24 * 60 * 60;\n    if (now - timestamp > max_age_in_seconds)\n    {\n        msg(M_WARN, \"ERROR: Client key is too old.\");\n        return false;\n    }\n    return true;\n}\n\nstatic bool\ntls_crypt_v2_verify_metadata(const struct tls_wrap_ctx *ctx, const struct tls_options *opt)\n{\n    bool ret = false;\n    struct gc_arena gc = gc_new();\n    const char *tmp_file = NULL;\n    struct buffer metadata = ctx->tls_crypt_v2_metadata;\n    int metadata_type = buf_read_u8(&metadata);\n    if (metadata_type < 0)\n    {\n        msg(M_WARN, \"ERROR: no metadata type\");\n        goto cleanup;\n    }\n\n    tmp_file = platform_create_temp_file(opt->tmp_dir, \"tls_crypt_v2_metadata_\", &gc);\n    if (!tmp_file || !buffer_write_file(tmp_file, &metadata))\n    {\n        msg(M_WARN, \"ERROR: could not write metadata to file\");\n        goto cleanup;\n    }\n\n    char metadata_type_str[4] = { 0 }; /* Max value: 255 */\n    snprintf(metadata_type_str, sizeof(metadata_type_str), \"%i\", (uint8_t)metadata_type);\n    struct env_set *es = env_set_create(NULL);\n    setenv_str(es, \"script_type\", \"tls-crypt-v2-verify\");\n    setenv_str(es, \"metadata_type\", metadata_type_str);\n    setenv_str(es, \"metadata_file\", tmp_file);\n\n    struct argv argv = argv_new();\n    argv_parse_cmd(&argv, opt->tls_crypt_v2_verify_script);\n    argv_msg_prefix(D_TLS_DEBUG, &argv, \"Executing tls-crypt-v2-verify\");\n\n    ret = openvpn_run_script(&argv, es, 0, \"--tls-crypt-v2-verify\");\n\n    argv_free(&argv);\n    env_set_destroy(es);\n\n    if (!platform_unlink(tmp_file))\n    {\n        msg(M_WARN, \"WARNING: failed to remove temp file '%s\", tmp_file);\n    }\n\n    if (ret)\n    {\n        msg(D_HANDSHAKE, \"TLS CRYPT V2 VERIFY SCRIPT OK\");\n    }\n    else\n    {\n        msg(D_HANDSHAKE, \"TLS CRYPT V2 VERIFY SCRIPT ERROR\");\n    }\n\ncleanup:\n    gc_free(&gc);\n    return ret;\n}\n\nbool\ntls_crypt_v2_extract_client_key(struct buffer *buf, struct tls_wrap_ctx *ctx,\n                                const struct tls_options *opt, bool initial_packet)\n{\n    if (!ctx->tls_crypt_v2_server_key.cipher)\n    {\n        msg(D_TLS_ERRORS, \"Client wants tls-crypt-v2, but no server key present.\");\n        return false;\n    }\n\n    msg(D_HANDSHAKE, \"Control Channel: using tls-crypt-v2 key\");\n\n    struct buffer wrapped_client_key = *buf;\n    uint16_t net_len = 0;\n\n    if (BLENZ(&wrapped_client_key) < sizeof(net_len))\n    {\n        msg(D_TLS_ERRORS, \"Can not read tls-crypt-v2 client key length\");\n        return false;\n    }\n    memcpy(&net_len, BEND(&wrapped_client_key) - sizeof(net_len), sizeof(net_len));\n\n    uint16_t wkc_len = ntohs(net_len);\n    if (!buf_advance(&wrapped_client_key, BLEN(&wrapped_client_key) - wkc_len))\n    {\n        msg(D_TLS_ERRORS, \"Can not locate tls-crypt-v2 client key\");\n        return false;\n    }\n\n    if (!initial_packet)\n    {\n        /* This might be a harmless resend of the packet but it is better to\n         * just ignore the WKC part than trying to setup tls-crypt keys again.\n         *\n         * A CONTROL_WKC_V1 packets has a normal packet part and an appended\n         * wrapped control key. These are authenticated individually. We already\n         * set up tls-crypt with the wrapped key, so we are ignoring this part\n         * of the message but we return the normal packet part as the normal\n         * part of the message might have been corrupted earlier and discarded\n         * and this is resend. So return the normal part of the packet,\n         * basically transforming the CONTROL_WKC_V1 into a normal CONTROL_V1\n         * packet*/\n        msg(D_TLS_ERRORS, \"control channel security already setup ignoring \"\n                          \"wrapped key part of packet.\");\n\n        /* Remove client key from buffer so tls-crypt code can unwrap message */\n        ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key))));\n        return true;\n    }\n\n    ctx->tls_crypt_v2_metadata = alloc_buf(TLS_CRYPT_V2_MAX_METADATA_LEN);\n    if (!tls_crypt_v2_unwrap_client_key(&ctx->original_wrap_keydata, &ctx->tls_crypt_v2_metadata,\n                                        wrapped_client_key, &ctx->tls_crypt_v2_server_key))\n    {\n        msg(D_TLS_ERRORS, \"Can not unwrap tls-crypt-v2 client key\");\n        secure_memzero(&ctx->original_wrap_keydata, sizeof(ctx->original_wrap_keydata));\n        return false;\n    }\n\n    if (opt && opt->tls_crypt_v2_max_age > 0 && !tls_crypt_v2_check_client_key_age(ctx, opt->tls_crypt_v2_max_age))\n    {\n        secure_memzero(&ctx->original_wrap_keydata, sizeof(ctx->original_wrap_keydata));\n        return false;\n    }\n\n    if (opt && opt->tls_crypt_v2_verify_script && !tls_crypt_v2_verify_metadata(ctx, opt))\n    {\n        secure_memzero(&ctx->original_wrap_keydata, sizeof(ctx->original_wrap_keydata));\n        return false;\n    }\n\n    /* Load the decrypted key */\n    ctx->mode = TLS_WRAP_CRYPT;\n    ctx->cleanup_key_ctx = true;\n    ctx->opt.flags |= CO_PACKET_ID_LONG_FORM;\n    memset(&ctx->opt.key_ctx_bi, 0, sizeof(ctx->opt.key_ctx_bi));\n    tls_crypt_v2_load_client_key(&ctx->opt.key_ctx_bi, &ctx->original_wrap_keydata, true);\n\n    /* Remove client key from buffer so tls-crypt code can unwrap message */\n    ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key))));\n\n    return true;\n}\n\nvoid\ntls_crypt_v2_write_server_key_file(const char *filename)\n{\n    write_pem_key_file(filename, tls_crypt_v2_srv_pem_name);\n}\n\nvoid\ntls_crypt_v2_write_client_key_file(const char *filename, const char *b64_metadata,\n                                   const char *server_key_file, bool server_key_inline)\n{\n    struct gc_arena gc = gc_new();\n    struct key_ctx server_key = { 0 };\n    struct buffer client_key_pem = { 0 };\n    struct buffer dst = alloc_buf_gc(TLS_CRYPT_V2_CLIENT_KEY_LEN + TLS_CRYPT_V2_MAX_WKC_LEN, &gc);\n    struct key2 client_key = { .n = 2 };\n\n    if (!rand_bytes((void *)client_key.keys, sizeof(client_key.keys)))\n    {\n        msg(M_FATAL, \"ERROR: could not generate random key\");\n        goto cleanup;\n    }\n    ASSERT(buf_write(&dst, client_key.keys, sizeof(client_key.keys)));\n\n    struct buffer metadata;\n    if (b64_metadata)\n    {\n        size_t b64_length = strlen(b64_metadata);\n        metadata = alloc_buf_gc(OPENVPN_BASE64_DECODED_LENGTH(b64_length) + 1, &gc);\n        ASSERT(buf_write(&metadata, &TLS_CRYPT_METADATA_TYPE_USER, 1));\n        int decoded_len = openvpn_base64_decode(b64_metadata, BEND(&metadata), BCAP(&metadata));\n        if (decoded_len < 0)\n        {\n            msg(M_FATAL, \"ERROR: failed to base64 decode provided metadata\");\n            goto cleanup;\n        }\n        if ((unsigned int)decoded_len > TLS_CRYPT_V2_MAX_METADATA_LEN - 1)\n        {\n            msg(M_FATAL, \"ERROR: metadata too long (%d bytes, max %u bytes)\", decoded_len,\n                TLS_CRYPT_V2_MAX_METADATA_LEN - 1);\n            goto cleanup;\n        }\n        ASSERT(buf_inc_len(&metadata, decoded_len));\n    }\n    else\n    {\n        metadata = alloc_buf_gc(1 + sizeof(int64_t), &gc);\n        int64_t timestamp = htonll((uint64_t)now);\n        ASSERT(buf_write(&metadata, &TLS_CRYPT_METADATA_TYPE_TIMESTAMP, 1));\n        ASSERT(buf_write(&metadata, &timestamp, sizeof(timestamp)));\n    }\n\n    tls_crypt_v2_init_server_key(&server_key, true, server_key_file, server_key_inline);\n    if (!tls_crypt_v2_wrap_client_key(&dst, &client_key, &metadata, &server_key, &gc))\n    {\n        msg(M_FATAL, \"ERROR: could not wrap generated client key\");\n        goto cleanup;\n    }\n\n    /* PEM-encode Kc || WKc */\n    if (!crypto_pem_encode(tls_crypt_v2_cli_pem_name, &client_key_pem, &dst, &gc))\n    {\n        msg(M_FATAL, \"ERROR: could not PEM-encode client key\");\n        goto cleanup;\n    }\n\n    const char *client_file = filename;\n    bool client_inline = false;\n\n    if (!filename || streq(filename, \"\"))\n    {\n        printf(\"%.*s\\n\", BLEN(&client_key_pem), BPTR(&client_key_pem));\n        client_file = (const char *)BPTR(&client_key_pem);\n        client_inline = true;\n    }\n    else if (!buffer_write_file(filename, &client_key_pem))\n    {\n        msg(M_FATAL, \"ERROR: could not write client key file\");\n        goto cleanup;\n    }\n\n    /* Sanity check: load client key (as \"client\") */\n    struct key_ctx_bi test_client_key;\n    struct buffer test_wrapped_client_key;\n    struct key2 keydata;\n    msg(D_GENKEY, \"Testing client-side key loading...\");\n    tls_crypt_v2_init_client_key(&test_client_key, &keydata, &test_wrapped_client_key, client_file,\n                                 client_inline);\n    free_key_ctx_bi(&test_client_key);\n\n    /* Sanity check: unwrap and load client key (as \"server\") */\n    struct buffer test_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN, &gc);\n    struct key2 test_client_key2 = { 0 };\n    free_key_ctx(&server_key);\n    tls_crypt_v2_init_server_key(&server_key, false, server_key_file, server_key_inline);\n    msg(D_GENKEY, \"Testing server-side key loading...\");\n    ASSERT(tls_crypt_v2_unwrap_client_key(&test_client_key2, &test_metadata,\n                                          test_wrapped_client_key, &server_key));\n    secure_memzero(&test_client_key2, sizeof(test_client_key2));\n    free_buf(&test_wrapped_client_key);\n\ncleanup:\n    secure_memzero(&client_key, sizeof(client_key));\n    free_key_ctx(&server_key);\n    buf_clear(&client_key_pem);\n    buf_clear(&dst);\n\n    gc_free(&gc);\n}\n"
  },
  {
    "path": "src/openvpn/tls_crypt.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * @defgroup tls_crypt Control channel encryption (tls-crypt, tls-crypt-v2)\n * @ingroup control_tls\n * @{\n *\n * Control channel encryption uses a pre-shared static key (like the @c --tls-auth\n * key) to encrypt control channel packets.\n *\n * Encrypting control channel packets has three main advantages:\n *  - It provides more privacy by hiding the certificate used for the TLS\n *    connection.\n *  - It is harder to identify OpenVPN traffic as such.\n *  - It provides \"poor-man's\" post-quantum security, against attackers who\n *    will never know the pre-shared key (i.e. no forward secrecy).\n *\n * @c --tls-crypt uses a tls-auth-style group key, where all servers and clients\n * share the same group key. @c --tls-crypt-v2 adds support for client-specific\n * keys, where all servers share the same client-key encryption key, and each\n * clients receives a unique client key, both in plaintext and in encrypted\n * form.  When connecting to a server, the client sends the encrypted key to\n * the server in the first packet (P_CONTROL_HARD_RESET_CLIENT_V3). The server\n * then decrypts that key, and both parties can use the same client-specific\n * key for tls-crypt packets. See doc/tls-crypt-v2.txt for more details.\n *\n * @par On-the-wire tls-crypt packet specification\n * @parblock\n * Control channel encryption is based on the SIV construction [0], to achieve\n * nonce misuse-resistant authenticated encryption:\n *\n * \\code{.unparsed}\n * msg      = control channel plaintext\n * header   = opcode (1 byte) || session_id (8 bytes) || packet_id (8 bytes)\n * Ka       = authentication key (256 bits)\n * Ke       = encryption key (256 bits)\n * (Ka and Ke are pre-shared keys, like with --tls-auth)\n *\n * auth_tag = HMAC-SHA256(Ka, header || msg)\n * IV       = 128 most-significant bits of auth_tag\n * ciph     = AES256-CTR(Ke, IV, msg)\n *\n * output   = Header || Tag || Ciph\n * \\endcode\n *\n * This boils down to the following on-the-wire packet format:\n *\n * \\code{.unparsed}\n * - opcode - || - session_id - || - packet_id - || auth_tag || * payload *\n * \\endcode\n *\n * Where\n * <tt>- XXX -</tt> means authenticated, and\n * <tt>* XXX *</tt> means authenticated and encrypted.\n *\n * @endparblock\n */\n\n#ifndef TLSCRYPT_H\n#define TLSCRYPT_H\n\n#include \"base64.h\"\n#include \"buffer.h\"\n#include \"crypto.h\"\n#include \"session_id.h\"\n#include \"ssl_common.h\"\n\n#define TLS_CRYPT_TAG_SIZE   (256 / 8)\n#define TLS_CRYPT_PID_SIZE   (sizeof(packet_id_type) + sizeof(net_time_t))\n#define TLS_CRYPT_BLOCK_SIZE (128 / 8)\n\n#define TLS_CRYPT_OFF_PID (1 + SID_SIZE)\n#define TLS_CRYPT_OFF_TAG (TLS_CRYPT_OFF_PID + TLS_CRYPT_PID_SIZE)\n#define TLS_CRYPT_OFF_CT  (TLS_CRYPT_OFF_TAG + TLS_CRYPT_TAG_SIZE)\n\n#define TLS_CRYPT_V2_MAX_WKC_LEN    (1024)\n#define TLS_CRYPT_V2_CLIENT_KEY_LEN (2048 / 8)\n#define TLS_CRYPT_V2_SERVER_KEY_LEN (sizeof(struct key))\n#define TLS_CRYPT_V2_TAG_SIZE       (TLS_CRYPT_TAG_SIZE)\n#define TLS_CRYPT_V2_MAX_METADATA_LEN   \\\n    (unsigned)(TLS_CRYPT_V2_MAX_WKC_LEN \\\n               - (TLS_CRYPT_V2_CLIENT_KEY_LEN + TLS_CRYPT_V2_TAG_SIZE + sizeof(uint16_t)))\n\n/**\n * Initialize a key_ctx_bi structure for use with @c --tls-crypt.\n *\n * @param key           The key context to initialize\n * @param key_file      The file to read the key from or the key itself if\n *                      key_inline is true.\n * @param keydata       The keydata used to create key will be written here.\n * @param key_inline    True if key_file contains an inline key, False\n *                      otherwise.\n * @param tls_server    Must be set to true is this is a TLS server instance.\n */\nvoid tls_crypt_init_key(struct key_ctx_bi *key, struct key2 *keydata, const char *key_file,\n                        bool key_inline, bool tls_server);\n\n/**\n * Generates a TLS-Crypt key to be used with dynamic tls-crypt using the\n * TLS EKM exporter function.\n *\n * All renegotiations of a session use the same generated dynamic key.\n *\n * @param session   session that will be used for the TLS EKM exporter\n * @return          true iff generating the key was successful\n */\nbool tls_session_generate_dynamic_tls_crypt_key(struct tls_session *session);\n\n/**\n * Returns the maximum overhead (in bytes) added to the destination buffer by\n * tls_crypt_wrap().\n */\nint tls_crypt_buf_overhead(void);\n\n/**\n * Wrap a control channel packet (both authenticates and encrypts the data).\n *\n * @param src   Data to authenticate and encrypt.\n * @param dst   Any data present in this buffer is first authenticated, then\n *              the wrapped packet id and data from the src buffer are appended.\n *              Must have at least tls_crypt_buf_overhead()+BLEN(src) headroom.\n * @param opt   The crypto state for this @c --tls-crypt instance.\n *\n * @returns true iff wrapping succeeded.\n */\nbool tls_crypt_wrap(const struct buffer *src, struct buffer *dst, struct crypto_options *opt);\n\n/**\n * Unwrap a control channel packet (decrypts, authenticates and performs\n * replay checks).\n *\n * @param src   Data to decrypt and authenticate.\n * @param dst   Returns the decrypted data, if unwrapping was successful.\n * @param opt   The crypto state for this @c --tls-crypt instance.\n *\n * @returns true iff unwrapping succeeded (data authenticated correctly and was\n * no replay).\n */\nbool tls_crypt_unwrap(const struct buffer *src, struct buffer *dst, struct crypto_options *opt);\n\n/**\n * Initialize a tls-crypt-v2 server key (used to encrypt/decrypt client keys).\n *\n * @param key_ctx       Key structure to be initialized.  Must be non-NULL.\n * @param encrypt       If true, initialize the key structure for encryption,\n *                      otherwise for decryption.\n * @param key_file      File path of the key file to load or the key itself if\n *                      key_inline is true.\n * @param key_inline    True if key_file contains an inline key, False\n *                      otherwise.\n *\n */\nvoid tls_crypt_v2_init_server_key(struct key_ctx *key_ctx, bool encrypt, const char *key_file,\n                                  bool key_inline);\n\n/**\n * Initialize a tls-crypt-v2 client key.\n *\n * @param key               Key structure to be initialized with the client\n *                          key.\n * @param original_key      contains the key data that has been used to\n *                          initialise the key parameter\n * @param wrapped_key_buf   Returns buffer containing the wrapped key that will\n *                          be sent to the server when connecting.  Caller must\n *                          free this buffer when no longer needed.\n * @param key_file          File path of the key file to load or the key itself\n *                          if key_inline is true.\n * @param key_inline        True if key_file contains an inline key, False\n *                          otherwise.\n */\nvoid tls_crypt_v2_init_client_key(struct key_ctx_bi *key, struct key2 *original_key,\n                                  struct buffer *wrapped_key_buf, const char *key_file,\n                                  bool key_inline);\n\n/**\n * Extract a tls-crypt-v2 client key from a P_CONTROL_HARD_RESET_CLIENT_V3\n * message, and load the key into the supplied tls wrap context.\n *\n * @param buf   Buffer containing a received P_CONTROL_HARD_RESET_CLIENT_V3\n *              message.\n * @param ctx   tls-wrap context to be initialized with the client key.\n * @param opt   TLS options, used for \\c tls-crypt-v2-verify script.\n *\n * @param initial_packet    whether this is the initial packet of the\n *                          connection. Only in these scenarios unwrapping\n *                          of a tls-crypt-v2 key is allowed\n *\n * @returns true if a key was successfully extracted.\n */\nbool tls_crypt_v2_extract_client_key(struct buffer *buf, struct tls_wrap_ctx *ctx,\n                                     const struct tls_options *opt, bool initial_packet);\n\n/**\n * Generate a tls-crypt-v2 server key, and write to file.\n *\n * @param filename          Filename of the server key file to create.\n */\nvoid tls_crypt_v2_write_server_key_file(const char *filename);\n\n/**\n * Generate a tls-crypt-v2 client key, and write to file.\n *\n * @param filename          Filename of the client key file to create.\n * @param b64_metadata      Base64 metadata to be included in the client key.\n * @param key_file          File path of the server key to use for wrapping the\n *                          client key or the key itself if key_inline is true.\n * @param key_inline        True if key_file contains an inline key, False\n *                          otherwise.\n */\nvoid tls_crypt_v2_write_client_key_file(const char *filename, const char *b64_metadata,\n                                        const char *key_file, bool key_inline);\n\n/** @} */\n\n#endif /* TLSCRYPT_H */\n"
  },
  {
    "path": "src/openvpn/tun.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * Support routines for configuring and accessing TUN/TAP\n * virtual network adapters.\n *\n * This file is based on the TUN/TAP driver interface routines\n * from VTun by Maxim Krasnyansky <max_mk@yahoo.com>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"openvpn.h\"\n#include \"tun.h\"\n#include \"fdmisc.h\"\n#include \"common.h\"\n#include \"run_command.h\"\n#include \"socket_util.h\"\n#include \"manage.h\"\n#include \"route.h\"\n#include \"win32.h\"\n#include \"wfp_block.h\"\n#include \"networking.h\"\n#include \"dhcp.h\"\n\n#include \"memdbg.h\"\n\n#ifdef _WIN32\n#include \"openvpn-msg.h\"\n#endif\n\n#include <string.h>\n\nconst char *\nprint_tun_backend_driver(enum tun_driver_type driver)\n{\n    switch (driver)\n    {\n        case WINDOWS_DRIVER_TAP_WINDOWS6:\n            return \"tap-windows6\";\n\n        case DRIVER_GENERIC_TUNTAP:\n            return \"tun/tap\";\n\n        case DRIVER_DCO:\n            return \"ovpn-dco\";\n\n        case DRIVER_AFUNIX:\n            return \"unix\";\n\n        case DRIVER_NULL:\n            return \"null\";\n\n        case DRIVER_UTUN:\n            return \"utun\";\n\n        default:\n            return \"unspecified\";\n    }\n}\n\n#ifdef _WIN32\n\nstatic const GUID GUID_DEVCLASS_NET = {\n    0x4d36e972L, 0xe325, 0x11ce, { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 }\n};\nstatic const GUID GUID_DEVINTERFACE_NET = {\n    0xcac88484, 0x7515, 0x4c03, { 0x82, 0xe6, 0x71, 0xa8, 0x7a, 0xba, 0xc3, 0x61 }\n};\n\n/* #define SIMULATE_DHCP_FAILED */ /* simulate bad DHCP negotiation */\n\n#define NI_TEST_FIRST (1 << 0)\n#define NI_IP_NETMASK (1 << 1)\n#define NI_OPTIONS    (1 << 2)\n\nstatic void netsh_ifconfig(const struct tuntap_options *to, DWORD adapter_index, const in_addr_t ip,\n                           const in_addr_t netmask, const unsigned int flags);\n\nstatic void windows_set_mtu(const int iface_index, const short family, const int mtu);\n\nstatic void netsh_set_dns6_servers(const struct in6_addr *addr_list, const unsigned int addr_len,\n                                   DWORD adapter_index);\n\nstatic void netsh_command(const struct argv *a, int n, msglvl_t msglevel);\n\nstatic void exec_command(const char *prefix, const struct argv *a, int n, msglvl_t msglevel);\n\nstatic const char *netsh_get_id(const char *dev_node, struct gc_arena *gc);\n\nstatic bool\ndo_address_service(const bool add, const short family, const struct tuntap *tt)\n{\n    bool ret = false;\n    ack_message_t ack;\n    struct gc_arena gc = gc_new();\n    HANDLE pipe = tt->options.msg_channel;\n\n    address_message_t addr = { .header = { (add ? msg_add_address : msg_del_address),\n                                           sizeof(address_message_t), 0 },\n                               .family = family,\n                               .iface = { .index = tt->adapter_index, .name = \"\" } };\n\n    if (addr.iface.index == TUN_ADAPTER_INDEX_INVALID)\n    {\n        strncpy(addr.iface.name, tt->actual_name, sizeof(addr.iface.name));\n        addr.iface.name[sizeof(addr.iface.name) - 1] = '\\0';\n    }\n\n    if (addr.family == AF_INET)\n    {\n        addr.address.ipv4.s_addr = htonl(tt->local);\n        addr.prefix_len = netmask_to_netbits2(tt->adapter_netmask);\n        msg(D_IFCONFIG, \"INET address service: %s %s/%d\", add ? \"add\" : \"remove\",\n            print_in_addr_t(tt->local, 0, &gc), addr.prefix_len);\n    }\n    else\n    {\n        addr.address.ipv6 = tt->local_ipv6;\n        addr.prefix_len = (tt->type == DEV_TYPE_TUN) ? 128 : tt->netbits_ipv6;\n        msg(D_IFCONFIG, \"INET6 address service: %s %s/%d\", add ? \"add\" : \"remove\",\n            print_in6_addr(tt->local_ipv6, 0, &gc), addr.prefix_len);\n    }\n\n    if (!send_msg_iservice(pipe, &addr, sizeof(addr), &ack, \"TUN\"))\n    {\n        goto out;\n    }\n\n    if (ack.error_number != NO_ERROR)\n    {\n        msg(M_WARN, \"TUN: %s address failed using service: %s [status=%u if_index=%lu]\",\n            (add ? \"adding\" : \"deleting\"), strerror_win32(ack.error_number, &gc), ack.error_number,\n            addr.iface.index);\n        goto out;\n    }\n\n    ret = true;\n\nout:\n    gc_free(&gc);\n    return ret;\n}\n\nstatic void\ndo_dns_domain_service(bool add, const struct tuntap *tt)\n{\n    ack_message_t ack;\n    struct gc_arena gc = gc_new();\n    const struct tuntap_options *o = &tt->options;\n\n    /* no domains to add or delete */\n    if (!o->domain && !o->domain_search_list[0])\n    {\n        goto out;\n    }\n\n    /* Use dns_cfg_msg with addr_len = 0 for setting only the DOMAIN */\n    dns_cfg_message_t dns = {\n        .header = { (add ? msg_add_dns_cfg : msg_del_dns_cfg), sizeof(dns_cfg_message_t), 0 },\n        .iface = { .index = tt->adapter_index, .name = \"\" },\n        .domains = \"\",     /* set below */\n        .family = AF_INET, /* unused */\n        .addr_len = 0      /* add/delete only the domain, not DNS servers */\n    };\n\n    /* interface name is required */\n    strncpynt(dns.iface.name, tt->actual_name, sizeof(dns.iface.name));\n\n    /* only use domain when there are no search domains */\n    if (o->domain && !o->domain_search_list[0])\n    {\n        strncpynt(dns.domains, o->domain, sizeof(dns.domains));\n    }\n\n    /* Create a comma separated list of search domains */\n    for (int i = 0; i < N_SEARCH_LIST_LEN && o->domain_search_list[i]; ++i)\n    {\n        size_t dstlen = strlen(dns.domains);\n        size_t srclen = strlen(o->domain_search_list[i]);\n        size_t extra = dstlen ? 2 : 1; /* space for comma and NUL */\n        if (dstlen + srclen + extra > sizeof(dns.domains))\n        {\n            msg(M_WARN, \"DNS search domains sent to service truncated to %d\", i);\n            break;\n        }\n        if (dstlen)\n        {\n            dns.domains[dstlen++] = ',';\n        }\n        strncpy(dns.domains + dstlen, o->domain_search_list[i], srclen + 1);\n    }\n\n    msg(D_LOW, \"%s DNS domains on '%s' (if_index = %lu) using service\",\n        (add ? \"Setting\" : \"Deleting\"), dns.iface.name, dns.iface.index);\n    if (!send_msg_iservice(o->msg_channel, &dns, sizeof(dns), &ack, \"TUN\"))\n    {\n        goto out;\n    }\n\n    if (ack.error_number != NO_ERROR)\n    {\n        msg(M_WARN, \"TUN: %s DNS domains failed using service: %s [status=%u if_name=%s]\",\n            (add ? \"adding\" : \"deleting\"), strerror_win32(ack.error_number, &gc), ack.error_number,\n            dns.iface.name);\n        goto out;\n    }\n\n    msg(M_INFO, \"DNS domains %s using service\", (add ? \"set\" : \"deleted\"));\n\nout:\n    gc_free(&gc);\n}\n\nstatic void\ndo_dns_service(bool add, const short family, const struct tuntap *tt)\n{\n    ack_message_t ack;\n    struct gc_arena gc = gc_new();\n    HANDLE pipe = tt->options.msg_channel;\n    unsigned int len = family == AF_INET6 ? tt->options.dns6_len : tt->options.dns_len;\n    unsigned int addr_len = add ? len : 0;\n    const char *ip_proto_name = family == AF_INET6 ? \"IPv6\" : \"IPv4\";\n\n    if (len == 0)\n    {\n        /* nothing to do */\n        goto out;\n    }\n\n    /* Use dns_cfg_msg with domain = \"\" for setting only the DNS servers */\n    dns_cfg_message_t dns = { .header = { (add ? msg_add_dns_cfg : msg_del_dns_cfg),\n                                          sizeof(dns_cfg_message_t), 0 },\n                              .iface = { .index = tt->adapter_index, .name = \"\" },\n                              .domains = \"\",\n                              .family = family,\n                              .addr_len = addr_len };\n\n    /* interface name is required */\n    strncpy(dns.iface.name, tt->actual_name, sizeof(dns.iface.name));\n    dns.iface.name[sizeof(dns.iface.name) - 1] = '\\0';\n\n    if (addr_len > _countof(dns.addr))\n    {\n        addr_len = _countof(dns.addr);\n        dns.addr_len = addr_len;\n        msg(M_WARN, \"Number of %s DNS addresses sent to service truncated to %u\",\n            ip_proto_name, addr_len);\n    }\n\n    for (unsigned int i = 0; i < addr_len; ++i)\n    {\n        if (family == AF_INET6)\n        {\n            dns.addr[i].ipv6 = tt->options.dns6[i];\n        }\n        else\n        {\n            dns.addr[i].ipv4.s_addr = htonl(tt->options.dns[i]);\n        }\n    }\n\n    msg(D_LOW, \"%s %s dns servers on '%s' (if_index = %lu) using service\",\n        (add ? \"Setting\" : \"Deleting\"), ip_proto_name, dns.iface.name, dns.iface.index);\n\n    if (!send_msg_iservice(pipe, &dns, sizeof(dns), &ack, \"TUN\"))\n    {\n        goto out;\n    }\n\n    if (ack.error_number != NO_ERROR)\n    {\n        msg(M_WARN, \"TUN: %s %s dns failed using service: %s [status=%u if_name=%s]\",\n            (add ? \"adding\" : \"deleting\"), ip_proto_name, strerror_win32(ack.error_number, &gc),\n            ack.error_number, dns.iface.name);\n        goto out;\n    }\n\n    msg(M_INFO, \"%s dns servers %s using service\", ip_proto_name, (add ? \"set\" : \"deleted\"));\n\nout:\n    gc_free(&gc);\n}\n\nstatic void\ndo_wins_service(bool add, const struct tuntap *tt)\n{\n    ack_message_t ack;\n    struct gc_arena gc = gc_new();\n    HANDLE pipe = tt->options.msg_channel;\n    unsigned int addr_len = add ? tt->options.wins_len : 0;\n\n    if (tt->options.wins_len == 0)\n    {\n        /* nothing to do */\n        goto out;\n    }\n\n    wins_cfg_message_t wins = { .header = { (add ? msg_add_wins_cfg : msg_del_wins_cfg),\n                                            sizeof(wins_cfg_message_t), 0 },\n                                .iface = { .index = tt->adapter_index, .name = \"\" },\n                                .addr_len = addr_len };\n\n    /* interface name is required */\n    strncpy(wins.iface.name, tt->actual_name, sizeof(wins.iface.name));\n    wins.iface.name[sizeof(wins.iface.name) - 1] = '\\0';\n\n    if (addr_len > _countof(wins.addr))\n    {\n        addr_len = _countof(wins.addr);\n        wins.addr_len = addr_len;\n        msg(M_WARN, \"Number of WINS addresses sent to service truncated to %u\", addr_len);\n    }\n\n    for (unsigned int i = 0; i < addr_len; ++i)\n    {\n        wins.addr[i].ipv4.s_addr = htonl(tt->options.wins[i]);\n    }\n\n    msg(D_LOW, \"%s WINS servers on '%s' (if_index = %lu) using service\",\n        (add ? \"Setting\" : \"Deleting\"), wins.iface.name, wins.iface.index);\n\n    if (!send_msg_iservice(pipe, &wins, sizeof(wins), &ack, \"TUN\"))\n    {\n        goto out;\n    }\n\n    if (ack.error_number != NO_ERROR)\n    {\n        msg(M_WARN, \"TUN: %s WINS failed using service: %s [status=%u if_name=%s]\",\n            (add ? \"adding\" : \"deleting\"), strerror_win32(ack.error_number, &gc),\n            ack.error_number, wins.iface.name);\n        goto out;\n    }\n\n    msg(M_INFO, \"WINS servers %s using service\", (add ? \"set\" : \"deleted\"));\n\nout:\n    gc_free(&gc);\n}\n\nstatic bool\ndo_set_mtu_service(const struct tuntap *tt, const short family, const int mtu)\n{\n    bool ret = false;\n    ack_message_t ack;\n    struct gc_arena gc = gc_new();\n    HANDLE pipe = tt->options.msg_channel;\n    const char *family_name = (family == AF_INET6) ? \"IPv6\" : \"IPv4\";\n    set_mtu_message_t mtu_msg = { .header = { msg_set_mtu, sizeof(set_mtu_message_t), 0 },\n                                  .iface = { .index = tt->adapter_index },\n                                  .mtu = mtu,\n                                  .family = family };\n    strncpynt(mtu_msg.iface.name, tt->actual_name, sizeof(mtu_msg.iface.name));\n    if (family == AF_INET6 && mtu < 1280)\n    {\n        msg(M_INFO,\n            \"NOTE: IPv6 interface MTU < 1280 conflicts with IETF standards and might not work\");\n    }\n\n    if (!send_msg_iservice(pipe, &mtu_msg, sizeof(mtu_msg), &ack, \"Set_mtu\"))\n    {\n        goto out;\n    }\n\n    if (ack.error_number != NO_ERROR)\n    {\n        msg(M_NONFATAL, \"TUN: setting %s mtu using service failed: %s [status=%u if_index=%lu]\",\n            family_name, strerror_win32(ack.error_number, &gc), ack.error_number,\n            mtu_msg.iface.index);\n    }\n    else\n    {\n        msg(M_INFO, \"%s MTU set to %d on interface %lu using service\", family_name, mtu,\n            mtu_msg.iface.index);\n        ret = true;\n    }\n\nout:\n    gc_free(&gc);\n    return ret;\n}\n\nstatic void\ndo_dns_domain_pwsh(bool add, const struct tuntap *tt)\n{\n    if (!tt->options.domain)\n    {\n        return;\n    }\n\n    struct argv argv = argv_new();\n    argv_printf(&argv,\n                \"%s%s -NoProfile -NonInteractive -Command Set-DnsClient -InterfaceIndex %lu -ConnectionSpecificSuffix '%s'\",\n                get_win_sys_path(),\n                POWERSHELL_PATH_SUFFIX,\n                tt->adapter_index,\n                add ? tt->options.domain : \"\");\n    exec_command(\"PowerShell\", &argv, 1, M_WARN);\n\n    argv_free(&argv);\n}\n\n/**\n * Requests the interactive service to create a VPN adapter of the specified type.\n *\n * @param msg_channel Handle to the interactive service communication pipe.\n * @param driver_type Adapter type to create (e.g., TAP, Wintun, DCO).\n *\n * @return true on success, false on failure.\n */\nstatic bool\ndo_create_adapter_service(HANDLE msg_channel, enum tun_driver_type driver_type)\n{\n    bool ret = false;\n    ack_message_t ack;\n    struct gc_arena gc = gc_new();\n\n    adapter_type_t t;\n    switch (driver_type)\n    {\n        case WINDOWS_DRIVER_TAP_WINDOWS6:\n            t = ADAPTER_TYPE_TAP;\n            break;\n\n        case DRIVER_DCO:\n            t = ADAPTER_TYPE_DCO;\n            break;\n\n        default:\n            msg(M_NONFATAL, \"Invalid backend driver %s\", print_tun_backend_driver(driver_type));\n            goto out;\n    }\n\n    create_adapter_message_t msg = {\n        .header = { msg_create_adapter, sizeof(create_adapter_message_t), 0 }, .adapter_type = t\n    };\n\n    if (!send_msg_iservice(msg_channel, &msg, sizeof(msg), &ack, \"create_adapter\"))\n    {\n        goto out;\n    }\n\n    if (ack.error_number != NO_ERROR)\n    {\n        msg(M_NONFATAL, \"TUN: creating %s adapter using service failed: %s [status=%u]\",\n            print_tun_backend_driver(driver_type), strerror_win32(ack.error_number, &gc),\n            ack.error_number);\n    }\n    else\n    {\n        msg(M_INFO, \"%s adapter created using service\", print_tun_backend_driver(driver_type));\n        ret = true;\n    }\n\nout:\n    gc_free(&gc);\n    return ret;\n}\n\n#endif /* ifdef _WIN32 */\n\n#ifdef TARGET_SOLARIS\nstatic void solaris_error_close(struct tuntap *tt, const struct env_set *es, const char *actual,\n                                bool unplumb_inet6);\n\n#include <stropts.h>\n#endif\n\n#if defined(TARGET_DARWIN)\n#include <sys/kern_control.h>\n#include <net/if_utun.h>\n#include <sys/sys_domain.h>\n#endif\n\nstatic void clear_tuntap(struct tuntap *tuntap);\n\nbool\nis_dev_type(const char *dev, const char *dev_type, const char *match_type)\n{\n    ASSERT(match_type);\n    if (!dev)\n    {\n        return false;\n    }\n    if (dev_type)\n    {\n        return !strcmp(dev_type, match_type);\n    }\n    else\n    {\n        return !strncmp(dev, match_type, strlen(match_type));\n    }\n}\n\nint\ndev_type_enum(const char *dev, const char *dev_type)\n{\n    /* We pretend that the null device is also a tun device but it does not\n     * really matter as it will discard everything anyway */\n    if (is_dev_type(dev, dev_type, \"tun\") || is_dev_type(dev, dev_type, \"null\"))\n    {\n        return DEV_TYPE_TUN;\n    }\n    else if (is_dev_type(dev, dev_type, \"tap\"))\n    {\n        return DEV_TYPE_TAP;\n    }\n    else\n    {\n        return DEV_TYPE_UNDEF;\n    }\n}\n\nconst char *\ndev_type_string(const char *dev, const char *dev_type)\n{\n    switch (dev_type_enum(dev, dev_type))\n    {\n        case DEV_TYPE_TUN:\n            return \"tun\";\n\n        case DEV_TYPE_TAP:\n            return \"tap\";\n\n        default:\n            return \"[unknown-dev-type]\";\n    }\n}\n\n/*\n * Try to predict the actual TUN/TAP device instance name,\n * before the device is actually opened.\n */\nconst char *\nguess_tuntap_dev(const char *dev, const char *dev_type, const char *dev_node, struct gc_arena *gc)\n{\n#ifdef _WIN32\n    const int dt = dev_type_enum(dev, dev_type);\n    if (dt == DEV_TYPE_TUN || dt == DEV_TYPE_TAP)\n    {\n        return netsh_get_id(dev_node, gc);\n    }\n#endif\n\n    /* default case */\n    return dev;\n}\n\n\n/* --ifconfig-nowarn disables some options sanity checking */\nstatic const char ifconfig_warn_how_to_silence[] = \"(silence this warning with --ifconfig-nowarn)\";\n\n/*\n * If !tun_p2p, make sure ifconfig_remote_netmask looks\n *  like a netmask.\n *\n * If tun_p2p, make sure ifconfig_remote_netmask looks\n *  like an IPv4 address.\n */\nstatic void\nifconfig_sanity_check(bool tun_p2p, in_addr_t addr)\n{\n    struct gc_arena gc = gc_new();\n    const bool looks_like_netmask = ((addr & 0xFF000000) == 0xFF000000);\n    if (tun_p2p)\n    {\n        if (looks_like_netmask)\n        {\n            msg(M_WARN,\n                \"WARNING: Since you are using --dev tun with a point-to-point topology, the second argument to --ifconfig must be an IP address.  You are using something (%s) that looks more like a netmask. %s\",\n                print_in_addr_t(addr, 0, &gc), ifconfig_warn_how_to_silence);\n        }\n    }\n    else\n    {\n        if (!looks_like_netmask)\n        {\n            msg(M_WARN,\n                \"WARNING: Since you are using subnet topology, the second argument to --ifconfig must be a netmask, for example something like 255.255.255.0. %s\",\n                ifconfig_warn_how_to_silence);\n        }\n    }\n    gc_free(&gc);\n}\n\n/*\n * Check that --local and --remote addresses do not\n * clash with ifconfig addresses or subnet.\n */\nstatic void\ncheck_addr_clash(const char *name, int type, in_addr_t public, in_addr_t local,\n                 in_addr_t remote_netmask)\n{\n    struct gc_arena gc = gc_new();\n#if 0\n    msg(M_INFO, \"CHECK_ADDR_CLASH type=%d public=%s local=%s, remote_netmask=%s\",\n        type,\n        print_in_addr_t(public, 0, &gc),\n        print_in_addr_t(local, 0, &gc),\n        print_in_addr_t(remote_netmask, 0, &gc));\n#endif\n\n    if (public)\n    {\n        if (type == DEV_TYPE_TUN)\n        {\n            const in_addr_t test_netmask = 0xFFFFFF00;\n            const in_addr_t public_net = public & test_netmask;\n            const in_addr_t local_net = local & test_netmask;\n            const in_addr_t remote_net = remote_netmask & test_netmask;\n\n            if (public == local || public == remote_netmask)\n            {\n                msg(M_WARN,\n                    \"WARNING: --%s address [%s] conflicts with --ifconfig address pair [%s, %s]. %s\",\n                    name, print_in_addr_t(public, 0, &gc), print_in_addr_t(local, 0, &gc),\n                    print_in_addr_t(remote_netmask, 0, &gc), ifconfig_warn_how_to_silence);\n            }\n\n            if (public_net == local_net || public_net == remote_net)\n            {\n                msg(M_WARN,\n                    \"WARNING: potential conflict between --%s address [%s] and --ifconfig address pair [%s, %s] -- this is a warning only that is triggered when local/remote addresses exist within the same /24 subnet as --ifconfig endpoints. %s\",\n                    name, print_in_addr_t(public, 0, &gc), print_in_addr_t(local, 0, &gc),\n                    print_in_addr_t(remote_netmask, 0, &gc), ifconfig_warn_how_to_silence);\n            }\n        }\n        else if (type == DEV_TYPE_TAP)\n        {\n            const in_addr_t public_network = public & remote_netmask;\n            const in_addr_t virtual_network = local & remote_netmask;\n            if (public_network == virtual_network)\n            {\n                msg(M_WARN,\n                    \"WARNING: --%s address [%s] conflicts with --ifconfig subnet [%s, %s] -- local and remote addresses cannot be inside of the --ifconfig subnet. %s\",\n                    name, print_in_addr_t(public, 0, &gc), print_in_addr_t(local, 0, &gc),\n                    print_in_addr_t(remote_netmask, 0, &gc), ifconfig_warn_how_to_silence);\n            }\n        }\n    }\n    gc_free(&gc);\n}\n\nvoid\nwarn_on_use_of_common_subnets(openvpn_net_ctx_t *ctx)\n{\n    struct gc_arena gc = gc_new();\n    struct route_gateway_info rgi;\n    const unsigned int needed = (RGI_ADDR_DEFINED | RGI_NETMASK_DEFINED);\n\n    get_default_gateway(&rgi, 0, ctx);\n    if ((rgi.flags & needed) == needed)\n    {\n        const in_addr_t lan_network = rgi.gateway.addr & rgi.gateway.netmask;\n        if (lan_network == 0xC0A80000 || lan_network == 0xC0A80100)\n        {\n            msg(M_WARN,\n                \"NOTE: your local LAN uses the extremely common subnet address 192.168.0.x or 192.168.1.x.  Be aware that this might create routing conflicts if you connect to the VPN server from public locations such as internet cafes that use the same subnet.\");\n        }\n    }\n    gc_free(&gc);\n}\n\n/*\n * Return a string to be used for options compatibility check\n * between peers.\n */\nconst char *\nifconfig_options_string(const struct tuntap *tt, bool remote, bool disable, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n    if (tt->did_ifconfig_setup && !disable)\n    {\n        if (!is_tun_p2p(tt))\n        {\n            buf_printf(&out, \"%s %s\", print_in_addr_t(tt->local & tt->remote_netmask, 0, gc),\n                       print_in_addr_t(tt->remote_netmask, 0, gc));\n        }\n        else if (tt->type == DEV_TYPE_TUN) /* tun p2p topology */\n        {\n            const char *l, *r;\n            if (remote)\n            {\n                r = print_in_addr_t(tt->local, 0, gc);\n                l = print_in_addr_t(tt->remote_netmask, 0, gc);\n            }\n            else\n            {\n                l = print_in_addr_t(tt->local, 0, gc);\n                r = print_in_addr_t(tt->remote_netmask, 0, gc);\n            }\n            buf_printf(&out, \"%s %s\", r, l);\n        }\n        else\n        {\n            buf_printf(&out, \"[undef]\");\n        }\n    }\n    return BSTR(&out);\n}\n\n/*\n * Return a status string describing wait state.\n */\nconst char *\ntun_stat(const struct tuntap *tt, unsigned int rwflags, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(64, gc);\n    if (tt)\n    {\n        if (rwflags & EVENT_READ)\n        {\n            buf_printf(&out, \"T%s\", (tt->rwflags_debug & EVENT_READ) ? \"R\" : \"r\");\n#ifdef _WIN32\n            buf_printf(&out, \"%s\", overlapped_io_state_ascii(&tt->reads));\n#endif\n        }\n        if (rwflags & EVENT_WRITE)\n        {\n            buf_printf(&out, \"T%s\", (tt->rwflags_debug & EVENT_WRITE) ? \"W\" : \"w\");\n#ifdef _WIN32\n            buf_printf(&out, \"%s\", overlapped_io_state_ascii(&tt->writes));\n#endif\n        }\n    }\n    else\n    {\n        buf_printf(&out, \"T?\");\n    }\n    return BSTR(&out);\n}\n\n/*\n * Return true for point-to-point topology, false for subnet topology\n */\nbool\nis_tun_p2p(const struct tuntap *tt)\n{\n    bool tun_p2p = false;\n\n    if (tt->type == DEV_TYPE_TAP || (tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET))\n    {\n        tun_p2p = false;\n    }\n    else if (tt->type == DEV_TYPE_TUN)\n    {\n        tun_p2p = true;\n    }\n    else\n    {\n        msg(M_FATAL, \"Error: problem with tun vs. tap setting\"); /* JYFIXME -- needs to be caught\n                                                                    earlier, in init_tun? */\n    }\n    return tun_p2p;\n}\n\n/*\n * Set the ifconfig_* environment variables, both for IPv4 and IPv6\n */\nvoid\ndo_ifconfig_setenv(const struct tuntap *tt, struct env_set *es)\n{\n    struct gc_arena gc = gc_new();\n    const char *ifconfig_local = print_in_addr_t(tt->local, 0, &gc);\n    const char *ifconfig_remote_netmask = print_in_addr_t(tt->remote_netmask, 0, &gc);\n\n    /*\n     * Set environmental variables with ifconfig parameters.\n     */\n    if (tt->did_ifconfig_setup)\n    {\n        bool tun = is_tun_p2p(tt);\n\n        setenv_str(es, \"ifconfig_local\", ifconfig_local);\n        if (tun)\n        {\n            setenv_str(es, \"ifconfig_remote\", ifconfig_remote_netmask);\n        }\n        else\n        {\n            setenv_str(es, \"ifconfig_netmask\", ifconfig_remote_netmask);\n        }\n    }\n\n    if (tt->did_ifconfig_ipv6_setup)\n    {\n        const char *ifconfig_ipv6_local = print_in6_addr(tt->local_ipv6, 0, &gc);\n        const char *ifconfig_ipv6_remote = print_in6_addr(tt->remote_ipv6, 0, &gc);\n\n        setenv_str(es, \"ifconfig_ipv6_local\", ifconfig_ipv6_local);\n        setenv_int(es, \"ifconfig_ipv6_netbits\", tt->netbits_ipv6);\n        setenv_str(es, \"ifconfig_ipv6_remote\", ifconfig_ipv6_remote);\n    }\n\n    gc_free(&gc);\n}\n\n/*\n * Init tun/tap object.\n *\n * Set up tuntap structure for ifconfig,\n * but don't execute yet.\n */\nstruct tuntap *\ninit_tun(const char *dev,                          /* --dev option */\n         const char *dev_type,                     /* --dev-type option */\n         int topology,                             /* one of the TOP_x values */\n         const char *ifconfig_local_parm,          /* --ifconfig parm 1 */\n         const char *ifconfig_remote_netmask_parm, /* --ifconfig parm 2 */\n         const char *ifconfig_ipv6_local_parm,     /* --ifconfig parm 1 IPv6 */\n         int ifconfig_ipv6_netbits_parm,\n         const char *ifconfig_ipv6_remote_parm,    /* --ifconfig parm 2 IPv6 */\n         struct addrinfo *local_public, struct addrinfo *remote_public, const bool strict_warn,\n         struct env_set *es, openvpn_net_ctx_t *ctx, struct tuntap *tt)\n{\n    if (!tt)\n    {\n        ALLOC_OBJ(tt, struct tuntap);\n        clear_tuntap(tt);\n    }\n\n    tt->type = dev_type_enum(dev, dev_type);\n    tt->topology = topology;\n\n    if (ifconfig_local_parm && ifconfig_remote_netmask_parm)\n    {\n        /*\n         * We only handle TUN/TAP devices here, not --dev null devices.\n         */\n        bool tun_p2p = is_tun_p2p(tt);\n\n        /*\n         * Convert arguments to binary IPv4 addresses.\n         */\n\n        tt->local =\n            getaddr(GETADDR_RESOLVE | GETADDR_HOST_ORDER | GETADDR_FATAL_ON_SIGNAL | GETADDR_FATAL,\n                    ifconfig_local_parm, 0, NULL, NULL);\n\n        tt->remote_netmask = getaddr((tun_p2p ? GETADDR_RESOLVE : 0) | GETADDR_HOST_ORDER\n                                         | GETADDR_FATAL_ON_SIGNAL | GETADDR_FATAL,\n                                     ifconfig_remote_netmask_parm, 0, NULL, NULL);\n\n        /*\n         * Look for common errors in --ifconfig parms\n         */\n        if (strict_warn)\n        {\n            struct addrinfo *curele;\n            ifconfig_sanity_check(tun_p2p, tt->remote_netmask);\n\n            /*\n             * If local_public or remote_public addresses are defined,\n             * make sure they do not clash with our virtual subnet.\n             */\n\n            for (curele = local_public; curele; curele = curele->ai_next)\n            {\n                if (curele->ai_family == AF_INET)\n                {\n                    const in_addr_t local =\n                        ntohl(((struct sockaddr_in *)curele->ai_addr)->sin_addr.s_addr);\n                    check_addr_clash(\"local\", tt->type, local, tt->local, tt->remote_netmask);\n                }\n            }\n\n            for (curele = remote_public; curele; curele = curele->ai_next)\n            {\n                if (curele->ai_family == AF_INET)\n                {\n                    const in_addr_t remote =\n                        ntohl(((struct sockaddr_in *)curele->ai_addr)->sin_addr.s_addr);\n                    check_addr_clash(\"remote\", tt->type, remote, tt->local, tt->remote_netmask);\n                }\n            }\n        }\n\n#ifdef _WIN32\n        /*\n         * Make sure that both ifconfig addresses are part of the\n         * same .252 subnet.\n         */\n        if (tun_p2p)\n        {\n            verify_255_255_255_252(tt->local, tt->remote_netmask);\n            tt->adapter_netmask = ~3;\n        }\n        else\n        {\n            tt->adapter_netmask = tt->remote_netmask;\n        }\n#endif\n\n        tt->did_ifconfig_setup = true;\n    }\n\n    if (ifconfig_ipv6_local_parm && ifconfig_ipv6_remote_parm)\n    {\n        /*\n         * Convert arguments to binary IPv6 addresses.\n         */\n\n        if (inet_pton(AF_INET6, ifconfig_ipv6_local_parm, &tt->local_ipv6) != 1\n            || inet_pton(AF_INET6, ifconfig_ipv6_remote_parm, &tt->remote_ipv6) != 1)\n        {\n            msg(M_FATAL, \"init_tun: problem converting IPv6 ifconfig addresses %s and %s to binary\",\n                ifconfig_ipv6_local_parm, ifconfig_ipv6_remote_parm);\n        }\n        tt->netbits_ipv6 = ifconfig_ipv6_netbits_parm;\n\n        tt->did_ifconfig_ipv6_setup = true;\n    }\n\n    /*\n     * Set environmental variables with ifconfig parameters.\n     */\n    if (es)\n    {\n        do_ifconfig_setenv(tt, es);\n    }\n\n    return tt;\n}\n\n/*\n * Platform specific tun initializations\n */\nvoid\ninit_tun_post(struct tuntap *tt, const struct frame *frame, const struct tuntap_options *options)\n{\n    tt->options = *options;\n#ifdef _WIN32\n    if (tt->backend_driver == DRIVER_DCO)\n    {\n        tt->dco.tt = tt;\n        return;\n    }\n\n    overlapped_io_init(&tt->reads, frame, FALSE);\n    overlapped_io_init(&tt->writes, frame, TRUE);\n    tt->adapter_index = TUN_ADAPTER_INDEX_INVALID;\n\n    tt->rw_handle.read = tt->reads.overlapped.hEvent;\n    tt->rw_handle.write = tt->writes.overlapped.hEvent;\n#endif /* ifdef _WIN32 */\n}\n\n#if defined(_WIN32)\n\n/* some of the platforms will auto-add a \"network route\" pointing\n * to the interface on \"ifconfig tunX 2001:db8::1/64\", others need\n * an extra call to \"route add...\"\n * -> helper function to simplify code below\n */\nstatic void\nadd_route_connected_v6_net(struct tuntap *tt, const struct env_set *es)\n{\n    struct route_ipv6 r6;\n\n    CLEAR(r6);\n    r6.network = tt->local_ipv6;\n    r6.netbits = tt->netbits_ipv6;\n    r6.gateway = tt->local_ipv6;\n    r6.metric = 0; /* connected route */\n    r6.flags = RT_DEFINED | RT_METRIC_DEFINED;\n    add_route_ipv6(&r6, tt, 0, es, NULL);\n}\n\nvoid\ndelete_route_connected_v6_net(const struct tuntap *tt)\n{\n    struct route_ipv6 r6;\n\n    CLEAR(r6);\n    r6.network = tt->local_ipv6;\n    r6.netbits = tt->netbits_ipv6;\n    r6.gateway = tt->local_ipv6;\n    r6.metric = 0; /* connected route */\n    r6.flags = RT_DEFINED | RT_ADDED | RT_METRIC_DEFINED;\n    route_ipv6_clear_host_bits(&r6);\n    delete_route_ipv6(&r6, tt, NULL, NULL);\n}\n#endif /* if defined(_WIN32) || defined(TARGET_DARWIN) || defined(TARGET_NETBSD) || \\\n          defined(TARGET_OPENBSD) */\n\n#if defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY) || defined(TARGET_NETBSD) \\\n    || defined(TARGET_OPENBSD)\n/* we can't use true subnet mode on tun on all platforms, as that\n * conflicts with IPv6 (wants to use ND then, which we don't do),\n * but the OSes want \"a remote address that is different from ours\"\n * - so we construct one, normally the first in the subnet, but if\n * this is the same as ours, use the second one.\n * The actual address does not matter at all, as the tun interface\n * is still point to point and no layer 2 resolution is done...\n */\n\nin_addr_t\ncreate_arbitrary_remote(struct tuntap *tt)\n{\n    in_addr_t remote;\n\n    remote = (tt->local & tt->remote_netmask) + 1;\n\n    if (remote == tt->local)\n    {\n        remote++;\n    }\n\n    return remote;\n}\n#endif\n\n/**\n * do_ifconfig_ipv6 - perform platform specific ifconfig6 commands\n *\n * @param tt        the tuntap interface context\n * @param ifname    the human readable interface name\n * @param tun_mtu   the MTU value to set the interface to\n * @param es        the environment to be used when executing the commands\n * @param ctx       the networking API opaque context\n */\nstatic void\ndo_ifconfig_ipv6(struct tuntap *tt, const char *ifname, int tun_mtu, const struct env_set *es,\n                 openvpn_net_ctx_t *ctx)\n{\n#if !defined(TARGET_LINUX)\n    struct argv argv = argv_new();\n    struct gc_arena gc = gc_new();\n    const char *ifconfig_ipv6_local = print_in6_addr(tt->local_ipv6, 0, &gc);\n#endif\n\n#if defined(TARGET_LINUX)\n    if (net_iface_mtu_set(ctx, ifname, tun_mtu) < 0)\n    {\n        msg(M_FATAL, \"Linux can't set mtu (%d) on %s\", tun_mtu, ifname);\n    }\n\n    if (net_iface_up(ctx, ifname, true) < 0)\n    {\n        msg(M_FATAL, \"Linux can't bring %s up\", ifname);\n    }\n\n    if (net_addr_v6_add(ctx, ifname, &tt->local_ipv6, tt->netbits_ipv6) < 0)\n    {\n        msg(M_FATAL, \"Linux can't add IPv6 to interface %s\", ifname);\n    }\n#elif defined(TARGET_ANDROID)\n    char out6[64];\n\n    snprintf(out6, sizeof(out6), \"%s/%d %d\", ifconfig_ipv6_local, tt->netbits_ipv6, tun_mtu);\n    management_android_control(management, \"IFCONFIG6\", out6);\n#elif defined(TARGET_SOLARIS)\n    argv_printf(&argv, \"%s %s inet6 unplumb\", IFCONFIG_PATH, ifname);\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, es, 0, NULL);\n\n    if (tt->type == DEV_TYPE_TUN)\n    {\n        const char *ifconfig_ipv6_remote = print_in6_addr(tt->remote_ipv6, 0, &gc);\n\n        argv_printf(&argv, \"%s %s inet6 plumb %s/%d %s mtu %d up\", IFCONFIG_PATH, ifname,\n                    ifconfig_ipv6_local, tt->netbits_ipv6, ifconfig_ipv6_remote, tun_mtu);\n    }\n    else /* tap mode */\n    {\n        /* base IPv6 tap interface needs to be brought up first */\n        argv_printf(&argv, \"%s %s inet6 plumb up\", IFCONFIG_PATH, ifname);\n        argv_msg(M_INFO, &argv);\n\n        if (!openvpn_execve_check(&argv, es, 0, \"Solaris ifconfig IPv6 (prepare) failed\"))\n        {\n            solaris_error_close(tt, es, ifname, true);\n        }\n\n        /* we might need to do \"ifconfig %s inet6 auto-dhcp drop\"\n         * after the system has noticed the interface and fired up\n         * the DHCPv6 client - but this takes quite a while, and the\n         * server will ignore the DHCPv6 packets anyway.  So we don't.\n         */\n\n        /* static IPv6 addresses need to go to a subinterface (tap0:1)\n         * and we cannot set an mtu here (must go to the \"parent\")\n         */\n        argv_printf(&argv, \"%s %s inet6 addif %s/%d up\", IFCONFIG_PATH, ifname, ifconfig_ipv6_local,\n                    tt->netbits_ipv6);\n    }\n    argv_msg(M_INFO, &argv);\n\n    if (!openvpn_execve_check(&argv, es, 0, \"Solaris ifconfig IPv6 failed\"))\n    {\n        solaris_error_close(tt, es, ifname, true);\n    }\n\n    if (tt->type != DEV_TYPE_TUN)\n    {\n        argv_printf(&argv, \"%s %s inet6 mtu %d\", IFCONFIG_PATH, ifname, tun_mtu);\n        argv_msg(M_INFO, &argv);\n        openvpn_execve_check(&argv, es, 0, \"Solaris ifconfig IPv6 mtu failed\");\n    }\n#elif defined(TARGET_OPENBSD) || defined(TARGET_NETBSD) || defined(TARGET_DARWIN) \\\n    || defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY)\n    argv_printf(&argv, \"%s %s inet6 %s/%d mtu %d up\", IFCONFIG_PATH, ifname, ifconfig_ipv6_local,\n                tt->netbits_ipv6, tun_mtu);\n    argv_msg(M_INFO, &argv);\n\n    openvpn_execve_check(&argv, es, S_FATAL, \"generic BSD ifconfig inet6 failed\");\n\n#if defined(TARGET_FREEBSD) && __FreeBSD_version >= 1200000 && __FreeBSD_version < 1300000\n    /* On FreeBSD 12.0-12.4, there is ipv6_activate_all_interfaces=\"YES\"\n     * in rc.conf, which is not set by default.  If it is *not* set,\n     * \"all new interfaces that are not already up\" are configured by\n     * devd -> /etc/pccard_ether -> /etc/network.subr as \"inet6 ifdisabled\".\n     *\n     * The \"is this interface already up?\" test is a non-zero time window\n     * which we manage to hit with our ifconfig often enough to cause\n     * frequent fails in the openvpn test environment.\n     *\n     * Thus: assume that the system might interfere, wait for things to\n     * settle (it's a very short time window), and remove -ifdisable again.\n     *\n     * See: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=248172\n     */\n    sleep(1);\n    argv_printf(&argv, \"%s %s inet6 -ifdisabled\", IFCONFIG_PATH, ifname);\n    argv_msg(M_INFO, &argv);\n\n    openvpn_execve_check(&argv, es, S_FATAL, \"FreeBSD BSD 'ifconfig inet6 -ifdisabled' failed\");\n#endif\n\n#elif defined(TARGET_AIX)\n    argv_printf(&argv, \"%s %s inet6 %s/%d mtu %d up\", IFCONFIG_PATH, ifname, ifconfig_ipv6_local,\n                tt->netbits_ipv6, tun_mtu);\n    argv_msg(M_INFO, &argv);\n\n    /* AIX ifconfig will complain if it can't find ODM path in env */\n    es = env_set_create(NULL);\n    env_set_add(es, \"ODMDIR=/etc/objrepos\");\n\n    openvpn_execve_check(&argv, es, S_FATAL, \"generic BSD ifconfig inet6 failed\");\n\n    env_set_destroy(es);\n#elif defined(_WIN32)\n    if (tt->options.ip_win32_type == IPW32_SET_MANUAL)\n    {\n        msg(M_INFO,\n            \"******** NOTE:  Please manually set the v6 IP of '%s' to %s (if it is not already set)\",\n            ifname, ifconfig_ipv6_local);\n    }\n    else if (tt->options.msg_channel)\n    {\n        do_address_service(true, AF_INET6, tt);\n        if (tt->type == DEV_TYPE_TUN)\n        {\n            add_route_connected_v6_net(tt, es);\n        }\n        do_dns_service(true, AF_INET6, tt);\n        do_set_mtu_service(tt, AF_INET6, tun_mtu);\n        /* If IPv4 is not enabled, set DNS domain here */\n        if (!tt->did_ifconfig_setup)\n        {\n            do_dns_domain_service(true, tt);\n        }\n    }\n    else\n    {\n        /* example: netsh interface ipv6 set address 42\n         *                  2001:608:8003::d/bits store=active\n         */\n\n        /* in TUN mode, we only simulate a subnet, so the interface\n         * is configured with /128 + a route to fe80::8.  In TAP mode,\n         * the correct netbits must be set, and no on-link route\n         */\n        int netbits = (tt->type == DEV_TYPE_TUN) ? 128 : tt->netbits_ipv6;\n\n        argv_printf(&argv, \"%s%s interface ipv6 set address %lu %s/%d store=active\",\n                    get_win_sys_path(), NETSH_PATH_SUFFIX, tt->adapter_index, ifconfig_ipv6_local,\n                    netbits);\n        netsh_command(&argv, 4, M_FATAL);\n        if (tt->type == DEV_TYPE_TUN)\n        {\n            add_route_connected_v6_net(tt, es);\n        }\n        /* set ipv6 dns servers if any are specified */\n        netsh_set_dns6_servers(tt->options.dns6, tt->options.dns6_len, tt->adapter_index);\n        windows_set_mtu(tt->adapter_index, AF_INET6, tun_mtu);\n\n        if (!tt->did_ifconfig_setup)\n        {\n            do_dns_domain_pwsh(true, tt);\n        }\n    }\n#else  /* platforms we have no IPv6 code for */\n    msg(M_FATAL,\n        \"Sorry, but I don't know how to do IPv6 'ifconfig' commands on this operating system.  You should ifconfig your TUN/TAP device manually or use an --up script.\");\n#endif /* outer \"if defined(TARGET_xxx)\" conditional */\n\n#if !defined(TARGET_LINUX)\n    gc_free(&gc);\n    argv_free(&argv);\n#endif\n}\n\n/**\n * do_ifconfig_ipv4 - perform platform specific ifconfig commands\n *\n * @param tt        the tuntap interface context\n * @param ifname    the human readable interface name\n * @param tun_mtu   the MTU value to set the interface to\n * @param es        the environment to be used when executing the commands\n * @param ctx       the networking API opaque context\n */\nstatic void\ndo_ifconfig_ipv4(struct tuntap *tt, const char *ifname, int tun_mtu, const struct env_set *es,\n                 openvpn_net_ctx_t *ctx)\n{\n#if !defined(_WIN32) && !defined(TARGET_ANDROID)\n    /*\n     * We only handle TUN/TAP devices here, not --dev null devices.\n     */\n    bool tun_p2p = is_tun_p2p(tt);\n#endif\n\n#if !defined(TARGET_LINUX)\n    const char *ifconfig_local = NULL;\n    const char *ifconfig_remote_netmask = NULL;\n    struct argv argv = argv_new();\n    struct gc_arena gc = gc_new();\n\n    /*\n     * Set ifconfig parameters\n     */\n    ifconfig_local = print_in_addr_t(tt->local, 0, &gc);\n    ifconfig_remote_netmask = print_in_addr_t(tt->remote_netmask, 0, &gc);\n#endif\n\n#if defined(TARGET_LINUX)\n    if (net_iface_mtu_set(ctx, ifname, tun_mtu) < 0)\n    {\n        msg(M_FATAL, \"Linux can't set mtu (%d) on %s\", tun_mtu, ifname);\n    }\n\n    if (net_iface_up(ctx, ifname, true) < 0)\n    {\n        msg(M_FATAL, \"Linux can't bring %s up\", ifname);\n    }\n\n    if (tun_p2p)\n    {\n        if (net_addr_ptp_v4_add(ctx, ifname, &tt->local, &tt->remote_netmask) < 0)\n        {\n            msg(M_FATAL, \"Linux can't add IP to interface %s\", ifname);\n        }\n    }\n    else\n    {\n        if (net_addr_v4_add(ctx, ifname, &tt->local, netmask_to_netbits2(tt->remote_netmask)) < 0)\n        {\n            msg(M_FATAL, \"Linux can't add IP to interface %s\", ifname);\n        }\n    }\n#elif defined(TARGET_ANDROID)\n    char out[64];\n\n    snprintf(out, sizeof(out), \"%s %s %d %s\", ifconfig_local, ifconfig_remote_netmask, tun_mtu,\n             print_topology(tt->topology));\n    management_android_control(management, \"IFCONFIG\", out);\n\n#elif defined(TARGET_SOLARIS)\n    /* Solaris 2.6 (and 7?) cannot set all parameters in one go...\n     * example:\n     *    ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 up\n     *    ifconfig tun2 netmask 255.255.255.255\n     */\n    if (tun_p2p)\n    {\n        argv_printf(&argv, \"%s %s %s %s mtu %d up\", IFCONFIG_PATH, ifname, ifconfig_local,\n                    ifconfig_remote_netmask, tun_mtu);\n\n        argv_msg(M_INFO, &argv);\n        if (!openvpn_execve_check(&argv, es, 0, \"Solaris ifconfig phase-1 failed\"))\n        {\n            solaris_error_close(tt, es, ifname, false);\n        }\n\n        argv_printf(&argv, \"%s %s netmask 255.255.255.255\", IFCONFIG_PATH, ifname);\n    }\n    else if (tt->type == DEV_TYPE_TUN)\n    {\n        argv_printf(&argv, \"%s %s %s %s netmask %s mtu %d up\", IFCONFIG_PATH, ifname,\n                    ifconfig_local, ifconfig_local, ifconfig_remote_netmask, tun_mtu);\n    }\n    else /* tap */\n    {\n        argv_printf(&argv, \"%s %s %s netmask %s up\", IFCONFIG_PATH, ifname, ifconfig_local,\n                    ifconfig_remote_netmask);\n    }\n\n    argv_msg(M_INFO, &argv);\n    if (!openvpn_execve_check(&argv, es, 0, \"Solaris ifconfig phase-2 failed\"))\n    {\n        solaris_error_close(tt, es, ifname, false);\n    }\n\n    if (!tun_p2p && tt->type == DEV_TYPE_TUN)\n    {\n        /* Add a network route for the local tun interface */\n        struct route_ipv4 r;\n        CLEAR(r);\n        r.flags = RT_DEFINED | RT_METRIC_DEFINED;\n        r.network = tt->local & tt->remote_netmask;\n        r.netmask = tt->remote_netmask;\n        r.gateway = tt->local;\n        r.metric = 0;\n        add_route(&r, tt, 0, NULL, es, NULL);\n    }\n\n#elif defined(TARGET_OPENBSD)\n\n    in_addr_t remote_end; /* for \"virtual\" subnet topology */\n\n    /*\n     * On OpenBSD, tun interfaces are persistent if created with\n     * \"ifconfig tunX create\", and auto-destroyed if created by\n     * opening \"/dev/tunX\" (so we just use the /dev/tunX)\n     */\n\n    /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */\n    if (tun_p2p)\n    {\n        argv_printf(&argv, \"%s %s %s %s mtu %d netmask 255.255.255.255 up -link0\", IFCONFIG_PATH,\n                    ifname, ifconfig_local, ifconfig_remote_netmask, tun_mtu);\n    }\n    else if (tt->type == DEV_TYPE_TUN)\n    {\n        remote_end = create_arbitrary_remote(tt);\n        argv_printf(&argv, \"%s %s %s %s mtu %d netmask %s up -link0\", IFCONFIG_PATH, ifname,\n                    ifconfig_local, print_in_addr_t(remote_end, 0, &gc), tun_mtu,\n                    ifconfig_remote_netmask);\n    }\n    else /* tap */\n    {\n        argv_printf(&argv, \"%s %s %s netmask %s mtu %d link0\", IFCONFIG_PATH, ifname,\n                    ifconfig_local, ifconfig_remote_netmask, tun_mtu);\n    }\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, es, S_FATAL, \"OpenBSD ifconfig failed\");\n\n    /* Add a network route for the local tun interface */\n    if (!tun_p2p && tt->type == DEV_TYPE_TUN)\n    {\n        struct route_ipv4 r;\n        CLEAR(r);\n        r.flags = RT_DEFINED;\n        r.network = tt->local & tt->remote_netmask;\n        r.netmask = tt->remote_netmask;\n        r.gateway = remote_end;\n        add_route(&r, tt, 0, NULL, es, NULL);\n    }\n\n#elif defined(TARGET_NETBSD)\n    in_addr_t remote_end = INADDR_ANY; /* for \"virtual\" subnet topology */\n\n    if (tun_p2p)\n    {\n        argv_printf(&argv, \"%s %s %s %s mtu %d netmask 255.255.255.255 up\", IFCONFIG_PATH, ifname,\n                    ifconfig_local, ifconfig_remote_netmask, tun_mtu);\n    }\n    else if (tt->type == DEV_TYPE_TUN)\n    {\n        remote_end = create_arbitrary_remote(tt);\n        argv_printf(&argv, \"%s %s %s %s mtu %d netmask %s up\", IFCONFIG_PATH, ifname,\n                    ifconfig_local, print_in_addr_t(remote_end, 0, &gc), tun_mtu,\n                    ifconfig_remote_netmask);\n    }\n    else /* tap */\n    {\n        /*\n         * NetBSD has distinct tun and tap devices\n         * so we don't need the \"link0\" extra parameter to specify we want to do\n         * tunneling at the ethernet level\n         */\n        argv_printf(&argv, \"%s %s %s netmask %s mtu %d\", IFCONFIG_PATH, ifname, ifconfig_local,\n                    ifconfig_remote_netmask, tun_mtu);\n    }\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, es, S_FATAL, \"NetBSD ifconfig failed\");\n\n    /* Add a network route for the local tun interface */\n    if (!tun_p2p && tt->type == DEV_TYPE_TUN)\n    {\n        struct route_ipv4 r;\n        CLEAR(r);\n        r.flags = RT_DEFINED;\n        r.network = tt->local & tt->remote_netmask;\n        r.netmask = tt->remote_netmask;\n        r.gateway = remote_end;\n        add_route(&r, tt, 0, NULL, es, NULL);\n    }\n\n#elif defined(TARGET_DARWIN)\n    /*\n     * Darwin (i.e. Mac OS X) seems to exhibit similar behaviour to OpenBSD...\n     */\n\n    argv_printf(&argv, \"%s %s delete\", IFCONFIG_PATH, ifname);\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, es, 0, NULL);\n    msg(M_INFO, \"NOTE: Tried to delete pre-existing tun/tap instance -- No Problem if failure\");\n\n\n    /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */\n    if (tun_p2p)\n    {\n        argv_printf(&argv, \"%s %s %s %s mtu %d netmask 255.255.255.255 up\", IFCONFIG_PATH, ifname,\n                    ifconfig_local, ifconfig_remote_netmask, tun_mtu);\n    }\n    else if (tt->type == DEV_TYPE_TUN)\n    {\n        argv_printf(&argv, \"%s %s %s %s netmask %s mtu %d up\", IFCONFIG_PATH, ifname,\n                    ifconfig_local, ifconfig_local, ifconfig_remote_netmask, tun_mtu);\n    }\n    else /* tap */\n    {\n        argv_printf(&argv, \"%s %s %s netmask %s mtu %d up\", IFCONFIG_PATH, ifname, ifconfig_local,\n                    ifconfig_remote_netmask, tun_mtu);\n    }\n\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, es, S_FATAL, \"Mac OS X ifconfig failed\");\n\n    /* Add a network route for the local tun interface */\n    if (!tun_p2p && tt->type == DEV_TYPE_TUN)\n    {\n        struct route_ipv4 r;\n        CLEAR(r);\n        r.flags = RT_DEFINED;\n        r.network = tt->local & tt->remote_netmask;\n        r.netmask = tt->remote_netmask;\n        r.gateway = tt->local;\n        add_route(&r, tt, 0, NULL, es, NULL);\n    }\n\n#elif defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY)\n\n    /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */\n    if (tun_p2p) /* point-to-point tun */\n    {\n        argv_printf(&argv, \"%s %s %s %s mtu %d netmask 255.255.255.255 up\", IFCONFIG_PATH, ifname,\n                    ifconfig_local, ifconfig_remote_netmask, tun_mtu);\n    }\n    else /* tun with topology subnet and tap mode (always subnet) */\n    {\n        int netbits = netmask_to_netbits2(tt->remote_netmask);\n        argv_printf(&argv, \"%s %s %s/%d mtu %d up\", IFCONFIG_PATH, ifname, ifconfig_local, netbits,\n                    tun_mtu);\n    }\n\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, es, S_FATAL, \"FreeBSD ifconfig failed\");\n\n#elif defined(TARGET_AIX)\n    {\n        /* AIX ifconfig will complain if it can't find ODM path in env */\n        struct env_set *aix_es = env_set_create(NULL);\n        env_set_add(aix_es, \"ODMDIR=/etc/objrepos\");\n\n        if (tt->type == DEV_TYPE_TUN)\n        {\n            msg(M_FATAL, \"no tun support on AIX (canthappen)\");\n        }\n\n        /* example: ifconfig tap0 172.30.1.1 netmask 255.255.254.0 up */\n        argv_printf(&argv, \"%s %s %s netmask %s mtu %d up\", IFCONFIG_PATH, ifname, ifconfig_local,\n                    ifconfig_remote_netmask, tun_mtu);\n\n        argv_msg(M_INFO, &argv);\n        openvpn_execve_check(&argv, aix_es, S_FATAL, \"AIX ifconfig failed\");\n\n        env_set_destroy(aix_es);\n    }\n#elif defined(_WIN32)\n    if (tt->options.ip_win32_type == IPW32_SET_MANUAL)\n    {\n        msg(M_INFO,\n            \"******** NOTE:  Please manually set the IP/netmask of '%s' to %s/%s (if it is not already set)\",\n            ifname, ifconfig_local, ifconfig_remote_netmask);\n    }\n    else if (tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ\n             || tt->options.ip_win32_type == IPW32_SET_ADAPTIVE)\n    {\n        /* Let the DHCP configure the interface. */\n    }\n    else if (tt->options.msg_channel)\n    {\n        do_address_service(true, AF_INET, tt);\n        do_dns_service(true, AF_INET, tt);\n        do_dns_domain_service(true, tt);\n        do_wins_service(true, tt);\n    }\n    else\n    {\n        if (tt->options.ip_win32_type == IPW32_SET_NETSH)\n        {\n            netsh_ifconfig(&tt->options, tt->adapter_index, tt->local, tt->adapter_netmask,\n                           NI_IP_NETMASK | NI_OPTIONS);\n        }\n\n        do_dns_domain_pwsh(true, tt);\n    }\n\n\n    if (tt->options.msg_channel)\n    {\n        do_set_mtu_service(tt, AF_INET, tun_mtu);\n    }\n    else\n    {\n        windows_set_mtu(tt->adapter_index, AF_INET, tun_mtu);\n    }\n#elif defined(TARGET_HAIKU)\n    /* example: ifconfig tun/0 inet 1.1.1.1 255.255.255.0 mtu 1450 up */\n    argv_printf(&argv, \"%s %s inet %s %s mtu %d up\", IFCONFIG_PATH, ifname, ifconfig_local,\n                ifconfig_remote_netmask, tun_mtu);\n\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, es, S_FATAL, \"Haiku ifconfig failed\");\n#else  /* if defined(TARGET_LINUX) */\n    msg(M_FATAL,\n        \"Sorry, but I don't know how to do 'ifconfig' commands on this operating system.  You should ifconfig your TUN/TAP device manually or use an --up script.\");\n#endif /* if defined(TARGET_LINUX) */\n\n#if !defined(TARGET_LINUX)\n    gc_free(&gc);\n    argv_free(&argv);\n#endif\n}\n\n/* execute the ifconfig command through the shell */\nvoid\ndo_ifconfig(struct tuntap *tt, const char *ifname, int tun_mtu, const struct env_set *es,\n            openvpn_net_ctx_t *ctx)\n{\n    msg(D_LOW, \"do_ifconfig, ipv4=%d, ipv6=%d\", tt->did_ifconfig_setup,\n        tt->did_ifconfig_ipv6_setup);\n\n#ifdef ENABLE_MANAGEMENT\n    if (management)\n    {\n        management_set_state(management, OPENVPN_STATE_ASSIGN_IP, NULL, &tt->local, &tt->local_ipv6,\n                             NULL, NULL);\n    }\n#endif\n\n    if (tt->did_ifconfig_setup)\n    {\n        do_ifconfig_ipv4(tt, ifname, tun_mtu, es, ctx);\n    }\n\n    if (tt->did_ifconfig_ipv6_setup)\n    {\n        do_ifconfig_ipv6(tt, ifname, tun_mtu, es, ctx);\n    }\n\n    /* release resources potentially allocated during interface setup */\n    net_ctx_free(ctx);\n}\n\nstatic void\nundo_ifconfig_ipv4(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n#if defined(TARGET_LINUX)\n    int netbits = netmask_to_netbits2(tt->remote_netmask);\n\n    if (is_tun_p2p(tt))\n    {\n        if (net_addr_ptp_v4_del(ctx, tt->actual_name, &tt->local, &tt->remote_netmask) < 0)\n        {\n            msg(M_WARN, \"Linux can't del IP from iface %s\", tt->actual_name);\n        }\n    }\n    else\n    {\n        if (net_addr_v4_del(ctx, tt->actual_name, &tt->local, netbits) < 0)\n        {\n            msg(M_WARN, \"Linux can't del IP from iface %s\", tt->actual_name);\n        }\n    }\n#elif defined(TARGET_FREEBSD)\n    struct gc_arena gc = gc_new();\n    const char *ifconfig_local = print_in_addr_t(tt->local, 0, &gc);\n    struct argv argv = argv_new();\n\n    argv_printf(&argv, \"%s %s %s -alias\", IFCONFIG_PATH, tt->actual_name, ifconfig_local);\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, NULL, 0, \"FreeBSD ip addr del failed\");\n\n    argv_free(&argv);\n    gc_free(&gc);\n#endif /* if defined(TARGET_LINUX) */\n       /* Empty for _WIN32 and all other unixoid platforms */\n}\n\nstatic void\nundo_ifconfig_ipv6(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n#if defined(TARGET_LINUX)\n    if (net_addr_v6_del(ctx, tt->actual_name, &tt->local_ipv6, tt->netbits_ipv6) < 0)\n    {\n        msg(M_WARN, \"Linux can't del IPv6 from iface %s\", tt->actual_name);\n    }\n#elif defined(TARGET_FREEBSD)\n    struct gc_arena gc = gc_new();\n    const char *ifconfig_ipv6_local = print_in6_addr(tt->local_ipv6, 0, &gc);\n    struct argv argv = argv_new();\n\n    argv_printf(&argv, \"%s %s inet6 %s/%d -alias\", IFCONFIG_PATH, tt->actual_name,\n                ifconfig_ipv6_local, tt->netbits_ipv6);\n\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, NULL, 0, \"FreeBSD ip -6 addr del failed\");\n\n    argv_free(&argv);\n    gc_free(&gc);\n#endif /* if defined(TARGET_LINUX) */\n       /* Empty for _WIN32 and all other unixoid platforms */\n}\n\nvoid\nundo_ifconfig(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    if (tt->backend_driver != DRIVER_NULL && tt->backend_driver != DRIVER_AFUNIX)\n    {\n        if (tt->did_ifconfig_setup)\n        {\n            undo_ifconfig_ipv4(tt, ctx);\n        }\n\n        if (tt->did_ifconfig_ipv6_setup)\n        {\n            undo_ifconfig_ipv6(tt, ctx);\n        }\n\n        /* release resources potentially allocated during undo */\n        net_ctx_reset(ctx);\n    }\n}\n\nstatic void\nclear_tuntap(struct tuntap *tuntap)\n{\n    CLEAR(*tuntap);\n#ifdef _WIN32\n    tuntap->hand = NULL;\n#else\n    tuntap->fd = -1;\n#endif\n#ifdef TARGET_SOLARIS\n    tuntap->ip_fd = -1;\n#endif\n}\n\n#if defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY) || defined(TARGET_NETBSD) || defined(TARGET_OPENBSD) || defined(TARGET_DARWIN)\n\n/*\n * BSDs and Mac OS X when using utun\n * have a slightly incompatible TUN device from\n * the rest of the world, in that it prepends a\n * uint32 to the beginning of the IP header\n * to designate the protocol (why not just\n * look at the version field in the IP header to\n * determine v4 or v6?).\n *\n * We strip off this field on reads and\n * put it back on writes.\n *\n * For TAP devices, this is not needed and must\n * not be done.\n */\n\n#include <netinet/ip.h>\n#include <sys/uio.h>\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\nstatic inline ssize_t\nheader_modify_read_write_return(ssize_t len)\n{\n    if (len > 0)\n    {\n        return len > sizeof(u_int32_t) ? len - sizeof(u_int32_t) : 0;\n    }\n    else\n    {\n        return len;\n    }\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nstatic ssize_t\nwrite_tun_header(struct tuntap *tt, uint8_t *buf, int len)\n{\n    if (tt->type == DEV_TYPE_TUN)\n    {\n        u_int32_t type;\n        struct iovec iv[2];\n        struct ip *iph = (struct ip *)buf;\n\n        if (iph->ip_v == 6)\n        {\n            type = htonl(AF_INET6);\n        }\n        else\n        {\n            type = htonl(AF_INET);\n        }\n\n        iv[0].iov_base = &type;\n        iv[0].iov_len = sizeof(type);\n        iv[1].iov_base = buf;\n        iv[1].iov_len = len;\n\n        return header_modify_read_write_return(writev(tt->fd, iv, 2));\n    }\n    else\n    {\n        return write(tt->fd, buf, len);\n    }\n}\n\nstatic ssize_t\nread_tun_header(struct tuntap *tt, uint8_t *buf, int len)\n{\n    if (tt->type == DEV_TYPE_TUN)\n    {\n        u_int32_t type;\n        struct iovec iv[2];\n\n        iv[0].iov_base = &type;\n        iv[0].iov_len = sizeof(type);\n        iv[1].iov_base = buf;\n        iv[1].iov_len = len;\n\n        return header_modify_read_write_return(readv(tt->fd, iv, 2));\n    }\n    else\n    {\n        return read(tt->fd, buf, len);\n    }\n}\n\n/* For MacOS this extra handling is conditional on the UTUN driver.\n * So it needs its own read_tun()/write_tun() with the necessary\n * checks. They are located in the macOS-specific section below.\n */\n#if !defined(TARGET_DARWIN)\nssize_t\nwrite_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    return write_tun_header(tt, buf, len);\n}\n\nssize_t\nread_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    return read_tun_header(tt, buf, len);\n}\n#endif\n\n#endif /* if defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY) || defined(TARGET_NETBSD) || defined (TARGET_OPENBSD) || defined(TARGET_DARWIN) */\n\nbool\ntun_name_is_fixed(const char *dev)\n{\n    return has_digit(dev);\n}\n\n#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)\nstatic bool\ntun_dco_enabled(struct tuntap *tt)\n{\n    return tt->backend_driver == DRIVER_DCO;\n}\n#endif\n\n\n#if !(defined(_WIN32) || defined(TARGET_LINUX) || defined(TARGET_SOLARIS) || defined(TARGET_ANDROID))\nstatic void\nopen_tun_generic(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt)\n{\n    char tunname[256];\n    char dynamic_name[256];\n    bool dynamic_opened = false;\n\n    /*\n     * --dev-node specified, so open an explicit device node\n     */\n    if (dev_node)\n    {\n        snprintf(tunname, sizeof(tunname), \"%s\", dev_node);\n    }\n    else\n    {\n        /*\n         * dynamic open is indicated by --dev specified without\n         * explicit unit number.  Try opening /dev/[dev]n\n         * where n = [0, 255].\n         */\n\n        if (!tun_name_is_fixed(dev))\n        {\n            for (int i = 0; i < 256; ++i)\n            {\n                /* some platforms have a dedicated directory per driver */\n                char *sep = \"\";\n#if defined(TARGET_HAIKU)\n                sep = \"/\";\n#endif\n                snprintf(tunname, sizeof(tunname), \"/dev/%s%s%d\", dev, sep, i);\n                snprintf(dynamic_name, sizeof(dynamic_name), \"%s%s%d\", dev, sep, i);\n                if ((tt->fd = open(tunname, O_RDWR)) > 0)\n                {\n                    dynamic_opened = true;\n                    break;\n                }\n                msg(D_READ_WRITE | M_ERRNO, \"Tried opening %s (failed)\", tunname);\n            }\n            if (!dynamic_opened)\n            {\n                msg(M_FATAL, \"Cannot allocate TUN/TAP dev dynamically\");\n            }\n        }\n        /*\n         * explicit unit number specified\n         */\n        else\n        {\n            snprintf(tunname, sizeof(tunname), \"/dev/%s\", dev);\n        }\n    }\n\n    if (!dynamic_opened)\n    {\n        /* has named device existed before? if so, don't destroy at end */\n        if (if_nametoindex(dev) > 0)\n        {\n            msg(M_INFO, \"TUN/TAP device %s exists previously, keep at program end\", dev);\n            tt->persistent_if = true;\n        }\n\n        if ((tt->fd = open(tunname, O_RDWR)) < 0)\n        {\n            msg(M_ERR, \"Cannot open TUN/TAP dev %s\", tunname);\n        }\n    }\n\n    set_nonblock(tt->fd);\n    set_cloexec(tt->fd); /* don't pass fd to scripts */\n    msg(M_INFO, \"TUN/TAP device %s opened\", tunname);\n\n    /* tt->actual_name is passed to up and down scripts and used as the ifconfig dev name */\n    tt->actual_name = string_alloc(dynamic_opened ? dynamic_name : dev, NULL);\n}\n#endif /* !_WIN32 && !TARGET_LINUX && !TARGET_FREEBSD*/\n\n#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)\nstatic void\nopen_tun_dco_generic(const char *dev, const char *dev_type, struct tuntap *tt,\n                     openvpn_net_ctx_t *ctx)\n{\n    char dynamic_name[256];\n    bool dynamic_opened = false;\n\n    /*\n     * unlike \"open_tun_generic()\", DCO on Linux and FreeBSD follows\n     * the device naming model of \"non-DCO linux\", that is:\n     *   --dev tun         -> try tun0, tun1, ... tun255, use first free\n     *   --dev <anything>  -> (try to) create a tun device named \"anything\"\n     * (\"--dev tap\" and \"--dev null\" are caught earlier and not handled here)\n     */\n\n    if (strcmp(dev, \"tun\") == 0)\n    {\n        for (int i = 0; i < 256; ++i)\n        {\n            snprintf(dynamic_name, sizeof(dynamic_name), \"%s%d\", dev, i);\n            int ret = open_tun_dco(tt, ctx, dynamic_name);\n            if (ret == 0)\n            {\n                dynamic_opened = true;\n                msg(M_INFO, \"DCO device %s opened\", dynamic_name);\n                break;\n            }\n            /* \"permission denied\" won't succeed if we try 256 times */\n            else if (ret == -EPERM)\n            {\n                break;\n            }\n        }\n        if (!dynamic_opened)\n        {\n            msg(M_FATAL, \"Cannot allocate DCO dev dynamically\");\n        }\n        /* tt->actual_name is passed to up and down scripts and used as\n         * the ifconfig dev name */\n        tt->actual_name = string_alloc(dynamic_name, NULL);\n    }\n    /*\n     * explicit unit number specified\n     */\n    else\n    {\n        int ret = open_tun_dco(tt, ctx, dev);\n        if (ret == -EEXIST)\n        {\n            msg(M_INFO, \"DCO device %s already exists, won't be destroyed at shutdown\", dev);\n            tt->persistent_if = true;\n        }\n        else if (ret < 0)\n        {\n            msg(M_ERR, \"Cannot open DCO device %s: %s (%d)\", dev, strerror(-ret), ret);\n        }\n        else\n        {\n            msg(M_INFO, \"DCO device %s opened\", dev);\n        }\n\n        /* tt->actual_name is passed to up and down scripts and used as the ifconfig dev name */\n        tt->actual_name = string_alloc(dev, NULL);\n    }\n}\n#endif /* TARGET_LINUX || TARGET_FREEBSD*/\n\n#if !(defined(_WIN32) || defined(TARGET_SOLARIS))\nstatic void\nclose_tun_generic(struct tuntap *tt)\n{\n    if (tt->fd >= 0)\n    {\n        close(tt->fd);\n    }\n\n    free(tt->actual_name);\n    clear_tuntap(tt);\n}\n#endif /* !_WIN32 */\n\n#if defined(TARGET_ANDROID)\nvoid\nopen_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt,\n         openvpn_net_ctx_t *ctx)\n{\n#define ANDROID_TUNNAME \"vpnservice-tun\"\n    struct gc_arena gc = gc_new();\n    bool opentun;\n\n    int oldtunfd = tt->fd;\n\n    /* Prefer IPv6 DNS servers,\n     * Android will use the DNS server in the order we specify*/\n    for (unsigned int i = 0; i < tt->options.dns6_len; i++)\n    {\n        management_android_control(management, \"DNS6SERVER\",\n                                   print_in6_addr(tt->options.dns6[i], 0, &gc));\n    }\n\n    for (unsigned int i = 0; i < tt->options.dns_len; i++)\n    {\n        management_android_control(management, \"DNSSERVER\",\n                                   print_in_addr_t(tt->options.dns[i], 0, &gc));\n    }\n\n    if (tt->options.domain)\n    {\n        management_android_control(management, \"DNSDOMAIN\", tt->options.domain);\n    }\n\n    if (tt->options.http_proxy)\n    {\n        struct buffer buf = alloc_buf_gc(strlen(tt->options.http_proxy) + 20, &gc);\n        buf_printf(&buf, \"%s %d\", tt->options.http_proxy, tt->options.http_proxy_port);\n        management_android_control(management, \"HTTPPROXY\", BSTR(&buf));\n    }\n\n    int android_method = managment_android_persisttun_action(management);\n\n    if (oldtunfd >= 0 && android_method == ANDROID_KEEP_OLD_TUN)\n    {\n        /* keep the old fd */\n        opentun = true;\n    }\n    else\n    {\n        opentun = management_android_control(management, \"OPENTUN\", dev);\n        /* Pick up the fd from management interface after calling the\n         * OPENTUN command */\n        tt->fd = management->connection.lastfdreceived;\n        management->connection.lastfdreceived = -1;\n    }\n\n    if (oldtunfd >= 0 && android_method == ANDROID_OPEN_BEFORE_CLOSE)\n    {\n        close(oldtunfd);\n    }\n\n    /* Set the actual name to a dummy name */\n    tt->actual_name = string_alloc(ANDROID_TUNNAME, NULL);\n\n    if ((tt->fd < 0) || !opentun)\n    {\n        msg(M_ERR, \"ERROR: Cannot open TUN\");\n    }\n\n    gc_free(&gc);\n}\n\nvoid\nclose_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    ASSERT(tt);\n\n    close_tun_generic(tt);\n    free(tt);\n}\n\nssize_t\nwrite_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    return write(tt->fd, buf, len);\n}\n\nssize_t\nread_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    return read(tt->fd, buf, len);\n}\n\n#elif defined(TARGET_LINUX)\n\n#if !PEDANTIC\n\nvoid\nopen_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt,\n         openvpn_net_ctx_t *ctx)\n{\n    struct ifreq ifr;\n\n    if (tun_dco_enabled(tt))\n    {\n        open_tun_dco_generic(dev, dev_type, tt, ctx);\n    }\n    else\n    {\n        /*\n         * Process --dev-node\n         */\n        const char *node = dev_node;\n        if (!node)\n        {\n            node = \"/dev/net/tun\";\n        }\n\n        /*\n         * Open the interface\n         */\n        if ((tt->fd = open(node, O_RDWR)) < 0)\n        {\n            msg(M_ERR, \"ERROR: Cannot open TUN/TAP dev %s\", node);\n        }\n\n        /*\n         * Process --tun-ipv6\n         */\n        CLEAR(ifr);\n        ifr.ifr_flags = IFF_NO_PI;\n\n#if defined(IFF_ONE_QUEUE) && defined(SIOCSIFTXQLEN)\n        ifr.ifr_flags |= IFF_ONE_QUEUE;\n#endif\n\n        /*\n         * Figure out if tun or tap device\n         */\n        if (tt->type == DEV_TYPE_TUN)\n        {\n            ifr.ifr_flags |= IFF_TUN;\n        }\n        else if (tt->type == DEV_TYPE_TAP)\n        {\n            ifr.ifr_flags |= IFF_TAP;\n        }\n        else\n        {\n            msg(M_FATAL, \"I don't recognize device %s as a tun or tap device\", dev);\n        }\n\n        /*\n         * Set an explicit name, if --dev is not tun or tap\n         */\n        if (strcmp(dev, \"tun\") && strcmp(dev, \"tap\"))\n        {\n            strncpynt(ifr.ifr_name, dev, IFNAMSIZ);\n        }\n\n        /*\n         * Use special ioctl that configures tun/tap device with the parms\n         * we set in ifr\n         */\n        if (ioctl(tt->fd, TUNSETIFF, (void *)&ifr) < 0)\n        {\n            msg(M_ERR, \"ERROR: Cannot ioctl TUNSETIFF %s\", dev);\n        }\n\n        msg(M_INFO, \"TUN/TAP device %s opened\", ifr.ifr_name);\n\n        /*\n         * Try making the TX send queue bigger\n         */\n#if defined(IFF_ONE_QUEUE) && defined(SIOCSIFTXQLEN)\n        if (tt->options.txqueuelen)\n        {\n            struct ifreq netifr;\n            int ctl_fd;\n\n            if ((ctl_fd = socket(AF_INET, SOCK_DGRAM, 0)) >= 0)\n            {\n                CLEAR(netifr);\n                strncpynt(netifr.ifr_name, ifr.ifr_name, IFNAMSIZ);\n                netifr.ifr_qlen = tt->options.txqueuelen;\n                if (ioctl(ctl_fd, SIOCSIFTXQLEN, (void *)&netifr) >= 0)\n                {\n                    msg(D_OSBUF, \"TUN/TAP TX queue length set to %d\", tt->options.txqueuelen);\n                }\n                else\n                {\n                    msg(M_WARN | M_ERRNO, \"Note: Cannot set tx queue length on %s\", ifr.ifr_name);\n                }\n                close(ctl_fd);\n            }\n            else\n            {\n                msg(M_WARN | M_ERRNO, \"Note: Cannot open control socket on %s\", ifr.ifr_name);\n            }\n        }\n#endif /* if defined(IFF_ONE_QUEUE) && defined(SIOCSIFTXQLEN) */\n\n        set_nonblock(tt->fd);\n        set_cloexec(tt->fd);\n        tt->actual_name = string_alloc(ifr.ifr_name, NULL);\n    }\n    return;\n}\n\n#else  /* if !PEDANTIC */\n\nvoid\nopen_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt,\n         openvpn_net_ctx_t *ctx)\n{\n    ASSERT(0);\n}\n\n#endif /* !PEDANTIC */\n\n#ifdef ENABLE_FEATURE_TUN_PERSIST\n\nvoid\ntuncfg(const char *dev, const char *dev_type, const char *dev_node, int persist_mode,\n       const char *username, const char *groupname, const struct tuntap_options *options,\n       openvpn_net_ctx_t *ctx)\n{\n    struct tuntap *tt;\n\n    ALLOC_OBJ(tt, struct tuntap);\n    clear_tuntap(tt);\n    tt->type = dev_type_enum(dev, dev_type);\n    tt->options = *options;\n\n    open_tun(dev, dev_type, dev_node, tt, ctx);\n    if (ioctl(tt->fd, TUNSETPERSIST, persist_mode) < 0)\n    {\n        msg(M_ERR, \"Cannot ioctl TUNSETPERSIST(%d) %s\", persist_mode, dev);\n    }\n    if (username != NULL)\n    {\n        struct platform_state_user platform_state_user;\n\n        if (!platform_user_get(username, &platform_state_user))\n        {\n            msg(M_ERR, \"Cannot get user entry for %s\", username);\n        }\n        else if (ioctl(tt->fd, TUNSETOWNER, platform_state_user.uid) < 0)\n        {\n            msg(M_ERR, \"Cannot ioctl TUNSETOWNER(%s) %s\", username, dev);\n        }\n    }\n    if (groupname != NULL)\n    {\n        struct platform_state_group platform_state_group;\n\n        if (!platform_group_get(groupname, &platform_state_group))\n        {\n            msg(M_ERR, \"Cannot get group entry for %s\", groupname);\n        }\n        else if (ioctl(tt->fd, TUNSETGROUP, platform_state_group.gid) < 0)\n        {\n            msg(M_ERR, \"Cannot ioctl TUNSETGROUP(%s) %s\", groupname, dev);\n        }\n    }\n    close_tun(tt, ctx);\n    msg(M_INFO, \"Persist state set to: %s\", (persist_mode ? \"ON\" : \"OFF\"));\n}\n\n#endif /* ENABLE_FEATURE_TUN_PERSIST */\n\nvoid\nclose_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    ASSERT(tt);\n\n#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)\n    if (tun_dco_enabled(tt))\n    {\n        close_tun_dco(tt, ctx);\n    }\n#endif\n    close_tun_generic(tt);\n    free(tt);\n}\n\nssize_t\nwrite_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    return write(tt->fd, buf, len);\n}\n\nssize_t\nread_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    return read(tt->fd, buf, len);\n}\n\n#elif defined(TARGET_SOLARIS)\n\n#ifndef TUNNEWPPA\n#error I need the symbol TUNNEWPPA from net/if_tun.h\n#endif\n\nvoid\nopen_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt,\n         openvpn_net_ctx_t *ctx)\n{\n    int if_fd = -1, ip_muxid = -1, arp_muxid = -1, arp_fd = -1, ppa = -1;\n    struct lifreq ifr;\n    const char *ptr;\n    const char *ip_node = NULL, *arp_node = NULL;\n    const char *dev_tuntap_type;\n    int link_type;\n    struct strioctl strioc_if, strioc_ppa;\n\n    /* improved generic TUN/TAP driver from\n     * https://web.archive.org/web/20250504214754/http://www.whiteboard.ne.jp/~admin2/tuntap/\n     * has IPv6 support\n     */\n    CLEAR(ifr);\n\n    if (tt->type == DEV_TYPE_TUN)\n    {\n        ip_node = \"/dev/udp\";\n        if (!dev_node)\n        {\n            dev_node = \"/dev/tun\";\n        }\n        dev_tuntap_type = \"tun\";\n        link_type = I_PLINK;\n    }\n    else if (tt->type == DEV_TYPE_TAP)\n    {\n        ip_node = \"/dev/udp\";\n        if (!dev_node)\n        {\n            dev_node = \"/dev/tap\";\n        }\n        arp_node = dev_node;\n        dev_tuntap_type = \"tap\";\n        link_type = I_PLINK; /* was: I_LINK */\n    }\n    else\n    {\n        msg(M_FATAL, \"I don't recognize device %s as a tun or tap device\", dev);\n    }\n\n    if ((tt->ip_fd = open(ip_node, O_RDWR, 0)) < 0)\n    {\n        msg(M_ERR, \"Can't open %s\", ip_node);\n    }\n\n    if ((tt->fd = open(dev_node, O_RDWR, 0)) < 0)\n    {\n        msg(M_ERR, \"Can't open %s\", dev_node);\n    }\n\n    ptr = dev;\n\n    /* get unit number */\n    if (*ptr)\n    {\n        while (*ptr && !isdigit((int)*ptr))\n        {\n            ptr++;\n        }\n        ppa = atoi(ptr);\n    }\n\n    /* Assign a new PPA and get its unit number. */\n    strioc_ppa.ic_cmd = TUNNEWPPA;\n    strioc_ppa.ic_timout = 0;\n    strioc_ppa.ic_len = sizeof(ppa);\n    strioc_ppa.ic_dp = (char *)&ppa;\n\n    if (*ptr == '\\0') /* no number given, try dynamic */\n    {\n        bool found_one = false;\n        while (!found_one && ppa < 64)\n        {\n            int new_ppa = ioctl(tt->fd, I_STR, &strioc_ppa);\n            if (new_ppa >= 0)\n            {\n                msg(M_INFO, \"open_tun: got dynamic interface '%s%d'\", dev_tuntap_type, new_ppa);\n                ppa = new_ppa;\n                found_one = true;\n                break;\n            }\n            if (errno != EEXIST)\n            {\n                msg(M_ERR, \"open_tun: unexpected error trying to find free %s interface\",\n                    dev_tuntap_type);\n            }\n            ppa++;\n        }\n        if (!found_one)\n        {\n            msg(M_ERR, \"open_tun: could not find free %s interface, give up.\", dev_tuntap_type);\n        }\n    }\n    else /* try this particular one */\n    {\n        if ((ppa = ioctl(tt->fd, I_STR, &strioc_ppa)) < 0)\n        {\n            msg(M_ERR, \"Can't assign PPA for new interface (%s%d)\", dev_tuntap_type, ppa);\n        }\n    }\n\n    if ((if_fd = open(dev_node, O_RDWR, 0)) < 0)\n    {\n        msg(M_ERR, \"Can't open %s (2)\", dev_node);\n    }\n\n    if (ioctl(if_fd, I_PUSH, \"ip\") < 0)\n    {\n        msg(M_ERR, \"Can't push IP module\");\n    }\n\n    if (tt->type == DEV_TYPE_TUN)\n    {\n        /* Assign ppa according to the unit number returned by tun device */\n        if (ioctl(if_fd, IF_UNITSEL, (char *)&ppa) < 0)\n        {\n            msg(M_ERR, \"Can't set PPA %d\", ppa);\n        }\n    }\n\n    tt->actual_name = (char *)malloc(32);\n    check_malloc_return(tt->actual_name);\n\n    snprintf(tt->actual_name, 32, \"%s%d\", dev_tuntap_type, ppa);\n\n    if (tt->type == DEV_TYPE_TAP)\n    {\n        if (ioctl(if_fd, SIOCGLIFFLAGS, &ifr) < 0)\n        {\n            msg(M_ERR, \"Can't get flags\");\n        }\n        strncpynt(ifr.lifr_name, tt->actual_name, sizeof(ifr.lifr_name));\n        ifr.lifr_ppa = ppa;\n        /* Assign ppa according to the unit number returned by tun device */\n        if (ioctl(if_fd, SIOCSLIFNAME, &ifr) < 0)\n        {\n            msg(M_ERR, \"Can't set PPA %d\", ppa);\n        }\n        if (ioctl(if_fd, SIOCGLIFFLAGS, &ifr) < 0)\n        {\n            msg(M_ERR, \"Can't get flags\");\n        }\n        /* Push arp module to if_fd */\n        if (ioctl(if_fd, I_PUSH, \"arp\") < 0)\n        {\n            msg(M_ERR, \"Can't push ARP module\");\n        }\n\n        /* Pop any modules on the stream */\n        while (true)\n        {\n            if (ioctl(tt->ip_fd, I_POP, NULL) < 0)\n            {\n                break;\n            }\n        }\n        /* Push arp module to ip_fd */\n        if (ioctl(tt->ip_fd, I_PUSH, \"arp\") < 0)\n        {\n            msg(M_ERR, \"Can't push ARP module\");\n        }\n\n        /* Open arp_fd */\n        if ((arp_fd = open(arp_node, O_RDWR, 0)) < 0)\n        {\n            msg(M_ERR, \"Can't open %s\", arp_node);\n        }\n        /* Push arp module to arp_fd */\n        if (ioctl(arp_fd, I_PUSH, \"arp\") < 0)\n        {\n            msg(M_ERR, \"Can't push ARP module\");\n        }\n\n        /* Set ifname to arp */\n        strioc_if.ic_cmd = SIOCSLIFNAME;\n        strioc_if.ic_timout = 0;\n        strioc_if.ic_len = sizeof(ifr);\n        strioc_if.ic_dp = (char *)&ifr;\n        if (ioctl(arp_fd, I_STR, &strioc_if) < 0)\n        {\n            msg(M_ERR, \"Can't set ifname to arp\");\n        }\n    }\n\n    if ((ip_muxid = ioctl(tt->ip_fd, link_type, if_fd)) < 0)\n    {\n        msg(M_ERR, \"Can't link %s device to IP\", dev_tuntap_type);\n    }\n\n    if (tt->type == DEV_TYPE_TAP)\n    {\n        if ((arp_muxid = ioctl(tt->ip_fd, link_type, arp_fd)) < 0)\n        {\n            msg(M_ERR, \"Can't link %s device to ARP\", dev_tuntap_type);\n        }\n        close(arp_fd);\n    }\n\n    CLEAR(ifr);\n    strncpynt(ifr.lifr_name, tt->actual_name, sizeof(ifr.lifr_name));\n    ifr.lifr_ip_muxid = ip_muxid;\n    if (tt->type == DEV_TYPE_TAP)\n    {\n        ifr.lifr_arp_muxid = arp_muxid;\n    }\n\n    if (ioctl(tt->ip_fd, SIOCSLIFMUXID, &ifr) < 0)\n    {\n        if (tt->type == DEV_TYPE_TAP)\n        {\n            ioctl(tt->ip_fd, I_PUNLINK, arp_muxid);\n        }\n        ioctl(tt->ip_fd, I_PUNLINK, ip_muxid);\n        msg(M_ERR, \"Can't set multiplexor id\");\n    }\n\n    set_nonblock(tt->fd);\n    set_cloexec(tt->fd);\n    set_cloexec(tt->ip_fd);\n\n    msg(M_INFO, \"TUN/TAP device %s opened\", tt->actual_name);\n}\n\nstatic void\nsolaris_close_tun(struct tuntap *tt)\n{\n    /* IPv6 interfaces need to be 'manually' de-configured */\n    if (tt->did_ifconfig_ipv6_setup)\n    {\n        struct argv argv = argv_new();\n        argv_printf(&argv, \"%s %s inet6 unplumb\", IFCONFIG_PATH, tt->actual_name);\n        argv_msg(M_INFO, &argv);\n        openvpn_execve_check(&argv, NULL, 0, \"Solaris ifconfig inet6 unplumb failed\");\n        argv_free(&argv);\n    }\n\n    if (tt->ip_fd >= 0)\n    {\n        struct lifreq ifr;\n        CLEAR(ifr);\n        strncpynt(ifr.lifr_name, tt->actual_name, sizeof(ifr.lifr_name));\n\n        if (ioctl(tt->ip_fd, SIOCGLIFFLAGS, &ifr) < 0)\n        {\n            msg(M_WARN | M_ERRNO, \"Can't get iface flags\");\n        }\n\n        if (ioctl(tt->ip_fd, SIOCGLIFMUXID, &ifr) < 0)\n        {\n            msg(M_WARN | M_ERRNO, \"Can't get multiplexor id\");\n        }\n\n        if (tt->type == DEV_TYPE_TAP)\n        {\n            if (ioctl(tt->ip_fd, I_PUNLINK, ifr.lifr_arp_muxid) < 0)\n            {\n                msg(M_WARN | M_ERRNO, \"Can't unlink interface(arp)\");\n            }\n        }\n\n        if (ioctl(tt->ip_fd, I_PUNLINK, ifr.lifr_ip_muxid) < 0)\n        {\n            msg(M_WARN | M_ERRNO, \"Can't unlink interface(ip)\");\n        }\n\n        close(tt->ip_fd);\n        tt->ip_fd = -1;\n    }\n\n    if (tt->fd >= 0)\n    {\n        close(tt->fd);\n        tt->fd = -1;\n    }\n}\n\n/*\n * Close TUN device.\n */\nvoid\nclose_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    ASSERT(tt);\n\n    solaris_close_tun(tt);\n\n    free(tt->actual_name);\n\n    clear_tuntap(tt);\n    free(tt);\n}\n\nstatic void\nsolaris_error_close(struct tuntap *tt, const struct env_set *es, const char *actual,\n                    bool unplumb_inet6)\n{\n    struct argv argv = argv_new();\n\n    if (unplumb_inet6)\n    {\n        argv_printf(&argv, \"%s %s inet6 unplumb\", IFCONFIG_PATH, actual);\n        argv_msg(M_INFO, &argv);\n        openvpn_execve_check(&argv, es, 0, \"Solaris ifconfig inet6 unplumb failed\");\n    }\n\n    argv_printf(&argv, \"%s %s unplumb\", IFCONFIG_PATH, actual);\n\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, es, 0, \"Solaris ifconfig unplumb failed\");\n    close_tun(tt, NULL);\n    msg(M_FATAL, \"Solaris ifconfig failed\");\n    argv_free(&argv);\n}\n\nssize_t\nwrite_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    struct strbuf sbuf;\n    sbuf.len = len;\n    sbuf.buf = (char *)buf;\n    return putmsg(tt->fd, NULL, &sbuf, 0) >= 0 ? sbuf.len : -1;\n}\n\nssize_t\nread_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    struct strbuf sbuf;\n    int f = 0;\n\n    sbuf.maxlen = len;\n    sbuf.buf = (char *)buf;\n    return getmsg(tt->fd, NULL, &sbuf, &f) >= 0 ? sbuf.len : -1;\n}\n\n#elif defined(TARGET_OPENBSD)\n\nvoid\nopen_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt,\n         openvpn_net_ctx_t *ctx)\n{\n    open_tun_generic(dev, dev_type, dev_node, tt);\n\n    /* Enable multicast on the interface */\n    if (tt->fd >= 0)\n    {\n        struct tuninfo info;\n\n        if (ioctl(tt->fd, TUNGIFINFO, &info) < 0)\n        {\n            msg(M_WARN | M_ERRNO, \"Can't get interface info\");\n        }\n\n#ifdef IFF_MULTICAST /* openbsd 4.x doesn't have this */\n        info.flags |= IFF_MULTICAST;\n#endif\n\n        if (ioctl(tt->fd, TUNSIFINFO, &info) < 0)\n        {\n            msg(M_WARN | M_ERRNO, \"Can't set interface info\");\n        }\n    }\n}\n\n/* tun(4): \"If the device was created by opening /dev/tunN, it will be\n *          automatically destroyed.  Devices created via ifconfig(8) are\n *          only marked as not running and traffic will be dropped\n *          returning EHOSTDOWN.\"\n * --> no special handling should be needed - *but* OpenBSD is misbehaving\n * here: if the interface was put in tap mode (\"ifconfig tunN link0\"), it\n * *will* stay around, and needs to be cleaned up manually\n */\n\nvoid\nclose_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    ASSERT(tt);\n\n    /* only *TAP* devices need destroying, tun devices auto-self-destruct\n     */\n    if (tt->type == DEV_TYPE_TUN || tt->persistent_if)\n    {\n        close_tun_generic(tt);\n        free(tt);\n        return;\n    }\n\n    struct argv argv = argv_new();\n\n    /* setup command, close tun dev (clears tt->actual_name!), run command\n     */\n\n    argv_printf(&argv, \"%s %s destroy\", IFCONFIG_PATH, tt->actual_name);\n\n    close_tun_generic(tt);\n\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, NULL, 0, \"OpenBSD 'destroy tun interface' failed (non-critical)\");\n\n    free(tt);\n    argv_free(&argv);\n}\n\n#elif defined(TARGET_NETBSD)\n\n/*\n * NetBSD 4.0 and up support IPv6 on tun interfaces, but we need to put\n * the tun interface into \"multi_af\" mode, which will prepend the address\n * family to all packets (same as OpenBSD and FreeBSD).\n *\n * If this is not enabled, the kernel silently drops all IPv6 packets on\n * output and gets confused on input.\n *\n * Note: --dev tap3 works *if* the interface is created externally by\n *         \"ifconfig tap3 create\"\n *         (and for devices beyond tap3, \"mknod /dev/tapN c ...\")\n *       but we do not have code to do that inside OpenVPN\n */\n\nvoid\nopen_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt,\n         openvpn_net_ctx_t *ctx)\n{\n    /* on NetBSD, tap (but not tun) devices are opened by\n     * opening /dev/tap and then querying the system about the\n     * actual device name (tap0, tap1, ...) assigned\n     */\n    if (strcmp(dev, \"tap\") == 0)\n    {\n        struct ifreq ifr;\n        if ((tt->fd = open(\"/dev/tap\", O_RDWR)) < 0)\n        {\n            msg(M_FATAL, \"Cannot allocate NetBSD TAP dev dynamically\");\n        }\n        if (ioctl(tt->fd, TAPGIFNAME, (void *)&ifr) < 0)\n        {\n            msg(M_FATAL, \"Cannot query NetBSD TAP device name\");\n        }\n        set_nonblock(tt->fd);\n        set_cloexec(tt->fd); /* don't pass fd to scripts */\n        msg(M_INFO, \"TUN/TAP device %s opened\", ifr.ifr_name);\n\n        tt->actual_name = string_alloc(ifr.ifr_name, NULL);\n    }\n    else\n    {\n        /* dynamic / named tun can be handled by the generic function\n         * named tap (\"tap3\") is handled there as well, if pre-created\n         */\n        open_tun_generic(dev, dev_type, dev_node, tt);\n    }\n\n    if (tt->fd >= 0)\n    {\n        int i = IFF_POINTOPOINT | IFF_MULTICAST;\n        ioctl(tt->fd, TUNSIFMODE, &i); /* multicast on */\n        i = 0;\n        ioctl(tt->fd, TUNSLMODE, &i);  /* link layer mode off */\n\n        if (tt->type == DEV_TYPE_TUN)\n        {\n            i = 1;\n            if (ioctl(tt->fd, TUNSIFHEAD, &i) < 0) /* multi-af mode on */\n            {\n                msg(M_WARN | M_ERRNO, \"ioctl(TUNSIFHEAD)\");\n            }\n        }\n    }\n}\n\n/* the current way OpenVPN handles tun devices on NetBSD leads to\n * lingering tunX interfaces after close -> for a full cleanup, they\n * need to be explicitly destroyed\n */\nvoid\nclose_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    ASSERT(tt);\n\n    /* only tun devices need destroying, tap devices auto-self-destruct\n     */\n    if (tt->type != DEV_TYPE_TUN || tt->persistent_if)\n    {\n        close_tun_generic(tt);\n        free(tt);\n        return;\n    }\n\n    struct argv argv = argv_new();\n\n    /* setup command, close tun dev (clears tt->actual_name!), run command\n     */\n\n    argv_printf(&argv, \"%s %s destroy\", IFCONFIG_PATH, tt->actual_name);\n\n    close_tun_generic(tt);\n\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, NULL, 0, \"NetBSD 'destroy tun interface' failed (non-critical)\");\n\n    free(tt);\n    argv_free(&argv);\n}\n\n#elif defined(TARGET_FREEBSD)\n\nvoid\nopen_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt,\n         openvpn_net_ctx_t *ctx)\n{\n    if (tun_dco_enabled(tt))\n    {\n        open_tun_dco_generic(dev, dev_type, tt, ctx);\n    }\n    else\n    {\n        open_tun_generic(dev, dev_type, dev_node, tt);\n\n        if (tt->fd >= 0 && tt->type == DEV_TYPE_TUN)\n        {\n            /* see \"Interface Flags\" in ifnet(9) */\n            int i = IFF_POINTOPOINT | IFF_MULTICAST;\n            if (tt->topology == TOP_SUBNET)\n            {\n                i = IFF_BROADCAST | IFF_MULTICAST;\n            }\n\n            if (ioctl(tt->fd, TUNSIFMODE, &i) < 0)\n            {\n                msg(M_WARN | M_ERRNO, \"ioctl(TUNSIFMODE)\");\n            }\n\n            /* multi_af mode for v4+v6, see \"tun(4)\" */\n            i = 1;\n            if (ioctl(tt->fd, TUNSIFHEAD, &i) < 0)\n            {\n                msg(M_WARN | M_ERRNO, \"ioctl(TUNSIFHEAD)\");\n            }\n        }\n    }\n}\n\n/* tun(4): \"These network interfaces persist until the if_tun.ko module is\n *          unloaded, or until removed with the ifconfig(8) command.\"\n *          (verified for FreeBSD 6.3, 7.4, 8.2 and 9, same for tap(4))\n *\n * so, to avoid lingering tun/tap interfaces after OpenVPN quits,\n * we need to call \"ifconfig ... destroy\" for cleanup\n */\nvoid\nclose_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    ASSERT(tt);\n\n    if (tt->persistent_if) /* keep pre-existing if around */\n    {\n        close_tun_generic(tt);\n        free(tt);\n        return;\n    }\n\n    /* close and destroy */\n    struct argv argv = argv_new();\n\n    /* setup command, close tun dev (clears tt->actual_name!), run command\n     */\n\n    argv_printf(&argv, \"%s %s destroy\", IFCONFIG_PATH, tt->actual_name);\n\n    close_tun_generic(tt);\n\n    argv_msg(M_INFO, &argv);\n    openvpn_execve_check(&argv, NULL, 0, \"FreeBSD 'destroy tun interface' failed (non-critical)\");\n\n    free(tt);\n    argv_free(&argv);\n}\n\n#elif defined(TARGET_DRAGONFLY)\n\nvoid\nopen_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt,\n         openvpn_net_ctx_t *ctx)\n{\n    open_tun_generic(dev, dev_type, dev_node, tt);\n\n    if (tt->fd >= 0)\n    {\n        int i = 0;\n\n        /* Disable extended modes */\n        ioctl(tt->fd, TUNSLMODE, &i);\n        i = 1;\n        ioctl(tt->fd, TUNSIFHEAD, &i);\n    }\n}\n\nvoid\nclose_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    ASSERT(tt);\n\n    close_tun_generic(tt);\n    free(tt);\n}\n\n#elif defined(TARGET_DARWIN)\n\n/* Darwin (MacOS X) is mostly \"just use the generic stuff\", but there\n * is always one caveat...:\n *\n * If IPv6 is configured, and the tun device is closed, the IPv6 address\n * configured to the tun interface changes to a lingering /128 route\n * pointing to lo0.  Need to unconfigure...  (observed on 10.5)\n */\n\n/*\n * utun is the native Darwin tun driver present since at least 10.7\n * Thanks goes to Jonathan Levin for providing an example how to utun\n * (https://www.cs.dartmouth.edu/~sergey/netreads/utun/utun-demo.c)\n */\n\n/* Helper functions that tries to open utun device\n * return -2 on early initialization failures (utun not supported\n * at all) and -1 on initlization failure of utun\n * device (utun works but utunX is already used)\n */\nstatic int\nutun_open_helper(struct ctl_info ctlInfo, int utunnum)\n{\n    struct sockaddr_ctl sc;\n    int fd;\n\n    fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);\n\n    if (fd < 0)\n    {\n        msg(M_INFO | M_ERRNO, \"Opening utun%d failed (socket(SYSPROTO_CONTROL))\", utunnum);\n        return -2;\n    }\n\n    if (ioctl(fd, CTLIOCGINFO, &ctlInfo) == -1)\n    {\n        close(fd);\n        msg(M_INFO | M_ERRNO, \"Opening utun%d failed (ioctl(CTLIOCGINFO))\", utunnum);\n        return -2;\n    }\n\n\n    sc.sc_id = ctlInfo.ctl_id;\n    sc.sc_len = sizeof(sc);\n    sc.sc_family = AF_SYSTEM;\n    sc.ss_sysaddr = AF_SYS_CONTROL;\n\n    sc.sc_unit = utunnum + 1;\n\n\n    /* If the connect is successful, a utun%d device will be created, where \"%d\"\n     * is (sc.sc_unit - 1) */\n\n    if (connect(fd, (struct sockaddr *)&sc, sizeof(sc)) < 0)\n    {\n        msg(M_INFO | M_ERRNO, \"Opening utun%d failed (connect(AF_SYS_CONTROL))\", utunnum);\n        close(fd);\n        return -1;\n    }\n\n    set_nonblock(fd);\n    set_cloexec(fd); /* don't pass fd to scripts */\n\n    return fd;\n}\n\nvoid\nopen_darwin_utun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt)\n{\n    struct ctl_info ctlInfo;\n    int fd;\n    char utunname[20];\n    int utunnum = -1;\n    socklen_t utunname_len = sizeof(utunname);\n\n    /* dev_node is simply utun, do the normal dynamic utun\n     * otherwise try to parse the utun number */\n    if (dev_node && (strcmp(\"utun\", dev_node) != 0))\n    {\n        if (sscanf(dev_node, \"utun%d\", &utunnum) != 1)\n        {\n            msg(M_FATAL,\n                \"Cannot parse 'dev-node %s' please use 'dev-node utunX'\"\n                \"to use a utun device number X\",\n                dev_node);\n        }\n    }\n\n\n    CLEAR(ctlInfo);\n    if (strlcpy(ctlInfo.ctl_name, UTUN_CONTROL_NAME, sizeof(ctlInfo.ctl_name))\n        >= sizeof(ctlInfo.ctl_name))\n    {\n        msg(M_ERR, \"Opening utun: UTUN_CONTROL_NAME too long\");\n    }\n\n    /* try to open first available utun device if no specific utun is requested */\n    if (utunnum == -1)\n    {\n        for (utunnum = 0; utunnum < 255; utunnum++)\n        {\n            char ifname[20];\n            /* if the interface exists silently skip it */\n            ASSERT(snprintf(ifname, sizeof(ifname), \"utun%d\", utunnum) > 0);\n            if (if_nametoindex(ifname))\n            {\n                continue;\n            }\n            fd = utun_open_helper(ctlInfo, utunnum);\n            /* Break if the fd is valid,\n             * or if early initialization failed (-2) */\n            if (fd != -1)\n            {\n                break;\n            }\n        }\n    }\n    else\n    {\n        fd = utun_open_helper(ctlInfo, utunnum);\n    }\n\n    /* opening an utun device failed */\n    tt->fd = fd;\n\n    if (fd < 0)\n    {\n        return;\n    }\n\n    /* Retrieve the assigned interface name. */\n    if (getsockopt(fd, SYSPROTO_CONTROL, UTUN_OPT_IFNAME, utunname, &utunname_len))\n    {\n        msg(M_ERR, \"Error retrieving utun interface name\");\n    }\n\n    tt->actual_name = string_alloc(utunname, NULL);\n\n    msg(M_INFO, \"Opened utun device %s\", utunname);\n    tt->backend_driver = DRIVER_UTUN;\n}\n\nvoid\nopen_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt,\n         openvpn_net_ctx_t *ctx)\n{\n    /* If dev_node does not start start with utun assume regular tun/tap */\n    if ((!dev_node && tt->type == DEV_TYPE_TUN) || (dev_node && !strncmp(dev_node, \"utun\", 4)))\n    {\n        /* Check if user has specific dev_type tap and forced utun with\n         * dev-node utun */\n        if (tt->type != DEV_TYPE_TUN)\n        {\n            msg(M_FATAL, \"Cannot use utun devices with --dev-type %s\",\n                dev_type_string(dev, dev_type));\n        }\n\n        /* Try utun first and fall back to normal tun if utun fails\n         * and dev_node is not specified */\n        open_darwin_utun(dev, dev_type, dev_node, tt);\n\n        if (tt->backend_driver != DRIVER_UTUN)\n        {\n            if (!dev_node)\n            {\n                /* No explicit utun and utun failed, try the generic way) */\n                msg(M_INFO, \"Failed to open utun device. Falling back to /dev/tun device\");\n                open_tun_generic(dev, dev_type, NULL, tt);\n            }\n            else\n            {\n                /* Specific utun device or generic utun request with no tun\n                 * fall back failed, consider this a fatal failure */\n                msg(M_FATAL, \"Cannot open utun device\");\n            }\n        }\n    }\n    else\n    {\n        /* Use plain dev-node tun to select /dev/tun style\n         * Unset dev_node variable prior to passing to open_tun_generic to\n         * let open_tun_generic pick the first available tun device */\n\n        if (dev_node && strcmp(dev_node, \"tun\") == 0)\n        {\n            dev_node = NULL;\n        }\n\n        open_tun_generic(dev, dev_type, dev_node, tt);\n    }\n}\n\nvoid\nclose_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    ASSERT(tt);\n\n    struct gc_arena gc = gc_new();\n    struct argv argv = argv_new();\n\n    if (tt->did_ifconfig_ipv6_setup)\n    {\n        const char *ifconfig_ipv6_local = print_in6_addr(tt->local_ipv6, 0, &gc);\n\n        argv_printf(&argv, \"%s delete -inet6 %s\", ROUTE_PATH, ifconfig_ipv6_local);\n        argv_msg(M_INFO, &argv);\n        openvpn_execve_check(&argv, NULL, 0, \"MacOS X 'remove inet6 route' failed (non-critical)\");\n    }\n\n    close_tun_generic(tt);\n    free(tt);\n    argv_free(&argv);\n    gc_free(&gc);\n}\n\nssize_t\nwrite_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    if (tt->backend_driver == DRIVER_UTUN)\n    {\n        return write_tun_header(tt, buf, len);\n    }\n    else\n    {\n        return write(tt->fd, buf, len);\n    }\n}\n\nssize_t\nread_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    if (tt->backend_driver == DRIVER_UTUN)\n    {\n        return read_tun_header(tt, buf, len);\n    }\n    else\n    {\n        return read(tt->fd, buf, len);\n    }\n}\n\n#elif defined(TARGET_AIX)\n\nvoid\nopen_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt,\n         openvpn_net_ctx_t *ctx)\n{\n    char tunname[256];\n    char dynamic_name[20];\n    const char *p;\n\n    if (tt->type == DEV_TYPE_TUN)\n    {\n        msg(M_FATAL, \"no support for 'tun' devices on AIX\");\n    }\n\n    if (strncmp(dev, \"tap\", 3) != 0 || dev_node)\n    {\n        msg(M_FATAL,\n            \"'--dev %s' and/or '--dev-node' not supported on AIX, use '--dev tap0', 'tap1', etc.\",\n            dev);\n    }\n\n    if (strcmp(dev, \"tap\") == 0) /* find first free tap dev */\n    {                            /* (= no /dev/tapN node) */\n        int i;\n        for (i = 0; i < 99; i++)\n        {\n            snprintf(tunname, sizeof(tunname), \"/dev/tap%d\", i);\n            if (access(tunname, F_OK) < 0 && errno == ENOENT)\n            {\n                break;\n            }\n        }\n        if (i >= 99)\n        {\n            msg(M_FATAL, \"cannot find unused tap device\");\n        }\n\n        snprintf(dynamic_name, sizeof(dynamic_name), \"tap%d\", i);\n        dev = dynamic_name;\n    }\n    else /* name given, sanity check */\n    {\n        /* ensure that dev name is \"tap+<digits>\" *only* */\n        p = &dev[3];\n        while (isdigit(*p))\n        {\n            p++;\n        }\n        if (*p != '\\0')\n        {\n            msg(M_FATAL, \"TAP device name must be '--dev tapNNNN'\");\n        }\n\n        snprintf(tunname, sizeof(tunname), \"/dev/%s\", dev);\n    }\n\n    /* pre-existing device?\n     */\n    if (access(tunname, F_OK) < 0 && errno == ENOENT)\n    {\n        /* tunnel device must be created with 'ifconfig tapN create'\n         */\n        struct argv argv = argv_new();\n        struct env_set *es = env_set_create(NULL);\n        argv_printf(&argv, \"%s %s create\", IFCONFIG_PATH, dev);\n        argv_msg(M_INFO, &argv);\n        env_set_add(es, \"ODMDIR=/etc/objrepos\");\n        openvpn_execve_check(&argv, es, S_FATAL, \"AIX 'create tun interface' failed\");\n        env_set_destroy(es);\n        argv_free(&argv);\n    }\n    else\n    {\n        /* we didn't make it, we're not going to break it */\n        tt->persistent_if = TRUE;\n    }\n\n    if ((tt->fd = open(tunname, O_RDWR)) < 0)\n    {\n        msg(M_ERR, \"Cannot open TAP device '%s'\", tunname);\n    }\n\n    set_nonblock(tt->fd);\n    set_cloexec(tt->fd); /* don't pass fd to scripts */\n    msg(M_INFO, \"TUN/TAP device %s opened\", tunname);\n\n    /* tt->actual_name is passed to up and down scripts and used as the ifconfig dev name */\n    tt->actual_name = string_alloc(dev, NULL);\n}\n\n/* tap devices need to be manually destroyed on AIX\n */\nvoid\nclose_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    ASSERT(tt);\n\n    struct argv argv = argv_new();\n    struct env_set *es = env_set_create(NULL);\n\n    /* persistent devices need IP address unconfig, others need destroyal\n     */\n    if (tt->persistent_if)\n    {\n        argv_printf(&argv, \"%s %s 0.0.0.0 down\", IFCONFIG_PATH, tt->actual_name);\n    }\n    else\n    {\n        argv_printf(&argv, \"%s %s destroy\", IFCONFIG_PATH, tt->actual_name);\n    }\n\n    close_tun_generic(tt);\n    argv_msg(M_INFO, &argv);\n    env_set_add(es, \"ODMDIR=/etc/objrepos\");\n    openvpn_execve_check(&argv, es, 0, \"AIX 'destroy tap interface' failed (non-critical)\");\n\n    free(tt);\n    env_set_destroy(es);\n    argv_free(&argv);\n}\n\nssize_t\nwrite_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    return write(tt->fd, buf, len);\n}\n\nssize_t\nread_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    return read(tt->fd, buf, len);\n}\n\n#elif defined(_WIN32)\n\nint\ntun_read_queue(struct tuntap *tt, int maxsize)\n{\n    if (tt->reads.iostate == IOSTATE_INITIAL)\n    {\n        BOOL status;\n        int err;\n\n        /* reset buf to its initial state */\n        tt->reads.buf = tt->reads.buf_init;\n\n        int len = maxsize ? maxsize : BLEN(&tt->reads.buf);\n        ASSERT(len <= BLEN(&tt->reads.buf));\n\n        /* the overlapped read will signal this event on I/O completion */\n        ASSERT(ResetEvent(tt->reads.overlapped.hEvent));\n\n        status =\n            ReadFile(tt->hand, BPTR(&tt->reads.buf), len, &tt->reads.size, &tt->reads.overlapped);\n\n        if (status) /* operation completed immediately? */\n        {\n            /* since we got an immediate return, we must signal the event object ourselves */\n            ASSERT(SetEvent(tt->reads.overlapped.hEvent));\n\n            tt->reads.iostate = IOSTATE_IMMEDIATE_RETURN;\n            tt->reads.status = 0;\n\n            dmsg(D_WIN32_IO, \"WIN32 I/O: TAP Read immediate return [%d,%lu]\", len,\n                 tt->reads.size);\n        }\n        else\n        {\n            err = GetLastError();\n            if (err == ERROR_IO_PENDING) /* operation queued? */\n            {\n                tt->reads.iostate = IOSTATE_QUEUED;\n                tt->reads.status = err;\n                dmsg(D_WIN32_IO, \"WIN32 I/O: TAP Read queued [%d]\", len);\n            }\n            else /* error occurred */\n            {\n                struct gc_arena gc = gc_new();\n                ASSERT(SetEvent(tt->reads.overlapped.hEvent));\n                tt->reads.iostate = IOSTATE_IMMEDIATE_RETURN;\n                tt->reads.status = err;\n                dmsg(D_WIN32_IO, \"WIN32 I/O: TAP Read error [%d] : %s\", len,\n                     strerror_win32(status, &gc));\n                gc_free(&gc);\n            }\n        }\n    }\n    return tt->reads.iostate;\n}\n\nint\ntun_write_queue(struct tuntap *tt, struct buffer *buf)\n{\n    if (tt->writes.iostate == IOSTATE_INITIAL)\n    {\n        BOOL status;\n        int err;\n\n        /* make a private copy of buf */\n        tt->writes.buf = tt->writes.buf_init;\n        tt->writes.buf.len = 0;\n        ASSERT(buf_copy(&tt->writes.buf, buf));\n\n        /* the overlapped write will signal this event on I/O completion */\n        ASSERT(ResetEvent(tt->writes.overlapped.hEvent));\n\n        status = WriteFile(tt->hand, BPTR(&tt->writes.buf), BLEN(&tt->writes.buf), &tt->writes.size,\n                           &tt->writes.overlapped);\n\n        if (status) /* operation completed immediately? */\n        {\n            tt->writes.iostate = IOSTATE_IMMEDIATE_RETURN;\n\n            /* since we got an immediate return, we must signal the event object ourselves */\n            ASSERT(SetEvent(tt->writes.overlapped.hEvent));\n\n            tt->writes.status = 0;\n\n            dmsg(D_WIN32_IO, \"WIN32 I/O: TAP Write immediate return [%d,%lu]\", BLEN(&tt->writes.buf),\n                 tt->writes.size);\n        }\n        else\n        {\n            err = GetLastError();\n            if (err == ERROR_IO_PENDING) /* operation queued? */\n            {\n                tt->writes.iostate = IOSTATE_QUEUED;\n                tt->writes.status = err;\n                dmsg(D_WIN32_IO, \"WIN32 I/O: TAP Write queued [%d]\", BLEN(&tt->writes.buf));\n            }\n            else /* error occurred */\n            {\n                struct gc_arena gc = gc_new();\n                ASSERT(SetEvent(tt->writes.overlapped.hEvent));\n                tt->writes.iostate = IOSTATE_IMMEDIATE_RETURN;\n                tt->writes.status = err;\n                dmsg(D_WIN32_IO, \"WIN32 I/O: TAP Write error [%d] : %s\", BLEN(&tt->writes.buf),\n                     strerror_win32(err, &gc));\n                gc_free(&gc);\n            }\n        }\n    }\n    return tt->writes.iostate;\n}\n\nint\ntun_write_win32(struct tuntap *tt, struct buffer *buf)\n{\n    int err = 0;\n    int status = 0;\n    if (overlapped_io_active(&tt->writes))\n    {\n        sockethandle_t sh = { .is_handle = true, .h = tt->hand };\n        status = sockethandle_finalize(sh, &tt->writes, NULL, NULL);\n        if (status < 0)\n        {\n            err = GetLastError();\n        }\n    }\n    tun_write_queue(tt, buf);\n    if (status < 0)\n    {\n        SetLastError(err);\n        return status;\n    }\n    else\n    {\n        return BLEN(buf);\n    }\n}\n\nstatic const struct device_instance_id_interface *\nget_device_instance_id_interface(struct gc_arena *gc)\n{\n    HDEVINFO dev_info_set;\n    DWORD err;\n    struct device_instance_id_interface *first = NULL;\n    struct device_instance_id_interface *last = NULL;\n\n    dev_info_set =\n        SetupDiGetClassDevsEx(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);\n    if (dev_info_set == INVALID_HANDLE_VALUE)\n    {\n        err = GetLastError();\n        msg(M_FATAL, \"Error [%lu] opening device information set key: %s\", err,\n            strerror_win32(err, gc));\n    }\n\n    msg(D_TAP_WIN_DEBUG, \"Enumerate device interface lists:\");\n    for (DWORD i = 0;; ++i)\n    {\n        SP_DEVINFO_DATA device_info_data;\n        BOOL res;\n        HKEY dev_key;\n        char net_cfg_instance_id_string[] = \"NetCfgInstanceId\";\n        BYTE net_cfg_instance_id[256];\n        char device_instance_id[256];\n        DWORD len;\n        DWORD data_type;\n        LONG status;\n        ULONG dev_interface_list_size;\n        CONFIGRET cr;\n\n        ZeroMemory(&device_info_data, sizeof(SP_DEVINFO_DATA));\n        device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);\n        res = SetupDiEnumDeviceInfo(dev_info_set, i, &device_info_data);\n        if (!res)\n        {\n            if (GetLastError() == ERROR_NO_MORE_ITEMS)\n            {\n                break;\n            }\n            else\n            {\n                continue;\n            }\n        }\n\n        dev_key = SetupDiOpenDevRegKey(dev_info_set, &device_info_data, DICS_FLAG_GLOBAL, 0,\n                                       DIREG_DRV, KEY_QUERY_VALUE);\n        if (dev_key == INVALID_HANDLE_VALUE)\n        {\n            continue;\n        }\n\n        len = sizeof(net_cfg_instance_id);\n        data_type = REG_SZ;\n        status = RegQueryValueEx(dev_key, net_cfg_instance_id_string, NULL, &data_type,\n                                 net_cfg_instance_id, &len);\n        if (status != ERROR_SUCCESS)\n        {\n            goto next;\n        }\n\n        len = sizeof(device_instance_id);\n        res = SetupDiGetDeviceInstanceId(dev_info_set, &device_info_data, device_instance_id, len,\n                                         &len);\n        if (!res)\n        {\n            goto next;\n        }\n\n        cr = CM_Get_Device_Interface_List_Size(&dev_interface_list_size,\n                                               (LPGUID)&GUID_DEVINTERFACE_NET, device_instance_id,\n                                               CM_GET_DEVICE_INTERFACE_LIST_PRESENT);\n\n        if (cr != CR_SUCCESS)\n        {\n            goto next;\n        }\n\n        char *dev_interface_list = gc_malloc(dev_interface_list_size, false, gc);\n        cr = CM_Get_Device_Interface_List((LPGUID)&GUID_DEVINTERFACE_NET, device_instance_id,\n                                          dev_interface_list, dev_interface_list_size,\n                                          CM_GET_DEVICE_INTERFACE_LIST_PRESENT);\n        if (cr != CR_SUCCESS)\n        {\n            goto next;\n        }\n\n        char *dev_if = dev_interface_list;\n\n        /* device interface list ends with empty string */\n        while (strlen(dev_if) > 0)\n        {\n            struct device_instance_id_interface *dev_iif;\n            ALLOC_OBJ_CLEAR_GC(dev_iif, struct device_instance_id_interface, gc);\n            dev_iif->net_cfg_instance_id =\n                (unsigned char *)string_alloc((char *)net_cfg_instance_id, gc);\n            dev_iif->device_interface = string_alloc(dev_if, gc);\n\n            msg(D_TAP_WIN_DEBUG, \"NetCfgInstanceId: %s, Device Interface: %s\",\n                dev_iif->net_cfg_instance_id, dev_iif->device_interface);\n\n            /* link into return list */\n            if (!first)\n            {\n                first = dev_iif;\n            }\n            if (last)\n            {\n                last->next = dev_iif;\n            }\n            last = dev_iif;\n\n            dev_if += strlen(dev_if) + 1;\n        }\n\nnext:\n        RegCloseKey(dev_key);\n    }\n\n    SetupDiDestroyDeviceInfoList(dev_info_set);\n\n    return first;\n}\n\nstatic const struct tap_reg *\nget_tap_reg(struct gc_arena *gc)\n{\n    HKEY adapter_key;\n    LONG status;\n    DWORD len;\n    struct tap_reg *first = NULL;\n    struct tap_reg *last = NULL;\n    int i = 0;\n\n    status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, ADAPTER_KEY, 0, KEY_READ, &adapter_key);\n\n    if (status != ERROR_SUCCESS)\n    {\n        msg(M_FATAL, \"Error opening registry key: %s\", ADAPTER_KEY);\n    }\n\n    msg(D_TAP_WIN_DEBUG, \"Enumerate drivers in registy: \");\n    while (true)\n    {\n        char enum_name[256];\n        char unit_string[256];\n        HKEY unit_key;\n        char component_id_string[] = \"ComponentId\";\n        char component_id[256];\n        char net_cfg_instance_id_string[] = \"NetCfgInstanceId\";\n        BYTE net_cfg_instance_id[256];\n        DWORD data_type;\n\n        len = sizeof(enum_name);\n        status = RegEnumKeyEx(adapter_key, i, enum_name, &len, NULL, NULL, NULL, NULL);\n        if (status == ERROR_NO_MORE_ITEMS)\n        {\n            break;\n        }\n        else if (status != ERROR_SUCCESS)\n        {\n            msg(M_FATAL, \"Error enumerating registry subkeys of key: %s\", ADAPTER_KEY);\n        }\n\n        if (!checked_snprintf(unit_string, sizeof(unit_string), \"%s\\\\%s\", ADAPTER_KEY, enum_name))\n        {\n            msg(M_WARN, \"Error constructing unit string for %s\", enum_name);\n            continue;\n        }\n\n        status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, unit_string, 0, KEY_READ, &unit_key);\n\n        if (status != ERROR_SUCCESS)\n        {\n            dmsg(D_REGISTRY, \"Error opening registry key: %s\", unit_string);\n        }\n        else\n        {\n            len = sizeof(component_id);\n            status = RegQueryValueEx(unit_key, component_id_string, NULL, &data_type,\n                                     (LPBYTE)component_id, &len);\n\n            if (status != ERROR_SUCCESS || data_type != REG_SZ)\n            {\n                dmsg(D_REGISTRY, \"Error opening registry key: %s\\\\%s\", unit_string,\n                     component_id_string);\n            }\n            else\n            {\n                len = sizeof(net_cfg_instance_id);\n                status = RegQueryValueEx(unit_key, net_cfg_instance_id_string, NULL, &data_type,\n                                         net_cfg_instance_id, &len);\n\n                if (status == ERROR_SUCCESS && data_type == REG_SZ)\n                {\n                    /* Is this adapter supported? */\n                    enum tun_driver_type windows_driver = WINDOWS_DRIVER_UNSPECIFIED;\n                    if (strcasecmp(component_id, TAP_WIN_COMPONENT_ID) == 0\n                        || strcasecmp(component_id, \"root\\\\\" TAP_WIN_COMPONENT_ID) == 0)\n                    {\n                        windows_driver = WINDOWS_DRIVER_TAP_WINDOWS6;\n                    }\n                    else if (strcasecmp(component_id, \"ovpn-dco\") == 0)\n                    {\n                        windows_driver = DRIVER_DCO;\n                    }\n\n                    if (windows_driver != WINDOWS_DRIVER_UNSPECIFIED)\n                    {\n                        struct tap_reg *reg;\n                        ALLOC_OBJ_CLEAR_GC(reg, struct tap_reg, gc);\n                        reg->guid = string_alloc((char *)net_cfg_instance_id, gc);\n                        reg->windows_driver = windows_driver;\n\n                        /* link into return list */\n                        if (!first)\n                        {\n                            first = reg;\n                        }\n                        if (last)\n                        {\n                            last->next = reg;\n                        }\n                        last = reg;\n\n                        msg(D_TAP_WIN_DEBUG, \"NetCfgInstanceId: %s, Driver: %s\", reg->guid,\n                            print_tun_backend_driver(reg->windows_driver));\n                    }\n                }\n            }\n            RegCloseKey(unit_key);\n        }\n        ++i;\n    }\n\n    RegCloseKey(adapter_key);\n    return first;\n}\n\nstatic const struct panel_reg *\nget_panel_reg(struct gc_arena *gc)\n{\n    LONG status;\n    HKEY network_connections_key;\n    DWORD len;\n    struct panel_reg *first = NULL;\n    struct panel_reg *last = NULL;\n    int i = 0;\n\n    status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ,\n                          &network_connections_key);\n\n    if (status != ERROR_SUCCESS)\n    {\n        msg(M_FATAL, \"Error opening registry key: %s\", NETWORK_CONNECTIONS_KEY);\n    }\n\n    while (true)\n    {\n        char enum_name[256];\n        char connection_string[256];\n        HKEY connection_key;\n        WCHAR name_data[256];\n        DWORD name_type;\n        const WCHAR name_string[] = L\"Name\";\n\n        len = sizeof(enum_name);\n        status = RegEnumKeyEx(network_connections_key, i, enum_name, &len, NULL, NULL, NULL, NULL);\n        if (status == ERROR_NO_MORE_ITEMS)\n        {\n            break;\n        }\n        else if (status != ERROR_SUCCESS)\n        {\n            msg(M_FATAL, \"Error enumerating registry subkeys of key: %s\", NETWORK_CONNECTIONS_KEY);\n        }\n\n        if (!checked_snprintf(connection_string, sizeof(connection_string),\n                              \"%s\\\\%s\\\\Connection\",\n                              NETWORK_CONNECTIONS_KEY, enum_name))\n        {\n            msg(M_WARN, \"Error constructing connection string for %s\", enum_name);\n            continue;\n        }\n\n\n        status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, connection_string, 0, KEY_READ, &connection_key);\n\n        if (status != ERROR_SUCCESS)\n        {\n            dmsg(D_REGISTRY, \"Error opening registry key: %s\", connection_string);\n        }\n        else\n        {\n            len = sizeof(name_data);\n            status = RegQueryValueExW(connection_key, name_string, NULL, &name_type,\n                                      (LPBYTE)name_data, &len);\n\n            if (status != ERROR_SUCCESS || name_type != REG_SZ)\n            {\n                dmsg(D_REGISTRY, \"Error opening registry key: %s\\\\%s\\\\%ls\", NETWORK_CONNECTIONS_KEY,\n                     connection_string, name_string);\n            }\n            else\n            {\n                int n;\n                LPSTR name;\n                struct panel_reg *reg;\n\n                ALLOC_OBJ_CLEAR_GC(reg, struct panel_reg, gc);\n                n = WideCharToMultiByte(CP_UTF8, 0, name_data, -1, NULL, 0, NULL, NULL);\n                name = gc_malloc(n, false, gc);\n                WideCharToMultiByte(CP_UTF8, 0, name_data, -1, name, n, NULL, NULL);\n                reg->name = name;\n                reg->guid = string_alloc(enum_name, gc);\n\n                /* link into return list */\n                if (!first)\n                {\n                    first = reg;\n                }\n                if (last)\n                {\n                    last->next = reg;\n                }\n                last = reg;\n            }\n            RegCloseKey(connection_key);\n        }\n        ++i;\n    }\n\n    RegCloseKey(network_connections_key);\n\n    return first;\n}\n\n/*\n * Check that two addresses are part of the same 255.255.255.252 subnet.\n */\nvoid\nverify_255_255_255_252(in_addr_t local, in_addr_t remote)\n{\n    struct gc_arena gc = gc_new();\n    const unsigned int mask = 3;\n    const char *err = NULL;\n\n    if (local == remote)\n    {\n        err = \"must be different\";\n        goto error;\n    }\n    if ((local & (~mask)) != (remote & (~mask)))\n    {\n        err =\n            \"must exist within the same 255.255.255.252 subnet.  This is a limitation of --dev tun when used with the TAP-WIN32 driver\";\n        goto error;\n    }\n    if ((local & mask) == 0 || (local & mask) == 3 || (remote & mask) == 0 || (remote & mask) == 3)\n    {\n        err =\n            \"cannot use the first or last address within a given 255.255.255.252 subnet.  This is a limitation of --dev tun when used with the TAP-WIN32 driver\";\n        goto error;\n    }\n\n    gc_free(&gc);\n    return;\n\nerror:\n    msg(M_FATAL,\n        \"There is a problem in your selection of --ifconfig endpoints [local=%s, remote=%s].  The local and remote VPN endpoints %s.  Try '\" PACKAGE\n        \" --show-valid-subnets' option for more info.\",\n        print_in_addr_t(local, 0, &gc), print_in_addr_t(remote, 0, &gc), err);\n    gc_free(&gc);\n}\n\nvoid\nshow_valid_win32_tun_subnets(void)\n{\n    int i;\n    int col = 0;\n\n    printf(\"On Windows, point-to-point IP support (i.e. --dev tun)\\n\");\n    printf(\"is emulated by the TAP-Windows driver.  The major limitation\\n\");\n    printf(\"imposed by this approach is that the --ifconfig local and\\n\");\n    printf(\"remote endpoints must be part of the same 255.255.255.252\\n\");\n    printf(\"subnet.  The following list shows examples of endpoint\\n\");\n    printf(\"pairs which satisfy this requirement.  Only the final\\n\");\n    printf(\"component of the IP address pairs is at issue.\\n\\n\");\n    printf(\"As an example, the following option would be correct:\\n\");\n    printf(\"    --ifconfig 10.7.0.5 10.7.0.6 (on host A)\\n\");\n    printf(\"    --ifconfig 10.7.0.6 10.7.0.5 (on host B)\\n\");\n    printf(\"because [5,6] is part of the below list.\\n\\n\");\n\n    for (i = 0; i < 256; i += 4)\n    {\n        printf(\"[%3d,%3d] \", i + 1, i + 2);\n        if (++col > 4)\n        {\n            col = 0;\n            printf(\"\\n\");\n        }\n    }\n    if (col)\n    {\n        printf(\"\\n\");\n    }\n}\n\nvoid\nshow_tap_win_adapters(msglvl_t msglevel, msglvl_t warnlevel)\n{\n    struct gc_arena gc = gc_new();\n\n    bool warn_panel_null = false;\n    bool warn_panel_dup = false;\n    bool warn_tap_dup = false;\n\n    int links;\n\n    const struct tap_reg *tr;\n    const struct tap_reg *tr1;\n    const struct panel_reg *pr;\n\n    const struct tap_reg *tap_reg = get_tap_reg(&gc);\n    const struct panel_reg *panel_reg = get_panel_reg(&gc);\n\n    msg(msglevel, \"Available adapters [name, GUID, driver]:\");\n\n    /* loop through each TAP-Windows adapter registry entry */\n    for (tr = tap_reg; tr != NULL; tr = tr->next)\n    {\n        links = 0;\n\n        /* loop through each network connections entry in the control panel */\n        for (pr = panel_reg; pr != NULL; pr = pr->next)\n        {\n            if (!strcmp(tr->guid, pr->guid))\n            {\n                msg(msglevel, \"'%s' %s %s\", pr->name, tr->guid,\n                    print_tun_backend_driver(tr->windows_driver));\n                ++links;\n            }\n        }\n\n        if (links > 1)\n        {\n            warn_panel_dup = true;\n        }\n        else if (links == 0)\n        {\n            /* a TAP adapter exists without a link from the network\n             * connections control panel */\n            warn_panel_null = true;\n            msg(msglevel, \"[NULL] %s\", tr->guid);\n        }\n    }\n\n    /* check for TAP-Windows adapter duplicated GUIDs */\n    for (tr = tap_reg; tr != NULL; tr = tr->next)\n    {\n        for (tr1 = tap_reg; tr1 != NULL; tr1 = tr1->next)\n        {\n            if (tr != tr1 && !strcmp(tr->guid, tr1->guid))\n            {\n                warn_tap_dup = true;\n            }\n        }\n    }\n\n    /* warn on registry inconsistencies */\n    if (warn_tap_dup)\n    {\n        msg(warnlevel, \"WARNING: Some TAP-Windows adapters have duplicate GUIDs\");\n    }\n\n    if (warn_panel_dup)\n    {\n        msg(warnlevel,\n            \"WARNING: Some TAP-Windows adapters have duplicate links from the Network Connections control panel\");\n    }\n\n    if (warn_panel_null)\n    {\n        msg(warnlevel,\n            \"WARNING: Some TAP-Windows adapters have no link from the Network Connections control panel\");\n    }\n\n    gc_free(&gc);\n}\n\n/*\n * Lookup an adapter by GUID.\n */\nstatic const struct tap_reg *\nget_adapter_by_guid(const char *guid, const struct tap_reg *tap_reg)\n{\n    const struct tap_reg *tr;\n\n    for (tr = tap_reg; tr != NULL; tr = tr->next)\n    {\n        if (guid && !strcmp(tr->guid, guid))\n        {\n            return tr;\n        }\n    }\n\n    return NULL;\n}\n\nstatic const char *\nguid_to_name(const char *guid, const struct panel_reg *panel_reg)\n{\n    const struct panel_reg *pr;\n\n    for (pr = panel_reg; pr != NULL; pr = pr->next)\n    {\n        if (guid && !strcmp(pr->guid, guid))\n        {\n            return pr->name;\n        }\n    }\n\n    return NULL;\n}\n\nstatic const struct tap_reg *\nget_adapter_by_name(const char *name, const struct tap_reg *tap_reg,\n                    const struct panel_reg *panel_reg)\n{\n    const struct panel_reg *pr;\n\n    for (pr = panel_reg; pr != NULL; pr = pr->next)\n    {\n        if (name && !strcmp(pr->name, name))\n        {\n            return get_adapter_by_guid(pr->guid, tap_reg);\n        }\n    }\n\n    return NULL;\n}\n\nstatic void\nat_least_one_tap_win(const struct tap_reg *tap_reg)\n{\n    if (!tap_reg)\n    {\n        msg(M_FATAL, \"There are no TAP-Windows or ovpn-dco adapters \"\n                     \"on this system.  You should be able to create an adapter \"\n                     \"by using tapctl.exe utility.\");\n    }\n}\n\n/*\n * Get an adapter GUID and optional actual_name from the\n * registry for the TAP device # = device_number.\n */\nstatic const char *\nget_unspecified_device_guid(const int device_number, uint8_t *actual_name, int actual_name_size,\n                            const struct tap_reg *tap_reg_src,\n                            const struct panel_reg *panel_reg_src,\n                            enum tun_driver_type *windows_driver, struct gc_arena *gc)\n{\n    const struct tap_reg *tap_reg = tap_reg_src;\n    struct buffer actual = clear_buf();\n    int i;\n\n    ASSERT(device_number >= 0);\n\n    /* Make sure we have at least one TAP adapter */\n    if (!tap_reg)\n    {\n        return NULL;\n    }\n\n    /* The actual_name output buffer may be NULL */\n    if (actual_name)\n    {\n        ASSERT(actual_name_size > 0);\n        buf_set_write(&actual, actual_name, actual_name_size);\n    }\n\n    /* Move on to specified device number */\n    for (i = 0; i < device_number; i++)\n    {\n        tap_reg = tap_reg->next;\n        if (!tap_reg)\n        {\n            return NULL;\n        }\n    }\n\n    /* Save Network Panel name (if exists) in actual_name */\n    if (actual_name)\n    {\n        const char *act = guid_to_name(tap_reg->guid, panel_reg_src);\n        if (act)\n        {\n            buf_printf(&actual, \"%s\", act);\n        }\n        else\n        {\n            buf_printf(&actual, \"%s\", tap_reg->guid);\n        }\n    }\n\n    /* Save GUID for return value */\n    struct buffer ret = alloc_buf_gc(256, gc);\n    buf_printf(&ret, \"%s\", tap_reg->guid);\n    if (windows_driver != NULL)\n    {\n        *windows_driver = tap_reg->windows_driver;\n    }\n    return BSTR(&ret);\n}\n\n/*\n * Lookup a --dev-node adapter name in the registry\n * returning the GUID and optional actual_name and device type\n */\nstatic const char *\nget_device_guid(const char *name, uint8_t *actual_name, int actual_name_size,\n                enum tun_driver_type *windows_driver, const struct tap_reg *tap_reg,\n                const struct panel_reg *panel_reg, struct gc_arena *gc)\n{\n    struct buffer ret = alloc_buf_gc(256, gc);\n    struct buffer actual = clear_buf();\n    const struct tap_reg *tr;\n\n    /* Make sure we have at least one TAP adapter */\n    if (!tap_reg)\n    {\n        return NULL;\n    }\n\n    /* The actual_name output buffer may be NULL */\n    if (actual_name)\n    {\n        ASSERT(actual_name_size > 0);\n        buf_set_write(&actual, actual_name, actual_name_size);\n    }\n\n    /* Check if GUID was explicitly specified as --dev-node parameter */\n    tr = get_adapter_by_guid(name, tap_reg);\n    if (tr)\n    {\n        const char *act = guid_to_name(name, panel_reg);\n        buf_printf(&ret, \"%s\", name);\n        if (act)\n        {\n            buf_printf(&actual, \"%s\", act);\n        }\n        else\n        {\n            buf_printf(&actual, \"%s\", name);\n        }\n        if (windows_driver)\n        {\n            *windows_driver = tr->windows_driver;\n        }\n        return BSTR(&ret);\n    }\n\n    /* Lookup TAP adapter in network connections list */\n    {\n        tr = get_adapter_by_name(name, tap_reg, panel_reg);\n        if (tr)\n        {\n            buf_printf(&actual, \"%s\", name);\n            if (windows_driver)\n            {\n                *windows_driver = tr->windows_driver;\n            }\n            buf_printf(&ret, \"%s\", tr->guid);\n            return BSTR(&ret);\n        }\n    }\n\n    return NULL;\n}\n\n/*\n * Get adapter info list\n */\nconst IP_ADAPTER_INFO *\nget_adapter_info_list(struct gc_arena *gc)\n{\n    ULONG size = 0;\n    IP_ADAPTER_INFO *pi = NULL;\n    DWORD status;\n\n    if ((status = GetAdaptersInfo(NULL, &size)) != ERROR_BUFFER_OVERFLOW)\n    {\n        msg(M_INFO, \"GetAdaptersInfo #1 failed (status=%lu) : %s\", status,\n            strerror_win32(status, gc));\n    }\n    else\n    {\n        pi = (PIP_ADAPTER_INFO)gc_malloc(size, false, gc);\n        if ((status = GetAdaptersInfo(pi, &size)) != NO_ERROR)\n        {\n            msg(M_INFO, \"GetAdaptersInfo #2 failed (status=%lu) : %s\", status,\n                strerror_win32(status, gc));\n            pi = NULL;\n        }\n    }\n    return pi;\n}\n\nconst IP_PER_ADAPTER_INFO *\nget_per_adapter_info(const DWORD index, struct gc_arena *gc)\n{\n    ULONG size = 0;\n    IP_PER_ADAPTER_INFO *pi = NULL;\n    DWORD status;\n\n    if (index != TUN_ADAPTER_INDEX_INVALID)\n    {\n        if ((status = GetPerAdapterInfo(index, NULL, &size)) != ERROR_BUFFER_OVERFLOW)\n        {\n            msg(M_INFO, \"GetPerAdapterInfo #1 failed (status=%lu) : %s\", status,\n                strerror_win32(status, gc));\n        }\n        else\n        {\n            pi = (PIP_PER_ADAPTER_INFO)gc_malloc(size, false, gc);\n            if ((status = GetPerAdapterInfo(index, pi, &size)) == ERROR_SUCCESS)\n            {\n                return pi;\n            }\n            else\n            {\n                msg(M_INFO, \"GetPerAdapterInfo #2 failed (status=%lu) : %s\", status,\n                    strerror_win32(status, gc));\n            }\n        }\n    }\n    return pi;\n}\n\nstatic const IP_INTERFACE_INFO *\nget_interface_info_list(struct gc_arena *gc)\n{\n    ULONG size = 0;\n    IP_INTERFACE_INFO *ii = NULL;\n    DWORD status;\n\n    if ((status = GetInterfaceInfo(NULL, &size)) != ERROR_INSUFFICIENT_BUFFER)\n    {\n        msg(M_INFO, \"GetInterfaceInfo #1 failed (status=%lu) : %s\", status,\n            strerror_win32(status, gc));\n    }\n    else\n    {\n        ii = (PIP_INTERFACE_INFO)gc_malloc(size, false, gc);\n        if ((status = GetInterfaceInfo(ii, &size)) == NO_ERROR)\n        {\n            return ii;\n        }\n        else\n        {\n            msg(M_INFO, \"GetInterfaceInfo #2 failed (status=%lu) : %s\", status,\n                strerror_win32(status, gc));\n        }\n    }\n    return ii;\n}\n\nstatic const IP_ADAPTER_INDEX_MAP *\nget_interface_info(DWORD index, struct gc_arena *gc)\n{\n    const IP_INTERFACE_INFO *list = get_interface_info_list(gc);\n    if (list)\n    {\n        int i;\n        for (i = 0; i < list->NumAdapters; ++i)\n        {\n            const IP_ADAPTER_INDEX_MAP *inter = &list->Adapter[i];\n            if (index == inter->Index)\n            {\n                return inter;\n            }\n        }\n    }\n    return NULL;\n}\n\n/*\n * Given an adapter index, return a pointer to the\n * IP_ADAPTER_INFO structure for that adapter.\n */\n\nconst IP_ADAPTER_INFO *\nget_adapter(const IP_ADAPTER_INFO *ai, DWORD index)\n{\n    if (ai && index != TUN_ADAPTER_INDEX_INVALID)\n    {\n        const IP_ADAPTER_INFO *a;\n\n        /* find index in the linked list */\n        for (a = ai; a != NULL; a = a->Next)\n        {\n            if (a->Index == index)\n            {\n                return a;\n            }\n        }\n    }\n    return NULL;\n}\n\nconst IP_ADAPTER_INFO *\nget_adapter_info(DWORD index, struct gc_arena *gc)\n{\n    return get_adapter(get_adapter_info_list(gc), index);\n}\n\nstatic int\nget_adapter_n_ip_netmask(const IP_ADAPTER_INFO *ai)\n{\n    if (ai)\n    {\n        int n = 0;\n        const IP_ADDR_STRING *ip = &ai->IpAddressList;\n\n        while (ip)\n        {\n            ++n;\n            ip = ip->Next;\n        }\n        return n;\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nstatic bool\nget_adapter_ip_netmask(const IP_ADAPTER_INFO *ai, const int n, in_addr_t *ip, in_addr_t *netmask)\n{\n    bool ret = false;\n    *ip = 0;\n    *netmask = 0;\n\n    if (ai)\n    {\n        const IP_ADDR_STRING *iplist = &ai->IpAddressList;\n        int i = 0;\n\n        while (iplist)\n        {\n            if (i == n)\n            {\n                break;\n            }\n            ++i;\n            iplist = iplist->Next;\n        }\n\n        if (iplist)\n        {\n            const unsigned int getaddr_flags = GETADDR_HOST_ORDER;\n            const char *ip_str = iplist->IpAddress.String;\n            const char *netmask_str = iplist->IpMask.String;\n            bool succeed1 = false;\n            bool succeed2 = false;\n\n            if (ip_str && netmask_str && strlen(ip_str) && strlen(netmask_str))\n            {\n                *ip = getaddr(getaddr_flags, ip_str, 0, &succeed1, NULL);\n                *netmask = getaddr(getaddr_flags, netmask_str, 0, &succeed2, NULL);\n                ret = (succeed1 == true && succeed2 == true);\n            }\n        }\n    }\n\n    return ret;\n}\n\nstatic bool\ntest_adapter_ip_netmask(const IP_ADAPTER_INFO *ai, const in_addr_t ip, const in_addr_t netmask)\n{\n    if (ai)\n    {\n        in_addr_t ip_adapter = 0;\n        in_addr_t netmask_adapter = 0;\n        const bool status = get_adapter_ip_netmask(ai, 0, &ip_adapter, &netmask_adapter);\n        return (status && ip_adapter == ip && netmask_adapter == netmask);\n    }\n    else\n    {\n        return false;\n    }\n}\n\nconst IP_ADAPTER_INFO *\nget_tun_adapter(const struct tuntap *tt, const IP_ADAPTER_INFO *list)\n{\n    if (list && tt)\n    {\n        return get_adapter(list, tt->adapter_index);\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\nbool\nis_adapter_up(const struct tuntap *tt, const IP_ADAPTER_INFO *list)\n{\n    int i;\n    bool ret = false;\n\n    const IP_ADAPTER_INFO *ai = get_tun_adapter(tt, list);\n\n    if (ai)\n    {\n        const int n = get_adapter_n_ip_netmask(ai);\n\n        /* loop once for every IP/netmask assigned to adapter */\n        for (i = 0; i < n; ++i)\n        {\n            in_addr_t ip, netmask;\n            if (get_adapter_ip_netmask(ai, i, &ip, &netmask))\n            {\n                if (tt->local && tt->adapter_netmask)\n                {\n                    /* wait for our --ifconfig parms to match the actual adapter parms */\n                    if (tt->local == ip && tt->adapter_netmask == netmask)\n                    {\n                        ret = true;\n                    }\n                }\n                else\n                {\n                    /* --ifconfig was not defined, maybe using a real DHCP server */\n                    if (ip && netmask)\n                    {\n                        ret = true;\n                    }\n                }\n            }\n        }\n    }\n    else\n    {\n        ret = true; /* this can occur when TAP adapter is bridged */\n    }\n    return ret;\n}\n\nbool\nis_ip_in_adapter_subnet(const IP_ADAPTER_INFO *ai, const in_addr_t ip, in_addr_t *highest_netmask)\n{\n    int i;\n    bool ret = false;\n\n    if (highest_netmask)\n    {\n        *highest_netmask = 0;\n    }\n\n    if (ai)\n    {\n        const int n = get_adapter_n_ip_netmask(ai);\n        for (i = 0; i < n; ++i)\n        {\n            in_addr_t adapter_ip, adapter_netmask;\n            if (get_adapter_ip_netmask(ai, i, &adapter_ip, &adapter_netmask))\n            {\n                if (adapter_ip && adapter_netmask\n                    && (ip & adapter_netmask) == (adapter_ip & adapter_netmask))\n                {\n                    if (highest_netmask && adapter_netmask > *highest_netmask)\n                    {\n                        *highest_netmask = adapter_netmask;\n                    }\n                    ret = true;\n                }\n            }\n        }\n    }\n    return ret;\n}\n\nDWORD\nadapter_index_of_ip(const IP_ADAPTER_INFO *list, const in_addr_t ip, int *count, in_addr_t *netmask)\n{\n    struct gc_arena gc = gc_new();\n    DWORD ret = TUN_ADAPTER_INDEX_INVALID;\n    in_addr_t highest_netmask = 0;\n    int lowest_metric = INT_MAX;\n    bool first = true;\n\n    if (count)\n    {\n        *count = 0;\n    }\n\n    while (list)\n    {\n        in_addr_t hn;\n\n        if (is_ip_in_adapter_subnet(list, ip, &hn))\n        {\n            int metric = get_interface_metric(list->Index, AF_INET, NULL);\n            if (first || hn > highest_netmask)\n            {\n                highest_netmask = hn;\n                if (metric >= 0)\n                {\n                    lowest_metric = metric;\n                }\n                if (count)\n                {\n                    *count = 1;\n                }\n                ret = list->Index;\n                first = false;\n            }\n            else if (hn == highest_netmask)\n            {\n                if (count)\n                {\n                    ++*count;\n                }\n                if (metric >= 0 && metric < lowest_metric)\n                {\n                    ret = list->Index;\n                    lowest_metric = metric;\n                }\n            }\n        }\n        list = list->Next;\n    }\n\n    dmsg(D_ROUTE_DEBUG, \"DEBUG: IP Locate: ip=%s nm=%s index=%lu count=%d metric=%d\",\n         print_in_addr_t(ip, 0, &gc), print_in_addr_t(highest_netmask, 0, &gc), ret,\n         count ? *count : -1, lowest_metric);\n\n    if (ret == TUN_ADAPTER_INDEX_INVALID && count)\n    {\n        *count = 0;\n    }\n\n    if (netmask)\n    {\n        *netmask = highest_netmask;\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n\n/*\n * Given an adapter index, return true if the adapter\n * is DHCP disabled.\n */\n\n#define DHCP_STATUS_UNDEF    0\n#define DHCP_STATUS_ENABLED  1\n#define DHCP_STATUS_DISABLED 2\n\nstatic int\ndhcp_status(DWORD index)\n{\n    struct gc_arena gc = gc_new();\n    int ret = DHCP_STATUS_UNDEF;\n    if (index != TUN_ADAPTER_INDEX_INVALID)\n    {\n        const IP_ADAPTER_INFO *ai = get_adapter_info(index, &gc);\n\n        if (ai)\n        {\n            if (ai->DhcpEnabled)\n            {\n                ret = DHCP_STATUS_ENABLED;\n            }\n            else\n            {\n                ret = DHCP_STATUS_DISABLED;\n            }\n        }\n    }\n    gc_free(&gc);\n    return ret;\n}\n\n/*\n * Delete all temporary address/netmask pairs which were added\n * to adapter (given by index) by previous calls to AddIPAddress.\n */\nstatic void\ndelete_temp_addresses(DWORD index)\n{\n    struct gc_arena gc = gc_new();\n    const IP_ADAPTER_INFO *a = get_adapter_info(index, &gc);\n\n    if (a)\n    {\n        const IP_ADDR_STRING *ip = &a->IpAddressList;\n        while (ip)\n        {\n            DWORD status;\n            const DWORD context = ip->Context;\n\n            if ((status = DeleteIPAddress(context)) == NO_ERROR)\n            {\n                msg(M_INFO, \"Successfully deleted previously set dynamic IP/netmask: %s/%s\",\n                    ip->IpAddress.String, ip->IpMask.String);\n            }\n            else\n            {\n                const char *empty = \"0.0.0.0\";\n                if (strcmp(ip->IpAddress.String, empty) || strcmp(ip->IpMask.String, empty))\n                {\n                    msg(M_INFO,\n                        \"NOTE: could not delete previously set dynamic IP/netmask: %s/%s (status=%lu)\",\n                        ip->IpAddress.String, ip->IpMask.String, status);\n                }\n            }\n            ip = ip->Next;\n        }\n    }\n    gc_free(&gc);\n}\n\n/*\n * Get interface index for use with IP Helper API functions.\n */\nstatic DWORD\nget_adapter_index_method_1(const char *guid)\n{\n    DWORD index;\n    ULONG aindex;\n    wchar_t wbuf[256];\n    swprintf(wbuf, SIZE(wbuf), L\"\\\\DEVICE\\\\TCPIP_%hs\", guid);\n    if (GetAdapterIndex(wbuf, &aindex) != NO_ERROR)\n    {\n        index = TUN_ADAPTER_INDEX_INVALID;\n    }\n    else\n    {\n        index = (DWORD)aindex;\n    }\n    return index;\n}\n\nstatic DWORD\nget_adapter_index_method_2(const char *guid)\n{\n    struct gc_arena gc = gc_new();\n    DWORD index = TUN_ADAPTER_INDEX_INVALID;\n\n    const IP_ADAPTER_INFO *list = get_adapter_info_list(&gc);\n\n    while (list)\n    {\n        if (!strcmp(guid, list->AdapterName))\n        {\n            index = list->Index;\n            break;\n        }\n        list = list->Next;\n    }\n\n    gc_free(&gc);\n    return index;\n}\n\nstatic DWORD\nget_adapter_index(const char *guid)\n{\n    DWORD index;\n    index = get_adapter_index_method_1(guid);\n    if (index == TUN_ADAPTER_INDEX_INVALID)\n    {\n        index = get_adapter_index_method_2(guid);\n    }\n    if (index == TUN_ADAPTER_INDEX_INVALID)\n    {\n        msg(M_INFO, \"NOTE: could not get adapter index for %s\", guid);\n    }\n    return index;\n}\n\n/*\n * Return a string representing a PIP_ADDR_STRING\n */\nstatic const char *\nformat_ip_addr_string(const IP_ADDR_STRING *ip, struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n    while (ip)\n    {\n        buf_printf(&out, \"%s\", ip->IpAddress.String);\n        if (strlen(ip->IpMask.String))\n        {\n            buf_printf(&out, \"/\");\n            buf_printf(&out, \"%s\", ip->IpMask.String);\n        }\n        buf_printf(&out, \" \");\n        ip = ip->Next;\n    }\n    return BSTR(&out);\n}\n\n/*\n * Show info for a single adapter\n */\nstatic void\nshow_adapter(msglvl_t msglevel, const IP_ADAPTER_INFO *a, struct gc_arena *gc)\n{\n    msg(msglevel, \"%s\", a->Description);\n    msg(msglevel, \"  Index = %lu\", a->Index);\n    msg(msglevel, \"  GUID = %s\", a->AdapterName);\n    msg(msglevel, \"  IP = %s\", format_ip_addr_string(&a->IpAddressList, gc));\n    msg(msglevel, \"  MAC = %s\", format_hex_ex(a->Address, a->AddressLength, 0, 1, \":\", gc));\n    msg(msglevel, \"  GATEWAY = %s\", format_ip_addr_string(&a->GatewayList, gc));\n    if (a->DhcpEnabled)\n    {\n        msg(msglevel, \"  DHCP SERV = %s\", format_ip_addr_string(&a->DhcpServer, gc));\n        msg(msglevel, \"  DHCP LEASE OBTAINED = %s\", time_string(a->LeaseObtained, 0, false, gc));\n        msg(msglevel, \"  DHCP LEASE EXPIRES  = %s\", time_string(a->LeaseExpires, 0, false, gc));\n    }\n    if (a->HaveWins)\n    {\n        msg(msglevel, \"  PRI WINS = %s\", format_ip_addr_string(&a->PrimaryWinsServer, gc));\n        msg(msglevel, \"  SEC WINS = %s\", format_ip_addr_string(&a->SecondaryWinsServer, gc));\n    }\n\n    {\n        const IP_PER_ADAPTER_INFO *pai = get_per_adapter_info(a->Index, gc);\n        if (pai)\n        {\n            msg(msglevel, \"  DNS SERV = %s\", format_ip_addr_string(&pai->DnsServerList, gc));\n        }\n    }\n}\n\n/*\n * Show current adapter list\n */\nvoid\nshow_adapters(msglvl_t msglevel)\n{\n    struct gc_arena gc = gc_new();\n    const IP_ADAPTER_INFO *ai = get_adapter_info_list(&gc);\n\n    msg(msglevel, \"SYSTEM ADAPTER LIST\");\n    if (ai)\n    {\n        const IP_ADAPTER_INFO *a;\n\n        /* find index in the linked list */\n        for (a = ai; a != NULL; a = a->Next)\n        {\n            show_adapter(msglevel, a, &gc);\n        }\n    }\n    gc_free(&gc);\n}\n\n/*\n * Set a particular TAP-Windows adapter (or all of them if\n * adapter_name == NULL) to allow it to be opened from\n * a non-admin account.  This setting will only persist\n * for the lifetime of the device object.\n */\n\nstatic void\ntap_allow_nonadmin_access_handle(const char *device_path, HANDLE hand)\n{\n    struct security_attributes sa;\n    BOOL status;\n\n    if (!init_security_attributes_allow_all(&sa))\n    {\n        msg(M_ERR, \"Error: init SA failed\");\n    }\n\n    status = SetKernelObjectSecurity(hand, DACL_SECURITY_INFORMATION, &sa.sd);\n    if (!status)\n    {\n        msg(M_ERRNO, \"Error: SetKernelObjectSecurity failed on %s\", device_path);\n    }\n    else\n    {\n        msg(M_INFO | M_NOPREFIX, \"TAP-Windows device: %s [Non-admin access allowed]\", device_path);\n    }\n}\n\nvoid\ntap_allow_nonadmin_access(const char *dev_node)\n{\n    struct gc_arena gc = gc_new();\n    const struct tap_reg *tap_reg = get_tap_reg(&gc);\n    const struct panel_reg *panel_reg = get_panel_reg(&gc);\n    const char *device_guid = NULL;\n    HANDLE hand;\n    uint8_t actual_buffer[256];\n    char device_path[256];\n\n    at_least_one_tap_win(tap_reg);\n\n    if (dev_node)\n    {\n        /* Get the device GUID for the device specified with --dev-node. */\n        device_guid = get_device_guid(dev_node, actual_buffer, sizeof(actual_buffer), NULL, tap_reg,\n                                      panel_reg, &gc);\n\n        if (!device_guid)\n        {\n            msg(M_FATAL, \"TAP-Windows adapter '%s' not found\", dev_node);\n        }\n\n        /* Open Windows TAP-Windows adapter */\n        snprintf(device_path, sizeof(device_path), \"%s%s%s\", USERMODEDEVICEDIR, device_guid,\n                 TAP_WIN_SUFFIX);\n\n        hand = CreateFile(device_path, MAXIMUM_ALLOWED, 0, /* was: FILE_SHARE_READ */\n                          0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);\n\n        if (hand == INVALID_HANDLE_VALUE)\n        {\n            msg(M_ERR, \"CreateFile failed on TAP device: %s\", device_path);\n        }\n\n        tap_allow_nonadmin_access_handle(device_path, hand);\n        CloseHandle(hand);\n    }\n    else\n    {\n        int device_number = 0;\n\n        /* Try opening all TAP devices */\n        while (true)\n        {\n            device_guid = get_unspecified_device_guid(\n                device_number, actual_buffer, sizeof(actual_buffer), tap_reg, panel_reg, NULL, &gc);\n\n            if (!device_guid)\n            {\n                break;\n            }\n\n            /* Open Windows TAP-Windows adapter */\n            snprintf(device_path, sizeof(device_path), \"%s%s%s\", USERMODEDEVICEDIR, device_guid,\n                     TAP_WIN_SUFFIX);\n\n            hand = CreateFile(device_path, MAXIMUM_ALLOWED, 0, /* was: FILE_SHARE_READ */\n                              0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);\n\n            if (hand == INVALID_HANDLE_VALUE)\n            {\n                msg(M_WARN, \"CreateFile failed on TAP device: %s\", device_path);\n            }\n            else\n            {\n                tap_allow_nonadmin_access_handle(device_path, hand);\n                CloseHandle(hand);\n            }\n\n            device_number++;\n        }\n    }\n    gc_free(&gc);\n}\n\n/*\n * DHCP release/renewal\n */\nbool\ndhcp_release_by_adapter_index(const DWORD adapter_index)\n{\n    struct gc_arena gc = gc_new();\n    bool ret = false;\n    const IP_ADAPTER_INDEX_MAP *inter = get_interface_info(adapter_index, &gc);\n\n    if (inter)\n    {\n        DWORD status = IpReleaseAddress((IP_ADAPTER_INDEX_MAP *)inter);\n        if (status == NO_ERROR)\n        {\n            msg(D_TUNTAP_INFO, \"TAP: DHCP address released\");\n            ret = true;\n        }\n        else\n        {\n            msg(M_WARN,\n                \"NOTE: Release of DHCP-assigned IP address lease on TAP-Windows adapter failed: %s (code=%lu)\",\n                strerror_win32(status, &gc), status);\n        }\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n\nstatic bool\ndhcp_release(const struct tuntap *tt)\n{\n    if (tt && tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ\n        && tt->adapter_index != TUN_ADAPTER_INDEX_INVALID)\n    {\n        return dhcp_release_by_adapter_index(tt->adapter_index);\n    }\n    else\n    {\n        return false;\n    }\n}\n\nbool\ndhcp_renew_by_adapter_index(const DWORD adapter_index)\n{\n    struct gc_arena gc = gc_new();\n    bool ret = false;\n    const IP_ADAPTER_INDEX_MAP *inter = get_interface_info(adapter_index, &gc);\n\n    if (inter)\n    {\n        DWORD status = IpRenewAddress((IP_ADAPTER_INDEX_MAP *)inter);\n        if (status == NO_ERROR)\n        {\n            msg(D_TUNTAP_INFO, \"TAP: DHCP address renewal succeeded\");\n            ret = true;\n        }\n        else\n        {\n            msg(M_WARN,\n                \"WARNING: Failed to renew DHCP IP address lease on TAP-Windows adapter: %s (code=%lu)\",\n                strerror_win32(status, &gc), status);\n        }\n    }\n    gc_free(&gc);\n    return ret;\n}\n\nstatic bool\ndhcp_renew(const struct tuntap *tt)\n{\n    if (tt && tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ\n        && tt->adapter_index != TUN_ADAPTER_INDEX_INVALID)\n    {\n        return dhcp_renew_by_adapter_index(tt->adapter_index);\n    }\n    else\n    {\n        return false;\n    }\n}\n\nstatic void\nexec_command(const char *prefix, const struct argv *a, int n, msglvl_t msglevel)\n{\n    int i;\n    for (i = 0; i < n; ++i)\n    {\n        bool status;\n        management_sleep(0);\n        netcmd_semaphore_lock();\n        argv_msg_prefix(M_INFO, a, prefix);\n        status = openvpn_execve_check(a, NULL, 0, \"ERROR: command failed\");\n        netcmd_semaphore_release();\n        if (status)\n        {\n            return;\n        }\n        management_sleep(4);\n    }\n    msg(msglevel, \"%s: command failed\", prefix);\n}\n\nstatic void\nnetsh_command(const struct argv *a, int n, msglvl_t msglevel)\n{\n    exec_command(\"NETSH\", a, n, msglevel);\n}\n\nvoid\nipconfig_register_dns(const struct env_set *es)\n{\n    struct argv argv = argv_new();\n    const char err[] = \"ERROR: Windows ipconfig command failed\";\n\n    msg(D_TUNTAP_INFO, \"Start ipconfig commands for register-dns...\");\n    netcmd_semaphore_lock();\n\n    argv_printf(&argv, \"%s%s /flushdns\", get_win_sys_path(), WIN_IPCONFIG_PATH_SUFFIX);\n    argv_msg(D_TUNTAP_INFO, &argv);\n    openvpn_execve_check(&argv, es, 0, err);\n\n    argv_printf(&argv, \"%s%s /registerdns\", get_win_sys_path(), WIN_IPCONFIG_PATH_SUFFIX);\n    argv_msg(D_TUNTAP_INFO, &argv);\n    openvpn_execve_check(&argv, es, 0, err);\n    argv_free(&argv);\n\n    netcmd_semaphore_release();\n    msg(D_TUNTAP_INFO, \"End ipconfig commands for register-dns...\");\n}\n\nstatic void\nip_addr_string_to_array(in_addr_t *dest, unsigned int *dest_len, const IP_ADDR_STRING *src)\n{\n    unsigned int i = 0;\n    while (src)\n    {\n        const unsigned int getaddr_flags = GETADDR_HOST_ORDER;\n        const char *ip_str = src->IpAddress.String;\n        in_addr_t ip = 0;\n        bool succeed = false;\n\n        if (i >= *dest_len)\n        {\n            break;\n        }\n        if (!ip_str || !strlen(ip_str))\n        {\n            break;\n        }\n\n        ip = getaddr(getaddr_flags, ip_str, 0, &succeed, NULL);\n        if (!succeed)\n        {\n            break;\n        }\n        dest[i++] = ip;\n\n        src = src->Next;\n    }\n    *dest_len = i;\n\n#if 0\n    {\n        struct gc_arena gc = gc_new();\n        msg(M_INFO, \"ip_addr_string_to_array [%d]\", *dest_len);\n        for (i = 0; i < *dest_len; ++i)\n        {\n            msg(M_INFO, \"%s\", print_in_addr_t(dest[i], 0, &gc));\n        }\n        gc_free(&gc);\n    }\n#endif\n}\n\nstatic bool\nip_addr_one_to_one(const in_addr_t *a1, const unsigned int a1len, const IP_ADDR_STRING *ias)\n{\n#define MAX_ADDRS 8\n    in_addr_t a2[MAX_ADDRS];\n    unsigned int a2len = MAX_ADDRS;\n\n    ip_addr_string_to_array(a2, &a2len, ias);\n    /*msg (M_INFO, \"a1len=%d a2len=%d\", a1len, a2len);*/\n    if (a1len != a2len)\n    {\n        return false;\n    }\n\n    for (unsigned int i = 0; i < a1len; ++i)\n    {\n        if (a1[i] != a2[i])\n        {\n            return false;\n        }\n    }\n    return true;\n}\n\nstatic bool\nip_addr_member_of(const in_addr_t addr, const IP_ADDR_STRING *ias)\n{\n    in_addr_t aa[MAX_ADDRS];\n    unsigned int len = MAX_ADDRS;\n\n    ip_addr_string_to_array(aa, &len, ias);\n    for (unsigned int i = 0; i < len; ++i)\n    {\n        if (addr == aa[i])\n        {\n            return true;\n        }\n    }\n    return false;\n}\n#undef MAX_ADDRS\n\n/**\n * Set the ipv6 dns servers on the specified interface.\n * The list of dns servers currently set on the interface\n * are cleared first.\n */\nstatic void\nnetsh_set_dns6_servers(const struct in6_addr *addr_list, const unsigned int addr_len, DWORD adapter_index)\n{\n    struct gc_arena gc = gc_new();\n    struct argv argv = argv_new();\n\n    /* delete existing DNS settings from TAP interface */\n    argv_printf(&argv, \"%s%s interface ipv6 delete dns %lu all\", get_win_sys_path(),\n                NETSH_PATH_SUFFIX, adapter_index);\n    netsh_command(&argv, 2, M_FATAL);\n\n    for (unsigned int i = 0; i < addr_len; ++i)\n    {\n        const char *fmt = (i == 0) ? \"%s%s interface ipv6 set dns %lu static %s\"\n                                   : \"%s%s interface ipv6 add dns %lu %s\";\n        argv_printf(&argv, fmt, get_win_sys_path(), NETSH_PATH_SUFFIX, adapter_index,\n                    print_in6_addr(addr_list[i], 0, &gc));\n\n        /* disable slow address validation */\n        argv_printf_cat(&argv, \"%s\", \"validate=no\");\n\n        /* Treat errors while adding as non-fatal as we do not check for duplicates */\n        netsh_command(&argv, 1, (i == 0) ? M_FATAL : M_NONFATAL);\n    }\n\n    argv_free(&argv);\n    gc_free(&gc);\n}\n\nstatic void\nnetsh_ifconfig_options(const char *type, const in_addr_t *addr_list, const unsigned int addr_len,\n                       const IP_ADDR_STRING *current, DWORD adapter_index, const bool test_first)\n{\n    struct gc_arena gc = gc_new();\n    struct argv argv = argv_new();\n    bool delete_first = false;\n    bool is_dns = !strcmp(type, \"dns\");\n\n    /* first check if we should delete existing DNS/WINS settings from TAP interface */\n    if (test_first)\n    {\n        if (!ip_addr_one_to_one(addr_list, addr_len, current))\n        {\n            delete_first = true;\n        }\n    }\n    else\n    {\n        delete_first = true;\n    }\n\n    /* delete existing DNS/WINS settings from TAP interface */\n    if (delete_first)\n    {\n        argv_printf(&argv, \"%s%s interface ip delete %s %lu all\", get_win_sys_path(),\n                    NETSH_PATH_SUFFIX, type, adapter_index);\n        netsh_command(&argv, 2, M_FATAL);\n    }\n\n    /* add new DNS/WINS settings to TAP interface */\n    {\n        bool first = true;\n        for (unsigned int i = 0; i < addr_len; ++i)\n        {\n            if (delete_first || !test_first || !ip_addr_member_of(addr_list[i], current))\n            {\n                const char *fmt = first ? \"%s%s interface ip set %s %lu static %s\"\n                                        : \"%s%s interface ip add %s %lu %s\";\n\n                argv_printf(&argv, fmt, get_win_sys_path(), NETSH_PATH_SUFFIX, type, adapter_index,\n                            print_in_addr_t(addr_list[i], 0, &gc));\n\n                /* disable slow address validation for DNS */\n                if (is_dns)\n                {\n                    argv_printf_cat(&argv, \"%s\", \"validate=no\");\n                }\n\n                netsh_command(&argv, 2, M_FATAL);\n\n                first = false;\n            }\n            else\n            {\n                msg(M_INFO, \"NETSH: %lu %s %s [already set]\", adapter_index, type,\n                    print_in_addr_t(addr_list[i], 0, &gc));\n            }\n        }\n    }\n\n    argv_free(&argv);\n    gc_free(&gc);\n}\n\nstatic void\ninit_ip_addr_string2(IP_ADDR_STRING *dest, const IP_ADDR_STRING *src1, const IP_ADDR_STRING *src2)\n{\n    CLEAR(dest[0]);\n    CLEAR(dest[1]);\n    if (src1)\n    {\n        dest[0] = *src1;\n        dest[0].Next = NULL;\n    }\n    if (src2)\n    {\n        dest[1] = *src2;\n        dest[0].Next = &dest[1];\n        dest[1].Next = NULL;\n    }\n}\n\nstatic void\nnetsh_ifconfig(const struct tuntap_options *to, DWORD adapter_index, const in_addr_t ip,\n               const in_addr_t netmask, const unsigned int flags)\n{\n    struct gc_arena gc = gc_new();\n    struct argv argv = argv_new();\n    const IP_ADAPTER_INFO *ai = NULL;\n    const IP_PER_ADAPTER_INFO *pai = NULL;\n\n    if (flags & NI_TEST_FIRST)\n    {\n        const IP_ADAPTER_INFO *list = get_adapter_info_list(&gc);\n        ai = get_adapter(list, adapter_index);\n        pai = get_per_adapter_info(adapter_index, &gc);\n    }\n\n    if (flags & NI_IP_NETMASK)\n    {\n        if (test_adapter_ip_netmask(ai, ip, netmask))\n        {\n            msg(M_INFO, \"NETSH: %lu %s/%s [already set]\", adapter_index,\n                print_in_addr_t(ip, 0, &gc), print_in_addr_t(netmask, 0, &gc));\n        }\n        else\n        {\n            /* example: netsh interface ip set address 42 static 10.3.0.1 255.255.255.0 store=active */\n            argv_printf(&argv, \"%s%s interface ip set address %lu static %s %s store=active\", get_win_sys_path(),\n                        NETSH_PATH_SUFFIX, adapter_index, print_in_addr_t(ip, 0, &gc),\n                        print_in_addr_t(netmask, 0, &gc));\n\n            netsh_command(&argv, 4, M_FATAL);\n        }\n    }\n\n    /* set WINS/DNS options */\n    if (flags & NI_OPTIONS)\n    {\n        IP_ADDR_STRING wins[2];\n        CLEAR(wins[0]);\n        CLEAR(wins[1]);\n\n        netsh_ifconfig_options(\"dns\", to->dns, to->dns_len, pai ? &pai->DnsServerList : NULL,\n                               adapter_index, BOOL_CAST(flags & NI_TEST_FIRST));\n        if (ai && ai->HaveWins)\n        {\n            init_ip_addr_string2(wins, &ai->PrimaryWinsServer, &ai->SecondaryWinsServer);\n        }\n\n        netsh_ifconfig_options(\"wins\", to->wins, to->wins_len, ai ? wins : NULL, adapter_index,\n                               BOOL_CAST(flags & NI_TEST_FIRST));\n    }\n\n    argv_free(&argv);\n    gc_free(&gc);\n}\n\nstatic void\nnetsh_enable_dhcp(DWORD adapter_index)\n{\n    struct argv argv = argv_new();\n\n    /* example: netsh interface ip set address 42 dhcp */\n    argv_printf(&argv, \"%s%s interface ip set address %lu dhcp\", get_win_sys_path(),\n                NETSH_PATH_SUFFIX, adapter_index);\n\n    netsh_command(&argv, 4, M_FATAL);\n\n    argv_free(&argv);\n}\n\n/* Enable dhcp on tap adapter using iservice */\nstatic bool\nservice_enable_dhcp(const struct tuntap *tt)\n{\n    bool ret = false;\n    ack_message_t ack;\n    struct gc_arena gc = gc_new();\n    HANDLE pipe = tt->options.msg_channel;\n\n    enable_dhcp_message_t dhcp = { .header = { msg_enable_dhcp, sizeof(enable_dhcp_message_t), 0 },\n                                   .iface = { .index = tt->adapter_index, .name = \"\" } };\n\n    if (!send_msg_iservice(pipe, &dhcp, sizeof(dhcp), &ack, \"Enable_dhcp\"))\n    {\n        goto out;\n    }\n\n    if (ack.error_number != NO_ERROR)\n    {\n        msg(M_NONFATAL, \"TUN: enabling dhcp using service failed: %s [status=%u if_index=%lu]\",\n            strerror_win32(ack.error_number, &gc), ack.error_number, dhcp.iface.index);\n    }\n    else\n    {\n        msg(M_INFO, \"DHCP enabled on interface %lu using service\", dhcp.iface.index);\n        ret = true;\n    }\n\nout:\n    gc_free(&gc);\n    return ret;\n}\n\nstatic void\nwindows_set_mtu(const int iface_index, const short family, const int mtu)\n{\n    DWORD err = 0;\n    struct gc_arena gc = gc_new();\n    MIB_IPINTERFACE_ROW ipiface;\n    InitializeIpInterfaceEntry(&ipiface);\n    const char *family_name = (family == AF_INET6) ? \"IPv6\" : \"IPv4\";\n    ipiface.Family = family;\n    ipiface.InterfaceIndex = iface_index;\n    if (family == AF_INET6 && mtu < 1280)\n    {\n        msg(M_INFO,\n            \"NOTE: IPv6 interface MTU < 1280 conflicts with IETF standards and might not work\");\n    }\n\n    err = GetIpInterfaceEntry(&ipiface);\n    if (err == NO_ERROR)\n    {\n        if (family == AF_INET)\n        {\n            ipiface.SitePrefixLength = 0;\n        }\n        ipiface.NlMtu = mtu;\n        err = SetIpInterfaceEntry(&ipiface);\n    }\n\n    if (err != NO_ERROR)\n    {\n        msg(M_WARN, \"TUN: Setting %s mtu failed: %s [status=%lu if_index=%d]\", family_name,\n            strerror_win32(err, &gc), err, iface_index);\n    }\n    else\n    {\n        msg(M_INFO, \"%s MTU set to %d on interface %d using SetIpInterfaceEntry()\", family_name,\n            mtu, iface_index);\n    }\n}\n\n\n/*\n * Return a TAP name for netsh commands.\n */\nstatic const char *\nnetsh_get_id(const char *dev_node, struct gc_arena *gc)\n{\n    const struct tap_reg *tap_reg = get_tap_reg(gc);\n    const struct panel_reg *panel_reg = get_panel_reg(gc);\n    struct buffer actual = alloc_buf_gc(256, gc);\n    const char *guid;\n\n    at_least_one_tap_win(tap_reg);\n\n    if (dev_node)\n    {\n        guid =\n            get_device_guid(dev_node, BPTR(&actual), BCAP(&actual), NULL, tap_reg, panel_reg, gc);\n    }\n    else\n    {\n        guid = get_unspecified_device_guid(0, BPTR(&actual), BCAP(&actual), tap_reg, panel_reg,\n                                           NULL, gc);\n\n        if (get_unspecified_device_guid(1, NULL, 0, tap_reg, panel_reg, NULL,\n                                        gc)) /* ambiguous if more than one TAP-Windows adapter */\n        {\n            guid = NULL;\n        }\n    }\n\n    if (!guid)\n    {\n        return \"NULL\"; /* not found */\n    }\n    else if (strcmp(BSTR(&actual), \"NULL\"))\n    {\n        return BSTR(&actual); /* control panel name */\n    }\n    else\n    {\n        return guid; /* no control panel name, return GUID instead */\n    }\n}\n\n/*\n * Called iteratively on TAP-Windows wait-for-initialization polling loop\n */\nvoid\ntun_standby_init(struct tuntap *tt)\n{\n    tt->standby_iter = 0;\n}\n\nbool\ntun_standby(struct tuntap *tt)\n{\n    bool ret = true;\n    ++tt->standby_iter;\n    if (tt->options.ip_win32_type == IPW32_SET_ADAPTIVE)\n    {\n        if (tt->standby_iter == IPW32_SET_ADAPTIVE_TRY_NETSH)\n        {\n            msg(M_INFO, \"NOTE: now trying netsh (this may take some time)\");\n            netsh_ifconfig(&tt->options, tt->adapter_index, tt->local, tt->adapter_netmask,\n                           NI_TEST_FIRST | NI_IP_NETMASK | NI_OPTIONS);\n        }\n        else if (tt->standby_iter >= IPW32_SET_ADAPTIVE_TRY_NETSH * 2)\n        {\n            ret = false;\n        }\n    }\n    return ret;\n}\n\nstatic void\nfork_dhcp_action(struct tuntap *tt)\n{\n    if (tt->options.dhcp_pre_release || tt->options.dhcp_renew)\n    {\n        struct gc_arena gc = gc_new();\n        struct buffer cmd = alloc_buf_gc(256, &gc);\n        const int verb = 3;\n        const int pre_sleep = 1;\n\n        buf_printf(&cmd, \"openvpn --verb %d --tap-sleep %d\", verb, pre_sleep);\n        if (tt->options.dhcp_pre_release)\n        {\n            buf_printf(&cmd, \" --dhcp-pre-release\");\n        }\n        if (tt->options.dhcp_renew)\n        {\n            buf_printf(&cmd, \" --dhcp-renew\");\n        }\n        buf_printf(&cmd, \" --dhcp-internal %lu\", tt->adapter_index);\n\n        fork_to_self(BSTR(&cmd));\n        gc_free(&gc);\n    }\n}\n\nstatic void\nregister_dns_service(const struct tuntap *tt)\n{\n    HANDLE msg_channel = tt->options.msg_channel;\n    ack_message_t ack;\n    struct gc_arena gc = gc_new();\n\n    message_header_t rdns = { msg_register_dns, sizeof(message_header_t), 0 };\n\n    if (!send_msg_iservice(msg_channel, &rdns, sizeof(rdns), &ack, \"Register_dns\"))\n    {\n        gc_free(&gc);\n        return;\n    }\n\n    else if (ack.error_number != NO_ERROR)\n    {\n        msg(M_WARN, \"Register_dns failed using service: %s [status=0x%x]\",\n            strerror_win32(ack.error_number, &gc), ack.error_number);\n    }\n\n    else\n    {\n        msg(M_INFO, \"Register_dns request sent to the service\");\n    }\n\n    gc_free(&gc);\n}\n\nvoid\nfork_register_dns_action(struct tuntap *tt)\n{\n    if (tt && tt->options.register_dns && tt->options.msg_channel)\n    {\n        register_dns_service(tt);\n    }\n    else if (tt && tt->options.register_dns)\n    {\n        struct gc_arena gc = gc_new();\n        struct buffer cmd = alloc_buf_gc(256, &gc);\n        const int verb = 3;\n\n        buf_printf(&cmd, \"openvpn --verb %d --register-dns --rdns-internal\", verb);\n        fork_to_self(BSTR(&cmd));\n        gc_free(&gc);\n    }\n}\n\nstatic uint32_t\ndhcp_masq_addr(const in_addr_t local, const in_addr_t netmask, const int offset)\n{\n    struct gc_arena gc = gc_new();\n    in_addr_t dsa; /* DHCP server addr */\n\n    if (offset < 0)\n    {\n        dsa = (local | (~netmask)) + offset;\n    }\n    else\n    {\n        dsa = (local & netmask) + offset;\n    }\n\n    if (dsa == local)\n    {\n        msg(M_FATAL,\n            \"ERROR: There is a clash between the --ifconfig local address and the internal DHCP server address -- both are set to %s -- please use the --ip-win32 dynamic option to choose a different free address from the --ifconfig subnet for the internal DHCP server\",\n            print_in_addr_t(dsa, 0, &gc));\n    }\n\n    if ((local & netmask) != (dsa & netmask))\n    {\n        msg(M_FATAL, \"ERROR: --ip-win32 dynamic [offset] : offset is outside of --ifconfig subnet\");\n    }\n\n    gc_free(&gc);\n    return htonl(dsa);\n}\n\nstatic void\ntuntap_get_version_info(const struct tuntap *tt)\n{\n    ULONG info[3];\n    DWORD len;\n    CLEAR(info);\n    if (DeviceIoControl(tt->hand, TAP_WIN_IOCTL_GET_VERSION, &info, sizeof(info), &info,\n                        sizeof(info), &len, NULL))\n    {\n        msg(D_TUNTAP_INFO, \"TAP-Windows Driver Version %lu.%lu %s\", info[0], info[1],\n            (info[2] ? \"(DEBUG)\" : \"\"));\n    }\n    if (!(info[0] == TAP_WIN_MIN_MAJOR && info[1] >= TAP_WIN_MIN_MINOR))\n    {\n        msg(M_FATAL,\n            \"ERROR:  This version of \" PACKAGE_NAME\n            \" requires a TAP-Windows driver that is at least version %u.%u -- If you recently upgraded your \" PACKAGE_NAME\n            \" distribution, a reboot is probably required at this point to get Windows to see the new driver.\",\n            TAP_WIN_MIN_MAJOR, TAP_WIN_MIN_MINOR);\n    }\n\n    /* usage of numeric constants is ugly, but this is really tied to\n     * *this* version of the driver\n     */\n    if (tt->type == DEV_TYPE_TUN && info[0] == 9 && info[1] < 8)\n    {\n        msg(M_INFO,\n            \"WARNING:  Tap-Win32 driver version %lu.%lu does not support IPv6 in TUN mode. IPv6 will not work. Upgrade your Tap-Win32 driver.\",\n            info[0], info[1]);\n    }\n\n    /* tap driver 9.8 (2.2.0 and 2.2.1 release) is buggy\n     */\n    if (tt->type == DEV_TYPE_TUN && info[0] == 9 && info[1] == 8)\n    {\n        msg(M_FATAL,\n            \"ERROR:  Tap-Win32 driver version 9.8 is buggy regarding small IPv4 packets in TUN mode. Upgrade your Tap-Win32 driver.\");\n    }\n}\n\nstatic void\ntuntap_get_mtu(struct tuntap *tt)\n{\n    ULONG mtu = 0;\n    DWORD len;\n    if (DeviceIoControl(tt->hand, TAP_WIN_IOCTL_GET_MTU, &mtu, sizeof(mtu), &mtu, sizeof(mtu), &len,\n                        NULL))\n    {\n        msg(D_MTU_INFO, \"TAP-Windows MTU=%lu\", mtu);\n    }\n}\n\nstatic void\ntuntap_set_ip_addr(struct tuntap *tt, const char *device_guid, bool dhcp_masq_post)\n{\n    struct gc_arena gc = gc_new();\n    const DWORD index = tt->adapter_index;\n\n    /* flush arp cache */\n    if (tt->backend_driver == WINDOWS_DRIVER_TAP_WINDOWS6 && index != TUN_ADAPTER_INDEX_INVALID)\n    {\n        DWORD status = (DWORD)-1;\n\n        if (tt->options.msg_channel)\n        {\n            ack_message_t ack;\n            flush_neighbors_message_t msg = { .header = { msg_flush_neighbors,\n                                                          sizeof(flush_neighbors_message_t), 0 },\n                                              .family = AF_INET,\n                                              .iface = { .index = index, .name = \"\" } };\n\n            if (send_msg_iservice(tt->options.msg_channel, &msg, sizeof(msg), &ack, \"TUN\"))\n            {\n                status = ack.error_number;\n            }\n        }\n        else\n        {\n            status = FlushIpNetTable(index);\n        }\n\n        if (status == NO_ERROR)\n        {\n            msg(M_INFO, \"Successful ARP Flush on interface [%lu] %s\", index, device_guid);\n        }\n        else if (status != (DWORD)-1)\n        {\n            msg(D_TUNTAP_INFO,\n                \"NOTE: FlushIpNetTable failed on interface [%lu] %s (status=%lu) : %s\", index,\n                device_guid, status, strerror_win32(status, &gc));\n        }\n\n        /*\n         * If the TAP-Windows driver is masquerading as a DHCP server\n         * make sure the TCP/IP properties for the adapter are\n         * set correctly.\n         */\n        if (dhcp_masq_post)\n        {\n            /* check dhcp enable status */\n            if (dhcp_status(index) == DHCP_STATUS_DISABLED)\n            {\n                msg(M_WARN,\n                    \"WARNING: You have selected '--ip-win32 dynamic', which will not work unless the TAP-Windows TCP/IP properties are set to 'Obtain an IP address automatically'\");\n            }\n\n            /* force an explicit DHCP lease renewal on TAP adapter? */\n            if (tt->options.dhcp_pre_release)\n            {\n                dhcp_release(tt);\n            }\n            if (tt->options.dhcp_renew)\n            {\n                dhcp_renew(tt);\n            }\n        }\n        else\n        {\n            fork_dhcp_action(tt);\n        }\n    }\n\n    if (tt->did_ifconfig_setup && tt->options.ip_win32_type == IPW32_SET_IPAPI)\n    {\n        DWORD status;\n        const char *error_suffix =\n            \"I am having trouble using the Windows 'IP helper API' to automatically set the IP address -- consider using other --ip-win32 methods (not 'ipapi')\";\n\n        /* couldn't get adapter index */\n        if (index == TUN_ADAPTER_INDEX_INVALID)\n        {\n            msg(M_FATAL, \"ERROR: unable to get adapter index for interface %s -- %s\", device_guid,\n                error_suffix);\n        }\n\n        /* check dhcp enable status */\n        if (dhcp_status(index) == DHCP_STATUS_DISABLED)\n        {\n            msg(M_WARN,\n                \"NOTE: You have selected (explicitly or by default) '--ip-win32 ipapi', which has a better chance of working correctly if the TAP-Windows TCP/IP properties are set to 'Obtain an IP address automatically'\");\n        }\n\n        /* delete previously added IP addresses which were not\n         * correctly deleted */\n        delete_temp_addresses(index);\n\n        /* add a new IP address */\n        if ((status = AddIPAddress(htonl(tt->local), htonl(tt->adapter_netmask), index,\n                                   &tt->ipapi_context, &tt->ipapi_instance))\n            == NO_ERROR)\n        {\n            msg(M_INFO,\n                \"Succeeded in adding a temporary IP/netmask of %s/%s to interface %s using the Win32 IP Helper API\",\n                print_in_addr_t(tt->local, 0, &gc), print_in_addr_t(tt->adapter_netmask, 0, &gc),\n                device_guid);\n        }\n        else\n        {\n            msg(M_FATAL,\n                \"ERROR: AddIPAddress %s/%s failed on interface %s, index=%lu, status=%lu (windows error: '%s') -- %s\",\n                print_in_addr_t(tt->local, 0, &gc), print_in_addr_t(tt->adapter_netmask, 0, &gc),\n                device_guid, index, status, strerror_win32(status, &gc), error_suffix);\n        }\n        tt->ipapi_context_defined = true;\n    }\n\n    gc_free(&gc);\n}\n\nstatic void\ntuntap_set_connected(const struct tuntap *tt)\n{\n    ULONG status = TRUE;\n    DWORD len;\n    if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status,\n                         sizeof(status), &len, NULL))\n    {\n        msg(M_WARN,\n            \"WARNING: The TAP-Windows driver rejected a TAP_WIN_IOCTL_SET_MEDIA_STATUS DeviceIoControl call.\");\n    }\n\n    int s = tt->options.tap_sleep;\n    if (s > 0)\n    {\n        msg(M_INFO, \"Sleeping for %d seconds...\", s);\n        management_sleep(s);\n    }\n}\n\nstatic void\ntuntap_set_ptp(const struct tuntap *tt)\n{\n    DWORD len;\n    struct gc_arena gc = gc_new();\n\n    if (!tt->did_ifconfig_setup && !tt->did_ifconfig_ipv6_setup)\n    {\n        msg(M_FATAL, \"ERROR: --dev tun also requires --ifconfig\");\n    }\n\n    /* send 0/0/0 to the TAP driver even if we have no IPv4 configured to\n     * ensure it is somehow initialized.\n     */\n    if (!tt->did_ifconfig_setup || tt->topology == TOP_SUBNET)\n    {\n        in_addr_t ep[3];\n        BOOL status;\n\n        ep[0] = htonl(tt->local);\n        ep[1] = htonl(tt->local & tt->remote_netmask);\n        ep[2] = htonl(tt->remote_netmask);\n\n        status = DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_TUN, ep, sizeof(ep), ep, sizeof(ep),\n                                 &len, NULL);\n\n        if (tt->did_ifconfig_setup)\n        {\n            msg(status ? M_INFO : M_FATAL,\n                \"Set TAP-Windows TUN subnet mode network/local/netmask = %s/%s/%s [%s]\",\n                print_in_addr_t(ep[1], IA_NET_ORDER, &gc),\n                print_in_addr_t(ep[0], IA_NET_ORDER, &gc),\n                print_in_addr_t(ep[2], IA_NET_ORDER, &gc), status ? \"SUCCEEDED\" : \"FAILED\");\n        }\n        else\n        {\n            msg(status ? M_INFO : M_FATAL, \"Set TAP-Windows TUN with fake IPv4 [%s]\",\n                status ? \"SUCCEEDED\" : \"FAILED\");\n        }\n    }\n    else\n    {\n        in_addr_t ep[2];\n        ep[0] = htonl(tt->local);\n        ep[1] = htonl(tt->remote_netmask);\n\n        if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT, ep, sizeof(ep), ep,\n                             sizeof(ep), &len, NULL))\n        {\n            msg(M_FATAL,\n                \"ERROR: The TAP-Windows driver rejected a DeviceIoControl call to set Point-to-Point mode, which is required for --dev tun\");\n        }\n    }\n\n    gc_free(&gc);\n}\n\nstatic void\ntuntap_dhcp_mask(const struct tuntap *tt, const char *device_guid)\n{\n    struct gc_arena gc = gc_new();\n    DWORD len;\n    uint32_t ep[4];\n\n    /* We will answer DHCP requests with a reply to set IP/subnet to these values */\n    ep[0] = htonl(tt->local);\n    ep[1] = htonl(tt->adapter_netmask);\n\n    /* At what IP address should the DHCP server masquerade at? */\n    if (tt->type == DEV_TYPE_TUN)\n    {\n        if (tt->topology == TOP_SUBNET)\n        {\n            ep[2] = dhcp_masq_addr(\n                tt->local, tt->remote_netmask,\n                tt->options.dhcp_masq_custom_offset ? tt->options.dhcp_masq_offset : 0);\n        }\n        else\n        {\n            ep[2] = htonl(tt->remote_netmask);\n        }\n    }\n    else\n    {\n        ASSERT(tt->type == DEV_TYPE_TAP);\n        ep[2] =\n            dhcp_masq_addr(tt->local, tt->adapter_netmask,\n                           tt->options.dhcp_masq_custom_offset ? tt->options.dhcp_masq_offset : 0);\n    }\n\n    /* lease time in seconds */\n    ep[3] = (uint32_t)tt->options.dhcp_lease_time;\n\n    ASSERT(ep[3] > 0);\n\n#ifndef SIMULATE_DHCP_FAILED /* this code is disabled to simulate bad DHCP negotiation */\n    if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_DHCP_MASQ, ep, sizeof(ep), ep, sizeof(ep),\n                         &len, NULL))\n    {\n        msg(M_FATAL,\n            \"ERROR: The TAP-Windows driver rejected a DeviceIoControl call to set TAP_WIN_IOCTL_CONFIG_DHCP_MASQ mode\");\n    }\n\n    msg(M_INFO,\n        \"Notified TAP-Windows driver to set a DHCP IP/netmask of %s/%s on interface %s [DHCP-serv: %s, lease-time: %d]\",\n        print_in_addr_t(tt->local, 0, &gc), print_in_addr_t(tt->adapter_netmask, 0, &gc),\n        device_guid, print_in_addr_t(ep[2], IA_NET_ORDER, &gc), ep[3]);\n\n    /* user-supplied DHCP options capability */\n    if (tt->options.dhcp_options)\n    {\n        struct buffer buf = alloc_buf(256);\n        if (build_dhcp_options_string(&buf, &tt->options))\n        {\n            msg(D_DHCP_OPT, \"DHCP option string: %s\", format_hex(BPTR(&buf), BLEN(&buf), 0, &gc));\n            if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT, BPTR(&buf),\n                                 BLEN(&buf), BPTR(&buf), BLEN(&buf), &len, NULL))\n            {\n                msg(M_FATAL,\n                    \"ERROR: The TAP-Windows driver rejected a TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT DeviceIoControl call\");\n            }\n        }\n        else\n        {\n            msg(M_WARN, \"DHCP option string not set due to error\");\n        }\n        free_buf(&buf);\n    }\n#endif                       /* ifndef SIMULATE_DHCP_FAILED */\n\n    gc_free(&gc);\n}\n\nstatic bool\ntun_try_open_device(struct tuntap *tt, const char *device_guid,\n                    const struct device_instance_id_interface *device_instance_id_interface)\n{\n    const char *path = NULL;\n    char tuntap_device_path[256];\n\n    if (tt->backend_driver == DRIVER_DCO)\n    {\n        const struct device_instance_id_interface *dev_if;\n\n        for (dev_if = device_instance_id_interface; dev_if != NULL; dev_if = dev_if->next)\n        {\n            if (strcmp((const char *)dev_if->net_cfg_instance_id, device_guid) != 0)\n            {\n                continue;\n            }\n\n            if (tt->backend_driver == DRIVER_DCO)\n            {\n                char *last_sep = strrchr(dev_if->device_interface, '\\\\');\n                if (!last_sep || strcmp(last_sep + 1, DCO_WIN_REFERENCE_STRING) != 0)\n                {\n                    continue;\n                }\n            }\n\n            path = dev_if->device_interface;\n            break;\n        }\n        if (path == NULL)\n        {\n            return false;\n        }\n    }\n    else\n    {\n        /* Open TAP-Windows */\n        snprintf(tuntap_device_path, sizeof(tuntap_device_path), \"%s%s%s\", USERMODEDEVICEDIR,\n                 device_guid, TAP_WIN_SUFFIX);\n        path = tuntap_device_path;\n    }\n\n    msg(D_TAP_WIN_DEBUG, \"Using device interface: %s\", path);\n\n    tt->hand = CreateFile(path, GENERIC_READ | GENERIC_WRITE, 0, /* was: FILE_SHARE_READ */\n                          0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);\n    if (tt->hand == INVALID_HANDLE_VALUE)\n    {\n        msg(D_TUNTAP_INFO | M_ERRNO, \"CreateFile failed on %s device: %s\",\n            print_tun_backend_driver(tt->backend_driver), path);\n        return false;\n    }\n\n    return true;\n}\n\nvoid\ntun_open_device(struct tuntap *tt, const char *dev_node, const char **device_guid,\n                struct gc_arena *gc)\n{\n    const struct tap_reg *tap_reg = get_tap_reg(gc);\n    const struct panel_reg *panel_reg = get_panel_reg(gc);\n    const struct device_instance_id_interface *device_instance_id_interface =\n        get_device_instance_id_interface(gc);\n\n    uint8_t actual_buffer[256];\n\n    /*\n     * Lookup the device name in the registry, using the --dev-node high level name.\n     */\n    if (dev_node)\n    {\n        enum tun_driver_type windows_driver = WINDOWS_DRIVER_UNSPECIFIED;\n\n        /* Get the device GUID for the device specified with --dev-node. */\n        *device_guid = get_device_guid(dev_node, actual_buffer, sizeof(actual_buffer),\n                                       &windows_driver, tap_reg, panel_reg, gc);\n\n        if (!*device_guid)\n        {\n            msg(M_FATAL, \"Adapter '%s' not found\", dev_node);\n        }\n\n        if (tt->backend_driver != windows_driver)\n        {\n            msg(M_FATAL,\n                \"Adapter '%s' is using %s driver, %s expected.\",\n                dev_node, print_tun_backend_driver(windows_driver),\n                print_tun_backend_driver(tt->backend_driver));\n        }\n\n        if (!tun_try_open_device(tt, *device_guid, device_instance_id_interface))\n        {\n            msg(M_FATAL, \"Failed to open %s adapter: %s\",\n                print_tun_backend_driver(tt->backend_driver), dev_node);\n        }\n    }\n    else\n    {\n        int device_number = 0;\n        int adapters_created = 0;\n\n        /* Try opening all TAP devices until we find one available */\n        while (true)\n        {\n            enum tun_driver_type windows_driver = WINDOWS_DRIVER_UNSPECIFIED;\n            *device_guid =\n                get_unspecified_device_guid(device_number, actual_buffer, sizeof(actual_buffer),\n                                            tap_reg, panel_reg, &windows_driver, gc);\n\n            if (!*device_guid)\n            {\n                /* try to create an adapter a few times if we have a service pipe handle */\n                if ((++adapters_created > 10)\n                    || !do_create_adapter_service(tt->options.msg_channel, tt->backend_driver))\n                {\n                    msg(M_FATAL, \"All %s adapters on this system are currently in use or disabled.\",\n                        print_tun_backend_driver(tt->backend_driver));\n                }\n                else\n                {\n                    /* we have created a new adapter so we must reinitialize adapters structs */\n                    tap_reg = get_tap_reg(gc);\n                    panel_reg = get_panel_reg(gc);\n                    device_instance_id_interface = get_device_instance_id_interface(gc);\n\n                    device_number = 0;\n\n                    continue;\n                }\n            }\n\n            if (tt->backend_driver != windows_driver)\n            {\n                goto next;\n            }\n\n            if (tun_try_open_device(tt, *device_guid, device_instance_id_interface))\n            {\n                break;\n            }\n\nnext:\n            device_number++;\n        }\n    }\n\n    /* translate high-level device name into a device instance\n     * GUID using the registry */\n    tt->actual_name = string_alloc((const char *)actual_buffer, NULL);\n\n    tt->adapter_index = get_adapter_index(*device_guid);\n}\n\nstatic void\ntuntap_set_ip_props(const struct tuntap *tt, bool *dhcp_masq, bool *dhcp_masq_post)\n{\n    if (tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ)\n    {\n        /*\n         * If adapter is set to non-DHCP, set to DHCP mode.\n         */\n        if (dhcp_status(tt->adapter_index) == DHCP_STATUS_DISABLED)\n        {\n            /* try using the service if available, else directly execute netsh */\n            if (tt->options.msg_channel)\n            {\n                service_enable_dhcp(tt);\n            }\n            else\n            {\n                netsh_enable_dhcp(tt->adapter_index);\n            }\n        }\n        *dhcp_masq = true;\n        *dhcp_masq_post = true;\n    }\n    else if (tt->options.ip_win32_type == IPW32_SET_ADAPTIVE)\n    {\n        /*\n         * If adapter is set to non-DHCP, use netsh right away.\n         */\n        if (dhcp_status(tt->adapter_index) != DHCP_STATUS_ENABLED)\n        {\n            netsh_ifconfig(&tt->options, tt->adapter_index, tt->local, tt->adapter_netmask,\n                           NI_TEST_FIRST | NI_IP_NETMASK | NI_OPTIONS);\n        }\n        else\n        {\n            *dhcp_masq = true;\n        }\n    }\n}\n\nstatic void\ntuntap_post_open(struct tuntap *tt, const char *device_guid)\n{\n    bool dhcp_masq = false;\n    bool dhcp_masq_post = false;\n\n    if (tt->backend_driver == WINDOWS_DRIVER_TAP_WINDOWS6)\n    {\n        /* get driver version info */\n        tuntap_get_version_info(tt);\n\n        /* get driver MTU */\n        tuntap_get_mtu(tt);\n\n        /*\n         * Preliminaries for setting TAP-Windows adapter TCP/IP\n         * properties via --ip-win32 dynamic or --ip-win32 adaptive.\n         */\n        if (tt->did_ifconfig_setup)\n        {\n            tuntap_set_ip_props(tt, &dhcp_masq, &dhcp_masq_post);\n        }\n\n        /* set point-to-point mode if TUN device */\n        if (tt->type == DEV_TYPE_TUN)\n        {\n            tuntap_set_ptp(tt);\n        }\n\n        /* should we tell the TAP-Windows driver to masquerade as a DHCP server as a means\n         * of setting the adapter address? */\n        if (dhcp_masq)\n        {\n            tuntap_dhcp_mask(tt, device_guid);\n        }\n\n        /* set driver media status to 'connected' */\n        tuntap_set_connected(tt);\n    }\n\n    /* possibly use IP Helper API to set IP address on adapter */\n    tuntap_set_ip_addr(tt, device_guid, dhcp_masq_post);\n}\n\nvoid\nopen_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt,\n         openvpn_net_ctx_t *ctx)\n{\n    if ((tt->options.dhcp_options & DHCP_OPTIONS_DHCP_REQUIRED)\n        && tt->backend_driver != WINDOWS_DRIVER_TAP_WINDOWS6)\n    {\n        msg(M_WARN,\n            \"Some --dhcp-option or --dns options require DHCP server,\"\n            \" which is not supported by the selected %s driver. They will be\"\n            \" ignored.\",\n            print_tun_backend_driver(tt->backend_driver));\n    }\n\n    /* dco-win already opened the device, which handle we treat as socket */\n    if (tuntap_is_dco_win(tt))\n    {\n        return;\n    }\n\n    const char *device_guid = NULL;\n\n    /*netcmd_semaphore_lock ();*/\n\n    msg(M_INFO, \"open_tun\");\n\n    if (tt->type != DEV_TYPE_TAP && tt->type != DEV_TYPE_TUN)\n    {\n        msg(M_FATAL | M_NOPREFIX, \"Unknown virtual device type: '%s'\", dev);\n    }\n\n    struct gc_arena gc = gc_new(); /* used also for device_guid allocation */\n    tun_open_device(tt, dev_node, &device_guid, &gc);\n\n    tuntap_post_open(tt, device_guid);\n\n    gc_free(&gc);\n\n    /*netcmd_semaphore_release ();*/\n}\n\nconst char *\ntap_win_getinfo(const struct tuntap *tt, struct gc_arena *gc)\n{\n    if (tt->backend_driver == WINDOWS_DRIVER_TAP_WINDOWS6)\n    {\n        struct buffer out = alloc_buf_gc(256, gc);\n        DWORD len;\n        if (DeviceIoControl(tt->hand, TAP_WIN_IOCTL_GET_INFO, BSTR(&out), BCAP(&out), BSTR(&out),\n                            BCAP(&out), &len, NULL))\n        {\n            return BSTR(&out);\n        }\n    }\n    return NULL;\n}\n\nvoid\ntun_show_debug(struct tuntap *tt)\n{\n    if (tt->backend_driver == WINDOWS_DRIVER_TAP_WINDOWS6)\n    {\n        struct buffer out = alloc_buf(1024);\n        DWORD len;\n        while (DeviceIoControl(tt->hand, TAP_WIN_IOCTL_GET_LOG_LINE, BSTR(&out), BCAP(&out),\n                               BSTR(&out), BCAP(&out), &len, NULL))\n        {\n            msg(D_TAP_WIN_DEBUG, \"TAP-Windows: %s\", BSTR(&out));\n        }\n        free_buf(&out);\n    }\n}\n\nstatic void\nnetsh_delete_address_dns(const struct tuntap *tt, bool ipv6, struct gc_arena *gc)\n{\n    const char *ifconfig_ip_local;\n    struct argv argv = argv_new();\n\n    /* delete ipvX dns servers if any were set */\n    unsigned int len = ipv6 ? tt->options.dns6_len : tt->options.dns_len;\n    if (len > 0)\n    {\n        argv_printf(&argv, \"%s%s interface %s delete dns %lu all\", get_win_sys_path(),\n                    NETSH_PATH_SUFFIX, ipv6 ? \"ipv6\" : \"ipv4\", tt->adapter_index);\n        netsh_command(&argv, 1, M_WARN);\n    }\n\n    if (!ipv6 && tt->options.wins_len > 0)\n    {\n        argv_printf(&argv, \"%s%s interface ipv4 delete winsservers %lu all\", get_win_sys_path(),\n                    NETSH_PATH_SUFFIX, tt->adapter_index);\n        netsh_command(&argv, 1, M_WARN);\n    }\n\n    if (ipv6 && tt->type == DEV_TYPE_TUN)\n    {\n        delete_route_connected_v6_net(tt);\n    }\n\n    /* \"store=active\" is needed in Windows 8(.1) to delete the\n     * address we added (pointed out by Cedric Tabary).\n     */\n\n    /* netsh interface ipvX delete address %lu %s */\n    if (ipv6)\n    {\n        ifconfig_ip_local = print_in6_addr(tt->local_ipv6, 0, gc);\n    }\n    else\n    {\n        ifconfig_ip_local = print_in_addr_t(tt->local, 0, gc);\n    }\n    argv_printf(&argv, \"%s%s interface %s delete address %lu %s store=active\", get_win_sys_path(),\n                NETSH_PATH_SUFFIX, ipv6 ? \"ipv6\" : \"ipv4\", tt->adapter_index, ifconfig_ip_local);\n    netsh_command(&argv, 1, M_WARN);\n\n    argv_free(&argv);\n}\n\nvoid\nclose_tun_handle(struct tuntap *tt)\n{\n    const char *adaptertype = print_tun_backend_driver(tt->backend_driver);\n\n    if (tt->hand)\n    {\n        dmsg(D_WIN32_IO_LOW, \"Attempting CancelIO on %s adapter\", adaptertype);\n        if (!CancelIo(tt->hand))\n        {\n            msg(M_WARN | M_ERRNO, \"Warning: CancelIO failed on %s adapter\", adaptertype);\n        }\n    }\n\n    dmsg(D_WIN32_IO_LOW, \"Attempting close of overlapped read event on %s adapter\", adaptertype);\n    overlapped_io_close(&tt->reads);\n\n    dmsg(D_WIN32_IO_LOW, \"Attempting close of overlapped write event on %s adapter\", adaptertype);\n    overlapped_io_close(&tt->writes);\n\n    if (tt->hand)\n    {\n        dmsg(D_WIN32_IO_LOW, \"Attempting CloseHandle on %s adapter\", adaptertype);\n        if (!CloseHandle(tt->hand))\n        {\n            msg(M_WARN | M_ERRNO, \"Warning: CloseHandle failed on %s adapter\", adaptertype);\n        }\n        tt->hand = NULL;\n    }\n}\n\nvoid\nclose_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    ASSERT(tt);\n\n    struct gc_arena gc = gc_new();\n\n    if (tt->did_ifconfig_ipv6_setup)\n    {\n        if (tt->options.ip_win32_type == IPW32_SET_MANUAL)\n        {\n            /* We didn't do ifconfig. */\n        }\n        else if (tt->options.msg_channel)\n        {\n            /* If IPv4 is not enabled, delete DNS domain here */\n            if (!tt->did_ifconfig_setup)\n            {\n                do_dns_domain_service(false, tt);\n            }\n            do_dns_service(false, AF_INET6, tt);\n            delete_route_connected_v6_net(tt);\n            do_address_service(false, AF_INET6, tt);\n        }\n        else\n        {\n            if (!tt->did_ifconfig_setup)\n            {\n                do_dns_domain_pwsh(false, tt);\n            }\n\n            netsh_delete_address_dns(tt, true, &gc);\n        }\n    }\n\n    if (tt->did_ifconfig_setup)\n    {\n        if (tt->options.ip_win32_type == IPW32_SET_MANUAL)\n        {\n            /* We didn't do ifconfig. */\n        }\n        else if (tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ\n                 || tt->options.ip_win32_type == IPW32_SET_ADAPTIVE)\n        {\n            /* We don't have to clean the configuration with DHCP. */\n        }\n        else if (tt->options.msg_channel)\n        {\n            do_wins_service(false, tt);\n            do_dns_domain_service(false, tt);\n            do_dns_service(false, AF_INET, tt);\n            do_address_service(false, AF_INET, tt);\n        }\n        else\n        {\n            do_dns_domain_pwsh(false, tt);\n\n            if (tt->options.ip_win32_type == IPW32_SET_NETSH)\n            {\n                netsh_delete_address_dns(tt, false, &gc);\n            }\n        }\n    }\n\n    if (tt->ipapi_context_defined)\n    {\n        DWORD status;\n\n        if ((status = DeleteIPAddress(tt->ipapi_context)) != NO_ERROR)\n        {\n            msg(M_WARN,\n                \"Warning: DeleteIPAddress[%lu] failed on TAP-Windows adapter, status=%lu : %s\",\n                tt->ipapi_context, status, strerror_win32(status, &gc));\n        }\n    }\n\n    dhcp_release(tt);\n\n    close_tun_handle(tt);\n\n    free(tt->actual_name);\n\n    clear_tuntap(tt);\n    free(tt);\n    gc_free(&gc);\n}\n\n/*\n * Convert --ip-win32 constants between index and ascii form.\n */\n\nstruct ipset_names\n{\n    const char *short_form;\n};\n\n/* Indexed by IPW32_SET_x */\nstatic const struct ipset_names ipset_names[] = {\n    { \"manual\" }, { \"netsh\" }, { \"ipapi\" }, { \"dynamic\" }, { \"adaptive\" }\n};\n\nint\nascii2ipset(const char *name)\n{\n    int i;\n    ASSERT(IPW32_SET_N == SIZE(ipset_names));\n    for (i = 0; i < IPW32_SET_N; ++i)\n    {\n        if (!strcmp(name, ipset_names[i].short_form))\n        {\n            return i;\n        }\n    }\n    return -1;\n}\n\nconst char *\nipset2ascii(int index)\n{\n    ASSERT(IPW32_SET_N == SIZE(ipset_names));\n    if (index < 0 || index >= IPW32_SET_N)\n    {\n        return \"[unknown --ip-win32 type]\";\n    }\n    else\n    {\n        return ipset_names[index].short_form;\n    }\n}\n\nconst char *\nipset2ascii_all(struct gc_arena *gc)\n{\n    struct buffer out = alloc_buf_gc(256, gc);\n    int i;\n\n    ASSERT(IPW32_SET_N == SIZE(ipset_names));\n    for (i = 0; i < IPW32_SET_N; ++i)\n    {\n        if (i)\n        {\n            buf_printf(&out, \" \");\n        }\n        buf_printf(&out, \"[%s]\", ipset2ascii(i));\n    }\n    return BSTR(&out);\n}\n\n#else                        /* generic */\n\nvoid\nopen_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt,\n         openvpn_net_ctx_t *ctx)\n{\n    open_tun_generic(dev, dev_type, dev_node, tt);\n}\n\nvoid\nclose_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)\n{\n    ASSERT(tt);\n\n    close_tun_generic(tt);\n    free(tt);\n}\n\nssize_t\nwrite_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    return write(tt->fd, buf, len);\n}\n\nssize_t\nread_tun(struct tuntap *tt, uint8_t *buf, int len)\n{\n    return read(tt->fd, buf, len);\n}\n\n#endif                       /* if defined (TARGET_ANDROID) */\n"
  },
  {
    "path": "src/openvpn/tun.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef TUN_H\n#define TUN_H\n\n#ifdef _WIN32\n#include <winioctl.h>\n#include <tap-windows.h>\n#include <setupapi.h>\n#include <cfgmgr32.h>\n#endif\n\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"mtu.h\"\n#include \"win32.h\"\n#include \"event.h\"\n#include \"proto.h\"\n#include \"misc.h\"\n#include \"networking.h\"\n#include \"dco.h\"\n\nenum tun_driver_type\n{\n    WINDOWS_DRIVER_UNSPECIFIED,\n    WINDOWS_DRIVER_TAP_WINDOWS6,\n    DRIVER_GENERIC_TUNTAP,\n    /** using an AF_UNIX socket to pass packets from/to an external program.\n     *  This is always defined. We error out if a user tries to open this type\n     *  of backend on unsupported platforms. */\n    DRIVER_AFUNIX,\n    DRIVER_NULL,\n    DRIVER_DCO,\n    /** macOS internal tun driver */\n    DRIVER_UTUN\n};\n\n#ifdef _WIN32\n#define DCO_WIN_REFERENCE_STRING \"ovpn-dco\"\n#endif\n\n#if defined(_WIN32) || defined(TARGET_ANDROID) || defined(DHCP_UNIT_TEST)\n\n/* time constants for --ip-win32 adaptive */\n#define IPW32_SET_ADAPTIVE_DELAY_WINDOW 300\n#define IPW32_SET_ADAPTIVE_TRY_NETSH    20\n\n/* bit flags for DHCP options */\n#define DHCP_OPTIONS_DHCP_OPTIONAL (1 << 0)\n#define DHCP_OPTIONS_DHCP_REQUIRED (1 << 1)\n\nstruct tuntap_options\n{\n    /* --ip-win32 options */\n    bool ip_win32_defined;\n\n#define IPW32_SET_MANUAL    0 /* \"--ip-win32 manual\" */\n#define IPW32_SET_NETSH     1 /* \"--ip-win32 netsh\" */\n#define IPW32_SET_IPAPI     2 /* \"--ip-win32 ipapi\" */\n#define IPW32_SET_DHCP_MASQ 3 /* \"--ip-win32 dynamic\" */\n#define IPW32_SET_ADAPTIVE  4 /* \"--ip-win32 adaptive\" */\n#define IPW32_SET_N         5\n    int ip_win32_type;\n\n#ifdef _WIN32\n    HANDLE msg_channel;\n#endif\n\n    /* --ip-win32 dynamic options */\n    bool dhcp_masq_custom_offset;\n    int dhcp_masq_offset;\n    int dhcp_lease_time;\n\n    /* --tap-sleep option */\n    int tap_sleep;\n\n    /* --dhcp-option options */\n\n    int dhcp_options;\n\n    const char *domain;        /* DOMAIN (15) */\n\n    const char *netbios_scope; /* NBS (47) */\n\n    uint8_t netbios_node_type; /* NBT 1,2,4,8 (46) */\n\n/* Max # of addresses allowed for  DNS, WINS, etc. */\n#define N_DHCP_ADDR 4\n\n    /* DNS (6) */\n    in_addr_t dns[N_DHCP_ADDR];\n    unsigned int dns_len;\n\n    /* WINS (44) */\n    in_addr_t wins[N_DHCP_ADDR];\n    unsigned int wins_len;\n\n    /* NTP (42) */\n    in_addr_t ntp[N_DHCP_ADDR];\n    unsigned int ntp_len;\n\n    /* NBDD (45) */\n    in_addr_t nbdd[N_DHCP_ADDR];\n    unsigned int nbdd_len;\n\n#define N_SEARCH_LIST_LEN 10 /* Max # of entries in domin-search list */\n\n    /* SEARCH (119), MacOS, Linux, Win10 1809+ */\n    const char *domain_search_list[N_SEARCH_LIST_LEN];\n    unsigned int domain_search_list_len;\n\n    /* DISABLE_NBT (43, Vendor option 001) */\n    bool disable_nbt;\n\n    bool dhcp_renew;\n    bool dhcp_pre_release;\n\n    bool register_dns;\n\n    struct in6_addr dns6[N_DHCP_ADDR];\n    unsigned int dns6_len;\n#if defined(TARGET_ANDROID)\n    const char *http_proxy;\n    int http_proxy_port;\n#endif\n};\n\n#elif defined(TARGET_LINUX)\n\nstruct tuntap_options\n{\n    int txqueuelen;\n};\n\n#else  /* if defined(_WIN32) || defined(TARGET_ANDROID) */\n\nstruct tuntap_options\n{\n    int dummy; /* not used */\n};\n\n#endif /* if defined(_WIN32) || defined(TARGET_ANDROID) */\n\n/*\n * Define a TUN/TAP dev.\n */\n#ifndef WIN32\ntypedef struct afunix_context\n{\n    pid_t childprocess;\n} afunix_context_t;\n\n#else /* ifndef WIN32 */\ntypedef struct\n{\n    int dummy;\n} afunix_context_t;\n#endif\n\nstruct tuntap\n{\n#define TUNNEL_TYPE(tt) ((tt) ? ((tt)->type) : DEV_TYPE_UNDEF)\n    int type; /* DEV_TYPE_x as defined in proto.h */\n\n#define TUNNEL_TOPOLOGY(tt) ((tt) ? ((tt)->topology) : TOP_UNDEF)\n    int topology; /* one of the TOP_x values */\n\n    /** The backend driver that used for this tun/tap device. This can be\n     * one of the various windows drivers, \"normal\" tun/tap, utun, dco, ...\n     */\n    enum tun_driver_type backend_driver;\n\n    /** if the internal variables related to ifconfig of this struct have\n     * been set up. This does NOT mean ifconfig has been called */\n    bool did_ifconfig_setup;\n\n    /** if the internal variables related to ifconfig-ipv6 of this struct have\n     * been set up. This does NOT mean ifconfig has been called */\n    bool did_ifconfig_ipv6_setup;\n\n    bool persistent_if;            /* if existed before, keep on program end */\n\n    struct tuntap_options options; /* options set on command line */\n\n    char *actual_name;             /* actual name of TUN/TAP dev, usually including unit number */\n\n    /* ifconfig parameters */\n    in_addr_t local;\n    in_addr_t remote_netmask;\n\n    struct in6_addr local_ipv6;\n    struct in6_addr remote_ipv6;\n    int netbits_ipv6;\n\n#ifdef _WIN32\n    HANDLE hand;\n    /* used for async NEW_PEER dco call, which might wait for TCP connect */\n    OVERLAPPED dco_new_peer_ov;\n    struct overlapped_io reads;\n    struct overlapped_io writes;\n    struct rw_handle rw_handle;\n\n    /* used for setting interface address via IP Helper API\n     * or DHCP masquerade */\n    bool ipapi_context_defined;\n    ULONG ipapi_context;\n    ULONG ipapi_instance;\n    in_addr_t adapter_netmask;\n\n    /* Windows adapter index for TAP-Windows adapter,\n     * TUN_ADAPTER_INDEX_INVALID if undefined */\n    DWORD adapter_index;\n\n    int standby_iter;\n\n#else  /* ifdef _WIN32 */\n    int fd; /* file descriptor for TUN/TAP dev */\n#endif /* ifdef _WIN32 */\n\n#ifdef TARGET_SOLARIS\n    int ip_fd;\n#endif\n\n    /* used for printing status info only */\n    unsigned int rwflags_debug;\n\n    dco_context_t dco;\n    afunix_context_t afunix;\n};\n\nstatic inline bool\ntuntap_defined(const struct tuntap *tt)\n{\n#ifdef _WIN32\n    return tt && tt->hand != NULL;\n#else\n    return tt && tt->fd >= 0;\n#endif\n}\n\n/*\n * Function prototypes\n */\n\nvoid open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt,\n              openvpn_net_ctx_t *ctx);\n\nvoid close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx);\n\nvoid tun_open_device(struct tuntap *tt, const char *dev_node, const char **device_guid,\n                     struct gc_arena *gc);\n\nvoid close_tun_handle(struct tuntap *tt);\n\nssize_t write_tun(struct tuntap *tt, uint8_t *buf, int len);\n\nssize_t read_tun(struct tuntap *tt, uint8_t *buf, int len);\n\n#ifdef ENABLE_FEATURE_TUN_PERSIST\nvoid tuncfg(const char *dev, const char *dev_type, const char *dev_node, int persist_mode,\n            const char *username, const char *groupname, const struct tuntap_options *options,\n            openvpn_net_ctx_t *ctx);\n#endif\n\nconst char *guess_tuntap_dev(const char *dev, const char *dev_type, const char *dev_node,\n                             struct gc_arena *gc);\n\nstruct tuntap *init_tun(const char *dev,                          /* --dev option */\n                        const char *dev_type,                     /* --dev-type option */\n                        int topology,                             /* one of the TOP_x values */\n                        const char *ifconfig_local_parm,          /* --ifconfig parm 1 */\n                        const char *ifconfig_remote_netmask_parm, /* --ifconfig parm 2 */\n                        const char *ifconfig_ipv6_local_parm,     /* --ifconfig parm 1 / IPv6 */\n                        int ifconfig_ipv6_netbits_parm,           /* --ifconfig parm 1 / bits */\n                        const char *ifconfig_ipv6_remote_parm,    /* --ifconfig parm 2 / IPv6 */\n                        struct addrinfo *local_public, struct addrinfo *remote_public,\n                        const bool strict_warn, struct env_set *es, openvpn_net_ctx_t *ctx,\n                        struct tuntap *tt);\n\nvoid init_tun_post(struct tuntap *tt, const struct frame *frame,\n                   const struct tuntap_options *options);\n\nvoid do_ifconfig_setenv(const struct tuntap *tt, struct env_set *es);\n\n/**\n * do_ifconfig - configure the tunnel interface\n *\n * @param tt        the tuntap interface context\n * @param ifname    the human readable interface name\n * @param tun_mtu   the MTU value to set the interface to\n * @param es        the environment to be used when executing the commands\n * @param ctx       the networking API opaque context\n */\nvoid do_ifconfig(struct tuntap *tt, const char *ifname, int tun_mtu, const struct env_set *es,\n                 openvpn_net_ctx_t *ctx);\n\n/**\n * undo_ifconfig - undo configuration of the tunnel interface\n *\n * @param tt    the tuntap interface context\n * @param ctx   the networking API opaque context\n */\nvoid undo_ifconfig(struct tuntap *tt, openvpn_net_ctx_t *ctx);\n\nbool is_dev_type(const char *dev, const char *dev_type, const char *match_type);\n\nint dev_type_enum(const char *dev, const char *dev_type);\n\nconst char *dev_type_string(const char *dev, const char *dev_type);\n\nconst char *ifconfig_options_string(const struct tuntap *tt, bool remote, bool disable,\n                                    struct gc_arena *gc);\n\nbool is_tun_p2p(const struct tuntap *tt);\n\nvoid warn_on_use_of_common_subnets(openvpn_net_ctx_t *ctx);\n\n/**\n * Return a string representation of the tun backed driver type\n */\nconst char *print_tun_backend_driver(enum tun_driver_type driver);\n\n/*\n * Should ifconfig be called before or after\n * tun dev open?\n */\n\n#define IFCONFIG_BEFORE_TUN_OPEN 0\n#define IFCONFIG_AFTER_TUN_OPEN  1\n\n#define IFCONFIG_DEFAULT IFCONFIG_AFTER_TUN_OPEN\n\nstatic inline int\nifconfig_order(struct tuntap *tt)\n{\n    if (tt->backend_driver == DRIVER_AFUNIX)\n    {\n        return IFCONFIG_BEFORE_TUN_OPEN;\n    }\n#if defined(TARGET_LINUX)\n    return IFCONFIG_AFTER_TUN_OPEN;\n#elif defined(TARGET_SOLARIS)\n    return IFCONFIG_AFTER_TUN_OPEN;\n#elif defined(TARGET_OPENBSD)\n    return IFCONFIG_AFTER_TUN_OPEN;\n#elif defined(TARGET_DARWIN)\n    return IFCONFIG_AFTER_TUN_OPEN;\n#elif defined(TARGET_NETBSD)\n    return IFCONFIG_AFTER_TUN_OPEN;\n#elif defined(_WIN32)\n    return IFCONFIG_AFTER_TUN_OPEN;\n#elif defined(TARGET_ANDROID)\n    return IFCONFIG_BEFORE_TUN_OPEN;\n#else /* if defined(TARGET_LINUX) */\n    return IFCONFIG_DEFAULT;\n#endif\n}\n\n#define ROUTE_BEFORE_TUN    0\n#define ROUTE_AFTER_TUN     1\n#define ROUTE_ORDER_DEFAULT ROUTE_AFTER_TUN\n\nstatic inline int\nroute_order(struct tuntap *tt)\n{\n    if (tt->backend_driver == DRIVER_AFUNIX)\n    {\n        return ROUTE_BEFORE_TUN;\n    }\n#if defined(TARGET_ANDROID)\n    return ROUTE_BEFORE_TUN;\n#else\n    return ROUTE_ORDER_DEFAULT;\n#endif\n}\n\n\n#ifdef _WIN32\n\nstruct tap_reg\n{\n    const char *guid;\n    enum tun_driver_type windows_driver;\n    struct tap_reg *next;\n};\n\nstruct panel_reg\n{\n    const char *name;\n    const char *guid;\n    struct panel_reg *next;\n};\n\nstruct device_instance_id_interface\n{\n    LPBYTE net_cfg_instance_id;\n    const char *device_interface;\n    struct device_instance_id_interface *next;\n};\n\nint ascii2ipset(const char *name);\n\nconst char *ipset2ascii(int index);\n\nconst char *ipset2ascii_all(struct gc_arena *gc);\n\nvoid verify_255_255_255_252(in_addr_t local, in_addr_t remote);\n\nconst IP_ADAPTER_INFO *get_adapter_info_list(struct gc_arena *gc);\n\nconst IP_ADAPTER_INFO *get_tun_adapter(const struct tuntap *tt, const IP_ADAPTER_INFO *list);\n\nconst IP_ADAPTER_INFO *get_adapter_info(DWORD index, struct gc_arena *gc);\n\nconst IP_PER_ADAPTER_INFO *get_per_adapter_info(const DWORD index, struct gc_arena *gc);\n\nconst IP_ADAPTER_INFO *get_adapter(const IP_ADAPTER_INFO *ai, DWORD index);\n\nbool is_adapter_up(const struct tuntap *tt, const IP_ADAPTER_INFO *list);\n\nbool is_ip_in_adapter_subnet(const IP_ADAPTER_INFO *ai, const in_addr_t ip,\n                             in_addr_t *highest_netmask);\n\nDWORD adapter_index_of_ip(const IP_ADAPTER_INFO *list, const in_addr_t ip, int *count,\n                          in_addr_t *netmask);\n\nvoid show_tap_win_adapters(msglvl_t msglevel, msglvl_t warnlevel);\n\nvoid show_adapters(msglvl_t msglevel);\n\nvoid tap_allow_nonadmin_access(const char *dev_node);\n\nvoid show_valid_win32_tun_subnets(void);\n\nconst char *tap_win_getinfo(const struct tuntap *tt, struct gc_arena *gc);\n\nvoid tun_show_debug(struct tuntap *tt);\n\nbool dhcp_release_by_adapter_index(const DWORD adapter_index);\n\nbool dhcp_renew_by_adapter_index(const DWORD adapter_index);\n\nvoid fork_register_dns_action(struct tuntap *tt);\n\nvoid ipconfig_register_dns(const struct env_set *es);\n\nvoid tun_standby_init(struct tuntap *tt);\n\nbool tun_standby(struct tuntap *tt);\n\nint tun_read_queue(struct tuntap *tt, int maxsize);\n\nint tun_write_queue(struct tuntap *tt, struct buffer *buf);\n\nstatic inline bool\ntuntap_stop(int status)\n{\n    /*\n     * This corresponds to the STATUS_NO_SUCH_DEVICE\n     * error in tapdrvr.c.\n     */\n    if (status < 0)\n    {\n        return GetLastError() == ERROR_FILE_NOT_FOUND;\n    }\n    return false;\n}\n\nstatic inline bool\ntuntap_abort(int status)\n{\n    /*\n     * Typically generated when driver is halted.\n     */\n    if (status < 0)\n    {\n        return GetLastError() == ERROR_OPERATION_ABORTED;\n    }\n    return false;\n}\n\nint tun_write_win32(struct tuntap *tt, struct buffer *buf);\n\nstatic inline bool\nis_ip_packet_valid(const struct buffer *buf)\n{\n    const struct openvpn_iphdr *ih = (const struct openvpn_iphdr *)BPTR(buf);\n\n    if (OPENVPN_IPH_GET_VER(ih->version_len) == 4)\n    {\n        if (BLENZ(buf) < sizeof(struct openvpn_iphdr))\n        {\n            return false;\n        }\n    }\n    else if (OPENVPN_IPH_GET_VER(ih->version_len) == 6)\n    {\n        if (BLENZ(buf) < sizeof(struct openvpn_ipv6hdr))\n        {\n            return false;\n        }\n    }\n    else\n    {\n        return false;\n    }\n\n    return true;\n}\n\nstatic inline bool\ntuntap_is_dco_win(struct tuntap *tt)\n{\n    return tt && tt->backend_driver == DRIVER_DCO;\n}\n\nstatic inline bool\ntuntap_is_dco_win_timeout(struct tuntap *tt, int status)\n{\n    return tuntap_is_dco_win(tt) && (status < 0) && (openvpn_errno() == ERROR_NETNAME_DELETED);\n}\n\n#else  /* ifdef _WIN32 */\n\nstatic inline bool\ntuntap_stop(int status)\n{\n    return false;\n}\n\nstatic inline bool\ntuntap_abort(int status)\n{\n    return false;\n}\n\nstatic inline void\ntun_standby_init(struct tuntap *tt)\n{\n}\n\nstatic inline bool\ntun_standby(struct tuntap *tt)\n{\n    return true;\n}\n\n\nstatic inline bool\ntuntap_is_dco_win(struct tuntap *tt)\n{\n    return false;\n}\n\nstatic inline bool\ntuntap_is_dco_win_timeout(struct tuntap *tt, int status)\n{\n    return false;\n}\n\n#endif /* ifdef _WIN32 */\n\n/*\n * TUN/TAP I/O wait functions\n */\n\nstatic inline event_t\ntun_event_handle(const struct tuntap *tt)\n{\n#ifdef _WIN32\n    return &tt->rw_handle;\n#else\n    return tt->fd;\n#endif\n}\n\nstatic inline void\ntun_set(struct tuntap *tt, struct event_set *es, unsigned int rwflags, void *arg,\n        unsigned int *persistent)\n{\n    if (!tuntap_defined(tt) || tuntap_is_dco_win(tt))\n    {\n        return;\n    }\n\n    /* if persistent is defined, call event_ctl only if rwflags has changed since last call */\n    if (!persistent || *persistent != rwflags)\n    {\n        event_ctl(es, tun_event_handle(tt), rwflags, arg);\n        if (persistent)\n        {\n            *persistent = rwflags;\n        }\n    }\n#ifdef _WIN32\n    if (tt->backend_driver == WINDOWS_DRIVER_TAP_WINDOWS6 && (rwflags & EVENT_READ))\n    {\n        tun_read_queue(tt, 0);\n    }\n#endif\n    tt->rwflags_debug = rwflags;\n}\n\nconst char *tun_stat(const struct tuntap *tt, unsigned int rwflags, struct gc_arena *gc);\nbool tun_name_is_fixed(const char *dev);\n\nstatic inline bool\nis_tun_type_set(const struct tuntap *tt)\n{\n    return tt && tt->type != DEV_TYPE_UNDEF;\n}\n\nstatic inline void\nopen_tun_null(struct tuntap *tt)\n{\n    tt->actual_name = string_alloc(\"null\", NULL);\n}\n#endif /* TUN_H */\n"
  },
  {
    "path": "src/openvpn/tun_afunix.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"tun.h\"\n#include \"fdmisc.h\"\n#include \"run_command.h\"\n#include \"manage.h\"\n#include \"win32.h\"\n#include \"wfp_block.h\"\n#include \"argv.h\"\n#include \"options.h\"\n#include \"socket.h\"\n\n#ifndef WIN32\n/* Windows does implement some AF_UNIX functionality but key features\n * like socketpair() and SOCK_DGRAM are missing */\n\n#include <string.h>\n#include <unistd.h>\n#include <sys/wait.h>\n#include <signal.h>\n#include <stdlib.h>\n\n\nstatic void\ntun_afunix_exec_child(const char *dev_node, struct tuntap *tt, struct env_set *env)\n{\n    const char *msgprefix = \"ERROR: failure executing process for tun:\";\n    struct argv argv = argv_new();\n\n    /* we should always called with a proper unix: dev node string */\n    ASSERT(dev_node && strncmp(dev_node, \"unix:\", strlen(\"unix:\")) == 0);\n    /* since we know that dev-node starts with unix: we can just skip that\n     * to get the program name */\n    const char *program = dev_node + strlen(\"unix:\");\n\n    argv_printf(&argv, \"%s\", program);\n\n    /* exit when executing fails to easier spot errors here and treat this\n     * command like an external script */\n    int flags = S_NOWAITPID | S_SCRIPT | S_FATAL;\n    tt->afunix.childprocess = openvpn_execve_check(&argv, env, flags, msgprefix);\n    if (!openvpn_waitpid_check(tt->afunix.childprocess, msgprefix, M_WARN))\n    {\n        tt->afunix.childprocess = 0;\n    }\n    argv_free(&argv);\n}\n\nvoid\nopen_tun_afunix(struct options *o, int mtu, struct tuntap *tt, struct env_set *orig_env)\n{\n    struct gc_arena gc = gc_new();\n\n    int fds[2];\n    if (!(socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) == 0))\n    {\n        msg(M_ERR, \"Cannot create socket pair for AF_UNIX socket to external \"\n                   \"program\");\n        return;\n    }\n\n\n    /* Ensure that the buffer sizes are decently sized. Otherwise macOS will\n     * just have 2048 */\n    struct socket_buffer_size newsizes = { 65536, 65536 };\n    socket_set_buffers(fds[0], &newsizes, false);\n    socket_set_buffers(fds[1], &newsizes, false);\n\n    /* Use the first file descriptor for our side and avoid passing it\n     * to the child */\n    tt->fd = fds[1];\n    set_cloexec(tt->fd);\n\n    /* Make a copy of the env, so we do not need to delete our custom\n     * environment variables later */\n    struct env_set *env = env_set_create(&gc);\n    env_set_inherit(env, orig_env);\n\n    setenv_int(env, \"TUNTAP_SOCKET_FD\", fds[0]);\n    setenv_str(env, \"TUNTAP_DEV_TYPE\", dev_type_string(o->dev, o->dev_type));\n    setenv_int(env, \"TUNTAP_MTU\", mtu);\n    if (o->route_default_gateway)\n    {\n        setenv_str(env, \"ifconfig_gateway\", o->route_default_gateway);\n    }\n    if (o->lladdr)\n    {\n        setenv_str(env, \"TUNTAP_LLADDR\", o->lladdr);\n    }\n\n    tun_afunix_exec_child(o->dev_node, tt, env);\n\n    close(fds[0]);\n\n    /* tt->actual_name is passed to up and down scripts and used as the ifconfig dev name */\n    tt->actual_name = string_alloc(\"internal:af_unix\", NULL);\n\n    gc_free(&gc);\n}\n\nvoid\nclose_tun_afunix(struct tuntap *tt)\n{\n    ASSERT(tt);\n    if (tt->fd >= 0)\n    {\n        close(tt->fd);\n        tt->fd = 0;\n    }\n    /* only kill the child process if the PID is not 0 to avoid killing\n     * ourselves by accident */\n    if (tt->afunix.childprocess)\n    {\n        kill(tt->afunix.childprocess, SIGINT);\n    }\n\n    free(tt->actual_name);\n    free(tt);\n}\n\nssize_t\nwrite_tun_afunix(struct tuntap *tt, uint8_t *buf, int len)\n{\n    const char *msg = \"ERROR: failure during write to AF_UNIX socket: \";\n    if (!openvpn_waitpid_check(tt->afunix.childprocess, msg, M_WARN))\n    {\n        tt->afunix.childprocess = 0;\n        return -ENXIO;\n    }\n\n    return write(tt->fd, buf, len);\n}\n\nssize_t\nread_tun_afunix(struct tuntap *tt, uint8_t *buf, int len)\n{\n    const char *msg = \"ERROR: failure during read from AF_UNIX socket: \";\n    if (!openvpn_waitpid_check(tt->afunix.childprocess, msg, M_WARN))\n    {\n        tt->afunix.childprocess = 0;\n    }\n    /* do an actual read on the file descriptor even in the error case since\n     * we otherwise loop on this on this from select and spam the console\n     * with error messages */\n    return read(tt->fd, buf, len);\n}\n#else  /* ifndef WIN32 */\nvoid\nopen_tun_afunix(const char *dev, const char *dev_type, int mtu, struct tuntap *tt,\n                struct env_set env)\n{\n    msg(M_ERR, \"AF_UNIX socket support not available on this platform\");\n}\n\nvoid\nclose_tun_afunix(struct tuntap *tt)\n{\n    /* should never be called as open_tun_afunix always fails */\n    ASSERT(0);\n}\n\nssize_t\nwrite_tun_afunix(struct tuntap *tt, uint8_t *buf, int len)\n{\n    /* should never be called as open_tun_afunix always fails */\n    ASSERT(0);\n}\n\nssize_t\nread_tun_afunix(struct tuntap *tt, uint8_t *buf, int len)\n{\n    /* should never be called as open_tun_afunix always fails */\n    ASSERT(0);\n}\n\n#endif /* ifndef WIN32 */\n"
  },
  {
    "path": "src/openvpn/tun_afunix.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef AFUNIX_TUN_H\n#define AFUNIX_TUN_H\n#include <sys/types.h>\n\n#include \"tun.h\"\n\n/**\n * Opens an AF_UNIX based tun device. This also executes the command that\n * the user provided taking care of implementing the actual tun\n * device.\n */\nvoid open_tun_afunix(struct options *o, int mtu, struct tuntap *tt, struct env_set *env);\n\n\n/**\n * Closes the socket used for the AF_UNIX based device. Also sends a\n * SIGINT to the child process that was spawned to handle the tun device\n */\nvoid close_tun_afunix(struct tuntap *tt);\n\n/**\n * Writes a packet to a AF_UNIX based tun device.\n */\nssize_t write_tun_afunix(struct tuntap *tt, uint8_t *buf, int len);\n\n/**\n * Reads a packet from a AF_UNIX based tun device.\n */\nssize_t read_tun_afunix(struct tuntap *tt, uint8_t *buf, int len);\n\n#endif /* AFUNIX_TUN_H */\n\n/**\n * Checks whether a --dev-node parameter specifies a AF_UNIX device\n * @param devnode   the string to check\n * @return          true if the string starts with unix:\n */\nstatic inline bool\nis_tun_afunix(const char *devnode)\n{\n    return devnode && strprefix(devnode, \"unix:\");\n}\n"
  },
  {
    "path": "src/openvpn/vlan.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Technologies, Inc. <sales@openvpn.net>\n *  Copyright (C) 2010      Fabian Knittel <fabian.knittel@lettink.de>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"multi.h\"\n#include \"options.h\"\n#include \"vlan.h\"\n\n/*\n * Retrieve the VLAN Identifier (VID) from the IEEE 802.1Q header.\n *\n * @param hdr Pointer to the Ethernet header with IEEE 802.1Q tagging.\n * @return    Returns the VID in host byte order.\n */\nstatic uint16_t\nvlanhdr_get_vid(const struct openvpn_8021qhdr *hdr)\n{\n    return ntohs(hdr->pcp_cfi_vid & OPENVPN_8021Q_MASK_VID);\n}\n\n/*\n * Set the VLAN Identifier (VID) in an IEEE 802.1Q header.\n *\n * @param hdr Pointer to the Ethernet header with IEEE 802.1Q tagging.\n * @param vid The VID to set (in host byte order).\n */\nstatic void\nvlanhdr_set_vid(struct openvpn_8021qhdr *hdr, const uint16_t vid)\n{\n    hdr->pcp_cfi_vid =\n        (hdr->pcp_cfi_vid & ~OPENVPN_8021Q_MASK_VID) | (htons(vid) & OPENVPN_8021Q_MASK_VID);\n}\n\n/*\n * vlan_decapsulate - remove 802.1q header and return VID\n *\n * For vlan_accept == VLAN_ONLY_UNTAGGED_OR_PRIORITY:\n *   Only untagged frames and frames that are priority-tagged (VID == 0) are\n *   accepted.  (This means that VLAN-tagged frames are dropped.)  For frames\n *   that aren't dropped, the global vlan_pvid is returned as VID.\n *\n * For vlan_accept == VLAN_ONLY_TAGGED:\n *   If a frame is VLAN-tagged the tagging is removed and the embedded VID is\n *   returned.  Any included priority information is lost.\n *   If a frame isn't VLAN-tagged, the frame is dropped.\n *\n * For vlan_accept == VLAN_ALL:\n *   Accepts both VLAN-tagged and untagged (or priority-tagged) frames and\n *   and handles them as described above.\n *\n * @param c   The global context.\n * @param buf The ethernet frame.\n * @return    Returns -1 if the frame is dropped or the VID if it is accepted.\n */\nint16_t\nvlan_decapsulate(const struct context *c, struct buffer *buf)\n{\n    const struct openvpn_8021qhdr *vlanhdr;\n    struct openvpn_ethhdr *ethhdr;\n    uint16_t vid;\n\n    /* assume untagged frame */\n    if (BLENZ(buf) < sizeof(*ethhdr))\n    {\n        goto drop;\n    }\n\n    ethhdr = (struct openvpn_ethhdr *)BPTR(buf);\n    if (ethhdr->proto != htons(OPENVPN_ETH_P_8021Q))\n    {\n        /* reject untagged frame */\n        if (c->options.vlan_accept == VLAN_ONLY_TAGGED)\n        {\n            msg(D_VLAN_DEBUG, \"dropping frame without vlan-tag (proto/len 0x%04x)\",\n                ntohs(ethhdr->proto));\n            goto drop;\n        }\n\n        /* untagged frame is accepted and associated with the global VID */\n        msg(D_VLAN_DEBUG, \"assuming pvid for frame without vlan-tag, pvid: %u (proto/len 0x%04x)\",\n            c->options.vlan_pvid, ntohs(ethhdr->proto));\n\n        return c->options.vlan_pvid;\n    }\n\n    /* tagged frame */\n    if (BLENZ(buf) < sizeof(*vlanhdr))\n    {\n        goto drop;\n    }\n\n    vlanhdr = (const struct openvpn_8021qhdr *)BPTR(buf);\n    vid = vlanhdr_get_vid(vlanhdr);\n\n    switch (c->options.vlan_accept)\n    {\n        case VLAN_ONLY_UNTAGGED_OR_PRIORITY:\n            /* VLAN-tagged frame: drop packet */\n            if (vid != 0)\n            {\n                msg(D_VLAN_DEBUG, \"dropping frame with vlan-tag, vid: %u (proto/len 0x%04x)\", vid,\n                    ntohs(vlanhdr->proto));\n                goto drop;\n            }\n\n        /* vid == 0 means prio-tagged packet: don't drop and fall-through */\n        case VLAN_ONLY_TAGGED:\n        case VLAN_ALL:\n            /* tagged frame can be accepted: extract vid and strip encapsulation */\n\n            /* in case of prio-tagged frame (vid == 0), assume the sender\n             * knows what he is doing and forward the packet as it is, so to\n             * keep the priority information intact.\n             */\n            if (vid == 0)\n            {\n                /* return the global VID for priority-tagged frames */\n                return c->options.vlan_pvid;\n            }\n\n            /* here we have a proper VLAN tagged frame: perform decapsulation\n             * and return embedded VID\n             */\n            msg(D_VLAN_DEBUG, \"removing vlan-tag from frame: vid: %u, wrapped proto/len: 0x%04x\",\n                vid, ntohs(vlanhdr->proto));\n\n            /* save inner protocol to be restored later after decapsulation */\n            uint16_t proto = vlanhdr->proto;\n            /* move the buffer head forward to adjust the headroom to a\n             * non-tagged frame\n             */\n            buf_advance(buf, SIZE_ETH_TO_8021Q_HDR);\n            /* move the content of the 802.1q header to the new head, so that\n             * src/dst addresses are copied over\n             */\n            ethhdr = memmove(BPTR(buf), vlanhdr, sizeof(*ethhdr));\n            /* restore the inner protocol value */\n            ethhdr->proto = proto;\n\n            return vid;\n    }\n\ndrop:\n    buf->len = 0;\n    return -1;\n}\n\n/*\n * vlan_encapsulate - add 802.1q header and set the context related VID\n *\n * Assumes vlan_accept == VLAN_ONLY_TAGGED\n *\n * @param c   The current context.\n * @param buf The ethernet frame to encapsulate.\n */\nvoid\nvlan_encapsulate(const struct context *c, struct buffer *buf)\n{\n    const struct openvpn_ethhdr *ethhdr;\n    struct openvpn_8021qhdr *vlanhdr;\n\n    if (BLENZ(buf) < sizeof(*ethhdr))\n    {\n        goto drop;\n    }\n\n    ethhdr = (const struct openvpn_ethhdr *)BPTR(buf);\n    if (ethhdr->proto == htons(OPENVPN_ETH_P_8021Q))\n    {\n        /* Priority-tagged frame. (VLAN-tagged frames have been dropped before\n         * getting to this point)\n         */\n\n        /* Frame too small for header type? */\n        if (BLENZ(buf) < sizeof(*vlanhdr))\n        {\n            goto drop;\n        }\n\n        vlanhdr = (struct openvpn_8021qhdr *)BPTR(buf);\n\n        /* sanity check: ensure this packet is really just prio-tagged */\n        uint16_t vid = vlanhdr_get_vid(vlanhdr);\n        if (vid != 0)\n        {\n            goto drop;\n        }\n    }\n    else\n    {\n        /* Untagged frame. */\n\n        /* Not enough head room for VLAN tag? */\n        if (buf_reverse_capacity(buf) < (int)SIZE_ETH_TO_8021Q_HDR)\n        {\n            goto drop;\n        }\n\n        vlanhdr = (struct openvpn_8021qhdr *)buf_prepend(buf, SIZE_ETH_TO_8021Q_HDR);\n\n        /* Initialise VLAN/802.1q header.\n         * Move the Eth header so to keep dst/src addresses the same and then\n         * assign the other fields.\n         *\n         * Also, save the inner protocol first, so that it can be restored later\n         * after the memmove()\n         */\n        uint16_t proto = ethhdr->proto;\n        memmove(vlanhdr, ethhdr, sizeof(*ethhdr));\n        vlanhdr->tpid = htons(OPENVPN_ETH_P_8021Q);\n        vlanhdr->pcp_cfi_vid = 0;\n        vlanhdr->proto = proto;\n    }\n\n    /* set the VID corresponding to the current context (client) */\n    vlanhdr_set_vid(vlanhdr, c->options.vlan_pvid);\n\n    msg(D_VLAN_DEBUG, \"tagging frame: vid %u (wrapping proto/len: %04x)\", c->options.vlan_pvid,\n        vlanhdr->proto);\n    return;\n\ndrop:\n    /* Drop the frame. */\n    buf->len = 0;\n}\n\n/*\n * vlan_is_tagged - check if a packet is VLAN-tagged\n *\n * Checks whether ethernet frame is VLAN-tagged.\n *\n * @param buf The ethernet frame.\n * @return    Returns true if the frame is VLAN-tagged, false otherwise.\n */\nbool\nvlan_is_tagged(const struct buffer *buf)\n{\n    const struct openvpn_8021qhdr *vlanhdr;\n    uint16_t vid;\n\n    if (BLENZ(buf) < sizeof(struct openvpn_8021qhdr))\n    {\n        /* frame too small to be VLAN-tagged */\n        return false;\n    }\n\n    vlanhdr = (const struct openvpn_8021qhdr *)BPTR(buf);\n\n    if (ntohs(vlanhdr->tpid) != OPENVPN_ETH_P_8021Q)\n    {\n        /* non tagged frame */\n        return false;\n    }\n\n    vid = vlanhdr_get_vid(vlanhdr);\n    if (vid == 0)\n    {\n        /* no vid: piority tagged only */\n        return false;\n    }\n\n    return true;\n}\n\nvoid\nvlan_process_outgoing_tun(struct multi_context *m, struct multi_instance *mi)\n{\n    if (!m->top.options.vlan_tagging)\n    {\n        return;\n    }\n\n    if (m->top.options.vlan_accept == VLAN_ONLY_UNTAGGED_OR_PRIORITY)\n    {\n        /* Packets forwarded to the TAP devices aren't VLAN-tagged. Only packets\n         * matching the PVID configured globally are allowed to be received\n         */\n        if (m->top.options.vlan_pvid != mi->context.options.vlan_pvid)\n        {\n            /* Packet is coming from the wrong VID, drop it.  */\n            mi->context.c2.to_tun.len = 0;\n        }\n    }\n    else if (m->top.options.vlan_accept == VLAN_ALL)\n    {\n        /* Packets either need to be VLAN-tagged or not, depending on the\n         * packet's originating VID and the port's native VID (PVID).  */\n\n        if (m->top.options.vlan_pvid != mi->context.options.vlan_pvid)\n        {\n            /* Packets need to be VLAN-tagged, because the packet's VID does not\n             * match the port's PVID.  */\n            vlan_encapsulate(&mi->context, &mi->context.c2.to_tun);\n        }\n    }\n    else if (m->top.options.vlan_accept == VLAN_ONLY_TAGGED)\n    {\n        /* All packets on the port (the tap device) need to be VLAN-tagged.  */\n        vlan_encapsulate(&mi->context, &mi->context.c2.to_tun);\n    }\n}\n"
  },
  {
    "path": "src/openvpn/vlan.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Technologies, Inc. <sales@openvpn.net>\n *  Copyright (C) 2010      Fabian Knittel <fabian.knittel@lettink.de>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef VLAN_H\n#define VLAN_H\n\n#include \"buffer.h\"\n#include \"mroute.h\"\n#include \"openvpn.h\"\n\nstruct multi_context;\nstruct multi_instance;\n\nint16_t vlan_decapsulate(const struct context *c, struct buffer *buf);\n\nbool vlan_is_tagged(const struct buffer *buf);\n\nvoid vlan_process_outgoing_tun(struct multi_context *m, struct multi_instance *mi);\n\n#endif /* VLAN_H */\n"
  },
  {
    "path": "src/openvpn/wfp_block.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *                2015-2016  <iam@valdikss.org.ru>\n *                2016 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#ifdef _WIN32\n\n#include <fwpmu.h>\n#include <initguid.h>\n#include <fwpmtypes.h>\n#include <winsock2.h>\n#include <ws2ipdef.h>\n#include <iphlpapi.h>\n\n#include \"wfp_block.h\"\n\n/*\n * WFP-related defines and GUIDs not in mingw32\n */\n\n#ifndef FWPM_SESSION_FLAG_DYNAMIC\n#define FWPM_SESSION_FLAG_DYNAMIC 0x00000001\n#endif\n\n/* c38d57d1-05a7-4c33-904f-7fbceee60e82 */\nDEFINE_GUID(FWPM_LAYER_ALE_AUTH_CONNECT_V4, 0xc38d57d1, 0x05a7, 0x4c33, 0x90, 0x4f, 0x7f, 0xbc,\n            0xee, 0xe6, 0x0e, 0x82);\n\n/* 4a72393b-319f-44bc-84c3-ba54dcb3b6b4 */\nDEFINE_GUID(FWPM_LAYER_ALE_AUTH_CONNECT_V6, 0x4a72393b, 0x319f, 0x44bc, 0x84, 0xc3, 0xba, 0x54,\n            0xdc, 0xb3, 0xb6, 0xb4);\n\n/* d78e1e87-8644-4ea5-9437-d809ecefc971 */\nDEFINE_GUID(FWPM_CONDITION_ALE_APP_ID, 0xd78e1e87, 0x8644, 0x4ea5, 0x94, 0x37, 0xd8, 0x09, 0xec,\n            0xef, 0xc9, 0x71);\n\n/* c35a604d-d22b-4e1a-91b4-68f674ee674b */\nDEFINE_GUID(FWPM_CONDITION_IP_REMOTE_PORT, 0xc35a604d, 0xd22b, 0x4e1a, 0x91, 0xb4, 0x68, 0xf6, 0x74,\n            0xee, 0x67, 0x4b);\n\n/* 4cd62a49-59c3-4969-b7f3-bda5d32890a4 */\nDEFINE_GUID(FWPM_CONDITION_IP_LOCAL_INTERFACE, 0x4cd62a49, 0x59c3, 0x4969, 0xb7, 0xf3, 0xbd, 0xa5,\n            0xd3, 0x28, 0x90, 0xa4);\n\n/* 632ce23b-5167-435c-86d7-e903684aa80c */\nDEFINE_GUID(FWPM_CONDITION_FLAGS, 0x632ce23b, 0x5167, 0x435c, 0x86, 0xd7, 0xe9, 0x03, 0x68, 0x4a,\n            0xa8, 0x0c);\n\n/* UUID of WFP sublayer used by all instances of openvpn\n * 2f660d7e-6a37-11e6-a181-001e8c6e04a2 */\nDEFINE_GUID(OPENVPN_WFP_BLOCK_SUBLAYER, 0x2f660d7e, 0x6a37, 0x11e6, 0xa1, 0x81, 0x00, 0x1e, 0x8c,\n            0x6e, 0x04, 0xa2);\n\nstatic WCHAR *FIREWALL_NAME = L\"OpenVPN\";\n\n/*\n * Default msg handler does nothing\n */\nstatic inline void\ndefault_msg_handler(DWORD err, const char *msg)\n{\n    return;\n}\n\n#define OUT_ON_ERROR(err, msg) \\\n    if (err)                   \\\n    {                          \\\n        msg_handler(err, msg); \\\n        goto out;              \\\n    }\n\n/*\n * Add a persistent sublayer with specified uuid.\n */\nstatic DWORD\nadd_sublayer(GUID uuid)\n{\n    FWPM_SESSION0 session;\n    HANDLE engine = NULL;\n    DWORD err = 0;\n    FWPM_SUBLAYER0 sublayer;\n\n    memset(&session, 0, sizeof(session));\n    memset(&sublayer, 0, sizeof(sublayer));\n\n    err = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engine);\n    if (err != ERROR_SUCCESS)\n    {\n        goto out;\n    }\n\n    sublayer.subLayerKey = uuid;\n    sublayer.displayData.name = FIREWALL_NAME;\n    sublayer.displayData.description = FIREWALL_NAME;\n    sublayer.flags = 0;\n    sublayer.weight = 0x100;\n\n    /* Add sublayer to the session */\n    err = FwpmSubLayerAdd0(engine, &sublayer, NULL);\n\nout:\n    if (engine)\n    {\n        FwpmEngineClose0(engine);\n    }\n    return err;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\n/*\n * Block outgoing local traffic, possibly DNS only, except for\n * (i) adapter with the specified index (and loopback, if all is blocked)\n * OR\n * (ii) processes with the specified executable path\n * The firewall filters added here are automatically removed when the process exits or\n * on calling delete_wfp_block_filters().\n * Arguments:\n *   engine_handle : On successful return contains the handle for a newly opened fwp session\n *                   in which the filters are added.\n *                   May be closed by passing to delete_wfp_block_filters to remove the filters.\n *   index         : The index of adapter for which traffic is permitted.\n *   exe_path      : Path of executable for which traffic is permitted.\n *   msg_handler   : An optional callback function for error reporting.\n *   dns_only      : Whether the blocking filters should apply for DNS only.\n * Returns 0 on success, a non-zero status code of the last failed action on failure.\n */\n\nDWORD\nadd_wfp_block_filters(HANDLE *engine_handle, int index, const WCHAR *exe_path,\n                      wfp_block_msg_handler_t msg_handler, BOOL dns_only)\n{\n    FWPM_SESSION0 session = { 0 };\n    FWPM_SUBLAYER0 *sublayer_ptr = NULL;\n    NET_LUID itf_luid;\n    UINT64 filterid;\n    FWP_BYTE_BLOB *openvpnblob = NULL;\n    FWPM_FILTER0 Filter = { 0 };\n    FWPM_FILTER_CONDITION0 Condition[2];\n    FWPM_FILTER_CONDITION0 match_openvpn = { 0 };\n    FWPM_FILTER_CONDITION0 match_port_53 = { 0 };\n    FWPM_FILTER_CONDITION0 match_interface = { 0 };\n    FWPM_FILTER_CONDITION0 match_loopback = { 0 };\n    FWPM_FILTER_CONDITION0 match_not_loopback = { 0 };\n    DWORD err = 0;\n\n    if (!msg_handler)\n    {\n        msg_handler = default_msg_handler;\n    }\n\n    /* Add temporary filters which don't survive reboots or crashes. */\n    session.flags = FWPM_SESSION_FLAG_DYNAMIC;\n\n    *engine_handle = NULL;\n\n    err = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, engine_handle);\n    OUT_ON_ERROR(err, \"FwpEngineOpen: open fwp session failed\");\n    msg_handler(0, \"WFP Block: WFP engine opened\");\n\n    /* Check sublayer exists and add one if it does not. */\n    if (FwpmSubLayerGetByKey0(*engine_handle, &OPENVPN_WFP_BLOCK_SUBLAYER, &sublayer_ptr)\n        == ERROR_SUCCESS)\n    {\n        msg_handler(0, \"WFP Block: Using existing sublayer\");\n        FwpmFreeMemory0((void **)&sublayer_ptr);\n    }\n    else\n    { /* Add a new sublayer -- as another process may add it in the meantime,\n       * do not treat \"already exists\" as an error */\n        err = add_sublayer(OPENVPN_WFP_BLOCK_SUBLAYER);\n\n        if (err == FWP_E_ALREADY_EXISTS || err == ERROR_SUCCESS)\n        {\n            msg_handler(0, \"WFP Block: Added a persistent sublayer with pre-defined UUID\");\n        }\n        else\n        {\n            OUT_ON_ERROR(err, \"add_sublayer: failed to add persistent sublayer\");\n        }\n    }\n\n    err = ConvertInterfaceIndexToLuid(index, &itf_luid);\n    OUT_ON_ERROR(err, \"Convert interface index to luid failed\");\n\n    err = FwpmGetAppIdFromFileName0(exe_path, &openvpnblob);\n    OUT_ON_ERROR(err, \"Get byte blob for openvpn executable name failed\");\n\n    /* Prepare match conditions */\n    match_openvpn.fieldKey = FWPM_CONDITION_ALE_APP_ID;\n    match_openvpn.matchType = FWP_MATCH_EQUAL;\n    match_openvpn.conditionValue.type = FWP_BYTE_BLOB_TYPE;\n    match_openvpn.conditionValue.byteBlob = openvpnblob;\n\n    match_port_53.fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;\n    match_port_53.matchType = FWP_MATCH_EQUAL;\n    match_port_53.conditionValue.type = FWP_UINT16;\n    match_port_53.conditionValue.uint16 = 53;\n\n    match_interface.fieldKey = FWPM_CONDITION_IP_LOCAL_INTERFACE;\n    match_interface.matchType = FWP_MATCH_EQUAL;\n    match_interface.conditionValue.type = FWP_UINT64;\n    match_interface.conditionValue.uint64 = &itf_luid.Value;\n\n    match_loopback.fieldKey = FWPM_CONDITION_FLAGS;\n    match_loopback.matchType = FWP_MATCH_FLAGS_ALL_SET;\n    match_loopback.conditionValue.type = FWP_UINT32;\n    match_loopback.conditionValue.uint32 = FWP_CONDITION_FLAG_IS_LOOPBACK;\n\n    match_not_loopback.fieldKey = FWPM_CONDITION_FLAGS;\n    match_not_loopback.matchType = FWP_MATCH_FLAGS_NONE_SET;\n    match_not_loopback.conditionValue.type = FWP_UINT32;\n    match_not_loopback.conditionValue.uint32 = FWP_CONDITION_FLAG_IS_LOOPBACK;\n\n    /* Prepare filter. */\n    Filter.subLayerKey = OPENVPN_WFP_BLOCK_SUBLAYER;\n    Filter.displayData.name = FIREWALL_NAME;\n    Filter.weight.type = FWP_UINT8;\n    Filter.weight.uint8 = 0xF;\n    Filter.filterCondition = Condition;\n    Filter.numFilterConditions = 1;\n\n    /* First filter. Permit IPv4 from OpenVPN itself. */\n    Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;\n    Filter.action.type = FWP_ACTION_PERMIT;\n    Condition[0] = match_openvpn;\n    if (dns_only)\n    {\n        Filter.numFilterConditions = 2;\n        Condition[1] = match_port_53;\n    }\n    err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);\n    OUT_ON_ERROR(err, \"Add filter to permit IPv4 traffic from OpenVPN failed\");\n\n    /* Second filter. Permit IPv6 from OpenVPN itself. */\n    Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;\n    err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);\n    OUT_ON_ERROR(err, \"Add filter to permit IPv6 traffic from OpenVPN failed\");\n\n    msg_handler(0, \"WFP Block: Added permit filters for exe_path\");\n\n    /* Third filter. Block IPv4 to port 53 or all except loopback. */\n    Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;\n    Filter.action.type = FWP_ACTION_BLOCK;\n    Filter.weight.type = FWP_EMPTY;\n    Filter.numFilterConditions = 1;\n    Condition[0] = match_not_loopback;\n    if (dns_only)\n    {\n        Filter.numFilterConditions = 2;\n        Condition[1] = match_port_53;\n    }\n    err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);\n    OUT_ON_ERROR(err, \"Add filter to block IPv4 traffic failed\");\n\n    /* Fourth filter. Block IPv6 to port 53 or all besides loopback */\n    Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;\n    err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);\n    OUT_ON_ERROR(err, \"Add filter to block IPv6 traffic failed\");\n\n    msg_handler(0, \"WFP Block: Added block filters for all interfaces\");\n\n    /* Fifth filter. Permit all IPv4 or just DNS traffic for the VPN interface.\n     * Use a non-zero weight so that the permit filters get higher priority\n     * over the block filter added with automatic weighting */\n    Filter.weight.type = FWP_UINT8;\n    Filter.weight.uint8 = 0xE;\n    Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;\n    Filter.action.type = FWP_ACTION_PERMIT;\n    Filter.numFilterConditions = 1;\n    Condition[0] = match_interface;\n    if (dns_only)\n    {\n        Filter.numFilterConditions = 2;\n        Condition[1] = match_port_53;\n    }\n    err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);\n    OUT_ON_ERROR(err, \"Add filter to permit IPv4 traffic through VPN interface failed\");\n\n    /* Sixth filter. Permit all IPv6 or just DNS traffic for the VPN interface.\n     * Use same weight as IPv4 filter */\n    Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;\n    err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);\n    OUT_ON_ERROR(err, \"Add filter to permit IPv6 traffic through VPN interface failed\");\n\n    msg_handler(0, \"WFP Block: Added permit filters for VPN interface\");\n\n    /* Seventh Filter. Block IPv4 DNS requests to loopback from other apps */\n    Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;\n    Filter.action.type = FWP_ACTION_BLOCK;\n    Filter.weight.type = FWP_EMPTY;\n    Filter.numFilterConditions = 2;\n    Condition[0] = match_loopback;\n    Condition[1] = match_port_53;\n    err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);\n    OUT_ON_ERROR(err, \"Add filter to block IPv4 DNS traffic to loopback failed\");\n\n    /* Eighth Filter. Block IPv6 DNS requests to loopback from other apps */\n    Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;\n    err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);\n    OUT_ON_ERROR(err, \"Add filter to block IPv6 DNS traffic to loopback failed\");\n\n    msg_handler(0, \"WFP Block: Added block filters for DNS traffic to loopback\");\n\nout:\n    if (openvpnblob)\n    {\n        FwpmFreeMemory0((void **)&openvpnblob);\n    }\n\n    if (err && *engine_handle)\n    {\n        FwpmEngineClose0(*engine_handle);\n        *engine_handle = NULL;\n    }\n\n    return err;\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nDWORD\ndelete_wfp_block_filters(HANDLE engine_handle)\n{\n    DWORD err = 0;\n    /*\n     * For dynamic sessions closing the engine removes all filters added in the session\n     */\n    if (engine_handle)\n    {\n        err = FwpmEngineClose0(engine_handle);\n    }\n    return err;\n}\n\n/*\n * Return interface metric value for the specified interface index.\n *\n * Arguments:\n *   index         : The index of TAP adapter.\n *   family        : Address family (AF_INET for IPv4 and AF_INET6 for IPv6).\n *   is_auto       : On return set to true if automatic metric is in use.\n *                   Unused if NULL.\n *\n * Returns positive metric value or -1 on error.\n */\nint\nget_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family, int *is_auto)\n{\n    DWORD err = 0;\n    MIB_IPINTERFACE_ROW ipiface;\n    InitializeIpInterfaceEntry(&ipiface);\n    ipiface.Family = family;\n    ipiface.InterfaceIndex = index;\n\n    if (is_auto)\n    {\n        *is_auto = 0;\n    }\n    err = GetIpInterfaceEntry(&ipiface);\n\n    /* On Windows metric is never > INT_MAX so return value of int is ok.\n     * But we check for overflow nevertheless.\n     */\n    if (err == NO_ERROR && ipiface.Metric <= INT_MAX)\n    {\n        if (is_auto)\n        {\n            *is_auto = ipiface.UseAutomaticMetric;\n        }\n        return (int)ipiface.Metric;\n    }\n    return -1;\n}\n\n/*\n * Sets interface metric value for specified interface index.\n *\n * Arguments:\n *   index         : The index of TAP adapter.\n *   family        : Address family (AF_INET for IPv4 and AF_INET6 for IPv6).\n *   metric        : Metric value. 0 for automatic metric.\n * Returns 0 on success, a non-zero status code of the last failed action on failure.\n */\n\nDWORD\nset_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family, const ULONG metric)\n{\n    DWORD err = 0;\n    MIB_IPINTERFACE_ROW ipiface;\n    InitializeIpInterfaceEntry(&ipiface);\n    ipiface.Family = family;\n    ipiface.InterfaceIndex = index;\n    err = GetIpInterfaceEntry(&ipiface);\n    if (err == NO_ERROR)\n    {\n        if (family == AF_INET)\n        {\n            /* required for IPv4 as per MSDN */\n            ipiface.SitePrefixLength = 0;\n        }\n        ipiface.Metric = metric;\n        if (metric == 0)\n        {\n            ipiface.UseAutomaticMetric = TRUE;\n        }\n        else\n        {\n            ipiface.UseAutomaticMetric = FALSE;\n        }\n        err = SetIpInterfaceEntry(&ipiface);\n        if (err == NO_ERROR)\n        {\n            return 0;\n        }\n    }\n    return err;\n}\n\n#endif /* ifdef _WIN32 */\n"
  },
  {
    "path": "src/openvpn/wfp_block.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef _WIN32\n\n#ifndef WFP_BLOCK_H\n#define WFP_BLOCK_H\n\n#include <windef.h>\n#include <iphlpapi.h>\n#include <ws2tcpip.h>\n\n/* Any value less than 5 should work fine. 3 is chosen without any real reason. */\n#define WFP_BLOCK_IFACE_METRIC 3\n\ntypedef void (*wfp_block_msg_handler_t)(DWORD err, const char *msg);\n\nDWORD\ndelete_wfp_block_filters(HANDLE engine);\n\nDWORD\nadd_wfp_block_filters(HANDLE *engine, int iface_index, const WCHAR *exe_path,\n                      wfp_block_msg_handler_t msg_handler_callback, BOOL dns_only);\n\n/**\n * Return interface metric value for the specified interface index.\n *\n * @param index         The index of TAP adapter.\n * @param family        Address family (AF_INET for IPv4 and AF_INET6 for IPv6).\n * @param is_auto       On return set to true if automatic metric is in use.\n *                      Unused if NULL.\n *\n * @return positive interface metric on success or -1 on error\n */\nint get_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family, int *is_auto);\n\n/**\n * Sets interface metric value for specified interface index.\n *\n * @param index The index of TAP adapter\n * @param family Address family (AF_INET for IPv4 and AF_INET6 for IPv6)\n * @param metric Metric value. 0 for automatic metric\n *\n * @return 0 on success, a non-zero status code of the last failed action on failure.\n */\n\nDWORD\nset_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family, const ULONG metric);\n\n#endif /* ifndef WFP_BLOCK_H */\n#endif /* ifdef _WIN32 */\n"
  },
  {
    "path": "src/openvpn/win32-util.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * Win32-specific OpenVPN code, targeted at the mingw\n * development environment.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#ifdef _WIN32\n\n#include \"buffer.h\"\n#include \"win32-util.h\"\n\nWCHAR *\nwide_string(const char *utf8, struct gc_arena *gc)\n{\n    int n = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);\n    WCHAR *ucs16 = gc_malloc(n * sizeof(WCHAR), false, gc);\n    MultiByteToWideChar(CP_UTF8, 0, utf8, -1, ucs16, n);\n    return ucs16;\n}\n\nchar *\nutf16to8(const wchar_t *utf16, struct gc_arena *gc)\n{\n    char *utf8 = NULL;\n    int n = WideCharToMultiByte(CP_UTF8, 0, utf16, -1, NULL, 0, NULL, NULL);\n    if (n > 0)\n    {\n        utf8 = gc_malloc(n, true, gc);\n        if (utf8)\n        {\n            WideCharToMultiByte(CP_UTF8, 0, utf16, -1, utf8, n, NULL, NULL);\n        }\n    }\n    return utf8;\n}\n\n/*\n * Return true if filename is safe to be used on Windows,\n * by avoiding the following reserved names:\n *\n * CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9,\n * LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9, and CLOCK$\n *\n * See: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file\n */\n\nstatic bool\ncmp_prefix(const char *str, const bool n, const char *pre)\n{\n    size_t i = 0;\n\n    if (!str)\n    {\n        return false;\n    }\n\n    while (true)\n    {\n        const int c1 = pre[i];\n        int c2 = str[i];\n        ++i;\n        if (c1 == '\\0')\n        {\n            if (n)\n            {\n                if (isdigit(c2))\n                {\n                    c2 = str[i];\n                }\n                else\n                {\n                    return false;\n                }\n            }\n            return c2 == '\\0' || c2 == '.';\n        }\n        else if (c2 == '\\0')\n        {\n            return false;\n        }\n        if (c1 != tolower(c2))\n        {\n            return false;\n        }\n    }\n}\n\nbool\nwin_safe_filename(const char *fn)\n{\n    if (cmp_prefix(fn, false, \"con\"))\n    {\n        return false;\n    }\n    if (cmp_prefix(fn, false, \"prn\"))\n    {\n        return false;\n    }\n    if (cmp_prefix(fn, false, \"aux\"))\n    {\n        return false;\n    }\n    if (cmp_prefix(fn, false, \"nul\"))\n    {\n        return false;\n    }\n    if (cmp_prefix(fn, true, \"com\"))\n    {\n        return false;\n    }\n    if (cmp_prefix(fn, true, \"lpt\"))\n    {\n        return false;\n    }\n    if (cmp_prefix(fn, false, \"clock$\"))\n    {\n        return false;\n    }\n    return true;\n}\n\nconst char *\nwin_get_tempdir(void)\n{\n    static char tmpdir[MAX_PATH];\n    WCHAR wtmpdir[MAX_PATH];\n\n    if (!GetTempPathW(_countof(wtmpdir), wtmpdir))\n    {\n        return NULL;\n    }\n\n    int ret = WideCharToMultiByte(CP_UTF8, 0, wtmpdir, -1, NULL, 0, NULL, NULL);\n    /* According to documentation ret is never < 0, but include it here just in case */\n    if (ret <= 0)\n    {\n        msg(M_WARN | M_ERRNO, \"Conversion of path name failed.\");\n        return NULL;\n    }\n    if ((unsigned int)ret > sizeof(tmpdir))\n    {\n        msg(M_WARN, \"Could not get temporary directory. Path is too long.\"\n                    \"  Consider using --tmp-dir\");\n        return NULL;\n    }\n\n    WideCharToMultiByte(CP_UTF8, 0, wtmpdir, -1, tmpdir, sizeof(tmpdir), NULL, NULL);\n    return tmpdir;\n}\n\n#endif /* _WIN32 */\n"
  },
  {
    "path": "src/openvpn/win32-util.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef _WIN32\n#ifndef OPENVPN_WIN32_UTIL_H\n#define OPENVPN_WIN32_UTIL_H\n\n#include <winioctl.h>\n\n#include \"mtu.h\"\n#include \"openvpn-msg.h\"\n#include \"argv.h\"\n\n/* Convert a string from UTF-8 to UCS-2 */\nWCHAR *wide_string(const char *utf8, struct gc_arena *gc);\n\n/* Convert a string from UTF-16 to UTF-8 */\nchar *utf16to8(const wchar_t *utf16, struct gc_arena *gc);\n\n/* return true if filename is safe to be used on Windows */\nbool win_safe_filename(const char *fn);\n\n/* Find temporary directory */\nconst char *win_get_tempdir(void);\n\n#endif /* OPENVPN_WIN32_UTIL_H */\n#endif /* ifdef _WIN32 */\n"
  },
  {
    "path": "src/openvpn/win32.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * Win32-specific OpenVPN code, targeted at the mingw\n * development environment.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#ifdef _WIN32\n\n#include <minwindef.h>\n#include <winsock2.h>\n\n#include \"buffer.h\"\n#include \"error.h\"\n#include \"mtu.h\"\n#include \"run_command.h\"\n#include \"sig.h\"\n#include \"win32-util.h\"\n#include \"win32.h\"\n#include \"openvpn-msg.h\"\n\n#include \"memdbg.h\"\n\n#include <versionhelpers.h>\n\n#include \"wfp_block.h\"\n\n/*\n * WFP handle\n */\nstatic HANDLE m_hEngineHandle = NULL; /* GLOBAL */\n\n/*\n * TAP adapter original metric value\n */\nstatic int tap_metric_v4 = -1; /* GLOBAL */\nstatic int tap_metric_v6 = -1; /* GLOBAL */\n\n/*\n * Windows internal socket API state (opaque).\n */\nstatic struct WSAData wsa_state; /* GLOBAL */\n\n/*\n * Should we call win32_pause() on program exit?\n */\nstatic bool pause_exit_enabled = false; /* GLOBAL */\n\n/*\n * win32_signal is used to get input from the keyboard\n * if we are running in a console, or get input from an\n * event object if we are running as a service.\n */\n\nstruct win32_signal win32_signal; /* GLOBAL */\n\n/*\n * Save our old window title so we can restore\n * it on exit.\n */\nstruct window_title window_title; /* GLOBAL*/\n\n/*\n * Special global semaphore used to protect network\n * shell commands from simultaneous instantiation.\n */\n\nstruct semaphore netcmd_semaphore; /* GLOBAL */\n\n/*\n * Windows system pathname such as c:\\windows\n */\nstatic char *win_sys_path = NULL; /* GLOBAL */\n\n/**\n * Set OpenSSL environment variables to a safe directory\n */\nstatic void set_openssl_env_vars(void);\n\nvoid\ninit_win32(void)\n{\n    if (WSAStartup(0x0101, &wsa_state))\n    {\n        msg(M_ERR, \"WSAStartup failed\");\n    }\n    window_title_clear(&window_title);\n    win32_signal_clear(&win32_signal);\n\n    set_openssl_env_vars();\n}\n\nvoid\nuninit_win32(void)\n{\n    netcmd_semaphore_close();\n    if (pause_exit_enabled)\n    {\n        if (win32_signal.mode == WSO_MODE_UNDEF)\n        {\n            struct win32_signal w;\n            win32_signal_open(&w, WSO_FORCE_CONSOLE, NULL, false);\n            win32_pause(&w);\n            win32_signal_close(&w);\n        }\n        else\n        {\n            win32_pause(&win32_signal);\n        }\n    }\n    window_title_restore(&window_title);\n    win32_signal_close(&win32_signal);\n    WSACleanup();\n    free(win_sys_path);\n}\n\nvoid\nset_pause_exit_win32(void)\n{\n    pause_exit_enabled = true;\n}\n\nbool\ninit_security_attributes_allow_all(struct security_attributes *obj)\n{\n    CLEAR(*obj);\n\n    obj->sa.nLength = sizeof(SECURITY_ATTRIBUTES);\n    obj->sa.lpSecurityDescriptor = &obj->sd;\n    obj->sa.bInheritHandle = FALSE;\n    if (!InitializeSecurityDescriptor(&obj->sd, SECURITY_DESCRIPTOR_REVISION))\n    {\n        return false;\n    }\n    if (!SetSecurityDescriptorDacl(&obj->sd, TRUE, NULL, FALSE))\n    {\n        return false;\n    }\n    return true;\n}\n\nvoid\noverlapped_io_init(struct overlapped_io *o, const struct frame *frame, BOOL event_state)\n{\n    CLEAR(*o);\n\n    /* manual reset event, initially set according to event_state */\n    o->overlapped.hEvent = CreateEvent(NULL, TRUE, event_state, NULL);\n    if (o->overlapped.hEvent == NULL)\n    {\n        msg(M_ERR, \"Error: overlapped_io_init: CreateEvent failed\");\n    }\n\n    /* allocate buffer for overlapped I/O */\n    alloc_buf_sock_tun(&o->buf_init, frame);\n}\n\nvoid\noverlapped_io_close(struct overlapped_io *o)\n{\n    if (o->overlapped.hEvent)\n    {\n        if (!CloseHandle(o->overlapped.hEvent))\n        {\n            msg(M_WARN | M_ERRNO, \"Warning: CloseHandle failed on overlapped I/O event object\");\n        }\n    }\n    free_buf(&o->buf_init);\n}\n\nchar *\noverlapped_io_state_ascii(const struct overlapped_io *o)\n{\n    switch (o->iostate)\n    {\n        case IOSTATE_INITIAL:\n            return \"0\";\n\n        case IOSTATE_QUEUED:\n            return \"Q\";\n\n        case IOSTATE_IMMEDIATE_RETURN:\n            return \"1\";\n    }\n    return \"?\";\n}\n\n/*\n * Event-based notification of network events\n */\n\nvoid\ninit_net_event_win32(struct rw_handle *event, long network_events, socket_descriptor_t sd,\n                     unsigned int flags)\n{\n    /* manual reset events, initially set to unsignaled */\n\n    /* initialize write event */\n    if (!(flags & NE32_PERSIST_EVENT) || !event->write)\n    {\n        if (flags & NE32_WRITE_EVENT)\n        {\n            event->write = CreateEvent(NULL, TRUE, FALSE, NULL);\n            if (event->write == NULL)\n            {\n                msg(M_ERR, \"Error: init_net_event_win32: CreateEvent (write) failed\");\n            }\n        }\n        else\n        {\n            event->write = NULL;\n        }\n    }\n\n    /* initialize read event */\n    if (!(flags & NE32_PERSIST_EVENT) || !event->read)\n    {\n        event->read = CreateEvent(NULL, TRUE, FALSE, NULL);\n        if (event->read == NULL)\n        {\n            msg(M_ERR, \"Error: init_net_event_win32: CreateEvent (read) failed\");\n        }\n    }\n\n    /* setup network events to change read event state */\n    if (WSAEventSelect(sd, event->read, network_events) != 0)\n    {\n        msg(M_FATAL | M_ERRNO, \"Error: init_net_event_win32: WSAEventSelect call failed\");\n    }\n}\n\nlong\nreset_net_event_win32(struct rw_handle *event, socket_descriptor_t sd)\n{\n    WSANETWORKEVENTS wne;\n    if (WSAEnumNetworkEvents(sd, event->read, &wne) != 0)\n    {\n        msg(M_FATAL | M_ERRNO, \"Error: reset_net_event_win32: WSAEnumNetworkEvents call failed\");\n        return 0; /* NOTREACHED */\n    }\n    else\n    {\n        return wne.lNetworkEvents;\n    }\n}\n\nvoid\nclose_net_event_win32(struct rw_handle *event, socket_descriptor_t sd, unsigned int flags)\n{\n    if (event->read)\n    {\n        if (socket_defined(sd))\n        {\n            if (WSAEventSelect(sd, event->read, 0) != 0)\n            {\n                msg(M_WARN | M_ERRNO, \"Warning: close_net_event_win32: WSAEventSelect call failed\");\n            }\n        }\n        if (!ResetEvent(event->read))\n        {\n            msg(M_WARN | M_ERRNO, \"Warning: ResetEvent (read) failed in close_net_event_win32\");\n        }\n        if (!(flags & NE32_PERSIST_EVENT))\n        {\n            if (!CloseHandle(event->read))\n            {\n                msg(M_WARN | M_ERRNO,\n                    \"Warning: CloseHandle (read) failed in close_net_event_win32\");\n            }\n            event->read = NULL;\n        }\n    }\n\n    if (event->write)\n    {\n        if (!ResetEvent(event->write))\n        {\n            msg(M_WARN | M_ERRNO, \"Warning: ResetEvent (write) failed in close_net_event_win32\");\n        }\n        if (!(flags & NE32_PERSIST_EVENT))\n        {\n            if (!CloseHandle(event->write))\n            {\n                msg(M_WARN | M_ERRNO,\n                    \"Warning: CloseHandle (write) failed in close_net_event_win32\");\n            }\n            event->write = NULL;\n        }\n    }\n}\n\n/*\n * struct net_event_win32\n */\n\nvoid\nnet_event_win32_init(struct net_event_win32 *ne)\n{\n    CLEAR(*ne);\n    ne->sd = SOCKET_UNDEFINED;\n}\n\nvoid\nnet_event_win32_start(struct net_event_win32 *ne, long network_events, socket_descriptor_t sd)\n{\n    ASSERT(!socket_defined(ne->sd));\n    ne->sd = sd;\n    ne->event_mask = 0;\n    init_net_event_win32(&ne->handle, network_events, sd, NE32_PERSIST_EVENT | NE32_WRITE_EVENT);\n}\n\nvoid\nnet_event_win32_reset_write(struct net_event_win32 *ne)\n{\n    BOOL status;\n    if (ne->event_mask & FD_WRITE)\n    {\n        status = SetEvent(ne->handle.write);\n    }\n    else\n    {\n        status = ResetEvent(ne->handle.write);\n    }\n    if (!status)\n    {\n        msg(M_WARN | M_ERRNO, \"Warning: SetEvent/ResetEvent failed in net_event_win32_reset_write\");\n    }\n}\n\nvoid\nnet_event_win32_reset(struct net_event_win32 *ne)\n{\n    ne->event_mask |= reset_net_event_win32(&ne->handle, ne->sd);\n}\n\nvoid\nnet_event_win32_stop(struct net_event_win32 *ne)\n{\n    if (net_event_win32_defined(ne))\n    {\n        close_net_event_win32(&ne->handle, ne->sd, NE32_PERSIST_EVENT);\n    }\n    ne->sd = SOCKET_UNDEFINED;\n    ne->event_mask = 0;\n}\n\nvoid\nnet_event_win32_close(struct net_event_win32 *ne)\n{\n    if (net_event_win32_defined(ne))\n    {\n        close_net_event_win32(&ne->handle, ne->sd, 0);\n    }\n    net_event_win32_init(ne);\n}\n\n/*\n * Simulate *nix signals on Windows.\n *\n * Two modes:\n * (1) Console mode -- map keyboard function keys to signals\n * (2) Service mode -- map Windows event object to SIGTERM\n */\n\nstatic void\nwin_trigger_event(struct win32_signal *ws)\n{\n    if (ws->mode == WSO_MODE_SERVICE && HANDLE_DEFINED(ws->in.read))\n    {\n        SetEvent(ws->in.read);\n    }\n    else /* generate a key-press event */\n    {\n        DWORD tmp;\n        INPUT_RECORD ir;\n        HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE);\n\n        CLEAR(ir);\n        ir.EventType = KEY_EVENT;\n        ir.Event.KeyEvent.bKeyDown = true;\n        if (!stdin_handle || !WriteConsoleInput(stdin_handle, &ir, 1, &tmp))\n        {\n            msg(M_WARN | M_ERRNO, \"WARN: win_trigger_event: WriteConsoleInput\");\n        }\n    }\n}\n\n/*\n * Callback to handle console ctrl events\n */\nstatic bool WINAPI\nwin_ctrl_handler(DWORD signum)\n{\n    msg(D_LOW, \"win_ctrl_handler: signal received (code=%lu)\", (unsigned long)signum);\n\n    if (siginfo_static.signal_received == SIGTERM)\n    {\n        return true;\n    }\n\n    switch (signum)\n    {\n        case CTRL_C_EVENT:\n        case CTRL_BREAK_EVENT:\n            throw_signal(SIGTERM);\n            /* trigget the win32_signal to interrupt the event loop */\n            win_trigger_event(&win32_signal);\n            return true;\n            break;\n\n        default:\n            msg(D_LOW, \"win_ctrl_handler: signal (code=%lu) not handled\", (unsigned long)signum);\n            break;\n    }\n    /* pass all other signals to the next handler */\n    return false;\n}\n\nvoid\nwin32_signal_clear(struct win32_signal *ws)\n{\n    CLEAR(*ws);\n}\n\nvoid\nwin32_signal_open(struct win32_signal *ws, int force, const char *exit_event_name,\n                  bool exit_event_initial_state)\n{\n    CLEAR(*ws);\n\n    ws->mode = WSO_MODE_UNDEF;\n    ws->in.read = INVALID_HANDLE_VALUE;\n    ws->in.write = INVALID_HANDLE_VALUE;\n    ws->console_mode_save = 0;\n    ws->console_mode_save_defined = false;\n\n    if (force == WSO_NOFORCE || force == WSO_FORCE_CONSOLE)\n    {\n        /*\n         * Try to open console.\n         */\n        ws->in.read = GetStdHandle(STD_INPUT_HANDLE);\n        if (ws->in.read != INVALID_HANDLE_VALUE)\n        {\n            if (GetConsoleMode(ws->in.read, &ws->console_mode_save))\n            {\n                /* running on a console */\n                const DWORD new_console_mode =\n                    ws->console_mode_save\n                    & ~(ENABLE_WINDOW_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT\n                        | ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT);\n\n                if (new_console_mode != ws->console_mode_save)\n                {\n                    if (!SetConsoleMode(ws->in.read, new_console_mode))\n                    {\n                        msg(M_ERR, \"Error: win32_signal_open: SetConsoleMode failed\");\n                    }\n                    ws->console_mode_save_defined = true;\n                }\n                ws->mode = WSO_MODE_CONSOLE;\n            }\n            else\n            {\n                ws->in.read = INVALID_HANDLE_VALUE; /* probably running as a service */\n            }\n        }\n    }\n\n    /*\n     * If console open failed, assume we are running\n     * as a service.\n     */\n    if ((force == WSO_NOFORCE || force == WSO_FORCE_SERVICE) && !HANDLE_DEFINED(ws->in.read)\n        && exit_event_name)\n    {\n        struct security_attributes sa;\n        struct gc_arena gc = gc_new();\n        const wchar_t *exit_event_nameW = wide_string(exit_event_name, &gc);\n\n        if (!init_security_attributes_allow_all(&sa))\n        {\n            msg(M_ERR, \"Error: win32_signal_open: init SA failed\");\n        }\n\n        ws->in.read =\n            CreateEventW(&sa.sa, TRUE, exit_event_initial_state ? TRUE : FALSE, exit_event_nameW);\n        if (ws->in.read == NULL)\n        {\n            msg(M_WARN | M_ERRNO, \"NOTE: CreateEventW '%s' failed\", exit_event_name);\n        }\n        else\n        {\n            if (WaitForSingleObject(ws->in.read, 0) != WAIT_TIMEOUT)\n            {\n                msg(M_FATAL, \"ERROR: Exit Event ('%s') is signaled\", exit_event_name);\n            }\n            else\n            {\n                ws->mode = WSO_MODE_SERVICE;\n            }\n        }\n        gc_free(&gc);\n    }\n    /* set the ctrl handler in both console and service modes */\n    if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)win_ctrl_handler, true))\n    {\n        msg(M_WARN | M_ERRNO, \"WARN: SetConsoleCtrlHandler failed\");\n    }\n}\n\nstatic bool\nkeyboard_input_available(struct win32_signal *ws)\n{\n    ASSERT(ws->mode == WSO_MODE_CONSOLE);\n    if (HANDLE_DEFINED(ws->in.read))\n    {\n        DWORD n;\n        if (GetNumberOfConsoleInputEvents(ws->in.read, &n))\n        {\n            return n > 0;\n        }\n    }\n    return false;\n}\n\nstatic unsigned int\nkeyboard_ir_to_key(INPUT_RECORD *ir)\n{\n    if (ir->Event.KeyEvent.uChar.AsciiChar == 0)\n    {\n        return ir->Event.KeyEvent.wVirtualScanCode;\n    }\n\n    if ((ir->Event.KeyEvent.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))\n        && (ir->Event.KeyEvent.wVirtualKeyCode != 18))\n    {\n        return ir->Event.KeyEvent.wVirtualScanCode * 256;\n    }\n\n    return ir->Event.KeyEvent.uChar.AsciiChar;\n}\n\nstatic unsigned int\nwin32_keyboard_get(struct win32_signal *ws)\n{\n    ASSERT(ws->mode == WSO_MODE_CONSOLE);\n    if (HANDLE_DEFINED(ws->in.read))\n    {\n        INPUT_RECORD ir;\n        do\n        {\n            DWORD n;\n            if (!keyboard_input_available(ws))\n            {\n                return 0;\n            }\n            if (!ReadConsoleInput(ws->in.read, &ir, 1, &n))\n            {\n                return 0;\n            }\n        } while (ir.EventType != KEY_EVENT || ir.Event.KeyEvent.bKeyDown != TRUE);\n\n        return keyboard_ir_to_key(&ir);\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nvoid\nwin32_signal_close(struct win32_signal *ws)\n{\n    if (ws->mode == WSO_MODE_SERVICE && HANDLE_DEFINED(ws->in.read))\n    {\n        CloseHandle(ws->in.read);\n    }\n    if (ws->console_mode_save_defined)\n    {\n        if (!SetConsoleMode(ws->in.read, ws->console_mode_save))\n        {\n            msg(M_ERR, \"Error: win32_signal_close: SetConsoleMode failed\");\n        }\n    }\n    CLEAR(*ws);\n}\n\n/*\n * Return true if interrupt occurs in service mode.\n */\nbool\nwin32_service_interrupt(struct win32_signal *ws)\n{\n    if (ws->mode == WSO_MODE_SERVICE)\n    {\n        if (HANDLE_DEFINED(ws->in.read) && WaitForSingleObject(ws->in.read, 0) == WAIT_OBJECT_0)\n        {\n            return true;\n        }\n    }\n    return false;\n}\n\nint\nwin32_signal_get(struct win32_signal *ws)\n{\n    int ret = 0;\n\n    if (ws->mode == WSO_MODE_SERVICE)\n    {\n        if (win32_service_interrupt(ws))\n        {\n            ret = SIGTERM;\n        }\n    }\n    else if (ws->mode == WSO_MODE_CONSOLE)\n    {\n        switch (win32_keyboard_get(ws))\n        {\n            case 0x3B: /* F1 -> USR1 */\n                ret = SIGUSR1;\n                break;\n\n            case 0x3C: /* F2 -> USR2 */\n                ret = SIGUSR2;\n                break;\n\n            case 0x3D: /* F3 -> HUP */\n                ret = SIGHUP;\n                break;\n\n            case 0x3E: /* F4 -> TERM */\n                ret = SIGTERM;\n                break;\n\n            case 0x03: /* CTRL-C -> TERM */\n                ret = SIGTERM;\n                break;\n        }\n    }\n    if (ret)\n    {\n        throw_signal(ret); /* this will update siginfo_static.signal received */\n    }\n    return (siginfo_static.signal_received);\n}\n\nvoid\nwin32_pause(struct win32_signal *ws)\n{\n    if (ws->mode == WSO_MODE_CONSOLE && HANDLE_DEFINED(ws->in.read))\n    {\n        msg(M_INFO | M_NOPREFIX, \"Press any key to continue...\");\n        do\n        {\n            WaitForSingleObject(ws->in.read, INFINITE);\n        } while (!win32_keyboard_get(ws));\n    }\n}\n\n/* window functions */\n\nvoid\nwindow_title_clear(struct window_title *wt)\n{\n    CLEAR(*wt);\n}\n\nvoid\nwindow_title_save(struct window_title *wt)\n{\n    if (!wt->saved)\n    {\n        if (!GetConsoleTitle(wt->old_window_title, sizeof(wt->old_window_title)))\n        {\n            wt->old_window_title[0] = 0;\n            wt->saved = false;\n        }\n        else\n        {\n            wt->saved = true;\n        }\n    }\n}\n\nvoid\nwindow_title_restore(const struct window_title *wt)\n{\n    if (wt->saved)\n    {\n        SetConsoleTitle(wt->old_window_title);\n    }\n}\n\nvoid\nwindow_title_generate(const char *title)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer out = alloc_buf_gc(256, &gc);\n    if (!title)\n    {\n        title = \"\";\n    }\n    buf_printf(&out, \"[%s] \" PACKAGE_NAME \" \" PACKAGE_VERSION \" F4:EXIT F1:USR1 F2:USR2 F3:HUP\",\n               title);\n    SetConsoleTitle(BSTR(&out));\n    gc_free(&gc);\n}\n\n/* semaphore functions */\n\nvoid\nsemaphore_clear(struct semaphore *s)\n{\n    CLEAR(*s);\n}\n\nvoid\nsemaphore_open(struct semaphore *s, const char *name)\n{\n    struct security_attributes sa;\n\n    s->locked = false;\n    s->name = name;\n    s->hand = NULL;\n\n    if (init_security_attributes_allow_all(&sa))\n    {\n        s->hand = CreateSemaphore(&sa.sa, 1, 1, name);\n    }\n\n    if (s->hand == NULL)\n    {\n        msg(M_WARN | M_ERRNO, \"WARNING: Cannot create Win32 semaphore '%s'\", name);\n    }\n    else\n    {\n        dmsg(D_SEMAPHORE, \"Created Win32 semaphore '%s'\", s->name);\n    }\n}\n\nbool\nsemaphore_lock(struct semaphore *s, int timeout_milliseconds)\n{\n    bool ret = true;\n\n    if (s->hand)\n    {\n        DWORD status;\n        ASSERT(!s->locked);\n\n        dmsg(\n            D_SEMAPHORE_LOW,\n            \"Attempting to lock Win32 semaphore '%s' prior to net shell command (timeout = %d sec)\",\n            s->name, timeout_milliseconds / 1000);\n        status = WaitForSingleObject(s->hand, timeout_milliseconds);\n        if (status == WAIT_FAILED)\n        {\n            msg(M_ERR, \"Wait failed on Win32 semaphore '%s'\", s->name);\n        }\n        ret = (status == WAIT_TIMEOUT) ? false : true;\n        if (ret)\n        {\n            dmsg(D_SEMAPHORE, \"Locked Win32 semaphore '%s'\", s->name);\n            s->locked = true;\n        }\n        else\n        {\n            dmsg(D_SEMAPHORE, \"Wait on Win32 semaphore '%s' timed out after %d milliseconds\",\n                 s->name, timeout_milliseconds);\n        }\n    }\n    return ret;\n}\n\nvoid\nsemaphore_release(struct semaphore *s)\n{\n    if (s->hand)\n    {\n        ASSERT(s->locked);\n        dmsg(D_SEMAPHORE, \"Releasing Win32 semaphore '%s'\", s->name);\n        if (!ReleaseSemaphore(s->hand, 1, NULL))\n        {\n            msg(M_WARN | M_ERRNO, \"ReleaseSemaphore failed on Win32 semaphore '%s'\", s->name);\n        }\n        s->locked = false;\n    }\n}\n\nvoid\nsemaphore_close(struct semaphore *s)\n{\n    if (s->hand)\n    {\n        if (s->locked)\n        {\n            semaphore_release(s);\n        }\n        dmsg(D_SEMAPHORE, \"Closing Win32 semaphore '%s'\", s->name);\n        CloseHandle(s->hand);\n        s->hand = NULL;\n    }\n}\n\n/*\n * Special global semaphore used to protect network\n * shell commands from simultaneous instantiation.\n */\n\nvoid\nnetcmd_semaphore_init(void)\n{\n    semaphore_open(&netcmd_semaphore, PACKAGE \"_netcmd\");\n}\n\nvoid\nnetcmd_semaphore_close(void)\n{\n    semaphore_close(&netcmd_semaphore);\n}\n\nvoid\nnetcmd_semaphore_lock(void)\n{\n    const int timeout_seconds = 600;\n\n    if (!netcmd_semaphore.hand)\n    {\n        netcmd_semaphore_init();\n    }\n\n    if (!semaphore_lock(&netcmd_semaphore, timeout_seconds * 1000))\n    {\n        msg(M_FATAL, \"Cannot lock net command semaphore\");\n    }\n}\n\nvoid\nnetcmd_semaphore_release(void)\n{\n    semaphore_release(&netcmd_semaphore);\n    /* netcmd_semaphore has max count of 1 - safe to close after release */\n    semaphore_close(&netcmd_semaphore);\n}\n\n/*\n * Service functions for openvpn_execve\n */\n\nstatic char *\nenv_block(const struct env_set *es)\n{\n    char force_path[256];\n    char *sysroot = get_win_sys_path();\n\n    if (!checked_snprintf(force_path, sizeof(force_path), \"PATH=%s\\\\System32;%s;%s\\\\System32\\\\Wbem\",\n                          sysroot, sysroot, sysroot))\n    {\n        msg(M_WARN, \"env_block: default path truncated to %s\", force_path);\n    }\n\n    if (es)\n    {\n        struct env_item *e;\n        char *ret;\n        char *p;\n        size_t nchars = 1;\n        bool path_seen = false;\n\n        for (e = es->list; e != NULL; e = e->next)\n        {\n            nchars += strlen(e->string) + 1;\n        }\n\n        nchars += strlen(force_path) + 1;\n\n        ret = (char *)malloc(nchars);\n        check_malloc_return(ret);\n\n        p = ret;\n        for (e = es->list; e != NULL; e = e->next)\n        {\n            if (env_allowed(e->string))\n            {\n                strcpy(p, e->string);\n                p += strlen(e->string) + 1;\n            }\n            if (strncmp(e->string, \"PATH=\", 5) == 0)\n            {\n                path_seen = true;\n            }\n        }\n\n        /* make sure PATH is set */\n        if (!path_seen)\n        {\n            msg(M_INFO, \"env_block: add %s\", force_path);\n            strcpy(p, force_path);\n            p += strlen(force_path) + 1;\n        }\n\n        *p = '\\0';\n        return ret;\n    }\n    else\n    {\n        return NULL;\n    }\n}\n\nstatic WCHAR *\nwide_cmd_line(const struct argv *a, struct gc_arena *gc)\n{\n    size_t nchars = 1;\n    size_t maxlen = 0;\n    size_t i;\n    struct buffer buf;\n    char *work = NULL;\n\n    if (!a)\n    {\n        return NULL;\n    }\n\n    for (i = 0; i < a->argc; ++i)\n    {\n        const char *arg = a->argv[i];\n        const size_t len = strlen(arg);\n        nchars += len + 3;\n        if (len > maxlen)\n        {\n            maxlen = len;\n        }\n    }\n\n    work = gc_malloc(maxlen + 1, false, gc);\n    check_malloc_return(work);\n    buf = alloc_buf_gc(nchars, gc);\n\n    for (i = 0; i < a->argc; ++i)\n    {\n        const char *arg = a->argv[i];\n        strcpy(work, arg);\n        string_mod(work, CC_PRINT, CC_DOUBLE_QUOTE | CC_CRLF, '_');\n        if (i)\n        {\n            buf_printf(&buf, \" \");\n        }\n        if (string_class(work, CC_ANY, CC_SPACE))\n        {\n            buf_printf(&buf, \"%s\", work);\n        }\n        else\n        {\n            buf_printf(&buf, \"\\\"%s\\\"\", work);\n        }\n    }\n\n    return wide_string(BSTR(&buf), gc);\n}\n\n/*\n * Attempt to simulate fork/execve on Windows\n */\nint\nopenvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags)\n{\n    int ret = OPENVPN_EXECVE_ERROR;\n    static bool exec_warn = false;\n\n    if (a && a->argv[0])\n    {\n        if (openvpn_execve_allowed(flags))\n        {\n            struct gc_arena gc = gc_new();\n            STARTUPINFOW start_info;\n            PROCESS_INFORMATION proc_info;\n\n            char *env = env_block(es);\n            WCHAR *cl = wide_cmd_line(a, &gc);\n            WCHAR *cmd = wide_string(a->argv[0], &gc);\n\n            /* this allows console programs to run, and is ignored otherwise */\n            DWORD proc_flags = CREATE_NO_WINDOW;\n\n            CLEAR(start_info);\n            CLEAR(proc_info);\n\n            /* fill in STARTUPINFO struct */\n            GetStartupInfoW(&start_info);\n            start_info.cb = sizeof(start_info);\n            start_info.dwFlags = STARTF_USESHOWWINDOW;\n            start_info.wShowWindow = SW_HIDE;\n\n            if (CreateProcessW(cmd, cl, NULL, NULL, FALSE, proc_flags, env, NULL, &start_info,\n                               &proc_info))\n            {\n                DWORD exit_status = 0;\n                CloseHandle(proc_info.hThread);\n                WaitForSingleObject(proc_info.hProcess, INFINITE);\n                if (GetExitCodeProcess(proc_info.hProcess, &exit_status))\n                {\n                    ret = (int)exit_status;\n                }\n                else\n                {\n                    msg(M_WARN | M_ERRNO, \"openvpn_execve: GetExitCodeProcess %ls failed\", cmd);\n                }\n                CloseHandle(proc_info.hProcess);\n            }\n            else\n            {\n                msg(M_WARN | M_ERRNO, \"openvpn_execve: CreateProcess %ls failed\", cmd);\n            }\n            free(env);\n            gc_free(&gc);\n        }\n        else\n        {\n            ret = OPENVPN_EXECVE_NOT_ALLOWED;\n            if (!exec_warn && (script_security() < SSEC_SCRIPTS))\n            {\n                msg(M_WARN, SCRIPT_SECURITY_WARNING);\n                exec_warn = true;\n            }\n        }\n    }\n    else\n    {\n        msg(M_WARN, \"openvpn_execve: called with empty argv\");\n    }\n    return ret;\n}\n\n/*\n * call ourself in another process\n */\nvoid\nfork_to_self(const char *cmdline)\n{\n    STARTUPINFO start_info;\n    PROCESS_INFORMATION proc_info;\n    char self_exe[256];\n    char *cl = string_alloc(cmdline, NULL);\n    DWORD status;\n\n    CLEAR(start_info);\n    CLEAR(proc_info);\n    CLEAR(self_exe);\n\n    status = GetModuleFileName(NULL, self_exe, sizeof(self_exe));\n    if (status == 0 || status == sizeof(self_exe))\n    {\n        msg(M_WARN | M_ERRNO,\n            \"fork_to_self: CreateProcess failed: cannot get module name via GetModuleFileName\");\n        goto done;\n    }\n\n    /* fill in STARTUPINFO struct */\n    GetStartupInfo(&start_info);\n    start_info.cb = sizeof(start_info);\n    start_info.dwFlags = STARTF_USESHOWWINDOW;\n    start_info.wShowWindow = SW_HIDE;\n\n    if (CreateProcess(self_exe, cl, NULL, NULL, FALSE, 0, NULL, NULL, &start_info, &proc_info))\n    {\n        CloseHandle(proc_info.hThread);\n        CloseHandle(proc_info.hProcess);\n    }\n    else\n    {\n        msg(M_WARN | M_ERRNO, \"fork_to_self: CreateProcess failed: %s\", cmdline);\n    }\n\ndone:\n    free(cl);\n}\n\nchar *\nget_win_sys_path(void)\n{\n    ASSERT(win_sys_path);\n    return win_sys_path;\n}\n\nvoid\nset_win_sys_path(const char *newpath, struct env_set *es)\n{\n    free(win_sys_path);\n    win_sys_path = string_alloc(newpath, NULL);\n    setenv_str(es, SYS_PATH_ENV_VAR_NAME, win_sys_path); /* route.exe needs this */\n}\n\nvoid\nset_win_sys_path_via_env(struct env_set *es)\n{\n    char buf[256];\n    DWORD status = GetEnvironmentVariable(SYS_PATH_ENV_VAR_NAME, buf, sizeof(buf));\n    if (!status)\n    {\n        msg(M_ERR, \"Cannot find environmental variable %s\", SYS_PATH_ENV_VAR_NAME);\n    }\n    if (status > sizeof(buf) - 1)\n    {\n        msg(M_FATAL, \"String overflow attempting to read environmental variable %s\",\n            SYS_PATH_ENV_VAR_NAME);\n    }\n    set_win_sys_path(buf, es);\n}\n\nstatic bool\nwin_get_exe_path(PWCHAR path, DWORD size)\n{\n    DWORD status = GetModuleFileNameW(NULL, path, size);\n    if (status == 0 || status == size)\n    {\n        msg(M_WARN | M_ERRNO, \"cannot get executable path\");\n        return false;\n    }\n    return true;\n}\n\nstatic void\nwin_wfp_msg_handler(DWORD err, const char *msg)\n{\n    struct gc_arena gc = gc_new();\n\n    if (err == 0)\n    {\n        msg(M_INFO, \"%s\", msg);\n    }\n    else\n    {\n        msg(M_WARN, \"Error in WFP: %s : %s [status=0x%lx]\", msg, strerror_win32(err, &gc), err);\n    }\n\n    gc_free(&gc);\n}\n\nstatic bool\nwin_wfp_block_service(bool add, bool dns_only, int index, const HANDLE pipe)\n{\n    bool ret = false;\n    ack_message_t ack;\n    struct gc_arena gc = gc_new();\n\n    wfp_block_message_t data = { .header = { (add ? msg_add_wfp_block : msg_del_wfp_block),\n                                             sizeof(wfp_block_message_t), 0 },\n                                 .flags = dns_only ? wfp_block_dns : wfp_block_local,\n                                 .iface = { .index = index, .name = \"\" } };\n\n    if (!send_msg_iservice(pipe, &data, sizeof(data), &ack, \"WFP block\"))\n    {\n        goto out;\n    }\n\n    if (ack.error_number != NO_ERROR)\n    {\n        msg(M_WARN,\n            \"WFP block: %s block filters using service failed: %s [status=0x%x if_index=%lu]\",\n            (add ? \"adding\" : \"deleting\"), strerror_win32(ack.error_number, &gc), ack.error_number,\n            data.iface.index);\n        goto out;\n    }\n\n    ret = true;\n    msg(M_INFO, \"%s WFP block filters using service succeeded.\", (add ? \"Adding\" : \"Deleting\"));\nout:\n    gc_free(&gc);\n    return ret;\n}\n\nbool\nwin_wfp_block(const NET_IFINDEX index, const HANDLE msg_channel, BOOL dns_only)\n{\n    WCHAR openvpnpath[MAX_PATH];\n    bool ret = false;\n    DWORD status;\n\n    if (msg_channel)\n    {\n        dmsg(D_LOW, \"Using service to add WFP block filters\");\n        ret = win_wfp_block_service(true, dns_only, index, msg_channel);\n        goto out;\n    }\n\n    ret = win_get_exe_path(openvpnpath, _countof(openvpnpath));\n    if (ret == false)\n    {\n        goto out;\n    }\n\n    status =\n        add_wfp_block_filters(&m_hEngineHandle, index, openvpnpath, win_wfp_msg_handler, dns_only);\n    if (status == 0)\n    {\n        int is_auto = 0;\n        tap_metric_v4 = get_interface_metric(index, AF_INET, &is_auto);\n        if (is_auto)\n        {\n            tap_metric_v4 = 0;\n        }\n        tap_metric_v6 = get_interface_metric(index, AF_INET6, &is_auto);\n        if (is_auto)\n        {\n            tap_metric_v6 = 0;\n        }\n        status = set_interface_metric(index, AF_INET, WFP_BLOCK_IFACE_METRIC);\n        if (!status)\n        {\n            set_interface_metric(index, AF_INET6, WFP_BLOCK_IFACE_METRIC);\n        }\n    }\n\n    ret = (status == 0);\n\nout:\n\n    return ret;\n}\n\nbool\nwin_wfp_uninit(const NET_IFINDEX index, const HANDLE msg_channel)\n{\n    dmsg(D_LOW, \"Uninitializing WFP\");\n\n    if (msg_channel)\n    {\n        msg(D_LOW, \"Using service to delete WFP block filters\");\n        win_wfp_block_service(false, false, index, msg_channel);\n    }\n    else\n    {\n        delete_wfp_block_filters(m_hEngineHandle);\n        m_hEngineHandle = NULL;\n        if (tap_metric_v4 >= 0)\n        {\n            set_interface_metric(index, AF_INET, tap_metric_v4);\n        }\n        if (tap_metric_v6 >= 0)\n        {\n            set_interface_metric(index, AF_INET6, tap_metric_v6);\n        }\n    }\n\n    return true;\n}\n\ntypedef enum\n{\n    ARCH_X86,\n    ARCH_AMD64,\n    ARCH_ARM64,\n    ARCH_NATIVE, /* means no emulation, makes sense for host arch */\n    ARCH_UNKNOWN\n} arch_t;\n\nstatic void\nwin32_get_arch(arch_t *process_arch, arch_t *host_arch)\n{\n    *process_arch = ARCH_UNKNOWN;\n    *host_arch = ARCH_NATIVE;\n\n    typedef BOOL(WINAPI * is_wow64_process2_t)(HANDLE, USHORT *, USHORT *);\n    is_wow64_process2_t is_wow64_process2 =\n        (is_wow64_process2_t)GetProcAddress(GetModuleHandle(\"Kernel32.dll\"), \"IsWow64Process2\");\n\n    USHORT process_machine = 0;\n    USHORT native_machine = 0;\n\n#ifdef _ARM64_\n    *process_arch = ARCH_ARM64;\n#elif defined(_WIN64)\n    *process_arch = ARCH_AMD64;\n    if (is_wow64_process2)\n    {\n        /* this could be amd64 on arm64 */\n        BOOL is_wow64 = is_wow64_process2(GetCurrentProcess(), &process_machine, &native_machine);\n        if (is_wow64 && native_machine == IMAGE_FILE_MACHINE_ARM64)\n        {\n            *host_arch = ARCH_ARM64;\n        }\n    }\n#elif defined(_WIN32)\n    *process_arch = ARCH_X86;\n\n    if (is_wow64_process2)\n    {\n        /* check if we're running on arm64 or amd64 machine */\n        BOOL is_wow64 = is_wow64_process2(GetCurrentProcess(), &process_machine, &native_machine);\n        if (is_wow64)\n        {\n            switch (native_machine)\n            {\n                case IMAGE_FILE_MACHINE_ARM64:\n                    *host_arch = ARCH_ARM64;\n                    break;\n\n                case IMAGE_FILE_MACHINE_AMD64:\n                    *host_arch = ARCH_AMD64;\n                    break;\n\n                default:\n                    *host_arch = ARCH_UNKNOWN;\n                    break;\n            }\n        }\n    }\n    else\n    {\n        BOOL w64 = FALSE;\n        BOOL is_wow64 = IsWow64Process(GetCurrentProcess(), &w64) && w64;\n        if (is_wow64)\n        {\n            /* we are unable to differentiate between arm64 and amd64\n             * machines here, so assume we are running on amd64 */\n            *host_arch = ARCH_AMD64;\n        }\n    }\n#endif /* _ARM64_ */\n}\n\nstatic void\nwin32_print_arch(arch_t arch, struct buffer *out)\n{\n    switch (arch)\n    {\n        case ARCH_X86:\n            buf_printf(out, \"x86\");\n            break;\n\n        case ARCH_AMD64:\n            buf_printf(out, \"amd64\");\n            break;\n\n        case ARCH_ARM64:\n            buf_printf(out, \"arm64\");\n            break;\n\n        case ARCH_UNKNOWN:\n            buf_printf(out, \"(unknown)\");\n            break;\n\n        default:\n            break;\n    }\n}\n\ntypedef LONG(WINAPI *RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);\n\nconst char *\nwin32_version_string(struct gc_arena *gc)\n{\n    HMODULE hMod = GetModuleHandleW(L\"ntdll.dll\");\n    if (!hMod)\n    {\n        return \"N/A\";\n    }\n\n    RtlGetVersionPtr fn = (RtlGetVersionPtr)GetProcAddress(hMod, \"RtlGetVersion\");\n    if (!fn)\n    {\n        return \"N/A\";\n    }\n\n    RTL_OSVERSIONINFOW rovi = { 0 };\n    rovi.dwOSVersionInfoSize = sizeof(rovi);\n    if (fn(&rovi) != 0)\n    {\n        return \"N/A\";\n    }\n\n    struct buffer out = alloc_buf_gc(256, gc);\n\n    buf_printf(&out, \"%lu.%lu.%lu\", rovi.dwMajorVersion, rovi.dwMinorVersion, rovi.dwBuildNumber);\n\n    buf_printf(&out, \",\");\n\n    arch_t process_arch, host_arch;\n    win32_get_arch(&process_arch, &host_arch);\n    win32_print_arch(process_arch, &out);\n\n    if (host_arch != ARCH_NATIVE)\n    {\n        buf_printf(&out, \" running on \");\n        win32_print_arch(host_arch, &out);\n        buf_printf(&out, \" host\");\n    }\n\n    return (const char *)out.data;\n}\n\nbool\nsend_msg_iservice(HANDLE pipe, const void *data, DWORD size, ack_message_t *ack,\n                  const char *context)\n{\n    struct gc_arena gc = gc_new();\n    DWORD len;\n    bool ret = true;\n\n    if (!WriteFile(pipe, data, size, &len, NULL) || !ReadFile(pipe, ack, sizeof(*ack), &len, NULL))\n    {\n        msg(M_WARN, \"%s: could not talk to service: %s [%lu]\", context ? context : \"Unknown\",\n            strerror_win32(GetLastError(), &gc), GetLastError());\n        ret = false;\n    }\n\n    gc_free(&gc);\n    return ret;\n}\n\nbool\nget_openvpn_reg_value(const WCHAR *key, WCHAR *value, DWORD size)\n{\n    WCHAR reg_path[256];\n    HKEY hkey;\n    swprintf(reg_path, _countof(reg_path), L\"SOFTWARE\\\\\" PACKAGE_NAME);\n\n    LONG status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, KEY_READ, &hkey);\n    if (status != ERROR_SUCCESS)\n    {\n        return false;\n    }\n\n    status = RegGetValueW(hkey, NULL, key, RRF_RT_REG_SZ, NULL, (LPBYTE)value, &size);\n\n    RegCloseKey(hkey);\n\n    return status == ERROR_SUCCESS;\n}\n\nstatic void\nset_openssl_env_vars(void)\n{\n    const WCHAR *ssl_fallback_dir = L\"C:\\\\Windows\\\\System32\";\n\n    WCHAR install_path[MAX_PATH] = { 0 };\n    if (!get_openvpn_reg_value(NULL, install_path, _countof(install_path)))\n    {\n        /* if we cannot find installation path from the registry,\n         * use Windows directory as a fallback\n         */\n        swprintf(install_path, _countof(install_path), L\"%ls\", ssl_fallback_dir);\n    }\n\n    if ((install_path[wcslen(install_path) - 1]) == L'\\\\')\n    {\n        install_path[wcslen(install_path) - 1] = L'\\0';\n    }\n\n    static struct\n    {\n        WCHAR *name;\n        WCHAR *value;\n    } ossl_env[] = { { L\"OPENSSL_CONF\", L\"openssl.cnf\" },\n                     { L\"OPENSSL_ENGINES\", L\"engines\" },\n                     { L\"OPENSSL_MODULES\", L\"modules\" } };\n\n    for (size_t i = 0; i < SIZE(ossl_env); ++i)\n    {\n        size_t size = 0;\n\n        _wgetenv_s(&size, NULL, 0, ossl_env[i].name);\n        if (size == 0)\n        {\n            WCHAR val[MAX_PATH] = { 0 };\n            swprintf(val, _countof(val), L\"%ls\\\\ssl\\\\%ls\", install_path, ossl_env[i].value);\n            _wputenv_s(ossl_env[i].name, val);\n        }\n    }\n}\n\nvoid\nwin32_sleep(const int n)\n{\n    if (n < 0)\n    {\n        return;\n    }\n\n    /* Sleep() is not interruptible. Use a WAIT_OBJECT to catch signal */\n\n    if (!HANDLE_DEFINED(win32_signal.in.read))\n    {\n        if (n > 0)\n        {\n            Sleep(n * 1000);\n        }\n        return;\n    }\n\n    update_time();\n    time_t expire = now + n;\n\n    while (expire >= now)\n    {\n        DWORD wait_ms = (DWORD)((expire - now) * 1000);\n        DWORD status = WaitForSingleObject(win32_signal.in.read, wait_ms);\n        if ((status == WAIT_OBJECT_0 && win32_signal_get(&win32_signal)) || status == WAIT_TIMEOUT)\n        {\n            return;\n        }\n\n        update_time();\n\n        if (status != WAIT_OBJECT_0) /* wait failed or some unexpected error ? */\n        {\n            if (expire > now)\n            {\n                Sleep((DWORD)((expire - now) * 1000));\n            }\n            return;\n        }\n    }\n}\n\nbool\nplugin_in_trusted_dir(const WCHAR *plugin_path)\n{\n    /* UNC paths are not allowed */\n    if (wcsncmp(plugin_path, L\"\\\\\\\\\", 2) == 0)\n    {\n        msg(M_WARN, \"UNC paths for plugins are not allowed.\");\n        return false;\n    }\n\n    WCHAR plugin_dir[MAX_PATH] = { 0 };\n\n    /* Attempt to retrieve the trusted plugin directory path from the registry,\n     * using installation path as a fallback */\n    if (!get_openvpn_reg_value(L\"plugin_dir\", plugin_dir, _countof(plugin_dir))\n        && !get_openvpn_reg_value(NULL, plugin_dir, _countof(plugin_dir)))\n    {\n        msg(M_WARN, \"Installation path could not be determined.\");\n    }\n\n    /* Get the system directory */\n    WCHAR system_dir[MAX_PATH] = { 0 };\n    if (GetSystemDirectoryW(system_dir, _countof(system_dir)) == 0)\n    {\n        msg(M_NONFATAL | M_ERRNO, \"Failed to get system directory.\");\n    }\n\n    if ((wcslen(plugin_dir) == 0) && (wcslen(system_dir) == 0))\n    {\n        return false;\n    }\n\n    WCHAR normalized_plugin_dir[MAX_PATH] = { 0 };\n\n    /* Normalize the plugin dir */\n    if (wcslen(plugin_dir) > 0)\n    {\n        if (!GetFullPathNameW(plugin_dir, MAX_PATH, normalized_plugin_dir, NULL))\n        {\n            msg(M_NONFATAL | M_ERRNO, \"Failed to normalize plugin dir.\");\n            return false;\n        }\n    }\n\n    /* Check if the plugin path resides within the plugin/install directory */\n    if ((wcslen(normalized_plugin_dir) > 0)\n        && (wcsnicmp(normalized_plugin_dir, plugin_path, wcslen(normalized_plugin_dir)) == 0))\n    {\n        return true;\n    }\n\n    /* Fallback to the system directory */\n    return wcsnicmp(system_dir, plugin_path, wcslen(system_dir)) == 0;\n}\n\nbool\nprotect_buffer_win32(char *buf, DWORD len)\n{\n    bool ret;\n    if (len % CRYPTPROTECTMEMORY_BLOCK_SIZE)\n    {\n        msg(M_NONFATAL, \"Error: Unable to encrypt memory: buffer size not a multiple of %d\",\n            CRYPTPROTECTMEMORY_BLOCK_SIZE);\n        return false;\n    }\n    ret = CryptProtectMemory(buf, len, CRYPTPROTECTMEMORY_SAME_PROCESS);\n    if (!ret)\n    {\n        msg(M_NONFATAL | M_ERRNO, \"Failed to encrypt memory.\");\n    }\n    return ret;\n}\n\nbool\nunprotect_buffer_win32(char *buf, DWORD len)\n{\n    bool ret;\n    if (len % CRYPTPROTECTMEMORY_BLOCK_SIZE)\n    {\n        msg(M_NONFATAL, \"Error: Unable to decrypt memory: buffer size not a multiple of %d\",\n            CRYPTPROTECTMEMORY_BLOCK_SIZE);\n        return false;\n    }\n    ret = CryptUnprotectMemory(buf, len, CRYPTPROTECTMEMORY_SAME_PROCESS);\n    if (!ret)\n    {\n        msg(M_FATAL | M_ERRNO, \"Failed to decrypt memory.\");\n    }\n    return ret;\n}\n\n#endif /* ifdef _WIN32 */\n"
  },
  {
    "path": "src/openvpn/win32.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef _WIN32\n#ifndef OPENVPN_WIN32_H\n#define OPENVPN_WIN32_H\n\n#include <iphlpapi.h>\n#include <ws2tcpip.h>\n\n#include \"syshead.h\"\n#include \"mtu.h\"\n#include \"openvpn-msg.h\"\n#include \"argv.h\"\n#include \"win32-util.h\"\n\n/* location of executables */\n#define SYS_PATH_ENV_VAR_NAME \\\n    \"SystemRoot\" /* environmental variable name that normally contains the system path */\n#define NETSH_PATH_SUFFIX        \"\\\\system32\\\\netsh.exe\"\n#define WIN_ROUTE_PATH_SUFFIX    \"\\\\system32\\\\route.exe\"\n#define WIN_IPCONFIG_PATH_SUFFIX \"\\\\system32\\\\ipconfig.exe\"\n#define WIN_NET_PATH_SUFFIX      \"\\\\system32\\\\net.exe\"\n#define POWERSHELL_PATH_SUFFIX   \"\\\\system32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe\"\n\n/*\n * Win32-specific OpenVPN code, targeted at the mingw\n * development environment.\n */\n\n/* MSVC headers do not define this macro, so do it here */\n#ifndef IN6_ARE_ADDR_EQUAL\n#define IN6_ARE_ADDR_EQUAL(a, b) \\\n    (memcmp((const void *)(a), (const void *)(b), sizeof(struct in6_addr)) == 0)\n#endif\n\nvoid init_win32(void);\n\nvoid uninit_win32(void);\n\nvoid set_pause_exit_win32(void);\n\nstruct security_attributes\n{\n    SECURITY_ATTRIBUTES sa;\n    SECURITY_DESCRIPTOR sd;\n};\n\n#define HANDLE_DEFINED(h) ((h) != NULL && (h) != INVALID_HANDLE_VALUE)\n\n/*\n * Save old window title.\n */\nstruct window_title\n{\n    bool saved;\n    char old_window_title[256];\n};\n\nstruct rw_handle\n{\n    HANDLE read;\n    HANDLE write;\n};\n\n/*\n * Event-based notification of incoming TCP connections\n */\n\n#define NE32_PERSIST_EVENT (1 << 0)\n#define NE32_WRITE_EVENT   (1 << 1)\n\nstatic inline bool\ndefined_net_event_win32(const struct rw_handle *event)\n{\n    return event->read != NULL;\n}\n\nvoid init_net_event_win32(struct rw_handle *event, long network_events, socket_descriptor_t sd,\n                          unsigned int flags);\n\nlong reset_net_event_win32(struct rw_handle *event, socket_descriptor_t sd);\n\nvoid close_net_event_win32(struct rw_handle *event, socket_descriptor_t sd, unsigned int flags);\n\n/*\n * A stateful variant of the net_event_win32 functions above\n */\n\nstruct net_event_win32\n{\n    struct rw_handle handle;\n    socket_descriptor_t sd;\n    long event_mask;\n};\n\nvoid net_event_win32_init(struct net_event_win32 *ne);\n\nvoid net_event_win32_start(struct net_event_win32 *ne, long network_events, socket_descriptor_t sd);\n\nvoid net_event_win32_reset(struct net_event_win32 *ne);\n\nvoid net_event_win32_reset_write(struct net_event_win32 *ne);\n\nvoid net_event_win32_stop(struct net_event_win32 *ne);\n\nvoid net_event_win32_close(struct net_event_win32 *ne);\n\nstatic inline bool\nnet_event_win32_defined(const struct net_event_win32 *ne)\n{\n    return defined_net_event_win32(&ne->handle);\n}\n\nstatic inline struct rw_handle *\nnet_event_win32_get_event(struct net_event_win32 *ne)\n{\n    return &ne->handle;\n}\n\nstatic inline long\nnet_event_win32_get_event_mask(const struct net_event_win32 *ne)\n{\n    return ne->event_mask;\n}\n\nstatic inline void\nnet_event_win32_clear_selected_events(struct net_event_win32 *ne, long selected_events)\n{\n    ne->event_mask &= ~selected_events;\n}\n\n/*\n * Signal handling\n */\nstruct win32_signal\n{\n#define WSO_MODE_UNDEF   0\n#define WSO_MODE_SERVICE 1\n#define WSO_MODE_CONSOLE 2\n    int mode;\n    struct rw_handle in;\n    DWORD console_mode_save;\n    bool console_mode_save_defined;\n};\n\nextern struct win32_signal win32_signal; /* static/global */\nextern struct window_title window_title; /* static/global */\n\nvoid win32_signal_clear(struct win32_signal *ws);\n\n/* win32_signal_open startup type */\n#define WSO_NOFORCE       0\n#define WSO_FORCE_SERVICE 1\n#define WSO_FORCE_CONSOLE 2\n\nvoid win32_signal_open(struct win32_signal *ws, int force, /* set to WSO force parm */\n                       const char *exit_event_name, bool exit_event_initial_state);\n\nvoid win32_signal_close(struct win32_signal *ws);\n\nint win32_signal_get(struct win32_signal *ws);\n\nvoid win32_pause(struct win32_signal *ws);\n\nbool win32_service_interrupt(struct win32_signal *ws);\n\n/*\n * Set the text on the window title bar\n */\n\nvoid window_title_clear(struct window_title *wt);\n\nvoid window_title_save(struct window_title *wt);\n\nvoid window_title_restore(const struct window_title *wt);\n\nvoid window_title_generate(const char *title);\n\n/*\n * We try to do all Win32 I/O using overlapped\n * (i.e. asynchronous) I/O for a performance win.\n */\nstruct overlapped_io\n{\n#define IOSTATE_INITIAL          0\n#define IOSTATE_QUEUED           1 /* overlapped I/O has been queued */\n#define IOSTATE_IMMEDIATE_RETURN 2 /* I/O function returned immediately without queueing */\n    int iostate;\n    OVERLAPPED overlapped;\n    DWORD size;\n    DWORD flags;\n    int status;\n    bool addr_defined;\n    union\n    {\n        struct sockaddr_in addr;\n        struct sockaddr_in6 addr6;\n    };\n    int addrlen;\n    struct buffer buf_init;\n    struct buffer buf;\n};\n\nvoid overlapped_io_init(struct overlapped_io *o, const struct frame *frame, BOOL event_state);\n\nvoid overlapped_io_close(struct overlapped_io *o);\n\nstatic inline bool\noverlapped_io_active(struct overlapped_io *o)\n{\n    return o->iostate == IOSTATE_QUEUED || o->iostate == IOSTATE_IMMEDIATE_RETURN;\n}\n\nchar *overlapped_io_state_ascii(const struct overlapped_io *o);\n\n/*\n * Use to control access to resources that only one\n * OpenVPN process on a given machine can access at\n * a given time.\n */\n\nstruct semaphore\n{\n    const char *name;\n    bool locked;\n    HANDLE hand;\n};\n\nvoid semaphore_clear(struct semaphore *s);\n\nvoid semaphore_open(struct semaphore *s, const char *name);\n\nbool semaphore_lock(struct semaphore *s, int timeout_milliseconds);\n\nvoid semaphore_release(struct semaphore *s);\n\nvoid semaphore_close(struct semaphore *s);\n\n/*\n * Special global semaphore used to protect network\n * shell commands from simultaneous instantiation.\n *\n * It seems you can't run more than one instance\n * of netsh on the same machine at the same time.\n */\n\nextern struct semaphore netcmd_semaphore;\nvoid netcmd_semaphore_init(void);\n\nvoid netcmd_semaphore_close(void);\n\nvoid netcmd_semaphore_lock(void);\n\nvoid netcmd_semaphore_release(void);\n\n/* Set Win32 security attributes structure to allow all access */\nbool init_security_attributes_allow_all(struct security_attributes *obj);\n\n/* add constant environmental variables needed by Windows */\nstruct env_set;\n\n/* get and set the current windows system path */\nvoid set_win_sys_path(const char *newpath, struct env_set *es);\n\nvoid set_win_sys_path_via_env(struct env_set *es);\n\nchar *get_win_sys_path(void);\n\n/* call self in a subprocess */\nvoid fork_to_self(const char *cmdline);\n\nbool win_wfp_block(const NET_IFINDEX index, const HANDLE msg_channel, BOOL dns_only);\n\nbool win_wfp_uninit(const NET_IFINDEX index, const HANDLE msg_channel);\n\n/**\n * @brief Get Windows version string with architecture info.\n *\n * @param gc gc arena to allocate string.\n * @return Version string, or \"N/A\" on failure.\n */\nconst char *win32_version_string(struct gc_arena *gc);\n\n/**\n * Send the \\p size bytes in buffer \\p data to the interactive service \\p pipe\n * and read the result in \\p ack.\n * The string in \\p context is used to prefix error messages.\n *\n * @return true on success, false on communication error\n */\nbool send_msg_iservice(HANDLE pipe, const void *data, DWORD size, ack_message_t *ack,\n                       const char *context);\n\n/*\n * Attempt to simulate fork/execve on Windows\n */\nint openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags);\n\n/* Sleep that can be interrupted by signals and exit event */\nvoid win32_sleep(const int n);\n\n/**\n * @brief Fetches a registry value for OpenVPN registry key.\n *\n * @param key Registry value name to fetch.\n * @param value Buffer to store the fetched string value.\n * @param size Size of `value` buffer in bytes.\n * @return `true` if successful, `false` otherwise.\n */\nbool get_openvpn_reg_value(const WCHAR *key, WCHAR *value, DWORD size);\n\n/**\n * @brief Checks if a plugin is located in a trusted directory.\n *\n * Verifies the plugin's path against a trusted directory, which is:\n *\n * - \"plugin_dir\" registry value or installation path, if the registry key is missing\n * - system directory\n *\n * UNC paths are explicitly disallowed.\n *\n * @param plugin_path Normalized path to the plugin.\n * @return \\c true if the plugin is in a trusted directory and not a UNC path; \\c false otherwise.\n */\nbool plugin_in_trusted_dir(const WCHAR *plugin_path);\n\n/**\n * Encrypt a region of memory using CryptProtectMemory()\n * with access restricted to the current process.\n *\n * - buf   pointer to the memory\n * - len   number of bytes to encrypt -- must be a multiple of\n *         CRYPTPROTECTMEMORY_BLOCK_SIZE = 16\n */\nbool protect_buffer_win32(char *buf, DWORD len);\n\n/**\n * Decrypt a previously encrypted region of memory using CryptUnProtectMemory()\n * with access restricted to the current process.\n *\n * - buf   pointer to the memory\n * - len   number of bytes to encrypt -- must be a multiple of\n *         CRYPTPROTECTMEMORY_BLOCK_SIZE = 16\n */\nbool unprotect_buffer_win32(char *buf, DWORD len);\n\n#endif /* ifndef OPENVPN_WIN32_H */\n#endif /* ifdef _WIN32 */\n"
  },
  {
    "path": "src/openvpn/xkey_common.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2021-2026 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by the\n *  Free Software Foundation, either version 2 of the License,\n *  or (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef XKEY_COMMON_H_\n#define XKEY_COMMON_H_\n\n/* Guard to only enable if OpenSSL is used and not trigger an error if mbed\n * TLS is compiled without OpenSSL being installed */\n#if defined(ENABLE_CRYPTO_OPENSSL)\n#include <openssl/opensslv.h>\n#if OPENSSL_VERSION_NUMBER >= 0x30000010L && !defined(DISABLE_XKEY_PROVIDER)\n#define HAVE_XKEY_PROVIDER 1\n#include <openssl/provider.h>\n#include <openssl/core_dispatch.h>\n#include <openssl/ecdsa.h>\n\n/**\n * Initialization function for OpenVPN external key provider for OpenSSL\n * Follows the function signature of OSSL_PROVIDER init()\n */\nOSSL_provider_init_fn xkey_provider_init;\n\n#define XKEY_PROV_PROPS \"provider=ovpn.xkey\"\n\n/**\n * Struct to encapsulate signature algorithm parameters to pass\n * to sign operation.\n */\ntypedef struct\n{\n    const char *padmode; /**< \"pkcs1\", \"pss\" or \"none\" */\n    const char *mdname;  /**< \"SHA256\" or \"SHA2-256\" etc. */\n    const char *saltlen; /**< \"digest\", \"auto\" or \"max\" */\n    const char *keytype; /**< \"EC\" or \"RSA\" */\n    const char *op;      /**< \"Sign\" or \"DigestSign\" */\n} XKEY_SIGALG;\n\n/**\n * Callback for sign operation -- must be implemented for each backend and\n * is used in xkey_signature_sign(), or set when loading the key.\n * (custom key loading not yet implemented).\n *\n * @param handle opaque key handle provided by the backend -- could be null\n *               or unused for management interface.\n * @param sig    On return caller should fill this with the signature\n * @param siglen On entry *siglen has max size of sig and on return must be\n *               set to the actual size of the signature\n * @param tbs    buffer to sign\n * @param tbslen size of data in tbs buffer\n * @sigalg       contains the signature algorithm parameters\n *\n * @returns 1 on success, 0 on error.\n *\n * If sigalg.op = \"Sign\", the data in tbs is the digest. If sigalg.op = \"DigestSign\"\n * it is the message that the backend should hash wih appropriate hash algorithm before\n * signing. In the former case no DigestInfo header is added to tbs. This is\n * unlike the deprecated RSA_sign callback which provides encoded digest.\n * For RSA_PKCS1 signatures, the external signing function must encode the digest\n * before signing. The digest algorithm used (or to be used) is passed in the sigalg\n * structure.\n */\ntypedef int(XKEY_EXTERNAL_SIGN_fn)(void *handle, unsigned char *sig, size_t *siglen,\n                                   const unsigned char *tbs, size_t tbslen, XKEY_SIGALG sigalg);\n/**\n * Signature of private key free function callback used\n * to free the opaque private key handle obtained from the\n * backend. Not required for management-external-key.\n */\ntypedef void(XKEY_PRIVKEY_FREE_fn)(void *handle);\n\n/**\n * Generate an encapsulated EVP_PKEY for management-external-key\n *\n * @param libctx library context in which xkey provider has been loaded\n * @param pubkey corresponding pubkey in the default provider's context\n *\n * @returns a new EVP_PKEY in the provider's keymgmt context.\n * The pubkey is up-refd if retained -- the caller can free it after return\n */\nEVP_PKEY *xkey_load_management_key(OSSL_LIB_CTX *libctx, EVP_PKEY *pubkey);\n\n/**\n * Add PKCS1 DigestInfo to tbs and return the result in *enc.\n *\n * @param enc           pointer to output buffer\n * @param enc_len       capacity in bytes of output buffer\n * @param mdname        name of the hash algorithm (SHA256, SHA1 etc.)\n * @param tbs           pointer to digest to be encoded\n * @param tbslen        length of data in bytes\n *\n * @return              false on error, true  on success\n *\n * On return enc_len is  set to actual size of the result.\n * enc is NULL or enc_len is not enough to store the result, it is set\n * to the required size and false is returned.\n *\n */\nbool encode_pkcs1(unsigned char *enc, size_t *enc_len, const char *mdname, const unsigned char *tbs,\n                  size_t tbslen);\n\n/**\n * Compute message digest\n *\n * @param src           pointer to message to be hashed\n * @param srclen        length of data in bytes\n * @param buf           pointer to output buffer\n * @param buflen        *buflen = capacity in bytes of output buffer\n * @param mdname        name of the hash algorithm (SHA256, SHA1 etc.)\n *\n * @return              false on error, true  on success\n *\n * On successful return *buflen is set to the actual size of the result.\n * TIP: EVP_MD_MAX_SIZE should be enough capacity of buf for al algorithms.\n */\nint xkey_digest(const unsigned char *src, size_t srclen, unsigned char *buf, size_t *buflen,\n                const char *mdname);\n\n/**\n * Load a generic external key with custom sign and free ops\n *\n * @param libctx    library context in which xkey provider has been loaded\n * @param handle    an opaque handle to the backend -- passed to alll callbacks\n * @param pubkey    corresponding pubkey in the default provider's context\n * @param sign_op   private key signature operation to callback\n * @param sign_op   private key signature operation to callback\n *\n * @returns a new EVP_PKEY in the provider's keymgmt context.\n * IMPORTANT: a reference to the handle is retained by the provider and\n * relased by calling free_op. The caller should not free it.\n */\nEVP_PKEY *xkey_load_generic_key(OSSL_LIB_CTX *libctx, void *handle, EVP_PKEY *pubkey,\n                                XKEY_EXTERNAL_SIGN_fn *sign_op, XKEY_PRIVKEY_FREE_fn *free_op);\n\nextern OSSL_LIB_CTX *tls_libctx; /* Global */\n\n/**\n * Maximum salt length for PSS signature.\n *\n * @param modBits    Number of bits in RSA modulus\n * @param hLen       Length of digest to be signed\n * @returns the maximum allowed salt length. Caller must check it's not < 0.\n */\nstatic inline int\nxkey_max_saltlen(int modBits, int hLen)\n{\n    int emLen = (modBits - 1 + 7) / 8; /* ceil((modBits - 1)/8) */\n\n    return emLen - hLen - 2;\n}\n\n/**\n * @brief Convert raw ECDSA signature to DER encoded\n * This function converts ECDSA signature provided as a buffer\n * containing r|s to DER encoded ASN.1 expected by OpenSSL\n * @param buf       signature containing r|s.\n * @param len       size of signature in bytes\n * @param capacity  max space in the buffer buf in bytes\n * @returns the size of the converted signature or <= 0 on error.\n * On success, buf is overwritten by its DER encoding\n */\nint ecdsa_bin2der(unsigned char *buf, int len, size_t capacity);\n\n#endif /* HAVE_XKEY_PROVIDER */\n\n#endif /* ENABLE_CRYPTO_OPENSSL */\n\n#endif /* XKEY_COMMON_H_ */\n"
  },
  {
    "path": "src/openvpn/xkey_helper.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2021-2026 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by the\n *  Free Software Foundation, either version 2 of the License,\n *  or (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n\n#include \"syshead.h\"\n#include \"error.h\"\n#include \"buffer.h\"\n#include \"xkey_common.h\"\n#include \"manage.h\"\n#include \"base64.h\"\n\n#ifdef HAVE_XKEY_PROVIDER\n\n#include <openssl/provider.h>\n#include <openssl/params.h>\n#include <openssl/core_dispatch.h>\n#include <openssl/core_object.h>\n#include <openssl/core_names.h>\n#include <openssl/store.h>\n#include <openssl/evp.h>\n#include <openssl/err.h>\n\nstatic const char *const props = XKEY_PROV_PROPS;\n\nXKEY_EXTERNAL_SIGN_fn xkey_management_sign;\n\nstatic void\nprint_openssl_errors(void)\n{\n    unsigned long e;\n    while ((e = ERR_get_error()))\n    {\n        msg(M_WARN, \"OpenSSL error %lu: %s\", e, ERR_error_string(e, NULL));\n    }\n}\n\n/** helper to compute digest */\nint\nxkey_digest(const unsigned char *src, size_t srclen, unsigned char *buf, size_t *buflen,\n            const char *mdname)\n{\n    dmsg(D_XKEY, \"In xkey_digest\");\n    EVP_MD *md = EVP_MD_fetch(NULL, mdname, NULL); /* from default context */\n    if (!md)\n    {\n        msg(M_WARN, \"WARN: xkey_digest: MD_fetch failed for <%s>\", mdname);\n        return 0;\n    }\n\n    unsigned int len = (unsigned int)*buflen;\n    if (EVP_Digest(src, srclen, buf, &len, md, NULL) != 1)\n    {\n        msg(M_WARN, \"WARN: xkey_digest: EVP_Digest failed\");\n        return 0;\n    }\n    EVP_MD_free(md);\n\n    *buflen = len;\n    return 1;\n}\n\n#ifdef ENABLE_MANAGEMENT\n/**\n * Load external key for signing via management interface.\n * The public key must be passed in by the caller as we may not\n * be able to get it from the management.\n * Returns an EVP_PKEY object attached to xkey provider.\n * Caller must free it when no longer needed.\n */\nEVP_PKEY *\nxkey_load_management_key(OSSL_LIB_CTX *libctx, EVP_PKEY *pubkey)\n{\n    ASSERT(pubkey);\n\n    /* Management interface doesn't require any handle to be\n     * stored in the key. We use a dummy pointer as we do need a\n     * non-NULL value to indicate private key is available.\n     */\n    void *dummy = &\"dummy\";\n\n    XKEY_EXTERNAL_SIGN_fn *sign_op = xkey_management_sign;\n\n    return xkey_load_generic_key(libctx, dummy, pubkey, sign_op, NULL);\n}\n#endif\n\n/**\n * Load a generic key into the xkey provider.\n * Returns an EVP_PKEY object attached to xkey provider.\n * Caller must free it when no longer needed.\n */\nEVP_PKEY *\nxkey_load_generic_key(OSSL_LIB_CTX *libctx, void *handle, EVP_PKEY *pubkey,\n                      XKEY_EXTERNAL_SIGN_fn *sign_op, XKEY_PRIVKEY_FREE_fn *free_op)\n{\n    EVP_PKEY *pkey = NULL;\n    const char *origin = \"external\";\n\n    /* UTF8 string pointers in here are only read from, so cast is safe */\n    OSSL_PARAM params[] = {\n        { \"xkey-origin\", OSSL_PARAM_UTF8_STRING, (char *)origin, 0, 0 },\n        { \"pubkey\", OSSL_PARAM_OCTET_STRING, &pubkey, sizeof(pubkey), 0 },\n        { \"handle\", OSSL_PARAM_OCTET_PTR, &handle, sizeof(handle), 0 },\n        { \"sign_op\", OSSL_PARAM_OCTET_PTR, (void **)&sign_op, sizeof(sign_op), 0 },\n        { \"free_op\", OSSL_PARAM_OCTET_PTR, (void **)&free_op, sizeof(free_op), 0 },\n        { NULL, 0, NULL, 0, 0 }\n    };\n\n    /* Do not use EVP_PKEY_new_from_pkey as that will take keymgmt from pubkey */\n    EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(libctx, EVP_PKEY_get0_type_name(pubkey), props);\n    if (!ctx || EVP_PKEY_fromdata_init(ctx) != 1\n        || EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) != 1)\n    {\n        print_openssl_errors();\n        msg(M_FATAL, \"OpenSSL error: failed to load key into ovpn.xkey provider\");\n    }\n    if (ctx)\n    {\n        EVP_PKEY_CTX_free(ctx);\n    }\n\n    return pkey;\n}\n\n#ifdef ENABLE_MANAGEMENT\n/**\n * Signature callback for xkey_provider with management-external-key\n *\n * @param handle        Unused -- may be null\n * @param sig           On successful return signature is in sig.\n * @param siglen        On entry *siglen has length of buffer sig,\n *                      on successful return size of signature\n * @param tbs           hash or message to be signed\n * @param tbslen        len of data in dgst\n * @param sigalg        extra signature parameters\n *\n * @return              signature length or -1 on error.\n */\nint\nxkey_management_sign(void *unused, unsigned char *sig, size_t *siglen, const unsigned char *tbs,\n                     size_t tbslen, XKEY_SIGALG alg)\n{\n    dmsg(D_XKEY, \"In xkey_management_sign with keytype = %s, op = %s\", alg.keytype, alg.op);\n\n    (void)unused;\n    char alg_str[128];\n    unsigned char buf[EVP_MAX_MD_SIZE]; /* for computing digest if required */\n    size_t buflen = sizeof(buf);\n\n    unsigned char enc[EVP_MAX_MD_SIZE + 32]; /* 32 bytes enough for digest info structure */\n    size_t enc_len = sizeof(enc);\n\n    unsigned int flags = management->settings.flags;\n    bool is_message = !strcmp(alg.op, \"DigestSign\"); /* tbs is message, not digest */\n\n    /* if management client cannot do digest -- we do it here */\n    if (!strcmp(alg.op, \"DigestSign\") && !(flags & MF_EXTERNAL_KEY_DIGEST)\n        && strcmp(alg.mdname, \"none\"))\n    {\n        dmsg(D_XKEY, \"xkey_management_sign: computing digest\");\n        if (xkey_digest(tbs, tbslen, buf, &buflen, alg.mdname))\n        {\n            tbs = buf;\n            tbslen = buflen;\n            alg.op = \"Sign\";\n            is_message = false;\n        }\n        else\n        {\n            return 0;\n        }\n    }\n\n    if (!strcmp(alg.keytype, \"EC\"))\n    {\n        if (!strcmp(alg.op, \"Sign\"))\n        {\n            strncpynt(alg_str, \"ECDSA\", sizeof(alg_str));\n        }\n        else\n        {\n            snprintf(alg_str, sizeof(alg_str), \"ECDSA,hashalg=%s\", alg.mdname);\n        }\n    }\n    else if (!strcmp(alg.keytype, \"ED448\") || !strcmp(alg.keytype, \"ED25519\"))\n    {\n        strncpynt(alg_str, alg.keytype, sizeof(alg_str));\n    }\n    /* else assume RSA key */\n    else if (!strcmp(alg.padmode, \"pkcs1\") && (flags & MF_EXTERNAL_KEY_PKCS1PAD))\n    {\n        /* For Sign, management interface expects a pkcs1 encoded digest -- add it */\n        if (!strcmp(alg.op, \"Sign\"))\n        {\n            if (!encode_pkcs1(enc, &enc_len, alg.mdname, tbs, tbslen))\n            {\n                return 0;\n            }\n            tbs = enc;\n            tbslen = enc_len;\n            strncpynt(alg_str, \"RSA_PKCS1_PADDING\", sizeof(alg_str));\n        }\n        /* For undigested message, add hashalg=digest parameter */\n        else\n        {\n            snprintf(alg_str, sizeof(alg_str), \"%s,hashalg=%s\", \"RSA_PKCS1_PADDING\", alg.mdname);\n        }\n    }\n    else if (!strcmp(alg.padmode, \"none\") && (flags & MF_EXTERNAL_KEY_NOPADDING)\n             && !strcmp(alg.op, \"Sign\")) /* NO_PADDING requires digested data */\n    {\n        strncpynt(alg_str, \"RSA_NO_PADDING\", sizeof(alg_str));\n    }\n    else if (!strcmp(alg.padmode, \"pss\") && (flags & MF_EXTERNAL_KEY_PSSPAD))\n    {\n        snprintf(alg_str, sizeof(alg_str), \"%s,hashalg=%s,saltlen=%s\", \"RSA_PKCS1_PSS_PADDING\",\n                 alg.mdname, alg.saltlen);\n    }\n    else\n    {\n        msg(M_NONFATAL, \"RSA padding mode not supported by management-client <%s>\", alg.padmode);\n        return 0;\n    }\n\n    if (is_message)\n    {\n        strncat(alg_str, \",data=message\", sizeof(alg_str) - strlen(alg_str) - 1);\n    }\n\n    dmsg(D_LOW, \"xkey management_sign: requesting sig with algorithm <%s>\", alg_str);\n\n    char *in_b64 = NULL;\n    char *out_b64 = NULL;\n    int len = -1;\n\n    int bencret = openvpn_base64_encode(tbs, (int)tbslen, &in_b64);\n\n    if (management && bencret > 0)\n    {\n        out_b64 = management_query_pk_sig(management, in_b64, alg_str);\n    }\n    if (out_b64)\n    {\n        len = openvpn_base64_decode(out_b64, sig, (int)*siglen);\n    }\n    free(in_b64);\n    free(out_b64);\n\n    *siglen = (len > 0) ? len : 0;\n\n    return (*siglen > 0);\n}\n#endif /* ENABLE MANAGEMENT */\n\n/**\n * Add PKCS1 DigestInfo to tbs and return the result in *enc.\n *\n * @param enc           pointer to output buffer\n * @param enc_len       capacity in bytes of output buffer\n * @param mdname        name of the hash algorithm (SHA256, SHA1 etc.)\n * @param tbs           pointer to digest to be encoded\n * @param tbslen        length of data in bytes\n *\n * @return              false on error, true  on success\n *\n * On return enc_len is  set to actual size of the result.\n * If enc is NULL or enc_len is not enough to store the result, it is set\n * to the required size and false is returned.\n */\nbool\nencode_pkcs1(unsigned char *enc, size_t *enc_len, const char *mdname, const unsigned char *tbs,\n             size_t tbslen)\n{\n    ASSERT(enc_len != NULL);\n    ASSERT(tbs != NULL);\n\n    /* Tabulate the digest info header for expected hash algorithms\n     * These were pre-computed using the DigestInfo definition:\n     * DigestInfo ::= SEQUENCE {\n     *    digestAlgorithm DigestAlgorithmIdentifier,\n     *    digest Digest }\n     * Also see the table in RFC 8017 section 9.2, Note 1.\n     */\n\n    const unsigned char sha1[] = { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,\n                                   0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };\n    const unsigned char sha256[] = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,\n                                     0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 };\n    const unsigned char sha384[] = { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,\n                                     0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30 };\n    const unsigned char sha512[] = { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,\n                                     0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40 };\n    const unsigned char sha224[] = { 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,\n                                     0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c };\n    const unsigned char sha512_224[] = { 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,\n                                         0x65, 0x03, 0x04, 0x02, 0x05, 0x05, 0x00, 0x04, 0x1c };\n    const unsigned char sha512_256[] = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,\n                                         0x65, 0x03, 0x04, 0x02, 0x06, 0x05, 0x00, 0x04, 0x20 };\n\n    typedef struct\n    {\n        const int nid;\n        const unsigned char *header;\n        size_t sz;\n    } DIG_INFO;\n\n#define MAKE_DI(x) { NID_##x, x, sizeof(x) }\n\n    /* clang-format off */\n    DIG_INFO dinfo[] = {\n        MAKE_DI(sha1),   MAKE_DI(sha256),     MAKE_DI(sha384),     MAKE_DI(sha512),\n        MAKE_DI(sha224), MAKE_DI(sha512_224), MAKE_DI(sha512_256), { 0, NULL, 0 }\n    };\n    /* clang-format on */\n\n    size_t out_len = 0;\n    bool ret = false;\n\n    int nid = OBJ_sn2nid(mdname);\n    if (nid == NID_undef)\n    {\n        /* try harder  -- name variants like SHA2-256 doesn't work */\n        nid = EVP_MD_type(EVP_get_digestbyname(mdname));\n        if (nid == NID_undef)\n        {\n            msg(M_WARN, \"Error: encode_pkcs11: invalid digest name <%s>\", mdname);\n            goto done;\n        }\n    }\n\n    if (tbslen != (size_t)EVP_MD_size(EVP_get_digestbyname(mdname)))\n    {\n        msg(M_WARN, \"Error: encode_pkcs11: invalid input length <%zu>\", tbslen);\n        goto done;\n    }\n\n    if (nid == NID_md5_sha1) /* no encoding needed -- just copy */\n    {\n        if (enc && (*enc_len >= tbslen))\n        {\n            memcpy(enc, tbs, tbslen);\n            ret = true;\n        }\n        out_len = tbslen;\n        goto done;\n    }\n\n    /* locate entry for nid in dinfo table */\n    DIG_INFO *di = dinfo;\n    while ((di->nid != nid) && (di->nid != 0))\n    {\n        di++;\n    }\n    if (di->nid != nid) /* not found in our table */\n    {\n        msg(M_WARN, \"Error: encode_pkcs11: unsupported hash algorithm <%s>\", mdname);\n        goto done;\n    }\n\n    out_len = tbslen + di->sz;\n\n    if (enc && (out_len <= *enc_len))\n    {\n        /* combine header and digest */\n        memcpy(enc, di->header, di->sz);\n        memcpy(enc + di->sz, tbs, tbslen);\n        dmsg(D_XKEY, \"encode_pkcs1: digest length = %zu encoded length = %zu\", tbslen, out_len);\n        ret = true;\n    }\n\ndone:\n    *enc_len = out_len; /* assignment safe as out_len is > 0 at this point */\n\n    return ret;\n}\n\n/**\n * Helper to convert ECDSA signature with r and s concatenated\n * to a DER encoded format used by OpenSSL.\n * Returns the size of the converted signature or <= 0 on error.\n * On success, buf is overwritten by the DER encoded signature.\n */\nint\necdsa_bin2der(unsigned char *buf, int len, size_t capacity)\n{\n    ECDSA_SIG *ecsig = NULL;\n    int rlen = len / 2;\n    BIGNUM *r = BN_bin2bn(buf, rlen, NULL);\n    BIGNUM *s = BN_bin2bn(buf + rlen, rlen, NULL);\n    if (!r || !s)\n    {\n        goto err;\n    }\n    ecsig = ECDSA_SIG_new(); /* this does not allocate r, s */\n    if (!ecsig)\n    {\n        goto err;\n    }\n    if (!ECDSA_SIG_set0(ecsig, r, s)) /* ecsig takes ownership of r and s */\n    {\n        ECDSA_SIG_free(ecsig);\n        goto err;\n    }\n\n    int derlen = i2d_ECDSA_SIG(ecsig, NULL);\n    if (derlen > (int)capacity)\n    {\n        ECDSA_SIG_free(ecsig);\n        msg(M_NONFATAL, \"Error: DER encoded ECDSA signature is too long (%d)\", derlen);\n        return 0;\n    }\n    derlen = i2d_ECDSA_SIG(ecsig, &buf);\n    ECDSA_SIG_free(ecsig);\n    return derlen;\n\nerr:\n    BN_free(r); /* it is ok to free NULL BN */\n    BN_free(s);\n    return 0;\n}\n\n#endif /* HAVE_XKEY_PROVIDER */\n"
  },
  {
    "path": "src/openvpn/xkey_provider.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2021-2026 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by the\n *  Free Software Foundation, either version 2 of the License,\n *  or (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n\n#include \"syshead.h\"\n#include \"error.h\"\n#include \"buffer.h\"\n#include \"xkey_common.h\"\n\n#ifdef HAVE_XKEY_PROVIDER\n\n#include <openssl/provider.h>\n#include <openssl/params.h>\n#include <openssl/core_dispatch.h>\n#include <openssl/core_object.h>\n#include <openssl/core_names.h>\n#include <openssl/store.h>\n#include <openssl/evp.h>\n#include <openssl/err.h>\n\n/* A descriptive name */\nstatic const char *provname = \"OpenVPN External Key Provider\";\n\ntypedef struct\n{\n    OSSL_LIB_CTX *libctx; /**< a child libctx for our own use */\n} XKEY_PROVIDER_CTX;\n\n/* helper to print debug messages */\n#define xkey_dmsg(f, ...)                                     \\\n    do                                                        \\\n    {                                                         \\\n        dmsg(f | M_NOLF, \"xkey_provider: In %s: \", __func__); \\\n        dmsg(f | M_NOPREFIX, __VA_ARGS__);                    \\\n    } while (0)\n\ntypedef enum\n{\n    ORIGIN_UNDEFINED = 0,\n    OPENSSL_NATIVE, /* native key imported in */\n    EXTERNAL_KEY\n} XKEY_ORIGIN;\n\n/**\n * XKEY_KEYDATA: Our keydata encapsulation:\n *\n * We keep an opaque handle provided by the backend for the loaded\n * key. It's passed back to the backend for any operation on private\n * keys --- in practice, sign() op only.\n *\n * We also keep the public key in the form of a native OpenSSL EVP_PKEY.\n * This allows us to do all public ops by calling ops in the default provider.\n * Both these are references retained by us and freed when the key is\n * destroyed. As the pubkey is native, we free it using EVP_PKEY_free().\n * To free the handle we call the backend if a free function\n * has been set for that key. It could be set when the key is\n * created/imported.\n * For native keys, there is no need to free the handle as its either\n * NULL of the same as the pubkey which we always free.\n */\ntypedef struct\n{\n    /** opaque handle dependent on KEY_ORIGIN -- could be NULL */\n    void *handle;\n    /** associated public key as an openvpn native key */\n    EVP_PKEY *pubkey;\n    /** origin of key -- native or external */\n    XKEY_ORIGIN origin;\n    /** sign function in backend to call */\n    XKEY_EXTERNAL_SIGN_fn *sign;\n    /** keydata handle free function of backend */\n    XKEY_PRIVKEY_FREE_fn *free;\n    XKEY_PROVIDER_CTX *prov;\n    int refcount; /**< reference count */\n} XKEY_KEYDATA;\n\nstatic inline const char *\nget_keytype(const XKEY_KEYDATA *key)\n{\n    int keytype = key->pubkey ? EVP_PKEY_get_id(key->pubkey) : 0;\n\n    switch (keytype)\n    {\n        case EVP_PKEY_RSA:\n            return \"RSA\";\n\n        case EVP_PKEY_ED448:\n            return \"ED448\";\n\n        case EVP_PKEY_ED25519:\n            return \"ED25519\";\n\n        default:\n            return \"EC\";\n    }\n}\n\n\nstatic int\nKEYSIZE(const XKEY_KEYDATA *key)\n{\n    return key->pubkey ? EVP_PKEY_get_size(key->pubkey) : 0;\n}\n\n/**\n * Helper sign function for native keys\n * Implemented using OpenSSL calls.\n */\nint xkey_native_sign(XKEY_KEYDATA *key, unsigned char *sig, size_t *siglen,\n                     const unsigned char *tbs, size_t tbslen, XKEY_SIGALG sigalg);\n\n\n/* keymgmt provider */\n\n/* keymgmt callbacks we implement */\nstatic OSSL_FUNC_keymgmt_new_fn keymgmt_new;\nstatic OSSL_FUNC_keymgmt_free_fn keymgmt_free;\nstatic OSSL_FUNC_keymgmt_load_fn keymgmt_load;\nstatic OSSL_FUNC_keymgmt_has_fn keymgmt_has;\nstatic OSSL_FUNC_keymgmt_match_fn keymgmt_match;\nstatic OSSL_FUNC_keymgmt_import_fn rsa_keymgmt_import;\nstatic OSSL_FUNC_keymgmt_import_fn ec_keymgmt_import;\nstatic OSSL_FUNC_keymgmt_import_types_fn keymgmt_import_types;\nstatic OSSL_FUNC_keymgmt_get_params_fn keymgmt_get_params;\nstatic OSSL_FUNC_keymgmt_gettable_params_fn keymgmt_gettable_params;\nstatic OSSL_FUNC_keymgmt_set_params_fn keymgmt_set_params;\nstatic OSSL_FUNC_keymgmt_query_operation_name_fn rsa_keymgmt_name;\nstatic OSSL_FUNC_keymgmt_query_operation_name_fn ec_keymgmt_name;\n\nstatic int keymgmt_import_helper(XKEY_KEYDATA *key, const OSSL_PARAM params[]);\n\nstatic XKEY_KEYDATA *\nkeydata_new(void)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    XKEY_KEYDATA *key = OPENSSL_zalloc(sizeof(*key));\n    if (!key)\n    {\n        msg(M_NONFATAL, \"xkey_keydata_new: out of memory\");\n    }\n\n    return key;\n}\n\nstatic void\nkeydata_free(XKEY_KEYDATA *key)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    if (!key || key->refcount-- > 0) /* free when refcount goes to zero */\n    {\n        return;\n    }\n    if (key->free && key->handle)\n    {\n        key->free(key->handle);\n        key->handle = NULL;\n    }\n    if (key->pubkey)\n    {\n        EVP_PKEY_free(key->pubkey);\n    }\n    OPENSSL_free(key);\n}\n\nstatic void *\nkeymgmt_new(void *provctx)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    XKEY_KEYDATA *key = keydata_new();\n    if (key)\n    {\n        key->prov = provctx;\n    }\n\n    return key;\n}\n\nstatic void *\nkeymgmt_load(const void *reference, size_t reference_sz)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    return NULL;\n}\n\n/**\n * Key import function\n * When key operations like sign/verify are done in our context\n * the key gets imported into us. We will also use import to\n * load an external key into the provider.\n *\n * For native keys we get called with standard OpenSSL params\n * appropriate for the key. We just use it to create a native\n * EVP_PKEY from params and assign to keydata->handle.\n *\n * For non-native keys the params[] array should include a custom\n * value with name \"xkey-origin\".\n *\n * Other required parameters in the params array are:\n *\n *  pubkey - pointer to native public key as a OCTET_STRING\n *           the public key is duplicated on receipt\n *  handle - reference to opaque handle to private key -- if not required\n *           pass a dummy value that is not zero. type = OCTET_PTR\n *           The reference is retained -- caller must _not_ free it.\n *  sign_op - function pointer for sign operation. type = OCTET_PTR\n *            Must be a reference to XKEY_EXTERNAL_SIGN_fn\n *  xkey-origin - A custom string to indicate the external key origin. UTF8_STRING\n *                The value doesn't really matter, but must be present.\n *\n * Optional params\n *  free_op - Called as free(handle) when the key is deleted. If the\n *           handle should not be freed, do not include. type = OCTET_PTR\n *           Must be a reference to XKEY_PRIVKEY_FREE_fn\n *\n *  See xkey_load_management_key for an example use.\n */\nstatic int\nkeymgmt_import(void *keydata, int selection, const OSSL_PARAM params[], const char *name)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    XKEY_KEYDATA *key = keydata;\n    ASSERT(key);\n\n    /* Our private key is immutable -- we import only if keydata is empty */\n    if (key->handle || key->pubkey)\n    {\n        msg(M_WARN, \"Error: keymgmt_import: keydata not empty -- our keys are immutable\");\n        return 0;\n    }\n\n    /* if params contain a custom origin, call our helper to import custom keys */\n    const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, \"xkey-origin\");\n    if (p && p->data_type == OSSL_PARAM_UTF8_STRING)\n    {\n        key->origin = EXTERNAL_KEY;\n        xkey_dmsg(D_XKEY, \"importing external key\");\n        return keymgmt_import_helper(key, params);\n    }\n\n    xkey_dmsg(D_XKEY, \"importing native key\");\n\n    /* create a native public key and assign it to key->pubkey */\n    EVP_PKEY *pkey = NULL;\n    int selection_pub = selection & ~OSSL_KEYMGMT_SELECT_PRIVATE_KEY;\n\n    EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(key->prov->libctx, name, NULL);\n    if (!ctx || (EVP_PKEY_fromdata_init(ctx) != 1)\n        || (EVP_PKEY_fromdata(ctx, &pkey, selection_pub, (OSSL_PARAM *)params) != 1))\n    {\n        msg(M_WARN, \"Error: keymgmt_import failed for key type <%s>\", name);\n        if (pkey)\n        {\n            EVP_PKEY_free(pkey);\n        }\n        if (ctx)\n        {\n            EVP_PKEY_CTX_free(ctx);\n        }\n        return 0;\n    }\n\n    key->pubkey = pkey;\n    key->origin = OPENSSL_NATIVE;\n    if (selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY)\n    {\n        /* create private key */\n        pkey = NULL;\n        if (EVP_PKEY_fromdata(ctx, &pkey, selection, (OSSL_PARAM *)params) == 1)\n        {\n            key->handle = pkey;\n            key->free = (XKEY_PRIVKEY_FREE_fn *)EVP_PKEY_free;\n        }\n    }\n    EVP_PKEY_CTX_free(ctx);\n\n    xkey_dmsg(D_XKEY, \"imported native %s key\", EVP_PKEY_get0_type_name(pkey));\n    return 1;\n}\n\nstatic int\nrsa_keymgmt_import(void *keydata, int selection, const OSSL_PARAM params[])\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    return keymgmt_import(keydata, selection, params, \"RSA\");\n}\n\nstatic int\nec_keymgmt_import(void *keydata, int selection, const OSSL_PARAM params[])\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    return keymgmt_import(keydata, selection, params, \"EC\");\n}\n\nstatic int\ned448_keymgmt_import(void *keydata, int selection, const OSSL_PARAM params[])\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    return keymgmt_import(keydata, selection, params, \"ED448\");\n}\n\nstatic int\ned25519_keymgmt_import(void *keydata, int selection, const OSSL_PARAM params[])\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    return keymgmt_import(keydata, selection, params, \"ED25519\");\n}\n\n/* This function has to exist for key import to work\n * though we do not support import of individual params\n * like n or e. We simply return an empty list here for\n * both rsa and ec, which works.\n */\nstatic const OSSL_PARAM *\nkeymgmt_import_types(int selection)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    static const OSSL_PARAM key_types[] = { OSSL_PARAM_END };\n\n    if (selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY)\n    {\n        return key_types;\n    }\n    return NULL;\n}\n\nstatic void\nkeymgmt_free(void *keydata)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    keydata_free(keydata);\n}\n\nstatic int\nkeymgmt_has(const void *keydata, int selection)\n{\n    xkey_dmsg(D_XKEY, \"selection = %d\", selection);\n\n    const XKEY_KEYDATA *key = keydata;\n    int ok = (key != NULL);\n\n    if (selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY)\n    {\n        ok = ok && key->pubkey;\n    }\n    if (selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY)\n    {\n        ok = ok && key->handle;\n    }\n\n    return ok;\n}\n\nstatic int\nkeymgmt_match(const void *keydata1, const void *keydata2, int selection)\n{\n    const XKEY_KEYDATA *key1 = keydata1;\n    const XKEY_KEYDATA *key2 = keydata2;\n\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    int ret = key1 && key2 && key1->pubkey && key2->pubkey;\n\n    /* our keys always have pubkey -- we only match them */\n\n    if (selection & OSSL_KEYMGMT_SELECT_KEYPAIR)\n    {\n        ret = ret && EVP_PKEY_eq(key1->pubkey, key2->pubkey);\n        xkey_dmsg(D_XKEY, \"checking key pair match: res = %d\", ret);\n    }\n\n    if (selection & OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS)\n    {\n        ret = ret && EVP_PKEY_parameters_eq(key1->pubkey, key2->pubkey);\n        xkey_dmsg(D_XKEY, \"checking parameter match: res = %d\", ret);\n    }\n\n    return ret;\n}\n\n/* A minimal set of key params that we can return */\nstatic const OSSL_PARAM *\nkeymgmt_gettable_params(void *provctx)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    static OSSL_PARAM gettable[] = { OSSL_PARAM_int(OSSL_PKEY_PARAM_BITS, NULL),\n                                     OSSL_PARAM_int(OSSL_PKEY_PARAM_SECURITY_BITS, NULL),\n                                     OSSL_PARAM_int(OSSL_PKEY_PARAM_MAX_SIZE, NULL),\n                                     OSSL_PARAM_END };\n    return gettable;\n}\n\nstatic int\nkeymgmt_get_params(void *keydata, OSSL_PARAM *params)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    XKEY_KEYDATA *key = keydata;\n    if (!key || !key->pubkey)\n    {\n        return 0;\n    }\n\n    return EVP_PKEY_get_params(key->pubkey, params);\n}\n\n/* Helper used by keymgmt_import and keymgmt_set_params\n * for our keys. Not to be used for OpenSSL native keys.\n */\nstatic int\nkeymgmt_import_helper(XKEY_KEYDATA *key, const OSSL_PARAM *params)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    const OSSL_PARAM *p;\n    EVP_PKEY *pkey = NULL;\n\n    ASSERT(key);\n    /* calling this with native keys is a coding error */\n    ASSERT(key->origin != OPENSSL_NATIVE);\n\n    if (params == NULL)\n    {\n        return 1; /* not an error */\n    }\n\n    /* our keys are immutable, we do not allow resetting parameters */\n    if (key->pubkey)\n    {\n        return 0;\n    }\n\n    /* only check params we understand and ignore the rest */\n\n    p = OSSL_PARAM_locate_const(params, \"pubkey\"); /*setting pubkey on our keydata */\n    if (p && p->data_type == OSSL_PARAM_OCTET_STRING && p->data_size == sizeof(pkey))\n    {\n        pkey = *(EVP_PKEY **)p->data;\n        ASSERT(pkey);\n\n        int id = EVP_PKEY_get_id(pkey);\n        if (id != EVP_PKEY_RSA && id != EVP_PKEY_EC && id != EVP_PKEY_ED25519\n            && id != EVP_PKEY_ED448)\n        {\n            msg(M_WARN, \"Error: xkey keymgmt_import: unknown key type (%d)\", id);\n            return 0;\n        }\n\n        key->pubkey = EVP_PKEY_dup(pkey);\n        if (key->pubkey == NULL)\n        {\n            msg(M_NONFATAL, \"Error: xkey keymgmt_import: duplicating pubkey failed.\");\n            return 0;\n        }\n    }\n\n    p = OSSL_PARAM_locate_const(params, \"handle\"); /*setting privkey */\n    if (p && p->data_type == OSSL_PARAM_OCTET_PTR && p->data_size == sizeof(key->handle))\n    {\n        key->handle = *(void **)p->data;\n        /* caller should keep the reference alive until we call free */\n        ASSERT(key->handle); /* fix your params array */\n    }\n\n    p = OSSL_PARAM_locate_const(params, \"sign_op\"); /*setting sign_op */\n    if (p && p->data_type == OSSL_PARAM_OCTET_PTR && p->data_size == sizeof(key->sign))\n    {\n        key->sign = *(void **)p->data;\n        ASSERT(key->sign); /* fix your params array */\n    }\n\n    /* optional parameters */\n    p = OSSL_PARAM_locate_const(params, \"free_op\"); /*setting free_op */\n    if (p && p->data_type == OSSL_PARAM_OCTET_PTR && p->data_size == sizeof(key->free))\n    {\n        key->free = *(void **)p->data;\n    }\n    xkey_dmsg(D_XKEY, \"imported external %s key\", EVP_PKEY_get0_type_name(key->pubkey));\n\n    return 1;\n}\n\n/**\n * Set params on a key.\n *\n * If the key is an encapsulated native key, we just call\n * EVP_PKEY_set_params in the default context. Only those params\n * supported by the default provider would work in this case.\n *\n * We treat our key object as immutable, so this works only with an\n * empty key. Supported params for external keys are the\n * same as those listed in the description of keymgmt_import.\n */\nstatic int\nkeymgmt_set_params(void *keydata, const OSSL_PARAM *params)\n{\n    XKEY_KEYDATA *key = keydata;\n    ASSERT(key);\n\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    if (key->origin != OPENSSL_NATIVE)\n    {\n        return keymgmt_import_helper(key, params);\n    }\n    else if (key->handle == NULL) /* once handle is set our key is immutable */\n    {\n        /* pubkey is always native -- just delegate */\n        return EVP_PKEY_set_params(key->pubkey, (OSSL_PARAM *)params);\n    }\n    else\n    {\n        msg(M_WARN, \"xkey keymgmt_set_params: key is immutable\");\n    }\n    return 1;\n}\n\nstatic const char *\nrsa_keymgmt_name(int id)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    return \"RSA\";\n}\n\nstatic const char *\nec_keymgmt_name(int id)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    if (id == OSSL_OP_SIGNATURE)\n    {\n        return \"ECDSA\";\n    }\n    /* though we do not implement keyexch we could be queried for\n     * keyexch mechanism supported by EC keys\n     */\n    else if (id == OSSL_OP_KEYEXCH)\n    {\n        return \"ECDH\";\n    }\n\n    msg(D_XKEY, \"xkey ec_keymgmt_name called with op_id != SIGNATURE or KEYEXCH id=%d\", id);\n    return \"EC\";\n}\n\nstatic const OSSL_DISPATCH rsa_keymgmt_functions[] = {\n    { OSSL_FUNC_KEYMGMT_NEW, (void (*)(void))keymgmt_new },\n    { OSSL_FUNC_KEYMGMT_FREE, (void (*)(void))keymgmt_free },\n    { OSSL_FUNC_KEYMGMT_LOAD, (void (*)(void))keymgmt_load },\n    { OSSL_FUNC_KEYMGMT_HAS, (void (*)(void))keymgmt_has },\n    { OSSL_FUNC_KEYMGMT_MATCH, (void (*)(void))keymgmt_match },\n    { OSSL_FUNC_KEYMGMT_IMPORT, (void (*)(void))rsa_keymgmt_import },\n    { OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (void (*)(void))keymgmt_import_types },\n    { OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (void (*)(void))keymgmt_gettable_params },\n    { OSSL_FUNC_KEYMGMT_GET_PARAMS, (void (*)(void))keymgmt_get_params },\n    { OSSL_FUNC_KEYMGMT_SET_PARAMS, (void (*)(void))keymgmt_set_params },\n    { OSSL_FUNC_KEYMGMT_SETTABLE_PARAMS,\n      (void (*)(void))keymgmt_gettable_params }, /* same as gettable */\n    { OSSL_FUNC_KEYMGMT_QUERY_OPERATION_NAME, (void (*)(void))rsa_keymgmt_name },\n    { 0, NULL }\n};\n\nstatic const OSSL_DISPATCH ec_keymgmt_functions[] = {\n    { OSSL_FUNC_KEYMGMT_NEW, (void (*)(void))keymgmt_new },\n    { OSSL_FUNC_KEYMGMT_FREE, (void (*)(void))keymgmt_free },\n    { OSSL_FUNC_KEYMGMT_LOAD, (void (*)(void))keymgmt_load },\n    { OSSL_FUNC_KEYMGMT_HAS, (void (*)(void))keymgmt_has },\n    { OSSL_FUNC_KEYMGMT_MATCH, (void (*)(void))keymgmt_match },\n    { OSSL_FUNC_KEYMGMT_IMPORT, (void (*)(void))ec_keymgmt_import },\n    { OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (void (*)(void))keymgmt_import_types },\n    { OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (void (*)(void))keymgmt_gettable_params },\n    { OSSL_FUNC_KEYMGMT_GET_PARAMS, (void (*)(void))keymgmt_get_params },\n    { OSSL_FUNC_KEYMGMT_SET_PARAMS, (void (*)(void))keymgmt_set_params },\n    { OSSL_FUNC_KEYMGMT_SETTABLE_PARAMS,\n      (void (*)(void))keymgmt_gettable_params }, /* same as gettable */\n    { OSSL_FUNC_KEYMGMT_QUERY_OPERATION_NAME, (void (*)(void))ec_keymgmt_name },\n    { 0, NULL }\n};\n\nstatic const OSSL_DISPATCH ed448_keymgmt_functions[] = {\n    { OSSL_FUNC_KEYMGMT_NEW, (void (*)(void))keymgmt_new },\n    { OSSL_FUNC_KEYMGMT_FREE, (void (*)(void))keymgmt_free },\n    { OSSL_FUNC_KEYMGMT_LOAD, (void (*)(void))keymgmt_load },\n    { OSSL_FUNC_KEYMGMT_HAS, (void (*)(void))keymgmt_has },\n    { OSSL_FUNC_KEYMGMT_MATCH, (void (*)(void))keymgmt_match },\n    { OSSL_FUNC_KEYMGMT_IMPORT, (void (*)(void))ed448_keymgmt_import },\n    { OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (void (*)(void))keymgmt_import_types },\n    { OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (void (*)(void))keymgmt_gettable_params },\n    { OSSL_FUNC_KEYMGMT_GET_PARAMS, (void (*)(void))keymgmt_get_params },\n    { OSSL_FUNC_KEYMGMT_SET_PARAMS, (void (*)(void))keymgmt_set_params },\n    { OSSL_FUNC_KEYMGMT_SETTABLE_PARAMS,\n      (void (*)(void))keymgmt_gettable_params }, /* same as gettable */\n    { 0, NULL }\n};\n\nstatic const OSSL_DISPATCH ed25519_keymgmt_functions[] = {\n    { OSSL_FUNC_KEYMGMT_NEW, (void (*)(void))keymgmt_new },\n    { OSSL_FUNC_KEYMGMT_FREE, (void (*)(void))keymgmt_free },\n    { OSSL_FUNC_KEYMGMT_LOAD, (void (*)(void))keymgmt_load },\n    { OSSL_FUNC_KEYMGMT_HAS, (void (*)(void))keymgmt_has },\n    { OSSL_FUNC_KEYMGMT_MATCH, (void (*)(void))keymgmt_match },\n    { OSSL_FUNC_KEYMGMT_IMPORT, (void (*)(void))ed25519_keymgmt_import },\n    { OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (void (*)(void))keymgmt_import_types },\n    { OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (void (*)(void))keymgmt_gettable_params },\n    { OSSL_FUNC_KEYMGMT_GET_PARAMS, (void (*)(void))keymgmt_get_params },\n    { OSSL_FUNC_KEYMGMT_SET_PARAMS, (void (*)(void))keymgmt_set_params },\n    { OSSL_FUNC_KEYMGMT_SETTABLE_PARAMS,\n      (void (*)(void))keymgmt_gettable_params }, /* same as gettable */\n    { 0, NULL }\n};\n\n\nconst OSSL_ALGORITHM keymgmts[] = {\n    { \"RSA:rsaEncryption\", XKEY_PROV_PROPS, rsa_keymgmt_functions, \"OpenVPN xkey RSA Key Manager\" },\n    { \"RSA-PSS:RSASSA-PSS\", XKEY_PROV_PROPS, rsa_keymgmt_functions,\n      \"OpenVPN xkey RSA-PSS Key Manager\" },\n    { \"EC:id-ecPublicKey\", XKEY_PROV_PROPS, ec_keymgmt_functions, \"OpenVPN xkey EC Key Manager\" },\n    { \"ED448\", XKEY_PROV_PROPS, ed448_keymgmt_functions, \"OpenVPN xkey ED448 Key Manager\" },\n    { \"ED25519\", XKEY_PROV_PROPS, ed25519_keymgmt_functions, \"OpenVPN xkey ED25519 Key Manager\" },\n    { NULL, NULL, NULL, NULL }\n};\n\n\n/* signature provider */\n\n/* signature provider callbacks we provide */\nstatic OSSL_FUNC_signature_newctx_fn signature_newctx;\nstatic OSSL_FUNC_signature_freectx_fn signature_freectx;\nstatic OSSL_FUNC_signature_sign_init_fn signature_sign_init;\nstatic OSSL_FUNC_signature_sign_fn signature_sign;\nstatic OSSL_FUNC_signature_digest_verify_init_fn signature_digest_verify_init;\nstatic OSSL_FUNC_signature_digest_verify_fn signature_digest_verify;\nstatic OSSL_FUNC_signature_digest_sign_init_fn signature_digest_sign_init;\nstatic OSSL_FUNC_signature_digest_sign_fn signature_digest_sign;\nstatic OSSL_FUNC_signature_set_ctx_params_fn signature_set_ctx_params;\nstatic OSSL_FUNC_signature_settable_ctx_params_fn signature_settable_ctx_params;\nstatic OSSL_FUNC_signature_get_ctx_params_fn signature_get_ctx_params;\nstatic OSSL_FUNC_signature_gettable_ctx_params_fn signature_gettable_ctx_params;\n\ntypedef struct\n{\n    XKEY_PROVIDER_CTX *prov;\n    XKEY_KEYDATA *keydata;\n    XKEY_SIGALG sigalg;\n} XKEY_SIGNATURE_CTX;\n\nstatic const XKEY_SIGALG default_sigalg = {\n    .mdname = \"MD5-SHA1\", .saltlen = \"digest\", .padmode = \"pkcs1\", .keytype = \"RSA\"\n};\n\nconst struct\n{\n    int nid;\n    const char *name;\n} digest_names[] = { { NID_md5_sha1, \"MD5-SHA1\" },\n                     { NID_sha1, \"SHA1\" },\n                     {\n                         NID_sha224,\n                         \"SHA224\",\n                     },\n                     { NID_sha256, \"SHA256\" },\n                     { NID_sha384, \"SHA384\" },\n                     { NID_sha512, \"SHA512\" },\n                     { 0, NULL } };\n/* Use of NIDs as opposed to EVP_MD_fetch is okay here\n * as these are only used for converting names passed in\n * by OpenSSL to const strings.\n */\n\nstatic struct\n{\n    int id;\n    const char *name;\n} padmode_names[] = { { RSA_PKCS1_PADDING, \"pkcs1\" },\n                      { RSA_PKCS1_PSS_PADDING, \"pss\" },\n                      { RSA_NO_PADDING, \"none\" },\n                      { 0, NULL } };\n\nstatic const char *saltlen_names[] = { \"digest\", \"max\", \"auto\", NULL };\n\n/* Return a string literal for digest name - normalizes\n * alternate names like SHA2-256 to SHA256 etc.\n */\nstatic const char *\nxkey_mdname(const char *name)\n{\n    if (name == NULL)\n    {\n        return \"none\";\n    }\n\n    int i = 0;\n\n    int nid = EVP_MD_get_type(EVP_get_digestbyname(name));\n\n    while (digest_names[i].name && nid != digest_names[i].nid)\n    {\n        i++;\n    }\n    return digest_names[i].name ? digest_names[i].name : \"MD5-SHA1\";\n}\n\nstatic void *\nsignature_newctx(void *provctx, const char *propq)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    (void)propq; /* unused */\n\n    XKEY_SIGNATURE_CTX *sctx = OPENSSL_zalloc(sizeof(*sctx));\n    if (!sctx)\n    {\n        msg(M_NONFATAL, \"xkey_signature_newctx: out of memory\");\n        return NULL;\n    }\n\n    sctx->prov = provctx;\n    sctx->sigalg = default_sigalg;\n\n    return sctx;\n}\n\nstatic void\nsignature_freectx(void *ctx)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    XKEY_SIGNATURE_CTX *sctx = ctx;\n\n    keydata_free(sctx->keydata);\n\n    OPENSSL_free(sctx);\n}\n\nstatic const OSSL_PARAM *\nsignature_settable_ctx_params(void *ctx, void *provctx)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    static OSSL_PARAM settable[] = {\n        OSSL_PARAM_utf8_string(OSSL_SIGNATURE_PARAM_PAD_MODE, NULL, 0),\n        OSSL_PARAM_utf8_string(OSSL_SIGNATURE_PARAM_DIGEST, NULL, 0),\n        OSSL_PARAM_utf8_string(OSSL_SIGNATURE_PARAM_PSS_SALTLEN, NULL, 0), OSSL_PARAM_END\n    };\n\n    return settable;\n}\n\nstatic int\nsignature_set_ctx_params(void *ctx, const OSSL_PARAM params[])\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    XKEY_SIGNATURE_CTX *sctx = ctx;\n    const OSSL_PARAM *p;\n\n    if (params == NULL)\n    {\n        return 1; /* not an error */\n    }\n    p = OSSL_PARAM_locate_const(params, OSSL_SIGNATURE_PARAM_PAD_MODE);\n    if (p && p->data_type == OSSL_PARAM_UTF8_STRING)\n    {\n        sctx->sigalg.padmode = NULL;\n        for (int i = 0; padmode_names[i].id != 0; i++)\n        {\n            if (!strcmp(p->data, padmode_names[i].name))\n            {\n                sctx->sigalg.padmode = padmode_names[i].name;\n                break;\n            }\n        }\n        if (sctx->sigalg.padmode == NULL)\n        {\n            msg(M_WARN, \"xkey signature_ctx: padmode <%s>, treating as <none>\", (char *)p->data);\n            sctx->sigalg.padmode = \"none\";\n        }\n        xkey_dmsg(D_XKEY, \"setting padmode as %s\", sctx->sigalg.padmode);\n    }\n    else if (p && p->data_type == OSSL_PARAM_INTEGER)\n    {\n        sctx->sigalg.padmode = NULL;\n        int padmode = 0;\n        if (OSSL_PARAM_get_int(p, &padmode))\n        {\n            for (int i = 0; padmode_names[i].id != 0; i++)\n            {\n                if (padmode == padmode_names[i].id)\n                {\n                    sctx->sigalg.padmode = padmode_names[i].name;\n                    break;\n                }\n            }\n        }\n        if (padmode == 0 || sctx->sigalg.padmode == NULL)\n        {\n            msg(M_WARN, \"xkey signature_ctx: padmode <%d>, treating as <none>\", padmode);\n            sctx->sigalg.padmode = \"none\";\n        }\n        xkey_dmsg(D_XKEY, \"setting padmode <%s>\", sctx->sigalg.padmode);\n    }\n    else if (p)\n    {\n        msg(M_WARN, \"xkey_signature_params: unknown padmode ignored\");\n    }\n\n    p = OSSL_PARAM_locate_const(params, OSSL_SIGNATURE_PARAM_DIGEST);\n    if (p && p->data_type == OSSL_PARAM_UTF8_STRING)\n    {\n        sctx->sigalg.mdname = xkey_mdname(p->data);\n        xkey_dmsg(D_XKEY, \"setting hashalg as %s\", sctx->sigalg.mdname);\n    }\n    else if (p)\n    {\n        msg(M_WARN, \"xkey_signature_params: unknown digest type ignored\");\n    }\n\n    p = OSSL_PARAM_locate_const(params, OSSL_SIGNATURE_PARAM_PSS_SALTLEN);\n    if (p && p->data_type == OSSL_PARAM_UTF8_STRING)\n    {\n        sctx->sigalg.saltlen = NULL;\n        for (int i = 0; saltlen_names[i] != NULL; i++)\n        {\n            if (!strcmp(p->data, saltlen_names[i]))\n            {\n                sctx->sigalg.saltlen = saltlen_names[i];\n                break;\n            }\n        }\n        if (sctx->sigalg.saltlen == NULL)\n        {\n            msg(M_WARN, \"xkey_signature_params: unknown saltlen <%s>\", (char *)p->data);\n            sctx->sigalg.saltlen = \"digest\"; /* most common */\n        }\n        xkey_dmsg(D_XKEY, \"setting saltlen to %s\", sctx->sigalg.saltlen);\n    }\n    else if (p)\n    {\n        msg(M_WARN, \"xkey_signature_params: unknown saltlen ignored\");\n    }\n\n    return 1;\n}\n\nstatic const OSSL_PARAM *\nsignature_gettable_ctx_params(void *ctx, void *provctx)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    static OSSL_PARAM gettable[] = { OSSL_PARAM_END }; /* Empty list */\n\n    return gettable;\n}\n\nstatic int\nsignature_get_ctx_params(void *ctx, OSSL_PARAM params[])\n{\n    xkey_dmsg(D_XKEY, \"not implemented\");\n    return 0;\n}\n\nstatic int\nsignature_sign_init(void *ctx, void *provkey, const OSSL_PARAM params[])\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    XKEY_SIGNATURE_CTX *sctx = ctx;\n\n    if (sctx->keydata)\n    {\n        keydata_free(sctx->keydata);\n    }\n    sctx->keydata = provkey;\n    sctx->keydata->refcount++; /* we are keeping a copy */\n    sctx->sigalg.keytype = get_keytype(sctx->keydata);\n\n    signature_set_ctx_params(sctx, params);\n\n    return 1;\n}\n\n/* Sign digest or message using sign function */\nstatic int\nxkey_sign_dispatch(XKEY_SIGNATURE_CTX *sctx, unsigned char *sig, size_t *siglen,\n                   const unsigned char *tbs, size_t tbslen)\n{\n    XKEY_EXTERNAL_SIGN_fn *sign = sctx->keydata->sign;\n    int ret = 0;\n\n    if (sctx->keydata->origin == OPENSSL_NATIVE)\n    {\n        ret = xkey_native_sign(sctx->keydata, sig, siglen, tbs, tbslen, sctx->sigalg);\n    }\n    else if (sign)\n    {\n        ret = sign(sctx->keydata->handle, sig, siglen, tbs, tbslen, sctx->sigalg);\n        xkey_dmsg(D_XKEY, \"xkey_provider: external sign op returned ret = %d siglen = %d\", ret,\n                  (int)*siglen);\n    }\n    else\n    {\n        msg(M_NONFATAL, \"xkey_provider: Internal error: No sign callback for external key.\");\n    }\n\n    return ret;\n}\n\nstatic int\nsignature_sign(void *ctx, unsigned char *sig, size_t *siglen, size_t sigsize,\n               const unsigned char *tbs, size_t tbslen)\n{\n    xkey_dmsg(D_XKEY, \"entry with siglen = %zu\", *siglen);\n\n    XKEY_SIGNATURE_CTX *sctx = ctx;\n    ASSERT(sctx);\n    ASSERT(sctx->keydata);\n\n    if (!sig)\n    {\n        *siglen = KEYSIZE(sctx->keydata);\n        return 1;\n    }\n\n    sctx->sigalg.op = \"Sign\";\n    return xkey_sign_dispatch(sctx, sig, siglen, tbs, tbslen);\n}\n\nstatic int\nsignature_digest_verify_init(void *ctx, const char *mdname, void *provkey,\n                             const OSSL_PARAM params[])\n{\n    xkey_dmsg(D_XKEY, \"mdname <%s>\", mdname);\n\n    msg(M_WARN, \"xkey_provider: DigestVerifyInit is not implemented\");\n    return 0;\n}\n\n/* We do not expect to be called for DigestVerify() but still\n * return an empty function for it in the sign dispatch array\n * for debugging purposes.\n */\nstatic int\nsignature_digest_verify(void *ctx, const unsigned char *sig, size_t siglen,\n                        const unsigned char *tbs, size_t tbslen)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    msg(M_WARN, \"xkey_provider: DigestVerify is not implemented\");\n    return 0;\n}\n\nstatic int\nsignature_digest_sign_init(void *ctx, const char *mdname, void *provkey, const OSSL_PARAM params[])\n{\n    xkey_dmsg(D_XKEY, \"mdname = <%s>\", mdname);\n\n    XKEY_SIGNATURE_CTX *sctx = ctx;\n\n    ASSERT(sctx);\n    ASSERT(provkey);\n    ASSERT(sctx->prov);\n\n    if (sctx->keydata)\n    {\n        keydata_free(sctx->keydata);\n    }\n    sctx->keydata = provkey; /* used by digest_sign */\n    sctx->keydata->refcount++;\n    sctx->sigalg.keytype = get_keytype(sctx->keydata);\n\n    signature_set_ctx_params(ctx, params);\n    if (!strcmp(sctx->sigalg.keytype, \"ED448\") || !strcmp(sctx->sigalg.keytype, \"ED25519\"))\n    {\n        /* EdDSA requires NULL as digest for the DigestSign API instead\n         * of using the normal Sign API. Ensure it is actually NULL too */\n        if (mdname != NULL)\n        {\n            msg(M_WARN, \"xkey digest_sign_init: mdname must be NULL for ED448/ED25519.\");\n            return 0;\n        }\n        sctx->sigalg.mdname = \"none\";\n    }\n    else if (mdname)\n    {\n        sctx->sigalg.mdname = xkey_mdname(mdname); /* get a string literal pointer */\n    }\n    else\n    {\n        msg(M_WARN, \"xkey digest_sign_init: mdname is NULL.\");\n    }\n    return 1;\n}\n\nstatic int\nsignature_digest_sign(void *ctx, unsigned char *sig, size_t *siglen, size_t sigsize,\n                      const unsigned char *tbs, size_t tbslen)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    XKEY_SIGNATURE_CTX *sctx = ctx;\n\n    ASSERT(sctx);\n    ASSERT(sctx->keydata);\n\n    if (!sig) /* set siglen and return */\n    {\n        *siglen = KEYSIZE(sctx->keydata);\n        return 1;\n    }\n\n    if (sctx->keydata->origin != OPENSSL_NATIVE)\n    {\n        /* pass the message itself to the backend */\n        sctx->sigalg.op = \"DigestSign\";\n        return xkey_sign_dispatch(ctx, sig, siglen, tbs, tbslen);\n    }\n\n    /* create digest and pass on to signature_sign() */\n\n    const char *mdname = sctx->sigalg.mdname;\n    EVP_MD *md = EVP_MD_fetch(sctx->prov->libctx, mdname, NULL);\n    if (!md)\n    {\n        msg(M_WARN, \"WARN: xkey digest_sign_init: MD_fetch failed for <%s>\", mdname);\n        return 0;\n    }\n\n    /* construct digest using OpenSSL */\n    unsigned char buf[EVP_MAX_MD_SIZE];\n    unsigned int sz;\n    if (EVP_Digest(tbs, tbslen, buf, &sz, md, NULL) != 1)\n    {\n        msg(M_WARN, \"WARN: xkey digest_sign: EVP_Digest failed\");\n        EVP_MD_free(md);\n        return 0;\n    }\n    EVP_MD_free(md);\n\n    return signature_sign(ctx, sig, siglen, sigsize, buf, sz);\n}\n\n/* Sign digest using native sign function -- will only work for native keys\n */\nint\nxkey_native_sign(XKEY_KEYDATA *key, unsigned char *sig, size_t *siglen, const unsigned char *tbs,\n                 size_t tbslen, XKEY_SIGALG sigalg)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    ASSERT(key);\n\n    EVP_PKEY *pkey = key->handle;\n    int ret = 0;\n\n    ASSERT(sig);\n\n    if (!pkey)\n    {\n        msg(M_NONFATAL, \"Error: xkey provider: signature request with empty private key\");\n        return 0;\n    }\n\n    const char *saltlen = sigalg.saltlen;\n    const char *mdname = sigalg.mdname;\n    const char *padmode = sigalg.padmode;\n\n    xkey_dmsg(D_XKEY, \"digest=<%s>, padmode=<%s>, saltlen=<%s>\", mdname, padmode, saltlen);\n\n    int i = 0;\n    OSSL_PARAM params[6];\n    if (EVP_PKEY_get_id(pkey) == EVP_PKEY_RSA)\n    {\n        params[i++] =\n            OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_DIGEST, (char *)mdname, 0);\n        params[i++] =\n            OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_PAD_MODE, (char *)padmode, 0);\n        if (!strcmp(sigalg.padmode, \"pss\"))\n        {\n            params[i++] = OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_PSS_SALTLEN,\n                                                           (char *)saltlen, 0);\n            /* same digest for mgf1 */\n            params[i++] = OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_MGF1_DIGEST,\n                                                           (char *)mdname, 0);\n        }\n    }\n    params[i++] = OSSL_PARAM_construct_end();\n\n    EVP_PKEY_CTX *ectx = EVP_PKEY_CTX_new_from_pkey(key->prov->libctx, pkey, NULL);\n\n    if (!ectx)\n    {\n        msg(M_WARN, \"WARN: xkey test_sign: call to EVP_PKEY_CTX_new...failed\");\n        return 0;\n    }\n\n    if (EVP_PKEY_sign_init_ex(ectx, NULL) != 1)\n    {\n        msg(M_WARN, \"WARN: xkey test_sign: call to EVP_PKEY_sign_init failed\");\n        return 0;\n    }\n    EVP_PKEY_CTX_set_params(ectx, params);\n\n    ret = EVP_PKEY_sign(ectx, sig, siglen, tbs, tbslen);\n    EVP_PKEY_CTX_free(ectx);\n\n    return ret;\n}\n\nstatic const OSSL_DISPATCH signature_functions[] = {\n    { OSSL_FUNC_SIGNATURE_NEWCTX, (void (*)(void))signature_newctx },\n    { OSSL_FUNC_SIGNATURE_FREECTX, (void (*)(void))signature_freectx },\n    { OSSL_FUNC_SIGNATURE_SIGN_INIT, (void (*)(void))signature_sign_init },\n    { OSSL_FUNC_SIGNATURE_SIGN, (void (*)(void))signature_sign },\n    { OSSL_FUNC_SIGNATURE_DIGEST_VERIFY_INIT, (void (*)(void))signature_digest_verify_init },\n    { OSSL_FUNC_SIGNATURE_DIGEST_VERIFY, (void (*)(void))signature_digest_verify },\n    { OSSL_FUNC_SIGNATURE_DIGEST_SIGN_INIT, (void (*)(void))signature_digest_sign_init },\n    { OSSL_FUNC_SIGNATURE_DIGEST_SIGN, (void (*)(void))signature_digest_sign },\n    { OSSL_FUNC_SIGNATURE_SET_CTX_PARAMS, (void (*)(void))signature_set_ctx_params },\n    { OSSL_FUNC_SIGNATURE_SETTABLE_CTX_PARAMS, (void (*)(void))signature_settable_ctx_params },\n    { OSSL_FUNC_SIGNATURE_GET_CTX_PARAMS, (void (*)(void))signature_get_ctx_params },\n    { OSSL_FUNC_SIGNATURE_GETTABLE_CTX_PARAMS, (void (*)(void))signature_gettable_ctx_params },\n    { 0, NULL }\n};\n\nconst OSSL_ALGORITHM signatures[] = {\n    { \"RSA:rsaEncryption\", XKEY_PROV_PROPS, signature_functions, \"OpenVPN xkey RSA Signature\" },\n    { \"ECDSA\", XKEY_PROV_PROPS, signature_functions, \"OpenVPN xkey ECDSA Signature\" },\n    { \"ED448\", XKEY_PROV_PROPS, signature_functions, \"OpenVPN xkey Ed448 Signature\" },\n    { \"ED25519\", XKEY_PROV_PROPS, signature_functions, \"OpenVPN xkey Ed25519 Signature\" },\n    { NULL, NULL, NULL, NULL }\n};\n\n/* main provider interface */\n\n/* provider callbacks we implement */\nstatic OSSL_FUNC_provider_query_operation_fn query_operation;\nstatic OSSL_FUNC_provider_gettable_params_fn gettable_params;\nstatic OSSL_FUNC_provider_get_params_fn get_params;\nstatic OSSL_FUNC_provider_teardown_fn teardown;\n\nstatic const OSSL_ALGORITHM *\nquery_operation(void *provctx, int op, int *no_store)\n{\n    xkey_dmsg(D_XKEY, \"op = %d\", op);\n\n    *no_store = 0;\n\n    switch (op)\n    {\n        case OSSL_OP_SIGNATURE:\n            return signatures;\n\n        case OSSL_OP_KEYMGMT:\n            return keymgmts;\n\n        default:\n            xkey_dmsg(D_XKEY, \"op not supported\");\n            break;\n    }\n    return NULL;\n}\n\nstatic const OSSL_PARAM *\ngettable_params(void *provctx)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    static const OSSL_PARAM param_types[] = {\n        OSSL_PARAM_DEFN(OSSL_PROV_PARAM_NAME, OSSL_PARAM_UTF8_PTR, NULL, 0), OSSL_PARAM_END\n    };\n\n    return param_types;\n}\nstatic int\nget_params(void *provctx, OSSL_PARAM params[])\n{\n    OSSL_PARAM *p;\n\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    p = OSSL_PARAM_locate(params, OSSL_PROV_PARAM_NAME);\n    if (p)\n    {\n        return (OSSL_PARAM_set_utf8_ptr(p, provname) != 0);\n    }\n\n    return 0;\n}\n\nstatic void\nteardown(void *provctx)\n{\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    XKEY_PROVIDER_CTX *prov = provctx;\n    if (prov && prov->libctx)\n    {\n        OSSL_LIB_CTX_free(prov->libctx);\n    }\n    OPENSSL_free(prov);\n}\n\nstatic const OSSL_DISPATCH dispatch_table[] = {\n    { OSSL_FUNC_PROVIDER_GETTABLE_PARAMS, (void (*)(void))gettable_params },\n    { OSSL_FUNC_PROVIDER_GET_PARAMS, (void (*)(void))get_params },\n    { OSSL_FUNC_PROVIDER_QUERY_OPERATION, (void (*)(void))query_operation },\n    { OSSL_FUNC_PROVIDER_TEARDOWN, (void (*)(void))teardown },\n    { 0, NULL }\n};\n\nint\nxkey_provider_init(const OSSL_CORE_HANDLE *handle, const OSSL_DISPATCH *in,\n                   const OSSL_DISPATCH **out, void **provctx)\n{\n    XKEY_PROVIDER_CTX *prov;\n\n    xkey_dmsg(D_XKEY, \"entry\");\n\n    prov = OPENSSL_zalloc(sizeof(*prov));\n    if (!prov)\n    {\n        msg(M_NONFATAL, \"xkey_provider_init: out of memory\");\n        return 0;\n    }\n\n    /* Make a child libctx for our use and set default prop query\n     * on it to ensure calls we delegate won't loop back to us.\n     */\n    prov->libctx = OSSL_LIB_CTX_new_child(handle, in);\n\n    EVP_set_default_properties(prov->libctx, \"provider!=ovpn.xkey\");\n\n    *out = dispatch_table;\n    *provctx = prov;\n\n    return 1;\n}\n\n#endif /* HAVE_XKEY_PROVIDER */\n"
  },
  {
    "path": "src/openvpnmsica/CMakeLists.txt",
    "content": "if (NOT WIN32)\n    return ()\nendif ()\n\nproject(openvpnmsica)\n\nadd_library(openvpnmsica SHARED)\n\ntarget_include_directories(openvpnmsica PRIVATE\n    ${CMAKE_CURRENT_BINARY_DIR}/../../\n    ../../include/\n    ../compat/\n    )\ntarget_sources(openvpnmsica PRIVATE\n    dllmain.c\n    msiex.c msiex.h\n    msica_arg.c msica_arg.h\n    openvpnmsica.c openvpnmsica.h\n    ../tapctl/basic.h\n    ../tapctl/error.c ../tapctl/error.h\n    ../tapctl/tap.c ../tapctl/tap.h\n    openvpnmsica_resources.rc\n    )\ntarget_compile_options(openvpnmsica PRIVATE\n    -D_UNICODE\n    -UNTDDI_VERSION\n    -D_WIN32_WINNT=_WIN32_WINNT_VISTA\n    )\n\nif (MSVC)\n    target_compile_options(openvpnmsica PRIVATE\n        \"$<$<CONFIG:Release>:/MT>\"\n        \"$<$<CONFIG:Debug>:/MTd>\"\n        )\nendif ()\n\ntarget_link_libraries(openvpnmsica\n    advapi32.lib ole32.lib msi.lib setupapi.lib iphlpapi.lib shell32.lib shlwapi.lib version.lib newdev.lib)\nif (MINGW)\n    target_compile_options(openvpnmsica PRIVATE -municode)\n    target_link_options(openvpnmsica PRIVATE -municode)\n    target_link_options(openvpnmsica PRIVATE\n        -Wl,--kill-at)\nendif ()\n"
  },
  {
    "path": "src/openvpnmsica/Makefile.am",
    "content": "#\n#  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n#\n#  This program is free software; you can redistribute it and/or modify\n#  it under the terms of the GNU General Public License version 2\n#  as published by the Free Software Foundation.\n#\n#  This program is distributed in the hope that it will be useful,\n#  but WITHOUT ANY WARRANTY; without even the implied warranty of\n#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n#  GNU General Public License for more details.\n#\n#  You should have received a copy of the GNU General Public License along\n#  with this program; if not, see <https://www.gnu.org/licenses/>.\n#\n\ninclude $(top_srcdir)/ltrc.inc\n\nMAINTAINERCLEANFILES = $(srcdir)/Makefile.in\n\nAM_CPPFLAGS = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat\n\nAM_CFLAGS = \\\n\t$(TAP_CFLAGS)\n\nEXTRA_DIST = \\\n\tCMakeLists.txt\n\nif WIN32\nlib_LTLIBRARIES = libopenvpnmsica.la\nlibopenvpnmsica_la_CFLAGS = \\\n\t-municode -D_UNICODE \\\n\t-UNTDDI_VERSION -U_WIN32_WINNT \\\n\t-D_WIN32_WINNT=_WIN32_WINNT_VISTA \\\n\t-Wl,--kill-at\nlibopenvpnmsica_la_LDFLAGS = -ladvapi32 -lole32 -lmsi -lsetupapi -liphlpapi -lshell32 -lshlwapi -lversion -lnewdev -no-undefined -avoid-version\nendif\n\nlibopenvpnmsica_la_SOURCES = \\\n\tdllmain.c \\\n\tmsiex.c msiex.h \\\n\tmsica_arg.c msica_arg.h \\\n\topenvpnmsica.c openvpnmsica.h \\\n\t$(top_srcdir)/src/tapctl/basic.h \\\n\t$(top_srcdir)/src/tapctl/error.c $(top_srcdir)/src/tapctl/error.h \\\n\t$(top_srcdir)/src/tapctl/tap.c $(top_srcdir)/src/tapctl/tap.h \\\n\topenvpnmsica_resources.rc\n"
  },
  {
    "path": "src/openvpnmsica/dllmain.c",
    "content": "/*\n *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages\n *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA\n *\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n\n#include \"openvpnmsica.h\"\n#include \"../tapctl/error.h\"\n\n#include <windows.h>\n#include <msi.h>\n#include <msiquery.h>\n#ifdef _MSC_VER\n#pragma comment(lib, \"msi.lib\")\n#endif\n#include <stdio.h>\n#include <wchar.h>\n\n\nDWORD openvpnmsica_thread_data_idx = TLS_OUT_OF_INDEXES;\n\n\n/**\n * DLL entry point\n */\nBOOL WINAPI\nDllMain(_In_ HINSTANCE hinstDLL, _In_ DWORD dwReason, _In_ LPVOID lpReserved)\n{\n    UNREFERENCED_PARAMETER(hinstDLL);\n    UNREFERENCED_PARAMETER(lpReserved);\n\n    switch (dwReason)\n    {\n        case DLL_PROCESS_ATTACH:\n            /* Allocate thread local storage index. */\n            openvpnmsica_thread_data_idx = TlsAlloc();\n            if (openvpnmsica_thread_data_idx == TLS_OUT_OF_INDEXES)\n            {\n                return FALSE;\n            }\n            /* Fall through. */\n\n        case DLL_THREAD_ATTACH:\n        {\n            /* Create thread local storage data. */\n            struct openvpnmsica_thread_data *s = (struct openvpnmsica_thread_data *)calloc(\n                1, sizeof(struct openvpnmsica_thread_data));\n            if (s == NULL)\n            {\n                return FALSE;\n            }\n\n            TlsSetValue(openvpnmsica_thread_data_idx, s);\n            break;\n        }\n\n        case DLL_PROCESS_DETACH:\n            if (openvpnmsica_thread_data_idx != TLS_OUT_OF_INDEXES)\n            {\n                /* Free thread local storage data and index. */\n                free(TlsGetValue(openvpnmsica_thread_data_idx));\n                TlsFree(openvpnmsica_thread_data_idx);\n            }\n            break;\n\n        case DLL_THREAD_DETACH:\n            /* Free thread local storage data. */\n            free(TlsGetValue(openvpnmsica_thread_data_idx));\n            break;\n    }\n\n    return TRUE;\n}\n\n\nbool\ndont_mute(unsigned int flags)\n{\n    UNREFERENCED_PARAMETER(flags);\n\n    return true;\n}\n\nvoid\nx_msg_va(const unsigned int flags, const char *format, va_list arglist)\n{\n    /* Secure last error before it is overridden. */\n    DWORD dwResult = (flags & M_ERRNO) != 0 ? GetLastError() : ERROR_SUCCESS;\n\n    struct openvpnmsica_thread_data *s =\n        (struct openvpnmsica_thread_data *)TlsGetValue(openvpnmsica_thread_data_idx);\n    if (s->hInstall == 0)\n    {\n        /* No MSI session, no fun. */\n        return;\n    }\n\n    /* Prepare the message record. The record will contain up to four fields. */\n    MSIHANDLE hRecordProg = MsiCreateRecord(4);\n\n    {\n        /* Field 2: The message string. */\n        char szBufStack[128];\n        int iResultLen = vsnprintf(szBufStack, _countof(szBufStack), format, arglist);\n        if (iResultLen > 0 && (unsigned int)iResultLen < _countof(szBufStack))\n        {\n            /* Use from stack. */\n            MsiRecordSetStringA(hRecordProg, 2, szBufStack);\n        }\n        else\n        {\n            /* Allocate on heap and retry. */\n            char *szMessage = (char *)malloc(++iResultLen * sizeof(char));\n            if (szMessage != NULL)\n            {\n                vsnprintf(szMessage, iResultLen, format, arglist);\n                MsiRecordSetStringA(hRecordProg, 2, szMessage);\n                free(szMessage);\n            }\n            else\n            {\n                /* Use stack variant anyway, but make sure it's zero-terminated. */\n                szBufStack[_countof(szBufStack) - 1] = 0;\n                MsiRecordSetStringA(hRecordProg, 2, szBufStack);\n            }\n        }\n    }\n\n    if ((flags & M_ERRNO) == 0)\n    {\n        /* Field 1: MSI Error Code */\n        MsiRecordSetInteger(hRecordProg, 1, ERROR_MSICA);\n    }\n    else\n    {\n        /* Field 1: MSI Error Code */\n        MsiRecordSetInteger(hRecordProg, 1, ERROR_MSICA_ERRNO);\n\n        /* Field 3: The Windows error number. */\n        MsiRecordSetInteger(hRecordProg, 3, dwResult);\n\n        /* Field 4: The Windows error description. */\n        LPWSTR szErrMessage = NULL;\n        if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER\n                              | FORMAT_MESSAGE_IGNORE_INSERTS,\n                          0, dwResult, 0, (LPWSTR)&szErrMessage, 0, NULL)\n            && szErrMessage)\n        {\n            /* Trim trailing whitespace. Set terminator after the last non-whitespace character.\n             * This prevents excessive trailing line breaks. */\n            for (size_t i = 0, i_last = 0;; i++)\n            {\n                if (szErrMessage[i])\n                {\n                    if (!iswspace(szErrMessage[i]))\n                    {\n                        i_last = i + 1;\n                    }\n                }\n                else\n                {\n                    szErrMessage[i_last] = 0;\n                    break;\n                }\n            }\n            MsiRecordSetString(hRecordProg, 4, szErrMessage);\n            LocalFree(szErrMessage);\n        }\n    }\n\n    MsiProcessMessage(s->hInstall, (flags & M_WARN) ? INSTALLMESSAGE_INFO : INSTALLMESSAGE_ERROR,\n                      hRecordProg);\n    MsiCloseHandle(hRecordProg);\n}\n"
  },
  {
    "path": "src/openvpnmsica/msica_arg.c",
    "content": "/*\n *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages\n *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA\n *\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n\n#include \"msica_arg.h\"\n#include \"../tapctl/error.h\"\n#include \"../tapctl/tap.h\"\n\n#include <windows.h>\n#include <malloc.h>\n\n\nvoid\nmsica_arg_seq_init(_Inout_ struct msica_arg_seq *seq)\n{\n    seq->head = NULL;\n    seq->tail = NULL;\n}\n\n\nvoid\nmsica_arg_seq_free(_Inout_ struct msica_arg_seq *seq)\n{\n    while (seq->head)\n    {\n        struct msica_arg *p = seq->head;\n        seq->head = seq->head->next;\n        free(p);\n    }\n    seq->tail = NULL;\n}\n\n\nvoid\nmsica_arg_seq_add_head(_Inout_ struct msica_arg_seq *seq, _In_z_ LPCWSTR argument)\n{\n    size_t argument_size = (wcslen(argument) + 1) * sizeof(WCHAR);\n    struct msica_arg *p = malloc(sizeof(struct msica_arg) + argument_size);\n    if (p == NULL)\n    {\n        msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__,\n            sizeof(struct msica_arg) + argument_size);\n    }\n    memcpy(p->val, argument, argument_size);\n    p->next = seq->head;\n    seq->head = p;\n    if (seq->tail == NULL)\n    {\n        seq->tail = p;\n    }\n}\n\n\nvoid\nmsica_arg_seq_add_tail(_Inout_ struct msica_arg_seq *seq, _Inout_ LPCWSTR argument)\n{\n    size_t argument_size = (wcslen(argument) + 1) * sizeof(WCHAR);\n    struct msica_arg *p = malloc(sizeof(struct msica_arg) + argument_size);\n    if (p == NULL)\n    {\n        msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__,\n            sizeof(struct msica_arg) + argument_size);\n    }\n    memcpy(p->val, argument, argument_size);\n    p->next = NULL;\n    *(seq->tail ? &seq->tail->next : &seq->head) = p;\n    seq->tail = p;\n}\n\n\nLPWSTR\nmsica_arg_seq_join(_In_ const struct msica_arg_seq *seq)\n{\n    /* Count required space. */\n    size_t size = 2 /*x + zero-terminator*/;\n    for (struct msica_arg *p = seq->head; p != NULL; p = p->next)\n    {\n        size += wcslen(p->val) + 1 /*space delimiter|zero-terminator*/;\n    }\n    size *= sizeof(WCHAR);\n\n    /* Allocate. */\n    LPWSTR str = malloc(size);\n    if (str == NULL)\n    {\n        msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__, size);\n        return NULL;\n    }\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(                                                                                   \\\n    disable : 4996) /* Using unsafe string functions: The space in s and termination of p->val has \\\n                       been implicitly verified at the beginning of this function. */\n#endif\n\n    /* Dummy argv[0] (i.e. executable name), for CommandLineToArgvW to work correctly when parsing\n     * this string. */\n    wcscpy(str, L\"x\");\n\n    /* Join. */\n    LPWSTR s = str + 1 /*x*/;\n    for (struct msica_arg *p = seq->head; p != NULL; p = p->next)\n    {\n        /* Convert zero-terminator into space delimiter. */\n        s[0] = L' ';\n        s++;\n        /* Append argument. */\n        wcscpy(s, p->val);\n        s += wcslen(p->val);\n    }\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n    return str;\n}\n"
  },
  {
    "path": "src/openvpnmsica/msica_arg.h",
    "content": "/*\n *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages\n *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA\n *\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef MSICA_ARG_H\n#define MSICA_ARG_H\n\n#include <windows.h>\n#include <wchar.h>\n#include \"../tapctl/basic.h\"\n\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable : 4200) /* Using zero-sized arrays in struct/union. */\n#endif\n\n\n/**\n * Argument list\n */\nstruct msica_arg\n{\n    struct msica_arg *next; /**< Pointer to the next argument in the sequence */\n    WCHAR val[];            /**< Zero terminated argument string */\n};\n\n\n/**\n * Argument sequence\n */\nstruct msica_arg_seq\n{\n    struct msica_arg *head; /**< Pointer to the first argument in the sequence */\n    struct msica_arg *tail; /**< Pointer to the last argument in the sequence */\n};\n\n\n/**\n * Initializes argument sequence\n *\n * @param seq           Pointer to uninitialized argument sequence\n */\nvoid msica_arg_seq_init(_Inout_ struct msica_arg_seq *seq);\n\n\n/**\n * Frees argument sequence\n *\n * @param seq           Pointer to the argument sequence\n */\nvoid msica_arg_seq_free(_Inout_ struct msica_arg_seq *seq);\n\n\n/**\n * Inserts argument to the beginning of the argument sequence\n *\n * @param seq           Pointer to the argument sequence\n *\n * @param argument      Zero-terminated argument string to insert.\n */\nvoid msica_arg_seq_add_head(_Inout_ struct msica_arg_seq *seq, _In_z_ LPCWSTR argument);\n\n\n/**\n * Appends argument to the end of the argument sequence\n *\n * @param seq           Pointer to the argument sequence\n *\n * @param argument      Zero-terminated argument string to append.\n */\nvoid msica_arg_seq_add_tail(_Inout_ struct msica_arg_seq *seq, _Inout_ LPCWSTR argument);\n\n/**\n * Join arguments of the argument sequence into a space delimited string\n *\n * @param seq           Pointer to the argument sequence\n *\n * @return Joined argument string. Must be released with free() after use.\n */\nLPWSTR\nmsica_arg_seq_join(_In_ const struct msica_arg_seq *seq);\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n#endif /* ifndef MSICA_ARG_H */\n"
  },
  {
    "path": "src/openvpnmsica/msiex.c",
    "content": "/*\n *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages\n *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA\n *\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n\n#include \"msiex.h\"\n#include \"../tapctl/error.h\"\n\n#include <windows.h>\n#include <malloc.h>\n#include <memory.h>\n#include <msiquery.h>\n#ifdef _MSC_VER\n#pragma comment(lib, \"msi.lib\")\n#endif\n\n\nUINT\nmsi_get_string(_In_ MSIHANDLE hInstall, _In_z_ LPCWSTR szName, _Out_ LPWSTR *pszValue)\n{\n    if (pszValue == NULL)\n    {\n        return ERROR_BAD_ARGUMENTS;\n    }\n\n    /* Try with stack buffer first. */\n    WCHAR szBufStack[128];\n    DWORD dwLength = _countof(szBufStack);\n    UINT uiResult = MsiGetProperty(hInstall, szName, szBufStack, &dwLength);\n    if (uiResult == ERROR_SUCCESS)\n    {\n        /* Copy from stack. */\n        *pszValue = (LPWSTR)malloc(++dwLength * sizeof(WCHAR));\n        if (*pszValue == NULL)\n        {\n            msg(M_FATAL, \"%s: malloc(%u) failed\", dwLength * sizeof(WCHAR));\n            return ERROR_OUTOFMEMORY;\n        }\n\n        memcpy(*pszValue, szBufStack, dwLength * sizeof(WCHAR));\n        return ERROR_SUCCESS;\n    }\n    else if (uiResult == ERROR_MORE_DATA)\n    {\n        /* Allocate on heap and retry. */\n        LPWSTR szBufHeap = (LPWSTR)malloc(++dwLength * sizeof(WCHAR));\n        if (szBufHeap == NULL)\n        {\n            msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__, dwLength * sizeof(WCHAR));\n            return ERROR_OUTOFMEMORY;\n        }\n\n        uiResult = MsiGetProperty(hInstall, szName, szBufHeap, &dwLength);\n        if (uiResult == ERROR_SUCCESS)\n        {\n            *pszValue = szBufHeap;\n        }\n        else\n        {\n            free(szBufHeap);\n        }\n        return uiResult;\n    }\n    else\n    {\n        SetLastError(uiResult); /* MSDN does not mention MsiGetProperty() to set GetLastError(). But\n                                   we do have an error code. Set last error manually. */\n        msg(M_NONFATAL | M_ERRNO, \"%s: MsiGetProperty failed\", __FUNCTION__);\n        return uiResult;\n    }\n}\n\n\nUINT\nmsi_get_record_string(_In_ MSIHANDLE hRecord, _In_ unsigned int iField, _Out_ LPWSTR *pszValue)\n{\n    if (pszValue == NULL)\n    {\n        return ERROR_BAD_ARGUMENTS;\n    }\n\n    /* Try with stack buffer first. */\n    WCHAR szBufStack[128];\n    DWORD dwLength = _countof(szBufStack);\n    UINT uiResult = MsiRecordGetString(hRecord, iField, szBufStack, &dwLength);\n    if (uiResult == ERROR_SUCCESS)\n    {\n        /* Copy from stack. */\n        *pszValue = (LPWSTR)malloc(++dwLength * sizeof(WCHAR));\n        if (*pszValue == NULL)\n        {\n            msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__, dwLength * sizeof(WCHAR));\n            return ERROR_OUTOFMEMORY;\n        }\n\n        memcpy(*pszValue, szBufStack, dwLength * sizeof(WCHAR));\n        return ERROR_SUCCESS;\n    }\n    else if (uiResult == ERROR_MORE_DATA)\n    {\n        /* Allocate on heap and retry. */\n        LPWSTR szBufHeap = (LPWSTR)malloc(++dwLength * sizeof(WCHAR));\n        if (szBufHeap == NULL)\n        {\n            msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__, dwLength * sizeof(WCHAR));\n            return ERROR_OUTOFMEMORY;\n        }\n\n        uiResult = MsiRecordGetString(hRecord, iField, szBufHeap, &dwLength);\n        if (uiResult == ERROR_SUCCESS)\n        {\n            *pszValue = szBufHeap;\n        }\n        else\n        {\n            free(szBufHeap);\n        }\n        return uiResult;\n    }\n    else\n    {\n        SetLastError(uiResult); /* MSDN does not mention MsiRecordGetString() to set GetLastError().\n                                   But we do have an error code. Set last error manually. */\n        msg(M_NONFATAL | M_ERRNO, \"%s: MsiRecordGetString failed\", __FUNCTION__);\n        return uiResult;\n    }\n}\n\n\nUINT\nmsi_format_record(_In_ MSIHANDLE hInstall, _In_ MSIHANDLE hRecord, _Out_ LPWSTR *pszValue)\n{\n    if (pszValue == NULL)\n    {\n        return ERROR_BAD_ARGUMENTS;\n    }\n\n    /* Try with stack buffer first. */\n    WCHAR szBufStack[128];\n    DWORD dwLength = _countof(szBufStack);\n    UINT uiResult = MsiFormatRecord(hInstall, hRecord, szBufStack, &dwLength);\n    if (uiResult == ERROR_SUCCESS)\n    {\n        /* Copy from stack. */\n        *pszValue = (LPWSTR)malloc(++dwLength * sizeof(WCHAR));\n        if (*pszValue == NULL)\n        {\n            msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__, dwLength * sizeof(WCHAR));\n            return ERROR_OUTOFMEMORY;\n        }\n\n        memcpy(*pszValue, szBufStack, dwLength * sizeof(WCHAR));\n        return ERROR_SUCCESS;\n    }\n    else if (uiResult == ERROR_MORE_DATA)\n    {\n        /* Allocate on heap and retry. */\n        LPWSTR szBufHeap = (LPWSTR)malloc(++dwLength * sizeof(WCHAR));\n        if (szBufHeap == NULL)\n        {\n            msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__, dwLength * sizeof(WCHAR));\n            return ERROR_OUTOFMEMORY;\n        }\n\n        uiResult = MsiFormatRecord(hInstall, hRecord, szBufHeap, &dwLength);\n        if (uiResult == ERROR_SUCCESS)\n        {\n            *pszValue = szBufHeap;\n        }\n        else\n        {\n            free(szBufHeap);\n        }\n        return uiResult;\n    }\n    else\n    {\n        SetLastError(uiResult); /* MSDN does not mention MsiFormatRecord() to set GetLastError().\n                                   But we do have an error code. Set last error manually. */\n        msg(M_NONFATAL | M_ERRNO, \"%s: MsiFormatRecord failed\", __FUNCTION__);\n        return uiResult;\n    }\n}\n\n\nUINT\nmsi_format_field(_In_ MSIHANDLE hInstall, _In_ MSIHANDLE hRecord, _In_ unsigned int iField,\n                 _Out_ LPWSTR *pszValue)\n{\n    if (pszValue == NULL)\n    {\n        return ERROR_BAD_ARGUMENTS;\n    }\n\n    /* Read string to format. */\n    LPWSTR szValue = NULL;\n    UINT uiResult = msi_get_record_string(hRecord, iField, &szValue);\n    if (uiResult != ERROR_SUCCESS)\n    {\n        return uiResult;\n    }\n    if (szValue[0] == 0)\n    {\n        /* The string is empty. There's nothing left to do. */\n        *pszValue = szValue;\n        return ERROR_SUCCESS;\n    }\n\n    /* Create a temporary record. */\n    MSIHANDLE hRecordEx = MsiCreateRecord(1);\n    if (!hRecordEx)\n    {\n        uiResult = ERROR_INVALID_HANDLE;\n        msg(M_NONFATAL, \"%s: MsiCreateRecord failed\", __FUNCTION__);\n        goto cleanup_szValue;\n    }\n\n    /* Populate the record with data. */\n    uiResult = MsiRecordSetString(hRecordEx, 0, szValue);\n    if (uiResult != ERROR_SUCCESS)\n    {\n        SetLastError(uiResult); /* MSDN does not mention MsiRecordSetString() to set GetLastError().\n                                   But we do have an error code. Set last error manually. */\n        msg(M_NONFATAL | M_ERRNO, \"%s: MsiRecordSetString failed\", __FUNCTION__);\n        goto cleanup_hRecordEx;\n    }\n\n    /* Do the formatting. */\n    uiResult = msi_format_record(hInstall, hRecordEx, pszValue);\n\ncleanup_hRecordEx:\n    MsiCloseHandle(hRecordEx);\ncleanup_szValue:\n    free(szValue);\n    return uiResult;\n}\n"
  },
  {
    "path": "src/openvpnmsica/msiex.h",
    "content": "/*\n *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages\n *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA\n *\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef MSIHLP_H\n#define MSIHLP_H\n\n#include <windows.h>\n#include <msi.h>\n#include \"../tapctl/basic.h\"\n\n\n/**\n * Gets MSI property value\n *\n * @param hInstall      Handle to the installation provided to the DLL custom action\n *\n * @param szName        Property name\n *\n * @param pszValue      Pointer to string to retrieve property value. The string must\n *                      be released with free() after use.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n */\nUINT msi_get_string(_In_ MSIHANDLE hInstall, _In_z_ LPCWSTR szName, _Out_ LPWSTR *pszValue);\n\n\n/**\n * Gets MSI record string value\n *\n * @param hRecord       Handle to the record\n *\n * @param iField        Field index\n *\n * @param pszValue      Pointer to string to retrieve field value. The string must be\n *                      released with free() after use.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n */\nUINT msi_get_record_string(_In_ MSIHANDLE hRecord, _In_ unsigned int iField,\n                           _Out_ LPWSTR *pszValue);\n\n\n/**\n * Formats MSI record\n *\n * @param hInstall      Handle to the installation. This may be omitted, in which case only the\n *                      record field parameters are processed and properties are not available\n *                      for substitution.\n *\n * @param hRecord       Handle to the record to format. The template string must be stored in\n *                      record field 0 followed by referenced data parameters.\n *\n * @param pszValue      Pointer to string to retrieve formatted value. The string must be\n *                      released with free() after use.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n */\nUINT msi_format_record(_In_ MSIHANDLE hInstall, _In_ MSIHANDLE hRecord, _Out_ LPWSTR *pszValue);\n\n\n/**\n * Formats MSI record field\n *\n * @param hInstall      Handle to the installation. This may be omitted, in which case only the\n *                      record field parameters are processed and properties are not available\n *                      for substitution.\n *\n * @param hRecord       Handle to the field record\n *\n * @param iField        Field index\n *\n * @param pszValue      Pointer to string to retrieve formatted value. The string must be\n *                      released with free() after use.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n */\nUINT msi_format_field(_In_ MSIHANDLE hInstall, _In_ MSIHANDLE hRecord, _In_ unsigned int iField,\n                      _Out_ LPWSTR *pszValue);\n\n#endif /* ifndef MSIHLP_H */\n"
  },
  {
    "path": "src/openvpnmsica/openvpnmsica.c",
    "content": "/*\n *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages\n *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA\n *\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n#include <winsock2.h> /* Must be included _before_ <windows.h> */\n\n#include \"openvpnmsica.h\"\n#include \"msica_arg.h\"\n#include \"msiex.h\"\n\n#include \"../tapctl/basic.h\"\n#include \"../tapctl/error.h\"\n#include \"../tapctl/tap.h\"\n\n#include <windows.h>\n#include <iphlpapi.h>\n#include <malloc.h>\n#include <memory.h>\n#include <msiquery.h>\n#include <shellapi.h>\n#include <shlwapi.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <wchar.h>\n#include <setupapi.h>\n#include <newdev.h>\n#include <initguid.h>\n#include <devguid.h>\n\n#ifdef _MSC_VER\n#pragma comment(lib, \"advapi32.lib\")\n#pragma comment(lib, \"iphlpapi.lib\")\n#pragma comment(lib, \"shell32.lib\")\n#pragma comment(lib, \"shlwapi.lib\")\n#pragma comment(lib, \"version.lib\")\n#endif\n\n\n/**\n * Local constants\n */\n\n/** Amount of tick space to reserve for one TAP/TUN adapter creation/deletition. */\n#define MSICA_ADAPTER_TICK_SIZE (16 * 1024)\n\n#define FILE_NEED_REBOOT L\".ovpn_need_reboot\"\n\n#define OPENVPN_CONNECT_ADAPTER_SUBSTR L\"OpenVPN Connect\"\n\n/**\n * Joins an argument sequence and sets it to the MSI property.\n *\n * @param hInstall      Handle to the installation provided to the DLL custom action\n *\n * @param szProperty    MSI property name to set to the joined argument sequence.\n *\n * @param seq           The argument sequence.\n *\n * @return ERROR_SUCCESS on success; An error code otherwise\n */\nstatic UINT\nsetup_sequence(_In_ MSIHANDLE hInstall, _In_z_ LPCWSTR szProperty, _In_ struct msica_arg_seq *seq)\n{\n    UINT uiResult;\n    LPWSTR szSequence = msica_arg_seq_join(seq);\n    uiResult = MsiSetProperty(hInstall, szProperty, szSequence);\n    free(szSequence);\n    if (uiResult != ERROR_SUCCESS)\n    {\n        /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error\n         * code. Set last error manually. */\n        SetLastError(uiResult);\n        msg(M_NONFATAL | M_ERRNO, \"%s: MsiSetProperty(\\\"%ls\\\") failed\", __FUNCTION__, szProperty);\n        return uiResult;\n    }\n    return ERROR_SUCCESS;\n}\n\n\n#ifdef _DEBUG\n\n/**\n * Pops up a message box creating a time window to attach a debugger to the installer process in\n * order to debug custom actions.\n *\n * @param szFunctionName  Function name that triggered the pop-up. Displayed in message box's\n *                        title.\n */\nstatic void\n_debug_popup(_In_z_ LPCSTR szFunctionName)\n{\n    WCHAR szTitle[0x100], szMessage[0x100 + MAX_PATH], szProcessPath[MAX_PATH];\n\n    /* Compose pop-up title. The dialog title will contain function name to ease the process\n     * locating. Mind that Visual Studio displays window titles on the process list. */\n    swprintf_s(szTitle, _countof(szTitle), L\"%hs v%ls\", szFunctionName, _L(PACKAGE_VERSION));\n\n    /* Get process name. */\n    GetModuleFileName(NULL, szProcessPath, _countof(szProcessPath));\n    LPCWSTR szProcessName = wcsrchr(szProcessPath, L'\\\\');\n    szProcessName = szProcessName ? szProcessName + 1 : szProcessPath;\n\n    /* Compose the pop-up message. */\n    swprintf_s(\n        szMessage, _countof(szMessage),\n        L\"The %ls process (PID: %u) has started to execute the %hs\"\n        L\" custom action.\\r\\n\"\n        L\"\\r\\n\"\n        L\"If you would like to debug the custom action, attach a debugger to this process and set breakpoints before dismissing this dialog.\\r\\n\"\n        L\"\\r\\n\"\n        L\"If you are not debugging this custom action, you can safely ignore this message.\",\n        szProcessName, GetCurrentProcessId(), szFunctionName);\n\n    MessageBox(NULL, szMessage, szTitle, MB_OK);\n}\n\n#define debug_popup(f) _debug_popup(f)\n#else  /* ifdef _DEBUG */\n#define debug_popup(f)\n#endif /* ifdef _DEBUG */\n\nstatic void\nfind_adapters(_In_ MSIHANDLE hInstall, _In_z_ LPCWSTR szzHardwareIDs,\n              _In_z_ LPCWSTR szAdaptersPropertyName, _In_z_ LPCWSTR szActiveAdaptersPropertyName)\n{\n    UINT uiResult;\n\n    /* Get network adapters with given hardware ID. */\n    struct tap_adapter_node *pAdapterList = NULL;\n    uiResult = tap_list_adapters(NULL, szzHardwareIDs, &pAdapterList);\n    if (uiResult != ERROR_SUCCESS)\n    {\n        return;\n    }\n    else if (pAdapterList == NULL)\n    {\n        /* No adapters - no fun. */\n        return;\n    }\n\n    /* Get IPv4/v6 info for all network adapters. Actually, we're interested in link status only:\n     * up/down? */\n    PIP_ADAPTER_ADDRESSES pAdapterAdresses = NULL;\n    ULONG ulAdapterAdressesSize = 16 * 1024;\n    for (size_t iteration = 0; iteration < 2; iteration++)\n    {\n        pAdapterAdresses = (PIP_ADAPTER_ADDRESSES)malloc(ulAdapterAdressesSize);\n        if (pAdapterAdresses == NULL)\n        {\n            msg(M_NONFATAL, \"%s: malloc(%u) failed\", __FUNCTION__, ulAdapterAdressesSize);\n            uiResult = ERROR_OUTOFMEMORY;\n            goto cleanup_pAdapterList;\n        }\n\n        ULONG ulResult = GetAdaptersAddresses(\n            AF_UNSPEC,\n            GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST\n                | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME\n                | GAA_FLAG_INCLUDE_ALL_INTERFACES,\n            NULL, pAdapterAdresses, &ulAdapterAdressesSize);\n\n        if (ulResult == ERROR_SUCCESS)\n        {\n            break;\n        }\n\n        free(pAdapterAdresses);\n        if (ulResult != ERROR_BUFFER_OVERFLOW)\n        {\n            SetLastError(\n                ulResult); /* MSDN does not mention GetAdaptersAddresses() to set GetLastError().\n                              But we do have an error code. Set last error manually. */\n            msg(M_NONFATAL | M_ERRNO, \"%s: GetAdaptersAddresses() failed\", __FUNCTION__);\n            uiResult = ulResult;\n            goto cleanup_pAdapterList;\n        }\n    }\n\n    /* Count adapters. */\n    size_t adapter_count = 0;\n    for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter; pAdapter = pAdapter->pNext)\n    {\n        adapter_count++;\n    }\n\n    /* Prepare semicolon delimited list of TAP adapter ID(s) and active TAP adapter ID(s). */\n    LPWSTR\n    szAdapters =\n        (LPWSTR)malloc(adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(WCHAR)),\n    szAdaptersTail = szAdapters;\n    if (szAdapters == NULL)\n    {\n        msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__,\n            adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(WCHAR));\n        uiResult = ERROR_OUTOFMEMORY;\n        goto cleanup_pAdapterAdresses;\n    }\n\n    LPWSTR\n    szAdaptersActive =\n        (LPWSTR)malloc(adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(WCHAR)),\n    szAdaptersActiveTail = szAdaptersActive;\n    if (szAdaptersActive == NULL)\n    {\n        msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__,\n            adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(WCHAR));\n        uiResult = ERROR_OUTOFMEMORY;\n        goto cleanup_szAdapters;\n    }\n\n    for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter; pAdapter = pAdapter->pNext)\n    {\n        /* exclude adapters created by OpenVPN Connect, since they're removed on Connect\n         * uninstallation */\n        if (wcsstr(pAdapter->szName, OPENVPN_CONNECT_ADAPTER_SUBSTR))\n        {\n            msg(M_WARN, \"%s: skip OpenVPN Connect adapter '%ls'\", __FUNCTION__, pAdapter->szName);\n            continue;\n        }\n\n        /* Convert adapter GUID to UTF-16 string. (LPOLESTR defaults to LPWSTR) */\n        LPOLESTR szAdapterId = NULL;\n        StringFromIID((REFIID)&pAdapter->guid, &szAdapterId);\n\n        /* Append to the list of TAP adapter ID(s). */\n        if (szAdapters < szAdaptersTail)\n        {\n            *(szAdaptersTail++) = L';';\n        }\n        memcpy(szAdaptersTail, szAdapterId, 38 * sizeof(WCHAR));\n        szAdaptersTail += 38;\n\n        /* If this adapter is active (connected), add it to the list of active TAP adapter ID(s). */\n        for (PIP_ADAPTER_ADDRESSES p = pAdapterAdresses; p; p = p->Next)\n        {\n            OLECHAR szId[38 /*GUID*/ + 1 /*terminator*/];\n            GUID guid;\n            if (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, p->AdapterName, -1, szId,\n                                    _countof(szId))\n                    > 0\n                && SUCCEEDED(IIDFromString(szId, &guid))\n                && memcmp(&guid, &pAdapter->guid, sizeof(GUID)) == 0)\n            {\n                if (p->OperStatus == IfOperStatusUp)\n                {\n                    /* This TAP adapter is active (connected). */\n                    if (szAdaptersActive < szAdaptersActiveTail)\n                    {\n                        *(szAdaptersActiveTail++) = L';';\n                    }\n                    memcpy(szAdaptersActiveTail, szAdapterId, 38 * sizeof(WCHAR));\n                    szAdaptersActiveTail += 38;\n                }\n                break;\n            }\n        }\n        CoTaskMemFree(szAdapterId);\n    }\n    szAdaptersTail[0] = 0;\n    szAdaptersActiveTail[0] = 0;\n\n    /* Set Installer properties. */\n    uiResult = MsiSetProperty(hInstall, szAdaptersPropertyName, szAdapters);\n    if (uiResult != ERROR_SUCCESS)\n    {\n        SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But\n                                   we do have an error code. Set last error manually. */\n        msg(M_NONFATAL | M_ERRNO, \"%s: MsiSetProperty(\\\"%s\\\") failed\", __FUNCTION__,\n            szAdaptersPropertyName);\n        goto cleanup_szAdaptersActive;\n    }\n    uiResult = MsiSetProperty(hInstall, szActiveAdaptersPropertyName, szAdaptersActive);\n    if (uiResult != ERROR_SUCCESS)\n    {\n        SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But\n                                   we do have an error code. Set last error manually. */\n        msg(M_NONFATAL | M_ERRNO, \"%s: MsiSetProperty(\\\"%s\\\") failed\", __FUNCTION__,\n            szActiveAdaptersPropertyName);\n        goto cleanup_szAdaptersActive;\n    }\n\ncleanup_szAdaptersActive:\n    free(szAdaptersActive);\ncleanup_szAdapters:\n    free(szAdapters);\ncleanup_pAdapterAdresses:\n    free(pAdapterAdresses);\ncleanup_pAdapterList:\n    tap_free_adapter_list(pAdapterList);\n}\n\n\nUINT __stdcall\nFindSystemInfo(_In_ MSIHANDLE hInstall)\n{\n#ifdef DLLEXP_EXPORT\n#pragma comment(linker, DLLEXP_EXPORT)\n#endif\n\n    debug_popup(__FUNCTION__);\n\n    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));\n\n    OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);\n\n    find_adapters(hInstall, L\"root\\\\\" _L(TAP_WIN_COMPONENT_ID) L\"\\0\" _L(TAP_WIN_COMPONENT_ID) L\"\\0\",\n                  L\"TAPWINDOWS6ADAPTERS\", L\"ACTIVETAPWINDOWS6ADAPTERS\");\n    find_adapters(hInstall,\n                  L\"ovpn-dco\"\n                  L\"\\0\",\n                  L\"OVPNDCOADAPTERS\", L\"ACTIVEOVPNDCOADAPTERS\");\n\n    if (bIsCoInitialized)\n    {\n        CoUninitialize();\n    }\n    return ERROR_SUCCESS;\n}\n\n\nUINT __stdcall\nCloseOpenVPNGUI(_In_ MSIHANDLE hInstall)\n{\n#ifdef DLLEXP_EXPORT\n#pragma comment(linker, DLLEXP_EXPORT)\n#endif\n    UNREFERENCED_PARAMETER(hInstall); /* This CA is does not interact with MSI session (report\n                                         errors, access properties, tables, etc.). */\n\n    debug_popup(__FUNCTION__);\n\n    /* Find OpenVPN GUI window. */\n    HWND hWnd = FindWindow(L\"OpenVPN-GUI\", NULL);\n    if (hWnd)\n    {\n        /* Ask it to close and wait for 100ms. Unfortunately, this will succeed only for recent\n         * OpenVPN GUI that do not run elevated. */\n        SendMessage(hWnd, WM_CLOSE, 0, 0);\n        Sleep(100);\n    }\n\n    return ERROR_SUCCESS;\n}\n\n\nUINT __stdcall\nStartOpenVPNGUI(_In_ MSIHANDLE hInstall)\n{\n#ifdef DLLEXP_EXPORT\n#pragma comment(linker, DLLEXP_EXPORT)\n#endif\n\n    debug_popup(__FUNCTION__);\n\n    UINT uiResult;\n    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));\n\n    OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);\n\n    /* Create and populate a MSI record. */\n    MSIHANDLE hRecord = MsiCreateRecord(1);\n    if (!hRecord)\n    {\n        uiResult = ERROR_INVALID_HANDLE;\n        msg(M_NONFATAL, \"%s: MsiCreateRecord failed\", __FUNCTION__);\n        goto cleanup_CoInitialize;\n    }\n    uiResult = MsiRecordSetString(hRecord, 0, L\"\\\"[#bin.openvpn_gui.exe]\\\"\");\n    if (uiResult != ERROR_SUCCESS)\n    {\n        SetLastError(uiResult); /* MSDN does not mention MsiRecordSetString() to set GetLastError().\n                                   But we do have an error code. Set last error manually. */\n        msg(M_NONFATAL | M_ERRNO, \"%s: MsiRecordSetString failed\", __FUNCTION__);\n        goto cleanup_MsiCreateRecord;\n    }\n\n    /* Format string. */\n    WCHAR szStackBuf[MAX_PATH];\n    DWORD dwPathSize = _countof(szStackBuf);\n    LPWSTR szPath = szStackBuf;\n    uiResult = MsiFormatRecord(hInstall, hRecord, szPath, &dwPathSize);\n    if (uiResult == ERROR_MORE_DATA)\n    {\n        /* Allocate buffer on heap (+1 for terminator), and retry. */\n        szPath = (LPWSTR)malloc((++dwPathSize) * sizeof(WCHAR));\n        if (szPath == NULL)\n        {\n            msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__, dwPathSize * sizeof(WCHAR));\n            uiResult = ERROR_OUTOFMEMORY;\n            goto cleanup_MsiCreateRecord;\n        }\n\n        uiResult = MsiFormatRecord(hInstall, hRecord, szPath, &dwPathSize);\n    }\n    if (uiResult != ERROR_SUCCESS)\n    {\n        SetLastError(uiResult); /* MSDN does not mention MsiFormatRecord() to set GetLastError().\n                                   But we do have an error code. Set last error manually. */\n        msg(M_NONFATAL | M_ERRNO, \"%s: MsiFormatRecord failed\", __FUNCTION__);\n        goto cleanup_malloc_szPath;\n    }\n\n    /* Launch the OpenVPN GUI. */\n    SHELLEXECUTEINFO sei = { .cbSize = sizeof(SHELLEXECUTEINFO),\n                             .fMask =\n                                 SEE_MASK_FLAG_NO_UI, /* Don't show error UI, we'll display it. */\n                             .lpFile = szPath,\n                             .nShow = SW_SHOWNORMAL };\n    if (!ShellExecuteEx(&sei))\n    {\n        uiResult = GetLastError();\n        msg(M_NONFATAL | M_ERRNO, \"%s: ShellExecuteEx(%s) failed\", __FUNCTION__, szPath);\n        goto cleanup_malloc_szPath;\n    }\n\n    uiResult = ERROR_SUCCESS;\n\ncleanup_malloc_szPath:\n    if (szPath != szStackBuf)\n    {\n        free(szPath);\n    }\ncleanup_MsiCreateRecord:\n    MsiCloseHandle(hRecord);\ncleanup_CoInitialize:\n    if (bIsCoInitialized)\n    {\n        CoUninitialize();\n    }\n    return uiResult;\n}\n\n\n/**\n * Schedules adapter creation.\n *\n * When the rollback is enabled, the adapter deletition is scheduled on rollback.\n *\n * @param seq           The argument sequence to pass to InstallTUNTAPAdapters custom action\n *\n * @param seqRollback   The argument sequence to pass to InstallTUNTAPAdaptersRollback custom\n *                      action. NULL when rollback is disabled.\n *\n * @param szDisplayName  Adapter display name\n *\n * @param szHardwareId  Adapter hardware ID\n *\n * @param iTicks        Pointer to an integer that represents amount of work (on progress\n *                      indicator) the InstallTUNTAPAdapters will take. This function increments it\n *                      by MSICA_ADAPTER_TICK_SIZE for each adapter to create.\n *\n * @return ERROR_SUCCESS on success; An error code otherwise\n */\nstatic DWORD\nschedule_adapter_create(_Inout_ struct msica_arg_seq *seq,\n                        _Inout_opt_ struct msica_arg_seq *seqRollback, _In_z_ LPCWSTR szDisplayName,\n                        _In_z_ LPCWSTR szHardwareId, _Inout_ int *iTicks)\n{\n    /* Get existing network adapters. */\n    struct tap_adapter_node *pAdapterList = NULL;\n    DWORD dwResult = tap_list_adapters(NULL, NULL, &pAdapterList);\n    if (dwResult != ERROR_SUCCESS)\n    {\n        return dwResult;\n    }\n\n    /* Does adapter exist? */\n    for (struct tap_adapter_node *pAdapterOther = pAdapterList;;\n         pAdapterOther = pAdapterOther->pNext)\n    {\n        if (pAdapterOther == NULL)\n        {\n            /* No adapter with a same name found. */\n            WCHAR szArgument[10 /*create=\"\"|deleteN=\"\"*/ + MAX_PATH /*szDisplayName*/ + 1 /*|*/\n                             + MAX_PATH /*szHardwareId*/ + 1 /*terminator*/];\n\n            /* InstallTUNTAPAdapters will create the adapter. */\n            swprintf_s(szArgument, _countof(szArgument), L\"create=\\\"%.*s|%.*s\\\"\", MAX_PATH,\n                       szDisplayName, MAX_PATH, szHardwareId);\n            msica_arg_seq_add_tail(seq, szArgument);\n\n            if (seqRollback)\n            {\n                /* InstallTUNTAPAdaptersRollback will delete the adapter. */\n                swprintf_s(szArgument, _countof(szArgument), L\"deleteN=\\\"%.*s\\\"\", MAX_PATH,\n                           szDisplayName);\n                msica_arg_seq_add_head(seqRollback, szArgument);\n            }\n\n            *iTicks += MSICA_ADAPTER_TICK_SIZE;\n            break;\n        }\n        else if (wcsicmp(szDisplayName, pAdapterOther->szName) == 0)\n        {\n            /* Adapter with a same name found. */\n            for (LPCWSTR hwid = pAdapterOther->szzHardwareIDs;; hwid += wcslen(hwid) + 1)\n            {\n                if (hwid[0] == 0)\n                {\n                    /* This adapter has a different hardware ID. */\n                    msg(M_NONFATAL, \"%s: Adapter with name \\\"%ls\\\" already exists\", __FUNCTION__,\n                        pAdapterOther->szName);\n                    dwResult = ERROR_ALREADY_EXISTS;\n                    goto cleanup_pAdapterList;\n                }\n                else if (wcsicmp(hwid, szHardwareId) == 0)\n                {\n                    /* This is an adapter with the requested hardware ID. We already have what we\n                     * want! */\n                    break;\n                }\n            }\n            break; /* Adapter names are unique. There should be no other adapter with this name. */\n        }\n    }\n\ncleanup_pAdapterList:\n    tap_free_adapter_list(pAdapterList);\n    return dwResult;\n}\n\n\n/**\n * Schedules adapter deletion.\n *\n * When the rollback is enabled, the adapter deletition is scheduled as: disable in\n * UninstallTUNTAPAdapters, enable on rollback, delete on commit.\n *\n * When rollback is disabled, the adapter deletition is scheduled as delete in\n * UninstallTUNTAPAdapters.\n *\n * @param seq           The argument sequence to pass to UninstallTUNTAPAdapters custom action\n *\n * @param seqCommit     The argument sequence to pass to UninstallTUNTAPAdaptersCommit custom\n *                      action. NULL when rollback is disabled.\n *\n * @param seqRollback   The argument sequence to pass to UninstallTUNTAPAdaptersRollback custom\n *                      action. NULL when rollback is disabled.\n *\n * @param szDisplayName  Adapter display name\n *\n * @param szzHardwareIDs  String of strings with acceptable adapter hardware IDs\n *\n * @param iTicks        Pointer to an integer that represents amount of work (on progress\n *                      indicator) the UninstallTUNTAPAdapters will take. This function increments\n *                      it by MSICA_ADAPTER_TICK_SIZE for each adapter to delete.\n *\n * @return ERROR_SUCCESS on success; An error code otherwise\n */\nstatic DWORD\nschedule_adapter_delete(_Inout_ struct msica_arg_seq *seq,\n                        _Inout_opt_ struct msica_arg_seq *seqCommit,\n                        _Inout_opt_ struct msica_arg_seq *seqRollback, _In_z_ LPCWSTR szDisplayName,\n                        _In_z_ LPCWSTR szzHardwareIDs, _Inout_ int *iTicks)\n{\n    /* Get adapters with given hardware ID. */\n    struct tap_adapter_node *pAdapterList = NULL;\n    DWORD dwResult = tap_list_adapters(NULL, szzHardwareIDs, &pAdapterList);\n    if (dwResult != ERROR_SUCCESS)\n    {\n        return dwResult;\n    }\n\n    /* Does adapter exist? */\n    for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter != NULL;\n         pAdapter = pAdapter->pNext)\n    {\n        if (wcsicmp(szDisplayName, pAdapter->szName) == 0)\n        {\n            /* Adapter found. */\n            WCHAR szArgument[8 /*disable=|enable=|delete=*/\n                             + 38 /*{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}*/ + 1 /*terminator*/];\n            if (seqCommit && seqRollback)\n            {\n                /* UninstallTUNTAPAdapters will disable the adapter. */\n                swprintf_s(szArgument, _countof(szArgument), L\"disable=\" _L(PRIXGUID),\n                           PRIGUID_PARAM(pAdapter->guid));\n                msica_arg_seq_add_tail(seq, szArgument);\n\n                /* UninstallTUNTAPAdaptersRollback will re-enable the adapter. */\n                swprintf_s(szArgument, _countof(szArgument), L\"enable=\" _L(PRIXGUID),\n                           PRIGUID_PARAM(pAdapter->guid));\n                msica_arg_seq_add_head(seqRollback, szArgument);\n\n                /* UninstallTUNTAPAdaptersCommit will delete the adapter. */\n                swprintf_s(szArgument, _countof(szArgument), L\"delete=\" _L(PRIXGUID),\n                           PRIGUID_PARAM(pAdapter->guid));\n                msica_arg_seq_add_tail(seqCommit, szArgument);\n            }\n            else\n            {\n                /* UninstallTUNTAPAdapters will delete the adapter. */\n                swprintf_s(szArgument, _countof(szArgument), L\"delete=\" _L(PRIXGUID),\n                           PRIGUID_PARAM(pAdapter->guid));\n                msica_arg_seq_add_tail(seq, szArgument);\n            }\n\n            iTicks += MSICA_ADAPTER_TICK_SIZE;\n            break; /* Adapter names are unique. There should be no other adapter with this name. */\n        }\n    }\n\n    tap_free_adapter_list(pAdapterList);\n    return dwResult;\n}\n\n\nUINT __stdcall\nEvaluateTUNTAPAdapters(_In_ MSIHANDLE hInstall)\n{\n#ifdef DLLEXP_EXPORT\n#pragma comment(linker, DLLEXP_EXPORT)\n#endif\n\n    debug_popup(__FUNCTION__);\n\n    UINT uiResult;\n    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));\n\n    OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);\n\n    struct msica_arg_seq seqInstall, seqInstallCommit, seqInstallRollback, seqUninstall,\n        seqUninstallCommit, seqUninstallRollback;\n    msica_arg_seq_init(&seqInstall);\n    msica_arg_seq_init(&seqInstallCommit);\n    msica_arg_seq_init(&seqInstallRollback);\n    msica_arg_seq_init(&seqUninstall);\n    msica_arg_seq_init(&seqUninstallCommit);\n    msica_arg_seq_init(&seqUninstallRollback);\n\n    /* Check rollback state. */\n    bool bRollbackEnabled =\n        MsiEvaluateCondition(hInstall, L\"RollbackDisabled\") != MSICONDITION_TRUE;\n\n    /* Open MSI database. */\n    MSIHANDLE hDatabase = MsiGetActiveDatabase(hInstall);\n    if (hDatabase == 0)\n    {\n        msg(M_NONFATAL, \"%s: MsiGetActiveDatabase failed\", __FUNCTION__);\n        uiResult = ERROR_INVALID_HANDLE;\n        goto cleanup_exec_seq;\n    }\n\n    /* Check if TUNTAPAdapter table exists. If it doesn't exist, there's nothing to do. */\n    switch (MsiDatabaseIsTablePersistent(hDatabase, L\"TUNTAPAdapter\"))\n    {\n        case MSICONDITION_FALSE:\n        case MSICONDITION_TRUE:\n            break;\n\n        default:\n            uiResult = ERROR_SUCCESS;\n            goto cleanup_hDatabase;\n    }\n\n    /* Prepare a query to get a list/view of adapters. */\n    MSIHANDLE hViewST = 0;\n    LPCWSTR szQuery =\n        L\"SELECT `Adapter`,`DisplayName`,`Condition`,`Component_`,`HardwareId` FROM `TUNTAPAdapter`\";\n    uiResult = MsiDatabaseOpenView(hDatabase, szQuery, &hViewST);\n    if (uiResult != ERROR_SUCCESS)\n    {\n        SetLastError(\n            uiResult); /* MSDN does not mention MsiDatabaseOpenView() to set GetLastError(). But we\n                          do have an error code. Set last error manually. */\n        msg(M_NONFATAL | M_ERRNO, \"%s: MsiDatabaseOpenView(\\\"%ls\\\") failed\", __FUNCTION__, szQuery);\n        goto cleanup_hDatabase;\n    }\n\n    /* Execute query! */\n    uiResult = MsiViewExecute(hViewST, 0);\n    if (uiResult != ERROR_SUCCESS)\n    {\n        SetLastError(uiResult); /* MSDN does not mention MsiViewExecute() to set GetLastError(). But\n                                   we do have an error code. Set last error manually. */\n        msg(M_NONFATAL | M_ERRNO, \"%s: MsiViewExecute(\\\"%ls\\\") failed\", __FUNCTION__, szQuery);\n        goto cleanup_hViewST;\n    }\n\n    /* Create a record to report progress with. */\n    MSIHANDLE hRecordProg = MsiCreateRecord(2);\n    if (!hRecordProg)\n    {\n        uiResult = ERROR_INVALID_HANDLE;\n        msg(M_NONFATAL, \"%s: MsiCreateRecord failed\", __FUNCTION__);\n        goto cleanup_hViewST_close;\n    }\n\n    for (;;)\n    {\n        /* Fetch one record from the view. */\n        MSIHANDLE hRecord = 0;\n        uiResult = MsiViewFetch(hViewST, &hRecord);\n        if (uiResult == ERROR_NO_MORE_ITEMS)\n        {\n            uiResult = ERROR_SUCCESS;\n            break;\n        }\n        else if (uiResult != ERROR_SUCCESS)\n        {\n            SetLastError(uiResult); /* MSDN does not mention MsiViewFetch() to set GetLastError().\n                                       But we do have an error code. Set last error manually. */\n            msg(M_NONFATAL | M_ERRNO, \"%s: MsiViewFetch failed\", __FUNCTION__);\n            goto cleanup_hRecordProg;\n        }\n\n        INSTALLSTATE iInstalled, iAction;\n        {\n            /* Read adapter component ID (`Component_` is field #4). */\n            LPWSTR szValue = NULL;\n            uiResult = msi_get_record_string(hRecord, 4, &szValue);\n            if (uiResult != ERROR_SUCCESS)\n            {\n                goto cleanup_hRecord;\n            }\n\n            /* Get the component state. */\n            uiResult = MsiGetComponentState(hInstall, szValue, &iInstalled, &iAction);\n            if (uiResult != ERROR_SUCCESS)\n            {\n                SetLastError(uiResult); /* MSDN does not mention MsiGetComponentState() to set\n                                           GetLastError(). But we do have an error code. Set last\n                                           error manually. */\n                msg(M_NONFATAL | M_ERRNO, \"%s: MsiGetComponentState(\\\"%ls\\\") failed\", __FUNCTION__,\n                    szValue);\n                free(szValue);\n                goto cleanup_hRecord;\n            }\n            free(szValue);\n        }\n\n        /* Get adapter display name (`DisplayName` is field #2). */\n        LPWSTR szDisplayName = NULL;\n        uiResult = msi_format_field(hInstall, hRecord, 2, &szDisplayName);\n        if (uiResult != ERROR_SUCCESS)\n        {\n            goto cleanup_hRecord;\n        }\n        /* `DisplayName` field type is\n         * [Filename](https://docs.microsoft.com/en-us/windows/win32/msi/filename), which is either\n         * \"8.3|long name\" or \"8.3\". */\n        LPWSTR szDisplayNameEx = wcschr(szDisplayName, L'|');\n        szDisplayNameEx = szDisplayNameEx != NULL ? szDisplayNameEx + 1 : szDisplayName;\n\n        /* Get adapter hardware ID (`HardwareId` is field #5). */\n        WCHAR szzHardwareIDs[0x100] = { 0 };\n        {\n            LPWSTR szHwId = NULL;\n            uiResult = msi_get_record_string(hRecord, 5, &szHwId);\n            if (uiResult != ERROR_SUCCESS)\n            {\n                goto cleanup_szDisplayName;\n            }\n            memcpy_s(szzHardwareIDs,\n                     sizeof(szzHardwareIDs)\n                         - 2 * sizeof(WCHAR) /*requires double zero termination*/,\n                     szHwId, wcslen(szHwId) * sizeof(WCHAR));\n            free(szHwId);\n        }\n\n        if (iAction > INSTALLSTATE_BROKEN)\n        {\n            int iTicks = 0;\n\n            if (iAction >= INSTALLSTATE_LOCAL)\n            {\n                /* Read and evaluate adapter condition (`Condition` is field #3). */\n                LPWSTR szValue = NULL;\n                uiResult = msi_get_record_string(hRecord, 3, &szValue);\n                if (uiResult != ERROR_SUCCESS)\n                {\n                    goto cleanup_szDisplayName;\n                }\n#if defined(__GNUC__) || defined(__clang__)\n/*\n * warning: enumeration value ‘MSICONDITION_TRUE’ not handled in switch\n * warning: enumeration value ‘MSICONDITION_NONE’ not handled in switch\n */\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wswitch\"\n#endif\n                switch (MsiEvaluateCondition(hInstall, szValue))\n                {\n                    case MSICONDITION_FALSE:\n                        free(szValue);\n                        goto cleanup_szDisplayName;\n\n                    case MSICONDITION_ERROR:\n                        uiResult = ERROR_INVALID_FIELD;\n                        msg(M_NONFATAL | M_ERRNO, \"%s: MsiEvaluateCondition(\\\"%ls\\\") failed\",\n                            __FUNCTION__, szValue);\n                        free(szValue);\n                        goto cleanup_szDisplayName;\n                }\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n                free(szValue);\n\n                /* Component is or should be installed. Schedule adapter creation. */\n                if (schedule_adapter_create(&seqInstall,\n                                            bRollbackEnabled ? &seqInstallRollback : NULL,\n                                            szDisplayNameEx, szzHardwareIDs, &iTicks)\n                    != ERROR_SUCCESS)\n                {\n                    uiResult = ERROR_INSTALL_FAILED;\n                    goto cleanup_szDisplayName;\n                }\n            }\n            else\n            {\n                /* Component is installed, but should be degraded to advertised/removed. Schedule\n                 * adapter deletition.\n                 *\n                 * Note: On adapter removal (product is being uninstalled), we tolerate dwResult\n                 * error. Better a partial uninstallation than no uninstallation at all.\n                 */\n                schedule_adapter_delete(&seqUninstall,\n                                        bRollbackEnabled ? &seqUninstallCommit : NULL,\n                                        bRollbackEnabled ? &seqUninstallRollback : NULL,\n                                        szDisplayNameEx, szzHardwareIDs, &iTicks);\n            }\n\n            /* Arrange the amount of tick space to add to the progress indicator.\n             * Do this within the loop to poll for user cancellation. */\n            MsiRecordSetInteger(hRecordProg, 1, 3 /* OP3 = Add ticks to the expected total number of progress of the progress bar */);\n            MsiRecordSetInteger(hRecordProg, 2, iTicks);\n            if (MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL)\n            {\n                uiResult = ERROR_INSTALL_USEREXIT;\n                goto cleanup_szDisplayName;\n            }\n        }\n\ncleanup_szDisplayName:\n        free(szDisplayName);\ncleanup_hRecord:\n        MsiCloseHandle(hRecord);\n        if (uiResult != ERROR_SUCCESS)\n        {\n            goto cleanup_hRecordProg;\n        }\n    }\n\n    /* save path to user's temp dir to be used later by deferred actions */\n    WCHAR tmpDir[MAX_PATH];\n    GetTempPath(MAX_PATH, tmpDir);\n\n    WCHAR str[MAX_PATH + 7];\n    swprintf_s(str, _countof(str), L\"tmpdir=%ls\", tmpDir);\n    msica_arg_seq_add_tail(&seqInstall, str);\n    msica_arg_seq_add_tail(&seqInstallCommit, str);\n    msica_arg_seq_add_tail(&seqInstallRollback, str);\n    msica_arg_seq_add_tail(&seqUninstall, str);\n    msica_arg_seq_add_tail(&seqUninstallCommit, str);\n    msica_arg_seq_add_tail(&seqUninstallRollback, str);\n\n    /* Store deferred custom action parameters. */\n    if ((uiResult = setup_sequence(hInstall, L\"InstallTUNTAPAdapters\", &seqInstall))\n            != ERROR_SUCCESS\n        || (uiResult = setup_sequence(hInstall, L\"InstallTUNTAPAdaptersCommit\", &seqInstallCommit))\n               != ERROR_SUCCESS\n        || (uiResult =\n                setup_sequence(hInstall, L\"InstallTUNTAPAdaptersRollback\", &seqInstallRollback))\n               != ERROR_SUCCESS\n        || (uiResult = setup_sequence(hInstall, L\"UninstallTUNTAPAdapters\", &seqUninstall))\n               != ERROR_SUCCESS\n        || (uiResult =\n                setup_sequence(hInstall, L\"UninstallTUNTAPAdaptersCommit\", &seqUninstallCommit))\n               != ERROR_SUCCESS\n        || (uiResult =\n                setup_sequence(hInstall, L\"UninstallTUNTAPAdaptersRollback\", &seqUninstallRollback))\n               != ERROR_SUCCESS)\n    {\n        goto cleanup_hRecordProg;\n    }\n\n    uiResult = ERROR_SUCCESS;\n\ncleanup_hRecordProg:\n    MsiCloseHandle(hRecordProg);\ncleanup_hViewST_close:\n    MsiViewClose(hViewST);\ncleanup_hViewST:\n    MsiCloseHandle(hViewST);\ncleanup_hDatabase:\n    MsiCloseHandle(hDatabase);\ncleanup_exec_seq:\n    msica_arg_seq_free(&seqInstall);\n    msica_arg_seq_free(&seqInstallCommit);\n    msica_arg_seq_free(&seqInstallRollback);\n    msica_arg_seq_free(&seqUninstall);\n    msica_arg_seq_free(&seqUninstallCommit);\n    msica_arg_seq_free(&seqUninstallRollback);\n    if (bIsCoInitialized)\n    {\n        CoUninitialize();\n    }\n    return uiResult;\n}\n\n\n/**\n * Parses string encoded GUID.\n *\n * @param szArg         Zero terminated string where the GUID string starts\n *\n * @param guid          Pointer to GUID that receives parsed value\n *\n * @return TRUE on success; FALSE otherwise\n */\nstatic BOOL\nparse_guid(_In_z_ LPCWSTR szArg, _Out_ GUID *guid)\n{\n    if (swscanf_s(szArg, _L(PRIXGUID), PRIGUID_PARAM_REF(*guid)) != 11)\n    {\n        msg(M_NONFATAL | M_ERRNO, \"%s: swscanf_s(\\\"%ls\\\") failed\", __FUNCTION__, szArg);\n        return FALSE;\n    }\n    return TRUE;\n}\n\n\n/**\n * Create empty file in user's temp directory. The existence of this file\n * is checked in the end of installation by ScheduleReboot immediate custom action\n * which schedules reboot.\n *\n * @param szTmpDir path to user's temp dirctory\n *\n */\nstatic void\nCreateRebootFile(_In_z_ LPCWSTR szTmpDir)\n{\n    WCHAR path[MAX_PATH];\n    swprintf_s(path, _countof(path), L\"%s%s\", szTmpDir, FILE_NEED_REBOOT);\n\n    msg(M_WARN, \"%s: Reboot required, create reboot indication file \\\"%ls\\\"\", __FUNCTION__, path);\n\n    HANDLE file =\n        CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);\n    if (file == INVALID_HANDLE_VALUE)\n    {\n        msg(M_NONFATAL | M_ERRNO, \"%s: CreateFile(\\\"%ls\\\") failed\", __FUNCTION__, path);\n    }\n    else\n    {\n        CloseHandle(file);\n    }\n}\n\nUINT __stdcall\nProcessDeferredAction(_In_ MSIHANDLE hInstall)\n{\n#ifdef DLLEXP_EXPORT\n#pragma comment(linker, DLLEXP_EXPORT)\n#endif\n\n    debug_popup(__FUNCTION__);\n\n    UINT uiResult;\n    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));\n    WCHAR tmpDir[MAX_PATH] = { 0 };\n\n    OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);\n\n    BOOL bIsCleanup =\n        MsiGetMode(hInstall, MSIRUNMODE_COMMIT) || MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK);\n\n    /* Get sequence arguments. Always Unicode as CommandLineToArgvW() is available as Unicode-only.\n     */\n    LPWSTR szSequence = NULL;\n    uiResult = msi_get_string(hInstall, L\"CustomActionData\", &szSequence);\n    if (uiResult != ERROR_SUCCESS)\n    {\n        goto cleanup_CoInitialize;\n    }\n    int nArgs;\n    LPWSTR *szArg = CommandLineToArgvW(szSequence, &nArgs);\n    if (szArg == NULL)\n    {\n        uiResult = GetLastError();\n        msg(M_NONFATAL | M_ERRNO, \"%s: CommandLineToArgvW(\\\"%ls\\\") failed\", __FUNCTION__,\n            szSequence);\n        goto cleanup_szSequence;\n    }\n\n    /* Tell the installer to use explicit progress messages. */\n    MSIHANDLE hRecordProg = MsiCreateRecord(3);\n    MsiRecordSetInteger(hRecordProg, 1, 1);\n    MsiRecordSetInteger(hRecordProg, 2, 1);\n    MsiRecordSetInteger(hRecordProg, 3, 0);\n    MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg);\n\n    /* Prepare hRecordProg for progress messages. */\n    MsiRecordSetInteger(hRecordProg, 1, 2);\n    MsiRecordSetInteger(hRecordProg, 3, 0);\n\n    BOOL bRebootRequired = FALSE;\n\n    for (int i = 1 /*CommandLineToArgvW injects msiexec.exe as szArg[0]*/; i < nArgs; ++i)\n    {\n        DWORD dwResult = ERROR_SUCCESS;\n\n        if (wcsncmp(szArg[i], L\"create=\", 7) == 0)\n        {\n            /* Create an adapter with a given name and hardware ID. */\n            LPWSTR szName = szArg[i] + 7;\n            LPWSTR szHardwareId = wcschr(szName, L'|');\n            if (szHardwareId == NULL)\n            {\n                goto invalid_argument;\n            }\n            szHardwareId[0] = 0;\n            ++szHardwareId;\n\n            {\n                /* Report the name of the adapter to installer. */\n                MSIHANDLE hRecord = MsiCreateRecord(4);\n                MsiRecordSetString(hRecord, 1, L\"Creating adapter\");\n                MsiRecordSetString(hRecord, 2, szName);\n                MsiRecordSetString(hRecord, 3, szHardwareId);\n                int iResult = MsiProcessMessage(hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);\n                MsiCloseHandle(hRecord);\n                if (iResult == IDCANCEL)\n                {\n                    uiResult = ERROR_INSTALL_USEREXIT;\n                    goto cleanup;\n                }\n            }\n\n            GUID guidAdapter;\n            dwResult = tap_create_adapter(NULL, NULL, szHardwareId, &bRebootRequired, &guidAdapter);\n            if (dwResult == ERROR_SUCCESS)\n            {\n                /* Set adapter name. May fail on some machines, but that is not critical - use\n                 * silent flag to mute messagebox and print error only to log */\n                tap_set_adapter_name(&guidAdapter, szName, TRUE);\n            }\n        }\n        else if (wcsncmp(szArg[i], L\"deleteN=\", 8) == 0)\n        {\n            /* Delete the adapter by name. */\n            LPCWSTR szName = szArg[i] + 8;\n\n            {\n                /* Report the name of the adapter to installer. */\n                MSIHANDLE hRecord = MsiCreateRecord(3);\n                MsiRecordSetString(hRecord, 1, L\"Deleting adapter\");\n                MsiRecordSetString(hRecord, 2, szName);\n                int iResult = MsiProcessMessage(hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);\n                MsiCloseHandle(hRecord);\n                if (iResult == IDCANCEL)\n                {\n                    uiResult = ERROR_INSTALL_USEREXIT;\n                    goto cleanup;\n                }\n            }\n\n            /* Get existing adapters. */\n            struct tap_adapter_node *pAdapterList = NULL;\n            dwResult = tap_list_adapters(NULL, NULL, &pAdapterList);\n            if (dwResult == ERROR_SUCCESS)\n            {\n                /* Does the adapter exist? */\n                for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter != NULL;\n                     pAdapter = pAdapter->pNext)\n                {\n                    if (wcsicmp(szName, pAdapter->szName) == 0)\n                    {\n                        /* Adapter found. */\n                        dwResult = tap_delete_adapter(NULL, &pAdapter->guid, &bRebootRequired);\n                        break;\n                    }\n                }\n\n                tap_free_adapter_list(pAdapterList);\n            }\n        }\n        else if (wcsncmp(szArg[i], L\"delete=\", 7) == 0)\n        {\n            /* Delete the adapter by GUID. */\n            GUID guid;\n            if (!parse_guid(szArg[i] + 7, &guid))\n            {\n                goto invalid_argument;\n            }\n            dwResult = tap_delete_adapter(NULL, &guid, &bRebootRequired);\n        }\n        else if (wcsncmp(szArg[i], L\"enable=\", 7) == 0)\n        {\n            /* Enable the adapter. */\n            GUID guid;\n            if (!parse_guid(szArg[i] + 7, &guid))\n            {\n                goto invalid_argument;\n            }\n            dwResult = tap_enable_adapter(NULL, &guid, TRUE, &bRebootRequired);\n        }\n        else if (wcsncmp(szArg[i], L\"disable=\", 8) == 0)\n        {\n            /* Disable the adapter. */\n            GUID guid;\n            if (!parse_guid(szArg[i] + 8, &guid))\n            {\n                goto invalid_argument;\n            }\n            dwResult = tap_enable_adapter(NULL, &guid, FALSE, &bRebootRequired);\n        }\n        else if (wcsncmp(szArg[i], L\"tmpdir=\", 7) == 0)\n        {\n            wcscpy_s(tmpDir, _countof(tmpDir), szArg[i] + 7);\n        }\n        else\n        {\n            goto invalid_argument;\n        }\n\n        if (dwResult != ERROR_SUCCESS && !bIsCleanup /* Ignore errors in case of commit/rollback to do as much work as possible. */)\n        {\n            uiResult = ERROR_INSTALL_FAILURE;\n            goto cleanup;\n        }\n\n        /* Report progress and check for user cancellation. */\n        MsiRecordSetInteger(hRecordProg, 2, MSICA_ADAPTER_TICK_SIZE);\n        if (MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL)\n        {\n            dwResult = ERROR_INSTALL_USEREXIT;\n            goto cleanup;\n        }\n\n        continue;\n\ninvalid_argument:\n        msg(M_NONFATAL, \"%s: Ignoring invalid argument: %ls\", __FUNCTION__, szArg[i]);\n    }\n\ncleanup:\n    if (bRebootRequired && wcslen(tmpDir) > 0)\n    {\n        CreateRebootFile(tmpDir);\n    }\n    MsiCloseHandle(hRecordProg);\n    LocalFree(szArg);\ncleanup_szSequence:\n    free(szSequence);\ncleanup_CoInitialize:\n    if (bIsCoInitialized)\n    {\n        CoUninitialize();\n    }\n    return uiResult;\n}\n\nUINT __stdcall\nCheckAndScheduleReboot(_In_ MSIHANDLE hInstall)\n{\n#ifdef DLLEXP_EXPORT\n#pragma comment(linker, DLLEXP_EXPORT)\n#endif\n\n    debug_popup(__FUNCTION__);\n\n    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));\n\n    OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);\n\n    /* get user-specific temp path, to where we create reboot indication file */\n    WCHAR tempPath[MAX_PATH];\n    GetTempPathW(MAX_PATH, tempPath);\n\n    /* check if reboot file exists */\n    WCHAR path[MAX_PATH];\n    swprintf_s(path, _countof(path), L\"%s%s\", tempPath, FILE_NEED_REBOOT);\n    WIN32_FIND_DATA data = { 0 };\n    HANDLE searchHandle = FindFirstFileW(path, &data);\n    if (searchHandle != INVALID_HANDLE_VALUE)\n    {\n        msg(M_WARN, \"%s: Reboot file exists, schedule reboot\", __FUNCTION__);\n\n        FindClose(searchHandle);\n        DeleteFileW(path);\n\n        MsiSetMode(hInstall, MSIRUNMODE_REBOOTATEND, TRUE);\n    }\n\n    if (bIsCoInitialized)\n    {\n        CoUninitialize();\n    }\n    return ERROR_SUCCESS;\n}\n"
  },
  {
    "path": "src/openvpnmsica/openvpnmsica.h",
    "content": "/*\n *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages\n *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA\n *\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef MSICA_H\n#define MSICA_H\n\n#include <windows.h>\n#include <msi.h>\n#include \"../tapctl/basic.h\"\n\n\n/*\n * Error codes (next unused 2552L)\n */\n#define ERROR_MSICA       2550L\n#define ERROR_MSICA_ERRNO 2551L\n\n\n/**\n * Thread local storage data\n */\nstruct openvpnmsica_thread_data\n{\n    MSIHANDLE hInstall; /**< Handle to the installation session. */\n};\n\n\n/**\n * MSI session handle thread local storage index\n */\nextern DWORD openvpnmsica_thread_data_idx;\n\n\n/**\n * Set MSI session handle in thread local storage.\n */\n#define OPENVPNMSICA_SAVE_MSI_SESSION(hInstall)                                           \\\n    {                                                                                     \\\n        struct openvpnmsica_thread_data *s =                                              \\\n            (struct openvpnmsica_thread_data *)TlsGetValue(openvpnmsica_thread_data_idx); \\\n        s->hInstall = (hInstall);                                                         \\\n    }\n\n\n/*\n * Exported DLL Functions\n */\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\n/* Ensure that clang-cl, which does not understand the cl specific\n * preprocessor defines like #pragma comment(linker, DLLEXP_EXPORT)\n * is handled the same way as mingw and uses the alternative instead\n * and does not define DLLEXP_EXPORT */\n#if defined(__GNUC__) || defined(__clang__)\n#define DLLEXP_DECL __declspec(dllexport)\n#else\n#define DLLEXP_DECL\n#define DLLEXP_EXPORT \"/EXPORT:\" __FUNCTION__ \"=\" __FUNCDNAME__\n#endif\n\n\n    /**\n     * Determines Windows information:\n     *\n     * - Sets `OPENVPNSERVICE` MSI property to PID of OpenVPN Service if running, or its EXE path if\n     *   configured for auto-start.\n     *\n     * - Finds existing TAP-Windows6 adapters and set TAPWINDOWS6ADAPTERS and\n     *   ACTIVETAPWINDOWS6ADAPTERS properties with semicolon delimited list of all installed adapter\n     *   GUIDs and active adapter GUIDs respectively.\n     *\n     * - Finds existing ovpn-dco adapters and set OVPNDCOADAPTERS and ACTIVEOVPNDCOADAPTERS\n     * properties with semicolon delimited list of all installed adapter GUIDs and active adapter\n     * GUIDs respectively.\n     *\n     * @param hInstall      Handle to the installation provided to the DLL custom action\n     *\n     * @return ERROR_SUCCESS on success; An error code otherwise\n     *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx\n     */\n    DLLEXP_DECL UINT __stdcall FindSystemInfo(_In_ MSIHANDLE hInstall);\n\n\n    /**\n     * Find OpenVPN GUI window and send it a WM_CLOSE message.\n     *\n     * @param hInstall      Handle to the installation provided to the DLL custom action\n     *\n     * @return ERROR_SUCCESS on success; An error code otherwise\n     *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx\n     */\n    DLLEXP_DECL UINT __stdcall CloseOpenVPNGUI(_In_ MSIHANDLE hInstall);\n\n\n    /**\n     * Launches OpenVPN GUI. It's path is obtained by expanding the `[#bin.openvpn_gui.exe]`\n     * therefore, its Id field in File table must be \"bin.openvpn_gui.exe\".\n     *\n     * @param hInstall      Handle to the installation provided to the DLL custom action\n     *\n     * @return ERROR_SUCCESS on success; An error code otherwise\n     *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx\n     */\n    DLLEXP_DECL UINT __stdcall StartOpenVPNGUI(_In_ MSIHANDLE hInstall);\n\n\n    /**\n     * Evaluate the TUNTAPAdapter table of the MSI package database and prepare a list of TAP\n     * adapters to install/remove.\n     *\n     * @param hInstall      Handle to the installation provided to the DLL custom action\n     *\n     * @return ERROR_SUCCESS on success; An error code otherwise\n     *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx\n     */\n    DLLEXP_DECL UINT __stdcall EvaluateTUNTAPAdapters(_In_ MSIHANDLE hInstall);\n\n\n    /**\n     * Perform scheduled deferred action.\n     *\n     * @param hInstall      Handle to the installation provided to the DLL custom action\n     *\n     * @return ERROR_SUCCESS on success; An error code otherwise\n     *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx\n     */\n    DLLEXP_DECL UINT __stdcall ProcessDeferredAction(_In_ MSIHANDLE hInstall);\n\n\n    /**\n     * Schedule reboot after installation if reboot\n     * indication file is found in user's temp directory\n     *\n     * @param hInstall      Handle to the installation provided to the DLL custom action\n     *\n     * @return ERROR_SUCCESS on success; An error code otherwise\n     *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx\n     */\n    DLLEXP_DECL UINT __stdcall CheckAndScheduleReboot(_In_ MSIHANDLE hInstall);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* ifndef MSICA_H */\n"
  },
  {
    "path": "src/openvpnmsica/openvpnmsica_resources.rc",
    "content": "/*\n *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages\n *\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n#include <winresrc.h>\n\n#pragma code_page(65001) /* UTF8 */\n\nLANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL\n\nVS_VERSION_INFO VERSIONINFO\n    FILEVERSION OPENVPN_VERSION_RESOURCE\n    PRODUCTVERSION OPENVPN_VERSION_RESOURCE\n    FILEFLAGSMASK VS_FF_DEBUG | VS_FF_PRERELEASE | VS_FF_PATCHED | VS_FF_PRIVATEBUILD | VS_FF_SPECIALBUILD\n#ifdef _DEBUG\n    FILEFLAGS VS_FF_DEBUG\n#else\n    FILEFLAGS 0x0L\n#endif\n    FILEOS VOS_NT_WINDOWS32\n    FILETYPE VFT_DLL\n    FILESUBTYPE 0x0L\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"040904b0\"\n        BEGIN\n            VALUE \"CompanyName\", \"The OpenVPN Project\"\n            VALUE \"FileDescription\", \"Custom Action DLL to provide OpenVPN-specific support to MSI packages\"\n            VALUE \"FileVersion\", PACKAGE_VERSION \".0\"\n            VALUE \"InternalName\", \"OpenVPN\"\n            VALUE \"LegalCopyright\", \"Copyright © The OpenVPN Project\"\n            VALUE \"OriginalFilename\", \"libopenvpnmsica.dll\"\n            VALUE \"ProductName\", \"OpenVPN\"\n            VALUE \"ProductVersion\", PACKAGE_VERSION \".0\"\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x409, 1200\n    END\nEND\n"
  },
  {
    "path": "src/openvpnserv/CMakeLists.txt",
    "content": "if (NOT WIN32)\n    return ()\nendif ()\n\nproject(openvpnserv)\n\nadd_executable(openvpnserv)\n\ninclude(CheckSymbolExists)\n\nset(MC_GEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/mc)\n\nfunction(add_common_options target)\n    target_include_directories(${target} PRIVATE\n        ${CMAKE_CURRENT_BINARY_DIR}/../../\n        ../../include/\n        ../openvpn/\n        ../compat/\n        ${MC_GEN_DIR}\n    )\n    target_compile_options(${target} PRIVATE\n        -D_UNICODE\n        -UNTDDI_VERSION\n        -D_WIN32_WINNT=_WIN32_WINNT_VISTA\n    )\n    target_link_libraries(${target} PRIVATE\n        advapi32.lib userenv.lib iphlpapi.lib fwpuclnt.lib rpcrt4.lib\n        shlwapi.lib netapi32.lib ws2_32.lib ntdll.lib ole32.lib pathcch.lib)\n    if (MINGW)\n        target_compile_options(${target} PRIVATE -municode)\n        target_link_options(${target} PRIVATE -municode)\n    endif ()\nendfunction()\n\nadd_common_options(openvpnserv)\ntarget_sources(openvpnserv PRIVATE\n    common.c\n    interactive.c\n    service.c service.h\n    validate.c validate.h\n    ../tapctl/basic.h\n    ../openvpn/wfp_block.c ../openvpn/wfp_block.h\n    openvpnserv_resources.rc\n    )\n\n# below we generate a DLL which contains an event source for event log messages from eventmsg.mc template\nfile(MAKE_DIRECTORY ${MC_GEN_DIR})\nset(MC_FILE ${CMAKE_CURRENT_SOURCE_DIR}/eventmsg.mc)\n\nfind_program(MC_COMPILER NAMES mc.exe x86_64-w64-mingw32-windmc i686-w64-mingw32-windmc windmc)\n\nif (NOT MC_COMPILER)\n    message(FATAL_ERROR \"No message compiler found.\")\nendif()\n\nadd_custom_command(\n    OUTPUT ${MC_GEN_DIR}/eventmsg.rc ${MC_GEN_DIR}/eventmsg.h\n    COMMAND ${MC_COMPILER} -U -h ${MC_GEN_DIR} -r ${MC_GEN_DIR} ${MC_FILE}\n    DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/eventmsg.mc\n    VERBATIM\n    )\n\n# generate rc file for DLL and header for the service binary\nadd_custom_target(msg_mc_gen ALL DEPENDS ${MC_GEN_DIR}/eventmsg.rc ${MC_GEN_DIR}/eventmsg.h)\n\nadd_library(openvpnservmsg SHARED ${MC_GEN_DIR}/eventmsg.rc)\nadd_dependencies(openvpnservmsg msg_mc_gen)\n\nif (MSVC)\n    set_target_properties(openvpnservmsg PROPERTIES LINK_FLAGS \"/NOENTRY\")\nelse()\n    set_target_properties(openvpnservmsg PROPERTIES LINKER_LANGUAGE C OUTPUT_NAME \"openvpnservmsg\")\n    target_link_options(openvpnservmsg PRIVATE\n        -Wl,--entry=0\n        -nostdlib\n        -nostartfiles\n    )\nendif()\n\nadd_dependencies(openvpnserv msg_mc_gen)\n\nif (BUILD_TESTING)\n    set(unit_tests\n        \"test_openvpnserv\"\n    )\n\n    foreach (test_name ${unit_tests})\n        add_test(${test_name} ${test_name})\n        add_executable(${test_name}\n            ../../tests/unit_tests/openvpnserv/${test_name}.c\n            ${MC_GEN_DIR}/eventmsg.h\n        )\n\n        add_common_options(${test_name})\n        target_link_libraries(${test_name} PUBLIC ${CMOCKA_LIBRARIES})\n        target_include_directories(${test_name} PRIVATE\n            .\n            ../../tests/unit_tests/openvpn\n        )\n    endforeach()\n\n    target_sources(test_openvpnserv PRIVATE\n        common.c\n        validate.c\n        ../openvpn/wfp_block.c ../openvpn/wfp_block.h\n    )\nendif ()\n"
  },
  {
    "path": "src/openvpnserv/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2006-2012 Alon Bar-Lev <alon.barlev@gmail.com>\n#\n\ninclude $(top_srcdir)/ltrc.inc\n\nMAINTAINERCLEANFILES = $(srcdir)/Makefile.in\n\nAM_CPPFLAGS = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/openvpn -I$(top_srcdir)/src/compat\n\nEXTRA_DIST = \\\n\tCMakeLists.txt\n\nif WIN32\nsbin_PROGRAMS = openvpnserv\nopenvpnserv_CFLAGS = \\\n\t-municode -D_UNICODE \\\n\t-UNTDDI_VERSION -U_WIN32_WINNT \\\n\t-D_WIN32_WINNT=_WIN32_WINNT_VISTA\nopenvpnserv_LDADD = \\\n\t-ladvapi32 -luserenv -liphlpapi -lfwpuclnt -lrpcrt4 \\\n\t-lshlwapi -lnetapi32 -lws2_32 -lntdll -lole32 -lpathcch\nnoinst_DATA = \\\n\tMSG00409.bin eventmsg.h eventmsg.rc openvpnservmsg.dll\nBUILT_SOURCES = \\\n\teventmsg.h\nendif\n\nopenvpnserv_SOURCES = \\\n        common.c \\\n\tinteractive.c \\\n\tservice.c service.h \\\n\tvalidate.c validate.h \\\n\t$(top_srcdir)/src/openvpn/wfp_block.c $(top_srcdir)/src/openvpn/wfp_block.h \\\n\topenvpnserv_resources.rc\n\nopenvpnservmsg.dll: eventmsg.o\n\t$(CC) -shared -Wl,--entry=0 -nostdlib -nostartfiles -o $@ $<\n\neventmsg.o: eventmsg.rc\n\neventmsg.h: eventmsg.mc\n\t$(WINDMC) -U $<\n"
  },
  {
    "path": "src/openvpnserv/common.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2011-2026 Heiko Hund <heiko.hund@sophos.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"service.h\"\n#include \"validate.h\"\n#include \"eventmsg.h\"\n\n#include <pathcch.h>\n\nLPCWSTR service_instance = L\"\";\nstatic wchar_t win_sys_path[MAX_PATH];\n\nstatic DWORD\nGetRegString(HKEY key, LPCWSTR value, LPWSTR data, DWORD size, LPCWSTR default_value)\n{\n    LONG status = RegGetValue(key, NULL, value, RRF_RT_REG_SZ, NULL, (LPBYTE)data, &size);\n\n    if (status == ERROR_FILE_NOT_FOUND && default_value)\n    {\n        size_t len = size / sizeof(data[0]);\n        if (swprintf(data, len, default_value))\n        {\n            status = ERROR_SUCCESS;\n        }\n    }\n\n    if (status != ERROR_SUCCESS)\n    {\n        SetLastError(status);\n        return MsgToEventLog(\n            M_SYSERR,\n            L\"Error querying registry value: HKLM\\\\SOFTWARE\\\\\" _L(PACKAGE_NAME) L\"%ls\\\\%ls\",\n            service_instance, value);\n    }\n\n    return ERROR_SUCCESS;\n}\n\n\n/**\n * Make sure that a dir path ends with a backslash.\n * If it doesn't, a \\ is added to the end of the path, if there's room in the buffer.\n *\n * @param dir       pointer to the wide dir path string buffer\n * @param size      maximum number of wide chars the dir path buffer\n * @return BOOL to indicate success or failure\n */\nstatic BOOL\nensure_trailing_backslash(PWSTR dir, size_t size)\n{\n    HRESULT res = PathCchAddBackslash(dir, size);\n    return (res == S_OK || res == S_FALSE) ? TRUE : FALSE;\n}\n\n\nDWORD\nGetOpenvpnSettings(settings_t *s)\n{\n    WCHAR reg_path[256];\n    WCHAR priority[64];\n    WCHAR append[2];\n    DWORD error;\n    HKEY key;\n    WCHAR install_path[MAX_PATH];\n    WCHAR default_value[MAX_PATH];\n\n    swprintf(reg_path, _countof(reg_path), L\"SOFTWARE\\\\\" _L(PACKAGE_NAME) L\"%ls\", service_instance);\n\n    LONG status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, reg_path, 0, KEY_READ, &key);\n    if (status != ERROR_SUCCESS)\n    {\n        SetLastError(status);\n        return MsgToEventLog(M_SYSERR, L\"Could not open Registry key HKLM\\\\%ls not found\",\n                             reg_path);\n    }\n\n    /* The default value of REG_KEY is the install path */\n    status = GetRegString(key, NULL, install_path, sizeof(install_path), NULL);\n    if (status != ERROR_SUCCESS)\n    {\n        error = status;\n        goto out;\n    }\n\n    swprintf(default_value, _countof(default_value), L\"%ls\\\\bin\\\\openvpn.exe\", install_path);\n    error = GetRegString(key, L\"exe_path\", s->exe_path, sizeof(s->exe_path), default_value);\n    if (error != ERROR_SUCCESS)\n    {\n        goto out;\n    }\n\n    swprintf(default_value, _countof(default_value), L\"%ls\\\\config\\\\\", install_path);\n    error = GetRegString(key, L\"config_dir\", s->config_dir, sizeof(s->config_dir), default_value);\n    if (error != ERROR_SUCCESS || !ensure_trailing_backslash(s->config_dir, _countof(s->config_dir)))\n    {\n        goto out;\n    }\n\n    swprintf(default_value, _countof(default_value), L\"%ls\\\\bin\\\\\", install_path);\n    error = GetRegString(key, L\"bin_dir\", s->bin_dir, sizeof(s->bin_dir), default_value);\n    if (error != ERROR_SUCCESS || !ensure_trailing_backslash(s->bin_dir, _countof(s->bin_dir)))\n    {\n        goto out;\n    }\n\n    error = GetRegString(key, L\"config_ext\", s->ext_string, sizeof(s->ext_string), L\".ovpn\");\n    if (error != ERROR_SUCCESS)\n    {\n        goto out;\n    }\n\n    swprintf(default_value, _countof(default_value), L\"%ls\\\\log\\\\\", install_path);\n    error = GetRegString(key, L\"log_dir\", s->log_dir, sizeof(s->log_dir), default_value);\n    if (error != ERROR_SUCCESS || !ensure_trailing_backslash(s->log_dir, _countof(s->log_dir)))\n    {\n        goto out;\n    }\n\n    error = GetRegString(key, L\"priority\", priority, sizeof(priority), L\"NORMAL_PRIORITY_CLASS\");\n    if (error != ERROR_SUCCESS)\n    {\n        goto out;\n    }\n\n    error = GetRegString(key, L\"log_append\", append, sizeof(append), L\"0\");\n    if (error != ERROR_SUCCESS)\n    {\n        goto out;\n    }\n\n    /* read if present, else use default */\n    error = GetRegString(key, L\"ovpn_admin_group\", s->ovpn_admin_group, sizeof(s->ovpn_admin_group),\n                         OVPN_ADMIN_GROUP);\n    if (error != ERROR_SUCCESS)\n    {\n        goto out;\n    }\n\n    error = GetRegString(key, L\"ovpn_service_user\", s->ovpn_service_user,\n                         sizeof(s->ovpn_service_user), OVPN_SERVICE_USER);\n    if (error != ERROR_SUCCESS)\n    {\n        goto out;\n    }\n\n    /* set process priority */\n    if (!_wcsicmp(priority, L\"IDLE_PRIORITY_CLASS\"))\n    {\n        s->priority = IDLE_PRIORITY_CLASS;\n    }\n    else if (!_wcsicmp(priority, L\"BELOW_NORMAL_PRIORITY_CLASS\"))\n    {\n        s->priority = BELOW_NORMAL_PRIORITY_CLASS;\n    }\n    else if (!_wcsicmp(priority, L\"NORMAL_PRIORITY_CLASS\"))\n    {\n        s->priority = NORMAL_PRIORITY_CLASS;\n    }\n    else if (!_wcsicmp(priority, L\"ABOVE_NORMAL_PRIORITY_CLASS\"))\n    {\n        s->priority = ABOVE_NORMAL_PRIORITY_CLASS;\n    }\n    else if (!_wcsicmp(priority, L\"HIGH_PRIORITY_CLASS\"))\n    {\n        s->priority = HIGH_PRIORITY_CLASS;\n    }\n    else\n    {\n        SetLastError(ERROR_INVALID_DATA);\n        error = MsgToEventLog(M_SYSERR, L\"Unknown priority name: %ls\", priority);\n        goto out;\n    }\n\n    /* set log file append/truncate flag */\n    if (append[0] == L'0')\n    {\n        s->append = FALSE;\n    }\n    else if (append[0] == L'1')\n    {\n        s->append = TRUE;\n    }\n    else\n    {\n        SetLastError(ERROR_INVALID_DATA);\n        error = MsgToEventLog(M_ERR, L\"Log file append flag (given as '%ls') must be '0' or '1'\",\n                              append);\n        goto out;\n    }\n\nout:\n    RegCloseKey(key);\n    return error;\n}\n\n\nLPCWSTR\nGetLastErrorText(void)\n{\n    DWORD error;\n    static WCHAR buf[256];\n    DWORD len;\n    LPWSTR tmp = NULL;\n\n    error = GetLastError();\n    len = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM\n                             | FORMAT_MESSAGE_IGNORE_INSERTS,\n                         NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&tmp, 0, NULL);\n\n    if (!len || !tmp)\n    {\n        swprintf(buf, _countof(buf), L\"Unknown error (0x%lx)\", error);\n        if (tmp)\n        {\n            LocalFree(tmp);\n        }\n        return buf;\n    }\n\n    /* trim trailing CR / LF / spaces safely */\n    while (len && (tmp[len - 1] == L'\\r' || tmp[len - 1] == L'\\n' || tmp[len - 1] == L' '))\n    {\n        tmp[--len] = L'\\0';\n    }\n\n    swprintf(buf, _countof(buf), L\"%ls (0x%lx)\", tmp, error);\n\n    LocalFree(tmp);\n    return buf;\n}\n\n\nDWORD\nMsgToEventLog(DWORD flags, LPCWSTR format, ...)\n{\n    HANDLE hEventSource;\n    WCHAR msg[2][256];\n    DWORD error = 0;\n    LPCWSTR err_msg = L\"\";\n    va_list arglist;\n\n    if (flags & MSG_FLAGS_SYS_CODE)\n    {\n        error = GetLastError();\n        err_msg = GetLastErrorText();\n    }\n\n    hEventSource = RegisterEventSource(NULL, APPNAME);\n    if (hEventSource != NULL)\n    {\n        swprintf(msg[0], _countof(msg[0]), L\"%ls%ls%ls: %ls\", APPNAME, service_instance,\n                 (flags & MSG_FLAGS_ERROR) ? L\" error\" : L\"\", err_msg);\n\n        va_start(arglist, format);\n        vswprintf(msg[1], _countof(msg[1]), format, arglist);\n        va_end(arglist);\n\n        const WCHAR *mesg[] = { msg[0], msg[1] };\n        ReportEvent(hEventSource,\n                    flags & MSG_FLAGS_ERROR ? EVENTLOG_ERROR_TYPE : EVENTLOG_INFORMATION_TYPE,\n                    0,\n                    EVT_TEXT_2,\n                    NULL,\n                    2,\n                    0,\n                    mesg,\n                    NULL);\n        DeregisterEventSource(hEventSource);\n    }\n\n    return error;\n}\n\nwchar_t *\nutf8to16_size(const char *utf8, int size)\n{\n    int n = MultiByteToWideChar(CP_UTF8, 0, utf8, size, NULL, 0);\n    if (n == 0)\n    {\n        return NULL;\n    }\n    wchar_t *utf16 = malloc(n * sizeof(wchar_t));\n    if (!utf16)\n    {\n        return NULL;\n    }\n    MultiByteToWideChar(CP_UTF8, 0, utf8, size, utf16, n);\n    return utf16;\n}\n\nconst wchar_t *\nget_win_sys_path(void)\n{\n    const wchar_t *default_sys_path = L\"C:\\\\Windows\\\\system32\";\n\n    if (!GetSystemDirectoryW(win_sys_path, _countof(win_sys_path)))\n    {\n        wcscpy_s(win_sys_path, _countof(win_sys_path), default_sys_path);\n        win_sys_path[_countof(win_sys_path) - 1] = L'\\0';\n    }\n\n    return win_sys_path;\n}\n"
  },
  {
    "path": "src/openvpnserv/eventmsg.mc",
    "content": "MessageIdTypedef=DWORD\n\nSeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS\n    Informational=0x1:STATUS_SEVERITY_INFORMATIONAL\n    Warning=0x2:STATUS_SEVERITY_WARNING\n    Error=0x3:STATUS_SEVERITY_ERROR\n    )\n\nFacilityNames=(System=0x0:FACILITY_SYSTEM\n    Runtime=0x2:FACILITY_RUNTIME\n    Stubs=0x3:FACILITY_STUBS\n    Io=0x4:FACILITY_IO_ERROR_CODE\n)\n\nLanguageNames=(English=0x409:MSG00409)\n\nMessageId=0x1\nSeverity=Error\nFacility=Runtime\nSymbolicName=EVT_TEXT_2\nLanguage=English\n%1%n%2\n.\n"
  },
  {
    "path": "src/openvpnserv/interactive.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2012-2026 Heiko Hund <heiko.hund@sophos.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n\n#include \"service.h\"\n\n#include <ws2tcpip.h>\n#include <iphlpapi.h>\n#include <userenv.h>\n#include <accctrl.h>\n#include <aclapi.h>\n#include <stdio.h>\n#include <sddl.h>\n#include <shellapi.h>\n#include <mstcpip.h>\n#include <inttypes.h>\n\n#include <versionhelpers.h>\n\n#include \"openvpn-msg.h\"\n#include \"validate.h\"\n#include \"wfp_block.h\"\n\n#define IO_TIMEOUT 2000 /*ms*/\n\n#define ERROR_OPENVPN_STARTUP 0x20000000\n#define ERROR_STARTUP_DATA    0x20000001\n#define ERROR_MESSAGE_DATA    0x20000002\n#define ERROR_MESSAGE_TYPE    0x20000003\n\nstatic SERVICE_STATUS_HANDLE service;\nstatic SERVICE_STATUS status = { .dwServiceType = SERVICE_WIN32_SHARE_PROCESS };\nstatic HANDLE exit_event = NULL;\nstatic settings_t settings;\nstatic HANDLE rdns_semaphore = NULL;\n#define RDNS_TIMEOUT 600 /* seconds to wait for the semaphore */\n\n#define TUN_IOCTL_REGISTER_RINGS \\\n    CTL_CODE(51820U, 0x970U, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)\n\nopenvpn_service_t interactive_service = { interactive, _L(PACKAGE_NAME) L\"ServiceInteractive\",\n                                          _L(PACKAGE_NAME) L\" Interactive Service\",\n                                          SERVICE_DEPENDENCIES, SERVICE_AUTO_START };\n\n\ntypedef struct\n{\n    WCHAR *directory;\n    WCHAR *options;\n    WCHAR *std_input;\n} STARTUP_DATA;\n\n\n/* Datatype for linked lists */\ntypedef struct _list_item\n{\n    struct _list_item *next;\n    LPVOID data;\n} list_item_t;\n\n\n/* Datatypes for undo information */\ntypedef enum\n{\n    address,\n    route,\n    wfp_block,\n    undo_dns4,\n    undo_dns6,\n    undo_nrpt,\n    undo_domains,\n    undo_wins,\n    _undo_type_max\n} undo_type_t;\ntypedef list_item_t *undo_lists_t[_undo_type_max];\n\ntypedef struct\n{\n    HANDLE engine;\n    DWORD index;\n    int metric_v4;\n    int metric_v6;\n} wfp_block_data_t;\n\ntypedef struct\n{\n    char itf_name[256];\n    PWSTR domains;\n} dns_domains_undo_data_t;\n\ntypedef union\n{\n    message_header_t header;\n    address_message_t address;\n    route_message_t route;\n    flush_neighbors_message_t flush_neighbors;\n    wfp_block_message_t wfp_block;\n    dns_cfg_message_t dns;\n    nrpt_dns_cfg_message_t nrpt_dns;\n    enable_dhcp_message_t dhcp;\n    set_mtu_message_t mtu;\n    wins_cfg_message_t wins;\n    create_adapter_message_t create_adapter;\n} pipe_message_t;\n\ntypedef struct\n{\n    CHAR addresses[NRPT_ADDR_NUM * NRPT_ADDR_SIZE];\n    WCHAR domains[512]; /* MULTI_SZ string */\n    DWORD domains_size; /* bytes in domains */\n} nrpt_exclude_data_t;\n\n\nstatic DWORD\nAddListItem(list_item_t **pfirst, LPVOID data)\n{\n    list_item_t *new_item = malloc(sizeof(list_item_t));\n    if (new_item == NULL)\n    {\n        return ERROR_OUTOFMEMORY;\n    }\n\n    new_item->next = *pfirst;\n    new_item->data = data;\n\n    *pfirst = new_item;\n    return NO_ERROR;\n}\n\ntypedef BOOL (*match_fn_t)(LPVOID item, LPVOID ctx);\n\nstatic LPVOID\nRemoveListItem(list_item_t **pfirst, match_fn_t match, LPVOID ctx)\n{\n    LPVOID data = NULL;\n    list_item_t **pnext;\n\n    for (pnext = pfirst; *pnext; pnext = &(*pnext)->next)\n    {\n        list_item_t *item = *pnext;\n        if (!match(item->data, ctx))\n        {\n            continue;\n        }\n\n        /* Found item, remove from the list and free memory */\n        *pnext = item->next;\n        data = item->data;\n        free(item);\n        break;\n    }\n    return data;\n}\n\n\nstatic HANDLE\nCloseHandleEx(LPHANDLE handle)\n{\n    if (handle && *handle && *handle != INVALID_HANDLE_VALUE)\n    {\n        CloseHandle(*handle);\n        *handle = INVALID_HANDLE_VALUE;\n    }\n    return INVALID_HANDLE_VALUE;\n}\n\nstatic HANDLE\nInitOverlapped(LPOVERLAPPED overlapped)\n{\n    ZeroMemory(overlapped, sizeof(OVERLAPPED));\n    overlapped->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);\n    return overlapped->hEvent;\n}\n\nstatic BOOL\nResetOverlapped(LPOVERLAPPED overlapped)\n{\n    HANDLE io_event = overlapped->hEvent;\n    if (!ResetEvent(io_event))\n    {\n        return FALSE;\n    }\n    ZeroMemory(overlapped, sizeof(OVERLAPPED));\n    overlapped->hEvent = io_event;\n    return TRUE;\n}\n\n\ntypedef enum\n{\n    peek,\n    peek_timed,\n    read,\n    write\n} async_op_t;\n\nstatic DWORD\nAsyncPipeOp(async_op_t op, HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events)\n{\n    DWORD i;\n    BOOL success;\n    HANDLE io_event;\n    DWORD res, bytes = 0;\n    OVERLAPPED overlapped;\n    LPHANDLE handles = NULL;\n\n    io_event = InitOverlapped(&overlapped);\n    if (!io_event)\n    {\n        goto out;\n    }\n\n    handles = malloc((count + 1) * sizeof(HANDLE));\n    if (!handles)\n    {\n        goto out;\n    }\n\n    if (op == write)\n    {\n        success = WriteFile(pipe, buffer, size, NULL, &overlapped);\n    }\n    else\n    {\n        success = ReadFile(pipe, buffer, size, NULL, &overlapped);\n    }\n    if (!success && GetLastError() != ERROR_IO_PENDING && GetLastError() != ERROR_MORE_DATA)\n    {\n        goto out;\n    }\n\n    handles[0] = io_event;\n    for (i = 0; i < count; i++)\n    {\n        handles[i + 1] = events[i];\n    }\n\n    res = WaitForMultipleObjects(count + 1, handles, FALSE, op == peek ? INFINITE : IO_TIMEOUT);\n    if (res != WAIT_OBJECT_0)\n    {\n        CancelIo(pipe);\n        goto out;\n    }\n\n    if (op == peek || op == peek_timed)\n    {\n        PeekNamedPipe(pipe, NULL, 0, NULL, &bytes, NULL);\n    }\n    else\n    {\n        GetOverlappedResult(pipe, &overlapped, &bytes, TRUE);\n    }\n\nout:\n    CloseHandleEx(&io_event);\n    free(handles);\n    return bytes;\n}\n\nstatic DWORD\nPeekNamedPipeAsync(HANDLE pipe, DWORD count, LPHANDLE events)\n{\n    return AsyncPipeOp(peek, pipe, NULL, 0, count, events);\n}\n\nstatic DWORD\nPeekNamedPipeAsyncTimed(HANDLE pipe, DWORD count, LPHANDLE events)\n{\n    return AsyncPipeOp(peek_timed, pipe, NULL, 0, count, events);\n}\n\nstatic DWORD\nReadPipeAsync(HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events)\n{\n    return AsyncPipeOp(read, pipe, buffer, size, count, events);\n}\n\nstatic DWORD\nWritePipeAsync(HANDLE pipe, LPVOID data, DWORD size, DWORD count, LPHANDLE events)\n{\n    return AsyncPipeOp(write, pipe, data, size, count, events);\n}\n\nstatic VOID\nReturnProcessId(HANDLE pipe, DWORD pid, DWORD count, LPHANDLE events)\n{\n    const WCHAR msg[] = L\"Process ID\";\n    WCHAR buf[22 + _countof(msg)]; /* 10 chars each for error and PID and 2 for line breaks */\n\n    /*\n     * Same format as error messages (3 line string) with error = 0 in\n     * 0x%08x format, PID on line 2 and a description \"Process ID\" on line 3\n     */\n    swprintf(buf, _countof(buf), L\"0x%08x\\n0x%08x\\n%ls\", 0, pid, msg);\n\n    WritePipeAsync(pipe, buf, (DWORD)(wcslen(buf) * 2), count, events);\n}\n\nstatic VOID\nReturnError(HANDLE pipe, DWORD error, LPCWSTR func, DWORD count, LPHANDLE events)\n{\n    DWORD result_len;\n    LPWSTR result = L\"0xffffffff\\nFormatMessage failed\\nCould not return result\";\n    DWORD_PTR args[] = { (DWORD_PTR)error, (DWORD_PTR)func, (DWORD_PTR) \"\" };\n\n    if (error != ERROR_OPENVPN_STARTUP)\n    {\n        FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER\n                           | FORMAT_MESSAGE_IGNORE_INSERTS,\n                       0, error, 0, (LPWSTR)&args[2], 0, NULL);\n    }\n\n    result_len = FormatMessageW(\n        FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY,\n        L\"0x%1!08x!\\n%2!s!\\n%3!s!\", 0, 0, (LPWSTR)&result, 0, (va_list *)args);\n\n    WritePipeAsync(pipe, result, (DWORD)(wcslen(result) * 2), count, events);\n    MsgToEventLog(MSG_FLAGS_ERROR, result);\n\n    if (error != ERROR_OPENVPN_STARTUP)\n    {\n        LocalFree((LPVOID)args[2]);\n    }\n    if (result_len)\n    {\n        LocalFree(result);\n    }\n}\n\n\nstatic VOID\nReturnLastError(HANDLE pipe, LPCWSTR func)\n{\n    ReturnError(pipe, GetLastError(), func, 1, &exit_event);\n}\n\n/*\n * Validate options against a white list. Also check the config_file is\n * inside the config_dir. The white list is defined in validate.c\n * Returns true on success, false on error with reason set in errmsg.\n */\nstatic BOOL\nValidateOptions(HANDLE pipe, const WCHAR *workdir, const WCHAR *options, WCHAR *errmsg,\n                DWORD capacity)\n{\n    WCHAR **argv;\n    int argc;\n    BOOL ret = FALSE;\n    int i;\n    const WCHAR *msg1 = L\"You have specified a config file location (%ls relative to %ls)\"\n                        L\" that requires admin approval. This error may be avoided\"\n                        L\" by adding your account to the \\\"%ls\\\" group\";\n\n    const WCHAR *msg2 = L\"You have specified an option (%ls) that may be used\"\n                        L\" only with admin approval. This error may be avoided\"\n                        L\" by adding your account to the \\\"%ls\\\" group\";\n\n    argv = CommandLineToArgvW(options, &argc);\n\n    if (!argv)\n    {\n        swprintf(errmsg, capacity,\n                 L\"Cannot validate options: CommandLineToArgvW failed with error = 0x%08x\",\n                 GetLastError());\n        goto out;\n    }\n\n    /* Note: argv[0] is the first option */\n    if (argc < 1) /* no options */\n    {\n        ret = TRUE;\n        goto out;\n    }\n\n    /*\n     * If only one argument, it is the config file\n     */\n    if (argc == 1)\n    {\n        WCHAR *argv_tmp[2] = { L\"--config\", argv[0] };\n\n        if (!CheckOption(workdir, 2, argv_tmp, &settings))\n        {\n            swprintf(errmsg, capacity, msg1, argv[0], workdir, settings.ovpn_admin_group);\n        }\n        goto out;\n    }\n\n    for (i = 0; i < argc; ++i)\n    {\n        if (!IsOption(argv[i]))\n        {\n            continue;\n        }\n\n        if (!CheckOption(workdir, argc - i, &argv[i], &settings))\n        {\n            if (wcscmp(L\"--config\", argv[i]) == 0 && argc - i > 1)\n            {\n                swprintf(errmsg, capacity, msg1, argv[i + 1], workdir, settings.ovpn_admin_group);\n            }\n            else\n            {\n                swprintf(errmsg, capacity, msg2, argv[i], settings.ovpn_admin_group);\n            }\n            goto out;\n        }\n    }\n\n    /* all options passed */\n    ret = TRUE;\n\nout:\n    if (argv)\n    {\n        LocalFree(argv);\n    }\n    return ret;\n}\n\nstatic BOOL\nGetStartupData(HANDLE pipe, STARTUP_DATA *sud)\n{\n    size_t size, len;\n    WCHAR *data = NULL;\n    DWORD bytes, read;\n\n    bytes = PeekNamedPipeAsyncTimed(pipe, 1, &exit_event);\n    if (bytes == 0)\n    {\n        MsgToEventLog(M_ERR, L\"Timeout waiting for startup data\");\n        ReturnError(pipe, ERROR_STARTUP_DATA, L\"GetStartupData (timeout)\", 1, &exit_event);\n        goto err;\n    }\n\n    size = bytes / sizeof(*data);\n    if ((size == 0) || (size > 4096)) /* our startup data is 1024 wchars at the moment */\n    {\n        MsgToEventLog(M_SYSERR, L\"malformed startup data: %lu bytes received\", size);\n        ReturnError(pipe, ERROR_STARTUP_DATA, L\"GetStartupData\", 1, &exit_event);\n        goto err;\n    }\n\n    data = malloc(bytes);\n    if (data == NULL)\n    {\n        MsgToEventLog(M_SYSERR, L\"malloc failed\");\n        ReturnLastError(pipe, L\"malloc\");\n        goto err;\n    }\n\n    read = ReadPipeAsync(pipe, data, bytes, 1, &exit_event);\n    if (bytes != read)\n    {\n        MsgToEventLog(M_SYSERR, L\"ReadPipeAsync failed\");\n        ReturnLastError(pipe, L\"ReadPipeAsync\");\n        goto err;\n    }\n\n    if (data[size - 1] != 0)\n    {\n        MsgToEventLog(M_ERR, L\"Startup data is not NULL terminated\");\n        ReturnError(pipe, ERROR_STARTUP_DATA, L\"GetStartupData\", 1, &exit_event);\n        goto err;\n    }\n\n    sud->directory = data;\n    len = wcslen(sud->directory) + 1;\n    size -= len;\n    if (size <= 0)\n    {\n        MsgToEventLog(M_ERR, L\"Startup data ends at working directory\");\n        ReturnError(pipe, ERROR_STARTUP_DATA, L\"GetStartupData\", 1, &exit_event);\n        goto err;\n    }\n\n    sud->options = sud->directory + len;\n    len = wcslen(sud->options) + 1;\n    size -= len;\n    if (size <= 0)\n    {\n        MsgToEventLog(M_ERR, L\"Startup data ends at command line options\");\n        ReturnError(pipe, ERROR_STARTUP_DATA, L\"GetStartupData\", 1, &exit_event);\n        goto err;\n    }\n\n    sud->std_input = sud->options + len;\n    return TRUE;\n\nerr:\n    sud->directory = NULL; /* caller must not free() */\n    free(data);\n    return FALSE;\n}\n\n\nstatic VOID\nFreeStartupData(STARTUP_DATA *sud)\n{\n    free(sud->directory);\n}\n\n\nstatic SOCKADDR_INET\nsockaddr_inet(short family, inet_address_t *addr)\n{\n    SOCKADDR_INET sa_inet;\n    ZeroMemory(&sa_inet, sizeof(sa_inet));\n    sa_inet.si_family = family;\n    if (family == AF_INET)\n    {\n        sa_inet.Ipv4.sin_addr = addr->ipv4;\n    }\n    else if (family == AF_INET6)\n    {\n        sa_inet.Ipv6.sin6_addr = addr->ipv6;\n    }\n    return sa_inet;\n}\n\nstatic DWORD\nInterfaceLuid(const char *iface_name, PNET_LUID luid)\n{\n    NETIO_STATUS status;\n    LPWSTR wide_name = utf8to16(iface_name);\n\n    if (wide_name)\n    {\n        status = ConvertInterfaceAliasToLuid(wide_name, luid);\n        free(wide_name);\n    }\n    else\n    {\n        status = ERROR_OUTOFMEMORY;\n    }\n    return status;\n}\n\nstatic BOOL\nCmpAddress(LPVOID item, LPVOID address)\n{\n    return memcmp(item, address, sizeof(MIB_UNICASTIPADDRESS_ROW)) == 0 ? TRUE : FALSE;\n}\n\nstatic DWORD\nDeleteAddress(PMIB_UNICASTIPADDRESS_ROW addr_row)\n{\n    return DeleteUnicastIpAddressEntry(addr_row);\n}\n\nstatic DWORD\nHandleAddressMessage(address_message_t *msg, undo_lists_t *lists)\n{\n    DWORD err;\n    PMIB_UNICASTIPADDRESS_ROW addr_row;\n    BOOL add = msg->header.type == msg_add_address;\n\n    addr_row = malloc(sizeof(*addr_row));\n    if (addr_row == NULL)\n    {\n        return ERROR_OUTOFMEMORY;\n    }\n\n    InitializeUnicastIpAddressEntry(addr_row);\n    addr_row->Address = sockaddr_inet(msg->family, &msg->address);\n    addr_row->OnLinkPrefixLength = (UINT8)msg->prefix_len;\n\n    if (msg->iface.index != TUN_ADAPTER_INDEX_INVALID)\n    {\n        addr_row->InterfaceIndex = msg->iface.index;\n    }\n    else\n    {\n        NET_LUID luid;\n        err = InterfaceLuid(msg->iface.name, &luid);\n        if (err)\n        {\n            goto out;\n        }\n        addr_row->InterfaceLuid = luid;\n    }\n\n    if (add)\n    {\n        err = CreateUnicastIpAddressEntry(addr_row);\n        if (err)\n        {\n            goto out;\n        }\n\n        err = AddListItem(&(*lists)[address], addr_row);\n        if (err)\n        {\n            DeleteAddress(addr_row);\n        }\n    }\n    else\n    {\n        err = DeleteAddress(addr_row);\n        if (err)\n        {\n            goto out;\n        }\n\n        free(RemoveListItem(&(*lists)[address], CmpAddress, addr_row));\n    }\n\nout:\n    if (!add || err)\n    {\n        free(addr_row);\n    }\n\n    return err;\n}\n\nstatic BOOL\nCmpRoute(LPVOID item, LPVOID route)\n{\n    return memcmp(item, route, sizeof(MIB_IPFORWARD_ROW2)) == 0 ? TRUE : FALSE;\n}\n\nstatic DWORD\nDeleteRoute(PMIB_IPFORWARD_ROW2 fwd_row)\n{\n    return DeleteIpForwardEntry2(fwd_row);\n}\n\nstatic DWORD\nHandleRouteMessage(route_message_t *msg, undo_lists_t *lists)\n{\n    DWORD err;\n    PMIB_IPFORWARD_ROW2 fwd_row;\n    BOOL add = msg->header.type == msg_add_route;\n\n    fwd_row = malloc(sizeof(*fwd_row));\n    if (fwd_row == NULL)\n    {\n        return ERROR_OUTOFMEMORY;\n    }\n\n    ZeroMemory(fwd_row, sizeof(*fwd_row));\n    fwd_row->ValidLifetime = 0xffffffff;\n    fwd_row->PreferredLifetime = 0xffffffff;\n    fwd_row->Protocol = MIB_IPPROTO_NETMGMT;\n    fwd_row->Metric = msg->metric;\n    fwd_row->DestinationPrefix.Prefix = sockaddr_inet(msg->family, &msg->prefix);\n    fwd_row->DestinationPrefix.PrefixLength = (UINT8)msg->prefix_len;\n    fwd_row->NextHop = sockaddr_inet(msg->family, &msg->gateway);\n\n    if (msg->iface.index != TUN_ADAPTER_INDEX_INVALID)\n    {\n        fwd_row->InterfaceIndex = msg->iface.index;\n    }\n    else if (strlen(msg->iface.name))\n    {\n        NET_LUID luid;\n        err = InterfaceLuid(msg->iface.name, &luid);\n        if (err)\n        {\n            goto out;\n        }\n        fwd_row->InterfaceLuid = luid;\n    }\n\n    if (add)\n    {\n        err = CreateIpForwardEntry2(fwd_row);\n        if (err)\n        {\n            goto out;\n        }\n\n        err = AddListItem(&(*lists)[route], fwd_row);\n        if (err)\n        {\n            DeleteRoute(fwd_row);\n        }\n    }\n    else\n    {\n        err = DeleteRoute(fwd_row);\n        if (err)\n        {\n            goto out;\n        }\n\n        free(RemoveListItem(&(*lists)[route], CmpRoute, fwd_row));\n    }\n\nout:\n    if (!add || err)\n    {\n        free(fwd_row);\n    }\n\n    return err;\n}\n\n\nstatic DWORD\nHandleFlushNeighborsMessage(flush_neighbors_message_t *msg)\n{\n    if (msg->family == AF_INET)\n    {\n        return FlushIpNetTable(msg->iface.index);\n    }\n\n    return FlushIpNetTable2(msg->family, msg->iface.index);\n}\n\nstatic void\nBlockDNSErrHandler(DWORD err, const char *msg)\n{\n    WCHAR buf[256];\n    LPCWSTR err_str;\n\n    if (!err)\n    {\n        return;\n    }\n\n    err_str = L\"Unknown Win32 Error\";\n\n    if (FormatMessageW(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,\n                       NULL, err, 0, buf, _countof(buf), NULL))\n    {\n        err_str = buf;\n    }\n\n    MsgToEventLog(M_ERR, L\"%hs (status = %lu): %ls\", msg, err, err_str);\n}\n\n/* Use an always-true match_fn to get the head of the list */\nstatic BOOL\nCmpAny(LPVOID item, LPVOID any)\n{\n    return TRUE;\n}\n\nstatic DWORD\nDeleteWfpBlock(undo_lists_t *lists)\n{\n    DWORD err = 0;\n    wfp_block_data_t *block_data = RemoveListItem(&(*lists)[wfp_block], CmpAny, NULL);\n\n    if (block_data)\n    {\n        err = delete_wfp_block_filters(block_data->engine);\n        if (block_data->metric_v4 >= 0)\n        {\n            set_interface_metric(block_data->index, AF_INET, block_data->metric_v4);\n        }\n        if (block_data->metric_v6 >= 0)\n        {\n            set_interface_metric(block_data->index, AF_INET6, block_data->metric_v6);\n        }\n        free(block_data);\n    }\n    else\n    {\n        MsgToEventLog(M_ERR, L\"No previous block filters to delete\");\n    }\n\n    return err;\n}\n\nstatic DWORD\nAddWfpBlock(const wfp_block_message_t *msg, undo_lists_t *lists)\n{\n    DWORD err = 0;\n    wfp_block_data_t *block_data = NULL;\n    HANDLE engine = NULL;\n    LPCWSTR exe_path;\n    BOOL dns_only;\n\n    exe_path = settings.exe_path;\n    dns_only = (msg->flags == wfp_block_dns);\n\n    err = add_wfp_block_filters(&engine, msg->iface.index, exe_path, BlockDNSErrHandler, dns_only);\n    if (!err)\n    {\n        block_data = malloc(sizeof(wfp_block_data_t));\n        if (!block_data)\n        {\n            err = ERROR_OUTOFMEMORY;\n            goto out;\n        }\n        block_data->engine = engine;\n        block_data->index = msg->iface.index;\n        int is_auto = 0;\n        block_data->metric_v4 = get_interface_metric(msg->iface.index, AF_INET, &is_auto);\n        if (is_auto)\n        {\n            block_data->metric_v4 = 0;\n        }\n        block_data->metric_v6 = get_interface_metric(msg->iface.index, AF_INET6, &is_auto);\n        if (is_auto)\n        {\n            block_data->metric_v6 = 0;\n        }\n\n        err = AddListItem(&(*lists)[wfp_block], block_data);\n        if (!err)\n        {\n            err = set_interface_metric(msg->iface.index, AF_INET, WFP_BLOCK_IFACE_METRIC);\n            if (!err)\n            {\n                /* for IPv6, we intentionally ignore errors, because\n                 * otherwise block-dns activation will fail if a user or\n                 * admin has disabled IPv6 on the tun/tap/dco interface\n                 * (if OpenVPN wants IPv6 ifconfig, we'll fail there)\n                 */\n                set_interface_metric(msg->iface.index, AF_INET6, WFP_BLOCK_IFACE_METRIC);\n            }\n            if (err)\n            {\n                /* delete the filters, remove undo item and free interface data */\n                DeleteWfpBlock(lists);\n                engine = NULL;\n            }\n        }\n    }\n\nout:\n    if (err && engine)\n    {\n        delete_wfp_block_filters(engine);\n        free(block_data);\n    }\n\n    return err;\n}\n\nstatic DWORD\nHandleWfpBlockMessage(const wfp_block_message_t *msg, undo_lists_t *lists)\n{\n    if (msg->header.type == msg_add_wfp_block)\n    {\n        return AddWfpBlock(msg, lists);\n    }\n    else\n    {\n        return DeleteWfpBlock(lists);\n    }\n}\n\n/*\n * Execute a command and return its exit code. If timeout > 0, terminate\n * the process if still running after timeout milliseconds. In that case\n * the return value is the windows error code WAIT_TIMEOUT = 0x102\n */\nstatic DWORD\nExecCommand(const WCHAR *argv0, const WCHAR *cmdline, DWORD timeout)\n{\n    DWORD exit_code;\n    STARTUPINFOW si;\n    PROCESS_INFORMATION pi;\n    DWORD proc_flags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;\n    WCHAR *cmdline_dup = NULL;\n\n    ZeroMemory(&si, sizeof(si));\n    ZeroMemory(&pi, sizeof(pi));\n\n    si.cb = sizeof(si);\n\n    /* CreateProcess needs a modifiable cmdline: make a copy */\n    cmdline_dup = _wcsdup(cmdline);\n    if (cmdline_dup\n        && CreateProcessW(argv0, cmdline_dup, NULL, NULL, FALSE, proc_flags, NULL, NULL, &si, &pi))\n    {\n        WaitForSingleObject(pi.hProcess, timeout ? timeout : INFINITE);\n        if (!GetExitCodeProcess(pi.hProcess, &exit_code))\n        {\n            MsgToEventLog(M_SYSERR, L\"ExecCommand: Error getting exit_code:\");\n            exit_code = GetLastError();\n        }\n        else if (exit_code == STILL_ACTIVE)\n        {\n            exit_code = WAIT_TIMEOUT; /* Windows error code 0x102 */\n\n            /* kill without impunity */\n            TerminateProcess(pi.hProcess, exit_code);\n            MsgToEventLog(M_ERR, L\"ExecCommand: \\\"%ls %ls\\\" killed after timeout\", argv0, cmdline);\n        }\n        else if (exit_code)\n        {\n            MsgToEventLog(M_ERR, L\"ExecCommand: \\\"%ls %ls\\\" exited with status = %lu\", argv0,\n                          cmdline, exit_code);\n        }\n        else\n        {\n            MsgToEventLog(M_INFO, L\"ExecCommand: \\\"%ls %ls\\\" completed\", argv0, cmdline);\n        }\n\n        CloseHandle(pi.hProcess);\n        CloseHandle(pi.hThread);\n    }\n    else\n    {\n        exit_code = GetLastError();\n        MsgToEventLog(M_SYSERR, L\"ExecCommand: could not run \\\"%ls %ls\\\" :\", argv0, cmdline);\n    }\n\n    free(cmdline_dup);\n    return exit_code;\n}\n\n/*\n * Entry point for register-dns thread.\n */\nstatic DWORD WINAPI\nRegisterDNS(LPVOID unused)\n{\n    DWORD err;\n    size_t i;\n    DWORD timeout = RDNS_TIMEOUT * 1000; /* in milliseconds */\n\n    /* path of ipconfig command */\n    WCHAR ipcfg[MAX_PATH];\n\n    struct\n    {\n        WCHAR *argv0;\n        WCHAR *cmdline;\n        DWORD timeout;\n    } cmds[] = {\n        { ipcfg, L\"ipconfig /flushdns\", timeout },\n        { ipcfg, L\"ipconfig /registerdns\", timeout },\n    };\n\n    HANDLE wait_handles[2] = { rdns_semaphore, exit_event };\n\n    swprintf(ipcfg, MAX_PATH, L\"%ls\\\\%ls\", get_win_sys_path(), L\"ipconfig.exe\");\n\n    if (WaitForMultipleObjects(2, wait_handles, FALSE, timeout) == WAIT_OBJECT_0)\n    {\n        /* Semaphore locked */\n        for (i = 0; i < _countof(cmds); ++i)\n        {\n            ExecCommand(cmds[i].argv0, cmds[i].cmdline, cmds[i].timeout);\n        }\n        err = 0;\n        if (!ReleaseSemaphore(rdns_semaphore, 1, NULL))\n        {\n            err =\n                MsgToEventLog(M_SYSERR, L\"RegisterDNS: Failed to release regsiter-dns semaphore:\");\n        }\n    }\n    else\n    {\n        MsgToEventLog(M_ERR, L\"RegisterDNS: Failed to lock register-dns semaphore\");\n        err = ERROR_SEM_TIMEOUT; /* Windows error code 0x79 */\n    }\n    return err;\n}\n\nstatic DWORD\nHandleRegisterDNSMessage(void)\n{\n    DWORD err;\n    HANDLE thread = NULL;\n\n    /* Delegate this job to a sub-thread */\n    thread = CreateThread(NULL, 0, RegisterDNS, NULL, 0, NULL);\n\n    /*\n     * We don't add these thread handles to the undo list -- the thread and\n     * processes it spawns are all supposed to terminate or timeout by themselves.\n     */\n    if (thread)\n    {\n        err = 0;\n        CloseHandle(thread);\n    }\n    else\n    {\n        err = GetLastError();\n    }\n\n    return err;\n}\n\n/**\n * Run the command: netsh interface ip $action wins $if_index [static] $addr\n * @param  action      \"delete\", \"add\" or \"set\"\n * @param  if_index    index of the interface to modify WINS for\n * @param  addr        IPv4 address as a string\n *\n * If addr is null and action = \"delete\" all addresses are deleted.\n * if action = \"set\" then \"static\" is added before $addr\n */\nstatic DWORD\nnetsh_wins_cmd(const wchar_t *action, DWORD if_index, const wchar_t *addr)\n{\n    DWORD err = 0;\n    int timeout = 30000; /* in msec */\n    wchar_t argv0[MAX_PATH];\n    wchar_t *cmdline = NULL;\n    const wchar_t *addr_static = (wcscmp(action, L\"set\") == 0) ? L\"static\" : L\"\";\n\n    if (!addr)\n    {\n        if (wcscmp(action, L\"delete\") == 0)\n        {\n            addr = L\"all\";\n        }\n        else /* nothing to do -- return success*/\n        {\n            goto out;\n        }\n    }\n\n    /* Path of netsh */\n    swprintf(argv0, _countof(argv0), L\"%ls\\\\%ls\", get_win_sys_path(), L\"netsh.exe\");\n\n    /* cmd template:\n     * netsh interface ip $action wins $if_name $static $addr\n     */\n    const wchar_t *fmt = L\"netsh interface ip %ls wins %lu %ls %ls\";\n\n    /* max cmdline length in wchars -- include room for worst case and some */\n    size_t ncmdline = wcslen(fmt) + 11 /*if_index*/ + wcslen(action) + wcslen(addr)\n                      + wcslen(addr_static) + 32 + 1;\n    cmdline = malloc(ncmdline * sizeof(wchar_t));\n    if (!cmdline)\n    {\n        err = ERROR_OUTOFMEMORY;\n        goto out;\n    }\n\n    swprintf(cmdline, ncmdline, fmt, action, if_index, addr_static, addr);\n\n    err = ExecCommand(argv0, cmdline, timeout);\n\nout:\n    free(cmdline);\n    return err;\n}\n\n/**\n * Signal the DNS resolver (and others potentially) to reload the\n * group policy (DNS) settings on 32 bit Windows systems\n *\n * @return BOOL to indicate if the reload was initiated\n */\nstatic BOOL\nApplyGpolSettings32(void)\n{\n    typedef NTSTATUS(__stdcall * publish_fn_t)(DWORD StateNameLo, DWORD StateNameHi, DWORD TypeId,\n                                               DWORD Buffer, DWORD Length, DWORD ExplicitScope);\n    publish_fn_t RtlPublishWnfStateData;\n    const DWORD WNF_GPOL_SYSTEM_CHANGES_HI = 0x0D891E2A;\n    const DWORD WNF_GPOL_SYSTEM_CHANGES_LO = 0xA3BC0875;\n\n    HMODULE ntdll = LoadLibraryA(\"ntdll.dll\");\n    if (ntdll == NULL)\n    {\n        return FALSE;\n    }\n\n    RtlPublishWnfStateData = (publish_fn_t)GetProcAddress(ntdll, \"RtlPublishWnfStateData\");\n    if (RtlPublishWnfStateData == NULL)\n    {\n        return FALSE;\n    }\n\n    if (RtlPublishWnfStateData(WNF_GPOL_SYSTEM_CHANGES_LO, WNF_GPOL_SYSTEM_CHANGES_HI, 0, 0, 0, 0)\n        != ERROR_SUCCESS)\n    {\n        return FALSE;\n    }\n\n    return TRUE;\n}\n\n/**\n * Signal the DNS resolver (and others potentially) to reload the\n * group policy (DNS) settings on 64 bit Windows systems\n *\n * @return BOOL to indicate if the reload was initiated\n */\nstatic BOOL\nApplyGpolSettings64(void)\n{\n    typedef NTSTATUS (*publish_fn_t)(INT64 StateName, INT64 TypeId, INT64 Buffer,\n                                     unsigned int Length, INT64 ExplicitScope);\n    publish_fn_t RtlPublishWnfStateData;\n    const INT64 WNF_GPOL_SYSTEM_CHANGES = 0x0D891E2AA3BC0875;\n\n    HMODULE ntdll = LoadLibraryA(\"ntdll.dll\");\n    if (ntdll == NULL)\n    {\n        return FALSE;\n    }\n\n    RtlPublishWnfStateData = (publish_fn_t)GetProcAddress(ntdll, \"RtlPublishWnfStateData\");\n    if (RtlPublishWnfStateData == NULL)\n    {\n        return FALSE;\n    }\n\n    if (RtlPublishWnfStateData(WNF_GPOL_SYSTEM_CHANGES, 0, 0, 0, 0) != ERROR_SUCCESS)\n    {\n        return FALSE;\n    }\n\n    return TRUE;\n}\n\n/**\n * Signal the DNS resolver (and others potentially) to reload the group policy (DNS) settings\n *\n * @return BOOL to indicate if the reload was initiated\n */\nstatic BOOL\nApplyGpolSettings(void)\n{\n    SYSTEM_INFO si;\n    GetSystemInfo(&si);\n    const BOOL win_32bit = si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL;\n    return win_32bit ? ApplyGpolSettings32() : ApplyGpolSettings64();\n}\n\n/**\n * Signal the DNS resolver to reload its settings\n *\n * @param apply_gpol    BOOL reload setting from group policy hives as well\n *\n * @return BOOL to indicate if the reload was initiated\n */\nstatic BOOL\nApplyDnsSettings(BOOL apply_gpol)\n{\n    BOOL res = FALSE;\n    SC_HANDLE scm = NULL;\n    SC_HANDLE dnssvc = NULL;\n\n    if (apply_gpol && ApplyGpolSettings() == FALSE)\n    {\n        MsgToEventLog(M_ERR, L\"%S: sending GPOL notification failed\", __func__);\n    }\n\n    scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);\n    if (scm == NULL)\n    {\n        MsgToEventLog(M_ERR, L\"%S: OpenSCManager call failed (%lu)\", __func__, GetLastError());\n        goto out;\n    }\n\n    dnssvc = OpenServiceA(scm, \"Dnscache\", SERVICE_PAUSE_CONTINUE);\n    if (dnssvc == NULL)\n    {\n        MsgToEventLog(M_ERR, L\"%S: OpenService call failed (%lu)\", __func__, GetLastError());\n        goto out;\n    }\n\n    SERVICE_STATUS status;\n    if (ControlService(dnssvc, SERVICE_CONTROL_PARAMCHANGE, &status) == 0)\n    {\n        MsgToEventLog(M_ERR, L\"%S: ControlService call failed (%lu)\", __func__, GetLastError());\n        goto out;\n    }\n\n    res = TRUE;\n\nout:\n    if (dnssvc)\n    {\n        CloseServiceHandle(dnssvc);\n    }\n    if (scm)\n    {\n        CloseServiceHandle(scm);\n    }\n    return res;\n}\n\n/**\n * Get the string interface UUID (with braces) for an interface alias name\n *\n * @param  itf_name   the interface alias name\n * @param  str        pointer to the buffer the wide UUID is returned in\n * @param  len        size of the str buffer in characters\n *\n * @return NO_ERROR on success, or the Windows error code for the failure\n */\nstatic DWORD\nInterfaceIdString(PCSTR itf_name, PWSTR str, size_t len)\n{\n    DWORD err;\n    GUID guid;\n    NET_LUID luid;\n    PWSTR iid_str = NULL;\n\n    err = InterfaceLuid(itf_name, &luid);\n    if (err)\n    {\n        MsgToEventLog(M_ERR, L\"%S: failed to convert itf alias '%s'\", __func__, itf_name);\n        goto out;\n    }\n    err = ConvertInterfaceLuidToGuid(&luid, &guid);\n    if (err)\n    {\n        MsgToEventLog(M_ERR, L\"%S: Failed to convert itf '%s' LUID\", __func__, itf_name);\n        goto out;\n    }\n\n    if (StringFromIID(&guid, &iid_str) != S_OK)\n    {\n        MsgToEventLog(M_ERR, L\"%S: Failed to convert itf '%s' IID\", __func__, itf_name);\n        err = ERROR_OUTOFMEMORY;\n        goto out;\n    }\n    if (wcslen(iid_str) + 1 > len)\n    {\n        err = ERROR_INVALID_PARAMETER;\n        goto out;\n    }\n\n    wcsncpy(str, iid_str, len);\n\nout:\n    if (iid_str)\n    {\n        CoTaskMemFree(iid_str);\n    }\n    return err;\n}\n\n/**\n * Check for a valid search list in a certain key of the registry\n *\n * Valid means that a string value \"SearchList\" exists and that it\n * contains one or more domains. We only check if the string contains\n * a valid domain name character, but the main point is to prevent letting\n * pass whitespace-only lists, so that check is good enough for that\n * purpose.\n *\n * @param  key  HKEY in which to check for a valid search list\n *\n * @return BOOL to indicate if a valid search list has been found\n */\nstatic BOOL\nHasValidSearchList(HKEY key)\n{\n    char data[64];\n    DWORD size = sizeof(data);\n    LSTATUS err = RegGetValueA(key, NULL, \"SearchList\", RRF_RT_REG_SZ, NULL, (PBYTE)data, &size);\n    if (!err || err == ERROR_MORE_DATA)\n    {\n        data[sizeof(data) - 1] = '\\0';\n        for (size_t i = 0; i < strlen(data); ++i)\n        {\n            if (isalnum(data[i]) || data[i] == '-' || data[i] == '.')\n            {\n                return TRUE;\n            }\n        }\n    }\n    return FALSE;\n}\n\n/**\n * Find the registry key for storing the DNS domains for the VPN interface\n *\n * @param  itf_name PCSTR that contains the alias name of the interface the domains\n *                  are related to. If this is NULL the interface probing is skipped.\n * @param  gpol     PBOOL to indicate if the key returned is the group policy hive\n * @param  key      PHKEY in which the found registry key is returned in\n *\n * @return BOOL to indicate if a search list is already present at the location.\n *         If the key returned is INVALID_HANDLE_VALUE, this indicates an\n *         unrecoverable error.\n *\n * The correct location to add them is where a non-empty \"SearchList\" value exists,\n * or in the interface configuration itself. However, the system-wide and then the\n * group policy search lists overrule the previous one respectively, so we need to\n * probe to find the effective list.\n */\nstatic BOOL\nGetDnsSearchListKey(PCSTR itf_name, PBOOL gpol, PHKEY key)\n{\n    LSTATUS err;\n\n    *gpol = FALSE;\n\n    /* Try the group policy search list */\n    err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, \"SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows NT\\\\DNSClient\",\n                        0, KEY_ALL_ACCESS, key);\n    if (!err)\n    {\n        if (HasValidSearchList(*key))\n        {\n            *gpol = TRUE;\n            return TRUE;\n        }\n        RegCloseKey(*key);\n    }\n\n    /* Try the system-wide search list */\n    err =\n        RegOpenKeyExA(HKEY_LOCAL_MACHINE, \"System\\\\CurrentControlSet\\\\Services\\\\TCPIP\\\\Parameters\",\n                      0, KEY_ALL_ACCESS, key);\n    if (!err)\n    {\n        if (HasValidSearchList(*key))\n        {\n            return TRUE;\n        }\n        RegCloseKey(*key);\n    }\n\n    if (itf_name)\n    {\n        /* Always return the VPN interface key (if it exists) */\n        WCHAR iid[64];\n        DWORD iid_err = InterfaceIdString(itf_name, iid, _countof(iid));\n        if (!iid_err)\n        {\n            HKEY itfs;\n            err =\n                RegOpenKeyExA(HKEY_LOCAL_MACHINE,\n                              \"System\\\\CurrentControlSet\\\\Services\\\\TCPIP\\\\Parameters\\\\Interfaces\",\n                              0, KEY_ALL_ACCESS, &itfs);\n            if (!err)\n            {\n                err = RegOpenKeyExW(itfs, iid, 0, KEY_ALL_ACCESS, key);\n                RegCloseKey(itfs);\n                if (!err)\n                {\n                    return FALSE; /* No need to preserve the VPN itf search list */\n                }\n            }\n        }\n    }\n\n    *key = INVALID_HANDLE_VALUE;\n    return FALSE;\n}\n\n/**\n * Check if a initial list had already been created\n *\n * @param  key      HKEY of the registry subkey to search in\n *\n * @return BOOL to indicate if the initial list is already present under key\n */\nstatic BOOL\nInitialSearchListExists(HKEY key)\n{\n    LSTATUS err;\n\n    err = RegGetValueA(key, NULL, \"InitialSearchList\", RRF_RT_REG_SZ, NULL, NULL, NULL);\n    if (err)\n    {\n        if (err == ERROR_FILE_NOT_FOUND)\n        {\n            return FALSE;\n        }\n        MsgToEventLog(M_ERR, L\"%S: failed to get InitialSearchList (%lu)\", __func__, err);\n    }\n\n    return TRUE;\n}\n\n/**\n * Return correct size for registry value to set for string\n *\n */\nstatic DWORD\nRegWStringSize(PCWSTR string)\n{\n    size_t length = (wcslen(string) + 1) * sizeof(wchar_t);\n    if (length > UINT_MAX)\n    {\n        length = UINT_MAX;\n    }\n    return (DWORD)length;\n}\n\n/**\n * Prepare DNS domain \"SearchList\" registry value, so additional\n * VPN domains can be added and its original state can be restored\n * in case the system cannot clean up regularly.\n *\n * @param  key      registry subkey to store the list in\n * @param  list     string of comma separated domains to use as the list\n *\n * @return boolean to indicate whether the list was stored successfully\n */\nstatic BOOL\nStoreInitialDnsSearchList(HKEY key, PCWSTR list)\n{\n    if (!list || wcslen(list) == 0)\n    {\n        MsgToEventLog(M_ERR, L\"%S: empty search list\", __func__);\n        return FALSE;\n    }\n\n    if (InitialSearchListExists(key))\n    {\n        /* Initial list had already been stored */\n        return TRUE;\n    }\n\n    DWORD size = RegWStringSize(list);\n    LSTATUS err = RegSetValueExW(key, L\"InitialSearchList\", 0, REG_SZ, (PBYTE)list, size);\n    if (err)\n    {\n        MsgToEventLog(M_ERR, L\"%S: failed to set InitialSearchList value (%lu)\", __func__, err);\n        return FALSE;\n    }\n\n    return TRUE;\n}\n\n/**\n * Append domain suffixes to an existing search list\n *\n * @param  key          HKEY the list is stored at\n * @param  have_list    BOOL to indicate if a search list already exists\n * @param  domains      domain suffixes as comma separated string\n *\n * @return BOOL to indicate success or failure\n */\nstatic BOOL\nAddDnsSearchDomains(HKEY key, BOOL have_list, PCWSTR domains)\n{\n    LSTATUS err;\n    WCHAR list[2048] = { 0 };\n    DWORD size = sizeof(list);\n\n    if (have_list)\n    {\n        err = RegGetValueW(key, NULL, L\"SearchList\", RRF_RT_REG_SZ, NULL, list, &size);\n        if (err)\n        {\n            MsgToEventLog(M_SYSERR, L\"%S: could not get SearchList from registry (%lu)\", __func__,\n                          err);\n            return FALSE;\n        }\n\n        if (!StoreInitialDnsSearchList(key, list))\n        {\n            return FALSE;\n        }\n\n        size_t listlen = (size / sizeof(list[0])) - 1; /* returned size is in bytes */\n        size_t domlen = wcslen(domains);\n        if (listlen + domlen + 2 > _countof(list))\n        {\n            MsgToEventLog(M_SYSERR, L\"%S: not enough space in list for search domains (len=%lu)\",\n                          __func__, domlen);\n            return FALSE;\n        }\n\n        /* Append to end of the search list */\n        PWSTR pos = list + listlen;\n        *pos = ',';\n        wcsncpy(pos + 1, domains, domlen + 1);\n    }\n    else\n    {\n        wcsncpy(list, domains, wcslen(domains) + 1);\n    }\n\n    size = RegWStringSize(list);\n    err = RegSetValueExW(key, L\"SearchList\", 0, REG_SZ, (PBYTE)list, size);\n    if (err)\n    {\n        MsgToEventLog(M_SYSERR, L\"%S: could not set SearchList to registry (%lu)\", __func__, err);\n        return FALSE;\n    }\n\n    return TRUE;\n}\n\n/**\n * Reset the DNS search list to its original value\n *\n * Looks for a \"InitialSearchList\" value as the one to reset to.\n * If it doesn't exist, doesn't reset anything, as there was no\n * SearchList in the first place.\n *\n * @param  key  HKEY of the location in the registry to reset\n *\n * @return BOOL to indicate if something was reset\n */\nstatic BOOL\nResetDnsSearchDomains(HKEY key)\n{\n    LSTATUS err;\n    BOOL ret = FALSE;\n    WCHAR list[2048];\n    DWORD size = sizeof(list);\n\n    err = RegGetValueW(key, NULL, L\"InitialSearchList\", RRF_RT_REG_SZ, NULL, list, &size);\n    if (err)\n    {\n        if (err != ERROR_FILE_NOT_FOUND)\n        {\n            MsgToEventLog(M_SYSERR, L\"%S: could not get InitialSearchList from registry (%lu)\",\n                          __func__, err);\n        }\n        goto out;\n    }\n\n    size = RegWStringSize(list);\n    err = RegSetValueExW(key, L\"SearchList\", 0, REG_SZ, (PBYTE)list, size);\n    if (err)\n    {\n        MsgToEventLog(M_SYSERR, L\"%S: could not set SearchList in registry (%lu)\", __func__, err);\n        goto out;\n    }\n\n    RegDeleteValueA(key, \"InitialSearchList\");\n    ret = TRUE;\n\nout:\n    return ret;\n}\n\n/**\n * Remove domain suffixes from an existing search list\n *\n * @param  key      HKEY the list is stored at\n * @param  domains  domain suffixes to remove as comma separated string\n */\nstatic void\nRemoveDnsSearchDomains(HKEY key, PCWSTR domains)\n{\n    LSTATUS err;\n    WCHAR list[2048];\n    DWORD size = sizeof(list);\n\n    err = RegGetValueW(key, NULL, L\"SearchList\", RRF_RT_REG_SZ, NULL, list, &size);\n    if (err)\n    {\n        MsgToEventLog(M_SYSERR, L\"%S: could not get SearchList from registry (%lu)\", __func__, err);\n        return;\n    }\n\n    PWSTR dst = wcsstr(list, domains);\n    if (!dst)\n    {\n        MsgToEventLog(M_ERR, L\"%S: could not find domains in search list\", __func__);\n        return;\n    }\n\n    /* Cut out domains from list */\n    size_t domlen = wcslen(domains);\n    PCWSTR src = dst + domlen;\n    /* Also remove the leading comma, if there is one */\n    dst = dst > list ? dst - 1 : dst;\n    wmemmove(dst, src, domlen);\n\n    size_t list_len = wcslen(list);\n    if (list_len)\n    {\n        /* Now check if the shortened list equals the initial search list */\n        WCHAR initial[2048];\n        size = sizeof(initial);\n        err = RegGetValueW(key, NULL, L\"InitialSearchList\", RRF_RT_REG_SZ, NULL, initial, &size);\n        if (err)\n        {\n            MsgToEventLog(M_SYSERR, L\"%S: could not get InitialSearchList from registry (%lu)\",\n                          __func__, err);\n            return;\n        }\n\n        /* If the search list is back to its initial state reset it */\n        if (wcsncmp(list, initial, list_len) == 0)\n        {\n            ResetDnsSearchDomains(key);\n            return;\n        }\n    }\n\n    size = RegWStringSize(list);\n    err = RegSetValueExW(key, L\"SearchList\", 0, REG_SZ, (PBYTE)list, size);\n    if (err)\n    {\n        MsgToEventLog(M_SYSERR, L\"%S: could not set SearchList in registry (%lu)\", __func__, err);\n    }\n}\n\n/**\n * Removes DNS domains from a search list they were previously added to\n *\n * @param undo_data     pointer to dns_domains_undo_data_t\n */\nstatic void\nUndoDnsSearchDomains(dns_domains_undo_data_t *undo_data)\n{\n    BOOL gpol;\n    HKEY dns_searchlist_key;\n    GetDnsSearchListKey(undo_data->itf_name, &gpol, &dns_searchlist_key);\n    if (dns_searchlist_key != INVALID_HANDLE_VALUE)\n    {\n        RemoveDnsSearchDomains(dns_searchlist_key, undo_data->domains);\n        RegCloseKey(dns_searchlist_key);\n        ApplyDnsSettings(gpol);\n\n        free(undo_data->domains);\n        undo_data->domains = NULL;\n    }\n}\n\n/**\n * Add or remove DNS search domains\n *\n * @param  itf_name   alias name of the interface the domains are set for\n * @param  domains    a comma separated list of domain name suffixes\n * @param  gpol       PBOOL to indicate if group policy values were modified\n * @param  lists      pointer to the undo lists\n *\n * @return NO_ERROR on success, an error status code otherwise\n *\n * If a SearchList is present in the registry already, the domains are added\n * to that list. Otherwise the domains are added to the VPN interface specific list.\n * A group policy search list takes precedence over a system-wide list, and that one\n * itself takes precedence over interface specific ones.\n *\n * This function will remove previously set domains if the domains parameter\n * is NULL or empty.\n *\n * The gpol value is only valid if the function returns no error. In the error\n * case nothing is changed.\n */\nstatic DWORD\nSetDnsSearchDomains(PCSTR itf_name, PCSTR domains, PBOOL gpol, undo_lists_t *lists)\n{\n    DWORD err = ERROR_OUTOFMEMORY;\n\n    HKEY list_key;\n    BOOL have_list = GetDnsSearchListKey(itf_name, gpol, &list_key);\n    if (list_key == INVALID_HANDLE_VALUE)\n    {\n        MsgToEventLog(M_SYSERR, L\"%S: could not get search list registry key\", __func__);\n        return ERROR_FILE_NOT_FOUND;\n    }\n\n    /* Remove previously installed search domains */\n    dns_domains_undo_data_t *undo_data = RemoveListItem(&(*lists)[undo_domains], CmpAny, NULL);\n    if (undo_data)\n    {\n        RemoveDnsSearchDomains(list_key, undo_data->domains);\n        free(undo_data->domains);\n        free(undo_data);\n        undo_data = NULL;\n    }\n\n    /* If there are search domains, add them */\n    if (domains && *domains)\n    {\n        wchar_t *wide_domains = utf8to16(domains); /* utf8 to wide-char */\n        if (!wide_domains)\n        {\n            goto out;\n        }\n\n        undo_data = malloc(sizeof(*undo_data));\n        if (!undo_data)\n        {\n            free(wide_domains);\n            wide_domains = NULL;\n            goto out;\n        }\n        strncpy(undo_data->itf_name, itf_name, sizeof(undo_data->itf_name));\n        undo_data->domains = wide_domains;\n\n        if (AddDnsSearchDomains(list_key, have_list, wide_domains) == FALSE\n            || AddListItem(&(*lists)[undo_domains], undo_data) != NO_ERROR)\n        {\n            RemoveDnsSearchDomains(list_key, wide_domains);\n            free(wide_domains);\n            free(undo_data);\n            undo_data = NULL;\n            goto out;\n        }\n    }\n\n    err = NO_ERROR;\n\nout:\n    RegCloseKey(list_key);\n    return err;\n}\n\n/**\n * Return the interfaces registry key for the specified address family\n *\n * @param family    the internet address family to open the key for\n * @param key       PHKEY to return the key in\n * @return BOOL to indicate success or failure\n */\nstatic BOOL\nGetInterfacesKey(short family, PHKEY key)\n{\n    PCSTR itfs_key = family == AF_INET6\n                         ? \"SYSTEM\\\\CurrentControlSet\\\\Services\\\\Tcpip6\\\\Parameters\\\\Interfaces\"\n                         : \"SYSTEM\\\\CurrentControlSet\\\\Services\\\\Tcpip\\\\Parameters\\\\Interfaces\";\n\n    LSTATUS err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, itfs_key, 0, KEY_ALL_ACCESS, key);\n    if (err)\n    {\n        *key = INVALID_HANDLE_VALUE;\n        MsgToEventLog(M_SYSERR, L\"%S: could not open interfaces registry key for family %d (%lu)\",\n                      __func__, family, err);\n    }\n\n    return err ? FALSE : TRUE;\n}\n\n/**\n * Set the DNS name servers in a registry interface configuration\n *\n * @param itf_id    the interface id to set the servers for\n * @param family    internet address family to set the servers for\n * @param value     the value to set the name servers to\n *\n * @return DWORD NO_ERROR on success, a Windows error code otherwise\n */\nstatic DWORD\nSetNameServersValue(PCWSTR itf_id, short family, PCSTR value)\n{\n    DWORD err;\n\n    HKEY itfs;\n    if (!GetInterfacesKey(family, &itfs))\n    {\n        return ERROR_FILE_NOT_FOUND;\n    }\n\n    HKEY itf = INVALID_HANDLE_VALUE;\n    err = RegOpenKeyExW(itfs, itf_id, 0, KEY_ALL_ACCESS, &itf);\n    if (err)\n    {\n        MsgToEventLog(M_SYSERR, L\"%S: could not open interface key for %s family %d (%lu)\",\n                      __func__, itf_id, family, err);\n        goto out;\n    }\n\n    err = RegSetValueExA(itf, \"NameServer\", 0, REG_SZ, (PBYTE)value, (DWORD)strlen(value) + 1);\n    if (err)\n    {\n        MsgToEventLog(M_SYSERR, L\"%S: could not set name servers '%S' for %s family %d (%lu)\",\n                      __func__, value, itf_id, family, err);\n    }\n\nout:\n    if (itf != INVALID_HANDLE_VALUE)\n    {\n        RegCloseKey(itf);\n    }\n    if (itfs != INVALID_HANDLE_VALUE)\n    {\n        RegCloseKey(itfs);\n    }\n    return err;\n}\n\n/**\n * Set the DNS name servers in a registry interface configuration\n *\n * @param itf_id    the interface id to set the servers for\n * @param family    internet address family to set the servers for\n * @param addrs     comma separated list of name server addresses\n *\n * @return DWORD NO_ERROR on success, a Windows error code otherwise\n */\nstatic DWORD\nSetNameServers(PCWSTR itf_id, short family, PCSTR addrs)\n{\n    return SetNameServersValue(itf_id, family, addrs);\n}\n\n/**\n * Delete all DNS name servers from a registry interface configuration\n *\n * @param itf_id    the interface id to clear the servers for\n * @param family    internet address family to clear the servers for\n *\n * @return DWORD NO_ERROR on success, a Windows error code otherwise\n */\nstatic DWORD\nResetNameServers(PCWSTR itf_id, short family)\n{\n    return SetNameServersValue(itf_id, family, \"\");\n}\n\nstatic DWORD\nHandleDNSConfigMessage(const dns_cfg_message_t *msg, undo_lists_t *lists)\n{\n    DWORD err = 0;\n    undo_type_t undo_type = (msg->family == AF_INET6) ? undo_dns6 : undo_dns4;\n    unsigned int addr_len = msg->addr_len;\n\n    /* sanity check */\n    const unsigned int max_addrs = _countof(msg->addr);\n    if (addr_len > max_addrs)\n    {\n        addr_len = max_addrs;\n    }\n\n    if (!msg->iface.name[0]) /* interface name is required */\n    {\n        return ERROR_MESSAGE_DATA;\n    }\n\n    /* use a non-const reference with limited scope to enforce null-termination of strings from\n     * client */\n    {\n        dns_cfg_message_t *msgptr = (dns_cfg_message_t *)msg;\n        msgptr->iface.name[_countof(msg->iface.name) - 1] = '\\0';\n        msgptr->domains[_countof(msg->domains) - 1] = '\\0';\n    }\n\n    WCHAR iid[64];\n    err = InterfaceIdString(msg->iface.name, iid, _countof(iid));\n    if (err)\n    {\n        return err;\n    }\n\n    /* We delete all current addresses before adding any\n     * OR if the message type is del_dns_cfg\n     */\n    if (addr_len > 0 || msg->header.type == msg_del_dns_cfg)\n    {\n        err = ResetNameServers(iid, msg->family);\n        if (err)\n        {\n            return err;\n        }\n        free(RemoveListItem(&(*lists)[undo_type], CmpAny, iid));\n    }\n\n    if (msg->header.type == msg_del_dns_cfg)\n    {\n        BOOL gpol = FALSE;\n        if (msg->domains[0])\n        {\n            /* setting an empty domain list removes any previous value */\n            err = SetDnsSearchDomains(msg->iface.name, NULL, &gpol, lists);\n        }\n        ApplyDnsSettings(gpol);\n        return err; /* job done */\n    }\n\n    if (msg->addr_len > 0)\n    {\n        /* prepare the comma separated address list */\n        /* cannot use max_addrs here as that is not considered compile\n         * time constant by all compilers and constexpr is C23 */\n        CHAR addrs[_countof(msg->addr) * 64]; /* 64 is enough for one IPv4/6 address */\n        size_t offset = 0;\n        for (unsigned int i = 0; i < addr_len; ++i)\n        {\n            if (i != 0)\n            {\n                addrs[offset++] = ',';\n            }\n            if (msg->family == AF_INET6)\n            {\n                RtlIpv6AddressToStringA(&msg->addr[i].ipv6, addrs + offset);\n            }\n            else\n            {\n                RtlIpv4AddressToStringA(&msg->addr[i].ipv4, addrs + offset);\n            }\n            offset = strlen(addrs);\n        }\n\n        err = SetNameServers(iid, msg->family, addrs);\n        if (err)\n        {\n            return err;\n        }\n\n        wchar_t *tmp_iid = _wcsdup(iid);\n        if (!tmp_iid || AddListItem(&(*lists)[undo_type], tmp_iid))\n        {\n            free(tmp_iid);\n            ResetNameServers(iid, msg->family);\n            return ERROR_OUTOFMEMORY;\n        }\n    }\n\n    BOOL gpol = FALSE;\n    if (msg->domains[0])\n    {\n        err = SetDnsSearchDomains(msg->iface.name, msg->domains, &gpol, lists);\n    }\n    ApplyDnsSettings(gpol);\n\n    return err;\n}\n\n/**\n * Checks if DHCP is enabled for an interface\n *\n * @param  key        HKEY of the interface to check for\n *\n * @return BOOL set to TRUE if DHCP is enabled, or FALSE if\n *         disabled or an error occurred\n */\nstatic BOOL\nIsDhcpEnabled(HKEY key)\n{\n    DWORD dhcp;\n    DWORD size = sizeof(dhcp);\n    LSTATUS err;\n\n    err = RegGetValueA(key, NULL, \"EnableDHCP\", RRF_RT_REG_DWORD, NULL, (PBYTE)&dhcp, &size);\n    if (err != NO_ERROR)\n    {\n        MsgToEventLog(M_SYSERR, L\"%S: Could not read DHCP status (%lu)\", __func__, err);\n        return FALSE;\n    }\n\n    return dhcp ? TRUE : FALSE;\n}\n\n/**\n * Set name servers from a NRPT address list\n *\n * @param itf_id        the VPN interface ID to set the name servers for\n * @param addresses     the list of NRPT addresses\n *\n * @return LSTATUS NO_ERROR in case of success, a Windows error code otherwise\n */\nstatic LSTATUS\nSetNameServerAddresses(PWSTR itf_id, const nrpt_address_t *addresses)\n{\n    const short families[] = { AF_INET, AF_INET6 };\n    for (size_t i = 0; i < _countof(families); i++)\n    {\n        short family = families[i];\n\n        /* Create a comma sparated list of addresses of this family */\n        size_t offset = 0;\n        char addr_list[NRPT_ADDR_SIZE * NRPT_ADDR_NUM];\n        for (int j = 0; j < NRPT_ADDR_NUM && addresses[j][0]; j++)\n        {\n            if ((family == AF_INET6 && strchr(addresses[j], ':') == NULL)\n                || (family == AF_INET && strchr(addresses[j], ':') != NULL))\n            {\n                /* Address family doesn't match, skip this one */\n                continue;\n            }\n            if (offset)\n            {\n                addr_list[offset++] = ',';\n            }\n            strcpy(addr_list + offset, addresses[j]);\n            offset += strlen(addresses[j]);\n        }\n\n        if (offset == 0)\n        {\n            /* No address for this family to set */\n            continue;\n        }\n\n        /* Set name server addresses */\n        LSTATUS err = SetNameServers(itf_id, family, addr_list);\n        if (err)\n        {\n            return err;\n        }\n    }\n    return NO_ERROR;\n}\n\n/**\n * Get DNS server IPv4 addresses of an interface\n *\n * @param  itf_key    registry key of the IPv4 interface data\n * @param  addrs      pointer to the buffer addresses are returned in\n * @param  size       pointer to the size of the buffer, contains the\n *                    size of the addresses on return\n *\n * @return LSTATUS NO_ERROR on success, a Windows error code otherwise\n */\nstatic LSTATUS\nGetItfDnsServersV4(HKEY itf_key, PSTR addrs, PDWORD size)\n{\n    addrs[*size - 1] = '\\0';\n\n    LSTATUS err;\n    DWORD s = *size;\n    err = RegGetValueA(itf_key, NULL, \"NameServer\", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);\n    if (err && err != ERROR_FILE_NOT_FOUND)\n    {\n        *size = 0;\n        return err;\n    }\n\n    /* Try DHCP addresses if we don't have some already */\n    if (!strchr(addrs, '.') && IsDhcpEnabled(itf_key))\n    {\n        s = *size;\n        RegGetValueA(itf_key, NULL, \"DhcpNameServer\", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);\n        if (err)\n        {\n            *size = 0;\n            return err;\n        }\n    }\n\n    if (strchr(addrs, '.'))\n    {\n        *size = s;\n        return NO_ERROR;\n    }\n\n    *size = 0;\n    return ERROR_FILE_NOT_FOUND;\n}\n\n/**\n * Get DNS server IPv6 addresses of an interface\n *\n * @param  itf_key    registry key of the IPv6 interface data\n * @param  addrs      pointer to the buffer addresses are returned in\n * @param  size       pointer to the size of the buffer\n *\n * @return LSTATUS NO_ERROR on success, a Windows error code otherwise\n */\nstatic LSTATUS\nGetItfDnsServersV6(HKEY itf_key, PSTR addrs, PDWORD size)\n{\n    addrs[*size - 1] = '\\0';\n\n    LSTATUS err;\n    DWORD s = *size;\n    err = RegGetValueA(itf_key, NULL, \"NameServer\", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);\n    if (err && err != ERROR_FILE_NOT_FOUND)\n    {\n        *size = 0;\n        return err;\n    }\n\n    /* Try DHCP addresses if we don't have some already */\n    if (!strchr(addrs, ':') && IsDhcpEnabled(itf_key))\n    {\n        IN6_ADDR in_addrs[8];\n        DWORD in_addrs_size = sizeof(in_addrs);\n        err = RegGetValueA(itf_key, NULL, \"Dhcpv6DNSServers\", RRF_RT_REG_BINARY, NULL,\n                           (PBYTE)in_addrs, &in_addrs_size);\n        if (err)\n        {\n            *size = 0;\n            return err;\n        }\n\n        s = *size;\n        PSTR pos = addrs;\n        size_t in_addrs_read = in_addrs_size / sizeof(IN6_ADDR);\n        for (size_t i = 0; i < in_addrs_read; ++i)\n        {\n            if (i != 0)\n            {\n                /* Add separator */\n                *pos++ = ',';\n                s--;\n            }\n\n            if (inet_ntop(AF_INET6, &in_addrs[i], pos, s) != NULL)\n            {\n                *size = 0;\n                return ERROR_MORE_DATA;\n            }\n\n            size_t addr_len = strlen(pos);\n            pos += addr_len;\n            s -= (DWORD)addr_len;\n        }\n        s = (DWORD)strlen(addrs) + 1;\n    }\n\n    if (strchr(addrs, ':'))\n    {\n        *size = s;\n        return NO_ERROR;\n    }\n\n    *size = 0;\n    return ERROR_FILE_NOT_FOUND;\n}\n\n/**\n * Check if a domain is contained in a comma separated list of domains\n *\n * @param list      Comma separated list of domains\n * @param domain    Domain string to search for\n * @param len       Length of the domain string, excluding the '\\0'\n *\n * @return TRUE when the domain was found in the list, FALSE otherwise.\n */\nstatic BOOL\nListContainsDomain(PCWSTR list, PCWSTR domain, size_t len)\n{\n    PCWSTR match = list;\n    while (match)\n    {\n        match = wcsstr(match, domain);\n        if (!match)\n        {\n            /* Domain has not matched */\n            break;\n        }\n        if ((match == list || *(match - 1) == ',')\n            && (*(match + len) == ',' || *(match + len) == '\\0'))\n        {\n            /* Domain has matched fully */\n            return TRUE;\n        }\n        match += len;\n    }\n    return FALSE;\n}\n\n/**\n * Convert interface specific domain suffix(es) from comma-separated\n * string to MULTI_SZ string.\n *\n * The \\p domains paramter will be set to a MULTI_SZ domains string.\n * In case of an error \\p size is set to 0 and the contents of \\p domains\n * are invalid.\n * Note that domains are deleted from the string if they match a search domain.\n *\n * @param[in]     search_domains  optional list of search domains\n * @param[in,out] domains         buffer that contains the input comma-separated\n *                                string and will contain the MULTI_SZ output string\n * @param[in,out] size            pointer to size of the input string in bytes. Will be\n *                                set to the size of the string returned, including\n *                                the terminating zeros or 0.\n * @param[in]     buf_size        size of the \\p domains buffer\n *\n * @return LSTATUS NO_ERROR if the domain suffix(es) were read successfully,\n *         ERROR_FILE_NOT_FOUND if no domain was found for the interface,\n *         ERROR_MORE_DATA if the list did not fit into the buffer\n */\nstatic LSTATUS\nConvertItfDnsDomains(PCWSTR search_domains, PWSTR domains, PDWORD size, const DWORD buf_size)\n{\n    const DWORD glyph_size = sizeof(*domains);\n    const DWORD buf_len = buf_size / glyph_size;\n\n    /*\n     * Found domain(s), now convert them:\n     *   - prefix each domain with a dot\n     *   - convert comma separated list to MULTI_SZ\n     */\n    PWCHAR pos = domains;\n    while (TRUE)\n    {\n        /* Terminate the domain at the next comma */\n        PWCHAR comma = wcschr(pos, ',');\n        if (comma)\n        {\n            *comma = '\\0';\n        }\n\n        DWORD domain_len = (DWORD)wcslen(pos);\n        DWORD domain_size = domain_len * glyph_size;\n        DWORD converted_size = (DWORD)(pos - domains) * glyph_size;\n\n        /* Ignore itf domains which match a pushed search domain */\n        if (ListContainsDomain(search_domains, pos, domain_len))\n        {\n            if (comma)\n            {\n                /* Overwrite the ignored domain with remaining one(s) */\n                memmove(pos, comma + 1, buf_size - converted_size);\n                *size -= domain_size + glyph_size;\n                continue;\n            }\n            else\n            {\n                /* This was the last domain */\n                *pos = '\\0';\n                *size -= domain_size;\n                return wcslen(domains) ? NO_ERROR : ERROR_FILE_NOT_FOUND;\n            }\n        }\n\n        /* Add space for the leading dot */\n        domain_len += 1;\n        domain_size += glyph_size;\n\n        /* Space for the terminating zeros */\n        const DWORD extra_size = 2 * glyph_size;\n\n        /* Check for enough space to convert this domain */\n        if (converted_size + domain_size + extra_size > buf_size)\n        {\n            /* Domain doesn't fit, bad luck if it's the first one */\n            *pos = '\\0';\n            *size = converted_size == 0 ? 0 : converted_size + glyph_size;\n            return ERROR_MORE_DATA;\n        }\n\n        /* Prefix domain at pos with the dot */\n        memmove(pos + 1, pos, buf_size - converted_size - glyph_size);\n        domains[buf_len - 1] = '\\0';\n        *pos = '.';\n        *size += glyph_size;\n\n        if (!comma)\n        {\n            /* Conversion is done */\n            *(pos + domain_len) = '\\0';\n            *size += glyph_size;\n            return NO_ERROR;\n        }\n\n        /* Comma pos is now +1 after adding leading dot */\n        pos = comma + 2;\n    }\n}\n\n/**\n * Return interface specific domain suffix(es)\n *\n * The \\p domains paramter will be set to a MULTI_SZ domains string.\n * In case of an error or if no domains are found for the interface\n * \\p size is set to 0 and the contents of \\p domains are invalid.\n * Note that the domains could have been set by DHCP or manually.\n * Note that domains are ignored if they match a pushed search domain.\n *\n * @param  itf             HKEY of the interface to read from\n * @param  search_domains  optional list of search domains\n * @param  domains         PWSTR buffer to return the domain(s) in\n * @param  size            pointer to size of the domains buffer in bytes. Will be\n *                         set to the size of the string returned, including\n *                         the terminating zeros or 0.\n *\n * @return LSTATUS NO_ERROR if the domain suffix(es) were read successfully,\n *         ERROR_FILE_NOT_FOUND if no domain was found for the interface,\n *         ERROR_MORE_DATA if the list did not fit into the buffer,\n *         any other error indicates an error while reading from the registry.\n */\nstatic LSTATUS\nGetItfDnsDomains(HKEY itf, PCWSTR search_domains, PWSTR domains, PDWORD size)\n{\n    if (domains == NULL || size == NULL || *size == 0)\n    {\n        return ERROR_INVALID_PARAMETER;\n    }\n\n    LSTATUS err = ERROR_FILE_NOT_FOUND;\n    const DWORD buf_size = *size;\n    const DWORD glyph_size = sizeof(*domains);\n    PWSTR values[] = { L\"SearchList\", L\"Domain\", L\"DhcpDomainSearchList\", L\"DhcpDomain\", NULL };\n\n    for (int i = 0; values[i]; i++)\n    {\n        *size = buf_size;\n        err = RegGetValueW(itf, NULL, values[i], RRF_RT_REG_SZ, NULL, (PBYTE)domains, size);\n        if (!err && *size > glyph_size && domains[(*size / glyph_size) - 1] == '\\0' && wcschr(domains, '.'))\n        {\n            return ConvertItfDnsDomains(search_domains, domains, size, buf_size);\n        }\n    }\n\n    *size = 0;\n    return err;\n}\n\n/**\n * Check if an interface is connected and up\n *\n * @param  iid_str    the interface GUID as string\n *\n * @return TRUE if the interface is connected and up, FALSE otherwise or in\n *         case an error happened\n */\nstatic BOOL\nIsInterfaceConnected(PWSTR iid_str)\n{\n    GUID iid;\n    BOOL res = FALSE;\n    MIB_IF_ROW2 itf_row;\n\n    /* Get GUID from string */\n    if (IIDFromString(iid_str, &iid) != S_OK)\n    {\n        MsgToEventLog(M_SYSERR, L\"%S: could not convert interface %s GUID string\", __func__,\n                      iid_str);\n        goto out;\n    }\n\n    /* Get LUID from GUID */\n    if (ConvertInterfaceGuidToLuid(&iid, &itf_row.InterfaceLuid) != NO_ERROR)\n    {\n        goto out;\n    }\n\n    /* Look up interface status */\n    if (GetIfEntry2(&itf_row) != NO_ERROR)\n    {\n        MsgToEventLog(M_SYSERR, L\"%S: could not get interface %s status\", __func__, iid_str);\n        goto out;\n    }\n\n    if (itf_row.MediaConnectState == MediaConnectStateConnected\n        && itf_row.OperStatus == IfOperStatusUp)\n    {\n        res = TRUE;\n    }\n\nout:\n    return res;\n}\n\n/**\n * Collect interface DNS settings to be used in excluding NRPT rules. This is\n * needed so that local DNS keeps working even when a catch all NRPT rule is\n * installed by a VPN connection.\n *\n * @param  search_domains  optional list of search domains\n * @param  data            pointer to the data structures the values are returned in\n * @param  data_size       number of exclude data structures pointed to\n */\nstatic void\nGetNrptExcludeData(PCWSTR search_domains, nrpt_exclude_data_t *data, size_t data_size)\n{\n    HKEY v4_itfs = INVALID_HANDLE_VALUE;\n    HKEY v6_itfs = INVALID_HANDLE_VALUE;\n\n    if (!GetInterfacesKey(AF_INET, &v4_itfs) || !GetInterfacesKey(AF_INET6, &v6_itfs))\n    {\n        goto out;\n    }\n\n    size_t i = 0;\n    DWORD enum_index = 0;\n    while (i < data_size)\n    {\n        WCHAR itf_guid[MAX_PATH];\n        DWORD itf_guid_len = _countof(itf_guid);\n        LSTATUS err =\n            RegEnumKeyExW(v4_itfs, enum_index++, itf_guid, &itf_guid_len, NULL, NULL, NULL, NULL);\n        if (err)\n        {\n            if (err != ERROR_NO_MORE_ITEMS)\n            {\n                MsgToEventLog(M_SYSERR, L\"%S: could not enumerate interfaces (%lu)\", __func__, err);\n            }\n            goto out;\n        }\n\n        /* Ignore interfaces that are not connected or disabled */\n        if (!IsInterfaceConnected(itf_guid))\n        {\n            continue;\n        }\n\n        HKEY v4_itf;\n        if (RegOpenKeyExW(v4_itfs, itf_guid, 0, KEY_READ, &v4_itf) != NO_ERROR)\n        {\n            MsgToEventLog(M_SYSERR, L\"%S: could not open interface %s v4 registry key\", __func__,\n                          itf_guid);\n            goto out;\n        }\n\n        /* Get the DNS domain(s) for exclude routing */\n        data[i].domains_size = sizeof(data[0].domains);\n        memset(data[i].domains, 0, data[i].domains_size);\n        err = GetItfDnsDomains(v4_itf, search_domains, data[i].domains, &data[i].domains_size);\n        if (err)\n        {\n            if (err != ERROR_FILE_NOT_FOUND)\n            {\n                MsgToEventLog(M_SYSERR, L\"%S: could not read interface %s domain suffix\", __func__,\n                              itf_guid);\n            }\n            goto next_itf;\n        }\n\n        /* Get the IPv4 DNS servers */\n        DWORD v4_addrs_size = sizeof(data[0].addresses);\n        err = GetItfDnsServersV4(v4_itf, data[i].addresses, &v4_addrs_size);\n        if (err && err != ERROR_FILE_NOT_FOUND)\n        {\n            MsgToEventLog(M_SYSERR, L\"%S: could not read interface %s v4 name servers (%ld)\",\n                          __func__, itf_guid, err);\n            goto next_itf;\n        }\n\n        /* Get the IPv6 DNS servers, if there's space left */\n        PSTR v6_addrs = data[i].addresses + v4_addrs_size;\n        DWORD v6_addrs_size = sizeof(data[0].addresses) - v4_addrs_size;\n        if (v6_addrs_size > NRPT_ADDR_SIZE)\n        {\n            HKEY v6_itf;\n            if (RegOpenKeyExW(v6_itfs, itf_guid, 0, KEY_READ, &v6_itf) != NO_ERROR)\n            {\n                MsgToEventLog(M_SYSERR, L\"%S: could not open interface %s v6 registry key\",\n                              __func__, itf_guid);\n                goto next_itf;\n            }\n            err = GetItfDnsServersV6(v6_itf, v6_addrs, &v6_addrs_size);\n            RegCloseKey(v6_itf);\n            if (err && err != ERROR_FILE_NOT_FOUND)\n            {\n                MsgToEventLog(M_SYSERR, L\"%S: could not read interface %s v6 name servers (%ld)\",\n                              __func__, itf_guid, err);\n                goto next_itf;\n            }\n        }\n\n        if (v4_addrs_size || v6_addrs_size)\n        {\n            /* Replace delimiters with semicolons, as required by NRPT */\n            for (size_t j = 0; j < sizeof(data[0].addresses) && data[i].addresses[j]; j++)\n            {\n                if (data[i].addresses[j] == ',' || data[i].addresses[j] == ' ')\n                {\n                    data[i].addresses[j] = ';';\n                }\n            }\n            ++i;\n        }\n\nnext_itf:\n        RegCloseKey(v4_itf);\n    }\n\nout:\n    RegCloseKey(v6_itfs);\n    RegCloseKey(v4_itfs);\n}\n\n/**\n * Set a NRPT rule (subkey) and its values in the registry\n *\n * @param  nrpt_key   NRPT registry key handle\n * @param  subkey     subkey string to create\n * @param  address    name server address string\n * @param  domains    domains to resolve by this server as MULTI_SZ\n * @param  dom_size   size of domains in bytes including the terminators\n * @param  dnssec     boolean to determine if DNSSEC is to be enabled\n *\n * @return NO_ERROR on success, or Windows error code\n */\nstatic DWORD\nSetNrptRule(HKEY nrpt_key, PCWSTR subkey, PCSTR address, PCWSTR domains, DWORD dom_size,\n            BOOL dnssec)\n{\n    /* Create rule subkey */\n    DWORD err = NO_ERROR;\n    HKEY rule_key;\n    err = RegCreateKeyExW(nrpt_key, subkey, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &rule_key, NULL);\n    if (err)\n    {\n        return err;\n    }\n\n    /* Set name(s) for DNS routing */\n    err = RegSetValueExW(rule_key, L\"Name\", 0, REG_MULTI_SZ, (PBYTE)domains, dom_size);\n    if (err)\n    {\n        goto out;\n    }\n\n    /* Set DNS Server address */\n    err = RegSetValueExA(rule_key, \"GenericDNSServers\", 0, REG_SZ, (PBYTE)address,\n                         (DWORD)strlen(address) + 1);\n    if (err)\n    {\n        goto out;\n    }\n\n    DWORD reg_val;\n    /* Set DNSSEC if required */\n    if (dnssec)\n    {\n        reg_val = 1;\n        err = RegSetValueExA(rule_key, \"DNSSECValidationRequired\", 0, REG_DWORD, (PBYTE)&reg_val,\n                             sizeof(reg_val));\n        if (err)\n        {\n            goto out;\n        }\n\n        reg_val = 0;\n        err = RegSetValueExA(rule_key, \"DNSSECQueryIPSECRequired\", 0, REG_DWORD, (PBYTE)&reg_val,\n                             sizeof(reg_val));\n        if (err)\n        {\n            goto out;\n        }\n\n        reg_val = 0;\n        err = RegSetValueExA(rule_key, \"DNSSECQueryIPSECEncryption\", 0, REG_DWORD, (PBYTE)&reg_val,\n                             sizeof(reg_val));\n        if (err)\n        {\n            goto out;\n        }\n    }\n\n    /* Set NRPT config options */\n    reg_val = dnssec ? 0x0000000A : 0x00000008;\n    err = RegSetValueExA(rule_key, \"ConfigOptions\", 0, REG_DWORD, (const PBYTE)&reg_val,\n                         sizeof(reg_val));\n    if (err)\n    {\n        goto out;\n    }\n\n    /* Mandatory NRPT version */\n    reg_val = 2;\n    err = RegSetValueExA(rule_key, \"Version\", 0, REG_DWORD, (const PBYTE)&reg_val, sizeof(reg_val));\n    if (err)\n    {\n        goto out;\n    }\n\nout:\n    if (err)\n    {\n        RegDeleteKeyW(nrpt_key, subkey);\n    }\n    RegCloseKey(rule_key);\n    return err;\n}\n\n/**\n * Set NRPT exclude rules to accompany a catch all rule. This is done so that\n * local resolution of names is not interfered with in case the VPN resolves\n * all names.\n *\n * @param  nrpt_key        the registry key to set the rules under\n * @param  ovpn_pid        the PID of the openvpn process\n * @param  search_domains  optional list of search domains\n */\nstatic void\nSetNrptExcludeRules(HKEY nrpt_key, DWORD ovpn_pid, PCWSTR search_domains)\n{\n    nrpt_exclude_data_t data[8]; /* data from up to 8 interfaces */\n    memset(data, 0, sizeof(data));\n    GetNrptExcludeData(search_domains, data, _countof(data));\n\n    unsigned n = 0;\n    for (size_t i = 0; i < _countof(data); ++i)\n    {\n        nrpt_exclude_data_t *d = &data[i];\n        if (d->domains_size == 0)\n        {\n            break;\n        }\n\n        DWORD err;\n        WCHAR subkey[48];\n        swprintf(subkey, _countof(subkey), L\"OpenVPNDNSRoutingX-%02x-%lu\", ++n, ovpn_pid);\n        err = SetNrptRule(nrpt_key, subkey, d->addresses, d->domains, d->domains_size, FALSE);\n        if (err)\n        {\n            MsgToEventLog(M_ERR, L\"%S: failed to set rule %s (%lu)\", __func__, subkey, err);\n        }\n    }\n}\n\n/**\n * Set NRPT rules for a openvpn process\n *\n * @param  nrpt_key          the registry key to set the rules under\n * @param  addresses         name server addresses\n * @param  domains           optional list of split routing domains\n * @param  search_domains    optional list of search domains\n * @param  dnssec            boolean whether DNSSEC is to be used\n * @param  ovpn_pid          the PID of the openvpn process\n *\n * @return NO_ERROR on success, or a Windows error code\n */\nstatic DWORD\nSetNrptRules(HKEY nrpt_key, const nrpt_address_t *addresses, const char *domains,\n             const char *search_domains, BOOL dnssec, DWORD ovpn_pid)\n{\n    DWORD err = NO_ERROR;\n    PWSTR wide_domains = L\".\\0\"; /* DNS route everything by default */\n    DWORD dom_size = 6;\n\n    /* Prepare DNS routing domains / split DNS */\n    if (domains[0])\n    {\n        size_t domains_len = strlen(domains);\n        dom_size = (DWORD)domains_len + 2; /* len + the trailing NULs */\n\n        wide_domains = utf8to16_size(domains, dom_size);\n        dom_size *= sizeof(*wide_domains);\n        if (!wide_domains)\n        {\n            return ERROR_OUTOFMEMORY;\n        }\n        /* Make a MULTI_SZ from a comma separated list */\n        for (size_t i = 0; i < domains_len; ++i)\n        {\n            if (wide_domains[i] == ',')\n            {\n                wide_domains[i] = 0;\n            }\n        }\n    }\n    else\n    {\n        PWSTR wide_search_domains;\n        wide_search_domains = utf8to16(search_domains);\n        if (!wide_search_domains)\n        {\n            return ERROR_OUTOFMEMORY;\n        }\n        SetNrptExcludeRules(nrpt_key, ovpn_pid, wide_search_domains);\n        free(wide_search_domains);\n    }\n\n    /* Create address string list */\n    CHAR addr_list[NRPT_ADDR_NUM * NRPT_ADDR_SIZE];\n    PSTR pos = addr_list;\n    for (int i = 0; i < NRPT_ADDR_NUM && addresses[i][0]; ++i)\n    {\n        if (i != 0)\n        {\n            *pos++ = ';';\n        }\n        strcpy(pos, addresses[i]);\n        pos += strlen(pos);\n    }\n\n    WCHAR subkey[MAX_PATH];\n    swprintf(subkey, _countof(subkey), L\"OpenVPNDNSRouting-%lu\", ovpn_pid);\n    err = SetNrptRule(nrpt_key, subkey, addr_list, wide_domains, dom_size, dnssec);\n    if (err)\n    {\n        MsgToEventLog(M_ERR, L\"%S: failed to set rule %s (%lu)\", __func__, subkey, err);\n    }\n\n    if (domains[0])\n    {\n        free(wide_domains);\n    }\n    return err;\n}\n\n/**\n * Return the registry key where NRPT rules are stored\n *\n * @param  key        pointer to the HKEY it is returned in\n * @param  gpol       pointer to BOOL the use of GPOL hive is returned in\n *\n * @return NO_ERROR on success, or a Windows error code\n */\nstatic LSTATUS\nOpenNrptBaseKey(PHKEY key, PBOOL gpol)\n{\n    /*\n     * Registry keys Name Service Policy Table (NRPT) rules can be stored at.\n     * When the group policy key exists, NRPT rules must be placed there.\n     * It is created when NRPT rules are pushed via group policy and it\n     * remains in the registry even if the last GP-NRPT rule is deleted.\n     */\n    static PCSTR gpol_key = \"SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows NT\\\\DNSClient\\\\DnsPolicyConfig\";\n    static PCSTR sys_key =\n        \"SYSTEM\\\\CurrentControlSet\\\\Services\\\\Dnscache\\\\Parameters\\\\DnsPolicyConfig\";\n\n    HKEY nrpt;\n    *gpol = TRUE;\n    LSTATUS err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, gpol_key, 0, KEY_ALL_ACCESS, &nrpt);\n    if (err == ERROR_FILE_NOT_FOUND)\n    {\n        *gpol = FALSE;\n        err = RegCreateKeyExA(HKEY_LOCAL_MACHINE, sys_key, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &nrpt,\n                              NULL);\n        if (err)\n        {\n            nrpt = INVALID_HANDLE_VALUE;\n        }\n    }\n    *key = nrpt;\n    return err;\n}\n\n/**\n * Delete OpenVPN NRPT rules from the registry\n *\n * If the pid parameter is 0 all NRPT rules added by OpenVPN are deleted.\n * In all other cases only rules matching the pid are deleted.\n *\n * @param  pid        PID of the process to delete the rules for or 0\n * @param  gpol\n *\n * @return BOOL to indicate if rules were deleted\n */\nstatic BOOL\nDeleteNrptRules(DWORD pid, PBOOL gpol)\n{\n    HKEY key;\n    LSTATUS err = OpenNrptBaseKey(&key, gpol);\n    if (err)\n    {\n        MsgToEventLog(M_SYSERR, L\"%S: could not open NRPT base key (%lu)\", __func__, err);\n        return FALSE;\n    }\n\n    /* PID suffix string to compare against later */\n    WCHAR pid_str[16];\n    size_t pidlen = 0;\n    if (pid)\n    {\n        swprintf(pid_str, _countof(pid_str), L\"-%lu\", pid);\n        pidlen = wcslen(pid_str);\n    }\n\n    int deleted = 0;\n    DWORD enum_index = 0;\n    while (TRUE)\n    {\n        WCHAR name[MAX_PATH];\n        DWORD namelen = _countof(name);\n        err = RegEnumKeyExW(key, enum_index++, name, &namelen, NULL, NULL, NULL, NULL);\n        if (err)\n        {\n            if (err != ERROR_NO_MORE_ITEMS)\n            {\n                MsgToEventLog(M_SYSERR, L\"%S: could not enumerate NRPT rules (%lu)\", __func__, err);\n            }\n            break;\n        }\n\n        /* Keep rule if name doesn't match */\n        if (wcsncmp(name, L\"OpenVPNDNSRouting\", 17) != 0\n            || (pid && wcsncmp(name + namelen - pidlen, pid_str, pidlen) != 0))\n        {\n            continue;\n        }\n\n        if (RegDeleteKeyW(key, name) == NO_ERROR)\n        {\n            enum_index--;\n            deleted++;\n        }\n    }\n\n    RegCloseKey(key);\n    return deleted ? TRUE : FALSE;\n}\n\n/**\n * Delete a process' NRPT rules and apply the reduced set of rules\n *\n * @param ovpn_pid  OpenVPN process id to delete rules for\n */\nstatic void\nUndoNrptRules(DWORD ovpn_pid)\n{\n    BOOL gpol;\n    if (DeleteNrptRules(ovpn_pid, &gpol))\n    {\n        ApplyDnsSettings(gpol);\n    }\n}\n\n/**\n * Add Name Resolution Policy Table (NRPT) rules as documented in\n * https://msdn.microsoft.com/en-us/library/ff957356.aspx for DNS name\n * resolution, as well as DNS search domain(s), if given.\n *\n * @param  msg        config messages sent by the openvpn process\n * @param  ovpn_pid   process id of the sending openvpn process\n * @param  lists      undo lists for this process\n *\n * @return NO_ERROR on success, or a Windows error code\n */\nstatic DWORD\nHandleDNSConfigNrptMessage(const nrpt_dns_cfg_message_t *msg, DWORD ovpn_pid, undo_lists_t *lists)\n{\n    /*\n     * Use a non-const reference with limited scope to\n     * enforce null-termination of strings from client\n     */\n    {\n        nrpt_dns_cfg_message_t *msgptr = (nrpt_dns_cfg_message_t *)msg;\n        msgptr->iface.name[_countof(msg->iface.name) - 1] = '\\0';\n        msgptr->search_domains[_countof(msg->search_domains) - 1] = '\\0';\n        msgptr->resolve_domains[_countof(msg->resolve_domains) - 1] = '\\0';\n        for (size_t i = 0; i < NRPT_ADDR_NUM; ++i)\n        {\n            msgptr->addresses[i][_countof(msg->addresses[0]) - 1] = '\\0';\n        }\n    }\n\n    /* Make sure we have the VPN interface name */\n    if (msg->iface.name[0] == 0)\n    {\n        return ERROR_MESSAGE_DATA;\n    }\n\n    /* Some sanity checks on the add message data */\n    if (msg->header.type == msg_add_nrpt_cfg)\n    {\n        /* At least one name server address is set */\n        if (msg->addresses[0][0] == 0)\n        {\n            return ERROR_MESSAGE_DATA;\n        }\n        /* Resolve domains are double zero terminated (MULTI_SZ) */\n        const char *rdom = msg->resolve_domains;\n        size_t rdom_size = sizeof(msg->resolve_domains);\n        size_t rdom_len = strlen(rdom);\n        if (rdom_len && (rdom_len + 1 >= rdom_size || rdom[rdom_len + 2] != 0))\n        {\n            return ERROR_MESSAGE_DATA;\n        }\n    }\n\n    BOOL gpol_nrpt = FALSE;\n    BOOL gpol_list = FALSE;\n\n    WCHAR iid[64];\n    DWORD iid_err = InterfaceIdString(msg->iface.name, iid, _countof(iid));\n    if (iid_err)\n    {\n        return iid_err;\n    }\n\n    /* Delete previously set values for this instance first, if any */\n    PDWORD undo_pid = RemoveListItem(&(*lists)[undo_nrpt], CmpAny, NULL);\n    if (undo_pid)\n    {\n        if (*undo_pid != ovpn_pid)\n        {\n            MsgToEventLog(M_INFO,\n                          L\"%S: PID stored for undo doesn't match: %lu vs %lu. \"\n                          \"This is likely an error. Cleaning up anyway.\",\n                          __func__, *undo_pid, ovpn_pid);\n        }\n        DeleteNrptRules(*undo_pid, &gpol_nrpt);\n        free(undo_pid);\n\n        ResetNameServers(iid, AF_INET);\n        ResetNameServers(iid, AF_INET6);\n    }\n    SetDnsSearchDomains(msg->iface.name, NULL, &gpol_list, lists);\n\n    if (msg->header.type == msg_del_nrpt_cfg)\n    {\n        ApplyDnsSettings(gpol_nrpt || gpol_list);\n        return NO_ERROR; /* Done dealing with del message */\n    }\n\n    HKEY key;\n    LSTATUS err = OpenNrptBaseKey(&key, &gpol_nrpt);\n    if (err)\n    {\n        goto out;\n    }\n\n    /* Add undo information first in case there's no heap left */\n    PDWORD pid = malloc(sizeof(ovpn_pid));\n    if (!pid)\n    {\n        err = ERROR_OUTOFMEMORY;\n        goto out;\n    }\n    *pid = ovpn_pid;\n    if (AddListItem(&(*lists)[undo_nrpt], pid))\n    {\n        err = ERROR_OUTOFMEMORY;\n        free(pid);\n        goto out;\n    }\n\n    /* Set NRPT rules */\n    BOOL dnssec = (msg->flags & nrpt_dnssec) != 0;\n    err = SetNrptRules(key, msg->addresses, msg->resolve_domains, msg->search_domains, dnssec,\n                       ovpn_pid);\n    if (err)\n    {\n        goto out;\n    }\n\n    /*\n     * Set DNS on the adapter for search domains to be considered.\n     * If split DNS is configured, do this only when search domains\n     * are given, so that look-ups for other domains do not go over\n     * the VPN all the time.\n     */\n    if (msg->search_domains[0] || !msg->resolve_domains[0])\n    {\n        err = SetNameServerAddresses(iid, msg->addresses);\n        if (err)\n        {\n            goto out;\n        }\n    }\n\n    /* Set search domains, if any */\n    if (msg->search_domains[0])\n    {\n        err = SetDnsSearchDomains(msg->iface.name, msg->search_domains, &gpol_list, lists);\n    }\n\n    ApplyDnsSettings(gpol_nrpt || gpol_list);\n\nout:\n    return err;\n}\n\nstatic DWORD\nHandleWINSConfigMessage(const wins_cfg_message_t *msg, undo_lists_t *lists)\n{\n    DWORD err = NO_ERROR;\n    wchar_t addr[16]; /* large enough to hold string representation of an ipv4 */\n    unsigned int addr_len = msg->addr_len;\n\n    /* sanity check */\n    if (addr_len > _countof(msg->addr))\n    {\n        addr_len = _countof(msg->addr);\n    }\n\n    if (!msg->iface.index) /* interface index is required */\n    {\n        return ERROR_MESSAGE_DATA;\n    }\n\n    /* We delete all current addresses before adding any\n     * OR if the message type is del_wins_cfg\n     */\n    if (addr_len > 0 || msg->header.type == msg_del_wins_cfg)\n    {\n        err = netsh_wins_cmd(L\"delete\", msg->iface.index, NULL);\n        if (err)\n        {\n            goto out;\n        }\n        free(RemoveListItem(&(*lists)[undo_wins], CmpAny, NULL));\n    }\n\n    if (addr_len == 0 || msg->header.type == msg_del_wins_cfg)\n    {\n        goto out; /* job done */\n    }\n\n    for (unsigned int i = 0; i < addr_len; ++i)\n    {\n        RtlIpv4AddressToStringW(&msg->addr[i].ipv4, addr);\n        err = netsh_wins_cmd(i == 0 ? L\"set\" : L\"add\", msg->iface.index, addr);\n        if (i == 0 && err)\n        {\n            goto out;\n        }\n        /* We do not check for duplicate addresses, so any error in adding\n         * additional addresses is ignored.\n         */\n    }\n\n    PDWORD if_index = malloc(sizeof(msg->iface.index));\n    if (if_index)\n    {\n        *if_index = msg->iface.index;\n    }\n\n    if (!if_index || AddListItem(&(*lists)[undo_wins], if_index))\n    {\n        free(if_index);\n        netsh_wins_cmd(L\"delete\", msg->iface.index, NULL);\n        err = ERROR_OUTOFMEMORY;\n        goto out;\n    }\n\n    err = 0;\n\nout:\n    return err;\n}\n\nstatic DWORD\nHandleEnableDHCPMessage(const enable_dhcp_message_t *dhcp)\n{\n    DWORD err = 0;\n    DWORD timeout = 5000; /* in milli seconds */\n    wchar_t argv0[MAX_PATH];\n\n    /* Path of netsh */\n    swprintf(argv0, _countof(argv0), L\"%ls\\\\%ls\", get_win_sys_path(), L\"netsh.exe\");\n\n    /* cmd template:\n     * netsh interface ipv4 set address name=$if_index source=dhcp\n     */\n    const wchar_t *fmt = L\"netsh interface ipv4 set address name=\\\"%lu\\\" source=dhcp\";\n\n    /* max cmdline length in wchars -- include room for if index:\n     * 10 chars for 32 bit int in decimal and +1 for NUL\n     */\n    size_t ncmdline = wcslen(fmt) + 10 + 1;\n    wchar_t *cmdline = malloc(ncmdline * sizeof(wchar_t));\n    if (!cmdline)\n    {\n        err = ERROR_OUTOFMEMORY;\n        return err;\n    }\n\n    swprintf(cmdline, ncmdline, fmt, dhcp->iface.index);\n\n    err = ExecCommand(argv0, cmdline, timeout);\n\n    /* Note: This could fail if dhcp is already enabled, so the caller\n     * may not want to treat errors as FATAL.\n     */\n\n    free(cmdline);\n    return err;\n}\n\nstatic DWORD\nHandleMTUMessage(const set_mtu_message_t *mtu)\n{\n    DWORD err = 0;\n    MIB_IPINTERFACE_ROW ipiface;\n    InitializeIpInterfaceEntry(&ipiface);\n    ipiface.Family = mtu->family;\n    ipiface.InterfaceIndex = mtu->iface.index;\n    err = GetIpInterfaceEntry(&ipiface);\n    if (err != NO_ERROR)\n    {\n        return err;\n    }\n    if (mtu->family == AF_INET)\n    {\n        ipiface.SitePrefixLength = 0;\n    }\n    ipiface.NlMtu = mtu->mtu;\n\n    err = SetIpInterfaceEntry(&ipiface);\n    return err;\n}\n\n/**\n * Creates a VPN adapter of the specified type by invoking tapctl.exe.\n *\n * @param msg Adapter creation request specifying the type.\n *\n * @return NO_ERROR on success, otherwise a Windows error code.\n */\nstatic DWORD\nHandleCreateAdapterMessage(const create_adapter_message_t *msg)\n{\n    const WCHAR *hwid;\n\n    switch (msg->adapter_type)\n    {\n        case ADAPTER_TYPE_DCO:\n            hwid = L\"ovpn-dco\";\n            break;\n\n        case ADAPTER_TYPE_TAP:\n            hwid = L\"root\\\\tap0901\";\n            break;\n\n        default:\n            return ERROR_INVALID_PARAMETER;\n    }\n\n    WCHAR cmd[MAX_PATH];\n    WCHAR args[MAX_PATH];\n\n    if (swprintf_s(cmd, _countof(cmd), L\"%s\\\\tapctl.exe\", settings.bin_dir) < 0)\n    {\n        return ERROR_BUFFER_OVERFLOW;\n    }\n\n    if (swprintf_s(args, _countof(args), L\"tapctl create --hwid %s\", hwid) < 0)\n    {\n        return ERROR_BUFFER_OVERFLOW;\n    }\n\n    return ExecCommand(cmd, args, 10000);\n}\n\nstatic VOID\nHandleMessage(HANDLE pipe, PPROCESS_INFORMATION proc_info, DWORD bytes, DWORD count,\n              LPHANDLE events, undo_lists_t *lists)\n{\n    pipe_message_t msg;\n    ack_message_t ack = {\n        .header = { .type = msg_acknowledgement, .size = sizeof(ack), .message_id = -1 },\n        .error_number = ERROR_MESSAGE_DATA\n    };\n\n    DWORD read = ReadPipeAsync(pipe, &msg, bytes, count, events);\n    if (read != bytes || read < sizeof(msg.header) || read != msg.header.size)\n    {\n        goto out;\n    }\n\n    ack.header.message_id = msg.header.message_id;\n\n    switch (msg.header.type)\n    {\n        case msg_add_address:\n        case msg_del_address:\n            if (msg.header.size == sizeof(msg.address))\n            {\n                ack.error_number = HandleAddressMessage(&msg.address, lists);\n            }\n            break;\n\n        case msg_add_route:\n        case msg_del_route:\n            if (msg.header.size == sizeof(msg.route))\n            {\n                ack.error_number = HandleRouteMessage(&msg.route, lists);\n            }\n            break;\n\n        case msg_flush_neighbors:\n            if (msg.header.size == sizeof(msg.flush_neighbors))\n            {\n                ack.error_number = HandleFlushNeighborsMessage(&msg.flush_neighbors);\n            }\n            break;\n\n        case msg_add_wfp_block:\n        case msg_del_wfp_block:\n            if (msg.header.size == sizeof(msg.wfp_block))\n            {\n                ack.error_number = HandleWfpBlockMessage(&msg.wfp_block, lists);\n            }\n            break;\n\n        case msg_register_dns:\n            ack.error_number = HandleRegisterDNSMessage();\n            break;\n\n        case msg_add_dns_cfg:\n        case msg_del_dns_cfg:\n            ack.error_number = HandleDNSConfigMessage(&msg.dns, lists);\n            break;\n\n        case msg_add_nrpt_cfg:\n        case msg_del_nrpt_cfg:\n        {\n            DWORD ovpn_pid = proc_info->dwProcessId;\n            ack.error_number = HandleDNSConfigNrptMessage(&msg.nrpt_dns, ovpn_pid, lists);\n        }\n        break;\n\n        case msg_add_wins_cfg:\n        case msg_del_wins_cfg:\n            ack.error_number = HandleWINSConfigMessage(&msg.wins, lists);\n            break;\n\n        case msg_enable_dhcp:\n            if (msg.header.size == sizeof(msg.dhcp))\n            {\n                ack.error_number = HandleEnableDHCPMessage(&msg.dhcp);\n            }\n            break;\n\n        case msg_set_mtu:\n            if (msg.header.size == sizeof(msg.mtu))\n            {\n                ack.error_number = HandleMTUMessage(&msg.mtu);\n            }\n            break;\n\n        case msg_create_adapter:\n            if (msg.header.size == sizeof(msg.create_adapter))\n            {\n                ack.error_number = HandleCreateAdapterMessage(&msg.create_adapter);\n            }\n            break;\n\n        default:\n            ack.error_number = ERROR_MESSAGE_TYPE;\n            MsgToEventLog(MSG_FLAGS_ERROR, L\"Unknown message type %d\", msg.header.type);\n            break;\n    }\n\nout:\n    WritePipeAsync(pipe, &ack, sizeof(ack), count, events);\n}\n\n\nstatic VOID\nUndo(undo_lists_t *lists)\n{\n    undo_type_t type;\n    wfp_block_data_t *interface_data;\n    for (type = 0; type < _undo_type_max; type++)\n    {\n        list_item_t **pnext = &(*lists)[type];\n        while (*pnext)\n        {\n            list_item_t *item = *pnext;\n            switch (type)\n            {\n                case address:\n                    DeleteAddress(item->data);\n                    break;\n\n                case route:\n                    DeleteRoute(item->data);\n                    break;\n\n                case undo_dns4:\n                    ResetNameServers(item->data, AF_INET);\n                    break;\n\n                case undo_dns6:\n                    ResetNameServers(item->data, AF_INET6);\n                    break;\n\n                case undo_nrpt:\n                    UndoNrptRules(*(PDWORD)item->data);\n                    break;\n\n                case undo_domains:\n                    UndoDnsSearchDomains(item->data);\n                    break;\n\n                case undo_wins:\n                    netsh_wins_cmd(L\"delete\", *(PDWORD)item->data, NULL);\n                    break;\n\n                case wfp_block:\n                    interface_data = (wfp_block_data_t *)(item->data);\n                    delete_wfp_block_filters(interface_data->engine);\n                    if (interface_data->metric_v4 >= 0)\n                    {\n                        set_interface_metric(interface_data->index, AF_INET,\n                                             interface_data->metric_v4);\n                    }\n                    if (interface_data->metric_v6 >= 0)\n                    {\n                        set_interface_metric(interface_data->index, AF_INET6,\n                                             interface_data->metric_v6);\n                    }\n                    break;\n\n                case _undo_type_max:\n                    /* unreachable */\n                    break;\n            }\n\n            /* Remove from the list and free memory */\n            *pnext = item->next;\n            free(item->data);\n            free(item);\n        }\n    }\n}\n\nstatic DWORD WINAPI\nRunOpenvpn(LPVOID p)\n{\n    HANDLE pipe = p;\n    HANDLE ovpn_pipe = NULL, svc_pipe = NULL;\n    PTOKEN_USER svc_user = NULL, ovpn_user = NULL;\n    HANDLE svc_token = NULL, imp_token = NULL, pri_token = NULL;\n    HANDLE stdin_read = NULL, stdin_write = NULL;\n    HANDLE stdout_write = NULL;\n    DWORD pipe_mode, len, exit_code = 0;\n    STARTUP_DATA sud = { 0, 0, 0 };\n    STARTUPINFOW startup_info;\n    PROCESS_INFORMATION proc_info;\n    LPVOID user_env = NULL;\n    WCHAR ovpn_pipe_name[256]; /* The entire pipe name string can be up to 256 characters long\n                                  according to MSDN. */\n    LPCWSTR exe_path;\n    WCHAR *cmdline = NULL;\n    size_t cmdline_size;\n    undo_lists_t undo_lists;\n    WCHAR errmsg[512] = L\"\";\n    BOOL flush_pipe = TRUE;\n\n    SECURITY_ATTRIBUTES inheritable = { .nLength = sizeof(inheritable),\n                                        .lpSecurityDescriptor = NULL,\n                                        .bInheritHandle = TRUE };\n\n    PACL ovpn_dacl;\n    EXPLICIT_ACCESS ea[2];\n    SECURITY_DESCRIPTOR ovpn_sd;\n    SECURITY_ATTRIBUTES ovpn_sa = { .nLength = sizeof(ovpn_sa),\n                                    .lpSecurityDescriptor = &ovpn_sd,\n                                    .bInheritHandle = FALSE };\n\n    ZeroMemory(&ea, sizeof(ea));\n    ZeroMemory(&startup_info, sizeof(startup_info));\n    ZeroMemory(&undo_lists, sizeof(undo_lists));\n    ZeroMemory(&proc_info, sizeof(proc_info));\n\n    if (!GetStartupData(pipe, &sud))\n    {\n        flush_pipe = FALSE; /* client did not provide startup data */\n        goto out;\n    }\n\n    if (!InitializeSecurityDescriptor(&ovpn_sd, SECURITY_DESCRIPTOR_REVISION))\n    {\n        ReturnLastError(pipe, L\"InitializeSecurityDescriptor\");\n        goto out;\n    }\n\n    /* Get SID of user the service is running under */\n    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &svc_token))\n    {\n        ReturnLastError(pipe, L\"OpenProcessToken\");\n        goto out;\n    }\n    len = 0;\n    while (!GetTokenInformation(svc_token, TokenUser, svc_user, len, &len))\n    {\n        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)\n        {\n            ReturnLastError(pipe, L\"GetTokenInformation (service token)\");\n            goto out;\n        }\n        free(svc_user);\n        svc_user = malloc(len);\n        if (svc_user == NULL)\n        {\n            ReturnLastError(pipe, L\"malloc (service token user)\");\n            goto out;\n        }\n    }\n    if (!IsValidSid(svc_user->User.Sid))\n    {\n        ReturnLastError(pipe, L\"IsValidSid (service token user)\");\n        goto out;\n    }\n\n    if (!ImpersonateNamedPipeClient(pipe))\n    {\n        ReturnLastError(pipe, L\"ImpersonateNamedPipeClient\");\n        goto out;\n    }\n    if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &imp_token))\n    {\n        ReturnLastError(pipe, L\"OpenThreadToken\");\n        goto out;\n    }\n    len = 0;\n    while (!GetTokenInformation(imp_token, TokenUser, ovpn_user, len, &len))\n    {\n        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)\n        {\n            ReturnLastError(pipe, L\"GetTokenInformation (impersonation token)\");\n            goto out;\n        }\n        free(ovpn_user);\n        ovpn_user = malloc(len);\n        if (ovpn_user == NULL)\n        {\n            ReturnLastError(pipe, L\"malloc (impersonation token user)\");\n            goto out;\n        }\n    }\n    if (!IsValidSid(ovpn_user->User.Sid))\n    {\n        ReturnLastError(pipe, L\"IsValidSid (impersonation token user)\");\n        goto out;\n    }\n\n    /*\n     * Only authorized users are allowed to use any command line options or\n     * have the config file in locations other than the global config directory.\n     *\n     * Check options are white-listed and config is in the global directory\n     * OR user is authorized to run any config.\n     */\n    if (!ValidateOptions(pipe, sud.directory, sud.options, errmsg, _countof(errmsg))\n        && !IsAuthorizedUser(ovpn_user->User.Sid, imp_token, settings.ovpn_admin_group,\n                             settings.ovpn_service_user))\n    {\n        ReturnError(pipe, ERROR_STARTUP_DATA, errmsg, 1, &exit_event);\n        goto out;\n    }\n\n    /* OpenVPN process DACL entry for access by service and user */\n    ea[0].grfAccessPermissions = SPECIFIC_RIGHTS_ALL | STANDARD_RIGHTS_ALL;\n    ea[0].grfAccessMode = SET_ACCESS;\n    ea[0].grfInheritance = NO_INHERITANCE;\n    ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;\n    ea[0].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;\n    ea[0].Trustee.ptstrName = (LPWSTR)svc_user->User.Sid;\n    ea[1].grfAccessPermissions = READ_CONTROL | SYNCHRONIZE | PROCESS_VM_READ | SYNCHRONIZE\n                                 | PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION;\n    ea[1].grfAccessMode = SET_ACCESS;\n    ea[1].grfInheritance = NO_INHERITANCE;\n    ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;\n    ea[1].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;\n    ea[1].Trustee.ptstrName = (LPWSTR)ovpn_user->User.Sid;\n\n    /* Set owner and DACL of OpenVPN security descriptor */\n    if (!SetSecurityDescriptorOwner(&ovpn_sd, svc_user->User.Sid, FALSE))\n    {\n        ReturnLastError(pipe, L\"SetSecurityDescriptorOwner\");\n        goto out;\n    }\n    if (SetEntriesInAcl(2, ea, NULL, &ovpn_dacl) != ERROR_SUCCESS)\n    {\n        ReturnLastError(pipe, L\"SetEntriesInAcl\");\n        goto out;\n    }\n    if (!SetSecurityDescriptorDacl(&ovpn_sd, TRUE, ovpn_dacl, FALSE))\n    {\n        ReturnLastError(pipe, L\"SetSecurityDescriptorDacl\");\n        goto out;\n    }\n\n    /* Create primary token from impersonation token */\n    if (!DuplicateTokenEx(imp_token, TOKEN_ALL_ACCESS, NULL, 0, TokenPrimary, &pri_token))\n    {\n        ReturnLastError(pipe, L\"DuplicateTokenEx\");\n        goto out;\n    }\n\n    /* use /dev/null for stdout of openvpn (client should use --log for output) */\n    stdout_write = CreateFile(_L(\"NUL\"), GENERIC_WRITE, FILE_SHARE_WRITE, &inheritable,\n                              OPEN_EXISTING, 0, NULL);\n    if (stdout_write == INVALID_HANDLE_VALUE)\n    {\n        ReturnLastError(pipe, L\"CreateFile for stdout\");\n        goto out;\n    }\n\n    if (!CreatePipe(&stdin_read, &stdin_write, &inheritable, 0)\n        || !SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0))\n    {\n        ReturnLastError(pipe, L\"CreatePipe\");\n        goto out;\n    }\n\n    UUID pipe_uuid;\n    RPC_STATUS rpc_stat = UuidCreate(&pipe_uuid);\n    if (rpc_stat != RPC_S_OK)\n    {\n        ReturnError(pipe, rpc_stat, L\"UuidCreate\", 1, &exit_event);\n        goto out;\n    }\n\n    RPC_WSTR pipe_uuid_str = NULL;\n    rpc_stat = UuidToStringW(&pipe_uuid, &pipe_uuid_str);\n    if (rpc_stat != RPC_S_OK)\n    {\n        ReturnError(pipe, rpc_stat, L\"UuidToString\", 1, &exit_event);\n        goto out;\n    }\n    swprintf(ovpn_pipe_name, _countof(ovpn_pipe_name),\n             L\"\\\\\\\\.\\\\pipe\\\\\" _L(PACKAGE) L\"%ls\\\\service_%lu_%ls\", service_instance,\n             GetCurrentThreadId(), pipe_uuid_str);\n    RpcStringFree(&pipe_uuid_str);\n\n    /* make a security descriptor for the named pipe with access\n     * restricted to the user and SYSTEM\n     */\n\n    SECURITY_ATTRIBUTES sa;\n    PSECURITY_DESCRIPTOR pSD = NULL;\n    LPCWSTR szSDDL = L\"D:(A;;GA;;;SY)(A;;GA;;;OW)\";\n    if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(\n            szSDDL, SDDL_REVISION_1, &pSD, NULL))\n    {\n        ReturnLastError(pipe, L\"ConvertSDDL\");\n        goto out;\n    }\n    sa.nLength = sizeof(sa);\n    sa.lpSecurityDescriptor = pSD;\n    sa.bInheritHandle = FALSE;\n\n    ovpn_pipe = CreateNamedPipe(\n        ovpn_pipe_name, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,\n        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, 128, 128, 0, &sa);\n    if (ovpn_pipe == INVALID_HANDLE_VALUE)\n    {\n        ReturnLastError(pipe, L\"CreateNamedPipe\");\n        goto out;\n    }\n\n    svc_pipe = CreateFile(ovpn_pipe_name, GENERIC_READ | GENERIC_WRITE, 0, &inheritable,\n                          OPEN_EXISTING, 0, NULL);\n    if (svc_pipe == INVALID_HANDLE_VALUE)\n    {\n        ReturnLastError(pipe, L\"CreateFile\");\n        goto out;\n    }\n\n    pipe_mode = PIPE_READMODE_MESSAGE;\n    if (!SetNamedPipeHandleState(svc_pipe, &pipe_mode, NULL, NULL))\n    {\n        ReturnLastError(pipe, L\"SetNamedPipeHandleState\");\n        goto out;\n    }\n\n    cmdline_size = wcslen(sud.options) + 128;\n    cmdline = malloc(cmdline_size * sizeof(*cmdline));\n    if (cmdline == NULL)\n    {\n        ReturnLastError(pipe, L\"malloc\");\n        goto out;\n    }\n    /* there seem to be no common printf specifier that works on all\n     * mingw/msvc platforms without trickery, so convert to void* and use\n     * PRIuPTR to print that as best compromise */\n    swprintf(cmdline, cmdline_size, L\"openvpn %ls --msg-channel %\" PRIuPTR, sud.options,\n             (uintptr_t)svc_pipe);\n\n    if (!CreateEnvironmentBlock(&user_env, imp_token, FALSE))\n    {\n        ReturnLastError(pipe, L\"CreateEnvironmentBlock\");\n        goto out;\n    }\n\n    startup_info.cb = sizeof(startup_info);\n    startup_info.dwFlags = STARTF_USESTDHANDLES;\n    startup_info.hStdInput = stdin_read;\n    startup_info.hStdOutput = stdout_write;\n    startup_info.hStdError = stdout_write;\n\n    exe_path = settings.exe_path;\n\n    /* TODO: make sure HKCU is correct or call LoadUserProfile() */\n    if (!CreateProcessAsUserW(pri_token, exe_path, cmdline, &ovpn_sa, NULL, TRUE,\n                              settings.priority | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,\n                              user_env, sud.directory, &startup_info, &proc_info))\n    {\n        ReturnLastError(pipe, L\"CreateProcessAsUser\");\n        goto out;\n    }\n\n    if (!RevertToSelf())\n    {\n        TerminateProcess(proc_info.hProcess, 1);\n        ReturnLastError(pipe, L\"RevertToSelf\");\n        goto out;\n    }\n\n    ReturnProcessId(pipe, proc_info.dwProcessId, 1, &exit_event);\n\n    CloseHandleEx(&stdout_write);\n    CloseHandleEx(&stdin_read);\n    CloseHandleEx(&svc_pipe);\n\n    DWORD input_size = WideCharToMultiByte(CP_UTF8, 0, sud.std_input, -1, NULL, 0, NULL, NULL);\n    LPSTR input = NULL;\n    if (input_size && (input = malloc(input_size)))\n    {\n        DWORD written;\n        WideCharToMultiByte(CP_UTF8, 0, sud.std_input, -1, input, input_size, NULL, NULL);\n        WriteFile(stdin_write, input, (DWORD)strlen(input), &written, NULL);\n        free(input);\n    }\n\n    while (TRUE)\n    {\n        DWORD bytes = PeekNamedPipeAsync(ovpn_pipe, 1, &exit_event);\n        if (bytes == 0)\n        {\n            break;\n        }\n\n        if (bytes > sizeof(pipe_message_t))\n        {\n            /* process at the other side of the pipe is misbehaving, shut it down */\n            MsgToEventLog(\n                MSG_FLAGS_ERROR,\n                L\"OpenVPN process sent too large payload length to the pipe (%lu bytes), it will be terminated\",\n                bytes);\n            break;\n        }\n\n        HandleMessage(ovpn_pipe, &proc_info, bytes, 1, &exit_event, &undo_lists);\n    }\n\n    WaitForSingleObject(proc_info.hProcess, IO_TIMEOUT);\n    GetExitCodeProcess(proc_info.hProcess, &exit_code);\n    if (exit_code == STILL_ACTIVE)\n    {\n        TerminateProcess(proc_info.hProcess, 1);\n    }\n    else if (exit_code != 0)\n    {\n        WCHAR buf[256];\n        swprintf(buf, _countof(buf), L\"OpenVPN exited with error: exit code = %lu\", exit_code);\n        ReturnError(pipe, ERROR_OPENVPN_STARTUP, buf, 1, &exit_event);\n    }\n    Undo(&undo_lists);\n\nout:\n    if (flush_pipe)\n    {\n        FlushFileBuffers(pipe);\n    }\n    DisconnectNamedPipe(pipe);\n\n    free(ovpn_user);\n    free(svc_user);\n    free(cmdline);\n    DestroyEnvironmentBlock(user_env);\n    FreeStartupData(&sud);\n    CloseHandleEx(&proc_info.hProcess);\n    CloseHandleEx(&proc_info.hThread);\n    CloseHandleEx(&stdin_read);\n    CloseHandleEx(&stdin_write);\n    CloseHandleEx(&stdout_write);\n    CloseHandleEx(&svc_token);\n    CloseHandleEx(&imp_token);\n    CloseHandleEx(&pri_token);\n    CloseHandleEx(&ovpn_pipe);\n    CloseHandleEx(&svc_pipe);\n    CloseHandleEx(&pipe);\n\n    return 0;\n}\n\n\nstatic DWORD WINAPI\nServiceCtrlInteractive(DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx)\n{\n    SERVICE_STATUS *status = ctx;\n    switch (ctrl_code)\n    {\n        case SERVICE_CONTROL_STOP:\n            status->dwCurrentState = SERVICE_STOP_PENDING;\n            ReportStatusToSCMgr(service, status);\n            if (exit_event)\n            {\n                SetEvent(exit_event);\n            }\n            return NO_ERROR;\n\n        case SERVICE_CONTROL_INTERROGATE:\n            return NO_ERROR;\n\n        default:\n            return ERROR_CALL_NOT_IMPLEMENTED;\n    }\n}\n\n\nstatic HANDLE\nCreateClientPipeInstance(VOID)\n{\n    /*\n     * allow all access for local system\n     * deny FILE_CREATE_PIPE_INSTANCE for everyone\n     * allow read/write for authenticated users\n     * deny all access to anonymous\n     */\n    const WCHAR *sddlString =\n        L\"D:(A;OICI;GA;;;S-1-5-18)(D;OICI;0x4;;;S-1-1-0)(A;OICI;GRGW;;;S-1-5-11)(D;;GA;;;S-1-5-7)\";\n\n    PSECURITY_DESCRIPTOR sd = NULL;\n    if (!ConvertStringSecurityDescriptorToSecurityDescriptor(sddlString, SDDL_REVISION_1, &sd,\n                                                             NULL))\n    {\n        MsgToEventLog(M_SYSERR, L\"ConvertStringSecurityDescriptorToSecurityDescriptor failed.\");\n        return INVALID_HANDLE_VALUE;\n    }\n\n    /* Set up SECURITY_ATTRIBUTES */\n    SECURITY_ATTRIBUTES sa = { 0 };\n    sa.nLength = sizeof(SECURITY_ATTRIBUTES);\n    sa.lpSecurityDescriptor = sd;\n    sa.bInheritHandle = FALSE;\n\n    DWORD flags = PIPE_ACCESS_DUPLEX | WRITE_DAC | FILE_FLAG_OVERLAPPED;\n\n    static BOOL first = TRUE;\n    if (first)\n    {\n        flags |= FILE_FLAG_FIRST_PIPE_INSTANCE;\n        first = FALSE;\n    }\n\n    WCHAR pipe_name[256]; /* The entire pipe name string can be up to 256 characters long according\n                             to MSDN. */\n    swprintf(pipe_name, _countof(pipe_name), L\"\\\\\\\\.\\\\pipe\\\\\" _L(PACKAGE) L\"%ls\\\\service\",\n             service_instance);\n    HANDLE pipe = CreateNamedPipe(\n        pipe_name, flags, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_REJECT_REMOTE_CLIENTS,\n        PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, &sa);\n\n    LocalFree(sd);\n\n    if (pipe == INVALID_HANDLE_VALUE)\n    {\n        MsgToEventLog(M_SYSERR, L\"Could not create named pipe\");\n        return INVALID_HANDLE_VALUE;\n    }\n\n    return pipe;\n}\n\n\nstatic DWORD\nUpdateWaitHandles(LPHANDLE *handles_ptr, LPDWORD count, HANDLE io_event, HANDLE exit_event,\n                  list_item_t *threads)\n{\n    static DWORD size = 10;\n    static LPHANDLE handles = NULL;\n    DWORD pos = 0;\n\n    if (handles == NULL)\n    {\n        handles = malloc(size * sizeof(HANDLE));\n        *handles_ptr = handles;\n        if (handles == NULL)\n        {\n            return ERROR_OUTOFMEMORY;\n        }\n    }\n\n    handles[pos++] = io_event;\n\n    if (!threads)\n    {\n        handles[pos++] = exit_event;\n    }\n\n    while (threads)\n    {\n        if (pos == size)\n        {\n            LPHANDLE tmp;\n            size += 10;\n            tmp = realloc(handles, size * sizeof(HANDLE));\n            if (tmp == NULL)\n            {\n                size -= 10;\n                *count = pos;\n                return ERROR_OUTOFMEMORY;\n            }\n            handles = tmp;\n            *handles_ptr = handles;\n        }\n        handles[pos++] = threads->data;\n        threads = threads->next;\n    }\n\n    *count = pos;\n    return NO_ERROR;\n}\n\n\nstatic VOID\nFreeWaitHandles(LPHANDLE h)\n{\n    free(h);\n}\n\nstatic BOOL\nCmpHandle(LPVOID item, LPVOID hnd)\n{\n    return item == hnd;\n}\n\n\nVOID WINAPI\nServiceStartInteractiveOwn(DWORD dwArgc, LPWSTR *lpszArgv)\n{\n    status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;\n    ServiceStartInteractive(dwArgc, lpszArgv);\n}\n\n/**\n * Clean up remains of previous sessions in registry. These remains can\n * happen with unclean shutdowns or crashes and would interfere with\n * normal operation of the system with and without active tunnels.\n */\nstatic void\nCleanupRegistry(void)\n{\n    BOOL changed = FALSE;\n\n    /* Clean up leftover NRPT rules */\n    BOOL gpol_nrpt;\n    changed = DeleteNrptRules(0, &gpol_nrpt);\n\n    /* Clean up leftover DNS search list fragments */\n    HKEY key;\n    BOOL gpol_list;\n    GetDnsSearchListKey(NULL, &gpol_list, &key);\n    if (key != INVALID_HANDLE_VALUE)\n    {\n        if (ResetDnsSearchDomains(key))\n        {\n            changed = TRUE;\n        }\n        RegCloseKey(key);\n    }\n\n    if (changed)\n    {\n        ApplyDnsSettings(gpol_nrpt || gpol_list);\n    }\n}\n\nVOID WINAPI\nServiceStartInteractive(DWORD dwArgc, LPWSTR *lpszArgv)\n{\n    HANDLE pipe, io_event = NULL;\n    OVERLAPPED overlapped;\n    DWORD error = NO_ERROR;\n    list_item_t *threads = NULL;\n    PHANDLE handles = NULL;\n    DWORD handle_count;\n\n    service =\n        RegisterServiceCtrlHandlerEx(interactive_service.name, ServiceCtrlInteractive, &status);\n    if (!service)\n    {\n        return;\n    }\n\n    status.dwCurrentState = SERVICE_START_PENDING;\n    status.dwServiceSpecificExitCode = NO_ERROR;\n    status.dwWin32ExitCode = NO_ERROR;\n    status.dwWaitHint = 3000;\n    ReportStatusToSCMgr(service, &status);\n\n    /* Clean up potentially left over registry values */\n    CleanupRegistry();\n\n    /* Read info from registry in key HKLM\\SOFTWARE\\OpenVPN */\n    error = GetOpenvpnSettings(&settings);\n    if (error != ERROR_SUCCESS)\n    {\n        goto out;\n    }\n\n    io_event = InitOverlapped(&overlapped);\n    exit_event = CreateEvent(NULL, TRUE, FALSE, NULL);\n    if (!exit_event || !io_event)\n    {\n        error = MsgToEventLog(M_SYSERR, L\"Could not create event\");\n        goto out;\n    }\n\n    rdns_semaphore = CreateSemaphoreW(NULL, 1, 1, NULL);\n    if (!rdns_semaphore)\n    {\n        error = MsgToEventLog(M_SYSERR, L\"Could not create semaphore for register-dns\");\n        goto out;\n    }\n\n    error = UpdateWaitHandles(&handles, &handle_count, io_event, exit_event, threads);\n    if (error != NO_ERROR)\n    {\n        goto out;\n    }\n\n    pipe = CreateClientPipeInstance();\n    if (pipe == INVALID_HANDLE_VALUE)\n    {\n        goto out;\n    }\n\n    status.dwCurrentState = SERVICE_RUNNING;\n    status.dwWaitHint = 0;\n    ReportStatusToSCMgr(service, &status);\n\n    while (TRUE)\n    {\n        if (!ConnectNamedPipe(pipe, &overlapped))\n        {\n            DWORD connect_error = GetLastError();\n            if (connect_error == ERROR_NO_DATA)\n            {\n                /*\n                 * Client connected and disconnected before we could process it.\n                 * Disconnect and retry instead of aborting the service.\n                 */\n                MsgToEventLog(M_ERR, L\"ConnectNamedPipe returned ERROR_NO_DATA (client dropped)\");\n                DisconnectNamedPipe(pipe);\n                ResetOverlapped(&overlapped);\n                continue;\n            }\n            else if (connect_error == ERROR_PIPE_CONNECTED)\n            {\n                /* No async I/O pending in this case; signal manually. */\n                SetEvent(overlapped.hEvent);\n            }\n            else if (connect_error != ERROR_IO_PENDING)\n            {\n                MsgToEventLog(M_SYSERR, L\"Could not connect pipe\");\n                break;\n            }\n        }\n\n        error = WaitForMultipleObjects(handle_count, handles, FALSE, INFINITE);\n        if (error == WAIT_OBJECT_0)\n        {\n            /* Client connected, spawn a worker thread for it */\n            HANDLE next_pipe = CreateClientPipeInstance();\n\n            /* Avoid exceeding WaitForMultipleObjects MAXIMUM_WAIT_OBJECTS */\n            if (handle_count + 1 > MAXIMUM_WAIT_OBJECTS)\n            {\n                ReturnError(pipe, ERROR_CANT_WAIT, L\"Too many concurrent clients\", 1, &exit_event);\n                CloseHandleEx(&pipe);\n                pipe = next_pipe;\n                ResetOverlapped(&overlapped);\n                continue;\n            }\n\n            HANDLE thread = CreateThread(NULL, 0, RunOpenvpn, pipe, CREATE_SUSPENDED, NULL);\n            if (thread)\n            {\n                error = AddListItem(&threads, thread);\n                if (!error)\n                {\n                    error =\n                        UpdateWaitHandles(&handles, &handle_count, io_event, exit_event, threads);\n                }\n                if (error)\n                {\n                    ReturnError(pipe, error, L\"Insufficient resources to service new clients\", 1,\n                                &exit_event);\n                    /* Update wait handles again after removing the last worker thread */\n                    RemoveListItem(&threads, CmpHandle, thread);\n                    UpdateWaitHandles(&handles, &handle_count, io_event, exit_event, threads);\n                    TerminateThread(thread, 1);\n                    CloseHandleEx(&thread);\n                    CloseHandleEx(&pipe);\n                }\n                else\n                {\n                    ResumeThread(thread);\n                }\n            }\n            else\n            {\n                CloseHandleEx(&pipe);\n            }\n\n            ResetOverlapped(&overlapped);\n            pipe = next_pipe;\n        }\n        else\n        {\n            CancelIo(pipe);\n            if (error == WAIT_FAILED)\n            {\n                MsgToEventLog(M_SYSERR, L\"WaitForMultipleObjects failed\");\n                SetEvent(exit_event);\n                /* Give some time for worker threads to exit and then terminate */\n                Sleep(1000);\n                break;\n            }\n            if (!threads)\n            {\n                /* exit event signaled */\n                CloseHandleEx(&pipe);\n                ResetEvent(exit_event);\n                error = NO_ERROR;\n                break;\n            }\n\n            /* Worker thread ended */\n            HANDLE thread = RemoveListItem(&threads, CmpHandle, handles[error]);\n            UpdateWaitHandles(&handles, &handle_count, io_event, exit_event, threads);\n            CloseHandleEx(&thread);\n        }\n    }\n\nout:\n    FreeWaitHandles(handles);\n    CloseHandleEx(&io_event);\n    CloseHandleEx(&exit_event);\n    CloseHandleEx(&rdns_semaphore);\n\n    status.dwCurrentState = SERVICE_STOPPED;\n    status.dwWin32ExitCode = error;\n    ReportStatusToSCMgr(service, &status);\n}\n"
  },
  {
    "path": "src/openvpnserv/openvpnserv_resources.rc",
    "content": "#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n#include <winresrc.h>\n\n#pragma code_page(65001) /* UTF8 */\n\nLANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL\n\nVS_VERSION_INFO VERSIONINFO\n FILEVERSION OPENVPN_VERSION_RESOURCE\n PRODUCTVERSION OPENVPN_VERSION_RESOURCE\n FILEFLAGSMASK 0x3fL\n#ifdef _DEBUG\n FILEFLAGS 0x1L\n#else\n FILEFLAGS 0x0L\n#endif\n FILEOS 0x40004L\n FILETYPE VFT_APP\n FILESUBTYPE 0x0L\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"040904b0\"\n        BEGIN\n            VALUE \"CompanyName\", \"The OpenVPN Project\"\n            VALUE \"FileDescription\", \"OpenVPN Service\"\n            VALUE \"FileVersion\", PACKAGE_VERSION \".0\"\n            VALUE \"InternalName\", \"OpenVPN\"\n            VALUE \"LegalCopyright\", \"Copyright © The OpenVPN Project\" \n            VALUE \"OriginalFilename\", \"openvpnserv.exe\"\n            VALUE \"ProductName\", \"OpenVPN\"\n            VALUE \"ProductVersion\", PACKAGE_VERSION \".0\"\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x409, 1200\n    END\nEND\n"
  },
  {
    "path": "src/openvpnserv/service.c",
    "content": "/*\n * THIS CODE AND INFORMATION IS PROVIDED \"AS IS\" WITHOUT WARRANTY OF\n * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED\n * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A\n * PARTICULAR PURPOSE.\n *\n * Copyright (C) 1993 - 2000.  Microsoft Corporation.  All rights reserved.\n *                      2013 Heiko Hund <heiko.hund@sophos.com>\n */\n\n#include \"service.h\"\n\n#include <windows.h>\n#include <stdio.h>\n#include <process.h>\n\n\nopenvpn_service_t openvpn_service[_service_max];\n\n\nBOOL\nReportStatusToSCMgr(SERVICE_STATUS_HANDLE service, SERVICE_STATUS *status)\n{\n    static DWORD dwCheckPoint = 1;\n    BOOL res = TRUE;\n\n    if (status->dwCurrentState == SERVICE_START_PENDING)\n    {\n        status->dwControlsAccepted = 0;\n    }\n    else\n    {\n        status->dwControlsAccepted = SERVICE_ACCEPT_STOP;\n    }\n\n    if (status->dwCurrentState == SERVICE_RUNNING || status->dwCurrentState == SERVICE_STOPPED)\n    {\n        status->dwCheckPoint = 0;\n    }\n    else\n    {\n        status->dwCheckPoint = dwCheckPoint++;\n    }\n\n    /* Report the status of the service to the service control manager. */\n    res = SetServiceStatus(service, status);\n    if (!res)\n    {\n        MsgToEventLog(MSG_FLAGS_ERROR, L\"SetServiceStatus\");\n    }\n\n    return res;\n}\n\nstatic int\nCmdInstallServices(void)\n{\n    SC_HANDLE service;\n    SC_HANDLE svc_ctl_mgr;\n    WCHAR path[512];\n    int i, ret = _service_max;\n\n    if (GetModuleFileName(NULL, path + 1, _countof(path) - 2) == 0)\n    {\n        wprintf(L\"Unable to install service - %ls\\n\", GetLastErrorText());\n        return 1;\n    }\n\n    path[0] = L'\\\"';\n    wcscat_s(path, _countof(path), L\"\\\"\");\n\n    svc_ctl_mgr = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);\n    if (svc_ctl_mgr == NULL)\n    {\n        wprintf(L\"OpenSCManager failed - %ls\\n\", GetLastErrorText());\n        return 1;\n    }\n\n    for (i = 0; i < _service_max; i++)\n    {\n        service = CreateService(\n            svc_ctl_mgr, openvpn_service[i].name, openvpn_service[i].display_name,\n            SERVICE_QUERY_STATUS, SERVICE_WIN32_SHARE_PROCESS, openvpn_service[i].start_type,\n            SERVICE_ERROR_NORMAL, path, NULL, NULL, openvpn_service[i].dependencies, NULL, NULL);\n        if (service)\n        {\n            wprintf(L\"%ls installed.\\n\", openvpn_service[i].display_name);\n            CloseServiceHandle(service);\n            --ret;\n        }\n        else\n        {\n            wprintf(L\"CreateService failed - %ls\\n\", GetLastErrorText());\n        }\n    }\n\n    CloseServiceHandle(svc_ctl_mgr);\n    return ret;\n}\n\n\nstatic int\nCmdStartService(openvpn_service_type type)\n{\n    int ret = 1;\n    SC_HANDLE svc_ctl_mgr;\n    SC_HANDLE service;\n\n    svc_ctl_mgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);\n    if (svc_ctl_mgr == NULL)\n    {\n        wprintf(L\"OpenSCManager failed - %ls\\n\", GetLastErrorText());\n        return 1;\n    }\n\n    service = OpenService(svc_ctl_mgr, openvpn_service[type].name, SERVICE_ALL_ACCESS);\n    if (service)\n    {\n        if (StartService(service, 0, NULL))\n        {\n            wprintf(L\"Service Started\\n\");\n            ret = 0;\n        }\n        else\n        {\n            wprintf(L\"StartService failed - %ls\\n\", GetLastErrorText());\n        }\n\n        CloseServiceHandle(service);\n    }\n    else\n    {\n        wprintf(L\"OpenService failed - %ls\\n\", GetLastErrorText());\n    }\n\n    CloseServiceHandle(svc_ctl_mgr);\n    return ret;\n}\n\n\nstatic int\nCmdRemoveServices(void)\n{\n    SC_HANDLE service;\n    SC_HANDLE svc_ctl_mgr;\n    SERVICE_STATUS status;\n    int i, ret = _service_max;\n\n    svc_ctl_mgr = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);\n    if (svc_ctl_mgr == NULL)\n    {\n        wprintf(L\"OpenSCManager failed - %ls\\n\", GetLastErrorText());\n        return 1;\n    }\n\n    for (i = 0; i < _service_max; i++)\n    {\n        openvpn_service_t *ovpn_svc = &openvpn_service[i];\n        service =\n            OpenService(svc_ctl_mgr, ovpn_svc->name, DELETE | SERVICE_STOP | SERVICE_QUERY_STATUS);\n        if (service == NULL)\n        {\n            wprintf(L\"OpenService failed - %ls\\n\", GetLastErrorText());\n            goto out;\n        }\n\n        /* try to stop the service */\n        if (ControlService(service, SERVICE_CONTROL_STOP, &status))\n        {\n            wprintf(L\"Stopping %ls.\", ovpn_svc->display_name);\n            Sleep(1000);\n\n            while (QueryServiceStatus(service, &status))\n            {\n                if (status.dwCurrentState == SERVICE_STOP_PENDING)\n                {\n                    wprintf(L\".\");\n                    Sleep(1000);\n                }\n                else\n                {\n                    break;\n                }\n            }\n\n            if (status.dwCurrentState == SERVICE_STOPPED)\n            {\n                wprintf(L\"\\n%ls stopped.\\n\", ovpn_svc->display_name);\n            }\n            else\n            {\n                wprintf(L\"\\n%ls failed to stop.\\n\", ovpn_svc->display_name);\n            }\n        }\n\n        /* now remove the service */\n        if (DeleteService(service))\n        {\n            wprintf(L\"%ls removed.\\n\", ovpn_svc->display_name);\n            --ret;\n        }\n        else\n        {\n            wprintf(L\"DeleteService failed - %ls\\n\", GetLastErrorText());\n        }\n\n        CloseServiceHandle(service);\n    }\n\nout:\n    CloseServiceHandle(svc_ctl_mgr);\n    return ret;\n}\n\n\nint\nwmain(int argc, WCHAR *argv[])\n{\n    /*\n     * Interactive service (as a SERVICE_WIN32_SHARE_PROCESS)\n     * This is the default.\n     */\n    const SERVICE_TABLE_ENTRY dispatchTable_shared[] = {\n        { interactive_service.name, ServiceStartInteractive }, { NULL, NULL }\n    };\n\n    /* Interactive service only (as a SERVICE_WIN32_OWN_PROCESS) */\n    const SERVICE_TABLE_ENTRY dispatchTable_interactive[] = { { L\"\", ServiceStartInteractiveOwn },\n                                                              { NULL, NULL } };\n\n    const SERVICE_TABLE_ENTRY *dispatchTable = dispatchTable_shared;\n\n    openvpn_service[interactive] = interactive_service;\n\n    for (int i = 1; i < argc; i++)\n    {\n        if (*argv[i] == L'-' || *argv[i] == L'/')\n        {\n            if (_wcsicmp(L\"install\", argv[i] + 1) == 0)\n            {\n                return CmdInstallServices();\n            }\n            else if (_wcsicmp(L\"remove\", argv[i] + 1) == 0)\n            {\n                return CmdRemoveServices();\n            }\n            else if (_wcsicmp(L\"start\", argv[i] + 1) == 0)\n            {\n                return CmdStartService(interactive);\n            }\n            else if (argc > i + 2 && _wcsicmp(L\"instance\", argv[i] + 1) == 0)\n            {\n                if (_wcsicmp(L\"interactive\", argv[i + 1]) == 0)\n                {\n                    dispatchTable = dispatchTable_interactive;\n                    service_instance = argv[i + 2];\n                    i += 2;\n                }\n                else\n                {\n                    MsgToEventLog(M_ERR,\n                                  L\"Invalid argument to -instance <%s>. Service not started.\",\n                                  argv[i + 1]);\n                    return 1;\n                }\n            }\n            else\n            {\n                wprintf(L\"%ls -install        to install the interactive service\\n\", APPNAME);\n                wprintf(\n                    L\"%ls -start [name]   to start the service (name = \\\"interactive\\\") is optional\\n\",\n                    APPNAME);\n                wprintf(L\"%ls -remove         to remove the service\\n\", APPNAME);\n\n                wprintf(L\"\\nService run-time parameters:\\n\");\n                wprintf(L\"-instance interactive <id>\\n\"\n                        L\"   Runs the service as an alternate instance.\\n\"\n                        L\"   The service settings will be loaded from\\n\"\n                        L\"   HKLM\\\\Software\\\\\" _L(\n                            PACKAGE_NAME) L\"<id> registry key, and the service will accept\\n\"\n                                          L\"   requests on \\\\\\\\.\\\\pipe\\\\\" _L(\n                                              PACKAGE) L\"<id>\\\\service named pipe.\\n\");\n\n                return 0;\n            }\n        }\n    }\n\n    /* If it doesn't match any of the above parameters\n     * the service control manager may be starting the service\n     * so we must call StartServiceCtrlDispatcher\n     */\n    wprintf(L\"\\nStartServiceCtrlDispatcher being called.\\n\");\n    wprintf(L\"This may take several seconds. Please wait.\\n\");\n\n    if (!StartServiceCtrlDispatcher(dispatchTable))\n    {\n        MsgToEventLog(MSG_FLAGS_ERROR, L\"StartServiceCtrlDispatcher failed.\");\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/openvpnserv/service.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2013-2026 Heiko Hund <heiko.hund@sophos.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef _SERVICE_H\n#define _SERVICE_H\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <winsock2.h>\n#include <windows.h>\n#include <stdlib.h>\n#include <wchar.h>\n#include \"../tapctl/basic.h\"\n\n#define APPNAME              _L(PACKAGE) L\"serv\"\n#define SERVICE_DEPENDENCIES _L(TAP_WIN_COMPONENT_ID) L\"\\0Dhcp\\0\\0\"\n\n/*\n * Message handling\n */\n#define MSG_FLAGS_ERROR    (1 << 0)\n#define MSG_FLAGS_SYS_CODE (1 << 1)\n#define M_INFO             (0)                                    /* informational */\n#define M_SYSERR           (MSG_FLAGS_ERROR | MSG_FLAGS_SYS_CODE) /* error + system code */\n#define M_ERR              (MSG_FLAGS_ERROR)                      /* error */\n\ntypedef enum\n{\n    interactive,\n    _service_max\n} openvpn_service_type;\n\ntypedef struct\n{\n    openvpn_service_type type;\n    WCHAR *name;\n    WCHAR *display_name;\n    WCHAR *dependencies;\n    DWORD start_type;\n} openvpn_service_t;\n\n#define MAX_NAME 256\ntypedef struct\n{\n    WCHAR exe_path[MAX_PATH];\n    WCHAR config_dir[MAX_PATH];\n    WCHAR bin_dir[MAX_PATH];\n    WCHAR ext_string[16];\n    WCHAR log_dir[MAX_PATH];\n    WCHAR ovpn_admin_group[MAX_NAME];\n    WCHAR ovpn_service_user[MAX_NAME];\n    DWORD priority;\n    BOOL append;\n} settings_t;\n\nextern openvpn_service_t interactive_service;\nextern LPCWSTR service_instance;\n\nVOID WINAPI ServiceStartInteractiveOwn(DWORD argc, LPWSTR *argv);\n\nVOID WINAPI ServiceStartInteractive(DWORD argc, LPWSTR *argv);\n\nDWORD GetOpenvpnSettings(settings_t *s);\n\nBOOL ReportStatusToSCMgr(SERVICE_STATUS_HANDLE service, SERVICE_STATUS *status);\n\nLPCWSTR GetLastErrorText(void);\n\nDWORD MsgToEventLog(DWORD flags, LPCWSTR lpszMsg, ...);\n\n/**\n * Convert a UTF-8 string to UTF-16\n *\n * The size parameter can be used to convert strings which contain inline NUL\n * characters, like MULTI_SZ strings used as values in the registry do,\n * or (sub)strings that are not zero terminated. If size is -1 the length\n * of the string is determined automatically by the WIN32 API. Make sure\n * you pass a terminated string or else bad things will happen. Note that\n * the size you pass should always include the terminating zero as well.\n *\n * If the returned string is not NULL it must be freed by the caller.\n *\n * @param utf8  const string to be converted\n * @param size  the size of the string\n *\n * @return wchar_t* heap allocated result string\n */\nwchar_t *utf8to16_size(const char *utf8, int size);\n\n/**\n * Convert a zero terminated UTF-8 string to UTF-16\n *\n * This is just a wrapper function that always passes -1 as string size\n * to \\ref utf8to16_size.\n *\n * @param utf8  const string to be converted\n *\n * @return wchar_t* heap allocated result string\n */\nstatic inline wchar_t *\nutf8to16(const char *utf8)\n{\n    return utf8to16_size(utf8, -1);\n}\n\n/* return windows system directory as a pointer to a static string */\nconst wchar_t *get_win_sys_path(void);\n\n#endif /* ifndef _SERVICE_H */\n"
  },
  {
    "path": "src/openvpnserv/validate.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"validate.h\"\n\n#include <lmaccess.h>\n#include <shlwapi.h>\n#include <pathcch.h>\n#include <lm.h>\n\nstatic const WCHAR *white_list[] = {\n    L\"auth-retry\",\n    L\"config\",\n    L\"log\",\n    L\"log-append\",\n    L\"management\",\n    L\"management-forget-disconnect\",\n    L\"management-hold\",\n    L\"management-query-passwords\",\n    L\"management-query-proxy\",\n    L\"management-signal\",\n    L\"management-up-down\",\n    L\"mute\",\n    L\"setenv\",\n    L\"service\",\n    L\"verb\",\n    L\"pull-filter\",\n    L\"script-security\",\n\n    NULL /* last value */\n};\n\nstatic BOOL IsUserInGroup(PSID sid, const PTOKEN_GROUPS groups, const WCHAR *group_name);\n\nstatic PTOKEN_GROUPS GetTokenGroups(const HANDLE token);\n\n/*\n * Check that config path is inside config_dir\n * The logic here is simple: if the path isn't prefixed with config_dir it's rejected\n */\nstatic BOOL\nCheckConfigPath(const WCHAR *workdir, const WCHAR *fname, const settings_t *s)\n{\n    HRESULT res;\n    WCHAR config_path[MAX_PATH];\n\n    /* fname = stdin is special: do not treat it as a relative path */\n    if (wcscmp(fname, L\"stdin\") == 0)\n    {\n        return FALSE;\n    }\n    /* convert fname to full canonical path */\n    if (PathIsRelativeW(fname))\n    {\n        res = PathCchCombine(config_path, _countof(config_path), workdir, fname);\n    }\n    else\n    {\n        res = PathCchCanonicalize(config_path, _countof(config_path), fname);\n    }\n\n    return res == S_OK && wcsnicmp(config_path, s->config_dir, wcslen(s->config_dir)) == 0;\n}\n\n\n/*\n * A simple linear search meant for a small wchar_t *array.\n * Returns index to the item if found, -1 otherwise.\n */\nstatic int\nOptionLookup(const WCHAR *name, const WCHAR *white_list[])\n{\n    int i;\n\n    for (i = 0; white_list[i]; i++)\n    {\n        if (wcscmp(white_list[i], name) == 0)\n        {\n            return i;\n        }\n    }\n\n    return -1;\n}\n\n/*\n * The Administrators group may be localized or renamed by admins.\n * Get the local name of the group using the SID.\n */\nstatic BOOL\nGetBuiltinAdminGroupName(WCHAR *name, DWORD nlen)\n{\n    BOOL b = FALSE;\n    PSID admin_sid = NULL;\n    DWORD sid_size = SECURITY_MAX_SID_SIZE;\n    SID_NAME_USE snu;\n\n    WCHAR domain[MAX_NAME];\n    DWORD dlen = _countof(domain);\n\n    admin_sid = malloc(sid_size);\n    if (!admin_sid)\n    {\n        return FALSE;\n    }\n\n    b = CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, admin_sid, &sid_size);\n    if (b)\n    {\n        b = LookupAccountSidW(NULL, admin_sid, name, &nlen, domain, &dlen, &snu);\n    }\n\n    free(admin_sid);\n\n    return b;\n}\n\nBOOL\nIsAuthorizedUser(PSID sid, const HANDLE token, const WCHAR *ovpn_admin_group,\n                 const WCHAR *ovpn_service_user)\n{\n    const WCHAR *admin_group[2];\n    WCHAR username[MAX_NAME];\n    WCHAR domain[MAX_NAME];\n    WCHAR sysadmin_group[MAX_NAME];\n    DWORD len = MAX_NAME;\n    BOOL ret = FALSE;\n    SID_NAME_USE sid_type;\n\n    /* Get username */\n    if (!LookupAccountSidW(NULL, sid, username, &len, domain, &len, &sid_type))\n    {\n        MsgToEventLog(M_SYSERR, L\"LookupAccountSid\");\n        /* not fatal as this is now used only for logging */\n        username[0] = '\\0';\n        domain[0] = '\\0';\n    }\n\n    /* is this service account? */\n    if ((wcscmp(username, ovpn_service_user) == 0) && (wcscmp(domain, L\"NT SERVICE\") == 0))\n    {\n        return TRUE;\n    }\n\n    if (GetBuiltinAdminGroupName(sysadmin_group, _countof(sysadmin_group)))\n    {\n        admin_group[0] = sysadmin_group;\n    }\n    else\n    {\n        MsgToEventLog(M_SYSERR,\n                      L\"Failed to get the name of Administrators group. Using the default.\");\n        /* use the default value */\n        admin_group[0] = SYSTEM_ADMIN_GROUP;\n    }\n    admin_group[1] = ovpn_admin_group;\n\n    PTOKEN_GROUPS token_groups = GetTokenGroups(token);\n    for (int i = 0; i < 2; ++i)\n    {\n        ret = IsUserInGroup(sid, token_groups, admin_group[i]);\n        if (ret)\n        {\n            MsgToEventLog(M_INFO,\n                          L\"Authorizing user '%ls@%ls' by virtue of membership in group '%ls'\",\n                          username, domain, admin_group[i]);\n            goto out;\n        }\n    }\n\nout:\n    free(token_groups);\n    return ret;\n}\n\n/**\n * Get a list of groups in token.\n * Returns a pointer to TOKEN_GROUPS struct or NULL on error.\n * The caller should free the returned pointer.\n */\nstatic PTOKEN_GROUPS\nGetTokenGroups(const HANDLE token)\n{\n    PTOKEN_GROUPS groups = NULL;\n    DWORD buf_size = 0;\n\n    if (!GetTokenInformation(token, TokenGroups, groups, buf_size, &buf_size)\n        && GetLastError() == ERROR_INSUFFICIENT_BUFFER)\n    {\n        groups = malloc(buf_size);\n    }\n    if (!groups)\n    {\n        MsgToEventLog(M_SYSERR, L\"GetTokenGroups\");\n    }\n    else if (!GetTokenInformation(token, TokenGroups, groups, buf_size, &buf_size))\n    {\n        MsgToEventLog(M_SYSERR, L\"GetTokenInformation\");\n        free(groups);\n    }\n    return groups;\n}\n\n/*\n * Find SID from name\n *\n * On input sid buffer should have space for at least sid_size bytes.\n * Returns true on success, false on failure.\n * Suggest: in caller allocate sid to hold SECURITY_MAX_SID_SIZE bytes\n */\nstatic BOOL\nLookupSID(const WCHAR *name, PSID sid, DWORD sid_size)\n{\n    SID_NAME_USE su;\n    WCHAR domain[MAX_NAME];\n    DWORD dlen = _countof(domain);\n\n    if (!LookupAccountName(NULL, name, sid, &sid_size, domain, &dlen, &su))\n    {\n        return FALSE; /* not fatal as the group may not exist */\n    }\n    return TRUE;\n}\n\n/**\n * User is in group if the token groups contain the SID of the group\n * of if the user is a direct member of the group. The latter check\n * catches dynamic changes in group membership in the local user\n * database not reflected in the token.\n * If token_groups or sid is NULL the corresponding check is skipped.\n *\n * Using sid and list of groups in token avoids reference to domains so that\n * this could be completed without access to a Domain Controller.\n *\n * Returns true if the user is in the group, false otherwise.\n */\nstatic BOOL\nIsUserInGroup(PSID sid, const PTOKEN_GROUPS token_groups, const WCHAR *group_name)\n{\n    BOOL ret = FALSE;\n    DWORD_PTR resume = 0;\n    DWORD err;\n    BYTE grp_sid[SECURITY_MAX_SID_SIZE];\n    int nloop = 0; /* a counter used to not get stuck in the do .. while() */\n\n    /* first check in the token groups */\n    if (token_groups && LookupSID(group_name, (PSID)grp_sid, _countof(grp_sid)))\n    {\n        for (DWORD i = 0; i < token_groups->GroupCount; ++i)\n        {\n            if (EqualSid((PSID)grp_sid, token_groups->Groups[i].Sid))\n            {\n                return TRUE;\n            }\n        }\n    }\n\n    /* check user's SID is a member of the group */\n    if (!sid)\n    {\n        return FALSE;\n    }\n    do\n    {\n        DWORD nread, nmax;\n        LOCALGROUP_MEMBERS_INFO_0 *members = NULL;\n        err = NetLocalGroupGetMembers(NULL, group_name, 0, (LPBYTE *)&members, MAX_PREFERRED_LENGTH,\n                                      &nread, &nmax, &resume);\n        if ((err != NERR_Success && err != ERROR_MORE_DATA))\n        {\n            break;\n        }\n        /* If a match is already found, ret == TRUE and the loop is skipped */\n        for (DWORD i = 0; i < nread && !ret; ++i)\n        {\n            ret = EqualSid(members[i].lgrmi0_sid, sid);\n        }\n        NetApiBufferFree(members);\n        /* MSDN says the lookup should always iterate until err != ERROR_MORE_DATA */\n    } while (err == ERROR_MORE_DATA && nloop++ < 100);\n\n    if (err != NERR_Success && err != NERR_GroupNotFound)\n    {\n        SetLastError(err);\n        MsgToEventLog(M_SYSERR, L\"In NetLocalGroupGetMembers for group '%ls'\", group_name);\n    }\n\n    return ret;\n}\n\n/*\n * Check whether option argv[0] is white-listed. If argv[0] == \"--config\",\n * also check that argv[1], if present, passes CheckConfigPath().\n * The caller should set argc to the number of valid elements in argv[] array.\n */\nBOOL\nCheckOption(const WCHAR *workdir, int argc, WCHAR *argv[], const settings_t *s)\n{\n    /* Do not modify argv or *argv -- ideally it should be const WCHAR *const *, but alas...*/\n\n    if (wcscmp(argv[0], L\"--config\") == 0 && argc > 1 && !CheckConfigPath(workdir, argv[1], s))\n    {\n        return FALSE;\n    }\n\n    /* option name starts at 2 characters from argv[i] */\n    if (OptionLookup(argv[0] + 2, white_list) == -1) /* not found */\n    {\n        return FALSE;\n    }\n\n    return TRUE;\n}\n"
  },
  {
    "path": "src/openvpnserv/validate.h",
    "content": "\n/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef VALIDATE_H\n#define VALIDATE_H\n\n#include \"service.h\"\n\n/* Authorized groups who can use any options and config locations */\n#define SYSTEM_ADMIN_GROUP L\"Administrators\"\n#define OVPN_ADMIN_GROUP \\\n    L\"OpenVPN Administrators\" /* may be set in HKLM\\Software\\OpenVPN\\ovpn_admin_group */\n#define OVPN_SERVICE_USER \\\n    L\"OpenVPNService\"         /* may be set in HKLM\\Software\\OpenVPN\\ovpn_service_user */\n\n/*\n * Check whether user is a member of Administrators group or\n * the group specified in ovpn_admin_group or\n * OpenVPN Virtual Service Account user\n */\nBOOL IsAuthorizedUser(PSID sid, const HANDLE token, const WCHAR *ovpn_admin_group,\n                      const WCHAR *ovpn_service_user);\n\nBOOL CheckOption(const WCHAR *workdir, int narg, WCHAR *argv[], const settings_t *s);\n\nstatic inline BOOL\nIsOption(const WCHAR *o)\n{\n    return (wcsncmp(o, L\"--\", 2) == 0);\n}\n\n#endif /* ifndef VALIDATE_H */\n"
  },
  {
    "path": "src/plugins/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2006-2012 Alon Bar-Lev <alon.barlev@gmail.com>\n#\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n\nSUBDIRS = auth-pam down-root\n"
  },
  {
    "path": "src/plugins/auth-pam/Makefile.am",
    "content": "#\n#  OpenVPN (TM) PAM Auth Plugin -- OpenVPN Plugin\n#\n#  Copyright (C) 2012      Alon Bar-Lev <alon.barlev@gmail.com>\n#\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n\nAM_CFLAGS = \\\n\t-I$(top_srcdir)/include \\\n\t$(PLUGIN_AUTH_PAM_CFLAGS) \\\n\t$(OPTIONAL_CRYPTO_CFLAGS)\n\nif ENABLE_PLUGIN_AUTH_PAM\nplugin_LTLIBRARIES = openvpn-plugin-auth-pam.la\ndist_doc_DATA = README.auth-pam\nendif\n\nopenvpn_plugin_auth_pam_la_SOURCES = \\\n\tutils.c \\\n\tauth-pam.c \\\n\tpamdl.c  pamdl.h \\\n\tauth-pam.exports\nopenvpn_plugin_auth_pam_la_LIBADD = \\\n\t$(PLUGIN_AUTH_PAM_LIBS)\nopenvpn_plugin_auth_pam_la_LDFLAGS = $(AM_LDFLAGS) \\\n\t-export-symbols \"$(srcdir)/auth-pam.exports\" \\\n\t-module -shared -avoid-version -no-undefined\n"
  },
  {
    "path": "src/plugins/auth-pam/README.auth-pam",
    "content": "openvpn-auth-pam\n\nSYNOPSIS\n\nThe openvpn-auth-pam module implements username/password\nauthentication via PAM, and essentially allows any authentication\nmethod supported by PAM (such as LDAP, RADIUS, or Linux Shadow\npasswords) to be used with OpenVPN.  While PAM supports\nusername/password authentication, this can be combined with X509\ncertificates to provide two independent levels of authentication.\n\nThis module uses a split privilege execution model which will\nfunction even if you drop openvpn daemon privileges using the user,\ngroup, or chroot directives.\n\nBUILD\n\nTo build openvpn-auth-pam, you will need to have the pam-devel\npackage installed.\n\nBuild with the \"make\" command.  The module will be named\nopenvpn-auth-pam.so\n\nUSAGE\n\nTo use this plugin module, add to your OpenVPN config file:\n\n  plugin openvpn-auth-pam.so service-type\n\nThe required service-type parameter corresponds to\nthe PAM service definition file usually found\nin /etc/pam.d.\n\nThis plugin also supports the usage of a list of name/value\npairs to answer PAM module queries.\n\nFor example:\n\n  plugin openvpn-auth-pam.so \"login login USERNAME password PASSWORD pin OTP\"\n\ntells auth-pam to (a) use the \"login\" PAM module, (b) answer a\n\"login\" query with the username given by the OpenVPN client,\n(c) answer a \"password\" query with the password, and (d) answer a\n\"pin\" query with the OTP given by the OpenVPN client.\nThis provides flexibility in dealing with different\ntypes of query strings which different PAM modules might generate.\nFor example, suppose you were using a PAM module called\n\"test\" which queried for \"name\" rather than \"login\":\n\n  plugin openvpn-auth-pam.so \"test name USERNAME password PASSWORD\"\n\nWhile \"USERNAME\" \"COMMONNAME\" \"PASSWORD\" and \"OTP\" are special strings which substitute\nto client-supplied values, it is also possible to name literal values\nto use as PAM module query responses.  For example, suppose that the\nlogin module queried for a third parameter, \"domain\" which\nis to be answered with the constant value \"mydomain.com\":\n\n  plugin openvpn-auth-pam.so \"login login USERNAME password PASSWORD domain mydomain.com\"\n\nThe following OpenVPN directives can also influence\nthe operation of this plugin:\n\n  verify-client-cert none\n  username-as-common-name\n  static-challenge\n\nUse of --static challenege is required to pass a pin (represented by \"OTP\" in\nparameter substitution) or a second password.\n\nRun OpenVPN with --verb 7 or higher to get debugging output from\nthis plugin, including the list of queries presented by the\nunderlying PAM module.  This is a useful debugging tool to figure\nout which queries a given PAM module is making, so that you can\ncraft the appropriate plugin directive to answer it.\n\nSince running OpenVPN with verb 7 is quite verbose, alternatively\nyou can put\n\n   verb 3\n   setenv verb 9\n\nin the openvpn config which will only increase logging for this plugin.\n\n\nASYNCHRONOUS OPERATION\n\nSometimes PAM modules take very long to complete (for example, a LDAP\nor Radius query might timeout trying to connect an unreachable external\nserver).  Normal plugin auth operation will block the whole OpenVPN\nprocess in this time, that is, all forwarding for all other clients stops.\n\nThe auth-pam plugin can operate asynchronously (\"deferred authentication\")\nto remedy this situation.  To enable this, put\n\n  setenv deferred_auth_pam 1\n\nin your openvpn server config.  If set, this will make the \"PAM background\nprocess\" fork() and do its job detached from OpenVPN.  When finished, a\nstatus file is written, which OpenVPN will then pick up and read the\nsuccess/failure result from it.\n\nWhile the plugin is working in the background, OpenVPN will continue to\nservice other clients normally.\n\nAsynchronous operation is recommended for all PAM queries that could\n\"take time\" (LDAP, Radius, NIS, ...).  If only local files are queried\n(passwd, pam_userdb, ...), synchronous operation has slightly lower\noverhead, so this is still the default mode of operation.\n\n\nCAVEATS\n\nThis module will only work on *nix systems which support PAM,\nnot Windows.\n"
  },
  {
    "path": "src/plugins/auth-pam/auth-pam.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2016-2026 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * OpenVPN plugin module to do PAM authentication using a split\n * privilege model.\n */\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n\n#include <security/pam_appl.h>\n\n#ifdef USE_PAM_DLOPEN\n#include \"pamdl.h\"\n#endif\n\n#include <stdio.h>\n#include <string.h>\n#include <ctype.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <sys/wait.h>\n#include <fcntl.h>\n#include <signal.h>\n#include <syslog.h>\n#include <limits.h>\n#include \"utils.h\"\n#include <arpa/inet.h>\n#include <openvpn-plugin.h>\n\n#define DEBUG(verb) ((verb) >= 4)\n\n/* Command codes for foreground -> background communication */\n#define COMMAND_VERIFY 0\n#define COMMAND_EXIT   1\n\n/* Response codes for background -> foreground communication */\n#define RESPONSE_INIT_SUCCEEDED   10\n#define RESPONSE_INIT_FAILED      11\n#define RESPONSE_VERIFY_SUCCEEDED 12\n#define RESPONSE_VERIFY_FAILED    13\n#define RESPONSE_DEFER            14\n\n/* Pointers to functions exported from openvpn */\nstatic plugin_log_t plugin_log = NULL;\nstatic plugin_secure_memzero_t plugin_secure_memzero = NULL;\nstatic plugin_base64_decode_t plugin_base64_decode = NULL;\n\n/* module name for plugin_log() */\nstatic char *MODULE = \"AUTH-PAM\";\n\n/*\n * Plugin state, used by foreground\n */\nstruct auth_pam_context\n{\n    /* Foreground's socket to background process */\n    int foreground_fd;\n\n    /* Process ID of background process */\n    pid_t background_pid;\n\n    /* Verbosity level of OpenVPN */\n    int verb;\n};\n\n/*\n * Name/Value pairs for conversation function.\n * Special Values:\n *\n *  \"USERNAME\" -- substitute client-supplied username\n *  \"PASSWORD\" -- substitute client-specified password\n *  \"COMMONNAME\" -- substitute client certificate common name\n *  \"OTP\" -- substitute static challenge response if available\n */\n\n#define N_NAME_VALUE 16\n\nstruct name_value\n{\n    const char *name;\n    const char *value;\n};\n\nstruct name_value_list\n{\n    int len;\n    struct name_value data[N_NAME_VALUE];\n};\n\n/*\n * Used to pass the username/password\n * to the PAM conversation function.\n */\nstruct user_pass\n{\n    int verb;\n\n    char username[128];\n    char password[128];\n    char common_name[128];\n    char response[128];\n    char remote[INET6_ADDRSTRLEN];\n\n    const struct name_value_list *name_value_list;\n};\n\n/* Background process function */\nstatic void pam_server(int fd, const char *service, int verb,\n                       const struct name_value_list *name_value_list);\n\n\n/*\n * Socket read/write functions.\n */\n\nstatic int\nrecv_control(int fd)\n{\n    unsigned char c;\n    const ssize_t size = read(fd, &c, sizeof(c));\n    if (size == sizeof(c))\n    {\n        return c;\n    }\n    else\n    {\n        /*fprintf (stderr, \"AUTH-PAM: DEBUG recv_control.read=%d\\n\", (int)size);*/\n        return -1;\n    }\n}\n\nstatic int\nsend_control(int fd, int code)\n{\n    unsigned char c = (unsigned char)code;\n    const ssize_t size = write(fd, &c, sizeof(c));\n    if (size == sizeof(c))\n    {\n        return (int)size;\n    }\n    else\n    {\n        return -1;\n    }\n}\n\nstatic ssize_t\nrecv_string(int fd, char *buffer, size_t len)\n{\n    if (len > 0)\n    {\n        memset(buffer, 0, len);\n        ssize_t size = read(fd, buffer, len);\n        buffer[len - 1] = 0;\n        if (size >= 1)\n        {\n            return size;\n        }\n    }\n    return -1;\n}\n\nstatic ssize_t\nsend_string(int fd, const char *string)\n{\n    const ssize_t len = strlen(string) + 1;\n    const ssize_t size = write(fd, string, len);\n    if (size == len)\n    {\n        return size;\n    }\n    else\n    {\n        return -1;\n    }\n}\n\n#ifdef DO_DAEMONIZE\n\n/*\n * Daemonize if \"daemon\" env var is true.\n * Preserve stderr across daemonization if\n * \"daemon_log_redirect\" env var is true.\n */\nstatic void\ndaemonize(const char *envp[])\n{\n    const char *daemon_string = get_env(\"daemon\", envp);\n    if (daemon_string && daemon_string[0] == '1')\n    {\n        const char *log_redirect = get_env(\"daemon_log_redirect\", envp);\n        int fd = -1;\n        if (log_redirect && log_redirect[0] == '1')\n        {\n            fd = dup(2);\n        }\n#if defined(__APPLE__) && defined(__clang__)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n#endif\n        if (daemon(0, 0) < 0)\n        {\n            plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"daemonization failed\");\n        }\n#if defined(__APPLE__) && defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n        else if (fd >= 3)\n        {\n            dup2(fd, 2);\n            close(fd);\n        }\n    }\n}\n\n#endif /* ifdef DO_DAEMONIZE */\n\n/*\n * Close most of parent's fds.\n * Keep stdin/stdout/stderr, plus one\n * other fd which is presumed to be\n * our pipe back to parent.\n * Admittedly, a bit of a kludge,\n * but posix doesn't give us a kind\n * of FD_CLOEXEC which will stop\n * fds from crossing a fork().\n */\nstatic void\nclose_fds_except(int keep)\n{\n    int i;\n    closelog();\n    for (i = 3; i <= 100; ++i)\n    {\n        if (i != keep)\n        {\n            close(i);\n        }\n    }\n}\n\n/*\n * Usually we ignore signals, because our parent will\n * deal with them.\n */\nstatic void\nset_signals(void)\n{\n    signal(SIGTERM, SIG_DFL);\n\n    signal(SIGINT, SIG_IGN);\n    signal(SIGHUP, SIG_IGN);\n    signal(SIGUSR1, SIG_IGN);\n    signal(SIGUSR2, SIG_IGN);\n    signal(SIGPIPE, SIG_IGN);\n}\n\n/*\n * Return 1 if query matches match.\n */\nstatic int\nname_value_match(const char *query, const char *match)\n{\n    while (!isalnum(*query))\n    {\n        if (*query == '\\0')\n        {\n            return 0;\n        }\n        ++query;\n    }\n    return strncasecmp(match, query, strlen(match)) == 0;\n}\n\n/*\n * Split and decode up->password in the form SCRV1:base64_pass:base64_response\n * into pass and response and save in up->password and up->response.\n * If the password is not in the expected format, input is not changed.\n */\nstatic void\nsplit_scrv1_password(struct user_pass *up)\n{\n    const int skip = strlen(\"SCRV1:\");\n    if (strncmp(up->password, \"SCRV1:\", skip) != 0)\n    {\n        return;\n    }\n\n    char *tmp = strdup(up->password);\n    if (!tmp)\n    {\n        plugin_log(PLOG_ERR, MODULE, \"out of memory parsing static challenge password\");\n        goto out;\n    }\n\n    char *pass = tmp + skip;\n    char *resp = strchr(pass, ':');\n    if (!resp) /* string not in SCRV1:xx:yy format */\n    {\n        goto out;\n    }\n    *resp++ = '\\0';\n\n    int n = plugin_base64_decode(pass, up->password, sizeof(up->password) - 1);\n    if (n >= 0)\n    {\n        up->password[n] = '\\0';\n        n = plugin_base64_decode(resp, up->response, sizeof(up->response) - 1);\n        if (n >= 0)\n        {\n            up->response[n] = '\\0';\n            if (DEBUG(up->verb))\n            {\n                plugin_log(PLOG_NOTE, MODULE, \"BACKGROUND: parsed static challenge password\");\n            }\n            goto out;\n        }\n    }\n\n    /* decode error: reinstate original value of up->password and return */\n    plugin_secure_memzero(up->password, sizeof(up->password));\n    plugin_secure_memzero(up->response, sizeof(up->response));\n    strcpy(up->password, tmp); /* tmp is guaranteed to fit in up->password */\n\n    plugin_log(PLOG_ERR, MODULE, \"base64 decode error while parsing static challenge password\");\n\nout:\n    if (tmp)\n    {\n        plugin_secure_memzero(tmp, strlen(tmp));\n        free(tmp);\n    }\n}\n\nOPENVPN_EXPORT int\nopenvpn_plugin_open_v3(const int v3structver, struct openvpn_plugin_args_open_in const *args,\n                       struct openvpn_plugin_args_open_return *ret)\n{\n    pid_t pid;\n    int fd[2];\n\n    struct auth_pam_context *context;\n    struct name_value_list name_value_list;\n\n    const int base_parms = 2;\n\n    const char **argv = args->argv;\n    const char **envp = args->envp;\n\n    /* Check API compatibility -- struct version 5 or higher needed */\n    if (v3structver < 5)\n    {\n        fprintf(stderr,\n                \"AUTH-PAM: This plugin is incompatible with the running version of OpenVPN\\n\");\n        return OPENVPN_PLUGIN_FUNC_ERROR;\n    }\n\n    /*\n     * Allocate our context\n     */\n    context = (struct auth_pam_context *)calloc(1, sizeof(struct auth_pam_context));\n    if (!context)\n    {\n        goto error;\n    }\n    context->foreground_fd = -1;\n\n    /*\n     * Intercept the --auth-user-pass-verify callback.\n     */\n    ret->type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY);\n\n    /* Save global pointers to functions exported from openvpn */\n    plugin_log = args->callbacks->plugin_log;\n    plugin_secure_memzero = args->callbacks->plugin_secure_memzero;\n    plugin_base64_decode = args->callbacks->plugin_base64_decode;\n\n    /*\n     * Make sure we have two string arguments: the first is the .so name,\n     * the second is the PAM service type.\n     */\n    if (string_array_len(argv) < base_parms)\n    {\n        plugin_log(PLOG_ERR, MODULE, \"need PAM service parameter\");\n        goto error;\n    }\n\n    /*\n     * See if we have optional name/value pairs to match against\n     * PAM module queried fields in the conversation function.\n     */\n    name_value_list.len = 0;\n    if (string_array_len(argv) > base_parms)\n    {\n        const int nv_len = string_array_len(argv) - base_parms;\n        int i;\n\n        if ((nv_len & 1) == 1 || (nv_len / 2) > N_NAME_VALUE)\n        {\n            plugin_log(PLOG_ERR, MODULE, \"bad name/value list length\");\n            goto error;\n        }\n\n        name_value_list.len = nv_len / 2;\n        for (i = 0; i < name_value_list.len; ++i)\n        {\n            const int base = base_parms + i * 2;\n            name_value_list.data[i].name = argv[base];\n            name_value_list.data[i].value = argv[base + 1];\n        }\n    }\n\n    /*\n     * Get verbosity level from environment\n     */\n    {\n        const char *verb_string = get_env(\"verb\", envp);\n        if (verb_string)\n        {\n            context->verb = atoi(verb_string);\n        }\n    }\n\n    /*\n     * Make a socket for foreground and background processes\n     * to communicate.\n     */\n    if (socketpair(PF_UNIX, SOCK_DGRAM, 0, fd) == -1)\n    {\n        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"socketpair call failed\");\n        goto error;\n    }\n\n    /*\n     * Fork off the privileged process.  It will remain privileged\n     * even after the foreground process drops its privileges.\n     */\n    pid = fork();\n\n    if (pid)\n    {\n        int status;\n\n        /*\n         * Foreground Process\n         */\n\n        context->background_pid = pid;\n\n        /* close our copy of child's socket */\n        close(fd[1]);\n\n        /* don't let future subprocesses inherit child socket */\n        if (fcntl(fd[0], F_SETFD, FD_CLOEXEC) < 0)\n        {\n            plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE,\n                       \"Set FD_CLOEXEC flag on socket file descriptor failed\");\n        }\n\n        /* wait for background child process to initialize */\n        status = recv_control(fd[0]);\n        if (status == RESPONSE_INIT_SUCCEEDED)\n        {\n            context->foreground_fd = fd[0];\n            ret->handle = (openvpn_plugin_handle_t *)context;\n            plugin_log(PLOG_NOTE, MODULE, \"initialization succeeded (fg)\");\n            return OPENVPN_PLUGIN_FUNC_SUCCESS;\n        }\n    }\n    else\n    {\n        /*\n         * Background Process\n         */\n\n        /* close all parent fds except our socket back to parent */\n        close_fds_except(fd[1]);\n\n        /* Ignore most signals (the parent will receive them) */\n        set_signals();\n\n#ifdef DO_DAEMONIZE\n        /* Daemonize if --daemon option is set. */\n        daemonize(envp);\n#endif\n\n        /* execute the event loop */\n        pam_server(fd[1], argv[1], context->verb, &name_value_list);\n\n        close(fd[1]);\n\n        exit(0);\n        return 0; /* NOTREACHED */\n    }\n\nerror:\n    free(context);\n    return OPENVPN_PLUGIN_FUNC_ERROR;\n}\n\nOPENVPN_EXPORT int\nopenvpn_plugin_func_v1(openvpn_plugin_handle_t handle, const int type, const char *argv[],\n                       const char *envp[])\n{\n    struct auth_pam_context *context = (struct auth_pam_context *)handle;\n\n    if (type == OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY && context->foreground_fd >= 0)\n    {\n        /* get username/password from envp string array */\n        const char *username = get_env(\"username\", envp);\n        const char *password = get_env(\"password\", envp);\n        const char *common_name = get_env(\"common_name\", envp) ? get_env(\"common_name\", envp) : \"\";\n        const char *remote = get_env(\"untrusted_ip6\", envp);\n\n        if (remote == NULL)\n        {\n            remote = get_env(\"untrusted_ip\", envp);\n        }\n\n        if (remote == NULL)\n        {\n            remote = \"\";\n        }\n\n        /* should we do deferred auth?\n         *  yes, if there is \"auth_control_file\" and \"deferred_auth_pam\" env\n         */\n        const char *auth_control_file = get_env(\"auth_control_file\", envp);\n        const char *deferred_auth_pam = get_env(\"deferred_auth_pam\", envp);\n        if (auth_control_file != NULL && deferred_auth_pam != NULL)\n        {\n            if (DEBUG(context->verb))\n            {\n                plugin_log(PLOG_NOTE, MODULE, \"do deferred auth '%s'\", auth_control_file);\n            }\n        }\n        else\n        {\n            auth_control_file = \"\";\n        }\n\n        if (username && strlen(username) > 0 && password)\n        {\n            if (send_control(context->foreground_fd, COMMAND_VERIFY) == -1\n                || send_string(context->foreground_fd, username) == -1\n                || send_string(context->foreground_fd, password) == -1\n                || send_string(context->foreground_fd, common_name) == -1\n                || send_string(context->foreground_fd, auth_control_file) == -1\n                || send_string(context->foreground_fd, remote) == -1)\n            {\n                plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE,\n                           \"Error sending auth info to background process\");\n            }\n            else\n            {\n                const int status = recv_control(context->foreground_fd);\n                if (status == RESPONSE_VERIFY_SUCCEEDED)\n                {\n                    return OPENVPN_PLUGIN_FUNC_SUCCESS;\n                }\n                if (status == RESPONSE_DEFER)\n                {\n                    if (DEBUG(context->verb))\n                    {\n                        plugin_log(PLOG_NOTE, MODULE, \"deferred authentication\");\n                    }\n                    return OPENVPN_PLUGIN_FUNC_DEFERRED;\n                }\n                if (status == -1)\n                {\n                    plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE,\n                               \"Error receiving auth confirmation from background process\");\n                }\n            }\n        }\n    }\n    return OPENVPN_PLUGIN_FUNC_ERROR;\n}\n\nOPENVPN_EXPORT void\nopenvpn_plugin_close_v1(openvpn_plugin_handle_t handle)\n{\n    struct auth_pam_context *context = (struct auth_pam_context *)handle;\n\n    if (DEBUG(context->verb))\n    {\n        plugin_log(PLOG_NOTE, MODULE, \"close\");\n    }\n\n    if (context->foreground_fd >= 0)\n    {\n        /* tell background process to exit */\n        if (send_control(context->foreground_fd, COMMAND_EXIT) == -1)\n        {\n            plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"Error signaling background process to exit\");\n        }\n\n        /* wait for background process to exit */\n        if (context->background_pid > 0)\n        {\n            waitpid(context->background_pid, NULL, 0);\n        }\n\n        close(context->foreground_fd);\n        context->foreground_fd = -1;\n    }\n\n    free(context);\n}\n\nOPENVPN_EXPORT void\nopenvpn_plugin_abort_v1(openvpn_plugin_handle_t handle)\n{\n    struct auth_pam_context *context = (struct auth_pam_context *)handle;\n\n    /* tell background process to exit */\n    if (context && context->foreground_fd >= 0)\n    {\n        send_control(context->foreground_fd, COMMAND_EXIT);\n        close(context->foreground_fd);\n        context->foreground_fd = -1;\n    }\n}\n\n/*\n * PAM conversation function\n */\nstatic int\nmy_conv(int num_msg, const struct pam_message **msg_array, struct pam_response **response_array,\n        void *appdata_ptr)\n{\n    const struct user_pass *up = (const struct user_pass *)appdata_ptr;\n    struct pam_response *aresp;\n    int ret = PAM_SUCCESS;\n\n    *response_array = NULL;\n\n    if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG)\n    {\n        return (PAM_CONV_ERR);\n    }\n    if ((aresp = calloc((size_t)num_msg, sizeof *aresp)) == NULL)\n    {\n        return (PAM_BUF_ERR);\n    }\n\n    /* loop through each PAM-module query */\n    for (int i = 0; i < num_msg; ++i)\n    {\n        const struct pam_message *msg = msg_array[i];\n        aresp[i].resp_retcode = 0;\n        aresp[i].resp = NULL;\n\n        if (DEBUG(up->verb))\n        {\n            plugin_log(PLOG_NOTE, MODULE, \"BACKGROUND: my_conv[%d] query='%s' style=%d\", i,\n                       msg->msg ? msg->msg : \"NULL\", msg->msg_style);\n        }\n\n        if (up->name_value_list && up->name_value_list->len > 0)\n        {\n            /* use name/value list match method */\n            const struct name_value_list *list = up->name_value_list;\n\n            /* loop through name/value pairs */\n            int j; /* checked after loop */\n            for (j = 0; j < list->len; ++j)\n            {\n                const char *match_name = list->data[j].name;\n                const char *match_value = list->data[j].value;\n\n                if (name_value_match(msg->msg, match_name))\n                {\n                    /* found name/value match */\n                    aresp[i].resp = NULL;\n\n                    if (DEBUG(up->verb))\n                    {\n                        plugin_log(\n                            PLOG_NOTE, MODULE,\n                            \"BACKGROUND: name match found, query/match-string ['%s', '%s'] = '%s'\",\n                            msg->msg, match_name, match_value);\n                    }\n\n                    if (strstr(match_value, \"USERNAME\"))\n                    {\n                        aresp[i].resp = searchandreplace(match_value, \"USERNAME\", up->username);\n                    }\n                    else if (strstr(match_value, \"PASSWORD\"))\n                    {\n                        aresp[i].resp = searchandreplace(match_value, \"PASSWORD\", up->password);\n                    }\n                    else if (strstr(match_value, \"COMMONNAME\"))\n                    {\n                        aresp[i].resp =\n                            searchandreplace(match_value, \"COMMONNAME\", up->common_name);\n                    }\n                    else if (strstr(match_value, \"OTP\"))\n                    {\n                        aresp[i].resp = searchandreplace(match_value, \"OTP\", up->response);\n                    }\n                    else\n                    {\n                        aresp[i].resp = strdup(match_value);\n                    }\n\n                    if (aresp[i].resp == NULL)\n                    {\n                        ret = PAM_CONV_ERR;\n                    }\n                    break;\n                }\n            }\n\n            if (j == list->len)\n            {\n                ret = PAM_CONV_ERR;\n            }\n        }\n        else\n        {\n            /* use PAM_PROMPT_ECHO_x hints */\n            switch (msg->msg_style)\n            {\n                case PAM_PROMPT_ECHO_OFF:\n                    aresp[i].resp = strdup(up->password);\n                    if (aresp[i].resp == NULL)\n                    {\n                        ret = PAM_CONV_ERR;\n                    }\n                    break;\n\n                case PAM_PROMPT_ECHO_ON:\n                    aresp[i].resp = strdup(up->username);\n                    if (aresp[i].resp == NULL)\n                    {\n                        ret = PAM_CONV_ERR;\n                    }\n                    break;\n\n                case PAM_ERROR_MSG:\n                case PAM_TEXT_INFO:\n                    break;\n\n                default:\n                    ret = PAM_CONV_ERR;\n                    break;\n            }\n        }\n    }\n\n    if (ret == PAM_SUCCESS)\n    {\n        *response_array = aresp;\n    }\n    else\n    {\n        free(aresp);\n    }\n\n    return ret;\n}\n\n/*\n * Return 1 if authenticated and 0 if failed.\n * Called once for every username/password\n * to be authenticated.\n */\nstatic int\npam_auth(const char *service, const struct user_pass *up)\n{\n    struct pam_conv conv;\n    pam_handle_t *pamh = NULL;\n    int status = PAM_SUCCESS;\n    int ret = 0;\n    const int name_value_list_provided = (up->name_value_list && up->name_value_list->len > 0);\n\n    /* Initialize PAM */\n    conv.conv = my_conv;\n    conv.appdata_ptr = (void *)up;\n    status = pam_start(service, name_value_list_provided ? NULL : up->username, &conv, &pamh);\n    if (status == PAM_SUCCESS)\n    {\n        /* Set PAM_RHOST environment variable */\n        if (*(up->remote))\n        {\n            status = pam_set_item(pamh, PAM_RHOST, up->remote);\n        }\n        /* Call PAM to verify username/password */\n        if (status == PAM_SUCCESS)\n        {\n            status = pam_authenticate(pamh, 0);\n        }\n        if (status == PAM_SUCCESS)\n        {\n            status = pam_acct_mgmt(pamh, 0);\n        }\n        if (status == PAM_SUCCESS)\n        {\n            ret = 1;\n        }\n\n        /* Output error message if failed */\n        if (!ret)\n        {\n            plugin_log(PLOG_ERR, MODULE, \"BACKGROUND: user '%s' failed to authenticate: %s\",\n                       up->username, pam_strerror(pamh, status));\n        }\n\n        /* Close PAM */\n        pam_end(pamh, status);\n    }\n\n    return ret;\n}\n\n/*\n * deferred auth handler\n *   - fork() (twice, to avoid the need for async wait / SIGCHLD handling)\n *   - query PAM stack via pam_auth()\n *   - send response back to OpenVPN via \"ac_file_name\"\n *\n * parent process returns \"0\" for \"fork() and wait() succeeded\",\n *                        \"-1\" for \"something went wrong, abort program\"\n */\n\nstatic void\ndo_deferred_pam_auth(int fd, const char *ac_file_name, const char *service,\n                     const struct user_pass *up)\n{\n    if (send_control(fd, RESPONSE_DEFER) == -1)\n    {\n        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"BACKGROUND: write error on response socket [4]\");\n        return;\n    }\n\n    /* double forking so we do not need to wait() for async auth kids */\n    pid_t p1 = fork();\n\n    if (p1 < 0)\n    {\n        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"BACKGROUND: fork(1) failed\");\n        return;\n    }\n    if (p1 != 0) /* parent */\n    {\n        waitpid(p1, NULL, 0);\n        return; /* parent's job succeeded */\n    }\n\n    /* child */\n    close(fd); /* socketpair no longer needed */\n\n    pid_t p2 = fork();\n    if (p2 < 0)\n    {\n        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"BACKGROUND: fork(2) failed\");\n        exit(1);\n    }\n\n    if (p2 != 0) /* new parent: exit right away */\n    {\n        exit(0);\n    }\n\n    /* grandchild */\n    plugin_log(PLOG_NOTE, MODULE, \"BACKGROUND: deferred auth for '%s', pid=%d\", up->username,\n               (int)getpid());\n\n    /* the rest is very simple: do PAM, write status byte to file, done */\n    int ac_fd = open(ac_file_name, O_WRONLY);\n    if (ac_fd < 0)\n    {\n        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"cannot open '%s' for writing\", ac_file_name);\n        exit(1);\n    }\n    int pam_success = pam_auth(service, up);\n\n    if (write(ac_fd, pam_success ? \"1\" : \"0\", 1) != 1)\n    {\n        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"cannot write to '%s'\", ac_file_name);\n    }\n    close(ac_fd);\n    plugin_log(PLOG_NOTE, MODULE, \"BACKGROUND: %s: deferred auth: PAM %s\", up->username,\n               pam_success ? \"succeeded\" : \"rejected\");\n    exit(0);\n}\n\n/*\n * Background process -- runs with privilege.\n */\nstatic void\npam_server(int fd, const char *service, int verb, const struct name_value_list *name_value_list)\n{\n    struct user_pass up;\n    char ac_file_name[PATH_MAX];\n    int command;\n#ifdef USE_PAM_DLOPEN\n    static const char pam_so[] = \"libpam.so\";\n#endif\n\n    /*\n     * Do initialization\n     */\n    if (DEBUG(verb))\n    {\n        plugin_log(PLOG_NOTE, MODULE, \"BACKGROUND: INIT service='%s'\", service);\n    }\n\n#ifdef USE_PAM_DLOPEN\n    /*\n     * Load PAM shared object\n     */\n    if (!dlopen_pam(pam_so))\n    {\n        plugin_log(PLOG_ERR, MODULE, \"BACKGROUND: could not load PAM lib %s: %s\", pam_so,\n                   dlerror());\n        send_control(fd, RESPONSE_INIT_FAILED);\n        goto done;\n    }\n#endif\n\n    /*\n     * Tell foreground that we initialized successfully\n     */\n    if (send_control(fd, RESPONSE_INIT_SUCCEEDED) == -1)\n    {\n        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE, \"BACKGROUND: write error on response socket [1]\");\n        goto done;\n    }\n\n    plugin_log(PLOG_NOTE, MODULE, \"BACKGROUND: initialization succeeded\");\n\n    /*\n     * Event loop\n     */\n    while (1)\n    {\n        memset(&up, 0, sizeof(up));\n        up.verb = verb;\n        up.name_value_list = name_value_list;\n\n        /* get a command from foreground process */\n        command = recv_control(fd);\n\n        if (DEBUG(verb))\n        {\n            plugin_log(PLOG_NOTE, MODULE, \"BACKGROUND: received command code: %d\", command);\n        }\n\n        switch (command)\n        {\n            case COMMAND_VERIFY:\n                if (recv_string(fd, up.username, sizeof(up.username)) == -1\n                    || recv_string(fd, up.password, sizeof(up.password)) == -1\n                    || recv_string(fd, up.common_name, sizeof(up.common_name)) == -1\n                    || recv_string(fd, ac_file_name, sizeof(ac_file_name)) == -1\n                    || recv_string(fd, up.remote, sizeof(up.remote)) == -1)\n                {\n                    plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE,\n                               \"BACKGROUND: read error on command channel: code=%d, exiting\",\n                               command);\n                    goto done;\n                }\n\n                if (DEBUG(verb))\n                {\n#if 0\n                    plugin_log(PLOG_NOTE, MODULE, \"BACKGROUND: USER/PASS: %s/%s\",\n                               up.username, up.password);\n#else\n                    plugin_log(PLOG_NOTE, MODULE, \"BACKGROUND: USER: %s\", up.username);\n                    plugin_log(PLOG_NOTE, MODULE, \"BACKGROUND: REMOTE: %s\", up.remote);\n#endif\n                }\n\n                /* If password is of the form SCRV1:base64:base64 split it up */\n                split_scrv1_password(&up);\n\n                /* client wants deferred auth\n                 */\n                if (strlen(ac_file_name) > 0)\n                {\n                    do_deferred_pam_auth(fd, ac_file_name, service, &up);\n                    break;\n                }\n\n\n                /* non-deferred auth: wait for pam result and send\n                 * result back via control socketpair\n                 */\n                if (pam_auth(service, &up)) /* Succeeded */\n                {\n                    if (send_control(fd, RESPONSE_VERIFY_SUCCEEDED) == -1)\n                    {\n                        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE,\n                                   \"BACKGROUND: write error on response socket [2]\");\n                        goto done;\n                    }\n                }\n                else /* Failed */\n                {\n                    if (send_control(fd, RESPONSE_VERIFY_FAILED) == -1)\n                    {\n                        plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE,\n                                   \"BACKGROUND: write error on response socket [3]\");\n                        goto done;\n                    }\n                }\n                plugin_secure_memzero(up.password, sizeof(up.password));\n                break;\n\n            case COMMAND_EXIT:\n                goto done;\n\n            case -1:\n                plugin_log(PLOG_ERR | PLOG_ERRNO, MODULE,\n                           \"BACKGROUND: read error on command channel\");\n                goto done;\n\n            default:\n                plugin_log(PLOG_ERR, MODULE, \"BACKGROUND: unknown command code: code=%d, exiting\",\n                           command);\n                goto done;\n        }\n        plugin_secure_memzero(up.response, sizeof(up.response));\n    }\ndone:\n    plugin_secure_memzero(up.password, sizeof(up.password));\n    plugin_secure_memzero(up.response, sizeof(up.response));\n#ifdef USE_PAM_DLOPEN\n    dlclose_pam();\n#endif\n    if (DEBUG(verb))\n    {\n        plugin_log(PLOG_NOTE, MODULE, \"BACKGROUND: EXIT\");\n    }\n\n    return;\n}\n"
  },
  {
    "path": "src/plugins/auth-pam/auth-pam.exports",
    "content": "openvpn_plugin_open_v3\nopenvpn_plugin_func_v1\nopenvpn_plugin_close_v1\nopenvpn_plugin_abort_v1\n"
  },
  {
    "path": "src/plugins/auth-pam/pamdl.c",
    "content": "#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n\n#ifdef USE_PAM_DLOPEN\n/*\n * If you want to dynamically load libpam using dlopen() or something,\n * then dlopen( ' this shared object ' ); It takes care of exporting\n * the right symbols to any modules loaded by libpam.\n *\n * Modified by JY for use with openvpn-pam-auth\n */\n\n#include <stdio.h>\n#include <dlfcn.h>\n#include <security/pam_appl.h>\n\n#include \"pamdl.h\"\n\nstatic void *libpam_h = NULL;\n\n#define RESOLVE_PAM_FUNCTION(x, y, z, err)                                         \\\n    {                                                                              \\\n        union                                                                      \\\n        {                                                                          \\\n            const void *tpointer;                                                  \\\n            y(*fn) z;                                                              \\\n        } fptr;                                                                    \\\n        fptr.tpointer = dlsym(libpam_h, #x);                                       \\\n        real_##x = fptr.fn;                                                        \\\n        if (real_##x == NULL)                                                      \\\n        {                                                                          \\\n            fprintf(stderr, \"PAMDL: unable to resolve '%s': %s\\n\", #x, dlerror()); \\\n            return err;                                                            \\\n        }                                                                          \\\n    }\n\nint\ndlopen_pam(const char *so)\n{\n    if (libpam_h == NULL)\n    {\n        libpam_h = dlopen(so, RTLD_GLOBAL | RTLD_NOW);\n    }\n    return libpam_h != NULL;\n}\n\nvoid\ndlclose_pam(void)\n{\n    if (libpam_h != NULL)\n    {\n        dlclose(libpam_h);\n        libpam_h = NULL;\n    }\n}\n\nint\npam_start(const char *service_name, const char *user, const struct pam_conv *pam_conversation,\n          pam_handle_t **pamh)\n{\n    int (*real_pam_start)(const char *, const char *, const struct pam_conv *, pam_handle_t **);\n    RESOLVE_PAM_FUNCTION(pam_start, int,\n                         (const char *, const char *, const struct pam_conv *, pam_handle_t **),\n                         PAM_ABORT);\n    return real_pam_start(service_name, user, pam_conversation, pamh);\n}\n\nint\npam_end(pam_handle_t *pamh, int pam_status)\n{\n    int (*real_pam_end)(pam_handle_t *, int);\n    RESOLVE_PAM_FUNCTION(pam_end, int, (pam_handle_t *, int), PAM_ABORT);\n    return real_pam_end(pamh, pam_status);\n}\n\nint\npam_set_item(pam_handle_t *pamh, int item_type, const void *item)\n{\n    int (*real_pam_set_item)(pam_handle_t *, int, const void *);\n    RESOLVE_PAM_FUNCTION(pam_set_item, int, (pam_handle_t *, int, const void *), PAM_ABORT);\n    return real_pam_set_item(pamh, item_type, item);\n}\n\nint\npam_get_item(const pam_handle_t *pamh, int item_type, const void **item)\n{\n    int (*real_pam_get_item)(const pam_handle_t *, int, const void **);\n    RESOLVE_PAM_FUNCTION(pam_get_item, int, (const pam_handle_t *, int, const void **), PAM_ABORT);\n    return real_pam_get_item(pamh, item_type, item);\n}\n\nint\npam_fail_delay(pam_handle_t *pamh, unsigned int musec_delay)\n{\n    int (*real_pam_fail_delay)(pam_handle_t *, unsigned int);\n    RESOLVE_PAM_FUNCTION(pam_fail_delay, int, (pam_handle_t *, unsigned int), PAM_ABORT);\n    return real_pam_fail_delay(pamh, musec_delay);\n}\n\ntypedef const char *const_char_pointer;\n\nconst_char_pointer\npam_strerror(pam_handle_t *pamh, int errnum)\n{\n    const_char_pointer (*real_pam_strerror)(pam_handle_t *, int);\n    RESOLVE_PAM_FUNCTION(pam_strerror, const_char_pointer, (pam_handle_t *, int), NULL);\n    return real_pam_strerror(pamh, errnum);\n}\n\nint\npam_putenv(pam_handle_t *pamh, const char *name_value)\n{\n    int (*real_pam_putenv)(pam_handle_t *, const char *);\n    RESOLVE_PAM_FUNCTION(pam_putenv, int, (pam_handle_t *, const char *), PAM_ABORT);\n    return real_pam_putenv(pamh, name_value);\n}\n\nconst_char_pointer\npam_getenv(pam_handle_t *pamh, const char *name)\n{\n    const_char_pointer (*real_pam_getenv)(pam_handle_t *, const char *);\n    RESOLVE_PAM_FUNCTION(pam_getenv, const_char_pointer, (pam_handle_t *, const char *), NULL);\n    return real_pam_getenv(pamh, name);\n}\n\ntypedef char **char_ppointer;\nchar_ppointer\npam_getenvlist(pam_handle_t *pamh)\n{\n    char_ppointer (*real_pam_getenvlist)(pam_handle_t *);\n    RESOLVE_PAM_FUNCTION(pam_getenvlist, char_ppointer, (pam_handle_t *), NULL);\n    return real_pam_getenvlist(pamh);\n}\n\n/* Authentication management */\n\nint\npam_authenticate(pam_handle_t *pamh, int flags)\n{\n    int (*real_pam_authenticate)(pam_handle_t *, int);\n    RESOLVE_PAM_FUNCTION(pam_authenticate, int, (pam_handle_t *, int), PAM_ABORT);\n    return real_pam_authenticate(pamh, flags);\n}\n\nint\npam_setcred(pam_handle_t *pamh, int flags)\n{\n    int (*real_pam_setcred)(pam_handle_t *, int);\n    RESOLVE_PAM_FUNCTION(pam_setcred, int, (pam_handle_t *, int), PAM_ABORT);\n    return real_pam_setcred(pamh, flags);\n}\n\n/* Account Management API's */\n\nint\npam_acct_mgmt(pam_handle_t *pamh, int flags)\n{\n    int (*real_pam_acct_mgmt)(pam_handle_t *, int);\n    RESOLVE_PAM_FUNCTION(pam_acct_mgmt, int, (pam_handle_t *, int), PAM_ABORT);\n    return real_pam_acct_mgmt(pamh, flags);\n}\n\n/* Session Management API's */\n\nint\npam_open_session(pam_handle_t *pamh, int flags)\n{\n    int (*real_pam_open_session)(pam_handle_t *, int);\n    RESOLVE_PAM_FUNCTION(pam_open_session, int, (pam_handle_t *, int), PAM_ABORT);\n    return real_pam_open_session(pamh, flags);\n}\n\nint\npam_close_session(pam_handle_t *pamh, int flags)\n{\n    int (*real_pam_close_session)(pam_handle_t *, int);\n    RESOLVE_PAM_FUNCTION(pam_close_session, int, (pam_handle_t *, int), PAM_ABORT);\n    return real_pam_close_session(pamh, flags);\n}\n\n/* Password Management API's */\n\nint\npam_chauthtok(pam_handle_t *pamh, int flags)\n{\n    int (*real_pam_chauthtok)(pam_handle_t *, int);\n    RESOLVE_PAM_FUNCTION(pam_chauthtok, int, (pam_handle_t *, int), PAM_ABORT);\n    return real_pam_chauthtok(pamh, flags);\n}\n#endif /* ifdef USE_PAM_DLOPEN */\n"
  },
  {
    "path": "src/plugins/auth-pam/pamdl.h",
    "content": "#ifdef USE_PAM_DLOPEN\n/* Dynamically load and unload the PAM library */\nint dlopen_pam(const char *so);\n\nvoid dlclose_pam(void);\n\n#endif\n"
  },
  {
    "path": "src/plugins/auth-pam/utils.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * OpenVPN plugin module to do PAM authentication using a split\n * privilege model.\n */\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n\n\n#include <string.h>\n#include <ctype.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <sys/types.h>\n#include <stdint.h>\n\n#include \"utils.h\"\n\nchar *\nsearchandreplace(const char *tosearch, const char *searchfor, const char *replacewith)\n{\n    if (!tosearch || !searchfor || !replacewith)\n    {\n        return NULL;\n    }\n\n    size_t tosearchlen = strlen(tosearch);\n    size_t replacewithlen = strlen(replacewith);\n    size_t templen = tosearchlen * replacewithlen;\n\n    if (tosearchlen == 0 || strlen(searchfor) == 0 || replacewithlen == 0)\n    {\n        return NULL;\n    }\n\n    bool is_potential_integer_overflow =\n        (templen == SIZE_MAX) || (templen / tosearchlen != replacewithlen);\n\n    if (is_potential_integer_overflow)\n    {\n        return NULL;\n    }\n\n    /* state: all parameters are valid */\n\n    const char *searching = tosearch;\n\n    char temp[templen + 1];\n    temp[0] = 0;\n\n    const char *scratch = strstr(searching, searchfor);\n    if (!scratch)\n    {\n        return strdup(tosearch);\n    }\n\n    while (scratch)\n    {\n        strncat(temp, searching, (size_t)(scratch - searching));\n        strcat(temp, replacewith);\n\n        searching = scratch + strlen(searchfor);\n        scratch = strstr(searching, searchfor);\n    }\n    return strdup(temp);\n}\n\nconst char *\nget_env(const char *name, const char *envp[])\n{\n    if (envp)\n    {\n        const size_t namelen = strlen(name);\n        for (int i = 0; envp[i]; ++i)\n        {\n            if (!strncmp(envp[i], name, namelen))\n            {\n                const char *cp = envp[i] + namelen;\n                if (*cp == '=')\n                {\n                    return cp + 1;\n                }\n            }\n        }\n    }\n    return NULL;\n}\n\nint\nstring_array_len(const char *array[])\n{\n    int i = 0;\n    if (array)\n    {\n        while (array[i])\n        {\n            ++i;\n        }\n    }\n    return i;\n}\n"
  },
  {
    "path": "src/plugins/auth-pam/utils.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef _PLUGIN_AUTH_PAM_UTILS__H\n#define _PLUGIN_AUTH_PAM_UTILS__H\n\n/**\n *  Read 'tosearch', replace all occurrences of 'searchfor' with 'replacewith' and return\n *  a pointer to the NEW string.  Does not modify the input strings.  Will not enter an\n *  infinite loop with clever 'searchfor' and 'replacewith' strings.\n *\n *  @author Daniel Johnson - Progman2000@usa.net / djohnson@progman.us\n *\n *  @param tosearch      haystack to search in\n *  @param searchfor     needle to search for in the haystack\n *  @param replacewith   when a match is found, replace needle with this string\n *\n *  @return Returns NULL when any parameter is NULL or the worst-case result is to large ( >=\n * SIZE_MAX). Otherwise it returns a pointer to a new buffer containing the modified input\n */\nchar *searchandreplace(const char *tosearch, const char *searchfor, const char *replacewith);\n\n/**\n * Given an environmental variable name, search\n * the envp array for its value\n *\n * @param name  Environment variable to look up\n * @param envp  Environment variable table with all key/value pairs\n *\n * @return Returns a pointer to the value of the environment variable if found, otherwise NULL is\n * returned.\n */\nconst char *get_env(const char *name, const char *envp[]);\n\n/**\n * Return the length of a string array\n *\n * @param array   Pointer to the array to calculate size of\n *\n */\nint string_array_len(const char *array[]);\n\n#endif\n"
  },
  {
    "path": "src/plugins/down-root/Makefile.am",
    "content": "#\n#  OpenVPN (TM) Down Root Plugin -- OpenVPN Plugin\n#\n#  Copyright (C) 2012      Alon Bar-Lev <alon.barlev@gmail.com>\n#\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n\nAM_CFLAGS = \\\n\t-I$(top_srcdir)/include \\\n\t$(OPTIONAL_CRYPTO_CFLAGS)\n\nif ENABLE_PLUGIN_DOWN_ROOT\nplugin_LTLIBRARIES = openvpn-plugin-down-root.la\ndist_doc_DATA = README.down-root\nendif\n\nopenvpn_plugin_down_root_la_SOURCES = \\\n\tdown-root.c \\\n\tdown-root.exports\nopenvpn_plugin_down_root_la_LDFLAGS = $(AM_LDFLAGS) \\\n\t-export-symbols \"$(srcdir)/down-root.exports\" \\\n\t-module -shared -avoid-version -no-undefined\n"
  },
  {
    "path": "src/plugins/down-root/README.down-root",
    "content": "down-root -- an OpenVPN Plugin Module\n\nSYNOPSIS\n\nThe down-root module allows an OpenVPN configuration to\ncall a down script with root privileges, even when privileges\nhave been dropped using --user/--group/--chroot.\n\nThis module uses a split privilege execution model which will\nfork() before OpenVPN drops root privileges, at the point where\nthe --up script is usually called.  The module will then remain\nin a wait state until it receives a message from OpenVPN via\npipe to execute the down script.  Thus, the down script will be\nrun in the same execution environment as the up script.\n\nBUILD\n\nBuild this module with the \"make\" command.  The plugin\nmodule will be named openvpn-plugin-down-root.so\n\nUSAGE\n\nTo use this module, add to your OpenVPN config file:\n\n  plugin openvpn-plugin-down-root.so \"command ...\"\n\nCAVEATS\n\nThis module will only work on *nix systems, not Windows.\n"
  },
  {
    "path": "src/plugins/down-root/down-root.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2013      David Sommerseth <davids@redhat.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/*\n * OpenVPN plugin module to do privileged down-script execution.\n */\n\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <sys/wait.h>\n#include <fcntl.h>\n#include <signal.h>\n#include <syslog.h>\n#include <errno.h>\n#include <err.h>\n\n#include <openvpn-plugin.h>\n\n#define DEBUG(verb) ((verb) >= 7)\n\n/* Command codes for foreground -> background communication */\n#define COMMAND_RUN_SCRIPT 1\n#define COMMAND_EXIT       2\n\n/* Response codes for background -> foreground communication */\n#define RESPONSE_INIT_SUCCEEDED   10\n#define RESPONSE_INIT_FAILED      11\n#define RESPONSE_SCRIPT_SUCCEEDED 12\n#define RESPONSE_SCRIPT_FAILED    13\n\n/* Background process function */\nstatic void down_root_server(const int fd, char *const *argv, char *const *envp, const int verb);\n\n/*\n * Plugin state, used by foreground\n */\nstruct down_root_context\n{\n    /* Foreground's socket to background process */\n    int foreground_fd;\n\n    /* Process ID of background process */\n    pid_t background_pid;\n\n    /* Verbosity level of OpenVPN */\n    int verb;\n\n    /* down command */\n    char **command;\n};\n\n/*\n * Given an environmental variable name, search\n * the envp array for its value, returning it\n * if found or NULL otherwise.\n */\nstatic const char *\nget_env(const char *name, const char *envp[])\n{\n    if (envp)\n    {\n        const size_t namelen = strlen(name);\n        for (int i = 0; envp[i]; ++i)\n        {\n            if (!strncmp(envp[i], name, namelen))\n            {\n                const char *cp = envp[i] + namelen;\n                if (*cp == '=')\n                {\n                    return cp + 1;\n                }\n            }\n        }\n    }\n    return NULL;\n}\n\n/*\n * Return the length of a string array\n */\nstatic size_t\nstring_array_len(const char *array[])\n{\n    size_t i = 0;\n    if (array)\n    {\n        while (array[i])\n        {\n            ++i;\n        }\n    }\n    return i;\n}\n\n/*\n * Socket read/write functions.\n */\n\nstatic int\nrecv_control(int fd)\n{\n    unsigned char c;\n    const ssize_t size = read(fd, &c, sizeof(c));\n    if (size == sizeof(c))\n    {\n        return c;\n    }\n    else\n    {\n        return -1;\n    }\n}\n\nstatic ssize_t\nsend_control(int fd, int code)\n{\n    unsigned char c = (unsigned char)code;\n    const ssize_t size = write(fd, &c, sizeof(c));\n    if (size == sizeof(c))\n    {\n        return size;\n    }\n    else\n    {\n        return -1;\n    }\n}\n\n/*\n * Daemonize if \"daemon\" env var is true.\n * Preserve stderr across daemonization if\n * \"daemon_log_redirect\" env var is true.\n */\nstatic void\ndaemonize(const char *envp[])\n{\n    const char *daemon_string = get_env(\"daemon\", envp);\n    if (daemon_string && daemon_string[0] == '1')\n    {\n        const char *log_redirect = get_env(\"daemon_log_redirect\", envp);\n        int fd = -1;\n        if (log_redirect && log_redirect[0] == '1')\n        {\n            fd = dup(2);\n        }\n#if defined(__APPLE__) && defined(__clang__)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n#endif\n        if (daemon(0, 0) < 0)\n        {\n            warn(\"DOWN-ROOT: daemonization failed\");\n        }\n#if defined(__APPLE__) && defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n        else if (fd >= 3)\n        {\n            dup2(fd, 2);\n            close(fd);\n        }\n    }\n}\n\n/*\n * Close most of parent's fds.\n * Keep stdin/stdout/stderr, plus one\n * other fd which is presumed to be\n * our pipe back to parent.\n * Admittedly, a bit of a kludge,\n * but posix doesn't give us a kind\n * of FD_CLOEXEC which will stop\n * fds from crossing a fork().\n */\nstatic void\nclose_fds_except(int keep)\n{\n    int i;\n    closelog();\n    for (i = 3; i <= 100; ++i)\n    {\n        if (i != keep)\n        {\n            close(i);\n        }\n    }\n}\n\n/*\n * Usually we ignore signals, because our parent will\n * deal with them.\n */\nstatic void\nset_signals(void)\n{\n    signal(SIGTERM, SIG_DFL);\n\n    signal(SIGINT, SIG_IGN);\n    signal(SIGHUP, SIG_IGN);\n    signal(SIGUSR1, SIG_IGN);\n    signal(SIGUSR2, SIG_IGN);\n    signal(SIGPIPE, SIG_IGN);\n}\n\n\nstatic void\nfree_context(struct down_root_context *context)\n{\n    if (context)\n    {\n        free(context->command);\n        free(context);\n    }\n}\n\n/* Run the script using execve().  As execve() replaces the\n * current process with the new one, do a fork first before\n * calling execve()\n */\nstatic int\nrun_script(char *const *argv, char *const *envp)\n{\n    pid_t pid;\n    int ret = 0;\n\n    pid = fork();\n    if (pid == (pid_t)0) /* child side */\n    {\n        execve(argv[0], argv, envp);\n        /* If execve() fails to run, exit child with exit code 127 */\n        err(127, \"DOWN-ROOT: Failed execute: %s\", argv[0]);\n    }\n    else if (pid < (pid_t)0)\n    {\n        warn(\"DOWN-ROOT: Failed to fork child to run %s\", argv[0]);\n        return -1;\n    }\n    else /* parent side */\n    {\n        if (waitpid(pid, &ret, 0) != pid)\n        {\n            /* waitpid does not return error information via errno */\n            fprintf(stderr, \"DOWN-ROOT: waitpid() failed, don't know exit code of child (%s)\\n\",\n                    argv[0]);\n            return -1;\n        }\n    }\n    return ret;\n}\n\nOPENVPN_EXPORT openvpn_plugin_handle_t\nopenvpn_plugin_open_v1(unsigned int *type_mask, const char *argv[], const char *envp[])\n{\n    struct down_root_context *context;\n\n    /*\n     * Allocate our context\n     */\n    context = (struct down_root_context *)calloc(1, sizeof(struct down_root_context));\n    if (!context)\n    {\n        warn(\"DOWN-ROOT: Could not allocate memory for plug-in context\");\n        goto error;\n    }\n    context->foreground_fd = -1;\n\n    /*\n     * Intercept the --up and --down callbacks\n     */\n    *type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_UP) | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_DOWN);\n\n    /*\n     * Make sure we have two string arguments: the first is the .so name,\n     * the second is the script command.\n     */\n    if (string_array_len(argv) < 2)\n    {\n        fprintf(stderr, \"DOWN-ROOT: need down script command\\n\");\n        goto error;\n    }\n\n    /*\n     * Save the arguments in our context\n     */\n    context->command = calloc(string_array_len(argv), sizeof(char *));\n    if (!context->command)\n    {\n        warn(\"DOWN-ROOT: Could not allocate memory for command array\");\n        goto error;\n    }\n\n    /* Ignore argv[0], as it contains just the plug-in file name */\n    for (size_t i = 1; i < string_array_len(argv); i++)\n    {\n        context->command[i - 1] = (char *)argv[i];\n    }\n\n    /*\n     * Get verbosity level from environment\n     */\n    {\n        const char *verb_string = get_env(\"verb\", envp);\n        if (verb_string)\n        {\n            context->verb = atoi(verb_string);\n        }\n    }\n\n    return (openvpn_plugin_handle_t)context;\n\nerror:\n    free_context(context);\n    return NULL;\n}\n\nOPENVPN_EXPORT int\nopenvpn_plugin_func_v1(openvpn_plugin_handle_t handle, const int type, const char *argv[],\n                       const char *envp[])\n{\n    struct down_root_context *context = (struct down_root_context *)handle;\n\n    if (type == OPENVPN_PLUGIN_UP\n        && context->foreground_fd == -1) /* fork off a process to hold onto root */\n    {\n        pid_t pid;\n        int fd[2];\n\n        /*\n         * Make a socket for foreground and background processes\n         * to communicate.\n         */\n        if (socketpair(PF_UNIX, SOCK_DGRAM, 0, fd) == -1)\n        {\n            warn(\"DOWN-ROOT: socketpair call failed\");\n            return OPENVPN_PLUGIN_FUNC_ERROR;\n        }\n\n        /*\n         * Fork off the privileged process.  It will remain privileged\n         * even after the foreground process drops its privileges.\n         */\n        pid = fork();\n\n        if (pid)\n        {\n            int status;\n\n            /*\n             * Foreground Process\n             */\n\n            context->background_pid = pid;\n\n            /* close our copy of child's socket */\n            close(fd[1]);\n\n            /* don't let future subprocesses inherit child socket */\n            if (fcntl(fd[0], F_SETFD, FD_CLOEXEC) < 0)\n            {\n                warn(\"DOWN-ROOT: Set FD_CLOEXEC flag on socket file descriptor failed\");\n            }\n\n            /* wait for background child process to initialize */\n            status = recv_control(fd[0]);\n            if (status == RESPONSE_INIT_SUCCEEDED)\n            {\n                context->foreground_fd = fd[0];\n                return OPENVPN_PLUGIN_FUNC_SUCCESS;\n            }\n        }\n        else\n        {\n            /*\n             * Background Process\n             */\n\n            /* close all parent fds except our socket back to parent */\n            close_fds_except(fd[1]);\n\n            /* Ignore most signals (the parent will receive them) */\n            set_signals();\n\n            /* Daemonize if --daemon option is set. */\n            daemonize(envp);\n\n            /* execute the event loop */\n            down_root_server(fd[1], context->command, (char *const *)envp, context->verb);\n\n            close(fd[1]);\n            exit(0);\n            return 0; /* NOTREACHED */\n        }\n    }\n    else if (type == OPENVPN_PLUGIN_DOWN && context->foreground_fd >= 0)\n    {\n        if (send_control(context->foreground_fd, COMMAND_RUN_SCRIPT) == -1)\n        {\n            warn(\"DOWN-ROOT: Error sending script execution signal to background process\");\n        }\n        else\n        {\n            const int status = recv_control(context->foreground_fd);\n            if (status == RESPONSE_SCRIPT_SUCCEEDED)\n            {\n                return OPENVPN_PLUGIN_FUNC_SUCCESS;\n            }\n            if (status == -1)\n            {\n                warn(\n                    \"DOWN-ROOT: Error receiving script execution confirmation from background process\");\n            }\n        }\n    }\n    return OPENVPN_PLUGIN_FUNC_ERROR;\n}\n\nOPENVPN_EXPORT void\nopenvpn_plugin_close_v1(openvpn_plugin_handle_t handle)\n{\n    struct down_root_context *context = (struct down_root_context *)handle;\n\n    if (DEBUG(context->verb))\n    {\n        fprintf(stderr, \"DOWN-ROOT: close\\n\");\n    }\n\n    if (context->foreground_fd >= 0)\n    {\n        /* tell background process to exit */\n        if (send_control(context->foreground_fd, COMMAND_EXIT) == -1)\n        {\n            warn(\"DOWN-ROOT: Error signalling background process to exit\");\n        }\n\n        /* wait for background process to exit */\n        if (context->background_pid > 0)\n        {\n            waitpid(context->background_pid, NULL, 0);\n        }\n\n        close(context->foreground_fd);\n        context->foreground_fd = -1;\n    }\n\n    free_context(context);\n}\n\nOPENVPN_EXPORT void\nopenvpn_plugin_abort_v1(openvpn_plugin_handle_t handle)\n{\n    struct down_root_context *context = (struct down_root_context *)handle;\n\n    if (context && context->foreground_fd >= 0)\n    {\n        /* tell background process to exit */\n        send_control(context->foreground_fd, COMMAND_EXIT);\n        close(context->foreground_fd);\n        context->foreground_fd = -1;\n    }\n}\n\n/*\n * Background process -- runs with privilege.\n */\nstatic void\ndown_root_server(const int fd, char *const *argv, char *const *envp, const int verb)\n{\n    /*\n     * Do initialization\n     */\n    if (DEBUG(verb))\n    {\n        fprintf(stderr, \"DOWN-ROOT: BACKGROUND: INIT command='%s'\\n\", argv[0]);\n    }\n\n    /*\n     * Tell foreground that we initialized successfully\n     */\n    if (send_control(fd, RESPONSE_INIT_SUCCEEDED) == -1)\n    {\n        warn(\"DOWN-ROOT: BACKGROUND: write error on response socket [1]\");\n        goto done;\n    }\n\n    /*\n     * Event loop\n     */\n    while (1)\n    {\n        int command_code;\n        int exit_code = -1;\n\n        /* get a command from foreground process */\n        command_code = recv_control(fd);\n\n        if (DEBUG(verb))\n        {\n            fprintf(stderr, \"DOWN-ROOT: BACKGROUND: received command code: %d\\n\", command_code);\n        }\n\n        switch (command_code)\n        {\n            case COMMAND_RUN_SCRIPT:\n                if ((exit_code = run_script(argv, envp)) == 0) /* Succeeded */\n                {\n                    if (send_control(fd, RESPONSE_SCRIPT_SUCCEEDED) == -1)\n                    {\n                        warn(\"DOWN-ROOT: BACKGROUND: write error on response socket [2]\");\n                        goto done;\n                    }\n                }\n                else /* Failed */\n                {\n                    fprintf(stderr, \"DOWN-ROOT: BACKGROUND: %s exited with exit code %i\\n\", argv[0],\n                            exit_code);\n                    if (send_control(fd, RESPONSE_SCRIPT_FAILED) == -1)\n                    {\n                        warn(\"DOWN-ROOT: BACKGROUND: write error on response socket [3]\");\n                        goto done;\n                    }\n                }\n                break;\n\n            case COMMAND_EXIT:\n                goto done;\n\n            case -1:\n                warn(\"DOWN-ROOT: BACKGROUND: read error on command channel\");\n                goto done;\n\n            default:\n                fprintf(stderr, \"DOWN-ROOT: BACKGROUND: unknown command code: code=%d, exiting\\n\",\n                        command_code);\n                goto done;\n        }\n    }\n\ndone:\n    if (DEBUG(verb))\n    {\n        fprintf(stderr, \"DOWN-ROOT: BACKGROUND: EXIT\\n\");\n    }\n\n    return;\n}\n\n\n/*\n * Local variables:\n * c-file-style: \"bsd\"\n * c-basic-offset: 4\n * indent-tabs-mode: nil\n * End:\n */\n"
  },
  {
    "path": "src/plugins/down-root/down-root.exports",
    "content": "openvpn_plugin_open_v1\nopenvpn_plugin_func_v1\nopenvpn_plugin_close_v1\nopenvpn_plugin_abort_v1\n"
  },
  {
    "path": "src/tapctl/CMakeLists.txt",
    "content": "if (NOT WIN32)\n    return ()\nendif ()\n\nproject(tapctl)\n\nadd_executable(tapctl)\n\ntarget_include_directories(tapctl PRIVATE\n    ${CMAKE_CURRENT_BINARY_DIR}/../../\n    ../../include/\n    ../compat/\n    )\ntarget_sources(tapctl PRIVATE\n    basic.h\n    error.c error.h\n    main.c\n    tap.c tap.h\n    tapctl_resources.rc\n    )\ntarget_compile_options(tapctl PRIVATE\n    -D_UNICODE\n    -UNTDDI_VERSION\n    -D_WIN32_WINNT=_WIN32_WINNT_VISTA\n    )\ntarget_link_libraries(tapctl\n    advapi32.lib ole32.lib setupapi.lib)\nif (MINGW)\n    target_compile_options(tapctl PRIVATE -municode)\n    target_link_options(tapctl PRIVATE -municode)\nendif ()\n"
  },
  {
    "path": "src/tapctl/Makefile.am",
    "content": "#\n#  tapctl -- Utility to manipulate TUN/TAP interfaces on Windows\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n#\n#  This program is free software; you can redistribute it and/or modify\n#  it under the terms of the GNU General Public License version 2\n#  as published by the Free Software Foundation.\n#\n#  This program is distributed in the hope that it will be useful,\n#  but WITHOUT ANY WARRANTY; without even the implied warranty of\n#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n#  GNU General Public License for more details.\n#\n#  You should have received a copy of the GNU General Public License along\n#  with this program; if not, see <https://www.gnu.org/licenses/>.\n#\n\ninclude $(top_srcdir)/ltrc.inc\n\nMAINTAINERCLEANFILES = $(srcdir)/Makefile.in\n\nAM_CPPFLAGS = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat\n\nAM_CFLAGS = \\\n\t$(TAP_CFLAGS)\n\nEXTRA_DIST = \\\n\tCMakeLists.txt \\\n\ttapctl.exe.manifest\n\nif WIN32\nsbin_PROGRAMS = tapctl\ntapctl_CFLAGS = \\\n\t-municode -D_UNICODE \\\n\t-UNTDDI_VERSION -U_WIN32_WINNT \\\n\t-D_WIN32_WINNT=_WIN32_WINNT_VISTA\ntapctl_LDADD = -ladvapi32 -lole32 -lsetupapi\nendif\n\ntapctl_SOURCES = \\\n\tbasic.h \\\n\terror.c error.h \\\n\tmain.c \\\n\ttap.c tap.h \\\n\ttapctl_resources.rc\n"
  },
  {
    "path": "src/tapctl/basic.h",
    "content": "/*\n *  basic -- Basic macros\n *           https://community.openvpn.net/openvpn/wiki/Tapctl\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef BASIC_H\n#define BASIC_H\n\n/* We do not support non-unicode builds */\n#ifndef UNICODE\n#define UNICODE\n#endif\n\n#define PRIXGUID \"{%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX}\"\n#define PRIGUID_PARAM(g)                                                                     \\\n    (g).Data1, (g).Data2, (g).Data3, (g).Data4[0], (g).Data4[1], (g).Data4[2], (g).Data4[3], \\\n        (g).Data4[4], (g).Data4[5], (g).Data4[6], (g).Data4[7]\n#define PRIGUID_PARAM_REF(g)                                                         \\\n    &(g).Data1, &(g).Data2, &(g).Data3, &(g).Data4[0], &(g).Data4[1], &(g).Data4[2], \\\n        &(g).Data4[3], &(g).Data4[4], &(g).Data4[5], &(g).Data4[6], &(g).Data4[7]\n\n#define __L(q) L##q\n#define _L(q)  __L(q)\n\n#ifndef _In_\n#define _In_\n#endif\n#ifndef _In_opt_\n#define _In_opt_\n#endif\n#ifndef _In_z_\n#define _In_z_\n#endif\n#ifndef _Inout_\n#define _Inout_\n#endif\n#ifndef _Inout_opt_\n#define _Inout_opt_\n#endif\n#ifndef _Out_\n#define _Out_\n#endif\n#ifndef _Out_opt_\n#define _Out_opt_\n#endif\n#ifndef _Out_z_cap_\n#define _Out_z_cap_(n)\n#endif\n\n#endif /* ifndef BASIC_H */\n"
  },
  {
    "path": "src/tapctl/error.c",
    "content": "/*\n *  error -- OpenVPN compatible error reporting API\n *           https://community.openvpn.net/openvpn/wiki/Tapctl\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"error.h\"\n\n\n/* Globals */\nunsigned int x_debug_level; /* GLOBAL */\n\n\nvoid\nx_msg(const unsigned int flags, const char *format, ...)\n{\n    va_list arglist;\n    va_start(arglist, format);\n    x_msg_va(flags, format, arglist);\n    va_end(arglist);\n}\n"
  },
  {
    "path": "src/tapctl/error.h",
    "content": "/*\n *  error -- OpenVPN compatible error reporting API\n *           https://community.openvpn.net/openvpn/wiki/Tapctl\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef ERROR_H\n#define ERROR_H\n\n#include <stdarg.h>\n#include <stdbool.h>\n#include <stdlib.h>\n\n/*\n * These globals should not be accessed directly,\n * but rather through macros or inline functions defined below.\n */\nextern unsigned int x_debug_level;\nextern int x_msg_line_num;\n\n/* msg() flags */\n\n#define M_DEBUG_LEVEL (0x0F) /* debug level mask */\n\n#define M_FATAL    (1 << 4)  /* exit program */\n#define M_NONFATAL (1 << 5)  /* non-fatal error */\n#define M_WARN     (1 << 6)  /* call syslog with LOG_WARNING */\n#define M_DEBUG    (1 << 7)\n\n#define M_ERRNO (1 << 8)         /* show errno description */\n\n#define M_NOMUTE       (1 << 11) /* don't do mute processing */\n#define M_NOPREFIX     (1 << 12) /* don't show date/time prefix */\n#define M_USAGE_SMALL  (1 << 13) /* fatal options error, call usage_small */\n#define M_MSG_VIRT_OUT (1 << 14) /* output message through msg_status_output callback */\n#define M_OPTERR       (1 << 15) /* print \"Options error:\" prefix */\n#define M_NOLF         (1 << 16) /* don't print new line */\n#define M_NOIPREFIX    (1 << 17) /* don't print instance prefix */\n\n/* flag combinations which are frequently used */\n#define M_ERR    (M_FATAL | M_ERRNO)\n#define M_USAGE  (M_USAGE_SMALL | M_NOPREFIX | M_OPTERR)\n#define M_CLIENT (M_MSG_VIRT_OUT | M_NOMUTE | M_NOIPREFIX)\n\n\n/** Check muting filter */\nbool dont_mute(unsigned int flags);\n\n/* Macro to ensure (and teach static analysis tools) we exit on fatal errors */\n#ifdef _MSC_VER\n#pragma warning(disable : 4127) /* EXIT_FATAL(flags) macro raises \"warning C4127: conditional \\\n                                   expression is constant\" on each non M_FATAL invocation. */\n#endif\n#define EXIT_FATAL(flags)      \\\n    do                         \\\n    {                          \\\n        if ((flags) & M_FATAL) \\\n        {                      \\\n            _exit(1);          \\\n        }                      \\\n    } while (false)\n\n#define msg(flags, ...)                  \\\n    do                                   \\\n    {                                    \\\n        if (msg_test(flags))             \\\n        {                                \\\n            x_msg((flags), __VA_ARGS__); \\\n        }                                \\\n        EXIT_FATAL(flags);               \\\n    } while (false)\n#ifdef ENABLE_DEBUG\n#define dmsg(flags, ...)                 \\\n    do                                   \\\n    {                                    \\\n        if (msg_test(flags))             \\\n        {                                \\\n            x_msg((flags), __VA_ARGS__); \\\n        }                                \\\n        EXIT_FATAL(flags);               \\\n    } while (false)\n#else\n#define dmsg(flags, ...)\n#endif\n\nvoid x_msg(const unsigned int flags, const char *format, ...); /* should be called via msg above */\n\nvoid x_msg_va(const unsigned int flags, const char *format, va_list arglist);\n\n/* Inline functions */\n\nstatic inline bool\ncheck_debug_level(unsigned int level)\n{\n    return (level & M_DEBUG_LEVEL) <= x_debug_level;\n}\n\n/** Return true if flags represent and enabled, not muted log level */\nstatic inline bool\nmsg_test(unsigned int flags)\n{\n    return check_debug_level(flags) && dont_mute(flags);\n}\n\n#endif /* ifndef ERROR_H */\n"
  },
  {
    "path": "src/tapctl/main.c",
    "content": "/*\n *  tapctl -- Utility to manipulate TUN/TAP adapters on Windows\n *            https://community.openvpn.net/openvpn/wiki/Tapctl\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n\n#include \"tap.h\"\n#include \"error.h\"\n\n#include <objbase.h>\n#include <setupapi.h>\n#include <stdio.h>\n#include <wchar.h>\n\n#ifdef _MSC_VER\n#pragma comment(lib, \"ole32.lib\")\n#pragma comment(lib, \"setupapi.lib\")\n#endif\n\n\n/* clang-format off */\nconst WCHAR title_string[] =\n    _L(PACKAGE_NAME) L\" \" _L(PACKAGE_VERSION)\n;\n\nstatic const WCHAR usage_message[] =\n    L\"%ls\\n\"\n    L\"\\n\"\n    L\"Usage:\\n\"\n    L\"\\n\"\n    L\"tapctl <command> [<command specific options>]\\n\"\n    L\"\\n\"\n    L\"Commands:\\n\"\n    L\"\\n\"\n    L\"create     Create a new VPN network adapter\\n\"\n    L\"list       List VPN network adapters\\n\"\n    L\"delete     Delete specified VPN network adapter\\n\"\n    L\"help       Display this text\\n\"\n    L\"\\n\"\n    L\"Hint: Use \\\"tapctl help <command>\\\" to display help for particular command.\\n\"\n;\n\nstatic const WCHAR usage_message_create[] =\n    L\"%ls\\n\"\n    L\"\\n\"\n    L\"Creates a new VPN network adapter\\n\"\n    L\"\\n\"\n    L\"Usage:\\n\"\n    L\"\\n\"\n    L\"tapctl create [<options>]\\n\"\n    L\"\\n\"\n    L\"Options:\\n\"\n    L\"\\n\"\n    L\"--name <name>  Set VPN network adapter name. Should the adapter with given     \\n\"\n    L\"               name already exist, an error is returned. If this option is not \\n\"\n    L\"               specified, an OpenVPN-specific default name is chosen.          \\n\"\n    L\"               Note: This name can also be specified as OpenVPN's --dev-node   \\n\"\n    L\"               option.                                                         \\n\"\n    L\"--hwid <hwid>  Adapter hardware ID. Default value is ovpn-dco, which uses      \\n\"\n    L\"               the OpenVPN Data Channel Offload driver. To work with          \\n\"\n    L\"               tap-windows6 driver, specify root\\\\tap0901 or tap0901.         \\n\"\n    L\"\\n\"\n    L\"Output:\\n\"\n    L\"\\n\"\n    L\"This command prints newly created VPN network adapter's GUID, name and         \\n\"\n    L\"hardware ID to stdout.                                                         \\n\"\n;\n\nstatic const WCHAR usage_message_list[] =\n    L\"%ls\\n\"\n    L\"\\n\"\n    L\"Lists VPN network adapters\\n\"\n    L\"\\n\"\n    L\"Usage:\\n\"\n    L\"\\n\"\n    L\"tapctl list\\n\"\n    L\"\\n\"\n    L\"Options:\\n\"\n    L\"\\n\"\n    L\"--hwid <hwid>  Adapter hardware ID. By default, root\\\\tap0901, tap0901 and \\n\"\n    L\"               ovpn-dco adapters are listed. Use this switch to limit the list.\\n\"\n    L\"\\n\"\n    L\"Output:\\n\"\n    L\"\\n\"\n    L\"This command prints VPN network adapter GUID, name and hardware ID to stdout.  \\n\"\n;\n\nstatic const WCHAR usage_message_delete[] =\n    L\"%ls\\n\"\n    L\"\\n\"\n    L\"Deletes the specified VPN network adapter\\n\"\n    L\"\\n\"\n    L\"Usage:\\n\"\n    L\"\\n\"\n    L\"tapctl delete <adapter GUID | adapter name>\\n\"\n;\n/* clang-format on */\n\n\n/**\n * Print the help message.\n */\nstatic void\nusage(void)\n{\n    fwprintf(stderr, usage_message, title_string);\n}\n\n/**\n * Locate an adapter node by its friendly name within the enumerated list.\n *\n * @param name          Friendly name to search for. Comparison is case-insensitive.\n * @param adapter_list  Head of the adapter list returned by tap_list_adapters().\n *\n * @return Pointer to the matching node, or NULL when not found.\n */\nstatic struct tap_adapter_node *\nfind_adapter_by_name(LPCWSTR name, struct tap_adapter_node *adapter_list)\n{\n    for (struct tap_adapter_node *a = adapter_list; a; a = a->pNext)\n    {\n        if (_wcsicmp(name, a->szName) == 0)\n        {\n            return a;\n        }\n    }\n\n    return NULL;\n}\n\n/**\n * Check whether the registry still reserves a given network-connection name.\n *\n * Windows keeps friendly names under\n * \\\\HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Control\\\\Network\\\\{NETCLASS}\\\\{GUID}\\\\Connection\\\\Name,\n * even after an adapter is removed. netsh refuses to rename to any reserved name.\n *\n * @param name  Friendly name to test.\n *\n * @return TRUE if the name exists in the registry, FALSE otherwise.\n */\nstatic BOOL\nregistry_name_exists(LPCWSTR name)\n{\n    static const WCHAR class_key[] =\n        L\"SYSTEM\\\\CurrentControlSet\\\\Control\\\\Network\\\\{4d36e972-e325-11ce-bfc1-08002be10318}\";\n\n    HKEY hClassKey = NULL;\n    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, class_key, 0, KEY_READ, &hClassKey) != ERROR_SUCCESS)\n    {\n        return FALSE;\n    }\n\n    BOOL found = FALSE;\n\n    for (DWORD index = 0;; ++index)\n    {\n        WCHAR adapter_id[64];\n        DWORD adapter_id_len = _countof(adapter_id);\n        LONG result = RegEnumKeyEx(hClassKey, index, adapter_id, &adapter_id_len, NULL, NULL, NULL,\n                                   NULL);\n        if (result == ERROR_NO_MORE_ITEMS)\n        {\n            break;\n        }\n        else if (result != ERROR_SUCCESS)\n        {\n            continue;\n        }\n\n        WCHAR connection_key[512];\n        swprintf_s(connection_key, _countof(connection_key), L\"%ls\\\\%ls\\\\Connection\", class_key,\n                   adapter_id);\n\n        DWORD value_size = 0;\n        LONG query = RegGetValueW(HKEY_LOCAL_MACHINE, connection_key, L\"Name\",\n                                  RRF_RT_REG_SZ | RRF_NOEXPAND, NULL, NULL, &value_size);\n        if (query != ERROR_SUCCESS || value_size < sizeof(WCHAR))\n        {\n            continue;\n        }\n\n        LPWSTR value = (LPWSTR)malloc(value_size);\n        if (!value)\n        {\n            continue;\n        }\n\n        query = RegGetValueW(HKEY_LOCAL_MACHINE, connection_key, L\"Name\",\n                             RRF_RT_REG_SZ | RRF_NOEXPAND, NULL, value, &value_size);\n        if (query == ERROR_SUCCESS && _wcsicmp(name, value) == 0)\n        {\n            found = TRUE;\n            free(value);\n            break;\n        }\n\n        free(value);\n\n        if (found)\n        {\n            break;\n        }\n    }\n\n    RegCloseKey(hClassKey);\n    return found;\n}\n\n/**\n * Determine whether a friendly name is currently in use by an adapter or reserved\n * in the registry.\n *\n * @param name          Friendly name to test.\n * @param adapter_list  Head of the adapter list returned by tap_list_adapters().\n *\n * @return TRUE when the name is taken/reserved, FALSE when available.\n */\nstatic BOOL\ntap_name_in_use(LPCWSTR name, struct tap_adapter_node *adapter_list)\n{\n    if (name == NULL)\n    {\n        return FALSE;\n    }\n\n    if (find_adapter_by_name(name, adapter_list))\n    {\n        return TRUE;\n    }\n\n    return registry_name_exists(name);\n}\n\n/**\n * Check whether a proposed adapter name satisfies Windows connection-name rules.\n *\n * Tabs, control characters (except space), and the following characters are disallowed:\n * \\ / : * ? \" < > |\n * Names must also be non-empty and no longer than 255 characters.\n */\nBOOL\ntap_is_valid_adapter_name(LPCWSTR name)\n{\n    if (name == NULL)\n    {\n        return FALSE;\n    }\n\n    size_t length = wcslen(name);\n    if (length == 0 || length > 255)\n    {\n        return FALSE;\n    }\n\n    static const WCHAR invalid_chars[] = L\"\\\\/:*?\\\"<>|\";\n\n    for (const WCHAR *p = name; *p; ++p)\n    {\n        WCHAR ch = *p;\n        if (ch < L' ')\n        {\n            return FALSE;\n        }\n        if (wcschr(invalid_chars, ch))\n        {\n            return FALSE;\n        }\n    }\n\n    return TRUE;\n}\n\n/**\n * Resolve the adapter name we should apply:\n *   - For user-specified names, ensure they are unique both in the adapter list and\n *     in the registry. On conflict, an explanatory message is printed and NULL is returned.\n *   - For automatic naming, derive the base string from HWID and append the first available\n *     suffix recognised by Windows.\n *\n * @param requested_name  Name provided via CLI or configuration (may be NULL/empty).\n * @param hwid            Hardware identifier of the adapter being created.\n * @param adapter_list    Existing adapters enumerated via tap_list_adapters().\n *\n * @return Newly allocated wide string containing the final name, or NULL on failure.\n */\nstatic LPWSTR\ntap_resolve_adapter_name(LPCWSTR requested_name, LPCWSTR hwid,\n                         struct tap_adapter_node *adapter_list)\n{\n    if (requested_name && requested_name[0])\n    {\n        if (!tap_is_valid_adapter_name(requested_name))\n        {\n            fwprintf(stderr,\n                     L\"Adapter name \\\"%ls\\\" contains invalid characters. Avoid tabs or the \"\n                     L\"characters \\\\ / : * ? \\\" < > | and keep the length within 255 characters.\\n\",\n                     requested_name);\n            return NULL;\n        }\n\n        struct tap_adapter_node *conflict = find_adapter_by_name(requested_name, adapter_list);\n        if (conflict)\n        {\n            LPOLESTR adapter_id = NULL;\n            StringFromIID((REFIID)&conflict->guid, &adapter_id);\n            fwprintf(stderr,\n                     L\"Adapter \\\"%ls\\\" already exists (GUID %\"\n                     L\"ls).\\n\",\n                     conflict->szName, adapter_id);\n            CoTaskMemFree(adapter_id);\n            return NULL;\n        }\n\n        if (registry_name_exists(requested_name))\n        {\n            fwprintf(stderr, L\"Adapter name \\\"%ls\\\" is already in use.\\n\", requested_name);\n            return NULL;\n        }\n\n        return wcsdup(requested_name);\n    }\n\n    if (hwid == NULL)\n    {\n        return NULL;\n    }\n\n    LPCWSTR base_name = NULL;\n    if (_wcsicmp(hwid, L\"ovpn-dco\") == 0)\n    {\n        base_name = L\"OpenVPN Data Channel Offload\";\n    }\n    else if (_wcsicmp(hwid, L\"root\\\\\" _L(TAP_WIN_COMPONENT_ID)) == 0\n             || _wcsicmp(hwid, _L(TAP_WIN_COMPONENT_ID)) == 0)\n    {\n        base_name = L\"OpenVPN TAP-Windows6\";\n    }\n    else\n    {\n        fwprintf(stderr,\n                 L\"Cannot auto-generate adapter name for hardware ID \\\"%ls\\\".\\n\", hwid);\n        return NULL;\n    }\n\n    if (!tap_name_in_use(base_name, adapter_list))\n    {\n        return wcsdup(base_name);\n    }\n\n    size_t name_len = wcslen(base_name) + 10;\n    LPWSTR name = (LPWSTR)malloc(name_len * sizeof(WCHAR));\n    if (name == NULL)\n    {\n        return NULL;\n    }\n\n    /* Windows never assigns the \"#1\" suffix, so skip it to avoid netsh failures. */\n    for (int i = 2; i < 100; ++i)\n    {\n        swprintf_s(name, name_len, L\"%ls #%d\", base_name, i);\n\n        if (!tap_name_in_use(name, adapter_list))\n        {\n            return name;\n        }\n    }\n\n    free(name);\n    fwprintf(stderr, L\"Unable to find available adapter name based on \\\"%ls\\\".\\n\", base_name);\n    return NULL;\n}\n\nstatic int\ncommand_create(int argc, LPCWSTR argv[], BOOL *bRebootRequired)\n{\n    LPCWSTR szName = NULL;\n    LPCWSTR defaultHwId = L\"ovpn-dco\";\n    LPCWSTR szHwId = defaultHwId;\n    LPWSTR adapter_name = NULL;\n    struct tap_adapter_node *pAdapterList = NULL;\n    GUID guidAdapter;\n    LPOLESTR szAdapterId = NULL;\n    DWORD dwResult;\n    int iResult = 1;\n    BOOL adapter_created = FALSE;\n\n    for (int i = 2; i < argc; i++)\n    {\n        if (wcsicmp(argv[i], L\"--name\") == 0)\n        {\n            if (++i >= argc)\n            {\n                fwprintf(stderr, L\"--name option requires a value. Ignored.\\n\");\n                break;\n            }\n            szName = argv[i];\n            if (szName[0] == L'\\0')\n            {\n                fwprintf(stderr, L\"--name option cannot be empty. Ignored.\\n\");\n                szName = NULL;\n            }\n        }\n        else if (wcsicmp(argv[i], L\"--hwid\") == 0)\n        {\n            if (++i >= argc)\n            {\n                fwprintf(stderr,\n                         L\"--hwid option requires a value. Using default \\\"%ls\\\".\\n\",\n                         defaultHwId);\n                break;\n            }\n            szHwId = argv[i];\n            if (szHwId[0] == L'\\0')\n            {\n                fwprintf(stderr,\n                         L\"--hwid option cannot be empty. Using default \\\"%ls\\\".\\n\",\n                         defaultHwId);\n                szHwId = defaultHwId;\n            }\n        }\n        else\n        {\n            fwprintf(stderr,\n                     L\"Unknown option \\\"%ls\"\n                     L\"\\\". Please, use \\\"tapctl help create\\\" to list supported options. Ignored.\\n\",\n                     argv[i]);\n        }\n    }\n\n    dwResult = tap_create_adapter(NULL, L\"Virtual Ethernet\", szHwId, bRebootRequired,\n                                  &guidAdapter);\n    if (dwResult != ERROR_SUCCESS)\n    {\n        fwprintf(stderr, L\"Creating network adapter failed (error 0x%x).\\n\", dwResult);\n        goto cleanup;\n    }\n    adapter_created = TRUE;\n\n    dwResult = tap_list_adapters(NULL, NULL, &pAdapterList);\n    if (dwResult != ERROR_SUCCESS)\n    {\n        fwprintf(stderr, L\"Enumerating adapters failed (error 0x%x).\\n\", dwResult);\n        goto cleanup;\n    }\n\n    adapter_name = tap_resolve_adapter_name(szName, szHwId, pAdapterList);\n    if (adapter_name == NULL)\n    {\n        goto cleanup;\n    }\n\n    dwResult = tap_set_adapter_name(&guidAdapter, adapter_name, FALSE);\n    if (dwResult != ERROR_SUCCESS)\n    {\n        StringFromIID((REFIID)&guidAdapter, &szAdapterId);\n        fwprintf(stderr,\n                 L\"Renaming network adapter %ls to \\\"%ls\\\" failed (error 0x%x).\\n\", szAdapterId,\n                 adapter_name, dwResult);\n        CoTaskMemFree(szAdapterId);\n        goto cleanup;\n    }\n\n    iResult = 0;\n    StringFromIID((REFIID)&guidAdapter, &szAdapterId);\n    const WCHAR *name_to_print = (adapter_name && adapter_name[0]) ? adapter_name : L\"(unnamed)\";\n    const WCHAR *hwid_to_print = (szHwId && szHwId[0]) ? szHwId : L\"(unknown hwid)\";\n    fwprintf(stdout, L\"%ls\\t%ls\\t%ls\\n\", szAdapterId, name_to_print, hwid_to_print);\n    CoTaskMemFree(szAdapterId);\n\ncleanup:\n    if (pAdapterList)\n    {\n        tap_free_adapter_list(pAdapterList);\n    }\n    free(adapter_name);\n\n    if (adapter_created && iResult != 0)\n    {\n        tap_delete_adapter(NULL, &guidAdapter, bRebootRequired);\n    }\n\n    return iResult;\n}\n\nstatic int\ncommand_list(int argc, LPCWSTR argv[])\n{\n    WCHAR szzHwId[0x100] =\n        L\"root\\\\\" _L(TAP_WIN_COMPONENT_ID) L\"\\0\" _L(TAP_WIN_COMPONENT_ID) L\"\\0\"\n                                                                          L\"ovpn-dco\\0\";\n\n    for (int i = 2; i < argc; i++)\n    {\n        if (wcsicmp(argv[i], L\"--hwid\") == 0)\n        {\n            memset(szzHwId, 0, sizeof(szzHwId));\n            ++i;\n            memcpy_s(szzHwId,\n                     sizeof(szzHwId) - 2 * sizeof(WCHAR),\n                     argv[i], wcslen(argv[i]) * sizeof(WCHAR));\n        }\n        else\n        {\n            fwprintf(stderr,\n                     L\"Unknown option \\\"%ls\"\n                     L\"\\\". Please, use \\\"tapctl help list\\\" to list supported options. Ignored.\\n\",\n                     argv[i]);\n        }\n    }\n\n    struct tap_adapter_node *adapter_list = NULL;\n    DWORD dwResult = tap_list_adapters(NULL, szzHwId, &adapter_list);\n    if (dwResult != ERROR_SUCCESS)\n    {\n        fwprintf(stderr, L\"Enumerating TUN/TAP adapters failed (error 0x%x).\\n\", dwResult);\n        return 1;\n    }\n\n    for (struct tap_adapter_node *adapter = adapter_list; adapter; adapter = adapter->pNext)\n    {\n        LPOLESTR adapter_id = NULL;\n        StringFromIID((REFIID)&adapter->guid, &adapter_id);\n        const WCHAR *name = adapter->szName ? adapter->szName : L\"\";\n        const WCHAR *hwid = (adapter->szzHardwareIDs && adapter->szzHardwareIDs[0])\n                                ? adapter->szzHardwareIDs\n                                : L\"\";\n        fwprintf(stdout, L\"%ls\\t%ls\\t%ls\\n\", adapter_id, name, hwid);\n        CoTaskMemFree(adapter_id);\n    }\n\n    tap_free_adapter_list(adapter_list);\n\n    return 0;\n}\n\nstatic int\ncommand_delete(int argc, LPCWSTR argv[], BOOL *bRebootRequired)\n{\n    if (argc < 3)\n    {\n        fwprintf(stderr,\n                 L\"Missing adapter GUID or name. Please, use \\\"tapctl help delete\\\" for usage info.\\n\");\n        return 1;\n    }\n\n    GUID guidAdapter;\n    if (FAILED(IIDFromString(argv[2], (LPIID)&guidAdapter)))\n    {\n        struct tap_adapter_node *adapter_list = NULL;\n        DWORD dwResult = tap_list_adapters(NULL, NULL, &adapter_list);\n        if (dwResult != ERROR_SUCCESS)\n        {\n            fwprintf(stderr, L\"Enumerating TUN/TAP adapters failed (error 0x%x).\\n\", dwResult);\n            return 1;\n        }\n\n        BOOL found = FALSE;\n        for (struct tap_adapter_node *adapter = adapter_list; adapter; adapter = adapter->pNext)\n        {\n            if (wcsicmp(argv[2], adapter->szName) == 0)\n            {\n                memcpy(&guidAdapter, &adapter->guid, sizeof(GUID));\n                found = TRUE;\n                break;\n            }\n        }\n\n        tap_free_adapter_list(adapter_list);\n\n        if (!found)\n        {\n            fwprintf(stderr, L\"\\\"%ls\\\" adapter not found.\\n\", argv[2]);\n            return 1;\n        }\n    }\n\n    DWORD dwResult = tap_delete_adapter(NULL, &guidAdapter, bRebootRequired);\n    if (dwResult != ERROR_SUCCESS)\n    {\n        fwprintf(stderr,\n                 L\"Deleting adapter \\\"%ls\"\n                 L\"\\\" failed (error 0x%x).\\n\",\n                 argv[2], dwResult);\n        return 1;\n    }\n\n    return 0;\n}\n\n/**\n * Program entry point\n */\nint __cdecl\nwmain(int argc, LPCWSTR argv[])\n{\n    int iResult;\n    BOOL bRebootRequired = FALSE;\n\n    /* Ask SetupAPI to keep quiet. */\n    SetupSetNonInteractiveMode(TRUE);\n\n    if (argc < 2)\n    {\n        usage();\n        return 1;\n    }\n    else if (wcsicmp(argv[1], L\"help\") == 0)\n    {\n        /* Output help. */\n        if (argc < 3)\n        {\n            usage();\n        }\n        else if (wcsicmp(argv[2], L\"create\") == 0)\n        {\n            fwprintf(stderr, usage_message_create, title_string);\n        }\n        else if (wcsicmp(argv[2], L\"list\") == 0)\n        {\n            fwprintf(stderr, usage_message_list, title_string);\n        }\n        else if (wcsicmp(argv[2], L\"delete\") == 0)\n        {\n            fwprintf(stderr, usage_message_delete, title_string);\n        }\n        else\n        {\n            fwprintf(stderr,\n                     L\"Unknown command \\\"%ls\"\n                     L\"\\\". Please, use \\\"tapctl help\\\" to list supported commands.\\n\",\n                     argv[2]);\n        }\n\n        return 1;\n    }\n    else if (wcsicmp(argv[1], L\"create\") == 0)\n    {\n        iResult = command_create(argc, argv, &bRebootRequired);\n        goto quit;\n    }\n    else if (wcsicmp(argv[1], L\"list\") == 0)\n    {\n        iResult = command_list(argc, argv);\n        goto quit;\n    }\n    else if (wcsicmp(argv[1], L\"delete\") == 0)\n    {\n        iResult = command_delete(argc, argv, &bRebootRequired);\n        goto quit;\n    }\n    else\n    {\n        fwprintf(stderr,\n                 L\"Unknown command \\\"%ls\"\n                 L\"\\\". Please, use \\\"tapctl help\\\" to list supported commands.\\n\",\n                 argv[1]);\n        return 1;\n    }\n\nquit:\n    if (bRebootRequired)\n    {\n        fwprintf(stderr, L\"A system reboot is required.\\n\");\n    }\n\n    return iResult;\n}\n\n\nbool\ndont_mute(unsigned int flags)\n{\n    UNREFERENCED_PARAMETER(flags);\n\n    return true;\n}\n\n\nvoid\nx_msg_va(const unsigned int flags, const char *format, va_list arglist)\n{\n    /* Output message string. Note: Message strings don't contain line terminators. */\n    vfprintf(stderr, format, arglist);\n    fwprintf(stderr, L\"\\n\");\n\n    if ((flags & M_ERRNO) != 0)\n    {\n        /* Output system error message (if possible). */\n        DWORD dwResult = GetLastError();\n        LPWSTR szErrMessage = NULL;\n        if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER\n                              | FORMAT_MESSAGE_IGNORE_INSERTS,\n                          0, dwResult, 0, (LPWSTR)&szErrMessage, 0, NULL)\n            && szErrMessage)\n        {\n            /* Trim trailing whitespace. Set terminator after the last non-whitespace character.\n             * This prevents excessive trailing line breaks. */\n            for (size_t i = 0, i_last = 0;; i++)\n            {\n                if (szErrMessage[i])\n                {\n                    if (!iswspace(szErrMessage[i]))\n                    {\n                        i_last = i + 1;\n                    }\n                }\n                else\n                {\n                    szErrMessage[i_last] = 0;\n                    break;\n                }\n            }\n\n            /* Output error message. */\n            fwprintf(stderr, L\"Error 0x%x: %ls\\n\", dwResult, szErrMessage);\n\n            LocalFree(szErrMessage);\n        }\n        else\n        {\n            fwprintf(stderr, L\"Error 0x%x\\n\", dwResult);\n        }\n    }\n}\n"
  },
  {
    "path": "src/tapctl/tap.c",
    "content": "/*\n *  tapctl -- Utility to manipulate TUN/TAP adapters on Windows\n *            https://community.openvpn.net/openvpn/wiki/Tapctl\n *\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n\n#include \"tap.h\"\n#include \"error.h\"\n\n#include <windows.h>\n#include <cfgmgr32.h>\n#include <objbase.h>\n#include <setupapi.h>\n#include <stdio.h>\n#include <wchar.h>\n#include <newdev.h>\n\n#ifdef _MSC_VER\n#pragma comment(lib, \"advapi32.lib\")\n#pragma comment(lib, \"ole32.lib\")\n#pragma comment(lib, \"setupapi.lib\")\n#pragma comment(lib, \"newdev.lib\")\n#endif\n\n\nstatic const GUID GUID_DEVCLASS_NET = {\n    0x4d36e972L, 0xe325, 0x11ce, { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 }\n};\n\nstatic const WCHAR szAdapterRegKeyPathTemplate[] =\n    L\"SYSTEM\\\\CurrentControlSet\\\\Control\\\\Network\\\\%ls\\\\%ls\\\\Connection\";\n#define ADAPTER_REGKEY_PATH_MAX                                                                \\\n    (_countof(L\"SYSTEM\\\\CurrentControlSet\\\\Control\\\\Network\\\\\") - 1 + 38 + _countof(L\"\\\\\") - 1 \\\n     + 38 + _countof(L\"\\\\Connection\"))\n\n/**\n * Dynamically load a library and find a function in it\n *\n * @param libname     Name of the library to load\n * @param funcname    Name of the function to find\n * @param m           Pointer to a module. On return this is set to the\n *                    the handle to the loaded library. The caller must\n *                    free it by calling FreeLibrary() if not NULL.\n *\n * @return            Pointer to the function\n *                    NULL on error -- use GetLastError() to find the error code.\n *\n **/\nstatic void *\nfind_function(const WCHAR *libname, const char *funcname, HMODULE *m)\n{\n    WCHAR libpath[MAX_PATH];\n    void *fptr = NULL;\n\n    /* Make sure the dll is loaded from the system32 folder */\n    if (!GetSystemDirectoryW(libpath, _countof(libpath)))\n    {\n        return NULL;\n    }\n\n    /* +1 for the path seperator '\\' */\n    const size_t path_length = wcslen(libpath) + 1 + wcslen(libname);\n    if (path_length >= _countof(libpath))\n    {\n        SetLastError(ERROR_INSUFFICIENT_BUFFER);\n        return NULL;\n    }\n    wcscat_s(libpath, _countof(libpath), L\"\\\\\");\n    wcscat_s(libpath, _countof(libpath), libname);\n\n    *m = LoadLibraryW(libpath);\n    if (*m == NULL)\n    {\n        return NULL;\n    }\n    fptr = GetProcAddress(*m, funcname);\n    if (!fptr)\n    {\n        FreeLibrary(*m);\n        *m = NULL;\n        return NULL;\n    }\n    return fptr;\n}\n\n/**\n * Returns length of string of strings\n *\n * @param szz           Pointer to a string of strings (terminated by an empty string)\n *\n * @return Number of characters not counting the final zero terminator\n **/\nstatic inline size_t\nwcszlen(_In_z_ LPCWSTR szz)\n{\n    LPCWSTR s;\n    for (s = szz; s[0]; s += wcslen(s) + 1)\n    {\n    }\n    return s - szz;\n}\n\n\n/**\n * Checks if string is contained in the string of strings. Comparison is made case-insensitive.\n *\n * @param szzHay        Pointer to a string of strings (terminated by an empty string) we are\n *                      looking in\n *\n * @param szNeedle      The string we are searching for\n *\n * @return Pointer to the string in szzHay that matches szNeedle is found; NULL otherwise\n */\nstatic LPCWSTR\nwcszistr(_In_z_ LPCWSTR szzHay, _In_z_ LPCWSTR szNeedle)\n{\n    for (LPCWSTR s = szzHay; s[0]; s += wcslen(s) + 1)\n    {\n        if (wcsicmp(s, szNeedle) == 0)\n        {\n            return s;\n        }\n    }\n\n    return NULL;\n}\n\n\n/**\n * Function that performs a specific task on a device\n *\n * @param hDeviceInfoSet  A handle to a device information set that contains a device\n *                      information element that represents the device.\n *\n * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the\n *                      device information element in hDeviceInfoSet.\n *\n * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,\n *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This\n *                      allows the flag to be globally initialized to FALSE and reused for multiple\n *                      adapter manipulations.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n **/\ntypedef DWORD (*devop_func_t)(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,\n                              _Inout_ LPBOOL pbRebootRequired);\n\n\n/**\n * Checks device install parameters if a system reboot is required.\n *\n * @param hDeviceInfoSet  A handle to a device information set that contains a device\n *                      information element that represents the device.\n *\n * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the\n *                      device information element in hDeviceInfoSet.\n *\n * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,\n *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This\n *                      allows the flag to be globally initialized to FALSE and reused for multiple\n *                      adapter manipulations.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n **/\nstatic DWORD\ncheck_reboot(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,\n             _Inout_ LPBOOL pbRebootRequired)\n{\n    if (pbRebootRequired == NULL)\n    {\n        return ERROR_BAD_ARGUMENTS;\n    }\n\n    SP_DEVINSTALL_PARAMS devinstall_params = { .cbSize = sizeof(SP_DEVINSTALL_PARAMS) };\n    if (!SetupDiGetDeviceInstallParams(hDeviceInfoSet, pDeviceInfoData, &devinstall_params))\n    {\n        DWORD dwResult = GetLastError();\n        msg(M_NONFATAL | M_ERRNO, \"%s: SetupDiGetDeviceInstallParams failed\", __FUNCTION__);\n        return dwResult;\n    }\n\n    if ((devinstall_params.Flags & (DI_NEEDREBOOT | DI_NEEDRESTART)) != 0)\n    {\n        *pbRebootRequired = TRUE;\n    }\n\n    return ERROR_SUCCESS;\n}\n\n\n/**\n * Deletes the device.\n *\n * @param hDeviceInfoSet  A handle to a device information set that contains a device\n *                      information element that represents the device.\n *\n * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the\n *                      device information element in hDeviceInfoSet.\n *\n * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,\n *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This\n *                      allows the flag to be globally initialized to FALSE and reused for multiple\n *                      adapter manipulations.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n **/\nstatic DWORD\ndelete_device(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,\n              _Inout_ LPBOOL pbRebootRequired)\n{\n    SP_REMOVEDEVICE_PARAMS params = {\n        .ClassInstallHeader = {\n            .cbSize = sizeof(SP_CLASSINSTALL_HEADER),\n            .InstallFunction = DIF_REMOVE,\n        },\n        .Scope = DI_REMOVEDEVICE_GLOBAL,\n        .HwProfile = 0,\n    };\n\n    /* Set class installer parameters for DIF_REMOVE. */\n    if (!SetupDiSetClassInstallParams(hDeviceInfoSet, pDeviceInfoData, &params.ClassInstallHeader,\n                                      sizeof(SP_REMOVEDEVICE_PARAMS)))\n    {\n        DWORD dwResult = GetLastError();\n        msg(M_NONFATAL | M_ERRNO, \"%s: SetupDiSetClassInstallParams failed\", __FUNCTION__);\n        return dwResult;\n    }\n\n    /* Call appropriate class installer. */\n    if (!SetupDiCallClassInstaller(DIF_REMOVE, hDeviceInfoSet, pDeviceInfoData))\n    {\n        DWORD dwResult = GetLastError();\n        msg(M_NONFATAL | M_ERRNO, \"%s: SetupDiCallClassInstaller(DIF_REMOVE) failed\", __FUNCTION__);\n        return dwResult;\n    }\n\n    /* Check if a system reboot is required. */\n    check_reboot(hDeviceInfoSet, pDeviceInfoData, pbRebootRequired);\n    return ERROR_SUCCESS;\n}\n\n\n/**\n * Changes the device state.\n *\n * @param hDeviceInfoSet  A handle to a device information set that contains a device\n *                      information element that represents the device.\n *\n * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the\n *                      device information element in hDeviceInfoSet.\n *\n * @param bEnable       TRUE to enable the device; FALSE to disable.\n *\n * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,\n *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This\n *                      allows the flag to be globally initialized to FALSE and reused for multiple\n *                      adapter manipulations.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n **/\nstatic DWORD\nchange_device_state(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,\n                    _In_ BOOL bEnable, _Inout_ LPBOOL pbRebootRequired)\n{\n    SP_PROPCHANGE_PARAMS params = {\n        .ClassInstallHeader = {\n            .cbSize = sizeof(SP_CLASSINSTALL_HEADER),\n            .InstallFunction = DIF_PROPERTYCHANGE,\n        },\n        .StateChange = bEnable ? DICS_ENABLE : DICS_DISABLE,\n        .Scope = DICS_FLAG_GLOBAL,\n        .HwProfile = 0,\n    };\n\n    /* Set class installer parameters for DIF_PROPERTYCHANGE. */\n    if (!SetupDiSetClassInstallParams(hDeviceInfoSet, pDeviceInfoData, &params.ClassInstallHeader,\n                                      sizeof(SP_PROPCHANGE_PARAMS)))\n    {\n        DWORD dwResult = GetLastError();\n        msg(M_NONFATAL | M_ERRNO, \"%s: SetupDiSetClassInstallParams failed\", __FUNCTION__);\n        return dwResult;\n    }\n\n    /* Call appropriate class installer. */\n    if (!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDeviceInfoSet, pDeviceInfoData))\n    {\n        DWORD dwResult = GetLastError();\n        msg(M_NONFATAL | M_ERRNO, \"%s: SetupDiCallClassInstaller(DIF_PROPERTYCHANGE) failed\",\n            __FUNCTION__);\n        return dwResult;\n    }\n\n    /* Check if a system reboot is required. */\n    check_reboot(hDeviceInfoSet, pDeviceInfoData, pbRebootRequired);\n    return ERROR_SUCCESS;\n}\n\n\n/**\n * Enables the device.\n *\n * @param hDeviceInfoSet  A handle to a device information set that contains a device\n *                      information element that represents the device.\n *\n * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the\n *                      device information element in hDeviceInfoSet.\n *\n * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,\n *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This\n *                      allows the flag to be globally initialized to FALSE and reused for multiple\n *                      adapter manipulations.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n **/\nstatic DWORD\nenable_device(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,\n              _Inout_ LPBOOL pbRebootRequired)\n{\n    return change_device_state(hDeviceInfoSet, pDeviceInfoData, TRUE, pbRebootRequired);\n}\n\n\n/**\n * Disables the device.\n *\n * @param hDeviceInfoSet  A handle to a device information set that contains a device\n *                      information element that represents the device.\n *\n * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the\n *                      device information element in hDeviceInfoSet.\n *\n * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,\n *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This\n *                      allows the flag to be globally initialized to FALSE and reused for multiple\n *                      adapter manipulations.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n **/\nstatic DWORD\ndisable_device(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,\n               _Inout_ LPBOOL pbRebootRequired)\n{\n    return change_device_state(hDeviceInfoSet, pDeviceInfoData, FALSE, pbRebootRequired);\n}\n\n\n/**\n * Reads string value from registry key.\n *\n * @param hKey          Handle of the registry key to read from. Must be opened with read\n *                      access.\n *\n * @param szName        Name of the value to read.\n *\n * @param pszValue      Pointer to string to retrieve registry value. If the value type is\n *                      REG_EXPAND_SZ the value is expanded using ExpandEnvironmentStrings().\n *                      The string must be released with free() after use.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n */\nstatic DWORD\nget_reg_string(_In_ HKEY hKey, _In_ LPCWSTR szName, _Out_ LPWSTR *pszValue)\n{\n    if (pszValue == NULL)\n    {\n        return ERROR_BAD_ARGUMENTS;\n    }\n\n    DWORD dwValueType = REG_NONE, dwSize = 0;\n    DWORD dwResult = RegQueryValueEx(hKey, szName, NULL, &dwValueType, NULL, &dwSize);\n    if (dwResult != ERROR_SUCCESS)\n    {\n        SetLastError(dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError().\n                                   But we do have an error code. Set last error manually. */\n        msg(M_NONFATAL | M_ERRNO, \"%s: enumerating \\\"%ls\\\" registry value failed\", __FUNCTION__,\n            szName);\n        return dwResult;\n    }\n\n    switch (dwValueType)\n    {\n        case REG_SZ:\n        case REG_EXPAND_SZ:\n        {\n            /* Read value. */\n            LPWSTR szValue = (LPWSTR)malloc(dwSize);\n            if (szValue == NULL)\n            {\n                msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__, dwSize);\n                return ERROR_OUTOFMEMORY;\n            }\n\n            dwResult = RegQueryValueEx(hKey, szName, NULL, NULL, (LPBYTE)szValue, &dwSize);\n            if (dwResult != ERROR_SUCCESS)\n            {\n                SetLastError(\n                    dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But\n                                  we do have an error code. Set last error manually. */\n                msg(M_NONFATAL | M_ERRNO, \"%s: reading \\\"%ls\\\" registry value failed\", __FUNCTION__,\n                    szName);\n                free(szValue);\n                return dwResult;\n            }\n\n            if (dwValueType == REG_EXPAND_SZ)\n            {\n                /* Expand the environment strings. */\n                DWORD\n                dwSizeExp = dwSize * 2, dwCountExp =\n#ifdef UNICODE\n                                            dwSizeExp / sizeof(WCHAR);\n#else\n                                            dwSizeExp / sizeof(WCHAR)\n                                            - 1; /* Note: ANSI version requires one extra char. */\n#endif\n                LPWSTR szValueExp = (LPWSTR)malloc(dwSizeExp);\n                if (szValueExp == NULL)\n                {\n                    free(szValue);\n                    msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__, dwSizeExp);\n                    return ERROR_OUTOFMEMORY;\n                }\n\n                DWORD dwCountExpResult = ExpandEnvironmentStrings(szValue, szValueExp, dwCountExp);\n                if (dwCountExpResult == 0)\n                {\n                    msg(M_NONFATAL | M_ERRNO, \"%s: expanding \\\"%ls\\\" registry value failed\",\n                        __FUNCTION__, szName);\n                    free(szValueExp);\n                    free(szValue);\n                    return dwResult;\n                }\n                else if (dwCountExpResult <= dwCountExp)\n                {\n                    /* The buffer was big enough. */\n                    free(szValue);\n                    *pszValue = szValueExp;\n                    return ERROR_SUCCESS;\n                }\n                else\n                {\n                    /* Retry with a bigger buffer. */\n                    free(szValueExp);\n#ifdef UNICODE\n                    dwSizeExp = dwCountExpResult * sizeof(WCHAR);\n#else\n                    /* Note: ANSI version requires one extra char. */\n                    dwSizeExp = (dwCountExpResult + 1) * sizeof(WCHAR);\n#endif\n                    dwCountExp = dwCountExpResult;\n                    szValueExp = (LPWSTR)malloc(dwSizeExp);\n                    if (szValueExp == NULL)\n                    {\n                        free(szValue);\n                        msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__, dwSizeExp);\n                        return ERROR_OUTOFMEMORY;\n                    }\n\n                    dwCountExpResult = ExpandEnvironmentStrings(szValue, szValueExp, dwCountExp);\n                    free(szValue);\n                    *pszValue = szValueExp;\n                    return ERROR_SUCCESS;\n                }\n            }\n            else\n            {\n                *pszValue = szValue;\n                return ERROR_SUCCESS;\n            }\n        }\n\n        default:\n            msg(M_NONFATAL, \"%s: \\\"%ls\\\" registry value is not string (type %u)\", __FUNCTION__,\n                dwValueType);\n            return ERROR_UNSUPPORTED_TYPE;\n    }\n}\n\n\n/**\n * Returns network adapter ID.\n *\n * @param hDeviceInfoSet  A handle to a device information set that contains a device\n *                      information element that represents the device.\n *\n * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the\n *                      device information element in hDeviceInfoSet.\n *\n * @param iNumAttempts  After the device is created, it might take some time before the\n *                      registry key is populated. This parameter specifies the number of\n *                      attempts to read NetCfgInstanceId value from registry. A 1sec sleep\n *                      is inserted between retry attempts.\n *\n * @param pguidAdapter  A pointer to GUID that receives network adapter ID.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n **/\nstatic DWORD\nget_net_adapter_guid(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,\n                     _In_ int iNumAttempts, _Out_ LPGUID pguidAdapter)\n{\n    DWORD dwResult = ERROR_BAD_ARGUMENTS;\n\n    if (pguidAdapter == NULL || iNumAttempts < 1)\n    {\n        return ERROR_BAD_ARGUMENTS;\n    }\n\n    /* Open HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Class\\<class>\\<id> registry key. */\n    HKEY hKey = SetupDiOpenDevRegKey(hDeviceInfoSet, pDeviceInfoData, DICS_FLAG_GLOBAL, 0,\n                                     DIREG_DRV, KEY_READ);\n    if (hKey == INVALID_HANDLE_VALUE)\n    {\n        dwResult = GetLastError();\n        msg(M_NONFATAL | M_ERRNO, \"%s: SetupDiOpenDevRegKey failed\", __FUNCTION__);\n        return dwResult;\n    }\n\n    while (iNumAttempts > 0)\n    {\n        /* Query the NetCfgInstanceId value. Using get_reg_string() right on might clutter the\n         * output with error messages while the registry is still being populated. */\n        LPWSTR szCfgGuidString = NULL;\n        dwResult = RegQueryValueEx(hKey, L\"NetCfgInstanceId\", NULL, NULL, NULL, NULL);\n        if (dwResult != ERROR_SUCCESS)\n        {\n            if (dwResult == ERROR_FILE_NOT_FOUND && --iNumAttempts > 0)\n            {\n                /* Wait and retry. */\n                Sleep(1000);\n                continue;\n            }\n\n            SetLastError(\n                dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But we\n                              do have an error code. Set last error manually. */\n            msg(M_NONFATAL | M_ERRNO, \"%s: querying \\\"NetCfgInstanceId\\\" registry value failed\",\n                __FUNCTION__);\n            break;\n        }\n\n        /* Read the NetCfgInstanceId value now. */\n        dwResult = get_reg_string(hKey, L\"NetCfgInstanceId\", &szCfgGuidString);\n        if (dwResult != ERROR_SUCCESS)\n        {\n            break;\n        }\n\n        dwResult = SUCCEEDED(CLSIDFromString(szCfgGuidString, (LPCLSID)pguidAdapter))\n                       ? ERROR_SUCCESS\n                       : ERROR_INVALID_DATA;\n        free(szCfgGuidString);\n        break;\n    }\n\n    RegCloseKey(hKey);\n    return dwResult;\n}\n\n\n/**\n * Returns a specified Plug and Play device property.\n *\n * @param hDeviceInfoSet  A handle to a device information set that contains a device\n *                      information element that represents the device.\n *\n * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the\n *                      device information element in hDeviceInfoSet.\n *\n * @param dwProperty     Specifies the property to be retrieved. See\n *                       https://msdn.microsoft.com/en-us/library/windows/hardware/ff551967.aspx\n *\n * @param pdwPropertyRegDataType  A pointer to a variable that receives the data type of the\n *                       property that is being retrieved. This is one of the standard\n *                       registry data types. This parameter is optional and can be NULL.\n *\n * @param ppData         A pointer to pointer to data that receives the device property. The\n *                       data must be released with free() after use.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n **/\nstatic DWORD\nget_device_reg_property(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,\n                        _In_ DWORD dwProperty, _Out_opt_ LPDWORD pdwPropertyRegDataType,\n                        _Out_ LPVOID *ppData)\n{\n    DWORD dwResult = ERROR_BAD_ARGUMENTS;\n\n    if (ppData == NULL)\n    {\n        return ERROR_BAD_ARGUMENTS;\n    }\n\n    /* Try with stack buffer first. */\n    BYTE bBufStack[128];\n    DWORD dwRequiredSize = 0;\n    if (SetupDiGetDeviceRegistryProperty(hDeviceInfoSet, pDeviceInfoData, dwProperty,\n                                         pdwPropertyRegDataType, bBufStack, sizeof(bBufStack),\n                                         &dwRequiredSize))\n    {\n        /* Copy from stack. */\n        *ppData = malloc(dwRequiredSize);\n        if (*ppData == NULL)\n        {\n            msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__, dwRequiredSize);\n            return ERROR_OUTOFMEMORY;\n        }\n\n        memcpy(*ppData, bBufStack, dwRequiredSize);\n        return ERROR_SUCCESS;\n    }\n    else\n    {\n        dwResult = GetLastError();\n        if (dwResult == ERROR_INSUFFICIENT_BUFFER)\n        {\n            /* Allocate on heap and retry. */\n            *ppData = malloc(dwRequiredSize);\n            if (*ppData == NULL)\n            {\n                msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__, dwRequiredSize);\n                return ERROR_OUTOFMEMORY;\n            }\n\n            if (SetupDiGetDeviceRegistryProperty(hDeviceInfoSet, pDeviceInfoData, dwProperty,\n                                                 pdwPropertyRegDataType, *ppData, dwRequiredSize,\n                                                 &dwRequiredSize))\n            {\n                return ERROR_SUCCESS;\n            }\n            else\n            {\n                dwResult = GetLastError();\n                msg(M_NONFATAL | M_ERRNO, \"%s: SetupDiGetDeviceRegistryProperty(%u) failed\",\n                    __FUNCTION__, dwProperty);\n                return dwResult;\n            }\n        }\n        else\n        {\n            msg(M_NONFATAL | M_ERRNO, \"%s: SetupDiGetDeviceRegistryProperty(%u) failed\",\n                __FUNCTION__, dwProperty);\n            return dwResult;\n        }\n    }\n}\n\n\nDWORD\ntap_create_adapter(_In_opt_ HWND hwndParent, _In_opt_ LPCWSTR szDeviceDescription,\n                   _In_ LPCWSTR szHwId, _Inout_ LPBOOL pbRebootRequired, _Out_ LPGUID pguidAdapter)\n{\n    DWORD dwResult;\n    HMODULE libnewdev = NULL;\n\n    if (szHwId == NULL || pbRebootRequired == NULL || pguidAdapter == NULL)\n    {\n        return ERROR_BAD_ARGUMENTS;\n    }\n\n    /* Create an empty device info set for network adapter device class. */\n    HDEVINFO hDevInfoList = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_NET, hwndParent);\n    if (hDevInfoList == INVALID_HANDLE_VALUE)\n    {\n        dwResult = GetLastError();\n        msg(M_NONFATAL, \"%s: SetupDiCreateDeviceInfoList failed\", __FUNCTION__);\n        return dwResult;\n    }\n\n    /* Get the device class name from GUID. */\n    WCHAR szClassName[MAX_CLASS_NAME_LEN];\n    if (!SetupDiClassNameFromGuid(&GUID_DEVCLASS_NET, szClassName, _countof(szClassName), NULL))\n    {\n        dwResult = GetLastError();\n        msg(M_NONFATAL, \"%s: SetupDiClassNameFromGuid failed\", __FUNCTION__);\n        goto cleanup_hDevInfoList;\n    }\n\n    /* Create a new device info element and add it to the device info set. */\n    SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };\n    if (!SetupDiCreateDeviceInfo(hDevInfoList, szClassName, &GUID_DEVCLASS_NET, szDeviceDescription,\n                                 hwndParent, DICD_GENERATE_ID, &devinfo_data))\n    {\n        dwResult = GetLastError();\n        msg(M_NONFATAL, \"%s: SetupDiCreateDeviceInfo failed\", __FUNCTION__);\n        goto cleanup_hDevInfoList;\n    }\n\n    /* Set a device information element as the selected member of a device information set. */\n    if (!SetupDiSetSelectedDevice(hDevInfoList, &devinfo_data))\n    {\n        dwResult = GetLastError();\n        msg(M_NONFATAL, \"%s: SetupDiSetSelectedDevice failed\", __FUNCTION__);\n        goto cleanup_hDevInfoList;\n    }\n\n    /* Set Plug&Play device hardware ID property. */\n    if (!SetupDiSetDeviceRegistryProperty(hDevInfoList, &devinfo_data, SPDRP_HARDWAREID,\n                                          (const BYTE *)szHwId,\n                                          (DWORD)((wcslen(szHwId) + 1) * sizeof(WCHAR))))\n    {\n        dwResult = GetLastError();\n        msg(M_NONFATAL, \"%s: SetupDiSetDeviceRegistryProperty failed\", __FUNCTION__);\n        goto cleanup_hDevInfoList;\n    }\n\n    /* Register the device instance with the PnP Manager */\n    if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, hDevInfoList, &devinfo_data))\n    {\n        dwResult = GetLastError();\n        msg(M_NONFATAL, \"%s: SetupDiCallClassInstaller(DIF_REGISTERDEVICE) failed\", __FUNCTION__);\n        goto cleanup_hDevInfoList;\n    }\n\n    /* Install the device using DiInstallDevice()\n     * We instruct the system to use the best driver in the driver store\n     * by setting the drvinfo argument of DiInstallDevice as NULL. This\n     * assumes a driver is already installed in the driver store.\n     */\n#ifdef HAVE_DIINSTALLDEVICE\n    if (!DiInstallDevice(hwndParent, hDevInfoList, &devinfo_data, NULL, 0, pbRebootRequired))\n#else\n    /* mingw does not resolve DiInstallDevice, so load it at run time. */\n    typedef BOOL(WINAPI * DiInstallDeviceFn)(HWND, HDEVINFO, SP_DEVINFO_DATA *, SP_DRVINFO_DATA *,\n                                             DWORD, BOOL *);\n    DiInstallDeviceFn installfn = find_function(L\"newdev.dll\", \"DiInstallDevice\", &libnewdev);\n\n    if (!installfn)\n    {\n        dwResult = GetLastError();\n        msg(M_NONFATAL | M_ERRNO, \"%s: Failed to locate DiInstallDevice()\", __FUNCTION__);\n        goto cleanup_hDevInfoList;\n    }\n\n    if (!installfn(hwndParent, hDevInfoList, &devinfo_data, NULL, 0, pbRebootRequired))\n#endif\n    {\n        dwResult = GetLastError();\n        msg(M_NONFATAL | M_ERRNO, \"%s: DiInstallDevice failed\", __FUNCTION__);\n        goto cleanup_remove_device;\n    }\n\n    /* Get network adapter ID from registry. Retry for max 30sec. */\n    dwResult = get_net_adapter_guid(hDevInfoList, &devinfo_data, 30, pguidAdapter);\n\ncleanup_remove_device:\n    if (dwResult != ERROR_SUCCESS)\n    {\n        /* The adapter was installed. But, the adapter ID was unobtainable. Clean-up. */\n        SP_REMOVEDEVICE_PARAMS removedevice_params = {\n            .ClassInstallHeader = {\n                .cbSize = sizeof(SP_CLASSINSTALL_HEADER),\n                .InstallFunction = DIF_REMOVE,\n            },\n            .Scope = DI_REMOVEDEVICE_GLOBAL,\n            .HwProfile = 0,\n        };\n\n        /* Set class installer parameters for DIF_REMOVE. */\n        if (SetupDiSetClassInstallParams(hDevInfoList, &devinfo_data,\n                                         &removedevice_params.ClassInstallHeader,\n                                         sizeof(SP_REMOVEDEVICE_PARAMS)))\n        {\n            /* Call appropriate class installer. */\n            if (SetupDiCallClassInstaller(DIF_REMOVE, hDevInfoList, &devinfo_data))\n            {\n                /* Check if a system reboot is required. */\n                check_reboot(hDevInfoList, &devinfo_data, pbRebootRequired);\n            }\n            else\n            {\n                msg(M_NONFATAL | M_ERRNO, \"%s: SetupDiCallClassInstaller(DIF_REMOVE) failed\",\n                    __FUNCTION__);\n            }\n        }\n        else\n        {\n            msg(M_NONFATAL | M_ERRNO, \"%s: SetupDiSetClassInstallParams failed\", __FUNCTION__);\n        }\n    }\n\ncleanup_hDevInfoList:\n    if (libnewdev)\n    {\n        FreeLibrary(libnewdev);\n    }\n    SetupDiDestroyDeviceInfoList(hDevInfoList);\n    return dwResult;\n}\n\n\n/**\n * Performs a given task on an adapter.\n *\n * @param hwndParent    A handle to the top-level window to use for any user adapter that is\n *                      related to non-device-specific actions (such as a select-device dialog\n *                      box that uses the global class driver list). This handle is optional\n *                      and can be NULL. If a specific top-level window is not required, set\n *                      hwndParent to NULL.\n *\n * @param pguidAdapter  A pointer to GUID that contains network adapter ID.\n *\n * @param funcOperation  A pointer for the function to perform specific task on the adapter.\n *\n * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,\n *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This\n *                      allows the flag to be globally initialized to FALSE and reused for multiple\n *                      adapter manipulations.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n **/\nstatic DWORD\nexecute_on_first_adapter(_In_opt_ HWND hwndParent, _In_ LPCGUID pguidAdapter,\n                         _In_ devop_func_t funcOperation, _Inout_ LPBOOL pbRebootRequired)\n{\n    DWORD dwResult;\n\n    if (pguidAdapter == NULL)\n    {\n        return ERROR_BAD_ARGUMENTS;\n    }\n\n    /* Create a list of network devices. */\n    HDEVINFO hDevInfoList = SetupDiGetClassDevsEx(&GUID_DEVCLASS_NET, NULL, hwndParent,\n                                                  DIGCF_PRESENT, NULL, NULL, NULL);\n    if (hDevInfoList == INVALID_HANDLE_VALUE)\n    {\n        dwResult = GetLastError();\n        msg(M_NONFATAL, \"%s: SetupDiGetClassDevsEx failed\", __FUNCTION__);\n        return dwResult;\n    }\n\n    /* Retrieve information associated with a device information set. */\n    SP_DEVINFO_LIST_DETAIL_DATA devinfo_list_detail_data = { .cbSize = sizeof(\n                                                                 SP_DEVINFO_LIST_DETAIL_DATA) };\n    if (!SetupDiGetDeviceInfoListDetail(hDevInfoList, &devinfo_list_detail_data))\n    {\n        dwResult = GetLastError();\n        msg(M_NONFATAL, \"%s: SetupDiGetDeviceInfoListDetail failed\", __FUNCTION__);\n        goto cleanup_hDevInfoList;\n    }\n\n    /* Iterate. */\n    for (DWORD dwIndex = 0;; dwIndex++)\n    {\n        /* Get the device from the list. */\n        SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };\n        if (!SetupDiEnumDeviceInfo(hDevInfoList, dwIndex, &devinfo_data))\n        {\n            if (GetLastError() == ERROR_NO_MORE_ITEMS)\n            {\n                LPOLESTR szAdapterId = NULL;\n                StringFromIID((REFIID)pguidAdapter, &szAdapterId);\n                msg(M_NONFATAL, \"%s: Adapter %ls not found\", __FUNCTION__, szAdapterId);\n                CoTaskMemFree(szAdapterId);\n                dwResult = ERROR_FILE_NOT_FOUND;\n                goto cleanup_hDevInfoList;\n            }\n            else\n            {\n                /* Something is wrong with this device. Skip it. */\n                msg(M_WARN | M_ERRNO, \"%s: SetupDiEnumDeviceInfo(%u) failed\", __FUNCTION__,\n                    dwIndex);\n                continue;\n            }\n        }\n\n        /* Get adapter GUID. */\n        GUID guidAdapter;\n        dwResult = get_net_adapter_guid(hDevInfoList, &devinfo_data, 1, &guidAdapter);\n        if (dwResult != ERROR_SUCCESS)\n        {\n            /* Something is wrong with this device. Skip it. */\n            continue;\n        }\n\n        /* Compare GUIDs. */\n        if (memcmp(pguidAdapter, &guidAdapter, sizeof(GUID)) == 0)\n        {\n            dwResult = funcOperation(hDevInfoList, &devinfo_data, pbRebootRequired);\n            break;\n        }\n    }\n\ncleanup_hDevInfoList:\n    SetupDiDestroyDeviceInfoList(hDevInfoList);\n    return dwResult;\n}\n\n\nDWORD\ntap_delete_adapter(_In_opt_ HWND hwndParent, _In_ LPCGUID pguidAdapter,\n                   _Inout_ LPBOOL pbRebootRequired)\n{\n    return execute_on_first_adapter(hwndParent, pguidAdapter, delete_device, pbRebootRequired);\n}\n\n\nDWORD\ntap_enable_adapter(_In_opt_ HWND hwndParent, _In_ LPCGUID pguidAdapter, _In_ BOOL bEnable,\n                   _Inout_ LPBOOL pbRebootRequired)\n{\n    return execute_on_first_adapter(hwndParent, pguidAdapter,\n                                    bEnable ? enable_device : disable_device, pbRebootRequired);\n}\n\n/* stripped version of ExecCommand in interactive.c */\nstatic DWORD\nExecCommand(const WCHAR *cmdline)\n{\n    DWORD exit_code;\n    STARTUPINFOW si;\n    PROCESS_INFORMATION pi;\n    DWORD proc_flags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;\n    WCHAR *cmdline_dup = NULL;\n\n    ZeroMemory(&si, sizeof(si));\n    ZeroMemory(&pi, sizeof(pi));\n\n    si.cb = sizeof(si);\n\n    /* CreateProcess needs a modifiable cmdline: make a copy */\n    cmdline_dup = _wcsdup(cmdline);\n    if (cmdline_dup\n        && CreateProcessW(NULL, cmdline_dup, NULL, NULL, FALSE, proc_flags, NULL, NULL, &si, &pi))\n    {\n        WaitForSingleObject(pi.hProcess, INFINITE);\n        if (!GetExitCodeProcess(pi.hProcess, &exit_code))\n        {\n            exit_code = GetLastError();\n        }\n\n        CloseHandle(pi.hProcess);\n        CloseHandle(pi.hThread);\n    }\n    else\n    {\n        exit_code = GetLastError();\n    }\n\n    free(cmdline_dup);\n    return exit_code;\n}\n\nDWORD\ntap_set_adapter_name(_In_ LPCGUID pguidAdapter, _In_ LPCWSTR szName, _In_ BOOL bSilent)\n{\n    DWORD dwResult;\n    int msg_flag = bSilent ? M_WARN : M_NONFATAL;\n    msg_flag |= M_ERRNO;\n\n    if (pguidAdapter == NULL || szName == NULL)\n    {\n        return ERROR_BAD_ARGUMENTS;\n    }\n\n    /* Get the device class GUID as string. */\n    LPOLESTR szDevClassNetId = NULL;\n    StringFromIID((REFIID)&GUID_DEVCLASS_NET, &szDevClassNetId);\n\n    /* Get the adapter GUID as string. */\n    LPOLESTR szAdapterId = NULL;\n    StringFromIID((REFIID)pguidAdapter, &szAdapterId);\n\n    /* Render registry key path. */\n    WCHAR szRegKey[ADAPTER_REGKEY_PATH_MAX];\n    swprintf_s(szRegKey, _countof(szRegKey), szAdapterRegKeyPathTemplate, szDevClassNetId,\n               szAdapterId);\n\n    /* Open network adapter registry key. */\n    HKEY hKey = NULL;\n    dwResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szRegKey, 0, KEY_QUERY_VALUE, &hKey);\n    if (dwResult != ERROR_SUCCESS)\n    {\n        SetLastError(dwResult); /* MSDN does not mention RegOpenKeyEx() to set GetLastError(). But\n                                   we do have an error code. Set last error manually. */\n        msg(msg_flag, \"%s: RegOpenKeyEx(HKLM, \\\"%ls\\\") failed\", __FUNCTION__, szRegKey);\n        goto cleanup_szAdapterId;\n    }\n\n    LPWSTR szOldName = NULL;\n    dwResult = get_reg_string(hKey, L\"Name\", &szOldName);\n    if (dwResult != ERROR_SUCCESS)\n    {\n        SetLastError(dwResult);\n        msg(msg_flag, \"%s: Error reading adapter name\", __FUNCTION__);\n        goto cleanup_hKey;\n    }\n\n    /* rename adapter via netsh call */\n    const WCHAR *szFmt = L\"netsh interface set interface name=\\\"%\"\n                         L\"ls\\\" newname=\\\"%ls\\\"\";\n    size_t ncmdline = wcslen(szFmt) + wcslen(szOldName) + wcslen(szName) + 1;\n    WCHAR *szCmdLine = malloc(ncmdline * sizeof(WCHAR));\n    swprintf_s(szCmdLine, ncmdline, szFmt, szOldName, szName);\n\n    free(szOldName);\n\n    dwResult = ExecCommand(szCmdLine);\n    free(szCmdLine);\n\n    if (dwResult != ERROR_SUCCESS)\n    {\n        SetLastError(dwResult);\n        msg(msg_flag, \"%s: Error renaming adapter\", __FUNCTION__);\n        goto cleanup_hKey;\n    }\n\ncleanup_hKey:\n    RegCloseKey(hKey);\ncleanup_szAdapterId:\n    CoTaskMemFree(szAdapterId);\n    CoTaskMemFree(szDevClassNetId);\n    return dwResult;\n}\n\n\nDWORD\ntap_list_adapters(_In_opt_ HWND hwndParent, _In_opt_ LPCWSTR szzHwIDs,\n                  _Out_ struct tap_adapter_node **ppAdapter)\n{\n    DWORD dwResult;\n\n    if (ppAdapter == NULL)\n    {\n        return ERROR_BAD_ARGUMENTS;\n    }\n\n    /* Create a list of network devices. */\n    HDEVINFO hDevInfoList = SetupDiGetClassDevsEx(&GUID_DEVCLASS_NET, NULL, hwndParent,\n                                                  DIGCF_PRESENT, NULL, NULL, NULL);\n    if (hDevInfoList == INVALID_HANDLE_VALUE)\n    {\n        dwResult = GetLastError();\n        msg(M_NONFATAL, \"%s: SetupDiGetClassDevsEx failed\", __FUNCTION__);\n        return dwResult;\n    }\n\n    /* Retrieve information associated with a device information set. */\n    SP_DEVINFO_LIST_DETAIL_DATA devinfo_list_detail_data = { .cbSize = sizeof(\n                                                                 SP_DEVINFO_LIST_DETAIL_DATA) };\n    if (!SetupDiGetDeviceInfoListDetail(hDevInfoList, &devinfo_list_detail_data))\n    {\n        dwResult = GetLastError();\n        msg(M_NONFATAL, \"%s: SetupDiGetDeviceInfoListDetail failed\", __FUNCTION__);\n        goto cleanup_hDevInfoList;\n    }\n\n    /* Get the device class GUID as string. */\n    LPOLESTR szDevClassNetId = NULL;\n    StringFromIID((REFIID)&GUID_DEVCLASS_NET, &szDevClassNetId);\n\n    /* Iterate. */\n    *ppAdapter = NULL;\n    struct tap_adapter_node *pAdapterTail = NULL;\n    for (DWORD dwIndex = 0;; dwIndex++)\n    {\n        /* Get the device from the list. */\n        SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };\n        if (!SetupDiEnumDeviceInfo(hDevInfoList, dwIndex, &devinfo_data))\n        {\n            if (GetLastError() == ERROR_NO_MORE_ITEMS)\n            {\n                break;\n            }\n            else\n            {\n                /* Something is wrong with this device. Skip it. */\n                msg(M_WARN | M_ERRNO, \"%s: SetupDiEnumDeviceInfo(%u) failed\", __FUNCTION__,\n                    dwIndex);\n                continue;\n            }\n        }\n\n        /* Get device hardware ID(s). */\n        DWORD dwDataType = REG_NONE;\n        LPWSTR szzDeviceHardwareIDs = NULL;\n        dwResult = get_device_reg_property(hDevInfoList, &devinfo_data, SPDRP_HARDWAREID,\n                                           &dwDataType, (LPVOID)&szzDeviceHardwareIDs);\n        if (dwResult != ERROR_SUCCESS)\n        {\n            /* Something is wrong with this device. Skip it. */\n            continue;\n        }\n\n        /* Check that hardware ID is REG_SZ/REG_MULTI_SZ, and optionally if it matches ours. */\n        if (dwDataType == REG_SZ)\n        {\n            if (szzHwIDs && !wcszistr(szzHwIDs, szzDeviceHardwareIDs))\n            {\n                /* This is not our device. Skip it. */\n                goto cleanup_szzDeviceHardwareIDs;\n            }\n        }\n        else if (dwDataType == REG_MULTI_SZ)\n        {\n            if (szzHwIDs)\n            {\n                for (LPWSTR s = szzDeviceHardwareIDs;; s += wcslen(s) + 1)\n                {\n                    if (s[0] == 0)\n                    {\n                        /* This is not our device. Skip it. */\n                        goto cleanup_szzDeviceHardwareIDs;\n                    }\n                    else if (wcszistr(szzHwIDs, s))\n                    {\n                        /* This is our device. */\n                        break;\n                    }\n                }\n            }\n        }\n        else\n        {\n            /* Unexpected hardware ID format. Skip device. */\n            goto cleanup_szzDeviceHardwareIDs;\n        }\n\n        /* Get adapter GUID. */\n        GUID guidAdapter;\n        dwResult = get_net_adapter_guid(hDevInfoList, &devinfo_data, 1, &guidAdapter);\n        if (dwResult != ERROR_SUCCESS)\n        {\n            /* Something is wrong with this device. Skip it. */\n            goto cleanup_szzDeviceHardwareIDs;\n        }\n\n        /* Get the adapter GUID as string. */\n        LPOLESTR szAdapterId = NULL;\n        StringFromIID((REFIID)&guidAdapter, &szAdapterId);\n\n        /* Render registry key path. */\n        WCHAR szRegKey[ADAPTER_REGKEY_PATH_MAX];\n        swprintf_s(szRegKey, _countof(szRegKey), szAdapterRegKeyPathTemplate, szDevClassNetId,\n                   szAdapterId);\n\n        /* Open network adapter registry key. */\n        HKEY hKey = NULL;\n        dwResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szRegKey, 0, KEY_READ, &hKey);\n        if (dwResult != ERROR_SUCCESS)\n        {\n            SetLastError(dwResult); /* MSDN does not mention RegOpenKeyEx() to set GetLastError().\n                                       But we do have an error code. Set last error manually. */\n            msg(M_WARN | M_ERRNO, \"%s: RegOpenKeyEx(HKLM, \\\"%ls\\\") failed\", __FUNCTION__, szRegKey);\n            goto cleanup_szAdapterId;\n        }\n\n        /* Read adapter name. */\n        LPWSTR szName = NULL;\n        dwResult = get_reg_string(hKey, L\"Name\", &szName);\n        if (dwResult != ERROR_SUCCESS)\n        {\n            SetLastError(dwResult);\n            msg(M_WARN | M_ERRNO, \"%s: Cannot determine %ls adapter name\", __FUNCTION__,\n                szAdapterId);\n            goto cleanup_hKey;\n        }\n\n        /* Append to the list. */\n        size_t hwid_size = (wcszlen(szzDeviceHardwareIDs) + 1) * sizeof(WCHAR);\n        size_t name_size = (wcslen(szName) + 1) * sizeof(WCHAR);\n        struct tap_adapter_node *node = (struct tap_adapter_node *)malloc(\n            sizeof(struct tap_adapter_node) + hwid_size + name_size);\n        if (node == NULL)\n        {\n            msg(M_FATAL, \"%s: malloc(%u) failed\", __FUNCTION__,\n                sizeof(struct tap_adapter_node) + hwid_size + name_size);\n            dwResult = ERROR_OUTOFMEMORY;\n            goto cleanup_szName;\n        }\n\n        memcpy(&node->guid, &guidAdapter, sizeof(GUID));\n        node->szzHardwareIDs = (LPWSTR)(node + 1);\n        memcpy(node->szzHardwareIDs, szzDeviceHardwareIDs, hwid_size);\n        node->szName = (LPWSTR)((LPBYTE)node->szzHardwareIDs + hwid_size);\n        memcpy(node->szName, szName, name_size);\n        node->pNext = NULL;\n        if (pAdapterTail)\n        {\n            pAdapterTail->pNext = node;\n            pAdapterTail = node;\n        }\n        else\n        {\n            *ppAdapter = pAdapterTail = node;\n        }\n\ncleanup_szName:\n        free(szName);\ncleanup_hKey:\n        RegCloseKey(hKey);\ncleanup_szAdapterId:\n        CoTaskMemFree(szAdapterId);\ncleanup_szzDeviceHardwareIDs:\n        free(szzDeviceHardwareIDs);\n    }\n\n    dwResult = ERROR_SUCCESS;\n\n    CoTaskMemFree(szDevClassNetId);\ncleanup_hDevInfoList:\n    SetupDiDestroyDeviceInfoList(hDevInfoList);\n    return dwResult;\n}\n\n\nvoid\ntap_free_adapter_list(_In_ struct tap_adapter_node *pAdapterList)\n{\n    /* Iterate over all nodes of the list. */\n    while (pAdapterList)\n    {\n        struct tap_adapter_node *node = pAdapterList;\n        pAdapterList = pAdapterList->pNext;\n\n        /* Free the adapter node. */\n        free(node);\n    }\n}\n"
  },
  {
    "path": "src/tapctl/tap.h",
    "content": "/*\n *  tapctl -- Utility to manipulate TUN/TAP adapters on Windows\n *            https://community.openvpn.net/openvpn/wiki/Tapctl\n *\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef TAP_H\n#define TAP_H\n\n#include <windows.h>\n#include \"basic.h\"\n\n\n/**\n * Creates a TUN/TAP adapter.\n *\n * @param hwndParent    A handle to the top-level window to use for any user adapter that is\n *                      related to non-device-specific actions (such as a select-device dialog\n *                      box that uses the global class driver list). This handle is optional\n *                      and can be NULL. If a specific top-level window is not required, set\n *                      hwndParent to NULL.\n *\n * @param szDeviceDescription  A pointer to a NULL-terminated string that supplies the text\n *                      description of the device. This pointer is optional and can be NULL.\n *\n * @param szHwId        A pointer to a NULL-terminated string that supplies the hardware id\n *                      of the device (e.g. \"root\\\\tap0901\").\n *\n * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,\n *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This\n *                      allows the flag to be globally initialized to FALSE and reused for multiple\n *                      adapter manipulations.\n *\n * @param pguidAdapter  A pointer to GUID that receives network adapter ID.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n **/\nDWORD\ntap_create_adapter(_In_opt_ HWND hwndParent, _In_opt_ LPCWSTR szDeviceDescription,\n                   _In_ LPCWSTR szHwId, _Inout_ LPBOOL pbRebootRequired, _Out_ LPGUID pguidAdapter);\n\n\n/**\n * Deletes an adapter.\n *\n * @param hwndParent    A handle to the top-level window to use for any user adapter that is\n *                      related to non-device-specific actions (such as a select-device dialog\n *                      box that uses the global class driver list). This handle is optional\n *                      and can be NULL. If a specific top-level window is not required, set\n *                      hwndParent to NULL.\n *\n * @param pguidAdapter  A pointer to GUID that contains network adapter ID.\n *\n * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,\n *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This\n *                      allows the flag to be globally initialized to FALSE and reused for multiple\n *                      adapter manipulations.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n **/\nDWORD\ntap_delete_adapter(_In_opt_ HWND hwndParent, _In_ LPCGUID pguidAdapter,\n                   _Inout_ LPBOOL pbRebootRequired);\n\n\n/**\n * Enables or disables an adapter.\n *\n * @param hwndParent    A handle to the top-level window to use for any user adapter that is\n *                      related to non-device-specific actions (such as a select-device dialog\n *                      box that uses the global class driver list). This handle is optional\n *                      and can be NULL. If a specific top-level window is not required, set\n *                      hwndParent to NULL.\n *\n * @param pguidAdapter  A pointer to GUID that contains network adapter ID.\n *\n * @param bEnable       TRUE to enable; FALSE to disable\n *\n * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,\n *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This\n *                      allows the flag to be globally initialized to FALSE and reused for multiple\n *                      adapter manipulations.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n **/\nDWORD\ntap_enable_adapter(_In_opt_ HWND hwndParent, _In_ LPCGUID pguidAdapter, _In_ BOOL bEnable,\n                   _Inout_ LPBOOL pbRebootRequired);\n\n\n/**\n * Sets adapter name.\n *\n * @param pguidAdapter  A pointer to GUID that contains network adapter ID.\n *\n * @param szName        New adapter name - must be unique\n *\n * @param bSilent       If true, MSI installer won't display message box and\n *                      only print error to log.\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n **/\nDWORD\ntap_set_adapter_name(_In_ LPCGUID pguidAdapter, _In_ LPCWSTR szName, _In_ BOOL bSilent);\n\n\n/**\n * Network adapter list node\n */\nstruct tap_adapter_node\n{\n    GUID guid;                      /**< Adapter GUID */\n    LPWSTR szzHardwareIDs;          /**< Device hardware ID(s) */\n    LPWSTR szName;                  /**< Adapter name */\n\n    struct tap_adapter_node *pNext; /**< Pointer to next adapter */\n};\n\n\n/**\n * Creates a list of existing network adapters.\n *\n * @param hwndParent    A handle to the top-level window to use for any user adapter that is\n *                      related to non-device-specific actions (such as a select-device dialog\n *                      box that uses the global class driver list). This handle is optional\n *                      and can be NULL. If a specific top-level window is not required, set\n *                      hwndParent to NULL.\n *\n * @param szzHwIDs      A string of strings that supplies the list of hardware IDs of the device.\n *                      This pointer is optional and can be NULL. When NULL, all network adapters\n *                      found are added to the list.\n *\n * @param ppAdapterList  A pointer to the list to receive pointer to the first adapter in\n *                      the list. After the list is no longer required, free it using\n *                      tap_free_adapter_list().\n *\n * @return ERROR_SUCCESS on success; Win32 error code otherwise\n */\nDWORD\ntap_list_adapters(_In_opt_ HWND hwndParent, _In_opt_ LPCWSTR szzHwIDs,\n                  _Out_ struct tap_adapter_node **ppAdapterList);\n\n\n/**\n * Frees a list of network adapters.\n *\n * @param pAdapterList  A pointer to the first adapter in the list to free.\n */\nvoid tap_free_adapter_list(_In_ struct tap_adapter_node *pAdapterList);\n\n#endif /* ifndef TAP_H */\n"
  },
  {
    "path": "src/tapctl/tapctl_resources.rc",
    "content": "/*\n *  tapctl -- Utility to manipulate TUN/TAP adapters on Windows\n *\n *  Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include <config.h>\n#endif\n#include <winresrc.h>\n\n#pragma code_page(65001) /* UTF8 */\n\nLANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL\n\nVS_VERSION_INFO VERSIONINFO\n    FILEVERSION OPENVPN_VERSION_RESOURCE\n    PRODUCTVERSION OPENVPN_VERSION_RESOURCE\n    FILEFLAGSMASK VS_FF_DEBUG | VS_FF_PRERELEASE | VS_FF_PATCHED | VS_FF_PRIVATEBUILD | VS_FF_SPECIALBUILD\n#ifdef _DEBUG\n    FILEFLAGS VS_FF_DEBUG\n#else\n    FILEFLAGS 0x0L\n#endif\n    FILEOS VOS_NT_WINDOWS32\n    FILETYPE VFT_APP\n    FILESUBTYPE 0x0L\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"040904b0\"\n        BEGIN\n            VALUE \"CompanyName\", \"The OpenVPN Project\"\n            VALUE \"FileDescription\", \"Utility to manipulate TUN/TAP adapters on Windows\"\n            VALUE \"FileVersion\", PACKAGE_VERSION \".0\"\n            VALUE \"InternalName\", \"OpenVPN\"\n            VALUE \"LegalCopyright\", \"Copyright © The OpenVPN Project\"\n            VALUE \"OriginalFilename\", \"tapctl.exe\"\n            VALUE \"ProductName\", \"OpenVPN\"\n            VALUE \"ProductVersion\", PACKAGE_VERSION \".0\"\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x409, 1200\n    END\nEND\n\n1 RT_MANIFEST \"tapctl.exe.manifest\"\n"
  },
  {
    "path": "tests/Makefile.am",
    "content": "#\n#  OpenVPN -- An application to securely tunnel IP networks\n#             over a single UDP port, with support for SSL/TLS-based\n#             session authentication and key exchange,\n#             packet encryption, packet authentication, and\n#             packet compression.\n#\n#  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n#  Copyright (C) 2006-2012 Alon Bar-Lev <alon.barlev@gmail.com>\n#\n\nMAINTAINERCLEANFILES = \\\n\t$(srcdir)/Makefile.in\n\nSUBDIRS = unit_tests\n\nAM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING) System Tests'\nSH_LOG_DRIVER = $(SHELL) $(top_srcdir)/forked-test-driver\n\nif !WIN32\ntest_scripts = t_client.sh t_lpback.sh t_cltsrv.sh t_server_null.sh\n\ncheck_PROGRAMS = ntlm_support\nif HAVE_SITNL\ntest_scripts += t_net.sh\nendif\nendif\n\nTESTS_ENVIRONMENT = top_srcdir=\"$(top_srcdir)\"\nTEST_EXTENSIONS = .sh\nTESTS = $(test_scripts)\n\ndist_noinst_SCRIPTS = \\\n\tt_cltsrv.sh \\\n\tt_cltsrv-down.sh \\\n\tt_lpback.sh \\\n\tt_net.sh \\\n\tt_server_null.sh \\\n\tt_server_null_client.sh \\\n\tt_server_null_server.sh \\\n\tt_server_null_default.rc \\\n\tupdate_t_client_ips.sh\n\nt_client.log: t_server_null.log\n\ndist_noinst_DATA = \\\n\tt_client.rc-sample\n\nntlm_support_CFLAGS  = -I$(top_srcdir)/src/openvpn -I$(top_srcdir)/src/compat -I$(top_srcdir)/tests/unit_tests/openvpn -DNO_CMOCKA @TEST_CFLAGS@\nntlm_support_LDFLAGS = @TEST_LDFLAGS@ -L$(top_srcdir)/src/openvpn $(OPTIONAL_CRYPTO_LIBS)\nntlm_support_SOURCES = ntlm_support.c \\\n\tunit_tests/openvpn/mock_msg.c unit_tests/openvpn/mock_msg.h\n"
  },
  {
    "path": "tests/lwip_client_up.sh",
    "content": "#!/bin/sh\n#\n# Determine the OpenVPN PID from its pid file. This works reliably even when\n# the OpenVPN process is backgrounded for parallel tests.\nMY_PPID=`cat $pid`\n\n# Add this client's VPN IP and PID to a file. This enables\n# t_server_null_client.sh to kill this OpenVPN client after fping tests have\n# finished.\necho \"$ifconfig_local,$MY_PPID\" >> ./$test_name.lwip\n\n# Wait long enough to allow fping tests to finish. Also ensure that this\n# OpenVPN client is killed even if t_server_null_client.sh failed to do it.\n(sleep 15\necho \"ERROR: t_server_null_client.sh failed to kill OpenVPN client with PID $MY_PPID in test $test_name. Killing it in lwip_client_up.sh.\"\nkill -15 $MY_PPID\n) &\n"
  },
  {
    "path": "tests/ntlm_support.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n * Copyright (C) 2023-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"error.h\"\n\nint\nmain(void)\n{\n    msg(M_FATAL, \"NTLM support not compiled in\");\n}\n"
  },
  {
    "path": "tests/null_client_up.sh",
    "content": "#!/bin/sh\n#\n# Stop the parent process (openvpn) gracefully after a small delay\n\n# Determine the OpenVPN PID from its pid file. This works reliably even when\n# the OpenVPN process is backgrounded for parallel tests.\nMY_PPID=`cat $pid`\n\n# Allow OpenVPN to finish initializing while waiting in the background and then\n# killing the process gracefully.\n(sleep 5 ; kill -15 $MY_PPID) &\n"
  },
  {
    "path": "tests/t_client.rc-sample",
    "content": "#\n# this is sourced from t_client.sh and defines which openvpn client tests\n# to run\n#\n# (sample config, copy to t_client.rc and adapt to your environment)\n#\n#\n# define these - if empty, no tests will run\n#\ntop_srcdir=\"${top_srcdir:-..}\"\nCA_CERT=\"${top_srcdir}/sample/sample-keys/ca.crt\"\nCLIENT_KEY=\"${top_srcdir}/sample/sample-keys/client.key\"\nCLIENT_CERT=\"${top_srcdir}/sample/sample-keys/client.crt\"\n#FPING_EXTRA_ARGS=\"-t 1000\"\n\n# Load EXPECT_IFCONFIG* parameters from cache\nif [ -r \"${top_builddir}/t_client_ips.rc\" ]; then\n    . \"${top_builddir}/t_client_ips.rc\"\nelse\n    echo \"NOTICE: missing t_client_ips.rc will be auto-generated\"\nfi\n\n#\n# remote host (used as macro below)\n#\nREMOTE=mytestserver\n#\n# tests to run (list suffixes for config stanzas below)\n#\nTEST_RUN_LIST=\"1 2 2n\"\n\n#\n# use \"sudo\" (etc) to give openvpn the necessary privileges\n# if this is not active, \"make check\" must be run as root\n#\n#RUN_SUDO=sudo\n\n#\n# base confic that is the same for all the p2mp test runs\n#\nOPENVPN_BASE_P2MP=\"--client --ca $CA_CERT \\\n\t--cert $CLIENT_CERT --key $CLIENT_KEY \\\n\t--remote-cert-tls server --nobind --comp-lzo --verb 3\"\n\n# base config for p2p tests\n#\nOPENVPN_BASE_P2P=\"...\"\n\n#\n#\n# now define the individual tests - all variables suffixed with _1, _2 etc\n# will be used in test run \"1\", \"2\", etc.\n#\n# if something is not defined here, the corresponding test is not run\n#\n# common test options:\n#\n# RUN_TITLE_x        = \"what is being tested on here\" (purely informational)\n# OPENVPN_CONF_x     = \"how to call ./openvpn\" [mandatory]\n# EXPECT_IFCONFIG4_x = \"this IPv4 address needs to show up in ifconfig\"\n# EXPECT_IFCONFIG6_x = \"this IPv6 address needs to show up in ifconfig\"\n# PING4_HOSTS_x      = \"these hosts musts ping when openvpn is up (IPv4 fping)\"\n# PING6_HOSTS_x      = \"these hosts musts ping when openvpn is up (IPv6 fping6)\"\n#\n# hook test options:\n#\n# CHECK_SKIP_x      = \"commands to execute before openvpn, skip test on failure\"\n# PREPARE_x         = \"commands to execute before openvpn\"\n# POSTINIT_CMD_x    = \"commands to execute after openvpn but before ping\"\n# CLEANUP_x         = \"commands to execute after the test\"\n#\n# Note: all hooks are \"eval\"ed, so run in the original shell of the t_client.sh\n# script, not a child process.\n#\n# Test 1: UDP / p2mp tun\n#   specify IPv4+IPv6 addresses expected from server and ping targets\n#\nRUN_TITLE_1=\"testing tun/udp/ipv4+ipv6\"\nOPENVPN_CONF_1=\"$OPENVPN_BASE_P2MP --dev tun --proto udp --remote $REMOTE --port 51194\"\nPING4_HOSTS_1=\"10.100.50.1 10.100.0.1\"\nPING6_HOSTS_1=\"2001:db8::1 2001:db8:a050::1\"\n\n# Test 2: TCP / p2mp tun\n#\nRUN_TITLE_2=\"testing tun/tcp/ipv4+ipv6\"\nOPENVPN_CONF_2=\"$OPENVPN_BASE_P2MP --dev tun --proto tcp --remote $REMOTE --port 51194\"\nPING4_HOSTS_2=\"10.100.51.1 10.100.0.1\"\nPING6_HOSTS_2=\"2001:db8::1 2001:db8:a051::1\"\n# run command after openvpn initialization is done - here: delay 5 seconds\nPOSTINIT_CMD_2=\"sleep 5\"\n\n# Test 2n: TCP / p2mp tun / via NTLM proxy\nRUN_TITLE_2n=\"testing tun/tcp/ntlm-proxy\"\nOPENVPN_CONF_2n=\"$OPENVPN_BASE_P2MP --dev tun --proto tcp --remote $REMOTE --port 51194\n --http-proxy 192.168.1.2 8080 $KEYBASE/t_client_auth.txt ntlm --http-proxy-option VERSION 1.1\"\nPING4_HOSTS_2n=\"10.100.51.1 10.100.0.1\"\nPING6_HOSTS_2n=\"2001:db8::1 2001:db8:a051::1\"\n# skip test if NTLM support is not available\nCHECK_SKIP_2n=\"${top_builddir}/tests/ntlm_support\"\n\n# Test 3: UDP / p2p tun\n# ...\n\n# Test 4: TCP / p2p tun\n# ...\n\n# Test 5: UDP / p2mp tap\n# ...\n\n# Test 6: TCP / p2mp tun\n# ...\n\n# Test 7: UDP / p2p tap\n# ...\n\n# Test 8: TCP / p2p tap\n# ...\n\n# Test 9: whatever you want to test... :-)\n"
  },
  {
    "path": "tests/t_client.sh.in",
    "content": "#!@SHELL@\n#\n# run OpenVPN client against ``test reference'' server\n# - check that ping, http, ... via tunnel works\n# - check that interface config / routes are properly cleaned after test end\n#\n# prerequisites:\n# - openvpn binary in current directory\n# - writable current directory to create subdir for logs\n# - t_client.rc in current directory OR source dir that specifies tests\n# - for \"ping4\" checks: fping binary in $PATH\n# - for \"ping6\" checks: fping (4.0+) or fping6 binary in $PATH\n#\n\n# by changing this to 1 we can force automated builds to fail\n# that are expected to have all the prerequisites\nTCLIENT_SKIP_RC=\"${TCLIENT_SKIP_RC:-77}\"\n\nsrcdir=\"${srcdir:-.}\"\ntop_builddir=\"${top_builddir:-..}\"\nopenvpn=\"${openvpn:-${top_builddir}/src/openvpn/openvpn}\"\nif [ -r \"${top_builddir}\"/t_client.rc ] ; then\n    . \"${top_builddir}\"/t_client.rc\nelif [ -r \"${srcdir}\"/t_client.rc ] ; then\n    . \"${srcdir}\"/t_client.rc\nelse\n    echo \"$0: cannot find 't_client.rc' in build dir ('${top_builddir}')\" >&2\n    echo \"$0: or source directory ('${srcdir}'). SKIPPING TEST.\" >&2\n    exit \"${TCLIENT_SKIP_RC}\"\nfi\n\n# Check for external dependencies\nFPING=\"fping\"\nFPING6=\"fping6\"\nwhich fping > /dev/null\nif [ $? -ne 0 ]; then\n    echo \"$0: fping is not available in \\$PATH\" >&2\n    exit \"${TCLIENT_SKIP_RC}\"\nfi\nwhich fping6 > /dev/null\nif [ $? -ne 0 ]; then\n    echo \"$0: fping6 is not available in \\$PATH, assuming fping 4.0 or later\" >&2\n    FPING=\"fping -4\"\n    FPING6=\"fping -6\"\nfi\n\nKILL_EXEC=`which kill`\nif [ $? -ne 0 ]; then\n    echo \"$0: kill not found in \\$PATH\" >&2\n    exit \"${TCLIENT_SKIP_RC}\"\nfi\n\nif [ ! -x \"${openvpn}\" ]\nthen\n    echo \"no (executable) openvpn binary in current build tree. FAIL.\" >&2\n    exit 1\nfi\n\nif [ ! -w . ]\nthen\n    echo \"current directory is not writable (required for logging). FAIL.\" >&2\n    exit 1\nfi\n\nif [ -z \"$CA_CERT\" ] ; then\n    echo \"CA_CERT not defined in 't_client.rc'. SKIP test.\" >&2\n    exit \"${TCLIENT_SKIP_RC}\"\nfi\n\nif [ -z \"$TEST_RUN_LIST\" ] ; then\n    echo \"TEST_RUN_LIST empty, no tests defined.  SKIP test.\" >&2\n    exit \"${TCLIENT_SKIP_RC}\"\nfi\n\n# Ensure PREFER_KSU is in a known state\nPREFER_KSU=\"${PREFER_KSU:-0}\"\n\n# make sure we have permissions to run ifconfig/route from OpenVPN\n# can't use \"id -u\" here - doesn't work on Solaris\nID=`id`\nif expr \"$ID\" : \"uid=0\" >/dev/null\nthen :\nelse\n    if [ \"${PREFER_KSU}\" -eq 1 ];\n    then\n        # Check if we have a valid kerberos ticket\n        klist -l 1>/dev/null 2>/dev/null\n        if [ $? -ne 0 ];\n        then\n            # No kerberos ticket found, skip ksu and fallback to RUN_SUDO\n            PREFER_KSU=0\n            echo \"$0: No Kerberos ticket available.  Will not use ksu.\"\n        else\n            RUN_SUDO=\"ksu -q -e\"\n        fi\n    fi\n\n    if [ -z \"$RUN_SUDO\" ]\n    then\n        echo \"$0: this test must run be as root, or RUN_SUDO=... \" >&2\n        echo \"      must be set correctly in 't_client.rc'. SKIP.\" >&2\n        exit \"${TCLIENT_SKIP_RC}\"\n    else\n        # We have to use sudo. Make sure that we (hopefully) do not have\n        # to ask the users password during the test. This is done to\n        # prevent timing issues, e.g. when the waits for openvpn to start\n\tif $RUN_SUDO $KILL_EXEC -0 $$\n\tthen\n\t    echo \"$0: $RUN_SUDO $KILL_EXEC -0 succeeded, good.\"\n\telse\n\t    echo \"$0: $RUN_SUDO $KILL_EXEC -0 failed, cannot go on. SKIP.\" >&2\n\t    exit \"${TCLIENT_SKIP_RC}\"\n\tfi\n    fi\nfi\n\nLOGDIR=t_client-`hostname`-`date +%Y%m%d-%H%M%S`\nif mkdir $LOGDIR\nthen :\nelse\n    echo \"can't create log directory '$LOGDIR'. FAIL.\" >&2\n    exit 1\nfi\n\n# verbosity, defaults to \"1\"\nV=\"${V:-1}\"\n\nexit_code=0\n\n# ----------------------------------------------------------\n# helper functions\n# ----------------------------------------------------------\n\n# output progress information\n#  depending on verbosity level, collect & print only on failure\noutput_start()\n{\n    case $V in\n\t0) outbuf=\"\" ;;\t\t\t# no per-test output at all\n\t1) echo -e \"$@\"\t\t\t# compact, details only on failure\n           outbuf=\"\\n\" ;;\n\t*) echo -e \"\\n$@\\n\" ;;\t\t# print all, with a bit formatting\n    esac\n}\n\noutput()\n{\n    NO_NL=''; if [ \"X$1\" = \"X-n\" ] ; then NO_NL=$1 ; shift ; fi\n    case $V in\n\t0) ;;\t\t\t\t# no per-test output at all\n\t1) outbuf=\"$outbuf$@\" \t\t# print details only on failure\n\t   test -z \"$NO_NL\" && outbuf=\"$outbuf\\n\"\n           ;;\n\t*) echo -e $NO_NL \"$@\" ;;\t# print everything\n    esac\n}\n\n# print failure message, increase FAIL counter\nfail()\n{\n    output \"FAIL: $@\\n\"\n    fail_count=$(( $fail_count + 1 ))\n}\n\n# print \"all interface IP addresses\" + \"all routes\"\n# this is higly system dependent...\nget_ifconfig_route()\n{\n    UNAME=`uname -s`\n    case $UNAME in\n\tLinux)\n            # linux / iproute2? (-> if configure got a path)\n            if [ -n \"@IPROUTE@\" ]\n            then\n                echo \"-- linux iproute2 --\"\n                @IPROUTE@ addr show     | grep -v valid_lft\n                @IPROUTE@ route show\n                @IPROUTE@ -o -6 route show | grep -v ' cache' | sed -E -e 's/ expires [0-9]*sec//' -e 's/ (mtu|hoplimit|cwnd|ssthresh) [0-9]+//g' -e 's/ (rtt|rttvar) [0-9]+ms//g'\n            else\n\t        echo \"-- linux / ifconfig --\"\n\t        LANG=C @IFCONFIG@ -a |egrep  \"( addr:|encap:)\"\n\t        LANG=C @NETSTAT@ -rn -4 -6\n            fi\n            ;;\n\tFreeBSD|NetBSD|Darwin)\n\t   echo \"-- FreeBSD/NetBSD/Darwin [MacOS X] --\"\n\t   @IFCONFIG@ -a | egrep \"(flags=|inet)\"\n\t   @NETSTAT@ -rn | awk '$3 !~ /^UHL/ { print $1,$2,$3,$NF }'\n\t   ;;\n\tOpenBSD)\n\t   echo \"-- OpenBSD --\"\n\t   @IFCONFIG@ -a | egrep \"(flags=|inet)\" | \\\n\t\tsed -e 's/pltime [0-9]*//' -e 's/vltime [0-9]*//'\n\t   @NETSTAT@ -rn | awk '$3 !~ /^UHL/ { print $1,$2,$3,$NF }'\n\t   ;;\n\tSunOS)\n\t   echo \"-- Solaris --\"\n\t   @IFCONFIG@ -a | egrep \"(flags=|inet)\"\n\t   @NETSTAT@ -rn | awk '$3 !~ /^UHL/ { print $1,$2,$3,$6 }'\n\t   ;;\n\tAIX)\n\t   echo \"-- AIX --\"\n\t   @IFCONFIG@ -a | egrep \"(flags=|inet)\"\n\t   @NETSTAT@ -rn | awk '$3 !~ /^UHL/ { print $1,$2,$3,$6 }'\n\t   ;;\n        *)\n           echo \"get_ifconfig_route(): no idea how to get info on your OS (`uname -s`).  FAIL.\" >&2\n           exit 20\n           ;;\n    esac\n\n    # another round of per-platform information gathering, for DNS info\n    # for most of the platforms \"cat /etc/resolv.conf\" is good enough\n    # except Linux and MacOS\n    case $UNAME in\n\tLinux)\n            if [ -x /usr/bin/resolvectl ] ; then\n                echo \"-- linux resolvectl --\"\n                resolvectl status\n            else\n                echo \"-- linux resolv.conf --\"\n                cat /etc/resolv.conf\n            fi\n            ;;\n\tDarwin)\n            echo \"-- MacOS scutil --dns\"\n            scutil --dns\n            ;;\n        *)\n            echo \"-- resolv.conf --\"\n            cat /etc/resolv.conf\n            ;;\n    esac\n}\n\n# ----------------------------------------------------------\n# check ifconfig\n#  arg1: \"4\" or \"6\" -> for message\n#  arg2: IPv4/IPv6 address that must show up in out of \"get_ifconfig_route\"\ncheck_ifconfig()\n{\n    proto=$1 ; shift\n    expect_list=\"$@\"\n\n    if [ -z \"$expect_list\" ] ; then return ; fi\n    if [ \"$expect_list\" = \"-\" ] ; then return ; fi\n\n    for expect in $expect_list\n    do\n\tif get_ifconfig_route | fgrep \"$expect\" >/dev/null\n\tthen :\n\telse\n\t    fail \"check_ifconfig(): expected IPv$proto address '$expect' not found in ifconfig output.\"\n\tfi\n    done\n}\n\n# ----------------------------------------------------------\n# run pings\n#  arg1: \"4\" or \"6\" -> fping/fing6\n#  arg2: \"want_ok\" or \"want_fail\" (expected ping result)\n#  arg3... -> fping arguments (host list)\nrun_ping_tests()\n{\n    proto=$1 ; want=$2 ; shift ; shift\n    targetlist=\"$@\"\n\n    # \"no targets\" is fine\n    if [ -z \"$targetlist\" ] ; then return ; fi\n\n    case $proto in\n\t4) cmd=\"$FPING\" ;;\n\t6) cmd=\"$FPING6\" ;;\n\t*) echo \"internal error in run_ping_tests arg 1: '$proto'\" >&2\n\t   exit 1 ;;\n    esac\n\n    case $want in\n\twant_ok)   sizes_list=\"64 1440 3000\" ;;\n\twant_fail) sizes_list=\"64\" ;;\n    esac\n\n    for bytes in $sizes_list\n    do\n\toutput \"run IPv$proto ping tests ($want), $bytes byte packets...\"\n\n\techo \"$cmd -b $bytes -C 20 -p 250 -q $fping_args $targetlist\" >>$LOGDIR/$SUF:fping.out\n\t$cmd -b $bytes -C 20 -p 250 -q $fping_args $targetlist >>$LOGDIR/$SUF:fping.out 2>&1\n\n\t# while OpenVPN is running, pings must succeed (want='want_ok')\n\t# before OpenVPN is up, pings must NOT succeed (want='want_fail')\n\n\trc=$?\n\tif [ $rc = 0 ] \t\t\t\t# all ping OK\n\tthen\n\t    if [ $want = \"want_fail\" ]\t\t# not what we want\n\t    then\n\t\tfail \"IPv$proto ping test succeeded, but needs to *fail*.\"\n\t    fi\n\telse\t\t\t\t\t# ping failed\n\t    if [ $want = \"want_ok\" ]\t\t# not what we wanted\n\t    then\n\t\tfail \"IPv$proto ping test ($bytes bytes) failed, but should succeed.\"\n\t    fi\n\tfi\n    done\n}\n\n# ----------------------------------------------------------\n# main test loop\n# ----------------------------------------------------------\nSUMMARY_OK=\nSUMMARY_SKIP=\nSUMMARY_FAIL=\n\nfor SUF in $TEST_RUN_LIST\ndo\n    # get config variables\n    eval test_prep=\\\"\\$PREPARE_$SUF\\\"\n    eval test_check_skip=\\\"\\$CHECK_SKIP_$SUF\\\"\n    eval test_postinit=\\\"\\$POSTINIT_CMD_$SUF\\\"\n    eval test_cleanup=\\\"\\$CLEANUP_$SUF\\\"\n    eval test_run_title=\\\"\\$RUN_TITLE_$SUF\\\"\n    eval openvpn_conf=\\\"\\$OPENVPN_CONF_$SUF\\\"\n    eval expect_ifconfig4=\\\"\\$EXPECT_IFCONFIG4_$SUF\\\"\n    eval expect_ifconfig6=\\\"\\$EXPECT_IFCONFIG6_$SUF\\\"\n    eval ping4_hosts=\\\"\\$PING4_HOSTS_$SUF\\\"\n    eval ping6_hosts=\\\"\\$PING6_HOSTS_$SUF\\\"\n    eval fping_args=\\\"\\$FPING_EXTRA_ARGS \\$FPING_ARGS_$SUF\\\"\n\n    # If EXCEPT_IFCONFIG* variables for this test are missing, run an --up\n    # script to generate them dynamically.\n    if [ -z \"$expect_ifconfig4\" ] || [ -z \"$expect_ifconfig6\" ]; then\n        up=\"--setenv TESTNUM $SUF --setenv TOP_BUILDDIR ${top_builddir} --script-security 2 --up ${srcdir}/update_t_client_ips.sh\"\n    else\n        up=\"\"\n    fi\n\n    output_start \"### test run $SUF: '$test_run_title' ###\"\n    fail_count=0\n\n    if [ -n \"$test_check_skip\" ]; then\n        output \"check whether we need to skip: '$test_check_skip'\"\n        if eval $test_check_skip; then :\n        else\n            output \"skip check failed, SKIP test $SUF.\"\n\t    SUMMARY_SKIP=\"$SUMMARY_SKIP $SUF\"\n\t    echo -e \"$outbuf\" ; continue\n        fi\n    fi\n\n    if [ -n \"$test_prep\" ]; then\n        output \"running preparation: '$test_prep'\"\n        eval $test_prep\n    fi\n\n    output \"save pre-openvpn ifconfig + route\"\n    get_ifconfig_route >$LOGDIR/$SUF:ifconfig_route_pre.txt\n\n    output \"\\nrun pre-openvpn ping tests - targets must not be reachable...\"\n    run_ping_tests 4 want_fail \"$ping4_hosts\"\n    run_ping_tests 6 want_fail \"$ping6_hosts\"\n    if [ \"$fail_count\" = 0 ] ; then\n        output \"OK.\\n\"\n    else\n\tfail \"make sure that ping hosts are ONLY reachable via VPN, SKIP test $SUF.\"\n\tSUMMARY_FAIL=\"$SUMMARY_FAIL $SUF\"\n\texit_code=31\n\techo -e \"$outbuf\" ; continue\n    fi\n\n    pidfile=\"${top_builddir}/tests/$LOGDIR/openvpn-$SUF.pid\"\n    openvpn_conf=\"$openvpn_conf --writepid $pidfile $up\"\n    output \" run openvpn $openvpn_conf\"\n    echo \"# ${openvpn} $openvpn_conf\" >$LOGDIR/$SUF:openvpn.log\n    umask 022\n    $RUN_SUDO \"${openvpn}\" $openvpn_conf >>$LOGDIR/$SUF:openvpn.log &\n    sudopid=$!\n\n    # Check if OpenVPN has initialized before continuing.  It will check every second up\n    # to $ovpn_init_check times.\n    ovpn_init_check=30\n    ovpn_init_success=0\n    while [ $ovpn_init_check -gt 0 ];\n    do\n       sleep 1  # Wait for OpenVPN to initialize and have had time to write the pid file\n       grep \"Initialization Sequence Completed\" $LOGDIR/$SUF:openvpn.log >/dev/null\n       if [ $? -eq 0 ]; then\n           ovpn_init_check=0\n           ovpn_init_success=1\n       fi\n       ovpn_init_check=$(( $ovpn_init_check - 1 ))\n    done\n\n    opid=`cat $pidfile`\n    if [ -n \"$opid\" ]; then\n        output \"  OpenVPN running with PID $opid\"\n    else\n        output \"  Could not read OpenVPN PID file\"\n    fi\n\n    # If OpenVPN did not start\n    if [ $ovpn_init_success -ne 1 -o -z \"$opid\" ]; then\n        output \"$0:  OpenVPN did not initialize in a reasonable time\"\n        if [ -n \"$opid\" ]; then\n           $RUN_SUDO $KILL_EXEC $opid\n        fi\n        $RUN_SUDO $KILL_EXEC $sudopid\n\toutput \"tail -5 $SUF:openvpn.log\"\n\toutput \"`tail -5 $LOGDIR/$SUF:openvpn.log`\"\n\tfail \"skip rest of sub-tests for test run $SUF.\"\n\ttrap - 0 1 2 3 15\n\tSUMMARY_FAIL=\"$SUMMARY_FAIL $SUF\"\n\texit_code=30\n\techo -e \"$outbuf\" ; continue\n    fi\n\n    # make sure openvpn client is terminated in case shell exits\n    trap \"$RUN_SUDO $KILL_EXEC $opid\" 0\n    trap \"$RUN_SUDO $KILL_EXEC $opid ; trap - 0 ; exit 1\" 1 2 3 15\n\n    # compare whether anything changed in ifconfig/route setup?\n    output \"save ifconfig+route\"\n    get_ifconfig_route >$LOGDIR/$SUF:ifconfig_route.txt\n\n    if [ \"$expect_ifconfig4\" = \"-\" ] ; then\n        output \"skip ifconfig+route check\"\n    else\n\toutput -n \"compare pre-openvpn ifconfig+route with current values...\"\n\tif diff $LOGDIR/$SUF:ifconfig_route_pre.txt \\\n\t\t$LOGDIR/$SUF:ifconfig_route.txt >/dev/null\n\tthen\n\t    fail \"no differences between ifconfig/route before OpenVPN start and now.\"\n\telse\n\t    output \" OK!\\n\"\n\tfi\n    fi\n\n    # post init script needed?\n    if [ -n \"$test_postinit\" ]; then\n        output \"running post-init cmd: '$test_postinit'\"\n        eval $test_postinit\n    fi\n\n    # expected ifconfig values in there?\n    check_ifconfig 4 \"$expect_ifconfig4\"\n    check_ifconfig 6 \"$expect_ifconfig6\"\n\n    run_ping_tests 4 want_ok \"$ping4_hosts\"\n    run_ping_tests 6 want_ok \"$ping6_hosts\"\n    output \"ping tests done.\\n\"\n\n    output \"stopping OpenVPN\"\n    $RUN_SUDO $KILL_EXEC $opid\n    wait $!\n    rc=$?\n    if [ $rc != 0 ] ; then\n\tfail \"OpenVPN return code $rc, expect 0\"\n    fi\n\n    output \"\\nsave post-openvpn ifconfig + route...\"\n    get_ifconfig_route >$LOGDIR/$SUF:ifconfig_route_post.txt\n\n    output -n \"compare pre- and post-openvpn ifconfig + route...\"\n    if diff $LOGDIR/$SUF:ifconfig_route_pre.txt \\\n\t    $LOGDIR/$SUF:ifconfig_route_post.txt >$LOGDIR/$SUF:ifconfig_route_diff.txt\n    then\n\toutput \" OK.\\n\"\n    else\n\toutput \"\\n\\n\" \"`cat $LOGDIR/$SUF:ifconfig_route_diff.txt`\" \"\\n\"\n\tfail \"differences between pre- and post-ifconfig/route.\"\n    fi\n    if [ \"$fail_count\" = 0 ] ; then\n        output \"test run $SUF: all tests OK.\\n\"\n\tSUMMARY_OK=\"$SUMMARY_OK $SUF\"\n    else\n\tif [ \"$V\" -gt 0 ] ; then\n\t    echo -e -n \"$outbuf\"\n\t    echo -e \"test run $SUF: $fail_count test failures. FAIL.\\n\"\n        fi\n\tSUMMARY_FAIL=\"$SUMMARY_FAIL $SUF\"\n\texit_code=30\n    fi\n\n    if [ -n \"$test_cleanup\" ]; then\n        echo -e \"cleaning up: '$test_cleanup'\"\n        eval $test_cleanup\n    fi\n\ndone\n\nif [ -z \"$SUMMARY_OK\" ] ; then SUMMARY_OK=\" none\"; fi\nif [ -z \"$SUMMARY_SKIP\" ] ; then SUMMARY_SKIP=\" none\"; fi\nif [ -z \"$SUMMARY_FAIL\" ] ; then SUMMARY_FAIL=\" none\"; fi\necho \"Test sets succeeded:$SUMMARY_OK.\"\necho \"Test sets skipped:$SUMMARY_SKIP.\"\necho \"Test sets failed:$SUMMARY_FAIL.\"\n\n# remove trap handler\ntrap - 0 1 2 3 15\nexit $exit_code\n"
  },
  {
    "path": "tests/t_cltsrv-down.sh",
    "content": "#! /bin/sh\necho \"${role}:${signal}\" >&3\n"
  },
  {
    "path": "tests/t_cltsrv.sh",
    "content": "#! /bin/sh\n#\n# t_cltsrv.sh - script to test OpenVPN's crypto loopback\n# Copyright (C) 2005, 2006, 2008  Matthias Andree\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License\n# as published by the Free Software Foundation; either version 2\n# of the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, see <https://www.gnu.org/licenses/>.\n\nset -e\nsrcdir=\"${srcdir:-.}\"\ntop_srcdir=\"${top_srcdir:-..}\"\ntop_builddir=\"${top_builddir:-..}\"\nopenvpn=\"${openvpn:-${top_builddir}/src/openvpn/openvpn}\"\ntrap \"rm -f log.$$ log.$$.signal ; trap 0 ; exit 77\" 1 2 15\ntrap \"rm -f log.$$ log.$$.signal ; exit 1\" 0 3\naddopts=\ncase `uname -s` in\n    FreeBSD)\n    # FreeBSD jails map the outgoing IP to the jail IP - we need to\n    # allow the real IP unless we want the test to run forever.\n    if test \"`sysctl 2>/dev/null -n security.jail.jailed`\" = 1 \\\n    || ps -ostate= -p $$ | grep -q J; then\n\taddopts=\"--float\"\n\tif test \"x`ifconfig | grep inet`\" = x ; then\n\t    echo \"###\"\n\t    echo \"### To run the test in a FreeBSD jail, you MUST add an IP alias for the jail's IP.\"\n\t    echo \"###\"\n\t    exit 77\n\tfi\n    fi\n    ;;\nesac\n\n# make sure that the --down script is executable -- fail (rather than\n# skip) test if it isn't.\ndownscript=\"../tests/t_cltsrv-down.sh\"\nroot=\"${top_srcdir}/sample\"\ntest -x \"${root}/${downscript}\" || chmod +x \"${root}/${downscript}\" || { echo >&2 \"${root}/${downscript} is not executable, failing.\" ; exit 1 ; }\necho \"The following test will take about two minutes.\" >&2\necho \"If the addresses are in use, this test will retry up to two times.\" >&2\n\n# go\nsuccess=0\nfor i in 1 2 3 ; do\n  set +e\n  (\n  \"${openvpn}\" --script-security 2 --cd \"${root}\" ${addopts} --setenv role srv --down \"${downscript}\" --tls-exit --ping-exit 180 --config \"sample-config-files/loopback-server\" &\n  \"${openvpn}\" --script-security 2 --cd \"${top_srcdir}/sample\" ${addopts} --setenv role clt --down \"${downscript}\" --tls-exit --ping-exit 180 --config \"sample-config-files/loopback-client\"\n  ) 3>log.$$.signal >log.$$ 2>&1\n  e1=$?\n  wait $!\n  e2=$?\n  grep 'TCP/UDP: Socket bind failed on local address.*in use' log.$$ >/dev/null && {\n    echo 'address in use, retrying in 150 s'\n    sleep 150\n    continue\n  }\n  grep -v ':inactive$' log.$$.signal >/dev/null && { cat log.$$.signal ; echo ; cat log.$$ ; exit 1 ; }\n  success=1\n  break\ndone\n\nset -e\n\n# exit code - defaults to 0, PASS\nec=0\n\nif [ $success != 1 ] ; then\n  # couldn't run test -- addresses in use, skip test\n  cat log.$$\n  ec=77\nelif [ $e1 != 0 ] || [ $e2 != 0 ] ; then\n  # failure -- fail test\n  cat log.$$\n  ec=1\nfi\n\nrm log.$$ log.$$.signal\ntrap 0\nexit $ec\n"
  },
  {
    "path": "tests/t_lpback.sh",
    "content": "#! /bin/sh\n#\n# t_lpback.sh - script to test OpenVPN's crypto loopback\n# Copyright (C) 2005  Matthias Andree\n# Copyright (C) 2014  Steffan Karger\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License\n# as published by the Free Software Foundation; either version 2\n# of the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, see <https://www.gnu.org/licenses/>.\n\nset -eu\ntop_builddir=\"${top_builddir:-..}\"\nopenvpn=\"${openvpn:-${top_builddir}/src/openvpn/openvpn}\"\ntrap \"rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; trap 0 ; exit 77\" 1 2 15\ntrap \"rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; exit 1\" 0 3\n\n# verbosity, defaults to \"1\"\nV=\"${V:-1}\"\ntests_passed=0\ntests_failed=0\n\n# ----------------------------------------------------------\n# helper functions\n# ----------------------------------------------------------\n\n# output progress information\n#  depending on verbosity level, collect & print only on failure\ntest_start()\n{\n    case $V in\n        0) outbuf=\"\" ;;                  # no per-test output at all\n        1) outbuf=\"$@\" ;;                # compact, details only on failure\n        *) printf \"$@\" ;;                # print all\n    esac\n}\ntest_end()\n{\n    RC=$1 ; LOG=$2\n    if [ $RC != 0 ]\n    then\n        case $V in\n            0) ;;                                # no per-test output\n            1) echo \"$outbuf\" \"FAIL (RC=$RC)\"; cat $LOG ;;\n            *) echo \"FAIL (RC=$RC)\"; cat $LOG ;;\n        esac\n        e=1\n        tests_failed=$(( $tests_failed + 1 ))\n    else\n        case $V in\n            0|1) ;;                              # no per-test output for 'OK'\n            *) echo \"OK\"                         # print all\n        esac\n        tests_passed=$(( $tests_passed + 1 ))\n    fi\n}\n\n# if running with V=1, give an indication what test runs now\nif [ \"$V\" = 1  ] ; then\n    echo \"$0: running with V=$V, only printing test fails\"\nfi\n\n\n# Get list of supported ciphers from openvpn --show-ciphers output\nCIPHERS=$(${openvpn} --show-ciphers | \\\n            sed -e '/The following/,/^$/d' -e s'/ .*//' -e '/^[[:space:]]*$/d')\n\n# SK, 2014-06-04: currently the DES-EDE3-CFB1 implementation of OpenSSL is\n# broken (see http://rt.openssl.org/Ticket/Display.html?id=2867), so exclude\n# that cipher from this test.\n# GD, 2014-07-06 so is DES-CFB1\n# GD, 2014-07-06 do not test RC5-* either (fails on NetBSD w/o libcrypto_rc5)\nCIPHERS=$(echo \"$CIPHERS\" | egrep -v '^(DES-EDE3-CFB1|DES-CFB1|RC5-)' )\n\ne=0\nif [ -z \"$CIPHERS\" ] ; then\n    echo \"'openvpn --show-ciphers' FAILED (empty list)\"\n    e=1\nfi\n\n# Also test cipher 'none'\nCIPHERS=${CIPHERS}$(printf \"\\nnone\")\n\nset +e\n\nfor cipher in ${CIPHERS}\ndo\n    test_start \"Testing cipher ${cipher}... \"\n    ( \"${openvpn}\" --test-crypto --cipher ${cipher} ) >log.$$ 2>&1\n    test_end $? log.$$\ndone\n\ntest_start \"Testing tls-crypt-v2 server key generation... \"\n\"${openvpn}\" \\\n    --genkey tls-crypt-v2-server tc-server-key.$$ >log.$$ 2>&1\ntest_end $? log.$$\n\ntest_start \"Testing tls-crypt-v2 key generation (no metadata)... \"\n\"${openvpn}\" --tls-crypt-v2 tc-server-key.$$ \\\n    --genkey tls-crypt-v2-client tc-client-key.$$ >log.$$ 2>&1\ntest_end $? log.$$\n\n# Generate max-length base64 metadata ('A' is 0b000000 in base64)\nMETADATA=\"\"\ni=0\nwhile [ $i -lt 732 ]; do\n    METADATA=\"${METADATA}A\"\n    i=$(expr $i + 1)\ndone\ntest_start \"Testing tls-crypt-v2 key generation (max length metadata)... \"\n\"${openvpn}\" --tls-crypt-v2 tc-server-key.$$ \\\n    --genkey tls-crypt-v2-client tc-client-key.$$ \"${METADATA}\" \\\n    >log.$$ 2>&1\ntest_end $? log.$$\n\nif [ \"$V\" -ge 1  ] ; then\n    echo \"$0: tests passed: $tests_passed  failed: $tests_failed\"\nfi\n\nrm tc-server-key.$$ tc-client-key.$$ log.$$\ntrap 0\nexit $e\n"
  },
  {
    "path": "tests/t_net.sh",
    "content": "#!/usr/bin/env bash\n\nIFACE=\"ovpn-dummy0\"\nUNIT_TEST=\"./unit_tests/openvpn/networking_testdriver\"\nLAST_AUTO_TEST=7\nLAST_TEST=8\n\nsrcdir=\"${srcdir:-.}\"\ntop_builddir=\"${top_builddir:-..}\"\nopenvpn=\"${openvpn:-${top_builddir}/src/openvpn/openvpn}\"\n\n\n# bail out right away on non-linux. NetLink (the object of this test) is only\n# used on Linux, therefore testing other platform is not needed.\n#\n# Note: statements in the rest of the script may not even pass syntax check on\n# solaris/bsd. It uses /bin/bash\nif [ \"$(uname -s)\" != \"Linux\" ]; then\n    echo \"$0: this test runs only on Linux. SKIPPING TEST.\"\n    exit 77\nfi\n\n# Commands used to retrieve the network state.\n# State is retrieved after running sitnl and after running\n# iproute commands. The two are then compared and expected to be equal.\ntypeset -a GET_STATE\nGET_STATE[0]=\"ip link show dev $IFACE | sed 's/^[0-9]\\+: //'\"\nGET_STATE[1]=\"ip addr show dev $IFACE | sed 's/^[0-9]\\+: //'\"\nGET_STATE[2]=\"ip route show dev $IFACE\"\nGET_STATE[3]=\"ip -6 route show dev $IFACE\"\n\nLAST_STATE=$((${#GET_STATE[@]} - 1))\n\nreload_dummy()\n{\n    $RUN_SUDO ip link del $IFACE\n    $RUN_SUDO ip link add $IFACE address 00:11:22:33:44:55 type dummy\n    $RUN_SUDO ip link set dev $IFACE state up\n\n    if [ $? -ne 0 ]; then\n        echo \"can't create interface $IFACE\"\n        exit 1\n    fi\n}\n\nrun_test()\n{\n    # run all test cases from 0 to $1 in sequence\n    CMD=\n    for k in $(seq 0 $1); do\n        # the unit-test prints to stdout the iproute command corresponding\n        # to the sitnl operation being executed.\n        # Format is \"CMD: <commandhere>\"\n        OUT=$($RUN_SUDO $UNIT_TEST $k $IFACE)\n        # ensure unit test worked properly\n        if [ $? -ne 0 ]; then\n            echo \"unit-test $k errored out:\"\n            echo \"$OUT\"\n            exit 1\n        fi\n\n        NEW=$(echo \"$OUT\" | sed -n 's/CMD: //p')\n        CMD=\"$CMD $RUN_SUDO $NEW ;\"\n    done\n\n    # collect state for later comparison\n    for k in $(seq 0 $LAST_STATE); do\n        STATE_TEST[$k]=\"$(eval ${GET_STATE[$k]})\"\n    done\n}\n\n\n## execution starts here\n\n# t_client.rc required only for RUN_SUDO definition\nif [ -r \"${top_builddir}\"/t_client.rc ]; then\n    . \"${top_builddir}\"/t_client.rc\nelif [ -r \"${srcdir}\"/t_client.rc ]; then\n    . \"${srcdir}\"/t_client.rc\nfi\n\nif [ ! -x \"$openvpn\" ]; then\n    echo \"no (executable) openvpn binary in current build tree. FAIL.\" >&2\n    exit 1\nfi\n\nif [ ! -x \"$UNIT_TEST\" ]; then\n    echo \"no test_networking driver available. SKIPPING TEST.\" >&2\n    exit 77\nfi\n\n\n# Ensure PREFER_KSU is in a known state\nPREFER_KSU=\"${PREFER_KSU:-0}\"\n\n# make sure we have permissions to run the networking unit-test\nID=`id`\nif expr \"$ID\" : \"uid=0\" >/dev/null\nthen :\nelse\n    if [ \"${PREFER_KSU}\" -eq 1 ];\n    then\n        # Check if we have a valid kerberos ticket\n        klist -l 1>/dev/null 2>/dev/null\n        if [ $? -ne 0 ];\n        then\n            # No kerberos ticket found, skip ksu and fallback to RUN_SUDO\n            PREFER_KSU=0\n            echo \"$0: No Kerberos ticket available.  Will not use ksu.\"\n        else\n            RUN_SUDO=\"ksu -q -e\"\n        fi\n    fi\n\n    if [ -z \"$RUN_SUDO\" ]\n    then\n        echo \"$0: no RUN_SUDO=... in t_client.rc or environment, defaulting to 'sudo'.\" >&2\n        echo \"      if that does not work, set RUN_SUDO= correctly for your system.\" >&2\n        RUN_SUDO=\"sudo\"\n    fi\n\n    # check that we can run the unit-test binary with sudo\n    if $RUN_SUDO $UNIT_TEST test\n    then\n        echo \"$0: $RUN_SUDO $UNIT_TEST succeeded, good.\"\n    else\n        echo \"$0: $RUN_SUDO $UNIT_TEST failed, cannot go on. SKIP.\" >&2\n        exit 77\n    fi\nfi\n\nfor i in $(seq 0 $LAST_AUTO_TEST); do\n    # reload dummy module to cleanup state\n    reload_dummy\n    typeset -a STATE_TEST\n    run_test $i\n\n    # reload dummy module to cleanup state before running iproute commands\n    reload_dummy\n\n    # CMD has been set by the unit test\n    eval $CMD\n    if [ $? -ne 0 ]; then\n        echo \"error while executing:\"\n        echo \"$CMD\"\n        exit 1\n    fi\n\n    # collect state after running manual ip command\n    for k in $(seq 0 $LAST_STATE); do\n        STATE_IP[$k]=\"$(eval ${GET_STATE[$k]})\"\n    done\n\n    # ensure states after running unit test matches the one after running\n    # manual iproute commands\n    for j in $(seq 0 $LAST_STATE); do\n        if [ \"${STATE_TEST[$j]}\" != \"${STATE_IP[$j]}\" ]; then\n            echo \"state $j mismatching after '$CMD'\"\n            echo \"after unit-test:\"\n            echo \"${STATE_TEST[$j]}\"\n            echo \"after iproute command:\"\n            echo \"${STATE_IP[$j]}\"\n            exit 1\n        fi\n    done\n    echo \"Test $i: OK\"\ndone\n\n# remove interface for good\n$RUN_SUDO ip link del $IFACE\n\nfor i in $(seq $(($LAST_AUTO_TEST + 1)) ${LAST_TEST}); do\n    $RUN_SUDO $UNIT_TEST $i\n    if [ $? -ne 0 ]; then\n        echo \"unit-test $i errored out\"\n        exit 1\n    fi\n\n    echo \"Test $i: OK\"\ndone\n\nexit 0\n"
  },
  {
    "path": "tests/t_server_null.rc-sample",
    "content": "# Uncomment to run tests with sudo\n#RUN_SUDO=\"sudo -E\"\n\nTEST_RUN_LIST=\"1 2 3 10 11\"\n\nTEST_NAME_10=\"t_server_null_client.sh-openvpn_2_6_8_udp\"\nSHOULD_PASS_10=\"yes\"\nCLIENT_EXEC_10=\"/usr/sbin/openvpn\"\nCLIENT_CONF_10=\"${CLIENT_CONF_BASE} --remote 127.0.0.1 1194 udp --proto udp\"\n\nTEST_NAME_11=\"t_server_null_client.sh-openvpn_2_6_8_tcp\"\nSHOULD_PASS_11=\"yes\"\nCLIENT_EXEC_11=\"/usr/sbin/openvpn\"\nCLIENT_CONF_11=\"${CLIENT_CONF_BASE} --remote 127.0.0.1 1195 tcp --proto tcp\"\n"
  },
  {
    "path": "tests/t_server_null.sh",
    "content": "#!/bin/sh\n#\nTSERVER_NULL_SKIP_RC=\"${TSERVER_NULL_SKIP_RC:-77}\"\n\nif ! [ -r \"./t_server_null.rc\" ] ; then\n    echo \"${0}: cannot find './t_server_null.rc. SKIPPING TEST.'\" >&2\n    exit \"${TSERVER_NULL_SKIP_RC}\"\nfi\n\n. ./t_server_null.rc\n\nif KILL_EXEC=$(which kill); then\n    export KILL_EXEC\nelse\n    echo \"${0}: kill not found in \\$PATH\" >&2\n    exit \"${TSERVER_NULL_SKIP_RC}\"\nfi\n\n# Ensure PREFER_KSU is in a known state\nPREFER_KSU=\"${PREFER_KSU:-0}\"\n\n# make sure we have permissions to run ifconfig/route from OpenVPN\n# can't use \"id -u\" here - doesn't work on Solaris\nID=$(id)\nif expr \"$ID\" : \"uid=0\" >/dev/null\nthen :\nelse\n    if [ \"${PREFER_KSU}\" -eq 1 ];\n    then\n        # Check if we have a valid kerberos ticket\n        if klist -l 1>/dev/null 2>/dev/null; then\n            RUN_SUDO=\"ksu -q -e\"\n        else\n            # No kerberos ticket found, skip ksu and fallback to RUN_SUDO\n            PREFER_KSU=0\n            echo \"${0}: No Kerberos ticket available.  Will not use ksu.\"\n        fi\n    fi\n\n    if [ -z \"$RUN_SUDO\" ]\n    then\n        echo \"${0}: this test must run be as root, or RUN_SUDO=... \" >&2\n        echo \"      must be set correctly in 't_server_null.rc'. SKIP.\" >&2\n        exit \"${TSERVER_NULL_SKIP_RC}\"\n    else\n\t# Run a no-op command with privilege escalation (e.g. sudo) so that\n\t# we (hopefully) do not have to ask the users password during the test.\n\tif $RUN_SUDO \"${KILL_EXEC}\" -0 $$\n\tthen\n\t    echo \"${0}: $RUN_SUDO $KILL_EXEC -0 succeeded, good.\"\n\telse\n\t    echo \"${0}: $RUN_SUDO $KILL_EXEC -0 failed, cannot go on. SKIP.\" >&2\n\t    exit \"${TSERVER_NULL_SKIP_RC}\"\n\tfi\n    fi\nfi\n\nsrcdir=\"${srcdir:-.}\"\nexport t_server_null_logdir=t_server_null-`hostname`-`date +%Y%m%d-%H%M%S`\n\n# Create directory for server and client logs\nmkdir $t_server_null_logdir\n\n\"${srcdir}/t_server_null_server.sh\" &\nT_SERVER_NULL_SERVER_PID=$!\n\n\"${srcdir}/t_server_null_client.sh\"\nretval=$?\n\n# When running make jobs in parallel (\"make -j<x> check\") we need to ensure\n# that this script does not exit before all --dev null servers are dead and\n# their network interfaces are gone. Otherwise t_client.sh will fail because\n# pre and post ifconfig output does not match.\nwait $T_SERVER_NULL_SERVER_PID\n\nif [ $? -ne 0 ]; then\n    exit 1\nelse\n    exit $retval\nfi\n"
  },
  {
    "path": "tests/t_server_null_client.sh",
    "content": "#!/bin/sh\n\nshould_run_test() {\n    test_name=\"$1\"\n\n    if echo \"$test_name\"|grep -q _lwip; then\n        if [ \"$has_lwipovpn\" = \"no\" ]; then\n            return 1\n        fi\n    fi\n\n    return 0\n}\n\nlaunch_client() {\n    test_name=$1\n    log=\"${test_name}.log\"\n    pid=\"${test_name}.pid\"\n    client_exec=$2\n    client_conf=$3\n\n    # Ensure that old log and pid files are gone\n    rm -f \"${log}\" \"${pid}\"\n\n    \"${client_exec}\" \\\n        $client_conf \\\n        --writepid \"${pid}\" \\\n        --setenv pid \"$pid\" \\\n        --setenv test_name \"$test_name\" \\\n        --log \"${t_server_null_logdir}/${log}\" &\n}\n\nping_and_kill() {\n    if fping -q -c 5 $1; then\n        echo \"PASS: fping lwipovpn client $target\"\n    else\n        echo \"FAIL: fping lwipovpn client $target\"\n\n        # This function runs multiple times in parallel in subshells. That\n        # makes it hard to implement \"fail the test suite if any single fping\n        # test fails\" using exit codes or variables given the limitations of\n        # \"wait\".  Therefore we use a marker file here, which solves the\n        # problem trivially.\n        touch ./lwip_failed\n    fi\n    kill -15 $2\n}\n\nping_lwip_clients() {\n    if [ \"$has_lwipovpn\" = \"yes\" ]; then\n        lwip_client_count=$(echo \"$lwip_test_names\"|wc -w|tr -d \" \")\n    else\n        lwip_client_count=0\n    fi\n\n    if [ $lwip_client_count -eq 0 ]; then\n        return 0\n    fi\n\n    count=0\n    maxcount=10\n    while [ $count -le $maxcount ]; do\n        lwip_client_ips=$(cat ./*.lwip 2>/dev/null|wc -l)\n        if [ $lwip_client_ips -lt $lwip_client_count ]; then\n            echo \"Waiting for LWIP clients to start up ($count/$maxcount)\"\n            count=$(( count + 1))\n            sleep 1\n        else\n            echo \"$lwip_client_ips/$lwip_client_count LWIP clients up\"\n            break\n        fi\n    done\n\n    wait_pids=\"\"\n    for line in $(cat ./*.lwip 2>/dev/null); do\n        target_ip=$(echo $line|cut -d \",\" -f 1)\n        client_pid=$(echo $line|cut -d \",\" -f 2)\n        ping_and_kill $target_ip $client_pid &\n        wait_pids=\"$wait_pids $!\"\n    done\n\n    wait $wait_pids\n\n    test -e ./lwip_failed && return 1 || return 0\n}\n\nwait_for_results() {\n    tests_running=\"yes\"\n\n    # Wait a bit to allow an OpenVPN client process to create a pidfile to\n    # prevent exiting too early\n    sleep 1\n\n    while [ \"${tests_running}\" = \"yes\" ]; do\n        tests_running=\"no\"\n        for t in $test_names; do\n            if [ -f \"${t}.pid\" ]; then\n                tests_running=\"yes\"\n            fi\n        done\n\n        if [ \"${tests_running}\" = \"yes\" ]; then\n            echo \"Clients still running\"\n            sleep 1\n        fi\n    done\n}\n\nget_client_test_result() {\n    test_name=$1\n    should_pass=$2\n    log=\"${test_name}.log\"\n\n    grep \"Initialization Sequence Completed\" \"${t_server_null_logdir}/${log}\" > /dev/null\n    exit_code=$?\n\n    if [ $exit_code -eq 0 ] && [ \"${should_pass}\" = \"yes\" ]; then\n        echo \"PASS ${test_name}\"\n    elif [ $exit_code -eq 1 ] && [ \"${should_pass}\" = \"no\" ]; then\n        echo \"PASS ${test_name} (test failure)\"\n    elif [ $exit_code -eq 0 ] && [ \"${should_pass}\" = \"no\" ]; then\n        echo \"FAIL ${test_name} (test failure)\"\n        cat \"${t_server_null_logdir}/${log}\"\n        retval=1\n    elif [ $exit_code -eq 1 ] && [ \"${should_pass}\" = \"yes\" ]; then\n        echo \"FAIL ${test_name}\"\n        cat \"${t_server_null_logdir}/${log}\"\n        retval=1\n    fi\n}\n\n# Load basic/default tests\n. ${srcdir}/t_server_null_default.rc || exit 1\n\n# Load additional local tests, if any\ntest -r ./t_server_null.rc && . ./t_server_null.rc\n\n# Return value for the entire test suite. Gets set to 1 if any test fails.\nexport retval=0\n\n# Wait until servers are up. This check is based on the presence of processes\n# matching the PIDs in each servers PID files\ncount=0\nserver_max_wait=15\nwhile [ $count -lt $server_max_wait ]; do\n    servers_up=0\n    server_count=$(echo \"$TEST_SERVER_LIST\"|wc -w|tr -d \" \")\n\n    # We need to trim single-quotes because some shells return quoted values\n    # and some don't. Using \"set -o posix\" which would resolve this problem is\n    # not supported in all shells.\n    #\n    # While inactive server configurations may get checked they won't increase\n    # the active server count as the processes won't be running.\n    for i in $(set|grep 'SERVER_NAME_'|cut -d \"=\" -f 2|tr -d \"[\\']\"); do\n        server_pid=$(cat \"$i.pid\" 2> /dev/null)\n        if [ -z \"$server_pid\" ] ; then\n            continue\n        fi\n        if $RUN_SUDO kill -0 $server_pid > /dev/null 2>&1; then\n            servers_up=$(( $servers_up + 1 ))\n        fi\n    done\n\n    echo \"OpenVPN test servers up: ${servers_up}/${server_count}\"\n\n    if [ $servers_up -ge $server_count ]; then\n        retval=0\n        break\n    else\n        count=$(( count + 1))\n        sleep 1\n    fi\n\n    if [ $count -eq $server_max_wait ]; then\n        retval=1\n        exit $retval\n    fi\ndone\n\n# Check for presence of the lwipovpn executable\nif test -r \"$LWIPOVPN_PATH\"; then\n    has_lwipovpn=\"yes\"\nelse\n    has_lwipovpn=\"no\"\n    echo \"WARNING: lwipovpn executable is missing: lwip tests will be skipped\"\nfi\n\n# Remove existing LWIP client IP files. This is to avoid pinging non-existent\n# IP addresses when tests are disabled.\nrm -f ./*.lwip\nrm -f ./lwip_failed\n\n# Wait a while to let server processes to settle down\nsleep 1\n\n# Launch OpenVPN clients. While at it, construct a list of test names. The list\n# is used later to determine when all OpenVPN clients have exited and it is\n# safe to check the test results.\ntest_names=\"\"\nlwip_test_names=\"\"\nfor SUF in $TEST_RUN_LIST\ndo\n    eval test_name=\\\"\\$TEST_NAME_$SUF\\\"\n    eval client_exec=\\\"\\$CLIENT_EXEC_$SUF\\\"\n    eval client_conf=\\\"\\$CLIENT_CONF_$SUF\\\"\n\n    test_names=\"${test_names} ${test_name}\"\n\n    if echo \"$test_name\"|grep -q _lwip; then\n        lwip_test_names=\"${lwip_test_names} ${test_name}\"\n    fi\n\n    if should_run_test \"$test_name\"; then\n        (launch_client \"${test_name}\" \"${client_exec}\" \"${client_conf}\")\n    fi\ndone\n\nping_lwip_clients\nretval=$?\n\n\n# Wait until all OpenVPN clients have exited\n(wait_for_results)\n\n# Check test results\nfor SUF in $TEST_RUN_LIST\ndo\n    eval test_name=\\\"\\$TEST_NAME_$SUF\\\"\n    eval should_pass=\\\"\\$SHOULD_PASS_$SUF\\\"\n\n    if should_run_test \"$test_name\"; then\n        get_client_test_result \"${test_name}\" \"${should_pass}\"\n    fi\ndone\n\nexit $retval\n"
  },
  {
    "path": "tests/t_server_null_default.rc",
    "content": "# -*- shell-script -*-\n# Notes regarding --dev null server and client configurations:\n#\n# The t_server_null_server.sh exits when all client pid files have gone\n# missing. That is the most reliable and fastest way to detect client\n# disconnections in the \"everything runs on localhost\" context. Checking server\n# status files for client connections works, but introduces long delays as\n# --explicit-exit-notify does not seem to work on all client configurations.\n# This means that, by default, there is about 1 minute delay before the server\n# purges clients that have already exited and have not reported back.\n#\nsrcdir=\"${srcdir:-.}\"\ntop_builddir=\"${top_builddir:-..}\"\nsample_keys=\"${srcdir}/../sample/sample-keys\"\n\nCA=\"${sample_keys}/ca.crt\"\nCLIENT_CERT=\"${sample_keys}/client.crt\"\nCLIENT_KEY=\"${sample_keys}/client.key\"\nSERVER_CERT=\"${sample_keys}/server.crt\"\nSERVER_KEY=\"${sample_keys}/server.key\"\nTA=\"${sample_keys}/ta.key\"\n\n# This parameter can't be overridden in t_server_null.rc because that gets\n# loaded too late. However, you can use\n#\n# LWIPOVPN_PATH=/some/path/to/lwipovpn make check\n#\n# to run the tests using lwipovpn in a custom location\n#\nLWIPOVPN_PATH=\"${LWIPOVPN_PATH:-lwipovpn}\"\n\n# Used to detect if graceful kill of any server instance failed during the test\n# run\nSERVER_KILL_FAIL_FILE=\".t_server_null_server.kill_failed\"\n\n# Test server configurations\nMAX_CLIENTS=\"10\"\nCLIENT_MATCH=\"Test-Client\"\nSERVER_EXEC=\"${top_builddir}/src/openvpn/openvpn\"\nSERVER_BASE_OPTS=\"--dev tun --topology subnet --max-clients $MAX_CLIENTS --persist-tun --verb 3 --duplicate-cn\"\nSERVER_BIND_OPTS=\"--local 127.0.0.1\"\nSERVER_CIPHER_OPTS=\"\"\nSERVER_CERT_OPTS=\"--ca ${CA} --cert ${SERVER_CERT} --key ${SERVER_KEY} --tls-auth ${TA} 0\"\nSERVER_CONF_BASE=\"${SERVER_BASE_OPTS} ${SERVER_CIPHER_OPTS} ${SERVER_CERT_OPTS} ${SERVER_BIND_OPTS}\"\nSERVER_CONF_BASE_MULTISOCKET=\"${SERVER_BASE_OPTS} ${SERVER_CIPHER_OPTS} ${SERVER_CERT_OPTS}\"\n\nTEST_SERVER_LIST=\"1 2 3 4\"\n\nSERVER_NAME_1=\"t_server_null_server-1194_udp\"\nSERVER_SERVER_1=\"--server 10.29.41.0 255.255.255.0\"\nSERVER_MGMT_PORT_1=\"11194\"\nSERVER_EXEC_1=\"${SERVER_EXEC}\"\nSERVER_CONF_1=\"${SERVER_CONF_BASE} ${SERVER_SERVER_1} --lport 1194 --proto udp --management 127.0.0.1 ${SERVER_MGMT_PORT_1}\"\n\nSERVER_NAME_2=\"t_server_null_server-1195_tcp\"\nSERVER_SERVER_2=\"--server 10.29.42.0 255.255.255.0\"\nSERVER_MGMT_PORT_2=\"11195\"\nSERVER_EXEC_2=\"${SERVER_EXEC}\"\nSERVER_CONF_2=\"${SERVER_CONF_BASE} ${SERVER_SERVER_2} --lport 1195 --proto tcp --management 127.0.0.1 ${SERVER_MGMT_PORT_2} --dh none\"\n\nSERVER_NAME_3=\"t_server_null_server-1196_udp\"\nSERVER_SERVER_3=\"--server 10.29.43.0 255.255.255.0\"\nSERVER_MGMT_PORT_3=\"11196\"\nSERVER_EXEC_3=\"${SERVER_EXEC}\"\nSERVER_CONF_3=\"${SERVER_CONF_BASE} ${SERVER_SERVER_3} --lport 1196 --proto udp --management 127.0.0.1 ${SERVER_MGMT_PORT_3} --dh none --cipher AES-192-CBC --data-ciphers DEFAULT:AES-192-CBC\"\n\nSERVER_NAME_4=\"t_server_null_server-1197_multisocket_ipv4_ipv6\"\nSERVER_SERVER_4=\"--server 10.29.44.0 255.255.255.0\"\nSERVER_MGMT_PORT_4=\"11197\"\nSERVER_EXEC_4=\"${SERVER_EXEC}\"\nSERVER_CONF_4=\"${SERVER_CONF_BASE_MULTISOCKET} ${SERVER_SERVER_4} --local 127.0.0.1 1197 tcp --local ::1 1197 udp --management 127.0.0.1 ${SERVER_MGMT_PORT_4}\"\n\n# Test client configurations\nCLIENT_EXEC=\"${top_builddir}/src/openvpn/openvpn\"\nCLIENT_BASE_OPTS=\"--client --nobind --remote-cert-tls server --persist-tun --verb 3 --resolv-retry infinite --connect-retry-max 3 --server-poll-timeout 5 --explicit-exit-notify 3 --script-security 2\"\nCLIENT_NULL_OPTS=\"--dev null --ifconfig-noexec --up ${srcdir}/null_client_up.sh\"\nCLIENT_LWIP_OPTS=\"--dev null --dev-node unix:${LWIPOVPN_PATH} --up ${srcdir}/lwip_client_up.sh\"\n\nCLIENT_CIPHER_OPTS=\"\"\nCLIENT_CERT_OPTS=\"--ca ${CA} --cert ${CLIENT_CERT} --key ${CLIENT_KEY} --tls-auth ${TA} 1\"\n\nTEST_RUN_LIST=\"1a 1b 1c 1L 2a 2L 3a 3b 4a 4b 4c\"\nCLIENT_CONF_BASE=\"${CLIENT_NULL_OPTS} ${CLIENT_BASE_OPTS} ${CLIENT_CIPHER_OPTS} ${CLIENT_CERT_OPTS}\"\nCLIENT_CONF_BASE_LWIP=\"${CLIENT_LWIP_OPTS} ${CLIENT_BASE_OPTS} ${CLIENT_CIPHER_OPTS} ${CLIENT_CERT_OPTS}\"\n\nTEST_NAME_1a=\"t_server_null_client.sh-openvpn_current_udp\"\nSHOULD_PASS_1a=\"yes\"\nCLIENT_EXEC_1a=\"${CLIENT_EXEC}\"\nCLIENT_CONF_1a=\"${CLIENT_CONF_BASE} --remote 127.0.0.1 1194 udp --proto udp\"\n\nTEST_NAME_1b=\"t_server_null_client.sh-openvpn_current_udp_fail\"\nSHOULD_PASS_1b=\"no\"\nCLIENT_EXEC_1b=\"${CLIENT_EXEC}\"\nCLIENT_CONF_1b=\"${CLIENT_CONF_BASE} --remote 127.0.0.1 11194 udp --proto udp\"\n\n# --data-cipher list against server with defaults\n# --cipher ignored\nTEST_NAME_1c=\"t_server_null_client.sh-openvpn_current_udp_dc1\"\nSHOULD_PASS_1c=\"yes\"\nCLIENT_EXEC_1c=\"${CLIENT_EXEC}\"\nCLIENT_CONF_1c=\"${CLIENT_CONF_BASE} --remote 127.0.0.1 1194 udp --proto udp --cipher AES-128-CBC --data-ciphers AES-192-CBC:DEFAULT\"\n\nTEST_NAME_1L=\"t_server_null_client.sh-openvpn_current_udp_lwip\"\nSHOULD_PASS_1L=\"yes\"\nCLIENT_EXEC_1L=\"${CLIENT_EXEC}\"\nCLIENT_CONF_1L=\"${CLIENT_CONF_BASE_LWIP} --remote 127.0.0.1 1194 udp --proto udp\"\n\nTEST_NAME_2a=\"t_server_null_client.sh-openvpn_current_tcp\"\nSHOULD_PASS_2a=\"yes\"\nCLIENT_EXEC_2a=\"${CLIENT_EXEC}\"\nCLIENT_CONF_2a=\"${CLIENT_CONF_BASE} --remote 127.0.0.1 1195 tcp --proto tcp\"\n\nTEST_NAME_2L=\"t_server_null_client.sh-openvpn_current_tcp_lwip\"\nSHOULD_PASS_2L=\"yes\"\nCLIENT_EXEC_2L=\"${CLIENT_EXEC}\"\nCLIENT_CONF_2L=\"${CLIENT_CONF_BASE_LWIP} --remote 127.0.0.1 1195 tcp --proto tcp\"\n\n# specific --data-cipher against server that supports that cipher\n# --cipher ignored\nTEST_NAME_3a=\"t_server_null_client.sh-openvpn_current_udp_dc3\"\nSHOULD_PASS_3a=\"yes\"\nCLIENT_EXEC_3a=\"${CLIENT_EXEC}\"\nCLIENT_CONF_3a=\"${CLIENT_CONF_BASE} --remote 127.0.0.1 1196 udp --proto udp --cipher AES-128-CBC --data-ciphers AES-192-CBC\"\n\n# specific --data-cipher against server that doesn't support that cipher\n# --cipher ignored\nTEST_NAME_3b=\"t_server_null_client.sh-openvpn_current_udp_dc3_fail\"\nSHOULD_PASS_3b=\"no\"\nCLIENT_EXEC_3b=\"${CLIENT_EXEC}\"\nCLIENT_CONF_3b=\"${CLIENT_CONF_BASE} --remote 127.0.0.1 1196 udp --proto udp --cipher AES-192-CBC --data-ciphers AES-128-CBC\"\n\nTEST_NAME_4a=\"t_server_null_client.sh-openvpn_current_multisocket_ipv4_tcp\"\nSHOULD_PASS_4a=\"yes\"\nCLIENT_EXEC_4a=\"${CLIENT_EXEC}\"\nCLIENT_CONF_4a=\"${CLIENT_CONF_BASE} --remote 127.0.0.1 1197 tcp\"\n\nTEST_NAME_4b=\"t_server_null_client.sh-openvpn_current_multisocket_ipv6_udp\"\nSHOULD_PASS_4b=\"yes\"\nCLIENT_EXEC_4b=\"${CLIENT_EXEC}\"\nCLIENT_CONF_4b=\"${CLIENT_CONF_BASE} --remote ::1 1197 udp\"\n\nTEST_NAME_4c=\"t_server_null_client.sh-openvpn_current_multisocket_ipv6_tcp_fail\"\nSHOULD_PASS_4c=\"no\"\nCLIENT_EXEC_4c=\"${CLIENT_EXEC}\"\nCLIENT_CONF_4c=\"${CLIENT_CONF_BASE} --remote ::1 1197 tcp\"\n"
  },
  {
    "path": "tests/t_server_null_server.sh",
    "content": "#!/bin/sh\n\nlaunch_server() {\n    server_name=$1\n    server_exec=$2\n    server_conf=$3\n    log=\"${t_server_null_logdir}/${server_name}.log\"\n    status=\"${server_name}.status\"\n    pid=\"${server_name}.pid\"\n\n    # Allow reading this file even umask values are strict\n    touch \"$log\"\n\n    # Try to launch the server\n    $RUN_SUDO \"${server_exec}\" \\\n               $server_conf \\\n               --status \"${status}\" 1 \\\n               --writepid \"${pid}\" \\\n               --explicit-exit-notify 3 > \"$log\" 2>&1 &\n\n    sleep 1\n\n    if ! [ -r \"$pid\" ] || [ -z \"$pid\" ]; then\n        echo \"ERROR: failed to start server $server_name\"\n        tail -n 20 \"$log\"\n    fi\n}\n\n# Make server log files readable by normal users\numask 022\n\n# Load base/default configuration\n. \"${srcdir}/t_server_null_default.rc\" || exit 1\n\n# Load local configuration, if any\ntest -r ./t_server_null.rc && . ./t_server_null.rc\n\n# We can't exit immediately on the first failure as that could leave processes\n# lying around.\nretval=0\n\n# Launch test servers\nfor SUF in $TEST_SERVER_LIST\ndo\n    eval server_name=\\\"\\$SERVER_NAME_$SUF\\\"\n    eval server_exec=\\\"\\$SERVER_EXEC_$SUF\\\"\n    eval server_conf=\\\"\\$SERVER_CONF_$SUF\\\"\n\n    (launch_server \"${server_name}\" \"${server_exec}\" \"${server_conf}\")\ndone\n\n# Create a list of server pid files so that servers can be killed at the end of\n# the test run.\n#\nexport server_pid_files=\"\"\nfor SUF in $TEST_SERVER_LIST\ndo\n    eval server_name=\\\"\\$SERVER_NAME_$SUF\\\"\n    server_pid_files=\"${server_pid_files} ./${server_name}.pid\"\ndone\n\n# Wait until clients are no more, based on the presence of their pid files.\n# Based on practical testing we have to wait at least four seconds to avoid\n# accidentally exiting too early.\ncount=0\nmaxcount=4\nwhile [ $count -le $maxcount ]; do\n    if ls t_server_null_client.sh*.pid > /dev/null 2>&1\n    then\n        count=0\n        sleep 1\n    else\n\tcount=$(( count + 1))\n        sleep 1\n    fi\ndone\n\necho \"All clients have disconnected from all servers\"\n\n# Make sure that the server processes are truly dead before exiting.  If a\n# server process does not exit in 15 seconds assume it never will, move on and\n# hope for the best.\necho \"Waiting for servers to exit\"\nfor PID_FILE in $server_pid_files\ndo\n    SERVER_PID=$(cat \"${PID_FILE}\")\n\n    if [ -z \"$SERVER_PID\" ] ; then\n        echo \"WARNING: could not kill server ${PID_FILE}!\"\n        continue\n    fi\n\n    # Attempt to kill the OpenVPN server gracefully with SIGTERM\n    $RUN_SUDO $KILL_EXEC \"${SERVER_PID}\"\n\n    count=0\n    maxcount=75\n    while [ $count -le $maxcount ]\n    do\n        $RUN_SUDO kill -0 \"${SERVER_PID}\" 2> /dev/null || break\n        count=$(( count + 1))\n        sleep 0.2\n    done\n\n    # If server is still up send a SIGKILL\n    if [ $count -ge $maxcount ]; then\n        $RUN_SUDO $KILL_EXEC -9 \"${SERVER_PID}\"\n        SERVER_NAME=$(basename $PID_FILE|cut -d . -f 1)\n        echo \"ERROR: had to send SIGKILL to server ${SERVER_NAME} with pid ${SERVER_PID}!\"\n        echo \"Tail of server log:\"\n        tail -n 20 \"${t_server_null_logdir}/${SERVER_NAME}.log\"\n        retval=1\n    fi\ndone\n\nexit $retval\n"
  },
  {
    "path": "tests/t_server_null_stress.sh",
    "content": "#!/bin/sh\n#\n# Run this stress test as root to avoid sudo authorization from timing out.\n\nITERATIONS=\"${1:-100}\"\n\n. ./t_server_null_default.rc\n\nexport pid_files=\"\"\nfor SUF in $TEST_SERVER_LIST\ndo\n    eval server_name=\\\"\\$SERVER_NAME_$SUF\\\"\n    pid_files=\"${pid_files} ./${server_name}.pid\"\ndone\n\nLOG_BASEDIR=\"make-check\"\nmkdir -p \"${LOG_BASEDIR}\"\n\ncount=0\nwhile [ $count -lt $ITERATIONS ]; do\n    count=$(( count + 1 ))\n    make check TESTS=t_server_null.sh SUBDIRS= > /dev/null 2>&1\n    retval=$?\n\n    echo \"Iteration ${count}: return value ${retval}\" >> \"${LOG_BASEDIR}/make-check.log\"\n    if [ $retval -ne 0 ]; then\n\tDIR=\"${LOG_BASEDIR}/make-check-${count}\"\n        mkdir -p \"${DIR}\"\n        cp t_server_null*.log \"${DIR}/\"\n        cp test-suite.log \"${DIR}/\"\n        ps aux|grep openvpn|grep -vE '(suppress|grep)' > \"${DIR}/psaux\"\n    fi\ndone\n"
  },
  {
    "path": "tests/unit_tests/Makefile.am",
    "content": "AUTOMAKE_OPTIONS = foreign\n\nif ENABLE_UNITTESTS\nSUBDIRS = example_test openvpn openvpnserv plugins\nendif\n"
  },
  {
    "path": "tests/unit_tests/README.md",
    "content": "Unit Tests\n===========\n\nThis directory contains unit tests for openvpn. New features/bugfixes should be written in a test friendly way and come with corresponding tests.\n\nRun tests\n----------\n\nTests are run by `make check`. A failed tests stops test execution. To run all\ntests regardless of errors call `make -k check`.\n\nAdd new tests to existing test suite\n-------------------------------------\n\nTest suites are organized in directories. [example_test/](example_test/) is an example\nfor a test suite with two test executables. Feel free to use it as a template for new tests.\n\nTest suites\n--------------------\n\nTest suites live inside a subdirectory of `$ROOT/tests/unit_tests`, e.g. `$ROOT/tests/unit_tests/my_feature`.\n\nTest suites are configured by a `Makefile.am`. Tests are executed by testdrivers. One testsuite can contain more than one testdriver.\n\n### Hints\n* Name suites & testdrivers in a way that the name of the driver says something about which component/feature is tested\n* Name the testdriver executable `*_testdriver`. This way it gets picked up by the default `.gitignore`\n  * If this is not feasible: Add all output to a `.gitignore`* Use descriptive test names: `coffee_brewing__with_no_beans__fails` vs. `test34`\n* Testing a configurable feature?  Wrap test execution with a conditional (see [auth_pam](plugins/auth-pam/Makefile.am) for an example)\n* Add multiple test-drivers when one testdriver looks crowded with tests\n\n### New Test Suites\n1.  Organize tests in folders for features.\n2.  Add the new test directory to `SUBDIRS` in `Makefile.am`\n3.  Edit `configure.ac` and add the new `Makefile` to `AC_CONFIG_FILES`\n4.  Run `./configure`, and *enable* the feature you'd like to test\n5.  Make sure that `make check` runs your tests\n6.  Check: Would a stranger be able to easily find your tests by you looking at the test output?\n7. Run `./configure`, and *disable* the feature you'd like to test\n8.  Make sure that `make check` does *not run* your tests\n"
  },
  {
    "path": "tests/unit_tests/example_test/Makefile.am",
    "content": "AUTOMAKE_OPTIONS = foreign\n\nAM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING) example Unit-Tests'\n\ncheck_PROGRAMS = example_testdriver example2_testdriver\n\nif !CROSS_COMPILING\nTESTS = $(check_PROGRAMS)\nendif\n\nexample_testdriver_CFLAGS  = @TEST_CFLAGS@\nexample_testdriver_LDFLAGS = @TEST_LDFLAGS@\nexample_testdriver_SOURCES = test.c\n\nexample2_testdriver_CFLAGS  = @TEST_CFLAGS@\nexample2_testdriver_LDFLAGS = @TEST_LDFLAGS@\nexample2_testdriver_SOURCES = test2.c\n"
  },
  {
    "path": "tests/unit_tests/example_test/README.md",
    "content": "This test only checks that test compilation works. This example contains two test executables.\n\nThese tests can be used as template for 'real' tests.\n"
  },
  {
    "path": "tests/unit_tests/example_test/test.c",
    "content": "#include <stdio.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <setjmp.h>\n#include <stdint.h>\n#include <cmocka.h>\n\nstatic int\nsetup(void **state)\n{\n    int *answer = malloc(sizeof(int));\n\n    *answer = 42;\n    *state = answer;\n\n    return 0;\n}\n\nstatic int\nteardown(void **state)\n{\n    free(*state);\n\n    return 0;\n}\n\nstatic void\nnull_test_success(void **state)\n{\n    (void)state;\n}\n\nstatic void\nint_test_success(void **state)\n{\n    int *answer = *state;\n    assert_int_equal(*answer, 42);\n}\n\n__attribute__((unused)) static void\nfailing_test(void **state)\n{\n    /* This tests fails to test that make check fails */\n    assert_int_equal(0, 42);\n}\n\nint\nmain(void)\n{\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(null_test_success),\n        cmocka_unit_test_setup_teardown(int_test_success, setup, teardown),\n        /*        cmocka_unit_test(failing_test), */\n    };\n\n    return cmocka_run_group_tests_name(\"success_test\", tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/example_test/test2.c",
    "content": "#include <stdio.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <setjmp.h>\n#include <stdint.h>\n#include <cmocka.h>\n\n\nstatic void\ntest_true(void **state)\n{\n    (void)state;\n}\n\n\nint\nmain(void)\n{\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(test_true),\n    };\n\n    return cmocka_run_group_tests_name(\"success_test2\", tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/Makefile.am",
    "content": "AUTOMAKE_OPTIONS = foreign\n\nEXTRA_DIST = input\n\nAM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING) Unit-Tests'\n\nAM_TESTS_ENVIRONMENT = export LSAN_OPTIONS=suppressions=$(srcdir)/input/leak_suppr.txt;\n\ntest_binaries = argv_testdriver \\\n\tauth_token_testdriver \\\n\tbuffer_testdriver \\\n\tcrypto_testdriver \\\n\tdhcp_testdriver \\\n\tncp_testdriver \\\n\tmbuf_testdriver \\\n\tmisc_testdriver \\\n\toptions_parse_testdriver \\\n\tpacket_id_testdriver \\\n\tpkt_testdriver \\\n\tprovider_testdriver \\\n\tpush_update_msg_testdriver \\\n\tsocket_testdriver \\\n\tssl_testdriver \\\n\tuser_pass_testdriver\n\nif HAVE_LD_WRAP_SUPPORT\nif !WIN32\ntest_binaries += tls_crypt_testdriver\nendif\nendif\n\nif WIN32\ntest_binaries += cryptoapi_testdriver\nLDADD = -lws2_32\nendif\n\nif HAVE_SOFTHSM2\nif !WIN32\ntest_binaries += pkcs11_testdriver\nendif\nendif\n\nif !CROSS_COMPILING\nTESTS = $(test_binaries)\nendif\ncheck_PROGRAMS = $(test_binaries)\n\nif HAVE_SITNL\ncheck_PROGRAMS += networking_testdriver\nendif\n\nargv_testdriver_CFLAGS  = -I$(top_srcdir)/src/openvpn -I$(top_srcdir)/src/compat @TEST_CFLAGS@\nargv_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(top_srcdir)/src/openvpn\nargv_testdriver_SOURCES = test_argv.c \\\n\tmock_msg.c mock_msg.h test_common.h \\\n\tmock_get_random.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c \\\n\t$(top_srcdir)/src/openvpn/argv.c\n\nbuffer_testdriver_CFLAGS  = -I$(top_srcdir)/src/openvpn -I$(top_srcdir)/src/compat @TEST_CFLAGS@\nbuffer_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(top_srcdir)/src/openvpn\nbuffer_testdriver_SOURCES = test_buffer.c \\\n\tmock_msg.c mock_msg.h test_common.h \\\n\tmock_get_random.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c \\\n\t$(top_srcdir)/src/openvpn/platform.c\n\ncrypto_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t@TEST_CFLAGS@\ncrypto_testdriver_LDFLAGS = @TEST_LDFLAGS@\ncrypto_testdriver_SOURCES = test_crypto.c \\\n\tmock_msg.c mock_msg.h test_common.h \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/crypto.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls_legacy.c \\\n\t$(top_srcdir)/src/openvpn/crypto_openssl.c \\\n\t$(top_srcdir)/src/openvpn/crypto_epoch.c \\\n\t$(top_srcdir)/src/openvpn/otime.c \\\n\t$(top_srcdir)/src/openvpn/packet_id.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/mtu.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c \\\n\t$(top_srcdir)/src/openvpn/mss.c\n\ndhcp_testdriver_CFLAGS  = -I$(top_srcdir)/src/openvpn -I$(top_srcdir)/src/compat @TEST_CFLAGS@ -DDHCP_UNIT_TEST\ndhcp_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(top_srcdir)/src/openvpn\ndhcp_testdriver_SOURCES = test_dhcp.c \\\n\tmock_msg.c mock_msg.h test_common.h \\\n\tmock_get_random.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c\n\nssl_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t@TEST_CFLAGS@\nssl_testdriver_LDFLAGS = @TEST_LDFLAGS@  $(OPTIONAL_CRYPTO_LIBS)\nssl_testdriver_SOURCES = test_ssl.c \\\n\tmock_msg.c mock_msg.h test_common.h \\\n\tmock_management.c \\\n\tmock_ssl_dependencies.c mock_win32_execve.c \\\n\t$(top_srcdir)/src/openvpn/argv.c \\\n\t$(top_srcdir)/src/openvpn/base64.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/compat/compat-strsep.c \\\n\t$(top_srcdir)/src/openvpn/crypto.c \\\n\t$(top_srcdir)/src/openvpn/cryptoapi.c \\\n\t$(top_srcdir)/src/openvpn/crypto_epoch.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls_legacy.c \\\n\t$(top_srcdir)/src/openvpn/crypto_openssl.c \\\n\t$(top_srcdir)/src/openvpn/env_set.c \\\n\t$(top_srcdir)/src/openvpn/mss.c \\\n\t$(top_srcdir)/src/openvpn/mtu.c \\\n\t$(top_srcdir)/src/openvpn/otime.c \\\n\t$(top_srcdir)/src/openvpn/options_util.c \\\n\t$(top_srcdir)/src/openvpn/packet_id.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/run_command.c \\\n\t$(top_srcdir)/src/openvpn/ssl_openssl.c \\\n\t$(top_srcdir)/src/openvpn/ssl_mbedtls.c \\\n\t$(top_srcdir)/src/openvpn/ssl_util.c \\\n\t$(top_srcdir)/src/openvpn/ssl_verify_mbedtls.c \\\n\t$(top_srcdir)/src/openvpn/ssl_verify_openssl.c \\\n\t$(top_srcdir)/src/openvpn/xkey_helper.c \\\n\t$(top_srcdir)/src/openvpn/xkey_provider.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c\n\nif WIN32\nssl_testdriver_LDADD =  -lcrypt32 -lncrypt -lfwpuclnt -liphlpapi -lws2_32\nendif\n\npacket_id_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t@TEST_CFLAGS@\npacket_id_testdriver_LDFLAGS = @TEST_LDFLAGS@\npacket_id_testdriver_SOURCES = test_packet_id.c \\\n\tmock_msg.c mock_msg.h test_common.h \\\n\tmock_get_random.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/otime.c \\\n\t$(top_srcdir)/src/openvpn/packet_id.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/reliable.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c \\\n\t$(top_srcdir)/src/openvpn/session_id.c\n\npkt_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t@TEST_CFLAGS@\npkt_testdriver_LDFLAGS = @TEST_LDFLAGS@\npkt_testdriver_SOURCES = test_pkt.c mock_msg.c mock_msg.h mock_win32_execve.c test_common.h \\\n\t$(top_srcdir)/src/openvpn/argv.c \\\n\t$(top_srcdir)/src/openvpn/base64.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/crypto.c \\\n\t$(top_srcdir)/src/openvpn/crypto_epoch.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls_legacy.c \\\n\t$(top_srcdir)/src/openvpn/crypto_openssl.c \\\n\t$(top_srcdir)/src/openvpn/env_set.c \\\n\t$(top_srcdir)/src/openvpn/otime.c \\\n\t$(top_srcdir)/src/openvpn/packet_id.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/reliable.c \\\n\t$(top_srcdir)/src/openvpn/run_command.c \\\n\t$(top_srcdir)/src/openvpn/session_id.c \\\n\t$(top_srcdir)/src/openvpn/ssl_pkt.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c \\\n\t$(top_srcdir)/src/openvpn/tls_crypt.c\n\nif !WIN32\ntls_crypt_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t@TEST_CFLAGS@\ntls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \\\n\t-Wl,--wrap=buffer_read_from_file \\\n\t-Wl,--wrap=buffer_write_file \\\n\t-Wl,--wrap=parse_line \\\n\t-Wl,--wrap=rand_bytes\ntls_crypt_testdriver_SOURCES = test_tls_crypt.c \\\n\tmock_msg.c mock_msg.h test_common.h \\\n\tmock_win32_execve.c \\\n\t$(top_srcdir)/src/openvpn/argv.c \\\n\t$(top_srcdir)/src/openvpn/base64.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/crypto.c \\\n\t$(top_srcdir)/src/openvpn/crypto_epoch.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls_legacy.c \\\n\t$(top_srcdir)/src/openvpn/crypto_openssl.c \\\n\t$(top_srcdir)/src/openvpn/env_set.c \\\n\t$(top_srcdir)/src/openvpn/otime.c \\\n\t$(top_srcdir)/src/openvpn/packet_id.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/run_command.c\nendif\n\nif HAVE_SITNL\nnetworking_testdriver_CFLAGS = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t@TEST_CFLAGS@ $(OPTIONAL_CRYPTO_CFLAGS)\nnetworking_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(top_srcdir)/src/openvpn \\\n\t$(OPTIONAL_CRYPTO_LIBS)\nnetworking_testdriver_SOURCES = test_networking.c mock_msg.c \\\n\t$(top_srcdir)/src/openvpn/networking_sitnl.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/crypto.c \\\n\t$(top_srcdir)/src/openvpn/crypto_epoch.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls_legacy.c \\\n\t$(top_srcdir)/src/openvpn/crypto_openssl.c \\\n\t$(top_srcdir)/src/openvpn/fdmisc.c \\\n\t$(top_srcdir)/src/openvpn/otime.c \\\n\t$(top_srcdir)/src/openvpn/packet_id.c \\\n\t$(top_srcdir)/src/openvpn/platform.c\nendif\n\noptions_parse_testdriver_CFLAGS  = -I$(top_srcdir)/src/openvpn -I$(top_srcdir)/src/compat @TEST_CFLAGS@\noptions_parse_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(top_srcdir)/src/openvpn\noptions_parse_testdriver_SOURCES = test_options_parse.c \\\n\tmock_msg.c mock_msg.h test_common.h \\\n\tmock_get_random.c \\\n\t$(top_srcdir)/src/openvpn/options_parse.c \\\n\t$(top_srcdir)/src/openvpn/options_util.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c \\\n\t$(top_srcdir)/src/openvpn/platform.c\n\nprovider_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t@TEST_CFLAGS@ $(OPTIONAL_CRYPTO_CFLAGS)\nprovider_testdriver_LDFLAGS = @TEST_LDFLAGS@ \\\n\t$(OPTIONAL_CRYPTO_LIBS)\n\nprovider_testdriver_SOURCES = test_provider.c mock_msg.c \\\n\t$(top_srcdir)/src/openvpn/xkey_helper.c \\\n\t$(top_srcdir)/src/openvpn/xkey_provider.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/base64.c \\\n\tmock_get_random.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c \\\n\t$(top_srcdir)/src/openvpn/platform.c\n\nif WIN32\ncryptoapi_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t@TEST_CFLAGS@ $(OPTIONAL_CRYPTO_CFLAGS)\ncryptoapi_testdriver_LDFLAGS = @TEST_LDFLAGS@ \\\n\t$(OPTIONAL_CRYPTO_LIBS) -lcrypt32 -lncrypt\ncryptoapi_testdriver_SOURCES = test_cryptoapi.c mock_msg.c \\\n\tpkey_test_utils.c cert_data.h \\\n\t$(top_srcdir)/src/openvpn/xkey_helper.c \\\n\t$(top_srcdir)/src/openvpn/xkey_provider.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/base64.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\tmock_get_random.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c\nendif\n\nif HAVE_SOFTHSM2\nif !WIN32\npkcs11_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t@TEST_CFLAGS@ $(OPTIONAL_CRYPTO_CFLAGS)\npkcs11_testdriver_LDFLAGS = @TEST_LDFLAGS@ \\\n\t$(OPTIONAL_CRYPTO_LIBS)\npkcs11_testdriver_SOURCES = test_pkcs11.c \\\n\tmock_msg.c test_common.h \\\n\tpkey_test_utils.c cert_data.h mock_get_random.c \\\n\t$(top_srcdir)/src/openvpn/xkey_helper.c \\\n\t$(top_srcdir)/src/openvpn/xkey_provider.c \\\n\t$(top_srcdir)/src/openvpn/argv.c \\\n\t$(top_srcdir)/src/openvpn/env_set.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/otime.c \\\n\t$(top_srcdir)/src/openvpn/run_command.c \\\n\t$(top_srcdir)/src/openvpn/base64.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/pkcs11.c \\\n\t$(top_srcdir)/src/openvpn/pkcs11_openssl.c\nendif\nendif\n\nauth_token_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t@TEST_CFLAGS@ $(OPTIONAL_CRYPTO_CFLAGS)\nauth_token_testdriver_LDFLAGS = @TEST_LDFLAGS@ \\\n\t$(OPTIONAL_CRYPTO_LIBS)\n\nauth_token_testdriver_SOURCES = test_auth_token.c \\\n\tmock_msg.c test_common.h \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/crypto.c \\\n\t$(top_srcdir)/src/openvpn/crypto_epoch.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls_legacy.c \\\n\t$(top_srcdir)/src/openvpn/crypto_openssl.c \\\n\t$(top_srcdir)/src/openvpn/otime.c \\\n\t$(top_srcdir)/src/openvpn/packet_id.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c \\\n\t$(top_srcdir)/src/openvpn/base64.c\n\n\nuser_pass_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t@TEST_CFLAGS@\nuser_pass_testdriver_LDFLAGS = @TEST_LDFLAGS@\n\nuser_pass_testdriver_SOURCES = test_user_pass.c \\\n\tmock_msg.c test_common.h \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/console.c \\\n\t$(top_srcdir)/src/openvpn/env_set.c \\\n\tmock_win32_execve.c \\\n\t$(top_srcdir)/src/openvpn/run_command.c \\\n\tmock_get_random.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c \\\n\t$(top_srcdir)/src/openvpn/base64.c\n\n\nncp_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t@TEST_CFLAGS@ $(OPTIONAL_CRYPTO_CFLAGS)\nncp_testdriver_LDFLAGS = @TEST_LDFLAGS@ \\\n\t$(OPTIONAL_CRYPTO_LIBS)\n\nncp_testdriver_SOURCES = test_ncp.c \\\n\tmock_msg.c test_common.h \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/crypto.c \\\n\t$(top_srcdir)/src/openvpn/crypto_epoch.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls.c \\\n\t$(top_srcdir)/src/openvpn/crypto_mbedtls_legacy.c \\\n\t$(top_srcdir)/src/openvpn/crypto_openssl.c \\\n\t$(top_srcdir)/src/openvpn/otime.c \\\n\t$(top_srcdir)/src/openvpn/packet_id.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c \\\n\t$(top_srcdir)/src/compat/compat-strsep.c \\\n\t$(top_srcdir)/src/openvpn/ssl_util.c\n\nmbuf_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t-DSOURCEDIR=\\\"$(top_srcdir)\\\" @TEST_CFLAGS@\n\nmbuf_testdriver_LDFLAGS = @TEST_LDFLAGS@\n\nmbuf_testdriver_SOURCES = test_mbuf.c \\\n\tmock_msg.c test_common.h  \\\n\tmock_get_random.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/mbuf.c\n\nmisc_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t-DSOURCEDIR=\\\"$(top_srcdir)\\\" @TEST_CFLAGS@\n\nmisc_testdriver_LDFLAGS = @TEST_LDFLAGS@\n\nmisc_testdriver_SOURCES = test_misc.c \\\n\tmock_msg.c test_common.h  \\\n\tmock_get_random.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/options_util.c \\\n\t$(top_srcdir)/src/openvpn/ssl_util.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/list.c\n\npush_update_msg_testdriver_CFLAGS = -I$(top_srcdir)/src/openvpn \\\n\t-I$(top_srcdir)/src/compat \\\n\t-I$(top_srcdir)/tests/unit_tests/openvpn \\\n\t@TEST_CFLAGS@\n\npush_update_msg_testdriver_LDFLAGS = \\\n\t@TEST_LDFLAGS@ \\\n\t-L$(top_srcdir)/src/openvpn\n\npush_update_msg_testdriver_SOURCES = test_push_update_msg.c \\\n\tmock_msg.c \\\n\tmock_get_random.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/options_util.c \\\n\t$(top_srcdir)/src/openvpn/otime.c \\\n\t$(top_srcdir)/src/openvpn/list.c\n\nsocket_testdriver_CFLAGS  = \\\n\t-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \\\n\t-DSOURCEDIR=\\\"$(top_srcdir)\\\" @TEST_CFLAGS@\n\nsocket_testdriver_LDFLAGS = @TEST_LDFLAGS@ $(SOCKETS_LIBS)\n\nsocket_testdriver_SOURCES = test_socket.c \\\n\tmock_msg.c test_common.h  \\\n\tmock_get_random.c \\\n\tmock_management.c \\\n\t$(top_srcdir)/src/openvpn/buffer.c \\\n\t$(top_srcdir)/src/openvpn/win32-util.c \\\n\t$(top_srcdir)/src/openvpn/platform.c \\\n\t$(top_srcdir)/src/openvpn/env_set.c \\\n\t$(top_srcdir)/src/openvpn/run_command.c \\\n\t$(top_srcdir)/src/openvpn/socket_util.c\n"
  },
  {
    "path": "tests/unit_tests/openvpn/cert_data.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2023-2026 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by the\n *  Free Software Foundation, either version 2 of the License,\n *  or (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef CERT_DATA_H\n#define CERT_DATA_H\n\n/* Some certificates and their private keys for testing cryptoapi.c.\n * Two certificates, cert1 (EC) and cert3 (RSA) are signed by one CA\n * and the other two, cert2 (EC) and cert4 (RSA), by another to have a\n * different issuer name. The common name of cert4 is the same as\n * that of cert3 but the former has expired. It is used to test\n * retrieval of valid certificate by name when an expired one with same\n * common name exists.\n * To reduce data volume, certs of same keytype use the same private key.\n */\n\n/* sample-ec.crt */\nstatic const char *const cert1 =\n    \"-----BEGIN CERTIFICATE-----\\n\"\n    \"MIIClzCCAX+gAwIBAgIRAIJr3cy95V63CPEtaAA8JN4wDQYJKoZIhvcNAQELBQAw\\n\"\n    \"GDEWMBQGA1UEAwwNT1ZQTiBURVNUIENBMTAgFw0yMzAzMTMxNjExMjhaGA8yMTIz\\n\"\n    \"MDIxNzE2MTEyOFowGDEWMBQGA1UEAwwNb3Zwbi10ZXN0LWVjMTBZMBMGByqGSM49\\n\"\n    \"AgEGCCqGSM49AwEHA0IABHhJG+dK4Z0mY+K0pupwVtyDLOwwGWHjBY6u3LgjRmUh\\n\"\n    \"fFjaoSfJvdgrPg50wbOkrsUt9Bl6EeDosZuVwuzgRbujgaQwgaEwCQYDVR0TBAIw\\n\"\n    \"ADAdBgNVHQ4EFgQUPWeU5BEmD8VEOSKeNf9kAvhcVuowUwYDVR0jBEwwSoAU3MLD\\n\"\n    \"NDOK13DqflQ8ra7FeGBXK06hHKQaMBgxFjAUBgNVBAMMDU9WUE4gVEVTVCBDQTGC\\n\"\n    \"FD55ErHXpK2JXS3WkfBm0NB1r3vKMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAsGA1Ud\\n\"\n    \"DwQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAhH/wOFqP4R+FK5QvU+oW/XacFMku\\n\"\n    \"+qT8lL9J7BG28WhZ0ZcAy/AmtnyynkDyuZSwnlzGgJ5m4L/RfwTzJKhEHiSU3BvB\\n\"\n    \"5C1Z1Q8k67MHSfb565iCn8GzPUQLK4zsILCoTkJPvimv2bJ/RZmNaD+D4LWiySD4\\n\"\n    \"tuOEdHKrxIrbJ5eAaN0WxRrvDdwGlyPvbMFvfhXzd/tbkP4R2xvlm7S2DPeSTJ8s\\n\"\n    \"srXMaPe0lAea4etMSZsjIRPwGRMXBrwbRmb6iN2Cq40867HdaJoAryYig7IiDwSX\\n\"\n    \"htCbOA6sX+60+FEOYDEx5cmkogl633Pw7LJ3ICkyzIrUSEt6BOT1Gsc1eQ==\\n\"\n    \"-----END CERTIFICATE-----\\n\";\nstatic const char *const key1 = \"-----BEGIN PRIVATE KEY-----\\n\"\n                                \"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg5Xpw/lLvBrWjAWDq\\n\"\n                                \"L6dm/4a1or6AQ6O3yXYgw78B23ihRANCAAR4SRvnSuGdJmPitKbqcFbcgyzsMBlh\\n\"\n                                \"4wWOrty4I0ZlIXxY2qEnyb3YKz4OdMGzpK7FLfQZehHg6LGblcLs4EW7\\n\"\n                                \"-----END PRIVATE KEY-----\\n\";\nstatic const char *const hash1 = \"A4B74F1D68AF50691F62CBD675E24C8655369567\";\nstatic const char *const cname1 = \"ovpn-test-ec1\";\n\nstatic const char *const cert2 =\n    \"-----BEGIN CERTIFICATE-----\\n\"\n    \"MIIClzCCAX+gAwIBAgIRAN9fIkTDOjX0Bd9adHVcLx8wDQYJKoZIhvcNAQELBQAw\\n\"\n    \"GDEWMBQGA1UEAwwNT1ZQTiBURVNUIENBMjAgFw0yMzAzMTMxODAzMzFaGA8yMTIz\\n\"\n    \"MDIxNzE4MDMzMVowGDEWMBQGA1UEAwwNb3Zwbi10ZXN0LWVjMjBZMBMGByqGSM49\\n\"\n    \"AgEGCCqGSM49AwEHA0IABHhJG+dK4Z0mY+K0pupwVtyDLOwwGWHjBY6u3LgjRmUh\\n\"\n    \"fFjaoSfJvdgrPg50wbOkrsUt9Bl6EeDosZuVwuzgRbujgaQwgaEwCQYDVR0TBAIw\\n\"\n    \"ADAdBgNVHQ4EFgQUPWeU5BEmD8VEOSKeNf9kAvhcVuowUwYDVR0jBEwwSoAUyX3c\\n\"\n    \"tpRP5cKlESsG80rOGhEphsGhHKQaMBgxFjAUBgNVBAMMDU9WUE4gVEVTVCBDQTKC\\n\"\n    \"FBc8ra53hwYrlIkdY3Ay1WCrrHJ8MBMGA1UdJQQMMAoGCCsGAQUFBwMCMAsGA1Ud\\n\"\n    \"DwQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAWmA40BvEgBbKb1ReKlKzk64xi2ak\\n\"\n    \"4tyr3sW9wIYQ2N1zkSomwEV6wGEawLqPADRbXiYdjtAqLz12OJvBnBwgxN3dVmqL\\n\"\n    \"6UN4ZIwMWJ4fSW9vK/Nt+JNwebN+Jgw/nIXvSdK95ha4iusZZOIZ4qDj3DWwjhjV\\n\"\n    \"L5/m6zP09L9G9/79j1Tsu4Stl5SI1XxtYc0eVn29vJEMBfpsS7pPD6V9JpY3Y1f3\\n\"\n    \"HeTsAlHjfFEReVDiNCI9vMQLKFKKWnAorT2+iyRueA3bt2gchf863BBhZvJddL7Q\\n\"\n    \"KBa0osXw+eGBRAwsm7m1qCho3b3fN2nFAa+k07ptRkOeablmFdXE81nVlA==\\n\"\n    \"-----END CERTIFICATE-----\\n\";\n#define key2 key1\nstatic const char *const hash2 = \"FA18FD34BAABE47D6E2910E080F421C109CA97F5\";\nstatic const char *const cname2 = \"ovpn-test-ec2\";\n\nstatic const char *const cert3 =\n    \"-----BEGIN CERTIFICATE-----\\n\"\n    \"MIIDYzCCAkugAwIBAgIRALrXTx4lqa8QgF7uGjISxmcwDQYJKoZIhvcNAQELBQAw\\n\"\n    \"GDEWMBQGA1UEAwwNT1ZQTiBURVNUIENBMTAgFw0yMzAzMTMxNjA5MThaGA8yMTIz\\n\"\n    \"MDIxNzE2MDkxOFowGTEXMBUGA1UEAwwOb3Zwbi10ZXN0LXJzYTEwggEiMA0GCSqG\\n\"\n    \"SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7xFoR6fmoyfsJIQDKKgbYgFw0MzVuDAmp\\n\"\n    \"Rx6KTEihgTchkQx9fHddWbKiOUbcEnQi3LNux7P4QVl/4dRR3skisBug6Vd5LXeB\\n\"\n    \"GZqmpu5XZiF4DgLz1lX21G0aOogFWkie2qGEcso40159x9FBDl5A3sLP18ubeex0\\n\"\n    \"pd/BzDFv6SLOTyVWO/GCNc8IX/i0uN4mLvoVU00SeqwTPnS+CRXrSq4JjGDJLsXl\\n\"\n    \"0/PlxkjsgU0yOOA0Z2d8Fzk3wClwP6Hc49BOMWKstUIhLbG2DcIv8l29EuEj2w3j\\n\"\n    \"u/7gkewol96XQ2twpPvpoVAaiVh/m7hQUcQORQCD6eJcDjOZVCArAgMBAAGjgaQw\\n\"\n    \"gaEwCQYDVR0TBAIwADAdBgNVHQ4EFgQUqYnRaBHrZmKLtMZES5AuwqzJkGYwUwYD\\n\"\n    \"VR0jBEwwSoAU3MLDNDOK13DqflQ8ra7FeGBXK06hHKQaMBgxFjAUBgNVBAMMDU9W\\n\"\n    \"UE4gVEVTVCBDQTGCFD55ErHXpK2JXS3WkfBm0NB1r3vKMBMGA1UdJQQMMAoGCCsG\\n\"\n    \"AQUFBwMCMAsGA1UdDwQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAZVcXrezA9Aby\\n\"\n    \"sfUNHAsMxrex/EO0PrIPSrmSmc9sCiD8cCIeB6kL8c5iPPigoWW0uLA9zteDRFes\\n\"\n    \"ez+Z8wBY6g8VQ0tFPURDooUg5011GZPDcuw7/PsI4+I2J9q6LHEp+6Oo4faSn/kl\\n\"\n    \"yWYCLjM4FZdGXbOijDacQJiN6HcRv0UdodBrEVRf7YHJJmMCbCI7ZUGW2zef/+rO\\n\"\n    \"e4Lkxh0MLYqCkNKH5ZfoGTC4Oeb0xKykswAanqgR60r+upaLU8PFuI2L9M3vc6KU\\n\"\n    \"F6MgVGSxl6eylJgDYckvJiAbmcp2PD/LRQQOxQA0yqeAMg2cbdvclETuYD6zoFfu\\n\"\n    \"Y8aO7dvDlw==\\n\"\n    \"-----END CERTIFICATE-----\\n\";\nstatic const char *const key3 = \"-----BEGIN PRIVATE KEY-----\\n\"\n                                \"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC7xFoR6fmoyfsJ\\n\"\n                                \"IQDKKgbYgFw0MzVuDAmpRx6KTEihgTchkQx9fHddWbKiOUbcEnQi3LNux7P4QVl/\\n\"\n                                \"4dRR3skisBug6Vd5LXeBGZqmpu5XZiF4DgLz1lX21G0aOogFWkie2qGEcso40159\\n\"\n                                \"x9FBDl5A3sLP18ubeex0pd/BzDFv6SLOTyVWO/GCNc8IX/i0uN4mLvoVU00SeqwT\\n\"\n                                \"PnS+CRXrSq4JjGDJLsXl0/PlxkjsgU0yOOA0Z2d8Fzk3wClwP6Hc49BOMWKstUIh\\n\"\n                                \"LbG2DcIv8l29EuEj2w3ju/7gkewol96XQ2twpPvpoVAaiVh/m7hQUcQORQCD6eJc\\n\"\n                                \"DjOZVCArAgMBAAECggEACqkuWAAJ3cyCBVWrXs8eDmLTWV9i9DmYvtS75ixIn2rf\\n\"\n                                \"v3cl12YevN0f6FgKLuqZT3Vqdqq+DCVhuIIQ9QkKMH8BQpSdE9NCCsFyZ23o8Gtr\\n\"\n                                \"EQ7ymfecb+RFwYx7NpqWrvZI32VJGArgPZH/zorLTTGYrAZbmBtHEqRsXOuEDw97\\n\"\n                                \"slwwcWaa9ztaYC8/N/7fgsnydaCFSaOByRlWuyvSmHvn6ZwLv8ANOshY6fstC0Jb\\n\"\n                                \"BW0GpSe9eZPjpl71VT2RtpghqLV5+iAoFDHoT+eZvBospcUGtfcZSU7RrBjKB8+a\\n\"\n                                \"U1d6hwKhduVs2peIQzl+FiOSdWriLcsZv79q4sBhsQKBgQDUDVTf5BGJ8apOs/17\\n\"\n                                \"YVk+Ad8Ey8sXvsfk49psmlCRa8Z4g0LVXfrP94qzhtl8U5kE9hs3nEF4j/kX1ZWG\\n\"\n                                \"k11tdsNTZN5x5bbAgEgPA6Ap6J/uto0HS8G0vSv0lyBymdKA3p/i5Dx+8Nc9cGns\\n\"\n                                \"LGI9MvviLX7pQFIkvbaCkdKwYwKBgQDirowjWZnm7BgVhF0G1m3DY9nQTYYU185W\\n\"\n                                \"UESaO5/nVzwUrA+FypJamD+AvmlSuY8rJeQAGAS6nQr9G8/617r+GwJnzRtxC6Vl\\n\"\n                                \"4OF5BJRsD70oX4CFOOlycMoJ8tzcYVH7NI8KVocjxb+QW82hqSvEwSsvnwwn3eOW\\n\"\n                                \"nr5u5vIHmQKBgCuc3lL6Dl1ntdZgEIdau0cUjXDoFUo589TwxBDIID/4gaZxoMJP\\n\"\n                                \"hPFXAVDxMDPw4azyjSB/47tPKTUsuYcnMfT8kynIujOEwnSPLcLgxQU5kgM/ynuw\\n\"\n                                \"qhNpQOwaVRMc7f2RTCMXPBYDpNE/GJn5eu8JWGLpZovEreBeoHX0VffvAoGAVrWn\\n\"\n                                \"+3mxykhzaf+oyg3KDNysG+cbq+tlDVVE+K5oG0kePVYX1fjIBQmJ+QhdJ3y9jCbB\\n\"\n                                \"UVveqzeZVXqHEw/kgoD4aZZmsdZfnVnpRa5/y9o1ZDUr50n+2nzUe/u/ijlb77iK\\n\"\n                                \"Is04gnGJNoI3ZWhdyrSNfXjcYH+bKClu9OM4n7kCgYAorc3PAX7M0bsQrrqYxUS8\\n\"\n                                \"56UU0YdhAgYitjM7Fm/0iIm0vDpSevxL9js4HnnsSMVR77spCBAGOCCZrTcI3Ejg\\n\"\n                                \"xKDYzh1xlfMRjJBuBu5Pd55ZAv9NXFGpsX5SO8fDZQJMwpcbQH36+UdqRRFDpjJ0\\n\"\n                                \"ZbX6nKcJ7jciJVKJds59Jg==\\n\"\n                                \"-----END PRIVATE KEY-----\\n\";\nstatic const char *const hash3 = \"2463628674E362578113F508BA05F29EF142E979\";\nstatic const char *const cname3 = \"ovpn-test-rsa1\";\n\nstatic const char *const cert4 =\n    \"-----BEGIN CERTIFICATE-----\\n\"\n    \"MIIDYTCCAkmgAwIBAgIRAPTJucQy27qoIv0oYoE71z8wDQYJKoZIhvcNAQELBQAw\\n\"\n    \"GDEWMBQGA1UEAwwNT1ZQTiBURVNUIENBMjAeFw0yMzAzMTMxNzQ2MDNaFw0yMzAz\\n\"\n    \"MTQxNzQ2MDNaMBkxFzAVBgNVBAMMDm92cG4tdGVzdC1yc2ExMIIBIjANBgkqhkiG\\n\"\n    \"9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu8RaEen5qMn7CSEAyioG2IBcNDM1bgwJqUce\\n\"\n    \"ikxIoYE3IZEMfXx3XVmyojlG3BJ0Ityzbsez+EFZf+HUUd7JIrAboOlXeS13gRma\\n\"\n    \"pqbuV2YheA4C89ZV9tRtGjqIBVpIntqhhHLKONNefcfRQQ5eQN7Cz9fLm3nsdKXf\\n\"\n    \"wcwxb+kizk8lVjvxgjXPCF/4tLjeJi76FVNNEnqsEz50vgkV60quCYxgyS7F5dPz\\n\"\n    \"5cZI7IFNMjjgNGdnfBc5N8ApcD+h3OPQTjFirLVCIS2xtg3CL/JdvRLhI9sN47v+\\n\"\n    \"4JHsKJfel0NrcKT76aFQGolYf5u4UFHEDkUAg+niXA4zmVQgKwIDAQABo4GkMIGh\\n\"\n    \"MAkGA1UdEwQCMAAwHQYDVR0OBBYEFKmJ0WgR62Zii7TGREuQLsKsyZBmMFMGA1Ud\\n\"\n    \"IwRMMEqAFMl93LaUT+XCpRErBvNKzhoRKYbBoRykGjAYMRYwFAYDVQQDDA1PVlBO\\n\"\n    \"IFRFU1QgQ0EyghQXPK2ud4cGK5SJHWNwMtVgq6xyfDATBgNVHSUEDDAKBggrBgEF\\n\"\n    \"BQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBAFjJvZFwhY77UOWu\\n\"\n    \"O6n5yLxcG6/VNWMbD0CazZP8pBqCGJRU9Rq0vXxZ00E0WSYTJLZoq1aFmeWIX0vZ\\n\"\n    \"sudVkdbfWLdiwuQZDWBS+qC4SkIcnNe5FYSSUlXlvpSUN2CgGCLmryP+SZKHp8YV\\n\"\n    \"e37pQxDjImXCu5Jdk5AhK6pkFm5IMskdTKfWJjjR69lBgWHPoM2WAwkV8vxKdpy8\\n\"\n    \"0Bqef8MZZM+qVYw7OguAFos2Am7waLpa3q9SYqCRYctq4Q2++p2WjINv3nkXIwYS\\n\"\n    \"353PpJJ9s2b/Fqoc4d7udqhQogA7jqbayTKhJxbT134l2NzqDROzuS0kXbX8bXCi\\n\"\n    \"mXSa4c8=\\n\"\n    \"-----END CERTIFICATE-----\\n\";\n#define key4 key3\nstatic const char *const hash4 = \"E1401D4497C944783E3D62CDBD2A1F69F5E5071E\";\n#define cname4 cname3 /* same CN as that of cert3 */\n\n#endif                /* CERT_DATA_H */\n"
  },
  {
    "path": "tests/unit_tests/openvpn/input/appears_empty.txt",
    "content": "\t\n\t\n(contains \\t\\n\\t\\n)\n"
  },
  {
    "path": "tests/unit_tests/openvpn/input/empty.txt",
    "content": ""
  },
  {
    "path": "tests/unit_tests/openvpn/input/leak_suppr.txt",
    "content": "leak:_assertions$\n"
  },
  {
    "path": "tests/unit_tests/openvpn/input/user_only.txt",
    "content": "fuser\n"
  },
  {
    "path": "tests/unit_tests/openvpn/input/user_pass.txt",
    "content": "fuser\nfpassword\n"
  },
  {
    "path": "tests/unit_tests/openvpn/mock_get_random.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2017-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <stdarg.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <setjmp.h>\n#include <stdint.h>\n#include <cmocka.h>\n\nunsigned long\nget_random(void)\n{\n    /* rand() is not very random, but it's C99 and this is just for testing */\n    return rand();\n}\n\nvoid\nprng_bytes(uint8_t *output, int len)\n{\n    for (int i = 0; i < len; i++)\n    {\n        output[i] = (uint8_t)rand();\n    }\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/mock_management.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/* Minimal set of mocked management function/globals to get unit tests to\n * compile */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include \"manage.h\"\n\n#ifdef ENABLE_MANAGEMENT\n\nstruct management *management; /* GLOBAL */\n\nvoid\nmanagement_auth_failure(struct management *man, const char *type, const char *reason)\n{\n    ASSERT(false);\n}\n\nchar *\nmanagement_query_pk_sig(struct management *man, const char *b64_data, const char *algorithm)\n{\n    return NULL;\n}\n\nvoid\nmanagement_set_state(struct management *man, const int state, const char *detail,\n                     const in_addr_t *tun_local_ip, const struct in6_addr *tun_local_ip6,\n                     const struct openvpn_sockaddr *local_addr,\n                     const struct openvpn_sockaddr *remote_addr)\n{\n}\n\n#endif\n\nvoid\nmanagement_sleep(const int n)\n{\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/mock_msg.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <stdarg.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <setjmp.h>\n#include <stdint.h>\n#ifndef NO_CMOCKA\n#include <cmocka.h>\n#endif\n\n#include \"errlevel.h\"\n#include \"error.h\"\n#include \"mock_msg.h\"\n\nmsglvl_t x_debug_level = 0; /* Default to (almost) no debugging output */\nmsglvl_t print_x_debug_level = 0;\n\n\nchar mock_msg_buf[MOCK_MSG_BUF];\n\n\nvoid\nmock_set_debug_level(msglvl_t level)\n{\n    x_debug_level = level;\n}\n\nmsglvl_t\nmock_get_debug_level(void)\n{\n    return x_debug_level;\n}\n\nvoid\nmock_set_print_debug_level(msglvl_t level)\n{\n    print_x_debug_level = level;\n}\n\nmsglvl_t\nget_debug_level(void)\n{\n    return x_debug_level;\n}\n\nvoid\nx_msg_va(const msglvl_t flags, const char *format, va_list arglist)\n{\n    if (flags & M_FATAL)\n    {\n        printf(\"FATAL ERROR:\");\n    }\n    CLEAR(mock_msg_buf);\n    vsnprintf(mock_msg_buf, sizeof(mock_msg_buf), format, arglist);\n\n    if ((flags & M_DEBUG_LEVEL) <= print_x_debug_level)\n    {\n        printf(\"%s\", mock_msg_buf);\n        printf(\"\\n\");\n    }\n#ifndef NO_CMOCKA\n    if (flags & M_FATAL)\n    {\n        mock_assert(false, \"FATAL ERROR\", __FILE__, __LINE__);\n    }\n#endif\n}\n\nvoid\nx_msg(const msglvl_t flags, const char *format, ...)\n{\n    va_list arglist;\n    va_start(arglist, format);\n    x_msg_va(flags, format, arglist);\n    va_end(arglist);\n}\n\n/* Allow to use mock_msg.c outside of UT */\n#ifndef NO_CMOCKA\nvoid\nassert_failed(const char *filename, int line, const char *condition)\n{\n    mock_assert(false, condition ? condition : \"\", filename, line);\n    /* Keep compiler happy.  Should not happen, mock_assert() does not return */\n    exit(1);\n}\n#else /* ifndef NO_CMOCKA */\nvoid\nassert_failed(const char *filename, int line, const char *condition)\n{\n    msg(M_FATAL, \"Assertion failed at %s:%d (%s)\", filename, line, condition ? condition : \"\");\n    _exit(1);\n}\n#endif\n\n\n/*\n * Fail memory allocation.  Don't use msg() because it tries\n * to allocate memory as part of its operation.\n */\nvoid\nout_of_memory(void)\n{\n    fprintf(stderr, \"Out of Memory\\n\");\n    exit(1);\n}\n\nbool\ndont_mute(msglvl_t flags)\n{\n    return true;\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/mock_msg.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifndef MOCK_MSG_H\n#define MOCK_MSG_H\n\n#include \"error.h\"\n\n/**\n * Mock debug level defaults to 0, which gives clean(-ish) test reports.  Call\n * this function from your test driver to increase debug output when you\n * need debug output.\n */\nvoid mock_set_debug_level(msglvl_t level);\n\n#define MOCK_MSG_BUF 2048\n\nextern bool fatal_error_triggered;\nextern char mock_msg_buf[MOCK_MSG_BUF];\n\nmsglvl_t mock_get_debug_level(void);\n\nvoid mock_set_print_debug_level(msglvl_t level);\n\n#endif /* MOCK_MSG */\n"
  },
  {
    "path": "tests/unit_tests/openvpn/mock_ssl_dependencies.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n/* Minimal set of mocked function/globals to get unit tests to\n * compile that use the ssl_* files */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <setjmp.h>\n#include <cmocka.h>\n\n\n#include \"ssl.h\"\n#include \"ssl_verify.h\"\n\nint\nparse_line(const char *line, char **p, const int n, const char *file, const int line_num,\n           msglvl_t msglevel, struct gc_arena *gc)\n{\n    /* Dummy function to get the linker happy, should never be called */\n    assert_true(false);\n    return 0;\n}\n\n\nint\npem_password_callback(char *buf, int size, int rwflag, void *u)\n{\n    return 0;\n}\n\nvoid\ncert_hash_remember(struct tls_session *session, const int cert_depth,\n                   const struct buffer *cert_hash)\n{\n    assert_false(true);\n}\n\nresult_t\nverify_cert(struct tls_session *session, openvpn_x509_cert_t *cert, int cert_depth)\n{\n    return FAILURE;\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/mock_win32_execve.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single TCP/UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2023-2026 OpenVPN Inc <sales@openvpn.net>\n *  Copyright (C) 2023-2026 Arne Schwabe <arne@rfc2549.org>\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#include \"config.h\"\n#include \"syshead.h\"\n\n#include \"win32.h\"\n\n#ifdef _WIN32\nint\nopenvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags)\n{\n    ASSERT(0);\n}\n#endif\n"
  },
  {
    "path": "tests/unit_tests/openvpn/pkey_test_utils.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2023-2026 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by the\n *  Free Software Foundation, either version 2 of the License,\n *  or (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n\n#include \"syshead.h\"\n#include \"xkey_common.h\"\n#include <setjmp.h>\n#include <cmocka.h>\n\n#ifdef HAVE_XKEY_PROVIDER\n\n#include <openssl/core_names.h>\n#include <openssl/evp.h>\n\nextern OSSL_LIB_CTX *tls_libctx;\n\n/* A message for signing */\nstatic const char *test_msg = \"Lorem ipsum dolor sit amet, consectetur \"\n                              \"adipisici elit, sed eiusmod tempor incidunt \"\n                              \"ut labore et dolore magna aliqua.\";\n\n/**\n * Sign \"test_msg\" using a private key. The key may be a \"provided\" key\n * in which case its signed by the provider's backend -- cryptoapi in our\n * case. Then verify the signature using OpenSSL.\n * Returns 1 on success, 0 on error.\n */\nint\ndigest_sign_verify(EVP_PKEY *privkey, EVP_PKEY *pubkey)\n{\n    uint8_t *sig = NULL;\n    size_t siglen = 0;\n    int ret = 0;\n\n    OSSL_PARAM params[2] = { OSSL_PARAM_END };\n    const char *mdname = \"SHA256\";\n\n    if (EVP_PKEY_get_id(privkey) == EVP_PKEY_RSA)\n    {\n        const char *padmode = \"pss\"; /* RSA_PSS: for all other params, use defaults */\n        params[0] =\n            OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_PAD_MODE, (char *)padmode, 0);\n        params[1] = OSSL_PARAM_construct_end();\n    }\n    else if (EVP_PKEY_get_id(privkey) == EVP_PKEY_EC)\n    {\n        params[0] = OSSL_PARAM_construct_end();\n    }\n    else\n    {\n        print_error(\"Unknown key type in digest_sign_verify()\");\n        return ret;\n    }\n\n    EVP_PKEY_CTX *pctx = NULL;\n    EVP_MD_CTX *mctx = EVP_MD_CTX_new();\n\n    if (!mctx || EVP_DigestSignInit_ex(mctx, &pctx, mdname, tls_libctx, NULL, privkey, params) <= 0)\n    {\n        /* cmocka assert output for these kinds of failures is hardly explanatory,\n         * print a message and assert in caller. */\n        print_error(\"Failed to initialize EVP_DigestSignInit_ex()\\n\");\n        goto done;\n    }\n\n    /* sign with sig = NULL to get required siglen */\n    if (EVP_DigestSign(mctx, sig, &siglen, (uint8_t *)test_msg, strlen(test_msg)) != 1)\n    {\n        print_error(\"EVP_DigestSign: failed to get required signature size\");\n        goto done;\n    }\n    assert_true(siglen > 0);\n\n    if ((sig = test_calloc(1, siglen)) == NULL)\n    {\n        print_error(\"Out of memory\");\n        goto done;\n    }\n    if (EVP_DigestSign(mctx, sig, &siglen, (uint8_t *)test_msg, strlen(test_msg)) != 1)\n    {\n        print_error(\"EVP_DigestSign: signing failed\");\n        goto done;\n    }\n\n    /*\n     * Now validate the signature using OpenSSL. Just use the public key\n     * which is a native OpenSSL key.\n     */\n    EVP_MD_CTX_free(mctx); /* this also frees pctx */\n    mctx = EVP_MD_CTX_new();\n    pctx = NULL;\n    if (!mctx\n        || EVP_DigestVerifyInit_ex(mctx, &pctx, mdname, tls_libctx, NULL, pubkey, params) <= 0)\n    {\n        print_error(\"Failed to initialize EVP_DigestVerifyInit_ex()\");\n        goto done;\n    }\n    if (EVP_DigestVerify(mctx, sig, siglen, (uint8_t *)test_msg, strlen(test_msg)) != 1)\n    {\n        print_error(\"EVP_DigestVerify failed\");\n        goto done;\n    }\n    ret = 1;\n\ndone:\n    if (mctx)\n    {\n        EVP_MD_CTX_free(mctx); /* this also frees pctx */\n    }\n    test_free(sig);\n    return ret;\n}\n#endif /* HAVE_XKEY_PROVIDER */\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_argv.c",
    "content": "#include \"config.h\"\n#include \"syshead.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <setjmp.h>\n#include <cmocka.h>\n#include <assert.h>\n#include <stdbool.h>\n\n#include \"argv.h\"\n#include \"buffer.h\"\n#include \"test_common.h\"\n\n/* Defines for use in the tests and the mock parse_line() */\n#define PATH1      \"/s p a c e\"\n#define PATH2      \"/foo bar/baz\"\n#define PARAM1     \"param1\"\n#define PARAM2     \"param two\"\n#define SCRIPT_CMD \"\\\"\" PATH1 PATH2 \"\\\"\" PARAM1 \"\\\"\" PARAM2 \"\\\"\"\n\nint\nparse_line(const char *line, char **p, const int n, const char *file, const int line_num,\n           msglvl_t msglevel, struct gc_arena *gc)\n{\n    p[0] = PATH1 PATH2;\n    p[1] = PARAM1;\n    p[2] = PARAM2;\n    return 3;\n}\n\nstatic void\nargv_printf__multiple_spaces_in_format__parsed_as_one(void **state)\n{\n    struct argv a = argv_new();\n\n    argv_printf(&a, \"    %s     %s  %d   \", PATH1, PATH2, 42);\n    assert_int_equal(a.argc, 3);\n\n    argv_free(&a);\n}\n\nstatic void\nargv_printf_cat__multiple_spaces_in_format__parsed_as_one(void **state)\n{\n    struct argv a = argv_new();\n\n    argv_printf(&a, \"%s \", PATH1);\n    argv_printf_cat(&a, \" %s  %s\", PATH2, PARAM1);\n    assert_int_equal(a.argc, 3);\n\n    argv_free(&a);\n}\n\nstatic void\nargv_printf__embedded_format_directive__replaced_in_output(void **state)\n{\n    struct argv a = argv_new();\n\n    argv_printf(&a, \"<p1:%s>\", PATH1);\n    assert_int_equal(a.argc, 1);\n    assert_string_equal(a.argv[0], \"<p1:\" PATH1 \">\");\n\n    argv_free(&a);\n}\n\nstatic void\nargv_printf__group_sep_in_arg__fail_no_ouput(void **state)\n{\n    struct argv a = argv_new();\n\n    assert_false(argv_printf(&a, \"tool --do %s\", \"this\\035--harmful\"));\n    assert_int_equal(a.argc, 0);\n\n    argv_free(&a);\n}\n\nstatic void\nargv_printf__combined_path_with_spaces__argc_correct(void **state)\n{\n    struct argv a = argv_new();\n\n    argv_printf(&a, \"%s%s\", PATH1, PATH2);\n    assert_int_equal(a.argc, 1);\n\n    argv_printf(&a, \"%s%s %d\", PATH1, PATH2, 42);\n    assert_int_equal(a.argc, 2);\n\n    argv_printf(&a, \"foo %s%s %s x y\", PATH2, PATH1, \"foo\");\n    assert_int_equal(a.argc, 5);\n\n    argv_free(&a);\n}\n\nstatic void\nargv_printf__empty_parameter__argc_correct(void **state)\n{\n    struct argv a = argv_new();\n\n    argv_printf(&a, \"%s\", \"\");\n    assert_int_equal(a.argc, 1);\n\n    argv_printf(&a, \"%s %s\", PATH1, \"\");\n    assert_int_equal(a.argc, 2);\n\n    argv_printf(&a, \"%s %s %s\", PATH1, \"\", PARAM1);\n    assert_int_equal(a.argc, 3);\n\n    argv_printf(&a, \"%s %s %s %s\", PATH1, \"\", \"\", PARAM1);\n    assert_int_equal(a.argc, 4);\n\n    argv_printf(&a, \"%s %s\", \"\", PARAM1);\n    assert_int_equal(a.argc, 2);\n\n    argv_free(&a);\n}\n\nstatic void\nargv_printf__long_args__data_correct(void **state)\n{\n    struct argv a = argv_new();\n    const char *args[] = {\n        \"good_tools_have_good_names_even_though_it_might_impair_typing\",\n        \"--long-opt=looooooooooooooooooooooooooooooooooooooooooooooooong\",\n        \"--long-cat=loooooooooooooooooooooooooooooooooooooooooooooooooooonger\",\n        \"file_with_very_descriptive_filename_that_leaves_no_questions_open.jpg.exe\"\n    };\n\n    argv_printf(&a, \"%s %s %s %s\", args[0], args[1], args[2], args[3]);\n    assert_int_equal(a.argc, 4);\n    for (size_t i = 0; i < a.argc; i++)\n    {\n        assert_string_equal(a.argv[i], args[i]);\n    }\n\n    argv_free(&a);\n}\n\nstatic void\nargv_parse_cmd__command_string__argc_correct(void **state)\n{\n    struct argv a = argv_new();\n\n    argv_parse_cmd(&a, SCRIPT_CMD);\n    assert_int_equal(a.argc, 3);\n\n    argv_free(&a);\n}\n\nstatic void\nargv_parse_cmd__command_and_extra_options__argc_correct(void **state)\n{\n    struct argv a = argv_new();\n\n    argv_parse_cmd(&a, SCRIPT_CMD);\n    argv_printf_cat(&a, \"bar baz %d %s\", 42, PATH1);\n    assert_int_equal(a.argc, 7);\n\n    argv_free(&a);\n}\n\nstatic void\nargv_printf_cat__used_twice__argc_correct(void **state)\n{\n    struct argv a = argv_new();\n\n    argv_printf(&a, \"%s %s %s\", PATH1, PATH2, PARAM1);\n    argv_printf_cat(&a, \"%s\", PARAM2);\n    argv_printf_cat(&a, \"foo\");\n    assert_int_equal(a.argc, 5);\n\n    argv_free(&a);\n}\n\nstatic void\nargv_str__empty_argv__empty_output(void **state)\n{\n    struct argv a = argv_new();\n    struct gc_arena gc = gc_new();\n    const char *output;\n\n    output = argv_str(&a, &gc, PA_BRACKET);\n    assert_string_equal(output, \"\");\n\n    argv_free(&a);\n    gc_free(&gc);\n}\n\nstatic void\nargv_str__multiple_argv__correct_output(void **state)\n{\n    struct argv a = argv_new();\n    struct gc_arena gc = gc_new();\n    const char *output;\n\n    argv_printf(&a, \"%s%s\", PATH1, PATH2);\n    argv_printf_cat(&a, \"%s\", PARAM1);\n    argv_printf_cat(&a, \"%s\", PARAM2);\n    argv_printf_cat(&a, \"%d\", -1);\n    argv_printf_cat(&a, \"%u\", -1);\n    argv_printf_cat(&a, \"%lu\", 1L);\n    output = argv_str(&a, &gc, PA_BRACKET);\n    assert_string_equal(output, \"[\" PATH1 PATH2 \"] [\" PARAM1 \"] [\" PARAM2 \"]\"\n                                \" [-1] [4294967295] [1]\");\n\n    argv_free(&a);\n    gc_free(&gc);\n}\n\nstatic void\nargv_insert_head__empty_argv__head_only(void **state)\n{\n    struct argv a = argv_new();\n    struct argv b;\n\n    b = argv_insert_head(&a, PATH1);\n    assert_int_equal(b.argc, 1);\n    assert_string_equal(b.argv[0], PATH1);\n    argv_free(&b);\n\n    argv_free(&a);\n}\n\nstatic void\nargv_insert_head__non_empty_argv__head_added(void **state)\n{\n    struct argv a = argv_new();\n    struct argv b;\n\n    argv_printf(&a, \"%s\", PATH2);\n    b = argv_insert_head(&a, PATH1);\n    assert_int_equal(b.argc, a.argc + 1);\n    for (size_t i = 0; i < b.argc; i++)\n    {\n        if (i == 0)\n        {\n            assert_string_equal(b.argv[i], PATH1);\n        }\n        else\n        {\n            assert_string_equal(b.argv[i], a.argv[i - 1]);\n        }\n    }\n    argv_free(&b);\n\n    argv_free(&a);\n}\n\nint\nmain(void)\n{\n    openvpn_unit_test_setup();\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(argv_printf__multiple_spaces_in_format__parsed_as_one),\n        cmocka_unit_test(argv_printf_cat__multiple_spaces_in_format__parsed_as_one),\n        cmocka_unit_test(argv_printf__embedded_format_directive__replaced_in_output),\n        cmocka_unit_test(argv_printf__group_sep_in_arg__fail_no_ouput),\n        cmocka_unit_test(argv_printf__combined_path_with_spaces__argc_correct),\n        cmocka_unit_test(argv_printf__empty_parameter__argc_correct),\n        cmocka_unit_test(argv_printf__long_args__data_correct),\n        cmocka_unit_test(argv_parse_cmd__command_string__argc_correct),\n        cmocka_unit_test(argv_parse_cmd__command_and_extra_options__argc_correct),\n        cmocka_unit_test(argv_printf_cat__used_twice__argc_correct),\n        cmocka_unit_test(argv_str__empty_argv__empty_output),\n        cmocka_unit_test(argv_str__multiple_argv__correct_output),\n        cmocka_unit_test(argv_insert_head__non_empty_argv__head_added),\n        cmocka_unit_test(argv_insert_head__empty_argv__head_only),\n    };\n\n    return cmocka_run_group_tests_name(\"argv\", tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_auth_token.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <setjmp.h>\n#include <cmocka.h>\n\n#include \"auth_token.c\"\n#include \"test_common.h\"\n\nstruct test_context\n{\n    struct tls_multi multi;\n    struct key_type kt;\n    struct user_pass up;\n    struct tls_session *session;\n};\n\n/* Dummy functions that do nothing to mock the functionality */\nvoid\nsend_push_reply_auth_token(struct tls_multi *multi)\n{\n}\n\nvoid\nauth_set_client_reason(struct tls_multi *multi, const char *reason)\n{\n}\n\nstatic const char *now0key0 =\n    \"SESS_ID_AT_0123456789abcdefAAAAAAAAAAAAAAAAAAAAAE5JsQJOVfo8jnI3RL3tBaR5NkE4yPfcylFUHmHSc5Bu\";\n\nstatic const char *zeroinline = \"-----BEGIN OpenVPN auth-token server key-----\\n\"\n                                \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\n\"\n                                \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\n\"\n                                \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\\n\"\n                                \"-----END OpenVPN auth-token server key-----\";\n\nstatic const char *allx01inline =\n    \"-----BEGIN OpenVPN auth-token server key-----\\n\"\n    \"AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB\\n\"\n    \"AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB\\n\"\n    \"AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=\\n\"\n    \"-----END OpenVPN auth-token server key-----\";\n\nstatic const char *random_key =\n    \"-----BEGIN OpenVPN auth-token server key-----\\n\"\n    \"+mmmf7IQ5cymtMVjKYTWk8IOcYanRlpQmV9Tb3EjkHYxueBVDg3yqRgzeBlVGzNLD//rAPiOVhau\\n\"\n    \"3NDBjNOQB8951bfs7Cc2mYfay92Bh2gRJ5XEM/DMfzCWN+7uU6NWoTTHr4FuojnIQtjtqVAj/JS9\\n\"\n    \"w+dTSp/vYHl+c7uHd19uVRu/qLqV85+rm4tUGIjO7FfYuwyPqwmhuIsi3hs9QkSimh888FmBpoKY\\n\"\n    \"/tbKVTJZmSERKti9KEwtV2eVAR0znN5KW7lCB3mHVAhN7bUpcoDjfCzYIFARxwswTFu9gFkwqUMY\\n\"\n    \"I1KUOgIsVNs4llACioeXplYekWETR+YkJwDc/A==\\n\"\n    \"-----END OpenVPN auth-token server key-----\";\n\nstatic const char *random_token =\n    \"SESS_ID_AT_ThhRItzOKNKrh3dfAAAAAFwzHpwAAAAAXDMenDdrq0RoH3dkA1f7O3wO+7kZcx2DusVZrRmFlWQM9HOb\";\n\n\nstatic int\nsetup(void **state)\n{\n    struct test_context *ctx = calloc(1, sizeof(*ctx));\n    *state = ctx;\n\n    struct key_parameters key = { 0 };\n    key.hmac_size = MAX_HMAC_KEY_LENGTH; /* 64 byte of 0 */\n\n    ctx->kt = auth_token_kt();\n    if (!ctx->kt.digest)\n    {\n        return 0;\n    }\n    ctx->multi.opt.auth_token_generate = true;\n    ctx->multi.opt.auth_token_lifetime = 3000;\n    ctx->session = &ctx->multi.session[TM_ACTIVE];\n\n    ctx->session->opt = calloc(1, sizeof(struct tls_options));\n    ctx->session->opt->renegotiate_seconds = 240;\n    ctx->session->opt->auth_token_renewal = 120;\n    ctx->session->opt->auth_token_lifetime = 3000;\n\n    strcpy(ctx->up.username, \"test user name\");\n    strcpy(ctx->up.password, \"ignored\");\n\n    init_key_ctx(&ctx->multi.opt.auth_token_key, &key, &ctx->kt, false, \"TEST\");\n\n    now = 0;\n    return 0;\n}\n\nstatic int\nteardown(void **state)\n{\n    struct test_context *ctx = (struct test_context *)*state;\n\n    free_key_ctx(&ctx->multi.opt.auth_token_key);\n    wipe_auth_token(&ctx->multi);\n\n    free(ctx->session->opt);\n    free(ctx);\n\n    return 0;\n}\n\nstatic void\nauth_token_basic_test(void **state)\n{\n    struct test_context *ctx = (struct test_context *)*state;\n\n    generate_auth_token(&ctx->up, &ctx->multi);\n    strcpy(ctx->up.password, ctx->multi.auth_token);\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), AUTH_TOKEN_HMAC_OK);\n}\n\nstatic void\nauth_token_fail_invalid_key(void **state)\n{\n    struct test_context *ctx = (struct test_context *)*state;\n\n    generate_auth_token(&ctx->up, &ctx->multi);\n    strcpy(ctx->up.password, ctx->multi.auth_token);\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), AUTH_TOKEN_HMAC_OK);\n\n    /* Change auth-token key */\n    struct key_parameters key;\n    memset(key.hmac, '1', sizeof(key.hmac));\n    key.hmac_size = MAX_HMAC_KEY_LENGTH;\n\n    free_key_ctx(&ctx->multi.opt.auth_token_key);\n    init_key_ctx(&ctx->multi.opt.auth_token_key, &key, &ctx->kt, false, \"TEST\");\n\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), 0);\n\n    /* Load original test key again */\n    memset(&key.hmac, 0, sizeof(key.hmac));\n    free_key_ctx(&ctx->multi.opt.auth_token_key);\n    init_key_ctx(&ctx->multi.opt.auth_token_key, &key, &ctx->kt, false, \"TEST\");\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), AUTH_TOKEN_HMAC_OK);\n}\n\n/* Note: only on 32bit Windows builds */\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#endif\n\nstatic void\nauth_token_test_timeout(void **state)\n{\n    struct test_context *ctx = (struct test_context *)*state;\n\n    now = 100000;\n    generate_auth_token(&ctx->up, &ctx->multi);\n\n    strcpy(ctx->up.password, ctx->multi.auth_token);\n    free(ctx->multi.auth_token_initial);\n    ctx->multi.auth_token_initial = NULL;\n\n    /* No time has passed */\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), AUTH_TOKEN_HMAC_OK);\n\n    /* Token before validity, should be rejected */\n    now = 100000 - 100;\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),\n                     AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_EXPIRED);\n\n    /* Token no valid for renegotiate_seconds but still for renewal_time */\n    now = 100000 + 2 * ctx->session->opt->renegotiate_seconds - 20;\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),\n                     AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_EXPIRED);\n\n\n    now = 100000 + 2 * ctx->session->opt->auth_token_renewal - 20;\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), AUTH_TOKEN_HMAC_OK);\n\n    /* Token past validity, should be rejected */\n    now = 100000 + 2 * ctx->session->opt->renegotiate_seconds + 20;\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),\n                     AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_EXPIRED);\n\n    /* But not when we reached our timeout */\n    now = 100000 + ctx->session->opt->auth_token_lifetime + 1;\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),\n                     AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_EXPIRED);\n\n    free(ctx->multi.auth_token_initial);\n    ctx->multi.auth_token_initial = NULL;\n\n    /* regenerate the token util it hits the expiry */\n    now = 100000;\n    while (now < 100000 + ctx->session->opt->auth_token_lifetime + 1)\n    {\n        assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),\n                         AUTH_TOKEN_HMAC_OK);\n        generate_auth_token(&ctx->up, &ctx->multi);\n        strcpy(ctx->up.password, ctx->multi.auth_token);\n        now += ctx->session->opt->auth_token_renewal;\n    }\n\n\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),\n                     AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_EXPIRED);\n    ctx->multi.opt.auth_token_lifetime = 0;\n\n    /* Non expiring token should be fine */\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), AUTH_TOKEN_HMAC_OK);\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\nstatic void\nzerohmac(char *token)\n{\n    char *hmacstart =\n        token + AUTH_TOKEN_SESSION_ID_LEN + strlen(SESSION_ID_PREFIX) + 2 * sizeof(uint64_t);\n    memset(hmacstart, 0x8d, strlen(hmacstart));\n}\n\nstatic void\nauth_token_test_known_keys(void **state)\n{\n    struct test_context *ctx = (struct test_context *)*state;\n\n    now = 0;\n    /* Preload the session id so the same session id is used here */\n    ctx->multi.auth_token_initial = strdup(now0key0);\n    assert_non_null(ctx->multi.auth_token_initial);\n\n    /* Zero the hmac part to ensure we have a newly generated token */\n    zerohmac(ctx->multi.auth_token_initial);\n\n    generate_auth_token(&ctx->up, &ctx->multi);\n\n    assert_string_equal(now0key0, ctx->multi.auth_token);\n\n    strcpy(ctx->up.password, ctx->multi.auth_token);\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), AUTH_TOKEN_HMAC_OK);\n}\n\nstatic const char *lastsesion_statevalue;\nvoid\nsetenv_str(struct env_set *es, const char *name, const char *value)\n{\n    if (streq(name, \"session_state\"))\n    {\n        lastsesion_statevalue = value;\n    }\n}\n\nvoid\nauth_token_test_session_mismatch(void **state)\n{\n    struct test_context *ctx = (struct test_context *)*state;\n\n    /* Generate first auth token and check it is correct */\n    generate_auth_token(&ctx->up, &ctx->multi);\n    strcpy(ctx->up.password, ctx->multi.auth_token);\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), AUTH_TOKEN_HMAC_OK);\n\n    char *token_sessiona = strdup(ctx->multi.auth_token);\n\n    /* Generate second token */\n    wipe_auth_token(&ctx->multi);\n\n    generate_auth_token(&ctx->up, &ctx->multi);\n    strcpy(ctx->up.password, ctx->multi.auth_token);\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), AUTH_TOKEN_HMAC_OK);\n\n    assert_memory_not_equal(ctx->multi.auth_token_initial + strlen(SESSION_ID_PREFIX),\n                            token_sessiona + strlen(SESSION_ID_PREFIX),\n                            AUTH_TOKEN_SESSION_ID_BASE64_LEN);\n\n    /* The first token is valid but should trigger the invalid response since\n     * the session id is not the same */\n    strcpy(ctx->up.password, token_sessiona);\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), 0);\n    free(token_sessiona);\n}\n\nstatic void\nauth_token_test_empty_user(void **state)\n{\n    struct test_context *ctx = (struct test_context *)*state;\n\n    CLEAR(ctx->up.username);\n    now = 0;\n\n    generate_auth_token(&ctx->up, &ctx->multi);\n    strcpy(ctx->up.password, ctx->multi.auth_token);\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), AUTH_TOKEN_HMAC_OK);\n\n    now = 100000;\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),\n                     AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_EXPIRED);\n    strcpy(ctx->up.username, \"test user name\");\n\n    now = 0;\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),\n                     AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_VALID_EMPTYUSER);\n\n    strcpy(ctx->up.username, \"test user name\");\n    now = 100000;\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session),\n                     AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_EXPIRED | AUTH_TOKEN_VALID_EMPTYUSER);\n\n    zerohmac(ctx->up.password);\n    assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), 0);\n}\n\nstatic void\nauth_token_test_env(void **state)\n{\n    struct test_context *ctx = (struct test_context *)*state;\n\n    struct key_state *ks = &ctx->multi.session[TM_ACTIVE].key[KS_PRIMARY];\n\n    ks->auth_token_state_flags = 0;\n    ctx->multi.auth_token = NULL;\n    add_session_token_env(ctx->session, &ctx->multi, &ctx->up);\n    assert_string_equal(lastsesion_statevalue, \"Initial\");\n\n    ks->auth_token_state_flags = 0;\n    strcpy(ctx->up.password, now0key0);\n    add_session_token_env(ctx->session, &ctx->multi, &ctx->up);\n    assert_string_equal(lastsesion_statevalue, \"Invalid\");\n\n    ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK;\n    add_session_token_env(ctx->session, &ctx->multi, &ctx->up);\n    assert_string_equal(lastsesion_statevalue, \"Authenticated\");\n\n    ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_EXPIRED;\n    add_session_token_env(ctx->session, &ctx->multi, &ctx->up);\n    assert_string_equal(lastsesion_statevalue, \"Expired\");\n\n    ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_VALID_EMPTYUSER;\n    add_session_token_env(ctx->session, &ctx->multi, &ctx->up);\n    assert_string_equal(lastsesion_statevalue, \"AuthenticatedEmptyUser\");\n\n    ks->auth_token_state_flags =\n        AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_EXPIRED | AUTH_TOKEN_VALID_EMPTYUSER;\n    add_session_token_env(ctx->session, &ctx->multi, &ctx->up);\n    assert_string_equal(lastsesion_statevalue, \"ExpiredEmptyUser\");\n}\n\nstatic void\nauth_token_test_random_keys(void **state)\n{\n    struct test_context *ctx = (struct test_context *)*state;\n\n    now = 0x5c331e9c;\n    /* Preload the session id so the same session id is used here */\n    ctx->multi.auth_token_initial = strdup(random_token);\n    assert_non_null(ctx->multi.auth_token_initial);\n\n    free_key_ctx(&ctx->multi.opt.auth_token_key);\n    auth_token_init_secret(&ctx->multi.opt.auth_token_key, random_key, true);\n\n    /* Zero the hmac part to ensure we have a newly generated token */\n    zerohmac(ctx->multi.auth_token_initial);\n\n    generate_auth_token(&ctx->up, &ctx->multi);\n\n    assert_string_equal(random_token, ctx->multi.auth_token);\n\n    strcpy(ctx->up.password, ctx->multi.auth_token);\n    assert_true(verify_auth_token(&ctx->up, &ctx->multi, ctx->session));\n}\n\n\nstatic void\nauth_token_test_key_load(void **state)\n{\n    struct test_context *ctx = (struct test_context *)*state;\n\n    free_key_ctx(&ctx->multi.opt.auth_token_key);\n    auth_token_init_secret(&ctx->multi.opt.auth_token_key, zeroinline, true);\n    strcpy(ctx->up.password, now0key0);\n    assert_true(verify_auth_token(&ctx->up, &ctx->multi, ctx->session));\n\n    free_key_ctx(&ctx->multi.opt.auth_token_key);\n    auth_token_init_secret(&ctx->multi.opt.auth_token_key, allx01inline, true);\n    assert_false(verify_auth_token(&ctx->up, &ctx->multi, ctx->session));\n}\n\nint\nmain(void)\n{\n    openvpn_unit_test_setup();\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test_setup_teardown(auth_token_basic_test, setup, teardown),\n        cmocka_unit_test_setup_teardown(auth_token_fail_invalid_key, setup, teardown),\n        cmocka_unit_test_setup_teardown(auth_token_test_known_keys, setup, teardown),\n        cmocka_unit_test_setup_teardown(auth_token_test_empty_user, setup, teardown),\n        cmocka_unit_test_setup_teardown(auth_token_test_env, setup, teardown),\n        cmocka_unit_test_setup_teardown(auth_token_test_random_keys, setup, teardown),\n        cmocka_unit_test_setup_teardown(auth_token_test_key_load, setup, teardown),\n        cmocka_unit_test_setup_teardown(auth_token_test_timeout, setup, teardown),\n        cmocka_unit_test_setup_teardown(auth_token_test_session_mismatch, setup, teardown)\n    };\n\n    return cmocka_run_group_tests_name(\"auth-token tests\", tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_buffer.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <setjmp.h>\n#include <cmocka.h>\n\n#include \"buffer.h\"\n#include \"buffer.c\"\n#include \"test_common.h\"\n\nstatic void\ntest_buffer_strprefix(void **state)\n{\n    assert_true(strprefix(\"123456\", \"123456\"));\n    assert_true(strprefix(\"123456\", \"123\"));\n    assert_true(strprefix(\"123456\", \"\"));\n    assert_false(strprefix(\"123456\", \"456\"));\n    assert_false(strprefix(\"12\", \"123\"));\n}\n\n#define testsep   \",\"\n#define testnosep \"\"\n#define teststr1  \"one\"\n#define teststr2  \"two\"\n#define teststr3  \"three\"\n\n#define assert_buf_equals_str(buf, str)        \\\n    assert_int_equal(BLENZ(buf), strlen(str)); \\\n    assert_memory_equal(BPTR(buf), str, BLENZ(buf));\n\nstatic void\ntest_buffer_printf_catrunc(void **state)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer buf = alloc_buf_gc(16, &gc);\n\n    buf_printf(&buf, \"%d\", 123);\n    buf_printf(&buf, \"%s\", \"some text, too long to fit\");\n    assert_buf_equals_str(&buf, \"123some text, t\");\n\n    buf_catrunc(&buf, \"...\");\n    assert_buf_equals_str(&buf, \"123some text...\");\n\n    buf_catrunc(&buf, \"some other text, much too long to fit\");\n    assert_buf_equals_str(&buf, \"123some text...\");\n\n    buf_catrunc(&buf, \"something else\"); /* exactly right */\n    assert_buf_equals_str(&buf, \"1something else\");\n\n    buf_catrunc(&buf, \"something other\"); /* 1 byte too long */\n    assert_buf_equals_str(&buf, \"1something else\");\n\n    gc_free(&gc);\n}\n\nstatic void\ntest_buffer_format_hex_ex(void **state)\n{\n    const int input_size = 10;\n    const uint8_t input[] = { 0x01, 0x00, 0xff, 0x10, 0xff, 0x00, 0xf0, 0x0f, 0x09, 0x0a };\n    char *output;\n    struct gc_arena gc = gc_new();\n\n    int maxoutput = 0;\n    unsigned int blocksize = 5;\n    char *separator = \" \";\n    output = format_hex_ex(input, input_size, maxoutput, blocksize, separator, &gc);\n    assert_string_equal(output, \"0100ff10ff 00f00f090a\");\n\n    maxoutput = 14;\n    output = format_hex_ex(input, input_size, maxoutput, blocksize, separator, &gc);\n    assert_string_equal(output, \"0100[more...]\");\n\n    maxoutput = 11;\n    output = format_hex_ex(input, input_size, maxoutput, blocksize, separator, &gc);\n    assert_string_equal(output, \"0[more...]\");\n\n    maxoutput = 10;\n    output = format_hex_ex(input, input_size, maxoutput, blocksize, separator, &gc);\n    assert_string_equal(output, \"0100ff10f\");\n\n    maxoutput = 9;\n    output = format_hex_ex(input, input_size, maxoutput, blocksize, separator, &gc);\n    assert_string_equal(output, \"0100ff10\");\n\n    gc_free(&gc);\n}\n\nstruct test_buffer_list_aggregate_ctx\n{\n    struct buffer_list *empty;\n    struct buffer_list *one_two_three;\n    struct buffer_list *zero_length_strings;\n    struct buffer_list *empty_buffers;\n};\n\nstatic int\ntest_buffer_list_setup(void **state)\n{\n    struct test_buffer_list_aggregate_ctx *ctx = calloc(1, sizeof(*ctx));\n    ctx->empty = buffer_list_new();\n\n    ctx->one_two_three = buffer_list_new();\n    buffer_list_push(ctx->one_two_three, teststr1);\n    buffer_list_push(ctx->one_two_three, teststr2);\n    buffer_list_push(ctx->one_two_three, teststr3);\n\n    ctx->zero_length_strings = buffer_list_new();\n    buffer_list_push(ctx->zero_length_strings, \"\");\n    buffer_list_push(ctx->zero_length_strings, \"\");\n\n    ctx->empty_buffers = buffer_list_new();\n    uint8_t data = 0;\n    buffer_list_push_data(ctx->empty_buffers, &data, 0);\n    buffer_list_push_data(ctx->empty_buffers, &data, 0);\n\n    *state = ctx;\n    return 0;\n}\n\nstatic int\ntest_buffer_list_teardown(void **state)\n{\n    struct test_buffer_list_aggregate_ctx *ctx = *state;\n\n    buffer_list_free(ctx->empty);\n    buffer_list_free(ctx->one_two_three);\n    buffer_list_free(ctx->zero_length_strings);\n    buffer_list_free(ctx->empty_buffers);\n    free(ctx);\n    return 0;\n}\n\nstatic void\ntest_buffer_list_aggregate_separator_empty(void **state)\n{\n    struct test_buffer_list_aggregate_ctx *ctx = *state;\n\n    /* aggregating an empty buffer list results in an empty buffer list */\n    buffer_list_aggregate_separator(ctx->empty, 3, testsep);\n    assert_null(ctx->empty->head);\n}\n\nstatic void\ntest_buffer_list_aggregate_separator_noop(void **state)\n{\n    struct test_buffer_list_aggregate_ctx *ctx = *state;\n\n    /* With a max length of 2, no aggregation should take place */\n    buffer_list_aggregate_separator(ctx->one_two_three, 2, testsep);\n    assert_int_equal(ctx->one_two_three->size, 3);\n    struct buffer *buf = buffer_list_peek(ctx->one_two_three);\n    assert_buf_equals_str(buf, teststr1);\n}\n\nstatic void\ntest_buffer_list_aggregate_separator_two(void **state)\n{\n    struct test_buffer_list_aggregate_ctx *ctx = *state;\n    const char *expected = teststr1 testsep teststr2 testsep;\n\n    /* Aggregate the first two elements\n     * (add 1 to max_len to test if \"three\" is not sneaked in too)\n     */\n    buffer_list_aggregate_separator(ctx->one_two_three, strlen(expected) + 1, testsep);\n    assert_int_equal(ctx->one_two_three->size, 2);\n    struct buffer *buf = buffer_list_peek(ctx->one_two_three);\n    assert_buf_equals_str(buf, expected);\n}\n\nstatic void\ntest_buffer_list_aggregate_separator_all(void **state)\n{\n    struct test_buffer_list_aggregate_ctx *ctx = *state;\n\n    /* Aggregate all */\n    buffer_list_aggregate_separator(ctx->one_two_three, 1 << 16, testsep);\n    assert_int_equal(ctx->one_two_three->size, 1);\n    struct buffer *buf = buffer_list_peek(ctx->one_two_three);\n    assert_buf_equals_str(buf, teststr1 testsep teststr2 testsep teststr3 testsep);\n}\n\nstatic void\ntest_buffer_list_aggregate_separator_nosep(void **state)\n{\n    struct test_buffer_list_aggregate_ctx *ctx = *state;\n\n    /* Aggregate all */\n    buffer_list_aggregate_separator(ctx->one_two_three, 1 << 16, testnosep);\n    assert_int_equal(ctx->one_two_three->size, 1);\n    struct buffer *buf = buffer_list_peek(ctx->one_two_three);\n    assert_buf_equals_str(buf, teststr1 teststr2 teststr3);\n}\n\nstatic void\ntest_buffer_list_aggregate_separator_zerolen(void **state)\n{\n    struct test_buffer_list_aggregate_ctx *ctx = *state;\n    struct buffer_list *bl_zerolen = ctx->zero_length_strings;\n\n    /* Aggregate all */\n    buffer_list_aggregate_separator(bl_zerolen, 1 << 16, testnosep);\n    assert_int_equal(bl_zerolen->size, 1);\n    struct buffer *buf = buffer_list_peek(bl_zerolen);\n    assert_buf_equals_str(buf, \"\");\n}\n\nstatic void\ntest_buffer_list_aggregate_separator_emptybuffers(void **state)\n{\n    struct test_buffer_list_aggregate_ctx *ctx = *state;\n    struct buffer_list *bl_emptybuffers = ctx->empty_buffers;\n\n    /* Aggregate all */\n    buffer_list_aggregate_separator(bl_emptybuffers, 1 << 16, testnosep);\n    assert_int_equal(bl_emptybuffers->size, 1);\n    struct buffer *buf = buffer_list_peek(bl_emptybuffers);\n    assert_int_equal(BLEN(buf), 0);\n}\n\nstatic void\ntest_buffer_free_gc_one(void **state)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer buf = alloc_buf_gc(1024, &gc);\n\n    assert_ptr_equal(gc.list + 1, buf.data);\n    free_buf_gc(&buf, &gc);\n    assert_null(gc.list);\n\n    gc_free(&gc);\n}\n\nstatic void\ntest_buffer_free_gc_two(void **state)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer buf1 = alloc_buf_gc(1024, &gc);\n    struct buffer buf2 = alloc_buf_gc(1024, &gc);\n    struct buffer buf3 = alloc_buf_gc(1024, &gc);\n\n    struct gc_entry *e;\n\n    e = gc.list;\n\n    assert_ptr_equal(e + 1, buf3.data);\n    assert_ptr_equal(e->next + 1, buf2.data);\n    assert_ptr_equal(e->next->next + 1, buf1.data);\n\n    free_buf_gc(&buf2, &gc);\n\n    assert_non_null(gc.list);\n\n    while (e)\n    {\n        assert_ptr_not_equal(e + 1, buf2.data);\n        e = e->next;\n    }\n\n    gc_free(&gc);\n}\n\n\nstatic void\ntest_buffer_gc_realloc(void **state)\n{\n    struct gc_arena gc = gc_new();\n\n    void *p1 = gc_realloc(NULL, 512, &gc);\n    void *p2 = gc_realloc(NULL, 512, &gc);\n\n    assert_ptr_not_equal(p1, p2);\n\n    memset(p1, '1', 512);\n    memset(p2, '2', 512);\n\n    p1 = gc_realloc(p1, 512, &gc);\n\n    /* allocate 512kB to ensure the pointer needs to change */\n    void *p1new = gc_realloc(p1, 512ul * 1024, &gc);\n    assert_ptr_not_equal(p1, p1new);\n\n    void *p2new = gc_realloc(p2, 512ul * 1024, &gc);\n    assert_ptr_not_equal(p2, p2new);\n\n    void *p3 = gc_realloc(NULL, 512, &gc);\n    memset(p3, '3', 512);\n\n\n    gc_free(&gc);\n}\n\nstatic void\ntest_character_class(void **state)\n{\n    char buf[256];\n    strcpy(buf, \"There is \\x01 a nice 1234 year old tr\\x7f ee!\");\n    assert_false(string_mod(buf, CC_PRINT, 0, '@'));\n    assert_string_equal(buf, \"There is @ a nice 1234 year old tr@ ee!\");\n\n    strcpy(buf, \"There is \\x01 a nice 1234 year old tr\\x7f ee!\");\n    assert_true(string_mod(buf, CC_ANY, 0, '@'));\n    assert_string_equal(buf, \"There is \\x01 a nice 1234 year old tr\\x7f ee!\");\n\n    /* 0 as replace removes characters */\n    strcpy(buf, \"There is \\x01 a nice 1234 year old tr\\x7f ee!\");\n    assert_false(string_mod(buf, CC_PRINT, 0, '\\0'));\n    assert_string_equal(buf, \"There is  a nice 1234 year old tr ee!\");\n\n    strcpy(buf, \"There is \\x01 a nice 1234 year old tr\\x7f ee!\");\n    assert_false(string_mod(buf, CC_PRINT, CC_DIGIT, '@'));\n    assert_string_equal(buf, \"There is @ a nice @@@@ year old tr@ ee!\");\n\n    strcpy(buf, \"There is \\x01 a nice 1234 year old tr\\x7f ee!\");\n    assert_false(string_mod(buf, CC_ALPHA, CC_DIGIT, '.'));\n    assert_string_equal(buf, \"There.is...a.nice......year.old.tr..ee.\");\n\n    strcpy(buf, \"There is \\x01 a 'nice' \\\"1234\\\"\\n year old \\ntr\\x7f ee!\");\n    assert_false(string_mod(buf, CC_ALPHA | CC_DIGIT | CC_NEWLINE | CC_SINGLE_QUOTE,\n                            CC_DOUBLE_QUOTE | CC_BLANK, '.'));\n    assert_string_equal(buf, \"There.is...a.'nice'..1234.\\n.year.old.\\ntr..ee.\");\n\n    strcpy(buf, \"There is a \\\\'nice\\\\' \\\"1234\\\" [*] year old \\ntree!\");\n    assert_false(string_mod(buf, CC_PRINT, CC_BACKSLASH | CC_ASTERISK, '.'));\n    assert_string_equal(buf, \"There is a .'nice.' \\\"1234\\\" [.] year old .tree!\");\n}\n\n\nstatic void\ntest_character_string_mod_buf(void **state)\n{\n    struct gc_arena gc = gc_new();\n\n    struct buffer buf = alloc_buf_gc(1024, &gc);\n\n    const char test1[] = \"There is a nice 1234\\x00 year old tree!\";\n    buf_write(&buf, test1, sizeof(test1));\n\n    /* allow the null bytes and string but not the ! */\n    assert_false(string_check_buf(&buf, CC_ALNUM | CC_SPACE | CC_NULL, 0));\n\n    /* remove final ! and null byte to pass */\n    buf_inc_len(&buf, -2);\n    assert_true(string_check_buf(&buf, CC_ALNUM | CC_SPACE | CC_NULL, 0));\n\n    /* Check excluding digits works */\n    assert_false(string_check_buf(&buf, CC_ALNUM | CC_SPACE | CC_NULL, CC_DIGIT));\n    gc_free(&gc);\n}\n\nstatic void\ntest_snprintf(void **state)\n{\n    /* we used to have a custom openvpn_snprintf function because some\n     * OS (the comment did not specify which) did not always put the\n     * null byte there. So we unit test this to be sure.\n     *\n     * This probably refers to the MSVC behaviour, see also\n     * https://stackoverflow.com/questions/7706936/is-snprintf-always-null-terminating\n     */\n\n    /* Instead of trying to trick the compiler here, disable the warnings\n     * for this unit test. We know that the results will be truncated\n     * and we want to test that. Note we need the clang as clang-cl (msvc) does\n     * not define __GNUC__ like it does under UNIX(-like) platforms */\n#if defined(__GNUC__) || defined(__clang__)\n/* some clang version do not understand -Wformat-truncation, so ignore the\n * warning to avoid warnings/errors (-Werror) about unknown pragma/option */\n#if defined(__clang__)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wunknown-warning-option\"\n#endif\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wformat-truncation\"\n#endif\n\n    char buf[10] = { 'a' };\n    int ret = 0;\n\n    ret = snprintf(buf, sizeof(buf), \"0123456789abcde\");\n    assert_int_equal(ret, 15);\n    assert_int_equal(buf[9], '\\0');\n\n    memset(buf, 'b', sizeof(buf));\n    ret = snprintf(buf, sizeof(buf), \"- %d - %d -\", 77, 88);\n    assert_int_equal(ret, 11);\n    assert_int_equal(buf[9], '\\0');\n\n    memset(buf, 'c', sizeof(buf));\n    ret = snprintf(buf, sizeof(buf), \"- %8.2f\", 77.8899);\n    assert_int_equal(ret, 10);\n    assert_int_equal(buf[9], '\\0');\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n#endif\n}\n\nstatic void\ntest_checked_snprintf(void **state)\n{\n    char buf[10];\n    assert_true(checked_snprintf(buf, sizeof(buf), \"%s\", \"Hello\"));\n    assert_true(checked_snprintf(buf, sizeof(buf), \"%s\", \"Hello Foo\"));\n    assert_false(checked_snprintf(buf, sizeof(buf), \"%s\", \"Hello Foo!\"));\n    assert_false(checked_snprintf(buf, sizeof(buf), \"%s\", \"Hello World!\"));\n}\n\nvoid\ntest_buffer_chomp(void **state)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer buf = alloc_buf_gc(1024, &gc);\n\n    const char test1[] = \"There is a nice 1234 year old tree!\\n\\r\";\n    buf_write(&buf, test1, sizeof(test1));\n    buf_chomp(&buf);\n    /* Check that our own method agrees */\n    assert_true(string_check_buf(&buf, CC_PRINT | CC_NULL, CC_CRLF));\n    assert_string_equal(BSTR(&buf), \"There is a nice 1234 year old tree!\");\n\n    struct buffer buf2 = alloc_buf_gc(1024, &gc);\n    const char test2[] = \"CR_RESPONSE,MTIx\\x0a\\x00\";\n    buf_write(&buf2, test2, sizeof(test2));\n    buf_chomp(&buf2);\n\n    buf_chomp(&buf2);\n    /* Check that our own method agrees */\n    assert_true(string_check_buf(&buf2, CC_PRINT | CC_NULL, CC_CRLF));\n    assert_string_equal(BSTR(&buf2), \"CR_RESPONSE,MTIx\");\n\n    gc_free(&gc);\n}\n\n/* for building long texts */\n#define A_TIMES_256 \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO\"\n\nvoid\ntest_buffer_parse(void **state)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer buf = alloc_buf_gc(1024, &gc);\n    char line[512];\n    bool status;\n    const char test1[] = A_TIMES_256 \"EOL\\n\" A_TIMES_256 \"EOF\";\n\n    /* line buffer bigger than actual line */\n    assert_true(buf_write(&buf, test1, sizeof(test1)));\n    status = buf_parse(&buf, '\\n', line, sizeof(line));\n    assert_true(status);\n    assert_string_equal(line, A_TIMES_256 \"EOL\");\n    status = buf_parse(&buf, '\\n', line, sizeof(line));\n    assert_true(status);\n    assert_string_equal(line, A_TIMES_256 \"EOF\");\n\n    /* line buffer exactly same size as actual line + terminating \\0 */\n    buf_reset_len(&buf);\n    assert_true(buf_write(&buf, test1, sizeof(test1)));\n    status = buf_parse(&buf, '\\n', line, 260);\n    assert_true(status);\n    assert_string_equal(line, A_TIMES_256 \"EOL\");\n    status = buf_parse(&buf, '\\n', line, 260);\n    assert_true(status);\n    assert_string_equal(line, A_TIMES_256 \"EOF\");\n\n    /* line buffer smaller than actual line */\n    buf_reset_len(&buf);\n    assert_true(buf_write(&buf, test1, sizeof(test1)));\n    status = buf_parse(&buf, '\\n', line, 257);\n    assert_true(status);\n    assert_string_equal(line, A_TIMES_256);\n    status = buf_parse(&buf, '\\n', line, 257);\n    assert_true(status);\n    assert_string_equal(line, \"EOL\");\n    status = buf_parse(&buf, '\\n', line, 257);\n    assert_true(status);\n    assert_string_equal(line, A_TIMES_256);\n    status = buf_parse(&buf, '\\n', line, 257);\n    assert_true(status);\n    assert_string_equal(line, \"EOF\");\n\n    gc_free(&gc);\n}\n\nint\nmain(void)\n{\n    openvpn_unit_test_setup();\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(test_buffer_strprefix),\n        cmocka_unit_test(test_buffer_printf_catrunc),\n        cmocka_unit_test(test_buffer_format_hex_ex),\n        cmocka_unit_test_setup_teardown(test_buffer_list_aggregate_separator_empty,\n                                        test_buffer_list_setup, test_buffer_list_teardown),\n        cmocka_unit_test_setup_teardown(test_buffer_list_aggregate_separator_noop,\n                                        test_buffer_list_setup, test_buffer_list_teardown),\n        cmocka_unit_test_setup_teardown(test_buffer_list_aggregate_separator_two,\n                                        test_buffer_list_setup, test_buffer_list_teardown),\n        cmocka_unit_test_setup_teardown(test_buffer_list_aggregate_separator_all,\n                                        test_buffer_list_setup, test_buffer_list_teardown),\n        cmocka_unit_test_setup_teardown(test_buffer_list_aggregate_separator_nosep,\n                                        test_buffer_list_setup, test_buffer_list_teardown),\n        cmocka_unit_test_setup_teardown(test_buffer_list_aggregate_separator_zerolen,\n                                        test_buffer_list_setup, test_buffer_list_teardown),\n        cmocka_unit_test_setup_teardown(test_buffer_list_aggregate_separator_emptybuffers,\n                                        test_buffer_list_setup, test_buffer_list_teardown),\n        cmocka_unit_test(test_buffer_free_gc_one),\n        cmocka_unit_test(test_buffer_free_gc_two),\n        cmocka_unit_test(test_buffer_gc_realloc),\n        cmocka_unit_test(test_character_class),\n        cmocka_unit_test(test_character_string_mod_buf),\n        cmocka_unit_test(test_snprintf),\n        cmocka_unit_test(test_checked_snprintf),\n        cmocka_unit_test(test_buffer_chomp),\n        cmocka_unit_test(test_buffer_parse)\n    };\n\n    return cmocka_run_group_tests_name(\"buffer\", tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_common.h",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#include <setjmp.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <cmocka.h>\n\n#if defined(ENABLE_CRYPTO_MBEDTLS)\n#include \"mbedtls_compat.h\"\n#endif\n\n/* Do we use cmocka < 2.0.0? */\n#ifndef HAVE_CMOCKA_VERSION_H\n#define HAVE_OLD_CMOCKA_API 1\n/* compat with various versions of cmocka.h\n * Older versions have LargestIntegralType. Newer\n * versions use uintmax_t. But LargestIntegralType\n * is not guaranteed to be equal to uintmax_t, so\n * we can't use that unconditionally. So we only use\n * it if cmocka.h does not define LargestIntegralType.\n */\n#ifndef LargestIntegralType\n#define LargestIntegralType uintmax_t\n#endif\n/* redefine 2.x API in terms of 1.x API */\n#define CMockaValueData             LargestIntegralType\n#define check_expected_uint         check_expected\n#define expect_uint_value           expect_value\n#define expect_check_data           expect_check\n#define cast_ptr_to_cmocka_value(x) (x)\n#endif\n\n/**\n * Sets up the environment for unit tests like making both stderr and stdout\n * non-buffered to avoid messages getting lost if the program exits early.\n *\n * This has a openvpn prefix to avoid confusion with cmocka's unit_test_setup_*\n * methods\n */\nstatic inline void\nopenvpn_unit_test_setup(void)\n{\n    assert_int_equal(setvbuf(stdout, NULL, _IONBF, BUFSIZ), 0);\n    assert_int_equal(setvbuf(stderr, NULL, _IONBF, BUFSIZ), 0);\n#if defined(ENABLE_CRYPTO_MBEDTLS)\n    mbedtls_compat_psa_crypto_init();\n#endif\n}\n\n/**\n * Helper function to get a file path from the unit test directory to open it\n * or pass its path to another function. This function will first look for\n * an environment variable or if failing that, will fall back to a hardcoded\n * value from compile time if compiled with CMake.\n *\n * @param buf           buffer holding the path to the file\n * @param bufsize       size of buf\n * @param filename      name of the filename to retrieve relative to the\n *                      unit test source directory\n */\nvoid\nopenvpn_test_get_srcdir_dir(char *buf, size_t bufsize, const char *filename)\n{\n    const char *srcdir = getenv(\"srcdir\");\n\n#if defined(UNIT_TEST_SOURCEDIR)\n    if (!srcdir)\n    {\n        srcdir = UNIT_TEST_SOURCEDIR;\n    }\n#endif\n    assert_non_null(srcdir);\n\n    snprintf(buf, bufsize, \"%s/%s\", srcdir, filename);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_crypto.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <setjmp.h>\n#include <cmocka.h>\n\n#include \"crypto.h\"\n#include \"crypto_epoch.h\"\n#include \"options.h\"\n#include \"ssl_backend.h\"\n\n#include \"mss.h\"\n#include \"test_common.h\"\n\n\n#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L\n#include <openssl/core_names.h>\n#include <openssl/kdf.h>\n#endif\n\nstatic const char testtext[] = \"Dummy text to test PEM encoding\";\n\nstatic void\ncrypto_pem_encode_decode_loopback(void **state)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer src_buf;\n    buf_set_read(&src_buf, (void *)testtext, sizeof(testtext));\n\n    uint8_t dec[sizeof(testtext)];\n    struct buffer dec_buf;\n    buf_set_write(&dec_buf, dec, sizeof(dec));\n\n    struct buffer pem_buf;\n\n    assert_true(crypto_pem_encode(\"TESTKEYNAME\", &pem_buf, &src_buf, &gc));\n    assert_true(BLEN(&src_buf) < BLEN(&pem_buf));\n\n    /* Wrong key name */\n    assert_false(crypto_pem_decode(\"WRONGNAME\", &dec_buf, &pem_buf));\n\n    assert_true(crypto_pem_decode(\"TESTKEYNAME\", &dec_buf, &pem_buf));\n    assert_int_equal(BLEN(&src_buf), BLEN(&dec_buf));\n    assert_memory_equal(BPTR(&src_buf), BPTR(&dec_buf), BLENZ(&src_buf));\n\n    gc_free(&gc);\n}\n\nstatic void\ntest_translate_cipher(const char *ciphername, const char *openvpn_name)\n{\n    bool cipher = cipher_valid(ciphername);\n\n    /* Empty cipher is fine */\n    if (!cipher)\n    {\n        return;\n    }\n\n    const char *kt_name = cipher_kt_name(ciphername);\n\n    assert_string_equal(kt_name, openvpn_name);\n}\n\nstatic void\ntest_cipher_names(const char *ciphername, const char *openvpn_name)\n{\n    struct gc_arena gc = gc_new();\n    /* Go through some variants, if the cipher library accepts these, they\n     * should be normalised to the openvpn name */\n    char *upper = string_alloc(ciphername, &gc);\n    char *lower = string_alloc(ciphername, &gc);\n    char *random_case = string_alloc(ciphername, &gc);\n\n    for (size_t i = 0; i < strlen(ciphername); i++)\n    {\n        upper[i] = (char)toupper((unsigned char)ciphername[i]);\n        lower[i] = (char)tolower((unsigned char)ciphername[i]);\n        if (rand() & 0x1)\n        {\n            random_case[i] = upper[i];\n        }\n        else\n        {\n            random_case[i] = lower[i];\n        }\n    }\n\n    if (!openvpn_name)\n    {\n        openvpn_name = upper;\n    }\n\n    test_translate_cipher(upper, openvpn_name);\n    test_translate_cipher(lower, openvpn_name);\n    test_translate_cipher(random_case, openvpn_name);\n    test_translate_cipher(ciphername, openvpn_name);\n\n\n    gc_free(&gc);\n}\n\nstatic void\ncrypto_translate_cipher_names(void **state)\n{\n    /* Test that a number of ciphers to see that they turn out correctly */\n    test_cipher_names(\"BF-CBC\", NULL);\n    test_cipher_names(\"BLOWFISH-CBC\", \"BF-CBC\");\n    test_cipher_names(\"Chacha20-Poly1305\", NULL);\n    test_cipher_names(\"AES-128-GCM\", NULL);\n    test_cipher_names(\"AES-128-CBC\", NULL);\n    test_cipher_names(\"CAMELLIA-128-CFB128\", \"CAMELLIA-128-CFB\");\n    test_cipher_names(\"id-aes256-GCM\", \"AES-256-GCM\");\n}\n\n\nstatic const char *ipsumlorem = \"Lorem ipsum dolor sit amet, consectetur \"\n                                \"adipisici elit, sed eiusmod tempor incidunt \"\n                                \"ut labore et dolore magna aliqua.\";\n\nstatic void\ncrypto_test_tls_prf(void **state)\n{\n    const char *seedstr = \"Quis aute iure reprehenderit in voluptate \"\n                          \"velit esse cillum dolore\";\n    const unsigned char *seed = (const unsigned char *)seedstr;\n    const size_t seed_len = strlen(seedstr);\n\n\n    const unsigned char *secret = (const unsigned char *)ipsumlorem;\n    size_t secret_len = strlen((const char *)secret);\n\n\n    uint8_t out[32];\n    bool ret = ssl_tls1_PRF(seed, seed_len, secret, secret_len, out, sizeof(out));\n\n#if defined(LIBRESSL_VERSION_NUMBER) || defined(ENABLE_CRYPTO_WOLFSSL)\n    /* No TLS1 PRF support in these libraries */\n    assert_false(ret);\n#else\n    assert_true(ret);\n    uint8_t good_prf[32] = { 0xd9, 0x8c, 0x85, 0x18, 0xc8, 0x5e, 0x94, 0x69, 0x27, 0x91, 0x6a,\n                             0xcf, 0xc2, 0xd5, 0x92, 0xfb, 0xb1, 0x56, 0x7e, 0x4b, 0x4b, 0x14,\n                             0x59, 0xe6, 0xa9, 0x04, 0xac, 0x2d, 0xda, 0xb7, 0x2d, 0x67 };\n    assert_memory_equal(good_prf, out, sizeof(out));\n#endif\n}\n\nstatic uint8_t testkey[20] = { 0x0b, 0x00 };\nstatic uint8_t goodhash[20] = { 0x58, 0xea, 0x5a, 0xf0, 0x42, 0x94, 0xe9, 0x17, 0xed, 0x84,\n                                0xb9, 0xf0, 0x83, 0x30, 0x23, 0xae, 0x8b, 0xa7, 0x7e, 0xb8 };\n\nstatic void\ncrypto_test_hmac(void **state)\n{\n    hmac_ctx_t *hmac = hmac_ctx_new();\n\n    assert_int_equal(md_kt_size(\"SHA1\"), 20);\n\n    uint8_t key[20];\n    memcpy(key, testkey, sizeof(key));\n\n    hmac_ctx_init(hmac, key, \"SHA1\");\n    hmac_ctx_update(hmac, (const uint8_t *)ipsumlorem, (int)strlen(ipsumlorem));\n    hmac_ctx_update(hmac, (const uint8_t *)ipsumlorem, (int)strlen(ipsumlorem));\n\n    uint8_t hash[20];\n    hmac_ctx_final(hmac, hash);\n\n    assert_memory_equal(hash, goodhash, sizeof(hash));\n    memset(hash, 0x00, sizeof(hash));\n\n    /* try again */\n    hmac_ctx_reset(hmac);\n    hmac_ctx_update(hmac, (const uint8_t *)ipsumlorem, (int)strlen(ipsumlorem));\n    hmac_ctx_update(hmac, (const uint8_t *)ipsumlorem, (int)strlen(ipsumlorem));\n    hmac_ctx_final(hmac, hash);\n\n    assert_memory_equal(hash, goodhash, sizeof(hash));\n\n    /* Fill our key with random data to ensure it is not used by hmac anymore */\n    memset(key, 0x55, sizeof(key));\n\n    hmac_ctx_reset(hmac);\n    hmac_ctx_update(hmac, (const uint8_t *)ipsumlorem, (int)strlen(ipsumlorem));\n    hmac_ctx_update(hmac, (const uint8_t *)ipsumlorem, (int)strlen(ipsumlorem));\n    hmac_ctx_final(hmac, hash);\n\n    assert_memory_equal(hash, goodhash, sizeof(hash));\n    hmac_ctx_cleanup(hmac);\n    hmac_ctx_free(hmac);\n}\n\n/* This test is in test_crypto as it calls into the functions that calculate\n * the crypto overhead */\nstatic void\ntest_occ_mtu_calculation(void **state)\n{\n    struct gc_arena gc = gc_new();\n\n    struct frame f = { 0 };\n    struct options o = { 0 };\n    size_t linkmtu;\n\n    /* common defaults */\n    o.ce.tun_mtu = 1400;\n    o.ce.proto = PROTO_UDP;\n\n    /* No crypto at all */\n    o.ciphername = \"none\";\n    o.authname = \"none\";\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1400);\n\n    /* Static key OCC examples */\n    o.shared_secret_file = \"not null\";\n\n    /* secret, auth none, cipher none */\n    o.ciphername = \"none\";\n    o.authname = \"none\";\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1408);\n\n    /* secret, cipher AES-128-CBC, auth none */\n    o.ciphername = \"AES-128-CBC\";\n    o.authname = \"none\";\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1440);\n\n    /* secret, cipher none, auth SHA256 */\n    o.ciphername = \"none\";\n    o.authname = \"SHA256\";\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1440);\n\n    /* secret, cipher BF-CBC, auth SHA1 */\n    o.ciphername = \"BF-CBC\";\n    o.authname = \"SHA1\";\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1444);\n\n    /* secret, cipher BF-CBC, auth SHA1, tcp-client */\n    o.ce.proto = PROTO_TCP_CLIENT;\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1446);\n\n    o.ce.proto = PROTO_UDP;\n\n#if defined(USE_COMP)\n    o.comp.alg = COMP_ALG_LZO;\n\n    /* secret, comp-lzo yes, cipher BF-CBC, auth SHA1 */\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1445);\n\n#if defined(ENABLE_FRAGMENT)\n    /* secret, comp-lzo yes, cipher BF-CBC, auth SHA1, fragment 1200 */\n    o.ce.fragment = 1200;\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1449);\n    o.ce.fragment = 0;\n#endif\n\n    o.comp.alg = COMP_ALG_UNDEF;\n#endif\n\n    /* TLS mode */\n    o.shared_secret_file = NULL;\n    o.tls_client = true;\n    o.pull = true;\n\n    /* tls client, cipher AES-128-CBC, auth SHA1, tls-auth */\n    o.authname = \"SHA1\";\n    o.ciphername = \"AES-128-CBC\";\n    o.tls_auth_file = \"dummy\";\n\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1457);\n\n    /* tls client, cipher AES-128-CBC, auth SHA1 */\n    o.tls_auth_file = NULL;\n\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1457);\n\n    /* tls client, cipher none, auth none */\n    o.authname = \"none\";\n    o.ciphername = \"none\";\n\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1405);\n\n    /* tls client, auth SHA1, cipher AES-256-GCM */\n    o.authname = \"SHA1\";\n    o.ciphername = \"AES-256-GCM\";\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1449);\n\n\n#if defined(USE_COMP) && defined(ENABLE_FRAGMENT)\n    o.comp.alg = COMP_ALG_LZO;\n\n    /* tls client, auth SHA1, cipher AES-256-GCM, fragment, comp-lzo yes */\n    o.ce.fragment = 1200;\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1454);\n\n    /* tls client, auth SHA1, cipher AES-256-GCM, fragment, comp-lzo yes, socks */\n    o.ce.socks_proxy_server = \"socks.example.com\";\n    linkmtu = calc_options_string_link_mtu(&o, &f);\n    assert_int_equal(linkmtu, 1464);\n#endif\n\n    gc_free(&gc);\n}\n\nstatic void\ntest_mssfix_mtu_calculation(void **state)\n{\n    struct gc_arena gc = gc_new();\n\n    struct frame f = { 0 };\n    struct options o = { 0 };\n\n    /* common defaults */\n    o.ce.tun_mtu = 1400;\n    o.ce.mssfix = 1000;\n    o.ce.proto = PROTO_UDP;\n\n    /* No crypto at all */\n    o.ciphername = \"none\";\n    o.authname = \"none\";\n    struct key_type kt;\n    init_key_type(&kt, o.ciphername, o.authname, false, false);\n\n    /* No encryption, just packet id (8) + TCP payload(20) + IP payload(20) */\n    frame_calculate_dynamic(&f, &kt, &o, NULL);\n    assert_int_equal(f.mss_fix, 952);\n\n    /* Static key OCC examples */\n    o.shared_secret_file = \"not null\";\n\n    /* secret, auth none, cipher none */\n    o.ciphername = \"none\";\n    o.authname = \"none\";\n    init_key_type(&kt, o.ciphername, o.authname, false, false);\n    frame_calculate_dynamic(&f, &kt, &o, NULL);\n    assert_int_equal(f.mss_fix, 952);\n\n    /* secret, cipher AES-128-CBC, auth none */\n    o.ciphername = \"AES-128-CBC\";\n    o.authname = \"none\";\n    init_key_type(&kt, o.ciphername, o.authname, false, false);\n\n    for (int i = 990; i <= 1010; i++)\n    {\n        /* 992 - 1008 should end up with the same mssfix value all they\n         * all result in the same CBC block size/padding and <= 991 and >=1008\n         * should be one block less and more respectively */\n        o.ce.mssfix = i;\n        frame_calculate_dynamic(&f, &kt, &o, NULL);\n        if (i <= 991)\n        {\n            assert_int_equal(f.mss_fix, 911);\n        }\n        else if (i >= 1008)\n        {\n            assert_int_equal(f.mss_fix, 943);\n        }\n        else\n        {\n            assert_int_equal(f.mss_fix, 927);\n        }\n    }\n#ifdef USE_COMP\n    o.comp.alg = COMP_ALG_LZO;\n\n    /* Same but with compression added. Compression adds one byte extra to the\n     * payload so the payload should be reduced by compared to the no\n     * compression calculation before */\n    for (int i = 990; i <= 1010; i++)\n    {\n        /* 992 - 1008 should end up with the same mssfix value all they\n         * all result in the same CBC block size/padding and <= 991 and >=1008\n         * should be one block less and more respectively */\n        o.ce.mssfix = i;\n        frame_calculate_dynamic(&f, &kt, &o, NULL);\n        if (i <= 991)\n        {\n            assert_int_equal(f.mss_fix, 910);\n        }\n        else if (i >= 1008)\n        {\n            assert_int_equal(f.mss_fix, 942);\n        }\n        else\n        {\n            assert_int_equal(f.mss_fix, 926);\n        }\n    }\n    o.comp.alg = COMP_ALG_UNDEF;\n#endif /* ifdef USE_COMP */\n\n    /* tls client, auth SHA1, cipher AES-256-GCM */\n    o.authname = \"SHA1\";\n    o.ciphername = \"AES-256-GCM\";\n    o.tls_client = true;\n    o.peer_id = 77;\n    o.use_peer_id = true;\n    init_key_type(&kt, o.ciphername, o.authname, true, false);\n\n    for (int i = 900; i <= 1200; i++)\n    {\n        /* For stream ciphers, the value should not be influenced by block\n         * sizes or similar but always have the same difference */\n        o.ce.mssfix = i;\n        frame_calculate_dynamic(&f, &kt, &o, NULL);\n\n        /* 4 byte opcode/peerid, 4 byte pkt ID, 16 byte tag, 40 TCP+IP */\n        assert_int_equal(f.mss_fix, i - 4 - 4 - 16 - 40);\n    }\n\n    gc_free(&gc);\n}\n\nvoid\ncrypto_test_aead_limits(void **state)\n{\n    /* if ChaCha20-Poly1305 is not supported by the crypto library or in the\n     * current mode (FIPS), this will still return -1 */\n    assert_int_equal(cipher_get_aead_limits(\"CHACHA20-POLY1305\"), 0);\n\n    int64_t aeslimit = cipher_get_aead_limits(\"AES-128-GCM\");\n\n    assert_int_equal(aeslimit, (1ull << 36) - 1);\n\n    /* Check if this matches our exception for 1600 size packets assuming\n     * AEAD_LIMIT_BLOCKSIZE (128 bits/ 16 bytes). Gives us 100 blocks\n     * + 1 for the packet */\n    int64_t L = 101;\n    /* 2 ^ 29.34, using the result here to avoid linking to libm */\n    assert_int_equal(aeslimit / L, 680390858);\n\n    /* and for 9000, 2^26.86 */\n    L = 563;\n    assert_int_equal(aeslimit / L, 122059461);\n}\n\nvoid\ncrypto_test_hkdf_expand_testa1(void **state)\n{\n    /* RFC 5889 A.1 Test Case 1 */\n    uint8_t prk[32] = { 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, 0x0d, 0xdc, 0x3f,\n                        0x0d, 0xc4, 0x7b, 0xba, 0x63, 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f,\n                        0x9c, 0x31, 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5 };\n\n    uint8_t info[10] = { 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9 };\n\n    uint8_t okm[42] = { 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f,\n                        0x64, 0xd0, 0x36, 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a,\n                        0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, 0x34,\n                        0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65 };\n\n    uint8_t out[42];\n    ovpn_hkdf_expand(prk, info, sizeof(info), out, sizeof(out));\n\n    assert_memory_equal(out, okm, sizeof(out));\n}\n\nvoid\ncrypto_test_hkdf_expand_testa2(void **state)\n{\n    /* RFC 5889 A.2 Test Case 2 */\n    uint8_t prk[32] = { 0x06, 0xa6, 0xb8, 0x8c, 0x58, 0x53, 0x36, 0x1a, 0x06, 0x10, 0x4c,\n                        0x9c, 0xeb, 0x35, 0xb4, 0x5c, 0xef, 0x76, 0x00, 0x14, 0x90, 0x46,\n                        0x71, 0x01, 0x4a, 0x19, 0x3f, 0x40, 0xc1, 0x5f, 0xc2, 0x44 };\n\n    uint8_t info[80] = { 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb,\n                         0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,\n                         0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3,\n                         0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,\n                         0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb,\n                         0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,\n                         0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff };\n\n    const int L = 82;\n    uint8_t okm[82] = { 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c,\n                        0x59, 0x6a, 0x49, 0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8,\n                        0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf, 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99,\n                        0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb, 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09,\n                        0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8, 0x36, 0x77, 0x93, 0xa9,\n                        0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87,\n                        0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87 };\n\n    uint8_t out[82] = { 0xaa };\n    ovpn_hkdf_expand(prk, info, sizeof(info), out, L);\n\n    assert_memory_equal(out, okm, L);\n}\n\nvoid\ncrypto_test_hkdf_expand_testa3(void **state)\n{\n    /* RFC 5889 A.3 Test Case 3 */\n    uint8_t prk[32] = { 0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16, 0x7f, 0x33, 0xa9,\n                        0x1d, 0x6f, 0x64, 0x8b, 0xdf, 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb,\n                        0x63, 0x77, 0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04 };\n\n    uint8_t info[] = { 0 };\n\n    int L = 42;\n    uint8_t okm[42] = { 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80,\n                        0x2a, 0x06, 0x3c, 0x5a, 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1,\n                        0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d, 0x9d,\n                        0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8 };\n\n    uint8_t out[42];\n    ovpn_hkdf_expand(prk, info, 0, out, L);\n\n    assert_memory_equal(out, okm, L);\n}\n\nvoid\ncrypto_test_hkdf_expand_test_ovpn(void **state)\n{\n    /* tests the HDKF with a label/okm that OpenVPN itself uses in OpenSSL 3\n     * HDKF unit test*/\n\n    uint8_t prk[32] = { 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, 0x0d, 0xdc, 0x3f,\n                        0x0d, 0xc4, 0x7b, 0xba, 0x63, 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f,\n                        0x9c, 0x31, 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5 };\n\n    uint8_t info[18] = { 0x00, 0x1b, 0x0e, 0x6f, 0x76, 0x70, 0x6e, 0x20, 0x75,\n                         0x6e, 0x69, 0x74, 0x20, 0x74, 0x65, 0x73, 0x74, 0x00 };\n\n    int L = 27;\n    uint8_t okm[27] = { 0x87, 0x5a, 0x8e, 0xec, 0x18, 0x55, 0x63, 0x80, 0xb8,\n                        0xd9, 0x33, 0xed, 0x32, 0x3c, 0x2d, 0xf8, 0xe8, 0xec,\n                        0xcf, 0x49, 0x72, 0xe6, 0x83, 0xf0, 0x6a, 0x83, 0xac };\n\n    uint8_t out[27];\n    ovpn_hkdf_expand(prk, info, sizeof(info), out, L);\n\n    assert_memory_equal(out, okm, L);\n}\n\nvoid\ncrypto_test_ovpn_label_expand(void **state)\n{\n    uint8_t secret[32] = { 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, 0x0d, 0xdc, 0x3f,\n                           0x0d, 0xc4, 0x7b, 0xba, 0x63, 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f,\n                           0x9c, 0x31, 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5 };\n\n    const uint8_t *label = (const uint8_t *)(\"unit test\");\n    uint8_t out[16];\n    ovpn_expand_label(secret, sizeof(secret), label, 9, NULL, 0, out, sizeof(out));\n\n    uint8_t out_expected[16] = { 0x18, 0x5e, 0xaa, 0x1c, 0x7f, 0x22, 0x8a, 0xb8,\n                                 0xeb, 0x29, 0x77, 0x32, 0x14, 0xd9, 0x20, 0x46 };\n\n    assert_memory_equal(out, out_expected, 16);\n}\n\n#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L\n/* We have OpenSSL 3.0+, we test if their implementation matches our\n * implementation. We currently do not use this code from the crypto library\n * in the main code yet as we don't want to repeat the mess that the current\n * openvpn_PRF ifdef maze */\n\nbool\nossl_expand_label(const uint8_t *secret, size_t secret_len, const uint8_t *label, size_t label_len,\n                  const uint8_t *context, size_t context_len, uint8_t *out, uint16_t out_len)\n{\n    OSSL_LIB_CTX *libctx = NULL;\n    const char *properties = NULL;\n\n    const uint8_t *label_prefix = (const uint8_t *)(\"ovpn \");\n    const size_t label_prefix_len = 5;\n\n    EVP_KDF *kdf = EVP_KDF_fetch(libctx, OSSL_KDF_NAME_TLS1_3_KDF, properties);\n    assert_non_null(kdf);\n\n    const char *mdname = \"SHA-256\";\n\n    size_t hashlen = SHA256_DIGEST_LENGTH;\n\n    EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf);\n    assert_non_null(kctx);\n\n    OSSL_PARAM params[7];\n    OSSL_PARAM *p = params;\n\n    int mode = EVP_PKEY_HKDEF_MODE_EXPAND_ONLY;\n\n    *p++ = OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode);\n    *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, (char *)mdname, 0);\n    *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (unsigned char *)secret, hashlen);\n    *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PREFIX, (unsigned char *)label_prefix,\n                                             label_prefix_len);\n    *p++ =\n        OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_LABEL, (unsigned char *)label, label_len);\n\n    *p++ = OSSL_PARAM_construct_end();\n\n    int ret = EVP_KDF_derive(kctx, out, out_len, params);\n    EVP_KDF_CTX_free(kctx);\n    EVP_KDF_free(kdf);\n\n    assert_int_equal(ret, 1);\n    return true;\n}\n\nvoid\ncrypto_test_ovpn_expand_openssl3(void **state)\n{\n    uint8_t secret[32] = { 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, 0x0d, 0xdc, 0x3f,\n                           0x0d, 0xc4, 0x7b, 0xba, 0x63, 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f,\n                           0x9c, 0x31, 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5 };\n\n    const uint8_t *label = (const uint8_t *)(\"unit test\");\n    const size_t labellen = 9;\n    uint8_t out[27];\n\n    ossl_expand_label(secret, sizeof(secret), label, labellen, NULL, 0, out, sizeof(out));\n\n    /* Do the same derivation with our own function */\n    uint8_t out_ovpn[27];\n\n    ovpn_expand_label(secret, sizeof(secret), label, 9, NULL, 0, out_ovpn, sizeof(out_ovpn));\n    assert_memory_equal(out_ovpn, out, sizeof(out_ovpn));\n}\n\n#else  /* if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L */\nvoid\ncrypto_test_ovpn_expand_openssl3(void **state)\n{\n    skip();\n}\n#endif /* if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L */\n\nstruct epoch_test_state\n{\n    struct key_type kt;\n    struct gc_arena gc;\n    struct crypto_options co;\n};\n\nstatic int\ncrypto_test_epoch_setup(void **state)\n{\n    uint16_t *num_future_keys = (uint16_t *)*state;\n    struct epoch_test_state *data = calloc(1, sizeof(struct epoch_test_state));\n\n    data->gc = gc_new();\n\n    init_key_type(&data->kt, \"AES-128-GCM\", \"none\", true, false);\n\n    /* have an epoch key that uses 0x23 for the key for all bytes */\n    struct epoch_key epoch1send = { .epoch = 1, .epoch_key = { 0x23 } };\n    struct epoch_key epoch1recv = { .epoch = 1, .epoch_key = { 0x27 } };\n\n    epoch_init_key_ctx(&data->co, &data->kt, &epoch1send, &epoch1recv, *num_future_keys);\n\n    *state = data;\n    return 0;\n}\n\nstatic int\ncrypto_test_epoch_teardown(void **state)\n{\n    struct epoch_test_state *data = *state;\n    free_epoch_key_ctx(&data->co);\n    free_key_ctx_bi(&data->co.key_ctx_bi);\n    gc_free(&data->gc);\n    free(*state);\n    return 0;\n}\n\nvoid\ncrypto_test_epoch_key_generation(void **state)\n{\n    struct epoch_test_state *data = *state;\n    struct crypto_options *co = &data->co;\n\n    /* check the keys look like expect */\n    assert_int_equal(co->epoch_data_keys_future[0].epoch, 2);\n    assert_int_equal(co->epoch_data_keys_future[15].epoch, 17);\n    assert_int_equal(co->epoch_key_send.epoch, 1);\n    assert_int_equal(co->epoch_key_recv.epoch, 17);\n\n    /* Now replace the recv key with the 6th future key (epoch = 8) */\n    free_key_ctx(&co->key_ctx_bi.decrypt);\n    assert_int_equal(co->epoch_data_keys_future[6].epoch, 8);\n    co->key_ctx_bi.decrypt = co->epoch_data_keys_future[6];\n    CLEAR(co->epoch_data_keys_future[6]);\n\n    epoch_generate_future_receive_keys(co);\n    assert_int_equal(co->epoch_data_keys_future[0].epoch, 9);\n    assert_int_equal(co->epoch_data_keys_future[15].epoch, 24);\n}\n\n\nvoid\ncrypto_test_epoch_key_rotation(void **state)\n{\n    struct epoch_test_state *data = *state;\n    struct crypto_options *co = &data->co;\n\n    /* should replace send + key recv */\n    epoch_replace_update_recv_key(co, 9);\n\n    assert_int_equal(co->key_ctx_bi.decrypt.epoch, 9);\n    assert_int_equal(co->key_ctx_bi.encrypt.epoch, 9);\n    assert_int_equal(co->epoch_key_send.epoch, 9);\n    assert_int_equal(co->epoch_retiring_data_receive_key.epoch, 1);\n\n    /* Iterate the data send key four times to get it to 13 */\n    for (int i = 0; i < 4; i++)\n    {\n        epoch_iterate_send_key(co);\n    }\n    assert_int_equal(co->key_ctx_bi.encrypt.epoch, 13);\n\n    epoch_replace_update_recv_key(co, 10);\n    assert_int_equal(co->key_ctx_bi.decrypt.epoch, 10);\n    assert_int_equal(co->key_ctx_bi.encrypt.epoch, 13);\n    assert_int_equal(co->epoch_key_send.epoch, 13);\n    assert_int_equal(co->epoch_retiring_data_receive_key.epoch, 9);\n\n    epoch_replace_update_recv_key(co, 12);\n    assert_int_equal(co->key_ctx_bi.decrypt.epoch, 12);\n    assert_int_equal(co->key_ctx_bi.encrypt.epoch, 13);\n    assert_int_equal(co->epoch_key_send.epoch, 13);\n    assert_int_equal(co->epoch_retiring_data_receive_key.epoch, 10);\n\n    epoch_iterate_send_key(co);\n    assert_int_equal(co->key_ctx_bi.encrypt.epoch, 14);\n}\n\nvoid\ncrypto_test_epoch_key_receive_lookup(void **state)\n{\n    struct epoch_test_state *data = *state;\n    struct crypto_options *co = &data->co;\n\n    /* lookup some wacky things that should fail */\n    assert_null(epoch_lookup_decrypt_key(co, 2000));\n    assert_null(epoch_lookup_decrypt_key(co, -1));\n    assert_null(epoch_lookup_decrypt_key(co, 0xefff));\n\n    /* Lookup the edges of the current window */\n    assert_null(epoch_lookup_decrypt_key(co, 0));\n    assert_int_equal(co->epoch_retiring_data_receive_key.epoch, 0);\n    assert_int_equal(epoch_lookup_decrypt_key(co, 1)->epoch, 1);\n    assert_int_equal(epoch_lookup_decrypt_key(co, 2)->epoch, 2);\n    assert_int_equal(epoch_lookup_decrypt_key(co, 13)->epoch, 13);\n    assert_int_equal(epoch_lookup_decrypt_key(co, 14)->epoch, 14);\n    assert_null(epoch_lookup_decrypt_key(co, 15));\n\n    /* Should move 1 to retiring key but leave 2-6 undefined, 7 as\n     * active and 8-20 as future keys*/\n    epoch_replace_update_recv_key(co, 7);\n\n    assert_null(epoch_lookup_decrypt_key(co, 0));\n    assert_int_equal(epoch_lookup_decrypt_key(co, 1)->epoch, 1);\n    assert_ptr_equal(epoch_lookup_decrypt_key(co, 1), &co->epoch_retiring_data_receive_key);\n\n    assert_null(epoch_lookup_decrypt_key(co, 2));\n    assert_null(epoch_lookup_decrypt_key(co, 3));\n    assert_null(epoch_lookup_decrypt_key(co, 4));\n    assert_null(epoch_lookup_decrypt_key(co, 5));\n    assert_null(epoch_lookup_decrypt_key(co, 6));\n    assert_int_equal(epoch_lookup_decrypt_key(co, 7)->epoch, 7);\n    assert_int_equal(epoch_lookup_decrypt_key(co, 8)->epoch, 8);\n    assert_int_equal(epoch_lookup_decrypt_key(co, 20)->epoch, 20);\n    assert_null(epoch_lookup_decrypt_key(co, 21));\n    assert_null(epoch_lookup_decrypt_key(co, 22));\n\n\n    /* Should move 7 to retiring key and have 8 as active key and\n     * 9-21 as future keys */\n    epoch_replace_update_recv_key(co, 8);\n    assert_null(epoch_lookup_decrypt_key(co, 0));\n    assert_null(epoch_lookup_decrypt_key(co, 1));\n    assert_null(epoch_lookup_decrypt_key(co, 2));\n    assert_null(epoch_lookup_decrypt_key(co, 3));\n    assert_null(epoch_lookup_decrypt_key(co, 4));\n    assert_null(epoch_lookup_decrypt_key(co, 5));\n    assert_null(epoch_lookup_decrypt_key(co, 6));\n    assert_int_equal(epoch_lookup_decrypt_key(co, 7)->epoch, 7);\n    assert_ptr_equal(epoch_lookup_decrypt_key(co, 7), &co->epoch_retiring_data_receive_key);\n    assert_int_equal(epoch_lookup_decrypt_key(co, 8)->epoch, 8);\n    assert_int_equal(epoch_lookup_decrypt_key(co, 20)->epoch, 20);\n    assert_int_equal(epoch_lookup_decrypt_key(co, 21)->epoch, 21);\n    assert_null(epoch_lookup_decrypt_key(co, 22));\n    assert_null(epoch_lookup_decrypt_key(co, 23));\n}\n\nvoid\ncrypto_test_epoch_key_overflow(void **state)\n{\n    struct epoch_test_state *data = *state;\n    struct crypto_options *co = &data->co;\n\n    /* Modify the receive epoch and keys to have a very high epoch to test\n     * the end of array. Iterating through all 65k keys takes a 2-3s, so we\n     * avoid this for the unit test */\n    co->key_ctx_bi.decrypt.epoch = 65500;\n    co->key_ctx_bi.encrypt.epoch = 65500;\n\n    co->epoch_key_send.epoch = 65500;\n    co->epoch_key_recv.epoch = 65500 + co->epoch_data_keys_future_count;\n\n    for (uint16_t i = 0; i < co->epoch_data_keys_future_count; i++)\n    {\n        co->epoch_data_keys_future[i].epoch = 65501 + i;\n    }\n\n    /* Move the last few keys until we are close to the limit */\n    while (co->key_ctx_bi.decrypt.epoch < (UINT16_MAX - 40))\n    {\n        epoch_replace_update_recv_key(co, co->key_ctx_bi.decrypt.epoch + 10);\n    }\n\n    /* Looking up this key should still work as it will not break the limit\n     * when generating keys */\n    assert_int_equal(epoch_lookup_decrypt_key(co, UINT16_MAX - 34)->epoch, UINT16_MAX - 34);\n    assert_int_equal(epoch_lookup_decrypt_key(co, UINT16_MAX - 33)->epoch, UINT16_MAX - 33);\n\n    /* This key is no longer eligible for decrypting as the 32 future keys\n     * would be larger than uint16_t maximum */\n    assert_int_equal(co->epoch_data_keys_future_count, 32);\n    assert_null(epoch_lookup_decrypt_key(co, UINT16_MAX - co->epoch_data_keys_future_count));\n    assert_null(epoch_lookup_decrypt_key(co, UINT16_MAX));\n\n    /* Check that moving to the last possible epoch works */\n    epoch_replace_update_recv_key(co, UINT16_MAX - 33);\n    assert_int_equal(epoch_lookup_decrypt_key(co, UINT16_MAX - 33)->epoch, UINT16_MAX - 33);\n    assert_null(epoch_lookup_decrypt_key(co, UINT16_MAX - 32));\n    assert_null(epoch_lookup_decrypt_key(co, UINT16_MAX));\n}\n\nvoid\ncrypto_test_epoch_edge(void **state)\n{\n    struct epoch_test_state *data = *state;\n    struct crypto_options *co = &data->co;\n\n    for (uint16_t i = 1; i <= 13; i++)\n    {\n        uint16_t current_epoch = co->key_ctx_bi.decrypt.epoch;\n        uint16_t target_epoch = current_epoch + i;\n\n        struct key_ctx *decrypt_key = epoch_lookup_decrypt_key(co, target_epoch);\n        assert_non_null(decrypt_key);\n\n        assert_int_equal(decrypt_key->epoch, target_epoch);\n\n        epoch_replace_update_recv_key(co, target_epoch);\n\n        assert_int_equal(co->key_ctx_bi.decrypt.epoch, target_epoch);\n    }\n\n    /* Check that 14 is not valid anymnore */\n    uint16_t current_epoch = co->key_ctx_bi.decrypt.epoch;\n    uint16_t target_epoch = current_epoch + 14;\n\n    struct key_ctx *decrypt_key = epoch_lookup_decrypt_key(co, target_epoch);\n    assert_null(decrypt_key);\n}\n\nvoid\nepoch_test_derive_data_key(void **state)\n{\n    struct epoch_key e17 = { .epoch = 17, .epoch_key = { 19, 12 } };\n    struct key_type kt = { 0 };\n    struct key_parameters key_parameters = { 0 };\n    init_key_type(&kt, \"AES-192-GCM\", \"none\", true, false);\n\n\n    epoch_data_key_derive(&key_parameters, &e17, &kt);\n\n    assert_int_equal(key_parameters.cipher_size, 24);\n    assert_int_equal(key_parameters.hmac_size, 12);\n\n    uint8_t exp_cipherkey[24] = { 0xed, 0x85, 0x33, 0xdb, 0x1c, 0x28, 0xac, 0xe4,\n                                  0x18, 0xe9, 0x00, 0x6a, 0xb2, 0x9c, 0x17, 0x41,\n                                  0x7d, 0x60, 0xeb, 0xe6, 0xcd, 0x90, 0xbf, 0x0a };\n\n    uint8_t exp_impl_iv[12] = { 0x86, 0x89, 0x0a, 0xab, 0xf0, 0x32,\n                                0xcb, 0x59, 0xf4, 0xcf, 0xa3, 0x4e };\n\n    assert_memory_equal(key_parameters.cipher, exp_cipherkey, sizeof(exp_cipherkey));\n    assert_memory_equal(key_parameters.hmac, exp_impl_iv, sizeof(exp_impl_iv));\n}\n\nint\nmain(void)\n{\n    uint16_t prestate_num13 = 13;\n    uint16_t prestate_num16 = 16;\n    uint16_t prestate_num32 = 32;\n\n    openvpn_unit_test_setup();\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(crypto_pem_encode_decode_loopback),\n        cmocka_unit_test(crypto_translate_cipher_names),\n        cmocka_unit_test(crypto_test_tls_prf),\n        cmocka_unit_test(crypto_test_hmac),\n        cmocka_unit_test(test_occ_mtu_calculation),\n        cmocka_unit_test(test_mssfix_mtu_calculation),\n        cmocka_unit_test(crypto_test_aead_limits),\n        cmocka_unit_test(crypto_test_hkdf_expand_testa1),\n        cmocka_unit_test(crypto_test_hkdf_expand_testa2),\n        cmocka_unit_test(crypto_test_hkdf_expand_testa3),\n        cmocka_unit_test(crypto_test_hkdf_expand_test_ovpn),\n        cmocka_unit_test(crypto_test_ovpn_label_expand),\n        cmocka_unit_test(crypto_test_ovpn_expand_openssl3),\n        cmocka_unit_test_prestate_setup_teardown(crypto_test_epoch_key_generation,\n                                                 crypto_test_epoch_setup,\n                                                 crypto_test_epoch_teardown, &prestate_num16),\n        cmocka_unit_test_prestate_setup_teardown(crypto_test_epoch_key_rotation,\n                                                 crypto_test_epoch_setup,\n                                                 crypto_test_epoch_teardown, &prestate_num13),\n        cmocka_unit_test_prestate_setup_teardown(crypto_test_epoch_key_receive_lookup,\n                                                 crypto_test_epoch_setup,\n                                                 crypto_test_epoch_teardown, &prestate_num13),\n        cmocka_unit_test_prestate_setup_teardown(crypto_test_epoch_key_overflow,\n                                                 crypto_test_epoch_setup,\n                                                 crypto_test_epoch_teardown, &prestate_num32),\n        cmocka_unit_test_prestate_setup_teardown(crypto_test_epoch_edge,\n                                                 crypto_test_epoch_setup,\n                                                 crypto_test_epoch_teardown, &prestate_num13),\n        cmocka_unit_test(epoch_test_derive_data_key)\n    };\n\n    return cmocka_run_group_tests_name(\"crypto tests\", tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_cryptoapi.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2023-2026 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by the\n *  Free Software Foundation, either version 2 of the License,\n *  or (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n#include \"manage.h\"\n#include \"integer.h\"\n#include \"xkey_common.h\"\n#include \"cert_data.h\"\n\n#if defined(HAVE_XKEY_PROVIDER) && defined(ENABLE_CRYPTOAPI)\n#include <setjmp.h>\n#include <cmocka.h>\n#include <openssl/bio.h>\n#include <openssl/pem.h>\n#include <openssl/core_names.h>\n#include <openssl/evp.h>\n#include <openssl/pkcs12.h>\n#include \"test_common.h\"\n\n#include <cryptoapi.h>\n#include <cryptoapi.c>         /* pull-in the whole file to test static functions */\n\nstruct management *management; /* global */\nstatic OSSL_PROVIDER *prov[2];\n\n/* mock a management function that xkey_provider needs */\nchar *\nmanagement_query_pk_sig(struct management *man, const char *b64_data, const char *algorithm)\n{\n    (void)man;\n    (void)b64_data;\n    (void)algorithm;\n    return NULL;\n}\n\n/* replacement for crypto_print_openssl_errors() */\nvoid\ncrypto_print_openssl_errors(const unsigned int flags)\n{\n    unsigned long e;\n    while ((e = ERR_get_error()))\n    {\n        msg(flags, \"OpenSSL error %lu: %s\", e, ERR_error_string(e, NULL));\n    }\n}\n\n/* tls_libctx is defined in ssl_openssl.c which we do not want to compile in */\nOSSL_LIB_CTX *tls_libctx;\n\n#ifndef _countof\n#define _countof(x) sizeof((x)) / sizeof(*(x))\n#endif\n\n/* test data */\nstatic const uint8_t test_hash[] = { 0x77, 0x38, 0x65, 0x00, 0x1e, 0x96, 0x48, 0xc6, 0x57, 0x0b,\n                                     0xae, 0xc0, 0xb7, 0x96, 0xf9, 0x66, 0x4d, 0x5f, 0xd0, 0xb7 };\n\n/* valid test strings to test with and without embedded and trailing spaces */\nstatic const char *valid_str[] = {\n    \"773865001e9648c6570baec0b796f9664d5fd0b7\",\n    \" 77 386500 1e 96 48 c6570b aec0b7   96f9664d5f  d0 b7\",\n    \"   773865001e9648c6570baec0b796f9664d5fd0b7  \",\n};\n\n/* some invalid strings to test */\nstatic const char *invalid_str[] = {\n    \"773 865001e9648c6570baec0b796f9664d5fd0b7\",  /* space within byte */\n    \"77:38:65001e9648c6570baec0b796f9664d5fd0b7\", /* invalid separator */\n    \"7738x5001e9648c6570baec0b796f9664d5fd0b7\",   /* non hex character */\n};\n\n/* Test certificate database: data for cert1, cert2 .. key1, key2 etc.\n * are stashed away in cert_data.h\n */\nstatic struct test_cert\n{\n    const char *const cert;          /* certificate as PEM */\n    const char *const key;           /* key as unencrypted PEM */\n    const char *const cname;         /* common-name */\n    const char *const issuer;        /* issuer common-name */\n    const char *const friendly_name; /* identifies certs loaded to the store -- keep unique */\n    const char *hash;                /* SHA1 fingerprint */\n    int valid;                       /* nonzero if certificate has not expired */\n} certs[5];\n\nstatic bool certs_loaded;\nstatic HCERTSTORE user_store;\n\n/* Fill-in certs[] array */\nvoid\ninit_cert_data(void)\n{\n    struct test_cert certs_local[] = {\n        { cert1, key1, cname1, \"OVPN TEST CA1\", \"OVPN Test Cert 1\", hash1, 1 },\n        { cert2, key2, cname2, \"OVPN TEST CA2\", \"OVPN Test Cert 2\", hash2, 1 },\n        { cert3, key3, cname3, \"OVPN TEST CA1\", \"OVPN Test Cert 3\", hash3, 1 },\n        { cert4, key4, cname4, \"OVPN TEST CA2\", \"OVPN Test Cert 4\", hash4, 0 },\n        { 0 }\n    };\n    assert_int_equal(sizeof(certs_local), sizeof(certs));\n    memcpy(certs, certs_local, sizeof(certs_local));\n}\n\n/* Lookup a certificate in our certificate/key db */\nstatic struct test_cert *\nlookup_cert(const char *friendly_name)\n{\n    struct test_cert *c = certs;\n    while (c->cert && strcmp(c->friendly_name, friendly_name))\n    {\n        c++;\n    }\n    return c->cert ? c : NULL;\n}\n\n/* import sample certificates into windows cert store */\nstatic void\nimport_certs(void **state)\n{\n    (void)state;\n    if (certs_loaded)\n    {\n        return;\n    }\n    init_cert_data();\n    user_store =\n        CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0,\n                      CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_OPEN_EXISTING_FLAG, L\"MY\");\n    assert_non_null(user_store);\n    for (struct test_cert *c = certs; c->cert; c++)\n    {\n        /* Convert PEM cert & key to pkcs12 and import */\n        const char *pass = \"opensesame\";      /* some password */\n        const wchar_t *wpass = L\"opensesame\"; /* same as a wide string */\n\n        X509 *x509 = NULL;\n        EVP_PKEY *pkey = NULL;\n\n        BIO *buf = BIO_new_mem_buf(c->cert, -1);\n        if (buf)\n        {\n            x509 = PEM_read_bio_X509(buf, NULL, NULL, NULL);\n        }\n        BIO_free(buf);\n\n        buf = BIO_new_mem_buf(c->key, -1);\n        if (buf)\n        {\n            pkey = PEM_read_bio_PrivateKey(buf, NULL, NULL, NULL);\n        }\n        BIO_free(buf);\n\n        if (!x509 || !pkey)\n        {\n            fail_msg(\"Failed to parse certificate/key data: <%s>\", c->friendly_name);\n            return;\n        }\n\n        PKCS12 *p12 = PKCS12_create(pass, c->friendly_name, pkey, x509, NULL, 0, 0, 0, 0, 0);\n        X509_free(x509);\n        EVP_PKEY_free(pkey);\n        if (!p12)\n        {\n            fail_msg(\"Failed to convert to PKCS12: <%s>\", c->friendly_name);\n            return;\n        }\n\n        CRYPT_DATA_BLOB blob = { .cbData = 0, .pbData = NULL };\n        int len = i2d_PKCS12(p12, &blob.pbData); /* pbData will be allocated by OpenSSL */\n        if (len <= 0)\n        {\n            fail_msg(\"Failed to DER encode PKCS12: <%s>\", c->friendly_name);\n            return;\n        }\n        blob.cbData = len;\n\n        DWORD flags = PKCS12_ALLOW_OVERWRITE_KEY | PKCS12_ALWAYS_CNG_KSP;\n        HCERTSTORE tmp_store = PFXImportCertStore(&blob, wpass, flags);\n        PKCS12_free(p12);\n        OPENSSL_free(blob.pbData);\n\n        assert_non_null(tmp_store);\n\n        /* The cert and key get imported into a temp store. We have to move it to\n         * user's store to accumulate all certs in one place and use them for tests.\n         * It seems there is no API to directly import a p12 blob into an existing store.\n         * Nothing in Windows is ever easy.\n         */\n\n        const CERT_CONTEXT *ctx = CertEnumCertificatesInStore(tmp_store, NULL);\n        assert_non_null(ctx);\n        bool added = CertAddCertificateContextToStore(user_store, ctx,\n                                                      CERT_STORE_ADD_REPLACE_EXISTING, NULL);\n        assert_true(added);\n\n        CertFreeCertificateContext(ctx);\n        CertCloseStore(tmp_store, 0);\n    }\n    certs_loaded = true;\n}\n\nstatic int\ncleanup(void **state)\n{\n    (void)state;\n    struct gc_arena gc = gc_new();\n    if (user_store) /* delete all certs we imported */\n    {\n        const CERT_CONTEXT *ctx = NULL;\n        while ((ctx = CertEnumCertificatesInStore(user_store, ctx)))\n        {\n            char *friendly_name = get_cert_name(ctx, &gc);\n            if (!lookup_cert(friendly_name)) /* not our cert */\n            {\n                continue;\n            }\n\n            /* create a dup context to not destroy the state of loop iterator */\n            const CERT_CONTEXT *ctx_dup = CertDuplicateCertificateContext(ctx);\n            if (ctx_dup)\n            {\n                CertDeleteCertificateFromStore(ctx_dup);\n                /* the above also releases ctx_dup */\n            }\n        }\n        CertCloseStore(user_store, 0);\n    }\n    user_store = NULL;\n    certs_loaded = false;\n    gc_free(&gc);\n    return 0;\n}\n\nstatic void\ntest_find_cert_bythumb(void **state)\n{\n    (void)state;\n    char select_string[64];\n    struct gc_arena gc = gc_new();\n    const CERT_CONTEXT *ctx;\n\n    import_certs(state); /* a no-op if already imported */\n    assert_non_null(user_store);\n\n    for (struct test_cert *c = certs; c->cert; c++)\n    {\n        snprintf(select_string, sizeof(select_string), \"THUMB:%s\", c->hash);\n        ctx = find_certificate_in_store(select_string, user_store);\n        if (ctx)\n        {\n            /* check we got the right certificate and is valid */\n            assert_int_equal(c->valid, 1);\n            char *friendly_name = get_cert_name(ctx, &gc);\n            assert_string_equal(c->friendly_name, friendly_name);\n            CertFreeCertificateContext(ctx);\n        }\n        else\n        {\n            /* find should fail only if the certificate has expired */\n            assert_int_equal(c->valid, 0);\n        }\n    }\n\n    gc_free(&gc);\n}\n\nstatic void\ntest_find_cert_byname(void **state)\n{\n    (void)state;\n    char select_string[64];\n    struct gc_arena gc = gc_new();\n    const CERT_CONTEXT *ctx;\n\n    import_certs(state); /* a no-op if already imported */\n    assert_non_null(user_store);\n\n    for (struct test_cert *c = certs; c->cert; c++)\n    {\n        snprintf(select_string, sizeof(select_string), \"SUBJ:%s\", c->cname);\n        ctx = find_certificate_in_store(select_string, user_store);\n        /* In this case we expect a successful return as there is at least one valid\n         * cert that matches the common name. But the returned cert may not exactly match\n         * c->cert as multiple certs with same common names exist in the db. We check that\n         * the return cert is one from our db, has a matching common name and is valid.\n         */\n        assert_non_null(ctx);\n\n        char *friendly_name = get_cert_name(ctx, &gc);\n        struct test_cert *found = lookup_cert(friendly_name);\n        assert_non_null(found);\n        assert_string_equal(found->cname, c->cname);\n        assert_int_equal(found->valid, 1);\n        CertFreeCertificateContext(ctx);\n    }\n\n    gc_free(&gc);\n}\n\nstatic void\ntest_find_cert_byissuer(void **state)\n{\n    (void)state;\n    char select_string[64];\n    struct gc_arena gc = gc_new();\n    const CERT_CONTEXT *ctx;\n\n    import_certs(state); /* a no-op if already imported */\n    assert_non_null(user_store);\n\n    for (struct test_cert *c = certs; c->cert; c++)\n    {\n        snprintf(select_string, sizeof(select_string), \"ISSUER:%s\", c->issuer);\n        ctx = find_certificate_in_store(select_string, user_store);\n        /* In this case we expect a successful return as there is at least one valid\n         * cert that matches the issuer. But the returned cert may not exactly match\n         * c->cert as multiple certs with same issuer exist in the db. We check that\n         * the returned cert is one from our db, has a matching issuer name and is valid.\n         */\n        assert_non_null(ctx);\n\n        char *friendly_name = get_cert_name(ctx, &gc);\n        struct test_cert *found = lookup_cert(friendly_name);\n        assert_non_null(found);\n        assert_string_equal(found->issuer, c->issuer);\n        assert_int_equal(found->valid, 1);\n        CertFreeCertificateContext(ctx);\n    }\n\n    gc_free(&gc);\n}\n\nstatic int\nsetup_xkey_provider(void **state)\n{\n    (void)state;\n    /* Initialize providers in a way matching what OpenVPN core does */\n    tls_libctx = OSSL_LIB_CTX_new();\n    prov[0] = OSSL_PROVIDER_load(tls_libctx, \"default\");\n    OSSL_PROVIDER_add_builtin(tls_libctx, \"ovpn.xkey\", xkey_provider_init);\n    prov[1] = OSSL_PROVIDER_load(tls_libctx, \"ovpn.xkey\");\n\n    /* set default propq as we do in ssl_openssl.c */\n    EVP_set_default_properties(tls_libctx, \"?provider!=ovpn.xkey\");\n    return 0;\n}\n\nstatic int\nteardown_xkey_provider(void **state)\n{\n    (void)state;\n    for (size_t i = 0; i < _countof(prov); i++)\n    {\n        if (prov[i])\n        {\n            OSSL_PROVIDER_unload(prov[i]);\n            prov[i] = NULL;\n        }\n    }\n    OSSL_LIB_CTX_free(tls_libctx);\n    tls_libctx = NULL;\n    return 0;\n}\n\nint digest_sign_verify(EVP_PKEY *privkey, EVP_PKEY *pubkey);\n\n/* Load sample certificates & keys, sign a test message using\n * them and verify the signature.\n */\nvoid\ntest_cryptoapi_sign(void **state)\n{\n    (void)state;\n    char select_string[64];\n    X509 *x509 = NULL;\n    EVP_PKEY *privkey = NULL;\n\n    import_certs(state); /* a no-op if already imported */\n    assert_true(certs_loaded);\n\n    for (struct test_cert *c = certs; c->cert; c++)\n    {\n        if (c->valid == 0)\n        {\n            continue;\n        }\n        snprintf(select_string, sizeof(select_string), \"THUMB:%s\", c->hash);\n        if (Load_CryptoAPI_certificate(select_string, &x509, &privkey) != 1)\n        {\n            fail_msg(\"Load_CryptoAPI_certificate failed: <%s>\", c->friendly_name);\n            return;\n        }\n        EVP_PKEY *pubkey = X509_get0_pubkey(x509);\n        assert_non_null(pubkey);\n        assert_int_equal(digest_sign_verify(privkey, pubkey), 1);\n        X509_free(x509);\n        EVP_PKEY_free(privkey);\n    }\n}\n\n/* Test that SSL_CTX_use_Cryptoapi_certificate() sets a matching certificate\n * and key in ssl_ctx.\n */\nvoid\ntest_ssl_ctx_use_cryptoapicert(void **state)\n{\n    (void)state;\n    char select_string[64];\n\n    import_certs(state); /* a no-op if already imported */\n    assert_true(certs_loaded);\n\n    for (struct test_cert *c = certs; c->cert; c++)\n    {\n        if (c->valid == 0)\n        {\n            continue;\n        }\n        SSL_CTX *ssl_ctx = SSL_CTX_new_ex(tls_libctx, NULL, SSLv23_client_method());\n        assert_non_null(ssl_ctx);\n\n        snprintf(select_string, sizeof(select_string), \"THUMB:%s\", c->hash);\n        if (!SSL_CTX_use_CryptoAPI_certificate(ssl_ctx, select_string))\n        {\n            fail_msg(\"SSL_CTX_use_CryptoAPI_certificate failed: <%s>\", c->friendly_name);\n            return;\n        }\n        /* Use OpenSSL to check that the cert and private key in ssl_ctx \"match\" */\n        if (!SSL_CTX_check_private_key(ssl_ctx))\n        {\n            fail_msg(\"Certificate and private key in ssl_ctx do not match for <%s>\",\n                     c->friendly_name);\n            return;\n        }\n\n        SSL_CTX_free(ssl_ctx);\n    }\n}\n\nstatic void\ntest_parse_hexstring(void **state)\n{\n    unsigned char hash[255];\n    (void)state;\n\n    for (size_t i = 0; i < _countof(valid_str); i++)\n    {\n        DWORD len = parse_hexstring(valid_str[i], hash, _countof(hash));\n        assert_int_equal(len, sizeof(test_hash));\n        assert_memory_equal(hash, test_hash, sizeof(test_hash));\n        memset(hash, 0, _countof(hash));\n    }\n\n    for (size_t i = 0; i < _countof(invalid_str); i++)\n    {\n        DWORD len = parse_hexstring(invalid_str[i], hash, _countof(hash));\n        assert_int_equal(len, 0);\n    }\n}\n\nint\nmain(void)\n{\n    openvpn_unit_test_setup();\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(test_parse_hexstring),\n        cmocka_unit_test(import_certs),\n        cmocka_unit_test(test_find_cert_bythumb),\n        cmocka_unit_test(test_find_cert_byname),\n        cmocka_unit_test(test_find_cert_byissuer),\n        cmocka_unit_test_setup_teardown(test_cryptoapi_sign, setup_xkey_provider,\n                                        teardown_xkey_provider),\n        cmocka_unit_test_setup_teardown(test_ssl_ctx_use_cryptoapicert, setup_xkey_provider,\n                                        teardown_xkey_provider),\n    };\n\n    int ret = cmocka_run_group_tests_name(\"cryptoapi tests\", tests, NULL, cleanup);\n\n    return ret;\n}\n\n#else  /* ifdef HAVE_XKEY_PROVIDER */\n\nint\nmain(void)\n{\n    return 0;\n}\n\n#endif /* ifdef HAVE_XKEY_PROVIDER */\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_dhcp.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2025-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <setjmp.h>\n#include <cmocka.h>\n\n#include \"test_common.h\"\n#include \"mock_msg.h\"\n\n#include \"dhcp.c\"\n\nuint16_t\nip_checksum(const sa_family_t af, const uint8_t *payload, const int len_payload,\n            const uint8_t *src_addr, const uint8_t *dest_addr, const int proto)\n{\n    return 0;\n}\nconst char *\nprint_in_addr_t(in_addr_t addr, unsigned int flags, struct gc_arena *gc)\n{\n    return \"dummy\";\n}\n\nstatic void\ntest_write_dhcp_search_str(void **state)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer out_buf = alloc_buf_gc(512, &gc);\n    struct buffer clear_buf = alloc_buf_gc(512, &gc);\n    buf_clear(&clear_buf);\n    bool error = false;\n\n#define LONGDOMAIN \"a-reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaally-long-domain\"\n    const char *search_list[] = {\n        \"openvpn.net\",\n        \"openvpn.org\",\n        LONGDOMAIN,\n        \"subdomain.\" LONGDOMAIN \".top123\",  /* maximum length */\n        \"subdomain-\" LONGDOMAIN \"-top123\",  /* maximum length */\n        \"subdomain.\" LONGDOMAIN \".top1234\", /* too long */\n        \"sub..tld\",                         /* invalid */\n    };\n    const unsigned char output_1[28] = \"\\x77\\x1a\\x07openvpn\\x03net\\x00\\x07openvpn\\x03org\";\n    buf_clear(&out_buf);\n    write_dhcp_search_str(&out_buf, DHCP_DOMAIN_SEARCH, search_list, 2, &error);\n    assert_memory_equal(BPTR(&out_buf), output_1, sizeof(output_1));\n    assert_false(error);\n\n    /* buf too small */\n    struct buffer small_buf = alloc_buf_gc(sizeof(output_1) - 1, &gc);\n    buf_clear(&small_buf);\n    write_dhcp_search_str(&small_buf, DHCP_DOMAIN_SEARCH, search_list, 2, &error);\n    assert_memory_equal(BPTR(&small_buf), BPTR(&clear_buf), buf_forward_capacity_total(&small_buf));\n    assert_true(error);\n    error = false;\n\n    const unsigned char output_2[0xEC + 3 + 1] = \"\\x77\\xEE\\xEC\" LONGDOMAIN;\n    buf_clear(&out_buf);\n    write_dhcp_search_str(&out_buf, DHCP_DOMAIN_SEARCH, search_list + 2, 1, &error);\n    assert_memory_equal(BPTR(&out_buf), output_2, sizeof(output_2));\n    assert_false(error);\n\n    const unsigned char output_3[0xEC + 3 + 10 + 7 + 1] = \"\\x77\\xFF\\x09subdomain\\xEC\" LONGDOMAIN \"\\x06top123\";\n    buf_clear(&out_buf);\n    write_dhcp_search_str(&out_buf, DHCP_DOMAIN_SEARCH, search_list + 3, 1, &error);\n    assert_memory_equal(BPTR(&out_buf), output_3, sizeof(output_3));\n    assert_false(error);\n\n    const unsigned char output_4[0xEC + 3 + 10 + 7 + 1] = \"\\x77\\xFF\\xFDsubdomain-\" LONGDOMAIN \"-top123\";\n    buf_clear(&out_buf);\n    write_dhcp_search_str(&out_buf, DHCP_DOMAIN_SEARCH, search_list + 4, 1, &error);\n    assert_memory_equal(BPTR(&out_buf), output_4, sizeof(output_4));\n    assert_false(error);\n\n    buf_clear(&out_buf);\n    write_dhcp_search_str(&out_buf, DHCP_DOMAIN_SEARCH, search_list + 5, 1, &error);\n    assert_memory_equal(BPTR(&out_buf), BPTR(&clear_buf), buf_forward_capacity_total(&clear_buf));\n    assert_true(error);\n    error = false;\n\n    buf_clear(&out_buf);\n    write_dhcp_search_str(&out_buf, DHCP_DOMAIN_SEARCH, search_list, 3, &error);\n    assert_memory_equal(BPTR(&out_buf), BPTR(&clear_buf), buf_forward_capacity_total(&clear_buf));\n    assert_true(error);\n    error = false;\n\n    /* FIXME: should probably throw an error instead adding that \\x00 ? */\n    const char output_5[12] = \"\\x77\\x0a\\x03sub\\x00\\x03tld\";\n    buf_clear(&out_buf);\n    write_dhcp_search_str(&out_buf, DHCP_DOMAIN_SEARCH, search_list + 6, 1, &error);\n    assert_memory_equal(BPTR(&out_buf), output_5, sizeof(output_5));\n    assert_false(error);\n\n    gc_free(&gc);\n}\n\nint\nmain(void)\n{\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(test_write_dhcp_search_str),\n    };\n\n    return cmocka_run_group_tests_name(\"dhcp\", tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_mbuf.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2025 OpenVPN Inc. <sales@openvpn.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <setjmp.h>\n#include <cmocka.h>\n\n#include \"buffer.h\"\n#include \"multi.h\"\n#include \"mbuf.h\"\n#include \"test_common.h\"\n\nstatic void\ntest_mbuf_init(void **state)\n{\n    struct mbuf_set *ms = mbuf_init(256);\n    assert_int_equal(ms->capacity, 256);\n    assert_false(mbuf_defined(ms));\n    assert_non_null(ms->array);\n    mbuf_free(ms);\n\n    ms = mbuf_init(257);\n    assert_int_equal(ms->capacity, 512);\n    mbuf_free(ms);\n\n#ifdef UNIT_TEST_ALLOW_BIG_ALLOC /* allocates up to 2GB of memory */\n    ms = mbuf_init(MBUF_SIZE_MAX);\n    assert_int_equal(ms->capacity, MBUF_SIZE_MAX);\n    mbuf_free(ms);\n\n/* NOTE: expect_assert_failure does not seem to work with MSVC */\n#ifndef _MSC_VER\n    expect_assert_failure(mbuf_init(MBUF_SIZE_MAX + 1));\n#endif\n#endif\n}\n\nstatic void\ntest_mbuf_add_remove(void **state)\n{\n    struct mbuf_set *ms = mbuf_init(4);\n    assert_int_equal(ms->capacity, 4);\n    assert_false(mbuf_defined(ms));\n    assert_non_null(ms->array);\n\n    /* instances */\n    struct multi_instance mi = { 0 };\n    struct multi_instance mi2 = { 0 };\n    /* buffers */\n    struct buffer buf = alloc_buf(16);\n    struct mbuf_buffer *mbuf_buf = mbuf_alloc_buf(&buf);\n    assert_int_equal(mbuf_buf->refcount, 1);\n    struct mbuf_buffer *mbuf_buf2 = mbuf_alloc_buf(&buf);\n    assert_int_equal(mbuf_buf2->refcount, 1);\n    free_buf(&buf);\n    /* items */\n    struct mbuf_item mb_item = { .buffer = mbuf_buf, .instance = &mi };\n    struct mbuf_item mb_item2 = { .buffer = mbuf_buf2, .instance = &mi2 };\n\n    mbuf_add_item(ms, &mb_item);\n    assert_int_equal(mbuf_buf->refcount, 2);\n    assert_int_equal(mbuf_buf2->refcount, 1);\n    assert_int_equal(mbuf_len(ms), 1);\n    assert_int_equal(mbuf_maximum_queued(ms), 1);\n    assert_int_equal(ms->head, 0);\n    assert_ptr_equal(mbuf_peek(ms), &mi);\n\n    mbuf_add_item(ms, &mb_item2);\n    assert_int_equal(mbuf_buf->refcount, 2);\n    assert_int_equal(mbuf_buf2->refcount, 2);\n    assert_int_equal(mbuf_len(ms), 2);\n    assert_int_equal(mbuf_maximum_queued(ms), 2);\n    assert_int_equal(ms->head, 0);\n    assert_ptr_equal(mbuf_peek(ms), &mi);\n\n    mbuf_add_item(ms, &mb_item2);\n    assert_int_equal(mbuf_buf->refcount, 2);\n    assert_int_equal(mbuf_buf2->refcount, 3);\n    assert_int_equal(mbuf_len(ms), 3);\n    assert_int_equal(mbuf_maximum_queued(ms), 3);\n    assert_int_equal(ms->head, 0);\n    assert_ptr_equal(mbuf_peek(ms), &mi);\n\n    mbuf_add_item(ms, &mb_item2);\n    mbuf_add_item(ms, &mb_item2); /* overflow, first item gets removed */\n    assert_int_equal(mbuf_buf->refcount, 1);\n    assert_int_equal(mbuf_buf2->refcount, 5);\n    assert_int_equal(mbuf_len(ms), 4);\n    assert_int_equal(mbuf_maximum_queued(ms), 4);\n    assert_int_equal(ms->head, 1);\n    assert_ptr_equal(mbuf_peek(ms), &mi2);\n\n    mbuf_add_item(ms, &mb_item);\n    assert_int_equal(mbuf_buf->refcount, 2);\n    assert_int_equal(mbuf_buf2->refcount, 4);\n    assert_int_equal(mbuf_len(ms), 4);\n    assert_int_equal(mbuf_maximum_queued(ms), 4);\n    assert_int_equal(ms->head, 2);\n    assert_ptr_equal(mbuf_peek(ms), &mi2);\n\n    struct mbuf_item out_item;\n    assert_true(mbuf_extract_item(ms, &out_item));\n    assert_ptr_equal(out_item.instance, mb_item2.instance);\n    assert_int_equal(mbuf_buf->refcount, 2);\n    assert_int_equal(mbuf_buf2->refcount, 4);\n    assert_int_equal(mbuf_len(ms), 3);\n    assert_int_equal(mbuf_maximum_queued(ms), 4);\n    assert_int_equal(ms->head, 3);\n    assert_ptr_equal(mbuf_peek(ms), &mi2);\n    mbuf_free_buf(out_item.buffer);\n\n    mbuf_dereference_instance(ms, &mi2);\n    assert_int_equal(mbuf_buf->refcount, 2);\n    assert_int_equal(mbuf_buf2->refcount, 1);\n    assert_int_equal(mbuf_len(ms), 3);\n    assert_int_equal(mbuf_maximum_queued(ms), 4);\n    assert_int_equal(ms->head, 3);\n    assert_ptr_equal(mbuf_peek(ms), &mi);\n\n    mbuf_free(ms);\n    assert_int_equal(mbuf_buf->refcount, 1);\n    mbuf_free_buf(mbuf_buf);\n    assert_int_equal(mbuf_buf2->refcount, 1);\n    mbuf_free_buf(mbuf_buf2);\n}\n\nint\nmain(void)\n{\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(test_mbuf_init),\n        cmocka_unit_test(test_mbuf_add_remove),\n    };\n\n    return cmocka_run_group_tests_name(\"mbuf\", tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_misc.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2021-2026 Arne Schwabe <arne@rfc2549.org>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <setjmp.h>\n#include <cmocka.h>\n\n#include \"ssl_util.h\"\n#include \"options_util.h\"\n#include \"test_common.h\"\n#include \"list.h\"\n#include \"mock_msg.h\"\n\nstatic void\ntest_compat_lzo_string(void **state)\n{\n    struct gc_arena gc = gc_new();\n\n    const char *input =\n        \"V4,dev-type tun,link-mtu 1457,tun-mtu 1400,proto UDPv4,auth SHA1,keysize 128,key-method 2,tls-server\";\n\n    const char *output = options_string_compat_lzo(input, &gc);\n\n    assert_string_equal(\n        output,\n        \"V4,dev-type tun,link-mtu 1458,tun-mtu 1400,proto UDPv4,auth SHA1,keysize 128,key-method 2,tls-server,comp-lzo\");\n\n    /* This string is has a much too small link-mtu so we should fail on it\" */\n    input =\n        \"V4,dev-type tun,link-mtu 2,tun-mtu 1400,proto UDPv4,auth SHA1,keysize 128,key-method 2,tls-server\";\n\n    output = options_string_compat_lzo(input, &gc);\n\n    assert_string_equal(input, output);\n\n    /* not matching at all */\n    input = \"V4,dev-type tun\";\n    output = options_string_compat_lzo(input, &gc);\n\n    assert_string_equal(input, output);\n\n\n    input =\n        \"V4,dev-type tun,link-mtu 999,tun-mtu 1400,proto UDPv4,auth SHA1,keysize 128,key-method 2,tls-server\";\n    output = options_string_compat_lzo(input, &gc);\n\n    /* 999 -> 1000, 3 to 4 chars */\n    assert_string_equal(\n        output,\n        \"V4,dev-type tun,link-mtu 1000,tun-mtu 1400,proto UDPv4,auth SHA1,keysize 128,key-method 2,tls-server,comp-lzo\");\n\n    gc_free(&gc);\n}\n\nstatic void\ntest_auth_fail_temp_no_flags(void **state)\n{\n    struct options o;\n\n    const char *teststr = \"TEMP:There are no flags here [really not]\";\n\n    const char *msg = parse_auth_failed_temp(&o, teststr + strlen(\"TEMP\"));\n    assert_string_equal(msg, \"There are no flags here [really not]\");\n}\n\nstatic void\ntest_auth_fail_temp_flags(void **state)\n{\n    struct options o;\n\n    const char *teststr = \"[backoff 42,advance no]\";\n\n    const char *msg = parse_auth_failed_temp(&o, teststr);\n    assert_string_equal(msg, \"\");\n    assert_int_equal(o.server_backoff_time, 42);\n    assert_true(o.no_advance);\n}\n\nstatic void\ntest_auth_fail_temp_flags_msg(void **state)\n{\n    struct options o;\n\n    const char *teststr = \"[advance remote,backoff 77]:go round and round\";\n\n    const char *msg = parse_auth_failed_temp(&o, teststr);\n    assert_string_equal(msg, \"go round and round\");\n    assert_int_equal(o.server_backoff_time, 77);\n}\n\n\nstruct word\n{\n    const char *word;\n    int n;\n};\n\n\nstatic uint32_t\nword_hash_function(const void *key, uint32_t iv)\n{\n    const char *str = (const char *)key;\n    const uint32_t len = (uint32_t)strlen(str);\n    return hash_func((const uint8_t *)str, len, iv);\n}\n\nstatic bool\nword_compare_function(const void *key1, const void *key2)\n{\n    return strcmp((const char *)key1, (const char *)key2) == 0;\n}\n\nstatic uint32_t\nget_random(void)\n{\n    /* rand() is not very random, but it's C99 and this is just for testing */\n    return (uint32_t)rand();\n}\n\nstatic struct hash_element *\nhash_lookup_by_value(struct hash *hash, void *value)\n{\n    struct hash_iterator hi;\n    struct hash_element *he;\n    struct hash_element *ret = NULL;\n    hash_iterator_init(hash, &hi);\n\n    while ((he = hash_iterator_next(&hi)))\n    {\n        if (he->value == value)\n        {\n            ret = he;\n        }\n    }\n    hash_iterator_free(&hi);\n    return ret;\n}\n\nstatic void\ntest_list(void **state)\n{\n    /*\n     * Test the hash code by implementing a simple\n     * word frequency algorithm.\n     */\n\n    struct gc_arena gc = gc_new();\n    struct hash *hash = hash_init(10000, get_random(), word_hash_function, word_compare_function);\n    struct hash *nhash = hash_init(256, get_random(), word_hash_function, word_compare_function);\n\n    printf(\"hash_init n_buckets=%u mask=0x%08x\\n\", hash->n_buckets, hash->mask);\n\n    char wordfile[PATH_MAX] = { 0 };\n    openvpn_test_get_srcdir_dir(wordfile, PATH_MAX, \"/../../../COPYRIGHT.GPL\");\n\n    FILE *words = fopen(wordfile, \"r\");\n    assert_non_null(words);\n\n    int wordcount = 0;\n\n    /* parse words from file */\n    while (true)\n    {\n        char buf[256];\n        char wordbuf[256];\n\n        if (!fgets(buf, sizeof(buf), words))\n        {\n            break;\n        }\n\n        char c = 0;\n        int bi = 0, wbi = 0;\n\n        do\n        {\n            c = buf[bi++];\n            if (isalnum(c) || c == '_')\n            {\n                assert_true(wbi < (int)sizeof(wordbuf));\n                wordbuf[wbi++] = c;\n            }\n            else\n            {\n                if (wbi)\n                {\n                    wordcount++;\n\n                    ASSERT(wbi < (int)sizeof(wordbuf));\n                    wordbuf[wbi++] = '\\0';\n\n                    /* word is parsed from stdin */\n\n                    /* does it already exist in table? */\n                    struct word *w = (struct word *)hash_lookup(hash, wordbuf);\n\n                    if (w)\n                    {\n                        assert_string_equal(w->word, wordbuf);\n                        /* yes, increment count */\n                        ++w->n;\n                    }\n                    else\n                    {\n                        /* no, make a new object */\n                        ALLOC_OBJ_GC(w, struct word, &gc);\n                        w->word = string_alloc(wordbuf, &gc);\n                        w->n = 1;\n                        assert_true(hash_add(hash, w->word, w, false));\n                        assert_true(hash_add(nhash, w->word,\n                                             (void *)((ptr_type)(random() & 0x0F) + 1), false));\n                    }\n                }\n                wbi = 0;\n            }\n        } while (c);\n    }\n\n    assert_int_equal(wordcount, 2971);\n\n    /* remove some words from the table */\n    {\n        assert_true(hash_remove(hash, \"DEFECTIVE\"));\n        assert_false(hash_remove(hash, \"false\"));\n    }\n\n    /* output contents of hash table */\n    {\n        uint32_t inc = 0;\n        int count = 0;\n\n        for (uint32_t base = 0; base < hash_n_buckets(hash); base += inc)\n        {\n            struct hash_iterator hi;\n            struct hash_element *he;\n            inc = (get_random() % 3) + 1;\n            hash_iterator_init_range(hash, &hi, base, base + inc);\n\n            while ((he = hash_iterator_next(&hi)))\n            {\n                struct word *w = (struct word *)he->value;\n                /*printf(\"%6d '%s'\\n\", w->n, w->word); */\n                ++count;\n                /* check a few words to match prior results */\n                if (!strcmp(w->word, \"is\"))\n                {\n                    assert_int_equal(w->n, 49);\n                }\n                else if (!strcmp(w->word, \"redistribute\"))\n                {\n                    assert_int_equal(w->n, 5);\n                }\n                else if (!strcmp(w->word, \"circumstances\"))\n                {\n                    assert_int_equal(w->n, 1);\n                }\n                else if (!strcmp(w->word, \"so\"))\n                {\n                    assert_int_equal(w->n, 8);\n                }\n                else if (!strcmp(w->word, \"BECAUSE\"))\n                {\n                    assert_int_equal(w->n, 1);\n                }\n            }\n\n            hash_iterator_free(&hi);\n        }\n        assert_int_equal(count, hash_n_elements(hash));\n    }\n\n    /* test hash_remove_by_value function */\n    {\n        for (ptr_type i = 1; i <= 16; ++i)\n        {\n            struct hash_element *item = hash_lookup_by_value(nhash, (void *)i);\n            hash_remove_by_value(nhash, (void *)i);\n            /* check item got removed if it was present before */\n            if (item)\n            {\n                assert_null(hash_lookup_by_value(nhash, (void *)i));\n            }\n        }\n    }\n\n    hash_free(hash);\n    hash_free(nhash);\n    gc_free(&gc);\n}\n\nstatic void\ntest_atoi_variants(void **state)\n{\n    assert_true(valid_integer(\"1234\", true));\n    assert_true(valid_integer(\"1234\", false));\n    assert_true(valid_integer(\"0\", false));\n    assert_true(valid_integer(\"0\", true));\n    assert_true(valid_integer(\"-777\", false));\n    assert_false(valid_integer(\"-777\", true));\n\n    assert_false(valid_integer(\"-777foo\", false));\n    assert_false(valid_integer(\"-777foo\", true));\n\n    assert_false(valid_integer(\"foo777\", true));\n    assert_false(valid_integer(\"foo777\", false));\n\n    /* 2**31 + 5 , just outside of signed int range */\n    assert_false(valid_integer(\"2147483653\", true));\n    assert_false(valid_integer(\"2147483653\", false));\n    assert_false(valid_integer(\"-2147483653\", true));\n    assert_false(valid_integer(\"-2147483653\", false));\n\n\n    msglvl_t msglevel = D_LOW;\n    msglvl_t saved_log_level = mock_get_debug_level();\n    mock_set_debug_level(D_LOW);\n\n    /* check happy path */\n    assert_int_equal(positive_atoi(\"1234\", msglevel), 1234);\n    assert_int_equal(positive_atoi(\"0\", msglevel), 0);\n\n    assert_int_equal(atoi_warn(\"1234\", msglevel), 1234);\n    assert_int_equal(atoi_warn(\"0\", msglevel), 0);\n    assert_int_equal(atoi_warn(\"-1194\", msglevel), -1194);\n\n    int parameter = 0;\n    assert_true(atoi_constrained(\"1234\", &parameter, \"test\", 0, INT_MAX, msglevel));\n    assert_int_equal(parameter, 1234);\n    assert_true(atoi_constrained(\"0\", &parameter, \"test\", -1, 0, msglevel));\n    assert_int_equal(parameter, 0);\n    assert_true(atoi_constrained(\"-1194\", &parameter, \"test\", INT_MIN, INT_MAX, msglevel));\n    assert_int_equal(parameter, -1194);\n\n    int64_t parameter64 = 0;\n    assert_true(positive_atoll(\"1234\", &parameter64, \"test\", msglevel));\n    assert_int_equal(parameter64, 1234);\n    assert_true(positive_atoll(\"0\", &parameter64, \"test\", msglevel));\n    assert_int_equal(parameter64, 0);\n    assert_true(positive_atoll(\"2147483653\", &parameter64, \"test\", msglevel));\n    assert_int_equal(parameter64, 2147483653);\n    /* overflow gets capped to LLONG_MAX */\n    assert_true(positive_atoll(\"9223372036854775810\", &parameter64, \"test\", msglevel));\n    assert_int_equal(parameter64, 9223372036854775807);\n\n    CLEAR(mock_msg_buf);\n    assert_int_equal(positive_atoi(\"-1234\", msglevel), 0);\n    assert_string_equal(mock_msg_buf, \"Cannot parse argument '-1234' as non-negative integer\");\n\n    /* 2**31 + 5 , just outside of signed int range */\n    CLEAR(mock_msg_buf);\n    assert_int_equal(positive_atoi(\"2147483653\", msglevel), 0);\n    assert_string_equal(mock_msg_buf, \"Cannot parse argument '2147483653' as non-negative integer\");\n\n    CLEAR(mock_msg_buf);\n    assert_int_equal(atoi_warn(\"2147483653\", msglevel), 0);\n    assert_string_equal(mock_msg_buf, \"Cannot parse argument '2147483653' as integer\");\n\n    CLEAR(mock_msg_buf);\n    parameter = -42;\n    assert_false(atoi_constrained(\"2147483653\", &parameter, \"test\", 0, INT_MAX, msglevel));\n    assert_string_equal(mock_msg_buf, \"test: Cannot parse '2147483653' as integer\");\n    assert_int_equal(parameter, -42);\n\n    CLEAR(mock_msg_buf);\n    assert_int_equal(positive_atoi(\"foo77\", msglevel), 0);\n    assert_string_equal(mock_msg_buf, \"Cannot parse argument 'foo77' as non-negative integer\");\n\n    CLEAR(mock_msg_buf);\n    assert_int_equal(positive_atoi(\"77foo\", msglevel), 0);\n    assert_string_equal(mock_msg_buf, \"Cannot parse argument '77foo' as non-negative integer\");\n\n    CLEAR(mock_msg_buf);\n    parameter = -42;\n    assert_false(atoi_constrained(\"foo77\", &parameter, \"test\", 0, INT_MAX, msglevel));\n    assert_string_equal(mock_msg_buf, \"test: Cannot parse 'foo77' as integer\");\n    assert_int_equal(parameter, -42);\n\n    CLEAR(mock_msg_buf);\n    parameter = -42;\n    assert_false(atoi_constrained(\"77foo\", &parameter, \"test\", 0, INT_MAX, msglevel));\n    assert_string_equal(mock_msg_buf, \"test: Cannot parse '77foo' as integer\");\n    assert_int_equal(parameter, -42);\n\n    CLEAR(mock_msg_buf);\n    assert_int_equal(atoi_warn(\"foo77\", msglevel), 0);\n    assert_string_equal(mock_msg_buf, \"Cannot parse argument 'foo77' as integer\");\n\n    CLEAR(mock_msg_buf);\n    assert_int_equal(atoi_warn(\"77foo\", msglevel), 0);\n    assert_string_equal(mock_msg_buf, \"Cannot parse argument '77foo' as integer\");\n\n    /* special tests for _constrained */\n    CLEAR(mock_msg_buf);\n    parameter = -42;\n    assert_false(atoi_constrained(\"77\", &parameter, \"test\", 0, 76, msglevel));\n    assert_string_equal(mock_msg_buf, \"test: Must be an integer between 0 and 76, not 77\");\n    assert_int_equal(parameter, -42);\n\n    CLEAR(mock_msg_buf);\n    parameter = -42;\n    assert_false(atoi_constrained(\"-77\", &parameter, \"test\", -76, 76, msglevel));\n    assert_string_equal(mock_msg_buf, \"test: Must be an integer between -76 and 76, not -77\");\n    assert_int_equal(parameter, -42);\n\n    CLEAR(mock_msg_buf);\n    parameter = -42;\n    assert_false(atoi_constrained(\"-77\", &parameter, \"test\", 0, INT_MAX, msglevel));\n    assert_string_equal(mock_msg_buf, \"test: Must be an integer >= 0, not -77\");\n    assert_int_equal(parameter, -42);\n\n    CLEAR(mock_msg_buf);\n    parameter = -42;\n    assert_false(atoi_constrained(\"0\", &parameter, \"test\", 1, INT_MAX, msglevel));\n    assert_string_equal(mock_msg_buf, \"test: Must be an integer >= 1, not 0\");\n    assert_int_equal(parameter, -42);\n\n    mock_set_debug_level(saved_log_level);\n}\n\nconst struct CMUnitTest misc_tests[] = { cmocka_unit_test(test_compat_lzo_string),\n                                         cmocka_unit_test(test_auth_fail_temp_no_flags),\n                                         cmocka_unit_test(test_auth_fail_temp_flags),\n                                         cmocka_unit_test(test_auth_fail_temp_flags_msg),\n                                         cmocka_unit_test(test_list),\n                                         cmocka_unit_test(test_atoi_variants) };\n\nint\nmain(void)\n{\n    openvpn_unit_test_setup();\n    return cmocka_run_group_tests(misc_tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_ncp.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2019-2026 Arne Schwabe <arne@rfc2549.org>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <setjmp.h>\n#include <cmocka.h>\n\n#include \"ssl_ncp.c\"\n#include \"test_common.h\"\n\n/* Defines for use in the tests and the mock parse_line() */\n\nconst char *bf_chacha = \"BF-CBC:CHACHA20-POLY1305\";\nconst char *aes_chacha = \"AES-128-CBC:CHACHA20-POLY1305\";\nconst char *aes_ciphers = \"AES-256-GCM:AES-128-GCM\";\n\n\n/* Define this function here as dummy since including the ssl_*.c files\n * leads to having to include even more unrelated code */\nbool\nkey_state_export_keying_material(struct tls_session *session, const char *label, size_t label_size,\n                                 void *ekm, size_t ekm_size)\n{\n    ASSERT(0);\n}\n\n\n/* Define a dummy dco cipher option to avoid linking against all the DCO\n * units */\n#if defined(ENABLE_DCO)\nconst char *\ndco_get_supported_ciphers(void)\n{\n    return \"AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305\";\n}\n#endif\n\nstatic void\ntest_check_ncp_ciphers_list(void **state)\n{\n    struct gc_arena gc = gc_new();\n    bool have_chacha = cipher_valid(\"CHACHA20-POLY1305\");\n    bool have_blowfish = cipher_valid(\"BF-CBC\");\n\n    assert_string_equal(mutate_ncp_cipher_list(\"none\", &gc), \"none\");\n    assert_string_equal(mutate_ncp_cipher_list(\"AES-256-GCM:none\", &gc), \"AES-256-GCM:none\");\n\n    assert_string_equal(mutate_ncp_cipher_list(aes_ciphers, &gc), aes_ciphers);\n\n    if (have_chacha)\n    {\n        assert_string_equal(mutate_ncp_cipher_list(aes_chacha, &gc), aes_chacha);\n    }\n\n    if (have_chacha && have_blowfish)\n    {\n        assert_string_equal(mutate_ncp_cipher_list(bf_chacha, &gc), bf_chacha);\n        assert_string_equal(mutate_ncp_cipher_list(\"BF-CBC:CHACHA20-POLY1305\", &gc), bf_chacha);\n    }\n    else\n    {\n        assert_ptr_equal(mutate_ncp_cipher_list(bf_chacha, &gc), NULL);\n    }\n\n    /* Check that optional ciphers work */\n    assert_string_equal(mutate_ncp_cipher_list(\"AES-256-GCM:?vollbit:AES-128-GCM\", &gc),\n                        aes_ciphers);\n\n    /* Check that optional ciphers work */\n    assert_string_equal(mutate_ncp_cipher_list(\"?AES-256-GCM:?AES-128-GCM\", &gc), aes_ciphers);\n\n    /* All unsupported should still yield an empty list */\n    assert_ptr_equal(mutate_ncp_cipher_list(\"?kugelfisch:?grasshopper\", &gc), NULL);\n\n    /* If the last is optional, previous invalid ciphers should be ignored */\n    assert_ptr_equal(mutate_ncp_cipher_list(\"Vollbit:Littlebit:AES-256-CBC:BF-CBC:?nixbit\", &gc),\n                     NULL);\n\n    /* We do not support CCM ciphers */\n    assert_ptr_equal(mutate_ncp_cipher_list(\"AES-256-GCM:AES-128-CCM\", &gc), NULL);\n\n    assert_string_equal(mutate_ncp_cipher_list(\"AES-256-GCM:?AES-128-CCM:AES-128-GCM\", &gc),\n                        aes_ciphers);\n\n    /* For testing that with OpenSSL 1.1.0+ that also accepts ciphers in\n     * a different spelling the normalised cipher output is the same */\n    bool have_chacha_mixed_case = cipher_valid(\"ChaCha20-Poly1305\");\n    if (have_chacha_mixed_case)\n    {\n        assert_string_equal(mutate_ncp_cipher_list(\"AES-128-CBC:ChaCha20-Poly1305\", &gc),\n                            aes_chacha);\n    }\n\n    assert_ptr_equal(mutate_ncp_cipher_list(\"vollbit\", &gc), NULL);\n    assert_ptr_equal(mutate_ncp_cipher_list(\"AES-256-GCM:vollbit\", &gc), NULL);\n    assert_ptr_equal(mutate_ncp_cipher_list(\"\", &gc), NULL);\n\n    const char long_string[MAX_NCP_CIPHERS_LENGTH] =\n        \"CHACHA20-POLY1305:CHACHA20-POLY1305:CHACHA20-POLY1305:\"\n        \"CHACHA20-POLY1305:CHACHA20-POLY1305:CHACHA20-POLY1305:\"\n        \"CHACHA20-POLY1305\";\n    const char longer_string[MAX_NCP_CIPHERS_LENGTH + 1] =\n        \"CHACHA20-POLY1305:CHACHA20-POLY1305:CHACHA20-POLY1305:\"\n        \"CHACHA20-POLY1305:CHACHA20-POLY1305:CHACHA20-POLY1305:\"\n        \"CHACHA20-POLY1305:\";\n    const char longest_string[] =\n        \"CHACHA20-POLY1305:CHACHA20-POLY1305:CHACHA20-POLY1305:\"\n        \"CHACHA20-POLY1305:CHACHA20-POLY1305:CHACHA20-POLY1305:\"\n        \"CHACHA20-POLY1305:CHACHA20-POLY1305\";\n    assert_string_equal(mutate_ncp_cipher_list(long_string, &gc),\n                        long_string);\n    assert_string_equal(mutate_ncp_cipher_list(longer_string, &gc),\n                        long_string);\n    assert_ptr_equal(mutate_ncp_cipher_list(longest_string, &gc),\n                     NULL);\n\n#ifdef ENABLE_CRYPTO_OPENSSL\n    assert_string_equal(mutate_ncp_cipher_list(\"id-aes128-GCM:id-aes256-GCM\", &gc),\n                        \"AES-128-GCM:AES-256-GCM\");\n#else\n    if (have_blowfish)\n    {\n        assert_string_equal(mutate_ncp_cipher_list(\"BLOWFISH-CBC\", &gc), \"BF-CBC\");\n    }\n#endif\n    gc_free(&gc);\n}\n\nstatic void\ntest_extract_client_ciphers(void **state)\n{\n    struct gc_arena gc = gc_new();\n    const char *client_peer_info;\n    const char *peer_list;\n\n    client_peer_info = \"foo=bar\\nIV_foo=y\\nIV_NCP=2\";\n    peer_list = tls_peer_ncp_list(client_peer_info, &gc);\n    assert_string_equal(aes_ciphers, peer_list);\n    assert_true(tls_peer_supports_ncp(client_peer_info));\n\n    client_peer_info = \"foo=bar\\nIV_foo=y\\nIV_NCP=2\\nIV_CIPHERS=BF-CBC\";\n    peer_list = tls_peer_ncp_list(client_peer_info, &gc);\n    assert_string_equal(\"BF-CBC\", peer_list);\n    assert_true(tls_peer_supports_ncp(client_peer_info));\n\n    client_peer_info = \"IV_NCP=2\\nIV_CIPHERS=BF-CBC:FOO-BAR\\nIV_BAR=7\";\n    peer_list = tls_peer_ncp_list(client_peer_info, &gc);\n    assert_string_equal(\"BF-CBC:FOO-BAR\", peer_list);\n    assert_true(tls_peer_supports_ncp(client_peer_info));\n\n    client_peer_info = \"IV_CIPHERS=BF-CBC:FOO-BAR\\nIV_BAR=7\";\n    peer_list = tls_peer_ncp_list(client_peer_info, &gc);\n    assert_string_equal(\"BF-CBC:FOO-BAR\", peer_list);\n    assert_true(tls_peer_supports_ncp(client_peer_info));\n\n    client_peer_info = \"IV_YOLO=NO\\nIV_BAR=7\";\n    peer_list = tls_peer_ncp_list(client_peer_info, &gc);\n    assert_string_equal(\"\", peer_list);\n    assert_false(tls_peer_supports_ncp(client_peer_info));\n\n    peer_list = tls_peer_ncp_list(NULL, &gc);\n    assert_string_equal(\"\", peer_list);\n    assert_false(tls_peer_supports_ncp(client_peer_info));\n\n    gc_free(&gc);\n}\n\nstatic void\ntest_poor_man(void **state)\n{\n    struct gc_arena gc = gc_new();\n    char *best_cipher;\n\n    const char *serverlist = \"CHACHA20_POLY1305:AES-128-GCM\";\n    const char *serverlistbfcbc = \"CHACHA20_POLY1305:AES-128-GCM:BF-CBC:none\";\n\n    best_cipher = ncp_get_best_cipher(serverlist, \"IV_YOLO=NO\\nIV_BAR=7\", \"BF-CBC\", &gc);\n\n    assert_ptr_equal(best_cipher, NULL);\n\n\n    best_cipher = ncp_get_best_cipher(serverlistbfcbc, \"IV_YOLO=NO\\nIV_BAR=7\", \"BF-CBC\", &gc);\n\n    assert_string_equal(best_cipher, \"BF-CBC\");\n\n\n    best_cipher = ncp_get_best_cipher(serverlist, \"IV_NCP=1\\nIV_BAR=7\", \"AES-128-GCM\", &gc);\n\n    assert_string_equal(best_cipher, \"AES-128-GCM\");\n\n    best_cipher = ncp_get_best_cipher(serverlist, NULL, \"AES-128-GCM\", &gc);\n\n    assert_string_equal(best_cipher, \"AES-128-GCM\");\n\n    best_cipher = ncp_get_best_cipher(serverlist, NULL, \"none\", &gc);\n    assert_ptr_equal(best_cipher, NULL);\n\n    best_cipher = ncp_get_best_cipher(serverlistbfcbc, NULL, \"none\", &gc);\n    assert_string_equal(best_cipher, \"none\");\n\n    best_cipher = ncp_get_best_cipher(serverlist, NULL, NULL, &gc);\n    assert_ptr_equal(best_cipher, NULL);\n\n    gc_free(&gc);\n}\n\n\nstatic void\ntest_ncp_best(void **state)\n{\n    struct gc_arena gc = gc_new();\n    char *best_cipher;\n\n    const char *serverlist = \"CHACHA20_POLY1305:AES-128-GCM:AES-256-GCM\";\n\n    best_cipher = ncp_get_best_cipher(serverlist, \"IV_YOLO=NO\\nIV_NCP=2\\nIV_BAR=7\", \"BF-CBC\", &gc);\n\n    assert_string_equal(best_cipher, \"AES-128-GCM\");\n\n    /* Best cipher is in --cipher of client */\n    best_cipher = ncp_get_best_cipher(serverlist, \"IV_NCP=2\\nIV_BAR=7\", \"CHACHA20_POLY1305\", &gc);\n\n    assert_string_equal(best_cipher, \"CHACHA20_POLY1305\");\n\n    /* Best cipher is in --cipher of client */\n    best_cipher = ncp_get_best_cipher(serverlist, \"IV_CIPHERS=AES-128-GCM\", \"AES-256-CBC\", &gc);\n\n\n    assert_string_equal(best_cipher, \"AES-128-GCM\");\n\n    /* IV_NCP=2 should be ignored if IV_CIPHERS is sent */\n    best_cipher = ncp_get_best_cipher(serverlist, \"IV_FOO=7\\nIV_CIPHERS=AES-256-GCM\\nIV_NCP=2\",\n                                      \"AES-256-CBC\", &gc);\n\n    assert_string_equal(best_cipher, \"AES-256-GCM\");\n\n\n    gc_free(&gc);\n}\n\nstatic void\ntest_ncp_default(void **state)\n{\n    bool have_chacha = cipher_valid(\"CHACHA20-POLY1305\");\n\n    struct options o = { 0 };\n\n    o.gc = gc_new();\n\n    /* no user specified string */\n    o.ncp_ciphers = NULL;\n    options_postprocess_setdefault_ncpciphers(&o);\n\n    if (have_chacha)\n    {\n        assert_string_equal(o.ncp_ciphers, \"AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305\");\n    }\n    else\n    {\n        assert_string_equal(o.ncp_ciphers, \"AES-256-GCM:AES-128-GCM\");\n    }\n    assert_string_equal(o.ncp_ciphers_conf, \"DEFAULT\");\n\n    /* check that a default string is replaced with DEFAULT */\n    if (have_chacha)\n    {\n        o.ncp_ciphers = \"AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305\";\n    }\n    else\n    {\n        o.ncp_ciphers = \"AES-256-GCM:AES-128-GCM\";\n    }\n\n    options_postprocess_setdefault_ncpciphers(&o);\n    assert_string_equal(o.ncp_ciphers_conf, \"DEFAULT\");\n\n    /* test default in the middle of the string */\n    o.ncp_ciphers = \"BF-CBC:DEFAULT:AES-128-CBC:AES-256-CBC\";\n    options_postprocess_setdefault_ncpciphers(&o);\n\n    if (have_chacha)\n    {\n        assert_string_equal(\n            o.ncp_ciphers,\n            \"BF-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-128-CBC:AES-256-CBC\");\n    }\n    else\n    {\n        assert_string_equal(o.ncp_ciphers,\n                            \"BF-CBC:AES-256-GCM:AES-128-GCM:AES-128-CBC:AES-256-CBC\");\n    }\n    assert_string_equal(o.ncp_ciphers_conf, \"BF-CBC:DEFAULT:AES-128-CBC:AES-256-CBC\");\n\n    /* string at the beginning */\n    o.ncp_ciphers = \"DEFAULT:AES-128-CBC:AES-192-CBC\";\n    options_postprocess_setdefault_ncpciphers(&o);\n\n    if (have_chacha)\n    {\n        assert_string_equal(o.ncp_ciphers,\n                            \"AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-128-CBC:AES-192-CBC\");\n    }\n    else\n    {\n        assert_string_equal(o.ncp_ciphers, \"AES-256-GCM:AES-128-GCM:AES-128-CBC:AES-192-CBC\");\n    }\n    assert_string_equal(o.ncp_ciphers_conf, \"DEFAULT:AES-128-CBC:AES-192-CBC\");\n\n    /* DEFAULT at the end */\n    o.ncp_ciphers = \"AES-192-GCM:AES-128-CBC:DEFAULT\";\n    options_postprocess_setdefault_ncpciphers(&o);\n\n    if (have_chacha)\n    {\n        assert_string_equal(o.ncp_ciphers,\n                            \"AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305\");\n    }\n    else\n    {\n        assert_string_equal(o.ncp_ciphers, \"AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM\");\n    }\n    assert_string_equal(o.ncp_ciphers_conf, \"AES-192-GCM:AES-128-CBC:DEFAULT\");\n\n    gc_free(&o.gc);\n}\n\nstatic void\ntest_ncp_expand(void **state)\n{\n    bool have_chacha = cipher_valid(\"CHACHA20-POLY1305\");\n    struct options o = { 0 };\n\n    o.gc = gc_new();\n    struct gc_arena gc = gc_new();\n\n    /* no user specified string */\n    o.ncp_ciphers = NULL;\n    options_postprocess_setdefault_ncpciphers(&o);\n\n    const char *expanded = ncp_expanded_ciphers(&o, &gc);\n\n    /* user specificed string with DEFAULT in it */\n    o.ncp_ciphers = \"AES-192-GCM:DEFAULT\";\n    options_postprocess_setdefault_ncpciphers(&o);\n    const char *expanded2 = ncp_expanded_ciphers(&o, &gc);\n\n    if (have_chacha)\n    {\n        assert_string_equal(expanded, \" (AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305)\");\n        assert_string_equal(expanded2, \" (AES-192-GCM:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305)\");\n    }\n    else\n    {\n        assert_string_equal(expanded, \" (AES-256-GCM:AES-128-GCM)\");\n        assert_string_equal(expanded2, \" (AES-192-GCM:AES-256-GCM:AES-128-GCM)\");\n    }\n\n    o.ncp_ciphers = \"AES-192-GCM:BF-CBC\";\n    options_postprocess_setdefault_ncpciphers(&o);\n\n    assert_string_equal(ncp_expanded_ciphers(&o, &gc), \"\");\n\n    gc_free(&o.gc);\n    gc_free(&gc);\n}\n\n\nconst struct CMUnitTest ncp_tests[] = {\n    cmocka_unit_test(test_check_ncp_ciphers_list),\n    cmocka_unit_test(test_extract_client_ciphers),\n    cmocka_unit_test(test_poor_man),\n    cmocka_unit_test(test_ncp_best),\n    cmocka_unit_test(test_ncp_default),\n    cmocka_unit_test(test_ncp_expand),\n};\n\n\nint\nmain(void)\n{\n    openvpn_unit_test_setup();\n\n    return cmocka_run_group_tests(ncp_tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_networking.c",
    "content": "#include \"config.h\"\n#include \"syshead.h\"\n#include \"networking.h\"\n\n#include <setjmp.h>\n#include <cmocka.h>\n\nstatic char *iface = \"ovpn-dummy0\";\n\nstatic int\nnet__iface_up(bool up)\n{\n    printf(\"CMD: ip link set %s %s\\n\", iface, up ? \"up\" : \"down\");\n\n    return net_iface_up(NULL, iface, up);\n}\n\nstatic int\nnet__iface_new(const char *name, const char *type)\n{\n    return net_iface_new(NULL, name, type, NULL);\n}\n\nstatic int\nnet__iface_type(const char *name, const char *type)\n{\n    char ret_type[IFACE_TYPE_LEN_MAX];\n    int ret = net_iface_type(NULL, name, ret_type);\n    if (ret == 0)\n    {\n        assert_string_equal(type, ret_type);\n    }\n\n    return ret;\n}\n\nstatic int\nnet__iface_del(const char *name)\n{\n    return net_iface_del(NULL, name);\n}\n\nstatic int\nnet__iface_mtu_set(int mtu)\n{\n    printf(\"CMD: ip link set %s mtu %d\\n\", iface, mtu);\n\n    return net_iface_mtu_set(NULL, iface, mtu);\n}\n\nstatic int\nnet__addr_v4_add(const char *addr_str, int prefixlen)\n{\n    in_addr_t addr;\n    int ret;\n\n    ret = inet_pton(AF_INET, addr_str, &addr);\n    if (ret != 1)\n    {\n        return -1;\n    }\n\n    addr = ntohl(addr);\n\n    printf(\"CMD: ip addr add %s/%d dev %s broadcast +\\n\", addr_str, prefixlen, iface);\n\n    return net_addr_v4_add(NULL, iface, &addr, prefixlen);\n}\n\nstatic int\nnet__addr_v6_add(const char *addr_str, int prefixlen)\n{\n    struct in6_addr addr;\n    int ret;\n\n    ret = inet_pton(AF_INET6, addr_str, &addr);\n    if (ret != 1)\n    {\n        return -1;\n    }\n\n    printf(\"CMD: ip -6 addr add %s/%d dev %s\\n\", addr_str, prefixlen, iface);\n\n    return net_addr_v6_add(NULL, iface, &addr, prefixlen);\n}\n\nstatic int\nnet__route_v4_add(const char *dst_str, int prefixlen, int metric)\n{\n    in_addr_t dst;\n    int ret;\n\n    if (!dst_str)\n    {\n        return -1;\n    }\n\n    ret = inet_pton(AF_INET, dst_str, &dst);\n    if (ret != 1)\n    {\n        return -1;\n    }\n\n    dst = ntohl(dst);\n\n    printf(\"CMD: ip route add %s/%d dev %s\", dst_str, prefixlen, iface);\n    if (metric > 0)\n    {\n        printf(\" metric %d\", metric);\n    }\n    printf(\"\\n\");\n\n    return net_route_v4_add(NULL, &dst, prefixlen, NULL, iface, 0, metric);\n}\n\nstatic int\nnet__route_v4_add_gw(const char *dst_str, int prefixlen, const char *gw_str, int metric)\n{\n    in_addr_t dst, gw;\n    int ret;\n\n    if (!dst_str || !gw_str)\n    {\n        return -1;\n    }\n\n    ret = inet_pton(AF_INET, dst_str, &dst);\n    if (ret != 1)\n    {\n        return -1;\n    }\n\n    ret = inet_pton(AF_INET, gw_str, &gw);\n    if (ret != 1)\n    {\n        return -1;\n    }\n\n    dst = ntohl(dst);\n    gw = ntohl(gw);\n\n    printf(\"CMD: ip route add %s/%d dev %s via %s\", dst_str, prefixlen, iface, gw_str);\n    if (metric > 0)\n    {\n        printf(\" metric %d\", metric);\n    }\n    printf(\"\\n\");\n\n    return net_route_v4_add(NULL, &dst, prefixlen, &gw, iface, 0, metric);\n}\n\nstatic int\nnet__route_v6_add(const char *dst_str, int prefixlen, int metric)\n{\n    struct in6_addr dst;\n    int ret;\n\n    if (!dst_str)\n    {\n        return -1;\n    }\n\n    ret = inet_pton(AF_INET6, dst_str, &dst);\n    if (ret != 1)\n    {\n        return -1;\n    }\n\n    printf(\"CMD: ip -6 route add %s/%d dev %s\", dst_str, prefixlen, iface);\n    if (metric > 0)\n    {\n        printf(\" metric %d\", metric);\n    }\n    printf(\"\\n\");\n\n    return net_route_v6_add(NULL, &dst, prefixlen, NULL, iface, 0, metric);\n}\n\nstatic int\nnet__route_v6_add_gw(const char *dst_str, int prefixlen, const char *gw_str, int metric)\n{\n    struct in6_addr dst, gw;\n    int ret;\n\n    if (!dst_str || !gw_str)\n    {\n        return -1;\n    }\n\n    ret = inet_pton(AF_INET6, dst_str, &dst);\n    if (ret != 1)\n    {\n        return -1;\n    }\n\n    ret = inet_pton(AF_INET6, gw_str, &gw);\n    if (ret != 1)\n    {\n        return -1;\n    }\n\n    printf(\"CMD: ip -6 route add %s/%d dev %s via %s\", dst_str, prefixlen, iface, gw_str);\n    if (metric > 0)\n    {\n        printf(\" metric %d\", metric);\n    }\n    printf(\"\\n\");\n\n    return net_route_v6_add(NULL, &dst, prefixlen, &gw, iface, 0, metric);\n}\n\nstatic void\nusage(char *name)\n{\n    printf(\"Usage: %s <0-8>\\n\", name);\n}\n\nint\nmain(int argc, char *argv[])\n{\n    int test;\n\n    if (argc < 2)\n    {\n        usage(argv[0]);\n        return -1;\n    }\n\n    /* the t_net script can use this command to perform a dry-run test */\n    if (strcmp(argv[1], \"test\") == 0)\n    {\n        return 0;\n    }\n\n    if (argc > 3)\n    {\n        iface = argv[2];\n    }\n\n    test = atoi(argv[1]);\n    switch (test)\n    {\n        case 0:\n            return net__iface_up(true);\n\n        case 1:\n            return net__iface_mtu_set(1281);\n\n        case 2:\n            return net__addr_v4_add(\"10.255.255.1\", 24);\n\n        case 3:\n            return net__addr_v6_add(\"2001::1\", 64);\n\n        case 4:\n            return net__route_v4_add(\"11.11.11.0\", 24, 0);\n\n        case 5:\n            return net__route_v4_add_gw(\"11.11.12.0\", 24, \"10.255.255.2\", 0);\n\n        case 6:\n            return net__route_v6_add(\"2001:babe:cafe:babe::\", 64, 600);\n\n        case 7:\n            return net__route_v6_add_gw(\"2001:cafe:babe::\", 48, \"2001::2\", 600);\n\n        /* following tests are standalone and do not print any CMD= */\n        case 8:\n            assert_int_equal(net__iface_new(\"dummy0815\", \"dummy\"), 0);\n            assert_int_equal(net__iface_type(\"dummy0815\", \"dummy\"), 0);\n            assert_int_equal(net__iface_del(\"dummy0815\"), 0);\n            assert_int_equal(net__iface_type(\"dummy0815\", NULL), -ENODEV);\n            return 0;\n\n        default:\n            printf(\"invalid test: %d\\n\", test);\n            break;\n    }\n\n    usage(argv[0]);\n    return -1;\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_options_parse.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2025-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <setjmp.h>\n#include <cmocka.h>\n\n#include \"options.h\"\n#include \"test_common.h\"\n#include \"mock_msg.h\"\n\nvoid\nadd_option(struct options *options, char *p[], bool is_inline, const char *file,\n           int line, const int level, const msglvl_t msglevel,\n           const unsigned int permission_mask, unsigned int *option_types_found,\n           struct env_set *es)\n{\n    function_called();\n    check_expected_ptr(p);\n    check_expected_uint(is_inline);\n}\n\nvoid\nremove_option(struct context *c, struct options *options, char *p[], bool is_inline,\n              const char *file, int line, const msglvl_t msglevel,\n              const unsigned int permission_mask, unsigned int *option_types_found,\n              struct env_set *es)\n{\n}\n\nvoid\nupdate_option(struct context *c, struct options *options, char *p[], bool is_inline,\n              const char *file, int line, const int level, const msglvl_t msglevel,\n              const unsigned int permission_mask, unsigned int *option_types_found,\n              struct env_set *es)\n{\n}\n\nvoid\nusage(void)\n{\n}\n\n/* for building long texts */\n#define A_TIMES_256 \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO\"\n\nstatic void\ntest_parse_line(void **state)\n{\n    char *p[MAX_PARMS + 1] = { 0 };\n    struct gc_arena gc = gc_new();\n    int res = 0;\n\n#define PARSE_LINE_TST(string)                                                          \\\n    do                                                                                  \\\n    {                                                                                   \\\n        CLEAR(p);                                                                       \\\n        res = parse_line(string, p, SIZE(p) - 1, \"test_options_parse\", 1, M_INFO, &gc); \\\n    } while (0);\n\n    /* basic example */\n    PARSE_LINE_TST(\"some-opt firstparm second-parm\");\n    assert_int_equal(res, 3);\n    assert_string_equal(p[0], \"some-opt\");\n    assert_string_equal(p[1], \"firstparm\");\n    assert_string_equal(p[2], \"second-parm\");\n    assert_null(p[res]);\n\n    /* basic quoting, -- is not handled special */\n    PARSE_LINE_TST(\"--some-opt 'first parm' \\\"second' 'parm\\\"\");\n    assert_int_equal(res, 3);\n    assert_string_equal(p[0], \"--some-opt\");\n    assert_string_equal(p[1], \"first parm\");\n    assert_string_equal(p[2], \"second' 'parm\");\n    assert_null(p[res]);\n\n    /* escaped quotes */\n    PARSE_LINE_TST(\"\\\"some opt\\\" 'first\\\" \\\"parm' \\\"second\\\\\\\" \\\\\\\"parm\\\"\");\n    assert_int_equal(res, 3);\n    assert_string_equal(p[0], \"some opt\");\n    assert_string_equal(p[1], \"first\\\" \\\"parm\");\n    assert_string_equal(p[2], \"second\\\" \\\"parm\");\n    assert_null(p[res]);\n\n    /* missing closing quote */\n    PARSE_LINE_TST(\"--some-opt 'first parm \\\"second parm\\\"\");\n    assert_int_equal(res, 0);\n\n    /* escaped backslash */\n    PARSE_LINE_TST(\"some\\\\\\\\opt C:\\\\\\\\directory\\\\\\\\file\");\n    assert_int_equal(res, 2);\n    assert_string_equal(p[0], \"some\\\\opt\");\n    assert_string_equal(p[1], \"C:\\\\directory\\\\file\");\n    assert_null(p[res]);\n\n    /* comment chars are not special inside parameter */\n    PARSE_LINE_TST(\"some-opt firstparm; second#parm\");\n    assert_int_equal(res, 3);\n    assert_string_equal(p[0], \"some-opt\");\n    assert_string_equal(p[1], \"firstparm;\");\n    assert_string_equal(p[2], \"second#parm\");\n    assert_null(p[res]);\n\n    /* comment */\n    PARSE_LINE_TST(\"some-opt firstparm # secondparm\");\n    assert_int_equal(res, 2);\n    assert_string_equal(p[0], \"some-opt\");\n    assert_string_equal(p[1], \"firstparm\");\n    assert_null(p[res]);\n\n    /* parameter just long enough */\n    PARSE_LINE_TST(\"opt \" A_TIMES_256);\n    assert_int_equal(res, 2);\n    assert_string_equal(p[0], \"opt\");\n    assert_string_equal(p[1], A_TIMES_256);\n    assert_null(p[res]);\n\n    /* quoting doesn't count for parameter length */\n    PARSE_LINE_TST(\"opt \\\"\" A_TIMES_256 \"\\\"\");\n    assert_int_equal(res, 2);\n    assert_string_equal(p[0], \"opt\");\n    assert_string_equal(p[1], A_TIMES_256);\n    assert_null(p[res]);\n\n    /* very long line */\n    PARSE_LINE_TST(\"opt \" A_TIMES_256 \" \" A_TIMES_256 \" \" A_TIMES_256 \" \" A_TIMES_256);\n    assert_int_equal(res, 5);\n    assert_string_equal(p[0], \"opt\");\n    assert_string_equal(p[1], A_TIMES_256);\n    assert_string_equal(p[2], A_TIMES_256);\n    assert_string_equal(p[3], A_TIMES_256);\n    assert_string_equal(p[4], A_TIMES_256);\n    assert_null(p[res]);\n\n    /* parameter too long */\n    PARSE_LINE_TST(\"opt \" A_TIMES_256 \"B\");\n    assert_int_equal(res, 0);\n\n    /* max parameters */\n    PARSE_LINE_TST(\"0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15\");\n    assert_int_equal(res, MAX_PARMS);\n    char num[3];\n    for (int i = 0; i < MAX_PARMS; i++)\n    {\n        assert_true(snprintf(num, 3, \"%d\", i) < 3);\n        assert_string_equal(p[i], num);\n    }\n    assert_null(p[res]);\n\n    /* too many parameters, overflow is ignored */\n    PARSE_LINE_TST(\"0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16\");\n    assert_int_equal(res, MAX_PARMS);\n    for (int i = 0; i < MAX_PARMS; i++)\n    {\n        assert_true(snprintf(num, 3, \"%d\", i) < 3);\n        assert_string_equal(p[i], num);\n    }\n    assert_null(p[res]);\n\n    gc_free(&gc);\n}\n\nstatic void\nread_single_config(struct options *options, const char *config)\n{\n    unsigned int option_types_found = 0;\n    struct env_set es;\n    CLEAR(es);\n    read_config_string(\"test_options_parse\", options, config, M_INFO, OPT_P_DEFAULT,\n                       &option_types_found, &es);\n}\n\n#if HAVE_OLD_CMOCKA_API\nunion token_parameter\n{\n    LargestIntegralType int_val;\n    void *ptr;\n};\n#endif\n\nstatic int\ncheck_tokens(const CMockaValueData value, const CMockaValueData expected)\n{\n#if HAVE_OLD_CMOCKA_API\n    union token_parameter temp;\n    temp.int_val = value;\n    const char **p = (const char **)temp.ptr;\n    temp.int_val = expected;\n    const char **expected_p = (const char **)temp.ptr;\n#else\n    const char **p = (const char **)value.ptr;\n    const char **expected_p = (const char **)expected.ptr;\n#endif\n    for (int i = 0; i < MAX_PARMS; i++)\n    {\n        if (!p[i] && !expected_p[i])\n        {\n            return true;\n        }\n        if ((p[i] && !expected_p[i])\n            || (!p[i] && expected_p[i]))\n        {\n            fprintf(stderr, \"diff at i=%d\\n\", i);\n            return false;\n        }\n        if (strcmp(p[i], expected_p[i]))\n        {\n            fprintf(stderr, \"diff at i=%d, p=<%s> ep=<%s>\\n\", i, p[i], expected_p[i]);\n            return false;\n        }\n    }\n    fprintf(stderr, \"fallthrough\");\n    return false;\n}\n\nstatic void\ntest_read_config(void **state)\n{\n    struct options o;\n    CLEAR(o); /* NB: avoiding init_options to limit dependencies */\n    gc_init(&o.gc);\n    gc_init(&o.dns_options.gc);\n\n    char *p_expect_someopt[MAX_PARMS];\n    char *p_expect_otheropt[MAX_PARMS];\n    char *p_expect_inlineopt[MAX_PARMS];\n    CLEAR(p_expect_someopt);\n    CLEAR(p_expect_otheropt);\n    CLEAR(p_expect_inlineopt);\n    p_expect_someopt[0] = \"someopt\";\n    p_expect_someopt[1] = \"parm1\";\n    p_expect_someopt[2] = \"parm2\";\n    p_expect_otheropt[0] = \"otheropt\";\n    p_expect_otheropt[1] = \"1\";\n    p_expect_otheropt[2] = \"2\";\n    p_expect_inlineopt[0] = \"inlineopt\";\n    p_expect_inlineopt[1] = \"some text\\nother text\\n\";\n\n    /* basic test */\n    expect_function_call(add_option);\n    expect_check_data(add_option, p, check_tokens, cast_ptr_to_cmocka_value(p_expect_someopt));\n    expect_uint_value(add_option, is_inline, 0);\n    expect_function_call(add_option);\n    expect_check_data(add_option, p, check_tokens, cast_ptr_to_cmocka_value(p_expect_otheropt));\n    expect_uint_value(add_option, is_inline, 0);\n    read_single_config(&o, \"someopt parm1 parm2\\n  otheropt 1 2\");\n\n    /* -- gets stripped */\n    expect_function_call(add_option);\n    expect_check_data(add_option, p, check_tokens, cast_ptr_to_cmocka_value(p_expect_someopt));\n    expect_uint_value(add_option, is_inline, 0);\n    expect_function_call(add_option);\n    expect_check_data(add_option, p, check_tokens, cast_ptr_to_cmocka_value(p_expect_otheropt));\n    expect_uint_value(add_option, is_inline, 0);\n    read_single_config(&o, \"someopt parm1 parm2\\n\\t--otheropt 1 2\");\n\n    /* inline options */\n    expect_function_call(add_option);\n    expect_check_data(add_option, p, check_tokens, cast_ptr_to_cmocka_value(p_expect_inlineopt));\n    expect_uint_value(add_option, is_inline, 1);\n    read_single_config(&o, \"<inlineopt>\\nsome text\\nother text\\n</inlineopt>\");\n\n    p_expect_inlineopt[0] = \"inlineopt\";\n    p_expect_inlineopt[1] = A_TIMES_256 A_TIMES_256 A_TIMES_256 A_TIMES_256 A_TIMES_256 \"\\n\";\n    expect_function_call(add_option);\n    expect_check_data(add_option, p, check_tokens, cast_ptr_to_cmocka_value(p_expect_inlineopt));\n    expect_uint_value(add_option, is_inline, 1);\n    read_single_config(&o, \"<inlineopt>\\n\" A_TIMES_256 A_TIMES_256 A_TIMES_256 A_TIMES_256 A_TIMES_256 \"\\n</inlineopt>\");\n\n    gc_free(&o.gc);\n    gc_free(&o.dns_options.gc);\n}\n\nint\nmain(void)\n{\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(test_parse_line),\n        cmocka_unit_test(test_read_config),\n    };\n\n    return cmocka_run_group_tests_name(\"options_parse\", tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_packet_id.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program (see the file COPYING included with this\n *  distribution); if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <stdarg.h>\n#include <stddef.h>\n#include <setjmp.h>\n#include <cmocka.h>\n\n#include \"packet_id.h\"\n#include \"reliable.h\"\n#include \"test_common.h\"\n\nstruct test_packet_id_write_data\n{\n    struct\n    {\n        uint32_t buf_id;\n        uint32_t buf_time;\n    } test_buf_data;\n    struct buffer test_buf;\n    struct packet_id_send pis;\n    struct gc_arena gc;\n};\n\nstatic int\ntest_packet_id_write_setup(void **state)\n{\n    struct test_packet_id_write_data *data = calloc(1, sizeof(struct test_packet_id_write_data));\n\n    if (!data)\n    {\n        return -1;\n    }\n\n    data->test_buf.data = (void *)&data->test_buf_data;\n    data->test_buf.capacity = sizeof(data->test_buf_data);\n    data->gc = gc_new();\n\n    *state = data;\n    return 0;\n}\n\nstatic int\ntest_packet_id_write_teardown(void **state)\n{\n    struct test_packet_id_write_data *data = *state;\n    gc_free(&data->gc);\n    free(*state);\n    return 0;\n}\n\nstatic void\ntest_packet_id_write_short(void **state)\n{\n    struct test_packet_id_write_data *data = *state;\n\n    now = 5010;\n    assert_true(packet_id_write(&data->pis, &data->test_buf, false, false));\n    assert_int_equal(data->pis.id, 1);\n    assert_int_equal(data->test_buf_data.buf_id, htonl(1));\n    assert_int_equal(data->test_buf_data.buf_time, 0);\n}\n\nstatic void\ntest_packet_id_write_long(void **state)\n{\n    struct test_packet_id_write_data *data = *state;\n\n    now = 5010;\n    assert_true(packet_id_write(&data->pis, &data->test_buf, true, false));\n    assert_int_equal(data->pis.id, 1);\n    assert_int_equal(data->pis.time, now);\n    assert_int_equal(data->test_buf_data.buf_id, htonl(1));\n    assert_int_equal(data->test_buf_data.buf_time, htonl((uint32_t)now));\n}\n\nstatic void\ntest_packet_id_write_short_prepend(void **state)\n{\n    struct test_packet_id_write_data *data = *state;\n\n    data->test_buf.offset = sizeof(packet_id_type);\n    now = 5010;\n    assert_true(packet_id_write(&data->pis, &data->test_buf, false, true));\n    assert_int_equal(data->pis.id, 1);\n    assert_int_equal(data->test_buf_data.buf_id, htonl(1));\n    assert_int_equal(data->test_buf_data.buf_time, 0);\n}\n\nstatic void\ntest_packet_id_write_long_prepend(void **state)\n{\n    struct test_packet_id_write_data *data = *state;\n\n    data->test_buf.offset = sizeof(data->test_buf_data);\n    now = 5010;\n    assert_true(packet_id_write(&data->pis, &data->test_buf, true, true));\n    assert_int_equal(data->pis.id, 1);\n    assert_int_equal(data->pis.time, now);\n    assert_int_equal(data->test_buf_data.buf_id, htonl(1));\n    assert_int_equal(data->test_buf_data.buf_time, htonl((uint32_t)now));\n}\n\nstatic void\ntest_packet_id_write_short_wrap(void **state)\n{\n    struct test_packet_id_write_data *data = *state;\n\n    /* maximum 32-bit packet id */\n    data->pis.id = (packet_id_type)(~0);\n    assert_false(packet_id_write(&data->pis, &data->test_buf, false, false));\n}\n\nstatic void\ntest_packet_id_write_long_wrap(void **state)\n{\n    struct test_packet_id_write_data *data = *state;\n\n    /* maximum 32-bit packet id */\n    data->pis.id = (packet_id_type)(~0);\n    data->pis.time = 5006;\n\n    /* Write fails if time did not change */\n    now = 5006;\n    assert_false(packet_id_write(&data->pis, &data->test_buf, true, false));\n\n    /* Write succeeds if time moved forward */\n    now = 5010;\n    assert_true(packet_id_write(&data->pis, &data->test_buf, true, false));\n\n    assert_int_equal(data->pis.id, 1);\n    assert_int_equal(data->pis.time, now);\n    assert_int_equal(data->test_buf_data.buf_id, htonl(1));\n    assert_int_equal(data->test_buf_data.buf_time, htonl((uint32_t)now));\n}\n\nstatic void\ntest_get_num_output_sequenced_available(void **state)\n{\n    struct reliable *rel = malloc(sizeof(struct reliable));\n    reliable_init(rel, 100, 50, 8, false);\n\n    rel->array[5].active = true;\n    rel->array[5].packet_id = 100;\n\n    rel->packet_id = 103;\n\n    assert_int_equal(5, reliable_get_num_output_sequenced_available(rel));\n\n    rel->array[6].active = true;\n    rel->array[6].packet_id = 97;\n    assert_int_equal(2, reliable_get_num_output_sequenced_available(rel));\n\n    /* test ids close to int/unsigned int barrier */\n\n    rel->array[5].active = true;\n    rel->array[5].packet_id = (0x80000000u - 3);\n    rel->array[6].active = false;\n    rel->packet_id = (0x80000000u - 1);\n\n    assert_int_equal(6, reliable_get_num_output_sequenced_available(rel));\n\n    rel->array[5].active = true;\n    rel->array[5].packet_id = (0x80000000u - 3);\n    rel->packet_id = 0x80000001u;\n\n    assert_int_equal(4, reliable_get_num_output_sequenced_available(rel));\n\n\n    /* test wrapping */\n    rel->array[5].active = true;\n    rel->array[5].packet_id = (0xffffffffu - 3);\n    rel->array[6].active = false;\n    rel->packet_id = (0xffffffffu - 1);\n\n    assert_int_equal(6, reliable_get_num_output_sequenced_available(rel));\n\n    rel->array[2].packet_id = 0;\n    rel->array[2].active = true;\n\n    assert_int_equal(6, reliable_get_num_output_sequenced_available(rel));\n\n    rel->packet_id = 3;\n    assert_int_equal(1, reliable_get_num_output_sequenced_available(rel));\n\n    reliable_free(rel);\n}\n\nstatic void\ntest_packet_id_write_epoch(void **state)\n{\n    struct test_packet_id_write_data *data = *state;\n\n    struct buffer buf = alloc_buf_gc(128, &data->gc);\n\n    /* test normal writing of packet id to the buffer */\n    assert_true(packet_id_write_epoch(&data->pis, 0x23, &buf));\n\n    assert_int_equal(buf.len, 8);\n    uint8_t expected_header[8] = { 0x00, 0x23, 0, 0, 0, 0, 0, 1 };\n    assert_memory_equal(BPTR(&buf), expected_header, 8);\n\n    /* too small buffer should error out */\n    struct buffer buf_short = alloc_buf_gc(5, &data->gc);\n    assert_false(packet_id_write_epoch(&data->pis, 0xabde, &buf_short));\n\n    /* test a true 48 bit packet id */\n    data->pis.id = 0xfa079ab9d2e8;\n    struct buffer buf_48 = alloc_buf_gc(128, &data->gc);\n    assert_true(packet_id_write_epoch(&data->pis, 0xfffe, &buf_48));\n    uint8_t expected_header_48[8] = { 0xff, 0xfe, 0xfa, 0x07, 0x9a, 0xb9, 0xd2, 0xe9 };\n    assert_memory_equal(BPTR(&buf_48), expected_header_48, 8);\n\n    /* test writing/checking the 48 bit per epoch packet counter\n     * overflow */\n    data->pis.id = 0xfffffffffffe;\n    struct buffer buf_of = alloc_buf_gc(128, &data->gc);\n    assert_true(packet_id_write_epoch(&data->pis, 0xf00f, &buf_of));\n    uint8_t expected_header_of[8] = { 0xf0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };\n    assert_memory_equal(BPTR(&buf_of), expected_header_of, 8);\n\n    /* This is go over 2^48 - 1 and should error out. */\n    assert_false(packet_id_write_epoch(&data->pis, 0xf00f, &buf_of));\n\n    /* Now read back the packet ids and check if they are the same as what we\n     * have written */\n    struct packet_id_net pin;\n    assert_int_equal(packet_id_read_epoch(&pin, &buf), 0x23);\n    assert_int_equal(pin.id, 1);\n\n    assert_int_equal(packet_id_read_epoch(&pin, &buf_48), 0xfffe);\n    assert_int_equal(pin.id, 0xfa079ab9d2e9);\n\n    assert_int_equal(packet_id_read_epoch(&pin, &buf_of), 0xf00f);\n    assert_int_equal(pin.id, 0xffffffffffff);\n}\n\nstatic void\ntest_copy_acks_to_lru(void **state)\n{\n    struct reliable_ack ack = { .len = 4, .packet_id = { 2, 1, 3, 2 } };\n\n    struct reliable_ack mru_ack = { 0 };\n\n    /* Test copying to empty ack structure */\n    copy_acks_to_mru(&ack, &mru_ack, 4);\n    assert_int_equal(mru_ack.len, 3);\n    assert_int_equal(mru_ack.packet_id[0], 2);\n    assert_int_equal(mru_ack.packet_id[1], 1);\n    assert_int_equal(mru_ack.packet_id[2], 3);\n\n    /* Copying again should not change the result */\n    copy_acks_to_mru(&ack, &mru_ack, 4);\n    assert_int_equal(mru_ack.len, 3);\n    assert_int_equal(mru_ack.packet_id[0], 2);\n    assert_int_equal(mru_ack.packet_id[1], 1);\n    assert_int_equal(mru_ack.packet_id[2], 3);\n\n    /* Copying just the first two element should not change the order\n     * as they are still the most recent*/\n    struct reliable_ack mru_ack2 = mru_ack;\n    copy_acks_to_mru(&ack, &mru_ack2, 2);\n    assert_int_equal(mru_ack2.packet_id[0], 2);\n    assert_int_equal(mru_ack2.packet_id[1], 1);\n    assert_int_equal(mru_ack2.packet_id[2], 3);\n\n    /* Adding just two packets shoudl ignore the 42 in array and\n     * reorder the order in the MRU */\n    struct reliable_ack ack2 = { .len = 3, .packet_id = { 3, 2, 42 } };\n    copy_acks_to_mru(&ack2, &mru_ack2, 2);\n    assert_int_equal(mru_ack2.packet_id[0], 3);\n    assert_int_equal(mru_ack2.packet_id[1], 2);\n    assert_int_equal(mru_ack2.packet_id[2], 1);\n\n    /* Copying a zero array into it should also change nothing */\n    struct reliable_ack empty_ack = { .len = 0 };\n    copy_acks_to_mru(&empty_ack, &mru_ack, 0);\n    assert_int_equal(mru_ack.len, 3);\n    assert_int_equal(mru_ack.packet_id[0], 2);\n    assert_int_equal(mru_ack.packet_id[1], 1);\n    assert_int_equal(mru_ack.packet_id[2], 3);\n\n    /* Or should just 0 elements of the ack */\n    copy_acks_to_mru(&ack, &mru_ack, 0);\n    assert_int_equal(mru_ack.len, 3);\n    assert_int_equal(mru_ack.packet_id[0], 2);\n    assert_int_equal(mru_ack.packet_id[1], 1);\n    assert_int_equal(mru_ack.packet_id[2], 3);\n\n    struct reliable_ack ack3 = { .len = 7, .packet_id = { 5, 6, 7, 8, 9, 10, 11 } };\n\n    /* Adding multiple acks tests if the a full array is handled correctly */\n    copy_acks_to_mru(&ack3, &mru_ack, 7);\n\n    struct reliable_ack expected_ack = { .len = 8, .packet_id = { 5, 6, 7, 8, 9, 10, 11, 2 } };\n    assert_int_equal(mru_ack.len, expected_ack.len);\n\n    assert_memory_equal(mru_ack.packet_id, expected_ack.packet_id, sizeof(expected_ack.packet_id));\n}\n\nint\nmain(void)\n{\n    openvpn_unit_test_setup();\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test_setup_teardown(test_packet_id_write_short, test_packet_id_write_setup,\n                                        test_packet_id_write_teardown),\n        cmocka_unit_test_setup_teardown(test_packet_id_write_long, test_packet_id_write_setup,\n                                        test_packet_id_write_teardown),\n        cmocka_unit_test_setup_teardown(test_packet_id_write_short_prepend,\n                                        test_packet_id_write_setup, test_packet_id_write_teardown),\n        cmocka_unit_test_setup_teardown(test_packet_id_write_long_prepend,\n                                        test_packet_id_write_setup, test_packet_id_write_teardown),\n        cmocka_unit_test_setup_teardown(test_packet_id_write_short_wrap, test_packet_id_write_setup,\n                                        test_packet_id_write_teardown),\n        cmocka_unit_test_setup_teardown(test_packet_id_write_long_wrap, test_packet_id_write_setup,\n                                        test_packet_id_write_teardown),\n        cmocka_unit_test_setup_teardown(test_packet_id_write_epoch, test_packet_id_write_setup,\n                                        test_packet_id_write_teardown),\n\n        cmocka_unit_test(test_get_num_output_sequenced_available),\n        cmocka_unit_test(test_copy_acks_to_lru)\n\n    };\n\n    return cmocka_run_group_tests_name(\"packet_id tests\", tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_pkcs11.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2023-2026 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by the\n *  Free Software Foundation, either version 2 of the License,\n *  or (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n#include \"manage.h\"\n#include \"base64.h\"\n#include \"run_command.h\"\n#include \"xkey_common.h\"\n#include \"cert_data.h\"\n#include \"pkcs11.h\"\n#include \"ssl.h\"\n\n#include <setjmp.h>\n#include <cmocka.h>\n#include \"test_common.h\"\n\n#define token_name \"Test Token\"\n#define PIN        \"12345\"\n#define HASHSIZE   20\n\n\nstruct management *management; /* global */\n\n/* replacement for crypto_print_openssl_errors() */\nvoid\ncrypto_print_openssl_errors(const unsigned int flags)\n{\n    unsigned long e;\n    while ((e = ERR_get_error()))\n    {\n        msg(flags, \"OpenSSL error %lu: %s\", e, ERR_error_string(e, NULL));\n    }\n}\n\n/* stubs for some unused functions instead of pulling in too many dependencies */\nint\nparse_line(const char *line, char **p, const int n, const char *file, const int line_num,\n           msglvl_t msglevel, struct gc_arena *gc)\n{\n    assert_true(0);\n    return 0;\n}\nchar *\nx509_get_subject(openvpn_x509_cert_t *cert, struct gc_arena *gc)\n{\n    return \"N/A\";\n}\nvoid\nquery_user_clear(void)\n{\n    assert_true(0);\n}\n#if defined(ENABLE_SYSTEMD)\nbool\nquery_user_exec_systemd(void)\n{\n    assert_true(0);\n    return false;\n}\n#endif\nbool\nquery_user_exec_builtin(void)\n{\n    assert_true(0);\n    return false;\n}\nvoid\nquery_user_add(char *prompt, size_t prompt_len, char *resp, size_t resp_len, bool echo)\n{\n    (void)prompt;\n    (void)prompt_len;\n    (void)resp;\n    (void)resp_len;\n    (void)echo;\n    assert_true(0);\n}\nvoid\npurge_user_pass(struct user_pass *up, const bool force)\n{\n    (void)force;\n    secure_memzero(up, sizeof(*up));\n}\n\nchar *\nmanagement_query_pk_sig(struct management *man, const char *b64_data, const char *algorithm)\n{\n    (void)man;\n    (void)b64_data;\n    (void)algorithm;\n    return NULL;\n}\n\nint digest_sign_verify(EVP_PKEY *privkey, EVP_PKEY *pubkey);\n\n/* Test certificate database: data for cert1, cert2 .. key1, key2 etc.\n * are defined in cert_data.h\n */\nstatic struct test_cert\n{\n    const char *const cert;          /* certificate as PEM */\n    const char *const key;           /* key as unencrypted PEM */\n    const char *const cname;         /* common-name */\n    const char *const issuer;        /* issuer common-name */\n    const char *const friendly_name; /* identifies certs loaded to the store -- keep unique */\n    uint8_t hash[HASHSIZE];          /* SHA1 fingerprint: computed and filled in later */\n    char *p11_id;                    /* PKCS#11 id -- filled in later */\n} certs[5];\n\nstatic bool pkcs11_id_management;\nstatic char softhsm2_tokens_path[] = \"softhsm2_tokens_XXXXXX\";\nstatic char softhsm2_conf_path[] = \"softhsm2_conf_XXXXXX\";\nint num_certs;\nstatic const char *pkcs11_id_current;\nstruct env_set *es;\n\n/* Fill-in certs[] array */\nvoid\ninit_cert_data(void)\n{\n    struct test_cert certs_local[] = {\n        { cert1, key1, cname1, \"OVPN TEST CA1\", \"OVPN Test Cert 1\", { 0 }, NULL },\n        { cert2, key2, cname2, \"OVPN TEST CA2\", \"OVPN Test Cert 2\", { 0 }, NULL },\n        { cert3, key3, cname3, \"OVPN TEST CA1\", \"OVPN Test Cert 3\", { 0 }, NULL },\n        { cert4, key4, cname4, \"OVPN TEST CA2\", \"OVPN Test Cert 4\", { 0 }, NULL },\n        { 0 }\n    };\n    assert_int_equal(sizeof(certs_local), sizeof(certs));\n    memcpy(certs, certs_local, sizeof(certs_local));\n}\n\n/* Intercept get_user_pass for PIN and other prompts */\nbool\nget_user_pass_cr(struct user_pass *up, const char *auth_file, const char *prefix,\n                 const unsigned int flags, const char *unused)\n{\n    (void)unused;\n    bool ret = true;\n    if (!strcmp(prefix, \"pkcs11-id-request\") && flags & GET_USER_PASS_NEED_STR)\n    {\n        assert_true(pkcs11_id_management);\n        strncpynt(up->password, pkcs11_id_current, sizeof(up->password));\n    }\n    else if (flags & GET_USER_PASS_PASSWORD_ONLY)\n    {\n        snprintf(up->password, sizeof(up->password), \"%s\", PIN);\n    }\n    else\n    {\n        msg(M_NONFATAL, \"ERROR: get_user_pass called with unknown request <%s> ignored\", prefix);\n        ret = false;\n    }\n\n    return ret;\n}\n\n/* Compute sha1 hash of a X509 certificate */\nstatic void\nsha1_fingerprint(X509 *x509, uint8_t *hash, int capacity)\n{\n    assert_true(capacity >= EVP_MD_size(EVP_sha1()));\n    assert_int_equal(X509_digest(x509, EVP_sha1(), hash, NULL), 1);\n}\n\n#if defined(HAVE_XKEY_PROVIDER)\nOSSL_LIB_CTX *tls_libctx;\nOSSL_PROVIDER *prov[2];\n#endif\n\nstatic int\ninit(void **state)\n{\n    (void)state;\n\n    umask(0077); /* ensure all files and directories we create get user only access */\n    char config[256];\n\n    init_cert_data();\n    if (!mkdtemp(softhsm2_tokens_path))\n    {\n        fail_msg(\"make tmpdir using template <%s> failed (error = %d)\", softhsm2_tokens_path,\n                 errno);\n    }\n\n    int fd = mkstemp(softhsm2_conf_path);\n    if (fd < 0)\n    {\n        fail_msg(\"make tmpfile using template <%s> failed (error = %d)\", softhsm2_conf_path, errno);\n    }\n    snprintf(config, sizeof(config), \"directories.tokendir=%s/\", softhsm2_tokens_path);\n    assert_int_equal(write(fd, config, strlen(config)), strlen(config));\n    close(fd);\n\n    /* environment */\n    setenv(\"SOFTHSM2_CONF\", softhsm2_conf_path, 1);\n    es = env_set_create(NULL);\n    setenv_str(es, \"SOFTHSM2_CONF\", softhsm2_conf_path);\n    setenv_str(es, \"GNUTLS_PIN\", PIN);\n\n    /* init the token using the temporary location as storage */\n    struct argv a = argv_new();\n    argv_printf(&a, \"%s --init-token --free --label \\\"%s\\\" --so-pin %s --pin %s\",\n                SOFTHSM2_UTIL_PATH, token_name, PIN, PIN);\n    assert_true(openvpn_execve_check(&a, es, 0, \"Failed to initialize token\"));\n\n    /* Import certificates and keys in our test database into the token */\n    char cert[] = \"cert_XXXXXX\";\n    char key[] = \"key_XXXXXX\";\n    int cert_fd = mkstemp(cert);\n    int key_fd = mkstemp(key);\n    if (cert_fd < 0 || key_fd < 0)\n    {\n        fail_msg(\"make tmpfile for certificate or key data failed (error = %d)\", errno);\n    }\n\n    for (struct test_cert *c = certs; c->cert; c++)\n    {\n        /* fill-in the hash of the cert */\n        BIO *buf = BIO_new_mem_buf(c->cert, -1);\n        X509 *x509 = NULL;\n        if (buf)\n        {\n            x509 = PEM_read_bio_X509(buf, NULL, NULL, NULL);\n            BIO_free(buf);\n        }\n        assert_non_null(x509);\n        sha1_fingerprint(x509, c->hash, HASHSIZE);\n        X509_free(x509);\n\n        /* we load all cert/key pairs even if expired as\n         * signing should still work */\n        assert_int_equal(write(cert_fd, c->cert, strlen(c->cert)), strlen(c->cert));\n        assert_int_equal(write(key_fd, c->key, strlen(c->key)), strlen(c->key));\n\n        argv_free(&a);\n        a = argv_new();\n        /* Use numcerts+1 as a unique id of the object  -- same id for matching cert and key */\n        argv_printf(\n            &a, \"%s --provider %s --load-certificate %s --label \\\"%s\\\" --id %08x --login --write\",\n            P11TOOL_PATH, SOFTHSM2_MODULE_PATH, cert, c->friendly_name, num_certs + 1);\n        assert_true(openvpn_execve_check(&a, es, 0, \"Failed to upload certificate into token\"));\n\n        argv_free(&a);\n        a = argv_new();\n        argv_printf(&a,\n                    \"%s --provider %s --load-privkey %s --label \\\"%s\\\" --id %08x --login --write\",\n                    P11TOOL_PATH, SOFTHSM2_MODULE_PATH, key, c->friendly_name, num_certs + 1);\n        assert_true(openvpn_execve_check(&a, es, 0, \"Failed to upload key into token\"));\n\n        assert_int_equal(ftruncate(cert_fd, 0), 0);\n        assert_int_equal(ftruncate(key_fd, 0), 0);\n        assert_int_equal(lseek(cert_fd, 0, SEEK_SET), 0);\n        assert_int_equal(lseek(key_fd, 0, SEEK_SET), 0);\n        num_certs++;\n    }\n\n    argv_free(&a);\n    close(cert_fd);\n    close(key_fd);\n    unlink(cert);\n    unlink(key);\n    return 0;\n}\n\nstatic int\ncleanup(void **state)\n{\n    (void)state;\n    struct argv a = argv_new();\n\n    argv_printf(&a, \"%s --delete-token --token \\\"%s\\\"\", SOFTHSM2_UTIL_PATH, token_name);\n    assert_true(openvpn_execve_check(&a, es, 0, \"Failed to delete token\"));\n    argv_free(&a);\n\n    rmdir(softhsm2_tokens_path); /* this must be empty after delete token */\n    unlink(softhsm2_conf_path);\n    for (struct test_cert *c = certs; c->cert; c++)\n    {\n        free(c->p11_id);\n        c->p11_id = NULL;\n    }\n    env_set_destroy(es);\n    return 0;\n}\n\nstatic int\nsetup_pkcs11(void **state)\n{\n#if defined(HAVE_XKEY_PROVIDER)\n    /* Initialize providers in a way matching what OpenVPN core does */\n    tls_libctx = OSSL_LIB_CTX_new();\n    prov[0] = OSSL_PROVIDER_load(tls_libctx, \"default\");\n    OSSL_PROVIDER_add_builtin(tls_libctx, \"ovpn.xkey\", xkey_provider_init);\n    prov[1] = OSSL_PROVIDER_load(tls_libctx, \"ovpn.xkey\");\n    assert_non_null(prov[1]);\n\n    /* set default propq as we do in ssl_openssl.c */\n    EVP_set_default_properties(tls_libctx, \"?provider!=ovpn.xkey\");\n#endif\n    pkcs11_initialize(true, 60); /* protected auth enabled, pin-cache = 60s */\n    pkcs11_addProvider(SOFTHSM2_MODULE_PATH, false, 0, false);\n    return 0;\n}\n\nstatic int\nteardown_pkcs11(void **state)\n{\n    pkcs11_terminate();\n#if defined(HAVE_XKEY_PROVIDER)\n    for (size_t i = 0; i < SIZE(prov); i++)\n    {\n        if (prov[i])\n        {\n            OSSL_PROVIDER_unload(prov[i]);\n            prov[i] = NULL;\n        }\n    }\n    OSSL_LIB_CTX_free(tls_libctx);\n#endif\n    return 0;\n}\n\nstatic struct test_cert *\nlookup_cert_byhash(uint8_t *sha1)\n{\n    struct test_cert *c = certs;\n    while (c->cert && memcmp(c->hash, sha1, HASHSIZE))\n    {\n        c++;\n    }\n    return c->cert ? c : NULL;\n}\n\n/* Enumerate usable items in the token and collect their pkcs11-ids */\nstatic void\ntest_pkcs11_ids(void **state)\n{\n    char *p11_id = NULL;\n    char *base64 = NULL;\n\n    int n = pkcs11_management_id_count();\n    assert_int_equal(n, num_certs);\n\n    for (int i = 0; i < n; i++)\n    {\n        X509 *x509 = NULL;\n        uint8_t sha1[HASHSIZE];\n\n        /* We use the management interface functions as a quick way\n         * to enumerate objects available for private key operations */\n        if (!pkcs11_management_id_get(i, &p11_id, &base64))\n        {\n            fail_msg(\"Failed to get pkcs11-id for index (%d) from pkcs11-helper\", i);\n        }\n        /* decode the base64 data and convert to X509 and get its sha1 fingerprint */\n        unsigned char *der = malloc(strlen(base64));\n        assert_non_null(der);\n        int derlen = openvpn_base64_decode(base64, der, (int)strlen(base64));\n        free(base64);\n        assert_true(derlen > 0);\n\n        const unsigned char *ppin = der; /* alias needed as d2i_X509 alters the pointer */\n        assert_non_null(d2i_X509(&x509, &ppin, derlen));\n        sha1_fingerprint(x509, sha1, HASHSIZE);\n        X509_free(x509);\n        free(der);\n\n        /* Save the pkcs11-id of this ceritificate in our database */\n        struct test_cert *c = lookup_cert_byhash(sha1);\n        assert_non_null(c);\n        c->p11_id = p11_id; /* p11_id is freed in cleanup */\n        assert_memory_equal(c->hash, sha1, HASHSIZE);\n    }\n    /* check whether all certs in our db were found by pkcs11-helper*/\n    for (struct test_cert *c = certs; c->cert; c++)\n    {\n        if (!c->p11_id)\n        {\n            fail_msg(\"Certificate <%s> not enumerated by pkcs11-helper\", c->friendly_name);\n        }\n    }\n}\n\n/* For each available pkcs11-id, load it into an SSL_CTX\n * and test signing with it.\n */\nstatic void\ntest_tls_ctx_use_pkcs11(void **state)\n{\n    (void)state;\n    struct tls_root_ctx tls_ctx = { 0 };\n    uint8_t sha1[HASHSIZE];\n    for (struct test_cert *c = certs; c->cert; c++)\n    {\n#ifdef HAVE_XKEY_PROVIDER\n        tls_ctx.ctx = SSL_CTX_new_ex(tls_libctx, NULL, SSLv23_client_method());\n#else\n        tls_ctx.ctx = SSL_CTX_new(SSLv23_client_method());\n#endif\n        if (pkcs11_id_management)\n        {\n            /* The management callback will return pkcs11_id_current as the\n             * selection. Set it here as the current certificate's p11_id\n             */\n            pkcs11_id_current = c->p11_id;\n            tls_ctx_use_pkcs11(&tls_ctx, 1, NULL);\n        }\n        else\n        {\n            /* directly use c->p11_id */\n            tls_ctx_use_pkcs11(&tls_ctx, 0, c->p11_id);\n        }\n\n        /* check that the cert set in SSL_CTX is what we intended */\n        X509 *x509 = SSL_CTX_get0_certificate(tls_ctx.ctx);\n        assert_non_null(x509);\n        sha1_fingerprint(x509, sha1, HASHSIZE);\n        assert_memory_equal(sha1, c->hash, HASHSIZE);\n\n        /* Test signing with the private key in SSL_CTX */\n        EVP_PKEY *pubkey = X509_get0_pubkey(x509);\n        EVP_PKEY *privkey = SSL_CTX_get0_privatekey(tls_ctx.ctx);\n        assert_non_null(pubkey);\n        assert_non_null(privkey);\n#ifdef HAVE_XKEY_PROVIDER\n        /* this will exercise signing via pkcs11 backend */\n        assert_int_equal(digest_sign_verify(privkey, pubkey), 1);\n#else\n        if (!SSL_CTX_check_private_key(tls_ctx.ctx))\n        {\n            fail_msg(\"Certificate and private key in ssl_ctx do not match for <%s>\",\n                     c->friendly_name);\n            return;\n        }\n#endif\n        SSL_CTX_free(tls_ctx.ctx);\n    }\n}\n\n/* same test as test_tls_ctx_use_pkcs11, with id selected via management i/f */\nstatic void\ntest_tls_ctx_use_pkcs11__management(void **state)\n{\n    pkcs11_id_management = true;\n    test_tls_ctx_use_pkcs11(state);\n}\n\nint\nmain(void)\n{\n    openvpn_unit_test_setup();\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test_setup_teardown(test_pkcs11_ids, setup_pkcs11, teardown_pkcs11),\n        cmocka_unit_test_setup_teardown(test_tls_ctx_use_pkcs11, setup_pkcs11, teardown_pkcs11),\n        cmocka_unit_test_setup_teardown(test_tls_ctx_use_pkcs11__management, setup_pkcs11,\n                                        teardown_pkcs11),\n    };\n    int ret = cmocka_run_group_tests_name(\"pkcs11_tests\", tests, init, cleanup);\n\n    return ret;\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_pkt.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <setjmp.h>\n#include <cmocka.h>\n#include \"test_common.h\"\n\n#include \"crypto.h\"\n#include \"options.h\"\n#include \"ssl_backend.h\"\n#include \"ssl_pkt.h\"\n#include \"tls_crypt.h\"\n\n#include \"mss.h\"\n#include \"reliable.h\"\n\nint\nparse_line(const char *line, char **p, const int n, const char *file, const int line_num,\n           msglvl_t msglevel, struct gc_arena *gc)\n{\n    /* Dummy function to get the linker happy, should never be called */\n    assert_true(false);\n    return 0;\n}\n\n/* Define this function here as dummy since including the ssl_*.c files\n * leads to having to include even more unrelated code */\nbool\nkey_state_export_keying_material(struct tls_session *session, const char *label, size_t label_size,\n                                 void *ekm, size_t ekm_size)\n{\n    ASSERT(0);\n}\n\nconst char *\nprint_link_socket_actual(const struct link_socket_actual *act, struct gc_arena *gc)\n{\n    return \"dummy print_link_socket_actual from unit test\";\n}\n\nconst char static_key[] = \"<tls-auth>\\n\"\n                          \"-----BEGIN OpenVPN Static key V1-----\\n\"\n                          \"37268ea8f95d7f71f9fb8fc03770c460\\n\"\n                          \"daf714a483d815c013ce0a537efc18f2\\n\"\n                          \"8f4f172669d9e6a413bac6741d8ea054\\n\"\n                          \"00f49b7fd6326470f23798c606bf53d4\\n\"\n                          \"de63ebc64ec59d57ce5d04d5b62e68b5\\n\"\n                          \"3ca6e5354351097fa370446c4d269f18\\n\"\n                          \"7bb6ae54af2dc70ff7317fe2f8754b82\\n\"\n                          \"82aad4202f9fa42c8640245d883e2c54\\n\"\n                          \"a0c1c489a036cf3a8964d8d289c1583b\\n\"\n                          \"9447c262b1da5fd167a5d27bd5ac5143\\n\"\n                          \"17bc2343a31a2efc38dd920d910375f5\\n\"\n                          \"1c2e27f3afd36c49269da079f7ce466e\\n\"\n                          \"bb0f9ad13e9bbb4665974e6bc24b513c\\n\"\n                          \"5700393bf4a3e7f967e2f384069ac8a8\\n\"\n                          \"e78b18b15604993fd16515cce9c0f3e4\\n\"\n                          \"2b4126b999005ade802797b0eeb8b9e6\\n\"\n                          \"-----END OpenVPN Static key V1-----\\n\"\n                          \"</tls-auth>\\n\";\n\nconst uint8_t client_reset_v2_none[] = { 0x38, 0x68, 0x91, 0x92, 0x3f, 0xa3, 0x10,\n                                         0x34, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00 };\n\nconst uint8_t client_reset_v2_tls_auth[] = { 0x38, 0xde, 0x69, 0x4c, 0x5c, 0x7b, 0xfb, 0xa2, 0x74,\n                                             0x93, 0x53, 0x7c, 0x1d, 0xed, 0x4e, 0x78, 0x15, 0x29,\n                                             0xae, 0x7c, 0xfe, 0x4b, 0x8c, 0x6d, 0x6b, 0x2b, 0x51,\n                                             0xf0, 0x5a, 0x00, 0x00, 0x00, 0x01, 0x61, 0xd3, 0xbf,\n                                             0x6c, 0x00, 0x00, 0x00, 0x00, 0x00 };\n\nconst uint8_t client_reset_v2_tls_crypt[] = {\n    0x38, 0xf4, 0x19, 0xcb, 0x12, 0xd1, 0xf9, 0xe4, 0x8f, 0x00, 0x00, 0x00, 0x01, 0x61,\n    0xd3, 0xf8, 0xe1, 0x33, 0x02, 0x06, 0xf5, 0x68, 0x02, 0xbe, 0x44, 0xfb, 0xed, 0x90,\n    0x50, 0x64, 0xe3, 0xdb, 0x43, 0x41, 0x6b, 0xec, 0x5e, 0x52, 0x67, 0x19, 0x46, 0x2b,\n    0x7e, 0xb9, 0x0c, 0x96, 0xde, 0xfc, 0x9b, 0x05, 0xc4, 0x48, 0x79, 0xf7\n};\n\n/* Valid tls-auth client CONTROL_V1 packet with random server id */\nconst uint8_t client_ack_tls_auth_randomid[] = {\n    0x20, 0x14, 0x01, 0x4e, 0xbc, 0x80, 0xc6, 0x14, 0x2b, 0x7b, 0xc8, 0x76, 0xfb, 0xc5, 0x2e, 0x27,\n    0xb1, 0xc5, 0x07, 0x35, 0x5b, 0xb6, 0x00, 0x6b, 0xae, 0x71, 0xba, 0x4e, 0x38, 0x00, 0x00, 0x00,\n    0x03, 0x61, 0xd3, 0xff, 0x53, 0x00, 0x00, 0x00, 0x00, 0x01, 0x16, 0x03, 0x01, 0x01, 0x0c, 0x01,\n    0x00, 0x01, 0x08, 0x03, 0x03, 0x8c, 0xaa, 0xac, 0x3a, 0x1a, 0x07, 0xbd, 0xe7, 0xb7, 0x50, 0x06,\n    0x9b, 0x94, 0x0c, 0x34, 0x4b, 0x5a, 0x35, 0xca, 0xc4, 0x79, 0xbd, 0xc9, 0x09, 0xb0, 0x7b, 0xd9,\n    0xee, 0xbb, 0x7d, 0xe7, 0x25, 0x20, 0x39, 0x38, 0xe2, 0x18, 0x33, 0x36, 0x14, 0x9f, 0x34, 0xf0,\n    0x44, 0x59, 0x96, 0x8d, 0x0e, 0xd2, 0x47, 0x76, 0x64, 0x88, 0x59, 0xe9, 0x38, 0x03, 0x97, 0x96,\n    0x98, 0x45, 0xfb, 0xf5, 0xff, 0x23, 0x00, 0x32, 0x13, 0x02, 0x13, 0x03, 0x13, 0x01, 0xc0, 0x2c,\n    0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, 0x00, 0x9e,\n    0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, 0xc0, 0x14,\n    0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0xff, 0x01, 0x00, 0x00, 0x8d, 0x00, 0x0b,\n    0x00, 0x04, 0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x1d, 0x00, 0x17,\n    0x00, 0x1e, 0x00, 0x19, 0x00, 0x18, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x0d,\n    0x00, 0x30, 0x00, 0x2e, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x07, 0x08, 0x08, 0x08, 0x09,\n    0x08, 0x0a, 0x08, 0x0b, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01,\n    0x03, 0x03, 0x02, 0x03, 0x03, 0x01, 0x02, 0x01, 0x03, 0x02, 0x02, 0x02, 0x04, 0x02, 0x05, 0x02,\n    0x06, 0x02, 0x00, 0x2b, 0x00, 0x05, 0x04, 0x03, 0x04, 0x03, 0x03, 0x00, 0x2d, 0x00, 0x02, 0x01,\n    0x01, 0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x0e, 0xc9, 0x7a, 0xff, 0x58,\n    0xdb, 0x56, 0xf6, 0x40, 0xd1, 0xed, 0xdb, 0x91, 0x81, 0xd6, 0xef, 0x83, 0x86, 0x8a, 0xb2, 0x3d,\n    0x88, 0x92, 0x3f, 0xd8, 0x51, 0x9c, 0xd6, 0x26, 0x56, 0x33, 0x6b\n};\n\n/* This is a truncated packet as we do not care for the TLS payload in the\n * unit test */\nconst uint8_t client_control_with_ack[] = { 0x20, 0x78, 0x19, 0xbf, 0x2e, 0xbc, 0xd1, 0x9a,\n                                            0x45, 0x01, 0x00, 0x00, 0x00, 0x00, 0xea, 0xfe,\n                                            0xbf, 0xa4, 0x41, 0x8a, 0xe3, 0x1b, 0x00, 0x00,\n                                            0x00, 0x01, 0x16, 0x03, 0x01 };\n\nconst uint8_t client_ack_none_random_id[] = { 0x28, 0xae, 0xb9, 0xaf, 0xe1, 0xf0, 0x1d, 0x79,\n                                              0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0xdd, 0x85,\n                                              0xdb, 0x53, 0x56, 0x23, 0xb0, 0x2e };\n\n/* no tls-auth, P_ACK_V1, acks 0,1, and 2 */\nconst uint8_t client_ack_123_none_random_id[] = {\n    0x28,\n    0xae, 0xb9, 0xaf, 0xe1, 0xf0, 0x1d, 0x79, 0xc8,\n    0x03,\n    0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x01,\n    0x00, 0x00, 0x00, 0x02,\n    0xdd, 0x85, 0xdb, 0x53, 0x56, 0x23, 0xb0, 0x2e\n};\n\n/* no tls-auth, P_CONTROL_V1, acks 0, msg-id 2 */\nconst uint8_t client_control_none_random_id[] = {\n    0x20,\n    0xae, 0xb9, 0xaf, 0xe1, 0xf0, 0x1d, 0x79, 0xc8,\n    0x01,\n    0x00, 0x00, 0x00, 0x00,\n    0x02\n};\n\n\nstruct tls_auth_standalone\ninit_tas_auth(int key_direction)\n{\n    struct tls_auth_standalone tas = { 0 };\n    struct frame frame = { .buf = { .headroom = 200, .payload_size = 1400 }, 0 };\n    tas.frame = frame;\n\n    tas.tls_wrap.mode = TLS_WRAP_AUTH;\n    /* we ignore packet ids on for the first packet check */\n    tas.tls_wrap.opt.flags |= (CO_IGNORE_PACKET_ID | CO_PACKET_ID_LONG_FORM);\n\n    struct key_type tls_crypt_kt;\n    init_key_type(&tls_crypt_kt, \"none\", \"SHA1\", true, false);\n\n    crypto_read_openvpn_key(&tls_crypt_kt, &tas.tls_wrap.opt.key_ctx_bi, static_key, true,\n                            key_direction, \"Control Channel Authentication\", \"tls-auth\", NULL);\n    tas.workbuf = alloc_buf(1600);\n\n    return tas;\n}\n\nstruct tls_auth_standalone\ninit_tas_crypt(bool server)\n{\n    struct tls_auth_standalone tas = { 0 };\n    tas.tls_wrap.mode = TLS_WRAP_CRYPT;\n    tas.tls_wrap.opt.flags |= (CO_IGNORE_PACKET_ID | CO_PACKET_ID_LONG_FORM);\n\n    tls_crypt_init_key(&tas.tls_wrap.opt.key_ctx_bi, &tas.tls_wrap.original_wrap_keydata,\n                       static_key, true, server);\n    tas.workbuf = alloc_buf(1600);\n    tas.tls_wrap.work = alloc_buf(1600);\n\n    return tas;\n}\n\nvoid\nfree_tas(struct tls_auth_standalone *tas)\n{\n    /* Not some of these might be null pointers but calling free on null\n     * pointers is a noop */\n    free_key_ctx_bi(&tas->tls_wrap.opt.key_ctx_bi);\n    free_buf(&tas->workbuf);\n    free_buf(&tas->tls_wrap.work);\n}\n\nvoid\ntest_tls_decrypt_lite_crypt(void **ut_state)\n{\n    struct link_socket_actual from = { 0 };\n    struct tls_pre_decrypt_state state = { 0 };\n\n    struct tls_auth_standalone tas = init_tas_crypt(true);\n    struct buffer buf = alloc_buf(1024);\n\n    /* tls-auth should be invalid */\n    buf_write(&buf, client_reset_v2_tls_auth, sizeof(client_reset_v2_tls_auth));\n    enum first_packet_verdict verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_INVALID);\n    free_tls_pre_decrypt_state(&state);\n\n    /* as well as the too short normal reset */\n    buf_reset_len(&buf);\n    buf_write(&buf, client_reset_v2_none, sizeof(client_reset_v2_none));\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_INVALID);\n    free_tls_pre_decrypt_state(&state);\n\n    /* the tls-crypt should validate */\n    buf_reset_len(&buf);\n    buf_write(&buf, client_reset_v2_tls_crypt, sizeof(client_reset_v2_tls_crypt));\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_RESET_V2);\n    free_tls_pre_decrypt_state(&state);\n\n    /* flip a byte in various places */\n    for (size_t i = 0; i < sizeof(client_reset_v2_tls_crypt); i++)\n    {\n        buf_reset_len(&buf);\n        buf_write(&buf, client_reset_v2_tls_crypt, sizeof(client_reset_v2_tls_crypt));\n        (BPTR(&buf))[i] = 0x23;\n        verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n        assert_int_equal(verdict, VERDICT_INVALID);\n        free_tls_pre_decrypt_state(&state);\n    }\n\n    free_key_ctx_bi(&tas.tls_wrap.opt.key_ctx_bi);\n    free_tas(&tas);\n    free_buf(&buf);\n}\n\n\nvoid\ntest_tls_decrypt_lite_auth(void **ut_state)\n{\n    struct link_socket_actual from = { 0 };\n    struct tls_auth_standalone tas = { 0 };\n    struct tls_pre_decrypt_state state = { 0 };\n    enum first_packet_verdict verdict;\n\n    struct buffer buf = alloc_buf(1024);\n    tas = init_tas_auth(KEY_DIRECTION_NORMAL);\n\n    /* Packet to short to contain the hmac */\n    buf_write(&buf, client_reset_v2_none, sizeof(client_reset_v2_none));\n\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_INVALID);\n    free_tls_pre_decrypt_state(&state);\n\n    /* Valid tls-auth packet, should validate */\n    buf_reset_len(&buf);\n    buf_write(&buf, client_reset_v2_tls_auth, sizeof(client_reset_v2_tls_auth));\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_RESET_V2);\n    free_tls_pre_decrypt_state(&state);\n\n    /* The pre decrypt function should not modify the buffer, so calling it\n     * again should have the same result */\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_RESET_V2);\n\n    /* and buf memory should be equal */\n    assert_memory_equal(BPTR(&buf), client_reset_v2_tls_auth, sizeof(client_reset_v2_tls_auth));\n    free_tls_pre_decrypt_state(&state);\n\n    buf_reset_len(&buf);\n    buf_write(&buf, client_ack_tls_auth_randomid, sizeof(client_ack_tls_auth_randomid));\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_CONTROL_V1);\n    free_tls_pre_decrypt_state(&state);\n\n    /* flip a byte in the hmac */\n    (BPTR(&buf))[20] = 0x23;\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_INVALID);\n    free_tls_pre_decrypt_state(&state);\n\n    /* Wrong key direction gives a wrong hmac key and should not validate */\n    free_key_ctx_bi(&tas.tls_wrap.opt.key_ctx_bi);\n    free_tas(&tas);\n    tas = init_tas_auth(KEY_DIRECTION_INVERSE);\n\n    buf_reset_len(&buf);\n    buf_write(&buf, client_reset_v2_tls_auth, sizeof(client_reset_v2_tls_auth));\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_INVALID);\n\n    free_tls_pre_decrypt_state(&state);\n    free_tas(&tas);\n    free_buf(&buf);\n}\n\nvoid\ntest_tls_decrypt_lite_none(void **ut_state)\n{\n    struct link_socket_actual from = { 0 };\n    struct tls_auth_standalone tas = { 0 };\n    struct tls_pre_decrypt_state state = { 0 };\n\n    struct buffer buf = alloc_buf(1024);\n    buf_write(&buf, client_reset_v2_tls_auth, sizeof(client_reset_v2_tls_auth));\n\n    tas.tls_wrap.mode = TLS_WRAP_NONE;\n\n    /* the method will not do additional test, so the tls-auth and tls-crypt\n     * reset will be accepted */\n    enum first_packet_verdict verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_RESET_V2);\n    free_tls_pre_decrypt_state(&state);\n\n    buf_reset_len(&buf);\n    buf_write(&buf, client_reset_v2_none, sizeof(client_reset_v2_none));\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_RESET_V2);\n    free_tls_pre_decrypt_state(&state);\n\n    buf_reset_len(&buf);\n    buf_write(&buf, client_reset_v2_tls_crypt, sizeof(client_reset_v2_none));\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_RESET_V2);\n    free_tls_pre_decrypt_state(&state);\n\n    /* This is not a reset packet and should trigger the other response */\n    buf_reset_len(&buf);\n    buf_write(&buf, client_ack_tls_auth_randomid, sizeof(client_ack_tls_auth_randomid));\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_CONTROL_V1);\n\n    free_tls_pre_decrypt_state(&state);\n    free_buf(&buf);\n    free_tas(&tas);\n}\n\nstatic void\ntest_parse_ack(void **ut_state)\n{\n    struct buffer buf = alloc_buf(1024);\n    buf_write(&buf, client_control_with_ack, sizeof(client_control_with_ack));\n\n    /* skip over op code and peer session id */\n    buf_advance(&buf, 9);\n\n    struct reliable_ack ack;\n    struct session_id sid;\n    bool ret;\n\n    ret = reliable_ack_parse(&buf, &ack, &sid);\n    assert_true(ret);\n\n    assert_int_equal(ack.len, 1);\n    assert_int_equal(ack.packet_id[0], 0);\n\n    struct session_id expected_id = { .id = { 0xea, 0xfe, 0xbf, 0xa4, 0x41, 0x8a, 0xe3, 0x1b } };\n    assert_memory_equal(&sid, &expected_id, SID_SIZE);\n\n    buf_reset_len(&buf);\n    buf_write(&buf, client_ack_none_random_id, sizeof(client_ack_none_random_id));\n\n    /* skip over op code and peer session id */\n    buf_advance(&buf, 9);\n    ret = reliable_ack_parse(&buf, &ack, &sid);\n    assert_true(ret);\n\n    assert_int_equal(ack.len, 1);\n    assert_int_equal(ack.packet_id[0], 0);\n\n    struct session_id expected_id2 = { .id = { 0xdd, 0x85, 0xdb, 0x53, 0x56, 0x23, 0xb0, 0x2e } };\n    assert_memory_equal(&sid, &expected_id2, SID_SIZE);\n\n    buf_reset_len(&buf);\n    buf_write(&buf, client_reset_v2_none, sizeof(client_reset_v2_none));\n\n    /* skip over op code and peer session id */\n    buf_advance(&buf, 9);\n    ret = reliable_ack_parse(&buf, &ack, &sid);\n\n    free_buf(&buf);\n}\n\nstatic void\ntest_verify_hmac_tls_auth(void **ut_state)\n{\n    hmac_ctx_t *hmac = session_id_hmac_init();\n\n    struct link_socket_actual from = { 0 };\n    from.dest.addr.sa.sa_family = AF_INET;\n    from.dest.addr.in4.sin_addr.s_addr = ntohl(0x01020304);\n    struct tls_auth_standalone tas = { 0 };\n    struct tls_pre_decrypt_state state = { 0 };\n\n    struct buffer buf = alloc_buf(1024);\n    enum first_packet_verdict verdict;\n\n    tas = init_tas_auth(KEY_DIRECTION_NORMAL);\n\n    buf_reset_len(&buf);\n    buf_write(&buf, client_ack_tls_auth_randomid, sizeof(client_ack_tls_auth_randomid));\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_CONTROL_V1);\n\n    /* This is a valid packet but containing a random id instead of an HMAC id*/\n    bool valid = check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, false);\n    assert_false(valid);\n\n    free_tls_pre_decrypt_state(&state);\n    free_buf(&buf);\n    free_tas(&tas);\n    hmac_ctx_cleanup(hmac);\n    hmac_ctx_free(hmac);\n}\n\nstatic void\ntest_verify_hmac_none(void **ut_state)\n{\n    now = 1000;\n    hmac_ctx_t *hmac = session_id_hmac_init();\n\n    struct link_socket_actual from = { 0 };\n    from.dest.addr.sa.sa_family = AF_INET;\n    from.dest.addr.in4.sin_addr.s_addr = ntohl(0x01020304);\n\n    struct tls_auth_standalone tas = { 0 };\n    struct tls_pre_decrypt_state state = { 0 };\n\n    struct buffer buf = alloc_buf(1024);\n    enum first_packet_verdict verdict;\n\n    tas.tls_wrap.mode = TLS_WRAP_NONE;\n\n    buf_reset_len(&buf);\n    buf_write(&buf, client_ack_none_random_id, sizeof(client_ack_none_random_id));\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_ACK_V1);\n\n    /* This packet has a random hmac, so it should fail to validate */\n    bool valid = check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true);\n    assert_false(valid);\n\n    struct session_id client_id = { { 0xae, 0xb9, 0xaf, 0xe1, 0xf0, 0x1d, 0x79, 0xc8 } };\n    assert_memory_equal(&client_id, &state.peer_session_id, sizeof(struct session_id));\n\n    struct session_id expected_id = calculate_session_id_hmac(client_id, &from.dest, hmac, 30, 0);\n\n    free_tls_pre_decrypt_state(&state);\n    buf_reset_len(&buf);\n\n    /* Write the packet again into the buffer but this time, replacing the peer packet\n     * id with the expected one */\n    buf_write(&buf, client_ack_none_random_id, sizeof(client_ack_none_random_id) - 8);\n    buf_write(&buf, expected_id.id, 8);\n\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_ACK_V1);\n    valid = check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true);\n\n    assert_true(valid);\n\n    /* Our handwindow is 30 so the slices are half of that, so they are\n     * (975,990), (990, 1005), (1005, 1020), (1020, 1035), (1035, 1050)\n     * So setting time to the two future ones should work\n     */\n    now = 980;\n    assert_false(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true));\n    now = 1040;\n    assert_false(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true));\n    now = 1002;\n    assert_true(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true));\n    now = 1022;\n    assert_true(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true));\n    now = 1010;\n    assert_true(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true));\n\n    /* Changing the IP address should make this invalid */\n    from.dest.addr.in4.sin_addr.s_addr = ntohl(0x01020305);\n    assert_false(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true));\n\n    /* Change to the correct one again */\n    from.dest.addr.in4.sin_addr.s_addr = ntohl(0x01020304);\n    assert_true(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true));\n\n    /* Modify the peer id, should now fail hmac verification */\n    buf_inc_len(&buf, -4);\n    buf_write_u32(&buf, 0x12345678);\n\n    free_tls_pre_decrypt_state(&state);\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_ACK_V1);\n    assert_false(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true));\n\n    free_tls_pre_decrypt_state(&state);\n    free_buf(&buf);\n    hmac_ctx_cleanup(hmac);\n    hmac_ctx_free(hmac);\n}\n\nstatic void\ntest_verify_hmac_none_out_of_range_ack(void **ut_state)\n{\n    hmac_ctx_t *hmac = session_id_hmac_init();\n\n    struct link_socket_actual from = { 0 };\n    from.dest.addr.sa.sa_family = AF_INET;\n\n    struct tls_auth_standalone tas = { 0 };\n    struct tls_pre_decrypt_state state = { 0 };\n\n    struct buffer buf = alloc_buf(1024);\n    enum first_packet_verdict verdict;\n\n    tas.tls_wrap.mode = TLS_WRAP_NONE;\n\n    buf_reset_len(&buf);\n    buf_write(&buf, client_ack_123_none_random_id, sizeof(client_ack_123_none_random_id));\n\n\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_ACK_V1);\n\n    /* should fail because it acks 2 */\n    bool valid = check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true);\n    assert_false(valid);\n    free_tls_pre_decrypt_state(&state);\n\n    /* Try test with the control with a too high message id now */\n    buf_reset_len(&buf);\n    buf_write(&buf, client_control_none_random_id, sizeof(client_control_none_random_id));\n\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_CONTROL_V1);\n\n    /* should fail because it has message id 2 */\n    valid = check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true);\n    assert_false(valid);\n\n    free_tls_pre_decrypt_state(&state);\n    free_buf(&buf);\n    hmac_ctx_cleanup(hmac);\n    hmac_ctx_free(hmac);\n}\n\nstatic hmac_ctx_t *\ninit_static_hmac(void)\n{\n    ASSERT(md_valid(\"SHA256\"));\n    hmac_ctx_t *hmac_ctx = hmac_ctx_new();\n\n    uint8_t key[SHA256_DIGEST_LENGTH] = { 1, 2, 3, 0 };\n\n    hmac_ctx_init(hmac_ctx, key, \"SHA256\");\n    return hmac_ctx;\n}\n\nstatic void\ntest_calc_session_id_hmac_static(void **ut_state)\n{\n    hmac_ctx_t *hmac = init_static_hmac();\n    static const int handwindow = 100;\n\n    struct openvpn_sockaddr addr = { 0 };\n\n    addr.addr.in4.sin_family = AF_INET;\n    addr.addr.in4.sin_addr.s_addr = ntohl(0xff000ff);\n    addr.addr.in4.sin_port = ntohs(1195);\n\n    struct session_id client_id = { { 0, 1, 2, 3, 4, 5, 6, 7 } };\n\n    now = 1005;\n    struct session_id server_id = calculate_session_id_hmac(client_id, &addr, hmac, handwindow, 0);\n\n\n    struct session_id expected_server_id = { { 0x84, 0x73, 0x52, 0x2b, 0x5b, 0xa9, 0x2a, 0x70 } };\n    /* We have to deal with different structs here annoyingly */\n    /* Linux has an unsigned short int as family_t and this is field is always\n     * stored in host endianness even though the rest of the struct isn't...,\n     * so Linux little endian differs from all BSD and Linux big endian */\n    if (sizeof(addr.addr.in4.sin_family) == sizeof(unsigned short int) && ntohs(AF_INET) != AF_INET)\n    {\n        struct session_id linuxle = { { 0x8b, 0xeb, 0x3d, 0x20, 0x14, 0x53, 0xbe, 0x0a } };\n        expected_server_id = linuxle;\n    }\n    assert_memory_equal(expected_server_id.id, server_id.id, SID_SIZE);\n\n    struct session_id server_id_m1 =\n        calculate_session_id_hmac(client_id, &addr, hmac, handwindow, -1);\n    struct session_id server_id_p1 =\n        calculate_session_id_hmac(client_id, &addr, hmac, handwindow, 1);\n    struct session_id server_id_p2 =\n        calculate_session_id_hmac(client_id, &addr, hmac, handwindow, 2);\n\n    assert_memory_not_equal(expected_server_id.id, server_id_m1.id, SID_SIZE);\n    assert_memory_not_equal(expected_server_id.id, server_id_p1.id, SID_SIZE);\n\n    /* changing the time puts us into the next hmac time window (handwindow/2=50)\n     * and shifts the ids by one */\n    now = 1062;\n\n    struct session_id server_id2_m2 =\n        calculate_session_id_hmac(client_id, &addr, hmac, handwindow, -2);\n    struct session_id server_id2_m1 =\n        calculate_session_id_hmac(client_id, &addr, hmac, handwindow, -1);\n    struct session_id server_id2 = calculate_session_id_hmac(client_id, &addr, hmac, handwindow, 0);\n    struct session_id server_id2_p1 =\n        calculate_session_id_hmac(client_id, &addr, hmac, handwindow, 1);\n\n    assert_memory_equal(server_id2_m2.id, server_id_m1.id, SID_SIZE);\n    assert_memory_equal(server_id2_m1.id, expected_server_id.id, SID_SIZE);\n    assert_memory_equal(server_id2.id, server_id_p1.id, SID_SIZE);\n    assert_memory_equal(server_id2_p1.id, server_id_p2.id, SID_SIZE);\n\n    hmac_ctx_cleanup(hmac);\n    hmac_ctx_free(hmac);\n}\n\nstatic void\ntest_generate_reset_packet_plain(void **ut_state)\n{\n    struct link_socket_actual from = { 0 };\n    struct tls_auth_standalone tas = { 0 };\n    struct tls_pre_decrypt_state state = { 0 };\n\n    struct session_id client_id = { { 0, 1, 2, 3, 4, 5, 6, 7 } };\n    struct session_id server_id = { { 8, 9, 0, 9, 8, 7, 6, 2 } };\n\n    enum first_packet_verdict verdict;\n\n    tas.tls_wrap.mode = TLS_WRAP_NONE;\n    struct frame frame = { .buf = { .headroom = 200, .payload_size = 1400 }, 0 };\n    tas.frame = frame;\n    tas.workbuf = alloc_buf(1600);\n\n    uint8_t header = 0 | (P_CONTROL_HARD_RESET_CLIENT_V2 << P_OPCODE_SHIFT);\n\n    struct buffer buf =\n        tls_reset_standalone(&tas.tls_wrap, &tas, &client_id, &server_id, header, false);\n\n\n    verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_RESET_V2);\n\n    /* Assure repeated generation of reset is deterministic/stateless*/\n    assert_memory_equal(state.peer_session_id.id, client_id.id, SID_SIZE);\n    struct buffer buf2 =\n        tls_reset_standalone(&tas.tls_wrap, &tas, &client_id, &server_id, header, false);\n    assert_int_equal(BLEN(&buf), BLEN(&buf2));\n    assert_memory_equal(BPTR(&buf), BPTR(&buf2), BLENZ(&buf));\n\n    free_tls_pre_decrypt_state(&state);\n    free_buf(&tas.workbuf);\n}\n\nstatic void\ntest_generate_reset_packet_tls_auth(void **ut_state)\n{\n    struct link_socket_actual from = { 0 };\n    struct tls_pre_decrypt_state state = { 0 };\n\n    struct tls_auth_standalone tas_server = init_tas_auth(KEY_DIRECTION_NORMAL);\n    struct tls_auth_standalone tas_client = init_tas_auth(KEY_DIRECTION_INVERSE);\n\n    packet_id_init(&tas_client.tls_wrap.opt.packet_id, 5, 5, \"UNITTEST\", 0);\n\n    struct session_id client_id = { { 0xab, 1, 2, 3, 4, 5, 6, 0xcd } };\n    struct session_id server_id = { { 8, 9, 0xa, 0xc, 8, 7, 6, 2 } };\n\n    uint8_t header = 0 | (P_CONTROL_HARD_RESET_CLIENT_V2 << P_OPCODE_SHIFT);\n\n    now = 0x22446688;\n    reset_packet_id_send(&tas_client.tls_wrap.opt.packet_id.send);\n    struct buffer buf = tls_reset_standalone(&tas_client.tls_wrap, &tas_client, &client_id,\n                                             &server_id, header, false);\n\n    enum first_packet_verdict verdict = tls_pre_decrypt_lite(&tas_server, &state, &from, &buf);\n    assert_int_equal(verdict, VERDICT_VALID_RESET_V2);\n\n    assert_memory_equal(state.peer_session_id.id, client_id.id, SID_SIZE);\n\n    /* Assure repeated generation of reset is deterministic/stateless*/\n    reset_packet_id_send(&tas_client.tls_wrap.opt.packet_id.send);\n    struct buffer buf2 = tls_reset_standalone(&tas_client.tls_wrap, &tas_client, &client_id,\n                                              &server_id, header, false);\n    assert_int_equal(BLEN(&buf), BLEN(&buf2));\n    assert_memory_equal(BPTR(&buf), BPTR(&buf2), BLENZ(&buf));\n\n    free_tls_pre_decrypt_state(&state);\n\n    packet_id_free(&tas_client.tls_wrap.opt.packet_id);\n\n    free_tas(&tas_client);\n    free_tas(&tas_server);\n}\n\nstatic void\ntest_extract_control_message(void **ut_state)\n{\n    struct gc_arena gc = gc_new();\n    struct buffer input_buf = alloc_buf_gc(1024, &gc);\n\n    /* This message will have a \\0x00 at the end since it is a C string */\n    const char input[] = \"valid control message\\r\\n\\0\\0Invalid\\r\\none\\0valid one again\";\n\n    buf_write(&input_buf, input, sizeof(input));\n    struct buffer cmd1 = extract_command_buffer(&input_buf, &gc);\n    struct buffer cmd2 = extract_command_buffer(&input_buf, &gc);\n    struct buffer cmd3 = extract_command_buffer(&input_buf, &gc);\n    struct buffer cmd4 = extract_command_buffer(&input_buf, &gc);\n    struct buffer cmd5 = extract_command_buffer(&input_buf, &gc);\n\n    assert_string_equal(BSTR(&cmd1), \"valid control message\");\n    /* empty message with just a \\0x00 */\n    assert_int_equal(cmd2.len, 1);\n    assert_string_equal(BSTR(&cmd2), \"\");\n    assert_int_equal(cmd3.len, 0);\n    assert_string_equal(BSTR(&cmd4), \"valid one again\");\n    assert_int_equal(cmd5.len, 0);\n\n    const uint8_t nonull[6] = { 'n', 'o', ' ', 'N', 'U', 'L' };\n    struct buffer nonull_buf = alloc_buf_gc(1024, &gc);\n\n    buf_write(&nonull_buf, nonull, sizeof(nonull));\n    struct buffer nonullcmd = extract_command_buffer(&nonull_buf, &gc);\n    assert_int_equal(nonullcmd.len, 0);\n\n    gc_free(&gc);\n}\n\nint\nmain(void)\n{\n    openvpn_unit_test_setup();\n\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(test_verify_hmac_none),\n        cmocka_unit_test(test_tls_decrypt_lite_none),\n        cmocka_unit_test(test_tls_decrypt_lite_auth),\n        cmocka_unit_test(test_tls_decrypt_lite_crypt),\n        cmocka_unit_test(test_parse_ack),\n        cmocka_unit_test(test_calc_session_id_hmac_static),\n        cmocka_unit_test(test_verify_hmac_tls_auth),\n        cmocka_unit_test(test_verify_hmac_none_out_of_range_ack),\n        cmocka_unit_test(test_generate_reset_packet_plain),\n        cmocka_unit_test(test_generate_reset_packet_tls_auth),\n        cmocka_unit_test(test_extract_control_message)\n    };\n\n    return cmocka_run_group_tests_name(\"pkt tests\", tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_provider.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2021-2026 Selva Nair <selva.nair@gmail.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by the\n *  Free Software Foundation, either version 2 of the License,\n *  or (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n#include \"manage.h\"\n#include \"integer.h\"\n#include \"xkey_common.h\"\n\n#ifdef HAVE_XKEY_PROVIDER\n\n#include <setjmp.h>\n#include <cmocka.h>\n#include <openssl/bio.h>\n#include <openssl/pem.h>\n#include <openssl/core_names.h>\n#include <openssl/evp.h>\n\n#include \"test_common.h\"\n\nstruct management *management; /* global */\nstatic int mgmt_callback_called;\n\n#ifndef _countof\n#define _countof(x) sizeof((x)) / sizeof(*(x))\n#endif\n\nstatic OSSL_PROVIDER *prov[2];\n\n/* public keys for testing -- RSA and EC */\nstatic const char pubkey1[] = \"-----BEGIN PUBLIC KEY-----\\n\"\n                              \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7GWP6RLCGlvmVioIqYI6\\n\"\n                              \"LUR4owA7sJ/nJxBAk+/xzD6gqgSigBsTqeb+gdZwkKjY1N4w2DUA0r5i8Eja/BWN\\n\"\n                              \"xMZtC5nxK4MACtMqIwvlzfk130NhFXKtlZj2cyFBXqDdRyeg1ZrUQagcHVcgcReP\\n\"\n                              \"9yiePgfO7NUOQk8edEeOR53SFCgnLBQQ9dGWtZN0hO/5BN6NSm/fd6vq0VjTRP5a\\n\"\n                              \"BAH/BnqX9/3jV0jh8N9AE59mI1rjVVQ9VDnuAPkS8dLfdC661/CNxt0YWByTIgt1\\n\"\n                              \"+qjW4LUvLbnU/rlPhuJ1SBZg+z/JtDBCKfs7syu5WYFqRvNFg7/91Rr/NwxvW/1h\\n\"\n                              \"8QIDAQAB\\n\"\n                              \"-----END PUBLIC KEY-----\\n\";\n\nstatic const char pubkey2[] = \"-----BEGIN PUBLIC KEY-----\\n\"\n                              \"MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEO85iXW+HgnUkwlj1DohNVw0GsnGIh1gZ\\n\"\n                              \"u95ff1JiUaJIkYNIkZA+hwIPFVH5aJcSCv3SPIeDS2VUAESNKHZJBQ==\\n\"\n                              \"-----END PUBLIC KEY-----\\n\";\n\nstatic const char pubkey3[] = \"-----BEGIN PUBLIC KEY-----\\n\"\n                              \"MCowBQYDK2VwAyEA+q5xjF5hGyyqYZidJdz/0saEQabL3N4wIZJBxNGbgJE=\\n\"\n                              \"-----END PUBLIC KEY-----\";\n\nstatic const char *pubkeys[] = { pubkey1, pubkey2, pubkey3 };\n\nstatic const char *prov_name = \"ovpn.xkey\";\n\nstatic const char *test_msg = \"Lorem ipsum dolor sit amet, consectetur \"\n                              \"adipisici elit, sed eiusmod tempor incidunt \"\n                              \"ut labore et dolore magna aliqua.\";\n\nstatic const char *test_msg_b64 =\n    \"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2ljaS\"\n    \"BlbGl0LCBzZWQgZWl1c21vZCB0ZW1wb3IgaW5jaWR1bnQgdXQgbGFib3JlIGV0IGRv\"\n    \"bG9yZSBtYWduYSBhbGlxdWEu\";\n\n/* Sha256 digest of test_msg excluding NUL terminator */\nstatic const uint8_t test_digest[] = { 0x77, 0x38, 0x65, 0x00, 0x1e, 0x96, 0x48, 0xc6,\n                                       0x57, 0x0b, 0xae, 0xc0, 0xb7, 0x96, 0xf9, 0x66,\n                                       0x4d, 0x5f, 0xd0, 0xb7, 0xdb, 0xf3, 0x3a, 0xbf,\n                                       0x02, 0xcc, 0x78, 0x61, 0x83, 0x20, 0x20, 0xee };\n\nstatic const char *test_digest_b64 = \"dzhlAB6WSMZXC67At5b5Zk1f0Lfb8zq/Asx4YYMgIO4=\";\n\n/* Dummy signature used only to check that the expected callback\n * was successfully exercised. Keep this shorter than 64 bytes\n * --- the smallest size of the actual signature with the above\n * keys.\n */\nstatic const uint8_t good_sig[] = { 0xd8, 0xa7, 0xd9, 0x81, 0xd8, 0xaa, 0xd8, 0xad,\n                                    0x20, 0xd9, 0x8a, 0xd8, 0xa7, 0x20, 0xd8, 0xb3,\n                                    0xd9, 0x85, 0xd8, 0xb3, 0xd9, 0x85, 0x0 };\n\nstatic const char *good_sig_b64 = \"2KfZgdiq2K0g2YrYpyDYs9mF2LPZhQA=\";\n\nstatic EVP_PKEY *\nload_pubkey(const char *pem)\n{\n    BIO *in = BIO_new_mem_buf(pem, -1);\n    assert_non_null(in);\n\n    EVP_PKEY *pkey = PEM_read_bio_PUBKEY(in, NULL, NULL, NULL);\n    assert_non_null(pkey);\n\n    BIO_free(in);\n    return pkey;\n}\n\nstatic void\ninit_test(void)\n{\n    openvpn_unit_test_setup();\n    prov[0] = OSSL_PROVIDER_load(NULL, \"default\");\n    OSSL_PROVIDER_add_builtin(NULL, prov_name, xkey_provider_init);\n    prov[1] = OSSL_PROVIDER_load(NULL, prov_name);\n\n    /* set default propq matching what we use in ssl_openssl.c */\n    EVP_set_default_properties(NULL, \"?provider!=ovpn.xkey\");\n\n#ifdef ENABLE_MANAGEMENT\n    management = test_calloc(sizeof(*management), 1);\n#endif\n}\n\nstatic void\nuninit_test(void)\n{\n    for (size_t i = 0; i < _countof(prov); i++)\n    {\n        if (prov[i])\n        {\n            OSSL_PROVIDER_unload(prov[i]);\n        }\n    }\n    test_free(management);\n}\n\n/* Mock management callback for signature.\n * We check that the received data to sign matches test_msg or\n * test_digest and return a predefined string as signature so that\n * the caller can validate all steps up to sending the data to\n * the management client.\n */\nchar *\nmanagement_query_pk_sig(struct management *man, const char *b64_data, const char *algorithm)\n{\n    char *out = NULL;\n\n    /* indicate entry to the callback */\n    mgmt_callback_called = 1;\n\n    const char *expected_tbs = test_digest_b64;\n    if (strstr(algorithm, \"data=message\"))\n    {\n        expected_tbs = test_msg_b64;\n        /* ED25519 does not have a hash algorithm even though it goes via\n         * the DigestSign path (data=message) */\n        if (!strstr(algorithm, \"ED25519\"))\n        {\n            assert_non_null(strstr(algorithm, \"hashalg=SHA256\"));\n        }\n    }\n    assert_string_equal(b64_data, expected_tbs);\n\n    /* We test using ED25519, ECDSA or PSS with saltlen = digest */\n    if (!strstr(algorithm, \"ECDSA\") && !strstr(algorithm, \"ED25519\"))\n    {\n        assert_non_null(strstr(algorithm, \"RSA_PKCS1_PSS_PADDING,hashalg=SHA256,saltlen=digest\"));\n    }\n\n    /* Return a predefined string as sig so that the caller\n     * can confirm that this callback was exercised.\n     */\n    out = strdup(good_sig_b64);\n    assert_non_null(out);\n\n    return out;\n}\n\n/* Check signature and keymgmt methods can be fetched from the provider */\nstatic void\nxkey_provider_test_fetch(void **state)\n{\n    assert_true(OSSL_PROVIDER_available(NULL, prov_name));\n\n    const char *algs[] = { \"RSA\", \"ECDSA\" };\n\n    for (size_t i = 0; i < _countof(algs); i++)\n    {\n        EVP_SIGNATURE *sig = EVP_SIGNATURE_fetch(NULL, algs[i], \"provider=ovpn.xkey\");\n        assert_non_null(sig);\n        assert_string_equal(OSSL_PROVIDER_get0_name(EVP_SIGNATURE_get0_provider(sig)), prov_name);\n\n        EVP_SIGNATURE_free(sig);\n    }\n\n    const char *names[] = { \"RSA\", \"EC\" };\n\n    for (size_t i = 0; i < _countof(names); i++)\n    {\n        EVP_KEYMGMT *km = EVP_KEYMGMT_fetch(NULL, names[i], \"provider=ovpn.xkey\");\n        assert_non_null(km);\n        assert_string_equal(OSSL_PROVIDER_get0_name(EVP_KEYMGMT_get0_provider(km)), prov_name);\n\n        EVP_KEYMGMT_free(km);\n    }\n}\n\n/* sign a test message using pkey -- caller must free the returned sig */\nstatic uint8_t *\ndigest_sign(EVP_PKEY *pkey)\n{\n    uint8_t *sig = NULL;\n    size_t siglen = 0;\n\n    OSSL_PARAM params[6] = { OSSL_PARAM_END };\n\n    const char *mdname = \"SHA256\";\n    const char *padmode = \"pss\";\n    const char *saltlen = \"digest\";\n\n    if (EVP_PKEY_get_id(pkey) == EVP_PKEY_RSA)\n    {\n        params[0] =\n            OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_DIGEST, (char *)mdname, 0);\n        params[1] =\n            OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_PAD_MODE, (char *)padmode, 0);\n        params[2] =\n            OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_PSS_SALTLEN, (char *)saltlen, 0);\n        /* same digest for mgf1 */\n        params[3] =\n            OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_MGF1_DIGEST, (char *)saltlen, 0);\n        params[4] = OSSL_PARAM_construct_end();\n    }\n    else if (EVP_PKEY_get_id(pkey) == EVP_PKEY_ED25519)\n    {\n        mdname = NULL;\n        params[0] = OSSL_PARAM_construct_end();\n    }\n\n\n    EVP_PKEY_CTX *pctx = NULL;\n    EVP_MD_CTX *mctx = EVP_MD_CTX_new();\n\n    if (!mctx || EVP_DigestSignInit_ex(mctx, &pctx, mdname, NULL, NULL, pkey, params) <= 0)\n    {\n        fail_msg(\"Failed to initialize EVP_DigestSignInit_ex()\");\n        goto done;\n    }\n\n    /* sign with sig = NULL to get required siglen */\n    assert_int_equal(EVP_DigestSign(mctx, sig, &siglen, (uint8_t *)test_msg, strlen(test_msg)), 1);\n    assert_true(siglen > 0);\n\n    if ((sig = test_calloc(1, siglen)) == NULL)\n    {\n        fail_msg(\"Out of memory\");\n    }\n    assert_int_equal(EVP_DigestSign(mctx, sig, &siglen, (uint8_t *)test_msg, strlen(test_msg)), 1);\n\ndone:\n    if (mctx)\n    {\n        EVP_MD_CTX_free(mctx); /* pctx is internally allocated and freed by mctx */\n    }\n    return sig;\n}\n\n#ifdef ENABLE_MANAGEMENT\n/* Check loading of management external key and have sign callback exercised\n * for RSA and EC keys with and without digest support in management client.\n * Sha256 digest used for both cases with pss padding for RSA.\n */\nstatic void\nxkey_provider_test_mgmt_sign_cb(void **state)\n{\n    EVP_PKEY *pubkey;\n    for (size_t i = 0; i < _countof(pubkeys); i++)\n    {\n        pubkey = load_pubkey(pubkeys[i]);\n        assert_non_null(pubkey);\n        EVP_PKEY *privkey = xkey_load_management_key(NULL, pubkey);\n        assert_non_null(privkey);\n\n        management->settings.flags = MF_EXTERNAL_KEY | MF_EXTERNAL_KEY_PSSPAD;\n\n        /* first without digest support in management client */\nagain:\n        mgmt_callback_called = 0;\n        uint8_t *sig = digest_sign(privkey);\n        assert_non_null(sig);\n\n        /* check callback for signature got exercised */\n        assert_int_equal(mgmt_callback_called, 1);\n        assert_memory_equal(sig, good_sig, sizeof(good_sig));\n        test_free(sig);\n\n        if (!(management->settings.flags & MF_EXTERNAL_KEY_DIGEST))\n        {\n            management->settings.flags |= MF_EXTERNAL_KEY_DIGEST;\n            goto again; /* this time with digest support announced */\n        }\n\n        EVP_PKEY_free(pubkey);\n        EVP_PKEY_free(privkey);\n    }\n}\n#endif /* ifdef ENABLE_MANAGEMENT */\n\n/* helpers for testing generic key load and sign */\nstatic int xkey_free_called;\nstatic int xkey_sign_called;\nstatic void\nxkey_free(void *handle)\n{\n    xkey_free_called = 1;\n    /* We use a dummy string as handle -- check its value */\n    assert_string_equal(handle, \"xkey_handle\");\n}\n\nstatic int\nxkey_sign(void *handle, unsigned char *sig, size_t *siglen, const unsigned char *tbs, size_t tbslen,\n          XKEY_SIGALG s)\n{\n    if (!sig)\n    {\n        *siglen = 256; /* some arbitrary size */\n        return 1;\n    }\n\n    xkey_sign_called = 1; /* called with non-null sig */\n\n    if (!strcmp(s.op, \"DigestSign\"))\n    {\n        assert_memory_equal(tbs, test_msg, strlen(test_msg));\n    }\n    else\n    {\n        assert_memory_equal(tbs, test_digest, sizeof(test_digest));\n    }\n\n    /* For the test use sha256 and PSS padding for RSA and none for EDDSA */\n    if (!strcmp(s.keytype, \"ED25519\"))\n    {\n        assert_string_equal(s.mdname, \"none\");\n    }\n    else\n    {\n        assert_int_equal(OBJ_sn2nid(s.mdname), NID_sha256);\n    }\n    if (!strcmp(s.keytype, \"RSA\"))\n    {\n        assert_string_equal(s.padmode, \"pss\"); /* we use PSS for the test */\n    }\n    else if (strcmp(s.keytype, \"EC\") && strcmp(s.keytype, \"ED25519\"))\n    {\n        fail_msg(\"Unknown keytype: %s\", s.keytype);\n    }\n\n    /* return a predefined string as sig */\n    memcpy(sig, good_sig, min_size(sizeof(good_sig), *siglen));\n\n    return 1;\n}\n\n/* Load a key as a generic key and check its sign op gets\n * called for signature.\n */\nstatic void\nxkey_provider_test_generic_sign_cb(void **state)\n{\n    EVP_PKEY *pubkey;\n    const char *dummy = \"xkey_handle\"; /* a dummy handle for the external key */\n\n    for (size_t i = 0; i < _countof(pubkeys); i++)\n    {\n        pubkey = load_pubkey(pubkeys[i]);\n        assert_non_null(pubkey);\n\n        EVP_PKEY *privkey =\n            xkey_load_generic_key(NULL, (void *)dummy, pubkey, xkey_sign, xkey_free);\n        assert_non_null(privkey);\n\n        xkey_sign_called = 0;\n        xkey_free_called = 0;\n        uint8_t *sig = digest_sign(privkey);\n        assert_non_null(sig);\n\n        /* check callback for signature got exercised */\n        assert_int_equal(xkey_sign_called, 1);\n        assert_memory_equal(sig, good_sig, sizeof(good_sig));\n        test_free(sig);\n\n        EVP_PKEY_free(pubkey);\n        EVP_PKEY_free(privkey);\n\n        /* check key's free-op got called */\n        assert_int_equal(xkey_free_called, 1);\n    }\n}\n\nint\nmain(void)\n{\n    init_test();\n\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(xkey_provider_test_fetch),\n#ifdef ENABLE_MANAGEMENT\n        cmocka_unit_test(xkey_provider_test_mgmt_sign_cb),\n#endif\n        cmocka_unit_test(xkey_provider_test_generic_sign_cb),\n    };\n\n    int ret = cmocka_run_group_tests_name(\"xkey provider tests\", tests, NULL, NULL);\n\n    uninit_test();\n    return ret;\n}\n#else  /* ifdef HAVE_XKEY_PROVIDER */\nint\nmain(void)\n{\n    return 0;\n}\n#endif /* HAVE_XKEY_PROVIDER */\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_push_update_msg.c",
    "content": "#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <stdlib.h>\n#include <stdarg.h>\n#include <setjmp.h>\n#include <cmocka.h>\n#include \"push.h\"\n#include \"options_util.h\"\n#include \"multi.h\"\n\n#include \"push_util.c\"\n\n/* mocks */\n\nvoid\nthrow_signal_soft(const int signum, const char *signal_text)\n{\n    msg(M_WARN, \"Offending option received from server\");\n}\n\nunsigned int\npull_permission_mask(const struct context *c)\n{\n    unsigned int flags = OPT_P_UP | OPT_P_ROUTE_EXTRAS | OPT_P_SOCKBUF | OPT_P_SOCKFLAGS\n                         | OPT_P_SETENV | OPT_P_SHAPER | OPT_P_TIMER | OPT_P_COMP | OPT_P_PERSIST\n                         | OPT_P_MESSAGES | OPT_P_EXPLICIT_NOTIFY | OPT_P_ECHO | OPT_P_PULL_MODE\n                         | OPT_P_PEER_ID | OPT_P_NCP | OPT_P_PUSH_MTU | OPT_P_ROUTE | OPT_P_DHCPDNS;\n    return flags;\n}\n\nvoid\nunlearn_ifconfig(struct multi_context *m, struct multi_instance *mi)\n{\n    return;\n}\n\nvoid\nunlearn_ifconfig_ipv6(struct multi_context *m, struct multi_instance *mi)\n{\n    return;\n}\n\nvoid\nupdate_vhash(struct multi_context *m, struct multi_instance *mi, const char *new_ip, const char *new_ipv6)\n{\n    return;\n}\n\nbool\noptions_postprocess_pull(struct options *options, struct env_set *es)\n{\n    return true;\n}\n\n/*\n * Counters to track route accumulation across continuation messages.\n * Used to verify the bug where update_options_found resets per message.\n */\nstatic int route_reset_count = 0;\nstatic int route_add_count = 0;\n\nstatic void\nreset_route_counters(void)\n{\n    route_reset_count = 0;\n    route_add_count = 0;\n}\n\nbool\napply_push_options(struct context *c, struct options *options, struct buffer *buf,\n                   unsigned int permission_mask, unsigned int *option_types_found,\n                   struct env_set *es, bool is_update)\n{\n    char line[OPTION_PARM_SIZE];\n\n    /*\n     * Use persistent push_update_options_found from options struct to track\n     * which option types have been reset across continuation messages.\n     * This is the FIXED behavior - routes are only reset once per PUSH_UPDATE sequence.\n     */\n\n    while (buf_parse(buf, ',', line, sizeof(line)))\n    {\n        unsigned int push_update_option_flags = 0;\n        int i = 0;\n\n        if (is_update || options->pull_filter_list)\n        {\n            /* skip leading spaces matching the behaviour of parse_line */\n            while (isspace(line[i]))\n            {\n                i++;\n            }\n\n            if ((is_update && !check_push_update_option_flags(line, &i, &push_update_option_flags))\n                || (options->pull_filter_list && !apply_pull_filter(options, &line[i])))\n            {\n                if (push_update_option_flags & PUSH_OPT_OPTIONAL)\n                {\n                    continue; /* Ignoring this option */\n                }\n                return false; /* Cause push/pull error and stop push processing */\n            }\n        }\n\n        /* Simulate route handling from update_option() in options.c */\n        if (strncmp(&line[i], \"route \", 6) == 0)\n        {\n            if (!(options->push_update_options_found & OPT_P_U_ROUTE))\n            {\n                /* First route in entire PUSH_UPDATE sequence - reset routes once */\n                route_reset_count++;\n                options->push_update_options_found |= OPT_P_U_ROUTE;\n            }\n            route_add_count++;\n        }\n        /* Simulate add_option() push-continuation logic */\n        else if (!strcmp(&line[i], \"push-continuation 2\"))\n        {\n            options->push_continuation = 2;\n        }\n        else if (!strcmp(&line[i], \"push-continuation 1\"))\n        {\n            options->push_continuation = 1;\n        }\n    }\n    return true;\n}\n\nint\nprocess_incoming_push_msg(struct context *c, const struct buffer *buffer,\n                          bool honor_received_options, unsigned int permission_mask,\n                          unsigned int *option_types_found)\n{\n    struct buffer buf = *buffer;\n\n    if (buf_string_compare_advance(&buf, \"PUSH_REQUEST\"))\n    {\n        return PUSH_MSG_REQUEST;\n    }\n    else if (honor_received_options && buf_string_compare_advance(&buf, push_reply_cmd))\n    {\n        return PUSH_MSG_REPLY;\n    }\n    else if (honor_received_options && buf_string_compare_advance(&buf, push_update_cmd))\n    {\n        return process_push_update(c, &c->options, permission_mask, option_types_found, &buf, false);\n    }\n    else\n    {\n        return PUSH_MSG_ERROR;\n    }\n}\n\nconst char *\ntls_common_name(const struct tls_multi *multi, const bool null)\n{\n    return NULL;\n}\n\n#ifndef ENABLE_MANAGEMENT\nbool\nsend_control_channel_string(struct context *c, const char *str, msglvl_t msglevel)\n{\n    return true;\n}\n#else  /* ifndef ENABLE_MANAGEMENT */\n\nbool\nsend_control_channel_string(struct context *c, const char *str, msglvl_t msglevel)\n{\n    check_expected_ptr(str);\n    return true;\n}\n\nstruct multi_instance *\nlookup_by_cid(struct multi_context *m, const unsigned long cid)\n{\n    return *(m->instances);\n}\n\nbool\nmroute_extract_openvpn_sockaddr(struct mroute_addr *addr,\n                                const struct openvpn_sockaddr *osaddr,\n                                bool use_port)\n{\n    return true;\n}\n\nunsigned int\nextract_iv_proto(const char *peer_info)\n{\n    return IV_PROTO_PUSH_UPDATE;\n}\n#endif /* ifdef ENABLE_MANAGEMENT */\n\n/* tests */\n\nstatic void\ntest_incoming_push_message_basic(void **state)\n{\n    struct context *c = *state;\n    struct buffer buf = alloc_buf(256);\n    const char *update_msg =\n        \"PUSH_UPDATE,dhcp-option DNS 8.8.8.8, route 0.0.0.0 0.0.0.0 10.10.10.1\";\n    buf_write(&buf, update_msg, strlen(update_msg));\n    unsigned int option_types_found = 0;\n\n    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c),\n                                               &option_types_found),\n                     PUSH_MSG_UPDATE);\n\n    free_buf(&buf);\n}\n\nstatic void\ntest_incoming_push_message_error1(void **state)\n{\n    struct context *c = *state;\n    struct buffer buf = alloc_buf(256);\n    const char *update_msg = \"PUSH_UPDATEerr,dhcp-option DNS 8.8.8.8\";\n    buf_write(&buf, update_msg, strlen(update_msg));\n    unsigned int option_types_found = 0;\n\n    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c),\n                                               &option_types_found),\n                     PUSH_MSG_ERROR);\n\n    free_buf(&buf);\n}\n\nstatic void\ntest_incoming_push_message_error2(void **state)\n{\n    struct context *c = *state;\n    struct buffer buf = alloc_buf(256);\n    const char *update_msg = \"PUSH_UPDATE ,dhcp-option DNS 8.8.8.8\";\n    buf_write(&buf, update_msg, strlen(update_msg));\n    unsigned int option_types_found = 0;\n\n    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c),\n                                               &option_types_found),\n                     PUSH_MSG_ERROR);\n\n    free_buf(&buf);\n}\n\nstatic void\ntest_incoming_push_message_1(void **state)\n{\n    struct context *c = *state;\n    struct buffer buf = alloc_buf(256);\n    const char *update_msg = \"PUSH_UPDATE, -?dns, route something, ?dhcp-option DNS 8.8.8.8\";\n    buf_write(&buf, update_msg, strlen(update_msg));\n    unsigned int option_types_found = 0;\n\n    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c),\n                                               &option_types_found),\n                     PUSH_MSG_UPDATE);\n\n    free_buf(&buf);\n}\n\nstatic void\ntest_incoming_push_message_bad_format(void **state)\n{\n    struct context *c = *state;\n    struct buffer buf = alloc_buf(256);\n    const char *update_msg = \"PUSH_UPDATE, -dhcp-option, ?-dns\";\n    buf_write(&buf, update_msg, strlen(update_msg));\n    unsigned int option_types_found = 0;\n\n    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c),\n                                               &option_types_found),\n                     PUSH_MSG_ERROR);\n\n    free_buf(&buf);\n}\n\nstatic void\ntest_incoming_push_message_not_updatable_option(void **state)\n{\n    struct context *c = *state;\n    struct buffer buf = alloc_buf(256);\n    const char *update_msg = \"PUSH_UPDATE, dev tun\";\n    buf_write(&buf, update_msg, strlen(update_msg));\n    unsigned int option_types_found = 0;\n\n    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c),\n                                               &option_types_found),\n                     PUSH_MSG_ERROR);\n\n    free_buf(&buf);\n}\n\nstatic void\ntest_incoming_push_message_mix(void **state)\n{\n    struct context *c = *state;\n    struct buffer buf = alloc_buf(256);\n    const char *update_msg =\n        \"PUSH_UPDATE,-dhcp-option, route 10.10.10.0, dhcp-option DNS 1.1.1.1, route 10.11.12.0, dhcp-option DOMAIN corp.local, keepalive 10 60\";\n    buf_write(&buf, update_msg, strlen(update_msg));\n    unsigned int option_types_found = 0;\n\n    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c),\n                                               &option_types_found),\n                     PUSH_MSG_UPDATE);\n\n    free_buf(&buf);\n}\n\nstatic void\ntest_incoming_push_message_mix2(void **state)\n{\n    struct context *c = *state;\n    struct buffer buf = alloc_buf(256);\n    const char *update_msg =\n        \"PUSH_UPDATE,-dhcp-option,dhcp-option DNS 8.8.8.8,redirect-gateway local,route 192.168.1.0 255.255.255.0\";\n    buf_write(&buf, update_msg, strlen(update_msg));\n    unsigned int option_types_found = 0;\n\n    assert_int_equal(process_incoming_push_msg(c, &buf, c->options.pull, pull_permission_mask(c),\n                                               &option_types_found),\n                     PUSH_MSG_UPDATE);\n\n    free_buf(&buf);\n}\n\n/**\n * Test that routes accumulate correctly across multiple continuation messages.\n * This test exposes a bug where update_options_found is reset to 0 for each\n * continuation message, causing routes to be reset on each message instead\n * of accumulating.\n *\n * Expected behavior: routes should only be reset ONCE (when the first route is received),\n * then all subsequent routes should accumulate.\n *\n * Current bug: routes are reset on the first route of EACH continuation message.\n */\nstatic void\ntest_incoming_push_continuation_route_accumulation(void **state)\n{\n    struct context *c = *state;\n    unsigned int option_types_found = 0;\n\n    reset_route_counters();\n\n    /* Message 1: first batch of routes, continuation 2 (more coming) */\n    struct buffer buf1 = alloc_buf(512);\n    const char *msg1 = \"PUSH_UPDATE, route 10.1.0.0 255.255.0.0, route 10.2.0.0 255.255.0.0, route 10.3.0.0 255.255.0.0,push-continuation 2\";\n    buf_write(&buf1, msg1, strlen(msg1));\n\n    assert_int_equal(process_incoming_push_msg(c, &buf1, c->options.pull, pull_permission_mask(c),\n                                               &option_types_found),\n                     PUSH_MSG_CONTINUATION);\n    free_buf(&buf1);\n\n    /* Message 2: more routes, continuation 2 (more coming) */\n    struct buffer buf2 = alloc_buf(512);\n    const char *msg2 = \"PUSH_UPDATE, route 10.4.0.0 255.255.0.0, route 10.5.0.0 255.255.0.0, route 10.6.0.0 255.255.0.0,push-continuation 2\";\n    buf_write(&buf2, msg2, strlen(msg2));\n\n    assert_int_equal(process_incoming_push_msg(c, &buf2, c->options.pull, pull_permission_mask(c),\n                                               &option_types_found),\n                     PUSH_MSG_CONTINUATION);\n    free_buf(&buf2);\n\n    /* Message 3: final batch of routes, continuation 1 (last message) */\n    struct buffer buf3 = alloc_buf(512);\n    const char *msg3 = \"PUSH_UPDATE, route 10.7.0.0 255.255.0.0, route 10.8.0.0 255.255.0.0, route 10.9.0.0 255.255.0.0,push-continuation 1\";\n    buf_write(&buf3, msg3, strlen(msg3));\n\n    assert_int_equal(process_incoming_push_msg(c, &buf3, c->options.pull, pull_permission_mask(c),\n                                               &option_types_found),\n                     PUSH_MSG_UPDATE);\n    free_buf(&buf3);\n\n    /* Verify: all 9 routes should have been added */\n    assert_int_equal(route_add_count, 9);\n\n    /*\n     * Verify: route option is reset only one time in the first message\n     * if a push-continuation is present.\n     */\n    assert_int_equal(route_reset_count, 1);\n}\n\n#ifdef ENABLE_MANAGEMENT\nchar *r0[] = {\n    \"PUSH_UPDATE,redirect-gateway local,route 192.168.1.0 255.255.255.0\",\n    NULL\n};\nchar *r1[] = {\n    \"PUSH_UPDATE,-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2\",\n    \"PUSH_UPDATE, akakakakakakakakakakakaf, dhcp-option DNS 8.8.8.8,redirect-gateway local,push-continuation 2\",\n    \"PUSH_UPDATE,route 192.168.1.0 255.255.255.0,push-continuation 1\",\n    NULL\n};\nchar *r3[] = {\n    \"PUSH_UPDATE,,,\",\n    NULL\n};\nchar *r4[] = {\n    \"PUSH_UPDATE,-dhcp-option, blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2\",\n    \"PUSH_UPDATE, akakakakakakakakakakakaf,dhcp-option DNS 8.8.8.8, redirect-gateway local,push-continuation 2\",\n    \"PUSH_UPDATE, route 192.168.1.0 255.255.255.0,,push-continuation 1\",\n    NULL\n};\nchar *r5[] = {\n    \"PUSH_UPDATE,,-dhcp-option, blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2\",\n    \"PUSH_UPDATE, akakakakakakakakakakakaf,dhcp-option DNS 8.8.8.8, redirect-gateway local,push-continuation 2\",\n    \"PUSH_UPDATE, route 192.168.1.0 255.255.255.0,push-continuation 1\",\n    NULL\n};\nchar *r6[] = {\n    \"PUSH_UPDATE,-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2\",\n    \"PUSH_UPDATE, akakakakakakakakakakakaf, dhcp-option DNS 8.8.8.8, redirect-gateway 10.10.10.10,,push-continuation 2\",\n    \"PUSH_UPDATE, route 192.168.1.0 255.255.255.0,,push-continuation 1\",\n    NULL\n};\nchar *r7[] = {\n    \"PUSH_UPDATE,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,push-continuation 2\",\n    \"PUSH_UPDATE,,,,,,,,,,,,,,,,,,,push-continuation 1\",\n    NULL\n};\nchar *r8[] = {\n    \"PUSH_UPDATE,-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,push-continuation 2\",\n    \"PUSH_UPDATE, akakakakakakakakakakakaf, dhcp-option DNS 8.8.8.8,redirect-gateway\\n local,push-continuation 2\",\n    \"PUSH_UPDATE,route 192.168.1.0 255.255.255.0\\n\\n\\n,push-continuation 1\",\n    NULL\n};\nchar *r9[] = {\n    \"PUSH_UPDATE,,\",\n    NULL\n};\nchar *r11[] = {\n    \"PUSH_UPDATE,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,push-continuation 2\",\n    \"PUSH_UPDATE,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,push-continuation 2\",\n    \"PUSH_UPDATE,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,push-continuation 2\",\n    \"PUSH_UPDATE,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,push-continuation 2\",\n    \"PUSH_UPDATE,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,push-continuation 1\",\n    NULL\n};\nchar *r12[] = {\n    \"PUSH_UPDATE,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,,,,,,a,push-continuation 2\",\n    \"PUSH_UPDATE,abc,push-continuation 1\",\n    NULL\n};\nchar *r13[] = {\n    \"PUSH_UPDATE,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,,,,,,a,\",\n    NULL\n};\nchar *r14[] = {\n    \"PUSH_UPDATE,a,push-continuation 2\",\n    \"PUSH_UPDATE,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,push-continuation 2\",\n    \"PUSH_UPDATE,a,push-continuation 1\",\n    NULL\n};\n\nconst char *msg0 = \"redirect-gateway local,route 192.168.1.0 255.255.255.0\";\nconst char *msg1 = \"-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,\"\n                   \" akakakakakakakakakakakaf, dhcp-option DNS 8.8.8.8,redirect-gateway local,route 192.168.1.0 255.255.255.0\";\nconst char *msg2 = \"\";\nconst char *msg3 = \",,\";\nconst char *msg4 = \"-dhcp-option, blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,\"\n                   \" akakakakakakakakakakakaf,dhcp-option DNS 8.8.8.8, redirect-gateway local, route 192.168.1.0 255.255.255.0,\";\nconst char *msg5 = \",-dhcp-option, blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf,\"\n                   \" akakakakakakakakakakakaf,dhcp-option DNS 8.8.8.8, redirect-gateway local, route 192.168.1.0 255.255.255.0\";\nconst char *msg6 = \"-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf, akakakakakakakakakakakaf,\"\n                   \" dhcp-option DNS 8.8.8.8, redirect-gateway 10.10.10.10,, route 192.168.1.0 255.255.255.0,\";\nconst char *msg7 = \",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\";\nconst char *msg8 = \"-dhcp-option,blablalalalalalalalalalalalalf, lalalalalalalalalalalalalalaf, akakakakakakakakakakakaf,\"\n                   \" dhcp-option DNS 8.8.8.8,redirect-gateway\\n local,route 192.168.1.0 255.255.255.0\\n\\n\\n\";\nconst char *msg9 = \",\";\n\nconst char *msg10 = \"abandon ability able about above absent absorb abstract absurd abuse access accident account accuse achieve\"\n                    \"acid acoustic acquire across act action actor actress actual adapt add addict address adjust\"\n                    \"baby bachelor bacon badge bag balance balcony ball bamboo banana banner bar barely bargain barrel base basic\"\n                    \"basket battle beach bean beauty because become beef before begin behave behind\"\n                    \"cabbage cabin cable cactus cage cake call calm camera camp can canal cancel candy cannon canoe canvas canyon\"\n                    \"capable capital captain car carbon card cargo carpet carry cart case\"\n                    \"daisy damage damp dance danger daring dash daughter dawn day deal debate debris decade december decide decline\"\n                    \"decorate decrease deer defense define defy degree delay deliver demand demise denial\";\n\nconst char *msg11 = \"a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,\"\n                    \"a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,\"\n                    \"a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,\"\n                    \"a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,\"\n                    \"a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a\";\n\nconst char *msg12 = \"a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,,,,,,a,abc\";\n\nconst char *msg13 = \"a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,,,,,,a,\";\n\nconst char *msg14 = \"a,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,a\";\n\n#define PUSH_BUNDLE_SIZE_TEST 184\n\n#define expect_control_channel_strings(res)                          \\\n    do                                                               \\\n    {                                                                \\\n        for (int j = 0; res[j] != NULL; j++)                         \\\n        {                                                            \\\n            expect_string(send_control_channel_string, str, res[j]); \\\n        }                                                            \\\n    } while (0)\n\nstatic void\ntest_send_push_msg0(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    expect_control_channel_strings(r0);\n    assert_int_equal(send_push_update(m, &cid, msg0, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);\n}\n\nstatic void\ntest_send_push_msg1(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    expect_control_channel_strings(r1);\n    assert_int_equal(send_push_update(m, &cid, msg1, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);\n}\n\nstatic void\ntest_send_push_msg2(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    assert_int_equal(send_push_update(m, &cid, msg2, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), -EINVAL);\n}\n\nstatic void\ntest_send_push_msg3(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    expect_control_channel_strings(r3);\n    assert_int_equal(send_push_update(m, &cid, msg3, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);\n}\n\nstatic void\ntest_send_push_msg4(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    expect_control_channel_strings(r4);\n    assert_int_equal(send_push_update(m, &cid, msg4, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);\n}\n\nstatic void\ntest_send_push_msg5(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    expect_control_channel_strings(r5);\n    assert_int_equal(send_push_update(m, &cid, msg5, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);\n}\n\nstatic void\ntest_send_push_msg6(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    expect_control_channel_strings(r6);\n    assert_int_equal(send_push_update(m, &cid, msg6, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);\n}\n\nstatic void\ntest_send_push_msg7(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    expect_control_channel_strings(r7);\n    assert_int_equal(send_push_update(m, &cid, msg7, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);\n}\n\nstatic void\ntest_send_push_msg8(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    expect_control_channel_strings(r8);\n    assert_int_equal(send_push_update(m, &cid, msg8, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);\n}\n\nstatic void\ntest_send_push_msg9(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    expect_control_channel_strings(r9);\n    assert_int_equal(send_push_update(m, &cid, msg9, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);\n}\n\nstatic void\ntest_send_push_msg10(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    assert_int_equal(send_push_update(m, &cid, msg10, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), -EINVAL);\n}\n\nstatic void\ntest_send_push_msg11(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    expect_control_channel_strings(r11);\n    assert_int_equal(send_push_update(m, &cid, msg11, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);\n}\n\nstatic void\ntest_send_push_msg12(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    expect_control_channel_strings(r12);\n    assert_int_equal(send_push_update(m, &cid, msg12, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);\n}\n\nstatic void\ntest_send_push_msg13(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    expect_control_channel_strings(r13);\n    assert_int_equal(send_push_update(m, &cid, msg13, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);\n}\n\nstatic void\ntest_send_push_msg14(void **state)\n{\n    struct multi_context *m = *state;\n    const unsigned long cid = 0;\n    expect_control_channel_strings(r14);\n    assert_int_equal(send_push_update(m, &cid, msg14, UPT_BY_CID, PUSH_BUNDLE_SIZE_TEST), 1);\n}\n\n#undef PUSH_BUNDLE_SIZE_TEST\n\nstatic int\nsetup2(void **state)\n{\n    struct multi_context *m = calloc(1, sizeof(struct multi_context));\n    m->instances = calloc(1, sizeof(struct multi_instance *));\n    struct multi_instance *mi = calloc(1, sizeof(struct multi_instance));\n    mi->context.c2.tls_multi = calloc(1, sizeof(struct tls_multi));\n    *(m->instances) = mi;\n    m->top.options.disable_dco = true;\n    *state = m;\n    return 0;\n}\n\nstatic int\nteardown2(void **state)\n{\n    struct multi_context *m = *state;\n    free((*(m->instances))->context.c2.tls_multi);\n    free(*(m->instances));\n    free(m->instances);\n    free(m);\n    return 0;\n}\n#endif /* ifdef ENABLE_MANAGEMENT */\n\nstatic int\nsetup(void **state)\n{\n    struct context *c = calloc(1, sizeof(struct context));\n    c->options.pull = true;\n    c->options.route_nopull = false;\n    *state = c;\n    return 0;\n}\n\nstatic int\nteardown(void **state)\n{\n    struct context *c = *state;\n    free(c);\n    return 0;\n}\n\nint\nmain(void)\n{\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test_setup_teardown(test_incoming_push_message_basic, setup, teardown),\n        cmocka_unit_test_setup_teardown(test_incoming_push_message_error1, setup, teardown),\n        cmocka_unit_test_setup_teardown(test_incoming_push_message_error2, setup, teardown),\n        cmocka_unit_test_setup_teardown(test_incoming_push_message_not_updatable_option, setup,\n                                        teardown),\n        cmocka_unit_test_setup_teardown(test_incoming_push_message_1, setup, teardown),\n        cmocka_unit_test_setup_teardown(test_incoming_push_message_bad_format, setup, teardown),\n        cmocka_unit_test_setup_teardown(test_incoming_push_message_mix, setup, teardown),\n        cmocka_unit_test_setup_teardown(test_incoming_push_message_mix2, setup, teardown),\n        cmocka_unit_test_setup_teardown(test_incoming_push_continuation_route_accumulation, setup,\n                                        teardown),\n#ifdef ENABLE_MANAGEMENT\n\n        cmocka_unit_test_setup_teardown(test_send_push_msg0, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg1, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg2, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg3, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg4, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg5, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg6, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg7, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg8, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg9, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg10, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg11, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg12, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg13, setup2, teardown2),\n        cmocka_unit_test_setup_teardown(test_send_push_msg14, setup2, teardown2)\n#endif\n    };\n\n    return cmocka_run_group_tests(tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_socket.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2021-2026 Arne Schwabe <arne@rfc2549.org>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <setjmp.h>\n#include <cmocka.h>\n\n#include \"socket.h\"\n#include \"win32.h\"\n\n/* stubs for some unused functions instead of pulling in too many dependencies */\nstruct signal_info siginfo_static; /* GLOBAL */\n\nint\nsignal_reset(struct signal_info *si, int signum)\n{\n    assert_true(0);\n    return 0;\n}\n\n#ifdef _WIN32\nstruct win32_signal win32_signal; /* GLOBAL */\n\nint\nwin32_signal_get(struct win32_signal *ws)\n{\n    assert_true(0);\n    return 0;\n}\n#endif\n\nint\nparse_line(const char *line, char **p, const int n, const char *file, const int line_num,\n           msglvl_t msglevel, struct gc_arena *gc)\n{\n    assert_true(0);\n    return 0;\n}\n\nstatic void\ntest_add_in6_addr_tc(const char *orig_str, uint32_t add, const char *expect_str)\n{\n    struct in6_addr orig, result, expected;\n    struct gc_arena gc = gc_new();\n    assert_int_equal(inet_pton(AF_INET6, orig_str, &orig), 1);\n    assert_int_equal(inet_pton(AF_INET6, expect_str, &expected), 1);\n    result = add_in6_addr(orig, add);\n    const char *result_str = print_in6_addr(result, 0, &gc);\n    assert_string_equal(result_str, expect_str);\n    assert_memory_equal(&result, &expected, sizeof(struct in6_addr));\n    gc_free(&gc);\n}\n\nstatic bool\ncheck_mapped_ipv4_address(void)\n{\n    struct gc_arena gc = gc_new();\n    const char *ipv4_output = \"::255.255.255.255\";\n    struct in6_addr addr;\n    assert_int_equal(inet_pton(AF_INET6, ipv4_output, &addr), 1);\n    const char *test_output = print_in6_addr(addr, 0, &gc);\n    bool ret = strcmp(test_output, ipv4_output) == 0;\n    gc_free(&gc);\n    return ret;\n}\n\nstatic void\ntest_add_in6_addr(void **state)\n{\n    /* Note that some of the result strings need to account for\n       print_in6_addr formatting the addresses potentially as IPv4 */\n    bool mapped_ipv4 = check_mapped_ipv4_address();\n    test_add_in6_addr_tc(\"::\", 1, \"::1\");\n    test_add_in6_addr_tc(\"::ff\", 1, \"::100\");\n    test_add_in6_addr_tc(\"::ffff\", 1, mapped_ipv4 ? \"::0.1.0.0\" : \"::1:0\");\n    test_add_in6_addr_tc(\"ffff::ffff\", 1, \"ffff::1:0\");\n    test_add_in6_addr_tc(\"::ffff:ffff\", 1, \"::1:0:0\");\n    test_add_in6_addr_tc(\"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff\", 1, \"::\");\n    test_add_in6_addr_tc(\"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff\", 2, \"::1\");\n\n    test_add_in6_addr_tc(\"::\", UINT32_MAX, mapped_ipv4 ? \"::255.255.255.255\" : \"::ffff:ffff\");\n    test_add_in6_addr_tc(\"::1\", UINT32_MAX, \"::1:0:0\");\n    test_add_in6_addr_tc(\"::ffff\", UINT32_MAX, \"::1:0:fffe\");\n    test_add_in6_addr_tc(\"ffff::ffff\", UINT32_MAX, \"ffff::1:0:fffe\");\n    test_add_in6_addr_tc(\"::ffff:ffff\", UINT32_MAX, \"::1:ffff:fffe\");\n    test_add_in6_addr_tc(\"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff\", UINT32_MAX,\n                         mapped_ipv4 ? \"::255.255.255.254\" : \"::ffff:fffe\");\n}\n\nconst struct CMUnitTest socket_tests[] = {\n    cmocka_unit_test(test_add_in6_addr)\n};\n\nint\nmain(void)\n{\n    return cmocka_run_group_tests(socket_tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_ssl.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n * Copyright (C) 2023-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <setjmp.h>\n#include <cmocka.h>\n\n#include \"crypto.h\"\n#include \"crypto_epoch.h\"\n#include \"options.h\"\n#include \"ssl_backend.h\"\n#include \"options_util.h\"\n\n#include \"mock_msg.h\"\n#include \"mss.h\"\n#include \"ssl_verify_backend.h\"\n#include \"win32.h\"\n#include \"test_common.h\"\n#include \"ssl.h\"\n#include \"buffer.h\"\n#include \"packet_id.h\"\n\n/* Mock function to be allowed to include win32.c which is required for\n * getting the temp directory */\n#ifdef _WIN32\nstruct signal_info siginfo_static; /* GLOBAL */\n\nconst char *\nstrerror_win32(DWORD errnum, struct gc_arena *gc)\n{\n    ASSERT(false);\n}\n\nvoid\nthrow_signal(const int signum)\n{\n    ASSERT(false);\n}\n#endif\n\n#if defined(ENABLE_CRYPTO_OPENSSL) && (OPENSSL_VERSION_NUMBER > 0x30000000L)\n#define HAVE_OPENSSL_STORE\n#endif\n\n/* stubs for some unused functions instead of pulling in too many dependencies */\nbool\nget_user_pass_cr(struct user_pass *up, const char *auth_file, const char *prefix,\n                 const unsigned int flags, const char *auth_challenge)\n{\n    return false;\n}\nvoid\npurge_user_pass(struct user_pass *up, bool force)\n{\n    return;\n}\n\n/* generated using\n * openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -keyout - \\\n * -noenc -sha256 -days 3650 -subj '/CN=ovpn-test-secp384r1'  -nodes \\\n * -addext 'subjectAltName=DNS:unittest.example.com' \\\n * -addext 'extendedKeyUsage=clientAuth'\n */\nstatic const char *const unittest_cert =\n    \"-----BEGIN CERTIFICATE-----\\n\"\n    \"MIICBjCCAYygAwIBAgIUFoXgpP4beykV7tpgrjHQTWPGi4cwCgYIKoZIzj0EAwIw\\n\"\n    \"HjEcMBoGA1UEAwwTb3Zwbi10ZXN0LXNlY3AzODRyMTAeFw0yNTA5MDgxMzExNTBa\\n\"\n    \"Fw0zNTA5MDYxMzExNTBaMB4xHDAaBgNVBAMME292cG4tdGVzdC1zZWNwMzg0cjEw\\n\"\n    \"djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQVDmf+TZB3rW6zqWFox606u/PhA93ysX/h\\n\"\n    \"1s2xyq9+QGzIdE/hks6p/Yzyu7RLOUjxvO0J45RHcYmo67DlvSOi496T3zrgvp1H\\n\"\n    \"KfHD5ohMyvzw0+e8lmjJqJjn+PegMkOjgYowgYcwHQYDVR0OBBYEFCH1eYnaV8fh\\n\"\n    \"E3Bv7lyrlYu24eoVMB8GA1UdIwQYMBaAFCH1eYnaV8fhE3Bv7lyrlYu24eoVMA8G\\n\"\n    \"A1UdEwEB/wQFMAMBAf8wHwYDVR0RBBgwFoIUdW5pdHRlc3QuZXhhbXBsZS5jb20w\\n\"\n    \"EwYDVR0lBAwwCgYIKwYBBQUHAwIwCgYIKoZIzj0EAwIDaAAwZQIxAL7q7jcwTOuq\\n\"\n    \"5sp0Beq81Vnznd3gsDZYNs1OYRWH33xergDVKlBb6kCwus0dhghtVAIwIgT4ytkY\\n\"\n    \"oAPx8LB3oP8ubEu1ue6V9jZln/cCiLyXDDtaiJOZHtDqHGfHqvc6rAok\\n\"\n    \"-----END CERTIFICATE-----\\n\";\n\nstatic const char *const unittest_key =\n    \"-----BEGIN PRIVATE KEY-----\\n\"\n    \"MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAXBC7tpa9UepoMVZlM\\n\"\n    \"OxUubkECGK7aWFebxDc3UPoEQemEPMOCdkWBSU/t7Mm4R66hZANiAAQVDmf+TZB3\\n\"\n    \"rW6zqWFox606u/PhA93ysX/h1s2xyq9+QGzIdE/hks6p/Yzyu7RLOUjxvO0J45RH\\n\"\n    \"cYmo67DlvSOi496T3zrgvp1HKfHD5ohMyvzw0+e8lmjJqJjn+PegMkM=\\n\"\n    \"-----END PRIVATE KEY-----\\n\";\n\n\nstatic const char *\nget_tmp_dir(void)\n{\n    const char *ret;\n#ifdef _WIN32\n    ret = win_get_tempdir();\n#else\n    ret = \"/tmp\";\n#endif\n    assert_non_null(ret);\n    return ret;\n}\n\nstatic struct\n{\n    struct gc_arena gc;\n    const char *certfile;\n    const char *keyfile;\n} global_state;\n\nstatic int\ninit(void **state)\n{\n    (void)state;\n    global_state.gc = gc_new();\n    global_state.certfile = platform_create_temp_file(get_tmp_dir(), \"cert\", &global_state.gc);\n    global_state.keyfile = platform_create_temp_file(get_tmp_dir(), \"key\", &global_state.gc);\n\n    int certfd = open(global_state.certfile, O_RDWR);\n    int keyfd = open(global_state.keyfile, O_RDWR);\n    if (certfd < 0 || keyfd < 0)\n    {\n        fail_msg(\"make tmpfile for certificate or key data failed (error = %d)\", errno);\n    }\n    /* Awkward casts required for MinGW with -O0 only */\n    assert_int_equal(write(certfd, unittest_cert, (unsigned int)strlen(unittest_cert)),\n                     strlen(unittest_cert));\n    assert_int_equal(write(keyfd, unittest_key, (unsigned int)strlen(unittest_key)),\n                     strlen(unittest_key));\n    close(certfd);\n    close(keyfd);\n    return 0;\n}\n\nstatic int\ncleanup(void **state)\n{\n    (void)state;\n    unlink(global_state.certfile);\n    unlink(global_state.keyfile);\n    gc_free(&global_state.gc);\n    return 0;\n}\n\nstatic void\ncrypto_pem_encode_certificate(void **state)\n{\n    struct gc_arena gc = gc_new();\n\n    struct tls_root_ctx ctx = { 0 };\n    tls_ctx_client_new(&ctx);\n    tls_ctx_load_cert_file(&ctx, unittest_cert, true);\n\n    openvpn_x509_cert_t *cert = NULL;\n\n    /* we do not have methods to fetch certificates from ssl contexts, use\n     * internal TLS library methods for the unit test */\n#ifdef ENABLE_CRYPTO_OPENSSL\n    cert = SSL_CTX_get0_certificate(ctx.ctx);\n#elif defined(ENABLE_CRYPTO_MBEDTLS)\n    cert = ctx.crt_chain;\n#endif\n\n    const char *tmpfile = platform_create_temp_file(get_tmp_dir(), \"ut_pem\", &gc);\n    backend_x509_write_pem(cert, tmpfile);\n\n    struct buffer exported_pem = buffer_read_from_file(tmpfile, &gc);\n    assert_string_equal(BSTR(&exported_pem), unittest_cert);\n\n    tls_ctx_free(&ctx);\n    unlink(tmpfile);\n    gc_free(&gc);\n}\n\nstatic void\ntest_load_certificate_and_key(void **state)\n{\n    (void)state;\n    struct tls_root_ctx ctx = { 0 };\n\n    /* test loading of inlined cert and key.\n     * loading the key also checks that it matches the loaded certificate\n     */\n    tls_ctx_client_new(&ctx);\n    tls_ctx_load_cert_file(&ctx, unittest_cert, true);\n    assert_int_equal(tls_ctx_load_priv_file(&ctx, unittest_key, true), 0);\n    tls_ctx_free(&ctx);\n\n    /* test loading of cert and key from file */\n    tls_ctx_client_new(&ctx);\n    tls_ctx_load_cert_file(&ctx, global_state.certfile, false);\n    assert_int_equal(tls_ctx_load_priv_file(&ctx, global_state.keyfile, false), 0);\n    tls_ctx_free(&ctx);\n}\n\n/* test loading cert and key using file:/path URI */\nstatic void\ntest_load_certificate_and_key_uri(void **state)\n{\n    (void)state;\n\n#if !defined(HAVE_OPENSSL_STORE)\n    skip();\n#else  /* HAVE_OPENSSL_STORE */\n\n    struct tls_root_ctx ctx = { 0 };\n    const char *certfile = global_state.certfile;\n    const char *keyfile = global_state.keyfile;\n    struct gc_arena *gc = &global_state.gc;\n\n    struct buffer certuri = alloc_buf_gc(6 + strlen(certfile) + 1, gc); /* 6 bytes for \"file:/\" */\n    struct buffer keyuri = alloc_buf_gc(6 + strlen(keyfile) + 1, gc);   /* 6 bytes for \"file:/\" */\n\n    /* Windows temp file path starts with drive letter -- add a leading slash for URI */\n    const char *lead = \"\";\n#ifdef _WIN32\n    lead = \"/\";\n#endif /* _WIN32 */\n    assert_true(buf_printf(&certuri, \"file:%s%s\", lead, certfile));\n    assert_true(buf_printf(&keyuri, \"file:%s%s\", lead, keyfile));\n\n    /* On Windows replace any '\\' in path by '/' required for URI */\n#ifdef _WIN32\n    string_mod(BSTR(&certuri), CC_ANY, CC_BACKSLASH, '/');\n    string_mod(BSTR(&keyuri), CC_ANY, CC_BACKSLASH, '/');\n#endif /* _WIN32 */\n\n    tls_ctx_client_new(&ctx);\n    tls_ctx_load_cert_file(&ctx, BSTR(&certuri), false);\n    assert_int_equal(tls_ctx_load_priv_file(&ctx, BSTR(&keyuri), false), 0);\n    tls_ctx_free(&ctx);\n#endif /* HAVE_OPENSSL_STORE */\n}\n\n\nstatic void\ninit_frame_parameters(struct frame *frame)\n{\n    int overhead = 0;\n\n    /* tls-auth and tls-crypt */\n    overhead += 128;\n\n    /* TCP length field and opcode */\n    overhead += 3;\n\n    /* ACK array and remote SESSION ID (part of the ACK array) */\n    overhead += ACK_SIZE(RELIABLE_ACK_SIZE);\n\n    /* Previous OpenVPN version calculated the maximum size and buffer of a\n     * control frame depending on the overhead of the data channel frame\n     * overhead and limited its maximum size to 1250. Since control frames\n     * also need to fit into data channel buffer we have the same\n     * default of 1500 + 100 as data channel buffers have. Increasing\n     * control channel mtu beyond this limit also increases the data channel\n     * buffers */\n    int tls_mtu = 1500;\n    frame->buf.payload_size = tls_mtu + 100;\n\n    frame->buf.headroom = overhead;\n    frame->buf.tailroom = overhead;\n\n    frame->tun_mtu = tls_mtu;\n}\n\nstatic void\ndo_data_channel_round_trip(struct crypto_options *co)\n{\n    struct gc_arena gc = gc_new();\n\n    /* initialise frame for the test */\n    struct frame frame;\n    init_frame_parameters(&frame);\n\n    struct buffer src = alloc_buf_gc(frame.buf.payload_size, &gc);\n    struct buffer work = alloc_buf_gc(BUF_SIZE(&frame), &gc);\n    struct buffer encrypt_workspace = alloc_buf_gc(BUF_SIZE(&frame), &gc);\n    struct buffer decrypt_workspace = alloc_buf_gc(BUF_SIZE(&frame), &gc);\n    struct buffer buf = clear_buf();\n    void *buf_p;\n\n    /* init work */\n    ASSERT(buf_init(&work, frame.buf.headroom));\n\n    update_time();\n\n    /* Test encryption, decryption for all packet sizes */\n    for (int i = 1; i <= frame.buf.payload_size; ++i)\n    {\n        /* msg(M_INFO, \"TESTING ENCRYPT/DECRYPT of packet length=%d\", i); */\n\n        /*\n         * Load src with random data.\n         */\n        ASSERT(buf_init(&src, 0));\n        ASSERT(i <= src.capacity);\n        src.len = i;\n        ASSERT(rand_bytes(BPTR(&src), BLEN(&src)));\n\n        /* copy source to input buf */\n        buf = work;\n        buf_p = buf_write_alloc(&buf, BLENZ(&src));\n        ASSERT(buf_p);\n        memcpy(buf_p, BPTR(&src), BLENZ(&src));\n\n        /* initialize work buffer with buf.headroom bytes of prepend capacity */\n        ASSERT(buf_init(&encrypt_workspace, frame.buf.headroom));\n\n        /* encrypt */\n        openvpn_encrypt(&buf, encrypt_workspace, co);\n\n        /* decrypt */\n        openvpn_decrypt(&buf, decrypt_workspace, co, &frame, BPTR(&buf));\n\n        /* compare */\n        assert_int_equal(buf.len, src.len);\n        assert_memory_equal(BPTR(&src), BPTR(&buf), i);\n    }\n    gc_free(&gc);\n}\n\nstatic void\nencrypt_one_packet(struct crypto_options *co, int len)\n{\n    struct frame frame;\n    init_frame_parameters(&frame);\n\n    struct gc_arena gc = gc_new();\n    struct buffer encrypt_workspace = alloc_buf_gc(BUF_SIZE(&frame), &gc);\n    struct buffer decrypt_workspace = alloc_buf_gc(BUF_SIZE(&frame), &gc);\n    struct buffer work = alloc_buf_gc(BUF_SIZE(&frame), &gc);\n    struct buffer buf = clear_buf();\n    struct buffer src = alloc_buf_gc(frame.buf.payload_size, &gc);\n    void *buf_p;\n\n    ASSERT(buf_init(&work, frame.buf.headroom));\n\n    /*\n     * Load src with random data.\n     */\n    ASSERT(buf_init(&src, 0));\n    ASSERT(len <= src.capacity);\n    src.len = len;\n    ASSERT(rand_bytes(BPTR(&src), BLEN(&src)));\n\n    /* copy source to input buf */\n    buf = work;\n    buf_p = buf_write_alloc(&buf, BLENZ(&src));\n    ASSERT(buf_p);\n    memcpy(buf_p, BPTR(&src), BLENZ(&src));\n\n    ASSERT(buf_init(&encrypt_workspace, frame.buf.headroom));\n    openvpn_encrypt(&buf, encrypt_workspace, co);\n\n    /* decrypt */\n    openvpn_decrypt(&buf, decrypt_workspace, co, &frame, BPTR(&buf));\n\n    /* compare */\n    assert_int_equal(buf.len, src.len);\n    assert_memory_equal(BPTR(&src), BPTR(&buf), len);\n\n    gc_free(&gc);\n}\n\n\nstatic void\ncheck_aead_limits(struct crypto_options *co, bool chachapoly)\n{\n    /* Check that we correctly react when we have a nearing AEAD limits */\n\n    /* manually increase the send counter to be past\n     * the GCM usage limit */\n    co->key_ctx_bi.encrypt.plaintext_blocks = 0x1ull << 40;\n\n\n    bool epoch = (co->flags & CO_EPOCH_DATA_KEY_FORMAT);\n\n    int expected_epoch = epoch ? 4 : 0;\n\n    /* Ensure that we are still on the initial key (our init_crypto_options\n     * unit test method iterates the initial key to 4) or that it is 0 when\n     * epoch is not in use\n     */\n    assert_int_equal(co->key_ctx_bi.encrypt.epoch, expected_epoch);\n\n    encrypt_one_packet(co, 1000);\n\n    /* either epoch key has been updated or warning is enabled */\n    if (epoch && !chachapoly)\n    {\n        expected_epoch++;\n    }\n\n    assert_int_equal(co->key_ctx_bi.encrypt.epoch, expected_epoch);\n\n    if (!epoch)\n    {\n        /* Check always against the GCM usage limit here to see if that\n         * check works */\n        assert_true(\n            aead_usage_limit_reached((1ull << 36), &co->key_ctx_bi.encrypt, co->packet_id.send.id));\n        return;\n    }\n\n    /* Move to the end of the epoch data key send PID range, ChachaPoly\n     * should now also move to a new epoch data key */\n    co->packet_id.send.id = PACKET_ID_EPOCH_MAX;\n\n    encrypt_one_packet(co, 1000);\n    encrypt_one_packet(co, 1000);\n\n    expected_epoch++;\n    assert_int_equal(co->key_ctx_bi.encrypt.epoch, expected_epoch);\n}\n\n\nstatic struct crypto_options\ninit_crypto_options(const char *cipher, const char *auth, bool epoch, struct key2 *statickey)\n{\n    struct key2 key2 = { .n = 2 };\n\n    if (statickey)\n    {\n        /* Use chosen static key instead of random key when defined */\n        key2 = *statickey;\n    }\n    else\n    {\n        ASSERT(rand_bytes(key2.keys[0].cipher, sizeof(key2.keys[0].cipher)));\n        ASSERT(rand_bytes(key2.keys[0].hmac, sizeof(key2.keys[0].hmac)));\n        ASSERT(rand_bytes(key2.keys[1].cipher, sizeof(key2.keys[1].cipher)));\n        ASSERT(rand_bytes(key2.keys[1].hmac, sizeof(key2.keys)[1].hmac));\n    }\n\n    struct crypto_options co = { 0 };\n\n    struct key_type kt = create_kt(cipher, auth, \"ssl-test\");\n\n    if (epoch)\n    {\n        struct epoch_key e1 = { .epoch = 1, .epoch_key = { 0 } };\n        memcpy(e1.epoch_key, key2.keys[0].cipher, sizeof(e1.epoch_key));\n        co.flags |= CO_EPOCH_DATA_KEY_FORMAT;\n        epoch_init_key_ctx(&co, &kt, &e1, &e1, 5);\n\n        /* Do a little of dancing for the epoch_send_key_iterate to test\n         * that this works too */\n        epoch_iterate_send_key(&co);\n        epoch_iterate_send_key(&co);\n        epoch_iterate_send_key(&co);\n    }\n    else\n    {\n        init_key_ctx_bi(&co.key_ctx_bi, &key2, KEY_DIRECTION_BIDIRECTIONAL, &kt, \"unit-test-ssl\");\n    }\n    packet_id_init(&co.packet_id, 5, 5, \"UNITTEST\", 0);\n    return co;\n}\n\nstatic void\nuninit_crypto_options(struct crypto_options *co)\n{\n    packet_id_free(&co->packet_id);\n    free_key_ctx_bi(&co->key_ctx_bi);\n    free_epoch_key_ctx(co);\n}\n\n/* This adds a few more methods than strictly necessary but this allows\n * us to see which exact test was run from the backtrace of the test\n * when it fails */\nstatic void\nrun_data_channel_with_cipher_epoch(const char *cipher)\n{\n    bool ischacha = !strcmp(cipher, \"ChaCha20-Poly1305\");\n\n    struct crypto_options co = init_crypto_options(cipher, \"none\", true, NULL);\n    do_data_channel_round_trip(&co);\n    check_aead_limits(&co, ischacha);\n    uninit_crypto_options(&co);\n}\n\nstatic void\nrun_data_channel_with_cipher(const char *cipher, const char *auth)\n{\n    bool ischacha = !strcmp(cipher, \"ChaCha20-Poly1305\");\n    struct crypto_options co = init_crypto_options(cipher, auth, false, NULL);\n    do_data_channel_round_trip(&co);\n    check_aead_limits(&co, ischacha);\n    uninit_crypto_options(&co);\n}\n\n\nstatic void\ntest_data_channel_roundtrip_aes_128_gcm(void **state)\n{\n    run_data_channel_with_cipher(\"AES-128-GCM\", \"none\");\n}\n\nstatic void\ntest_data_channel_roundtrip_aes_128_gcm_epoch(void **state)\n{\n    run_data_channel_with_cipher_epoch(\"AES-128-GCM\");\n}\n\nstatic void\ntest_data_channel_roundtrip_aes_192_gcm(void **state)\n{\n    run_data_channel_with_cipher(\"AES-192-GCM\", \"none\");\n}\n\nstatic void\ntest_data_channel_roundtrip_aes_192_gcm_epoch(void **state)\n{\n    run_data_channel_with_cipher_epoch(\"AES-192-GCM\");\n}\n\nstatic void\ntest_data_channel_roundtrip_aes_256_gcm(void **state)\n{\n    run_data_channel_with_cipher(\"AES-256-GCM\", \"none\");\n}\n\nstatic void\ntest_data_channel_roundtrip_aes_256_gcm_epoch(void **state)\n{\n    run_data_channel_with_cipher_epoch(\"AES-256-GCM\");\n}\n\nstatic void\ntest_data_channel_roundtrip_aes_128_cbc(void **state)\n{\n    run_data_channel_with_cipher(\"AES-128-CBC\", \"SHA256\");\n}\n\nstatic void\ntest_data_channel_roundtrip_aes_192_cbc(void **state)\n{\n    run_data_channel_with_cipher(\"AES-192-CBC\", \"SHA256\");\n}\n\nstatic void\ntest_data_channel_roundtrip_aes_256_cbc(void **state)\n{\n    run_data_channel_with_cipher(\"AES-256-CBC\", \"SHA256\");\n}\n\nstatic void\ntest_data_channel_roundtrip_chacha20_poly1305(void **state)\n{\n    if (!cipher_valid(\"ChaCha20-Poly1305\"))\n    {\n        skip();\n        return;\n    }\n\n    run_data_channel_with_cipher(\"ChaCha20-Poly1305\", \"none\");\n}\n\nstatic void\ntest_data_channel_roundtrip_chacha20_poly1305_epoch(void **state)\n{\n    if (!cipher_valid(\"ChaCha20-Poly1305\"))\n    {\n        skip();\n        return;\n    }\n\n    run_data_channel_with_cipher_epoch(\"ChaCha20-Poly1305\");\n}\n\nstatic void\ntest_data_channel_roundtrip_bf_cbc(void **state)\n{\n    if (!cipher_valid(\"BF-CBC\"))\n    {\n        skip();\n        return;\n    }\n    run_data_channel_with_cipher(\"BF-CBC\", \"SHA1\");\n}\n\n\nstatic struct key2\ncreate_key(void)\n{\n    struct key2 key2 = { .n = 2 };\n\n    const uint8_t key[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '0', '1', '2',\n                            '3', '4', '5', '6', '7', 'A', 'B', 'C', 'D', 'E', 'F',\n                            'G', 'H', 'j', 'k', 'u', 'c', 'h', 'e', 'n', 'l' };\n\n    static_assert(sizeof(key) == 32, \"Size of key should be 32 bytes\");\n\n    /* copy the key a few times to ensure to have the size we need for\n     * Statickey but XOR it to not repeat it */\n    uint8_t keydata[sizeof(key2.keys)];\n\n    for (size_t i = 0; i < sizeof(key2.keys); i++)\n    {\n        keydata[i] = (uint8_t)(key[i % sizeof(key)] ^ i);\n    }\n\n    ASSERT(memcpy(key2.keys[0].cipher, keydata, sizeof(key2.keys[0].cipher)));\n    ASSERT(memcpy(key2.keys[0].hmac, keydata + 64, sizeof(key2.keys[0].hmac)));\n    ASSERT(memcpy(key2.keys[1].cipher, keydata + 128, sizeof(key2.keys[1].cipher)));\n    ASSERT(memcpy(key2.keys[1].hmac, keydata + 192, sizeof(key2.keys)[1].hmac));\n\n    return key2;\n}\n\nstatic void\ntest_data_channel_known_vectors_run(bool epoch)\n{\n    struct key2 key2 = create_key();\n\n    struct crypto_options co = init_crypto_options(\"AES-256-GCM\", \"none\", epoch, &key2);\n\n    struct gc_arena gc = gc_new();\n\n    /* initialise frame for the test */\n    struct frame frame;\n    init_frame_parameters(&frame);\n\n    struct buffer src = alloc_buf_gc(frame.buf.payload_size, &gc);\n    struct buffer work = alloc_buf_gc(BUF_SIZE(&frame), &gc);\n    struct buffer encrypt_workspace = alloc_buf_gc(BUF_SIZE(&frame), &gc);\n    struct buffer decrypt_workspace = alloc_buf_gc(BUF_SIZE(&frame), &gc);\n    struct buffer buf = clear_buf();\n    void *buf_p;\n\n    /* init work */\n    ASSERT(buf_init(&work, frame.buf.headroom));\n\n    now = 0;\n\n    /*\n     * Load src with known data.\n     */\n    ASSERT(buf_init(&src, 0));\n    const char *plaintext = \"The quick little fox jumps over the bureaucratic hurdles\";\n\n    ASSERT(buf_write(&src, plaintext, strlen(plaintext)));\n\n    /* copy source to input buf */\n    buf = work;\n    buf_p = buf_write_alloc(&buf, BLENZ(&src));\n    ASSERT(buf_p);\n    memcpy(buf_p, BPTR(&src), BLENZ(&src));\n\n    /* initialize work buffer with buf.headroom bytes of prepend capacity */\n    ASSERT(buf_init(&encrypt_workspace, frame.buf.headroom));\n\n    /* add packet opcode and peer id */\n    buf_write_u8(&encrypt_workspace, 7);\n    buf_write_u8(&encrypt_workspace, 0);\n    buf_write_u8(&encrypt_workspace, 0);\n    buf_write_u8(&encrypt_workspace, 23);\n\n    /* encrypt */\n    openvpn_encrypt(&buf, encrypt_workspace, &co);\n\n    /* separate buffer in authenticated data and encrypted data */\n    uint8_t *ad_start = BPTR(&buf);\n    buf_advance(&buf, 4);\n\n    if (epoch)\n    {\n        uint8_t packetid1[8] = { 0, 0x04, 0, 0, 0, 0, 0, 1 };\n        assert_memory_equal(BPTR(&buf), packetid1, 8);\n    }\n    else\n    {\n        uint8_t packetid1[4] = { 0, 0, 0, 1 };\n        assert_memory_equal(BPTR(&buf), packetid1, 4);\n    }\n\n    if (epoch)\n    {\n        uint8_t *tag_location = BEND(&buf) - OPENVPN_AEAD_TAG_LENGTH;\n        const uint8_t exp_tag_epoch[16] = { 0x0f, 0xff, 0xf5, 0x91, 0x3d, 0x39, 0xd7, 0x5b,\n                                            0x18, 0x57, 0x3b, 0x57, 0x48, 0x58, 0x9a, 0x7d };\n\n        assert_memory_equal(tag_location, exp_tag_epoch, OPENVPN_AEAD_TAG_LENGTH);\n    }\n    else\n    {\n        uint8_t *tag_location = BPTR(&buf) + 4;\n        const uint8_t exp_tag_noepoch[16] = { 0x1f, 0xdd, 0x90, 0x8f, 0x0e, 0x9d, 0xc2, 0x5e,\n                                              0x79, 0xd8, 0x32, 0x02, 0x0d, 0x58, 0xe7, 0x3f };\n        assert_memory_equal(tag_location, exp_tag_noepoch, OPENVPN_AEAD_TAG_LENGTH);\n    }\n\n    /* Check some bytes at the beginning of the encrypted part */\n    if (epoch)\n    {\n        const uint8_t bytesat14[6] = { 0x36, 0xaa, 0xb4, 0xd4, 0x9c, 0xe6 };\n        assert_memory_equal(BPTR(&buf) + 14, bytesat14, sizeof(bytesat14));\n    }\n    else\n    {\n        const uint8_t bytesat30[6] = { 0xa8, 0x2e, 0x6b, 0x17, 0x06, 0xd9 };\n        assert_memory_equal(BPTR(&buf) + 30, bytesat30, sizeof(bytesat30));\n    }\n\n    /* decrypt */\n    openvpn_decrypt(&buf, decrypt_workspace, &co, &frame, ad_start);\n\n    /* compare */\n    assert_int_equal(buf.len, strlen(plaintext));\n    assert_memory_equal(BPTR(&buf), plaintext, strlen(plaintext));\n\n    uninit_crypto_options(&co);\n    gc_free(&gc);\n}\n\nstatic void\ntest_data_channel_known_vectors_epoch(void **state)\n{\n    test_data_channel_known_vectors_run(true);\n}\n\nstatic void\ntest_data_channel_known_vectors_shortpktid(void **state)\n{\n    test_data_channel_known_vectors_run(false);\n}\n\n\nint\nmain(void)\n{\n    openvpn_unit_test_setup();\n\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(crypto_pem_encode_certificate),\n        cmocka_unit_test(test_load_certificate_and_key),\n        cmocka_unit_test(test_load_certificate_and_key_uri),\n        cmocka_unit_test(test_data_channel_roundtrip_aes_128_gcm),\n        cmocka_unit_test(test_data_channel_roundtrip_aes_128_gcm_epoch),\n        cmocka_unit_test(test_data_channel_roundtrip_aes_192_gcm),\n        cmocka_unit_test(test_data_channel_roundtrip_aes_192_gcm_epoch),\n        cmocka_unit_test(test_data_channel_roundtrip_aes_256_gcm),\n        cmocka_unit_test(test_data_channel_roundtrip_aes_256_gcm_epoch),\n        cmocka_unit_test(test_data_channel_roundtrip_chacha20_poly1305),\n        cmocka_unit_test(test_data_channel_roundtrip_chacha20_poly1305_epoch),\n        cmocka_unit_test(test_data_channel_roundtrip_aes_128_cbc),\n        cmocka_unit_test(test_data_channel_roundtrip_aes_192_cbc),\n        cmocka_unit_test(test_data_channel_roundtrip_aes_256_cbc),\n        cmocka_unit_test(test_data_channel_roundtrip_bf_cbc),\n        cmocka_unit_test(test_data_channel_known_vectors_epoch),\n        cmocka_unit_test(test_data_channel_known_vectors_shortpktid)\n    };\n\n#if defined(ENABLE_CRYPTO_OPENSSL)\n    tls_init_lib();\n#endif\n\n    int ret = cmocka_run_group_tests_name(\"ssl tests\", tests, init, cleanup);\n\n#if defined(ENABLE_CRYPTO_OPENSSL)\n    tls_free_lib();\n#endif\n\n    return ret;\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_tls_crypt.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2016-2026 Sentyron B.V. <openvpn@sentyron.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License version 2\n *  as published by the Free Software Foundation.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <setjmp.h>\n#include <cmocka.h>\n\n#include \"test_common.h\"\n#include \"tls_crypt.c\"\n\n/* Define this function here as dummy since including the ssl_*.c files\n * leads to having to include even more unrelated code */\nbool\nkey_state_export_keying_material(struct tls_session *session, const char *label, size_t label_size,\n                                 void *ekm, size_t ekm_size)\n{\n    memset(ekm, 0xba, ekm_size);\n    return true;\n}\n\n\n#define TESTBUF_SIZE 128\n\n/* Defines for use in the tests and the mock parse_line() */\n#define PATH1  \"/s p a c e\"\n#define PATH2  \"/foo bar/baz\"\n#define PARAM1 \"param1\"\n#define PARAM2 \"param two\"\n\nstatic const char *test_server_key =\n    \"-----BEGIN OpenVPN tls-crypt-v2 server key-----\\n\"\n    \"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v\\n\"\n    \"MDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f\\n\"\n    \"YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8=\\n\"\n    \"-----END OpenVPN tls-crypt-v2 server key-----\\n\";\n\nstatic const char *test_client_key =\n    \"-----BEGIN OpenVPN tls-crypt-v2 client key-----\\n\"\n    \"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v\\n\"\n    \"MDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f\\n\"\n    \"YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P\\n\"\n    \"kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/\\n\"\n    \"wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v\\n\"\n    \"8PHy8/T19vf4+fr7/P3+/xd9pcB0qUYZsWvkrLcfGmzPJPM8a7r0mEWdXwbDadSV\\n\"\n    \"LHg5bv2TwlmPR3HgaMr8o9LTh9hxUTkrH3S0PfKRNwcso86ua/dBFTyXsM9tg4aw\\n\"\n    \"3dS6ogH9AkaT+kRRDgNcKWkQCbwmJK2JlfkXHBwbAtmn78AkNuho6QCFqCdqGab3\\n\"\n    \"zh2vheFqGMPdGpukbFrT3rcO3VLxUeG+RdzXiMTCpJSovFBP1lDkYwYJPnz6daEh\\n\"\n    \"j0TzJ3BVru9W3CpotdNt7u09knxAfpCxjtrP3semsDew/gTBtcfQ/OoTFyFHnN5k\\n\"\n    \"RZ+q17SC4nba3Pp8/Fs0+hSbv2tJozoD8SElFq7SIWJsciTYh8q8f5yQxjdt4Wxu\\n\"\n    \"/Z5wtPCAZ0tOzj4ItTI77fBOYRTfEayzHgEr\\n\"\n    \"-----END OpenVPN tls-crypt-v2 client key-----\\n\";\n\n\n/* Has custom metadata of AABBCCDD (base64) */\nstatic const char *test_client_key_metadata =\n    \"-----BEGIN OpenVPN tls-crypt-v2 client key-----\\n\"\n    \"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v\\n\"\n    \"MDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f\\n\"\n    \"YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P\\n\"\n    \"kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/\\n\"\n    \"wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v\\n\"\n    \"8PHy8/T19vf4+fr7/P3+/2ntp1WCqhcLjJQY/igkjNt3Yb6i0neqFkfrOp2UCDcz\\n\"\n    \"6RSJtPLZbvOOKUHk2qwxPYUsFCnz/IWV6/ZiLRrabzUpS8oSN1HS6P7qqAdrHKgf\\n\"\n    \"hVTHasdSf2UdMTPC7HBgnP9Ll0FhKN0h7vSzbbt7QM7wH9mr1ecc/Mt0SYW2lpwA\\n\"\n    \"aJObYGTyk6hTgWm0g/MLrworLrezTqUHBZzVsu+LDyqLWK1lzJNd66MuNOsGA4YF\\n\"\n    \"fbCsDh8n3H+Cw1k5YNBZDYYJOtVUgBWXheO6vgoOmqDdI0dAQ3hVo9DE+SkCFjgf\\n\"\n    \"l4FY2yLEh9ZVZZrl1eD1Owh/X178CkHrBJYl9LNQSyQEKlDGWwBLQ/pY3qtjctr3\\n\"\n    \"pV62MPQdBo+1lcsjDCJVQA6XUyltas4BKQ==\\n\"\n    \"-----END OpenVPN tls-crypt-v2 client key-----\\n\";\n\nint\n__wrap_parse_line(const char *line, char **p, const int n, const char *file, const int line_num,\n                  msglvl_t msglevel, struct gc_arena *gc)\n{\n    p[0] = PATH1 PATH2;\n    p[1] = PARAM1;\n    p[2] = PARAM2;\n    return 3;\n}\n\nbool\n__wrap_buffer_write_file(const char *filename, const struct buffer *buf)\n{\n    const char *pem = BSTR(buf);\n    check_expected_ptr(filename);\n    check_expected_ptr(pem);\n\n    return mock_type(bool);\n}\n\nstruct buffer\n__wrap_buffer_read_from_file(const char *filename, struct gc_arena *gc)\n{\n    check_expected_ptr(filename);\n\n    const char *pem_str = mock_ptr_type(const char *);\n    struct buffer ret = alloc_buf_gc(strlen(pem_str) + 1, gc);\n    buf_write(&ret, pem_str, strlen(pem_str) + 1);\n\n    return ret;\n}\n\n\n/** Predictable random for tests */\nint\n__wrap_rand_bytes(uint8_t *output, int len)\n{\n    for (int i = 0; i < len; i++)\n    {\n        output[i] = (uint8_t)i;\n    }\n    return true;\n}\n\nstruct test_tls_crypt_context\n{\n    struct crypto_options co;\n    struct key_type kt;\n    struct buffer source;\n    struct buffer ciphertext;\n    struct buffer unwrapped;\n};\n\n\nstatic int\ntest_tls_crypt_setup(void **state)\n{\n    struct test_tls_crypt_context *ctx = calloc(1, sizeof(*ctx));\n    *state = ctx;\n\n    struct key_parameters key = { .cipher = { 0 },\n                                  .hmac = { 0 },\n                                  .hmac_size = MAX_HMAC_KEY_LENGTH,\n                                  .cipher_size = MAX_CIPHER_KEY_LENGTH };\n\n    ctx->kt = tls_crypt_kt();\n    if (!ctx->kt.cipher || !ctx->kt.digest)\n    {\n        return 0;\n    }\n    init_key_ctx(&ctx->co.key_ctx_bi.encrypt, &key, &ctx->kt, true, \"TEST\");\n    init_key_ctx(&ctx->co.key_ctx_bi.decrypt, &key, &ctx->kt, false, \"TEST\");\n\n    packet_id_init(&ctx->co.packet_id, 0, 0, \"test\", 0);\n\n    ctx->source = alloc_buf(TESTBUF_SIZE);\n    ctx->ciphertext = alloc_buf(TESTBUF_SIZE);\n    ctx->unwrapped = alloc_buf(TESTBUF_SIZE);\n\n    /* Write test plaintext */\n    const char *plaintext = \"1234567890\";\n    buf_write(&ctx->source, plaintext, strlen(plaintext));\n\n    /* Write test ciphertext */\n    const char *ciphertext = \"012345678\";\n    buf_write(&ctx->ciphertext, ciphertext, strlen(ciphertext));\n\n    return 0;\n}\n\nstatic int\ntest_tls_crypt_teardown(void **state)\n{\n    struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *)*state;\n\n    free_buf(&ctx->source);\n    free_buf(&ctx->ciphertext);\n    free_buf(&ctx->unwrapped);\n\n    free_key_ctx_bi(&ctx->co.key_ctx_bi);\n\n    free(ctx);\n\n    return 0;\n}\n\nstatic void\nskip_if_tls_crypt_not_supported(struct test_tls_crypt_context *ctx)\n{\n    if (!ctx->kt.cipher || !ctx->kt.digest)\n    {\n        skip();\n    }\n}\n\n/**\n * Check that short messages are successfully wrapped-and-unwrapped.\n */\nstatic void\ntls_crypt_loopback(void **state)\n{\n    struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *)*state;\n\n    skip_if_tls_crypt_not_supported(ctx);\n\n    assert_true(tls_crypt_wrap(&ctx->source, &ctx->ciphertext, &ctx->co));\n    assert_true(BLEN(&ctx->source) < BLEN(&ctx->ciphertext));\n    assert_true(tls_crypt_unwrap(&ctx->ciphertext, &ctx->unwrapped, &ctx->co));\n    assert_int_equal(BLEN(&ctx->source), BLEN(&ctx->unwrapped));\n    assert_memory_equal(BPTR(&ctx->source), BPTR(&ctx->unwrapped), BLENZ(&ctx->source));\n}\n\n\n/**\n * Test generating dynamic tls-crypt key\n */\nstatic void\ntest_tls_crypt_secure_reneg_key(void **state)\n{\n    struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *)*state;\n\n    struct gc_arena gc = gc_new();\n\n    struct tls_session session = { 0 };\n\n    struct tls_options tls_opt = { 0 };\n    tls_opt.replay_window = 32;\n    tls_opt.replay_time = 60;\n    tls_opt.frame.buf.payload_size = 512;\n    session.opt = &tls_opt;\n\n    tls_session_generate_dynamic_tls_crypt_key(&session);\n\n    struct tls_wrap_ctx *rctx = &session.tls_wrap_reneg;\n\n    tls_crypt_wrap(&ctx->source, &rctx->work, &rctx->opt);\n    assert_int_equal(buf_len(&ctx->source) + 40, buf_len(&rctx->work));\n\n    uint8_t expected_ciphertext[] = { 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x19,\n                                      0x27, 0x7f, 0x1c, 0x8d, 0x6e, 0x6a, 0x77, 0x96, 0xa8, 0x55,\n                                      0x33, 0x7b, 0x9c, 0xfb, 0x56, 0xe1, 0xf1, 0x3a, 0x87, 0x0e,\n                                      0x66, 0x47, 0xdf, 0xa1, 0x95, 0xc9, 0x2c, 0x17, 0xa0, 0x15,\n                                      0xba, 0x49, 0x67, 0xa1, 0x1d, 0x55, 0xea, 0x1a, 0x06, 0xa7 };\n    assert_memory_equal(BPTR(&rctx->work), expected_ciphertext, BLENZ(&rctx->work));\n    tls_wrap_free(&session.tls_wrap_reneg);\n\n    /* Use previous tls-crypt key as 0x00, with xor we should have the same key\n     * and expect the same result */\n    session.tls_wrap.mode = TLS_WRAP_CRYPT;\n    memset(&session.tls_wrap.original_wrap_keydata.keys, 0x00,\n           sizeof(session.tls_wrap.original_wrap_keydata.keys));\n    session.tls_wrap.original_wrap_keydata.n = 2;\n\n    tls_session_generate_dynamic_tls_crypt_key(&session);\n    tls_crypt_wrap(&ctx->source, &rctx->work, &rctx->opt);\n    assert_int_equal(buf_len(&ctx->source) + 40, buf_len(&rctx->work));\n\n    assert_memory_equal(BPTR(&rctx->work), expected_ciphertext, BLENZ(&rctx->work));\n    tls_wrap_free(&session.tls_wrap_reneg);\n\n    /* XOR should not force a different key */\n    memset(&session.tls_wrap.original_wrap_keydata.keys, 0x42,\n           sizeof(session.tls_wrap.original_wrap_keydata.keys));\n    tls_session_generate_dynamic_tls_crypt_key(&session);\n\n    tls_crypt_wrap(&ctx->source, &rctx->work, &rctx->opt);\n    assert_int_equal(buf_len(&ctx->source) + 40, buf_len(&rctx->work));\n\n    /* packet id at the start should be equal */\n    assert_memory_equal(BPTR(&rctx->work), expected_ciphertext, 8);\n\n    /* Skip packet id */\n    buf_advance(&rctx->work, 8);\n    assert_memory_not_equal(BPTR(&rctx->work), expected_ciphertext, BLENZ(&rctx->work));\n    tls_wrap_free(&session.tls_wrap_reneg);\n\n\n    gc_free(&gc);\n}\n\n/**\n * Check that zero-byte messages are successfully wrapped-and-unwrapped.\n */\nstatic void\ntls_crypt_loopback_zero_len(void **state)\n{\n    struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *)*state;\n\n    skip_if_tls_crypt_not_supported(ctx);\n\n    buf_clear(&ctx->source);\n\n    assert_true(tls_crypt_wrap(&ctx->source, &ctx->ciphertext, &ctx->co));\n    assert_true(BLEN(&ctx->source) < BLEN(&ctx->ciphertext));\n    assert_true(tls_crypt_unwrap(&ctx->ciphertext, &ctx->unwrapped, &ctx->co));\n    assert_int_equal(BLEN(&ctx->source), BLEN(&ctx->unwrapped));\n    assert_memory_equal(BPTR(&ctx->source), BPTR(&ctx->unwrapped), BLENZ(&ctx->source));\n}\n\n/**\n * Check that max-length messages are successfully wrapped-and-unwrapped.\n */\nstatic void\ntls_crypt_loopback_max_len(void **state)\n{\n    struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *)*state;\n\n    skip_if_tls_crypt_not_supported(ctx);\n\n    buf_clear(&ctx->source);\n    assert_non_null(buf_write_alloc(&ctx->source, TESTBUF_SIZE - BLEN(&ctx->ciphertext)\n                                                      - tls_crypt_buf_overhead()));\n\n    assert_true(tls_crypt_wrap(&ctx->source, &ctx->ciphertext, &ctx->co));\n    assert_true(BLEN(&ctx->source) < BLEN(&ctx->ciphertext));\n    assert_true(tls_crypt_unwrap(&ctx->ciphertext, &ctx->unwrapped, &ctx->co));\n    assert_int_equal(BLEN(&ctx->source), BLEN(&ctx->unwrapped));\n    assert_memory_equal(BPTR(&ctx->source), BPTR(&ctx->unwrapped), BLENZ(&ctx->source));\n}\n\n/**\n * Check that too-long messages are gracefully rejected.\n */\nstatic void\ntls_crypt_fail_msg_too_long(void **state)\n{\n    struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *)*state;\n\n    skip_if_tls_crypt_not_supported(ctx);\n\n    buf_clear(&ctx->source);\n    assert_non_null(buf_write_alloc(&ctx->source, TESTBUF_SIZE - BLEN(&ctx->ciphertext)\n                                                      - tls_crypt_buf_overhead() + 1));\n    assert_false(tls_crypt_wrap(&ctx->source, &ctx->ciphertext, &ctx->co));\n}\n\n/**\n * Check that packets that were wrapped (or unwrapped) with a different key\n * are not accepted.\n */\nstatic void\ntls_crypt_fail_invalid_key(void **state)\n{\n    struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *)*state;\n\n    skip_if_tls_crypt_not_supported(ctx);\n\n    /* Change decrypt key */\n    struct key_parameters key = { .cipher = { 1 },\n                                  .hmac = { 1 },\n                                  .cipher_size = MAX_CIPHER_KEY_LENGTH,\n                                  .hmac_size = MAX_HMAC_KEY_LENGTH };\n    free_key_ctx(&ctx->co.key_ctx_bi.decrypt);\n    init_key_ctx(&ctx->co.key_ctx_bi.decrypt, &key, &ctx->kt, false, \"TEST\");\n\n    assert_true(tls_crypt_wrap(&ctx->source, &ctx->ciphertext, &ctx->co));\n    assert_true(BLEN(&ctx->source) < BLEN(&ctx->ciphertext));\n    assert_false(tls_crypt_unwrap(&ctx->ciphertext, &ctx->unwrapped, &ctx->co));\n}\n\n/**\n * Check that replayed packets are not accepted.\n */\nstatic void\ntls_crypt_fail_replay(void **state)\n{\n    struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *)*state;\n\n    skip_if_tls_crypt_not_supported(ctx);\n\n    assert_true(tls_crypt_wrap(&ctx->source, &ctx->ciphertext, &ctx->co));\n    assert_true(BLEN(&ctx->source) < BLEN(&ctx->ciphertext));\n    struct buffer tmp = ctx->ciphertext;\n    assert_true(tls_crypt_unwrap(&tmp, &ctx->unwrapped, &ctx->co));\n    buf_clear(&ctx->unwrapped);\n    assert_false(tls_crypt_unwrap(&ctx->ciphertext, &ctx->unwrapped, &ctx->co));\n}\n\n/**\n * Check that packet replays are accepted when CO_IGNORE_PACKET_ID is set. This\n * is used for the first control channel packet that arrives, because we don't\n * know the packet ID yet.\n */\nstatic void\ntls_crypt_ignore_replay(void **state)\n{\n    struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *)*state;\n\n    skip_if_tls_crypt_not_supported(ctx);\n\n    ctx->co.flags |= CO_IGNORE_PACKET_ID;\n\n    assert_true(tls_crypt_wrap(&ctx->source, &ctx->ciphertext, &ctx->co));\n    assert_true(BLEN(&ctx->source) < BLEN(&ctx->ciphertext));\n    struct buffer tmp = ctx->ciphertext;\n    assert_true(tls_crypt_unwrap(&tmp, &ctx->unwrapped, &ctx->co));\n    buf_clear(&ctx->unwrapped);\n    assert_true(tls_crypt_unwrap(&ctx->ciphertext, &ctx->unwrapped, &ctx->co));\n}\n\nstruct test_tls_crypt_v2_context\n{\n    struct gc_arena gc;\n    struct key2 server_key2;\n    struct key_ctx_bi server_keys;\n    struct key2 client_key2;\n    struct key_ctx_bi client_key;\n    struct buffer metadata;\n    struct buffer unwrapped_metadata;\n    struct buffer wkc;\n};\n\nstatic int\ntest_tls_crypt_v2_setup(void **state)\n{\n    struct test_tls_crypt_v2_context *ctx = calloc(1, sizeof(*ctx));\n    *state = ctx;\n\n    ctx->gc = gc_new();\n\n    /* Slightly longer buffers to be able to test too-long data */\n    ctx->metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN + 16, &ctx->gc);\n    ctx->unwrapped_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN + 16, &ctx->gc);\n    ctx->wkc = alloc_buf_gc(TLS_CRYPT_V2_MAX_WKC_LEN + 16, &ctx->gc);\n\n    /* Generate server key */\n    rand_bytes((void *)ctx->server_key2.keys, sizeof(ctx->server_key2.keys));\n    ctx->server_key2.n = 2;\n    struct key_type kt = tls_crypt_kt();\n    init_key_ctx_bi(&ctx->server_keys, &ctx->server_key2, KEY_DIRECTION_BIDIRECTIONAL, &kt,\n                    \"tls-crypt-v2 server key\");\n\n    /* Generate client key */\n    rand_bytes((void *)ctx->client_key2.keys, sizeof(ctx->client_key2.keys));\n    ctx->client_key2.n = 2;\n\n    return 0;\n}\n\nstatic int\ntest_tls_crypt_v2_teardown(void **state)\n{\n    struct test_tls_crypt_v2_context *ctx = (struct test_tls_crypt_v2_context *)*state;\n\n    free_key_ctx_bi(&ctx->server_keys);\n    free_key_ctx_bi(&ctx->client_key);\n\n    gc_free(&ctx->gc);\n\n    free(ctx);\n\n    return 0;\n}\n\n/**\n * Check wrapping and unwrapping a tls-crypt-v2 client key without metadata.\n */\nstatic void\ntls_crypt_v2_wrap_unwrap_no_metadata(void **state)\n{\n    struct test_tls_crypt_v2_context *ctx = (struct test_tls_crypt_v2_context *)*state;\n\n    struct buffer wrapped_client_key = alloc_buf_gc(TLS_CRYPT_V2_MAX_WKC_LEN, &ctx->gc);\n    assert_true(tls_crypt_v2_wrap_client_key(&wrapped_client_key, &ctx->client_key2, &ctx->metadata,\n                                             &ctx->server_keys.encrypt, &ctx->gc));\n\n    struct buffer unwrap_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN, &ctx->gc);\n    struct key2 unwrapped_client_key2 = { 0 };\n    assert_true(tls_crypt_v2_unwrap_client_key(&unwrapped_client_key2, &unwrap_metadata,\n                                               wrapped_client_key, &ctx->server_keys.decrypt));\n\n    assert_memory_equal(ctx->client_key2.keys, unwrapped_client_key2.keys,\n                        sizeof(ctx->client_key2.keys));\n}\n\n/**\n * Check wrapping and unwrapping a tls-crypt-v2 client key with maximum length\n * metadata.\n */\nstatic void\ntls_crypt_v2_wrap_unwrap_max_metadata(void **state)\n{\n    struct test_tls_crypt_v2_context *ctx = (struct test_tls_crypt_v2_context *)*state;\n\n    uint8_t *metadata = buf_write_alloc(&ctx->metadata, TLS_CRYPT_V2_MAX_METADATA_LEN);\n    assert_true(rand_bytes(metadata, TLS_CRYPT_V2_MAX_METADATA_LEN));\n    assert_true(tls_crypt_v2_wrap_client_key(&ctx->wkc, &ctx->client_key2, &ctx->metadata,\n                                             &ctx->server_keys.encrypt, &ctx->gc));\n\n    struct buffer unwrap_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN, &ctx->gc);\n    struct key2 unwrapped_client_key2 = { 0 };\n    assert_true(tls_crypt_v2_unwrap_client_key(&unwrapped_client_key2, &unwrap_metadata, ctx->wkc,\n                                               &ctx->server_keys.decrypt));\n\n    assert_memory_equal(ctx->client_key2.keys, unwrapped_client_key2.keys,\n                        sizeof(ctx->client_key2.keys));\n    assert_true(buf_equal(&ctx->metadata, &unwrap_metadata));\n\n    struct tls_wrap_ctx wrap_ctx = {\n        .mode = TLS_WRAP_CRYPT,\n        .tls_crypt_v2_server_key = ctx->server_keys.encrypt,\n    };\n    assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx, NULL, true));\n    tls_wrap_free(&wrap_ctx);\n}\n\n/**\n * Check that wrapping a tls-crypt-v2 client key with too long metadata fails\n * as expected.\n */\nstatic void\ntls_crypt_v2_wrap_too_long_metadata(void **state)\n{\n    struct test_tls_crypt_v2_context *ctx = (struct test_tls_crypt_v2_context *)*state;\n\n    assert_true(buf_inc_len(&ctx->metadata, TLS_CRYPT_V2_MAX_METADATA_LEN + 1));\n    assert_false(tls_crypt_v2_wrap_client_key(&ctx->wkc, &ctx->client_key2, &ctx->metadata,\n                                              &ctx->server_keys.encrypt, &ctx->gc));\n}\n\n/**\n * Check that unwrapping a tls-crypt-v2 client key with the wrong server key\n * fails as expected.\n */\nstatic void\ntls_crypt_v2_wrap_unwrap_wrong_key(void **state)\n{\n    struct test_tls_crypt_v2_context *ctx = (struct test_tls_crypt_v2_context *)*state;\n\n    assert_true(tls_crypt_v2_wrap_client_key(&ctx->wkc, &ctx->client_key2, &ctx->metadata,\n                                             &ctx->server_keys.encrypt, &ctx->gc));\n\n    /* Change server key */\n    struct key_type kt = tls_crypt_kt();\n    free_key_ctx_bi(&ctx->server_keys);\n    memset(&ctx->server_key2.keys, 0, sizeof(ctx->server_key2.keys));\n    init_key_ctx_bi(&ctx->server_keys, &ctx->server_key2, KEY_DIRECTION_BIDIRECTIONAL, &kt,\n                    \"wrong tls-crypt-v2 server key\");\n\n\n    struct key2 unwrapped_client_key2 = { 0 };\n    assert_false(tls_crypt_v2_unwrap_client_key(&unwrapped_client_key2, &ctx->unwrapped_metadata,\n                                                ctx->wkc, &ctx->server_keys.decrypt));\n\n    const struct key2 zero = { 0 };\n    assert_memory_equal(&unwrapped_client_key2, &zero, sizeof(zero));\n    assert_int_equal(0, BLEN(&ctx->unwrapped_metadata));\n}\n\n/**\n * Check that unwrapping a tls-crypt-v2 client key to a too small metadata\n * buffer fails as expected.\n */\nstatic void\ntls_crypt_v2_wrap_unwrap_dst_too_small(void **state)\n{\n    struct test_tls_crypt_v2_context *ctx = (struct test_tls_crypt_v2_context *)*state;\n\n    uint8_t *metadata = buf_write_alloc(&ctx->metadata, TLS_CRYPT_V2_MAX_METADATA_LEN);\n    assert_true(rand_bytes(metadata, TLS_CRYPT_V2_MAX_METADATA_LEN));\n    assert_true(tls_crypt_v2_wrap_client_key(&ctx->wkc, &ctx->client_key2, &ctx->metadata,\n                                             &ctx->server_keys.encrypt, &ctx->gc));\n\n    struct key2 unwrapped_client_key2 = { 0 };\n    struct buffer unwrapped_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN - 1, &ctx->gc);\n    assert_false(tls_crypt_v2_unwrap_client_key(&unwrapped_client_key2, &unwrapped_metadata,\n                                                ctx->wkc, &ctx->server_keys.decrypt));\n\n    const struct key2 zero = { 0 };\n    assert_memory_equal(&unwrapped_client_key2, &zero, sizeof(zero));\n    assert_int_equal(0, BLEN(&ctx->unwrapped_metadata));\n}\n\nstatic void\ntest_tls_crypt_v2_write_server_key_file(void **state)\n{\n    const char *filename = \"testfilename.key\";\n\n    expect_string(__wrap_buffer_write_file, filename, filename);\n    expect_memory(__wrap_buffer_write_file, pem, test_server_key, strlen(test_server_key));\n    will_return(__wrap_buffer_write_file, true);\n\n    tls_crypt_v2_write_server_key_file(filename);\n}\n\nstatic void\ntest_tls_crypt_v2_write_client_key_file(void **state)\n{\n    const char *filename = \"testfilename.key\";\n\n    /* Test writing the client key */\n    expect_string(__wrap_buffer_write_file, filename, filename);\n    expect_memory(__wrap_buffer_write_file, pem, test_client_key, strlen(test_client_key));\n    will_return(__wrap_buffer_write_file, true);\n\n    /* Key generation re-reads the created file as a sanity check */\n    expect_string(__wrap_buffer_read_from_file, filename, filename);\n    will_return(__wrap_buffer_read_from_file, test_client_key);\n\n    tls_crypt_v2_write_client_key_file(filename, NULL, test_server_key, true);\n}\n\nstatic void\ntest_tls_crypt_v2_write_client_key_file_metadata(void **state)\n{\n    const char *filename = \"testfilename.key\";\n    const char *b64metadata = \"AABBCCDD\";\n\n    /* Test writing the client key */\n    expect_string(__wrap_buffer_write_file, filename, filename);\n    expect_memory(__wrap_buffer_write_file, pem, test_client_key_metadata,\n                  strlen(test_client_key_metadata));\n    will_return(__wrap_buffer_write_file, true);\n\n    /* Key generation re-reads the created file as a sanity check */\n    expect_string(__wrap_buffer_read_from_file, filename, filename);\n    will_return(__wrap_buffer_read_from_file, test_client_key_metadata);\n\n    tls_crypt_v2_write_client_key_file(filename, b64metadata, test_server_key, true);\n}\n\nint\nmain(void)\n{\n    openvpn_unit_test_setup();\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test_setup_teardown(tls_crypt_loopback, test_tls_crypt_setup,\n                                        test_tls_crypt_teardown),\n        cmocka_unit_test_setup_teardown(tls_crypt_loopback_zero_len, test_tls_crypt_setup,\n                                        test_tls_crypt_teardown),\n        cmocka_unit_test_setup_teardown(tls_crypt_loopback_max_len, test_tls_crypt_setup,\n                                        test_tls_crypt_teardown),\n        cmocka_unit_test_setup_teardown(tls_crypt_fail_msg_too_long, test_tls_crypt_setup,\n                                        test_tls_crypt_teardown),\n        cmocka_unit_test_setup_teardown(tls_crypt_fail_invalid_key, test_tls_crypt_setup,\n                                        test_tls_crypt_teardown),\n        cmocka_unit_test_setup_teardown(tls_crypt_fail_replay, test_tls_crypt_setup,\n                                        test_tls_crypt_teardown),\n        cmocka_unit_test_setup_teardown(tls_crypt_ignore_replay, test_tls_crypt_setup,\n                                        test_tls_crypt_teardown),\n        cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_no_metadata,\n                                        test_tls_crypt_v2_setup, test_tls_crypt_v2_teardown),\n        cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_max_metadata,\n                                        test_tls_crypt_v2_setup, test_tls_crypt_v2_teardown),\n        cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_too_long_metadata,\n                                        test_tls_crypt_v2_setup, test_tls_crypt_v2_teardown),\n        cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_wrong_key, test_tls_crypt_v2_setup,\n                                        test_tls_crypt_v2_teardown),\n        cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_dst_too_small,\n                                        test_tls_crypt_v2_setup, test_tls_crypt_v2_teardown),\n        cmocka_unit_test_setup_teardown(test_tls_crypt_secure_reneg_key, test_tls_crypt_setup,\n                                        test_tls_crypt_teardown),\n        cmocka_unit_test(test_tls_crypt_v2_write_server_key_file),\n        cmocka_unit_test(test_tls_crypt_v2_write_client_key_file),\n        cmocka_unit_test(test_tls_crypt_v2_write_client_key_file_metadata),\n    };\n\n    return cmocka_run_group_tests_name(\"tls-crypt tests\", tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpn/test_user_pass.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2023-2026 OpenVPN Inc <sales@openvpn.net>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by the\n *  Free Software Foundation, either version 2 of the License,\n *  or (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include \"syshead.h\"\n#include \"manage.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <setjmp.h>\n#include <cmocka.h>\n#include \"test_common.h\"\n\n#include \"misc.c\"\n\nstruct management *management; /* global */\n\n/* mocking */\n#if defined(ENABLE_SYSTEMD)\nbool\nquery_user_exec_systemd(void)\n{\n    return query_user_exec_builtin();\n}\n#endif\nbool\nquery_user_exec_builtin(void)\n{\n    /* Loop through configured query_user slots */\n    for (int i = 0; i < QUERY_USER_NUMSLOTS && query_user[i].response != NULL; i++)\n    {\n        check_expected_ptr(query_user[i].prompt);\n        strncpy(query_user[i].response, mock_ptr_type(char *), query_user[i].response_len);\n    }\n\n    return mock();\n}\nvoid\nmanagement_auth_failure(struct management *man, const char *type, const char *reason)\n{\n    assert_true(0);\n}\nbool\nmanagement_query_user_pass(struct management *man, struct user_pass *up, const char *type,\n                           const unsigned int flags, const char *static_challenge)\n{\n    assert_true(0);\n    return false;\n}\n/* stubs for some unused functions instead of pulling in too many dependencies */\nint\nparse_line(const char *line, char **p, const int n, const char *file, const int line_num,\n           msglvl_t msglevel, struct gc_arena *gc)\n{\n    assert_true(0);\n    return 0;\n}\n\n#ifdef _WIN32\nbool\nprotect_buffer_win32(char *buf, DWORD len)\n{\n    return true;\n}\n\nbool\nunprotect_buffer_win32(char *buf, DWORD len)\n{\n    return true;\n}\n#endif\n\n/* tooling */\nstatic void\nreset_user_pass(struct user_pass *up)\n{\n    up->defined = false;\n    up->token_defined = false;\n    up->nocache = false;\n    strcpy(up->username, \"user\");\n    strcpy(up->password, \"password\");\n}\n\nstatic void\ntest_get_user_pass_defined(void **state)\n{\n    struct user_pass up = { 0 };\n    reset_user_pass(&up);\n    up.defined = true;\n    assert_true(get_user_pass_cr(&up, NULL, \"UT\", 0, NULL));\n}\n\nstatic void\ntest_get_user_pass_needok(void **state)\n{\n    struct user_pass up = { 0 };\n    reset_user_pass(&up);\n    unsigned int flags = GET_USER_PASS_NEED_OK;\n\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"NEED-OK|UT|user:\");\n    will_return(query_user_exec_builtin, \"\");\n    will_return(query_user_exec_builtin, true);\n    /*FIXME: query_user_exec() called even though nothing queued */\n    will_return(query_user_exec_builtin, true);\n    assert_true(get_user_pass_cr(&up, NULL, \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.password, \"ok\");\n\n    reset_user_pass(&up);\n\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"NEED-OK|UT|user:\");\n    will_return(query_user_exec_builtin, \"cancel\");\n    will_return(query_user_exec_builtin, true);\n    /*FIXME: query_user_exec() called even though nothing queued */\n    will_return(query_user_exec_builtin, true);\n    assert_true(get_user_pass_cr(&up, NULL, \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.password, \"cancel\");\n}\n\nstatic void\ntest_get_user_pass_inline_creds(void **state)\n{\n    struct user_pass up = { 0 };\n    reset_user_pass(&up);\n    unsigned int flags = GET_USER_PASS_INLINE_CREDS;\n\n    /*FIXME: query_user_exec() called even though nothing queued */\n    will_return(query_user_exec_builtin, true);\n    assert_true(get_user_pass_cr(&up, \"iuser\\nipassword\", \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"iuser\");\n    assert_string_equal(up.password, \"ipassword\");\n\n    reset_user_pass(&up);\n\n    /* Test various valid characters */\n    /*FIXME: query_user_exec() called even though nothing queued */\n    will_return(query_user_exec_builtin, true);\n    /* FIXME? content after first two lines just ignored */\n    assert_true(\n        get_user_pass_cr(&up, \"#iuser and 커뮤니티\\n//ipasswörd!\\nsome other content\\nnot relevant\",\n                         \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"#iuser and 커뮤니티\");\n    assert_string_equal(up.password, \"//ipasswörd!\");\n\n    reset_user_pass(&up);\n\n    /* Test various invalid characters */\n    /*FIXME: query_user_exec() called even though nothing queued */\n    will_return(query_user_exec_builtin, true);\n    /*FIXME? allows arbitrary crap if c > 127 */\n    /*FIXME? silently removes control characters */\n    assert_true(get_user_pass_cr(&up, \"\\tiuser\\r\\nipass\\xffwo\\x1erd\", \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"iuser\");\n    assert_string_equal(up.password, \"ipass\\xffword\");\n\n    reset_user_pass(&up);\n\n    /*FIXME: query_user_exec() called even though nothing queued */\n    will_return(query_user_exec_builtin, true);\n    /*FIXME: silently removes control characters but does not error out */\n    assert_true(get_user_pass_cr(&up, \"\\t\\n\\t\", \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"\");\n    assert_string_equal(up.password, \"\");\n\n    reset_user_pass(&up);\n\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"Enter UT Password:\");\n    will_return(query_user_exec_builtin, \"cpassword\");\n    will_return(query_user_exec_builtin, true);\n    /* will try to retrieve missing password from stdin */\n    assert_true(get_user_pass_cr(&up, \"iuser\", \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"iuser\");\n    assert_string_equal(up.password, \"cpassword\");\n\n    reset_user_pass(&up);\n\n    flags |= GET_USER_PASS_PASSWORD_ONLY;\n    /*FIXME: query_user_exec() called even though nothing queued */\n    will_return(query_user_exec_builtin, true);\n    assert_true(get_user_pass_cr(&up, \"ipassword\\n\", \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"user\");\n    assert_string_equal(up.password, \"ipassword\");\n\n    reset_user_pass(&up);\n\n    flags |= GET_USER_PASS_PASSWORD_ONLY;\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"Enter UT Password:\");\n    will_return(query_user_exec_builtin, \"cpassword\");\n    will_return(query_user_exec_builtin, true);\n    /* will try to retrieve missing password from stdin */\n    assert_true(get_user_pass_cr(&up, \"\", \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"user\");\n    assert_string_equal(up.password, \"cpassword\");\n}\n\n/* NOTE: expect_assert_failure does not seem to work with MSVC */\n#ifndef _MSC_VER\n/* NOTE: leaks gc memory */\nstatic void\ntest_get_user_pass_inline_creds_assertions(void **state)\n{\n    struct user_pass up = { 0 };\n    reset_user_pass(&up);\n    unsigned int flags = GET_USER_PASS_INLINE_CREDS;\n\n    reset_user_pass(&up);\n\n    /*FIXME: query_user_exec() called even though nothing queued */\n    /*FIXME? username error thrown very late in stdin handling */\n    will_return(query_user_exec_builtin, true);\n    expect_assert_failure(get_user_pass_cr(&up, \"\\nipassword\\n\", \"UT\", flags, NULL));\n}\n#endif\n\nstatic void\ntest_get_user_pass_authfile_stdin(void **state)\n{\n    struct user_pass up = { 0 };\n    reset_user_pass(&up);\n    unsigned int flags = 0;\n\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"Enter UT Username:\");\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"Enter UT Password:\");\n    will_return(query_user_exec_builtin, \"cuser\");\n    will_return(query_user_exec_builtin, \"cpassword\");\n    will_return(query_user_exec_builtin, true);\n    assert_true(get_user_pass_cr(&up, \"stdin\", \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"cuser\");\n    assert_string_equal(up.password, \"cpassword\");\n\n    reset_user_pass(&up);\n\n    flags |= GET_USER_PASS_PASSWORD_ONLY;\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"Enter UT Password:\");\n    will_return(query_user_exec_builtin, \"cpassword\");\n    will_return(query_user_exec_builtin, true);\n    assert_true(get_user_pass_cr(&up, \"stdin\", \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"user\");\n    assert_string_equal(up.password, \"cpassword\");\n\n    reset_user_pass(&up);\n\n    flags |= GET_USER_PASS_PASSWORD_ONLY;\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"Enter UT Password:\");\n    will_return(query_user_exec_builtin, \"\");\n    will_return(query_user_exec_builtin, true);\n    /*FIXME? does not error out on empty password */\n    assert_true(get_user_pass_cr(&up, \"stdin\", \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"user\");\n    assert_string_equal(up.password, \"\");\n}\n\n/* NOTE: expect_assert_failure does not seem to work with MSVC */\n#ifndef _MSC_VER\n/* NOTE: leaks gc memory */\nstatic void\ntest_get_user_pass_authfile_stdin_assertions(void **state)\n{\n    struct user_pass up = { 0 };\n    reset_user_pass(&up);\n    unsigned int flags = 0;\n\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"Enter UT Username:\");\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"Enter UT Password:\");\n    will_return(query_user_exec_builtin, \"\");\n    will_return(query_user_exec_builtin, \"cpassword\");\n    will_return(query_user_exec_builtin, true);\n    expect_assert_failure(get_user_pass_cr(&up, \"stdin\", \"UT\", flags, NULL));\n}\n#endif\n\nstatic void\ntest_get_user_pass_authfile_file(void **state)\n{\n    struct user_pass up = { 0 };\n    reset_user_pass(&up);\n    unsigned int flags = 0;\n\n    char authfile[PATH_MAX] = { 0 };\n    openvpn_test_get_srcdir_dir(authfile, PATH_MAX, \"input/user_pass.txt\");\n\n    /*FIXME: query_user_exec() called even though nothing queued */\n    will_return(query_user_exec_builtin, true);\n    assert_true(get_user_pass_cr(&up, authfile, \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"fuser\");\n    assert_string_equal(up.password, \"fpassword\");\n\n    reset_user_pass(&up);\n\n    openvpn_test_get_srcdir_dir(authfile, PATH_MAX, \"input/appears_empty.txt\");\n    /*FIXME: query_user_exec() called even though nothing queued */\n    will_return(query_user_exec_builtin, true);\n    /*FIXME? does not error out */\n    assert_true(get_user_pass_cr(&up, authfile, \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"\");\n    assert_string_equal(up.password, \"\");\n\n    reset_user_pass(&up);\n\n    openvpn_test_get_srcdir_dir(authfile, PATH_MAX, \"input/user_only.txt\");\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"Enter UT Password:\");\n    will_return(query_user_exec_builtin, \"cpassword\");\n    will_return(query_user_exec_builtin, true);\n    /* will try to retrieve missing password from stdin */\n    assert_true(get_user_pass_cr(&up, authfile, \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"fuser\");\n    assert_string_equal(up.password, \"cpassword\");\n\n    reset_user_pass(&up);\n\n    flags |= GET_USER_PASS_PASSWORD_ONLY;\n    openvpn_test_get_srcdir_dir(authfile, PATH_MAX, \"input/user_only.txt\");\n    /*FIXME: query_user_exec() called even though nothing queued */\n    will_return(query_user_exec_builtin, true);\n    assert_true(get_user_pass_cr(&up, authfile, \"UT\", flags, NULL));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"user\");\n    assert_string_equal(up.password, \"fuser\");\n}\n\n#ifdef ENABLE_MANAGEMENT\nstatic void\ntest_get_user_pass_dynamic_challenge(void **state)\n{\n    struct user_pass up = { 0 };\n    reset_user_pass(&up);\n    const char *challenge = \"CRV1:R,E:Om01u7Fh4LrGBS7uh0SWmzwabUiGiW6l:Y3Ix:Please enter token PIN\";\n    unsigned int flags = GET_USER_PASS_DYNAMIC_CHALLENGE;\n\n    expect_string(query_user_exec_builtin, query_user[i].prompt,\n                  \"CHALLENGE: Please enter token PIN\");\n    will_return(query_user_exec_builtin, \"challenge_response\");\n    will_return(query_user_exec_builtin, true);\n    assert_true(get_user_pass_cr(&up, NULL, \"UT\", flags, challenge));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"cr1\");\n    assert_string_equal(up.password, \"CRV1::Om01u7Fh4LrGBS7uh0SWmzwabUiGiW6l::challenge_response\");\n}\n\nstatic void\ntest_get_user_pass_static_challenge(void **state)\n{\n    struct user_pass up = { 0 };\n    reset_user_pass(&up);\n    const char *challenge = \"Please enter token PIN\";\n    unsigned int flags = GET_USER_PASS_STATIC_CHALLENGE;\n\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"Enter UT Username:\");\n    will_return(query_user_exec_builtin, \"cuser\");\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"Enter UT Password:\");\n    will_return(query_user_exec_builtin, \"cpassword\");\n    will_return(query_user_exec_builtin, true);\n    expect_string(query_user_exec_builtin, query_user[i].prompt,\n                  \"CHALLENGE: Please enter token PIN\");\n    will_return(query_user_exec_builtin, \"challenge_response\");\n    will_return(query_user_exec_builtin, true);\n    assert_true(get_user_pass_cr(&up, NULL, \"UT\", flags, challenge));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"cuser\");\n    /* SCRV1:cpassword:challenge_response but base64-encoded */\n    assert_string_equal(up.password, \"SCRV1:Y3Bhc3N3b3Jk:Y2hhbGxlbmdlX3Jlc3BvbnNl\");\n\n    reset_user_pass(&up);\n\n    flags |= GET_USER_PASS_STATIC_CHALLENGE_CONCAT;\n\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"Enter UT Username:\");\n    will_return(query_user_exec_builtin, \"c1user\");\n    expect_string(query_user_exec_builtin, query_user[i].prompt, \"Enter UT Password:\");\n    will_return(query_user_exec_builtin, \"c1password\");\n    will_return(query_user_exec_builtin, true);\n    expect_string(query_user_exec_builtin, query_user[i].prompt,\n                  \"CHALLENGE: Please enter token PIN\");\n    will_return(query_user_exec_builtin, \"0123456\");\n    will_return(query_user_exec_builtin, true);\n    assert_true(get_user_pass_cr(&up, NULL, \"UT\", flags, challenge));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"c1user\");\n    /* password and response concatenated */\n    assert_string_equal(up.password, \"c1password0123456\");\n\n    reset_user_pass(&up);\n\n    flags = GET_USER_PASS_STATIC_CHALLENGE | GET_USER_PASS_INLINE_CREDS;\n\n    /*FIXME: query_user_exec() called even though nothing queued */\n    will_return(query_user_exec_builtin, true);\n    expect_string(query_user_exec_builtin, query_user[i].prompt,\n                  \"CHALLENGE: Please enter token PIN\");\n    will_return(query_user_exec_builtin, \"challenge_response\");\n    will_return(query_user_exec_builtin, true);\n    assert_true(get_user_pass_cr(&up, \"iuser\\nipassword\", \"UT\", flags, challenge));\n    assert_true(up.defined);\n    assert_string_equal(up.username, \"iuser\");\n    /* SCRV1:ipassword:challenge_response but base64-encoded */\n    assert_string_equal(up.password, \"SCRV1:aXBhc3N3b3Jk:Y2hhbGxlbmdlX3Jlc3BvbnNl\");\n}\n#endif /* ENABLE_MANAGEMENT */\n\n/* NOTE: expect_assert_failure does not seem to work with MSVC */\n#ifndef _MSC_VER\n/* NOTE: leaks gc memory */\nstatic void\ntest_get_user_pass_authfile_file_assertions(void **state)\n{\n    struct user_pass up = { 0 };\n    reset_user_pass(&up);\n    unsigned int flags = 0;\n\n    char authfile[PATH_MAX] = { 0 };\n\n    openvpn_test_get_srcdir_dir(authfile, PATH_MAX, \"input/empty.txt\");\n    expect_assert_failure(get_user_pass_cr(&up, authfile, \"UT\", flags, NULL));\n\n    reset_user_pass(&up);\n\n    flags |= GET_USER_PASS_PASSWORD_ONLY;\n    openvpn_test_get_srcdir_dir(authfile, PATH_MAX, \"input/empty.txt\");\n    expect_assert_failure(get_user_pass_cr(&up, authfile, \"UT\", flags, NULL));\n}\n#endif /* ifndef _MSC_VER */\n\nconst struct CMUnitTest user_pass_tests[] = {\n    cmocka_unit_test(test_get_user_pass_defined),\n    cmocka_unit_test(test_get_user_pass_needok),\n    cmocka_unit_test(test_get_user_pass_inline_creds),\n    cmocka_unit_test(test_get_user_pass_authfile_stdin),\n    cmocka_unit_test(test_get_user_pass_authfile_file),\n#ifdef ENABLE_MANAGEMENT\n    cmocka_unit_test(test_get_user_pass_dynamic_challenge),\n    cmocka_unit_test(test_get_user_pass_static_challenge),\n#endif /* ENABLE_MANAGEMENT */\n#ifndef _MSC_VER\n    cmocka_unit_test(test_get_user_pass_inline_creds_assertions),\n    cmocka_unit_test(test_get_user_pass_authfile_stdin_assertions),\n    cmocka_unit_test(test_get_user_pass_authfile_file_assertions),\n#endif\n};\n\nint\nmain(void)\n{\n    openvpn_unit_test_setup();\n    return cmocka_run_group_tests(user_pass_tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/unit_tests/openvpnserv/Makefile.am",
    "content": "AUTOMAKE_OPTIONS = foreign\n\nAM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING) service Unit-Tests'\n\nif WIN32\ntest_binaries = openvpnserv_testdriver\nendif\n\nopenvpnserv_testdriver_CFLAGS  = -I$(top_srcdir)/include -I$(top_srcdir)/src/openvpn -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpnserv @TEST_CFLAGS@\nopenvpnserv_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(top_srcdir)/src/openvpn\nopenvpnserv_testdriver_SOURCES = test_openvpnserv.c \\\n\t$(top_srcdir)/src/openvpnserv/common.c \\\n\t$(top_srcdir)/src/openvpnserv/validate.c \\\n\t$(top_srcdir)/src//openvpn/wfp_block.c \\\n\t$(top_srcdir)/src//openvpn/wfp_block.h\n"
  },
  {
    "path": "tests/unit_tests/openvpnserv/test_openvpnserv.c",
    "content": "/*\n *  OpenVPN -- An application to securely tunnel IP networks\n *             over a single UDP port, with support for SSL/TLS-based\n *             session authentication and key exchange,\n *             packet encryption, packet authentication, and\n *             packet compression.\n *\n *  Copyright (C) 2025 Frank Lichtenheld <frank@lichtenheld.com>\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by the\n *  Free Software Foundation, either version 2 of the License,\n *  or (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License along\n *  with this program; if not, see <https://www.gnu.org/licenses/>.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <setjmp.h>\n#include <cmocka.h>\n#include \"test_common.h\"\n\n#include <winsock2.h>\n#include <windows.h>\n\n#include \"interactive.c\"\n\nBOOL\nReportStatusToSCMgr(SERVICE_STATUS_HANDLE service, SERVICE_STATUS *status)\n{\n    return TRUE;\n}\n\nstatic void\ntest_list_contains_domain(void **state)\n{\n    PCWSTR domain = L\"openvpn.net\";\n    size_t domain_len = wcslen(domain);\n    assert_true(ListContainsDomain(domain, domain, domain_len));\n    assert_true(ListContainsDomain(L\"openvpn.com,openvpn.net\", domain, domain_len));\n    assert_true(ListContainsDomain(L\"openvpn.net,openvpn.com\", domain, domain_len));\n\n    assert_false(ListContainsDomain(L\"openvpn.com\", domain, domain_len));\n    assert_false(ListContainsDomain(L\"internal.openvpn.net\", domain, domain_len));\n}\n\n#define BUF_SIZE 64\nstatic void\ntest_convert_itf_dns_domains(void **state)\n{\n    DWORD size, orig_size, len, res_len;\n    LSTATUS err;\n    const DWORD glyph_size = sizeof(wchar_t);\n\n    wchar_t domains_1[BUF_SIZE] = L\"openvpn.com\";\n    len = (DWORD)wcslen(domains_1) + 1;\n    size = orig_size = len * glyph_size;\n    wchar_t domains_1_res[BUF_SIZE] = L\".openvpn.com\";\n    res_len = len + 2; /* adds . and \\0 */\n    err = ConvertItfDnsDomains(L\"openvpn.net\", domains_1, &size, BUF_SIZE);\n    assert_memory_equal(domains_1, domains_1_res, size);\n    assert_int_equal(size, res_len * glyph_size);\n    assert_int_equal(err, NO_ERROR);\n\n    wchar_t domains_2[BUF_SIZE] = L\"openvpn.com,openvpn.net\";\n    len = (DWORD)wcslen(domains_2) + 1;\n    size = orig_size = len * glyph_size;\n    wchar_t domains_2_res[BUF_SIZE] = L\".openvpn.com\";\n    res_len = (DWORD)wcslen(domains_2_res) + 2;\n    err = ConvertItfDnsDomains(L\"openvpn.net\", domains_2, &size, BUF_SIZE);\n    assert_memory_equal(domains_2, domains_2_res, size);\n    assert_int_equal(size, res_len * glyph_size);\n    assert_int_equal(err, NO_ERROR);\n\n    wchar_t domains_3[BUF_SIZE] = L\"openvpn.com,openvpn.net\";\n    len = (DWORD)wcslen(domains_3) + 1;\n    size = orig_size = len * glyph_size;\n    wchar_t domains_3_res[BUF_SIZE] = L\".openvpn.net\";\n    res_len = (DWORD)wcslen(domains_3_res) + 2;\n    err = ConvertItfDnsDomains(L\"openvpn.com\", domains_3, &size, BUF_SIZE);\n    assert_memory_equal(domains_3, domains_3_res, size);\n    assert_int_equal(size, res_len * glyph_size);\n    assert_int_equal(err, NO_ERROR);\n\n    wchar_t domains_4[BUF_SIZE] = L\"openvpn.com,openvpn.net\";\n    len = (DWORD)wcslen(domains_4) + 1;\n    size = orig_size = len * glyph_size;\n    wchar_t domains_4_res[BUF_SIZE] = L\".openvpn.com\\0.openvpn.net\";\n    res_len = len + 3; /* adds two . and one \\0 */\n    err = ConvertItfDnsDomains(NULL, domains_4, &size, BUF_SIZE);\n    assert_memory_equal(domains_4, domains_4_res, size);\n    assert_int_equal(size, res_len * glyph_size);\n    assert_int_equal(err, NO_ERROR);\n}\n\nint\nwmain(void)\n{\n    openvpn_unit_test_setup();\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(test_list_contains_domain),\n        cmocka_unit_test(test_convert_itf_dns_domains),\n    };\n\n    int ret = cmocka_run_group_tests_name(\"openvpnserv tests\", tests, NULL, NULL);\n\n    return ret;\n}\n"
  },
  {
    "path": "tests/unit_tests/plugins/Makefile.am",
    "content": "AUTOMAKE_OPTIONS = foreign\n\nSUBDIRS = auth-pam\n"
  },
  {
    "path": "tests/unit_tests/plugins/auth-pam/Makefile.am",
    "content": "AUTOMAKE_OPTIONS = foreign\n\nAM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING) auth_pam Plugin Unit-Tests'\n\nif ENABLE_PLUGIN_AUTH_PAM\ncheck_PROGRAMS = auth_pam_testdriver\nTESTS = $(check_PROGRAMS)\nendif\n\nauth_pam_testdriver_SOURCES = test_search_and_replace.c  $(top_srcdir)/src/plugins/auth-pam/utils.h $(top_srcdir)/src/plugins/auth-pam/utils.c\nauth_pam_testdriver_CFLAGS  = @TEST_CFLAGS@ -I$(top_srcdir)/src/plugins/auth-pam\nauth_pam_testdriver_LDFLAGS = @TEST_LDFLAGS@\n"
  },
  {
    "path": "tests/unit_tests/plugins/auth-pam/test_search_and_replace.c",
    "content": "#include <stdio.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <stdint.h>\n#include <string.h>\n#include <setjmp.h>\n#include <cmocka.h>\n\n#include \"utils.h\"\n\nstatic void\npass_any_null_param__returns_null(void **state)\n{\n    char DUMMY[] = \"DUMMY\";\n\n    assert_null(searchandreplace(NULL, DUMMY, DUMMY));\n    assert_null(searchandreplace(DUMMY, NULL, DUMMY));\n    assert_null(searchandreplace(DUMMY, DUMMY, NULL));\n}\n\nstatic void\npass_any_empty_string__returns_null(void **state)\n{\n    char DUMMY[] = \"DUMMY\";\n    char EMPTY[] = \"\";\n\n    assert_null(searchandreplace(EMPTY, DUMMY, DUMMY));\n    assert_null(searchandreplace(DUMMY, EMPTY, DUMMY));\n    assert_null(searchandreplace(DUMMY, DUMMY, EMPTY));\n}\n\nstatic void\nreplace_single_char__one_time__match_is_replaced(void **state)\n{\n    char *replaced = searchandreplace(\"X\", \"X\", \"Y\");\n\n    assert_non_null(replaced);\n    assert_string_equal(\"Y\", replaced);\n\n    free(replaced);\n}\n\nstatic void\nreplace_single_char__multiple_times__match_all_matches_are_replaced(void **state)\n{\n    char *replaced = searchandreplace(\"XaX\", \"X\", \"Y\");\n\n    assert_non_null(replaced);\n    assert_string_equal(\"YaY\", replaced);\n\n    free(replaced);\n}\n\nstatic void\nreplace_longer_text__multiple_times__match_all_matches_are_replaced(void **state)\n{\n    char *replaced = searchandreplace(\"XXaXX\", \"XX\", \"YY\");\n\n    assert_non_null(replaced);\n    assert_string_equal(\"YYaYY\", replaced);\n\n    free(replaced);\n}\n\nstatic void\npattern_not_found__returns_original(void **state)\n{\n    char *replaced = searchandreplace(\"abc\", \"X\", \"Y\");\n\n    assert_non_null(replaced);\n    assert_string_equal(\"abc\", replaced);\n\n    free(replaced);\n}\n\n\nint\nmain(void)\n{\n    const struct CMUnitTest tests[] = {\n        cmocka_unit_test(pass_any_null_param__returns_null),\n        cmocka_unit_test(pass_any_empty_string__returns_null),\n        cmocka_unit_test(replace_single_char__one_time__match_is_replaced),\n        cmocka_unit_test(replace_single_char__multiple_times__match_all_matches_are_replaced),\n        cmocka_unit_test(replace_longer_text__multiple_times__match_all_matches_are_replaced),\n        cmocka_unit_test(pattern_not_found__returns_original),\n    };\n\n    return cmocka_run_group_tests_name(\"searchandreplace\", tests, NULL, NULL);\n}\n"
  },
  {
    "path": "tests/update_t_client_ips.sh",
    "content": "#!/bin/sh\n#\n# This --up script caches the IPs handed out by the test VPN server to a file\n# for later use.\n\nRC=\"$TOP_BUILDDIR/t_client_ips.rc\"\n\ngrep EXPECT_IFCONFIG4_$TESTNUM= $RC > /dev/null 2>&1\nif [ $? -ne 0 ]; then\n    echo \"EXPECT_IFCONFIG4_$TESTNUM=$ifconfig_local\" >> $RC\nfi\n\ngrep EXPECT_IFCONFIG6_$TESTNUM= $RC > /dev/null 2>&1\nif [ $? -ne 0 ]; then\n    echo \"EXPECT_IFCONFIG6_$TESTNUM=$ifconfig_ipv6_local\" >> $RC\nfi\n"
  },
  {
    "path": "version.m4",
    "content": "dnl define the OpenVPN version\ndefine([PRODUCT_NAME], [OpenVPN])\ndefine([PRODUCT_TARNAME], [openvpn])\ndefine([PRODUCT_VERSION_MAJOR], [2])\ndefine([PRODUCT_VERSION_MINOR], [8])\ndefine([PRODUCT_VERSION_PATCH], [_git])\nm4_append([PRODUCT_VERSION], [PRODUCT_VERSION_MAJOR])\nm4_append([PRODUCT_VERSION], [PRODUCT_VERSION_MINOR], [[.]])\nm4_append([PRODUCT_VERSION], [PRODUCT_VERSION_PATCH], [[]])\ndefine([PRODUCT_BUGREPORT], [openvpn-users@lists.sourceforge.net])\ndefine([PRODUCT_VERSION_RESOURCE], [2,8,0,0])\ndnl define the TAP version\ndefine([PRODUCT_TAP_WIN_COMPONENT_ID], [tap0901])\ndefine([PRODUCT_TAP_WIN_MIN_MAJOR], [9])\ndefine([PRODUCT_TAP_WIN_MIN_MINOR], [9])\n"
  }
]