Compare commits

...

50 Commits

Author SHA1 Message Date
tracer 289e6530a8 fixed a bug in CLIController 2024-07-13 16:48:15 +02:00
tracer 1db989d7c8 adaptet to KH v24.1 api changes 2024-07-13 16:34:27 +02:00
tracer 9ed956ad7d patch version bump 2024-07-09 19:13:47 +02:00
tracer 3a29039bde some bugfixes 2024-07-09 19:03:48 +02:00
tracer 02cdc70e9b pushed to 1.1.1 2024-05-03 20:15:37 +02:00
tracer ac0d8c776e moved check:version to dev enviroment, for regular check use -v 2024-05-03 20:06:06 +02:00
tracer b147cda096 added check:bootstrap 2024-05-03 19:41:18 +02:00
tracer 17e70f14a2 refactored command groups, migrations:make only available in dev/test 2024-05-03 19:15:19 +02:00
tracer bb1a16988d moced color consts to a utility class 2024-05-02 20:18:22 +02:00
tracer f04c306f91 added chekcs for cron job 2024-05-01 18:57:37 +02:00
tracer 5d2e95ac3d added chekcs for cron job 2024-05-01 18:41:24 +02:00
tracer 4e056f6831 added chekcs for cron job 2024-05-01 18:39:55 +02:00
tracer 753e96ed85 added self for nameservers for openApi defaults. 2024-04-30 19:33:07 +02:00
tracer d31ee8bdec added self for nameservers for openApi defaults. 2024-04-30 17:27:31 +02:00
tracer 26b0b6de6b modified version update 2024-04-30 17:22:43 +02:00
tracer efb069eb5a modified version update 2024-04-30 14:11:54 +02:00
tracer 514c77de55 modified version update 2024-04-30 14:08:59 +02:00
tracer 7b40624218 modified version update 2024-04-30 13:49:59 +02:00
tracer 06df37ed3c modified version update 2024-04-30 13:35:21 +02:00
tracer c6ece08a0b changed config table 2024-04-30 13:21:01 +02:00
tracer 0f13e29fe9 changed config table 2024-04-30 13:14:05 +02:00
tracer b06128e819 changed config table 2024-04-30 13:11:54 +02:00
tracer f25e90f292 added self for nameservers for openApi defaults. 2024-04-30 11:13:24 +02:00
tracer d0224f6746 new migration 2024-04-27 15:43:37 +02:00
tracer 71a275198f added version to api 2024-04-26 19:24:04 +02:00
tracer 578f76426e added version to api 2024-04-26 19:22:44 +02:00
tracer 96689879c4 try to fix cors issues 2024-04-25 21:03:35 +02:00
tracer e9b14a11d7 try to fix cors issues 2024-04-25 20:58:01 +02:00
tracer a312ad9095 move log out of root 2024-04-23 19:53:57 +02:00
tracer 1518deee87 added a more verbose error if API key is wrong 2024-04-22 20:19:34 +02:00
tracer c9757ead13 fixed cron issues 2024-04-22 18:57:49 +02:00
tracer 3e591eee9c updated readme 2024-04-21 13:52:57 +02:00
tracer 94334694d4 added systemd service & timer 2024-04-21 13:49:02 +02:00
tracer 6fc85b8692 added semaphore fo zone creation 2024-04-21 13:04:38 +02:00
tracer 8784acbed9 made check:panel more verbose 2024-04-19 18:11:21 +02:00
tracer cc4bbbecb4 fixed change path for BindApi class 2024-04-19 16:36:00 +02:00
tracer 730ae25d63 fixed change path for BindApi class 2024-04-19 16:19:17 +02:00
tracer e4f5a4a07e modified apikeys table 2024-04-19 15:50:12 +02:00
tracer 4f52e99a92 added version handling 2024-04-19 15:11:21 +02:00
tracer 176bd9dc83 renamed doains:refresh to domains:update as former update has been dropped 2024-04-18 18:04:25 +02:00
tracer 65ea85da39 updated domain refresh 2024-04-18 17:33:07 +02:00
tracer 2c65f743e7 fixed marking oown panel bug 2024-04-18 14:20:46 +02:00
tracer eaa137291b made name mandatory for apikeys 2024-04-17 20:28:51 +02:00
tracer 17535d825d fixed .htaccess 2024-04-17 15:27:21 +02:00
tracer 93e12b9949 fixed .htaccess 2024-04-17 15:25:53 +02:00
tracer 43b8f19a7b fixed bug when in nameserver creation 2024-04-17 14:04:47 +02:00
tracer 645f3d00fe fixed bug when in nameserver creation 2024-04-17 13:52:44 +02:00
tracer fd4175987b fixed bug when in domain refresh 2024-04-17 13:34:01 +02:00
tracer 2eb96742bb fixed bug when no domains exist 2024-04-17 13:14:09 +02:00
tracer 8f946d9360 fixed controller init bug 2024-04-17 12:55:44 +02:00
47 changed files with 5120 additions and 1131 deletions

3
.gitignore vendored
View File

