1
0
Fork 0
mirror of https://github.com/fernwerker/ownDynDNS.git synced 2025-07-13 07:05:43 +02:00

add docker functionality

This commit is contained in:
NiiWiiCamo 2024-02-13 14:46:01 +01:00
parent 058a32533b
commit 28f384ee92
10 changed files with 46 additions and 0 deletions

13
data/.env.dist Normal file
View file

@ -0,0 +1,13 @@
username="max_mustermann"
password="s3cr3t"
apiKey="netcup DNS API Key"
apiPassword="netcup DNS API Password"
customerId="netcup customer ID"
debug=true
log=true
logFile=log.json
returnIp=true
allowCreate=false
restrictDomain=false
domain=example.com
host=nas.home

276
data/src/Config.php Normal file
View file

@ -0,0 +1,276 @@
<?php
namespace netcup\DNS\API;
final class Config
{
/**
* @var string
*/
private $username;
/**
* @var string
*/
private $password;
/**
* @var string
*/
private $apiKey;
/**
* @var string
*/
private $apiPassword;
/**
* @var int
*/
private $customerId;
/**
* @var bool
*/
private $log = true;
/**
* @var string
*/
private $logFile;
/**
* @var bool
*/
private $debug = false;
/**
* @var bool
*/
private $returnIp = true;
/**
* @var bool
*/
private $allowCreate = false;
/**
* @var bool
*/
private $allowNetcupCreds = false;
/**
* @var bool
*/
private $allowAnonymous = false;
/**
* @var bool
*/
private $restrictDomain = false;
/**
* @var string
*/
private $domain;
/**
* @var string
*/
private $host;
public function __construct(array $config)
{
foreach (get_object_vars($this) as $key => $val) {
if (isset($config[$key])) {
$this->$key = $config[$key];
}
}
}
/**
* @return bool
*/
public function isValid()
{
return
(
!empty($this->username) &&
!empty($this->password)
) ||
(
$this->isAllowAnonymous()
) &&
(
(
!empty($this->apiKey) &&
!empty($this->apiPassword) &&
!empty($this->customerId)
) ||
(
$this->isAllowNetcupCreds()
)
) &&
(
(
$this->isLog() &&
!empty($this->logFile)
) ||
(
$this->isLog() == false
)
);
}
/**
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* @return string
*/
public function getApiKey()
{
return $this->apiKey;
}
/**
* @return string
*/
public function getApiPassword()
{
return $this->apiPassword;
}
/**
* @return int
*/
public function getCustomerId()
{
return $this->customerId;
}
/**
* @return bool
*/
public function isLog()
{
return $this->log;
}
/**
* @return string
*/
public function getLogFile()
{
return $this->logFile;
}
/**
* @return bool
*/
public function isDebug()
{
return $this->debug;
}
/**
* @return bool
*/
public function isReturnIp()
{
return $this->returnIp;
}
/**
* @return bool
*/
public function isAllowCreate()
{
return $this->allowCreate;
}
/**
* @return bool
*/
public function isRestrictDomain()
{
return $this->restrictDomain;
}
/**
* @return bool
*/
public function isAllowNetcupCreds()
{
return $this->allowNetcupCreds;
}
/**
* @return bool
*/
public function isAllowAnonymous()
{
return $this->allowAnonymous;
}
/**
* @return string
*/
public function getDomain()
{
if (empty($this->host))
{
return $this->domain;
}
else
{
return $this->host . "." . $this->domain;
}
}
/**
* @return string
*/
public function getHost()
{
if (!empty($this->host))
{
return $this->host;
}
else
{
$domainParts = explode('.', $this->domain);
return $domainParts[0];
}
}
/**
* @return string
*/
public function getDomainName()
{
// hack if top level domain are used for dynDNS
if (1 === substr_count($this->domain, '.')) {
return $this->domain;
}
$domainParts = explode('.', $this->domain);
array_shift($domainParts); // remove sub domain
return implode('.', $domainParts);
}
}

331
data/src/Handler.php Normal file
View file

