pax_global_header00006660000000000000000000000064151011764240014513gustar00rootroot0000000000000052 comment=fccd5de840f258f33c015dee903061f332089183 portsentry-2.0.6/000077500000000000000000000000001510117642400137515ustar00rootroot00000000000000portsentry-2.0.6/.clang-format000066400000000000000000000124441510117642400163310ustar00rootroot00000000000000--- Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignArrayOfStructures: None AlignConsecutiveMacros: None AlignConsecutiveAssignments: None AlignConsecutiveBitFields: None AlignConsecutiveDeclarations: None AlignEscapedNewlines: Right AlignOperands: Align AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortEnumsOnASingleLine: true AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortLambdasOnASingleLine: All AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: MultiLine AttributeMacros: - __capability BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: false AfterClass: false AfterControlStatement: Never AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false BeforeLambdaBody: false BeforeWhile: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeConceptDeclarations: true BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 0 CommentPragmas: '^ IWYU pragma:' QualifierAlignment: Leave CompactNamespaces: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DeriveLineEnding: true DerivePointerAlignment: false DisableFormat: false EmptyLineAfterAccessModifier: Never EmptyLineBeforeAccessModifier: LogicalBlock ExperimentalAutoDetectBinPacking: false PackConstructorInitializers: BinPack BasedOnStyle: '' ConstructorInitializerAllOnOneLineOrOnePerLine: false AllowAllConstructorInitializersOnNextLine: true FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IfMacros: - KJ_IF_MAYBE IncludeBlocks: Preserve IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 SortPriority: 0 CaseSensitive: false - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 SortPriority: 0 CaseSensitive: false - Regex: '.*' Priority: 1 SortPriority: 0 CaseSensitive: false IncludeIsMainRegex: '(Test)?$' IncludeIsMainSourceRegex: '' IndentAccessModifiers: false IndentCaseLabels: false IndentCaseBlocks: false IndentGotoLabels: true IndentPPDirectives: None IndentExternBlock: AfterExternBlock IndentRequires: false IndentWidth: 2 IndentWrappedFunctionNames: false InsertTrailingCommas: None JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true LambdaBodyIndentation: Signature MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 2 ObjCBreakBeforeNestedBlockParam: true ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakOpenParenthesis: 0 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PenaltyIndentedWhitespace: 0 PointerAlignment: Right PPIndentWidth: -1 ReferenceAlignment: Pointer ReflowComments: true RemoveBracesLLVM: false SeparateDefinitionBlocks: Leave ShortNamespaceLines: 1 SortIncludes: false SortJavaStaticImport: Before SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCaseColon: false SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeParensOptions: AfterControlStatements: true AfterForeachMacros: true AfterFunctionDefinitionName: false AfterFunctionDeclarationName: false AfterIfMacros: true AfterOverloadedOperator: false BeforeNonEmptyParentheses: false SpaceAroundPointerQualifiers: Default SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: Never SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInLineCommentPrefix: Minimum: 1 Maximum: -1 SpacesInParentheses: false SpacesInSquareBrackets: false SpaceBeforeSquareBrackets: false BitFieldColonSpacing: Both Standard: Latest StatementAttributeLikeMacros: - Q_EMIT StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION TabWidth: 8 UseCRLF: false UseTab: Never WhitespaceSensitiveMacros: - STRINGIZE - PP_STRINGIZE - BOOST_PP_STRINGIZE - NS_SWIFT_NAME - CF_SWIFT_NAME ... portsentry-2.0.6/.github/000077500000000000000000000000001510117642400153115ustar00rootroot00000000000000portsentry-2.0.6/.github/workflows/000077500000000000000000000000001510117642400173465ustar00rootroot00000000000000portsentry-2.0.6/.github/workflows/cmake-single-platform.yml000066400000000000000000000013031510117642400242470ustar00rootroot00000000000000name: CMake on a single platform on: pull_request: branches: [ "master" ] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install pcap run: | sudo apt-get update sudo apt-get install libpcap-dev - name: clean run: ./build.sh clean - name: debug build run: ./build.sh debug - name: release build run: ./build.sh release - name: clean run: ./build.sh clean - name: debug build with pcap disabled run: CMAKE_OPTS="-D USE_PCAP=OFF" ./build.sh debug - name: release build with pcap disabled run: CMAKE_OPTS="-D USE_PCAP=OFF" ./build.sh release portsentry-2.0.6/.github/workflows/codacy.yml000066400000000000000000000044271510117642400213420ustar00rootroot00000000000000# This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. # This workflow checks out code, performs a Codacy security scan # and integrates the results with the # GitHub Advanced Security code scanning feature. For more information on # the Codacy security scan action usage and parameters, see # https://github.com/codacy/codacy-analysis-cli-action. # For more information on Codacy Analysis CLI in general, see # https://github.com/codacy/codacy-analysis-cli. name: Codacy Security Scan on: pull_request: branches: [ "master" ] permissions: contents: read jobs: codacy-security-scan: permissions: contents: read # for actions/checkout to fetch code security-events: write # for github/codeql-action/upload-sarif to upload SARIF results actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status name: Codacy Security Scan runs-on: ubuntu-latest steps: # Checkout the repository to the GitHub Actions runner - name: Checkout code uses: actions/checkout@v4 # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis - name: Run Codacy Analysis CLI uses: codacy/codacy-analysis-cli-action@d840f886c4bd4edc059706d09c6a1586111c540b with: # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository # You can also omit the token and run the tools that support default configurations project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} verbose: true output: results.sarif format: sarif # Adjust severity of non-security issues gh-code-scanning-compat: true # Force 0 exit code to allow SARIF file generation # This will handover control about PR rejection to the GitHub side max-allowed-issues: 2147483647 # Upload the SARIF file generated in the previous step - name: Upload SARIF results file uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif portsentry-2.0.6/.github/workflows/codeql.yml000066400000000000000000000103131510117642400213360ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: pull_request: branches: [ "master" ] jobs: analyze: name: Analyze (${{ matrix.language }}) # Runner size impacts CodeQL analysis time. To learn more, please see: # - https://gh.io/recommended-hardware-resources-for-running-codeql # - https://gh.io/supported-runners-and-hardware-resources # - https://gh.io/using-larger-runners (GitHub.com only) # Consider using larger runners or machines with greater resources for possible analysis time improvements. runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} permissions: # required for all workflows security-events: write # required to fetch internal or private CodeQL packs packages: read # only required for workflows in private repositories actions: read contents: read strategy: fail-fast: false matrix: include: - language: c-cpp build-mode: autobuild # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' # Use `c-cpp` to analyze code written in C, C++ or both # Use 'java-kotlin' to analyze code written in Java, Kotlin or both # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality # If the analyze step fails for one of the languages you are analyzing with # "We were unable to automatically build your code", modify the matrix above # to set the build mode to "manual" for that language. Then modify this step # to build your code. # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - if: matrix.build-mode == 'manual' shell: bash run: | echo 'If you are using a "manual" build mode for one or more of the' \ 'languages you are analyzing, replace this with the commands to build' \ 'your code, for example:' echo ' make bootstrap' echo ' make release' exit 1 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" portsentry-2.0.6/.github/workflows/docker-push-to-repo.yml000066400000000000000000000011561510117642400237030ustar00rootroot00000000000000name: Docker Image Create and Push To Docker HUB on: push: branches: [ "master" ] jobs: build-and-push-docker: runs-on: self-hosted steps: - name: Checkout uses: actions/checkout@v4 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build Docker image run: ./build.sh docker - name: Push Docker image run: | docker tag portsentry:unstable portsentry/portsentry:unstable docker push portsentry/portsentry:unstable portsentry-2.0.6/.github/workflows/release-docker.yml000066400000000000000000000026271510117642400227650ustar00rootroot00000000000000name: Docker Release Image Create and Push To Docker HUB on: push: tags: - 'v[0-9]+.[0-9]+.[0-9]+' jobs: build-and-push-docker: runs-on: self-hosted steps: - name: Checkout uses: actions/checkout@v4 - name: Setup BRANCH_VERSION run: | BRANCH_VERSION=$(echo "${{ github.ref_name }}" | sed 's|\(v[0-9]\.[0-9]\).*|\1|') if ! echo "$BRANCH_VERSION" | grep -q "v[0-9]\.[0-9]"; then echo "Error: Invalid version format. Expected format is vX.Y" exit 1 fi echo "BRANCH_VERSION=${BRANCH_VERSION}" >> $GITHUB_ENV - name: Build Docker image run: ./build.sh docker - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Push Docker image run: | docker tag portsentry:unstable portsentry/portsentry:latest docker tag portsentry:unstable portsentry/portsentry:stable docker tag portsentry:unstable portsentry/portsentry:${{ github.ref_name }} docker tag portsentry:unstable portsentry/portsentry:${{ env.BRANCH_VERSION }} docker push portsentry/portsentry:latest docker push portsentry/portsentry:stable docker push portsentry/portsentry:${{ github.ref_name }} docker push portsentry/portsentry:${{ env.BRANCH_VERSION }} portsentry-2.0.6/.github/workflows/system_test.yml000066400000000000000000000006541510117642400224610ustar00rootroot00000000000000name: System Tests on: pull_request: branches: [ "master" ] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install pcap run: | sudo apt-get update sudo apt-get install libpcap-dev nmap - name: Build run: ./build.sh debug - name: System Test run: | cd system_test sudo ./run_all_tests.sh portsentry-2.0.6/.github/workflows/unit_tests.yml000066400000000000000000000011701510117642400222710ustar00rootroot00000000000000name: Unit Tests on: pull_request: branches: [ "master" ] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install pcap run: | sudo apt-get update sudo apt-get install libpcap-dev nmap - name: clean run: ./build.sh clean - name: debug build with tests enabled run: CMAKE_OPTS="-D BUILD_TESTS=ON" ./build.sh debug - name: release build with tests enabled run: CMAKE_OPTS="-D BUILD_TESTS=ON" ./build.sh release - name: run unit tests run: ctest --test-dir debug && ctest --test-dir releaseportsentry-2.0.6/.github/workflows/x-compile.yml000066400000000000000000000004041510117642400217640ustar00rootroot00000000000000name: Cross-compile for supported platforms on: pull_request: branches: [ "master" ] jobs: build: runs-on: self-hosted steps: - name: Checkout uses: actions/checkout@v4 - name: Cross-compile run: ./build.sh docker portsentry-2.0.6/.gitignore000066400000000000000000000001611510117642400157370ustar00rootroot00000000000000portsentry debug release build portsentry.blocked.* Testing/** remotes.txt portsentry*.tar.xz _CPack_Packages/** portsentry-2.0.6/CMakeLists.txt000066400000000000000000000300111510117642400165040ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10) project(portsentry VERSION 2.0.6) include(CheckCCompilerFlag) option(BUILD_FUZZER "Build fuzzer tests" OFF) option(BUILD_TESTS "Build unit tests" OFF) option(USE_PCAP "Build with pcap code and link with libpcap" ON) option(INSTALL_LICENSE "Install license file" ON) option(USE_SYSTEMD "Install systemd unit file" ON) if (NOT DEFINED CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release") endif() # We're using an absolute path for the systemd unit file, so we need to set the CPack destination directory to ON # This is a workaround for the fact that the systemd unit file is installed in a non-standard location. Only affects the use of cpack. set(CPACK_SET_DESTDIR "ON") set(CONFIG_FILE "\"/etc/portsentry/portsentry.conf\"" CACHE STRING "Path to portsentry config file") set(WRAPPER_HOSTS_DENY "\"/etc/hosts.deny\"" CACHE STRING "Path to hosts.deny file") set(STANDARD_COMPILE_OPTS -Wall -Wextra -pedantic -Werror -Wformat -Wformat-security -Wstack-protector -Wshadow -Wredundant-decls -Wdisabled-optimization -Wnested-externs -Wstrict-overflow=2 -Wconversion -Wmissing-prototypes -Wcast-qual -fPIE -fstack-protector-strong -fstrict-aliasing -fno-common -fno-strict-overflow) string(REGEX MATCH "-D_FORTIFY_SOURCE" FORTIFY_MATCH "${CMAKE_C_FLAGS}") if(NOT FORTIFY_MATCH) set(STANDARD_COMPILE_OPTS ${STANDARD_COMPILE_OPTS} -D_FORTIFY_SOURCE=2) endif() check_c_compiler_flag("-fcf-protection=full" COMPILER_SUPPORTS_CFI_PROTECTION) if (COMPILER_SUPPORTS_CFI_PROTECTION) set(STANDARD_COMPILE_OPTS ${STANDARD_COMPILE_OPTS} -fcf-protection=full) endif() set(STANDARD_LINK_OPTS -pie -Wl,-z,noexecstack -Wl,-z,now -Wl,-z,relro -Wl,-z,defs -Wl,--no-undefined) set(CORE_SOURCE_FILES src/config_data.c src/configfile.c src/io.c src/util.c src/state_machine.c src/cmdline.c src/sentry_connect.c src/sighandler.c src/port.c src/packet_info.c src/ignore.c src/sentry.c src/block.c) execute_process(COMMAND git log -1 --format=%h WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_COMMIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE) add_definitions("-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"") if (USE_PCAP) set(CORE_SOURCE_FILES ${CORE_SOURCE_FILES} src/pcap_listener.c src/pcap_device.c src/sentry_pcap.c) set(STANDARD_COMPILE_OPTS ${STANDARD_COMPILE_OPTS} -DUSE_PCAP) endif() if (CMAKE_SYSTEM_NAME STREQUAL "Linux") set(CORE_SOURCE_FILES ${CORE_SOURCE_FILES} src/sentry_stealth.c) endif() if (CMAKE_SYSTEM_NAME STREQUAL "Linux") set(CORE_SOURCE_FILES ${CORE_SOURCE_FILES} src/kernelmsg_linux.c) elseif (CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") set(CORE_SOURCE_FILES ${CORE_SOURCE_FILES} src/kernelmsg_bsd.c) else() message(FATAL_ERROR "Unsupported operating system ${CMAKE_SYSTEM_NAME}") endif() configure_file(config.h.in config.h) # CPack configuration set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) set(CPACK_DEBIAN_PACKAGE_NAME "portsentry") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.36), libpcap0.8 (>= 1.10.3)") set(CPACK_DEBIAN_PACKAGE_SECTION "net") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Marcus Hufvudsson ) add_test(NAME test_util_getlong COMMAND $) add_test(NAME test_util_createdatetime COMMAND $) add_test(NAME test_util_strtouint16 COMMAND $) add_test(NAME test_util_reallocandappend COMMAND $) add_test(NAME test_state_machine COMMAND $) add_test(NAME test_port COMMAND $) add_test(NAME test_io_subststring COMMAND $) endif() portsentry-2.0.6/CPPLINT.cfg000066400000000000000000000001011510117642400155330ustar00rootroot00000000000000linelength=500 filter=-readability/casting,-build/include_subdir portsentry-2.0.6/Changes.md000066400000000000000000000300561510117642400156470ustar00rootroot00000000000000# Portsentry Changelog ## 2025-10-31 2.0.6 ### Improvements - Various compiler flags to increase security - Added systemd private devices - Various compile time configuration- and installation options to improve packaging ### Bugfixes - Fixed bug where the build number would not be added during release - Fix manual which stated incorrect license ## 2025-09-03 2.0.5 ### Improvements - Added support and docker containers for i386 and 64bit risc-v - Compiled man pages with pandoc v3 in order to fix issues with markdown -> man - Added option to not install/package LICENSE file (useful for some package managers) ## 2025-08-26 2.0.4 ### Bugfixes - Fixed bug in the CI which didn't properly updated the stable docker tag ### Improvements - When testing for existing, legitimate service on a scan detection, take IP:PORT binding into account - Added compiler flags to increase correctness/security of the code ## 2025-07-21 2.0.3 ### Improvements - Added the ability to turn off the block cache. This enables Portsentry to always block and/or run commands on a triggered scan. ## 2025-06-24 2.0.2 ### Improvements - Added multi-architecture build of docker containers - Improved documentation - Improved CMake install process with definable directories - Added CPack support for packaging - Moved man pages to build process - Improved systemd unit file with better handling of install paths and start conditions ## 2025-05-24 2.0.1 ### Improvements - Added detailed usage instructions in docs/HOWTO-Use.md - Minor documentation updates - Added automatic package building and compile targets arm - Fixed architecture code issues for arm64 and armhf - Added unit testing ## 2025-05-09 2.0.0 ### Bugfixes - Fixed bug where block and ignore file would be written to even though user has configured portsentry to not do any blocking - Fixed multiple potential race conditions which could manifest when running two portsentry instances - Fixed potential bug in state engine which could overflow and miss reporting on packets - Various smaller bugfixes ### Features - Added libpcap support, which enables stealth mode on *BSD systems and increases performance - Added IPv6 support - Added Docker support and added portsentry registry on docker hub - Added fail2ban integration ### Improvements - Significant disk usage reduction after parser redesign. Code no longer continually re-reads config file - Removed/consolidated several duplicate code/DRY violations, significantly reducing code size and potential errors - Fixed resource leak of socket file descriptors in connect mode in certain situations - Merged tcp/udp modes so both protocols can be monitored at the same time. No more dual processes - General code cleanup and removal of legacy code - Added more command line options in order to increase runtime flexibility - Change to runtime debug/verbose log output, additionally supporting stdout instead of only syslog and added distinction between log and error messages - Incorporated "advanced mode" features into both connect and stealth modes - Changed to CMake instead of hand written makefiles - Added Linting and Formating - Added integration tests - Added fuzzing tests - Added SAST testing - Added systemd unit - Updated and increased the amount of documentation ## 2003-05-23 - 1.2 - Removed references to old psionic e-mail and changed license to Common Public License. ## 2001-06-26 - 1.1 - Added Mac OS X build support (Same as FreeBSD). - Fixed bug for Advanced mode to properly monitor 1024 ports (it only did first 1023 before). Thanks Guido. ## 2001-03-23 - 1.1 - Fixed a bug that showed up under Linux 2.4 Kernel that would cause accept to loop. There was an error with how I used a count variable after trying to bind to ports. If the port didn't bind the count for the openSockfd would still increment and this caused the error to show up. ## 2000-09-09 - 1.1 - Finally moved resolver functions to own area. - Made CleanAndResolve to ensure DNS records returned are sanitized correctly before being passed back. ## 2000-09-08 - 1.1 - Added in netmask support ## 2000-07-05 - 1.1 - Added iptables support (thanks Scott Catterton ) - Added Makefile support for Irix - Put in ports for common DDOS ports ## 2000-06-21 - 1.1 - Added in feature to disable DNS host resolution by checking RESOLVE_HOST in conf file. - Added in feature to have external command run before or after blocking has occurred as defined in KILL_RUN_CMD_FIRST option in conf file. - Removed DoBlockTCP/UDP functions. Converted over to generic flag checker. ## 2000-06-08 - 1.1 - Fixed an error in the state engine portion that could cause an increment error under certain conditions. Thanks Peter M. Allan for finding this. ## 2000-03-31 - 1.1 - Updated .conf to add ipf blocking rule. Thanks Graham Dunn ## 1999-12-21 - 1.1 - Fixed typo in bare-bones TCP list where 524 was supposed to be for 1524. ## 1999-11-14 - 1.0 - Y2K fix in WriteBlocked functions. Now uses four digit year in .blocked and .history files. PortSentry doesn't use dates as part of its operations, however third party scripts may use the .blocked and .history files and the two digit format would roll over to 1/1/100 on Jan 1 instead of 1/1/00 causing a potential problem. Sorry about that. :( ``` Old format: 942286729 - 11/10/99 20:18:49 Host: 192.168.2.12/192.168.2.12 Port: 111 TCP Blocked New format: 942286729 - 11/10/1999 20:18:49 Host: 192.168.2.12/192.168.2.12 Port: 111 TCP Blocked ``` ## 1999-10-24 - 1.0 - Updated docs to tell users how to add LOCAL0 facility for syslog reporting. ## 1999-10-15 - 1.0 - NeverBlock() function fixed so now it actually does ignore hosts correctly. I was stripping off the EOL prematurely during a strlen check and this caused the problem. Thanks to all reporters for finding this. ## 1999-08-02 - 0.99 - WriteBlocked() now writes out packet type being blocked "TCP" or "UDP" in the log files. ## 1999-07-28 - 0.99 - Finally put back in the parts that correctly read IP options and added extra code to check for illegal sizes, etc. This has been a long time coming...sorry for the delay. ## 1999-07-27 - 0.98.1 - Fixed a nasty bug where an attacker could cause Sentry to ignore a scan if you have a specific configuration setup on your host. Bug reported by Reuven Gevaryahu . ## 1999-07-12 - 0.98 - Fixed IP parsing problems in .ignore and .blocked functions. Now correctly ignores blocked hosts and ignored hosts. Bug reported by ## 1999-07-09 - 0.91 - Fixed corrupted readme.qa file ## 1999-06-03 - 0.90c - Added ignore.csh script contributed by Christopher Lindsey. ## 1999-06-02 - 0.90b - Added OSF build option. - Added OSF KILL_ROUTE command in .conf file - Fixed ipchains entry in .conf file. - Updated conf file to warn about bogus route. - Switched to native linux tcphdr/udphdr structs instead of BSD style. ## 1999-05-26 - 0.90a - Applied patch from to get new version to compile under older Linux distributions. - Added AIX build option. ## 1999-05-12 - 0.90 - Found very subtle bug in SafeStrncpy that would overwrite last part of dest data with extra null outside of bounds and would intermittently corrupt data. This was a real pain and took almost six hours of testing on various platforms to track down as no debugger turned it up. I hope you people appreciate all the aggravation I go through to write this thing. ;) ## 1999-05-11 - 0.90 - Changed install dir to /usr/local/psionic/portsentry - Updated docs again. - Removed NeXTSTEP make rule because OS lacks vsnprintf() and I don't have a working version to put in. - Re-Ordered #include files to prevent warnings/errors under BSD (OpenBSD) ## 1999-05-10 - 0.90 - QA checklist completed. - Incremented version to 0.90 - Updated docs/spellcheck ## 1999-05-04 - 0.89a - Added new trojan horse ports for TCP_PORTS: - 20034 - NetBus Pro - 5742 - Win Crash Trojan - 30303 - Socket De Troye - 40421 - Unknown Trojan Horse (Master's Paradise [CHR]) - The above were taken from a post on comp.security.unix by: jeromexxx@club-internet.fr on 05-03-99 ## 1999-05-03 - 0.89 - Added $PORT$ option parsing to all kill options - Added KILL_TCP/KILL_UDP option of "2" to allow for running a command, but not running other kill route/hosts.deny options - Fixed another bug in the subst function that would return an empty string if nothing found instead of a string copy of the original. This was causing a string that didn't have the optional $PORT$ command to return empty. - Moved check for BLOCKED_FILE into init function from the checkconfig function. - Printed out corrupted config file warnings to screen and syslog instead of just syslog before aborting. - Made QA checklist part of package in case people are interested. - Added some more logging to kill* functions. Also added print out to log files of exact string run for route, hostsdeny, runcmd options. - Changed BANNER to something a little shorter. - Fixed NeXTSTEP route command entry. ## 1999-05-01 - 0.89 - All docs updated and code base given distribution version 0.89-BETA for release. ## 1999-04-30 - 0.80 - Fixed buggy string substitute function. It is now faster and more reliable. - Removed config file reading from all sub-functions and put it into InitConfig() in main executable. This cuts down on file IO during heavy activation of the probe scanner. - Lots of small code cleanups. ## 1999-04-14 - 0.80 - Added port 1524TCP (ingreslock) after several reports of ttdbserver overflows using this as the backdoor insertion point. ## 1999-04-13 - 0.80 - Cleaned up more code. Moved packet classification to separate function. - Began regression testing. ## 1999-04-12 - 0.80 - Cleaned up lots of code. Reduced major redundant sections. - Eliminated redundant config file reads and moved code to inititialize variables at startup instead of during program loops. - Added ports 635TCP, 12345TCP, 12346TCP, 31337UDP to port list. - Lines of code reduced by 20%. - Removed hacked-in tcpip.h/ip.h header files and used proper Linux defines. - Renamed package from "Abacus Sentry" to "PortSentry" ## 1998-05-28 - 0.61 - Added tcp/ip headers with distribution because some versions of Linux still use the "old" style and not the BSD variant. ## 1998-05-26 - 0.60 - Added "Smart-Verify" port profiling in all stealth modes to avoid blocking legitimate connections. ## 1998-05-25 - 0.60 - Added TCP SYN stealth scan detection - Added TCP FIN stealth scan detection - Added "Advanced" stealth scan detection mode. ## 1998-05-11 - 0.60 - Put in reverse DNS reporting on connections. Should have done this long ago.. - Changed history file and blocked file to write out resolved hostname on connects ## 1998-03-11 - 0.50 - FINALLY got back to work on this thing. - Added/changed configuration option to not react to TCP/UDP probes (report only). - Added history file to store permanent list of all blocked hosts. - Changed blocked file to be truncated to 0 bytes on startup so users don't have to manually clean it out on re-start. - Removed reporting of already blocked hosts for UDP to prevent possible denial of service attack. - Put in limit of 64 open sockets per instance (some systems did not have FD_SETSIZE defined correctly and this caused errors). - Removed logging of connections from ignored hosts (why ignore them if I still report their connection??) - Began renaming from "Abacus Sentry" to just plain "sentry" to eliminate confusion. - Updated docs. - Minor code cleanups. ## 1997-12-10 - 0.10 - Added alert for possible stealth scan detection. ## 1997-12-06 - 0.09 - Added option to skip blocking on UDP scans to prevent DOS attacks. ## 1997-12-04 - 0.08 - Code cleanup and public alpha release. ## 1997-11-30 - 0.06 - Re-vamped config file options. - Consolidated variables and added $TARGET$ macro expansion. - Added option to run external command. ## 1997-11-04 - 0.05 - Added PORT_BANNER option ## 1997-11-02 - 0.04 - Bug fixes in state engine. - Speed enhancements. ## 1997-10-14 - 0.03 - Added state engine. ## 1997-10-13 - 0.02 - Multiple port binding. - Config file added. - TCP wrapper support added. ## 1997-10-12 - 0.01 - Project Begins. portsentry-2.0.6/LICENSE000066400000000000000000000024241510117642400147600ustar00rootroot00000000000000Copyright 2025 Marcus Hufvudsson and contributors Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. portsentry-2.0.6/README.md000066400000000000000000000071121510117642400152310ustar00rootroot00000000000000
## What is Portsentry? **Port Scan Detection** Portsentry monitors network traffic in order to detect port scans in real-time. It can identify several types of scans, including TCP, SYN, FIN, XMAS, and NULL scans and UDP probing. **Response Mechanisms** Upon detecting a port scan, Portsentry can respond in several ways to mitigate the threat: * Blocking the Attacker: It can automatically add the attacker's IP address to the system's firewall or access control list, effectively blocking any further connections from that IP. * Logging: Portsentry logs the details of the scan attempt, including the source IP address, timestamp, and type of scan detected. This information can be useful for forensic analysis and monitoring. * Notification: It can send alerts to system administrators via email or other messaging systems to notify them of the detected scan. **Stealth Mode** Portsentry operates in stealth mode where it listens on unused ports. Since these ports should not receive any legitimate traffic, any connection attempts are considered suspicious and are flagged as potential scans. **Integration with Security Tools** Portsentry can be integrated with other security tools and systems to provide a comprehensive security solution. For example, it can be used with fail2ban in order to take advantage of its sophisticated blocking mechanism. ## Quickstart Detailed installation instructions can be found in the [HOWTO-Use](docs/HOWTO-Use.md) guide. ### Docker ``` docker run -d --network=host --name portsentry portsentry/portsentry:unstable ``` More docker configuration options available in the [HOWTO-Docker.md](docs/HOWTO-Docker.md) ### Linux Download the latest release from the [Release page](https://github.com/portsentry/portsentry/releases) #### Debian/Ubuntu based systems Download the .deb file and install it by typing: ```bash sudo apt install libpcap0.8 sudo dpkg -i portsentry-*.deb ``` #### tarball Download and extract the tarball and run the installer script by typing: ```bash sudo tar --strip-components=1 -C / -xvf portsentry-*.tar.xz ``` ### *BSD OpenBSD, NetBSD and FreeBSD is supported but must currently be compiled manually, see below ### Compiling the Source Code * Make sure you have: **CMake**, **gcc or clang** and **libpcap** installed. * git clone https://github.com/portsentry/portsentry.git * ./build.sh release Detailed compilation instructions can be found on the [HOWTO-Compile](docs/HOWTO-Compile.md) page. ## Documentation All documentation for portsentry is indexed in the [docs/README.md](docs/README.md). ## Support Please use the [Discussions Forums](https://github.com/portsentry/portsentry/discussions) for any support questions or feedback ## Links Website: https://portsentry.xyz Github: https://github.com/portsentry/portsentry Docker Hub: https://hub.docker.com/r/portsentry/portsentry portsentry-2.0.6/build.sh000077500000000000000000000072121510117642400154110ustar00rootroot00000000000000#!/bin/sh ACTION=$1 _SYSTEM=$(uname -s) if [ "$_SYSTEM" != "Linux" ]; then CMAKE_OPTS="$CMAKE_OPTS -DUSE_SYSTEMD=OFF" fi if [ "$ACTION" = "clean" ]; then rm -rf debug release && \ rm -f portsentry.blocked.* && \ rm -f portsentry.history && \ rm -f portsentry*.tar.xz rm -f docs/portsentry.8 rm -f docs/portsentry.conf.8 elif [ "$ACTION" = "debug" ]; then cmake -B debug -D CMAKE_BUILD_TYPE=Debug -D CMAKE_INSTALL_PREFIX=/ $CMAKE_OPTS cmake --build debug -v elif [ "$ACTION" = "release" ]; then cmake -B release -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/ $CMAKE_OPTS cmake --build release -v elif [ "$ACTION" = "sast" ]; then rm -rf /tmp/portsentry rsync -avz ../portsentry /tmp/ codeql database create /tmp/portsentry/codeqldb --language=cpp --source-root=/tmp/portsentry codeql database analyze /tmp/portsentry/codeqldb --format=csv --output=/tmp/portsentry/codeqlout.csv echo "========== CodeQL Results Start ==========" cat /tmp/portsentry/codeqlout.csv echo "========== CodeQL Results End ==========" semgrep scan --config=auto exit 0 elif [ "$ACTION" = "build_fuzz" ]; then export CC=/usr/bin/clang $0 clean && \ cmake -B debug -D CMAKE_BUILD_TYPE=Debug -D BUILD_FUZZER=ON $CMAKE_OPTS && \ cmake --build debug -v elif [ "$ACTION" = "run_fuzz" ]; then total_time=60 [ -n "$2" ] && total_time=$2 find debug -maxdepth 1 -name "fuzz_*" | while read f do echo "Running $f" ./$f -max_total_time=$total_time tests/fuzzing/corpus_$(basename $f) done elif [ "$ACTION" = "autobuild" ]; then while [ 1 ]; do inotifywait -e modify src/[a-zA-Z]*.c ./build.sh debug && \ ./build.sh release && \ ./build.sh sast sleep 5 done elif [ "$ACTION" = "docker" ]; then docker buildx build -t portsentry:unstable -f docker/Dockerfile --platform=linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6,linux/i386,linux/riscv64 . elif [ "$ACTION" = "docker_export" ]; then BUILD_DIR=/tmp/portsentry-build rm -rf $BUILD_DIR docker buildx build -t export -f docker/Dockerfile --target export --platform=linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6,linux/i386,linux/riscv64 --output type=local,dest=$BUILD_DIR . find /tmp/portsentry-build -mindepth 2 -type f -regex ".*portsentry-[0-9\.]*-Linux.*" | while read f; do new_name=$(echo $f |sed "s/-Linux\./-$(basename $(dirname $f))\./") mv -v "$f" "$new_name" done elif [ "$ACTION" = "build_test" ]; then ./build.sh clean && \ CMAKE_OPTS="-D BUILD_TESTS=ON" ./build.sh debug elif [ "$ACTION" = "run_test" ]; then if [ -d "debug" ]; then ctest --test-dir debug else echo "No debug build directory found" exit 1 fi elif [ "$ACTION" = "test_all" ]; then ./build.sh clean && \ CMAKE_OPTS="-D USE_PCAP=OFF" ./build.sh debug && \ CMAKE_OPTS="-D USE_PCAP=OFF" ./build.sh release && \ ./build.sh clean && \ CMAKE_OPTS="-D BUILD_TESTS=ON" ./build.sh debug && \ CMAKE_OPTS="-D BUILD_TESTS=ON" ./build.sh release && \ ctest --test-dir debug && \ ctest --test-dir release (command -v docker >/dev/null 2>&1 && ./build.sh docker) cd system_test ./run_all_tests.sh else echo "Usage: $0 " echo "Commands:" echo " clean - Remove all build files/caches" echo " debug - Build debug version" echo " release - Build release version" echo echo " sast - Run static analysis tools" echo " build_test - Build unit test targets" echo " run_test - Run unit test targets" echo echo " build_fuzz - Build fuzzing targets" echo " run_fuzz - Run fuzzing targets" echo " docker - Build docker image" echo echo " doc - Build man pages" exit 0 fi portsentry-2.0.6/build_and_test.sh000077500000000000000000000017471510117642400173010ustar00rootroot00000000000000#!/usr/bin/env bash [ -z "$BUILD_TYPE" ] && BUILD_TYPE="debug" if [ -n "$1" ]; then REMOTE_HOSTS="$1" else REMOTE_HOSTS="deb-portsentry netbsd freebsd openbsd" fi # Build on all remote hosts in parallel for host in $REMOTE_HOSTS; do echo "Uploading to $host" ssh root@$host "rm -rf /tmp/portsentry" rsync -az -e ssh ../portsentry root@$host:/tmp/ done # Run tests tmux new -s rat -d sleep 1 tmux split-window -h sleep 1 tmux split-window -v -t 0 sleep 1 tmux split-window -v -t 2 sleep 1 tmux select-pane -t 0 sleep 1 tmux send-keys 'ssh root@deb-portsentry "cd /tmp/portsentry && ./build.sh test_all"' C-m sleep 1 tmux select-pane -t 1 sleep 1 tmux send-keys 'ssh root@netbsd "cd /tmp/portsentry && ./build.sh test_all"' C-m sleep 1 tmux select-pane -t 2 sleep 1 tmux send-keys 'ssh root@freebsd "cd /tmp/portsentry && ./build.sh test_all"' C-m sleep 1 tmux select-pane -t 3 sleep 1 tmux send-keys 'ssh root@openbsd "cd /tmp/portsentry && ./build.sh test_all"' C-m sleep 1 tmux a portsentry-2.0.6/build_remotes.sh000077500000000000000000000007421510117642400171500ustar00rootroot00000000000000#!/usr/bin/env bash [ -z "$BUILD_TYPE" ] && BUILD_TYPE="debug" if [ -n "$1" ]; then REMOTE_HOSTS="$1" else REMOTE_HOSTS="deb-portsentry netbsd freebsd openbsd" fi for host in $REMOTE_HOSTS; do echo "Building on $host" ssh $host "rm -rf /tmp/portsentry" rsync -az -e ssh ../portsentry $host:/tmp/ ssh $host "cd /tmp/portsentry && ./build.sh clean && ./build.sh $BUILD_TYPE" if [ $? -ne 0 ]; then echo "ERROR: Failed to build on $host" exit 1 fi echo done portsentry-2.0.6/config.h.in000066400000000000000000000004401510117642400157720ustar00rootroot00000000000000#pragma once #define PORTSENTRY_VERSION_MAJOR @portsentry_VERSION_MAJOR@ #define PORTSENTRY_VERSION_MINOR @portsentry_VERSION_MINOR@ #define PORTSENTRY_VERSION_PATCH @portsentry_VERSION_PATCH@ #cmakedefine CONFIG_FILE @CONFIG_FILE@ #cmakedefine WRAPPER_HOSTS_DENY @WRAPPER_HOSTS_DENY@ portsentry-2.0.6/docker/000077500000000000000000000000001510117642400152205ustar00rootroot00000000000000portsentry-2.0.6/docker/Dockerfile000066400000000000000000000016361510117642400172200ustar00rootroot00000000000000FROM debian:stable AS builder RUN apt update && apt install -y build-essential libpcap-dev cmake git WORKDIR /build COPY . . RUN ./build.sh clean RUN CMAKE_OPTS="-D SYSTEMD_SYSTEM_UNIT_DIR=/lib/systemd/system" ./build.sh release RUN cpack --config release/CPackConfig.cmake -G DEB RUN cpack --config release/CPackConfig.cmake -G TXZ FROM scratch AS export COPY --from=builder /build/release/portsentry /portsentry COPY --from=builder /build/portsentry-*.deb / COPY --from=builder /build/portsentry-*.tar.xz / FROM debian:stable RUN apt update && apt install -y libpcap0.8t64 RUN mkdir -p /etc/portsentry RUN mkdir -p /var/run/portsentry COPY --from=builder /build/release/portsentry /usr/local/sbin/portsentry COPY --from=builder /build/examples/portsentry.conf /etc/portsentry/portsentry.conf COPY --from=builder /build/examples/portsentry.ignore /etc/portsentry/portsentry.ignore ENTRYPOINT ["/usr/local/sbin/portsentry"] portsentry-2.0.6/docker/docker-compose.yaml000066400000000000000000000007421510117642400210210ustar00rootroot00000000000000services: portsentry: container_name: portsentry image: portsentry/portsentry:unstable restart: unless-stopped network_mode: host volumes: - type: bind source: ./portsentry.conf target: /etc/portsentry/portsentry.conf read_only: true - type: bind source: ./portsentry.ignore target: /etc/portsentry/portsentry.ignore read_only: true - type: bind source: ./logs target: /var/log portsentry-2.0.6/docs/000077500000000000000000000000001510117642400147015ustar00rootroot00000000000000portsentry-2.0.6/docs/Acknowledgement.md000066400000000000000000000157031510117642400203400ustar00rootroot00000000000000# Credits Craig Rowland - Inventor, Main developer and Maintainer of Portsentry from its inception in 1997 to its end in 2003 Thanks to the following people who offered testing and bug fixes/suggestions. I apologize if I've forgotten anyone. (No particular order on this list either): Jeff Johnson - Initial tester. Recommended ipfwadm addition. Ted Serreyn - Initial tester and bug reports. Les Fenison - Reported problems with logging of ignored hosts. Dave Andersen - FreeBSD testing. ipfw syntax for configuration file. Anonymous - HPUX testing and compile flags. Superuser - Lots of suggestions and general discussions... Timothy Luoma - NeXT Step fixes/bug testing/various suggestions and a really nice NeXTStep software archive (ftp.peak.org)!! Marcus Butler - Sharing ideas, suggestions and testing. Forrest Aldrich - FreeBSD testing and comments. Drew Levandoski - HPUX testing and compile flags. Yaron Yanay - Additional UDP ports to exclude in advanced mode. Andrew Raphael - Binary and source RPMs. Samuli Karkkainen - Helped in debugging Advanced mode problem with SSH connection failures to Windows boxes. System Administrator - Slackware testing and confirmation. - Linux Alpha testing. Alex Petrov - Lots of comments concerning code clean-up and general efficiency recommendations. Dmitry Prokhorov - Patches to add $PORT$ token and fixes to install scripts. Rich Homolka - Reported problem with binding over port 61000 for advanced mode was due to ip masquerading feature in Linux kernels. Vesselin Atanasov - Patch to handle scan flooding attacks (not included in this version for other reasons, but I thank him for sending them in). Lukasz Zalubski - Mirrored tools to Europe. Matthias Lohmann - Solaris porting and testing. Don Bindner - Found typo in UDP attack logging that logged it as TCP attack. Jon Coyle - VERY helpful in testing for SCO. Also made up HTML pages and man pages. I should be paying him!! Brian D. Winters - Pointed out some compile errors on various Linux platforms and submitted fixes. Also made suggestions for areas of code cleanup. Jeremy T. Bouse - Made Debian packages of Sentry as well as Debain init scripts and install scripts (not included yet). Jeremy Hinegardner - Testing on SGI platforms. Densin Roy. - Suggested adding $PORT$ macro to command execution. - NetBSD testing. Dr. Stefan Demetrescu - Discussions concerning the smart-verify timeout (or lack thereof) and recommendations on future additions and fixes. Sean Amon - Solaris testing and fixes. Eric Hines - Solaris testing and fixes. David LaPorte - Alpha Linux testing. Sent in route drop command for new RedHat 6.0 systems (-reject flag). Joshua Chamas - Solaris testing. Tom Briglia - Solaris testing. Roger Books - Solaris testing and fixes. Steven Walker - RedHat init scripts and RPMs. Dominic J. Eidson - AIX testing. Christopher P. Lindsey - Patch to get version 0.90 and below to work in stealth modes on older Linux systems/systems with oddball tcpip.h files. Also helped in testing and debugging. Found bug in .ignore processing that wouldn't skip commented out lines. Contributed script to automatically setup ignore file on system startup by parsing ifconfig. Found bug in ignore functions that would sometimes mis-compare IP's (also lead me to find similar bug in isBlocked function). Adrian - Submitted bug report for 2.2.9 kernels with respect to possible UDP struct change (dest vs. u_dport). Thomas Molina - Made binary and source RPMs for 0.90 of PortSentry. Steve Marple - Reported bug in commenting out of .ignore file entries. Graham Allan - Sent in compile options for Digital OSF Unix. Sent in route flags for same. Ian Quick - Sent in a fix for the ipchains rule to insert blocks into filter list correctly. Reuven Gevaryahu - Found a nasty bug where if you setup to ignore either a tcp or a udp event an attacker could activate the detector, cause the blocked file to be written to and then activate the side you *wanted* the blocking to occur on and have Sentry ignore them. A big thanks for finding this! Jayakrishnan Krishnan - Reported the problem on the latest build not ignoring hosts in the .ignore file. Garth Brown - Also reported a problem with .blocked file mis-reading blocked hosts and submitted a fix. Morio Taneda - Submitted a *really cool* patch to use netlink with PortSentry. I'm sorry I didn't have time to implement this, but it is on my short list of fixes to put in after 1.0 is released. A big thanks for this!! Howard Arons - Suggested using LOCAL0 for possible syslog configuration in config.h file. Guido Guenther - Sent in bug fixes, Debian packages, comments, and many other contributions. Christophe Rolland - Wrote in to correct bug in .conf file where I watched first 1025 ports instead of first 1024. Now advanced modes will watch ports 1023 and below. Ralf Hildebrandt - Sent in route kill command for HPUX and fix for GCC under HPUX for Makefile. Graham Dunn - Sent in KILL_ROUTE string for ipf filters. Peter M. Allan - Found an increment error in the host state engine that could cause an error under certain configurations. Also contributed several fixes relating to function usage and cosmetics. Scott Catterton - Sent in new iptables support KILL_ROUTE command. Dino A Amato - Sent in Makefile strings for Irix Scott McCrory - Sent in common DDOS daemon ports Per Jönsson - Sent in changes to allow for ignoring networks/ports. Flower - Sent in request to disable DNS lookups. Paul T. Kooros - Sent in some code formatting issues that were resolved. Justin Pettit - Solaris fixes, proofreading, and lots of other stuff. portsentry-2.0.6/docs/Contributing.md000066400000000000000000000037561510117642400177050ustar00rootroot00000000000000# Contribute to Portsentry ## Coding Style + Use ``clang-format`` tool to format the code according to the ``.clang-format`` config file + Use the ``cpplint`` tool using the ``CPPLING.cfg`` config to make sure you follow the project style. ## Development Environment Setup + Install the following formater/linting tools - clang-format - cpplint + Review [HOWTO-Compile.md](HOWTO-Compile.md) for compilation requirements. + (optional) Install [CodeQL](https://codeql.github.com) in order to be able to run it locally. ## Pull Requests Before submitting a pull request, please make sure that you do the following: + Make sure that you have run ``clang-format`` using the ``.clang-format`` config file + Make sure the code compiles without Warnings or Errors + (optional) Run [CodeQL](https://codeql.github.com) locally if you have it installed + Run the fuzzers: ``./build.sh build_fuzz ; ./build.sh run_fuzz`` + Run the integration tests: ``cd system_test ; ./run_all_tests.sh``. Consider running the tests in a VM. It would be greatly appreciated if you could run the tests on Linux, NetBSD, FreeBSD and OpenBSD but it's not a requirement. - Note; The shell script build_and_test.sh can be used in order to build and run the tests on several VM's at the same time ## Documentation Make sure to update the documentation when needed. All documentation is in the docs/ folder. Pay special attention to: + Keep examples/portsentry.conf in sync with docs/portsentry.conf.md ## Issue Reporting When reporting issues: + Check if the issue has already been reported in the issue tracker + Provide detailed information: - Operating system and version - Portsentry version - Steps to reproduce - Expected vs actual behavior - Relevant logs or error messages ## Code Review Process + Pull requests will be reviewed by maintainers + Be responsive to feedback and requested changes + Keep pull requests focused and manageable in size + Include tests for new features or bug fixes + Update documentation as needed portsentry-2.0.6/docs/HOWTO-Compile.md000066400000000000000000000041021510117642400175060ustar00rootroot00000000000000# HOWTO Compile Portsentry ## Requirements In order to compile, Portsentry requires: * **CMake (version 3.10 or higher)** * **GCC or Clang** * **libpcap** (including headers, often packaged as libpcap-dev) ## Quickstart The build.sh convenience script can be used in order to build Portsentry To build a debug version: ``` ./build.sh debug ``` To build a release version ``` ./build.sh release ``` ## Running CMake manually In order to compile, you should supply the **CMAKE_BUILD_TYPE** flag to CMake (see below). It should be set to either **Debug** or **Release**. Portsentry accepts these flags: | Flag | Default | Description | | ---- | ------- | ----------- | | CMAKE_BUILD_TYPE | Release | Should be set to either Debug or Release | | USE_PCAP | ON | If used with **USE_PCAP=OFF** set, all pcap code is excluded and Portsentry will not link to libpcap. This option can be used where libpcap is not desired and/or available. | | BUILD_FUZZER | OFF | If used with **BUILD_FUZZER=ON**, the clang fuzzer tests are built. | | BUILD_TESTS | OFF | If used with **BUILD_TESTS=ON**, unit tests are built and can be run with the ctest suit. | | INSTALL_LICENSE | ON | When **INSTALL_LICENSE=ON** the LICENSE file will be included in the generated install files. However, some package managers (like debian and red hat for example) handle license installations separately. In these cases (when building distro packages), you might want to set **INSTALL_LICENSE=OFF**. | ### Compilation Examples **Compiling for release** ``` cmake -B release -D CMAKE_BUILD_TYPE=Release cmake --build release -v ``` **Compiling with debug symbols** ``` cmake -B debug -D CMAKE_BUILD_TYPE=Debug cmake --build debug -v ``` **Compiling without LIBPCAP** ``` cmake -B release -D CMAKE_BUILD_TYPE=Release -DUSE_PCAP=OFF cmake --build release -v ``` **Compiling old version (v1.2)** Tag v1.2 is the release from 2003, before the project was orphaned and uses a different build method, execute _make_ in order to see compilation instructions. ## Supported Platforms - Linux - OpenBSD - FreeBSD - NetBSD >= 8.0 portsentry-2.0.6/docs/HOWTO-Docker.md000066400000000000000000000056501510117642400173360ustar00rootroot00000000000000# Using Portsentry with Docker ## Versions It's recommended to use either the **latest tag**, which corresponds to the latest stable version of Portsentry, or if you want to follow a specific branch, use a branch tag (such as v2.0). These are the tags you can use: | Tag Name | Description | | -------- | ----------- | | unstable | Follows the master branch, unreleased/unstable version | | latest | Follows the latest stable release brnach/version | | v2.0 | Follows the v2.0 branch (current stable branch) | | v1.2 | The old **legacy/unmaintained** version | ## Quickstart ``` docker run -d --network=host --name portsentry portsentry/portsentry:latest ``` ### Mounting important directories and files There are **three** important files/directories you might want to consider mounting: * The configuration file * The ignore file * The log directory The configuration file should be mounted to `/etc/portsentry/portsentry.conf` and the ignore file to `/etc/portsentry/portsentry.ignore`. The log directory should be mounted to `/var/log`. You can download the example config and ignore file with extensive documentation here: https://github.com/portsentry/portsentry/blob/master/examples/portsentry.conf https://github.com/portsentry/portsentry/blob/master/examples/portsentry.ignore Here is a complete example of how to run Portsentry with a custom configruation and ignore file and the log directory mounted: ``` docker run -d --mount type=bind,src=./portsentry.ignore,dst=/etc/portsentry/portsentry.ignore \ --mount type=bind,src=./portsentry.conf,dst=/etc/portsentry/portsentry.conf \ --mount type=bind,src=./logs,dst=/var/log \ --network=host --name portsentry portsentry/portsentry:latest ``` ## Using Docker Compose An example docker-compose file can be found here: https://github.com/portsentry/portsentry/blob/master/docker/docker-compose.yaml Here is an example of how to run Portsentry with a custom configuration and ignore file and the log directory mounted using Docker Compose: ``` services: portsentry: container_name: portsentry image: portsentry/portsentry:latest restart: unless-stopped network_mode: host volumes: - type: bind source: ./portsentry.conf target: /etc/portsentry/portsentry.conf read_only: true - type: bind source: ./portsentry.ignore target: /etc/portsentry/portsentry.ignore read_only: true - type: bind source: ./logs target: /var/log ``` ## Fail2ban Integration It is highly recommended to use Portsentry with fail2ban if you want to block ip addresses. Fail2ban is able to block ip addresses using a wide variety of methods and will enforce state between system reboots and service restarts. Get the fail2ban integration files here: https://github.com/portsentry/portsentry/tree/master/fail2ban ## Visit the Portsentry project at Website: https://portsentry.xyz/ Github: https://github.com/portsentry/portsentry portsentry-2.0.6/docs/HOWTO-Fail2Ban.md000066400000000000000000000014741510117642400175050ustar00rootroot00000000000000# Fail2ban Integration Portsentry integrates will with fail2ban and it's highly recommended to use fail2ban in cases where you want to "ban" IP addresses triggering the portsentry detection engine. The directory [fail2ban](https://github.com/portsentry/portsentry/tree/master/fail2ban) contains both an example jail file [portsentry.local](https://github.com/portsentry/portsentry/blob/master/fail2ban/portsentry.local) as well as a filter definition file [portsentry.conf](https://github.com/portsentry/portsentry/blob/master/fail2ban/portsentry.conf). Simply copy the filter file, *portsentry.conf* into /etc/fail2ban/filter.d and copy the portsentry jail file *portsentry.local* into /etc/fail2ban/jail.d and reload fail2ban. Note that you might need to tweak the *portsentry.local* jail file in order to suit your needs. portsentry-2.0.6/docs/HOWTO-Logfile.md000066400000000000000000000060741510117642400175110ustar00rootroot00000000000000Whenever a packet is matched to the filter specified in the config file (TCP_PORTS and/or UDP_PORTS) the packet is logged in the following way: * If portsentry is configured to block the source host, the packet is logged in the BLOCKED_FILE file. The primary use for this log file is internal to portsentry. If another packet is detected from the same source host, the host won't be blocked again. * If the HISTORY_FILE file is specified in the config file, the packet is logged in this file. This file will thus contain a complete record of all matched incoming packets. It can contain duplicate source hosts. This file will contain a complete historic record of triggered scans * Finally, portsentry will also output a matched packet in stdout/syslog among all other portsentry logging (such as state of the program). The format of the HISTORY_LOG and stdout/syslog output is: Scan from: [IP] (HOSTNAME) protocol: [PROTOCOL] port: [PORT] type: [SCAN TYPE] IP opts: [IP OPTIONS] ignored: [IGNORED] triggered: [TRIGGERED] noblock: [NOBLOCK] blocked: [BLOCKED] Where: | Log Keyword | Decription | | ----------- | ---------- | | IP | is the IP address (IPv4 or IPv6) of the source host | | HOSTNAME | is the hostname of the source host (if RESOLVED_HOST is set to "1" in the config file) | | PROTOCOL | is the protocol of the packet (TCP or UDP) | | PORT | is the destination port number of the packet | | SCAN TYPE | is the type of scan detected | | | "Connect" - A full TCP connect (--connect mode) | | | "TCP NULL scan" | | | "TCP XMAS scan" | | | "TCP FIN scan" | | | "TCP SYN/Normal scan" | | | "Unknown Type: TCP Packet Flags: ..." - A TCP packet with unknown flags | | IP OPTIONS | IP options of the packet. can also be "unknown" or "not set" if the options are not obtainable (such as --connect mode or if no options are present) | | IGNORED | true/false: whether the packet was ignored, according to the IGNORE_FILE. If no IGNORE_FILE is specified, this will always be false | | TRIGGERED | true/false/unset: Whether the packet triggered a block according to the SCAN_TRIGGER setting. | | NOBLOCK | true/false/unset: If BLOCK_TCP or BLOCK_UDP was set to 0 and the packet matched, then NOBLOCK will be true | | BLOCKED | true/false/unset: If BLOCK_TCP or BLOCK_UDP > 0 and the packet matched and the source host was blocked, then BLOCKED will be true. If the source host was already blocked by a previous packets, will be true. | | In certain situations, the boolean flags **TRIGGERED**, **NOBLOCK**, and **BLOCKED** will be unset. If a flag is unset, a previous rule/flag in the rule engine has caused an abort before the current rule/flag could be set. This is normal behavior and should not be considered an error. The rule engine has been designed to halt processing of packets as soon as possible in order to be as efficient as possible. This is the reason you can't rely on **TRIGGERED**, **NOBLOCK**, and **BLOCKED** to be set to either true or false in all cases. portsentry-2.0.6/docs/HOWTO-Use-Cases.md000066400000000000000000000043421510117642400177140ustar00rootroot00000000000000# How to use Portsentry At a high level overview, Portsentry does **three** main things: * It listens to TCP and/or UDP ports you specify. * It stealthily (or visibly) logs connection attempts to the ports you have specified. * It can optionally execute scripts or applications when connection attempts are made. How could one leverage this into strengthening the Cyber Security posture of an organization? Here are two use-cases where you might want to deploy Portsentry. ## As a complementary Network Intrusion Detection System (NIDS) within your organization Let's assume your small organisation consist of: * Office LAN * WIFI network * Internal server network You could put a dedicated instance (server, a virtual machine or a container) running Portsentry on all of these networks and listening to a wide range of ports. Since this node is not part of the organization, no legitimate traffic should be directed towards it. However, an attacker inside your network will most certainly want to probe your network as part of their reconnaissance and lateral movement. When they do, they will trigger Portsentry and you will be alerted to a potential intruder in your network. ![Portsentry Inside Internal Organization](images/PS-Int-Org.png) ## Enumeration Prevention Consider a scenario where you have one or several services offered on the public Internet. Portsentry could be deployed to listen for traffic on unused services. Since no legitimate traffic will try to access the unused services, you could block any and all attempts to access the unused services. Access attempts on unused traffic are more often than not bots, looking for vulnerable services. By blocking them, you interfere with their ability to enumerate your servers, and in some cases even your services (when Portsentry triggers before probes reach legitimate services). This setup also helps protect against targeted enumeration attacks where an adversary is specifically targeting your organization. Blocking an adversary actively enumerating your servers/services could significantly interfere with their attempts. Especially when paired with a "mostly-closed firewall" design, dropping illegitimate traffic. ![Portsentry Blocking Enumeration Attempts](images/PS-Enumeration.png) portsentry-2.0.6/docs/HOWTO-Use.md000066400000000000000000000074011510117642400166570ustar00rootroot00000000000000# Howto Install and Setup Guide ## Choosing a Deployment Method **At the time of writing, Portsentry has limited deployment options due to the fact that the project has just recently been relaunched. More deployment options are next on the roadmap and will be comming soon.** Your current options are: ### Docker This is the recommeded deployment option if you have Docker available. Refer to rhe [HOWTO-Docker.md](HOWTO-Docker.md) guide for more information. ### Debian/Ubuntu based systems Download the .deb file from the [releases](https://github.com/portsentry/portsentry/releases) page and install it by typing: ```bash sudo apt install libpcap0.8 sudo dpkg -i portsentry-*.deb ``` ### Tarball Download the .tar.xz file from the [releases](https://github.com/portsentry/portsentry/releases) page and install if by tping: ```bash sudo tar --strip-components=1 -C / -xvf portsentry-*.tar.xz ``` ### Compile from source If you are using a system that is not supported by the precompiled binaries, you can compile Portsentry from source. Refer to the [HOWTO-Compile.md](HOWTO-Compile.md) guide for more information. ## Setup Once Portsentry is installed, you need to configure it. The configuration file is located in **/etc/portsentry/portsentry.conf**. The default configuration file is well documented and should be easy to understand. More documentation and in-depth discusson on the configuration can be found in the [portsentry.conf.md](portsentry.conf.md) manual. You can also use **man portsentry.conf** to read the manual page for the configuration file. ## Running Portsentry ### Running in a Terminal You can run Portsentry directly in your terminal to get a feel for it. Simply run the following command: ```bash sudo portsentry ``` This will start Portsentry in the foreground and you will see the output in your terminal. You can stop Portsentry by pressing **Ctrl+C**. ### Systemd Portsentry comes with a systemd service file which is installed during installation. To enable the Portsentry service to start on boot, you can run the following command: ```bash sudo systemctl enable portsentry ``` You can start Portsentry by running the following command: ```bash sudo systemctl start portsentry ``` You can check the status of Portsentry by running: ```bash sudo systemctl status portsentry ``` You the journalctl command to view the Portsentry system log: ```bash sudo journalctl -u portsentry ``` ## Portsentry Logfile Portsentry logs all activity to the **/var/log/portsentry.log** file. Refer to the [HOWTO-Logfile.md](HOWTO-Logfile.md) for more information on how the log file format is structured. You can view the log file by running the following command: ```bash sudo tail -f /var/log/portsentry.log ``` ## Fail2ban Integration Fail2ban is a log-parsing tool that can be used to block IP addresses that are detected by its parsing engine. It incorporates many costomizable features which makes it a flexible and powerful tool for protecting your server from malicious activity. Portsentry can be configured to work with Fail2ban in order to take advantage of Fail2ban's excellent capabilities. * Download fail2ban for your platform [https://github.com/fail2ban/fail2ban](https://github.com/fail2ban/fail2ban) * Download the [portsentry fail2ban filter file](https://github.com/portsentry/portsentry/blob/master/fail2ban/portsentry.conf) and place it in the **/etc/fail2ban/filter.d/** directory. * Download the [portsentry fail2ban jail file](https://github.com/portsentry/portsentry/blob/master/fail2ban/portsentry.local) and place it in the **/etc/fail2ban/jail.d/** directory. * Tweak the jail file to your liking, such as setting the ban time, find time, and max retry values. * Restart the fail2ban service to apply the changes: ```bash sudo systemctl restart fail2ban ``` portsentry-2.0.6/docs/Manual.md000066400000000000000000000142701510117642400164440ustar00rootroot00000000000000% portsentry(8) | System Manager's Manual # NAME **portsentry** \- Detect and respond to port scans against a target host in real\-time # SYNOPSIS **portsentry** \[options\] # DESCRIPTION Portsentry does three main things: * It listens to TCP and/or UDP ports you specify. * It stealthily (or visibly) logs connection attempts to the ports you have specified. * It can optionally execute scripts or applications when connection attempts are made. The most common use\-case for Portsentry is to block unwanted service enumeration attempts against your host. This could be accomplished by simply listening to a wide variety of **unused** ports and block all connection attempts to those ports. Portsentry can also be deployed as a Network Intrusion Detection System (NIDS). By listening to unused ports on your internal networks, you will be notified as soon as a potential attacker tries to scan for services within your organization. A more detailed explanation and guide of the various uses of portsentry, refer to the [HOWTO-Use](https://github.com/portsentry/portsentry/blob/master/docs/HOWTO-Use.md) guide. # OPTIONS ## \-\-stealth Stealth mode **(default)** uses libpcap (or raw sockets on Linux if desired, see the **\-m** option) in order to quietly listen for incoming packets on the network. The main advantage of Stealth mode is that the system gives off no indication that it is listening for incoming packets making it very difficult (if not impossible) for an attacker to detect that Portsentry is running. ## \-\-connect Connect mode **(legacy option)** uses the kernel socket API to listen for incoming packets. Connect mode is considered a legacy mode and is mainly preserved for users with very specific use\-cases. For example, connect mode can be used to add a "port banner", thus sending a message to anyone connecting to a specified port. Be aware however that connect mode comes with several additional security implications which must be considered. For example: * When monitoring TCP ports, Portsentry will require a three\-way TCP handshake to be completed before Portsentry registers the connection attempt. Thus, a "stealth scan attack" will go unnoticed by Portsentry. * Additionally, other TCP protocol attacks, such as SYN floods must be taken into consideration when using Connect Mode. * Connect mode will require Portsentry to bind to each port to be monitored individually. If you are monitoring a large number of ports you could potentially hit the max number of file descriptors allowed by the system and could also lead to performance issues. Most modern systems will allow you to increase the number of max opened file descriptors, but this is something to be aware of. ## Stealth Mode Options This section covers options only relevant when Stealth mode **\-\-stealth** is used. ### \-m, \-\-method=pcap|raw **This option is only relevant on Linux**. It sets the sentry method to use in stealth mode. Can be set to use either **pcap** or Linux **raw** sockets. **(default: pcap)** * **pcap**: Uses the libpcap library to listen for incoming packets. This is the default method and is recommended for most use-cases. * **raw**: Uses the Linux raw socket API to listen for incoming packets. This method is less efficient than pcap and is not recommended unless you have a specific use-case where pcap is not available or not desired. ### \-i, \-\-interface=ALL|ALL_NLO|\ **This option is only relevant when pcap mode is used**. Specify interface(s) to listen on. You can either specify an "interface alias or a specific interface: * `ALL` - Listen on all interfaces (including the loopback interface) (Alias) * `ALL_NLO` - Listen on all interfaces except the loopback interface (Alias) * `any` - This is a special "interface" option, built-in to libpcap. The libpcap library will attempt to listen to "all" interfaces except some special interfaces when using this option. * `` - Listen on the specified interface. NOTE: You can specify multiple interfaces by using multiple `--interface` switches, e.g. `--interface eth0 --interface eth1` ## Generic Options These options can be used regardless of mode used. ### \-L, \-\-disable\-local\-check Under normal operations; if Portsentry detects traffic with the same source and destination IP address, no logging or actions are performed. This is to prevent Portsentry from potentially taking actions on itself. This option disables this logic. I.e, logging and actions are taken on the host on which Portsentry is run. Use this option with care. ### \-l, \-\-logoutput=stdout|syslog Portsentry can log to either `stdout` or `syslog`. The log output can be set using the `--logoutput` (or `-l`) command line option. The default log output is `stdout`. ### \-c, \-\-configfile=path Portsentry can be configured using an alternative path for the configuration file. The configuration file can be set using the `--configfile` (or `-c`) command line option. The default configuration file is `/etc/portsentry/portsentry.conf`. See portsentry.conf(8) for more information. ### \-D, \-\-daemon Portsentry can be run as a daemon using the `--daemon` (or `-D`) command line option. This will cause Portsentry to fork into the background and run as a daemon. By default portsentry runs in the foreground. ### \-d, \-\-debug Enable debug output using the `--debug` (or `-d`) command line option. This will cause Portsentry to become very noisy. ### \-v, \-\-verbose Enable verbose output using the `--verbose` (or `-v`) command line option. This will cause Portsentry to log additional information to the log output. ### \-h, \-\-help Display command line help message ### \-V, \-\-version Display version information ## EXAMPLES Review the [HOWTO-Use](https://github.com/portsentry/portsentry/blob/master/docs/HOWTO-Use.md) guide for detailed setup scenarios and configuration guides. ## FILES /etc/portsentry/portsentry.conf /etc/portsentry/portsentry.ignore /var/log/portsentry.log /tmp/portsentry.blocked ## BUGS All bugs should be reported via the portsentry github issue tracker https://github.com/portsentry/portsentry/issues ## AUTHORS Marcus Hufvudsson ## SEE ALSO portsentry.conf(8) ## LICENSE Portsentry is licensed under the BSD-2-Clause license. portsentry-2.0.6/docs/README.md000066400000000000000000000020251510117642400161570ustar00rootroot00000000000000# Portsentry Documentation If you are new to Portsentry, consider reading the [HOWTO-Use-Cases.md](HOWTO-Use-Cases.md) guide in order to get an idea how Portsentry could be useful for you. Read the [HOWTO-Use.md](HOWTO-Use.md) guide for a detailed setup and configuration guide. | Doc | Description | | --- | ----------- | | [Manual.md](Manual.md) | Portsentry manual, command line options and usage | | [portsentry.conf.md](portsentry.conf.md) | Portsentry configuration file description | | [HOWTO-Use.md](HOWTO-Use.md) | Detailed setup and configuration guide | | [HOWTO-Use-Cases.md](HOWTO-Use-Cases.md) | Overview and suggestions on how to use Portsentry | | [HOWTO-Docker.md](HOWTO-Docker.md) | Guide for running Portsentry in Docker containers | | [HOWTO-Fail2Ban.md](HOWTO-Fail2Ban.md) | Guide for integrating Portsentry with Fail2ban | | [HOWTO-Logfile.md](HOWTO-Logfile.md) | Description of the logfile format and how/when the logs are produced | | [Contributing.md](Contributing.md) | Instructions for contributing to the project | portsentry-2.0.6/docs/images/000077500000000000000000000000001510117642400161465ustar00rootroot00000000000000portsentry-2.0.6/docs/images/PS-Enumeration.png000066400000000000000000001030151510117642400214620ustar00rootroot00000000000000PNG  IHDRqb!sRGB IDATx^}.j{/u6Ɣ@ :)? FjBc qzVZkw%hc{w{߽s> bF`]K0# "dNJ0#0٥y#0Lv#0i]ڋ0#d:0#0٥y#0Lv#0i]ڋ0#d:0#0٥y#0Lv#0i]ڋ0#d:0#0٥y#0Lv@ƦMo/\XhQ3!dz$7nxN?t&ɁeX gq\*W||1@|]|d/"u։?"w^qދ/XH"d_<5#@n{,&".~^f:#Lv11]ёK7M0?($+ˆF ,Lv kDKt޽;'8H(@~\0B~ LvR3I@*Ʈ!rs`4~D^ŨWp@H_t%~qt!d7]{%@iFvn""E%!4$#&;V F ,.xuG{vc9!d''iX#B`l/z_TT$&wf*G<#obR& wizvQO/%xE"B.I2#vhu62uEꫡ=TIHu+, =!u0Rya`cc|䦥b⻬20٥(#F \ 38߇t)LvBn?=`KO .6";)U@ɎCKUx dG`le:E:KҾ]$GeFߓn0٥Dy< Y92?!d'7plYRj%90%p~]rKŲ^DK%m~W*!dJ?`Lx.* (Lv)*V|LaSCgJ#d2N:0a }g0?H&;iȉ{#lKF*ď Lvi"HFxxeo_`eЙd*x ;بnT#/GɎu!vq!QQ{7՞&;VF jS1 ;|d-ۑ\.̯h/OAg15!}8vUxk,^\{P}Lv @ =he__cWtEɎuB6#<<^Xw^IҘ})ʎu@GxrWtr4WvGz6m qk;0rIw&Gݘcw1#K׋\ .}naʗ9Lvcw2#H&; 0#9Lvcw2#H&; 0#9Lvcw2#H&; 0#9Lvcw2#H&; 0#9Lvcw2#H&; 0#9Lvcw2#H&; 0#9Lvcw2#H&; ^y{ӟ!/z)woo/Je?G?^}U}فߵZ|O>eˎ~3r-s|KV{?$ ~ # a$,c\yIO>e1R yxgŋE"$ N9n>|Xt5(knnߓdF#&I\]}b;u]'>M>ZQʯ~+} %OMo֬Y2n çhs][dU"""0";ٳQWW{7|Hr__qWҾ\ 呻2-w}b ?z&H&;Y yIj7 /.૶VBtY,aժUbBs|Mtvvd8֍fppsUv-4ye^"d'WF((H ik_3g6uVqEw> ҪV=KB{?좖$? G(usLP_.vȢ"tuuMN:$P"W` ~ri &$;I1 bdLdF@0Yz!466⪫BMMu&; x设jEDt6mbK<FX)![oOi!AofF`<X7 'kb͚5կ~|P\h#$&ao ?W[[++iweҿhȏ/FHLv"#uIF7Z/Kqw7`]17DGCKv3&<)5%`K 1p'pD71᥃y RBNJ⾦,~\neG=ڿɥ9,w Lvw9xA'X#=>F 0SnQFN2Z8\FCM*LvI_NDBt4οcK'౤Lv&$K7Q#!;?I$# $"(fj!0^e^FJvLx%cMz!d^$h.R7fp' xI@!dv"%h.g)En[0Q<(nqcUVb?EɎ,Lz&÷0 dH|4``Ku q&E R lMŠI70Q dX|k! #R쨏󫮺*&q&tp/r 7WYIFCcHuRsIae&<~`cb =~S&#Jѽ# u.A_D&$$e$!&d0E:H9' ّΧx:~{ d.8R=n"HަMPSS#M!#Lv(4TR ƃ^dDŽLd'SKiR':)ٍ)!d8l8 \ttcɟvZK/F@J0IIZ2r"!;*-}t>'N*H5`<4$!u v|͜* Lv)@NdDŽr w("oIndDŽo#ҹjG:]ȥI%*Lv*+R gtWYN`c G]dqp⻢C.:8 zU``"Ǎ&;֎`2&pc7ytxd#K;LvcUVnjX3"Fs"*LvDŽnT(Lv!)4MQSoM^-DK^NhjD2M G&' LvSÒt\կ,ԠLv{d\p.&H>ɩc(*)-LvQ5LxQn0M.B. "psY) (ȹtQLvm\V,B6&;V8-q AdG!q/Y@%3OaLvS/JV̄1]|qf‹/]J6q1E X 3ZpmLv2<3N0%gv'_d'u Na|q&K3%cNS9 N@F ] &8M0٥P#]H>&8Y+䭠b}W=&;%_ UVd&dHv yGLv=8ڵk~[J!dRH|g8 &cUVy.U%~1%(d8ʄG0%M9.s/]bpUHPJ{[8.5d7rWY;)K^0@ޜbBf~9pYtnO{tZ*]SS|K"hDm2٥b! ]BdK15ʄl~N1`RK&<&D#< s4+")n|͸ꪫv~U`K6 ~]BLvS/ArY2٥PbŊ\rcKѾΣEL3ISn?M9^\1Oj#aK&LQ.J|;{F x_dd:&D6i[d‹7]sZ iU B(ṊK\*]"PVe`攫_26''E&p6Ɇ4D>jt(+@WdE| f/)JyB3ѭ1%(Ke1ٱ}C3S{EU:9 4 jumNq I+8G@o0z\ B#]JJ(UmCOf 3""- 7oEmd7e@2'ds/|6:Өͩ>^Y~cGdaU'$Uq~]V=M64~bwmh|P; ^dT+R^ b3(C^Pkxg\sa;,^AՎD IDATGtAG$X0S@j-TjeJܹ6iG޷}/dBdG3s x<fV (֣"YuJYL}NJ+'92P؃ (=*Gl.0'MclN~5ls ']쐏x&%cCWbB_'zn!5h07dDVF.:11cޚLsG]!(Kz,N8{32i&ޣ-]DRu`eLvKP;VZB '5 stG/j"8ڍ[hM={J6 9C~G?C osds2:joYT^^6'mH҅#ojw#Bm;kϫpA[یӿ몡*McjH͍-@E SS @ny<^lxt?*KkՌ* j!(+{˥]vy ^yJ49NdXQy:5 Z&GK.P`Qi=ڋvk*ܥ(9(.!Czvl .`sj9bl ݋- 9?À WUeuhjB 5jɖIz0 bE>+o)ZdCpC\A~|^@(ƶTwte3ӎ_-{^ JU,a,T&G]Rڰ^+A2l}p)(4"<:+Yyaw@?:46|C&<~%ƀKuY'+^?67nʙ,cjukҪ/b[CHsciE 0 lE5kS؁o?17<2C/_+r Clz0G9:yy:Tg<'$KvCf~;_譂J"*QȝY-`W72a%2Doۋ}wfO܇QfZ-oŠ'݄v *p(VD+8l.@.:{ѸՅ (}:9_ xx{hx#cn\tGBktc뮑(M_&qgJ{׋xzV@oT˜N|?Ãy)X`hxŊ>DY=pp['}8VU^Pu㦏Ν 0ybjd' Ptw ܋Y- ףTz`PS4`s|zr,ى6q~;CDv9=i9d^ECT]07ľ.)#(ͭ<3 (0ìN]V@ܿ<nԜ5k|─!__q xZ7LDyQ52 FeίxqKO>h.P+|FlD7&#;MH6^\`.Thyv8yMH+x ^م!;xӐ 4pv\Ӹ(͛9dG6 iüxA6g$v`,y8=Z?k:53<`Enrs+~1`dt[)KTMjsZ ]86hs@;."Vu8Һ $4 >A"/ݍ✙ e*Np`ž@ =gsTF4è^?s\-JC蜨\9dvЄ|CF9bhi;g[xI(t8 f/`r1Q.],@v^6psVtsW% Eo We#+u5wb>iv'w;˚Ώ Q{ ѤɩG5r=ubW4y!TPDu[{ݭPǤ1+ Hy0dζfX0ha4^;D{Dv] %9$9+̨:"$Jl=W4`sȵI+Yn?rq_GwlN2h;/ks\A?i<Sb9-śu"9](u)(6lV_H3v= ۍ!rAnDt^yyQRR VܙNV;amτF Xe],ݐ3|g2dGNAr8<5t#UK>o?>_WGL7vD?[CY P lYw0ԬAiI4:Uʎܨ3N^քfUVvqkCqndrgVv kDI]o?ڂvaXhAݙvy)U#( XO'xvlNiaʩuhFxN[2U6mƙWώ)K<^pPT^ڎ/eusL6o9r,^Wr]f@Dv6,^J$N0:fVvL.=jЖlIU95NԜnǺ Jh~xd2r_yPX`rwJ+ds_)y}^/_/ފ g}/ 3{z؀Uǵ2Dag'.d)Kqmξ`jʀF5:,aҵ9#;؏leU@lA :P9X}~ tggfz[Gk&ou. *׌>+]c|}ޟu \cܟjqz\N woNHPՇ/,1坒#~qYf|'Y063 [V? TQfWYP*aYaw`y^̤ bb)E5bŔh/6uC*YuMX96xv p7rfZaهC 7'~'Hџ2#jFva+NR}Q*؜jV>!6Ǘ9ey- ;7t@*9MθD6Grdv <'xs>߇SϏMB!- )J#RK&R n5?]pZ}(#M7{+dwrF3hLh>b9;,@v&OEL}tʅB$f7,4t tZQIzFʂQhK~q+4sVdgR4a}'r?t:0~val{?9*ԙmrl(,ςl|4gfyT#%"/>~,>g в ǐz^}d T:A#6w>A ;]e?]οz!攝ن.mS;L6geh>8#:hq(ڜ UD"}=7ׂks$Gvo}ۋCʅ֌O}%~dG|Ϫ.'+Q\mGQT0 eT(TctpoDκ׶;6̳ `i]X]^Xz=0TPɚi‚SB㓻.D27o@PSxJ Zn͡c.廩sB%Jj x?M(.|l*d7o֗;PYjs;|tm+`#GAY١v9Hu,`;PI(/DIn~]n,7] fO'2Ǯ ϙ_168pT^`B+GK: hz8J/D_J+ّ/('KEN 2˙q>a݇ thjƑw0sC8RvaFe^ل,]A@փџ8g7&{-XyV%5hUy5I߼J';y 0p8uuwoϠD͆~ց, }{vMs lw{$d( |m :bs참zE3k$]!N[ph 3S.vpnˁ:ʎ*|65HlV! &z+5xm*ZP7E9a<.Evカ:@P+nE뛠q]@#nzdXSh,q^tx#ʌºB+j ( ƣ bsV$Rx=]8 cFv쬮AzEQz3pٙmøVJ'2XkshBq*a#T*Ti>FU"#9+ +>v#6;]8 iԓQՀ w :Jvk7rvd!m[aCRK21gw*F3~E[ yqyѻ=^l'2?.?Wvh pјu|QH2]1;\s2ӊYh' n&8J(d'L8  NGNqGldv{QwY=N;'#ͳ(lbSAC}}mΰ'7+j9g.9^)Q,HLvlGq^|[.ϧRg/g@Wx(kgx) YWpUuv/WL>v>#Qu#'Pk1$iTx /ŒKDgG2l¢ q+Xp;(Fed< ̈WPU˱T\y: )IJ-4tFUT)N}e7~sesi2k[&:vjAݩmGQS9 1mȎ.4lC M`V46㚿G_E%\J ΐ=;x4N:i}n**I5 "+DXV|d/ Kg2S䗏ːMЅ/Ī~y ]}̞Ci$DN\) [s<?>#[ّܵJsa!e,rvy's+ IDAT'p5- mlFD.@e͡`0d hs#,`sf CcRI$Gvޮ ^h ݸQA%;}* Ѵ{p[,,GJqe@~adF~Z߳؇#rhƓn|?<7s dјH<ۅv̘YwuDq9\([FՂ =]ٳiNM쬞\vwst,S<ٿ%&z޶J3)N0a+[dw^| njV]?+`2YDcY3ßZ:ZB ~kOlŌE s}Qt5Dq >O!7`ħW̬?X?@͌jZBIud+voE,7AI *=3Ez>lnJ7~ nwԃ߿ukrhilDesxj*K;枦Gfm$ɮH?>G( pcs =uV,7>!+8tQ%dfJd)mŋasѕ`~YI!V{?w?Vf@nadӀ{7 5Xukڈt~μ/q}aN0zS!'1͟|gs:pQA?}E3l|64# aJI$;:Gpx=Yoº# )dGAVoAnA.VۃjFQne@(!̆"lWՁgwvqPT]$dGܙ6Z2Z5c2 ,8ޘm}8Q'{~o5[E} NjuXlNɎm-^^-Vt"( Ԋkos$Iv$}Qs{Q|g]_ad>*(H "/,>FeY TJJCC[ :L 9,P8Z`: *#xn<}/?iI;ps"${c:؆*;xXj^ g1it.w_ٍ<́@ڏ,oF;79xcpӉ[@!}ct6FSuQۜg݂3ǵ9tv"(FiEt[a:2xWgGȣSUw$Kvo?ـWGKjK;(F<j+fCt|=GQ`PI]M @vspW<e SŖvߌqvkG!:>,[uS3:>;`6gQ3O@nC'IlN >Bn!#6yXvzt8:H춽ֆݏfpp;_p8<׍}6\:ddG'2Ut4 ȶ~T0sQ)cwzhā }vlfW9P9Sz+bݍO`݋~]BfD~x>݃]?jQPyT@)<ݎ2vi K!ѕwstLNDح-tz0oa#F||7Zpg8 j8yUJ-os:,hjOZ λ);uas$MvCN<.hyų98\|ҋ~ъ K wJj#ϧB ;`jCQsK֔,;İ=(5΁׭]WO%x9kk <2㬟hQYW 0G,1bw8˾ɸ>~Zk9E3#0K lioCtQyk2SV;su#x\%s)Zj%wBA^]-P(xe'F!a:շ^tB!b~؏:jsH?̶6gHgNloOGlΛ6|B3bU?9XFOpn|DdU6+CQ{jŪ@ɒ&Ld|A~kimY_ %GsCVg3-ٜ pTMͩ݃}ٜ@7m8Q(%y`7يַAjDheG3_2TYy_J*Ծ!lAs]J*2Zj GvN95K|rhdoy5 =3dGp:!{w<ڎl}#~7u@!/7? &;#VS6V4oMbsC}$ƒK8%bH.97ې &;É6.N/Ɏp/^R,23=vd-kCV;r dGT{ JPTh9}*t}GEUZTV<6Ԯ +#e]At2R$;^ڹ wzxO]C>C#=қyˡ$WHP UڧCU[^i@yqCUY+a VWmqch=҇xZ܊O!3BvdsܺA|(K7ݴk}OΠCBv.;jWѷM =)9 +P"F27P ;";?_{^B܄ df紂8LX|VZ`-OA1}Xt Гa_z^֋d,u'y1q-b"6dwtg^:E0r^j;jS0:Cx+ؼ}Xqn|9Nt u y9(AqeWnzx_ؠSf*#Sz,Zqy 9^7V|͂O]?#6C9';9~,X=Z)`s40Fl5˜~6'minG^ˮpjttpqԌg<s1@v[  H?^^dg *ڧ1ZCv|a1n&q_ l~~7 3k5[ ^r+}hsNt}Vp]%XQ}zw4D~I6s M˰O ]NxQΊ滛~o GB?Ph1D:5ڗFFk2Mi݊?^WݲKOziOX [XZXqm2EALXwu4n<ݸU/3$4xzїڜюw+;_km2zRW_Gk9oA,8m,nf߽>;a2V/Vy;4;']=( :Z3| {{qW pj3X?~n|noyA,5s*\h=}f%ّy]pY(2V2:pՃyXzO<./vd?xaO`>< }VsAMhw p ELv 3OR#t4i#e\|fL?IAFx~UUBQP{?{@߁5Fj̘w̐{,Z'eAAL>OGaǬ5jVTv6:pyX~渽o<7n.HEbT65bI,M- jKlo 1**`DEA tm߻w޻.;wٹy3ߜ=N||Jkb 99'^}C]ʂT$rCJX9DW6b s= pMr;JϬ7mAbYPËC<2';#sH`M@|EX 鹣J1"xľuw_o%~芊Dg+jx *9 *4b KRPQx!VkN߾RYSg/ǦɁa [1|z lF_y_?Zɓcص=+TU h'4*\O(Nu.}>iu frm9ް e#rN"d;K(Ez֯cn-pvUY|GQ~t*4bdڏXrN] ̓ceK5+tzyDco{>h! 8mK,>epmqIDpѬm(\IIW~aW_,3@bw0gj9|^Z;NG]I-ʀ_؈܁!9k[D[//AYa-SBqH:p.P~n5:l,q?%T8CmfzU>:/.> ca5] |XfٱNS qCϯ99gdMQk)Oݎƈ ]#rN~N1|?u9U/`3v\qg ˷c2m9̅#~[1:;/z,hBP)=C*08=BoaCv;!>M2@C7_\NΫ%~K&-Ηw_Cev0}Vⷰo]pMhM7g<3*E""Gi}Ltwoռ|{[! 6>:(ːJ<nUP`" :6U7PM*`%{ߒM 6ik+N\ߺrۻ;/f). _kݟͬL(a# ܉uP9,Tî4.c[oA*32]oIGNs< ;6nã6!eJ|: Qan)7q5ΏN>íHq"29U57Eo*&-®$~.{ytYS)Šѡ!rhZl!nS_;6$Jzv?Ąk#Cr5_x;KsaǵC/dDUl#6"(2q9eAa9f9us9'@ѿo5G⸝;;9ѳ-D~)!']{kKܩQ2ODƾslsJ.Gt6fC//Rg[Z ނRtzox'~9#.\t&*۰ gW'L,Dσ0d,;jwJ(h,nPR IDATsK|r_sNѩ_99.zs܊ŸsNE|!x8}VΉ q.}[D46>{})M,ܧ vvFyehۧxf??/J ^|D sz]w=rw[|1wvUB2 :G/ļD$ƥFv%X[;o_3yѥ0#kD.UNpCmw>Y/[؏|x,s18na9Ǐsf96rN!Kl]MU`ΉK)W&i1.s\1dUTgN=,v 9To]B+7an蜙l*Q fXᗕcIҺkr/g.ÎoҜQvΟ2{FCFCm;葊.C0D1m~l[))H'3@;p֔hF89_=V{ΩL*8kJ/tU=UaT#`5+:Ixʥ1s32rJG$`Ή(-)}g-]tK?0W<1 #;7 4q<< }O,A">>gr≲6䤤7#pTsԐkܸ2߷ފs`Q bT8ur{6G ,|oeaۅ(XI)HNN1z.Ѿ@9Ȝ54tA+F\\Avm ]QQ>Ó_ ,uCRJdY8k>VZrN\-?:fsNL ux5Ț^uϼ~R;ccF+vsw⛗*Ώ>&ѭwm]Eb%H=Ki_p̤Q oX_Z\wGȮiCbĥǡFb|OWGފ $Fs=b}a7$%X׿%՟/߸Y %l@8w@t>HT{nDZ6w;yI~dVs2#sr|F k9*k\>]#sN÷/GoWJP$b  G>Qœg:*u$?ϳp)kNҊ]B{ YKcZK|TC Ta^1>=}SS0F FdD|c]6UZ\~~ͫ B/qǡx yMZ֯1#@`*<}\l^Ysaǜ Lu0+Ի v*wW =4tl1wAczj$W`ɘ2e9 )PyمJprN1ЭG k>S#6M\^Ӑy@:Z=XހS1`ƻ6sΊ =ttrNT2΍ ՘>S̙3:$b}uWq]H΅&M'|k׺8ZIJ],9N / bP.+.0\hB4( ة!Q@9Ǎjv.4Ts!\+ZЁ \C; (縖NF:5hԋc5r[媏΅jx.D!٩ V@9ǵtE#^4ŶnkV9RN /*b`.w]yidtjxѨ v+UN#SËJ>Xi]W^9ǵtE#^4]l9n.*/b~וWq-FvHz}`r[4J95us\K]4ҩE^l+ŶnkV9RN /*b`.w]yidtjxѨ v+UN#SËJ>Xi]W^9ǵtE#^4]l9n.*/b~וWq-FvHz}`r[4J95us\K[#_[uW YEή6F;瘑=٫2{osD+m'k"=/. j?*g'kv-RcS?n~gX&];LX-C'`^-=u9YصFl&LZs-5}s̙oy)za~}N26 9 ]ֆ<Q!;?tFx*fh\Y2ryѓk=3mbCT.ʆÕs(X3ܓJoDl'U(cFhvѨ vM,p- 4 nvfvMsc;CBk. v{$ؙ7G3" v(Y`gμ^(`gGys;{!`g vk. v{$ؙ7G3" v(Y`gμ^(`gGys;{!`g1 ;  voΦO0}u=Tn v9f-ZΦwNÂdO.^<,ؙuiOS1E]fAkF-IK"~fHkF;oW$o`׌ v(`g-;͋Iٲ1uٙ4i^s/Ds嘃]K^+Ucro1+NSGTZm7٫q9Gs.H/7&eΥp1~`g,H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;],H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;],H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;],H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;],H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;],H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;],H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;],H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;],H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;],H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;],H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;],H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;],H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;],H3o`g^s/DwQ#μ9yͽQEb;v5BD~;=̛#ؙ ;]9؍ sM™_[IάZ;6 vڛ:"fmȫZ&i$n~5 sp_kFf{E:&xZ @Ĉ=\`׌ v(`g-;#_`gy]3z$5}+7/`g%W *ҚyͽQ1wQ#μ9{ۍiEnLw=Js;sZ78``ɢ>@Z˜<`g킝 vk. v{$ؙ7G3" vs oy]srck7 v=hFݘ+kC֎6m[YDܨHv$d#foQlJL=Q|#;>l"| ]3z$5}+7/`g%Wݘ4yͽQ1wQ#μ9{ۍٙ%DwI#μ9{+rQ1(έriH]%.j c v{$ؙ7G3" v(Y`gμ^(`gGys;{!`g vk. v{$ؙ7G3" v(Y`gμ^('aɼ +_+T* ؙ+єswғ_v] Ly`֠3'YjdjժEjn^tSڎQi,%w?O԰8 .رcѮ];lܸ=,Y'|Mt?9s&{XwyGbbbJ^l,sK&ݼy0zh|#C=X~=\ׄŔ1 N8SNY-Lf…?/۶mCjj*u놓O>7p3CGC駟v.ߦz8ҿ/{[^s5GvӧOg7xgunW?ClWM7J/_xLt?s96m5\#Gկ~tm۶p#//9/.{ߙʴ'Omsٲe1w\af#FUmJKKqw^sF;.SLyc?~xdffK.qKF߾}`o߾1;;w:u}N;qt*ZZ^Tα?D ,tM$=BFǎd^<9Gqr뭷/~ y8p |]w[n a7.L41X#]v9z\[nC=6m? W5bGʕ+Nl6===t/."g-ꫯ*Y]}v^^gTF46ދjk99'j" *)1c裏QG&^+{\q`8ҥB"7|M߂=w}^1x': > ű5awWgq!֭[u]t́c١:9sѿS\QQFk͛ѫW{1<駟NG9(пW_}5"͎_Wqsm~p:b[=y֜Ƭ}jtYg2ޛ6/gcǢEvfxGnmb}﫺rN4ui(nD8O3:HKKsO>H:)$h# N {Gk׮ ";<̚5 [lqnhc0rȩ?iS=knumڰ@z)gz)K g}(GќvS0ט8@pر;HO>(//wq. rӞوDŽׅmckРA =iҤĹITm bԨQXx+4w=kŽo]5W}yv„ 1`be爽^;g"\%XBP4+ dN7 mS,풝hc˗/wڹfʺVϚTIW]u~6׬Yt[o崛?ܙ ξpfSMQ^}UW^SιֹO24 37Knj3p':G8\ZRmB` 7[pj7駟\Lmgm#,ZW69{9 ,Xsmcg_ěox,XpB;Ip]c~믝xp]Jk ;nɦ7;|.Q}4"M:piBvث"c$/Sl;ޅwvrMqNNE2upG,`bt\W3h[=k.8EYYY›ӱտ7<Khb}rN 7uΉvHqDNir4x >,߯Z̑oTN;0gڑ )X w-r4!ZsM"Žۓ##Ke牻9%M&]l3`c##;֏uc%0 YÖ`a;}-G^zX7 {;g !7&xmb4yM9':4 i:6v`c(*Fn;7p G߰'͙3ɑa^ux&G,g‹muarS4n,T~pCO}!é;v\<6OP@3bԩ74p*,BG\A '&R99>\,{znn6yN18rɾfn9NcGՆrϹsq꙰ uKH vW<) 0`g\rR@ H vW<) 0`g\rR@ H vW<) 0`g\rR@ H vW<) 0`g\rR@ H vW<) 0`g\rR@ H vW<) 0`g\rR@ H vW<) 0`g\rR@ H vW<) 0`g\rR@ H vW<) 0`g\rR@ H vW<) 0`g\rR@ H vW<) 0DVIENDB`portsentry-2.0.6/docs/images/PS-Int-Org.png000066400000000000000000000602571510117642400204650ustar00rootroot00000000000000PNG  IHDRsRGB IDATx^Tv,4A@T 6bh)_jb5h511؍5*FBv6rwgٙ;3|콧|޳{ssD$@$@IDDK"k$@$@ ; @%`   @%`   @%`   @%`   @%`   @%`   @%`   H6l@]]iP\\ӧ #:蚀?@IŋSumMVzPH`}݃^W/J$c?U~TcU9eSqF[37|, Pg( PޜzjMJ%4O rj"bwy^N7HLʼn/NYy晽b335Hh]NǞ˒II% N$TOH!@ӆ#s1ի`^OpdAj@Pbœ@TI x h(~fQ1 Ho%uv   P+@0HPnfŻ %*3V8+,'PI!B'¦XX՛吀 PlmXY4v=F0|tHCJ# Sh5'#GJR4 B %>[ ”_Kr:ܶy ^,z @0ᓀ#,j2_&|c@hC6_zHMlC% b-/5`kF5w1 P۾ :~Hϰ]yDԳX7B_d? AiÑĈg HHd&@Kf{~Υ쾬Ptd Vep?sH`(~g7D_%e kǩ;X܀T}NZ>L]m% P"F bEs^tK,'QPŒIKiO1'LI &jJ)NfMA<q})2 F'> @㚟:(`(?C+++2'"()׹#_!ܿl}x(~q[q"AzY P m䫼xڞ[-ޗ|lq(~3d1&ItAQcX\%aY>=Xo3eK9ⱿǵDl[ PMG@(x7B]uW?QT4P܉`X- x.Z3 G7#^w yUi}Ssx̐P0a!A[H!@ӆ#s! 0Ū hC GB$@$` ?U% ІO̅HH@(~2J$@$ 6 P d,VHH@?m82  X* 6(~pd.$@$@"@3XU  mP\HH Dg c3gK444jd?W^y%~?in71m4Y&g_~9׿Os=w@]Ʉ{?[n7pY#6 @H(~!aC@`馛 Bz*^|EtIx+/\+zс#F(Bֆ* "~f̘O>%_".h(~F6!Sɽy֭CSS⭩ISΝqeaժUXln6\uUAדּ3=/dAОO{QdK/?YtQG>"L{ lyWu@┩M5g?Ýwީx}娩AYY*~z+Z^wuxwuDItaX%@KXӲa4ȑ#u7N<7?xٸqcϞ={\.FC9ofo1v2e*e+_NDu~J ŏ}bO{,1d}NDH.%OVd+Ö-[Iٶ fΜ ǣ¨驧¹瞋[Y#Tx/*~du]O~Bs`I◜vOV?~;ezS<󟊷&oQ 3̚5KnOկ~e˖lHbE_H"@K(s1IH◄Fg#'@!s xœ>6,aMNJBǎ@aPtd V8(~ƱkJP/H 08bMI hDHfCq"@/NY Pm?֞(~$_ OG`UCg[$5?ЈO#̆D_Xc~= PH 08bMIk~$+V`ʕ|lHbI_,i!@KS!IJ◤g##@&x,?j*wrwKTWk;J73&d'@K7on݊a < Oa&§6v7$0(;HBxX| զOê$T7_-5#§¡jM ((~ A ] @2po`(~&LG4tWeuF3:HF?$ cH8,|lPt _?FF֗v% r ߁f+q&@X|(|ޏ$ !P6SlT|$@?cxƨhk) lo a&VHH@K?-i2/  CL$ (~Zd^$@$@ @3XI  - PɼJ@nh_yL/#  PBK@Dok[ .V?}o& PC A` R+G0N|(~']B7lp_'I I PFj6/tkQBg'/oSB7/tV|2 Ph}']+CTn;>;$!7M5Uaڨu#C#%ҕ7kd&@Kfs?YD,p C%P@uPSJ%;_(l Ut:`#Z[/X vHC3@fuA 3J5mC݅'C PiPP ]4wB#@ #Dc(BBڂMWB>=8vF0(~ cmP73XSk E@%+T=C P,e$0<q 0 &:NWJ pV舱Pv =8tPiH?C-*p%Mh-""[zbSHA*<.BR8[=QPzSw /|v|3FBUGPtmVNG(~:2/A Ln䶿!ZO LCm='>IMM gyB48F >l* $_™4TYY^ډoYxӰE$scp'j+"$@KL&T/^ %p˱bŊc$(~iwôZ~WƢE SxUTDoʕx0Jr C^l)Z G7/I mf'^x{1N_]yyy/IH$1)q(|Mǃ$ ` j%i0u^KfT?"L$@}(~ "mcmy2!@K[% p |A%fҥJ  @c/z(5?,˗x6K3%6&Ɔ3K11еTOpaTbt=܍Eg,{%\m[r Dly46 _3uN0$)6(RMo$l_r`|IWX,\lc."\qHOOO:j"iMφ[ @ ."~ׯwF[xpG+GPoxR(.B?> WEl,0YV< PI? ʎUK0oӦMx'zޮxp"*O=T+&'S}f̘>/#?YûkzNNdPX'5qxPO2cLE{^}ոەM~9sdwYgW^AWWWD'J<Nz@/W5Fc;رNؒpu0ԙAX PF u\<\hرfI#@ۤY/.I[G@U /,l|IH$xG?FΊO` =$K 0OE[Ļ&o p P%1/I ?H}ˆ(~FZ8q@U Jǎ2 _ȨA $ haMۊ3%ZrWo_|-f˰% 7L)~1U|*ʩp7z@[0OK|GBE/i_5aN3b_Q _'RHLPϦV`LL"黀nPw/ IDATtkU.c%s8j6QoCM[JSl?+v"ql.{ظlπFV)|"|U\Kf_ ^,zpPx$2JӱqbU5Ċ4~?=X!u` B'q$6NNӉ!U ŋ<@xœ~˦,^7nQQQA"$3WA :З=6.ZWNfl{ Pb[/E1E/`=TȾkXsyʎX8KXAY::  POHP۾cK۠M΀զhT_P; @+6gK9= fP6ChY%hIyŒƒvEKp: ". nlg-Ш:iO'F5 30ē<_Y pYj.]dұk'/vcVbŐ0(_. fP6@3hI%MUnڤ˘@&gKMC ?-( u=D40L$0Qb90%xN ^%C7Z:|.:4 33 W&U\"&HFV- =  bP ``UdA jGFkBgJ3yՅ˗CDH2"G E7!:3+@]Cו<Vnܸq?2`cucF{cQ d>Xj\ p D\p!emoʕZOp1X͸`h\pM0FbuIKR?]aJ0EbtO@%\h[+vŎK\c˰Pp ;B0? 8p Rby,[ $ŸO.:4 0!̨I#(~`.hǒ9@0~!(~:*Ց1X&- mސG SlbKl8?؟§CIC@fZd T )K.ME`.J$!LBo2=8۞.q6Oz]G3%Y4 &_w\#|Mc4/N <%Ap`t_l8@g$"~Qbl@8#0PÀfW(~14b[㢼/./Kк() ٤q.^wuʕE/+D\_tu֠(ۋv:[I >_ƍ@KFNIE!^tf47c*@jt,5`$=)~g@xpΉmflx /E97ʹdfB#Q>5yH˶jBU:@ď}KEϟ1an7uvU_Y`q, / f+`L>t8ZDT䍴!TR"%_.WăzˤE1\ 8`ϗ/tUK  $~&s,&xx(!Ă F# &.FhؾN-XF"eDYwu8uUE[U LA`EO6lLiʴg/~EPp|ddjfKK/E\"\ Ӂ{Nlg|CeN[Pre2ab5x|~*~ʔpyvyCƟ0th0D PQ"؆׋A (@l/[49%vx>Tnlľ-Nݳag0Op"δ` "@ ^E$afԁ_ԣao<9zZĠ.o'RJ[P~3N؃f`6v`;Dnf RazA].l˷ao)g  dxpz~V74̹(#bu &>?Q]?fTHc1=(4aBCݪxt #6QDp`D2OC|(/?^â2+<9b͉Zv|р ؓts!5+'^*~^1k>yʩ+3˃;o>5P۱4= b5_9nج)t;0y [Ipa6t֦ -+~jlooFCS J30<9vKBˀq[?@rͅ]hbudE&M<;r/~"3;qVdfgt_<񳎬Œ_%b8U6ށه}_aڜQgEiط_17rTjA*i[ݨk։QrmF~I&i6Xmn|'t i` h_uu8а;ho"4)iMҔOєӂ;`3a;6+{cq30r\^čj:iY3T2{n (nx:(*,@ulBu98Yfd6 g)606,!MDpwlCȺYn<|5.NX`wőKXrM&6k kku2w =7l)Z%~oWG v!SQ61rBe`c8!^}i%=S4{nؿi]}OySكpO5kX:'*"*~zzwXiOϯW)Qc: F'=xj6E=v ߁ǏDH; h+Zu?֝t3' Ԫ䓩ṛn@k;٘BSJ``'{;G5رe/Ŕʴ~OįeXp&#Cn>Fg2Vڐ.]F|e(ܛfq±ȱ!-'ʱÁ{؇4? '.܆Sr&L/5݋Ԩ.vw950eҡʒEݹڳG^Nzutrx]BiXXůIį 7fG50&q& oh2 X>*k~^"35?`3kbAs= 8<9n"0zK. >1p,`Y=օ}k stuuw" ,*[S>X֯)~ð \+G{be_&uⷯO܅yXpxGXؽ؆5oDS#dc^+䧏SmJ3#?-v-OܒPhJ<+iOWn\zblzdr#`w#7Շq5i@Vlv\KB#_ۆ/nO jA ;U"ZO|.Ä# I ƍS`iȖZy7mao_{;~4*tuvcuH7'xqc70jnD"řK( n#6<C6ThXqVXn˃ϫn|A´Em\֜c~ PΚ*AU/8f^EЏYXuk/L]eҿAJJgonXʍMA UbłnG7j,<~ٰv;j*P_[ <^r-?.:eruQ$i_uk.l56N"NBteWݭ\z4,ټ1Xnlz @&, S'We3~&1,+Ū&Wyptd #ߌ'&L苏-HI+>^g\;yHˌv붣fO&wՉC=;-76b'N;?;=I">OI`6{֍}/^KN=fΉ _ہ9x/ os_Ljm>eTx29E2:h+z~{c ,m==#"f Kc]N7of(5ͨj{xڋ=wW]_XS͊'ktY# 'Œ1ӣjd9aGSWa´ԯGX1N9,6z~rEJ c皐_^"w9E?P%F@J >8EmP݆Owas*?7TkF!3tr(=W1฀ַ  2_\%gH>xil]E$}rѢE<ñN{1j◨sܡd0[l*++C&Yj_"%D?Nj̉--N ˖64vp#rC?v]nEftvtzO:EE \S ;RspSŗwysYꚟޜ:qb݂yO?ɱnP&'"~+X@kzJ洧l?}s7< wW|rzN;χYdž߯Wb7dߵ '$+7afr3czC-p}Ʊnciᖥ%b8pr F}~ giág0 _ʂg?^e`׉@5ujc'Z0y^>KJXt;Qi)(ԠrHj:] ~; IDAT.7&5c̑Ѫ&#.1:oٕ)R3' zFS];Y#Lcօf:9].To8"4?'hZZ]U;6ٌsiCuw:}C= YHì<Ɔ:=;Z)X72i&~z}XU|m=TŰux蚍0Ռ ?ۍu8fY1G1&^uE >}RQz3Ӿц/.AFNx{'}0fR@j'FLDqY2*ۋr<oۉc/e՘ 8 =2i&~z}X<:2+\1|rҋ)>xjB;,mM]xh^_گ0;2y})e87#P:!S?kU0[L0}rvԌӋWNly%'ޟ遭r3feNK6sOe: ɆV<~e ҭyt`E.N|+v۞̽^*~n_7ы'j<$O 2m'"hR Vx^dfK :5yhVy'.`Ncoh^idlw_Y<>8--"#Y@USޅ;E G*Q3jx~Lz|ntvv!+#GsuOcƉ%n2Ǻ:_h)#tMLb6= xͽӞ"׳7sf-rK5j?T⧈zxxݾ^s9ۉ1rd>_h0QB&Oe!XF)N'a* 8)(U; [Ӆ7#ʔ)^3` m9,Pjk0"dσ/=A F4?΃!0ZKӍ7^\rn~/~!r V+>~yq_ubcc} HoDx*))VRlX=ӠAT*$bCc]h0i"~' )Vݵ)'xG7l *~FflLj|`۫vxkK%ӕ^Uұ~MY[t"3='s'|p8HgDI^$Sp:ď !dL0EFq_^ Ҟhڂsӑwp \Wkk(w4Z6ſϮ{9Jytw#wcɄA:ij"''[#ڳ݂)J &6uC#3Xq:`a9𡻴>pv+Xbʀc6NSG"5[][ZQ.>oAMtc>;$/ ԣ+;&;^g2౵cT+JN Gfw:4])Y]C!Xcf'8n6}~ DY7۳ oۈ׍GX1yAJ8{]ֻMB^Jj\gwSp Y0)Lik&tmHkr`v  ߊǺeTHν(>F5$eݻ^Ŕ((KUJϝ\ 'E ~.tnn7xT(vaۚ.8 `Km9x Sn~h@ӾnNL-n׬IǺ@ŏ!QR*rۏd?t'~߆ɝNT|چ6,=g^]21#7-}')=8EET|艍XqPQB=g}. _cV4e18y?~)''^/.Hu]F31U7+A.fj*q%P5ϭxʡzx7K$mX$qy7j⧶FoPk#wyӸ!pn۟*͜1MlH쭑зк=>oV"̵8>ЉYÁϞoCVfvOd>;Y9}KT|YFp:bÐU2zfP 8[D]q뀕[E)ږΡ)Z4vǷ_myҔ-fg9+ːwMxJ/wX?cgRCOʆ==#.T|ބmӳO/ŁOLAfnߝ]n_0NGDt0rd<2oD#Ն:m8F3_4¼Wkw}g:,^3Ên,'|g42rcvol?W]NA=KmWpW0$6ms=3i쿞H?>{%0rB~V'>x2Yժy>Xrqi<{: YI>@$#$&|uX}O;|='\9=|M|Uc$ɍcb81Ј7[sOb'~@f`L=08n|>| 4깲H?0&{ l hH!Lf_]xXVL{'Iv8n.#{IN/{j> ]U߈X8 ddMWVW5ҌkabcBZ?ݾ&ƴ#n_~kEWʍCVyK!r ||I@[?my28عws#:vR-pME('Å]憣*/h |ݍqѥX߈O;B9I9u]:i~{mh*Z/ud M&8œ@Pbłxi_V SgN秖-^Ѝ? %}mNl}U9/tGf5x9up~a ER޼ wg}$~NO'FՉCFt[:ĵp)VUtnA1 Giwh #P"WOjk e%voO?Ɂm8"X0)}ePWoCQpW&/li&a'Nxd*^tꦏpfjy=u .Bq9/b Ngt U5j>Ʉϥn}۷ʳjktWWRSS2,>).5xZxS?e-8PvH f?OIGXێm;˂͜0/~Ns3fe#(p}Qӆ&qf| n6eEzz:FSN9\s2=E2>HKK~3~W9nw?GSS~iuYt/G>YXXߟ']HI鋲x<3oB_Wzj,Zh@[~@;2L<5&3 @gުǿu"~S@[eSq qŇxTh_~=Wkм 7M_ߓ3[Hт9'\q>8$zK.//e]C?_1n8|2/=?pP$c_⋡<gRv؁'B#?Ÿv}/^Hx |@]]]wuxq1.Sdff?O@"Р \y~z~DkS ^W_fů ‰l3FM'aBly8Ѷ ;VKO@K0<ʏ`%('x"t(䡇ҥKqG?QDp84XᎏN덛I%,<_TwީxgwV%K,W\W_}Uo5 $~aԥY er-J+WW_xjGʼ!*Wfd.+x#\88d23?ɿݻY$(7I}iӔ,:;;L2߯|T3"=X@Q[nU]ntM)CƧ*(pǡ!pIM2xw(m,5k)SweHH>DEE"f3a2{J ~"ȧ~:}]:iO1ftAʔLIqAĸYYYz<ޮthur e˖)aȑ;v5&Uoƌx7!ޞ_?)~.'"X3 rl0SfGd)N@ҙ.pXqA3¦˪wŻ`o~NJ+cGUU ep݊ep:G+w@ۼy3LeeL>}@J='t2;' e<#~XAD՘;w*<.R?P>*>S姛o۷oWm{~b#GHf#I1^xA`2o}K qsl?Y ,P')mܸQ*K.?*'_qbwJ/..ƷmE%!e[,nF%j_<:SDRU!^?IU=?hLjb*~ꗇ|)_S Ehq߸|+ ʜ\M"A/rN/D'x$ TWS8[O"Z%Q讱c:|nnGcur*~yva҂ ;{FO|%4,w"`.G)''GWhe?]|Jޒ|p:1hQF w|N=z]zg}~u'@4gԪE꯾JgyFyE]a)~U(_t OJt̔!Aʡ!v*c80"c+69Y4 WqT| HSwQ6K_y@U$Q2G?ɿӝO/Tx'Iԑ|9H\KOeoLU)IDD lU~W3DdSfdGVe9l|N=$Ob[$΅̆I@>$I0lȚwJL$%6Cd4'c[)[Wi&~<_:h=o*I4PXl,d:4iRPO}O"EB<5UKOVOqʑFU* sC p#1DdS>ne 1CP4T%(N@F\L$RIeI' E<l|(8 98no1(l9@`OQfPO/6e+a:O6:dn]Hh$8D:z@_6򾨿|('2,S,I_D?F$I27ngI d@6S3EeMN<@.'F2e'K+OO,5 &~q-㏌#; SI2#~GP!c̆ %%IDG^<9l$$E_ME*e`׮@:یLk~ c=ߞJ zq @B@ u _o +m @@* ~)q(įPI  @B&'@B@ u _o +m @@* ~)q(FEIDATįPI  @B&'@B@ u _o 3l:IENDB`portsentry-2.0.6/docs/portsentry.conf.md000066400000000000000000000125421510117642400204040ustar00rootroot00000000000000% portsentry.conf(8) | System Manager's Manual # NAME **portsentry.conf** \- Configuration file for Portsentry # DESCRIPTION The portsentry.conf file is used to configure Portsentry. The default location for this file is **/etc/portsentry/portsentry.conf**. The configuration file is read by Portsentry at startup. The configuration file is a simple text file with one option per line. Lines starting with a **#** are considered comments and are ignored. There is an example configuration file included with portsentry and available at [Github](https://github.com/portsentry/portsentry/blob/master/examples/portsentry.conf) with additional comments, examples and recommendations for how to setup Portsentry. # OPTIONS ## TCP_PORTS="\" A comma separated list of TCP ports you want to monitor. You can specify inclusive port ranges with a dash, e.g, 10-20 to include port 10 to 20. ### Example TCP_PORTS="10,20,30-40" ## UDP_PORTS="\" A comma separated list of UDP ports you want to monitor. You can specify inclusive port ranges with a dash, e.g, 10-20 to include port 10 to 20. ### Example UDP_PORTS="10,20,30-40" ## IGNORE_FILE="\" Path to a file with IP addresses to ignore when logging and taking actions. Any IP/CIDR address in this file will be ignored. This is useful for ignoring local traffic or other trusted hosts. ## HISTORY_FILE="\" This file contains all the IP addresses that have triggered the Portsentry detection engine in some way. See the [HOWTO-Logfile](https://github.com/portsentry/portsentry/blob/master/docs/HOWTO-Logfile.md) for more information on how to read this file. ## BLOCKED_FILE="/tmp/portsentry.blocked" When Portsentry's action mechanism is used (BLOCK_TCP and/or BLOCK_UDP is set to 1 or 2, see below), this file will contain a list of all hosts that triggers an action. If a host is matched against this file no further action will be taken. Leaving this field as an empty string "" or not setting it will cause the action mechanism to always trigger when a scan is detected. ## RESOLVE_HOST = "<1|0>" DNS Name resolution - Setting this to "1" will turn on DNS lookups for attacking hosts. Setting it to "0" (or any other value) will not perform DNS lookups. Default is "0". NOTE: Using DNS resolution can slow down the response time of Portsentry ## BLOCK_TCP="0|1|2" and BLOCK_UDP="0|1|2" This option controls how Portsentry reacts to a detected connection attempt. 0 = Do not block UDP/TCP scans. The detected connection attempt is only logged. This could be useful for monitoring purposes or use of an external tool, such as fail2ban. This option is the default. NOTE: It is highly recommended to only log connection attempts (by using: BLOCK_TCP="0" and BLOCK_UDP="0") and use an external tool such as fail2ban to block the attacking host based on the log file generated by Portsentry. The reason for this is that other tools are more sophisticated. For example, fail2ban will persist the blocked host across reboots. 1 = Block UDP/TCP scans. This option will block the attacking host after the scan is detected using the technique specified in the KILL_ROUTE and/or KILL_HOSTS_DENY section below. If KILL_ROUTE is defined, it will run first, followed by KILL_HOSTS_DENY if it is set. If the KILL_RUN_CMD option is set, the command will also be executed. NOTE: These options are preserved as a legacy option for those who cannot use an external tool to block the attacking host or has some specific use-case, where this method is preferred. 2 = Run external command only (KILL_RUN_CMD). This option will only run the external command specified in the KILL_RUN_CMD ## KILL_ROUTE="" The KILL_ROUTE option is used to drop blacklist the attacking host. This can be done in a number of ways depending on your OS. The string $TARGET$ is replaced with the attacking host. ### Example KILL_ROUTE="/usr/local/bin/iptables -I INPUT -s $TARGET$ -j DROP" ## KILL_HOSTS_DENY="" This text will be dropped into the hosts.deny file for wrappers to use. ### Example KILL_HOSTS_DENY="ALL: $TARGET$" ## KILL_RUN_CMD="/some/path/here/script $TARGET$ $PORT$" This is a command that can be run when Portsentry is triggered, it can be whatever you want it to be. ## KILL_RUN_CMD_FIRST = "0|1" The KILL_RUN_CMD_FIRST value should be set to "1" to force the command to run *before* the KILL_ROUTE/KILL_HOSTS_DENY is executed and should be set to "0" to make the command run *after* the blocking has occurred. ## SCAN_TRIGGER="0" Enter the number of port connects from the same host you will allow before an alarm is given. The default is 0, which will react immediately. ## PORT_BANNER="*** UNAUTHORIZED ACCESS PROHIBITED *** YOUR CONNECTION ATTEMPT HAS BEEN LOGGED." When Portsentry is used in "connect" mode [Legacy option] you can specify a banner to be displayed to the connecting host. Once the banner is displayed, the connection will be closed. ## EXAMPLES View the example configuration file [portsentry.conf](https://github.com/portsentry/portsentry/blob/master/examples/portsentry.conf) ## FILES /etc/portsentry/portsentry.conf ## BUGS All bugs should be reported via the portsentry github issue tracker https://github.com/portsentry/portsentry/issues ## AUTHORS Marcus Hufvudsson ## SEE ALSO portsentry(8) ## LICENSE Portsentry is licensed under the BSD-2-Clause license. portsentry-2.0.6/examples/000077500000000000000000000000001510117642400155675ustar00rootroot00000000000000portsentry-2.0.6/examples/logrotate.conf000066400000000000000000000001461510117642400204370ustar00rootroot00000000000000/var/log/portsentry.log { rotate 12 monthly missingok notifempty compress delaycompress } portsentry-2.0.6/examples/portsentry.conf000066400000000000000000000235531510117642400206770ustar00rootroot00000000000000################################ # Portsentry 2.0 Configuration # ################################ # # IMPORTANT: This is the configuration file for Portsentry 2.0 # For an example of the 1.2 configuration, use the 1.2.x branch ####################### # Port Configurations # ####################### # # The ports you want to monitor. # Any port listed here will trigger an action (defined below) on a connection attempt. # The ports list must only contain port numbers, separated by commas. # Range of ports can be specified using a dash. No spaces are allowed. # # Note however that Portsentry will automatically exclude any port which is in use by a service on your system. # If you where to include port 22 (SSH) in the TCP_PORTS list, and you have an SSH server running on your system, # no action will be taken on any connection attempt to that port. # # The default port list is very conservative, only monitoring ports which, arguably, # should not be in on modern systems. These ports are essentially used to detect attacks # against older/legacy services as well as catching broad port scans. TCP_PORTS="1,7,9,11,13,15,17,19,20,21,70,79,111,113,119,389,512-515,540,543-544,873,989,990,992,1080" UDP_PORTS="7,9,13,19,111,389,513,517-518" # This list is a bit more aggressive, monitoring ports which are commonly used by attackers and # include ports which are used by current services. This list will most likely catch more # enumeration attempts, you might want to manually review some of the more popular services listed here. # Depending on your circumstances, you might want to use this list instead of the first one. #TCP_PORTS="1,7,9,11,13,15,17,19,20,21,23,25,37,43,53,79,88,110,111,139,143,161-162,389,445,512-515,540,543-544,873,989,990,992,1090,1812-1813" #UDP_PORTS="7,9,13,19,37,53,69,111,123,137-138,161-162,389,513,514,517-518,520,1812-1813,2049,5060-5061" # This list is a catch-all list, monitoring most service ports. A use-case for this list is a jump-host or bastion server #TCP_PORTS="1-1813" # # Skip 53 (DNS), 67 (bootps/bootpc) and 137-138 (netbios) since they are commonly used and might generate false positives #UDP_PORTS="7-52,54-66,69-136,139-5061" ####################### # Configuration Files # ####################### # # Hosts to ignore (i.e. never block). Each line of this file should contain # an IP address or a network in CIDR notation. The file is read at startup # Example: # 127.0.0.1/32 # NOTE: If no ignore file is specified, no hosts will be ignored. IGNORE_FILE="/etc/portsentry/portsentry.ignore" # Log file containing all hosts that have triggered an alert # although, not necessarily blocked. # If you don't specify a history file, nothing will be logged # However, the information contained in the history file is still # available in stdout or syslog, as specified with the # --logoutput or -l command line option. If running via systemd, # the log output is also available in the journal. HISTORY_FILE="/var/log/portsentry.log" # When Portsentry is set to mode 1 or 2 (BLOCK_TCP or BLOCK_UDP is set to 1 or 2), then # this file will be used as an internal database to keep track of hosts that have been blocked. # If a host is matched against this file, no action will be taken. This ensures that a host is # only blocked once. # # IMPORTANT: # Leaving the BLOCKED_FILE empty will will cause Portsentry to always block hosts and/or run the KILL_RUN_CMD. # Be very careful leaving the BLOCKED_FILE empty as it could cause Portsentry to spam block commands and run the KILL_RUN_CMD often. # # NOTE: # When portsentry is (re)started, no attempt to "re-block" the hosts in this file will be made. # This means that if you manually remove a blocked host from your system (e.g. using iptables, routes, etc.) # this file will not reflect the current state of your system and hosts which are in this file but # not blocked by the system will not be blocked again until the file is removed. # If you want to re-block all hosts in this file, you will have to remove the file and restart portsentry. # It is highly recommended that the this file is located in a directory that will be cleared on reboot so that # portsentry can start with a clean slate. # # NOTE: # If you want to persist blocked hosts across reboots, you should use an external tool such as fail2ban instead # see the fail2ban directory for configuration files for fail2ban. BLOCKED_FILE="/tmp/portsentry.blocked" ############################### # Misc. Configuration Options # ############################### # # DNS Name resolution - Setting this to "1" will turn on DNS lookups # for attacking hosts. Setting it to "0" (or any other value) will shut # it off. Default is "0". # # NOTE: Using DNS resolution can slow down the response time of Portsentry #RESOLVE_HOST = "0" #################### # Response Options # #################### # # Configure how Portsentry will react to detected connection attempts. # # These options allow you to enable automatic response # options for UDP/TCP. This is useful if you just want # warnings for connections, but don't want to react for # a particular protocol (i.e. you want to block TCP, but # not UDP). To prevent a possible Denial of service attack # against UDP and stealth scan detection for TCP, you may # want to disable blocking, but leave the warning enabled. # # # 0 = Do not block UDP/TCP scans # The detected connection attempt is only logged. This could be useful # for monitoring purposes or use of an external tool, such as fail2ban. # This option is the default. # # NOTE: It is highly recommended to only log connection attempts # (by using this option, BLOCK_TCP="0" and BLOCK_UDP="0") and use an # external tool such as fail2ban to block the attacking host based on # the log file generated by Portsentry. The reason for this is that # other tools are more sophisticated. For example, fail2ban will # persist the blocked host across reboots. # # # 1 = Block UDP/TCP scans. # This option will block the attacking host after the scan is detected # using the technique specified in the KILL_ROUTE and/or KILL_HOSTS_DENY # section below. If KILL_ROUTE is defined, it will run first, followed by # KILL_HOSTS_DENY if it is set. # If the KILL_RUN_CMD option is set, the command will also be executed. # # NOTE: These options are preserved as a legacy option for those who # cannot use an external tool to block the attacking host or has some # specific use-case, where this method is preferred. # # 2 = Run external command only (KILL_RUN_CMD) # This option will only run the external command specified in the KILL_RUN_CMD #BLOCK_TCP="0" #BLOCK_UDP="0" ################### # Dropping Routes # ################### # # The KILL_ROUTE option is used to drop blacklist the attacking host. # This can be done in a number of ways depending on your OS. Below # are some examples of how to drop the route or use firewall # tools to block the host. # # The string $TARGET$ is replaced with the attacking host. # # NOTE:: The route commands are the least optimal way of blocking # and do not provide complete protection against UDP attacks and # will still generate alarms for both UDP and stealth scans. I # always recommend you use a packet filter because they are made # for this purpose. # Generic #KILL_ROUTE="/sbin/route add $TARGET$ 333.444.555.666" # Generic Linux #KILL_ROUTE="/sbin/route add -host $TARGET$ gw 333.444.555.666" # Newer versions of Linux support the reject flag now. This # is cleaner than the above option. #KILL_ROUTE="/sbin/route add -host $TARGET$ reject" # Generic BSD (BSDI, OpenBSD, NetBSD, FreeBSD) #KILL_ROUTE="/sbin/route add $TARGET$ 333.444.555.666" # FreeBSD #KILL_ROUTE="route add -net $TARGET$ -netmask 255.255.255.255 127.0.0.1 -blackhole" # iptables support for Linux #KILL_ROUTE="/usr/local/bin/iptables -I INPUT -s $TARGET$ -j DROP" # nftables support for Linux #KILL_ROUTE="nftables add rule ip filter input ip saddr $TARGET$ drop" # For those of you running FreeBSD (and compatible) firewall #KILL_ROUTE="/sbin/ipfw add 1 deny all from $TARGET$:255.255.255.255 to any" # For those running pf (OpenBSD, etc.) # NOTE THAT YOU NEED TO CHANGE external_interface to a valid interface #KILL_ROUTE="/bin/echo 'block in log on external_interface from $TARGET$/32 to any' | /sbin/pfctl -f -" ################ # TCP Wrappers # ################ # # This text will be dropped into the hosts.deny file for wrappers # to use. There are two formats for TCP wrappers: # # Format One: Old Style - The default when extended host processing # options are not enabled. # #KILL_HOSTS_DENY="ALL: $TARGET$" # Format Two: New Style - The format used when extended option # processing is enabled. You can drop in extended processing # options, but be sure you escape all '%' symbols with a backslash # to prevent problems writing out (i.e. \%c \%h ) # #KILL_HOSTS_DENY="ALL: $TARGET$ : DENY" #################### # External Command # #################### # This is a command that is run when a host connects, it can be whatever # you want it to be (pager, etc.). This command is executed before the # route is dropped or after depending on the KILL_RUN_CMD_FIRST option below # # The KILL_RUN_CMD_FIRST value should be set to "1" to force the command # to run *before* the blocking occurs and should be set to "0" to make the # command run *after* the blocking has occurred. # #KILL_RUN_CMD_FIRST = "0" # #KILL_RUN_CMD="/some/path/here/script $TARGET$ $PORT$" ###################### # Scan trigger value # ###################### # Enter in the number of port connects you will allow before an # alarm is given. The default is 0, which will react immediately. # #SCAN_TRIGGER="0" ####################### # Port Banner Section # ####################### # # If Portsentry is used in "connect" mode (starting Portsentry with the --connect option), # you can specify a banner to be displayed to the connecting host. Once the banner is displayed, # the connection will be closed. # #PORT_BANNER="*** UNAUTHORIZED ACCESS PROHIBITED *** YOUR CONNECTION ATTEMPT HAS BEEN LOGGED." portsentry-2.0.6/examples/portsentry.ignore000066400000000000000000000006261510117642400212310ustar00rootroot00000000000000# Put hosts in here you never want blocked. This includes the IP addresses # of all local interfaces on the protected host (i.e virtual host, mult-home) # # PortSentry can support full netmasks for networks as well. Format is: # # / # # Example: # # 192.168.2.0/24 # 192.168.0.0/16 # 192.168.2.1/32 # Etc. # # If you don't supply a netmask it is assumed to be 32 bits. # 127.0.0.1/32 portsentry-2.0.6/fail2ban/000077500000000000000000000000001510117642400154275ustar00rootroot00000000000000portsentry-2.0.6/fail2ban/README.md000066400000000000000000000003771510117642400167150ustar00rootroot00000000000000# ATTENTION These files are intended for the upcomming 2.0 release of portsentry. They will **not** work with version 1.2. Please refer to the [1.2.x](https://github.com/portsentry/portsentry/tree/v1.2.x/fail2ban) branch for v1.2 versions of these files portsentry-2.0.6/fail2ban/portsentry.conf000066400000000000000000000000661510117642400205310ustar00rootroot00000000000000[Definition] failregex = ^[^ ]* Scan from: \[\] portsentry-2.0.6/fail2ban/portsentry.local000066400000000000000000000004041510117642400206720ustar00rootroot00000000000000[portsentry] enabled = true filter = portsentry maxretry = 0 bantime = 31536000 findtime = 10m logpath = /var/log/portsentry.log banaction = iptables-ipset-proto6-allports action = iptables-ipset-proto6-allports[name=portsentry, chain=INPUT, blocktype=DROP] portsentry-2.0.6/init/000077500000000000000000000000001510117642400147145ustar00rootroot00000000000000portsentry-2.0.6/init/portsentry.service.in000066400000000000000000000004301510117642400211310ustar00rootroot00000000000000[Unit] Description=Portsentry Documentation=man:portsentry(8) Wants=network-online.target After=network-online.target [Service] Type=simple ExecStart=@CMAKE_INSTALL_FULL_SBINDIR@/portsentry Restart=on-failure RestartSec=1 PrivateDevices=yes [Install] WantedBy=multi-user.target portsentry-2.0.6/man/000077500000000000000000000000001510117642400145245ustar00rootroot00000000000000portsentry-2.0.6/man/portsentry.8000066400000000000000000000153141510117642400170520ustar00rootroot00000000000000.\" Automatically generated by Pandoc 3.1.11.1 .\" .TH "portsentry" "8" "" "" "System Manager\[cq]s Manual" .SH NAME \f[B]portsentry\f[R] \- Detect and respond to port scans against a target host in real\-time .SH SYNOPSIS \f[B]portsentry\f[R] [options] .SH DESCRIPTION Portsentry does three main things: .IP \[bu] 2 It listens to TCP and/or UDP ports you specify. .IP \[bu] 2 It stealthily (or visibly) logs connection attempts to the ports you have specified. .IP \[bu] 2 It can optionally execute scripts or applications when connection attempts are made. .PP The most common use\-case for Portsentry is to block unwanted service enumeration attempts against your host. This could be accomplished by simply listening to a wide variety of \f[B]unused\f[R] ports and block all connection attempts to those ports. Portsentry can also be deployed as a Network Intrusion Detection System (NIDS). By listening to unused ports on your internal networks, you will be notified as soon as a potential attacker tries to scan for services within your organization. A more detailed explanation and guide of the various uses of portsentry, refer to the \c .UR https://github.com/portsentry/portsentry/blob/master/docs/HOWTO-Use.md HOWTO\-Use .UE \c \ guide. .SH OPTIONS .SS \-\-stealth Stealth mode \f[B](default)\f[R] uses libpcap (or raw sockets on Linux if desired, see the \f[B]\-m\f[R] option) in order to quietly listen for incoming packets on the network. The main advantage of Stealth mode is that the system gives off no indication that it is listening for incoming packets making it very difficult (if not impossible) for an attacker to detect that Portsentry is running. .SS \-\-connect Connect mode \f[B](legacy option)\f[R] uses the kernel socket API to listen for incoming packets. Connect mode is considered a legacy mode and is mainly preserved for users with very specific use\-cases. For example, connect mode can be used to add a \[lq]port banner\[rq], thus sending a message to anyone connecting to a specified port. Be aware however that connect mode comes with several additional security implications which must be considered. For example: .IP \[bu] 2 When monitoring TCP ports, Portsentry will require a three\-way TCP handshake to be completed before Portsentry registers the connection attempt. Thus, a \[lq]stealth scan attack\[rq] will go unnoticed by Portsentry. .IP \[bu] 2 Additionally, other TCP protocol attacks, such as SYN floods must be taken into consideration when using Connect Mode. .IP \[bu] 2 Connect mode will require Portsentry to bind to each port to be monitored individually. If you are monitoring a large number of ports you could potentially hit the max number of file descriptors allowed by the system and could also lead to performance issues. Most modern systems will allow you to increase the number of max opened file descriptors, but this is something to be aware of. .SS Stealth Mode Options This section covers options only relevant when Stealth mode \f[B]\-\-stealth\f[R] is used. .SS \-m, \-\-method=pcap|raw \f[B]This option is only relevant on Linux\f[R]. It sets the sentry method to use in stealth mode. Can be set to use either \f[B]pcap\f[R] or Linux \f[B]raw\f[R] sockets. \f[B](default: pcap)\f[R] .IP \[bu] 2 \f[B]pcap\f[R]: Uses the libpcap library to listen for incoming packets. This is the default method and is recommended for most use\-cases. .IP \[bu] 2 \f[B]raw\f[R]: Uses the Linux raw socket API to listen for incoming packets. This method is less efficient than pcap and is not recommended unless you have a specific use\-case where pcap is not available or not desired. .SS \-i, \-\-interface=ALL|ALL_NLO| \f[B]This option is only relevant when pcap mode is used\f[R]. Specify interface(s) to listen on. You can either specify an \[lq]interface alias or a specific interface: .IP \[bu] 2 \f[CR]ALL\f[R] \- Listen on all interfaces (including the loopback interface) (Alias) .IP \[bu] 2 \f[CR]ALL_NLO\f[R] \- Listen on all interfaces except the loopback interface (Alias) .IP \[bu] 2 \f[CR]any\f[R] \- This is a special \[lq]interface\[rq] option, built\-in to libpcap. The libpcap library will attempt to listen to \[lq]all\[rq] interfaces except some special interfaces when using this option. .IP \[bu] 2 \f[CR]\f[R] \- Listen on the specified interface. NOTE: You can specify multiple interfaces by using multiple \f[CR]\-\-interface\f[R] switches, e.g.\ \f[CR]\-\-interface eth0 \-\-interface eth1\f[R] .SS Generic Options These options can be used regardless of mode used. .SS \-L, \-\-disable\-local\-check Under normal operations; if Portsentry detects traffic with the same source and destination IP address, no logging or actions are performed. This is to prevent Portsentry from potentially taking actions on itself. This option disables this logic. I.e, logging and actions are taken on the host on which Portsentry is run. Use this option with care. .SS \-l, \-\-logoutput=stdout|syslog Portsentry can log to either \f[CR]stdout\f[R] or \f[CR]syslog\f[R]. The log output can be set using the \f[CR]\-\-logoutput\f[R] (or \f[CR]\-l\f[R]) command line option. The default log output is \f[CR]stdout\f[R]. .SS \-c, \-\-configfile=path Portsentry can be configured using an alternative path for the configuration file. The configuration file can be set using the \f[CR]\-\-configfile\f[R] (or \f[CR]\-c\f[R]) command line option. The default configuration file is \f[CR]/etc/portsentry/portsentry.conf\f[R]. See portsentry.conf(8) for more information. .SS \-D, \-\-daemon Portsentry can be run as a daemon using the \f[CR]\-\-daemon\f[R] (or \f[CR]\-D\f[R]) command line option. This will cause Portsentry to fork into the background and run as a daemon. By default portsentry runs in the foreground. .SS \-d, \-\-debug Enable debug output using the \f[CR]\-\-debug\f[R] (or \f[CR]\-d\f[R]) command line option. This will cause Portsentry to become very noisy. .SS \-v, \-\-verbose Enable verbose output using the \f[CR]\-\-verbose\f[R] (or \f[CR]\-v\f[R]) command line option. This will cause Portsentry to log additional information to the log output. .SS \-h, \-\-help Display command line help message .SS \-V, \-\-version Display version information .SS EXAMPLES Review the \c .UR https://github.com/portsentry/portsentry/blob/master/docs/HOWTO-Use.md HOWTO\-Use .UE \c \ guide for detailed setup scenarios and configuration guides. .SS FILES /etc/portsentry/portsentry.conf .PP /etc/portsentry/portsentry.ignore .PP /var/log/portsentry.log .PP /tmp/portsentry.blocked .SS BUGS All bugs should be reported via the portsentry github issue tracker https://github.com/portsentry/portsentry/issues .SS AUTHORS Marcus Hufvudsson \c .MT mh@protohuf.com .ME \c .SS SEE ALSO portsentry.conf(8) .SS LICENSE Portsentry is licensed under the BSD\-2\-Clause license. portsentry-2.0.6/man/portsentry.conf.8000066400000000000000000000136741510117642400200050ustar00rootroot00000000000000.\" Automatically generated by Pandoc 3.1.11.1 .\" .TH "portsentry.conf" "8" "" "" "System Manager\[cq]s Manual" .SH NAME \f[B]portsentry.conf\f[R] \- Configuration file for Portsentry .SH DESCRIPTION The portsentry.conf file is used to configure Portsentry. The default location for this file is \f[B]/etc/portsentry/portsentry.conf\f[R]. The configuration file is read by Portsentry at startup. The configuration file is a simple text file with one option per line. Lines starting with a \f[B]#\f[R] are considered comments and are ignored. .PP There is an example configuration file included with portsentry and available at \c .UR https://github.com/portsentry/portsentry/blob/master/examples/portsentry.conf Github .UE \c \ with additional comments, examples and recommendations for how to setup Portsentry. .SH OPTIONS .SS TCP_PORTS=\[lq]\[rq] A comma separated list of TCP ports you want to monitor. You can specify inclusive port ranges with a dash, e.g, 10\-20 to include port 10 to 20. .SS Example TCP_PORTS=\[lq]10,20,30\-40\[rq] .SS UDP_PORTS=\[lq]\[rq] A comma separated list of UDP ports you want to monitor. You can specify inclusive port ranges with a dash, e.g, 10\-20 to include port 10 to 20. .SS Example UDP_PORTS=\[lq]10,20,30\-40\[rq] .SS IGNORE_FILE=\[lq]\[rq] Path to a file with IP addresses to ignore when logging and taking actions. Any IP/CIDR address in this file will be ignored. This is useful for ignoring local traffic or other trusted hosts. .SS HISTORY_FILE=\[lq]\[rq] This file contains all the IP addresses that have triggered the Portsentry detection engine in some way. See the \c .UR https://github.com/portsentry/portsentry/blob/master/docs/HOWTO-Logfile.md HOWTO\-Logfile .UE \c \ for more information on how to read this file. .SS BLOCKED_FILE=\[lq]/tmp/portsentry.blocked\[rq] When Portsentry\[cq]s action mechanism is used (BLOCK_TCP and/or BLOCK_UDP is set to 1 or 2, see below), this file will contain a list of all hosts that triggers an action. If a host is matched against this file no further action will be taken. Leaving this field as an empty string \[lq]\[rq] or not setting it will cause the action mechanism to always trigger when a scan is detected. .SS RESOLVE_HOST = \[lq]<1|0>\[rq] DNS Name resolution \- Setting this to \[lq]1\[rq] will turn on DNS lookups for attacking hosts. Setting it to \[lq]0\[rq] (or any other value) will not perform DNS lookups. Default is \[lq]0\[rq]. .PP NOTE: Using DNS resolution can slow down the response time of Portsentry .SS BLOCK_TCP=\[lq]0|1|2\[rq] and BLOCK_UDP=\[lq]0|1|2\[rq] This option controls how Portsentry reacts to a detected connection attempt. .PP 0 = Do not block UDP/TCP scans. The detected connection attempt is only logged. This could be useful for monitoring purposes or use of an external tool, such as fail2ban. This option is the default. .PP NOTE: It is highly recommended to only log connection attempts (by using: BLOCK_TCP=\[lq]0\[rq] and BLOCK_UDP=\[lq]0\[rq]) and use an external tool such as fail2ban to block the attacking host based on the log file generated by Portsentry. The reason for this is that other tools are more sophisticated. For example, fail2ban will persist the blocked host across reboots. .PP 1 = Block UDP/TCP scans. This option will block the attacking host after the scan is detected using the technique specified in the KILL_ROUTE and/or KILL_HOSTS_DENY section below. If KILL_ROUTE is defined, it will run first, followed by KILL_HOSTS_DENY if it is set. If the KILL_RUN_CMD option is set, the command will also be executed. .PP NOTE: These options are preserved as a legacy option for those who cannot use an external tool to block the attacking host or has some specific use\-case, where this method is preferred. .PP 2 = Run external command only (KILL_RUN_CMD). This option will only run the external command specified in the KILL_RUN_CMD .SS KILL_ROUTE=\[lq]\[rq] The KILL_ROUTE option is used to drop blacklist the attacking host. This can be done in a number of ways depending on your OS. The string \f[I]T\f[R]\f[I]A\f[R]\f[I]R\f[R]\f[I]G\f[R]\f[I]E\f[R]\f[I]T\f[R] is replaced with the attacking host. .SS Example KILL_ROUTE=\[lq]/usr/local/bin/iptables \-I INPUT \-s \f[I]T\f[R]\f[I]A\f[R]\f[I]R\f[R]\f[I]G\f[R]\f[I]E\f[R]\f[I]T\f[R] \-j DROP\[rq] .SS KILL_HOSTS_DENY=\[lq]\[rq] This text will be dropped into the hosts.deny file for wrappers to use. .SS Example KILL_HOSTS_DENY=\[lq]ALL: \f[I]T\f[R]\f[I]A\f[R]\f[I]R\f[R]\f[I]G\f[R]\f[I]E\f[R]\f[I]T\f[R]\[rq] .SS KILL_RUN_CMD=\[lq]/some/path/here/script \f[I]T\f[R]\f[I]A\f[R]\f[I]R\f[R]\f[I]G\f[R]\f[I]E\f[R]\f[I]T\f[R] \f[I]P\f[R]\f[I]O\f[R]\f[I]R\f[R]\f[I]T\f[R]\[rq] This is a command that can be run when Portsentry is triggered, it can be whatever you want it to be. .SS KILL_RUN_CMD_FIRST = \[lq]0|1\[rq] The KILL_RUN_CMD_FIRST value should be set to \[lq]1\[rq] to force the command to run \f[I]before\f[R] the KILL_ROUTE/KILL_HOSTS_DENY is executed and should be set to \[lq]0\[rq] to make the command run \f[I]after\f[R] the blocking has occurred. .SS SCAN_TRIGGER=\[lq]0\[rq] Enter the number of port connects from the same host you will allow before an alarm is given. The default is 0, which will react immediately. .SS PORT_BANNER=\[lq]*** UNAUTHORIZED ACCESS PROHIBITED *** YOUR CONNECTION ATTEMPT HAS BEEN LOGGED.\[rq] When Portsentry is used in \[lq]connect\[rq] mode [Legacy option] you can specify a banner to be displayed to the connecting host. Once the banner is displayed, the connection will be closed. .SS EXAMPLES View the example configuration file \c .UR https://github.com/portsentry/portsentry/blob/master/examples/portsentry.conf portsentry.conf .UE \c .SS FILES /etc/portsentry/portsentry.conf .SS BUGS All bugs should be reported via the portsentry github issue tracker https://github.com/portsentry/portsentry/issues .SS AUTHORS Marcus Hufvudsson \c .MT mh@protohuf.com .ME \c .SS SEE ALSO portsentry(8) .SS LICENSE Portsentry is licensed under the BSD\-2\-Clause license. portsentry-2.0.6/portcon/000077500000000000000000000000001510117642400154355ustar00rootroot00000000000000portsentry-2.0.6/portcon/main.c000066400000000000000000000046341510117642400165340ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause /* This is a simple program used in the system testing framework (see the system_test directory) */ #include #include #include #include #include #include #include #include #define BUF_SIZE 1024 int main(int argc, char **argv) { int protocol, sock; ssize_t result; uint16_t port; char buf[BUF_SIZE]; struct sockaddr_in addr; socklen_t addr_len = sizeof(addr); struct pollfd fds; if (argc < 3) { printf("Usage: %s \n", argv[0]); return 1; } port = (uint16_t)atoi(argv[1]); // yolo if (strncmp("tcp", argv[2], 3) == 0) { protocol = IPPROTO_TCP; } else if (strncmp("udp", argv[2], 3) == 0) { protocol = IPPROTO_UDP; } else { printf("Invalid protocol: %s\n", argv[2]); return 1; } if ((sock = socket(AF_INET, (protocol == IPPROTO_TCP) ? SOCK_STREAM : SOCK_DGRAM, 0)) == -1) { perror("socket"); return 1; } addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (protocol == IPPROTO_TCP) { if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { perror("connect"); return 1; } fds.fd = sock; fds.events = POLLIN; fds.revents = 0; result = poll(&fds, 1, 5000); if (result == -1) { perror("poll"); return 1; } else if (result == 0) { printf("Timeout\n"); return 1; } if (fds.revents & POLLIN) { if ((result = read(sock, buf, BUF_SIZE)) == -1) { perror("read"); return 1; } } else { printf("No POLLIN event\n"); return 1; } } else { sendto(sock, "Hello", 5, 0, (struct sockaddr *)&addr, sizeof(addr)); fds.fd = sock; fds.events = POLLIN; fds.revents = 0; result = poll(&fds, 1, 5000); if (result == -1) { perror("poll"); return 1; } else if (result == 0) { printf("Timeout\n"); return 1; } if (fds.revents & POLLIN) { if ((result = recvfrom(sock, buf, BUF_SIZE, 0, (struct sockaddr *)&addr, &addr_len)) == -1) { perror("recvfrom"); return 1; } } else { printf("No POLLIN event\n"); return 1; } } buf[result] = '\0'; printf("%s\n", buf); return 0; } portsentry-2.0.6/scripts/000077500000000000000000000000001510117642400154405ustar00rootroot00000000000000portsentry-2.0.6/scripts/install.sh000077500000000000000000000032721510117642400174510ustar00rootroot00000000000000#!/usr/bin/env sh PATH_BIN=${PATH_BIN:-/usr/local/sbin} PATH_MAN=${PATH_MAN:-/usr/share/man/man8} PATH_DOC=${PATH_DOC:-/usr/share/doc/portsentry} PATH_ETC=${PATH_ETC:-/etc/portsentry} PATH_SHARE=${PATH_SHARE:-/usr/local/share/portsentry} if [ "$(id -u)" != "0" ]; then echo "This script must be run as root" 1>&2 exit 1 fi # Install binary install -m 755 -d $PATH_BIN install -m 755 portsentry $PATH_BIN/portsentry # Install man pages install -m 644 -d $PATH_MAN install -m 644 docs/portsentry.8 $PATH_MAN/portsentry.8 install -m 644 docs/portsentry.conf.8 $PATH_MAN/portsentry.conf.8 # Install documentation install -m 644 -d $PATH_DOC cp -rf docs $PATH_DOC chown -R root:root $PATH_DOC chmod -R 644 $PATH_DOC # Install config files install -m 755 -d $PATH_ETC install -m 644 -o root -g root examples/portsentry.conf $PATH_ETC/portsentry.conf install -m 644 -o root -g root examples/portsentry.ignore $PATH_ETC/portsentry.ignore if [ -d /etc/logrotate.d ]; then install -m 644 -o root -g root examples/logrotate.conf /etc/logrotate.d/portsentry fi # Install fail2ban config files install -m 644 -d $PATH_SHARE install -m 644 -d $PATH_SHARE/fail2ban install -m 644 fail2ban/portsentry.conf $PATH_SHARE/fail2ban/portsentry.conf install -m 644 fail2ban/portsentry.local $PATH_SHARE/fail2ban/portsentry.local install -m 644 Changes.md $PATH_SHARE/Changes.md install -m 644 README.md $PATH_SHARE/README.md install -m 644 LICENSE $PATH_SHARE/LICENSE # Install systemd service file if [ -d /usr/lib/systemd/system ]; then sed -i "s|^ExecStart=.*|ExecStart=$PATH_BIN/portsentry|g" init/portsentry.service install -m 644 -o root -g root init/portsentry.service /usr/lib/systemd/system/portsentry.service fi portsentry-2.0.6/scripts/uninstall.sh000077500000000000000000000012061510117642400200070ustar00rootroot00000000000000#!/usr/bin/env sh PATH_BIN=${PATH_BIN:-/usr/local/sbin} PATH_MAN=${PATH_MAN:-/usr/share/man/man8} PATH_DOC=${PATH_DOC:-/usr/share/doc/portsentry} PATH_ETC=${PATH_ETC:-/etc/portsentry} PATH_SHARE=${PATH_SHARE:-/usr/local/share/portsentry} if [ "$(id -u)" != "0" ]; then echo "This script must be run as root" 1>&2 exit 1 fi rm -f $PATH_BIN/portsentry rm -f $PATH_MAN/portsentry.8 rm -f $PATH_MAN/portsentry.conf.8 rm -rf $PATH_DOC rm -rf $PATH_ETC if [ -f /etc/logrotate.d ]; then rm -f /etc/logrotate.d/portsentry fi rm -rf $PATH_SHARE if [ -d /usr/lib/systemd/system ]; then rm -f /usr/lib/systemd/system/portsentry.service fi portsentry-2.0.6/shell.nix000066400000000000000000000005651510117642400156060ustar00rootroot00000000000000let nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-24.05"; pkgs = import nixpkgs { config = {}; overlays = []; }; in pkgs.mkShellNoCC { packages = with pkgs; [ codeql semgrep netcat-gnu clang cmake libpcap ]; GREETING = "Welcome to the Portsentry dev environment!"; shellHook = '' echo $GREETING ''; } portsentry-2.0.6/src/000077500000000000000000000000001510117642400145405ustar00rootroot00000000000000portsentry-2.0.6/src/block.c000066400000000000000000000204251510117642400160010ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include "block.h" #include "portsentry.h" #include "io.h" #include "util.h" #include "config_data.h" static void FreeBlockedNodeList(struct BlockedNode *node); static struct BlockedNode *AddBlockedNode(struct BlockedState *bs, const struct sockaddr *address); static int RemoveBlockedNode(struct BlockedState *bs, const struct BlockedNode *node); static int WriteAddressToBlockFile(FILE *fp, const struct sockaddr_in6 *addr); int IsBlocked(const struct sockaddr *address, const struct BlockedState *bs) { struct BlockedNode *node; assert(address != NULL); assert(bs != NULL); if (bs == NULL) { return FALSE; } node = bs->head; while (node != NULL) { if (address->sa_family == AF_INET && node->address.sin6_family == AF_INET) { const struct sockaddr_in *target = (const struct sockaddr_in *)address; struct sockaddr_in *current = (struct sockaddr_in *)&node->address; if (memcmp(¤t->sin_addr.s_addr, &target->sin_addr.s_addr, sizeof(target->sin_addr.s_addr)) == 0) { return TRUE; } } else if (address->sa_family == AF_INET6 && node->address.sin6_family == AF_INET6) { const struct sockaddr_in6 *target = (const struct sockaddr_in6 *)address; struct sockaddr_in6 *current = (struct sockaddr_in6 *)&node->address; if (memcmp(¤t->sin6_addr, &target->sin6_addr, sizeof(target->sin6_addr)) == 0) { return TRUE; } } node = node->next; } return FALSE; } /* Initialize the BlockedState structure by reading the blocked file. * returns: * TRUE: Success * FALSE: Potentially partial success, but the structure is not fully initialized but usable * ERROR: Failure, unrecoverable error */ int BlockedStateInit(struct BlockedState *bs) { int status = ERROR; FILE *fp = NULL; char err[ERRNOMAXBUF]; sa_family_t family; struct sockaddr_in6 sa; // Use the larger sockaddr_in6 to hold both IPv4 and IPv6 addresses. Otherwise _FORTIFY_SOURCE=2 will erroneously complain in AddBlockedNode assert(bs != NULL); memset(bs, 0, sizeof(struct BlockedState)); if ((fp = fopen(configData.blockedFile, "r")) == NULL) { Error("Cannot open blocked file: %s for reading: %s", configData.blockedFile, ErrnoString(err, sizeof(err))); goto exit; } while (TRUE) { memset(&sa, 0, sizeof(sa)); if (fread(&family, sizeof(family), 1, fp) != 1) { if (feof(fp)) { break; } Error("Unable to read address family from blocked file: %s", configData.blockedFile); status = FALSE; goto exit; } if (family == AF_INET) { struct sockaddr_in *sa4 = (struct sockaddr_in *)&sa; if (fread(&sa4->sin_addr.s_addr, sizeof(sa4->sin_addr.s_addr), 1, fp) != 1) { Error("Unable to read address from blocked file: %s", configData.blockedFile); status = FALSE; goto exit; } sa.sin6_family = family; AddBlockedNode(bs, (struct sockaddr *)&sa); } else if (family == AF_INET6) { if (fread(&sa.sin6_addr, sizeof(sa.sin6_addr), 1, fp) != 1) { Error("Unable to read address from blocked file: %s", configData.blockedFile); status = FALSE; goto exit; } sa.sin6_family = family; AddBlockedNode(bs, (struct sockaddr *)&sa); } else { Error("Unsupported address family: %d", family); status = FALSE; goto exit; } } status = TRUE; exit: if (status == TRUE || status == FALSE) { bs->isInitialized = TRUE; } if (fp != NULL) { fclose(fp); } if (status == ERROR) { BlockedStateFree(bs); } return status; } void BlockedStateFree(struct BlockedState *bs) { if (bs->isInitialized == FALSE) { return; } FreeBlockedNodeList(bs->head); memset(bs, 0, sizeof(struct BlockedState)); bs->isInitialized = FALSE; } int WriteBlockedFile(const struct sockaddr *address, struct BlockedState *bs) { int status = ERROR; FILE *fp = NULL; struct BlockedNode *node = NULL; char err[ERRNOMAXBUF]; assert(address != NULL); assert(bs != NULL); assert(address->sa_family == AF_INET || address->sa_family == AF_INET6); if ((fp = fopen(configData.blockedFile, "a")) == NULL) { Error("Unable to open blocked file: %s for writing: %s", configData.blockedFile, ErrnoString(err, sizeof(err))); goto exit; } if ((node = AddBlockedNode(bs, address)) == NULL) { Error("Unable to add blocked node"); goto exit; } // Ignore file write errors. Atlreast the addr is in memory and will be ignored in this session. // The function will report any errors to the log. WriteAddressToBlockFile(fp, (const struct sockaddr_in6 *)address); status = TRUE; exit: if (fp != NULL) { fclose(fp); } if (status != TRUE && node != NULL) { RemoveBlockedNode(bs, node); } return status; } int RewriteBlockedFile(const struct BlockedState *bs) { int status = ERROR; FILE *fp = NULL; struct BlockedNode *node = NULL; char err[ERRNOMAXBUF]; assert(bs != NULL); if (bs == NULL || bs->isInitialized == FALSE || bs->head == NULL) { return FALSE; } if ((fp = fopen(configData.blockedFile, "w")) == NULL) { Error("Unable to open blocked file: %s for writing: %s", configData.blockedFile, ErrnoString(err, sizeof(err))); goto exit; } node = bs->head; while (node != NULL) { if (WriteAddressToBlockFile(fp, &node->address) == ERROR) { goto exit; } node = node->next; } status = TRUE; exit: if (fp != NULL) { fclose(fp); } return status; } static void FreeBlockedNodeList(struct BlockedNode *node) { if (node == NULL) { return; } FreeBlockedNodeList(node->next); free(node); } static struct BlockedNode *AddBlockedNode(struct BlockedState *bs, const struct sockaddr *address) { struct BlockedNode *node = NULL; assert(bs != NULL); assert(address != NULL); assert(address->sa_family == AF_INET || address->sa_family == AF_INET6); if ((node = calloc(1, sizeof(struct BlockedNode))) == NULL) { Error("Unable to allocate memory for blocked node"); return NULL; } if (address->sa_family == AF_INET) { const struct sockaddr_in *addr = (const struct sockaddr_in *)address; memcpy(&node->address, addr, sizeof(struct sockaddr_in)); } else if (address->sa_family == AF_INET6) { const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)address; memcpy(&node->address, addr, sizeof(struct sockaddr_in6)); } else { free(node); return NULL; } node->next = bs->head; bs->head = node; return node; } static int RemoveBlockedNode(struct BlockedState *bs, const struct BlockedNode *node) { struct BlockedNode *prev = NULL; struct BlockedNode *current = bs->head; if (node == NULL) { return FALSE; } while (current != NULL) { if (current == node) { if (prev == NULL) { bs->head = current->next; } else { prev->next = current->next; } free(current); return TRUE; } prev = current; current = current->next; } return FALSE; } static int WriteAddressToBlockFile(FILE *fp, const struct sockaddr_in6 *addr) { assert(fp != NULL); assert(addr != NULL); if (fp == NULL || addr == NULL || (addr->sin6_family != AF_INET && addr->sin6_family != AF_INET6)) { return FALSE; } if (addr->sin6_family == AF_INET) { const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr; if (fwrite(&addr4->sin_family, sizeof(addr4->sin_family), 1, fp) != 1) { Error("Unable to write sin_family to blocked file: %s", configData.blockedFile); return ERROR; } if (fwrite(&addr4->sin_addr.s_addr, sizeof(addr4->sin_addr.s_addr), 1, fp) != 1) { Error("Unable to write sin_addr to blocked file: %s", configData.blockedFile); return ERROR; } } else if (addr->sin6_family == AF_INET6) { if (fwrite(&addr->sin6_family, sizeof(addr->sin6_family), 1, fp) != 1) { Error("Unable to write sin6_family to blocked file: %s", configData.blockedFile); return ERROR; } if (fwrite(&addr->sin6_addr, sizeof(addr->sin6_addr), 1, fp) != 1) { Error("Unable to write sin6_addr to blocked file: %s", configData.blockedFile); return ERROR; } } return TRUE; } portsentry-2.0.6/src/block.h000066400000000000000000000013501510117642400160020ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include #include #include struct BlockedNode { struct sockaddr_in6 address; // Will be casred to sockaddr_in or sockaddr_in6 depending on address family struct BlockedNode *next; }; struct BlockedState { uint8_t isInitialized; struct BlockedNode *head; }; int WriteBlockedFile(const struct sockaddr *address, struct BlockedState *bs); int IsBlocked(const struct sockaddr *address, const struct BlockedState *bs); int BlockedStateInit(struct BlockedState *bs); void BlockedStateFree(struct BlockedState *bs); int RewriteBlockedFile(const struct BlockedState *bs); portsentry-2.0.6/src/cmdline.c000066400000000000000000000143621510117642400163250ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include "cmdline.h" #include "config.h" #include "config_data.h" #include "io.h" #include "portsentry.h" #include "util.h" #ifndef GIT_COMMIT_HASH #define GIT_COMMIT_HASH "-" #endif #define CMDLINE_CONNECT 0 #define CMDLINE_STEALTH 1 #define CMDLINE_LOGOUTPUT 'l' #define CMDLINE_CONFIGFILE 'c' #define CMDLINE_DAEMON 'D' #define CMDLINE_DEBUG 'd' #define CMDLINE_VERBOSE 'v' #define CMDLINE_HELP 'h' #define CMDLINE_VERSION 'V' #define CMDLINE_INTERFACE 'i' #define CMDLINE_METHOD 'm' #define CMDLINE_DISABLE_LOCAL_CHECK 'L' static void Usage(void); void ParseCmdline(const int argc, char **argv) { int opt; uint8_t ifFlagAll = FALSE, ifFlagNlo = FALSE, ifFlagOther = FALSE, flagModeSet = FALSE; struct ConfigData cmdlineConfig; const struct option long_options[] = { {"connect", no_argument, 0, CMDLINE_CONNECT}, {"stealth", no_argument, 0, CMDLINE_STEALTH}, #ifdef USE_PCAP {"interface", required_argument, 0, CMDLINE_INTERFACE}, #endif {"logoutput", required_argument, 0, CMDLINE_LOGOUTPUT}, {"configfile", required_argument, 0, CMDLINE_CONFIGFILE}, {"daemon", no_argument, 0, CMDLINE_DAEMON}, {"method", required_argument, 0, CMDLINE_METHOD}, {"disable-local-check", no_argument, 0, CMDLINE_DISABLE_LOCAL_CHECK}, {"debug", no_argument, 0, CMDLINE_DEBUG}, {"verbose", no_argument, 0, CMDLINE_VERBOSE}, {"help", no_argument, 0, CMDLINE_HELP}, {"version", no_argument, 0, CMDLINE_VERSION}, {0, 0, 0, 0}}; ResetConfigData(&cmdlineConfig); while (1) { int option_index = 0; opt = getopt_long(argc, argv, "l:c:t:s:a:u:i:m:DLdvhV", long_options, &option_index); if (opt >= CMDLINE_CONNECT && opt <= CMDLINE_STEALTH && flagModeSet == TRUE) { fprintf(stderr, "Error: Only one mode can be specified, Use only one of --stealth or --connect\n"); Exit(EXIT_FAILURE); } else if (opt == -1) { break; } switch (opt) { case CMDLINE_CONNECT: cmdlineConfig.sentryMode = SENTRY_MODE_CONNECT; flagModeSet = TRUE; break; case CMDLINE_STEALTH: cmdlineConfig.sentryMode = SENTRY_MODE_STEALTH; flagModeSet = TRUE; break; case CMDLINE_INTERFACE: if (strncmp(optarg, "ALL", 5) == 0) { ifFlagAll = TRUE; } else if (strncmp(optarg, "ALL_NLO", 9) == 0) { ifFlagNlo = TRUE; } else { ifFlagOther = TRUE; } if ((ifFlagAll && ifFlagNlo) || (ifFlagNlo && ifFlagOther) || (ifFlagOther && ifFlagAll)) { fprintf(stderr, "Error: Only one interface type can be specified (ALL, ALL_NLO or interfaces)\n"); Exit(EXIT_FAILURE); } AddInterface(&cmdlineConfig, optarg); break; case CMDLINE_LOGOUTPUT: if (strcmp(optarg, "stdout") == 0) { cmdlineConfig.logFlags |= LOGFLAG_OUTPUT_STDOUT; } else if (strcmp(optarg, "syslog") == 0) { cmdlineConfig.logFlags |= LOGFLAG_OUTPUT_SYSLOG; } else { fprintf(stderr, "Error: Invalid log output specified\n"); Exit(EXIT_FAILURE); } break; case CMDLINE_CONFIGFILE: if (strlen(optarg) >= (sizeof(cmdlineConfig.configFile) - 1)) { fprintf(stderr, "Error: Config file path too long\n"); Exit(EXIT_FAILURE); } SafeStrncpy(cmdlineConfig.configFile, optarg, sizeof(cmdlineConfig.configFile)); break; case CMDLINE_METHOD: if (strncmp(optarg, "raw", 3) == 0) { cmdlineConfig.sentryMethod = SENTRY_METHOD_RAW; #ifdef USE_PCAP } else if (strncmp(optarg, "pcap", 4) == 0) { cmdlineConfig.sentryMethod = SENTRY_METHOD_PCAP; #endif } else { fprintf(stderr, "Error: Invalid sentry method specified\n"); Exit(EXIT_FAILURE); } break; case CMDLINE_DISABLE_LOCAL_CHECK: cmdlineConfig.disableLocalCheck = TRUE; break; case CMDLINE_DAEMON: cmdlineConfig.daemon = TRUE; break; case CMDLINE_DEBUG: cmdlineConfig.logFlags |= LOGFLAG_DEBUG; break; case CMDLINE_VERBOSE: cmdlineConfig.logFlags |= LOGFLAG_VERBOSE; break; case CMDLINE_HELP: Usage(); break; case CMDLINE_VERSION: exit(EXIT_SUCCESS); break; default: printf("Unknown argument, getopt returned character code 0%o\n", opt); Exit(EXIT_FAILURE); break; } } #ifdef BSD if (cmdlineConfig.sentryMethod == SENTRY_METHOD_RAW) { fprintf(stderr, "Error: Raw sockets not supported on BSD\n"); Exit(EXIT_FAILURE); } #endif PostProcessConfig(&cmdlineConfig); if (cmdlineConfig.logFlags & LOGFLAG_DEBUG) { printf("debug: Command Line Configuration:\n"); PrintConfigData(cmdlineConfig); } // Set the global config to the values gotten from the command line memcpy(&configData, &cmdlineConfig, sizeof(struct ConfigData)); } static void Usage(void) { printf("Portsentry - Port Scan Detector.\n"); printf("Usage: portsentry [--stealth, --connect] \n\n"); printf("--stealth\tUse Stealth mode (default)\n"); printf("--connect\tUse Connect mode\n"); #ifdef USE_PCAP printf("--interface, -i - Set interface to listen on. Use ALL for all interfaces, ALL_NLO for all interfaces except loopback (default: ALL_NLO)\n"); #endif printf("--logoutput, -l [stdout|syslog] - Set Log output (default to stdout)\n"); printf("--configfile, -c - Set config file path\n"); printf("--method, -m\t[pcap|raw] - Set sentry method to use the stealth mode. Use libpcap or linux raw sockets (only available on linux) (default: pcap)\n"); printf("--disable-local-check, -L\tIf source and destination address are the same we don't do any actions. This option disables this check\n"); printf("--daemon, -D\tRun as a daemon\n"); printf("--debug, -d\tEnable debugging output\n"); printf("--verbose, -v\tEnable verbose output\n"); printf("--help, -h\tDisplay this help message\n"); printf("--version, -V\tDisplay version information\n"); Exit(EXIT_SUCCESS); } void Version(void) { printf("Portsentry %d.%d.%d (%s)\n", PORTSENTRY_VERSION_MAJOR, PORTSENTRY_VERSION_MINOR, PORTSENTRY_VERSION_PATCH, GIT_COMMIT_HASH); } portsentry-2.0.6/src/cmdline.h000066400000000000000000000003031510117642400163200ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once void ParseCmdline(const int argc, char **argv); void Version(void); portsentry-2.0.6/src/config_data.c000066400000000000000000000135071510117642400171500ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include "config.h" #include "config_data.h" #include "io.h" #include "port.h" #include "portsentry.h" #include "util.h" // Define the constants that are declared as extern in the header const uint8_t LOGFLAG_NONE = 0x00; const uint8_t LOGFLAG_DEBUG = 0x1; const uint8_t LOGFLAG_VERBOSE = 0x2; const uint8_t LOGFLAG_OUTPUT_STDOUT = 0x4; const uint8_t LOGFLAG_OUTPUT_SYSLOG = 0x8; struct ConfigData configData; static char *GetSentryMethodString(const enum SentryMethod sentryMethod); void ResetConfigData(struct ConfigData *cd) { memset(cd, 0, sizeof(struct ConfigData)); #ifndef USE_PCAP cd->sentryMethod = SENTRY_METHOD_RAW; #endif } void PostProcessConfig(struct ConfigData *cd) { // If no log output is specified, default to stdout if ((cd->logFlags & LOGFLAG_OUTPUT_STDOUT) == 0 && (cd->logFlags & LOGFLAG_OUTPUT_SYSLOG) == 0) { cd->logFlags |= LOGFLAG_OUTPUT_STDOUT; } if (cd->daemon == TRUE) { cd->logFlags &= (uint8_t)~LOGFLAG_OUTPUT_STDOUT; cd->logFlags |= LOGFLAG_OUTPUT_SYSLOG; } if (strlen(cd->configFile) == 0) { if (strlen(CONFIG_FILE) > (sizeof(cd->configFile) - 1)) { fprintf(stderr, "Error: Config file path too long\n"); Exit(EXIT_FAILURE); } SafeStrncpy(cd->configFile, CONFIG_FILE, sizeof(cd->configFile)); } if (GetNoInterfaces(cd) == 0) { AddInterface(cd, "ALL_NLO"); } } void PrintConfigData(const struct ConfigData cd) { int i; printf("debug: killRoute: %s\n", cd.killRoute); printf("debug: killHostsDeny: %s\n", cd.killHostsDeny); printf("debug: killRunCmd: %s\n", cd.killRunCmd); printf("debug: disableLocalCheck: %d\n", cd.disableLocalCheck); if (GetNoInterfaces(&cd) > 0) { i = 0; while (cd.interfaces[i] != NULL) { printf("debug: interface: %s\n", cd.interfaces[i]); i++; } } else { printf("debug: [no interfaces set]\n"); } printf("debug: tcpPorts (%zu): ", cd.tcpPortsLength); for (size_t j = 0; j < cd.tcpPortsLength; j++) { if (IsPortSingle(&cd.tcpPorts[j])) { printf("%d ", cd.tcpPorts[j].single); } else { printf("%d-%d ", cd.tcpPorts[j].range.start, cd.tcpPorts[j].range.end); } } printf("\n"); printf("debug: udpPorts (%zu): ", cd.udpPortsLength); for (size_t j = 0; j < cd.udpPortsLength; j++) { if (IsPortSingle(&cd.udpPorts[j])) { printf("%d ", cd.udpPorts[j].single); } else { printf("%d-%d ", cd.udpPorts[j].range.start, cd.udpPorts[j].range.end); } } printf("\n"); if (cd.portBannerPresent == TRUE) { printf("debug: portBanner: %s\n", cd.portBanner); } printf("debug: configFile: %s\n", cd.configFile); printf("debug: blockedFile: %s\n", cd.blockedFile); printf("debug: historyFile: %s\n", cd.historyFile); printf("debug: ignoreFile: %s\n", cd.ignoreFile); printf("debug: blockTCP: %d\n", cd.blockTCP); printf("debug: blockUDP: %d\n", cd.blockUDP); printf("debug: runCmdFirst: %d\n", cd.runCmdFirst); printf("debug: resolveHost: %d\n", cd.resolveHost); printf("debug: configTriggerCount: %d\n", cd.configTriggerCount); printf("debug: sentryMode: %s\n", GetSentryModeString(cd.sentryMode)); printf("debug: sentryMethod: %s\n", GetSentryMethodString(cd.sentryMethod)); printf("debug: log output stdout: %s\n", (cd.logFlags & LOGFLAG_OUTPUT_STDOUT) != 0 ? "true" : "false"); printf("debug: log output syslog: %s\n", (cd.logFlags & LOGFLAG_OUTPUT_SYSLOG) != 0 ? "true" : "false"); printf("debug: log debug: %s\n", (cd.logFlags & LOGFLAG_DEBUG) != 0 ? "true" : "false"); printf("debug: log verbose: %s\n", (cd.logFlags & LOGFLAG_VERBOSE) != 0 ? "true" : "false"); printf("debug: daemon: %s\n", cd.daemon == TRUE ? "true" : "false"); } char *GetSentryModeString(const enum SentryMode sentryMode) { switch (sentryMode) { case SENTRY_MODE_STEALTH: return "stealth"; case SENTRY_MODE_CONNECT: return "connect"; default: return "unknown"; } } static char *GetSentryMethodString(const enum SentryMethod sentryMethod) { switch (sentryMethod) { case SENTRY_METHOD_PCAP: return "pcap"; case SENTRY_METHOD_RAW: return "raw"; default: return "unknown"; } } int AddInterface(struct ConfigData *cd, const char *interface) { size_t noInterfaces; if (strlen(interface) >= IF_NAMESIZE) { fprintf(stderr, "Error: Interface name %s too long\n", interface); Exit(EXIT_FAILURE); } if (IsInterfacePresent(cd, interface) == TRUE) { return TRUE; } noInterfaces = GetNoInterfaces(cd); cd->interfaces = realloc(cd->interfaces, (noInterfaces + 2) * sizeof(char *)); cd->interfaces[noInterfaces + 1] = NULL; cd->interfaces[noInterfaces] = malloc(IF_NAMESIZE); SafeStrncpy(cd->interfaces[noInterfaces], interface, IF_NAMESIZE); return TRUE; } size_t GetNoInterfaces(const struct ConfigData *cd) { size_t i = 0; if (cd->interfaces == NULL) { return 0; } while (cd->interfaces[i] != NULL) { i++; } return i; } int IsInterfacePresent(const struct ConfigData *cd, const char *interface) { int i = 0; if (cd->interfaces == NULL) { return FALSE; } while (cd->interfaces[i] != NULL) { if (strlen(cd->interfaces[i]) != strlen(interface)) { i++; continue; } if (strncmp(cd->interfaces[i], interface, strlen(cd->interfaces[i])) == 0) { return TRUE; } i++; } return FALSE; } void FreeConfigData(struct ConfigData *cd) { if (cd->interfaces != NULL) { int i = 0; while (cd->interfaces[i] != NULL) { free(cd->interfaces[i]); i++; } free(cd->interfaces); cd->interfaces = NULL; } if (cd->tcpPorts != NULL) { free(cd->tcpPorts); cd->tcpPorts = NULL; } if (cd->udpPorts != NULL) { free(cd->udpPorts); cd->udpPorts = NULL; } } portsentry-2.0.6/src/config_data.h000066400000000000000000000034241510117642400171520ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include #include #include #include "portsentry.h" #include "port.h" extern const uint8_t LOGFLAG_NONE; extern const uint8_t LOGFLAG_DEBUG; extern const uint8_t LOGFLAG_VERBOSE; extern const uint8_t LOGFLAG_OUTPUT_STDOUT; extern const uint8_t LOGFLAG_OUTPUT_SYSLOG; enum SentryMode { SENTRY_MODE_STEALTH = 0, SENTRY_MODE_CONNECT }; enum SentryMethod { SENTRY_METHOD_PCAP = 0, SENTRY_METHOD_RAW }; struct ConfigData { char killRoute[MAXBUF]; char killHostsDeny[MAXBUF]; char killRunCmd[MAXBUF]; char **interfaces; struct Port *tcpPorts; size_t tcpPortsLength; struct Port *udpPorts; size_t udpPortsLength; char portBanner[MAXBUF]; uint8_t portBannerPresent; char configFile[PATH_MAX]; char blockedFile[PATH_MAX]; char historyFile[PATH_MAX]; char ignoreFile[PATH_MAX]; int blockTCP; int blockUDP; int runCmdFirst; int resolveHost; uint16_t configTriggerCount; int disableLocalCheck; enum SentryMode sentryMode; enum SentryMethod sentryMethod; uint8_t logFlags; uint8_t daemon; }; extern struct ConfigData configData; void ResetConfigData(struct ConfigData *cd); void PostProcessConfig(struct ConfigData *cd); void PrintConfigData(const struct ConfigData cd); char *GetSentryModeString(const enum SentryMode sentryMode); void SetConfigData(const struct ConfigData *fileConfig, const struct ConfigData *cmdlineConfig); int AddInterface(struct ConfigData *cd, const char *interface); size_t GetNoInterfaces(const struct ConfigData *cd); void FreeConfigData(struct ConfigData *cd); int IsInterfacePresent(const struct ConfigData *cd, const char *interface); portsentry-2.0.6/src/configfile.c000066400000000000000000000276441510117642400170260ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include "config_data.h" #include "configfile.h" #include "io.h" #include "portsentry.h" #include "util.h" #include "port.h" static void SetConfiguration(const char *buffer, const size_t keySize, char *ptr, const size_t valueSize, const size_t line, struct ConfigData *fileConfig); static void ValidateConfig(struct ConfigData *fileConfig); static void MergeToConfigData(struct ConfigData *fileConfig); static char *SkipSpaceAndTab(char *buffer); static size_t GetKeySize(char *buffer); static void StripTrailingSpace(char *buffer); static ssize_t GetSizeToQuote(const char *buffer); static int ParsePortsList(char *str, struct Port **ports, size_t *portsLength); void ReadConfigFile(void) { struct ConfigData fileConfig; FILE *config; char buffer[MAXBUF], *ptr; size_t keySize, line = 0; ssize_t valueSize; ResetConfigData(&fileConfig); if ((config = fopen(configData.configFile, "r")) == NULL) { fprintf(stderr, "Cannot open config file: %s.\n", configData.configFile); Exit(EXIT_FAILURE); } while (fgets(buffer, MAXBUF, config) != NULL) { line++; if (buffer[0] == '#' || buffer[0] == '\n' || buffer[0] == '\r') { /* Skip comments and blank lines */ continue; } StripTrailingSpace(buffer); if ((keySize = GetKeySize(buffer)) == 0) { fprintf(stderr, "Invalid config file entry at line %zu\n", line); fclose(config); Exit(EXIT_FAILURE); } ptr = buffer + keySize; ptr = SkipSpaceAndTab(ptr); if (*ptr != '=') { fprintf(stderr, "Invalid character found after config key. Require equals (=) after key. Line %zu\n", line); fclose(config); Exit(EXIT_FAILURE); } ptr++; ptr = SkipSpaceAndTab(ptr); if (*ptr != '"') { fprintf(stderr, "Invalid value on line %zu, require quote character (\") to start value\n", line); fclose(config); Exit(EXIT_FAILURE); } ptr++; if ((valueSize = GetSizeToQuote(ptr)) == ERROR) { fprintf(stderr, "Invalid value at line %zu, require an end quote character (\") at end of value\n", line); fclose(config); Exit(EXIT_FAILURE); } if (valueSize < 1) { fprintf(stderr, "Invalid value at line %zu, require a value\n", line); fclose(config); Exit(EXIT_FAILURE); } *(ptr + valueSize) = '\0'; // Remove trailing quote SetConfiguration(buffer, keySize, ptr, (size_t)valueSize, line, &fileConfig); } fclose(config); /* Make sure config is valid */ ValidateConfig(&fileConfig); MergeToConfigData(&fileConfig); } static void SetConfiguration(const char *buffer, const size_t keySize, char *ptr, const size_t valueSize, const size_t line, struct ConfigData *fileConfig) { char err[ERRNOMAXBUF]; Debug("SetConfiguration: %s keySize: %zu valueSize: %zd sentryMode: %s", buffer, keySize, valueSize, GetSentryModeString(configData.sentryMode)); if (strncmp(buffer, "BLOCK_TCP", keySize) == 0) { if (strncmp(ptr, "0", valueSize) == 0) { fileConfig->blockTCP = 0; } else if (strncmp(ptr, "1", valueSize) == 0) { fileConfig->blockTCP = 1; } else if (strncmp(ptr, "2", valueSize) == 0) { fileConfig->blockTCP = 2; } else { fprintf(stderr, "Invalid config file entry for BLOCK_TCP\n"); Exit(EXIT_FAILURE); } } else if (strncmp(buffer, "BLOCK_UDP", keySize) == 0) { if (strncmp(ptr, "0", valueSize) == 0) { fileConfig->blockUDP = 0; } else if (strncmp(ptr, "1", valueSize) == 0) { fileConfig->blockUDP = 1; } else if (strncmp(ptr, "2", valueSize) == 0) { fileConfig->blockUDP = 2; } else { fprintf(stderr, "Invalid config file entry for BLOCK_UDP\n"); Exit(EXIT_FAILURE); } } else if (strncmp(buffer, "RESOLVE_HOST", keySize) == 0) { if (strncmp(ptr, "1", valueSize) == 0) { fileConfig->resolveHost = TRUE; } else if (strncmp(ptr, "0", valueSize) == 0) { fileConfig->resolveHost = FALSE; } else { fprintf(stderr, "Invalid config file entry for RESOLVE_HOST\n"); Exit(EXIT_FAILURE); } } else if (strncmp(buffer, "SCAN_TRIGGER", keySize) == 0) { long scanTriggerCount = GetLong(ptr); if (scanTriggerCount < 0) { fprintf(stderr, "Invalid config file entry for SCAN_TRIGGER\n"); Exit(EXIT_FAILURE); } fileConfig->configTriggerCount = (uint16_t)scanTriggerCount; } else if (strncmp(buffer, "KILL_ROUTE", keySize) == 0) { if (snprintf(fileConfig->killRoute, MAXBUF, "%s", ptr) >= MAXBUF) { fprintf(stderr, "KILL_ROUTE value too long\n"); Exit(EXIT_FAILURE); } } else if (strncmp(buffer, "KILL_HOSTS_DENY", keySize) == 0) { if (snprintf(fileConfig->killHostsDeny, MAXBUF, "%s", ptr) >= MAXBUF) { fprintf(stderr, "KILL_HOSTS_DENY value too long\n"); Exit(EXIT_FAILURE); } } else if (strncmp(buffer, "KILL_RUN_CMD", keySize) == 0) { if (snprintf(fileConfig->killRunCmd, MAXBUF, "%s", ptr) >= MAXBUF) { fprintf(stderr, "KILL_RUN_CMD value too long\n"); Exit(EXIT_FAILURE); } } else if (strncmp(buffer, "KILL_RUN_CMD_FIRST", keySize) == 0) { if (strncmp(ptr, "1", valueSize) == 0) { fileConfig->runCmdFirst = TRUE; } else if (strncmp(ptr, "0", valueSize) == 0) { fileConfig->runCmdFirst = FALSE; } else { fprintf(stderr, "Invalid config file entry for KILL_RUN_CMD_FIRST\n"); Exit(EXIT_FAILURE); } } else if (strncmp(buffer, "BLOCKED_FILE", keySize) == 0) { if (snprintf(fileConfig->blockedFile, PATH_MAX, "%s", ptr) >= PATH_MAX) { fprintf(stderr, "BLOCKED_FILE path value too long\n"); Exit(EXIT_FAILURE); } if (strlen(fileConfig->blockedFile) > 0 && TestFileAccess(fileConfig->blockedFile, "a", TRUE) == FALSE) { fprintf(stderr, "Unable to open block file for writing %s: %s\n", fileConfig->blockedFile, ErrnoString(err, sizeof(err))); Exit(EXIT_FAILURE); } } else if (strncmp(buffer, "HISTORY_FILE", keySize) == 0) { if (snprintf(fileConfig->historyFile, PATH_MAX, "%s", ptr) >= PATH_MAX) { fprintf(stderr, "HISTORY_FILE path value too long\n"); Exit(EXIT_FAILURE); } if (TestFileAccess(fileConfig->historyFile, "w", TRUE) == FALSE) { fprintf(stderr, "Unable to open history file for writing %s: %s\n", fileConfig->historyFile, ErrnoString(err, sizeof(err))); Exit(EXIT_FAILURE); } } else if (strncmp(buffer, "IGNORE_FILE", keySize) == 0) { if (snprintf(fileConfig->ignoreFile, PATH_MAX, "%s", ptr) >= PATH_MAX) { fprintf(stderr, "IGNORE_FILE path value too long\n"); Exit(EXIT_FAILURE); } } else if (strncmp(buffer, "TCP_PORTS", keySize) == 0) { if (ParsePortsList(ptr, &fileConfig->tcpPorts, &fileConfig->tcpPortsLength) == FALSE) { fprintf(stderr, "Unable to parse TCP_PORTS directive in config file\n"); Exit(EXIT_FAILURE); } } else if (strncmp(buffer, "UDP_PORTS", keySize) == 0) { if (ParsePortsList(ptr, &fileConfig->udpPorts, &fileConfig->udpPortsLength) == FALSE) { fprintf(stderr, "Unable to parse UDP_PORTS directive in config file\n"); Exit(EXIT_FAILURE); } } else if (strncmp(buffer, "PORT_BANNER", keySize) == 0) { if (snprintf(fileConfig->portBanner, MAXBUF, "%s", ptr) >= MAXBUF) { fprintf(stderr, "PORT_BANNER value too long\n"); Exit(EXIT_FAILURE); } fileConfig->portBannerPresent = TRUE; } else { fprintf(stderr, "Invalid config file entry at line %zu\n", line); Exit(EXIT_FAILURE); } } static void ValidateConfig(struct ConfigData *fileConfig) { if (configData.sentryMode == SENTRY_MODE_STEALTH && fileConfig->tcpPortsLength == 0 && fileConfig->udpPortsLength == 0) { fprintf(stderr, "Selected mode: %s, but no TCP_PORTS or UDP_PORTS specified in config file\n", GetSentryModeString(configData.sentryMode)); Exit(EXIT_FAILURE); } else if (configData.sentryMode == SENTRY_MODE_CONNECT && fileConfig->tcpPortsLength == 0 && fileConfig->udpPortsLength == 0) { fprintf(stderr, "Selected mode: %s, but no TCP_PORTS or UDP_PORTS specified in config file\n", GetSentryModeString(configData.sentryMode)); Exit(EXIT_FAILURE); } if (fileConfig->blockTCP < 0 || fileConfig->blockTCP > 2) { fprintf(stderr, "Invalid BLOCK_TCP value in config file\n"); Exit(EXIT_FAILURE); } if (fileConfig->blockUDP < 0 || fileConfig->blockUDP > 2) { fprintf(stderr, "Invalid BLOCK_UDP value in config file\n"); Exit(EXIT_FAILURE); } if ((fileConfig->blockTCP == 2 || fileConfig->blockUDP == 2) && strlen(fileConfig->killRunCmd) == 0) { fprintf(stderr, "KILL_RUN_CMD must be specified if BLOCK_TCP or BLOCK_UDP is set to 2\n"); Exit(EXIT_FAILURE); } if ((fileConfig->blockTCP == 1 || fileConfig->blockUDP == 1) && (strlen(fileConfig->killHostsDeny) == 0 && strlen(fileConfig->killRoute) == 0)) { fprintf(stderr, "KILL_HOSTS_DENY and/or KILL_ROUTE must be specified if BLOCK_TCP or BLOCK_UDP is set to 1\n"); Exit(EXIT_FAILURE); } } static void MergeToConfigData(struct ConfigData *fileConfig) { struct ConfigData temp; /* * WARNING: Exercise caution when modifying this function. Both configData and fileConfig will hold pointers to to allocated memory. * Make sure copying is done correctly so no heap memory is lost. * As of this note; the ConfigData structure (config_data.h) holds pointers to: * * char **interfaces - array of strings of interfaces to listen to. Set in cmdline (therefore present in configData) * struct Port *tcpPorts - array of Port structs for TCP ports to listen to. Set in config file (therefore present in fileConfig) * struct Port *udpPorts - array of Port structs for UDP ports to listen to. Set in config file (therefore present in fileConfig) */ // backup current configData (at this point,it's assumed the configData holds the cmdline options) memcpy(&temp, &configData, sizeof(struct ConfigData)); // Set values from config file to be the "base" configData memcpy(&configData, fileConfig, sizeof(struct ConfigData)); // Overlay values from the backup (cmdline) onto the configData // None of the options below are settable via the config file so they need to be added configData.sentryMode = temp.sentryMode; configData.sentryMethod = temp.sentryMethod; configData.logFlags = temp.logFlags; configData.daemon = temp.daemon; configData.interfaces = temp.interfaces; configData.disableLocalCheck = temp.disableLocalCheck; memcpy(configData.configFile, temp.configFile, sizeof(configData.configFile)); } static char *SkipSpaceAndTab(char *buffer) { char *ptr = buffer; while (*ptr == ' ' || *ptr == '\t') { ptr++; } return ptr; } static size_t GetKeySize(char *buffer) { char *ptr = buffer; size_t keySize = 0; while (isupper((int)*ptr) || *ptr == '_') { ptr++; keySize++; } return keySize; } static void StripTrailingSpace(char *buffer) { char *ptr = buffer + strlen(buffer) - 1; if (ptr < buffer) { return; } while (isspace((int)*ptr)) { *ptr = '\0'; if (ptr == buffer) { break; } } } static ssize_t GetSizeToQuote(const char *buffer) { char *ptr; ssize_t valueSize = 0; if ((ptr = strstr(buffer, "\"")) == NULL) { return ERROR; } valueSize = ptr - buffer; return valueSize; } static int ParsePortsList(char *str, struct Port **ports, size_t *portsLength) { size_t count = 0; char *temp, *saveptr, *p = str; if (strlen(str) == 0) { return FALSE; } if (*ports != NULL) { free(*ports); *ports = NULL; *portsLength = 0; } while ((temp = strtok_r(p, ",", &saveptr)) != NULL) { if ((*ports = realloc(*ports, (count + 1) * sizeof(struct Port))) == NULL) { fprintf(stderr, "Unable to allocate memory for ports\n"); Exit(EXIT_FAILURE); } ParsePort(temp, &(*ports)[count]); p = NULL; count++; } if ((*portsLength = count) == 0) { return FALSE; } return TRUE; } portsentry-2.0.6/src/configfile.h000066400000000000000000000002321510117642400170130ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once void ReadConfigFile(void); portsentry-2.0.6/src/ignore.c000066400000000000000000000143141510117642400161720ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include #include #include "io.h" #include "portsentry.h" #include "config_data.h" #include "ignore.h" #include "util.h" static int IgnoreParse(const char *buffer, struct IgnoreIp *ignoreIp); static int IsValidIPChar(const char c); static int IsValidIPChar(const char c) { if ((c >= '0' && c <= '9') || c == '.' || c == ':' || (c >= 'a' && c <= 'f') || c == '/') { return TRUE; } return FALSE; } static int IgnoreParse(const char *buffer, struct IgnoreIp *ignoreIp) { int ret, status = ERROR; struct addrinfo hints, *res = NULL; char *separator = NULL; long mask = -1; memset(ignoreIp, 0, sizeof(struct IgnoreIp)); if ((separator = strchr(buffer, '/')) != NULL) { *separator = '\0'; separator++; if ((mask = GetLong(separator)) == ERROR) { separator--; *separator = '/'; Error("Invalid netmask in ignore file: %s", buffer); goto exit; } } memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_NUMERICHOST; if ((ret = getaddrinfo(buffer, NULL, &hints, &res)) != 0) { Error("Unable to read IP address %s: %s", buffer, gai_strerror(ret)); goto exit; } if (res->ai_family == AF_INET) { ignoreIp->family = AF_INET; memcpy(&ignoreIp->ip.addr4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, sizeof(struct in_addr)); if (mask == -1) { ignoreIp->mask.mask4.s_addr = 0xffffffff; } else { if (mask < 0 || mask > 32) { Error("Invalid netmask in ignore file, must be 0-32: %s", buffer); goto exit; } ignoreIp->mask.mask4.s_addr = htonl(0xffffffff << (32 - mask)); } } else if (res->ai_family == AF_INET6) { ignoreIp->family = AF_INET6; memcpy(&ignoreIp->ip.addr6, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, sizeof(struct in6_addr)); if (mask == -1) { memset(&ignoreIp->mask.mask6, 0xff, sizeof(struct in6_addr)); } else { if (mask < 0 || mask > 128) { Error("Invalid netmask in ignore file, must be 0-128: %s", buffer); goto exit; } memset(&ignoreIp->mask.mask6, 0, sizeof(struct in6_addr)); for (int i = 0; i < 16; i++) { if (mask >= 8) { ignoreIp->mask.mask6.s6_addr[i] = 0xff; mask -= 8; } else { ignoreIp->mask.mask6.s6_addr[i] = (uint8_t)(0xff << (8 - mask)); break; } } } } else { Error("Invalid IP address family: %s", buffer); goto exit; } status = TRUE; exit: if (res != NULL) { freeaddrinfo(res); res = NULL; } return status; } void FreeIgnore(struct IgnoreState *is) { if (is->ignoreIpList != NULL) { free(is->ignoreIpList); } memset(is, 0, sizeof(struct IgnoreState)); is->isInitialized = FALSE; } /* Initialize the ignore state * Returns TRUE if the ignore file is read successfully * Returns FALSE if the ignore file is not set * Returns ERROR if the ignore file is set but cannot be read */ int InitIgnore(struct IgnoreState *is) { FILE *fp = NULL; int status = ERROR; char buffer[MAXBUF]; struct IgnoreIp ii; if (strlen(configData.ignoreFile) == 0) { return FALSE; } FreeIgnore(is); if ((fp = fopen(configData.ignoreFile, "r")) == NULL) { Error("Unable to open ignore file: %s", configData.ignoreFile); goto exit; } while (fgets(buffer, MAXBUF, fp) != NULL) { if ((buffer[0] == '#') || (buffer[0] == '\n')) continue; buffer[strlen(buffer) - 1] = '\0'; // Remove newline for (size_t i = 0; i < strlen(buffer); i++) { if (!IsValidIPChar(buffer[i])) { Error("Invalid character in ignore file: %s", buffer); goto exit; } } if (IgnoreParse(buffer, &ii) != TRUE) { goto exit; } if ((is->ignoreIpList = realloc(is->ignoreIpList, (is->ignoreIpListSize + 1) * sizeof(struct IgnoreIp))) == NULL) { Error("Unable to allocate memory for ignore list"); goto exit; } is->ignoreIpListSize++; memcpy(&is->ignoreIpList[is->ignoreIpListSize - 1], &ii, sizeof(struct IgnoreIp)); } is->isInitialized = TRUE; if (configData.logFlags & LOGFLAG_VERBOSE) { for (size_t i = 0; i < is->ignoreIpListSize; i++) { if (is->ignoreIpList[i].family == AF_INET) { char ip[INET_ADDRSTRLEN]; char mask[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &is->ignoreIpList[i].ip.addr4, ip, INET_ADDRSTRLEN); inet_ntop(AF_INET, &is->ignoreIpList[i].mask.mask4, mask, INET_ADDRSTRLEN); Verbose("Ignoring IP: %s/%s", ip, mask); } else if (is->ignoreIpList[i].family == AF_INET6) { char ip[INET6_ADDRSTRLEN]; char mask[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &is->ignoreIpList[i].ip.addr6, ip, INET6_ADDRSTRLEN); inet_ntop(AF_INET6, &is->ignoreIpList[i].mask.mask6, mask, INET6_ADDRSTRLEN); Verbose("Ignoring IP: %s/%s", ip, mask); } } } status = TRUE; exit: if (fp != NULL) { fclose(fp); } if (status != TRUE) { FreeIgnore(is); } return status; } int IgnoreIpIsPresent(const struct IgnoreState *is, const struct sockaddr *sa) { assert(is != NULL); assert(sa != NULL); if (is->isInitialized == FALSE) { return ERROR; } for (size_t i = 0; i < is->ignoreIpListSize; i++) { if (is->ignoreIpList[i].family != sa->sa_family) { continue; } if (sa->sa_family == AF_INET) { const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; if ((sin->sin_addr.s_addr & is->ignoreIpList[i].mask.mask4.s_addr) == is->ignoreIpList[i].ip.addr4.s_addr) { return TRUE; } } else if (sa->sa_family == AF_INET6) { const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sa; for (int j = 0; j < 16; j++) { if ((sin6->sin6_addr.s6_addr[j] & is->ignoreIpList[i].mask.mask6.s6_addr[j]) != is->ignoreIpList[i].ip.addr6.s6_addr[j]) { break; } if (j == 15) { return TRUE; } } } } return FALSE; } portsentry-2.0.6/src/ignore.h000066400000000000000000000011561510117642400161770ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include #include struct IgnoreIp { union { struct in_addr addr4; struct in6_addr addr6; } ip; union { struct in_addr mask4; struct in6_addr mask6; } mask; int family; }; struct IgnoreState { struct IgnoreIp *ignoreIpList; size_t ignoreIpListSize; uint8_t isInitialized; }; int InitIgnore(struct IgnoreState *is); void FreeIgnore(struct IgnoreState *is); int IgnoreIpIsPresent(const struct IgnoreState *is, const struct sockaddr *sa); portsentry-2.0.6/src/io.c000066400000000000000000000451421510117642400153210ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Craig Rowland // SPDX-FileContributor: Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "config_data.h" #include "io.h" #include "portsentry.h" #include "util.h" #define MAXPORTBUF 10 static int MkdirP(const char *path); static uint8_t isSyslogOpen = FALSE; enum LogType { LogTypeNone, LogTypeError, LogTypeDebug, LogTypeVerbose }; static void LogEntry(const enum LogType logType, const char *logentry, va_list argsPtr); static void LogEntry(const enum LogType logType, const char *logentry, va_list argsPtr) { char logbuffer[MAXBUF]; if (vsnprintf(logbuffer, MAXBUF, logentry, argsPtr) >= MAXBUF) { logbuffer[MAXBUF - 1] = '\0'; logbuffer[MAXBUF - 2] = '.'; logbuffer[MAXBUF - 3] = '.'; logbuffer[MAXBUF - 4] = '.'; } if (configData.logFlags & LOGFLAG_OUTPUT_STDOUT) { if (logType == LogTypeError) { fprintf(stderr, "%s\n", logbuffer); fflush(stderr); } else { printf("%s%s\n", (logType == LogTypeDebug) ? "debug: " : "", logbuffer); fflush(stdout); } } if (configData.logFlags & LOGFLAG_OUTPUT_SYSLOG) { if (isSyslogOpen == FALSE) { openlog("portsentry", LOG_PID, LOG_DAEMON); isSyslogOpen = TRUE; } syslog((logType == LogTypeNone) ? LOG_NOTICE : (logType == LogTypeError) ? LOG_ERR : (logType == LogTypeDebug) ? LOG_DEBUG : LOG_INFO, "%s%s", (logType == LogTypeDebug) ? "debug: " : "", logbuffer); } } void Log(const char *logentry, ...) { va_list argsPtr; va_start(argsPtr, logentry); LogEntry(LogTypeNone, logentry, argsPtr); va_end(argsPtr); } void Error(const char *logentry, ...) { va_list argsPtr; va_start(argsPtr, logentry); LogEntry(LogTypeError, logentry, argsPtr); va_end(argsPtr); } void Debug(const char *logentry, ...) { va_list argsPtr; if ((configData.logFlags & LOGFLAG_DEBUG) == 0) { return; } va_start(argsPtr, logentry); LogEntry(LogTypeDebug, logentry, argsPtr); va_end(argsPtr); } void Verbose(const char *logentry, ...) { va_list argsPtr; if ((configData.logFlags & LOGFLAG_VERBOSE) == 0) { return; } va_start(argsPtr, logentry); LogEntry(LogTypeVerbose, logentry, argsPtr); va_end(argsPtr); } void Crash(const int errCode, const char *logentry, ...) { va_list argsPtr; va_start(argsPtr, logentry); LogEntry(LogTypeError, logentry, argsPtr); va_end(argsPtr); Exit(errCode); } void Exit(const int status) { Log("Portsentry is shutting down"); if (isSyslogOpen == TRUE) { closelog(); isSyslogOpen = FALSE; } if (configData.tcpPorts != NULL) { free(configData.tcpPorts); configData.tcpPorts = NULL; configData.tcpPortsLength = 0; } if (configData.udpPorts != NULL) { free(configData.udpPorts); configData.udpPorts = NULL; configData.udpPortsLength = 0; } exit(status); } int BindSocket(const int sockfd, const struct sockaddr *addr, const socklen_t addrLen, const uint8_t proto) { char err[ERRNOMAXBUF]; uint16_t port; if (addr->sa_family == AF_INET6) { const struct sockaddr_in6 *addr6Ptr = (const struct sockaddr_in6 *)addr; port = ntohs(addr6Ptr->sin6_port); } else if (addr->sa_family == AF_INET) { const struct sockaddr_in *addr4Ptr = (const struct sockaddr_in *)addr; port = ntohs(addr4Ptr->sin_port); } else { Error("Unsupported address family: %d", addr->sa_family); return ERROR; } if ((bind(sockfd, addr, addrLen)) == -1) { Verbose("Binding %s %s %d failed: %s", GetFamilyString(addr->sa_family), GetProtocolString(proto), port, ErrnoString(err, sizeof(err))); return ERROR; } if (proto == IPPROTO_TCP) { if (listen(sockfd, 5) == -1) { Error("Listen failed: %s %d %s", GetFamilyString(addr->sa_family), port, ErrnoString(err, sizeof(err))); return ERROR; } } return TRUE; } int OpenSocket(const int family, const int type, const int protocol, const uint8_t tcpReuseAddr) { int sockfd; int optval; socklen_t optlen; char err[ERRNOMAXBUF]; assert(family == AF_INET || family == AF_INET6); assert(type == SOCK_STREAM || type == SOCK_DGRAM); assert(protocol == IPPROTO_TCP || protocol == IPPROTO_UDP); if ((sockfd = socket(family, type, protocol)) < 0) { Error("Could not open socket family: %d type: %d protocol: %d: %s", family, type, protocol, ErrnoString(err, sizeof(err))); return ERROR; } if (type == SOCK_STREAM && tcpReuseAddr == TRUE) { optval = 1; optlen = sizeof(optval); if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, optlen) < 0) { Error("Could not set SO_REUSEADDR on TCP socket: %s", ErrnoString(err, sizeof(err))); return ERROR; } } #ifndef __OpenBSD__ if (family == AF_INET6) { optval = 0; optlen = sizeof(optval); if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &optval, optlen) < 0) { Error("Could not set IPV6_V6ONLY on socket: %s", ErrnoString(err, sizeof(err))); return ERROR; } optlen = sizeof(optval); if (getsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &optval, &optlen) < 0) { Error("Could not get IPV6_V6ONLY on socket: %s", ErrnoString(err, sizeof(err))); return ERROR; } if (optval != 0) { Error("Could not set IPV6_V6ONLY on socket: %s", ErrnoString(err, sizeof(err))); return ERROR; } } #endif return sockfd; } /* This will use a system() call to change the route of the target host to */ /* a dead IP address on your LOCAL SUBNET. */ int KillRoute(const char *target, const int port, const char *killString, const char *detectionType) { char commandStringTemp[MAXBUF]; char commandStringTemp2[MAXBUF], commandStringFinal[MAXBUF]; char portString[MAXPORTBUF]; int killStatus = ERROR, substStatus = ERROR; if (strlen(killString) == 0) return FALSE; if (snprintf(portString, MAXPORTBUF, "%d", port) >= MAXPORTBUF) { Error("KillRoute: Port number too large for buffer: %d", port); return ERROR; } substStatus = SubstString(target, "$TARGET$", killString, commandStringTemp, MAXBUF); if (substStatus == 0) { Log("No target variable specified in KILL_ROUTE option. Skipping."); return ERROR; } else if (substStatus == ERROR) { Log("Error trying to parse $TARGET$ Token for KILL_ROUTE. Skipping."); return ERROR; } if (SubstString(portString, "$PORT$", commandStringTemp, commandStringTemp2, MAXBUF) == ERROR) { Log("Error trying to parse $PORT$ Token for KILL_ROUTE. Skipping."); return ERROR; } if (SubstString(detectionType, "$MODE$", commandStringTemp2, commandStringFinal, MAXBUF) == ERROR) { Log("Error trying to parse $MODE$ Token for KILL_ROUTE. Skipping."); return ERROR; } Debug("KillRoute: running route command: %s", commandStringFinal); /* Kill the bastard and report a status */ killStatus = system(commandStringFinal); if (killStatus == 127) { Error("There was an error trying to block host (exec fail) %s", target); return ERROR; } else if (killStatus < 0) { Error("There was an error trying to block host (system fail) %s", target); return ERROR; } Log("attackalert: Host %s has been blocked via dropped route using command: \"%s\"", target, commandStringFinal); return TRUE; } /* This will run a specified command with TARGET as the option if one is given. */ int KillRunCmd(const char *target, const int port, const char *killString, const char *detectionType) { char commandStringTemp[MAXBUF]; char commandStringTemp2[MAXBUF], commandStringFinal[MAXBUF]; char portString[MAXBUF]; int killStatus = ERROR; if (strlen(killString) == 0) return FALSE; if (snprintf(portString, MAXPORTBUF, "%d", port) >= MAXPORTBUF) { Error("KillRunCmd: Port number too large for buffer: %d", port); return ERROR; } /* Tokens are not required, but we check for an error anyway */ if (SubstString(target, "$TARGET$", killString, commandStringTemp, MAXBUF) == ERROR) { Log("Error trying to parse $TARGET$ Token for KILL_RUN_CMD. Skipping."); return ERROR; } if (SubstString(portString, "$PORT$", commandStringTemp, commandStringTemp2, MAXBUF) == ERROR) { Log("Error trying to parse $PORT$ Token for KILL_RUN_CMD. Skipping."); return ERROR; } if (SubstString(detectionType, "$MODE$", commandStringTemp2, commandStringFinal, MAXBUF) == ERROR) { Log("Error trying to parse $MODE$ Token for KILL_RUN_CMD. Skipping."); return ERROR; } /* Kill the bastard and report a status */ killStatus = system(commandStringFinal); if (killStatus == 127) { Error("There was an error trying to run command (exec fail) %s", target); return ERROR; } else if (killStatus < 0) { Error("There was an error trying to run command (system fail) %s", target); return ERROR; } /* report success */ Log("attackalert: External command run for host: %s using command: \"%s\"", target, commandStringFinal); return TRUE; } /* this function will drop the host into the TCP wrappers hosts.deny file to deny * all access. The drop route metod is preferred as this stops UDP attacks as well * as TCP. You may find though that host.deny will be a more permanent home.. */ int KillHostsDeny(const char *target, const int port, const char *killString, const char *detectionType) { FILE *output = NULL; char commandStringTemp[MAXBUF]; char commandStringTemp2[MAXBUF], commandStringFinal[MAXBUF]; char portString[MAXBUF]; int substStatus = ERROR; struct stat st; char err[ERRNOMAXBUF]; if (strlen(killString) == 0) return FALSE; if (snprintf(portString, MAXPORTBUF, "%d", port) >= MAXPORTBUF) { Error("KillHostsDeny: Port number too large for buffer: %d", port); return ERROR; } Debug("KillHostsDeny: parsing string for block: %s", killString); substStatus = SubstString(target, "$TARGET$", killString, commandStringTemp, MAXBUF); if (substStatus == 0) { Log("No target variable specified in KILL_HOSTS_DENY option. Skipping."); return ERROR; } else if (substStatus == ERROR) { Log("Error trying to parse $TARGET$ Token for KILL_HOSTS_DENY. Skipping."); return ERROR; } if (SubstString(portString, "$PORT$", commandStringTemp, commandStringTemp2, MAXBUF) == ERROR) { Log("Error trying to parse $PORT$ Token for KILL_HOSTS_DENY. Skipping."); return ERROR; } if (SubstString(detectionType, "$MODE$", commandStringTemp2, commandStringFinal, MAXBUF) == ERROR) { Log("Error trying to parse $MODE$ Token for KILL_HOSTS_DENY. Skipping."); return ERROR; } Debug("KillHostsDeny: result string for block: %s", commandStringFinal); if (stat(WRAPPER_HOSTS_DENY, &st) == -1) { Error("Cannot stat file %s: %s", WRAPPER_HOSTS_DENY, ErrnoString(err, sizeof(err))); return ERROR; } if (S_ISLNK(st.st_mode)) { Error("File %s is a symbolic link, refusing to modify", WRAPPER_HOSTS_DENY); return ERROR; } if ((st.st_mode & S_IWOTH) != 0) { Error("File %s is world-writable, refusing to modify", WRAPPER_HOSTS_DENY); return ERROR; } if (FindInFile(commandStringFinal, WRAPPER_HOSTS_DENY) == TRUE) { Log("Host %s already in hosts.deny file, skipping.", target); return TRUE; } char tempFile[PATH_MAX]; if (snprintf(tempFile, PATH_MAX, "%s.tmp", WRAPPER_HOSTS_DENY) >= PATH_MAX) { Error("KillHostsDeny: Temp file name too large for buffer: %s", WRAPPER_HOSTS_DENY); return ERROR; } if ((output = fopen(tempFile, "w")) == NULL) { Error("Cannot create temporary file %s: %s", tempFile, ErrnoString(err, sizeof(err))); return ERROR; } FILE *input = fopen(WRAPPER_HOSTS_DENY, "r"); if (input != NULL) { char line[MAXBUF]; while (fgets(line, sizeof(line), input) != NULL) { fputs(line, output); } fclose(input); } if (fprintf(output, "%s\n", commandStringFinal) < 0) { Error("Error writing to temporary file %s", tempFile); fclose(output); unlink(tempFile); return ERROR; } fclose(output); if (rename(tempFile, WRAPPER_HOSTS_DENY) == -1) { Error("Cannot rename temporary file %s to %s: %s", tempFile, WRAPPER_HOSTS_DENY, ErrnoString(err, sizeof(err))); unlink(tempFile); return ERROR; } Log("attackalert: Host %s has been blocked via wrappers with string: \"%s\"", target, commandStringFinal); return TRUE; } int FindInFile(const char *searchString, const char *filename) { FILE *fp = NULL; char line[MAXBUF]; size_t searchLen; int status = ERROR; char err[ERRNOMAXBUF]; struct stat st; if (searchString == NULL || filename == NULL) { Error("Invalid parameters to FindInFile"); goto exit; } if ((fp = fopen(filename, "r")) == NULL) { Error("Unable to open file %s for reading: %s", filename, ErrnoString(err, sizeof(err))); goto exit; } if (fstat(fileno(fp), &st) == -1) { Error("Cannot stat file %s: %s", filename, ErrnoString(err, sizeof(err))); goto exit; } if (S_ISLNK(st.st_mode)) { Error("File %s is a symbolic link, refusing to read", filename); goto exit; } if ((st.st_mode & S_IWOTH) != 0) { Error("File %s is world-writable, refusing to read", filename); goto exit; } searchLen = strlen(searchString); if (searchLen == 0 || searchLen >= MAXBUF) { Error("Invalid search string length"); goto exit; } while (fgets(line, sizeof(line), fp) != NULL) { size_t lineLen = strlen(line); if (lineLen == sizeof(line) - 1 && line[lineLen - 1] != '\n') { Error("Line too long in file %s", filename); status = ERROR; goto exit; } if (lineLen > 0 && line[lineLen - 1] == '\n') { line[lineLen - 1] = '\0'; lineLen--; } if (lineLen == searchLen && strcmp(line, searchString) == 0) { status = TRUE; goto exit; } } status = FALSE; exit: if (fp != NULL) { fclose(fp); } return status; } /********************************************************************************* * String substitute function * * replaceToken - The token to replace with. * findToken - The token to find. * source - The source string to search. * dest - The destination string to copy to. * destSize - The size of the destination buffer. * * Returns the number of substitutions made during the operation. * Returns ERROR on failure. **********************************************************************************/ int SubstString(const char *replaceToken, const char *findToken, const char *source, char *dest, const int destSize) { if (replaceToken == NULL || findToken == NULL || source == NULL || dest == NULL || destSize <= 0) { return ERROR; } if (strlen(findToken) == 0) { return ERROR; } size_t remainDestSize = (size_t)destSize; size_t findTokenLen = strlen(findToken); size_t replaceTokenLen = strlen(replaceToken); int numberOfSubst = 0; const char *srcPtr = source; char *destPtr = dest; while (remainDestSize > 1) { const char *srcToken = strstr(srcPtr, findToken); if (!srcToken) { break; } size_t chunkSize = (size_t)(srcToken - srcPtr); if (chunkSize >= remainDestSize) { return ERROR; } if (chunkSize > 0) { memcpy(destPtr, srcPtr, chunkSize); destPtr += chunkSize; remainDestSize -= chunkSize; } if (replaceTokenLen >= remainDestSize) { return ERROR; } if (replaceTokenLen > 0) { memcpy(destPtr, replaceToken, replaceTokenLen); destPtr += replaceTokenLen; remainDestSize -= replaceTokenLen; } srcPtr = srcToken + findTokenLen; numberOfSubst++; } size_t remainingLen = strlen(srcPtr); if (remainingLen >= remainDestSize) { return ERROR; } if (remainingLen > 0) { memcpy(destPtr, srcPtr, remainingLen); destPtr += remainingLen; remainDestSize -= remainingLen; } *destPtr = '\0'; return numberOfSubst; } int TestFileAccess(const char *filename, const char *mode, const uint8_t createDir) { FILE *testFile = NULL; char *pathCopy = NULL, *dirPath; int status = FALSE; if ((testFile = fopen(filename, mode)) != NULL) { fclose(testFile); return TRUE; } if (createDir == FALSE) { goto exit; } if ((pathCopy = strdup(filename)) == NULL) { Error("Unable to allocate memory for path copy: %s", filename); goto exit; } if ((dirPath = dirname(pathCopy)) == NULL) { Error("Unable to get directory name from path: %s", filename); goto exit; } if (MkdirP(dirPath) != TRUE) { goto exit; } if ((testFile = fopen(filename, mode)) == NULL) { goto exit; } status = TRUE; exit: if (testFile != NULL) { fclose(testFile); } if (pathCopy != NULL) { free(pathCopy); } return status; } static int MkdirP(const char *path) { char *p; char *pathCopy = NULL; int status = ERROR; char err[ERRNOMAXBUF]; assert(path != NULL); if ((pathCopy = strdup(path)) == NULL) { Error("Could not allocate memory for path copy: %s", ErrnoString(err, sizeof(err))); goto exit; } for (p = strchr(pathCopy + 1, '/'); p; p = strchr(p + 1, '/')) { *p = '\0'; if (mkdir(pathCopy, 0755) == -1 && errno != EEXIST) { Error("Could not create directory: %s: %s", pathCopy, ErrnoString(err, sizeof(err))); goto exit; } *p = '/'; } if (mkdir(pathCopy, 0755) == -1 && errno != EEXIST) { Error("Could not create directory: %s: %s", pathCopy, ErrnoString(err, sizeof(err))); goto exit; } status = TRUE; exit: if (pathCopy != NULL) { free(pathCopy); } return status; } void XmitBannerIfConfigured(const int proto, const int socket, const struct sockaddr *saddr, const socklen_t saddrLen) { ssize_t result = 0; char err[ERRNOMAXBUF]; assert(proto == IPPROTO_TCP || proto == IPPROTO_UDP); if (configData.portBannerPresent == FALSE) return; errno = 0; if (proto == IPPROTO_TCP) { result = write(socket, configData.portBanner, strlen(configData.portBanner)); } else if (proto == IPPROTO_UDP) { if (saddr == NULL) { Error("No client address specified for UDP banner transmission (ignoring)"); return; } const struct sockaddr *addr = saddr; result = sendto(socket, configData.portBanner, strlen(configData.portBanner), 0, addr, saddrLen); } if (result == -1) { Error("Could not write banner to socket (ignoring): %s", ErrnoString(err, sizeof(err))); } } portsentry-2.0.6/src/io.h000066400000000000000000000027141510117642400153240ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Craig Rowland // SPDX-FileContributor: Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include #include #include #include "packet_info.h" __attribute__((format(printf, 1, 2))) void Log(const char *, ...); __attribute__((format(printf, 1, 2))) void Error(const char *, ...); __attribute__((format(printf, 1, 2))) void Debug(const char *logentry, ...); __attribute__((format(printf, 1, 2))) void Verbose(const char *logentry, ...); __attribute__((format(printf, 2, 3))) void Crash(const int errCode, const char *logentry, ...); void Exit(const int); int NeverBlock(const char *, const char *); int CheckConfig(void); int OpenSocket(const int family, const int type, const int protocol, const uint8_t tcpReuseAddr); int BindSocket(const int, const struct sockaddr *, const socklen_t, const uint8_t proto); int KillRoute(const char *, const int, const char *, const char *); int KillHostsDeny(const char *, const int, const char *, const char *); int KillRunCmd(const char *, const int, const char *, const char *); int FindInFile(const char *, const char *); int SubstString(const char *replaceToken, const char *findToken, const char *source, char *dest, const int destSize); int TestFileAccess(const char *, const char *, const uint8_t); void XmitBannerIfConfigured(const int proto, const int socket, const struct sockaddr *saddr, const socklen_t saddrLen); portsentry-2.0.6/src/kernelmsg.h000066400000000000000000000020341510117642400166770ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include #include #include #ifdef __linux__ #include #include #endif #include "pcap_listener.h" #include "pcap_device.h" enum KernelMessageType { KMT_UNKNOWN = 0, KMT_INTERFACE, KMT_ADDRESS, }; enum KernelMessageAction { KMA_UNKNOWN = 0, KMA_ADD, KMA_DEL, }; struct KernelMessage { enum KernelMessageType type; enum KernelMessageAction action; union { struct { char ifName[IF_NAMESIZE]; } interface; struct { int family; char ipAddr[INET6_ADDRSTRLEN]; char ifName[IF_NAMESIZE]; } address; }; }; int ListenKernel(void); #ifdef __linux__ int ParseKernelMessage(const struct nlmsghdr *nh, struct KernelMessage *kernelMessage); #elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) int ParseKernelMessage(const char *buf, struct KernelMessage *kernelMessage); #endif portsentry-2.0.6/src/kernelmsg_bsd.c000066400000000000000000000120751510117642400175300ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include #include #include #include #include #include #include "portsentry.h" #include "io.h" #include "util.h" #include "kernelmsg.h" #if defined(__OpenBSD__) || defined(__FreeBSD__) #define ROUNDUP(a) \ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) #define RT_ADVANCE(x, n) (x += ROUNDUP((n)->sa_len)) #endif static int HandleInterfaceAnnounce(const char *buf, struct KernelMessage *kernelMessage); static int HandleAddressChange(const char *buf, struct KernelMessage *kernelMessage); static int HandleRTAX_IFA(const struct sockaddr *sa, struct KernelMessage *kernelMessage); static int HandleRTAX_IFP(const struct sockaddr *sa, struct KernelMessage *kernelMessage); int ListenKernel(void) { char err[ERRNOMAXBUF]; int sockFd; if ((sockFd = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC)) < 0) { Error("Failed to create routing socket: %s", ErrnoString(err, sizeof(err))); return ERROR; } return sockFd; } int ParseKernelMessage(const char *buf, struct KernelMessage *kernelMessage) { const struct rt_msghdr *rtm = (const struct rt_msghdr *)buf; memset(kernelMessage, 0, sizeof(struct KernelMessage)); if (rtm->rtm_type == RTM_IFANNOUNCE) { return HandleInterfaceAnnounce(buf, kernelMessage); } else if ((rtm->rtm_type == RTM_NEWADDR || rtm->rtm_type == RTM_DELADDR) && rtm->rtm_addrs > 0) { return HandleAddressChange(buf, kernelMessage); } return FALSE; } static int HandleInterfaceAnnounce(const char *buf, struct KernelMessage *kernelMessage) { const struct if_announcemsghdr *ifan = (const struct if_announcemsghdr *)buf; kernelMessage->type = KMT_INTERFACE; switch (ifan->ifan_what) { case IFAN_ARRIVAL: if (strlen(ifan->ifan_name) >= IF_NAMESIZE) { Error("Interface name %s is too long", ifan->ifan_name); return FALSE; } Debug("Interface %s (index %d) arrived", ifan->ifan_name, ifan->ifan_index); kernelMessage->action = KMA_ADD; SafeStrncpy(kernelMessage->interface.ifName, ifan->ifan_name, IF_NAMESIZE); break; case IFAN_DEPARTURE: if (strlen(ifan->ifan_name) >= IF_NAMESIZE) { Error("Interface name %s is too long", ifan->ifan_name); return FALSE; } Debug("Interface %s (index %d) departed", ifan->ifan_name, ifan->ifan_index); kernelMessage->action = KMA_DEL; SafeStrncpy(kernelMessage->interface.ifName, ifan->ifan_name, IF_NAMESIZE); break; default: Error("Unknown interface announce type: IFAN_WHAT: %d, ignoring", ifan->ifan_what); return FALSE; } return TRUE; } static int HandleAddressChange(const char *buf, struct KernelMessage *kernelMessage) { const struct ifa_msghdr *ifam = (const struct ifa_msghdr *)buf; const struct sockaddr *sa; const char *cp = (const char *)(ifam + 1); kernelMessage->type = KMT_ADDRESS; kernelMessage->action = ifam->ifam_type == RTM_NEWADDR ? KMA_ADD : KMA_DEL; for (int i = 0; i < RTAX_MAX; i++) { if (ifam->ifam_addrs & (1 << i)) { sa = (const struct sockaddr *)cp; if (i == RTAX_IFA) { if (HandleRTAX_IFA(sa, kernelMessage) == FALSE) { return FALSE; } } else if (i == RTAX_IFP) { if (HandleRTAX_IFP(sa, kernelMessage) == FALSE) { return FALSE; } } RT_ADVANCE(cp, sa); } } Debug("Final kernel message: %s IPv%d address %s on interface %s index %d", kernelMessage->action == KMA_ADD ? "Added" : "Removed", kernelMessage->address.family == AF_INET ? 4 : 6, kernelMessage->address.ipAddr, kernelMessage->address.ifName, ifam->ifam_index); return TRUE; } static int HandleRTAX_IFA(const struct sockaddr *sa, struct KernelMessage *kernelMessage) { if (sa->sa_family == AF_INET) { inet_ntop(AF_INET, &((const struct sockaddr_in *)sa)->sin_addr, kernelMessage->address.ipAddr, sizeof(kernelMessage->address.ipAddr)); } else if (sa->sa_family == AF_INET6) { inet_ntop(AF_INET6, &((const struct sockaddr_in6 *)sa)->sin6_addr, kernelMessage->address.ipAddr, sizeof(kernelMessage->address.ipAddr)); } else { Error("Unexpected address family: %d for RTAX_IFA. Unable to parse address", sa->sa_family); return FALSE; } kernelMessage->address.family = sa->sa_family; return TRUE; } static int HandleRTAX_IFP(const struct sockaddr *sa, struct KernelMessage *kernelMessage) { if (sa->sa_family != AF_LINK) { Error("Unexpected address family: %d for RTAX_IFP. Unable to parse interface name", sa->sa_family); return FALSE; } const struct sockaddr_dl *sdl = (const struct sockaddr_dl *)sa; if (sdl->sdl_nlen >= IF_NAMESIZE) { Error("Unexpected interface length %d (IF_NAMESIZE: %d) on RTAX_IFP", sdl->sdl_nlen, IF_NAMESIZE); return FALSE; } else { SafeStrncpy(kernelMessage->address.ifName, sdl->sdl_data, sdl->sdl_nlen + 1); } return TRUE; } portsentry-2.0.6/src/kernelmsg_linux.c000066400000000000000000000070501510117642400201140ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include #include #include #include "portsentry.h" #include "io.h" #include "util.h" #include "kernelmsg.h" static int ParseInterface(const struct nlmsghdr *nh, struct KernelMessage *kernelMessage); static int ParseAddress(const struct nlmsghdr *nh, struct KernelMessage *kernelMessage); int ListenKernel(void) { char err[ERRNOMAXBUF]; int sock_fd; struct sockaddr_nl addr; if ((sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) { Error("Failed to create netlink socket: %s", ErrnoString(err, sizeof(err))); return ERROR; } memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; if (bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { Error("Failed to bind netlink socket: %s", ErrnoString(err, sizeof(err))); close(sock_fd); return ERROR; } return sock_fd; } int ParseKernelMessage(const struct nlmsghdr *nh, struct KernelMessage *kernelMessage) { memset(kernelMessage, 0, sizeof(struct KernelMessage)); if (nh->nlmsg_type == RTM_NEWADDR || nh->nlmsg_type == RTM_DELADDR) { struct ifaddrmsg *ifa = NLMSG_DATA(nh); // Adding IPv6 address will trigger 2 messages, only care when address is added if (nh->nlmsg_type == RTM_NEWADDR && ifa->ifa_family == AF_INET6 && !(ifa->ifa_flags & IFA_F_TENTATIVE)) { Debug("ParseKernelMessage: IPv6 addr tentative, ignore"); return FALSE; } } if (nh->nlmsg_type == RTM_NEWLINK || nh->nlmsg_type == RTM_DELLINK) { return ParseInterface(nh, kernelMessage); } else if (nh->nlmsg_type == RTM_NEWADDR || nh->nlmsg_type == RTM_DELADDR) { return ParseAddress(nh, kernelMessage); } Debug("Abort: Failed to handle message"); return FALSE; } static int ParseInterface(const struct nlmsghdr *nh, struct KernelMessage *kernelMessage) { struct ifinfomsg *ifi = NLMSG_DATA(nh); struct rtattr *rta; size_t payload_len = RTM_PAYLOAD(nh); kernelMessage->type = KMT_INTERFACE; kernelMessage->action = nh->nlmsg_type == RTM_NEWLINK ? KMA_ADD : KMA_DEL; for (rta = IFLA_RTA(ifi); RTA_OK(rta, payload_len); rta = RTA_NEXT(rta, payload_len)) { if (rta->rta_type == IFLA_IFNAME) { if (strlen(RTA_DATA(rta)) >= IF_NAMESIZE) { Error("ParseInterface: Interface name %s is too long", (char *)RTA_DATA(rta)); return FALSE; } SafeStrncpy(kernelMessage->interface.ifName, RTA_DATA(rta), IF_NAMESIZE); return TRUE; } } return FALSE; } static int ParseAddress(const struct nlmsghdr *nh, struct KernelMessage *kernelMessage) { struct ifaddrmsg *ifa = NLMSG_DATA(nh); struct rtattr *rta; size_t payload_len = RTM_PAYLOAD(nh); int target_type = (ifa->ifa_family == AF_INET6) ? IFA_ADDRESS : IFA_LOCAL; kernelMessage->type = KMT_ADDRESS; kernelMessage->action = nh->nlmsg_type == RTM_NEWADDR ? KMA_ADD : KMA_DEL; kernelMessage->address.family = ifa->ifa_family; // Get interface name from index if (if_indextoname(ifa->ifa_index, kernelMessage->address.ifName) == NULL) { return FALSE; } for (rta = IFA_RTA(ifa); RTA_OK(rta, payload_len); rta = RTA_NEXT(rta, payload_len)) { if (rta->rta_type == target_type) { inet_ntop(ifa->ifa_family, RTA_DATA(rta), kernelMessage->address.ipAddr, INET6_ADDRSTRLEN - 1); return TRUE; } } return FALSE; } portsentry-2.0.6/src/packet_info.c000066400000000000000000000257141510117642400171770ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include #include #include #include "portsentry.h" #include "packet_info.h" #include "io.h" #include "util.h" #define IPV4_MAPPED_IPV6_PREFIX "::ffff:" static int SetSockaddr4(struct sockaddr_in *sa, const in_addr_t *addr, const uint16_t port, char *buf, const socklen_t buflen); static int SetSockaddr6(struct sockaddr_in6 *sa6, const struct in6_addr *addr6, const uint16_t port, char *buf, const socklen_t buflen); // Create a lookup table for IPv6 extension headers static const uint8_t IPV6_EXT_HEADERS[] = { 0, // Hop-by-Hop 43, // Routing 44, // Fragment 50, // ESP 51, // Auth 59, // No Next 60, // Dest Options 135, // Mobility 139, // HIP 140, // Shim6 253, // Experimental 254 // Experimental }; static int IsIpv6ExtensionHeader(uint8_t header) { for (size_t i = 0; i < sizeof(IPV6_EXT_HEADERS); i++) { if (header == IPV6_EXT_HEADERS[i]) return TRUE; } return FALSE; } void ClearPacketInfo(struct PacketInfo *pi) { memset(pi, 0, sizeof(struct PacketInfo)); pi->listenSocket = -1; pi->tcpAcceptSocket = -1; } int SetPacketInfoFromPacket(struct PacketInfo *pi, const unsigned char *packet, const uint32_t packetLength) { size_t iplen; uint8_t nextHeader; uint8_t protocol, ipVersion; const struct ip6_ext *ip6ext; const struct ip *ip = NULL; const struct ip6_hdr *ip6 = NULL; const struct tcphdr *tcp = NULL; const struct udphdr *udp = NULL; assert(pi != NULL); assert(packet != NULL); pi->packet = packet; ipVersion = (*pi->packet >> 4) & 0x0f; if (ipVersion == 4) { if (packetLength < 20) { Error("IPv4 packet is too short (%d bytes), ignoring", packetLength); return FALSE; } ip = (const struct ip *)pi->packet; iplen = ip->ip_hl * 4; protocol = ip->ip_p; } else if (ipVersion == 6) { if (packetLength < 40) { Error("IPv6 packet is too short (%d bytes), ignoring", packetLength); return FALSE; } ip6 = (const struct ip6_hdr *)pi->packet; nextHeader = ip6->ip6_nxt; iplen = sizeof(struct ip6_hdr); while (IsIpv6ExtensionHeader(nextHeader)) { Debug("Processing IPv6 extension header %d", nextHeader); if (iplen + sizeof(struct ip6_ext) > packetLength) { Error("IPv6 extension header exceeds packet length, ignoring"); return FALSE; } // Handle special cases that should be rejected switch (nextHeader) { case 59: // IPv6-NoNxt Error("IPv6-NoNxt detected, ignoring packet"); return FALSE; case 44: // Fragment Header Error("Fragment Header for IPv6 detected, ignoring packet"); return FALSE; case 253: // Experimental case 254: Error("RFC3692 Experimental/testing header detected, ignoring packet"); return FALSE; } ip6ext = (const struct ip6_ext *)(pi->packet + iplen); nextHeader = ip6ext->ip6e_nxt; if (ip6ext->ip6e_len == 0) { Error("IPv6 extension header length is 0, ignoring packet"); return FALSE; } uint32_t extlen = ((uint32_t)ip6ext->ip6e_len * 8) + 8; if (iplen + extlen > packetLength) { Error("IPv6 extension header length exceeds packet bounds, ignoring"); return FALSE; } iplen += extlen; } protocol = nextHeader; } else { Debug("Packet have unknown IP version %d", ipVersion); return FALSE; } if (protocol == IPPROTO_TCP) { if ((int)(packetLength - iplen) < (int)sizeof(struct tcphdr)) { Error("Packet is too short for TCP header, ignoring"); return FALSE; } tcp = (const struct tcphdr *)(pi->packet + iplen); } else if (protocol == IPPROTO_UDP) { if ((int)(packetLength - iplen) < (int)sizeof(struct udphdr)) { Error("Packet is too short for UDP header, ignoring"); return FALSE; } udp = (const struct udphdr *)(pi->packet + iplen); } else { // Debug("Unknown protocol %d", protocol); return FALSE; } pi->version = ipVersion; pi->protocol = protocol; pi->port = (protocol == IPPROTO_TCP) ? ntohs(tcp->th_dport) : ntohs(udp->uh_dport); pi->ip = (ip != NULL) ? ip : NULL; pi->ip6 = (ip6 != NULL) ? ip6 : NULL; pi->tcp = (tcp != NULL) ? tcp : NULL; pi->udp = (udp != NULL) ? udp : NULL; if (pi->version != 4 && pi->version != 6) { Error("Packet validation failes, unknown IP version %d", pi->version); return FALSE; } if (pi->protocol != IPPROTO_TCP && pi->protocol != IPPROTO_UDP) { Error("Packet validation failed, unknown protocol %d", pi->protocol); return FALSE; } if (pi->ip == NULL && pi->ip6 == NULL) { Error("Packet validation failed, no IP header found"); return FALSE; } if (pi->tcp == NULL && pi->udp == NULL) { Error("Packet validation failed, no TCP or UDP header found"); return FALSE; } if (pi->ip != NULL) { if (SetSockaddr4(&pi->sa_saddr, &pi->ip->ip_src.s_addr, (tcp != NULL) ? tcp->th_sport : udp->uh_sport, pi->saddr, sizeof(pi->saddr)) != TRUE) { return FALSE; } if (SetSockaddr4(&pi->sa_daddr, &pi->ip->ip_dst.s_addr, (tcp != NULL) ? tcp->th_dport : udp->uh_dport, pi->daddr, sizeof(pi->daddr)) != TRUE) { return FALSE; } } else if (pi->ip6 != NULL) { if (SetSockaddr6(&pi->sa6_saddr, &pi->ip6->ip6_src, (tcp != NULL) ? tcp->th_sport : udp->uh_sport, pi->saddr, sizeof(pi->saddr)) != TRUE) { return FALSE; } if (SetSockaddr6(&pi->sa6_daddr, &pi->ip6->ip6_dst, (tcp != NULL) ? tcp->th_dport : udp->uh_dport, pi->daddr, sizeof(pi->daddr)) != TRUE) { return FALSE; } } return TRUE; } int SetPacketInfoFromConnectData(struct PacketInfo *pi, const uint16_t port, const int family, const uint8_t protocol, const int sockfd, const int incomingSockfd, const struct sockaddr_in *client4, const struct sockaddr_in6 *client6) { pi->protocol = protocol; pi->port = port; pi->version = (family == AF_INET) ? 4 : 6; pi->listenSocket = sockfd; pi->tcpAcceptSocket = incomingSockfd; // There will only by one correct client address, depending on the family (only valid in sentry_connect) if (pi->version == 4) { pi->client4 = client4; pi->client6 = NULL; } else { pi->client4 = NULL; pi->client6 = client6; } if (pi->version == 4) { SetSockaddr4(&pi->sa_saddr, &client4->sin_addr.s_addr, client4->sin_port, pi->saddr, sizeof(pi->saddr)); pi->sa_daddr.sin_port = port; } else { // In a dual stack environment, we may receive an IPv4-mapped IPv6 address // In this case, extract the ipv4 address and port from the mapped address // in order to present it as an IPv4 address instead of ::ffff: if (IN6_IS_ADDR_V4MAPPED(&client6->sin6_addr)) { struct in_addr addr4; memcpy(&addr4, &client6->sin6_addr.s6_addr[12], sizeof(struct in_addr)); SetSockaddr4(&pi->sa_saddr, &addr4.s_addr, client6->sin6_port, pi->saddr, sizeof(pi->saddr)); pi->sa_daddr.sin_port = port; pi->version = 4; // Since we are treating this as an IPv4 address and the sockaddr_in is set, set version to 4 too (needed by GetSourceSockaddr*()) } else { SetSockaddr6(&pi->sa6_saddr, &client6->sin6_addr, client6->sin6_port, pi->saddr, sizeof(pi->saddr)); pi->sa6_daddr.sin6_port = port; } } return TRUE; } static int SetSockaddr4(struct sockaddr_in *sa, const in_addr_t *addr, const uint16_t port, char *buf, const socklen_t buflen) { char err[ERRNOMAXBUF]; if (sa == NULL || addr == NULL) { Error("Invalid NULL parameter"); return ERROR; } memset(sa, 0, sizeof(struct sockaddr_in)); sa->sin_addr.s_addr = *addr; sa->sin_family = AF_INET; sa->sin_port = port; if (buf != NULL) { if (buflen < INET_ADDRSTRLEN) { Error("Buffer too small for IPv4 address"); return ERROR; } if (inet_ntop(AF_INET, &sa->sin_addr, buf, buflen) == NULL) { Error("Unable to resolve IP address: %s", ErrnoString(err, sizeof(err))); return ERROR; } } return TRUE; } static int SetSockaddr6(struct sockaddr_in6 *sa6, const struct in6_addr *addr6, const uint16_t port, char *buf, const socklen_t buflen) { char err[ERRNOMAXBUF]; if (sa6 == NULL || addr6 == NULL) { Error("Invalid NULL parameter"); return ERROR; } memset(sa6, 0, sizeof(struct sockaddr_in6)); memcpy(&sa6->sin6_addr, addr6, sizeof(struct in6_addr)); sa6->sin6_family = AF_INET6; sa6->sin6_port = port; if (buf != NULL) { if (buflen < INET6_ADDRSTRLEN) { Error("Buffer too small for IPv6 address"); return ERROR; } if (inet_ntop(AF_INET6, &sa6->sin6_addr, buf, buflen) == NULL) { Error("Unable to resolve IPv6 address: %s", ErrnoString(err, sizeof(err))); return ERROR; } } return TRUE; } const struct sockaddr *GetSourceSockaddrFromPacketInfo(const struct PacketInfo *pi) { if (pi->version == 6 && pi->sa6_saddr.sin6_family == AF_INET6) { return (const struct sockaddr *)&pi->sa6_saddr; } else if (pi->version == 4 && pi->sa_saddr.sin_family == AF_INET) { return (const struct sockaddr *)&pi->sa_saddr; } return NULL; } socklen_t GetSourceSockaddrLenFromPacketInfo(const struct PacketInfo *pi) { if (pi->version == 6 && pi->sa6_saddr.sin6_family == AF_INET6) { return sizeof(struct sockaddr_in6); } else if (pi->version == 4 && pi->sa_saddr.sin_family == AF_INET) { return sizeof(struct sockaddr_in); } return 0; } const struct sockaddr *GetDestSockaddrFromPacketInfo(const struct PacketInfo *pi) { if (pi->version == 6 && pi->sa6_daddr.sin6_family == AF_INET6) { return (const struct sockaddr *)&pi->sa6_daddr; } else if (pi->version == 4 && pi->sa_daddr.sin_family == AF_INET) { return (const struct sockaddr *)&pi->sa_daddr; } return NULL; } socklen_t GetDestSockaddrLenFromPacketInfo(const struct PacketInfo *pi) { if (pi->version == 6 && pi->sa6_daddr.sin6_family == AF_INET6) { return sizeof(struct sockaddr_in6); } else if (pi->version == 4 && pi->sa_daddr.sin_family == AF_INET) { return sizeof(struct sockaddr_in); } return 0; } const struct sockaddr *GetClientSockaddrFromPacketInfo(const struct PacketInfo *pi) { if (pi->client4 != NULL) { return (const struct sockaddr *)pi->client4; } else if (pi->client6 != NULL) { return (const struct sockaddr *)pi->client6; } return NULL; } socklen_t GetClientSockaddrLenFromPacketInfo(const struct PacketInfo *pi) { if (pi->client4 != NULL) { return sizeof(struct sockaddr_in); } else if (pi->client6 != NULL) { return sizeof(struct sockaddr_in6); } return 0; } int IsSameSourceAndDestAddress(const struct PacketInfo *pi) { if ((pi->version == 4 && pi->sa_saddr.sin_addr.s_addr == pi->sa_daddr.sin_addr.s_addr) || (pi->version == 6 && IN6_ARE_ADDR_EQUAL(&pi->sa6_saddr.sin6_addr, &pi->sa6_daddr.sin6_addr))) { return TRUE; } return FALSE; } portsentry-2.0.6/src/packet_info.h000066400000000000000000000056011510117642400171750ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include #include #include #include /* Convenient information about a packet, used primarily * by the sentry engine to make decisions */ struct PacketInfo { uint8_t version; // The IP version of the packet 4 or 6 uint8_t protocol; // The protocol of the packet (IPPROTO_TCP/UDP) uint16_t port; // The destination port for the packet struct sockaddr_in sa_saddr; // The source address for ipv4 connections struct sockaddr_in6 sa6_saddr; // The source address for ipv6 connections struct sockaddr_in sa_daddr; // The dest address for ipv4 connections struct sockaddr_in6 sa6_daddr; // The dest address for ipv6 connections char saddr[INET6_ADDRSTRLEN]; // The source address as a string char daddr[INET6_ADDRSTRLEN]; // The dest address as a string const unsigned char *packet; // The raw packet + pointers into the various headers, where applicable int packetLength; const struct ip *ip; // pointer into packet for ipv4 header const struct ip6_hdr *ip6; // pointer into packet for ipv6 header const struct tcphdr *tcp; // pointer into packet for tcp header (if it's a tcp packet, otherwise NULL) const struct udphdr *udp; // pointer into packet for udp header (if it's a udp packet, otherwise NULL) // Connection based (sentry_connect) information int listenSocket; // The listening socket for the connection (also used to sendUDP packets if applicable) int tcpAcceptSocket; // If TCP connection, the socket that accept()ed the connection const struct sockaddr_in *client4; // The client address for ipv4 connections as returned by accept() or recvfrom() const struct sockaddr_in6 *client6; // The client address for ipv6 connections as returned by accept() or recvfrom() }; void ClearPacketInfo(struct PacketInfo *pi); int SetPacketInfoFromPacket(struct PacketInfo *pi, const unsigned char *packet, const uint32_t packetLength); int SetPacketInfoFromConnectData(struct PacketInfo *pi, const uint16_t port, const int family, const uint8_t protocol, const int sockfd, const int incomingSockfd, const struct sockaddr_in *client4, const struct sockaddr_in6 *client6); const struct sockaddr *GetSourceSockaddrFromPacketInfo(const struct PacketInfo *pi); socklen_t GetSourceSockaddrLenFromPacketInfo(const struct PacketInfo *pi); const struct sockaddr *GetDestSockaddrFromPacketInfo(const struct PacketInfo *pi); socklen_t GetDestSockaddrLenFromPacketInfo(const struct PacketInfo *pi); const struct sockaddr *GetClientSockaddrFromPacketInfo(const struct PacketInfo *pi); socklen_t GetClientSockaddrLenFromPacketInfo(const struct PacketInfo *pi); int IsSameSourceAndDestAddress(const struct PacketInfo *pi); portsentry-2.0.6/src/pcap_device.c000066400000000000000000000434751510117642400171630ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include #include #include #include #include "portsentry.h" #include "pcap_device.h" #include "util.h" #include "io.h" #include "config_data.h" #define BUFFER_TIMEOUT 2000 static pcap_t *PcapOpenLiveImmediate(const char *source, const int snaplen, const int promisc, const int to_ms, char *errbuf); static char **RemoveElementFromArray(char **array, const size_t index, size_t *count); static char *AllocAndBuildPcapFilter(const struct Device *device); /* Heavily inspired by src/lib/libpcap/pcap-bpf.c from OpenBSD's pcap implementation. * We must use pcap_create() and pcap_activate() instead of pcap_open_live() because * we need to set the immediate mode flag, which can only be done on an unactivated * pcap_t. * * OpenBSD's libpcap implementation require immediate mode and non-blocking socket * in order for poll() (and select()/kevent()) to work properly. This approach works * for other Unixes as well, so it's no harm in doing it this way. Using immediate mode * with a non-blocking fd makes pcap a bit snappier anyway so it's a win-win. * See: https://marc.info/?l=openbsd-tech&m=169878430118943&w=2 for more information. * */ static pcap_t *PcapOpenLiveImmediate(const char *source, const int snaplen, const int promisc, const int to_ms, char *errbuf) { pcap_t *p; int status; if ((p = pcap_create(source, errbuf)) == NULL) return (NULL); if ((status = pcap_set_snaplen(p, snaplen)) < 0) goto fail; if ((status = pcap_set_promisc(p, promisc)) < 0) goto fail; if ((status = pcap_set_timeout(p, to_ms)) < 0) goto fail; if ((status = pcap_set_immediate_mode(p, 1)) < 0) goto fail; if ((status = pcap_activate(p)) < 0) goto fail; return (p); fail: SafeStrncpy(errbuf, pcap_geterr(p), PCAP_ERRBUF_SIZE); pcap_close(p); return (NULL); } static char **RemoveElementFromArray(char **array, const size_t index, size_t *count) { char **tmp = array; assert(array != NULL); assert(count != NULL); assert(index < *count); assert(*count > 0); free(array[index]); (*count)--; for (size_t i = index; i < *count; i++) { array[i] = array[i + 1]; } if (*count > 0) { if ((tmp = realloc(array, sizeof(char *) * *count)) == NULL) { Crash(1, "Unable to reallocate IP address memory"); } } else { free(array); tmp = NULL; } return tmp; } static char *AllocAndBuildPcapFilter(const struct Device *device) { size_t i; size_t filterLen = 0; char *filter = NULL; assert(device != NULL); if (device->inet4_addrs_count > 0 || device->inet6_addrs_count > 0) { filter = ReallocAndAppend(filter, &filterLen, "("); } for (i = 0; i < device->inet4_addrs_count; i++) { if (i > 0) { filter = ReallocAndAppend(filter, &filterLen, " or "); } filter = ReallocAndAppend(filter, &filterLen, "ip dst host %s", device->inet4_addrs[i]); } if (device->inet4_addrs_count > 0 && device->inet6_addrs_count > 0) { filter = ReallocAndAppend(filter, &filterLen, " or "); } for (i = 0; i < device->inet6_addrs_count; i++) { if (i > 0) { filter = ReallocAndAppend(filter, &filterLen, " or "); } filter = ReallocAndAppend(filter, &filterLen, "ip6 dst host %s", device->inet6_addrs[i]); } if (device->inet4_addrs_count > 0 || device->inet6_addrs_count > 0) { filter = ReallocAndAppend(filter, &filterLen, ")"); } filter = ReallocAndAppend(filter, &filterLen, " and ("); if (configData.tcpPortsLength > 0) { if (configData.tcpPortsLength > 0 && configData.udpPortsLength > 0) { filter = ReallocAndAppend(filter, &filterLen, "("); } for (i = 0; i < configData.tcpPortsLength; i++) { if (i > 0) { filter = ReallocAndAppend(filter, &filterLen, " or "); } if (IsPortSingle(&configData.tcpPorts[i])) { filter = ReallocAndAppend(filter, &filterLen, "tcp dst port %d", configData.tcpPorts[i].single); } else { /* OpenBSD's libpcap doesn't support portrange */ #ifdef __OpenBSD__ for (int j = configData.tcpPorts[i].range.start; j <= configData.tcpPorts[i].range.end; j++) { filter = ReallocAndAppend(filter, &filterLen, "tcp dst port %d", j); if (j < configData.tcpPorts[i].range.end) { filter = ReallocAndAppend(filter, &filterLen, " or "); } } #else filter = ReallocAndAppend(filter, &filterLen, "tcp dst portrange %d-%d", configData.tcpPorts[i].range.start, configData.tcpPorts[i].range.end); #endif } } if (configData.tcpPortsLength > 0 && configData.udpPortsLength > 0) { filter = ReallocAndAppend(filter, &filterLen, ")"); } } if (configData.udpPortsLength > 0) { if (configData.tcpPortsLength > 0 && configData.udpPortsLength > 0) { filter = ReallocAndAppend(filter, &filterLen, " or ("); } for (i = 0; i < configData.udpPortsLength; i++) { if (i > 0) { filter = ReallocAndAppend(filter, &filterLen, " or "); } if (IsPortSingle(&configData.udpPorts[i])) { filter = ReallocAndAppend(filter, &filterLen, "udp dst port %d", configData.udpPorts[i].single); } else { /* OpenBSD's libpcap doesn't support portrange */ #ifdef __OpenBSD__ for (int j = configData.udpPorts[i].range.start; j <= configData.udpPorts[i].range.end; j++) { filter = ReallocAndAppend(filter, &filterLen, "udp dst port %d", j); if (j < configData.udpPorts[i].range.end) { filter = ReallocAndAppend(filter, &filterLen, " or "); } } #else filter = ReallocAndAppend(filter, &filterLen, "udp dst portrange %d-%d", configData.udpPorts[i].range.start, configData.udpPorts[i].range.end); #endif } } if (configData.tcpPortsLength > 0 && configData.udpPortsLength > 0) { filter = ReallocAndAppend(filter, &filterLen, ")"); } } filter = ReallocAndAppend(filter, &filterLen, ")"); Debug("Device: %s pcap filter len %zu: [%s]", device->name, filterLen, filter); return filter; } struct Device *CreateDevice(const char *name) { struct Device *new; if (name == NULL) { Error("Device name cannot be NULL"); return NULL; } if (strlen(name) >= IF_NAMESIZE) { Error("Device name %s is too long", name); return NULL; } if ((new = calloc(1, sizeof(struct Device))) == NULL) { Error("Unable to allocate memory for device %s", name); return NULL; } SafeStrncpy(new->name, name, IF_NAMESIZE); return new; } int AddAddress(struct Device *device, const char *address, const int type) { char **addresses = NULL; size_t addresses_count = 0; assert(device != NULL); assert(address != NULL); assert(type == AF_INET || type == AF_INET6); if (AddressExists(device, address, type) == TRUE) { Debug("AddAddress: Address %s already exists on %s, skipping", address, device->name); return FALSE; } if (type == AF_INET) { struct sockaddr_in addr4; if (inet_pton(AF_INET, address, &addr4.sin_addr) != 1) { Error("Invalid IPv4 address format: %s", address); return ERROR; } // Check for IPv4 link-local (169.254.0.0/16) uint32_t addr = ntohl(addr4.sin_addr.s_addr); if ((addr & 0xFFFF0000) == 0xA9FE0000) { Debug("Ignoring IPv4 link-local address %s on %s", address, device->name); return FALSE; } } else if (type == AF_INET6) { struct sockaddr_in6 addr6; if (inet_pton(AF_INET6, address, &addr6.sin6_addr) != 1) { Error("Invalid IPv6 address format: %s", address); return ERROR; } if (IN6_IS_ADDR_LINKLOCAL(&addr6.sin6_addr)) { Debug("Ignoring IPv6 link-local address %s on %s", address, device->name); return FALSE; } } if (type == AF_INET) { addresses = device->inet4_addrs; addresses_count = device->inet4_addrs_count; } else if (type == AF_INET6) { addresses = device->inet6_addrs; addresses_count = device->inet6_addrs_count; } else { Crash(1, "Invalid address type"); } if ((addresses = realloc(addresses, sizeof(char *) * (addresses_count + 1))) == NULL) { Crash(1, "Unable to reallocate IP address memory"); } if ((addresses[addresses_count] = strdup(address)) == NULL) { Crash(1, "Unable to allocate memory for address %s", address); } addresses_count++; if (type == AF_INET) { device->inet4_addrs = addresses; device->inet4_addrs_count = addresses_count; } else if (type == AF_INET6) { device->inet6_addrs = addresses; device->inet6_addrs_count = addresses_count; } else { Crash(1, "Invalid address type"); } return TRUE; } int AddressExists(const struct Device *device, const char *address, const int type) { size_t i, addresses_count = 0; char **addresses = NULL; assert(device != NULL); assert(address != NULL); assert(type == AF_INET || type == AF_INET6); if (type == AF_INET) { addresses = device->inet4_addrs; addresses_count = device->inet4_addrs_count; } else if (type == AF_INET6) { addresses = device->inet6_addrs; addresses_count = device->inet6_addrs_count; } else { Crash(1, "Invalid address type"); } for (i = 0; i < addresses_count; i++) { if (strncmp(addresses[i], address, strlen(addresses[i])) == 0) { return TRUE; } } return FALSE; } size_t GetNoAddresses(const struct Device *device) { assert(device != NULL); return device->inet4_addrs_count + device->inet6_addrs_count; } int RemoveAddress(struct Device *device, const char *address) { size_t i; assert(device != NULL); assert(address != NULL); for (i = 0; i < device->inet4_addrs_count; i++) { if (strcmp(device->inet4_addrs[i], address) == 0) { device->inet4_addrs = RemoveElementFromArray(device->inet4_addrs, i, &device->inet4_addrs_count); return TRUE; } } for (i = 0; i < device->inet6_addrs_count; i++) { if (strcmp(device->inet6_addrs[i], address) == 0) { device->inet6_addrs = RemoveElementFromArray(device->inet6_addrs, i, &device->inet6_addrs_count); return TRUE; } } return FALSE; } void RemoveAllAddresses(struct Device *device) { size_t i; assert(device != NULL); if (device->inet4_addrs != NULL) { for (i = 0; i < device->inet4_addrs_count; i++) { free(device->inet4_addrs[i]); } free(device->inet4_addrs); device->inet4_addrs = NULL; device->inet4_addrs_count = 0; } if (device->inet6_addrs != NULL) { for (i = 0; i < device->inet6_addrs_count; i++) { free(device->inet6_addrs[i]); } free(device->inet6_addrs); device->inet6_addrs = NULL; device->inet6_addrs_count = 0; } } int SetAllAddresses(struct Device *device) { int status = TRUE; struct ifaddrs *ifaddrs = NULL, *ifa = NULL; char err[ERRNOMAXBUF]; char host[NI_MAXHOST]; if (getifaddrs(&ifaddrs) == -1) { Error("Unable to retrieve network addresses: %s", ErrnoString(err, ERRNOMAXBUF)); status = ERROR; goto cleanup; } for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr == NULL) { continue; } if (ifa->ifa_addr->sa_family != AF_INET && ifa->ifa_addr->sa_family != AF_INET6) { continue; } if (strncmp(device->name, ifa->ifa_name, strlen(device->name)) != 0) { continue; } // Ignore link-local addresses before calling getnameinfo() since Linux and BSD have different link-local address formats. Linux includes the scope id, BSD doesn't if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)ifa->ifa_addr; if (IN6_IS_ADDR_LINKLOCAL(&addr6->sin6_addr)) { continue; } } else if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in *addr4 = (struct sockaddr_in *)ifa->ifa_addr; uint32_t addr = ntohl(addr4->sin_addr.s_addr); if ((addr & 0xFFFF0000) == 0xA9FE0000) { continue; } } if (getnameinfo(ifa->ifa_addr, (ifa->ifa_addr->sa_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == -1) { Crash(1, "Unable to retrieve network addresses for device %s: %s", device->name, ErrnoString(err, ERRNOMAXBUF)); } Debug("Found address %s for device %s", host, device->name); if (ifa->ifa_addr->sa_family == AF_INET) { AddAddress(device, host, AF_INET); } else if (ifa->ifa_addr->sa_family == AF_INET6) { AddAddress(device, host, AF_INET6); } else { Error("Unknown address family %d for address %s, ignoring", ifa->ifa_addr->sa_family, host); } } cleanup: if (ifaddrs != NULL) { freeifaddrs(ifaddrs); } return status; } uint8_t FreeDevice(struct Device *device) { size_t i; if (device == NULL) { return FALSE; } StopDevice(device); if (device->inet4_addrs != NULL) { for (i = 0; i < device->inet4_addrs_count; i++) { free(device->inet4_addrs[i]); } free(device->inet4_addrs); device->inet4_addrs = NULL; } if (device->inet6_addrs != NULL) { for (i = 0; i < device->inet6_addrs_count; i++) { free(device->inet6_addrs[i]); } free(device->inet6_addrs); device->inet6_addrs = NULL; } free(device); return TRUE; } uint8_t StopDevice(struct Device *device) { assert(device != NULL); if (device->state == DEVICE_STATE_STOPPED) { Debug("StopDevice: Device %s is already stopped, skipping", device->name); return TRUE; } if (device->state == DEVICE_STATE_ERROR) { Error("StopDevice: Device %s is in error state, skipping", device->name); return FALSE; } pcap_close(device->handle); device->handle = NULL; device->state = DEVICE_STATE_STOPPED; device->fd = -1; Debug("StopDevice: Device %s stopped", device->name); return TRUE; } int StartDevice(struct Device *device) { int status = ERROR; char errbuf[PCAP_ERRBUF_SIZE]; assert(device != NULL); if (device->state == DEVICE_STATE_RUNNING) { Debug("StartDevice: Device %s is already running, skipping", device->name); status = TRUE; goto exit; } RemoveAllAddresses(device); if (SetAllAddresses(device) == ERROR) { Error("StartDevice: Unable to set all addresses for device %s, skipping", device->name); status = ERROR; goto exit; } if (GetNoAddresses(device) == 0) { Error("StartDevice: Device %s has no addresses, skipping", device->name); status = FALSE; goto exit; } if ((device->handle = PcapOpenLiveImmediate(device->name, BUFSIZ, 0, BUFFER_TIMEOUT, errbuf)) == NULL) { Error("StartDevice: Couldn't open device %s: %s", device->name, errbuf); status = ERROR; goto exit; } if (pcap_setnonblock(device->handle, 1, errbuf) < 0) { Error("StartDevice: Unable to set pcap_setnonblock on %s: %s", device->name, errbuf); status = ERROR; goto exit; } /* * OpenBSD and NetBSD has some quirks with pcap_setdirection(). Neither one of them will detect packets on the loopback interface * if direction is set to PCAP_D_IN for example. There are some other inconsistencies as well and I might not have found all of them. * By setting direction to PCAP_D_INOUT we make sure to capture as much as possible. The BPF filter will take care of most unwanted packets * anyway so atleast for now, we set this to PCAP_D_INOUT on all platforms in order to avoid any potential missed packets. */ if (pcap_setdirection(device->handle, PCAP_D_INOUT) < 0) { Error("StartDevice: Couldn't set direction on %s: %s", device->name, pcap_geterr(device->handle)); status = ERROR; goto exit; } // We assume that since pcap_lookupnet() succeeded, we have a valid link type if (pcap_datalink(device->handle) != DLT_EN10MB && pcap_datalink(device->handle) != DLT_RAW && pcap_datalink(device->handle) != DLT_NULL #ifdef __linux__ && pcap_datalink(device->handle) != DLT_LINUX_SLL #elif __OpenBSD__ && pcap_datalink(device->handle) != DLT_LOOP #endif ) { Error("StartDevice: Device %s is unsupported (linktype: %d), skipping this device", device->name, pcap_datalink(device->handle)); status = ERROR; goto exit; } if ((device->fd = pcap_get_selectable_fd(device->handle)) < 0) { Error("StartDevice: Couldn't get file descriptor on device %s: %s", device->name, pcap_geterr(device->handle)); status = ERROR; goto exit; } if (SetupFilter(device) == ERROR) { Error("StartDevice: Unable to setup filter for device %s, skipping", device->name); status = ERROR; goto exit; } status = TRUE; exit: if (status == ERROR || status == FALSE) { if (device->handle != NULL) { pcap_close(device->handle); device->handle = NULL; } device->fd = -1; if (status == ERROR) { device->state = DEVICE_STATE_ERROR; } else { device->state = DEVICE_STATE_STOPPED; } } else { device->state = DEVICE_STATE_RUNNING; } return status; } int SetupFilter(const struct Device *device) { struct bpf_program fp; char *filter = NULL; int status = ERROR; uint8_t isCompiled = FALSE; assert(device != NULL); assert(device->handle != NULL); if ((filter = AllocAndBuildPcapFilter(device)) == NULL) { goto exit; } // Using PCAP_NETMASK_UNKNOWN because we might use IPv6 and mask is only used for broadcast packets which we don't care about if (pcap_compile(device->handle, &fp, filter, 1, PCAP_NETMASK_UNKNOWN) == PCAP_ERROR) { Error("SetupFilter: Unable to compile pcap filter %s: %s", filter, pcap_geterr(device->handle)); goto exit; } isCompiled = TRUE; if (pcap_setfilter(device->handle, &fp) == PCAP_ERROR) { Error("SetupFilter: Unable to set filter %s: %s", filter, pcap_geterr(device->handle)); goto exit; } status = TRUE; exit: if (filter != NULL) { free(filter); filter = NULL; } if (isCompiled) { pcap_freecode(&fp); } return status; } portsentry-2.0.6/src/pcap_device.h000066400000000000000000000020641510117642400171550ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include #include enum DeviceState { DEVICE_STATE_STOPPED = 0, DEVICE_STATE_RUNNING, DEVICE_STATE_ERROR, }; struct Device { pcap_t *handle; char name[IF_NAMESIZE]; int fd; enum DeviceState state; char **inet4_addrs; size_t inet4_addrs_count; char **inet6_addrs; size_t inet6_addrs_count; struct Device *next; }; struct Device *CreateDevice(const char *name); uint8_t FreeDevice(struct Device *device); int StartDevice(struct Device *device); uint8_t StopDevice(struct Device *device); int AddAddress(struct Device *device, const char *address, const int type); int AddressExists(const struct Device *device, const char *address, const int type); size_t GetNoAddresses(const struct Device *device); int RemoveAddress(struct Device *device, const char *address); void RemoveAllAddresses(struct Device *device); int SetAllAddresses(struct Device *device); int SetupFilter(const struct Device *device); portsentry-2.0.6/src/pcap_listener.c000066400000000000000000000223631510117642400175420ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include #include #include #include #include #include #include "port.h" #include "portsentry.h" #include "pcap_listener.h" #include "config_data.h" #include "io.h" #include "util.h" #include "pcap_device.h" static uint8_t CreateAndAddDevice(struct ListenerModule *lm, const char *name); static int AutoPrepDevices(struct ListenerModule *lm, const uint8_t includeLo); static int PrepDevices(struct ListenerModule *lm); static void PrintDevices(const struct ListenerModule *lm); static void SetFdParams(struct pollfd *pollfd, const int fd); static uint8_t CreateAndAddDevice(struct ListenerModule *lm, const char *name) { struct Device *dev; assert(lm != NULL); if (FindDeviceByName(lm, name) != NULL) { Error("Device %s appears twice", name); return FALSE; } if ((dev = CreateDevice(name)) == NULL) { return FALSE; } if (AddDevice(lm, dev) == FALSE) { Error("Unable to add device %s", name); FreeDevice(dev); return FALSE; } return TRUE; } static int AutoPrepDevices(struct ListenerModule *lm, const uint8_t includeLo) { pcap_if_t *alldevs, *d; char errbuf[PCAP_ERRBUF_SIZE]; if (pcap_findalldevs(&alldevs, errbuf) == PCAP_ERROR) { Error("Unable to retrieve network interfaces: %s", errbuf); return FALSE; } for (d = alldevs; d != NULL; d = d->next) { if (includeLo == FALSE && ((d->flags & PCAP_IF_LOOPBACK) != 0)) { continue; } // When using ALL or ALL_NLO (and thus use pcap_findalldevs()), don't include the "any" device if ((strncmp(d->name, "any", 3) == 0) && strlen(d->name) == 3) { continue; } Debug("Adding device %s", d->name); if (CreateAndAddDevice(lm, d->name) == FALSE) { Error("Unable to add device %s, skipping", d->name); } } pcap_freealldevs(alldevs); return TRUE; } static int PrepDevices(struct ListenerModule *lm) { int i; assert(lm != NULL); assert(GetNoInterfaces(&configData) > 0); if (strncmp(configData.interfaces[0], "ALL_NLO", (IF_NAMESIZE - 1)) == 0) { if (AutoPrepDevices(lm, FALSE) == FALSE) { return FALSE; } } else if (strncmp(configData.interfaces[0], "ALL", (IF_NAMESIZE - 1)) == 0) { if (AutoPrepDevices(lm, TRUE) == FALSE) { return FALSE; } } else { i = 0; while (configData.interfaces[i] != NULL) { if (CreateAndAddDevice(lm, configData.interfaces[i]) == FALSE) { Error("Unable to add device %s, skipping", configData.interfaces[i]); } i++; } } if (lm->root == NULL) { Error("No network devices could be added"); return FALSE; } return TRUE; } int GetNoDevices(const struct ListenerModule *lm) { int count; struct Device *current; assert(lm != NULL); count = 0; current = lm->root; while (current != NULL) { count++; current = current->next; } return count; } size_t GetNoRunningDevices(const struct ListenerModule *lm) { size_t count; struct Device *current; assert(lm != NULL); count = 0; current = lm->root; while (current != NULL) { if (current->state == DEVICE_STATE_RUNNING) { count++; } current = current->next; } return count; } struct ListenerModule *AllocListenerModule(void) { struct ListenerModule *lm; if ((lm = malloc(sizeof(struct ListenerModule))) == NULL) { Error("Unable to allocate memory for listener module"); return NULL; } memset(lm, 0, sizeof(struct ListenerModule)); return lm; } void FreeListenerModule(struct ListenerModule *lm) { struct Device *current, *next; if (lm == NULL) { return; } current = lm->root; while (current != NULL) { next = current->next; FreeDevice(current); current = next; } free(lm); } int InitListenerModule(struct ListenerModule *lm) { struct Device *current; if (PrepDevices(lm) == FALSE) { return FALSE; } current = lm->root; while (current != NULL) { StartDevice(current); current = current->next; } if (lm->root == NULL) { Error("No network devices could be initiated, stopping"); return FALSE; } if ((configData.logFlags & LOGFLAG_VERBOSE) != 0) { PrintDevices(lm); } return TRUE; } uint8_t AddDevice(struct ListenerModule *lm, struct Device *add) { struct Device *current; if (lm == NULL || add == NULL) { return FALSE; } if (FindDeviceByName(lm, add->name) != NULL) { Verbose("Device %s already specified", add->name); return FALSE; } if (lm->root == NULL) { lm->root = add; } else { current = lm->root; while (current->next != NULL) { current = current->next; } current->next = add; } return TRUE; } uint8_t RemoveDevice(struct ListenerModule *lm, const struct Device *remove) { struct Device *current, *previous; if (lm == NULL || remove == NULL) { return FALSE; } current = lm->root; previous = NULL; while (current != NULL) { if (current == remove) { if (previous == NULL) { lm->root = current->next; } else { previous->next = current->next; } FreeDevice(current); return TRUE; } previous = current; current = current->next; } return FALSE; } struct Device *FindDeviceByName(const struct ListenerModule *lm, const char *name) { struct Device *current; assert(lm != NULL); assert(name != NULL); current = lm->root; while (current != NULL) { if (strncmp(current->name, name, (IF_NAMESIZE - 1)) == 0) { return current; } current = current->next; } return NULL; } struct Device *FindDeviceByIpAddr(const struct ListenerModule *lm, const char *ip_addr) { struct Device *current; size_t i; assert(lm != NULL); assert(ip_addr != NULL); current = lm->root; while (current != NULL) { for (i = 0; i < current->inet4_addrs_count; i++) { if (strcmp(current->inet4_addrs[i], ip_addr) == 0) { return current; } } for (i = 0; i < current->inet6_addrs_count; i++) { if (strcmp(current->inet6_addrs[i], ip_addr) == 0) { return current; } } current = current->next; } return NULL; } static void SetFdParams(struct pollfd *pollfd, const int fd) { assert(pollfd != NULL); assert(fd >= 0); memset(pollfd, 0, sizeof(struct pollfd)); pollfd->fd = fd; pollfd->events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI; pollfd->revents = 0; } struct pollfd *SetupPollFds(const struct ListenerModule *lm, nfds_t *nfds) { struct pollfd *fds = NULL; struct Device *current = NULL; nfds_t i = 0; if ((fds = malloc(sizeof(struct pollfd) * GetNoRunningDevices(lm))) == NULL) { Crash(1, "Unable to allocate memory for pollfd"); } current = lm->root; while (current != NULL) { if (current->state == DEVICE_STATE_RUNNING) { SetFdParams(&fds[i], current->fd); i++; } current = current->next; } // Currently a redundant check, but it's here to make sure we don't have/pickup any issues if (i != GetNoRunningDevices(lm)) { Crash(1, "Number of running devices does not match number of pollfd"); } *nfds = i; return fds; } struct pollfd *AddPollFd(struct pollfd *fds, nfds_t *nfds, const int fd) { struct pollfd *newFds = NULL; // Already in the list? for (size_t i = 0; i < *nfds; i++) { if (fds[i].fd == fd) { return fds; } } if ((newFds = realloc(fds, sizeof(struct pollfd) * (*nfds + 1))) == NULL) { Crash(1, "Unable to allocate memory for pollfd"); } SetFdParams(&newFds[*nfds], fd); *nfds += 1; return newFds; } struct pollfd *RemovePollFd(struct pollfd *fds, nfds_t *nfds, const int fd) { size_t i, j; struct pollfd *newFds = NULL; assert(fds != NULL); assert(nfds != NULL); if (fd == -1) { return fds; } // Find the index of the fd to remove for (i = 0; i < *nfds; i++) { if (fds[i].fd == fd) { break; } } if (i == *nfds) { return fds; } if ((newFds = malloc(sizeof(struct pollfd) * (*nfds - 1))) == NULL) { Crash(1, "Unable to allocate memory for pollfd"); } for (i = 0, j = 0; i < *nfds; i++) { if (fds[i].fd == fd) { continue; } newFds[j].fd = fds[i].fd; newFds[j].events = fds[i].events; newFds[j].revents = fds[i].revents; j++; } free(fds); *nfds -= 1; return newFds; } struct Device *GetDeviceByFd(const struct ListenerModule *lm, const int fd) { for (struct Device *current = lm->root; current != NULL; current = current->next) { if (current->fd == fd) { return current; } } return NULL; } static void PrintDevices(const struct ListenerModule *lm) { size_t i; struct Device *current; if (lm == NULL) { return; } current = lm->root; while (current != NULL) { Verbose("Ready Device: %s pcap handle: %p, fd: %d", current->name, (void *)current->handle, current->fd); for (i = 0; i < current->inet4_addrs_count; i++) { Verbose(" inet4 addr: %s", current->inet4_addrs[i]); } for (i = 0; i < current->inet6_addrs_count; i++) { Verbose(" inet6 addr: %s", current->inet6_addrs[i]); } current = current->next; } } portsentry-2.0.6/src/pcap_listener.h000066400000000000000000000022751510117642400175470ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include #include #include struct ListenerModule { struct Device *root; }; int GetNoDevices(const struct ListenerModule *lm); size_t GetNoRunningDevices(const struct ListenerModule *lm); struct ListenerModule *AllocListenerModule(void); void FreeListenerModule(struct ListenerModule *lm); int InitListenerModule(struct ListenerModule *lm); uint8_t AddDevice(struct ListenerModule *lm, struct Device *add); uint8_t RemoveDevice(struct ListenerModule *lm, const struct Device *remove); struct Device *FindDeviceByName(const struct ListenerModule *lm, const char *name); struct Device *FindDeviceByIpAddr(const struct ListenerModule *lm, const char *ip_addr); struct pollfd *SetupPollFds(const struct ListenerModule *lm, nfds_t *nfds); struct pollfd *RemovePollFd(struct pollfd *fds, nfds_t *nfds, const int fd); struct Device *GetDeviceByFd(const struct ListenerModule *lm, const int fd); struct pollfd *AddPollFd(struct pollfd *fds, nfds_t *nfds, const int fd); // void HandlePacket(u_char *args, const struct pcap_pkthdr *header, const u_char *packet); portsentry-2.0.6/src/port.c000066400000000000000000000055301510117642400156730ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include "port.h" #include "portsentry.h" #include "io.h" #include "util.h" #define MAX_RANGE_PORTSTRING 12 void ResetPort(struct Port *port) { port->single = 0; port->range.start = 0; port->range.end = 0; } void SetPortSingle(struct Port *port, const uint16_t single) { ResetPort(port); port->single = single; } void SetPortRange(struct Port *port, const uint16_t start, const uint16_t end) { ResetPort(port); port->range.start = start; port->range.end = end; } int IsPortPresent(const struct Port *port, const size_t portLength, const uint16_t portNumber) { size_t i; for (i = 0; i < portLength; i++) { if (IsPortInRange(&port[i], portNumber) == TRUE) { return TRUE; } } return FALSE; } int IsPortInRange(const struct Port *port, const uint16_t portNumber) { if (port->single == portNumber) { return TRUE; } if (port->range.start <= portNumber && port->range.end >= portNumber) { return TRUE; } return FALSE; } int IsPortSingle(const struct Port *port) { return port->single != 0; } int ParsePort(const char *portString, struct Port *port) { char *dash; uint16_t start; uint16_t end; uint16_t single; char ps[MAX_RANGE_PORTSTRING]; if (strlen(portString) >= MAX_RANGE_PORTSTRING) { Error("Invalid port range: %s", portString); return ERROR; } SafeStrncpy(ps, portString, MAX_RANGE_PORTSTRING); dash = strchr(ps, '-'); if (dash != NULL) { *dash = '\0'; if (StrToUint16_t(ps, &start) == FALSE) { Error("Unable to extract port range start: %s", portString); return ERROR; } if (StrToUint16_t(dash + 1, &end) == FALSE) { Error("Unable to extract port range end: %s", portString); return ERROR; } if (start == 0 || end == 0) { Error("Invalid port range: %s, 0 is not a valid port", portString); return ERROR; } if (start > end) { Error("Invalid port range: %s, start port must be less than or equal to end port", portString); return ERROR; } else if (start == end) { SetPortSingle(port, start); } else { SetPortRange(port, start, end); } } else { if (StrToUint16_t(ps, &single) == FALSE) { Error("Unable to extract single port: %s", portString); return ERROR; } if (single == 0) { Error("Invalid port: %s, 0 is not a valid port", portString); return ERROR; } SetPortSingle(port, single); } return TRUE; } size_t GetNoPorts(const struct Port *port, const size_t portLength) { size_t i; size_t noPorts = 0; for (i = 0; i < portLength; i++) { if (IsPortSingle(&port[i])) { noPorts++; } else { noPorts += (size_t)(port[i].range.end - port[i].range.start + 1); } } return noPorts; } portsentry-2.0.6/src/port.h000066400000000000000000000014151510117642400156760ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include struct PortRange { uint16_t start; uint16_t end; }; struct Port { uint16_t single; struct PortRange range; }; void ResetPort(struct Port *port); void SetPortSingle(struct Port *port, const uint16_t single); void SetPortRange(struct Port *port, const uint16_t start, const uint16_t end); int IsPortPresent(const struct Port *port, const size_t portLength, const uint16_t portNumber); int IsPortInRange(const struct Port *port, const uint16_t portNumber); int IsPortSingle(const struct Port *port); int ParsePort(const char *portString, struct Port *port); size_t GetNoPorts(const struct Port *port, const size_t portLength); portsentry-2.0.6/src/portsentry.c000066400000000000000000000037321510117642400171420ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // SPDX-FileContributor: Craig Rowland // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #ifdef __linux__ #include "sentry_stealth.h" #endif #include "cmdline.h" #include "config_data.h" #include "configfile.h" #include "sentry_connect.h" #include "io.h" #include "portsentry.h" #include "sentry.h" #ifdef USE_PCAP #include "sentry_pcap.h" #endif #include "sighandler.h" #include "config.h" uint8_t g_isRunning = TRUE; int main(int argc, char *argv[]) { int status = EXIT_FAILURE; Version(); ParseCmdline(argc, argv); if (SetupSignalHandlers() != TRUE) { fprintf(stderr, "Could not setup signal handler. Shutting down.\n"); goto exit; } ReadConfigFile(); if (configData.logFlags & LOGFLAG_DEBUG) { printf("debug: Final Configuration:\n"); PrintConfigData(configData); } if ((geteuid()) && (getuid()) != 0) { fprintf(stderr, "You need to be root to run this.\n"); goto exit; } if (configData.daemon == TRUE) { if (daemon(0, 0) == -1) { fprintf(stderr, "Could not go into daemon mode. Shutting down.\n"); goto exit; } } if (InitSentry() != TRUE) { fprintf(stderr, "Could not initialize sentry. Shutting down.\n"); goto exit; } if (configData.sentryMode == SENTRY_MODE_CONNECT) { status = PortSentryConnectMode(); } else if (configData.sentryMode == SENTRY_MODE_STEALTH) { #ifdef __linux__ if (configData.sentryMethod == SENTRY_METHOD_RAW) { status = PortSentryStealthMode(); goto exit; } #endif #ifdef USE_PCAP if (configData.sentryMethod == SENTRY_METHOD_PCAP) { status = PortSentryPcap(); goto exit; } #endif Error("Invalid sentry method specified. Shutting down."); goto exit; } else { Error("Invalid sentry mode specified. Shutting down."); goto exit; } exit: FreeSentry(); FreeConfigData(&configData); Exit(status); } portsentry-2.0.6/src/portsentry.h000066400000000000000000000006021510117642400171400ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // SPDX-FileContributor: Craig Rowland // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include #define TCPPACKETLEN 80 #define UDPPACKETLEN 68 #define ERROR -1 #define TRUE 1 #define FALSE 0 #define MAXBUF 1024 #define ERRNOMAXBUF 1024 #undef max #define max(x, y) ((x) > (y) ? (x) : (y)) portsentry-2.0.6/src/sentry.c000066400000000000000000000167751510117642400162500ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include "portsentry.h" #include "config_data.h" #include "ignore.h" #include "io.h" #include "util.h" #include "packet_info.h" #include "state_machine.h" #include "block.h" #include "sentry.h" #define MAX_BUF_SCAN_EVENT 1024 static uint8_t isInitialized = FALSE; static struct IgnoreState is = {0}; static struct BlockedState bs = {0}; static struct SentryState ss = {0}; static void LogScanEvent(const char *target, const char *resolvedHost, const int protocol, const uint16_t port, const struct ip *ip, const struct tcphdr *tcp, const int flagIgnored, const int flagTriggerCountExceeded, const int flagDontBlock, const int flagBlockSuccessful); static void LogScanEvent(const char *target, const char *resolvedHost, const int protocol, const uint16_t port, const struct ip *ip, const struct tcphdr *tcp, const int flagIgnored, const int flagTriggerCountExceeded, const int flagDontBlock, const int flagBlockSuccessful) { int ret; size_t bufsize = MAX_BUF_SCAN_EVENT; char buf[MAX_BUF_SCAN_EVENT], *p = buf; char err[ERRNOMAXBUF]; FILE *output; if (CreateDateTime(p, bufsize) != TRUE) { return; } bufsize -= strlen(p); p += strlen(p); if (bufsize < 2) { Error("Insufficient buffer size to write scan event"); return; } *p = ' '; p++; *p = '\0'; bufsize--; ret = snprintf(p, bufsize, "Scan from: [%s] (%s) protocol: [%s] port: [%d] type: [%s] IP opts: [%s] ignored: [%s] triggered: [%s] noblock: [%s] blocked: [%s]", target, resolvedHost, (protocol == IPPROTO_TCP) ? "TCP" : "UDP", port, (configData.sentryMode == SENTRY_MODE_CONNECT) ? "Connect" : (protocol == IPPROTO_TCP) ? ReportPacketType(tcp) : "UDP", (ip != NULL) ? (ip->ip_hl > 5) ? "set" : "not set" : "unknown", (flagIgnored == TRUE) ? "true" : (flagIgnored == -100) ? "unset" : "false", (flagTriggerCountExceeded == TRUE) ? "true" : (flagTriggerCountExceeded == -100) ? "unset" : "false", (flagDontBlock == TRUE) ? "true" : (flagDontBlock == -100) ? "unset" : "false", (flagBlockSuccessful == TRUE) ? "true" : (flagBlockSuccessful == -100) ? "unset" : "false"); if (ret < 0) { Error("Unable to log scan event: %s", ErrnoString(err, sizeof(err))); return; } size_t len = (size_t)ret; if (len >= bufsize) { Error("Unable to log scan event due to internal buffer too small"); return; } // Log w/o date to stdout/stderr Log("%s", p); // Also write the log to the history file (if configured) if (strlen(configData.historyFile) == 0) { return; } bufsize -= len; p += len; ret = snprintf(p, bufsize, "\n"); if (ret < 0) { Error("Unable to add newline to scan event: %s", ErrnoString(err, sizeof(err))); return; } len = (size_t)ret; if (len >= bufsize) { Error("Unable to add newline to scan event due to internal buffer too small"); return; } bufsize -= len; p += len; if ((output = fopen(configData.historyFile, "a")) == NULL) { Log("Unable to open history log file: %s (%s)", configData.historyFile, ErrnoString(err, sizeof(err))); return; } if (fwrite(buf, 1, strlen(buf), output) < strlen(buf)) { Error("Unable to write history file"); return; } fclose(output); } int InitSentry(void) { if (isInitialized == TRUE) { return TRUE; } if (is.isInitialized == FALSE && InitIgnore(&is) == ERROR) { Error("Error initializing ignore file %s", configData.ignoreFile); return ERROR; } if (bs.isInitialized == FALSE && strlen(configData.blockedFile) > 0) { int ret; ret = BlockedStateInit(&bs); if (ret == ERROR) { Error("Error initializing blocked file %s", configData.blockedFile); FreeIgnore(&is); return ERROR; } else if (ret == FALSE) { if (RewriteBlockedFile(&bs) == ERROR) { return ERROR; } } } if (ss.isInitialized == FALSE) { InitSentryState(&ss); } isInitialized = TRUE; return TRUE; } void FreeSentry(void) { if (isInitialized == FALSE) { return; } if (is.isInitialized == TRUE) { FreeIgnore(&is); } if (bs.isInitialized == TRUE) { BlockedStateFree(&bs); } isInitialized = FALSE; } void RunSentry(const struct PacketInfo *pi) { char resolvedHost[NI_MAXHOST]; int flagIgnored = -100, flagTriggerCountExceeded = -100, flagDontBlock = -100, flagBlockSuccessful = -100; // -100 => unset assert(isInitialized == TRUE); assert(pi != NULL); if (configData.resolveHost == TRUE) { ResolveAddr(pi, resolvedHost, NI_MAXHOST); } else { if (snprintf(resolvedHost, NI_MAXHOST, "%s", pi->saddr) >= NI_MAXHOST) { Error("RunSentry: Host name too long for buffer: %s, truncating", pi->saddr); resolvedHost[NI_MAXHOST - 1] = '\0'; } } if ((flagIgnored = IgnoreIpIsPresent(&is, GetSourceSockaddrFromPacketInfo(pi))) == ERROR) { flagIgnored = FALSE; } else if (flagIgnored == TRUE) { Verbose("Host: %s found in ignore file %s, aborting actions", pi->saddr, configData.ignoreFile); goto sentry_exit; } if ((flagTriggerCountExceeded = CheckState(&ss, GetSourceSockaddrFromPacketInfo(pi))) != TRUE) { goto sentry_exit; } if (configData.disableLocalCheck == FALSE && IsSameSourceAndDestAddress(pi) == TRUE) { Debug("Source address %s same as destination address %s, skipping", pi->saddr, pi->daddr); flagIgnored = TRUE; flagDontBlock = TRUE; flagBlockSuccessful = FALSE; goto sentry_exit; } if (configData.sentryMode == SENTRY_MODE_CONNECT && pi->protocol == IPPROTO_TCP) { XmitBannerIfConfigured(IPPROTO_TCP, pi->tcpAcceptSocket, NULL, 0); } else if (configData.sentryMode == SENTRY_MODE_CONNECT && pi->protocol == IPPROTO_UDP) { XmitBannerIfConfigured(IPPROTO_UDP, pi->listenSocket, GetClientSockaddrFromPacketInfo(pi), GetClientSockaddrLenFromPacketInfo(pi)); } // If in log-only mode, don't run any of the blocking code if ((configData.blockTCP == 0 && pi->protocol == IPPROTO_TCP) || (configData.blockUDP == 0 && pi->protocol == IPPROTO_UDP)) { flagDontBlock = TRUE; flagBlockSuccessful = FALSE; goto sentry_exit; } else { flagDontBlock = FALSE; } if (bs.isInitialized == FALSE || IsBlocked(GetSourceSockaddrFromPacketInfo(pi), &bs) == FALSE) { if (DisposeTarget(pi->saddr, pi->port, pi->protocol) != TRUE) { Error("attackalert: Error during target dispose %s/%s!", resolvedHost, pi->saddr); flagBlockSuccessful = FALSE; } else { if (bs.isInitialized == TRUE) { WriteBlockedFile(GetSourceSockaddrFromPacketInfo(pi), &bs); } flagBlockSuccessful = TRUE; } } else { Log("attackalert: Host: %s/%s is already blocked Ignoring", resolvedHost, pi->saddr); flagBlockSuccessful = TRUE; } sentry_exit: LogScanEvent(pi->saddr, resolvedHost, pi->protocol, pi->port, pi->ip, pi->tcp, flagIgnored, flagTriggerCountExceeded, flagDontBlock, flagBlockSuccessful); } portsentry-2.0.6/src/sentry.h000066400000000000000000000003631510117642400162370ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include "packet_info.h" int InitSentry(void); void FreeSentry(void); void RunSentry(const struct PacketInfo *pi); portsentry-2.0.6/src/sentry_connect.c000066400000000000000000000261331510117642400177460ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // SPDX-FileContributor: Craig Rowland // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config_data.h" #include "sentry_connect.h" #include "io.h" #include "util.h" #include "portsentry.h" #include "packet_info.h" #include "sentry.h" extern uint8_t g_isRunning; struct ConnectionData { uint16_t port; int family; uint8_t protocol; int sockfd; }; static int SetConnectionData(struct ConnectionData **cd, const size_t cdIdx, const uint16_t port, const uint8_t proto, const int family); static nfds_t ConstructConnectionData(struct ConnectionData **cd); static void FreeConnectionData(struct ConnectionData **cd, nfds_t *cdSize); static int PrepareNoFds(void); int PortSentryConnectMode(void) { int status = EXIT_FAILURE; struct sockaddr_in client4; struct sockaddr_in6 client6; socklen_t clientLength; int incomingSockfd = -1, result; ssize_t recvResult; size_t count = 0; char err[ERRNOMAXBUF]; struct pollfd *fds = NULL; struct ConnectionData *connectionData = NULL; struct PacketInfo pi; nfds_t connectionDataSize = 0; char tmp; assert(configData.sentryMode == SENTRY_MODE_CONNECT); if (PrepareNoFds() == FALSE) { return EXIT_FAILURE; } if ((connectionDataSize = ConstructConnectionData(&connectionData)) == 0) { return EXIT_FAILURE; } if ((fds = (struct pollfd *)malloc(sizeof(struct pollfd) * connectionDataSize)) == NULL) { Error("Unable to allocate memory for pollfd"); return EXIT_FAILURE; } for (count = 0; count < connectionDataSize; count++) { fds[count].fd = connectionData[count].sockfd; fds[count].events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI; fds[count].revents = 0; } Log("Portsentry is now active and listening."); while (g_isRunning == TRUE) { result = poll(fds, connectionDataSize, -1); if (result == -1) { if (errno == EINTR) { continue; } Error("poll() failed %s", ErrnoString(err, sizeof(err))); goto exit; } else if (result == 0) { Error("poll() timed out. Aborting."); goto exit; } for (count = 0; count < connectionDataSize; count++) { if ((fds[count].revents & POLLIN) == 0) { continue; } incomingSockfd = -1; if (connectionData[count].family == AF_INET) { clientLength = sizeof(client4); } else { clientLength = sizeof(client6); } if (connectionData[count].protocol == IPPROTO_TCP) { if (connectionData[count].family == AF_INET) { incomingSockfd = accept(connectionData[count].sockfd, (struct sockaddr *)&client4, &clientLength); } else { incomingSockfd = accept(connectionData[count].sockfd, (struct sockaddr *)&client6, &clientLength); } if (incomingSockfd == -1) { Log("attackalert: Possible stealth scan from unknown host to TCP port: %d (accept failed %d: %s)", connectionData[count].port, errno, ErrnoString(err, sizeof(err))); continue; } } else if (connectionData[count].protocol == IPPROTO_UDP) { if (connectionData[count].family == AF_INET) { recvResult = recvfrom(connectionData[count].sockfd, &tmp, 1, 0, (struct sockaddr *)&client4, &clientLength); } else { recvResult = recvfrom(connectionData[count].sockfd, &tmp, 1, 0, (struct sockaddr *)&client6, &clientLength); } if (recvResult == -1) { Error("Could not receive incoming data on UDP port: %d: %s", connectionData[count].port, ErrnoString(err, sizeof(err))); continue; } } ClearPacketInfo(&pi); SetPacketInfoFromConnectData(&pi, connectionData[count].port, connectionData[count].family, connectionData[count].protocol, connectionData[count].sockfd, incomingSockfd, &client4, &client6); Debug("RunSentry connect mode: accepted %s connection from: %s", GetProtocolString(pi.protocol), pi.saddr); RunSentry(&pi); if (incomingSockfd != -1) { close(incomingSockfd); incomingSockfd = -1; pi.tcpAcceptSocket = -1; } } } status = EXIT_SUCCESS; exit: FreeConnectionData(&connectionData, &connectionDataSize); if (fds != NULL) { free(fds); fds = NULL; } if (incomingSockfd != -1) { close(incomingSockfd); incomingSockfd = -1; } for (count = 0; count < connectionDataSize; count++) { if (connectionData[count].sockfd != -1) { close(connectionData[count].sockfd); connectionData[count].sockfd = -1; } } return status; } static int SetConnectionData(struct ConnectionData **cd, const size_t cdIdx, const uint16_t port, const uint8_t proto, const int family) { int sockfd; assert(proto == IPPROTO_TCP || proto == IPPROTO_UDP); assert(family == AF_INET || family == AF_INET6); if (port == 0) { Error("Invalid port 0 defined in %s, unable to listen. Skipping", (proto == IPPROTO_TCP ? "TCP_PORTS" : "UDP_PORTS")); return FALSE; } Log("Listen on %s: %s port: %d", (family == AF_INET) ? "AF_INET" : "AF_INET6", (proto == IPPROTO_TCP ? "TCP" : "UDP"), port); if (family == AF_INET6) { struct sockaddr_in6 addr6; memset(&addr6, 0, sizeof(addr6)); addr6.sin6_family = AF_INET6; addr6.sin6_port = htons(port); addr6.sin6_addr = in6addr_any; sockfd = SetupPort((struct sockaddr *)&addr6, sizeof(addr6), proto, TRUE); } else { struct sockaddr_in addr4; memset(&addr4, 0, sizeof(addr4)); addr4.sin_family = AF_INET; addr4.sin_port = htons(port); addr4.sin_addr.s_addr = htonl(INADDR_ANY); sockfd = SetupPort((struct sockaddr *)&addr4, sizeof(addr4), proto, TRUE); } if (sockfd < 0) { if (errno == EMFILE) { Error("Unable to open all ports (TCP_PORTS/UDP_PORTS) specified in the configuration file. Reduce the number of ports to listen to or increase the max number of allowed file descriptors open by a process or use stealth mode instead"); return ERROR; } Error("Could not bind %s socket on port %d. Attempting to continue", GetProtocolString(proto), port); return FALSE; } if ((*cd = realloc(*cd, sizeof(struct ConnectionData) * (cdIdx + 1))) == NULL) { Crash(EXIT_FAILURE, "Unable to allocate memory for connection data"); } memset(&(*cd)[cdIdx], 0, sizeof(struct ConnectionData)); (*cd)[cdIdx].port = port; (*cd)[cdIdx].family = family; (*cd)[cdIdx].protocol = proto; (*cd)[cdIdx].sockfd = sockfd; return TRUE; } nfds_t ConstructConnectionData(struct ConnectionData **cd) { int ret; uint16_t j; nfds_t i, cdIdx = 0; /* OpenBSD doesn't support IPv4/IPv6 dual-stack sockets, * so we need to manually open an IPv4 socket */ for (i = 0; i < configData.tcpPortsLength; i++) { if (IsPortSingle(&configData.tcpPorts[i])) { ret = SetConnectionData(cd, cdIdx, configData.tcpPorts[i].single, IPPROTO_TCP, AF_INET6); if (ret == TRUE) { cdIdx++; } else if (ret == ERROR) { goto err; } #ifdef __OpenBSD__ ret = SetConnectionData(cd, cdIdx, configData.tcpPorts[i].single, IPPROTO_TCP, AF_INET); if (ret == TRUE) { cdIdx++; } else if (ret == ERROR) { goto err; } #endif } else { for (j = configData.tcpPorts[i].range.start; j <= configData.tcpPorts[i].range.end; j++) { ret = SetConnectionData(cd, cdIdx, j, IPPROTO_TCP, AF_INET6); if (ret == TRUE) { cdIdx++; } else if (ret == ERROR) { goto err; } #ifdef __OpenBSD__ ret = SetConnectionData(cd, cdIdx, j, IPPROTO_TCP, AF_INET); if (ret == TRUE) { cdIdx++; } else if (ret == ERROR) { goto err; } #endif } } } for (i = 0; i < configData.udpPortsLength; i++) { if (IsPortSingle(&configData.udpPorts[i])) { ret = SetConnectionData(cd, cdIdx, configData.udpPorts[i].single, IPPROTO_UDP, AF_INET6); if (ret == TRUE) { cdIdx++; } else if (ret == ERROR) { goto err; } #ifdef __OpenBSD__ ret = SetConnectionData(cd, cdIdx, configData.udpPorts[i].single, IPPROTO_UDP, AF_INET); if (ret == TRUE) { cdIdx++; } else if (ret == ERROR) { goto err; } #endif } else { for (j = configData.udpPorts[i].range.start; j <= configData.udpPorts[i].range.end; j++) { ret = SetConnectionData(cd, cdIdx, j, IPPROTO_UDP, AF_INET6); if (ret == TRUE) { cdIdx++; } else if (ret == ERROR) { goto err; } #ifdef __OpenBSD__ ret = SetConnectionData(cd, cdIdx, j, IPPROTO_UDP, AF_INET); if (ret == TRUE) { cdIdx++; } else if (ret == ERROR) { goto err; } #endif } } } goto exit; err: FreeConnectionData(cd, &cdIdx); cdIdx = 0; exit: return cdIdx; } void FreeConnectionData(struct ConnectionData **cd, nfds_t *cdSize) { if (*cd != NULL) { free(*cd); *cd = NULL; } *cdSize = 0; } static int PrepareNoFds(void) { size_t noFds; struct rlimit rlim; char err[ERRNOMAXBUF]; noFds = GetNoPorts(configData.tcpPorts, configData.tcpPortsLength); noFds += GetNoPorts(configData.udpPorts, configData.udpPortsLength); #ifdef __OpenBSD__ /* OpenBSD doesn't support IPv4/IPv6 dual-stack sockets, * so we need to double the number of file descriptors */ noFds *= 2; #endif /* FIXME: Should write a portable function to get number of fd's currently open * in order to get an accurate count but 4 should be a fairly good guess: * stdin, stdout, stderr, CWD */ noFds += 4; if (getrlimit(RLIMIT_NOFILE, &rlim) == -1) { Error("getrlimit RLIMIT_NOFILE failed: %s", strerror(errno)); return FALSE; } if (rlim.rlim_cur == RLIM_INFINITY) { return TRUE; } // FIXME: On FreeBSD rlim_t is long, so we need to cast to size_t // since we don't know what to do with any negative value if ((size_t)rlim.rlim_cur >= noFds) { return TRUE; } Debug("Setting RLIMIT_NOFILE to %zu (from cur: %" PRIuMAX " max: %" PRIuMAX ")", noFds, (uintmax_t)rlim.rlim_cur, (uintmax_t)rlim.rlim_max); rlim.rlim_cur = (rlim_t)noFds; rlim.rlim_max = (rlim_t)noFds; if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) { Error("setrlimit RLIMIT_NOFILE %zu failed: %s", noFds, ErrnoString(err, sizeof(err))); return FALSE; } if (getrlimit(RLIMIT_NOFILE, &rlim) == -1) { Error("Check getrlimit RLIMIT_NOFILE after set failed: %s", strerror(errno)); return FALSE; } if ((size_t)rlim.rlim_cur >= noFds) { return TRUE; } Error("Unable to increase the number of allowed open file descriptors. Needed fd's: %zu, " "soft limit: %" PRIuMAX ", hard limit: %" PRIuMAX ". " "Reduce the number of ports to listen to or increase the max number of allowed file descriptors open by a process or use stealth mode instead", noFds, (uintmax_t)rlim.rlim_cur, (uintmax_t)rlim.rlim_max); return FALSE; } portsentry-2.0.6/src/sentry_connect.h000066400000000000000000000003341510117642400177460ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // SPDX-FileContributor: Craig Rowland // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include int PortSentryConnectMode(void); portsentry-2.0.6/src/sentry_pcap.c000066400000000000000000000344041510117642400172400ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include #include #include #include #include #include #include "portsentry.h" #include "sentry_pcap.h" #include "pcap_listener.h" #include "pcap_device.h" #include "io.h" #include "util.h" #include "packet_info.h" #include "sentry.h" #include "kernelmsg.h" #include "config_data.h" #define POLL_TIMEOUT 500 static void HandlePacket(u_char *args, const struct pcap_pkthdr *header, const u_char *packet); static int PrepPacket(struct PacketInfo *pi, const struct Device *device, const u_char *packet, const uint32_t packetLength); static void ProcessKernelMessage(const int kernel_socket, struct ListenerModule *lm, struct pollfd **fds, nfds_t *nfds); static void ExecKernelMessageLogic(struct ListenerModule *lm, struct pollfd **fds, nfds_t *nfds, struct KernelMessage *kernelMessage); static struct Device *GetDeviceByKernelMessage(struct ListenerModule *lm, struct KernelMessage *kernelMessage); static void StartDeviceAndAddPollFd(struct Device *device, struct pollfd **fds, nfds_t *nfds); static void StopDeviceAndRemovePollFd(struct Device *device, struct pollfd **fds, nfds_t *nfds); static void HandleAddressAdded(struct Device *device, struct KernelMessage *kernelMessage, struct pollfd **fds, nfds_t *nfds); static void HandleAddressRemoved(struct Device *device, struct KernelMessage *kernelMessage, struct pollfd **fds, nfds_t *nfds); static void HandleInterfaceAdded(struct Device *device, struct pollfd **fds, nfds_t *nfds); static void HandleInterfaceRemoved(struct Device *device, struct pollfd **fds, nfds_t *nfds); extern uint8_t g_isRunning; #ifdef FUZZ_SENTRY_PCAP_PREP_PACKET uint8_t g_isRunning = TRUE; int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { struct PacketInfo pi; if (PrepPacket(&pi, NULL, Data, Size) != TRUE) { return -1; } return 0; } #endif int PortSentryPcap(void) { int status = EXIT_FAILURE, ret, kernel_socket; char err[ERRNOMAXBUF]; struct ListenerModule *lm = NULL; struct pollfd *fds = NULL; struct Device *current = NULL; size_t i; nfds_t nfds = 0; if ((lm = AllocListenerModule()) == NULL) { goto exit; } if (InitListenerModule(lm) == FALSE) { goto exit; } if ((fds = SetupPollFds(lm, &nfds)) == NULL) { Error("Unable to allocate memory for pollfd"); goto exit; } if ((kernel_socket = ListenKernel()) == -1) { goto exit; } fds = AddPollFd(fds, &nfds, kernel_socket); Log("Portsentry is now active and listening."); while (g_isRunning == TRUE) { ret = poll(fds, nfds, POLL_TIMEOUT); if (ret == -1) { if (errno == EINTR) { continue; } Error("poll() failed %s", ErrnoString(err, sizeof(err))); goto exit; } else if (ret == 0) { continue; } for (i = 0; i < nfds; i++) { if (fds[i].revents & POLLIN) { if (fds[i].fd == kernel_socket) { ProcessKernelMessage(kernel_socket, lm, &fds, &nfds); continue; } if ((current = GetDeviceByFd(lm, fds[i].fd)) == NULL) { Error("Unable to find device by fd %d in main pcap loop", fds[i].fd); goto exit; } do { ret = pcap_dispatch(current->handle, -1, HandlePacket, (u_char *)current); if (ret == PCAP_ERROR) { Error("pcap_dispatch() failed %s", pcap_geterr(current->handle)); if (strncmp("The interface disappeared", pcap_geterr(current->handle), 25) == 0) { StopDeviceAndRemovePollFd(current, &fds, &nfds); } } else if (ret == PCAP_ERROR_BREAK) { Error("Got PCAP_ERROR_BREAK, ignoring"); } } while (ret > 0); } else if (fds[i].revents & POLLERR) { if ((current = GetDeviceByFd(lm, fds[i].fd)) == NULL) { Error("On POLLERR: Unable to find device by fd %d in main pcap loop", fds[i].fd); goto exit; } Error("Got POLLERR on %s (fd: %d), stopping interface from sentry", current->name, fds[i].fd); StopDeviceAndRemovePollFd(current, &fds, &nfds); } } } status = EXIT_SUCCESS; exit: if (fds) free(fds); if (lm) FreeListenerModule(lm); return status; } static void HandlePacket(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) { struct Device *device = (struct Device *)args; struct PacketInfo pi; (void)header; if (PrepPacket(&pi, device, packet, header->len) == FALSE) { return; } if (pi.protocol == IPPROTO_TCP && (((pi.tcp->th_flags & TH_ACK) != 0) || ((pi.tcp->th_flags & TH_RST) != 0))) { return; } // FIXME: In pcap we need to consider the interface if (IsPortInUse(&pi) != FALSE) { Log("Ignoring packet from %s to destination port %d, a service is running", pi.saddr, pi.port); return; } RunSentry(&pi); } static int PrepPacket(struct PacketInfo *pi, const struct Device *device, const u_char *packet, const uint32_t packetLength) { int ipOffset = ERROR; if (device == NULL) { ipOffset = 0; } else if (pcap_datalink(device->handle) == DLT_EN10MB) { ipOffset = sizeof(struct ether_header); } else if (pcap_datalink(device->handle) == DLT_RAW) { ipOffset = 0; } else if (pcap_datalink(device->handle) == DLT_NULL) { uint32_t nulltype = *packet; if (nulltype != 2 && nulltype != 24 && nulltype != 28 && nulltype != 30) { Error("Packet on %s have unsupported nulltype set (nulltype: %d) on a DLT_NULL dev", device->name, nulltype); return FALSE; } ipOffset = 4; } #ifdef __OpenBSD__ else if (pcap_datalink(device->handle) == DLT_LOOP) { /* * FIXME: On OpenBSD 7.4 the nulltype is 0 on the loopback interface receiving IPv4 packets. * According to libpcap documentation it's supposed to be a network byte-order AF_ value. * If this holds true for OpenBSD's then packets are for some reason classified as AF_UNSPEC. * Confirm this */ uint32_t nulltype = *packet; if (nulltype != 0) { Error("Packet on %s have unsupported nulltype set (nulltype: %d) on a DLT_LOOP dev", device->name, nulltype); return FALSE; } ipOffset = 4; } #endif #ifdef __linux__ else if (pcap_datalink(device->handle) == DLT_LINUX_SLL) { if (ntohs(*(const uint16_t *)packet) != 0) { Verbose("Packet type on %s is not \"sent to us by somebody else\"", device->name); return FALSE; } if (ntohs(*(const uint16_t *)(packet + 2)) != ARPHRD_ETHER) { Verbose("Packet type on %s is not Ethernet (type: %d)", device->name, ntohs(*(const uint16_t *)(packet + 2))); return FALSE; } ipOffset = 16; } #endif else { Error("Packet on %s have unsupported datalink type set (datalink: %d)", device->name, pcap_datalink(device->handle)); return FALSE; } if (ipOffset == ERROR) { Error("Unable to determine IP offset for packet on %s", device->name); return FALSE; } if (packetLength < (uint32_t)ipOffset) { Error("Packet on %s is too short (%d bytes), ignoring", device->name, packetLength); return FALSE; } ClearPacketInfo(pi); return SetPacketInfoFromPacket(pi, (const unsigned char *)packet + ipOffset, packetLength - (uint32_t)ipOffset); } #ifdef __linux__ static void ProcessKernelMessage(const int kernel_socket, struct ListenerModule *lm, struct pollfd **fds, nfds_t *nfds) { struct nlmsghdr *nh; struct KernelMessage kernelMessage; char buf[4096]; struct iovec iov = {buf, sizeof(buf)}; struct sockaddr_nl sa; struct msghdr msg = {.msg_name = &sa, .msg_namelen = sizeof(sa), .msg_iov = &iov, .msg_iovlen = 1}; char err[ERRNOMAXBUF]; size_t len; ssize_t ret = recvmsg(kernel_socket, &msg, 0); if (ret < 0) { Error("Failed to receive routing message: %s", ErrnoString(err, sizeof(err))); return; } else if (ret == 0) { Debug("Received 0 bytes from kernel socket"); return; } len = (size_t)ret; for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) { if (ParseKernelMessage(nh, &kernelMessage) != TRUE) { continue; } ExecKernelMessageLogic(lm, fds, nfds, &kernelMessage); } } #elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) static void ProcessKernelMessage(const int kernel_socket, struct ListenerModule *lm, struct pollfd **fds, nfds_t *nfds) { char buf[4096]; char err[ERRNOMAXBUF]; struct KernelMessage kernelMessage; ssize_t len; if ((len = read(kernel_socket, buf, sizeof(buf))) < 0) { Error("Failed to receive routing message: %s", ErrnoString(err, sizeof(err))); return; } if (ParseKernelMessage(buf, &kernelMessage) != TRUE) { return; } ExecKernelMessageLogic(lm, fds, nfds, &kernelMessage); } #endif static void ExecKernelMessageLogic(struct ListenerModule *lm, struct pollfd **fds, nfds_t *nfds, struct KernelMessage *kernelMessage) { struct Device *device = NULL; if ((device = GetDeviceByKernelMessage(lm, kernelMessage)) == NULL) { if ((IsInterfacePresent(&configData, "ALL") || IsInterfacePresent(&configData, "ALL_NLO")) && kernelMessage->action == KMA_ADD) { struct Device *newDevice; const char *ifName = kernelMessage->type == KMT_INTERFACE ? kernelMessage->interface.ifName : kernelMessage->address.ifName; Debug("ExecKernelMessageLogic - Device not found: %s - attempting bringup", ifName); if ((newDevice = CreateDevice(ifName)) == NULL) { Error("ExecKernelMessageLogic - Device %s not found, and not able to create it", ifName); return; } if (AddDevice(lm, newDevice) == FALSE) { Error("ExecKernelMessageLogic - Device %s not found, and not able to add it", ifName); FreeDevice(newDevice); newDevice = NULL; return; } device = newDevice; } else { Debug("ExecKernelMessageLogic - Device not found: %s %s: %s", kernelMessage->type == KMT_INTERFACE ? "Interface" : "Address", kernelMessage->action == KMA_ADD ? "Added" : "Removed", kernelMessage->type == KMT_INTERFACE ? kernelMessage->interface.ifName : kernelMessage->address.ipAddr); return; } } if (kernelMessage->type == KMT_ADDRESS) { if (kernelMessage->action == KMA_ADD) { HandleAddressAdded(device, kernelMessage, fds, nfds); } else if (kernelMessage->action == KMA_DEL) { HandleAddressRemoved(device, kernelMessage, fds, nfds); } } else if (kernelMessage->type == KMT_INTERFACE) { if (kernelMessage->action == KMA_ADD) { HandleInterfaceAdded(device, fds, nfds); } else if (kernelMessage->action == KMA_DEL) { HandleInterfaceRemoved(device, fds, nfds); } } } static struct Device *GetDeviceByKernelMessage(struct ListenerModule *lm, struct KernelMessage *kernelMessage) { if (kernelMessage->type == KMT_ADDRESS) { if (kernelMessage->action == KMA_ADD) { return FindDeviceByName(lm, kernelMessage->address.ifName); } else if (kernelMessage->action == KMA_DEL) { return FindDeviceByIpAddr(lm, kernelMessage->address.ipAddr); } } else if (kernelMessage->type == KMT_INTERFACE) { return FindDeviceByName(lm, kernelMessage->interface.ifName); } return NULL; } static void StartDeviceAndAddPollFd(struct Device *device, struct pollfd **fds, nfds_t *nfds) { if (StartDevice(device) == TRUE) { *fds = AddPollFd(*fds, nfds, device->fd); } } static void StopDeviceAndRemovePollFd(struct Device *device, struct pollfd **fds, nfds_t *nfds) { int fd = device->fd; StopDevice(device); *fds = RemovePollFd(*fds, nfds, fd); } static void HandleAddressAdded(struct Device *device, struct KernelMessage *kernelMessage, struct pollfd **fds, nfds_t *nfds) { if (device->state != DEVICE_STATE_RUNNING) { Debug("ProcessKernelMessage[KMT_ADDRESS ADD]: %s not running, starting it", device->name); // Start device resets and adds all addresses StartDeviceAndAddPollFd(device, fds, nfds); } else { Debug("ProcessKernelMessage[KMT_ADDRESS ADD]: %s is already running, adding address and refiltering", device->name); if (AddAddress(device, kernelMessage->address.ipAddr, kernelMessage->address.family) == TRUE) { SetupFilter(device); } } } static void HandleAddressRemoved(struct Device *device, struct KernelMessage *kernelMessage, struct pollfd **fds, nfds_t *nfds) { RemoveAddress(device, kernelMessage->address.ipAddr); if (GetNoAddresses(device) == 0) { Debug("ProcessKernelMessage[KMT_ADDRESS DEL]: No addresses left on %s, stopping device", device->name); StopDeviceAndRemovePollFd(device, fds, nfds); } else { if (device->state == DEVICE_STATE_RUNNING) { Debug("ProcessKernelMessage[KMT_ADDRESS DEL]: %s has addresses left, refiltering", device->name); SetupFilter(device); } } } static void HandleInterfaceAdded(struct Device *device, struct pollfd **fds, nfds_t *nfds) { if (device->state != DEVICE_STATE_RUNNING) { Debug("ProcessKernelMessage[KMT_INTERFACE UP]: Device %s was not running, starting it", device->name); StartDeviceAndAddPollFd(device, fds, nfds); } else { Debug("ProcessKernelMessage[KMT_INTERFACE UP]: Device %s is running, reset all addresses and refiltering", device->name); // When interface becomes available, it might have new addresses. Reinitialize. RemoveAllAddresses(device); if (SetAllAddresses(device) == ERROR) { Error("ProcessKernelMessage[KMT_INTERFACE UP]: Unable to set all addresses for device %s. Emergency stop", device->name); StopDeviceAndRemovePollFd(device, fds, nfds); return; } if (SetupFilter(device) == ERROR) { Error("ProcessKernelMessage[KMT_INTERFACE UP]: Unable to setup filter for device %s. Emergency stop", device->name); StopDeviceAndRemovePollFd(device, fds, nfds); } } } static void HandleInterfaceRemoved(struct Device *device, struct pollfd **fds, nfds_t *nfds) { if (device->state == DEVICE_STATE_RUNNING) { Debug("ProcessKernelMessage[KMT_INTERFACE DOWN]: Device %s is running, stopping it", device->name); StopDeviceAndRemovePollFd(device, fds, nfds); } else { Debug("ProcessKernelMessage[KMT_INTERFACE DOWN]: Device %s is not running, skipping", device->name); } } portsentry-2.0.6/src/sentry_pcap.h000066400000000000000000000002531510117642400172400ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include int PortSentryPcap(void); portsentry-2.0.6/src/sentry_stealth.c000066400000000000000000000131351510117642400177570ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // SPDX-FileContributor: Craig Rowland // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "portsentry.h" #include "config_data.h" #include "packet_info.h" #include "io.h" #include "util.h" #include "sentry.h" #include "sentry_stealth.h" #define NFDS 2 extern uint8_t g_isRunning; static ssize_t PacketRead(const int socket, char *buffer, const size_t bufferLen); #ifdef FUZZ_SENTRY_STEALTH_PREP_PACKET uint8_t g_isRunning = TRUE; int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { struct PacketInfo pi; ClearPacketInfo(&pi); pi.packetLength = IP_MAXPACKET; if (SetPacketInfoFromPacket(&pi, (unsigned char *)Data, Size) != TRUE) { return -1; } return 0; } #endif int PortSentryStealthMode(void) { ssize_t packetLen; int status = EXIT_FAILURE, result; char packetBuffer[IP_MAXPACKET], err[ERRNOMAXBUF]; struct pollfd fds[NFDS]; struct PacketInfo pi; size_t i; nfds_t nfds = NFDS; assert(configData.sentryMode == SENTRY_MODE_STEALTH); memset(fds, 0, sizeof(fds)); for (i = 0; i < nfds; i++) { fds[i].fd = -1; fds[i].events = POLLIN; } /* Listen for IPv4 and IPv6 packets on different sockets, it will probably(?) * be faster to let the kernel filter out all the other packet types than * using ETH_P_ALL and filter ourselves. */ if ((fds[0].fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { Error("Unable to create socket: %s", ErrnoString(err, sizeof(err))); return ERROR; } if ((fds[1].fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6))) < 0) { Error("Unable to create socket: %s", ErrnoString(err, sizeof(err))); return ERROR; } Log("Portsentry is now active and listening."); while (g_isRunning == TRUE) { result = poll(fds, nfds, -1); if (result == -1) { if (errno == EINTR) { continue; } Error("poll() failed: %s. Aborting.", ErrnoString(err, sizeof(err))); goto exit; } else if (result == 0) { Error("poll() timed out. Aborting."); goto exit; } for (i = 0; i < nfds; i++) { if (fds[i].revents != POLLIN) { continue; } if ((packetLen = PacketRead(fds[i].fd, packetBuffer, IP_MAXPACKET)) == ERROR) continue; ClearPacketInfo(&pi); pi.packetLength = IP_MAXPACKET; if (SetPacketInfoFromPacket(&pi, (unsigned char *)packetBuffer, (uint32_t)packetLen) != TRUE) { continue; } if (pi.protocol == IPPROTO_TCP) { if (((pi.tcp->th_flags & TH_ACK) != 0) || ((pi.tcp->th_flags & TH_RST) != 0)) { continue; } if (IsPortPresent(configData.tcpPorts, configData.tcpPortsLength, pi.port) == FALSE) { continue; } } else if (pi.protocol == IPPROTO_UDP) { if (IsPortPresent(configData.udpPorts, configData.udpPortsLength, pi.port) == FALSE) { continue; } } else { Error("Unknown protocol %d. Skipping", pi.protocol); continue; } if (IsPortInUse(&pi) != FALSE) { Log("Ignoring packet from %s to destination port %d, a service is running", pi.saddr, pi.port); continue; } RunSentry(&pi); } } status = EXIT_SUCCESS; exit: for (i = 0; i < nfds; i++) { if (fds[i].fd != -1) close(fds[i].fd); } return status; } static ssize_t PacketRead(const int socket, char *buffer, const size_t bufferLen) { char err[ERRNOMAXBUF]; ssize_t result; struct sockaddr_ll sll; socklen_t sllLen = sizeof(struct sockaddr_ll); if ((result = recvfrom(socket, buffer, bufferLen, 0, (struct sockaddr *)&sll, &sllLen)) == -1) { Error("Could not read from socket %d: %s. Aborting", socket, ErrnoString(err, sizeof(err))); return ERROR; } else if (result < (ssize_t)sizeof(struct ip)) { Error("Packet read from socket %d is too small (%zu bytes). Aborting", socket, result); return ERROR; } if (sll.sll_pkttype != PACKET_HOST) { Debug("Recived invalid packet on raw socket PacketRead: sllLen: %d, sll_family: %d, sll_protocol: %d (%x), sll_ifindex: %d, sll_hatype: %d, sll_pkttype: %d (%s), sll_halen: %d", sllLen, sll.sll_family, ntohs(sll.sll_protocol), ntohs(sll.sll_protocol), sll.sll_ifindex, sll.sll_hatype, sll.sll_pkttype, (sll.sll_pkttype == PACKET_HOST) ? "PACKET_HOST" : (sll.sll_pkttype == PACKET_BROADCAST) ? "PACKET_BROADCAST" : (sll.sll_pkttype == PACKET_MULTICAST) ? "PACKET_MULTICAST" : (sll.sll_pkttype == PACKET_OTHERHOST) ? "PACKET_OTHERHOST" : (sll.sll_pkttype == PACKET_OUTGOING) ? "PACKET_OUTGOING" : (sll.sll_pkttype == PACKET_LOOPBACK) ? "PACKET_LOOPBACK" : (sll.sll_pkttype == PACKET_USER) ? "PACKET_USER" : (sll.sll_pkttype == PACKET_KERNEL) ? "PACKET_KERNEL" : "UNKNOWN", sll.sll_halen); return ERROR; } return result; } portsentry-2.0.6/src/sentry_stealth.h000066400000000000000000000003071510117642400177610ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // SPDX-FileContributor: Craig Rowland // // SPDX-License-Identifier: BSD-2-Clause #pragma once int PortSentryStealthMode(void); portsentry-2.0.6/src/sighandler.c000066400000000000000000000013601510117642400170240ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include "portsentry.h" #include "sighandler.h" extern uint8_t g_isRunning; void ExitSignalHandler(int signum); int SetupSignalHandlers(void) { struct sigaction sa; signal(SIGPIPE, SIG_IGN); sa.sa_handler = ExitSignalHandler; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); if (sigaction(SIGINT, &sa, NULL) == -1) { perror("sigaction SIGINT"); return FALSE; } if (sigaction(SIGTERM, &sa, NULL) == -1) { perror("sigaction SIGTERM"); return FALSE; } return TRUE; } void ExitSignalHandler(int signum) { (void)signum; g_isRunning = FALSE; } portsentry-2.0.6/src/sighandler.h000066400000000000000000000002361510117642400170320ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once int SetupSignalHandlers(void); portsentry-2.0.6/src/state_machine.c000066400000000000000000000074361510117642400175220ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include "config.h" #include "config_data.h" #include "portsentry.h" #include "io.h" #include "state_machine.h" #define MAX_HASH_SIZE 1000000 static int CheckStateIpv4(struct SentryState *state, const struct sockaddr_in *addr); static int CheckStateIpv6(struct SentryState *state, const struct sockaddr_in6 *addr); static int CheckStateIpv4(struct SentryState *state, const struct sockaddr_in *addr) { const struct sockaddr_in *addr_in = (const struct sockaddr_in *)addr; struct AddrStateIpv4 *addrStateIpv4; HASH_FIND(hh, state->addrStateIpv4, &addr_in->sin_addr.s_addr, sizeof(in_addr_t), addrStateIpv4); if (addrStateIpv4 == NULL) { if (HASH_COUNT(state->addrStateIpv4) >= MAX_HASH_SIZE) { addrStateIpv4 = state->addrStateIpv4; HASH_DEL(state->addrStateIpv4, addrStateIpv4); free(addrStateIpv4); } if ((addrStateIpv4 = malloc(sizeof(struct AddrStateIpv4))) == NULL) { Error("Unable to allocate new memory for AddrStateIpv4"); return ERROR; } addrStateIpv4->ip = addr_in->sin_addr.s_addr; addrStateIpv4->count = 0; HASH_ADD(hh, state->addrStateIpv4, ip, sizeof(in_addr_t), addrStateIpv4); } addrStateIpv4->count++; if (addrStateIpv4->count >= configData.configTriggerCount) { return TRUE; } return FALSE; } static int CheckStateIpv6(struct SentryState *state, const struct sockaddr_in6 *addr) { const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)addr; struct AddrStateIpv6 *addrStateIpv6; HASH_FIND(hh, state->addrStateIpv6, &addr_in6->sin6_addr, sizeof(struct in6_addr), addrStateIpv6); if (addrStateIpv6 == NULL) { if (HASH_COUNT(state->addrStateIpv6) >= MAX_HASH_SIZE) { addrStateIpv6 = state->addrStateIpv6; HASH_DEL(state->addrStateIpv6, addrStateIpv6); free(addrStateIpv6); } if ((addrStateIpv6 = malloc(sizeof(struct AddrStateIpv6))) == NULL) { Error("Unable to allocate new memory for AddrStateIpv6"); return ERROR; } addrStateIpv6->ip = addr_in6->sin6_addr; addrStateIpv6->count = 0; HASH_ADD(hh, state->addrStateIpv6, ip, sizeof(struct in6_addr), addrStateIpv6); } addrStateIpv6->count++; if (addrStateIpv6->count >= configData.configTriggerCount) { return TRUE; } return FALSE; } void InitSentryState(struct SentryState *sentryState) { sentryState->addrStateIpv4 = NULL; sentryState->addrStateIpv6 = NULL; sentryState->isInitialized = TRUE; } void FreeSentryState(struct SentryState *sentryState) { struct AddrStateIpv4 *addrStateIpv4, *tmpAddrStateIpv4; struct AddrStateIpv6 *addrStateIpv6, *tmpAddrStateIpv6; HASH_ITER(hh, sentryState->addrStateIpv4, addrStateIpv4, tmpAddrStateIpv4) { HASH_DEL(sentryState->addrStateIpv4, addrStateIpv4); free(addrStateIpv4); } HASH_ITER(hh, sentryState->addrStateIpv6, addrStateIpv6, tmpAddrStateIpv6) { HASH_DEL(sentryState->addrStateIpv6, addrStateIpv6); free(addrStateIpv6); } sentryState->isInitialized = FALSE; } int CheckState(struct SentryState *state, const struct sockaddr *addr) { assert(state != NULL); assert(addr != NULL); if (state->isInitialized == FALSE) { Error("Sentry state is not initialized"); return ERROR; } // If the trigger count is 0, we don't need to check the state if (configData.configTriggerCount == 0) { return TRUE; } if (addr->sa_family == AF_INET) { return CheckStateIpv4(state, (const struct sockaddr_in *)addr); } else if (addr->sa_family == AF_INET6) { return CheckStateIpv6(state, (const struct sockaddr_in6 *)addr); } Error("Unsupported address family"); return ERROR; } portsentry-2.0.6/src/state_machine.h000066400000000000000000000012031510117642400175110ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include #include "uthash.h" struct AddrStateIpv4 { in_addr_t ip; int count; UT_hash_handle hh; }; struct AddrStateIpv6 { struct in6_addr ip; int count; UT_hash_handle hh; }; struct SentryState { struct AddrStateIpv4 *addrStateIpv4; struct AddrStateIpv6 *addrStateIpv6; uint8_t isInitialized; }; void InitSentryState(struct SentryState *sentryState); void FreeSentryState(struct SentryState *sentryState); int CheckState(struct SentryState *state, const struct sockaddr *addr); portsentry-2.0.6/src/uthash.h000066400000000000000000002202751510117642400162150ustar00rootroot00000000000000/* Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTHASH_H #define UTHASH_H #define UTHASH_VERSION 2.3.0 #include /* memcmp, memset, strlen */ #include /* ptrdiff_t */ #include /* exit */ #if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT /* This codepath is provided for backward compatibility, but I plan to remove it. */ #warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" typedef unsigned int uint32_t; typedef unsigned char uint8_t; #elif defined(HASH_NO_STDINT) && HASH_NO_STDINT #else #include /* uint8_t, uint32_t */ #endif /* These macros use decltype or the earlier __typeof GNU extension. As decltype is only available in newer compilers (VS2010 or gcc 4.3+ when compiling c++ source) this code uses whatever method is needed or, for VS2008 where neither is available, uses casting workarounds. */ #if !defined(DECLTYPE) && !defined(NO_DECLTYPE) #if defined(_MSC_VER) /* MS compiler */ #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ #define DECLTYPE(x) (decltype(x)) #else /* VS2008 or older (or VS2010 in C mode) */ #define NO_DECLTYPE #endif #elif defined(__MCST__) /* Elbrus C Compiler */ #define DECLTYPE(x) (__typeof(x)) #elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) #define NO_DECLTYPE #else /* GNU, Sun and other compilers */ #define DECLTYPE(x) (__typeof(x)) #endif #endif #ifdef NO_DECLTYPE #define DECLTYPE(x) #define DECLTYPE_ASSIGN(dst,src) \ do { \ char **_da_dst = (char**)(&(dst)); \ *_da_dst = (char*)(src); \ } while (0) #else #define DECLTYPE_ASSIGN(dst,src) \ do { \ (dst) = DECLTYPE(dst)(src); \ } while (0) #endif #ifndef uthash_malloc #define uthash_malloc(sz) malloc(sz) /* malloc fcn */ #endif #ifndef uthash_free #define uthash_free(ptr,sz) free(ptr) /* free fcn */ #endif #ifndef uthash_bzero #define uthash_bzero(a,n) memset(a,'\0',n) #endif #ifndef uthash_strlen #define uthash_strlen(s) strlen(s) #endif #ifndef HASH_FUNCTION #define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) #endif #ifndef HASH_KEYCMP #define HASH_KEYCMP(a,b,n) memcmp(a,b,n) #endif #ifndef uthash_noexpand_fyi #define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ #endif #ifndef uthash_expand_fyi #define uthash_expand_fyi(tbl) /* can be defined to log expands */ #endif #ifndef HASH_NONFATAL_OOM #define HASH_NONFATAL_OOM 0 #endif #if HASH_NONFATAL_OOM /* malloc failures can be recovered from */ #ifndef uthash_nonfatal_oom #define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) #define IF_HASH_NONFATAL_OOM(x) x #else /* malloc failures result in lost memory, hash tables are unusable */ #ifndef uthash_fatal #define uthash_fatal(msg) exit(-1) /* fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") #define IF_HASH_NONFATAL_OOM(x) #endif /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ #define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ /* calculate the element whose hash handle address is hhp */ #define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) /* calculate the hash handle from element address elp */ #define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) #define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ do { \ struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ unsigned _hd_bkt; \ HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ (head)->hh.tbl->buckets[_hd_bkt].count++; \ _hd_hh_item->hh_next = NULL; \ _hd_hh_item->hh_prev = NULL; \ } while (0) #define HASH_VALUE(keyptr,keylen,hashv) \ do { \ HASH_FUNCTION(keyptr, keylen, hashv); \ } while (0) #define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_bkt; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ if (HASH_BLOOM_TEST((head)->hh.tbl, hashval)) { \ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ } \ } \ } while (0) #define HASH_FIND(hh,head,keyptr,keylen,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_hashv; \ HASH_VALUE(keyptr, keylen, _hf_hashv); \ HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ } \ } while (0) #ifdef HASH_BLOOM #define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) #define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) #define HASH_BLOOM_MAKE(tbl,oomed) \ do { \ (tbl)->bloom_nbits = HASH_BLOOM; \ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ if (!(tbl)->bloom_bv) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ } \ } while (0) #define HASH_BLOOM_FREE(tbl) \ do { \ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ } while (0) #define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) #define HASH_BLOOM_BITTEST(bv,idx) ((bv[(idx)/8U] & (1U << ((idx)%8U))) != 0) #define HASH_BLOOM_ADD(tbl,hashv) \ HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #define HASH_BLOOM_TEST(tbl,hashv) \ HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #else #define HASH_BLOOM_MAKE(tbl,oomed) #define HASH_BLOOM_FREE(tbl) #define HASH_BLOOM_ADD(tbl,hashv) #define HASH_BLOOM_TEST(tbl,hashv) 1 #define HASH_BLOOM_BYTELEN 0U #endif #define HASH_MAKE_TABLE(hh,head,oomed) \ do { \ (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ if (!(head)->hh.tbl) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ (head)->hh.tbl->tail = &((head)->hh); \ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ (head)->hh.tbl->signature = HASH_SIGNATURE; \ if (!(head)->hh.tbl->buckets) { \ HASH_RECORD_OOM(oomed); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } else { \ uthash_bzero((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ uthash_free((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } \ ) \ } \ } \ } while (0) #define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ } while (0) #define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ } while (0) #define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ } while (0) #define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ } while (0) #define HASH_APPEND_LIST(hh, head, add) \ do { \ (add)->hh.next = NULL; \ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ (head)->hh.tbl->tail->next = (add); \ (head)->hh.tbl->tail = &((add)->hh); \ } while (0) #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ do { \ if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ break; \ } \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #ifdef NO_DECLTYPE #undef HASH_AKBI_INNER_LOOP #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ char *_hs_saved_head = (char*)(head); \ do { \ DECLTYPE_ASSIGN(head, _hs_iter); \ if (cmpfcn(head, add) > 0) { \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ break; \ } \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #endif #if HASH_NONFATAL_OOM #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ if (!(oomed)) { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ if (oomed) { \ HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ HASH_DELETE_HH(hh, head, &(add)->hh); \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } else { \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } \ } else { \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } \ } while (0) #else #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } while (0) #endif #define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (char*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ void *_hs_iter = (head); \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ if (_hs_iter) { \ (add)->hh.next = _hs_iter; \ if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ } else { \ (head) = (add); \ } \ HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ } else { \ HASH_APPEND_LIST(hh, head, add); \ } \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ } while (0) #define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ do { \ unsigned _hs_hashv; \ HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ } while (0) #define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) #define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) #define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (const void*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_APPEND_LIST(hh, head, add); \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ } while (0) #define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ do { \ unsigned _ha_hashv; \ HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ } while (0) #define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) #define HASH_ADD(hh,head,fieldname,keylen_in,add) \ HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) #define HASH_TO_BKT(hashv,num_bkts,bkt) \ do { \ bkt = ((hashv) & ((num_bkts) - 1U)); \ } while (0) /* delete "delptr" from the hash table. * "the usual" patch-up process for the app-order doubly-linked-list. * The use of _hd_hh_del below deserves special explanation. * These used to be expressed using (delptr) but that led to a bug * if someone used the same symbol for the head and deletee, like * HASH_DELETE(hh,users,users); * We want that to work, but by changing the head (users) below * we were forfeiting our ability to further refer to the deletee (users) * in the patch-up process. Solution: use scratch space to * copy the deletee pointer, then the latter references are via that * scratch pointer rather than through the repointed (users) symbol. */ #define HASH_DELETE(hh,head,delptr) \ HASH_DELETE_HH(hh, head, &(delptr)->hh) #define HASH_DELETE_HH(hh,head,delptrhh) \ do { \ const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } else { \ unsigned _hd_bkt; \ if (_hd_hh_del == (head)->hh.tbl->tail) { \ (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ } \ if (_hd_hh_del->prev != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ } else { \ DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ } \ if (_hd_hh_del->next != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ } \ HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ (head)->hh.tbl->num_items--; \ } \ HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ } while (0) /* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ #define HASH_FIND_STR(head,findstr,out) \ do { \ unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ } while (0) #define HASH_ADD_STR(head,strfield,add) \ do { \ unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ } while (0) #define HASH_REPLACE_STR(head,strfield,add,replaced) \ do { \ unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ } while (0) #define HASH_FIND_INT(head,findint,out) \ HASH_FIND(hh,head,findint,sizeof(int),out) #define HASH_ADD_INT(head,intfield,add) \ HASH_ADD(hh,head,intfield,sizeof(int),add) #define HASH_REPLACE_INT(head,intfield,add,replaced) \ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) #define HASH_FIND_PTR(head,findptr,out) \ HASH_FIND(hh,head,findptr,sizeof(void *),out) #define HASH_ADD_PTR(head,ptrfield,add) \ HASH_ADD(hh,head,ptrfield,sizeof(void *),add) #define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) #define HASH_DEL(head,delptr) \ HASH_DELETE(hh,head,delptr) /* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. */ #ifdef HASH_DEBUG #include /* fprintf, stderr */ #define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) #define HASH_FSCK(hh,head,where) \ do { \ struct UT_hash_handle *_thh; \ if (head) { \ unsigned _bkt_i; \ unsigned _count = 0; \ char *_prev; \ for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ unsigned _bkt_count = 0; \ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ _prev = NULL; \ while (_thh) { \ if (_prev != (char*)(_thh->hh_prev)) { \ HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ (where), (void*)_thh->hh_prev, (void*)_prev); \ } \ _bkt_count++; \ _prev = (char*)(_thh); \ _thh = _thh->hh_next; \ } \ _count += _bkt_count; \ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ } \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ _count = 0; \ _prev = NULL; \ _thh = &(head)->hh; \ while (_thh) { \ _count++; \ if (_prev != (char*)_thh->prev) { \ HASH_OOPS("%s: invalid prev %p, actual %p\n", \ (where), (void*)_thh->prev, (void*)_prev); \ } \ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ } \ } while (0) #else #define HASH_FSCK(hh,head,where) #endif /* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to * the descriptor to which this macro is defined for tuning the hash function. * The app can #include to get the prototype for write(2). */ #ifdef HASH_EMIT_KEYS #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ do { \ unsigned _klen = fieldlen; \ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ } while (0) #else #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) #endif /* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ #define HASH_BER(key,keylen,hashv) \ do { \ unsigned _hb_keylen = (unsigned)keylen; \ const unsigned char *_hb_key = (const unsigned char*)(key); \ (hashv) = 0; \ while (_hb_keylen-- != 0U) { \ (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ } \ } while (0) /* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx * (archive link: https://archive.is/Ivcan ) */ #define HASH_SAX(key,keylen,hashv) \ do { \ unsigned _sx_i; \ const unsigned char *_hs_key = (const unsigned char*)(key); \ hashv = 0; \ for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ } \ } while (0) /* FNV-1a variation */ #define HASH_FNV(key,keylen,hashv) \ do { \ unsigned _fn_i; \ const unsigned char *_hf_key = (const unsigned char*)(key); \ (hashv) = 2166136261U; \ for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ hashv = hashv ^ _hf_key[_fn_i]; \ hashv = hashv * 16777619U; \ } \ } while (0) #define HASH_OAT(key,keylen,hashv) \ do { \ unsigned _ho_i; \ const unsigned char *_ho_key=(const unsigned char*)(key); \ hashv = 0; \ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ hashv += _ho_key[_ho_i]; \ hashv += (hashv << 10); \ hashv ^= (hashv >> 6); \ } \ hashv += (hashv << 3); \ hashv ^= (hashv >> 11); \ hashv += (hashv << 15); \ } while (0) #define HASH_JEN_MIX(a,b,c) \ do { \ a -= b; a -= c; a ^= ( c >> 13 ); \ b -= c; b -= a; b ^= ( a << 8 ); \ c -= a; c -= b; c ^= ( b >> 13 ); \ a -= b; a -= c; a ^= ( c >> 12 ); \ b -= c; b -= a; b ^= ( a << 16 ); \ c -= a; c -= b; c ^= ( b >> 5 ); \ a -= b; a -= c; a ^= ( c >> 3 ); \ b -= c; b -= a; b ^= ( a << 10 ); \ c -= a; c -= b; c ^= ( b >> 15 ); \ } while (0) #define HASH_JEN(key,keylen,hashv) \ do { \ unsigned _hj_i,_hj_j,_hj_k; \ unsigned const char *_hj_key=(unsigned const char*)(key); \ hashv = 0xfeedbeefu; \ _hj_i = _hj_j = 0x9e3779b9u; \ _hj_k = (unsigned)(keylen); \ while (_hj_k >= 12U) { \ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + ( (unsigned)_hj_key[2] << 16 ) \ + ( (unsigned)_hj_key[3] << 24 ) ); \ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + ( (unsigned)_hj_key[6] << 16 ) \ + ( (unsigned)_hj_key[7] << 24 ) ); \ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + ( (unsigned)_hj_key[10] << 16 ) \ + ( (unsigned)_hj_key[11] << 24 ) ); \ \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ \ _hj_key += 12; \ _hj_k -= 12U; \ } \ hashv += (unsigned)(keylen); \ switch ( _hj_k ) { \ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ default: ; \ } \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ } while (0) /* The Paul Hsieh hash function */ #undef get16bits #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) #define get16bits(d) (*((const uint16_t *) (d))) #endif #if !defined (get16bits) #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif #define HASH_SFH(key,keylen,hashv) \ do { \ unsigned const char *_sfh_key=(unsigned const char*)(key); \ uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ \ unsigned _sfh_rem = _sfh_len & 3U; \ _sfh_len >>= 2; \ hashv = 0xcafebabeu; \ \ /* Main loop */ \ for (;_sfh_len > 0U; _sfh_len--) { \ hashv += get16bits (_sfh_key); \ _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ hashv = (hashv << 16) ^ _sfh_tmp; \ _sfh_key += 2U*sizeof (uint16_t); \ hashv += hashv >> 11; \ } \ \ /* Handle end cases */ \ switch (_sfh_rem) { \ case 3: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 16; \ hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ hashv += hashv >> 11; \ break; \ case 2: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 11; \ hashv += hashv >> 17; \ break; \ case 1: hashv += *_sfh_key; \ hashv ^= hashv << 10; \ hashv += hashv >> 1; \ break; \ default: ; \ } \ \ /* Force "avalanching" of final 127 bits */ \ hashv ^= hashv << 3; \ hashv += hashv >> 5; \ hashv ^= hashv << 4; \ hashv += hashv >> 17; \ hashv ^= hashv << 25; \ hashv += hashv >> 6; \ } while (0) /* iterate over items in a known bucket to find desired item */ #define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ do { \ if ((head).hh_head != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ } else { \ (out) = NULL; \ } \ while ((out) != NULL) { \ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ break; \ } \ } \ if ((out)->hh.hh_next != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ } else { \ (out) = NULL; \ } \ } \ } while (0) /* add an item to a bucket */ #define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ do { \ UT_hash_bucket *_ha_head = &(head); \ _ha_head->count++; \ (addhh)->hh_next = _ha_head->hh_head; \ (addhh)->hh_prev = NULL; \ if (_ha_head->hh_head != NULL) { \ _ha_head->hh_head->hh_prev = (addhh); \ } \ _ha_head->hh_head = (addhh); \ if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ && !(addhh)->tbl->noexpand) { \ HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ HASH_DEL_IN_BKT(head,addhh); \ } \ ) \ } \ } while (0) /* remove an item from a given bucket */ #define HASH_DEL_IN_BKT(head,delhh) \ do { \ UT_hash_bucket *_hd_head = &(head); \ _hd_head->count--; \ if (_hd_head->hh_head == (delhh)) { \ _hd_head->hh_head = (delhh)->hh_next; \ } \ if ((delhh)->hh_prev) { \ (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ } \ if ((delhh)->hh_next) { \ (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ } \ } while (0) /* Bucket expansion has the effect of doubling the number of buckets * and redistributing the items into the new buckets. Ideally the * items will distribute more or less evenly into the new buckets * (the extent to which this is true is a measure of the quality of * the hash function as it applies to the key domain). * * With the items distributed into more buckets, the chain length * (item count) in each bucket is reduced. Thus by expanding buckets * the hash keeps a bound on the chain length. This bounded chain * length is the essence of how a hash provides constant time lookup. * * The calculation of tbl->ideal_chain_maxlen below deserves some * explanation. First, keep in mind that we're calculating the ideal * maximum chain length based on the *new* (doubled) bucket count. * In fractions this is just n/b (n=number of items,b=new num buckets). * Since the ideal chain length is an integer, we want to calculate * ceil(n/b). We don't depend on floating point arithmetic in this * hash, so to calculate ceil(n/b) with integers we could write * * ceil(n/b) = (n/b) + ((n%b)?1:0) * * and in fact a previous version of this hash did just that. * But now we have improved things a bit by recognizing that b is * always a power of two. We keep its base 2 log handy (call it lb), * so now we can write this with a bit shift and logical AND: * * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) * */ #define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ do { \ unsigned _he_bkt; \ unsigned _he_bkt_i; \ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ if (!_he_new_buckets) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero(_he_new_buckets, \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ (tbl)->ideal_chain_maxlen = \ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ (tbl)->nonideal_items = 0; \ for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ while (_he_thh != NULL) { \ _he_hh_nxt = _he_thh->hh_next; \ HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ _he_newbkt = &(_he_new_buckets[_he_bkt]); \ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ (tbl)->nonideal_items++; \ if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ _he_newbkt->expand_mult++; \ } \ } \ _he_thh->hh_prev = NULL; \ _he_thh->hh_next = _he_newbkt->hh_head; \ if (_he_newbkt->hh_head != NULL) { \ _he_newbkt->hh_head->hh_prev = _he_thh; \ } \ _he_newbkt->hh_head = _he_thh; \ _he_thh = _he_hh_nxt; \ } \ } \ uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ (tbl)->num_buckets *= 2U; \ (tbl)->log2_num_buckets++; \ (tbl)->buckets = _he_new_buckets; \ (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ ((tbl)->ineff_expands+1U) : 0U; \ if ((tbl)->ineff_expands > 1U) { \ (tbl)->noexpand = 1; \ uthash_noexpand_fyi(tbl); \ } \ uthash_expand_fyi(tbl); \ } \ } while (0) /* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ /* Note that HASH_SORT assumes the hash handle name to be hh. * HASH_SRT was added to allow the hash handle name to be passed in. */ #define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) #define HASH_SRT(hh,head,cmpfcn) \ do { \ unsigned _hs_i; \ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ if (head != NULL) { \ _hs_insize = 1; \ _hs_looping = 1; \ _hs_list = &((head)->hh); \ while (_hs_looping != 0U) { \ _hs_p = _hs_list; \ _hs_list = NULL; \ _hs_tail = NULL; \ _hs_nmerges = 0; \ while (_hs_p != NULL) { \ _hs_nmerges++; \ _hs_q = _hs_p; \ _hs_psize = 0; \ for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ _hs_psize++; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ if (_hs_q == NULL) { \ break; \ } \ } \ _hs_qsize = _hs_insize; \ while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ if (_hs_psize == 0U) { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else if ((cmpfcn( \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ )) <= 0) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } \ if ( _hs_tail != NULL ) { \ _hs_tail->next = ((_hs_e != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ } else { \ _hs_list = _hs_e; \ } \ if (_hs_e != NULL) { \ _hs_e->prev = ((_hs_tail != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ } \ _hs_tail = _hs_e; \ } \ _hs_p = _hs_q; \ } \ if (_hs_tail != NULL) { \ _hs_tail->next = NULL; \ } \ if (_hs_nmerges <= 1U) { \ _hs_looping = 0; \ (head)->hh.tbl->tail = _hs_tail; \ DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ } \ _hs_insize *= 2U; \ } \ HASH_FSCK(hh, head, "HASH_SRT"); \ } \ } while (0) /* This function selects items from one hash into another hash. * The end result is that the selected items have dual presence * in both hashes. There is no copy of the items made; rather * they are added into the new hash through a secondary hash * hash handle that must be present in the structure. */ #define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ do { \ unsigned _src_bkt, _dst_bkt; \ void *_last_elt = NULL, *_elt; \ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ if ((src) != NULL) { \ for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ _src_hh != NULL; \ _src_hh = _src_hh->hh_next) { \ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ if (cond(_elt)) { \ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ _dst_hh->key = _src_hh->key; \ _dst_hh->keylen = _src_hh->keylen; \ _dst_hh->hashv = _src_hh->hashv; \ _dst_hh->prev = _last_elt; \ _dst_hh->next = NULL; \ if (_last_elt_hh != NULL) { \ _last_elt_hh->next = _elt; \ } \ if ((dst) == NULL) { \ DECLTYPE_ASSIGN(dst, _elt); \ HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ uthash_nonfatal_oom(_elt); \ (dst) = NULL; \ continue; \ } \ ) \ } else { \ _dst_hh->tbl = (dst)->hh_dst.tbl; \ } \ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ (dst)->hh_dst.tbl->num_items++; \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ _dst_hh->tbl = NULL; \ uthash_nonfatal_oom(_elt); \ continue; \ } \ ) \ HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ _last_elt = _elt; \ _last_elt_hh = _dst_hh; \ } \ } \ } \ } \ HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ } while (0) #define HASH_CLEAR(hh,head) \ do { \ if ((head) != NULL) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } \ } while (0) #define HASH_OVERHEAD(hh,head) \ (((head) != NULL) ? ( \ (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ sizeof(UT_hash_table) + \ (HASH_BLOOM_BYTELEN))) : 0U) #ifdef NO_DECLTYPE #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) #else #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) #endif /* obtain a count of items in the hash */ #define HASH_COUNT(head) HASH_CNT(hh,head) #define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) typedef struct UT_hash_bucket { struct UT_hash_handle *hh_head; unsigned count; /* expand_mult is normally set to 0. In this situation, the max chain length * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If * the bucket's chain exceeds this length, bucket expansion is triggered). * However, setting expand_mult to a non-zero value delays bucket expansion * (that would be triggered by additions to this particular bucket) * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. * (The multiplier is simply expand_mult+1). The whole idea of this * multiplier is to reduce bucket expansions, since they are expensive, in * situations where we know that a particular bucket tends to be overused. * It is better to let its chain length grow to a longer yet-still-bounded * value, than to do an O(n) bucket expansion too often. */ unsigned expand_mult; } UT_hash_bucket; /* random signature used only to find hash tables in external analysis */ #define HASH_SIGNATURE 0xa0111fe1u #define HASH_BLOOM_SIGNATURE 0xb12220f2u typedef struct UT_hash_table { UT_hash_bucket *buckets; unsigned num_buckets, log2_num_buckets; unsigned num_items; struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ /* in an ideal situation (all buckets used equally), no bucket would have * more than ceil(#items/#buckets) items. that's the ideal chain length. */ unsigned ideal_chain_maxlen; /* nonideal_items is the number of items in the hash whose chain position * exceeds the ideal chain maxlen. these items pay the penalty for an uneven * hash distribution; reaching them in a chain traversal takes >ideal steps */ unsigned nonideal_items; /* ineffective expands occur when a bucket doubling was performed, but * afterward, more than half the items in the hash had nonideal chain * positions. If this happens on two consecutive expansions we inhibit any * further expansion, as it's not helping; this happens when the hash * function isn't a good fit for the key domain. When expansion is inhibited * the hash will still work, albeit no longer in constant time. */ unsigned ineff_expands, noexpand; uint32_t signature; /* used only to find hash tables in external analysis */ #ifdef HASH_BLOOM uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ uint8_t *bloom_bv; uint8_t bloom_nbits; #endif } UT_hash_table; typedef struct UT_hash_handle { struct UT_hash_table *tbl; void *prev; /* prev element in app order */ void *next; /* next element in app order */ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ struct UT_hash_handle *hh_next; /* next hh in bucket order */ const void *key; /* ptr to enclosing struct's key */ unsigned keylen; /* enclosing struct's key len */ unsigned hashv; /* result of hash-fcn(key) */ } UT_hash_handle; #endif /* UTHASH_H */ portsentry-2.0.6/src/util.c000066400000000000000000000313661510117642400156720ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Craig Rowland // SPDX-FileContributor: Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config_data.h" #include "io.h" #include "portsentry.h" #include "util.h" #include "packet_info.h" static char *Realloc(char *filter, size_t newLen); char *SafeStrncpy(char *dest, const char *src, size_t size) { if (dest == NULL || src == NULL) { return NULL; } if (size < 1 || size > MAX_SAFESTRNCMP_SIZE) { return NULL; } if (size > SIZE_MAX - 1) { return NULL; } size_t src_len = strnlen(src, MAX_SAFESTRNCMP_SIZE); if (src_len >= MAX_SAFESTRNCMP_SIZE) { return NULL; } size_t copy_size = (src_len < (size - 1)) ? src_len : (size - 1); memmove(dest, src, copy_size); dest[copy_size] = '\0'; return dest; } void ResolveAddr(const struct PacketInfo *pi, char *resolvedHost, const socklen_t resolvedHostSize) { char err[ERRNOMAXBUF]; assert(resolvedHostSize > 0); assert(resolvedHostSize < INT_MAX); assert(resolvedHost != NULL); if (getnameinfo(GetSourceSockaddrFromPacketInfo(pi), GetSourceSockaddrLenFromPacketInfo(pi), resolvedHost, resolvedHostSize, NULL, 0, NI_NUMERICHOST) != 0) { Error("ResolveAddr: Unable to resolve address for %s: %s", pi->saddr, ErrnoString(err, sizeof(err))); int ret = snprintf(resolvedHost, resolvedHostSize, ""); if (ret <= 0) { resolvedHost[0] = '\0'; } else if (ret >= (int)resolvedHostSize) { Error("ResolveAddr: placeholder too long for buffer: %s", ErrnoString(err, sizeof(err))); resolvedHost[resolvedHostSize - 1] = '\0'; } } Debug("ResolveAddr: Resolved: %s", resolvedHost); } long GetLong(const char *buffer) { long value = 0; char *endptr = NULL; if (buffer == NULL) return ERROR; value = strtol(buffer, &endptr, 10); if (value == LONG_MIN || value == LONG_MAX) return ERROR; if (endptr == buffer) return ERROR; if (*endptr != '\0') return ERROR; return value; } int StrToUint16_t(const char *str, uint16_t *val) { if (str == NULL || val == NULL) { return FALSE; } if (strnlen(str, 6) > 5) { // UINT16_MAX is 65535 (5 digits) return FALSE; } char *endptr; errno = 0; long value = strtol(str, &endptr, 10); if (errno != 0 || // Conversion error endptr == str || // No digits found *endptr != '\0' || // Extra characters after number value > UINT16_MAX || // Value too large value <= 0) { // Zero or negative not allowed return FALSE; } *val = (uint16_t)value; return TRUE; } int DisposeTarget(const char *target, int port, int protocol) { int status, killRunCmdStatus, killHostsDenyStatus, killRouteStatus; int blockProtoConfig; if (protocol == IPPROTO_TCP) { blockProtoConfig = configData.blockTCP; } else if (protocol == IPPROTO_UDP) { blockProtoConfig = configData.blockUDP; } else { Error("DisposeTarget: Unknown protocol: %d", protocol); return ERROR; } if (blockProtoConfig == 0) { status = FALSE; // Not an error, but we'r not blocking } else if (blockProtoConfig == 1) { Debug("DisposeTarget: disposing of host %s on port %d with option: %d (%s)", target, port, blockProtoConfig, (protocol == IPPROTO_TCP) ? "tcp" : "udp"); Debug("DisposeTarget: killRunCmd: %s", configData.killRunCmd); Debug("DisposeTarget: runCmdFirst: %d", configData.runCmdFirst); Debug("DisposeTarget: killHostsDeny: %s", configData.killHostsDeny); Debug("DisposeTarget: killRoute: %s (%zu)", configData.killRoute, strlen(configData.killRoute)); // Need to init variable to avoid uninitialized variable warning for some compilers killRunCmdStatus = FALSE; if (configData.runCmdFirst == TRUE) { killRunCmdStatus = KillRunCmd(target, port, configData.killRunCmd, GetSentryModeString(configData.sentryMode)); } killHostsDenyStatus = KillHostsDeny(target, port, configData.killHostsDeny, GetSentryModeString(configData.sentryMode)); killRouteStatus = KillRoute(target, port, configData.killRoute, GetSentryModeString(configData.sentryMode)); if (configData.runCmdFirst == FALSE) { killRunCmdStatus = KillRunCmd(target, port, configData.killRunCmd, GetSentryModeString(configData.sentryMode)); } /* It's going to be impossible to determine a cookie cutter course of action which will work for everyone, so, * if there are multiple actions to take, we'll consider the host "blocked" if any of the actions succeed. */ if (killRunCmdStatus == TRUE || killHostsDenyStatus == TRUE || killRouteStatus == TRUE) { status = TRUE; } else { status = FALSE; } } else if (blockProtoConfig == 2) { status = KillRunCmd(target, port, configData.killRunCmd, GetSentryModeString(configData.sentryMode)); } else { Error("DisposeTarget: Unknown blockProto: %d", blockProtoConfig); status = ERROR; } return status; } const char *GetProtocolString(int proto) { switch (proto) { case IPPROTO_TCP: return ("TCP"); break; case IPPROTO_UDP: return ("UDP"); break; default: return ("UNKNOWN"); break; } } const char *GetFamilyString(int family) { switch (family) { case AF_INET: return ("AF_INET"); break; case AF_INET6: return ("AF_INET6"); break; default: return ("UNKNOWN"); break; } } int SetupPort(const struct sockaddr *addr, const socklen_t addrLen, uint8_t proto, uint8_t tcpReuseAddr) { int sock; assert(proto == IPPROTO_TCP || proto == IPPROTO_UDP); if ((sock = OpenSocket(addr->sa_family, (proto == IPPROTO_TCP) ? SOCK_STREAM : SOCK_DGRAM, proto, tcpReuseAddr)) == ERROR) { return -1; } if (BindSocket(sock, addr, addrLen, proto) == ERROR) { close(sock); return -2; } return sock; } int IsPortInUse(struct PacketInfo *pi) { int sock; sock = SetupPort(GetDestSockaddrFromPacketInfo(pi), GetDestSockaddrLenFromPacketInfo(pi), pi->protocol, FALSE); if (sock == -1) { return ERROR; } else if (sock == -2) { close(sock); return TRUE; } else { close(sock); return FALSE; } } /* This takes a tcp packet and reports what type of scan it is */ char *ReportPacketType(const struct tcphdr *tcpPkt) { static char packetDesc[MAXBUF]; static char *packetDescPtr = packetDesc; int ret; if (tcpPkt->th_flags == 0) ret = snprintf(packetDesc, MAXBUF, "TCP NULL scan"); else if (((tcpPkt->th_flags & TH_FIN) != 0) && ((tcpPkt->th_flags & TH_URG) != 0) && ((tcpPkt->th_flags & TH_PUSH) != 0)) ret = snprintf(packetDesc, MAXBUF, "TCP XMAS scan"); else if (((tcpPkt->th_flags & TH_FIN) != 0) && ((tcpPkt->th_flags & TH_SYN) == 0) && ((tcpPkt->th_flags & TH_ACK) == 0) && ((tcpPkt->th_flags & TH_PUSH) == 0) && ((tcpPkt->th_flags & TH_RST) == 0) && ((tcpPkt->th_flags & TH_URG) == 0)) ret = snprintf(packetDesc, MAXBUF, "TCP FIN scan"); else if (((tcpPkt->th_flags & TH_SYN) != 0) && ((tcpPkt->th_flags & TH_FIN) == 0) && ((tcpPkt->th_flags & TH_ACK) == 0) && ((tcpPkt->th_flags & TH_PUSH) == 0) && ((tcpPkt->th_flags & TH_RST) == 0) && ((tcpPkt->th_flags & TH_URG) == 0)) ret = snprintf(packetDesc, MAXBUF, "TCP SYN/Normal scan"); else ret = snprintf(packetDesc, MAXBUF, "Unknown Type: TCP Packet Flags: SYN: %d FIN: %d ACK: %d PSH: %d URG: %d RST: %d", tcpPkt->th_flags & TH_SYN ? 1 : 0, tcpPkt->th_flags & TH_FIN ? 1 : 0, tcpPkt->th_flags & TH_ACK ? 1 : 0, tcpPkt->th_flags & TH_PUSH ? 1 : 0, tcpPkt->th_flags & TH_URG ? 1 : 0, tcpPkt->th_flags & TH_RST ? 1 : 0); if (ret >= MAXBUF) { Error("ReportPacketType: Packet description too long for buffer: %s, truncating", packetDesc); packetDesc[MAXBUF - 1] = '\0'; } return (packetDescPtr); } char *ErrnoString(char *buf, const size_t buflen) { char *p; #if ((_POSIX_C_SOURCE >= 200112L) && !_GNU_SOURCE) || defined(BSD) strerror_r(errno, buf, buflen); p = buf; #else p = strerror_r(errno, buf, buflen); #endif return p; } int CreateDateTime(char *buf, const size_t size) { if (buf == NULL) { Error("NULL buffer provided"); return ERROR; } if (size < MIN_DATETIME_BUFFER) { Error("Buffer too small for datetime format"); return ERROR; } char *p = buf; char err[ERRNOMAXBUF]; size_t remainingSize = size; int ret; struct tm tm, *tmptr; struct timespec ts; if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { Error("Unable to get current clock time: %s", ErrnoString(err, sizeof(err))); return ERROR; } tmptr = localtime_r(&ts.tv_sec, &tm); if (tmptr != &tm) { Error("Unable to determine local time: %s", ErrnoString(err, sizeof(err))); return ERROR; } ret = (int)strftime(p, remainingSize, "%Y-%m-%dT%H:%M:%S.", tmptr); if (ret == 0 || (size_t)ret >= remainingSize) { Error("Buffer overflow while writing datetime format"); *buf = '\0'; return ERROR; } remainingSize -= (size_t)ret; p += ret; ret = snprintf(p, remainingSize, "%03ld", ts.tv_nsec / 1000000); if (ret < 0 || (size_t)ret >= remainingSize) { Error("Buffer overflow while writing milliseconds"); *buf = '\0'; return ERROR; } remainingSize -= (size_t)ret; p += ret; ret = (int)strftime(p, remainingSize, "%z", tmptr); if (ret == 0) { Error("Buffer overflow while writing timezone"); *buf = '\0'; return ERROR; } return TRUE; } static char *Realloc(char *filter, size_t newLen) { char *newFilter = NULL; if ((newFilter = realloc(filter, newLen)) == NULL) { Error("Unable to reallocate %zu bytes of memory for pcap filter", newLen); Exit(EXIT_FAILURE); } return newFilter; } char *ReallocAndAppend(char *filter, size_t *filterLen, const char *append, ...) { int neededBufferLen; char *p; va_list args; if (filterLen == NULL || append == NULL) { return NULL; } // Calculate the length of the buffer needed (excluding the null terminator) va_start(args, append); neededBufferLen = vsnprintf(NULL, 0, append, args); va_end(args); if (neededBufferLen < 0) { return NULL; } size_t totalSize = *filterLen + (size_t)neededBufferLen + 1; // Overflow if (totalSize < *filterLen) { return NULL; } filter = Realloc(filter, totalSize); // Position pointer at the end of existing string (null terminator) if (*filterLen == 0) { p = filter; } else { p = filter + *filterLen; // Points to null terminator } // Append the new string to the buffer va_start(args, append); int written = vsnprintf(p, (size_t)neededBufferLen + 1, append, args); va_end(args); if (written < 0 || written >= neededBufferLen + 1) { // vsnprintf failed or truncated return NULL; } // Update the length (excluding null terminator) *filterLen += (size_t)written; return filter; } #ifndef NDEBUG void DebugWritePacketToFs(const struct PacketInfo *pi) { int fd = -1; char filename[64], err[ERRNOMAXBUF]; size_t ipLen; const unsigned char *ip; if (pi->ip != NULL) { ip = (const unsigned char *)pi->ip; } else if (pi->ip6 != NULL) { ip = (const unsigned char *)pi->ip6; } else { Error("No IP address to write to file"); goto exit; } if (pi->tcp != NULL) { ipLen = (size_t)((const unsigned char *)pi->tcp - ip); } else if (pi->udp != NULL) { ipLen = (size_t)((const unsigned char *)pi->udp - ip); } else { Error("No TCP or UDP header to write to file"); goto exit; } #ifdef __linux__ snprintf(filename, sizeof(filename), "/tmp/packet-%lu", time(NULL)); #elif __OpenBSD__ snprintf(filename, sizeof(filename), "/tmp/packet-%lld", time(NULL)); #else snprintf(filename, sizeof(filename), "/tmp/packet-%ld", time(NULL)); #endif if ((fd = open(filename, O_CREAT | O_WRONLY, 0644)) == -1) { Error("Unable to open file %s for writing: %s", filename, ErrnoString(err, sizeof(err))); goto exit; } if (write(fd, ip, ipLen) == -1) { Error("Unable to write IP header to file %s: %s", filename, ErrnoString(err, sizeof(err))); goto exit; } if (pi->tcp != NULL) { if (write(fd, pi->tcp, sizeof(struct tcphdr)) == -1) { Error("Unable to write TCP header to file %s: %s", filename, ErrnoString(err, sizeof(err))); goto exit; } } else if (pi->udp != NULL) { if (write(fd, pi->udp, sizeof(struct udphdr)) == -1) { Error("Unable to write UDP header to file %s: %s", filename, ErrnoString(err, sizeof(err))); goto exit; } } Debug("Wrote packet to file %s", filename); exit: if (fd != -1) close(fd); } #endif portsentry-2.0.6/src/util.h000066400000000000000000000024141510117642400156670ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Craig Rowland // SPDX-FileContributor: Marcus Hufvudsson // // SPDX-License-Identifier: BSD-2-Clause #pragma once #include #include #include #include #include #include #include #include "packet_info.h" #define MAX_SAFESTRNCMP_SIZE ((size_t)(1024 * 1024)) #define MIN_DATETIME_BUFFER 32 char *SafeStrncpy(char *, const char *, size_t); void ResolveAddr(const struct PacketInfo *pi, char *resolvedHost, const socklen_t resolvedHostSize); long GetLong(const char *buffer); int StrToUint16_t(const char *str, uint16_t *val); int DisposeTarget(const char *, int, int); const char *GetProtocolString(int proto); const char *GetFamilyString(int family); int SetupPort(const struct sockaddr *addr, const socklen_t addrLen, uint8_t proto, uint8_t tcpReuseAddr); int IsPortInUse(struct PacketInfo *pi); char *ReportPacketType(const struct tcphdr *); char *ErrnoString(char *buf, const size_t buflen); int CreateDateTime(char *buf, const size_t size); __attribute__((format(printf, 3, 4))) char *ReallocAndAppend(char *filter, size_t *filterLen, const char *append, ...); #ifndef NDEBUG void DebugWritePacketToFs(const struct PacketInfo *pi); #endif portsentry-2.0.6/system_test/000077500000000000000000000000001510117642400163345ustar00rootroot00000000000000portsentry-2.0.6/system_test/001-connect_sentry_tcp/000077500000000000000000000000001510117642400225355ustar00rootroot00000000000000portsentry-2.0.6/system_test/001-connect_sentry_tcp/portsentry.conf000066400000000000000000000004241510117642400256350ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_ROUTE="true $TARGET$" portsentry-2.0.6/system_test/001-connect_sentry_tcp/portsentry.test000066400000000000000000000000151510117642400256630ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/001-connect_sentry_tcp/test.sh000077500000000000000000000006351510117642400240570ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T confirmBlockTriggered tcp confirmBlockFileSize 1 0 runNmap 11 T confirmAlreadyBlocked confirmBlockFileSize 1 0 runNmap 11 U runNmap 11 U confirmOccurrenceStdout 2 "Scan from: \[127.0.0.1\] (127.0.0.1) protocol: \[TCP\] port: \[11\] type: \[Connect\]" confirmOccurrenceStdout 2 "Scan from: \[127.0.0.1\] (127.0.0.1) protocol: \[UDP\] port: \[11\] type: \[Connect\]" ok portsentry-2.0.6/system_test/002-connect_sentry_udp/000077500000000000000000000000001510117642400225405ustar00rootroot00000000000000portsentry-2.0.6/system_test/002-connect_sentry_udp/portsentry.conf000066400000000000000000000004241510117642400256400ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_ROUTE="true $TARGET$" portsentry-2.0.6/system_test/002-connect_sentry_udp/portsentry.test000066400000000000000000000000151510117642400256660ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/002-connect_sentry_udp/test.sh000077500000000000000000000006341510117642400240610ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 U confirmBlockTriggered udp confirmBlockFileSize 1 0 runNmap 11 U confirmAlreadyBlocked confirmBlockFileSize 1 0 runNmap 11 T runNmap 11 T confirmOccurrenceStdout 2 "Scan from: \[127.0.0.1\] (127.0.0.1) protocol: \[TCP\] port: \[11\] type: \[Connect\]" confirmOccurrenceStdout 2 "Scan from: \[127.0.0.1\] (127.0.0.1) protocol: \[UDP\] port: \[11\] type: \[Connect]" ok portsentry-2.0.6/system_test/003-banner_tcp/000077500000000000000000000000001510117642400207475ustar00rootroot00000000000000portsentry-2.0.6/system_test/003-banner_tcp/portsentry.conf000066400000000000000000000004771510117642400240570ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" PORT_BANNER="Some banner printed on port" KILL_ROUTE="true $TARGET$" portsentry-2.0.6/system_test/003-banner_tcp/portsentry.test000066400000000000000000000000151510117642400240750ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/003-banner_tcp/test.sh000077500000000000000000000004701510117642400222660ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh verbose "expect connect to tcp localhost:11 w/ banner" if ! $TEST_DIR/portcon 11 tcp | grep -q "Some banner printed on port"; then err "Expected banner not found" fi confirmBlockTriggered tcp confirmBlockFileSize 1 0 runNmap 11 T confirmAlreadyBlocked confirmBlockFileSize 1 0 ok portsentry-2.0.6/system_test/004-banner_udp/000077500000000000000000000000001510117642400207525ustar00rootroot00000000000000portsentry-2.0.6/system_test/004-banner_udp/portsentry.conf000066400000000000000000000004771510117642400240620ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" PORT_BANNER="Some banner printed on port" KILL_ROUTE="true $TARGET$" portsentry-2.0.6/system_test/004-banner_udp/portsentry.test000066400000000000000000000000151510117642400241000ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/004-banner_udp/test.sh000077500000000000000000000004701510117642400222710ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh verbose "expect connect to udp localhost:11 w/ banner" if ! $TEST_DIR/portcon 11 udp | grep -q "Some banner printed on port"; then err "Expected banner not found" fi confirmBlockTriggered udp confirmBlockFileSize 1 0 runNmap 11 U confirmAlreadyBlocked confirmBlockFileSize 1 0 ok portsentry-2.0.6/system_test/005-block_0_tcp/000077500000000000000000000000001510117642400210155ustar00rootroot00000000000000portsentry-2.0.6/system_test/005-block_0_tcp/portsentry.conf000066400000000000000000000003431510117642400241150ustar00rootroot00000000000000TCP_PORTS="1,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="0" BLOCK_TCP="0" portsentry-2.0.6/system_test/005-block_0_tcp/portsentry.test000066400000000000000000000000151510117642400241430ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/005-block_0_tcp/test.sh000077500000000000000000000003341510117642400223330ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T confirmStdoutScanMessage tcp confirmHistoryFileMessage tcp runNmap 11 T confirmOccurrenceStdout 2 "^Scan from: \[127\.0\.0\.1\] (127\.0\.0\.1) protocol: \[TCP\] port: \[11\]" ok portsentry-2.0.6/system_test/006-block_0_udp/000077500000000000000000000000001510117642400210205ustar00rootroot00000000000000portsentry-2.0.6/system_test/006-block_0_udp/portsentry.conf000066400000000000000000000003471510117642400241240ustar00rootroot00000000000000UDP_PORTS="1,7,9,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="0" BLOCK_TCP="0" portsentry-2.0.6/system_test/006-block_0_udp/portsentry.test000066400000000000000000000000151510117642400241460ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/006-block_0_udp/test.sh000077500000000000000000000003351510117642400223370ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 U confirmStdoutScanMessage udp confirmHistoryFileMessage udp runNmap 11 U confirmOccurrenceStdout 2 "^Scan from: \[127\.0\.0\.1\] (127\.0\.0\.1) protocol: \[UDP\] port: \[11\]" ok portsentry-2.0.6/system_test/007-block_2_tcp/000077500000000000000000000000001510117642400210215ustar00rootroot00000000000000portsentry-2.0.6/system_test/007-block_2_tcp/portsentry.conf000066400000000000000000000004671510117642400241300ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="2" BLOCK_TCP="2" KILL_RUN_CMD="/tmp/portsentry-test/extcmd.sh $TARGET$ $PORT$" portsentry-2.0.6/system_test/007-block_2_tcp/portsentry.test000066400000000000000000000000151510117642400241470ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/007-block_2_tcp/test.sh000077500000000000000000000006371510117642400223450ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh cat > $TEST_DIR/extcmd.sh < /tmp/portsentry-test/extcmd.stdout EOF chmod +x $TEST_DIR/extcmd.sh runNmap 11 T confirmStdoutScanMessage tcp confirmHistoryFileMessage tcp confirmExternalCommandRunMessage verbose "expect extcmd.sh output" if ! findInFile "^127\.0\.0\.1 11" $TEST_DIR/extcmd.stdout ; then err "Expected extcmd.sh output not found" fi ok portsentry-2.0.6/system_test/008-block_2_udp/000077500000000000000000000000001510117642400210245ustar00rootroot00000000000000portsentry-2.0.6/system_test/008-block_2_udp/portsentry.conf000066400000000000000000000004671510117642400241330ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="2" BLOCK_TCP="2" KILL_RUN_CMD="/tmp/portsentry-test/extcmd.sh $TARGET$ $PORT$" portsentry-2.0.6/system_test/008-block_2_udp/portsentry.test000066400000000000000000000000151510117642400241520ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/008-block_2_udp/test.sh000077500000000000000000000006371510117642400223500ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh cat > $TEST_DIR/extcmd.sh < /tmp/portsentry-test/extcmd.stdout EOF chmod +x $TEST_DIR/extcmd.sh runNmap 11 U confirmStdoutScanMessage udp confirmHistoryFileMessage udp confirmExternalCommandRunMessage verbose "expect extcmd.sh output" if ! findInFile "^127\.0\.0\.1 11" $TEST_DIR/extcmd.stdout ; then err "Expected extcmd.sh output not found" fi ok portsentry-2.0.6/system_test/009-scantrigger/000077500000000000000000000000001510117642400211525ustar00rootroot00000000000000portsentry-2.0.6/system_test/009-scantrigger/portsentry.conf000066400000000000000000000004461510117642400242560ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" SCAN_TRIGGER="2" KILL_ROUTE="true $TARGET$" portsentry-2.0.6/system_test/009-scantrigger/portsentry.test000066400000000000000000000000151510117642400243000ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/009-scantrigger/test.sh000077500000000000000000000007241510117642400224730ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T verbose "expect no trigger" if ! findInFile "^Scan from: \[127.0.0.1\] (127.0.0.1) protocol: \[TCP\] port: \[11\] type: \[Connect\] IP opts: \[unknown\] ignored: \[false\] triggered: \[false\] noblock: \[unset\]" $PORTSENTRY_STDOUT; then err "Unable to find scan message w/ no trigger info" fi runNmap 11 T confirmBlockTriggered tcp confirmBlockFileSize 1 0 runNmap 11 T confirmAlreadyBlocked confirmBlockFileSize 1 0 ok portsentry-2.0.6/system_test/010-route_kill/000077500000000000000000000000001510117642400210035ustar00rootroot00000000000000portsentry-2.0.6/system_test/010-route_kill/portsentry.conf000066400000000000000000000004561510117642400241100ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_ROUTE="/tmp/portsentry-test/extcmd.sh $TARGET$" portsentry-2.0.6/system_test/010-route_kill/portsentry.test000066400000000000000000000000151510117642400241310ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/010-route_kill/test.sh000077500000000000000000000006131510117642400223210ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh cat > $TEST_DIR/extcmd.sh < /tmp/portsentry-test/extcmd.stdout EOF chmod +x $TEST_DIR/extcmd.sh runNmap 11 T confirmBlockTriggered tcp confirmBlockFileSize 1 0 confirmRouteKillMessage verbose "expect extcmd.sh output" if ! findInFile "^127\.0\.0\.1" $TEST_DIR/extcmd.stdout ; then err "Expected extcmd.sh output not found" fi ok portsentry-2.0.6/system_test/011-kill_and_extcmd/000077500000000000000000000000001510117642400217545ustar00rootroot00000000000000portsentry-2.0.6/system_test/011-kill_and_extcmd/portsentry.conf000066400000000000000000000005571510117642400250630ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_ROUTE="/tmp/portsentry-test/routesim.sh $TARGET$" KILL_RUN_CMD="/tmp/portsentry-test/extcmd.sh $TARGET$ $PORT$" portsentry-2.0.6/system_test/011-kill_and_extcmd/portsentry.test000066400000000000000000000000151510117642400251020ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/011-kill_and_extcmd/test.sh000077500000000000000000000021561510117642400232760ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh cat > $TEST_DIR/extcmd.sh < /tmp/portsentry-test/extcmd.stdout EOF cat > $TEST_DIR/routesim.sh < /tmp/portsentry-test/routesim.stdout EOF chmod +x $TEST_DIR/extcmd.sh chmod +x $TEST_DIR/routesim.sh runNmap 11 T waitForFile $TEST_DIR/extcmd.stdout sleep 1 confirmBlockFileSize 1 0 verbose "expect routesim.sh output" if ! findInFile "^127\.0\.0\.1" $TEST_DIR/routesim.stdout ; then err "Expected routesim.sh output not found" fi verbose "expect extcmd.sh output" if ! findInFile "^127\.0\.0\.1 11" $TEST_DIR/extcmd.stdout ; then err "Expected extcmd.sh output not found" fi verbose "expect correct message ordering: route kill, external command, scan message" if ! cat $PORTSENTRY_STDOUT | tr -d '\n' | grep -q "attackalert: Host 127.0.0.1 has been blocked via dropped route using command.*attackalert: External command run for host: 127.0.0.1 using command.*Scan from: \[127\.0\.0\.1\] (127\.0\.0\.1) protocol: \[TCP\] port: \[11\]"; then err "Expected correct connect, route kill and external command run messages - not found" fi ok portsentry-2.0.6/system_test/012-extcmd_and_kill/000077500000000000000000000000001510117642400217555ustar00rootroot00000000000000portsentry-2.0.6/system_test/012-extcmd_and_kill/portsentry.conf000066400000000000000000000006101510117642400250520ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_ROUTE="/tmp/portsentry-test/routesim.sh $TARGET$" KILL_RUN_CMD="/tmp/portsentry-test/extcmd.sh $TARGET$ $PORT$" KILL_RUN_CMD_FIRST = "1" portsentry-2.0.6/system_test/012-extcmd_and_kill/portsentry.test000066400000000000000000000000151510117642400251030ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/012-extcmd_and_kill/test.sh000077500000000000000000000021431510117642400232730ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh cat > $TEST_DIR/extcmd.sh < /tmp/portsentry-test/extcmd.stdout EOF cat > $TEST_DIR/routesim.sh < /tmp/portsentry-test/routesim.stdout EOF chmod +x $TEST_DIR/extcmd.sh chmod +x $TEST_DIR/routesim.sh runNmap 11 T waitForFile $TEST_DIR/extcmd.stdout sleep 1 confirmBlockFileSize 1 0 verbose "expect routesim.sh output" if ! findInFile "^127\.0\.0\.1" $TEST_DIR/routesim.stdout ; then err "Expected routesim.sh output not found" fi verbose "expect extcmd.sh output" if ! findInFile "^127\.0\.0\.1 11" $TEST_DIR/extcmd.stdout ; then err "Expected extcmd.sh output not found" fi verbose "expect correct route kill, external and scan command message ordering" if ! cat $PORTSENTRY_STDOUT | tr -d '\n' | grep -q "attackalert: External command run for host: 127.0.0.1 using command.*attackalert: Host 127.0.0.1 has been blocked via dropped route using command.*Scan from: \[127\.0\.0\.1\] (127\.0\.0\.1) protocol: \[TCP\] port: \[11\]"; then err "Expected correct route kill, external and scan command run messages not found" fi ok portsentry-2.0.6/system_test/013-kill_route_and_hosts_deny/000077500000000000000000000000001510117642400240675ustar00rootroot00000000000000portsentry-2.0.6/system_test/013-kill_route_and_hosts_deny/portsentry.conf000066400000000000000000000005201510117642400271640ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_ROUTE="/tmp/portsentry-test/routesim.sh $TARGET$" KILL_HOSTS_DENY="ALL: $TARGET$" portsentry-2.0.6/system_test/013-kill_route_and_hosts_deny/portsentry.test000066400000000000000000000000151510117642400272150ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/013-kill_route_and_hosts_deny/test.sh000077500000000000000000000007301510117642400254050ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh rm /etc/hosts.deny touch /etc/hosts.deny cat > $TEST_DIR/routesim.sh < /tmp/portsentry-test/routesim.stdout EOF chmod +x $TEST_DIR/routesim.sh runNmap 11 T verbose "expect routesim.sh output" if ! findInFile "^127\.0\.0\.1" $TEST_DIR/routesim.stdout ; then err "Expected routesim.sh output not found" fi confirmBlockTriggered tcp confirmBlockFileSize 1 0 confirmRouteKillMessage confirmHostWrapperMessage ok portsentry-2.0.6/system_test/014-stealth_sentry_tcp/000077500000000000000000000000001510117642400225545ustar00rootroot00000000000000portsentry-2.0.6/system_test/014-stealth_sentry_tcp/portsentry.conf000066400000000000000000000004771510117642400256640ustar00rootroot00000000000000TCP_PORTS="1,11,22" UDP_PORTS="1,7,9" HISTORY_FILE="/tmp/portsentry-test/portsentry.history" BLOCKED_FILE="/tmp/portsentry-test/portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_HOSTS_DENY="ALL: $TARGET$" portsentry-2.0.6/system_test/014-stealth_sentry_tcp/portsentry.test000066400000000000000000000000371510117642400257060ustar00rootroot00000000000000--stealth -m pcap -d -i ALL -L portsentry-2.0.6/system_test/014-stealth_sentry_tcp/test.sh000077500000000000000000000002351510117642400240720ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T confirmBlockTriggered tcp confirmBlockFileSize 1 0 runNmap 11 T confirmAlreadyBlocked confirmBlockFileSize 1 0 ok portsentry-2.0.6/system_test/015-stealth_sentry_udp/000077500000000000000000000000001510117642400225575ustar00rootroot00000000000000portsentry-2.0.6/system_test/015-stealth_sentry_udp/portsentry.conf000066400000000000000000000005021510117642400256540ustar00rootroot00000000000000TCP_PORTS="1,11,22" UDP_PORTS="1,7,9,11" HISTORY_FILE="/tmp/portsentry-test/portsentry.history" BLOCKED_FILE="/tmp/portsentry-test/portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_HOSTS_DENY="ALL: $TARGET$" portsentry-2.0.6/system_test/015-stealth_sentry_udp/portsentry.test000066400000000000000000000000371510117642400257110ustar00rootroot00000000000000--stealth -m pcap -d -i ALL -L portsentry-2.0.6/system_test/015-stealth_sentry_udp/test.sh000077500000000000000000000002351510117642400240750ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 U confirmBlockTriggered udp confirmBlockFileSize 1 0 runNmap 11 U confirmAlreadyBlocked confirmBlockFileSize 1 0 ok portsentry-2.0.6/system_test/016-self_ignore/000077500000000000000000000000001510117642400211345ustar00rootroot00000000000000portsentry-2.0.6/system_test/016-self_ignore/portsentry.conf000066400000000000000000000002371510117642400242360ustar00rootroot00000000000000TCP_PORTS="1,11,22" HISTORY_FILE="/tmp/portsentry-test/portsentry.history" BLOCKED_FILE="/tmp/portsentry-test/portsentry.blocked" BLOCK_UDP="0" BLOCK_TCP="0"portsentry-2.0.6/system_test/016-self_ignore/portsentry.test000066400000000000000000000000341510117642400242630ustar00rootroot00000000000000--stealth -m pcap -d -i ALL portsentry-2.0.6/system_test/016-self_ignore/test.sh000077500000000000000000000003341510117642400224520ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T if ! findInFile "^debug: Source address 127.0.0.1 same as destination address 127.0.0.1, skipping" $PORTSENTRY_STDOUT ; then err "Expected self-ignore message not found" fi ok portsentry-2.0.6/system_test/018-syn_scan/000077500000000000000000000000001510117642400204575ustar00rootroot00000000000000portsentry-2.0.6/system_test/018-syn_scan/portsentry.conf000066400000000000000000000005021510117642400235540ustar00rootroot00000000000000TCP_PORTS="1,11,22" UDP_PORTS="1,7,9,11" HISTORY_FILE="/tmp/portsentry-test/portsentry.history" BLOCKED_FILE="/tmp/portsentry-test/portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_HOSTS_DENY="ALL: $TARGET$" portsentry-2.0.6/system_test/018-syn_scan/portsentry.test000066400000000000000000000000371510117642400236110ustar00rootroot00000000000000--stealth -m pcap -d -i ALL -L portsentry-2.0.6/system_test/018-syn_scan/test.sh000077500000000000000000000001571510117642400220000ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 S confirmSynScan confirmBlockTriggered tcp confirmBlockFileSize 1 0 ok portsentry-2.0.6/system_test/019-null_scan/000077500000000000000000000000001510117642400206215ustar00rootroot00000000000000portsentry-2.0.6/system_test/019-null_scan/portsentry.conf000066400000000000000000000005021510117642400237160ustar00rootroot00000000000000TCP_PORTS="1,11,22" UDP_PORTS="1,7,9,11" HISTORY_FILE="/tmp/portsentry-test/portsentry.history" BLOCKED_FILE="/tmp/portsentry-test/portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_HOSTS_DENY="ALL: $TARGET$" portsentry-2.0.6/system_test/019-null_scan/portsentry.test000066400000000000000000000000371510117642400237530ustar00rootroot00000000000000--stealth -m pcap -d -i ALL -L portsentry-2.0.6/system_test/019-null_scan/test.sh000077500000000000000000000001601510117642400221340ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 N confirmNullScan confirmBlockTriggered tcp confirmBlockFileSize 1 0 ok portsentry-2.0.6/system_test/020-xmas_scan/000077500000000000000000000000001510117642400206075ustar00rootroot00000000000000portsentry-2.0.6/system_test/020-xmas_scan/portsentry.conf000066400000000000000000000005021510117642400237040ustar00rootroot00000000000000TCP_PORTS="1,11,22" UDP_PORTS="1,7,9,11" HISTORY_FILE="/tmp/portsentry-test/portsentry.history" BLOCKED_FILE="/tmp/portsentry-test/portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_HOSTS_DENY="ALL: $TARGET$" portsentry-2.0.6/system_test/020-xmas_scan/portsentry.test000066400000000000000000000000371510117642400237410ustar00rootroot00000000000000--stealth -m pcap -d -i ALL -L portsentry-2.0.6/system_test/020-xmas_scan/test.sh000077500000000000000000000001601510117642400221220ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 X confirmXmasScan confirmBlockTriggered tcp confirmBlockFileSize 1 0 ok portsentry-2.0.6/system_test/021-fin_scan/000077500000000000000000000000001510117642400204145ustar00rootroot00000000000000portsentry-2.0.6/system_test/021-fin_scan/portsentry.conf000066400000000000000000000005021510117642400235110ustar00rootroot00000000000000TCP_PORTS="1,11,22" UDP_PORTS="1,7,9,11" HISTORY_FILE="/tmp/portsentry-test/portsentry.history" BLOCKED_FILE="/tmp/portsentry-test/portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_HOSTS_DENY="ALL: $TARGET$" portsentry-2.0.6/system_test/021-fin_scan/portsentry.test000066400000000000000000000000371510117642400235460ustar00rootroot00000000000000--stealth -m pcap -d -i ALL -L portsentry-2.0.6/system_test/021-fin_scan/test.sh000077500000000000000000000001571510117642400217350ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 F confirmFinScan confirmBlockTriggered tcp confirmBlockFileSize 1 0 ok portsentry-2.0.6/system_test/023-sentry_connect_range/000077500000000000000000000000001510117642400230475ustar00rootroot00000000000000portsentry-2.0.6/system_test/023-sentry_connect_range/portsentry.conf000066400000000000000000000003701510117642400261470ustar00rootroot00000000000000TCP_PORTS="1,5-11" UDP_PORTS="1,7-11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="0" BLOCK_TCP="0" portsentry-2.0.6/system_test/023-sentry_connect_range/portsentry.test000066400000000000000000000000151510117642400261750ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/023-sentry_connect_range/test.sh000077500000000000000000000006321510117642400243660ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T confirmStdoutScanMessage tcp confirmHistoryFileMessage tcp runNmap 11 T confirmOccurrenceStdout 2 "^Scan from: \[127\.0\.0\.1\] (127\.0\.0\.1) protocol: \[TCP\] port: \[11\]" runNmap 11 U confirmStdoutScanMessage udp confirmHistoryFileMessage udp runNmap 11 U confirmOccurrenceStdout 2 "^Scan from: \[127\.0\.0\.1\] (127\.0\.0\.1) protocol: \[UDP\] port: \[11\]" ok portsentry-2.0.6/system_test/024-sentry_pcap_range/000077500000000000000000000000001510117642400223425ustar00rootroot00000000000000portsentry-2.0.6/system_test/024-sentry_pcap_range/portsentry.conf000066400000000000000000000003701510117642400254420ustar00rootroot00000000000000TCP_PORTS="1,5-11" UDP_PORTS="1,7-11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="0" BLOCK_TCP="0" portsentry-2.0.6/system_test/024-sentry_pcap_range/portsentry.test000066400000000000000000000000341510117642400254710ustar00rootroot00000000000000--stealth -m pcap -d -i ALL portsentry-2.0.6/system_test/024-sentry_pcap_range/test.sh000077500000000000000000000006321510117642400236610ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T confirmStdoutScanMessage tcp confirmHistoryFileMessage tcp runNmap 11 T confirmOccurrenceStdout 2 "^Scan from: \[127\.0\.0\.1\] (127\.0\.0\.1) protocol: \[TCP\] port: \[11\]" runNmap 11 U confirmStdoutScanMessage udp confirmHistoryFileMessage udp runNmap 11 U confirmOccurrenceStdout 2 "^Scan from: \[127\.0\.0\.1\] (127\.0\.0\.1) protocol: \[UDP\] port: \[11\]" ok portsentry-2.0.6/system_test/025-pcap_invalid_probe/000077500000000000000000000000001510117642400224605ustar00rootroot00000000000000portsentry-2.0.6/system_test/025-pcap_invalid_probe/portsentry.conf000066400000000000000000000003431510117642400255600ustar00rootroot00000000000000TCP_PORTS="1,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="0" BLOCK_TCP="0" portsentry-2.0.6/system_test/025-pcap_invalid_probe/portsentry.test000066400000000000000000000000341510117642400256070ustar00rootroot00000000000000--stealth -m pcap -d -i ALL portsentry-2.0.6/system_test/025-pcap_invalid_probe/test.sh000077500000000000000000000003341510117642400237760ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 10 T verbose "don't expect attackalert block message" if findInFile "^Scan from: \[127\.0\.0\.1\]" $PORTSENTRY_STDOUT; then err "No attackalert message expected but was found" fi ok portsentry-2.0.6/system_test/026-connect_invalid_probe/000077500000000000000000000000001510117642400231675ustar00rootroot00000000000000portsentry-2.0.6/system_test/026-connect_invalid_probe/portsentry.conf000066400000000000000000000003431510117642400262670ustar00rootroot00000000000000TCP_PORTS="1,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="0" BLOCK_TCP="0" portsentry-2.0.6/system_test/026-connect_invalid_probe/portsentry.test000066400000000000000000000000241510117642400263150ustar00rootroot00000000000000--connect -d -i ALL portsentry-2.0.6/system_test/026-connect_invalid_probe/test.sh000077500000000000000000000003341510117642400245050ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 10 T verbose "don't expect attackalert block message" if findInFile "^Scan from: \[127\.0\.0\.1\]" $PORTSENTRY_STDOUT; then err "No attackalert message expected but was found" fi ok portsentry-2.0.6/system_test/027-stealth_invalid_probe/000077500000000000000000000000001510117642400232035ustar00rootroot00000000000000portsentry-2.0.6/system_test/027-stealth_invalid_probe/hook_pre_setup.sh000077500000000000000000000001511510117642400265650ustar00rootroot00000000000000#!/bin/sh if [ "$(uname -s)" != "Linux" ]; then echo "Skipping test on non-Linux system" exit 0 fi portsentry-2.0.6/system_test/027-stealth_invalid_probe/portsentry.conf000066400000000000000000000003431510117642400263030ustar00rootroot00000000000000TCP_PORTS="1,11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="0" BLOCK_TCP="0" portsentry-2.0.6/system_test/027-stealth_invalid_probe/portsentry.test000066400000000000000000000000331510117642400263310ustar00rootroot00000000000000--stealth -m raw -d -i ALL portsentry-2.0.6/system_test/027-stealth_invalid_probe/test.sh000077500000000000000000000003341510117642400245210ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 10 T verbose "don't expect attackalert block message" if findInFile "^Scan from: \[127\.0\.0\.1\]" $PORTSENTRY_STDOUT; then err "No attackalert message expected but was found" fi ok portsentry-2.0.6/system_test/028-ipv6_connect/000077500000000000000000000000001510117642400212405ustar00rootroot00000000000000portsentry-2.0.6/system_test/028-ipv6_connect/portsentry.conf000066400000000000000000000004771510117642400243500ustar00rootroot00000000000000TCP_PORTS="1,11,22" UDP_PORTS="1,7,9" HISTORY_FILE="/tmp/portsentry-test/portsentry.history" BLOCKED_FILE="/tmp/portsentry-test/portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_HOSTS_DENY="ALL: $TARGET$" portsentry-2.0.6/system_test/028-ipv6_connect/portsentry.test000066400000000000000000000000151510117642400243660ustar00rootroot00000000000000--connect -d portsentry-2.0.6/system_test/028-ipv6_connect/test.sh000077500000000000000000000002451510117642400225570ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T 6 confirmBlockTriggered tcp 6 confirmBlockFileSize 0 1 runNmap 11 T 6 confirmAlreadyBlocked 6 confirmBlockFileSize 0 1 ok portsentry-2.0.6/system_test/029-ipv6_pcap/000077500000000000000000000000001510117642400205335ustar00rootroot00000000000000portsentry-2.0.6/system_test/029-ipv6_pcap/portsentry.conf000066400000000000000000000004771510117642400236430ustar00rootroot00000000000000TCP_PORTS="1,11,22" UDP_PORTS="1,7,9" HISTORY_FILE="/tmp/portsentry-test/portsentry.history" BLOCKED_FILE="/tmp/portsentry-test/portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_HOSTS_DENY="ALL: $TARGET$" portsentry-2.0.6/system_test/029-ipv6_pcap/portsentry.test000066400000000000000000000000371510117642400236650ustar00rootroot00000000000000--stealth -m pcap -d -i ALL -L portsentry-2.0.6/system_test/029-ipv6_pcap/test.sh000077500000000000000000000002451510117642400220520ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T 6 confirmBlockTriggered tcp 6 confirmBlockFileSize 0 1 runNmap 11 T 6 confirmAlreadyBlocked 6 confirmBlockFileSize 0 1 ok portsentry-2.0.6/system_test/030-ipv6_raw/000077500000000000000000000000001510117642400203715ustar00rootroot00000000000000portsentry-2.0.6/system_test/030-ipv6_raw/hook_pre_setup.sh000077500000000000000000000001511510117642400237530ustar00rootroot00000000000000#!/bin/sh if [ "$(uname -s)" != "Linux" ]; then echo "Skipping test on non-Linux system" exit 0 fi portsentry-2.0.6/system_test/030-ipv6_raw/portsentry.conf000066400000000000000000000004771510117642400235010ustar00rootroot00000000000000TCP_PORTS="1,11,22" UDP_PORTS="1,7,9" HISTORY_FILE="/tmp/portsentry-test/portsentry.history" BLOCKED_FILE="/tmp/portsentry-test/portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_HOSTS_DENY="ALL: $TARGET$" portsentry-2.0.6/system_test/030-ipv6_raw/portsentry.test000066400000000000000000000000271510117642400235220ustar00rootroot00000000000000--stealth -m raw -d -L portsentry-2.0.6/system_test/030-ipv6_raw/test.sh000077500000000000000000000002451510117642400217100ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T 6 confirmBlockTriggered tcp 6 confirmBlockFileSize 0 1 runNmap 11 T 6 confirmAlreadyBlocked 6 confirmBlockFileSize 0 1 ok portsentry-2.0.6/system_test/031-ignore_file/000077500000000000000000000000001510117642400211175ustar00rootroot00000000000000portsentry-2.0.6/system_test/031-ignore_file/portsentry.conf000066400000000000000000000004321510117642400242160ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" IGNORE_FILE="./portsentry.ignore" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="0" BLOCK_TCP="0" portsentry-2.0.6/system_test/031-ignore_file/portsentry.ignore000066400000000000000000000007401510117642400245560ustar00rootroot00000000000000# Put hosts in here you never want blocked. This includes the IP addresses # of all local interfaces on the protected host (i.e virtual host, mult-home) # Keep 127.0.0.1 and 0.0.0.0 to keep people from playing games. # # PortSentry can support full netmasks for networks as well. Format is: # # / # # Example: # # 192.168.2.0/24 # 192.168.0.0/16 # 192.168.2.1/32 # Etc. # # If you don't supply a netmask it is assumed to be 32 bits. # # 127.0.0.1/32 0.0.0.0 portsentry-2.0.6/system_test/031-ignore_file/portsentry.test000066400000000000000000000000371510117642400242510ustar00rootroot00000000000000--stealth -m pcap -d -i ALL -v portsentry-2.0.6/system_test/031-ignore_file/test.sh000077500000000000000000000000761510117642400224400ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T confirmIgnoreFile ok portsentry-2.0.6/system_test/032-ignore_file_no_mask/000077500000000000000000000000001510117642400226275ustar00rootroot00000000000000portsentry-2.0.6/system_test/032-ignore_file_no_mask/portsentry.conf000066400000000000000000000004321510117642400257260ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" IGNORE_FILE="./portsentry.ignore" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="0" BLOCK_TCP="0" portsentry-2.0.6/system_test/032-ignore_file_no_mask/portsentry.ignore000066400000000000000000000007351510117642400262720ustar00rootroot00000000000000# Put hosts in here you never want blocked. This includes the IP addresses # of all local interfaces on the protected host (i.e virtual host, mult-home) # Keep 127.0.0.1 and 0.0.0.0 to keep people from playing games. # # PortSentry can support full netmasks for networks as well. Format is: # # / # # Example: # # 192.168.2.0/24 # 192.168.0.0/16 # 192.168.2.1/32 # Etc. # # If you don't supply a netmask it is assumed to be 32 bits. # # 127.0.0.1 0.0.0.0 portsentry-2.0.6/system_test/032-ignore_file_no_mask/portsentry.test000066400000000000000000000000371510117642400257610ustar00rootroot00000000000000--stealth -m pcap -d -i ALL -v portsentry-2.0.6/system_test/032-ignore_file_no_mask/test.sh000077500000000000000000000000761510117642400241500ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T confirmIgnoreFile ok portsentry-2.0.6/system_test/033-ignore_file_ipv6/000077500000000000000000000000001510117642400220655ustar00rootroot00000000000000portsentry-2.0.6/system_test/033-ignore_file_ipv6/portsentry.conf000066400000000000000000000004321510117642400251640ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" IGNORE_FILE="./portsentry.ignore" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="0" BLOCK_TCP="0" portsentry-2.0.6/system_test/033-ignore_file_ipv6/portsentry.ignore000066400000000000000000000007221510117642400255240ustar00rootroot00000000000000# Put hosts in here you never want blocked. This includes the IP addresses # of all local interfaces on the protected host (i.e virtual host, mult-home) # Keep 127.0.0.1 and 0.0.0.0 to keep people from playing games. # # PortSentry can support full netmasks for networks as well. Format is: # # / # # Example: # # 192.168.2.0/24 # 192.168.0.0/16 # 192.168.2.1/32 # Etc. # # If you don't supply a netmask it is assumed to be 32 bits. # # ::1/128 portsentry-2.0.6/system_test/033-ignore_file_ipv6/portsentry.test000066400000000000000000000000371510117642400252170ustar00rootroot00000000000000--stealth -m pcap -d -i ALL -v portsentry-2.0.6/system_test/033-ignore_file_ipv6/test.sh000077500000000000000000000001011510117642400233730ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T 6 confirmIgnoreFile6 ok portsentry-2.0.6/system_test/034-ignore_file_no_mask_ipv6/000077500000000000000000000000001510117642400235755ustar00rootroot00000000000000portsentry-2.0.6/system_test/034-ignore_file_no_mask_ipv6/portsentry.conf000066400000000000000000000004321510117642400266740ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" IGNORE_FILE="./portsentry.ignore" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="0" BLOCK_TCP="0" portsentry-2.0.6/system_test/034-ignore_file_no_mask_ipv6/portsentry.ignore000066400000000000000000000007161510117642400272370ustar00rootroot00000000000000# Put hosts in here you never want blocked. This includes the IP addresses # of all local interfaces on the protected host (i.e virtual host, mult-home) # Keep 127.0.0.1 and 0.0.0.0 to keep people from playing games. # # PortSentry can support full netmasks for networks as well. Format is: # # / # # Example: # # 192.168.2.0/24 # 192.168.0.0/16 # 192.168.2.1/32 # Etc. # # If you don't supply a netmask it is assumed to be 32 bits. # # ::1 portsentry-2.0.6/system_test/034-ignore_file_no_mask_ipv6/portsentry.test000066400000000000000000000000371510117642400267270ustar00rootroot00000000000000--stealth -m pcap -d -i ALL -v portsentry-2.0.6/system_test/034-ignore_file_no_mask_ipv6/test.sh000077500000000000000000000001011510117642400251030ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T 6 confirmIgnoreFile6 ok portsentry-2.0.6/system_test/100-raw_socket_tcp/000077500000000000000000000000001510117642400216415ustar00rootroot00000000000000portsentry-2.0.6/system_test/100-raw_socket_tcp/hook_pre_setup.sh000077500000000000000000000001511510117642400252230ustar00rootroot00000000000000#!/bin/sh if [ "$(uname -s)" != "Linux" ]; then echo "Skipping test on non-Linux system" exit 0 fi portsentry-2.0.6/system_test/100-raw_socket_tcp/portsentry.conf000066400000000000000000000004261510117642400247430ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="1" BLOCK_TCP="1" KILL_HOSTS_DENY="ALL: $TARGET$" portsentry-2.0.6/system_test/100-raw_socket_tcp/portsentry.test000066400000000000000000000000271510117642400247720ustar00rootroot00000000000000--stealth -m raw -d -L portsentry-2.0.6/system_test/100-raw_socket_tcp/test.sh000077500000000000000000000002351510117642400231570ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T confirmBlockTriggered tcp confirmBlockFileSize 1 0 runNmap 11 T confirmAlreadyBlocked confirmBlockFileSize 1 0 ok portsentry-2.0.6/system_test/101-sentry_stealth_range/000077500000000000000000000000001510117642400230575ustar00rootroot00000000000000portsentry-2.0.6/system_test/101-sentry_stealth_range/hook_pre_setup.sh000077500000000000000000000001511510117642400264410ustar00rootroot00000000000000#!/bin/sh if [ "$(uname -s)" != "Linux" ]; then echo "Skipping test on non-Linux system" exit 0 fi portsentry-2.0.6/system_test/101-sentry_stealth_range/portsentry.conf000066400000000000000000000003701510117642400261570ustar00rootroot00000000000000TCP_PORTS="1,5-11" UDP_PORTS="1,7-11" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="0" BLOCK_TCP="0" portsentry-2.0.6/system_test/101-sentry_stealth_range/portsentry.test000066400000000000000000000000241510117642400262050ustar00rootroot00000000000000--stealth -m raw -d portsentry-2.0.6/system_test/101-sentry_stealth_range/test.sh000077500000000000000000000006321510117642400243760ustar00rootroot00000000000000#!/bin/sh . ./testlib.sh runNmap 11 T confirmStdoutScanMessage tcp confirmHistoryFileMessage tcp runNmap 11 T confirmOccurrenceStdout 2 "^Scan from: \[127\.0\.0\.1\] (127\.0\.0\.1) protocol: \[TCP\] port: \[11\]" runNmap 11 U confirmStdoutScanMessage udp confirmHistoryFileMessage udp runNmap 11 U confirmOccurrenceStdout 2 "^Scan from: \[127\.0\.0\.1\] (127\.0\.0\.1) protocol: \[UDP\] port: \[11\]" ok portsentry-2.0.6/system_test/run_all_tests.sh000077500000000000000000000003621510117642400215520ustar00rootroot00000000000000#!/bin/sh for f in $(find . -maxdepth 1 -mindepth 1 -type d |sort); do echo "Running test $f" if ! ./run_test.sh ../debug/portsentry $f; then echo "Stopping further tests due to failure" exit 1 fi done echo "All tests passed" portsentry-2.0.6/system_test/run_test.sh000077500000000000000000000103521510117642400205370ustar00rootroot00000000000000#!/bin/sh TEST_DIR=/tmp/portsentry-test SRC_DIR="" PORTSENTRY_EXEC="" PORTSENTRY_CONF="" PORTSENTRY_TEST="" PORTSENTRY_SCRIPT="" PORTSENTRY_HOOK_PRE_SETUP="" log() { echo "$@" } debug() { if [ -z "$DEBUG" ]; then return fi log "DEBUG: $@" } init() { if [ "$(whoami)" != "root" ]; then echo "Need root to run" exit 1 fi if [ $# -lt 2 ]; then echo "Usage: $0 " exit 1 fi PORTSENTRY_EXEC=$1 SRC_DIR=$2 PORTSENTRY_CONF=$SRC_DIR/portsentry.conf PORTSENTRY_TEST=$SRC_DIR/portsentry.test PORTSENTRY_SCRIPT=$SRC_DIR/test.sh PORTSENTRY_HOOK_PRE_SETUP=$SRC_DIR/hook_pre_setup.sh if [ ! -x $PORTSENTRY_EXEC ]; then echo "Error: portsentry executable file: $PORTSENTRY_EXEC not found or not executable" exit 1 fi if [ ! -f $PORTSENTRY_CONF ]; then echo "Error: portsentry config: $PORTSENTRY_CONF not found" exit 1 fi if [ ! -f $PORTSENTRY_TEST ]; then echo "Error: portsentry test file: $PORTSENTRY_TEST not found" exit 1 fi if [ ! -x $PORTSENTRY_SCRIPT ]; then echo "Error: portsentry script file: $PORTSENTRY_SCRIPT not found or not executable" exit 1 fi } hook_pre_setup() { if [ -x $PORTSENTRY_HOOK_PRE_SETUP ]; then . $PORTSENTRY_HOOK_PRE_SETUP fi } setup() { rm -rf $TEST_DIR mkdir -p $TEST_DIR cp $PORTSENTRY_EXEC $TEST_DIR cp $(dirname $PORTSENTRY_EXEC)/portcon $TEST_DIR cp $PORTSENTRY_CONF $TEST_DIR cp $PORTSENTRY_TEST $TEST_DIR cp $PORTSENTRY_SCRIPT $TEST_DIR cp ./testlib.sh $TEST_DIR/testlib.sh [ -f $SRC_DIR/portsentry.ignore ] && cp $SRC_DIR/portsentry.ignore $TEST_DIR/ PORTSENTRY_EXEC=$TEST_DIR/$(basename $PORTSENTRY_EXEC) PORTSENTRY_CONF=$TEST_DIR/$(basename $PORTSENTRY_CONF) PORTSENTRY_TEST=$TEST_DIR/$(basename $PORTSENTRY_TEST) PORTSENTRY_SCRIPT=$TEST_DIR/$(basename $PORTSENTRY_SCRIPT) PORTSENTRY_STDOUT=$TEST_DIR/portsentry.stdout PORTSENTRY_STDERR=$TEST_DIR/portsentry.stderr debug "PORTSENTRY_EXEC: $PORTSENTRY_EXEC" debug "PORTSENTRY_CONF: $PORTSENTRY_CONF" debug "PORTSENTRY_TEST: $PORTSENTRY_TEST" debug "PORTSENTRY_SCRIPT: $PORTSENTRY_SCRIPT" } run_portsentry() { local switches="$(head -n 1 $PORTSENTRY_TEST)" debug "switches: $switches" if echo $switches | grep -q "pcap" && ! ldd $PORTSENTRY_EXEC | grep -q "libpcap\.so"; then log "pcap test detected on portsentry binary without pcap support, skipping" exit 0 fi cd $TEST_DIR $PORTSENTRY_EXEC -c $PORTSENTRY_CONF $switches > $PORTSENTRY_STDOUT 2>$PORTSENTRY_STDERR & local timeout=5 while [ ! -f $PORTSENTRY_STDOUT ]; do debug "waiting for $PORTSENTRY_STDOUT to be created" sleep 1 timeout=$((timeout - 1)) if [ $timeout -eq 0 ]; then echo "Error: Timeout waiting for $PORTSENTRY_STDOUT to be created" exit 1 fi done timeout=5 while [ $timeout -gt 0 ]; do debug "waiting for portsentry to report ready in $PORTSENTRY_STDOUT" if grep -q "Portsentry is now active and listening." $PORTSENTRY_STDOUT; then return fi sleep 1 timeout=$((timeout - 1)) done echo "Error: Unable to parse portsentry ready message, aborting" report_and_stop exit 1 } stop_portsentry() { local pid="$(ps auxww|grep "$PORTSENTRY_EXEC -c $PORTSENTRY_CONF $switches"|grep -v grep | awk '{print $2}')" debug "Stopping portsentry with pid: $pid" if [ -n "$pid" ]; then kill $pid return fi timeout=5 while [ $timeout -gt 0 ]; do debug "waiting for portsentry to stop $PORTSENTRY_STDOUT" if grep -q "Portsentry is shutting down" $PORTSENTRY_STDOUT; then return fi sleep 1 timeout=$((timeout - 1)) done echo "Error: Unable to parse portsentry stop message, aborting" exit 1 } report_and_stop() { echo "Detected test failure, stopping portsentry, printing portsentry run log and exit" stop_portsentry echo "#### PORTSENTRY STDOUT ####" cat $PORTSENTRY_STDOUT echo echo "#### PORTSENTRY STDERR ####" cat $PORTSENTRY_STDERR exit 1 } run_test() { cd $TEST_DIR if ! $PORTSENTRY_SCRIPT $TEST_DIR $PORTSENTRY_EXEC $PORTSENTRY_CONF $PORTSENTRY_TEST $PORTSENTRY_SCRIPT $PORTSENTRY_STDOUT $PORTSENTRY_STDERR; then report_and_stop fi } init $@ hook_pre_setup setup run_portsentry run_test stop_portsentry portsentry-2.0.6/system_test/run_test_std.sh000077500000000000000000000000571510117642400214120ustar00rootroot00000000000000#!/bin/sh ./run_test.sh ../debug/portsentry $1 portsentry-2.0.6/system_test/template.conf000066400000000000000000000004321510117642400210150ustar00rootroot00000000000000TCP_PORTS="1,11" UDP_PORTS="1,7,9,11" IGNORE_FILE="./portsentry.ignore" HISTORY_FILE="./portsentry.history" BLOCKED_FILE="./portsentry.blocked" # 0 = Do not block UDP/TCP scans. # 1 = Block UDP/TCP scans. # 2 = Run external command only (KILL_RUN_CMD) BLOCK_UDP="0" BLOCK_TCP="0" portsentry-2.0.6/system_test/testlib.sh000066400000000000000000000164561510117642400203520ustar00rootroot00000000000000SCRIPT_NAME=$(basename $0) TEST_DIR=$1 PORTSENTRY_EXEC=$2 PORTSENTRY_CONF=$3 PORTSENTRY_TEST=$4 PORTSENTRY_SCRIPT=$5 PORTSENTRY_STDOUT=$6 PORTSENTRY_STDERR=$7 set -e log() { echo "$SCRIPT_NAME: $@" } debug() { if [ -z "$DEBUG" ]; then return fi log "DEBUG: $@" } verbose() { if [ -z "$VERBOSE" ]; then return fi log "$@" } print_env() { echo "$0 $@" echo "pwd: $(pwd)" log $TEST_DIR log $PORTSENTRY_EXEC log $PORTSENTRY_CONF log $PORTSENTRY_TEST log $PORTSENTRY_SCRIPT log $PORTSENTRY_STDOUT log $PORTSENTRY_STDERR } ok() { log "Test passed OK" exit 0 } err() { log "Test failed: $@" exit 1 } findInFile() { local str="$1" local file=$2 if [ -z "$3" ]; then local timeout=5 else local timeout=$3 fi while [ ! -f $file ]; do debug "waiting for $file to be created" sleep 1 timeout=$((timeout - 1)) if [ $timeout -eq 0 ]; then echo "Error: Timeout waiting for $file to be created" exit 1 fi done if [ -z "$3" ]; then local timeout=5 else local timeout=$3 fi while [ $timeout -gt 0 ]; do debug "waiting for string $str in file $file" if grep -q "$str" $file; then debug "Found string $str in file $file" findInFileCount=$(grep -c "$str" $file) return 0 fi sleep 1 timeout=$((timeout - 1)) done return 1 } setProtoVars() { proto=$1 if [ "$1" = "tcp" ] || [ "$1" = "udp" ] ; then proto_l=$1 proto_u=$(echo $proto_l | tr '[:lower:]' '[:upper:]') else err "confirmBlockTriggered: invalid protocol $1" fi } confirmOccurrenceStdout() { local count=$1 local str="$2" local timeout=${3:-5} verbose "expect $count occurances of $str in stdout" while [ $timeout -gt 0 ]; do findInFile "$str" $PORTSENTRY_STDOUT if [ "$findInFileCount" -eq "$count" ]; then return 0 fi debug "retrying for $count occurances of $str in stdout" sleep 1 timeout=$((timeout - 1)) done err "Expected $count occurances of $str in stdout, found $findInFileCount" } confirmStdoutScanMessage() { setProtoVars $1 local host="127.0.0.1" if [ "$2" = "6" ]; then host="::1" fi verbose "expect log scan from message" if ! findInFile "^Scan from: \[$host\]" $PORTSENTRY_STDOUT; then err "Expected attackalert message not found" fi } confirmBlockFileSize() { local noIpv4=$1 local noIpv6=$2 [ -z "$noIpv4" ] && noIpv4=0 [ -z "$noIpv6" ] && noIpv6=0 # Linux uses 2 bytes for address family identifier, BSD uses 1 if [ "$(uname -s)" = "Linux" ]; then local family_size=2 else local family_size=1 fi verbose "expect block file contains $noIpv4 IPv4 and $noIpv6 IPv6 entries" # ipv4 = 4 bytes for address + X bytes address family identifier # ipv6 = 16 bytes for address + X bytes address family identifier local sum=$((($noIpv4 * (4 + $family_size)) + ($noIpv6 * (16 + $family_size)))) if [ $(/bin/ls -l $TEST_DIR/portsentry.blocked |tr -s ' '|cut -d ' ' -f 5) -ne $sum ]; then err "Expected block file size $sum, found $(/bin/ls -l $TEST_DIR/portsentry.blocked |tr -s ' '|cut -d ' ' -f 5)" fi } confirmHistoryFileMessage() { setProtoVars $1 local host="127.0.0.1" if [ "$2" = "6" ]; then host="::1" fi verbose "expect history file entry" if ! findInFile ".*Scan from: \[$host] ($host) protocol: \[$proto_u\] port: \[11\]" $TEST_DIR/portsentry.history; then err "Expected history entry not found" fi } confirmExternalCommandRunMessage() { verbose "expect external command run message" if ! findInFile "^attackalert: External command run for host: 127.0.0.1 using command" $PORTSENTRY_STDOUT; then err "Expected external command run message not found" fi } confirmRouteKillMessage() { verbose "expect route kill message" if ! findInFile "^attackalert: Host 127.0.0.1 has been blocked via dropped route using command" $PORTSENTRY_STDOUT; then err "Expected external command run message not found" fi } confirmHostWrapperMessage() { verbose "expect attackalert hosts deny block message" if ! findInFile "^attackalert: Host 127.0.0.1 has been blocked via wrappers with string" $PORTSENTRY_STDOUT; then err "Expected attackalert message not found" fi } confirmBlockTriggered() { setProtoVars $1 confirmStdoutScanMessage $1 $2 confirmHistoryFileMessage $1 $2 } confirmAlreadyBlocked() { local host="127.0.0.1" if [ "$1" = "6" ]; then host="::1" fi verbose "expect already blocked message" if ! findInFile "attackalert: Host: $host/$host is already blocked Ignoring" $PORTSENTRY_STDOUT; then err "Expected already blocked message not found" fi } confirmSynScan() { verbose "expect syn scan block message" if ! findInFile "Scan from: \[127\.0\.0\.1\] (127\.0\.0\.1) protocol: \[TCP\] port: \[11\] type: \[TCP SYN/Normal scan\]" $PORTSENTRY_STDOUT; then err "Expected syn scan block message not found" fi } confirmNullScan() { verbose "expect null scan block message" if ! findInFile "Scan from: \[127\.0\.0\.1\] (127\.0\.0\.1) protocol: \[TCP\] port: \[11\] type: \[TCP NULL scan\]" $PORTSENTRY_STDOUT; then err "Expected null scan block message not found" fi } confirmXmasScan() { verbose "expect xmas scan block message" if ! findInFile "Scan from: \[127\.0\.0\.1] (127\.0\.0\.1) protocol: \[TCP\] port: \[11\] type: \[TCP XMAS scan\]" $PORTSENTRY_STDOUT; then err "Expected xmas scan block message not found" fi } confirmFinScan() { verbose "expect fin scan block message" if ! findInFile "Scan from: \[127\.0\.0\.1] (127\.0\.0\.1) protocol: \[TCP\] port: \[11\] type: \[TCP FIN scan\]" $PORTSENTRY_STDOUT; then err "Expected fin scan block message not found" fi } confirmIgnoreFile() { verbose "expect ignore file entry" if ! findInFile "Host: 127\.0\.0\.1 found in ignore file [^ ]* aborting actions" $PORTSENTRY_STDOUT; then err "Expected ignore file entry not found" fi } confirmIgnoreFile6() { verbose "expect ignore file entry" if ! findInFile "Host: ::1 found in ignore file [^ ]* aborting actions" $PORTSENTRY_STDOUT; then err "Expected ignore file entry not found" fi } waitForFile() { if [ -z "$1" ]; then err "waitForFile: no file specified" fi local file=$1 if [ -z "$2" ]; then local timeout=5 else local timeout=$2 fi while [ $timeout -gt 0 ]; do debug "waiting for file $file" if [ -f $file ]; then debug "Found file $file" return 0 fi sleep 1 timeout=$((timeout - 1)) done err "Unable to find file $file, giving up" } runNmap() { local opts="" if [ -z "$1" ]; then err "runNmap: no port specified" fi local NMAP=$(which nmap) if [ -z "$NMAP" ]; then err "runNmap: nmap not found" exit 1 fi local port=$1 if [ "$2" = "T" ]; then local proto="T" elif [ "$2" = "U" ]; then local proto="U" elif [ "$2" = "S" ]; then local proto="S" elif [ "$2" = "N" ]; then local proto="N" elif [ "$2" = "F" ]; then local proto="F" elif [ "$2" = "X" ]; then local proto="X" else err "runNmap: invalid protocol $2" fi if [ "$3" = "6" ]; then local host="::1" opts="$opts -6" else local host="localhost" fi verbose "expect connect to $proto localhost:$port" debug "runNmap: $NMAP $opts --max-retries 0 -s$proto -p$port-$port $host" $NMAP $opts --max-retries 0 -s$proto -p$port-$port $host >/dev/null } portsentry-2.0.6/tests/000077500000000000000000000000001510117642400151135ustar00rootroot00000000000000portsentry-2.0.6/tests/fuzzing/000077500000000000000000000000001510117642400166075ustar00rootroot00000000000000portsentry-2.0.6/tests/fuzzing/corpus_fuzz_sentry_pcap/000077500000000000000000000000001510117642400236075ustar00rootroot00000000000000portsentry-2.0.6/tests/fuzzing/corpus_fuzz_sentry_pcap/packet_ipv4_tcp_dport_11000066400000000000000000000000501510117642400303150ustar00rootroot00000000000000E, ()I O`portsentry-2.0.6/tests/fuzzing/corpus_fuzz_sentry_pcap/packet_ipv4_udp_dport_9000066400000000000000000000000341510117642400302500ustar00rootroot00000000000000E+3^f =lportsentry-2.0.6/tests/fuzzing/corpus_fuzz_sentry_pcap/packet_ipv6_tcp_dport_11000066400000000000000000000000741510117642400303250ustar00rootroot00000000000000` &3.`portsentry-2.0.6/tests/fuzzing/corpus_fuzz_sentry_pcap/packet_ipv6_udp_dport_9000066400000000000000000000000601510117642400302510ustar00rootroot00000000000000`  portsentry-2.0.6/tests/fuzzing/corpus_fuzz_sentry_stealth/000077500000000000000000000000001510117642400243305ustar00rootroot00000000000000portsentry-2.0.6/tests/fuzzing/corpus_fuzz_sentry_stealth/packet_ipv4_tcp_dport_11000066400000000000000000000000501510117642400310360ustar00rootroot00000000000000E,1u,_U $``portsentry-2.0.6/tests/fuzzing/corpus_fuzz_sentry_stealth/packet_ipv4_udp_dport_9000066400000000000000000000000341510117642400307710ustar00rootroot00000000000000E>;C 3portsentry-2.0.6/tests/fuzzing/corpus_fuzz_sentry_stealth/packet_ipv6_tcp_dport_11000066400000000000000000000000741510117642400310460ustar00rootroot00000000000000` d`portsentry-2.0.6/tests/fuzzing/corpus_fuzz_sentry_stealth/packet_ipv6_udp_dport_9000066400000000000000000000000601510117642400307720ustar00rootroot00000000000000` \portsentry-2.0.6/tests/test_io_subststring.c000066400000000000000000000145061510117642400214020ustar00rootroot00000000000000#include #include #include #include #include #include "../src/io.h" #include "../src/portsentry.h" void TestSubstStringNormalCase(void); void TestSubstStringNoMatch(void); void TestSubstStringEmptyReplace(void); void TestSubstStringNullInputs(void); void TestSubstStringInvalidSize(void); void TestSubstStringEmptyFindToken(void); void TestSubstStringBufferOverflow(void); void TestSubstStringExactFit(void); void TestSubstStringMultipleReplacements(void); void TestSubstStringOverlappingTokens(void); void TestSubstStringSpecialCharacters(void); void TestSubstStringUnicodeSafe(void); void TestSubstStringBoundaryConditions(void); void TestSubstStringNormalCase(void) { char dest[100]; const char *source = "Hello world, hello universe"; const char *findToken = "hello"; const char *replaceToken = "hi"; int result = SubstString(replaceToken, findToken, source, dest, sizeof(dest)); assert(result == 1); assert(strcmp(dest, "Hello world, hi universe") == 0); printf("Normal case test passed\n"); } void TestSubstStringNoMatch(void) { char dest[100]; const char *source = "Hello world"; const char *findToken = "xyz"; const char *replaceToken = "abc"; int result = SubstString(replaceToken, findToken, source, dest, sizeof(dest)); assert(result == 0); assert(strcmp(dest, "Hello world") == 0); printf("No match test passed\n"); } void TestSubstStringEmptyReplace(void) { char dest[100]; const char *source = "Hello world hello"; const char *findToken = "hello"; const char *replaceToken = ""; int result = SubstString(replaceToken, findToken, source, dest, sizeof(dest)); assert(result == 1); assert(strcmp(dest, "Hello world ") == 0); printf("Empty replace token test passed\n"); } void TestSubstStringNullInputs(void) { char dest[100]; const char *source = "test"; const char *findToken = "test"; const char *replaceToken = "new"; assert(SubstString(NULL, findToken, source, dest, sizeof(dest)) == ERROR); assert(SubstString(replaceToken, NULL, source, dest, sizeof(dest)) == ERROR); assert(SubstString(replaceToken, findToken, NULL, dest, sizeof(dest)) == ERROR); assert(SubstString(replaceToken, findToken, source, NULL, sizeof(dest)) == ERROR); printf("Null inputs test passed\n"); } void TestSubstStringInvalidSize(void) { char dest[100]; const char *source = "test"; const char *findToken = "test"; const char *replaceToken = "new"; assert(SubstString(replaceToken, findToken, source, dest, 0) == ERROR); assert(SubstString(replaceToken, findToken, source, dest, -1) == ERROR); printf("Invalid size test passed\n"); } void TestSubstStringEmptyFindToken(void) { char dest[100]; const char *source = "test"; const char *findToken = ""; const char *replaceToken = "new"; assert(SubstString(replaceToken, findToken, source, dest, sizeof(dest)) == ERROR); printf("Empty find token test passed\n"); } void TestSubstStringBufferOverflow(void) { char dest[10]; const char *source = "Hello world hello universe"; const char *findToken = "hello"; const char *replaceToken = "very long replacement string"; int result = SubstString(replaceToken, findToken, source, dest, sizeof(dest)); assert(result == ERROR); printf("Buffer overflow test passed\n"); } void TestSubstStringExactFit(void) { char dest[15]; const char *source = "Hello world"; const char *findToken = "world"; const char *replaceToken = "universe"; int result = SubstString(replaceToken, findToken, source, dest, sizeof(dest)); assert(result == 1); assert(strcmp(dest, "Hello universe") == 0); printf("Exact fit test passed\n"); } void TestSubstStringMultipleReplacements(void) { char dest[200]; const char *source = "aaa aaa aaa aaa"; const char *findToken = "aaa"; const char *replaceToken = "bbb"; int result = SubstString(replaceToken, findToken, source, dest, sizeof(dest)); assert(result == 4); assert(strcmp(dest, "bbb bbb bbb bbb") == 0); printf("Multiple replacements test passed\n"); } void TestSubstStringOverlappingTokens(void) { char dest[100]; const char *source = "aaaaa"; const char *findToken = "aaa"; const char *replaceToken = "b"; int result = SubstString(replaceToken, findToken, source, dest, sizeof(dest)); assert(result == 1); assert(strcmp(dest, "baa") == 0); printf("Overlapping tokens test passed\n"); } void TestSubstStringSpecialCharacters(void) { char dest[100]; const char *source = "Hello\nworld\thello"; const char *findToken = "\n"; const char *replaceToken = " "; int result = SubstString(replaceToken, findToken, source, dest, sizeof(dest)); assert(result == 1); assert(strcmp(dest, "Hello world\thello") == 0); printf("Special characters test passed\n"); } void TestSubstStringUnicodeSafe(void) { char dest[100]; const char *source = "Hello\x80\x81\x82 world"; const char *findToken = "\x80\x81"; const char *replaceToken = "test"; int result = SubstString(replaceToken, findToken, source, dest, sizeof(dest)); assert(result == 1); assert(strcmp(dest, "Hellotest\x82 world") == 0); printf("Unicode safe test passed\n"); } void TestSubstStringBoundaryConditions(void) { char dest[3]; const char *source = "test"; const char *findToken = "test"; const char *replaceToken = "new"; // Should fail due to insufficient buffer space int result = SubstString(replaceToken, findToken, source, dest, sizeof(dest)); assert(result == ERROR); // Should succeed with exact space char dest2[4]; result = SubstString(replaceToken, findToken, source, dest2, sizeof(dest2)); assert(result == 1); // "new" is 3 chars + null terminator = 4, which fits in dest2[4] char dest3[5]; result = SubstString(replaceToken, findToken, source, dest3, sizeof(dest3)); assert(result == 1); assert(strcmp(dest3, "new") == 0); printf("Boundary conditions test passed\n"); } int main(void) { TestSubstStringNormalCase(); TestSubstStringNoMatch(); TestSubstStringEmptyReplace(); TestSubstStringNullInputs(); TestSubstStringInvalidSize(); TestSubstStringEmptyFindToken(); TestSubstStringBufferOverflow(); TestSubstStringExactFit(); TestSubstStringMultipleReplacements(); TestSubstStringOverlappingTokens(); TestSubstStringSpecialCharacters(); TestSubstStringUnicodeSafe(); TestSubstStringBoundaryConditions(); printf("All SubstString tests passed!\n"); return 0; } portsentry-2.0.6/tests/test_port.c000066400000000000000000000105561510117642400173110ustar00rootroot00000000000000// tests/test_port.c #include #include #include #include #include "../src/port.h" #include "../src/util.h" #include "../src/portsentry.h" void TestResetPort(void); void TestSetPortSingle(void); void TestSetPortRange(void); void TestIsPortPresent(void); void TestIsPortInRange(void); void TestIsPortSingle(void); void TestParsePort(void); void TestGetNoPorts(void); void TestResetPort(void) { struct Port port; port.single = 1234; port.range.start = 1000; port.range.end = 2000; ResetPort(&port); assert(port.single == 0); assert(port.range.start == 0); assert(port.range.end == 0); } void TestSetPortSingle(void) { struct Port port; SetPortSingle(&port, 8080); assert(port.single == 8080); assert(port.range.start == 0); assert(port.range.end == 0); SetPortSingle(&port, 443); assert(port.single == 443); assert(port.range.start == 0); assert(port.range.end == 0); } void TestSetPortRange(void) { struct Port port; SetPortRange(&port, 1000, 2000); assert(port.single == 0); assert(port.range.start == 1000); assert(port.range.end == 2000); SetPortRange(&port, 3000, 4000); assert(port.single == 0); assert(port.range.start == 3000); assert(port.range.end == 4000); } void TestIsPortPresent(void) { struct Port ports[3]; SetPortSingle(&ports[0], 80); SetPortRange(&ports[1], 1000, 2000); SetPortSingle(&ports[2], 443); assert(IsPortPresent(ports, 3, 80) == TRUE); assert(IsPortPresent(ports, 3, 443) == TRUE); assert(IsPortPresent(ports, 3, 1500) == TRUE); assert(IsPortPresent(ports, 3, 1000) == TRUE); assert(IsPortPresent(ports, 3, 2000) == TRUE); assert(IsPortPresent(ports, 3, 81) == FALSE); assert(IsPortPresent(ports, 3, 999) == FALSE); assert(IsPortPresent(ports, 3, 2001) == FALSE); assert(IsPortPresent(ports, 3, 444) == FALSE); } void TestIsPortInRange(void) { struct Port port; SetPortSingle(&port, 80); assert(IsPortInRange(&port, 80) == TRUE); assert(IsPortInRange(&port, 81) == FALSE); SetPortRange(&port, 1000, 2000); assert(IsPortInRange(&port, 1000) == TRUE); assert(IsPortInRange(&port, 1500) == TRUE); assert(IsPortInRange(&port, 2000) == TRUE); assert(IsPortInRange(&port, 999) == FALSE); assert(IsPortInRange(&port, 2001) == FALSE); } void TestIsPortSingle(void) { struct Port port; SetPortSingle(&port, 80); assert(IsPortSingle(&port) == TRUE); SetPortRange(&port, 1000, 2000); assert(IsPortSingle(&port) == FALSE); ResetPort(&port); assert(IsPortSingle(&port) == FALSE); } void TestParsePort(void) { struct Port port; assert(ParsePort("80", &port) == TRUE); assert(port.single == 80); assert(port.range.start == 0); assert(port.range.end == 0); assert(ParsePort("1000-2000", &port) == TRUE); assert(port.single == 0); assert(port.range.start == 1000); assert(port.range.end == 2000); assert(ParsePort("2000-2000", &port) == TRUE); // Range start == Range end assert(port.single == 2000); assert(port.range.start == 0); assert(port.range.end == 0); assert(ParsePort("0", &port) == ERROR); // Port 0 assert(ParsePort("0-1000", &port) == ERROR); // Range start 0 assert(ParsePort("1000-0", &port) == ERROR); // Range end 0 assert(ParsePort("abc", &port) == ERROR); // Non-numeric assert(ParsePort("1000-abc", &port) == ERROR); // Invalid range end assert(ParsePort("abc-2000", &port) == ERROR); // Invalid range start assert(ParsePort("2000-1000", &port) == ERROR); // Range start > Range end assert(ParsePort("", &port) == ERROR); // Empty string assert(ParsePort("123456789012", &port) == ERROR); // Too long string } void TestGetNoPorts(void) { struct Port ports[3]; SetPortSingle(&ports[0], 80); SetPortRange(&ports[1], 1000, 2000); SetPortSingle(&ports[2], 443); size_t count = GetNoPorts(ports, 3); assert(count == 1003); // 1 + (2000-1000+1) + 1 assert(GetNoPorts(NULL, 0) == 0); struct Port single_port; SetPortSingle(&single_port, 80); assert(GetNoPorts(&single_port, 1) == 1); struct Port range_port; SetPortRange(&range_port, 1000, 2000); assert(GetNoPorts(&range_port, 1) == 1001); // 2000-1000+1 } int main(void) { TestResetPort(); TestSetPortSingle(); TestSetPortRange(); TestIsPortPresent(); TestIsPortInRange(); TestIsPortSingle(); TestParsePort(); TestGetNoPorts(); return 0; } portsentry-2.0.6/tests/test_state_machine.c000066400000000000000000000075211510117642400211270ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "../src/portsentry.h" #include "../src/config_data.h" #include "../src/state_machine.h" #include "../src/config_data.h" struct sockaddr_in CreateIpv4Addr(const char *ip_str); struct sockaddr_in6 CreateIpv6Addr(const char *ip_str); void TestUninitializedState(void); void TestTriggerCountZero(void); void TestIpv4TriggerLogic(void); void TestIpv4Eviction(void); void TestIpv6TriggerLogic(void); void TestIpv6Eviction(void); void TestUnsupportedFamily(void); struct sockaddr_in CreateIpv4Addr(const char *ip_str) { struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; if (inet_pton(AF_INET, ip_str, &addr.sin_addr) != 1) { perror("inet_pton IPv4 failed"); exit(EXIT_FAILURE); } return addr; } struct sockaddr_in6 CreateIpv6Addr(const char *ip_str) { struct sockaddr_in6 addr; memset(&addr, 0, sizeof(addr)); addr.sin6_family = AF_INET6; if (inet_pton(AF_INET6, ip_str, &addr.sin6_addr) != 1) { perror("inet_pton IPv6 failed"); exit(EXIT_FAILURE); } return addr; } void TestUninitializedState(void) { struct SentryState state; state.isInitialized = FALSE; struct sockaddr_in addr_ipv4 = CreateIpv4Addr("192.168.1.1"); configData.configTriggerCount = 1; int result = CheckState(&state, (struct sockaddr *)&addr_ipv4); assert(result == ERROR); } void TestTriggerCountZero(void) { struct SentryState state; InitSentryState(&state); struct sockaddr_in addr_ipv4 = CreateIpv4Addr("192.168.1.1"); configData.configTriggerCount = 0; int result = CheckState(&state, (struct sockaddr *)&addr_ipv4); assert(result == TRUE); FreeSentryState(&state); } void TestIpv4TriggerLogic(void) { struct SentryState state; InitSentryState(&state); struct sockaddr_in addr1 = CreateIpv4Addr("192.168.0.1"); struct sockaddr_in addr2 = CreateIpv4Addr("192.168.0.2"); configData.configTriggerCount = 3; int result; result = CheckState(&state, (struct sockaddr *)&addr1); assert(result == FALSE); result = CheckState(&state, (struct sockaddr *)&addr1); assert(result == FALSE); result = CheckState(&state, (struct sockaddr *)&addr2); assert(result == FALSE); result = CheckState(&state, (struct sockaddr *)&addr1); assert(result == TRUE); result = CheckState(&state, (struct sockaddr *)&addr1); assert(result == TRUE); result = CheckState(&state, (struct sockaddr *)&addr2); assert(result == FALSE); result = CheckState(&state, (struct sockaddr *)&addr2); assert(result == TRUE); FreeSentryState(&state); } void TestIpv6TriggerLogic(void) { struct SentryState state; InitSentryState(&state); struct sockaddr_in6 addr1 = CreateIpv6Addr("2001:db8::1"); struct sockaddr_in6 addr2 = CreateIpv6Addr("2001:db8::2"); configData.configTriggerCount = 2; int result; result = CheckState(&state, (struct sockaddr *)&addr1); assert(result == FALSE); result = CheckState(&state, (struct sockaddr *)&addr2); assert(result == FALSE); result = CheckState(&state, (struct sockaddr *)&addr1); assert(result == TRUE); result = CheckState(&state, (struct sockaddr *)&addr1); assert(result == TRUE); result = CheckState(&state, (struct sockaddr *)&addr2); assert(result == TRUE); FreeSentryState(&state); } void TestUnsupportedFamily(void) { struct SentryState state; InitSentryState(&state); struct sockaddr unsupported_addr; unsupported_addr.sa_family = AF_UNIX; configData.configTriggerCount = 1; int result = CheckState(&state, &unsupported_addr); assert(result == ERROR); FreeSentryState(&state); } int main(void) { TestUninitializedState(); TestTriggerCountZero(); TestIpv4TriggerLogic(); TestIpv6TriggerLogic(); TestUnsupportedFamily(); return 0; } portsentry-2.0.6/tests/test_util_createdatetime.c000066400000000000000000000007211510117642400223330ustar00rootroot00000000000000#include #include #include "../src/util.h" #include "../src/portsentry.h" void TestCreateDateTime(void); void TestCreateDateTime(void) { char buffer[MIN_DATETIME_BUFFER]; assert(CreateDateTime(buffer, sizeof(buffer)) == TRUE); assert(CreateDateTime(NULL, MIN_DATETIME_BUFFER) == ERROR); char small_buf[10]; assert(CreateDateTime(small_buf, sizeof(small_buf)) == ERROR); } int main(void) { TestCreateDateTime(); return 0; } portsentry-2.0.6/tests/test_util_getlong.c000066400000000000000000000024331510117642400210140ustar00rootroot00000000000000#include #include #include #include "../src/util.h" #include "../src/portsentry.h" void TestGetLongValidNumbers(void); void TestGetLongNullInput(void); void TestGetLongInvalidInput(void); void TestGetLongBoundaryCases(void); void TestGetLongValidNumbers(void) { assert(GetLong("123") == 123); assert(GetLong("0") == 0); assert(GetLong("-456") == -456); printf("Valid numbers test passed\n"); } void TestGetLongNullInput(void) { assert(GetLong(NULL) == ERROR); printf("Null input test passed\n"); } void TestGetLongInvalidInput(void) { assert(GetLong("abc") == ERROR); assert(GetLong("") == ERROR); assert(GetLong(" ") == ERROR); assert(GetLong("123abc") == ERROR); // trailing characters printf("Invalid input test passed\n"); } void TestGetLongBoundaryCases(void) { char max_buffer[32]; char min_buffer[32]; snprintf(max_buffer, sizeof(max_buffer), "%ld", LONG_MAX); snprintf(min_buffer, sizeof(min_buffer), "%ld", LONG_MIN); assert(GetLong(max_buffer) == ERROR); assert(GetLong(min_buffer) == ERROR); printf("Boundary cases test passed\n"); } int main(void) { TestGetLongValidNumbers(); TestGetLongNullInput(); TestGetLongInvalidInput(); TestGetLongBoundaryCases(); printf("All GetLong tests passed!\n"); return 0; } portsentry-2.0.6/tests/test_util_reallocandappend.c000066400000000000000000000012371510117642400226520ustar00rootroot00000000000000#include #include #include #include #include "../src/util.h" void TestReallocAndAppend(void); void TestReallocAndAppend(void) { size_t len = 0; char *buf = NULL; buf = ReallocAndAppend(buf, &len, "Hello %s", "World"); assert(buf != NULL); assert(strcmp(buf, "Hello World") == 0); char *new_buf = ReallocAndAppend(buf, &len, ", %s!", "User"); assert(new_buf != NULL); assert(strcmp(new_buf, "Hello World, User!") == 0); assert(ReallocAndAppend(NULL, NULL, "test") == NULL); assert(ReallocAndAppend(buf, &len, NULL) == NULL); free(new_buf); } int main(void) { TestReallocAndAppend(); return 0; } portsentry-2.0.6/tests/test_util_safestrncpy.c000066400000000000000000000041151510117642400217150ustar00rootroot00000000000000#include #include #include #include #include "../src/util.h" void TestSafeStrncpyNormalCase(void); void TestSafeStrncpyNullDest(void); void TestSafeStrncpyZeroSize(void); void TestSafeStrncpyExactSize(void); void TestSafeStrncpyTruncated(void); void TestSafeStrncpySecurity(void); void TestSafeStrncpyNormalCase(void) { char dest[10]; const char *src = "test"; char *result = SafeStrncpy(dest, src, sizeof(dest)); assert(result == dest); assert(strcmp(dest, "test") == 0); printf("Normal case test passed\n"); } void TestSafeStrncpyNullDest(void) { char *result = SafeStrncpy(NULL, "test", 10); assert(result == NULL); printf("Null destination test passed\n"); } void TestSafeStrncpyZeroSize(void) { char dest[10]; char *result = SafeStrncpy(dest, "test", 0); assert(result == NULL); printf("Zero size test passed\n"); } void TestSafeStrncpyExactSize(void) { char dest[5]; char *result = SafeStrncpy(dest, "test", 5); assert(result == dest); assert(strcmp(dest, "test") == 0); printf("Exact size test passed\n"); } void TestSafeStrncpyTruncated(void) { char dest[4]; char *result = SafeStrncpy(dest, "test", 4); assert(result == dest); assert(strcmp(dest, "tes") == 0); printf("Truncated test passed\n"); } void TestSafeStrncpySecurity(void) { char dest[32]; assert(SafeStrncpy(dest, "test", MAX_SAFESTRNCMP_SIZE + 1) == NULL); strcpy(dest, "test"); assert(SafeStrncpy(dest + 1, dest, sizeof(dest) - 1) != NULL); assert(strcmp(dest + 1, "test") == 0); char *long_string = malloc(MAX_SAFESTRNCMP_SIZE); if (long_string) { memset(long_string, 'A', MAX_SAFESTRNCMP_SIZE - 1); long_string[MAX_SAFESTRNCMP_SIZE - 1] = '\0'; assert(SafeStrncpy(dest, long_string, sizeof(dest)) != NULL); assert(strlen(dest) == sizeof(dest) - 1); free(long_string); } } int main(void) { TestSafeStrncpyNormalCase(); TestSafeStrncpyNullDest(); TestSafeStrncpyZeroSize(); TestSafeStrncpyExactSize(); TestSafeStrncpyTruncated(); TestSafeStrncpySecurity(); printf("All tests passed!\n"); return 0; } portsentry-2.0.6/tests/test_util_strtouint16.c000066400000000000000000000017151510117642400216010ustar00rootroot00000000000000#include #include "../src/util.h" #include "../src/portsentry.h" void TestStrToUint16(void); void TestStrToUint16(void) { uint16_t val; // Test valid cases assert(StrToUint16_t("1", &val) == TRUE && val == 1); assert(StrToUint16_t("65535", &val) == TRUE && val == 65535); assert(StrToUint16_t("8080", &val) == TRUE && val == 8080); assert(StrToUint16_t(" 123", &val) == TRUE && val == 123); // Test invalid cases assert(StrToUint16_t(NULL, &val) == FALSE); assert(StrToUint16_t("", &val) == FALSE); assert(StrToUint16_t("0", &val) == FALSE); assert(StrToUint16_t("-1", &val) == FALSE); assert(StrToUint16_t("65536", &val) == FALSE); assert(StrToUint16_t("12345678", &val) == FALSE); assert(StrToUint16_t("123abc", &val) == FALSE); assert(StrToUint16_t("abc", &val) == FALSE); assert(StrToUint16_t("12.34", &val) == FALSE); assert(StrToUint16_t("123 ", &val) == FALSE); } int main(void) { TestStrToUint16(); return 0; }