@ -14,3 +14,6 @@
/config.json.prod
keys.txt
/.phpunit.cache
/var/log/*
/public/openapi/bindapi.json
/public/openapi/bootstrap.php

View File

@ -6,9 +6,6 @@
6. [DynDNS](#6-dyndns)
7. [Conclusion](#7-conclusion)
Don't use this code right now.
You can try 1.0.2, but it's not well tested.
NOTICE: This documentation is not current as of September 2022.
After I finished the refactoring I'll upgrade it.

6
TODO
View File

@ -1,3 +1,7 @@
API Endpoint cleanup
check keytype of panel
check keytype of 1bindApi
check:configkey => update config.json
more UNIT tests
DB migrations

View File

@ -10,11 +10,11 @@ if (php_sapi_name() !== 'cli') {
// check php version (must be >= 8.1)
/** @noinspection PhpArgumentWithoutNamedIdentifierInspection */
if (version_compare(PHP_VERSION, '8.1.0', '<')) {
echo 'This application requires PHP 8.1 or newer. You are running ' . PHP_VERSION . PHP_EOL;
echo 'If you are using KeyHelp, use keyhelp-php81 ' . $argv[0] . ' instead.' . PHP_EOL;
if (version_compare(PHP_VERSION, '8.2.0', '<')) {
echo 'This application requires PHP 8.2 or newer. You are running ' . PHP_VERSION . PHP_EOL;
echo 'If you are using KeyHelp, use keyhelp-php82 ' . $argv[0] . ' instead.' . PHP_EOL;
exit;
}
/** @noinspection PhpArgumentWithoutNamedIdentifierInspection */
require dirname(__DIR__, 1) . '/src/Util/Console.php';
require dirname(__DIR__, 1) . '/src/Utilities/Console.php';

148
bindapi.json Normal file
View File

@ -0,0 +1,148 @@
{
"openapi": "3.0.0",
"info": {
"title": "bindAPI",
"version": "1.0.9"
},
"servers": [
{
"url": "{schema}://{hostname}/api",
"description": "The bindAPI URL.",
"variables": {
"schema": {
"enum": [
"http",
"https"
],
"default": "https"
},
"hostname": {
"enum": [
"ns1.24unix.net",
"ns2.24unix.net"
],
"default": "ns2.24unix.net"
}
}
}
],
"paths": {
"/ping": {
"get": {
"tags": [
"Server"
],
"description": "Checks for connectivity and valid APIkey",
"operationId": "ping",
"responses": {
"200": {
"description": "OK"
},
"401": {
"description": "API key is missing or invalid."
}
},
"security": [
{
"Authorization": []
}
]
}
},
"/version": {
"get": {
"tags": [
"Server"
],
"description": "Check the API version of the nameserver.",
"operationId": "version",
"responses": {
"200": {
"description": "x.y.z, aka major, minor, patch"
},
"401": {
"description": "API key is missing or invalid."
}
},
"security": [
{
"Authorization": []
}
]
}
},
"/domains": {
"get": {
"tags": [
"Domains"
],
"summary": "List all domains.",
"description": "Returns a list of all domains on this server.",
"operationId": "getAllDomains",
"responses": {
"200": {
"description": "OK"
},
"401": {
"description": "API key is missing or invalid."
},
"404": {
"description": "Domain not found."
}
},
"security": [
{
"Authorization": []
}
]
}
},
"/domains/{name}": {
"get": {
"tags": [
"Domains"
],
"summary": "Returns a single domain.",
"description": "Returns information of a single domain specified by its domain name.",
"operationId": "getSingleDomain",
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"401": {
"description": "API key is missing or invalid."
},
"404": {
"description": "Domain not found."
}
},
"security": []
}
}
},
"components": {
"securitySchemes": {
"Authorization": {
"type": "apiKey",
"description": "Api Authentication",
"name": "X-API-Key",
"in": "header"
}
}
},
"tags": [
{
"name": "Server"
}
]
}

View File

