Merge pull request #361 from Froxlor/dns-subzone
Dns subzone fixes by nachtgeist
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
* @package Functions
|
||||
*
|
||||
*/
|
||||
function createDomainZone($domain_id, $froxlorhostname = false)
|
||||
function createDomainZone($domain_id, $froxlorhostname = false, $isMainButSubTo = false)
|
||||
{
|
||||
if (!$froxlorhostname)
|
||||
{
|
||||
@@ -49,9 +49,18 @@ function createDomainZone($domain_id, $froxlorhostname = false)
|
||||
|
||||
addRequiredEntry('@', 'A', $required_entries);
|
||||
addRequiredEntry('@', 'AAAA', $required_entries);
|
||||
addRequiredEntry('@', 'NS', $required_entries);
|
||||
if (! $isMainButSubTo) {
|
||||
addRequiredEntry('@', 'NS', $required_entries);
|
||||
}
|
||||
if ($domain['isemaildomain'] === '1') {
|
||||
addRequiredEntry('@', 'MX', $required_entries);
|
||||
if (Settings::Get('system.dns_createmailentry')) {
|
||||
foreach(['imap', 'pop3', 'mail', 'smtp'] as $record) {
|
||||
foreach(['AAAA', 'A'] as $type) {
|
||||
addRequiredEntry($record, $type, $required_entries);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// additional required records by setting
|
||||
@@ -67,7 +76,7 @@ function createDomainZone($domain_id, $froxlorhostname = false)
|
||||
{
|
||||
// additional required records for subdomains
|
||||
$subdomains_stmt = Database::prepare("
|
||||
SELECT `domain` FROM `" . TABLE_PANEL_DOMAINS . "`
|
||||
SELECT `domain`, `iswildcarddomain`, `wwwserveralias` FROM `" . TABLE_PANEL_DOMAINS . "`
|
||||
WHERE `parentdomainid` = :domainid
|
||||
");
|
||||
Database::pexecute($subdomains_stmt, array(
|
||||
@@ -81,28 +90,14 @@ function createDomainZone($domain_id, $froxlorhostname = false)
|
||||
addRequiredEntry(str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $required_entries);
|
||||
|
||||
// Check whether to add a www.-prefix
|
||||
if ($domain['iswildcarddomain'] == '1') {
|
||||
if ($subdomain['iswildcarddomain'] == '1') {
|
||||
addRequiredEntry('*.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'A', $required_entries);
|
||||
addRequiredEntry('*.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $required_entries);
|
||||
} elseif ($domain['wwwserveralias'] == '1') {
|
||||
} elseif ($subdomain['wwwserveralias'] == '1') {
|
||||
addRequiredEntry('www.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'A', $required_entries);
|
||||
addRequiredEntry('www.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $required_entries);
|
||||
}
|
||||
}
|
||||
|
||||
// additional required records for main-but-subdomain-to
|
||||
$mainbutsub_stmt = Database::prepare("
|
||||
SELECT `domain` FROM `" . TABLE_PANEL_DOMAINS . "`
|
||||
WHERE `ismainbutsubto` = :domainid
|
||||
");
|
||||
Database::pexecute($mainbutsub_stmt, array(
|
||||
'domainid' => $domain_id
|
||||
));
|
||||
|
||||
while ($mainbutsubtodomain = $mainbutsub_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
// Add NS entry for subdomain-records of "main-but-subdomain-to"-domains, they get their own Zone
|
||||
addRequiredEntry(str_replace('.' . $domain['domain'], '', $mainbutsubtodomain['domain']), 'NS', $required_entries);
|
||||
}
|
||||
}
|
||||
|
||||
// additional required records for SPF and DKIM if activated
|
||||
@@ -265,16 +260,31 @@ function createDomainZone($domain_id, $froxlorhostname = false)
|
||||
$primary_ns = Settings::Get('system.hostname');
|
||||
}
|
||||
|
||||
// TODO for now, dummy time-periods
|
||||
$soa_content = $primary_ns . " " . escapeSoaAdminMail(Settings::Get('panel.adminmail')) . " (" . PHP_EOL;
|
||||
$soa_content .= $domain['bindserial'] . "\t; serial" . PHP_EOL;
|
||||
$soa_content .= "1800\t; refresh (30 mins)" . PHP_EOL;
|
||||
$soa_content .= "900\t; retry (15 mins)" . PHP_EOL;
|
||||
$soa_content .= "604800\t; expire (7 days)" . PHP_EOL;
|
||||
$soa_content .= "1200\t)\t; minimum (20 mins)";
|
||||
if (! $isMainButSubTo) {
|
||||
$date = date('Ymd');
|
||||
$domain['bindserial'] = (preg_match('/^' . $date . '/', $domain['bindserial']) ?
|
||||
$domain['bindserial'] + 1 :
|
||||
$date . '00');
|
||||
if (!$froxlorhostname) {
|
||||
$upd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_DOMAINS . "` SET
|
||||
`bindserial` = :serial
|
||||
WHERE `id` = :id
|
||||
");
|
||||
Database::pexecute($upd_stmt, array('serial' => $domain['bindserial'], 'id' => $domain['id']));
|
||||
}
|
||||
|
||||
$soa_record = new DnsEntry('@', 'SOA', $soa_content);
|
||||
array_unshift($zonerecords, $soa_record);
|
||||
$soa_content = $primary_ns . " " . escapeSoaAdminMail(Settings::Get('panel.adminmail')) . " (" . PHP_EOL;
|
||||
$soa_content .= $domain['bindserial'] . "\t; serial" . PHP_EOL;
|
||||
// TODO for now, dummy time-periods
|
||||
$soa_content .= "1800\t; refresh (30 mins)" . PHP_EOL;
|
||||
$soa_content .= "900\t; retry (15 mins)" . PHP_EOL;
|
||||
$soa_content .= "604800\t; expire (7 days)" . PHP_EOL;
|
||||
$soa_content .= "1200\t)\t; minimum (20 mins)";
|
||||
|
||||
$soa_record = new DnsEntry('@', 'SOA', $soa_content);
|
||||
array_unshift($zonerecords, $soa_record);
|
||||
}
|
||||
|
||||
$zone = new DnsZone((int) Settings::Get('system.defaultttl'), $domain['domain'], $domain['bindserial'], $zonerecords);
|
||||
|
||||
|
||||
@@ -69,14 +69,38 @@ abstract class DnsBase
|
||||
|
||||
protected function getDomainList()
|
||||
{
|
||||
// get all Domains
|
||||
$result_domains_stmt = Database::query("
|
||||
SELECT `d`.`id`, `d`.`domain`, `d`.`customerid`, `d`.`zonefile`, `c`.`loginname`, `c`.`guid`
|
||||
FROM `" . TABLE_PANEL_DOMAINS . "` `d` LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`)
|
||||
WHERE `d`.`isbinddomain` = '1' ORDER BY `d`.`domain` ASC
|
||||
$result_domains_stmt = Database::query(
|
||||
"
|
||||
SELECT
|
||||
`d`.`id`,
|
||||
`d`.`domain`,
|
||||
`d`.`isemaildomain`,
|
||||
`d`.`iswildcarddomain`,
|
||||
`d`.`wwwserveralias`,
|
||||
`d`.`customerid`,
|
||||
`d`.`zonefile`,
|
||||
`d`.`bindserial`,
|
||||
`d`.`dkim`,
|
||||
`d`.`dkim_id`,
|
||||
`d`.`dkim_pubkey`,
|
||||
`d`.`ismainbutsubto`,
|
||||
`c`.`loginname`,
|
||||
`c`.`guid`
|
||||
FROM
|
||||
`" . TABLE_PANEL_DOMAINS . "` `d`
|
||||
LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`)
|
||||
WHERE
|
||||
`d`.`isbinddomain` = '1'
|
||||
ORDER BY
|
||||
`d`.`domain` ASC
|
||||
");
|
||||
|
||||
$domains = $result_domains_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$domains = array();
|
||||
// don't use fetchall() to be able to set the first column to the domain id and use it later on to set the rows'
|
||||
// array of direct children without having to search the outer array
|
||||
while ($domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$domains[$domain["id"]] = $domain;
|
||||
}
|
||||
|
||||
// frolxor-hostname (#1090)
|
||||
if (Settings::get('system.dns_createhostnameentry') == 1) {
|
||||
@@ -98,11 +122,58 @@ abstract class DnsBase
|
||||
}
|
||||
|
||||
if (empty($domains)) {
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, skipping...');
|
||||
return null;
|
||||
}
|
||||
|
||||
return $domains;
|
||||
// collect domain IDs of direct child domains as arrays in ['children'] column
|
||||
foreach (array_keys($domains) as $key) {
|
||||
if (! isset($domains[$key]['children'])) {
|
||||
$domains[$key]['children'] = array();
|
||||
}
|
||||
if ($domains[$key]['ismainbutsubto'] > 0) {
|
||||
if (isset($domains[ $domains[$key]['ismainbutsubto'] ])) {
|
||||
$domains[ $domains[$key]['ismainbutsubto'] ]['children'][] = $domains[$key]['id'];
|
||||
} else {
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_ERR,
|
||||
'Database inconsistency: domain ' . $domain['domain'] . ' (ID #' . $key .
|
||||
') is set to to be subdomain to non-existent domain ID #' .
|
||||
$domains[$key]['ismainbutsubto'] .
|
||||
'. No DNS record(s) will be created for this domain.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_DEBUG,
|
||||
str_pad('domId', 9, ' ') . str_pad('domain', 40, ' ') .
|
||||
'ismainbutsubto ' . str_pad('parent domain', 40, ' ') .
|
||||
"list of child domain ids");
|
||||
foreach ($domains as $domain) {
|
||||
$logLine =
|
||||
str_pad($domain['id'], 9, ' ') .
|
||||
str_pad($domain['domain'], 40, ' ') .
|
||||
str_pad($domain['ismainbutsubto'], 15, ' ') .
|
||||
str_pad(((isset($domains[ $domain['ismainbutsubto'] ])) ?
|
||||
$domains[ $domain['ismainbutsubto'] ]['domain'] :
|
||||
'-'), 40, ' ') .
|
||||
join(', ', $domain['children']);
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_DEBUG, $logLine);
|
||||
}
|
||||
|
||||
return $domains;
|
||||
}
|
||||
|
||||
public function reloadDaemon()
|
||||
{
|
||||
// reload DNS daemon
|
||||
$cmd = Settings::Get('system.bindreload_command');
|
||||
$cmdStatus = 1;
|
||||
safe_exec(escapeshellcmd($cmd), $cmdStatus);
|
||||
if ($cmdStatus === 0) {
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, Settings::Get('system.dns_server') . ' daemon reloaded');
|
||||
} else {
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_ERR, 'Error while running `' . $cmd .
|
||||
'`: exit code (' . $cmdStatus . ') - please check your system logs');
|
||||
}
|
||||
}
|
||||
|
||||
public function writeDKIMconfigs()
|
||||
|
||||
@@ -19,6 +19,8 @@ if (! defined('MASTER_CRONJOB'))
|
||||
class bind extends DnsBase
|
||||
{
|
||||
|
||||
private $_bindconf_file = "";
|
||||
|
||||
public function writeConfigs()
|
||||
{
|
||||
// tell the world what we are doing
|
||||
@@ -35,39 +37,71 @@ class bind extends DnsBase
|
||||
|
||||
$domains = $this->getDomainList();
|
||||
|
||||
if (! empty($domains)) {
|
||||
$bindconf_file = '# ' . Settings::Get('system.bindconf_directory') . 'froxlor_bind.conf' . "\n" . '# Created ' . date('d.m.Y H:i') . "\n" . '# Do NOT manually edit this file, all changes will be deleted after the next domain change at the panel.' . "\n\n";
|
||||
if (empty($domains)) {
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
// check for system-hostname
|
||||
$isFroxlorHostname = false;
|
||||
if (isset($domain['froxlorhost']) && $domain['froxlorhost'] == 1) {
|
||||
$isFroxlorHostname = true;
|
||||
}
|
||||
// create zone-file
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns zone for ' . $domain['domain']);
|
||||
$zone = createDomainZone(($domain['id'] == 'none') ? $domain : $domain['id'], $isFroxlorHostname);
|
||||
$zonefile = (string)$zone;
|
||||
$domain['zonefile'] = 'domains/' . $domain['domain'] . '.zone';
|
||||
$zonefile_name = makeCorrectFile(Settings::Get('system.bindconf_directory') . '/' . $domain['zonefile']);
|
||||
$zonefile_handler = fopen($zonefile_name, 'w');
|
||||
fwrite($zonefile_handler, $zonefile);
|
||||
fclose($zonefile_handler);
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, '`' . $zonefile_name . '` zone written');
|
||||
$bindconf_file = '# ' . Settings::Get('system.bindconf_directory') . 'froxlor_bind.conf' . "\n" . '# Created ' . date('d.m.Y H:i') . "\n" . '# Do NOT manually edit this file, all changes will be deleted after the next domain change at the panel.' . "\n\n";
|
||||
|
||||
// generate config
|
||||
$bindconf_file .= $this->_generateDomainConfig($domain);
|
||||
foreach ($domains as $domain) {
|
||||
if ($domain['ismainbutsubto'] > 0) {
|
||||
// domains with ismainbutsubto>0 are handled by recursion within walkDomainList()
|
||||
continue;
|
||||
}
|
||||
$this->walkDomainList($domain, $domains);
|
||||
}
|
||||
|
||||
$bindconf_file_handler = fopen(makeCorrectFile(Settings::Get('system.bindconf_directory') . '/froxlor_bind.conf'), 'w');
|
||||
fwrite($bindconf_file_handler, $this->_bindconf_file);
|
||||
fclose($bindconf_file_handler);
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, 'froxlor_bind.conf written');
|
||||
$this->reloadDaemon();
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Task4 finished');
|
||||
}
|
||||
|
||||
|
||||
private function walkDomainList($domain, $domains)
|
||||
{
|
||||
$zoneContent = '';
|
||||
$subzones = '';
|
||||
|
||||
foreach ($domain['children'] as $child_domain_id) {
|
||||
$subzones .= $this->walkDomainList($domains[$child_domain_id], $domains);
|
||||
}
|
||||
|
||||
if ($domain['zonefile'] == '') {
|
||||
// check for system-hostname
|
||||
$isFroxlorHostname = false;
|
||||
if (isset($domain['froxlorhost']) && $domain['froxlorhost'] == 1) {
|
||||
$isFroxlorHostname = true;
|
||||
}
|
||||
|
||||
// write config
|
||||
$bindconf_file_handler = fopen(makeCorrectFile(Settings::Get('system.bindconf_directory') . '/froxlor_bind.conf'), 'w');
|
||||
fwrite($bindconf_file_handler, $bindconf_file);
|
||||
fclose($bindconf_file_handler);
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, 'froxlor_bind.conf written');
|
||||
|
||||
// reload Bind
|
||||
safe_exec(escapeshellcmd(Settings::Get('system.bindreload_command')));
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Bind9 reloaded');
|
||||
if ($domain['ismainbutsubto'] == 0) {
|
||||
$zoneContent = (string) createDomainZone(($domain['id'] == 'none') ?
|
||||
$domain :
|
||||
$domain['id'],
|
||||
$isFroxlorHostname);
|
||||
$domain['zonefile'] = 'domains/' . $domain['domain'] . '.zone';
|
||||
$zonefile_name = makeCorrectFile(Settings::Get('system.bindconf_directory') . '/' .
|
||||
$domain['zonefile']);
|
||||
$zonefile_handler = fopen($zonefile_name, 'w');
|
||||
fwrite($zonefile_handler, $zoneContent . $subzones);
|
||||
fclose($zonefile_handler);
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, '`' . $zonefile_name . '` written');
|
||||
$this->_bindconf_file .= $this->_generateDomainConfig($domain);
|
||||
} else {
|
||||
return (string) createDomainZone(($domain['id'] == 'none') ?
|
||||
$domain :
|
||||
$domain['id'],
|
||||
$isFroxlorHostname,
|
||||
true);
|
||||
}
|
||||
} else {
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO,
|
||||
'Added zonefile ' . $domain['zonefile'] . ' for domain ' . $domain['domain'] .
|
||||
' - Note that you will also have to handle ALL records for ALL subdomains.');
|
||||
$this->_bindconf_file .= $this->_generateDomainConfig($domain);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,38 +30,72 @@ class pdns extends DnsBase
|
||||
$this->_connectToPdnsDb();
|
||||
|
||||
// clean up
|
||||
$this->_cleanZonefiles();
|
||||
$this->_clearZoneTables();
|
||||
|
||||
$domains = $this->getDomainList();
|
||||
|
||||
if (! empty($domains)) {
|
||||
if (empty($domains)) {
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
// check for system-hostname
|
||||
$isFroxlorHostname = false;
|
||||
if (isset($domain['froxlorhost']) && $domain['froxlorhost'] == 1) {
|
||||
$isFroxlorHostname = true;
|
||||
}
|
||||
// create zone-file
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns zone for ' . $domain['domain']);
|
||||
$zone = createDomainZone(($domain['id'] == 'none') ? $domain : $domain['id'], $isFroxlorHostname);
|
||||
foreach ($domains as $domain) {
|
||||
if ($domain['ismainbutsubto'] > 0) {
|
||||
// domains with ismainbutsubto>0 are handled by recursion within walkDomainList()
|
||||
continue;
|
||||
}
|
||||
$this->walkDomainList($domain, $domains);
|
||||
}
|
||||
|
||||
$dom_id = $this->_insertZone($zone->origin, $zone->serial);
|
||||
$this->_insertRecords($dom_id, $zone->records, $zone->origin);
|
||||
$this->_insertAllowedTransfers($dom_id);
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, 'PowerDNS database updated');
|
||||
$this->reloadDaemon();
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Task4 finished');
|
||||
}
|
||||
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, '`' . $domain['domain'] . '` zone written');
|
||||
private function walkDomainList($domain, $domains)
|
||||
{
|
||||
$zoneContent = '';
|
||||
$subzones = array();
|
||||
|
||||
foreach ($domain['children'] as $child_domain_id) {
|
||||
$subzones[] = $this->walkDomainList($domains[$child_domain_id], $domains);
|
||||
}
|
||||
|
||||
if ($domain['zonefile'] == '') {
|
||||
// check for system-hostname
|
||||
$isFroxlorHostname = false;
|
||||
if (isset($domain['froxlorhost']) && $domain['froxlorhost'] == 1) {
|
||||
$isFroxlorHostname = true;
|
||||
}
|
||||
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Database updated');
|
||||
|
||||
// reload Bind
|
||||
safe_exec(escapeshellcmd(Settings::Get('system.bindreload_command')));
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, 'pdns reloaded');
|
||||
if ($domain['ismainbutsubto'] == 0) {
|
||||
$zoneContent = createDomainZone(($domain['id'] == 'none') ?
|
||||
$domain :
|
||||
$domain['id'],
|
||||
$isFroxlorHostname);
|
||||
if (count($subzones)) {
|
||||
array_push($zoneContent->records, ...$subzones);
|
||||
}
|
||||
$pdnsDomId = $this->_insertZone($zoneContent->origin, $zoneContent->serial);
|
||||
$this->_insertRecords($pdnsDomId, $zoneContent->records, $zoneContent->origin);
|
||||
$this->_insertAllowedTransfers($pdnsDomId);
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, 'DB entries stored for zone `' . $domain['domain'] . '`');
|
||||
} else {
|
||||
return createDomainZone(($domain['id'] == 'none') ?
|
||||
$domain :
|
||||
$domain['id'],
|
||||
$isFroxlorHostname,
|
||||
true);
|
||||
}
|
||||
} else {
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_ERROR,
|
||||
'Zonefiles are NOT supported when PowerDNS is selected as DNS daemon (triggered by: ' .
|
||||
$domain['domain'] . ')');
|
||||
$this->_bindconf_file .= $this->_generateDomainConfig($domain);
|
||||
}
|
||||
}
|
||||
|
||||
private function _cleanZonefiles()
|
||||
private function _clearZoneTables()
|
||||
{
|
||||
$this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Cleaning dns zone entries from database');
|
||||
|
||||
@@ -96,6 +130,11 @@ class pdns extends DnsBase
|
||||
|
||||
foreach ($records as $record)
|
||||
{
|
||||
if ($record instanceof DnsZone) {
|
||||
$this->_insertRecords($domainid, $record->records, $record->origin);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($record->record == '@') {
|
||||
$_record = $origin;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user