From 312b0d99675a1b0a82fb222afe91c4f28347cea6 Mon Sep 17 00:00:00 2001 From: Nils Blume Date: Wed, 23 Aug 2023 23:04:45 +0200 Subject: [PATCH 1/6] cleanup previous tests --- src/Handler.php | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/src/Handler.php b/src/Handler.php index 40117d5..afa4278 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -103,7 +103,6 @@ final class Handler } /** - * * @return self */ public function doRun() @@ -133,21 +132,11 @@ final class Handler $clientRequestId ); - // test: create new entry if it does not exist - $createnewentry = true; $exists = false; - $testing = true; - $ipv4changes = false; $ipv6changes = false; $txtchanges = false; - - // TODO: delete, testing - // echo "--- EXISTING ENTRIES BELOW ---", PHP_EOL; - // $teststring = print_r($infoHandle->responsedata->dnsrecords, true); - // echo $teststring, PHP_EOL; - foreach ($infoHandle->responsedata->dnsrecords as $key => $record) { $recordHostnameReal = (!in_array($record->hostname, $this->payload->getMatcher())) ? $record->hostname . '.' . $this->payload->getDomainName() : $this->payload->getDomainName(); @@ -195,10 +184,7 @@ final class Handler } } - // echo "--- Exists ---", $exists, PHP_EOL; - // echo "--- Createnewentry ---", $createnewentry, PHP_EOL; - - // TODO: if entry does not exist and createnewentry is 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 @@ -208,10 +194,6 @@ final class Handler foreach ($this->payload->getTypes() as $key => $type) { $record = new Soap\Dnsrecord(); - - // echo "getDomain: ", $this->payload->getDomain(), PHP_EOL; - // echo "getDomainName: ", $this->payload->getDomainName(), PHP_EOL; - // echo "getHost: ", $this->payload->getHost(), PHP_EOL; $record->hostname = $this->payload->getHost(); $record->type = $type; @@ -219,7 +201,6 @@ final class Handler switch ($type) { case 'A': - // echo "A record set: ", $this->payload->getIpv4(), PHP_EOL; $record->destination = $this->payload->getIpv4(); break; @@ -231,17 +212,10 @@ final class Handler $record->destination = $this->payload->getTxt(); break; } - // echo "destination: ", $record->destination, PHP_EOL; - // echo "--- NEW ENTRY BELOW ---", PHP_EOL; - // $teststring = print_r($record, true); - // echo $teststring, PHP_EOL; array_push($newRecordSet->dnsrecords, $record); // push new record into array } - // echo "--- newRecordSet ---", PHP_EOL; - // $teststring = print_r($newRecordSet, true); - // echo $teststring, PHP_EOL; $dnsClient->updateDnsRecords( $this->payload->getDomainName(), From f2bfa9008f2a1ac9ba6ef07afa1d690e9dcba15b Mon Sep 17 00:00:00 2001 From: Nils Blume Date: Wed, 23 Aug 2023 23:12:54 +0200 Subject: [PATCH 2/6] cleanup previous tests --- src/Payload.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Payload.php b/src/Payload.php index 7cca9b5..3e1776d 100644 --- a/src/Payload.php +++ b/src/Payload.php @@ -255,12 +255,4 @@ final class Payload return $this->force; } - /** - * @return string - */ - public function getType() - { - // TODO: - return "A"; - } } From fac7616c25e085f9cf0fc2de8a3bb5af98cc3872 Mon Sep 17 00:00:00 2001 From: Nils Blume Date: Wed, 23 Aug 2023 23:47:23 +0200 Subject: [PATCH 3/6] added domain restriction capability --- README.md | 27 ++++++++++-------- mydomain/.env.dist | 12 ++++++++ mydomain/update.php | 20 +++++++++++++ src/Config.php | 69 +++++++++++++++++++++++++++++++++++++++++++++ src/Handler.php | 32 ++++++++++++++------- 5 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 mydomain/.env.dist create mode 100755 mydomain/update.php diff --git a/README.md b/README.md index 7e53618..d3b8e31 100644 --- a/README.md +++ b/README.md @@ -11,20 +11,25 @@ Self-hosted dynamic DNS php script to update netcup DNS API from Router like AVM ### Installation * Copy all files to your webspace * create a copy of `.env.dist` as `.env` and configure: - * `username` -> The username for your Router to authenticate (so not everyone can update your DNS) - * `password` -> password for your Router - * `apiKey` -> API key which is generated in netcup CCP - * `apiPassword` -> API password which is generated in netcup CCP - * `customerId` -> your netcup Customer ID - * `log` -> true|false enables logging - * `logFile` -> configures logfile location if enabled - * `debug` -> true|false enables debug mode and generates more output from update.php (normal operation has no output). Needed to receive stack traces from errors. - * `returnIp` -> true|false enables return of result if a record was changed - * `allowCreate` -> true|false allows creation of entries if parameter `create=true` in URL + +Parameter | Example | Explanation +---: | :--- | :--- +`username` | dnsupdater | The username for your Router to authenticate (so not everyone can update your DNS) +`password` | secretpleasechange | password for your Router +`apiKey` | 18neqwd91e21onei1p23841 | API key which is generated in netcup CCP +`apiPassword` | 82jewqde9m30 | API password which is generated in netcup CCP +`customerId` | 12345 | your netcup Customer ID +`log` | `true` / false | enables logging +`logFile` | log.json | configures logfile location if enabled +`debug` | true / `false` | enables debug mode and generates more output from update.php (normal operation has no output). Needed to receive stack traces from errors. +`returnIp` | `true` / false | enables return of result if a record was changed +`allowCreate` | true/`false` | allows creation of entries if parameter `create=true` in URL +`restrictDomain` | true / `false` | allows admin to restrict the domain to update to a given value `domain` and/or `host`. See URL parameters for host parameter explanation * alternatively you can use .configure.sh to create your .env file for you (if you are on a *NIX system) -* Create each host record in your netcup CCP (DNS settings) before using the script. The script does not create any missing records. +* Create each host record in your netcup CCP (DNS settings) before using the script. The script does not create any missing records.
+You can now set `allowCreate=true` in .env and pass `create=true` as URL parameter to create entries on the fly. ## URL possible uses: diff --git a/mydomain/.env.dist b/mydomain/.env.dist new file mode 100644 index 0000000..50635c3 --- /dev/null +++ b/mydomain/.env.dist @@ -0,0 +1,12 @@ +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="mydomain.example.com" diff --git a/mydomain/update.php b/mydomain/update.php new file mode 100755 index 0000000..bbb8b8e --- /dev/null +++ b/mydomain/update.php @@ -0,0 +1,20 @@ +doRun(); diff --git a/src/Config.php b/src/Config.php index 12e4d24..aa85c5c 100644 --- a/src/Config.php +++ b/src/Config.php @@ -55,6 +55,21 @@ final class Config */ private $allowCreate = false; + /** + * @var bool + */ + private $restrictDomain = false; + + /** + * @var string + */ + private $domain; + + /** + * @var string + */ + private $host; + public function __construct(array $config) { @@ -158,4 +173,58 @@ final class Config { return $this->allowCreate; } + + /** + * @return bool + */ + public function isRestrictDomain() + { + return $this->restrictDomain; + } + + /** + * @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); + } } diff --git a/src/Handler.php b/src/Handler.php index afa4278..9882782 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -124,8 +124,21 @@ final class Handler $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( - $this->payload->getDomainName(), + $updateDomainName, $this->config->getCustomerId(), $this->config->getApiKey(), $loginHandle->responsedata->apisessionid, @@ -138,10 +151,9 @@ final class Handler $txtchanges = false; foreach ($infoHandle->responsedata->dnsrecords as $key => $record) { - $recordHostnameReal = (!in_array($record->hostname, $this->payload->getMatcher())) ? $record->hostname . '.' . $this->payload->getDomainName() : $this->payload->getDomainName(); + $recordHostnameReal = (!in_array($record->hostname, $this->payload->getMatcher())) ? $record->hostname . '.' . $updateDomainName : $updateDomainName; - - if ($recordHostnameReal === $this->payload->getDomain()) { + if ($recordHostnameReal === $updateDomain) { // found matching entry, no need to create one $exists = true; @@ -154,7 +166,7 @@ final class Handler ) ) { $record->destination = $this->payload->getIpv4(); - $this->doLog(sprintf('IPv4 for %s set to %s', $record->hostname . '.' . $this->payload->getDomainName(), $this->payload->getIpv4())); + $this->doLog(sprintf('IPv4 for %s set to %s', $record->hostname . '.' . $updateDomainName, $this->payload->getIpv4())); $ipv4changes = true; } @@ -166,7 +178,7 @@ final class Handler ) ) { $record->destination = $this->payload->getIpv6(); - $this->doLog(sprintf('IPv6 for %s set to %s', $record->hostname . '.' . $this->payload->getDomainName(), $this->payload->getIpv6())); + $this->doLog(sprintf('IPv6 for %s set to %s', $record->hostname . '.' . $updateDomainName, $this->payload->getIpv6())); $ipv6changes = true; } @@ -178,7 +190,7 @@ final class Handler ) ) { $record->destination = $this->payload->getTxt(); - $this->doLog(sprintf('TXT for %s set to %s', $record->hostname . '.' . $this->payload->getDomainName(), $this->payload->getTxt())); + $this->doLog(sprintf('TXT for %s set to %s', $record->hostname . '.' . $updateDomainName, $this->payload->getTxt())); $txtchanges = true; } } @@ -195,7 +207,7 @@ final class Handler { $record = new Soap\Dnsrecord(); - $record->hostname = $this->payload->getHost(); + $record->hostname = $updateHost; $record->type = $type; $record->priority = "0"; // only for MX, can possibly be removed @@ -218,7 +230,7 @@ final class Handler $dnsClient->updateDnsRecords( - $this->payload->getDomainName(), + $updateDomainName, $this->config->getCustomerId(), $this->config->getApiKey(), $loginHandle->responsedata->apisessionid, @@ -235,7 +247,7 @@ final class Handler $recordSet->dnsrecords = $infoHandle->responsedata->dnsrecords; $dnsClient->updateDnsRecords( - $this->payload->getDomainName(), + $updateDomainName, $this->config->getCustomerId(), $this->config->getApiKey(), $loginHandle->responsedata->apisessionid, From 78ec4ee94bea04ea229c5723bcfa6641739413df Mon Sep 17 00:00:00 2001 From: Nils Blume Date: Wed, 23 Aug 2023 23:49:06 +0200 Subject: [PATCH 4/6] updated with example --- mydomain/.env.dist | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mydomain/.env.dist b/mydomain/.env.dist index 50635c3..9162fbe 100644 --- a/mydomain/.env.dist +++ b/mydomain/.env.dist @@ -1,12 +1,12 @@ -username="max_mustermann" -password="s3cr3t" +username="only-mydomain" +password="changemeplease" apiKey="netcup DNS API Key" apiPassword="netcup DNS API Password" customerId="netcup customer ID" -debug=true +debug=false log=true -logFile=log.json +logFile=mydomain.json returnIp=true allowCreate=false -restrictDomain=false +restrictDomain=true domain="mydomain.example.com" From 0de4252b2de6ae87afa0e6da04f8bfef796f90e7 Mon Sep 17 00:00:00 2001 From: Nils Blume Date: Wed, 23 Aug 2023 23:51:32 +0200 Subject: [PATCH 5/6] added multi-endpoint description --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d3b8e31..28c52c3 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Self-hosted dynamic DNS php script to update netcup DNS API from Router like AVM ## Usage ### Installation * Copy all files to your webspace +* If you want multiple endpoints that each can only update one domain look at the mydomain folder.
+The update URL would be https://`url`/mydomain/update.php?(...) * create a copy of `.env.dist` as `.env` and configure: Parameter | Example | Explanation From c1833df127956c0036f368a359238cb402739114 Mon Sep 17 00:00:00 2001 From: Nils Blume Date: Thu, 24 Aug 2023 00:34:13 +0200 Subject: [PATCH 6/6] updated examples --- examples/multisite.md | 67 ++++++++++++++++++++++ {mydomain => examples/mydomain}/.env.dist | 0 {mydomain => examples/mydomain}/update.php | 0 3 files changed, 67 insertions(+) create mode 100644 examples/multisite.md rename {mydomain => examples/mydomain}/.env.dist (100%) rename {mydomain => examples/mydomain}/update.php (100%) diff --git a/examples/multisite.md b/examples/multisite.md new file mode 100644 index 0000000..fcbf073 --- /dev/null +++ b/examples/multisite.md @@ -0,0 +1,67 @@ +# Multiple Endpoints with separate credentials +For advanced use you might want to have separate users that can each only update one domain entry. + +In that case it might be beneficial to habe multiple endpoints, e.g. `https://dyndns.example.com/endpointN/update.php` where endpointN is any directory name you wish. + +## Setting up multiple endpoints +The directory structure of your webroot might look like this: +
+├── index.html
+├── src
+│   ├── Config.php
+│   ├── Handler.php
+│   ├── Payload.php
+│   └── Soap.php
+├── fritzbox        # this is a subdomain
+│   ├── .env
+│   └── update.php
+├── nas             # this is another
+│   ├── .env
+│   └── update.php
+├── examplenet      # uses another netcup account
+│   ├── .env
+│   └── update.php
+└── subdomain1      # and another subdomain
+    ├── .env
+    └── update.php
+
+ +Here the update.php files are copied from the mydomain example directory. All .env files contain different user credentials and may even use different netcup credentials. + +## Setting up domain restrictions per .env file +It is nice to have multiple sets of credentials, but if anyone can update any entry of any domain this defeats the purpose. + +That is why you can enable domain restriction per .env file and thereby per set of user credentials. + +In these cases you the domain you send in your url will be ignored in favour of the one configured in the .env file. You still need to send a placeholder for validation purposes. + +Example .env file for fritzbox.example.com.
+Callable by: `https://dyndns.example.com/fritzbox/update.php?user=fritzbox&password=changeme&domain=placeholder&ipv4=1.2.3.4` +
+username="fritzbox"
+password="changemeplease"
+apiKey="j1meo213em823jd2q9"
+apiPassword="12345secret"
+customerId="12345"
+debug=false
+log=true
+logFile=/var/log/dnsupdater/fritzbox.json
+restrictDomain=true
+domain=fritzbox.example.com
+
+ +Example .env file for nas.home.example.com.
+Callable by: `https://dyndns.example.com/nas/update.php?user=nas&password=changeme&domain=placeholder&ipv4=1.2.3.4` +
+username="nas"
+password="changemeplease"
+apiKey="j1meo213em823jd2q9"
+apiPassword="12345secret"
+customerId="12345"
+debug=false
+log=true
+logFile=/var/log/dnsupdater/nas.json
+restrictDomain=true
+domain=example.com    # for explicit use of third-level domain
+host=nas.home         # we use the optional host parameter
+
\ No newline at end of file diff --git a/mydomain/.env.dist b/examples/mydomain/.env.dist similarity index 100% rename from mydomain/.env.dist rename to examples/mydomain/.env.dist diff --git a/mydomain/update.php b/examples/mydomain/update.php similarity index 100% rename from mydomain/update.php rename to examples/mydomain/update.php