diff --git a/.idea/deployment.xml b/.idea/deployment.xml index 5626784..df88304 100644 --- a/.idea/deployment.xml +++ b/.idea/deployment.xml @@ -1,11 +1,11 @@ - + - + - + diff --git a/.idea/php.xml b/.idea/php.xml index d6dbc40..4c5432f 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -1,5 +1,10 @@ + + + + + diff --git a/bin/console b/bin/console index 5830058..b6b3265 100755 --- a/bin/console +++ b/bin/console @@ -1,10 +1,12 @@ #!/usr/bin/keyhelp-php81 runCommand(); -} catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); + $app = new CLIController(config: $config, argumentsCount: count(value: $arguments), arguments: $arguments); +} catch (DependencyException|NotFoundException|Exception $e) { + echo $e->getMessage() . PHP_EOL; exit(1); -} catch (Exception $e) { - echo $e->getMessage(); } +$app->runCommand(); + /** * @param String $message @@ -89,7 +90,7 @@ try { * * @return bool */ -function confirm(String $message = 'Are you sure? ', array $options = ['y', 'n'], string $default ='n'): bool +function confirm(string $message = 'Are you sure? ', array $options = ['y', 'n'], string $default = 'n'): bool { // first $options means true, any other false echo $message, ' ('; diff --git a/composer.json b/composer.json index ec9b5a2..e431325 100644 --- a/composer.json +++ b/composer.json @@ -11,16 +11,17 @@ "minimum-stability": "stable", "prefer-stable": true, "require": { - "php": ">=8.1", - "ext-curl": "*", - "ext-json": "*", - "ext-pdo": "*", - "arubacao/tld-checker": "^1.2", - "monolog/monolog": "^2.3", - "php-di/php-di": "^6.3", - "phplucidframe/console-table": "^1.2", - "zircote/swagger-php": "^4.2" - }, + "php": ">=8.1", + "ext-curl": "*", + "ext-json": "*", + "ext-pdo": "*", + "arubacao/tld-checker": "^1.2", + "monolog/monolog": "^2.3", + "php-di/php-di": "^6.3", + "phplucidframe/console-table": "^1.2", + "squizlabs/php_codesniffer": "^3.7", + "zircote/swagger-php": "^4.2" + }, "config": { "optimize-autoloader": true, "preferred-install": { @@ -39,6 +40,7 @@ } }, "require-dev": { - "phpunit/phpunit": "^9.5" - } + "escapestudios/symfony2-coding-standard": "3.x-dev", + "phpunit/phpunit": "^9.5" + } } diff --git a/composer.lock b/composer.lock index a3ee6d7..c374166 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f7096254eb316738d722dfff777682ef", + "content-hash": "ebdf6dd62a66775d7344acac9e59a5ce", "packages": [ { "name": "arubacao/tld-checker", @@ -745,6 +745,62 @@ }, "time": "2021-07-14T16:46:02+00:00" }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.7.1", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2022-06-18T07:21:10+00:00" + }, { "name": "symfony/finder", "version": "v6.0.3", @@ -1109,6 +1165,64 @@ ], "time": "2022-03-03T08:28:38+00:00" }, + { + "name": "escapestudios/symfony2-coding-standard", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/djoos/Symfony-coding-standard.git", + "reference": "5cc7ad11da242182d9776b98b950d5565c32acd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/djoos/Symfony-coding-standard/zipball/5cc7ad11da242182d9776b98b950d5565c32acd0", + "reference": "5cc7ad11da242182d9776b98b950d5565c32acd0", + "shasum": "" + }, + "require": { + "squizlabs/php_codesniffer": "^3.3.1" + }, + "conflict": { + "squizlabs/php_codesniffer": "<3 || >=4" + }, + "require-dev": { + "phpunit/phpunit": "^5.0 || ^6.0 || ^7.0" + }, + "default-branch": true, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Joos", + "email": "iam@davidjoos.com" + }, + { + "name": "Community contributors", + "homepage": "https://github.com/djoos/Symfony-coding-standard/graphs/contributors" + } + ], + "description": "CodeSniffer ruleset for the Symfony 2+ coding standard", + "homepage": "https://github.com/djoos/Symfony-coding-standard", + "keywords": [ + "Coding Standard", + "Symfony2", + "phpcs", + "symfony" + ], + "support": { + "issues": "https://github.com/djoos/Symfony-coding-standard/issues", + "source": "https://github.com/djoos/Symfony-coding-standard" + }, + "time": "2021-03-24T15:17:16+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.11.0", @@ -3058,7 +3172,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "escapestudios/symfony2-coding-standard": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": { diff --git a/src/Controller/BindAPI.php b/src/Controller/BindAPI.php index ead47f2..261dba5 100755 --- a/src/Controller/BindAPI.php +++ b/src/Controller/BindAPI.php @@ -4,17 +4,6 @@ namespace App\Controller; error_reporting(error_level: E_ALL); - -define(constant_name: 'COLOR_RED', value: "\033[31m"); -define(constant_name: 'COLOR_GREEN', value: "\033[32m"); -define(constant_name: 'COLOR_YELLOW', value: "\033[33m"); -define(constant_name: 'COLOR_BLUE', value: "\033[34m"); -define(constant_name: 'COLOR_DEFAULT', value: "\033[39m"); - - -use App\Entity\Domain; -use App\Entity\Nameserver; -use App\Entity\Panel; use App\Repository\ApikeyRepository; use App\Repository\DomainRepository; use App\Repository\NameserverRepository; @@ -22,33 +11,24 @@ use App\Repository\PanelRepository; use Arubacao\TldChecker\Validator; use DI\Container; use DI\ContainerBuilder; -use DI\DependencyException; -use DI\NotFoundException; -use LucidFrame\Console\ConsoleTable; use Monolog\Formatter\LineFormatter; use Monolog\Handler\StreamHandler; use Monolog\Logger; -use UnhandledMatchError; use function DI\autowire; -if (php_sapi_name() !== 'cli') { - exit; -} /** * */ class BindAPI { - private Logger $log; - private ApiController $apiController; - private ApikeyRepository $apikeyRepository; - private DomainController $domainController; - private DomainRepository $domainRepository; - private NameserverController $nameserverController; - private NameserverRepository $nameserverRepository; - private PanelController $panelController; - private PanelRepository $panelRepository; + public Logger $log; + public ApiController $apiController; + public ApikeyRepository $apikeyRepository; + public DomainController $domainController; + public DomainRepository $domainRepository; + public NameserverRepository $nameserverRepository; + public PanelRepository $panelRepository; private Container $container; @@ -57,7 +37,7 @@ class BindAPI * @throws \DI\NotFoundException * @throws \Exception */ - public function __construct(private readonly array $config, private readonly int $argumentsCount, private readonly array $arguments) + public function __construct(private readonly array $config) { $dateFormat = "Y:m:d H:i:s"; $output = "%datetime% %channel%.%level_name% %message%\n"; // %context% %extra% @@ -83,279 +63,13 @@ class BindAPI $this->apiController = $this->container->get(name: ApiController::class); $this->domainController = $this->container->get(name: DomainController::class); $this->domainRepository = $this->container->get(name: DomainRepository::class); - $this->nameserverController = $this->container->get(name: NameserverController::class); $this->nameserverRepository = $this->container->get(name: NameserverRepository::class); - $this->panelController = $this->container->get(name: PanelController::class); $this->panelRepository = $this->container->get(name: PanelRepository::class); $this->apikeyRepository = $this->container->get(name: ApikeyRepository::class); - - //$dotenv->required(['DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS']); - } - function runCommand() - { - if ($this->config['debug']) { - $this->log->debug(message: "runCommand()"); - } - if ($this->argumentsCount < 1) { - $this->showUsage(); - exit(0); - } - - if (str_contains(haystack: $this->arguments[0], needle: ':')) { - [$command, $subcommand] = explode(separator: ':', string: $this->arguments[0]); - } else { - $command = $this->arguments[0]; - $subcommand = ''; - } - - try { - match ($command) { - 'check' => $this->handleChecks(subcommand: $subcommand), - 'panels' => $this->handlePanels(subcommand: $subcommand), - 'apikeys' => $this->handleApiKeys(subcommand: $subcommand), - 'domains' => $this->handleDomains(subcommand: $subcommand), - 'nameservers' => $this->handleNameservers(subcommand: $subcommand) - }; - } catch (UnhandledMatchError) { - echo 'Unknown command: ' . $command . PHP_EOL; - exit(1); - } - } - - - /** - * @return void - */ - function showUsage(): void - { - if ($this->config['debug']) { - $this->log->debug(message: "showUsage()"); - } - - echo COLOR_YELLOW . 'Usage:' . PHP_EOL; - echo COLOR_DEFAULT . "\t./bin/console {options} {arguments}" . PHP_EOL . PHP_EOL; - - echo COLOR_YELLOW . 'Options:' . PHP_EOL; - echo COLOR_GREEN . "\t-v, --version\t\t" . COLOR_DEFAULT . "Display the version of the API" . PHP_EOL; - echo COLOR_GREEN . "\t-V, --verbose\t\t" . COLOR_DEFAULT . "All :lists command are auto-verbose" . PHP_EOL . PHP_EOL; - - echo COLOR_YELLOW . "check" . COLOR_DEFAULT . "\t health checks the system can perform" . PHP_EOL; - echo COLOR_GREEN . "\t check:permissions" . PHP_EOL; - echo COLOR_GREEN . "\t check:panels {ID} {fix=yes}" . PHP_EOL; - echo COLOR_GREEN . "\t check:domains" . PHP_EOL; - echo COLOR_GREEN . "\t check:showincludes" . COLOR_DEFAULT . " Temporary needed until KeyHelp 22.1" . PHP_EOL; - - - echo COLOR_YELLOW . "panels" . COLOR_DEFAULT . "\t all Keyhelp systems configured" . PHP_EOL; - echo COLOR_GREEN . "\t panels:list" . PHP_EOL; - echo COLOR_GREEN . "\t panels:create {A=} {AAAA=} {apikey=}" . PHP_EOL; - echo COLOR_GREEN . "\t panels:update {name=} {A=} {AAAA=} {apikey=}" . PHP_EOL; - echo COLOR_GREEN . "\t panels:delete " . PHP_EOL; - echo COLOR_GREEN . "\t panels:apiping {}" . PHP_EOL; - - echo COLOR_YELLOW . "nameservers" . COLOR_DEFAULT . " available nameservers" . PHP_EOL; - echo COLOR_GREEN . "\t nameservers:list" . PHP_EOL; - echo COLOR_GREEN . "\t nameservers:create {A=} {AAAA=} {apikey=}" . PHP_EOL; - echo COLOR_GREEN . "\t nameservers:update {name=} {A=} {AAAA=} {apikey=}" . PHP_EOL; - echo COLOR_GREEN . "\t nameservers:delete " . PHP_EOL; - echo COLOR_GREEN . "\t nameservers:apiping {}" . PHP_EOL; - - echo COLOR_YELLOW . "domains" . COLOR_DEFAULT . " domains this server is responsible for" . PHP_EOL; - echo COLOR_GREEN . "\t domains:list" . PHP_EOL; - echo COLOR_GREEN . "\t domains:create {panel=}" . PHP_EOL; - echo COLOR_GREEN . "\t domains:update {name=} {panel=}" . PHP_EOL; - echo COLOR_GREEN . "\t domains:delete " . PHP_EOL; - echo COLOR_GREEN . "\t domains:dyndns hostname.domain.tld {A=} {AAAA=}" . PHP_EOL; - - echo COLOR_YELLOW . "apikeys" . COLOR_DEFAULT . "\t API keys to access this server" . PHP_EOL; - echo COLOR_GREEN . "\t apikeys:list" . PHP_EOL; - echo COLOR_GREEN . "\t apikeys:create {name=}" . PHP_EOL; - echo COLOR_GREEN . "\t apikeys:update {name=}" . PHP_EOL; - echo COLOR_GREEN . "\t apikeys:delete " . PHP_EOL; - - echo PHP_EOL . "\033[39me.g. ./bin/console apikeys:list" . PHP_EOL; - } - - function handleChecks(string $subcommand) - { - if ($this->config['debug']) { - $this->log->debug(message: "handleChecks()"); - } - - try { - match ($subcommand) { - 'permissions' => $this->handleCheckPermissions(), - 'panels' => $this->handleCheckPanels(), - 'domains' => $this->handleCheckDomains(), - 'showincludes' => $this->handleCheckShowIncludes(), - }; - } catch (UnhandledMatchError) { - echo 'Unknown action: ' . $subcommand . PHP_EOL; - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - } - } - - /** - * @throws \DI\DependencyException - * @throws \DI\NotFoundException - */ - function handleCheckPermissions() - { - if ($this->config['debug']) { - $this->log->debug(message: "handleCheckPermissions()"); - } - - $this->domainController->checkPermissions(); - } - - /** - * @throws \DI\DependencyException - * @throws \DI\NotFoundException - */ - function handleCheckPanels() - { - if ($this->config['debug']) { - $this->log->debug(message: "handleCheckPanels()"); - } - - $id = intval(value: $this->arguments[1] ?? 0); - - if ($id != 0) { - if ($panel = $this->panelRepository->findByID(id: $id)) { - $this->checkSinglePanel(panel: $panel); - } else { - echo "Unknown panel ID: $id" . PHP_EOL; - } - } else { - echo "check all …" . PHP_EOL; - $panels = $this->panelRepository->findAll(); - foreach ($panels as $panel) { - $this->checkSinglePanel(panel: $panel); - } - } - } - - - /** - * @param \App\Entity\Panel $panel - * - * @return void - */ - public function checkSinglePanel(Panel $panel): void - { - if ($this->config['debug']) { - $this->log->debug(message: "checkSinglePanel()"); - } - - echo COLOR_DEFAULT . 'Keyhelp-Panel: ' . COLOR_YELLOW . $panel->getName(); - - if ($this->config['verbose']) { - if (empty($panel->getA())) { - try { - $panelRequest = $this->apiController->sendCommand( - requestType: 'GET', - serverName : $panel->getName(), - versionIP : 6, - apiKey : $panel->getApikey(), - command : '/server', - serverType : 'panel'); - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - } else { - try { - $panelRequest = $this->apiController->sendCommand( - requestType: 'GET', - serverName : $panel->getName(), - versionIP : 4, - apiKey : $panel->getApikey(), - command : '/server', - serverType : 'panel'); - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(); - } - } - - $panelData = json_decode(json: $panelRequest['data']); - if (!empty($panelData)) { - $panelVersion = $panelData->meta->panel_version; - $responseTime = sprintf("%0.3f", $panelRequest['responseTime']); - } else { - $panelVersion = 'n/a'; - $responseTime = 'n/a'; - } - echo COLOR_DEFAULT . ' KeyHelp version: ' . $panelVersion . " ($responseTime seconds)" . PHP_EOL; - } else { - echo PHP_EOL; - } - - if (empty($panel->getA())) { - try { - $result = $this->apiController->sendCommand( - requestType: 'GET', - serverName : $panel->getName(), - versionIP : 6, - apiKey : $panel->getApikey(), - command : 'domains', - serverType : 'panel' - ); - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - } - } else { - try { - $result = $this->apiController->sendCommand( - requestType: 'GET', - serverName : $panel->getName(), - versionIP : 4, - apiKey : $panel->getApikey(), - command : 'domains', - serverType : 'panel'); - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - } - } - - - if (!empty($result['error'])) { - echo $result['data'] . PHP_EOL; - exit(1); - } - if (!empty($result['data'])) { - $domains = json_decode(json: $result['data']); - } else { - echo 'No domains found' . PHP_EOL; - exit(1); - } - - $maxDomainName = 0; - // TODO this is ugly code ↓↓↓ - foreach ($domains as $domain) { - if ($this->isValidSecondLevelDomain(domainName: $domain->domain, panel: $panel->getName(), parent: $domain->id_parent_domain) && (strlen(string: $domain->domain) > $maxDomainName)) { - $maxDomainName = strlen(string: $domain->domain); - } - } - - $domainCount = 0; - foreach ($domains as $domain) { - if ($this->isValidSecondLevelDomain(domainName: $domain->domain, panel: $panel->getName(), parent: $domain->id_parent_domain)) { - echo COLOR_DEFAULT . " Domain: " . COLOR_YELLOW . str_pad(string: $domain->domain, length: $maxDomainName); - $this->checkNS(domainName: $domain->domain, panel: $panel); - $domainCount++; - } - } - if ($domainCount == 0) { - echo 'No second level domains found.' . COLOR_DEFAULT . PHP_EOL; - } - echo PHP_EOL; - } - + // TODO mode to domainController function isValidSecondLevelDomain(string $domainName, string $panel, int $parent): bool { if ($this->config['debug']) { @@ -386,357 +100,6 @@ class BindAPI } - /** - * @param String $domainName - * @param \App\Entity\Panel $panel - * - * @return void - */ - function checkNS(string $domainName, Panel $panel) - { - if ($this->config['debug']) { - $this->log->debug(message: "checkNS()"); - } - - try { - $nameServers = $this->nameserverRepository->findAll(); - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - foreach ($nameServers as $nameServer) { - try { - echo COLOR_YELLOW . ' ' . $nameServer->getName(); - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - try { - if (!empty($nameServer->getName())) { - $result = $this->apiController->sendCommand( - requestType: 'GET', - serverName : $nameServer->getName(), - versionIP : 6, - apiKey : $nameServer->getApikey(), - command : 'domains/name/' . $domainName, - serverType : 'nameserver'); - } else { - $result = $this->apiController->sendCommand( - requestType: 'GET', - serverName : $nameServer->getName(), - versionIP : 4, - apiKey : $nameServer->getApikey(), - command : 'domains/name/', - serverType : 'nameserver' . $domainName); - } - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - - switch ($result['header']) { - case 200: - echo COLOR_GREEN . ' OK'; - break; - case 404: - echo COLOR_RED . ' ' . $result['header'] . COLOR_DEFAULT; - $arguments = $this->parseArguments(); - if (!empty($arguments['fix']) && $arguments['fix'] == 'yes') { - echo ' trying to fix …'; - $body = [ - 'name' => $domainName, - 'panel' => $panel->getName(), - ]; - try { - if (!empty($nameServer->getAaaa())) { - $create = $this->apiController->sendCommand( - requestType: 'POST', - serverName : $nameServer->getName(), - versionIP : 6, - apiKey : $nameServer->getApikey(), - command : 'domains', - serverType : 'nameserver', - body : $body); - } else { - $create = $this->apiController->sendCommand( - requestType: 'POST', - serverName : $nameServer->getName(), - versionIP : 4, - apiKey : $nameServer->getAPikey(), - command : 'domains', - serverType : 'nameserver', - body : $body); - } - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - if ($create['header'] != 201) { - print_r(value: $create); - die("make error handling"); - } else { - echo COLOR_GREEN . 'OK' . COLOR_DEFAULT; - } - } - break; - default: - echo 'Server error' . PHP_EOL; - exit(1); - } - } - echo PHP_EOL; - } - - /** - * @return array - */ - public function parseArguments(): array - { - if ($this->config['debug']) { - $this->log->debug(message: "parseArguments()"); - } - - $arguments = []; - foreach ($this->arguments as $argument) { - if (str_contains(haystack: $argument, needle: '=')) { - [$key, $value] = explode(separator: '=', string: $argument); - $arguments[strtolower(string: $key)] = $value; - } else { - $arguments[strtolower(string: $argument)] = $argument; - } - } - return $arguments; - } - - /** - * @param string $subcommand - * - * @return void - */ - public function handlePanels(string $subcommand): void - { - if ($this->config['debug']) { - $this->log->debug(message: "handlePanels()"); - } - - try { - match ($subcommand) { - 'create' => $this->handlePanelsCreate(), - 'list' => $this->handlePanelsList(), - 'update' => $this->handlePanelsUpdate(), - 'delete' => $this->handlePanelsDelete(), - 'apiping' => $this->handleAPIPing(type: 'panel') - }; - } catch (UnhandledMatchError) { - echo 'Unknown action: ' . $subcommand . PHP_EOL; - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - } - } - - /** - * @return void - */ - function handlePanelsCreate(): void - { - if ($this->config['debug']) { - $this->log->debug(message: "handlePanelsCreate()"); - } - - $name = $this->arguments[1] ?? ''; - if (empty($name)) { - echo 'You need to supply the panel name.' . PHP_EOL; - exit(1); - } - - $filteredName = filter_var(value: $name, filter: FILTER_VALIDATE_DOMAIN, options: FILTER_FLAG_HOSTNAME); - if (!empty($filteredName) && str_contains(haystack: $filteredName, needle: '.')) { - $name = $filteredName; - } else { - echo "$name is no valid DNS domain name." . PHP_EOL; - exit(1); - } - - $arguments = $this->parseArguments(); - - $a = $arguments['a'] ?? ''; - $aaaa = $arguments['aaaa'] ?? ''; - if (empty($a) && empty($aaaa)) { - echo 'At least one IP address is required.' . PHP_EOL; - exit(0); - } - $apikey = $arguments['apikey'] ?? ''; - - try { - if ($this->panelRepository->findByName(name: $name)) { - echo "Panel: $name already exists." . PHP_EOL; - exit(1); - } else { - $result = $this->panelRepository->insert(name: $name, a: $a, aaaa: $aaaa, apikey: $apikey); - echo "Panel $name has been created with id $result" . PHP_EOL; - exit(0); - } - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - } - - /** - * @return void - */ - function handlePanelsList(): void - { - if ($this->config['debug']) { - $this->log->debug(message: "handlePanelsList()"); - } - - echo 'All available panels:' . PHP_EOL; - try { - $panels = $this->panelRepository->findAll(); - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - } - if (!empty($panels)) { - $table = new ConsoleTable(); - $table->setHeaders(content: ['ID', 'Name', 'A', 'AAAA', 'API Key']); - foreach ($panels as $panel) { - $row = []; - try { - $token = strtok(string: $panel->getApikey(), token: '.'); - $row[] = $panel->getID(); - $row[] = $panel->getName(); - $row[] = $panel->getA(); - $row[] = $panel->getAaaa(); - $row[] = $token; - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - $table->addRow(data: $row); - } - $table->setPadding(value: 2); - $table->display(); - } else { - echo 'No panels found.' . PHP_EOL; - exit(1); - } - exit(0); - } - - - /** - * @throws \DI\DependencyException - * @throws \DI\NotFoundException - */ - function handlePanelsUpdate() - { - if ($this->config['debug']) { - $this->log->debug(message: "handlePanelsUpdate()"); - } - - $arguments = $this->parseArguments(); - - $id = intval(value: $this->arguments[1]) ?? 0; - $name = $arguments['name'] ?? ''; - $a = $arguments['a'] ?? ''; - $aaaa = $arguments['aaaa'] ?? ''; - $apikey = $arguments['apikey'] ?? ''; - - if ($id == 0) { - echo 'An ID is required' . PHP_EOL; - exit(1); - } - if (!$this->panelRepository->findByID(id: $id)) { - echo "Panel with ID : $id doesn't exist." . PHP_EOL; - exit(1); - } - if ($this->panelRepository->update(id: $id, name: $name, a: $a, aaaa: $aaaa, apikey: $apikey) !== false) { - echo 'Panel has been updated' . PHP_EOL; - } else { - echo 'Error while updating domain server.' . PHP_EOL; - } - } - - /** - * @throws \DI\DependencyException - * @throws \DI\NotFoundException - */ - function handlePanelsDelete() - { - if ($this->config['debug']) { - $this->log->debug(message: "handlePanelsDelete()"); - } - - if (empty($this->arguments[1])) { - echo "You need to supply an ID." . PHP_EOL; - exit(1); - } - - $id = intval(value: $this->arguments[1]) ?? 0; - if ($id == 0) { - echo "Panel with ID $id not found." . PHP_EOL; - exit(1); - } - if (!$this->panelRepository->findByID(id: $id)) { - echo "There is no panel with ID $id." . PHP_EOL; - exit(1); - } - $this->panelRepository->delete(id: $id); - echo "The panel with ID $id has been deleted." . PHP_EOL; - } - - /** - * @throws \DI\NotFoundException - * @throws \DI\DependencyException - */ - function handleAPIPing(string $type) - { - if ($this->config['debug']) { - $this->log->debug(message: "handleApiPing()"); - } - - $error = false; - - $id = $this->getId(); - - if ($id != 0) { - if ($type == 'panel') { - $server = $this->panelController->findByID(id: $id); - } else { - $server = $this->nameserverController->findByID(id: $id); - } - - if ($server) { - if (!$this->checkPing(server: $server, type: $type)) { - $error = true; - } - } else { - if ($this->config['verbose']) { - echo "Unknown $type ID: $id" . PHP_EOL; - } - $error = true; - } - } else { - if ($type == 'panel') { - $servers = $this->panelRepository->findAll(); - } else { - $servers = $this->nameserverRepository->findAll(); - } - foreach ($servers as $server) { - if (!$this->checkPing(server: $server, type: $type)) { - $error = true; - } - } - } - echo PHP_EOL; - if ($error) { - exit(1); - } else { - exit(0); - } - } - /** * @return int|void */ @@ -754,664 +117,4 @@ class BindAPI } return $id; } - - /** - * @param \App\Entity\Panel|\App\Entity\Nameserver $server - * @param String $type - * - * @return bool - */ - public function checkPing(Panel|Nameserver $server, string $type): bool - { - $error = false; - - try { - if ($type == 'nameserver') { - $maxName = $this->nameserverRepository->getLongestEntry(field: 'name'); - $maxA = $this->nameserverRepository->getLongestEntry(field: 'a'); - $maxAAAA = $this->nameserverRepository->getLongestEntry(field: 'aaaa'); - } else { - $maxName = $this->panelRepository->getLongestEntry(field: 'name'); - $maxA = $this->panelRepository->getLongestEntry(field: 'a'); - $maxAAAA = $this->panelRepository->getLongestEntry(field: 'aaaa'); - } - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - - if ($this->config['verbose']) { - echo COLOR_YELLOW . str_pad(string: $server->getName(), length: $maxName); - } - - $a = $server->getA() ?? ''; - if (!empty($a)) { - if ($this->config['verbose']) { - echo COLOR_DEFAULT . ' ' . str_pad(string: $a, length: $maxA, pad_type: STR_PAD_LEFT) . ' '; - } - try { - if ($result = $this->apiController->sendCommand( - requestType: 'GET', - serverName : $server->getName(), - versionIP : 4, - apiKey : $server->getApikey(), - command : 'ping', - serverType : $type)) { - if ($this->config['verbose']) { - if ($result['data'] == 'pong') { - echo COLOR_GREEN . $result['data']; - } else { - echo COLOR_BLUE . 'skip'; - } - } - } else { - $error = true; - } - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - } - } - $aaaa = $server->getAaaa() ?? ''; - if (!empty($aaaa)) { - if ($this->config['verbose']) { - echo COLOR_DEFAULT . ' ' . str_pad(string: $aaaa, length: $maxAAAA, pad_type: STR_PAD_LEFT) . ' '; - } - try { - if ($result = $this->apiController->sendCommand( - requestType: 'GET', - serverName : $server->getName(), - versionIP : 6, - apiKey : $server->getApikey(), - command : 'ping', - serverType : $type)) { - if ($this->config['verbose']) { - if ($result['data'] == 'pong') { - echo COLOR_GREEN . $result['data']; - } else { - echo COLOR_BLUE . $result['data']; // TODO 'skip'; - } - } - } else { - $error = true; - } - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - } - if ($this->config['verbose']) { - echo PHP_EOL; - } - return $error; - } - - /** - * @param string $subcommand - * - * @return void - */ - public function handleApiKeys(string $subcommand): void - { - try { - match ($subcommand) { - 'create' => $this->handleApikeysCreate(), - 'list' => $this->handleApikeysList(), - 'update' => $this->handleApikeysUpdate(), - 'delete' => $this->handleApikeysDelete(), - }; - } catch (UnhandledMatchError) { - echo 'Unknown action: ' . $subcommand . PHP_EOL; - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - } - - /** - * @return void - */ - function handleApikeysCreate(): void - { - $arguments = $this->parseArguments(); - $name = $arguments['name'] ?? ''; - - try { - $result = $this->apikeyRepository->create(name: $name); - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - echo 'API key ' . $result['row'] . ' has been generated. Store it in a save place, it cannot be recovered.' . PHP_EOL; - echo "\033[32m\t" . $result['tokenPrefix'] . '.' . $result['key'] . PHP_EOL; - exit(0); - } - - /** - * @return void - */ - function handleApikeysList(): void - { - echo 'All valid API keys:' . PHP_EOL; - try { - $keys = $this->apikeyRepository->findAll(); - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - if (!empty($keys)) { - $table = new ConsoleTable(); - $table->setHeaders(content: ['ID', 'Name', 'API key prefix']); - foreach ($keys as $key) { - $row = []; - try { - $row[] = $key->getID(); - $row[] = $key->getName(); - $row[] = $key->getApiTokenPrefix(); - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - $table->addRow(data: $row); - } - $table->setPadding(value: 2); - $table->display(); - } else { - echo 'No keys found.' . PHP_EOL; - } - exit(0); - } - - - /** - * @throws \DI\DependencyException - * @throws \DI\NotFoundException - */ - function handleApikeysUpdate() - { - $arguments = $this->parseArguments(); - - $id = $this->arguments[1] ?? 0; - $name = $arguments['name'] ?? ''; - - if ($id == 0) { - echo 'An ID is required' . PHP_EOL; - exit(1); - } - if (empty($name)) { - echo 'You need to supply the new name.' . PHP_EOL; - exit(1); - } - if (!$this->apikeyRepository->findByID(id: intval(value: $id))) { - echo "Apikeys with ID : $id doesn't exist." . PHP_EOL; - exit(1); - } - if ($this->apikeyRepository->update(id: intval(value: $id), name: $name) !== false) { - echo 'Apikey has been updated' . PHP_EOL; - } else { - echo 'Error while updating apikey.' . PHP_EOL; - } - } - - - /** - * @return void - */ - function handleApikeysDelete(): void - { - $id = intval(value: $this->arguments[1] ?? 0); - if ($id == 0) { - echo 'You need to add the ID of the API key.' . PHP_EOL; - exit(1); - } - try { - if ($this->apikeyRepository->findByID(id: $id)) { - $this->apikeyRepository->delete(id: $id); - echo 'API key ' . $id . ' has been deleted.' . PHP_EOL; - exit(0); - } else { - echo 'Unknown ID: ' . $id . PHP_EOL; - exit(1); - } - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - } - } - - /** - * @param string $subcommand - * - * @return void - */ - public function handleDomains(string $subcommand): void - { - try { - match ($subcommand) { - 'list' => $this->handleDomainsList(), - 'create' => $this->handleDomainsCreate(), - 'update' => $this->handleDomainsUpdate(), - 'delete' => $this->handleDomainsDelete(), - 'dyndns' => $this->handleDomainsDynDns(), - }; - } catch (UnhandledMatchError) { - echo "Unknown Command: $subcommand" . PHP_EOL; - exit(1); - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - } - } - - /** - * @return void - */ - function handleDomainsList(): void - { - echo 'All available domains:' . PHP_EOL; - try { - $domains = $this->domainRepository->findAll(); - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - if (!empty($domains)) { - $table = new ConsoleTable(); - $table->setHeaders(content: ['ID', 'Name', 'Panel', 'Type']); - /** @var Domain $domain */ - foreach ($domains as $domain) { - $row = []; - try { - $row[] = $domain->getId(); - $row[] = $domain->getName(); - $row[] = $domain->getPanel(); - $row[] = $this->domainController->isMasterZone(domain: $domain) ? 'MASTER' : 'SLAVE'; - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - } - $table->addRow(data: $row); - } - $table->setPadding(value: 2); - $table->display(); - } - exit(0); - } - - - /** - * @return void - */ - function handleDomainsCreate(): void - { - $name = $this->arguments[1] ?? ""; - if (empty($name)) { - echo 'You need to supply the domain name.' . PHP_EOL; - exit(1); - } - - $filteredName = filter_var(value: $name, filter: FILTER_VALIDATE_DOMAIN, options: FILTER_FLAG_HOSTNAME); - if (!empty($filteredName) && str_contains(haystack: $filteredName, needle: '.')) { - $name = $filteredName; - } else { - echo "$name is no valid DNS domain name." . PHP_EOL; - exit(1); - } - - $arguments = $this->parseArguments(); - $panel = $arguments['panel'] ?? ''; - - if (empty($panel)) { - echo 'You need to supply the panel name.' . PHP_EOL; - exit(1); - } - - try { - if ($this->domainRepository->findByName(name: $name)) { - echo "Domain: $name already exists." . PHP_EOL; - exit(1); - } else { - if (!$this->panelRepository->findByName(name: $panel)) { - echo 'Unknown panel: ' . $panel; - exit(1); - } - $domain = new Domain(name: $name, panel: $panel); - $result = $this->domainRepository->insert(domain: $domain); - echo "Domain $name has been created with id $result" . PHP_EOL; - $this->domainController->createSlaveZoneFile(domain: $domain); - exit(0); - } - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - } - } - - /** - * @throws \DI\DependencyException - * @throws \DI\NotFoundException - */ - function handleDomainsUpdate() - { - $arguments = $this->parseArguments(); - - $id = intval(value: $this->arguments[1] ?? 0); - $name = $arguments['name'] ?? ''; - $panelName = $arguments['panel'] ?? ''; - - if ($id == 0) { - echo 'An ID is required' . PHP_EOL; - exit(1); - } - if (!$domain = $this->domainRepository->findByID(id: $id)) { - echo "Domain with ID : $id doesn't exist." . PHP_EOL; - exit(1); - } - - if (!empty($panelName)) { - $panel = $this->panelRepository->findByName(name: $panelName); - } - - if (empty($name) && empty($panel)) { - echo 'No name or panel given, just recreate the config file' . PHP_EOL; - $this->domainController->updateSlaveZones(); - exit(1); - } - $newDomain = new Domain(name: $name, panel: $panelName, id: $domain->getId()); - - if ($this->domainRepository->update(domain: $newDomain) !== false) { - echo 'Domain server has been updated' . PHP_EOL; - $this->domainController->updateSlaveZones(); - } else { - echo 'Error while updating domain server.' . PHP_EOL; - } - } - - - /** - * @throws \DI\DependencyException - * @throws \DI\NotFoundException - */ - function handleDomainsDelete() - { - if (empty($this->arguments[1])) { - echo "You need to supply an ID." . PHP_EOL; - exit(1); - } - - $id = intval(value: $this->arguments[1]) ?? 0; - if ($id == 0) { - echo "Domain with ID $id not found." . PHP_EOL; - exit(1); - } - if (!$domain = $this->domainRepository->findByID(id: $id)) { - echo "There is no domain with ID $id." . PHP_EOL; - exit(1); - } - $this->domainRepository->delete(domain: $domain); - $this->domainController->deleteZone(domain: $domain); - echo "The domain with ID $id has been deleted." . PHP_EOL; - } - - - /** - * @param string $subcommand - * - * @return void - */ - public - function handleNameservers(string $subcommand): void - { - try { - match ($subcommand) { - 'create' => $this->handleNameserversCreate(), - 'list' => $this->handleNameserversList(), - 'update' => $this->handleNameserversUpdate(), - 'delete' => $this->handleNameserversDelete(), - 'apiping' => $this->handleAPIPing(type: 'nameserver') - }; - } catch (UnhandledMatchError) { - echo 'Unknown action: ' . $subcommand . PHP_EOL; - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - } - } - - - /** - * @return void - */ - function handleNameserversCreate(): void - { - $name = $this->arguments[1] ?? ''; - if (empty($name)) { - echo 'You need to supply the nameserver name.' . PHP_EOL; - exit(1); - } - - $filteredName = filter_var(value: $name, filter: FILTER_VALIDATE_DOMAIN); - if (!empty($filteredName) && str_contains(haystack: $filteredName, needle: '.')) { - $name = $filteredName; - } else { - echo "$name is no valid nameserver name." . PHP_EOL; - exit(1); - } - - $arguments = $this->parseArguments(); - - $a = $arguments['a'] ?? ''; - $aaaa = $arguments['aaaa'] ?? ''; - if (empty($a) && empty($aaaa)) { - echo 'At least one IP address is required.' . PHP_EOL; - exit(0); - } - $apikey = $arguments['apikey'] ?? ''; - - try { - if ($this->nameserverRepository->findByName(name: $name)) { - echo "Nameserver: $name already exists." . PHP_EOL; - exit(1); - } else { - $nameserver = new Nameserver(name: $name, a: $a, aaaa: $aaaa, apikey: $apikey); - $result = $this->nameserverRepository->insert(nameserver: $nameserver); - echo "Nameserver $name has been created with id $result" . PHP_EOL; - exit(0); - } - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - } - - - /** - * @return void - */ - function handleNameserversList(): void - { - echo 'All available nameservers:' . PHP_EOL; - try { - $nameservers = $this->nameserverRepository->findAll(); - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - } - if (!empty($nameservers)) { - $table = new ConsoleTable(); - $table->setHeaders(content: ['ID', 'Name', 'A', 'AAAA', 'API Key']); - foreach ($nameservers as $nameserver) { - $row = []; - try { - $token = strtok(string: $nameserver->getApiKey(), token: '.'); - $row[] = $nameserver->getId(); - $row[] = $nameserver->getName(); - $row[] = $nameserver->getA(); - $row[] = $nameserver->getAaaa(); - $row[] = $token; - } catch (DependencyException|NotFoundException $e) { - echo $e->getMessage(); - exit(1); - } - $table->addRow(data: $row); - } - $table->setPadding(value: 2); - $table->display(); - } else { - echo 'No nameservers found.' . PHP_EOL; - exit(1); - } - exit(0); - } - - - /** - * @throws \DI\DependencyException - * @throws \DI\NotFoundException - */ - function handleNameserversUpdate() - { - $arguments = $this->parseArguments(); - - $id = $this->arguments[1] ?? 0; - $name = $arguments['name'] ?? ''; - $a = $arguments['a'] ?? ''; - $aaaa = $arguments['aaaa'] ?? ''; - $apikey = $arguments['apikey'] ?? ''; - - if ($id == 0) { - echo 'An ID is required' . PHP_EOL; - exit(1); - } - if (!$this->nameserverRepository->findByID(id: intval(value: $id))) { - echo "Nameserver with ID : $id doesn't exist." . PHP_EOL; - exit(1); - } - if ($this->nameserverRepository->update(id: intval(value: $id), name: $name, a: $a, aaaa: $aaaa, apikey: $apikey) !== false) { - echo 'Nameserver server has been updated' . PHP_EOL; - } else { - echo 'Error while updating nameserver.' . PHP_EOL; - } - } - - - /** - * @throws \DI\DependencyException - * @throws \DI\NotFoundException - */ - function handleNameserversDelete() - { - if (empty($this->arguments[1])) { - echo "You need to supply an ID." . PHP_EOL; - exit(1); - } - - $id = intval(value: $this->arguments[1]) ?? 0; - if ($id == 0) { - echo "Nameserver with ID $id not found." . PHP_EOL; - exit(1); - } - if (!$this->nameserverRepository->findByID(id: $id)) { - echo "There is no nameserver with ID $id." . PHP_EOL; - exit(1); - } - $this->nameserverRepository->delete(id: $id); - echo "The nameserver with ID $id has been deleted." . PHP_EOL; - } - - - /** - * @throws \DI\DependencyException - * @throws \DI\NotFoundException - */ - function handleCheckShowIncludes() - { - $nameservers = $this->nameserverRepository->findAll(); - - echo COLOR_DEFAULT . 'You need to add these lines to ' . COLOR_YELLOW . '/etc/bind/local.bindapi.options' . COLOR_DEFAULT . ' on every panel and make sure' . PHP_EOL; - echo 'that ' . COLOR_YELLOW . 'include "/etc/bind/local.bindapi.options";' . COLOR_DEFAULT . ' exists in ' . COLOR_YELLOW . '/etc/bind/named.conf.options' . COLOR_DEFAULT . '.' . PHP_EOL; - $ip = []; - foreach ($nameservers as $nameserver) { - if (!empty($nameserver->getA())) { - $ip[] = $nameserver->getA(); - } - if (!empty($nameserver->getAaaa())) { - $ip[] = $nameserver->getAaaa(); - } - } - echo PHP_EOL . 'allow-transfer {' . PHP_EOL; - foreach ($ip as $currentIp) - echo "\t$currentIp;" . PHP_EOL; - echo '};'; - echo PHP_EOL . 'also-notify {' . PHP_EOL; - foreach ($ip as $currentIp) - echo "\t$currentIp;" . PHP_EOL; - echo '};' . PHP_EOL; - echo PHP_EOL . 'After the modification feel free to run ' . COLOR_YELLOW . 'named-checkconf' . COLOR_DEFAULT . ' to ensure there were no errors.' . PHP_EOL; - echo PHP_EOL . 'Run ' . COLOR_YELLOW . 'rndc reload' . COLOR_DEFAULT . ' to activate the changes' . PHP_EOL; - - - } - - /** - * @throws \DI\DependencyException - * @throws \DI\NotFoundException - */ - function handleCheckDomains() - { - $this->domainController->checkDomains(); - } - - - /** - * @throws \DI\NotFoundException - * @throws \DI\DependencyException - */ - private function handleDomainsDynDns(): void - { - $hostName = $this->arguments[1] ?? ''; - - if (empty($hostName)) { - echo 'You need to supply at least the hostname' . PHP_EOL; - exit(1); - } - - if ($this->config['verbose']) { - echo "Updating DynDNS host: $hostName" . PHP_EOL; - } - - echo 'here'; - $domain = $this->domainRepository->findByHost(host: $hostName); - print_r(value: $domain); - echo 'there'; - // we need the panel who is master for zone - $panel = $this->panelRepository->findByName(name: $domain->getPanel()); - - // which NS belongs to that panel - - - if (!empty($panel->getAaaa())) { - $result = $this->apiController->sendCommand( - requestType: 'POST', - serverName : $panel->getName(), - versionIP : 6, - apiKey : $panel->getApikey(), - command : 'dyndns/' . $hostName, - serverType : 'nameserver'); - } else { - $result = $this->apiController->sendCommand( - requestType: 'POST', - serverName : $panel->getName(), - versionIP : 4, - apiKey : $panel->getApikey(), - command : 'dyndns/' . $hostName, - serverType : 'nameserver'); - } - - if ($result['header'] == 200) { - if ($this->config['verbose']) { - $data = $result['data']; - $decodedData = json_decode(json: $data, associative: true); - echo $decodedData['message'] . PHP_EOL; - } - } else { - echo 'Something went wrong:' . PHP_EOL; - print_r(value: $result); - exit(1); - } - exit(0); - } } diff --git a/src/Controller/CLIController.php b/src/Controller/CLIController.php new file mode 100644 index 0000000..edcd7f8 --- /dev/null +++ b/src/Controller/CLIController.php @@ -0,0 +1,1311 @@ +bindAPI = new bindAPI(config: $config); + } + + + function runCommand(): void + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "runCommand()"); + } + if ($this->argumentsCount < 1) { + $this->showUsage(); + exit(0); + } + + if (str_contains(haystack: $this->arguments[0], needle: ':')) { + [$command, $subcommand] = explode(separator: ':', string: $this->arguments[0]); + } else { + $command = $this->arguments[0]; + $subcommand = ''; + } + + try { + match ($command) { + 'check' => $this->handleChecks(subcommand: $subcommand), + 'panels' => $this->handlePanels(subcommand: $subcommand), + 'apikeys' => $this->handleApiKeys(subcommand: $subcommand), + 'domains' => $this->handleDomains(subcommand: $subcommand), + 'nameservers' => $this->handleNameservers(subcommand: $subcommand) + }; + } catch (UnhandledMatchError) { + echo 'Unknown command: ' . $command . PHP_EOL; + exit(1); + } + } + + + /** + * @return void + */ + function showUsage(): void + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "showUsage()"); + } + + echo COLOR_YELLOW . 'Usage:' . PHP_EOL; + echo COLOR_DEFAULT . "\t./bin/console {options} {arguments}" . PHP_EOL . PHP_EOL; + + echo COLOR_YELLOW . 'Options:' . PHP_EOL; + echo COLOR_GREEN . "\t-v, --version\t\t" . COLOR_DEFAULT . "Display the version of the API" . PHP_EOL; + echo COLOR_GREEN . "\t-V, --verbose\t\t" . COLOR_DEFAULT . "All :lists command are auto-verbose" . PHP_EOL . PHP_EOL; + + echo COLOR_YELLOW . "check" . COLOR_WHITE . "\t health checks the system can perform" . PHP_EOL; + echo COLOR_GREEN . "\t check:permissions" . PHP_EOL; + echo COLOR_GREEN . "\t check:panels {ID} {fix=yes}" . PHP_EOL; + echo COLOR_GREEN . "\t check:domains" . PHP_EOL; + echo COLOR_GREEN . "\t check:showincludes" . COLOR_WHITE . " Shows needed setting on panels" . PHP_EOL; + echo COLOR_GREEN . "\t check:setup " . COLOR_WHITE . " Adapt filesystem permissions (requires elaborated permissions)" . PHP_EOL; + + + echo COLOR_YELLOW . "panels" . COLOR_WHITE . "\t all Keyhelp systems configured" . PHP_EOL; + echo COLOR_GREEN . "\t panels:list" . PHP_EOL; + echo COLOR_GREEN . "\t panels:create {A=} {AAAA=} {apikey=}" . PHP_EOL; + echo COLOR_GREEN . "\t panels:update {name=} {A=} {AAAA=} {apikey=}" . PHP_EOL; + echo COLOR_GREEN . "\t panels:delete " . PHP_EOL; + echo COLOR_GREEN . "\t panels:apiping {}" . PHP_EOL; + + echo COLOR_YELLOW . "nameservers" . COLOR_WHITE . " available nameservers" . PHP_EOL; + echo COLOR_GREEN . "\t nameservers:list" . PHP_EOL; + echo COLOR_GREEN . "\t nameservers:create {A=} {AAAA=} {apikey=}" . PHP_EOL; + echo COLOR_GREEN . "\t nameservers:update {name=} {A=} {AAAA=} {apikey=}" . PHP_EOL; + echo COLOR_GREEN . "\t nameservers:delete " . PHP_EOL; + echo COLOR_GREEN . "\t nameservers:apiping {}" . PHP_EOL; + + echo COLOR_YELLOW . "domains" . COLOR_WHITE . " domains this server is responsible for" . PHP_EOL; + echo COLOR_GREEN . "\t domains:list" . PHP_EOL; + echo COLOR_GREEN . "\t domains:create {panel=}" . PHP_EOL; + echo COLOR_GREEN . "\t domains:update {name=} {panel=}" . PHP_EOL; + echo COLOR_GREEN . "\t domains:delete " . PHP_EOL; + echo COLOR_GREEN . "\t domains:dyndns hostname.domain.tld {A=} {AAAA=}" . PHP_EOL; + + echo COLOR_YELLOW . "apikeys" . COLOR_WHITE . "\t API keys to access this server" . PHP_EOL; + echo COLOR_GREEN . "\t apikeys:list" . PHP_EOL; + echo COLOR_GREEN . "\t apikeys:create {name=}" . PHP_EOL; + echo COLOR_GREEN . "\t apikeys:update {name=}" . PHP_EOL; + echo COLOR_GREEN . "\t apikeys:delete " . PHP_EOL; + + echo PHP_EOL . "\033[39me.g. ./bin/console apikeys:list" . PHP_EOL; + } + + function handleChecks(string $subcommand): void + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "handleChecks()"); + } + + match ($subcommand) { + 'permissions' => $this->handleCheckPermissions(), + 'panels' => $this->handleCheckPanels(), + 'domains' => $this->handleCheckDomains(), + 'showincludes' => $this->handleCheckShowIncludes(), + 'setup' => $this->handleCheckSetup(), + }; + } + + + /** + */ + function handleCheckPermissions(): void + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "handleCheckPermissions()"); + } + + if (!$this->bindAPI->domainController->checkPermissions()) { + if ($this->config['verbose']) { + echo PHP_EOL . COLOR_DEFAULT; + echo 'Missing permissions, please run ' . COLOR_YELLOW . './bin/console check:setup' . COLOR_DEFAULT . ' as root or with sudo.' . PHP_EOL; + } + exit(1); + } else { + exit(0); + } + } + + + function handleCheckSetup(): void + { + if ($this->argumentsCount < 2) { + echo 'You need to supply a username.' . PHP_EOL; + exit(1); + } + $username = $this->arguments[1]; + $uid = posix_getuid(); + if ($uid != 0) { + echo 'You need to run this as root or with sudo.' . PHP_EOL; + exit(1); + } + + // deluser tracer bind + // adduser tracer bind + + // add user to group bind + exec(command: "adduser $username bind", output: $output, result_code: $return); + if ($return != 0) { + echo 'Could not add user to bind group.' . PHP_EOL; + exit(1); + } + + // for /etc/bind/local.zones file + if (!file_exists(filename: $this->bindAPI->domainController->localZoneFile)) { + echo 'Could not find ' . COLOR_YELLOW . $this->bindAPI->domainController->localZoneFile . COLOR_DEFAULT . '.' . PHP_EOL; + echo 'Creating …'; + touch(filename: $this->bindAPI->domainController->localZoneFile); + if (!file_exists(filename: $this->bindAPI->domainController->localZoneFile)) { + echo 'Could not create ' . $this->bindAPI->domainController->localZoneFile . '.' . PHP_EOL; + exit(1); + } else { + echo ' done.' . PHP_EOL; + echo 'Setting owner …'; + if (chown(filename: $this->bindAPI->domainController->localZoneFile, user: 'bind')) { + echo " done." . PHP_EOL; + } + echo 'Setting permissions …'; + if (chmod(filename: $this->bindAPI->domainController->localZoneFile, permissions: 0664)) { + echo " done." . PHP_EOL; + } + } + } + + // /etc/bind/local.zones file must be included in /etc/bind/named.conf.local + $namedConfLocal = file_get_contents(filename: $this->bindAPI->domainController->namedConfLocalFile); + if (str_contains(haystack: $namedConfLocal, needle: $this->bindAPI->domainController->localZoneFile)) { + echo 'Found ' . COLOR_YELLOW . $this->bindAPI->domainController->localZoneFile . COLOR_DEFAULT . ' in ' . COLOR_YELLOW . $this->bindAPI->domainController->namedConfLocalFile . COLOR_DEFAULT . '.' . PHP_EOL; + } else { + echo 'Could not find ' . COLOR_YELLOW . $this->bindAPI->domainController->localZoneFile . COLOR_DEFAULT . ' in ' . COLOR_YELLOW . $this->bindAPI->domainController->namedConfLocalFile . COLOR_DEFAULT . '.' . PHP_EOL; + echo 'Adding …'; + $namedConfLocal .= PHP_EOL . 'include "' . $this->bindAPI->domainController->localZoneFile . '";' . PHP_EOL; + file_put_contents(filename: $this->bindAPI->domainController->namedConfLocalFile, data: $namedConfLocal); + if (str_contains(haystack: $namedConfLocal, needle: $this->bindAPI->domainController->localZoneFile)) { + echo ' done.' . PHP_EOL; + } else { + echo 'Could not add ' . COLOR_YELLOW . $this->bindAPI->domainController->localZoneFile . COLOR_DEFAULT . ' to ' . COLOR_YELLOW . $this->bindAPI->domainController->namedConfLocalFile . COLOR_DEFAULT . '.' . PHP_EOL; + exit(1); + } + } + + // check /etc/bind/zones exists + echo 'Check for ' . COLOR_YELLOW . $this->bindAPI->domainController->localZonesDir . COLOR_DEFAULT . ' …'; + if (is_dir(filename: $this->bindAPI->domainController->localZonesDir)) { + echo " done." . PHP_EOL; + } else { + echo ' Could not find ' . COLOR_YELLOW . $this->bindAPI->domainController->localZonesDir . COLOR_DEFAULT . '.' . PHP_EOL; + echo 'Creating …'; + mkdir(directory: $this->bindAPI->domainController->localZonesDir, permissions: 0775, recursive: true); + echo ' done.' . PHP_EOL; + echo 'Setting owner …'; + if (chown(filename: $this->bindAPI->domainController->localZonesDir, user: 'bind')) { + echo " done." . PHP_EOL; + } + echo 'Setting permissions …'; + if (chmod(filename: $this->bindAPI->domainController->localZonesDir, permissions: 0774)) { + echo " done." . PHP_EOL; + } + } + } + + + /** + */ + function handleCheckPanels(): void + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "handleCheckPanels()"); + } + + $id = intval(value: $this->arguments[1] ?? 0); + + if ($id != 0) { + if ($panel = $this->bindAPI->panelRepository->findByID(id: $id)) { + $this->checkSinglePanel(panel: $panel); + } else { + echo "Unknown panel ID $id" . PHP_EOL; + } + } else { + echo "check all …" . PHP_EOL; + $panels = $this->bindAPI->panelRepository->findAll(); + foreach ($panels as $panel) { + $this->checkSinglePanel(panel: $panel); + } + } + } + + + /** + * @param \App\Entity\Panel $panel + * + * @return void + */ + public function checkSinglePanel(Panel $panel): void + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "checkSinglePanel()"); + } + + echo COLOR_DEFAULT . 'Keyhelp-Panel: ' . COLOR_YELLOW . $panel->getName(); + + if ($this->config['verbose']) { + if (empty($panel->getA())) { + $panelRequest = $this->bindAPI->apiController->sendCommand( + requestType: 'GET', + serverName : $panel->getName(), + versionIP : 6, + apiKey : $panel->getApikey(), + command : '/server', + serverType : 'panel'); + } else { + $panelRequest = $this->bindAPI->apiController->sendCommand( + requestType: 'GET', + serverName : $panel->getName(), + versionIP : 4, + apiKey : $panel->getApikey(), + command : '/server', + serverType : 'panel'); + } + + $panelData = json_decode(json: $panelRequest['data']); + if (!empty($panelData)) { + $panelVersion = $panelData->meta->panel_version; + $responseTime = sprintf("%0.3f", $panelRequest['responseTime']); + } else { + $panelVersion = 'n/a'; + $responseTime = 'n/a'; + } + echo COLOR_DEFAULT . ' KeyHelp version: ' . $panelVersion . " ($responseTime seconds)" . PHP_EOL; + } else { + echo PHP_EOL; + } + + if (empty($panel->getA())) { + $result = $this->bindAPI->apiController->sendCommand( + requestType: 'GET', + serverName : $panel->getName(), + versionIP : 6, + apiKey : $panel->getApikey(), + command : 'domains', + serverType : 'panel' + ); + } else { + $result = $this->bindAPI->apiController->sendCommand( + requestType: 'GET', + serverName : $panel->getName(), + versionIP : 4, + apiKey : $panel->getApikey(), + command : 'domains', + serverType : 'panel'); + } + + + if (!empty($result['error'])) { + echo $result['data'] . PHP_EOL; + exit(1); + } + if (!empty($result['data'])) { + $domains = json_decode(json: $result['data']); + } else { + echo 'No domains found' . PHP_EOL; + exit(1); + } + + $maxDomainName = 0; + // TODO this is ugly code ↓↓↓ + foreach ($domains as $domain) { + if ($this->isValidSecondLevelDomain(domainName: $domain->domain, panel: $panel->getName(), parent: $domain->id_parent_domain) && (strlen(string: $domain->domain) > $maxDomainName)) { + $maxDomainName = strlen(string: $domain->domain); + } + } + + $domainCount = 0; + foreach ($domains as $domain) { + if ($this->isValidSecondLevelDomain(domainName: $domain->domain, panel: $panel->getName(), parent: $domain->id_parent_domain)) { + echo COLOR_DEFAULT . " Domain: " . COLOR_YELLOW . str_pad(string: $domain->domain, length: $maxDomainName); + $this->checkNS(domainName: $domain->domain, panel: $panel); + $domainCount++; + } + } + if ($domainCount == 0) { + echo 'No second level domains found.' . COLOR_DEFAULT . PHP_EOL; + } + echo PHP_EOL; + } + + function isValidSecondLevelDomain(string $domainName, string $panel, int $parent): bool + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "isValidSecondLevelDomain()"); + } + + // subdomain + if ($parent != 0) { + return false; + } + + // system domain + if (str_contains(haystack: $domainName, needle: $panel)) { + return false; + } + + // valid second level domain + if (!Validator::endsWithTld(value: $domainName)) { + return false; + } + + // no second level domain + if (substr_count(haystack: $domainName, needle: '.') > 1) { + return false; + } + + return true; + } + + + /** + * @param String $domainName + * @param \App\Entity\Panel $panel + * + * @return void + */ + function checkNS(string $domainName, Panel $panel): void + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "checkNS()"); + } + + $nameServers = $this->bindAPI->nameserverRepository->findAll(); + foreach ($nameServers as $nameServer) { + echo COLOR_YELLOW . ' ' . $nameServer->getName(); + if (!empty($nameServer->getName())) { + $result = $this->bindAPI->apiController->sendCommand( + requestType: 'GET', + serverName : $nameServer->getName(), + versionIP : 6, + apiKey : $nameServer->getApikey(), + command : 'domains/name/' . $domainName, + serverType : 'nameserver'); + } else { + $result = $this->bindAPI->apiController->sendCommand( + requestType: 'GET', + serverName : $nameServer->getName(), + versionIP : 4, + apiKey : $nameServer->getApikey(), + command : 'domains/name/', + serverType : 'nameserver' . $domainName); + } + + switch ($result['header']) { + case 200: + echo COLOR_GREEN . ' OK'; + break; + case 404: + echo COLOR_RED . ' ' . $result['header'] . COLOR_DEFAULT; + $arguments = $this->parseArguments(); + if (!empty($arguments['fix']) && $arguments['fix'] == 'yes') { + echo ' trying to fix …'; + $body = [ + 'name' => $domainName, + 'panel' => $panel->getName(), + ]; + if (!empty($nameServer->getAaaa())) { + $create = $this->bindAPI->apiController->sendCommand( + requestType: 'POST', + serverName : $nameServer->getName(), + versionIP : 6, + apiKey : $nameServer->getApikey(), + command : 'domains', + serverType : 'nameserver', + body : $body); + } else { + $create = $this->bindAPI->apiController->sendCommand( + requestType: 'POST', + serverName : $nameServer->getName(), + versionIP : 4, + apiKey : $nameServer->getAPikey(), + command : 'domains', + serverType : 'nameserver', + body : $body); + } + if ($create['header'] != 201) { + print_r(value: $create); + die("make error handling"); + } else { + echo COLOR_GREEN . 'OK' . COLOR_DEFAULT; + } + } + break; + default: + echo 'Server error' . PHP_EOL; + exit(1); + } + } + echo PHP_EOL; + } + + /** + * @return array + */ + public function parseArguments(): array + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "parseArguments()"); + } + + $arguments = []; + foreach ($this->arguments as $argument) { + if (str_contains(haystack: $argument, needle: '=')) { + [$key, $value] = explode(separator: '=', string: $argument); + $arguments[strtolower(string: $key)] = $value; + } else { + $arguments[strtolower(string: $argument)] = $argument; + } + } + return $arguments; + } + + /** + * @param string $subcommand + * + * @return void + */ + public function handlePanels(string $subcommand): void + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "handlePanels()"); + } + + match ($subcommand) { + 'create' => $this->handlePanelsCreate(), + 'list' => $this->handlePanelsList(), + 'update' => $this->handlePanelsUpdate(), + 'delete' => $this->handlePanelsDelete(), + 'apiping' => $this->handleAPIPing(type: 'panel') + }; + } + + /** + * @return void + */ + function handlePanelsCreate(): void + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "handlePanelsCreate()"); + } + + $name = $this->arguments[1] ?? ''; + if (empty($name)) { + echo 'You need to supply the panel name.' . PHP_EOL; + exit(1); + } + + $filteredName = filter_var(value: $name, filter: FILTER_VALIDATE_DOMAIN, options: FILTER_FLAG_HOSTNAME); + if (!empty($filteredName) && str_contains(haystack: $filteredName, needle: '.')) { + $name = $filteredName; + } else { + echo "$name is no valid DNS domain name." . PHP_EOL; + exit(1); + } + + $arguments = $this->parseArguments(); + + $a = $arguments['a'] ?? ''; + $aaaa = $arguments['aaaa'] ?? ''; + if (empty($a) && empty($aaaa)) { + echo 'At least one IP address is required.' . PHP_EOL; + exit(0); + } + $apikey = $arguments['apikey'] ?? ''; + + if ($this->bindAPI->panelRepository->findByName(name: $name)) { + echo "Panel: $name already exists." . PHP_EOL; + exit(1); + } else { + $result = $this->bindAPI->panelRepository->insert(name: $name, a: $a, aaaa: $aaaa, apikey: $apikey); + echo "Panel $name has been created with id $result" . PHP_EOL; + exit(0); + } + } + + /** + * @return void + */ + function handlePanelsList(): void + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "handlePanelsList()"); + } + + $panels = $this->bindAPI->panelRepository->findAll(); + if (!empty($panels)) { + echo 'All available panels:' . PHP_EOL; + $table = new ConsoleTable(); + $table->setHeaders(content: ['ID', 'Name', 'A', 'AAAA', 'API Key']); + foreach ($panels as $panel) { + $row = []; + $token = strtok(string: $panel->getApikey(), token: '.'); + $row[] = $panel->getID(); + $row[] = $panel->getName(); + $row[] = $panel->getA(); + $row[] = $panel->getAaaa(); + $row[] = $token; + $table->addRow(data: $row); + } + $table->setPadding(value: 2); + $table->display(); + } else { + echo 'No panels found.' . PHP_EOL; + exit(1); + } + exit(0); + } + + + /** + */ + function handlePanelsUpdate(): void + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "handlePanelsUpdate()"); + } + + $arguments = $this->parseArguments(); + + $id = intval(value: $this->arguments[1] ?? 0); + $name = $arguments['name'] ?? ''; + $a = $arguments['a'] ?? ''; + $aaaa = $arguments['aaaa'] ?? ''; + $apikey = $arguments['apikey'] ?? ''; + + if ($id == 0) { + echo 'An ID is required' . PHP_EOL; + exit(1); + } + if (!$this->bindAPI->panelRepository->findByID(id: $id)) { + echo "Panel with ID : $id doesn't exist." . PHP_EOL; + exit(1); + } + if ($this->bindAPI->panelRepository->update(id: $id, name: $name, a: $a, aaaa: $aaaa, apikey: $apikey) !== false) { + echo 'Panel has been updated' . PHP_EOL; + } else { + echo 'Error while updating domain server.' . PHP_EOL; + } + } + + /** + */ + function handlePanelsDelete(): void + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "handlePanelsDelete()"); + } + + if (empty($this->arguments[1])) { + echo "You need to supply an ID." . PHP_EOL; + exit(1); + } + + $id = intval(value: $this->arguments[1]) ?? 0; + if ($id == 0) { + echo "Panel with ID $id not found." . PHP_EOL; + exit(1); + } + if (!$this->bindAPI->panelRepository->findByID(id: $id)) { + echo "There is no panel with ID $id." . PHP_EOL; + exit(1); + } + $this->bindAPI->panelRepository->delete(id: $id); + echo "The panel with ID $id has been deleted." . PHP_EOL; + } + + /** + */ + function handleAPIPing(string $type): void + { + if ($this->config['debug']) { + $this->bindAPI->log->debug(message: "handleApiPing()"); + } + + $error = false; + + $id = $this->getId(); + + if ($id != 0) { + if ($type == 'panel') { + $server = $this->bindAPI->panelRepository->findByID(id: $id); + } else { + $server = $this->bindAPI->nameserverRepository->findByID(id: $id); + } + + if ($server) { + if (!$this->checkPing(server: $server, type: $type)) { + $error = true; + } + } else { + if ($this->config['verbose']) { + echo "Unknown $type ID $id" . PHP_EOL; + } + $error = true; + } + } else { + if ($type == 'panel') { + $servers = $this->bindAPI->panelRepository->findAll(); + } else { + $servers = $this->bindAPI->nameserverRepository->findAll(); + } + foreach ($servers as $server) { + if (!$this->checkPing(server: $server, type: $type)) { + $error = true; + } + } + } + echo PHP_EOL; + if ($error) { + exit(1); + } else { + exit(0); + } + } + + /** + * @return int|void + */ + public function getId() + { + if (!empty($this->arguments[1])) { + $id = intval(value: $this->arguments[1] ?? 0); + + if ($id != $this->arguments[1]) { + echo 'ID has to be a number.' . PHP_EOL; + exit(1); + } + } else { + $id = 0; + } + return $id; + } + + /** + * @param \App\Entity\Panel|\App\Entity\Nameserver $server + * @param String $type + * + * @return bool + */ + public function checkPing(Panel|Nameserver $server, string $type): bool + { + $error = false; + + if ($type == 'nameserver') { + $maxName = $this->bindAPI->nameserverRepository->getLongestEntry(field: 'name'); + $maxA = $this->bindAPI->nameserverRepository->getLongestEntry(field: 'a'); + $maxAAAA = $this->bindAPI->nameserverRepository->getLongestEntry(field: 'aaaa'); + } else { + $maxName = $this->bindAPI->panelRepository->getLongestEntry(field: 'name'); + $maxA = $this->bindAPI->panelRepository->getLongestEntry(field: 'a'); + $maxAAAA = $this->bindAPI->panelRepository->getLongestEntry(field: 'aaaa'); + } + + if ($this->config['verbose']) { + echo COLOR_YELLOW . str_pad(string: $server->getName(), length: $maxName); + } + + $a = $server->getA() ?? ''; + if (!empty($a)) { + if ($this->config['verbose']) { + echo COLOR_DEFAULT . ' ' . str_pad(string: $a, length: $maxA, pad_type: STR_PAD_LEFT) . ' '; + } + if ($result = $this->bindAPI->apiController->sendCommand( + requestType: 'GET', + serverName : $server->getName(), + versionIP : 4, + apiKey : $server->getApikey(), + command : 'ping', + serverType : $type)) { + if ($this->config['verbose']) { + if ($result['data'] == 'pong') { + echo COLOR_GREEN . $result['data']; + } else { + echo COLOR_BLUE . 'skip'; + } + } + } else { + $error = true; + } + } + $aaaa = $server->getAaaa() ?? ''; + if (!empty($aaaa)) { + if ($this->config['verbose']) { + echo COLOR_DEFAULT . ' ' . str_pad(string: $aaaa, length: $maxAAAA, pad_type: STR_PAD_LEFT) . ' '; + } + if ($result = $this->bindAPI->apiController->sendCommand( + requestType: 'GET', + serverName : $server->getName(), + versionIP : 6, + apiKey : $server->getApikey(), + command : 'ping', + serverType : $type)) { + if ($this->config['verbose']) { + if ($result['data'] == 'pong') { + echo COLOR_GREEN . $result['data']; + } else { + echo COLOR_BLUE . $result['data']; // TODO 'skip'; + } + } + } else { + $error = true; + } + } + if ($this->config['verbose']) { + echo PHP_EOL; + } + return $error; + } + + /** + * @param string $subcommand + * + * @return void + */ + public function handleApiKeys(string $subcommand): void + { + match ($subcommand) { + 'create' => $this->handleApikeysCreate(), + 'list' => $this->handleApikeysList(), + 'update' => $this->handleApikeysUpdate(), + 'delete' => $this->handleApikeysDelete(), + }; + } + + /** + * @return void + */ + function handleApikeysCreate(): void + { + $arguments = $this->parseArguments(); + $name = $arguments['name'] ?? ''; + + $result = $this->bindAPI->apikeyRepository->create(name: $name); + echo 'API key ' . COLOR_YELLOW . $result['row'] . COLOR_DEFAULT . ' has been generated. Store it in a save place, it cannot be recovered.' . PHP_EOL; + echo "\033[32m\t" . $result['tokenPrefix'] . '.' . $result['key'] . PHP_EOL; + exit(0); + } + + /** + * @return void + */ + function handleApikeysList(): void + { + $keys = $this->bindAPI->apikeyRepository->findAll(); + if (!empty($keys)) { + echo 'All valid API keys:' . PHP_EOL; + $table = new ConsoleTable(); + $table->setHeaders(content: ['ID', 'Name', 'API key prefix']); + foreach ($keys as $key) { + $row = []; + $row[] = $key->getID(); + $row[] = $key->getName(); + $row[] = $key->getApiTokenPrefix(); + $table->addRow(data: $row); + } + $table->setPadding(value: 2); + $table->display(); + } else { + echo 'No keys found.' . PHP_EOL; + } + exit(0); + } + + + /** + */ + function handleApikeysUpdate(): void + { + $arguments = $this->parseArguments(); + + $id = $this->arguments[1] ?? 0; + $name = $arguments['name'] ?? ''; + + if ($id == 0) { + echo 'An ID is required' . PHP_EOL; + exit(1); + } + if (empty($name)) { + echo 'You need to supply the new name.' . PHP_EOL; + exit(1); + } + if (!$this->bindAPI->apikeyRepository->findByID(id: intval(value: $id))) { + echo "Apikeys with ID : $id doesn't exist." . PHP_EOL; + exit(1); + } + if ($this->bindAPI->apikeyRepository->update(id: intval(value: $id), name: $name) !== false) { + echo 'Apikey has been updated' . PHP_EOL; + } else { + echo 'Error while updating apikey.' . PHP_EOL; + } + } + + + /** + * @return void + */ + function handleApikeysDelete(): void + { + $id = intval(value: $this->arguments[1] ?? 0); + if ($id == 0) { + echo 'You need to add the ID of the API key.' . PHP_EOL; + exit(1); + } + if ($this->bindAPI->apikeyRepository->findByID(id: $id)) { + $this->bindAPI->apikeyRepository->delete(id: $id); + echo 'API key ' . COLOR_YELLOW . $id . COLOR_DEFAULT . ' has been deleted.' . PHP_EOL; + exit(0); + } else { + echo 'Unknown ID ' . COLOR_YELLOW . $id . '.' . PHP_EOL; + exit(1); + } + } + + /** + * @param string $subcommand + * + * @return void + */ + public function handleDomains(string $subcommand): void + { + match ($subcommand) { + 'list' => $this->handleDomainsList(), + 'create' => $this->handleDomainsCreate(), + 'update' => $this->handleDomainsUpdate(), + 'delete' => $this->handleDomainsDelete(), + 'dyndns' => $this->handleDomainsDynDns(), + }; + } + + /** + * @return void + */ + function handleDomainsList(): void + { + $domains = $this->bindAPI->domainRepository->findAll(); + if (!empty($domains)) { + echo 'All available domains:' . PHP_EOL; + $table = new ConsoleTable(); + $table->setHeaders(content: ['ID', 'Name', 'Panel', 'Type']); + /** @var Domain $domain */ + foreach ($domains as $domain) { + $row = []; + $row[] = $domain->getId(); + $row[] = $domain->getName(); + $row[] = $domain->getPanel(); + $row[] = $this->bindAPI->domainController->isMasterZone(domain: $domain) ? 'MASTER' : 'SLAVE'; + $table->addRow(data: $row); + } + $table->setPadding(value: 2); + $table->display(); + } else { + echo 'No domains found.' . PHP_EOL; + } + exit(0); + } + + + /** + * @return void + */ + function handleDomainsCreate(): void + { + // check if we're correctly setup + if (!$this->bindAPI->domainController->checkPermissions()) { + echo 'You need to setup the bindAPI first.' . PHP_EOL; + exit(1); + } + + $name = $this->arguments[1] ?? ""; + if (empty($name)) { + echo 'You need to supply the domain name.' . PHP_EOL; + exit(1); + } + + $filteredName = filter_var(value: $name, filter: FILTER_VALIDATE_DOMAIN, options: FILTER_FLAG_HOSTNAME); + if (!empty($filteredName) && str_contains(haystack: $filteredName, needle: '.')) { + $name = $filteredName; + } else { + echo "$name is no valid DNS domain name." . PHP_EOL; + exit(1); + } + + $arguments = $this->parseArguments(); + $panel = $arguments['panel'] ?? ''; + + if (empty($panel)) { + echo 'You need to supply the panel name.' . PHP_EOL; + exit(1); + } + + if ($this->bindAPI->domainRepository->findByName(name: $name)) { + echo "Domain: $name already exists." . PHP_EOL; + exit(1); + } else { + if (!$this->bindAPI->panelRepository->findByName(name: $panel)) { + echo 'Unknown panel: ' . COLOR_YELLOW . $panel . COLOR_DEFAULT . '.' . PHP_EOL; + exit(1); + } + $domain = new Domain(name: $name, panel: $panel); + $result = $this->bindAPI->domainRepository->insert(domain: $domain); + echo 'Domain' . COLOR_YELLOW . $name . COLOR_DEFAULT . ' has been created with id ' . COLOR_YELLOW . $result . COLOR_DEFAULT . '.' . PHP_EOL; + $this->bindAPI->domainController->createSlaveZoneFile(domain: $domain); + exit(0); + } + } + + /** + */ + function handleDomainsUpdate(): void + { + // check if we're correctly setup + if (!$this->bindAPI->domainController->checkPermissions()) { + echo 'You need to setup the bindAPI first.' . PHP_EOL; + exit(1); + } + + $arguments = $this->parseArguments(); + + $id = intval(value: $this->arguments[1] ?? 0); + $name = $arguments['name'] ?? ''; + $panelName = $arguments['panel'] ?? ''; + + if ($id == 0) { + echo 'An ID is required' . PHP_EOL; + exit(1); + } + if (!$domain = $this->bindAPI->domainRepository->findByID(id: $id)) { + echo "Domain with ID : $id doesn't exist." . PHP_EOL; + exit(1); + } + + if (!empty($panelName)) { + $panel = $this->bindAPI->panelRepository->findByName(name: $panelName); + } + + if (empty($name) && empty($panel)) { + echo 'No name or panel given, just recreate the config file' . PHP_EOL; + $this->bindAPI->domainController->updateSlaveZones(); + exit(1); + } + $newDomain = new Domain(name: $name, panel: $panelName, id: $domain->getId()); + + if ($this->bindAPI->domainRepository->update(domain: $newDomain) !== false) { + echo 'Domain server has been updated' . PHP_EOL; + $this->bindAPI->domainController->updateSlaveZones(); + } else { + echo 'Error while updating domain server.' . PHP_EOL; + } + } + + + /** + */ + function handleDomainsDelete(): void + { + if (empty($this->arguments[1])) { + echo "You need to supply an ID." . PHP_EOL; + exit(1); + } + + $id = intval(value: $this->arguments[1]) ?? 0; + if ($id == 0) { + echo "Domain with ID $id not found." . PHP_EOL; + exit(1); + } + if (!$domain = $this->bindAPI->domainRepository->findByID(id: $id)) { + echo "There is no domain with ID $id." . PHP_EOL; + exit(1); + } + $this->bindAPI->domainRepository->delete(domain: $domain); + $this->bindAPI->domainController->deleteZone(domain: $domain); + echo "The domain with ID $id has been deleted." . PHP_EOL; + } + + + /** + * @param string $subcommand + * + * @return void + */ + public + function handleNameservers(string $subcommand): void + { + match ($subcommand) { + 'create' => $this->handleNameserversCreate(), + 'list' => $this->handleNameserversList(), + 'update' => $this->handleNameserversUpdate(), + 'delete' => $this->handleNameserversDelete(), + 'apiping' => $this->handleAPIPing(type: 'nameserver') + }; + } + + + /** + * @return void + */ + function handleNameserversCreate(): void + { + $name = $this->arguments[1] ?? ''; + if (empty($name)) { + echo 'You need to supply the nameserver name.' . PHP_EOL; + exit(1); + } + + $filteredName = filter_var(value: $name, filter: FILTER_VALIDATE_DOMAIN); + if (!empty($filteredName) && str_contains(haystack: $filteredName, needle: '.')) { + $name = $filteredName; + } else { + echo "$name is no valid nameserver name." . PHP_EOL; + exit(1); + } + + $arguments = $this->parseArguments(); + + $a = $arguments['a'] ?? ''; + $aaaa = $arguments['aaaa'] ?? ''; + if (empty($a) && empty($aaaa)) { + echo 'At least one IP address is required.' . PHP_EOL; + exit(0); + } + $apikey = $arguments['apikey'] ?? ''; + + if ($this->bindAPI->nameserverRepository->findByName(name: $name)) { + echo "Nameserver: $name already exists." . PHP_EOL; + exit(1); + } else { + $nameserver = new Nameserver(name: $name, a: $a, aaaa: $aaaa, apikey: $apikey); + $result = $this->bindAPI->nameserverRepository->insert(nameserver: $nameserver); + echo "Nameserver $name has been created with id $result" . PHP_EOL; + exit(0); + } + } + + + /** + * @return void + */ + function handleNameserversList(): void + { + $nameservers = $this->bindAPI->nameserverRepository->findAll(); + if (!empty($nameservers)) { + echo 'All available nameservers:' . PHP_EOL; + $table = new ConsoleTable(); + $table->setHeaders(content: ['ID', 'Name', 'A', 'AAAA', 'API Key']); + foreach ($nameservers as $nameserver) { + $row = []; + $token = strtok(string: $nameserver->getApiKey(), token: '.'); + $row[] = $nameserver->getId(); + $row[] = $nameserver->getName(); + $row[] = $nameserver->getA(); + $row[] = $nameserver->getAaaa(); + $row[] = $token; + $table->addRow(data: $row); + } + $table->setPadding(value: 2); + $table->display(); + } else { + echo 'No nameservers found.' . PHP_EOL; + exit(1); + } + exit(0); + } + + + /** + */ + function handleNameserversUpdate(): void + { + $arguments = $this->parseArguments(); + + $id = $this->arguments[1] ?? 0; + $name = $arguments['name'] ?? ''; + $a = $arguments['a'] ?? ''; + $aaaa = $arguments['aaaa'] ?? ''; + $apikey = $arguments['apikey'] ?? ''; + + if ($id == 0) { + echo 'An ID is required.' . PHP_EOL; + exit(1); + } + if (!$this->bindAPI->nameserverRepository->findByID(id: intval(value: $id))) { + echo 'Nameserver with ID ' . COLOR_YELLOW . $id . COLOR_DEFAULT . " doesn't exist." . PHP_EOL; + exit(1); + } + if ($this->bindAPI->nameserverRepository->update(id: intval(value: $id), name: $name, a: $a, aaaa: $aaaa, apikey: $apikey) !== false) { + echo 'Nameserver ' . COLOR_YELLOW . $id . COLOR_DEFAULT . ' has been updated.' . PHP_EOL; + } else { + echo 'Error while updating nameserver ' . COLOR_YELLOW . $id . '.' . PHP_EOL; + } + } + + + /** + */ + function handleNameserversDelete(): void + { + if (empty($this->arguments[1])) { + echo "You need to supply an ID." . PHP_EOL; + exit(1); + } + + $id = intval(value: $this->arguments[1] ?? 0); + if ($id == 0) { + echo 'Nameserver with ID ' . COLOR_YELLOW . $id . COLOR_DEFAULT . ' not found.' . PHP_EOL; + exit(1); + } + if (!$this->bindAPI->nameserverRepository->findByID(id: $id)) { + echo 'There is no nameserver with ID ' . COLOR_YELLOW . $id . COLOR_DEFAULT . '.' . PHP_EOL; + exit(1); + } + $this->bindAPI->nameserverRepository->delete(id: $id); + echo 'The nameserver with ID ' . COLOR_YELLOW . $id . COLOR_DEFAULT . ' has been deleted.' . PHP_EOL; + } + + + /** + */ + function handleCheckShowIncludes(): void + { + $nameservers = $this->bindAPI->nameserverRepository->findAll(); + + if (count($nameservers) === 0) { + echo 'No nameservers found.' . PHP_EOL; + echo 'You first need to setup the system.' . PHP_EOL; + exit(1); + } + + + echo COLOR_DEFAULT . 'You need to add these lines to ' . COLOR_YELLOW . '/etc/bind/local.bindapi.options' . COLOR_DEFAULT . ' on every panel and make sure' . PHP_EOL; + echo 'that ' . COLOR_YELLOW . 'include "/etc/bind/local.bindapi.options";' . COLOR_DEFAULT . ' exists in ' . COLOR_YELLOW . '/etc/bind/named.conf.options' . COLOR_DEFAULT . '.' . PHP_EOL; + $ip = []; + foreach ($nameservers as $nameserver) { + if (!empty($nameserver->getA())) { + $ip[] = $nameserver->getA(); + } + if (!empty($nameserver->getAaaa())) { + $ip[] = $nameserver->getAaaa(); + } + } + echo PHP_EOL . 'allow-transfer {' . PHP_EOL; + foreach ($ip as $currentIp) + echo "\t$currentIp;" . PHP_EOL; + echo '};'; + echo PHP_EOL . 'also-notify {' . PHP_EOL; + foreach ($ip as $currentIp) + echo "\t$currentIp;" . PHP_EOL; + echo '};' . PHP_EOL; + echo PHP_EOL . 'After the modification feel free to run ' . COLOR_YELLOW . 'named-checkconf' . COLOR_DEFAULT . ' to ensure there were no errors.' . PHP_EOL; + echo PHP_EOL . 'Run ' . COLOR_YELLOW . 'rndc reload' . COLOR_DEFAULT . ' to activate the changes.' . PHP_EOL; + + + } + + /** + */ + function handleCheckDomains(): void + { + $this->bindAPI->domainController->checkDomains(); + } + + + /** + */ + private function handleDomainsDynDns(): void + { + $hostName = $this->arguments[1] ?? ''; + + if (empty($hostName)) { + echo 'You need to supply at least the hostname' . PHP_EOL; + exit(1); + } + + if ($this->config['verbose']) { + echo "Updating DynDNS host: $hostName" . PHP_EOL; + } + + echo 'here'; + $domain = $this->bindAPI->domainRepository->findByHost(host: $hostName); + print_r(value: $domain); + echo 'there'; + // we need the panel who is master for zone + $panel = $this->bindAPI->panelRepository->findByName(name: $domain->getPanel()); + + // which NS belongs to that panel + + + if (!empty($panel->getAaaa())) { + $result = $this->bindAPI->apiController->sendCommand( + requestType: 'POST', + serverName : $panel->getName(), + versionIP : 6, + apiKey : $panel->getApikey(), + command : 'dyndns/' . $hostName, + serverType : 'nameserver'); + } else { + $result = $this->bindAPI->apiController->sendCommand( + requestType: 'POST', + serverName : $panel->getName(), + versionIP : 4, + apiKey : $panel->getApikey(), + command : 'dyndns/' . $hostName, + serverType : 'nameserver'); + } + + if ($result['header'] == 200) { + if ($this->config['verbose']) { + $data = $result['data']; + $decodedData = json_decode(json: $data, associative: true); + echo $decodedData['message'] . PHP_EOL; + } + } else { + echo 'Something went wrong:' . PHP_EOL; + print_r(value: $result); + exit(1); + } + exit(0); + } +} diff --git a/src/Controller/DatabaseConnection.php b/src/Controller/DatabaseConnection.php index e890276..52488de 100644 --- a/src/Controller/DatabaseConnection.php +++ b/src/Controller/DatabaseConnection.php @@ -24,7 +24,7 @@ class DatabaseConnection public function __construct(private array $config) { extract(array: $this->config); - + // TODO create config => encryption key try { $this->dbConnection = new PDO( dsn: "mysql:host=$dbHost;port=$dbPort;charset=utf8mb4;dbname=$dbDatabase", @@ -136,4 +136,4 @@ class DatabaseConnection { return $this->dbConnection; } -} \ No newline at end of file +} diff --git a/src/Controller/DomainController.php b/src/Controller/DomainController.php index 1cf08b4..2f91dfb 100644 --- a/src/Controller/DomainController.php +++ b/src/Controller/DomainController.php @@ -17,9 +17,9 @@ error_reporting(error_level: E_ALL); */ class DomainController { - private string $localZoneFile; - private string $localZonesDir; - private string $namedConfLocalFile; + public string $localZoneFile; + public string $localZonesDir; + public string $namedConfLocalFile; private string $zoneCachePath; @@ -132,55 +132,90 @@ class DomainController /** - * @return void + * @return bool */ - function checkPermissions(): void + function checkPermissions(): bool { + $setupIsValid = true; + if ($this->config['debug']) { $this->log->debug(message: "checkPermissions()"); } - echo 'Checking permission:' . PHP_EOL . PHP_EOL; + if ($this->config['verbose']) { + echo 'Checking permissions...' . PHP_EOL; + } $uid = posix_geteuid(); - echo "UID:\t" . COLOR_YELLOW . $uid . PHP_EOL; - + if ($this->config['verbose']) { + echo "UID:\t" . COLOR_YELLOW . $uid . PHP_EOL; + } $pwuid = posix_getpwuid(user_id: $uid); $name = $pwuid['name']; - echo COLOR_DEFAULT . "Name:\t" . COLOR_YELLOW . $name . PHP_EOL; + if ($this->config['verbose']) { + echo COLOR_DEFAULT . "Name:\t" . COLOR_YELLOW . $name . PHP_EOL; + } $bindGroup = posix_getgrnam(name: 'bind'); $members = $bindGroup['members']; if (in_array(needle: $name, haystack: $members)) { - echo "\t✅ $name" . COLOR_DEFAULT . ' is in group ' . COLOR_YELLOW . 'bind' . PHP_EOL; - } else { - echo "\t❌$name needs to be in group " . COLOR_YELLOW . 'bind' . COLOR_DEFAULT . '!' . PHP_EOL; - } - - echo COLOR_DEFAULT . 'Checking ' . COLOR_YELLOW . $this->localZoneFile . PHP_EOL; - $localZoneFilePermissions = @fileperms(filename: $this->localZoneFile); - if ($localZoneFilePermissions & 0x0010) { - echo COLOR_DEFAULT . "\t✅ Group has write access." . PHP_EOL; - } else { - echo COLOR_RED . "\t❌Group needs write permission!" . PHP_EOL; - } - - echo 'Checking ' . COLOR_YELLOW . $this->namedConfLocalFile . PHP_EOL; - if ($namedConfLocal = file_get_contents(filename: $this->namedConfLocalFile)) { - if (!str_contains(haystack: $namedConfLocal, needle: $this->localZoneFile)) { - echo "\t❌ $this->localZoneFile" . COLOR_RED . ' needs to be included in ' . COLOR_YELLOW . $this->namedConfLocalFile . PHP_EOL; - } else { - echo "\t✅ $this->localZoneFile" . COLOR_DEFAULT . ' is included in ' . COLOR_YELLOW . $this->namedConfLocalFile . PHP_EOL; + if ($this->config['verbose']) { + echo "\t✅ $name" . COLOR_DEFAULT . ' is in group ' . COLOR_YELLOW . 'bind' . PHP_EOL; } } else { - echo "\t❌ No access to '$this->namedConfLocalFile' . Please check permissions" . PHP_EOL; + $setupIsValid = false; + if ($this->config['verbose']) { + echo COLOR_RED . "\t❌$name needs to be in group " . COLOR_YELLOW . 'bind' . COLOR_DEFAULT . '!' . PHP_EOL; + } + } + if ($this->config['verbose']) { + echo COLOR_DEFAULT . 'Checking ' . COLOR_YELLOW . $this->localZoneFile . PHP_EOL; + } + $localZoneFilePermissions = @fileperms(filename: $this->localZoneFile); + if ($localZoneFilePermissions & 0x0010) { + if ($this->config['verbose']) { + echo COLOR_DEFAULT . "\t✅ Group has write access." . PHP_EOL; + } + } else { + $setupIsValid = false; + if ($this->config['verbose']) { + echo COLOR_RED . "\t❌Group needs write permission!" . COLOR_DEFAULT . PHP_EOL; + } + } + if ($this->config['verbose']) { + echo 'Checking ' . COLOR_YELLOW . $this->namedConfLocalFile . PHP_EOL; + } + if ($namedConfLocal = file_get_contents(filename: $this->namedConfLocalFile)) { + if (!str_contains(haystack: $namedConfLocal, needle: $this->localZoneFile)) { + $setupIsValid = false; + if ($this->config['verbose']) { + echo "\t❌ $this->localZoneFile" . COLOR_RED . ' needs to be included in ' . COLOR_YELLOW . $this->namedConfLocalFile . PHP_EOL; + } + } else { + if ($this->config['verbose']) { + echo "\t✅ $this->localZoneFile" . COLOR_DEFAULT . ' is included in ' . COLOR_YELLOW . $this->namedConfLocalFile . PHP_EOL; + } + } + } else { + $setupIsValid = false; + if ($this->config['verbose']) { + echo "\t❌ No access to '$this->namedConfLocalFile' . Please check permissions" . PHP_EOL; + } + } + if ($this->config['verbose']) { + echo COLOR_DEFAULT . 'Checking directory: ' . COLOR_YELLOW . $this->localZonesDir . PHP_EOL; } - - echo COLOR_DEFAULT . 'Checking directory: ' . COLOR_YELLOW . $this->localZonesDir . PHP_EOL; $localZoneDirPermissions = @fileperms(filename: $this->localZonesDir); if ($localZoneDirPermissions & 0x0010) { - echo "\t✅ Group has write access." . PHP_EOL; + if ($this->config['verbose']) { + echo "\t✅ Group has write access." . PHP_EOL; + } } else { - echo "\t❌Group needs write permission!" . PHP_EOL; + $setupIsValid = false; + if ($this->config['verbose']) { + echo COLOR_RED . "\t❌Group needs write permission!" . PHP_EOL; + } } + + return $setupIsValid; } @@ -189,6 +224,10 @@ class DomainController */ function checkDomains(): void { + if (!file_exists($this->localZoneFile)) { + echo COLOR_DEFAULT . 'Local Zone file ' . COLOR_YELLOW . $this->localZoneFile . COLOR_DEFAULT . ' does not exist.' . PHP_EOL; + exit(1); + } $localZones = file_get_contents(filename: $this->localZoneFile); $maxNameLength = $this->domainRepository->getLongestEntry(field: 'name'); $domains = $this->domainRepository->findAll(); @@ -270,4 +309,4 @@ class DomainController } } -} \ No newline at end of file +} diff --git a/src/Controller/NameserverController.php b/src/Controller/NameserverController.php deleted file mode 100644 index c657539..0000000 --- a/src/Controller/NameserverController.php +++ /dev/null @@ -1,18 +0,0 @@ -databaseConnection->getConnection()->prepare(query: $sql); $statement->bindParam(param: ':id', var: $id); $statement->execute(); - $result = $statement->fetch(mode: PDO::FETCH_ASSOC); - return new Nameserver(name: $result['name'], a: $result['a'], aaaa: $result['aaaa'], apikey: $result['apikey']); + if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) { + return new Nameserver(name: $result['name'], a: $result['a'], aaaa: $result['aaaa'], apikey: $result['apikey']); + } else { + return null; + } } catch (PDOException $e) { exit($e->getMessage()); } @@ -248,4 +251,4 @@ class NameserverRepository exit($e->getMessage()); } } -} \ No newline at end of file +} diff --git a/src/Repository/PanelRepository.php b/src/Repository/PanelRepository.php index b52908f..ea8f2fa 100644 --- a/src/Repository/PanelRepository.php +++ b/src/Repository/PanelRepository.php @@ -44,9 +44,9 @@ class PanelRepository /** * @param int $id * - * @return \App\Entity\Panel + * @return null|\App\Entity\Panel */ - public function findByID(int $id): Panel + public function findByID(int $id): ?Panel { $sql = " SELECT id, name, a, aaaa, apikey @@ -57,8 +57,11 @@ class PanelRepository $statement = $this->databaseConnection->getConnection()->prepare(query: $sql); $statement->bindParam(param: ':id', var: $id); $statement->execute(); - $result = $statement->fetch(mode: PDO::FETCH_ASSOC); - return new Panel(name: $result['name'], a: $result['a'], aaaa: $result['aaaa'], apikey: $result['apikey']); + if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) { + return new Panel(name: $result['name'], a: $result['a'], aaaa: $result['aaaa'], apikey: $result['apikey']); + } else { + return null; + } } catch (PDOException $e) { exit($e->getMessage()); } @@ -214,4 +217,4 @@ class PanelRepository exit($e->getMessage()); } } -} \ No newline at end of file +}