Skip to content

Commit

Permalink
Support for names beginning with $ to be used as targets for RRCLONE …
Browse files Browse the repository at this point in the history
…records. (#50)

- Names beginning with $ will not be output to the zone file.
- RRCLONE records can point at these records to make use of them

Disabled RRCLONE records checking that the target exists, they will now just resolve to nothing if they do not.

Added "raw" export type to allow exporting zones as-is, including variable-named records and without RRCLONE records being expanded. (BIND import type will accept these records already)
  • Loading branch information
ShaneMcC committed Aug 4, 2020
1 parent 2016c8d commit 1cb196c
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 22 deletions.
7 changes: 7 additions & 0 deletions classes/ZoneFileHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ public final static function get($type = 'bind') {
class RecordsInfo {
private $records = [];

public function removeRecords($rrname, $rrtype) {
if (!isset($this->records[$rrtype])) { return; }
if (!isset($this->records[$rrtype][$rrname])) { return; }

unset($this->records[$rrtype][$rrname]);
}

public function addRecord($rrname, $rrtype, $content, $ttl, $priority = null) {
if (!isset($this->records[$rrtype])) { $this->records[$rrtype] = []; }
if (!isset($this->records[$rrtype][$rrname])) { $this->records[$rrtype][$rrname] = []; }
Expand Down
53 changes: 38 additions & 15 deletions classes/domain.php
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,19 @@ private function fixRecordContent($record, $recordDomain = Null) {
return $content;
}

public function getRecordsInfo($expandRecordsInfo = false) {
/**
* Get RecordsInfo for this domain.
* This will return an array that can be passed into ZoneFileHandlers.
*
* @param $expandRecordsInfo (Default: False) Should the 'records' entry
* be expanded into an array rather than a RecordsInfo object?
* @param $raw (Default: False) Should we return special records (RRCLONE)
* as-is rather than expanding them to real records?
* @return Array of ['soa' => <soa array>,
* 'records' => <RecordsInfo|Records Array>,
* 'hasNS' => <boolean>];
*/
public function getRecordsInfo($expandRecordsInfo = false, $raw = false) {
$recordDomain = ($this->getAliasOf() != null) ? $this->getAliasDomain(true) : $this;

$soa = $recordDomain->getSOARecord()->parseSOA();
Expand All @@ -470,31 +482,42 @@ public function getRecordsInfo($expandRecordsInfo = false) {

$hasNS |= ($record->getType() == "NS" && $record->getName() == $recordDomain->getDomain());

if ($record->getType() == 'RRCLONE') {
if (!$raw && $record->getType() == 'RRCLONE') {
$cloneRecords[] = $record;
continue;
}

$records->addRecord($name, $record->getType(), $content, $record->getTTL(), $record->getPriority());
}

// TODO: Allow RRCLONE to reference other RRCLONE records eventually.
foreach ($cloneRecords as $record) {
$name = $this->fixRecordName($record, $recordDomain);
$content = $this->fixRecordContent($record, $recordDomain);
if (!$raw) {
// TODO: Maybe allow RRCLONE to reference other RRCLONE records eventually.
foreach ($cloneRecords as $record) {
$name = $this->fixRecordName($record, $recordDomain);
$content = $this->fixRecordContent($record, $recordDomain);

$wantedRecord = explode(' ', $content);
$wantedRecord = $wantedRecord[count($wantedRecord) - 1];
$wantedRecord = explode(' ', $content);
$wantedRecord = $wantedRecord[count($wantedRecord) - 1];

$importTypes = [];
if (preg_match('#^\(([A-Z,*]+)\) ([^\s]+)$#i', $content, $m)) {
$importTypes = explode(',', strtoupper($m[1]));
$importTypes = [];
if (preg_match('#^\(([A-Z,*]+)\) ([^\s]+)$#i', $content, $m)) {
$importTypes = explode(',', strtoupper($m[1]));
}

foreach ($records->getByName($wantedRecord) as $sourceRecord) {
if (empty($importTypes) || in_array($sourceRecord['Type'], $importTypes) || in_array('*', $importTypes)) {
$records->addRecord($name, $sourceRecord['Type'], $sourceRecord['Address'], $sourceRecord['TTL'], $sourceRecord['Priority']);
$hasNS |= ($sourceRecord['Type'] == "NS" && $record->getName() == $recordDomain->getDomain());
}
}
}

foreach ($records->getByName($wantedRecord) as $sourceRecord) {
if (empty($importTypes) || in_array($sourceRecord['Type'], $importTypes) || in_array('*', $importTypes)) {
$records->addRecord($name, $sourceRecord['Type'], $sourceRecord['Address'], $sourceRecord['TTL'], $sourceRecord['Priority']);
$hasNS |= ($sourceRecord['Type'] == "NS" && $record->getName() == $recordDomain->getDomain());
// Remove names starting with $ which are only used as sources for RRCLONE records.
foreach ($records->get() as $rrtype => $rrnames) {
foreach (array_keys($rrnames) as $rrname) {
if (preg_match('#^\$#', $rrname)) {
$records->removeRecords($rrname, $rrtype);
}
}
}
}
Expand Down
16 changes: 13 additions & 3 deletions classes/record.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ public function validate() {

$testName = $this->getName();
$testName = preg_replace('#^\*\.#', 'WILDCARD.', $testName);
$testName = preg_replace('#^\$#', 'VAR', $testName);

if (!empty($testName) && !Domain::validDomainName($testName)) {
throw new ValidationFailed('Invalid name: "' . $this->getName() . '"');
Expand Down Expand Up @@ -260,7 +261,11 @@ public function validate() {
$testName = substr($testName, 0, -1);
}

if (!Domain::validDomainName($testName)) {
$checkName = $testName;
$checkName = preg_replace('#^\*\.#', 'WILDCARD.', $checkName);
$checkName = preg_replace('#^\$#', 'VAR', $checkName);

if (!Domain::validDomainName($checkName)) {
throw new ValidationFailed('Target must be a valid FQDN.');
} else {
$this->setContent('(' . $m[1] . ') ' . $testName);
Expand All @@ -273,7 +278,11 @@ public function validate() {
$testName = substr($testName, 0, -1);
}

if (!Domain::validDomainName($testName)) {
$checkName = $testName;
$checkName = preg_replace('#^\*\.#', 'WILDCARD.', $checkName);
$checkName = preg_replace('#^\$#', 'VAR', $checkName);

if (!Domain::validDomainName($checkName)) {
throw new ValidationFailed('Target must be a valid FQDN.');
} else {
$this->setContent($testName);
Expand Down Expand Up @@ -364,7 +373,8 @@ public function validate() {
}
}

if ($this->getType() == 'RRCLONE') {
// TODO: Until this can check for pending records, it's not a useful check.
if (false && $this->getType() == 'RRCLONE') {
// Check that the content we want exists.
$contentFilter = explode(' ', $this->getContent());
$contentFilter = $contentFilter[count($contentFilter) - 1];
Expand Down
12 changes: 9 additions & 3 deletions web/1.0/methods/domains.php
Original file line number Diff line number Diff line change
Expand Up @@ -342,12 +342,13 @@ protected function getDomainSync($domain) {
*
* @param $domain Domain object based on the 'domain' parameter.
* @param $type Type of zone file to export as.
* @param $raw Export Special records (eg RRCLONE) as-is?
* @return TRUE if we handled this method.
*/
protected function getDomainExport($domain, $type) {
protected function getDomainExport($domain, $type, $raw) {
try {
$zfh = ZoneFileHandler::get($type);
$output = $zfh->generateZoneFile($domain->getDomain(), $domain->getRecordsInfo(true));
$output = $zfh->generateZoneFile($domain->getDomain(), $domain->getRecordsInfo(true, $raw));

$this->getContextKey('response')->data(['zone' => $output]);
} catch (Exception $ex) {
Expand Down Expand Up @@ -1599,9 +1600,14 @@ function run($domain) {
function run($domain, $type = 'bind') {
$this->checkPermissions(['domains_read']);

if ($type == 'raw') {
$raw = true;
$type = 'bind';
}

$domain = $this->getDomainFromParam($domain);
// $this->checkAliasOf($domain);
return $this->getDomainExport($domain, $type);
return $this->getDomainExport($domain, $type, $raw);
}
});

Expand Down
8 changes: 7 additions & 1 deletion web/1.0/methods/system_datavalues.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,20 @@ function run() {
$router->get('/system/datavalue/importTypes', new class extends RouterMethod {
function run() {
$this->getContextKey('response')->set('importTypes', ['bind', 'tinydns']);
$this->getContextKey('response')->set('descriptions', ['bind' => 'Standard BIND9 Zone File (Default)',
'tinydns' => 'tinydns Compatible Zone File',
]);

return TRUE;
}
});

$router->get('/system/datavalue/exportTypes', new class extends RouterMethod {
function run() {
$this->getContextKey('response')->set('exportTypes', ['bind']);
$this->getContextKey('response')->set('exportTypes', ['bind', 'raw']);
$this->getContextKey('response')->set('descriptions', ['bind' => 'Standard BIND9 Zone File (Default)',
'raw' => 'BIND9 Zone File without expanding RRCLONE records.',
]);

return TRUE;
}
Expand Down

0 comments on commit 1cb196c

Please sign in to comment.