@ -1,8 +1,8 @@
{
"name": "24unix/bindapi",
"description": "manage Bind9 DNS server via REST API",
"version": "2023.0.1",
"build_number": "333",
"name": "tracer/bindapi",
"description": "manage Bind9 client zones for KeyHelp",
"version": "1.1.2",
"build_number": "380",
"authors": [
{
"name": "Micha Espey",
@ -23,6 +23,7 @@
"ext-posix": "*",
"ext-sodium": "*",
"arubacao/tld-checker": "^1.2",
"bartlett/php-compatinfo": "^7.1",
"monolog/monolog": "^3.1",
"netresearch/jsonmapper": "^4.4",
"php-di/php-di": "^6.3",
@ -30,7 +31,7 @@
"robmorgan/phinx": "^0.15",
"symfony/property-access": "^6.1",
"symfony/serializer": "^6.1",
"zircote/swagger-php": "^4.2"
"zircote/swagger-php": "^4.8"
},
"config": {
"optimize-autoloader": true,

3099
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,10 @@
{
"env": "prod",
"dbHost": "localhost",
"dbPort": 3306,
"dbDatabase": "sampledb",
"dbUser": "sampleuser",
"dbPassword": "secret",
"encryptionKey": "12345678901234567890123456789012",
"encryptionKey": "1bad::babe",
"debug": false
}

View File

@ -0,0 +1,33 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class ConfigTable extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('config', [
'id' => false,
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->addColumn('name', 'string', [
'null' => false,
'limit' => 256,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
])
->addColumn('value', 'string', [
'null' => false,
'limit' => 256,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'after' => 'name',
])
->removeColumn('version')
->save();
}
}

View File

@ -0,0 +1,24 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class ApiKeyCreationDate extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('apikeys', [
'id' => false,
'primary_key' => ['id'],
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->addColumn('created_at', 'timestamp', [
'null' => false,
'after' => 'apikey',
])
->save();
}
}

View File

@ -0,0 +1,24 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class UniqueConfigValues extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('config', [
'id' => false,
'primary_key' => ['name'],
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->addIndex(['name'], [
'name' => 'name',
'unique' => true,
])
->save();
}
}

View File

@ -0,0 +1,25 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class TimeStampDefaultforApiKeys extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('apikeys', [
'id' => false,
'primary_key' => ['id'],
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->changeColumn('created_at', 'timestamp', [
'null' => true,
'default' => 'current_timestamp()',
'after' => 'apikey',
])
->save();
}
}

View File

@ -0,0 +1,37 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class AddSelfToNameservers extends Phinx\Migration\AbstractMigration
{
public function change()
{
// $this->table('domains', [
// 'id' => false,
// 'primary_key' => ['id'],
// 'engine' => 'InnoDB',
// 'encoding' => 'utf8mb4',
// 'collation' => 'utf8mb4_unicode_ci',
// 'comment' => '',
// 'row_format' => 'DYNAMIC',
// ])
// ->removeColumn('self')
// ->save();
$this->table('nameservers', [
'id' => false,
'primary_key' => ['id'],
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->addColumn('self', 'enum', [
'null' => false,
'limit' => 3,
'values' => ['yes', 'no'],
'after' => 'apikey_prefix',
])
->save();
}
}

View File

@ -0,0 +1,41 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class UUIDForConfig extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('config', [
'id' => false,
'primary_key' => ['id'],
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->addColumn('id', 'uuid', [
'null' => false,
])
->changeColumn('name', 'string', [
'null' => false,
'limit' => 256,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'after' => 'id',
])
->changeColumn('value', 'string', [
'null' => false,
'limit' => 256,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'after' => 'name',
])
->addIndex(['id'], [
'name' => 'id',
'unique' => true,
])
->save();
}
}

View File

@ -0,0 +1,24 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class DefaultUUIDforConfig extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('config', [
'id' => false,
'primary_key' => ['id'],
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->changeColumn('id', 'uuid', [
'null' => false,
'default' => 'uuid()',
])
->save();
}
}

View File

@ -112,23 +112,71 @@ return array (
),
'columns' =>
array (
'version' =>
'id' =>
array (
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'config',
'COLUMN_NAME' => 'version',
'COLUMN_NAME' => 'id',
'ORDINAL_POSITION' => 1,
'COLUMN_DEFAULT' => 'uuid()',
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'uuid',
'CHARACTER_MAXIMUM_LENGTH' => NULL,
'CHARACTER_OCTET_LENGTH' => NULL,
'NUMERIC_PRECISION' => NULL,
'NUMERIC_SCALE' => NULL,
'DATETIME_PRECISION' => NULL,
'CHARACTER_SET_NAME' => NULL,
'COLLATION_NAME' => NULL,
'COLUMN_TYPE' => 'uuid',
'COLUMN_KEY' => 'PRI',
'EXTRA' => '',
'PRIVILEGES' => 'select,insert,update,references',
'COLUMN_COMMENT' => '',
'IS_GENERATED' => 'NEVER',
'GENERATION_EXPRESSION' => NULL,
),
'name' =>
array (
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'config',
'COLUMN_NAME' => 'name',
'ORDINAL_POSITION' => 2,
'COLUMN_DEFAULT' => NULL,
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'varchar',
'CHARACTER_MAXIMUM_LENGTH' => 11,
'CHARACTER_OCTET_LENGTH' => 44,
'CHARACTER_MAXIMUM_LENGTH' => 256,
'CHARACTER_OCTET_LENGTH' => 1024,
'NUMERIC_PRECISION' => NULL,
'NUMERIC_SCALE' => NULL,
'DATETIME_PRECISION' => NULL,
'CHARACTER_SET_NAME' => 'utf8mb4',
'COLLATION_NAME' => 'utf8mb4_general_ci',
'COLUMN_TYPE' => 'varchar(11)',
'COLUMN_TYPE' => 'varchar(256)',
'COLUMN_KEY' => 'UNI',
'EXTRA' => '',
'PRIVILEGES' => 'select,insert,update,references',
'COLUMN_COMMENT' => '',
'IS_GENERATED' => 'NEVER',
'GENERATION_EXPRESSION' => NULL,
),
'value' =>
array (
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'config',
'COLUMN_NAME' => 'value',
'ORDINAL_POSITION' => 3,
'COLUMN_DEFAULT' => NULL,
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'varchar',
'CHARACTER_MAXIMUM_LENGTH' => 256,
'CHARACTER_OCTET_LENGTH' => 1024,
'NUMERIC_PRECISION' => NULL,
'NUMERIC_SCALE' => NULL,
'DATETIME_PRECISION' => NULL,
'CHARACTER_SET_NAME' => 'utf8mb4',
'COLLATION_NAME' => 'utf8mb4_general_ci',
'COLUMN_TYPE' => 'varchar(256)',
'COLUMN_KEY' => '',
'EXTRA' => '',
'PRIVILEGES' => 'select,insert,update,references',
@ -139,6 +187,60 @@ return array (
),
'indexes' =>
array (
'PRIMARY' =>
array (
1 =>
array (
'Table' => 'config',
'Non_unique' => 0,
'Key_name' => 'PRIMARY',
'Seq_in_index' => 1,
'Column_name' => 'id',
'Collation' => 'A',
'Sub_part' => NULL,
'Packed' => NULL,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
),
),
'name' =>
array (
1 =>
array (
'Table' => 'config',
'Non_unique' => 0,
'Key_name' => 'name',
'Seq_in_index' => 1,
'Column_name' => 'name',
'Collation' => 'A',
'Sub_part' => NULL,
'Packed' => NULL,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
),
),
'id' =>
array (
1 =>
array (
'Table' => 'config',
'Non_unique' => 0,
'Key_name' => 'id',
'Seq_in_index' => 1,
'Column_name' => 'id',
'Collation' => 'A',
'Sub_part' => NULL,
'Packed' => NULL,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
),
),
),
'foreign_keys' => NULL,
),
@ -879,6 +981,30 @@ return array (
'IS_GENERATED' => 'NEVER',
'GENERATION_EXPRESSION' => NULL,
),
'created_at' =>
array (
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'apikeys',
'COLUMN_NAME' => 'created_at',
'ORDINAL_POSITION' => 5,
'COLUMN_DEFAULT' => 'current_timestamp()',
'IS_NULLABLE' => 'YES',
'DATA_TYPE' => 'timestamp',
'CHARACTER_MAXIMUM_LENGTH' => NULL,
'CHARACTER_OCTET_LENGTH' => NULL,
'NUMERIC_PRECISION' => NULL,
'NUMERIC_SCALE' => NULL,
'DATETIME_PRECISION' => 0,
'CHARACTER_SET_NAME' => NULL,
'COLLATION_NAME' => NULL,
'COLUMN_TYPE' => 'timestamp',
'COLUMN_KEY' => '',
'EXTRA' => '',
'PRIVILEGES' => 'select,insert,update,references',
'COLUMN_COMMENT' => '',
'IS_GENERATED' => 'NEVER',
'GENERATION_EXPRESSION' => NULL,
),
),
'indexes' =>
array (
@ -1060,6 +1186,30 @@ return array (
'IS_GENERATED' => 'NEVER',
'GENERATION_EXPRESSION' => NULL,
),
'self' =>
array (
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'nameservers',
'COLUMN_NAME' => 'self',
'ORDINAL_POSITION' => 7,
'COLUMN_DEFAULT' => NULL,
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'enum',
'CHARACTER_MAXIMUM_LENGTH' => 3,
'CHARACTER_OCTET_LENGTH' => 12,
'NUMERIC_PRECISION' => NULL,
'NUMERIC_SCALE' => NULL,
'DATETIME_PRECISION' => NULL,
'CHARACTER_SET_NAME' => 'utf8mb4',
'COLLATION_NAME' => 'utf8mb4_unicode_ci',
'COLUMN_TYPE' => 'enum(\'yes\',\'no\')',
'COLUMN_KEY' => '',
'EXTRA' => '',
'PRIVILEGES' => 'select,insert,update,references',
'COLUMN_COMMENT' => '',
'IS_GENERATED' => 'NEVER',
'GENERATION_EXPRESSION' => NULL,
),
),
'indexes' =>
array (

6
dist/systemd/README.md vendored Normal file
View File

@ -0,0 +1,6 @@
Copy these files to /etc/systems/system, adapt the path in the service unit and enable the timer by issuing:
systemctl daemon-reload
systemctl enable bindAPI.timer
systemctl start bindAPI.timer
systemctl list-timers

6
dist/systemd/bindAPI.service vendored Normal file
View File

@ -0,0 +1,6 @@
[Unit]
Description=BindAPI Service to check zone file and reload configuration
[Service]
User=<paneluser>
ExecStart=/home/users/<user>/<bindApi>/bin/console -q cron:run

10
dist/systemd/bindAPI.timer vendored Normal file
View File

@ -0,0 +1,10 @@
[Unit]
Description=Runs BindAPI every minute
[Timer]
OnBootSec=1min
OnUnitActiveSec=1min
Unit=bindAPI.service
[Install]
WantedBy=timers.target

View File

@ -1,17 +1,20 @@
<?php declare(strict_types=1);
namespace App\Controller;
use Exception;
use App\Service\BindAPI;
error_reporting(error_level: E_ALL);
require dirname(path: __DIR__) . '/vendor/autoload.php';
$uri = parse_url(url: $_SERVER['REQUEST_URI'], component: PHP_URL_PATH);
$uri = explode(separator: '/', string: $uri);
$parsedUrl = parse_url(url: $_SERVER['REQUEST_URI'], component: PHP_URL_PATH);
$uri = explode(separator: '/', string: $parsedUrl);
if ($uri[1] !== 'api') {
$baseRoutes = ['app', 'api'];
$uriPrefix = $uriFirstThreeLetters = substr(string: $uri[1], offset: 0, length: 3);
if (!in_array(needle: $uriPrefix, haystack: $baseRoutes)) {
// only handle $baseRoutes, elso go to swagger ui
$scheme = $_SERVER['REQUEST_SCHEME'];
$host = $_SERVER['SERVER_NAME'];
$header = "$scheme://$host/openapi/index.html";
@ -23,15 +26,22 @@ header(header: "Access-Control-Allow-Origin: *");
header(header: "Content-Type: application/json; charset=UTF-8");
header(header: "Access-Control-Allow-Methods: OPTIONS,GET,POST,PUT,DELETE");
header(header: "Access-Control-Max-Age: 3600");
header(header: "Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
header(header: "Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With, x-api-key");
$requestMethod = $_SERVER["REQUEST_METHOD"];
if ($requestMethod === "OPTIONS") {
// Respond with OK status code for preflight requests
http_response_code(response_code: 200);
exit();
}
try {
$app = new BindAPI();
$app = new BindAPI(quiet: false);
$app->handleRequest(requestMethod: $requestMethod, uri: $uri);
} catch (Exception $e) {
echo json_encode(value: [
'error' => $e->getMessage()
]);
}

View File

@ -0,0 +1,5 @@
<?php
const DEFAULT_NS = 'ns2.24unix.net';
const NAMESERVERS = ['ns1.24unix.net', 'ns2.24unix.net'];

View File

@ -19,7 +19,8 @@
<script>
window.onload = function () {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
let ui;
ui = SwaggerUIBundle({
url: "/openapi/bindapi.json",
dom_id: "#swagger-ui",
deepLinking: true,

View File

@ -1,94 +0,0 @@
<?php declare(strict_types=1);
namespace App\Controller;
error_reporting(error_level: E_ALL);
use App\Repository\DomainRepository;
use App\Repository\DynDNSRepository;
use DI\Container;
use DI\ContainerBuilder;
use DI\DependencyException;
use DI\NotFoundException;
use Exception;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\StreamHandler;
use Monolog\Level;
use Monolog\Logger;
use function DI\autowire;
/**
*
*/
class BindAPI
{
private Logger $logger;
private Container $container;
/**
* @throws Exception
*/
public function __construct(bool $quiet)
{
// init the logger
$dateFormat = "Y:m:d H:i:s";
$output = "%datetime% %channel%.%level_name% %message%\n"; // %context% %extra%
$formatter = new LineFormatter(format: $output, dateFormat: $dateFormat);
$debug = (new ConfigController(quiet: $quiet))->getConfig(configKey: 'debug');
if ($debug) {
$stream = new StreamHandler(stream: dirname(path: __DIR__, levels: 2) . '/bindAPI.log', level: Level::Debug);
} else {
$stream = new StreamHandler(stream: dirname(path: __DIR__, levels: 2) . '/bindAPI.log', level: Level::Info);
}
$stream->setFormatter(formatter: $formatter);
$this->logger = new Logger(name: 'bindAPI');
$this->logger->pushHandler(handler: $stream);
$this->logger->debug(message: 'bindAPI started');
$containerBuilder = new ContainerBuilder();
$containerBuilder->addDefinitions([
ConfigController::class => autowire()
->constructorParameter(parameter: 'quiet', value: $quiet),
CLIController::class => autowire()
->constructorParameter(parameter: 'logger', value: $this->logger)
->constructorParameter(parameter: 'quiet', value: $quiet),
DomainController::class => autowire()
->constructorParameter(parameter: 'logger', value: $this->logger)
->constructorParameter(parameter: 'quiet', value: $quiet),
DomainRepository::class => autowire()
->constructorParameter(parameter: 'logger', value: $this->logger),
DynDnsRepository::class => autowire()
->constructorParameter(parameter: 'logger', value: $this->logger),
RequestController::class => autowire()
->constructorParameter(parameter: 'logger', value: $this->logger)
]);
$this->container = $containerBuilder->build();
}
/**
* @throws DependencyException
* @throws NotFoundException
*/
public function runCommand(array $arguments): void
{
$this->logger->debug(message: 'runCommand()');
$cliController = $this->container->get(name: CLIController::class);
$cliController->runCommand(arguments: $arguments);
}
/**
* @throws DependencyException
* @throws NotFoundException
*/
public function handleRequest(string $requestMethod, array $uri): void
{
$this->logger->debug(message: 'handleRequest()');
$requestController = $this->container->get(name: RequestController::class);
$requestController->handleRequest(requestMethod: $requestMethod, uri: $uri);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,8 @@
namespace App\Controller\Commands;
use App\Utilities\Colors;
/**
*
*/
@ -31,17 +33,17 @@ class CommandGroup
public function printCommands(int $longestCommandLength): void
{
echo COLOR_YELLOW . str_pad(string: $this->name, length: $longestCommandLength + 1) . COLOR_WHITE . $this->description . COLOR_DEFAULT . PHP_EOL;
echo Colors::YELLOW . str_pad(string: $this->name, length: $longestCommandLength + 1) . Colors::WHITE . $this->description . Colors::DEFAULT . PHP_EOL;
foreach ($this->commands as $command) {
echo COLOR_GREEN . str_pad(string: ' ', length: $longestCommandLength + 1, pad_type: STR_PAD_LEFT) . $this->name . ':' . $command->getName();
echo Colors::GREEN . str_pad(string: ' ', length: $longestCommandLength + 1, pad_type: STR_PAD_LEFT) . $this->name . ':' . $command->getName();
foreach ($command->getMandatoryParameters() as $optionals) {
echo ' <' . $optionals . '>';
}
foreach ($command->getOptionalParameters() as $mandatory) {
echo ' {' . $mandatory . '}';
}
echo COLOR_WHITE . ' ' . $command->getDescription();
echo COLOR_DEFAULT . PHP_EOL;
echo Colors::WHITE . ' ' . $command->getDescription();
echo Colors::DEFAULT . PHP_EOL;
}
}

View File

@ -2,6 +2,8 @@
namespace App\Controller\Commands;
use App\Utilities\Colors;
/**
*
*/
@ -64,23 +66,23 @@ class CommandGroupContainer
if ($group->exec(subcommand: $subcommand)) {
exit(0);
} else {
echo COLOR_DEFAULT . 'Unknown subcommand ' . COLOR_YELLOW . $subcommand . COLOR_DEFAULT .' for ' . COLOR_YELLOW . $command . COLOR_DEFAULT . '.' . PHP_EOL;
echo Colors::DEFAULT . 'Unknown subcommand ' . Colors::YELLOW . $subcommand . Colors::DEFAULT .' for ' . Colors::YELLOW . $command . Colors::DEFAULT . '.' . PHP_EOL;
exit(1);
}
} else {
echo COLOR_DEFAULT . 'Unknown subcommand ' . COLOR_YELLOW . $subcommand . COLOR_DEFAULT .' for ' . COLOR_YELLOW . $command . COLOR_DEFAULT . '.' . PHP_EOL;
echo Colors::DEFAULT . 'Unknown command group ' . Colors::YELLOW . $command . Colors::DEFAULT . '.' . PHP_EOL;
exit(1);
}
} else {
// check for command group and print available commands
foreach ($this->commandGroups as $group) {
if ($group->getName() === $command) {
echo 'Available subcommands for: ' . COLOR_YELLOW . $group->getName() . COLOR_DEFAULT . ':' . PHP_EOL;
echo 'Available subcommands for: ' . Colors::YELLOW . $group->getName() . Colors::DEFAULT . ':' . PHP_EOL;
$group->printCommands(strlen(string: $group->getName()));
exit(0);
}
}
}
echo COLOR_DEFAULT . 'Unknown command ' . COLOR_YELLOW . $command . COLOR_DEFAULT . '.' . PHP_EOL;
echo Colors::DEFAULT . 'Unknown command ' . Colors::YELLOW . $command . Colors::DEFAULT . '.' . PHP_EOL;
}
}

View File

@ -2,14 +2,14 @@
namespace App\Controller;
/**
*
*/
use App\Utilities\Colors;
class ConfigController
{
private array $config;
private static $missingEncryptionShown = false;
public function __construct(bool $quiet, bool $test = false)
public function __construct(private readonly bool $quiet, bool $test = false)
{
if ($test) {
@ -38,21 +38,33 @@ class ConfigController
}
$configJSON = file_get_contents(filename: $configFile);
if (json_decode(json: $configJSON) === null) {
// first check if json is valid, after make the assignment
if (json_decode(json: $configJSON, associative: true) === null) {
echo 'Config file is not valid JSON.' . PHP_EOL;
echo $configJSON . PHP_EOL;
exit(1);
}
$this->config = json_decode(json: $configJSON, associative: true);
if (!ConfigController::$missingEncryptionShown) {
if (!isset($this->config['encryptionKey']) || ($this->config['encryptionKey'] === '1bad::babe')) {
ConfigController::$missingEncryptionShown = true;
if (!$this->quiet) {
echo Colors::RED . 'Error: ' . Colors::DEFAULT . 'No encryption key, please run ' . Colors::YELLOW . './bin/console check:generatekey' . Colors::DEFAULT . PHP_EOL;
}
exit(1);
}
}
$this->config['quiet'] = (bool)$quiet;
$this->config['test'] = (bool)$test;
}
public function getConfig(string $configKey): string
public function getConfig(string $configKey): ?string
{
return $this->config[$configKey];
if (isset($this->config[$configKey])) {
return $this->config[$configKey];
} else {
return null;
}
}
}

View File

@ -6,6 +6,8 @@ use App\Entity\Domain;
use App\Repository\DomainRepository;
use App\Repository\NameserverRepository;
use App\Repository\PanelRepository;
use App\Service\ApiClient;
use App\Utilities\Colors;
use Monolog\Logger;
error_reporting(error_level: E_ALL);
@ -25,7 +27,7 @@ class DomainController
public function __construct(
private readonly NameserverRepository $nameserverRepository,
private readonly ApiController $checkController,
private readonly ApiClient $checkController,
private readonly DomainRepository $domainRepository,
private readonly PanelRepository $panelRepository,
private readonly ConfigController $configController,
@ -70,28 +72,58 @@ class DomainController
function updateSlaveZones(): void
{
$this->logger->debug(message: 'Delete all slave zones');
$zones = glob(pattern: $this->localZonesDir . '*');
foreach ($zones as $zone) {
unlink(filename: $zone);
}
$this->logger->debug(message: 'update slave zones');
$existingZones = glob(pattern: $this->localZonesDir . '*');
$domains = $this->domainRepository->findAll();
$longestEntry = $this->domainRepository->getLongestEntry('name');
$longestEntry = $this->domainRepository->getLongestEntry(field: 'name');
$self = $this->panelRepository->getSelf();
foreach ($domains as $domain) {
$zoneFile = $this->localZonesDir . $domain->getName();
if (!$this->quiet) {
echo ' ' . COLOR_YELLOW . str_pad($domain->getName(), $longestEntry + 1, " ", STR_PAD_RIGHT) ;
echo ' ' . Colors::YELLOW . str_pad(string: $domain->getName(), length: $longestEntry + 1, pad_string: " ", pad_type: STR_PAD_RIGHT) ;
}
if ($this->createSlaveZoneFile(domain: $domain)) {
if (strcmp(string1: $self->getName(), string2: $domain->getPanel()) !== 0) {
if (!file_exists(filename: $zoneFile)) {
if (!$this->quiet) {
echo Colors::GREEN . ' OK' . Colors::DEFAULT . PHP_EOL;
}
$this->createSlaveZoneFile(domain: $domain);
} else {
if (($key = array_search(needle: $zoneFile, haystack: $existingZones)) !== false) {
if (isset($existingZones[$key])) {
unset($existingZones[$key]);
}
} else {
echo 'missing value: ' . $zoneFile;
}
if (!$this->quiet) {
echo Colors::DEFAULT . 'Zone already exists.' . PHP_EOL;
}
}
} else {
if (!$this->quiet) {
echo COLOR_GREEN . ' OK' . COLOR_DEFAULT . PHP_EOL;
echo Colors::DEFAULT . 'We are master for ' . Colors::YELLOW . $domain->getName() . PHP_EOL;
}
}
}
// remove stale zones
foreach ($existingZones as $zone) {
if (!$this->quiet) {
echo 'Removing stale zone: ' . Colors::YELLOW . $zone . Colors::DEFAULT . PHP_EOL;
}
echo $zone . PHP_EOL;
unlink(filename: $zone);
}
$this->createIncludeFile();
$semaphore = $this->localZonesDir . 'zones.flag';
if (file_exists(filename: $semaphore)) {
unlink(filename: $semaphore);
$this->createIncludeFile();
}
}
@ -159,12 +191,12 @@ class DomainController
$uid = posix_geteuid();
}
if (!$this->quiet) {
echo "UID:\t" . COLOR_YELLOW . $uid . PHP_EOL;
echo "UID:\t" . Colors::YELLOW . $uid . PHP_EOL;
}
$pwuid = posix_getpwuid(user_id: $uid);
$name = $pwuid['name'];
if (!$this->quiet) {
echo COLOR_DEFAULT . "Name:\t" . COLOR_YELLOW . $name . PHP_EOL;
echo Colors::DEFAULT . "Name:\t" . Colors::YELLOW . $name . PHP_EOL;
}
if (!$bindGroup = posix_getgrnam(name: 'bind')) {
@ -173,40 +205,40 @@ class DomainController
$members = $bindGroup['members'] ?? [];
if (in_array(needle: $name, haystack: $members)) {
if (!$this->quiet) {
echo "\t$name" . COLOR_DEFAULT . ' is in group ' . COLOR_YELLOW . 'bind' . PHP_EOL;
echo "\t$name" . Colors::DEFAULT . ' is in group ' . Colors::YELLOW . 'bind' . PHP_EOL;
}
} else {
$setupIsValid = false;
if (!$this->quiet) {
echo COLOR_RED . "\t$name needs to be in group " . COLOR_YELLOW . 'bind' . COLOR_DEFAULT . '!' . PHP_EOL;
echo Colors::RED . "\t$name needs to be in group " . Colors::YELLOW . 'bind' . Colors::DEFAULT . '!' . PHP_EOL;
}
}
if (!$this->quiet) {
echo COLOR_DEFAULT . 'Checking ' . COLOR_YELLOW . $this->localZoneFile . PHP_EOL;
echo Colors::DEFAULT . 'Checking ' . Colors::YELLOW . $this->localZoneFile . PHP_EOL;
}
$localZoneFilePermissions = @fileperms(filename: $this->localZoneFile);
if ($localZoneFilePermissions & 0x0010) {
if (!$this->quiet) {
echo COLOR_DEFAULT . "\t✅ Group has write access." . PHP_EOL;
echo Colors::DEFAULT . "\t✅ Group has write access." . PHP_EOL;
}
} else {
$setupIsValid = false;
if (!$this->quiet) {
echo COLOR_RED . "\t❌Group needs write permission!" . COLOR_DEFAULT . PHP_EOL;
echo Colors::RED . "\t❌Group needs write permission!" . Colors::DEFAULT . PHP_EOL;
}
}
if (!$this->quiet) {
echo 'Checking ' . COLOR_YELLOW . $this->namedConfLocalFile . PHP_EOL;
echo 'Checking ' . Colors::YELLOW . $this->namedConfLocalFile . PHP_EOL;
}
if (file_exists(filename: $this->namedConfLocalFile) && $namedConfLocal = file_get_contents(filename: $this->namedConfLocalFile)) {
if (!str_contains(haystack: $namedConfLocal, needle: $this->localZoneFile)) {
$setupIsValid = false;
if (!$this->quiet) {
echo "\t$this->localZoneFile" . COLOR_RED . ' needs to be included in ' . COLOR_YELLOW . $this->namedConfLocalFile . PHP_EOL;
echo "\t$this->localZoneFile" . Colors::RED . ' needs to be included in ' . Colors::YELLOW . $this->namedConfLocalFile . PHP_EOL;
}
} else {
if (!$this->quiet) {
echo "\t$this->localZoneFile" . COLOR_DEFAULT . ' is included in ' . COLOR_YELLOW . $this->namedConfLocalFile . PHP_EOL;
echo "\t$this->localZoneFile" . Colors::DEFAULT . ' is included in ' . Colors::YELLOW . $this->namedConfLocalFile . PHP_EOL;
}
}
} else {
@ -216,7 +248,7 @@ class DomainController
}
}
if (!$this->quiet) {
echo COLOR_DEFAULT . 'Checking directory: ' . COLOR_YELLOW . $this->localZonesDir . PHP_EOL;
echo Colors::DEFAULT . 'Checking directory: ' . Colors::YELLOW . $this->localZonesDir . PHP_EOL;
}
$localZoneDirPermissions = @fileperms(filename: $this->localZonesDir);
if ($localZoneDirPermissions & 0x0010) {
@ -226,7 +258,7 @@ class DomainController
} else {
$setupIsValid = false;
if (!$this->quiet) {
echo COLOR_RED . "\t❌Group needs write permission!" . PHP_EOL;
echo Colors::RED . "\t❌Group needs write permission!" . PHP_EOL;
}
}
return $setupIsValid;
@ -240,7 +272,7 @@ class DomainController
{
if (!file_exists(filename: $this->localZoneFile)) {
if (!$this->quiet) {
echo COLOR_DEFAULT . 'Local Zone file ' . COLOR_YELLOW . $this->localZoneFile . COLOR_DEFAULT . ' does not exist.' . PHP_EOL;
echo Colors::DEFAULT . 'Local Zone file ' . Colors::YELLOW . $this->localZoneFile . Colors::DEFAULT . ' does not exist.' . PHP_EOL;
}
exit(1);
}
@ -251,33 +283,33 @@ class DomainController
foreach ($domains as $domain) {
$idString = '(' . $domain->getId() . ') ';
if (!$this->quiet) {
echo COLOR_YELLOW .
echo Colors::YELLOW .
str_pad(string: $domain->getName(), length: $maxNameLength + 1)
. COLOR_DEFAULT
. Colors::DEFAULT
. str_pad(string: $idString, length: 7, pad_type: STR_PAD_LEFT);
}
$hasError = false;
if ($this->isMasterZone(domain: $domain)) {
if (!$this->quiet) {
echo COLOR_GREEN . 'Master Zone';
echo Colors::GREEN . 'Master Zone';
}
} else {
if (!str_contains(haystack: $localZones, needle: $domain->getName())) {
if (!$this->quiet) {
echo COLOR_RED . 'is missing in ' . COLOR_YELLOW . $this->localZoneFile . COLOR_DEFAULT;
echo Colors::RED . 'is missing in ' . Colors::YELLOW . $this->localZoneFile . Colors::DEFAULT;
}
$hasError = true;
} else {
if (!$this->quiet) {
echo COLOR_GREEN . 'OK';
echo Colors::GREEN . 'OK';
}
}
$zoneFile = $this->localZonesDir . $domain->getName();
if (!file_exists(filename: $zoneFile)) {
echo ' Missing zone file for ' . COLOR_YELLOW . $zoneFile . COLOR_DEFAULT;
echo ' Missing zone file for ' . Colors::YELLOW . $zoneFile . Colors::DEFAULT;
$hasError = true;
}
@ -286,7 +318,7 @@ class DomainController
}
}
if (!$this->quiet) {
echo COLOR_DEFAULT . PHP_EOL;
echo Colors::DEFAULT . PHP_EOL;
}
}
@ -300,7 +332,9 @@ class DomainController
*/
public function createSlaveZoneFile(Domain $domain): bool
{
$domainName = $domain->getName();
touch(filename: $this->localZonesDir . 'zones.flag');