1
0
Fork 0
mirror of https://github.com/fernwerker/ownDynDNS.git synced 2025-07-10 14:15:14 +02:00

Merge pull request #1 from NiiWiiCamo/testing-multisite

Multisite with domain restriction
This commit is contained in:
NiiWiiCamo 2023-08-24 12:18:15 +02:00 committed by GitHub
commit a1feba58bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 209 additions and 56 deletions

View file

@ -10,21 +10,28 @@ Self-hosted dynamic DNS php script to update netcup DNS API from Router like AVM
## Usage ## Usage
### Installation ### Installation
* Copy all files to your webspace * Copy all files to your webspace
* If you want multiple endpoints that each can only update one domain look at the mydomain folder.<br>
The update URL would be https://`url`/mydomain/update.php?(...)
* create a copy of `.env.dist` as `.env` and configure: * 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 Parameter | Example | Explanation
* `apiKey` -> API key which is generated in netcup CCP ---: | :--- | :---
* `apiPassword` -> API password which is generated in netcup CCP `username` | dnsupdater | The username for your Router to authenticate (so not everyone can update your DNS)
* `customerId` -> your netcup Customer ID `password` | secretpleasechange | password for your Router
* `log` -> true|false enables logging `apiKey` | 18neqwd91e21onei1p23841 | API key which is generated in netcup CCP
* `logFile` -> configures logfile location if enabled `apiPassword` | 82jewqde9m30 | API password which is generated in netcup CCP
* `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. `customerId` | 12345 | your netcup Customer ID
* `returnIp` -> true|false enables return of result if a record was changed `log` | `true` / false | enables logging
* `allowCreate` -> true|false allows creation of entries if parameter `create=true` in URL `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) * 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. <s>The script does not create any missing records.</s><br>
You can now set `allowCreate=true` in .env and pass `create=true` as URL parameter to create entries on the fly.
## URL possible uses: ## URL possible uses:

67
examples/multisite.md Normal file
View file

@ -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:
<pre>
├── 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
</pre>
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. <b>You still need to send a placeholder for validation purposes.</b>
Example .env file for fritzbox.example.com.<br>
Callable by: `https://dyndns.example.com/fritzbox/update.php?user=fritzbox&password=changeme&domain=placeholder&ipv4=1.2.3.4`
<pre>
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
</pre>
Example .env file for nas.home.example.com.<br>
Callable by: `https://dyndns.example.com/nas/update.php?user=nas&password=changeme&domain=placeholder&ipv4=1.2.3.4`
<pre>
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
</pre>

View file

@ -0,0 +1,12 @@
username="only-mydomain"
password="changemeplease"
apiKey="netcup DNS API Key"
apiPassword="netcup DNS API Password"
customerId="netcup customer ID"
debug=false
log=true
logFile=mydomain.json
returnIp=true
allowCreate=false
restrictDomain=true
domain="mydomain.example.com"

20
examples/mydomain/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();

View file

@ -55,6 +55,21 @@ final class Config
*/ */
private $allowCreate = false; private $allowCreate = false;
/**
* @var bool
*/
private $restrictDomain = false;
/**
* @var string
*/
private $domain;
/**
* @var string
*/
private $host;
public function __construct(array $config) public function __construct(array $config)
{ {
@ -158,4 +173,58 @@ final class Config
{ {
return $this->allowCreate; 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);
}
} }

View file

@ -103,7 +103,6 @@ final class Handler
} }
/** /**
*
* @return self * @return self
*/ */
public function doRun() public function doRun()
@ -125,34 +124,36 @@ final class Handler
$this->doLog(sprintf('api login failed, message: %s', $loginHandle->longmessage)); $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( $infoHandle = $dnsClient->infoDnsRecords(
$this->payload->getDomainName(), $updateDomainName,
$this->config->getCustomerId(), $this->config->getCustomerId(),
$this->config->getApiKey(), $this->config->getApiKey(),
$loginHandle->responsedata->apisessionid, $loginHandle->responsedata->apisessionid,
$clientRequestId $clientRequestId
); );
// test: create new entry if it does not exist
$createnewentry = true;
$exists = false; $exists = false;
$testing = true;
$ipv4changes = false; $ipv4changes = false;
$ipv6changes = false; $ipv6changes = false;
$txtchanges = 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) { 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 === $updateDomain) {
if ($recordHostnameReal === $this->payload->getDomain()) {
// found matching entry, no need to create one // found matching entry, no need to create one
$exists = true; $exists = true;
@ -165,7 +166,7 @@ final class Handler
) )
) { ) {
$record->destination = $this->payload->getIpv4(); $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; $ipv4changes = true;
} }
@ -177,7 +178,7 @@ final class Handler
) )
) { ) {
$record->destination = $this->payload->getIpv6(); $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; $ipv6changes = true;
} }
@ -189,16 +190,13 @@ final class Handler
) )
) { ) {
$record->destination = $this->payload->getTxt(); $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; $txtchanges = true;
} }
} }
} }
// echo "--- Exists ---", $exists, PHP_EOL; // if entry does not exist and createnewentry is true:
// echo "--- Createnewentry ---", $createnewentry, PHP_EOL;
// TODO: if entry does not exist and createnewentry is true:
if ( !$exists && $this->payload->getCreate() && $this->config->isAllowCreate() ) if ( !$exists && $this->payload->getCreate() && $this->config->isAllowCreate() )
{ {
// init new record set containing empty array // init new record set containing empty array
@ -208,18 +206,13 @@ final class Handler
foreach ($this->payload->getTypes() as $key => $type) foreach ($this->payload->getTypes() as $key => $type)
{ {
$record = new Soap\Dnsrecord(); $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->hostname = $updateHost;
$record->type = $type; $record->type = $type;
$record->priority = "0"; // only for MX, can possibly be removed $record->priority = "0"; // only for MX, can possibly be removed
switch ($type) { switch ($type) {
case 'A': case 'A':
// echo "A record set: ", $this->payload->getIpv4(), PHP_EOL;
$record->destination = $this->payload->getIpv4(); $record->destination = $this->payload->getIpv4();
break; break;
@ -231,20 +224,13 @@ final class Handler
$record->destination = $this->payload->getTxt(); $record->destination = $this->payload->getTxt();
break; 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 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( $dnsClient->updateDnsRecords(
$this->payload->getDomainName(), $updateDomainName,
$this->config->getCustomerId(), $this->config->getCustomerId(),
$this->config->getApiKey(), $this->config->getApiKey(),
$loginHandle->responsedata->apisessionid, $loginHandle->responsedata->apisessionid,
@ -261,7 +247,7 @@ final class Handler
$recordSet->dnsrecords = $infoHandle->responsedata->dnsrecords; $recordSet->dnsrecords = $infoHandle->responsedata->dnsrecords;
$dnsClient->updateDnsRecords( $dnsClient->updateDnsRecords(
$this->payload->getDomainName(), $updateDomainName,
$this->config->getCustomerId(), $this->config->getCustomerId(),
$this->config->getApiKey(), $this->config->getApiKey(),
$loginHandle->responsedata->apisessionid, $loginHandle->responsedata->apisessionid,

View file

@ -255,12 +255,4 @@ final class Payload
return $this->force; return $this->force;
} }
/**
* @return string
*/
public function getType()
{
// TODO:
return "A";
}
} }