pax_global_header00006660000000000000000000000064145246672120014523gustar00rootroot0000000000000052 comment=6a9be44deab3c675829f503639faf152308a51ce gvm-tools-23.11.0/000077500000000000000000000000001452466721200136165ustar00rootroot00000000000000gvm-tools-23.11.0/.bandit000066400000000000000000000002341452466721200150570ustar00rootroot00000000000000# B410, B320: Use of defusedxml is recommended for any server code that parses untrusted XML data. We don't have server code here. skips: ['B410', 'B320'] gvm-tools-23.11.0/.coveragerc000066400000000000000000000001331452466721200157340ustar00rootroot00000000000000# Configuration for coverage.py [run] omit = tests/* source = gvmtools scripts gvm-tools-23.11.0/.docker/000077500000000000000000000000001452466721200151435ustar00rootroot00000000000000gvm-tools-23.11.0/.docker/Dockerfile000066400000000000000000000022451452466721200171400ustar00rootroot00000000000000FROM debian:stable-slim as builder COPY . /source WORKDIR /source RUN apt-get update && \ apt-get install --no-install-recommends --no-install-suggests -y \ gosu \ python3 \ python-is-python3 \ pipx && \ apt-get remove --purge --auto-remove -y && \ rm -rf /var/lib/apt/lists/* RUN pipx install poetry RUN rm -rf dist && /root/.local/bin/poetry build -f wheel FROM debian:stable-slim ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 ENV PIP_NO_CACHE_DIR off WORKDIR /gvm-tools RUN apt-get update && \ apt-get install --no-install-recommends --no-install-suggests -y \ gosu \ python3 \ python3-pip && \ apt-get remove --purge --auto-remove -y && \ rm -rf /var/lib/apt/lists/* RUN addgroup --gid 1001 --system gvm && \ adduser --no-create-home --shell /bin/false --disabled-password --uid 1001 --system --group gvm COPY --from=builder /source/dist/* /gvm-tools/ COPY .docker/entrypoint.sh /usr/local/bin/entrypoint RUN python3 -m pip install --break-system-packages /gvm-tools/* RUN chown -R gvm:gvm /gvm-tools && \ chmod 755 /usr/local/bin/entrypoint ENTRYPOINT [ "/usr/local/bin/entrypoint" ] CMD ["/bin/bash"] gvm-tools-23.11.0/.docker/entrypoint.sh000066400000000000000000000000401452466721200177040ustar00rootroot00000000000000#!/bin/bash exec gosu gvm "$@" gvm-tools-23.11.0/.dockerignore000066400000000000000000000000371452466721200162720ustar00rootroot00000000000000.git/ .github/ .vscode/ .venv/ gvm-tools-23.11.0/.github/000077500000000000000000000000001452466721200151565ustar00rootroot00000000000000gvm-tools-23.11.0/.github/CODEOWNERS000066400000000000000000000002311452466721200165450ustar00rootroot00000000000000# default reviewers * @greenbone/python-gvm-maintainers # dev ops .github/ @greenbone/devops @greenbone/python-gvm-maintainers gvm-tools-23.11.0/.github/ISSUE_TEMPLATE.md000066400000000000000000000016711452466721200176700ustar00rootroot00000000000000 ### Expected behavior ### Current behavior ### Steps to reproduce 1. 2. 3. ### GVM versions **gsa:** (gsad --version) **gvm:** (gvmd --version) **openvas-scanner:** (openvassd --version) **gvm-libs:** **gvm-tools:** (gvm-cli --version) ### Environment **Operating system:** **Installation method / source:** (packages, source installation) ### Logfiles ``` ```gvm-tools-23.11.0/.github/dependabot.yml000066400000000000000000000010061452466721200200030ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: pip directory: "/" schedule: interval: weekly time: "04:00" allow: - dependency-type: direct - dependency-type: indirect commit-message: prefix: "Deps" groups: python-packages: patterns: - "*" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" commit-message: prefix: "Deps" groups: github-actions: patterns: - "*" gvm-tools-23.11.0/.github/workflows/000077500000000000000000000000001452466721200172135ustar00rootroot00000000000000gvm-tools-23.11.0/.github/workflows/auto-merge.yml000066400000000000000000000003351452466721200220040ustar00rootroot00000000000000name: Auto-merge squash on: pull_request_target permissions: contents: write pull-requests: write jobs: auto-merge: uses: greenbone/workflows/.github/workflows/auto-merge.yml@main secrets: inherit gvm-tools-23.11.0/.github/workflows/ci-python.yml000066400000000000000000000040121452466721200216450ustar00rootroot00000000000000name: Build and test Python package on: push: branches: - main pull_request: jobs: linting: name: Linting strategy: matrix: python-version: - "3.9" - "3.10" - "3.11" uses: greenbone/workflows/.github/workflows/lint-python.yml@main with: linter: ruff lint-packages: gvmtools tests python-version: ${{ matrix.python-version }} lint-scripts: name: Lint all scripts runs-on: "ubuntu-latest" strategy: matrix: python-version: - "3.9" - "3.10" - "3.11" steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: greenbone/actions/poetry@v3 with: python-version: ${{ matrix.python-version }} - name: Install additional script dependencies run: poetry run pip install -r scripts/requirements.txt - name: Check with black run: poetry run black --check scripts/*.gmp.py - name: Lint scripts run: | poetry run ruff --config scripts/ruff.toml scripts/*.py test: name: Unit tests strategy: matrix: python-version: - "3.9" - "3.10" - "3.11" uses: greenbone/workflows/.github/workflows/test-python.yml@main with: python-version: ${{ matrix.python-version }} codecov: name: Upload coverage to codecov.io needs: test runs-on: "ubuntu-latest" steps: - uses: actions/checkout@v4 - name: Calculate and upload coverage to codecov.io uses: greenbone/actions/coverage-python@v3 build-docs: name: Build the documentation runs-on: "ubuntu-latest" steps: - uses: actions/checkout@v4 - name: Install poetry and dependencies uses: greenbone/actions/poetry@v3 - name: Build docs run: | cd docs poetry run make html versioning: name: Check versioning uses: greenbone/workflows/.github/workflows/check-version.yml@main gvm-tools-23.11.0/.github/workflows/codeql-analysis-python.yml000066400000000000000000000013151452466721200243450ustar00rootroot00000000000000name: "CodeQL" on: push: branches: [ main ] pull_request: branches: [ main ] paths-ignore: - '**/*.md' - '**/*.txt' schedule: - cron: '30 5 * * 0' # 5:30h on Sundays jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'python' ] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 gvm-tools-23.11.0/.github/workflows/container.yml000066400000000000000000000030661452466721200217250ustar00rootroot00000000000000name: Container Image Builds on: push: branches: [ main ] tags: ["v*"] workflow_dispatch: jobs: images: name: Build images runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Login to Dockerhub Container Registry uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Setup container meta information id: meta uses: docker/metadata-action@v5 with: images: greenbone/gvm-tools labels: | org.opencontainers.image.vendor=Greenbone org.opencontainers.image.documentation=https://greenbone.github.io/gvm-tools/ org.opencontainers.image.base.name=debian/stable-slim flavor: latest=false # no latest container tag for git tags tags: | # create container tag for git tags type=ref,event=tag # set latest for main branch pushes type=raw,value=latest,enable={{is_default_branch}} - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and push Container image uses: docker/build-push-action@v5 with: context: . push: true file: .docker/Dockerfile platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} gvm-tools-23.11.0/.github/workflows/conventional-commits.yml000066400000000000000000000004631452466721200241110ustar00rootroot00000000000000name: Conventional Commits on: pull_request_target: permissions: pull-requests: write contents: read jobs: conventional-commits: name: Conventional Commits runs-on: ubuntu-latest steps: - name: Report Conventional Commits uses: greenbone/actions/conventional-commits@v3 gvm-tools-23.11.0/.github/workflows/dependency-review.yml000066400000000000000000000003451452466721200233550ustar00rootroot00000000000000name: 'Dependency Review' on: [pull_request] permissions: contents: read jobs: dependency-review: runs-on: ubuntu-latest steps: - name: 'Dependency Review' uses: greenbone/actions/dependency-review@v3 gvm-tools-23.11.0/.github/workflows/deploy-pypi.yml000066400000000000000000000002451452466721200222120ustar00rootroot00000000000000name: Deploy on PyPI on: release: types: [created] jobs: deploy: uses: greenbone/workflows/.github/workflows/deploy-pypi.yml@main secrets: inherit gvm-tools-23.11.0/.github/workflows/github-pages.yml000066400000000000000000000010111452466721200223060ustar00rootroot00000000000000name: Deploy docs to GitHub Pages on: # Runs on pushes targeting the default branch push: branches: [main] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write # Allow one concurrent deployment concurrency: group: "pages" cancel-in-progress: true jobs: deploy: uses: greenbone/workflows/.github/workflows/docs-python.yml@main gvm-tools-23.11.0/.github/workflows/release-pontos.yml000066400000000000000000000003631452466721200227000ustar00rootroot00000000000000name: Release Python package on: pull_request: types: [closed] workflow_dispatch: jobs: build-and-release: name: Create a new release uses: greenbone/workflows/.github/workflows/release-python.yml@main secrets: inherit gvm-tools-23.11.0/.github/workflows/sbom-upload.yml000066400000000000000000000004151452466721200221600ustar00rootroot00000000000000name: SBOM upload on: workflow_dispatch: push: branches: ["main"] jobs: SBOM-upload: runs-on: ubuntu-latest permissions: id-token: write contents: write steps: - name: 'SBOM upload' uses: greenbone/actions/sbom-upload@v3 gvm-tools-23.11.0/.gitignore000066400000000000000000000001211452466721200156000ustar00rootroot00000000000000__pycache__ *.pyc *.log .egg *.egg-info dist build .idea .vscode .coverage .venv gvm-tools-23.11.0/.pylintrc000066400000000000000000000342061452466721200154700ustar00rootroot00000000000000[MASTER] # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code extension-pkg-whitelist=lxml # Add files or directories to the blacklist. They should be base names, not # paths. ignore=CVS # Add files or directories matching the regex patterns to the blacklist. The # regex matches against base names, not paths. ignore-patterns=docs,.*?.gmp.py,.*?osp.py # Pickle collected data for later comparisons. persistent=yes # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages suggestion-mode=yes # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED confidence= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once).You can also use "--disable=all" to # disable everything first and then reenable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" # bad-continuation is disabled because of a bug in pylint. # See https://github.com/ambv/black/issues/48 and https://github.com/PyCQA/pylint/issues/289 disable=len-as-condition, attribute-defined-outside-init, missing-docstring, bad-continuation, R #disable=print-statement, # parameter-unpacking, # unpacking-in-except, # old-raise-syntax, # backtick, # long-suffix, # old-ne-operator, # old-octal-literal, # import-star-module-level, # non-ascii-bytes-literal, # raw-checker-failed, # bad-inline-option, # locally-disabled, # locally-enabled, # file-ignored, # suppressed-message, # useless-suppression, # deprecated-pragma, # apply-builtin, # basestring-builtin, # buffer-builtin, # cmp-builtin, # coerce-builtin, # execfile-builtin, # file-builtin, # long-builtin, # raw_input-builtin, # reduce-builtin, # standarderror-builtin, # unicode-builtin, # xrange-builtin, # coerce-method, # delslice-method, # getslice-method, # setslice-method, # no-absolute-import, # old-division, # dict-iter-method, # dict-view-method, # next-method-called, # metaclass-assignment, # indexing-exception, # raising-string, # reload-builtin, # oct-method, # hex-method, # nonzero-method, # cmp-method, # input-builtin, # round-builtin, # intern-builtin, # unichr-builtin, # map-builtin-not-iterating, # zip-builtin-not-iterating, # range-builtin-not-iterating, # filter-builtin-not-iterating, # using-cmp-argument, # eq-without-hash, # div-method, # idiv-method, # rdiv-method, # exception-message-attribute, # invalid-str-codec, # sys-max-int, # bad-python3-import, # deprecated-string-function, # deprecated-str-translate-call, # deprecated-itertools-function, # deprecated-types-field, # next-method-defined, # dict-items-not-iterating, # dict-keys-not-iterating, # dict-values-not-iterating # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. enable=c-extension-no-member [REPORTS] # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details #msg-template= # Set the output format. Available formats are text, parseable, colorized, json # and msvs (visual studio).You can also give a reporter class, eg # mypackage.mymodule.MyReporterClass. output-format=text # Tells whether to display a full report or only the messages reports=no # Deactivate the evaluation score. score=no [REFACTORING] # Maximum number of nested blocks for function / method body max-nested-blocks=5 # Complete name of functions that never returns. When checking for # inconsistent-return-statements if a never returning function is called then # it will be considered as an explicit return statement and no message will be # printed. never-returning-functions=optparse.Values,sys.exit [VARIABLES] # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= # Tells whether unused global variables should be treated as a violation. allow-global-unused-variables=yes # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_, _cb # A regular expression matching the name of dummy variables (i.e. expectedly # not used). dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.*|^ignored_|^unused_ # Tells whether we should check for unused import in __init__ files. init-import=no # List of qualified module names which can have objects that can redefine # builtins. redefining-builtins-modules=six.moves,past.builtins,future.builtins [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME, XXX, TODO [BASIC] # Regular expression which should only match correct argument names argument-rgx=[a-z_][a-z0-9_]{1,40}$ # Regular expression which should only match correct instance attribute names attr-rgx=[a-z_][a-z0-9_]{1,40}$ # Bad variable names which should always be refused, separated by a comma bad-names=foo, bar, baz, toto, tutu, tata # Regular expression matching correct class attribute names. class-attribute-rgx=([a-z_][a-z0-9_]{1,40})|([A-Z_][A-Z0-9_]{1,30})$ # Naming style matching correct class names class-naming-style=PascalCase # Naming style matching correct constant names const-naming-style=UPPER_CASE # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=3 # Regular expression which should only match correct function names function-rgx=[a-z_][a-z0-9_]+$ # Good variable names which should always be accepted, separated by a comma good-names=e, f, i, j, k, ex, Run, logger, _ # Include a hint for the correct naming format with invalid-name include-naming-hint=yes # Regular expression matching correct inline iteration names. inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Regular expression which should only match correct method names method-rgx=[a-z_][a-z0-9_]+$ # Regular expression which should only match correct module names module-rgx=([a-z]+)|(test_*)$ # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ # List of decorators that produce properties, such as abc.abstractproperty. Add # to this list to register other decorators that produce valid properties. property-classes=abc.abstractproperty # Regular expression which should only match correct variable names variable-rgx=[a-z_][a-z0-9_]+$ [SIMILARITIES] # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=no # Minimum lines number of a similarity. min-similarity-lines=4 [LOGGING] # Logging modules to check that the string format arguments are in logging # function parameter format logging-modules=logging [TYPECHECK] # List of decorators that produce context managers, such as # contextlib.contextmanager. Add to this list to register other decorators that # produce valid context managers. contextmanager-decorators=contextlib.contextmanager # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members= # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # This flag controls whether pylint should warn about no-member and similar # checks whenever an opaque object is returned when inferring. The inference # can return multiple potential results while evaluating a Python object, but # some branches might not be evaluated, which results in partial inference. In # that case, it might be useful to still emit no-member and other checks for # the rest of the inferred objects. ignore-on-opaque-inference=yes # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of # qualified names. ignored-classes=optparse.Values,thread._local,_thread._local # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. ignored-modules= # Show a hint with possible names when a member name was not found. The aspect # of finding the hint is based on edit distance. missing-member-hint=yes # The minimum edit distance a name should have in order to be considered a # similar match for a missing member name. missing-member-hint-distance=1 # The total number of similar names that should be taken in consideration when # showing a hint for a missing member. missing-member-max-choices=1 [FORMAT] # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format=LF # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Maximum number of characters on a single line. max-line-length=80 # Maximum number of lines in a module max-module-lines=1000 # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no [IMPORTS] # Allow wildcard imports from modules that define __all__. allow-wildcard-with-all=no # Analyse import fallback blocks. This can be used to support both Python 2 and # 3 compatible code, which means that the block might have code that exists # only in one or another interpreter, leading to false positives when analysed. analyse-fallback-blocks=no # Deprecated modules which should not be used, separated by a comma deprecated-modules=optparse,tkinter.tix # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= # Force import order to recognize a module as part of the standard # compatibility libraries. known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant [DESIGN] # Maximum number of arguments for function / method max-args=15 # Maximum number of attributes for a class (see R0902). max-attributes=20 # Maximum number of boolean expressions in a if statement max-bool-expr=5 # Maximum number of branch for function / method body max-branches=12 # Maximum number of locals for function / method body max-locals=15 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of public methods for a class (see R0904). max-public-methods=30 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of statements in function / method body max-statements=50 # Minimum number of public methods for a class (see R0903). min-public-methods=0 [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__, __new__, setUp # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict, _fields, _replace, _source, _make # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=builtins.Exception gvm-tools-23.11.0/LICENSE000066400000000000000000001045051452466721200146300ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . gvm-tools-23.11.0/README.md000066400000000000000000000144301452466721200150770ustar00rootroot00000000000000![Greenbone Logo](https://www.greenbone.net/wp-content/uploads/gb_new-logo_horizontal_rgb_small.png) # Greenbone Vulnerability Management Tools [![GitHub releases](https://img.shields.io/github/release-pre/greenbone/gvm-tools.svg)](https://github.com/greenbone/gvm-tools/releases) [![PyPI release](https://img.shields.io/pypi/v/gvm-tools.svg)](https://pypi.org/project/gvm-tools/) [![code test coverage](https://codecov.io/gh/greenbone/gvm-tools/branch/master/graph/badge.svg)](https://codecov.io/gh/greenbone/gvm-tools) [![Build and test](https://github.com/greenbone/gvm-tools/actions/workflows/ci-python.yml/badge.svg)](https://github.com/greenbone/gvm-tools/actions/workflows/ci-python.yml) The Greenbone Vulnerability Management Tools `gvm-tools` are a collection of tools that help with remote controlling a Greenbone Enterprise Appliance and Greenbone Community Edition installations. The tools aid in accessing the communication protocols GMP (Greenbone Management Protocol) and OSP (Open Scanner Protocol). This module is comprised of interactive and non-interactive clients. The programming language Python is supported directly for interactive scripting. But it is also possible to issue remote GMP/OSP commands without programming in Python. ## Table of Contents - [Documentation](#documentation) - [Installation](#installation) - [Requirements](#requirements) - [Version](#version) - [Usage](#usage) - [gvm-cli](#gvm-cli) - [Examples](#examples) - [gvm-script](#gvm-script) - [Example script](#example-script) - [More example scripts](#more-example-scripts) - [gvm-pyshell](#gvm-pyshell) - [Example program use](#example-program-use) - [Support](#support) - [Maintainer](#maintainer) - [Contributing](#contributing) - [License](#license) ## Documentation The documentation for `gvm-tools` can be found at [https://greenbone.github.io/gvm-tools/](https://greenbone.github.io/gvm-tools/). Please refer to the documentation for more details as this README just gives a short overview. ## Installation See the [documentation](https://greenbone.github.io/gvm-tools/install.html) for all supported installation options. ### Requirements Python 3.9 and later is supported. ### Version Please consider to always use the **newest** version of `gvm-tools` and `python-gvm`. We frequently update this projects to add features and keep them free from bugs. This is why installing `gvm-tools` using pip is recommended. **To use `gvm-tools` with an old GMP version (7, 8, 9) you must use a release version** **that is `<21.06`, combined with an `python-gvm` version `<21.05`.** **In the `21.06` release the support of these older versions has been dropped.** ## Usage There are several clients to communicate via GMP/OSP. All clients have the ability to build a connection in various ways: * Unix Socket * TLS Connection * SSH Connection ### gvm-cli This tool sends plain GMP/OSP commands and prints the result to the standard output. #### Examples Return the current protocol version used by the server: ```bash gvm-cli socket --xml "" ``` Return all tasks visible to the GMP user with the provided credentials: ```bash gvm-cli --gmp-username foo --gmp-password bar socket --xml "" ``` Read a file with GMP commands and return the result: ```bash gvm-cli --gmp-username foo --gmp-password bar socket myfile.xml ``` Note that `gvm-cli` will by default print an error message and exit with a non-zero exit code when a command is rejected by the server. If this kind of error handling is not desired, the unparsed XML response can be requested using the `--raw` parameter: ```bash gvm-cli socket --raw --xml "" ``` ### gvm-script This tool has a lot more features than the simple `gvm-cli` client. You have the possibility to create your own custom gmp or osp scripts with commands from the [python-gvm library](https://github.com/greenbone/python-gvm) and from Python 3 itself. #### Example script ```python # Retrieve current GMP version version = gmp.get_version() # Prints the XML in beautiful form from gvmtools.helper import pretty_print pretty_print(version) # Retrieve all tasks tasks = gmp.get_tasks() # Get names of tasks task_names = tasks.xpath('task/name/text()') pretty_print(task_names) ``` #### More example scripts There is a growing collection of gmp-scripts in the ["scripts/"](scripts/) folder. Some of them might be exactly what you need and all of them help writing your own gmp scripts. ### gvm-pyshell This tool is for running gmp or osp scripts interactively. It provides the same API as [gvm-script](#gvm-script) using the [python-gvm library](https://github.com/greenbone/python-gvm). #### Example program use Connect with given credentials via a unix domain socket and open an interactive shell: ```bash gvm-pyshell --gmp-username user --gmp-password pass socket ``` Connect through SSH connection and open the interactive shell: ```bash gvm-pyshell --hostname 127.0.0.1 ssh ``` ## Support For any question on the usage of `gvm-tools` or gmp scripts please use the [Greenbone Community Portal](https://community.greenbone.net/c/gmp). If you found a problem with the software, please [create an issue](https://github.com/greenbone/gvm-tools/issues) on GitHub. ## Maintainer This project is maintained by [Greenbone AG](https://www.greenbone.net/). ## Contributing Your contributions are highly appreciated. Please [create a pull request](https://github.com/greenbone/gvm-tools/pulls) on GitHub. For bigger changes, please discuss it first in the [issues](https://github.com/greenbone/gvm-tools/issues). For development you should use [poetry](https://python-poetry.org/) to keep you python packages separated in different environments. First install poetry via pip python3 -m pip install --user poetry Afterwards run poetry install in the checkout directory of `gvm-tools` (the directory containing the `pyproject.toml` file) to install all dependencies including the packages only required for development. Afterwards active the git hooks for auto-formatting and linting via [autohooks](https://github.com/greenbone/autohooks). poetry run autohooks activate --force ## License Copyright (C) 2017-2023 [Greenbone AG](https://www.greenbone.net/) Licensed under the [GNU General Public License v3.0 or later](LICENSE). gvm-tools-23.11.0/docs/000077500000000000000000000000001452466721200145465ustar00rootroot00000000000000gvm-tools-23.11.0/docs/Makefile000066400000000000000000000012321452466721200162040ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SOURCEDIR = . BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) livehtml: sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile livehtml # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) gvm-tools-23.11.0/docs/_static/000077500000000000000000000000001452466721200161745ustar00rootroot00000000000000gvm-tools-23.11.0/docs/_static/custom.css000066400000000000000000000107101452466721200202170ustar00rootroot00000000000000div.document { width: 1200px; } body { font-family: Helvetica, Verdana; } div.body { max-width: 1200px; } h1, h2, h3, h4, h5, h6 { font-weight:500; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: Helvetica, Verdana; } .sphinx-tabs-tab { color:var(--color-content-foreground); font-family: Helvetica, Verdana; } a { color: var(--color-content-foreground); text-decoration: none; border-bottom: 1px dotted var(--color-content-foreground); } a:hover { color: #11AB51; border-bottom: 1px solid var(--color-content-foreground); } .related-pages a .page-info .title, .bottom-of-page a { color: var(--color-foreground-secondary); text-decoration: none; border-bottom: 1px dotted var(--color-foreground-secondary); } .related-pages a:hover .page-info .title, .bottom-of-page a:hover { text-decoration: none; color: #11AB51; border-bottom: 1px solid var(--color-foreground-secondary); } a.muted-link { border-bottom:0; } a.muted-link:hover { border-bottom:0; color:#11AB51; } a.sidebar-brand, .toctree-l1 > a.reference, .toc-tree a.reference { border-bottom: 0; } .toctree-l1 > a.reference:hover, .toc-tree a.reference:hover { border-bottom: 0; color: #11AB51; } .related-pages a { border-bottom:0; } .related-pages a:hover { border-bottom:0; } .toctree-wrapper .caption-text, .sidebar-tree .caption-text { font-size:2em; font-weight:500; color: var(--color-content-foreground); } .sidebar-brand-text { text-align:left; } .sidebar-search { color:var(--color-content-foreground); } .sidebar-tree .toctree-l1 > .reference { color: var(--color-content-foreground); } .toc-tree li.scroll-current > .reference { color: var(--color-content-foreground); } .toc-tree .reference { color: var(--color-content-foreground); font-size:1.1em; } .toc-title { color: var(--color-content-foreground); font-size:1.2em; } div.admonition { background-color:var(--color-background-secondary); padding: 20px 20px; border-radius: 4px; } .admonition.note > .admonition-title, .admonition.warning > .admonition-title, .admonition.tip > .admonition-title, .admonition.important > .admonition-title, .admonition.hint > .admonition-title { background-color:var(--color-background-secondary); font-family: Helvetica, Verdana; font-weight: bold; font-size:1em; } div.body p.caption { font-size: 240%; margin-bottom: 0px; } div.sphinxsidebar h3 { font-family: Helvetica, Verdana; } div.sphinxsidebar a { border: 0px; } div.sphinxsidebarwrapper p.description { font-size: 15px; } div.sphinxsidebarwrapper h1.logo { font-size: 25px; } div.sphinxsidebarwrapper h1.logo-name { margin-top:50px; } div.sphinxsidebarwrapper p.description { margin-bottom: 50px; } div.sphinxsidebarwrapper img { max-width: 70%; } .highlight-shell-session .go::before { content: ">"; padding-right: 1em; } .highlight pre { border-radius: 6px; font-family: Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace; font-size: 0.9em; line-height: 1.2; padding: 1.25rem 1.5rem; } .code-block-caption { font-size:0.7em; background-color: var(--color-background-secondary); color: var(--color-foreground-primary); } .code-block-caption a { color:var(--color-foreground-primary); } .code-block-caption a:hover { color:#11AB51; } div.edit-this-page > a.muted-link, svg.icon-tabler-shadow, svg.theme-icon-when-dark, svg.theme-icon-when-light { color:var(--color-content-foreground); } div.edit-this-page > a.muted-link:hover, svg.icon.icon-tabler.icon-tabler-copy:hover, svg.icon-tabler-shadow:hover, svg.theme-icon-when-dark:hover, svg.theme-icon-when-light:hover { color:#11AB51; } .highlight { background-color: var(--color-code-background); color:var(--color-code-foreground); } .highlight button.copybtn { background-color:transparent; } .highlight button.copybtn:hover { background-color:transparent; } svg.icon.icon-tabler.icon-tabler-copy { color: var(--color-code-foreground); } .highlight .go { color: var(--color-code-foreground); font-weight: normal; } .highlight .l { color: var(--color-code-foreground); } #contents.toc { background-color: #ffffff; border: none; } .back-to-top { background-color:var(--color-background-secondary); } div.topic { border-radius: 4px; } div.topic a.reference.internal { border: 0px; } div.toctree-wrapper.compound a.reference.internal { border: 0px; } #indices-and-tables a.reference.internal { border: 0px; } .sidebar-logo { max-width: 70%; } gvm-tools-23.11.0/docs/_static/greenbone.png000066400000000000000000000575161452466721200206640ustar00rootroot00000000000000‰PNG  IHDRKD!ó_ IDATxœì½¸Gu8~fvo{ý===Iïɲ$I–;î°qÇ1½:àPì$´ =€ Õ¡%@h!L³±±qllã&YͲ,Yý=õ×nÙÝ9¿ofgfÏÌî}²ó_¾ÿ÷±þäwïÝÝ™3gΜ>gàÏן¯?_¾þ|ýùúóõçëÿ·+|ddÄùŽúÁì/Ó¯¢~†Þ6WÁ;s¢(zU¥RþÜèØ.h5›0kÖ,(—ʈc¿DÄÆØÏ8çŸæœC£Ù„=»wÿŸÎÁ¼áyÐQë€8Ž%ŒîMDý[³ÙÄkQCµRF³¡~ëìèd š”Ëeõ›HDqA€«‚´«ç‚ô'Ÿ‘ïÉßäqA”$P BÂÀÞîëßã$Qý˾ä?ù¼yÏÀ.¯0 !Iû›|¶¿¿*•Šjtjj&ÆÇìj,B@©TR0J<ÈÏæ½(ŠTûò7ùwÇÎP­Va @õFJ0²ù]Ò…|F>/aqÓ¾9Aµj…žŠl<²ïV«•⋌Ïâ(›º }5 5Ö€ ! Ÿ˜Pc–W©\†þ¾>…'Iòo:ïp¨aÈ1$jNêõºj¯–Ô˜Œò©6‹Š‘94ó¨ùOq"ÛvÆ… Æºwß^ÝK¨‚R)„éFD’@‡¤GêžlCŽA¶aèHŽG¶'/9Ç;wî,„3,úÑ4öŒ8FÛç( žá»éãÆØg“D¼:àü9òWI•rE"-Âà’é}015~úì¹s/€×ÀúgéŸöÂt;óg±ü-^Ð:%&òE£ùH‡i"ÇÐÈKHðï|&ŒFö‡æ3s;D¯ôaóú¤°!Ä #r¦á-†Ù¥¦ÿ³cÔ Õ§53®d„I¤x‡T4š¡8 áÑ´i¦„1 LˆyÜ‹i³¶ gÙ˜È|úófð£o‰Yò`AJˆ=QMûÍ-g–µQ´¢}ÁSt3™(v:x6ƒü::Då±Ï²ì6g,’ܸ1Q?¡§§ûÝ==—ʇ”Àê¦vö/œ;w^ÿaÑÆß­<½oÞìÀ±°ùÙCû¿w!³‹Õ[QäÐßÕ„ËÅï=j%[›¹É¤p¾Jìó3“€fÞôMÌÏKäÌë ˆ-%Ût,H~1Qfús°ä?O‰Ý2\ó jÀ…‡ 9n­2‡9xLׯmÁ ÛW/n.à sÁœÇÀ¡¡W“§Ð‡±ˆ¶Ü+‡w_žyÚœÏ:é°1÷»3™$Îw@ÔÈvŸa&:Ç“÷¥Êao¹³,õHÍø’RGùZ‹/Jõ—qÎ÷Nì_~ì}í@<ïÃSÉS|¢wî¼¹÷uww„RÕ.0ár:¯ß\EÏÑÉÌëÎVå–ªc:>wö™•,ífsŸÑjD•T¬êL¤x‹) c$ÆlHA+Q…Š(5Wòp3wE8¿<Ñæs­[j%ï<éðBÌÔùLAôLBÄj‘V…¾{Ñ~k@…Àfc6 —¶Sô’ù3Þž‰QA†é³ˆyf_¤õPÌp‹æ(…"ZÉ®B&ÓÕÕ5ÃpHÃ7ˆžÕ%Ç–DI’|Ñõ‰;¶tLøB©£r;c|¨)S·£ ŒÕûËÅÓýOŒ5{¦†{‡z¿Çñ5H¤KÑò.dŠÄtÈ«¤žj¬?3¯-åwP‹¨½ê]t¹Û…ÓJ§"d{ZaÑDÉĈf@‡—cÆ1gÒ•åúf2Ș¢U4ÚÓzn y_éØa¦Dêcí'ƒ sÂ2×=sŸÍ5†tl®ùY0 òj{Bh˲¼•í7‘šä¶ÌðTˆnÚ*¾ìðûý™KÚѦ­XßÂËzö'Ù‡M/ê$.bVt¡ÇÓÍ9agV]ÐÇ–ok47?¹©2Ü=|3KÄ‘Bˆ@Wu¤š N×yé±õß¼ª¢ë­Óõéë°Æù™!ÈíÕUHÈ=$9ÃÏîo¶‘ÀA4‰LÔç†áø0ÈB Ì«pF3IXª%x̽ëXól†Å¤è¡aE¢ûn±¹”Ç)¶óèñú‹ÖÇIWsÉ-Ö"s‰ú¥Ú0¿󹈹;xôiã`Ú^{“.šxc á™ÂbÌ`òpn·Ÿis2™²òú[Ìz.Oê;ªw†Bæhè@¦v{¦çÐÚ?}xX+PÝuùP½ë–Z«¾ojAµ·ã$÷t”;Âêœn± '9d+Îï GîžÑoUàþÒF´ûr2fصa¼([¦væü%†À–Þ÷€8 b1Ó²¦Ë´d:o.*a]Kö³LÆf²°§BeÔwD³g}Øacö…Ø”ŽÄmÓ°b.!ûæ&:•cd..(ó2{„t#™ÁÌWá¼Hh#Ã’~™ÒÄù93c Mµ!PGá̦[yαíÇŸ÷öÁ‘\²à*d2{öìiûÂ3½æÎ›°oï^»‘Jþ•“ÔÕ_…¨™@Ô”›×80¹À™ÑË  ªÃâ@ûáöI\9Á—¿äÌÚÜË-Þ±Möw=W-E¡)Ñ R kœ3ôþ²»ÙšnVƒrp;»Êµž’pº8ËìKN™ œXjÈX/×ÒÑ1‡ ³]¢.ºf4<ç.süD޳Ȳ`6%g±xRÔÝëDÏïÄ4²uÃþÖÆ_ÁJ˜”Ã;òŒ„YXÉ8, Œ£iH¾3L„H0ÛDÜñaÁGº° ´ƒ™ÇÏÀ|Î3Ê‚è!´_Ît¢Ï³ìw ?e¼1— ™ÌÿÊ¥Í ×cÊÄèè-CsRÀôdJ,T>›R9”CÔ·þ¾Uâ(8þ©Ë;º;ªAnþõ Ñ]îàåÃP*¶ÞwÚ/÷›aæ'Á¢¥ó’>}½œqvcË’$Y«Ò®‰4Í9‰h 7ÑÈ–pȾåRèDN¨¤ ©ÈDr¾2â,¥A ³hHhÒõ«Ð½H‚ãÄÎpU›Äeµ.s›ÙEl`HïeÄç˜)j[•œ8¢j(С;&¨´é—oÉÕž\¡Pxͤ=Aæ»",Ë’{l׆Õ³¿ íøV»Í õA¶ê˜{49ÕÝ#—ç è3%Âý͜Оg9ןŽÉ Ò€”$™?F.Ôj9Taà§7nú`wG7žöÎ [ƒçÖ]æ0•ŒÃŽ»Ö5ŸxðñòÇ-ƒc,Á4$ZÖýÄÚfìMì !¬ô×°1ôuV’úžÉï‹Aô˲&‹yôÓö3ÝQà½'MÁ~`e I’s¨æÇîýbL÷•ßÉíNôºÍ¢.`}.¾Igɦ9¹y ò ªá¸YÝŒà,g°¹Ä"Aíq{3ÁÃg¼4ǽ½±»ï+\#Ë÷å1ŸB«…ùpx¯çúöCÓ¢¨Ÿvh[Çj¹ñCžé14 {nŽ Ï£vAÍ㙯?“ÉIBäDê«z­¶nÝúõEGùšeï9/鬬cÍ=ðëÆz8â±-Òà\p<«•òæé°-iA?­SªÄµ %M˜J(­1û%K¢' ÿÄm}ýŸ*—Ëï“>aòXrš–Ú-4óÉ3aXÒ>áG:ÐÔ $!zF³]M˜»€1“†n(¤ëæšT[È/̺¶LŘó…!æÜ¤ªrªÅ 5gÐòBü1fžÎL-f&þvæ,N—ÏzQ5m*ކ¹°dŒ lÒd­à™â2Ÿ÷3…ÓÑP L›¶*Ävš μ߳°Ÿb5&‚kKÙ¿ø®Éx¶²¹Ré™B”BØ?:¶°øíã/{îY‡]{.tòJ0Ö:cͧááÖ¾”i¼p¸tì’Ádô¬Rylÿx"ž†€q˜öÍëUÒÔÚaRµˆZ\ˆµ'WÊÇœs²Xy׃ïœ=û_ÇØ wkû TEŸE$1tttA¹\J£L¹ÂN^2·S"ÀªÖ\aN."g¾/¨hŒŠî¹¥²| ¼0 ?ІŠpö1c3ÂkW¬íÐOݸ[ŒÄ÷ûðÙL%”“$I/\ï¾\À-ˆ cExÁŽg?¼ž­I ‹ÛŸÕ(‹ÖKq¢¸Ò$èd¢Ì]õìf ÌŸÂ‹å… õu1R.#÷À íþI™ älÀ,DV×Ú½kì¢ÎRõ§¿ãâÞáKŽX3ñ<ØÜ­âÒ<€>V‚u D á%¨ïUÈídºy‰ŒîH|3€ˆ ]#•áþ&Îå=ìÐ÷œ›ì\±‘ïÚµë#ðjZŸÑ Çé¹÷A!»Zi#RÊÑ*Ð&ôfRÒ¸’0[ôf¡ ‹¶g'Ú#h_½.Rss°“ªyV °Ê•«ºëmK¨É¹H 3Æh¦d+Ñ33È6b˜ÁYá¾Ö£˜Ï4•" Òd/gh`„¹ù™°ÅNÓgdæm]U^¿ŽI¦á´phfFø¸¸pµë<ãi#$6_r:s´fj/âxž`)ºþäL†:*S'VJq[×m¼vhÎÜ/œú¡+¡{É îŠÇÙ½ãa*Ž¡3àЖ”¯¥ CƒB5ž§[–dk¡d.² ;FDL㉠`g‚À«q°ìeÏÅ]_ÿÙ«†‡‡?Oa€¡º!ÖvÎUC(Òéë˜4RÌ.\²¡<¤‘”Iˆœìwh¦ýDδXi6¨ÂÖ0dÌ,¿u€:›)Ðï†Hûí4¬ƒh`PDºÎ†gVÈ P¬ÖjÐi1Å3â%t,ΆEwŒES@}–Á;fo›5B+r`æÌº6ƒ, D`©þL* Ìà€þ“F—ü‰€¨Ñ ×­Øø£N;᪥¹Zl>ð[+£rP›2R3 5r„r«‚rôÁ!Y º}¥U`‚0ùx®û“:›÷òãÅâß­aW=qùÀ¬Y_2È/r”e’¦¸ø‰Q+Êá›Q]ŠT+p!v=dÒ‰2FÕ£5Yà€´GQíe;†sÁä3Ú'ñhMÅ€ätCÇG†¦ðe+¤ÑýW3û´…ÛW"ˆ_¬ˆt {p4«1´'ø¼æÇþ¼÷\áãÜÀìEDÏ™o’Ø<“ÒÕdff($ VAÔ³­9ÆKÝ CÈa,‰`"ž„ÓKjß-F÷¾³ÚÛùo°ÒñèÝáRšËF»È·%?úÃÜZªôv~^qÓHÊL&îæ÷Ù”ŸeyRYÞ³V«e'Fzª¨C$,¬µZ­ž$I~“™ èøÒç žE©™µjêÓõ½†Ôþ©°•J¹ÍLøR3+À•¹\)×nƒ\qó™“ÕÙ]îsFËÀÌæI ŸÙÔ˜©÷Ìj}Å2WËÇ-9@C±FÒû–;”LË¡óÌ3áÒË_s^ºF1èK9*imt¤IöY–DÄìåØ<涉úôFK¢‡,ä”C ºËõ`¨Æê'u³?Ð="šìמìy|+l~xÍ s稉”¾˜H3 F·ô[±‘áGj9ÒoÓÔGtR5º­Í›¶ù¬¦Õú­ÁAsv!À¦Üáêa(ÔP;±ÖtttÔÛæ.xÝûš…‡Ügqíªp‘¢¡L»Æ¼Í“´bn!ùÝצ̻hçžùƒz‹·ÈŠUQxÌôCǾiGýHŒeOXGzϵÌ^ ݈Sž©QU€ÂBÎÜr*ù1g-úJ\ÎçG™6‰€f¯xŒ+§¼¤þG³&Ìwpß2€çǯ¯B&3wΜ”£9‹†ªÆÙg3éWŸœ€ kÖžÙvÜrê篂Á¥óqJ$ìÞ«a,iT–¯aÕ½C_?Ü{ïÒéô}É‹.ƒcOß´ àíG*?L¬Až‹Pã9@£JʼnqñÓÑxò/ëùÊU:t: ½äY%bèÎ9ãðÑã룇lïX_‹l+wtu´Z­¡D$cæìi…/VÒ´šŽÜoÇêìbÔÎb/î4_@GGÇëlýdF]—÷Jâä\A“:üÌÊÑ  cæ$x¹ ÂõŸø;„]'^‘7…údŠBÊ–iÓ"=j*ûhé‚:`uÝ̇|ý˱šLÔбÿ2œä5“6æ,ep?†)™P·ÃpÑÅPq„›úAlc>JÛŽÕµ»ùø_ßÉš¢óæÈqˆ`¬-)u­ IDAT† ©´ÓHé,8o†ëè§V2˜DˆæØ®]gTÊåVoo¯?udã‚bÚ‡'“`”ÀèŠ(ØFž-(ºä²EC*!rWžƒ}&l0óÉÒq5-g{€g.¨¶ÉQ¹E ’t𣠇ñúRžôÁuY[¶€â€Ž›Jˆê üÛÆDt¨_Ä¿Ì|·KHµƒt1 }Ⱦƒ3-ˆ¬6ã™;–mS8Ü ‚¤¿Ü/Äñس×rr|b@ÆØî*d2kV¯nûBÑ%£H=•ΞýSã÷žõž—C§-œÕøú‰ °¡5 ³y;ÃæïX{ë#pÆ)§Â[Þ|MÛö¶îÙìì*¤)µ˜O7?Ê’›R-Q &ÁúÒ/mŽÖܽ¢G‚°ðèÃë‹ú;±?^u|%„”q@Ö:býaÍõXU«E øƒ{qBLWfõ À®;D ‚õ ž/Ç ö7„E"\»¼xv\N^dÊ0€Ëœ Q߆a«œæ ‚©üiÜ ¾9ƒ'3ÐÏ€ï!Ç¿Vô+K†2Þ\Š€'€ã‘ÁñZx”yc@€K„GàR@X²–"cŒM!Àvð8< ¿€ipˆcL˜½Dxçüˆ$IŽ€YœK­LDBÀ$c°&à|ö0ü6{Û‘ÌÃzŒæì^Žogœm³Ø.Æ^È9?NZãRó㜠!î€1O©eÄÁ&ÄšX&@q2e\¼®`§!â1À Â•åC‚ ¸²²<È>³‹$NcK8ç‡ëœ(.PL1;à±RXÞÀ¿Iâ43UœÒ$¤Bw:d ƒ X†áƒ ­¤,˜]Äó)8”îÎÁ‡8ç·"à¯\‚òIZ™ï‡–KåçK ˵™Þ šœˆ(£4ñ!¸÷`áùB&#K¸ÁlÁQÓ/u< u@ÿþxü®“_}þ¬Ù/\œ„¬+Ø6½ ®A?”ßD¨÷¦]°ÞñîkÛôßÿþCX;µð‚Ó¡,¸b0†Ô¤-%’̇}l}²öÑu]ó=$êyÁ‚æêKJÀQ®Ô* & 8"Œ–“rÇ1³ðHÂñ$À8$.NŽ=ÿùÑØž:ïÉ{VÜÝ„xë¬þ¾ B"þ;‚Ï8VËü^ú«ÝÙÄ/i³'ÇH©È‰6(ï‡axãÐìÙ¶~ppåLÎ4oŽo7k'8cð%D\fçE$· ÊaXz çì`À´ÁÓˆ‹:ûÆH-!ðXÆà+œóçÊßlB¢WÃüM’d"þ-öE<Ó(UïYfD‹·1Á>—2ýl!3]@Lû‚ÎSaü”èå"{3ç|çôÔ4twuCRã;¾ tm0xücl ‚ë‘üJ©"‡¾¥°#âÇÀ)øh‘×4ó)q^âxuÀùùAÀeŸó ¬GÝô9ê‚â¾€~7dDì±ç[ ÊIÞGUc9ñR=÷¥ñ=:nˆìdœ-3m Ó(p \)ÝŠˆ')<²v#âfÒ…”z÷gÎJÍä9æe4rñÀ÷ÁÜf}^–¼ˆãHm•A€F½þYð¿p5/'·Â¦zæ™V–J#q·¤vY’f©uªMj i¿d¬Œ1©UôúóÀÀÑKð i;Ñã!Uÿ`]_€k¨&Iý/Ä û"n†*ÒÚâúµZíhÎù# €Ê<Î`£í¢Ñ †ÀO€±wpÎÿÙš%Ùüo„a¦u³ÕC!:*•ʯñ,ã8öciUÊ þ~öÐìÛ9ã÷ȲԮı f0`ßÀ×)\˜¨šM®/ÎùUŒó³¢(š_¬ã·a2Ö6ùÞ6sOò¹Í›7¿mþÂo\øwçb…wðɤÉ\DèSÙÚr/’€£o‡Ç›o}g±™´bÕcpÎÑÏ…ñê ^4ºb®ü/BçÂÈ>'ƒ–¯Šš«n´³·Üï¼z¼YŠJй™#£'31€2O?¾[>ØwÛÆZcïŸwÈpãÎ#§ñ‚h ú ]Ç ŠÎÿèOîûέ¥9Cs¶(b0§1(‚`¹¢@E¡>zÑv'5ª6¡¿”hëdÂôÏéWZÐïî¹þõ«_ƒýêF€¡ô~ò 8 :äéHDo+ˆÅïvɼ¼Ž9§-n<Ñ ¥Z@ P™U&dëļF .¯7(àmÛ¸Eé£Ëžs|¼£JMo…³»û”KYžÐ{܇ï(pI•[n-˜˜œT¸àN%eÒèîîjGоŸ¦ ¦–ŠU‘1.—ËM2…[èBx—‹}uýI’‰r¹ÌšÍæ|Îù‡9ç‹Ì3Òa].—ßð¬˜L£^·¦œL™qÒ—þâܾzëÇ€ÕØöúXÓ:AZPJíŽæ:#SÛ÷«–~ü››`ŒÂüÁ!ß7«W­‡­{wÂü+އm¯? €9ÍZºà“LÀK cä"*?è%! (éõ˜FÒ0wêÂ@¨Iæ”|åe}KgŸ[ŸøÏ'£m›¶Ô&7ïïÆf¸±ù4,«MÑÀýlUNSx=cl"Š¢w\´»äütwwgÞxϬ± ‡A`î“ôÿDgï±´ÐdÎHÉà)ÑëÅ’Ë <ÁÜÌ6[ZÔÎ\zlËãBˆ_ !ÖHG("Jtí“0K›¼T*ý¥ÍÕÉU’™?—1ÖT[˜5Žç2`§=G^ÏçœN¶†áâ0 Ï2ã2þjü7LÄÛ9¨ü>òœ.ÆŒnTOâd10SÙºŽk2W¬ënƘôÜ•ÄÉFy¿T .N¸)퟇¦ì“«¹*˜'§ôƒ»Î>š$âyð_% JÏëúP¹\þ0’ìaù9Š¢+à ø„RH]~–»ÉA«Ú°4?ãÖ0 ›tL*§'­œ¿OÍÌ3O¯¼/ý1a^ÀÌQÄüN’äûÆÁ-Ç!éLr!ÄjU€Tù53ÉÏ^±XM„ø2€A+Ž>X­T>!@ZñOëfÆXÖÌB׋:V&˜H2öH¤|¶ñUˆl›Á•„¹Z©È…ö‘(Š®S޽ʥ?#I’ã't?çaÀƒs2ßÝŒšÁ€6µëaš1ñ Dx;‰n’¥µÚ)Aü¦Õl©ÓLcý4)°÷ê‰I3YÚW(WØx„&-C§"óÿ)¥áÂ\Š tvuÞ ÛQ~±RÉkT.—0É‚d3j”[ úš‘Éøžå”Ð8ìݽûêá#Ͼ洤Ì+@§ž–¢†‚rz’€tÔ"ÀP"÷4Þ¿žîMÝPý1‡F!æ "PI:½Óš µËšëÎÈü2rSÒc§V‚C;4YÿÀj6oÏâxÇ,Qâ6+­³VèÉ’[k¥¶3$wg×’ÚÚ«ç%ËN9¯¹ñÃwG×ñ#Î>¦5:g´¹ù¿í”.»}{÷@JÐ?2[J¤Î™3ôKÎØ#@5âXÚùK/¶6 ’R”1ð´â¶ãØs¯‚³F]§bÌk×ÌDüÍÕÈ~OŸOáî`Œu+tQÛá·&»“Z8—Òñ()ݳñ©äg}ºè"-¶­V¦áÞl$r’{Bi’àË©_Å@*„hdNèl“Íd ÿ¥d.\lüZB™¸(+†d¾0/_Ç¢œ:³YšMÌ8ëCÄmãZ³0§na ú,´is#·Œ±Ã°¼)E¿§s ­Ù––J;Kê\²¬z¤¼Æ\•«„V“ ù@5¦ &3%åQÊ*:¨‚¬:ׯ7ã³ý]#àæÒS7*ÒújïW‰bP«–^Å.m æ’`íUÈdzzz –@Ô'ë5ø—åïº*%åæ‚]ÍQXÙØ¯NÒUõOœYR!í ŒtéÕß)Ñ!¢ÔXP F¨ü]š†­ë%‹ÔLJ8Œ"ìºzYÞ÷t8ð‹]|Çë†àª ´0õÝ$¡&£–jav‹àa°æøjü×EõCÞ¿×ß½²S¦ZuVºX­o–˜{Ò¢úä–½åí+ž*õŽ Ê2¯‰Q<žƒÒHGC ~’ŸLäEfÔ°…T΢Ff}?¯§.Ó[núºßÉSM„Ósþ"¹XºœÈOÚÈ›c/¤Ì Sím©$ÊL Ðó$Äíò«tzÊr…Ñ Î¾Ã›v”BæœIý&‡œó^û—e]/E:Ca³§ÍxÎG»’ Ùãæ9¿Nz»d~+•B½m@h‡'4MÒ¡ñµpλe!3Ý÷ßKó¦¶3¤;À•@ש ûhª€º/ÓZ­šÔ$dY’+Ù´þìYõµ7Ó…GC"téÈàS`è„ê?S““wpD\g©‡©¿ÅÑt|jehs2™É©©Lrè¦$<ÒÙ;¶kô¶.t-«ò©d ~7µ9Ä0¦’攣µ‘O­¾fZŸW uYZ“kMÇ0…¤ÉvròLÍ^•ˆ§ªÞ¥ÌCFú¢Ö,ÁòQ—œÔ\õÓûÃå Ïi¬:§«c0 Y¬ >° 3'¨>¤¹Œ³£v•ãZå˧;¯ÛÕz:K»ìË_vNsÑÇ•Ç4ž7}3®ï™˽µ³…É+º¤4 3)c&ÏÏŸALmV/ÌÊFU©Á2oD'¯©²žvcœ;!Ü.«2/㜯´~áÖñµù¾†dí¹Z@F]®3Ø2‡’(ÉŒ¦e?S3Œ3ˆ“d ¸+à<̇èg Çã8^)„øJ¥’¤ª´0ÚQä›–z±ÝÄÛFëVh¿·ò #ânÎùmaÜ-O甉®Ü7eè0¼ò .#V·´#c^dŠDç°@C0N_Åð4ÌaV”'ÈF„ –Ô´Ÿàº_Ôc×Ñ´0ÍæN}›²D‡¡+¬‡D30rΛé¦ÚH%ÓÙ2ŽÙN ‰PóYm–Ë\2ZV˜ieš¤ƒïï!â³ s`„§ŠíÈHe«ý‚çGt¥ªb+)IŠîg!ìŒÁ¾ tȤêfF|š±J§\)TÞw·xSNõ½/Žã—º¡Wõ÷¯›­æ…Ah Æl„”ÀGú[ÌdiIø‡‰>Ñ>rËÓùA!b?cì"VíO)MÉP÷_9 3×H‚–ÌE.œèCîÔ{m}0? 8[VH²1e³ÖQSß:b“åÍÀàÚ ½»Ã3šGÛÎP|œÐ´ÙíT/T¹)Ùîx©í•ËåÍÚg!áÞ”F3…N $•‹$LÀ9)™©ˆ¿Ÿª‰ÆYÝjµ¦$ޤ«‚™°3MSÐັƒLrÞY0¤H ¼T*ÈpœÁÀ8{çü€ÍX.<Ô?5oM°¨è*Üx³mÛ6õoû¶íòøXذnýÒø»¿át¬ò’2“äæÇ§[Ó0ÈCÅ¥š‡Š¯ë|®µ£›tÿqà~®E8Ó5}›y’ií/#ì cèßL-¹ascå¿ý¶³ Xš÷ü#â #¼$w½I × Tša1©ûÀ-ãJcK#>è.Éò¬ÔW•†?“ô÷p2‰šIß1== ÓÓuHÿšSÊw5>9©¼îåJY1ÂÕLÇü+—äq¶áÏLòVf÷+œo.Öd¹ßå:ºr²W¶­À®7”aŸ¡jºt&J‚Uùòs «}Á>ºtnÍÙÀØqŠ…€Xç|ÐHšIŸ—Im2Äj˜ ¼ÛŒÕ¹>’a³|Õ»h•ÕB–ÌV.P¹`kÕª=ˆæÂHF:ß“¯¶Ñ0zÑÅŒßÆ13ìÇ)cIǤû°ä˜ÏI‚ÃÒcùaDcŒÁjÚ˜^¨WÈ”r)4Q¸T (†§Èv¥æóÇå|Ù‹dÌm:ÝfVœ—åŒËŒeŒ“³û˜ugfçÁ7%óÐ97ê^©TNÀ"ÕºU´P®íg«ÉÌš5Ë!Ê­[¶þÓ²KN…Îe°¶«µmî…N™`/¦ŽÕÀúÇR_‹WFŒÌnê4Ÿ%ó¹Tô #©kÌ&Ô‡¶ÉvÇJ‘Äbý¸›Çë¾ym]Ò´5"ö®Ü^:öÖj}ÅE}åÑ”gÇ,- Aö 9Q&ÈêÓ¨ÉÙ¯âr ÞXu5Á,ŠÜS€ÍJµúß)p_äY“011áÞÈÛÈä>x0W”˕"y+"Þ&“ßd bø<<Ä„oÝ>SMF5ÏYn1[†%\sÀÀ*kád5Ô¡sw†a¸Ô¾g¢*aø;ƘÌ6¾wCª9HtvÉLÆØ cl4·‚0ø¡©WÌ8¿@…G!zÆØ… ñ'y>¦|ú¨ ™!ÍÙ(£]‡'qüs^.o‘LZj3ÜK:L] ¦}ޤµ™?è Xˆ¡0 7JIÊ RËXî ¾Ñuž§šI³Ùü¡ÌvÖš ÔjÕ[8ŽòÒd–ì¿°ke]st0¾Î÷ý5² ‚LC0‚ÉÚ²ZM—иædL4íÂä¿Ød–ßÄ„üýæý¥°Ô'0Ñ}ÉXâ—‘ü„1öYìqH“ï%Ú«j¾ÈjsœóÅ­VëGÒÐÈ#³ “¡ˆ™Ú;~BoW÷%C/?NTy'¯‹&Ü?¹IÝëdò; ¸E #I‘Ãì b­ÙpÍ€b^k'²Ú™ÊØÝ^Š`Éf1±ÿúGøc[wHBÄ‹.xáôâÅ‹;ÇF+7þøÆŽ¶v³ã”©•o AÈy’ÅÄXÚ&Z 2Ó´¸iZ=Öè䤋I‹£kò¿^ŰüšÝ»wO åªl5Š#èéí…ŽZMGAihŸ×»<¾) ¹ÐM¢“ê6pö@|Ž™?«Õq5˜CîÕ""å"‘Çd!¼–@u~Š™ë°Tú0gì%­CC!ºák:Ÿe\16Î$ÍÔPj}Ú<`Œ—ËåMGãá‹ìz´ª}ê½ 8ÎBÈfS²±Š:ÁQUYl4D£Ùü*Í}êФ§`R¿@sqîùÖ’™ Z^'Eè­­Vk4Í?AÉø»xãG³dgœÄ÷íÝ»o'!t™ÜÞÕÕ¥6‡zp\ˆ0¹¡U*Zƒ)óAb:Z*øX¬ïŒüN˜¶{&¶ñ!º%XWw·|>áAðŸÅ[fÌ2†v…@¼‚q&óp’VKf¶a™qA³öšÔ†¿—C~ÛdUý™².§ÿýûÿ.¾ëA?#ýIDATËB¶oz\Ï¥›v$ðö‘8 …v0“\Ýbp“€¾ioØ ôôÃîÇ·òñÇžPJN_gïw;†z^#ý;µZÛ(ª|6,•`ïÞ½°g߾⇼«··÷²®®®_%q¼˜š,™SÎ9¥à^ÎùŒ±9I’\é„zåÖ0w3ÛP¶„í‚c†„Äíþ‰PŸžv«T«Ÿœ5«#‰ã1K˜±f §s!¢ÌŽ%„ØIüÓq,žÇ9ÿ¹ôdºsæœEjªa*Í$ɰ¤ƒ1VµîKb6èÃð¬C“F¨Tq'/ℽfw½Óê‹Èìt#†¡Rç¿MÀdOMNN½q|||SºÝÂJ£Ù¸¦V«öF"Îj›°,O<&«Lr¦Ì­ûc—’¼ÍxÔ×a¦Sˆy; ˆvb»J?°„ªe­^˜mP)q,“ý‚ Ø]©TÎEÄïÀéà]Ìa*Eì2áp¨ÿ޹ ™ŒJuæ Æ“Ÿ9õŠó‚¾y}bO4ÎhŽÁ²_ì‚Ç¿?,¼÷pØñÞ#a¬‹Áœˆ«$¹(zRÊi5l%¥KFaÖ´kU—kuÄh5ªò,LªTæÖ¼ÏoOîÚÝñ®¿¹N2˜š†W¼ãškÿüµ¯I‡-_²dIyÞìÙqÃdÛC+#ûç‹mýz·R´¯'ÆÔ9-RL‰ù¿Ø?¾sgçðððo“ZùŽNÞ{JE?L’äÛ' üÃË‹®ƒ”{(˜¬'’$9V ~™38QU|ƒ DĵŒ±G_Ó¦‚Üi»Ö•Ö8áôŠðGdö`×ÃYÓ˜X¨¤—Ïd‚´Ÿ#ÂÏðpRJÐL-btkà^DU`i…<­³£c†#Ýc#·Œ´¢ñŽŽŽs8çÒ{ÚnŽÅš„uz“Ÿ¬t÷KÙf%Ý+dÆ·×*'ZÌž,/‰ð±4õê!Y%UG–Íž°'AgÓ–¤‡xO£Ñ86àüJÎùódÉé–„^©ådym)P!]Æ6 !naŒ}@¡vѣȧI‡-"¾oäœËÖGÊöK€jarB$›c?B|Ú8Õ‹+=èDœ—ÝRm­403žÐŠÜ ¹‰Öº•0–¡¢à—Q± Î`Œ]›ÄÉ+ç ƒ!‡¡ea¼q`°›H\Ü$µ®¶4_ôãü‘hN7Ùä¾Óç|ãêê¬EÃpßÔJذw†Þó0LÃ$Öaé N€µoŽ$„bÆZÜ¡I%„Œ±”´é‚šc0–j=LšN„ÇþpïÄŠü¾÷”“OnÜÿÀœŠâSN>üãKŸüØÇZïûàÕ¨ôÝ$/Í+KG~òöIJRÐ… ¡¥,Ìn7ØWŽaÖ$kˆ¿¼»* ûÕú;åQ%uµÉ.ŠT4Åx;bP- sOL¨î3¹dßZU•#–þœ@–I ‚`aº³‚5[MYL< )Ê>‘ËçðaÈ…nÁ5%ì¥Mr”ÅŠ’$¶ÏÉ”øþþ~eúµ¤d UdP±˜ÜuÄqËf›q¯<›ˆû‡±k'©üM:M»ºÒR2'³b¥™†áB΃2çŒEQ$³d9ËíÚï£ç Á:’ Æb#kd£ž‰ÔX@|\èñ›Å¤LšÙÆFhÉn–e=D”‡¾ „aX– †QíãøÉ ÷¡ÞO415 “¾Ó__ÒW×ÝÙ™‚Áy  !)çç|Bj€ˆ¸nzº^¯Êêxš ÒŸäišà™NΔþ“.¢%• »?*H‹¦MMNÉ4‚JðùÍfkĤ H3>*·œO˜y’¸Û¹sg!Ì…šÌT½ûöí½îÄ—œ]í_4ÇZ{؆hŽ~*†'FÇ hÊÓxú7+áÐ+æÁÓó茥©0~„$§ŸkS 2Lb4‹t£¶4ŒAp{y Ëÿؘ\ñƒßË~äÃF%6ÖŽH÷Â… ÍÙnR5•ÖWLË–KÝšZC•vÖ^•ÑÐw¶•žœ‡ZoÏ;vì¨Ër ½==ª”(´ã¾ä²Su°‹.Í„I1`™ÂÖÊ~ç™SË”…È(%ëÜ…!»ÑÖ7aLwºŽŽÉ8˜™IÒ‚ÝŒ±Ýfƒõ›œ ò~!޲K’€Œl2žüŒóÕ†ˆ‰³Òä9Žj²¢^ÉÂÊÖa¶?΀i³Å€í(TDMÁJCø+¾ˆ©šv¥…Äp½­Ý+q@ΆrÆCðH“/sa0ù[ù³¦2&EI*€rÞ§Ñ´& l@!684eÝù=zEW!»ŒëMy¬Ú‡F^r¢Š0®–Iwr «ÆU¨½c£°wr?ô/šc] ÷H1ëÔQCª¸gxL•šéÈ!í"8ôéxbË?Þ«*`ýÅË^:uáÅ—˜ºè³×_?~éÅÇ=òˆŠDΞmšwÝ}Wê"I£`jž•ôæË’ÙÆÀ<澆XsëÃAÿàìëÆ Xš‰}ffÐÿ„Ëhósíô§Dà>–9#Ûô‡¹÷ò¿çÓô²Ýιþm¾ ]™äà5ˆuk$a,ç'°š P<ÓLTÔ±MÑà;t‹ëªhÆÜ…èF÷À™ï”/’σOŒð·_“»‚ßÖW×®«¢€Ã3¼l®N.ÉŽøàÈyTù+Ëhnwj2õ©—Ÿybo÷p?l˜Þ [“†Z¥ã+Ó“Cžu ”é‚úsû¡Þ@o‹CÌtc<Íæ¥¾l¨÷*¥^îì¼å}¡ØçÒ5­©ÑO>Ž‹éêâ#Ÿþ÷oÇp†_ÿò—›ïzï{%K ‡fׯºòÊæÙÏ~‡iþ×·Ü®¶)Õ‡JjàÒØ›4ËÌL‡]¥mŠOþ¾ZT6 ïíéîÖ“"9öŽ ÐÿõËDg|zqHý„Yº¾%¦,˜e«€4–Ñ(]b“°–µ ‚cû•h$±Ó¾«ÉPŸÏÄòäçj\nå8bX£›å3{9ò˜‚ו©£âìÍ€,JFaÉ2Ö\mY¬™·ögOÓÎi Âó{‡F‰V–µ1Ct|ÅRCc..¨B@kØ€¥´ÍU\ê¡ÒùÆ…¯: öF»àþ©På¡ïŒùÐ{Æ<þ¢>0û¡&W{•BÈ©6‘’eѤØúÒê6“2N*OT§pzñÍ{põwï“MùôÓO›úÞw¿Ëj2åSª0­fýCÿ¸:Mñƒï{ßÔ»ÞûÖÛ×ßeÁï¾ö¯aë®í°üŧÔW̓jEÖ—ÑÎç4R-£Utú§«MES00gè"iKAhdÂyÑDNOÔAΜÉMõA“¹+·MÞGö, ÅÚð'84—sØÙ0d6.òHnŒÅð2NÎIå7 ËBÁvÇ´sî7a´Lspïm †å¯3àß-cC·¼× 3«5@®\©›O#´/è z,º³KÒ?ˆ•{Ð~Ì0X!©&Ѧ$ôÌfþ ·ÕóE¶ÏÌDÖ…Lfþq‡ŸY9¢~2µFµÖÈês+/ïSß{“TYT#Í}ºCI‡Œ™ÉÜÕ.ýXC±K®x®öEÃ{ Øa¼j´öÆÂÕM¬ õ â«_ÿÊÉÏ~þ‹eÝDôLjß÷¾÷±Ñ;+W½ìeSûä'«&µF2˜/]ÿ9ü§/}zÆÒéU¯ªò°OVã“»ÀÍ9M•X¦7¯‚mÚ±æÏ»FÄÉÚž®n×iFÕל“?Òb÷YXL9ÿ@»û¹žé3$q < öÅ}Émýu¦µª:kÜs2F¯v£yøæ:KÌ7y˜=0®ÍU€ï6ÇÀYX³þÁÃ]8¦¢×#šÌÁçÝžå8Š ÿµbæmRÊ;ÄŒ¯Ð‚ÎÿHýL¬¨o›œä#Õ»ŠëÉì:0þÔäV•à?›…iN¯<7)T¢]¤ò‹Ó-tCc3§®Ð%ïÉ‚à<‘ÔÓZòx«ß±7ܱ²²=õ •æõ Á®{Cãï?ú‘Rµ\Q^žÛn¹eêÓ×_¿¹óNÉ'ÂÅ‹Où«_e”Áüõ›ßŽ_øÆ—ÅÈG7Ÿ¾faXPšÓL Ó~™o‚5}tn\õduxd䟒8þše.Äy•“á¾7ž|"Ŭg$𢫽ã‘`FÄÊ\¼¹Bf:wå"2ta‘¤¬"Æé:Þ %æ…9G é—`@ޏÝÓ!]‰˜U¿{V†j…"0¢©¢{ŸšGí"932A:xÊÈvLÖó¨6…QDÿ9ôþ‚K7Åþ/ ²ÅYÁ³Ä|}ÖæÒæ›6uÝ8|¼x AÎ9Ýd;¢+Ú ’ÉeÒ©)™i* •‘謭)DóøŸ××ÜühuÝè¸\ûüй‡°3žwZô¯¸*¼üŠ—p“85q`ýW½oºå£±D½ð…_ýú×ÂY³jº«øôeÏáXûpkàm§%ÛάÁœ–Ñ`R­Jú`‚i¨h%lZ¿©6{öì—Ë¥w—d3³u_#Žérˆ-Ž,ž<¦YŒ5Bس\ ž¹“k<³rsî¶Áì³Ïªos2aµ!n×·€Î{44kfô‚ØR Øf™±pݼ™Ü¶€~éG{fròÛ‡(jÚð¯ ß`M]§)2  €öÍ©«É”îÜ{ûÆWÂeƒ¸»”@‹«[DPiÌy‡²ò]¬M$¹u2=¯¢¥ŸßÜzôž]ó{æ&¯Ï_Á9盼àüº>55Ü~˯ê?þÉOÂÿüñƒ¨ÙäÇ}ttÁùçGgŸ}¶¸ôòË+Ú|?ø÷ïàµo|;î¬7*_:Ÿí]PéêC(¡LDuc°?ˆáЭzüùÕÁ–Í[ËsæÎýy’$/56µÉø4„Í™RµÉWÅsæüòì4óZ¡Ÿ‰C©Þž¼->#Ð Û̾çÚ¶R<ƒ©ôÇ èšI™ªæ€A¤fëè>WÔwQt,çñ3ß)Ó«)+Úí=ëvž¨Ýe YõB·ÁL»ò5\ðÆæÎ5 —bFL5Q*MÄ+%Ó€™=¦ÆeFþUÈdº~¹sÛ8öWû×ö$²(€Ìè-kç®É=¡Û(ýWèÂQÓª8FG}y{sõ=+º_pöÙïÿ׊¡¡¹5ýVü¶·¼%ºõ7¿a£ccÁäÄ„Ô\ø²¥K£¿¹öÚäÍo}+Ó©-ÚÄ/9ÿEü§¿¹9.=wQï^6Ês¢’ æ7¡¦âW‘ªä¨ßO6¶~ñÁÚx4ŇG†¿ŸÄÉ«ì‰+ÒóˆÔYÑAAâÝ3a?;mâ -G©ö䟚1´ (°‚¯¹ÄéÛäms3|U.§…–ëö­.7¿‚:y #R#Y°ÀÖ!µp=C´ÓrŸhü f$B–’nÌA}ì6’“óíÏà ÊuRt?›˜B ³½möñ“ÌîùɈ:¢•3CYö ýj\v‚(sæ™hó‰´2™j­¶³¯ÚsÓßøý% N¾0Ú<7 äb–~˜³„:Ô{‚TyM=Ge=©û'ü®Ñ|䶇;N<îøÆíw©<rŽN9餸Á‡RÙþr'è²¥Køçßúü 7Èçjœä;ßú¾ÿ¯Þ [ëcÍCÿêŒäé ¤ÙÄ‚Áf‚§€UuNv$jõƒ±æºÿº¿[ž:0odøëI”¼5Ít.ÕQ§œƒwfor~žÿš ä'Êþµ¡Ì윫â,N@3ž€³·‹'‰å1ç!Ë$ ‡ëgpV° òï™qå³RÍæÊ€ŸÉU É8!ZA/áÚÖ/TØ7mˆÕèÀl+kG9̹gÚuZ\eÔ6èhÀT+׌ÍÌÞŒ&-/F½ñ–äÌd/.õ$P›ÕõªýÛÆ÷7¬«ÂÇ—¶FKqyNªd<®,BÅt¨¬x5Àî²€ž)h®üç{$awÞ}‡äEªÌØè¨¸ð ›<úhø®ë®«_þâ'#óçóE‡Vó˜ ÔëSÉ[ßp5þÇ€lNW´à+ãÓýA—,&Î…ŒZ Uó7$S‹`ÖnQïÿúÆdõƒkººú ÚÛñѨ}ˆe¬$É)“³ ½ÃôI%xÊP͓̽hsÙE˜EoÒòˆù"JíüB®ºBmùƒuÎÈ‘§®Énü@t×¶ZH¾  \öw‚4Ë8ÉWµÌ7æôÛf§6øÚ æ´‹Ï¢ç¼6)H6/ü¼Ïvþ ² RüùL‹ü9mR‚2ºÍÉÃhX·þ˜h* 7:Z°åͬØÄ&W!“Q»?“äÀÜys¯Ý¸êÉ/.»¡ ×¼c~4ZŠK’Ñ$ÚN2;­ËÚ„²e„ó¶[¬n´ªïß{¦{zû•³oÏ^|ÙK_:Ù×Û+Ï‚ášñ }믢×_õ†pÇþQ\|ѱͧ޲ ¼ <»¦')r„’t,—“Ô<úíT}Ç×7Lèž7O&Ö½ºÕl}/ éð R-B}„“–ýë8êžÍE¦“ÅŒ5竳®ôv>™$•¦N–©“¿B¡Rß#Ø"#·ˆ}éçgé!Ó×ÃVÍ :»Î:EZF¢*š!Â~–àáO´êͯ¯ƒ'6<Ý=Ø? Jµò·¢VôvÆØ”ÜÐǤC·-V]I[E*0HœûÙÏœÈsN9ÿ¾·çGùï:WÎáë·7[Å$/Í 2…2†ìu—KÓ Î1—|éìà”¹ª7Ò§2 Ëßfà\HAÌÞËá ÍBÈ¡ ½Pº÷€½šÉìÈîgõù@žl¬úئA酪Â(`N¨¸•ö¬¿Í‡Õü)À Ît.Y{&“¥…kÿ›æÎ›»vûúMŸäÿ¸©kéù'@¸¼·5½°&öÍ ™ TÏÛ%å Óáäï¶wl]÷ŒÌ}{÷à _ùJÇE]4}Ñ¥—Vg`0xÇí·%qáËøîä@²üÊSâU¯’°UfGÊÉii“æÝ`tÔ-û[Û¾õXí@s‚ÏNâVô®¨}Áú_¼tn«òùDéÚvç‰%>ï®tXú³¸¬ÿÅG‚=â#c(Åábo‚Ÿ©"…®Ä²˜É IsĬrlñ,‚ÁH‰ÕöyE… ø–ˆãZ*\ þX½Æ‹`‘)͵SŒŠj]ù=m]›‹jpºQÇÁʹ9…4»mç£ r²Ú)¤ÖUõ ƒÔ IáG{D-yÀ¤»¸‹KS@{&ãB®ÏúÌìáy7FõÖ?¬¿í‘WòÛ Ü7k:{;ccã°êtBfÍûÍ(Š¿Ô××Û5==õžË¯¼òE¿½óÎøÔ3Ôé–4¨ÑúÛk®e_ùÆ¿È1´Žûðùâ±ã«5`h¦Û˜ ‰ì €IcÉ7·%«ïz¬³'ì”Ñ£›[¦¬¥ºŽÓä.Oqí{ý©À3Ó¢È>TámÑ"§£‚›ñê3cN’¶ÓÈ6ZU$X½;Š ê¸5 }Ôôhë&ŒuÊ€¹“ÓÆ¼ZíˆÞbk«ýͰ֙³¶½Ð¹×(9tß”Ùtú›ˆù¬$ ÁoÑœˆ]»±Òdíð„kûÍ“t·w¶éѧ‚œv–µ{0ø L&ÏÑ“8y8¼¶pð(Ä1çMnÛ7GšÂŽòþ¹sæÞÅ8ûrÔj=©òiÂ!‡rù–-[¾qÙ‹¯xã?~ô#W¾ö5PíèÄõkV‡wÜñqýG?Ã7mÁáÓÿ_{WïGÅßìÝíùîì!kËÈe"%-ˆ‰‰6¢ˆèh(ùRa$ DCB*$DE”@> g¤ H;²dÇwñ÷víÇ̼÷æãl *äÏÛÝùØ÷ñ{oÞ{sîp㣗¢Û {gÒ†qFIÿ´Ë“ós¿Œ÷×~ï®?ڀ䅳;ПŒž©B×Ä­Nü–âÐT¬Ñ÷¦ôãì2à8ƒÐ}<Ìž~FÛv§è^kΘðE@hÒ«ÙZ´ÖÓá\³¡fJe:`S€ûYl ¥$¿ó-ZPÉ 7kÙ-“²ô»`JiÆ;-)þD¹czxR#å<0g“9ó€KpX~“εrP)<å«4Hƹ4dFÙy— l!ƒ«m¡GWg°ü•¦éå^·{¹ÕkëÊZ v©ûŠ*[EŸ8Ž/noo½øþ¥Ko~våÊA§ÛÉ 4wöžŽœô§oLï•èE4žŸ4Ë­ébÇjÜØÒbÛjüòÚ¦X¿~³Û†&$KK_æYöde)Dºèzøˆyö‘HLÓíNFà„ÑÜ+trx•ɰÜù$N†_ïé×ßIHÚ šN1#Òë°=îB…|¼$TÅœÈ\sž>g£Ä5€ 0wkçãÅd¦î¯~F%0ÝQÌ._‚» GúwÎÑÌ|U ³&Q ø_ÂzŒwýpð$‰ÇBèʺÅÛª6—Ð6žÖrõ³TÁ%=WõñßE¡mqxI’¼•KùðÎÝ»«õ-“óï¼–Þ]jÞëÀ|?‹ GåNÕn™lP¥$\øq(Ÿ|óÇÜýGp¦úûV¯ýñ4ÞPgJÚ'vUíTSU'¯Ýe‘Êé\ièã$Hj$c½P÷s\šr&¯rÖn€ŽiÁcÄ™®uõñ¼vTõVÓoÇãûMðŸ=O…šºVu§™]ÂQ±JððضÔùìAbf'=9Ï=³ÐB2x®>÷œž§ƒ€ÑaÄ(p'»½úÞ«°ùú©|°Í›úsYöŠ˜¤Š–é+? ãñÏ[÷o   Íï––—?ϦÓoñ"ððôY\Õ(Y4½FªzäHB8âsÖ:rñe¿zжö­wœBÊ™@iï8pÏŽÆQ!Ú^ži"àìeP9nU6÷  ª,äj̯*ôóf‚Ë#`dv•FG8'ŒR$I+°ßGh-ÀI–H1zZ0ö‡ èÍõ'¡4êèÇh'æJ<@§€GBú¿dasÛÛi+Ï€ñeraž—Ç»–±Gåù>w¦ãô"LòµÝtÊ€›"ïHf°¼•Áâà 46'bôëvüÛ­õÝàúüBÿëî©ÞÕâ(¢ÀuQä¸r<:Ìnø’±2߀NÕWÂÃ&…:ì²J¼ðì¹#6QS.Ïï¡z²:Ä{á”*“üjÛÔ³'F@11Cå®Vˆ B' ˆØ &‘ÔÇ„žèýѾ@kÉ£ØI>“‹sM…¼KˆmC4jFú}¬sÛ–¯JÖûF.‡*3e°PõçÒœ±nP)Ÿ¡DH«RœÜ_È—BÑo½&r ¡yÿ6±É¬Øn‡ ®ŠjkqÔB!Fá^"ve!yî‹ÁW?%ý«‹.¬ž>ŒûF:šdã!üù÷ÖÁ²&Àµ•••µ8Ž?ÝÛ+ÏZŽZ‚$?é ͈B+‚ L à –²§K ’ÚÚ³Àݸ6@g„jÃù3 í°®™€ÅˆLÌ‘¬[Käx¸ñ»¨¹ ´C–E%|šël3ˆ€Á  !€Tíyy–@3Œ9ÀNOK*³uãÌÂuafuÖ eÝÄÌ8ÁQ"Ÿ¯%¬xw’Ð<`%+)r!>•$ŒRJl~q›y8¾æ¤´“vÒþÿÿ#²d¸QIGrIEND®B`‚gvm-tools-23.11.0/docs/_static/logo.svg000066400000000000000000000507151452466721200176650ustar00rootroot00000000000000 gvm-tools-23.11.0/docs/conf.py000066400000000000000000000154051452466721200160520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # pylint: disable=invalid-name,redefined-builtin,wrong-import-position import os import sys sys.path.insert(0, os.path.abspath("..")) import gvmtools # -- Project information ----------------------------------------------------- project = "gvm-tools" copyright = "2018-2023, Greenbone AG" author = "Greenbone AG" version = gvmtools.get_version() release = version # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.githubpages", "sphinx.ext.napoleon", "myst_parser", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = [".md", ".rst"] # The master toctree document. master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ["build", "dist", ".venv"] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "furo" html_title = project html_favicon = "favicon.png" html_css_files = ["custom.css"] html_logo = "_static/logo.svg" html_static_path = ["_static"] repo_url = "https://github.com/greenbone/gvm-tools/" html_theme_options = { "source_repository": repo_url, "source_branch": "main", "source_directory": "src/", "light_css_variables": { "color-content-foreground": "#4C4C4C", "color-foreground-primary": "4C4C4C", "color-foreground-secondary": "#7F7F7F", "color-code-background": "#333333", "color-code-foreground": "#E5E5E5", "color-admonition-title--note": "#11AB51", "admonition-font-size": "0.9rem", "color-background-primary": "#FFFFFF", "color-background-secondary": "#F3F3F3", "color-sidebar-background": "#F3F3F3", }, "dark_css_variables": { "color-content-foreground": "#F3F3F3", "color-foreground-primary": "F3F3F3", "color-foreground-secondary": "#E5E5E5", "color-code-background": "#333333", "color-code-foreground": "#E5E5E5", "color-admonition-title--note": "#11AB51", "admonition-font-size": "0.9rem", "color-background-primary": "#171717", "color-background-secondary": "#4C4C4C", "color-sidebar-background": "#333333", }, "footer_icons": [ { "name": "GitHub", "url": repo_url, "html": """ """, "class": "", }, ], } autodoc_typehints = "description" autodoc_typehints_description_target = "documented" autodoc_class_signature = "separated" pygments_style = "zenburn" # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = "gvm-toolsdoc" # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( master_doc, "gvm-tools.tex", "gvm-tools Documentation", "Greenbone AG", "manual", ) ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, "gvm-tools", "gvm-tools Documentation", [author], 1)] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( master_doc, "gvm-tools", "gvm-tools Documentation", author, "gvm-tools", "One line description of project.", "Miscellaneous", ) ] # -- Options for Epub output ------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ["search.html"] # -- Extension configuration ------------------------------------------------- gvm-tools-23.11.0/docs/config.md000066400000000000000000000072041452466721200163400ustar00rootroot00000000000000(config)= # Configuration ```{versionchanged} 2.0 ``` By default, {program}`gvm-tools` {ref}`programs ` are evaluating the {file}`~/.config/gvm-tools.conf` [ini style](https://docs.python.org/3/library/configparser.html#supported-ini-file-structure) config file since version 2.0. The name of the used config file can be set using the {command}`-c/--config` command line switch. ## Settings The configuration file consists of sections, each led by a {code}`[section]` header, followed by key/value entries separated by a {code}`=` character. Whitespaces between key and value are ignored, i.e., {code}`key = value` is the same as {code}`key=value`. Currently five sections are evaluated: - {ref}`Main section ` - {ref}`GMP section ` - {ref}`Socket section ` - {ref}`TLS section ` - {ref}`SSH section ` (main-section)= ```{rubric} Main Section ``` The main section allows changing the default connection timeout besides defining variables for {ref}`interpolation`. ```ini [main] timeout = 60 ``` (gmp-section)= ```{rubric} GMP Section ``` The GMP section allows setting the default user name and password for [Greenbone Management Protocol (GMP)](https://community.greenbone.net/t/about-the-greenbone-management-protocol-gmp-category/83) based communication. ```ini [gmp] username=gmpuser password=gmppassword ``` (socket-config-section)= ```{rubric} Socket Section ``` This section is only relevant if the {ref}`socket connection type ` is used. The socket section allows setting the default path to the Unix Domain socket of {term}`gvmd`. It must not be confused with the socket path to the redis server used by {term}`openvas`. ```ini [unixsocket] socketpath=/run/gvmd/gvmd.sock ``` (tls-config-section)= ```{rubric} TLS Section ``` This section is only relevant if the {ref}`TLS connection type ` is used. The TLS section allows setting the default port, TLS certificate file, TLS key file and TLS certificate authority file. ```ini [tls] port=1234 certfile=/path/to/tls.cert keyfile=/path/to/tls.key cafile=/path/to/tls.ca ``` (ssh-config-section)= ```{rubric} SSH Section ``` This section is only relevant if the {ref}`SSH connection type ` is used. The SSH section allows setting the default SSH port, SSH user name and SSH password. ```ini [ssh] username=sshuser password=sshpassword port=2222 ``` ```{rubric} Comments ``` Configuration files may also contain comments by using the special character {code}`#`. A comment should be placed on a separate line above or below the setting. ```ini [main] # connection timeout of 120 seconds timeout=120 ``` (interpolation)= ```{rubric} Interpolation ``` The configuration file also supports the [interpolation of values](https://docs.python.org/3/library/configparser.html#interpolation-of-values). It is possible to define values in the {code}`[main]` section and reference them via a {code}`%()s` syntax. Additionally, values of the same section can be referenced. ```ini [main] my_first_name=John [gmp] my_last_name=Smith username=%(my_first_name)s%(my_last_name)s ``` Using this syntax will set the gmp user name setting to `JohnSmith`. ## Example Full example configuration: ```ini [main] # increased timeout to 5 minutes timeout = 300 tls_path=/data/tls default_user=johnsmith [gmp] username=%(default_user)s password=choo4Gahdi2e [unixsocket] socketpath=/run/gvmd/gvmd.sock [tls] port=1234 certfile=%(tls_path)s/tls.cert keyfile=%(tls_path)s/tls.key cafile=%(tls_path)s/tls.ca [ssh] username=%(default_user)s password=Poa8Ies1iJee ``` gvm-tools-23.11.0/docs/connectiontypes.md000066400000000000000000000055531452466721200203240ustar00rootroot00000000000000(connection-types)= # Connection Types Before being able to talk to a remote {term}`GMP` or {term}`OSP` server using one of the {ref}`provided command line clients `, the user has to choose a connection type for establishing a communication channel. Currently three different connection types are supported for being used as transport protocol: > - {ref}`TLS – tls ` > - {ref}`SSH – ssh ` > - {ref}`Unix Domain Socket – socket ` For the most common use case (querying {term}`gvmd` via {term}`GMP` on the same host) the {ref}`socket connection ` should be chosen. The other connection types require some setup and possible adjustments at the server side, if no {term}`Greenbone OS ` based system is used. (socket-connection-type)= ## Using a Unix Domain Socket The Unix Domain Socket is the default connection type of {term}`gvmd` in the {term}`Greenbone Community Edition`. It is only usable when running the client tool on the same host as the daemon. The location and name of the Unix Domain Socket provided by {term}`gvmd` highly depends on the environment and {term}`Greenbone Community Edition` installation. For current releases of the Greenbone Community Edition 21.4 and 22.4 the socket should be found at {file}`/run/gvmd/gvmd.sock`. For {term}`GOS 4 ` the path is either {file}`/run/openvas/openvasmd.sock` or {file}`/usr/share/openvas/gsa/classic/openvasmd.sock` and for {term}`GOS 5 ` and later the path is either {file}`/run/gvm/gvmd.sock` or {file}`/usr/share/gvm/gsad/web/gvmd.sock`. {term}`OSPd based scanners ` may be accessed via Unix Domain Sockets as well. The location and name of these sockets is configurable and depends on the used OSPd scanner implementation. (don-t-use-sudo)= ```{warning} Accessing a Unix Domain Socket requires sufficient Unix file permissions for the user running the {ref}`command line interface tool `. Please do not start a tool as **root** user via {command}`sudo` or {command}`su` only to be able to access the socket path. Instead, adjust the socket file permissions, e.g. by setting the {command}`--listen-owner`, {command}`--listen-group` or {command}`--listen-mode` arguments of {term}`gvmd`. ``` (tls-connection-type)= ## Using TLS The TLS connection type was the default connection type for remote and local communication in {term}`GOS 3.1 ` and before. It is used to secure the transport protocol connection of {term}`GMP` or {term}`OSP`. It requires to provide a TLS certificate file, TLS key file and TLS certificate authority file. (ssh-connection-type)= ## Using SSH Since {term}`GOS 4 `, SSH is the default connection type for secure remote communication with the manager daemon via {term}`GMP`. The {term}`Greenbone Management Protocol ` is tunneled through SSH and forwarded to {term}`gvmd`. gvm-tools-23.11.0/docs/favicon.png000066400000000000000000000501421452466721200167030ustar00rootroot00000000000000‰PNG  IHDR szzôgAMA± üa IiCCPsRGB IEC61966-2.1H‰SwX“÷>ß÷eVBØð±—l"#¬ÈY¢’a„@Å…ˆ VœHUÄ‚Õ Hˆâ (¸gAŠˆZ‹U\8îܧµ}zïííû×û¼çœçüÎyÏ€&‘æ¢j9R…<:ØOHÄɽ€Hà æËÂgÅðyx~t°?ü¯opÕ.$ÇáÿƒºP&W ‘à"ç RÈ.TÈȰS³d ”ly|B"ª ìôI>Ø©“ÜØ¢©™(G$@»`UR,À ¬@".À®€Y¶2G€½vŽX@`€™B,Ì 8CÍ L 0Ò¿à©_p…¸HÀ˕͗KÒ3¸•Ðwòðàâ!âÂl±Ba)f ä"œ—›#HçLÎ ùÑÁþ8?çæäáæfçlïôÅ¢þkðo">!ñßþ¼ŒNÏïÚ_ååÖpǰu¿k©[ÚVhßù]3Û  Z Ðzù‹y8ü@ž¡PÈ< í%b¡½0ã‹>ÿ3áoà‹~öü@þÛzðqš@™­À£ƒýqanv®RŽçËB1n÷ç#þÇ…ýŽ)Ñâ4±\,ŠñX‰¸P"MÇy¹R‘D!É•âé2ñ–ý “w ¬†OÀN¶µËlÀ~î‹XÒv@~ó-Œ ‘g42y÷“¿ù@+Í—¤ã¼è\¨”LÆD *°A Á¬ÀœÁ¼ÀaD@ $À<Bä€ ¡–ATÀ:ص° šá´Á18 çà\ëp`žÂ¼† AÈa!:ˆbŽØ"ΙŽ"aH4’€¤ éˆQ"ÅÈr¤©Bj‘]H#ò-r9\@úÛÈ 2ŠüмG1”²QÔu@¹¨ŠÆ sÑt4]€–¢kÑ´=€¶¢§ÑKèut}ŠŽc€Ñ1fŒÙa\Œ‡E`‰X&ÇcåX5V5cX7vÀžaï$‹€ì^„Âl‚GXLXC¨%ì#´ºW ƒ„1Â'"“¨O´%zùÄxb:±XF¬&î!!ž%^'_“H$É’äN !%2I IkHÛH-¤S¤>ÒiœL&ëmÉÞä²€¬ —‘·O’ûÉÃä·:ňâL ¢$R¤”J5e?奟2B™ ªQÍ©žÔªˆ:ŸZIm vP/S‡©4uš%Í›Cˤ-£ÕКigi÷h/étº ݃E—ЗÒkèéçéƒôw † ƒÇHb(k{§·/™L¦Ó—™ÈT0×2™g˜˜oUX*ö*|‘Ê•:•V•~•çªTUsU?Õyª T«U«^V}¦FU³Pã© Ô«Õ©U»©6®ÎRwRPÏQ_£¾_ý‚úc ²†…F †H£Tc·Æ!Æ2eñXBÖrVë,k˜Mb[²ùìLvûv/{LSCsªf¬f‘fæqÍƱàð9ÙœJÎ!Î Î{--?-±Öj­f­~­7ÚzÚ¾ÚbírííëÚïup@,õ:m:÷u º6ºQº…ºÛuÏê>Ócëyé õÊõéÝÑGõmô£õêïÖïÑ7046l18cðÌcèk˜i¸Ñð„á¨Ëhº‘Äh£ÑI£'¸&î‡gã5x>f¬ob¬4ÞeÜkVyVõV׬IÖ\ë,ëmÖWlPW› ›:›Ë¶¨­›­Äv›mßâ)Ò)õSnÚ1ìüì ìšìí9öaö%ömöÏÌÖ;t;|rtuÌvlp¼ë¤á4éĩÃéWgg¡só5¦KË—v—Sm§Š§nŸzË•åîºÒµÓõ£›»›Ü­ÙmÔÝÌ=Å}«ûM.›É]Ã=ïAôð÷XâqÌã§›§Âóç/^v^Y^û½O³œ&žÖ0mÈÛÄ[à½Ë{`:>=eúÎé>Æ>ŸzŸ‡¾¦¾"ß=¾#~Ö~™~üžû;úËýø¿áyòñN`Áå½³k™¥5»/ >B Yr“oÀòùc3Üg,šÑÊZú0Ì&LÖކÏß~o¦ùLé̶ˆàGlˆ¸i™ù})*2ª.êQ´Stqt÷,Ö¬äYûg½Žñ©Œ¹;Ûj¶rvg¬jlRlc웸€¸ª¸x‡øEñ—t$ í‰äÄØÄ=‰ãsçlš3œäšT–tc®åÜ¢¹æéÎËžwç|þü/÷„óû-G8Ï cHRMz&€„ú€èu0ê`:˜pœºQ< pHYs  šœ €IDATX…VkpUÕþÖÞû<î½¹y‘' IÄ"X±*ò®E‹XÔ2¶B§:Õ*"m­‡©¢uÚ:b‹E TlËÔÖªÕ:TÄ`Q“òV HHL äuonnÎ=gïÕ—Gx)ÚYsæìk}ë[k{QQQÎÓlîéìì\‘•ÝMDwêTpI±H ±õ\ƒ6ŒótÁD‹"‘ÈÓ}±„"AÙ™3+¯§Lëfð9¸ ú¼óe WŽÊcÉsõÿm“¶* ß8™~Z±÷¦û¾2¸R¤ÎÀ@S𑥝,„]×½ÔÊùà`B1ÉÚö ´¥ýûX :;cÌé§ÌÇ?àèßñ@GÃEMD)¿"ÓÐÈ,¶±ŒCúÆ2Øu±EÔ«_p(ðƒ³ÿ´C!% é(°a" °À0Ú€µ1Üã!sOŸÎ©BÔåmI ®Ìæ`\á ëݦgµà;úz“‰³)?…S%!°Êr¨ •‚““² mÀ @RŒ-V9\Q4T„2B"œe>Fµ6d.É5ÒQ73›_òQFÏÈ¥?Ä †ÁÄ`2`fèÞ²Ê ¾[0ã¢ûÃ×–Q[n ö¶ÅUCD,€€ˆB0(Œð’…þ›»k ó«gpVhg0“ðò\:ô¡ì9cî꫈„÷&ÚÑÓ‡º¶P6Y)2I q¬ 2éèÜÑ2Ü<ÙV{à !EÏñX:={â\“qï¢Ü)UkÔ‚± U‰p}¢‰X” K’Öt"9àJCJú½®æüÙc.ŽºÞKy8æVȆ²!my: Ò²ÀÄP®DjÊs®XnæŽ×¼c?­«…—¯ Ò §©?fÌ@Ô(‹"é²Ì» ~Í¡ »šÛÖj£=m42²3!”üj~,9käÝ“–©›†Û¿o’m¤‚±šÈAcO+DiÐG‡ŒØÈP€g BRhÁ ¨MyA¸8±½¥Ï±í÷Ç´Òy¿tt2uSåÂéÏóÕóöÆ¿@ϪíxdÞøÁÝwÁO-ÄÆx¡P( "¬€^_Ë¿î7bKiA)âØ´¸Ì(+’–°f‘«å~ºpV&Ì1ê‹bBiÎg] ˆíjÁÔœÑX¸èA„Â!@4áXšrW‚zõÒ^ÍŽ$3¹D˜áY5‡™7 R¬ §<ÕëÝÀÆÔIK¶h;#6|Ùˆ;¯[™<,g{[R*@xk ‹~üäñ䯼¼ìÏŠæ¥E²5©­?îÑzâ@¡¿ž¯` ‚-a:úùŸcrm=ø—Sm׫›^ùÈïýüÈÃʱþp&Ńg]úráwFØqd?úŒ7pvÆðög›°5^ê7áí`'üqyAºz±£Ã0ô5E ž!0—D!D ù}|ñ•™ÙŸ¶ þéÁiʱVK«ßÊ¥r+­ºpÁÄ«x‡ÑÖÛ E"`païå6à­žmø¬°Tž É”>®z)•òØu]œ €2Ý é†ÜÅ>ü>zƒÇàˆõÄ„X¬YÙY€T@A åû®å°b±Y6&ZÅžî+)¸è êâP¯G°ãH]4?;oò¤I¢lH™,,(=­]fÁOð{à54~õܪBéD\’ÿn†|©´«ýW"i~ްÚ‚çy8#Y¡(ˆ¤ E9KšGŒæH½™ò¼Z²§ß7ÿ¾eÓ&LÁ¶jÍÊš×ÍöŠ^)FçK;3DÔÚùÚˆ÷VsJ/óFÁ”dWÌø2N°8XDpÀ0>L¯¤à™@†,g~¤²ø©žëòàΕ‚<ìAn8Úܶ-=¿'ßá°ìB` á\œ¾òAðqì• „ОçýN%“ƒegê6µá°͉NìíÞŸ8¬ª9à$ĹøÉö é?÷ò+IEND®B`‚I,.¨ïËè¦À£ÖB”€™a(çLšÒWN-c×>›Á?n35š¾H4Ú I$ËkõPfâ_ǶÖâ¼)‡¥™H*bH:€$!0ºç® ÷<ɦ´ ÓÃTx²­e¡ýËpSúŒ#cëø EQ­x–ûp|דRøé8 ­™T%Ñ™’,LCriåÕyÃ$Úgï1&îÛ3„/Ê °(ac<›o¡áfì8>µ¨[.¿±u~kGqÚÏÙúoB£!8E¸;%Oö¥äÏQÒ)Q”Ã×=pl´kTß`n¨'7„µñ7ºÑu!Óz^çKh¤FÉc8 é°8¡5_ÎJ:§+’•‘¢ÛžÓ’OñxN¿ã‘CX þþþÿNz;dzÏB¾„F¾µÈív.HÒ[TV%5²TJJ6Û«¬¹œ: ¢7<_÷ÿ>甈§a-B[e¸áÂæ?ÃÛBãÛÛ)ù¶'¥ôÖYŸÐšËëL‹F²âЧx¬ßOp=›Æ¤añÚ2#¥ù½Zк1¼šÔ >6%4})ˆV¥·‘€¬³`+Ýžýµ•³ãeŽçÓâÞx ‹¡i¡Ëþz_A„½éyý(5S[ ¸–£«ŽêÚ;•úêêµeQ?ì_Kå]ɧŽÞ€qo/zfŒžáz"ÂRI_ -âþ‚¨oðgG²‘$xÏ›Èt:jþâ·©3÷®Ru õ_<¸Iõó,í5eCB‘„¬Qˆ +9w6Ã=HLJ<Êñ¼÷âqDXÂåüÉ ¶AE½3èšB ß8„ù‰>J ±-¯Ž¾Ô®‚£”©¨¬«¦2'4¾6´aI>ï†Dd•B܉¬q§Ø€cl]x$æÚR#B{0o NáS\Û¼ÈV©ãg¥uŒ§NܹD™‹ÛJ)¿0c"•=Ü®e…¤¯'–uóËUz9íƒ óU…RQÚÔ`®ÿ»ZU 5dŽG!n»q>;äÏï€dÊA‡·’/8,éYnÊ{³é¬éå¾7ͨU»91õ„ÖvÀ„‰ø ÄÐnªðן-R‡&tF§äÕx^„æÝ)€ºTv²¾Úñ›ñû¸jÛ_l퇬Ryv`ŽB£º`¡R˜2¥‹æ”¢³Û.Ù3A;¡Þ/_òm§[HL±í°Åà “9 …'B(ž‹ìsš² Þ.'Ù´Ê•Ï ¯<Ï àÂÞ’ $¤Ùk•@Cí÷ã[)kà…%ï«è¦ø¶ýàü‹±»-f™™°äÝüÒ% ˜jÖôª-+'Üa±Ô¦ã)EðXh³ 7Qà¨ä"¾„F4ÑÈ$!“ú'Gq,}—²ŽÜÈT—óImZW/ÖML²“¶]T6*¬mXgscójÕ5±šþö :D÷™Õ`­qƒðÅ# ˜æ0ÄÕÆáÉE0Zl’òæÆ-&àA n½ÚÔ¡iVö7o"øû©¬8uX/Aæ`åÑ-ÆVÁ€qÚÏÙ÷Q›¬;S)KN‡aîyøß÷d‡"Â=þÛC^$á~˜éó{þ8ðšÚQS*ð3mÂÓ‹ ËrÀ*i¾Y\Âý*€Ðªù7§5x7ÆÍ«Õ9RÇn] l…G7“šˆ‡#KÞJ:¦ÐªÏ[žÄTwÁ®»dƒBvÀ—Έy6Ð=îqSé¬åTØ€„Ëy†ß¡Aîí´Í„ÏX£Óéð#ž@&“i`c´(ptòs«IÙ8»’/¡%l™Å»õ‰µÏ“E/í¥¥ïQ¶Ä·;W“´m‰±#@Þ;h½aØÀ<…õ9Y¼Ïfˆopø5»––µg‰B£¼Îz}µê­•þøm¦~^ˆÓŒh"´ÿ ‘ß|ƒh\œWM+$ò¸r€ù2Ïý‚7mp\8U^[iSR›óõÆÙÏRÅEhK3ï*|u;,BfŽêó i¾MòIz¡YÏñ¥x®FtÚé^ o9Iíxš#b{-¾ñ y;þ‡q7…4¨{ðyäü†ç(¶]ºo ektÕGÔÓÞ%ï¥UjU—ÌœÍPh¶IßK¹`ÉϦpÕ²gKkmqÆ3-M¢¯æƒGãh_nލ÷ž+BhþÔY¿ÝG†1nØèŒª¶¡Î¦„FÛÕš–ÍôÒù±â^2³ ±Îî›HUŠŽ.»e/Ææ3ÅÇÌ]Ò‰Q{XIí市±ÔÑáëÌv™¡?Oµ}Ú|£¹33IuÚ0ÞB»,ýØ:¦ÍºöTžÍ­´o ãi %=“b ´yVàZ!%º—ª  ¬±Š_ïYÝŠ\’QÂ&ò(~¢…eâºÑ.t=Î.°ßÒA†)¿¾17„Zä¦ÉfØòU¶Mþ Õÿ³%ꌺ ˆ%Ùê„ök¯:y BÝ2¹—ÓAé¼®‡­e•1-•Ns†o­„ö[(ÉQû»—©gÃÁÁáÿ $㮬2E<°xâ~4ßî Á¥ 4Z/d>§tù3–Ùü¿À`â@ÆMºùü>›[ikNí0žUƒ_Z—в«åY3•0°9Oòaê¥ÖúÌŠgf´«wsR«7̾³ˆTzõÎ|>í°s”ï1kð"qGa¿¿þþÿ²àïŸÀŸ‹`ý ÿÜOB¦¤ æ®Áµ![´L§—a-š.ȳ2uÿZ´ä-sxÍçäm¥Í‹eÜÀÄZ"V“-Ad‡H ¯…UÑ/¸µ-´ZhwÊá[³%ïì¾ Êc [Û²TtõbÌÊ*üuÇ›ý,Ù“8 M§-‹Y=ùa Y«õÓøJ<ÁÕ®ÏCR;ˆY ª…; q×Õ'­›ñ€·r-èÆëþdÑ/ïìÆ¸±È [ã½5ߪJP­k¥5Èc<×ò ”Cð_òIZX’¤Û€ehô•'?ûcÖE>¿_øwÃo1*än˜^narCR9ˆw‹ì’ÊQCÅÿ:ù¶8š÷šá– &¿žÀ¸©"ƒ©*˜`K¹!2JLCWäÙA«yTüß’Îè²OlEÁ¤T„%SY#Y–ýØZÿ¼{>×ïèíw5OøÆ•³zSiÔÔÚwbà-#×îÓa‚¦?uZ?Þòî Ë,ÏwWeSB+«.§ÂÒº¶Wí1[d3MYÊXïí¬e凜èIíîN‚°o† úNÊ™YüzŸ#Ž^.—‘ÔÚ¡=gT?¥û5aãìÌWÞ0:°0 , FEÛ«0²Î–q´¾3†gèÆF´¦¨cBÉÞn¤rÒœsø>óX{Q‰;Ì*A4öqY‡|pè‚©öZ1¤NÈžm.²Ðå÷É÷§u?¦vÒÞARkû™Î0’k!#”ÞùLujàý·y¥XÞ×ò® ¨]0m¨ŒñýmZ¾1fÁLš@¶3åðSëÕ¥)\6qe7õÍå"V !uq¬:h!®Gô?Å3ôrÒ'BÁ[0…ØšVÊ–9õÁ¯õÎç1$IM¤™NÒ¤Þâ›Þ)À½*5‡¦3ÝB=F È‚¸vLjÉž?lf¡~s½kôZBëYi¯&ìà¢Õ‰âdÜl•òžØ íɘCÇ|sIM–ÈHjŽ¿÷§ÒͰִÞÎ÷ÔÄm¥ýj oLÅý0¡XÈC&êknô!ëÉ"×Ý´ˆŒ¶¹8zó<5õ“—)/7.’ õRâ°n^ŠÛÊyÐa@›™7úe""Bi©ì|Œí=B×O0™Ô4.Žå,ïý ²JëÚÇ:?»@h‘" ÀZõоšÀ{3zc:u¸ø,oÙ!2aŠÙ‚ß¿Ð:æ9-œ’Îé*ž8Ô¸ˆõœŸÙOüIƒ"ëë¤HØä¡ã#N²Zþû%" ¦d@¡È›«^í'â!ÃØžÐ"çtvº“¾m~ƒ‡¸v´õíÐPÁ›:¨K¨¾ô‚¸¤Ùü£MßëëØ>Ýòõ¯åŸê­±ôÏp[d†±h¤—|”&®àúXnõWÚÙÖ&ă…^¡u3)q |„ëúÞPz$”ÔbWM.çù.‘"vd„ìdpÓ ãh)›øOÒË·äL·x½jƒ_«ÒUøèô¢”DÇMtÁõ™]6q~~ ‡6GjM¤ýBì1¥V] èË'Ù7ŸÏµ…& B^ë-äs<‚vÃldëã)¸Ùk ãC±KÇ R°%Vkf;[´þtvo2sÖPòþ”ô…XʰæIT¤ö~ê^NRƒù›m•Ô»IÉSôê5¼žNs×ÞZÁß MÙ<§&¦U ÜGu`±MDÚ±n=Ú$£Fõ©= „šáÁëÇÙ®Ÿ1Ø•ÏôoJéAÉFtÖ H’‘t¤ù]ò}æã³AÉ¿A•9̉ŒÓw'È&ER²1á”ì¹Ny-d3¥/Æ>v1Å1[€º-‘Ë)c½?ÞNÛ4©ýIà@a„O»åð¯„Û|®é Z¤µëKÜoh\©_HlÖ‹£6ª»Å– ©G#+‘ ^ÞÛv¤Æb©)Ü)Ùèp2å›j‡ÖôÑršœÊíå÷…™ ú"](Sá´Ö~Ìâ5_ÁÆ527´Ïnðz&꺆» @² û:[|éuZJˆ‚mÓrùã9Û¶ÎdÐZf²¾[µ5ITíEƒ:qÉðT@<°®=ýÎ’%™¥Š(ÝlMü 7Íeɧéå|®Þ‡ÑxάZçHÿk iÔ@Œ-ÙÈrVÚ‡F³:¹*¸÷ÍŠ½Ìóºg·&D ™5_K2®ZáF­8LŪäöFâI2%ž%4ñHéyLò¸kßô¨ã[æj¸5ŽþîEŽÕ8::J‘Ì„\.O7lvöéU,”ЭÙÛÉQeþ¤™¤‡H< ‰ŒÆíäÊ:O684¯½þîÈ©‘§ûïæ”-"K­z H÷?WcTæÑØšœ3«NëïvÓJI¨•ÈJæYh „ª,nQ¾áå\›’3»Jh¶ÓÙÂÃS„vH$éÿDc8ØŸ¦ŸdË’Ñsíý襋8D&›[pò¿| ·ªæ×_;±Ö56è²53ë8èìd©ò X]+j¥®ƒÄÑö­µr‡¥åížØˆÐd¼ÏvN)¦æäÖÝÿ8H¡×:‹ÏËé€ J†Î{xxüR”ðò †73pdR¡PBë E¶’åHmb-îyé-Æ¢TÐ+³—{!sÔqùÏP©ÊUŽêBÖBŽF–ævºªƒ:ù»W¥mž['Pjz;û#Y´¥ ü!¡ëXK; $Ân\rPúµ„ꇕÖiâM![ñw;s K â~š 89à³zEÛ[õro'Æ1sÒç£öÙÕý€R®±{­Ö~§P¤!]ñܨ±Fnç˜Á]QŸG‚h«ÊâŒ"ޤ¯¯Y‡Ÿ2+í®–ï±TxƒÈˆíd,@³ºnÔýn§Ÿ[UZîÜZ!„–¼e6ôÅõE‚hËk~,±NhUZåQ;ì2‘=°– #(\üBÖ…˜•“îÇ­›Võý¨ÛžÝ;_2áZwáØþ ™‹ÝíÜd ·ÓÓJ¡pÙØ:‰óbì4ê¼ËîîÉ¢n¤¼©ÜBsK ¾’¸q&­‘öÉàB8s Ë;‘º˜“# oXÀÈ$Án'™‘ˆ„ÐNÖ²ìr¥£æê•LŽÜkwDß#`›©„æ™~1-wk¯tç Í NFö¢V«u4Tjpòv­IÝ2§Fx¶³’A{Н}œzœÅ:©–ΈÙoW÷difXS%‚ f5ªûÉ›fÕóJ° ˆ9/ÀRûŒÞJ3JãÇ|5RpMšµ¨àj%ë$Õo5ËÁª”½›oW÷ã±6›0Õã®^;t¿ÒÇ›9[ 7L¯²ªç™ÍC3&´†7Ê»g¸à8Z|Î ,²m¿½‘÷•Zc [¡T”‚†Ü»¹ßô$E¹ZC“ €¦QÅó˜öî76™ïŒ…ËÈbÍ@<ÀM¹Ñ"3ã¤mHY?³B(©¹®z  =WØOÙÈêé4gÚ›<k&4Äw&TᨺØâ^‚Œ}Çucõón„õÓøªä>@&ki¥-4¼I ú_Jh‘§àÁoÿ«V¡UcUò–g7$OÚ¨øºžÑž´%0$þ±qã¹ÒuòºÁg–2Y#@R(İÉ5: Tè Vòmƒ­PvKâ§§pÓµ£Ìp-ßÞPéÄ(V…G˜×Ñ%gªñ –WŸ¹ÈG<Ùì1þ7cŸá â„Sî ž7f4x;Y¿f—CüŒMšºÚžzCáîy|HM² ù Ÿëy[jîŸB“]WN|Àãú%HgÝÎ1F­P£’פ¥Oyk?âj½ƒäÂ[³§*Í+®æð}æ¾×T­x–ŠkL$$ožÍ§÷‚ÝXdr¸-†NhÜuu©›g×%5¯ÕÃñ ÛÙR†»³ ?®…Liµ½Ü‹§¿ìÁGÝVpò„ ]6=vG}\ïr\6¬+ßÍøHø¼rÓñÛÙú>£ÜOfÝ}Pµ;’×pJ®7©l–çê¡”slÀ)ŽëkïÉC¥sW"49¶už¾Þº}I)œ7³&  ÌÁî’'ÞÜJ·f‘¦Ÿó>Žëϵ÷äÀ!£äÀϗܾV<Æs-g–or´Ýõ‚òÐZ«7+†é'ãúÉöìv¦iX¿*\' kÒì,ãY¥÷ØÀ#vTi3 Œ’ƒBvpÜ—Z³H-Ð…ÔjÁûúo»$4ÐIûO µ¢dž”M³M…JÚ< ³v´žþ®çM…»v7¯ªy?ç½öx¤ TÂ1˜å¾Y– ¯Ó~–ëïµçŽ oHØ?û]ZdëE‚xØí¤¿ñÍÄClýžFÝPao—¤6·ë!Ö{ƒ’Í"5gí –ëÏ·×ä€ÊpˆŠ.È£"-w^ƒ 8ÚTà°MþEFàRHË»a[­*¶§¾ÏäÿÏ„¬-RH0‹Ô´ê+,²Cþöj¥}e$+´xìu!„±q2v{8 ¦žT9kOO’Ç{o·Û{öÏÄ“¬÷D%Mgö"• -ß^“A†ýži¡·pÞ.i¡ryªITš¢è*y=ñ¤½Þ;ÙKñGXïÍk ¦ßsBÅ[c+-Çpæ@ÂÓ•p¸¯Š¿=Ú?b÷(´êsæèîK¾ëyÏnIE­ãñœÔl“K:Ȇk?ÔétöØÕÍðføŽÔß²(xðÛéú¢û%E€n‡%ƾɻzo—|’^hq5é¸pÆì°lVóâiÎL™ÕíÑH{ \ÏÃ-5ÒÕTò†YBÜN,ßh‡ëǬ2Ešé ¨°ÂÊjbÈ„ì Ò×)Ó¸¾zõêœúúúbJ à5w¿øâ‹ý¼¬7Wíñö6¿€ÉM|b¡fòê´ ñ7Ò•AdŠÜΡöZh;Ú¨êk~SÖ=WCBh„¶–/¡¹ºº–¬_¿~#pÓCÊLÜ¿ÿ\|||1§ÞÚøÈÝíŠÔTÊ"Nõ’|Ù£ˆ>!Q.ÒEìýKì’ÐüýýÿÜ€²Â.a¾¼ÆÝuÞ0 ¡=Zÿ`ÞÚ´iÓ¶•uƒ² À--Ÿ={öqÖ÷†ÃkN…½¨\Ï÷R„d“ë®Ú£DPRÞ7h»<ÉgH:‘ÛrŽ×°[½4øåGÞØ¥ã‹øØÊ–÷FRhëlJtC,¦Årqq)9}úôÊŠxçwX³­Ò91‡ÚE}ZŠï6+gŽ/Bù†Æ^åÒH]Ú™æ7Ä=6ˆ×üN7,°mû¸Wãwñ鈌ŒFw@è -À0…ó {]!<çßÑgÐÞLâ¡›åEI¾Gw´U-…—âˆëSÃöœ¢¢¢Î@ ­ØÄZMXXØk÷„’v 6wÿʬ„y œî8iU„•g#À‡`½ KŽlögKÔ’ÃT:z=äê\7š¹0ΛŸëÐÉ ‰­µíëž—A½U:Z—Ê ;yÜÔìæÑ£GómÑè®uÛÙæ¬´¾AÛùÄŀДM• pNŸ…û¹qø>øoßÁz]M€kàdømÝåóa¬ª¶]s¦Q¦Ãònа@o¬¸¢ÙH46ÕC˪„V£}\=–§NÚhNÉÆÌ™3ÏðÙðsÛ¡¤cOMMM¼¬Òà2•µµµE¤Ü#==ý8£zGû³ÒnÛ} ßŒ2Ž·Z(qø»U¤mÛÀ–íÔ¬ìÏü ” ø›V6$‰Æ–VBœ7gíÕW_]onZHHH×û|óÍ7kô‡Rk9íD÷6¤Ž 1e®XZ5IÜ!;™¨ ÿw¸wZÄP>z…ÍJ ^7ŽÙ¥W®š"HI>HE±EÇÀô.9LÍÎOæOøû_…8Úms è!×swrrº ?Z"äº ´ÃI¤¤]l3O˜´ÎqoF ;™n¥ i!ããZ•žË®—¦f±Òd“"M‹$ø 騦À–K­ä€rÍµÒ w“뙃<Ñf¡×ÍÎÎÞÝ–Ü™$»›•c¼‹Ìd^Ö³E\%â½A¬ª¶q9ÓÙ•"ÜÍ øJ>NGò±Þ<ÎR…£Š³z}ܸqéçµË\ïõÓO?­z]ȦÒþ²gÄ_„+y;ù,[_-;àXþ™ÉôbÛ®€·kMzî\V+ÍoÍH涨»~¨9¤&Ö È:«AìšÃuÿ!^U ¢ŽÇ-Aj7nÜ8Åõ~GŽY'ð²uØ¢µIS¸¸‹œ³ë•ÎÚ,÷£~72“yVÚ7ÍojØý/²Z¨pHëÍ&l~Ë ´T!YÁåy®Ój>÷ÿ£>Zk)…³gÏrÎ3())Ù!°¾ˆ)(v™oyÿ¶R#ÚYÉ €Ü²nâ£'¢Üuµi[æÖ±‘šËϱ?´.žæ×ùê„,x+iógÍ2GàÉ.ZŠÔvîÜyˆë=¡TãŒÀ8#Q‚%ºKäó²ÄÑÞCV2?A0½ùMíôBöyS3ž¢3)d0Í.¤ôrB"²äaú¦ÇU¨‡*àsïA†{5eA€ÈãiŽ€ø=ø±GB® Œm–Ôñ·•Û©pÓ²Y­ˆ0+²’y šiO”6UZ˜¶ž3»’y*ÔLJ²œ}Ìô•xËT†Gz Yjýœ}ŸïH;??¿+À¥–"´²²²óà °¾gçÎ ½îªU«Ö¶5KMÖ‡µs  –R’ùVZZ‹aC.³õwê~ÈýàžëdRÃD•7|t[ùÞ÷ãÇ›ShÛÉ…ë<8E&C-]ºt·»»{%#Áe#™e$E”§èbj’Ég8T8"#Y&Aðsó›°jZ‰¹3ˆ^šÙ¤æ¬¡¾î„d)êŠ0÷ |ï{ß¾}w1°ã@5x¬{‚‚‚JMyÞüñïWźŒ5vòdqe?%K2KÉTy–òß,ÒøÚB²  y=æI±­ZE%o˜õÈԼ׌0=Ž0;FŸÁÔ§Ö“|ôÍíHû,ŸCÁö~EEE¾[Gk†Ê¿“õé˜Ûê-PÃÂòxÌàŠ dxš^˜Î=¶ãC+Í—E# W+ÆÌgË„䪜ԧ„’̸ Ò?¹fZgzìÚµë•êÝñÉ'Ÿ°Y? b˜Ê®ÿ¢a|l$´%H?V€D"ùߦ›ùÙЈn'ˆxÜK‡Ò.ȆwÞªðtÜÇ5“A>è>4}Wó¡%ÈeÆ @8’+›JD%«,Ehðžl„V}Æ­Þã)£¯Eãêo=±ìÿF²È€Ó¦ôRÖI!i¸¬¼¾è~I–¸2gû è}ÇÔú/BW|ñÅ›,¥‡ ê×`~/w÷üùó-EhPØË–鬒NŽjõV¨ÆâZ.}:2À9ÙÇzñ4EÓÍÖz;_"#îô]›§#©´†[ù]Ï{`mìT¸jrMuâ!¿]ýÖ[om¸ÙUs Šq]¼xñÀ¬Y³Iòú\ñññùB i™ÊѾýö[Æ ¢B©(•½›ßê1´—≔SÇ}©C[+ÚK¤-Ü…U“Ë©¹¯Š$c«õcæ}Ù¸ð<¹·ÓSÜJ:7,³ ³Ëæ²ÉíÛ·¿ð ÝÜÜ*~Žh2ßf2-9räQÆ÷‰ä³n­þeôJÜq„FÖTdëãoÍk}æeO‚V( ’• 2³«d³böƒxá6)g!ËÛÛ»xÙ²e«ÁÍ,6·b&ž¥Ë¦~–W^yÅìy•••ç¡íê:ËÌŠí0«ºÕ íµÄã|ž!„z>Gº± úà‰ êårÕoͨz$ë4–K^Ž;( wÏ5'Ff¸âââÎ>|˜ÌÈ,7—H.]ºt®wÜϓžž~˜XXæ|ŽË—/ïë°‚±¨vR¤(”le/Ä+²œÇ}É!²íH­EY€ìŸIû‘„,¸u¿*ïîŸ]W,Ed¤hâe[Aû0ñÒÌ%³»wïžË좹Ÿ Z¥Žši)ÖÿöÛoy`ÕÐv @ÿäè¾* Åç£H¸ ŠÇ}9ý4™Æ¶¤ÖB8Pä’‹ddv¿ß#é¤ðí }ùE¥È,++ë4ŽoK¨ÌEèõ¼1³#–ølsæÌ1«Õ Èð.dU°¨mäÂl… Qh¢ ÛÃó¹à¼ÎÖ)ëx×àATB[S!’“)šzÞ«l+HÌ[‚(àÙÔ•¿uëÖ-PJq‘² DbÄá™û¡˜÷ÊÕ«W·™™8Ì$ËóP6.B4ƒSä}‚v²öœþ¹Š¡Í¦uH­Q¶‡ÿF$)A£Îjå©>xº#¬ËËËë.ˆ4î8ÙVSäx¸C‚O÷êÕë¢Ȭ`ïÞ½$–WaNËÓ?þ¸ ÊOèÝMGõ9é‡iDChéþ{xÞŸ{X‹ÖŠJ¥Z#S£~ɪDÂâ±÷¼×èf &†ÀÀÀ ųwÍš5¹`íì™,ѾÄP4{\Í|ø3™È ¨jêÔ©; w™Û)Ÿ§È•QcLÞÉm‡ÃOYå" 'ÔÂÀšÝ<ïÓ¸ÇáÈ,­W3šl#›¹I‹Kˆ±G‘ÒQ}^1„††€u²š¼OP6ÀÇÏ¿ýöÛû!¹PcFàd^^Þ¢Ža‰ÏtæÌ™}žžžLYà و°í¢yÎK2JîZ¾b›€ÐâQÄAj¿YkZõyp«j¼í‹n7ª?NxTØŸ†Fòmª²ç*Z-?uêÔ>qwÞT"+ò:Heò=nÁÏUöúë¯3Zµ ­ê¢ôýTÑ I‰ùBøL—xÞ³* ´nÈ&≫ £-6" 3¡t›ýë%J­ú*Ï¢Ø[0òm‹‰ó3yó«^Þ¾}ûN|ä~jM!2xÝÃ×^{mû­[·öZB™¶9ŠŠŠò;uêt›uŠúÒŒrÑ<ã×NB‡o=á#H ¤#“ˆ3?[aÔª«O¨@"k1ˆö‘R§á5nÞ¼ysÙÉ€ öÑ-[¶ìxÿý÷w”õž1cÆäwëÖí¼««k¥™AÿÂM›6å€%uÛ Vãƒ÷Þ{o/K,¯R684OLÏX:1j¥'ÙYøÝEÄi­­ µÖ†lF2k$´Ÿ³ë•xÔíß¿ƒ% cï¸ÜÁ2KÕ»5­ÌÌÌ#.\ØAâöÖ0¡Tãhxx8sy‹V}EòvòYe±3œ <ïa -ê‚ìцJ;·¥¨nB°ô’¤ôã¼pmtŸâû÷ï°„‘îä^˜ºTii2ƒÀÿ Ðøßi Òe°Î*@Ìq[¦U貇LYO;£¬QT€ï}¼¿_2‡ˆA†?Àƒ¢Û(w·ûL¨ìù(N tó¯AY†ÙYMRé?xðà³–&³°°°‹ÐW¹ÍZe#ÖÙ±èèh¶þÑJÙ€`Qí'ɇiWˆÕ(à^Þ ­#²FÛÈ‚¾Á4ÚLúzÂA{%4éÇéw¡‘ú!¡‚ÜÏ ”`\ò¹gaB«ÿàƒ6uߊ×ëÛËZ§í›É'Dõl§Fäz¶-,L…âüŽ~Èm‡ÔLÊPÝ}ÖaY¦ýäþ’ ³JYK#t:]iyy¹ÙÃE ëxÊÝݽڒ„“œ®‚õ´ËŠ™W2®îÔß±N”‚Iñâr7É®“§øn?#ëÄK‘)Ú±}Ì8¥§g€Ý% äCŽsôgVáÞf!B«±™A#uÕ‚ 6ƒu×ZdIŒ’W_}u?Çg»éðU;ÐrTà=݆jmЇçÌ¢ÀZ-ùgâQ»Év~Öí¸&¬MßË—/_-&B *‚Ž…ˆËZÓ:ƒ¬é~ÈÈ–²~[›»©×@S«n ¼¯Ë aóoÈm7ø„Ñ€¾PÑfÙP¥Ÿî ÛföÙgw™[a¡ÕûúúÞ…`|aFFÆ©I“&í_¸páö;väBîike4›%1nÏœ9ó0gìÉßy¸›e"r7@Ae—ÐÙ`‰¿Gâ/È mÄ̆z“ñaûB|ä×^ í:Û9½ËeŽêû8àÍ%4Pxe%4 ¼’Ù³gïÌÏÏßI„$ Oµ 3b/”¬<àj—}‡˜žåÓ_ö¸ îf¾Ðä ÎhÖÚ`±OÁ¶Þ0”¬…Fu“í÷‡ |³†ôB-ÛEpiª&AÕ‚`âîF5Œjª•Q[[{cüøñÜ1(p7%o‰,»9=:j-…f“Ëaÿ?ƒ,ÐþðÎ`<þí¤‡c8k¹ä_IÇÚUràÓnå`{¬‡ÌfĤîvéÒŨQFÁí2;+2#zgׇ Âghq•|@È6Q=Çw’/*y«k4_»à‹\‰'¿ý—xŒfÝ*å]‡Ó.·RS»°v¼øâ‹Ì ܳgÏkͯçççwãܹs¹Öð Äïö€uÆ-õ­Q_•.H>#ªÚ³^AÛMQ 2û3œöEl?rmî§u¿Õæ;^Œ»Î1(øÔf™:ñ¼²†-²©£Fʳ†d·ugw@Ù–× ±¹›’Ó¯)ÝO˜`Õ¡ÍÀSngÉdÿ®è9Ž q¦e›|(–Aç€Nsíwܹs§ÉÉï¾ûîp³D@UNNÎz+k¬ ÂÅ‹@«W/w³¿ˆÜMÈÂËŸ ÝÉsBº‘Ê ;Úw64¨ùàcÚ¥VK¶MWTÖ/„ÕíLJJ:lj&òرcO ´ÏîA)ÇN±¸Ä¥/½ô?u º@Lî¦ä«î·”^N‡L,T> <ÙHl)¤«€=Ʀº#y+él›²Ò>ïþˆ£s J0öšB ‡}jÑjç\„Ìç)±$<ŽÁgº#ÀÝ,͗М®ù”iâgD•O4¢)¾6€GUv¹tbä¾6SÂè|†Côñ!dO˜’HLLÔ×»õéÓg¿™ÓË->[½zõN(!©ç™ÝM1­ôçì*yŒçvèOúPIâ O1‚ŽØžçõ˜à½ÓaYv¨“Ó¢¯ðV<$´6íÛo¿Õ»Ðô½ÑÌù˜MŒ=ú$¯çCGĤL+ù(í²ÊIsÎDwó2x'<½6b{‘×ÁpwïVf‚Ú y¿c\­Oæ¬áÇoS«éxxë­·vó©×Òמ‰HèSö\§<“ö ¸Î°þÛîïxòÖŽ³¹ÃfÛd2ihT·]óeýƒJ^O¸ ’@@¼‘þP,É  žê®l|Ä)EœO¾R§)´‘шE®KB`РAœ ݤÎKú^ÊÑXgK2K~Î{M¼ÿÄúŽÀÓ†°µÕ6U\èR)+ÀE¹¡tT_uÚbsþ4ë.¬µ°ÁúÖ!®ÊuJ×ܸq£5ãk ³}$Œ¸5õ²X¯\R‘/B{-á”B£¼nJLîû»Øê„h5ÂG°Ü>å[úÑ ë$|¾ì4õLÑÀûÓôz²@‡ë´Qi…ìæ¥pÆ ü¯É^Ž?"ª!(Y$Pk³:Ï£ ž*„(Ð8~ï ‘[1‰ÿuà1-¨qÍWLׂJý˜Ðn+îG0šo/ ©ãì rÙ.¦Fô§¿ì~Û„™›MƒP’dž$„XÉmQcƒqk)uxÔZ&bÑ.ºëÂØºóÖž·YTTt4**ª˜‡›~W2)"OT}¶3»˜2…¬‹ànÆáÉAˆÿv ц·™ÁáxT7¤æ|n¸†?ÐA÷;v,lœ¿iQ€„Ñ¥3fœâõ{‚ƘânÅ¢RÖèâ¹Ãe âž.„õ_xZm1¡H‚¿ðç)K[e`]ýfÁÛüÕRŸˆÑ‘Äwfu–ž?~‡%È f}žŸ7o^>Ü^¿«l ¸J5¤¤]Té4¦´¤‘˜[ ž D»¸…j2wò"#tª6,"ø\£·5c0pm§ÆÏÇ4¤eÄÙLqGk òGŒÁŸà5'¦]`@v½ì™Àí&„Êà¹ï€SÑí...ÿN¾µÉ€Y¢ºë³Æa0k`m†•ëKø÷á@4Þ6¶0ÝI”öBÙAÕÂ… ·—––#ådl‚’PrdÑ¢E{`&h™KTÖ+(—LRM©ÆgÝ®+\µÇLP¤ýd›qÇ#­ кá3–- à.Ì÷ÌÿüóÏ÷,^¼x?ˆHî×òŒÍ»äW'˜<@õä“ôB1 A‘ %…&`õÄ„@ˆ`e ±Yö–hž î”'¦ØXg…& A© ‰b‰ãB D8¤oZ›ÐäÞN$‹ºßUìl°¾oSˆ*-É‚þ ¡îBÜø ÖUÖ±ÎT7%“£v‹j ×»)ç¡ÏVh¶úXµQ¸Uˆ6ÒÆ–5a5(BÝv’ÆoÑÚ÷™”ñ>$³Y#°{cDÌj"m' ã“8àTÔÐiÎH^‰;&ªDÀ¨Î»`hõ-¿I,H$ÿ‹;hÛñµÉf$JäÏïS™†ôͤÓJgí ¿éï]®¦wÑ~ˆí¡Ù@y‚Ïv‡Åe¢±Î  WÑÑe·€'" ´„ô÷â@ ÚˆËÅÔJehÕÈ#=¶J¾êqS<%éEŠÎn»ÈÒ[ +Ÿ<ÑŽîWGZ™jGÕÙÈλˆ ­˜æFxy”ÇòPÄ2[È!ø´ûqCG’äÃÔK¢’z+ù´"Äm—2#ÜR 4?|„‚Ì¢lN ²±a»D @['™¹W`Ÿ&!½¯È |ª„ƒèé7Žq{2Pº ùL«Xeï§^§øn8àÙÛD™Ÿ&hН) sɇڤ‡Æ^ƒ¹ÛÈ4)YbÅFmÁä†ú q-4Ù ±ù%±o{Þ•N‰Ü+ñÜÞHdBTgëHðV<>1Á $º#¢+ï´]6«Ë½¤Ð²ìZN[–UVX!E2 XîžG2«&Hg7Mjz Í„)Ä6šg#û=IéÔ§‰EAý£äï0öîrc»Rƒ™íXDàòcX‘øT„¹Ä6ߤdÊ*kÖœãïø$„Å@f1Ô"3I陵ùf㨹¿áG V¼Q`ò$ Ò «ûdì#™Öt¨q^Ãï¤&®qŽÃ2¬uÿD«ÚþH*ÂCa%)£Æÿï&•Jµd^*Þ)@ @ @ @ @ @ @ @ @ @ @ Cüì. eIEND®B`‚gvm-tools-23.11.0/docs/glossary.md000066400000000000000000000123171452466721200167370ustar00rootroot00000000000000# Glossary ```{glossary} gvmd Management daemon shipped with {term}`GVM 10 ` and later. Abbreviation for **G**reenbone **V**ulnerability **M**anager **D**aemon. openvassd Scanner daemon used by {term}`GVM 10 ` and before. It listens for incoming connections via OTP and starts scan processes to run the actual vulnerability tests. It collects the results and reports them to the management daemon. With {term}`GVM 11 ` it has been converted into the {term}`openvas` application by removing the daemon and OTP parts. Abbreviation for **OpenVAS** **S**canner **D**aemon. openvas Scanner application executable to run vulnerability tests against targets and to store scan results into a redis database. Used in {term}`GVM 11 ` and later. It has originated from the {term}`openvassd` daemon. OSPd A [framework](https://github.com/greenbone/ospd) for several scanner daemons speaking the {term}`Open Scanner Protocol (OSP) `. ospd-openvas A {term}`OSP ` scanner daemon managing the {term}`openvas ` executable for reporting scan results to the management daemon {term}`gvmd`. Used in {term}`GVM 11 ` and later. GOS Greenbone Operating System, the operating system of the {term}`Greenbone Enterprise` Appliance. It provides the commercial version of the {term}`Greenbone Community Edition` with enterprise support and features. GSM Greenbone Security Manager (GSM) is the former name of our commercial product line {term}`Greenbone Enterprise` as hardware or virtual appliances. GMP The Greenbone Management Protocol (GMP) is an XML-based communication protocol provided by {term}`gvmd`. It provides an API to create, read, update and delete scans and vulnerability information. OSP The Open Scanner Protocol is an XML-based communication protocol provided by {term}`ospd-openvas`. It provides an API to start scans, get {term}`VT` information and to receive scan results. GVM The {term}`Greenbone Community Edition` consists of several services. This software framework has been named Greenbone Vulnerability Management (GVM) in the past. Greenbone Community Edition The Greenbone Community Edition covers the actual releases of the Greenbone application framework for vulnerability scanning and vulnerability management provided as open-source software to the community. The Greenbone Community Edition is adopted by external third parties, e.g., if the software framework is provided by a Linux distribution, it is build from the Greenbone Community Edition. It is developed as part of the commercial {term}`Greenbone Enterprise` product line. Sometimes referred to as the OpenVAS framework. GVM9 [Version 9](https://community.greenbone.net/t/gvm-9-end-of-life-initial-release-2017-03-07/211) of the {term}`Greenbone Community Edition`. Also known as **OpenVAS 9**. Used in the {term}`GOS 4 ` series. GVM10 [Version 10](https://community.greenbone.net/t/gvm-10-old-stable-initial-release-2019-04-05/208) of the {term}`Greenbone Community Edition`. Used in {term}`GOS 5 `. GVM11 [Version 11](https://community.greenbone.net/t/gvm-11-stable-initial-release-2019-10-14/3674) of the {term}`Greenbone Community Edition`. Used in {term}`GOS 6 `. GVM20.08 [Version 20.08](https://community.greenbone.net/t/gvm-20-08-stable-initial-release-2020-08-12/6312) of the {term}`Greenbone Community Edition`. Used in {term}`GOS 20.08 `. First version using [Calendar Versioning](https://calver.org/) GVM21.4 [Version 21.4](https://community.greenbone.net/t/gvm-21-04-oldstable-initial-release-2021-04-16/8942) of the {term}`Greenbone Community Edition`. Used in {term}`GOS 21.04 `. GVM22.4 [Version 22.4](https://community.greenbone.net/t/greenbone-community-edition-22-4-stable-initial-release-2022-07-25/12638) of the {term}`Greenbone Community Edition`. Used in {term}`GOS 22.04 `. Greenbone Enterprise Greenbone Enterprise is the [Greenbone product line for on-premises solutions](https://www.greenbone.net/en/product-comparison/). Included are virtual or hardware Greenbone Enterprise Appliances with the {term}`Greenbone Operating System (GOS)`, the {term}`Greenbone Community Edition` framework, and the {term}`Greenbone Enterprise Feed`. Greenbone Community Feed The Greenbone Community Feed is the freely available feed for vulnerability information licensed as open-source. It contains basic scan configurations, report formats, port lists and the most important vulnerability tests. The provided data is updated on a daily basis with no warranty or promises for fixes or completeness. Greenbone Enterprise Feed The Greenbone Enterprise Feed is the commercial feed provided by Greenbone Networks containing additional enterprise features like vulnerability tests for enterprise products, policy and compliance checks, extensive reports formats and special scan configurations. The feed comes with a service-level agreement ensuring support, quality assurance and availability. VT Vulnerability Tests (VTs), also known as Network Vulnerability Tests (NVTs), are scripts written in the NASL programming language to detect vulnerabilities at remote hosts. ``` gvm-tools-23.11.0/docs/index.md000066400000000000000000000012141452466721200161750ustar00rootroot00000000000000# gvm-tools: Remote Control of Your Greenbone Vulnerability Manager (GVM) The Greenbone Vulnerability Management Tools, or {program}`gvm-tools` in short, are a collection of tools that help with controlling a [Greenbone Enterprise Appliance](https://www.greenbone.net/en/product-comparison/) and Greenbone Community Edition installations remotely. Essentially, the tools aid accessing the communication protocols {term}`Greenbone Management Protocol (GMP) ` and {term}`Open Scanner Protocol (OSP) `. ```{note} {program}`gvm-tools` requires at least Python 3.9. ``` ```{toctree} install connectiontypes tools config scripting glossary ``` gvm-tools-23.11.0/docs/install.md000066400000000000000000000054011452466721200165360ustar00rootroot00000000000000(installation)= # Installation of gvm-tools ```{note} The current universally applicable installation process for Python is using the [pipx] installer tool in conjunction with the [pypi] package repository. ``` ## Installing the Latest Stable Release of gvm-tools For installing the latest stable release of {program}`gvm-tools` from the [Python Package Index](https://pypi.org/), [pipx], [pip] or [poetry] can be used. (using-pipx)= ### Using pipx You can install the latest release of **gvm-tools** using [pipx]. ```shell python3 -m pipx install gvm-tools ``` On Debian based Distributions like Ubuntu and Kali [pipx] itself can be installed via ```shell sudo apt install pipx ``` ### Using pip ```{note} The {command}`pip install` command does no longer work out-of-the-box in newer distributions like Ubuntu 23.04 or Debian 12 because of [PEP 668](https://peps.python.org/pep-0668). Please use the {ref}`installation via pipx ` instead. ``` The following command installs {program}`gvm-tools` system wide: ```shell python3 -m pip install gvm-tools ``` A system wide installation usually requires admin permissions. Therefore, {program}`gvm-tools` may only be installed for the [current user](https://docs.python.org/3/library/site.html#site.USER_BASE) via: ```shell python3 -m pip install --user gvm-tools ``` For further details and additional installation options, please take a look at the documentation of [pip]. ### Using poetry To avoid polluting the system and user namespaces with Python packages and to allow installing different versions of the same package at the same time, [python virtual environments](https://docs.python.org/3/library/venv.html) have been introduced. [poetry] is a tool combining the use of virtual environments and handling dependencies elegantly. Please follow the [poetry documentation](https://python-poetry.org/docs/#installation) to install the tool. To install {program}`gvm-tools` into a virtual environment, defaulting into the folder `.venv`, the following command need to be executed: ```shell poetry install ``` Afterwards, the environment containing the installed {program}`gvm-tools` can be activated by running: ```shell poetry shell ``` It is also possible to run single commands within the virtual environment: ```shell poetry run gvm-cli -h ``` ## Getting the Source The source code of **python-gvm** can be found at [GitHub](https://github.com/greenbone/python-gvm). To clone this public repository and install from source for the current user run the following commands: ```shell git clone git://github.com/greenbone/gvm-tools.git && cd gvm-tools python3 -m pip install -e . ``` [pip]: https://pip.pypa.io/en/stable/ [pipx]: https://pypa.github.io/pipx/ [poetry]: https://python-poetry.org/ [pypi]: https://pypi.org/ gvm-tools-23.11.0/docs/requirements.txt000066400000000000000000000000141452466721200200250ustar00rootroot00000000000000sphinx furo gvm-tools-23.11.0/docs/scripting.md000066400000000000000000000236531452466721200171030ustar00rootroot00000000000000(scripting)= # Scripting (xml-scripting)= ## XML Scripting ```{note} XML scripting via {program}`gvm-cli` should only be considered for simpler use cases. {ref}`Greenbone Management Protocol (GMP) or Open Scanner Protocol (OSP) scripts ` are often more powerful and easier to write. ``` Scripting via {program}`gvm-cli` is directly based on [GMP](https://docs.greenbone.net/API/GMP/gmp-22.04.html) and [OSP](https://docs.greenbone.net/API/OSP/osp-22.04.html). Both protocols make use of XML command requests and corresponding responses. A typical example for using GMP is the automatic scan of a new system. In the example below, it is assumed that an Intrusion Detection System (IDS) that monitors the systems in the Demilitarized Zone (DMZ) and immediately discovers new systems and unusual, new TCP ports is in use. If such an event is being discovered, the IDS should automatically initiate a scan of the new system. This can be done with the help of a script. 1. Starting point is the IP address of the new suspected system. For this IP address, a target needs to be created on the {term}`Greenbone Enterprise` Appliance. If the IP address is saved in the environment variable {envvar}`IPADDRESS` by the IDS, the respective target can be created: ```shell > gvm-cli socket --xml "Suspect Host"$IPADDRESS"" ``` See {command}`create_target` command for all [details](https://docs.greenbone.net/API/GMP/gmp-22.04.html#command_create_target). 2. Create a task using the default *Full and Fast* scan configuration with UUID {token}`daba56c8-73ec-11df-a475-002264764cea` and the previously generated target: ```shell > gvm-cli socket --xml "Scan Suspect Host" ``` See {command}`create_task` command for all [details](https://docs.greenbone.net/API/GMP/gmp-22.04.html#command_create_task). 3. Start the task using the UUID return from the last response: ```shell > gvm-cli socket --xml "" 0f9ea6ca-abf5-4139-a772-cb68937cdfbb ``` See {command}`start_task` command for all [details](https://docs.greenbone.net/API/GMP/gmp-22.04.html#command_start_task). → The task is running. The response returns the UUID of the report which will contain the results of the scan. 4. Display the current status of the task: ```shell > gvm-cli socket --xml "" ... Running98 ... ... ``` See {command}`get_tasks` command for all [details](https://docs.greenbone.net/API/GMP/gmp-22.04.html#command_get_tasks). → As soon as the scan is completed, the full report is available and can be displayed. 5. Display the full report: ```shell > gvm-cli socket --xml "" ... ``` See {command}`get_reports` command for all [details](https://docs.greenbone.net/API/GMP/gmp-22.04.html#command_get_reports). 6. Additionally, the report can be downloaded in a specific report format instead of plain XML. List all report formats: ```shell > gvm-cli socket --xml "" ... ``` See {command}`get_report_formats` command for all [details](https://docs.greenbone.net/API/GMP/gmp-22.04.html#command_get_report_formats). 7. Download the report in the desired format. Example: download the report as a PDF file: ```shell > gvm-cli socket --xml "" ``` ```{note} Please be aware that the PDF is returned as [base64 encoded](https://en.wikipedia.org/wiki/Base64) content of the *\\* element in the XML response. ``` (gvm-scripting)= ## GVM Scripts ```{versionchanged} 2.0 ``` Scripting of {term}`Greenbone Management Protocol (GMP) ` and {term}`Open Scanner Protocol (OSP) ` via {program}`gvm-script` or interactively via {program}`gvm-pyshell` is based on the [python-gvm] library. Please take a look at [python-gvm] for further details about the API. ```{note} By convention, scripts using {term}`GMP` are called *GMP scripts* and are files with the ending {file}`.gmp.py`. Accordingly, *OSP scripts* with the ending {file}`.osp.py` are using {term}`OSP`. Technically both protocols could be used in one single script file. ``` The following sections are using the same example as it was used in {ref}`XML Scripting ` where it was assumed that an Intrusion Detection System (IDS) that monitors the systems in the Demilitarized Zone (DMZ) and immediately discovers new systems and unusual, new TCP ports is in use. The IDS will provide the IP address of a new system to the GMP script. 1. Define the function that should be called when the script is started by adding the following code to a file named {file}`scan-new-system.gmp.py`: ```python3 if __name__ == '__gmp__': main(gmp, args) ``` → The script is only called when being run as a GMP script. The {dfn}`gmp` and {dfn}`args` variables are provided by {program}`gvm-cli` or {program}`gvm-pyshell`. {dfn}`args` contains arguments for the script, e.g., the user name and password for the GMP connection. The most important aspect about the example script is that it contains the {dfn}`argv` property with the list of additional script specific arguments. The {dfn}`gmp` variable contains a connected and authenticated instance of a [Greenbone Management Protocol class](https://greenbone.github.io/python-gvm/api/gmp.html#module-gvm.protocols.gmp). 2. The main function begins with the following code lines: ```python3 def main(gmp: Gmp, args: Namespace) -> None: # check if IP address is provided to the script # argv[0] contains the script name if len(args.argv) <= 1: print('Missing IP address argument') return 1 ipaddress = args.argv[1] ``` → The main function stores the first argument passed to the script as the {envvar}`ipaddress` variable. 3\. Add the logic to create a target, create a new scan task for the target, start the task and print the corresponding report ID: ```python3 ipaddress = args.argv[1] target_id = create_target(gmp, ipaddress) full_and_fast_scan_config_id = 'daba56c8-73ec-11df-a475-002264764cea' openvas_scanner_id = '08b69003-5fc2-4037-a479-93b440211c73' task_id = create_task( gmp, ipaddress, target_id, full_and_fast_scan_config_id, openvas_scanner_id, ) report_id = start_task(gmp, task_id) print( f"Started scan of host {ipaddress}. Corresponding report ID is {report_id}" ) ``` For creating the target from an IP address (DNS name is also possible), the following is used. Since target names must be unique, the current date and time in ISO 8601 format (YYYY-MM-DDTHH:MM:SS.mmmmmm) is added: ```python3 def create_target(gmp, ipaddress): import datetime # create a unique name by adding the current datetime name = f"Suspect Host {ipaddress} {str(datetime.datetime.now())}" response = gmp.create_target(name=name, hosts=[ipaddress]) return response.get('id') ``` The function for creating the task is defined as: ```python3 def create_task(gmp, ipaddress, target_id, scan_config_id, scanner_id): name = f"Scan Suspect Host {ipaddress}" response = gmp.create_task( name=name, config_id=scan_config_id, target_id=target_id, scanner_id=scanner_id, ) return response.get('id') ``` Finally, the function to start the task and get the report ID: ```python3 def start_task(gmp, task_id): response = gmp.start_task(task_id) # the response is # id return response[0].text ``` For getting a PDF document of the report, a second script {file}`pdf-report.gmp.py` can be used: ```python3 from base64 import b64decode from pathlib import Path def main(gmp: Gmp, args: Namespace) -> None: # check if report id and PDF filename are provided to the script # argv[0] contains the script name if len(args.argv) <= 2: print('Please provide report ID and PDF file name as script arguments') return 1 report_id = args.argv[1] pdf_filename = args.argv[2] pdf_report_format_id = "c402cc3e-b531-11e1-9163-406186ea4fc5" response = gmp.get_report( report_id=report_id, report_format_id=pdf_report_format_id ) report_element = response[0] # get the full content of the report element content = "".join(report_element.itertext()) # convert content to 8-bit ASCII bytes binary_base64_encoded_pdf = content.encode('ascii') # decode base64 binary_pdf = b64decode(binary_base64_encoded_pdf) # write to file and support ~ in filename path pdf_path = Path(pdf_filename).expanduser() pdf_path.write_bytes(binary_pdf) print('Done.') if __name__ == '__gmp__': main(gmp, args) ``` ## Example Scripts All example scripts can be found at [GitHub](https://github.com/greenbone/gvm-tools/tree/main/scripts). [python-gvm]: https://greenbone.github.io/python-gvm/ gvm-tools-23.11.0/docs/tools.md000066400000000000000000000146361452466721200162420ustar00rootroot00000000000000(tools)= # Provided Tools Currently, {program}`gvm-tools` comes with three command line interface programs: - {ref}`gvm-cli ` - {ref}`gvm-script ` - {ref}`gvm-pyshell ` All of these programs are clients communicating either via {term}`Greenbone Management Protocol (GMP) ` or {term}`Open Scanner Protocol (OSP) `. The {ref}`connection ` is established using a {ref}`TLS `, {ref}`SSH ` or {ref}`Unix Domain Socket ` communication channel. All tools take several arguments and parameters. {program}`gvm-tools` allows setting defaults for most of these in a configuration file. See {doc}`config` for details about the possible settings and capabilities. (gvm-cli)= ## gvm-cli {program}`gvm-cli` is a low level tool which offers sending and receiving commands and responses for the XML-based {term}`GMP ` and {term}`OSP ` directly via the command line. It is intended for {ref}`simple scripting ` via shell. ```shell > gvm-cli --help usage: gvm-cli [-h] [-c [CONFIG]] [--log [{DEBUG,INFO,WARNING,ERROR,CRITICAL}]] [--timeout TIMEOUT] [--gmp-username GMP_USERNAME] [--gmp-password GMP_PASSWORD] [-V] CONNECTION_TYPE ... optional arguments: -h, --help show this help message and exit -c [CONFIG], --config [CONFIG] Configuration file path (default: ~/.config/gvm- tools.conf) --log [{DEBUG,INFO,WARNING,ERROR,CRITICAL}] Activate logging (default level: None) --timeout TIMEOUT Response timeout in seconds, or -1 to wait indefinitely (default: 60) --gmp-username GMP_USERNAME Username for GMP service (default: '') --gmp-password GMP_PASSWORD Password for GMP service (default: '') -V, --version Show version information and exit connections: valid connection types CONNECTION_TYPE Connection type to use ssh Use SSH to connect to service tls Use TLS secured connection to connect to service socket Use UNIX Domain socket to connect to service ``` Examples: ```shell > gvm-cli socket --xml "" 7.0 > gvm-cli socket --xml "" ... > gvm-cli socket < commands.xml ``` (gvm-script)= ## gvm-script ```{versionadded} 2.0 ``` {program}`gvm-script` allows running {ref}`gvm scripts ` which are Python based scripts calling the [python-gvm API]. Depending on the {command}`--protocol` argument a global gmp or osp object is passed to the script. ```{note} {program}`gvm-script` is only available with {program}`gvm-tools` version 2.0 and beyond. ``` ```shell usage: gvm-script [-h] [-c [CONFIG]] [--log [{DEBUG,INFO,WARNING,ERROR,CRITICAL}]] [--timeout TIMEOUT] [--gmp-username GMP_USERNAME] [--gmp-password GMP_PASSWORD] [-V] [--protocol {GMP,OSP}] CONNECTION_TYPE ... optional arguments: -h, --help show this help message and exit -c [CONFIG], --config [CONFIG] Configuration file path (default: ~/.config/gvm- tools.conf) --log [{DEBUG,INFO,WARNING,ERROR,CRITICAL}] Activate logging (default level: None) --timeout TIMEOUT Response timeout in seconds, or -1 to wait indefinitely (default: 60) --gmp-username GMP_USERNAME Username for GMP service (default: '') --gmp-password GMP_PASSWORD Password for GMP service (default: '') -V, --version Show version information and exit --protocol {GMP,OSP} Service protocol to use (default: GMP) connections: valid connection types CONNECTION_TYPE Connection type to use ssh Use SSH to connect to service tls Use TLS secured connection to connect to service socket Use UNIX Domain socket to connect to service ``` (gvm-pyshell)= ## gvm-pyshell {program}`gvm-pyshell` is a tool to use the [python-gvm API] interactively. Running the tool will open a Python interpreter in the [interactive mode](https://docs.python.org/3/tutorial/interpreter.html#interactive-mode) providing a global gmp or osp object depending on the {command}`--protocol` argument. The interactive shell can be exited with: - {kbd}`Ctrl+D` on Linux or - {kbd}`Ctrl+Z` on Windows ```shell > gvm-pyshell --help usage: gvm-pyshell [-h] [-c [CONFIG]] [--log [{DEBUG,INFO,WARNING,ERROR,CRITICAL}]] [--timeout TIMEOUT] [--gmp-username GMP_USERNAME] [--gmp-password GMP_PASSWORD] [-V] [--protocol {GMP,OSP}] CONNECTION_TYPE ... optional arguments: -h, --help show this help message and exit -c [CONFIG], --config [CONFIG] Configuration file path (default: ~/.config/gvm- tools.conf) --log [{DEBUG,INFO,WARNING,ERROR,CRITICAL}] Activate logging (default level: None) --timeout TIMEOUT Response timeout in seconds, or -1 to wait indefinitely (default: 60) --gmp-username GMP_USERNAME Username for GMP service (default: '') --gmp-password GMP_PASSWORD Password for GMP service (default: '') -V, --version Show version information and exit --protocol {GMP,OSP} Service protocol to use (default: GMP) connections: valid connection types CONNECTION_TYPE Connection type to use ssh Use SSH to connect to service tls Use TLS secured connection to connect to service socket Use UNIX Domain socket to connect to service ``` Example: ```python > gvm-pyshell socket GVM Interactive Console 2.0.0 API 1.0.0. Type "help" to get information about functionality. >>> gmp.get_protocol_version() '7' >>> gmp.get_version().get('status') '200' >>> gmp.get_version()[0].text '7.0' >>> [t.find('name').text for t in tasks.xpath('task')] ['Scan Task', 'Simple Scan', 'Host Discovery'] ``` [python-gvm api]: https://greenbone.github.io/python-gvm/ gvm-tools-23.11.0/gvmtools/000077500000000000000000000000001452466721200154705ustar00rootroot00000000000000gvm-tools-23.11.0/gvmtools/__init__.py000066400000000000000000000021561452466721200176050ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2018-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ Main module of gvm-tools. """ def get_version() -> str: """Returns the version of gvm-tools as a string in `PEP440`_ compliant format. Returns: str: Current version of gvm-tools .. _PEP440: https://www.python.org/dev/peps/pep-0440 """ # pylint: disable=import-outside-toplevel from .__version__ import __version__ return __version__ gvm-tools-23.11.0/gvmtools/__version__.py000066400000000000000000000001501452466721200203170ustar00rootroot00000000000000# pylint: disable=invalid-name # THIS IS AN AUTOGENERATED FILE. DO NOT TOUCH! __version__ = "23.11.0" gvm-tools-23.11.0/gvmtools/cli.py000066400000000000000000000103741452466721200166160ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2018-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import logging import sys import time from gvm.errors import GvmError from gvm.protocols.gmp import Gmp from gvm.protocols.latest import Osp from gvm.transforms import CheckCommandTransform from gvm.xml import pretty_print, validate_xml_string from gvmtools.helper import authenticate, do_not_run_as_root from gvmtools.parser import ( PROTOCOL_GMP, PROTOCOL_OSP, create_connection, create_parser, ) logger = logging.getLogger(__name__) HELP_TEXT = """ Command line tool to access services via GMP (Greenbone Management Protocol) and OSP (Open Scanner Protocol) Examples: gvm-cli socket --help gvm-cli tls --help gvm-cli ssh --help gvm-cli socket --xml "" gvm-cli socket --xml "myusermypass" gvm-cli socket --gmp-username foo --gmp-password foo myfile.xml The protocol specifications for GMP and OSP are available at: https://docs.greenbone.net/index.html#api_documentation""" # noqa: E501 def _load_infile(filename=None): if not filename: return None with open(filename, encoding="utf-8") as f: return f.read() def main(): do_not_run_as_root() parser = create_parser(description=HELP_TEXT, logfilename="gvm-cli.log") parser.add_protocol_argument() parser.add_argument("-X", "--xml", help="XML request to send") parser.add_argument( "-r", "--raw", help="Return raw XML", action="store_true", default=False ) parser.add_argument( "--pretty", help="Pretty format the returned xml", action="store_true", default=False, ) parser.add_argument( "--duration", action="store_true", help="Measure command execution time" ) parser.add_argument( "infile", nargs="?", help="File to read XML commands from." ) args = parser.parse_args() # If timeout value is -1, then the socket has no timeout for this session if args.timeout == -1: args.timeout = None if args.xml is not None: xml = args.xml else: try: xml = _load_infile(args.infile) except IOError as e: print(e, file=sys.stderr) sys.exit(1) # If no command was given, program asks for one if len(xml) == 0: xml = input() try: validate_xml_string(xml) except GvmError as e: print(e, file=sys.stderr) sys.exit(1) connection = create_connection(**vars(args)) if args.raw: transform = None else: transform = CheckCommandTransform() if args.protocol == PROTOCOL_OSP: protocol_class = Osp else: protocol_class = Gmp try: with protocol_class(connection, transform=transform) as protocol: if args.protocol == PROTOCOL_GMP: # Ask for password if none are given authenticate(protocol, args.gmp_username, args.gmp_password) if args.duration: starttime = time.time() result = protocol.send_command(xml) if args.duration: duration = time.time() - starttime print(f"Elapsed time: {duration} seconds") elif args.pretty: pretty_print(result) else: print(result) except Exception as e: # pylint: disable=broad-except logger.error(e) sys.exit(1) sys.exit(0) if __name__ == "__main__": main() gvm-tools-23.11.0/gvmtools/config.py000066400000000000000000000053541452466721200173160ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2019-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ Module to store gvm-tools configuration settings """ import configparser import logging from gvm.connections import ( DEFAULT_GVM_PORT, DEFAULT_HOSTNAME, DEFAULT_SSH_PORT, DEFAULT_UNIX_SOCKET_PATH, ) logger = logging.getLogger(__name__) class Config: def __init__(self): self._config = configparser.ConfigParser(default_section="main") self._config = {} self._config["gmp"] = dict(username="", password="") self._config["ssh"] = dict( username="gmp", password="gmp", port=DEFAULT_SSH_PORT, hostname=DEFAULT_HOSTNAME, ) self._config["unixsocket"] = dict(socketpath=DEFAULT_UNIX_SOCKET_PATH) self._config["tls"] = dict( port=DEFAULT_GVM_PORT, hostname=DEFAULT_HOSTNAME ) self._defaults = dict() def load(self, filepath): path = filepath.expanduser() config = configparser.ConfigParser(default_section="main") with path.open() as f: config.read_file(f) if "Auth" in config: logger.warning( "Warning: Loaded config file %s contains deprecated 'Auth' " "section. This section will be ignored in future.", str(filepath), ) gmp_username = config.get("Auth", "gmp_username", fallback="") gmp_password = config.get("Auth", "gmp_password", fallback="") self._config["gmp"]["username"] = gmp_username self._config["gmp"]["password"] = gmp_password self._defaults.update(config.defaults()) for section in config.sections(): if section == "Auth": continue for key, value in config.items(section): self._config.setdefault(section, dict())[key] = value def defaults(self): return self._defaults def get(self, section, name): if section not in self._config: return None return self._config[section].get(name) gvm-tools-23.11.0/gvmtools/helper.py000066400000000000000000000142411452466721200173230ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2018-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import getpass import os import string import sys import uuid from random import choice, randrange from gvm.errors import GvmError from gvm.xml import pretty_print from lxml import etree __all__ = ["authenticate", "pretty_print", "run_script"] class Table: def __init__(self, heading=None, rows=None, divider=" | "): self.heading = heading or [] self.rows = rows or [] self.divider = divider def _calculate_dimensions(self): column_sizes = [] for column in self.heading: column_sizes.append(len(column)) for row in self.rows: for i, column in enumerate(row): dim = column_sizes[i] column_size = len(column) if dim < column_size: column_sizes[i] = column_size return column_sizes def _create_column(self, column, size): return f'{column}{" " * (size - len(column))}' def _create_row(self, columns): return self.divider.join(columns) def __str__(self): column_sizes = self._calculate_dimensions() row_strings = [] heading_columns = [] heading_divider_columns = [] for i, column in enumerate(self.heading): column_size = column_sizes[i] heading_columns.append(self._create_column(column, column_size)) heading_divider_columns.append( self._create_column("-" * column_size, column_size) ) row_strings.append(self._create_row(heading_columns)) row_strings.append(self._create_row(heading_divider_columns)) for row in self.rows: row_columns = [] for i, column in enumerate(row): column_size = column_sizes[i] row_columns.append(self._create_column(column, column_size)) row_strings.append(self._create_row(row_columns)) return "\n".join(row_strings) def yes_or_no(question: str) -> bool: """Asks the user to proceed or not in a gvmtools script Arguments: question (str): The condition the user should answer """ reply = str(input(question + " (y/n): ")).lower().strip() if reply[0] == ("y"): return True if reply[0] == ("n"): return False else: return yes_or_no("Please enter 'y' or 'n'") def error_and_exit(msg: str) -> None: """Prints an error message and quits the gvmtools script Arguments: msg (str): The error message, that will be printed """ print(f"\nError: {msg}\n", file=sys.stderr) sys.exit(1) def generate_random_ips(count: int): """Generate count random IPv4s""" exclude_127 = [i for i in range(1, 256)] exclude_127.remove(127) return [ f"{choice(exclude_127)}.{randrange(0, 256)}." f"{randrange(0, 256)}.{randrange(1, 256)}" for i in range(count) ] def generate_id( size: int = 12, chars: str = string.ascii_uppercase + string.digits ): """Generate a random ID""" return "".join(choice(chars) for _ in range(size)) def generate_uuid(): """Generate a random new uuid""" return str(uuid.uuid4()) def create_xml_tree(xml_doc): """Creates an XML tree that can be read by an gvmtools script Arguments: xml_doc (str): Path to the xml document """ try: xml_tree = etree.parse(xml_doc) xml_tree = xml_tree.getroot() except IOError as err: error_and_exit(f"Failed to read xml_file: {str(err)} (exit)") except etree.Error as err: error_and_exit(f"Failed to parse xml_file: {str(err)} (exit)") if len(xml_tree) == 0: error_and_exit("XML file is empty (exit)") return xml_tree def do_not_run_as_root(): if hasattr(os, "geteuid") and os.geteuid() == 0: raise RuntimeError("This tool MUST NOT be run as root user.") def authenticate(gmp, username=None, password=None): """Authentication helper Tries to get authentication username and password from arguments and if not present asks the username and/or password from the terminal. Arguments: gmp: A protocol instance username (:obj:`str`, optional): Username to authenticate with. If None, username will be read from terminal. password (:obj:`str`, optional): Password to authenticate with. If None, password will be read from the terminal. Returns: tuple: (username, password) tuple Raises: GmpError: Raises GmpError if authentication fails. """ if gmp.is_authenticated(): return # Ask for login credentials if none are given. if not username: while username is None or len(username) == 0: username = input("Enter username: ") if not password: password = getpass.getpass(f"Enter password for {username}: ") try: gmp.authenticate(username, password) return (username, password) except GvmError as e: print("Could not authenticate. Please check your credentials.") raise e def run_script(path, global_vars) -> None: """Loads and executes a file as a python script Arguments: path (str): Path to the script file vars (dict): Variables passed as globals to the script """ try: file = open(path, "r", encoding="utf-8", newline="").read() except FileNotFoundError: print(f"Script {path} does not exist", file=sys.stderr) sys.exit(2) exec(file, global_vars) # pylint: disable=exec-used gvm-tools-23.11.0/gvmtools/parser.py000066400000000000000000000253041452466721200173420ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2018-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """Command Line Interface Parser """ import argparse import logging from pathlib import Path from typing import Optional from gvm import get_version as get_gvm_version from gvm.connections import ( DEFAULT_TIMEOUT, SSHConnection, TLSConnection, UnixSocketConnection, ) from gvmtools import get_version from gvmtools.config import Config logger = logging.getLogger(__name__) __version__ = get_version() __api_version__ = get_gvm_version() DEFAULT_CONFIG_PATH = "~/.config/gvm-tools.conf" PROTOCOL_OSP = "OSP" PROTOCOL_GMP = "GMP" DEFAULT_PROTOCOL = PROTOCOL_GMP class CliParser: def __init__( self, description: str, logfilename, *, prog=None, ignore_config=False ): bootstrap_parser = argparse.ArgumentParser( prog=prog, description=description, formatter_class=argparse.RawTextHelpFormatter, # don't parse help initially. the args from parser wouldn't be shown add_help=False, ) bootstrap_parser.add_argument( "-c", "--config", nargs="?", default=DEFAULT_CONFIG_PATH, help="Configuration file path (default: %(default)s)", ) choices = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] bootstrap_parser.add_argument( "--log", nargs="?", dest="loglevel", const="INFO", type=lambda arg: {x.upper(): x for x in choices}[arg.upper()], choices=choices, help="Activate logging (default level: %(default)s)", ) parser = argparse.ArgumentParser(prog=prog, parents=[bootstrap_parser]) parser.add_argument( "--timeout", required=False, default=DEFAULT_TIMEOUT, type=int, help="Response timeout in seconds, or -1 to wait " "indefinitely (default: %(default)s)", ) parser.add_argument( "--gmp-username", help="Username for GMP service (default: %(default)r)", ) parser.add_argument( "--gmp-password", help="Password for GMP service (default: %(default)r)", ) parser.add_argument( "-V", "--version", action="version", version=f"%(prog)s {__version__} (API version {__api_version__})", help="Show version information and exit", ) subparsers = parser.add_subparsers( metavar="CONNECTION_TYPE", title="connections", description="valid connection types", help="Connection type to use", ) subparsers.required = True subparsers.dest = "connection_type" self._subparsers = subparsers self._parser = parser self._bootstrap_parser = bootstrap_parser self._logfilename = logfilename self._ignore_config = ignore_config self._add_subparsers() def parse_args(self, args=None): args, unkown_args = self.parse_known_args(args) if unkown_args: self._parser.error( f'unrecognized arguments {" ".join(unkown_args)}' ) return args def parse_known_args(self, args=None): args_before, _ = self._bootstrap_parser.parse_known_args(args) if args_before.loglevel is not None: level = logging.getLevelName(args_before.loglevel) logging.basicConfig(filename=self._logfilename, level=level) self._set_defaults(None if self._ignore_config else args_before.config) args, unknown_args = self._parser.parse_known_args(args) # If timeout value is -1, then the socket should have no timeout if args.timeout == -1: args.timeout = None logging.debug("Parsed arguments %r", args) return args, unknown_args def add_argument(self, *args, **kwargs): self._parser_socket.add_argument(*args, **kwargs) self._parser_ssh.add_argument(*args, **kwargs) self._parser_tls.add_argument(*args, **kwargs) def add_protocol_argument(self): self._parser.add_argument( "--protocol", required=False, default=DEFAULT_PROTOCOL, choices=[PROTOCOL_GMP, PROTOCOL_OSP], help="Service protocol to use (default: %(default)s)", ) def _load_config(self, configfile): config = Config() if not configfile: return config configpath = Path(configfile) try: if not configpath.expanduser().resolve().exists(): logger.debug("Ignoring non existing config file %s", configfile) return config except FileNotFoundError: # we are on python 3.5 and Path.resolve raised a FileNotFoundError logger.debug("Ignoring non existing config file %s", configfile) return config try: config.load(configpath) logger.debug("Loaded config %s", configfile) except Exception as e: # pylint: disable=broad-except raise RuntimeError( f"Error while parsing config file {configfile}. Error was {e}" ) from None return config def _add_subparsers(self): parser_ssh = self._subparsers.add_parser( "ssh", help="Use SSH to connect to service" ) parser_ssh.add_argument( "--hostname", help="Hostname or IP address (default: %(default)s)" ) parser_ssh.add_argument( "--port", required=False, help="SSH port (default: %(default)s)", type=int, ) parser_ssh.add_argument( "--ssh-username", help="SSH username (default: %(default)r)" ) parser_ssh.add_argument( "--ssh-password", help="SSH password (default: %(default)r)" ) parser_ssh.add_argument( "-A", "--auto-accept-host", action="store_true", help="When executed in e.g. CI, auto accept SSH host addition", ) parser_tls = self._subparsers.add_parser( "tls", help="Use TLS secured connection to connect to service" ) parser_tls.add_argument( "--hostname", help="Hostname or IP address (default: %(default)s)" ) parser_tls.add_argument( "--port", required=False, help="GMP/OSP port (default: %(default)s)", type=int, ) parser_tls.add_argument( "--certfile", required=False, help="Path to the certificate file for client authentication. " "(default: %(default)s)", ) parser_tls.add_argument( "--keyfile", required=False, help="Path to key file for client authentication. " "(default: %(default)s)", ) parser_tls.add_argument( "--cafile", required=False, help="Path to CA certificate for server authentication. " "(default: %(default)s)", ) parser_tls.add_argument( "--no-credentials", required=False, default=False, action="store_true", help="Use only certificates for authentication", ) parser_socket = self._subparsers.add_parser( "socket", help="Use UNIX Domain socket to connect to service" ) socketpath_group = parser_socket.add_mutually_exclusive_group() socketpath_group.add_argument( "--sockpath", nargs="?", default=None, dest="socketpath", help="Deprecated, use --socketpath instead", ) socketpath_group.add_argument( "--socketpath", nargs="?", help="Path to UNIX Domain socket (default: %(default)s)", ) self._parser_ssh = parser_ssh self._parser_socket = parser_socket self._parser_tls = parser_tls def _set_defaults(self, configfilename=None): self._config = self._load_config(configfilename) self._parser.set_defaults( gmp_username=self._config.get("gmp", "username"), gmp_password=self._config.get("gmp", "password"), **self._config.defaults(), ) self._parser_ssh.set_defaults( port=int(self._config.get("ssh", "port")), ssh_username=self._config.get("ssh", "username"), ssh_password=self._config.get("ssh", "password"), hostname=self._config.get("ssh", "hostname"), ) self._parser_tls.set_defaults( port=int(self._config.get("tls", "port")), certfile=self._config.get("tls", "certfile"), keyfile=self._config.get("tls", "keyfile"), cafile=self._config.get("tls", "cafile"), hostname=self._config.get("tls", "hostname"), ) self._parser_socket.set_defaults( socketpath=self._config.get("unixsocket", "socketpath") ) def create_parser(description, logfilename): return CliParser(description, logfilename) def create_connection( connection_type, socketpath=None, timeout=None, hostname=None, port=None, certfile=None, keyfile=None, cafile=None, ssh_username=None, ssh_password=None, auto_accept_host: Optional[bool] = None, **kwargs, # pylint: disable=unused-argument ): if "socket" in connection_type: return UnixSocketConnection(timeout=timeout, path=socketpath) if "tls" in connection_type: return TLSConnection( timeout=timeout, hostname=hostname, port=port, certfile=certfile, keyfile=keyfile, cafile=cafile, ) return SSHConnection( timeout=timeout, hostname=hostname, port=port, username=ssh_username, password=ssh_password, auto_accept_host=auto_accept_host, ) gvm-tools-23.11.0/gvmtools/pyshell.py000066400000000000000000000123151452466721200175240ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2018-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import code import logging import os import sys from argparse import Namespace from gvm import get_version as get_gvm_version from gvm.protocols.gmp import Gmp from gvm.protocols.latest import Osp from gvm.transforms import EtreeCheckCommandTransform from gvm.xml import pretty_print from gvmtools import get_version from gvmtools.helper import authenticate, do_not_run_as_root, run_script from gvmtools.parser import ( PROTOCOL_GMP, PROTOCOL_OSP, create_connection, create_parser, ) __version__ = get_version() __api_version__ = get_gvm_version() logger = logging.getLogger(__name__) HELP_TEXT = """ Command line tool to access services via GMP (Greenbone Management Protocol) and OSP (Open Scanner Protocol) gvm-pyshell provides an interactive shell for GMP and OSP services. Example: >>> tasks = gmp.get_tasks() >>> task_names = tasks.xpath('task/name/text()') >>> print(task_names) ['Scan Task'] >>> pretty_print('') The interactive shell can be exited with: Ctrl + D on Linux or Ctrl + Z on Windows The protocol specifications for GMP and OSP are available at: https://docs.greenbone.net/index.html#api_documentation""" class Help(object): """Help class to overwrite the help function from python itself.""" def __call__(self): return print(HELP_TEXT) def __repr__(self): # do pwd command return HELP_TEXT def main(): do_not_run_as_root() parser = create_parser(description=HELP_TEXT, logfilename="gvm-pyshell.log") parser.add_protocol_argument() parser.add_argument( "-i", "--interactive", action="store_true", default=False, help="Start an interactive Python shell", ) parser.add_argument( "scriptname", nargs="?", metavar="SCRIPT", help="Path to script to be preloaded (example: myscript.gmp.py)", ) parser.add_argument( "scriptargs", nargs="*", metavar="ARG", help="Arguments for preloaded script", ) args = parser.parse_args() connection = create_connection(**vars(args)) transform = EtreeCheckCommandTransform() global_vars = { "help": Help(), "pretty_print": pretty_print, "__version__": __version__, "__api_version__": __api_version__, } username = None password = None if args.protocol == PROTOCOL_OSP: protocol_class = Osp name = "osp" else: protocol_class = Gmp name = "gmp" with protocol_class(connection, transform=transform) as protocol: global_vars[name] = protocol global_vars["__name__"] = f"__{name}__" if args.protocol == PROTOCOL_GMP: if args.gmp_username: (username, password) = authenticate( protocol, username=args.gmp_username, password=args.gmp_password, ) shell_args = Namespace(username=username, password=password) global_vars["args"] = shell_args with_script = args.scriptname and len(args.scriptname) > 0 if with_script: argv = [os.path.abspath(args.scriptname), *args.scriptargs] shell_args.argv = argv # for backwards compatibility we add script here shell_args.script = argv no_script_no_interactive = not args.interactive and not with_script script_and_interactive = args.interactive and with_script only_interactive = not with_script and args.interactive only_script = not args.interactive and with_script if only_interactive or no_script_no_interactive: enter_interactive_mode(global_vars) if script_and_interactive or only_script: if only_script: print( "Using gvm-pyshell for running scripts only is deprecated. " "Please use gvm-script instead", file=sys.stderr, ) script_name = args.scriptname run_script(script_name, global_vars) if not only_script: enter_interactive_mode(global_vars) def enter_interactive_mode(global_vars): code.interact( banner=f"GVM Interactive Console {__version__} API {__api_version__}." 'Type "help" to get information about functionality.', local=dict(global_vars), ) if __name__ == "__main__": main() gvm-tools-23.11.0/gvmtools/script.py000066400000000000000000000067701452466721200173600ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2018-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import sys import traceback from argparse import Namespace from gvm import get_version as get_gvm_version from gvm.protocols.gmp import Gmp from gvm.protocols.latest import Osp from gvm.transforms import EtreeCheckCommandTransform from gvmtools import get_version from gvmtools.helper import authenticate, do_not_run_as_root, run_script from gvmtools.parser import ( PROTOCOL_GMP, PROTOCOL_OSP, create_connection, create_parser, ) HELP_TEXT = """ Command line tool to execute custom GMP (Greenbone Management Protocol) and OSP (Open Scanner Protocol) scripts. The protocol specifications for GMP and OSP are available at: https://docs.greenbone.net/index.html#api_documentation """ __version__ = get_version() __api_version__ = get_gvm_version() def main(): do_not_run_as_root() parser = create_parser(description=HELP_TEXT, logfilename="gvm-script.log") parser.add_protocol_argument() parser.add_argument( "scriptname", metavar="SCRIPT", help="Path to script to be executed (example: myscript.gmp.py)", ) parser.add_argument( "scriptargs", nargs="*", metavar="ARG", help="Arguments for the script" ) args, script_args = parser.parse_known_args() connection = create_connection(**vars(args)) transform = EtreeCheckCommandTransform() global_vars = { "__version__": __version__, "__api_version__": __api_version__, } username = None password = None if args.protocol == PROTOCOL_OSP: protocol_class = Osp name = "osp" else: protocol_class = Gmp name = "gmp" try: with protocol_class(connection, transform=transform) as protocol: global_vars[name] = protocol global_vars["__name__"] = f"__{name}__" if args.protocol == PROTOCOL_GMP: if args.gmp_username: (username, password) = authenticate( protocol, username=args.gmp_username, password=args.gmp_password, ) argv = [os.path.abspath(args.scriptname), *args.scriptargs] shell_args = Namespace( username=username, password=password, argv=argv, # for backwards compatibility we add script here script=argv, # the unknown args, which are owned by the script. script_args=script_args, ) global_vars["args"] = shell_args run_script(args.scriptname, global_vars) except Exception: # pylint: disable=broad-except print(traceback.format_exc()) sys.exit(1) sys.exit(0) if __name__ == "__main__": main() gvm-tools-23.11.0/poetry.lock000066400000000000000000003532211452466721200160200ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" optional = false python-versions = ">=3.6" files = [ {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] [[package]] name = "anyio" version = "4.0.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ {file = "anyio-4.0.0-py3-none-any.whl", hash = "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f"}, {file = "anyio-4.0.0.tar.gz", hash = "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" [package.extras] doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.22)"] [[package]] name = "autohooks" version = "23.10.0" description = "Library for managing git hooks" optional = false python-versions = ">=3.9,<4.0" files = [ {file = "autohooks-23.10.0-py3-none-any.whl", hash = "sha256:a4fdf33097daae47068c1ce6ec075a1d5fc692a0a182b0c69e8abda75b45c39e"}, {file = "autohooks-23.10.0.tar.gz", hash = "sha256:4de41476ed72e0a3670c40b1fc9b7d67c66eb788ab01b6a7b516fcebb2283166"}, ] [package.dependencies] pontos = ">=22.8.0" rich = ">=12.5.1" tomlkit = ">=0.5.11" [[package]] name = "autohooks-plugin-black" version = "23.10.0" description = "An autohooks plugin for python code formatting via black" optional = false python-versions = ">=3.9,<4.0" files = [ {file = "autohooks_plugin_black-23.10.0-py3-none-any.whl", hash = "sha256:88d648251df749586af9ea5be3105daa4358ed916b61aee738d0727387214470"}, {file = "autohooks_plugin_black-23.10.0.tar.gz", hash = "sha256:8415b5f566d861236bde2b0973699f64a8b861208af4fa05fe04a1f923ea3ef6"}, ] [package.dependencies] autohooks = ">=21.6.0" black = ">=20.8" [[package]] name = "autohooks-plugin-isort" version = "23.10.0" description = "An autohooks plugin for python code formatting via isort" optional = false python-versions = ">=3.9,<4.0" files = [ {file = "autohooks_plugin_isort-23.10.0-py3-none-any.whl", hash = "sha256:40619191a1d21d7ebb59f03ad21e032cadfe56e2e016cf430d37218340727f6c"}, {file = "autohooks_plugin_isort-23.10.0.tar.gz", hash = "sha256:9548efb7350021bda0f0c6a1ab030e8a76d7a88774cff5ef130423329afe7ce3"}, ] [package.dependencies] autohooks = ">=21.6.0" isort = ">=5.8.0" [[package]] name = "autohooks-plugin-ruff" version = "23.11.0" description = "An autohooks plugin for python code formatting via ruff" optional = false python-versions = ">=3.9,<4.0" files = [ {file = "autohooks_plugin_ruff-23.11.0-py3-none-any.whl", hash = "sha256:26f7be03e232a505e65246b666ea48a323a900d05bdef68e337a5c93dfdb23b1"}, {file = "autohooks_plugin_ruff-23.11.0.tar.gz", hash = "sha256:1f6c3bc92449fc57c71dd4ac0a410d7f5229f3aff96921fa80c70a28d7db9366"}, ] [package.dependencies] autohooks = ">=23.4.0" ruff = ">=0.0.272" [[package]] name = "babel" version = "2.13.1" description = "Internationalization utilities" optional = false python-versions = ">=3.7" files = [ {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, ] [package.dependencies] setuptools = {version = "*", markers = "python_version >= \"3.12\""} [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "bcrypt" version = "4.0.1" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.6" files = [ {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"}, {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"}, {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"}, {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"}, {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"}, {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"}, {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"}, {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"}, {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"}, {file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"}, {file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"}, {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"}, {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"}, {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"}, {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"}, {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"}, {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"}, {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"}, {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"}, {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"}, {file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"}, ] [package.extras] tests = ["pytest (>=3.2.1,!=3.3.0)"] typecheck = ["mypy"] [[package]] name = "beautifulsoup4" version = "4.12.2" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" files = [ {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, ] [package.dependencies] soupsieve = ">1.2" [package.extras] html5lib = ["html5lib"] lxml = ["lxml"] [[package]] name = "black" version = "23.11.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, ] [package.dependencies] pycparser = "*" [[package]] name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] name = "click" version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "colorful" version = "0.5.5" description = "Terminal string styling done right, in Python." optional = false python-versions = "*" files = [ {file = "colorful-0.5.5-py2.py3-none-any.whl", hash = "sha256:62c187e27c1433db9463ff93b1451898d1e7e23a7e553583fd9daeb6325182e4"}, {file = "colorful-0.5.5.tar.gz", hash = "sha256:66f8c1264b2a26f7293b96a03bb7a76c4bc8b9634369a0bffdcd12d618056a1d"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "coverage" version = "7.3.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, ] [package.extras] toml = ["tomli"] [[package]] name = "cryptography" version = "41.0.5" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"}, {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"}, {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"}, {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"}, {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"}, {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"}, {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"}, {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"}, {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"}, {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"}, {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"}, {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"}, {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"}, {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"}, {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"}, {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"}, {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"}, {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"}, {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"}, {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"}, {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"}, {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"}, {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, ] [package.dependencies] cffi = ">=1.12" [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] nox = ["nox"] pep8test = ["black", "check-sdist", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "defusedxml" version = "0.7.1" description = "XML bomb protection for Python stdlib modules" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] [[package]] name = "docutils" version = "0.20.1" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.7" files = [ {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] [[package]] name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "furo" version = "2023.9.10" description = "A clean customisable Sphinx documentation theme." optional = false python-versions = ">=3.8" files = [ {file = "furo-2023.9.10-py3-none-any.whl", hash = "sha256:513092538537dc5c596691da06e3c370714ec99bc438680edc1debffb73e5bfc"}, {file = "furo-2023.9.10.tar.gz", hash = "sha256:5707530a476d2a63b8cad83b4f961f3739a69f4b058bcf38a03a39fa537195b2"}, ] [package.dependencies] beautifulsoup4 = "*" pygments = ">=2.7" sphinx = ">=6.0,<8.0" sphinx-basic-ng = "*" [[package]] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] [[package]] name = "h2" version = "4.1.0" description = "HTTP/2 State-Machine based protocol implementation" optional = false python-versions = ">=3.6.1" files = [ {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, ] [package.dependencies] hpack = ">=4.0,<5" hyperframe = ">=6.0,<7" [[package]] name = "hpack" version = "4.0.0" description = "Pure-Python HPACK header compression" optional = false python-versions = ">=3.6.1" files = [ {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, ] [[package]] name = "httpcore" version = "1.0.2" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, ] [package.dependencies] certifi = "*" h11 = ">=0.13,<0.15" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] trio = ["trio (>=0.22.0,<0.23.0)"] [[package]] name = "httpx" version = "0.25.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ {file = "httpx-0.25.1-py3-none-any.whl", hash = "sha256:fec7d6cc5c27c578a391f7e87b9aa7d3d8fbcd034f6399f9f79b45bcc12a866a"}, {file = "httpx-0.25.1.tar.gz", hash = "sha256:ffd96d5cf901e63863d9f1b4b6807861dbea4d301613415d9e6e57ead15fc5d0"}, ] [package.dependencies] anyio = "*" certifi = "*" h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""} httpcore = "*" idna = "*" sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] [[package]] name = "hyperframe" version = "6.0.1" description = "HTTP/2 framing layer for Python" optional = false python-versions = ">=3.6.1" files = [ {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, ] [[package]] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] [[package]] name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] [[package]] name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" files = [ {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, ] [package.extras] colors = ["colorama (>=0.4.3)"] pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] [package.dependencies] MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "livereload" version = "2.6.3" description = "Python LiveReload is an awesome tool for web developers" optional = false python-versions = "*" files = [ {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"}, {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, ] [package.dependencies] six = "*" tornado = {version = "*", markers = "python_version > \"2.7\""} [[package]] name = "lxml" version = "4.9.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] source = ["Cython (>=0.29.35)"] [[package]] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] [package.dependencies] mdurl = ">=0.1,<1.0" [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark"] code-style = ["pre-commit (>=3.0,<4.0)"] compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] [[package]] name = "mdit-py-plugins" version = "0.4.0" description = "Collection of plugins for markdown-it-py" optional = false python-versions = ">=3.8" files = [ {file = "mdit_py_plugins-0.4.0-py3-none-any.whl", hash = "sha256:b51b3bb70691f57f974e257e367107857a93b36f322a9e6d44ca5bf28ec2def9"}, {file = "mdit_py_plugins-0.4.0.tar.gz", hash = "sha256:d8ab27e9aed6c38aa716819fedfde15ca275715955f8a185a8e1cf90fb1d2c1b"}, ] [package.dependencies] markdown-it-py = ">=1.0.0,<4.0.0" [package.extras] code-style = ["pre-commit"] rtd = ["myst-parser", "sphinx-book-theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] [[package]] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] name = "myst-parser" version = "2.0.0" description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," optional = false python-versions = ">=3.8" files = [ {file = "myst_parser-2.0.0-py3-none-any.whl", hash = "sha256:7c36344ae39c8e740dad7fdabf5aa6fc4897a813083c6cc9990044eb93656b14"}, {file = "myst_parser-2.0.0.tar.gz", hash = "sha256:ea929a67a6a0b1683cdbe19b8d2e724cd7643f8aa3e7bb18dd65beac3483bead"}, ] [package.dependencies] docutils = ">=0.16,<0.21" jinja2 = "*" markdown-it-py = ">=3.0,<4.0" mdit-py-plugins = ">=0.4,<1.0" pyyaml = "*" sphinx = ">=6,<8" [package.extras] code-style = ["pre-commit (>=3.0,<4.0)"] linkify = ["linkify-it-py (>=2.0,<3.0)"] rtd = ["ipython", "pydata-sphinx-theme (==v0.13.0rc4)", "sphinx-autodoc2 (>=0.4.2,<0.5.0)", "sphinx-book-theme (==1.0.0rc2)", "sphinx-copybutton", "sphinx-design2", "sphinx-pyscript", "sphinx-tippy (>=0.3.1)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.8.2,<0.9.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=7,<8)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx-pytest"] testing-docutils = ["pygments", "pytest (>=7,<8)", "pytest-param-files (>=0.3.4,<0.4.0)"] [[package]] name = "packaging" version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] name = "paramiko" version = "3.3.1" description = "SSH2 protocol library" optional = false python-versions = ">=3.6" files = [ {file = "paramiko-3.3.1-py3-none-any.whl", hash = "sha256:b7bc5340a43de4287bbe22fe6de728aa2c22468b2a849615498dd944c2f275eb"}, {file = "paramiko-3.3.1.tar.gz", hash = "sha256:6a3777a961ac86dbef375c5f5b8d50014a1a96d0fd7f054a43bc880134b0ff77"}, ] [package.dependencies] bcrypt = ">=3.2" cryptography = ">=3.3" pynacl = ">=1.5" [package.extras] all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] invoke = ["invoke (>=2.0)"] [[package]] name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] name = "platformdirs" version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, ] [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pontos" version = "23.11.1" description = "Common utilities and tools maintained by Greenbone Networks" optional = false python-versions = ">=3.9,<4.0" files = [ {file = "pontos-23.11.1-py3-none-any.whl", hash = "sha256:4f5e218607bb02333988aaa2a6ee82875d315dd17a016a0df7d17188a8e80364"}, {file = "pontos-23.11.1.tar.gz", hash = "sha256:9102c96419459dd34f12e7f1cc4f1e4fd83ef3b49cf1da21ce44860150912adc"}, ] [package.dependencies] colorful = ">=0.5.4" httpx = {version = ">=0.23", extras = ["http2"]} lxml = ">=4.9.0" packaging = ">=20.3" python-dateutil = ">=2.8.2" rich = ">=12.4.4" semver = ">=2.13" tomlkit = ">=0.5.11" [[package]] name = "pycparser" version = "2.21" description = "C parser in Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] [[package]] name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] plugins = ["importlib-metadata"] [[package]] name = "pynacl" version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" optional = false python-versions = ">=3.6" files = [ {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, ] [package.dependencies] cffi = ">=1.4.1" [package.extras] docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] [package.dependencies] six = ">=1.5" [[package]] name = "python-gvm" version = "23.10.1" description = "Library to communicate with remote servers over GMP or OSP" optional = false python-versions = ">=3.9,<4.0" files = [ {file = "python_gvm-23.10.1-py3-none-any.whl", hash = "sha256:eed0b80118e905414681e3d03e158fb6833343fe452d4e7ae868f0458653d662"}, {file = "python_gvm-23.10.1.tar.gz", hash = "sha256:7a919d0842c53852c787003c360d10abafe4ae1429423448cb39dd0c70544e42"}, ] [package.dependencies] defusedxml = ">=0.6" lxml = ">=4.5.0" paramiko = ">=2.7.1" [[package]] name = "pytoolconfig" version = "1.2.6" description = "Python tool configuration" optional = false python-versions = ">=3.7" files = [ {file = "pytoolconfig-1.2.6-py3-none-any.whl", hash = "sha256:e8b2e538f11dbabc4617884d45401e0105e2d7db920cb8ae6baa94d66126a8e3"}, {file = "pytoolconfig-1.2.6.tar.gz", hash = "sha256:f2d00ea4f8cbdffd3006780ba51016618c835b338f634e3f7f8b2715b1710889"}, ] [package.dependencies] packaging = ">=22.0" platformdirs = {version = ">=1.4.4", optional = true, markers = "extra == \"global\""} tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} [package.extras] doc = ["sphinx (>=4.5.0)", "tabulate (>=0.8.9)"] gendocs = ["pytoolconfig[doc]", "sphinx (>=4.5.0)", "sphinx-autodoc-typehints (>=1.18.1)", "sphinx-rtd-theme (>=1.0.0)"] global = ["platformdirs (>=1.4.4)"] validation = ["pydantic (>=1.7.4)"] [[package]] name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" version = "13.6.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ {file = "rich-13.6.0-py3-none-any.whl", hash = "sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245"}, {file = "rich-13.6.0.tar.gz", hash = "sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rope" version = "1.11.0" description = "a python refactoring library..." optional = false python-versions = ">=3.8" files = [ {file = "rope-1.11.0-py3-none-any.whl", hash = "sha256:62c9028353d1f020653bd1b090d659031539842ede6bd65985f86db3d6430932"}, {file = "rope-1.11.0.tar.gz", hash = "sha256:ac0cbdcda5a546e1e56c54976df07ea2cb04c806f65459bc213536c5d1bc073e"}, ] [package.dependencies] pytoolconfig = {version = ">=1.2.2", extras = ["global"]} [package.extras] dev = ["build (>=0.7.0)", "pre-commit (>=2.20.0)", "pytest (>=7.0.1)", "pytest-timeout (>=2.1.0)"] doc = ["pytoolconfig[doc]", "sphinx (>=4.5.0)", "sphinx-autodoc-typehints (>=1.18.1)", "sphinx-rtd-theme (>=1.0.0)"] release = ["pip-tools (>=6.12.1)", "toml (>=0.10.2)", "twine (>=4.0.2)"] [[package]] name = "ruff" version = "0.1.5" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ {file = "ruff-0.1.5-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:32d47fc69261c21a4c48916f16ca272bf2f273eb635d91c65d5cd548bf1f3d96"}, {file = "ruff-0.1.5-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:171276c1df6c07fa0597fb946139ced1c2978f4f0b8254f201281729981f3c17"}, {file = "ruff-0.1.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ef33cd0bb7316ca65649fc748acc1406dfa4da96a3d0cde6d52f2e866c7b39"}, {file = "ruff-0.1.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2c205827b3f8c13b4a432e9585750b93fd907986fe1aec62b2a02cf4401eee6"}, {file = "ruff-0.1.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb408e3a2ad8f6881d0f2e7ad70cddb3ed9f200eb3517a91a245bbe27101d379"}, {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f20dc5e5905ddb407060ca27267c7174f532375c08076d1a953cf7bb016f5a24"}, {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aafb9d2b671ed934998e881e2c0f5845a4295e84e719359c71c39a5363cccc91"}, {file = "ruff-0.1.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4894dddb476597a0ba4473d72a23151b8b3b0b5f958f2cf4d3f1c572cdb7af7"}, {file = "ruff-0.1.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00a7ec893f665ed60008c70fe9eeb58d210e6b4d83ec6654a9904871f982a2a"}, {file = "ruff-0.1.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8c11206b47f283cbda399a654fd0178d7a389e631f19f51da15cbe631480c5b"}, {file = "ruff-0.1.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fa29e67b3284b9a79b1a85ee66e293a94ac6b7bb068b307a8a373c3d343aa8ec"}, {file = "ruff-0.1.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9b97fd6da44d6cceb188147b68db69a5741fbc736465b5cea3928fdac0bc1aeb"}, {file = "ruff-0.1.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:721f4b9d3b4161df8dc9f09aa8562e39d14e55a4dbaa451a8e55bdc9590e20f4"}, {file = "ruff-0.1.5-py3-none-win32.whl", hash = "sha256:f80c73bba6bc69e4fdc73b3991db0b546ce641bdcd5b07210b8ad6f64c79f1ab"}, {file = "ruff-0.1.5-py3-none-win_amd64.whl", hash = "sha256:c21fe20ee7d76206d290a76271c1af7a5096bc4c73ab9383ed2ad35f852a0087"}, {file = "ruff-0.1.5-py3-none-win_arm64.whl", hash = "sha256:82bfcb9927e88c1ed50f49ac6c9728dab3ea451212693fe40d08d314663e412f"}, {file = "ruff-0.1.5.tar.gz", hash = "sha256:5cbec0ef2ae1748fb194f420fb03fb2c25c3258c86129af7172ff8f198f125ab"}, ] [[package]] name = "semver" version = "3.0.2" description = "Python helper for Semantic Versioning (https://semver.org)" optional = false python-versions = ">=3.7" files = [ {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, ] [[package]] name = "setuptools" version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] [[package]] name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" files = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] [[package]] name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = false python-versions = "*" files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] [[package]] name = "soupsieve" version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" files = [ {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, ] [[package]] name = "sphinx" version = "7.2.6" description = "Python documentation generator" optional = false python-versions = ">=3.9" files = [ {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} docutils = ">=0.18.1,<0.21" imagesize = ">=1.3" importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" packaging = ">=21.0" Pygments = ">=2.14" requests = ">=2.25.0" snowballstemmer = ">=2.0" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.9" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"] [[package]] name = "sphinx-autobuild" version = "2021.3.14" description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." optional = false python-versions = ">=3.6" files = [ {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, ] [package.dependencies] colorama = "*" livereload = "*" sphinx = "*" [package.extras] test = ["pytest", "pytest-cov"] [[package]] name = "sphinx-basic-ng" version = "1.0.0b2" description = "A modern skeleton for Sphinx themes." optional = false python-versions = ">=3.7" files = [ {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"}, {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"}, ] [package.dependencies] sphinx = ">=4.0" [package.extras] docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] [[package]] name = "sphinxcontrib-applehelp" version = "1.0.7" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" files = [ {file = "sphinxcontrib_applehelp-1.0.7-py3-none-any.whl", hash = "sha256:094c4d56209d1734e7d252f6e0b3ccc090bd52ee56807a5d9315b19c122ab15d"}, {file = "sphinxcontrib_applehelp-1.0.7.tar.gz", hash = "sha256:39fdc8d762d33b01a7d8f026a3b7d71563ea3b72787d5f00ad8465bd9d6dfbfa"}, ] [package.dependencies] Sphinx = ">=5" [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" version = "1.0.5" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" files = [ {file = "sphinxcontrib_devhelp-1.0.5-py3-none-any.whl", hash = "sha256:fe8009aed765188f08fcaadbb3ea0d90ce8ae2d76710b7e29ea7d047177dae2f"}, {file = "sphinxcontrib_devhelp-1.0.5.tar.gz", hash = "sha256:63b41e0d38207ca40ebbeabcf4d8e51f76c03e78cd61abe118cf4435c73d4212"}, ] [package.dependencies] Sphinx = ">=5" [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.0.4" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" files = [ {file = "sphinxcontrib_htmlhelp-2.0.4-py3-none-any.whl", hash = "sha256:8001661c077a73c29beaf4a79968d0726103c5605e27db92b9ebed8bab1359e9"}, {file = "sphinxcontrib_htmlhelp-2.0.4.tar.gz", hash = "sha256:6c26a118a05b76000738429b724a0568dbde5b72391a688577da08f11891092a"}, ] [package.dependencies] Sphinx = ">=5" [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] [package.extras] test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" version = "1.0.6" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" files = [ {file = "sphinxcontrib_qthelp-1.0.6-py3-none-any.whl", hash = "sha256:bf76886ee7470b934e363da7a954ea2825650013d367728588732c7350f49ea4"}, {file = "sphinxcontrib_qthelp-1.0.6.tar.gz", hash = "sha256:62b9d1a186ab7f5ee3356d906f648cacb7a6bdb94d201ee7adf26db55092982d"}, ] [package.dependencies] Sphinx = ">=5" [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" version = "1.1.9" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" files = [ {file = "sphinxcontrib_serializinghtml-1.1.9-py3-none-any.whl", hash = "sha256:9b36e503703ff04f20e9675771df105e58aa029cfcbc23b8ed716019b7416ae1"}, {file = "sphinxcontrib_serializinghtml-1.1.9.tar.gz", hash = "sha256:0c64ff898339e1fac29abd2bf5f11078f3ec413cfe9c046d3120d7ca65530b54"}, ] [package.dependencies] Sphinx = ">=5" [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] name = "tomlkit" version = "0.12.2" description = "Style preserving TOML library" optional = false python-versions = ">=3.7" files = [ {file = "tomlkit-0.12.2-py3-none-any.whl", hash = "sha256:eeea7ac7563faeab0a1ed8fe12c2e5a51c61f933f2502f7e9db0241a65163ad0"}, {file = "tomlkit-0.12.2.tar.gz", hash = "sha256:df32fab589a81f0d7dc525a4267b6d7a64ee99619cbd1eeb0fae32c1dd426977"}, ] [[package]] name = "tornado" version = "6.3.3" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">= 3.8" files = [ {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d"}, {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a"}, {file = "tornado-6.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f"}, {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a"}, {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2"}, {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0"}, {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16"}, {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17"}, {file = "tornado-6.3.3-cp38-abi3-win32.whl", hash = "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3"}, {file = "tornado-6.3.3-cp38-abi3-win_amd64.whl", hash = "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5"}, {file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"}, ] [[package]] name = "typing-extensions" version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] name = "urllib3" version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "zipp" version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [metadata] lock-version = "2.0" python-versions = "^3.9" content-hash = "ee90c8b0358b1e2f84542d45570aa01de35ecfb5e3c3579c832d66f24b7e40e0" gvm-tools-23.11.0/pyproject.toml000066400000000000000000000041271452466721200165360ustar00rootroot00000000000000[build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry] name = "gvm-tools" version = "23.11.0" authors = ["Greenbone AG "] description = "Tools to control a GSM/GVM over GMP or OSP" license = "GPL-3.0-or-later" readme = "README.md" homepage = "https://github.com/greenbone/gvm-tools/" repository = "https://github.com/greenbone/gvm-tools/" documentation = "https://greenbone.github.io/gvm-tools/" classifiers = [ # Full list: https://pypi.org/pypi?%3Aaction=list_classifiers 'Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries :: Python Modules', ] packages = [ { include = "gvmtools" }, { include = "tests", format = "sdist" }, { include = "scripts", format = "sdist" }, ] [tool.poetry.scripts] gvm-pyshell = "gvmtools.pyshell:main" gvm-cli = "gvmtools.cli:main" gvm-script = "gvmtools.script:main" [tool.poetry.dependencies] python = "^3.9" python-gvm = ">=23.4.2" [tool.poetry.dev-dependencies] autohooks = ">=22.8.0" autohooks-plugin-ruff = ">=23.6.1" autohooks-plugin-black = ">=22.8.1" autohooks-plugin-isort = ">=22.8.0" pontos = ">=22.8.1" sphinx = ">=5.3.0" coverage = ">=7.2" rope = ">=1.9.0" furo = ">=2023.3.27" sphinx-autobuild = ">=2021.3.14" myst-parser = ">=2.0.0" [tool.black] line-length = 80 target-version = ['py39', 'py310', 'py311'] exclude = ''' /( \.git | \.venv | \.github | \.vscode | _build | build | dist | docs )/ ''' [tool.isort] profile = "black" line_length = 80 [tool.autohooks] mode = "poetry" pre-commit = [ 'autohooks.plugins.black', 'autohooks.plugins.ruff', 'autohooks.plugins.isort', ] [tool.pontos.version] version-module-file = "gvmtools/__version__.py" gvm-tools-23.11.0/scripts/000077500000000000000000000000001452466721200153055ustar00rootroot00000000000000gvm-tools-23.11.0/scripts/.pylintrc000066400000000000000000000341451452466721200171610ustar00rootroot00000000000000[MASTER] # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code extension-pkg-whitelist=lxml # Add files or directories to the blacklist. They should be base names, not # paths. ignore=CVS # Add files or directories matching the regex patterns to the blacklist. The # regex matches against base names, not paths. ignore-patterns=docs # Pickle collected data for later comparisons. persistent=yes # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages suggestion-mode=yes # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED confidence= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once).You can also use "--disable=all" to # disable everything first and then reenable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" # # bad-continuation is disabled because of a bug in pylint. # See https://github.com/ambv/black/issues/48 and https://github.com/PyCQA/pylint/issues/289 disable=len-as-condition, attribute-defined-outside-init, missing-docstring, bad-continuation #disable=print-statement, # parameter-unpacking, # unpacking-in-except, # old-raise-syntax, # backtick, # long-suffix, # old-ne-operator, # old-octal-literal, # import-star-module-level, # non-ascii-bytes-literal, # raw-checker-failed, # bad-inline-option, # locally-disabled, # locally-enabled, # file-ignored, # suppressed-message, # useless-suppression, # deprecated-pragma, # apply-builtin, # basestring-builtin, # buffer-builtin, # cmp-builtin, # coerce-builtin, # execfile-builtin, # file-builtin, # long-builtin, # raw_input-builtin, # reduce-builtin, # standarderror-builtin, # unicode-builtin, # xrange-builtin, # coerce-method, # delslice-method, # getslice-method, # setslice-method, # no-absolute-import, # old-division, # dict-iter-method, # dict-view-method, # next-method-called, # metaclass-assignment, # indexing-exception, # raising-string, # reload-builtin, # oct-method, # hex-method, # nonzero-method, # cmp-method, # input-builtin, # round-builtin, # intern-builtin, # unichr-builtin, # map-builtin-not-iterating, # zip-builtin-not-iterating, # range-builtin-not-iterating, # filter-builtin-not-iterating, # using-cmp-argument, # eq-without-hash, # div-method, # idiv-method, # rdiv-method, # exception-message-attribute, # invalid-str-codec, # sys-max-int, # bad-python3-import, # deprecated-string-function, # deprecated-str-translate-call, # deprecated-itertools-function, # deprecated-types-field, # next-method-defined, # dict-items-not-iterating, # dict-keys-not-iterating, # dict-values-not-iterating # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. enable=c-extension-no-member [REPORTS] # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details #msg-template= # Set the output format. Available formats are text, parseable, colorized, json # and msvs (visual studio).You can also give a reporter class, eg # mypackage.mymodule.MyReporterClass. output-format=text # Tells whether to display a full report or only the messages reports=no # Activate the evaluation score. score=no [REFACTORING] # Maximum number of nested blocks for function / method body max-nested-blocks=5 # Complete name of functions that never returns. When checking for # inconsistent-return-statements if a never returning function is called then # it will be considered as an explicit return statement and no message will be # printed. never-returning-functions=optparse.Values,sys.exit [VARIABLES] # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins=gmp,osp,args # Tells whether unused global variables should be treated as a violation. allow-global-unused-variables=yes # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_, _cb # A regular expression matching the name of dummy variables (i.e. expectedly # not used). dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.*|^ignored_|^unused_ # Tells whether we should check for unused import in __init__ files. init-import=no # List of qualified module names which can have objects that can redefine # builtins. redefining-builtins-modules=six.moves,past.builtins,future.builtins [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME, XXX [BASIC] # Regular expression which should only match correct argument names argument-rgx=[a-z_][a-z0-9_]{1,40}$ # Regular expression which should only match correct instance attribute names attr-rgx=[a-z_][a-z0-9_]{1,40}$ # Bad variable names which should always be refused, separated by a comma bad-names=foo, bar, baz, toto, tutu, tata # Regular expression matching correct class attribute names. class-attribute-rgx=([a-z_][a-z0-9_]{1,40})|([A-Z_][A-Z0-9_]{1,30})$ # Naming style matching correct class names class-naming-style=PascalCase # Naming style matching correct constant names const-naming-style=UPPER_CASE # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=3 # Regular expression which should only match correct function names function-rgx=[a-z_][a-z0-9_]+$ # Good variable names which should always be accepted, separated by a comma good-names=e, f, i, j, k, ex, Run, logger, _ # Include a hint for the correct naming format with invalid-name include-naming-hint=yes # Regular expression matching correct inline iteration names. inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Regular expression which should only match correct method names method-rgx=[a-z_][a-z0-9_]+$ # Regular expression which should only match correct module names module-rgx=([a-z]+)|(test_*)$ # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ # List of decorators that produce properties, such as abc.abstractproperty. Add # to this list to register other decorators that produce valid properties. property-classes=abc.abstractproperty # Regular expression which should only match correct variable names variable-rgx=[a-z_][a-z0-9_]+$ [SIMILARITIES] # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=no # Minimum lines number of a similarity. min-similarity-lines=4 [LOGGING] # Logging modules to check that the string format arguments are in logging # function parameter format logging-modules=logging [TYPECHECK] # List of decorators that produce context managers, such as # contextlib.contextmanager. Add to this list to register other decorators that # produce valid context managers. contextmanager-decorators=contextlib.contextmanager # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members= # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # This flag controls whether pylint should warn about no-member and similar # checks whenever an opaque object is returned when inferring. The inference # can return multiple potential results while evaluating a Python object, but # some branches might not be evaluated, which results in partial inference. In # that case, it might be useful to still emit no-member and other checks for # the rest of the inferred objects. ignore-on-opaque-inference=yes # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of # qualified names. ignored-classes=optparse.Values,thread._local,_thread._local # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. ignored-modules= # Show a hint with possible names when a member name was not found. The aspect # of finding the hint is based on edit distance. missing-member-hint=yes # The minimum edit distance a name should have in order to be considered a # similar match for a missing member name. missing-member-hint-distance=1 # The total number of similar names that should be taken in consideration when # showing a hint for a missing member. missing-member-max-choices=1 [FORMAT] # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format=LF # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Maximum number of characters on a single line. max-line-length=80 # Maximum number of lines in a module max-module-lines=1000 # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no [IMPORTS] # Allow wildcard imports from modules that define __all__. allow-wildcard-with-all=no # Analyse import fallback blocks. This can be used to support both Python 2 and # 3 compatible code, which means that the block might have code that exists # only in one or another interpreter, leading to false positives when analysed. analyse-fallback-blocks=no # Deprecated modules which should not be used, separated by a comma deprecated-modules=optparse,tkinter.tix # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= # Force import order to recognize a module as part of the standard # compatibility libraries. known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant [DESIGN] # Maximum number of arguments for function / method max-args=15 # Maximum number of attributes for a class (see R0902). max-attributes=20 # Maximum number of boolean expressions in a if statement max-bool-expr=5 # Maximum number of branch for function / method body max-branches=12 # Maximum number of locals for function / method body max-locals=15 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of public methods for a class (see R0904). max-public-methods=30 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of statements in function / method body max-statements=50 # Minimum number of public methods for a class (see R0903). min-public-methods=0 [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__, __new__, setUp # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict, _fields, _replace, _source, _make # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=builtins.Exception gvm-tools-23.11.0/scripts/README.md000066400000000000000000000264161452466721200165750ustar00rootroot00000000000000![Greenbone Logo](https://www.greenbone.net/wp-content/uploads/gb_logo_resilience_horizontal.png) # GVM Example Scripts ## `application-detection.gmp.py` This script will search the reports and display all hosts with the requested applications! ### Arguments * ``: Name of the application ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/application-detection.gmp.py ` --- ## `cfg-gen-for-certs.gmp.py` This script creates a new scan config with nvts from a given CERT-Bund! ### Arguments * ``: Name or ID of the CERT-Bund ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/cfg-gen-for-certs.gmp.py CB-K16/0943` --- ## `check-gmp.gmp.py` This script can test different methods of the gmp API. | Optional argument | Description | | --- | --- | |`-H`: | Show this help message and exit |`-V`, `--version`: | Show program's version number and exit |`--cache [CACHE]`: | Path to cache file. Default: `/var/folders/mk/ dfxkj16j4779x98r26n21qnr0000gn/ T/check_gmp/reports.db` | `--clean` | Activate to clean the database | `-u GMP_USERNAME`, `--gmp-username GMP_USERNAME` | GMP username | `-w GMP_PASSWORD`, `--gmp-password GMP_PASSWORD` | GMP password |`-F HOSTADDRESS`, `--hostaddress HOSTADDRESS` | Report last report status of host ``. |`-T TASK`, `--task TASK` | Report status of task ``. |`--apply-overrides` | Apply overrides. |`--overrides` | Include overrides. |`-d`, `--details` | Include connection details in output. |`-l`, `--report-link` | Include URL of report in output. |`--dfn` | Include DFN-CERT IDs on vulnerabilities in output. |`--oid` | Include OIDs of NVTs finding vulnerabilities in output. |`--descr` | Include descriptions of NVTs finding vulnerabilities in output. |`--showlog` | Include log messages in output. | `--show-ports` | Include port of given vulnerable nvt in output. | `--scanend` | Include timestamp of scan end in output. | `--autofp {0,1,2}` | Trust vendor security updates for automatic false positive filtering (`0=No`, `1=full match`, `2=partial`). | `-e`, `--empty-as-unknown` | Respond with `UNKNOWN` on empty results. | `-I MAX_RUNNING_INSTANCES`, `--max-running-instances MAX_RUNNING_INSTANCES` | Set the maximum simultaneous processes of check-gmp | `--hostname [HOSTNAME]` | `--ping` | Ping the gsm appliance. | `--status` | Report status of task. | `--days DAYS` | Delete database entries that are older than given days. | `--ip IP` | Delete database entry for given ip. | `--trend` | Report status by trend. | `--last-report` | Report status by last report. ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/check-gmp.gmp.py --ip 127.0.0.1 --ping` --- ## `clean-sensor.gmp.py` This script removes all resources from a sensor, except active tasks. ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/clean-sensor.gmp.py` --- ## `combine-reports.gmp.py` This script will combine desired reports into a single report. The combined report will then be sent to a desired container task. This script will create a container task for the combined report to be sent to, however, if you would like the report to be sent to an existing task, place the report of the desired task first and add the argument 'first_task'. ### Arguments * `, ..., `: UUIDs of the reports to be combined ### Example `$ gvm-script --gmp-username=namessh --gmp-password=pass ssh --hostname=hostname scripts/combine-reports.gmp.py "d15a337c-56f3-4208-a462-afeb79eb03b7" "303fa0a6-aa9b-43c4-bac0-66ae0b2d1698" 'first_task'` --- ## `create-dummy-data.gmp.py` This script will create random data in the given GVM database. ### Arguments * ``: Number of datasets to create ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/create-dummy-data.gmp.py ` --- ## `create-targets-from-host-list.gmp.py` This script pulls hostnames from a text file and creates a target for each. ### Arguments * ``: IP of the GVM host * ``: text file containing hostnames ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/create_targets_from_host_list.gmp.py ` --- ## `delete-overrides-by-filter.gmp.py` This script deletes overrides with a specific filter value. ### Arguments * ``: the parameter for the filter. ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/delete-overrides-by-filter.gmp.py ` --- ## `gen-random-targets.gmp.py` This script generates random task data and feeds it to a desired GSM database. ### Arguments * ``: number of dummy hosts to select from * ``: number of targets to be generated * `'with-gauss'`: (optional), if you would like for the number of targets generated to be randomized on a Gaussian distribution ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/gen-random-targets.gmp.py 3 40 with-gauss` --- ## `list-tasks.gmp.py` Lists the tasks stored in an GSM Database ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/list-tasks.gmp.py` --- ## `monthly-report.gmp.py` This script will display all vulnerabilities from the hosts of the reports in a given month! ### Arguments * ``: month of the monthly report * ``: year of the monthly report * `'with-tables'`: (optional), parameter to activate a verbose output of hosts. ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/monthly-report.gmp.py 05 2019 with-tables` --- ## `monthly-report2.gmp.py` This script will display all vulnerabilities from the hosts of the reports in a given month! ### Arguments * ``: month of the monthly report * ``: year of the monthly report ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/monthly-report2.gmp.py 05 2019` --- ## `nvt-scan.gmp.py` This script creates a new task with specific host and nvt! ### Arguments * ``: oid of the nvt * ``: scan target. ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname 1.3.6.1.4.1.25623.1.0.106223 localhost` --- ## `pdf-report.gmp.py` This script requests the given report and saves it as a pdf file locally. ### Arguments * ``: ID of the report * ``: (optional), pdf file name ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/pdf-report.gmp.py ` --- ## `random-report-gen.gmp.py` This script generates randomized report data. ### Arguments * ``: number of tasks to be generated * ``: number of reports per task * ``: number of results per report * ``: number of randomized hosts to select from * `'with-gauss'`: if you would like for the number of reports/task and results/report to be randomized along a Gaussian distribution ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/random-report-gen.gmp.py 10 50 2500 256 with-gauss` --- ## `scan-new-system.gmp.py` This script starts a new scan on the given host. ### Arguments * `` IP Address of the host system ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/scan-new-system.gmp.py ` --- ## `send-delta-emails.gmp.py` This script, once started, will continuously send delta reports via email for selected tasks. The routine follows this procedure: Every `` minutes do: * Get all tasks where the tag `` is attached. * For each of these tasks get the finished reports: * If less than 2 reports, continue with next task * If latest report has tag "delta_alert_sent", continue with next task * Create a CSV report from the delta of latest vs. previous report where filtered for only the new results. * Send the CSV as an attachment to the configured email address. > You may edit the scripts hardcoded variables like `from_address`, `to_address`, etc. ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/send-delta-emails.gmp.py` --- ## `send-schedules.gmp.py` This script pulls schedule data from an xml document and feeds it to a desired GSM. ### Arguments * ``: .xml file containing schedules ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/send-schedules.gmp.py example_file.xml` --- ## `send-targets.gmp.py` This script pulls target data from an xml document and feeds it to a desired GSM. ### Arguments * ``: .xml file containing schedules ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/send-targets.gmp.py example_file.xml` --- ## `send-tasks.gmp.py` This script pulls tasks data from an xml document and feeds it to a desired GSM. ### Arguments * ``: .xml file containing schedules ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/send-tasks.gmp.py example_file.xml` --- ## `start-alert-scan.gmp.py` This script makes an alert scan and sends the report via email. ### Arguments * ``: E-Mail of the sender * ``: E-Mail of the receiver ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/start-alert-scan.gmp.py ` --- ## `start-multiple-alerts-scan.gmp.py` This script makes an alert scan and sends the report via email. ### Arguments * ``: E-Mail of the sender * ``: E-Mail of the receiver ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/start-multiple-alerts-scan.gmp.py ` --- ## `start-nvt-scan.gmp.py` This script creates a new task (if the target is not existing) with specific host and nvt! ### Arguments * ``: oid of the nvt * ``: scan target. ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/start-nvt-scan.gmp.py 1.3.6.1.4.1.25623.1.0.106223 localhost` --- ## `sync-assets.gmp.py` This script reads asset data from a csv file and sync it with the gsm. ### Arguments * ``: should contain a table of IP-addresses with an optional a comment ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/sync-assets.gmp.py ` --- ## `update-task-target.gmp.py` This script will update target hosts information for a desired task. ### Arguments * ``: .csv file containing desired target hosts separated by ',' * ``: uuid of task to be modified ### Example `$ gvm-script --gmp-username name --gmp-password pass ssh --hostname scripts/update-task-target.gmp.py hosts_file.csv "303fa0a6-aa9b-43c4-bac0-66ae0b2d1698"` gvm-tools-23.11.0/scripts/application-detection.gmp.py000066400000000000000000000050311452466721200227170ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import Namespace from gvm.protocols.gmp import Gmp def check_args(args): len_args = len(args.script) - 1 if len_args != 1: message = """ This script will display all hosts with the searched applications! 1. -- Name of the application Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/application-detection.gmp.py """ print(message) sys.exit() def print_assets(gmp, appname): res = gmp.get_reports(details=False) reports = res.xpath("/get_reports_response/report") for report in reports: report_id = report.attrib["id"] print_assets_for_host(gmp, appname, report_id) def print_assets_for_host(gmp, appname, report_id): res = gmp.get_report( report_id, details=True, filter_string="rows=1 result_hosts_only=0" ) hosts = res.xpath("/get_reports_response/report/report/host") for host in hosts: ip = host.xpath("ip/text()") if len(ip) == 0: continue else: ip = ip[0] hostname = host.xpath('detail/name[text()="hostname"]/../value/text()') if len(hostname) == 0: hostname = "" else: hostname = hostname[0] apps = host.xpath( 'detail/name[text() = "App"]/../value[' f'contains(text(), "{appname}")]/text()' ) if len(apps) == 0: continue print(f"{ip} ({hostname})") for app in apps: print("\t" + app) print("\n") def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) print_assets(gmp, args.script[1]) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/bulk-modify-schedules.gmp.py000066400000000000000000000051701452466721200226430ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import Namespace from gvm.protocols.gmp import Gmp def check_args(args): len_args = len(args.script) - 1 if len_args != 3: message = """ This script modifies the timezone and/or icalendar of all schedules in a filter selection. -- the filter text used to filter the schedules. -- the new timezone to set or empty to keep existing one. -- the new icalendar to set or empty to keep existing one. Example: $ gvm-script --gmp-username name --gmp-password pass \\ ssh --hostname scripts/bulk-modify-schedules.gmp.py \\ """ print(message) sys.exit() def bulk_modify_schedules(gmp, filter_term, new_timezone, new_icalendar): get_response = gmp.get_schedules(filter=filter_term) schedules = get_response.findall("schedule") for schedule in schedules: uuid = schedule.attrib["id"] name = schedule.find("name").text comment = schedule.find("comment").text if new_timezone: timezone = new_timezone else: timezone = schedule.find("timezone").text if new_icalendar: icalendar = new_icalendar else: icalendar = schedule.find("icalendar").text print(f"- Modifying {name} ({uuid})") gmp.modify_schedule( uuid, name=name, comment=comment, timezone=timezone, icalendar=icalendar, ) def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=unused-argument check_args(args) filter_term = args.script[1] new_timezone = args.script[2] new_icalendar = args.script[3] bulk_modify_schedules(gmp, filter_term, new_timezone, new_icalendar) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/certbund-report.gmp.py000066400000000000000000000262671452466721200215750ustar00rootroot00000000000000# -*- coding: utf-8 -*- # - # Copyright © 2022 Thorsten Glaser # Licensor: Greenbone Networks GmbH # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # List vulnerabilities with CERT-BUND IDs and severities from a report, # per host and CERT-BUND advisory. import os import re import sys from argparse import ArgumentParser, Namespace from itertools import zip_longest from typing import Dict, List, Optional, Sequence, Tuple, TypeVar, overload import ssv_csv from gvm.errors import GvmResponseError from gvm.protocols.gmp import Gmp # from gvm.xml import pretty_print sys.path.append(os.path.dirname(args.argv[0])) # type: ignore class _Row(Dict, total=False): host: str port: str hostname: str name: str severity: str cves: str cb: List[str] class _Host(Dict, total=False): ip: str name: str operating_system: str class _CBund(Dict, total=False): severity: str title: str @overload def _get_text(e, other: str) -> str: """Signature of _get_text if other is str""" @overload def _get_text(e, other: None = None) -> Optional[str]: """Signature of _get_text if other is None""" def _get_text(e, other: Optional[str] = None) -> Optional[str]: """Return (recursive) inner text of element if truthy""" if e is not None: text = "".join(e.itertext()) text = text.strip() if text: return text return other @overload def _assign( tgt: _Host, tgtfield: str, src: Dict[str, Optional[str]], srcfield: str ) -> None: """Signature of _assign for _Host targets""" @overload def _assign( tgt: Dict[str, str], tgtfield: str, src: Dict[str, Optional[str]], srcfield: str, ) -> None: """Signature of _assign for Dict[str, str] targets""" def _assign( tgt, tgtfield: str, src: Dict[str, Optional[str]], srcfield: str ) -> None: """Assign src[srcfield] to tgt[tgtfield] if extant and truthy""" if srcfield in src: srcval = src[srcfield] if srcval: tgt[tgtfield] = srcval def _info(string: str) -> None: print("I:", string, file=sys.stderr) def _warn(string: str) -> None: print("W:", string, file=sys.stderr) def _err(string: str) -> None: print("E:", string, file=sys.stderr) T = TypeVar("T") def _group_batch( tutti: Sequence[T], batch_size: int ) -> Sequence[Tuple[Optional[T], ...]]: iterlist = [iter(tutti)] * batch_size return list(zip_longest(*iterlist)) # validate CERT-BUND ID _cb_id_match = re.compile("CB-K[0-9]+/[0-9]+") # encode for filter_string def _cb_fmt(cbid: str) -> str: if _cb_id_match.fullmatch(cbid) is not None: return "uuid=" + cbid _warn(f"invalid CERT-BUND ID: {cbid}") return "" def main(gmp: Gmp, args: Namespace) -> None: raw_args = [] + args.argv[1:] + args.script_args parser = ArgumentParser( prog="certbund-report.gmp.py", description="Displays CERT-Bund advisories for vulnerabilities.", epilog=( "Usage: gvm-script [opts] connection_type " "certbund-report.gmp.py [Options] ID" ), add_help=False, ) ogroup = parser.add_argument_group("Options") ogroup.add_argument( "-H", action="help", help="show this help message and exit" ) ogroup.add_argument( "-o", "--output", metavar="outfile", help='write to this CSV file, "-" for stdout (default)', default="-", ) ogroup.add_argument( "-r", "--report", action="store_true", help="ID is a report ID, not a task ID", ) agroup = parser.add_argument_group("Arguments") agroup.add_argument("ID", help="task (or report) ID to analyse") script_args = parser.parse_args(raw_args) if script_args.report: report_id = script_args.ID else: task_id = script_args.ID _info("obtaining task") try: task = gmp.get_task(task_id) except GvmResponseError as e: if e.status != "404": raise e _err(f"task {task_id} not found") sys.exit(1) try: task_report = task.xpath( "/get_tasks_response/task[1]/last_report/report[1]" ) report_id = task_report[0].get("id") except IndexError: _err("task does not have any (finished) report") sys.exit(1) _info("obtaining report") try: report = gmp.get_report(report_id, ignore_pagination=True, details=True) except GvmResponseError as e: if e.status != "404": raise e _err(f"report {report_id} not found") sys.exit(1) # with open("report.xml", "w", encoding="utf-8") as rf: # pretty_print(report, file=rf) ### gather data # + host IP hosts[row['host']]['ip'] # - vuln port row['port'] # - host name row.get('hostname', # hosts[row['host']].get('name', 'N/A')) # - host OS hosts[row['host']].get('os', 'N/A') # + vuln name row['name'] # + vuln severity row['severity'] # + vuln CVEs row['cves'] # + bund ID row['cb'] : list(str) # + bund severity cbund[…].get('severity', 'N/A') # - bund title cbund[…].get('title', 'N/A') orows: List[_Row] = [] hosts: Dict[str, _Host] = {} cbund: Dict[str, _CBund] = {} results = report.xpath( "/get_reports_response/report/report/results/" 'result[./nvt/refs/ref/@type="cert-bund"]' ) # pretty_print(results) _info(f"processing {len(results)} results") for result in results: orow: _Row = {} r_host = result.find("host") asset = r_host.find("asset").attrib["asset_id"] hosts[asset] = {"ip": r_host.text} # more filled in later orow["host"] = asset r_hostname = _get_text(r_host.find("hostname")) if r_hostname: orow["hostname"] = r_hostname orow["port"] = _get_text(result.find("port"), "N/A") orow["name"] = _get_text(result.find("name"), "N/A") orow["severity"] = _get_text(result.find("severity"), "N/A") r_cve: List[str] = [] r_cb: List[str] = [] for ref in result.find("nvt").find("refs").findall("ref"): if ref.attrib["type"] == "cve": r_cve.append(ref.attrib["id"]) elif ref.attrib["type"] == "cert-bund": cbid = ref.attrib["id"] r_cb.append(cbid) cbund[cbid] = {} # more filled in later orow["cves"] = ", ".join(r_cve) orow["cb"] = r_cb orows.append(orow) hostdatas = report.xpath("/get_reports_response/report/report/host") # pretty_print(hostdatas) _info(f"processing {len(hosts)}/{len(hostdatas)} hosts") for hostdata in hostdatas: asset = hostdata.find("asset").attrib["asset_id"] if asset not in hosts: continue details: Dict[str, Optional[str]] = {} details["ip"] = _get_text(hostdata.find("ip")) for detail in hostdata.findall("detail"): dname = _get_text(detail.find("name")) if dname in ("best_os_cpe", "hostname", "OS"): details[dname] = _get_text(detail.find("value")) hostent: _Host = hosts[asset] _assign(hostent, "ip", details, "ip") _assign(hostent, "name", details, "hostname") # try best_os_cpe first but overwrite with OS if better _assign(hostent, "os", details, "best_os_cpe") _assign(hostent, "os", details, "OS") ### retrieve CERT-BUND Advisories _info(f"retrieving {len(cbund)} CERT-BUND advisories") # one-by-one # cb_retrieve_problem = False # for id, cbdata in cbund.items(): # try: # cb = gmp.get_cert_bund_advisory(id).find( # 'info' # ).find('cert_bund_adv') # cbdata['severity'] = _get_text(cb.find('severity'), 'N/A') # cbdata['title'] = _get_text(cb.find('title'), 'N/A') # except GvmResponseError as e: # if e.status != '404': # raise e # cb_retrieve_problem = True # batched for cb_batch in _group_batch(list(cbund.keys()), 50): actual_batch = [x for x in cb_batch if x is not None] fstr = " ".join(map(_cb_fmt, actual_batch)) + " first=1 rows=-1" try: cbs = gmp.get_cert_bund_advisories(filter_string=fstr) except GvmResponseError as e: if e.status != "404": raise e # warn below continue for cbi in cbs.findall("info"): if "id" not in cbi.attrib: # we have both (which we want) # and, for some reason, continue cbid = cbi.attrib["id"] if cbid in cbund: cb = cbi.find("cert_bund_adv") cbund[cbid]["severity"] = _get_text(cb.find("severity"), "N/A") cbund[cbid]["title"] = _get_text(cb.find("title"), "N/A") cb_retrieve_problem = {} ### output _info("emitting CSV") if script_args.o == "-": outfile = sys.stdout else: outfile = open(script_args.o, "w", encoding="utf-8") writer = ssv_csv.CSVWriter(outfile, sep=",") writer.writeln("sep=,") writer.write( "IP", "Port", "Hostname", "OS", "Vulnerability", "Severity", "CVEs", "CertBUND-ID", "CertBUND-Severity", "CertBUND-Title", ) for row in orows: ip = hosts[row["host"]]["ip"] port = row["port"] hname = row.get("hostname", hosts[row["host"]].get("name", "N/A")) operating_system = hosts[row["host"]].get("os", "N/A") vname = row["name"] vsev = row["severity"] cves = row["cves"] for cb in row["cb"]: if "severity" in cbund[cb]: cbsev = cbund[cb]["severity"] cbtitle = cbund[cb]["title"] else: cb_retrieve_problem[cb] = 1 cbsev = "N/A" cbtitle = "N/A (could not be retrieved)" writer.write( ip, port, hname, operating_system, vname, vsev, cves, cb, cbsev, cbtitle, ) cb_nproblems = len(cb_retrieve_problem) if cb_nproblems > 0: _warn(f"{cb_nproblems} CERT-BUND advisories could not be obtained") else: _info("done") if __name__ == "__gmp__": main(gmp, args) # type: ignore gvm-tools-23.11.0/scripts/cfg-gen-for-certs.gmp.py000066400000000000000000000065321452466721200216570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import Namespace from gvm.errors import GvmError from gvm.protocols.gmp import Gmp def check_args(args): len_args = len(args.script) - 1 if len_args != 1: message = """ This script creates a new scan config with nvts from a given CERT-Bund! It needs one parameter after the script name. 1. -- Name or ID of the CERT-Bund Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/cfg-gen-for-certs.gmp.py CB-K16/0943 """ print(message) sys.exit() def create_scan_config(gmp, cert_bund_name): cert_bund_details = gmp.get_info( info_id=cert_bund_name, info_type=gmp.types.InfoType.CERT_BUND_ADV ) list_cves = cert_bund_details.xpath( "info/cert_bund_adv/raw_data/Advisory/CVEList/CVE/text()" ) nvt_dict = dict() counter = 0 for cve in list_cves: # Get all nvts of this cve cve_info = gmp.get_info(info_id=cve, info_type=gmp.types.InfoType.CVE) nvts = cve_info.xpath("info/cve/nvts/nvt") for nvt in nvts: counter += 1 oid = nvt.xpath("@oid")[0] # We need the nvt family to modify scan config nvt_data = gmp.get_scan_config_nvt(oid) family = nvt_data.xpath("nvt/family/text()")[0] # Create key value map if family in nvt_dict and oid not in nvt_dict[family]: nvt_dict[family].append(oid) else: nvt_dict[family] = [oid] # Create new config copy_id = "085569ce-73ed-11df-83c3-002264764cea" config_name = f"scanconfig_for_{cert_bund_name}" config_id = "" try: res = gmp.create_scan_config(copy_id, config_name) config_id = res.xpath("@id")[0] # Modify the config with the nvts oid for family, nvt_oid in nvt_dict.items(): gmp.modify_scan_config( config_id=config_id, nvt_oids=nvt_oid, family=family ) # This nvts must be present to work family = "Port scanners" nvts = ["1.3.6.1.4.1.25623.1.0.14259", "1.3.6.1.4.1.25623.1.0.100315"] gmp.modify_scan_config( config_id=config_id, nvt_oids=nvts, family=family ) except GvmError: print("Config exist") def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) cert_bund_name = args.script[1] print(f"Creating scan config for {cert_bund_name}") create_scan_config(gmp, cert_bund_name) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/check-gmp.gmp.py000066400000000000000000001230161452466721200203020ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # pylint: disable=too-many-lines import logging import os import re import signal import sqlite3 import sys import tempfile from argparse import ArgumentParser, Namespace, RawTextHelpFormatter from datetime import datetime, timedelta, tzinfo from decimal import Decimal from pathlib import Path from gvm.protocols.gmp import Gmp from lxml import etree __version__ = "21.7.0" logger = logging.getLogger(__name__) HELP_TEXT = f""" Check-GMP Nagios Command Plugin {__version__} (C) 2017-2021 Greenbone AG This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ NAGIOS_OK = 0 NAGIOS_WARNING = 1 NAGIOS_CRITICAL = 2 NAGIOS_UNKNOWN = 3 NAGIOS_MSG = ["OK", "WARNING", "CRITICAL", "UNKNOWN"] MAX_RUNNING_INSTANCES = 10 class InstanceManager: """Class for managing instances of this plugin All new reports will be cached in a sqlite database. The first call with a unknown host takes longer, because the remote gvmd/openvasmd has to generate the report. The second call will retrieve the data from the database if the scan duration does not differ. Additionally this class handles all instances of check-gmp. No more than MAX_RUNNING_INSTANCES can run simultaneously. Other instances are stopped and wait for continuation. """ def __init__(self, path, parser): """Initialise the sqlite database. Create it if it does not exist else connect to it. Arguments: path (string): Path to the database. """ self.cursor = None self.con_db = None self.db = Path(path) self.pid = os.getpid() # Try to read file with information about cached reports # First check whether the file exist or not try: exist = self.db.is_file() logger.debug("DB file exist?: %s ", exist) if not exist: if not self.db.parent.is_dir(): self.db.parent.mkdir(parents=True, exist_ok=True) else: self.db.touch() # Connect to db self.connect_db() # Create the tables self.cursor.execute( """CREATE TABLE Report( host text, scan_end text, params_used text, report text )""" ) self.cursor.execute( """CREATE TABLE Instance( created_at text, pid integer, pending integer default 0 )""" ) logger.debug("Tables created") else: self.connect_db() except PermissionError: parser.error( f"The selected temporary database file {self.db} or the parent " "dir has not the correct permissions." ) @staticmethod def _to_sql_bool(pending): """Replace True/False with 1/0.""" return "1" if pending else "0" def connect_db(self): """Connect to the database Simply connect to the database at location """ try: logger.debug("connect db: %s", self.db) self.con_db = sqlite3.connect(str(self.db)) self.cursor = self.con_db.cursor() logger.debug(sqlite3.sqlite_version) except Exception as e: # pylint: disable=broad-except logger.debug(e) def close_db(self): """Close database""" self.con_db.close() def set_host(self, host): """Sets the host variable Arguments: host (string): Given ip or hostname of target. """ self.host = host def is_old_report(self, last_scan_end, params_used): """Decide whether the current report is old or not At first the last scanend and the params that were used are fetched from the database. If no report is fetched, then True will be returned. The next step is to compare the old and the new scanend. If the scanends matches, then return False, because it is the same report. Else the old report will be deleted. Arguments: last_scan_end (string): Last scan end of report params_used (string): Params used for this check Returns: True if it is an old report or empty. False if it is the same report. """ # Before we do anything here, check existing instance # Retrieve the scan_end value self.cursor.execute( "SELECT scan_end, params_used FROM Report WHERE host=?", (self.host,), ) db_entry = self.cursor.fetchone() logger.debug("%s %s", db_entry, last_scan_end) if not db_entry: return True else: old = parse_date(db_entry[0]) new = parse_date(last_scan_end) logger.debug( "Old time (from db): %s\nNew time (from rp): %s", old, new ) if new <= old and params_used == db_entry[1]: return False else: # Report is newer. Delete old entry. logger.debug("Delete old report for host %s", self.host) self.delete_report() return True def load_local_report(self): """Load report from local database Select the report from the database according due the hostname or ip. Returns: An lxml ElementTree """ self.cursor.execute( "SELECT report FROM Report WHERE host=?", (self.host,) ) db_entry = self.cursor.fetchone() if db_entry: return etree.fromstring(db_entry[0]) else: logger.debug("Report from host %s is not in the db", self.host) def add_report(self, scan_end, params_used, report): """Create new entry with the lxml report Create a string from the lxml object and add it to the database. Additional data is the scanend and the params used. Arguments: scan_end (string): Scan end of the report params_used (string): Params used for this check report (obj): An lxml ElementTree """ data = etree.tostring(report) logger.debug("add_report: %s, %s, %s", self.host, scan_end, params_used) # Insert values self.cursor.execute( "INSERT INTO Report VALUES (?, ?, ?, ?)", (self.host, scan_end, params_used, data), ) # Save the changes self.con_db.commit() def delete_report(self): """Delete report from database""" self.cursor.execute("DELETE FROM Report WHERE host=?", (self.host,)) # Save the changes self.con_db.commit() def delete_entry_with_ip(self, ip): """Delete report from database with given ip Arguments: ip (string): IP-Adress """ logger.debug("Delete entry with ip: %s", ip) self.cursor.execute("DELETE FROM Report WHERE host=?", (ip,)) self.con_db.isolation_level = None self.cursor.execute("VACUUM") self.con_db.isolation_level = "" # see: https://github.com/CxAalto/gtfspy/commit/8d05c3c94a6d4ca3ed675d88af93def7d5053bfe # pylint: disable=line-too-long # noqa: E501 # Save the changes self.con_db.commit() def delete_older_entries(self, days): """Delete reports from database older than given days Arguments: days (int): Number of days in past """ logger.debug("Delete entries older than: %s days", days) self.cursor.execute( "DELETE FROM Report WHERE scan_end <= " f'date("now", "-{days} day")' ) self.cursor.execute("VACUUM") # Save the changes self.con_db.commit() def has_entries(self, pending): """Return number of instance entries Arguments: pending (bool): True for pending instances. False for running instances. Returns: The number of pending or non pending instances entries. """ self.cursor.execute( "SELECT count(*) FROM Instance WHERE pending=?", (self._to_sql_bool(pending),), ) res = self.cursor.fetchone() return res[0] def check_instances(self): """This method checks the status of check-gmp instances. Checks whether instances are pending or not and start instances according to the number saved in the MAX_RUNNING_INSTANCES variable. """ # Need to check whether any instances are in the database that were # killed f.e. because a restart of nagios self.clean_orphaned_instances() # How many processes are currently running? number_instances = self.has_entries(pending=False) # How many pending entries are waiting? number_pending_instances = self.has_entries(pending=True) logger.debug( "check_instances: %i %i", number_instances, number_pending_instances ) if ( number_instances < MAX_RUNNING_INSTANCES and number_pending_instances == 0 ): # Add entry for running process and go on logger.debug("Fall 1") self.add_instance(pending=False) elif ( number_instances < MAX_RUNNING_INSTANCES and number_pending_instances > 0 ): # Change pending entries and wake them up until enough instances # are running logger.debug("Fall 2") while ( number_instances < MAX_RUNNING_INSTANCES and number_pending_instances > 0 ): pending_entries = self.get_oldest_pending_entries( MAX_RUNNING_INSTANCES - number_instances ) logger.debug("Oldest pending pids: %s", pending_entries) for entry in pending_entries: created_at = entry[0] pid = entry[1] # Change status to not pending and continue the process self.update_pending_status(created_at, False) self.start_process(pid) # Refresh number of instances for next while loop number_instances = self.has_entries(pending=False) number_pending_instances = self.has_entries(pending=True) # TODO: Check if this is really necessary # self.add_instance(pending=False) # if number_instances >= MAX_RUNNING_INSTANCES: # self.stop_process(self.pid) elif ( number_instances >= MAX_RUNNING_INSTANCES and number_pending_instances == 0 ): # There are running enough instances and no pending instances # Add new entry with pending status true and stop this instance logger.debug("Fall 3") self.add_instance(pending=True) self.stop_process(self.pid) elif ( number_instances >= MAX_RUNNING_INSTANCES and number_pending_instances > 0 ): # There are running enough instances and there are min one # pending instance # Add new entry with pending true and stop this instance logger.debug("Fall 4") self.add_instance(pending=True) self.stop_process(self.pid) # If an entry is pending and the same params at another process is # starting, then exit with gmp pending since data # if self.has_pending_entries(): # Check if an pending entry is the same as this process # If hostname # date = datetime.now() # end_session('GMP PENDING: since %s' % date, NAGIOS_OK) # end_session('GMP RUNNING: since', NAGIOS_OK) def add_instance(self, pending): """Add new instance entry to database Retrieve the current time in ISO 8601 format. Create a new entry with pending status and the dedicated pid Arguments: pending (bool): State of instance """ current_time = datetime.now().isoformat() # Insert values self.cursor.execute( "INSERT INTO Instance VALUES (?, ?, ?)", (current_time, self.pid, self._to_sql_bool(pending)), ) # Save the changes self.con_db.commit() def get_oldest_pending_entries(self, number): """Return the oldest last entries of pending entries from database Return: the oldest instances with status pending limited by the variable """ self.cursor.execute( "SELECT * FROM Instance WHERE pending=1 ORDER BY " "created_at LIMIT ? ", (number,), ) return self.cursor.fetchall() def update_pending_status(self, date, pending): """Update pending status of instance The date variable works as a primary key for the instance table. The entry with date get his pending status updated. Arguments: date (string): Date of creation for entry pending (bool): Status of instance """ self.cursor.execute( "UPDATE Instance SET pending=? WHERE created_at=?", (self._to_sql_bool(pending), date), ) # Save the changes self.con_db.commit() def delete_instance(self, pid=None): """Delete instance from database If a pid different from zero is given, then delete the entry with given pid. Else delete the entry with the pid stored in this class instance. Keyword Arguments: pid (number): Process Indentificattion Number (default: {0}) """ if not pid: pid = self.pid logger.debug("Delete entry with pid: %i", pid) self.cursor.execute("DELETE FROM Instance WHERE pid=?", (pid,)) # Save the changes self.con_db.commit() def clean_orphaned_instances(self): """Delete non existing instance entries This method check whether a pid exist on the os and if not then delete the orphaned entry from database. """ self.cursor.execute("SELECT pid FROM Instance") pids = self.cursor.fetchall() for pid in pids: if not self.check_pid(pid[0]): self.delete_instance(pid[0]) def wake_instance(self): """Wake up a pending instance This method is called at the end of any session from check_gmp. Get the oldest pending entries and wake them up. """ # How many processes are currently running? number_instances = self.has_entries(pending=False) # How many pending entries are waiting? number_pending_instances = self.has_entries(pending=True) if ( number_instances < MAX_RUNNING_INSTANCES and number_pending_instances > 0 ): pending_entries = self.get_oldest_pending_entries( MAX_RUNNING_INSTANCES - number_instances ) logger.debug( "wake_instance: %i %i", number_instances, number_pending_instances, ) for entry in pending_entries: created_at = entry[0] pid = entry[1] # Change status to not pending and continue the process self.update_pending_status(created_at, False) self.start_process(pid) def start_process(self, pid): """Continue a stopped process Send a continue signal to the process with given pid Arguments: pid (int): Process Identification Number """ logger.debug("Continue pid: %i", pid) os.kill(pid, signal.SIGCONT) def stop_process(self, pid): """Stop a running process Send a stop signal to the process with given pid Arguments: pid (int): Process Identification Number """ os.kill(pid, signal.SIGSTOP) def check_pid(self, pid): """Check for the existence of a process. Arguments: pid (int): Process Identification Number """ try: os.kill(pid, 0) except OSError: return False else: return True def ping(gmp, im): """Checks for connectivity This function sends the get_version command and checks whether the status is ok or not. """ version = gmp.get_version() version_status = version.xpath("@status") if "200" in version_status: end_session(im, "GMP OK: Ping successful", NAGIOS_OK) else: end_session(im, "GMP CRITICAL: Machine dead?", NAGIOS_CRITICAL) def status(gmp, im, script_args): """Returns the current status of a host This functions return the current state of a host. Either directly over the host management or within a task. For a task you can explicitly ask for the trend. Otherwise the last report of the task will be filtered. In the host management the report id in the details is taken as report for the filter. If the host information contains any vulnerabilities, then will the report be filtered too. With additional parameters it is possible to add more information about the vulnerabilities. * DFN-Certs * Logs * Autofp * Scanend * Overrides """ params_used = ( f"task={script_args.task} autofp={script_args.autofp} " f"overrides={script_args.overrides} " f"apply_overrides={script_args.apply_overrides}" ) if script_args.task: task = gmp.get_tasks( filter_string=( "permission=any owner=any rows=1 " f'name="{script_args.task}"' ) ) if script_args.trend: trend = task.xpath("task/trend/text()") if not trend: end_session( im, "GMP UNKNOWN: Trend is not available.", NAGIOS_UNKNOWN ) trend = trend[0] if trend in ["up", "more"]: end_session( im, f"GMP CRITICAL: Trend is {trend}.", NAGIOS_CRITICAL ) elif trend in ["down", "same", "less"]: end_session(im, f"GMP OK: Trend is {trend}.", NAGIOS_OK) else: end_session( im, f"GMP UNKNOWN: Trend is unknown: {trend}", NAGIOS_UNKNOWN, ) else: last_report_id = task.xpath("task/last_report/report/@id") if not last_report_id: end_session( im, "GMP UNKNOWN: Report is not available", NAGIOS_UNKNOWN ) last_report_id = last_report_id[0] last_scan_end = task.xpath( "task/last_report/report/scan_end/text()" ) if last_scan_end: last_scan_end = last_scan_end[0] else: last_scan_end = "" if im.is_old_report(last_scan_end, params_used): host = script_args.hostaddress full_report = gmp.get_report( report_id=last_report_id, filter_string=( "sort-reverse=id result_hosts_only=1 min_cvss_base= " f"min_qod= levels=hmlgd autofp={script_args.autofp} " "notes=0 " f"apply_overrides={script_args.apply_overrides} " f"overrides={script_args.overrides} first=1 rows=-1 " f"delta_states=cgns host={host}" ), details=True, ) im.add_report(last_scan_end, params_used, full_report) logger.debug("Report added to db") else: full_report = im.load_local_report() filter_report( im, full_report.xpath("report/report")[0], script_args ) def filter_report(im, report, script_args): """Filter out the information in a report This function filters the results of a given report. Arguments: report (obj): Report as lxml ElementTree. """ report_id = report.xpath("@id") if report_id: report_id = report_id[0] results = report.xpath("//results") if not results: end_session( im, "GMP UNKNOWN: Failed to get results list", NAGIOS_UNKNOWN ) results = results[0] # Init variables any_found = False high_count = 0 medium_count = 0 low_count = 0 log_count = 0 error_count = 0 nvts = {"high": [], "medium": [], "low": [], "log": []} all_results = results.xpath("result") for result in all_results: if script_args.hostaddress: host = result.xpath("host/text()") if not host: end_session( im, "GMP UNKNOWN: Failed to parse result host", NAGIOS_UNKNOWN, ) if script_args.hostaddress != host[0]: continue any_found = True threat = result.xpath("threat/text()") if not threat: end_session( im, "GMP UNKNOWN: Failed to parse result threat.", NAGIOS_UNKNOWN, ) threat = threat[0] if threat in "High": high_count += 1 if script_args.oid: nvts["high"].append(retrieve_nvt_data(result)) elif threat in "Medium": medium_count += 1 if script_args.oid: nvts["medium"].append(retrieve_nvt_data(result)) elif threat in "Low": low_count += 1 if script_args.oid: nvts["low"].append(retrieve_nvt_data(result)) elif threat in "Log": log_count += 1 if script_args.oid: nvts["log"].append(retrieve_nvt_data(result)) else: end_session( im, f"GMP UNKNOWN: Unknown result threat: {threat}", NAGIOS_UNKNOWN, ) errors = report.xpath("errors") if errors: errors = errors[0] if script_args.hostaddress: for error in errors.xpath("error"): host = error.xpath("host/text()") if script_args.hostaddress == host[0]: error_count += 1 else: error_count = errors.xpath("count/text()")[0] ret = 0 if high_count > 0: ret = NAGIOS_CRITICAL elif medium_count > 0: ret = NAGIOS_WARNING if script_args.empty_as_unknown and ( not all_results or (not any_found and script_args.hostaddress) ): ret = NAGIOS_UNKNOWN print( f"GMP {NAGIOS_MSG[ret]}: " f"{str((high_count + medium_count + low_count))} " f"vulnerabilities found - High: {str(high_count)} " f"Medium: {str(medium_count)} Low: {str(low_count)}" ) if not all_results: print("Report did not contain any vulnerabilities") elif not any_found and script_args.hostaddress: print( "Report did not contain vulnerabilities " f"for IP {script_args.hostaddress}" ) if int(error_count) > 0: if script_args.hostaddress: print_without_pipe( f"Report did contain {str(error_count)} " f"errors for IP {script_args.hostaddress}" ) else: print_without_pipe(f"Report did contain {error_count} errors") if script_args.report_link: print( f"https://{script_args.hostname}/omp" f"?cmd=get_report&report_id={report_id}" ) if script_args.oid: print_nvt_data( nvts, show_log=script_args.showlog, show_ports=script_args.show_ports, descr=script_args.descr, dfn=script_args.dfn, ) if script_args.scanend: end = report.xpath("//end/text()") end = end[0] if end else "Timestamp of scan end not given" print(f"SCAN_END: {end}") if script_args.details: if script_args.hostname: print(f"GSM_Host: {script_args.hostname}:{str(script_args.port)}") if script_args.gmp_username: print(f"GMP_User: {script_args.gmp_username}") if script_args.task: print_without_pipe(f"Task: {script_args.task}") end_session( im, f"|High={str(high_count)} " f"Medium={str(medium_count)} " f"Low={str(low_count)}", ret, ) def retrieve_nvt_data(result): """Retrieve the nvt data out of the result object This function parse the xml tree to find the important nvt data. Arguments: result (obj): Result as lxml ElementTree Returns: Tuple -- List with oid, name, desc, port and dfn """ oid = result.xpath("nvt/@oid") name = result.xpath("nvt/name/text()") desc = result.xpath("description/text()") port = result.xpath("port/text()") if oid: oid = oid[0] if name: name = name[0] if desc: desc = desc[0] else: desc = "" if port: port = port[0] else: port = "" certs = result.xpath("nvt/cert/cert_ref") dfn_list = [] for ref in certs: ref_type = ref.xpath("@type")[0] ref_id = ref.xpath("@id")[0] if ref_type in "DFN-CERT": dfn_list.append(ref_id) return (oid, name, desc, port, dfn_list) def print_nvt_data( nvts, show_log=False, show_ports=False, descr=False, dfn=False ): """Print nvt data Prints for each nvt found in the array the relevant data Arguments: nvts (obj): Object holding all nvts """ for key, nvt_data in nvts.items(): if key == "log" and not show_log: continue for nvt in nvt_data: print_without_pipe(f"NVT: {nvt[0]} ({key}) {nvt[1]}") if show_ports: print_without_pipe(f"PORT: {nvt[3]}") if descr: print_without_pipe(f"DESCR: {nvt[2]}") if dfn and nvt[4]: dfn_list = ", ".join(nvt[4]) if dfn_list: print_without_pipe(f"DFN-CERT: {dfn_list}") def end_session(im, msg, nagios_status): """End the session Close the socket if open and print the last msg Arguments: msg string): Message to print nagios_status (int): Exit status """ print(msg) # Delete this instance im.delete_instance() # Activate some waiting instances if possible im.wake_instance() # Close the connection to database im.close_db() sys.exit(nagios_status) def print_without_pipe(msg): """Prints the message, but without any pipe symbol If any pipe symbol is in the msg string, then it will be replaced with broken pipe symbol. Arguments: msg (string): Message to print """ if "|" in msg: msg = msg.replace("|", "¦") print(msg) # ISO 8601 date time string parsing # Copyright (c) 2007 - 2015 Michael Twomey # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. __all__ = ["parse_date", "ParseError", "UTC"] # Adapted from http://delete.me.uk/2005/03/iso8601.html ISO8601_REGEX = re.compile( r""" (?P[0-9]{4}) ( ( (-(?P[0-9]{1,2})) | (?P[0-9]{2}) (?!$) # Don't allow YYYYMM ) ( ( (-(?P[0-9]{1,2})) | (?P[0-9]{2}) ) ( ( (?P[ T]) (?P[0-9]{2}) (:{0,1}(?P[0-9]{2})){0,1} ( :{0,1}(?P[0-9]{1,2}) ([.,](?P[0-9]+)){0,1} ){0,1} (?P Z | ( (?P[-+]) (?P[0-9]{2}) :{0,1} (?P[0-9]{2}){0,1} ) ){0,1} ){0,1} ) ){0,1} # YYYY-MM ){0,1} # YYYY only $ """, re.VERBOSE, ) class ParseError(Exception): """Raised when there is a problem parsing a date string""" # Yoinked from python docs ZERO = timedelta(0) class Utc(tzinfo): """UTC Timezone""" def utcoffset(self, dt): return ZERO def tzname(self, dt): return "UTC" def dst(self, dt): return ZERO def __repr__(self): return "" UTC = Utc() class FixedOffset(tzinfo): """Fixed offset in hours and minutes from UTC""" def __init__(self, offset_hours, offset_minutes, name): self.__offset_hours = offset_hours # Keep for later __getinitargs__ # Keep for later __getinitargs__ self.__offset_minutes = offset_minutes self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes) self.__name = name def __eq__(self, other): if isinstance(other, FixedOffset): # pylint: disable=protected-access return (other.__offset == self.__offset) and ( other.__name == self.__name ) if isinstance(other, tzinfo): return other == self return False def __getinitargs__(self): return (self.__offset_hours, self.__offset_minutes, self.__name) def utcoffset(self, dt): return self.__offset def tzname(self, dt): return self.__name def dst(self, dt): return ZERO def __repr__(self): return f"" def to_int( source_dict, key, default_to_zero=False, default=None, required=True ): """Pull a value from the dict and convert to int :param default_to_zero: If the value is None or empty, treat it as zero :param default: If the value is missing in the dict use this default """ value = source_dict.get(key) if value in [None, ""]: value = default if (value in ["", None]) and default_to_zero: return 0 if value is None: if required: raise ParseError(f"Unable to read {key} from {source_dict}") return value else: return int(value) def parse_timezone(matches, default_timezone=UTC): """Parses ISO 8601 time zone specs into tzinfo offsets""" if matches["timezone"] == "Z": return UTC # This isn't strictly correct, but it's common to encounter dates without # timezones so I'll assume the default (which defaults to UTC). # Addresses issue 4. if matches["timezone"] is None: return default_timezone sign = matches["tz_sign"] hours = to_int(matches, "tz_hour") minutes = to_int(matches, "tz_minute", default_to_zero=True) description = f"{sign}{str(hours)}:{str(minutes)}" if sign == "-": hours = -1 * hours minutes = -1 * minutes return FixedOffset(hours, minutes, description) def parse_date(datestring, default_timezone=UTC): """Parses ISO 8601 dates into datetime objects The timezone is parsed from the date string. However it is quite common to have dates without a timezone (not strictly correct). In this case the default timezone specified in default_timezone is used. This is UTC by default. Arguments datestring: The date to parse as a string default_timezone: A datetime tzinfo instance to use when no timezone is specified in the datestring. If this is set to None then a naive datetime object is returned. Returns: A datetime.datetime instance Raises: ParseError when there is a problem parsing the date or constructing the datetime instance. """ if not isinstance(datestring, str): raise ParseError(f"Expecting a string {datestring}") match = ISO8601_REGEX.match(datestring) if not match: raise ParseError(f"Unable to parse date string {datestring}") groups = match.groupdict() tz = parse_timezone(groups, default_timezone=default_timezone) groups["second_fraction"] = int( Decimal(f"0.{groups['second_fraction'] or 0}") * Decimal("1000000.0") ) try: return datetime( year=to_int(groups, "year"), month=to_int( groups, "month", default=to_int(groups, "monthdash", required=False, default=1), ), day=to_int( groups, "day", default=to_int(groups, "daydash", required=False, default=1), ), hour=to_int(groups, "hour", default_to_zero=True), minute=to_int(groups, "minute", default_to_zero=True), second=to_int(groups, "second", default_to_zero=True), microsecond=groups["second_fraction"], tzinfo=tz, ) except Exception as e: raise ParseError(e) from None def main(gmp: Gmp, args: Namespace) -> None: tmp_path = f"{tempfile.gettempdir()}/check_gmp/" tmp_path_db = tmp_path + "reports.db" prog = "check-gmp" parser = ArgumentParser( prog=prog, prefix_chars="-", description=HELP_TEXT, formatter_class=RawTextHelpFormatter, add_help=False, epilog=""" usage: gvm-script [connection_type] check-gmp.gmp.py ... or: gvm-script [connection_type] check-gmp.gmp.py -H or: gvm-script connection_type --help""", ) parser.add_argument( "-H", action="help", help="Show this help message and exit." ) parser.add_argument( "-V", "--version", action="version", version=f"{prog} {__version__}", help="Show program's version number and exit", ) parser.add_argument( "--cache", nargs="?", default=tmp_path_db, help=f"Path to cache file. Default: {tmp_path_db}.", ) parser.add_argument( "--clean", action="store_true", help="Activate to clean the database." ) parser.add_argument( "-u", "--gmp-username", help="GMP username.", required=False ) parser.add_argument( "-w", "--gmp-password", help="GMP password.", required=False ) parser.add_argument( "-F", "--hostaddress", required=False, default="", help="Report last report status of host .", ) parser.add_argument( "-T", "--task", required=False, help="Report status of task ." ) parser.add_argument( "--apply-overrides", action="store_true", help="Apply overrides." ) parser.add_argument( "--overrides", action="store_true", help="Include overrides." ) parser.add_argument( "-d", "--details", action="store_true", help="Include connection details in output.", ) parser.add_argument( "-l", "--report-link", action="store_true", help="Include URL of report in output.", ) parser.add_argument( "--dfn", action="store_true", help="Include DFN-CERT IDs on vulnerabilities in output.", ) parser.add_argument( "--oid", action="store_true", help="Include OIDs of NVTs finding vulnerabilities in output.", ) parser.add_argument( "--descr", action="store_true", help="Include descriptions of NVTs finding vulnerabilities in output.", ) parser.add_argument( "--showlog", action="store_true", help="Include log messages in output." ) parser.add_argument( "--show-ports", action="store_true", help="Include port of given vulnerable nvt in output.", ) parser.add_argument( "--scanend", action="store_true", help="Include timestamp of scan end in output.", ) parser.add_argument( "--autofp", type=int, choices=[0, 1, 2], default=0, help="Trust vendor security updates for automatic false positive" " filtering (0=No, 1=full match, 2=partial).", ) parser.add_argument( "-e", "--empty-as-unknown", action="store_true", help="Respond with UNKNOWN on empty results.", ) parser.add_argument( "-I", "--max-running-instances", default=10, type=int, help="Set the maximum simultaneous processes of check-gmp", ) parser.add_argument("--hostname", nargs="?", required=False) group = parser.add_mutually_exclusive_group(required=False) group.add_argument( "--ping", action="store_true", help="Ping the gsm appliance." ) group.add_argument( "--status", action="store_true", help="Report status of task." ) group = parser.add_mutually_exclusive_group(required=False) group.add_argument( "--days", type=int, help="Delete database entries that are older than" " given days.", ) group.add_argument("--ip", help="Delete database entry for given ip.") group = parser.add_mutually_exclusive_group(required=False) group.add_argument( "--trend", action="store_true", help="Report status by trend." ) group.add_argument( "--last-report", action="store_true", help="Report status by last report.", ) script_args = parser.parse_args(args.script_args) aux_parser = ArgumentParser( prefix_chars="-", formatter_class=RawTextHelpFormatter ) aux_parser.add_argument("--hostname", nargs="?", required=False) gvm_tool_args, _ = aux_parser.parse_known_args(sys.argv) if "hostname" in gvm_tool_args: script_args.hostname = gvm_tool_args.hostname # Set the max running instances variable if script_args.max_running_instances: # TODO should be passed as local variable instead of using a global one # pylint: disable=global-statement global MAX_RUNNING_INSTANCES MAX_RUNNING_INSTANCES = script_args.max_running_instances # Set the report manager if script_args.cache: tmp_path_db = script_args.cache im = InstanceManager(tmp_path_db, parser) # Check if command holds clean command if script_args.clean: if script_args.ip: logger.info("Delete entry with ip %s", script_args.ip) im.delete_entry_with_ip(script_args.ip) elif script_args.days: logger.info("Delete entries older than %s days", script_args.days) im.delete_older_entries(script_args.days) sys.exit(1) # Set the host im.set_host(script_args.hostaddress) # Check if no more than 10 instances of check-gmp runs simultaneously im.check_instances() try: gmp.get_version() except Exception as e: # pylint: disable=broad-except end_session(im, f"GMP CRITICAL: {str(e)}", NAGIOS_CRITICAL) if script_args.ping: ping(gmp, im) if "status" in script_args: status(gmp, im, script_args) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/clean-sensor.gmp.py000066400000000000000000000057041452466721200210400ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from argparse import Namespace from gvm.protocols.gmp import Gmp def clean_sensor(gmp: Gmp) -> None: tasks = gmp.get_tasks( filter_string="rows=-1 not status=Running and " "not status=Requested and not " "status="Stop Requested"" ) for task_id in tasks.xpath("task/@id"): print(f"Removing task {task_id} ... ") status_text = gmp.delete_task(task_id, ultimate=True).xpath( "@status_text" )[0] print(status_text) targets = gmp.get_targets(filter_string="rows=-1 not _owner=""") for target_id in targets.xpath("target/@id"): print(f"Removing target {target_id} ... ") status_text = gmp.delete_target(target_id, ultimate=True).xpath( "@status_text" )[0] print(status_text) configs = gmp.get_scan_configs( filter_string="rows=-1 not _owner=""" ) for config_id in configs.xpath("config/@id"): print(f"Removing config {config_id} ... ") status_text = gmp.delete_scan_config(config_id, ultimate=True).xpath( "@status_text" )[0] print(status_text) port_lists = gmp.get_port_lists( filter_string="rows=-1 not _owner=""" ) for port_list_id in port_lists.xpath("port_list/@id"): print(f"Removing port_list {port_list_id} ... ") status_text = gmp.delete_port_list(port_list_id, ultimate=True).xpath( "@status_text" )[0] print(status_text) credentials = gmp.get_credentials( filter_string="rows=-1 not _owner=""" ) for config_id in credentials.xpath("credential/@id"): print(f"Removing credential {config_id} ... ") status_text = gmp.delete_credential(config_id, ultimate=True).xpath( "@status_text" )[0] print(status_text) print("Emptying trash... ") status_text = gmp.empty_trashcan().xpath("@status_text")[0] print(status_text) def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=unused-argument print( "This script removes all resources from a sensor, except active tasks." ) clean_sensor(gmp) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/combine-reports.gmp.py000066400000000000000000000077761452466721200215720ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # ruff: noqa: E501 import sys import time from argparse import Namespace from gvm.protocols.gmp import Gmp from lxml import etree as e from gvmtools.helper import generate_uuid def check_args(args: Namespace) -> None: len_args = len(args.script) - 1 if len_args < 2: message = """ This script will combine desired reports into a single report. \ The combined report will then be sent to a desired container task. \ This script will create a container task for the combined report to\ be sent to, however, if you would like the report to be sent to an \ existing task, place the report of the desired task first and add \ the argument 'first_task'. 1. --uuid of report to be combined 2. --uuid of report to be combined ... n. --uuid of report to be combined Example for starting up the routine: $ gvm-script --gmp-username=namessh --gmp-password=pass ssh --hostname=hostname \ scripts/combine-reports.gmp.py \ "d15a337c-56f3-4208-a462-afeb79eb03b7" \ "303fa0a6-aa9b-43c4-bac0-66ae0b2d1698" 'first_task' """ print(message) sys.exit() def combine_reports(gmp: Gmp, args: Namespace) -> e.Element: new_uuid = generate_uuid() combined_report = e.Element( "report", { "id": new_uuid, "format_id": "d5da9f67-8551-4e51-807b-b6a873d70e34", "extension": "xml", "content_type": "text/xml", }, ) report_elem = e.Element("report", {"id": new_uuid}) ports_elem = e.Element("ports", {"start": "1", "max": "-1"}) results_elem = e.Element("results", {"start": "1", "max": "-1"}) combined_report.append(report_elem) report_elem.append(results_elem) if "first_task" in args.script: arg_len = args.script[1:-1] else: arg_len = args.script[1:] for argument in arg_len: current_report = gmp.get_report( argument, details=True, ignore_pagination=True )[0] for port in current_report.xpath("report/ports/port"): ports_elem.append(port) for result in current_report.xpath("report/results/result"): results_elem.append(result) for host in current_report.xpath("report/host"): report_elem.append(host) return combined_report def send_report(gmp: Gmp, args: Namespace, combined_report: e.Element) -> str: if "first_task" in args.script: main_report = gmp.get_report(args.script[1])[0] task_id = main_report.xpath("//task/@id")[0] else: the_time = time.strftime("%Y/%m/%d-%H:%M:%S") task_id = "" task_name = f"Combined_Report_{the_time}" res = gmp.create_container_task( name=task_name, comment="Created with gvm-tools." ) task_id = res.xpath("//@id")[0] combined_report = e.tostring(combined_report) res = gmp.import_report(combined_report, task_id=task_id, in_assets=True) return res.xpath("//@id")[0] def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) combined_report = combine_reports(gmp, args) send_report(gmp, args, combined_report) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/create-consolidated-report.gmp.py000077500000000000000000000264301452466721200236730ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from argparse import ArgumentParser, Namespace, RawTextHelpFormatter from datetime import date from typing import List, Tuple from uuid import UUID from gvm.errors import GvmError from gvm.protocols.gmp import Gmp from lxml import etree as e from gvmtools.helper import error_and_exit, generate_uuid HELP_TEXT = ( "This script creates a consolidated report and imports it to the GSM. " "You are able to set a time period. Within this period the last report" "of all tasks will be consolidated. You can additionally filter the " "tasks by one or more tags and the results with a filter id or filter " "term.\n" " Usable with gvm-script (gvm-tools). Help: gvm-script -h" ) def parse_tags(tags: List[str]) -> List[str]: """Parsing and validating the given tags tags (List): A list containing tags: name, tag-id, name=value Returns a list containing tag="name", tag_id="id" ... """ filter_tags = [] for tag in tags: try: UUID(tag, version=4) filter_tags.append(f'tag_id="{tag}"') except ValueError: filter_tags.append(f'tag="{tag}"') return filter_tags def parse_period(period: List[str]) -> Tuple[date, date]: """Parsing and validating the given time period period (List): A list with two entries containing dates in the format yyyy/mm/dd Returns two date-objects containing the passed dates """ try: s_year, s_month, s_day = map(int, period[0].split("/")) except ValueError as exc: error_and_exit( f"Start date [{period[0]}] is not a " f"correct date format:\n{exc.args[0]}." ) try: e_year, e_month, e_day = map(int, period[1].split("/")) except ValueError as exc: error_and_exit( f"End date [{period[1]}] is not " f"a correct date format:\n{exc.args[0]}." ) try: period_start = date(s_year, s_month, s_day) except ValueError as exc: error_and_exit(f"Start date: {exc.args[0]}") try: period_end = date(e_year, e_month, e_day) except ValueError as exc: error_and_exit(f"End date: {exc.args[0]}") if period_end < period_start: error_and_exit("The start date seems to after the end date.") return period_start, period_end def parse_args(args: Namespace) -> Namespace: # pylint: disable=unused-argument """Parsing args ...""" parser = ArgumentParser( prefix_chars="+", add_help=False, formatter_class=RawTextHelpFormatter, description=HELP_TEXT, ) parser.add_argument( "+h", "++help", action="help", help="Show this help message and exit.", ) parser.add_argument( "+p", "++period", nargs=2, type=str, required=True, dest="period", help=( "Choose a time period that is filtering the tasks.\n" "Use the date format YYYY/MM/DD." ), ) parser.add_argument( "+t", "++tags", nargs="+", type=str, dest="tags", help=( "Filter the tasks by given tag(s).\n" "If you pass more than on tag, they will be concatenated with " or "\n" "You can pass tag names, tag ids or tag name=value to this argument" ), ) filter_args = parser.add_mutually_exclusive_group() filter_args.add_argument( "++filter-terms", nargs="+", type=str, dest="filter_term", help="Filter the results by given filter terms.", ) filter_args.add_argument( "++filter-id", type=str, dest="filter_id", help="Filter the results by given filter id.", ) script_args, _ = parser.parse_known_args() return script_args def generate_task_filter( period_start: date, period_end: date, tags: List[str] ) -> str: """Generate the tasks filter period_start: the start date period_end: the end date tags: list of tags for the filter Returns an task filter string """ task_filter = "rows=-1 " # last is for the timestamp of the last report in that task # created is for the timestamp of when the task has been created # Note: the "first" argument for tasks is currently not working period_filter = ( f"last>{period_start.isoformat()} " f"and created<{period_end.isoformat()}" ) filter_parts = [] if tags: for tag in tags: filter_parts.append(f"{period_filter} and {tag}") tags_filter = " or ".join(filter_parts) task_filter += tags_filter else: task_filter += period_filter return task_filter def get_last_report_in_time_period( gmp: Gmp, task_filter: str, period_start: date, period_end: date, ) -> List[str]: """Get the last reports from the tasks in the given time period Therefore all tasks, that match the filter within the time period will be looked up Afterwards the reports from that tasks will be searched for the last report in the timeperiod by sorting them reverse by creation date gmp: the GMP object task_filter: task filter string """ print( f"Filtering the task with the filter term [{task_filter}]\n" f"Looking for the last report before {period_end.isoformat()}, " f"but after {period_start.isoformat()}." ) tasks_xml = gmp.get_tasks(filter_string=task_filter) reports = [] for task_id in tasks_xml.xpath("task/@id"): # sort-reverse for getting the latest report ... reports_xml = gmp.get_reports( filter_string=( f"rows=1 task_id={task_id} and " f"created<{period_end.isoformat()} and " f"created>{period_start.isoformat()} sort-reverse=created" ) ) # should always be max 1 report reports.append(reports_xml.xpath("report/@id")[0]) return reports def combine_reports( gmp: Gmp, reports: List[str], filter_term: str, filter_id: str ) -> e.Element: """Combining the filtered ports, results and hosts of the given report ids into one new report. gmp: the GMP object reports (List): List of report_ids filter_term (str): the result filter string """ new_uuid = generate_uuid() combined_report = e.Element( "report", { "id": new_uuid, "format_id": "d5da9f67-8551-4e51-807b-b6a873d70e34", "extension": "xml", "content_type": "text/xml", }, ) report_elem = e.Element("report", {"id": new_uuid}) ports_elem = e.Element("ports", {"start": "1", "max": "-1"}) results_elem = e.Element("results", {"start": "1", "max": "-1"}) combined_report.append(report_elem) report_elem.append(ports_elem) report_elem.append(results_elem) for report in reports: try: if filter_id: current_report = gmp.get_report( report, filter_id=filter_id, details=True, ignore_pagination=True, ).find("report") else: current_report = gmp.get_report( report, filter_string=filter_term, details=True, ignore_pagination=True, ).find("report") except GvmError: print(f"Could not find the report [{report}]") for port in current_report.xpath("report/ports/port"): ports_elem.append(port) for result in current_report.xpath("report/results/result"): results_elem.append(result) for host in current_report.xpath("report/host"): report_elem.append(host) return combined_report def send_report( gmp: Gmp, combined_report: e.Element, period_start: date, period_end: date ) -> str: """Creating a container task and sending the combined report to the GSM gmp: the GMP object combined_report: the combined report xml object period_start: the start date period_end: the end date """ task_name = f"Consolidated Report [{period_start} - {period_end}]" res = gmp.create_container_task( name=task_name, comment="Created with gvm-tools." ) task_id = res.xpath("//@id")[0] combined_report = e.tostring(combined_report) res = gmp.import_report(combined_report, task_id=task_id, in_assets=True) return res.xpath("//@id")[0] def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable parsed_args = parse_args(args=args) period_start, period_end = parse_period(period=parsed_args.period) print( "Combining last reports from tasks within the " f"time period [{period_start}, {period_end}]" ) # Generate Task Filter filter_tags = None if parsed_args.tags: filter_tags = parse_tags(tags=parsed_args.tags) task_filter = generate_task_filter( period_start=period_start, period_end=period_end, tags=filter_tags, ) # Find reports reports = get_last_report_in_time_period( gmp=gmp, task_filter=task_filter, period_start=period_start, period_end=period_end, ) print(f"Combining {len(reports)} found reports.") filter_term = "" if parsed_args.filter_term: filter_term = " ".join(parsed_args.filter_term) print( "Filtering the results by the " f"following filter term [{filter_term}]" ) elif parsed_args.filter_id: try: filter_xml = gmp.get_filter(filter_id=parsed_args.filter_id).find( "filter" ) filter_term = filter_xml.find("term").text print( "Filtering the results by the following filter term " f"[{filter_term}]" ) except GvmError: print( "Filter with the ID [{parsed_args.filter_id}] is not existing." ) else: print("No results filter given.") # Combine the reports combined_report = combine_reports( gmp=gmp, reports=reports, filter_term=filter_term, filter_id=parsed_args.filter_id, ) # Import the generated report to GSM report = send_report( gmp=gmp, combined_report=combined_report, period_start=period_start, period_end=period_end, ) print(f"Successfully imported new consolidated report [{report}]") if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/create-cve-report-from-json.gmp.py000066400000000000000000000456131452466721200237110ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import csv import datetime import json import time from argparse import ArgumentParser, Namespace, RawTextHelpFormatter from pathlib import Path from typing import Dict, Tuple from cpe import CPE from gvm.protocols.gmp import Gmp from lxml import etree as e from gvmtools.helper import error_and_exit, generate_uuid HELP_TEXT = ( "This script creates a cve report from a JSON document.\n" "The JSON document needs to be formatted like this: " "[" " {" ' "headings": [' ' "name",' ' "IP Address",' ' "IP range",' ' "Operating System",' ' "CPE String 23",' ' "Name",' ' "Full Version (version)",' ' "CPE String 23"' " ]," " ...," ' "results": [' " [" ' "foo",' ' "127.0.0.1",' ' "127.0.0.1/32",' ' "Some Windows",' ' "cpe:2.3:o:microsoft:some_windows:-:*:*:*:*:*:*:*",' " [" ' "Some Microsoftware",' " ." " ]," " [" ' "0.1",' " ..." " ]," " [" ' "cpe:2.3:a:microsoft:microsoftware:0.1:*:*:*:*:*:*:*",' " ..." " ]" " ]," " ]" " }" "]" " Usable with gvm-script (gvm-tools). Help: gvm-script -h" ) class ProgressBar: def __init__(self, length: int, count: int, pl_name: str): self.length = length self.count = count self.current = 0 self.start_time = datetime.datetime.now() self.entities = pl_name self.eta = "???" self.seq = "" self.end = "" self._print() self.seq = "\r" def _leading_zeros(self) -> str: return (len(str(self.count)) - len(str(self.current))) * " " def _bar(self): points = int(self.length * (self.current / self.count)) return str("·" * points + " " * (self.length - points)) def _print(self): print( f"{self.seq}[{self._bar()}] | " f"{self._leading_zeros()}{str(self.current)}/{str(self.count)} " f"{self.entities} processed. | " f"ETA: {self.eta}", flush=True, end=self.end, ) def update(self, progressed): self.current = progressed elapsed = datetime.datetime.now() - self.start_time self.eta = str(elapsed / self.current * (self.count - self.current)) self._print() def done(self): self.current = self.count self.eta = ( f"Done! Time consumed: {datetime.datetime.now() - self.start_time}" ) self.end = "\n" self._print() class ListGenerator: """ Creating the CPE to CVE list used for the report generation in this this script. """ def __init__(self, gmp: Gmp, filename: Path, recreate: bool): self.gmp = gmp if filename.exists(): if recreate: filename.unlink() else: error_and_exit( f'The file "{filename}" already exists. ' "If you want to delete the old list and " 'recreate the list run with "++create-list ' f'recreate +f {filename}"' ) self.file = open(filename, "w", encoding="utf-8") def _cpe_to_cve(self, resp): """Write the CPEs and CVEs to the list""" cve_tags = resp.findall("info") for cve_tag in cve_tags[ :-1 ]: # -1 because the last info tag is a wrongy. :D cve = None cpes = None if "id" in cve_tag.attrib: cve = cve_tag.attrib["id"] cpes = cve_tag.find("cve").find("products").text cvss = cve_tag.find("cve").find("cvss").text if cpes: for cpe in cpes.strip().split(" "): print( f"'{cpe}','{cve}','{cvss}'", file=self.file, end="\n", ) def create_cpe_list(self, step: int = 3000): """Creates a CPE to CVE list in a CSV format: 'cpe', 'cve', 'cvss' The CPE's have a 1-to-1-relation to the CVE's so CPE's can appear more then once in this list step(int): How many CVEs will be requested from the GSM in one request. Be careful with higher values. You will need to set the default timeout in gvm-tools higher if you set step >3000. A higher step will make the list generation faster. """ resp = self.gmp.get_info_list( info_type=self.gmp.types.InfoType.CVE, filter="rows=1" ) count = resp.find("info_count").text first = 0 count = int(count) print(f"Creating CPE to CVE list. Found {count} CVE's.") progress_bar = ProgressBar(length=100, count=count, pl_name="CVEs") while (first + step) < count: resp = self.gmp.get_info_list( info_type=self.gmp.types.InfoType.CVE, filter=f"rows={step} first={first}", ) self._cpe_to_cve(resp) first = first + step progress_bar.update(progressed=first) # find the rest resp = self.gmp.get_info_list( info_type=self.gmp.types.InfoType.CVE, filter=f"rows={count - first} first={first}", ) self._cpe_to_cve(resp) progress_bar.done() self.file.close() class Report: def __init__(self, gmp): self.results = e.Element("results", {"start": "1", "max": "-1"}) self.hosts = [] self.report = None self.gmp = gmp def finish_report(self): report_format_id = "d5da9f67-8551-4e51-807b-b6a873d70e34" self.report_id = generate_uuid() self.report = e.Element( "report", { "id": self.report_id, "format_id": report_format_id, "extension": "xml", "content_type": "text/xml", }, ) owner_elem = e.SubElement(self.report, "owner") e.SubElement(owner_elem, "name").text = "" e.SubElement(self.report, "name").text = "Report created from JSON-File" inner_report = e.SubElement( self.report, "report", {"id": self.report_id} ) ports_elem = e.SubElement( inner_report, "ports", {"start": "1", "max": "-1"} ) inner_report.append(ports_elem) inner_report.append(self.results) inner_report.extend(self.hosts) self.report.append(inner_report) def send_report(self) -> str: the_time = time.strftime("%Y/%m/%d-%H:%M:%S") task_id = "" task_name = f"CVE_Scan_Report_{the_time}" res = self.gmp.create_container_task( name=task_name, comment="Created with gvm-tools." ) task_id = res.xpath("//@id")[0] report = e.tostring(self.report) res = self.gmp.import_report(report, task_id=task_id, in_assets=True) return res.xpath("//@id")[0] def generate_host_detail( self, name, value, source_name=None, source_description=None, source_type=None, ): """Generating a host details xml element""" host_detail_elem = e.Element("detail") e.SubElement(host_detail_elem, "name").text = name e.SubElement(host_detail_elem, "value").text = value if source_name: source_elem = e.SubElement(host_detail_elem, "source") e.SubElement(source_elem, "name").text = source_name if source_type: e.SubElement(source_elem, "type").text = source_type if source_description: e.SubElement( source_elem, "description" ).text = source_description return host_detail_elem def add_results(self, ip, hostname, cpes: Dict, cpeo, os, date_time): host_id = generate_uuid() source_name = "gvm-tools" date_format = "%Y-%m-%dT%H:%M:%S" date_time = f"{date_time.strftime(date_format)}Z" host_elem = e.Element("host") e.SubElement(host_elem, "ip").text = ip e.SubElement(host_elem, "asset", {"asset_id": host_id}) e.SubElement(host_elem, "start").text = date_time e.SubElement(host_elem, "end").text = date_time host_result_count_elem = e.SubElement(host_elem, "result_count") host_elem.append( self.generate_host_detail( name="hostname", value=hostname, source_name=source_name ) ) host_elem.append( self.generate_host_detail( name="best_os_txt", value=os, source_name=source_name, source_description="Host Details", ) ) host_elem.append( self.generate_host_detail( name="best_os_cpe", value=cpeo, source_name=source_name, source_description="Host Details", ) ) host_details = 0 for cpe, cves in cpes.items(): if cves: for cve, cvss in cves.items(): result_id = generate_uuid() result = e.Element("result", {"id": result_id}) e.SubElement(result, "name").text = f"Result for host {ip}" e.SubElement( result, "comment" ).text = "Imported with gvm-tools" e.SubElement(result, "modification_time").text = date_time e.SubElement(result, "creation_time").text = date_time detect_elem = e.Element("detection") detect_result_elem = e.SubElement( detect_elem, "result", {"id": result_id} ) details_elem = e.SubElement(detect_result_elem, "details") # We need to add the detection details here # but actually they are not imported to GSM anyways ... e.SubElement(details_elem, "detail") result_host_elem = e.Element("host") result_host_elem.text = ip e.SubElement( result_host_elem, "asset", {"asset_id": host_id} ) e.SubElement(result_host_elem, "hostname").text = hostname result.append(result_host_elem) nvt_elem = e.Element("nvt", {"oid": cve}) e.SubElement(nvt_elem, "type").text = "cve" e.SubElement(nvt_elem, "name").text = cve e.SubElement(nvt_elem, "cvss_base").text = str(cvss) e.SubElement(nvt_elem, "cve").text = cve result.append(nvt_elem) e.SubElement(result, "severity").text = str(cvss) host_elem.append( self.generate_host_detail( name="App", value=cpe, source_type="cve", source_name=cve, source_description="CVE Scanner", ) ) host_details = host_details + 1 self.results.append(result) e.SubElement(host_result_count_elem, "page").text = str(host_details) self.hosts.append(host_elem) class Parser: """Class handles the Parsing from JSON to a Report""" def __init__(self, gmp: Gmp, json_file: Path, cpe_list: Path) -> None: try: self.cpe_list = open(cpe_list, "r", encoding="utf-8") self.reader = csv.reader(self.cpe_list) except FileNotFoundError: error_and_exit( f'There is no file "{cpe_list}". ' "Maybe you need to create a list first. Run with " f'argument "++create-list +f {cpe_list}", to create ' "a new list, or pass the correct location of an existing list." ) self.gmp = gmp try: self.json_fp = open(json_file, encoding="utf-8") self.json_dump = json.load(self.json_fp)[0]["results"] except FileNotFoundError: error_and_exit(f'There is no file "{json_file}".') except json.JSONDecodeError as exc: error_and_exit(f"The JSON seems to be invalid: {exc.args[0]}") def parse(self) -> Report: """Loads an JSON file and extracts host information: Args: host_dump: the dumped json results, containing a hostname, host_ip, host_ip_range, host_operating_system, host_os_cpe, arrays of found_app, app_version, app_cpe """ report = Report(gmp=gmp) date_time = datetime.datetime.now() count = len(self.json_dump) progressed = 0 print(f"Found {str(count)} hosts:") progressbar = ProgressBar(length=100, count=count, pl_name="Hosts") for entry in self.json_dump: if entry[3] is None: error_and_exit("The JSON format is not correct.") name = entry[0] # print(f"Creating Results for the host {name}") ips = entry[1] if isinstance(ips, str): ips = [ips] os = entry[3] os_cpe = convert_cpe23_to_cpe22(entry[4])[0] cpes = [] # entry[7] should be the CPEs ... if entry[7] is not None: if isinstance(entry[7], str): cpes.extend(self._get_cpes(entry[7])) else: for cpe in entry[7]: if cpe: cpes.extend(self._get_cpes(cpe)) vulns = self._get_cves(cpes) if vulns: for ip in ips: report.add_results( ip=ip, hostname=name, cpes=vulns, cpeo=os_cpe, os=os, date_time=date_time, ) progressed += 1 progressbar.update(progressed=progressed) progressbar.done() return report def _get_cpes(self, cpe): """Parse and return the CPE's from the JSON. Convert the CPEs to v2.2 and check if they have a version part. If not get this CPE in all versions from the GSM and return them. This may result in a lot of false positives or false negatives. """ cpe = convert_cpe23_to_cpe22(cpe) if cpe[1] is False: return [cpe[0]] cpes = [] cpe_xml = self.gmp.get_info_list( info_type=gmp.types.InfoType.CPE, filter=f'rows=-1 uuid~"{cpe[0]}:"' ) infos = cpe_xml.findall("info") for cpe in infos[:-1]: # -1 because the last info tag is a wrongy. :D cpes.append(cpe.get("id")) return cpes def _get_cves(self, cpes): """Get CVEs for the CPEs from the CSV List""" vulns = {} i = 0 for cpe in cpes: vulns[cpe] = {} for row in self.reader: # O(n) for cpe in cpes: if cpe in row[0]: vulns[cpe][row[1].strip("'")] = float(row[2].strip("'")) i = i + 1 self.cpe_list.seek(0) return vulns def finish_lookup(self): self.json_fp.close() self.cpe_list.close() def convert_cpe23_to_cpe22(cpe: str) -> Tuple[str, bool]: """Convert a CPE v2.3 to a CPE v2.2 returns the CPE v2.2 and True if no product version is given """ # MAKE ME BETTER!!! cpe = CPE(cpe) any_version = False if cpe.get_version()[0] == "*": any_version = True return ( str(CPE(cpe.as_uri_2_3(), CPE.VERSION_2_2)).replace("CPE v2.2: ", ""), any_version, ) def parse_args(args: Namespace) -> Namespace: # pylint: disable=unused-argument """Parsing args ...""" parser = ArgumentParser( prefix_chars="+", add_help=False, formatter_class=RawTextHelpFormatter, description=HELP_TEXT, ) parser.add_argument( "+h", "++help", action="help", help="Show this help message and exit.", ) parser.add_argument( "++create-list", nargs="?", type=str, choices=("no_creation", "recreate", "create"), const="create", default="no_creation", dest="create_list", help="Create the CPE to CVE helper list", ) parser.add_argument( "+l", "++list", type=str, dest="list", required=True, help="Create the CPE to CVE helper list", ) parser.add_argument( "+f", "++file", type=str, dest="json_file", help="File that should be parsed", ) args, _ = parser.parse_known_args() return args def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable parsed_args = parse_args(args=args) recreate = False if parsed_args.create_list == "recreate": recreate = True if parsed_args.create_list != "no_creation": print("Generating CPE to CVE list.") list_generator = ListGenerator( gmp, filename=Path(parsed_args.list).absolute(), recreate=recreate ) list_generator.create_cpe_list() print("Generation of CPE to CVE list done.") if parsed_args.json_file: report = Parser( gmp=gmp, json_file=parsed_args.json_file, cpe_list=parsed_args.list ).parse() report.finish_report() report_id = report.send_report() print(f"Imported Report [{report_id}]") if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/create-dummy-data.gmp.py000066400000000000000000000054461452466721200217550ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import Namespace from random import choice from gvm.protocols.gmp import Gmp from gvmtools.helper import generate_id def check_args(args): len_args = len(args.script) - 1 if len_args != 1: message = """ This script will create random data in the given GVM database 1. -- Number of datasets to create Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/create-dummy-data.gmp.py """ print(message) sys.exit() def create_data(gmp, count): config_ids = [] target_ids = [] for _ in range(0, count): name = generate_id() gmp.create_credential( name, login=name, password=name, credential_type=gmp.types.CredentialType.PASSWORD_ONLY, ) print(str(count) + " random credentials generated.") for _ in range(0, count): name = generate_id() gmp.create_port_list(name, port_range="T:1-42") print(str(count) + " random port lists generated.") for _ in range(0, count): name = generate_id() res = gmp.create_scan_config( "085569ce-73ed-11df-83c3-002264764cea", name ) config_ids.append(res.xpath("@id")[0]) print(str(count) + " random scan configs generated.") for _ in range(0, count): name = generate_id() res = gmp.create_target(name, hosts=["127.0.0.1"]) target_ids.append(res.xpath("@id")[0]) print(str(count) + " random targets generated.") for _ in range(0, count): name = generate_id() config_id = choice(config_ids) target_id = choice(target_ids) gmp.create_task( name, config_id, target_id, "08b69003-5fc2-4037-a479-93b440211c73" ) print(str(count) + " random tasks generated.") def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) create_data(gmp, int(args.script[1])) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/create-targets-from-host-list.gmp.py000066400000000000000000000112411452466721200242370ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys import time from argparse import ArgumentParser, Namespace, RawTextHelpFormatter from pathlib import Path from typing import List from gvm.protocols.gmp import Gmp from gvmtools.helper import error_and_exit HELP_TEXT = ( "This script pulls hostnames from a text " "file and creates a target for each." ) def check_args(args): len_args = len(args.script) - 1 if len_args != 2: message = """ This script pulls hostnames from a text file and creates a target \ for each. One parameter after the script name is required. 1. -- IP of the GVM host 2. -- text file containing hostnames Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/create_targets_from_host_list.gmp \ """ print(message) sys.exit() def parse_args(args: Namespace) -> Namespace: # pylint: disable=unused-argument """Parsing args ...""" parser = ArgumentParser( prefix_chars="+", add_help=False, formatter_class=RawTextHelpFormatter, description=HELP_TEXT, ) parser.add_argument( "+h", "++help", action="help", help="Show this help message and exit.", ) parser.add_argument( "hostname", type=str, help="Host name to create targets for.", ) parser.add_argument( "hosts_file", type=str, help=("File containing host names / IPs"), ) ports = parser.add_mutually_exclusive_group() ports.add_argument( "+pl", "++port-list-id", type=str, dest="port_list_id", help="UUID of existing port list.", ) ports.add_argument( "+pr", "++port-range", dest="port_range", type=str, help=( "Port range to create port list from, e.g. " "T:1-1234 for ports 1-1234/TCP" ), ) ports.set_defaults( port_list_id="4a4717fe-57d2-11e1-9a26-406186ea4fc5" ) # All IANA assigned TCP and UDP script_args, _ = parser.parse_known_args(args) return script_args def load_host_list(host_file): try: with open(host_file, encoding="utf-8") as f: content = f.readlines() host_list = [x.strip() for x in content] host_list = list(filter(None, host_list)) except IOError as e: error_and_exit(f"Failed to read host_file: {str(e)} (exit)") if len(host_list) == 0: error_and_exit("Host file is empty (exit)") return host_list def send_targets( gmp: Gmp, host_name: str, host_file: Path, host_list: List[str], port_list_id: str, ): print(f"\nSending targets from {host_file} to {host_name}...") for host in host_list: name = f"Target for {host}" comment = f"Created: {time.strftime('%Y/%m/%d-%H:%M:%S')}" hosts = [host] gmp.create_target( name=name, comment=comment, hosts=hosts, port_list_id=port_list_id ) def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable if args.script: args = args.script[1:] parsed_args = parse_args(args=args) hosts_list = load_host_list(parsed_args.hosts_file) if parsed_args.port_range: print(parsed_args.port_range) resp = gmp.create_port_list( name=f"Port list for target {parsed_args.hostname}", port_range=parsed_args.port_range, comment="Port List created by gvm-script", ) port_list_id = resp.xpath("//@id")[0] print(f"Port list {port_list_id} created!\n") else: port_list_id = parsed_args.port_list_id send_targets( gmp, parsed_args.hostname, parsed_args.hosts_file, hosts_list, port_list_id, ) print("\n Target(s) created!\n") if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/default_report_data.json000066400000000000000000000672221452466721200222210ustar00rootroot00000000000000{ "vulns": [ { "oid": "1.3.6.1.4.1.25623.1.0.10056", "port": "80/tcp", "severity": "5.0", "name": "/doc directory browsable" }, { "oid": "1.3.6.1.4.1.25623.1.0.902830", "port": "80/tcp", "severity": "4.3", "name": "Apache HTTP Server 'httpOnly' Cookie Information Disclosure Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.108082", "port": "8009/tcp", "severity": "0.0", "name": "Apache JServ Protocol v1.3 Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.900498", "port": "80/tcp", "severity": "0.0", "name": "Apache Web Server Version Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.103210", "port": "80/tcp", "severity": "5.0", "name": "awiki Multiple Local File Include Vulnerabilities" }, { "oid": "1.3.6.1.4.1.25623.1.0.111038", "port": "80/tcp", "severity": "0.0", "name": "CGI Scanning Consolidation" }, { "oid": "1.3.6.1.4.1.25623.1.0.900600", "port": "21/tcp", "severity": "6.4", "name": "Check for Anonymous FTP Login" }, { "oid": "1.3.6.1.4.1.25623.1.0.100111", "port": "512/tcp", "severity": "10.0", "name": "Check for rexecd Service" }, { "oid": "1.3.6.1.4.1.25623.1.0.901202", "port": "513/tcp", "severity": "7.5", "name": "Check for rlogin Service" }, { "oid": "1.3.6.1.4.1.25623.1.0.100074", "port": "1524/tcp", "severity": "0.0", "name": "Check for Telnet Server" }, { "oid": "1.3.6.1.4.1.25623.1.0.100072", "port": "25/tcp", "severity": "5.0", "name": "Check if Mailserver answer to VRFY and EXPN requests" }, { "oid": "1.3.6.1.4.1.25623.1.0.810002", "port": "general/CPE-T", "severity": "0.0", "name": "CPE Inventory" }, { "oid": "1.3.6.1.4.1.25623.1.0.902799", "port": "5432/tcp", "severity": "0.0", "name": "Database Open Access Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.902799", "port": "3306/tcp", "severity": "0.0", "name": "Database Open Access Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.10028", "port": "53/tcp", "severity": "0.0", "name": "Determine which version of BIND name daemon is running" }, { "oid": "1.3.6.1.4.1.25623.1.0.12638", "port": "3632/tcp", "severity": "8.5", "name": "DistCC Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.103553", "port": "3632/tcp", "severity": "9.3", "name": "DistCC Remote Code Execution Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.108010", "port": "8787/tcp", "severity": "10.0", "name": "Distributed Ruby (dRuby/DRb) Multiple Remote Code Execution Vulnerabilities" }, { "oid": "1.3.6.1.4.1.25623.1.0.108018", "port": "53/tcp", "severity": "0.0", "name": "DNS Server Detection (TCP)" }, { "oid": "1.3.6.1.4.1.25623.1.0.20108", "port": "80/tcp", "severity": "0.0", "name": "Fingerprint web server with favicon.ico" }, { "oid": "1.3.6.1.4.1.25623.1.0.10092", "port": "21/tcp", "severity": "0.0", "name": "FTP Banner Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.10092", "port": "2121/tcp", "severity": "0.0", "name": "FTP Banner Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.11213", "port": "80/tcp", "severity": "5.8", "name": "HTTP Debugging Methods (TRACE/TRACK) Enabled" }, { "oid": "1.3.6.1.4.1.25623.1.0.112081", "port": "80/tcp", "severity": "0.0", "name": "HTTP Security Headers Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.10107", "port": "80/tcp", "severity": "0.0", "name": "HTTP Server type and version" }, { "oid": "1.3.6.1.4.1.25623.1.0.103190", "port": "general/icmp", "severity": "0.0", "name": "ICMP Timestamp Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.11156", "port": "6667/tcp", "severity": "0.0", "name": "IRC daemon identification" }, { "oid": "1.3.6.1.4.1.25623.1.0.140051", "port": "1099/tcp", "severity": "10.0", "name": "Java RMI Server Insecure Default Configuration Remote Code Execution Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.802726", "port": "445/tcp", "severity": "0.0", "name": "Microsoft SMB Signing Disabled" }, { "oid": "1.3.6.1.4.1.25623.1.0.902425", "port": "445/tcp", "severity": "0.0", "name": "Microsoft Windows SMB Accessible Shares" }, { "oid": "1.3.6.1.4.1.25623.1.0.103935", "port": "25/tcp", "severity": "6.8", "name": "Multiple Vendors STARTTLS Implementation Plaintext Arbitrary Command Injection Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.103551", "port": "3306/tcp", "severity": "9.0", "name": "MySQL / MariaDB weak password" }, { "oid": "1.3.6.1.4.1.25623.1.0.100152", "port": "3306/tcp", "severity": "0.0", "name": "MySQL/MariaDB Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.11111", "port": "111/tcp", "severity": "0.0", "name": "Obtain list of all port mapper registered programs via RPC" }, { "oid": "1.3.6.1.4.1.25623.1.0.105937", "port": "general/tcp", "severity": "0.0", "name": "OS Detection Consolidation and Reporting" }, { "oid": "1.3.6.1.4.1.25623.1.0.103674", "port": "general/tcp", "severity": "10.0", "name": "OS End Of Life Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.800109", "port": "80/tcp", "severity": "0.0", "name": "PHP Version Detection (Remote)" }, { "oid": "1.3.6.1.4.1.25623.1.0.103482", "port": "80/tcp", "severity": "7.5", "name": "PHP-CGI-based setups vulnerability when parsing query string parameters from php files." }, { "oid": "1.3.6.1.4.1.25623.1.0.11229", "port": "80/tcp", "severity": "7.5", "name": "phpinfo() output accessible" }, { "oid": "1.3.6.1.4.1.25623.1.0.801660", "port": "80/tcp", "severity": "4.3", "name": "phpMyAdmin 'error.php' Cross Site Scripting Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.900129", "port": "80/tcp", "severity": "0.0", "name": "phpMyAdmin Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.103549", "port": "1524/tcp", "severity": "10.0", "name": "Possible Backdoor: Ingreslock" }, { "oid": "1.3.6.1.4.1.25623.1.0.111086", "port": "25/tcp", "severity": "0.0", "name": "Postfix SMTP Server Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.100151", "port": "5432/tcp", "severity": "0.0", "name": "PostgreSQL Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.105013", "port": "5432/tcp", "severity": "0.0", "name": "PostgreSQL TLS Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.103552", "port": "5432/tcp", "severity": "9.0", "name": "PostgreSQL weak password" }, { "oid": "1.3.6.1.4.1.25623.1.0.900815", "port": "2121/tcp", "severity": "0.0", "name": "ProFTPD Server Version Detection (Remote)" }, { "oid": "1.3.6.1.4.1.25623.1.0.10281", "port": "23/tcp", "severity": "0.0", "name": "Report Telnet Banner" }, { "oid": "1.3.6.1.4.1.25623.1.0.10281", "port": "1524/tcp", "severity": "0.0", "name": "Report Telnet Banner" }, { "oid": "1.3.6.1.4.1.25623.1.0.105839", "port": "1099/tcp", "severity": "0.0", "name": "RMI-Registry Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.108090", "port": "111/tcp", "severity": "0.0", "name": "RPC portmapper (TCP)" }, { "oid": "1.3.6.1.4.1.25623.1.0.108011", "port": "445/tcp", "severity": "6.0", "name": "Samba MS-RPC Remote Shell Command Execution Vulnerability (Active Check)" }, { "oid": "1.3.6.1.4.1.25623.1.0.108204", "port": "512/tcp", "severity": "0.0", "name": "Service Detection with 'BINARY' Request" }, { "oid": "1.3.6.1.4.1.25623.1.0.17975", "port": "8787/tcp", "severity": "0.0", "name": "Service Detection with 'GET' Request" }, { "oid": "1.3.6.1.4.1.25623.1.0.11153", "port": "6667/tcp", "severity": "0.0", "name": "Service Detection with 'HELP' Request" }, { "oid": "1.3.6.1.4.1.25623.1.0.66286", "port": "514/tcp", "severity": "0.0", "name": "Service Detection with nmap" }, { "oid": "1.3.6.1.4.1.25623.1.0.10330", "port": "23/tcp", "severity": "0.0", "name": "Services" }, { "oid": "1.3.6.1.4.1.25623.1.0.10330", "port": "21/tcp", "severity": "0.0", "name": "Services" }, { "oid": "1.3.6.1.4.1.25623.1.0.10330", "port": "2121/tcp", "severity": "0.0", "name": "Services" }, { "oid": "1.3.6.1.4.1.25623.1.0.10330", "port": "5432/tcp", "severity": "0.0", "name": "Services" }, { "oid": "1.3.6.1.4.1.25623.1.0.10330", "port": "25/tcp", "severity": "0.0", "name": "Services" }, { "oid": "1.3.6.1.4.1.25623.1.0.10330", "port": "22/tcp", "severity": "0.0", "name": "Services" }, { "oid": "1.3.6.1.4.1.25623.1.0.10330", "port": "80/tcp", "severity": "0.0", "name": "Services" }, { "oid": "1.3.6.1.4.1.25623.1.0.10330", "port": "3306/tcp", "severity": "0.0", "name": "Services" }, { "oid": "1.3.6.1.4.1.25623.1.0.10394", "port": "445/tcp", "severity": "0.0", "name": "SMB log in" }, { "oid": "1.3.6.1.4.1.25623.1.0.102011", "port": "445/tcp", "severity": "0.0", "name": "SMB NativeLanMan" }, { "oid": "1.3.6.1.4.1.25623.1.0.807830", "port": "445/tcp", "severity": "0.0", "name": "SMB Remote Version Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.11011", "port": "139/tcp", "severity": "0.0", "name": "SMB/CIFS Server Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.11011", "port": "445/tcp", "severity": "0.0", "name": "SMB/CIFS Server Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.10263", "port": "25/tcp", "severity": "0.0", "name": "SMTP Server type and version" }, { "oid": "1.3.6.1.4.1.25623.1.0.103118", "port": "25/tcp", "severity": "0.0", "name": "SMTP STARTTLS Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.103239", "port": "22/tcp", "severity": "7.5", "name": "SSH Brute Force Logins With Default Credentials Reporting" }, { "oid": "1.3.6.1.4.1.25623.1.0.105565", "port": "22/tcp", "severity": "0.0", "name": "SSH Protocol Algorithms Supported" }, { "oid": "1.3.6.1.4.1.25623.1.0.100259", "port": "22/tcp", "severity": "0.0", "name": "SSH Protocol Versions Supported" }, { "oid": "1.3.6.1.4.1.25623.1.0.10267", "port": "22/tcp", "severity": "0.0", "name": "SSH Server type and version" }, { "oid": "1.3.6.1.4.1.25623.1.0.105611", "port": "22/tcp", "severity": "4.3", "name": "SSH Weak Encryption Algorithms Supported" }, { "oid": "1.3.6.1.4.1.25623.1.0.105610", "port": "22/tcp", "severity": "2.6", "name": "SSH Weak MAC Algorithms Supported" }, { "oid": "1.3.6.1.4.1.25623.1.0.805188", "port": "25/tcp", "severity": "4.3", "name": "SSL/TLS: 'DHE_EXPORT' Man in the Middle Security Bypass Vulnerability (LogJam)" }, { "oid": "1.3.6.1.4.1.25623.1.0.103140", "port": "25/tcp", "severity": "0.0", "name": "SSL/TLS: Certificate - Self-Signed Certificate Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.103140", "port": "5432/tcp", "severity": "0.0", "name": "SSL/TLS: Certificate - Self-Signed Certificate Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.103955", "port": "25/tcp", "severity": "5.0", "name": "SSL/TLS: Certificate Expired" }, { "oid": "1.3.6.1.4.1.25623.1.0.103955", "port": "5432/tcp", "severity": "5.0", "name": "SSL/TLS: Certificate Expired" }, { "oid": "1.3.6.1.4.1.25623.1.0.105880", "port": "5432/tcp", "severity": "4.0", "name": "SSL/TLS: Certificate Signed Using A Weak Signature Algorithm" }, { "oid": "1.3.6.1.4.1.25623.1.0.105880", "port": "25/tcp", "severity": "4.0", "name": "SSL/TLS: Certificate Signed Using A Weak Signature Algorithm" }, { "oid": "1.3.6.1.4.1.25623.1.0.103692", "port": "5432/tcp", "severity": "0.0", "name": "SSL/TLS: Collect and Report Certificate Details" }, { "oid": "1.3.6.1.4.1.25623.1.0.103692", "port": "25/tcp", "severity": "0.0", "name": "SSL/TLS: Collect and Report Certificate Details" }, { "oid": "1.3.6.1.4.1.25623.1.0.111012", "port": "5432/tcp", "severity": "4.3", "name": "SSL/TLS: Deprecated SSLv2 and SSLv3 Protocol Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.111012", "port": "25/tcp", "severity": "4.3", "name": "SSL/TLS: Deprecated SSLv2 and SSLv3 Protocol Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.106223", "port": "25/tcp", "severity": "4.0", "name": "SSL/TLS: Diffie-Hellman Key Exchange Insufficient DH Group Strength Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.106223", "port": "5432/tcp", "severity": "4.0", "name": "SSL/TLS: Diffie-Hellman Key Exchange Insufficient DH Group Strength Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.111010", "port": "general/tcp", "severity": "0.0", "name": "SSL/TLS: Hostname discovery from server certificate" }, { "oid": "1.3.6.1.4.1.25623.1.0.105042", "port": "5432/tcp", "severity": "6.8", "name": "SSL/TLS: OpenSSL CCS Man in the Middle Security Bypass Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.902816", "port": "5432/tcp", "severity": "0.0", "name": "SSL/TLS: Report Medium Cipher Suites" }, { "oid": "1.3.6.1.4.1.25623.1.0.902816", "port": "25/tcp", "severity": "0.0", "name": "SSL/TLS: Report Medium Cipher Suites" }, { "oid": "1.3.6.1.4.1.25623.1.0.103441", "port": "5432/tcp", "severity": "0.0", "name": "SSL/TLS: Report Non Weak Cipher Suites" }, { "oid": "1.3.6.1.4.1.25623.1.0.103441", "port": "25/tcp", "severity": "0.0", "name": "SSL/TLS: Report Non Weak Cipher Suites" }, { "oid": "1.3.6.1.4.1.25623.1.0.105018", "port": "5432/tcp", "severity": "0.0", "name": "SSL/TLS: Report Perfect Forward Secrecy (PFS) Cipher Suites" }, { "oid": "1.3.6.1.4.1.25623.1.0.105018", "port": "25/tcp", "severity": "0.0", "name": "SSL/TLS: Report Perfect Forward Secrecy (PFS) Cipher Suites" }, { "oid": "1.3.6.1.4.1.25623.1.0.802067", "port": "25/tcp", "severity": "0.0", "name": "SSL/TLS: Report Supported Cipher Suites" }, { "oid": "1.3.6.1.4.1.25623.1.0.802067", "port": "5432/tcp", "severity": "0.0", "name": "SSL/TLS: Report Supported Cipher Suites" }, { "oid": "1.3.6.1.4.1.25623.1.0.103440", "port": "5432/tcp", "severity": "4.3", "name": "SSL/TLS: Report Weak Cipher Suites" }, { "oid": "1.3.6.1.4.1.25623.1.0.103440", "port": "25/tcp", "severity": "0.0", "name": "SSL/TLS: Report Weak Cipher Suites" }, { "oid": "1.3.6.1.4.1.25623.1.0.805142", "port": "25/tcp", "severity": "4.3", "name": "SSL/TLS: RSA Temporary Key Handling 'RSA_EXPORT' Downgrade Issue (FREAK)" }, { "oid": "1.3.6.1.4.1.25623.1.0.802087", "port": "25/tcp", "severity": "4.3", "name": "SSL/TLS: SSLv3 Protocol CBC Cipher Suites Information Disclosure Vulnerability (POODLE)" }, { "oid": "1.3.6.1.4.1.25623.1.0.802087", "port": "5432/tcp", "severity": "4.3", "name": "SSL/TLS: SSLv3 Protocol CBC Cipher Suites Information Disclosure Vulnerability (POODLE)" }, { "oid": "1.3.6.1.4.1.25623.1.0.80091", "port": "general/tcp", "severity": "2.6", "name": "TCP timestamps" }, { "oid": "1.3.6.1.4.1.25623.1.0.10498", "port": "80/tcp", "severity": "7.5", "name": "Test HTTP dangerous methods" }, { "oid": "1.3.6.1.4.1.25623.1.0.108064", "port": "80/tcp", "severity": "5.0", "name": "Tiki Wiki CMS Groupware 'fixedURLData' Local File Inclusion Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.100537", "port": "80/tcp", "severity": "7.5", "name": "Tiki Wiki CMS Groupware < 4.2 Multiple Unspecified Vulnerabilities" }, { "oid": "1.3.6.1.4.1.25623.1.0.800315", "port": "80/tcp", "severity": "5.0", "name": "Tiki Wiki CMS Groupware Input Sanitation Weakness Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.901001", "port": "80/tcp", "severity": "0.0", "name": "Tiki Wiki CMS Groupware Version Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.140797", "port": "80/tcp", "severity": "6.5", "name": "Tiki Wiki CMS Groupware XSS Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.51662", "port": "general/tcp", "severity": "0.0", "name": "Traceroute" }, { "oid": "1.3.6.1.4.1.25623.1.0.800400", "port": "80/tcp", "severity": "6.0", "name": "TWiki Cross-Site Request Forgery Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.801281", "port": "80/tcp", "severity": "6.8", "name": "TWiki Cross-Site Request Forgery Vulnerability - Sep10" }, { "oid": "1.3.6.1.4.1.25623.1.0.800399", "port": "80/tcp", "severity": "0.0", "name": "TWiki Version Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.800320", "port": "80/tcp", "severity": "10.0", "name": "TWiki XSS and Command Execution Vulnerabilities" }, { "oid": "1.3.6.1.4.1.25623.1.0.106056", "port": "5900/tcp", "severity": "9.0", "name": "VNC Brute Force Login" }, { "oid": "1.3.6.1.4.1.25623.1.0.19288", "port": "5900/tcp", "severity": "0.0", "name": "VNC security types" }, { "oid": "1.3.6.1.4.1.25623.1.0.10342", "port": "5900/tcp", "severity": "0.0", "name": "VNC Server and Protocol Version Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.103185", "port": "6200/tcp", "severity": "7.5", "name": "vsftpd Compromised Source Packages Backdoor Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.103185", "port": "21/tcp", "severity": "7.5", "name": "vsftpd Compromised Source Packages Backdoor Vulnerability" }, { "oid": "1.3.6.1.4.1.25623.1.0.111050", "port": "21/tcp", "severity": "0.0", "name": "vsFTPd FTP Server Detection" }, { "oid": "1.3.6.1.4.1.25623.1.0.10407", "port": "6000/tcp", "severity": "0.0", "name": "X Server Detection" } ], "oss": { "Ubuntu 16.04": "cpe:/o:canonical:ubuntu_linux:16.04", "Debian GNU/Linux": "cpe:/o:debian:debian_linux:9", "Linux/Unix": "cpe:/o:linux:kernel", "Greenbone OS (GOS)": "cpe:/o:greenbone:greenbone_os:5.0.10" }, "apps": { "Greenbone Security Manager (GSM) / Greenbone OS (GOS) Detection (Version)": "cpe:/a:greenbone:gsm_expo", "OpenSSH 4.3": "cpe:/a:openbsd:openssh:4.3", "OpenSSH 5.3": "cpe:/a:openbsd:openssh:5.3", "OpenSSH 6.6.1": "cpe:/a:openbsd:openssh:6.6.1", "Plone": "cpe:/a:plone:plone", "Apache Server 2.2.3": "cpe:/a:apache:http_server:2.2.3", "Apache Tomcat": "cpe:/a:apache:tomcat", "PHP 5.1.6": "cpe:/a:php:php:5.1.6", "MySQL 5.1.73": "cpe:/a:oracle:mysql:5.1.73", "Webmin 1.791": "cpe:/a:webmin:webmin:1.791", "Samba 3.3.9": "cpe:/a:samba:samba:3.3.9" }, "host_details": [ { "name": "MAC", "value": "00:CC:29:DD:01:D6", "source_name": "1.3.6.1.4.1.25623.1.0.103585", "source_description": "Nmap MAC Scan", "source_type": "nvt" }, { "name": "Services", "value": "6000,tcp,X11", "source_name": "1.3.6.1.4.1.25623.1.0.10407", "source_description": "Service detection (1.3.6.1.4.1.25623.1.0.10407)", "source_type": "nvt" }, { "name": "TLS/5432", "value": "SSLv3", "source_name": "1.3.6.1.4.1.25623.1.0.103823", "source_description": "SSL/TLS: Version Detection Report", "source_type": "nvt" }, { "name": "Services", "value": "2049,tcp,RPC/nfs", "source_name": "1.3.6.1.4.1.25623.1.0.11111", "source_description": "Service detection (1.3.6.1.4.1.25623.1.0.11111)", "source_type": "nvt" }, { "name": "tcp_ports", "value": "80,8787,5900,3632,8009,6667,445,21,2049,111,22,6000,512,23,513,1099,25,514,3306,2121,139,1524,53,5432", "source_name": "1.3.6.1.4.1.25623.1.0.900239", "source_description": "Check Open TCP Ports", "source_type": "nvt" }, { "name": "SSLInfo", "value": "25::E7A7FA0D63E457C7C4A59B31230849C6A70BDA6F830C7AF1E32DEE436DE813CC", "source_name": "1.3.6.1.4.1.25623.1.0.103692", "source_description": "SSL/TLS Certificate Information", "source_type": "nvt" }, { "name": "Services", "value": "23,tcp,telnet,A telnet server seems to be running on this port", "source_name": "1.3.6.1.4.1.25623.1.0.100074", "source_description": "Service detection (1.3.6.1.4.1.25623.1.0.100074)", "source_type": "nvt" }, { "name": "Auth-SMB-Success", "value": "Protocol SMB, Port 445, User", "source_name": "1.3.6.1.4.1.25623.1.0.10394", "source_type": "nvt" }, { "name": "TLS/25", "value": "TLSv1.0", "source_name": "1.3.6.1.4.1.25623.1.0.103823", "source_description": "SSL/TLS: Version Detection Report", "source_type": "nvt" }, { "name": "ssh-key", "value": "22 ssh-rsa AAAABCNzaC1yc2EAAAABIwAAAQEA123nuFMBOZvO3WTEjP4TUdjgWkIVNdTq6kboEDjteOfc65TlI7sRvQBwqAhQjeeyyIk8T55gMDkOD0akSlSXvLDcmcdYfxeIF0ZSuT+nkRhij7XSSA/Oc5QSk3sJ/SInfb78e3anbRHpmkJcVgETJ5WhKObUNf1AKZW++4Xlc63M4KI5cjvMMIPEVOyR3AKmI78Fo3HJjYucg87JjLeC66I7+dlEYX6zT8i1XYwa/L1vZ3qSJISGVu8kRPikMv/cNSvki4j+qDYyZ2E5497W87+Ed46/8P42LNGoOV8OcX/ro6pAcbEPUdUEfkJrqi2YXbhvwIJ0gFMb6wfe5cnQew==", "source_name": "1.3.6.1.4.1.25623.1.0.100259", "source_description": "SSH Key", "source_type": "nvt" } ], "not_vuln": { "name": "EXIT_CODE", "value": "EXIT_NOTVULN", "source_name": "1.3.6.1.4.1.25623.1.0.", "source_type": "nvt" } } gvm-tools-23.11.0/scripts/delete-overrides-by-filter.gmp.py000066400000000000000000000036751452466721200236110ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys import time from argparse import Namespace from gvm.protocols.gmp import Gmp def check_args(args): len_args = len(args.script) - 1 if len_args != 1: message = """ This script deletes overrides with a specific filter value -- the parameter for the filter. Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/delete-overrides-by-filter.gmp.py """ print(message) sys.exit() def delete_overrides(gmp, filter_value): filters = gmp.get_overrides(filter=filter_value) if not filters.xpath("override"): print(f"No overrides with filter: {filter_value}") for f_id in filters.xpath("override/@id"): print(f"Delete override: {f_id}", end="") res = gmp.delete_override(f_id) if "OK" in res.xpath("@status_text")[0]: print(" OK") else: print(" ERROR") time.sleep(60) def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) filter_value = args.script[1] delete_overrides(gmp, filter_value) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/export-pdf-report.gmp.py000066400000000000000000000053401452466721200220440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2019-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import Namespace from base64 import b64decode from pathlib import Path from gvm.protocols.gmp import Gmp def check_args(args): len_args = len(args.script) - 1 if len_args < 1: message = """ This script requests the given report and exports it as a pdf file locally. It requires one parameters after the script name. 1. -- ID of the report Optional a file name to save the pdf in. Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/export-pdf-report.gmp.py """ print(message) sys.exit() def main(gmp: Gmp, args: Namespace) -> None: # check if report id and PDF filename are provided to the script # argv[0] contains the script name check_args(args) report_id = args.argv[1] if len(args.argv) == 3: pdf_filename = args.argv[2] else: pdf_filename = args.argv[1] + ".pdf" pdf_report_format_id = "c402cc3e-b531-11e1-9163-406186ea4fc5" response = gmp.get_report( report_id=report_id, report_format_id=pdf_report_format_id ) report_element = response.find("report") # get the full content of the report element content = report_element.find("report_format").tail if not content: print( "Requested report is empty. Either the report does not contain any " " results or the necessary tools for creating the report are " "not installed.", file=sys.stderr, ) sys.exit(1) # convert content to 8-bit ASCII bytes binary_base64_encoded_pdf = content.encode("ascii") # decode base64 binary_pdf = b64decode(binary_base64_encoded_pdf) # write to file and support ~ in filename path pdf_path = Path(pdf_filename).expanduser() pdf_path.write_bytes(binary_pdf) print("Done. PDF created: " + str(pdf_path)) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/generate-random-reports.gmp.py000077500000000000000000000403651452466721200232200ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2023 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # pylint: disable=too-many-lines import json import textwrap from argparse import ArgumentParser, Namespace, RawTextHelpFormatter from pathlib import Path from random import choice, gauss, randrange, seed from datetime import datetime, timedelta from gvm.protocols.gmp import Gmp from lxml import etree as e from gvmtools.helper import generate_id, generate_random_ips, generate_uuid __version__ = "0.3.1" HELP_TEXT = f""" Random Report Generation Script {__version__} (C) 2017-2023 Greenbone AG This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . This script generates randomized report data. """ LOREM_IPSUM = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.""" local_date_time = datetime.now() def calculate_timestamp(delta: int = 0) -> str: return ( (local_date_time + timedelta(seconds=delta)) .astimezone() .replace(microsecond=0) .isoformat() ) def generate_ports(n_ports): protocol = ["/tcp", "/udp"] return [str(randrange(0, 65536)) + choice(protocol) for i in range(n_ports)] def generate_report_elem(task, **kwargs): rep_format_id = "a994b278-1f62-11e1-96ac-406186ea4fc5" rep_id = generate_uuid() outer_report_elem = e.Element( "report", attrib={ "extension": "xml", "id": rep_id, "format_id": rep_format_id, "content_type": "text/xml", }, ) owner_elem = e.SubElement(outer_report_elem, "owner") e.SubElement(owner_elem, "name").text = "testowner" e.SubElement(outer_report_elem, "name").text = "testname" e.SubElement(outer_report_elem, "writeable").text = str(0) e.SubElement(outer_report_elem, "in_use").text = str(0) task_elem = e.SubElement(outer_report_elem, "task", attrib={"id": task[0]}) e.SubElement(task_elem, "name").text = task[1] repform_elem = e.SubElement( outer_report_elem, "report_format", attrib={"id": rep_format_id} ) e.SubElement(repform_elem, "name").text = "XML" # Generating inner tag outer_report_elem.append(generate_inner_report(rep_id, **kwargs)) return outer_report_elem def generate_inner_report( rep_id, n_results, n_hosts, data, with_descriptions=False, **kwargs ): report_elem = e.Element("report", attrib={"id": rep_id}) results_elem = e.SubElement( report_elem, "results", {"max": str(n_results), "start": "1"} ) # Create Hosts, Ports, Data hosts = generate_random_ips(n_hosts) # Host IPs ports = generate_ports(n_hosts) oid_dict = {host: [] for host in hosts} asset_dict = {host: generate_uuid() for host in hosts} host_names = {host: generate_id() for host in hosts} max_sev = 0.0 # Create tags with random data for _ in range(n_results): host_ip = choice(hosts) host_port = choice(ports) result_elem, oid, severity = generate_result_elem( data["vulns"], host_ip, host_port, asset_dict[host_ip], host_names[host_ip], with_descriptions=with_descriptions, ) if float(severity) > max_sev: max_sev = float(severity) oid_dict[host_ip].append(oid) results_elem.append(result_elem) e.SubElement(report_elem, "result_count").text = str(n_results) sev_elem = e.Element("severity") e.SubElement(sev_elem, "full").text = str(max_sev) e.SubElement(sev_elem, "filtered").text = str(max_sev) report_elem.append(sev_elem) # Create tags with random data for host in hosts: if len(oid_dict[host]) > 0: report_elem.append( generate_host_elem( host, oid_dict[host][0], asset_dict[host], host_names[host], data=data, **kwargs, ) ) return report_elem def generate_result_elem( vulns, host_ip, host_port, host_asset, host_name, with_descriptions=False ): result_elem = e.Element("result", {"id": generate_uuid()}) e.SubElement(result_elem, "name").text = "a_result" + generate_id() own = e.SubElement(result_elem, "owner") e.SubElement(own, "name").text = generate_id() elem = e.Element("modification_time") e.SubElement(result_elem, "modification_time").text = calculate_timestamp(0) e.SubElement(result_elem, "comment").text = "" e.SubElement(result_elem, "creation_time").text = calculate_timestamp(-20) host_elem = e.Element("host") host_elem.text = host_ip e.SubElement(host_elem, "asset", {"asset_id": host_asset}).text = "" e.SubElement(host_elem, "hostname").text = host_name result_elem.append(host_elem) port_elem = e.Element("port") port_elem.text = host_port result_elem.append(port_elem) nvt = vulns[randrange(len(vulns))] e.SubElement(result_elem, "severity").text = nvt["severity"] nvt_elem = e.Element("nvt", {"oid": nvt["oid"]}) result_elem.append(nvt_elem) if with_descriptions: nvt_oid = nvt["oid"] description = ( f"Generated result for VT {nvt_oid} on {host_ip}" + f" port {host_port}\n{LOREM_IPSUM}" ) e.SubElement(result_elem, "description").text = description e.SubElement(result_elem, "notes").text = "TestNotes" result_elem.append(elem) return result_elem, nvt["oid"], nvt["severity"] def generate_host_detail_elem( name, value, source_name=None, source_description=None, source_type=None ): host_detail_elem = e.Element("detail") e.SubElement(host_detail_elem, "name").text = name e.SubElement(host_detail_elem, "value").text = value if source_name: source_elem = e.SubElement(host_detail_elem, "source") e.SubElement(source_elem, "name").text = source_name if source_description: e.SubElement(source_elem, "description").text = source_description if source_type: e.SubElement(source_elem, "type").text = source_type return host_detail_elem def generate_additional_host_details( n_details, host_details, *, not_vuln=False ): host_detail_elems = [] for _ in range(n_details): details = None if not_vuln: details = host_details.copy() details["source_name"] += str(randrange(14259, 103585)) else: details = choice(host_details) host_detail_elems.append( generate_host_detail_elem( details["name"], details["value"], source_name=details.get("source_name"), source_description=details.get("source_description"), source_type=details.get("source_type"), ) ) return host_detail_elems def generate_host_elem( host_ip, oid, host_asset, host_name, n_host_details, n_not_vuln, data ): host_elem = e.Element("host") e.SubElement(host_elem, "ip").text = host_ip e.SubElement(host_elem, "asset", {"asset_id": host_asset}).text = "" e.SubElement(host_elem, "start").text = calculate_timestamp(-1000) e.SubElement(host_elem, "end").text = calculate_timestamp(-30) app = choice(list(data["apps"])) os = choice(list(data["oss"])) host_elem.append( generate_host_detail_elem( "App", data["apps"].get(app), source_name=oid, source_type="nvt", ) ) host_elem.append( generate_host_detail_elem( data["apps"].get(app), "/usr/bin/foo", source_name=oid, source_type="nvt", ) ) host_elem.append( generate_host_detail_elem( "hostname", host_name, source_name=oid, source_description="Host Details", source_type="nvt", ) ) host_elem.append( generate_host_detail_elem( "best_os_txt", os, source_name=oid, source_description="Host Details", source_type="nvt", ) ) host_elem.append( generate_host_detail_elem( "best_os_cpe", data["oss"].get(os), source_name=oid, source_description="Host Details", source_type="nvt", ) ) if n_host_details: host_elem.extend( generate_additional_host_details( n_host_details, data["host_details"] ) ) dev = n_not_vuln / 10 if n_not_vuln: host_elem.extend( generate_additional_host_details( n_not_vuln + randrange(-dev, dev), data["not_vuln"], not_vuln=True, ) ) return host_elem def generate_reports(task, n_reports, n_results, with_gauss, **kwargs): reports = [] if with_gauss: n_reports = abs(int(gauss(n_reports, 1))) if n_reports == 0: n_reports += 1 for _ in range(n_reports): if with_gauss: n_results = abs(int(gauss(n_results, 2))) report_elem = generate_report_elem(task, n_results=n_results, **kwargs) report_elem = e.tostring(report_elem) reports.append(report_elem) return reports def generate_target(): target_name = f"Random_Report_Generation_Target_{calculate_timestamp(0)}" target_comment = ( f"Generated by Random Report Generation Script Version {__version__}." ) target_hosts = ["198.18.0.0/24"] target_port_list_id = "c7e03b6c-3bbe-11e1-a057-406186ea4fc5" gmp.create_target( name=target_name, comment=target_comment, hosts=target_hosts, port_list_id=target_port_list_id, ) def generate_data(gmp, n_tasks, task_type, **kwargs): if task_type == "scan": scanner_id = "08b69003-5fc2-4037-a479-93b440211c73" config_id = "085569ce-73ed-11df-83c3-002264764cea" generate_target() target_id = gmp.get_targets( filter_string=f'"Random_Report_Generation_Target_{calculate_timestamp(0)}"' ).xpath("//@id")[0] for i in range(n_tasks): index = f"{{0:0>{len(str(n_tasks))}}}" task_name = f"Random_Report_Generation_{task_type.capitalize()}_Task_{calculate_timestamp(0)}_{index.format(i + 1)}" task_comment = f"Generated by Random Report Generation Script Version {__version__}." gmp.create_container_task(name=task_name, comment=task_comment) task_id = gmp.get_tasks(filter_string=f"name={task_name}").xpath( "//@id" )[0] if task_type == "scan": gmp.modify_task(task_id=task_id, alterable=1) reports = generate_reports(task=(task_id, task_name), **kwargs) for report in reports[0:]: gmp.import_report(report, task_id=task_id, in_assets=True) if task_type == "scan": gmp.modify_task( task_id=task_id, target_id=target_id, scanner_id=scanner_id, config_id=config_id, ) def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable, line-too-long parser = ArgumentParser( prog="random-report-gen", prefix_chars="-", description=HELP_TEXT, formatter_class=RawTextHelpFormatter, add_help=False, epilog=textwrap.dedent( """ Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/gen-random-reports.gmp.py \ -T 5 -r 4 -R 3 --hosts 10 """ ), ) parser.add_argument( "-H", action="help", help="Show this help message and exit." ) parser.add_argument( "--datafile", default=Path(args.script[0]).parent / "default_report_data.json", help="A json file containing the following information: " "vulnerabilities, operating systems, applications and host details. " "Take the default json file as an example.", ) parser.add_argument( "--tasks", "-T", type=int, default="1", help="Number of Tasks to be generated.", ) parser.add_argument( "--reports", "-r", type=int, default="5", help="Number of Reports per Task.", ) parser.add_argument( "--results", "-R", type=int, default="5", help="Number of Results per Report.", ) parser.add_argument( "--hosts", type=int, default="5", help="Number of randomized hosts to select from.", ) parser.add_argument( "--host-details", dest="host_details", type=int, default="2", help="Number of additional host details per host.", ) parser.add_argument( "--not-vuln-details", dest="not_vuln", type=int, default="10", help="Number of 'NOT_VULN' host details per host.", ) parser.add_argument( "--with-descriptions", dest="with_descriptions", action="store_true", help="Adds descriptions to results generated from other result fields" " and some dummy text.", ) parser.add_argument( "--with-gauss", dest="with_gauss", action="store_true", help="if you would like for the number of reports/task and " "results/report to be randomized along a Gaussian distribution.", ) parser.add_argument( "--seed", help="RNG Seed, in case the same data should be generated." ) parser.add_argument( "--task-type", dest="task_type", type=str, choices=["container", "scan"], default="container", help="Type of Task(s) to store the generated Reports. Can either " "be 'container' or 'scan', default: 'container'.", ) script_args = parser.parse_args(args.script_args) if not script_args.seed: seed() else: seed(script_args.seed) with open(str(script_args.datafile), encoding="utf-8") as file: data = json.load(file) print("\n Generating randomized data...\n") generate_data( gmp, n_tasks=script_args.tasks, n_reports=script_args.reports, n_results=script_args.results, n_hosts=script_args.hosts, n_host_details=script_args.host_details, n_not_vuln=script_args.not_vuln, data=data, with_gauss=script_args.with_gauss, with_descriptions=script_args.with_descriptions, task_type=script_args.task_type, ) print("\n Generation completed.\n") if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/generate-random-targets.gmp.py000066400000000000000000000044671452466721200231730ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import Namespace from random import choice, gauss from gvm.protocols.gmp import Gmp from gvmtools.helper import generate_random_ips def check_args(args): len_args = len(args.script) - 1 if len_args < 2: message = """ This script generates random task data and feeds it to\ a desired GSM It needs two parameters after the script name. 1. -- number of dummy hosts to select from 2. -- number of targets to be generated In addition, if you would like for the number of targets generated to be randomized on a Gaussian distribution, add 'with-gauss' Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/gen-random-targets.gmp.py 3 40 with-gauss """ print(message) sys.exit() def generate(gmp, args, n_targets, n_ips): ips = generate_random_ips(n_ips) if "with-gauss" in args.script: n_targets = int(gauss(n_targets, 2)) for i in range(n_targets): host_ip = choice(ips) index = f"{{0:0>{len(str(n_targets))}}}" name = f"Target_{index.format(i + 1)}" gmp.create_target(name=name, make_unique=True, hosts=[host_ip]) def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) host_number = int(args.script[1]) number_targets = int(args.script[2]) print("Generating random targets...") generate(gmp, args, number_targets, host_number) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/list-tasks.gmp.py000066400000000000000000000025661452466721200205500ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2019-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from argparse import Namespace from gvm.protocols.gmp import Gmp from gvmtools.helper import Table def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=unused-argument response_xml = gmp.get_tasks() tasks_xml = response_xml.xpath("task") heading = ["ID", "Name", "Severity"] rows = [] for task in tasks_xml: name = "".join(task.xpath("name/text()")) task_id = task.get("id") severity = "".join(task.xpath("last_report/report/severity/text()")) rows.append([task_id, name, severity]) print(Table(heading=heading, rows=rows)) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/monthly-report-gos4.gmp.py000066400000000000000000000071411452466721200223210ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import Namespace from datetime import date, timedelta from gvm.protocols.gmp import Gmp from terminaltables import AsciiTable def check_args(args: Namespace) -> None: len_args = len(args.script) - 1 if len_args < 2: message = """ This script will display all vulnerabilities from the hosts of the reports in a given month! It needs two parameters after the script name. First one is the month and second one is the year. Both parameters are plain numbers, so no text. Explicitly made for GOS 4.X. 1. -- month of the monthly report 2. -- year of the monthly report Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/monthly-report2.gmp.py 05 2019 """ print(message) sys.exit() def print_reports(gmp: Gmp, from_date: date, to_date: date) -> None: host_filter = ( f"rows=-1 and modified>{from_date.isoformat()} " f"and modified<{to_date.isoformat()}" ) hosts_xml = gmp.get_hosts(filter_string=host_filter) sum_high = 0 sum_medium = 0 sum_low = 0 table_data = [["Hostname", "IP", "Bericht", "high", "medium", "low"]] for host in hosts_xml.xpath("asset"): ip = host.xpath("name/text()")[0] hostnames = host.xpath( 'identifiers/identifier/name[text()="hostname"]/../value/text()' ) if len(hostnames) == 0: continue hostname = hostnames[0] results = gmp.get_results( details=False, filter=f"host={ip} and severity>0.0" ) low = int(results.xpath('count(//result/threat[text()="Low"])')) sum_low += low medium = int(results.xpath('count(//result/threat[text()="Medium"])')) sum_medium += medium high = int(results.xpath('count(//result/threat[text()="High"])')) sum_high += high best_os_cpe_report_id = host.xpath( 'host/detail/name[text()="best_os_cpe"]/../source/@id' )[0] table_data.append( [hostname, ip, best_os_cpe_report_id, high, medium, low] ) table = AsciiTable(table_data) print(f"{table.table}\n") print( f"Summary of results from {from_date.isoformat()} " f"to {to_date.isoformat()}" ) print(f"High: {int(sum_high)}") print(f"Medium: {int(sum_medium)}") print(f"Low: {int(sum_low)}\n\n") def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) month = int(args.script[1]) year = int(args.script[2]) from_date = date(year, month, 1) to_date = from_date + timedelta(days=31) # To have the first day in month to_date = to_date.replace(day=1) print_reports(gmp, from_date, to_date) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/monthly-report.gmp.py000066400000000000000000000103061452466721200214440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import Namespace from datetime import date, timedelta from gvm.protocols.gmp import Gmp from lxml.etree import Element from terminaltables import AsciiTable def check_args(args: Namespace) -> None: len_args = len(args.script) - 1 if len_args < 2: message = """ This script will display all vulnerabilities from the hosts of the reports in a given month! 1. -- month of the monthly report 2. -- year of the monthly report The third is 'with-tables' parameter to activate a verbose output of hosts. Explicitly made for GOS 3.1. Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/monthly-report.gmp.py 05 2017 with-tables """ print(message) sys.exit() def get_reports_xml(gmp: Gmp, from_date: date, to_date: date) -> Element: """Getting the Reports in the defined time period""" report_filter = ( f"rows=-1 created>{from_date.isoformat()} and " f"created<{to_date.isoformat()}" ) return gmp.get_reports(filter_string=report_filter) def print_result_sums( reports_xml: Element, from_date: date, to_date: date ) -> None: report_count = len(reports_xml.xpath("report")) print(f"Found {report_count} reports") sum_high = reports_xml.xpath( "sum(report/report/result_count/hole/full/text())" ) sum_medium = reports_xml.xpath( "sum(report/report/result_count/warning/full/text())" ) sum_low = reports_xml.xpath( "sum(report/report/result_count/info/full/text())" ) print( f"Summary of results from {from_date.isoformat()} " f"to {to_date.isoformat()}" ) print(f"High: {int(sum_high)}") print(f"Medium: {int(sum_medium)}") print(f"Low: {int(sum_low)}") def print_result_tables(gmp: Gmp, reports_xml: Element) -> None: report_list = reports_xml.xpath("report") for report in report_list: report_id = report.xpath("report/@id")[0] name = report.xpath("name/text()")[0] res = gmp.get_report(report_id) print(f"\nReport: {report_id}") table_data = [["Hostname", "IP", "Bericht", "high", "medium", "low"]] for host in res.xpath("report/report/host"): hostname = host.xpath( 'detail/name[text()="hostname"]/../value/text()' ) if len(hostname) > 0: hostname = str(hostname[0]) else: hostname = "" ip = host.xpath("ip/text()")[0] high = host.xpath("result_count/hole/page/text()")[0] medium = host.xpath("result_count/warning/page/text()")[0] low = host.xpath("result_count/info/page/text()")[0] table_data.append([hostname, ip, name, high, medium, low]) table = AsciiTable(table_data) print(table.table + "\n") def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) month = int(args.script[1]) year = int(args.script[2]) from_date = date(year, month, 1) to_date = from_date + timedelta(days=31) # To have the first day in month to_date = to_date.replace(day=1) reports_xml = get_reports_xml(gmp, from_date, to_date) print_result_sums(reports_xml, from_date, to_date) if "with-tables" in args.script: print_result_tables(gmp, reports_xml) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/nvt-scan.gmp.py000066400000000000000000000071461452466721200202020ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import Namespace from datetime import datetime from gvm.errors import GvmError from gvm.protocols.gmp import Gmp def check_args(args): len_args = len(args.script) - 1 if len_args != 2: message = """ This script creates a new task with specific host and nvt! It needs two parameters after the script name. -- oid of the nvt -- scan target Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname 1.3.6.1.4.1.25623.1.0.106223 localhost """ print(message) sys.exit() def create_scan_config(gmp, nvt_oid) -> str: # Create new config copy_id = "085569ce-73ed-11df-83c3-002264764cea" config_name = nvt_oid config_id = "" try: res = gmp.create_scan_config(copy_id, config_name) config_id = res.xpath("@id")[0] # Modify the config with an nvt oid nvt = gmp.get_scan_config_nvt(nvt_oid) family = nvt.xpath("nvt/family/text()")[0] gmp.modify_scan_config_set_nvt_selection( config_id=config_id, nvt_oids=[nvt_oid], family=family ) # This nvts must be present to work family = "Port scanners" nvts = ["1.3.6.1.4.1.25623.1.0.14259", "1.3.6.1.4.1.25623.1.0.100315"] gmp.modify_scan_config_set_nvt_selection( config_id, nvt_oids=nvts, family=family ) except GvmError: res = gmp.get_scan_configs(filter_string=f"name={config_name}") config_id = res.xpath("config/@id")[0] return config_id def create_target(gmp: Gmp, name: str) -> str: try: res = gmp.create_target(name, hosts=[name]) target_id = res.xpath("@id")[0] except GvmError: res = gmp.get_targets(filter_string=f"name={name} hosts={name}") target_id = res.xpath("target/@id")[0] return target_id def create_and_start_task( gmp: Gmp, name: str, nvt_oid: str, config_id: str, target_id: str ) -> None: # Standard Scanner OpenVAS Default scanner_id = "08b69003-5fc2-4037-a479-93b440211c73" date_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # Create task task_name = f"{name}_{nvt_oid}_{date_time}" res = gmp.create_task( name=task_name, config_id=config_id, target_id=target_id, scanner_id=scanner_id, ) task_id = res.xpath("@id")[0] # Start the task gmp.start_task(task_id=task_id) print(f"\nTask {task_id} started") def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) nvt_oid = args.script[1] target_name = args.script[2] config_id = create_scan_config(gmp, nvt_oid) target_id = create_target(gmp, target_name) create_and_start_task(gmp, target_name, nvt_oid, config_id, target_id) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/requirements.txt000066400000000000000000000002031452466721200205640ustar00rootroot00000000000000# required for monthly-report and monthly-report2 and certbund-report terminaltables # required for create-cve-report-from-json cpegvm-tools-23.11.0/scripts/ruff.toml000066400000000000000000000000331452466721200171400ustar00rootroot00000000000000builtins = ["gmp", "args"] gvm-tools-23.11.0/scripts/scan-new-system.gmp.py000066400000000000000000000062051452466721200215010ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2019-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import datetime import sys from argparse import Namespace from gvm.protocols.gmp import Gmp def check_args(args): len_args = len(args.script) - 1 message = """ This script starts a new scan on the given host. It needs one parameters after the script name. 1. IP Address of the host system 2. Port List UUID for scanning the host system. Preconfigured UUID might be under /var/lib/gvm/data-objects/gvmd/20.08/port_lists/. ex. iana-tcp-udp is "4a4717fe-57d2-11e1-9a26-406186ea4fc5". Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/scan-new-system.gmp.py """ if len_args != 2: print(message) sys.exit() def create_target(gmp, ipaddress, port_list_id): # create a unique name by adding the current datetime name = f"Suspect Host {ipaddress} {str(datetime.datetime.now())}" response = gmp.create_target( name=name, hosts=[ipaddress], port_list_id=port_list_id ) return response.get("id") def create_task(gmp, ipaddress, target_id, scan_config_id, scanner_id): name = f"Scan Suspect Host {ipaddress}" response = gmp.create_task( name=name, config_id=scan_config_id, target_id=target_id, scanner_id=scanner_id, ) return response.get("id") def start_task(gmp, task_id): response = gmp.start_task(task_id) # the response is # id return response[0].text def main(gmp: Gmp, args: Namespace) -> None: check_args(args) ipaddress = args.argv[1] port_list_id = args.argv[2] target_id = create_target(gmp, ipaddress, port_list_id) full_and_fast_scan_config_id = "daba56c8-73ec-11df-a475-002264764cea" openvas_scanner_id = "08b69003-5fc2-4037-a479-93b440211c73" task_id = create_task( gmp, ipaddress, target_id, full_and_fast_scan_config_id, openvas_scanner_id, ) report_id = start_task(gmp, task_id) print( f"Started scan of host {ipaddress}. " f"Corresponding report ID is {report_id}" ) if __name__ == "__gmp__": # pylint: disable=undefined-variable main(gmp, args) gvm-tools-23.11.0/scripts/send-delta-emails.gmp.py000066400000000000000000000166651452466721200217470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # GMP script for gvm-pyshell to send emails with delta reports. import base64 import datetime import sched import smtplib import sys import time from argparse import Namespace from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.utils import formatdate from gvm.protocols.gmp import Gmp def check_args(args: Namespace) -> None: len_args = len(args.script) - 1 if len_args != 0: message = """ This script, once started, will continuously send delta reports via email for selected tasks. Example for starting up the routine: $ gvm-script --gmp-username name --gmp-password pass ssh \ --hostname scripts/send-delta-emails.gmp.py The routine follows this procedure: Every minutes do: Get all tasks where the tag is attached. For each of these tasks get the finished reports: If less than 2 reports, continue with next task If latest report has tag "delta_alert_sent", continue with next task Create a CSV report from the delta of latest vs. previous report where filtered for only the new results. Send the CSV as an attachment to the configured email address. """ print(message) sys.exit() def execute_send_delta_emails(sc: sched.scheduler, **kwargs: dict) -> None: gmp = kwargs.get("gmp") task_tag = kwargs.get("task_tag") interval = kwargs.get("interval") email_subject = kwargs.get("email_subject") to_addresses = kwargs.get("to_addresses") from_address = kwargs.get("from_address") mta_address = kwargs.get("mta_address") mta_user = kwargs.get("mta_user") mta_port = kwargs.get("mta_port") mta_password = kwargs.get("mta_password") report_tag_name = kwargs.get("report_tag_name") print("Retrieving task list ...") task_filter = f"tag={task_tag}" tasks = gmp.get_tasks(filter_string=task_filter).xpath("task") print(f'Found {str(len(tasks))} task(s) with tag "{task_tag}".') for task in tasks: task_id = task.xpath("@id")[0] task_name = task.xpath("name/text()")[0] print(f'Processing task "{task_name}" ({task_id})...') reports = gmp.get_reports( filter_string=f"task_id={task_id} and status=Done " "sort-reverse=date" ).xpath("report") print(f" Found {str(len(reports))} report(s).") if len(reports) < 2: print(" Delta-reporting requires at least 2 finished reports.") continue if reports[0].xpath( 'report/user_tags/tag/name[text()="delta_alert_sent"]' ): print(" Delta report for latest finished report already sent") continue print( " Latest finished report not send yet. Preparing delta " "report..." ) delta_report = gmp.get_report( report_id=reports[0].xpath("@id")[0], delta_report_id=reports[1].xpath("@id")[0], filter_string="delta_states=n", format_id="c1645568-627a-11e3-a660-406186ea4fc5", ) csv_in_b64 = delta_report.xpath("report/text()")[0] csv = base64.b64decode(csv_in_b64) print(" Composing Email...") alert_email = MIMEMultipart() alert_email["Subject"] = email_subject alert_email["To"] = ", ".join(to_addresses) alert_email["From"] = from_address alert_email["Date"] = formatdate(localtime=True) report_attachment = MIMEBase("application", "octet-stream") report_attachment.add_header( "Content-Disposition", "attachment", filename="delta.csv" ) report_attachment.set_payload(csv) alert_email.attach(report_attachment) print(" Sending Email...") try: with smtplib.SMTP(mta_address, mta_port) as smtp: smtp.ehlo() smtp.starttls() smtp.ehlo() smtp.login(mta_user, mta_password) # if required smtp.sendmail( from_address, to_addresses, alert_email.as_string() ) smtp.close() print(" Email has been sent!") gmp.create_tag( name=report_tag_name, resource_id=reports[0].xpath("@id")[0], resource_type="report", value=datetime.datetime.now(), ) except Exception: # pylint: disable=broad-except print(" Unable to send the email. Error: ", sys.exc_info()[0]) # raise # in case an error should stop the script continue # ignore the problem for the time being print(f"\nCheck will be repeated in {str(interval)} minutes...\n") sc.enter( interval * 60, 1, execute_send_delta_emails, argument=(sc,), kwargs=kwargs, ) def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) interval = 1 # in minutes task_tag = "send_delta_alert" report_tag_name = "delta_alert_sent" email_subject = "Delta Report" from_address = "admin@example.com" to_addresses = ["user1@example.com", "user2@example.com"] mta_address = "mail.example.com" mta_port = 25 mta_user = "admin@example.com" mta_password = "mysecret" print("send_delta_alerts starting up with following settings:") print(f"User: {args.username}") print(f"Interval: {str(interval)} minutes") print(f"Task tag: {task_tag}") print(f"Email subject: {email_subject}") print(f"From Address: {from_address}") print(f"To Addresses: {to_addresses}") print(f"MTA Address: {mta_address}") print(f"MTA Port: {str(mta_port)}") print(f"MTA User: {mta_user}") print("MTA Password: ") print() print(f"Entering loop with interval {str(interval)} minutes ...") schedule = sched.scheduler(time.time, time.sleep) # Enter the scheduled execution with the given interval schedule.enter( 0, 1, execute_send_delta_emails, argument=(schedule,), kwargs={ "gmp": gmp, "task_tag": task_tag, "interval": interval, "email_subject": email_subject, "to_addresses": to_addresses, "from_address": from_address, "mta_address": mta_address, "mta_password": mta_password, "mta_port": mta_port, "mta_user": mta_user, "report_tag_name": report_tag_name, }, ) schedule.run() if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/send-schedules.gmp.py000066400000000000000000000046211452466721200213520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import Namespace from gvm.protocols.gmp import Gmp from lxml.etree import Element from gvmtools.helper import create_xml_tree def check_args(gmp: Gmp, args: Namespace) -> None: len_args = len(args.script) - 1 if len_args != 1: message = """ This script pulls schedule data from an xml document and feeds it to \ a desired GSM One parameter after the script name is required. 1. -- .xml file containing schedules Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/send-schedules.gmp.py example_file.xml """ print(message) sys.exit() major, minor = gmp.get_protocol_version() if major < 21 and minor < 5: print(f"This script requires GMP version {major}.{minor}") sys.exit() def parse_send_xml_tree(gmp: Gmp, xml_tree: Element) -> None: for schedule in xml_tree.xpath("schedule"): name = schedule.find("name").text comment = schedule.find("comment").text if comment is None: comment = "" ical = schedule.find("icalendar").text timezone = schedule.find("timezone").text gmp.create_schedule( name=name, comment=comment, timezone=timezone, icalendar=ical ) def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(gmp=gmp, args=args) xml_doc = args.script[1] print("\nSending schedules...") xml_tree = create_xml_tree(xml_doc) parse_send_xml_tree(gmp, xml_tree) print("\n Schedule(s) created!\n") if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/send-targets.gmp.py000066400000000000000000000105531452466721200210450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2018-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import Namespace from gvm.protocols.gmp import Gmp from lxml.etree import Element from gvmtools.helper import create_xml_tree, yes_or_no def check_args(args: Namespace) -> None: len_args = len(args.script) - 1 if len_args != 1: message = """ This script pulls target data from an xml document and feeds it to \ a desired GSM One parameter after the script name is required. 1. -- .xml file containing targets Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/send-targets.gmp.py example_file.xml """ print(message) sys.exit() def parse_send_xml_tree(gmp: Gmp, xml_tree: Element) -> None: credential_options = [ "ssh_credential", "smb_credential", "esxi_credential", "snmp_credential", ] counter = 1 for target in xml_tree.xpath("target"): keywords = {} # {'make_unique': True} keywords["name"] = target.find("name").text keywords["hosts"] = target.find("hosts").text.split(",") exclude_hosts = target.find("exclude_hosts").text if exclude_hosts is not None: keywords["exclude_hosts"] = exclude_hosts.split(",") comment = target.find("comment").text if comment is not None: keywords["comment"] = comment credentials = gmp.get_credentials()[0].xpath("//credential/@id") for credential in credential_options: cred_id = target.find(credential).xpath("@id")[0] if cred_id == "": continue if cred_id not in credentials: response = yes_or_no( f"\nThe credential '{credential}' for 'target {counter}' " "could not be located...\nWould you like to continue?" ) if response is False: print("Terminating...\n") sys.exit() else: continue key = f"{credential}_id" keywords[key] = cred_id elem_path = target.find(credential) port = elem_path.find("port") if port is not None and port.text is not None: port_key = f"{credential}_port" keywords[port_key] = elem_path.find("port").text alive_test = gmp.types.AliveTest.from_string( target.find("alive_tests").text ) if alive_test is not None: keywords["alive_test"] = alive_test reverse_lookup_only = target.find("reverse_lookup_only").text if reverse_lookup_only == "1": keywords["reverse_lookup_only"] = 1 reverse_lookup_unify = target.find("reverse_lookup_unify").text if reverse_lookup_unify == "1": keywords["reverse_lookup_unify"] = 1 port_range = target.find("port_range") if port_range is not None: keywords["port_range"] = port_range.text if target.xpath("port_list/@id") is not None: port_list = {} port_list = target.xpath("port_list/@id")[0] keywords["port_list_id"] = port_list print(keywords) gmp.create_target(**keywords) counter += 1 def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) xml_doc = args.script[1] print("\nSending targets...") xml_tree = create_xml_tree(xml_doc) parse_send_xml_tree(gmp, xml_tree) print("\n Target(s) created!\n") if __name__ == "__gmp__": main(gmp, args) # pylint: disable=undefined-variable gvm-tools-23.11.0/scripts/send-tasks.gmp.py000066400000000000000000000134131452466721200205170ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2018-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import ArgumentParser, Namespace, RawTextHelpFormatter from gvm.protocols.gmp import Gmp from gvmtools.helper import create_xml_tree, error_and_exit, yes_or_no HELP_TEXT = """ This script pulls tasks data from an xml document and feeds it to \ a desired GSM Usage examples: $ gvm-script --gmp-username name --gmp-password pass ssh --hostname ... send-task.gmp.py +h ... send-task.gmp.py ++x xml_file """ def numerical_option(statement, list_range): choice = int(input(statement)) if choice in range(1, list_range + 1): return choice else: return numerical_option( f"Please enter valid number from 1 to {list_range}...", list_range, ) def interactive_options(gmp, task, keywords): options_dict = {} options_dict["config"] = gmp.get_scan_configs() options_dict["scanner"] = gmp.get_scanners() options_dict["target"] = gmp.get_targets() for option_key, option_value in options_dict.items(): object_dict, object_list = {}, [] object_id = task.find(option_key).get("id") object_xml = option_value for i in object_xml.findall(option_key): object_dict[i.find("name").text] = i.xpath("@id")[0] object_list.append(i.find("name").text) if object_id in object_dict.values(): keywords[f"{option_key}_id"] = object_id elif object_id not in object_dict.values() and len(object_dict) != 0: response = yes_or_no( f"\nRequired Field: failed to detect {option_key}_id: " f"{task.xpath(f'{option_key}/@id')[0]}... " "\nWould you like to select from available options, or exit " "the script?" ) if response is True: counter = 1 print(f"{option_key.capitalize()} options:") for j in object_list: print(f" {counter} - {j}") counter += 1 answer = numerical_option( "\nPlease enter the number of your choice.", len(object_list), ) keywords[f"{option_key}_id"] = object_dict[ object_list[answer - 1] ] else: print("\nTerminating...") sys.exit() else: error_and_exit( f"Failed to detect {option_key}_id\nThis field is required " "therefore the script is unable to continue.\n" ) def parse_send_xml_tree(gmp, xml_tree): task_xml_elements = xml_tree.xpath("task") print(task_xml_elements) if not task_xml_elements: error_and_exit("No tasks found.") tasks = [] for task in task_xml_elements: keywords = {"name": task.find("name").text} if task.find("comment").text is not None: keywords["comment"] = task.find("comment").text interactive_options(gmp, task, keywords) if task.find("schedule_periods") is not None: keywords["schedule_periods"] = int( task.find("schedule_periods").text ) if task.find("observers").text: keywords["observers"] = task.find("observers").text if task.xpath("schedule/@id")[0]: keywords["schedule_id"] = task.xpath("schedule/@id")[0] if task.xpath("preferences/preference"): preferences, scanner_name_list, value_list = {}, [], [] for preference in task.xpath("preferences/preference"): scanner_name_list.append(preference.find("scanner_name").text) if preference.find("value").text is not None: value_list.append(preference.find("value").text) else: value_list.append("") preferences["scanner_name"] = scanner_name_list preferences["value"] = value_list keywords["preferences"] = preferences new_task = gmp.create_task(**keywords) tasks.append(new_task.xpath("//@id")[0]) return tasks def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable, unused-argument parser = ArgumentParser( prefix_chars="+", add_help=False, formatter_class=RawTextHelpFormatter, description=HELP_TEXT, ) parser.add_argument( "+h", "++help", action="help", help="Show this help message and exit.", ) parser.add_argument( "+x", "++xml-file", dest="xml", type=str, required=True, help="xml file containing tasks", ) script_args, _ = parser.parse_known_args() # check_args(args) print("\nSending task(s)...") xml_tree = create_xml_tree(script_args.xml) tasks = parse_send_xml_tree(gmp, xml_tree) for task in tasks: print(task) print("\nTask(s) sent!\n") if __name__ == "__gmp__": main(gmp, args) # pylint: disable=undefined-variable gvm-tools-23.11.0/scripts/ssv_csv.py000066400000000000000000000356661452466721200173650ustar00rootroot00000000000000#!/usr/bin/env python3 # coding: utf-8 # - # Copyright © 2015, 2017, 2020, 2022 # mirabilos # Licensor: tarent solutions GmbH # # Provided that these terms and disclaimer and all copyright notices # are retained or reproduced in an accompanying document, permission # is granted to deal in this work without restriction, including un†# limited rights to use, publicly perform, distribute, sell, modify, # merge, give away, or sublicence. # # This work is provided “AS IS†and WITHOUT WARRANTY of any kind, to # the utmost extent permitted by applicable law, neither express nor # implied; without malicious intent or gross negligence. In no event # may a licensor, author or contributor be held liable for indirect, # direct, other damage, loss, or other issues arising in any way out # of dealing in the work, even if advised of the possibility of such # damage or existence of a defect, except proven that it results out # of said person’s immediate fault when using the work as intended. r"""SSV reader/writer and CSV writer library This module offers the following classes: - CSVInvalidCharacterError, CSVShapeError -- Exception classes that can be thrown by code from this library - CSVPrinter -- configurable CSV row formatter and writer that ensures the output is quoted properly and in rectangular shape - SSVPrinter -- CSVPrinter configured to produce SSV output - CSVWriter, SSVWriter -- same but writing to a file-like object - SSVReader -- class to read SSV files, returning lists of str|bytes (depending on the input file binary flag) When run directly, it acts as SSV to CSV converter, which may be of limited use but demonstrates how to use the module somewhat; -h for usage (help). """ __all__ = [ "CSVInvalidCharacterError", "CSVPrinter", "CSVShapeError", "CSVWriter", "SSVPrinter", "SSVReader", "SSVWriter", ] import re import sys from typing import IO, AnyStr, List, Optional, TextIO class CSVShapeError(Exception): r"""Error: data to write did not have a consistent amount of columns. The message string is descriptive, but the want and got fields of an object may be user-accessed. """ def __init__(self, want: int, got: int) -> None: Exception.__init__( self, f"got {got} column{got != 1 and 's' or ''} but wanted {want}" ) self.want = want # type: int self.got = got # type: int class CSVInvalidCharacterError(Exception): r"""Error: disallowed characters in cell (writer) / row (reader). The message is deliberately constant in order to not show the actual cell or row content in logs, etc. (in case it’s a password) but the actual content is available in the questionable_content field. """ def __init__( self, value: AnyStr, what: str = "prohibited character in cell" ) -> None: Exception.__init__(self, what) self.questionable_content = value # type: Union[str, bytes] class CSVPrinter(object): r"""CSV writer library, configurable. The defaults follow RFC 4180 and thus are suitable for use with most environments; newlines embedded in cell data are normalised (from ASCII/Unix/Mac to ASCII) by default, every cell data is quoted. The following arguments configure the writer instance: - sep -- output cell separator: ',' (default) or ';' or '\t' - quot -- output quote character (default '"'), escape by doubling; None to disable quoting and disallow embedded newlines (but not double quotes; the caller must not pass any if the result needs to conform to the RFC or just use default quoting of course) - eol -- output line terminator: '\r\n' or (Unix) '\n' or (Mac) '\r' - qnl -- output embedded newline, should match eol (default '\r\n'); None to disable embedded newline normalisation These arguments must all be str, bytes is not supported. Cell data passed in that is not str will be stringified. Rows are written or returned as str; however, ASCII or a compatible encoding needs to be used (for conformance and portability); UTF-8 is ideal. Note: RFC 4180 permits only printable ASCII and space and, if quoted, newlines in cell data but this library permits any character except NUL, (unquoted) CR and LF. """ # embedded NUL is never permitted _invf = re.compile("[\x00]") # type: Pattern[str] # normalise embedded newlines (match) _nlf = re.compile("\r\n?|(? None: if quot is None: # cell joiner ('","') self._sep = sep # type: str # one quote, e.g. for line beginning/end self._quots = "" # type: str # two quotes to escape self._quotd = "" # type: str # forbid newlines if we cannot quote them self._invf = re.compile("[\x00\r\n]") else: self._sep = quot + sep + quot self._quots = quot self._quotd = quot + quot # None if not quoting self._quot = quot # type: Optional[str] # None or embedded newline replacement string self._nlrpl = qnl # type: Optional[str] # EOL string self._eol = eol # type: str def _mapcell(self, cell) -> str: if isinstance(cell, str): cstr = cell # type: str else: cstr = str(cell) if self._invf.search(cstr) is not None: raise CSVInvalidCharacterError(cstr) if self._nlrpl is not None: cstr = self._nlf.sub(self._nlrpl, cstr) if self._quot is not None: cstr = cstr.replace(self._quots, self._quotd) return cstr def write(self, *args) -> None: r"""Print a CSV line (row) to standard output. - *args -- cell data by columns Note: reconfigures the newline mode of sys.stdout once, but make sure to run sys.stdout.reconfigure(newline='\n') e.g. when emitting an MS Excel sep= line and get the line ending for that right; see _main() for an example. """ if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(newline="\n") # type: ignore setattr(CSVPrinter, "write", getattr(CSVPrinter, "_write")) delattr(CSVPrinter, "_write") return self.write(*args) def _write(self, *args) -> None: print(self.format(*args), end="") _write.__doc__ = write.__doc__ def format(self, *args) -> str: r"""Produce a CSV row from cells. - *args -- cell data by columns Returns the row, including the trailing newline, as string. """ if self._ncols == -1: self._ncols = len(args) elif self._ncols != len(args): raise CSVShapeError(self._ncols, len(args)) cells = map(self._mapcell, args) return self._quots + self._sep.join(cells) + self._quots + self._eol class CSVWriter(CSVPrinter): r"""CSV writer library, configurable. The defaults follow RFC 4180 and thus are suitable for use with most environments; newlines embedded in cell data are normalised (from ASCII/Unix/Mac to ASCII) by default, every cell data is quoted. The following arguments configure the writer instance: - file -- file-like object to output CSV to - sep -- output cell separator: ',' (default) or ';' or '\t' - quot -- output quote character (default '"'), escape by doubling; None to disable quoting and disallow embedded newlines (but not double quotes; the caller must not pass any if the result needs to conform to the RFC or just use default quoting of course) - eol -- output line terminator: '\r\n' or (Unix) '\n' or (Mac) '\r' - qnl -- output embedded newline, should match eol (default '\r\n'); None to disable embedded newline normalisation These arguments must all be str, bytes is not supported. Cell data passed in that is not str will be stringified. Rows are written or returned as str; however, ASCII or a compatible encoding needs to be used (for conformance and portability); UTF-8 is ideal. Note: RFC 4180 permits only printable ASCII and space and, if quoted, newlines in cell data but this library permits any character except NUL, (unquoted) CR and LF. Note: the file argument will be reconfigured to disable automatic '\n' conversion; if prepending data (e.g. a sep= line for MS Excel) use the writeln() method. """ def __init__( self, file: TextIO, sep: str = ",", quot: Optional[str] = '"', eol: str = "\r\n", qnl: Optional[str] = "\r\n", ) -> None: CSVPrinter.__init__(self, sep, quot, eol, qnl) # disable any automatic newline conversion if preset file.reconfigure(newline="\n") # type: ignore self.outfile = file def write(self, *args) -> None: r"""Print a CSV line (row) to the output file. - *args -- cell data by columns """ print(self.format(*args), end="", file=self.outfile) def writeln(self, line: str) -> None: r"""Print an arbitrary nÅn-CSV line to the output file. - line -- str to output; trailing newline is automatically added """ print(line, end=self._eol, file=self.outfile) class SSVPrinter(CSVPrinter): r"""SSV writer library. This subclass sets up a CSVPrinter instance to produce SSV (see below). The writer supports str, or stringified arguments, only, not bytes. The caller must ensure the encoding is UTF-8 (ideally), or at least ASCII-compatible (CR, LF and \x1F require identity mapping), and that CR or LF characters output are not converted. shell-parseable separated values (or separator-separated values) is an idea to make CSV into something usable: • newline (\x0A) is row separator • unit separator (\x1F) is column separator • n̲o̲ quotes or escape characters • carriage return (\x0D) represents embedded newlines in cells Cell content is, in theory, arbitrary binary except NUL and the separators (\x1F and \x0A). In practice it should be UTF-8. SSV can be easily read from shell scripts: while IFS=$'\x1F' read -r col1 col2…; do # do something done """ def __init__(self) -> None: CSVPrinter.__init__(self, sep="\x1F", quot=None, eol="\n", qnl="\r") # not permitted in SSV data self._invf = re.compile("[\x00\x1F]") class SSVWriter(CSVWriter): r"""SSV writer library (same as SSVPrinter except to file)""" def __init__(self, file: TextIO) -> None: # pylint: disable=C0301 CSVWriter.__init__( self, file, sep="\x1F", quot=None, eol="\n", qnl="\r" ) # not permitted in SSV data self._invf = re.compile("[\x00\x1F]") if SSVPrinter.__doc__ is not None: SSVWriter.__doc__ = SSVPrinter.__doc__.replace("CSVPrinter", "CSVWriter") class SSVReader(object): r"""SSV reader library. This library is initialised with a files-like object that must support .readline() and either must not use newline conversion or support .reconfigure() as in _io.TextIOWrapper, which is called with newline='\n' if it exists. SSVReader.read() will then proceed to read from it. See SSVPrinter about the SSV format. """ def __init__(self, file: IO) -> None: if hasattr(file, "reconfigure"): # see https://bugs.python.org/issue46695 though file.reconfigure(newline="\n") # type: ignore self.f = file # type: IO @staticmethod def _read( line: AnyStr, lf: AnyStr, cr: AnyStr, us: AnyStr, nl: AnyStr, nul: AnyStr, ) -> List[AnyStr]: if line.find(nul) != -1: raise CSVInvalidCharacterError(line, "NUL in row") if line[-1:] != lf: raise CSVInvalidCharacterError(line, "unterminated row") line = line[:-1] if line.find(lf) != -1: raise CSVInvalidCharacterError(line, "LF in row") return line.replace(cr, nl).split(us) def read(self) -> Optional[List[AnyStr]]: r"""Read and decode one SSV line. Returns a list of cells, or None on EOF. """ line = self.f.readline() # type: Optional[AnyStr] if not line: return None if isinstance(line, str): return self._read(line, "\n", "\r", "\x1F", "\r\n", "\x00") if isinstance(line, bytes): return self._read(line, b"\n", b"\r", b"\x1F", b"\r\n", b"\x00") raise TypeError() # mostly example of how to use this def _main() -> None: # pylint: disable=C0103 newline_ways = { "ascii": "\r\n", "unix": "\n", "mac": "\r", } p = argparse.ArgumentParser( description="Converts SSV to CSV.", # part of https://bugs.python.org/issue46700 workaround add_help=False, ) g = p.add_argument_group("Options") # issue46700 g.add_argument("-h", action="help", help="show this help message and exit") g.add_argument( "-s", metavar="sep", help="cell separator, e.g. \x27,\x27 (default: tab)", default="\t", ) g.add_argument( "-q", metavar="qch", help="quote character, e.g. \x27\x22\x27 (default: none)", default=None, ) g.add_argument( "-n", metavar="eoltype", choices=list(newline_ways.keys()), help="line endings (ascii (default), unix, mac)", default="ascii", ) g.add_argument( "-P", metavar="preset", choices=["std", "sep", "ssv"], help="predefined config (std=RFC 4180, sep=Excel header, ssv=SSV)", ) g = p.add_argument_group("Arguments") # issue46700 g.add_argument( "file", nargs="?", help='SSV file to read, "-" for stdin (default)', default="-", ) args = p.parse_args() if args.P in ("std", "sep"): args.s = "," args.q = '"' args.n = "ascii" nl = newline_ways[args.n] if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(newline="\n") # type: ignore if args.P == "sep": print(f"sep={args.s}", end=nl) if args.P != "ssv": w = CSVPrinter(args.s, args.q, nl, nl) else: w = SSVPrinter() def _convert(f): r = SSVReader(f) # no walrus in Python 3.7 yet ☹ while True: row = r.read() if row is None: break w.write(*row) if args.file == "-": _convert(sys.stdin) else: with open(args.file, "r", encoding="utf-8") as file: _convert(file) if __name__ == "__main__": import argparse _main() gvm-tools-23.11.0/scripts/start-alert-scan.gmp.py000066400000000000000000000262151452466721200216330ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2018 Henning Häcker # Copyright (C) 2019-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from argparse import ArgumentParser, Namespace, RawTextHelpFormatter from typing import List from gvm.protocols.gmp import Gmp HELP_TEXT = """ This script makes an E-Mail alert scan. Usage examples: $ gvm-script --gmp-username name --gmp-password pass ssh --hostname ... start-alert-scan.gmp.py +h ... start-alert-scan.gmp.py ++target-name ++hosts ++ports \ ++port-list-name +C +R +S ... start-alert-scan.gmp.py ++target-name ++hosts ++port-list-id \ +C ++recipient ++sender ... start-alert-scan.gmp.py ++target-id +C ++recipient ++sender """ def get_scan_config(gmp: Gmp, config: int, debug: bool = False): # get all configs of the openvas instance # filter for all rows! res = gmp.get_scan_configs(filter_string="rows=-1") if config < 0 or config > 4: raise ValueError("Wrong config identifier. Choose between [0,4].") # match the config abbreviation to accepted config names config_list = [ "Full and fast", "Full and fast ultimate", "Full and very deep", "Full and very deep ultimate", "System Discovery", ] template_abbreviation_mapper = { 0: config_list[0], 1: config_list[1], 2: config_list[2], 3: config_list[3], 4: config_list[4], } for conf in res.xpath("config"): cid = conf.xpath("@id")[0] name = conf.xpath("name/text()")[0] # get the config id of the desired template if template_abbreviation_mapper.get(config) == name: config_id = cid if debug: print(name + ": " + config_id) break return config_id def get_target( gmp, target_name: str = None, hosts: List[str] = None, ports: str = None, port_list_name: str = None, port_list_id: str = None, debug: bool = False, ): if target_name is None: target_name = "target" targets = gmp.get_targets(filter_string=target_name) existing_targets = [""] for target in targets.findall("target"): existing_targets.append(str(target.find("name").text)) counter = 0 # iterate over existing targets and find a vacant targetName if target_name in existing_targets: while True: tmp_name = f"{target_name} ({str(counter)})" if tmp_name in existing_targets: counter += 1 else: target_name = tmp_name break if debug: print(f"target name: {target_name}") if not port_list_id: existing_port_lists = [""] port_lists_tree = gmp.get_port_lists() for plist in port_lists_tree.findall("port_list"): existing_port_lists.append(str(plist.find("name").text)) if port_list_name is None: port_list_name = "portlist" if port_list_name in existing_port_lists: counter = 0 while True: tmp_name = f"{port_list_name} ({str(counter)})" if tmp_name in existing_port_lists: counter += 1 else: port_list_name = tmp_name break port_list = gmp.create_port_list(name=port_list_name, port_range=ports) # create port list port_list_id = port_list.xpath("@id")[0] if debug: print(f"New Portlist-name:\t{port_list_name}") print(f"New Portlist-id: \t{str(port_list_id)}") # integrate port list id into create_target res = gmp.create_target(target_name, hosts=hosts, port_list_id=port_list_id) print(f"New target '{target_name}' created.") return res.xpath("@id")[0] def get_alert( gmp: Gmp, sender_email: str, recipient_email: str, alert_name: str = None, debug: bool = False, ): # create alert if necessary alert_object = gmp.get_alerts(filter_string=f"name={alert_name}") alert = alert_object.xpath("alert") if len(alert) == 0: print(f"creating new alert {alert_name}") gmp.create_alert( name=alert_name, event=gmp.types.AlertEvent.TASK_RUN_STATUS_CHANGED, event_data={"status": "Done"}, condition=gmp.types.AlertCondition.ALWAYS, method=gmp.types.AlertMethod.EMAIL, method_data={ """Task '$n': $e After the event $e, the following condition was met: $c This email escalation is configured to attach report format '$r'. Full details and other report formats are available on the scan engine. $t Note: This email was sent to you as a configured security scan escalation. Please contact your local system administrator if you think you should not have received it. """: "message", "2": "notice", sender_email: "from_address", "[OpenVAS-Manager] Task": "subject", "c402cc3e-b531-11e1-9163-406186ea4fc5": "notice_attach_format", recipient_email: "to_address", }, ) alert_object = gmp.get_alerts(filter_string=f"name={recipient_email}") alert = alert_object.xpath("alert") alert_id = alert[0].get("id", "no id found") if debug: print(f"alert_id: {str(alert_id)}") return alert_id def get_scanner(gmp: Gmp): res = gmp.get_scanners() scanner_ids = res.xpath("scanner/@id") return scanner_ids[1] # "default scanner" def create_and_start_task( gmp: Gmp, config_id: str, target_id: str, scanner_id: str, alert_id: str, alert_name: str, debug: bool = False, ) -> str: # Create the task task_name = f"Alert Scan for Alert {alert_name}" tasks = gmp.get_tasks(filter_string=f'name="{task_name}"') existing_tasks = tasks.findall("task") if existing_tasks: task_name = f"Alert Scan for Alert {alert_name} ({len(existing_tasks)})" task_comment = "Alert Scan" res = gmp.create_task( name=task_name, config_id=config_id, target_id=target_id, scanner_id=scanner_id, alert_ids=[alert_id], comment=task_comment, ) # Start the task task_id = res.xpath("@id")[0] gmp.start_task(task_id) if debug: # Stop the task (for performance reasons) gmp.stop_task(task_id=task_id) print("Task stopped") return task_name def parse_args(args: Namespace): # pylint: disable=unused-argument parser = ArgumentParser( prefix_chars="+", add_help=False, formatter_class=RawTextHelpFormatter, description=HELP_TEXT, ) parser.add_argument( "+h", "++help", action="help", help="Show this help message and exit.", ) target = parser.add_mutually_exclusive_group(required=True) target.add_argument( "++target-id", type=str, dest="target_id", help="Use an existing target by target id", ) target.add_argument( "++target-name", type=str, dest="target_name", help="Create a target by name", ) parser.add_argument( "++hosts", nargs="+", dest="hosts", help="Host(s) for the new target", ) ports = parser.add_mutually_exclusive_group() ports.add_argument( "++port-list-id", type=str, dest="port_list_id", help="An existing portlist id for the new target", ) ports.add_argument( "++ports", type=str, dest="ports", help="Ports in the new target: e.g. T:80-80,8080", ) parser.add_argument( "++port-list-name", type=str, dest="port_list_name", help="Name for the new portlist in the new target", ) config = parser.add_mutually_exclusive_group() config.add_argument( "+C", "++scan-config", default=0, type=int, dest="config", help="Choose from existing scan config:" "\n 0: Full and fast" "\n 1: Full and fast ultimate" "\n 2: Full and very deep" "\n 3: Full and very deep ultimate" "\n 4: System Discovery", ) config.add_argument( "++scan-config-id", type=str, dest="scan_config_id", help="Use existing scan config by id", ) parser.add_argument( "++scanner-id", type=str, dest="scanner_id", help="Use existing scanner by id", ) parser.add_argument( "+R", "++recipient", required=True, dest="recipient_email", type=str, help="Alert recipient E-Mail address", ) parser.add_argument( "+S", "++sender", required=True, dest="sender_email", type=str, help="Alert senders E-Mail address", ) parser.add_argument( "++alert-name", dest="alert_name", type=str, help="Optional Alert name", ) script_args, _ = parser.parse_known_args() return script_args def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable, unused-argument script_args = parse_args(args) # set alert_name to recipient email if no other name # is given if script_args.alert_name is None: script_args.alert_name = script_args.recipient_email # use existing config from argument if not script_args.scan_config_id: config_id = get_scan_config(gmp, script_args.config) else: config_id = script_args.scan_config_id # create new target or use existing one from id if not script_args.target_id: target_id = get_target( gmp, target_name=script_args.target_name, hosts=script_args.hosts, ports=script_args.ports, port_list_name=script_args.port_list_name, port_list_id=script_args.port_list_id, ) else: target_id = script_args.target_id alert_id = get_alert( gmp, script_args.sender_email, script_args.recipient_email, script_args.alert_name, ) if not script_args.scanner_id: scanner_id = get_scanner(gmp) else: scanner_id = script_args.scanner_id task_name = create_and_start_task( gmp, config_id, target_id, scanner_id, alert_id, script_args.alert_name ) print(f"Task started: {task_name}\n") print("Script finished\n") if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/start-multiple-alerts-scan.gmp.py000066400000000000000000000212251452466721200236430ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2018 inovex GmbH # Copyright (C) 2019-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import Namespace from typing import List from gvm.protocols.gmp import Gmp def check_args(args): len_args = len(args.script) - 1 message = """ This script makes an alert scan It needs two parameters after the script name. 1. -- E-Mail of the sender 2. -- E-Mail of the receiver Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/start-multiple-alert-scan.gmp.py \ """ if len_args != 2: print(message) sys.exit() # returns a list containing all port_list names def get_port_list_names(gmp) -> List[str]: res = gmp.get_port_lists() port_names_list = [""] for name in res.findall("port_list/name"): port_names_list.append(str(name.text)) return port_names_list def get_scan_config(gmp, debug=False): # get all configs of the openvas instance res = gmp.get_scan_configs() # configurable template template = "fast" # match the config abbreviation to accepted config names config_list = [ "Full and fast", "Full and fast ultimate", "Full and very deep", "Full and very deep ultimate", "System Discovery", ] template_abbreviation_mapper = { "fast": config_list[0], "fast-ulti": config_list[1], "deep": config_list[2], "deep-ulti": config_list[3], "discovery": config_list[4], } config_id = "-" for conf in res.xpath("config"): cid = conf.xpath("@id")[0] name = conf.xpath("name/text()")[0] # get the config id of the desired template if template_abbreviation_mapper.get(template, "-") == name: config_id = cid if debug: print(f"{name}: {config_id}") break # check for existence of the desired config if config_id == "-": print( f"error: could not recognize template '{template}'\n" f"valid template names are: {config_list}\n" ) exit() return config_id def get_target(gmp, debug=False): # find a targetName targets = gmp.get_targets() counter = 0 exists = True # iterate over existing targets and find a vacant targetName while exists: exists = False target_name = f"targetName{str(counter)}" for target in targets.xpath("target"): name = target.xpath("name/text()")[0] if name == target_name: exists = True break counter += 1 if debug: print(f"target name: {target_name}") # iterate over existing port lists and find a vacant name new_port_list_name = "portlistName" counter = 0 while True: portlist_name = f"{new_port_list_name}{str(counter)}" if portlist_name not in get_port_list_names(gmp): break counter += 1 # configurable port string port_string = "T:80-80" # create port list portlist = gmp.create_port_list(portlist_name, port_string) portlist_id = portlist.xpath("@id")[0] if debug: print(f"Portlist-name:\t{str(portlist_name)}") print(f"Portlist-id:\t{str(portlist_id)}") # configurable hosts hosts = ["localhost"] # integrate port list id into create_target res = gmp.create_target( name=target_name, hosts=hosts, port_list_id=portlist_id ) return res.xpath("@id")[0] def get_alerts(gmp, sender_email, recipient_email, debug=False) -> List[str]: # configurable alert name alert_name = recipient_email # create alert if necessary alert_object = gmp.get_alerts(filter=f"name={alert_name}") alert_id = None alert = alert_object.xpath("alert") if len(alert) == 0: gmp.create_alert( alert_name, event=gmp.types.AlertEvent.TASK_RUN_STATUS_CHANGED, event_data={"status": "Done"}, condition=gmp.types.AlertCondition.ALWAYS, method=gmp.types.AlertMethod.EMAIL, method_data={ """Task '$n': $e After the event $e, the following condition was met: $c This email escalation is configured to attach report format '$r'. Full details and other report formats are available on the scan engine. $t Note: This email was sent to you as a configured security scan escalation. Please contact your local system administrator if you think you should not have received it. """: "message", "2": "notice", sender_email: "from_address", "[OpenVAS-Manager] Task": "subject", "c402cc3e-b531-11e1-9163-406186ea4fc5": "notice_attach_format", recipient_email: "to_address", }, ) alert_object = gmp.get_alerts(filter=f"name={recipient_email}") alert = alert_object.xpath("alert") alert_id = alert[0].get("id", "no id found") else: alert_id = alert[0].get("id", "no id found") if debug: print(f"alert_id: {str(alert_id)}") # second configurable alert name alert_name2 = f"{recipient_email}-2" # create second alert if necessary alert_object2 = gmp.get_alerts(filter=f"name={alert_name2}") alert_id2 = None alert2 = alert_object2.xpath("alert") if len(alert2) == 0: gmp.create_alert( alert_name2, event=gmp.types.AlertEvent.TASK_RUN_STATUS_CHANGED, event_data={"status": "Done"}, condition=gmp.types.AlertCondition.ALWAYS, method=gmp.types.AlertMethod.EMAIL, method_data={ """Task '$n': $e After the event $e, the following condition was met: $c This email escalation is configured to attach report format '$r'. Full details and other report formats are available on the scan engine. $t Note: This email was sent to you as a configured security scan escalation. Please contact your local system administrator if you think you should not have received it. """: "message", "2": "notice", sender_email: "from_address", "[OpenVAS-Manager] Task": "subject", recipient_email: "to_address", }, ) alert_object2 = gmp.get_alerts(filter=f"name={recipient_email}") alert2 = alert_object2.xpath("alert") alert_id2 = alert2[0].get("id", "no id found") else: alert_id2 = alert2[0].get("id", "no id found") if debug: print(f"alert_id2: {str(alert_id2)}") return [alert_id, alert_id2] def get_scanner(gmp): res = gmp.get_scanners() scanner_ids = res.xpath("scanner/@id") return scanner_ids[1] # default scanner def create_and_start_task( gmp, config_id, target_id, scanner_id, alerts, debug=False ): # Create the task tasks = gmp.get_tasks(filter="name~ScanDoneMultipleAlert") task_name = f"ScanDoneMultipleAlert{len(tasks.xpath('tasks/@id'))}" task_comment = "test comment" res = gmp.create_task( name=task_name, config_id=config_id, target_id=target_id, scanner_id=scanner_id, alert_ids=alerts, comment=task_comment, ) # Start the task task_id = res.xpath("@id")[0] gmp.start_task(task_id) print(f"Task started: {task_name}") if debug: # Stop the task (for performance reasons) gmp.stop_task(task_id) print("Task stopped") def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) sender_email = args.script[1] recipient_email = args.script[2] config_id = get_scan_config(gmp) target_id = get_target(gmp) alerts = get_alerts(gmp, sender_email, recipient_email) scanner_id = get_scanner(gmp) create_and_start_task(gmp, config_id, target_id, scanner_id, alerts) print("\nScript finished\n") if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/start-nvt-scan.gmp.py000066400000000000000000000127251452466721200213340ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from argparse import Namespace from gvm.protocols.gmp import Gmp def check_args(args): len_args = len(args.script) - 1 if len_args != 2: message = """ This script creates a new task with specific host and nvt! It needs two parameters after the script name. First one is the oid of the nvt and the second one is the chosen scan target. Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/start-nvt-scan.gmp.py \ 1.3.6.1.4.1.25623.1.0.106223 localhost """ print(message) sys.exit() def get_scan_config(gmp, nvt_oid): # Choose from existing config, which to copy or create new config res = gmp.get_scan_configs() config_ids = res.xpath("config/@id") for i, conf in enumerate(res.xpath("config")): config_id = conf.xpath("@id")[0] name = conf.xpath("name/text()")[0] print(f"\n({i}) {name}: ({config_id})") while True: chosen_config = input( "\nChoose your config or create new one" f"[0-{len(config_ids) - 1} | n]: " ) if chosen_config == "n": chosen_copy_config = int( input(f"Which config to copy from? [0-{len(config_ids) - 1}]: ") ) config_name = input("Enter new Name for config: ") copy_id = config_ids[chosen_copy_config] res = gmp.clone_scan_config(copy_id) config_id = res.xpath("@id")[0] # Modify the config with an nvt oid if len(nvt_oid) == 0: nvt_oid = input("NVT OID: ") nvt = gmp.get_scan_config_nvt(nvt_oid=nvt_oid) family = nvt.xpath("nvt/family/text()")[0] gmp.modify_scan_config( config_id, "nvt_selection", name=config_name, nvt_oids=[nvt_oid], family=family, ) # This nvts must be present to work family = "Port scanners" nvts = [ "1.3.6.1.4.1.25623.1.0.14259", "1.3.6.1.4.1.25623.1.0.100315", ] gmp.modify_scan_config( config_id, "nvt_selection", nvt_oids=nvts, family=family ) return config_id if 0 <= int(chosen_config) < len(config_ids): return config_ids[int(chosen_config)] def get_target(gmp, hosts): # create a new target or use an existing targets = gmp.get_targets() target_ids = targets.xpath("target/@id") for i, target in enumerate(targets.xpath("target")): name = target.xpath("name/text()")[0] print(f"\n({i}) {name}") while True: if target_ids: chosen_target = input( "\nChoose your target or create new" f" one[0-{len(target_ids) - 1} | n]: " ) else: chosen_target = "n" if chosen_target == "n": if len(hosts) == 0: hosts = input("Target hosts (comma separated): ") name = input("Name of target: ") res = gmp.create_target(name, hosts=hosts.split(",")) return res.xpath("@id")[0] if 0 <= int(chosen_target) < len(target_ids): return target_ids[int(chosen_target)] def get_scanner(gmp): res = gmp.get_scanners() scanner_ids = res.xpath("scanner/@id") for i, scanner in enumerate(res.xpath("scanner")): scanner_id = scanner.xpath("@id")[0] name = scanner.xpath("name/text()")[0] # configs[id] = name print(f"\n({i})\n{name}: ({scanner_id})") while True: chosen_scanner = int( input(f"\nChoose your scanner [0-{len(scanner_ids) - 1}]: ") ) if 0 <= chosen_scanner < len(scanner_ids): return scanner_ids[chosen_scanner] def create_and_start_task( gmp, task_name, task_comment, config_id, target_id, scanner_id ): res = gmp.create_task( name=task_name, config_id=config_id, target_id=target_id, scanner_id=scanner_id, comment=task_comment, ) # Start the task task_id = res.xpath("@id")[0] gmp.start_task(task_id) print("Task started") def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) nvt_oid = args.script[1] hosts = args.script[2] task_name = input("Task name: ") task_comment = input("Task comment: ") config_id = get_scan_config(gmp, nvt_oid) target_id = get_target(gmp, hosts) scanner_id = get_scanner(gmp) create_and_start_task( gmp, task_name, task_comment, config_id, target_id, scanner_id ) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/sync-hosts.gmp.py000066400000000000000000000046171452466721200205630ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import csv import sys from argparse import Namespace from gvm.protocols.gmp import Gmp def check_args(args): len_args = len(args.script) - 1 if len_args != 1: message = """ This script reads host data from a csv file and sync it with the gsm. It needs one parameters after the script name. 1. - should contain a table of IP-addresses with an optional a comment Example: $ gvm-script --gmp-username name --gmp-password pass \ ssh --hostname scripts/sync-hosts.gmp.py """ print(message) sys.exit() def sync_hosts(gmp, filename): with open(filename, newline="", encoding="utf-8") as f: reader = csv.reader(f, delimiter=",", quotechar="|") for row in reader: if len(row) == 2: ip = row[0] comment = row[1] # check if host exists ret = gmp.get_hosts(filter_string=f"ip={ip}") if ret.xpath("host"): print(f"\nAsset with IP {ip} exist") host_id = ret.xpath("host/@id")[0] gmp.delete_host(host_id=host_id) else: print(f"Asset with ip {ip} does not exist. Sync...") ret = gmp.create_host(name=ip, comment=comment) if "OK" in ret.xpath("@status_text")[0]: print("Asset synced") def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable check_args(args) file = args.script[1] sync_hosts(gmp, file) if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/scripts/update-task-target.gmp.py000066400000000000000000000115301452466721200221470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2017-2021 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import time from argparse import ArgumentParser, FileType, Namespace, RawTextHelpFormatter from typing import List from gvm.protocols.gmp import Gmp from gvmtools.helper import error_and_exit HELP_TEXT = ( "This script will update target hosts information for a desired task.\n" 'The given task needs to have the status "new".\n\n' "Example for starting up the routine:\n" " $ gvm-script --gmp-username name --gmp-password pass " "ssh --hostname scripts/update-task-target.gmp.py +hf hosts_file.csv " "+t 303fa0a6-aa9b-43c4-bac0-66ae0b2d1698" ) def parse_args(args: Namespace) -> Namespace: # pylint: disable=unused-argument """Parsing args ...""" parser = ArgumentParser( prefix_chars="+", add_help=False, formatter_class=RawTextHelpFormatter, description=HELP_TEXT, ) parser.add_argument( "+h", "++help", action="help", help="Show this help message and exit.", ) parser.add_argument( "+t", "++task-id", type=str, required=True, dest="task_id", help="UUID of task to be modified", ) hosts_args = parser.add_mutually_exclusive_group() hosts_args.add_argument( "+hl", "++host-list", nargs="+", type=str, dest="host_list", help="Use the given hosts (IPs) for the new target.", ) hosts_args.add_argument( "+hf", "++host-file", type=FileType("r"), dest="host_file", help=".csv file containing desired target hosts separated by ','", ) script_args, _ = parser.parse_known_args() return script_args def load_host_file(filename) -> List[str]: host_list = list() try: for line in filename.readlines(): host = line.split(",")[0] host = host.strip() if len(host) == 0: continue host_list.append(host) except IOError as e: error_and_exit(f"Failed to read host_file: {str(e)} (exit)") if len(host_list) == 0: error_and_exit("Host file is empty (exit)") return host_list def copy_send_target(gmp, host_list, old_target_id): keywords = {"hosts": host_list} keywords["comment"] = ( "This target was automatically " f'modified: {time.strftime("%Y/%m/%d-%H:%M:%S")}' ) old_target = gmp.get_target(target_id=old_target_id)[0] objects = ("reverse_lookup_only", "reverse_lookup_unify", "name") for obj in objects: var = old_target.xpath(f"{obj}/text()")[0] if var == "0": var = "" keywords[f"{obj}"] = var port_list = {} port_list = old_target.xpath("port_list/@id")[0] keywords["port_list_id"] = port_list keywords["name"] += "_copy" # the name must differ from existing names new_target_id = gmp.create_target(**keywords).xpath("@id")[0] print("\n New target created!\n") print(f"Target_id: {new_target_id}") print(f'Target_name: {keywords["name"]}\n') return new_target_id def create_target_hosts(gmp: Gmp, host_file, task_id, old_target_id): new_target_id = copy_send_target(gmp, host_file, old_target_id) gmp.modify_task(task_id=task_id, target_id=new_target_id) print(" Task successfully modified!\n") def check_to_delete(gmp: Gmp, target_id: str) -> None: target = gmp.get_target(target_id=target_id)[0] if "0" in target.xpath("in_use/text()"): gmp.delete_target(target_id=target_id) def main(gmp: Gmp, args: Namespace) -> None: # pylint: disable=undefined-variable parsed_args = parse_args(args=args) host_list = parsed_args.host_list if parsed_args.host_file: host_list = load_host_file(filename=parsed_args.host_file) task_id = parsed_args.task_id task = gmp.get_task(task_id=task_id)[1] old_target_id = task.xpath("target/@id")[0] if old_target_id: create_target_hosts(gmp, host_list, task_id, old_target_id) check_to_delete(gmp, old_target_id) else: error_and_exit("The given task doesn't have an existing target.") if __name__ == "__gmp__": main(gmp, args) gvm-tools-23.11.0/tests/000077500000000000000000000000001452466721200147605ustar00rootroot00000000000000gvm-tools-23.11.0/tests/__init__.py000066400000000000000000000031471452466721200170760ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2019-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import sys class SuppressOutput: def __init__(self, *, suppress_stdout=False, suppress_stderr=False): self.suppress_stdout = suppress_stdout self.suppress_stderr = suppress_stderr self.original_stdout = None self.original_stderr = None def __enter__(self): self.devnull = open(os.devnull, "w", encoding="utf-8") # Suppress streams if self.suppress_stdout: self.original_stdout = sys.stdout sys.stdout = self.devnull if self.suppress_stderr: self.original_stderr = sys.stderr sys.stderr = self.devnull def __exit__(self, *args, **kwargs): # Restore streams if self.suppress_stdout: sys.stdout = self.original_stdout if self.suppress_stderr: sys.stderr = self.original_stderr self.devnull.close() gvm-tools-23.11.0/tests/root_help.3.10.snap000066400000000000000000000023241452466721200202170ustar00rootroot00000000000000usage: gvm-test-cli [-h] [-c [CONFIG]] [--log [{DEBUG,INFO,WARNING,ERROR,CRITICAL}]] [--timeout TIMEOUT] [--gmp-username GMP_USERNAME] [--gmp-password GMP_PASSWORD] [-V] CONNECTION_TYPE ... options: -h, --help show this help message and exit -c [CONFIG], --config [CONFIG] Configuration file path (default: ~/.config/gvm- tools.conf) --log [{DEBUG,INFO,WARNING,ERROR,CRITICAL}] Activate logging (default level: None) --timeout TIMEOUT Response timeout in seconds, or -1 to wait indefinitely (default: 60) --gmp-username GMP_USERNAME Username for GMP service (default: None) --gmp-password GMP_PASSWORD Password for GMP service (default: None) -V, --version Show version information and exit connections: valid connection types CONNECTION_TYPE Connection type to use ssh Use SSH to connect to service tls Use TLS secured connection to connect to service socket Use UNIX Domain socket to connect to service gvm-tools-23.11.0/tests/root_help.snap000066400000000000000000000023371452466721200176430ustar00rootroot00000000000000usage: gvm-test-cli [-h] [-c [CONFIG]] [--log [{DEBUG,INFO,WARNING,ERROR,CRITICAL}]] [--timeout TIMEOUT] [--gmp-username GMP_USERNAME] [--gmp-password GMP_PASSWORD] [-V] CONNECTION_TYPE ... optional arguments: -h, --help show this help message and exit -c [CONFIG], --config [CONFIG] Configuration file path (default: ~/.config/gvm- tools.conf) --log [{DEBUG,INFO,WARNING,ERROR,CRITICAL}] Activate logging (default level: None) --timeout TIMEOUT Response timeout in seconds, or -1 to wait indefinitely (default: 60) --gmp-username GMP_USERNAME Username for GMP service (default: None) --gmp-password GMP_PASSWORD Password for GMP service (default: None) -V, --version Show version information and exit connections: valid connection types CONNECTION_TYPE Connection type to use ssh Use SSH to connect to service tls Use TLS secured connection to connect to service socket Use UNIX Domain socket to connect to service gvm-tools-23.11.0/tests/scripts/000077500000000000000000000000001452466721200164475ustar00rootroot00000000000000gvm-tools-23.11.0/tests/scripts/__init__.py000066400000000000000000000040601452466721200205600ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2020-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from importlib.util import module_from_spec, spec_from_file_location from pathlib import Path from typing import Union from unittest.mock import MagicMock, create_autospec from gvm.protocols.latest import Gmp from lxml import etree def load_script(path: Union[str, Path], script_name: str): """loading a script for a test case""" spec = spec_from_file_location( script_name, f"{str(path)}/{script_name}.gmp.py" ) script = module_from_spec(spec) spec.loader.exec_module(script) return script class GmpMockFactory: def __init__(self, *args, **kwargs): # pylint: disable=unused-argument gmp_protocol_mock = create_autospec(Gmp) self.gmp_protocol = gmp_protocol_mock self.gmp = MagicMock() self.gmp.is_authenticated = MagicMock(return_value=True) self.gmp_protocol.types = MagicMock() self.gmp.__enter__.return_value = gmp_protocol_mock def __call__(self, *args, **kwargs): return self.gmp def mock_response(self, request_name: str, content: str): func = getattr(self.gmp_protocol, request_name) func.return_value = etree.fromstring(content) def mock_responses(self, request_name: str, content: str): func = getattr(self.gmp_protocol, request_name) func.side_effect = [etree.fromstring(c) for c in content] gvm-tools-23.11.0/tests/scripts/example_schedules.xml000066400000000000000000000167251452466721200226760ustar00rootroot00000000000000 jloechte 2020-08-23T08:51:15Z 2020-09-29T22:12:27Z 1 1 Everything BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Greenbone.net//NONSGML Greenbone Security Manager 20.08.0//EN BEGIN:VEVENT DTSTART:20200826T094100Z DURATION:PT0S RRULE:FREQ=DAILY UID:7ae6ad13-1e5f-4581-ac95-f1c25c59d602 DTSTAMP:20200929T221226Z END:VEVENT END:VCALENDAR UTC jloechte Another Testzone Zone Testing ... 2020-09-27T06:45:25Z 2020-09-27T06:45:25Z 1 0 Everything BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Greenbone.net//NONSGML Greenbone Security Manager 20.08.0//EN BEGIN:VTIMEZONE TZID:/freeassociation.sourceforge.net/Europe/Berlin X-LIC-LOCATION:Europe/Berlin BEGIN:DAYLIGHT TZNAME:CEST DTSTART:19160426T230000 TZOFFSETFROM:+0100 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;UNTIL=19160430T220000Z;BYDAY=-1SU;BYMONTH=4 END:DAYLIGHT BEGIN:STANDARD TZNAME:CET DTSTART:19161004T010000 TZOFFSETFROM:+0200 TZOFFSETTO:+0100 RRULE:FREQ=YEARLY;UNTIL=19160930T230000Z;BYDAY=1SU;BYMONTH=10 END:STANDARD BEGIN:DAYLIGHT TZNAME:CEST DTSTART:19170420T020000 TZOFFSETFROM:+0100 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;UNTIL=19180415T010000Z;BYDAY=3MO;BYMONTH=4 END:DAYLIGHT BEGIN:STANDARD TZNAME:CET DTSTART:19170921T030000 TZOFFSETFROM:+0200 TZOFFSETTO:+0100 RRULE:FREQ=YEARLY;UNTIL=19180916T010000Z;BYDAY=3MO;BYMONTH=9 END:STANDARD BEGIN:DAYLIGHT TZNAME:CEST DTSTART:19400406T020000 TZOFFSETFROM:+0100 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;UNTIL=19400401T010000Z;BYDAY=1MO;BYMONTH=4 END:DAYLIGHT BEGIN:STANDARD TZNAME:CET DTSTART:19421102T030000 TZOFFSETFROM:+0200 TZOFFSETTO:+0100 RRULE:FREQ=YEARLY;UNTIL=19421102T010000Z;BYDAY=1MO;BYMONTH=11 END:STANDARD BEGIN:DAYLIGHT TZNAME:CEST DTSTART:19430330T020000 TZOFFSETFROM:+0100 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;UNTIL=19430329T010000Z;BYDAY=-1MO;BYMONTH=3 END:DAYLIGHT BEGIN:STANDARD TZNAME:CET DTSTART:19431005T030000 TZOFFSETFROM:+0200 TZOFFSETTO:+0100 RRULE:FREQ=YEARLY;UNTIL=19441002T010000Z;BYDAY=1MO;BYMONTH=10 END:STANDARD BEGIN:DAYLIGHT TZNAME:CEST DTSTART:19440406T020000 TZOFFSETFROM:+0100 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;UNTIL=19450402T000000Z;BYDAY=1MO;BYMONTH=4 END:DAYLIGHT BEGIN:DAYLIGHT TZNAME:CEMT DTSTART:19450521T020000 TZOFFSETFROM:+0200 TZOFFSETTO:+0300 RRULE:FREQ=YEARLY;UNTIL=19450523T230000Z;BYDAY=-2TH;BYMONTH=5 END:DAYLIGHT BEGIN:DAYLIGHT TZNAME:CEST DTSTART:19450928T030000 TZOFFSETFROM:+0300 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;UNTIL=19450924T020000Z;BYDAY=-1MO;BYMONTH=9 END:DAYLIGHT BEGIN:STANDARD TZNAME:CET DTSTART:19451115T030000 TZOFFSETFROM:+0200 TZOFFSETTO:+0100 RRULE:FREQ=YEARLY;UNTIL=19451118T020000Z;BYDAY=3SU;BYMONTH=11 END:STANDARD BEGIN:STANDARD TZNAME:CET DTSTART:19460107T000000 TZOFFSETFROM:+0100 TZOFFSETTO:+0100 RRULE:FREQ=YEARLY;UNTIL=19451231T220000Z;BYDAY=1TU;BYMONTH=1 END:STANDARD BEGIN:DAYLIGHT TZNAME:CEST DTSTART:19460412T020000 TZOFFSETFROM:+0100 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;UNTIL=19460414T010000Z;BYDAY=2SU;BYMONTH=4 END:DAYLIGHT BEGIN:STANDARD TZNAME:CET DTSTART:19461005T030000 TZOFFSETFROM:+0200 TZOFFSETTO:+0100 RRULE:FREQ=YEARLY;UNTIL=19461007T010000Z;BYDAY=1MO;BYMONTH=10 END:STANDARD BEGIN:DAYLIGHT TZNAME:CEST DTSTART:19470405T030000 TZOFFSETFROM:+0100 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;UNTIL=19470406T010000Z;BYDAY=1SU;BYMONTH=4 END:DAYLIGHT BEGIN:DAYLIGHT TZNAME:CEMT DTSTART:19470510T030000 TZOFFSETFROM:+0200 TZOFFSETTO:+0300 RRULE:FREQ=YEARLY;UNTIL=19470511T000000Z;BYDAY=2SU;BYMONTH=5 END:DAYLIGHT BEGIN:DAYLIGHT TZNAME:CEST DTSTART:19470628T030000 TZOFFSETFROM:+0300 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;UNTIL=19470629T020000Z;BYDAY=-1SU;BYMONTH=6 END:DAYLIGHT BEGIN:STANDARD TZNAME:CET DTSTART:19471004T030000 TZOFFSETFROM:+0200 TZOFFSETTO:+0100 RRULE:FREQ=YEARLY;UNTIL=19491002T020000Z;BYDAY=1SU;BYMONTH=10 END:STANDARD BEGIN:DAYLIGHT TZNAME:CEST DTSTART:19480419T020000 TZOFFSETFROM:+0100 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;UNTIL=19480418T010000Z;BYDAY=3SU;BYMONTH=4 END:DAYLIGHT BEGIN:DAYLIGHT TZNAME:CEST DTSTART:19490412T020000 TZOFFSETFROM:+0100 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;UNTIL=19490410T010000Z;BYDAY=2SU;BYMONTH=4 END:DAYLIGHT BEGIN:STANDARD TZNAME:CET DTSTART:19800107T000000 TZOFFSETFROM:+0100 TZOFFSETTO:+0100 RRULE:FREQ=YEARLY;UNTIL=19791231T220000Z;BYDAY=1TU;BYMONTH=1 END:STANDARD BEGIN:DAYLIGHT TZNAME:CEST DTSTART:19800405T020000 TZOFFSETFROM:+0100 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;UNTIL=19800406T010000Z;BYDAY=1SU;BYMONTH=4 END:DAYLIGHT BEGIN:STANDARD TZNAME:CET DTSTART:19800927T030000 TZOFFSETFROM:+0200 TZOFFSETTO:+0100 RRULE:FREQ=YEARLY;UNTIL=19950924T010000Z;BYDAY=-1SU;BYMONTH=9 END:STANDARD BEGIN:DAYLIGHT TZNAME:CEST DTSTART:19810329T020000 TZOFFSETFROM:+0100 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 END:DAYLIGHT BEGIN:STANDARD TZNAME:CET DTSTART:19961025T030000 TZOFFSETFROM:+0200 TZOFFSETTO:+0100 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTART;TZID=/freeassociation.sourceforge.net/Europe/Berlin: 20200927T090000 DURATION:PT0S RRULE:FREQ=DAILY UID:5aae8a71-b0e8-43b2-91da-01961dcf9c4f DTSTAMP:20200927T084524Z END:VEVENT END:VCALENDAR Europe/Berlin rows=2 first=1 sort=name rows = 2 first = 1 sort = name nameascending 33 2 gvm-tools-23.11.0/tests/scripts/example_target.xml000066400000000000000000000233741452466721200222030ustar00rootroot00000000000000 jloechte Unnamed 2020-09-30T05:16:36Z 2020-09-30T10:33:21Z 1 0 Everything 123.0.1.123 1 All IANA assigned TCP 0 Unnamed 22 0 0 0 0 0 0 Scan Config Default T:1-3,5,7,9,11,13,17-25,27,29,31,33,35,37-39,41-46,48-50,52-59,62-80,82-99,101-113,115-224,242-248,256-257,259-269,271,280-284,286-287,308-324,333,344-584,586-658,660-702,704-707,709-715,729-731,741-742,744,747-754,758-765,767,769-777,780,800-802,810,828-833,847-848,853-854,860-862,873,886-888,900-903,910-913,953,989-993,995-1001,1010,1021-1022,1025-1026,1029,1033-1108,1110-1275,1277-1300,1303-1490,1492-1527,1529-1782,1784-2193,2197-2199,2201-2258,2260-2368,2370-2377,2379-2681,2683-2692,2694-2793,2795-2824,2826-2872,2874-2924,2926-3091,3093-3096,3098-3125,3127-3300,3302-3321,3326-3366,3372-3402,3405-3545,3547-3693,3695-3993,3995-4047,4049-4076,4078-4083,4085,4087-4143,4145-4172,4174-4190,4192-4193,4197,4199,4300-4314,4316,4320-4323,4325-4336,4340,4343-4360,4368-4379,4389-4393,4395-4396,4400-4411,4413-4417,4419-4423,4425-4433,4442-4458,4484-4488,4500,4535-4538,4545-4556,4559,4563,4566-4570,4573,4590-4591,4593-4605,4646,4658-4692,4700-4704,4711,4725,4727-4728,4730-4731,4733,4737-4745,4749-4753,4756,4774,4784,4786-4788,4800-4803,4827,4837-4851,4867-4871,4876-4877,4879-4880,4883-4885,4888-4889,4894,4899-4902,4912-4915,4940-4942,4949-4953,4969-4971,4984-4991,4999-5013,5015,5020-5030,5032-5034,5042-5045,5048-5057,5059-5075,5080-5087,5093-5094,5099-5103,5106-5107,5111-5112,5114-5115,5117,5120,5133-5135,5137,5145-5146,5150-5152,5154-5157,5161-5168,5172,5190-5197,5200-5203,5209,5215,5221-5237,5245,5248-5254,5264-5265,5269-5272,5280-5282,5298-5310,5312-5318,5320-5321,5343-5344,5349,5352-5363,5397-5435,5443,5445,5450,5453-5456,5461-5465,5470-5473,5475,5500-5507,5550,5553-5557,5565-5569,5573-5575,5579-5586,5597-5605,5618,5627-5639,5646,5666,5670-5681,5683-5684,5688-5689,5693,5696,5700,5705,5713-5730,5741-5748,5750,5755,5757,5766-5771,5777,5780-5783,5785,5793,5813-5814,5841-5842,5859,5863,5868,5883,5900,5910-5913,5963,5968-5969,5984-5993,5999,6064-6066,6068-6077,6084-6088,6099-6117,6121-6124,6130,6133,6140-6149,6159-6163,6200,6209,6222,6241-6244,6251-6253,6267-6269,6300-6301,6306,6315-6317,6320-6322,6324-6326,6343-6344,6346-6347,6350,6355,6360,6370,6379,6382,6389-6390,6417-6421,6432,6440,6442-6446,6455-6456,6464,6471,6480-6489,6500-6503,6505-6510,6513-6515,6543-6544,6547-6551,6558,6566,6568,6579-6583,6600-6602,6619-6629,6632,6640,6653,6655-6656,6670-6673,6678-6679,6687-6690,6697,6701-6703,6714-6716,6767-6771,6777-6778,6785-6791,6801,6817,6831,6841-6842,6850,6868,6888,6900-6901,6924,6935-6936,6946,6951,6961-6966,6969-6970,6997-7026,7030-7031,7070-7073,7080,7099-7101,7117,7121,7128-7129,7161-7174,7200-7202,7215-7216,7227-7229,7236-7237,7244,7262,7272-7283,7365,7391-7395,7397,7400-7402,7410-7411,7421,7426-7431,7437,7443,7471,7473-7474,7478,7491,7500-7501,7508-7511,7542-7549,7551,7560,7563,7566,7569-7570,7574,7588,7606,7624,7626-7631,7633,7648,7663,7672-7677,7680,7683,7687,7689,7697,7700,7707-7708,7720,7724-7728,7734,7738,7741-7744,7747,7775,7777-7779,7781,7786-7787,7789,7794,7797-7801,7810,7845-7847,7869-7871,7878,7880,7887,7900-7903,7913,7932-7933,7962,7967,7979-7982,7997,7999-8009,8015-8016,8019-8023,8025-8026,8032-8034,8040-8044,8051-8059,8066-8067,8070,8074,8077,8080-8084,8086-8088,8090-8091,8097,8100-8102,8115-8118,8121-8122,8128-8132,8140,8148,8153,8160-8162,8181-8184,8190-8192,8194-8195,8199-8201,8204-8208,8230,8243,8270,8276,8280,8282,8292-8294,8300-8301,8313,8320-8322,8351,8376-8380,8383,8400-8405,8415-8417,8423,8442-8445,8450,8457,8470-8474,8500-8502,8554-8555,8567,8600,8610-8615,8665-8666,8675,8686,8688,8699,8710-8711,8733,8750,8763-8770,8778,8786-8787,8793,8800,8804,8873,8880-8881,8883,8888-8894,8899-8901,8910-8913,8937,8953-8954,8980,8989-8991,8997-9002,9005,9008-9010,9020-9026,9050-9051,9060,9080,9083-9093,9100-9107,9111,9119,9122-9123,9131,9160-9164,9191,9200-9217,9222,9255,9278-9285,9287,9292-9295,9300,9306,9310,9312,9318,9321,9339,9343-9346,9374,9380,9387-9390,9396-9397,9400-9402,9418,9443-9445,9450,9500,9535-9536,9555,9592-9600,9612,9614,9616-9618,9628-9631,9640,9666-9668,9694-9695,9700,9747,9750,9753,9762,9800-9802,9875-9877,9888-9889,9898,9900,9909,9911,9925,9950-9955,9966,9978-9979,9981,9987-9988,9990-10010,10020,10050-10051,10055,10080-10081,10100-10104,10107,10110,10113-10117,10125,10128-10129,10160-10162,10200-10201,10252,10260-10261,10288,10321,10443,10540-10544,10548,10631,10800,10805,10809,10860,10880,10933,10990,11000-11001,11095,11103-11106,11109-11112,11161-11165,11172-11175,11201-11202,11208,11211,11319-11321,11367,11371,11489,11600,11623,11720,11723,11751,11796,11876,11967,11971,12000-12008,12010,12012-12013,12109,12121,12168,12172,12300,12302,12321-12322,12345,12753,12865,13160,13216-13218,13223-13224,13400,13720-13722,13724,13782-13783,13785-13786,13818-13823,13894,13929-13930,14000-14001,14033-14034,14141-14143,14145,14149-14150,14154,14250,14414,14500,14936-14937,15000,15002,15345,15363,15555,15660,15740,15999-16002,16020-16021,16161-16162,16309-16311,16360-16361,16367-16368,16384-16385,16619,16665,16789,16900,16950,16991-16995,17007,17184-17185,17219-17221,17223,17225,17234-17235,17500,17555,17729,17754-17756,17777,18000,18104,18136,18181-18187,18241-18243,18262,18463,18634-18635,18668,18769,18881,18888,19000,19007,19020,19191,19194,19220,19283,19315,19398,19410-19412,19539-19541,19998-20003,20005,20013-20014,20034,20046,20048-20049,20057,20167,20202,20222,20480,20670,20999-21000,21010,21212-21213,21221,21553-21554,21590,21800,21845-21849,22000-22005,22125,22128,22222,22273,22305,22335,22343,22347,22350-22351,22537,22555,22763,22800,22951,23000-23005,23053,23294,23333,23400-23402,23456-23457,23546,24000-24006,24242,24249,24321,24323,24386,24465,24554,24577,24666,24676-24678,24680,24754,24922,25000-25009,25576,25604,25793,25900-25903,26000,26133,26208,26257,26260-26263,26486-26487,26489,27010,27017,27345,27442,27504,27782,27876,27999-28001,28010,28200,28240,28589,29167,29999-30003,30100,30260,30400,30999,31016,31020,31400,31416,31457,31620,31685,31765,31948-31949,32034,32249,32400,32483,32635-32636,32767-32777,32801,32811,32896,33000,33060,33123,33331,33333-33334,33434,33656,34249,34378-34379,34567,34962-34964,34980,35000-35006,35100,35354-35357,36001,36524,36602,36700,36865,37475,37483,37601,37654,38000-38002,38201-38203,38800,38865,39681,40000,40404,40841-40843,41111,41121,41230,41794-41797,42508-42510,43000,43188-43191,43210,43439-43441,44123,44321-44323,44444-44445,44553,44818,44900,45000-45002,45045,45054,45514,45678,45824-45825,45966,46336,46998-47001,47557,47624,47806,47808,48000-48005,48048-48050,48128-48129,48556,48619,48653,49000-49001,49150 first=1 rows=-1 uuid= uuid=f362f2cb-c218-4e8b-8ae8-dc818edcb0b8 sort=name first = 1 rows = -1 uuid = uuid = f362f2cb-c218-4e8b-8ae8-dc818edcb0b8 sort = name nameascending 51 1 gvm-tools-23.11.0/tests/scripts/example_task.xml000066400000000000000000000133061452466721200216510ustar00rootroot00000000000000 0 jloechte sgdfg 2020-11-02T10:18:51Z 2020-11-02T10:18:58Z 1 1 Everything 0 scan -1 0 Target for waitress 0 CVE 3 0 Running 0 10 0 1 2020-11-02T10:18:58Z 2020-11-02T10:18:58Z 0 0 Maximum concurrently executed NVTs per host max_checks 4 Maximum concurrently scanned hosts max_hosts 20 Network Source Interface source_iface Add results to Asset Management in_assets no Apply Overrides when adding Assets assets_apply_overrides yes Min QOD when adding Assets assets_min_qod 70 Auto Delete Reports auto_delete no Auto Delete Reports Data auto_delete_data 5 apply_overrides=0 min_qod=70 first=1 rows=-1 uuid= uuid=c8ef0597-e2c1-4e23-869f-072fa2914bf2 sort=name apply_overrides = 0 min_qod = 70 first = 1 rows = -1 uuid = uuid = c8ef0597-e2c1-4e23-869f-072fa2914bf2 sort = name nameascending 101 1 gvm-tools-23.11.0/tests/scripts/get_alerts.xml000066400000000000000000000054651452466721200213340ustar00rootroot00000000000000 jloechte test_alert 2021-01-03T13:45:20Z 2021-01-03T13:45:20Z 1 0 Everything Always Task run status changedstatusDone EmailTask '$n': $e After the event $e, the following condition was met: $c This email escalation is configured to attach report format '$r'. Full details and other report formats are available on the scan engine. $t Note: This email was sent to you as a configured security scan escalation. Please contact your local system administrator if you think you should not have received it. message2noticesender@test.comfrom_address[OpenVAS-Manager] Tasksubjectc402cc3e-b531-11e1-9163-406186ea4fc5notice_attach_formatrecipient@test.comto_address 1 name=test_alert first=1 rows=100 sort=name name = test_alert first = 1 rows = 100 sort = name nameascending 1311 gvm-tools-23.11.0/tests/scripts/get_scan_configs.xml000066400000000000000000000103451452466721200224670ustar00rootroot00000000000000 Base Basic configuration template with a minimum set of NVTs required for a scan. Version 20200827. 2020-02-26T23:52:51Z 2020-12-02T04:09:50Z Discovery Network Discovery scan configuration. Version 20200827. 2019-09-05T12:25:40Z 2020-12-02T04:09:55Z empty Empty and static configuration template. Version 20200827. 2019-09-05T12:25:40Z 2020-12-02T04:09:56Z EulerOS Linux Security Configuration Check compliance status of EulerOS installation against above named Policy as distributed by Huawei. Version 20200910. 2020-09-28T09:50:58Z 2020-12-02T04:09:59Z Full and fast Most NVT's; optimized by using previously collected information. Version 20200827. 2019-09-05T12:25:40Z 2020-12-02T04:09:55Z Full and fast ultimate Most NVT's including those that can stop services/hosts; optimized by using previously collected information. Version 20200827. 2019-09-05T12:25:40Z 2020-12-02T04:09:50Z Full and very deep Most NVT's; don't trust previously collected information; slow. Version 20200827. 2019-09-05T12:25:40Z 2020-12-02T04:09:56Z Full and very deep ultimate Most NVT's including those that can stop services/hosts; don't trust previously collected information; slow. Version 20200827. 2019-09-05T12:25:40Z 2020-12-02T04:09:56Z Host Discovery Network Host Discovery scan configuration. Version 20200827. 2019-09-05T12:25:40Z 2020-12-02T04:09:56Z System Discovery Network System Discovery scan configuration. Version 20200827. 2019-09-05T12:25:40Z 2020-12-02T04:09:55Z gvm-tools-23.11.0/tests/scripts/invalid_xml.xml000066400000000000000000000016311452466721200215000ustar00rootroot00000000000000 jloechte gvm-tools-23.11.0/tests/scripts/test_combine_reports.py000066400000000000000000000163631452466721200232630ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2021-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import unittest from argparse import Namespace from pathlib import Path from unittest.mock import patch from lxml import etree from . import GmpMockFactory, load_script CWD = Path(__file__).absolute().parent class CombineReportsTestCase(unittest.TestCase): def setUp(self): self.combine_reports = load_script( (CWD.parent.parent / "scripts"), "combine-reports" ) @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_combine_reports(self, mock_gmp: GmpMockFactory): # bah! mock_gmp.mock_responses( "get_report", [ '' '' "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" '' "Offline Scan from 2020-11-13T15:47:28+01:00 8" "" '' "Done" "2020-11-13T14:47:48Z" "2020-11-13T14:47:28Z" '' '' "" '' "" "" "2020-11-13T14:47:28Z" "" "" "", '' '' "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" '' "Offline Scan from 2020-11-13T15:47:28+01:00 8" "" '' "Done" "2020-11-13T14:47:48Z" "2020-11-13T14:47:28Z" '' '' "" '' "" "" "2020-11-13T14:47:28Z" "" "" "", '' '' "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" '' "Offline Scan from 2020-11-13T15:47:28+01:00 8" "" '' "Done" "2020-11-13T14:47:48Z" "2020-11-13T14:47:28Z" '' '' "" "" "2020-11-13T14:47:28Z" "" "" "", ], ) args = Namespace( script=[ "foo", "00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002", ] ) combined_report = self.combine_reports.combine_reports( gmp=mock_gmp.gmp_protocol, args=args ) results = ( combined_report.find("report").find("results").findall("result") ) i = 0 for result in results: self.assertEqual( result.get("id"), f"00000001-0000-0000-0000-00000000000{str(i)}" ) i += 1 self.assertEqual(i, 5) @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_send_report(self, mock_gmp: GmpMockFactory): args = Namespace( script=[ "foo", "00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002", ] ) combined_report = etree.fromstring( '' '' '' '' '' '' '' '' "" ) report_id = "0e4d8fb2-47fa-494e-a242-d5327d3772f9" mock_gmp.mock_response( "import_report", '', ) mock_gmp.mock_response( "create_container_task", '', ) created_report_id = self.combine_reports.send_report( gmp=mock_gmp.gmp_protocol, args=args, combined_report=combined_report, ) self.assertEqual(report_id, created_report_id) gvm-tools-23.11.0/tests/scripts/test_create_consolidated_report.py000066400000000000000000000373001452466721200254510ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2021-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import unittest from datetime import date from pathlib import Path from unittest.mock import patch from lxml import etree from . import GmpMockFactory, load_script CWD = Path(__file__).absolute().parent class CreateConsolidatedReportsTestCase(unittest.TestCase): def setUp(self): self.create_consolidated_report = load_script( (CWD.parent.parent / "scripts"), "create-consolidated-report" ) def test_parse_period(self): with self.assertRaises(SystemExit), self.assertRaises(ValueError): self.create_consolidated_report.parse_period( ["a/b/c", "2020/02/03"] ) with self.assertRaises(SystemExit), self.assertRaises(ValueError): self.create_consolidated_report.parse_period( ["1/2/3/4", "2020/02/03"] ) with self.assertRaises(SystemExit), self.assertRaises(ValueError): self.create_consolidated_report.parse_period( ["2020/02/03", "a/b/c"] ) with self.assertRaises(SystemExit), self.assertRaises(ValueError): self.create_consolidated_report.parse_period( ["2020/02/03", "1/2/3/4"] ) with self.assertRaises(SystemExit), self.assertRaises(ValueError): self.create_consolidated_report.parse_period( ["2020/20/03", "2001/2/3"] ) with self.assertRaises(SystemExit), self.assertRaises(ValueError): self.create_consolidated_report.parse_period( ["2020/12/03", "2001/2/300"] ) with self.assertRaises(SystemExit), self.assertRaises(ValueError): self.create_consolidated_report.parse_period( ["2020/12/03", "2001/22/30"] ) with self.assertRaises(SystemExit), self.assertRaises(ValueError): self.create_consolidated_report.parse_period( ["2020/12/43", "2001/2/3"] ) date1, date2 = self.create_consolidated_report.parse_period( ["1980/1/11", "2001/2/3"] ) self.assertEqual(date1, date(1980, 1, 11)) self.assertEqual(date2, date(2001, 2, 3)) def test_parse_tags(self): tags = ["abc", "dba8624e-a56c-4901-a3f2-591f062e4c20"] filter_tags = self.create_consolidated_report.parse_tags(tags) self.assertEqual(filter_tags[0], 'tag="abc"') self.assertEqual( filter_tags[1], 'tag_id="dba8624e-a56c-4901-a3f2-591f062e4c20"' ) def test_generate_task_filter(self): asserted_task_filter = ( 'rows=-1 last>2020-01-01 and created<2020-02-01 and tag="blah"' ) period_start = date(2020, 1, 1) period_end = date(2020, 2, 1) tags = ['tag="blah"'] task_filter = self.create_consolidated_report.generate_task_filter( period_start, period_end, tags ) self.assertEqual(task_filter, asserted_task_filter) asserted_task_filter = ( "rows=-1 last>2020-01-01 and created<2020-02-01 and " 'tag="blah" or last>2020-01-01 and created<2020-02-01' ' and tag="blah2"' ) tags = ['tag="blah"', 'tag="blah2"'] task_filter = self.create_consolidated_report.generate_task_filter( period_start, period_end, tags ) self.assertEqual(task_filter, asserted_task_filter) @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_combine_reports_with_term(self, mock_gmp: GmpMockFactory): reports = [ "00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002", ] mock_gmp.mock_responses( "get_report", [ '' '' "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" '' "Offline Scan from 2020-11-13T15:47:28+01:00 8" "" '' "Done" "2020-11-13T14:47:48Z" "2020-11-13T14:47:28Z" '' "0127.0.0.0" "1127.0.0.1" '' '' "" '' "" "2020-11-13T14:47:28Z" "", '' '' "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" '' "Offline Scan from 2020-11-13T15:47:28+01:00 8" "" '' "Done" "2020-11-13T14:47:48Z" "2020-11-13T14:47:28Z" '' "2127.0.0.2" "3127.0.0.3" '' '' '' "" "2020-11-13T14:47:28Z" "127.0.0.0" "", '' '' "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" '' "Offline Scan from 2020-11-13T15:47:28+01:00 8" "" '' "Done" "2020-11-13T14:47:48Z" "2020-11-13T14:47:28Z" '' '' "" "2020-11-13T14:47:28Z" "127.0.0.1" "", ], ) combined_report = self.create_consolidated_report.combine_reports( mock_gmp.gmp_protocol, reports, filter_term="foo", filter_id=None ) ports = combined_report.find("report").find("ports").findall("port") i = 0 for port in ports: self.assertEqual(port.text, f"{str(i)}") i += 1 self.assertEqual(i, 4) results = ( combined_report.find("report").find("results").findall("result") ) i = 0 for result in results: self.assertEqual( result.get("id"), f"00000001-0000-0000-0000-00000000000{str(i)}" ) i += 1 self.assertEqual(i, 5) hosts = combined_report.find("report").findall("host") i = 0 for host in hosts: self.assertEqual(host.find("ip").text, f"127.0.0.{str(i)}") i += 1 self.assertEqual(i, 2) @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_combine_reports_with_id(self, mock_gmp: GmpMockFactory): reports = [ "00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002", ] mock_gmp.mock_responses( "get_report", [ '' '' "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" '' "Offline Scan from 2020-11-13T15:47:28+01:00 8" "" '' "Done" "2020-11-13T14:47:48Z" "2020-11-13T14:47:28Z" '' "0127.0.0.0" "1127.0.0.1" '' '' "" '' "" "2020-11-13T14:47:28Z" "", '' '' "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" '' "Offline Scan from 2020-11-13T15:47:28+01:00 8" "" '' "Done" "2020-11-13T14:47:48Z" "2020-11-13T14:47:28Z" '' "2127.0.0.2" "3127.0.0.3" '' '' '' "" "2020-11-13T14:47:28Z" "127.0.0.0" "", '' '' "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" "2020-11-13T14:47:28Z" '' "Offline Scan from 2020-11-13T15:47:28+01:00 8" "" '' "Done" "2020-11-13T14:47:48Z" "2020-11-13T14:47:28Z" '' '' "" "2020-11-13T14:47:28Z" "127.0.0.1" "", ], ) combined_report = self.create_consolidated_report.combine_reports( mock_gmp.gmp_protocol, reports, filter_term="", filter_id="123" ) ports = combined_report.find("report").find("ports").findall("port") i = 0 for port in ports: self.assertEqual(port.text, f"{str(i)}") i += 1 self.assertEqual(i, 4) results = ( combined_report.find("report").find("results").findall("result") ) i = 0 for result in results: self.assertEqual( result.get("id"), f"00000001-0000-0000-0000-00000000000{str(i)}" ) i += 1 self.assertEqual(i, 5) hosts = combined_report.find("report").findall("host") i = 0 for host in hosts: self.assertEqual(host.find("ip").text, f"127.0.0.{str(i)}") i += 1 self.assertEqual(i, 2) @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_send_report(self, mock_gmp: GmpMockFactory): combined_report = etree.fromstring( '' '' '' '' '' '' '' '' "" ) report_id = "0e4d8fb2-47fa-494e-a242-d5327d3772f9" mock_gmp.mock_response( "import_report", '', ) mock_gmp.mock_response( "create_container_task", '', ) period_start = date(2020, 1, 1) period_end = date(2020, 2, 1) created_report_id = self.create_consolidated_report.send_report( gmp=mock_gmp.gmp_protocol, combined_report=combined_report, period_start=period_start, period_end=period_end, ) self.assertEqual(report_id, created_report_id) gvm-tools-23.11.0/tests/scripts/test_send_schedules.py000066400000000000000000000045261452466721200230570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2020-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see import unittest from argparse import Namespace from pathlib import Path from unittest.mock import patch from lxml import etree from . import GmpMockFactory, load_script CWD = Path(__file__).absolute().parent class SendSchedulesTestCase(unittest.TestCase): def setUp(self): self.send_schedules = load_script( (CWD.parent.parent / "scripts"), "send-schedules" ) @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_sent_schedule(self, mock_gmp: GmpMockFactory): schedule_xml_path = CWD / "example_schedules.xml" schedule_xml_str = schedule_xml_path.read_text(encoding="utf-8") mock_gmp.mock_responses( "create_schedule", [ '', '', ], ) schedule = etree.XML(schedule_xml_str) self.send_schedules.parse_send_xml_tree(mock_gmp.gmp_protocol, schedule) @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_args(self, mock_gmp: GmpMockFactory): args = Namespace(script=["foo"]) with self.assertRaises(SystemExit): self.send_schedules.check_args(gmp=mock_gmp, args=args) args2 = Namespace(script=["foo", "bar", "baz"]) with self.assertRaises(SystemExit): self.send_schedules.check_args(gmp=mock_gmp, args=args2) gvm-tools-23.11.0/tests/scripts/test_send_targets.py000066400000000000000000000071471452466721200225530ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2020-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import unittest from argparse import Namespace from pathlib import Path from unittest.mock import patch from lxml import etree from . import GmpMockFactory, load_script CWD = Path(__file__).absolute().parent class SendTargetTestCase(unittest.TestCase): def setUp(self): self.send_targets = load_script( (CWD.parent.parent / "scripts"), "send-targets" ) @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_sent_target(self, mock_gmp: GmpMockFactory): target_xml_path = CWD / "example_target.xml" target_xml_str = target_xml_path.read_text(encoding="utf-8") mock_gmp.mock_response( "get_credentials", '' '' "" '' "" '' "" '' "" "", ) mock_gmp.mock_response( "create_target", '', ) target = etree.XML(target_xml_str) self.send_targets.parse_send_xml_tree(mock_gmp.gmp_protocol, target) @patch("builtins.input", lambda *args: "n") @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_sent_target_no_credential(self, mock_gmp: GmpMockFactory): target_xml_path = CWD / "example_target.xml" target_xml_str = target_xml_path.read_text(encoding="utf-8") mock_gmp.mock_response( "get_credentials", '' '' "" '' "" "", ) mock_gmp.mock_response( "create_target", '', ) target = etree.XML(target_xml_str) with self.assertRaises(SystemExit): self.send_targets.parse_send_xml_tree(mock_gmp.gmp_protocol, target) def test_args(self): args = Namespace(script=["foo"]) with self.assertRaises(SystemExit): self.send_targets.check_args(args) args2 = Namespace(script=["foo", "bar", "baz"]) with self.assertRaises(SystemExit): self.send_targets.check_args(args2) gvm-tools-23.11.0/tests/scripts/test_send_tasks.py000066400000000000000000000065051452466721200222240ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2020-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see import unittest from pathlib import Path from unittest.mock import MagicMock, patch from lxml import etree from . import GmpMockFactory, load_script CWD = Path(__file__).absolute().parent class SendTasksTestCase(unittest.TestCase): def setUp(self): self.send_tasks = load_script( (CWD.parent.parent / "scripts"), "send-tasks" ) @patch("builtins.input", lambda *args: "y") @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_sent_task(self, mock_gmp: GmpMockFactory): task_xml_path = CWD / "example_task.xml" task_xml_str = task_xml_path.read_text(encoding="utf-8") self.send_tasks.numerical_option = MagicMock(return_value=1) configs_file = CWD / "get_scan_configs.xml" configs = configs_file.read_text(encoding="utf-8") mock_gmp.mock_response("get_scan_configs", configs) mock_gmp.mock_response( "get_scanners", '' '' "as" "" '' "CVE" "" '' "OpenVAS Default" "" "", ) mock_gmp.mock_response( "get_targets", '' '' "own" "" '' "Target for xn" "" '' "Unnamed" "" '' "work" "" '' "work2" "" "", ) mock_gmp.mock_response( "create_task", '', ) task = etree.XML(task_xml_str) tasks = self.send_tasks.parse_send_xml_tree(mock_gmp.gmp_protocol, task) self.assertEqual(tasks, ["c8ef0597-e2c1-4e23-869f-072fa2914bf2"]) gvm-tools-23.11.0/tests/scripts/test_start_alert_scan.py000066400000000000000000000156671452466721200234270ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2020-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see import unittest from pathlib import Path from unittest.mock import patch from . import GmpMockFactory, load_script CWD = Path(__file__).absolute().parent class StartAlertScanTestCase(unittest.TestCase): def setUp(self): self.start_alert_scan = load_script( (CWD.parent.parent / "scripts"), "start-alert-scan" ) @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_get_scan_config(self, mock_gmp: GmpMockFactory): configs_file = CWD / "get_scan_configs.xml" configs = configs_file.read_text(encoding="utf-8") mock_gmp.mock_response("get_scan_configs", configs) # Full and Fast config_id = self.start_alert_scan.get_scan_config( gmp=mock_gmp.gmp_protocol, config=0 ) self.assertEqual(config_id, "daba56c8-73ec-11df-a475-002264764cea") # Full and Fast ultimate config_id = self.start_alert_scan.get_scan_config( gmp=mock_gmp.gmp_protocol, config=1 ) self.assertEqual(config_id, "698f691e-7489-11df-9d8c-002264764cea") # Full and Fast deep config_id = self.start_alert_scan.get_scan_config( gmp=mock_gmp.gmp_protocol, config=2 ) self.assertEqual(config_id, "708f25c4-7489-11df-8094-002264764cea") # Full and Fast deep ultimate config_id = self.start_alert_scan.get_scan_config( gmp=mock_gmp.gmp_protocol, config=3 ) self.assertEqual(config_id, "74db13d6-7489-11df-91b9-002264764cea") # System Discovery config_id = self.start_alert_scan.get_scan_config( gmp=mock_gmp.gmp_protocol, config=4 ) self.assertEqual(config_id, "bbca7412-a950-11e3-9109-406186ea4fc5") with self.assertRaises(ValueError): config_id = self.start_alert_scan.get_scan_config( gmp=mock_gmp.gmp_protocol, config=-1 ) @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_get_alert(self, mock_gmp: GmpMockFactory): sender_email = "sender@test.com" recipient_email = "recipient@test.com" alert_name = "test_alert" alert_id = "3eefd4b9-59ec-48d6-b84d-f6a73bdb909f" alerts_file = CWD / "get_alerts.xml" alerts = alerts_file.read_text(encoding="utf-8") mock_gmp.mock_response("get_alerts", alerts) mock_gmp.mock_response( "create_alert", '', ) returned_id = self.start_alert_scan.get_alert( gmp=mock_gmp.gmp_protocol, alert_name=alert_name, recipient_email=recipient_email, sender_email=sender_email, ) self.assertEqual(alert_id, returned_id) @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_get_target(self, mock_gmp: GmpMockFactory): target_name = "test_target" hosts = ["127.0.0.1", "8.8.8.8"] ports = "T:1-3,5,7,9,11,13,17-25" port_list_name = "test_port_list" port_list_id = "6742e61a-a7b0-45dd-a8e1-35751c970958" target_id = "3b76a0c2-14fc-4de2-868c-35132977a25e" mock_gmp.mock_response( "create_port_list", '', ) mock_gmp.mock_response( "create_target", '', ) returned_id = self.start_alert_scan.get_target( gmp=mock_gmp.gmp_protocol, target_name=target_name, hosts=hosts, ports=ports, port_list_name=port_list_name, ) self.assertEqual(target_id, returned_id) @patch("gvm.protocols.latest.Gmp", new_callable=GmpMockFactory) def test_create_and_start_task(self, mock_gmp: GmpMockFactory): alert_name = "test_alert" alert_id = "3eefd4b9-59ec-48d6-b84d-f6a73bdb909f" target_id = "3b76a0c2-14fc-4de2-868c-35132977a25e" config_id = "daba56c8-73ec-11df-a475-002264764cea" scanner_id = "08b69003-5fc2-4037-a479-93b440211c73" task_id = "d78453ab-d907-44b6-abe0-2ef54a77f1c2" mock_gmp.mock_response( "create_task", '', ) mock_gmp.mock_response( "get_tasks", """ 0 apply_overrides=0 min_qod=70 name="Alert Scan for Alert test_alert" first=1 rows=100 sort=name apply_overrides = 0 min_qod = 70 name = "Alert Scan for Alert test_alert" first = 1 rows = 100 sort = name nameascending 2700 """, ) task_name = f"Alert Scan for Alert {alert_name}" returned_name = self.start_alert_scan.create_and_start_task( gmp=mock_gmp.gmp_protocol, config_id=config_id, target_id=target_id, scanner_id=scanner_id, alert_id=alert_id, alert_name=alert_name, ) self.assertEqual(task_name, returned_name) gvm-tools-23.11.0/tests/socket_help.3.10.snap000066400000000000000000000005511452466721200205240ustar00rootroot00000000000000usage: gvm-test-cli socket [-h] [--sockpath [SOCKETPATH] | --socketpath [SOCKETPATH]] options: -h, --help show this help message and exit --sockpath [SOCKETPATH] Deprecated, use --socketpath instead --socketpath [SOCKETPATH] Path to UNIX Domain socket (default: None) gvm-tools-23.11.0/tests/socket_help.snap000066400000000000000000000005641452466721200201500ustar00rootroot00000000000000usage: gvm-test-cli socket [-h] [--sockpath [SOCKETPATH] | --socketpath [SOCKETPATH]] optional arguments: -h, --help show this help message and exit --sockpath [SOCKETPATH] Deprecated, use --socketpath instead --socketpath [SOCKETPATH] Path to UNIX Domain socket (default: None) gvm-tools-23.11.0/tests/ssh_help.3.10.snap000066400000000000000000000012201452466721200200230ustar00rootroot00000000000000usage: gvm-test-cli ssh [-h] [--hostname HOSTNAME] [--port PORT] [--ssh-username SSH_USERNAME] [--ssh-password SSH_PASSWORD] [-A] options: -h, --help show this help message and exit --hostname HOSTNAME Hostname or IP address (default: 127.0.0.1) --port PORT SSH port (default: 22) --ssh-username SSH_USERNAME SSH username (default: 'gmp') --ssh-password SSH_PASSWORD SSH password (default: 'gmp') -A, --auto-accept-host When executed in e.g. CI, auto accept SSH host addition gvm-tools-23.11.0/tests/ssh_help.snap000066400000000000000000000012331452466721200174470ustar00rootroot00000000000000usage: gvm-test-cli ssh [-h] [--hostname HOSTNAME] [--port PORT] [--ssh-username SSH_USERNAME] [--ssh-password SSH_PASSWORD] [-A] optional arguments: -h, --help show this help message and exit --hostname HOSTNAME Hostname or IP address (default: 127.0.0.1) --port PORT SSH port (default: 22) --ssh-username SSH_USERNAME SSH username (default: 'gmp') --ssh-password SSH_PASSWORD SSH password (default: 'gmp') -A, --auto-accept-host When executed in e.g. CI, auto accept SSH host addition gvm-tools-23.11.0/tests/test.cfg000066400000000000000000000003261452466721200164210ustar00rootroot00000000000000[main] foo=bar timeout=1000 username=ipsum [gmp] username=%(foo)s password=bar [unixsocket] socketpath=/foo/bar.sock [ssh] password=lorem port=123 [tls] port=123 certfile=foo.cert keyfile=foo.key cafile=foo.ca gvm-tools-23.11.0/tests/test_auth.cfg000066400000000000000000000000511452466721200174350ustar00rootroot00000000000000[Auth] gmp_username=foo gmp_password=bar gvm-tools-23.11.0/tests/test_config.py000066400000000000000000000065661452466721200176530ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2019-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import logging import unittest from pathlib import Path from gvm.connections import DEFAULT_GVM_PORT, DEFAULT_UNIX_SOCKET_PATH from gvmtools.config import DEFAULT_SSH_PORT, Config from gvmtools.config import __name__ as name __here__ = Path(__file__).parent.resolve() class ConfigTestCase(unittest.TestCase): def test_config_defaults(self): config = Config() self.assertEqual(config.get("gmp", "username"), "") self.assertEqual(config.get("gmp", "password"), "") self.assertEqual(config.get("ssh", "username"), "gmp") self.assertEqual(config.get("ssh", "password"), "gmp") self.assertEqual(config.get("ssh", "port"), DEFAULT_SSH_PORT) self.assertEqual( config.get("unixsocket", "socketpath"), DEFAULT_UNIX_SOCKET_PATH ) self.assertEqual(config.get("tls", "port"), DEFAULT_GVM_PORT) def test_get_unknown_setting(self): config = Config() self.assertIsNone(config.get("foo", "bar")) def test_load(self): test_config_path = __here__ / "test.cfg" self.assertTrue(test_config_path.is_file()) config = Config() config.load(test_config_path) self.assertEqual(config.get("gmp", "username"), "bar") self.assertEqual(config.get("gmp", "password"), "bar") self.assertEqual(config.get("ssh", "username"), "ipsum") self.assertEqual(config.get("ssh", "password"), "lorem") self.assertEqual(config.get("ssh", "port"), "123") self.assertEqual( config.get("unixsocket", "socketpath"), "/foo/bar.sock" ) self.assertEqual(config.get("tls", "port"), "123") self.assertEqual(config.get("tls", "certfile"), "foo.cert") self.assertEqual(config.get("tls", "keyfile"), "foo.key") self.assertEqual(config.get("tls", "cafile"), "foo.ca") self.assertDictEqual( config.defaults(), dict(timeout="1000", foo="bar", username="ipsum") ) def test_load_auth(self): root = logging.getLogger(name) root.disabled = True test_config_path = __here__ / "test_auth.cfg" self.assertTrue(test_config_path.is_file()) config = Config() config.load(test_config_path) self.assertEqual(config.get("gmp", "username"), "foo") self.assertEqual(config.get("gmp", "password"), "bar") root.disabled = False def test_load_with_non_existing_configfile(self): test_config_path = __here__ / "foo.cfg" self.assertFalse(test_config_path.is_file()) config = Config() with self.assertRaises(FileNotFoundError): config.load(test_config_path) gvm-tools-23.11.0/tests/test_helper.py000066400000000000000000000210341452466721200176500ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2020-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import ipaddress import sys import unittest import uuid from io import BytesIO from pathlib import Path from unittest.mock import MagicMock, patch from gvm.errors import GvmError from lxml import etree from gvmtools.helper import ( Table, authenticate, create_xml_tree, do_not_run_as_root, error_and_exit, generate_id, generate_random_ips, generate_uuid, run_script, yes_or_no, ) CWD = Path(__file__).absolute().parent class TableTestCase(unittest.TestCase): def setUp(self): self.heading = ["ID", "Name", "Severity"] self.rows = [ ["1", "foobar", "high"], ["2", "bla", "low"], ["3", "blub", "medium"], ] self.divider = " - " self.table = Table( heading=self.heading, rows=self.rows, divider=self.divider ) def test_init_no_args(self): table = Table() self.assertListEqual(table.heading, []) self.assertListEqual(table.rows, []) self.assertEqual(table.divider, " | ") def test_init_with_args(self): self.assertListEqual(self.table.heading, self.heading) self.assertListEqual(self.table.rows, self.rows) self.assertEqual(self.table.divider, self.divider) def test_calculate_dimensions(self): expected_result = [ len(self.heading[0]), len(self.rows[0][1]), len(self.heading[2]), ] column_sizes = ( self.table._calculate_dimensions() # pylint: disable=protected-access ) self.assertEqual(column_sizes, expected_result) def test_create_column(self): column = "foobar" size = 20 expected = "foobar " result = self.table._create_column( # pylint: disable=protected-access column, size ) self.assertEqual(result, expected) def test_create_row(self): columns = ["foo", "bar", "blub"] expected = self.divider.join(columns) result = self.table._create_row(columns) # pylint: disable=W0212 self.assertEqual(result, expected) def test_str(self): expected = ( "ID - Name - Severity\n" + "-- - ------ - --------\n" + "1 - foobar - high \n" + "2 - bla - low \n" + "3 - blub - medium " ) self.assertEqual(str(self.table), expected) class DoNotRunAsRootTestCase(unittest.TestCase): @patch("gvmtools.helper.os") def test_do_not_run_as_root_as_root(self, mock_os): mock_os.geteuid = MagicMock(spec="geteuid") mock_os.geteuid.return_value = 0 self.assertRaises(RuntimeError, do_not_run_as_root) @patch("gvmtools.helper.os") def test_do_not_run_as_root_as_non_root(self, mock_os): mock_os.geteuid = MagicMock(spec="geteuid") mock_os.geteuid.return_value = 123 self.assertIsNone(do_not_run_as_root()) class AuthenticateTestCase(unittest.TestCase): def test_authenticate_already_authenticated(self): mock_gmp = self.create_gmp_mock(True) self.assertIsNone(authenticate(mock_gmp)) @patch("gvmtools.helper.input", return_value="foo") def test_authenticate_username_is_none( self, mock_input ): # pylint: disable=unused-argument,line-too-long mock_gmp = self.create_gmp_mock(False) return_value = authenticate(mock_gmp, password="bar") self.assertTrue(isinstance(return_value, tuple)) self.assertEqual(return_value[0], "foo") self.assertEqual(return_value[1], "bar") @patch("gvmtools.helper.getpass") def test_authenticate_password_is_none(self, mock_getpass): mock_gmp = self.create_gmp_mock(False) mock_getpass.getpass = MagicMock(return_value="SuperSecret123!") return_value = authenticate(mock_gmp, username="user") self.assertTrue(isinstance(return_value, tuple)) self.assertEqual(return_value[0], "user") self.assertEqual(return_value[1], "SuperSecret123!") def test_authenticate(self): mock_gmp = self.create_gmp_mock(False) return_value = authenticate( mock_gmp, username="user", password="password" ) self.assertTrue(isinstance(return_value, tuple)) self.assertEqual(return_value[0], "user") self.assertEqual(return_value[1], "password") def test_authenticate_bad_credentials(self): mock_gmp = self.create_gmp_mock(False) def my_authenticate(username, password): raise GvmError("foo") mock_gmp.authenticate = my_authenticate self.assertRaises(GvmError, authenticate, mock_gmp, "user", "password") def create_gmp_mock(self, authenticated_return_value): mock_gmp = MagicMock() mock_gmp.is_authenticated = MagicMock( return_value=authenticated_return_value ) return mock_gmp class RunScriptTestCase(unittest.TestCase): @patch("gvmtools.helper.open") @patch("gvmtools.helper.exec") def test_run_script(self, mock_exec, mock_open): path = "foo" global_vars = ["OpenVAS", "is", "awesome"] mock_open().read.return_value = "file content" run_script(path, global_vars) mock_open.assert_called_with(path, "r", encoding="utf-8", newline="") mock_exec.assert_called_with("file content", global_vars) @patch("gvmtools.helper.open") @patch("gvmtools.helper.print") def test_run_script_file_not_found(self, mock_print, mock_open): def my_open(path, mode, newline, encoding): raise FileNotFoundError mock_open.side_effect = my_open path = "foo" global_vars = ["OpenVAS", "is", "awesome"] with self.assertRaises(SystemExit): run_script(path, global_vars) mock_print.assert_called_with( f"Script {path} does not exist", file=sys.stderr ) class ScriptUtilsTestCase(unittest.TestCase): @patch("builtins.input", lambda *args: "y") def test_yes(self): yes = yes_or_no("foo?") self.assertTrue(yes) @patch("builtins.input", lambda *args: "n") def test_no(self): no = yes_or_no("bar?") self.assertFalse(no) def test_error_and_exit(self): with self.assertRaises(SystemExit): error_and_exit("foo") def test_create_xml_tree(self): tree = create_xml_tree(BytesIO(b"glurp")) self.assertIsInstance( tree, etree._Element # pylint: disable=protected-access ) self.assertEqual(tree.tag, "foo") def test_create_xml_tree_invalid_file(self): target_xml_path = CWD / "invalid_file.xml" with self.assertRaises(SystemExit): with self.assertRaises(OSError): create_xml_tree(str(target_xml_path)) def test_create_xml_tree_invalid_xml(self): with self.assertRaises(SystemExit): with self.assertRaises(etree.Error): create_xml_tree(BytesIO(b"glurp")) def test_generate_uuid(self): random_uuid = generate_uuid() try: uuid.UUID(random_uuid, version=4) except (ValueError, TypeError, AttributeError): self.fail("No valid UUID.") def test_generate_id(self): random_id = generate_id(size=1, chars="a") self.assertEqual(random_id, "a") random_id = generate_id(size=10) self.assertEqual(len(random_id), 10) self.assertTrue(random_id.isalnum()) def test_generate_random_ips(self): random_ip = generate_random_ips(1) ip_addr = ipaddress.ip_address(random_ip[0]) self.assertEqual(ip_addr.version, 4) self.assertEqual(str(ip_addr), random_ip[0]) num = 10 random_ips = generate_random_ips(num) self.assertEqual(len(random_ips), num) gvm-tools-23.11.0/tests/test_parser.py000066400000000000000000000417301452466721200176720ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2019-2022 Greenbone AG # # SPDX-License-Identifier: GPL-3.0-or-later # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import sys import unittest from argparse import Namespace from pathlib import Path from unittest.mock import patch from gvm.connections import ( DEFAULT_TIMEOUT, DEFAULT_UNIX_SOCKET_PATH, SSHConnection, TLSConnection, UnixSocketConnection, ) from gvmtools.parser import CliParser, create_connection, create_parser from . import SuppressOutput __here__ = Path(__file__).parent.resolve() class ConfigParserTestCase(unittest.TestCase): def setUp(self): self.test_config_path = __here__ / "test.cfg" self.assertTrue(self.test_config_path.is_file()) self.parser = CliParser("TestParser", "test.log") def test_socket_defaults_from_config(self): args = self.parser.parse_args( ["--config", str(self.test_config_path), "socket"] ) self.assertEqual(args.foo, "bar") self.assertEqual(args.timeout, 1000) self.assertEqual(args.gmp_password, "bar") self.assertEqual(args.gmp_username, "bar") self.assertEqual(args.socketpath, "/foo/bar.sock") def test_ssh_defaults_from_config(self): args = self.parser.parse_args( ["--config", str(self.test_config_path), "ssh", "--hostname", "foo"] ) self.assertEqual(args.foo, "bar") self.assertEqual(args.timeout, 1000) self.assertEqual(args.gmp_password, "bar") self.assertEqual(args.gmp_username, "bar") self.assertEqual(args.ssh_password, "lorem") self.assertEqual(args.ssh_username, "ipsum") self.assertEqual(args.port, 123) def test_tls_defaults_from_config(self): args = self.parser.parse_args( ["--config", str(self.test_config_path), "tls", "--hostname", "foo"] ) self.assertEqual(args.foo, "bar") self.assertEqual(args.timeout, 1000) self.assertEqual(args.gmp_password, "bar") self.assertEqual(args.gmp_username, "bar") self.assertEqual(args.certfile, "foo.cert") self.assertEqual(args.keyfile, "foo.key") self.assertEqual(args.cafile, "foo.ca") self.assertEqual(args.port, 123) @patch("gvmtools.parser.logger") @patch("gvmtools.parser.Path") def test_resolve_file_not_found_error(self, path_mock, logger_mock): # Making sure that resolve raises an error def resolve_raises_error(): raise FileNotFoundError() configpath = unittest.mock.MagicMock() configpath.expanduser().resolve = unittest.mock.MagicMock( side_effect=resolve_raises_error ) path_mock.return_value = configpath logger_mock.debug = unittest.mock.MagicMock() args = self.parser.parse_args(["socket"]) self.assertIsInstance(args, Namespace) self.assertEqual(args.connection_type, "socket") self.assertEqual(args.config, "~/.config/gvm-tools.conf") logger_mock.debug.assert_any_call( "Ignoring non existing config file %s", "~/.config/gvm-tools.conf" ) @patch("gvmtools.parser.Path") @patch("gvmtools.parser.Config") def test_config_load_raises_error(self, config_mock, path_mock): def config_load_error(): raise Exception # pylint: disable=broad-exception-raised config = unittest.mock.MagicMock() config.load = unittest.mock.MagicMock(side_effect=config_load_error) config_mock.return_value = config # Making sure that the function thinks the config file exists configpath_exists = unittest.mock.Mock() configpath_exists.expanduser().resolve().exists = ( unittest.mock.MagicMock(return_value=True) ) path_mock.return_value = configpath_exists self.assertRaises(RuntimeError, self.parser.parse_args, ["socket"]) class IgnoreConfigParserTestCase(unittest.TestCase): def test_unkown_config_file(self): test_config_path = __here__ / "foo.cfg" self.assertFalse(test_config_path.is_file()) self.parser = CliParser("TestParser", "test.log") args = self.parser.parse_args( ["--config", str(test_config_path), "socket"] ) self.assertEqual(args.timeout, DEFAULT_TIMEOUT) self.assertEqual(args.gmp_password, "") self.assertEqual(args.gmp_username, "") self.assertEqual(args.socketpath, DEFAULT_UNIX_SOCKET_PATH) def test_unkown_config_file_in_unkown_dir(self): test_config_path = __here__ / "foo" / "foo.cfg" self.assertFalse(test_config_path.is_file()) self.parser = CliParser("TestParser", "test.log") args = self.parser.parse_args( ["--config", str(test_config_path), "socket"] ) self.assertEqual(args.timeout, DEFAULT_TIMEOUT) self.assertEqual(args.gmp_password, "") self.assertEqual(args.gmp_username, "") self.assertEqual(args.socketpath, DEFAULT_UNIX_SOCKET_PATH) class ParserTestCase(unittest.TestCase): def setUp(self): self.parser = CliParser( "TestParser", "test.log", ignore_config=True, prog="gvm-test-cli" ) class RootArgumentsParserTest(ParserTestCase): def test_config(self): args = self.parser.parse_args(["--config", "foo.cfg", "socket"]) self.assertEqual(args.config, "foo.cfg") def test_defaults(self): args = self.parser.parse_args(["socket"]) self.assertEqual(args.config, "~/.config/gvm-tools.conf") self.assertEqual(args.gmp_password, "") self.assertEqual(args.gmp_username, "") self.assertEqual(args.timeout, 60) self.assertIsNone(args.loglevel) def test_loglevel(self): args = self.parser.parse_args(["--log", "ERROR", "socket"]) self.assertEqual(args.loglevel, "ERROR") def test_loglevel_after_subparser(self): with SuppressOutput(suppress_stderr=True): with self.assertRaises(SystemExit): self.parser.parse_args(["socket", "--log", "ERROR"]) def test_timeout(self): args = self.parser.parse_args(["--timeout", "1000", "socket"]) self.assertEqual(args.timeout, 1000) def test_timeout_after_subparser(self): with SuppressOutput(suppress_stderr=True): with self.assertRaises(SystemExit): self.parser.parse_args(["socket", "--timeout", "1000"]) def test_gmp_username(self): args = self.parser.parse_args(["--gmp-username", "foo", "socket"]) self.assertEqual(args.gmp_username, "foo") def test_gmp_username_after_subparser(self): with SuppressOutput(suppress_stderr=True): with self.assertRaises(SystemExit): self.parser.parse_args(["socket", "--gmp-username", "foo"]) def test_gmp_password(self): args = self.parser.parse_args(["--gmp-password", "foo", "socket"]) self.assertEqual(args.gmp_password, "foo") def test_gmp_password_after_subparser(self): with SuppressOutput(suppress_stderr=True): with self.assertRaises(SystemExit): self.parser.parse_args(["socket", "--gmp-password", "foo"]) def test_with_unknown_args(self): args, script_args = self.parser.parse_known_args( ["--gmp-password", "foo", "socket", "--bar", "--bar2"] ) self.assertEqual(args.gmp_password, "foo") self.assertEqual(script_args, ["--bar", "--bar2"]) @patch("gvmtools.parser.logging") def test_socket_has_no_timeout(self, _logging_mock): # pylint: disable=protected-access args_mock = unittest.mock.MagicMock() args_mock.timeout = -1 self.parser._parser.parse_known_args = unittest.mock.MagicMock( return_value=(args_mock, unittest.mock.MagicMock()) ) args, _ = self.parser.parse_known_args( ["socket", "--timeout", "--", "-1"] ) self.assertIsNone(args.timeout) @patch("gvmtools.parser.logging") @patch("gvmtools.parser.argparse.ArgumentParser.print_usage") @patch("gvmtools.parser.argparse.ArgumentParser._print_message") def test_no_args_provided( self, _logging_mock, _print_usage_mock, _print_message ): # pylint: disable=protected-access self.parser._set_defaults = unittest.mock.MagicMock() self.assertRaises(SystemExit, self.parser.parse_known_args, None) class SocketParserTestCase(ParserTestCase): def test_defaults(self): args = self.parser.parse_args(["socket"]) self.assertEqual(args.socketpath, DEFAULT_UNIX_SOCKET_PATH) def test_connection_type(self): args = self.parser.parse_args(["socket"]) self.assertEqual(args.connection_type, "socket") def test_sockpath(self): args = self.parser.parse_args(["socket", "--sockpath", "foo.sock"]) self.assertEqual(args.socketpath, "foo.sock") def test_socketpath(self): args = self.parser.parse_args(["socket", "--socketpath", "foo.sock"]) self.assertEqual(args.socketpath, "foo.sock") class SshParserTestCase(ParserTestCase): def test_defaults(self): args = self.parser.parse_args(["ssh", "--hostname=foo"]) self.assertEqual(args.port, 22) self.assertEqual(args.ssh_username, "gmp") self.assertEqual(args.ssh_password, "gmp") def test_connection_type(self): args = self.parser.parse_args(["ssh", "--hostname=foo"]) self.assertEqual(args.connection_type, "ssh") def test_hostname(self): args = self.parser.parse_args(["ssh", "--hostname", "foo"]) self.assertEqual(args.hostname, "foo") def test_port(self): args = self.parser.parse_args( ["ssh", "--hostname", "foo", "--port", "123"] ) self.assertEqual(args.port, 123) def test_ssh_username(self): args = self.parser.parse_args( ["ssh", "--hostname", "foo", "--ssh-username", "foo"] ) self.assertEqual(args.ssh_username, "foo") def test_ssh_password(self): args = self.parser.parse_args( ["ssh", "--hostname", "foo", "--ssh-password", "foo"] ) self.assertEqual(args.ssh_password, "foo") class TlsParserTestCase(ParserTestCase): def test_defaults(self): args = self.parser.parse_args(["tls", "--hostname=foo"]) self.assertIsNone(args.certfile) self.assertIsNone(args.keyfile) self.assertIsNone(args.cafile) self.assertEqual(args.port, 9390) def test_connection_type(self): args = self.parser.parse_args(["tls", "--hostname=foo"]) self.assertEqual(args.connection_type, "tls") def test_hostname(self): args = self.parser.parse_args(["tls", "--hostname", "foo"]) self.assertEqual(args.hostname, "foo") def test_port(self): args = self.parser.parse_args( ["tls", "--hostname", "foo", "--port", "123"] ) self.assertEqual(args.port, 123) def test_certfile(self): args = self.parser.parse_args( ["tls", "--hostname", "foo", "--certfile", "foo.cert"] ) self.assertEqual(args.certfile, "foo.cert") def test_keyfile(self): args = self.parser.parse_args( ["tls", "--hostname", "foo", "--keyfile", "foo.key"] ) self.assertEqual(args.keyfile, "foo.key") def test_cafile(self): args = self.parser.parse_args( ["tls", "--hostname", "foo", "--cafile", "foo.ca"] ) self.assertEqual(args.cafile, "foo.ca") def test_no_credentials(self): args = self.parser.parse_args( ["tls", "--hostname", "foo", "--no-credentials"] ) self.assertTrue(args.no_credentials) class CustomizeParserTestCase(ParserTestCase): def test_add_optional_argument(self): self.parser.add_argument("--foo", type=int) args = self.parser.parse_args(["socket", "--foo", "123"]) self.assertEqual(args.foo, 123) args = self.parser.parse_args( ["ssh", "--hostname", "bar", "--foo", "123"] ) self.assertEqual(args.foo, 123) args = self.parser.parse_args( ["tls", "--hostname", "bar", "--foo", "123"] ) self.assertEqual(args.foo, 123) def test_add_positional_argument(self): self.parser.add_argument("foo", type=int) args = self.parser.parse_args(["socket", "123"]) self.assertEqual(args.foo, 123) def test_add_protocol_argument(self): self.parser.add_protocol_argument() args = self.parser.parse_args(["socket"]) self.assertEqual(args.protocol, "GMP") args = self.parser.parse_args(["--protocol", "OSP", "socket"]) self.assertEqual(args.protocol, "OSP") class HelpFormattingParserTestCase(ParserTestCase): # pylint: disable=protected-access maxDiff = None python_version = ".".join([str(i) for i in sys.version_info[:2]]) def setUp(self): super().setUp() # ensure all tests are using the same terminal width self.columns = os.environ.get("COLUMNS") os.environ["COLUMNS"] = "80" def tearDown(self): super().tearDown() if not self.columns: del os.environ["COLUMNS"] else: os.environ["COLUMNS"] = self.columns def _snapshot_specific_path(self, name): return __here__ / f"{name}.{self.python_version}.snap" def _snapshot_generic_path(self, name): return __here__ / f"{name}.snap" def _snapshot_failed_path(self, name): return __here__ / f"{name}.{self.python_version}-failed.snap" def _snapshot_path(self, name): snapshot_specific_path = self._snapshot_specific_path(name) if snapshot_specific_path.exists(): return snapshot_specific_path return self._snapshot_generic_path(name) def assert_snapshot(self, name, output): path = self._snapshot_path(name) if not path.exists(): path.write_text(output) content = path.read_text(encoding="utf-8") try: self.assertEqual(content, output, "Snapshot differs from output") except AssertionError: # write new output to snapshot file # reraise error afterwards path = self._snapshot_failed_path(name) path.write_text(output) raise @unittest.skipIf( sys.version_info[:2] > (3, 10), "missing snapshot for Python 3.11" ) def test_root_help(self): help_output = self.parser._parser.format_help() self.assert_snapshot("root_help", help_output) @unittest.skipIf( sys.version_info[:2] > (3, 10), "missing snapshot for Python 3.11" ) def test_socket_help(self): help_output = self.parser._parser_socket.format_help() self.assert_snapshot("socket_help", help_output) @unittest.skipIf( sys.version_info[:2] > (3, 10), "missing snapshot for Python 3.11" ) def test_ssh_help(self): self.parser._set_defaults(None) help_output = self.parser._parser_ssh.format_help() self.assert_snapshot("ssh_help", help_output) @unittest.skipIf( sys.version_info[:2] > (3, 10), "missing snapshot for Python 3.11" ) def test_tls_help(self): self.parser._set_defaults(None) help_output = self.parser._parser_tls.format_help() self.assert_snapshot("tls_help", help_output) class CreateParserFunctionTestCase(unittest.TestCase): # pylint: disable=protected-access def test_create_parser(self): description = "parser description" logfilename = "logfilename" parser = create_parser(description, logfilename) self.assertIsInstance(parser, CliParser) self.assertEqual(parser._logfilename, logfilename) self.assertEqual(parser._bootstrap_parser.description, description) class CreateConnectionTestCase(unittest.TestCase): def test_create_unix_socket_connection(self): self.perform_create_connection_test() def test_create_tls_connection(self): self.perform_create_connection_test("tls", TLSConnection) def test_create_ssh_connection(self): self.perform_create_connection_test("ssh", SSHConnection, 22) def perform_create_connection_test( self, connection_type="socket", connection_class=UnixSocketConnection, port=None, ): connection = create_connection(connection_type, port=port) self.assertIsInstance(connection, connection_class) gvm-tools-23.11.0/tests/tls_help.3.10.snap000066400000000000000000000014101452466721200200310ustar00rootroot00000000000000usage: gvm-test-cli tls [-h] [--hostname HOSTNAME] [--port PORT] [--certfile CERTFILE] [--keyfile KEYFILE] [--cafile CAFILE] [--no-credentials] options: -h, --help show this help message and exit --hostname HOSTNAME Hostname or IP address (default: 127.0.0.1) --port PORT GMP/OSP port (default: 9390) --certfile CERTFILE Path to the certificate file for client authentication. (default: None) --keyfile KEYFILE Path to key file for client authentication. (default: None) --cafile CAFILE Path to CA certificate for server authentication. (default: None) --no-credentials Use only certificates for authentication gvm-tools-23.11.0/tests/tls_help.snap000066400000000000000000000014231452466721200174550ustar00rootroot00000000000000usage: gvm-test-cli tls [-h] [--hostname HOSTNAME] [--port PORT] [--certfile CERTFILE] [--keyfile KEYFILE] [--cafile CAFILE] [--no-credentials] optional arguments: -h, --help show this help message and exit --hostname HOSTNAME Hostname or IP address (default: 127.0.0.1) --port PORT GMP/OSP port (default: 9390) --certfile CERTFILE Path to the certificate file for client authentication. (default: None) --keyfile KEYFILE Path to key file for client authentication. (default: None) --cafile CAFILE Path to CA certificate for server authentication. (default: None) --no-credentials Use only certificates for authentication