@ -0,0 +1,331 @@
<?php
namespace netcup\DNS\API;
use RuntimeException;
final class Handler
{
/**
* @var array
*/
private $log;
/**
* @var Config
*/
private $config;
/**
* @var Payload
*/
private $payload;
/**
* @var int
*/
private $customerid;
/**
* @var string
*/
private $apikey;
/**
* @var string
*/
private $apipassword;
public function __construct(array $config, array $payload)
{
$this->config = new Config($config);
if (!$this->config->isValid()) {
if ($this->config->isDebug()) {
throw new RuntimeException('configuration invalid');
} else {
exit("configuration invalid\n");
}
}
$this->payload = new Payload($payload);
if (!$this->payload->isValid()) {
if ($this->config->isDebug()) {
throw new RuntimeException('payload invalid');
} else {
exit("payload invalid\n");
}
}
if ($this->config->isAllowAnonymous()) {
if ($this->payload->getUser() == 'anonymous') {
if ($this->config->isDebug()) {
$this->doLog('anonymous login by client');
}
}
} else {
if (
$this->config->getUsername() !== $this->payload->getUser() ||
$this->config->getPassword() !== $this->payload->getPassword()
) {
if ($this->config->isDebug()) {
throw new RuntimeException('credentials invalid');
} else {
exit("credentials invalid\n");
}
}
}
if ($this->config->isAllowNetcupCreds()) {
if ($this->payload->isValidNetcupCreds()) {
if ($this->config->isDebug()) {
$this->doLog('received valid Netcup credentials');
}
}
$this->customerid = $this->payload->getCustomerId();
$this->apikey = $this->payload->getApiKey();
$this->apipassword = $this->payload->getApiPassword();
} else {
$this->customerid = $this->config->getCustomerId();
$this->apikey = $this->config->getApiKey();
$this->apipassword = $this->config->getApiPassword();
}
if ($this->config->isLog()) {
if (is_readable($this->config->getLogFile())) {
$this->log = json_decode(file_get_contents($this->config->getLogFile()), true);
} else {
$this->log[$this->payload->getDomain()] = [];
}
}
}
public function __destruct()
{
$this->doExit();
}
/**
* @param string $msg
*
* @return self
*/
private function doLog($msg)
{
if ($this->config->isLog()) {
$this->log[$this->payload->getDomain()][] = sprintf('[%s] %s', date('c'), $msg);
if ($this->config->isDebug()) {
printf('[DEBUG] %s %s', $msg, PHP_EOL);
}
return $this;
}
}
private function doExit()
{
if (!$this->config->isLog()) {
return;
}
if (!file_exists($this->config->getLogFile())) {
if (!touch($this->config->getLogFile())) {
printf('[ERROR] unable to create %s %s', $this->config->getLogFile(), PHP_EOL);
}
}
// save only the newest 100 log entries for each domain
$this->log[$this->payload->getDomain()] = array_reverse(array_slice(array_reverse($this->log[$this->payload->getDomain()]), 0, 100));
if (!is_writable($this->config->getLogFile()) || !file_put_contents($this->config->getLogFile(), json_encode($this->log, JSON_PRETTY_PRINT))) {
printf('[ERROR] unable to write %s %s', $this->config->getLogFile(), PHP_EOL);
}
}
/**
* @return self
*/
public function doRun()
{
$clientRequestId = md5($this->payload->getDomain() . time());
$dnsClient = new Soap\DomainWebserviceSoapClient();
$loginHandle = $dnsClient->login(
$this->customerid,
$this->apikey,
$this->apipassword,
$clientRequestId
);
if (2000 === $loginHandle->statuscode) {
$this->doLog('api login successful');
} else {
$this->doLog(sprintf('api login failed, message: %s', $loginHandle->longmessage));
}
// check if domain is restricted in config, force use of config values for domain and host
if ($this->config->isRestrictDomain()) {
$this->doLog('domain is restricted by .env file');
$updateDomain = $this->config->getDomain();
$updateDomainName = $this->config->getDomainName();
$updateHost = $this->config->getHost();
$this->doLog(sprintf('ignoring received domain, using configured domain: %s', $updateDomain));
} else {
$updateDomain = $this->payload->getDomain();
$updateDomainName = $this->payload->getDomainName();
$updateHost = $this->payload->getHost();
}
$infoHandle = $dnsClient->infoDnsRecords(
$updateDomainName,
$this->customerid,
$this->apikey,
$loginHandle->responsedata->apisessionid,
$clientRequestId
);
$exists = false;
$ipv4changes = false;
$ipv6changes = false;
$txtchanges = false;
foreach ($infoHandle->responsedata->dnsrecords as $key => $record) {
$recordHostnameReal = (!in_array($record->hostname, $this->payload->getMatcher())) ? $record->hostname . '.' . $updateDomainName : $updateDomainName;
if ($recordHostnameReal === $updateDomain) {
// found matching entry, no need to create one
$exists = true;
// update A Record if exists and IP has changed
if ('A' === $record->type && $this->payload->getIpv4() &&
(
$this->payload->isForce() ||
$record->destination !== $this->payload->getIpv4()
)
) {
$record->destination = $this->payload->getIpv4();
$this->doLog(sprintf('IPv4 for %s set to %s', $record->hostname . '.' . $updateDomainName, $this->payload->getIpv4()));
$ipv4changes = true;
}
// update AAAA Record if exists and IP has changed
if ('AAAA' === $record->type && $this->payload->getIpv6() &&
(
$this->payload->isForce()
|| $record->destination !== $this->payload->getIpv6()
)
) {
$record->destination = $this->payload->getIpv6();
$this->doLog(sprintf('IPv6 for %s set to %s', $record->hostname . '.' . $updateDomainName, $this->payload->getIpv6()));
$ipv6changes = true;
}
// update TXT Record if exists and content has changed
if ('TXT' === $record->type && $this->payload->getTxt() &&
(
$this->payload->isForce()
|| $record->destination !== $this->payload->getTxt()
)
) {
$record->destination = $this->payload->getTxt();
$this->doLog(sprintf('TXT for %s set to %s', $record->hostname . '.' . $updateDomainName, $this->payload->getTxt()));
$txtchanges = true;
}
}
}
// if entry does not exist and createnewentry is true:
if ( !$exists && $this->payload->getCreate() && $this->config->isAllowCreate() )
{
// init new record set containing empty array
$newRecordSet = new Soap\Dnsrecordset();
$newRecordSet->dnsrecords = array();
foreach ($this->payload->getTypes() as $key => $type)
{
$record = new Soap\Dnsrecord();
$record->hostname = $updateHost;
$record->type = $type;
$record->priority = "0"; // only for MX, can possibly be removed
switch ($type) {
case 'A':
$record->destination = $this->payload->getIpv4();
break;
case 'AAAA':
$record->destination = $this->payload->getIpv6();
break;
case 'TXT':
$record->destination = $this->payload->getTxt();
break;
}
array_push($newRecordSet->dnsrecords, $record); // push new record into array
}
$dnsClient->updateDnsRecords(
$updateDomainName,
$this->customerid,
$this->apikey,
$loginHandle->responsedata->apisessionid,
$clientRequestId,
$newRecordSet
);
$this->doLog('dns recordset created');
}
// if anything was changed, push the update and log
if ($ipv4changes or $ipv6changes or $txtchanges) {
$recordSet = new Soap\Dnsrecordset();
$recordSet->dnsrecords = $infoHandle->responsedata->dnsrecords;
$dnsClient->updateDnsRecords(
$updateDomainName,
$this->customerid,
$this->apikey,
$loginHandle->responsedata->apisessionid,
$clientRequestId,
$recordSet
);
$this->doLog('dns recordset updated');
} else {
$this->doLog('dns recordset NOT updated (no changes)');
}
$logoutHandle = $dnsClient->logout(
$this->customerid,
$this->apikey,
$loginHandle->responsedata->apisessionid,
$clientRequestId
);
if (2000 === $logoutHandle->statuscode) {
$this->doLog('api logout successful');
} else {
$this->doLog(sprintf('api logout failed, message: %s', $loginHandle->longmessage));
}
if ($this->config->isReturnIp()) {
if ($ipv4changes) {
echo "IPv4 changed: " . $this->payload->getIpv4(), PHP_EOL;
}
if ($ipv6changes) {
echo "IPv6 changed: " . $this->payload->getIpv6(), PHP_EOL;
}
if ($txtchanges) {
echo "TXT changed: " . $this->payload->getTxt(), PHP_EOL;
}
}
return $this;
}
}

