Compare commits

..

12 Commits

Author SHA1 Message Date
tracer 0a67c16862 added findFirst()
Signed-off-by: tracer <tracer@24unix.net>
2022-04-06 16:29:14 +02:00
tracer b90d91fda2 improved handling os dyndns endpoints
Signed-off-by: tracer <tracer@24unix.net>
2022-04-06 16:28:38 +02:00
tracer ddae287748 modified handling of slave zones
Signed-off-by: tracer <tracer@24unix.net>
2022-04-06 16:27:58 +02:00
tracer 22e2d57b61 added support for dyndns
Signed-off-by: tracer <tracer@24unix.net>
2022-04-06 16:26:35 +02:00
tracer d6922be2a4 added zone type
Signed-off-by: tracer <tracer@24unix.net>
2022-04-06 16:26:08 +02:00
tracer 2f79e39a8c changed php path
Signed-off-by: tracer <tracer@24unix.net>
2022-04-06 16:25:27 +02:00
tracer 7036633a8b fixed some typos
Signed-off-by: tracer <tracer@24unix.net>
2022-04-06 16:24:30 +02:00
tracer 69cca0c2a7 initial commit
Signed-off-by: tracer <tracer@24unix.net>
2022-04-06 16:23:57 +02:00
tracer 47585fccd4 initial commit
Signed-off-by: tracer <tracer@24unix.net>
2022-04-06 16:23:30 +02:00
tracer 8635c9625f added logger
Signed-off-by: tracer <tracer@24unix.net>
2022-04-06 16:22:56 +02:00
tracer 747b157ac3 initial commit
Signed-off-by: tracer <tracer@24unix.net>
2022-04-06 16:22:27 +02:00
tracer f803570ba7 changed get to post for dyndns
Signed-off-by: tracer <tracer@24unix.net>
2022-04-04 14:53:53 +02:00
12 changed files with 579 additions and 107 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@
/swagger-ui/
/config.json.local
/bindAPI.log

View File

