diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml deleted file mode 100644 index e3e085bbb..000000000 --- a/.github/workflows/CI.yml +++ /dev/null @@ -1,51 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: CI -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Check out the repo - uses: actions/checkout@v4.1.2 - - - name: Build the code with docker - run: docker build . -t nettacker - - - name: Lint - run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm nettacker flake8 --extend-exclude - '*.txt,*.js,*.md,*.html' --count --select=E9,F63,F7,F82 --show-source" - - - name: Help Menu - run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm nettacker python nettacker.py --help" - - - name: Help Menu in Persian - run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm nettacker python nettacker.py --help - -L fa" - - - name: Show all modules - run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm nettacker python nettacker.py - --show-all-modules" - - - name: Show all profiles - run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm nettacker python nettacker.py --show-all-profiles" - - - name: Test all modules command + check if it's finish successfully + csv - run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm -i nettacker python nettacker.py - -i 127.0.0.1 -u user1,user2 -p pass1,pass2 -m all -g 21,25,80,443 -t 1000 -T 3 -o out.csv" - - - name: Test all modules command + check if it's finish successfully + csv - run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm -i nettacker python nettacker.py - -i 127.0.0.1 -u user1,user2 -p pass1,pass2 -m all -g 21,25,80,443 -t 1000 -T 3 -o out.csv --skip-service-discovery" - - - name: Test all modules command + check if it's finish successfully + with graph + Persian - run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm -i nettacker python nettacker.py -i - 127.0.0.1 -L fa -u user1,user2 -p pass1,pass2 --profile all -g 21,25,80,443 -t 1000 -T 3 - --graph d3_tree_v2_graph -v" - - - name: Test all modules command + check if it's finish successfully + with graph + Persian - run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm -i nettacker python nettacker.py -i - 127.0.0.1 -L fa -u user1,user2 -p pass1,pass2 --profile all -g 21,25,80,443 -t 1000 -T 3 - --graph d3_tree_v2_graph -v --skip-service-discovery" diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml new file mode 100644 index 000000000..21b0eb1e5 --- /dev/null +++ b/.github/workflows/ci_cd.yml @@ -0,0 +1,179 @@ +name: CI/CD +on: [push, pull_request] + +jobs: + run-pytest: + name: Run pytest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade poetry + poetry install --with test + + - name: Run tests + run: | + poetry run pytest + + build-package: + name: Build package + needs: run-pytest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade poetry + poetry install + + - name: Build package + run: | + poetry build --no-interaction + + - name: Upload package artifacts + uses: actions/upload-artifact@v3 + with: + name: dist + path: dist + + publish-to-test-pypi: + name: Publish to Test PyPI + + if: | + ${{ + github.repository == 'owasp/nettacker' && + github.event_name == 'push' && + github.ref_name == 'implement-package-publishing-poc' }} + # environment: test + needs: + - build-package + permissions: + contents: read + id-token: write + runs-on: ubuntu-latest + steps: + - name: Get package artifacts + uses: actions/download-artifact@v3 + with: + name: dist + path: dist + + - name: Publish package distributions to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + publish-to-pypi: + name: Publish to PyPI + if: | + ${{ github.repository == 'owasp/nettacker' && + github.event_name == 'push' && + github.ref_name == 'implement-package-publishing-poc' }} + # environment: release + needs: + - build-package + permissions: + contents: read + id-token: write + runs-on: ubuntu-latest + steps: + - name: Get package artifacts + uses: actions/download-artifact@v3 + with: + name: dist + path: dist + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + test-docker-image: + name: Test Docker image + # needs: publish-to-pypi + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4.1.2 + + - name: Build Docker image + run: docker build . -t nettacker + + - name: Test help menu + run: | + docker run -e github_ci=true --rm nettacker \ + poetry run python nettacker.py --help + + - name: Test help menu in Persian + run: | + docker run -e github_ci=true --rm nettacker \ + poetry run python nettacker.py --help -L fa + + - name: Show all modules + run: | + docker run -e github_ci=true --rm nettacker \ + poetry run python nettacker.py --show-all-modules + + - name: Show all profiles + run: | + docker run -e github_ci=true --rm nettacker \ + poetry run python nettacker.py --show-all-profiles + + - name: Test all modules command + check if it's finish successfully + csv + run: | + docker run -e github_ci=true --rm -i nettacker \ + poetry run python nettacker.py -i 127.0.0.1 -u user1,user2 -p pass1,pass2 -m all -g 21,25,80,443 \ + -t 1000 -T 3 -o out.csv + + - name: Test all modules command + check if it's finish successfully + csv + run: | + docker run -e github_ci=true --rm -i nettacker \ + poetry run python nettacker.py -i 127.0.0.1 -u user1,user2 -p pass1,pass2 -m all -g 21,25,80,443 \ + -t 1000 -T 3 -o out.csv --skip-service-discovery + + - name: Test all modules command + check if it's finish successfully + with graph + Persian + run: | + docker run -e github_ci=true --rm -i nettacker \ + poetry run python nettacker.py -i 127.0.0.1 -L fa -u user1,user2 -p pass1,pass2 --profile all \ + -g 21,25,80,443 -t 1000 -T 3 --graph d3_tree_v2_graph -v + + - name: Test all modules command + check if it's finish successfully + with graph + Persian + run: | + docker run -e github_ci=true --rm -i nettacker \ + poetry run python nettacker.py -i 127.0.0.1 -L fa -u user1,user2 -p pass1,pass2 --profile all \ + -g 21,25,80,443 -t 1000 -T 3 --graph d3_tree_v2_graph -v --skip-service-discovery + + publish-to-docker-registry: + name: Publish Docker image + needs: + - test-docker-image + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4.1.0 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: Dockerfile + push: true + tags: owasp/nettacker:dev diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql_analysis.yml similarity index 100% rename from .github/workflows/codeql-analysis.yml rename to .github/workflows/codeql_analysis.yml diff --git a/.gitignore b/.gitignore index 247d77a0c..533ea355e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,8 @@ *.code-workspace #setup -build/* -dist/* +build +dist *egg-info* #tmp files @@ -27,4 +27,4 @@ results.* .coverage coverage.xml -venv/* \ No newline at end of file +venv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..54b0d80e1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-ast + - id: check-builtin-literals + - id: check-yaml + - id: fix-encoding-pragma + args: + - --remove + - id: mixed-line-ending + args: + - --fix=lf + + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.13 + hooks: + - id: ruff + args: + - --fix + - id: ruff-format diff --git a/Dockerfile b/Dockerfile index cfcfbf261..419b40605 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,9 +4,8 @@ WORKDIR /usr/src/owaspnettacker COPY . . RUN mkdir -p .data/results RUN apt-get update -RUN apt-get install -y $(cat requirements-apt-get.txt) -RUN pip3 install --upgrade pip -RUN pip3 install -r requirements.txt -RUN pip3 install -r requirements-dev.txt +RUN apt-get install -y gcc libssl-dev +RUN pip3 install --upgrade poetry +RUN python -m poetry install ENV docker_env=true -CMD [ "python3", "./nettacker.py" ] +CMD [ "poetry", "run", "python", "./nettacker.py" ] diff --git a/api/__init__.py b/api/__init__.py deleted file mode 100644 index 6f334996f..000000000 --- a/api/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -pass diff --git a/config.py b/config.py deleted file mode 100644 index 41a634537..000000000 --- a/config.py +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import os -import sys -from core.time import now -from core.utility import generate_random_token - - -def nettacker_paths(): - """ - home path for the framework (could be modify by user) - - Returns: - a JSON contain the working, tmp and results path - """ - return { - "requirements_path": os.path.join(sys.path[0], 'requirements.txt'), - "requirements_dev_path": os.path.join(sys.path[0], 'requirements-dev.txt'), - "home_path": os.path.join(sys.path[0]), - "data_path": os.path.join(sys.path[0], '.data'), - "tmp_path": os.path.join(sys.path[0], '.data/tmp'), - "results_path": os.path.join(sys.path[0], '.data/results'), - "database_path": os.path.join(sys.path[0], '.data/nettacker.db'), - "version_file": os.path.join(sys.path[0], 'version.txt'), - "logo_file": os.path.join(sys.path[0], 'logo.txt'), - "messages_path": os.path.join(sys.path[0], 'lib/messages'), - "modules_path": os.path.join(sys.path[0], 'modules'), - "web_browser_user_agents": os.path.join(sys.path[0], 'lib/payloads/User-Agents/web_browsers_user_agents.txt'), - "web_static_files_path": os.path.join(sys.path[0], 'web/static'), - "payloads_path": os.path.join(sys.path[0], 'lib/payloads'), - "module_protocols_path": os.path.join(sys.path[0], 'core/module_protocols'), - } - - -def nettacker_api_config(): - """ - API Config (could be modify by user) - - Returns: - a JSON with API configuration - """ - return { # OWASP Nettacker API Default Configuration - "start_api_server": False, - "api_hostname": "0.0.0.0" if os.environ.get("docker_env") == "true" else "nettacker-api.z3r0d4y.com", - "api_port": 5000, - "api_debug_mode": False, - "api_access_key": generate_random_token(32), - "api_client_whitelisted_ips": [], # disabled - to enable please put an array with list of ips/cidr/ranges - # [ - # "127.0.0.1", - # "10.0.0.0/24", - # "192.168.1.1-192.168.1.255" - # ], - "api_access_log": os.path.join(sys.path[0], '.data/nettacker.log'), - } - - -def nettacker_database_config(): - """ - Database Config (could be modified by user) - For sqlite database: - fill the name of the DB as sqlite, - DATABASE as the name of the db user wants - other details can be left empty - For mysql users: - fill the name of the DB as mysql - DATABASE as the name of the database you want to create - USERNAME, PASSWORD, HOST and the PORT of the MySQL server - need to be filled respectively - - Returns: - a JSON with Database configuration - """ - return { - "DB": "sqlite", - # "DB":"mysql", "DB": "postgres" - "DATABASE": nettacker_paths()["database_path"], - # Name of the database - "USERNAME": "", - "PASSWORD": "", - "HOST": "", - "PORT": "" - } - - -def nettacker_user_application_config(): - """ - core framework default config (could be modify by user) - - Returns: - a JSON with all user default configurations - """ - from core.compatible import version_info - return { # OWASP Nettacker Default Configuration - "language": "en", - "verbose_mode": False, - "verbose_event": False, - "show_version": False, - "report_path_filename": "{results_path}/results_{date_time}_{random_chars}.html".format( - results_path=nettacker_paths()["results_path"], - date_time=now(model="%Y_%m_%d_%H_%M_%S"), - random_chars=generate_random_token(10) - ), - "graph_name": "d3_tree_v2_graph", - "show_help_menu": False, - "targets": None, - "targets_list": None, - "selected_modules": None, - "excluded_modules": None, - "usernames": None, - "usernames_list": None, - "passwords": None, - "passwords_list": None, - "ports": None, - "timeout": 3.0, - "time_sleep_between_requests": 0.0, - "scan_ip_range": False, - "scan_subdomains": False, - "skip_service_discovery": False, - "thread_per_host": 100, - "parallel_module_scan": 1, - "socks_proxy": None, - "retries": 1, - "ping_before_scan": False, - "profiles": None, - "set_hardware_usage": "maximum", # low, normal, high, maximum - "user_agent": "Nettacker {version_number} {version_code}".format( - version_number=version_info()[0], version_code=version_info()[1] - ), - "show_all_modules": False, - "show_all_profiles": False, - "modules_extra_args": None - } - - -def nettacker_global_config(): - return { - "nettacker_paths": nettacker_paths(), - "nettacker_api_config": nettacker_api_config(), - "nettacker_database_config": nettacker_database_config(), - "nettacker_user_application_config": nettacker_user_application_config() - } diff --git a/core/__init__.py b/core/__init__.py deleted file mode 100644 index 56fafa58b..000000000 --- a/core/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- diff --git a/core/alert.py b/core/alert.py deleted file mode 100644 index d47f996b4..000000000 --- a/core/alert.py +++ /dev/null @@ -1,225 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import sys -from core import color -from core.messages import load_message -from core.time import now - -message_cache = load_message().messages - - -def run_from_api(): - """ - check if framework run from API to prevent any alert - - Returns: - True if run from API otherwise False - """ - return "--start-api" in sys.argv - - -def verbose_mode_is_enabled(): - return '--verbose' in sys.argv or '-v' in sys.argv - - -def event_verbose_mode_is_enabled(): - return '--verbose-event' in sys.argv - - -def messages(msg_id): - """ - load a message from message library with specified language - - Args: - msg_id: message id - - Returns: - the message content in the selected language if - message found otherwise return message in English - """ - return message_cache[str(msg_id)] - - -def info(content): - """ - build the info message, log the message in database if requested, - rewrite the thread temporary file - - Args: - content: content of the message - - Returns: - None - """ - if not run_from_api(): - sys.stdout.buffer.write( - bytes( - color.color("yellow") - + "[{0}][+] ".format(now()) - + color.color("green") - + content - + color.color("reset") - + "\n", - "utf8", - ) - ) - sys.stdout.flush() - - -def verbose_event_info(content): - """ - build the info message, log the message in database if requested, - rewrite the thread temporary file - - Args: - content: content of the message - - Returns: - None - """ - if (not run_from_api()) and ( - verbose_mode_is_enabled() or event_verbose_mode_is_enabled() - ): # prevent to stdout if run from API - sys.stdout.buffer.write( - bytes( - color.color("yellow") - + "[{0}][+] ".format(now()) - + color.color("green") - + content - + color.color("reset") - + "\n", - "utf8", - ) - ) - sys.stdout.flush() - - -def success_event_info(content): - """ - build the info message, log the message in database if requested, - rewrite the thread temporary file - - Args: - content: content of the message - - Returns: - None - """ - if not run_from_api(): - sys.stdout.buffer.write( - bytes( - color.color("red") - + "[{0}][+++] ".format(now()) - + color.color("cyan") - + content - + color.color("reset") - + "\n", - "utf8", - ) - ) - sys.stdout.flush() - - -def verbose_info(content): - """ - build the info message, log the message in database if requested, - rewrite the thread temporary file - - Args: - content: content of the message - - Returns: - None - """ - if verbose_mode_is_enabled(): - sys.stdout.buffer.write( - bytes( - color.color("yellow") - + "[{0}][+] ".format(now()) - + color.color("purple") - + content - + color.color("reset") - + "\n", - "utf8", - ) - ) - sys.stdout.flush() - - -def write(content): - """ - simple print a message - - Args: - content: content of the message - - Returns: - None - """ - if not run_from_api(): - sys.stdout.buffer.write( - bytes(content, "utf8") if isinstance(content, str) else content - ) - sys.stdout.flush() - - -def warn(content): - """ - build the warn message - - Args: - content: content of the message - - Returns: - the message in warn structure - None - """ - if not run_from_api(): - sys.stdout.buffer.write( - bytes( - color.color("blue") - + "[{0}][!] ".format(now()) - + color.color("yellow") - + content - + color.color("reset") - + "\n", - "utf8", - ) - ) - sys.stdout.flush() - - -def error(content): - """ - build the error message - - Args: - content: content of the message - - Returns: - the message in error structure - None - """ - data = ( - color.color("red") - + "[{0}][X] ".format(now()) - + color.color("yellow") - + content - + color.color("reset") - + "\n" - ) - sys.stdout.buffer.write(data.encode("utf8")) - sys.stdout.flush() - - -def write_to_api_console(content): - """ - simple print a message in API mode - - Args: - content: content of the message - - Returns: - None - """ - sys.stdout.buffer.write(bytes(content, "utf8")) - sys.stdout.flush() diff --git a/core/args_loader.py b/core/args_loader.py deleted file mode 100644 index d9bf806d0..000000000 --- a/core/args_loader.py +++ /dev/null @@ -1,639 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import argparse -import sys -import json - -from core.alert import write -from core.alert import warn -from core.alert import info -from core.alert import messages -from core.color import color -from core.compatible import version_info -from config import nettacker_global_config -from core.load_modules import load_all_languages -from core.utility import (application_language, - select_maximum_cpu_core) -from core.die import die_success -from core.die import die_failure -from core.color import reset_color -from core.load_modules import load_all_modules -from core.load_modules import load_all_graphs -from core.load_modules import load_all_profiles - - -def load_all_args(): - """ - create the ARGS and help menu - - Returns: - the parser, the ARGS - """ - - nettacker_global_configuration = nettacker_global_config() - - # Language Options - language = application_language() - languages_list = load_all_languages() - if language not in languages_list: - die_failure( - "Please select one of these languages {0}".format( - languages_list - ) - ) - - reset_color() - # Start Parser - parser = argparse.ArgumentParser(prog="Nettacker", add_help=False) - - # Engine Options - engineOpt = parser.add_argument_group( - messages("engine"), messages("engine_input") - ) - engineOpt.add_argument( - "-L", - "--language", - action="store", - dest="language", - default=nettacker_global_configuration['nettacker_user_application_config']["language"], - help=messages("select_language").format(languages_list), - ) - engineOpt.add_argument( - "-v", - "--verbose", - action="store_true", - dest="verbose_mode", - default=nettacker_global_configuration['nettacker_user_application_config']['verbose_mode'], - help=messages("verbose_mode"), - ) - engineOpt.add_argument( - "--verbose-event", - action="store_true", - dest="verbose_event", - default=nettacker_global_configuration['nettacker_user_application_config']['verbose_event'], - help=messages("verbose_event"), - ) - engineOpt.add_argument( - "-V", - "--version", - action="store_true", - default=nettacker_global_configuration['nettacker_user_application_config']['show_version'], - dest="show_version", - help=messages("software_version"), - ) - engineOpt.add_argument( - "-o", - "--output", - action="store", - default=nettacker_global_configuration['nettacker_user_application_config']['report_path_filename'], - dest="report_path_filename", - help=messages("save_logs"), - ) - engineOpt.add_argument( - "--graph", - action="store", - default=nettacker_global_configuration['nettacker_user_application_config']["graph_name"], - dest="graph_name", - help=messages("available_graph").format(load_all_graphs()), - ) - engineOpt.add_argument( - "-h", - "--help", - action="store_true", - default=nettacker_global_configuration['nettacker_user_application_config']["show_help_menu"], - dest="show_help_menu", - help=messages("help_menu"), - ) - - # Target Options - target = parser.add_argument_group( - messages("target"), messages("target_input") - ) - target.add_argument( - "-i", - "--targets", - action="store", - dest="targets", - default=nettacker_global_configuration['nettacker_user_application_config']["targets"], - help=messages("target_list"), - ) - target.add_argument( - "-l", - "--targets-list", - action="store", - dest="targets_list", - default=nettacker_global_configuration['nettacker_user_application_config']["targets_list"], - help=messages("read_target"), - ) - - # Exclude Module Name - exclude_modules = list(load_all_modules(limit=10).keys()) - exclude_modules.remove("all") - - # Methods Options - modules = parser.add_argument_group( - messages("Method"), messages("scan_method_options") - ) - modules.add_argument( - "-m", - "--modules", - action="store", - dest="selected_modules", - default=nettacker_global_configuration['nettacker_user_application_config']["selected_modules"], - help=messages("choose_scan_method").format(list(load_all_modules(limit=10).keys())), - ) - modules.add_argument( - "--modules-extra-args", - action="store", - dest="modules_extra_args", - default=nettacker_global_configuration['nettacker_user_application_config']['modules_extra_args'], - help=messages("modules_extra_args_help") - ) - modules.add_argument( - "--show-all-modules", - action="store_true", - dest="show_all_modules", - default=nettacker_global_configuration['nettacker_user_application_config']["show_all_modules"], - help=messages("show_all_modules"), - ) - modules.add_argument( - "--profile", - action="store", - default=nettacker_global_configuration['nettacker_user_application_config']["profiles"], - dest="profiles", - help=messages("select_profile").format(list(load_all_profiles(limit=10).keys())), - ) - modules.add_argument( - "--show-all-profiles", - action="store_true", - dest="show_all_profiles", - default=nettacker_global_configuration['nettacker_user_application_config']["show_all_profiles"], - help=messages("show_all_profiles"), - ) - modules.add_argument( - "-x", - "--exclude-modules", - action="store", - dest="excluded_modules", - default=nettacker_global_configuration['nettacker_user_application_config']["excluded_modules"], - help=messages("exclude_scan_method").format(exclude_modules), - ) - modules.add_argument( - "-u", - "--usernames", - action="store", - dest="usernames", - default=nettacker_global_configuration['nettacker_user_application_config']["usernames"], - help=messages("username_list"), - ) - modules.add_argument( - "-U", - "--users-list", - action="store", - dest="usernames_list", - default=nettacker_global_configuration['nettacker_user_application_config']["usernames_list"], - help=messages("username_from_file"), - ) - modules.add_argument( - "-p", - "--passwords", - action="store", - dest="passwords", - default=nettacker_global_configuration['nettacker_user_application_config']["passwords"], - help=messages("password_seperator"), - ) - modules.add_argument( - "-P", - "--passwords-list", - action="store", - dest="passwords_list", - default=nettacker_global_configuration['nettacker_user_application_config']["passwords_list"], - help=messages("read_passwords"), - ) - modules.add_argument( - "-g", - "--ports", - action="store", - dest="ports", - default=nettacker_global_configuration['nettacker_user_application_config']["ports"], - help=messages("port_seperator"), - ) - modules.add_argument( - "--user-agent", - action="store", - dest="user_agent", - default=nettacker_global_configuration['nettacker_user_application_config']["user_agent"], - help=messages("select_user_agent"), - ) - modules.add_argument( - "-T", - "--timeout", - action="store", - dest="timeout", - default=nettacker_global_configuration['nettacker_user_application_config']["timeout"], - type=float, - help=messages("read_passwords"), - ) - modules.add_argument( - "-w", - "--time-sleep-between-requests", - action="store", - dest="time_sleep_between_requests", - default=nettacker_global_configuration['nettacker_user_application_config']["time_sleep_between_requests"], - type=float, - help=messages("time_to_sleep"), - ) - modules.add_argument( - "-r", - "--range", - action="store_true", - default=nettacker_global_configuration['nettacker_user_application_config']["scan_ip_range"], - dest="scan_ip_range", - help=messages("range"), - ) - modules.add_argument( - "-s", - "--sub-domains", - action="store_true", - default=nettacker_global_configuration['nettacker_user_application_config']["scan_subdomains"], - dest="scan_subdomains", - help=messages("subdomains"), - ) - modules.add_argument( - "--skip-service-discovery", - action="store_true", - default=nettacker_global_configuration['nettacker_user_application_config']["skip_service_discovery"], - dest="skip_service_discovery", - help=messages("skip_service_discovery") - ) - modules.add_argument( - "-t", - "--thread-per-host", - action="store", - default=nettacker_global_configuration['nettacker_user_application_config']["thread_per_host"], - type=int, - dest="thread_per_host", - help=messages("thread_number_connections"), - ) - modules.add_argument( - "-M", - "--parallel-module-scan", - action="store", - default=nettacker_global_configuration['nettacker_user_application_config']["parallel_module_scan"], - type=int, - dest="parallel_module_scan", - help=messages("thread_number_modules"), - ) - modules.add_argument( - "--set-hardware-usage", - action="store", - dest="set_hardware_usage", - default=nettacker_global_configuration['nettacker_user_application_config']['set_hardware_usage'], - help=messages("set_hardware_usage") - ) - modules.add_argument( - "-R", - "--socks-proxy", - action="store", - dest="socks_proxy", - default=nettacker_global_configuration['nettacker_user_application_config']["socks_proxy"], - help=messages("outgoing_proxy"), - ) - modules.add_argument( - "--retries", - action="store", - dest="retries", - type=int, - default=nettacker_global_configuration['nettacker_user_application_config']["retries"], - help=messages("connection_retries"), - ) - modules.add_argument( - "--ping-before-scan", - action="store_true", - dest="ping_before_scan", - default=nettacker_global_configuration['nettacker_user_application_config']["ping_before_scan"], - help=messages("ping_before_scan"), - ) - # API Options - api = parser.add_argument_group( - messages("API"), - messages("API_options") - ) - api.add_argument( - "--start-api", - action="store_true", - dest="start_api_server", - default=nettacker_global_configuration['nettacker_api_config']["start_api_server"], - help=messages("start_api_server") - ) - api.add_argument( - "--api-host", - action="store", - dest="api_hostname", - default=nettacker_global_configuration['nettacker_api_config']["api_hostname"], - help=messages("API_host") - ) - api.add_argument( - "--api-port", - action="store", - dest="api_port", - default=nettacker_global_configuration['nettacker_api_config']["api_port"], - help=messages("API_port") - ) - api.add_argument( - "--api-debug-mode", - action="store_true", - dest="api_debug_mode", - default=nettacker_global_configuration['nettacker_api_config']["api_debug_mode"], - help=messages("API_debug") - ) - api.add_argument( - "--api-access-key", - action="store", - dest="api_access_key", - default=nettacker_global_configuration['nettacker_api_config']["api_access_key"], - help=messages("API_access_key") - ) - api.add_argument( - "--api-client-whitelisted-ips", - action="store", - dest="api_client_whitelisted_ips", - default=nettacker_global_configuration['nettacker_api_config']["api_client_whitelisted_ips"], - help=messages("define_whie_list") - ) - api.add_argument( - "--api-access-log", - action="store", - dest="api_access_log", - default=nettacker_global_configuration['nettacker_api_config']["api_access_log"], - help=messages("API_access_log_file") - ) - api.add_argument( - "--api-cert", - action="store", - dest="api_cert", - help=messages("API_cert") - ) - api.add_argument( - "--api-cert-key", - action="store", - dest="api_cert_key", - help=messages("API_cert_key") - ) - # Return Options - return parser - - -def check_all_required(parser, api_forms=None): - """ - check all rules and requirements for ARGS - - Args: - parser: parser from argparse - api_forms: values from API - - Returns: - all ARGS with applied rules - """ - # Checking Requirements - options = parser.parse_args() if not api_forms else api_forms - modules_list = load_all_modules(full_details=True) - profiles_list = load_all_profiles() - - # Check Help Menu - if options.show_help_menu: - parser.print_help() - write("\n\n") - write(messages("license")) - die_success() - - # Check version - if options.show_version: - info( - messages("current_version").format( - color("yellow"), - version_info()[0], - color("reset"), - color("cyan"), - version_info()[1], - color("reset"), - color("green"), - ) - ) - die_success() - if options.show_all_modules: - messages("loading_modules") - for module in modules_list: - info( - messages("module_profile_full_information").format( - color('cyan'), - module, - color('green'), - ", ".join( - [ - "{key}: {value}".format( - key=key, value=modules_list[module][key] - ) for key in modules_list[module] - ] - ) - ) - ) - die_success() - if options.show_all_profiles: - messages("loading_profiles") - for profile in profiles_list: - info( - messages("module_profile_full_information").format( - color('cyan'), - profile, - color('green'), - ", ".join(profiles_list[profile]) - ) - ) - die_success() - # API mode - if options.start_api_server: - if '--start-api' in sys.argv and api_forms: - die_failure(messages("cannot_run_api_server")) - from api.engine import start_api_server - if options.api_client_whitelisted_ips: - if type(options.api_client_whitelisted_ips) == str: - options.api_client_whitelisted_ips = options.api_client_whitelisted_ips.split(',') - whielisted_ips = [] - for ip in options.api_client_whitelisted_ips: - from core.ip import (is_single_ipv4, - is_single_ipv6, - is_ipv4_cidr, - is_ipv6_range, - is_ipv6_cidr, - is_ipv4_range, - generate_ip_range) - if is_single_ipv4(ip) or is_single_ipv6(ip): - whielisted_ips.append(ip) - elif is_ipv4_range(ip) or is_ipv6_range(ip) or is_ipv4_cidr(ip) or is_ipv6_cidr(ip): - whielisted_ips += generate_ip_range(ip) - options.api_client_whitelisted_ips = whielisted_ips - start_api_server(options) - - # Check the target(s) - if not (options.targets or options.targets_list) or (options.targets and options.targets_list): - parser.print_help() - write("\n") - die_failure(messages("error_target")) - if options.targets: - options.targets = list(set(options.targets.split(","))) - if options.targets_list: - try: - options.targets = list(set(open(options.targets_list, "rb").read().decode().split())) - except Exception: - die_failure( - messages("error_target_file").format( - options.targets_list - ) - ) - - # check for modules - if not (options.selected_modules or options.profiles): - die_failure(messages("scan_method_select")) - if options.selected_modules: - if options.selected_modules == 'all': - options.selected_modules = list(set(modules_list.keys())) - options.selected_modules.remove('all') - else: - options.selected_modules = list(set(options.selected_modules.split(','))) - for module_name in options.selected_modules: - if module_name not in modules_list: - die_failure( - messages("scan_module_not_found").format( - module_name - ) - ) - if options.profiles: - if not options.selected_modules: - options.selected_modules = [] - if options.profiles == 'all': - options.selected_modules = list(set(modules_list.keys())) - options.selected_modules.remove('all') - else: - options.profiles = list(set(options.profiles.split(','))) - for profile in options.profiles: - if profile not in profiles_list: - die_failure( - messages("profile_404").format( - profile - ) - ) - for module_name in profiles_list[profile]: - if module_name not in options.selected_modules: - options.selected_modules.append(module_name) - # threading & processing - if options.set_hardware_usage not in ['low', 'normal', 'high', 'maximum']: - die_failure( - messages("wrong_hardware_usage") - ) - options.set_hardware_usage = select_maximum_cpu_core(options.set_hardware_usage) - - options.thread_per_host = int(options.thread_per_host) - if not options.thread_per_host >= 1: - options.thread_per_host = 1 - options.parallel_module_scan = int(options.parallel_module_scan) - if not options.parallel_module_scan >= 1: - options.parallel_module_scan = 1 - - # Check for excluding modules - if options.excluded_modules: - options.excluded_modules = options.excluded_modules.split(",") - if 'all' in options.excluded_modules: - die_failure(messages("error_exclude_all")) - for excluded_module in options.excluded_modules: - if excluded_module in options.selected_modules: - del options.selected_modules[excluded_module] - # Check port(s) - if options.ports: - tmp_ports = [] - for port in options.ports.split(","): - try: - if "-" in port: - for port_number in range(int(port.split('-')[0]), int(port.split('-')[1]) + 1): - if port_number not in tmp_ports: - tmp_ports.append(port_number) - else: - if int(port) not in tmp_ports: - tmp_ports.append(int(port)) - except Exception: - die_failure(messages("ports_int")) - options.ports = tmp_ports - - if options.user_agent == 'random_user_agent': - options.user_agents = open( - nettacker_global_config()['nettacker_paths']['web_browser_user_agents'] - ).read().split('\n') - - # Check user list - if options.usernames: - options.usernames = list(set(options.usernames.split(","))) - elif options.usernames_list: - try: - options.usernames = list(set(open(options.usernames_list).read().split("\n"))) - except Exception: - die_failure( - messages("error_username").format(options.usernames_list) - ) - # Check password list - if options.passwords: - options.passwords = list(set(options.passwords.split(","))) - elif options.passwords_list: - try: - options.passwords = list(set(open(options.passwords_list).read().split("\n"))) - except Exception: - die_failure( - messages("error_passwords").format(options.passwords_list) - ) - # Check output file - try: - temp_file = open(options.report_path_filename, "w") - temp_file.close() - except Exception: - die_failure( - messages("file_write_error").format(options.report_path_filename) - ) - # Check Graph - if options.graph_name: - if options.graph_name not in load_all_graphs(): - die_failure( - messages("graph_module_404").format(options.graph_name) - ) - if not (options.report_path_filename.endswith(".html") or options.report_path_filename.endswith(".htm")): - warn(messages("graph_output")) - options.graph_name = None - # check modules extra args - if options.modules_extra_args: - all_args = {} - for args in options.modules_extra_args.split("&"): - value = args.split('=')[1] - if value.lower() == 'true': - value = True - elif value.lower() == 'false': - value = False - elif '.' in value: - try: - value = float(value) - except Exception as _: - del _ - elif '{' in value or '[' in value: - try: - value = json.loads(value) - except Exception as _: - del _ - else: - try: - value = int(value) - except Exception as _: - del _ - all_args[args.split('=')[0]] = value - options.modules_extra_args = all_args - options.timeout = float(options.timeout) - options.time_sleep_between_requests = float(options.time_sleep_between_requests) - options.retries = int(options.retries) - return options diff --git a/core/color.py b/core/color.py deleted file mode 100644 index ad0d9505d..000000000 --- a/core/color.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import sys - - -def reset_color(): - """ - reset the color of terminal before exit - """ - sys.stdout.write("\033[0m") - - -def color(color_name): - """ - color_names for terminal and windows cmd - - Args: - color_name: color name - - Returns: - color_name values or empty string - """ - if color_name == "reset": - return "\033[0m" - elif color_name == "grey": - return "\033[1;30m" - elif color_name == "red": - return "\033[1;31m" - elif color_name == "green": - return "\033[1;32m" - elif color_name == "yellow": - return "\033[1;33m" - elif color_name == "blue": - return "\033[1;34m" - elif color_name == "purple": - return "\033[1;35m" - elif color_name == "cyan": - return "\033[1;36m" - elif color_name == "white": - return "\033[1;37m" - return "" diff --git a/core/compatible.py b/core/compatible.py deleted file mode 100644 index 0df76cc03..000000000 --- a/core/compatible.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import sys -import os -import json -from core.die import die_failure -from core import color - - -def version_info(): - """ - version information of the framework - - Returns: - an array of version and code name - """ - from config import nettacker_paths - return open(nettacker_paths()['version_file']).read().split() - - -def logo(): - """ - OWASP Nettacker Logo - """ - from core.alert import write_to_api_console - from core import color - from core.color import reset_color - from config import nettacker_paths - from config import nettacker_user_application_config - write_to_api_console( - open( - nettacker_paths()['logo_file'] - ).read().format( - version_info()[0], - version_info()[1], - color.color('red'), - color.color('reset'), - color.color('yellow'), - color.color('reset'), - color.color('cyan'), - color.color('reset'), - color.color('cyan'), - color.color('reset'), - color.color('cyan'), - color.color('reset') - ) - ) - reset_color() - - -def python_version(): - """ - version of python - - Returns: - integer version of python (2 or 3) - """ - return int(sys.version_info[0]) - - -def os_name(): - """ - OS name - - Returns: - OS name in string - """ - return sys.platform - - -def check_dependencies(): - if python_version() == 2: - sys.exit(color.color("red") + "[X] " + color.color("yellow") + "Python2 is No longer supported!" + color.color( - "reset")) - - # check os compatibility - from config import nettacker_paths, nettacker_database_config - external_modules = open(nettacker_paths()["requirements_path"]).read().split('\n') - for module_name in external_modules: - try: - __import__( - module_name.split('==')[0] if 'library_name=' not in module_name - else module_name.split('library_name=')[1].split()[0] - ) - except Exception: - if 'is_optional=true' not in module_name: - sys.exit( - color.color("red") + "[X] " + color.color("yellow") + "pip3 install -r requirements.txt ---> " + - module_name.split('#')[0].strip() + " not installed!" + color.color("reset") - ) - logo() - - from core.alert import messages - if not ('linux' in os_name() or 'darwin' in os_name()): - die_failure(messages("error_platform")) - - if not os.path.exists(nettacker_paths()["home_path"]): - try: - os.mkdir(nettacker_paths()["home_path"]) - os.mkdir(nettacker_paths()["tmp_path"]) - os.mkdir(nettacker_paths()["results_path"]) - except Exception: - die_failure("cannot access the directory {0}".format( - nettacker_paths()["home_path"]) - ) - if not os.path.exists(nettacker_paths()["tmp_path"]): - try: - os.mkdir(nettacker_paths()["tmp_path"]) - except Exception: - die_failure("cannot access the directory {0}".format( - nettacker_paths()["results_path"]) - ) - if not os.path.exists(nettacker_paths()["results_path"]): - try: - os.mkdir(nettacker_paths()["results_path"]) - except Exception: - die_failure("cannot access the directory {0}".format( - nettacker_paths()["results_path"]) - ) - - if nettacker_database_config()["DB"] == "sqlite": - try: - if not os.path.isfile(nettacker_paths()["database_path"]): - from database.sqlite_create import sqlite_create_tables - sqlite_create_tables() - except Exception: - die_failure("cannot access the directory {0}".format( - nettacker_paths()["home_path"]) - ) - elif nettacker_database_config()["DB"] == "mysql": - try: - from database.mysql_create import ( - mysql_create_tables, - mysql_create_database - ) - mysql_create_database() - mysql_create_tables() - except Exception: - die_failure(messages("database_connection_failed")) - elif nettacker_database_config()["DB"] == "postgres": - try: - from database.postgres_create import postgres_create_database - postgres_create_database() - except Exception: - die_failure(messages("database_connection_failed")) - else: - die_failure(messages("invalid_database")) diff --git a/core/graph.py b/core/graph.py deleted file mode 100644 index 5f75d3148..000000000 --- a/core/graph.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import json -import csv -import texttable -import html -from core.alert import messages -from core.alert import info, write -from core.compatible import version_info -from core.time import now -from core.die import die_failure -from database.db import get_logs_by_scan_unique_id -from database.db import submit_report_to_db -from core.utility import merge_logs_to_list - - -def build_graph(graph_name, events): - """ - build a graph - - Args: - graph_name: graph name - events: list of events - - Returns: - graph in HTML type - """ - info(messages("build_graph")) - try: - start = getattr( - __import__( - 'lib.graph.{0}.engine'.format( - graph_name.rsplit('_graph')[0] - ), - fromlist=['start'] - ), - 'start' - ) - except Exception: - die_failure( - messages("graph_module_unavailable").format(graph_name) - ) - - info(messages("finish_build_graph")) - return start( - events - ) - - -def build_texttable(events): - """ - value['date'], value["target"], value['module_name'], value['scan_unique_id'], - value['options'], value['event'] - build a text table with generated event related to the scan - - :param events: all events - :return: - array [text table, event_number] - """ - _table = texttable.Texttable() - table_headers = [ - 'date', - 'target', - 'module_name', - 'port', - 'logs' - - ] - _table.add_rows( - [ - table_headers - ] - ) - for event in events: - log = merge_logs_to_list(json.loads(event["json_event"]), []) - _table.add_rows( - [ - table_headers, - [ - event['date'], - event['target'], - event['module_name'], - event['port'], - "\n".join(log) if log else "Detected" - - ] - ] - ) - return _table.draw().encode('utf8') + b'\n\n' + messages("nettacker_version_details").format( - version_info()[0], - version_info()[1], - now() - ).encode('utf8') + b"\n" - - -def create_report(options, scan_unique_id): - """ - sort all events, create log file in HTML/TEXT/JSON and remove old logs - - Args: - options: parsing options - scan_unique_id: scan unique id - - Returns: - True if success otherwise None - """ - all_scan_logs = get_logs_by_scan_unique_id(scan_unique_id) - if not all_scan_logs: - info(messages("no_events_for_report")) - return True - report_path_filename = options.report_path_filename - if ( - len(report_path_filename) >= 5 and report_path_filename[-5:] == '.html' - ) or ( - len(report_path_filename) >= 4 and report_path_filename[-4:] == '.htm' - ): - if options.graph_name: - html_graph = build_graph(options.graph_name, all_scan_logs) - else: - html_graph = '' - - from lib.html_log import log_data - html_table_content = log_data.table_title.format( - html_graph, - log_data.css_1, - 'date', - 'target', - 'module_name', - 'port', - 'logs', - 'json_event' - ) - index=1 - for event in all_scan_logs: - log = merge_logs_to_list(json.loads(event["json_event"]), []) - html_table_content += log_data.table_items.format( - event["date"], - event["target"], - event["module_name"], - event["port"], - "
".join(log) if log else "Detected", #event["event"], #log - index, - html.escape(event["json_event"]) - ) - index+=1 - html_table_content += log_data.table_end + '
' + str(index-1) + '
' + '' + log_data.json_parse_js - with open(report_path_filename, 'w', encoding='utf-8') as save: - save.write(html_table_content + '\n') - save.close() - elif len(report_path_filename) >= 5 and report_path_filename[-5:] == '.json': - with open(report_path_filename, 'w', encoding='utf-8') as save: - save.write( - str( - json.dumps(all_scan_logs) - ) + '\n' - ) - save.close() - elif len(report_path_filename) >= 5 and report_path_filename[-4:] == '.csv': - keys = all_scan_logs[0].keys() - with open(report_path_filename, 'a') as csvfile: - writer = csv.DictWriter(csvfile, fieldnames=keys) - writer.writeheader() - for log in all_scan_logs: - dict_data = { - key: value for key, value in log.items() if key in keys - } - writer.writerow(dict_data) - csvfile.close() - - else: - with open(report_path_filename, 'wb') as save: - save.write( - build_texttable(all_scan_logs) - ) - save.close() - write(build_texttable(all_scan_logs)) - submit_report_to_db( - { - "date": now(model=None), - "scan_unique_id": scan_unique_id, - "options": vars(options), - } - ) - - info(messages("file_saved").format(report_path_filename)) - return True diff --git a/core/load_modules.py b/core/load_modules.py deleted file mode 100644 index f153e2fc7..000000000 --- a/core/load_modules.py +++ /dev/null @@ -1,328 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import copy -import os -import socket -import yaml -import time -import json -from glob import glob -from io import StringIO -from core.socks_proxy import set_socks_proxy - - -class NettackerModules: - def __init__(self, options, module_name, scan_unique_id, process_number, thread_number, total_number_threads): - from config import nettacker_paths - self.module_name = module_name - self.process_number = process_number - self.module_thread_number = thread_number - self.total_module_thread_number = total_number_threads - self.module_inputs = vars(options) - if options.modules_extra_args: - for module_extra_args in self.module_inputs['modules_extra_args']: - self.module_inputs[module_extra_args] = \ - self.module_inputs['modules_extra_args'][module_extra_args] - self.scan_unique_id = scan_unique_id - self.target = options.target - self.skip_service_discovery = options.skip_service_discovery - - self.discovered_services = None - self.ignored_core_modules = [ - 'subdomain_scan', - 'icmp_scan', - 'port_scan' - ] - self.service_discovery_signatures = list(set(yaml.load( - StringIO( - open(nettacker_paths()['modules_path'] + '/scan/port.yaml').read().format( - **{'target': 'dummy'} - ) - ), - Loader=yaml.FullLoader - )['payloads'][0]['steps'][0]['response']['conditions'].keys())) - self.libraries = [ - module_protocol.split('.py')[0] for module_protocol in - os.listdir(nettacker_paths()['module_protocols_path']) if - module_protocol.endswith('.py') and module_protocol != '__init__.py' - ] - - def load(self): - from config import nettacker_paths - from core.utility import find_and_replace_configuration_keys - from database.db import find_events - self.module_content = find_and_replace_configuration_keys( - yaml.load( - StringIO( - open( - nettacker_paths()['modules_path'] + - '/' + - self.module_name.split('_')[-1].split('.yaml')[0] + - '/' + - '_'.join(self.module_name.split('_')[:-1]) + - '.yaml', - 'r' - ).read().format( - **self.module_inputs - ) - ), - Loader=yaml.FullLoader - ), - self.module_inputs - ) - if not self.skip_service_discovery and self.module_name not in self.ignored_core_modules: - services = {} - for service in find_events(self.target, 'port_scan', self.scan_unique_id): - service_event = json.loads(service.json_event) - port = service_event['ports'] - protocols = service_event['response']['conditions_results'].keys() - for protocol in protocols: - if 'core_' + protocol in self.libraries and protocol: - if protocol in services: - services[protocol].append(port) - else: - services[protocol] = [port] - self.discovered_services = copy.deepcopy(services) - index_payload = 0 - for payload in copy.deepcopy(self.module_content['payloads']): - if payload['library'] not in self.discovered_services and \ - payload['library'] in self.service_discovery_signatures: - del self.module_content['payloads'][index_payload] - index_payload -= 1 - else: - index_step = 0 - for step in copy.deepcopy( - self.module_content['payloads'][index_payload]['steps'] - ): - find_and_replace_configuration_keys( - step, - { - "ports": self.discovered_services[payload['library']] - } - ) - self.module_content['payloads'][index_payload]['steps'][index_step] = step - index_step += 1 - index_payload += 1 - - def generate_loops(self): - from core.utility import expand_module_steps - self.module_content['payloads'] = expand_module_steps(self.module_content['payloads']) - - def sort_loops(self): - steps = [] - for index in range(len(self.module_content['payloads'])): - for step in copy.deepcopy(self.module_content['payloads'][index]['steps']): - if 'dependent_on_temp_event' not in step[0]['response']: - steps.append(step) - - for step in copy.deepcopy(self.module_content['payloads'][index]['steps']): - if 'dependent_on_temp_event' in step[0]['response'] and \ - 'save_to_temp_events_only' in step[0]['response']: - steps.append(step) - - for step in copy.deepcopy(self.module_content['payloads'][index]['steps']): - if 'dependent_on_temp_event' in step[0]['response'] and \ - 'save_to_temp_events_only' not in step[0]['response']: - steps.append(step) - self.module_content['payloads'][index]['steps'] = steps - - def start(self): - from terminable_thread import Thread - from core.utility import wait_for_threads_to_finish - active_threads = [] - from core.alert import warn - from core.alert import verbose_event_info - from core.alert import messages - - # counting total number of requests - total_number_of_requests = 0 - for payload in self.module_content['payloads']: - if 'core_' + payload['library'] not in self.libraries: - warn(messages("library_not_supported").format(payload['library'])) - return None - for step in payload['steps']: - total_number_of_requests += len(step) - request_number_counter = 0 - for payload in self.module_content['payloads']: - protocol = getattr( - __import__( - 'core.module_protocols.core_{library}'.format(library=payload['library']), - fromlist=['Engine'] - ), - 'Engine' - ) - for step in payload['steps']: - for sub_step in step: - thread = Thread( - target=protocol.run, - args=( - sub_step, - self.module_name, - self.target, - self.scan_unique_id, - self.module_inputs, - self.process_number, - self.module_thread_number, - self.total_module_thread_number, - request_number_counter, - total_number_of_requests - ) - ) - thread.name = f"{self.target} -> {self.module_name} -> {sub_step}" - request_number_counter += 1 - verbose_event_info( - messages("sending_module_request").format( - self.process_number, - self.module_name, - self.target, - self.module_thread_number, - self.total_module_thread_number, - request_number_counter, - total_number_of_requests - ) - ) - thread.start() - time.sleep(self.module_inputs['time_sleep_between_requests']) - active_threads.append(thread) - wait_for_threads_to_finish( - active_threads, - maximum=self.module_inputs['thread_per_host'], - terminable=True - ) - wait_for_threads_to_finish( - active_threads, - maximum=None, - terminable=True - ) - - -def load_all_graphs(): - """ - load all available graphs - - Returns: - an array of graph names - """ - from config import nettacker_paths - graph_names = [] - for graph_library in glob(os.path.join(nettacker_paths()['home_path'] + '/lib/graph/*/engine.py')): - graph_names.append(graph_library.split('/')[-2] + '_graph') - return list(set(graph_names)) - - -def load_all_languages(): - """ - load all available languages - - Returns: - an array of languages - """ - languages_list = [] - from config import nettacker_paths - for language in glob(os.path.join(nettacker_paths()['home_path'] + '/lib/messages/*.yaml')): - languages_list.append(language.split('/')[-1].split('.')[0]) - return list(set(languages_list)) - - -def load_all_modules(limit=-1, full_details=False): - """ - load all available modules - - limit: return limited number of modules - full: with full details - - Returns: - an array of all module names - """ - # Search for Modules - from config import nettacker_paths - from core.utility import sort_dictionary - if full_details: - import yaml - module_names = {} - for module_name in glob(os.path.join(nettacker_paths()['modules_path'] + '/*/*.yaml')): - libname = module_name.split('/')[-1].split('.')[0] - category = module_name.split('/')[-2] - module_names[libname + '_' + category] = yaml.load( - StringIO( - open( - nettacker_paths()['modules_path'] + - '/' + - category + - '/' + - libname + - '.yaml', - 'r' - ).read().split('payload:')[0] - ), - Loader=yaml.FullLoader - )['info'] if full_details else None - if len(module_names) == limit: - module_names['...'] = {} - break - module_names = sort_dictionary(module_names) - module_names['all'] = {} - - return module_names - - -def load_all_profiles(limit=-1): - """ - load all available profiles - - Returns: - an array of all profile names - """ - from core.utility import sort_dictionary - all_modules_with_details = load_all_modules(limit=limit, full_details=True) - profiles = {} - if '...' in all_modules_with_details: - del all_modules_with_details['...'] - del all_modules_with_details['all'] - for key in all_modules_with_details: - for tag in all_modules_with_details[key]['profiles']: - if tag not in profiles: - profiles[tag] = [] - profiles[tag].append(key) - else: - profiles[tag].append(key) - if len(profiles) == limit: - profiles = sort_dictionary(profiles) - profiles['...'] = [] - profiles['all'] = [] - return profiles - profiles = sort_dictionary(profiles) - profiles['all'] = [] - return profiles - - -def perform_scan(options, target, module_name, scan_unique_id, process_number, thread_number, total_number_threads): - from core.alert import (verbose_event_info, - messages) - - socket.socket, socket.getaddrinfo = set_socks_proxy(options.socks_proxy) - options.target = target - - validate_module = NettackerModules( - options, - module_name, - scan_unique_id, - process_number, - thread_number, - total_number_threads - ) - validate_module.load() - validate_module.generate_loops() - validate_module.sort_loops() - validate_module.start() - - verbose_event_info( - messages("finished_parallel_module_scan").format( - process_number, - module_name, - target, - thread_number, - total_number_threads - ) - ) - return os.EX_OK diff --git a/core/messages.py b/core/messages.py deleted file mode 100644 index 230216957..000000000 --- a/core/messages.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import yaml -from io import StringIO - - -def load_yaml(filename): - return yaml.load( - StringIO( - open(filename, 'r').read() - ), - Loader=yaml.FullLoader - ) - - -class load_message: - def __init__(self): - from core.utility import application_language - from config import nettacker_global_config - self.language = application_language() - self.messages = load_yaml( - "{messages_path}/{language}.yaml".format( - messages_path=nettacker_global_config()['nettacker_paths']['messages_path'], - language=self.language - ) - ) - if self.language != 'en': - self.messages_en = load_yaml( - "{messages_path}/en.yaml".format( - messages_path=nettacker_global_config()['nettacker_paths']['messages_path'] - ) - ) - for message_id in self.messages_en: - if message_id not in self.messages: - self.messages[message_id] = self.messages_en[message_id] diff --git a/core/module_protocols/__init__.py b/core/module_protocols/__init__.py deleted file mode 100644 index 56fafa58b..000000000 --- a/core/module_protocols/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- diff --git a/core/module_protocols/core_ftp.py b/core/module_protocols/core_ftp.py deleted file mode 100644 index 9352b1cd6..000000000 --- a/core/module_protocols/core_ftp.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import copy -import ftplib -# from core.utility import reverse_and_regex_condition -from core.utility import process_conditions -from core.utility import get_dependent_results_from_database -from core.utility import replace_dependent_values - - -# def response_conditions_matched(sub_step, response): -# return response - - -class NettackFTPLib: - def ftp_brute_force(host, ports, usernames, passwords, timeout): - ftp_connection = ftplib.FTP(timeout=int(timeout)) - ftp_connection.connect(host, int(ports)) - ftp_connection.login(usernames, passwords) - ftp_connection.close() - return { - "host": host, - "username": usernames, - "password": passwords, - "port": ports - } - - def ftps_brute_force(host, ports, usernames, passwords, timeout): - ftp_connection = ftplib.FTP_TLS(timeout=int(timeout)) - ftp_connection.connect(host, int(ports)) - ftp_connection.login(usernames, passwords) - ftp_connection.close() - return { - "host": host, - "username": usernames, - "password": passwords, - "port": ports - } - - -class Engine: - def run( - sub_step, - module_name, - target, - scan_unique_id, - options, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ): - backup_method = copy.deepcopy(sub_step['method']) - backup_response = copy.deepcopy(sub_step['response']) - del sub_step['method'] - del sub_step['response'] - if 'dependent_on_temp_event' in backup_response: - temp_event = get_dependent_results_from_database( - target, - module_name, - scan_unique_id, - backup_response['dependent_on_temp_event'] - ) - sub_step = replace_dependent_values( - sub_step, - temp_event - ) - action = getattr(NettackFTPLib, backup_method, None) - for _ in range(options['retries']): - try: - response = action(**sub_step) - break - except Exception as _: - response = [] - sub_step['method'] = backup_method - sub_step['response'] = backup_response - sub_step['response']['conditions_results'] = response - # sub_step['response']['conditions_results'] = response_conditions_matched(sub_step, response) - return process_conditions( - sub_step, - module_name, - target, - scan_unique_id, - options, - response, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ) diff --git a/core/module_protocols/core_http.py b/core/module_protocols/core_http.py deleted file mode 100644 index b5bf0e23b..000000000 --- a/core/module_protocols/core_http.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import re -import aiohttp -import asyncio -import copy -import random -import time -from core.utility import reverse_and_regex_condition -from core.utility import process_conditions -from core.utility import get_dependent_results_from_database -from core.utility import replace_dependent_values -from core.utility import replace_dependent_response - - -async def perform_request_action(action, request_options): - start_time = time.time() - async with action(**request_options) as response: - return { - "reason": response.reason, - "status_code": str(response.status), - "content": await response.content.read(), - "headers": dict(response.headers), - "responsetime": time.time() - start_time - } - - -async def send_request(request_options, method): - async with aiohttp.ClientSession() as session: - action = getattr(session, method, None) - response = await asyncio.gather( - *[ - asyncio.ensure_future( - perform_request_action(action, request_options) - ) - ] - ) - return response[0] - - -def response_conditions_matched(sub_step, response): - if not response: - return {} - condition_type = sub_step['response']['condition_type'] - conditions = sub_step['response']['conditions'] - condition_results = {} - for condition in conditions: - if condition in ['reason', 'status_code', 'content']: - regex = re.findall(re.compile(conditions[condition]['regex']), response[condition]) - reverse = conditions[condition]['reverse'] - condition_results[condition] = reverse_and_regex_condition(regex, reverse) - if condition == 'headers': - # convert headers to case insensitive dict - for key in response["headers"].copy(): - response['headers'][key.lower()] = response['headers'][key] - condition_results['headers'] = {} - for header in conditions['headers']: - reverse = conditions['headers'][header]['reverse'] - try: - regex = re.findall( - re.compile(conditions['headers'][header]['regex']), - response['headers'][header.lower()] if header.lower() in response['headers'] else False - ) - condition_results['headers'][header] = reverse_and_regex_condition(regex, reverse) - except TypeError: - condition_results['headers'][header] = [] - if condition == 'responsetime': - if len(conditions[condition].split()) == 2 and conditions[condition].split()[0] in [ - "==", - "!=", - ">=", - "<=", - ">", - "<" - ]: - exec( - "condition_results['responsetime'] = response['responsetime'] if (" + - "response['responsetime'] {0} float(conditions['responsetime'].split()[-1])".format( - conditions['responsetime'].split()[0] - ) + - ") else []" - - ) - else: - condition_results['responsetime'] = [] - if condition_type.lower() == "or": - # if one of the values are matched, it will be a string or float object in the array - # we count False in the array and if it's not all []; then we know one of the conditions is matched. - if ( - 'headers' not in condition_results and - ( - list(condition_results.values()).count([]) != len(list(condition_results.values())) - ) - ) or ( - 'headers' in condition_results and - ( - - len(list(condition_results.values())) + - len(list(condition_results['headers'].values())) - - list(condition_results.values()).count([]) - - list(condition_results['headers'].values()).count([]) - - 1 != 0 - ) - ): - if sub_step['response'].get('log', False): - condition_results['log'] = sub_step['response']['log'] - if 'response_dependent' in condition_results['log']: - condition_results['log'] = replace_dependent_response(condition_results['log'], condition_results) - return condition_results - else: - return {} - if condition_type.lower() == "and": - if [] in condition_results.values() or \ - ('headers' in condition_results and [] in condition_results['headers'].values()): - return {} - else: - if sub_step['response'].get('log', False): - condition_results['log'] = sub_step['response']['log'] - if 'response_dependent' in condition_results['log']: - condition_results['log'] = replace_dependent_response(condition_results['log'], condition_results) - return condition_results - return {} - - -class Engine: - def run( - sub_step, - module_name, - target, - scan_unique_id, - options, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ): - backup_method = copy.deepcopy(sub_step['method']) - backup_response = copy.deepcopy(sub_step['response']) - backup_iterative_response_match = copy.deepcopy( - sub_step['response']['conditions'].get('iterative_response_match', None)) - if options['user_agent'] == 'random_user_agent': - sub_step['headers']['User-Agent'] = random.choice(options['user_agents']) - del sub_step['method'] - if 'dependent_on_temp_event' in backup_response: - temp_event = get_dependent_results_from_database( - target, - module_name, - scan_unique_id, - backup_response['dependent_on_temp_event'] - ) - sub_step = replace_dependent_values( - sub_step, - temp_event - ) - backup_response = copy.deepcopy(sub_step['response']) - del sub_step['response'] - for _ in range(options['retries']): - try: - response = asyncio.run(send_request(sub_step, backup_method)) - response['content'] = response['content'].decode(errors="ignore") - break - except Exception: - response = [] - sub_step['method'] = backup_method - sub_step['response'] = backup_response - - if backup_iterative_response_match != None: - backup_iterative_response_match = copy.deepcopy( - sub_step['response']['conditions'].get('iterative_response_match')) - del sub_step['response']['conditions']['iterative_response_match'] - - sub_step['response']['conditions_results'] = response_conditions_matched(sub_step, response) - - if backup_iterative_response_match != None and ( - sub_step['response']['conditions_results'] or sub_step['response']['condition_type'] == 'or'): - sub_step['response']['conditions']['iterative_response_match'] = backup_iterative_response_match - for key in sub_step['response']['conditions']['iterative_response_match']: - result = response_conditions_matched( - sub_step['response']['conditions']['iterative_response_match'][key], response) - if result: - sub_step['response']['conditions_results'][key] = result - - return process_conditions( - sub_step, - module_name, - target, - scan_unique_id, - options, - response, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ) diff --git a/core/module_protocols/core_pop3.py b/core/module_protocols/core_pop3.py deleted file mode 100644 index 1d0af1901..000000000 --- a/core/module_protocols/core_pop3.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env core_pop3.py -# -*- coding: utf-8 -*- - -import copy -import poplib -from core.utility import process_conditions -from core.utility import get_dependent_results_from_database -from core.utility import replace_dependent_values - - -class NettackPOP3Lib: - def pop3_brute_force(host, ports, usernames, passwords, timeout): - server = poplib.POP3(host, port=ports, timeout=timeout) - server.user(usernames) - server.pass_(passwords) - server.quit() - return { - "host": host, - "username": usernames, - "password": passwords, - "port": ports - } - - def pop3_ssl_brute_force(host, ports, usernames, passwords, timeout): - server = poplib.POP3_SSL(host, port=ports, timeout=timeout) - server.user(usernames) - server.pass_(passwords) - server.quit() - return { - "host": host, - "username": usernames, - "password": passwords, - "port": ports - } - - -class Engine: - def run( - sub_step, - module_name, - target, - scan_unique_id, - options, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ): - backup_method = copy.deepcopy(sub_step['method']) - backup_response = copy.deepcopy(sub_step['response']) - del sub_step['method'] - del sub_step['response'] - if 'dependent_on_temp_event' in backup_response: - temp_event = get_dependent_results_from_database( - target, - module_name, - scan_unique_id, - backup_response['dependent_on_temp_event'] - ) - sub_step = replace_dependent_values( - sub_step, - temp_event - ) - action = getattr(NettackPOP3Lib, backup_method, None) - for _ in range(options['retries']): - try: - response = action(**sub_step) - break - except Exception as _: - response = [] - sub_step['method'] = backup_method - sub_step['response'] = backup_response - sub_step['response']['conditions_results'] = response - return process_conditions( - sub_step, - module_name, - target, - scan_unique_id, - options, - response, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ) diff --git a/core/module_protocols/core_smtp.py b/core/module_protocols/core_smtp.py deleted file mode 100644 index 459cf9eb8..000000000 --- a/core/module_protocols/core_smtp.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import copy -import smtplib -# from core.utility import reverse_and_regex_condition -from core.utility import process_conditions -from core.utility import get_dependent_results_from_database -from core.utility import replace_dependent_values - - -# def response_conditions_matched(sub_step, response): -# return response - - -class NettackSMTPLib: - def smtp_brute_force(host, ports, usernames, passwords, timeout): - smtp_connection = smtplib.SMTP(host, int(ports), timeout=int(timeout)) - smtp_connection.login(usernames, passwords) - smtp_connection.close() - return { - "host": host, - "username": usernames, - "password": passwords, - "port": ports - } - - def smtps_brute_force(host, ports, usernames, passwords, timeout): - smtp_connection = smtplib.SMTP(host, int(ports), timeout=int(timeout)) - smtp_connection.starttls() - smtp_connection.login(usernames, passwords) - smtp_connection.close() - return { - "host": host, - "username": usernames, - "password": passwords, - "port": ports - } - - -class Engine: - def run( - sub_step, - module_name, - target, - scan_unique_id, - options, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ): - backup_method = copy.deepcopy(sub_step['method']) - backup_response = copy.deepcopy(sub_step['response']) - del sub_step['method'] - del sub_step['response'] - if 'dependent_on_temp_event' in backup_response: - temp_event = get_dependent_results_from_database( - target, - module_name, - scan_unique_id, - backup_response['dependent_on_temp_event'] - ) - sub_step = replace_dependent_values( - sub_step, - temp_event - ) - action = getattr(NettackSMTPLib, backup_method, None) - for _ in range(options['retries']): - try: - response = action(**sub_step) - break - except Exception as _: - response = [] - sub_step['method'] = backup_method - sub_step['response'] = backup_response - sub_step['response']['conditions_results'] = response - # sub_step['response']['conditions_results'] = response_conditions_matched(sub_step, response) - return process_conditions( - sub_step, - module_name, - target, - scan_unique_id, - options, - response, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ) diff --git a/core/module_protocols/core_socket.py b/core/module_protocols/core_socket.py deleted file mode 100644 index 6522a437d..000000000 --- a/core/module_protocols/core_socket.py +++ /dev/null @@ -1,282 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import socket -import copy -import re -import os -import select -import struct -import time -import ssl -from core.utility import reverse_and_regex_condition -from core.utility import process_conditions -from core.utility import get_dependent_results_from_database -from core.utility import replace_dependent_values - - -def response_conditions_matched(sub_step, response): - conditions = sub_step['response']['conditions'] - condition_type = sub_step['response']['condition_type'] - condition_results = {} - if sub_step['method'] == 'tcp_connect_only': - return response - if sub_step['method'] == 'tcp_connect_send_and_receive': - if response: - received_content = response['response'] - for condition in conditions: - regex = re.findall(re.compile(conditions[condition]['regex']), received_content) - reverse = conditions[condition]['reverse'] - condition_results[condition] = reverse_and_regex_condition(regex, reverse) - for condition in copy.deepcopy(condition_results): - if not condition_results[condition]: - del condition_results[condition] - if 'open_port' in condition_results and len(condition_results) > 1: - del condition_results['open_port'] - del conditions['open_port'] - if condition_type == 'and': - return condition_results if len(condition_results) == len(conditions) else [] - if condition_type == 'or': - return condition_results if condition_results else [] - return [] - if sub_step['method'] == 'socket_icmp': - return response - return [] - - -def create_tcp_socket(host, ports, timeout): - socket_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - socket_connection.settimeout(timeout) - socket_connection.connect((host, int(ports))) - ssl_flag = False - try: - socket_connection = ssl.wrap_socket(socket_connection) - ssl_flag = True - except Exception: - socket_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - socket_connection.settimeout(timeout) - socket_connection.connect((host, int(ports))) - return socket_connection, ssl_flag - - -class NettackerSocket: - def tcp_connect_only(host, ports, timeout): - socket_connection, ssl_flag = create_tcp_socket(host, ports, timeout) - peer_name = socket_connection.getpeername() - socket_connection.close() - return { - "peer_name": peer_name, - "service": socket.getservbyport(int(ports)), - "ssl_flag": ssl_flag - } - - def tcp_connect_send_and_receive(host, ports, timeout): - socket_connection, ssl_flag = create_tcp_socket(host, ports, timeout) - peer_name = socket_connection.getpeername() - try: - socket_connection.send(b"ABC\x00\r\n\r\n\r\n" * 10) - response = socket_connection.recv(1024 * 1024 * 10) - socket_connection.close() - except Exception: - try: - socket_connection.close() - response = b"" - except Exception: - response = b"" - return { - "peer_name": peer_name, - "service": socket.getservbyport(int(ports)), - "response": response.decode(errors='ignore'), - "ssl_flag": ssl_flag - } - - def socket_icmp(host, timeout): - """ - A pure python ping implementation using raw socket. - Note that ICMP messages can only be sent from processes running as root. - Derived from ping.c distributed in Linux's netkit. That code is - copyright (c) 1989 by The Regents of the University of California. - That code is in turn derived from code written by Mike Muuss of the - US Army Ballistic Research Laboratory in December, 1983 and - placed in the public domain. They have my thanks. - Bugs are naturally mine. I'd be glad to hear about them. There are - certainly word - size dependenceies here. - Copyright (c) Matthew Dixon Cowles, . - Distributable under the terms of the GNU General Public License - version 2. Provided with no warranties of any sort. - Original Version from Matthew Dixon Cowles: - -> ftp://ftp.visi.com/users/mdc/ping.py - Rewrite by Jens Diemer: - -> http://www.python-forum.de/post-69122.html#69122 - Rewrite by George Notaras: - -> http://www.g-loaded.eu/2009/10/30/python-ping/ - Fork by Pierre Bourdon: - -> http://bitbucket.org/delroth/python-ping/ - Revision history - ~~~~~~~~~~~~~~~~ - November 22, 1997 - ----------------- - Initial hack. Doesn't do much, but rather than try to guess - what features I (or others) will want in the future, I've only - put in what I need now. - December 16, 1997 - ----------------- - For some reason, the checksum bytes are in the wrong order when - this is run under Solaris 2.X for SPARC but it works right under - Linux x86. Since I don't know just what's wrong, I'll swap the - bytes always and then do an htons(). - December 4, 2000 - ---------------- - Changed the struct.pack() calls to pack the checksum and ID as - unsigned. My thanks to Jerome Poincheval for the fix. - May 30, 2007 - ------------ - little rewrite by Jens Diemer: - - change socket asterisk import to a normal import - - replace time.time() with time.clock() - - delete "return None" (or change to "return" only) - - in checksum() rename "str" to "source_string" - November 8, 2009 - ---------------- - Improved compatibility with GNU/Linux systems. - Fixes by: - * George Notaras -- http://www.g-loaded.eu - Reported by: - * Chris Hallman -- http://cdhallman.blogspot.com - Changes in this release: - - Re-use time.time() instead of time.clock(). The 2007 implementation - worked only under Microsoft Windows. Failed on GNU/Linux. - time.clock() behaves differently under the two OSes[1]. - [1] http://docs.python.org/library/time.html#time.clock - September 25, 2010 - ------------------ - Little modifications by Georgi Kolev: - - Added quiet_ping function. - - returns percent lost packages, max round trip time, avrg round trip - time - - Added packet size to verbose_ping & quiet_ping functions. - - Bump up version to 0.2 - ------------------ - 5 Aug 2021 - Modified by Ali Razmjoo Qalaei (Reformat the code and more human readable) - """ - icmp_socket = socket.getprotobyname("icmp") - socket_connection = socket.socket( - socket.AF_INET, - socket.SOCK_RAW, - icmp_socket - ) - random_integer = os.getpid() & 0xFFFF - icmp_echo_request = 8 - # Make a dummy header with a 0 checksum. - dummy_checksum = 0 - header = struct.pack("bbHHh", icmp_echo_request, 0, dummy_checksum, random_integer, 1) - data = struct.pack("d", time.time()) + struct.pack("d", time.time()) + str( - (76 - struct.calcsize("d")) * "Q" - ).encode() # packet size = 76 (removed 8 bytes size of header) - source_string = header + data - # Calculate the checksum on the data and the dummy header. - calculate_data = 0 - max_size = (len(source_string) / 2) * 2 - counter = 0 - while counter < max_size: - calculate_data += source_string[counter + 1] * 256 + source_string[counter] - calculate_data = calculate_data & 0xffffffff # Necessary? - counter += 2 - - if max_size < len(source_string): - calculate_data += source_string[len(source_string) - 1] - calculate_data = calculate_data & 0xffffffff # Necessary? - - calculate_data = (calculate_data >> 16) + (calculate_data & 0xffff) - calculate_data = calculate_data + (calculate_data >> 16) - calculated_data = ~calculate_data & 0xffff - - # Swap bytes. Bugger me if I know why. - dummy_checksum = calculated_data >> 8 | (calculated_data << 8 & 0xff00) - - header = struct.pack( - "bbHHh", icmp_echo_request, 0, socket.htons(dummy_checksum), random_integer, 1 - ) - socket_connection.sendto(header + data, (socket.gethostbyname(host), 1)) # Don't know about the 1 - - while True: - started_select = time.time() - what_ready = select.select([socket_connection], [], [], timeout) - how_long_in_select = (time.time() - started_select) - if not what_ready[0]: # Timeout - break - time_received = time.time() - received_packet, address = socket_connection.recvfrom(1024) - icmp_header = received_packet[20:28] - packet_type, packet_code, packet_checksum, packet_id, packet_sequence = struct.unpack( - "bbHHh", icmp_header - ) - if packet_id == random_integer: - packet_bytes = struct.calcsize("d") - time_sent = struct.unpack("d", received_packet[28:28 + packet_bytes])[0] - delay = time_received - time_sent - break - - timeout = timeout - how_long_in_select - if timeout <= 0: - break - socket_connection.close() - return { - "host": host, - "response_time": delay, - "ssl_flag": False - } - - -class Engine: - def run( - sub_step, - module_name, - target, - scan_unique_id, - options, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ): - backup_method = copy.deepcopy(sub_step['method']) - backup_response = copy.deepcopy(sub_step['response']) - del sub_step['method'] - del sub_step['response'] - if 'dependent_on_temp_event' in backup_response: - temp_event = get_dependent_results_from_database( - target, - module_name, - scan_unique_id, - backup_response['dependent_on_temp_event'] - ) - sub_step = replace_dependent_values( - sub_step, - temp_event - ) - action = getattr(NettackerSocket, backup_method, None) - for _ in range(options['retries']): - try: - response = action(**sub_step) - break - except Exception: - response = [] - sub_step['method'] = backup_method - sub_step['response'] = backup_response - sub_step['response']['ssl_flag'] = response['ssl_flag'] if type(response) == dict else False - sub_step['response']['conditions_results'] = response_conditions_matched(sub_step, response) - return process_conditions( - sub_step, - module_name, - target, - scan_unique_id, - options, - response, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ) diff --git a/core/module_protocols/core_ssh.py b/core/module_protocols/core_ssh.py deleted file mode 100644 index c4dcbc3de..000000000 --- a/core/module_protocols/core_ssh.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import copy -import paramiko -import logging -# from core.utility import reverse_and_regex_condition -from core.utility import process_conditions -from core.utility import get_dependent_results_from_database -from core.utility import replace_dependent_values - - -# def response_conditions_matched(sub_step, response): -# return response - - -class NettackSSHLib: - def ssh_brute_force(host, ports, usernames, passwords, timeout): - paramiko_logger = logging.getLogger("paramiko.transport") - paramiko_logger.disabled = True - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh.connect( - hostname=host, - port=int(ports), - timeout=int(timeout), - auth_strategy=paramiko.auth_strategy.Password( - username=usernames, - password_getter=lambda:passwords - ) if passwords else paramiko.auth_strategy.NoneAuth( - username=usernames - ), - ) - ssh.close() - return { - "host": host, - "username": usernames, - "password": passwords, - "port": ports - } - - -class Engine: - def run( - sub_step, - module_name, - target, - scan_unique_id, - options, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ): - backup_method = copy.deepcopy(sub_step['method']) - backup_response = copy.deepcopy(sub_step['response']) - del sub_step['method'] - del sub_step['response'] - if 'dependent_on_temp_event' in backup_response: - temp_event = get_dependent_results_from_database( - target, - module_name, - scan_unique_id, - backup_response['dependent_on_temp_event'] - ) - sub_step = replace_dependent_values( - sub_step, - temp_event - ) - action = getattr(NettackSSHLib, backup_method, None) - for _ in range(options['retries']): - try: - response = action(**sub_step) - break - except Exception: - response = [] - sub_step['method'] = backup_method - sub_step['response'] = backup_response - sub_step['response']['conditions_results'] = response - # sub_step['response']['conditions_results'] = response_conditions_matched(sub_step, response) - return process_conditions( - sub_step, - module_name, - target, - scan_unique_id, - options, - response, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ) diff --git a/core/module_protocols/core_telnet.py b/core/module_protocols/core_telnet.py deleted file mode 100644 index 1f551334f..000000000 --- a/core/module_protocols/core_telnet.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import copy -import telnetlib -# from core.utility import reverse_and_regex_condition -from core.utility import process_conditions -from core.utility import get_dependent_results_from_database -from core.utility import replace_dependent_values - - -# def response_conditions_matched(sub_step, response): -# return response - - -class NettackTelnetLib: - def telnet_brute_force(host, ports, usernames, passwords, timeout): - telnet_connection = telnetlib.Telnet(host, port=int(ports), timeout=int(timeout)) - telnet_connection.read_until(b"login: ") - telnet_connection.write(usernames + "\n") - telnet_connection.read_until(b"Password: ") - telnet_connection.write(passwords + "\n") - telnet_connection.close() - return { - "host": host, - "username": usernames, - "password": passwords, - "port": ports - } - - -class Engine: - def run( - sub_step, - module_name, - target, - scan_unique_id, - options, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ): - backup_method = copy.deepcopy(sub_step['method']) - backup_response = copy.deepcopy(sub_step['response']) - del sub_step['method'] - del sub_step['response'] - if 'dependent_on_temp_event' in backup_response: - temp_event = get_dependent_results_from_database( - target, - module_name, - scan_unique_id, - backup_response['dependent_on_temp_event'] - ) - sub_step = replace_dependent_values( - sub_step, - temp_event - ) - action = getattr(NettackTelnetLib, backup_method, None) - for _ in range(options['retries']): - try: - response = action(**sub_step) - break - except Exception as _: - response = [] - sub_step['method'] = backup_method - sub_step['response'] = backup_response - sub_step['response']['conditions_results'] = response - # sub_step['response']['conditions_results'] = response_conditions_matched(sub_step, response) - return process_conditions( - sub_step, - module_name, - target, - scan_unique_id, - options, - response, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ) diff --git a/core/parse.py b/core/parse.py deleted file mode 100644 index b8776834e..000000000 --- a/core/parse.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - - -from core.scan_targets import start_scan_processes -from core.alert import info -from core.alert import write -from core.alert import messages -from core.load_modules import load_all_modules -from core.args_loader import load_all_args -from core.args_loader import check_all_required - - -def load(): - """ - load all ARGS, Apply rules and go for attacks - - Returns: - True if success otherwise None - """ - write("\n\n") - options = check_all_required(load_all_args()) - - info(messages("scan_started")) - info(messages("loaded_modules").format(len(load_all_modules()))) - exit_code = start_scan_processes(options) - info(messages("done")) - return exit_code diff --git a/core/scan_targets.py b/core/scan_targets.py deleted file mode 100644 index 495411165..000000000 --- a/core/scan_targets.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import numpy -import multiprocessing -from core.alert import (info, - verbose_event_info, - messages) -from core.targets import expand_targets -from core.utility import generate_random_token -from core.load_modules import perform_scan -from terminable_thread import Thread -from core.utility import wait_for_threads_to_finish -from core.graph import create_report - - -def parallel_scan_process(options, targets, scan_unique_id, process_number): - active_threads = [] - verbose_event_info(messages("single_process_started").format(process_number)) - total_number_of_modules = len(targets) * len(options.selected_modules) - total_number_of_modules_counter = 1 - for target in targets: - for module_name in options.selected_modules: - thread = Thread( - target=perform_scan, - args=( - options, - target, - module_name, - scan_unique_id, - process_number, - total_number_of_modules_counter, - total_number_of_modules - ) - ) - thread.name = f"{target} -> {module_name}" - thread.start() - verbose_event_info( - messages("start_parallel_module_scan").format( - process_number, - module_name, - target, - total_number_of_modules_counter, - total_number_of_modules - ) - ) - total_number_of_modules_counter += 1 - active_threads.append(thread) - if not wait_for_threads_to_finish(active_threads, options.parallel_module_scan, True): - return False - wait_for_threads_to_finish(active_threads, maximum=None, terminable=True) - return True - - -def multi_processor(options, scan_unique_id): - if not options.targets: - info(messages("no_live_service_found")) - return True - number_of_total_targets = len(options.targets) - options.targets = [ - targets.tolist() for targets in numpy.array_split( - options.targets, - options.set_hardware_usage if options.set_hardware_usage <= len(options.targets) - else number_of_total_targets - ) - ] - info(messages("removing_old_db_records")) - from database.db import remove_old_logs - for target_group in options.targets: - for target in target_group: - for module_name in options.selected_modules: - remove_old_logs( - { - "target": target, - "module_name": module_name, - "scan_unique_id": scan_unique_id, - } - ) - for _ in range(options.targets.count([])): - options.targets.remove([]) - active_processes = [] - info( - messages("start_multi_process").format( - number_of_total_targets, - len(options.targets) - ) - ) - process_number = 0 - for targets in options.targets: - process_number += 1 - process = multiprocessing.Process( - target=parallel_scan_process, - args=(options, targets, scan_unique_id, process_number,) - ) - process.start() - active_processes.append(process) - return wait_for_threads_to_finish(active_processes, sub_process=True) - - -def start_scan_processes(options): - """ - preparing for attacks and managing multi-processing for host - - Args: - options: all options - - Returns: - True when it ends - """ - scan_unique_id = generate_random_token(32) - # find total number of targets + types + expand (subdomain, IPRanges, etc) - # optimize CPU usage - info(messages("regrouping_targets")) - options.targets = expand_targets(options, scan_unique_id) - if options.targets: - exit_code = multi_processor(options, scan_unique_id) - create_report(options, scan_unique_id) - else: - info(messages("no_live_service_found")) - exit_code = True - return exit_code diff --git a/core/targets.py b/core/targets.py deleted file mode 100644 index b7e568628..000000000 --- a/core/targets.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import copy -import json -import os -from core.ip import (get_ip_range, - generate_ip_range, - is_single_ipv4, - is_ipv4_range, - is_ipv4_cidr, - is_single_ipv6, - is_ipv6_range, - is_ipv6_cidr) -from database.db import find_events - - -def filter_target_by_event(targets, scan_unique_id, module_name): - for target in copy.deepcopy(targets): - if not find_events(target, module_name, scan_unique_id): - targets.remove(target) - return targets - - -def expand_targets(options, scan_unique_id): - """ - analysis and calulcate targets. - - Args: - options: all options - scan_unique_id: unique scan identifier - - Returns: - a generator - """ - from core.scan_targets import multi_processor - targets = [] - for target in options.targets: - if '://' in target: - # remove url proto; uri; port - target = target.split('://')[1].split('/')[0].split(':')[0] - targets.append(target) - # single IPs - elif is_single_ipv4(target) or is_single_ipv6(target): - if options.scan_ip_range: - targets += get_ip_range(target) - else: - targets.append(target) - # IP ranges - elif is_ipv4_range(target) or is_ipv6_range(target) or is_ipv4_cidr(target) or is_ipv6_cidr(target): - targets += generate_ip_range(target) - # domains probably - else: - targets.append(target) - options.targets = targets - - # subdomain_scan - if options.scan_subdomains: - selected_modules = options.selected_modules - options.selected_modules = ['subdomain_scan'] - multi_processor( - copy.deepcopy(options), - scan_unique_id - ) - options.selected_modules = selected_modules - if 'subdomain_scan' in options.selected_modules: - options.selected_modules.remove('subdomain_scan') - - for target in copy.deepcopy(options.targets): - for row in find_events(target, 'subdomain_scan', scan_unique_id): - for sub_domain in json.loads(row.json_event)['response']['conditions_results']['content']: - if sub_domain not in options.targets: - options.targets.append(sub_domain) - # icmp_scan - if options.ping_before_scan: - if os.geteuid() == 0: - selected_modules = options.selected_modules - options.selected_modules = ['icmp_scan'] - multi_processor( - copy.deepcopy(options), - scan_unique_id - ) - options.selected_modules = selected_modules - if 'icmp_scan' in options.selected_modules: - options.selected_modules.remove('icmp_scan') - options.targets = filter_target_by_event(targets, scan_unique_id, 'icmp_scan') - else: - from core.alert import warn - from core.alert import messages - warn(messages("icmp_need_root_access")) - if 'icmp_scan' in options.selected_modules: - options.selected_modules.remove('icmp_scan') - # port_scan - if not options.skip_service_discovery: - options.skip_service_discovery = True - selected_modules = options.selected_modules - options.selected_modules = ['port_scan'] - multi_processor( - copy.deepcopy(options), - scan_unique_id - ) - options.selected_modules = selected_modules - if 'port_scan' in options.selected_modules: - options.selected_modules.remove('port_scan') - options.targets = filter_target_by_event(targets, scan_unique_id, 'port_scan') - options.skip_service_discovery = False - return list(set(options.targets)) diff --git a/core/time.py b/core/time.py deleted file mode 100644 index 07436f80c..000000000 --- a/core/time.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import datetime - - -def now(model="%Y-%m-%d %H:%M:%S"): - """ - get now date and time - Args: - model: the date and time model, default is "%Y-%m-%d %H:%M:%S" - - Returns: - the date and time of now - """ - return datetime.datetime.now().strftime(model) if model else datetime.datetime.now() diff --git a/core/utility.py b/core/utility.py deleted file mode 100644 index 76ceb54cb..000000000 --- a/core/utility.py +++ /dev/null @@ -1,575 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import copy -import random -import string -import sys -import ctypes -import time -import json -import os -import multiprocessing -import yaml -import hashlib -import re -from core.load_modules import load_all_languages -from core.time import now -from core.color import color - - -def process_conditions( - event, - module_name, - target, - scan_unique_id, - options, - response, - process_number, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests -): - from core.alert import (success_event_info, - verbose_info, - messages) - - if 'save_to_temp_events_only' in event.get('response', ''): - from database.db import submit_temp_logs_to_db - submit_temp_logs_to_db( - { - "date": now(model=None), - "target": target, - "module_name": module_name, - "scan_unique_id": scan_unique_id, - "event_name": event['response']['save_to_temp_events_only'], - "port": event.get('ports', ''), - "event": event, - "data": response - } - ) - if event['response']['conditions_results'] and 'save_to_temp_events_only' not in event.get('response', ''): - from database.db import submit_logs_to_db - - # remove sensitive information before submitting to db - from config import nettacker_api_config - options = copy.deepcopy(options) - for key in nettacker_api_config(): - try: - del options[key] - except Exception: - continue - del event['response']['conditions'] - del event['response']['condition_type'] - if 'log' in event['response']: - del event['response']['log'] - event_request_keys = copy.deepcopy(event) - del event_request_keys['response'] - submit_logs_to_db( - { - "date": now(model=None), - "target": target, - "module_name": module_name, - "scan_unique_id": scan_unique_id, - "port": event.get('ports') or event.get('port') or ( - event.get('url').split(':')[2].split('/')[0] if - type(event.get('url')) == str and len(event.get('url').split(':')) >= 3 and - event.get('url').split(':')[2].split('/')[0].isdigit() else "" - ), - "event": " ".join( - yaml.dump(event_request_keys).split() - ) + " conditions: " + " ".join( - yaml.dump(event['response']['conditions_results']).split() - ), - "json_event": event - } - ) - log_list = merge_logs_to_list(event['response']['conditions_results']) - if log_list: - success_event_info( - messages("send_success_event_from_module").format( - process_number, - module_name, - target, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests, - " ", - filter_large_content( - "\n".join( - [ - color('purple') + key + color('reset') - for key in log_list - ] - ), - filter_rate=100000 - ) - ) - ) - else: - success_event_info( - messages("send_success_event_from_module").format( - process_number, - module_name, - target, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests, - " ".join( - [ - color('yellow') + key + color('reset') if ':' in key - else color('green') + key + color('reset') - for key in yaml.dump(event_request_keys).split() - ] - ), - filter_large_content( - "conditions: " + " ".join( - [ - color('purple') + key + color('reset') if ':' in key - else color('green') + key + color('reset') - for key in yaml.dump(event['response']['conditions_results']).split() - ] - ), - filter_rate=150 - ) - ) - ) - verbose_info( - json.dumps(event) - ) - return True - else: - del event['response']['conditions'] - verbose_info( - messages("send_unsuccess_event_from_module").format( - process_number, - module_name, - target, - module_thread_number, - total_module_thread_number, - request_number_counter, - total_number_of_requests - ) - ) - verbose_info( - json.dumps(event) - ) - return 'save_to_temp_events_only' in event['response'] - - -def filter_large_content(content, filter_rate=150): - from core.alert import messages - if len(content) <= filter_rate: - return content - else: - filter_rate -= 1 - filter_index = filter_rate - for char in content[filter_rate:]: - if char == ' ': - return content[0:filter_index] + messages('filtered_content') - else: - filter_index += 1 - return content - - -def get_dependent_results_from_database(target, module_name, scan_unique_id, event_names): - from database.db import find_temp_events - events = [] - for event_name in event_names.split(','): - while True: - event = find_temp_events(target, module_name, scan_unique_id, event_name) - if event: - events.append(json.loads(event.event)['response']['conditions_results']) - break - time.sleep(0.1) - return events - - -def find_and_replace_dependent_values(sub_step, dependent_on_temp_event): - if type(sub_step) == dict: - for key in copy.deepcopy(sub_step): - if type(sub_step[key]) not in [str, float, int, bytes]: - sub_step[key] = find_and_replace_dependent_values( - copy.deepcopy(sub_step[key]), dependent_on_temp_event - ) - else: - if type(sub_step[key]) == str: - if 'dependent_on_temp_event' in sub_step[key]: - globals().update(locals()) - generate_new_step = copy.deepcopy(sub_step[key]) - key_name = re.findall( - re.compile("dependent_on_temp_event\\[\\S+\\]\\['\\S+\\]\\[\\S+\\]"), - generate_new_step - )[0] - try: - key_value = eval(key_name) - except Exception: - key_value = "error" - sub_step[key] = sub_step[key].replace(key_name, key_value) - if type(sub_step) == list: - value_index = 0 - for value in copy.deepcopy(sub_step): - if type(sub_step[value_index]) not in [str, float, int, bytes]: - sub_step[key] = find_and_replace_dependent_values( - copy.deepcopy(sub_step[value_index]), dependent_on_temp_event - ) - else: - if type(sub_step[value_index]) == str: - if 'dependent_on_temp_event' in sub_step[value_index]: - globals().update(locals()) - generate_new_step = copy.deepcopy(sub_step[key]) - key_name = re.findall( - re.compile("dependent_on_temp_event\\['\\S+\\]\\[\\S+\\]"), - generate_new_step - )[0] - try: - key_value = eval(key_name) - except Exception: - key_value = "error" - sub_step[value_index] = sub_step[value_index].replace(key_name, key_value) - value_index += 1 - return sub_step - - -def replace_dependent_values(sub_step, dependent_on_temp_event): - return find_and_replace_dependent_values(sub_step, dependent_on_temp_event) - - -def replace_dependent_response(log, result): - response_dependent = result - if str(log): - key_name = re.findall( - re.compile("response_dependent\\['\\S+\\]"), - log - ) - for i in key_name: - try: - key_value = eval(i) - except Exception: - key_value = "response dependent error" - log = log.replace(i, " ".join(key_value)) - return log - - -def merge_logs_to_list(result, log_list=[]): - if type(result) == dict: - for i in result: - if 'log' == i: - log_list.append(result['log']) - else: - merge_logs_to_list(result[i], log_list) - return list(set(log_list)) - - -def reverse_and_regex_condition(regex, reverse): - if regex: - if reverse: - return [] - return list(set(regex)) - else: - if reverse: - return True - return [] - - -def select_maximum_cpu_core(mode): - if mode == 'maximum': - return int(multiprocessing.cpu_count() - 1) if int(multiprocessing.cpu_count() - 1) >= 1 else 1 - elif mode == 'high': - return int(multiprocessing.cpu_count() / 2) if int(multiprocessing.cpu_count() - 1) >= 1 else 1 - elif mode == 'normal': - return int(multiprocessing.cpu_count() / 4) if int(multiprocessing.cpu_count() - 1) >= 1 else 1 - elif mode == 'low': - return int(multiprocessing.cpu_count() / 8) if int(multiprocessing.cpu_count() - 1) >= 1 else 1 - else: - return 1 - - -def wait_for_threads_to_finish(threads, maximum=None, terminable=False, sub_process=False): - while threads: - try: - dead_threads = [] - for thread in threads: - if not thread.is_alive(): - dead_threads.append(thread) - if dead_threads: - for thread in dead_threads: - threads.remove(thread) - dead_threads = [] - if maximum and len(threads) < maximum: - break - time.sleep(0.01) - except KeyboardInterrupt: - if terminable: - for thread in threads: - terminate_thread(thread) - if sub_process: - for thread in threads: - thread.kill() - return False - return True - - -def terminate_thread(thread, verbose=True): - """ - kill a thread https://stackoverflow.com/a/15274929 - Args: - thread: an alive thread - verbose: verbose mode/boolean - Returns: - True/None - """ - from core.alert import info - if verbose: - info("killing {0}".format(thread.name)) - if not thread.is_alive(): - return - - exc = ctypes.py_object(SystemExit) - res = ctypes.pythonapi.PyThreadState_SetAsyncExc( - ctypes.c_long(thread.ident), - exc - ) - if res == 0: - raise ValueError("nonexistent thread id") - elif res > 1: - # if it returns a number greater than one, you're in trouble, - # and you should call it again with exc=NULL to revert the effect - ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None) - raise SystemError("PyThreadState_SetAsyncExc failed") - return True - - -def find_args_value(args_name): - try: - return sys.argv[sys.argv.index(args_name) + 1] - except Exception: - return None - - -def application_language(): - from config import nettacker_global_config - nettacker_global_configuration = nettacker_global_config() - if "-L" in sys.argv: - language = find_args_value('-L') or 'en' - elif "--language" in sys.argv: - language = find_args_value('--language') or 'en' - else: - language = nettacker_global_configuration['nettacker_user_application_config']['language'] - if language not in load_all_languages(): - language = 'en' - return language - - -def generate_random_token(length=10): - return "".join( - random.choice(string.ascii_lowercase) for _ in range(length) - ) - - -def re_address_repeaters_key_name(key_name): - return "".join(['[\'' + _key + '\']' for _key in key_name.split('/')[:-1]]) - - -def generate_new_sub_steps(sub_steps, data_matrix, arrays): - original_sub_steps = copy.deepcopy(sub_steps) - steps_array = [] - for array in data_matrix: - array_name_position = 0 - for array_name in arrays: - for sub_step in sub_steps: - exec( - "original_sub_steps{key_name} = {matrix_value}".format( - key_name=re_address_repeaters_key_name(array_name), - matrix_value='"' + str(array[array_name_position]) + '"' if type( - array[array_name_position]) == int or type(array[array_name_position]) == str else array[ - array_name_position] - ) - ) - array_name_position += 1 - steps_array.append(copy.deepcopy(original_sub_steps)) - return steps_array - - -def find_repeaters(sub_content, root, arrays): - if type(sub_content) == dict and 'nettacker_fuzzer' not in sub_content: - temprory_content = copy.deepcopy(sub_content) - original_root = root - for key in sub_content: - root = original_root - root += key + '/' - temprory_content[key], _root, arrays = find_repeaters(sub_content[key], root, arrays) - sub_content = copy.deepcopy(temprory_content) - root = original_root - if (type(sub_content) not in [bool, int, float]) and ( - type(sub_content) == list or 'nettacker_fuzzer' in sub_content): - arrays[root] = sub_content - return (sub_content, root, arrays) if root != '' else arrays - - -def find_and_replace_configuration_keys(module_content, module_inputs): - if type(module_content) == dict: - for key in copy.deepcopy(module_content): - if key in module_inputs: - if module_inputs[key]: - module_content[key] = module_inputs[key] - elif type(module_content[key]) in [dict, list]: - module_content[key] = find_and_replace_configuration_keys(module_content[key], module_inputs) - elif type(module_content) == list: - array_index = 0 - for key in copy.deepcopy(module_content): - module_content[array_index] = find_and_replace_configuration_keys(key, module_inputs) - array_index += 1 - else: - return module_content - return module_content - - -class value_to_class: - def __init__(self, value): - self.value = value - - -def class_to_value(arrays): - original_arrays = copy.deepcopy(arrays) - array_index = 0 - for array in arrays: - value_index = 0 - for value in array: - if type(value) == value_to_class: - original_arrays[array_index][value_index] = value.value - value_index += 1 - array_index += 1 - return original_arrays - - -def generate_and_replace_md5(content): - # todo: make it betetr and document it - md5_content = content.split('NETTACKER_MD5_GENERATOR_START')[1].split('NETTACKER_MD5_GENERATOR_STOP')[0] - md5_content_backup = md5_content - if type(md5_content) == str: - md5_content = md5_content.encode() - md5_hash = hashlib.md5(md5_content).hexdigest() - return content.replace( - 'NETTACKER_MD5_GENERATOR_START' + md5_content_backup + 'NETTACKER_MD5_GENERATOR_STOP', - md5_hash - ) - - -def arrays_to_matrix(arrays): - import numpy - return numpy.array( - numpy.meshgrid(*[ - arrays[array_name] for array_name in arrays - ]) - ).T.reshape( - -1, - len(arrays.keys()) - ).tolist() - - -def string_to_bytes(string): - return string.encode() - - -def fuzzer_function_read_file_as_array(filename): - from config import nettacker_paths - return open( - os.path.join( - nettacker_paths()['payloads_path'], - filename - ) - ).read().split('\n') - - -def apply_data_functions(data): - original_data = copy.deepcopy(data) - function_results = {} - globals().update(locals()) - for data_name in data: - if type(data[data_name]) == str and data[data_name].startswith('fuzzer_function'): - exec( - "fuzzer_function = {fuzzer_function}".format( - fuzzer_function=data[data_name] - ), - globals(), - function_results - ) - original_data[data_name] = function_results['fuzzer_function'] - return original_data - - -def nettacker_fuzzer_repeater_perform(arrays): - original_arrays = copy.deepcopy(arrays) - for array_name in arrays: - if 'nettacker_fuzzer' in arrays[array_name]: - data = arrays[array_name]['nettacker_fuzzer']['data'] - data_matrix = arrays_to_matrix(apply_data_functions(data)) - prefix = arrays[array_name]['nettacker_fuzzer']['prefix'] - input_format = arrays[array_name]['nettacker_fuzzer']['input_format'] - interceptors = copy.deepcopy(arrays[array_name]['nettacker_fuzzer']['interceptors']) - if interceptors: - interceptors = interceptors.split(',') - suffix = arrays[array_name]['nettacker_fuzzer']['suffix'] - processed_array = [] - for sub_data in data_matrix: - formatted_data = {} - index_input = 0 - for value in sub_data: - formatted_data[list(data.keys())[index_input]] = value - index_input += 1 - interceptors_function = '' - interceptors_function_processed = '' - if interceptors: - interceptors_function += 'interceptors_function_processed = ' - for interceptor in interceptors[::-1]: - interceptors_function += '{interceptor}('.format(interceptor=interceptor) - interceptors_function += 'input_format.format(**formatted_data)' + str( - ')' * interceptors_function.count('(')) - expected_variables = {} - globals().update(locals()) - exec(interceptors_function, globals(), expected_variables) - interceptors_function_processed = expected_variables['interceptors_function_processed'] - else: - interceptors_function_processed = input_format.format(**formatted_data) - processed_sub_data = interceptors_function_processed - if prefix: - processed_sub_data = prefix + processed_sub_data - if suffix: - processed_sub_data = processed_sub_data + suffix - processed_array.append(copy.deepcopy(processed_sub_data)) - original_arrays[array_name] = processed_array - return original_arrays - - -def expand_module_steps(content): - return [expand_protocol(x) for x in copy.deepcopy(content)] - - -def expand_protocol(protocol): - protocol['steps'] = [expand_step(x) for x in protocol['steps']] - return protocol - - -def expand_step(step): - arrays = nettacker_fuzzer_repeater_perform(find_repeaters(step, '', {})) - if arrays: - return generate_new_sub_steps(step, class_to_value(arrays_to_matrix(arrays)), arrays) - else: - # Minimum 1 step in array - return [step] - - -def sort_dictionary(dictionary): - etc_flag = '...' in dictionary - if etc_flag: - del dictionary['...'] - sorted_dictionary = {} - for key in sorted(dictionary): - sorted_dictionary[key] = dictionary[key] - if etc_flag: - sorted_dictionary['...'] = {} - return sorted_dictionary diff --git a/database/__init__.py b/database/__init__.py deleted file mode 100644 index 6f334996f..000000000 --- a/database/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -pass diff --git a/database/mysql_create.py b/database/mysql_create.py deleted file mode 100644 index 348e3cd14..000000000 --- a/database/mysql_create.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from sqlalchemy import create_engine - -from config import nettacker_database_config -from database.models import Base - - -USER = nettacker_database_config()["USERNAME"] -PASSWORD = nettacker_database_config()["PASSWORD"] -HOST = nettacker_database_config()["HOST"] -PORT = nettacker_database_config()["PORT"] -DATABASE = nettacker_database_config()["DATABASE"] - - -def mysql_create_database(): - """ - when using mysql database, this is the function that is used to create the database for the first time when you run - the nettacker module. - - Args: - None - - Returns: - True if success otherwise False - """ - try: - engine = create_engine('mysql://{0}:{1}@{2}:{3}'.format(USER, PASSWORD, HOST, PORT)) - existing_databases = engine.execute("SHOW DATABASES;") - existing_databases = [ - d[0] for d in existing_databases - ] - if DATABASE not in existing_databases: - engine.execute("CREATE DATABASE {0} ".format(DATABASE)) - return True - except Exception: - return False - - -def mysql_create_tables(): - """ - when using mysql database, this is the function that is used to create the tables in the database for the first - time when you run the nettacker module. - - Args: - None - - Returns: - True if success otherwise False - """ - try: - db_engine = create_engine('mysql://{0}:{1}@{2}:{3}/{4}'.format(USER, PASSWORD, HOST, PORT, DATABASE)) - Base.metadata.create_all(db_engine) - return True - except Exception: - return False \ No newline at end of file diff --git a/database/postgres_create.py b/database/postgres_create.py deleted file mode 100644 index b5bf6a77f..000000000 --- a/database/postgres_create.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from sqlalchemy import create_engine -from config import nettacker_database_config -from database.models import Base -from sqlalchemy.exc import OperationalError - -USER = nettacker_database_config()["USERNAME"] -PASSWORD = nettacker_database_config()["PASSWORD"] -HOST = nettacker_database_config()["HOST"] -PORT = nettacker_database_config()["PORT"] -DATABASE = nettacker_database_config()["DATABASE"] - - -def postgres_create_database(): - """ - when using postgres database, this is the function that is used to create the database for the first time when you - the nettacker run module. - - Args: - None - - Returns: - True if success otherwise False - """ - - try: - engine = create_engine( - 'postgres+psycopg2://{0}:{1}@{2}:{3}/{4}'.format(USER, PASSWORD, HOST, PORT, DATABASE) - ) - Base.metadata.create_all(engine) - return True - except OperationalError: - # if the database does not exist - engine = create_engine( - "postgres+psycopg2://postgres:postgres@localhost/postgres") - conn = engine.connect() - conn.execute("commit") - conn.execute('CREATE DATABASE {0}'.format(DATABASE)) - conn.close() - engine = create_engine( - 'postgres+psycopg2://{0}:{1}@{2}:{3}/{4}'.format( - USER, - PASSWORD, - HOST, - PORT, - DATABASE - ) - ) - Base.metadata.create_all(engine) - except Exception: - return False diff --git a/database/sqlite_create.py b/database/sqlite_create.py deleted file mode 100644 index 5bb272d0b..000000000 --- a/database/sqlite_create.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from sqlalchemy import create_engine - -from database.models import Base -from config import nettacker_database_config - -DATABASE = nettacker_database_config()["DATABASE"] - - -def sqlite_create_tables(): - """ - when using sqlite database, this is the function that is used to create the database schema for the first time when - you run the nettacker module. - - Args: - None - - Returns: - True if success otherwise False - """ - try: - db_engine = create_engine( - 'sqlite:///{0}'.format(DATABASE), - connect_args={ - 'check_same_thread': False - } - ) - Base.metadata.create_all(db_engine) - return True - except Exception: - return False diff --git a/docker-compose.yml b/docker-compose.yml index 7da8faa49..16cccb42f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: build: context: . dockerfile: "Dockerfile" - command: python3 nettacker.py --start-api --api-host 0.0.0.0 + command: poetry run python nettacker.py --start-api --api-host 0.0.0.0 ports: - 5000:5000 volumes: diff --git a/lib/__init__.py b/lib/__init__.py deleted file mode 100644 index 6f334996f..000000000 --- a/lib/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -pass diff --git a/lib/graph/__init__.py b/lib/graph/__init__.py deleted file mode 100644 index 6f334996f..000000000 --- a/lib/graph/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -pass diff --git a/lib/graph/d3_tree_v1/__init__.py b/lib/graph/d3_tree_v1/__init__.py deleted file mode 100644 index 6f334996f..000000000 --- a/lib/graph/d3_tree_v1/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -pass diff --git a/lib/graph/d3_tree_v1/engine.py b/lib/graph/d3_tree_v1/engine.py deleted file mode 100644 index 051c6b238..000000000 --- a/lib/graph/d3_tree_v1/engine.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import json -import os -from core.alert import messages - - -def start(events): - """ - generate the d3_tree_v1_graph with events - - Args: - events: all events - - Returns: - a graph in HTML - """ - - # define a normalised_json - normalisedjson = { - "name": "Started attack", - "children": {} - } - # get data for normalised_json - for event in events: - if event['target'] not in normalisedjson['children']: - normalisedjson['children'].update( - { - event['target']: {} - } - ) - normalisedjson['children'][event['target']].update( - { - event['module_name']: [] - } - ) - - if event['module_name'] not in normalisedjson['children'][event['target']]: - normalisedjson['children'][event['target']].update( - { - event['module_name']: [] - } - ) - normalisedjson['children'][event['target']][event['module_name']].append( - f"target: {event['target']}, module_name: {event['module_name']}, port: " - f"{event['port']}, event: {event['event']}" - ) - # define a d3_structure_json - d3_structure = { - "name": "Starting attack", - "children": [] - } - # get data for normalised_json - for target in list(normalisedjson['children'].keys()): - for module_name in list(normalisedjson['children'][target].keys()): - for description in normalisedjson["children"][target][module_name]: - children_array = [ - { - "name": module_name, - "children": [ - { - "name": description - } - ] - } - ] - d3_structure["children"].append( - { - "name": target, - "children": children_array - } - ) - - from config import nettacker_paths - data = open( - os.path.join( - nettacker_paths()['web_static_files_path'], - 'report/d3_tree_v1.html' - ) - ).read().replace( - '__data_will_locate_here__', - json.dumps(d3_structure) - ).replace( - '__title_to_replace__', - messages("pentest_graphs") - ).replace( - '__description_to_replace__', - messages("graph_message") - ).replace( - '__html_title_to_replace__', - messages("nettacker_report") - ) - return data diff --git a/lib/graph/d3_tree_v2/__init__.py b/lib/graph/d3_tree_v2/__init__.py deleted file mode 100644 index 6f334996f..000000000 --- a/lib/graph/d3_tree_v2/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -pass diff --git a/lib/html_log/__init__.py b/lib/html_log/__init__.py deleted file mode 100644 index 6f334996f..000000000 --- a/lib/html_log/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -pass diff --git a/lib/html_log/log_data.py b/lib/html_log/log_data.py deleted file mode 100644 index 3be8dade1..000000000 --- a/lib/html_log/log_data.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import os -from config import nettacker_paths - -css_1 = open( - os.path.join( - nettacker_paths()['web_static_files_path'], - 'report/html_table.css' - ) -).read() - -json_parse_js = open( - os.path.join( - nettacker_paths()['web_static_files_path'], - 'report/json_parse.js' - ) -).read() - -table_title = open( - os.path.join( - nettacker_paths()['web_static_files_path'], - 'report/table_title.html' - ) -).read() - -table_items = open( - os.path.join( - nettacker_paths()['web_static_files_path'], - 'report/table_items.html' - ) -).read() - -table_end = open( - os.path.join( - nettacker_paths()['web_static_files_path'], - 'report/table_end.html' - ) -).read() diff --git a/lib/icmp/__init__.py b/lib/icmp/__init__.py deleted file mode 100644 index 6f334996f..000000000 --- a/lib/icmp/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -pass diff --git a/lib/payloads/__init__.py b/lib/payloads/__init__.py deleted file mode 100644 index 6f334996f..000000000 --- a/lib/payloads/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -pass diff --git a/logo.txt b/logo.txt deleted file mode 100644 index 8128abe23..000000000 --- a/logo.txt +++ /dev/null @@ -1,15 +0,0 @@ - - ______ __ _____ _____ - / __ \ \ / /\ / ____| __ \ - | | | \ \ /\ / / \ | (___ | |__) | - | | | |\ \/ \/ / /\ \ \___ \| ___/ - | |__| | \ /\ / ____ \ ____) | | {2}Version {0}{3} - \____/ \/ \/_/ \_\_____/|_| {4}{1}{5} - _ _ _ _ _ - | \ | | | | | | | | - {6}github.com/OWASP {7} | \| | ___| |_| |_ __ _ ___| | _____ _ __ - {8}owasp.org{9} | . ` |/ _ \ __| __/ _` |/ __| |/ / _ \ '__| - {10}z3r0d4y.com{11} | |\ | __/ |_| || (_| | (__| < __/ | - |_| \_|\___|\__|\__\__,_|\___|_|\_\___|_| - - diff --git a/nettacker.py b/nettacker.py index 1848335d3..ac8bbac5d 100644 --- a/nettacker.py +++ b/nettacker.py @@ -1,18 +1,7 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- +"""OWASP Nettacker application entry point.""" -from core.compatible import check_dependencies +from nettacker.core.app import Nettacker -""" -entry point of OWASP Nettacker framework -""" - -# __check_external_modules created to check requirements before load the engine if __name__ == "__main__": - check_dependencies() # check for dependencies - - # if dependencies and OS requirements are match then load the program - from core.parse import load - - load() # load and parse the ARGV - # sys.exit(main()) + app = Nettacker() + app.run() diff --git a/nettacker/__init__.py b/nettacker/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nettacker/api/__init__.py b/nettacker/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/api/api_core.py b/nettacker/api/core.py similarity index 67% rename from api/api_core.py rename to nettacker/api/core.py index 55463dc1a..82163c1e0 100644 --- a/api/api_core.py +++ b/nettacker/api/core.py @@ -1,29 +1,11 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - import os -from core.load_modules import load_all_modules, load_all_profiles -from core.load_modules import load_all_graphs -from core.alert import messages -from flask import abort -from config import nettacker_paths +from flask import abort -def structure(status="", msg=""): - """ - basic JSON message structure - - Args: - status: status (ok, failed) - msg: the message content - - Returns: - a JSON message - """ - return { - "status": status, - "msg": msg - } +from nettacker.config import Config +from nettacker.core.app import Nettacker +from nettacker.core.messages import messages as _ +from nettacker.core.messages import get_languages def get_value(flask_request, key): @@ -37,13 +19,12 @@ def get_value(flask_request, key): Returns: the value content if found otherwise None """ - return dict( - flask_request.args - ).get(key) or dict( - flask_request.form - ).get(key) or dict( - flask_request.cookies - ).get(key) or "" + return ( + dict(flask_request.args).get(key) + or dict(flask_request.form).get(key) + or dict(flask_request.cookies).get(key) + or "" + ) def mime_types(): @@ -54,6 +35,9 @@ def mime_types(): all mime types in json """ return { + ".3g2": "video/3gpp2", + ".3gp": "video/3gpp", + ".7z": "application/x-7z-compressed", ".aac": "audio/aac", ".abw": "application/x-abiword", ".arc": "application/octet-stream", @@ -90,8 +74,8 @@ def mime_types(): ".ogv": "video/ogg", ".ogx": "application/ogg", ".otf": "font/otf", - ".png": "image/png", ".pdf": "application/pdf", + ".png": "image/png", ".ppt": "application/vnd.ms-powerpoint", ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", ".rar": "application/x-rar-compressed", @@ -118,11 +102,8 @@ def mime_types(): ".xml": "application/xml", ".xul": "application/vnd.mozilla.xul+xml", ".zip": "application/zip", - ".3gp": "video/3gpp", "audio/3gpp": "video", - ".3g2": "video/3gpp2", "audio/3gpp2": "video", - ".7z": "application/x-7z-compressed" } @@ -136,7 +117,7 @@ def get_file(filename): Returns: content of the file or abort(404) """ - if not os.path.normpath(filename).startswith(nettacker_paths()["web_static_files_path"]): + if not os.path.normpath(filename).startswith(str(Config.path.web_static_dir)): abort(404) try: return open(filename, "rb").read() @@ -159,7 +140,7 @@ def api_key_is_valid(app, flask_request): """ if app.config["OWASP_NETTACKER_CONFIG"]["api_access_key"] != get_value(flask_request, "key"): - abort(401, messages("API_invalid")) + abort(401, _("API_invalid")) return @@ -170,40 +151,37 @@ def languages_to_country(): Returns: HTML code for each language with its country flag """ - from core.load_modules import load_all_languages - languages = load_all_languages() + languages = get_languages() res = "" flags = { + "ar": "sa", + "bn": "in", + "de": "de", "el": "gr", - "fr": "fr", "en": "us", - "nl": "nl", - "ps": "ps", - "tr": "tr", - "de": "de", - "ko": "kr", - "it": "it", - "ja": "jp", + "es": "es", "fa": "ir", - "hy": "am", - "ar": "sa", - "zh-cn": "cn", - "vi": "vi", - "ru": "ru", + "fr": "fr", "hi": "in", - "ur": "pk", + "hy": "am", "id": "id", - "es": "es", + "it": "it", "iw": "il", + "ja": "jp", + "ko": "kr", + "nl": "nl", + "ps": "ps", "pt-br": "br", - "bn": "in" + "ru": "ru", + "tr": "tr", + "ur": "pk", + "vi": "vi", + "zh-cn": "cn", } for language in languages: - res += """""".format( - language, - flags[language], - "selected" if language == "en" else "" + language, flags[language], "selected" if language == "en" else "" ) return res @@ -215,11 +193,15 @@ def graphs(): Returns: HTML content or available graphs """ - res = """     """ - for graph in load_all_graphs(): - res += """     """.format(graph) + res = """ +      """ + for graph in Nettacker.load_graphs(): + res += """ +      """.format( + graph + ) return res @@ -231,16 +213,21 @@ def profiles(): HTML content or available profiles """ res = "" - for profile in sorted(load_all_profiles().keys()): - label = "success" if ( - profile == "scan" - ) else "warning" if ( - profile == "brute" - ) else "danger" if ( - profile == "vulnerability" - ) else "default" - res += """     """.format(profile, label) + for profile in sorted(Nettacker.load_profiles().keys()): + label = ( + "success" + if (profile == "scan") + else "warning" + if (profile == "brute") + else "danger" + if (profile == "vulnerability") + else "default" + ) + res += """ +      """.format( + profile, label + ) return res @@ -251,24 +238,30 @@ def scan_methods(): Returns: HTML content or available modules """ - methods = load_all_modules() + methods = Nettacker.load_modules() methods.pop("all") res = "" for sm in methods.keys(): - label = "success" if sm.endswith( - "_scan" - ) else "warning" if sm.endswith( - "_brute" - ) else "danger" if sm.endswith( - "_vuln" - ) else "default" - profile = "scan" if sm.endswith( - "_scan" - ) else "brute" if sm.endswith( - "_brute" - ) else "vuln" if sm.endswith( - "_vuln" - ) else "default" + label = ( + "success" + if sm.endswith("_scan") + else "warning" + if sm.endswith("_brute") + else "danger" + if sm.endswith("_vuln") + else "default" + ) + profile = ( + "scan" + if sm.endswith("_scan") + else "brute" + if sm.endswith("_brute") + else "vuln" + if sm.endswith("_vuln") + else "default" + ) res += """     """.format(sm, label, profile) + {0}     """.format( + sm, label, profile + ) return res diff --git a/api/database.sqlite3 b/nettacker/api/database.sqlite3 similarity index 100% rename from api/database.sqlite3 rename to nettacker/api/database.sqlite3 diff --git a/api/engine.py b/nettacker/api/engine.py similarity index 56% rename from api/engine.py rename to nettacker/api/engine.py index ca183bb89..eead7f0a0 100644 --- a/api/engine.py +++ b/nettacker/api/engine.py @@ -1,55 +1,54 @@ -# !/usr/bin/env python -# -*- coding: utf-8 -*- - -import multiprocessing -import time -import random import csv import json -import string +import multiprocessing import os -import copy +import random +import string +import time +from threading import Thread from types import SimpleNamespace -from database.db import create_connection, get_logs_by_scan_unique_id -from database.models import Report -from flask import Flask -from flask import jsonify + +from flask import Flask, jsonify from flask import request as flask_request -from flask import render_template -from flask import abort -from flask import Response -from flask import make_response -from core.alert import write_to_api_console -from core.alert import messages -from core.die import die_success, die_failure -from core.time import now -from api.api_core import structure -from api.api_core import get_value -from api.api_core import get_file -from api.api_core import mime_types -from api.api_core import scan_methods -from api.api_core import profiles -from api.api_core import graphs -from api.api_core import languages_to_country -from api.api_core import api_key_is_valid -from database.db import select_reports -from database.db import get_scan_result -from database.db import last_host_logs -from database.db import logs_to_report_json -from database.db import search_logs -from database.db import logs_to_report_html -from config import nettacker_global_config -from core.scan_targets import start_scan_processes -from core.args_loader import check_all_required - -app = Flask( - __name__, - template_folder=nettacker_global_config()['nettacker_paths']['web_static_files_path'] +from flask import render_template, abort, Response, make_response + +from nettacker import logger +from nettacker.api.core import ( + get_value, + get_file, + mime_types, + scan_methods, + profiles, + graphs, + languages_to_country, + api_key_is_valid, ) +from nettacker.api.helpers import structure +from nettacker.config import Config +from nettacker.core.app import Nettacker +from nettacker.core.die import die_failure +from nettacker.core.messages import messages as _ +from nettacker.core.utils.common import now +from nettacker.database.db import ( + create_connection, + get_logs_by_scan_id, + select_reports, + get_scan_result, + last_host_logs, + logs_to_report_json, + search_logs, + logs_to_report_html, +) +from nettacker.database.models import Report + +log = logger.get_logger() + +app = Flask(__name__, template_folder=str(Config.path.web_static_dir)) app.config.from_object(__name__) -nettacker_application_config = nettacker_global_config()['nettacker_user_application_config'] -nettacker_application_config.update(nettacker_global_config()['nettacker_api_config']) -del nettacker_application_config['api_access_key'] + +nettacker_application_config = Config.settings.as_dict() +nettacker_application_config.update(Config.api.as_dict()) +del nettacker_application_config["api_access_key"] @app.errorhandler(400) @@ -63,12 +62,7 @@ def error_400(error): Returns: 400 JSON error """ - return jsonify( - structure( - status="error", - msg=error.description - ) - ), 400 + return jsonify(structure(status="error", msg=error.description)), 400 @app.errorhandler(401) @@ -82,12 +76,7 @@ def error_401(error): Returns: 401 JSON error """ - return jsonify( - structure( - status="error", - msg=error.description - ) - ), 401 + return jsonify(structure(status="error", msg=error.description)), 401 @app.errorhandler(403) @@ -101,12 +90,7 @@ def error_403(error): Returns: 403 JSON error """ - return jsonify( - structure( - status="error", - msg=error.description - ) - ), 403 + return jsonify(structure(status="error", msg=error.description)), 403 @app.errorhandler(404) @@ -120,12 +104,7 @@ def error_404(error): Returns: 404 JSON error """ - return jsonify( - structure( - status="error", - msg=messages("not_found") - ) - ), 404 + return jsonify(structure(status="error", msg=_("not_found"))), 404 @app.before_request @@ -138,8 +117,11 @@ def limit_remote_addr(): """ # IP Limitation if app.config["OWASP_NETTACKER_CONFIG"]["api_client_whitelisted_ips"]: - if flask_request.remote_addr not in app.config["OWASP_NETTACKER_CONFIG"]["api_client_whitelisted_ips"]: - abort(403, messages("unauthorized_IP")) + if ( + flask_request.remote_addr + not in app.config["OWASP_NETTACKER_CONFIG"]["api_client_whitelisted_ips"] + ): + abort(403, _("unauthorized_IP")) return @@ -155,12 +137,9 @@ def access_log(response): the flask response """ if app.config["OWASP_NETTACKER_CONFIG"]["api_access_log"]: - log_request = open( - app.config["OWASP_NETTACKER_CONFIG"]["api_access_log"], - "ab" - ) + log_request = open(app.config["OWASP_NETTACKER_CONFIG"]["api_access_log"], "ab") log_request.write( - "{0} [{1}] {2} \"{3} {4}\" {5} {6} {7}\r\n".format( + '{0} [{1}] {2} "{3} {4}" {5} {6} {7}\r\n'.format( flask_request.remote_addr, now(), flask_request.host, @@ -168,7 +147,7 @@ def access_log(response): flask_request.full_path, flask_request.user_agent, response.status_code, - json.dumps(flask_request.form) + json.dumps(flask_request.form), ).encode() ) log_request.close() @@ -188,16 +167,8 @@ def get_statics(path): """ static_types = mime_types() return Response( - get_file( - os.path.join( - nettacker_global_config()['nettacker_paths']['web_static_files_path'], - path - ) - ), - mimetype=static_types.get( - os.path.splitext(path)[1], - "text/html" - ) + get_file(os.path.join(Config.path.web_static_dir, path)), + mimetype=static_types.get(os.path.splitext(path)[1], "text/html"), ) @@ -209,15 +180,13 @@ def index(): Returns: rendered HTML page """ - from config import nettacker_user_application_config - filename = nettacker_user_application_config()["report_path_filename"] return render_template( "index.html", selected_modules=scan_methods(), profile=profiles(), languages=languages_to_country(), graphs=graphs(), - filename=filename + filename=Config.settings.report_path_filename, ) @@ -234,18 +203,13 @@ def new_scan(): for key in nettacker_application_config: if key not in form_values: form_values[key] = nettacker_application_config[key] - options = check_all_required( - None, - api_forms=SimpleNamespace(**copy.deepcopy(form_values)) - ) - app.config["OWASP_NETTACKER_CONFIG"]["options"] = options - new_process = multiprocessing.Process(target=start_scan_processes, args=(options,)) - new_process.start() - return jsonify( - vars( - options - ) - ), 200 + + nettacker_app = Nettacker(api_arguments=SimpleNamespace(**form_values)) + app.config["OWASP_NETTACKER_CONFIG"]["options"] = nettacker_app.arguments + thread = Thread(target=nettacker_app.run) + thread.start() + + return jsonify(vars(nettacker_app.arguments)), 200 @app.route("/session/check", methods=["GET"]) @@ -257,12 +221,7 @@ def session_check(): a JSON message if it's valid otherwise abort(401) """ api_key_is_valid(app, flask_request) - return jsonify( - structure( - status="ok", - msg=messages("browser_session_valid") - ) - ), 200 + return jsonify(structure(status="ok", msg=_("browser_session_valid"))), 200 @app.route("/session/set", methods=["GET", "POST"]) @@ -275,14 +234,7 @@ def session_set(): response if success otherwise abort(403) """ api_key_is_valid(app, flask_request) - res = make_response( - jsonify( - structure( - status="ok", - msg=messages("browser_session_valid") - ) - ) - ) + res = make_response(jsonify(structure(status="ok", msg=_("browser_session_valid")))) res.set_cookie("key", value=app.config["OWASP_NETTACKER_CONFIG"]["api_access_key"]) return res @@ -296,14 +248,7 @@ def session_kill(): a 200 HTTP response with set-cookie to "expired" to unset the cookie on the browser """ - res = make_response( - jsonify( - structure( - status="ok", - msg=messages("browser_session_killed") - ) - ) - ) + res = make_response(jsonify(structure(status="ok", msg=_("browser_session_killed")))) res.set_cookie("key", "", expires=0) return res @@ -320,11 +265,7 @@ def get_results(): page = get_value(flask_request, "page") if not page: page = 1 - return jsonify( - select_reports( - int(page) - ) - ), 200 + return jsonify(select_reports(int(page))), 200 @app.route("/results/get", methods=["GET"]) @@ -338,22 +279,17 @@ def get_result_content(): api_key_is_valid(app, flask_request) scan_id = get_value(flask_request, "id") if not scan_id: - return jsonify( - structure( - status="error", - msg=messages("invalid_scan_id") - ) - ), 400 - filename, file_content = get_scan_result(scan_id) + return jsonify(structure(status="error", msg=_("invalid_scan_id"))), 400 + + try: + filename, file_content = get_scan_result(scan_id) + except Exception: + return jsonify(structure(status="error", msg="database error!")), 500 + return Response( file_content, - mimetype=mime_types().get( - os.path.splitext(filename)[1], - "text/plain" - ), - headers={ - 'Content-Disposition': 'attachment;filename=' + filename.split('/')[-1] - } + mimetype=mime_types().get(os.path.splitext(filename)[1], "text/plain"), + headers={"Content-Disposition": "attachment;filename=" + filename.split("/")[-1]}, ) @@ -369,25 +305,14 @@ def get_results_json(): session = create_connection() result_id = get_value(flask_request, "id") if not result_id: - return jsonify( - structure( - status="error", - msg=messages("invalid_scan_id") - ) - ), 400 + return jsonify(structure(status="error", msg=_("invalid_scan_id"))), 400 scan_details = session.query(Report).filter(Report.id == result_id).first() - json_object = json.dumps( - get_logs_by_scan_unique_id( - scan_details.scan_unique_id - ) - ) - filename = ".".join(scan_details.report_path_filename.split('.')[:-1])[1:] + '.json' + json_object = json.dumps(get_logs_by_scan_id(scan_details.scan_unique_id)) + filename = ".".join(scan_details.report_path_filename.split(".")[:-1])[1:] + ".json" return Response( json_object, - mimetype='application/json', - headers={ - 'Content-Disposition': 'attachment;filename=' + filename - } + mimetype="application/json", + headers={"Content-Disposition": "attachment;filename=" + filename}, ) @@ -403,37 +328,22 @@ def get_results_csv(): # todo: need to fix time format session = create_connection() result_id = get_value(flask_request, "id") if not result_id: - return jsonify( - structure( - status="error", - msg=messages("invalid_scan_id") - ) - ), 400 + return jsonify(structure(status="error", msg=_("invalid_scan_id"))), 400 scan_details = session.query(Report).filter(Report.id == result_id).first() - data = get_logs_by_scan_unique_id(scan_details.scan_unique_id) + data = get_logs_by_scan_id(scan_details.scan_unique_id) keys = data[0].keys() - filename = ".".join(scan_details.report_path_filename.split('.')[:-1])[1:] + '.csv' + filename = ".".join(scan_details.report_path_filename.split(".")[:-1])[1:] + ".csv" with open(filename, "w") as report_path_filename: - dict_writer = csv.DictWriter( - report_path_filename, - fieldnames=keys, - quoting=csv.QUOTE_ALL - ) + dict_writer = csv.DictWriter(report_path_filename, fieldnames=keys, quoting=csv.QUOTE_ALL) dict_writer.writeheader() for event in data: - dict_writer.writerow( - { - key: value for key, value in event.items() if key in keys - } - ) - with open(filename, 'r') as report_path_filename: + dict_writer.writerow({key: value for key, value in event.items() if key in keys}) + with open(filename, "r") as report_path_filename: reader = report_path_filename.read() return Response( reader, - mimetype='text/csv', - headers={ - 'Content-Disposition': 'attachment;filename=' + filename - } + mimetype="text/csv", + headers={"Content-Disposition": "attachment;filename=" + filename}, ) @@ -449,11 +359,7 @@ def get_last_host_logs(): # need to check page = get_value(flask_request, "page") if not page: page = 1 - return jsonify( - last_host_logs( - int(page) - ) - ), 200 + return jsonify(last_host_logs(int(page))), 200 @app.route("/logs/get_html", methods=["GET"]) @@ -466,9 +372,7 @@ def get_logs_html(): # todo: check until here - ali """ api_key_is_valid(app, flask_request) target = get_value(flask_request, "target") - return make_response( - logs_to_report_html(target) - ) + return make_response(logs_to_report_html(target)) @app.route("/logs/get_json", methods=["GET"]) @@ -483,17 +387,15 @@ def get_logs(): target = get_value(flask_request, "target") data = logs_to_report_json(target) json_object = json.dumps(data) - filename = "report-" + now( - model="%Y_%m_%d_%H_%M_%S" - ) + "".join( - random.choice(string.ascii_lowercase) for _ in range(10) + filename = ( + "report-" + + now(format="%Y_%m_%d_%H_%M_%S") + + "".join(random.choice(string.ascii_lowercase) for _ in range(10)) ) return Response( json_object, - mimetype='application/json', - headers={ - 'Content-Disposition': 'attachment;filename=' + filename + '.json' - } + mimetype="application/json", + headers={"Content-Disposition": "attachment;filename=" + filename + ".json"}, ) @@ -509,33 +411,22 @@ def get_logs_csv(): target = get_value(flask_request, "target") data = logs_to_report_json(target) keys = data[0].keys() - filename = "report-" + now( - model="%Y_%m_%d_%H_%M_%S" - ) + "".join( - random.choice( - string.ascii_lowercase - ) for _ in range(10) + filename = ( + "report-" + + now(format="%Y_%m_%d_%H_%M_%S") + + "".join(random.choice(string.ascii_lowercase) for _ in range(10)) ) with open(filename, "w") as report_path_filename: - dict_writer = csv.DictWriter( - report_path_filename, - fieldnames=keys, - quoting=csv.QUOTE_ALL - ) + dict_writer = csv.DictWriter(report_path_filename, fieldnames=keys, quoting=csv.QUOTE_ALL) dict_writer.writeheader() for event in data: - dict_writer.writerow( - { - key: value for key, value in event.items() if key in keys - } - ) - with open(filename, 'r') as report_path_filename: + dict_writer.writerow({key: value for key, value in event.items() if key in keys}) + with open(filename, "r") as report_path_filename: reader = report_path_filename.read() return Response( - reader, mimetype='text/csv', - headers={ - 'Content-Disposition': 'attachment;filename=' + filename + '.csv' - } + reader, + mimetype="text/csv", + headers={"Content-Disposition": f"attachment;filename={filename}.csv"}, ) @@ -576,7 +467,7 @@ def start_api_subprocess(options): "api_cert": options.api_cert, "api_cert_key": options.api_cert_key, "language": options.language, - "options": options + "options": options, } try: if options.api_cert and options.api_cert_key: @@ -584,19 +475,16 @@ def start_api_subprocess(options): host=options.api_hostname, port=options.api_port, debug=options.api_debug_mode, - ssl_context=( - options.api_cert, - options.api_cert_key - ), - threaded=True + ssl_context=(options.api_cert, options.api_cert_key), + threaded=True, ) else: app.run( host=options.api_hostname, port=options.api_port, debug=options.api_debug_mode, - ssl_context='adhoc', - threaded=True + ssl_context="adhoc", + threaded=True, ) except Exception as e: die_failure(str(e)) @@ -610,16 +498,8 @@ def start_api_server(options): options: all options """ # Starting the API - write_to_api_console( - messages("API_key").format( - options.api_port, - options.api_access_key - ) - ) - p = multiprocessing.Process( - target=start_api_subprocess, - args=(options,) - ) + log.write_to_api_console(_("API_key").format(options.api_port, options.api_access_key)) + p = multiprocessing.Process(target=start_api_subprocess, args=(options,)) p.start() # Sometimes it's take much time to terminate flask with CTRL+C # So It's better to use KeyboardInterrupt to terminate! @@ -630,4 +510,3 @@ def start_api_server(options): for process in multiprocessing.active_children(): process.terminate() break - die_success() diff --git a/nettacker/api/helpers.py b/nettacker/api/helpers.py new file mode 100644 index 000000000..22a9b229a --- /dev/null +++ b/nettacker/api/helpers.py @@ -0,0 +1,12 @@ +def structure(status="", msg=""): + """ + basic JSON message structure + + Args: + status: status (ok, failed) + msg: the message content + + Returns: + a JSON message + """ + return {"status": status, "msg": msg} diff --git a/api/readme.md b/nettacker/api/readme.md similarity index 100% rename from api/readme.md rename to nettacker/api/readme.md diff --git a/nettacker/config.py b/nettacker/config.py new file mode 100644 index 000000000..3f7b5a732 --- /dev/null +++ b/nettacker/config.py @@ -0,0 +1,157 @@ +import inspect +from functools import lru_cache +from pathlib import Path + +import tomli + +from nettacker.core.utils.common import now, generate_random_token + +CWD = Path.cwd() +PACKAGE_PATH = Path(__file__).parent + + +@lru_cache(maxsize=128) +def version_info(): + """ + version information of the framework + + Returns: + an array of version and code name + """ + with open("pyproject.toml", "rb") as toml_file: + tools = tomli.load(toml_file)["tool"] + + return tools["poetry"]["version"], tools["nettacker"]["release_name"] + + +class ConfigBase: + @classmethod + def as_dict(cls): + return {attr_name: getattr(cls, attr_name) for attr_name in cls()} + + def __init__(self) -> None: + self.attributes = sorted( + ( + attribute[0] + for attribute in inspect.getmembers(self) + if not attribute[0].startswith("_") and not inspect.ismethod(attribute[1]) + ) + ) + self.idx = 0 + + def __iter__(self): + yield from self.attributes + + +class ApiConfig(ConfigBase): + """OWASP Nettacker API Default Configuration""" + + api_access_log = str(CWD / ".data/nettacker.log") + api_access_key = generate_random_token(32) + api_client_whitelisted_ips = [] # disabled - to enable please put an array with list of ips/cidr/ranges + # [ + # "127.0.0.1", + # "10.0.0.0/24", + # "192.168.1.1-192.168.1.255" + # ], + api_debug_mode = False + api_hostname = "0.0.0.0" + api_port = 5000 + start_api_server = False + + +class DbConfig(ConfigBase): + """ + Database Config (could be modified by user) + For sqlite database: + fill the name of the DB as sqlite, + DATABASE as the name of the db user wants + other details can be left empty + For mysql users: + fill the name of the DB as mysql + DATABASE as the name of the database you want to create + USERNAME, PASSWORD, HOST and the PORT of the MySQL server + need to be filled respectively + + """ + + engine = "sqlite" + name = str(CWD / ".data/nettacker.db") + host = "" + port = "" + username = "" + password = "" + + +class PathConfig: + """ + home path for the framework (could be modify by user) + + Returns: + a JSON contain the working, tmp and results path + """ + + data_dir = CWD / ".data" + database_file = CWD / ".data/nettacker.db" + graph_dir = PACKAGE_PATH / "lib/graph" + home_dir = CWD + locale_dir = PACKAGE_PATH / "locale" + logo_file = PACKAGE_PATH / "logo.txt" + module_protocols_dir = PACKAGE_PATH / "core/lib" + modules_dir = PACKAGE_PATH / "modules" + payloads_dir = PACKAGE_PATH / "lib/payloads" + release_name_file = PACKAGE_PATH / "release_name.txt" + results_dir = CWD / ".data/results" + tmp_dir = CWD / ".data/tmp" + web_static_dir = PACKAGE_PATH / "web/static" + user_agents_file = PACKAGE_PATH / "lib/payloads/User-Agents/web_browsers_user_agents.txt" + + +class DefaultSettings(ConfigBase): + """OWASP Nettacker Default Configuration""" + + excluded_modules = None + graph_name = "d3_tree_v2_graph" + language = "en" + modules_extra_args = None + parallel_module_scan = 1 + passwords = None + passwords_list = None + ping_before_scan = False + ports = None + profiles = None + report_path_filename = "{results_path}/results_{date_time}_{random_chars}.html".format( + results_path=PathConfig.results_dir, + date_time=now(format="%Y_%m_%d_%H_%M_%S"), + random_chars=generate_random_token(10), + ) + retries = 1 + scan_ip_range = False + scan_subdomains = False + selected_modules = None + set_hardware_usage = "maximum" # low, normal, high, maximum + show_all_modules = False + show_all_profiles = False + show_help_menu = False + show_version = False + skip_service_discovery = False + socks_proxy = None + targets = None + targets_list = None + thread_per_host = 100 + time_sleep_between_requests = 0.0 + timeout = 3.0 + user_agent = "Nettacker {version_number} {version_code}".format( + version_number=version_info()[0], version_code=version_info()[1] + ) + usernames = None + usernames_list = None + verbose_event = False + verbose_mode = False + + +class Config: + api = ApiConfig() + db = DbConfig() + path = PathConfig() + settings = DefaultSettings() diff --git a/nettacker/core/__init__.py b/nettacker/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nettacker/core/app.py b/nettacker/core/app.py new file mode 100644 index 000000000..449a7ea88 --- /dev/null +++ b/nettacker/core/app.py @@ -0,0 +1,335 @@ +import copy +import json +import os +import socket +import sys +from threading import Thread + +import multiprocess +import numpy + +from nettacker import logger +from nettacker.config import Config, version_info +from nettacker.core.arg_parser import ArgParser +from nettacker.core.die import die_failure +from nettacker.core.graph import create_report +from nettacker.core.ip import ( + get_ip_range, + generate_ip_range, + is_single_ipv4, + is_ipv4_range, + is_ipv4_cidr, + is_single_ipv6, + is_ipv6_range, + is_ipv6_cidr, +) +from nettacker.core.messages import messages as _ +from nettacker.core.module import Module +from nettacker.core.socks_proxy import set_socks_proxy +from nettacker.core.utils import common as utils +from nettacker.core.utils.common import wait_for_threads_to_finish +from nettacker.database.db import find_events, remove_old_logs +from nettacker.database.mysql import mysql_create_database, mysql_create_tables +from nettacker.database.postgresql import postgres_create_database +from nettacker.database.sqlite import sqlite_create_tables +from nettacker.logger import TerminalCodes + +log = logger.get_logger() + + +class Nettacker(ArgParser): + def __init__(self, api_arguments=None): + if not api_arguments: + self.print_logo() + self.check_dependencies() + + log.info(_("scan_started")) + super().__init__(api_arguments=api_arguments) + + @staticmethod + def print_logo(): + """ + OWASP Nettacker Logo + """ + log.write_to_api_console( + open(Config.path.logo_file) + .read() + .format( + cyan=TerminalCodes.CYAN.value, + red=TerminalCodes.RED.value, + rst=TerminalCodes.RESET.value, + v1=version_info()[0], + v2=version_info()[1], + yellow=TerminalCodes.YELLOW.value, + ) + ) + log.reset_color() + + def check_dependencies(self): + if sys.platform not in {"darwin", "linux"}: + die_failure(_("error_platform")) + + if not os.path.exists(Config.path.home_dir): + try: + os.mkdir(Config.path.home_dir) + os.mkdir(Config.path.tmp_dir) + os.mkdir(Config.path.results_dir) + except Exception: + die_failure("cannot access the directory {0}".format(Config.path.home_dir)) + if not os.path.exists(Config.path.tmp_dir): + try: + os.mkdir(Config.path.tmp_dir) + except Exception: + die_failure("cannot access the directory {0}".format(Config.path.tmp_dir)) + if not os.path.exists(Config.path.results_dir): + try: + os.mkdir(Config.path.results_dir) + except Exception: + die_failure("cannot access the directory {0}".format(Config.path.results_dir)) + + if Config.db.engine == "sqlite": + try: + if not os.path.isfile(Config.path.database_file): + sqlite_create_tables() + except Exception: + die_failure("cannot access the directory {0}".format(Config.path.home_dir)) + elif Config.db.engine == "mysql": + try: + mysql_create_database() + mysql_create_tables() + except Exception: + die_failure(_("database_connection_failed")) + elif Config.db.engine == "postgres": + try: + postgres_create_database() + except Exception: + die_failure(_("database_connection_failed")) + else: + die_failure(_("invalid_database")) + + def expand_targets(self, scan_id): + """ + determine targets. + + Args: + options: all options + scan_id: unique scan identifier + + Returns: + a generator + """ + + targets = [] + for target in self.arguments.targets: + if "://" in target: + # remove url proto; uri; port + target = target.split("://")[1].split("/")[0].split(":")[0] + targets.append(target) + # single IPs + elif is_single_ipv4(target) or is_single_ipv6(target): + if self.arguments.scan_ip_range: + targets += get_ip_range(target) + else: + targets.append(target) + # IP ranges + elif ( + is_ipv4_range(target) + or is_ipv6_range(target) + or is_ipv4_cidr(target) + or is_ipv6_cidr(target) + ): + targets += generate_ip_range(target) + # domains probably + else: + targets.append(target) + self.arguments.targets = targets + + # subdomain_scan + if self.arguments.scan_subdomains: + selected_modules = self.arguments.selected_modules + self.arguments.selected_modules = ["subdomain_scan"] + self.start_scan(scan_id) + self.arguments.selected_modules = selected_modules + if "subdomain_scan" in self.arguments.selected_modules: + self.arguments.selected_modules.remove("subdomain_scan") + + for target in copy.deepcopy(self.arguments.targets): + for row in find_events(target, "subdomain_scan", scan_id): + for sub_domain in json.loads(row.json_event)["response"]["conditions_results"][ + "content" + ]: + if sub_domain not in self.arguments.targets: + self.arguments.targets.append(sub_domain) + # icmp_scan + if self.arguments.ping_before_scan: + if os.geteuid() == 0: + selected_modules = self.arguments.selected_modules + self.arguments.selected_modules = ["icmp_scan"] + self.start_scan(scan_id) + self.arguments.selected_modules = selected_modules + if "icmp_scan" in self.arguments.selected_modules: + self.arguments.selected_modules.remove("icmp_scan") + self.arguments.targets = self.filter_target_by_event(targets, scan_id, "icmp_scan") + else: + log.warn(_("icmp_need_root_access")) + if "icmp_scan" in self.arguments.selected_modules: + self.arguments.selected_modules.remove("icmp_scan") + # port_scan + if not self.arguments.skip_service_discovery: + self.arguments.skip_service_discovery = True + selected_modules = self.arguments.selected_modules + self.arguments.selected_modules = ["port_scan"] + self.start_scan(scan_id) + self.arguments.selected_modules = selected_modules + if "port_scan" in self.arguments.selected_modules: + self.arguments.selected_modules.remove("port_scan") + self.arguments.targets = self.filter_target_by_event(targets, scan_id, "port_scan") + self.arguments.skip_service_discovery = False + + return list(set(self.arguments.targets)) + + def filter_target_by_event(self, targets, scan_id, module_name): + for target in copy.deepcopy(targets): + if not find_events(target, module_name, scan_id): + targets.remove(target) + return targets + + def run(self): + """ + preparing for attacks and managing multi-processing for host + + Args: + options: all options + + Returns: + True when it ends + """ + scan_id = utils.generate_random_token(32) + + log.info(_("regrouping_targets")) + # find total number of targets + types + expand (subdomain, IPRanges, etc) + # optimize CPU usage + self.arguments.targets = self.expand_targets(scan_id) + if not self.arguments.targets: + log.info(_("no_live_service_found")) + return True + + exit_code = self.start_scan(scan_id) + create_report(self.arguments, scan_id) + log.info(_("done")) + + return exit_code + + def start_scan(self, scan_id): + number_of_total_targets = len(self.arguments.targets) + target_groups = [ + targets.tolist() + for targets in numpy.array_split( + self.arguments.targets, + ( + self.arguments.set_hardware_usage + if self.arguments.set_hardware_usage <= len(self.arguments.targets) + else number_of_total_targets + ), + ) + ] + log.info(_("removing_old_db_records")) + + for target_group in target_groups: + for target in target_group: + for module_name in self.arguments.selected_modules: + remove_old_logs( + { + "target": target, + "module_name": module_name, + "scan_id": scan_id, + } + ) + + for _i in range(target_groups.count([])): + target_groups.remove([]) + + log.info(_("start_multi_process").format(number_of_total_targets, len(target_groups))) + active_processes = [] + for t_id, target_groups in enumerate(target_groups): + process = multiprocess.Process( + target=self.scan_target_group, args=(target_groups, scan_id, t_id) + ) + process.start() + active_processes.append(process) + + return wait_for_threads_to_finish(active_processes, sub_process=True) + + def scan_target( + self, + target, + module_name, + scan_id, + process_number, + thread_number, + total_number_threads, + ): + options = copy.deepcopy(self.arguments) + + socket.socket, socket.getaddrinfo = set_socks_proxy(options.socks_proxy) + + module = Module( + module_name, + options, + target, + scan_id, + process_number, + thread_number, + total_number_threads, + ) + module.load() + module.generate_loops() + module.sort_loops() + module.start() + + log.verbose_event_info( + _("finished_parallel_module_scan").format( + process_number, module_name, target, thread_number, total_number_threads + ) + ) + + return os.EX_OK + + def scan_target_group(self, targets, scan_id, process_number): + active_threads = [] + log.verbose_event_info(_("single_process_started").format(process_number)) + total_number_of_modules = len(targets) * len(self.arguments.selected_modules) + total_number_of_modules_counter = 1 + + for target in targets: + for module_name in self.arguments.selected_modules: + thread = Thread( + target=self.scan_target, + args=( + target, + module_name, + scan_id, + process_number, + total_number_of_modules_counter, + total_number_of_modules, + ), + ) + thread.name = f"{target} -> {module_name}" + thread.start() + log.verbose_event_info( + _("start_parallel_module_scan").format( + process_number, + module_name, + target, + total_number_of_modules_counter, + total_number_of_modules, + ) + ) + total_number_of_modules_counter += 1 + active_threads.append(thread) + if not wait_for_threads_to_finish( + active_threads, self.arguments.parallel_module_scan, True + ): + return False + wait_for_threads_to_finish(active_threads, maximum=None, terminable=True) + return True diff --git a/nettacker/core/arg_parser.py b/nettacker/core/arg_parser.py new file mode 100644 index 000000000..8e029cb43 --- /dev/null +++ b/nettacker/core/arg_parser.py @@ -0,0 +1,699 @@ +import json +import sys +from argparse import ArgumentParser + +import yaml + +from nettacker.config import version_info, Config +from nettacker.core.die import die_failure, die_success +from nettacker.core.ip import ( + is_single_ipv4, + is_single_ipv6, + is_ipv4_cidr, + is_ipv6_range, + is_ipv6_cidr, + is_ipv4_range, + generate_ip_range, +) +from nettacker.core.messages import messages as _ +from nettacker.core.template import TemplateLoader +from nettacker.core.utils import common as utils +from nettacker.logger import TerminalCodes, get_logger + +log = get_logger() + + +class ArgParser(ArgumentParser): + def __init__(self, api_arguments=None) -> None: + super().__init__(prog="Nettacker", add_help=False) + + self.api_arguments = api_arguments + self.graphs = self.load_graphs() + self.languages = self.load_languages() + + self.modules = self.load_modules(full_details=True) + log.info(_("loaded_modules").format(len(self.modules))) + + self.profiles = self.load_profiles() + + self.add_arguments() + self.parse_arguments() + + @staticmethod + def load_graphs(): + """ + load all available graphs + + Returns: + an array of graph names + """ + + graph_names = [] + for graph_library in Config.path.graph_dir.glob("*/engine.py"): + graph_names.append(str(graph_library).split("/")[-2] + "_graph") + return list(set(graph_names)) + + @staticmethod + def load_languages(): + """ + Get available languages + + Returns: + an array of languages + """ + languages_list = [] + + for language in Config.path.locale_dir.glob("*.yaml"): + languages_list.append(str(language).split("/")[-1].split(".")[0]) + + return list(set(languages_list)) + + @staticmethod + def load_modules(limit=-1, full_details=False): + """ + load all available modules + + limit: return limited number of modules + full: with full details + + Returns: + an array of all module names + """ + # Search for Modules + + module_names = {} + for module_name in sorted(Config.path.modules_dir.glob("**/*.yaml")): + library = str(module_name).split("/")[-1].split(".")[0] + category = str(module_name).split("/")[-2] + module = f"{library}_{category}" + contents = yaml.safe_load(TemplateLoader(module).open().split("payload:")[0]) + module_names[module] = contents["info"] if full_details else None + + if len(module_names) == limit: + module_names["..."] = {} + break + module_names = utils.sort_dictionary(module_names) + module_names["all"] = {} + + return module_names + + @staticmethod + def load_profiles(limit=-1): + """ + load all available profiles + + Returns: + an array of all profile names + """ + all_modules_with_details = ArgParser.load_modules(full_details=True).copy() + profiles = {} + if "..." in all_modules_with_details: + del all_modules_with_details["..."] + del all_modules_with_details["all"] + for key in all_modules_with_details: + for tag in all_modules_with_details[key]["profiles"]: + if tag not in profiles: + profiles[tag] = [] + profiles[tag].append(key) + else: + profiles[tag].append(key) + if len(profiles) == limit: + profiles = utils.sort_dictionary(profiles) + profiles["..."] = [] + profiles["all"] = [] + return profiles + profiles = utils.sort_dictionary(profiles) + profiles["all"] = [] + + return profiles + + def add_arguments(self): + # Engine Options + engine_options = self.add_argument_group(_("engine"), _("engine_input")) + engine_options.add_argument( + "-L", + "--language", + action="store", + dest="language", + default=Config.settings.language, + help=_("select_language").format(self.languages), + ) + engine_options.add_argument( + "-v", + "--verbose", + action="store_true", + dest="verbose_mode", + default=Config.settings.verbose_mode, + help=_("verbose_mode"), + ) + engine_options.add_argument( + "--verbose-event", + action="store_true", + dest="verbose_event", + default=Config.settings.verbose_event, + help=_("verbose_event"), + ) + engine_options.add_argument( + "-V", + "--version", + action="store_true", + default=Config.settings.show_version, + dest="show_version", + help=_("software_version"), + ) + engine_options.add_argument( + "-o", + "--output", + action="store", + default=Config.settings.report_path_filename, + dest="report_path_filename", + help=_("save_logs"), + ) + engine_options.add_argument( + "--graph", + action="store", + default=Config.settings.graph_name, + dest="graph_name", + help=_("available_graph").format(self.graphs), + ) + engine_options.add_argument( + "-h", + "--help", + action="store_true", + default=Config.settings.show_help_menu, + dest="show_help_menu", + help=_("help_menu"), + ) + + # Target Options + target_options = self.add_argument_group(_("target"), _("target_input")) + target_options.add_argument( + "-i", + "--targets", + action="store", + dest="targets", + default=Config.settings.targets, + help=_("target_list"), + ) + target_options.add_argument( + "-l", + "--targets-list", + action="store", + dest="targets_list", + default=Config.settings.targets_list, + help=_("read_target"), + ) + + # Exclude Module Name + exclude_modules = sorted(self.modules.keys())[:10] + exclude_modules.remove("all") + + # Method Options + method_options = self.add_argument_group(_("Method"), _("scan_method_options")) + method_options.add_argument( + "-m", + "--modules", + action="store", + dest="selected_modules", + default=Config.settings.selected_modules, + help=_("choose_scan_method").format(list(self.modules.keys())[:10]), + ) + method_options.add_argument( + "--modules-extra-args", + action="store", + dest="modules_extra_args", + default=Config.settings.modules_extra_args, + help=_("modules_extra_args_help"), + ) + method_options.add_argument( + "--show-all-modules", + action="store_true", + dest="show_all_modules", + default=Config.settings.show_all_modules, + help=_("show_all_modules"), + ) + method_options.add_argument( + "--profile", + action="store", + default=Config.settings.profiles, + dest="profiles", + help=_("select_profile").format(list(self.profiles.keys())[:10]), + ) + method_options.add_argument( + "--show-all-profiles", + action="store_true", + dest="show_all_profiles", + default=Config.settings.show_all_profiles, + help=_("show_all_profiles"), + ) + method_options.add_argument( + "-x", + "--exclude-modules", + action="store", + dest="excluded_modules", + default=Config.settings.excluded_modules, + help=_("exclude_scan_method").format(exclude_modules), + ) + method_options.add_argument( + "-u", + "--usernames", + action="store", + dest="usernames", + default=Config.settings.usernames, + help=_("username_list"), + ) + method_options.add_argument( + "-U", + "--users-list", + action="store", + dest="usernames_list", + default=Config.settings.usernames_list, + help=_("username_from_file"), + ) + method_options.add_argument( + "-p", + "--passwords", + action="store", + dest="passwords", + default=Config.settings.passwords, + help=_("password_separator"), + ) + method_options.add_argument( + "-P", + "--passwords-list", + action="store", + dest="passwords_list", + default=Config.settings.passwords_list, + help=_("read_passwords"), + ) + method_options.add_argument( + "-g", + "--ports", + action="store", + dest="ports", + default=Config.settings.ports, + help=_("port_separator"), + ) + method_options.add_argument( + "--user-agent", + action="store", + dest="user_agent", + default=Config.settings.user_agent, + help=_("select_user_agent"), + ) + method_options.add_argument( + "-T", + "--timeout", + action="store", + dest="timeout", + default=Config.settings.timeout, + type=float, + help=_("read_passwords"), + ) + method_options.add_argument( + "-w", + "--time-sleep-between-requests", + action="store", + dest="time_sleep_between_requests", + default=Config.settings.time_sleep_between_requests, + type=float, + help=_("time_to_sleep"), + ) + method_options.add_argument( + "-r", + "--range", + action="store_true", + default=Config.settings.scan_ip_range, + dest="scan_ip_range", + help=_("range"), + ) + method_options.add_argument( + "-s", + "--sub-domains", + action="store_true", + default=Config.settings.scan_subdomains, + dest="scan_subdomains", + help=_("subdomains"), + ) + method_options.add_argument( + "--skip-service-discovery", + action="store_true", + default=Config.settings.skip_service_discovery, + dest="skip_service_discovery", + help=_("skip_service_discovery"), + ) + method_options.add_argument( + "-t", + "--thread-per-host", + action="store", + default=Config.settings.thread_per_host, + type=int, + dest="thread_per_host", + help=_("thread_number_connections"), + ) + method_options.add_argument( + "-M", + "--parallel-module-scan", + action="store", + default=Config.settings.parallel_module_scan, + type=int, + dest="parallel_module_scan", + help=_("thread_number_modules"), + ) + method_options.add_argument( + "--set-hardware-usage", + action="store", + dest="set_hardware_usage", + default=Config.settings.set_hardware_usage, + help=_("set_hardware_usage"), + ) + method_options.add_argument( + "-R", + "--socks-proxy", + action="store", + dest="socks_proxy", + default=Config.settings.socks_proxy, + help=_("outgoing_proxy"), + ) + method_options.add_argument( + "--retries", + action="store", + dest="retries", + type=int, + default=Config.settings.retries, + help=_("connection_retries"), + ) + method_options.add_argument( + "--ping-before-scan", + action="store_true", + dest="ping_before_scan", + default=Config.settings.ping_before_scan, + help=_("ping_before_scan"), + ) + + # API Options + api_options = self.add_argument_group(_("API"), _("API_options")) + api_options.add_argument( + "--start-api", + action="store_true", + dest="start_api_server", + default=Config.api.start_api_server, + help=_("start_api_server"), + ) + api_options.add_argument( + "--api-host", + action="store", + dest="api_hostname", + default=Config.api.api_hostname, + help=_("API_host"), + ) + api_options.add_argument( + "--api-port", + action="store", + dest="api_port", + default=Config.api.api_port, + help=_("API_port"), + ) + api_options.add_argument( + "--api-debug-mode", + action="store_true", + dest="api_debug_mode", + default=Config.api.api_debug_mode, + help=_("API_debug"), + ) + api_options.add_argument( + "--api-access-key", + action="store", + dest="api_access_key", + default=Config.api.api_access_key, + help=_("API_access_key"), + ) + api_options.add_argument( + "--api-client-whitelisted-ips", + action="store", + dest="api_client_whitelisted_ips", + default=Config.api.api_client_whitelisted_ips, + help=_("define_white_list"), + ) + api_options.add_argument( + "--api-access-log", + action="store", + dest="api_access_log", + default=Config.api.api_access_log, + help=_("API_access_log_file"), + ) + api_options.add_argument( + "--api-cert", + action="store", + dest="api_cert", + help=_("API_cert"), + ) + api_options.add_argument( + "--api-cert-key", + action="store", + dest="api_cert_key", + help=_("API_cert_key"), + ) + + def parse_arguments(self): + """ + check all rules and requirements for ARGS + + Args: + api_forms: values from nettacker.api + + Returns: + all ARGS with applied rules + """ + # Checking Requirements + options = self.api_arguments or self.parse_args() + + if options.language not in self.languages: + die_failure("Please select one of these languages {0}".format(self.languages)) + + # Check Help Menu + if options.show_help_menu: + self.print_help() + log.write("\n\n") + log.write(_("license")) + die_success() + + # Check version + if options.show_version: + log.info( + _("current_version").format( + TerminalCodes.YELLOW.value, + version_info()[0], + TerminalCodes.RESET.value, + TerminalCodes.CYAN.value, + version_info()[1], + TerminalCodes.RESET.value, + TerminalCodes.GREEN.value, + ) + ) + die_success() + + if options.show_all_modules: + log.info(_("loading_modules")) + for module in self.modules: + log.info( + _("module_profile_full_information").format( + TerminalCodes.CYAN.value, + module, + TerminalCodes.GREEN.value, + ", ".join( + [ + "{key}: {value}".format(key=key, value=self.modules[module][key]) + for key in self.modules[module] + ] + ), + ) + ) + die_success() + + if options.show_all_profiles: + log.info(_("loading_profiles")) + for profile in self.profiles: + log.info( + _("module_profile_full_information").format( + TerminalCodes.CYAN.value, + profile, + TerminalCodes.GREEN.value, + ", ".join(self.profiles[profile]), + ) + ) + die_success() + + # API mode + if options.start_api_server: + if "--start-api" in sys.argv and self.api_arguments: + die_failure(_("cannot_run_api_server")) + from nettacker.api.engine import start_api_server + + if options.api_client_whitelisted_ips: + if isinstance(options.api_client_whitelisted_ips, str): + options.api_client_whitelisted_ips = options.api_client_whitelisted_ips.split( + "," + ) + whitelisted_ips = [] + for ip in options.api_client_whitelisted_ips: + if is_single_ipv4(ip) or is_single_ipv6(ip): + whitelisted_ips.append(ip) + elif ( + is_ipv4_range(ip) + or is_ipv6_range(ip) + or is_ipv4_cidr(ip) + or is_ipv6_cidr(ip) + ): + whitelisted_ips += generate_ip_range(ip) + options.api_client_whitelisted_ips = whitelisted_ips + start_api_server(options) + + # Check the target(s) + if not (options.targets or options.targets_list) or ( + options.targets and options.targets_list + ): + # self.print_help() + # write("\n") + die_failure(_("error_target")) + if options.targets: + options.targets = list(set(options.targets.split(","))) + if options.targets_list: + try: + options.targets = list( + set(open(options.targets_list, "rb").read().decode().split()) + ) + except Exception: + die_failure(_("error_target_file").format(options.targets_list)) + + # check for modules + if not (options.selected_modules or options.profiles): + die_failure(_("scan_method_select")) + if options.selected_modules: + if options.selected_modules == "all": + options.selected_modules = list(set(self.modules.keys())) + options.selected_modules.remove("all") + else: + options.selected_modules = list(set(options.selected_modules.split(","))) + for module_name in options.selected_modules: + if module_name not in self.modules: + die_failure(_("scan_module_not_found").format(module_name)) + if options.profiles: + if not options.selected_modules: + options.selected_modules = [] + if options.profiles == "all": + options.selected_modules = list(set(self.modules.keys())) + options.selected_modules.remove("all") + else: + options.profiles = list(set(options.profiles.split(","))) + for profile in options.profiles: + if profile not in self.profiles: + die_failure(_("profile_404").format(profile)) + for module_name in self.profiles[profile]: + if module_name not in options.selected_modules: + options.selected_modules.append(module_name) + # threading & processing + if options.set_hardware_usage not in {"low", "normal", "high", "maximum"}: + die_failure(_("wrong_hardware_usage")) + options.set_hardware_usage = utils.select_maximum_cpu_core(options.set_hardware_usage) + + options.thread_per_host = int(options.thread_per_host) + if options.thread_per_host < 1: + options.thread_per_host = 1 + options.parallel_module_scan = int(options.parallel_module_scan) + if options.parallel_module_scan < 1: + options.parallel_module_scan = 1 + + # Check for excluding modules + if options.excluded_modules: + options.excluded_modules = options.excluded_modules.split(",") + if "all" in options.excluded_modules: + die_failure(_("error_exclude_all")) + for excluded_module in options.excluded_modules: + if excluded_module in options.selected_modules: + options.selected_modules.remove(excluded_module) + # Check port(s) + if options.ports: + tmp_ports = [] + for port in options.ports.split(","): + try: + if "-" in port: + for port_number in range( + int(port.split("-")[0]), int(port.split("-")[1]) + 1 + ): + if port_number not in tmp_ports: + tmp_ports.append(port_number) + else: + if int(port) not in tmp_ports: + tmp_ports.append(int(port)) + except Exception: + die_failure(_("ports_int")) + options.ports = tmp_ports + + if options.user_agent == "random_user_agent": + options.user_agents = open(Config.path.user_agents_file).read().split("\n") + + # Check user list + if options.usernames: + options.usernames = list(set(options.usernames.split(","))) + elif options.usernames_list: + try: + options.usernames = list(set(open(options.usernames_list).read().split("\n"))) + except Exception: + die_failure(_("error_username").format(options.usernames_list)) + # Check password list + if options.passwords: + options.passwords = list(set(options.passwords.split(","))) + elif options.passwords_list: + try: + options.passwords = list(set(open(options.passwords_list).read().split("\n"))) + except Exception: + die_failure(_("error_passwords").format(options.passwords_list)) + # Check output file + try: + temp_file = open(options.report_path_filename, "w") + temp_file.close() + except Exception: + die_failure(_("file_write_error").format(options.report_path_filename)) + # Check Graph + if options.graph_name: + if options.graph_name not in self.graphs: + die_failure(_("graph_module_404").format(options.graph_name)) + if not ( + options.report_path_filename.endswith(".html") + or options.report_path_filename.endswith(".htm") + ): + log.warn(_("graph_output")) + options.graph_name = None + # check modules extra args + if options.modules_extra_args: + all_args = {} + for args in options.modules_extra_args.split("&"): + value = args.split("=")[1] + if value.lower() == "true": + value = True + elif value.lower() == "false": + value = False + elif "." in value: + try: + value = float(value) + except Exception: + pass + elif "{" in value or "[" in value: + try: + value = json.loads(value) + except Exception: + pass + else: + try: + value = int(value) + except Exception: + pass + all_args[args.split("=")[0]] = value + options.modules_extra_args = all_args + + options.timeout = float(options.timeout) + options.time_sleep_between_requests = float(options.time_sleep_between_requests) + options.retries = int(options.retries) + + self.arguments = options diff --git a/core/die.py b/nettacker/core/die.py similarity index 52% rename from core/die.py rename to nettacker/core/die.py index 9532e1e17..c299c7c7d 100644 --- a/core/die.py +++ b/nettacker/core/die.py @@ -1,15 +1,15 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - import sys +from nettacker import logger + +log = logger.get_logger() + def die_success(): """ exit the framework with code 0 """ - from core.color import reset_color - reset_color() + log.reset_color() sys.exit(0) @@ -20,8 +20,7 @@ def die_failure(msg): Args: msg: the error message """ - from core.color import reset_color - from core.alert import error - error(msg) - reset_color() + + log.error(msg) + log.reset_color() sys.exit(1) diff --git a/nettacker/core/fuzzer.py b/nettacker/core/fuzzer.py new file mode 100644 index 000000000..bf82e9eff --- /dev/null +++ b/nettacker/core/fuzzer.py @@ -0,0 +1,5 @@ +from nettacker.config import Config + + +def read_from_file(file_path): + return open(Config.path.payloads_dir / file_path).read().split("\n") diff --git a/nettacker/core/graph.py b/nettacker/core/graph.py new file mode 100644 index 000000000..9039b9a92 --- /dev/null +++ b/nettacker/core/graph.py @@ -0,0 +1,170 @@ +import csv +import html +import importlib +import json +from datetime import datetime + +import texttable + +from nettacker import logger +from nettacker.config import version_info +from nettacker.core.die import die_failure +from nettacker.core.messages import messages as _ +from nettacker.core.utils.common import merge_logs_to_list, now +from nettacker.database.db import get_logs_by_scan_id, submit_report_to_db + +log = logger.get_logger() + + +def build_graph(graph_name, events): + """ + build a graph + + Args: + graph_name: graph name + events: list of events + + Returns: + graph in HTML type + """ + log.info(_("build_graph")) + try: + start = getattr( + importlib.import_module( + f"nettacker.lib.graph.{graph_name.rsplit('_graph')[0]}.engine" + ), + "start", + ) + except ModuleNotFoundError: + die_failure(_("graph_module_unavailable").format(graph_name)) + + log.info(_("finish_build_graph")) + return start(events) + + +def build_text_table(events): + """ + value['date'], value["target"], value['module_name'], value['scan_id'], + value['options'], value['event'] + build a text table with generated event related to the scan + + :param events: all events + :return: + array [text table, event_number] + """ + _table = texttable.Texttable() + table_headers = ["date", "target", "module_name", "port", "logs"] + _table.add_rows([table_headers]) + for event in events: + log = merge_logs_to_list(json.loads(event["json_event"]), []) + _table.add_rows( + [ + table_headers, + [ + event["date"], + event["target"], + event["module_name"], + str(event["port"]), + "\n".join(log) if log else "Detected", + ], + ] + ) + return ( + _table.draw() + + "\n\n" + + _("nettacker_version_details").format(version_info()[0], version_info()[1], now()) + + "\n" + ) + + +def create_report(options, scan_id): + """ + sort all events, create log file in HTML/TEXT/JSON and remove old logs + + Args: + options: parsing options + scan_id: scan unique id + + Returns: + True if success otherwise None + """ + all_scan_logs = get_logs_by_scan_id(scan_id) + if not all_scan_logs: + log.info(_("no_events_for_report")) + return True + report_path_filename = options.report_path_filename + if (len(report_path_filename) >= 5 and report_path_filename[-5:] == ".html") or ( + len(report_path_filename) >= 4 and report_path_filename[-4:] == ".htm" + ): + if options.graph_name: + html_graph = build_graph(options.graph_name, all_scan_logs) + else: + html_graph = "" + + from nettacker.lib.html_log import log_data + + html_table_content = log_data.table_title.format( + html_graph, + log_data.css_1, + "date", + "target", + "module_name", + "port", + "logs", + "json_event", + ) + index = 1 + for event in all_scan_logs: + log_list = merge_logs_to_list(json.loads(event["json_event"]), []) + html_table_content += log_data.table_items.format( + event["date"], + event["target"], + event["module_name"], + event["port"], + "
".join(log_list) if log_list else "Detected", # event["event"], #log + index, + html.escape(event["json_event"]), + ) + index += 1 + html_table_content += ( + log_data.table_end + + '
' + + str(index - 1) + + "
" + + '" + + log_data.json_parse_js + ) + with open(report_path_filename, "w", encoding="utf-8") as report_file: + report_file.write(html_table_content + "\n") + report_file.close() + elif len(report_path_filename) >= 5 and report_path_filename[-5:] == ".json": + with open(report_path_filename, "w", encoding="utf-8") as report_file: + report_file.write(str(json.dumps(all_scan_logs)) + "\n") + report_file.close() + elif len(report_path_filename) >= 5 and report_path_filename[-4:] == ".csv": + keys = all_scan_logs[0].keys() + with open(report_path_filename, "a") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=keys) + writer.writeheader() + for log_list in all_scan_logs: + dict_data = {key: value for key, value in log_list.items() if key in keys} + writer.writerow(dict_data) + csvfile.close() + + else: + with open(report_path_filename, "w", encoding="utf-8") as report_file: + report_file.write(build_text_table(all_scan_logs)) + + log.write(build_text_table(all_scan_logs)) + submit_report_to_db( + { + "date": datetime.now(), + "scan_id": scan_id, + "options": vars(options), + } + ) + + log.info(_("file_saved").format(report_path_filename)) + return True diff --git a/core/ip.py b/nettacker/core/ip.py similarity index 55% rename from core/ip.py rename to nettacker/core/ip.py index 01448d6a3..cdc61af90 100644 --- a/core/ip.py +++ b/nettacker/core/ip.py @@ -1,11 +1,8 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - import json + import netaddr import requests -from netaddr import iprange_to_cidrs -from netaddr import IPNetwork +from netaddr import iprange_to_cidrs, IPNetwork def generate_ip_range(ip_range): @@ -18,13 +15,13 @@ def generate_ip_range(ip_range): Returns: an array with CIDRs """ - if '/' in ip_range: - return [ - ip.format() for ip in [cidr for cidr in IPNetwork(ip_range)] - ] + if "/" in ip_range: + return [ip.format() for ip in [cidr for cidr in IPNetwork(ip_range)]] else: ips = [] - for generator_ip_range in [cidr.iter_hosts() for cidr in iprange_to_cidrs(*ip_range.rsplit('-'))]: + for generator_ip_range in [ + cidr.iter_hosts() for cidr in iprange_to_cidrs(*ip_range.rsplit("-")) + ]: for ip in generator_ip_range: ips.append(ip.format()) return ips @@ -44,9 +41,9 @@ def get_ip_range(ip): return generate_ip_range( json.loads( requests.get( - 'https://rest.db.ripe.net/search.json?query-string={ip}&flags=no-filtering'.format(ip=ip) + f"https://rest.db.ripe.net/search.json?query-string={ip}&flags=no-filtering" ).content - )['objects']['object'][0]['primary-key']['attribute'][0]['value'].replace(' ', '') + )["objects"]["object"][0]["primary-key"]["attribute"][0]["value"].replace(" ", "") ) except Exception: return [ip] @@ -67,14 +64,24 @@ def is_single_ipv4(ip): def is_ipv4_range(ip_range): try: - return '/' in ip_range and '.' in ip_range and '-' not in ip_range and netaddr.IPNetwork(ip_range) + return ( + "/" in ip_range + and "." in ip_range + and "-" not in ip_range + and netaddr.IPNetwork(ip_range) + ) except Exception: return False def is_ipv4_cidr(ip_range): try: - return '/' not in ip_range and '.' in ip_range and '-' in ip_range and iprange_to_cidrs(*ip_range.split('-')) + return ( + "/" not in ip_range + and "." in ip_range + and "-" in ip_range + and iprange_to_cidrs(*ip_range.split("-")) + ) except Exception: return False @@ -94,13 +101,23 @@ def is_single_ipv6(ip): def is_ipv6_range(ip_range): try: - return '/' not in ip_range and ':' in ip_range and '-' in ip_range and iprange_to_cidrs(*ip_range.split('-')) + return ( + "/" not in ip_range + and ":" in ip_range + and "-" in ip_range + and iprange_to_cidrs(*ip_range.split("-")) + ) except Exception: return False def is_ipv6_cidr(ip_range): try: - return '/' in ip_range and ':' in ip_range and '-' not in ip_range and netaddr.IPNetwork(ip_range) + return ( + "/" in ip_range + and ":" in ip_range + and "-" not in ip_range + and netaddr.IPNetwork(ip_range) + ) except Exception: return False diff --git a/nettacker/core/lib/__init__.py b/nettacker/core/lib/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nettacker/core/lib/base.py b/nettacker/core/lib/base.py new file mode 100644 index 000000000..98573bf29 --- /dev/null +++ b/nettacker/core/lib/base.py @@ -0,0 +1,311 @@ +import copy +import json +import re +import time +from abc import ABC +from datetime import datetime + +import yaml + +from nettacker.config import Config +from nettacker.core.messages import messages as _ +from nettacker.core.utils.common import merge_logs_to_list +from nettacker.database.db import find_temp_events, submit_temp_logs_to_db, submit_logs_to_db +from nettacker.logger import get_logger, TerminalCodes + +log = get_logger() + + +class BaseLibrary(ABC): + """Nettacker library base class.""" + + client = None + + def brute_force(self): + """Brute force method.""" + + +class BaseEngine(ABC): + """Nettacker engine base class.""" + + library = None + + def apply_extra_data(self, *args, **kwargs): + """Add extra data into step context.""" + + def filter_large_content(self, content, filter_rate=150): + if len(content) <= filter_rate: + return content + else: + filter_rate -= 1 + filter_index = filter_rate + for char in content[filter_rate:]: + if char == " ": + return content[0:filter_index] + _("filtered_content") + else: + filter_index += 1 + return content + + def get_dependent_results_from_database(self, target, module_name, scan_id, event_names): + events = [] + for event_name in event_names.split(","): + while True: + event = find_temp_events(target, module_name, scan_id, event_name) + if event: + events.append(json.loads(event.event)["response"]["conditions_results"]) + break + time.sleep(0.1) + return events + + def find_and_replace_dependent_values(self, sub_step, dependent_on_temp_event): + if isinstance(sub_step, dict): + for key in copy.deepcopy(sub_step): + if not isinstance(sub_step[key], (str, float, int, bytes)): + sub_step[key] = self.find_and_replace_dependent_values( + copy.deepcopy(sub_step[key]), dependent_on_temp_event + ) + else: + if isinstance(sub_step[key], str): + if "dependent_on_temp_event" in sub_step[key]: + globals().update(locals()) + generate_new_step = copy.deepcopy(sub_step[key]) + key_name = re.findall( + re.compile( + "dependent_on_temp_event\\[\\S+\\]\\['\\S+\\]\\[\\S+\\]" + ), + generate_new_step, + )[0] + try: + key_value = eval(key_name) + except Exception: + key_value = "error" + sub_step[key] = sub_step[key].replace(key_name, key_value) + if isinstance(sub_step, list): + value_index = 0 + for key in copy.deepcopy(sub_step): + if type(sub_step[value_index]) not in [str, float, int, bytes]: + sub_step[key] = self.find_and_replace_dependent_values( + copy.deepcopy(sub_step[value_index]), dependent_on_temp_event + ) + else: + if isinstance(sub_step[value_index], str): + if "dependent_on_temp_event" in sub_step[value_index]: + globals().update(locals()) + generate_new_step = copy.deepcopy(sub_step[key]) + key_name = re.findall( + re.compile("dependent_on_temp_event\\['\\S+\\]\\[\\S+\\]"), + generate_new_step, + )[0] + try: + key_value = eval(key_name) + except Exception: + key_value = "error" + sub_step[value_index] = sub_step[value_index].replace( + key_name, key_value + ) + value_index += 1 + return sub_step + + def process_conditions( + self, + event, + module_name, + target, + scan_id, + options, + response, + process_number, + module_thread_number, + total_module_thread_number, + request_number_counter, + total_number_of_requests, + ): + if "save_to_temp_events_only" in event.get("response", ""): + submit_temp_logs_to_db( + { + "date": datetime.now(), + "target": target, + "module_name": module_name, + "scan_id": scan_id, + "event_name": event["response"]["save_to_temp_events_only"], + "port": event.get("ports", ""), + "event": event, + "data": response, + } + ) + if event["response"]["conditions_results"] and "save_to_temp_events_only" not in event.get( + "response", "" + ): + # remove sensitive information before submitting to db + + options = copy.deepcopy(options) + for key in Config.api: + try: + del options[key] + except KeyError: + continue + + del event["response"]["conditions"] + del event["response"]["condition_type"] + if "log" in event["response"]: + del event["response"]["log"] + event_request_keys = copy.deepcopy(event) + del event_request_keys["response"] + submit_logs_to_db( + { + "date": datetime.now(), + "target": target, + "module_name": module_name, + "scan_id": scan_id, + "port": event.get("ports") + or event.get("port") + or ( + event.get("url").split(":")[2].split("/")[0] + if isinstance(event.get("url"), str) + and len(event.get("url").split(":")) >= 3 + and event.get("url").split(":")[2].split("/")[0].isdigit() + else "" + ), + "event": " ".join(yaml.dump(event_request_keys).split()) + + " conditions: " + + " ".join(yaml.dump(event["response"]["conditions_results"]).split()), + "json_event": event, + } + ) + log_list = merge_logs_to_list(event["response"]["conditions_results"]) + if log_list: + log.success_event_info( + _("send_success_event_from_module").format( + process_number, + module_name, + target, + module_thread_number, + total_module_thread_number, + request_number_counter, + total_number_of_requests, + " ", + self.filter_large_content( + "\n".join( + [ + TerminalCodes.PURPLE.value + key + TerminalCodes.RESET.value + for key in log_list + ] + ), + filter_rate=100000, + ), + ) + ) + else: + log.success_event_info( + _("send_success_event_from_module").format( + process_number, + module_name, + target, + module_thread_number, + total_module_thread_number, + request_number_counter, + total_number_of_requests, + " ".join( + [ + TerminalCodes.YELLOW.value + key + TerminalCodes.RESET.value + if ":" in key + else TerminalCodes.GREEN.value + key + TerminalCodes.RESET.value + for key in yaml.dump(event_request_keys).split() + ] + ), + self.filter_large_content( + "conditions: " + + " ".join( + [ + TerminalCodes.PURPLE.value + key + TerminalCodes.RESET.value + if ":" in key + else TerminalCodes.GREEN.value + + key + + TerminalCodes.RESET.value + for key in yaml.dump( + event["response"]["conditions_results"] + ).split() + ] + ), + filter_rate=150, + ), + ) + ) + log.verbose_info(json.dumps(event)) + return True + else: + del event["response"]["conditions"] + log.verbose_info( + _("send_unsuccess_event_from_module").format( + process_number, + module_name, + target, + module_thread_number, + total_module_thread_number, + request_number_counter, + total_number_of_requests, + ) + ) + log.verbose_info(json.dumps(event)) + return "save_to_temp_events_only" in event["response"] + + def replace_dependent_values(self, sub_step, dependent_on_temp_event): + return self.find_and_replace_dependent_values(sub_step, dependent_on_temp_event) + + def run( + self, + sub_step, + module_name, + target, + scan_id, + options, + process_number, + module_thread_number, + total_module_thread_number, + request_number_counter, + total_number_of_requests, + ): + """Engine entry point.""" + backup_method = copy.deepcopy(sub_step["method"]) + backup_response = copy.deepcopy(sub_step["response"]) + del sub_step["method"] + del sub_step["response"] + + for attr_name in ("ports", "usernames", "passwords"): + if attr_name in sub_step: + value = sub_step.pop(attr_name) + sub_step[attr_name.rstrip("s")] = int(value) if attr_name == "ports" else value + + if "dependent_on_temp_event" in backup_response: + temp_event = self.get_dependent_results_from_database( + target, module_name, scan_id, backup_response["dependent_on_temp_event"] + ) + sub_step = self.replace_dependent_values(sub_step, temp_event) + + action = getattr(self.library(), backup_method) + for _i in range(options["retries"]): + try: + response = action(**sub_step) + break + except Exception: + response = [] + + sub_step["method"] = backup_method + sub_step["response"] = backup_response + sub_step["response"]["conditions_results"] = response + + self.apply_extra_data(sub_step, response) + + return self.process_conditions( + sub_step, + module_name, + target, + scan_id, + options, + response, + process_number, + module_thread_number, + total_module_thread_number, + request_number_counter, + total_number_of_requests, + ) diff --git a/nettacker/core/lib/ftp.py b/nettacker/core/lib/ftp.py new file mode 100644 index 000000000..f15079901 --- /dev/null +++ b/nettacker/core/lib/ftp.py @@ -0,0 +1,24 @@ +import ftplib + +from nettacker.core.lib.base import BaseEngine, BaseLibrary + + +class FtpLibrary(BaseLibrary): + client = ftplib.FTP + + def brute_force(self, host, port, username, password, timeout): + connection = self.client(timeout=timeout) + connection.connect(host, port) + connection.login(username, password) + connection.close() + + return { + "host": host, + "port": port, + "username": username, + "password": password, + } + + +class FtpEngine(BaseEngine): + library = FtpLibrary diff --git a/nettacker/core/lib/ftps.py b/nettacker/core/lib/ftps.py new file mode 100644 index 000000000..d607fc453 --- /dev/null +++ b/nettacker/core/lib/ftps.py @@ -0,0 +1,11 @@ +import ftplib + +from nettacker.core.lib.ftp import FtpEngine, FtpLibrary + + +class FtpsLibrary(FtpLibrary): + client = ftplib.FTP_TLS + + +class FtpsEngine(FtpEngine): + library = FtpsLibrary diff --git a/nettacker/core/lib/http.py b/nettacker/core/lib/http.py new file mode 100644 index 000000000..630109915 --- /dev/null +++ b/nettacker/core/lib/http.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python + +import asyncio +import copy +import random +import re +import time + +import aiohttp + +from nettacker.core.lib.base import BaseEngine +from nettacker.core.utils.common import replace_dependent_response, reverse_and_regex_condition + + +async def perform_request_action(action, request_options): + start_time = time.time() + async with action(**request_options) as response: + return { + "reason": response.reason, + "status_code": str(response.status), + "content": await response.content.read(), + "headers": dict(response.headers), + "responsetime": time.time() - start_time, + } + + +async def send_request(request_options, method): + async with aiohttp.ClientSession() as session: + action = getattr(session, method, None) + response = await asyncio.gather( + *[asyncio.ensure_future(perform_request_action(action, request_options))] + ) + return response[0] + + +def response_conditions_matched(sub_step, response): + if not response: + return {} + condition_type = sub_step["response"]["condition_type"] + conditions = sub_step["response"]["conditions"] + condition_results = {} + for condition in conditions: + if condition in ["reason", "status_code", "content"]: + regex = re.findall(re.compile(conditions[condition]["regex"]), response[condition]) + reverse = conditions[condition]["reverse"] + condition_results[condition] = reverse_and_regex_condition(regex, reverse) + if condition == "headers": + # convert headers to case insensitive dict + for key in response["headers"].copy(): + response["headers"][key.lower()] = response["headers"][key] + condition_results["headers"] = {} + for header in conditions["headers"]: + reverse = conditions["headers"][header]["reverse"] + try: + regex = re.findall( + re.compile(conditions["headers"][header]["regex"]), + response["headers"][header.lower()] + if header.lower() in response["headers"] + else False, + ) + condition_results["headers"][header] = reverse_and_regex_condition( + regex, reverse + ) + except TypeError: + condition_results["headers"][header] = [] + if condition == "responsetime": + if len(conditions[condition].split()) == 2 and conditions[condition].split()[0] in [ + "==", + "!=", + ">=", + "<=", + ">", + "<", + ]: + exec( + "condition_results['responsetime'] = response['responsetime'] if (" + + "response['responsetime'] {0} float(conditions['responsetime'].split()[-1])".format( + conditions["responsetime"].split()[0] + ) + + ") else []" + ) + else: + condition_results["responsetime"] = [] + if condition_type.lower() == "or": + # if one of the values are matched, it will be a string or float object in the array + # we count False in the array and if it's not all []; then we know one of the conditions + # is matched. + if ( + "headers" not in condition_results + and ( + list(condition_results.values()).count([]) != len(list(condition_results.values())) + ) + ) or ( + "headers" in condition_results + and ( + len(list(condition_results.values())) + + len(list(condition_results["headers"].values())) + - list(condition_results.values()).count([]) + - list(condition_results["headers"].values()).count([]) + - 1 + != 0 + ) + ): + if sub_step["response"].get("log", False): + condition_results["log"] = sub_step["response"]["log"] + if "response_dependent" in condition_results["log"]: + condition_results["log"] = replace_dependent_response( + condition_results["log"], condition_results + ) + return condition_results + else: + return {} + if condition_type.lower() == "and": + if [] in condition_results.values() or ( + "headers" in condition_results and [] in condition_results["headers"].values() + ): + return {} + else: + if sub_step["response"].get("log", False): + condition_results["log"] = sub_step["response"]["log"] + if "response_dependent" in condition_results["log"]: + condition_results["log"] = replace_dependent_response( + condition_results["log"], condition_results + ) + return condition_results + return {} + + +class HttpEngine(BaseEngine): + def run( + self, + sub_step, + module_name, + target, + scan_id, + options, + process_number, + module_thread_number, + total_module_thread_number, + request_number_counter, + total_number_of_requests, + ): + backup_method = copy.deepcopy(sub_step["method"]) + backup_response = copy.deepcopy(sub_step["response"]) + backup_iterative_response_match = copy.deepcopy( + sub_step["response"]["conditions"].get("iterative_response_match", None) + ) + if options["user_agent"] == "random_user_agent": + sub_step["headers"]["User-Agent"] = random.choice(options["user_agents"]) + del sub_step["method"] + if "dependent_on_temp_event" in backup_response: + temp_event = self.get_dependent_results_from_database( + target, + module_name, + scan_id, + backup_response["dependent_on_temp_event"], + ) + sub_step = self.replace_dependent_values(sub_step, temp_event) + backup_response = copy.deepcopy(sub_step["response"]) + del sub_step["response"] + for _i in range(options["retries"]): + try: + response = asyncio.run(send_request(sub_step, backup_method)) + response["content"] = response["content"].decode(errors="ignore") + break + except Exception: + response = [] + sub_step["method"] = backup_method + sub_step["response"] = backup_response + + if backup_iterative_response_match is not None: + backup_iterative_response_match = copy.deepcopy( + sub_step["response"]["conditions"].get("iterative_response_match") + ) + del sub_step["response"]["conditions"]["iterative_response_match"] + + sub_step["response"]["conditions_results"] = response_conditions_matched( + sub_step, response + ) + + if backup_iterative_response_match is not None and ( + sub_step["response"]["conditions_results"] + or sub_step["response"]["condition_type"] == "or" + ): + sub_step["response"]["conditions"][ + "iterative_response_match" + ] = backup_iterative_response_match + for key in sub_step["response"]["conditions"]["iterative_response_match"]: + result = response_conditions_matched( + sub_step["response"]["conditions"]["iterative_response_match"][key], + response, + ) + if result: + sub_step["response"]["conditions_results"][key] = result + + return self.process_conditions( + sub_step, + module_name, + target, + scan_id, + options, + response, + process_number, + module_thread_number, + total_module_thread_number, + request_number_counter, + total_number_of_requests, + ) diff --git a/nettacker/core/lib/pop3.py b/nettacker/core/lib/pop3.py new file mode 100644 index 000000000..2b83895d8 --- /dev/null +++ b/nettacker/core/lib/pop3.py @@ -0,0 +1,24 @@ +import poplib + +from nettacker.core.lib.base import BaseEngine, BaseLibrary + + +class Pop3Library(BaseLibrary): + client = poplib.POP3 + + def brute_force(self, host, port, username, password, timeout): + connection = self.client(host, port=port, timeout=timeout) + connection.user(username) + connection.pass_(password) + connection.quit() + + return { + "host": host, + "port": port, + "username": username, + "password": password, + } + + +class Pop3Engine(BaseEngine): + library = Pop3Library diff --git a/nettacker/core/lib/pop3s.py b/nettacker/core/lib/pop3s.py new file mode 100644 index 000000000..3c8338ad3 --- /dev/null +++ b/nettacker/core/lib/pop3s.py @@ -0,0 +1,11 @@ +import poplib + +from nettacker.core.lib.pop3 import Pop3Engine, Pop3Library + + +class Pop3sLibrary(Pop3Library): + client = poplib.POP3_SSL + + +class Pop3sEngine(Pop3Engine): + library = Pop3sLibrary diff --git a/nettacker/core/lib/smtp.py b/nettacker/core/lib/smtp.py new file mode 100644 index 000000000..24a9e9ba3 --- /dev/null +++ b/nettacker/core/lib/smtp.py @@ -0,0 +1,23 @@ +import smtplib + +from nettacker.core.lib.base import BaseEngine, BaseLibrary + + +class SmtpLibrary(BaseLibrary): + client = smtplib.SMTP + + def brute_force(self, host, port, username, password, timeout): + connection = self.client(host, port, timeout=timeout) + connection.login(username, password) + connection.close() + + return { + "host": host, + "port": port, + "username": username, + "password": password, + } + + +class SmtpEngine(BaseEngine): + library = SmtpLibrary diff --git a/nettacker/core/lib/smtps.py b/nettacker/core/lib/smtps.py new file mode 100644 index 000000000..01be62a4c --- /dev/null +++ b/nettacker/core/lib/smtps.py @@ -0,0 +1,23 @@ +import smtplib + +from nettacker.core.lib.base import BaseEngine, BaseLibrary + + +class SmtpsLibrary(BaseLibrary): + client = smtplib.SMTP + + def brute_force(self, host, port, username, password, timeout): + connection = self.client(host, port, timeout=timeout) + connection.starttls() + connection.login(username, password) + connection.close() + return { + "host": host, + "port": port, + "username": username, + "password": password, + } + + +class SmtpsEngine(BaseEngine): + library = SmtpsLibrary diff --git a/nettacker/core/lib/socket.py b/nettacker/core/lib/socket.py new file mode 100644 index 000000000..fbf26b3e4 --- /dev/null +++ b/nettacker/core/lib/socket.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python + +import copy +import logging +import os +import re +import select +import socket +import ssl +import struct +import time + +from nettacker.core.lib.base import BaseEngine, BaseLibrary +from nettacker.core.utils.common import reverse_and_regex_condition + +log = logging.getLogger(__name__) + + +def create_tcp_socket(host, port, timeout): + try: + socket_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + socket_connection.settimeout(timeout) + socket_connection.connect((host, port)) + ssl_flag = False + except ConnectionRefusedError: + return None + + try: + socket_connection = ssl.wrap_socket(socket_connection) + ssl_flag = True + except Exception: + socket_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + socket_connection.settimeout(timeout) + socket_connection.connect((host, port)) + # finally: + # socket_connection.shutdown() + + return socket_connection, ssl_flag + + +class SocketLibrary(BaseLibrary): + def tcp_connect_only(self, host, port, timeout): + tcp_socket = create_tcp_socket(host, port, timeout) + if tcp_socket is None: + return None + + socket_connection, ssl_flag = tcp_socket + peer_name = socket_connection.getpeername() + socket_connection.close() + return { + "peer_name": peer_name, + "service": socket.getservbyport(int(port)), + "ssl_flag": ssl_flag, + } + + def tcp_connect_send_and_receive(self, host, port, timeout): + tcp_socket = create_tcp_socket(host, port, timeout) + if tcp_socket is None: + return None + + socket_connection, ssl_flag = tcp_socket + peer_name = socket_connection.getpeername() + try: + socket_connection.send(b"ABC\x00\r\n\r\n\r\n" * 10) + response = socket_connection.recv(1024 * 1024 * 10) + socket_connection.close() + # except ConnectionRefusedError: + # return None + except Exception: + try: + socket_connection.close() + response = b"" + except Exception: + response = b"" + return { + "peer_name": peer_name, + "service": socket.getservbyport(port), + "response": response.decode(errors="ignore"), + "ssl_flag": ssl_flag, + } + + def socket_icmp(self, host, timeout): + """ + A pure python ping implementation using raw socket. + Note that ICMP messages can only be sent from processes running as root. + Derived from ping.c distributed in Linux's netkit. That code is + copyright (c) 1989 by The Regents of the University of California. + That code is in turn derived from code written by Mike Muuss of the + US Army Ballistic Research Laboratory in December, 1983 and + placed in the public domain. They have my thanks. + Bugs are naturally mine. I'd be glad to hear about them. There are + certainly word - size dependenceies here. + Copyright (c) Matthew Dixon Cowles, . + Distributable under the terms of the GNU General Public License + version 2. Provided with no warranties of any sort. + Original Version from Matthew Dixon Cowles: + -> ftp://ftp.visi.com/users/mdc/ping.py + Rewrite by Jens Diemer: + -> http://www.python-forum.de/post-69122.html#69122 + Rewrite by George Notaras: + -> http://www.g-loaded.eu/2009/10/30/python-ping/ + Fork by Pierre Bourdon: + -> http://bitbucket.org/delroth/python-ping/ + Revision history + ~~~~~~~~~~~~~~~~ + November 22, 1997 + ----------------- + Initial hack. Doesn't do much, but rather than try to guess + what features I (or others) will want in the future, I've only + put in what I need now. + December 16, 1997 + ----------------- + For some reason, the checksum bytes are in the wrong order when + this is run under Solaris 2.X for SPARC but it works right under + Linux x86. Since I don't know just what's wrong, I'll swap the + bytes always and then do an htons(). + December 4, 2000 + ---------------- + Changed the struct.pack() calls to pack the checksum and ID as + unsigned. My thanks to Jerome Poincheval for the fix. + May 30, 2007 + ------------ + little rewrite by Jens Diemer: + - change socket asterisk import to a normal import + - replace time.time() with time.clock() + - delete "return None" (or change to "return" only) + - in checksum() rename "str" to "source_string" + November 8, 2009 + ---------------- + Improved compatibility with GNU/Linux systems. + Fixes by: + * George Notaras -- http://www.g-loaded.eu + Reported by: + * Chris Hallman -- http://cdhallman.blogspot.com + Changes in this release: + - Re-use time.time() instead of time.clock(). The 2007 implementation + worked only under Microsoft Windows. Failed on GNU/Linux. + time.clock() behaves differently under the two OSes[1]. + [1] http://docs.python.org/library/time.html#time.clock + September 25, 2010 + ------------------ + Little modifications by Georgi Kolev: + - Added quiet_ping function. + - returns percent lost packages, max round trip time, avrg round trip + time + - Added packet size to verbose_ping & quiet_ping functions. + - Bump up version to 0.2 + ------------------ + 5 Aug 2021 - Modified by Ali Razmjoo Qalaei (Reformat the code and more human readable) + """ + icmp_socket = socket.getprotobyname("icmp") + socket_connection = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp_socket) + random_integer = os.getpid() & 0xFFFF + icmp_echo_request = 8 + # Make a dummy header with a 0 checksum. + dummy_checksum = 0 + header = struct.pack("bbHHh", icmp_echo_request, 0, dummy_checksum, random_integer, 1) + data = ( + struct.pack("d", time.time()) + + struct.pack("d", time.time()) + + str((76 - struct.calcsize("d")) * "Q").encode() + ) # packet size = 76 (removed 8 bytes size of header) + source_string = header + data + # Calculate the checksum on the data and the dummy header. + calculate_data = 0 + max_size = (len(source_string) / 2) * 2 + counter = 0 + while counter < max_size: + calculate_data += source_string[counter + 1] * 256 + source_string[counter] + calculate_data = calculate_data & 0xFFFFFFFF # Necessary? + counter += 2 + + if max_size < len(source_string): + calculate_data += source_string[len(source_string) - 1] + calculate_data = calculate_data & 0xFFFFFFFF # Necessary? + + calculate_data = (calculate_data >> 16) + (calculate_data & 0xFFFF) + calculate_data = calculate_data + (calculate_data >> 16) + calculated_data = ~calculate_data & 0xFFFF + + # Swap bytes. Bugger me if I know why. + dummy_checksum = calculated_data >> 8 | (calculated_data << 8 & 0xFF00) + + header = struct.pack( + "bbHHh", + icmp_echo_request, + 0, + socket.htons(dummy_checksum), + random_integer, + 1, + ) + socket_connection.sendto( + header + data, (socket.gethostbyname(host), 1) + ) # Don't know about the 1 + + while True: + started_select = time.time() + what_ready = select.select([socket_connection], [], [], timeout) + how_long_in_select = time.time() - started_select + if not what_ready[0]: # Timeout + break + time_received = time.time() + received_packet, address = socket_connection.recvfrom(1024) + icmp_header = received_packet[20:28] + ( + packet_type, + packet_code, + packet_checksum, + packet_id, + packet_sequence, + ) = struct.unpack("bbHHh", icmp_header) + if packet_id == random_integer: + packet_bytes = struct.calcsize("d") + time_sent = struct.unpack("d", received_packet[28 : 28 + packet_bytes])[0] + delay = time_received - time_sent + break + + timeout = timeout - how_long_in_select + if timeout <= 0: + break + socket_connection.close() + return {"host": host, "response_time": delay, "ssl_flag": False} + + +class SocketEngine(BaseEngine): + library = SocketLibrary + + def response_conditions_matched(self, sub_step, response): + conditions = sub_step["response"]["conditions"] + condition_type = sub_step["response"]["condition_type"] + condition_results = {} + if sub_step["method"] == "tcp_connect_only": + return response + if sub_step["method"] == "tcp_connect_send_and_receive": + if response: + received_content = response["response"] + for condition in conditions: + regex = re.findall( + re.compile(conditions[condition]["regex"]), received_content + ) + reverse = conditions[condition]["reverse"] + condition_results[condition] = reverse_and_regex_condition(regex, reverse) + for condition in copy.deepcopy(condition_results): + if not condition_results[condition]: + del condition_results[condition] + if "open_port" in condition_results and len(condition_results) > 1: + del condition_results["open_port"] + del conditions["open_port"] + if condition_type == "and": + return condition_results if len(condition_results) == len(conditions) else [] + if condition_type == "or": + return condition_results if condition_results else [] + return [] + if sub_step["method"] == "socket_icmp": + return response + return [] + + def apply_extra_data(self, sub_step, response): + sub_step["response"]["ssl_flag"] = ( + response["ssl_flag"] if isinstance(response, dict) else False + ) + sub_step["response"]["conditions_results"] = self.response_conditions_matched( + sub_step, response + ) diff --git a/nettacker/core/lib/ssh.py b/nettacker/core/lib/ssh.py new file mode 100644 index 000000000..e8b41d5b8 --- /dev/null +++ b/nettacker/core/lib/ssh.py @@ -0,0 +1,39 @@ +import logging + +from paramiko import SSHClient, AutoAddPolicy +from paramiko.auth_strategy import NoneAuth, Password + +from nettacker.core.lib.base import BaseEngine, BaseLibrary + +logging.getLogger("paramiko.transport").disabled = True + + +class SshLibrary(BaseLibrary): + def brute_force(self, *args, **kwargs): + host = kwargs["host"] + port = kwargs["port"] + username = kwargs["username"] + password = kwargs["password"] + + connection = SSHClient() + connection.set_missing_host_key_policy(AutoAddPolicy()) + connection.connect( + **{ + "hostname": host, + "port": port, + "auth_strategy": Password(username=username, password_getter=lambda: password) + if password + else NoneAuth(username=username), + } + ) + + return { + "host": host, + "port": port, + "username": username, + "password": password, + } + + +class SshEngine(BaseEngine): + library = SshLibrary diff --git a/nettacker/core/lib/telnet.py b/nettacker/core/lib/telnet.py new file mode 100644 index 000000000..40055a325 --- /dev/null +++ b/nettacker/core/lib/telnet.py @@ -0,0 +1,26 @@ +import telnetlib + +from nettacker.core.lib.base import BaseEngine, BaseLibrary + + +class TelnetLibrary(BaseLibrary): + client = telnetlib.Telnet + + def brute_force(host, port, username, password, timeout): + connection = telnetlib.Telnet(host, port, timeout) + connection.read_until(b"login: ") + connection.write(username + "\n") + connection.read_until(b"Password: ") + connection.write(password + "\n") + connection.close() + + return { + "host": host, + "port": port, + "username": username, + "password": password, + } + + +class TelnetEngine(BaseEngine): + library = TelnetLibrary diff --git a/nettacker/core/messages.py b/nettacker/core/messages.py new file mode 100644 index 000000000..9f07cb730 --- /dev/null +++ b/nettacker/core/messages.py @@ -0,0 +1,71 @@ +import sys +from io import StringIO + +import yaml + +from nettacker.config import Config +from nettacker.core.utils.common import find_args_value + + +def application_language(): + if "-L" in sys.argv: + language = find_args_value("-L") or "en" + elif "--language" in sys.argv: + language = find_args_value("--language") or "en" + else: + language = Config.settings.language + if language not in get_languages(): + language = "en" + return language + + +def load_yaml(filename): + return yaml.load(StringIO(open(filename, "r").read()), Loader=yaml.FullLoader) + + +def get_languages(): + """ + Get available languages + + Returns: + an array of languages + """ + languages_list = [] + + for language in Config.path.locale_dir.glob("*.yaml"): + languages_list.append(str(language).split("/")[-1].split(".")[0]) + return list(set(languages_list)) + + +class load_message: + def __init__(self): + self.language = application_language() + self.messages = load_yaml( + "{messages_path}/{language}.yaml".format( + messages_path=Config.path.locale_dir, language=self.language + ) + ) + if self.language != "en": + self.messages_en = load_yaml( + "{messages_path}/en.yaml".format(messages_path=Config.path.locale_dir) + ) + for message_id in self.messages_en: + if message_id not in self.messages: + self.messages[message_id] = self.messages_en[message_id] + + +message_cache = load_message().messages + + +def messages(msg_id): + """ + load a message from message library with specified language + + Args: + msg_id: message id + + Returns: + the message content in the selected language if + message found otherwise return message in English + """ + return message_cache[str(msg_id)] diff --git a/nettacker/core/module.py b/nettacker/core/module.py new file mode 100644 index 000000000..032b82d1d --- /dev/null +++ b/nettacker/core/module.py @@ -0,0 +1,181 @@ +import copy +import importlib +import json +import os +import time +from threading import Thread + +from nettacker import logger +from nettacker.config import Config +from nettacker.core.messages import messages as _ +from nettacker.core.template import TemplateLoader +from nettacker.core.utils.common import expand_module_steps, wait_for_threads_to_finish +from nettacker.database.db import find_events + +log = logger.get_logger() + + +class Module: + def __init__( + self, + module_name, + options, + target, + scan_id, + process_number, + thread_number, + total_number_threads, + ): + self.module_name = module_name + self.process_number = process_number + self.module_thread_number = thread_number + self.total_module_thread_number = total_number_threads + + self.module_inputs = options.__dict__ + self.module_inputs["target"] = target + + if options.modules_extra_args: + for module_extra_args in self.module_inputs["modules_extra_args"]: + self.module_inputs[module_extra_args] = self.module_inputs["modules_extra_args"][ + module_extra_args + ] + self.target = target + + self.scan_id = scan_id + self.skip_service_discovery = options.skip_service_discovery + + self.discovered_services = None + self.ignored_core_modules = ["subdomain_scan", "icmp_scan", "port_scan"] + + contents = TemplateLoader("port_scan", {"target": ""}).load() + self.service_discovery_signatures = list( + set(contents["payloads"][0]["steps"][0]["response"]["conditions"].keys()) + ) + + self.libraries = [ + module_protocol.split(".py")[0] + for module_protocol in os.listdir(Config.path.module_protocols_dir) + if module_protocol.endswith(".py") + and module_protocol not in {"__init__.py", "base.py"} + ] + + def load(self): + self.module_content = TemplateLoader(self.module_name, self.module_inputs).load() + if not self.skip_service_discovery and self.module_name not in self.ignored_core_modules: + services = {} + for service in find_events(self.target, "port_scan", self.scan_id): + service_event = json.loads(service.json_event) + port = service_event["port"] + protocols = service_event["response"]["conditions_results"].keys() + for protocol in protocols: + if protocol and protocol in self.libraries: + if protocol in services: + services[protocol].append(port) + else: + services[protocol] = [port] + self.discovered_services = copy.deepcopy(services) + index_payload = 0 + for payload in copy.deepcopy(self.module_content["payloads"]): + if ( + payload["library"] not in self.discovered_services + and payload["library"] in self.service_discovery_signatures + ): + del self.module_content["payloads"][index_payload] + index_payload -= 1 + else: + index_step = 0 + for step in copy.deepcopy( + self.module_content["payloads"][index_payload]["steps"] + ): + step = TemplateLoader.parse( + step, {"port": self.discovered_services[payload["library"]]} + ) + self.module_content["payloads"][index_payload]["steps"][index_step] = step + index_step += 1 + index_payload += 1 + + def generate_loops(self): + self.module_content["payloads"] = expand_module_steps(self.module_content["payloads"]) + + def sort_loops(self): + steps = [] + for index in range(len(self.module_content["payloads"])): + for step in copy.deepcopy(self.module_content["payloads"][index]["steps"]): + if "dependent_on_temp_event" not in step[0]["response"]: + steps.append(step) + + for step in copy.deepcopy(self.module_content["payloads"][index]["steps"]): + if ( + "dependent_on_temp_event" in step[0]["response"] + and "save_to_temp_events_only" in step[0]["response"] + ): + steps.append(step) + + for step in copy.deepcopy(self.module_content["payloads"][index]["steps"]): + if ( + "dependent_on_temp_event" in step[0]["response"] + and "save_to_temp_events_only" not in step[0]["response"] + ): + steps.append(step) + self.module_content["payloads"][index]["steps"] = steps + + def start(self): + active_threads = [] + + # counting total number of requests + total_number_of_requests = 0 + for payload in self.module_content["payloads"]: + if payload["library"] not in self.libraries: + log.warn(_("library_not_supported").format(payload["library"])) + return None + for step in payload["steps"]: + total_number_of_requests += len(step) + + request_number_counter = 0 + for payload in self.module_content["payloads"]: + library = payload["library"] + engine = getattr( + importlib.import_module(f"nettacker.core.lib.{library.lower()}"), + f"{library.capitalize()}Engine", + )() + + for step in payload["steps"]: + for sub_step in step: + thread = Thread( + target=engine.run, + args=( + sub_step, + self.module_name, + self.target, + self.scan_id, + self.module_inputs, + self.process_number, + self.module_thread_number, + self.total_module_thread_number, + request_number_counter, + total_number_of_requests, + ), + ) + thread.name = f"{self.target} -> {self.module_name} -> {sub_step}" + request_number_counter += 1 + log.verbose_event_info( + _("sending_module_request").format( + self.process_number, + self.module_name, + self.target, + self.module_thread_number, + self.total_module_thread_number, + request_number_counter, + total_number_of_requests, + ) + ) + thread.start() + time.sleep(self.module_inputs["time_sleep_between_requests"]) + active_threads.append(thread) + wait_for_threads_to_finish( + active_threads, + maximum=self.module_inputs["thread_per_host"], + terminable=True, + ) + + wait_for_threads_to_finish(active_threads, maximum=None, terminable=True) diff --git a/core/readme.md b/nettacker/core/readme.md similarity index 100% rename from core/readme.md rename to nettacker/core/readme.md diff --git a/core/socks_proxy.py b/nettacker/core/socks_proxy.py similarity index 50% rename from core/socks_proxy.py rename to nettacker/core/socks_proxy.py index f9fb8f04e..7f470b08a 100644 --- a/core/socks_proxy.py +++ b/nettacker/core/socks_proxy.py @@ -11,29 +11,30 @@ def getaddrinfo(*args): Returns: getaddrinfo """ - return [(socket.AF_INET, socket.SOCK_STREAM, 6, '', (args[0], args[1]))] + return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", (args[0], args[1]))] def set_socks_proxy(socks_proxy): if socks_proxy: import socks - socks_version = socks.SOCKS5 if socks_proxy.startswith('socks5://') else socks.SOCKS4 - socks_proxy = socks_proxy.split('://')[1] if '://' in socks_proxy else socks_proxy - if '@' in socks_proxy: - socks_username = socks_proxy.split(':')[0] - socks_password = socks_proxy.split(':')[1].split('@')[0] + + socks_version = socks.SOCKS5 if socks_proxy.startswith("socks5://") else socks.SOCKS4 + socks_proxy = socks_proxy.split("://")[1] if "://" in socks_proxy else socks_proxy + if "@" in socks_proxy: + socks_username = socks_proxy.split(":")[0] + socks_password = socks_proxy.split(":")[1].split("@")[0] socks.set_default_proxy( socks_version, - str(socks_proxy.rsplit('@')[1].rsplit(':')[0]), # hostname - int(socks_proxy.rsplit(':')[-1]), # port + str(socks_proxy.rsplit("@")[1].rsplit(":")[0]), # hostname + int(socks_proxy.rsplit(":")[-1]), # port username=socks_username, - password=socks_password + password=socks_password, ) else: socks.set_default_proxy( socks_version, - str(socks_proxy.rsplit(':')[0]), # hostname - int(socks_proxy.rsplit(':')[1]) # port + str(socks_proxy.rsplit(":")[0]), # hostname + int(socks_proxy.rsplit(":")[1]), # port ) return socks.socksocket, getaddrinfo else: diff --git a/nettacker/core/template.py b/nettacker/core/template.py new file mode 100644 index 000000000..ab6207237 --- /dev/null +++ b/nettacker/core/template.py @@ -0,0 +1,42 @@ +import copy + +import yaml + +from nettacker.config import Config + + +class TemplateLoader: + def __init__(self, name, inputs=None) -> None: + self.name = name + self.inputs = inputs or {} + + @staticmethod + def parse(module_content, module_inputs): + if isinstance(module_content, dict): + for key in copy.deepcopy(module_content): + if key in module_inputs: + if module_inputs[key]: + module_content[key] = module_inputs[key] + elif isinstance(module_content[key], (dict, list)): + module_content[key] = TemplateLoader.parse(module_content[key], module_inputs) + elif isinstance(module_content, list): + array_index = 0 + for key in copy.deepcopy(module_content): + module_content[array_index] = TemplateLoader.parse(key, module_inputs) + array_index += 1 + + return module_content + + def open(self): + module_name_parts = self.name.split("_") + action = module_name_parts[-1] + library = "_".join(module_name_parts[:-1]) + + with open(Config.path.modules_dir / action / f"{library}.yaml") as yaml_file: + return yaml_file.read() + + def format(self): + return self.open().format(**self.inputs) + + def load(self): + return self.parse(yaml.safe_load(self.format()), self.inputs) diff --git a/nettacker/core/utils/__init__.py b/nettacker/core/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nettacker/core/utils/common.py b/nettacker/core/utils/common.py new file mode 100644 index 000000000..a54d561f0 --- /dev/null +++ b/nettacker/core/utils/common.py @@ -0,0 +1,352 @@ +import copy +import ctypes +import datetime +import hashlib +import importlib +import math +import multiprocessing +import random +import re +import string +import sys +import time + +from nettacker import logger + +log = logger.get_logger() + + +def replace_dependent_response(log, response_dependent): + """The `response_dependent` is needed for `eval` below.""" + if str(log): + key_name = re.findall(re.compile("response_dependent\\['\\S+\\]"), log) + for i in key_name: + try: + key_value = eval(i) + except Exception: + key_value = "response dependent error" + log = log.replace(i, " ".join(key_value)) + return log + + +def merge_logs_to_list(result, log_list=[]): + if isinstance(result, dict): + for i in result: + if "log" == i: + log_list.append(result["log"]) + else: + merge_logs_to_list(result[i], log_list) + return list(set(log_list)) + + +def reverse_and_regex_condition(regex, reverse): + if regex: + if reverse: + return [] + return list(set(regex)) + else: + if reverse: + return True + return [] + + +def wait_for_threads_to_finish(threads, maximum=None, terminable=False, sub_process=False): + while threads: + try: + for thread in threads: + if not thread.is_alive(): + threads.remove(thread) + if maximum and len(threads) < maximum: + break + time.sleep(0.01) + except KeyboardInterrupt: + if terminable: + for thread in threads: + terminate_thread(thread) + if sub_process: + for thread in threads: + thread.kill() + return False + return True + + +def terminate_thread(thread, verbose=True): + """ + kill a thread https://stackoverflow.com/a/15274929 + Args: + thread: an alive thread + verbose: verbose mode/boolean + Returns: + True/None + """ + + if verbose: + log.info("killing {0}".format(thread.name)) + if not thread.is_alive(): + return + + exc = ctypes.py_object(SystemExit) + res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread.ident), exc) + if res == 0: + raise ValueError("nonexistent thread id") + elif res > 1: + # if it returns a number greater than one, you're in trouble, + # and you should call it again with exc=NULL to revert the effect + ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None) + raise SystemError("PyThreadState_SetAsyncExc failed") + return True + + +def find_args_value(args_name): + try: + return sys.argv[sys.argv.index(args_name) + 1] + except Exception: + return None + + +def re_address_repeaters_key_name(key_name): + return "".join(["['" + _key + "']" for _key in key_name.split("/")[:-1]]) + + +def generate_new_sub_steps(sub_steps, data_matrix, arrays): + original_sub_steps = copy.deepcopy(sub_steps) + steps_array = [] + for array in data_matrix: + array_name_position = 0 + for array_name in arrays: + for sub_step in sub_steps: + exec( + "original_sub_steps{key_name} = {matrix_value}".format( + key_name=re_address_repeaters_key_name(array_name), + matrix_value='"' + str(array[array_name_position]) + '"' + if isinstance(array[array_name_position], int) + or isinstance(array[array_name_position], str) + else array[array_name_position], + ) + ) + array_name_position += 1 + steps_array.append(copy.deepcopy(original_sub_steps)) + return steps_array + + +def find_repeaters(sub_content, root, arrays): + if isinstance(sub_content, dict) and "nettacker_fuzzer" not in sub_content: + temporary_content = copy.deepcopy(sub_content) + original_root = root + for key in sub_content: + root = original_root + root += key + "/" + temporary_content[key], _root, arrays = find_repeaters(sub_content[key], root, arrays) + sub_content = copy.deepcopy(temporary_content) + root = original_root + if (not isinstance(sub_content, (bool, int, float))) and ( + isinstance(sub_content, list) or "nettacker_fuzzer" in sub_content + ): + arrays[root] = sub_content + return (sub_content, root, arrays) if root != "" else arrays + + +class value_to_class: + def __init__(self, value): + self.value = value + + +def class_to_value(arrays): + original_arrays = copy.deepcopy(arrays) + array_index = 0 + for array in arrays: + value_index = 0 + for value in array: + if isinstance(value, value_to_class): + original_arrays[array_index][value_index] = value.value + value_index += 1 + array_index += 1 + return original_arrays + + +def generate_and_replace_md5(content): + # todo: make it betetr and document it + md5_content = content.split("NETTACKER_MD5_GENERATOR_START")[1].split( + "NETTACKER_MD5_GENERATOR_STOP" + )[0] + md5_content_backup = md5_content + if isinstance(md5_content, str): + md5_content = md5_content.encode() + md5_hash = hashlib.md5(md5_content).hexdigest() + return content.replace( + "NETTACKER_MD5_GENERATOR_START" + md5_content_backup + "NETTACKER_MD5_GENERATOR_STOP", + md5_hash, + ) + + +def arrays_to_matrix(arrays): + import numpy + + return ( + numpy.array(numpy.meshgrid(*[arrays[array_name] for array_name in arrays])) + .T.reshape(-1, len(arrays.keys())) + .tolist() + ) + + +def string_to_bytes(string): + return string.encode() + + +AVAILABLE_DATA_FUNCTIONS = { + "passwords": {"read_from_file"}, + "paths": {"read_from_file"}, + "urls": {"read_from_file"}, +} + + +def fuzzer_function_read_file_as_array(filename): + from nettacker.config import PathConfig + + return open(PathConfig().payloads_dir / filename).read().split("\n") + + +def apply_data_functions(data): + def apply_data_functions_new(): + if item not in AVAILABLE_DATA_FUNCTIONS: + return + + for fn_name in data[item]: + if fn_name in AVAILABLE_DATA_FUNCTIONS[item]: + fn = getattr(importlib.import_module("nettacker.core.fuzzer"), fn_name) + if fn is not None: + original_data[item] = fn(data[item][fn_name]) + + def apply_data_functions_old(): + function_results = {} + globals().update(locals()) + exec( + "fuzzer_function = {fuzzer_function}".format(fuzzer_function=data[item]), + globals(), + function_results, + ) + original_data[item] = function_results["fuzzer_function"] + + original_data = copy.deepcopy(data) + for item in data: + if isinstance((data[item]), str) and data[item].startswith("fuzzer_function"): + apply_data_functions_old() + else: + apply_data_functions_new() + + return original_data + + +def fuzzer_repeater_perform(arrays): + original_arrays = copy.deepcopy(arrays) + for array_name in arrays: + if "nettacker_fuzzer" not in arrays[array_name]: + continue + + data = arrays[array_name]["nettacker_fuzzer"]["data"] + data_matrix = arrays_to_matrix(apply_data_functions(data)) + prefix = arrays[array_name]["nettacker_fuzzer"]["prefix"] + input_format = arrays[array_name]["nettacker_fuzzer"]["input_format"] + interceptors = copy.deepcopy(arrays[array_name]["nettacker_fuzzer"]["interceptors"]) + if interceptors: + interceptors = interceptors.split(",") + suffix = arrays[array_name]["nettacker_fuzzer"]["suffix"] + processed_array = [] + + for sub_data in data_matrix: + formatted_data = {} + index_input = 0 + for value in sub_data: + formatted_data[list(data.keys())[index_input]] = value + index_input += 1 + interceptors_function = "" + interceptors_function_processed = "" + + if interceptors: + interceptors_function += "interceptors_function_processed = " + for interceptor in interceptors[::-1]: + interceptors_function += "{interceptor}(".format(interceptor=interceptor) + interceptors_function += "input_format.format(**formatted_data)" + str( + ")" * interceptors_function.count("(") + ) + expected_variables = {} + globals().update(locals()) + exec(interceptors_function, globals(), expected_variables) + interceptors_function_processed = expected_variables[ + "interceptors_function_processed" + ] + else: + interceptors_function_processed = input_format.format(**formatted_data) + + processed_sub_data = interceptors_function_processed + if prefix: + processed_sub_data = prefix + processed_sub_data + if suffix: + processed_sub_data = processed_sub_data + suffix + processed_array.append(copy.deepcopy(processed_sub_data)) + original_arrays[array_name] = processed_array + + return original_arrays + + +def expand_module_steps(content): + return [expand_protocol(x) for x in copy.deepcopy(content)] + + +def expand_protocol(protocol): + protocol["steps"] = [expand_step(x) for x in protocol["steps"]] + return protocol + + +def expand_step(step): + arrays = fuzzer_repeater_perform(find_repeaters(step, "", {})) + if arrays: + return generate_new_sub_steps(step, class_to_value(arrays_to_matrix(arrays)), arrays) + else: + # Minimum 1 step in array + return [step] + + +def generate_random_token(length=10): + return "".join(random.choice(string.ascii_lowercase) for _ in range(length)) + + +def now(format="%Y-%m-%d %H:%M:%S"): + """ + get now date and time + Args: + format: the date and time model, default is "%Y-%m-%d %H:%M:%S" + + Returns: + the date and time of now + """ + return datetime.datetime.now().strftime(format) + + +def select_maximum_cpu_core(mode): + cpu_count = multiprocessing.cpu_count() + + if cpu_count - 1 == 0: + return 1 + + mode_core_mapping = { + "maximum": cpu_count - 1, + "high": cpu_count / 2, + "normal": cpu_count / 4, + "low": cpu_count / 8, + } + rounded = math.ceil if mode == "high" else math.floor + + return int(max((rounded(mode_core_mapping.get(mode, 1)), 1))) + + +def sort_dictionary(dictionary): + etc_flag = "..." in dictionary + if etc_flag: + del dictionary["..."] + sorted_dictionary = {} + for key in sorted(dictionary): + sorted_dictionary[key] = dictionary[key] + if etc_flag: + sorted_dictionary["..."] = {} + return sorted_dictionary diff --git a/nettacker/core/utils/time.py b/nettacker/core/utils/time.py new file mode 100644 index 000000000..48a4319da --- /dev/null +++ b/nettacker/core/utils/time.py @@ -0,0 +1,13 @@ +import datetime + + +def now(format="%Y-%m-%d %H:%M:%S"): + """ + get now date and time + Args: + format: the date and time model, default is "%Y-%m-%d %H:%M:%S" + + Returns: + the date and time of now + """ + return datetime.datetime.now().strftime(format) diff --git a/nettacker/database/__init__.py b/nettacker/database/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/database/db.py b/nettacker/database/db.py similarity index 61% rename from database/db.py rename to nettacker/database/db.py index 1d643a981..0ddf96dde 100644 --- a/database/db.py +++ b/nettacker/database/db.py @@ -1,26 +1,17 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - import json import time -from flask import jsonify + from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -from database.models import (HostsLog, - Report, - TempEvents) -from core.alert import warn -from core.alert import verbose_info -from core.alert import messages -from api.api_core import structure -from config import nettacker_database_config - -DB = nettacker_database_config()["DB"] -USER = nettacker_database_config()["USERNAME"] -PASSWORD = nettacker_database_config()["PASSWORD"] -HOST = nettacker_database_config()["HOST"] -PORT = nettacker_database_config()["PORT"] -DATABASE = nettacker_database_config()["DATABASE"] + +from nettacker import logger +from nettacker.api.helpers import structure +from nettacker.config import Config +from nettacker.core.messages import messages +from nettacker.database.models import HostsLog, Report, TempEvents + +config = Config() +log = logger.get_logger() def db_inputs(connection_type): @@ -34,10 +25,13 @@ def db_inputs(connection_type): Returns: corresponding command to connect to the db """ + context = Config.db.as_dict() return { - "postgres": 'postgres+psycopg2://{0}:{1}@{2}:{3}/{4}'.format(USER, PASSWORD, HOST, PORT, DATABASE), - "mysql": 'mysql://{0}:{1}@{2}:{3}/{4}'.format(USER, PASSWORD, HOST, PORT, DATABASE), - "sqlite": 'sqlite:///{0}'.format(DATABASE) + "postgres": "postgres+psycopg2://{username}:{password}@{host}:{port}/{name}".format( + **context + ), + "mysql": "mysql://{username}:{password}@{host}:{port}/{name}".format(**context), + "sqlite": "sqlite:///{name}".format(**context), }[connection_type] @@ -48,25 +42,20 @@ def create_connection(): Returns: connection if success otherwise False """ - try: - db_engine = create_engine( - db_inputs(DB), - connect_args={ - 'check_same_thread': False - }, - pool_pre_ping=True - ) - Session = sessionmaker(bind=db_engine) - session = Session() - return session - except Exception: - warn(messages("database_connect_fail")) - return False + db_engine = create_engine( + db_inputs(Config.db.engine), + connect_args={"check_same_thread": False}, + pool_pre_ping=True, + ) + Session = sessionmaker(bind=db_engine) + + return Session() def send_submit_query(session): """ - a function to send submit based queries to db (such as insert and update or delete), it retries 100 times if + a function to send submit based queries to db + (such as insert and update or delete), it retries 100 times if connection returned an error. Args: @@ -82,15 +71,16 @@ def send_submit_query(session): return True except Exception: time.sleep(0.1) - except Exception as _: - warn(messages("database_connect_fail")) + except Exception: + log.warn(messages("database_connect_fail")) return False return False def submit_report_to_db(event): """ - this function created to submit the generated reports into db, the files are not stored in db, just the path! + this function created to submit the generated reports into db, the + files are not stored in db, just the path! Args: event: event log @@ -98,15 +88,13 @@ def submit_report_to_db(event): Returns: return True if submitted otherwise False """ - verbose_info(messages("inserting_report_db")) + log.verbose_info(messages("inserting_report_db")) session = create_connection() session.add( Report( date=event["date"], - scan_unique_id=event["scan_unique_id"], - report_path_filename=json.dumps( - event["options"]["report_path_filename"] - ), + scan_unique_id=event["scan_id"], + report_path_filename=event["options"]["report_path_filename"], options=json.dumps(event["options"]), ) ) @@ -115,7 +103,8 @@ def submit_report_to_db(event): def remove_old_logs(options): """ - this function remove old events (and duplicated) from database based on target, module, scan_unique_id + this function remove old events (and duplicated) + from nettacker.database based on target, module, scan_id Args: options: identifiers @@ -127,7 +116,7 @@ def remove_old_logs(options): session.query(HostsLog).filter( HostsLog.target == options["target"], HostsLog.module_name == options["module_name"], - HostsLog.scan_unique_id != options["scan_unique_id"] + HostsLog.scan_unique_id != options["scan_id"], ).delete(synchronize_session=False) return send_submit_query(session) @@ -149,15 +138,15 @@ def submit_logs_to_db(log): target=log["target"], date=log["date"], module_name=log["module_name"], - scan_unique_id=log["scan_unique_id"], + scan_unique_id=log["scan_id"], port=json.dumps(log["port"]), event=json.dumps(log["event"]), - json_event=json.dumps(log["json_event"]) + json_event=json.dumps(log["json_event"]), ) ) return send_submit_query(session) else: - warn(messages("invalid_json_type_to_db").format(log)) + log.warn(messages("invalid_json_type_to_db").format(log)) return False @@ -178,27 +167,27 @@ def submit_temp_logs_to_db(log): target=log["target"], date=log["date"], module_name=log["module_name"], - scan_unique_id=log["scan_unique_id"], + scan_unique_id=log["scan_id"], event_name=log["event_name"], port=json.dumps(log["port"]), event=json.dumps(log["event"]), - data=json.dumps(log["data"]) + data=json.dumps(log["data"]), ) ) return send_submit_query(session) else: - warn(messages("invalid_json_type_to_db").format(log)) + log.warn(messages("invalid_json_type_to_db").format(log)) return False -def find_temp_events(target, module_name, scan_unique_id, event_name): +def find_temp_events(target, module_name, scan_id, event_name): """ select all events by scan_unique id, target, module_name Args: target: target module_name: module name - scan_unique_id: unique scan identifier + scan_id: unique scan identifier event_name: event_name Returns: @@ -208,44 +197,52 @@ def find_temp_events(target, module_name, scan_unique_id, event_name): try: for _ in range(1, 100): try: - return session.query(TempEvents).filter( - TempEvents.target == target, - TempEvents.module_name == module_name, - TempEvents.scan_unique_id == scan_unique_id, - TempEvents.event_name == event_name - ).first() + return ( + session.query(TempEvents) + .filter( + TempEvents.target == target, + TempEvents.module_name == module_name, + TempEvents.scan_unique_id == scan_id, + TempEvents.event_name == event_name, + ) + .first() + ) except Exception: time.sleep(0.1) - except Exception as _: - warn(messages("database_connect_fail")) + except Exception: + log.warn(messages("database_connect_fail")) return False return False - -def find_events(target, module_name, scan_unique_id): +def find_events(target, module_name, scan_id): """ select all events by scan_unique id, target, module_name Args: target: target module_name: module name - scan_unique_id: unique scan identifier + scan_id: unique scan identifier Returns: an array with JSON events or an empty array """ session = create_connection() - return session.query(HostsLog).filter( - HostsLog.target == target, - HostsLog.module_name == module_name, - HostsLog.scan_unique_id == scan_unique_id - ).all() + return ( + session.query(HostsLog) + .filter( + HostsLog.target == target, + HostsLog.module_name == module_name, + HostsLog.scan_unique_id == scan_id, + ) + .all() + ) def select_reports(page): """ - this function created to crawl into submitted results, it shows last 10 results submitted in the database. + this function created to crawl into submitted results, + it shows last 10 results submitted in the database. you may change the page (default 1) to go to next/previous page. Args: @@ -257,16 +254,16 @@ def select_reports(page): selected = [] session = create_connection() try: - search_data = session.query(Report).order_by( - Report.id.desc() - ).offset((page * 10) - 10).limit(10) + search_data = ( + session.query(Report).order_by(Report.id.desc()).offset((page * 10) - 10).limit(10) + ) for data in search_data: tmp = { "id": data.id, "date": data.date, - "scan_unique_id": data.scan_unique_id, + "scan_id": data.scan_unique_id, "report_path_filename": data.report_path_filename, - "options": json.loads(data.options) + "options": json.loads(data.options), } selected.append(tmp) except Exception: @@ -285,20 +282,15 @@ def get_scan_result(id): result file content (TEXT, HTML, JSON) if success otherwise and error in JSON type. """ session = create_connection() - try: - try: - filename = session.query(Report).filter_by(id=id).first().report_path_filename[1:-1] - # for some reason filename saved like "filename" with double quotes in the beginning and end - return filename, open(str(filename), 'rb').read() - except Exception: - return jsonify(structure(status="error", msg="cannot find the file!")), 404 - except Exception: - return jsonify(structure(status="error", msg="database error!")), 500 + filename = session.query(Report).filter_by(id=id).first().report_path_filename + + return filename, open(str(filename), "rb").read() def last_host_logs(page): """ - this function created to select the last 10 events from the database. you can goto next page by changing page value. + this function created to select the last 10 events from the database. + you can goto next page by changing page value. Args: page: page number @@ -312,43 +304,45 @@ def last_host_logs(page): "target": host.target, "info": { "module_name": [ - _.module_name for _ in session.query(HostsLog).filter( - HostsLog.target == host.target - ).group_by(HostsLog.module_name).all() + _.module_name + for _ in session.query(HostsLog) + .filter(HostsLog.target == host.target) + .group_by(HostsLog.module_name) + .all() ], - "date": session.query(HostsLog).filter( - HostsLog.target == host.target - ).order_by( - HostsLog.id.desc() - ).first().date, + "date": session.query(HostsLog) + .filter(HostsLog.target == host.target) + .order_by(HostsLog.id.desc()) + .first() + .date, # "options": [ # unnecessary data? # _.options for _ in session.query(HostsLog).filter( # HostsLog.target == host.target # ).all() # ], "events": [ - _.event for _ in session.query(HostsLog).filter( - HostsLog.target == host.target - ).all() + _.event + for _ in session.query(HostsLog).filter(HostsLog.target == host.target).all() ], - } - } for host in session.query(HostsLog).group_by(HostsLog.target).order_by(HostsLog.id.desc()).offset( - ( - page * 10 - ) - 10 - ).limit(10) + }, + } + for host in session.query(HostsLog) + .group_by(HostsLog.target) + .order_by(HostsLog.id.desc()) + .offset((page * 10) - 10) + .limit(10) ] if len(hosts) == 0: return structure(status="finished", msg="No more search results") return hosts -def get_logs_by_scan_unique_id(scan_unique_id): +def get_logs_by_scan_id(scan_id): """ select all events by scan id hash Args: - scan_unique_id: scan id hash + scan_id: scan id hash Returns: an array with JSON events or an empty array @@ -356,7 +350,7 @@ def get_logs_by_scan_unique_id(scan_unique_id): session = create_connection() return [ { - "scan_unique_id": scan_unique_id, + "scan_id": scan_id, "target": log.target, "module_name": log.module_name, "date": str(log.date), @@ -364,9 +358,7 @@ def get_logs_by_scan_unique_id(scan_unique_id): "event": json.loads(log.event), "json_event": log.json_event, } - for log in session.query(HostsLog).filter( - HostsLog.scan_unique_id == scan_unique_id - ).all() + for log in session.query(HostsLog).filter(HostsLog.scan_unique_id == scan_id).all() ] @@ -386,7 +378,7 @@ def logs_to_report_json(target): logs = session.query(HostsLog).filter(HostsLog.target == target) for log in logs: data = { - "scan_unique_id": log.scan_unique_id, + "scan_id": log.scan_unique_id, "target": log.target, "port": json.loads(log.port), "event": json.loads(log.event), @@ -408,55 +400,55 @@ def logs_to_report_html(target): Returns: HTML report """ - from core.graph import build_graph - from lib.html_log import log_data + from nettacker.core.graph import build_graph + from nettacker.lib.html_log import log_data + session = create_connection() logs = [ { "date": log.date, "target": log.target, "module_name": log.module_name, - "scan_unique_id": log.scan_unique_id, + "scan_id": log.scan_unique_id, "port": log.port, "event": log.event, - "json_event": log.json_event - } for log in session.query(HostsLog).filter( - HostsLog.target == target - ).all() + "json_event": log.json_event, + } + for log in session.query(HostsLog).filter(HostsLog.target == target).all() ] - html_graph = build_graph( - "d3_tree_v2_graph", - logs - ) + html_graph = build_graph("d3_tree_v2_graph", logs) html_content = log_data.table_title.format( html_graph, log_data.css_1, - 'date', - 'target', - 'module_name', - 'scan_unique_id', - 'port', - 'event', - 'json_event' + "date", + "target", + "module_name", + "scan_id", + "port", + "event", + "json_event", ) for event in logs: html_content += log_data.table_items.format( - event['date'], + event["date"], event["target"], - event['module_name'], - event['scan_unique_id'], - event['port'], - event['event'], - event['json_event'] + event["module_name"], + event["scan_id"], + event["port"], + event["event"], + event["json_event"], ) - html_content += log_data.table_end + '' + html_content += ( + log_data.table_end + '" + ) return html_content def search_logs(page, query): """ - search in events (host, date, port, module, category, description, username, password, scan_unique_id, scan_cmd) + search in events (host, date, port, module, category, description, + username, password, scan_id, scan_cmd) Args: page: page number @@ -468,17 +460,33 @@ def search_logs(page, query): session = create_connection() selected = [] try: - for host in session.query(HostsLog).filter( + for host in ( + session.query(HostsLog) + .filter( (HostsLog.target.like("%" + str(query) + "%")) | (HostsLog.date.like("%" + str(query) + "%")) | (HostsLog.module_name.like("%" + str(query) + "%")) | (HostsLog.port.like("%" + str(query) + "%")) | (HostsLog.event.like("%" + str(query) + "%")) | (HostsLog.scan_unique_id.like("%" + str(query) + "%")) - ).group_by(HostsLog.target).order_by(HostsLog.id.desc()).offset((page * 10) - 10).limit(10): - for data in session.query(HostsLog).filter(HostsLog.target == str(host.target)).group_by( - HostsLog.module_name, HostsLog.port, HostsLog.scan_unique_id, HostsLog.event - ).order_by(HostsLog.id.desc()).all(): + ) + .group_by(HostsLog.target) + .order_by(HostsLog.id.desc()) + .offset((page * 10) - 10) + .limit(10) + ): + for data in ( + session.query(HostsLog) + .filter(HostsLog.target == str(host.target)) + .group_by( + HostsLog.module_name, + HostsLog.port, + HostsLog.scan_unique_id, + HostsLog.event, + ) + .order_by(HostsLog.id.desc()) + .all() + ): n = 0 capture = None for selected_data in selected: @@ -493,8 +501,8 @@ def search_logs(page, query): "port": [], "date": [], "event": [], - "json_event": [] - } + "json_event": [], + }, } selected.append(tmp) n = 0 @@ -508,17 +516,11 @@ def search_logs(page, query): if data.date not in selected[capture]["info"]["date"]: selected[capture]["info"]["date"].append(data.date) if data.port not in selected[capture]["info"]["port"]: - selected[capture]["info"]["port"].append( - json.loads(data.port) - ) + selected[capture]["info"]["port"].append(json.loads(data.port)) if data.event not in selected[capture]["info"]["event"]: - selected[capture]["info"]["event"].append( - json.loads(data.event) - ) + selected[capture]["info"]["event"].append(json.loads(data.event)) if data.json_event not in selected[capture]["info"]["json_event"]: - selected[capture]["info"]["json_event"].append( - json.loads(data.json_event) - ) + selected[capture]["info"]["json_event"].append(json.loads(data.json_event)) except Exception: return structure(status="error", msg="database error!") if len(selected) == 0: diff --git a/database/models.py b/nettacker/database/models.py similarity index 72% rename from database/models.py rename to nettacker/database/models.py index 1dbddad7e..452ea7e6c 100644 --- a/database/models.py +++ b/nettacker/database/models.py @@ -1,20 +1,16 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - +from sqlalchemy import Column, Integer, Text, DateTime from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy import (Column, - Integer, - Text, - DateTime) Base = declarative_base() class Report(Base): """ - This class defines the table schema of the reports table. Any changes to the reports table need to be done here. + This class defines the table schema of the reports table. Any changes + to the reports table need to be done here. """ - __tablename__ = 'reports' + + __tablename__ = "reports" id = Column(Integer, primary_key=True, autoincrement=True) date = Column(DateTime) @@ -27,18 +23,17 @@ def __repr__(self): returns a printable representation of the object of the class Report """ return "".format( - self.id, - self.scan_unique_id, - self.date, - self.report_path_filename + self.id, self.scan_unique_id, self.date, self.report_path_filename ) class TempEvents(Base): """ - This class defines the table schema of the reports table. Any changes to the reports table need to be done here. + This class defines the table schema of the reports table. Any changes to + the reports table need to be done here. """ - __tablename__ = 'temp_events' + + __tablename__ = "temp_events" id = Column(Integer, primary_key=True, autoincrement=True) date = Column(DateTime) @@ -54,10 +49,10 @@ def __repr__(self): """ returns a printable representation of the object of the class Report """ - return ''' - - '''.format( + """.format( self.id, self.target, self.date, @@ -65,15 +60,17 @@ def __repr__(self): self.scan_unique_id, self.port, self.event, - self.data + self.data, ) class HostsLog(Base): """ - This class defines the table schema of the hosts_log table. Any changes to the reports hosts_log need to be done here. + This class defines the table schema of the hosts_log table. + Any changes to the reports hosts_log need to be done here. """ - __tablename__ = 'scan_events' + + __tablename__ = "scan_events" id = Column(Integer, primary_key=True, autoincrement=True) date = Column(DateTime) @@ -88,10 +85,10 @@ def __repr__(self): """ returns a printable representation of the object of the class HostsLog """ - return ''' - - '''.format( + """.format( self.id, self.target, self.date, @@ -99,5 +96,5 @@ def __repr__(self): self.scan_unique_id, self.port, self.event, - self.json_event + self.json_event, ) diff --git a/nettacker/database/mysql.py b/nettacker/database/mysql.py new file mode 100644 index 000000000..5f6284d76 --- /dev/null +++ b/nettacker/database/mysql.py @@ -0,0 +1,36 @@ +from sqlalchemy import create_engine + +from nettacker.config import Config +from nettacker.database.models import Base + + +def mysql_create_database(): + """ + when using mysql database, this is the function that is used to create the + database for the first time when you run the nettacker module. + """ + engine = create_engine( + "mysql://{username}:{password}@{host}:{port}".format(**Config.db.as_dict()) + ) + existing_databases = engine.execute("SHOW DATABASES;") + existing_databases = [d[0] for d in existing_databases] + + if Config.db.name not in existing_databases: + engine.execute("CREATE DATABASE {0} ".format(Config.db.name)) + + +def mysql_create_tables(): + """ + when using mysql database, this is the function that is used to create the + tables in the database for the first time when you run the nettacker module. + + Args: + None + + Returns: + True if success otherwise False + """ + db_engine = create_engine( + "mysql://{username}:{password}@{host}:{port}/{name}".format(**Config.db.as_dict()) + ) + Base.metadata.create_all(db_engine) diff --git a/nettacker/database/postgresql.py b/nettacker/database/postgresql.py new file mode 100644 index 000000000..65ce6b6a2 --- /dev/null +++ b/nettacker/database/postgresql.py @@ -0,0 +1,34 @@ +from sqlalchemy import create_engine +from sqlalchemy.exc import OperationalError + +from nettacker.config import Config +from nettacker.database.models import Base + + +def postgres_create_database(): + """ + when using postgres database, this is the function that is used to + create the database for the first time when you the nettacker run module. + """ + + try: + engine = create_engine( + "postgres+psycopg2://{username}:{password}@{host}:{port}/{name}".format( + **Config.db.as_dict() + ) + ) + Base.metadata.create_all(engine) + except OperationalError: + # if the database does not exist + engine = create_engine("postgres+psycopg2://postgres:postgres@localhost/postgres") + conn = engine.connect() + conn.execute("commit") + conn.execute(f"CREATE DATABASE {Config.db.name}") + conn.close() + + engine = create_engine( + "postgres+psycopg2://{username}:{password}@{host}:{port}/{name}".format( + **Config.db.as_dict() + ) + ) + Base.metadata.create_all(engine) diff --git a/database/readme.md b/nettacker/database/readme.md similarity index 98% rename from database/readme.md rename to nettacker/database/readme.md index 64b9bba81..c4171cb59 100644 --- a/database/readme.md +++ b/nettacker/database/readme.md @@ -1,8 +1,8 @@ -OWASP Nettacker Database Files -======================= -This folder mainly contains all the files which handle the database transactions for the OWASP Nettacker. - -* `db.py` contains the database transaction functions -* `models.py` contains the database structure layout -* `mysql_create.py` contains functions to create the db structure mentioned in `models.py` into a MySQL database -* `sqlite_create.py` contains functions to create the db structure mentioned in `models.py` into a SQLite database +OWASP Nettacker Database Files +======================= +This folder mainly contains all the files which handle the database transactions for the OWASP Nettacker. + +* `db.py` contains the database transaction functions +* `models.py` contains the database structure layout +* `mysql_create.py` contains functions to create the db structure mentioned in `models.py` into a MySQL database +* `sqlite_create.py` contains functions to create the db structure mentioned in `models.py` into a SQLite database diff --git a/nettacker/database/sqlite.py b/nettacker/database/sqlite.py new file mode 100644 index 000000000..9f8891691 --- /dev/null +++ b/nettacker/database/sqlite.py @@ -0,0 +1,17 @@ +from sqlalchemy import create_engine + +from nettacker.config import Config +from nettacker.database.models import Base + + +def sqlite_create_tables(): + """ + when using sqlite database, this is the function that is used to create + the database schema for the first time when you run the nettacker module. + + """ + db_engine = create_engine( + "sqlite:///{name}".format(**Config.db.as_dict()), + connect_args={"check_same_thread": False}, + ) + Base.metadata.create_all(db_engine) diff --git a/nettacker/lib/__init__.py b/nettacker/lib/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nettacker/lib/graph/__init__.py b/nettacker/lib/graph/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nettacker/lib/graph/d3_tree_v1/__init__.py b/nettacker/lib/graph/d3_tree_v1/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nettacker/lib/graph/d3_tree_v1/engine.py b/nettacker/lib/graph/d3_tree_v1/engine.py new file mode 100644 index 000000000..d2ed7dd65 --- /dev/null +++ b/nettacker/lib/graph/d3_tree_v1/engine.py @@ -0,0 +1,49 @@ +import json + +from nettacker.config import Config +from nettacker.core.messages import messages + + +def start(events): + """ + generate the d3_tree_v1_graph with events + + Args: + events: all events + + Returns: + a graph in HTML + """ + + # define a normalised_json + normalisedjson = {"name": "Started attack", "children": {}} + # get data for normalised_json + for event in events: + if event["target"] not in normalisedjson["children"]: + normalisedjson["children"].update({event["target"]: {}}) + normalisedjson["children"][event["target"]].update({event["module_name"]: []}) + + if event["module_name"] not in normalisedjson["children"][event["target"]]: + normalisedjson["children"][event["target"]].update({event["module_name"]: []}) + normalisedjson["children"][event["target"]][event["module_name"]].append( + f"target: {event['target']}, module_name: {event['module_name']}, port: " + f"{event['port']}, event: {event['event']}" + ) + # define a d3_structure_json + d3_structure = {"name": "Starting attack", "children": []} + # get data for normalised_json + for target in list(normalisedjson["children"].keys()): + for module_name in list(normalisedjson["children"][target].keys()): + for description in normalisedjson["children"][target][module_name]: + children_array = [{"name": module_name, "children": [{"name": description}]}] + d3_structure["children"].append({"name": target, "children": children_array}) + + data = ( + open(Config.path.web_static_dir / "report/d3_tree_v1.html") + .read() + .replace("__data_will_locate_here__", json.dumps(d3_structure)) + .replace("__title_to_replace__", messages("pentest_graphs")) + .replace("__description_to_replace__", messages("graph_message")) + .replace("__html_title_to_replace__", messages("nettacker_report")) + ) + return data diff --git a/lib/graph/d3_tree_v1/readme.md b/nettacker/lib/graph/d3_tree_v1/readme.md similarity index 100% rename from lib/graph/d3_tree_v1/readme.md rename to nettacker/lib/graph/d3_tree_v1/readme.md diff --git a/nettacker/lib/graph/d3_tree_v2/__init__.py b/nettacker/lib/graph/d3_tree_v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/graph/d3_tree_v2/engine.py b/nettacker/lib/graph/d3_tree_v2/engine.py similarity index 53% rename from lib/graph/d3_tree_v2/engine.py rename to nettacker/lib/graph/d3_tree_v2/engine.py index 1ce91c169..e17425f54 100644 --- a/lib/graph/d3_tree_v2/engine.py +++ b/nettacker/lib/graph/d3_tree_v2/engine.py @@ -1,10 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import string -import random -import json - - def start(events): """ generate the d3_tree_v2_graph with events (using d3_tree_v1_graph) @@ -15,10 +8,11 @@ def start(events): Returns: a graph in HTML """ - from lib.graph.d3_tree_v1.engine import start + from nettacker.lib.graph.d3_tree_v1.engine import start + return start(events).replace( - '''\t root.children.forEach(function(child){ + """\t root.children.forEach(function(child){ collapse(child); -\t });''', - '' +\t });""", + "", ) diff --git a/lib/graph/d3_tree_v2/readme.md b/nettacker/lib/graph/d3_tree_v2/readme.md similarity index 100% rename from lib/graph/d3_tree_v2/readme.md rename to nettacker/lib/graph/d3_tree_v2/readme.md diff --git a/lib/graph/readme.md b/nettacker/lib/graph/readme.md similarity index 100% rename from lib/graph/readme.md rename to nettacker/lib/graph/readme.md diff --git a/nettacker/lib/html_log/__init__.py b/nettacker/lib/html_log/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nettacker/lib/html_log/log_data.py b/nettacker/lib/html_log/log_data.py new file mode 100644 index 000000000..884239371 --- /dev/null +++ b/nettacker/lib/html_log/log_data.py @@ -0,0 +1,7 @@ +from nettacker.config import Config + +css_1 = open(Config.path.web_static_dir / "report/html_table.css").read() +json_parse_js = open(Config.path.web_static_dir / "report/json_parse.js").read() +table_end = open(Config.path.web_static_dir / "report/table_end.html").read() +table_items = open(Config.path.web_static_dir / "report/table_items.html").read() +table_title = open(Config.path.web_static_dir / "report/table_title.html").read() diff --git a/lib/html_log/readme.md b/nettacker/lib/html_log/readme.md similarity index 100% rename from lib/html_log/readme.md rename to nettacker/lib/html_log/readme.md diff --git a/nettacker/lib/icmp/__init__.py b/nettacker/lib/icmp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/icmp/engine.py b/nettacker/lib/icmp/engine.py similarity index 92% rename from lib/icmp/engine.py rename to nettacker/lib/icmp/engine.py index ac0cdd66c..e7943c28b 100644 --- a/lib/icmp/engine.py +++ b/nettacker/lib/icmp/engine.py @@ -94,7 +94,6 @@ import select import socket import struct -import sys import time # From /usr/include/linux/icmp.h; your milage may vary. @@ -112,25 +111,24 @@ def checksum_py3(source_string): while count < countTo: thisVal = source_string[count + 1] * 256 + source_string[count] sum = sum + thisVal - sum = sum & 0xffffffff # Necessary? + sum = sum & 0xFFFFFFFF # Necessary? count = count + 2 if countTo < len(source_string): sum = sum + source_string[len(source_string) - 1] - sum = sum & 0xffffffff # Necessary? + sum = sum & 0xFFFFFFFF # Necessary? - sum = (sum >> 16) + (sum & 0xffff) + sum = (sum >> 16) + (sum & 0xFFFF) sum = sum + (sum >> 16) answer = ~sum - answer = answer & 0xffff + answer = answer & 0xFFFF # Swap bytes. Bugger me if I know why. - answer = answer >> 8 | (answer << 8 & 0xff00) + answer = answer >> 8 | (answer << 8 & 0xFF00) return answer - def receive_one_ping(my_socket, id, timeout): """ Receive the ping from the socket. @@ -139,19 +137,17 @@ def receive_one_ping(my_socket, id, timeout): while True: started_select = time.time() what_ready = select.select([my_socket], [], [], time_left) - how_long_in_select = (time.time() - started_select) + how_long_in_select = time.time() - started_select if what_ready[0] == []: # Timeout return time_received = time.time() received_packet, addr = my_socket.recvfrom(1024) icmpHeader = received_packet[20:28] - type, code, checksum, packet_id, sequence = struct.unpack( - "bbHHh", icmpHeader - ) + type, code, checksum, packet_id, sequence = struct.unpack("bbHHh", icmpHeader) if packet_id == id: bytes = struct.calcsize("d") - time_sent = struct.unpack("d", received_packet[28:28 + bytes])[0] + time_sent = struct.unpack("d", received_packet[28 : 28 + bytes])[0] return time_received - time_sent time_left = time_left - how_long_in_select @@ -165,7 +161,7 @@ def send_one_ping(my_socket, dest_addr, id, psize): """ try: dest_addr = socket.gethostbyname(dest_addr) - except: + except Exception: return None # Remove header size from packet size psize = psize - 8 @@ -177,17 +173,14 @@ def send_one_ping(my_socket, dest_addr, id, psize): header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, id, 1) bytes = struct.calcsize("d") data = (psize - bytes) * "Q" - data = struct.pack("d", time.time( - )) + struct.pack("d", time.time()) + data.encode() + data = struct.pack("d", time.time()) + struct.pack("d", time.time()) + data.encode() # Calculate the checksum on the data and the dummy header. my_checksum = checksum_py3(header + data) # Now that we have the right checksum, we put that in. It's just easier # to make up a new header than to stuff it into the dummy. - header = struct.pack( - "bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), id, 1 - ) + header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), id, 1) packet = header + data my_socket.sendto(packet, (dest_addr, 1)) # Don't know about the 1 diff --git a/lib/icmp/readme.md b/nettacker/lib/icmp/readme.md similarity index 100% rename from lib/icmp/readme.md rename to nettacker/lib/icmp/readme.md diff --git a/lib/payloads/User-Agents/web_browsers_user_agents.txt b/nettacker/lib/payloads/User-Agents/web_browsers_user_agents.txt similarity index 100% rename from lib/payloads/User-Agents/web_browsers_user_agents.txt rename to nettacker/lib/payloads/User-Agents/web_browsers_user_agents.txt diff --git a/nettacker/lib/payloads/__init__.py b/nettacker/lib/payloads/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/payloads/passwords/top_1000_common_passwords.txt b/nettacker/lib/payloads/passwords/top_1000_common_passwords.txt similarity index 100% rename from lib/payloads/passwords/top_1000_common_passwords.txt rename to nettacker/lib/payloads/passwords/top_1000_common_passwords.txt diff --git a/lib/payloads/readme.md b/nettacker/lib/payloads/readme.md similarity index 100% rename from lib/payloads/readme.md rename to nettacker/lib/payloads/readme.md diff --git a/lib/payloads/wordlists/admin_wordlist.txt b/nettacker/lib/payloads/wordlists/admin_wordlist.txt similarity index 100% rename from lib/payloads/wordlists/admin_wordlist.txt rename to nettacker/lib/payloads/wordlists/admin_wordlist.txt diff --git a/lib/payloads/wordlists/pma_wordlist.txt b/nettacker/lib/payloads/wordlists/pma_wordlist.txt similarity index 100% rename from lib/payloads/wordlists/pma_wordlist.txt rename to nettacker/lib/payloads/wordlists/pma_wordlist.txt diff --git a/lib/payloads/wordlists/wp_plugin_small.txt b/nettacker/lib/payloads/wordlists/wp_plugin_small.txt similarity index 100% rename from lib/payloads/wordlists/wp_plugin_small.txt rename to nettacker/lib/payloads/wordlists/wp_plugin_small.txt diff --git a/lib/payloads/wordlists/wp_theme_small.txt b/nettacker/lib/payloads/wordlists/wp_theme_small.txt similarity index 100% rename from lib/payloads/wordlists/wp_theme_small.txt rename to nettacker/lib/payloads/wordlists/wp_theme_small.txt diff --git a/lib/payloads/wordlists/wp_timethumbs.txt b/nettacker/lib/payloads/wordlists/wp_timethumbs.txt similarity index 100% rename from lib/payloads/wordlists/wp_timethumbs.txt rename to nettacker/lib/payloads/wordlists/wp_timethumbs.txt diff --git a/lib/readme.md b/nettacker/lib/readme.md similarity index 100% rename from lib/readme.md rename to nettacker/lib/readme.md diff --git a/lib/messages/ar.yaml b/nettacker/locale/ar.yaml similarity index 99% rename from lib/messages/ar.yaml rename to nettacker/locale/ar.yaml index 57ffc6dc0..6f01b5e98 100644 --- a/lib/messages/ar.yaml +++ b/nettacker/locale/ar.yaml @@ -19,9 +19,9 @@ choose_scan_method: اختر طريقة المسح الضوئي {0} exclude_scan_method: اختر طريقة الفحص لاستبعاد {0} username_list: اسم المستخدم (أسماء) ، منفصلة مع "،" username_from_file: قراءة اسم المستخدم (ق) من الملف -password_seperator: كلمة (كلمات) المرور ، منفصلة مع "،" +password_separator: كلمة (كلمات) المرور ، منفصلة مع "،" read_passwords: قراءة كلمة (كلمات) من الملف -port_seperator: قائمة port (s) ، منفصلة مع "،" +port_separator: قائمة port (s) ، منفصلة مع "،" time_to_sleep: وقت للنوم بين كل طلب error_target: لا يمكن تحديد الهدف (الأهداف) error_target_file: "لا يمكن تحديد الهدف (الأهداف) ، غير قادر على فتح الملف: {0}" @@ -187,7 +187,7 @@ API_port: رقم منفذ واجهة برمجة التطبيقات API_debug: وضع تصحيح API API_access_key: مفتاح الوصول إلى واجهة برمجة التطبيقات white_list_API: فقط اسمح لمضيفي القائمة البيضاء بالاتصال بواجهة برمجة التطبيقات -define_whie_list: +define_white_list: "تعريف مضيفي القائمة البيضاء ، مع فصل ، (أمثلة: 127.0.0.1 ، 192.168.0.1/24 ، 10.0.0.1-10.0.0.255)" gen_API_access_log: توليد سجل وصول API diff --git a/lib/messages/bn.yaml b/nettacker/locale/bn.yaml similarity index 97% rename from lib/messages/bn.yaml rename to nettacker/locale/bn.yaml index e0ddaa4bb..84e114733 100644 --- a/lib/messages/bn.yaml +++ b/nettacker/locale/bn.yaml @@ -21,7 +21,7 @@ connection_retries: সংযোগ সময়সীমা (ডিফল্ট current_version: আপনি কোড নাম {3}{4}{5} সহ OWASP Nettacker সংস্করণ {0}{1}{2}{6} চালাচ্ছেন database_connect_fail: ডাটাবেস সংযোগ করতে পারে না! database_connection_failed: সংযোগ নির্বাচিত ডাটাবেস থেকে ব্যর্থ হয়েছে -define_whie_list: "হোয়াইট লিস্ট হোস্টকে সংজ্ঞায়িত করুন, এটি আলাদা করুন, (উদাহরণ: 127.0.0.1, 1 9 2.168.0.1/24, 10.0.0.1-10.0.0.255 )" +define_white_list: "হোয়াইট লিস্ট হোস্টকে সংজ্ঞায়িত করুন, এটি আলাদা করুন, (উদাহরণ: 127.0.0.1, 1 9 2.168.0.1/24, 10.0.0.1-10.0.0.255 )" engine: ইঞ্জিন filtered_content: ... [রিপোর্টে পূর্ণ সামগ্রী দেখুন] engine_input: ইঞ্জিন ইনপুট বিকল্প @@ -62,10 +62,10 @@ nettacker_report: OWASP Nettacker রিপোর্ট nettacker_version_details: "সফটওয়্যার বর্ণনা: {0} [{1}] {2}" not_found: পাওয়া যায় নি! outgoing_proxy: "বহির্গামী সংযোগ প্রক্সি (socks)। উদাহরণ socks5: 127.0.0.1:9050, socks://127.0.0.1:9050 socks5://127.0.0.1:9050 or socks4: socks4://127.0.0.1:9050, প্রমাণীকরণ: socks://username: password@ 127.0.0.1, socks4://username:password@127.0.0.1, socks5://username:password@127.0.0.1" -password_seperator: পাসওয়ার্ড(গুলি) তালিকা, "," দিয়ে আলাদা +password_separator: পাসওয়ার্ড(গুলি) তালিকা, "," দিয়ে আলাদা pentest_graphs: ভর্তি পরীক্ষা গ্রাফ ping_before_scan: হোস্ট স্ক্যান করার আগে পিং -port_seperator: পোর্ট(গুলি) তালিকা, "," দিয়ে আলাদা +port_separator: পোর্ট(গুলি) তালিকা, "," দিয়ে আলাদা ports_int: বন্দর পূর্ণসংখ্যা হতে হবে!(উদাহরণস্বরূপ 80 || 80,1080 || 80,1080-1300,9000,12000-15000) profile_404: প্রোফাইল "{0}" পেয়ে গেল না! range: পরিসরের সমস্ত আইপি স্ক্যান করুন @@ -76,7 +76,7 @@ save_logs: ফাইলের সমস্ত লগ সংরক্ষণ ক scan_method_options: স্ক্যান পদ্ধতি বিকল্প scan_method_select: আপনার স্ক্যানিং পদ্ধতি নির্বাচন করুন! scan_module_not_found: এই স্ক্যান মডিউল [{0}] এটি পাইনি! -scan_started: "নেটটেকার ইঞ্জিন শুরু হয়েছে..." +scan_started: "নেটটেকার ইঞ্জিন শুরু হয়েছে..." select_language: একটি ভাষা নির্বাচন করুন {0} select_profile: প্রোফাইল নির্বাচন করুন {0} select_user_agent: "HTTP অনুরোধগুলি পাঠানোর জন্য একটি ব্যবহারকারী এজেন্ট নির্বাচন করুন অথবা অনুরোধে ব্যবহারকারী-এজেন্টকে র্যান্ডমাইজ করতে \"random_user_agent\" লিখুন।" diff --git a/lib/messages/de.yaml b/nettacker/locale/de.yaml similarity index 98% rename from lib/messages/de.yaml rename to nettacker/locale/de.yaml index 4a1e981e8..38adc45a4 100644 --- a/lib/messages/de.yaml +++ b/nettacker/locale/de.yaml @@ -19,9 +19,9 @@ choose_scan_method: Suchmethode {0} auswählen exclude_scan_method: Suchmethode auswählen, um {0} auszuschließen username_list: Benutzername (s) Liste, getrennt mit "," username_from_file: Lese den Benutzernamen aus der Datei -password_seperator: Passwort (s) Liste, getrennt mit "," +password_separator: Passwort (s) Liste, getrennt mit "," read_passwords: Lies das Passwort aus der Datei -port_seperator: Port (s) Liste, getrennt mit "," +port_separator: Port (s) Liste, getrennt mit "," time_to_sleep: Zeit zwischen jeder Anfrage zu schlafen error_target: Das Ziel (die Ziele) kann nicht angegeben werden error_target_file: @@ -205,7 +205,7 @@ API_port: API-Portnummer API_debug: API-Debugmodus API_access_key: API-Zugriffsschlüssel white_list_API: erlauben Sie Whitelist-Hosts nur, sich mit der API zu verbinden -define_whie_list: +define_white_list: "Definieren Sie Whitelist-Hosts, getrennt mit, (Beispiele: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)" gen_API_access_log: API-Zugriffsprotokoll generieren diff --git a/lib/messages/el.yaml b/nettacker/locale/el.yaml similarity index 99% rename from lib/messages/el.yaml rename to nettacker/locale/el.yaml index 5f8a45605..773e5c457 100644 --- a/lib/messages/el.yaml +++ b/nettacker/locale/el.yaml @@ -22,9 +22,9 @@ choose_scan_method: επιλέξτε τη μέθοδο σάρωσης {0} exclude_scan_method: επιλέξτε μέθοδο σάρωσης για να εξαιρέσετε {0} username_list: όνομα χρήστη (ες), χωριστά με "," username_from_file: να διαβάσετε το όνομα χρήστη από το αρχείο -password_seperator: λίστα κωδικών πρόσβασης, ξεχωριστά με "," +password_separator: λίστα κωδικών πρόσβασης, ξεχωριστά με "," read_passwords: διαβάζουν τον κωδικό πρόσβασης από το αρχείο -port_seperator: λίστα λιμένων, χωριστά με "," +port_separator: λίστα λιμένων, χωριστά με "," time_to_sleep: χρόνος για ύπνο μεταξύ κάθε αιτήματος error_target: Δεν είναι δυνατός ο προσδιορισμός του στόχου error_target_file: @@ -203,7 +203,7 @@ API_port: Αριθμός θύρας API API_debug: Λειτουργία εντοπισμού σφαλμάτων API API_access_key: Πλήκτρο πρόσβασης API white_list_API: απλά επιτρέψτε στους οικοδεσπότες λευκής λίστας να συνδεθούν στο API -define_whie_list: +define_white_list: "καθορίστε ξένους καταλόγους φιλοξενίας, ξεχωριστοί με, (παραδείγματα: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)" gen_API_access_log: Δημιουργία αρχείου καταγραφής πρόσβασης API diff --git a/lib/messages/en.yaml b/nettacker/locale/en.yaml similarity index 96% rename from lib/messages/en.yaml rename to nettacker/locale/en.yaml index a7ae1c577..594d58050 100644 --- a/lib/messages/en.yaml +++ b/nettacker/locale/en.yaml @@ -21,7 +21,7 @@ connection_retries: Retries when the connection timeout (default 3) current_version: you are running OWASP Nettacker version {0}{1}{2}{6} with code name {3}{4}{5} database_connect_fail: could not connect to the database! database_connection_failed: Connection to the selected db failed -define_whie_list: "define white list hosts, separate with , (examples: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)" +define_white_list: "define white list hosts, separate with , (examples: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)" engine: Engine filtered_content: ... [see the full content in the report] engine_input: Engine input options @@ -66,10 +66,10 @@ outgoing_proxy: "outgoing connections proxy (socks). example socks5: 127.0.0.1:9 socks://127.0.0.1:9050 socks5://127.0.0.1:9050 or socks4: socks4://127.0.0.1:9050, authentication: socks://username: password@127.0.0.1, socks4://username:password@127.0.0.1, socks5://username:password@127.0.0.1" -password_seperator: password(s) list, separate with "," +password_separator: password(s) list, separate with "," pentest_graphs: Penetration Testing Graphs ping_before_scan: ping before scan the host -port_seperator: port(s) list, separate with "," +port_separator: port(s) list, separate with "," ports_int: ports must be integers! (e.g. 80 || 80,1080 || 80,1080-1300,9000,12000-15000) profile_404: the profile "{0}" not found! range: scan all IPs in the range diff --git a/lib/messages/es.yaml b/nettacker/locale/es.yaml similarity index 98% rename from lib/messages/es.yaml rename to nettacker/locale/es.yaml index 76f18eb67..fd5e60a6c 100644 --- a/lib/messages/es.yaml +++ b/nettacker/locale/es.yaml @@ -20,9 +20,9 @@ choose_scan_method: elija el método de escaneo {0} exclude_scan_method: elija el método de escaneo para excluir {0} username_list: nombre de usuario (s) list, separe con "," username_from_file: leer nombre (s) de usuario del archivo -password_seperator: lista de contraseña (s), separe con "," +password_separator: lista de contraseña (s), separe con "," read_passwords: leer contraseña (s) del archivo -port_seperator: lista (s) de puerto (s), separarse con "," +port_separator: lista (s) de puerto (s), separarse con "," time_to_sleep: tiempo para dormir entre cada solicitud error_target: No se puede especificar el objetivo (s) error_target_file: @@ -203,7 +203,7 @@ API_port: Número de puerto API API_debug: Modo de depuración API API_access_key: Clave de acceso API white_list_API: solo permita que los hosts de la lista blanca se conecten a la API -define_whie_list: +define_white_list: "definir los hosts de la lista blanca, separar con, (ejemplos: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)" gen_API_access_log: generar registro de acceso API diff --git a/lib/messages/fa.yaml b/nettacker/locale/fa.yaml similarity index 99% rename from lib/messages/fa.yaml rename to nettacker/locale/fa.yaml index cc22b56aa..8f990d7e8 100644 --- a/lib/messages/fa.yaml +++ b/nettacker/locale/fa.yaml @@ -23,9 +23,9 @@ choose_scan_method: متود اسکن را انتخاب کنید {0} exclude_scan_method: انتخاب متود اسکن استثنا {0} username_list: لیست نام کاربری (ها)، با "," جدا شود username_from_file: خواندن نام کاربری (ها) از لیست -password_seperator: لیست کلمه عبور (ها)، با "," جدا شود +password_separator: لیست کلمه عبور (ها)، با "," جدا شود read_passwords: خواندن کلمه عبور (ها) از فایل -port_seperator: لیست درگاه (ها)، با "," جدا شود +port_separator: لیست درگاه (ها)، با "," جدا شود time_to_sleep: زمان مکث بین هر درخواست error_target: عدم توانایی در مشخص کردن هدف (ها) error_target_file: @@ -193,7 +193,7 @@ API_port: شماره درگاه API API_debug: حالت اشکال زدایی API API_access_key: کلید دسترسی API white_list_API: اجازه دادن فقط به لیست سفید هاست ها برای ارتباط با API -define_whie_list: +define_white_list: 'تعریف کردن لیست سفید، با "," جدا کنید (مثال: 127.0.0.1, 192.168.1.1/24, 10.0.0.1-10.0.0.255)' gen_API_access_log: تولید لیست دسترسی به API diff --git a/lib/messages/fr.yaml b/nettacker/locale/fr.yaml similarity index 99% rename from lib/messages/fr.yaml rename to nettacker/locale/fr.yaml index 429daf612..b0d276dcc 100644 --- a/lib/messages/fr.yaml +++ b/nettacker/locale/fr.yaml @@ -22,9 +22,9 @@ choose_scan_method: choisissez la méthode de scan {0} exclude_scan_method: choisissez la méthode de scan pour exclure {0} username_list: nom d'utilisateur (s), séparé par "," username_from_file: lire le (s) nom (s) d'utilisateur à partir du fichier -password_seperator: mot de passe (s), séparé par "," +password_separator: mot de passe (s), séparé par "," read_passwords: lire le (s) mot de passe (s) du fichier -port_seperator: port (s) list, séparé par "," +port_separator: port (s) list, séparé par "," time_to_sleep: le temps de dormir entre chaque demande error_target: Impossible de spécifier la ou les cibles error_target_file: @@ -203,7 +203,7 @@ API_access_key: Clé d'accès à l'API white_list_API: autorisez simplement les hôtes de la liste blanche à se connecter à l'API -define_whie_list: +define_white_list: "définir des hôtes de liste blanche, séparés par, (exemples: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)" gen_API_access_log: générer un journal d'accès à l'API diff --git a/lib/messages/hi.yaml b/nettacker/locale/hi.yaml similarity index 96% rename from lib/messages/hi.yaml rename to nettacker/locale/hi.yaml index 4a83a8712..e04721cf2 100644 --- a/lib/messages/hi.yaml +++ b/nettacker/locale/hi.yaml @@ -18,9 +18,9 @@ choose_scan_method: स्कैन मॉड्यूल का चयन क exclude_scan_method: "{0} बाहर करने के लिए स्कैन मॉड्यूल का चयन करें" username_list: उपयोगकर्ता नाम (ओं) सूची, "," से अलग username_from_file: फ़ाइल से उपयोगकर्ता नाम (ओं) पढ़ें -password_seperator: पासवर्ड (ओं) सूची, "," से अलग +password_separator: पासवर्ड (ओं) सूची, "," से अलग read_passwords: फ़ाइल से पासवर्ड पढ़ें -port_seperator: पोर्ट (ओं) सूची, "," से अलग +port_separator: पोर्ट (ओं) सूची, "," से अलग time_to_sleep: प्रत्येक अनुरोध के बीच रुकने के लिए समय error_target: लक्ष्य निर्दिष्ट नहीं कर सकते error_target_file: @@ -46,8 +46,8 @@ graph_output: ग्राफ़ सुविधा का उपयोग क build_graph: ग्राफ निर्माण ... finish_build_graph: निर्माण ग्राफ खत्म हुआ। pentest_graphs: प्रवेश परीक्षण ग्राफ -graph_message: - यह ग्राफ OWASP Nettacker द्वारा बनाया गया है। ग्राफ़ में सभी मॉड्यूल गतिविधियां, +graph_message: + यह ग्राफ OWASP Nettacker द्वारा बनाया गया है। ग्राफ़ में सभी मॉड्यूल गतिविधियां, नेटवर्क मानचित्र और संवेदनशील जानकारी शामिल है, अगर यह विश्वसनीय नहीं है तो कृपया इस फ़ाइल को किसी के साथ साझा न करें। nettacker_report: OWASP Nettacker रिपोर्ट @@ -66,7 +66,7 @@ API_host: API होस्ट पता API_port: API पोर्ट नंबर API_debug: API डीबग मोड API_access_key: API एक्सेस कुंजी -define_whie_list: "श्वेत सूची होस्ट को परिभाषित करें, ',' से अलग, (उदाहरण: 127.0.0.1, 1 9 2.168.0.1/24, 10.0.0.1-10.0.0.255)" +define_white_list: "श्वेत सूची होस्ट को परिभाषित करें, ',' से अलग, (उदाहरण: 127.0.0.1, 1 9 2.168.0.1/24, 10.0.0.1-10.0.0.255)" API_access_log_file: API एक्सेस लॉग फ़ाइल नाम API_key: "* API का उपयोग किया जा सकता है https://nettacker-api.z3r0d4y.com:{0}/ के माध्यम से API कुंजी: {1}" ports_int: पोर्ट्स को पूर्णांक होने चाहिए! (उदाहरण के लिए 80 || 80,1080 || 80,1080-1300,9000,12000-15000) @@ -97,7 +97,7 @@ cannot_run_api_server: आप स्वयं के माध्यम से A library_not_supported: लाइब्रेरी [{0}] समर्थित नहीं है! removing_old_db_records: चयनित लक्ष्यों और मॉड्यूल के लिए पुराने डेटाबेस रिकॉर्ड को हटा रहा है। regrouping_targets: हार्डवेयर संसाधनों के आधार पर लक्ष्यों को फिर से संगठित किया जा रहा है! -finished_parallel_module_scan: प्रक्रिया- {0} | {1} | {2} | मॉड्यूल थ्रेड संख्या {3}, {4} से समाप्त +finished_parallel_module_scan: प्रक्रिया- {0} | {1} | {2} | मॉड्यूल थ्रेड संख्या {3}, {4} से समाप्त invalid_json_type_to_db: "डेटाबेस के लिए JSON डेटा का अमान्य प्रकार। डेटाबेस को सब्बिशन छोड़ा जा रहा है। डेटा: {0}" loading_modules: सभी मॉड्यूल लोड हो रहा है ... यह कुछ समय हो सकता है! loading_profiles: सभी प्रोफाइल लोड हो रहा है ... यह कुछ समय ले सकता है! diff --git a/lib/messages/hy.yaml b/nettacker/locale/hy.yaml similarity index 99% rename from lib/messages/hy.yaml rename to nettacker/locale/hy.yaml index 9ee5bfa29..78cee4290 100644 --- a/lib/messages/hy.yaml +++ b/nettacker/locale/hy.yaml @@ -20,9 +20,9 @@ choose_scan_method: ընտրեք սկանավորման մեթոդը {0} exclude_scan_method: ընտրեք սկանավորման եղանակը, բացառելու {0} username_list: օգտագործողի անուն (ներ) ցուցակը, առանձին "," username_from_file: կարդացեք օգտվողի անունը (ներ) ը ֆայլից -password_seperator: գաղտնաբառերի ցուցակը, որը առանձին է «,» +password_separator: գաղտնաբառերի ցուցակը, որը առանձին է «,» read_passwords: կարդալ գաղտնաբառ (ներ) ը ֆայլից -port_seperator: պորտ (ներ) ցուցակը, որը առանձին է «,» +port_separator: պորտ (ներ) ցուցակը, որը առանձին է «,» time_to_sleep: յուրաքանչյուր խնդրի միջեւ քնելու ժամանակ error_target: Հնարավոր չէ նշել թիրախ (ներ) error_target_file: Հնարավոր չէ նշել թիրախ (ներ) ը, չի կարող բացել ֆայլը, {0} @@ -195,7 +195,7 @@ API_port: API պորտի համարը API_debug: API- ի կարգաբերման ռեժիմ API_access_key: API մուտքի բանալին white_list_API: պարզապես թույլ տվեք սպիտակ ցուցակի հյուրընկալողներին միանալ API- ին -define_whie_list: +define_white_list: սահմանել սպիտակ ցուցակի հանգույցներ, առանձին, (օրինակ `127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255) gen_API_access_log: API- ի հասանելիության մատյան diff --git a/lib/messages/id.yaml b/nettacker/locale/id.yaml similarity index 98% rename from lib/messages/id.yaml rename to nettacker/locale/id.yaml index 8ccaac2cb..42971e32f 100644 --- a/lib/messages/id.yaml +++ b/nettacker/locale/id.yaml @@ -20,9 +20,9 @@ choose_scan_method: pilih metode pemindaian {0} exclude_scan_method: pilih metode pemindaian untuk mengecualikan {0} username_list: daftar nama pengguna (s), terpisah dengan "," username_from_file: baca nama pengguna (s) dari file -password_seperator: daftar kata sandi, terpisah dengan "," +password_separator: daftar kata sandi, terpisah dengan "," read_passwords: baca kata sandi (s) dari file -port_seperator: daftar port (s), terpisah dengan "," +port_separator: daftar port (s), terpisah dengan "," time_to_sleep: waktu untuk tidur di antara setiap permintaan error_target: Tidak dapat menentukan target (s) error_target_file: "Tidak dapat menentukan target (s), tidak dapat membuka file: {0}" @@ -191,7 +191,7 @@ API_port: Nomor port API API_debug: Mode debug API API_access_key: Kunci akses API white_list_API: cukup izinkan host daftar putih untuk terhubung ke API -define_whie_list: +define_white_list: "mendefinisikan host daftar putih, terpisah dengan, (contoh: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)" gen_API_access_log: menghasilkan log akses API diff --git a/lib/messages/it.yaml b/nettacker/locale/it.yaml similarity index 98% rename from lib/messages/it.yaml rename to nettacker/locale/it.yaml index 8ccaac2cb..42971e32f 100644 --- a/lib/messages/it.yaml +++ b/nettacker/locale/it.yaml @@ -20,9 +20,9 @@ choose_scan_method: pilih metode pemindaian {0} exclude_scan_method: pilih metode pemindaian untuk mengecualikan {0} username_list: daftar nama pengguna (s), terpisah dengan "," username_from_file: baca nama pengguna (s) dari file -password_seperator: daftar kata sandi, terpisah dengan "," +password_separator: daftar kata sandi, terpisah dengan "," read_passwords: baca kata sandi (s) dari file -port_seperator: daftar port (s), terpisah dengan "," +port_separator: daftar port (s), terpisah dengan "," time_to_sleep: waktu untuk tidur di antara setiap permintaan error_target: Tidak dapat menentukan target (s) error_target_file: "Tidak dapat menentukan target (s), tidak dapat membuka file: {0}" @@ -191,7 +191,7 @@ API_port: Nomor port API API_debug: Mode debug API API_access_key: Kunci akses API white_list_API: cukup izinkan host daftar putih untuk terhubung ke API -define_whie_list: +define_white_list: "mendefinisikan host daftar putih, terpisah dengan, (contoh: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)" gen_API_access_log: menghasilkan log akses API diff --git a/lib/messages/iw.yaml b/nettacker/locale/iw.yaml similarity index 98% rename from lib/messages/iw.yaml rename to nettacker/locale/iw.yaml index 652867ca2..b1c22380c 100644 --- a/lib/messages/iw.yaml +++ b/nettacker/locale/iw.yaml @@ -20,9 +20,9 @@ choose_scan_method: בחר שיטת סריקה {0} exclude_scan_method: בחר שיטת סריקה כדי לא לכלול {0} username_list: שם משתמש, נפרד עם "," username_from_file: לקרוא שם משתמש (ים) מקובץ -password_seperator: רשימת הסיסמאות, בנפרד עם "," +password_separator: רשימת הסיסמאות, בנפרד עם "," read_passwords: לקרוא את הסיסמה (s) מקובץ -port_seperator: פורט (ים), נפרד עם "," +port_separator: פורט (ים), נפרד עם "," time_to_sleep: זמן לישון בין כל בקשה error_target: לא ניתן להגדיר את המיקודים error_target_file: "לא ניתן לציין את היעדים, לא ניתן לפתוח את הקובץ: {0}" @@ -175,7 +175,7 @@ API_port: מספר יציאת ממשק API API_debug: מצב איתור באגים ב- API API_access_key: מפתח גישה לממשק API white_list_API: רק לאפשר למארחים רשימה לבנה להתחבר API -define_whie_list: +define_white_list: "להגדיר את המארחים רשימה לבנה, להפריד עם, (דוגמאות: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)" gen_API_access_log: ליצור יומן גישה API diff --git a/lib/messages/ja.yaml b/nettacker/locale/ja.yaml similarity index 98% rename from lib/messages/ja.yaml rename to nettacker/locale/ja.yaml index b1fc13067..002ce3963 100644 --- a/lib/messages/ja.yaml +++ b/nettacker/locale/ja.yaml @@ -20,9 +20,9 @@ choose_scan_method: スキャン方法{0}を選択してください exclude_scan_method: スキャン方法を選択して{0}を除外する username_list: ユーザ名リスト、 "、" username_from_file: ファイルからユーザー名を読み込む -password_seperator: パスワードのリスト、 "、"で区切る +password_separator: パスワードのリスト、 "、"で区切る read_passwords: ファイルからパスワードを読み込む -port_seperator: ポートリスト、 "、" +port_separator: ポートリスト、 "、" time_to_sleep: 各リクエストの間にスリープする時間 error_target: ターゲットを指定できません error_target_file: ファイルを開くことができないターゲットを指定することはできません:{0} @@ -147,7 +147,7 @@ API_port: APIポート番号 API_debug: APIデバッグモード API_access_key: APIアクセスキー white_list_API: ホワイトリストホストだけがAPIに接続できるようにする -define_whie_list: ホワイトリストホストを定義し、で区切って(例:127.0.0.1,192.168.0.1/24、10.0.0.1-10.0.0.255) +define_white_list: ホワイトリストホストを定義し、で区切って(例:127.0.0.1,192.168.0.1/24、10.0.0.1-10.0.0.255) gen_API_access_log: APIアクセスログを生成する API_access_log_file: APIアクセスログファイル名 API_port_int: APIポートは整数でなければなりません! diff --git a/lib/messages/ko.yaml b/nettacker/locale/ko.yaml similarity index 99% rename from lib/messages/ko.yaml rename to nettacker/locale/ko.yaml index 3e47a43ad..68302f2b3 100644 --- a/lib/messages/ko.yaml +++ b/nettacker/locale/ko.yaml @@ -20,9 +20,9 @@ choose_scan_method: 검사 방법 {0}을 선택하십시오. exclude_scan_method: 검색 방법을 선택하여 {0}을 (를) 제외하십시오. username_list: 사용자 이름 목록, "," username_from_file: 파일에서 사용자 이름 읽기 -password_seperator: 암호 목록, "," +password_separator: 암호 목록, "," read_passwords: 파일에서 암호 읽기 -port_seperator: 포트 목록, "," +port_separator: 포트 목록, "," time_to_sleep: 각 요청 사이에 잠자기 시간 error_target: 타겟을 지정할 수 없습니다. error_target_file: "파일을 열 수없는 대상을 지정할 수 없습니다 : {0}" @@ -163,7 +163,7 @@ API_port: API 포트 번호 API_debug: API 디버그 모드 API_access_key: API 액세스 키 white_list_API: 화이트리스트 호스트가 API에 연결하도록 허용하기 -define_whie_list: +define_white_list: "(예 : 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)로 구분 된 화이트리스트 호스트를 정의하십시오." gen_API_access_log: API 액세스 로그 생성 diff --git a/lib/messages/nl.yaml b/nettacker/locale/nl.yaml similarity index 98% rename from lib/messages/nl.yaml rename to nettacker/locale/nl.yaml index 45f8421ba..fec1de89c 100644 --- a/lib/messages/nl.yaml +++ b/nettacker/locale/nl.yaml @@ -20,9 +20,9 @@ choose_scan_method: kies scanmethode {0} exclude_scan_method: kies scanmethode om {0} uit te sluiten username_list: gebruikersnaam (s) lijst, gescheiden door "," username_from_file: lees gebruikersnaam (s) uit bestand -password_seperator: wachtwoord (en) lijst, gescheiden door "," +password_separator: wachtwoord (en) lijst, gescheiden door "," read_passwords: lees wachtwoord (s) uit bestand -port_seperator: poort (en) lijst, gescheiden door "," +port_separator: poort (en) lijst, gescheiden door "," time_to_sleep: tijd om te slapen tussen elk verzoek error_target: Kan het doel (de doelen) niet specificeren error_target_file: "Kan doel (en) niet specificeren, kan bestand niet openen: {0}" @@ -192,7 +192,7 @@ API_port: API poortnummer API_debug: API-foutopsporingsmodus API_access_key: API-toegangssleutel white_list_API: staat witte lijst hosts toe om verbinding te maken met de API -define_whie_list: +define_white_list: "definieer witte lijst hosts, gescheiden met, (voorbeelden: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)" gen_API_access_log: API-toegangslog genereren diff --git a/lib/messages/ps.yaml b/nettacker/locale/ps.yaml similarity index 99% rename from lib/messages/ps.yaml rename to nettacker/locale/ps.yaml index 79353f19d..3c49ce66c 100644 --- a/lib/messages/ps.yaml +++ b/nettacker/locale/ps.yaml @@ -20,9 +20,9 @@ choose_scan_method: د سکین طریقه غوره کړه {0} exclude_scan_method: "{0} لرې کولو لپاره د سکین طریقه وټاکئ" username_list: د کارن نوموونکي لیست، د "،" سره جلا username_from_file: د دوتنې څخه کارن نوم -password_seperator: د پټنوم (لسټ) لیست، د "،" سره جلا کړئ +password_separator: د پټنوم (لسټ) لیست، د "،" سره جلا کړئ read_passwords: د دوتنې څخه پټنوم (پوسټ) ولولئ -port_seperator: د بندرونو لیست، د "،" سره جلا کول +port_separator: د بندرونو لیست، د "،" سره جلا کول time_to_sleep: د هرې غوښتنې په منځ کې د خوب کولو وخت error_target: هدف یا هدف مشخص کولی نشی error_target_file: "نشی ټاکل شوی هدفونه، د فایل پرانستلو توان نلري: {0}" @@ -186,7 +186,7 @@ API_port: د API پورتنۍ شمیره API_debug: د API ډبګ موډ API_access_key: د API لاسرسی کیلي white_list_API: یوازې د سپینې لیست الوتکې ته اجازه ورکړئ چې API سره ونښلول شي -define_whie_list: +define_white_list: "د سپینې لیست میزان تعریف کړئ، سره جلا کړئ، (مثالونه: 127.0.0.1، 192.168.0.1/24، 10.0.0.1-10.0.0.255)" gen_API_access_log: د API لاسرسی پیدا کړئ diff --git a/lib/messages/pt-br.yaml b/nettacker/locale/pt-br.yaml similarity index 99% rename from lib/messages/pt-br.yaml rename to nettacker/locale/pt-br.yaml index 81aa7f760..431f6a0bc 100644 --- a/lib/messages/pt-br.yaml +++ b/nettacker/locale/pt-br.yaml @@ -25,9 +25,9 @@ choose_scan_method: 'escolha o método de escaneio {0}' exclude_scan_method: 'escolha método de escaneio para excluir {0}' username_list: 'lista de nome(s) de usuário(s), separe com ","' username_from_file: ler nome(s) de usuário(s) do arquivo -password_seperator: 'lista de senha(s), separe com ","' +password_separator: 'lista de senha(s), separe com ","' read_passwords: ler senhas(s) do arquivo -port_seperator: 'lsita de porta(s), separe com ","' +port_separator: 'lsita de porta(s), separe com ","' time_to_sleep: tempo para esperar entre cada solicitação error_target: Não foi possível especificar o(s) alvo(s) error_target_file: 'Não foi possível especificar o(s) alvo(s), impossível abrir o arquivo: {0}' @@ -204,7 +204,7 @@ API_port: Número de porta API API_debug: Modo de depuração API API_access_key: Chave de acceso API white_list_API: apenas permita que os hosts da white list se conectem com a API -define_whie_list: >- +define_white_list: >- definir os hosts da white list, separar com , (exemplos: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255) gen_API_access_log: gerar registros de acceso API diff --git a/lib/messages/readme.md b/nettacker/locale/readme.md similarity index 100% rename from lib/messages/readme.md rename to nettacker/locale/readme.md diff --git a/lib/messages/ru.yaml b/nettacker/locale/ru.yaml similarity index 99% rename from lib/messages/ru.yaml rename to nettacker/locale/ru.yaml index 999c89f19..8f9b905ec 100644 --- a/lib/messages/ru.yaml +++ b/nettacker/locale/ru.yaml @@ -20,9 +20,9 @@ choose_scan_method: выбрать метод сканирования {0} exclude_scan_method: выберите метод сканирования, чтобы исключить {0} username_list: список пользователей, разделяемых с "," username_from_file: читать имя пользователя из файла -password_seperator: список паролей, разделяемых «,», +password_separator: список паролей, разделяемых «,», read_passwords: читать пароль (ы) из файла -port_seperator: порт (ы), разделенные с "," +port_separator: порт (ы), разделенные с "," time_to_sleep: время спать между каждым запросом error_target: Невозможно указать цель (ы) error_target_file: "Невозможно указать цель (ы), неспособную открыть файл: {0}" @@ -189,7 +189,7 @@ API_port: Номер порта API API_debug: Режим отладки API API_access_key: Ключ доступа к API white_list_API: просто разрешить хостам белого списка подключаться к API -define_whie_list: +define_white_list: "определить узлы белого списка, разделить с, (примеры: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)" gen_API_access_log: генерировать журнал доступа к API diff --git a/lib/messages/tr.yaml b/nettacker/locale/tr.yaml similarity index 99% rename from lib/messages/tr.yaml rename to nettacker/locale/tr.yaml index 9c4fdd668..670120ac6 100644 --- a/lib/messages/tr.yaml +++ b/nettacker/locale/tr.yaml @@ -20,9 +20,9 @@ choose_scan_method: tarama yöntemini seçin {0} exclude_scan_method: "{0} öğesini hariç tutmak için tarama yöntemini seçin" username_list: kullanıcı adı (lar) listesi, "," ile ayrı username_from_file: dosyadan kullanıcı adlarını oku -password_seperator: şifre listesi "," ile ayrı +password_separator: şifre listesi "," ile ayrı read_passwords: dosyadan şifre (ler) oku -port_seperator: port (lar) listesi, "," ile ayrı +port_separator: port (lar) listesi, "," ile ayrı time_to_sleep: her istek arasında uyumak için zaman error_target: Hedef (ler) belirtilemiyor error_target_file: "Dosya açılamayan hedef (ler) belirtilemiyor: {0}" @@ -192,7 +192,7 @@ API_port: API bağlantı noktası numarası API_debug: API hata ayıklama modu API_access_key: API erişim anahtarı white_list_API: API'ye bağlanmak için beyaz liste ana bilgisayarlarına izin ver -define_whie_list: +define_white_list: "ile beyaz liste konaklarını tanımlar, (örnek: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)" gen_API_access_log: API erişim günlüğü oluştur diff --git a/lib/messages/ur.yaml b/nettacker/locale/ur.yaml similarity index 99% rename from lib/messages/ur.yaml rename to nettacker/locale/ur.yaml index 65189c561..021824c1b 100644 --- a/lib/messages/ur.yaml +++ b/nettacker/locale/ur.yaml @@ -22,9 +22,9 @@ choose_scan_method: اسکین کا طریقہ منتخب کریں {0} exclude_scan_method: "{0} کو خارج کرنے کے لئے اسکین کا طریقہ منتخب کریں" username_list: صارف نام (ے) کی فہرست، کے ساتھ علیحدہ "،" username_from_file: فائل سے صارف نام (زبانیں) پڑھیں -password_seperator: پاس ورڈ (ے) کی فہرست، کے ساتھ علیحدہ "،" +password_separator: پاس ورڈ (ے) کی فہرست، کے ساتھ علیحدہ "،" read_passwords: فائل سے پاس ورڈ (پڑھنا) پڑھیں -port_seperator: بندرگاہ کی فہرست، کے ساتھ علیحدہ "،" +port_separator: بندرگاہ کی فہرست، کے ساتھ علیحدہ "،" time_to_sleep: ہر درخواست کے درمیان سونے کا وقت error_target: ہدف مقرر نہیں کر سکتے ہیں error_target_file: "ہدف (ن) کی وضاحت نہیں کرسکتا، فائل کو کھولنے میں ناکام ہے: {0}" @@ -197,7 +197,7 @@ API_port: API پورٹ نمبر API_debug: API ڈیبگ موڈ API_access_key: API رسائی کی چابی white_list_API: صرف سفید فہرست میزبان API سے منسلک کرنے کی اجازت دیتے ہیں -define_whie_list: +define_white_list: "سفید فہرست میزبان کی وضاحت، کے ساتھ علیحدہ کریں، (مثال: 127.0.0.1، 192.168.0.1/24، 10.0.0.1-10.0.0.255)" gen_API_access_log: API رسائی لاگ پیدا کریں diff --git a/lib/messages/vi.yaml b/nettacker/locale/vi.yaml similarity index 98% rename from lib/messages/vi.yaml rename to nettacker/locale/vi.yaml index b47fd306f..2494612dc 100644 --- a/lib/messages/vi.yaml +++ b/nettacker/locale/vi.yaml @@ -20,9 +20,9 @@ choose_scan_method: chọn phương pháp quét {0} exclude_scan_method: chọn phương pháp quét để loại trừ {0} username_list: (các) tên người dùng, riêng biệt với "," username_from_file: đọc (các) tên người dùng từ tệp -password_seperator: (các) mật khẩu, riêng biệt với "," +password_separator: (các) mật khẩu, riêng biệt với "," read_passwords: đọc (các) mật khẩu từ tệp -port_seperator: danh sách cổng, tách biệt với "," +port_separator: danh sách cổng, tách biệt với "," time_to_sleep: thời gian để ngủ giữa mỗi yêu cầu error_target: Không thể chỉ định (các) mục tiêu error_target_file: "Không thể chỉ định (các) đích, không thể mở tệp: {0}" @@ -194,7 +194,7 @@ API_port: Số cổng API API_debug: Chế độ gỡ lỗi API API_access_key: Khóa truy cập API white_list_API: chỉ cho phép các máy chủ danh sách trắng kết nối với API -define_whie_list: +define_white_list: "xác định các máy chủ danh sách trắng, tách biệt với, (ví dụ: 127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)" gen_API_access_log: tạo nhật ký truy cập API diff --git a/lib/messages/zh-cn.yaml b/nettacker/locale/zh-cn.yaml similarity index 97% rename from lib/messages/zh-cn.yaml rename to nettacker/locale/zh-cn.yaml index 06048a3d3..b9b2deb34 100644 --- a/lib/messages/zh-cn.yaml +++ b/nettacker/locale/zh-cn.yaml @@ -20,9 +20,9 @@ choose_scan_method: 选择扫描方法{0} exclude_scan_method: 排除扫描方法{0} username_list: 用户名列表,用“,”分开 username_from_file: 从文件中读取用户名 -password_seperator: 密码列表,用“,”分开 +password_separator: 密码列表,用“,”分开 read_passwords: 从文件中读取密码 -port_seperator: 端口列表,用“,”分开 +port_separator: 端口列表,用“,”分开 time_to_sleep: 每次请求的间隔时间 error_target: 无法指定目标(s) error_target_file: 无法指定目标,无法打开文件:{0} @@ -147,7 +147,7 @@ API_port: API 端口号 API_debug: API 调试模式 API_access_key: API 访问密钥 white_list_API: 只允许白名单主机连接到 API -define_whie_list: 定义白名单主机,与之分开,(例如:127.0.0.1,192.168.0.1/24,10.0.0.1-10.0.0.255) +define_white_list: 定义白名单主机,与之分开,(例如:127.0.0.1,192.168.0.1/24,10.0.0.1-10.0.0.255) gen_API_access_log: 生成 API 访问日志 API_access_log_file: API 访问日志文件名 API_port_int: API 端口必须是整数! diff --git a/nettacker/logger.py b/nettacker/logger.py new file mode 100644 index 000000000..1ead98a74 --- /dev/null +++ b/nettacker/logger.py @@ -0,0 +1,202 @@ +import sys +from enum import Enum +from functools import cached_property + +from nettacker.core.utils import time + + +class TerminalCodes(Enum): + RESET = "\033[0m" + + # Colors \033[1; + GREY = "\033[1;30m" + RED = "\033[1;31m" + GREEN = "\033[1;32m" + YELLOW = "\033[1;33m" + BLUE = "\033[1;34m" + PURPLE = "\033[1;35m" + CYAN = "\033[1;36m" + WHITE = "\033[1;37m" + + +class Logger: + """Nettacker logger.""" + + @staticmethod + def log(text): + print(text, end="", flush=True) # noqa: T201 + + @cached_property + def run_from_api(self): + """ + check if framework run from API to prevent any alert + + Returns: + True if run from API otherwise False + """ + return "--start-api" in sys.argv + + @cached_property + def verbose_mode_is_enabled(self): + return "--verbose" in sys.argv or "-v" in sys.argv + + @cached_property + def event_verbose_mode_is_enabled(self): + return "--verbose-event" in sys.argv + + def info(self, content): + """ + build the info message, log the message in database if requested, + rewrite the thread temporary file + + Args: + content: content of the message + + Returns: + None + """ + if not self.run_from_api: + self.log( + TerminalCodes.YELLOW.value + + "[{0}][+] ".format(time.now()) + + TerminalCodes.GREEN.value + + content + + TerminalCodes.RESET.value + + "\n", + ) + + def verbose_event_info(self, content): + """ + build the info message, log the message in database if requested, + rewrite the thread temporary file + + Args: + content: content of the message + + Returns: + None + """ + if not self.run_from_api and ( + self.verbose_mode_is_enabled or self.event_verbose_mode_is_enabled + ): # prevent to stdout if run from API + self.log( + TerminalCodes.YELLOW.value + + "[{0}][+] ".format(time.now()) + + TerminalCodes.GREEN.value + + content + + TerminalCodes.RESET.value + + "\n", + ) + + def write(self, content): + """ + simple print a message + + Args: + content: content of the message + + Returns: + None + """ + if not self.run_from_api: + self.log(content) + + def success_event_info(self, content): + """ + build the info message, log the message in database if requested, + rewrite the thread temporary file + + Args: + content: content of the message + + Returns: + None + """ + if not self.run_from_api: + self.log( + TerminalCodes.RED.value + + "[{0}][+++] ".format(time.now()) + + TerminalCodes.CYAN.value + + content + + TerminalCodes.RESET.value + + "\n", + ) + + def verbose_info(self, content): + """ + build the info message, log the message in database if requested, + rewrite the thread temporary file + + Args: + content: content of the message + + Returns: + None + """ + if self.verbose_mode_is_enabled: + self.log( + TerminalCodes.YELLOW.value + + "[{0}][+] ".format(time.now()) + + TerminalCodes.PURPLE.value + + content + + TerminalCodes.RESET.value + + "\n", + ) + + def warn(self, content): + """ + build the warn message + + Args: + content: content of the message + + Returns: + the message in warn structure - None + """ + if not self.run_from_api: + self.log( + TerminalCodes.BLUE.value + + "[{0}][!] ".format(time.now()) + + TerminalCodes.YELLOW.value + + content + + TerminalCodes.RESET.value + + "\n", + ) + + def error(self, content): + """ + build the error message + + Args: + content: content of the message + + Returns: + the message in error structure - None + """ + self.log( + TerminalCodes.RED.value + + "[{0}][X] ".format(time.now()) + + TerminalCodes.YELLOW.value + + content + + TerminalCodes.RESET.value + + "\n" + ) + + def write_to_api_console(self, content): + """ + simple print a message in API mode + + Args: + content: content of the message + + Returns: + None + """ + self.log(content) + + def reset_color(self): + self.log(TerminalCodes.RESET.value) + + +def get_logger(): + return Logger() diff --git a/nettacker/logo.txt b/nettacker/logo.txt new file mode 100644 index 000000000..beee1d57d --- /dev/null +++ b/nettacker/logo.txt @@ -0,0 +1,13 @@ + ______ __ _____ _____ + / __ \ \ / /\ / ____| __ \ + | | | \ \ /\ / / \ | (___ | |__) | + | | | |\ \/ \/ / /\ \ \___ \| ___/ + | |__| | \ /\ / ____ \ ____) | | {red}Version {v1}{rst} + \____/ \/ \/_/ \_\_____/|_| {yellow}{v2}{rst} + _ _ _ _ _ + | \ | | | | | | | | + {cyan}github.com/OWASP{rst} | \| | ___| |_| |_ __ _ ___| | _____ _ __ + {cyan}owasp.org{rst} | . ` |/ _ \ __| __/ _` |/ __| |/ / _ \ '__| + {cyan}z3r0d4y.com{rst} | |\ | __/ |_| || (_| | (__| < __/ | + |_| \_|\___|\__|\__\__,_|\___|_|\_\___|_| + diff --git a/modules/brute/ftp.yaml b/nettacker/modules/brute/ftp.yaml similarity index 78% rename from modules/brute/ftp.yaml rename to nettacker/modules/brute/ftp.yaml index a4e3c98ee..3f62c4c77 100644 --- a/modules/brute/ftp.yaml +++ b/nettacker/modules/brute/ftp.yaml @@ -12,9 +12,9 @@ info: payloads: - library: ftp steps: - - method: ftp_brute_force + - method: brute_force timeout: 3 - host: "{target}" + host: '{target}' ports: - 21 usernames: @@ -30,11 +30,11 @@ payloads: suffix: interceptors: data: - passwords: fuzzer_function_read_file_as_array('passwords/top_1000_common_passwords.txt') + passwords: + read_from_file: passwords/top_1000_common_passwords.txt response: condition_type: or conditions: successful_login: - regex: "" + regex: '' reverse: false - diff --git a/modules/brute/ftps.yaml b/nettacker/modules/brute/ftps.yaml similarity index 82% rename from modules/brute/ftps.yaml rename to nettacker/modules/brute/ftps.yaml index 750428d7b..509905f5c 100644 --- a/modules/brute/ftps.yaml +++ b/nettacker/modules/brute/ftps.yaml @@ -10,9 +10,9 @@ info: - ftp payloads: - - library: ftp + - library: ftps steps: - - method: ftps_brute_force + - method: brute_force timeout: 3 host: "{target}" ports: @@ -30,7 +30,8 @@ payloads: suffix: interceptors: data: - passwords: fuzzer_function_read_file_as_array('passwords/top_1000_common_passwords.txt') + passwords: + read_from_file: passwords/top_1000_common_passwords.txt response: condition_type: or conditions: diff --git a/modules/brute/pop3.yaml b/nettacker/modules/brute/pop3.yaml similarity index 83% rename from modules/brute/pop3.yaml rename to nettacker/modules/brute/pop3.yaml index 43b16926b..5af1860a2 100644 --- a/modules/brute/pop3.yaml +++ b/nettacker/modules/brute/pop3.yaml @@ -12,7 +12,7 @@ info: payloads: - library: pop3 steps: - - method: pop3_brute_force + - method: brute_force timeout: 3 host: "{target}" ports: @@ -29,7 +29,8 @@ payloads: suffix: interceptors: data: - passwords: fuzzer_function_read_file_as_array('passwords/top_1000_common_passwords.txt') + passwords: + read_from_file: passwords/top_1000_common_passwords.txt response: condition_type: or conditions: diff --git a/modules/brute/pop3s.yaml b/nettacker/modules/brute/pop3s.yaml similarity index 81% rename from modules/brute/pop3s.yaml rename to nettacker/modules/brute/pop3s.yaml index 6683ff8ef..f47d3ca75 100644 --- a/modules/brute/pop3s.yaml +++ b/nettacker/modules/brute/pop3s.yaml @@ -10,9 +10,9 @@ info: - pop3 payloads: - - library: pop3 + - library: pop3s steps: - - method: pop3_ssl_brute_force + - method: brute_force timeout: 3 host: "{target}" ports: @@ -29,7 +29,8 @@ payloads: suffix: interceptors: data: - passwords: fuzzer_function_read_file_as_array('passwords/top_1000_common_passwords.txt') + passwords: + read_from_file: passwords/top_1000_common_passwords.txt response: condition_type: or conditions: diff --git a/modules/brute/smtp.yaml b/nettacker/modules/brute/smtp.yaml similarity index 83% rename from modules/brute/smtp.yaml rename to nettacker/modules/brute/smtp.yaml index f5241561e..e0c338d06 100644 --- a/modules/brute/smtp.yaml +++ b/nettacker/modules/brute/smtp.yaml @@ -12,7 +12,7 @@ info: payloads: - library: smtp steps: - - method: smtp_brute_force + - method: brute_force timeout: 3 host: "{target}" ports: @@ -30,7 +30,8 @@ payloads: suffix: interceptors: data: - passwords: fuzzer_function_read_file_as_array('passwords/top_1000_common_passwords.txt') + passwords: + read_from_file: passwords/top_1000_common_passwords.txt response: condition_type: or conditions: diff --git a/modules/brute/smtps.yaml b/nettacker/modules/brute/smtps.yaml similarity index 82% rename from modules/brute/smtps.yaml rename to nettacker/modules/brute/smtps.yaml index 4ca5c6334..c57004b51 100644 --- a/modules/brute/smtps.yaml +++ b/nettacker/modules/brute/smtps.yaml @@ -10,9 +10,9 @@ info: - smtp payloads: - - library: smtp + - library: smtps steps: - - method: smtps_brute_force + - method: brute_force timeout: 3 host: "{target}" ports: @@ -32,7 +32,8 @@ payloads: suffix: interceptors: data: - passwords: fuzzer_function_read_file_as_array('passwords/top_1000_common_passwords.txt') + passwords: + read_from_file: passwords/top_1000_common_passwords.txt response: condition_type: or conditions: diff --git a/modules/brute/ssh.yaml b/nettacker/modules/brute/ssh.yaml similarity index 78% rename from modules/brute/ssh.yaml rename to nettacker/modules/brute/ssh.yaml index c6b97daf0..3d5e3eb68 100644 --- a/modules/brute/ssh.yaml +++ b/nettacker/modules/brute/ssh.yaml @@ -12,9 +12,9 @@ info: payloads: - library: ssh steps: - - method: ssh_brute_force + - method: brute_force timeout: 3 - host: "{target}" + host: '{target}' ports: - 22 - 2222 @@ -30,11 +30,11 @@ payloads: suffix: interceptors: data: - passwords: fuzzer_function_read_file_as_array('passwords/top_1000_common_passwords.txt') + passwords: + read_from_file: passwords/top_1000_common_passwords.txt response: condition_type: or conditions: successful_login: - regex: "" + regex: '' reverse: false - diff --git a/modules/brute/telnet.yaml b/nettacker/modules/brute/telnet.yaml similarity index 83% rename from modules/brute/telnet.yaml rename to nettacker/modules/brute/telnet.yaml index 507f301ed..6b1aa4324 100644 --- a/modules/brute/telnet.yaml +++ b/nettacker/modules/brute/telnet.yaml @@ -12,7 +12,7 @@ info: payloads: - library: telnet steps: - - method: telnet_brute_force + - method: brute_force timeout: 3 host: "{target}" ports: @@ -29,7 +29,8 @@ payloads: suffix: interceptors: data: - passwords: fuzzer_function_read_file_as_array('passwords/top_1000_common_passwords.txt') + passwords: + read_from_file: passwords/top_1000_common_passwords.txt response: condition_type: or conditions: diff --git a/modules/scan/admin.yaml b/nettacker/modules/scan/admin.yaml similarity index 90% rename from modules/scan/admin.yaml rename to nettacker/modules/scan/admin.yaml index 32a7def68..cbfe6a4b8 100644 --- a/modules/scan/admin.yaml +++ b/nettacker/modules/scan/admin.yaml @@ -26,7 +26,8 @@ payloads: suffix: "" interceptors: data: - urls: fuzzer_function_read_file_as_array('wordlists/admin_wordlist.txt') + urls: + read_from_file: wordlists/admin_wordlist.txt schema: - "http" - "https" diff --git a/modules/scan/citrix_lastpatcheddate.yaml b/nettacker/modules/scan/citrix_lastpatcheddate.yaml similarity index 100% rename from modules/scan/citrix_lastpatcheddate.yaml rename to nettacker/modules/scan/citrix_lastpatcheddate.yaml diff --git a/modules/scan/confluence_version.yaml b/nettacker/modules/scan/confluence_version.yaml similarity index 100% rename from modules/scan/confluence_version.yaml rename to nettacker/modules/scan/confluence_version.yaml diff --git a/modules/scan/drupal_modules.yaml b/nettacker/modules/scan/drupal_modules.yaml similarity index 94% rename from modules/scan/drupal_modules.yaml rename to nettacker/modules/scan/drupal_modules.yaml index bfe074f67..ba40f150c 100644 --- a/modules/scan/drupal_modules.yaml +++ b/nettacker/modules/scan/drupal_modules.yaml @@ -37,5 +37,5 @@ payloads: condition_type: or conditions: content: - regex: \/(?i)\bmodules\b\/(.+?)\/ + regex: (?i)\/\bmodules\b\/(.+?)\/ reverse: false diff --git a/modules/scan/drupal_theme.yaml b/nettacker/modules/scan/drupal_theme.yaml similarity index 100% rename from modules/scan/drupal_theme.yaml rename to nettacker/modules/scan/drupal_theme.yaml diff --git a/modules/scan/drupal_version.yaml b/nettacker/modules/scan/drupal_version.yaml similarity index 100% rename from modules/scan/drupal_version.yaml rename to nettacker/modules/scan/drupal_version.yaml diff --git a/modules/scan/http_html_title.yaml b/nettacker/modules/scan/http_html_title.yaml similarity index 100% rename from modules/scan/http_html_title.yaml rename to nettacker/modules/scan/http_html_title.yaml diff --git a/modules/scan/http_redirect.yaml b/nettacker/modules/scan/http_redirect.yaml similarity index 100% rename from modules/scan/http_redirect.yaml rename to nettacker/modules/scan/http_redirect.yaml diff --git a/modules/scan/http_status.yaml b/nettacker/modules/scan/http_status.yaml similarity index 100% rename from modules/scan/http_status.yaml rename to nettacker/modules/scan/http_status.yaml diff --git a/modules/scan/icmp.yaml b/nettacker/modules/scan/icmp.yaml similarity index 100% rename from modules/scan/icmp.yaml rename to nettacker/modules/scan/icmp.yaml diff --git a/modules/scan/ivanti_epmm_lastpatcheddate.yaml b/nettacker/modules/scan/ivanti_epmm_lastpatcheddate.yaml similarity index 100% rename from modules/scan/ivanti_epmm_lastpatcheddate.yaml rename to nettacker/modules/scan/ivanti_epmm_lastpatcheddate.yaml diff --git a/modules/scan/ivanti_ics_lastpatcheddate.yaml b/nettacker/modules/scan/ivanti_ics_lastpatcheddate.yaml similarity index 100% rename from modules/scan/ivanti_ics_lastpatcheddate.yaml rename to nettacker/modules/scan/ivanti_ics_lastpatcheddate.yaml diff --git a/modules/scan/joomla_template.yaml b/nettacker/modules/scan/joomla_template.yaml similarity index 100% rename from modules/scan/joomla_template.yaml rename to nettacker/modules/scan/joomla_template.yaml diff --git a/modules/scan/joomla_user_enum.yaml b/nettacker/modules/scan/joomla_user_enum.yaml similarity index 100% rename from modules/scan/joomla_user_enum.yaml rename to nettacker/modules/scan/joomla_user_enum.yaml diff --git a/modules/scan/joomla_version.yaml b/nettacker/modules/scan/joomla_version.yaml similarity index 100% rename from modules/scan/joomla_version.yaml rename to nettacker/modules/scan/joomla_version.yaml diff --git a/modules/scan/moveit_version.yaml b/nettacker/modules/scan/moveit_version.yaml similarity index 100% rename from modules/scan/moveit_version.yaml rename to nettacker/modules/scan/moveit_version.yaml diff --git a/modules/scan/pma.yaml b/nettacker/modules/scan/pma.yaml similarity index 90% rename from modules/scan/pma.yaml rename to nettacker/modules/scan/pma.yaml index 9a9ed9ad7..607c45bd5 100644 --- a/modules/scan/pma.yaml +++ b/nettacker/modules/scan/pma.yaml @@ -26,7 +26,8 @@ payloads: suffix: "" interceptors: data: - urls: fuzzer_function_read_file_as_array('wordlists/pma_wordlist.txt') + urls: + read_from_file: wordlists/pma_wordlist.txt schema: - "http" - "https" diff --git a/modules/scan/port.yaml b/nettacker/modules/scan/port.yaml similarity index 99% rename from modules/scan/port.yaml rename to nettacker/modules/scan/port.yaml index d0cdeb376..02ea4d163 100644 --- a/modules/scan/port.yaml +++ b/nettacker/modules/scan/port.yaml @@ -1030,45 +1030,62 @@ payloads: open_port: regex: "" reverse: false - http: - regex: "HTTPStatus.BAD_REQUEST|HTTP\\/[\\d.]+\\s+[\\d]+|Server: |Content-Length: \\d+|Content-Type: |Access-Control-Request-Headers: |Forwarded: |Proxy-Authorization: |User-Agent: |X-Forwarded-Host: |Content-MD5: |Access-Control-Request-Method: |Accept-Language: " - reverse: false - ftp: + + ftp: &ftp regex: "220-You are user number|530 USER and PASS required|Invalid command: try being more creative|220 \\S+ FTP (Service|service|Server|server)|220 FTP Server ready|Directory status|Service closing control connection|Requested file action|Connection closed; transfer aborted|Directory not empty" reverse: false - ssh: - regex: "openssh|\\-OpenSSH\\_|\\r\\nProtocol mism|\\_sshlib|\\x00\\x1aversion info line too long|SSH Windows NT Server|WinNT sshd|sshd| SSH Secure Shell|WinSSHD" - reverse: false - telnet: - regex: "Check Point FireWall-1 authenticated Telnet server running on|Raptor Firewall Secure Gateway|No more connections are allowed to telnet server|Closing Telnet connection due to host problems|NetportExpress|WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING|Login authentication|recommended to use Stelnet|is not a secure protocol|Welcome to Microsoft Telnet Servic|no decompiling or reverse-engineering shall be allowed" - reverse: false - smtp: - regex: "Fidelix Fx2020|ESMTP|Server ready|SMTP synchronization error|220-Greetings|ESMTP Arnet Email Security|SMTP 2.0" + ftps: *ftp + + http: + regex: "HTTPStatus.BAD_REQUEST|HTTP\\/[\\d.]+\\s+[\\d]+|Server: |Content-Length: \\d+|Content-Type: |Access-Control-Request-Headers: |Forwarded: |Proxy-Authorization: |User-Agent: |X-Forwarded-Host: |Content-MD5: |Access-Control-Request-Method: |Accept-Language: " reverse: false + imap: regex: "Internet Mail Server|IMAP4 service|BYE Hi This is the IMAP SSL Redirect|LITERAL\\+ SASL\\-IR LOGIN\\-REFERRALS ID ENABLE IDLE AUTH\\=PLAIN AUTH\\=LOGIN AUTH\\=DIGEST\\-MD5 AUTH\\=CRAM-MD5|CAPABILITY completed|OK IMAPrev1|LITERAL\\+ SASL\\-IR LOGIN\\-REFERRALS ID ENABLE IDLE NAMESPACE AUTH\\=PLAIN AUTH\\=LOGIN|BAD Error in IMAP command received by server|IMAP4rev1 SASL-IR|OK \\[CAPABILITY IMAP4rev1" reverse: false + mariadb: regex: "is not allowed to connect to this MariaDB server" reverse: false + mysql: regex: "is not allowed to connect to this MySQL server" reverse: false + + nntp: + regex: "NetWare\\-News\\-Server|NetWare nntpd|nntp|Leafnode nntpd|InterNetNews NNRP server INN" + reverse: false + + pop3: &pop3 + regex: "POP3|POP3 gateway ready|POP3 Server|Welcome to mpopd|OK Hello there" + reverse: false + pop3s: *pop3 + + portmap: + regex: "Program Version Protocol Port|portmapper|nfs 2|nlockmgr 1" + reverse: false + postgressql: regex: "FATAL 1\\: invalid length of startup packet|received invalid response to SSL negotiation\\:|unsupported frontend protocol|fe\\_sendauth\\: no password supplied|no pg\\_hba\\.conf entry for host" reverse: false + pptp: regex: "Hostname: pptp server|Vendor: Fortinet pptp" reverse: false + + smtp: &smtp + regex: "Fidelix Fx2020|ESMTP|Server ready|SMTP synchronization error|220-Greetings|ESMTP Arnet Email Security|SMTP 2.0" + reverse: false + smtps: *smtp + rsync: regex: "@RSYNCD\\:" reverse: false - portmap: - regex: "Program Version Protocol Port|portmapper|nfs 2|nlockmgr 1" - reverse: false - nntp: - regex: "NetWare\\-News\\-Server|NetWare nntpd|nntp|Leafnode nntpd|InterNetNews NNRP server INN" + + ssh: + regex: "openssh|\\-OpenSSH\\_|\\r\\nProtocol mism|\\_sshlib|\\x00\\x1aversion info line too long|SSH Windows NT Server|WinNT sshd|sshd| SSH Secure Shell|WinSSHD" reverse: false - pop3: - regex: "POP3|POP3 gateway ready|POP3 Server|Welcome to mpopd|OK Hello there" + + telnet: + regex: "Check Point FireWall-1 authenticated Telnet server running on|Raptor Firewall Secure Gateway|No more connections are allowed to telnet server|Closing Telnet connection due to host problems|NetportExpress|WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING|Login authentication|recommended to use Stelnet|is not a secure protocol|Welcome to Microsoft Telnet Servic|no decompiling or reverse-engineering shall be allowed" reverse: false diff --git a/modules/scan/subdomain.yaml b/nettacker/modules/scan/subdomain.yaml similarity index 100% rename from modules/scan/subdomain.yaml rename to nettacker/modules/scan/subdomain.yaml diff --git a/modules/scan/viewdns_reverse_iplookup.yaml b/nettacker/modules/scan/viewdns_reverse_iplookup.yaml similarity index 100% rename from modules/scan/viewdns_reverse_iplookup.yaml rename to nettacker/modules/scan/viewdns_reverse_iplookup.yaml diff --git a/modules/scan/waf.yaml b/nettacker/modules/scan/waf.yaml similarity index 99% rename from modules/scan/waf.yaml rename to nettacker/modules/scan/waf.yaml index 98c602b7f..0a5776202 100644 --- a/modules/scan/waf.yaml +++ b/nettacker/modules/scan/waf.yaml @@ -27,7 +27,8 @@ payloads: suffix: "" interceptors: data: - #urls: fuzzer_function_read_file_as_array('wordlists/admin_wordlist.txt') + # urls: + # read_from_file: wordlists/admin_wordlist.txt schema: # - "http" - "https" diff --git a/modules/scan/web_technologies.yaml b/nettacker/modules/scan/web_technologies.yaml similarity index 99% rename from modules/scan/web_technologies.yaml rename to nettacker/modules/scan/web_technologies.yaml index e3d7144ab..ce2c459d3 100644 --- a/modules/scan/web_technologies.yaml +++ b/nettacker/modules/scan/web_technologies.yaml @@ -29,7 +29,8 @@ payloads: suffix: "" interceptors: data: - #urls: fuzzer_function_read_file_as_array('wordlists/admin_wordlist.txt') + # urls: + # read_from_file: wordlists/admin_wordlist.txt schema: - "http" - "https" diff --git a/modules/scan/wordpress_version.yaml b/nettacker/modules/scan/wordpress_version.yaml similarity index 100% rename from modules/scan/wordpress_version.yaml rename to nettacker/modules/scan/wordpress_version.yaml diff --git a/modules/scan/wp_plugin.yaml b/nettacker/modules/scan/wp_plugin.yaml similarity index 92% rename from modules/scan/wp_plugin.yaml rename to nettacker/modules/scan/wp_plugin.yaml index 0940e59e8..034350303 100644 --- a/modules/scan/wp_plugin.yaml +++ b/nettacker/modules/scan/wp_plugin.yaml @@ -35,7 +35,8 @@ payloads: - 80 - 443 #We are using small txt file. Work need to be done for handling user input based format files - paths: fuzzer_function_read_file_as_array('wordlists/wp_plugin_small.txt') + paths: + read_from_file: wordlists/wp_plugin_small.txt response: condition_type: and diff --git a/modules/scan/wp_theme.yaml b/nettacker/modules/scan/wp_theme.yaml similarity index 91% rename from modules/scan/wp_theme.yaml rename to nettacker/modules/scan/wp_theme.yaml index d55c3c9fe..2a4300ec2 100644 --- a/modules/scan/wp_theme.yaml +++ b/nettacker/modules/scan/wp_theme.yaml @@ -35,7 +35,8 @@ payloads: ports: - 80 - 443 - paths: fuzzer_function_read_file_as_array('wordlists/wp_theme_small.txt') + paths: + read_from_file: wordlists/wp_theme_small.txt response: condition_type: or diff --git a/modules/scan/wp_timethumbs.yaml b/nettacker/modules/scan/wp_timethumbs.yaml similarity index 90% rename from modules/scan/wp_timethumbs.yaml rename to nettacker/modules/scan/wp_timethumbs.yaml index 11117a2e8..4edbef6b1 100644 --- a/modules/scan/wp_timethumbs.yaml +++ b/nettacker/modules/scan/wp_timethumbs.yaml @@ -35,7 +35,9 @@ payloads: ports: - 80 - 443 - paths: fuzzer_function_read_file_as_array('wordlists/wp_timethumbs.txt') + paths: + read_from_file: wordlists/wp_timethumbs.txt + response: condition_type: or diff --git a/modules/vuln/accela_cve_2021_34370.yaml b/nettacker/modules/vuln/accela_cve_2021_34370.yaml similarity index 100% rename from modules/vuln/accela_cve_2021_34370.yaml rename to nettacker/modules/vuln/accela_cve_2021_34370.yaml diff --git a/modules/vuln/adobe_coldfusion_cve_2023_26360.yaml b/nettacker/modules/vuln/adobe_coldfusion_cve_2023_26360.yaml similarity index 100% rename from modules/vuln/adobe_coldfusion_cve_2023_26360.yaml rename to nettacker/modules/vuln/adobe_coldfusion_cve_2023_26360.yaml diff --git a/modules/vuln/apache_cve_2021_41773.yaml b/nettacker/modules/vuln/apache_cve_2021_41773.yaml similarity index 100% rename from modules/vuln/apache_cve_2021_41773.yaml rename to nettacker/modules/vuln/apache_cve_2021_41773.yaml diff --git a/modules/vuln/apache_cve_2021_42013.yaml b/nettacker/modules/vuln/apache_cve_2021_42013.yaml similarity index 100% rename from modules/vuln/apache_cve_2021_42013.yaml rename to nettacker/modules/vuln/apache_cve_2021_42013.yaml diff --git a/modules/vuln/apache_struts.yaml b/nettacker/modules/vuln/apache_struts.yaml similarity index 100% rename from modules/vuln/apache_struts.yaml rename to nettacker/modules/vuln/apache_struts.yaml diff --git a/modules/vuln/aviatrix_cve_2021_40870.yaml b/nettacker/modules/vuln/aviatrix_cve_2021_40870.yaml similarity index 100% rename from modules/vuln/aviatrix_cve_2021_40870.yaml rename to nettacker/modules/vuln/aviatrix_cve_2021_40870.yaml diff --git a/modules/vuln/cisco_hyperflex_cve_2021_1497.yaml b/nettacker/modules/vuln/cisco_hyperflex_cve_2021_1497.yaml similarity index 100% rename from modules/vuln/cisco_hyperflex_cve_2021_1497.yaml rename to nettacker/modules/vuln/cisco_hyperflex_cve_2021_1497.yaml diff --git a/modules/vuln/citrix_cve_2019_19781.yaml b/nettacker/modules/vuln/citrix_cve_2019_19781.yaml similarity index 100% rename from modules/vuln/citrix_cve_2019_19781.yaml rename to nettacker/modules/vuln/citrix_cve_2019_19781.yaml diff --git a/modules/vuln/citrix_cve_2023_24488.yaml b/nettacker/modules/vuln/citrix_cve_2023_24488.yaml similarity index 100% rename from modules/vuln/citrix_cve_2023_24488.yaml rename to nettacker/modules/vuln/citrix_cve_2023_24488.yaml diff --git a/modules/vuln/citrix_cve_2023_4966.yaml b/nettacker/modules/vuln/citrix_cve_2023_4966.yaml similarity index 100% rename from modules/vuln/citrix_cve_2023_4966.yaml rename to nettacker/modules/vuln/citrix_cve_2023_4966.yaml diff --git a/modules/vuln/clickjacking.yaml b/nettacker/modules/vuln/clickjacking.yaml similarity index 100% rename from modules/vuln/clickjacking.yaml rename to nettacker/modules/vuln/clickjacking.yaml diff --git a/modules/vuln/cloudron_cve_2021_40868.yaml b/nettacker/modules/vuln/cloudron_cve_2021_40868.yaml similarity index 100% rename from modules/vuln/cloudron_cve_2021_40868.yaml rename to nettacker/modules/vuln/cloudron_cve_2021_40868.yaml diff --git a/modules/vuln/confluence_cve_2023_22515.yaml b/nettacker/modules/vuln/confluence_cve_2023_22515.yaml similarity index 100% rename from modules/vuln/confluence_cve_2023_22515.yaml rename to nettacker/modules/vuln/confluence_cve_2023_22515.yaml diff --git a/modules/vuln/confluence_cve_2023_22527.yaml b/nettacker/modules/vuln/confluence_cve_2023_22527.yaml similarity index 100% rename from modules/vuln/confluence_cve_2023_22527.yaml rename to nettacker/modules/vuln/confluence_cve_2023_22527.yaml diff --git a/modules/vuln/content_security_policy.yaml b/nettacker/modules/vuln/content_security_policy.yaml similarity index 100% rename from modules/vuln/content_security_policy.yaml rename to nettacker/modules/vuln/content_security_policy.yaml diff --git a/modules/vuln/content_type_options.yaml b/nettacker/modules/vuln/content_type_options.yaml similarity index 100% rename from modules/vuln/content_type_options.yaml rename to nettacker/modules/vuln/content_type_options.yaml diff --git a/modules/vuln/cyberoam_netgenie_cve_2021_38702.yaml b/nettacker/modules/vuln/cyberoam_netgenie_cve_2021_38702.yaml similarity index 100% rename from modules/vuln/cyberoam_netgenie_cve_2021_38702.yaml rename to nettacker/modules/vuln/cyberoam_netgenie_cve_2021_38702.yaml diff --git a/modules/vuln/exponent_cms_cve_2021_38751.yaml b/nettacker/modules/vuln/exponent_cms_cve_2021_38751.yaml similarity index 100% rename from modules/vuln/exponent_cms_cve_2021_38751.yaml rename to nettacker/modules/vuln/exponent_cms_cve_2021_38751.yaml diff --git a/modules/vuln/f5_cve_2020_5902.yaml b/nettacker/modules/vuln/f5_cve_2020_5902.yaml similarity index 100% rename from modules/vuln/f5_cve_2020_5902.yaml rename to nettacker/modules/vuln/f5_cve_2020_5902.yaml diff --git a/modules/vuln/forgerock_am_cve_2021_35464.yaml b/nettacker/modules/vuln/forgerock_am_cve_2021_35464.yaml similarity index 100% rename from modules/vuln/forgerock_am_cve_2021_35464.yaml rename to nettacker/modules/vuln/forgerock_am_cve_2021_35464.yaml diff --git a/modules/vuln/galera_webtemp_cve_2021_40960.yaml b/nettacker/modules/vuln/galera_webtemp_cve_2021_40960.yaml similarity index 100% rename from modules/vuln/galera_webtemp_cve_2021_40960.yaml rename to nettacker/modules/vuln/galera_webtemp_cve_2021_40960.yaml diff --git a/modules/vuln/grafana_cve_2021_43798.yaml b/nettacker/modules/vuln/grafana_cve_2021_43798.yaml similarity index 100% rename from modules/vuln/grafana_cve_2021_43798.yaml rename to nettacker/modules/vuln/grafana_cve_2021_43798.yaml diff --git a/modules/vuln/graphql.yaml b/nettacker/modules/vuln/graphql.yaml similarity index 100% rename from modules/vuln/graphql.yaml rename to nettacker/modules/vuln/graphql.yaml diff --git a/modules/vuln/gurock_testrail_cve_2021_40875.yaml b/nettacker/modules/vuln/gurock_testrail_cve_2021_40875.yaml similarity index 100% rename from modules/vuln/gurock_testrail_cve_2021_40875.yaml rename to nettacker/modules/vuln/gurock_testrail_cve_2021_40875.yaml diff --git a/modules/vuln/hoteldruid_cve_2021-37833.yaml b/nettacker/modules/vuln/hoteldruid_cve_2021-37833.yaml similarity index 100% rename from modules/vuln/hoteldruid_cve_2021-37833.yaml rename to nettacker/modules/vuln/hoteldruid_cve_2021-37833.yaml diff --git a/modules/vuln/http_cookie.yaml b/nettacker/modules/vuln/http_cookie.yaml similarity index 100% rename from modules/vuln/http_cookie.yaml rename to nettacker/modules/vuln/http_cookie.yaml diff --git a/modules/vuln/http_cors.yaml b/nettacker/modules/vuln/http_cors.yaml similarity index 100% rename from modules/vuln/http_cors.yaml rename to nettacker/modules/vuln/http_cors.yaml diff --git a/modules/vuln/http_options_enabled.yaml b/nettacker/modules/vuln/http_options_enabled.yaml similarity index 100% rename from modules/vuln/http_options_enabled.yaml rename to nettacker/modules/vuln/http_options_enabled.yaml diff --git a/modules/vuln/ivanti_epmm_cve_2023_35082.yaml b/nettacker/modules/vuln/ivanti_epmm_cve_2023_35082.yaml similarity index 100% rename from modules/vuln/ivanti_epmm_cve_2023_35082.yaml rename to nettacker/modules/vuln/ivanti_epmm_cve_2023_35082.yaml diff --git a/modules/vuln/ivanti_ics_cve_2023_46805.yaml b/nettacker/modules/vuln/ivanti_ics_cve_2023_46805.yaml similarity index 100% rename from modules/vuln/ivanti_ics_cve_2023_46805.yaml rename to nettacker/modules/vuln/ivanti_ics_cve_2023_46805.yaml diff --git a/modules/vuln/justwirting_cve_2021_41878.yaml b/nettacker/modules/vuln/justwirting_cve_2021_41878.yaml similarity index 100% rename from modules/vuln/justwirting_cve_2021_41878.yaml rename to nettacker/modules/vuln/justwirting_cve_2021_41878.yaml diff --git a/modules/vuln/log4j_cve_2021_44228.yaml b/nettacker/modules/vuln/log4j_cve_2021_44228.yaml similarity index 100% rename from modules/vuln/log4j_cve_2021_44228.yaml rename to nettacker/modules/vuln/log4j_cve_2021_44228.yaml diff --git a/modules/vuln/maxsite_cms_cve_2021_35265.yaml b/nettacker/modules/vuln/maxsite_cms_cve_2021_35265.yaml similarity index 100% rename from modules/vuln/maxsite_cms_cve_2021_35265.yaml rename to nettacker/modules/vuln/maxsite_cms_cve_2021_35265.yaml diff --git a/modules/vuln/msexchange_cve_2021_26855.yaml b/nettacker/modules/vuln/msexchange_cve_2021_26855.yaml similarity index 100% rename from modules/vuln/msexchange_cve_2021_26855.yaml rename to nettacker/modules/vuln/msexchange_cve_2021_26855.yaml diff --git a/modules/vuln/msexchange_cve_2021_34473.yaml b/nettacker/modules/vuln/msexchange_cve_2021_34473.yaml similarity index 100% rename from modules/vuln/msexchange_cve_2021_34473.yaml rename to nettacker/modules/vuln/msexchange_cve_2021_34473.yaml diff --git a/modules/vuln/novnc_cve_2021_3654.yaml b/nettacker/modules/vuln/novnc_cve_2021_3654.yaml similarity index 100% rename from modules/vuln/novnc_cve_2021_3654.yaml rename to nettacker/modules/vuln/novnc_cve_2021_3654.yaml diff --git a/modules/vuln/omigod_cve_2021_38647.yaml b/nettacker/modules/vuln/omigod_cve_2021_38647.yaml similarity index 100% rename from modules/vuln/omigod_cve_2021_38647.yaml rename to nettacker/modules/vuln/omigod_cve_2021_38647.yaml diff --git a/modules/vuln/payara_cve_2021_41381.yaml b/nettacker/modules/vuln/payara_cve_2021_41381.yaml similarity index 100% rename from modules/vuln/payara_cve_2021_41381.yaml rename to nettacker/modules/vuln/payara_cve_2021_41381.yaml diff --git a/modules/vuln/phpinfo_cve_2021_37704.yaml b/nettacker/modules/vuln/phpinfo_cve_2021_37704.yaml similarity index 100% rename from modules/vuln/phpinfo_cve_2021_37704.yaml rename to nettacker/modules/vuln/phpinfo_cve_2021_37704.yaml diff --git a/modules/vuln/placeos_cve_2021_41826.yaml b/nettacker/modules/vuln/placeos_cve_2021_41826.yaml similarity index 100% rename from modules/vuln/placeos_cve_2021_41826.yaml rename to nettacker/modules/vuln/placeos_cve_2021_41826.yaml diff --git a/modules/vuln/prestashop_cve_2021_37538.yaml b/nettacker/modules/vuln/prestashop_cve_2021_37538.yaml similarity index 100% rename from modules/vuln/prestashop_cve_2021_37538.yaml rename to nettacker/modules/vuln/prestashop_cve_2021_37538.yaml diff --git a/modules/vuln/puneethreddyhc_sqli_cve_2021_41648.yaml b/nettacker/modules/vuln/puneethreddyhc_sqli_cve_2021_41648.yaml similarity index 100% rename from modules/vuln/puneethreddyhc_sqli_cve_2021_41648.yaml rename to nettacker/modules/vuln/puneethreddyhc_sqli_cve_2021_41648.yaml diff --git a/modules/vuln/puneethreddyhc_sqli_cve_2021_41649.yaml b/nettacker/modules/vuln/puneethreddyhc_sqli_cve_2021_41649.yaml similarity index 100% rename from modules/vuln/puneethreddyhc_sqli_cve_2021_41649.yaml rename to nettacker/modules/vuln/puneethreddyhc_sqli_cve_2021_41649.yaml diff --git a/modules/vuln/qsan_storage_xss_cve_2021_37216.yaml b/nettacker/modules/vuln/qsan_storage_xss_cve_2021_37216.yaml similarity index 100% rename from modules/vuln/qsan_storage_xss_cve_2021_37216.yaml rename to nettacker/modules/vuln/qsan_storage_xss_cve_2021_37216.yaml diff --git a/modules/vuln/server_version.yaml b/nettacker/modules/vuln/server_version.yaml similarity index 100% rename from modules/vuln/server_version.yaml rename to nettacker/modules/vuln/server_version.yaml diff --git a/modules/vuln/strict_transport_security.yaml b/nettacker/modules/vuln/strict_transport_security.yaml similarity index 100% rename from modules/vuln/strict_transport_security.yaml rename to nettacker/modules/vuln/strict_transport_security.yaml diff --git a/modules/vuln/subdomain_takeover.yaml b/nettacker/modules/vuln/subdomain_takeover.yaml similarity index 100% rename from modules/vuln/subdomain_takeover.yaml rename to nettacker/modules/vuln/subdomain_takeover.yaml diff --git a/modules/vuln/teamcity_cve_2024_27198.yaml b/nettacker/modules/vuln/teamcity_cve_2024_27198.yaml similarity index 100% rename from modules/vuln/teamcity_cve_2024_27198.yaml rename to nettacker/modules/vuln/teamcity_cve_2024_27198.yaml diff --git a/modules/vuln/tieline_cve_2021_35336.yaml b/nettacker/modules/vuln/tieline_cve_2021_35336.yaml similarity index 100% rename from modules/vuln/tieline_cve_2021_35336.yaml rename to nettacker/modules/vuln/tieline_cve_2021_35336.yaml diff --git a/modules/vuln/tjws_cve_2021_37573.yaml b/nettacker/modules/vuln/tjws_cve_2021_37573.yaml similarity index 100% rename from modules/vuln/tjws_cve_2021_37573.yaml rename to nettacker/modules/vuln/tjws_cve_2021_37573.yaml diff --git a/modules/vuln/vbulletin_cve_2019_16759.yaml b/nettacker/modules/vuln/vbulletin_cve_2019_16759.yaml similarity index 100% rename from modules/vuln/vbulletin_cve_2019_16759.yaml rename to nettacker/modules/vuln/vbulletin_cve_2019_16759.yaml diff --git a/modules/vuln/wp_plugin_cve_2021_38314.yaml b/nettacker/modules/vuln/wp_plugin_cve_2021_38314.yaml similarity index 100% rename from modules/vuln/wp_plugin_cve_2021_38314.yaml rename to nettacker/modules/vuln/wp_plugin_cve_2021_38314.yaml diff --git a/modules/vuln/wp_plugin_cve_2021_39316.yaml b/nettacker/modules/vuln/wp_plugin_cve_2021_39316.yaml similarity index 100% rename from modules/vuln/wp_plugin_cve_2021_39316.yaml rename to nettacker/modules/vuln/wp_plugin_cve_2021_39316.yaml diff --git a/modules/vuln/wp_plugin_cve_2021_39320.yaml b/nettacker/modules/vuln/wp_plugin_cve_2021_39320.yaml similarity index 100% rename from modules/vuln/wp_plugin_cve_2021_39320.yaml rename to nettacker/modules/vuln/wp_plugin_cve_2021_39320.yaml diff --git a/modules/vuln/wp_plugin_cve_2023_6875.yaml b/nettacker/modules/vuln/wp_plugin_cve_2023_6875.yaml similarity index 100% rename from modules/vuln/wp_plugin_cve_2023_6875.yaml rename to nettacker/modules/vuln/wp_plugin_cve_2023_6875.yaml diff --git a/modules/vuln/wp_xmlrpc_bruteforce.yaml b/nettacker/modules/vuln/wp_xmlrpc_bruteforce.yaml similarity index 100% rename from modules/vuln/wp_xmlrpc_bruteforce.yaml rename to nettacker/modules/vuln/wp_xmlrpc_bruteforce.yaml diff --git a/modules/vuln/wp_xmlrpc_dos.yaml b/nettacker/modules/vuln/wp_xmlrpc_dos.yaml similarity index 100% rename from modules/vuln/wp_xmlrpc_dos.yaml rename to nettacker/modules/vuln/wp_xmlrpc_dos.yaml diff --git a/modules/vuln/wp_xmlrpc_pingback.yaml b/nettacker/modules/vuln/wp_xmlrpc_pingback.yaml similarity index 100% rename from modules/vuln/wp_xmlrpc_pingback.yaml rename to nettacker/modules/vuln/wp_xmlrpc_pingback.yaml diff --git a/modules/vuln/x_powered_by.yaml b/nettacker/modules/vuln/x_powered_by.yaml similarity index 100% rename from modules/vuln/x_powered_by.yaml rename to nettacker/modules/vuln/x_powered_by.yaml diff --git a/modules/vuln/x_xss_protection.yaml b/nettacker/modules/vuln/x_xss_protection.yaml similarity index 100% rename from modules/vuln/x_xss_protection.yaml rename to nettacker/modules/vuln/x_xss_protection.yaml diff --git a/modules/vuln/xdebug_rce.yaml b/nettacker/modules/vuln/xdebug_rce.yaml similarity index 100% rename from modules/vuln/xdebug_rce.yaml rename to nettacker/modules/vuln/xdebug_rce.yaml diff --git a/modules/vuln/zoho_cve_2021_40539.yaml b/nettacker/modules/vuln/zoho_cve_2021_40539.yaml similarity index 100% rename from modules/vuln/zoho_cve_2021_40539.yaml rename to nettacker/modules/vuln/zoho_cve_2021_40539.yaml diff --git a/web/readme.md b/nettacker/web/readme.md similarity index 100% rename from web/readme.md rename to nettacker/web/readme.md diff --git a/web/static/css/animate.min.css b/nettacker/web/static/css/animate.min.css similarity index 100% rename from web/static/css/animate.min.css rename to nettacker/web/static/css/animate.min.css diff --git a/web/static/css/bootstrap-select.min.css b/nettacker/web/static/css/bootstrap-select.min.css similarity index 100% rename from web/static/css/bootstrap-select.min.css rename to nettacker/web/static/css/bootstrap-select.min.css diff --git a/web/static/css/bootstrap-tagsinput.css b/nettacker/web/static/css/bootstrap-tagsinput.css similarity index 100% rename from web/static/css/bootstrap-tagsinput.css rename to nettacker/web/static/css/bootstrap-tagsinput.css diff --git a/web/static/css/bootstrap.min.css b/nettacker/web/static/css/bootstrap.min.css similarity index 100% rename from web/static/css/bootstrap.min.css rename to nettacker/web/static/css/bootstrap.min.css diff --git a/web/static/css/buttons.css b/nettacker/web/static/css/buttons.css similarity index 100% rename from web/static/css/buttons.css rename to nettacker/web/static/css/buttons.css diff --git a/web/static/css/flag-icon.min.css b/nettacker/web/static/css/flag-icon.min.css similarity index 100% rename from web/static/css/flag-icon.min.css rename to nettacker/web/static/css/flag-icon.min.css diff --git a/web/static/css/font-awesome.min.css b/nettacker/web/static/css/font-awesome.min.css similarity index 100% rename from web/static/css/font-awesome.min.css rename to nettacker/web/static/css/font-awesome.min.css diff --git a/web/static/css/introjs-dark.css b/nettacker/web/static/css/introjs-dark.css similarity index 100% rename from web/static/css/introjs-dark.css rename to nettacker/web/static/css/introjs-dark.css diff --git a/web/static/css/introjs-flattener.css b/nettacker/web/static/css/introjs-flattener.css similarity index 100% rename from web/static/css/introjs-flattener.css rename to nettacker/web/static/css/introjs-flattener.css diff --git a/web/static/css/introjs-modern.css b/nettacker/web/static/css/introjs-modern.css similarity index 100% rename from web/static/css/introjs-modern.css rename to nettacker/web/static/css/introjs-modern.css diff --git a/web/static/css/introjs-nassim.css b/nettacker/web/static/css/introjs-nassim.css similarity index 95% rename from web/static/css/introjs-nassim.css rename to nettacker/web/static/css/introjs-nassim.css index 734bdb27a..fd4238ad7 100644 --- a/web/static/css/introjs-nassim.css +++ b/nettacker/web/static/css/introjs-nassim.css @@ -1,308 +1,308 @@ -.introjs-overlay { - position: absolute; - z-index: 999999; - background: #181818; - opacity: 0; - - -webkit-transition: all 0.3s ease-out; - -moz-transition: all 0.3s ease-out; - -ms-transition: all 0.3s ease-out; - -o-transition: all 0.3s ease-out; - transition: all 0.3s ease-out; -} - -.introjs-fixParent { - z-index: auto !important; - opacity: 1.0 !important; -} - -.introjs-showElement { - z-index: 9999999 !important; -} - -.introjs-relativePosition { - position: relative; -} - -.introjs-helperLayer { - position: absolute; - z-index: 9999998; - background-color: #FFF; - background-color: rgba(255, 255, 255, .9); - border: 1px solid #777; - border: 2px solid rgba(117, 117, 117, 1); - border-radius: 4px; - box-shadow: 0 5px 8px -3px rgba(0, 0, 0, .6); - -webkit-transition: all 0.3s ease-out; - -moz-transition: all 0.3s ease-out; - -ms-transition: all 0.3s ease-out; - -o-transition: all 0.3s ease-out; - transition: all 0.3s ease-out; -} - -.introjs-helperNumberLayer { - position: absolute; - top: -16px; - left: -9px; - z-index: 9999999999 !important; - padding: 2px; - font-family: Arial, verdana, tahoma; - font-size: 13px; - font-weight: bold; - color: #fff; /* Old browsers */ /* Chrome10+,Safari5.1+ */ - background: #DA4433; - width: 20px; - box-shadow: 0 1px 1px rgba(0, 0, 0, .35); - height: 20px; - text-align: center; - line-height: 20px; - border: 2px solid #DA4433; - border-radius: 0; /* IE6-9 */ /* IE10 text shadows */ -} - -.introjs-helperNumberLayer:after { - content: ""; - width: 0; - height: 0; - position: absolute; - content: 0; - left: -3px; - bottom: -10px; - border: 4px solid transparent; - border-right-color: #900; - border-top-color: #900; -} - -.introjs-arrow { - border: 5px solid white; - content: ''; - position: absolute; -} - -.introjs-arrow.top { - top: -10px; - border-top-color: transparent; - border-right-color: transparent; - border-bottom-color: #ecf0f1; - border-left-color: transparent; -} - -.introjs-arrow.right { - right: -10px; - top: 10px; - border-top-color: transparent; - border-right-color: transparent; - border-bottom-color: transparent; - border-left-color: #ecf0f1; -} - -.introjs-arrow.bottom { - bottom: -10px; - border-top-color: #ecf0f1; - border-right-color: transparent; - border-bottom-color: transparent; - border-left-color: transparent; -} - -.introjs-arrow.left { - left: -10px; - top: 10px; - border-top-color: transparent; - border-right-color: #ecf0f1; - border-bottom-color: transparent; - border-left-color: transparent; -} - -.introjs-tooltip { - position: absolute; - padding: 10px 10px; - background-color: #ecf0f1; - min-width: 200px; - max-width: 300px; - border-radius: 3px; - /* border-radius: 3px; */ - /* box-shadow: 0 6px 7px -4px rgba(0,0,0,.4); */ - -webkit-transition: opacity 0.1s ease-out; - -moz-transition: opacity 0.1s ease-out; - -ms-transition: opacity 0.1s ease-out; - -o-transition: opacity 0.1s ease-out; - transition: opacity 0.1s ease-out; - /* border: 5px double #0787AF; */ -} - -.introjs-tooltiptext { - margin-left: -10px; - text-align: justify !important; - margin-right: -10px; - /* border-top: 1px solid #FFFFFF; */ - /* background: #FAFAFA; */ - color: #2c3e50; - padding: 25px 30px 15px; - /* border-bottom: 1px solid #FFFFFF; */ -} - -.introjs-tooltipbuttons { - text-align: center; - /* background: rgba(0, 0, 0, 0.06); */ - border-radius: 0 0 8px 8px; - padding-bottom: 10px; -} - -/* - Buttons style by http://nicolasgallagher.com/lab/css3-github-buttons/ - Changed by Afshin Mehrabani -*/ -.introjs-button { - position: relative; - overflow: visible; - display: inline-block; - padding: 0.5em 0.8em; - /* box-shadow: 0 2px 0px -0px #306588; */ - margin: 0; - outline: none; - border: 2px solid; - background: transparent; - text-decoration: none; - font: 11px/normal sans-serif; - color: #2980b9 !important; - white-space: nowrap; - cursor: pointer; - outline: none !important; - -webkit-background-clip: padding; - -moz-background-clip: padding; - -o-background-clip: padding-box; - /*background-clip: padding-box;*/ /* commented out due to Opera 11.10 bug */ - -webkit-border-radius: 0.2em; - -moz-border-radius: 0.2em; - border-radius: 0.2em; - /* IE hacks */ - zoom: 1; - *display: inline; - margin-top: 10px; - transition: all 0.25s ease; - -webkit-transition: all 0.25s ease; - -moz-transition: all 0.25s ease; - -ms-transition: all 0.25s ease; - -o-transition: all 0.25s ease; -} - -.introjs-button:hover { - color: #fff; - background: #0ca29b; - text-decoration: none; - border-color: #235677; -} - -.introjs-button:focus, -.introjs-button:active { - background: #23587A; - text-decoration: none; - color: #fff; - /* bottom: -1px; */ - box-shadow: none; - border-color: #173B53; -} - -/* overrides extra padding on button elements in Firefox */ -.introjs-button::-moz-focus-inner { - padding: 0; - border: 0; -} - -.introjs-skipbutton { - margin-right: 5px; - color: #c00; - background: transparent; -} - -.introjs-skipbutton:hover { - background: #00ebd7; - border-color: #000ead; - -} - -.introjs-skipbutton:active, .introjs-skipbutton:focus { - background: #C02312; - /* box-shadow: 0 1px 0px -0px #6F1309; */ - -} - -.introjs-prevbutton { - -webkit-border-radius: 0.2em 0 0 0.2em; - -moz-border-radius: 0.2em 0 0 0.2em; - border-radius: 0.2em 0 0 0.2em; - border-right: none; -} - -.introjs-nextbutton { - -webkit-border-radius: 0 0.2em 0.2em 0; - -moz-border-radius: 0 0.2em 0.2em 0; - border-radius: 0 0.2em 0.2em 0; -} - -.introjs-disabled, .introjs-disabled:hover, .introjs-disabled:focus { - color: #C2C2C2 !important; - border-color: #d4d4d4; - cursor: default; - /* box-shadow: 0 2px 0px -0px #CACED1; */ - background-color: #E6E6E6; - background-image: none; - text-decoration: none; -} - -.introjs-bullets { - text-align: center; - position: absolute; - left: 0; - right: 0; - top: -5px; -} - -.introjs-bullets ul { - clear: both; - margin: 15px auto 0; - padding: 0; - display: inline-block; -} - -.introjs-bullets ul li { - list-style: none; - float: left; - margin: 0 2px; -} - -.introjs-bullets ul li a { - display: block; - width: 6px; - height: 6px; - background: #ccc; - border-radius: 10px; - -moz-border-radius: 10px; - -webkit-border-radius: 10px; - text-decoration: none; -} - -.introjs-bullets ul li a:hover { - background: #999; -} - -.introjs-bullets ul li a.active { - background: #999; -} - -.introjs-progress { - background-color: #FAFAFA; - margin: 5px 20px; -} - -.introjs-progressbar { - background-color: #2980b9; -} - -.introjsFloatingElement { - position: absolute; - height: 0; - width: 0; - left: 50%; - top: 50%; -} +.introjs-overlay { + position: absolute; + z-index: 999999; + background: #181818; + opacity: 0; + + -webkit-transition: all 0.3s ease-out; + -moz-transition: all 0.3s ease-out; + -ms-transition: all 0.3s ease-out; + -o-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; +} + +.introjs-fixParent { + z-index: auto !important; + opacity: 1.0 !important; +} + +.introjs-showElement { + z-index: 9999999 !important; +} + +.introjs-relativePosition { + position: relative; +} + +.introjs-helperLayer { + position: absolute; + z-index: 9999998; + background-color: #FFF; + background-color: rgba(255, 255, 255, .9); + border: 1px solid #777; + border: 2px solid rgba(117, 117, 117, 1); + border-radius: 4px; + box-shadow: 0 5px 8px -3px rgba(0, 0, 0, .6); + -webkit-transition: all 0.3s ease-out; + -moz-transition: all 0.3s ease-out; + -ms-transition: all 0.3s ease-out; + -o-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; +} + +.introjs-helperNumberLayer { + position: absolute; + top: -16px; + left: -9px; + z-index: 9999999999 !important; + padding: 2px; + font-family: Arial, verdana, tahoma; + font-size: 13px; + font-weight: bold; + color: #fff; /* Old browsers */ /* Chrome10+,Safari5.1+ */ + background: #DA4433; + width: 20px; + box-shadow: 0 1px 1px rgba(0, 0, 0, .35); + height: 20px; + text-align: center; + line-height: 20px; + border: 2px solid #DA4433; + border-radius: 0; /* IE6-9 */ /* IE10 text shadows */ +} + +.introjs-helperNumberLayer:after { + content: ""; + width: 0; + height: 0; + position: absolute; + content: 0; + left: -3px; + bottom: -10px; + border: 4px solid transparent; + border-right-color: #900; + border-top-color: #900; +} + +.introjs-arrow { + border: 5px solid white; + content: ''; + position: absolute; +} + +.introjs-arrow.top { + top: -10px; + border-top-color: transparent; + border-right-color: transparent; + border-bottom-color: #ecf0f1; + border-left-color: transparent; +} + +.introjs-arrow.right { + right: -10px; + top: 10px; + border-top-color: transparent; + border-right-color: transparent; + border-bottom-color: transparent; + border-left-color: #ecf0f1; +} + +.introjs-arrow.bottom { + bottom: -10px; + border-top-color: #ecf0f1; + border-right-color: transparent; + border-bottom-color: transparent; + border-left-color: transparent; +} + +.introjs-arrow.left { + left: -10px; + top: 10px; + border-top-color: transparent; + border-right-color: #ecf0f1; + border-bottom-color: transparent; + border-left-color: transparent; +} + +.introjs-tooltip { + position: absolute; + padding: 10px 10px; + background-color: #ecf0f1; + min-width: 200px; + max-width: 300px; + border-radius: 3px; + /* border-radius: 3px; */ + /* box-shadow: 0 6px 7px -4px rgba(0,0,0,.4); */ + -webkit-transition: opacity 0.1s ease-out; + -moz-transition: opacity 0.1s ease-out; + -ms-transition: opacity 0.1s ease-out; + -o-transition: opacity 0.1s ease-out; + transition: opacity 0.1s ease-out; + /* border: 5px double #0787AF; */ +} + +.introjs-tooltiptext { + margin-left: -10px; + text-align: justify !important; + margin-right: -10px; + /* border-top: 1px solid #FFFFFF; */ + /* background: #FAFAFA; */ + color: #2c3e50; + padding: 25px 30px 15px; + /* border-bottom: 1px solid #FFFFFF; */ +} + +.introjs-tooltipbuttons { + text-align: center; + /* background: rgba(0, 0, 0, 0.06); */ + border-radius: 0 0 8px 8px; + padding-bottom: 10px; +} + +/* + Buttons style by http://nicolasgallagher.com/lab/css3-github-buttons/ + Changed by Afshin Mehrabani +*/ +.introjs-button { + position: relative; + overflow: visible; + display: inline-block; + padding: 0.5em 0.8em; + /* box-shadow: 0 2px 0px -0px #306588; */ + margin: 0; + outline: none; + border: 2px solid; + background: transparent; + text-decoration: none; + font: 11px/normal sans-serif; + color: #2980b9 !important; + white-space: nowrap; + cursor: pointer; + outline: none !important; + -webkit-background-clip: padding; + -moz-background-clip: padding; + -o-background-clip: padding-box; + /*background-clip: padding-box;*/ /* commented out due to Opera 11.10 bug */ + -webkit-border-radius: 0.2em; + -moz-border-radius: 0.2em; + border-radius: 0.2em; + /* IE hacks */ + zoom: 1; + *display: inline; + margin-top: 10px; + transition: all 0.25s ease; + -webkit-transition: all 0.25s ease; + -moz-transition: all 0.25s ease; + -ms-transition: all 0.25s ease; + -o-transition: all 0.25s ease; +} + +.introjs-button:hover { + color: #fff; + background: #0ca29b; + text-decoration: none; + border-color: #235677; +} + +.introjs-button:focus, +.introjs-button:active { + background: #23587A; + text-decoration: none; + color: #fff; + /* bottom: -1px; */ + box-shadow: none; + border-color: #173B53; +} + +/* overrides extra padding on button elements in Firefox */ +.introjs-button::-moz-focus-inner { + padding: 0; + border: 0; +} + +.introjs-skipbutton { + margin-right: 5px; + color: #c00; + background: transparent; +} + +.introjs-skipbutton:hover { + background: #00ebd7; + border-color: #000ead; + +} + +.introjs-skipbutton:active, .introjs-skipbutton:focus { + background: #C02312; + /* box-shadow: 0 1px 0px -0px #6F1309; */ + +} + +.introjs-prevbutton { + -webkit-border-radius: 0.2em 0 0 0.2em; + -moz-border-radius: 0.2em 0 0 0.2em; + border-radius: 0.2em 0 0 0.2em; + border-right: none; +} + +.introjs-nextbutton { + -webkit-border-radius: 0 0.2em 0.2em 0; + -moz-border-radius: 0 0.2em 0.2em 0; + border-radius: 0 0.2em 0.2em 0; +} + +.introjs-disabled, .introjs-disabled:hover, .introjs-disabled:focus { + color: #C2C2C2 !important; + border-color: #d4d4d4; + cursor: default; + /* box-shadow: 0 2px 0px -0px #CACED1; */ + background-color: #E6E6E6; + background-image: none; + text-decoration: none; +} + +.introjs-bullets { + text-align: center; + position: absolute; + left: 0; + right: 0; + top: -5px; +} + +.introjs-bullets ul { + clear: both; + margin: 15px auto 0; + padding: 0; + display: inline-block; +} + +.introjs-bullets ul li { + list-style: none; + float: left; + margin: 0 2px; +} + +.introjs-bullets ul li a { + display: block; + width: 6px; + height: 6px; + background: #ccc; + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + text-decoration: none; +} + +.introjs-bullets ul li a:hover { + background: #999; +} + +.introjs-bullets ul li a.active { + background: #999; +} + +.introjs-progress { + background-color: #FAFAFA; + margin: 5px 20px; +} + +.introjs-progressbar { + background-color: #2980b9; +} + +.introjsFloatingElement { + position: absolute; + height: 0; + width: 0; + left: 50%; + top: 50%; +} diff --git a/web/static/css/introjs-nazanin.css b/nettacker/web/static/css/introjs-nazanin.css similarity index 95% rename from web/static/css/introjs-nazanin.css rename to nettacker/web/static/css/introjs-nazanin.css index 682252834..913aa6905 100644 --- a/web/static/css/introjs-nazanin.css +++ b/nettacker/web/static/css/introjs-nazanin.css @@ -1,265 +1,265 @@ -.introjs-overlay { - position: absolute; - z-index: 999999; - background: #525252; - opacity: 0; - - -webkit-transition: all 0.3s ease-out; - -moz-transition: all 0.3s ease-out; - -ms-transition: all 0.3s ease-out; - -o-transition: all 0.3s ease-out; - transition: all 0.3s ease-out; -} - -.introjs-fixParent { - z-index: auto !important; - opacity: 1.0 !important; -} - -.introjs-showElement { - z-index: 9999999 !important; -} - -.introjs-relativePosition { - position: relative; -} - -.introjs-helperLayer { - position: absolute; - z-index: 9999998; - background-color: #FFF; - background-color: rgba(255,255,255,.9); - border: 1px solid #777; - border: 3px solid rgba(211, 214, 209, 1); - border-radius: 0; - box-shadow: 0 5px 8px -3px rgba(0,0,0,.6); - -webkit-transition: all 0.3s ease-out; - -moz-transition: all 0.3s ease-out; - -ms-transition: all 0.3s ease-out; - -o-transition: all 0.3s ease-out; - transition: all 0.3s ease-out; -} - -.introjs-helperNumberLayer { - position: absolute; - top: -16px; - left: -16px; - z-index: 9999999999 !important; - padding: 2px; - font-family: Arial, verdana, tahoma; - font-size: 13px; - font-weight: bold; - color: white; /* Old browsers */ /* Chrome10+,Safari5.1+ */ - background:#ff3019 ; - width: 20px; - height:20px; - text-align: center; - line-height: 20px; - border: 3px solid #DA4433; - border-radius: 10px 10px 0; /* IE6-9 */ /* IE10 text shadows */ -} - -.introjs-arrow { - border: 5px solid white; - content:''; - position: absolute; -} -.introjs-arrow.top { - top: -10px; - border-top-color:transparent; - border-right-color:transparent; - border-bottom-color: #ecf0f1; - border-left-color:transparent; -} -.introjs-arrow.right { - right: -10px; - top: 10px; - border-top-color:transparent; - border-right-color:transparent; - border-bottom-color:transparent; - border-left-color:#ecf0f1; -} -.introjs-arrow.bottom { - bottom: -10px; - border-top-color:#ecf0f1; - border-right-color:transparent; - border-bottom-color:transparent; - border-left-color:transparent; -} -.introjs-arrow.left { - left: -10px; - top: 10px; - border-top-color:transparent; - border-right-color: #ecf0f1; - border-bottom-color:transparent; - border-left-color:transparent; -} - -.introjs-tooltip { - position: absolute; - padding: 10px; - background-color: #ecf0f1; - min-width: 200px; - max-width: 300px; - /* border-radius: 3px; */ - box-shadow: 0 6px 7px -4px rgba(0,0,0,.4); - -webkit-transition: opacity 0.1s ease-out; - -moz-transition: opacity 0.1s ease-out; - -ms-transition: opacity 0.1s ease-out; - -o-transition: opacity 0.1s ease-out; - transition: opacity 0.1s ease-out; -} - -.introjs-tooltiptext { - margin-left: -10px; - - margin-right: -10px; - border-top: 1px solid #FFFFFF; - background: #FAFAFA; - color: #2c3e50; - padding: 5px 10px; - border-bottom: 1px solid #FFFFFF; -} - -.introjs-tooltipbuttons { - text-align: center; -} - -/* - Buttons style by http://nicolasgallagher.com/lab/css3-github-buttons/ - Changed by Afshin Mehrabani -*/ -.introjs-button { - position: relative; - overflow: visible; - display: inline-block; - padding: 0.5em 0.8em; - box-shadow: 0 2px 0px -0px #306588; - margin: 0; - outline: none; - background: #2980b9; - text-decoration: none; - font: 11px/normal sans-serif; - color: #fff !important; - white-space: nowrap; - cursor: pointer; - outline: none !important; - -webkit-background-clip: padding; - -moz-background-clip: padding; - -o-background-clip: padding-box; - /*background-clip: padding-box;*/ /* commented out due to Opera 11.10 bug */ - -webkit-border-radius: 0.2em; - -moz-border-radius: 0.2em; - border-radius: 0.2em; - /* IE hacks */ - zoom: 1; - *display: inline; - margin-top: 10px; -} - -.introjs-button:hover { - color: #fff; - background: #2671A2; - text-decoration: none; - box-shadow: 0 2px 0px -0px #235677; -} - -.introjs-button:focus, -.introjs-button:active { - background: #23587A; text-decoration: none; - /* bottom: -1px; */ - box-shadow: 0 2px 0px 0px #173B53; -} - -/* overrides extra padding on button elements in Firefox */ -.introjs-button::-moz-focus-inner { - padding: 0; - border: 0; -} - -.introjs-skipbutton { - margin-right: 5px; - color: #fff; - background: #e74c3c; - box-shadow: 0 2px 0px -0px #B91D0D; -} - -.introjs-skipbutton:hover { - background: #EB1540; box-shadow: 0 2px 0px -0px #B91D0D; - -} - -.introjs-skipbutton:active, .introjs-skipbutton:focus { - background: #C02312; - box-shadow: 0 1px 0px -0px #6F1309; - -} - -.introjs-prevbutton { - -webkit-border-radius: 0.2em 0 0 0.2em; - -moz-border-radius: 0.2em 0 0 0.2em; - border-radius: 0.2em 0 0 0.2em; - border-right: none; -} - -.introjs-nextbutton { - -webkit-border-radius: 0 0.2em 0.2em 0; - -moz-border-radius: 0 0.2em 0.2em 0; - border-radius: 0 0.2em 0.2em 0; -} - -.introjs-disabled, .introjs-disabled:hover, .introjs-disabled:focus { - color: #C2C2C2 !important; - border-color: #d4d4d4; - cursor: default; - box-shadow: 0 2px 0px -0px #CACED1; - background-color: #E6E6E6; - background-image: none; - text-decoration: none; -} - -.introjs-bullets { - text-align: center; -} -.introjs-bullets ul { - clear: both; - margin: 15px auto 0; - padding: 0; - display: inline-block; -} -.introjs-bullets ul li { - list-style: none; - float: left; - margin: 0 2px; -} -.introjs-bullets ul li a { - display: block; - width: 6px; - height: 6px; - background: #ccc; - border-radius: 10px; - -moz-border-radius: 10px; - -webkit-border-radius: 10px; - text-decoration: none; -} -.introjs-bullets ul li a:hover { - background: #999; -} -.introjs-bullets ul li a.active { - background: #999; -} - -.introjs-progress { - background-color: #FAFAFA; -} -.introjs-progressbar { - background-color: #2980b9; -} - -.introjsFloatingElement { - position: absolute; - height: 0; - width: 0; - left: 50%; - top: 50%; -} +.introjs-overlay { + position: absolute; + z-index: 999999; + background: #525252; + opacity: 0; + + -webkit-transition: all 0.3s ease-out; + -moz-transition: all 0.3s ease-out; + -ms-transition: all 0.3s ease-out; + -o-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; +} + +.introjs-fixParent { + z-index: auto !important; + opacity: 1.0 !important; +} + +.introjs-showElement { + z-index: 9999999 !important; +} + +.introjs-relativePosition { + position: relative; +} + +.introjs-helperLayer { + position: absolute; + z-index: 9999998; + background-color: #FFF; + background-color: rgba(255,255,255,.9); + border: 1px solid #777; + border: 3px solid rgba(211, 214, 209, 1); + border-radius: 0; + box-shadow: 0 5px 8px -3px rgba(0,0,0,.6); + -webkit-transition: all 0.3s ease-out; + -moz-transition: all 0.3s ease-out; + -ms-transition: all 0.3s ease-out; + -o-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; +} + +.introjs-helperNumberLayer { + position: absolute; + top: -16px; + left: -16px; + z-index: 9999999999 !important; + padding: 2px; + font-family: Arial, verdana, tahoma; + font-size: 13px; + font-weight: bold; + color: white; /* Old browsers */ /* Chrome10+,Safari5.1+ */ + background:#ff3019 ; + width: 20px; + height:20px; + text-align: center; + line-height: 20px; + border: 3px solid #DA4433; + border-radius: 10px 10px 0; /* IE6-9 */ /* IE10 text shadows */ +} + +.introjs-arrow { + border: 5px solid white; + content:''; + position: absolute; +} +.introjs-arrow.top { + top: -10px; + border-top-color:transparent; + border-right-color:transparent; + border-bottom-color: #ecf0f1; + border-left-color:transparent; +} +.introjs-arrow.right { + right: -10px; + top: 10px; + border-top-color:transparent; + border-right-color:transparent; + border-bottom-color:transparent; + border-left-color:#ecf0f1; +} +.introjs-arrow.bottom { + bottom: -10px; + border-top-color:#ecf0f1; + border-right-color:transparent; + border-bottom-color:transparent; + border-left-color:transparent; +} +.introjs-arrow.left { + left: -10px; + top: 10px; + border-top-color:transparent; + border-right-color: #ecf0f1; + border-bottom-color:transparent; + border-left-color:transparent; +} + +.introjs-tooltip { + position: absolute; + padding: 10px; + background-color: #ecf0f1; + min-width: 200px; + max-width: 300px; + /* border-radius: 3px; */ + box-shadow: 0 6px 7px -4px rgba(0,0,0,.4); + -webkit-transition: opacity 0.1s ease-out; + -moz-transition: opacity 0.1s ease-out; + -ms-transition: opacity 0.1s ease-out; + -o-transition: opacity 0.1s ease-out; + transition: opacity 0.1s ease-out; +} + +.introjs-tooltiptext { + margin-left: -10px; + + margin-right: -10px; + border-top: 1px solid #FFFFFF; + background: #FAFAFA; + color: #2c3e50; + padding: 5px 10px; + border-bottom: 1px solid #FFFFFF; +} + +.introjs-tooltipbuttons { + text-align: center; +} + +/* + Buttons style by http://nicolasgallagher.com/lab/css3-github-buttons/ + Changed by Afshin Mehrabani +*/ +.introjs-button { + position: relative; + overflow: visible; + display: inline-block; + padding: 0.5em 0.8em; + box-shadow: 0 2px 0px -0px #306588; + margin: 0; + outline: none; + background: #2980b9; + text-decoration: none; + font: 11px/normal sans-serif; + color: #fff !important; + white-space: nowrap; + cursor: pointer; + outline: none !important; + -webkit-background-clip: padding; + -moz-background-clip: padding; + -o-background-clip: padding-box; + /*background-clip: padding-box;*/ /* commented out due to Opera 11.10 bug */ + -webkit-border-radius: 0.2em; + -moz-border-radius: 0.2em; + border-radius: 0.2em; + /* IE hacks */ + zoom: 1; + *display: inline; + margin-top: 10px; +} + +.introjs-button:hover { + color: #fff; + background: #2671A2; + text-decoration: none; + box-shadow: 0 2px 0px -0px #235677; +} + +.introjs-button:focus, +.introjs-button:active { + background: #23587A; text-decoration: none; + /* bottom: -1px; */ + box-shadow: 0 2px 0px 0px #173B53; +} + +/* overrides extra padding on button elements in Firefox */ +.introjs-button::-moz-focus-inner { + padding: 0; + border: 0; +} + +.introjs-skipbutton { + margin-right: 5px; + color: #fff; + background: #e74c3c; + box-shadow: 0 2px 0px -0px #B91D0D; +} + +.introjs-skipbutton:hover { + background: #EB1540; box-shadow: 0 2px 0px -0px #B91D0D; + +} + +.introjs-skipbutton:active, .introjs-skipbutton:focus { + background: #C02312; + box-shadow: 0 1px 0px -0px #6F1309; + +} + +.introjs-prevbutton { + -webkit-border-radius: 0.2em 0 0 0.2em; + -moz-border-radius: 0.2em 0 0 0.2em; + border-radius: 0.2em 0 0 0.2em; + border-right: none; +} + +.introjs-nextbutton { + -webkit-border-radius: 0 0.2em 0.2em 0; + -moz-border-radius: 0 0.2em 0.2em 0; + border-radius: 0 0.2em 0.2em 0; +} + +.introjs-disabled, .introjs-disabled:hover, .introjs-disabled:focus { + color: #C2C2C2 !important; + border-color: #d4d4d4; + cursor: default; + box-shadow: 0 2px 0px -0px #CACED1; + background-color: #E6E6E6; + background-image: none; + text-decoration: none; +} + +.introjs-bullets { + text-align: center; +} +.introjs-bullets ul { + clear: both; + margin: 15px auto 0; + padding: 0; + display: inline-block; +} +.introjs-bullets ul li { + list-style: none; + float: left; + margin: 0 2px; +} +.introjs-bullets ul li a { + display: block; + width: 6px; + height: 6px; + background: #ccc; + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + text-decoration: none; +} +.introjs-bullets ul li a:hover { + background: #999; +} +.introjs-bullets ul li a.active { + background: #999; +} + +.introjs-progress { + background-color: #FAFAFA; +} +.introjs-progressbar { + background-color: #2980b9; +} + +.introjsFloatingElement { + position: absolute; + height: 0; + width: 0; + left: 50%; + top: 50%; +} diff --git a/web/static/css/introjs-royal.css b/nettacker/web/static/css/introjs-royal.css similarity index 95% rename from web/static/css/introjs-royal.css rename to nettacker/web/static/css/introjs-royal.css index a2281adcf..8a98cafb1 100644 --- a/web/static/css/introjs-royal.css +++ b/nettacker/web/static/css/introjs-royal.css @@ -1,285 +1,285 @@ -.introjs-overlay { - position: absolute; - z-index: 999999; - background: #525252; - opacity: 0; - - -webkit-transition: all 0.3s ease-out; - -moz-transition: all 0.3s ease-out; - -ms-transition: all 0.3s ease-out; - -o-transition: all 0.3s ease-out; - transition: all 0.3s ease-out; -} - -.introjs-fixParent { - z-index: auto !important; - opacity: 1.0 !important; -} - -.introjs-showElement { - z-index: 9999999 !important; -} - -.introjs-relativePosition { - position: relative; -} - -.introjs-helperLayer { - position: absolute; - z-index: 9999998; - background-color: #FFF; - background-color: rgba(255,255,255,.9); - border: 1px solid #777; - border: 3px solid rgba(255, 255, 255, 1); - border-radius: 0; - box-shadow: 0 8px 50px -10px rgba(0,0,0,.6); - -webkit-transition: all 0.3s ease-out; - -moz-transition: all 0.3s ease-out; - -ms-transition: all 0.3s ease-out; - -o-transition: all 0.3s ease-out; - transition: all 0.3s ease-out; -} - -.introjs-helperNumberLayer { - position: absolute; - top: -29px; - left: -29px; - z-index: 9999999999 !important; - padding: 3px; - font-family: Arial, verdana, tahoma; - font-size: 13px; - font-weight: bold; - color: #DA4433; /* Old browsers */ /* Chrome10+,Safari5.1+ */ - background: #FFFFFF; - width: 20px; - height:20px; - text-align: center; - line-height: 20px; - border: 3px solid #DA4433; - border-right: none; - border-bottom: none; /* IE6-9 */ /* IE10 text shadows */ - border-radius: 10px 0 0 0; -} - -.introjs-arrow { - border: 5px solid white; - content:''; - position: absolute; -} -.introjs-arrow.top { - top: -10px; - border-top-color:transparent; - border-right-color:transparent; - border-bottom-color: #ecf0f1; - border-left-color:transparent; - display: none !important; -} -.introjs-arrow.right { - right: -10px; - top: 10px; - border-top-color:transparent; - border-right-color:transparent; - border-bottom-color:transparent; - border-left-color:#ecf0f1; -} -.introjs-arrow.bottom { - bottom: -10px; - border-top-color:#ecf0f1; - border-right-color:transparent; - border-bottom-color:transparent; - border-left-color:transparent; -} -.introjs-arrow.left { - left: -10px; - top: 10px; - border-top-color:transparent; - border-right-color: #ecf0f1; - border-bottom-color:transparent; - border-left-color:transparent; -} - -.introjs-tooltip { - position: fixed; - padding: 10px 170px 30px 10px; - background-color: #ecf0f1; - min-width: 200px; - max-width: 300px; - /* border-radius: 3px; */ - border-top: 3px solid #236591; - box-shadow: 0 -6px 50px -4px rgba(0,0,0,.4); - -webkit-transition: opacity 0.1s ease-out; - -moz-transition: opacity 0.1s ease-out; - -ms-transition: opacity 0.1s ease-out; - -o-transition: opacity 0.1s ease-out; - transition: opacity 0.1s ease-out; - bottom: 0 !important; - left: 0 !Important; - top: initial !important; - right: 0 !Important; - max-width: initial; - width: auto !important; -} - -.introjs-tooltiptext { - margin-left: -10px; - - margin-right: -10px; - /* border-top: 1px solid #FFFFFF; */ - /* background: #FAFAFA; */ - color: #2c3e50; - padding: 5px 10px; - /* border-bottom: 1px solid #FFFFFF; */ -} - -.introjs-tooltipbuttons { - text-align: center; - position: absolute; - right: 10px; - top: 0; -} - -/* - Buttons style by http://nicolasgallagher.com/lab/css3-github-buttons/ - Changed by Afshin Mehrabani -*/ -.introjs-button { - position: relative; - overflow: visible; - display: inline-block; - padding: 0.5em 0.8em; - box-shadow: 0 2px 0px -0px #306588; - margin: 0; - outline: none; - background: #2980b9; - text-decoration: none; - font: 11px/normal sans-serif; - color: #fff !important; - white-space: nowrap; - cursor: pointer; - outline: none !important; - -webkit-background-clip: padding; - -moz-background-clip: padding; - -o-background-clip: padding-box; - /*background-clip: padding-box;*/ /* commented out due to Opera 11.10 bug */ - -webkit-border-radius: 0.2em; - -moz-border-radius: 0.2em; - border-radius: 0.2em; - /* IE hacks */ - zoom: 1; - *display: inline; - margin-top: 10px; -} - -.introjs-button:hover { - color: #fff; - background: #2671A2; - text-decoration: none; - box-shadow: 0 2px 0px -0px #235677; -} - -.introjs-button:focus, -.introjs-button:active { - background: #23587A; text-decoration: none; - /* bottom: -1px; */ - box-shadow: 0 2px 0px 0px #173B53; -} - -/* overrides extra padding on button elements in Firefox */ -.introjs-button::-moz-focus-inner { - padding: 0; - border: 0; -} - -.introjs-skipbutton { - margin-right: 5px; - color: #fff; - background: #e74c3c; - box-shadow: 0 2px 0px -0px #B91D0D; -} - -.introjs-skipbutton:hover { - background: #EB1540; box-shadow: 0 2px 0px -0px #B91D0D; - -} - -.introjs-skipbutton:active, .introjs-skipbutton:focus { - background: #C02312; - box-shadow: 0 1px 0px -0px #6F1309; - -} - -.introjs-prevbutton { - -webkit-border-radius: 0.2em 0 0 0.2em; - -moz-border-radius: 0.2em 0 0 0.2em; - border-radius: 0.2em 0 0 0.2em; - border-right: none; -} - -.introjs-nextbutton { - -webkit-border-radius: 0 0.2em 0.2em 0; - -moz-border-radius: 0 0.2em 0.2em 0; - border-radius: 0 0.2em 0.2em 0; -} - -.introjs-disabled, .introjs-disabled:hover, .introjs-disabled:focus { - color: #C2C2C2 !important; - border-color: #d4d4d4; - cursor: default; - box-shadow: 0 2px 0px -0px #CACED1; - background-color: #E6E6E6; - background-image: none; - text-decoration: none; -} - -.introjs-bullets { - text-align: center; - float: right; - position: absolute; - right: 10px; - bottom: 10px; -} -.introjs-bullets ul { - clear: both; - margin: 15px auto 0; - padding: 0; - display: inline-block; -} -.introjs-bullets ul li { - list-style: none; - float: left; - margin: 0 2px; -} -.introjs-bullets ul li a { - display: block; - width: 6px; - height: 6px; - background: #ccc; - border-radius: 10px; - -moz-border-radius: 10px; - -webkit-border-radius: 10px; - text-decoration: none; -} -.introjs-bullets ul li a:hover { - background: #999; -} -.introjs-bullets ul li a.active { - background: #999; -} - -.introjs-progress { - width: 20%; - position: absolute; - bottom: 10px; - background-color: #fff; -} -.introjs-progressbar { - background-color: #2980b9; -} - -.introjsFloatingElement { - position: absolute; - height: 0; - width: 0; - left: 50%; - top: 50%; -} +.introjs-overlay { + position: absolute; + z-index: 999999; + background: #525252; + opacity: 0; + + -webkit-transition: all 0.3s ease-out; + -moz-transition: all 0.3s ease-out; + -ms-transition: all 0.3s ease-out; + -o-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; +} + +.introjs-fixParent { + z-index: auto !important; + opacity: 1.0 !important; +} + +.introjs-showElement { + z-index: 9999999 !important; +} + +.introjs-relativePosition { + position: relative; +} + +.introjs-helperLayer { + position: absolute; + z-index: 9999998; + background-color: #FFF; + background-color: rgba(255,255,255,.9); + border: 1px solid #777; + border: 3px solid rgba(255, 255, 255, 1); + border-radius: 0; + box-shadow: 0 8px 50px -10px rgba(0,0,0,.6); + -webkit-transition: all 0.3s ease-out; + -moz-transition: all 0.3s ease-out; + -ms-transition: all 0.3s ease-out; + -o-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; +} + +.introjs-helperNumberLayer { + position: absolute; + top: -29px; + left: -29px; + z-index: 9999999999 !important; + padding: 3px; + font-family: Arial, verdana, tahoma; + font-size: 13px; + font-weight: bold; + color: #DA4433; /* Old browsers */ /* Chrome10+,Safari5.1+ */ + background: #FFFFFF; + width: 20px; + height:20px; + text-align: center; + line-height: 20px; + border: 3px solid #DA4433; + border-right: none; + border-bottom: none; /* IE6-9 */ /* IE10 text shadows */ + border-radius: 10px 0 0 0; +} + +.introjs-arrow { + border: 5px solid white; + content:''; + position: absolute; +} +.introjs-arrow.top { + top: -10px; + border-top-color:transparent; + border-right-color:transparent; + border-bottom-color: #ecf0f1; + border-left-color:transparent; + display: none !important; +} +.introjs-arrow.right { + right: -10px; + top: 10px; + border-top-color:transparent; + border-right-color:transparent; + border-bottom-color:transparent; + border-left-color:#ecf0f1; +} +.introjs-arrow.bottom { + bottom: -10px; + border-top-color:#ecf0f1; + border-right-color:transparent; + border-bottom-color:transparent; + border-left-color:transparent; +} +.introjs-arrow.left { + left: -10px; + top: 10px; + border-top-color:transparent; + border-right-color: #ecf0f1; + border-bottom-color:transparent; + border-left-color:transparent; +} + +.introjs-tooltip { + position: fixed; + padding: 10px 170px 30px 10px; + background-color: #ecf0f1; + min-width: 200px; + max-width: 300px; + /* border-radius: 3px; */ + border-top: 3px solid #236591; + box-shadow: 0 -6px 50px -4px rgba(0,0,0,.4); + -webkit-transition: opacity 0.1s ease-out; + -moz-transition: opacity 0.1s ease-out; + -ms-transition: opacity 0.1s ease-out; + -o-transition: opacity 0.1s ease-out; + transition: opacity 0.1s ease-out; + bottom: 0 !important; + left: 0 !Important; + top: initial !important; + right: 0 !Important; + max-width: initial; + width: auto !important; +} + +.introjs-tooltiptext { + margin-left: -10px; + + margin-right: -10px; + /* border-top: 1px solid #FFFFFF; */ + /* background: #FAFAFA; */ + color: #2c3e50; + padding: 5px 10px; + /* border-bottom: 1px solid #FFFFFF; */ +} + +.introjs-tooltipbuttons { + text-align: center; + position: absolute; + right: 10px; + top: 0; +} + +/* + Buttons style by http://nicolasgallagher.com/lab/css3-github-buttons/ + Changed by Afshin Mehrabani +*/ +.introjs-button { + position: relative; + overflow: visible; + display: inline-block; + padding: 0.5em 0.8em; + box-shadow: 0 2px 0px -0px #306588; + margin: 0; + outline: none; + background: #2980b9; + text-decoration: none; + font: 11px/normal sans-serif; + color: #fff !important; + white-space: nowrap; + cursor: pointer; + outline: none !important; + -webkit-background-clip: padding; + -moz-background-clip: padding; + -o-background-clip: padding-box; + /*background-clip: padding-box;*/ /* commented out due to Opera 11.10 bug */ + -webkit-border-radius: 0.2em; + -moz-border-radius: 0.2em; + border-radius: 0.2em; + /* IE hacks */ + zoom: 1; + *display: inline; + margin-top: 10px; +} + +.introjs-button:hover { + color: #fff; + background: #2671A2; + text-decoration: none; + box-shadow: 0 2px 0px -0px #235677; +} + +.introjs-button:focus, +.introjs-button:active { + background: #23587A; text-decoration: none; + /* bottom: -1px; */ + box-shadow: 0 2px 0px 0px #173B53; +} + +/* overrides extra padding on button elements in Firefox */ +.introjs-button::-moz-focus-inner { + padding: 0; + border: 0; +} + +.introjs-skipbutton { + margin-right: 5px; + color: #fff; + background: #e74c3c; + box-shadow: 0 2px 0px -0px #B91D0D; +} + +.introjs-skipbutton:hover { + background: #EB1540; box-shadow: 0 2px 0px -0px #B91D0D; + +} + +.introjs-skipbutton:active, .introjs-skipbutton:focus { + background: #C02312; + box-shadow: 0 1px 0px -0px #6F1309; + +} + +.introjs-prevbutton { + -webkit-border-radius: 0.2em 0 0 0.2em; + -moz-border-radius: 0.2em 0 0 0.2em; + border-radius: 0.2em 0 0 0.2em; + border-right: none; +} + +.introjs-nextbutton { + -webkit-border-radius: 0 0.2em 0.2em 0; + -moz-border-radius: 0 0.2em 0.2em 0; + border-radius: 0 0.2em 0.2em 0; +} + +.introjs-disabled, .introjs-disabled:hover, .introjs-disabled:focus { + color: #C2C2C2 !important; + border-color: #d4d4d4; + cursor: default; + box-shadow: 0 2px 0px -0px #CACED1; + background-color: #E6E6E6; + background-image: none; + text-decoration: none; +} + +.introjs-bullets { + text-align: center; + float: right; + position: absolute; + right: 10px; + bottom: 10px; +} +.introjs-bullets ul { + clear: both; + margin: 15px auto 0; + padding: 0; + display: inline-block; +} +.introjs-bullets ul li { + list-style: none; + float: left; + margin: 0 2px; +} +.introjs-bullets ul li a { + display: block; + width: 6px; + height: 6px; + background: #ccc; + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + text-decoration: none; +} +.introjs-bullets ul li a:hover { + background: #999; +} +.introjs-bullets ul li a.active { + background: #999; +} + +.introjs-progress { + width: 20%; + position: absolute; + bottom: 10px; + background-color: #fff; +} +.introjs-progressbar { + background-color: #2980b9; +} + +.introjsFloatingElement { + position: absolute; + height: 0; + width: 0; + left: 50%; + top: 50%; +} diff --git a/web/static/css/introjs-rtl.min.css b/nettacker/web/static/css/introjs-rtl.min.css similarity index 100% rename from web/static/css/introjs-rtl.min.css rename to nettacker/web/static/css/introjs-rtl.min.css diff --git a/web/static/css/introjs.min.css b/nettacker/web/static/css/introjs.min.css similarity index 100% rename from web/static/css/introjs.min.css rename to nettacker/web/static/css/introjs.min.css diff --git a/web/static/css/style.css b/nettacker/web/static/css/style.css similarity index 100% rename from web/static/css/style.css rename to nettacker/web/static/css/style.css diff --git a/web/static/favicon.ico b/nettacker/web/static/favicon.ico similarity index 100% rename from web/static/favicon.ico rename to nettacker/web/static/favicon.ico diff --git a/web/static/fonts/fontawesome-webfont.ttf b/nettacker/web/static/fonts/fontawesome-webfont.ttf similarity index 100% rename from web/static/fonts/fontawesome-webfont.ttf rename to nettacker/web/static/fonts/fontawesome-webfont.ttf diff --git a/web/static/fonts/fontawesome-webfont.woff b/nettacker/web/static/fonts/fontawesome-webfont.woff similarity index 100% rename from web/static/fonts/fontawesome-webfont.woff rename to nettacker/web/static/fonts/fontawesome-webfont.woff diff --git a/web/static/fonts/fontawesome-webfont.woff2 b/nettacker/web/static/fonts/fontawesome-webfont.woff2 similarity index 100% rename from web/static/fonts/fontawesome-webfont.woff2 rename to nettacker/web/static/fonts/fontawesome-webfont.woff2 diff --git a/web/static/fonts/fontawesome.woff b/nettacker/web/static/fonts/fontawesome.woff similarity index 100% rename from web/static/fonts/fontawesome.woff rename to nettacker/web/static/fonts/fontawesome.woff diff --git a/web/static/fonts/glyphicons-halflings-regular.woff2 b/nettacker/web/static/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from web/static/fonts/glyphicons-halflings-regular.woff2 rename to nettacker/web/static/fonts/glyphicons-halflings-regular.woff2 diff --git a/web/static/img/background.jpeg b/nettacker/web/static/img/background.jpeg similarity index 100% rename from web/static/img/background.jpeg rename to nettacker/web/static/img/background.jpeg diff --git a/web/static/img/flags/1x1/ad.svg b/nettacker/web/static/img/flags/1x1/ad.svg similarity index 100% rename from web/static/img/flags/1x1/ad.svg rename to nettacker/web/static/img/flags/1x1/ad.svg diff --git a/web/static/img/flags/1x1/ae.svg b/nettacker/web/static/img/flags/1x1/ae.svg similarity index 100% rename from web/static/img/flags/1x1/ae.svg rename to nettacker/web/static/img/flags/1x1/ae.svg diff --git a/web/static/img/flags/1x1/af.svg b/nettacker/web/static/img/flags/1x1/af.svg similarity index 100% rename from web/static/img/flags/1x1/af.svg rename to nettacker/web/static/img/flags/1x1/af.svg diff --git a/web/static/img/flags/1x1/ag.svg b/nettacker/web/static/img/flags/1x1/ag.svg similarity index 100% rename from web/static/img/flags/1x1/ag.svg rename to nettacker/web/static/img/flags/1x1/ag.svg diff --git a/web/static/img/flags/1x1/ai.svg b/nettacker/web/static/img/flags/1x1/ai.svg similarity index 100% rename from web/static/img/flags/1x1/ai.svg rename to nettacker/web/static/img/flags/1x1/ai.svg diff --git a/web/static/img/flags/1x1/al.svg b/nettacker/web/static/img/flags/1x1/al.svg similarity index 100% rename from web/static/img/flags/1x1/al.svg rename to nettacker/web/static/img/flags/1x1/al.svg diff --git a/web/static/img/flags/1x1/am.svg b/nettacker/web/static/img/flags/1x1/am.svg similarity index 100% rename from web/static/img/flags/1x1/am.svg rename to nettacker/web/static/img/flags/1x1/am.svg diff --git a/web/static/img/flags/1x1/ao.svg b/nettacker/web/static/img/flags/1x1/ao.svg similarity index 100% rename from web/static/img/flags/1x1/ao.svg rename to nettacker/web/static/img/flags/1x1/ao.svg diff --git a/web/static/img/flags/1x1/aq.svg b/nettacker/web/static/img/flags/1x1/aq.svg similarity index 100% rename from web/static/img/flags/1x1/aq.svg rename to nettacker/web/static/img/flags/1x1/aq.svg diff --git a/web/static/img/flags/1x1/ar.svg b/nettacker/web/static/img/flags/1x1/ar.svg similarity index 100% rename from web/static/img/flags/1x1/ar.svg rename to nettacker/web/static/img/flags/1x1/ar.svg diff --git a/web/static/img/flags/1x1/as.svg b/nettacker/web/static/img/flags/1x1/as.svg similarity index 100% rename from web/static/img/flags/1x1/as.svg rename to nettacker/web/static/img/flags/1x1/as.svg diff --git a/web/static/img/flags/1x1/at.svg b/nettacker/web/static/img/flags/1x1/at.svg similarity index 100% rename from web/static/img/flags/1x1/at.svg rename to nettacker/web/static/img/flags/1x1/at.svg diff --git a/web/static/img/flags/1x1/au.svg b/nettacker/web/static/img/flags/1x1/au.svg similarity index 100% rename from web/static/img/flags/1x1/au.svg rename to nettacker/web/static/img/flags/1x1/au.svg diff --git a/web/static/img/flags/1x1/aw.svg b/nettacker/web/static/img/flags/1x1/aw.svg similarity index 100% rename from web/static/img/flags/1x1/aw.svg rename to nettacker/web/static/img/flags/1x1/aw.svg diff --git a/web/static/img/flags/1x1/ax.svg b/nettacker/web/static/img/flags/1x1/ax.svg similarity index 100% rename from web/static/img/flags/1x1/ax.svg rename to nettacker/web/static/img/flags/1x1/ax.svg diff --git a/web/static/img/flags/1x1/az.svg b/nettacker/web/static/img/flags/1x1/az.svg similarity index 100% rename from web/static/img/flags/1x1/az.svg rename to nettacker/web/static/img/flags/1x1/az.svg diff --git a/web/static/img/flags/1x1/ba.svg b/nettacker/web/static/img/flags/1x1/ba.svg similarity index 100% rename from web/static/img/flags/1x1/ba.svg rename to nettacker/web/static/img/flags/1x1/ba.svg diff --git a/web/static/img/flags/1x1/bb.svg b/nettacker/web/static/img/flags/1x1/bb.svg similarity index 100% rename from web/static/img/flags/1x1/bb.svg rename to nettacker/web/static/img/flags/1x1/bb.svg diff --git a/web/static/img/flags/1x1/bd.svg b/nettacker/web/static/img/flags/1x1/bd.svg similarity index 100% rename from web/static/img/flags/1x1/bd.svg rename to nettacker/web/static/img/flags/1x1/bd.svg diff --git a/web/static/img/flags/1x1/be.svg b/nettacker/web/static/img/flags/1x1/be.svg similarity index 100% rename from web/static/img/flags/1x1/be.svg rename to nettacker/web/static/img/flags/1x1/be.svg diff --git a/web/static/img/flags/1x1/bf.svg b/nettacker/web/static/img/flags/1x1/bf.svg similarity index 100% rename from web/static/img/flags/1x1/bf.svg rename to nettacker/web/static/img/flags/1x1/bf.svg diff --git a/web/static/img/flags/1x1/bg.svg b/nettacker/web/static/img/flags/1x1/bg.svg similarity index 100% rename from web/static/img/flags/1x1/bg.svg rename to nettacker/web/static/img/flags/1x1/bg.svg diff --git a/web/static/img/flags/1x1/bh.svg b/nettacker/web/static/img/flags/1x1/bh.svg similarity index 100% rename from web/static/img/flags/1x1/bh.svg rename to nettacker/web/static/img/flags/1x1/bh.svg diff --git a/web/static/img/flags/1x1/bi.svg b/nettacker/web/static/img/flags/1x1/bi.svg similarity index 100% rename from web/static/img/flags/1x1/bi.svg rename to nettacker/web/static/img/flags/1x1/bi.svg diff --git a/web/static/img/flags/1x1/bj.svg b/nettacker/web/static/img/flags/1x1/bj.svg similarity index 100% rename from web/static/img/flags/1x1/bj.svg rename to nettacker/web/static/img/flags/1x1/bj.svg diff --git a/web/static/img/flags/1x1/bl.svg b/nettacker/web/static/img/flags/1x1/bl.svg similarity index 100% rename from web/static/img/flags/1x1/bl.svg rename to nettacker/web/static/img/flags/1x1/bl.svg diff --git a/web/static/img/flags/1x1/bm.svg b/nettacker/web/static/img/flags/1x1/bm.svg similarity index 100% rename from web/static/img/flags/1x1/bm.svg rename to nettacker/web/static/img/flags/1x1/bm.svg diff --git a/web/static/img/flags/1x1/bn.svg b/nettacker/web/static/img/flags/1x1/bn.svg similarity index 100% rename from web/static/img/flags/1x1/bn.svg rename to nettacker/web/static/img/flags/1x1/bn.svg diff --git a/web/static/img/flags/1x1/bo.svg b/nettacker/web/static/img/flags/1x1/bo.svg similarity index 100% rename from web/static/img/flags/1x1/bo.svg rename to nettacker/web/static/img/flags/1x1/bo.svg diff --git a/web/static/img/flags/1x1/bq.svg b/nettacker/web/static/img/flags/1x1/bq.svg similarity index 100% rename from web/static/img/flags/1x1/bq.svg rename to nettacker/web/static/img/flags/1x1/bq.svg diff --git a/web/static/img/flags/1x1/br.svg b/nettacker/web/static/img/flags/1x1/br.svg similarity index 100% rename from web/static/img/flags/1x1/br.svg rename to nettacker/web/static/img/flags/1x1/br.svg diff --git a/web/static/img/flags/1x1/bs.svg b/nettacker/web/static/img/flags/1x1/bs.svg similarity index 100% rename from web/static/img/flags/1x1/bs.svg rename to nettacker/web/static/img/flags/1x1/bs.svg diff --git a/web/static/img/flags/1x1/bt.svg b/nettacker/web/static/img/flags/1x1/bt.svg similarity index 100% rename from web/static/img/flags/1x1/bt.svg rename to nettacker/web/static/img/flags/1x1/bt.svg diff --git a/web/static/img/flags/1x1/bv.svg b/nettacker/web/static/img/flags/1x1/bv.svg similarity index 100% rename from web/static/img/flags/1x1/bv.svg rename to nettacker/web/static/img/flags/1x1/bv.svg diff --git a/web/static/img/flags/1x1/bw.svg b/nettacker/web/static/img/flags/1x1/bw.svg similarity index 100% rename from web/static/img/flags/1x1/bw.svg rename to nettacker/web/static/img/flags/1x1/bw.svg diff --git a/web/static/img/flags/1x1/by.svg b/nettacker/web/static/img/flags/1x1/by.svg similarity index 100% rename from web/static/img/flags/1x1/by.svg rename to nettacker/web/static/img/flags/1x1/by.svg diff --git a/web/static/img/flags/1x1/bz.svg b/nettacker/web/static/img/flags/1x1/bz.svg similarity index 100% rename from web/static/img/flags/1x1/bz.svg rename to nettacker/web/static/img/flags/1x1/bz.svg diff --git a/web/static/img/flags/1x1/ca.svg b/nettacker/web/static/img/flags/1x1/ca.svg similarity index 100% rename from web/static/img/flags/1x1/ca.svg rename to nettacker/web/static/img/flags/1x1/ca.svg diff --git a/web/static/img/flags/1x1/cc.svg b/nettacker/web/static/img/flags/1x1/cc.svg similarity index 100% rename from web/static/img/flags/1x1/cc.svg rename to nettacker/web/static/img/flags/1x1/cc.svg diff --git a/web/static/img/flags/1x1/cd.svg b/nettacker/web/static/img/flags/1x1/cd.svg similarity index 100% rename from web/static/img/flags/1x1/cd.svg rename to nettacker/web/static/img/flags/1x1/cd.svg diff --git a/web/static/img/flags/1x1/cf.svg b/nettacker/web/static/img/flags/1x1/cf.svg similarity index 100% rename from web/static/img/flags/1x1/cf.svg rename to nettacker/web/static/img/flags/1x1/cf.svg diff --git a/web/static/img/flags/1x1/cg.svg b/nettacker/web/static/img/flags/1x1/cg.svg similarity index 100% rename from web/static/img/flags/1x1/cg.svg rename to nettacker/web/static/img/flags/1x1/cg.svg diff --git a/web/static/img/flags/1x1/ch.svg b/nettacker/web/static/img/flags/1x1/ch.svg similarity index 100% rename from web/static/img/flags/1x1/ch.svg rename to nettacker/web/static/img/flags/1x1/ch.svg diff --git a/web/static/img/flags/1x1/ci.svg b/nettacker/web/static/img/flags/1x1/ci.svg similarity index 100% rename from web/static/img/flags/1x1/ci.svg rename to nettacker/web/static/img/flags/1x1/ci.svg diff --git a/web/static/img/flags/1x1/ck.svg b/nettacker/web/static/img/flags/1x1/ck.svg similarity index 100% rename from web/static/img/flags/1x1/ck.svg rename to nettacker/web/static/img/flags/1x1/ck.svg diff --git a/web/static/img/flags/1x1/cl.svg b/nettacker/web/static/img/flags/1x1/cl.svg similarity index 100% rename from web/static/img/flags/1x1/cl.svg rename to nettacker/web/static/img/flags/1x1/cl.svg diff --git a/web/static/img/flags/1x1/cm.svg b/nettacker/web/static/img/flags/1x1/cm.svg similarity index 100% rename from web/static/img/flags/1x1/cm.svg rename to nettacker/web/static/img/flags/1x1/cm.svg diff --git a/web/static/img/flags/1x1/cn.svg b/nettacker/web/static/img/flags/1x1/cn.svg similarity index 100% rename from web/static/img/flags/1x1/cn.svg rename to nettacker/web/static/img/flags/1x1/cn.svg diff --git a/web/static/img/flags/1x1/co.svg b/nettacker/web/static/img/flags/1x1/co.svg similarity index 100% rename from web/static/img/flags/1x1/co.svg rename to nettacker/web/static/img/flags/1x1/co.svg diff --git a/web/static/img/flags/1x1/cr.svg b/nettacker/web/static/img/flags/1x1/cr.svg similarity index 100% rename from web/static/img/flags/1x1/cr.svg rename to nettacker/web/static/img/flags/1x1/cr.svg diff --git a/web/static/img/flags/1x1/cu.svg b/nettacker/web/static/img/flags/1x1/cu.svg similarity index 100% rename from web/static/img/flags/1x1/cu.svg rename to nettacker/web/static/img/flags/1x1/cu.svg diff --git a/web/static/img/flags/1x1/cv.svg b/nettacker/web/static/img/flags/1x1/cv.svg similarity index 100% rename from web/static/img/flags/1x1/cv.svg rename to nettacker/web/static/img/flags/1x1/cv.svg diff --git a/web/static/img/flags/1x1/cw.svg b/nettacker/web/static/img/flags/1x1/cw.svg similarity index 100% rename from web/static/img/flags/1x1/cw.svg rename to nettacker/web/static/img/flags/1x1/cw.svg diff --git a/web/static/img/flags/1x1/cx.svg b/nettacker/web/static/img/flags/1x1/cx.svg similarity index 100% rename from web/static/img/flags/1x1/cx.svg rename to nettacker/web/static/img/flags/1x1/cx.svg diff --git a/web/static/img/flags/1x1/cy.svg b/nettacker/web/static/img/flags/1x1/cy.svg similarity index 100% rename from web/static/img/flags/1x1/cy.svg rename to nettacker/web/static/img/flags/1x1/cy.svg diff --git a/web/static/img/flags/1x1/cz.svg b/nettacker/web/static/img/flags/1x1/cz.svg similarity index 100% rename from web/static/img/flags/1x1/cz.svg rename to nettacker/web/static/img/flags/1x1/cz.svg diff --git a/web/static/img/flags/1x1/de.svg b/nettacker/web/static/img/flags/1x1/de.svg similarity index 100% rename from web/static/img/flags/1x1/de.svg rename to nettacker/web/static/img/flags/1x1/de.svg diff --git a/web/static/img/flags/1x1/dj.svg b/nettacker/web/static/img/flags/1x1/dj.svg similarity index 100% rename from web/static/img/flags/1x1/dj.svg rename to nettacker/web/static/img/flags/1x1/dj.svg diff --git a/web/static/img/flags/1x1/dk.svg b/nettacker/web/static/img/flags/1x1/dk.svg similarity index 100% rename from web/static/img/flags/1x1/dk.svg rename to nettacker/web/static/img/flags/1x1/dk.svg diff --git a/web/static/img/flags/1x1/dm.svg b/nettacker/web/static/img/flags/1x1/dm.svg similarity index 100% rename from web/static/img/flags/1x1/dm.svg rename to nettacker/web/static/img/flags/1x1/dm.svg diff --git a/web/static/img/flags/1x1/do.svg b/nettacker/web/static/img/flags/1x1/do.svg similarity index 100% rename from web/static/img/flags/1x1/do.svg rename to nettacker/web/static/img/flags/1x1/do.svg diff --git a/web/static/img/flags/1x1/dz.svg b/nettacker/web/static/img/flags/1x1/dz.svg similarity index 100% rename from web/static/img/flags/1x1/dz.svg rename to nettacker/web/static/img/flags/1x1/dz.svg diff --git a/web/static/img/flags/1x1/ec.svg b/nettacker/web/static/img/flags/1x1/ec.svg similarity index 100% rename from web/static/img/flags/1x1/ec.svg rename to nettacker/web/static/img/flags/1x1/ec.svg diff --git a/web/static/img/flags/1x1/ee.svg b/nettacker/web/static/img/flags/1x1/ee.svg similarity index 100% rename from web/static/img/flags/1x1/ee.svg rename to nettacker/web/static/img/flags/1x1/ee.svg diff --git a/web/static/img/flags/1x1/eg.svg b/nettacker/web/static/img/flags/1x1/eg.svg similarity index 100% rename from web/static/img/flags/1x1/eg.svg rename to nettacker/web/static/img/flags/1x1/eg.svg diff --git a/web/static/img/flags/1x1/eh.svg b/nettacker/web/static/img/flags/1x1/eh.svg similarity index 100% rename from web/static/img/flags/1x1/eh.svg rename to nettacker/web/static/img/flags/1x1/eh.svg diff --git a/web/static/img/flags/1x1/er.svg b/nettacker/web/static/img/flags/1x1/er.svg similarity index 100% rename from web/static/img/flags/1x1/er.svg rename to nettacker/web/static/img/flags/1x1/er.svg diff --git a/web/static/img/flags/1x1/es-ct.svg b/nettacker/web/static/img/flags/1x1/es-ct.svg similarity index 100% rename from web/static/img/flags/1x1/es-ct.svg rename to nettacker/web/static/img/flags/1x1/es-ct.svg diff --git a/web/static/img/flags/1x1/es.svg b/nettacker/web/static/img/flags/1x1/es.svg similarity index 100% rename from web/static/img/flags/1x1/es.svg rename to nettacker/web/static/img/flags/1x1/es.svg diff --git a/web/static/img/flags/1x1/et.svg b/nettacker/web/static/img/flags/1x1/et.svg similarity index 100% rename from web/static/img/flags/1x1/et.svg rename to nettacker/web/static/img/flags/1x1/et.svg diff --git a/web/static/img/flags/1x1/eu.svg b/nettacker/web/static/img/flags/1x1/eu.svg similarity index 100% rename from web/static/img/flags/1x1/eu.svg rename to nettacker/web/static/img/flags/1x1/eu.svg diff --git a/web/static/img/flags/1x1/fi.svg b/nettacker/web/static/img/flags/1x1/fi.svg similarity index 100% rename from web/static/img/flags/1x1/fi.svg rename to nettacker/web/static/img/flags/1x1/fi.svg diff --git a/web/static/img/flags/1x1/fj.svg b/nettacker/web/static/img/flags/1x1/fj.svg similarity index 100% rename from web/static/img/flags/1x1/fj.svg rename to nettacker/web/static/img/flags/1x1/fj.svg diff --git a/web/static/img/flags/1x1/fk.svg b/nettacker/web/static/img/flags/1x1/fk.svg similarity index 100% rename from web/static/img/flags/1x1/fk.svg rename to nettacker/web/static/img/flags/1x1/fk.svg diff --git a/web/static/img/flags/1x1/fm.svg b/nettacker/web/static/img/flags/1x1/fm.svg similarity index 100% rename from web/static/img/flags/1x1/fm.svg rename to nettacker/web/static/img/flags/1x1/fm.svg diff --git a/web/static/img/flags/1x1/fo.svg b/nettacker/web/static/img/flags/1x1/fo.svg similarity index 100% rename from web/static/img/flags/1x1/fo.svg rename to nettacker/web/static/img/flags/1x1/fo.svg diff --git a/web/static/img/flags/1x1/fr.svg b/nettacker/web/static/img/flags/1x1/fr.svg similarity index 100% rename from web/static/img/flags/1x1/fr.svg rename to nettacker/web/static/img/flags/1x1/fr.svg diff --git a/web/static/img/flags/1x1/ga.svg b/nettacker/web/static/img/flags/1x1/ga.svg similarity index 100% rename from web/static/img/flags/1x1/ga.svg rename to nettacker/web/static/img/flags/1x1/ga.svg diff --git a/web/static/img/flags/1x1/gb-eng.svg b/nettacker/web/static/img/flags/1x1/gb-eng.svg similarity index 100% rename from web/static/img/flags/1x1/gb-eng.svg rename to nettacker/web/static/img/flags/1x1/gb-eng.svg diff --git a/web/static/img/flags/1x1/gb-nir.svg b/nettacker/web/static/img/flags/1x1/gb-nir.svg similarity index 100% rename from web/static/img/flags/1x1/gb-nir.svg rename to nettacker/web/static/img/flags/1x1/gb-nir.svg diff --git a/web/static/img/flags/1x1/gb-sct.svg b/nettacker/web/static/img/flags/1x1/gb-sct.svg similarity index 100% rename from web/static/img/flags/1x1/gb-sct.svg rename to nettacker/web/static/img/flags/1x1/gb-sct.svg diff --git a/web/static/img/flags/1x1/gb-wls.svg b/nettacker/web/static/img/flags/1x1/gb-wls.svg similarity index 100% rename from web/static/img/flags/1x1/gb-wls.svg rename to nettacker/web/static/img/flags/1x1/gb-wls.svg diff --git a/web/static/img/flags/1x1/gb.svg b/nettacker/web/static/img/flags/1x1/gb.svg similarity index 100% rename from web/static/img/flags/1x1/gb.svg rename to nettacker/web/static/img/flags/1x1/gb.svg diff --git a/web/static/img/flags/1x1/gd.svg b/nettacker/web/static/img/flags/1x1/gd.svg similarity index 100% rename from web/static/img/flags/1x1/gd.svg rename to nettacker/web/static/img/flags/1x1/gd.svg diff --git a/web/static/img/flags/1x1/ge.svg b/nettacker/web/static/img/flags/1x1/ge.svg similarity index 100% rename from web/static/img/flags/1x1/ge.svg rename to nettacker/web/static/img/flags/1x1/ge.svg diff --git a/web/static/img/flags/1x1/gf.svg b/nettacker/web/static/img/flags/1x1/gf.svg similarity index 100% rename from web/static/img/flags/1x1/gf.svg rename to nettacker/web/static/img/flags/1x1/gf.svg diff --git a/web/static/img/flags/1x1/gg.svg b/nettacker/web/static/img/flags/1x1/gg.svg similarity index 100% rename from web/static/img/flags/1x1/gg.svg rename to nettacker/web/static/img/flags/1x1/gg.svg diff --git a/web/static/img/flags/1x1/gh.svg b/nettacker/web/static/img/flags/1x1/gh.svg similarity index 100% rename from web/static/img/flags/1x1/gh.svg rename to nettacker/web/static/img/flags/1x1/gh.svg diff --git a/web/static/img/flags/1x1/gi.svg b/nettacker/web/static/img/flags/1x1/gi.svg similarity index 100% rename from web/static/img/flags/1x1/gi.svg rename to nettacker/web/static/img/flags/1x1/gi.svg diff --git a/web/static/img/flags/1x1/gl.svg b/nettacker/web/static/img/flags/1x1/gl.svg similarity index 100% rename from web/static/img/flags/1x1/gl.svg rename to nettacker/web/static/img/flags/1x1/gl.svg diff --git a/web/static/img/flags/1x1/gm.svg b/nettacker/web/static/img/flags/1x1/gm.svg similarity index 100% rename from web/static/img/flags/1x1/gm.svg rename to nettacker/web/static/img/flags/1x1/gm.svg diff --git a/web/static/img/flags/1x1/gn.svg b/nettacker/web/static/img/flags/1x1/gn.svg similarity index 100% rename from web/static/img/flags/1x1/gn.svg rename to nettacker/web/static/img/flags/1x1/gn.svg diff --git a/web/static/img/flags/1x1/gp.svg b/nettacker/web/static/img/flags/1x1/gp.svg similarity index 100% rename from web/static/img/flags/1x1/gp.svg rename to nettacker/web/static/img/flags/1x1/gp.svg diff --git a/web/static/img/flags/1x1/gq.svg b/nettacker/web/static/img/flags/1x1/gq.svg similarity index 100% rename from web/static/img/flags/1x1/gq.svg rename to nettacker/web/static/img/flags/1x1/gq.svg diff --git a/web/static/img/flags/1x1/gr.svg b/nettacker/web/static/img/flags/1x1/gr.svg similarity index 100% rename from web/static/img/flags/1x1/gr.svg rename to nettacker/web/static/img/flags/1x1/gr.svg diff --git a/web/static/img/flags/1x1/gs.svg b/nettacker/web/static/img/flags/1x1/gs.svg similarity index 100% rename from web/static/img/flags/1x1/gs.svg rename to nettacker/web/static/img/flags/1x1/gs.svg diff --git a/web/static/img/flags/1x1/gt.svg b/nettacker/web/static/img/flags/1x1/gt.svg similarity index 100% rename from web/static/img/flags/1x1/gt.svg rename to nettacker/web/static/img/flags/1x1/gt.svg diff --git a/web/static/img/flags/1x1/gu.svg b/nettacker/web/static/img/flags/1x1/gu.svg similarity index 100% rename from web/static/img/flags/1x1/gu.svg rename to nettacker/web/static/img/flags/1x1/gu.svg diff --git a/web/static/img/flags/1x1/gw.svg b/nettacker/web/static/img/flags/1x1/gw.svg similarity index 100% rename from web/static/img/flags/1x1/gw.svg rename to nettacker/web/static/img/flags/1x1/gw.svg diff --git a/web/static/img/flags/1x1/gy.svg b/nettacker/web/static/img/flags/1x1/gy.svg similarity index 100% rename from web/static/img/flags/1x1/gy.svg rename to nettacker/web/static/img/flags/1x1/gy.svg diff --git a/web/static/img/flags/1x1/hk.svg b/nettacker/web/static/img/flags/1x1/hk.svg similarity index 100% rename from web/static/img/flags/1x1/hk.svg rename to nettacker/web/static/img/flags/1x1/hk.svg diff --git a/web/static/img/flags/1x1/hm.svg b/nettacker/web/static/img/flags/1x1/hm.svg similarity index 100% rename from web/static/img/flags/1x1/hm.svg rename to nettacker/web/static/img/flags/1x1/hm.svg diff --git a/web/static/img/flags/1x1/hn.svg b/nettacker/web/static/img/flags/1x1/hn.svg similarity index 100% rename from web/static/img/flags/1x1/hn.svg rename to nettacker/web/static/img/flags/1x1/hn.svg diff --git a/web/static/img/flags/1x1/hr.svg b/nettacker/web/static/img/flags/1x1/hr.svg similarity index 100% rename from web/static/img/flags/1x1/hr.svg rename to nettacker/web/static/img/flags/1x1/hr.svg diff --git a/web/static/img/flags/1x1/ht.svg b/nettacker/web/static/img/flags/1x1/ht.svg similarity index 100% rename from web/static/img/flags/1x1/ht.svg rename to nettacker/web/static/img/flags/1x1/ht.svg diff --git a/web/static/img/flags/1x1/hu.svg b/nettacker/web/static/img/flags/1x1/hu.svg similarity index 100% rename from web/static/img/flags/1x1/hu.svg rename to nettacker/web/static/img/flags/1x1/hu.svg diff --git a/web/static/img/flags/1x1/id.svg b/nettacker/web/static/img/flags/1x1/id.svg similarity index 100% rename from web/static/img/flags/1x1/id.svg rename to nettacker/web/static/img/flags/1x1/id.svg diff --git a/web/static/img/flags/1x1/ie.svg b/nettacker/web/static/img/flags/1x1/ie.svg similarity index 100% rename from web/static/img/flags/1x1/ie.svg rename to nettacker/web/static/img/flags/1x1/ie.svg diff --git a/web/static/img/flags/1x1/il.svg b/nettacker/web/static/img/flags/1x1/il.svg similarity index 100% rename from web/static/img/flags/1x1/il.svg rename to nettacker/web/static/img/flags/1x1/il.svg diff --git a/web/static/img/flags/1x1/im.svg b/nettacker/web/static/img/flags/1x1/im.svg similarity index 100% rename from web/static/img/flags/1x1/im.svg rename to nettacker/web/static/img/flags/1x1/im.svg diff --git a/web/static/img/flags/1x1/in.svg b/nettacker/web/static/img/flags/1x1/in.svg similarity index 100% rename from web/static/img/flags/1x1/in.svg rename to nettacker/web/static/img/flags/1x1/in.svg diff --git a/web/static/img/flags/1x1/io.svg b/nettacker/web/static/img/flags/1x1/io.svg similarity index 100% rename from web/static/img/flags/1x1/io.svg rename to nettacker/web/static/img/flags/1x1/io.svg diff --git a/web/static/img/flags/1x1/iq.svg b/nettacker/web/static/img/flags/1x1/iq.svg similarity index 100% rename from web/static/img/flags/1x1/iq.svg rename to nettacker/web/static/img/flags/1x1/iq.svg diff --git a/web/static/img/flags/1x1/ir.svg b/nettacker/web/static/img/flags/1x1/ir.svg similarity index 100% rename from web/static/img/flags/1x1/ir.svg rename to nettacker/web/static/img/flags/1x1/ir.svg diff --git a/web/static/img/flags/1x1/is.svg b/nettacker/web/static/img/flags/1x1/is.svg similarity index 100% rename from web/static/img/flags/1x1/is.svg rename to nettacker/web/static/img/flags/1x1/is.svg diff --git a/web/static/img/flags/1x1/it.svg b/nettacker/web/static/img/flags/1x1/it.svg similarity index 100% rename from web/static/img/flags/1x1/it.svg rename to nettacker/web/static/img/flags/1x1/it.svg diff --git a/web/static/img/flags/1x1/je.svg b/nettacker/web/static/img/flags/1x1/je.svg similarity index 100% rename from web/static/img/flags/1x1/je.svg rename to nettacker/web/static/img/flags/1x1/je.svg diff --git a/web/static/img/flags/1x1/jm.svg b/nettacker/web/static/img/flags/1x1/jm.svg similarity index 100% rename from web/static/img/flags/1x1/jm.svg rename to nettacker/web/static/img/flags/1x1/jm.svg diff --git a/web/static/img/flags/1x1/jo.svg b/nettacker/web/static/img/flags/1x1/jo.svg similarity index 100% rename from web/static/img/flags/1x1/jo.svg rename to nettacker/web/static/img/flags/1x1/jo.svg diff --git a/web/static/img/flags/1x1/jp.svg b/nettacker/web/static/img/flags/1x1/jp.svg similarity index 100% rename from web/static/img/flags/1x1/jp.svg rename to nettacker/web/static/img/flags/1x1/jp.svg diff --git a/web/static/img/flags/1x1/ke.svg b/nettacker/web/static/img/flags/1x1/ke.svg similarity index 100% rename from web/static/img/flags/1x1/ke.svg rename to nettacker/web/static/img/flags/1x1/ke.svg diff --git a/web/static/img/flags/1x1/kg.svg b/nettacker/web/static/img/flags/1x1/kg.svg similarity index 100% rename from web/static/img/flags/1x1/kg.svg rename to nettacker/web/static/img/flags/1x1/kg.svg diff --git a/web/static/img/flags/1x1/kh.svg b/nettacker/web/static/img/flags/1x1/kh.svg similarity index 100% rename from web/static/img/flags/1x1/kh.svg rename to nettacker/web/static/img/flags/1x1/kh.svg diff --git a/web/static/img/flags/1x1/ki.svg b/nettacker/web/static/img/flags/1x1/ki.svg similarity index 100% rename from web/static/img/flags/1x1/ki.svg rename to nettacker/web/static/img/flags/1x1/ki.svg diff --git a/web/static/img/flags/1x1/km.svg b/nettacker/web/static/img/flags/1x1/km.svg similarity index 100% rename from web/static/img/flags/1x1/km.svg rename to nettacker/web/static/img/flags/1x1/km.svg diff --git a/web/static/img/flags/1x1/kn.svg b/nettacker/web/static/img/flags/1x1/kn.svg similarity index 100% rename from web/static/img/flags/1x1/kn.svg rename to nettacker/web/static/img/flags/1x1/kn.svg diff --git a/web/static/img/flags/1x1/kp.svg b/nettacker/web/static/img/flags/1x1/kp.svg similarity index 100% rename from web/static/img/flags/1x1/kp.svg rename to nettacker/web/static/img/flags/1x1/kp.svg diff --git a/web/static/img/flags/1x1/kr.svg b/nettacker/web/static/img/flags/1x1/kr.svg similarity index 100% rename from web/static/img/flags/1x1/kr.svg rename to nettacker/web/static/img/flags/1x1/kr.svg diff --git a/web/static/img/flags/1x1/kw.svg b/nettacker/web/static/img/flags/1x1/kw.svg similarity index 100% rename from web/static/img/flags/1x1/kw.svg rename to nettacker/web/static/img/flags/1x1/kw.svg diff --git a/web/static/img/flags/1x1/ky.svg b/nettacker/web/static/img/flags/1x1/ky.svg similarity index 100% rename from web/static/img/flags/1x1/ky.svg rename to nettacker/web/static/img/flags/1x1/ky.svg diff --git a/web/static/img/flags/1x1/kz.svg b/nettacker/web/static/img/flags/1x1/kz.svg similarity index 100% rename from web/static/img/flags/1x1/kz.svg rename to nettacker/web/static/img/flags/1x1/kz.svg diff --git a/web/static/img/flags/1x1/la.svg b/nettacker/web/static/img/flags/1x1/la.svg similarity index 100% rename from web/static/img/flags/1x1/la.svg rename to nettacker/web/static/img/flags/1x1/la.svg diff --git a/web/static/img/flags/1x1/lb.svg b/nettacker/web/static/img/flags/1x1/lb.svg similarity index 100% rename from web/static/img/flags/1x1/lb.svg rename to nettacker/web/static/img/flags/1x1/lb.svg diff --git a/web/static/img/flags/1x1/lc.svg b/nettacker/web/static/img/flags/1x1/lc.svg similarity index 100% rename from web/static/img/flags/1x1/lc.svg rename to nettacker/web/static/img/flags/1x1/lc.svg diff --git a/web/static/img/flags/1x1/li.svg b/nettacker/web/static/img/flags/1x1/li.svg similarity index 100% rename from web/static/img/flags/1x1/li.svg rename to nettacker/web/static/img/flags/1x1/li.svg diff --git a/web/static/img/flags/1x1/lk.svg b/nettacker/web/static/img/flags/1x1/lk.svg similarity index 100% rename from web/static/img/flags/1x1/lk.svg rename to nettacker/web/static/img/flags/1x1/lk.svg diff --git a/web/static/img/flags/1x1/lr.svg b/nettacker/web/static/img/flags/1x1/lr.svg similarity index 100% rename from web/static/img/flags/1x1/lr.svg rename to nettacker/web/static/img/flags/1x1/lr.svg diff --git a/web/static/img/flags/1x1/ls.svg b/nettacker/web/static/img/flags/1x1/ls.svg similarity index 100% rename from web/static/img/flags/1x1/ls.svg rename to nettacker/web/static/img/flags/1x1/ls.svg diff --git a/web/static/img/flags/1x1/lt.svg b/nettacker/web/static/img/flags/1x1/lt.svg similarity index 100% rename from web/static/img/flags/1x1/lt.svg rename to nettacker/web/static/img/flags/1x1/lt.svg diff --git a/web/static/img/flags/1x1/lu.svg b/nettacker/web/static/img/flags/1x1/lu.svg similarity index 100% rename from web/static/img/flags/1x1/lu.svg rename to nettacker/web/static/img/flags/1x1/lu.svg diff --git a/web/static/img/flags/1x1/lv.svg b/nettacker/web/static/img/flags/1x1/lv.svg similarity index 100% rename from web/static/img/flags/1x1/lv.svg rename to nettacker/web/static/img/flags/1x1/lv.svg diff --git a/web/static/img/flags/1x1/ly.svg b/nettacker/web/static/img/flags/1x1/ly.svg similarity index 100% rename from web/static/img/flags/1x1/ly.svg rename to nettacker/web/static/img/flags/1x1/ly.svg diff --git a/web/static/img/flags/1x1/ma.svg b/nettacker/web/static/img/flags/1x1/ma.svg similarity index 100% rename from web/static/img/flags/1x1/ma.svg rename to nettacker/web/static/img/flags/1x1/ma.svg diff --git a/web/static/img/flags/1x1/mc.svg b/nettacker/web/static/img/flags/1x1/mc.svg similarity index 100% rename from web/static/img/flags/1x1/mc.svg rename to nettacker/web/static/img/flags/1x1/mc.svg diff --git a/web/static/img/flags/1x1/md.svg b/nettacker/web/static/img/flags/1x1/md.svg similarity index 100% rename from web/static/img/flags/1x1/md.svg rename to nettacker/web/static/img/flags/1x1/md.svg diff --git a/web/static/img/flags/1x1/me.svg b/nettacker/web/static/img/flags/1x1/me.svg similarity index 100% rename from web/static/img/flags/1x1/me.svg rename to nettacker/web/static/img/flags/1x1/me.svg diff --git a/web/static/img/flags/1x1/mf.svg b/nettacker/web/static/img/flags/1x1/mf.svg similarity index 100% rename from web/static/img/flags/1x1/mf.svg rename to nettacker/web/static/img/flags/1x1/mf.svg diff --git a/web/static/img/flags/1x1/mg.svg b/nettacker/web/static/img/flags/1x1/mg.svg similarity index 100% rename from web/static/img/flags/1x1/mg.svg rename to nettacker/web/static/img/flags/1x1/mg.svg diff --git a/web/static/img/flags/1x1/mh.svg b/nettacker/web/static/img/flags/1x1/mh.svg similarity index 100% rename from web/static/img/flags/1x1/mh.svg rename to nettacker/web/static/img/flags/1x1/mh.svg diff --git a/web/static/img/flags/1x1/mk.svg b/nettacker/web/static/img/flags/1x1/mk.svg similarity index 100% rename from web/static/img/flags/1x1/mk.svg rename to nettacker/web/static/img/flags/1x1/mk.svg diff --git a/web/static/img/flags/1x1/ml.svg b/nettacker/web/static/img/flags/1x1/ml.svg similarity index 100% rename from web/static/img/flags/1x1/ml.svg rename to nettacker/web/static/img/flags/1x1/ml.svg diff --git a/web/static/img/flags/1x1/mm.svg b/nettacker/web/static/img/flags/1x1/mm.svg similarity index 100% rename from web/static/img/flags/1x1/mm.svg rename to nettacker/web/static/img/flags/1x1/mm.svg diff --git a/web/static/img/flags/1x1/mn.svg b/nettacker/web/static/img/flags/1x1/mn.svg similarity index 100% rename from web/static/img/flags/1x1/mn.svg rename to nettacker/web/static/img/flags/1x1/mn.svg diff --git a/web/static/img/flags/1x1/mo.svg b/nettacker/web/static/img/flags/1x1/mo.svg similarity index 100% rename from web/static/img/flags/1x1/mo.svg rename to nettacker/web/static/img/flags/1x1/mo.svg diff --git a/web/static/img/flags/1x1/mp.svg b/nettacker/web/static/img/flags/1x1/mp.svg similarity index 100% rename from web/static/img/flags/1x1/mp.svg rename to nettacker/web/static/img/flags/1x1/mp.svg diff --git a/web/static/img/flags/1x1/mq.svg b/nettacker/web/static/img/flags/1x1/mq.svg similarity index 100% rename from web/static/img/flags/1x1/mq.svg rename to nettacker/web/static/img/flags/1x1/mq.svg diff --git a/web/static/img/flags/1x1/mr.svg b/nettacker/web/static/img/flags/1x1/mr.svg similarity index 100% rename from web/static/img/flags/1x1/mr.svg rename to nettacker/web/static/img/flags/1x1/mr.svg diff --git a/web/static/img/flags/1x1/ms.svg b/nettacker/web/static/img/flags/1x1/ms.svg similarity index 100% rename from web/static/img/flags/1x1/ms.svg rename to nettacker/web/static/img/flags/1x1/ms.svg diff --git a/web/static/img/flags/1x1/mt.svg b/nettacker/web/static/img/flags/1x1/mt.svg similarity index 100% rename from web/static/img/flags/1x1/mt.svg rename to nettacker/web/static/img/flags/1x1/mt.svg diff --git a/web/static/img/flags/1x1/mu.svg b/nettacker/web/static/img/flags/1x1/mu.svg similarity index 100% rename from web/static/img/flags/1x1/mu.svg rename to nettacker/web/static/img/flags/1x1/mu.svg diff --git a/web/static/img/flags/1x1/mv.svg b/nettacker/web/static/img/flags/1x1/mv.svg similarity index 100% rename from web/static/img/flags/1x1/mv.svg rename to nettacker/web/static/img/flags/1x1/mv.svg diff --git a/web/static/img/flags/1x1/mw.svg b/nettacker/web/static/img/flags/1x1/mw.svg similarity index 100% rename from web/static/img/flags/1x1/mw.svg rename to nettacker/web/static/img/flags/1x1/mw.svg diff --git a/web/static/img/flags/1x1/mx.svg b/nettacker/web/static/img/flags/1x1/mx.svg similarity index 100% rename from web/static/img/flags/1x1/mx.svg rename to nettacker/web/static/img/flags/1x1/mx.svg diff --git a/web/static/img/flags/1x1/my.svg b/nettacker/web/static/img/flags/1x1/my.svg similarity index 100% rename from web/static/img/flags/1x1/my.svg rename to nettacker/web/static/img/flags/1x1/my.svg diff --git a/web/static/img/flags/1x1/mz.svg b/nettacker/web/static/img/flags/1x1/mz.svg similarity index 100% rename from web/static/img/flags/1x1/mz.svg rename to nettacker/web/static/img/flags/1x1/mz.svg diff --git a/web/static/img/flags/1x1/na.svg b/nettacker/web/static/img/flags/1x1/na.svg similarity index 100% rename from web/static/img/flags/1x1/na.svg rename to nettacker/web/static/img/flags/1x1/na.svg diff --git a/web/static/img/flags/1x1/nc.svg b/nettacker/web/static/img/flags/1x1/nc.svg similarity index 100% rename from web/static/img/flags/1x1/nc.svg rename to nettacker/web/static/img/flags/1x1/nc.svg diff --git a/web/static/img/flags/1x1/ne.svg b/nettacker/web/static/img/flags/1x1/ne.svg similarity index 100% rename from web/static/img/flags/1x1/ne.svg rename to nettacker/web/static/img/flags/1x1/ne.svg diff --git a/web/static/img/flags/1x1/nf.svg b/nettacker/web/static/img/flags/1x1/nf.svg similarity index 100% rename from web/static/img/flags/1x1/nf.svg rename to nettacker/web/static/img/flags/1x1/nf.svg diff --git a/web/static/img/flags/1x1/ng.svg b/nettacker/web/static/img/flags/1x1/ng.svg similarity index 100% rename from web/static/img/flags/1x1/ng.svg rename to nettacker/web/static/img/flags/1x1/ng.svg diff --git a/web/static/img/flags/1x1/ni.svg b/nettacker/web/static/img/flags/1x1/ni.svg similarity index 100% rename from web/static/img/flags/1x1/ni.svg rename to nettacker/web/static/img/flags/1x1/ni.svg diff --git a/web/static/img/flags/1x1/nl.svg b/nettacker/web/static/img/flags/1x1/nl.svg similarity index 100% rename from web/static/img/flags/1x1/nl.svg rename to nettacker/web/static/img/flags/1x1/nl.svg diff --git a/web/static/img/flags/1x1/no.svg b/nettacker/web/static/img/flags/1x1/no.svg similarity index 100% rename from web/static/img/flags/1x1/no.svg rename to nettacker/web/static/img/flags/1x1/no.svg diff --git a/web/static/img/flags/1x1/np.svg b/nettacker/web/static/img/flags/1x1/np.svg similarity index 100% rename from web/static/img/flags/1x1/np.svg rename to nettacker/web/static/img/flags/1x1/np.svg diff --git a/web/static/img/flags/1x1/nr.svg b/nettacker/web/static/img/flags/1x1/nr.svg similarity index 100% rename from web/static/img/flags/1x1/nr.svg rename to nettacker/web/static/img/flags/1x1/nr.svg diff --git a/web/static/img/flags/1x1/nu.svg b/nettacker/web/static/img/flags/1x1/nu.svg similarity index 100% rename from web/static/img/flags/1x1/nu.svg rename to nettacker/web/static/img/flags/1x1/nu.svg diff --git a/web/static/img/flags/1x1/nz.svg b/nettacker/web/static/img/flags/1x1/nz.svg similarity index 100% rename from web/static/img/flags/1x1/nz.svg rename to nettacker/web/static/img/flags/1x1/nz.svg diff --git a/web/static/img/flags/1x1/om.svg b/nettacker/web/static/img/flags/1x1/om.svg similarity index 100% rename from web/static/img/flags/1x1/om.svg rename to nettacker/web/static/img/flags/1x1/om.svg diff --git a/web/static/img/flags/1x1/pa.svg b/nettacker/web/static/img/flags/1x1/pa.svg similarity index 100% rename from web/static/img/flags/1x1/pa.svg rename to nettacker/web/static/img/flags/1x1/pa.svg diff --git a/web/static/img/flags/1x1/pe.svg b/nettacker/web/static/img/flags/1x1/pe.svg similarity index 100% rename from web/static/img/flags/1x1/pe.svg rename to nettacker/web/static/img/flags/1x1/pe.svg diff --git a/web/static/img/flags/1x1/pf.svg b/nettacker/web/static/img/flags/1x1/pf.svg similarity index 100% rename from web/static/img/flags/1x1/pf.svg rename to nettacker/web/static/img/flags/1x1/pf.svg diff --git a/web/static/img/flags/1x1/pg.svg b/nettacker/web/static/img/flags/1x1/pg.svg similarity index 100% rename from web/static/img/flags/1x1/pg.svg rename to nettacker/web/static/img/flags/1x1/pg.svg diff --git a/web/static/img/flags/1x1/ph.svg b/nettacker/web/static/img/flags/1x1/ph.svg similarity index 100% rename from web/static/img/flags/1x1/ph.svg rename to nettacker/web/static/img/flags/1x1/ph.svg diff --git a/web/static/img/flags/1x1/pk.svg b/nettacker/web/static/img/flags/1x1/pk.svg similarity index 100% rename from web/static/img/flags/1x1/pk.svg rename to nettacker/web/static/img/flags/1x1/pk.svg diff --git a/web/static/img/flags/1x1/pl.svg b/nettacker/web/static/img/flags/1x1/pl.svg similarity index 100% rename from web/static/img/flags/1x1/pl.svg rename to nettacker/web/static/img/flags/1x1/pl.svg diff --git a/web/static/img/flags/1x1/pm.svg b/nettacker/web/static/img/flags/1x1/pm.svg similarity index 100% rename from web/static/img/flags/1x1/pm.svg rename to nettacker/web/static/img/flags/1x1/pm.svg diff --git a/web/static/img/flags/1x1/pn.svg b/nettacker/web/static/img/flags/1x1/pn.svg similarity index 100% rename from web/static/img/flags/1x1/pn.svg rename to nettacker/web/static/img/flags/1x1/pn.svg diff --git a/web/static/img/flags/1x1/pr.svg b/nettacker/web/static/img/flags/1x1/pr.svg similarity index 100% rename from web/static/img/flags/1x1/pr.svg rename to nettacker/web/static/img/flags/1x1/pr.svg diff --git a/web/static/img/flags/1x1/ps.svg b/nettacker/web/static/img/flags/1x1/ps.svg similarity index 100% rename from web/static/img/flags/1x1/ps.svg rename to nettacker/web/static/img/flags/1x1/ps.svg diff --git a/web/static/img/flags/1x1/pt.svg b/nettacker/web/static/img/flags/1x1/pt.svg similarity index 100% rename from web/static/img/flags/1x1/pt.svg rename to nettacker/web/static/img/flags/1x1/pt.svg diff --git a/web/static/img/flags/1x1/pw.svg b/nettacker/web/static/img/flags/1x1/pw.svg similarity index 100% rename from web/static/img/flags/1x1/pw.svg rename to nettacker/web/static/img/flags/1x1/pw.svg diff --git a/web/static/img/flags/1x1/py.svg b/nettacker/web/static/img/flags/1x1/py.svg similarity index 100% rename from web/static/img/flags/1x1/py.svg rename to nettacker/web/static/img/flags/1x1/py.svg diff --git a/web/static/img/flags/1x1/qa.svg b/nettacker/web/static/img/flags/1x1/qa.svg similarity index 100% rename from web/static/img/flags/1x1/qa.svg rename to nettacker/web/static/img/flags/1x1/qa.svg diff --git a/web/static/img/flags/1x1/re.svg b/nettacker/web/static/img/flags/1x1/re.svg similarity index 100% rename from web/static/img/flags/1x1/re.svg rename to nettacker/web/static/img/flags/1x1/re.svg diff --git a/web/static/img/flags/1x1/ro.svg b/nettacker/web/static/img/flags/1x1/ro.svg similarity index 100% rename from web/static/img/flags/1x1/ro.svg rename to nettacker/web/static/img/flags/1x1/ro.svg diff --git a/web/static/img/flags/1x1/rs.svg b/nettacker/web/static/img/flags/1x1/rs.svg similarity index 100% rename from web/static/img/flags/1x1/rs.svg rename to nettacker/web/static/img/flags/1x1/rs.svg diff --git a/web/static/img/flags/1x1/ru.svg b/nettacker/web/static/img/flags/1x1/ru.svg similarity index 100% rename from web/static/img/flags/1x1/ru.svg rename to nettacker/web/static/img/flags/1x1/ru.svg diff --git a/web/static/img/flags/1x1/rw.svg b/nettacker/web/static/img/flags/1x1/rw.svg similarity index 100% rename from web/static/img/flags/1x1/rw.svg rename to nettacker/web/static/img/flags/1x1/rw.svg diff --git a/web/static/img/flags/1x1/sa.svg b/nettacker/web/static/img/flags/1x1/sa.svg similarity index 100% rename from web/static/img/flags/1x1/sa.svg rename to nettacker/web/static/img/flags/1x1/sa.svg diff --git a/web/static/img/flags/1x1/sb.svg b/nettacker/web/static/img/flags/1x1/sb.svg similarity index 100% rename from web/static/img/flags/1x1/sb.svg rename to nettacker/web/static/img/flags/1x1/sb.svg diff --git a/web/static/img/flags/1x1/sc.svg b/nettacker/web/static/img/flags/1x1/sc.svg similarity index 100% rename from web/static/img/flags/1x1/sc.svg rename to nettacker/web/static/img/flags/1x1/sc.svg diff --git a/web/static/img/flags/1x1/sd.svg b/nettacker/web/static/img/flags/1x1/sd.svg similarity index 100% rename from web/static/img/flags/1x1/sd.svg rename to nettacker/web/static/img/flags/1x1/sd.svg diff --git a/web/static/img/flags/1x1/se.svg b/nettacker/web/static/img/flags/1x1/se.svg similarity index 100% rename from web/static/img/flags/1x1/se.svg rename to nettacker/web/static/img/flags/1x1/se.svg diff --git a/web/static/img/flags/1x1/sg.svg b/nettacker/web/static/img/flags/1x1/sg.svg similarity index 100% rename from web/static/img/flags/1x1/sg.svg rename to nettacker/web/static/img/flags/1x1/sg.svg diff --git a/web/static/img/flags/1x1/sh.svg b/nettacker/web/static/img/flags/1x1/sh.svg similarity index 100% rename from web/static/img/flags/1x1/sh.svg rename to nettacker/web/static/img/flags/1x1/sh.svg diff --git a/web/static/img/flags/1x1/si.svg b/nettacker/web/static/img/flags/1x1/si.svg similarity index 100% rename from web/static/img/flags/1x1/si.svg rename to nettacker/web/static/img/flags/1x1/si.svg diff --git a/web/static/img/flags/1x1/sj.svg b/nettacker/web/static/img/flags/1x1/sj.svg similarity index 100% rename from web/static/img/flags/1x1/sj.svg rename to nettacker/web/static/img/flags/1x1/sj.svg diff --git a/web/static/img/flags/1x1/sk.svg b/nettacker/web/static/img/flags/1x1/sk.svg similarity index 100% rename from web/static/img/flags/1x1/sk.svg rename to nettacker/web/static/img/flags/1x1/sk.svg diff --git a/web/static/img/flags/1x1/sl.svg b/nettacker/web/static/img/flags/1x1/sl.svg similarity index 100% rename from web/static/img/flags/1x1/sl.svg rename to nettacker/web/static/img/flags/1x1/sl.svg diff --git a/web/static/img/flags/1x1/sm.svg b/nettacker/web/static/img/flags/1x1/sm.svg similarity index 100% rename from web/static/img/flags/1x1/sm.svg rename to nettacker/web/static/img/flags/1x1/sm.svg diff --git a/web/static/img/flags/1x1/sn.svg b/nettacker/web/static/img/flags/1x1/sn.svg similarity index 100% rename from web/static/img/flags/1x1/sn.svg rename to nettacker/web/static/img/flags/1x1/sn.svg diff --git a/web/static/img/flags/1x1/so.svg b/nettacker/web/static/img/flags/1x1/so.svg similarity index 100% rename from web/static/img/flags/1x1/so.svg rename to nettacker/web/static/img/flags/1x1/so.svg diff --git a/web/static/img/flags/1x1/sr.svg b/nettacker/web/static/img/flags/1x1/sr.svg similarity index 100% rename from web/static/img/flags/1x1/sr.svg rename to nettacker/web/static/img/flags/1x1/sr.svg diff --git a/web/static/img/flags/1x1/ss.svg b/nettacker/web/static/img/flags/1x1/ss.svg similarity index 100% rename from web/static/img/flags/1x1/ss.svg rename to nettacker/web/static/img/flags/1x1/ss.svg diff --git a/web/static/img/flags/1x1/st.svg b/nettacker/web/static/img/flags/1x1/st.svg similarity index 100% rename from web/static/img/flags/1x1/st.svg rename to nettacker/web/static/img/flags/1x1/st.svg diff --git a/web/static/img/flags/1x1/sv.svg b/nettacker/web/static/img/flags/1x1/sv.svg similarity index 100% rename from web/static/img/flags/1x1/sv.svg rename to nettacker/web/static/img/flags/1x1/sv.svg diff --git a/web/static/img/flags/1x1/sx.svg b/nettacker/web/static/img/flags/1x1/sx.svg similarity index 100% rename from web/static/img/flags/1x1/sx.svg rename to nettacker/web/static/img/flags/1x1/sx.svg diff --git a/web/static/img/flags/1x1/sy.svg b/nettacker/web/static/img/flags/1x1/sy.svg similarity index 100% rename from web/static/img/flags/1x1/sy.svg rename to nettacker/web/static/img/flags/1x1/sy.svg diff --git a/web/static/img/flags/1x1/sz.svg b/nettacker/web/static/img/flags/1x1/sz.svg similarity index 100% rename from web/static/img/flags/1x1/sz.svg rename to nettacker/web/static/img/flags/1x1/sz.svg diff --git a/web/static/img/flags/1x1/tc.svg b/nettacker/web/static/img/flags/1x1/tc.svg similarity index 100% rename from web/static/img/flags/1x1/tc.svg rename to nettacker/web/static/img/flags/1x1/tc.svg diff --git a/web/static/img/flags/1x1/td.svg b/nettacker/web/static/img/flags/1x1/td.svg similarity index 100% rename from web/static/img/flags/1x1/td.svg rename to nettacker/web/static/img/flags/1x1/td.svg diff --git a/web/static/img/flags/1x1/tf.svg b/nettacker/web/static/img/flags/1x1/tf.svg similarity index 100% rename from web/static/img/flags/1x1/tf.svg rename to nettacker/web/static/img/flags/1x1/tf.svg diff --git a/web/static/img/flags/1x1/tg.svg b/nettacker/web/static/img/flags/1x1/tg.svg similarity index 100% rename from web/static/img/flags/1x1/tg.svg rename to nettacker/web/static/img/flags/1x1/tg.svg diff --git a/web/static/img/flags/1x1/th.svg b/nettacker/web/static/img/flags/1x1/th.svg similarity index 100% rename from web/static/img/flags/1x1/th.svg rename to nettacker/web/static/img/flags/1x1/th.svg diff --git a/web/static/img/flags/1x1/tj.svg b/nettacker/web/static/img/flags/1x1/tj.svg similarity index 100% rename from web/static/img/flags/1x1/tj.svg rename to nettacker/web/static/img/flags/1x1/tj.svg diff --git a/web/static/img/flags/1x1/tk.svg b/nettacker/web/static/img/flags/1x1/tk.svg similarity index 100% rename from web/static/img/flags/1x1/tk.svg rename to nettacker/web/static/img/flags/1x1/tk.svg diff --git a/web/static/img/flags/1x1/tl.svg b/nettacker/web/static/img/flags/1x1/tl.svg similarity index 100% rename from web/static/img/flags/1x1/tl.svg rename to nettacker/web/static/img/flags/1x1/tl.svg diff --git a/web/static/img/flags/1x1/tm.svg b/nettacker/web/static/img/flags/1x1/tm.svg similarity index 100% rename from web/static/img/flags/1x1/tm.svg rename to nettacker/web/static/img/flags/1x1/tm.svg diff --git a/web/static/img/flags/1x1/tn.svg b/nettacker/web/static/img/flags/1x1/tn.svg similarity index 100% rename from web/static/img/flags/1x1/tn.svg rename to nettacker/web/static/img/flags/1x1/tn.svg diff --git a/web/static/img/flags/1x1/to.svg b/nettacker/web/static/img/flags/1x1/to.svg similarity index 100% rename from web/static/img/flags/1x1/to.svg rename to nettacker/web/static/img/flags/1x1/to.svg diff --git a/web/static/img/flags/1x1/tr.svg b/nettacker/web/static/img/flags/1x1/tr.svg similarity index 100% rename from web/static/img/flags/1x1/tr.svg rename to nettacker/web/static/img/flags/1x1/tr.svg diff --git a/web/static/img/flags/1x1/tt.svg b/nettacker/web/static/img/flags/1x1/tt.svg similarity index 100% rename from web/static/img/flags/1x1/tt.svg rename to nettacker/web/static/img/flags/1x1/tt.svg diff --git a/web/static/img/flags/1x1/tv.svg b/nettacker/web/static/img/flags/1x1/tv.svg similarity index 100% rename from web/static/img/flags/1x1/tv.svg rename to nettacker/web/static/img/flags/1x1/tv.svg diff --git a/web/static/img/flags/1x1/tw.svg b/nettacker/web/static/img/flags/1x1/tw.svg similarity index 100% rename from web/static/img/flags/1x1/tw.svg rename to nettacker/web/static/img/flags/1x1/tw.svg diff --git a/web/static/img/flags/1x1/tz.svg b/nettacker/web/static/img/flags/1x1/tz.svg similarity index 100% rename from web/static/img/flags/1x1/tz.svg rename to nettacker/web/static/img/flags/1x1/tz.svg diff --git a/web/static/img/flags/1x1/ua.svg b/nettacker/web/static/img/flags/1x1/ua.svg similarity index 100% rename from web/static/img/flags/1x1/ua.svg rename to nettacker/web/static/img/flags/1x1/ua.svg diff --git a/web/static/img/flags/1x1/ug.svg b/nettacker/web/static/img/flags/1x1/ug.svg similarity index 100% rename from web/static/img/flags/1x1/ug.svg rename to nettacker/web/static/img/flags/1x1/ug.svg diff --git a/web/static/img/flags/1x1/um.svg b/nettacker/web/static/img/flags/1x1/um.svg similarity index 100% rename from web/static/img/flags/1x1/um.svg rename to nettacker/web/static/img/flags/1x1/um.svg diff --git a/web/static/img/flags/1x1/un.svg b/nettacker/web/static/img/flags/1x1/un.svg similarity index 100% rename from web/static/img/flags/1x1/un.svg rename to nettacker/web/static/img/flags/1x1/un.svg diff --git a/web/static/img/flags/1x1/us.svg b/nettacker/web/static/img/flags/1x1/us.svg similarity index 100% rename from web/static/img/flags/1x1/us.svg rename to nettacker/web/static/img/flags/1x1/us.svg diff --git a/web/static/img/flags/1x1/uy.svg b/nettacker/web/static/img/flags/1x1/uy.svg similarity index 100% rename from web/static/img/flags/1x1/uy.svg rename to nettacker/web/static/img/flags/1x1/uy.svg diff --git a/web/static/img/flags/1x1/uz.svg b/nettacker/web/static/img/flags/1x1/uz.svg similarity index 100% rename from web/static/img/flags/1x1/uz.svg rename to nettacker/web/static/img/flags/1x1/uz.svg diff --git a/web/static/img/flags/1x1/va.svg b/nettacker/web/static/img/flags/1x1/va.svg similarity index 100% rename from web/static/img/flags/1x1/va.svg rename to nettacker/web/static/img/flags/1x1/va.svg diff --git a/web/static/img/flags/1x1/vc.svg b/nettacker/web/static/img/flags/1x1/vc.svg similarity index 100% rename from web/static/img/flags/1x1/vc.svg rename to nettacker/web/static/img/flags/1x1/vc.svg diff --git a/web/static/img/flags/1x1/ve.svg b/nettacker/web/static/img/flags/1x1/ve.svg similarity index 100% rename from web/static/img/flags/1x1/ve.svg rename to nettacker/web/static/img/flags/1x1/ve.svg diff --git a/web/static/img/flags/1x1/vg.svg b/nettacker/web/static/img/flags/1x1/vg.svg similarity index 100% rename from web/static/img/flags/1x1/vg.svg rename to nettacker/web/static/img/flags/1x1/vg.svg diff --git a/web/static/img/flags/1x1/vi.svg b/nettacker/web/static/img/flags/1x1/vi.svg similarity index 100% rename from web/static/img/flags/1x1/vi.svg rename to nettacker/web/static/img/flags/1x1/vi.svg diff --git a/web/static/img/flags/1x1/vn.svg b/nettacker/web/static/img/flags/1x1/vn.svg similarity index 100% rename from web/static/img/flags/1x1/vn.svg rename to nettacker/web/static/img/flags/1x1/vn.svg diff --git a/web/static/img/flags/1x1/vu.svg b/nettacker/web/static/img/flags/1x1/vu.svg similarity index 100% rename from web/static/img/flags/1x1/vu.svg rename to nettacker/web/static/img/flags/1x1/vu.svg diff --git a/web/static/img/flags/1x1/wf.svg b/nettacker/web/static/img/flags/1x1/wf.svg similarity index 100% rename from web/static/img/flags/1x1/wf.svg rename to nettacker/web/static/img/flags/1x1/wf.svg diff --git a/web/static/img/flags/1x1/ws.svg b/nettacker/web/static/img/flags/1x1/ws.svg similarity index 100% rename from web/static/img/flags/1x1/ws.svg rename to nettacker/web/static/img/flags/1x1/ws.svg diff --git a/web/static/img/flags/1x1/ye.svg b/nettacker/web/static/img/flags/1x1/ye.svg similarity index 100% rename from web/static/img/flags/1x1/ye.svg rename to nettacker/web/static/img/flags/1x1/ye.svg diff --git a/web/static/img/flags/1x1/yt.svg b/nettacker/web/static/img/flags/1x1/yt.svg similarity index 100% rename from web/static/img/flags/1x1/yt.svg rename to nettacker/web/static/img/flags/1x1/yt.svg diff --git a/web/static/img/flags/1x1/za.svg b/nettacker/web/static/img/flags/1x1/za.svg similarity index 100% rename from web/static/img/flags/1x1/za.svg rename to nettacker/web/static/img/flags/1x1/za.svg diff --git a/web/static/img/flags/1x1/zm.svg b/nettacker/web/static/img/flags/1x1/zm.svg similarity index 100% rename from web/static/img/flags/1x1/zm.svg rename to nettacker/web/static/img/flags/1x1/zm.svg diff --git a/web/static/img/flags/1x1/zw.svg b/nettacker/web/static/img/flags/1x1/zw.svg similarity index 100% rename from web/static/img/flags/1x1/zw.svg rename to nettacker/web/static/img/flags/1x1/zw.svg diff --git a/web/static/img/flags/4x3/ad.svg b/nettacker/web/static/img/flags/4x3/ad.svg similarity index 100% rename from web/static/img/flags/4x3/ad.svg rename to nettacker/web/static/img/flags/4x3/ad.svg diff --git a/web/static/img/flags/4x3/ae.svg b/nettacker/web/static/img/flags/4x3/ae.svg similarity index 100% rename from web/static/img/flags/4x3/ae.svg rename to nettacker/web/static/img/flags/4x3/ae.svg diff --git a/web/static/img/flags/4x3/af.svg b/nettacker/web/static/img/flags/4x3/af.svg similarity index 100% rename from web/static/img/flags/4x3/af.svg rename to nettacker/web/static/img/flags/4x3/af.svg diff --git a/web/static/img/flags/4x3/ag.svg b/nettacker/web/static/img/flags/4x3/ag.svg similarity index 100% rename from web/static/img/flags/4x3/ag.svg rename to nettacker/web/static/img/flags/4x3/ag.svg diff --git a/web/static/img/flags/4x3/ai.svg b/nettacker/web/static/img/flags/4x3/ai.svg similarity index 100% rename from web/static/img/flags/4x3/ai.svg rename to nettacker/web/static/img/flags/4x3/ai.svg diff --git a/web/static/img/flags/4x3/al.svg b/nettacker/web/static/img/flags/4x3/al.svg similarity index 100% rename from web/static/img/flags/4x3/al.svg rename to nettacker/web/static/img/flags/4x3/al.svg diff --git a/web/static/img/flags/4x3/am.svg b/nettacker/web/static/img/flags/4x3/am.svg similarity index 100% rename from web/static/img/flags/4x3/am.svg rename to nettacker/web/static/img/flags/4x3/am.svg diff --git a/web/static/img/flags/4x3/ao.svg b/nettacker/web/static/img/flags/4x3/ao.svg similarity index 100% rename from web/static/img/flags/4x3/ao.svg rename to nettacker/web/static/img/flags/4x3/ao.svg diff --git a/web/static/img/flags/4x3/aq.svg b/nettacker/web/static/img/flags/4x3/aq.svg similarity index 100% rename from web/static/img/flags/4x3/aq.svg rename to nettacker/web/static/img/flags/4x3/aq.svg diff --git a/web/static/img/flags/4x3/ar.svg b/nettacker/web/static/img/flags/4x3/ar.svg similarity index 100% rename from web/static/img/flags/4x3/ar.svg rename to nettacker/web/static/img/flags/4x3/ar.svg diff --git a/web/static/img/flags/4x3/as.svg b/nettacker/web/static/img/flags/4x3/as.svg similarity index 100% rename from web/static/img/flags/4x3/as.svg rename to nettacker/web/static/img/flags/4x3/as.svg diff --git a/web/static/img/flags/4x3/at.svg b/nettacker/web/static/img/flags/4x3/at.svg similarity index 100% rename from web/static/img/flags/4x3/at.svg rename to nettacker/web/static/img/flags/4x3/at.svg diff --git a/web/static/img/flags/4x3/au.svg b/nettacker/web/static/img/flags/4x3/au.svg similarity index 100% rename from web/static/img/flags/4x3/au.svg rename to nettacker/web/static/img/flags/4x3/au.svg diff --git a/web/static/img/flags/4x3/aw.svg b/nettacker/web/static/img/flags/4x3/aw.svg similarity index 100% rename from web/static/img/flags/4x3/aw.svg rename to nettacker/web/static/img/flags/4x3/aw.svg diff --git a/web/static/img/flags/4x3/ax.svg b/nettacker/web/static/img/flags/4x3/ax.svg similarity index 100% rename from web/static/img/flags/4x3/ax.svg rename to nettacker/web/static/img/flags/4x3/ax.svg diff --git a/web/static/img/flags/4x3/az.svg b/nettacker/web/static/img/flags/4x3/az.svg similarity index 100% rename from web/static/img/flags/4x3/az.svg rename to nettacker/web/static/img/flags/4x3/az.svg diff --git a/web/static/img/flags/4x3/ba.svg b/nettacker/web/static/img/flags/4x3/ba.svg similarity index 100% rename from web/static/img/flags/4x3/ba.svg rename to nettacker/web/static/img/flags/4x3/ba.svg diff --git a/web/static/img/flags/4x3/bb.svg b/nettacker/web/static/img/flags/4x3/bb.svg similarity index 100% rename from web/static/img/flags/4x3/bb.svg rename to nettacker/web/static/img/flags/4x3/bb.svg diff --git a/web/static/img/flags/4x3/bd.svg b/nettacker/web/static/img/flags/4x3/bd.svg similarity index 100% rename from web/static/img/flags/4x3/bd.svg rename to nettacker/web/static/img/flags/4x3/bd.svg diff --git a/web/static/img/flags/4x3/be.svg b/nettacker/web/static/img/flags/4x3/be.svg similarity index 100% rename from web/static/img/flags/4x3/be.svg rename to nettacker/web/static/img/flags/4x3/be.svg diff --git a/web/static/img/flags/4x3/bf.svg b/nettacker/web/static/img/flags/4x3/bf.svg similarity index 100% rename from web/static/img/flags/4x3/bf.svg rename to nettacker/web/static/img/flags/4x3/bf.svg diff --git a/web/static/img/flags/4x3/bg.svg b/nettacker/web/static/img/flags/4x3/bg.svg similarity index 100% rename from web/static/img/flags/4x3/bg.svg rename to nettacker/web/static/img/flags/4x3/bg.svg diff --git a/web/static/img/flags/4x3/bh.svg b/nettacker/web/static/img/flags/4x3/bh.svg similarity index 100% rename from web/static/img/flags/4x3/bh.svg rename to nettacker/web/static/img/flags/4x3/bh.svg diff --git a/web/static/img/flags/4x3/bi.svg b/nettacker/web/static/img/flags/4x3/bi.svg similarity index 100% rename from web/static/img/flags/4x3/bi.svg rename to nettacker/web/static/img/flags/4x3/bi.svg diff --git a/web/static/img/flags/4x3/bj.svg b/nettacker/web/static/img/flags/4x3/bj.svg similarity index 100% rename from web/static/img/flags/4x3/bj.svg rename to nettacker/web/static/img/flags/4x3/bj.svg diff --git a/web/static/img/flags/4x3/bl.svg b/nettacker/web/static/img/flags/4x3/bl.svg similarity index 100% rename from web/static/img/flags/4x3/bl.svg rename to nettacker/web/static/img/flags/4x3/bl.svg diff --git a/web/static/img/flags/4x3/bm.svg b/nettacker/web/static/img/flags/4x3/bm.svg similarity index 100% rename from web/static/img/flags/4x3/bm.svg rename to nettacker/web/static/img/flags/4x3/bm.svg diff --git a/web/static/img/flags/4x3/bn.svg b/nettacker/web/static/img/flags/4x3/bn.svg similarity index 100% rename from web/static/img/flags/4x3/bn.svg rename to nettacker/web/static/img/flags/4x3/bn.svg diff --git a/web/static/img/flags/4x3/bo.svg b/nettacker/web/static/img/flags/4x3/bo.svg similarity index 100% rename from web/static/img/flags/4x3/bo.svg rename to nettacker/web/static/img/flags/4x3/bo.svg diff --git a/web/static/img/flags/4x3/bq.svg b/nettacker/web/static/img/flags/4x3/bq.svg similarity index 100% rename from web/static/img/flags/4x3/bq.svg rename to nettacker/web/static/img/flags/4x3/bq.svg diff --git a/web/static/img/flags/4x3/br.svg b/nettacker/web/static/img/flags/4x3/br.svg similarity index 100% rename from web/static/img/flags/4x3/br.svg rename to nettacker/web/static/img/flags/4x3/br.svg diff --git a/web/static/img/flags/4x3/bs.svg b/nettacker/web/static/img/flags/4x3/bs.svg similarity index 100% rename from web/static/img/flags/4x3/bs.svg rename to nettacker/web/static/img/flags/4x3/bs.svg diff --git a/web/static/img/flags/4x3/bt.svg b/nettacker/web/static/img/flags/4x3/bt.svg similarity index 100% rename from web/static/img/flags/4x3/bt.svg rename to nettacker/web/static/img/flags/4x3/bt.svg diff --git a/web/static/img/flags/4x3/bv.svg b/nettacker/web/static/img/flags/4x3/bv.svg similarity index 100% rename from web/static/img/flags/4x3/bv.svg rename to nettacker/web/static/img/flags/4x3/bv.svg diff --git a/web/static/img/flags/4x3/bw.svg b/nettacker/web/static/img/flags/4x3/bw.svg similarity index 100% rename from web/static/img/flags/4x3/bw.svg rename to nettacker/web/static/img/flags/4x3/bw.svg diff --git a/web/static/img/flags/4x3/by.svg b/nettacker/web/static/img/flags/4x3/by.svg similarity index 100% rename from web/static/img/flags/4x3/by.svg rename to nettacker/web/static/img/flags/4x3/by.svg diff --git a/web/static/img/flags/4x3/bz.svg b/nettacker/web/static/img/flags/4x3/bz.svg similarity index 100% rename from web/static/img/flags/4x3/bz.svg rename to nettacker/web/static/img/flags/4x3/bz.svg diff --git a/web/static/img/flags/4x3/ca.svg b/nettacker/web/static/img/flags/4x3/ca.svg similarity index 100% rename from web/static/img/flags/4x3/ca.svg rename to nettacker/web/static/img/flags/4x3/ca.svg diff --git a/web/static/img/flags/4x3/cc.svg b/nettacker/web/static/img/flags/4x3/cc.svg similarity index 100% rename from web/static/img/flags/4x3/cc.svg rename to nettacker/web/static/img/flags/4x3/cc.svg diff --git a/web/static/img/flags/4x3/cd.svg b/nettacker/web/static/img/flags/4x3/cd.svg similarity index 100% rename from web/static/img/flags/4x3/cd.svg rename to nettacker/web/static/img/flags/4x3/cd.svg diff --git a/web/static/img/flags/4x3/cf.svg b/nettacker/web/static/img/flags/4x3/cf.svg similarity index 100% rename from web/static/img/flags/4x3/cf.svg rename to nettacker/web/static/img/flags/4x3/cf.svg diff --git a/web/static/img/flags/4x3/cg.svg b/nettacker/web/static/img/flags/4x3/cg.svg similarity index 100% rename from web/static/img/flags/4x3/cg.svg rename to nettacker/web/static/img/flags/4x3/cg.svg diff --git a/web/static/img/flags/4x3/ch.svg b/nettacker/web/static/img/flags/4x3/ch.svg similarity index 100% rename from web/static/img/flags/4x3/ch.svg rename to nettacker/web/static/img/flags/4x3/ch.svg diff --git a/web/static/img/flags/4x3/ci.svg b/nettacker/web/static/img/flags/4x3/ci.svg similarity index 100% rename from web/static/img/flags/4x3/ci.svg rename to nettacker/web/static/img/flags/4x3/ci.svg diff --git a/web/static/img/flags/4x3/ck.svg b/nettacker/web/static/img/flags/4x3/ck.svg similarity index 100% rename from web/static/img/flags/4x3/ck.svg rename to nettacker/web/static/img/flags/4x3/ck.svg diff --git a/web/static/img/flags/4x3/cl.svg b/nettacker/web/static/img/flags/4x3/cl.svg similarity index 100% rename from web/static/img/flags/4x3/cl.svg rename to nettacker/web/static/img/flags/4x3/cl.svg diff --git a/web/static/img/flags/4x3/cm.svg b/nettacker/web/static/img/flags/4x3/cm.svg similarity index 100% rename from web/static/img/flags/4x3/cm.svg rename to nettacker/web/static/img/flags/4x3/cm.svg diff --git a/web/static/img/flags/4x3/cn.svg b/nettacker/web/static/img/flags/4x3/cn.svg similarity index 100% rename from web/static/img/flags/4x3/cn.svg rename to nettacker/web/static/img/flags/4x3/cn.svg diff --git a/web/static/img/flags/4x3/co.svg b/nettacker/web/static/img/flags/4x3/co.svg similarity index 100% rename from web/static/img/flags/4x3/co.svg rename to nettacker/web/static/img/flags/4x3/co.svg diff --git a/web/static/img/flags/4x3/cr.svg b/nettacker/web/static/img/flags/4x3/cr.svg similarity index 100% rename from web/static/img/flags/4x3/cr.svg rename to nettacker/web/static/img/flags/4x3/cr.svg diff --git a/web/static/img/flags/4x3/cu.svg b/nettacker/web/static/img/flags/4x3/cu.svg similarity index 100% rename from web/static/img/flags/4x3/cu.svg rename to nettacker/web/static/img/flags/4x3/cu.svg diff --git a/web/static/img/flags/4x3/cv.svg b/nettacker/web/static/img/flags/4x3/cv.svg similarity index 100% rename from web/static/img/flags/4x3/cv.svg rename to nettacker/web/static/img/flags/4x3/cv.svg diff --git a/web/static/img/flags/4x3/cw.svg b/nettacker/web/static/img/flags/4x3/cw.svg similarity index 100% rename from web/static/img/flags/4x3/cw.svg rename to nettacker/web/static/img/flags/4x3/cw.svg diff --git a/web/static/img/flags/4x3/cx.svg b/nettacker/web/static/img/flags/4x3/cx.svg similarity index 100% rename from web/static/img/flags/4x3/cx.svg rename to nettacker/web/static/img/flags/4x3/cx.svg diff --git a/web/static/img/flags/4x3/cy.svg b/nettacker/web/static/img/flags/4x3/cy.svg similarity index 100% rename from web/static/img/flags/4x3/cy.svg rename to nettacker/web/static/img/flags/4x3/cy.svg diff --git a/web/static/img/flags/4x3/cz.svg b/nettacker/web/static/img/flags/4x3/cz.svg similarity index 100% rename from web/static/img/flags/4x3/cz.svg rename to nettacker/web/static/img/flags/4x3/cz.svg diff --git a/web/static/img/flags/4x3/de.svg b/nettacker/web/static/img/flags/4x3/de.svg similarity index 100% rename from web/static/img/flags/4x3/de.svg rename to nettacker/web/static/img/flags/4x3/de.svg diff --git a/web/static/img/flags/4x3/dj.svg b/nettacker/web/static/img/flags/4x3/dj.svg similarity index 100% rename from web/static/img/flags/4x3/dj.svg rename to nettacker/web/static/img/flags/4x3/dj.svg diff --git a/web/static/img/flags/4x3/dk.svg b/nettacker/web/static/img/flags/4x3/dk.svg similarity index 100% rename from web/static/img/flags/4x3/dk.svg rename to nettacker/web/static/img/flags/4x3/dk.svg diff --git a/web/static/img/flags/4x3/dm.svg b/nettacker/web/static/img/flags/4x3/dm.svg similarity index 100% rename from web/static/img/flags/4x3/dm.svg rename to nettacker/web/static/img/flags/4x3/dm.svg diff --git a/web/static/img/flags/4x3/do.svg b/nettacker/web/static/img/flags/4x3/do.svg similarity index 100% rename from web/static/img/flags/4x3/do.svg rename to nettacker/web/static/img/flags/4x3/do.svg diff --git a/web/static/img/flags/4x3/dz.svg b/nettacker/web/static/img/flags/4x3/dz.svg similarity index 100% rename from web/static/img/flags/4x3/dz.svg rename to nettacker/web/static/img/flags/4x3/dz.svg diff --git a/web/static/img/flags/4x3/ec.svg b/nettacker/web/static/img/flags/4x3/ec.svg similarity index 100% rename from web/static/img/flags/4x3/ec.svg rename to nettacker/web/static/img/flags/4x3/ec.svg diff --git a/web/static/img/flags/4x3/ee.svg b/nettacker/web/static/img/flags/4x3/ee.svg similarity index 100% rename from web/static/img/flags/4x3/ee.svg rename to nettacker/web/static/img/flags/4x3/ee.svg diff --git a/web/static/img/flags/4x3/eg.svg b/nettacker/web/static/img/flags/4x3/eg.svg similarity index 100% rename from web/static/img/flags/4x3/eg.svg rename to nettacker/web/static/img/flags/4x3/eg.svg diff --git a/web/static/img/flags/4x3/eh.svg b/nettacker/web/static/img/flags/4x3/eh.svg similarity index 100% rename from web/static/img/flags/4x3/eh.svg rename to nettacker/web/static/img/flags/4x3/eh.svg diff --git a/web/static/img/flags/4x3/er.svg b/nettacker/web/static/img/flags/4x3/er.svg similarity index 100% rename from web/static/img/flags/4x3/er.svg rename to nettacker/web/static/img/flags/4x3/er.svg diff --git a/web/static/img/flags/4x3/es-ct.svg b/nettacker/web/static/img/flags/4x3/es-ct.svg similarity index 100% rename from web/static/img/flags/4x3/es-ct.svg rename to nettacker/web/static/img/flags/4x3/es-ct.svg diff --git a/web/static/img/flags/4x3/es.svg b/nettacker/web/static/img/flags/4x3/es.svg similarity index 100% rename from web/static/img/flags/4x3/es.svg rename to nettacker/web/static/img/flags/4x3/es.svg diff --git a/web/static/img/flags/4x3/et.svg b/nettacker/web/static/img/flags/4x3/et.svg similarity index 100% rename from web/static/img/flags/4x3/et.svg rename to nettacker/web/static/img/flags/4x3/et.svg diff --git a/web/static/img/flags/4x3/eu.svg b/nettacker/web/static/img/flags/4x3/eu.svg similarity index 100% rename from web/static/img/flags/4x3/eu.svg rename to nettacker/web/static/img/flags/4x3/eu.svg diff --git a/web/static/img/flags/4x3/fi.svg b/nettacker/web/static/img/flags/4x3/fi.svg similarity index 100% rename from web/static/img/flags/4x3/fi.svg rename to nettacker/web/static/img/flags/4x3/fi.svg diff --git a/web/static/img/flags/4x3/fj.svg b/nettacker/web/static/img/flags/4x3/fj.svg similarity index 100% rename from web/static/img/flags/4x3/fj.svg rename to nettacker/web/static/img/flags/4x3/fj.svg diff --git a/web/static/img/flags/4x3/fk.svg b/nettacker/web/static/img/flags/4x3/fk.svg similarity index 100% rename from web/static/img/flags/4x3/fk.svg rename to nettacker/web/static/img/flags/4x3/fk.svg diff --git a/web/static/img/flags/4x3/fm.svg b/nettacker/web/static/img/flags/4x3/fm.svg similarity index 100% rename from web/static/img/flags/4x3/fm.svg rename to nettacker/web/static/img/flags/4x3/fm.svg diff --git a/web/static/img/flags/4x3/fo.svg b/nettacker/web/static/img/flags/4x3/fo.svg similarity index 100% rename from web/static/img/flags/4x3/fo.svg rename to nettacker/web/static/img/flags/4x3/fo.svg diff --git a/web/static/img/flags/4x3/fr.svg b/nettacker/web/static/img/flags/4x3/fr.svg similarity index 100% rename from web/static/img/flags/4x3/fr.svg rename to nettacker/web/static/img/flags/4x3/fr.svg diff --git a/web/static/img/flags/4x3/ga.svg b/nettacker/web/static/img/flags/4x3/ga.svg similarity index 100% rename from web/static/img/flags/4x3/ga.svg rename to nettacker/web/static/img/flags/4x3/ga.svg diff --git a/web/static/img/flags/4x3/gb-eng.svg b/nettacker/web/static/img/flags/4x3/gb-eng.svg similarity index 100% rename from web/static/img/flags/4x3/gb-eng.svg rename to nettacker/web/static/img/flags/4x3/gb-eng.svg diff --git a/web/static/img/flags/4x3/gb-nir.svg b/nettacker/web/static/img/flags/4x3/gb-nir.svg similarity index 100% rename from web/static/img/flags/4x3/gb-nir.svg rename to nettacker/web/static/img/flags/4x3/gb-nir.svg diff --git a/web/static/img/flags/4x3/gb-sct.svg b/nettacker/web/static/img/flags/4x3/gb-sct.svg similarity index 100% rename from web/static/img/flags/4x3/gb-sct.svg rename to nettacker/web/static/img/flags/4x3/gb-sct.svg diff --git a/web/static/img/flags/4x3/gb-wls.svg b/nettacker/web/static/img/flags/4x3/gb-wls.svg similarity index 100% rename from web/static/img/flags/4x3/gb-wls.svg rename to nettacker/web/static/img/flags/4x3/gb-wls.svg diff --git a/web/static/img/flags/4x3/gb.svg b/nettacker/web/static/img/flags/4x3/gb.svg similarity index 100% rename from web/static/img/flags/4x3/gb.svg rename to nettacker/web/static/img/flags/4x3/gb.svg diff --git a/web/static/img/flags/4x3/gd.svg b/nettacker/web/static/img/flags/4x3/gd.svg similarity index 100% rename from web/static/img/flags/4x3/gd.svg rename to nettacker/web/static/img/flags/4x3/gd.svg diff --git a/web/static/img/flags/4x3/ge.svg b/nettacker/web/static/img/flags/4x3/ge.svg similarity index 100% rename from web/static/img/flags/4x3/ge.svg rename to nettacker/web/static/img/flags/4x3/ge.svg diff --git a/web/static/img/flags/4x3/gf.svg b/nettacker/web/static/img/flags/4x3/gf.svg similarity index 100% rename from web/static/img/flags/4x3/gf.svg rename to nettacker/web/static/img/flags/4x3/gf.svg diff --git a/web/static/img/flags/4x3/gg.svg b/nettacker/web/static/img/flags/4x3/gg.svg similarity index 100% rename from web/static/img/flags/4x3/gg.svg rename to nettacker/web/static/img/flags/4x3/gg.svg diff --git a/web/static/img/flags/4x3/gh.svg b/nettacker/web/static/img/flags/4x3/gh.svg similarity index 100% rename from web/static/img/flags/4x3/gh.svg rename to nettacker/web/static/img/flags/4x3/gh.svg diff --git a/web/static/img/flags/4x3/gi.svg b/nettacker/web/static/img/flags/4x3/gi.svg similarity index 100% rename from web/static/img/flags/4x3/gi.svg rename to nettacker/web/static/img/flags/4x3/gi.svg diff --git a/web/static/img/flags/4x3/gl.svg b/nettacker/web/static/img/flags/4x3/gl.svg similarity index 100% rename from web/static/img/flags/4x3/gl.svg rename to nettacker/web/static/img/flags/4x3/gl.svg diff --git a/web/static/img/flags/4x3/gm.svg b/nettacker/web/static/img/flags/4x3/gm.svg similarity index 100% rename from web/static/img/flags/4x3/gm.svg rename to nettacker/web/static/img/flags/4x3/gm.svg diff --git a/web/static/img/flags/4x3/gn.svg b/nettacker/web/static/img/flags/4x3/gn.svg similarity index 100% rename from web/static/img/flags/4x3/gn.svg rename to nettacker/web/static/img/flags/4x3/gn.svg diff --git a/web/static/img/flags/4x3/gp.svg b/nettacker/web/static/img/flags/4x3/gp.svg similarity index 100% rename from web/static/img/flags/4x3/gp.svg rename to nettacker/web/static/img/flags/4x3/gp.svg diff --git a/web/static/img/flags/4x3/gq.svg b/nettacker/web/static/img/flags/4x3/gq.svg similarity index 100% rename from web/static/img/flags/4x3/gq.svg rename to nettacker/web/static/img/flags/4x3/gq.svg diff --git a/web/static/img/flags/4x3/gr.svg b/nettacker/web/static/img/flags/4x3/gr.svg similarity index 100% rename from web/static/img/flags/4x3/gr.svg rename to nettacker/web/static/img/flags/4x3/gr.svg diff --git a/web/static/img/flags/4x3/gs.svg b/nettacker/web/static/img/flags/4x3/gs.svg similarity index 100% rename from web/static/img/flags/4x3/gs.svg rename to nettacker/web/static/img/flags/4x3/gs.svg diff --git a/web/static/img/flags/4x3/gt.svg b/nettacker/web/static/img/flags/4x3/gt.svg similarity index 100% rename from web/static/img/flags/4x3/gt.svg rename to nettacker/web/static/img/flags/4x3/gt.svg diff --git a/web/static/img/flags/4x3/gu.svg b/nettacker/web/static/img/flags/4x3/gu.svg similarity index 100% rename from web/static/img/flags/4x3/gu.svg rename to nettacker/web/static/img/flags/4x3/gu.svg diff --git a/web/static/img/flags/4x3/gw.svg b/nettacker/web/static/img/flags/4x3/gw.svg similarity index 100% rename from web/static/img/flags/4x3/gw.svg rename to nettacker/web/static/img/flags/4x3/gw.svg diff --git a/web/static/img/flags/4x3/gy.svg b/nettacker/web/static/img/flags/4x3/gy.svg similarity index 100% rename from web/static/img/flags/4x3/gy.svg rename to nettacker/web/static/img/flags/4x3/gy.svg diff --git a/web/static/img/flags/4x3/hk.svg b/nettacker/web/static/img/flags/4x3/hk.svg similarity index 100% rename from web/static/img/flags/4x3/hk.svg rename to nettacker/web/static/img/flags/4x3/hk.svg diff --git a/web/static/img/flags/4x3/hm.svg b/nettacker/web/static/img/flags/4x3/hm.svg similarity index 100% rename from web/static/img/flags/4x3/hm.svg rename to nettacker/web/static/img/flags/4x3/hm.svg diff --git a/web/static/img/flags/4x3/hn.svg b/nettacker/web/static/img/flags/4x3/hn.svg similarity index 100% rename from web/static/img/flags/4x3/hn.svg rename to nettacker/web/static/img/flags/4x3/hn.svg diff --git a/web/static/img/flags/4x3/hr.svg b/nettacker/web/static/img/flags/4x3/hr.svg similarity index 100% rename from web/static/img/flags/4x3/hr.svg rename to nettacker/web/static/img/flags/4x3/hr.svg diff --git a/web/static/img/flags/4x3/ht.svg b/nettacker/web/static/img/flags/4x3/ht.svg similarity index 100% rename from web/static/img/flags/4x3/ht.svg rename to nettacker/web/static/img/flags/4x3/ht.svg diff --git a/web/static/img/flags/4x3/hu.svg b/nettacker/web/static/img/flags/4x3/hu.svg similarity index 100% rename from web/static/img/flags/4x3/hu.svg rename to nettacker/web/static/img/flags/4x3/hu.svg diff --git a/web/static/img/flags/4x3/id.svg b/nettacker/web/static/img/flags/4x3/id.svg similarity index 100% rename from web/static/img/flags/4x3/id.svg rename to nettacker/web/static/img/flags/4x3/id.svg diff --git a/web/static/img/flags/4x3/ie.svg b/nettacker/web/static/img/flags/4x3/ie.svg similarity index 100% rename from web/static/img/flags/4x3/ie.svg rename to nettacker/web/static/img/flags/4x3/ie.svg diff --git a/web/static/img/flags/4x3/il.svg b/nettacker/web/static/img/flags/4x3/il.svg similarity index 100% rename from web/static/img/flags/4x3/il.svg rename to nettacker/web/static/img/flags/4x3/il.svg diff --git a/web/static/img/flags/4x3/im.svg b/nettacker/web/static/img/flags/4x3/im.svg similarity index 100% rename from web/static/img/flags/4x3/im.svg rename to nettacker/web/static/img/flags/4x3/im.svg diff --git a/web/static/img/flags/4x3/in.svg b/nettacker/web/static/img/flags/4x3/in.svg similarity index 100% rename from web/static/img/flags/4x3/in.svg rename to nettacker/web/static/img/flags/4x3/in.svg diff --git a/web/static/img/flags/4x3/io.svg b/nettacker/web/static/img/flags/4x3/io.svg similarity index 100% rename from web/static/img/flags/4x3/io.svg rename to nettacker/web/static/img/flags/4x3/io.svg diff --git a/web/static/img/flags/4x3/iq.svg b/nettacker/web/static/img/flags/4x3/iq.svg similarity index 100% rename from web/static/img/flags/4x3/iq.svg rename to nettacker/web/static/img/flags/4x3/iq.svg diff --git a/web/static/img/flags/4x3/ir.svg b/nettacker/web/static/img/flags/4x3/ir.svg similarity index 100% rename from web/static/img/flags/4x3/ir.svg rename to nettacker/web/static/img/flags/4x3/ir.svg diff --git a/web/static/img/flags/4x3/is.svg b/nettacker/web/static/img/flags/4x3/is.svg similarity index 100% rename from web/static/img/flags/4x3/is.svg rename to nettacker/web/static/img/flags/4x3/is.svg diff --git a/web/static/img/flags/4x3/it.svg b/nettacker/web/static/img/flags/4x3/it.svg similarity index 100% rename from web/static/img/flags/4x3/it.svg rename to nettacker/web/static/img/flags/4x3/it.svg diff --git a/web/static/img/flags/4x3/je.svg b/nettacker/web/static/img/flags/4x3/je.svg similarity index 100% rename from web/static/img/flags/4x3/je.svg rename to nettacker/web/static/img/flags/4x3/je.svg diff --git a/web/static/img/flags/4x3/jm.svg b/nettacker/web/static/img/flags/4x3/jm.svg similarity index 100% rename from web/static/img/flags/4x3/jm.svg rename to nettacker/web/static/img/flags/4x3/jm.svg diff --git a/web/static/img/flags/4x3/jo.svg b/nettacker/web/static/img/flags/4x3/jo.svg similarity index 100% rename from web/static/img/flags/4x3/jo.svg rename to nettacker/web/static/img/flags/4x3/jo.svg diff --git a/web/static/img/flags/4x3/jp.svg b/nettacker/web/static/img/flags/4x3/jp.svg similarity index 100% rename from web/static/img/flags/4x3/jp.svg rename to nettacker/web/static/img/flags/4x3/jp.svg diff --git a/web/static/img/flags/4x3/ke.svg b/nettacker/web/static/img/flags/4x3/ke.svg similarity index 100% rename from web/static/img/flags/4x3/ke.svg rename to nettacker/web/static/img/flags/4x3/ke.svg diff --git a/web/static/img/flags/4x3/kg.svg b/nettacker/web/static/img/flags/4x3/kg.svg similarity index 100% rename from web/static/img/flags/4x3/kg.svg rename to nettacker/web/static/img/flags/4x3/kg.svg diff --git a/web/static/img/flags/4x3/kh.svg b/nettacker/web/static/img/flags/4x3/kh.svg similarity index 100% rename from web/static/img/flags/4x3/kh.svg rename to nettacker/web/static/img/flags/4x3/kh.svg diff --git a/web/static/img/flags/4x3/ki.svg b/nettacker/web/static/img/flags/4x3/ki.svg similarity index 100% rename from web/static/img/flags/4x3/ki.svg rename to nettacker/web/static/img/flags/4x3/ki.svg diff --git a/web/static/img/flags/4x3/km.svg b/nettacker/web/static/img/flags/4x3/km.svg similarity index 100% rename from web/static/img/flags/4x3/km.svg rename to nettacker/web/static/img/flags/4x3/km.svg diff --git a/web/static/img/flags/4x3/kn.svg b/nettacker/web/static/img/flags/4x3/kn.svg similarity index 100% rename from web/static/img/flags/4x3/kn.svg rename to nettacker/web/static/img/flags/4x3/kn.svg diff --git a/web/static/img/flags/4x3/kp.svg b/nettacker/web/static/img/flags/4x3/kp.svg similarity index 100% rename from web/static/img/flags/4x3/kp.svg rename to nettacker/web/static/img/flags/4x3/kp.svg diff --git a/web/static/img/flags/4x3/kr.svg b/nettacker/web/static/img/flags/4x3/kr.svg similarity index 100% rename from web/static/img/flags/4x3/kr.svg rename to nettacker/web/static/img/flags/4x3/kr.svg diff --git a/web/static/img/flags/4x3/kw.svg b/nettacker/web/static/img/flags/4x3/kw.svg similarity index 100% rename from web/static/img/flags/4x3/kw.svg rename to nettacker/web/static/img/flags/4x3/kw.svg diff --git a/web/static/img/flags/4x3/ky.svg b/nettacker/web/static/img/flags/4x3/ky.svg similarity index 100% rename from web/static/img/flags/4x3/ky.svg rename to nettacker/web/static/img/flags/4x3/ky.svg diff --git a/web/static/img/flags/4x3/kz.svg b/nettacker/web/static/img/flags/4x3/kz.svg similarity index 100% rename from web/static/img/flags/4x3/kz.svg rename to nettacker/web/static/img/flags/4x3/kz.svg diff --git a/web/static/img/flags/4x3/la.svg b/nettacker/web/static/img/flags/4x3/la.svg similarity index 100% rename from web/static/img/flags/4x3/la.svg rename to nettacker/web/static/img/flags/4x3/la.svg diff --git a/web/static/img/flags/4x3/lb.svg b/nettacker/web/static/img/flags/4x3/lb.svg similarity index 100% rename from web/static/img/flags/4x3/lb.svg rename to nettacker/web/static/img/flags/4x3/lb.svg diff --git a/web/static/img/flags/4x3/lc.svg b/nettacker/web/static/img/flags/4x3/lc.svg similarity index 100% rename from web/static/img/flags/4x3/lc.svg rename to nettacker/web/static/img/flags/4x3/lc.svg diff --git a/web/static/img/flags/4x3/li.svg b/nettacker/web/static/img/flags/4x3/li.svg similarity index 100% rename from web/static/img/flags/4x3/li.svg rename to nettacker/web/static/img/flags/4x3/li.svg diff --git a/web/static/img/flags/4x3/lk.svg b/nettacker/web/static/img/flags/4x3/lk.svg similarity index 100% rename from web/static/img/flags/4x3/lk.svg rename to nettacker/web/static/img/flags/4x3/lk.svg diff --git a/web/static/img/flags/4x3/lr.svg b/nettacker/web/static/img/flags/4x3/lr.svg similarity index 100% rename from web/static/img/flags/4x3/lr.svg rename to nettacker/web/static/img/flags/4x3/lr.svg diff --git a/web/static/img/flags/4x3/ls.svg b/nettacker/web/static/img/flags/4x3/ls.svg similarity index 100% rename from web/static/img/flags/4x3/ls.svg rename to nettacker/web/static/img/flags/4x3/ls.svg diff --git a/web/static/img/flags/4x3/lt.svg b/nettacker/web/static/img/flags/4x3/lt.svg similarity index 100% rename from web/static/img/flags/4x3/lt.svg rename to nettacker/web/static/img/flags/4x3/lt.svg diff --git a/web/static/img/flags/4x3/lu.svg b/nettacker/web/static/img/flags/4x3/lu.svg similarity index 100% rename from web/static/img/flags/4x3/lu.svg rename to nettacker/web/static/img/flags/4x3/lu.svg diff --git a/web/static/img/flags/4x3/lv.svg b/nettacker/web/static/img/flags/4x3/lv.svg similarity index 100% rename from web/static/img/flags/4x3/lv.svg rename to nettacker/web/static/img/flags/4x3/lv.svg diff --git a/web/static/img/flags/4x3/ly.svg b/nettacker/web/static/img/flags/4x3/ly.svg similarity index 100% rename from web/static/img/flags/4x3/ly.svg rename to nettacker/web/static/img/flags/4x3/ly.svg diff --git a/web/static/img/flags/4x3/ma.svg b/nettacker/web/static/img/flags/4x3/ma.svg similarity index 100% rename from web/static/img/flags/4x3/ma.svg rename to nettacker/web/static/img/flags/4x3/ma.svg diff --git a/web/static/img/flags/4x3/mc.svg b/nettacker/web/static/img/flags/4x3/mc.svg similarity index 100% rename from web/static/img/flags/4x3/mc.svg rename to nettacker/web/static/img/flags/4x3/mc.svg diff --git a/web/static/img/flags/4x3/md.svg b/nettacker/web/static/img/flags/4x3/md.svg similarity index 100% rename from web/static/img/flags/4x3/md.svg rename to nettacker/web/static/img/flags/4x3/md.svg diff --git a/web/static/img/flags/4x3/me.svg b/nettacker/web/static/img/flags/4x3/me.svg similarity index 100% rename from web/static/img/flags/4x3/me.svg rename to nettacker/web/static/img/flags/4x3/me.svg diff --git a/web/static/img/flags/4x3/mf.svg b/nettacker/web/static/img/flags/4x3/mf.svg similarity index 100% rename from web/static/img/flags/4x3/mf.svg rename to nettacker/web/static/img/flags/4x3/mf.svg diff --git a/web/static/img/flags/4x3/mg.svg b/nettacker/web/static/img/flags/4x3/mg.svg similarity index 100% rename from web/static/img/flags/4x3/mg.svg rename to nettacker/web/static/img/flags/4x3/mg.svg diff --git a/web/static/img/flags/4x3/mh.svg b/nettacker/web/static/img/flags/4x3/mh.svg similarity index 100% rename from web/static/img/flags/4x3/mh.svg rename to nettacker/web/static/img/flags/4x3/mh.svg diff --git a/web/static/img/flags/4x3/mk.svg b/nettacker/web/static/img/flags/4x3/mk.svg similarity index 100% rename from web/static/img/flags/4x3/mk.svg rename to nettacker/web/static/img/flags/4x3/mk.svg diff --git a/web/static/img/flags/4x3/ml.svg b/nettacker/web/static/img/flags/4x3/ml.svg similarity index 100% rename from web/static/img/flags/4x3/ml.svg rename to nettacker/web/static/img/flags/4x3/ml.svg diff --git a/web/static/img/flags/4x3/mm.svg b/nettacker/web/static/img/flags/4x3/mm.svg similarity index 100% rename from web/static/img/flags/4x3/mm.svg rename to nettacker/web/static/img/flags/4x3/mm.svg diff --git a/web/static/img/flags/4x3/mn.svg b/nettacker/web/static/img/flags/4x3/mn.svg similarity index 100% rename from web/static/img/flags/4x3/mn.svg rename to nettacker/web/static/img/flags/4x3/mn.svg diff --git a/web/static/img/flags/4x3/mo.svg b/nettacker/web/static/img/flags/4x3/mo.svg similarity index 100% rename from web/static/img/flags/4x3/mo.svg rename to nettacker/web/static/img/flags/4x3/mo.svg diff --git a/web/static/img/flags/4x3/mp.svg b/nettacker/web/static/img/flags/4x3/mp.svg similarity index 100% rename from web/static/img/flags/4x3/mp.svg rename to nettacker/web/static/img/flags/4x3/mp.svg diff --git a/web/static/img/flags/4x3/mq.svg b/nettacker/web/static/img/flags/4x3/mq.svg similarity index 100% rename from web/static/img/flags/4x3/mq.svg rename to nettacker/web/static/img/flags/4x3/mq.svg diff --git a/web/static/img/flags/4x3/mr.svg b/nettacker/web/static/img/flags/4x3/mr.svg similarity index 100% rename from web/static/img/flags/4x3/mr.svg rename to nettacker/web/static/img/flags/4x3/mr.svg diff --git a/web/static/img/flags/4x3/ms.svg b/nettacker/web/static/img/flags/4x3/ms.svg similarity index 100% rename from web/static/img/flags/4x3/ms.svg rename to nettacker/web/static/img/flags/4x3/ms.svg diff --git a/web/static/img/flags/4x3/mt.svg b/nettacker/web/static/img/flags/4x3/mt.svg similarity index 100% rename from web/static/img/flags/4x3/mt.svg rename to nettacker/web/static/img/flags/4x3/mt.svg diff --git a/web/static/img/flags/4x3/mu.svg b/nettacker/web/static/img/flags/4x3/mu.svg similarity index 100% rename from web/static/img/flags/4x3/mu.svg rename to nettacker/web/static/img/flags/4x3/mu.svg diff --git a/web/static/img/flags/4x3/mv.svg b/nettacker/web/static/img/flags/4x3/mv.svg similarity index 100% rename from web/static/img/flags/4x3/mv.svg rename to nettacker/web/static/img/flags/4x3/mv.svg diff --git a/web/static/img/flags/4x3/mw.svg b/nettacker/web/static/img/flags/4x3/mw.svg similarity index 100% rename from web/static/img/flags/4x3/mw.svg rename to nettacker/web/static/img/flags/4x3/mw.svg diff --git a/web/static/img/flags/4x3/mx.svg b/nettacker/web/static/img/flags/4x3/mx.svg similarity index 100% rename from web/static/img/flags/4x3/mx.svg rename to nettacker/web/static/img/flags/4x3/mx.svg diff --git a/web/static/img/flags/4x3/my.svg b/nettacker/web/static/img/flags/4x3/my.svg similarity index 100% rename from web/static/img/flags/4x3/my.svg rename to nettacker/web/static/img/flags/4x3/my.svg diff --git a/web/static/img/flags/4x3/mz.svg b/nettacker/web/static/img/flags/4x3/mz.svg similarity index 100% rename from web/static/img/flags/4x3/mz.svg rename to nettacker/web/static/img/flags/4x3/mz.svg diff --git a/web/static/img/flags/4x3/na.svg b/nettacker/web/static/img/flags/4x3/na.svg similarity index 100% rename from web/static/img/flags/4x3/na.svg rename to nettacker/web/static/img/flags/4x3/na.svg diff --git a/web/static/img/flags/4x3/nc.svg b/nettacker/web/static/img/flags/4x3/nc.svg similarity index 100% rename from web/static/img/flags/4x3/nc.svg rename to nettacker/web/static/img/flags/4x3/nc.svg diff --git a/web/static/img/flags/4x3/ne.svg b/nettacker/web/static/img/flags/4x3/ne.svg similarity index 100% rename from web/static/img/flags/4x3/ne.svg rename to nettacker/web/static/img/flags/4x3/ne.svg diff --git a/web/static/img/flags/4x3/nf.svg b/nettacker/web/static/img/flags/4x3/nf.svg similarity index 100% rename from web/static/img/flags/4x3/nf.svg rename to nettacker/web/static/img/flags/4x3/nf.svg diff --git a/web/static/img/flags/4x3/ng.svg b/nettacker/web/static/img/flags/4x3/ng.svg similarity index 100% rename from web/static/img/flags/4x3/ng.svg rename to nettacker/web/static/img/flags/4x3/ng.svg diff --git a/web/static/img/flags/4x3/ni.svg b/nettacker/web/static/img/flags/4x3/ni.svg similarity index 100% rename from web/static/img/flags/4x3/ni.svg rename to nettacker/web/static/img/flags/4x3/ni.svg diff --git a/web/static/img/flags/4x3/nl.svg b/nettacker/web/static/img/flags/4x3/nl.svg similarity index 100% rename from web/static/img/flags/4x3/nl.svg rename to nettacker/web/static/img/flags/4x3/nl.svg diff --git a/web/static/img/flags/4x3/no.svg b/nettacker/web/static/img/flags/4x3/no.svg similarity index 100% rename from web/static/img/flags/4x3/no.svg rename to nettacker/web/static/img/flags/4x3/no.svg diff --git a/web/static/img/flags/4x3/np.svg b/nettacker/web/static/img/flags/4x3/np.svg similarity index 100% rename from web/static/img/flags/4x3/np.svg rename to nettacker/web/static/img/flags/4x3/np.svg diff --git a/web/static/img/flags/4x3/nr.svg b/nettacker/web/static/img/flags/4x3/nr.svg similarity index 100% rename from web/static/img/flags/4x3/nr.svg rename to nettacker/web/static/img/flags/4x3/nr.svg diff --git a/web/static/img/flags/4x3/nu.svg b/nettacker/web/static/img/flags/4x3/nu.svg similarity index 100% rename from web/static/img/flags/4x3/nu.svg rename to nettacker/web/static/img/flags/4x3/nu.svg diff --git a/web/static/img/flags/4x3/nz.svg b/nettacker/web/static/img/flags/4x3/nz.svg similarity index 100% rename from web/static/img/flags/4x3/nz.svg rename to nettacker/web/static/img/flags/4x3/nz.svg diff --git a/web/static/img/flags/4x3/om.svg b/nettacker/web/static/img/flags/4x3/om.svg similarity index 100% rename from web/static/img/flags/4x3/om.svg rename to nettacker/web/static/img/flags/4x3/om.svg diff --git a/web/static/img/flags/4x3/pa.svg b/nettacker/web/static/img/flags/4x3/pa.svg similarity index 100% rename from web/static/img/flags/4x3/pa.svg rename to nettacker/web/static/img/flags/4x3/pa.svg diff --git a/web/static/img/flags/4x3/pe.svg b/nettacker/web/static/img/flags/4x3/pe.svg similarity index 100% rename from web/static/img/flags/4x3/pe.svg rename to nettacker/web/static/img/flags/4x3/pe.svg diff --git a/web/static/img/flags/4x3/pf.svg b/nettacker/web/static/img/flags/4x3/pf.svg similarity index 100% rename from web/static/img/flags/4x3/pf.svg rename to nettacker/web/static/img/flags/4x3/pf.svg diff --git a/web/static/img/flags/4x3/pg.svg b/nettacker/web/static/img/flags/4x3/pg.svg similarity index 100% rename from web/static/img/flags/4x3/pg.svg rename to nettacker/web/static/img/flags/4x3/pg.svg diff --git a/web/static/img/flags/4x3/ph.svg b/nettacker/web/static/img/flags/4x3/ph.svg similarity index 100% rename from web/static/img/flags/4x3/ph.svg rename to nettacker/web/static/img/flags/4x3/ph.svg diff --git a/web/static/img/flags/4x3/pk.svg b/nettacker/web/static/img/flags/4x3/pk.svg similarity index 100% rename from web/static/img/flags/4x3/pk.svg rename to nettacker/web/static/img/flags/4x3/pk.svg diff --git a/web/static/img/flags/4x3/pl.svg b/nettacker/web/static/img/flags/4x3/pl.svg similarity index 100% rename from web/static/img/flags/4x3/pl.svg rename to nettacker/web/static/img/flags/4x3/pl.svg diff --git a/web/static/img/flags/4x3/pm.svg b/nettacker/web/static/img/flags/4x3/pm.svg similarity index 100% rename from web/static/img/flags/4x3/pm.svg rename to nettacker/web/static/img/flags/4x3/pm.svg diff --git a/web/static/img/flags/4x3/pn.svg b/nettacker/web/static/img/flags/4x3/pn.svg similarity index 100% rename from web/static/img/flags/4x3/pn.svg rename to nettacker/web/static/img/flags/4x3/pn.svg diff --git a/web/static/img/flags/4x3/pr.svg b/nettacker/web/static/img/flags/4x3/pr.svg similarity index 100% rename from web/static/img/flags/4x3/pr.svg rename to nettacker/web/static/img/flags/4x3/pr.svg diff --git a/web/static/img/flags/4x3/ps.svg b/nettacker/web/static/img/flags/4x3/ps.svg similarity index 100% rename from web/static/img/flags/4x3/ps.svg rename to nettacker/web/static/img/flags/4x3/ps.svg diff --git a/web/static/img/flags/4x3/pt.svg b/nettacker/web/static/img/flags/4x3/pt.svg similarity index 100% rename from web/static/img/flags/4x3/pt.svg rename to nettacker/web/static/img/flags/4x3/pt.svg diff --git a/web/static/img/flags/4x3/pw.svg b/nettacker/web/static/img/flags/4x3/pw.svg similarity index 100% rename from web/static/img/flags/4x3/pw.svg rename to nettacker/web/static/img/flags/4x3/pw.svg diff --git a/web/static/img/flags/4x3/py.svg b/nettacker/web/static/img/flags/4x3/py.svg similarity index 100% rename from web/static/img/flags/4x3/py.svg rename to nettacker/web/static/img/flags/4x3/py.svg diff --git a/web/static/img/flags/4x3/qa.svg b/nettacker/web/static/img/flags/4x3/qa.svg similarity index 100% rename from web/static/img/flags/4x3/qa.svg rename to nettacker/web/static/img/flags/4x3/qa.svg diff --git a/web/static/img/flags/4x3/re.svg b/nettacker/web/static/img/flags/4x3/re.svg similarity index 100% rename from web/static/img/flags/4x3/re.svg rename to nettacker/web/static/img/flags/4x3/re.svg diff --git a/web/static/img/flags/4x3/ro.svg b/nettacker/web/static/img/flags/4x3/ro.svg similarity index 100% rename from web/static/img/flags/4x3/ro.svg rename to nettacker/web/static/img/flags/4x3/ro.svg diff --git a/web/static/img/flags/4x3/rs.svg b/nettacker/web/static/img/flags/4x3/rs.svg similarity index 100% rename from web/static/img/flags/4x3/rs.svg rename to nettacker/web/static/img/flags/4x3/rs.svg diff --git a/web/static/img/flags/4x3/ru.svg b/nettacker/web/static/img/flags/4x3/ru.svg similarity index 100% rename from web/static/img/flags/4x3/ru.svg rename to nettacker/web/static/img/flags/4x3/ru.svg diff --git a/web/static/img/flags/4x3/rw.svg b/nettacker/web/static/img/flags/4x3/rw.svg similarity index 100% rename from web/static/img/flags/4x3/rw.svg rename to nettacker/web/static/img/flags/4x3/rw.svg diff --git a/web/static/img/flags/4x3/sa.svg b/nettacker/web/static/img/flags/4x3/sa.svg similarity index 100% rename from web/static/img/flags/4x3/sa.svg rename to nettacker/web/static/img/flags/4x3/sa.svg diff --git a/web/static/img/flags/4x3/sb.svg b/nettacker/web/static/img/flags/4x3/sb.svg similarity index 100% rename from web/static/img/flags/4x3/sb.svg rename to nettacker/web/static/img/flags/4x3/sb.svg diff --git a/web/static/img/flags/4x3/sc.svg b/nettacker/web/static/img/flags/4x3/sc.svg similarity index 100% rename from web/static/img/flags/4x3/sc.svg rename to nettacker/web/static/img/flags/4x3/sc.svg diff --git a/web/static/img/flags/4x3/sd.svg b/nettacker/web/static/img/flags/4x3/sd.svg similarity index 100% rename from web/static/img/flags/4x3/sd.svg rename to nettacker/web/static/img/flags/4x3/sd.svg diff --git a/web/static/img/flags/4x3/se.svg b/nettacker/web/static/img/flags/4x3/se.svg similarity index 100% rename from web/static/img/flags/4x3/se.svg rename to nettacker/web/static/img/flags/4x3/se.svg diff --git a/web/static/img/flags/4x3/sg.svg b/nettacker/web/static/img/flags/4x3/sg.svg similarity index 100% rename from web/static/img/flags/4x3/sg.svg rename to nettacker/web/static/img/flags/4x3/sg.svg diff --git a/web/static/img/flags/4x3/sh.svg b/nettacker/web/static/img/flags/4x3/sh.svg similarity index 100% rename from web/static/img/flags/4x3/sh.svg rename to nettacker/web/static/img/flags/4x3/sh.svg diff --git a/web/static/img/flags/4x3/si.svg b/nettacker/web/static/img/flags/4x3/si.svg similarity index 100% rename from web/static/img/flags/4x3/si.svg rename to nettacker/web/static/img/flags/4x3/si.svg diff --git a/web/static/img/flags/4x3/sj.svg b/nettacker/web/static/img/flags/4x3/sj.svg similarity index 100% rename from web/static/img/flags/4x3/sj.svg rename to nettacker/web/static/img/flags/4x3/sj.svg diff --git a/web/static/img/flags/4x3/sk.svg b/nettacker/web/static/img/flags/4x3/sk.svg similarity index 100% rename from web/static/img/flags/4x3/sk.svg rename to nettacker/web/static/img/flags/4x3/sk.svg diff --git a/web/static/img/flags/4x3/sl.svg b/nettacker/web/static/img/flags/4x3/sl.svg similarity index 100% rename from web/static/img/flags/4x3/sl.svg rename to nettacker/web/static/img/flags/4x3/sl.svg diff --git a/web/static/img/flags/4x3/sm.svg b/nettacker/web/static/img/flags/4x3/sm.svg similarity index 100% rename from web/static/img/flags/4x3/sm.svg rename to nettacker/web/static/img/flags/4x3/sm.svg diff --git a/web/static/img/flags/4x3/sn.svg b/nettacker/web/static/img/flags/4x3/sn.svg similarity index 100% rename from web/static/img/flags/4x3/sn.svg rename to nettacker/web/static/img/flags/4x3/sn.svg diff --git a/web/static/img/flags/4x3/so.svg b/nettacker/web/static/img/flags/4x3/so.svg similarity index 100% rename from web/static/img/flags/4x3/so.svg rename to nettacker/web/static/img/flags/4x3/so.svg diff --git a/web/static/img/flags/4x3/sr.svg b/nettacker/web/static/img/flags/4x3/sr.svg similarity index 100% rename from web/static/img/flags/4x3/sr.svg rename to nettacker/web/static/img/flags/4x3/sr.svg diff --git a/web/static/img/flags/4x3/ss.svg b/nettacker/web/static/img/flags/4x3/ss.svg similarity index 100% rename from web/static/img/flags/4x3/ss.svg rename to nettacker/web/static/img/flags/4x3/ss.svg diff --git a/web/static/img/flags/4x3/st.svg b/nettacker/web/static/img/flags/4x3/st.svg similarity index 100% rename from web/static/img/flags/4x3/st.svg rename to nettacker/web/static/img/flags/4x3/st.svg diff --git a/web/static/img/flags/4x3/sv.svg b/nettacker/web/static/img/flags/4x3/sv.svg similarity index 100% rename from web/static/img/flags/4x3/sv.svg rename to nettacker/web/static/img/flags/4x3/sv.svg diff --git a/web/static/img/flags/4x3/sx.svg b/nettacker/web/static/img/flags/4x3/sx.svg similarity index 100% rename from web/static/img/flags/4x3/sx.svg rename to nettacker/web/static/img/flags/4x3/sx.svg diff --git a/web/static/img/flags/4x3/sy.svg b/nettacker/web/static/img/flags/4x3/sy.svg similarity index 100% rename from web/static/img/flags/4x3/sy.svg rename to nettacker/web/static/img/flags/4x3/sy.svg diff --git a/web/static/img/flags/4x3/sz.svg b/nettacker/web/static/img/flags/4x3/sz.svg similarity index 100% rename from web/static/img/flags/4x3/sz.svg rename to nettacker/web/static/img/flags/4x3/sz.svg diff --git a/web/static/img/flags/4x3/tc.svg b/nettacker/web/static/img/flags/4x3/tc.svg similarity index 100% rename from web/static/img/flags/4x3/tc.svg rename to nettacker/web/static/img/flags/4x3/tc.svg diff --git a/web/static/img/flags/4x3/td.svg b/nettacker/web/static/img/flags/4x3/td.svg similarity index 100% rename from web/static/img/flags/4x3/td.svg rename to nettacker/web/static/img/flags/4x3/td.svg diff --git a/web/static/img/flags/4x3/tf.svg b/nettacker/web/static/img/flags/4x3/tf.svg similarity index 100% rename from web/static/img/flags/4x3/tf.svg rename to nettacker/web/static/img/flags/4x3/tf.svg diff --git a/web/static/img/flags/4x3/tg.svg b/nettacker/web/static/img/flags/4x3/tg.svg similarity index 100% rename from web/static/img/flags/4x3/tg.svg rename to nettacker/web/static/img/flags/4x3/tg.svg diff --git a/web/static/img/flags/4x3/th.svg b/nettacker/web/static/img/flags/4x3/th.svg similarity index 100% rename from web/static/img/flags/4x3/th.svg rename to nettacker/web/static/img/flags/4x3/th.svg diff --git a/web/static/img/flags/4x3/tj.svg b/nettacker/web/static/img/flags/4x3/tj.svg similarity index 100% rename from web/static/img/flags/4x3/tj.svg rename to nettacker/web/static/img/flags/4x3/tj.svg diff --git a/web/static/img/flags/4x3/tk.svg b/nettacker/web/static/img/flags/4x3/tk.svg similarity index 100% rename from web/static/img/flags/4x3/tk.svg rename to nettacker/web/static/img/flags/4x3/tk.svg diff --git a/web/static/img/flags/4x3/tl.svg b/nettacker/web/static/img/flags/4x3/tl.svg similarity index 100% rename from web/static/img/flags/4x3/tl.svg rename to nettacker/web/static/img/flags/4x3/tl.svg diff --git a/web/static/img/flags/4x3/tm.svg b/nettacker/web/static/img/flags/4x3/tm.svg similarity index 100% rename from web/static/img/flags/4x3/tm.svg rename to nettacker/web/static/img/flags/4x3/tm.svg diff --git a/web/static/img/flags/4x3/tn.svg b/nettacker/web/static/img/flags/4x3/tn.svg similarity index 100% rename from web/static/img/flags/4x3/tn.svg rename to nettacker/web/static/img/flags/4x3/tn.svg diff --git a/web/static/img/flags/4x3/to.svg b/nettacker/web/static/img/flags/4x3/to.svg similarity index 100% rename from web/static/img/flags/4x3/to.svg rename to nettacker/web/static/img/flags/4x3/to.svg diff --git a/web/static/img/flags/4x3/tr.svg b/nettacker/web/static/img/flags/4x3/tr.svg similarity index 100% rename from web/static/img/flags/4x3/tr.svg rename to nettacker/web/static/img/flags/4x3/tr.svg diff --git a/web/static/img/flags/4x3/tt.svg b/nettacker/web/static/img/flags/4x3/tt.svg similarity index 100% rename from web/static/img/flags/4x3/tt.svg rename to nettacker/web/static/img/flags/4x3/tt.svg diff --git a/web/static/img/flags/4x3/tv.svg b/nettacker/web/static/img/flags/4x3/tv.svg similarity index 100% rename from web/static/img/flags/4x3/tv.svg rename to nettacker/web/static/img/flags/4x3/tv.svg diff --git a/web/static/img/flags/4x3/tw.svg b/nettacker/web/static/img/flags/4x3/tw.svg similarity index 100% rename from web/static/img/flags/4x3/tw.svg rename to nettacker/web/static/img/flags/4x3/tw.svg diff --git a/web/static/img/flags/4x3/tz.svg b/nettacker/web/static/img/flags/4x3/tz.svg similarity index 100% rename from web/static/img/flags/4x3/tz.svg rename to nettacker/web/static/img/flags/4x3/tz.svg diff --git a/web/static/img/flags/4x3/ua.svg b/nettacker/web/static/img/flags/4x3/ua.svg similarity index 100% rename from web/static/img/flags/4x3/ua.svg rename to nettacker/web/static/img/flags/4x3/ua.svg diff --git a/web/static/img/flags/4x3/ug.svg b/nettacker/web/static/img/flags/4x3/ug.svg similarity index 100% rename from web/static/img/flags/4x3/ug.svg rename to nettacker/web/static/img/flags/4x3/ug.svg diff --git a/web/static/img/flags/4x3/um.svg b/nettacker/web/static/img/flags/4x3/um.svg similarity index 100% rename from web/static/img/flags/4x3/um.svg rename to nettacker/web/static/img/flags/4x3/um.svg diff --git a/web/static/img/flags/4x3/un.svg b/nettacker/web/static/img/flags/4x3/un.svg similarity index 100% rename from web/static/img/flags/4x3/un.svg rename to nettacker/web/static/img/flags/4x3/un.svg diff --git a/web/static/img/flags/4x3/us.svg b/nettacker/web/static/img/flags/4x3/us.svg similarity index 100% rename from web/static/img/flags/4x3/us.svg rename to nettacker/web/static/img/flags/4x3/us.svg diff --git a/web/static/img/flags/4x3/uy.svg b/nettacker/web/static/img/flags/4x3/uy.svg similarity index 100% rename from web/static/img/flags/4x3/uy.svg rename to nettacker/web/static/img/flags/4x3/uy.svg diff --git a/web/static/img/flags/4x3/uz.svg b/nettacker/web/static/img/flags/4x3/uz.svg similarity index 100% rename from web/static/img/flags/4x3/uz.svg rename to nettacker/web/static/img/flags/4x3/uz.svg diff --git a/web/static/img/flags/4x3/va.svg b/nettacker/web/static/img/flags/4x3/va.svg similarity index 100% rename from web/static/img/flags/4x3/va.svg rename to nettacker/web/static/img/flags/4x3/va.svg diff --git a/web/static/img/flags/4x3/vc.svg b/nettacker/web/static/img/flags/4x3/vc.svg similarity index 100% rename from web/static/img/flags/4x3/vc.svg rename to nettacker/web/static/img/flags/4x3/vc.svg diff --git a/web/static/img/flags/4x3/ve.svg b/nettacker/web/static/img/flags/4x3/ve.svg similarity index 100% rename from web/static/img/flags/4x3/ve.svg rename to nettacker/web/static/img/flags/4x3/ve.svg diff --git a/web/static/img/flags/4x3/vg.svg b/nettacker/web/static/img/flags/4x3/vg.svg similarity index 100% rename from web/static/img/flags/4x3/vg.svg rename to nettacker/web/static/img/flags/4x3/vg.svg diff --git a/web/static/img/flags/4x3/vi.svg b/nettacker/web/static/img/flags/4x3/vi.svg similarity index 100% rename from web/static/img/flags/4x3/vi.svg rename to nettacker/web/static/img/flags/4x3/vi.svg diff --git a/web/static/img/flags/4x3/vn.svg b/nettacker/web/static/img/flags/4x3/vn.svg similarity index 100% rename from web/static/img/flags/4x3/vn.svg rename to nettacker/web/static/img/flags/4x3/vn.svg diff --git a/web/static/img/flags/4x3/vu.svg b/nettacker/web/static/img/flags/4x3/vu.svg similarity index 100% rename from web/static/img/flags/4x3/vu.svg rename to nettacker/web/static/img/flags/4x3/vu.svg diff --git a/web/static/img/flags/4x3/wf.svg b/nettacker/web/static/img/flags/4x3/wf.svg similarity index 100% rename from web/static/img/flags/4x3/wf.svg rename to nettacker/web/static/img/flags/4x3/wf.svg diff --git a/web/static/img/flags/4x3/ws.svg b/nettacker/web/static/img/flags/4x3/ws.svg similarity index 100% rename from web/static/img/flags/4x3/ws.svg rename to nettacker/web/static/img/flags/4x3/ws.svg diff --git a/web/static/img/flags/4x3/ye.svg b/nettacker/web/static/img/flags/4x3/ye.svg similarity index 100% rename from web/static/img/flags/4x3/ye.svg rename to nettacker/web/static/img/flags/4x3/ye.svg diff --git a/web/static/img/flags/4x3/yt.svg b/nettacker/web/static/img/flags/4x3/yt.svg similarity index 100% rename from web/static/img/flags/4x3/yt.svg rename to nettacker/web/static/img/flags/4x3/yt.svg diff --git a/web/static/img/flags/4x3/za.svg b/nettacker/web/static/img/flags/4x3/za.svg similarity index 100% rename from web/static/img/flags/4x3/za.svg rename to nettacker/web/static/img/flags/4x3/za.svg diff --git a/web/static/img/flags/4x3/zm.svg b/nettacker/web/static/img/flags/4x3/zm.svg similarity index 100% rename from web/static/img/flags/4x3/zm.svg rename to nettacker/web/static/img/flags/4x3/zm.svg diff --git a/web/static/img/flags/4x3/zw.svg b/nettacker/web/static/img/flags/4x3/zw.svg similarity index 100% rename from web/static/img/flags/4x3/zw.svg rename to nettacker/web/static/img/flags/4x3/zw.svg diff --git a/web/static/img/owasp-nettacker.png b/nettacker/web/static/img/owasp-nettacker.png similarity index 100% rename from web/static/img/owasp-nettacker.png rename to nettacker/web/static/img/owasp-nettacker.png diff --git a/web/static/img/owasp.png b/nettacker/web/static/img/owasp.png similarity index 100% rename from web/static/img/owasp.png rename to nettacker/web/static/img/owasp.png diff --git a/web/static/index.html b/nettacker/web/static/index.html similarity index 100% rename from web/static/index.html rename to nettacker/web/static/index.html diff --git a/web/static/js/angular.min.js b/nettacker/web/static/js/angular.min.js similarity index 100% rename from web/static/js/angular.min.js rename to nettacker/web/static/js/angular.min.js diff --git a/web/static/js/bootstrap-select.min.js b/nettacker/web/static/js/bootstrap-select.min.js similarity index 100% rename from web/static/js/bootstrap-select.min.js rename to nettacker/web/static/js/bootstrap-select.min.js diff --git a/web/static/js/bootstrap-tagsinput-angular.min.js b/nettacker/web/static/js/bootstrap-tagsinput-angular.min.js similarity index 100% rename from web/static/js/bootstrap-tagsinput-angular.min.js rename to nettacker/web/static/js/bootstrap-tagsinput-angular.min.js diff --git a/web/static/js/bootstrap-tagsinput.min.js b/nettacker/web/static/js/bootstrap-tagsinput.min.js similarity index 100% rename from web/static/js/bootstrap-tagsinput.min.js rename to nettacker/web/static/js/bootstrap-tagsinput.min.js diff --git a/web/static/js/bootstrap.min.js b/nettacker/web/static/js/bootstrap.min.js similarity index 100% rename from web/static/js/bootstrap.min.js rename to nettacker/web/static/js/bootstrap.min.js diff --git a/web/static/js/buttons.js b/nettacker/web/static/js/buttons.js similarity index 100% rename from web/static/js/buttons.js rename to nettacker/web/static/js/buttons.js diff --git a/web/static/js/d3.v4.min.js b/nettacker/web/static/js/d3.v4.min.js similarity index 100% rename from web/static/js/d3.v4.min.js rename to nettacker/web/static/js/d3.v4.min.js diff --git a/web/static/js/intro.min.js b/nettacker/web/static/js/intro.min.js similarity index 100% rename from web/static/js/intro.min.js rename to nettacker/web/static/js/intro.min.js diff --git a/web/static/js/jquery.min.js b/nettacker/web/static/js/jquery.min.js similarity index 100% rename from web/static/js/jquery.min.js rename to nettacker/web/static/js/jquery.min.js diff --git a/web/static/js/main.js b/nettacker/web/static/js/main.js similarity index 99% rename from web/static/js/main.js rename to nettacker/web/static/js/main.js index facbb0b99..aaab1c050 100644 --- a/web/static/js/main.js +++ b/nettacker/web/static/js/main.js @@ -379,7 +379,7 @@ $(document).ready(function () { var i; var id; var date; - var scan_unique_id; + var scan_id; // var report_filename; // var events_num; // var verbose; @@ -418,7 +418,7 @@ $(document).ready(function () { for (i = 0; i < res.length; i++) { id = res[i]["id"]; date = res[i]["date"]; - scan_unique_id = res[i]["scan_unique_id"]; + scan_id = res[i]["scan_id"]; // report_filename = res[i]["report_filename"]; // events_num = res[i]["events_num"]; // verbose = res[i]["verbose"]; @@ -446,8 +446,8 @@ $(document).ready(function () { "" + "" + "
" + - "

scan_unique_id:" + - scan_unique_id + + "

scan_id:" + + scan_id + "


" // "

report_filename:" + // report_filename + @@ -681,7 +681,7 @@ function filter_large_content(content, filter_rate){ var html_module_name; var html_date; - + for (i = 0; i < res.length; i++) { console.log(res[i]) @@ -695,7 +695,7 @@ function filter_large_content(content, filter_rate){ // open_ports = res[i]["info"]["open_ports"]; // scan_methods = res[i]["info"]["scan_methods"]; // category = res[i]["info"]["category"]; - + // html_categories = ""; // html_scan_methods = ""; // html_open_ports = ""; @@ -741,7 +741,7 @@ function filter_large_content(content, filter_rate){ html_module_name += "

condition_results: " + filter_large_content(results, 100) + "



"; } - + // html_scan_methods = ""; // for (j = 0; j < scan_methods.length; j++) { // html_scan_methods += diff --git a/web/static/js/renderjson.js b/nettacker/web/static/js/renderjson.js similarity index 100% rename from web/static/js/renderjson.js rename to nettacker/web/static/js/renderjson.js diff --git a/web/static/report/d3_tree_v1.html b/nettacker/web/static/report/d3_tree_v1.html similarity index 100% rename from web/static/report/d3_tree_v1.html rename to nettacker/web/static/report/d3_tree_v1.html diff --git a/web/static/report/html_table.css b/nettacker/web/static/report/html_table.css similarity index 100% rename from web/static/report/html_table.css rename to nettacker/web/static/report/html_table.css diff --git a/web/static/report/json_parse.js b/nettacker/web/static/report/json_parse.js similarity index 100% rename from web/static/report/json_parse.js rename to nettacker/web/static/report/json_parse.js diff --git a/web/static/report/table_end.html b/nettacker/web/static/report/table_end.html similarity index 100% rename from web/static/report/table_end.html rename to nettacker/web/static/report/table_end.html diff --git a/web/static/report/table_items.html b/nettacker/web/static/report/table_items.html similarity index 100% rename from web/static/report/table_items.html rename to nettacker/web/static/report/table_items.html diff --git a/web/static/report/table_title.html b/nettacker/web/static/report/table_title.html similarity index 100% rename from web/static/report/table_title.html rename to nettacker/web/static/report/table_title.html diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 000000000..906930f37 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1961 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + +[[package]] +name = "aiohappyeyeballs" +version = "2.4.0" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"}, + {file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"}, +] + +[[package]] +name = "aiohttp" +version = "3.10.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683"}, + {file = "aiohttp-3.10.5-cp310-cp310-win32.whl", hash = "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef"}, + {file = "aiohttp-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058"}, + {file = "aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072"}, + {file = "aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6"}, + {file = "aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12"}, + {file = "aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987"}, + {file = "aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04"}, + {file = "aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511"}, + {file = "aiohttp-3.10.5-cp38-cp38-win32.whl", hash = "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a"}, + {file = "aiohttp-3.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11"}, + {file = "aiohttp-3.10.5-cp39-cp39-win32.whl", hash = "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1"}, + {file = "aiohttp-3.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862"}, + {file = "aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "argparse" +version = "1.4.0" +description = "Python command-line parsing library" +optional = false +python-versions = "*" +files = [ + {file = "argparse-1.4.0-py2.py3-none-any.whl", hash = "sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314"}, + {file = "argparse-1.4.0.tar.gz", hash = "sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4"}, +] + +[[package]] +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "asyncio" +version = "3.4.3" +description = "reference implementation of PEP 3156" +optional = false +python-versions = "*" +files = [ + {file = "asyncio-3.4.3-cp33-none-win32.whl", hash = "sha256:b62c9157d36187eca799c378e572c969f0da87cd5fc42ca372d92cdb06e7e1de"}, + {file = "asyncio-3.4.3-cp33-none-win_amd64.whl", hash = "sha256:c46a87b48213d7464f22d9a497b9eef8c1928b68320a2fa94240f969f6fec08c"}, + {file = "asyncio-3.4.3-py3-none-any.whl", hash = "sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d"}, + {file = "asyncio-3.4.3.tar.gz", hash = "sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41"}, +] + +[[package]] +name = "attrs" +version = "24.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + +[[package]] +name = "bcrypt" +version = "4.2.0" +description = "Modern password hashing for your software and your servers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7"}, + {file = "bcrypt-4.2.0-cp37-abi3-win32.whl", hash = "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458"}, + {file = "bcrypt-4.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5"}, + {file = "bcrypt-4.2.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8"}, + {file = "bcrypt-4.2.0-cp39-abi3-win32.whl", hash = "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34"}, + {file = "bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9"}, + {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:39e1d30c7233cfc54f5c3f2c825156fe044efdd3e0b9d309512cc514a263ec2a"}, + {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f4f4acf526fcd1c34e7ce851147deedd4e26e6402369304220250598b26448db"}, + {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1ff39b78a52cf03fdf902635e4c81e544714861ba3f0efc56558979dd4f09170"}, + {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:373db9abe198e8e2c70d12b479464e0d5092cc122b20ec504097b5f2297ed184"}, + {file = "bcrypt-4.2.0.tar.gz", hash = "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221"}, +] + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + +[[package]] +name = "blinker" +version = "1.8.2" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.8" +files = [ + {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, + {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, +] + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "cffi" +version = "1.17.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"}, + {file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"}, + {file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"}, + {file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"}, + {file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"}, + {file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"}, + {file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"}, + {file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"}, + {file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"}, + {file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"}, + {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"}, + {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"}, + {file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"}, + {file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"}, + {file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"}, + {file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"}, + {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"}, + {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"}, + {file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"}, + {file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"}, + {file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"}, + {file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"}, + {file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"}, + {file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"}, + {file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"}, + {file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"}, + {file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"}, + {file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"}, +] + +[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 = "coverage" +version = "7.6.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cryptography" +version = "41.0.7" +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.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, +] + +[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 = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "dill" +version = "0.3.8" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "execnet" +version = "2.1.1" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=3.8" +files = [ + {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, + {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, +] + +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + +[[package]] +name = "executing" +version = "2.0.1" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.5" +files = [ + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + +[[package]] +name = "flask" +version = "3.0.3" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, + {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, +] + +[package.dependencies] +blinker = ">=1.6.2" +click = ">=8.1.3" +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=3.0.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "importlib-metadata" +version = "8.4.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, + {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "ipaddr" +version = "2.2.0" +description = "Google's IP address manipulation library" +optional = false +python-versions = "*" +files = [ + {file = "ipaddr-2.2.0.tar.gz", hash = "sha256:4092dfe667588d16aa12b59acb7c8a4024e5dcb23a681cd0b0b602373eca88d6"}, +] + +[[package]] +name = "ipython" +version = "8.18.1" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, + {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +prompt-toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" +typing-extensions = {version = "*", markers = "python_version < \"3.10\""} + +[package.extras] +all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.8" +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + +[[package]] +name = "jedi" +version = "0.19.1" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "multidict" +version = "6.0.5" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, +] + +[[package]] +name = "multiprocess" +version = "0.70.16" +description = "better multiprocessing and multithreading in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "multiprocess-0.70.16-pp310-pypy310_pp73-macosx_10_13_x86_64.whl", hash = "sha256:476887be10e2f59ff183c006af746cb6f1fd0eadcfd4ef49e605cbe2659920ee"}, + {file = "multiprocess-0.70.16-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d951bed82c8f73929ac82c61f01a7b5ce8f3e5ef40f5b52553b4f547ce2b08ec"}, + {file = "multiprocess-0.70.16-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37b55f71c07e2d741374998c043b9520b626a8dddc8b3129222ca4f1a06ef67a"}, + {file = "multiprocess-0.70.16-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba8c31889abf4511c7308a8c52bb4a30b9d590e7f58523302ba00237702ca054"}, + {file = "multiprocess-0.70.16-pp39-pypy39_pp73-macosx_10_13_x86_64.whl", hash = "sha256:0dfd078c306e08d46d7a8d06fb120313d87aa43af60d66da43ffff40b44d2f41"}, + {file = "multiprocess-0.70.16-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e7b9d0f307cd9bd50851afaac0dba2cb6c44449efff697df7c7645f7d3f2be3a"}, + {file = "multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02"}, + {file = "multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a"}, + {file = "multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e"}, + {file = "multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435"}, + {file = "multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3"}, + {file = "multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1"}, +] + +[package.dependencies] +dill = ">=0.3.8" + +[[package]] +name = "netaddr" +version = "0.9.0" +description = "A network address manipulation library for Python" +optional = false +python-versions = "*" +files = [ + {file = "netaddr-0.9.0-py3-none-any.whl", hash = "sha256:5148b1055679d2a1ec070c521b7db82137887fabd6d7e37f5199b44f775c3bb1"}, + {file = "netaddr-0.9.0.tar.gz", hash = "sha256:7b46fa9b1a2d71fd5de9e4a3784ef339700a53a08c8040f08baf5f1194da0128"}, +] + +[[package]] +name = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "paramiko" +version = "3.4.1" +description = "SSH2 protocol library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "paramiko-3.4.1-py3-none-any.whl", hash = "sha256:8e49fd2f82f84acf7ffd57c64311aa2b30e575370dc23bdb375b10262f7eac32"}, + {file = "paramiko-3.4.1.tar.gz", hash = "sha256:8b15302870af7f6652f2e038975c1d2973f06046cb5d7d65355668b3ecbece0c"}, +] + +[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 = "parso" +version = "0.8.4" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.47" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, + {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "py3dns" +version = "4.0.2" +description = "Python 3 DNS library" +optional = false +python-versions = ">=3.2" +files = [ + {file = "py3dns-4.0.2-py3-none-any.whl", hash = "sha256:36bffe62b59a72cfa09c03f0bd3473e0126f20ee4285d14c07415dbf6f5fd571"}, + {file = "py3dns-4.0.2.tar.gz", hash = "sha256:98652e80ecec143c60f78f0e6b341631ca9a7560edd8dddfc864c02902618a39"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[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 = "pyopenssl" +version = "23.3.0" +description = "Python wrapper module around the OpenSSL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyOpenSSL-23.3.0-py3-none-any.whl", hash = "sha256:6756834481d9ed5470f4a9393455154bc92fe7a64b7bc6ee2c804e78c52099b2"}, + {file = "pyOpenSSL-23.3.0.tar.gz", hash = "sha256:6b2cba5cc46e822750ec3e5a81ee12819850b11303630d575e98108a079c2b12"}, +] + +[package.dependencies] +cryptography = ">=41.0.5,<42" + +[package.extras] +docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"] +test = ["flaky", "pretend", "pytest (>=3.0.1)"] + +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, + {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, + {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, +] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-xdist" +version = "3.6.1" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, + {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, +] + +[package.dependencies] +execnet = ">=2.1" +pytest = ">=7.0.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[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 = "ruff" +version = "0.2.2" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"}, + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"}, + {file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"}, + {file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"}, + {file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"}, + {file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"}, +] + +[[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 = "sqlalchemy" +version = "2.0.32" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c9045ecc2e4db59bfc97b20516dfdf8e41d910ac6fb667ebd3a79ea54084619"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1467940318e4a860afd546ef61fefb98a14d935cd6817ed07a228c7f7c62f389"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5954463675cb15db8d4b521f3566a017c8789222b8316b1e6934c811018ee08b"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:167e7497035c303ae50651b351c28dc22a40bb98fbdb8468cdc971821b1ae533"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b27dfb676ac02529fb6e343b3a482303f16e6bc3a4d868b73935b8792edb52d0"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bf2360a5e0f7bd75fa80431bf8ebcfb920c9f885e7956c7efde89031695cafb8"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-win32.whl", hash = "sha256:306fe44e754a91cd9d600a6b070c1f2fadbb4a1a257b8781ccf33c7067fd3e4d"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-win_amd64.whl", hash = "sha256:99db65e6f3ab42e06c318f15c98f59a436f1c78179e6a6f40f529c8cc7100b22"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21b053be28a8a414f2ddd401f1be8361e41032d2ef5884b2f31d31cb723e559f"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b178e875a7a25b5938b53b006598ee7645172fccafe1c291a706e93f48499ff5"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723a40ee2cc7ea653645bd4cf024326dea2076673fc9d3d33f20f6c81db83e1d"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:295ff8689544f7ee7e819529633d058bd458c1fd7f7e3eebd0f9268ebc56c2a0"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49496b68cd190a147118af585173ee624114dfb2e0297558c460ad7495f9dfe2"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:acd9b73c5c15f0ec5ce18128b1fe9157ddd0044abc373e6ecd5ba376a7e5d961"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-win32.whl", hash = "sha256:9365a3da32dabd3e69e06b972b1ffb0c89668994c7e8e75ce21d3e5e69ddef28"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-win_amd64.whl", hash = "sha256:8bd63d051f4f313b102a2af1cbc8b80f061bf78f3d5bd0843ff70b5859e27924"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bab3db192a0c35e3c9d1560eb8332463e29e5507dbd822e29a0a3c48c0a8d92"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:19d98f4f58b13900d8dec4ed09dd09ef292208ee44cc9c2fe01c1f0a2fe440e9"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd33c61513cb1b7371fd40cf221256456d26a56284e7d19d1f0b9f1eb7dd7e8"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6ba0497c1d066dd004e0f02a92426ca2df20fac08728d03f67f6960271feec"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2b6be53e4fde0065524f1a0a7929b10e9280987b320716c1509478b712a7688c"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:916a798f62f410c0b80b63683c8061f5ebe237b0f4ad778739304253353bc1cb"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-win32.whl", hash = "sha256:31983018b74908ebc6c996a16ad3690301a23befb643093fcfe85efd292e384d"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-win_amd64.whl", hash = "sha256:4363ed245a6231f2e2957cccdda3c776265a75851f4753c60f3004b90e69bfeb"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b8afd5b26570bf41c35c0121801479958b4446751a3971fb9a480c1afd85558e"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c750987fc876813f27b60d619b987b057eb4896b81117f73bb8d9918c14f1cad"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0102afff4890f651ed91120c1120065663506b760da4e7823913ebd3258be"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:78c03d0f8a5ab4f3034c0e8482cfcc415a3ec6193491cfa1c643ed707d476f16"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:3bd1cae7519283ff525e64645ebd7a3e0283f3c038f461ecc1c7b040a0c932a1"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-win32.whl", hash = "sha256:01438ebcdc566d58c93af0171c74ec28efe6a29184b773e378a385e6215389da"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-win_amd64.whl", hash = "sha256:4979dc80fbbc9d2ef569e71e0896990bc94df2b9fdbd878290bd129b65ab579c"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c742be912f57586ac43af38b3848f7688863a403dfb220193a882ea60e1ec3a"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:62e23d0ac103bcf1c5555b6c88c114089587bc64d048fef5bbdb58dfd26f96da"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:251f0d1108aab8ea7b9aadbd07fb47fb8e3a5838dde34aa95a3349876b5a1f1d"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef18a84e5116340e38eca3e7f9eeaaef62738891422e7c2a0b80feab165905f"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3eb6a97a1d39976f360b10ff208c73afb6a4de86dd2a6212ddf65c4a6a2347d5"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0c1c9b673d21477cec17ab10bc4decb1322843ba35b481585facd88203754fc5"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-win32.whl", hash = "sha256:c41a2b9ca80ee555decc605bd3c4520cc6fef9abde8fd66b1cf65126a6922d65"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-win_amd64.whl", hash = "sha256:8a37e4d265033c897892279e8adf505c8b6b4075f2b40d77afb31f7185cd6ecd"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:52fec964fba2ef46476312a03ec8c425956b05c20220a1a03703537824b5e8e1"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:328429aecaba2aee3d71e11f2477c14eec5990fb6d0e884107935f7fb6001632"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85a01b5599e790e76ac3fe3aa2f26e1feba56270023d6afd5550ed63c68552b3"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf04784797dcdf4c0aa952c8d234fa01974c4729db55c45732520ce12dd95b4"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4488120becf9b71b3ac718f4138269a6be99a42fe023ec457896ba4f80749525"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14e09e083a5796d513918a66f3d6aedbc131e39e80875afe81d98a03312889e6"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-win32.whl", hash = "sha256:0d322cc9c9b2154ba7e82f7bf25ecc7c36fbe2d82e2933b3642fc095a52cfc78"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-win_amd64.whl", hash = "sha256:7dd8583df2f98dea28b5cd53a1beac963f4f9d087888d75f22fcc93a07cf8d84"}, + {file = "SQLAlchemy-2.0.32-py3-none-any.whl", hash = "sha256:e567a8793a692451f706b363ccf3c45e056b67d90ead58c3bc9471af5d212202"}, + {file = "SQLAlchemy-2.0.32.tar.gz", hash = "sha256:c1b88cc8b02b6a5f0efb0345a03672d4c897dc7d92585176f88c67346f565ea8"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "texttable" +version = "1.7.0" +description = "module to create simple ASCII tables" +optional = false +python-versions = "*" +files = [ + {file = "texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917"}, + {file = "texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638"}, +] + +[[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 = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[[package]] +name = "werkzeug" +version = "3.0.3" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, + {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[[package]] +name = "yarl" +version = "1.9.4" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[[package]] +name = "zipp" +version = "3.20.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"}, + {file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"}, +] + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9, <3.13" +content-hash = "135c3fa158adb8ec07ddaca0319773c8c887256d186e611d88472c7ea3c418d1" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..539fd8207 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,99 @@ +[tool.poetry] +name = "nettacker" +version = "0.4.0" +description = "Automates information gathering, vulnerability scanning and aids penetration testing engagements in general" +license = "Apache-2.0" +readme = "README.md" + +authors = ["OWASP Nettacker Contributors"] +classifiers = [ + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] +keywords = [ + "automation", + "bruteforce", + "cve", + "hacking-tools", + "information-gathering", + "network-analysis", + "owasp", + "penetration-testing", + "pentesting", + "pentesting-tools", + "port-scanner", + "python", + "security-tools", + "security", + "vulnerability-management", + "vulnerability-scanner", +] + +homepage = "https://owasp.org/www-project-nettacker" +repository = "https://github.com/OWASP/Nettacker" +documentation = "https://github.com/OWASP/Nettacker/wiki" + +packages = [{ include = "nettacker"}] + +[tool.nettacker] +release_name = "QUIN" + +[tool.poetry.dependencies] +python = "^3.9, <3.13" +aiohttp = "^3.9.5" +argparse = "^1.4.0" +asyncio = "^3.4.3" +flask = "^3.0.1" +ipaddr = "^2.2.0" +multiprocess = "^0.70.15" +netaddr = "^0.9.0" +numpy = "^1.26.1" +paramiko = "^3.4.0" +py3dns = "^4.0.0" +pyopenssl = "^23.2.0" +pysocks = "^1.7.1" +pyyaml = "^6.0.1" +requests = "^2.31.0" +sqlalchemy = "^2.0.22" +texttable = "^1.7.0" +tomli = "^2.0.1" + +[tool.poetry.group.dev.dependencies] +ipython = "^8.16.1" +ruff = "^0.2.1" + +[tool.poetry.group.test.dependencies] +coverage = "^7.3.2" +pytest = "^7.4.3" +pytest-cov = "^4.1.0" +pytest-xdist = "^3.3.1" + +[tool.poetry.urls] +"Sponsor" = "https://owasp.org/donate/?reponame=www-project-nettacker&title=OWASP+Nettacker" + +[tool.coverage.run] +branch = true + +[tool.isort] +known_first_party = ["nettacker", "tests"] +line_length = 99 +multi_line_output = 3 +no_inline_sort = true +profile = "black" + +[tool.pytest.ini_options] +addopts = "--cov=nettacker --cov-config=pyproject.toml --cov-report term --cov-report xml --dist loadscope --no-cov-on-fail --numprocesses auto" +testpaths = ["tests"] + +[tool.ruff] +line-length = 99 + +# [tool.ruff.lint] +# select = ["E4", "E7", "E9", "F", "T"] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements-apt-get.txt b/requirements-apt-get.txt deleted file mode 100644 index 616782032..000000000 --- a/requirements-apt-get.txt +++ /dev/null @@ -1,2 +0,0 @@ -libssl-dev -gcc \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index d8c2ac6d4..000000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,2 +0,0 @@ -flake8==7.0.0 -ipython==8.18.1 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 93cefbcc9..000000000 --- a/requirements.txt +++ /dev/null @@ -1,16 +0,0 @@ -argparse==1.4.0 -netaddr==0.9.0 -ipaddr==2.2.0 -requests==2.31.0 -aiohttp==3.9.5 -asyncio==3.4.3 -paramiko==3.4.0 -texttable==1.6.7 -PySocks==1.7.1 # library_name=socks # module name is not equal to socks name; this is required to be checked on startup -pyOpenSSL==23.2.0 # library_name=OpenSSL -flask==3.0.1 -SQLAlchemy>=1.4.43 # library_name=sqlalchemy -py3DNS==4.0.0 # library_name=DNS -numpy==1.26.3 -terminable_thread==0.7.1 -PyYAML==6.0.1 # library_name=yaml \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/common.py b/tests/common.py new file mode 100644 index 000000000..abfb89874 --- /dev/null +++ b/tests/common.py @@ -0,0 +1,9 @@ +import unittest +from pathlib import Path + +from conftest import nettacker_dir, tests_dir + + +class TestCase(unittest.TestCase): + nettacker_path = Path(nettacker_dir) + tests_path = Path(tests_dir) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..c30b36bf5 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,9 @@ +import sys +from os.path import abspath, dirname, join + +project_root = dirname(dirname(__file__)) +nettacker_dir = abspath(join(project_root, "src/nettacker")) +tests_dir = abspath(join(project_root, "tests")) + +sys.path.insert(0, nettacker_dir) +sys.path.insert(1, tests_dir) diff --git a/tests/core/test_utils.py b/tests/core/test_utils.py new file mode 100644 index 000000000..0cb116c00 --- /dev/null +++ b/tests/core/test_utils.py @@ -0,0 +1,53 @@ +from unittest.mock import patch + +from nettacker.core.utils import common as utils +from tests.common import TestCase + + +class TestUtils(TestCase): + def test_sort_dictionary(self): + input_dict = { + "a": 1, + "c": 3, + "d": 23, + "b": 2, + } + expected_dict = { + "a": 1, + "b": 2, + "c": 3, + "d": 23, + } + input_dict_keys = tuple(input_dict.keys()) + expected_dict_keys = tuple(expected_dict.keys()) + self.assertNotEqual(input_dict_keys, expected_dict_keys) + + sorted_dict_keys = tuple(utils.sort_dictionary(input_dict).keys()) + self.assertEqual(sorted_dict_keys, expected_dict_keys) + + @patch("multiprocessing.cpu_count") + def test_select_maximum_cpu_core(self, cpu_count_mock): + cores_mapping = { + 1: {"low": 1, "normal": 1, "high": 1, "maximum": 1}, + 2: {"low": 1, "normal": 1, "high": 1, "maximum": 1}, + 4: {"low": 1, "normal": 1, "high": 2, "maximum": 3}, + 6: {"low": 1, "normal": 1, "high": 3, "maximum": 5}, + 8: {"low": 1, "normal": 2, "high": 4, "maximum": 7}, + 10: {"low": 1, "normal": 2, "high": 5, "maximum": 9}, + 12: {"low": 1, "normal": 3, "high": 6, "maximum": 11}, + 16: {"low": 2, "normal": 4, "high": 8, "maximum": 15}, + 32: {"low": 4, "normal": 8, "high": 16, "maximum": 31}, + 48: {"low": 6, "normal": 12, "high": 24, "maximum": 47}, + 64: {"low": 8, "normal": 16, "high": 32, "maximum": 63}, + } + for num_cores, levels in cores_mapping.items(): + cpu_count_mock.return_value = num_cores + for level in ("low", "normal", "high", "maximum"): + self.assertEqual( + utils.select_maximum_cpu_core(level), + levels[level], + f"It should be {utils.select_maximum_cpu_core(level)} " + "of {num_cores} cores for '{level}' mode", + ) + + self.assertEqual(utils.select_maximum_cpu_core("invalid"), 1) diff --git a/tests/core/utility.test.py b/tests/core/utility.test.py deleted file mode 100644 index 75a0f0675..000000000 --- a/tests/core/utility.test.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -This is the utility unit testing module -""" - -import sys -import multiprocessing -import unittest -from core import utility - -sys.path.insert(1, '../../') - - -class UtilityTesting(unittest.TestCase): - """ - This is the class that tests the utility module functions. - """ - - def test_sort_dictionary(self): - """Tests if the function sorts the input dictionary.""" - input_dict = { - 'a': 1, - 'c': 3, - 'd': 23, - 'b': 2, - } - sorted_dict = { - 'a': 1, - 'b': 2, - 'c': 3, - 'd': 23, - } - self.assertDictEqual(utility.sort_dictionary(input_dict), sorted_dict) - - def test_select_maximum_cpu_core(self): - """Tests if it selects the proper amount of cpu's""" - - num_cores = int(multiprocessing.cpu_count()) - 1 - - self.assertNotEqual(utility.select_maximum_cpu_core('maximum'), 3) - self.assertEqual(utility.select_maximum_cpu_core('max'), 1) - self.assertEqual(utility.select_maximum_cpu_core('maximum'), num_cores) - self.assertGreaterEqual(utility.select_maximum_cpu_core('high'), 1) - self.assertGreaterEqual(utility.select_maximum_cpu_core('normal'), 1) - self.assertGreaterEqual(utility.select_maximum_cpu_core('low'), 1) - self.assertEqual(utility.select_maximum_cpu_core('some rand value'), 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/lib/payloads/test_passwords.py b/tests/lib/payloads/test_passwords.py new file mode 100644 index 000000000..c819478ab --- /dev/null +++ b/tests/lib/payloads/test_passwords.py @@ -0,0 +1,22 @@ +from collections import Counter + +import pytest + +from tests.common import TestCase + + +class TestPasswords(TestCase): + top_1000_common_passwords_path = "lib/payloads/passwords/top_1000_common_passwords.txt" + + @pytest.mark.xfail(reason="It currently contains 1001 passwords.") + def test_top_1000_common_passwords(self): + with open(self.nettacker_path / self.top_1000_common_passwords_path) as top_1000_file: + top_1000_passwords = [line.strip() for line in top_1000_file.readlines()] + + self.assertEqual(len(top_1000_passwords), 1000, "There should be exactly 1000 passwords") + + self.assertEqual( + len(set(top_1000_passwords)), + len(top_1000_passwords), + f"The passwords aren't unique: {Counter(top_1000_passwords).most_common(1)[0][0]}", + ) diff --git a/version.txt b/version.txt deleted file mode 100644 index 925f5e795..000000000 --- a/version.txt +++ /dev/null @@ -1 +0,0 @@ -0.3.3 TRENT