311
data/src/Payload.php Normal file
View file

@ -0,0 +1,311 @@
<?php
namespace netcup\DNS\API;
final class Payload
{
/**
* @var string
*/
private $user;
/**
* @var string
*/
private $password;
/**
* @var string
*/
private $domain;
/**
* @var string
*/
private $mode;
/**
* @var string
*/
private $ipv4;
/**
* @var string
*/
private $ipv6;
/**
* @var string
*/
private $txt;
/**
* @var string
*/
private $host;
/**
* @var int
*/
private $customerId;
/**
* @var string
*/
private $apiKey;
/**
* @var string
*/
private $apiPassword;
/**
* @var bool
*/
private $create = false;
/**
* @var bool
*/
private $force = false;
public function __construct(array $payload)
{
foreach (get_object_vars($this) as $key => $val) {
if (isset($payload[$key])) {
$this->$key = $payload[$key];
}
}
}
/**
* @return bool
*/
public function isValid()
{
return
!empty($this->user) &&
(
$this->user == 'anonymous' ||
!empty($this->password)
) &&
!empty($this->domain) &&
(
(
!empty($this->ipv4) && $this->isValidIpv4()
)
||
(
!empty($this->ipv6) && $this->isValidIpv6()
)
||
!empty($this->txt)
);
}
/**
* @return bool
*/
public function isValidNetcupCreds()
{
return
!empty($this->customerId) &&
!empty($this->apiKey) &&
!empty($this->apiPassword);
}
/**
* @return int
*/
public function getCustomerId()
{
return $this->customerId;
}
/**
* @return string
*/
public function getapiKey()
{
return $this->apiKey;
}
/**
* @return string
*/
public function getApiPassword()
{
return $this->apiPassword;
}
/**
* @return string
*/
public function getUser()
{
return $this->user;
}
/**
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* @return string
*/
public function getDomain()
{
if (empty($this->host))
{
return $this->domain;
}
else
{
return $this->host . "." . $this->domain;
}
}
/**
* @return string
*/
public function getHost()
{
if (!empty($this->host))
{
return $this->host;
}
else
{
$domainParts = explode('.', $this->domain);
return $domainParts[0];
}
}
/**
* @return array
*/
public function getMatcher()
{
switch ($this->mode) {
case 'both':
return ['@', '*'];
case '*':
return ['*'];
default:
return ['@'];
}
}
/**
* @return bool
*/
public function getCreate()
{
return $this->create;
}
/**
* @return array
*/
public function getTypes()
{
$types = array();
if ($this->getIpv4() && $this->isValidIpv4())
{
array_push($types, "A");
}
if ($this->getIpv6() && $this->isValidIpv6())
{
array_push($types, "AAAA");
}
if ($this->getTxt())
{
array_push($types, "TXT");
}
return $types;
}
/**
* there is no good way to get the correct "registrable" Domain without external libs!
*
* @see https://github.com/jeremykendall/php-domain-parser
*
* this method is still tricky, because:
*
* works: nas.tld.com
* works: nas.tld.de
* works: tld.com
* failed: nas.tld.co.uk
* failed: nas.home.tld.de ** see new below
*
* new: for explicit host / domain separation use "&host=nas.home&domain=tld.de" for the last example
*
* @return string
*/
public function getDomainName()
{
// hack if top level domain are used for dynDNS
if (1 === substr_count($this->domain, '.')) {
return $this->domain;
}
$domainParts = explode('.', $this->domain);
array_shift($domainParts); // remove sub domain
return implode('.', $domainParts);
}
/**
* @return string
*/
public function getIpv4()
{
return $this->ipv4;
}
/**
* @return bool
*/
public function isValidIpv4()
{
return (bool)filter_var($this->ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
}
/**
* @return string
*/
public function getIpv6()
{
return $this->ipv6;
}
/**
* @return bool
*/
public function isValidIpv6()
{
return (bool)filter_var($this->ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
}
/**
* @return string
*/
public function getTxt()
{
return $this->txt;
}
/**
* @return bool
*/
public function isForce()
{
return $this->force;
}
}

1377
data/src/Soap.php Normal file

File diff suppressed because it is too large Load diff

20
data/update.php Executable file
View file

@ -0,0 +1,20 @@
<?php
error_reporting(-1);
ini_set('display_errors', 1);
ini_set('html_errors', 0);
header('Content-Type: text/plain; charset=utf-8');
require_once __DIR__ . '/src/Soap.php';
require_once __DIR__ . '/src/Config.php';
require_once __DIR__ . '/src/Payload.php';
require_once __DIR__ . '/src/Handler.php';
if (!file_exists('.env')) {
throw new RuntimeException('.env file missing');
}
$config = parse_ini_file('.env', false, INI_SCANNER_TYPED);
(new netcup\DNS\API\Handler($config, $_REQUEST))->doRun();