@ -92,7 +92,7 @@ Here I will install it under /usr/local/bin, in the example with the standalone
`wget https://getcomposer.org/installer`
`php composer-setup.php --install-dir=/usr/local/bin --filename=composer`
`php installer --install-dir=/usr/local/bin --filename=composer`
Now we can change into our new user, remind to give him shell access in the panel.
@ -296,10 +296,10 @@ GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, DROP, ALTER, CREATE TEMPORA
There is no need to run FLUSH PRIVILEGES when using GRANT!
```
So, now it offers us the SQL statements to create a new user and database and set permissions.
If were on plain debian, we just can copy and paste (the password is random) this as root into mysql.
So, now it offers us the SQL statements to create a new user and database and set permissions. If were on plain debian,
we just can copy and paste (the password is random) this as root into mysql.
If we're using hte panel, lets create a database and write down the credentials and update config.json.
If we're using the panel, lets create a database and write down the credentials and update config.json.
And another call to the console:
@ -420,7 +420,7 @@ We create a new key:
```
$ ./bin/console apikeys:create
API key 1 has been generated. Store it in a save place, it cannot be recovered.
6213acb116613.[truncated]]
6213acb116613.[truncated]
```
And add it to our list of nameservers:

View File

@ -1,6 +1,7 @@
#!/usr/bin/keyhelp-php81
#!/usr/local/bin/php
<?php declare(strict_types=1);
namespace App\Controller;
// #!/usr/bin/keyhelp-php81
// & ~E_DEPRECATED is needed because of a bug in PhpStorm
use DI\DependencyException;

View File

@ -70,7 +70,7 @@
}
},
"/dyndns/{hostname}": {
"get": {
"post": {
"tags": [
"DNS"
],

View File

@ -384,6 +384,7 @@ class BindAPI
return true;
}
/**
* @param String $domainName
* @param \App\Entity\Panel $panel
@ -470,6 +471,7 @@ class BindAPI
exit(1);
}
if ($create['header'] != 201) {
print_r(value: $create);
die("make error handling");
} else {
echo COLOR_GREEN . 'OK' . COLOR_DEFAULT;
@ -1011,7 +1013,7 @@ class BindAPI
}
if (!empty($domains)) {
$table = new ConsoleTable();
$table->setHeaders(content: ['ID', 'Name', 'Panel']);
$table->setHeaders(content: ['ID', 'Name', 'Panel', 'Type']);
/** @var Domain $domain */
foreach ($domains as $domain) {
$row = [];
@ -1019,6 +1021,7 @@ class BindAPI
$row[] = $domain->getId();
$row[] = $domain->getName();
$row[] = $domain->getPanel();
$row[] = $this->domainController->isMasterZone(domain: $domain) ? 'MASTER' : 'SLAVE';
} catch (DependencyException|NotFoundException $e) {
echo $e->getMessage();
}
@ -1105,14 +1108,14 @@ class BindAPI
if (empty($name) && empty($panel)) {
echo 'No name or panel given, just recreate the config file' . PHP_EOL;
$this->domainController->createSlaveZoneFile(domain: $domain);
$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->createSlaveZoneFile(domain: $domain);
$this->domainController->updateSlaveZones();
} else {
echo 'Error while updating domain server.' . PHP_EOL;
}

View File

@ -19,6 +19,7 @@ class DatabaseConnection
const TABLE_NAMESERVERS = self::TABLE_PREFIX . "nameservers";
const TABLE_PANELS = self::TABLE_PREFIX . "panels";
const TABLE_APIKEYS = self::TABLE_PREFIX . "apikeys";
const TABLE_DYNDNS = self::TABLE_PREFIX . "dyndns";
public function __construct(private array $config)
{
@ -83,6 +84,19 @@ class DatabaseConnection
$statement = $this->dbConnection->prepare(query: $sql);
$statement->execute();
$sql = "
CREATE TABLE `dyndns` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`a` VARBINARY(255) NOT NULL,
`aaaa` VARBINARY(255) NOT NULL,
`last_update` TIMESTAMP NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
$statement = $this->dbConnection->prepare(query: $sql);
$statement->execute();
echo 'Tables have been created.' . PHP_EOL;
}
exit(1);

View File

@ -67,6 +67,30 @@ class DomainController
}
function updateSlaveZones()
{
if ($this->config['verbose']) {
echo 'Delete all slave zones';
}
$zones = glob(pattern: $this->localZonesDir . '*');
foreach ($zones as $zone) {
unlink(filename: $zone);
}
$domains = $this->domainRepository->findAll();
foreach ($domains as $domain) {
if ($this->config['verbose']) {
echo 'Create zone: ' . $domain->getName() . PHP_EOL;
}
$this->createSlaveZoneFile(domain: $domain);
}
$this->createIncludeFile();
}
function deleteOnNameservers(Domain $domain)
{
if ($this->config['debug']) {
@ -202,25 +226,28 @@ class DomainController
*/
public function createSlaveZoneFile(Domain $domain): void
{
if ($this->config['debug']) {
$domainName = $domain->getName();
if ($this->config['debug']) {
$this->log->debug(message: "createZoneFile($domainName)");
}
// check if we're a master zone
if ($this->isMasterZone(domain: $domain)) {
echo 'We are zone master for ' . $domain->getName() . PHP_EOL;
echo 'We are zone master for ' . $domainName . PHP_EOL;
exit(1);
}
if ($zonefile = fopen(filename: $this->localZonesDir . $domain->getName(), mode: 'w')) {
if ($zonefile = fopen(filename: $this->localZonesDir . $domainName, mode: 'w')) {
$panelName = $domain->getPanel();
$panel = $this->panelRepository->findByName(name: $panelName);
if (!$panel = $this->panelRepository->findByName(name: $panelName)) {
echo "Error: Panel $panelName doesn't exist." . PHP_EOL;
die();
}
$a = $panel->getA();
$aaaa = $panel->getAaaa();
fputs(stream: $zonefile, data: 'zone "' . $domain->getName() . '"' . ' IN {' . PHP_EOL);
fputs(stream: $zonefile, data: 'zone "' . $domainName . '"' . ' IN {' . PHP_EOL);
fputs(stream: $zonefile, data: "\ttype slave;" . PHP_EOL);
fputs(stream: $zonefile, data: "\tfile \"" . $this->zoneCachePath . $domain->getName() . '.db";' . PHP_EOL);
fputs(stream: $zonefile, data: "\tfile \"" . $this->zoneCachePath . $domainName . '.db";' . PHP_EOL);
fputs(stream: $zonefile, data: "\tmasters {" . PHP_EOL);
if (!empty($a)) {
fputs(stream: $zonefile, data: "\t\t" . $a . ';' . PHP_EOL);
@ -231,10 +258,10 @@ class DomainController
fputs(stream: $zonefile, data: "\t};" . PHP_EOL);
fputs(stream: $zonefile, data: "};" . PHP_EOL);
}
$this->createIncludeFile();
}
private function isMasterZone(Domain $domain): bool
public function isMasterZone(Domain $domain): bool
{
if (file_exists(filename: '/etc/bind/keyhelp_domains/' . $domain->getName())) {
return true;

View File

@ -5,8 +5,10 @@ namespace App\Controller;
error_reporting(error_level: E_ALL);
use App\Entity\Domain;
use App\Entity\DynDNS;
use App\Repository\ApikeyRepository;
use App\Repository\DomainRepository;
use App\Repository\DynDNSRepository;
use App\Repository\PanelRepository;
use DI\Container;
use DI\ContainerBuilder;
@ -55,6 +57,7 @@ class RequestController
private DomainController $domainController;
private DomainRepository $domainRepository;
private PanelRepository $panelRepository;
private DynDNSRepository $DynDNSRepository;
private Container $container;
private string $header;
private array $result;
@ -73,7 +76,6 @@ class RequestController
{
$this->requestMethod = strtoupper(string: $requestMethod);
$dateFormat = "Y:m:d H:i:s";
$output = "%datetime% %channel%.%level_name% %message%\n"; // %context% %extra%
$formatter = new LineFormatter(format: $output, dateFormat: $dateFormat);
@ -84,6 +86,10 @@ class RequestController
$this->log = new Logger(name: 'bindAPI');
$this->log->pushHandler(handler: $stream);
if ($this->config['debug']) {
$this->log->debug(message: 'RequestController::__construct');
}
$containerBuilder = new ContainerBuilder();
$containerBuilder->addDefinitions([
@ -94,6 +100,9 @@ class RequestController
DomainRepository::class => autowire()
->constructorParameter(parameter: 'config', value: $this->config)
->constructorParameter(parameter: 'log', value: $this->log),
DynDNSRepository::class => autowire()
->constructorParameter(parameter: 'config', value: $this->config)
->constructorParameter(parameter: 'log', value: $this->log),
]);
$this->container = $containerBuilder->build();
@ -103,6 +112,7 @@ class RequestController
$this->domainController = $this->container->get(name: DomainController::class);
$this->domainRepository = $this->container->get(name: DomainRepository::class);
$this->panelRepository = $this->container->get(name: PanelRepository::class);
$this->dynDNSRepository = $this->container->get(name: DynDNSRepository::class);
}
/**
@ -288,7 +298,6 @@ class RequestController
echo $this->status;
} else {
echo json_encode(value: [
'status' => $this->status ?? "Error: No status",
'message' => $this->message ?? "Error: No message."
]);
}
@ -516,6 +525,10 @@ class RequestController
private function handleDynDNS()
{
if ($this->config['debug']) {
$this->log->debug(message: 'handleDynDNS()');
}
if ($this->checkPassword()) {
$host = $this->uri[3] ?? '';
@ -536,9 +549,58 @@ class RequestController
}
}
if ($this->config['debug']) {
$this->log->debug(message: 'a: ' . $a);
$this->log->debug(message: 'aaaa: ' . $aaaa);
}
$domainName = $this->getDomain(host: $host);
$hostName = str_replace(search: '.' . $domainName, replace: '', subject: $host);
$domain = $this->domainRepository->findByName(name: $domainName);
if (!$domain = $this->domainRepository->findByName(name: $domainName)) {
$this->header = '404 Not Found';
$this->message = 'Domain ' . $domainName . ' not found';
} else {
// check if address has changed
if ($dynDNS = $this->dynDNSRepository->findByName(name: $host)) {
echo 'found host';
print_r($dynDNS);
print("a: $a");
print("aaaa: $aaaa");
$ipChanged = false;
if (!empty($a)) {
if ($a != $dynDNS->getA()) {
echo $a . '!=' . $dynDNS->getA();
$dynDNS->setA(a: $a);
$ipChanged = true;
}
}
if (!empty($aaaa)) {
if ($aaaa != $dynDNS->getAaaa()) {
$dynDNS->setAaaa(aaaa: $aaaa);
$ipChanged = true;
}
}
if (!$ipChanged) {
$this->header = '304 Not Modified';
$this->message = 'Not modified';
} else {
$this->dynDNSRepository->update(dynDNS: $dynDNS);
}
} else {
$dynDNS = new DynDNS(name: $host, a: $a, aaaa: $aaaa);
$this->dynDNSRepository->insert(dynDNS: $dynDNS);
}
$panel = $this->panelRepository->findByName(name: $domain->getPanel());
if (!empty($panel->getAaaa())) {
@ -634,11 +696,12 @@ class RequestController
}
if ($result['header'] == 200) {
$this->header = '200 OK';
$this->status = json_encode(value: ['message' => 'DynDNS host successfully updated']);
$this->message = 'DynDNS host successfully updated';
}
} else {
$this->header = '404 Not Found';
$this->status = 'Host ' . COLOR_YELLOW . $hostName . ' not found';
$this->message = 'Host ' . $hostName . ' not found';
}
}
}
}

82
src/Entity/DynDNS.php Normal file
View File

@ -0,0 +1,82 @@
<?php declare(strict_types=1);
namespace App\Entity;
/**
*
*/
class DynDNS
{
/**
*/
public function __construct(private string $name, private string $a, private string $aaaa, private int $id = 0)
{
}
/**
* @return String
*/
public function getA(): string
{
return $this->a;
}
/**
* @return String
*/
public function getAaaa(): string
{
return $this->aaaa;
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @return String
*/
public function getName(): string
{
return $this->name;
}
/**
* @param String $a
*/
public function setA(string $a): void
{
$this->a = $a;
}
/**
* @param String $aaaa
*/
public function setAaaa(string $aaaa): void
{
$this->aaaa = $aaaa;
}
/**
* @param int $id
*/
public function setId(int $id): void
{
$this->id = $id;
}
/**
* @param String $name
*/
public function setName(string $name): void
{
$this->name = $name;
}
}

14
src/Enums/PanelType.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace App\Enums;
/**
*
*/
enum PanelType
{
case Panel;
case Custom;
case Undefined;
}

View File

@ -0,0 +1,245 @@
<?php declare(strict_types=1);
namespace App\Repository;
use App\Controller\DatabaseConnection;
use App\Entity\DynDNS;
use Monolog\Logger;
use PDO;
use PDOException;
/**
*
*/
class DynDNSRepository
{
public function __construct(private DatabaseConnection $databaseConnection, private array $config, private Logger $log)
{
if ($this->config['debug']) {
$this->log->debug(message: "DynDNSRepository::__construct()");
}
}
/**
* @param \App\Entity\DynDNS $dynDNS
*
* @return int
*/
public function delete(DynDNS $dynDNS): int
{
$dynDNSName = $dynDNS->getName();
if ($this->config['debug']) {
$this->log->debug(message: "delete($dynDNSName)");
}
$sql = "
DELETE FROM " . DatabaseConnection::TABLE_DYNDNS . "
WHERE id = :id";
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$id = $dynDNS->getId();
$statement->bindParam(param: 'id', var: $id);
$statement->execute();
return $statement->rowCount();
} catch (PDOException $e) {
exit($e->getMessage());
}
}
/**
* @return array
*/
public function findAll(): array
{
if ($this->config['debug']) {
$this->log->debug(message: "findAll()");
}
$dyndns = [];
$sql = "
SELECT id, name, a, aaaa
FROM " . DatabaseConnection::TABLE_DYNDNS . "
ORDER BY name";
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->execute();
while ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
$dyndns = new DynDNS(name: $result['name'], a: $result['a'], aaaa: $result['aaaa'], id: $result['id']);
$dyndns[] = $dyndns;
}
return $dyndns;
} catch (PDOException $e) {
exit($e->getMessage());
}
}
/**
* @param String $name
*
* @return \App\Entity\Domain|bool
*/
public function findByName(string $name): DynDNS|bool
{
if ($this->config['debug']) {
$this->log->debug(message: "findByName($name)");
}
$sql = "
SELECT id, name, a, aaaa
FROM " . DatabaseConnection::TABLE_DYNDNS . "
WHERE name = :name";
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->bindParam(param: ':name', var: $name);
$statement->execute();
if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
return new DynDNS(name: $result['name'], a: $result['a'], aaaa: $result['aaaa'], id: $result['id']);
} else {
return false;
}
} catch (PDOException $e) {
exit($e->getMessage());
}
}
/**
* @param String $field
*
* @return int
*/
public function getLongestEntry(string $field): int
{
$sql = "
SELECT MAX(LENGTH(" . $field . ")) as length FROM " . DatabaseConnection::TABLE_DYNDNS;
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->execute();
$result = $statement->fetch();
return $result['length'];
} catch (PDOException $e) {
exit($e->getMessage());
}
}
/**
* @param \App\Entity\DynDNS $dynDNS
*
* @return string|false
*/
public function insert(DynDNS $dynDNS): bool|string
{
$dynDNSName = $dynDNS->getName();
if ($this->config['debug']) {
$this->log->debug(message: "insert($dynDNSName)");
}
$sql = "
INSERT INTO " . DatabaseConnection::TABLE_DYNDNS . " (name, a, aaaa)
VALUES (:name, :a, :aaaa)";
try {
$a = $dynDNS->getA();
$aaaa = $dynDNS->getAaaa();
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->bindParam(param: ':name', var: $dynDNSName);
$statement->bindParam(param: ':a', var: $a);
$statement->bindParam(param: ':aaaa', var: $aaaa);
$statement->execute();
return $this->databaseConnection->getConnection()->lastInsertId();
} catch (PDOException $e) {
exit($e->getMessage());
}
}
/**
* @param \App\Entity\DynDNS $dynDNS
*
* @return false|int
*/
public function update(DynDNS $dynDNS): bool|int
{
$dynDNSnName = $dynDNS->getName();
if ($this->config['debug']) {
$this->log->debug(message: "update($dynDNSnName)");
}
$id = $dynDNS->getId();
$current = $this->findByID(id: $id);
if (empty($dynDNSnName)) {
$name = $current->getName();
} else {
$name = $dynDNSnName;
}
if (empty($dynDNS->getA())) {
$a = $current->getA();
} else {
$a = $dynDNS->getA();
}
if (empty($dynDNS->getAaaa())) {
$aaaa = $current->getAaaa();
} else {
$aaaa = $dynDNS->getAaaa();
}
$sql = "
UPDATE " . DatabaseConnection::TABLE_DYNDNS . " SET
name = :name,
a = :a,
aaaa = :aaaa
WHERE id = :id";
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->bindParam(param: 'id', var: $id);
$statement->bindParam(param: 'name', var: $name);
$statement->bindParam(param: 'a', var: $a);
$statement->bindParam(param: 'aaaa', var: $aaaa);
$statement->execute();
return $statement->rowCount();
} catch (PDOException $e) {
echo $e->getMessage();
return false;
}
}
/**
* @param int $id
*
* @return bool|\App\Entity\Domain
*/
public function findByID(int $id): bool|DynDNS
{
if ($this->config['debug']) {
$this->log->debug(message: "findById($id)");
}
$sql = "
SELECT id, name, a, aaaa
FROM . " . DatabaseConnection::TABLE_DYNDNS . "
WHERE id = :id";
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->bindParam(param: ':id', var: $id);
$statement->execute();
if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
return new DynDNS(name: $result['name'], a: $result['a'], aaaa: $result['aaaa'], id: $result['id']);
} else {
return false;
}
} catch (PDOException $e) {
exit($e->getMessage());
}
}
}

View File

@ -42,6 +42,28 @@ class NameserverRepository
}
/**
* @return \App\Entity\Nameserver
*/
public function findFirst(): Nameserver
{
$nameservers = [];
$sql = "
SELECT id, name, a, aaaa, apikey
FROM " . DatabaseConnection::TABLE_NAMESERVERS . "
ORDER BY name";
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->execute();
$result = $statement->fetch(mode: PDO::FETCH_ASSOC);
return new Nameserver(name: $result['name'], id: $result['id'], a: $result['a'], aaaa: $result['aaaa'], apikey: $result['apikey']);
} catch (PDOException $e) {
exit($e->getMessage());
}
}
/**
* @param int $id
*