From 5acd51fdd34c49811d045286c390779508e01755 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 9 May 2016 15:09:09 +0200 Subject: [PATCH 01/43] add first part of new dns-editor Signed-off-by: Michael Kaufmann (d00p) --- admin_domains.php | 3 + dns_editor.php | 233 ++++++++++++++++++ install/froxlor.sql | 15 +- .../updates/froxlor/0.9/update_0.9.inc.php | 23 ++ lib/tables.inc.php | 1 + lib/version.inc.php | 2 +- .../Sparkle/admin/domains/domains_edit.tpl | 1 + templates/Sparkle/assets/css/main.css | 22 ++ templates/Sparkle/dns_editor/entry_bit.tpl | 10 + templates/Sparkle/dns_editor/index.tpl | 28 +++ templates/Sparkle/dns_editor/list.tpl | 25 ++ 11 files changed, 361 insertions(+), 2 deletions(-) create mode 100644 dns_editor.php create mode 100644 templates/Sparkle/dns_editor/entry_bit.tpl create mode 100644 templates/Sparkle/dns_editor/index.tpl create mode 100644 templates/Sparkle/dns_editor/list.tpl diff --git a/admin_domains.php b/admin_domains.php index 479e1bc5..7ecc3d5d 100644 --- a/admin_domains.php +++ b/admin_domains.php @@ -2061,6 +2061,9 @@ elseif (Settings::Get('system.validate_domain') && ! validateDomain($domain)) { eval("echo \"" . getTemplate("domains/domains_import") . "\";"); } } +} elseif ($page == 'domaindnseditor') { + + require_once __DIR__.'/dns_editor.php'; } function formatDomainEntry(&$row, &$idna_convert) diff --git a/dns_editor.php b/dns_editor.php new file mode 100644 index 00000000..b6dd43e6 --- /dev/null +++ b/dns_editor.php @@ -0,0 +1,233 @@ + (2016-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Panel + * + */ + +// This file is being included in admin_domains and customer_domains + // and therefore does not need to require lib/init.php + +$domain_id = isset($_GET['domain_id']) ? (int) $_GET['domain_id'] : null; + +$record = isset($_POST['record']['record']) ? trim($_POST['record']['record']) : null; +$type = isset($_POST['record']['type']) ? $_POST['record']['type'] : 'A'; +$prio = isset($_POST['record']['prio']) ? (int) $_POST['record']['prio'] : null; +$content = isset($_POST['record']['content']) ? trim($_POST['record']['content']) : null; +$ttl = isset($_POST['record']['ttl']) ? (int) $_POST['record']['ttl'] : 18000; + +// get domain-name +$dom_stmt = Database::prepare("SELECT domain FROM `" . TABLE_PANEL_DOMAINS . "` WHERE id = :did"); +$domain = Database::pexecute_first($dom_stmt, array( + 'did' => $domain_id +)); +$domain = $idna_convert->decode($domain['domain']); + +// select all entries +$sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_DOMAIN_DNS . "` WHERE domain_id = :did"); +Database::pexecute($sel_stmt, array( + 'did' => $domain_id +)); +$dom_entries = $sel_stmt->fetchAll(PDO::FETCH_ASSOC); + +$errors = array(); +$success_message = ""; + +// action for adding a new entry +if ($action == 'add_record' && ! empty($_POST)) { + + // validation + if (empty($record)) { + $record = "@"; + } + + $record = strtolower($record); + + if ($ttl <= 0) { + $ttl = 18000; + } + + if (empty($content)) { + $errors[] = $lng['error']['dns_content_empty']; + } + + // types + if ($type == 'A' && filter_var($content, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false) { + $errors[] = $lng['error']['dns_arec_noipv4']; + } elseif ($type == 'AAAA' && filter_var($content, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) { + $errors[] = $lng['errors']['dns_aaaarec_noipv6']; + } elseif ($type == 'MX' && empty($prio)) { + $errors[] = $lng['error']['dns_mx_prioempty']; + } elseif ($type == 'CNAME') { + // check for trailing dot + if (substr($content, - 1) == '.') { + // remove it for checks + $content = substr($content, 0, - 1); + } + if (! validateDomain($content)) { + $errors[] = $lng['error']['dns_cname_invaliddom']; + } else { + // check whether there are RR-records for the same resource + foreach ($dom_entries as $existing_entries) { + if (($existing_entries['type'] == 'A' || $existing_entries['type'] == 'AAAA' || $existing_entries['type'] == 'MX' || $existing_entries['type'] == 'NS') && $existing_entries['record'] == $record) { + $errors[] = $lng['error']['dns_cname_nomorerr']; + break; + } + } + } + // append trailing dot (again) + $content .= '.'; + } elseif ($type == 'TXT' && ! empty($content)) { + // check that TXT content is enclosed in " " + if (substr($content, 0, 1) != '"') { + $content = '"' . $content; + } + if (substr($content, - 1) != '"') { + $content .= '"'; + } + } elseif ($type == 'SRV') { + // check for trailing dot + if (substr($content, - 1) == '.') { + // remove it for checks + $content = substr($content, 0, - 1); + } + // + if (! validateDomain($content)) { + $errors[] = $lng['error']['dns_srv_needdom']; + } else { + // check whether there is a CNAME-record for the same resource + foreach ($dom_entries as $existing_entries) { + $fqdn = $existing_entries['record'] . '.' . $domain; + if ($existing_entries['type'] == 'CNAME' && $fqdn == $content) { + $errors[] = $lng['error']['dns_srv_noalias']; + break; + } + } + } + // append trailing dot (again) + $content .= '.'; + } + + $new_entry = array( + 'record' => $record, + 'type' => $type, + 'prio' => $prio, + 'content' => $content, + 'ttl' => $ttl, + 'domain_id' => $domain_id + ); + ksort($new_entry); + + // check for duplicate + foreach ($dom_entries as $existing_entry) { + // compare serialized string of array + $check_entry = $existing_entry; + // new entry has no ID yet + unset($check_entry['id']); + // sort by key + ksort($check_entry); + // format integer fields to real integer (as they are read as string from the DB) + $check_entry['prio'] = (int)$check_entry['prio']; + $check_entry['ttl'] = (int)$check_entry['ttl']; + $check_entry['domain_id'] = (int)$check_entry['domain_id']; + // serialize both + $check_entry = serialize($check_entry); + $new = serialize($new_entry); + // compare + if ($check_entry === $new) { + $errors[] = $lng['error']['dns_duplicate_entry']; + unset($check_entry); + break; + } + } + + if (empty($errors)) { + $ins_stmt = Database::prepare(" + INSERT INTO `" . TABLE_DOMAIN_DNS . "` SET + `record` = :record, + `type` = :type, + `prio` = :prio, + `content` = :content, + `ttl` = :ttl, + `domain_id` = :domain_id + "); + + Database::pexecute($ins_stmt, $new_entry); + + $new_entry_id = Database::lastInsertId(); + + // add temporary to the entries-array (no reread of DB necessary) + $new_entry['id'] = $new_entry_id; + $dom_entries[] = $new_entry; + + // success message (inline) + $success_message = $lng['success']['dns_record_added']; + } else { + // show $errors + $errors = implode("
", $errors); + } +} elseif ($action == 'delete') { + // remove entry + $entry_id = isset($_GET['id']) ? (int) $_GET['id'] : 0; + if ($entry_id > 0) { + $del_stmt = Database::prepare("DELETE FROM `" . TABLE_DOMAIN_DNS . "` WHERE `id` = :id"); + Database::pexecute($del_stmt, array( + 'id' => $entry_id + )); + + // remove deleted entry from internal data array (no reread of DB necessary) + $_t = $dom_entries; + foreach ($_t as $idx => $entry) { + if ($entry['id'] == $entry_id) { + unset($dom_entries[$idx]); + break; + } + } + unset($_t); + // success message (inline) + $success_message = $lng['success']['dns_record_deleted']; + } +} + +// show editor +$record_list = ""; +$existing_entries = ""; +$type_select = ""; +$entriescount = 0; + +if (! empty($dom_entries)) { + $entriescount = count($dom_entries); + foreach ($dom_entries as $entry) { + eval("\$existing_entries.=\"" . getTemplate("dns_editor/entry_bit", true) . "\";"); + } +} + +// available types +$type_select_values = array( + 'A', + 'AAAA', + 'NS', + 'MX', + 'SRV', + 'TXT', + 'CNAME' +); +asort($type_select_values); +foreach ($type_select_values as $_type) { + $type_select .= makeoption($_type, $_type, $type); +} + +eval("\$record_list=\"" . getTemplate("dns_editor/list", true) . "\";"); +eval("echo \"" . getTemplate("dns_editor/index", true) . "\";"); diff --git a/install/froxlor.sql b/install/froxlor.sql index 178b9135..7b0d39ca 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -557,7 +557,7 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES ('panel', 'password_special_char_required', '0'), ('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'version', '0.9.35.1'), - ('panel', 'db_version', '201604270'); + ('panel', 'db_version', '201605090'); DROP TABLE IF EXISTS `panel_tasks`; @@ -855,3 +855,16 @@ CREATE TABLE IF NOT EXISTS `panel_domaintoip` ( PRIMARY KEY (`id_domain`,`id_ipandports`) ) ENGINE=MyISAM CHARSET=utf8 COLLATE=utf8_general_ci; + +DROP TABLE IF EXISTS `domain_dns_entries`; +CREATE TABLE `domain_dns_entries` ( + `id` int(20) NOT NULL, + `domain_id` int(15) NOT NULL, + `record` varchar(255) NOT NULL, + `type` varchar(10) NOT NULL DEFAULT 'A', + `content` text NOT NULL, + `ttl` int(11) NOT NULL DEFAULT '18000', + `prio` int(11) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM CHARSET=utf8 COLLATE=utf8_general_ci; + diff --git a/install/updates/froxlor/0.9/update_0.9.inc.php b/install/updates/froxlor/0.9/update_0.9.inc.php index 5a096f90..88c7139d 100644 --- a/install/updates/froxlor/0.9/update_0.9.inc.php +++ b/install/updates/froxlor/0.9/update_0.9.inc.php @@ -3312,3 +3312,26 @@ if (isFroxlorVersion('0.9.35.1') && isDatabaseVersion('201603150')) { updateToDbVersion('201604270'); } + +if (isFroxlorVersion('0.9.35.1') && isDatabaseVersion('201604270')) { + + showUpdateStep("Adding new dns related tables and settings"); + $enable_dns = isset($_POST['enable_dns']) ? (int) $_POST['enable_dns'] : "0"; + Settings::AddNew("system.dnsenabled", $enable_dns); + + Database::query("DROP TABLE IF EXISTS `domain_dns_entries`;"); + $sql = "CREATE TABLE `domain_dns_entries` ( + `id` int(20) NOT NULL, + `domain_id` int(15) NOT NULL, + `record` varchar(255) NOT NULL, + `type` varchar(10) NOT NULL DEFAULT 'A', + `content` text NOT NULL, + `ttl` int(11) NOT NULL DEFAULT '18000', + `prio` int(11) DEFAULT NULL, + PRIMARY KEY (`id`) + ) DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + Database::query($sql); + lastStepStatus(0); + + updateToDbVersion('201605090'); +} diff --git a/lib/tables.inc.php b/lib/tables.inc.php index 65fafc0a..39b37e24 100644 --- a/lib/tables.inc.php +++ b/lib/tables.inc.php @@ -50,5 +50,6 @@ define('TABLE_PANEL_REDIRECTCODES', 'redirect_codes'); define('TABLE_PANEL_DOMAINREDIRECTS', 'domain_redirect_codes'); define('TABLE_PANEL_DOMAIN_SSL_SETTINGS', 'domain_ssl_settings'); define('TABLE_DOMAINTOIP', 'panel_domaintoip'); +define('TABLE_DOMAIN_DNS', 'domain_dns_entries'); require dirname(__FILE__).'/version.inc.php'; diff --git a/lib/version.inc.php b/lib/version.inc.php index 2d17e6d1..5bc79f20 100644 --- a/lib/version.inc.php +++ b/lib/version.inc.php @@ -19,7 +19,7 @@ $version = '0.9.35.1'; // Database version (YYYYMMDDC where C is a daily counter) -$dbversion = '201604270'; +$dbversion = '201605090'; // Distribution branding-tag (used for Debian etc.) $branding = ''; diff --git a/templates/Sparkle/admin/domains/domains_edit.tpl b/templates/Sparkle/admin/domains/domains_edit.tpl index 89b1acc4..fdd9b203 100644 --- a/templates/Sparkle/admin/domains/domains_edit.tpl +++ b/templates/Sparkle/admin/domains/domains_edit.tpl @@ -4,6 +4,7 @@ $header

{$title}  {$title} +  (edit DNS)

diff --git a/templates/Sparkle/assets/css/main.css b/templates/Sparkle/assets/css/main.css index 38e07046..3d3f27bc 100644 --- a/templates/Sparkle/assets/css/main.css +++ b/templates/Sparkle/assets/css/main.css @@ -601,6 +601,12 @@ input[type="password"] { background:#fff url(../img/icons/lock.png) no-repeat 5px 4px; } +input[class="small"] { + width:auto; + margin-top: 5px; +} + + /* * BUTTONS */ @@ -1513,3 +1519,19 @@ fieldset.file { table.hl tbody tr.domain-hostname:hover { background-color: rgb(64, 150, 238); } + +td.size-5 { + width: 5%; +} + +td.size-10 { + width: 10%; +} + +td.size-20 { + width: 20%; +} + +td.size-50 { + width: 50%; +} diff --git a/templates/Sparkle/dns_editor/entry_bit.tpl b/templates/Sparkle/dns_editor/entry_bit.tpl new file mode 100644 index 00000000..1e80ff67 --- /dev/null +++ b/templates/Sparkle/dns_editor/entry_bit.tpl @@ -0,0 +1,10 @@ + + {$entry['record']} + {$entry['type']} +  {$entry['prio']} + {$entry['content']} + {$entry['ttl']} + + {$lng['panel']['delete']} + + diff --git a/templates/Sparkle/dns_editor/index.tpl b/templates/Sparkle/dns_editor/index.tpl new file mode 100644 index 00000000..92f37970 --- /dev/null +++ b/templates/Sparkle/dns_editor/index.tpl @@ -0,0 +1,28 @@ +$header +
+
+

+   + DNS Editor ({$domain}, {$entriescount} records) +

+
+ + +
+
{$lng['error']['error']}
+
{$errors}
+
+
+ +
+
{$lng['success']['success']}
+
{$success_message}
+
+
+ +
+ {$record_list} +
+ +
+$footer diff --git a/templates/Sparkle/dns_editor/list.tpl b/templates/Sparkle/dns_editor/list.tpl new file mode 100644 index 00000000..3d2e490a --- /dev/null +++ b/templates/Sparkle/dns_editor/list.tpl @@ -0,0 +1,25 @@ +
+ + + + + + + + + + + + + + + + + + + + + {$existing_entries} + +
RecordTypePriorityContentTTL 
+
From 31d08d532c276b10ff308878a2b8b9e4454fc6d9 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 9 May 2016 21:31:02 +0200 Subject: [PATCH 02/43] fix missing auto-increment for new table Signed-off-by: Michael Kaufmann (d00p) --- install/froxlor.sql | 2 +- install/updates/froxlor/0.9/update_0.9.inc.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/install/froxlor.sql b/install/froxlor.sql index 7b0d39ca..ad61eec2 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -858,7 +858,7 @@ CREATE TABLE IF NOT EXISTS `panel_domaintoip` ( DROP TABLE IF EXISTS `domain_dns_entries`; CREATE TABLE `domain_dns_entries` ( - `id` int(20) NOT NULL, + `id` int(20) NOT NULL auto_increment, `domain_id` int(15) NOT NULL, `record` varchar(255) NOT NULL, `type` varchar(10) NOT NULL DEFAULT 'A', diff --git a/install/updates/froxlor/0.9/update_0.9.inc.php b/install/updates/froxlor/0.9/update_0.9.inc.php index 88c7139d..4af29fef 100644 --- a/install/updates/froxlor/0.9/update_0.9.inc.php +++ b/install/updates/froxlor/0.9/update_0.9.inc.php @@ -3321,7 +3321,7 @@ if (isFroxlorVersion('0.9.35.1') && isDatabaseVersion('201604270')) { Database::query("DROP TABLE IF EXISTS `domain_dns_entries`;"); $sql = "CREATE TABLE `domain_dns_entries` ( - `id` int(20) NOT NULL, + `id` int(20) NOT NULL auto_increment, `domain_id` int(15) NOT NULL, `record` varchar(255) NOT NULL, `type` varchar(10) NOT NULL DEFAULT 'A', From 283e272b9983455b807a153fe3b09b382128e2ae Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Mon, 9 May 2016 21:52:58 +0200 Subject: [PATCH 03/43] enhance MX validation; fix SRV validation Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 34 +++++++++++++++++++++++++++++----- lng/english.lng.php | 13 +++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/dns_editor.php b/dns_editor.php index b6dd43e6..b56bb4f1 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -68,8 +68,29 @@ if ($action == 'add_record' && ! empty($_POST)) { $errors[] = $lng['error']['dns_arec_noipv4']; } elseif ($type == 'AAAA' && filter_var($content, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) { $errors[] = $lng['errors']['dns_aaaarec_noipv6']; - } elseif ($type == 'MX' && empty($prio)) { - $errors[] = $lng['error']['dns_mx_prioempty']; + } elseif ($type == 'MX') { + if ($prio === null || $prio < 0) { + $errors[] = $lng['error']['dns_mx_prioempty']; + } + // check for trailing dot + if (substr($content, - 1) == '.') { + // remove it for checks + $content = substr($content, 0, - 1); + } + if (! validateDomain($content)) { + $errors[] = $lng['error']['dns_mx_needdom']; + } else { + // check whether there is a CNAME-record for the same resource + foreach ($dom_entries as $existing_entries) { + $fqdn = $existing_entries['record'] . '.' . $domain; + if ($existing_entries['type'] == 'CNAME' && $fqdn == $content) { + $errors[] = $lng['error']['dns_mx_noalias']; + break; + } + } + } + // append trailing dot (again) + $content .= '.'; } elseif ($type == 'CNAME') { // check for trailing dot if (substr($content, - 1) == '.') { @@ -103,14 +124,17 @@ if ($action == 'add_record' && ! empty($_POST)) { // remove it for checks $content = substr($content, 0, - 1); } - // - if (! validateDomain($content)) { + // check only last part of content, as it can look like: + // _service._proto.name. TTL class SRV priority weight port target. + $_split_content = explode(" ", $content); + $target = trim($_split_content[count($_split_content)-1]); + if (! validateDomain($target)) { $errors[] = $lng['error']['dns_srv_needdom']; } else { // check whether there is a CNAME-record for the same resource foreach ($dom_entries as $existing_entries) { $fqdn = $existing_entries['record'] . '.' . $domain; - if ($existing_entries['type'] == 'CNAME' && $fqdn == $content) { + if ($existing_entries['type'] == 'CNAME' && $fqdn == $target) { $errors[] = $lng['error']['dns_srv_noalias']; break; } diff --git a/lng/english.lng.php b/lng/english.lng.php index 6783d8a8..83e7b232 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -1989,3 +1989,16 @@ $lng['serversettings']['backupenabled']['description'] = "If activated, the cust $lng['extras']['path_protection_label'] = 'Important'; $lng['extras']['path_protection_info'] = 'We strongly recommend protecting the given path, see "Extras" -> "Directory protection"'; $lng['tasks']['backup_customerfiles'] = 'Backup job for customer %loginname%'; + +$lng['error']['dns_content_empty'] = 'No content given'; +$lng['error']['dns_arec_noipv4'] = 'No valid IP address for A-record given'; +$lng['error']['dns_mx_prioempty'] = 'Invalid MX priority given'; +$lng['error']['dns_mx_needdom'] = 'The MX content value must be a valid domain-name'; +$lng['error']['dns_mx_noalias'] = 'The MX-content value cannot be an CNAME entry.'; +$lng['error']['dns_cname_invaliddom'] = 'Invalid domain-name for CNAME record'; +$lng['error']['dns_cname_nomorerr'] = 'There already exists a resource-record with the same record-name. It cannot be used as CNAME.'; +$lng['error']['dns_srv_needdom'] = 'The SRV target value must be a valid domain-name'; +$lng['error']['dns_srv_noalias'] = 'The SRV-target value cannot be an CNAME entry.'; +$lng['error']['dns_duplicate_entry'] = 'Record already exists'; +$lng['success']['dns_record_added'] = 'Record added successfully'; +$lng['success']['dns_record_deleted'] = 'Record deleted successfully'; From e33d7b756f953681cb652538a74072ac1f9ae597 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 10 May 2016 07:37:07 +0200 Subject: [PATCH 04/43] add missing error-language-string; check whether domain is bind-domain Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 10 +++++++--- lng/english.lng.php | 2 ++ templates/Sparkle/admin/domains/domains_edit.tpl | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/dns_editor.php b/dns_editor.php index b56bb4f1..9fbddf2e 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -18,7 +18,7 @@ if (! defined('AREA')) */ // This file is being included in admin_domains and customer_domains - // and therefore does not need to require lib/init.php +// and therefore does not need to require lib/init.php $domain_id = isset($_GET['domain_id']) ? (int) $_GET['domain_id'] : null; @@ -29,10 +29,14 @@ $content = isset($_POST['record']['content']) ? trim($_POST['record']['content'] $ttl = isset($_POST['record']['ttl']) ? (int) $_POST['record']['ttl'] : 18000; // get domain-name -$dom_stmt = Database::prepare("SELECT domain FROM `" . TABLE_PANEL_DOMAINS . "` WHERE id = :did"); +$dom_stmt = Database::prepare("SELECT domain, isbinddomain FROM `" . TABLE_PANEL_DOMAINS . "` WHERE id = :did"); $domain = Database::pexecute_first($dom_stmt, array( 'did' => $domain_id )); + +if ($domain['isbinddomain'] != '0') { + standard_error('dns_domain_nodns'); +} $domain = $idna_convert->decode($domain['domain']); // select all entries @@ -67,7 +71,7 @@ if ($action == 'add_record' && ! empty($_POST)) { if ($type == 'A' && filter_var($content, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false) { $errors[] = $lng['error']['dns_arec_noipv4']; } elseif ($type == 'AAAA' && filter_var($content, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) { - $errors[] = $lng['errors']['dns_aaaarec_noipv6']; + $errors[] = $lng['error']['dns_aaaarec_noipv6']; } elseif ($type == 'MX') { if ($prio === null || $prio < 0) { $errors[] = $lng['error']['dns_mx_prioempty']; diff --git a/lng/english.lng.php b/lng/english.lng.php index 83e7b232..976c0ae7 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -1990,8 +1990,10 @@ $lng['extras']['path_protection_label'] = 'Important {$title}  {$title} -  (edit DNS) + +  (edit DNS) + From 64d068659f4df0546a0f841075f118d39ca3f783 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 10 May 2016 10:54:59 +0200 Subject: [PATCH 05/43] more validation for NS and SRV records; fix display of long records Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 37 ++++++++++++++++++---- lng/english.lng.php | 3 ++ templates/Sparkle/dns_editor/entry_bit.tpl | 2 +- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/dns_editor.php b/dns_editor.php index 9fbddf2e..7d500f32 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -18,7 +18,7 @@ if (! defined('AREA')) */ // This file is being included in admin_domains and customer_domains -// and therefore does not need to require lib/init.php + // and therefore does not need to require lib/init.php $domain_id = isset($_GET['domain_id']) ? (int) $_GET['domain_id'] : null; @@ -34,7 +34,7 @@ $domain = Database::pexecute_first($dom_stmt, array( 'did' => $domain_id )); -if ($domain['isbinddomain'] != '0') { +if ($domain['isbinddomain'] != '1') { standard_error('dns_domain_nodns'); } $domain = $idna_convert->decode($domain['domain']); @@ -114,6 +114,17 @@ if ($action == 'add_record' && ! empty($_POST)) { } // append trailing dot (again) $content .= '.'; + } elseif ($type == 'NS') { + // check for trailing dot + if (substr($content, - 1) == '.') { + // remove it for checks + $content = substr($content, 0, - 1); + } + if (! validateDomain($content)) { + $errors[] = $lng['error']['dns_ns_invaliddom']; + } + // append trailing dot (again) + $content .= '.'; } elseif ($type == 'TXT' && ! empty($content)) { // check that TXT content is enclosed in " " if (substr($content, 0, 1) != '"') { @@ -123,6 +134,9 @@ if ($action == 'add_record' && ! empty($_POST)) { $content .= '"'; } } elseif ($type == 'SRV') { + if ($prio === null || $prio < 0) { + $errors[] = $lng['error']['dns_srv_prioempty']; + } // check for trailing dot if (substr($content, - 1) == '.') { // remove it for checks @@ -131,7 +145,11 @@ if ($action == 'add_record' && ! empty($_POST)) { // check only last part of content, as it can look like: // _service._proto.name. TTL class SRV priority weight port target. $_split_content = explode(" ", $content); - $target = trim($_split_content[count($_split_content)-1]); + // SRV content must be [weight] [port] [target] + if (count($_split_content) != 3) { + $errors[] = $lng['error']['dns_srv_invalidcontent']; + } + $target = trim($_split_content[count($_split_content) - 1]); if (! validateDomain($target)) { $errors[] = $lng['error']['dns_srv_needdom']; } else { @@ -167,9 +185,9 @@ if ($action == 'add_record' && ! empty($_POST)) { // sort by key ksort($check_entry); // format integer fields to real integer (as they are read as string from the DB) - $check_entry['prio'] = (int)$check_entry['prio']; - $check_entry['ttl'] = (int)$check_entry['ttl']; - $check_entry['domain_id'] = (int)$check_entry['domain_id']; + $check_entry['prio'] = (int) $check_entry['prio']; + $check_entry['ttl'] = (int) $check_entry['ttl']; + $check_entry['domain_id'] = (int) $check_entry['domain_id']; // serialize both $check_entry = serialize($check_entry); $new = serialize($new_entry); @@ -202,6 +220,12 @@ if ($action == 'add_record' && ! empty($_POST)) { // success message (inline) $success_message = $lng['success']['dns_record_added']; + + unset($record); + unset($type); + unset($prio); + unset($content); + unset($ttl); } else { // show $errors $errors = implode("
", $errors); @@ -238,6 +262,7 @@ $entriescount = 0; if (! empty($dom_entries)) { $entriescount = count($dom_entries); foreach ($dom_entries as $entry) { + $entry['content'] = wordwrap($entry['content'], 100, '
', true); eval("\$existing_entries.=\"" . getTemplate("dns_editor/entry_bit", true) . "\";"); } } diff --git a/lng/english.lng.php b/lng/english.lng.php index 976c0ae7..a42edaf9 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -1999,6 +1999,9 @@ $lng['error']['dns_mx_needdom'] = 'The MX content value must be a valid domain-n $lng['error']['dns_mx_noalias'] = 'The MX-content value cannot be an CNAME entry.'; $lng['error']['dns_cname_invaliddom'] = 'Invalid domain-name for CNAME record'; $lng['error']['dns_cname_nomorerr'] = 'There already exists a resource-record with the same record-name. It cannot be used as CNAME.'; +$lng['error']['dns_ns_invaliddom'] = 'Invalid domain-name for NS record'; +$lng['error']['dns_srv_prioempty'] = 'Invalid SRV priority given'; +$lng['error']['dns_srv_invalidcontent'] = 'Invalid SRV content, must contain of fields weight, port and target, e.g.: 5 5060 sipserver.example.com.'; $lng['error']['dns_srv_needdom'] = 'The SRV target value must be a valid domain-name'; $lng['error']['dns_srv_noalias'] = 'The SRV-target value cannot be an CNAME entry.'; $lng['error']['dns_duplicate_entry'] = 'Record already exists'; diff --git a/templates/Sparkle/dns_editor/entry_bit.tpl b/templates/Sparkle/dns_editor/entry_bit.tpl index 1e80ff67..1882b0ba 100644 --- a/templates/Sparkle/dns_editor/entry_bit.tpl +++ b/templates/Sparkle/dns_editor/entry_bit.tpl @@ -1,7 +1,7 @@ {$entry['record']} {$entry['type']} -  {$entry['prio']} +  {$entry['prio']} {$entry['content']} {$entry['ttl']} From 407a7c01aa68ed3fb0804ac5bdaae8209691f47e Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 10 May 2016 14:11:01 +0200 Subject: [PATCH 06/43] add createDomainZone function for new dns-editor; dump zone below the editor for testing purposes; all required entries that are not custom entered will be auto-generated like the bind-cron does this for now Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 2 + .../dns/function.createDomainZone.php | 213 ++++++++++++++++++ templates/Sparkle/dns_editor/index.tpl | 4 + 3 files changed, 219 insertions(+) create mode 100644 lib/functions/dns/function.createDomainZone.php diff --git a/dns_editor.php b/dns_editor.php index 7d500f32..2ce58c78 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -283,4 +283,6 @@ foreach ($type_select_values as $_type) { } eval("\$record_list=\"" . getTemplate("dns_editor/list", true) . "\";"); + +$zonefile = createDomainZone($domain_id); eval("echo \"" . getTemplate("dns_editor/index", true) . "\";"); diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php new file mode 100644 index 00000000..9775ac7a --- /dev/null +++ b/lib/functions/dns/function.createDomainZone.php @@ -0,0 +1,213 @@ + (2016-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Functions + * + */ +function createDomainZone($domain_id) +{ + // get domain-name + $dom_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` WHERE id = :did"); + $domain = Database::pexecute_first($dom_stmt, array( + 'did' => $domain_id + )); + + if ($domain['isbinddomain'] != '1') { + return; + } + + // select all entries + $sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_DOMAIN_DNS . "` WHERE domain_id = :did ORDER BY id ASC"); + Database::pexecute($sel_stmt, array( + 'did' => $domain_id + )); + $dom_entries = $sel_stmt->fetchAll(PDO::FETCH_ASSOC); + + // @TODO alias domains + + // TODO for now, dummy time-periods + $soa_content = getPrimaryNs($dom_entries) . " " . str_replace('@', '.', 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)"; + + // create Zone + $zonefile = "\$TTL " . (int) Settings::Get('system.defaultttl') . PHP_EOL; + $zonefile .= "\$ORIGIN " . $domain['domain'] . "." . PHP_EOL; + $zonefile .= formatEntry('@', 'SOA', $soa_content); + + // check for required records + $required_entries = array(); + + addRequiredEntry('@', 'A', $required_entries); + addRequiredEntry('@', 'AAAA', $required_entries); + addRequiredEntry('@', 'NS', $required_entries); + if ($domain['isemaildomain'] === '1') { + addRequiredEntry('@', 'MX', $required_entries); + } + + // additional required records by setting + if ($domain['iswildcarddomain'] == '1') { + addRequiredEntry('*', 'A', $required_entries); + addRequiredEntry('*', 'AAAA', $required_entries); + } else + if ($domain['wwwserveralias'] == '1') { + addRequiredEntry('www', 'A', $required_entries); + addRequiredEntry('www', 'AAAA', $required_entries); + } + + // additional required records for subdomains + $subdomains_stmt = Database::prepare(" + SELECT `domain` FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE `parentdomainid` = :domainid + "); + Database::pexecute($subdomains_stmt, array( + 'domainid' => $domain_id + )); + + while ($subdomain = $subdomains_stmt->fetch(PDO::FETCH_ASSOC)) { + // Listing domains is enough as there currently is no support for choosing + // different ips for a subdomain => use same IPs as toplevel + addRequiredEntry(str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'A', $required_entries); + addRequiredEntry(str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $required_entries); + + // Check whether to add a www.-prefix + if ($domain['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') { + addRequiredEntry('www.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'A', $required_entries); + addRequiredEntry('www.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $required_entries); + } + } + + // now generate all records and unset the required entries we have + foreach ($dom_entries as $entry) { + if (array_key_exists($entry['type'], $required_entries) && $required_entries[$entry['type']][md5($entry['record'])] == $entry['record']) { + unset($required_entries[$entry['type']][md5($entry['record'])]); + } + $zonefile .= formatEntry($entry['record'], $entry['type'], $entry['content'], $entry['prio'], $entry['ttl']); + } + + // add missing required entries + if (! empty($required_entries)) { + + // A / AAAA records + if (array_key_exists("A", $required_entries) || array_key_exists("AAAA", $required_entries)) { + $result_ip_stmt = Database::prepare(" + SELECT `p`.`ip` AS `ip` + FROM `" . TABLE_PANEL_IPSANDPORTS . "` `p`, `" . TABLE_DOMAINTOIP . "` `di` + WHERE `di`.`id_domain` = :domainid AND `p`.`id` = `di`.`id_ipandports` + GROUP BY `p`.`ip`; + "); + Database::pexecute($result_ip_stmt, array( + 'domainid' => $domain_id + )); + $all_ips = $result_ip_stmt->fetchAll(PDO::FETCH_ASSOC); + + foreach ($all_ips as $ip) { + foreach ($required_entries as $type => $records) { + foreach ($records as $record) { + if ($type == 'A' && filter_var($ip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) { + $zonefile .= formatEntry($record, 'A', $ip['ip']); + } elseif ($type == 'AAAA' && filter_var($ip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) { + $zonefile .= formatEntry($record, 'AAAA', $ip['ip']); + } + } + } + unset($required_entries['A']); + unset($required_entries['AAAA']); + } + } + + // NS records + if (array_key_exists("NS", $required_entries)) { + if (Settings::Get('system.nameservers') != '') { + $nameservers = explode(',', Settings::Get('system.nameservers')); + foreach ($nameservers as $nameserver) { + $nameserver = trim($nameserver); + // append dot to hostname + if (substr($nameserver, - 1, 1) != '.') { + $nameserver .= '.'; + } + foreach ($required_entries as $type => $records) { + if ($type == 'NS') { + foreach ($records as $record) { + $zonefile .= formatEntry($record, 'NS', $nameserver); + } + } + } + } + unset($required_entries['NS']); + } + } + + // MX records + if (array_key_exists("MX", $required_entries)) { + if (Settings::Get('system.mxservers') != '') { + $mxservers = explode(',', Settings::Get('system.mxservers')); + foreach ($mxservers as $mxserver) { + if (substr($mxserver, - 1, 1) != '.') { + $mxserver .= '.'; + } + // split in prio and server + $mx_details = explode(" ", $mxserver); + if (count($mx_details) == 1) { + $mx_details[1] = $mx_details[0]; + $mx_details[0] = 10; + } + foreach ($required_entries as $type => $records) { + if ($type == 'MX') { + foreach ($records as $record) { + $zonefile .= formatEntry($record, 'MX', $mx_details[1], $mx_details[0]); + } + } + } + } + unset($required_entries['MX']); + } + } + } + + var_dump($required_entries); + + return $zonefile; +} + +function formatEntry($record = '@', $type = 'A', $content = null, $prio = 0, $ttl = 18000, $class = 'IN') +{ + $result = $record . "\t" . $ttl . "\t" . $class . "\t" . $type . "\t" . (($prio >= 0 && ($type == 'MX' || $type == 'SRV')) ? $prio . "\t" : "") . $content . PHP_EOL; + return $result; +} + +function addRequiredEntry($record = '@', $type = 'A', &$required) +{ + if (!isset($required[$type])) { + $required[$type] = array(); + } + $required[$type][md5($record)] = $record; +} + +function getPrimaryNs($dom_entries) +{ + // go through all records and use the first NS record as primary NS + foreach ($dom_entries as $entry) { + if ($entry['type'] == 'NS') { + return $entry['content']; + } + } + // FIXME use default from settings somehow if none given? + return 'no.dns-server.given.tld.'; +} diff --git a/templates/Sparkle/dns_editor/index.tpl b/templates/Sparkle/dns_editor/index.tpl index 92f37970..737873cb 100644 --- a/templates/Sparkle/dns_editor/index.tpl +++ b/templates/Sparkle/dns_editor/index.tpl @@ -25,4 +25,8 @@ $header + +

+ + $footer From b775c2f60ea15de3c4c6da86705402f911dedf82 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 10 May 2016 19:20:01 +0200 Subject: [PATCH 07/43] minor fixes Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 10 +++++----- lib/functions/dns/function.createDomainZone.php | 4 +--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/dns_editor.php b/dns_editor.php index 2ce58c78..46a57ce7 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -221,11 +221,11 @@ if ($action == 'add_record' && ! empty($_POST)) { // success message (inline) $success_message = $lng['success']['dns_record_added']; - unset($record); - unset($type); - unset($prio); - unset($content); - unset($ttl); + $record = ""; + $type = 'A'; + $prio = ""; + $content = ""; + $ttl = ""; } else { // show $errors $errors = implode("
", $errors); diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php index 9775ac7a..dd4e585b 100644 --- a/lib/functions/dns/function.createDomainZone.php +++ b/lib/functions/dns/function.createDomainZone.php @@ -95,7 +95,7 @@ function createDomainZone($domain_id) // now generate all records and unset the required entries we have foreach ($dom_entries as $entry) { - if (array_key_exists($entry['type'], $required_entries) && $required_entries[$entry['type']][md5($entry['record'])] == $entry['record']) { + if (array_key_exists($entry['type'], $required_entries) && array_key_exists(md5($entry['record']), $required_entries[$entry['type']])) { unset($required_entries[$entry['type']][md5($entry['record'])]); } $zonefile .= formatEntry($entry['record'], $entry['type'], $entry['content'], $entry['prio'], $entry['ttl']); @@ -181,8 +181,6 @@ function createDomainZone($domain_id) } } - var_dump($required_entries); - return $zonefile; } From d1106dd9844370670689c05b94e5b6c7f58f8225 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 11 May 2016 07:44:32 +0200 Subject: [PATCH 08/43] fix wrong unset of array-element Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 2 ++ lib/functions/dns/function.createDomainZone.php | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dns_editor.php b/dns_editor.php index 46a57ce7..ca53b698 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -59,6 +59,8 @@ if ($action == 'add_record' && ! empty($_POST)) { $record = strtolower($record); + // TODO regex validate record and content for invalid characters + if ($ttl <= 0) { $ttl = 18000; } diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php index dd4e585b..86ffa75c 100644 --- a/lib/functions/dns/function.createDomainZone.php +++ b/lib/functions/dns/function.createDomainZone.php @@ -127,9 +127,9 @@ function createDomainZone($domain_id) } } } - unset($required_entries['A']); - unset($required_entries['AAAA']); } + unset($required_entries['A']); + unset($required_entries['AAAA']); } // NS records From 7379398d229b07d043768c7364fc1fde693af48f Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 11 May 2016 13:47:36 +0200 Subject: [PATCH 09/43] set correct primary nameserver for SOA record Signed-off-by: Michael Kaufmann (d00p) --- .../dns/function.createDomainZone.php | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php index 86ffa75c..0ddf9e39 100644 --- a/lib/functions/dns/function.createDomainZone.php +++ b/lib/functions/dns/function.createDomainZone.php @@ -33,21 +33,6 @@ function createDomainZone($domain_id) )); $dom_entries = $sel_stmt->fetchAll(PDO::FETCH_ASSOC); - // @TODO alias domains - - // TODO for now, dummy time-periods - $soa_content = getPrimaryNs($dom_entries) . " " . str_replace('@', '.', 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)"; - - // create Zone - $zonefile = "\$TTL " . (int) Settings::Get('system.defaultttl') . PHP_EOL; - $zonefile .= "\$ORIGIN " . $domain['domain'] . "." . PHP_EOL; - $zonefile .= formatEntry('@', 'SOA', $soa_content); - // check for required records $required_entries = array(); @@ -93,11 +78,18 @@ function createDomainZone($domain_id) } } + $primary_ns = null; + $zonefile = ""; + // now generate all records and unset the required entries we have foreach ($dom_entries as $entry) { if (array_key_exists($entry['type'], $required_entries) && array_key_exists(md5($entry['record']), $required_entries[$entry['type']])) { unset($required_entries[$entry['type']][md5($entry['record'])]); } + if (empty($primary_ns) && $entry['type'] == 'NS') { + // use the first NS entry as primary ns + $primary_ns = $entry['content']; + } $zonefile .= formatEntry($entry['record'], $entry['type'], $entry['content'], $entry['prio'], $entry['ttl']); } @@ -145,6 +137,10 @@ function createDomainZone($domain_id) foreach ($required_entries as $type => $records) { if ($type == 'NS') { foreach ($records as $record) { + if (empty($primary_ns)) { + // use the first NS entry as primary ns + $primary_ns = $nameserver; + } $zonefile .= formatEntry($record, 'NS', $nameserver); } } @@ -181,6 +177,24 @@ function createDomainZone($domain_id) } } + if (empty($primary_ns)) { + // TODO log error: no NS given, use system-hostname + $primary_ns = Settings::Get('system.hostname'); + } + + // TODO for now, dummy time-periods + $soa_content = $primary_ns . " " . str_replace('@', '.', 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)"; + + $_zonefile = "\$TTL " . (int) Settings::Get('system.defaultttl') . PHP_EOL; + $_zonefile .= "\$ORIGIN " . $domain['domain'] . "." . PHP_EOL; + $_zonefile .= formatEntry('@', 'SOA', $soa_content); + $zonefile = $_zonefile.$zonefile; + return $zonefile; } @@ -197,15 +211,3 @@ function addRequiredEntry($record = '@', $type = 'A', &$required) } $required[$type][md5($record)] = $record; } - -function getPrimaryNs($dom_entries) -{ - // go through all records and use the first NS record as primary NS - foreach ($dom_entries as $entry) { - if ($entry['type'] == 'NS') { - return $entry['content']; - } - } - // FIXME use default from settings somehow if none given? - return 'no.dns-server.given.tld.'; -} From 9b5ce83e8b4ccbee750866e6cae0d3918dbce3da Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 11 May 2016 16:11:23 +0200 Subject: [PATCH 10/43] add testing-bind-cron for new dns-stuff (not activated); added main-but-subdomain-stuff Signed-off-by: Michael Kaufmann (d00p) --- .../dns/function.createDomainZone.php | 35 ++- scripts/jobs/cron_tasks.inc.dns.15.bind.php | 214 ++++++++++++++++++ 2 files changed, 242 insertions(+), 7 deletions(-) create mode 100644 scripts/jobs/cron_tasks.inc.dns.15.bind.php diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php index 0ddf9e39..aa0b4cc0 100644 --- a/lib/functions/dns/function.createDomainZone.php +++ b/lib/functions/dns/function.createDomainZone.php @@ -14,7 +14,7 @@ * @package Functions * */ -function createDomainZone($domain_id) +function createDomainZone($domain_id, $froxlorhostname = false) { // get domain-name $dom_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` WHERE id = :did"); @@ -78,6 +78,20 @@ function createDomainZone($domain_id) } } + // 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); + } + $primary_ns = null; $zonefile = ""; @@ -98,12 +112,19 @@ function createDomainZone($domain_id) // A / AAAA records if (array_key_exists("A", $required_entries) || array_key_exists("AAAA", $required_entries)) { - $result_ip_stmt = Database::prepare(" - SELECT `p`.`ip` AS `ip` - FROM `" . TABLE_PANEL_IPSANDPORTS . "` `p`, `" . TABLE_DOMAINTOIP . "` `di` - WHERE `di`.`id_domain` = :domainid AND `p`.`id` = `di`.`id_ipandports` - GROUP BY `p`.`ip`; - "); + if ($froxlorhostname) { + // use all available IP's for the froxlor-hostname + $result_ip_stmt = Database::prepare(" + SELECT `ip` FROM `".TABLE_PANEL_IPSANDPORTS."` GROUP BY `ip` + "); + } else { + $result_ip_stmt = Database::prepare(" + SELECT `p`.`ip` AS `ip` + FROM `" . TABLE_PANEL_IPSANDPORTS . "` `p`, `" . TABLE_DOMAINTOIP . "` `di` + WHERE `di`.`id_domain` = :domainid AND `p`.`id` = `di`.`id_ipandports` + GROUP BY `p`.`ip`; + "); + } Database::pexecute($result_ip_stmt, array( 'domainid' => $domain_id )); diff --git a/scripts/jobs/cron_tasks.inc.dns.15.bind.php b/scripts/jobs/cron_tasks.inc.dns.15.bind.php new file mode 100644 index 00000000..5918266a --- /dev/null +++ b/scripts/jobs/cron_tasks.inc.dns.15.bind.php @@ -0,0 +1,214 @@ + (2016-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Cron + * + */ +class bind2 +{ + + private $_logger = false; + + private $_ns = array(); + + private $_mx = array(); + + private $_axfr = array(); + + public function __construct($logger) + { + $this->_logger = $logger; + + if (Settings::Get('system.nameservers') != '') { + $nameservers = explode(',', Settings::Get('system.nameservers')); + foreach ($nameservers as $nameserver) { + $nameserver = trim($nameserver); + // DNS servers might be multi homed; allow transfer from all ip + // addresses of the DNS server + $nameserver_ips = gethostbynamel($nameserver); + // append dot to hostname + if (substr($nameserver, - 1, 1) != '.') { + $nameserver .= '.'; + } + // ignore invalid responses + if (! is_array($nameserver_ips)) { + // act like gethostbyname() and return unmodified hostname on error + $nameserver_ips = array( + $nameserver + ); + } + $this->_ns[] = array( + 'hostname' => $nameserver, + 'ips' => $nameserver_ips + ); + } + } + + if (Settings::Get('system.mxservers') != '') { + $mxservers = explode(',', Settings::Get('system.mxservers')); + foreach ($mxservers as $mxserver) { + if (substr($mxserver, - 1, 1) != '.') { + $mxserver .= '.'; + } + $this->_mx[] = $mxserver; + } + } + + // AXFR server #100 + if (Settings::Get('system.axfrservers') != '') { + $axfrservers = explode(',', Settings::Get('system.axfrservers')); + foreach ($axfrservers as $axfrserver) { + $this->_axfr[] = trim($axfrserver); + } + } + } + + public function writeConfigs() + { + // tell the world what we are doing + $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Task4 started - Rebuilding froxlor_bind.conf'); + + // clean up + $this->_cleanZonefiles(); + + // check for subfolder in bind-config-directory + if (! file_exists(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))) { + $this->logger->logAction(CRON_ACTION, LOG_NOTICE, 'mkdir ' . escapeshellarg(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))); + safe_exec('mkdir -p ' . escapeshellarg(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))); + } + + // 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 + "); + + $domains = $result_domains_stmt->fetchAll(PDO::FETCH_ASSOC); + + // frolxor-hostname (#1090) + if (Settings::get('system.dns_createhostnameentry') == 1) { + $hostname_arr = array( + 'id' => 'none', + 'domain' => Settings::Get('system.hostname'), + 'isemaildomain' => Settings::Get('system.dns_createmailentry'), + 'customerid' => 'none', + 'loginname' => 'froxlor.panel', + 'bindserial' => date('Ymd') . '00', + 'dkim' => '0', + 'iswildcarddomain' => '1', + 'ismainbutsubto' => '0', + 'zonefile' => '', + 'froxlorhost' => '1' + ); + $domains['none'] = $hostname_arr; + } + + if (empty($domains)) { + $this->logger->logAction(CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, skipping...'); + return; + } + + $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"; + + foreach ($domains as $domain) { + // check for system-hostname + $isFroxlorHostname = false; + if (isset($domain['froxlorhost']) && $domain['froxlorhost'] == 1) { + $isFroxlorHostname = true; + } + // create zone-file + $zonefile = createDomainZone($domain, $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, $zonefile); + fclose($zonefile_handler); + $this->logger->logAction(CRON_ACTION, LOG_INFO, '`' . $zonefile_name . '` zone written'); + + // generate config + $bindconf_file .= $this->_generateDomainConfig($domain); + } + + // 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'); + } + + private function _generateDomainConfig($domain = array()) + { + $bindconf_file = '# Domain ID: ' . $domain['id'] . ' - CustomerID: ' . $domain['customerid'] . ' - CustomerLogin: ' . $domain['loginname'] . "\n"; + $bindconf_file .= 'zone "' . $domain['domain'] . '" in {' . "\n"; + $bindconf_file .= ' type master;' . "\n"; + $bindconf_file .= ' file "' . makeCorrectFile(Settings::Get('system.bindconf_directory') . '/' . $domain['zonefile']) . '";' . "\n"; + $bindconf_file .= ' allow-query { any; };' . "\n"; + + if (count($this->nameservers) > 0 || count($this->axfrservers) > 0) { + // open allow-transfer + $bindconf_file .= ' allow-transfer {' . "\n"; + // put nameservers in allow-transfer + if (count($this->nameservers) > 0) { + foreach ($this->nameservers as $ns) { + foreach ($ns["ips"] as $ip) { + $bindconf_file .= ' ' . $ip . ";\n"; + } + } + } + // AXFR server #100 + if (count($this->axfrservers) > 0) { + foreach ($this->axfrservers as $axfrserver) { + if (validate_ip($axfrserver, true) !== false) { + $bindconf_file .= ' ' . $axfrserver . ';' . "\n"; + } + } + } + // close allow-transfer + $bindconf_file .= ' };' . "\n"; + } + + $bindconf_file .= '};' . "\n"; + $bindconf_file .= "\n"; + + return $bindconf_file; + } + + private function _cleanZonefiles() + { + $config_dir = makeCorrectFile(Settings::Get('system.bindconf_directory') . '/domains/'); + + $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Cleaning dns zone files from '.$config_dir); + + // check directory + if (@is_dir($config_dir)) { + + // create directory iterator + $its = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($config_dir)); + + // iterate through all subdirs, look for zone files and delete them + foreach ($its as $it) { + if ($it->isFile()) { + // remove file + safe_exec('rm -f ' . escapeshellarg(makeCorrectFile($its->getPathname()))); + } + } + } + } +} From 0404618c24a66dd150c6e672c6b4ef23ccd910b1 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 11 May 2016 18:10:17 +0200 Subject: [PATCH 11/43] add experimental bind-cron for testing purposes Signed-off-by: Michael Kaufmann (d00p) --- scripts/jobs/cron_tasks.inc.dns.15.bind.php | 25 ++++++++++++--------- scripts/jobs/cron_tasks.php | 8 +++++-- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/scripts/jobs/cron_tasks.inc.dns.15.bind.php b/scripts/jobs/cron_tasks.inc.dns.15.bind.php index 5918266a..0e423951 100644 --- a/scripts/jobs/cron_tasks.inc.dns.15.bind.php +++ b/scripts/jobs/cron_tasks.inc.dns.15.bind.php @@ -85,7 +85,7 @@ class bind2 // check for subfolder in bind-config-directory if (! file_exists(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))) { - $this->logger->logAction(CRON_ACTION, LOG_NOTICE, 'mkdir ' . escapeshellarg(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))); + $this->_logger->logAction(CRON_ACTION, LOG_NOTICE, 'mkdir ' . escapeshellarg(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))); safe_exec('mkdir -p ' . escapeshellarg(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))); } @@ -117,7 +117,7 @@ class bind2 } if (empty($domains)) { - $this->logger->logAction(CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, skipping...'); + $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, skipping...'); return; } @@ -130,13 +130,14 @@ class bind2 $isFroxlorHostname = true; } // create zone-file - $zonefile = createDomainZone($domain, $isFroxlorHostname); + $this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns zone for '.$domain['domain']); + $zonefile = createDomainZone($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, $zonefile); fclose($zonefile_handler); - $this->logger->logAction(CRON_ACTION, LOG_INFO, '`' . $zonefile_name . '` zone written'); + $this->_logger->logAction(CRON_ACTION, LOG_INFO, '`' . $zonefile_name . '` zone written'); // generate config $bindconf_file .= $this->_generateDomainConfig($domain); @@ -146,35 +147,37 @@ class bind2 $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'); + $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'); + $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Bind9 reloaded'); } private function _generateDomainConfig($domain = array()) { + $this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns config for '.$domain['domain']); + $bindconf_file = '# Domain ID: ' . $domain['id'] . ' - CustomerID: ' . $domain['customerid'] . ' - CustomerLogin: ' . $domain['loginname'] . "\n"; $bindconf_file .= 'zone "' . $domain['domain'] . '" in {' . "\n"; $bindconf_file .= ' type master;' . "\n"; $bindconf_file .= ' file "' . makeCorrectFile(Settings::Get('system.bindconf_directory') . '/' . $domain['zonefile']) . '";' . "\n"; $bindconf_file .= ' allow-query { any; };' . "\n"; - if (count($this->nameservers) > 0 || count($this->axfrservers) > 0) { + if (count($this->_ns) > 0 || count($this->_axfr) > 0) { // open allow-transfer $bindconf_file .= ' allow-transfer {' . "\n"; // put nameservers in allow-transfer - if (count($this->nameservers) > 0) { - foreach ($this->nameservers as $ns) { + if (count($this->_ns) > 0) { + foreach ($this->_ns as $ns) { foreach ($ns["ips"] as $ip) { $bindconf_file .= ' ' . $ip . ";\n"; } } } // AXFR server #100 - if (count($this->axfrservers) > 0) { - foreach ($this->axfrservers as $axfrserver) { + if (count($this->_axfr) > 0) { + foreach ($this->_axfr as $axfrserver) { if (validate_ip($axfrserver, true) !== false) { $bindconf_file .= ' ' . $axfrserver . ';' . "\n"; } diff --git a/scripts/jobs/cron_tasks.php b/scripts/jobs/cron_tasks.php index 45020038..9a175892 100644 --- a/scripts/jobs/cron_tasks.php +++ b/scripts/jobs/cron_tasks.php @@ -19,6 +19,7 @@ // necessary includes require_once makeCorrectFile(dirname(__FILE__) . '/cron_tasks.inc.dns.10.bind.php'); +require_once makeCorrectFile(dirname(__FILE__) . '/cron_tasks.inc.dns.15.bind.php'); require_once makeCorrectFile(dirname(__FILE__) . '/cron_tasks.inc.http.10.apache.php'); require_once makeCorrectFile(dirname(__FILE__) . '/cron_tasks.inc.http.15.apache_fcgid.php'); require_once makeCorrectFile(dirname(__FILE__) . '/cron_tasks.inc.http.20.lighttpd.php'); @@ -179,11 +180,14 @@ while ($row = $result_tasks_stmt->fetch(PDO::FETCH_ASSOC)) { * TYPE=4 MEANS THAT SOMETHING IN THE BIND CONFIG HAS CHANGED. REBUILD froxlor_bind.conf IF BIND IS ENABLED */ elseif ($row['type'] == '4' && (int)Settings::Get('system.bind_enable') != 0) { + + $bindclass ="bind2"; + if (!isset($nameserver)) { - $nameserver = new bind($cronlog); + $nameserver = new $bindclass($cronlog); } - if (Settings::Get('dkim.use_dkim') == '1') { + if (Settings::Get('dkim.use_dkim') == '1' && $bindclass == 'bind') { $nameserver->writeDKIMconfigs(); } From 68d579b6290e959038392a1dbfbc1df249f154ff Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 12 May 2016 09:36:33 +0200 Subject: [PATCH 12/43] add SPF and DKIM stuff to DNS Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 7 +- .../dns/function.createDomainZone.php | 143 ++++++++++++++++-- scripts/jobs/cron_tasks.inc.dns.15.bind.php | 86 ++++++++++- scripts/jobs/cron_tasks.php | 2 +- 4 files changed, 219 insertions(+), 19 deletions(-) diff --git a/dns_editor.php b/dns_editor.php index ca53b698..06a68861 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -129,12 +129,7 @@ if ($action == 'add_record' && ! empty($_POST)) { $content .= '.'; } elseif ($type == 'TXT' && ! empty($content)) { // check that TXT content is enclosed in " " - if (substr($content, 0, 1) != '"') { - $content = '"' . $content; - } - if (substr($content, - 1) != '"') { - $content .= '"'; - } + $content = encloseTXTContent($content); } elseif ($type == 'SRV') { if ($prio === null || $prio < 0) { $errors[] = $lng['error']['dns_srv_prioempty']; diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php index aa0b4cc0..a1202e1d 100644 --- a/lib/functions/dns/function.createDomainZone.php +++ b/lib/functions/dns/function.createDomainZone.php @@ -47,11 +47,10 @@ function createDomainZone($domain_id, $froxlorhostname = false) if ($domain['iswildcarddomain'] == '1') { addRequiredEntry('*', 'A', $required_entries); addRequiredEntry('*', 'AAAA', $required_entries); - } else - if ($domain['wwwserveralias'] == '1') { - addRequiredEntry('www', 'A', $required_entries); - addRequiredEntry('www', 'AAAA', $required_entries); - } + } elseif ($domain['wwwserveralias'] == '1') { + addRequiredEntry('www', 'A', $required_entries); + addRequiredEntry('www', 'AAAA', $required_entries); + } // additional required records for subdomains $subdomains_stmt = Database::prepare(" @@ -92,13 +91,33 @@ function createDomainZone($domain_id, $froxlorhostname = false) addRequiredEntry(str_replace('.' . $domain['domain'], '', $mainbutsubtodomain['domain']), 'NS', $required_entries); } + // additional required records for SPF and DKIM if activated + if (Settings::Get('spf.use_spf') == '1') { + // check for SPF content later + addRequiredEntry('@SPF@', 'TXT', $required_entries); + } + if (Settings::Get('dkim.use_dkim') == '1') { + // check for DKIM content later + addRequiredEntry('dkim_' . $domain['dkim_id'] . '._domainkey', 'TXT', $required_entries); + // check for ASDP + if (Settings::Get('dkim.dkim_add_adsp') == "1") { + addRequiredEntry('_adsp._domainkey', 'TXT', $required_entries); + } + } + $primary_ns = null; $zonefile = ""; // now generate all records and unset the required entries we have foreach ($dom_entries as $entry) { if (array_key_exists($entry['type'], $required_entries) && array_key_exists(md5($entry['record']), $required_entries[$entry['type']])) { - unset($required_entries[$entry['type']][md5($entry['record'])]); + // check for SPF entry if required + if (Settings::Get('spf.use_spf') == '1' && $entry['type'] == 'TXT' && $entry['record'] == '@' && strtolower(substr($entry['content'], 0, 7)) == '"v=spf1') { + // unset special spf required-entry + unset($required_entries[$entry['type']][md5("@SPF@")]); + } else { + unset($required_entries[$entry['type']][md5($entry['record'])]); + } } if (empty($primary_ns) && $entry['type'] == 'NS') { // use the first NS entry as primary ns @@ -115,7 +134,7 @@ function createDomainZone($domain_id, $froxlorhostname = false) if ($froxlorhostname) { // use all available IP's for the froxlor-hostname $result_ip_stmt = Database::prepare(" - SELECT `ip` FROM `".TABLE_PANEL_IPSANDPORTS."` GROUP BY `ip` + SELECT `ip` FROM `" . TABLE_PANEL_IPSANDPORTS . "` GROUP BY `ip` "); } else { $result_ip_stmt = Database::prepare(" @@ -196,6 +215,29 @@ function createDomainZone($domain_id, $froxlorhostname = false) unset($required_entries['MX']); } } + + // TXT (SPF and DKIM) + if (array_key_exists("TXT", $required_entries)) { + + if (Settings::Get('dkim.use_dkim') == '1') { + $dkim_entries = generateDkimEntries($domain); + } + + foreach ($required_entries as $type => $records) { + if ($type == 'TXT') { + foreach ($records as $record) { + if ($record == '@SPF@') { + $txt_content = Settings::Get('spf.spf_entry'); + $zonefile .= formatEntry('@', 'TXT', encloseTXTContent($txt_content)); + } elseif ($record == 'dkim_' . $domain['dkim_id'] . '._domainkey' && ! empty($dkim_entries)) { + $zonefile .= formatEntry($record, 'TXT', encloseTXTContent($dkim_entries[0])); + } elseif ($record == '_adsp._domainkey' && ! empty($dkim_entries) && isset($dkim_entries[1])) { + $zonefile .= formatEntry($record, 'TXT', encloseTXTContent($dkim_entries[1])); + } + } + } + } + } } if (empty($primary_ns)) { @@ -214,7 +256,7 @@ function createDomainZone($domain_id, $froxlorhostname = false) $_zonefile = "\$TTL " . (int) Settings::Get('system.defaultttl') . PHP_EOL; $_zonefile .= "\$ORIGIN " . $domain['domain'] . "." . PHP_EOL; $_zonefile .= formatEntry('@', 'SOA', $soa_content); - $zonefile = $_zonefile.$zonefile; + $zonefile = $_zonefile . $zonefile; return $zonefile; } @@ -227,8 +269,91 @@ function formatEntry($record = '@', $type = 'A', $content = null, $prio = 0, $tt function addRequiredEntry($record = '@', $type = 'A', &$required) { - if (!isset($required[$type])) { + if (! isset($required[$type])) { $required[$type] = array(); } $required[$type][md5($record)] = $record; } + +function generateDkimEntries($domain) +{ + $zone_dkim = array(); + + if (Settings::Get('dkim.use_dkim') == '1' && $domain['dkim'] == '1' && $domain['dkim_pubkey'] != '') { + // start + $dkim_txt = 'v=DKIM1;'; + + // algorithm + $algorithm = explode(',', Settings::Get('dkim.dkim_algorithm')); + $alg = ''; + foreach ($algorithm as $a) { + if ($a == 'all') { + break; + } else { + $alg .= $a . ':'; + } + } + + if ($alg != '') { + $alg = substr($alg, 0, - 1); + $dkim_txt .= 'h=' . $alg . ';'; + } + + // notes + if (trim(Settings::Get('dkim.dkim_notes') != '')) { + $dkim_txt .= 'n=' . trim(Settings::Get('dkim.dkim_notes')) . ';'; + } + + // key + $dkim_txt .= 'k=rsa;p=' . trim(preg_replace('/-----BEGIN PUBLIC KEY-----(.+)-----END PUBLIC KEY-----/s', '$1', str_replace("\n", '', $domain['dkim_pubkey']))) . ';'; + + // service-type + if (Settings::Get('dkim.dkim_servicetype') == '1') { + $dkim_txt .= 's=email;'; + } + + // end-part + $dkim_txt .= 't=s'; + + // split if necessary + $txt_record_split = ''; + $lbr = 50; + for ($pos = 0; $pos <= strlen($dkim_txt) - 1; $pos += $lbr) { + $txt_record_split .= (($pos == 0) ? '("' : "\t\t\t\t\t \"") . substr($dkim_txt, $pos, $lbr) . (($pos >= strlen($dkim_txt) - $lbr) ? '")' : '"') . "\n"; + } + + // dkim-entry + $zone_dkim[] = $txt_record_split; + + // adsp-entry + if (Settings::Get('dkim.dkim_add_adsp') == "1") { + $adsp = '"dkim='; + switch ((int) Settings::Get('dkim.dkim_add_adsppolicy')) { + case 0: + $adsp .= 'unknown"'; + break; + case 1: + $adsp .= 'all"'; + break; + case 2: + $adsp .= 'discardable"'; + break; + } + $zone_dkim[] = $adsp; + } + } + + return $zone_dkim; +} + +function encloseTXTContent($txt_content) +{ + // check that TXT content is enclosed in " " + if (substr($txt_content, 0, 1) != '"') { + $txt_content = '"' . $txt_content; + } + if (substr($txt_content, - 1) != '"') { + $txt_content .= '"'; + } + return $txt_content; +} diff --git a/scripts/jobs/cron_tasks.inc.dns.15.bind.php b/scripts/jobs/cron_tasks.inc.dns.15.bind.php index 0e423951..55347a2d 100644 --- a/scripts/jobs/cron_tasks.inc.dns.15.bind.php +++ b/scripts/jobs/cron_tasks.inc.dns.15.bind.php @@ -130,7 +130,7 @@ class bind2 $isFroxlorHostname = true; } // create zone-file - $this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns zone for '.$domain['domain']); + $this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns zone for ' . $domain['domain']); $zonefile = createDomainZone($domain['id'], $isFroxlorHostname); $domain['zonefile'] = 'domains/' . $domain['domain'] . '.zone'; $zonefile_name = makeCorrectFile(Settings::Get('system.bindconf_directory') . '/' . $domain['zonefile']); @@ -154,9 +154,89 @@ class bind2 $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Bind9 reloaded'); } + public function writeDKIMconfigs() + { + if (Settings::Get('dkim.use_dkim') == '1') { + if (! file_exists(makeCorrectDir(Settings::Get('dkim.dkim_prefix')))) { + $this->_logger->logAction(CRON_ACTION, LOG_NOTICE, 'mkdir -p ' . escapeshellarg(makeCorrectDir(Settings::Get('dkim.dkim_prefix')))); + safe_exec('mkdir -p ' . escapeshellarg(makeCorrectDir(Settings::Get('dkim.dkim_prefix')))); + } + + $dkimdomains = ''; + $dkimkeys = ''; + $result_domains_stmt = Database::query(" + SELECT `id`, `domain`, `dkim`, `dkim_id`, `dkim_pubkey`, `dkim_privkey` + FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `dkim` = '1' ORDER BY `id` ASC + "); + + while ($domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) { + + $privkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id']); + $pubkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id'] . '.public'); + + if ($domain['dkim_privkey'] == '' || $domain['dkim_pubkey'] == '') { + $max_dkim_id_stmt = Database::query("SELECT MAX(`dkim_id`) as `max_dkim_id` FROM `" . TABLE_PANEL_DOMAINS . "`"); + $max_dkim_id = $max_dkim_id_stmt->fetch(PDO::FETCH_ASSOC); + $domain['dkim_id'] = (int) $max_dkim_id['max_dkim_id'] + 1; + $privkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id']); + safe_exec('openssl genrsa -out ' . escapeshellarg($privkey_filename) . ' ' . Settings::Get('dkim.dkim_keylength')); + $domain['dkim_privkey'] = file_get_contents($privkey_filename); + safe_exec("chmod 0640 " . escapeshellarg($privkey_filename)); + $pubkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id'] . '.public'); + safe_exec('openssl rsa -in ' . escapeshellarg($privkey_filename) . ' -pubout -outform pem -out ' . escapeshellarg($pubkey_filename)); + $domain['dkim_pubkey'] = file_get_contents($pubkey_filename); + safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename)); + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DOMAINS . "` SET + `dkim_id` = :dkimid, + `dkim_privkey` = :privkey, + `dkim_pubkey` = :pubkey + WHERE `id` = :id + "); + $upd_data = array( + 'dkimid' => $domain['dkim_id'], + 'privkey' => $domain['dkim_privkey'], + 'pubkey' => $domain['dkim_pubkey'], + 'id' => $domain['id'] + ); + Database::pexecute($upd_stmt, $upd_data); + } + + if (! file_exists($privkey_filename) && $domain['dkim_privkey'] != '') { + $privkey_file_handler = fopen($privkey_filename, "w"); + fwrite($privkey_file_handler, $domain['dkim_privkey']); + fclose($privkey_file_handler); + safe_exec("chmod 0640 " . escapeshellarg($privkey_filename)); + } + + if (! file_exists($pubkey_filename) && $domain['dkim_pubkey'] != '') { + $pubkey_file_handler = fopen($pubkey_filename, "w"); + fwrite($pubkey_file_handler, $domain['dkim_pubkey']); + fclose($pubkey_file_handler); + safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename)); + } + + $dkimdomains .= $domain['domain'] . "\n"; + $dkimkeys .= "*@" . $domain['domain'] . ":" . $domain['domain'] . ":" . $privkey_filename . "\n"; + } + + $dkimdomains_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/' . Settings::Get('dkim.dkim_domains')); + $dkimdomains_file_handler = fopen($dkimdomains_filename, "w"); + fwrite($dkimdomains_file_handler, $dkimdomains); + fclose($dkimdomains_file_handler); + $dkimkeys_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/' . Settings::Get('dkim.dkim_dkimkeys')); + $dkimkeys_file_handler = fopen($dkimkeys_filename, "w"); + fwrite($dkimkeys_file_handler, $dkimkeys); + fclose($dkimkeys_file_handler); + + safe_exec(escapeshellcmd(Settings::Get('dkim.dkimrestart_command'))); + $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Dkim-milter reloaded'); + } + } + private function _generateDomainConfig($domain = array()) { - $this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns config for '.$domain['domain']); + $this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns config for ' . $domain['domain']); $bindconf_file = '# Domain ID: ' . $domain['id'] . ' - CustomerID: ' . $domain['customerid'] . ' - CustomerLogin: ' . $domain['loginname'] . "\n"; $bindconf_file .= 'zone "' . $domain['domain'] . '" in {' . "\n"; @@ -197,7 +277,7 @@ class bind2 { $config_dir = makeCorrectFile(Settings::Get('system.bindconf_directory') . '/domains/'); - $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Cleaning dns zone files from '.$config_dir); + $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Cleaning dns zone files from ' . $config_dir); // check directory if (@is_dir($config_dir)) { diff --git a/scripts/jobs/cron_tasks.php b/scripts/jobs/cron_tasks.php index 9a175892..99573e9b 100644 --- a/scripts/jobs/cron_tasks.php +++ b/scripts/jobs/cron_tasks.php @@ -187,7 +187,7 @@ while ($row = $result_tasks_stmt->fetch(PDO::FETCH_ASSOC)) { $nameserver = new $bindclass($cronlog); } - if (Settings::Get('dkim.use_dkim') == '1' && $bindclass == 'bind') { + if (Settings::Get('dkim.use_dkim') == '1') { $nameserver->writeDKIMconfigs(); } From bd9ef50e94bf8a02caff91762e06ead8e485a361 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 12 May 2016 09:47:36 +0200 Subject: [PATCH 13/43] correct SPF dns entry for new layout Signed-off-by: Michael Kaufmann (d00p) --- actions/admin/settings/185.spf.php | 2 +- install/froxlor.sql | 4 ++-- install/updates/froxlor/0.9/update_0.9.inc.php | 12 ++++++++++++ lib/version.inc.php | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/actions/admin/settings/185.spf.php b/actions/admin/settings/185.spf.php index c4d14cf5..55a44782 100644 --- a/actions/admin/settings/185.spf.php +++ b/actions/admin/settings/185.spf.php @@ -34,7 +34,7 @@ return array( 'settinggroup' => 'spf', 'varname' => 'spf_entry', 'type' => 'string', - 'default' => '@ IN TXT "v=spf1 a mx -all"', + 'default' => '"v=spf1 a mx -all"', 'save_method' => 'storeSettingField' ) ) diff --git a/install/froxlor.sql b/install/froxlor.sql index ad61eec2..8758e141 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -376,7 +376,7 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES ('admin', 'show_version_login', '0'), ('admin', 'show_version_footer', '0'), ('spf', 'use_spf', '0'), - ('spf', 'spf_entry', '@ IN TXT "v=spf1 a mx -all"'), + ('spf', 'spf_entry', '"v=spf1 a mx -all"'), ('dkim', 'dkim_algorithm', 'all'), ('dkim', 'dkim_add_adsp', '1'), ('dkim', 'dkim_keylength', '1024'), @@ -557,7 +557,7 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES ('panel', 'password_special_char_required', '0'), ('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'version', '0.9.35.1'), - ('panel', 'db_version', '201605090'); + ('panel', 'db_version', '201605120'); DROP TABLE IF EXISTS `panel_tasks`; diff --git a/install/updates/froxlor/0.9/update_0.9.inc.php b/install/updates/froxlor/0.9/update_0.9.inc.php index 4af29fef..ef9dbe27 100644 --- a/install/updates/froxlor/0.9/update_0.9.inc.php +++ b/install/updates/froxlor/0.9/update_0.9.inc.php @@ -3335,3 +3335,15 @@ if (isFroxlorVersion('0.9.35.1') && isDatabaseVersion('201604270')) { updateToDbVersion('201605090'); } + +if (isFroxlorVersion('0.9.35.1') && isDatabaseVersion('201605090')) { + + showUpdateStep("Adjusting SPF record setting"); + $current_spf = Settings::Get('spf.spf_entry'); + // @ IN TXT "v=spf1 a mx -all" + $new_spf = substr($current_spf, strpos($current_spf, '"')); + Settings::Set('spf.spf_entry', $new_spf, true); + lastStepStatus(0); + + updateToDbVersion('201605120'); +} diff --git a/lib/version.inc.php b/lib/version.inc.php index 5bc79f20..bc203f9b 100644 --- a/lib/version.inc.php +++ b/lib/version.inc.php @@ -19,7 +19,7 @@ $version = '0.9.35.1'; // Database version (YYYYMMDDC where C is a daily counter) -$dbversion = '201605090'; +$dbversion = '201605120'; // Distribution branding-tag (used for Debian etc.) $branding = ''; From cec5f33870f1290376ce69f7dcd828df66e1671e Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 12 May 2016 10:14:04 +0200 Subject: [PATCH 14/43] fix checking for existing SPF entry in DNS Signed-off-by: Michael Kaufmann (d00p) --- lib/functions/dns/function.createDomainZone.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php index a1202e1d..6d7b61bb 100644 --- a/lib/functions/dns/function.createDomainZone.php +++ b/lib/functions/dns/function.createDomainZone.php @@ -111,13 +111,11 @@ function createDomainZone($domain_id, $froxlorhostname = false) // now generate all records and unset the required entries we have foreach ($dom_entries as $entry) { if (array_key_exists($entry['type'], $required_entries) && array_key_exists(md5($entry['record']), $required_entries[$entry['type']])) { - // check for SPF entry if required - if (Settings::Get('spf.use_spf') == '1' && $entry['type'] == 'TXT' && $entry['record'] == '@' && strtolower(substr($entry['content'], 0, 7)) == '"v=spf1') { - // unset special spf required-entry - unset($required_entries[$entry['type']][md5("@SPF@")]); - } else { - unset($required_entries[$entry['type']][md5($entry['record'])]); - } + unset($required_entries[$entry['type']][md5($entry['record'])]); + } + if (Settings::Get('spf.use_spf') == '1' && $entry['type'] == 'TXT' && $entry['record'] == '@' && strtolower(substr($entry['content'], 0, 7)) == '"v=spf1') { + // unset special spf required-entry + unset($required_entries[$entry['type']][md5("@SPF@")]); } if (empty($primary_ns) && $entry['type'] == 'NS') { // use the first NS entry as primary ns From 689a1fdbd2c0af00258ab13eaddd3997f3965c97 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 12 May 2016 10:18:45 +0200 Subject: [PATCH 15/43] inform cronjob to regenerate bind-configs on changes Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dns_editor.php b/dns_editor.php index 06a68861..0a5747b5 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -223,6 +223,9 @@ if ($action == 'add_record' && ! empty($_POST)) { $prio = ""; $content = ""; $ttl = ""; + + // re-generate bind configs + inserttask('4'); } else { // show $errors $errors = implode("
", $errors); @@ -247,6 +250,9 @@ if ($action == 'add_record' && ! empty($_POST)) { unset($_t); // success message (inline) $success_message = $lng['success']['dns_record_deleted']; + + // re-generate bind configs + inserttask('4'); } } From 899663350d46a46fb355fbe2abc6e7c9539e46fa Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 12 May 2016 13:36:17 +0200 Subject: [PATCH 16/43] fix handling of DKIM multi-line entries; outsource some code to new DnsBase class Signed-off-by: Michael Kaufmann (d00p) --- .../dns/function.createDomainZone.php | 22 +- scripts/classes/class.DnsBase.php | 185 +++++++++++++++ scripts/jobs/cron_tasks.inc.dns.15.bind.php | 222 +++--------------- 3 files changed, 229 insertions(+), 200 deletions(-) create mode 100644 scripts/classes/class.DnsBase.php diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php index 6d7b61bb..51011107 100644 --- a/lib/functions/dns/function.createDomainZone.php +++ b/lib/functions/dns/function.createDomainZone.php @@ -228,7 +228,12 @@ function createDomainZone($domain_id, $froxlorhostname = false) $txt_content = Settings::Get('spf.spf_entry'); $zonefile .= formatEntry('@', 'TXT', encloseTXTContent($txt_content)); } elseif ($record == 'dkim_' . $domain['dkim_id'] . '._domainkey' && ! empty($dkim_entries)) { - $zonefile .= formatEntry($record, 'TXT', encloseTXTContent($dkim_entries[0])); + // check for multiline entry + $multiline = false; + if (substr($dkim_entries[0], 0, 1) == '(') { + $multiline = true; + } + $zonefile .= formatEntry($record, 'TXT', encloseTXTContent($dkim_entries[0], $multiline)); } elseif ($record == '_adsp._domainkey' && ! empty($dkim_entries) && isset($dkim_entries[1])) { $zonefile .= formatEntry($record, 'TXT', encloseTXTContent($dkim_entries[1])); } @@ -344,14 +349,17 @@ function generateDkimEntries($domain) return $zone_dkim; } -function encloseTXTContent($txt_content) +function encloseTXTContent($txt_content, $isMultiLine = false) { // check that TXT content is enclosed in " " - if (substr($txt_content, 0, 1) != '"') { - $txt_content = '"' . $txt_content; - } - if (substr($txt_content, - 1) != '"') { - $txt_content .= '"'; + if ($isMultiLine == false) + { + if (substr($txt_content, 0, 1) != '"') { + $txt_content = '"' . $txt_content; + } + if (substr($txt_content, - 1) != '"') { + $txt_content .= '"'; + } } return $txt_content; } diff --git a/scripts/classes/class.DnsBase.php b/scripts/classes/class.DnsBase.php new file mode 100644 index 00000000..3a2c138e --- /dev/null +++ b/scripts/classes/class.DnsBase.php @@ -0,0 +1,185 @@ +_logger = $logger; + + if (Settings::Get('system.nameservers') != '') { + $nameservers = explode(',', Settings::Get('system.nameservers')); + foreach ($nameservers as $nameserver) { + $nameserver = trim($nameserver); + // DNS servers might be multi homed; allow transfer from all ip + // addresses of the DNS server + $nameserver_ips = gethostbynamel($nameserver); + // append dot to hostname + if (substr($nameserver, - 1, 1) != '.') { + $nameserver .= '.'; + } + // ignore invalid responses + if (! is_array($nameserver_ips)) { + // act like gethostbyname() and return unmodified hostname on error + $nameserver_ips = array( + $nameserver + ); + } + $this->_ns[] = array( + 'hostname' => $nameserver, + 'ips' => $nameserver_ips + ); + } + } + + if (Settings::Get('system.mxservers') != '') { + $mxservers = explode(',', Settings::Get('system.mxservers')); + foreach ($mxservers as $mxserver) { + if (substr($mxserver, - 1, 1) != '.') { + $mxserver .= '.'; + } + $this->_mx[] = $mxserver; + } + } + + // AXFR server #100 + if (Settings::Get('system.axfrservers') != '') { + $axfrservers = explode(',', Settings::Get('system.axfrservers')); + foreach ($axfrservers as $axfrserver) { + $this->_axfr[] = trim($axfrserver); + } + } + } + + 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 + "); + + $domains = $result_domains_stmt->fetchAll(PDO::FETCH_ASSOC); + + // frolxor-hostname (#1090) + if (Settings::get('system.dns_createhostnameentry') == 1) { + $hostname_arr = array( + 'id' => 'none', + 'domain' => Settings::Get('system.hostname'), + 'isemaildomain' => Settings::Get('system.dns_createmailentry'), + 'customerid' => 'none', + 'loginname' => 'froxlor.panel', + 'bindserial' => date('Ymd') . '00', + 'dkim' => '0', + 'iswildcarddomain' => '1', + 'ismainbutsubto' => '0', + 'zonefile' => '', + 'froxlorhost' => '1' + ); + $domains['none'] = $hostname_arr; + } + + if (empty($domains)) { + $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, skipping...'); + return null; + } + + return $domains; + } + + public function writeDKIMconfigs() + { + if (Settings::Get('dkim.use_dkim') == '1') { + if (! file_exists(makeCorrectDir(Settings::Get('dkim.dkim_prefix')))) { + $this->_logger->logAction(CRON_ACTION, LOG_NOTICE, 'mkdir -p ' . escapeshellarg(makeCorrectDir(Settings::Get('dkim.dkim_prefix')))); + safe_exec('mkdir -p ' . escapeshellarg(makeCorrectDir(Settings::Get('dkim.dkim_prefix')))); + } + + $dkimdomains = ''; + $dkimkeys = ''; + $result_domains_stmt = Database::query(" + SELECT `id`, `domain`, `dkim`, `dkim_id`, `dkim_pubkey`, `dkim_privkey` + FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `dkim` = '1' ORDER BY `id` ASC + "); + + while ($domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) { + + $privkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id']); + $pubkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id'] . '.public'); + + if ($domain['dkim_privkey'] == '' || $domain['dkim_pubkey'] == '') { + $max_dkim_id_stmt = Database::query("SELECT MAX(`dkim_id`) as `max_dkim_id` FROM `" . TABLE_PANEL_DOMAINS . "`"); + $max_dkim_id = $max_dkim_id_stmt->fetch(PDO::FETCH_ASSOC); + $domain['dkim_id'] = (int) $max_dkim_id['max_dkim_id'] + 1; + $privkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id']); + safe_exec('openssl genrsa -out ' . escapeshellarg($privkey_filename) . ' ' . Settings::Get('dkim.dkim_keylength')); + $domain['dkim_privkey'] = file_get_contents($privkey_filename); + safe_exec("chmod 0640 " . escapeshellarg($privkey_filename)); + $pubkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id'] . '.public'); + safe_exec('openssl rsa -in ' . escapeshellarg($privkey_filename) . ' -pubout -outform pem -out ' . escapeshellarg($pubkey_filename)); + $domain['dkim_pubkey'] = file_get_contents($pubkey_filename); + safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename)); + $upd_stmt = Database::prepare(" + UPDATE `" . TABLE_PANEL_DOMAINS . "` SET + `dkim_id` = :dkimid, + `dkim_privkey` = :privkey, + `dkim_pubkey` = :pubkey + WHERE `id` = :id + "); + $upd_data = array( + 'dkimid' => $domain['dkim_id'], + 'privkey' => $domain['dkim_privkey'], + 'pubkey' => $domain['dkim_pubkey'], + 'id' => $domain['id'] + ); + Database::pexecute($upd_stmt, $upd_data); + } + + if (! file_exists($privkey_filename) && $domain['dkim_privkey'] != '') { + $privkey_file_handler = fopen($privkey_filename, "w"); + fwrite($privkey_file_handler, $domain['dkim_privkey']); + fclose($privkey_file_handler); + safe_exec("chmod 0640 " . escapeshellarg($privkey_filename)); + } + + if (! file_exists($pubkey_filename) && $domain['dkim_pubkey'] != '') { + $pubkey_file_handler = fopen($pubkey_filename, "w"); + fwrite($pubkey_file_handler, $domain['dkim_pubkey']); + fclose($pubkey_file_handler); + safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename)); + } + + $dkimdomains .= $domain['domain'] . "\n"; + $dkimkeys .= "*@" . $domain['domain'] . ":" . $domain['domain'] . ":" . $privkey_filename . "\n"; + } + + $dkimdomains_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/' . Settings::Get('dkim.dkim_domains')); + $dkimdomains_file_handler = fopen($dkimdomains_filename, "w"); + fwrite($dkimdomains_file_handler, $dkimdomains); + fclose($dkimdomains_file_handler); + $dkimkeys_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/' . Settings::Get('dkim.dkim_dkimkeys')); + $dkimkeys_file_handler = fopen($dkimkeys_filename, "w"); + fwrite($dkimkeys_file_handler, $dkimkeys); + fclose($dkimkeys_file_handler); + + safe_exec(escapeshellcmd(Settings::Get('dkim.dkimrestart_command'))); + $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Dkim-milter reloaded'); + } + } + +} \ No newline at end of file diff --git a/scripts/jobs/cron_tasks.inc.dns.15.bind.php b/scripts/jobs/cron_tasks.inc.dns.15.bind.php index 55347a2d..1e72166d 100644 --- a/scripts/jobs/cron_tasks.inc.dns.15.bind.php +++ b/scripts/jobs/cron_tasks.inc.dns.15.bind.php @@ -16,65 +16,9 @@ if (! defined('MASTER_CRONJOB')) * @package Cron * */ -class bind2 +class bind2 extends DnsBase { - private $_logger = false; - - private $_ns = array(); - - private $_mx = array(); - - private $_axfr = array(); - - public function __construct($logger) - { - $this->_logger = $logger; - - if (Settings::Get('system.nameservers') != '') { - $nameservers = explode(',', Settings::Get('system.nameservers')); - foreach ($nameservers as $nameserver) { - $nameserver = trim($nameserver); - // DNS servers might be multi homed; allow transfer from all ip - // addresses of the DNS server - $nameserver_ips = gethostbynamel($nameserver); - // append dot to hostname - if (substr($nameserver, - 1, 1) != '.') { - $nameserver .= '.'; - } - // ignore invalid responses - if (! is_array($nameserver_ips)) { - // act like gethostbyname() and return unmodified hostname on error - $nameserver_ips = array( - $nameserver - ); - } - $this->_ns[] = array( - 'hostname' => $nameserver, - 'ips' => $nameserver_ips - ); - } - } - - if (Settings::Get('system.mxservers') != '') { - $mxservers = explode(',', Settings::Get('system.mxservers')); - foreach ($mxservers as $mxserver) { - if (substr($mxserver, - 1, 1) != '.') { - $mxserver .= '.'; - } - $this->_mx[] = $mxserver; - } - } - - // AXFR server #100 - if (Settings::Get('system.axfrservers') != '') { - $axfrservers = explode(',', Settings::Get('system.axfrservers')); - foreach ($axfrservers as $axfrserver) { - $this->_axfr[] = trim($axfrserver); - } - } - } - public function writeConfigs() { // tell the world what we are doing @@ -89,148 +33,40 @@ class bind2 safe_exec('mkdir -p ' . escapeshellarg(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))); } - // 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 - "); + $domains = $this->getDomainList(); - $domains = $result_domains_stmt->fetchAll(PDO::FETCH_ASSOC); + 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"; - // frolxor-hostname (#1090) - if (Settings::get('system.dns_createhostnameentry') == 1) { - $hostname_arr = array( - 'id' => 'none', - 'domain' => Settings::Get('system.hostname'), - 'isemaildomain' => Settings::Get('system.dns_createmailentry'), - 'customerid' => 'none', - 'loginname' => 'froxlor.panel', - 'bindserial' => date('Ymd') . '00', - 'dkim' => '0', - 'iswildcarddomain' => '1', - 'ismainbutsubto' => '0', - 'zonefile' => '', - 'froxlorhost' => '1' - ); - $domains['none'] = $hostname_arr; - } + 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']); + $zonefile = createDomainZone($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, $zonefile); + fclose($zonefile_handler); + $this->_logger->logAction(CRON_ACTION, LOG_INFO, '`' . $zonefile_name . '` zone written'); - if (empty($domains)) { - $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, skipping...'); - return; - } - - $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"; - - 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']); - $zonefile = createDomainZone($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, $zonefile); - fclose($zonefile_handler); - $this->_logger->logAction(CRON_ACTION, LOG_INFO, '`' . $zonefile_name . '` zone written'); - - // generate config - $bindconf_file .= $this->_generateDomainConfig($domain); - } - - // 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'); - } - - public function writeDKIMconfigs() - { - if (Settings::Get('dkim.use_dkim') == '1') { - if (! file_exists(makeCorrectDir(Settings::Get('dkim.dkim_prefix')))) { - $this->_logger->logAction(CRON_ACTION, LOG_NOTICE, 'mkdir -p ' . escapeshellarg(makeCorrectDir(Settings::Get('dkim.dkim_prefix')))); - safe_exec('mkdir -p ' . escapeshellarg(makeCorrectDir(Settings::Get('dkim.dkim_prefix')))); + // generate config + $bindconf_file .= $this->_generateDomainConfig($domain); } - $dkimdomains = ''; - $dkimkeys = ''; - $result_domains_stmt = Database::query(" - SELECT `id`, `domain`, `dkim`, `dkim_id`, `dkim_pubkey`, `dkim_privkey` - FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `dkim` = '1' ORDER BY `id` ASC - "); + // 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'); - while ($domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) { - - $privkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id']); - $pubkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id'] . '.public'); - - if ($domain['dkim_privkey'] == '' || $domain['dkim_pubkey'] == '') { - $max_dkim_id_stmt = Database::query("SELECT MAX(`dkim_id`) as `max_dkim_id` FROM `" . TABLE_PANEL_DOMAINS . "`"); - $max_dkim_id = $max_dkim_id_stmt->fetch(PDO::FETCH_ASSOC); - $domain['dkim_id'] = (int) $max_dkim_id['max_dkim_id'] + 1; - $privkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id']); - safe_exec('openssl genrsa -out ' . escapeshellarg($privkey_filename) . ' ' . Settings::Get('dkim.dkim_keylength')); - $domain['dkim_privkey'] = file_get_contents($privkey_filename); - safe_exec("chmod 0640 " . escapeshellarg($privkey_filename)); - $pubkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id'] . '.public'); - safe_exec('openssl rsa -in ' . escapeshellarg($privkey_filename) . ' -pubout -outform pem -out ' . escapeshellarg($pubkey_filename)); - $domain['dkim_pubkey'] = file_get_contents($pubkey_filename); - safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename)); - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET - `dkim_id` = :dkimid, - `dkim_privkey` = :privkey, - `dkim_pubkey` = :pubkey - WHERE `id` = :id - "); - $upd_data = array( - 'dkimid' => $domain['dkim_id'], - 'privkey' => $domain['dkim_privkey'], - 'pubkey' => $domain['dkim_pubkey'], - 'id' => $domain['id'] - ); - Database::pexecute($upd_stmt, $upd_data); - } - - if (! file_exists($privkey_filename) && $domain['dkim_privkey'] != '') { - $privkey_file_handler = fopen($privkey_filename, "w"); - fwrite($privkey_file_handler, $domain['dkim_privkey']); - fclose($privkey_file_handler); - safe_exec("chmod 0640 " . escapeshellarg($privkey_filename)); - } - - if (! file_exists($pubkey_filename) && $domain['dkim_pubkey'] != '') { - $pubkey_file_handler = fopen($pubkey_filename, "w"); - fwrite($pubkey_file_handler, $domain['dkim_pubkey']); - fclose($pubkey_file_handler); - safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename)); - } - - $dkimdomains .= $domain['domain'] . "\n"; - $dkimkeys .= "*@" . $domain['domain'] . ":" . $domain['domain'] . ":" . $privkey_filename . "\n"; - } - - $dkimdomains_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/' . Settings::Get('dkim.dkim_domains')); - $dkimdomains_file_handler = fopen($dkimdomains_filename, "w"); - fwrite($dkimdomains_file_handler, $dkimdomains); - fclose($dkimdomains_file_handler); - $dkimkeys_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/' . Settings::Get('dkim.dkim_dkimkeys')); - $dkimkeys_file_handler = fopen($dkimkeys_filename, "w"); - fwrite($dkimkeys_file_handler, $dkimkeys); - fclose($dkimkeys_file_handler); - - safe_exec(escapeshellcmd(Settings::Get('dkim.dkimrestart_command'))); - $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Dkim-milter reloaded'); + // reload Bind + safe_exec(escapeshellcmd(Settings::Get('system.bindreload_command'))); + $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Bind9 reloaded'); } } From 4a4acc5c01b6f5decf731fb7c90d5fd58ff875cf Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 12 May 2016 14:32:41 +0200 Subject: [PATCH 17/43] fix constructor of DnsBase --- scripts/classes/class.DnsBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/classes/class.DnsBase.php b/scripts/classes/class.DnsBase.php index 3a2c138e..11e06996 100644 --- a/scripts/classes/class.DnsBase.php +++ b/scripts/classes/class.DnsBase.php @@ -17,7 +17,7 @@ abstract class DnsBase { abstract public function writeConfigs(); - protected function __construct($logger) + public function __construct($logger) { $this->_logger = $logger; From 11eb08e03105c7483a5d86c7bcd09fbebe12f89a Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 13 May 2016 13:27:33 +0200 Subject: [PATCH 18/43] add enabled-flag; enable dns-editor for customers; add german translations; few more fixes Signed-off-by: Michael Kaufmann (d00p) --- admin_domains.php | 2 +- customer_domains.php | 3 +++ install/froxlor.sql | 1 + .../preconfig/0.9/preconfig_0.9.inc.php | 8 ++++++++ lng/english.lng.php | 2 ++ lng/german.lng.php | 20 +++++++++++++++++++ scripts/jobs/cron_tasks.php | 6 +++++- .../Sparkle/admin/domains/domains_edit.tpl | 4 ++-- .../Sparkle/customer/domains/domains_edit.tpl | 3 +++ templates/Sparkle/dns_editor/index.tpl | 2 +- 10 files changed, 46 insertions(+), 5 deletions(-) diff --git a/admin_domains.php b/admin_domains.php index 7ecc3d5d..51de4672 100644 --- a/admin_domains.php +++ b/admin_domains.php @@ -2061,7 +2061,7 @@ elseif (Settings::Get('system.validate_domain') && ! validateDomain($domain)) { eval("echo \"" . getTemplate("domains/domains_import") . "\";"); } } -} elseif ($page == 'domaindnseditor') { +} elseif ($page == 'domaindnseditor' && Settings::Get('system.dnsenabled') == '1') { require_once __DIR__.'/dns_editor.php'; } diff --git a/customer_domains.php b/customer_domains.php index de18a20c..643866e6 100644 --- a/customer_domains.php +++ b/customer_domains.php @@ -904,4 +904,7 @@ if ($page == 'overview') { eval("echo \"" . getTemplate("domains/domain_ssleditor") . "\";"); } +} elseif ($page == 'domaindnseditor' && Settings::Get('system.dnsenabled') == '1') { + + require_once __DIR__.'/dns_editor.php'; } diff --git a/install/froxlor.sql b/install/froxlor.sql index 8758e141..b6462902 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -526,6 +526,7 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES ('system', 'letsencryptreuseold', 0), ('system', 'leenabled', '0'), ('system', 'backupenabled', '0'), + ('system', 'dnsenabled', '0'), ('panel', 'decimal_places', '4'), ('panel', 'adminmail', 'admin@SERVERNAME'), ('panel', 'phpmyadmin_url', ''), diff --git a/install/updates/preconfig/0.9/preconfig_0.9.inc.php b/install/updates/preconfig/0.9/preconfig_0.9.inc.php index 0c9d6559..5ebd6bf5 100644 --- a/install/updates/preconfig/0.9/preconfig_0.9.inc.php +++ b/install/updates/preconfig/0.9/preconfig_0.9.inc.php @@ -716,4 +716,12 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c $question.= makeyesno('enable_backup', '1', '0', '0').'
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } + + if (versionInUpdate($current_db_version, '201605090')) { + $has_preconfig = true; + $description = 'You can chose whether you want to enable or disable our DNS editor

'; + $question = 'Do you want to enable the DNS editor? (default: no): '; + $question.= makeyesno('enable_dns', '1', '0', '0').'
'; + eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); + } } diff --git a/lng/english.lng.php b/lng/english.lng.php index a42edaf9..98487720 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -2007,3 +2007,5 @@ $lng['error']['dns_srv_noalias'] = 'The SRV-target value cannot be an CNAME entr $lng['error']['dns_duplicate_entry'] = 'Record already exists'; $lng['success']['dns_record_added'] = 'Record added successfully'; $lng['success']['dns_record_deleted'] = 'Record deleted successfully'; +$lng['dnseditor']['edit'] = 'edit DNS'; +$lng['dnseditor']['records'] = 'records'; diff --git a/lng/german.lng.php b/lng/german.lng.php index 671ce6c1..0584a5b2 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1642,3 +1642,23 @@ $lng['serversettings']['backupenabled']['description'] = "Wenn dies aktiviert is $lng['extras']['path_protection_label'] = 'Wichtig'; $lng['extras']['path_protection_info'] = 'Wir raten dringend dazu den angegebenen Pfad zu schützen, siehe "Extras" -> "Verzeichnisschutz"'; $lng['tasks']['backup_customerfiles'] = 'Datensicherung für Kunde %loginname%'; + +$lng['error']['dns_domain_nodns'] = 'DNS ist für diese Domain nicht aktiviert'; +$lng['error']['dns_content_empty'] = 'Keinen Inhalt angegeben'; +$lng['error']['dns_arec_noipv4'] = 'Kein gültige IP Adresse für A-Eintrag angegeben'; +$lng['error']['dns_aaaarec_noipv6'] = 'Kein gültige IP Adresse für AAAA-Eintrag angegeben'; +$lng['error']['dns_mx_prioempty'] = 'Ungültige MX Priorität angegeben'; +$lng['error']['dns_mx_needdom'] = 'Der Wert des MX Eintrags muss ein gültiger Domainname sein'; +$lng['error']['dns_mx_noalias'] = 'Der MX Eintrag darf kein CNAME Eintrag sein.'; +$lng['error']['dns_cname_invaliddom'] = 'Ungültiger Domain-Name für CNAME Eintrag'; +$lng['error']['dns_cname_nomorerr'] = 'Es existiert bereits ein Eintrag mit dem gleichen Namen. Dieser Eintrag kann daher nicht für CNAME genutzt werden.'; +$lng['error']['dns_ns_invaliddom'] = 'Ungültiger Domain-Name für NS Eintrag'; +$lng['error']['dns_srv_prioempty'] = 'Ungültige SRV Priorität angegeben'; +$lng['error']['dns_srv_invalidcontent'] = 'Ungültiger Wert des SRV Eintrags, dieser muss aus den Feldern: weight, port und target, bestehen. Bsp.: 5 5060 sipserver.example.com.'; +$lng['error']['dns_srv_needdom'] = 'Der Wert des SRV Eintrags muss ein gültiger Domainname sein'; +$lng['error']['dns_srv_noalias'] = 'Der SRV Eintrag darf kein CNAME Eintrag sein..'; +$lng['error']['dns_duplicate_entry'] = 'Eintrag existiert bereits'; +$lng['success']['dns_record_added'] = 'Eintrag erfolgreich hinzugefügt'; +$lng['success']['dns_record_deleted'] = 'Eintrag erfolgreich entfernt'; +$lng['dnseditor']['edit'] = 'DNS editieren'; +$lng['dnseditor']['records'] = 'Einträge'; diff --git a/scripts/jobs/cron_tasks.php b/scripts/jobs/cron_tasks.php index 99573e9b..1bf460be 100644 --- a/scripts/jobs/cron_tasks.php +++ b/scripts/jobs/cron_tasks.php @@ -181,7 +181,11 @@ while ($row = $result_tasks_stmt->fetch(PDO::FETCH_ASSOC)) { */ elseif ($row['type'] == '4' && (int)Settings::Get('system.bind_enable') != 0) { - $bindclass ="bind2"; + $bindclass ="bind"; + + if (Settings::Get('system.dnsenabled') == '1') { + $bindclass = "bind2"; + } if (!isset($nameserver)) { $nameserver = new $bindclass($cronlog); diff --git a/templates/Sparkle/admin/domains/domains_edit.tpl b/templates/Sparkle/admin/domains/domains_edit.tpl index 275b0077..75f71bfd 100644 --- a/templates/Sparkle/admin/domains/domains_edit.tpl +++ b/templates/Sparkle/admin/domains/domains_edit.tpl @@ -4,8 +4,8 @@ $header

{$title}  {$title} - -  (edit DNS) + +  ({$lng['dnseditor']['edit']})

diff --git a/templates/Sparkle/customer/domains/domains_edit.tpl b/templates/Sparkle/customer/domains/domains_edit.tpl index 3979537e..d7a3c9ea 100644 --- a/templates/Sparkle/customer/domains/domains_edit.tpl +++ b/templates/Sparkle/customer/domains/domains_edit.tpl @@ -4,6 +4,9 @@ $header

{$title}  {$title} + +  ({$lng['dnseditor']['edit']}) +

diff --git a/templates/Sparkle/dns_editor/index.tpl b/templates/Sparkle/dns_editor/index.tpl index 737873cb..bc18f76f 100644 --- a/templates/Sparkle/dns_editor/index.tpl +++ b/templates/Sparkle/dns_editor/index.tpl @@ -3,7 +3,7 @@ $header

  - DNS Editor ({$domain}, {$entriescount} records) + DNS Editor ({$domain}, {$entriescount} {$lng['dnseditor']['records']})

From 86dc57c2ccd2c84c0c48f05343fcd5c827ca35b1 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 13 May 2016 19:40:37 +0200 Subject: [PATCH 19/43] outsource some dns functions to own files; allow opening of dns-editor only for domains that belong to the user (or the user has permission to edit as admin/reseller) Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 10 +-- .../dns/function.createDomainZone.php | 71 --------------- .../dns/function.generateDkimEntries.php | 87 +++++++++++++++++++ .../dns/function.getAllowedDomainEntry.php | 49 +++++++++++ lng/english.lng.php | 1 + lng/german.lng.php | 1 + 6 files changed, 139 insertions(+), 80 deletions(-) create mode 100644 lib/functions/dns/function.generateDkimEntries.php create mode 100644 lib/functions/dns/function.getAllowedDomainEntry.php diff --git a/dns_editor.php b/dns_editor.php index 0a5747b5..777eeeff 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -29,15 +29,7 @@ $content = isset($_POST['record']['content']) ? trim($_POST['record']['content'] $ttl = isset($_POST['record']['ttl']) ? (int) $_POST['record']['ttl'] : 18000; // get domain-name -$dom_stmt = Database::prepare("SELECT domain, isbinddomain FROM `" . TABLE_PANEL_DOMAINS . "` WHERE id = :did"); -$domain = Database::pexecute_first($dom_stmt, array( - 'did' => $domain_id -)); - -if ($domain['isbinddomain'] != '1') { - standard_error('dns_domain_nodns'); -} -$domain = $idna_convert->decode($domain['domain']); +$domain = getAllowedDomainEntry($domain_id, AREA, $userinfo, $idna_convert); // select all entries $sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_DOMAIN_DNS . "` WHERE domain_id = :did"); diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php index 51011107..e3d72a62 100644 --- a/lib/functions/dns/function.createDomainZone.php +++ b/lib/functions/dns/function.createDomainZone.php @@ -278,77 +278,6 @@ function addRequiredEntry($record = '@', $type = 'A', &$required) $required[$type][md5($record)] = $record; } -function generateDkimEntries($domain) -{ - $zone_dkim = array(); - - if (Settings::Get('dkim.use_dkim') == '1' && $domain['dkim'] == '1' && $domain['dkim_pubkey'] != '') { - // start - $dkim_txt = 'v=DKIM1;'; - - // algorithm - $algorithm = explode(',', Settings::Get('dkim.dkim_algorithm')); - $alg = ''; - foreach ($algorithm as $a) { - if ($a == 'all') { - break; - } else { - $alg .= $a . ':'; - } - } - - if ($alg != '') { - $alg = substr($alg, 0, - 1); - $dkim_txt .= 'h=' . $alg . ';'; - } - - // notes - if (trim(Settings::Get('dkim.dkim_notes') != '')) { - $dkim_txt .= 'n=' . trim(Settings::Get('dkim.dkim_notes')) . ';'; - } - - // key - $dkim_txt .= 'k=rsa;p=' . trim(preg_replace('/-----BEGIN PUBLIC KEY-----(.+)-----END PUBLIC KEY-----/s', '$1', str_replace("\n", '', $domain['dkim_pubkey']))) . ';'; - - // service-type - if (Settings::Get('dkim.dkim_servicetype') == '1') { - $dkim_txt .= 's=email;'; - } - - // end-part - $dkim_txt .= 't=s'; - - // split if necessary - $txt_record_split = ''; - $lbr = 50; - for ($pos = 0; $pos <= strlen($dkim_txt) - 1; $pos += $lbr) { - $txt_record_split .= (($pos == 0) ? '("' : "\t\t\t\t\t \"") . substr($dkim_txt, $pos, $lbr) . (($pos >= strlen($dkim_txt) - $lbr) ? '")' : '"') . "\n"; - } - - // dkim-entry - $zone_dkim[] = $txt_record_split; - - // adsp-entry - if (Settings::Get('dkim.dkim_add_adsp') == "1") { - $adsp = '"dkim='; - switch ((int) Settings::Get('dkim.dkim_add_adsppolicy')) { - case 0: - $adsp .= 'unknown"'; - break; - case 1: - $adsp .= 'all"'; - break; - case 2: - $adsp .= 'discardable"'; - break; - } - $zone_dkim[] = $adsp; - } - } - - return $zone_dkim; -} - function encloseTXTContent($txt_content, $isMultiLine = false) { // check that TXT content is enclosed in " " diff --git a/lib/functions/dns/function.generateDkimEntries.php b/lib/functions/dns/function.generateDkimEntries.php new file mode 100644 index 00000000..4b53d056 --- /dev/null +++ b/lib/functions/dns/function.generateDkimEntries.php @@ -0,0 +1,87 @@ + (2016-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Functions + * + */ + +function generateDkimEntries($domain) +{ + $zone_dkim = array(); + + if (Settings::Get('dkim.use_dkim') == '1' && $domain['dkim'] == '1' && $domain['dkim_pubkey'] != '') { + // start + $dkim_txt = 'v=DKIM1;'; + + // algorithm + $algorithm = explode(',', Settings::Get('dkim.dkim_algorithm')); + $alg = ''; + foreach ($algorithm as $a) { + if ($a == 'all') { + break; + } else { + $alg .= $a . ':'; + } + } + + if ($alg != '') { + $alg = substr($alg, 0, - 1); + $dkim_txt .= 'h=' . $alg . ';'; + } + + // notes + if (trim(Settings::Get('dkim.dkim_notes') != '')) { + $dkim_txt .= 'n=' . trim(Settings::Get('dkim.dkim_notes')) . ';'; + } + + // key + $dkim_txt .= 'k=rsa;p=' . trim(preg_replace('/-----BEGIN PUBLIC KEY-----(.+)-----END PUBLIC KEY-----/s', '$1', str_replace("\n", '', $domain['dkim_pubkey']))) . ';'; + + // service-type + if (Settings::Get('dkim.dkim_servicetype') == '1') { + $dkim_txt .= 's=email;'; + } + + // end-part + $dkim_txt .= 't=s'; + + // split if necessary + $txt_record_split = ''; + $lbr = 50; + for ($pos = 0; $pos <= strlen($dkim_txt) - 1; $pos += $lbr) { + $txt_record_split .= (($pos == 0) ? '("' : "\t\t\t\t\t \"") . substr($dkim_txt, $pos, $lbr) . (($pos >= strlen($dkim_txt) - $lbr) ? '")' : '"') . "\n"; + } + + // dkim-entry + $zone_dkim[] = $txt_record_split; + + // adsp-entry + if (Settings::Get('dkim.dkim_add_adsp') == "1") { + $adsp = '"dkim='; + switch ((int) Settings::Get('dkim.dkim_add_adsppolicy')) { + case 0: + $adsp .= 'unknown"'; + break; + case 1: + $adsp .= 'all"'; + break; + case 2: + $adsp .= 'discardable"'; + break; + } + $zone_dkim[] = $adsp; + } + } + + return $zone_dkim; +} \ No newline at end of file diff --git a/lib/functions/dns/function.getAllowedDomainEntry.php b/lib/functions/dns/function.getAllowedDomainEntry.php new file mode 100644 index 00000000..29ba2a55 --- /dev/null +++ b/lib/functions/dns/function.getAllowedDomainEntry.php @@ -0,0 +1,49 @@ + (2016-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Functions + * + */ + +function getAllowedDomainEntry($domain_id, $area = 'customer', $userinfo, &$idna_convert) +{ + $dom_data = array( + 'did' => $domain_id + ); + + $where_clause = ''; + if ($area == 'admin') { + if ($userinfo['domains_see_all'] != '1') { + $where_clause = '`adminid` = :uid'; + $dom_data['uid'] = $userinfo['userid']; + } + } else { + $where_clause = '`customerid` = :uid'; + $dom_data['uid'] = $userinfo['userid']; + } + + $dom_stmt = Database::prepare(" + SELECT domain, isbinddomain + FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE " . $where_clause . " AND id = :did + "); + $domain = Database::pexecute_first($dom_stmt, $dom_data); + + if ($domain) { + if ($domain['isbinddomain'] != '1') { + standard_error('dns_domain_nodns'); + } + return $idna_convert->decode($domain['domain']); + } + standard_error('dns_notfoundorallowed'); +} diff --git a/lng/english.lng.php b/lng/english.lng.php index 98487720..bbd3a7f1 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -2009,3 +2009,4 @@ $lng['success']['dns_record_added'] = 'Record added successfully'; $lng['success']['dns_record_deleted'] = 'Record deleted successfully'; $lng['dnseditor']['edit'] = 'edit DNS'; $lng['dnseditor']['records'] = 'records'; +$lng['error']['dns_notfoundorallowed'] = 'Domain not found or no permission'; diff --git a/lng/german.lng.php b/lng/german.lng.php index 0584a5b2..0079982f 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1662,3 +1662,4 @@ $lng['success']['dns_record_added'] = 'Eintrag erfolgreich hinzugefügt'; $lng['success']['dns_record_deleted'] = 'Eintrag erfolgreich entfernt'; $lng['dnseditor']['edit'] = 'DNS editieren'; $lng['dnseditor']['records'] = 'Einträge'; +$lng['error']['dns_notfoundorallowed'] = 'Domain nicht gefunden oder keine Berechtigung'; From 552c6e6cf9f76a2c9b9096ad321e0bba5eec6709 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 14 May 2016 08:32:38 +0200 Subject: [PATCH 20/43] add 'enable dns editor'-setting; fix missing isbinddomain index in customer_domain Signed-off-by: Michael Kaufmann (d00p) --- actions/admin/settings/160.nameserver.php | 8 ++++++++ customer_domains.php | 2 +- lng/english.lng.php | 2 ++ lng/german.lng.php | 2 ++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/actions/admin/settings/160.nameserver.php b/actions/admin/settings/160.nameserver.php index e738c3f3..030ceaeb 100644 --- a/actions/admin/settings/160.nameserver.php +++ b/actions/admin/settings/160.nameserver.php @@ -31,6 +31,14 @@ return array( 'save_method' => 'storeSettingField', 'overview_option' => true ), + 'system_dnsenabled' => array( + 'label' => $lng['serversettings']['dnseditorenable'], + 'settinggroup' => 'system', + 'varname' => 'dnsenabled', + 'type' => 'bool', + 'default' => false, + 'save_method' => 'storeSettingField' + ), 'system_bindconf_directory' => array( 'label' => $lng['serversettings']['bindconf_directory'], 'settinggroup' => 'system', diff --git a/customer_domains.php b/customer_domains.php index 643866e6..28f4833a 100644 --- a/customer_domains.php +++ b/customer_domains.php @@ -505,7 +505,7 @@ if ($page == 'overview') { } } elseif ($action == 'edit' && $id != 0) { - $stmt = Database::prepare("SELECT `d`.`id`, `d`.`customerid`, `d`.`domain`, `d`.`documentroot`, `d`.`isemaildomain`, `d`.`wwwserveralias`, `d`.`iswildcarddomain`, + $stmt = Database::prepare("SELECT `d`.`id`, `d`.`customerid`, `d`.`domain`, `d`.`documentroot`, `d`.`isemaildomain`, `d`.`isbinddomain`, `d`.`wwwserveralias`, `d`.`iswildcarddomain`, `d`.`parentdomainid`, `d`.`ssl_redirect`, `d`.`aliasdomain`, `d`.`openbasedir`, `d`.`openbasedir_path`, `d`.`letsencrypt`, `pd`.`subcanemaildomain` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_DOMAINS . "` `pd` WHERE `d`.`customerid` = :customerid diff --git a/lng/english.lng.php b/lng/english.lng.php index bbd3a7f1..47e8d9f3 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -2010,3 +2010,5 @@ $lng['success']['dns_record_deleted'] = 'Record deleted successfully'; $lng['dnseditor']['edit'] = 'edit DNS'; $lng['dnseditor']['records'] = 'records'; $lng['error']['dns_notfoundorallowed'] = 'Domain not found or no permission'; +$lng['serversettings']['dnseditorenable']['title'] = 'Enable DNS editor'; +$lng['serversettings']['dnseditorenable']['description'] = 'Allows admins and customer to manage domain dns entries'; diff --git a/lng/german.lng.php b/lng/german.lng.php index 0079982f..7f62b53b 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1663,3 +1663,5 @@ $lng['success']['dns_record_deleted'] = 'Eintrag erfolgreich entfernt'; $lng['dnseditor']['edit'] = 'DNS editieren'; $lng['dnseditor']['records'] = 'Einträge'; $lng['error']['dns_notfoundorallowed'] = 'Domain nicht gefunden oder keine Berechtigung'; +$lng['serversettings']['dnseditorenable']['title'] = 'DNS Editor aktivieren'; +$lng['serversettings']['dnseditorenable']['description'] = 'Erlaubt es Admins und Kunden die DNS Einträge Ihrer Domains zu verwalten'; From 02654a256d9a191b2af6302cdb2586d0d1d747f7 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 14 May 2016 08:59:46 +0200 Subject: [PATCH 21/43] fix sql query in getAllowedDomainEntry() Signed-off-by: Michael Kaufmann (d00p) --- lib/functions/dns/function.generateDkimEntries.php | 2 +- lib/functions/dns/function.getAllowedDomainEntry.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/functions/dns/function.generateDkimEntries.php b/lib/functions/dns/function.generateDkimEntries.php index 4b53d056..e3d5e95a 100644 --- a/lib/functions/dns/function.generateDkimEntries.php +++ b/lib/functions/dns/function.generateDkimEntries.php @@ -84,4 +84,4 @@ function generateDkimEntries($domain) } return $zone_dkim; -} \ No newline at end of file +} diff --git a/lib/functions/dns/function.getAllowedDomainEntry.php b/lib/functions/dns/function.getAllowedDomainEntry.php index 29ba2a55..8026859c 100644 --- a/lib/functions/dns/function.getAllowedDomainEntry.php +++ b/lib/functions/dns/function.getAllowedDomainEntry.php @@ -24,18 +24,18 @@ function getAllowedDomainEntry($domain_id, $area = 'customer', $userinfo, &$idna $where_clause = ''; if ($area == 'admin') { if ($userinfo['domains_see_all'] != '1') { - $where_clause = '`adminid` = :uid'; + $where_clause = '`adminid` = :uid AND '; $dom_data['uid'] = $userinfo['userid']; } } else { - $where_clause = '`customerid` = :uid'; + $where_clause = '`customerid` = :uid AND '; $dom_data['uid'] = $userinfo['userid']; } $dom_stmt = Database::prepare(" SELECT domain, isbinddomain FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE " . $where_clause . " AND id = :did + WHERE " . $where_clause . " id = :did "); $domain = Database::pexecute_first($dom_stmt, $dom_data); From e0e748a0bc98a3c675198bc6048c7564d9527697 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 15 May 2016 08:46:23 +0200 Subject: [PATCH 22/43] outsource record-generation and zone-generation to classes for better handling Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/dns/class.DnsEntry.php | 47 +++++++++++++++++++ lib/classes/dns/class.DnsZone.php | 44 +++++++++++++++++ .../dns/function.createDomainZone.php | 32 +++++-------- lng/english.lng.php | 1 + templates/Sparkle/dns_editor/index.tpl | 7 +++ 5 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 lib/classes/dns/class.DnsEntry.php create mode 100644 lib/classes/dns/class.DnsZone.php diff --git a/lib/classes/dns/class.DnsEntry.php b/lib/classes/dns/class.DnsEntry.php new file mode 100644 index 00000000..f1e36ded --- /dev/null +++ b/lib/classes/dns/class.DnsEntry.php @@ -0,0 +1,47 @@ + (2016-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Classes + * + */ +class DnsEntry +{ + + public $record; + + public $ttl; + + public $class = 'IN'; + + public $type; + + public $priority; + + public $content; + + public function __construct($record = '', $type = 'A', $content = null, $prio = 0, $ttl = 18000, $class = 'IN') + { + $this->record = $record; + $this->type = $type; + $this->content = $content; + $this->priority = $prio; + $this->ttl = $ttl; + $this->class = $class; + } + + public function __toString() + { + $result = $this->record . "\t" . $this->ttl . "\t" . $this->class . "\t" . $this->type . "\t" . (($this->prio >= 0 && ($this->type == 'MX' || $this->type == 'SRV')) ? $this->prio . "\t" : "") . $this->content . PHP_EOL; + return $result; + } +} diff --git a/lib/classes/dns/class.DnsZone.php b/lib/classes/dns/class.DnsZone.php new file mode 100644 index 00000000..b1b04eb5 --- /dev/null +++ b/lib/classes/dns/class.DnsZone.php @@ -0,0 +1,44 @@ + (2016-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Classes + * + */ +class DnsZone +{ + + public $ttl; + + public $origin; + + public $records; + + public function __construct($ttl = 18000, $origin = '', $records = null) + { + $this->ttl = $ttl; + $this->origin = $origin; + $this->records = $records; + } + + public function __toString() + { + $_zonefile = "\$TTL " . $this->ttl . PHP_EOL; + $_zonefile .= "\$ORIGIN " . $this->origin . "." . PHP_EOL; + if (! empty($this->records)) { + foreach ($this->records as $record) { + $_zonefile .= (string) $record; + } + } + return $_zonefile; + } +} diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php index e3d72a62..4b990127 100644 --- a/lib/functions/dns/function.createDomainZone.php +++ b/lib/functions/dns/function.createDomainZone.php @@ -106,7 +106,7 @@ function createDomainZone($domain_id, $froxlorhostname = false) } $primary_ns = null; - $zonefile = ""; + $zonerecords = array(); // now generate all records and unset the required entries we have foreach ($dom_entries as $entry) { @@ -121,7 +121,7 @@ function createDomainZone($domain_id, $froxlorhostname = false) // use the first NS entry as primary ns $primary_ns = $entry['content']; } - $zonefile .= formatEntry($entry['record'], $entry['type'], $entry['content'], $entry['prio'], $entry['ttl']); + $zonerecords[] = new DnsEntry($entry['record'], $entry['type'], $entry['content'], $entry['prio'], $entry['ttl']); } // add missing required entries @@ -151,9 +151,9 @@ function createDomainZone($domain_id, $froxlorhostname = false) foreach ($required_entries as $type => $records) { foreach ($records as $record) { if ($type == 'A' && filter_var($ip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) { - $zonefile .= formatEntry($record, 'A', $ip['ip']); + $zonerecords[] = new DnsEntry($record, 'A', $ip['ip']); } elseif ($type == 'AAAA' && filter_var($ip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) { - $zonefile .= formatEntry($record, 'AAAA', $ip['ip']); + $zonerecords[] = new DnsEntry($record, 'AAAA', $ip['ip']); } } } @@ -179,7 +179,7 @@ function createDomainZone($domain_id, $froxlorhostname = false) // use the first NS entry as primary ns $primary_ns = $nameserver; } - $zonefile .= formatEntry($record, 'NS', $nameserver); + $zonerecords[] = new DnsEntry($record, 'NS', $nameserver); } } } @@ -205,7 +205,7 @@ function createDomainZone($domain_id, $froxlorhostname = false) foreach ($required_entries as $type => $records) { if ($type == 'MX') { foreach ($records as $record) { - $zonefile .= formatEntry($record, 'MX', $mx_details[1], $mx_details[0]); + $zonerecords[] = new DnsEntry($record, 'MX', $mx_details[1], $mx_details[0]); } } } @@ -226,16 +226,16 @@ function createDomainZone($domain_id, $froxlorhostname = false) foreach ($records as $record) { if ($record == '@SPF@') { $txt_content = Settings::Get('spf.spf_entry'); - $zonefile .= formatEntry('@', 'TXT', encloseTXTContent($txt_content)); + $zonerecords[] = new DnsEntry('@', 'TXT', encloseTXTContent($txt_content)); } elseif ($record == 'dkim_' . $domain['dkim_id'] . '._domainkey' && ! empty($dkim_entries)) { // check for multiline entry $multiline = false; if (substr($dkim_entries[0], 0, 1) == '(') { $multiline = true; } - $zonefile .= formatEntry($record, 'TXT', encloseTXTContent($dkim_entries[0], $multiline)); + $zonerecords[] = new DnsEntry($record, 'TXT', encloseTXTContent($dkim_entries[0], $multiline)); } elseif ($record == '_adsp._domainkey' && ! empty($dkim_entries) && isset($dkim_entries[1])) { - $zonefile .= formatEntry($record, 'TXT', encloseTXTContent($dkim_entries[1])); + $zonerecords[] = new DnsEntry($record, 'TXT', encloseTXTContent($dkim_entries[1])); } } } @@ -256,18 +256,12 @@ function createDomainZone($domain_id, $froxlorhostname = false) $soa_content .= "604800\t; expire (7 days)" . PHP_EOL; $soa_content .= "1200\t)\t; minimum (20 mins)"; - $_zonefile = "\$TTL " . (int) Settings::Get('system.defaultttl') . PHP_EOL; - $_zonefile .= "\$ORIGIN " . $domain['domain'] . "." . PHP_EOL; - $_zonefile .= formatEntry('@', 'SOA', $soa_content); - $zonefile = $_zonefile . $zonefile; + $soa_record = new DnsEntry('@', 'SOA', $soa_content); + array_unshift($zonerecords, $soa_record); - return $zonefile; -} + $zone = new DnsZone((int) Settings::Get('system.defaultttl'), $domain['domain'], $zonerecords); -function formatEntry($record = '@', $type = 'A', $content = null, $prio = 0, $ttl = 18000, $class = 'IN') -{ - $result = $record . "\t" . $ttl . "\t" . $class . "\t" . $type . "\t" . (($prio >= 0 && ($type == 'MX' || $type == 'SRV')) ? $prio . "\t" : "") . $content . PHP_EOL; - return $result; + return (string)$zone; } function addRequiredEntry($record = '@', $type = 'A', &$required) diff --git a/lng/english.lng.php b/lng/english.lng.php index 47e8d9f3..43538921 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -2012,3 +2012,4 @@ $lng['dnseditor']['records'] = 'records'; $lng['error']['dns_notfoundorallowed'] = 'Domain not found or no permission'; $lng['serversettings']['dnseditorenable']['title'] = 'Enable DNS editor'; $lng['serversettings']['dnseditorenable']['description'] = 'Allows admins and customer to manage domain dns entries'; +$lng['dns']['howitworks'] = 'Here you can manage DNS entries for your domain. Note that froxlor will automatically generate NS/MX/A/AAAA records for you. The custom entries are prefered, only missing entries will be automatically generated.'; diff --git a/templates/Sparkle/dns_editor/index.tpl b/templates/Sparkle/dns_editor/index.tpl index bc18f76f..732c3e90 100644 --- a/templates/Sparkle/dns_editor/index.tpl +++ b/templates/Sparkle/dns_editor/index.tpl @@ -7,6 +7,13 @@ $header +
+
+
{$lng['admin']['warning']}
+
{$lng['dns']['howitworks']}
+
+
+
{$lng['error']['error']}
From 8d8da0986a1a1d69881d89b5a69fbba5bde22470 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 15 May 2016 08:53:47 +0200 Subject: [PATCH 23/43] fix typo in DnsEntry class Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/dns/class.DnsEntry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/dns/class.DnsEntry.php b/lib/classes/dns/class.DnsEntry.php index f1e36ded..c5bcab79 100644 --- a/lib/classes/dns/class.DnsEntry.php +++ b/lib/classes/dns/class.DnsEntry.php @@ -41,7 +41,7 @@ class DnsEntry public function __toString() { - $result = $this->record . "\t" . $this->ttl . "\t" . $this->class . "\t" . $this->type . "\t" . (($this->prio >= 0 && ($this->type == 'MX' || $this->type == 'SRV')) ? $this->prio . "\t" : "") . $this->content . PHP_EOL; + $result = $this->record . "\t" . $this->ttl . "\t" . $this->class . "\t" . $this->type . "\t" . (($this->priority >= 0 && ($this->type == 'MX' || $this->type == 'SRV')) ? $this->priority . "\t" : "") . $this->content . PHP_EOL; return $result; } } From 68fa0e6576c05eb0ac8d0c38feba05d07e989d60 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 15 May 2016 09:56:48 +0200 Subject: [PATCH 24/43] let createDomainZone() return the DnsZone object for better use later Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 3 ++- lib/classes/dns/class.DnsZone.php | 5 ++++- lib/functions/dns/function.createDomainZone.php | 4 ++-- scripts/jobs/cron_tasks.inc.dns.15.bind.php | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/dns_editor.php b/dns_editor.php index 777eeeff..bc9726a3 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -279,5 +279,6 @@ foreach ($type_select_values as $_type) { eval("\$record_list=\"" . getTemplate("dns_editor/list", true) . "\";"); -$zonefile = createDomainZone($domain_id); +$zone = createDomainZone($domain_id); +$zonefile = (string)$zone; eval("echo \"" . getTemplate("dns_editor/index", true) . "\";"); diff --git a/lib/classes/dns/class.DnsZone.php b/lib/classes/dns/class.DnsZone.php index b1b04eb5..9c4a8161 100644 --- a/lib/classes/dns/class.DnsZone.php +++ b/lib/classes/dns/class.DnsZone.php @@ -21,12 +21,15 @@ class DnsZone public $origin; + public $serial; + public $records; - public function __construct($ttl = 18000, $origin = '', $records = null) + public function __construct($ttl = 18000, $origin = '', $serial = '', $records = null) { $this->ttl = $ttl; $this->origin = $origin; + $this->serial = $serial; $this->records = $records; } diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php index 4b990127..ca1e71dd 100644 --- a/lib/functions/dns/function.createDomainZone.php +++ b/lib/functions/dns/function.createDomainZone.php @@ -259,9 +259,9 @@ function createDomainZone($domain_id, $froxlorhostname = false) $soa_record = new DnsEntry('@', 'SOA', $soa_content); array_unshift($zonerecords, $soa_record); - $zone = new DnsZone((int) Settings::Get('system.defaultttl'), $domain['domain'], $zonerecords); + $zone = new DnsZone((int) Settings::Get('system.defaultttl'), $domain['domain'], $domain['bindserial'], $zonerecords); - return (string)$zone; + return $zone; } function addRequiredEntry($record = '@', $type = 'A', &$required) diff --git a/scripts/jobs/cron_tasks.inc.dns.15.bind.php b/scripts/jobs/cron_tasks.inc.dns.15.bind.php index 1e72166d..4c368be2 100644 --- a/scripts/jobs/cron_tasks.inc.dns.15.bind.php +++ b/scripts/jobs/cron_tasks.inc.dns.15.bind.php @@ -46,7 +46,8 @@ class bind2 extends DnsBase } // create zone-file $this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns zone for ' . $domain['domain']); - $zonefile = createDomainZone($domain['id'], $isFroxlorHostname); + $zone = createDomainZone($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'); From 28115e6b1d40d195fcb748803aed595bcbc88ccd Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sun, 15 May 2016 10:08:42 +0200 Subject: [PATCH 25/43] add missing german language string Signed-off-by: Michael Kaufmann (d00p) --- lng/german.lng.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lng/german.lng.php b/lng/german.lng.php index 7f62b53b..70e483ad 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1665,3 +1665,4 @@ $lng['dnseditor']['records'] = 'Einträge'; $lng['error']['dns_notfoundorallowed'] = 'Domain nicht gefunden oder keine Berechtigung'; $lng['serversettings']['dnseditorenable']['title'] = 'DNS Editor aktivieren'; $lng['serversettings']['dnseditorenable']['description'] = 'Erlaubt es Admins und Kunden die DNS Einträge Ihrer Domains zu verwalten'; +$lng['dns']['howitworks'] = 'Hier können DNS Einträge für die Domain verwaltet werden. Beachte das Froxlor automatisch NS/MX/A/AAAA Einträge generiert. Die benutzerdefinierten Einträge werden bevorzugt, nur fehlende Einträge werden automatisch generiert.'; From 69443d95d5300373cb6fc49a437883b382a7cd38 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 17 May 2016 08:30:02 +0200 Subject: [PATCH 26/43] add support for power-dns (untested); most config-templates missing for pdns; create SPF/DKIM entries only if domain is emaildomain Signed-off-by: Michael Kaufmann (d00p) --- actions/admin/settings/160.nameserver.php | 10 + install/froxlor.sql | 3 +- .../updates/froxlor/0.9/update_0.9.inc.php | 14 +- .../preconfig/0.9/preconfig_0.9.inc.php | 515 +++++++------- lib/configfiles/gentoo.xml | 542 ++++++++++++++- .../dns/function.createDomainZone.php | 25 +- lib/version.inc.php | 2 +- lng/english.lng.php | 10 +- lng/german.lng.php | 10 +- scripts/jobs/cron_tasks.inc.dns.10.bind.php | 648 +++--------------- scripts/jobs/cron_tasks.inc.dns.15.bind.php | 134 ---- scripts/jobs/cron_tasks.inc.dns.20.pdns.php | 204 ++++++ scripts/jobs/cron_tasks.php | 8 +- 13 files changed, 1113 insertions(+), 1012 deletions(-) delete mode 100644 scripts/jobs/cron_tasks.inc.dns.15.bind.php create mode 100644 scripts/jobs/cron_tasks.inc.dns.20.pdns.php diff --git a/actions/admin/settings/160.nameserver.php b/actions/admin/settings/160.nameserver.php index 030ceaeb..11d2b6d3 100644 --- a/actions/admin/settings/160.nameserver.php +++ b/actions/admin/settings/160.nameserver.php @@ -39,6 +39,16 @@ return array( 'default' => false, 'save_method' => 'storeSettingField' ), + 'system_dns_server' => array( + 'label' => $lng['serversettings']['dns_server'], + 'settinggroup' => 'system', + 'varname' => 'dns_server', + 'type' => 'option', + 'default' => 'bind', + 'option_mode' => 'one', + 'option_options' => array('bind' => 'Bind9', 'pdns' => 'PowerDNS'), + 'save_method' => 'storeSettingField' + ), 'system_bindconf_directory' => array( 'label' => $lng['serversettings']['bindconf_directory'], 'settinggroup' => 'system', diff --git a/install/froxlor.sql b/install/froxlor.sql index b6462902..dc692f2f 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -527,6 +527,7 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES ('system', 'leenabled', '0'), ('system', 'backupenabled', '0'), ('system', 'dnsenabled', '0'), + ('system', 'dns_server', 'bind'), ('panel', 'decimal_places', '4'), ('panel', 'adminmail', 'admin@SERVERNAME'), ('panel', 'phpmyadmin_url', ''), @@ -558,7 +559,7 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES ('panel', 'password_special_char_required', '0'), ('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'version', '0.9.35.1'), - ('panel', 'db_version', '201605120'); + ('panel', 'db_version', '201605170'); DROP TABLE IF EXISTS `panel_tasks`; diff --git a/install/updates/froxlor/0.9/update_0.9.inc.php b/install/updates/froxlor/0.9/update_0.9.inc.php index ef9dbe27..d0e7b94c 100644 --- a/install/updates/froxlor/0.9/update_0.9.inc.php +++ b/install/updates/froxlor/0.9/update_0.9.inc.php @@ -3313,7 +3313,7 @@ if (isFroxlorVersion('0.9.35.1') && isDatabaseVersion('201603150')) { updateToDbVersion('201604270'); } -if (isFroxlorVersion('0.9.35.1') && isDatabaseVersion('201604270')) { +if (isDatabaseVersion('201604270')) { showUpdateStep("Adding new dns related tables and settings"); $enable_dns = isset($_POST['enable_dns']) ? (int) $_POST['enable_dns'] : "0"; @@ -3336,7 +3336,7 @@ if (isFroxlorVersion('0.9.35.1') && isDatabaseVersion('201604270')) { updateToDbVersion('201605090'); } -if (isFroxlorVersion('0.9.35.1') && isDatabaseVersion('201605090')) { +if (isDatabaseVersion('201605090')) { showUpdateStep("Adjusting SPF record setting"); $current_spf = Settings::Get('spf.spf_entry'); @@ -3347,3 +3347,13 @@ if (isFroxlorVersion('0.9.35.1') && isDatabaseVersion('201605090')) { updateToDbVersion('201605120'); } + +if (isDatabaseVersion('201605120')) { + + showUpdateStep("Adding new dns-server setting"); + $new_dns_daemon = isset($_POST['new_dns_daemon']) ? $_POST['new_dns_daemon'] : "bind"; + Settings::AddNew("system.dns_server", $new_dns_daemon); + lastStepStatus(0); + + updateToDbVersion('201605170'); +} diff --git a/install/updates/preconfig/0.9/preconfig_0.9.inc.php b/install/updates/preconfig/0.9/preconfig_0.9.inc.php index 5ebd6bf5..403d55d3 100644 --- a/install/updates/preconfig/0.9/preconfig_0.9.inc.php +++ b/install/updates/preconfig/0.9/preconfig_0.9.inc.php @@ -18,47 +18,45 @@ /** * checks if the new-version has some updating to do * - * @param boolean $has_preconfig pointer to check if any preconfig has to be output - * @param string $return pointer to output string - * @param string $current_version current froxlor version + * @param boolean $has_preconfig + * pointer to check if any preconfig has to be output + * @param string $return + * pointer to output string + * @param string $current_version + * current froxlor version * * @return null */ -function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $current_db_version) { - +function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $current_db_version) +{ global $lng; - if(versionInUpdate($current_version, '0.9.4-svn2')) - { + if (versionInUpdate($current_version, '0.9.4-svn2')) { $has_preconfig = true; $description = 'Froxlor now enables the usage of a domain-wildcard entry and subdomains for this domain at the same time (subdomains are parsed before the main-domain vhost container).'; - $description.= 'This makes it possible to catch all non-existing subdomains with the main vhost but also have the ability to use subdomains for that domain.
'; - $description.= 'If you would like Froxlor to do so with your domains, the update script can set the correct values for existing domains for you. Note: future domains will have wildcard-entries enabled by default no matter how you decide here.'; + $description .= 'This makes it possible to catch all non-existing subdomains with the main vhost but also have the ability to use subdomains for that domain.
'; + $description .= 'If you would like Froxlor to do so with your domains, the update script can set the correct values for existing domains for you. Note: future domains will have wildcard-entries enabled by default no matter how you decide here.'; $question = 'Do you want to use wildcard-entries for existing domains?: '; - $question.= makeyesno('update_domainwildcardentry', '1', '0', '1'); + $question .= makeyesno('update_domainwildcardentry', '1', '0', '1'); eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.6-svn2')) - { - if(!PHPMailer::ValidateAddress(Settings::Get('panel.adminmail'))) - { + if (versionInUpdate($current_version, '0.9.6-svn2')) { + if (! PHPMailer::ValidateAddress(Settings::Get('panel.adminmail'))) { $has_preconfig = true; $description = 'Froxlor uses a newer version of the phpMailerClass and determined that your current admin-mail address is invalid.'; - $question = 'Please specify a new admin-email address: '; + $question = 'Please specify a new admin-email address: '; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } } - if(versionInUpdate($current_version, '0.9.6-svn3')) - { + if (versionInUpdate($current_version, '0.9.6-svn3')) { $has_preconfig = true; $description = 'You now have the possibility to define default error-documents for your webserver which replace the default webserver error-messages.'; $question = 'Do you want to enable default error-documents?: '; - $question .= makeyesno('update_deferr_enable', '1', '0', '0').'

'; - if(Settings::Get('system.webserver') == 'apache2') - { + $question .= makeyesno('update_deferr_enable', '1', '0', '0') . '

'; + if (Settings::Get('system.webserver') == 'apache2') { $question .= 'Path/URL for error 500: 

'; $question .= 'Path/URL for error 401: 

'; $question .= 'Path/URL for error 403: 

'; @@ -67,37 +65,33 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.6-svn4')) - { + if (versionInUpdate($current_version, '0.9.6-svn4')) { $has_preconfig = true; $description = 'You can define a default support-ticket priority level which is pre-selected for new support-tickets.'; $question = 'Which should be the default ticket-priority?: '; $question .= ''; + $priorities .= makeoption($lng['ticket']['normal'], '2', '2'); + $priorities .= makeoption($lng['ticket']['low'], '3', '2'); + $question .= $priorities . ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.6-svn5')) - { + if (versionInUpdate($current_version, '0.9.6-svn5')) { $has_preconfig = true; $description = 'If you have more than one PHP configurations defined in Froxlor you can now set a default one which will be used for every domain.'; $question = 'Select default PHP configuration: '; $question .= ''; + $question .= $configs . ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.6-svn6')) - { + if (versionInUpdate($current_version, '0.9.6-svn6')) { $has_preconfig = true; $description = 'For the new FTP-quota feature, you can now chose the currently used ftpd-software.'; $question = 'Used FTPd-software: '; @@ -108,72 +102,63 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.7-svn1')) - { + if (versionInUpdate($current_version, '0.9.7-svn1')) { $has_preconfig = true; $description = 'You can now choose whether customers can select the http-redirect code and which of them acts as default.'; $question = 'Allow customer chosen redirects?: '; - $question.= makeyesno('update_customredirect_enable', '1', '0', '1').'

'; - $question.= 'Select default redirect code (default: empty): '; - $question.= ''; + $question .= makeyesno('update_customredirect_enable', '1', '0', '1') . '

'; + $question .= 'Select default redirect code (default: empty): '; + $question .= ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.7-svn2')) - { + if (versionInUpdate($current_version, '0.9.7-svn2')) { $result = Database::query("SELECT `domain` FROM " . TABLE_PANEL_DOMAINS . " WHERE `documentroot` LIKE '%:%' AND `documentroot` NOT LIKE 'http://%' AND `openbasedir_path` = '0' AND `openbasedir` = '1'"); $wrongOpenBasedirDomain = array(); - while($row = $result->fetch(PDO::FETCH_ASSOC)) { + while ($row = $result->fetch(PDO::FETCH_ASSOC)) { $wrongOpenBasedirDomain[] = $row['domain']; } - if(count($wrongOpenBasedirDomain) > 0) - { + if (count($wrongOpenBasedirDomain) > 0) { $has_preconfig = true; $description = 'Resetting the open_basedir to customer - root'; $question = 'Due to a security - issue regarding open_basedir, Froxlor will set the open_basedir for the following domains to the customers root instead of the chosen documentroot:
 '; - $question.= '
    '; + $question .= '
      '; $idna_convert = new idna_convert_wrapper(); - foreach($wrongOpenBasedirDomain as $domain) - { - $question.= '
    • ' . $idna_convert->decode($domain) . '
    • '; + foreach ($wrongOpenBasedirDomain as $domain) { + $question .= '
    • ' . $idna_convert->decode($domain) . '
    • '; } - $question.= '
    '; + $question .= '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } } - if(versionInUpdate($current_version, '0.9.9-svn1')) - { + if (versionInUpdate($current_version, '0.9.9-svn1')) { $has_preconfig = true; $description = 'When entering MX servers to Froxlor there was no mail-, imap-, pop3- and smtp-"A record" created. You can now chose whether this should be done or not.'; $question = 'Do you want these A-records to be created even with MX servers given?: '; - $question.= makeyesno('update_defdns_mailentry', '1', '0', '0'); + $question .= makeyesno('update_defdns_mailentry', '1', '0', '0'); eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.10-svn1')) - { + if (versionInUpdate($current_version, '0.9.10-svn1')) { $has_nouser = false; $has_nogroup = false; $result_stmt = Database::query("SELECT * FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'system' AND `varname` = 'httpuser'"); $result = $result_stmt->fetch(PDO::FETCH_ASSOC); - if(!isset($result) || !isset($result['value'])) - { + if (! isset($result) || ! isset($result['value'])) { $has_preconfig = true; $has_nouser = true; $guessed_user = 'www-data'; - if(function_exists('posix_getuid') - && function_exists('posix_getpwuid') - ) { + if (function_exists('posix_getuid') && function_exists('posix_getpwuid')) { $_httpuser = posix_getpwuid(posix_getuid()); $guessed_user = $_httpuser['name']; } @@ -182,31 +167,24 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c $result_stmt = Database::query("SELECT * FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'system' AND `varname` = 'httpgroup'"); $result = $result_stmt->fetch(PDO::FETCH_ASSOC); - if(!isset($result) || !isset($result['value'])) - { + if (! isset($result) || ! isset($result['value'])) { $has_preconfig = true; $has_nogroup = true; $guessed_group = 'www-data'; - if(function_exists('posix_getgid') - && function_exists('posix_getgrgid') - ) { + if (function_exists('posix_getgid') && function_exists('posix_getgrgid')) { $_httpgroup = posix_getgrgid(posix_getgid()); $guessed_group = $_httpgroup['name']; } } - if($has_nouser || $has_nogroup) - { + if ($has_nouser || $has_nogroup) { $description = 'Please enter the correct username/groupname of the webserver on your system We\'re guessing the user but it might not be correct, so please check.'; - if($has_nouser) - { - $question = 'Please enter the webservers username: '; - } - elseif($has_nogroup) - { - $question2 = 'Please enter the webservers groupname: '; - if($has_nouser) { - $question .= '

'.$question2; + if ($has_nouser) { + $question = 'Please enter the webservers username: '; + } elseif ($has_nogroup) { + $question2 = 'Please enter the webservers groupname: '; + if ($has_nouser) { + $question .= '

' . $question2; } else { $question = $question2; } @@ -215,227 +193,202 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c } } - if(versionInUpdate($current_version, '0.9.10')) - { + if (versionInUpdate($current_version, '0.9.10')) { $has_preconfig = true; $description = 'you can now decide whether Froxlor should be reached via hostname/froxlor or directly via the hostname.'; $question = 'Do you want Froxlor to be reached directly via the hostname?: '; - $question.= makeyesno('update_directlyviahostname', '1', '0', '0'); + $question .= makeyesno('update_directlyviahostname', '1', '0', '0'); eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.11-svn1')) - { + if (versionInUpdate($current_version, '0.9.11-svn1')) { $has_preconfig = true; $description = 'It is possible to enhance security with setting a regular expression to force your customers to enter more complex passwords.'; $question = 'Enter a regular expression to force a higher password complexity (leave empty for none): '; - $question.= ''; + $question .= ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.11-svn3')) - { + if (versionInUpdate($current_version, '0.9.11-svn3')) { $has_preconfig = true; $description = 'As Froxlor can now handle perl, you have to specify where the perl executable is (only if you\'re running lighttpd, else just leave empty).'; $question = 'Path to perl (default \'/usr/bin/perl\'): '; - $question.= ''; + $question .= ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.12-svn1')) - { - if(Settings::Get('system.mod_fcgid') == 1) - { + if (versionInUpdate($current_version, '0.9.12-svn1')) { + if (Settings::Get('system.mod_fcgid') == 1) { $has_preconfig = true; $description = 'You can chose whether you want Froxlor to use FCGID itself too now.'; $question = 'Use FCGID for the Froxlor Panel?: '; - $question.= makeyesno('update_fcgid_ownvhost', '1', '0', '0').'

'; - $question.= 'If \'yes\', please specify local user/group (have to exist, Froxlor does not add them automatically):

'; - $question.= 'Local user: '; - $question.= '

'; - $question.= 'Local group: '; - $question.= '
'; + $question .= makeyesno('update_fcgid_ownvhost', '1', '0', '0') . '

'; + $question .= 'If \'yes\', please specify local user/group (have to exist, Froxlor does not add them automatically):

'; + $question .= 'Local user: '; + $question .= '

'; + $question .= 'Local group: '; + $question .= '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } } - if(versionInUpdate($current_version, '0.9.12-svn2')) - { + if (versionInUpdate($current_version, '0.9.12-svn2')) { $has_preconfig = true; $description = 'Many apache user will have problems using perl/CGI as the customer docroots are not within the suexec path. Froxlor provides a simple workaround for that.'; $question = 'Enable Apache/SuExec/Perl workaround?: '; - $question.= makeyesno('update_perl_suexecworkaround', '1', '0', '0').'

'; - $question.= 'If \'yes\', please specify a path within the suexec path where Froxlor will create symlinks to customer perl-enabled paths:

'; - $question.= 'Path for symlinks (must be within suexec path): '; - $question.= '
'; + $question .= makeyesno('update_perl_suexecworkaround', '1', '0', '0') . '

'; + $question .= 'If \'yes\', please specify a path within the suexec path where Froxlor will create symlinks to customer perl-enabled paths:

'; + $question .= 'Path for symlinks (must be within suexec path): '; + $question .= '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.12-svn4')) - { - if((int)Settings::Get('system.awstats_enabled') == 1) - { + if (versionInUpdate($current_version, '0.9.12-svn4')) { + if ((int) Settings::Get('system.awstats_enabled') == 1) { $has_preconfig = true; $description = 'Due to different paths of awstats_buildstaticpages.pl and awstats.pl you can set a different path for awstats.pl now.'; $question = 'Path to \'awstats.pl\'?: '; - $question.= '
'; + $question .= '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } } - if(versionInUpdate($current_version, '0.9.13-svn1')) - { - if((int)Settings::Get('autoresponder.autoresponder_active') == 1) - { + if (versionInUpdate($current_version, '0.9.13-svn1')) { + if ((int) Settings::Get('autoresponder.autoresponder_active') == 1) { $has_preconfig = true; $description = 'Froxlor can now limit the number of autoresponder-entries for each user. Here you can set the value which will be available for each customer (Of course you can change the value for each customer separately after the update).'; $question = 'How many autoresponders should your customers be able to add?: '; - $question.= ' '.makecheckbox('update_autoresponder_default', $lng['customer']['unlimited'], '-1', false, 0, true, true).'
'; + $question .= ' ' . makecheckbox('update_autoresponder_default', $lng['customer']['unlimited'], '-1', false, 0, true, true) . '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } } - if(versionInUpdate($current_version, '0.9.13.1')) - { - if((int)Settings::Get('system.mod_fcgid_ownvhost') == 1) - { + if (versionInUpdate($current_version, '0.9.13.1')) { + if ((int) Settings::Get('system.mod_fcgid_ownvhost') == 1) { $has_preconfig = true; $description = 'You have FCGID for Froxlor itself activated. You can now specify a PHP-configuration for this.'; $question = 'Select Froxlor-vhost PHP configuration: '; $question .= ''; + $question .= $configs . ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } } - if(versionInUpdate($current_version, '0.9.14-svn3')) - { - if((int)Settings::Get('system.awstats_enabled') == 1) - { + if (versionInUpdate($current_version, '0.9.14-svn3')) { + if ((int) Settings::Get('system.awstats_enabled') == 1) { $has_preconfig = true; $description = 'To have icons in AWStats statistic-pages please enter the path to AWStats icons folder.'; $question = 'Path to AWSTats icons folder: '; - $question.= ''; + $question .= ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } } - if(versionInUpdate($current_version, '0.9.14-svn4')) - { - if((int)Settings::Get('system.use_ssl') == 1) - { + if (versionInUpdate($current_version, '0.9.14-svn4')) { + if ((int) Settings::Get('system.use_ssl') == 1) { $has_preconfig = true; $description = 'Froxlor now has the possibility to set \'SSLCertificateChainFile\' for the apache webserver.'; $question = 'Enter filename (leave empty for none): '; - $question.= ''; + $question .= ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } } - if(versionInUpdate($current_version, '0.9.14-svn6')) - { + if (versionInUpdate($current_version, '0.9.14-svn6')) { $has_preconfig = true; $description = 'You can now allow customers to use any of their domains as username for the login.'; $question = 'Do you want to enable domain-login for all customers?: '; - $question.= makeyesno('update_allow_domain_login', '1', '0', '0'); + $question .= makeyesno('update_allow_domain_login', '1', '0', '0'); eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.14-svn10')) - { + if (versionInUpdate($current_version, '0.9.14-svn10')) { $has_preconfig = true; $description = 'This update removes the unsupported real-time option. Additionally the deprecated tables for navigation and cronscripts are removed, any modules using these tables need to be updated to the new structure!'; $question = ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.16-svn1')) - { + if (versionInUpdate($current_version, '0.9.16-svn1')) { $has_preconfig = true; $description = 'Froxlor now features support for php-fpm.'; $question = 'Do you want to enable php-fpm?: '; - $question.= makeyesno('update_phpfpm_enabled', '1', '0', '0').'

'; - $question.= 'If \'yes\', please specify the configuration directory: '; - $question.= '

'; - $question.= 'Please specify the temporary files directory: '; - $question.= '

'; - $question.= 'Please specify the PEAR directory: '; - $question.= '

'; - $question.= 'Please specify the php-fpm restart-command: '; - $question.= '

'; - $question.= 'Please specify the php-fpm rocess manager control: '; - $question.= '

'; + $question .= 'Please specify the temporary files directory: '; + $question .= '

'; + $question .= 'Please specify the PEAR directory: '; + $question .= '

'; + $question .= 'Please specify the php-fpm restart-command: '; + $question .= '

'; + $question .= 'Please specify the php-fpm rocess manager control: '; + $question .= '

'; - $question.= 'Please specify the number of child processes: '; - $question.= '

'; - $question.= 'Please specify the number of requests per child before respawning: '; - $question.= '

'; - $question.= 'The following settings are only required if you chose process manager = dynamic

'; - $question.= 'Please specify the number of child processes created on startup: '; - $question.= '

'; - $question.= 'Please specify the desired minimum number of idle server processes: '; - $question.= '

'; - $question.= 'Please specify the desired maximum number of idle server processes: '; - $question.= '
'; + $redirects .= makeoption('dynamic', 'dynamic', 'static'); + $question .= $redirects . '

'; + $question .= 'Please specify the number of child processes: '; + $question .= '

'; + $question .= 'Please specify the number of requests per child before respawning: '; + $question .= '

'; + $question .= 'The following settings are only required if you chose process manager = dynamic

'; + $question .= 'Please specify the number of child processes created on startup: '; + $question .= '

'; + $question .= 'Please specify the desired minimum number of idle server processes: '; + $question .= '

'; + $question .= 'Please specify the desired maximum number of idle server processes: '; + $question .= '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.16-svn2')) - { - if((int)Settings::Get('phpfpm.enabled') == 1) - { + if (versionInUpdate($current_version, '0.9.16-svn2')) { + if ((int) Settings::Get('phpfpm.enabled') == 1) { $has_preconfig = true; $description = 'You can chose whether you want Froxlor to use PHP-FPM itself too now.'; $question = 'Use PHP-FPM for the Froxlor Panel?: '; - $question.= makeyesno('update_phpfpm_enabled_ownvhost', '1', '0', '0').'

'; - $question.= 'If \'yes\', please specify local user/group (have to exist, Froxlor does not add them automatically):

'; - $question.= 'Local user: '; - $question.= '

'; - $question.= 'Local group: '; - $question.= '
'; + $question .= makeyesno('update_phpfpm_enabled_ownvhost', '1', '0', '0') . '

'; + $question .= 'If \'yes\', please specify local user/group (have to exist, Froxlor does not add them automatically):

'; + $question .= 'Local user: '; + $question .= '

'; + $question .= 'Local group: '; + $question .= '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } } - if(versionInUpdate($current_version, '0.9.17-svn1')) - { + if (versionInUpdate($current_version, '0.9.17-svn1')) { $has_preconfig = true; $description = 'Select if you want to enable the web- and traffic-reports'; $question = 'Enable?: '; - $question.= makeyesno('update_system_report_enable', '1', '0', '1').'

'; - $question.= 'If \'yes\', please specify a percentage value for web- and traffic when reports are to be sent:

'; - $question.= 'Webusage warning level: '; - $question.= '

'; - $question.= 'Traffic warning level: '; - $question.= '
'; + $question .= makeyesno('update_system_report_enable', '1', '0', '1') . '

'; + $question .= 'If \'yes\', please specify a percentage value for web- and traffic when reports are to be sent:

'; + $question .= 'Webusage warning level: '; + $question .= '

'; + $question .= 'Traffic warning level: '; + $question .= '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.18-svn2')) - { + if (versionInUpdate($current_version, '0.9.18-svn2')) { $has_preconfig = true; $description = 'As you can (obviously) see, Froxlor now comes with a new theme. You also have the possibility to switch back to "Classic" if you want to.'; $question = 'Select default panel theme: '; - $question.= ''; $themes = getThemes(); - foreach($themes as $cur_theme) // $theme is already in use - { - $question.= makeoption($cur_theme, $cur_theme, 'Froxlor'); + foreach ($themes as $cur_theme) // $theme is already in use +{ + $question .= makeoption($cur_theme, $cur_theme, 'Froxlor'); } - $question.= ''; + $question .= ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } - if(versionInUpdate($current_version, '0.9.28-svn4')) - { + if (versionInUpdate($current_version, '0.9.28-svn4')) { $has_preconfig = true; $description = 'This version introduces a lot of profound changes:'; $description .= '
  • Improving the whole template system
  • Full UTF-8 support
  • Removing support for the former default theme \'Classic\'
'; @@ -444,13 +397,12 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c $description .= 'test this update in a testing environment using your existing data.

'; $question = 'Select your preferred Classic Theme replacement: '; - $question.= ''; $themes = getThemes(); - foreach($themes as $cur_theme) - { - $question.= makeoption($cur_theme, $cur_theme, 'Froxlor'); + foreach ($themes as $cur_theme) { + $question .= makeoption($cur_theme, $cur_theme, 'Froxlor'); } - $question.= ''; + $question .= ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } @@ -460,16 +412,16 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c if (Settings::Get('system.webserver') == 'apache2') { $has_preconfig = true; $description = 'Froxlor now supports the new Apache 2.4. Please be aware that you need to load additional apache-modules in ordner to use it.
'; - $description.= '
LoadModule authz_core_module modules/mod_authz_core.so
+			$description .= '
LoadModule authz_core_module modules/mod_authz_core.so
 					LoadModule authz_host_module modules/mod_authz_host.so

'; $question = 'Do you want to enable the Apache-2.4 modification?: '; - $question.= makeyesno('update_system_apache24', '1', '0', '0'); + $question .= makeyesno('update_system_apache24', '1', '0', '0'); eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } elseif (Settings::Get('system.webserver') == 'nginx') { $has_preconfig = true; $description = 'The path to nginx\'s fastcgi_params file is now customizable.

'; $question = 'Please enter full path to you nginx/fastcgi_params file (including filename): '; - $question.= ''; + $question .= ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } } @@ -478,11 +430,11 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c $has_preconfig = true; - $description = 'This version adds an option to append the domain-name to the document-root for domains and subdomains.
'; + $description = 'This version adds an option to append the domain-name to the document-root for domains and subdomains.
'; $description .= 'You can enable or disable this feature anytime from settings -> system settings.
'; $question = 'Do you want to automatically append the domain-name to the documentroot of newly created domains?: '; - $question.= makeyesno('update_system_documentroot_use_default_value', '1', '0', '0'); + $question .= makeyesno('update_system_documentroot_use_default_value', '1', '0', '0'); eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } @@ -491,12 +443,10 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c $has_preconfig = true; // just an information about the new sendmail parameter (#1134) - $description = 'Froxlor changed the default parameter-set of sendmail (php.ini)
'; + $description = 'Froxlor changed the default parameter-set of sendmail (php.ini)
'; $description .= 'sendmail_path = "/usr/sbin/sendmail -t -i -f {CUSTOMER_EMAIL}"

'; $description .= 'If you don\'t have any problems with sending mails, you don\'t need to change this'; - if (Settings::Get('system.mod_fcgid') == '1' - || Settings::Get('phpfpm.enabled') == '1' - ) { + if (Settings::Get('system.mod_fcgid') == '1' || Settings::Get('phpfpm.enabled') == '1') { // information about removal of php's safe_mode $description .= '

The php safe_mode flag has been removed as current versions of PHP
'; $description .= 'do not support it anymore.

'; @@ -509,45 +459,43 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c if (versionInUpdate($current_version, '0.9.29-dev1')) { // we only need to ask if fcgid|php-fpm is enabled - if (Settings::Get('system.mod_fcgid') == '1' - || Settings::Get('phpfpm.enabled') == '1' - ) { + if (Settings::Get('system.mod_fcgid') == '1' || Settings::Get('phpfpm.enabled') == '1') { $has_preconfig = true; - $description = 'Standard-subdomains can now be hidden from the php-configuration overview.
'; + $description = 'Standard-subdomains can now be hidden from the php-configuration overview.
'; $question = 'Do you want to hide the standard-subdomains (this can be changed in the settings any time)?: '; - $question.= makeyesno('hide_stdsubdomains', '1', '0', '0'); + $question .= makeyesno('hide_stdsubdomains', '1', '0', '0'); eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } } if (versionInUpdate($current_version, '0.9.29-dev2')) { $has_preconfig = true; - $description = 'You can now decide whether admins/customers are able to change the theme
'; + $description = 'You can now decide whether admins/customers are able to change the theme
'; $question = 'If you want to disallow theme-changing, select "no" from the dropdowns: '; - $question.= "Admins: ". makeyesno('allow_themechange_a', '1', '0', '1').'  '; - $question.= "Customers: ".makeyesno('allow_themechange_c', '1', '0', '1'); + $question .= "Admins: " . makeyesno('allow_themechange_a', '1', '0', '1') . '  '; + $question .= "Customers: " . makeyesno('allow_themechange_c', '1', '0', '1'); eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } if (versionInUpdate($current_version, '0.9.29-dev3')) { $has_preconfig = true; - $description = 'There is now a possibility to specify AXFR servers for your bind zone-configuration
'; + $description = 'There is now a possibility to specify AXFR servers for your bind zone-configuration
'; $question = 'Enter a comma-separated list of AXFR servers or leave empty (default): '; - $question.= ''; + $question .= ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } if (versionInUpdate($current_version, '0.9.29-dev4')) { $has_preconfig = true; - $description = 'As customers can now specify ssl-certificate data for their domains, you need to specify where the generated files are stored
'; + $description = 'As customers can now specify ssl-certificate data for their domains, you need to specify where the generated files are stored
'; $question = 'Specify the directory for customer ssl-certificates: '; - $question.= ''; + $question .= ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } if (versionInUpdate($current_version, '0.9.29.1-dev3')) { $has_preconfig = true; - $description = 'The build in logrotation-feature has been removed. Please follow the configuration-instructions for your system to enable logrotating again.'; + $description = 'The build in logrotation-feature has been removed. Please follow the configuration-instructions for your system to enable logrotating again.'; $question = ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } @@ -555,11 +503,9 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c // let the apache+fpm users know that they MUST change their config // for the domains / webserver to work after the update if (versionInUpdate($current_version, '0.9.30-dev1')) { - if (Settings::Get('system.webserver') == 'apache2' - && Settings::Get('phpfpm.enabled') == '1' - ) { + if (Settings::Get('system.webserver') == 'apache2' && Settings::Get('phpfpm.enabled') == '1') { $has_preconfig = true; - $description = 'The PHP-FPM implementation for apache2 has changed. Please look for the "fastcgi.conf" (Debian/Ubuntu) or "70_fastcgi.conf" (Gentoo) within /etc/apache2/ and change it as shown below:

'; + $description = 'The PHP-FPM implementation for apache2 has changed. Please look for the "fastcgi.conf" (Debian/Ubuntu) or "70_fastcgi.conf" (Gentoo) within /etc/apache2/ and change it as shown below:

'; $description .= '
<IfModule mod_fastcgi.c>
     FastCgiIpcDir /var/lib/apache2/fastcgi/
     <Location "/fastcgiphp">
@@ -575,11 +521,9 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c
 	}
 
 	if (versionInUpdate($current_version, '0.9.31-dev2')) {
-		if (Settings::Get('system.webserver') == 'apache2'
-				&& Settings::Get('phpfpm.enabled') == '1'
-		) {
+		if (Settings::Get('system.webserver') == 'apache2' && Settings::Get('phpfpm.enabled') == '1') {
 			$has_preconfig = true;
-			$description  = 'The FPM socket directory is now a setting in froxlor. Its default is /var/lib/apache2/fastcgi/.
If you are using /var/run/apache2 in the "fastcgi.conf" (Debian/Ubuntu) or "70_fastcgi.conf" (Gentoo) please correct this path accordingly
'; + $description = 'The FPM socket directory is now a setting in froxlor. Its default is /var/lib/apache2/fastcgi/.
If you are using /var/run/apache2 in the "fastcgi.conf" (Debian/Ubuntu) or "70_fastcgi.conf" (Gentoo) please correct this path accordingly
'; $question = ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } @@ -587,50 +531,50 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c if (versionInUpdate($current_version, '0.9.31-dev4')) { $has_preconfig = true; - $description = 'The template-variable {PASSWORD} has been replaced with {LINK}. Please update your password reset templates!
'; + $description = 'The template-variable {PASSWORD} has been replaced with {LINK}. Please update your password reset templates!
'; $question = ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } if (versionInUpdate($current_version, '0.9.31-dev5')) { $has_preconfig = true; - $description = 'You can enable/disable error-reporting for admins and customers!

'; + $description = 'You can enable/disable error-reporting for admins and customers!

'; $question = 'Do you want to enable error-reporting for admins? (default: yes): '; - $question.= makeyesno('update_error_report_admin', '1', '0', '1').'
'; - $question.= 'Do you want to enable error-reporting for customers? (default: no): '; - $question.= makeyesno('update_error_report_customer', '1', '0', '0'); + $question .= makeyesno('update_error_report_admin', '1', '0', '1') . '
'; + $question .= 'Do you want to enable error-reporting for customers? (default: no): '; + $question .= makeyesno('update_error_report_customer', '1', '0', '0'); eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } if (versionInUpdate($current_version, '0.9.31-rc2')) { $has_preconfig = true; - $description = 'You can enable/disable the display/usage of the news-feed for admins

'; + $description = 'You can enable/disable the display/usage of the news-feed for admins

'; $question = 'Do you want to enable the news-feed for admins? (default: yes): '; - $question.= makeyesno('update_admin_news_feed', '1', '0', '1').'
'; + $question .= makeyesno('update_admin_news_feed', '1', '0', '1') . '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } if (versionInUpdate($current_version, '0.9.32-dev2')) { $has_preconfig = true; - $description = 'To enable logging of the mail-traffic, you need to set the following settings accordingly

'; + $description = 'To enable logging of the mail-traffic, you need to set the following settings accordingly

'; $question = 'Do you want to enable the traffic collection for mail? (default: yes): '; - $question.= makeyesno('mailtraffic_enabled', '1', '0', '1').'
'; - $question.= 'Mail Transfer Agent
'; - $question.= 'Type of your MTA: '; - $question.= '
'; - $question.= 'Logfile for your MTA: '; - $question.= '
'; - $question.= 'Mail Delivery Agent
'; - $question.= 'Type of your MDA: '; - $question.= '

'; - $question.= 'Logfile for your MDA: '; - $question.= '
'; + $question .= makeyesno('mailtraffic_enabled', '1', '0', '1') . '
'; + $question .= 'Mail Transfer Agent
'; + $question .= 'Type of your MTA: '; + $question .= '
'; + $question .= 'Logfile for your MTA: '; + $question .= '
'; + $question .= 'Mail Delivery Agent
'; + $question .= 'Type of your MDA: '; + $question .= '

'; + $question .= 'Logfile for your MDA: '; + $question .= '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } @@ -638,7 +582,7 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c $has_preconfig = true; $description = 'Froxlor now generates a cron-configuration file for the cron-daemon. Please set a filename which will be included automatically by your crond (e.g. files in /etc/cron.d/)

'; $question = 'Path to the cron-service configuration-file. This file will be updated regularly and automatically by froxlor.
Note: please be sure to use the same filename as for the main froxlor cronjob (default: /etc/cron.d/froxlor)!
'; - $question.= '
'; + $question .= '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } @@ -646,7 +590,7 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c $has_preconfig = true; $description = 'In order for the new cron.d file to work properly, we need to know about the cron-service reload command.

'; $question = 'Please specify the reload-command of your cron-daemon (default: /etc/init.d/cron reload)
'; - $question.= '
'; + $question .= '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } @@ -654,17 +598,17 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c $has_preconfig = true; $description = 'To customize the command which executes the cronjob (php - basically) change the path below according to your system.

'; $question = 'Please specify the command to execute cronscripts (default: "/usr/bin/nice -n 5 /usr/bin/php5 -q")
'; - $question.= '
'; + $question .= '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } if (versionInUpdate($current_version, '0.9.33-dev1')) { $has_preconfig = true; - $description = 'You can enable/disable the display/usage of the custom newsfeed for customers.

'; + $description = 'You can enable/disable the display/usage of the custom newsfeed for customers.

'; $question = 'Do you want to enable the custom newsfeed for customer? (default: no): '; - $question.= makeyesno('customer_show_news_feed', '1', '0', '0').'
'; - $question.= 'You have to set the URL for your RSS-feed here, if you have chosen to enable the custom newsfeed on the customer-dashboard: '; - $question.= '
'; + $question .= makeyesno('customer_show_news_feed', '1', '0', '0') . '
'; + $question .= 'You have to set the URL for your RSS-feed here, if you have chosen to enable the custom newsfeed on the customer-dashboard: '; + $question .= '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } @@ -672,56 +616,67 @@ function parseAndOutputPreconfig(&$has_preconfig, &$return, $current_version, $c // only if bind is used - if not the default will be set, which is '0' (off) if (Settings::get('system.bind_enable') == 1) { $has_preconfig = true; - $description = 'You can enable/disable the generation of the bind-zone / config for the system hostname.

'; + $description = 'You can enable/disable the generation of the bind-zone / config for the system hostname.

'; $question = 'Do you want to generate a bind-zone for the system-hostname? (default: no): '; - $question.= makeyesno('dns_createhostnameentry', '1', '0', '0').'
'; + $question .= makeyesno('dns_createhostnameentry', '1', '0', '0') . '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } } if (versionInUpdate($current_version, '0.9.33-rc2')) { $has_preconfig = true; - $description = 'You can chose whether you want to receive an e-mail on cronjob errors. Keep in mind that this can lead to an e-mail being sent every 5 minutes.

'; + $description = 'You can chose whether you want to receive an e-mail on cronjob errors. Keep in mind that this can lead to an e-mail being sent every 5 minutes.

'; $question = 'Do you want to receive cron-errors via mail? (default: no): '; - $question.= makeyesno('system_send_cron_errors', '1', '0', '0').'
'; + $question .= makeyesno('system_send_cron_errors', '1', '0', '0') . '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } if (versionInUpdate($current_version, '0.9.34-dev3')) { - $has_preconfig = true; - $description = 'Froxlor now requires the PHP mbstring-extension as we need to be multibyte-character safe in some cases'; - $question = 'PHP mbstring is currently: '; - if (!extension_loaded('mbstring')) { - $question .= 'not installed/loaded'; - $question .= '
Please install the PHP mbstring extension in order to finish the update'; - } else { - $question .= 'installed/loaded'; - } - $question .= '
'; - eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); + $has_preconfig = true; + $description = 'Froxlor now requires the PHP mbstring-extension as we need to be multibyte-character safe in some cases'; + $question = 'PHP mbstring is currently: '; + if (! extension_loaded('mbstring')) { + $question .= 'not installed/loaded'; + $question .= '
Please install the PHP mbstring extension in order to finish the update'; + } else { + $question .= 'installed/loaded'; + } + $question .= '
'; + eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } if (versionInUpdate($current_db_version, '201603070')) { - $has_preconfig = true; - $description = 'You can chose whether you want to enable or disable our Let\'s Encrypt implementation.
Please remember that you need to go through the webserver-configuration when enabled because this feature needs a special configuration.

'; - $question = 'Do you want to enable Let\'s Encrypt? (default: yes): '; - $question.= makeyesno('enable_letsencrypt', '1', '0', '1').'
'; - eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); + $has_preconfig = true; + $description = 'You can chose whether you want to enable or disable our Let\'s Encrypt implementation.
Please remember that you need to go through the webserver-configuration when enabled because this feature needs a special configuration.

'; + $question = 'Do you want to enable Let\'s Encrypt? (default: yes): '; + $question .= makeyesno('enable_letsencrypt', '1', '0', '1') . '
'; + eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } if (versionInUpdate($current_db_version, '201604270')) { $has_preconfig = true; - $description = 'You can chose whether you want to enable or disable our backup function.

'; + $description = 'You can chose whether you want to enable or disable our backup function.

'; $question = 'Do you want to enable Backup? (default: no): '; - $question.= makeyesno('enable_backup', '1', '0', '0').'
'; + $question .= makeyesno('enable_backup', '1', '0', '0') . '
'; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } if (versionInUpdate($current_db_version, '201605090')) { $has_preconfig = true; - $description = 'You can chose whether you want to enable or disable our DNS editor

'; + $description = 'You can chose whether you want to enable or disable our DNS editor

'; $question = 'Do you want to enable the DNS editor? (default: no): '; - $question.= makeyesno('enable_dns', '1', '0', '0').'
'; + $question .= makeyesno('enable_dns', '1', '0', '0') . '
'; + eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); + } + + if (versionInUpdate($current_db_version, '201605170')) { + $has_preconfig = true; + $description = 'Froxlor now supports the dns-daemon Power-DNS, you can chose between bind and powerdns now.'; + $question = 'Select dns-daemon you want to use: '; + $question .= ''; eval("\$return.=\"" . getTemplate("update/preconfigitem") . "\";"); } } diff --git a/lib/configfiles/gentoo.xml b/lib/configfiles/gentoo.xml index 1c945572..71374ccb 100644 --- a/lib/configfiles/gentoo.xml +++ b/lib/configfiles/gentoo.xml @@ -388,7 +388,547 @@ mail IN A - + + + + + +################################# +# allow-dnsupdate-from A global setting to allow DNS updates from these IP ranges. +# +# allow-dnsupdate-from=127.0.0.0/8,::1 + +################################# +# allow-recursion List of subnets that are allowed to recurse +# +allow-recursion=127.0.0.1 + +################################# +# also-notify When notifying a domain, also notify these nameservers +# +# also-notify= + +################################# +# any-to-tcp Answer ANY queries with tc=1, shunting to TCP +# +# any-to-tcp=no + +################################# +# cache-ttl Seconds to store packets in the PacketCache +# +# cache-ttl=20 + +################################# +# carbon-interval Number of seconds between carbon (graphite) updates +# +# carbon-interval=30 + +################################# +# carbon-ourname If set, overrides our reported hostname for carbon stats +# +# carbon-ourname= + +################################# +# carbon-server If set, send metrics in carbon (graphite) format to this server +# +# carbon-server= + +################################# +# chroot If set, chroot to this directory for more security +# +# chroot= + +################################# +# config-dir Location of configuration directory (pdns.conf) +# +config-dir=/etc/powerdns + +################################# +# config-name Name of this virtual configuration - will rename the binary image +# +# config-name= + +################################# +# control-console Debugging switch - don't use +# +# control-console=no + +################################# +# daemon Operate as a daemon +# +daemon=yes + +################################# +# default-ksk-algorithms Default KSK algorithms +# +# default-ksk-algorithms=rsasha256 + +################################# +# default-ksk-size Default KSK size (0 means default) +# +# default-ksk-size=0 + +################################# +# default-soa-mail mail address to insert in the SOA record if none set in the backend +# +# default-soa-mail= + +################################# +# default-soa-name name to insert in the SOA record if none set in the backend +# +# default-soa-name=a.misconfigured.powerdns.server + +################################# +# default-ttl Seconds a result is valid if not set otherwise +# +# default-ttl=3600 + +################################# +# default-zsk-algorithms Default ZSK algorithms +# +# default-zsk-algorithms=rsasha256 + +################################# +# default-zsk-size Default ZSK size (0 means default) +# +# default-zsk-size=0 + +################################# +# direct-dnskey Fetch DNSKEY RRs from backend during DNSKEY synthesis +# +# direct-dnskey=no + +################################# +# disable-axfr Disable zonetransfers but do allow TCP queries +# +# disable-axfr=no + +################################# +# disable-axfr-rectify Disable the rectify step during an outgoing AXFR. Only required for regression testing. +# +# disable-axfr-rectify=no + +################################# +# disable-tcp Do not listen to TCP queries +# +# disable-tcp=no + +################################# +# distributor-threads Default number of Distributor (backend) threads to start +# +# distributor-threads=3 + +################################# +# do-ipv6-additional-processing Do AAAA additional processing +# +# do-ipv6-additional-processing=yes + +################################# +# edns-subnet-processing If we should act on EDNS Subnet options +# +# edns-subnet-processing=no + +################################# +# entropy-source If set, read entropy from this file +# +# entropy-source=/dev/urandom + +################################# +# experimental-api-key REST API Static authentication key (required for API use) +# +# experimental-api-key= + +################################# +# experimental-api-readonly If the JSON API should disallow data modification +# +# experimental-api-readonly=no + +################################# +# experimental-dname-processing If we should support DNAME records +# +# experimental-dname-processing=no + +################################# +# experimental-dnsupdate Enable/Disable DNS update (RFC2136) support. Default is no. +# +# experimental-dnsupdate=no + +################################# +# experimental-json-interface If the webserver should serve JSON data +# +# experimental-json-interface=no + +################################# +# experimental-logfile Filename of the log file for JSON parser +# +# experimental-logfile=/var/log/pdns.log + +################################# +# forward-dnsupdate A global setting to allow DNS update packages that are for a Slave domain, to be forwarded to the master. +# +# forward-dnsupdate=yes + +################################# +# guardian Run within a guardian process +# +guardian=yes + +################################# +# include-dir Include *.conf files from this directory +# +# include-dir= + +################################# +# launch Which backends to launch and order to query them in +# +# launch= + +################################# +# load-modules Load this module - supply absolute or relative path +# +# load-modules= + +################################# +# local-address Local IP addresses to which we bind +# +local-address=,127.0.0.1 + +################################# +# local-address-nonexist-fail Fail to start if one or more of the local-address's do not exist on this server +# +# local-address-nonexist-fail=yes + +################################# +# local-ipv6 Local IP address to which we bind +# +# local-ipv6= + +################################# +# local-ipv6-nonexist-fail Fail to start if one or more of the local-ipv6 addresses do not exist on this server +# +# local-ipv6-nonexist-fail=yes + +################################# +# local-port The port on which we listen +# +local-port=53 + +################################# +# log-dns-details If PDNS should log DNS non-erroneous details +# +log-dns-details=yes + +################################# +# log-dns-queries If PDNS should log all incoming DNS queries +# +# log-dns-queries=no + +################################# +# logging-facility Log under a specific facility +# +# logging-facility= + +################################# +# loglevel Amount of logging. Higher is more. Do not set below 3 +# +# loglevel=4 + +################################# +# lua-prequery-script Lua script with prequery handler +# +# lua-prequery-script= + +################################# +# master Act as a master +# +master=yes + +################################# +# max-cache-entries Maximum number of cache entries +# +# max-cache-entries=1000000 + +################################# +# max-ent-entries Maximum number of empty non-terminals in a zone +# +# max-ent-entries=100000 + +################################# +# max-nsec3-iterations Limit the number of NSEC3 hash iterations +# +# max-nsec3-iterations=500 + +################################# +# max-queue-length Maximum queuelength before considering situation lost +# +# max-queue-length=5000 + +################################# +# max-signature-cache-entries Maximum number of signatures cache entries +# +# max-signature-cache-entries= + +################################# +# max-tcp-connections Maximum number of TCP connections +# +# max-tcp-connections=10 + +################################# +# module-dir Default directory for modules +# +module-dir=/usr/lib/powerdns/pdns/ + +################################# +# negquery-cache-ttl Seconds to store negative query results in the QueryCache +# +# negquery-cache-ttl=60 + +################################# +# no-shuffle Set this to prevent random shuffling of answers - for regression testing +# +# no-shuffle=off + +################################# +# only-notify Only send AXFR NOTIFY to these IP addresses or netmasks +# +# only-notify=0.0.0.0/0,::/0 + +################################# +# out-of-zone-additional-processing Do out of zone additional processing +# +# out-of-zone-additional-processing=yes + +################################# +# overload-queue-length Maximum queuelength moving to packetcache only +# +# overload-queue-length=0 + +################################# +# pipebackend-abi-version Version of the pipe backend ABI +# +# pipebackend-abi-version=1 + +################################# +# prevent-self-notification Don't send notifications to what we think is ourself +# +# prevent-self-notification=yes + +################################# +# query-cache-ttl Seconds to store query results in the QueryCache +# +# query-cache-ttl=20 + +################################# +# query-local-address Source IP address for sending queries +# +# query-local-address=0.0.0.0 + +################################# +# query-local-address6 Source IPv6 address for sending queries +# +# query-local-address6=:: + +################################# +# query-logging Hint backends that queries should be logged +# +# query-logging=no + +################################# +# queue-limit Maximum number of milliseconds to queue a query +# +# queue-limit=1500 + +################################# +# receiver-threads Default number of receiver threads to start +# +# receiver-threads=1 + +################################# +# recursive-cache-ttl Seconds to store packets for recursive queries in the PacketCache +# +# recursive-cache-ttl=10 + +################################# +# recursor If recursion is desired, IP address of a recursing nameserver +# +# recursor=no + +################################# +# retrieval-threads Number of AXFR-retrieval threads for slave operation +# +# retrieval-threads=2 + +################################# +# reuseport Enable higher performance on compliant kernels by using SO_REUSEPORT allowing each receiver thread to open its own socket +# +# reuseport=no + +################################# +# security-poll-suffix Domain name from which to query security update notifications +# +# security-poll-suffix=secpoll.powerdns.com. + +################################# +# send-root-referral Send out old-fashioned root-referral instead of ServFail in case of no authority +# +# send-root-referral=no + +################################# +# server-id Returned when queried for 'server.id' TXT or NSID, defaults to hostname - disabled or custom +# +# server-id= + +################################# +# setgid If set, change group id to this gid for more security +# +setgid=pdns + +################################# +# setuid If set, change user id to this uid for more security +# +setuid=pdns + +################################# +# signing-threads Default number of signer threads to start +# +# signing-threads=3 + +################################# +# slave Act as a slave +# +# slave=no + +################################# +# slave-cycle-interval Reschedule failed SOA serial checks once every .. seconds +# +# slave-cycle-interval=60 + +################################# +# slave-renotify If we should send out notifications for slaved updates +# +# slave-renotify=no + +################################# +# soa-expire-default Default SOA expire +# +# soa-expire-default=604800 + +################################# +# soa-minimum-ttl Default SOA minimum ttl +# +# soa-minimum-ttl=3600 + +################################# +# soa-refresh-default Default SOA refresh +# +# soa-refresh-default=10800 + +################################# +# soa-retry-default Default SOA retry +# +# soa-retry-default=3600 + +################################# +# socket-dir Where the controlsocket will live +# +socket-dir=/var/run + +################################# +# tcp-control-address If set, PowerDNS can be controlled over TCP on this address +# +# tcp-control-address= + +################################# +# tcp-control-port If set, PowerDNS can be controlled over TCP on this address +# +# tcp-control-port=53000 + +################################# +# tcp-control-range If set, remote control of PowerDNS is possible over these networks only +# +# tcp-control-range=127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fe80::/10 + +################################# +# tcp-control-secret If set, PowerDNS can be controlled over TCP after passing this secret +# +# tcp-control-secret= + +################################# +# traceback-handler Enable the traceback handler (Linux only) +# +# traceback-handler=yes + +################################# +# trusted-notification-proxy IP address of incoming notification proxy +# +# trusted-notification-proxy= + +################################# +# udp-truncation-threshold Maximum UDP response size before we truncate +# +# udp-truncation-threshold=1680 + +################################# +# version-string PowerDNS version in packets - full, anonymous, powerdns or custom +# +version-string=powerdns + +################################# +# webserver Start a webserver for monitoring +# +# webserver=no + +################################# +# webserver-address IP Address of webserver to listen on +# +# webserver-address=127.0.0.1 + +################################# +# webserver-allow-from Webserver access is only allowed from these subnets +# +# webserver-allow-from=0.0.0.0/0,::/0 + +################################# +# webserver-password Password required for accessing the webserver +# +# webserver-password= + +################################# +# webserver-port Port of webserver to listen on +# +# webserver-port=8081 + +################################# +# webserver-print-arguments If the webserver should print arguments +# +# webserver-print-arguments=no + +# include froxlor-bind-specific config +include=/etc/powerdns/pdns_froxlor.conf +]]> + + + + + + + + + (2003-2009) - * @author Froxlor team (2010-) - * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt - * @package Cron + * @copyright (c) the authors + * @author Froxlor team (2016-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Cron * */ +class bind extends DnsBase +{ -class bind { - public $logger = false; - public $nameservers = array(); - public $mxservers = array(); - public $axfrservers = array(); + public function writeConfigs() + { + // tell the world what we are doing + $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Task4 started - Rebuilding froxlor_bind.conf'); - private $_known_filenames = array(); - private $_bindconf_file = ''; + // clean up + $this->_cleanZonefiles(); - public function __construct($logger) { + // check for subfolder in bind-config-directory + if (! file_exists(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))) { + $this->_logger->logAction(CRON_ACTION, LOG_NOTICE, 'mkdir ' . escapeshellarg(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))); + safe_exec('mkdir -p ' . escapeshellarg(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))); + } - $this->logger = $logger; + $domains = $this->getDomainList(); - if (Settings::Get('system.nameservers') != '') { - $nameservers = explode(',', Settings::Get('system.nameservers')); - foreach ($nameservers as $nameserver) { - $nameserver = trim($nameserver); - // DNS servers might be multi homed; allow transfer from all ip - // addresses of the DNS server - $nameserver_ips = gethostbynamel($nameserver); - // append dot to hostname - if (substr($nameserver, -1, 1) != '.') { - $nameserver.= '.'; + 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"; + + foreach ($domains as $domain) { + // check for system-hostname + $isFroxlorHostname = false; + if (isset($domain['froxlorhost']) && $domain['froxlorhost'] == 1) { + $isFroxlorHostname = true; } - // ignore invalid responses - if (!is_array($nameserver_ips)) { - // act like gethostbyname() and return unmodified hostname on error - $nameserver_ips = array($nameserver); - } - $this->nameservers[] = array( - 'hostname' => $nameserver, - 'ips' => $nameserver_ips - ); - } - } - - if (Settings::Get('system.mxservers') != '') { - $mxservers = explode(',', Settings::Get('system.mxservers')); - foreach ($mxservers as $mxserver) { - if (substr($mxserver, -1, 1) != '.') { - $mxserver.= '.'; - } - $this->mxservers[] = $mxserver; - } - } - - // AXFR server #100 - if (Settings::Get('system.axfrservers') != '') { - $axfrservers = explode(',', Settings::Get('system.axfrservers')); - foreach ($axfrservers as $axfrserver) { - $this->axfrservers[] = trim($axfrserver); - } - } - } - - - public function writeConfigs() { - $this->logger->logAction(CRON_ACTION, LOG_INFO, 'Task4 started - Rebuilding froxlor_bind.conf'); - - if (!file_exists(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))) { - $this->logger->logAction(CRON_ACTION, LOG_NOTICE, 'mkdir ' . escapeshellarg(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))); - safe_exec('mkdir ' . escapeshellarg(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))); - } - - $this->_known_filenames = array(); - - $this->_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"; - $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 = 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) { - $hostname_arr = array( - 'id' => 'none', - 'domain' => Settings::Get('system.hostname'), - 'isemaildomain' => Settings::Get('system.dns_createmailentry'), - 'customerid' => 'none', - 'loginname' => 'froxlor.panel', - 'bindserial' => date('Ymd').'00', - 'dkim' => '0', - 'iswildcarddomain' => '1', - 'ismainbutsubto' => '0', - 'zonefile' => '', - 'froxlorhost' => '1' - ); - $domains['none'] = $hostname_arr; - } - - if (empty($domains)) { - $this->logger->logAction(CRON_ACTION, LOG_INFO, 'No domains found for nameserver-config, skipping...'); - return; - } - - // 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); - } - - 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'); - safe_exec(escapeshellcmd(Settings::Get('system.bindreload_command'))); - $this->logger->logAction(CRON_ACTION, LOG_INFO, 'Bind9 reloaded'); - $domains_dir = makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'); - - if (file_exists($domains_dir) - && is_dir($domains_dir)) { - $domain_file_dirhandle = opendir($domains_dir); - - while (false !== ($domain_filename = readdir($domain_file_dirhandle))) { - $full_filename = makeCorrectFile($domains_dir . '/' . $domain_filename); - - if ($domain_filename != '.' - && $domain_filename != '..' - && !in_array($domain_filename, $this->_known_filenames) - && is_file($full_filename) - && file_exists($full_filename)) { - $this->logger->logAction(CRON_ACTION, LOG_WARNING, 'Deleting ' . $domain_filename); - unlink(makeCorrectFile($domains_dir . '/' . $domain_filename)); - } - } - } - $this->logger->logAction(CRON_ACTION, LOG_INFO, 'Task4 finished'); - } - - private function walkDomainList($domain, $domains) { - $zonefile = ''; - $subzones = ''; - - foreach($domain['children'] as $child_domain_id) { - $subzones.= $this->walkDomainList($domains[$child_domain_id], $domains); - } - - if ($domain['zonefile'] == '') { - if ($domain['ismainbutsubto'] == 0) { - $zonefile = $this->generateZone($domain); + // create zone-file + $this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns zone for ' . $domain['domain']); + $zone = createDomainZone($domain['id'], $isFroxlorHostname); + $zonefile = (string)$zone; $domain['zonefile'] = 'domains/' . $domain['domain'] . '.zone'; $zonefile_name = makeCorrectFile(Settings::Get('system.bindconf_directory') . '/' . $domain['zonefile']); - $this->_known_filenames[] = basename($zonefile_name); $zonefile_handler = fopen($zonefile_name, 'w'); - fwrite($zonefile_handler, $zonefile.$subzones); + fwrite($zonefile_handler, $zonefile); fclose($zonefile_handler); - $this->logger->logAction(CRON_ACTION, LOG_INFO, '`' . $zonefile_name . '` zone written'); - $this->_bindconf_file .= $this->_generateDomainConfig($domain); - } else { - return $this->generateZone($domain); + $this->_logger->logAction(CRON_ACTION, LOG_INFO, '`' . $zonefile_name . '` zone written'); + + // generate config + $bindconf_file .= $this->_generateDomainConfig($domain); } - } 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); + + // 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'); } } - private function _generateDomainConfig($domain = array()) { - if (isset($domain['froxlorhost']) && $domain['froxlorhost'] === '1') { - $froxlorhost = true; - } else { - $froxlorhost = false; - } + private function _generateDomainConfig($domain = array()) + { + $this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns config for ' . $domain['domain']); $bindconf_file = '# Domain ID: ' . $domain['id'] . ' - CustomerID: ' . $domain['customerid'] . ' - CustomerLogin: ' . $domain['loginname'] . "\n"; - $bindconf_file.= 'zone "' . $domain['domain'] . '" in {' . "\n"; - $bindconf_file.= ' type master;' . "\n"; - $bindconf_file.= ' file "' . makeCorrectFile(Settings::Get('system.bindconf_directory') . '/' . $domain['zonefile']) . '";' . "\n"; - $bindconf_file.= ' allow-query { any; };' . "\n"; + $bindconf_file .= 'zone "' . $domain['domain'] . '" in {' . "\n"; + $bindconf_file .= ' type master;' . "\n"; + $bindconf_file .= ' file "' . makeCorrectFile(Settings::Get('system.bindconf_directory') . '/' . $domain['zonefile']) . '";' . "\n"; + $bindconf_file .= ' allow-query { any; };' . "\n"; - if (count($this->nameservers) > 0 - || count($this->axfrservers) > 0 - ) { + if (count($this->_ns) > 0 || count($this->_axfr) > 0) { // open allow-transfer - $bindconf_file.= ' allow-transfer {' . "\n"; + $bindconf_file .= ' allow-transfer {' . "\n"; // put nameservers in allow-transfer - if (count($this->nameservers) > 0) { - foreach ($this->nameservers as $ns) { - foreach($ns["ips"] as $ip) { - $bindconf_file.= ' ' . $ip . ";\n"; + if (count($this->_ns) > 0) { + foreach ($this->_ns as $ns) { + foreach ($ns["ips"] as $ip) { + $bindconf_file .= ' ' . $ip . ";\n"; } } } // AXFR server #100 - if (count($this->axfrservers) > 0) { - foreach ($this->axfrservers as $axfrserver) { + if (count($this->_axfr) > 0) { + foreach ($this->_axfr as $axfrserver) { if (validate_ip($axfrserver, true) !== false) { - $bindconf_file.= ' ' . $axfrserver . ';' . "\n"; + $bindconf_file .= ' ' . $axfrserver . ';' . "\n"; } } } // close allow-transfer - $bindconf_file.= ' };' . "\n"; + $bindconf_file .= ' };' . "\n"; } - $bindconf_file.= '};' . "\n"; - $bindconf_file.= "\n"; + $bindconf_file .= '};' . "\n"; + $bindconf_file .= "\n"; return $bindconf_file; } - /** - * generate bind zone content. - * - * @param array $domain - * - * @return string - */ - protected function generateZone($domain) { - if (isset($domain['froxlorhost']) && $domain['froxlorhost'] === '1') { - $froxlorhost = true; - } else { - $froxlorhost = false; - } + private function _cleanZonefiles() + { + $config_dir = makeCorrectFile(Settings::Get('system.bindconf_directory') . '/domains/'); - // Array to save all ips needed in the records (already including IN A/AAAA) - $ip_a_records = array(); - // Array to save DNS records - $records = array(); + $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Cleaning dns zone files from ' . $config_dir); - $domainidquery = ''; - $query_params = array(); - if (!$froxlorhost) { + // check directory + if (@is_dir($config_dir)) { - $domainidquery = "`di`.`id_domain` = :domainid AND "; - $query_params['domainid'] = $domain['id']; + // create directory iterator + $its = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($config_dir)); - $result_ip_stmt = Database::prepare(" - SELECT `p`.`ip` AS `ip` - FROM `".TABLE_PANEL_IPSANDPORTS."` `p`, `".TABLE_DOMAINTOIP."` `di` - WHERE ".$domainidquery."`p`.`id` = `di`.`id_ipandports` - GROUP BY `p`.`ip`; - "); - } else { - // use all available IP's for the froxlor-hostname - $result_ip_stmt = Database::prepare(" - SELECT `ip` FROM `".TABLE_PANEL_IPSANDPORTS."` GROUP BY `ip` - "); - } - Database::pexecute($result_ip_stmt, $query_params); - - while ($ip = $result_ip_stmt->fetch(PDO::FETCH_ASSOC)) { - - if (filter_var($ip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { - $ip_a_records[] = "A\t\t" . $ip['ip']; - } - elseif (filter_var($ip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { - $ip_a_records[] = "AAAA\t\t" . $ip['ip']; - } - else { - return ";Error in at least one IP Address (".$ip['ip']."), could not create zonefile!"; - } - } - - if ($domain['ismainbutsubto'] == 0) { - $date = date('Ymd'); - $bindserial = (preg_match('/^' . $date . '/', $domain['bindserial']) ? $domain['bindserial'] + 1 : $date . '00'); - - if (!$froxlorhost) { - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET - `bindserial` = :serial - WHERE `id` = :id - "); - Database::pexecute($upd_stmt, array('serial' => $bindserial, 'id' => $domain['id'])); - } - - $zonefile = '$TTL ' . (int)Settings::Get('system.defaultttl') . "\n"; - if (count($this->nameservers) == 0) { - $zonefile.= '@ IN SOA ns ' . str_replace('@', '.', Settings::Get('panel.adminmail')) . '. (' . "\n"; - } else { - $zonefile.= '@ IN SOA ' . $this->nameservers[0]['hostname'] . ' ' . str_replace('@', '.', Settings::Get('panel.adminmail')) . '. (' . "\n"; - } - - $zonefile.= ' ' . $bindserial . ' ; serial' . "\n" . ' 8H ; refresh' . "\n" . ' 2H ; retry' . "\n" . ' 1W ; expiry' . "\n" . ' 11h) ; minimum' . "\n"; - - // no nameservers given, use all of the A/AAAA entries - if (count($this->nameservers) == 0) { - $zonefile .= '@ IN NS ns' . "\n"; - foreach ($ip_a_records as $ip_a_record) { - $zonefile .= 'ns IN ' . $ip_a_record . "\n"; - } - } else { - foreach ($this->nameservers as $nameserver) { - $zonefile.= '@ IN NS ' . trim($nameserver['hostname']) . "\n"; + // iterate through all subdirs, look for zone files and delete them + foreach ($its as $it) { + if ($it->isFile()) { + // remove file + safe_exec('rm -f ' . escapeshellarg(makeCorrectFile($its->getPathname()))); } } - } else { - $zonefile = '$ORIGIN ' . $domain["domain"] . ".\n"; - } - - if ($domain['isemaildomain'] === '1') { - if (count($this->mxservers) == 0) { - $zonefile.= '@ IN MX 10 mail' . "\n"; - $records[] = 'mail'; - if ($domain['iswildcarddomain'] != '1') { - $records[] = 'imap'; - $records[] = 'smtp'; - $records[] = 'pop3'; - } - } else { - foreach ($this->mxservers as $mxserver) { - $zonefile.= '@ IN MX ' . trim($mxserver) . "\n"; - } - - if (Settings::Get('system.dns_createmailentry') == '1') { - $records[] = 'mail'; - if ($domain['iswildcarddomain'] != '1') { - $records[] = 'imap'; - $records[] = 'smtp'; - $records[] = 'pop3'; - } - } - } - - /* - * @TODO domain-based spf-settings - */ - if (Settings::Get('spf.use_spf') == '1' - /*&& $domain['spf'] == '1' */ - ) { - $zonefile.= Settings::Get('spf.spf_entry') . "\n"; - if (in_array('mail', $records)) { - $zonefile.= str_replace('@', 'mail', Settings::Get('spf.spf_entry')) . "\n"; - } - } - } - - /** - * generate dkim-zone-entries - */ - $zonefile.= $this->generateDkim($domain); - - if (!$froxlorhost) { - $nssubdomains_stmt = Database::prepare(" - SELECT `domain` FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `isbinddomain` = '1' AND `ismainbutsubto` = '0' AND `domain` LIKE :domain - "); - Database::pexecute($nssubdomains_stmt, array('domain' => '%.' . $domain['domain'])); - - while ($nssubdomain = $nssubdomains_stmt->fetch(PDO::FETCH_ASSOC)) { - - if (preg_match('/^[^\.]+\.' . preg_quote($domain['domain'], '/') . '/', $nssubdomain['domain'])) { - - $nssubdomain = str_replace('.' . $domain['domain'], '', $nssubdomain['domain']); - - if (count($this->nameservers) == 0) { - $zonefile.= $nssubdomain . ' IN NS ns.' . $nssubdomain . "\n"; - } else { - foreach ($this->nameservers as $nameserver) { - $zonefile.= $nssubdomain . ' IN NS ' . trim($nameserver['hostname']) . "\n"; - } - } - } - } - } - - $records[] = '@'; - - if ($domain['iswildcarddomain'] == '1') { - $records[] = '*'; - } else if ($domain['wwwserveralias'] == '1') { - $records[] = 'www'; - } - - if (!$froxlorhost) { - $subdomains_stmt = Database::prepare(" - SELECT `domain` FROM `".TABLE_PANEL_DOMAINS."` - WHERE `parentdomainid` = :domainid - "); - Database::pexecute($subdomains_stmt, array('domainid' => $domain['id'])); - - while ($subdomain = $subdomains_stmt->fetch(PDO::FETCH_ASSOC)) { - // Listing domains is enough as there currently is no support for choosing - // different ips for a subdomain => use same IPs as toplevel - $records[] = str_replace('.' . $domain['domain'], '', $subdomain['domain']); - - // Check whether to add a www.-prefix - if ($domain['wwwserveralias'] == '1') { - $records[] = 'www.'.str_replace('.' . $domain['domain'], '', $subdomain['domain']); - } - } - } - - // Create DNS-Records for every name we have saved - foreach ($records as $record) { - // we create an entry for every ip we have saved - foreach ($ip_a_records as $ip_a_record) { - $zonefile.= $record . "\tIN\t" . $ip_a_record . "\n"; - } - } - - return $zonefile; - } - - - private function generateDkim($domain) { - $zone_dkim = ''; - - if (Settings::Get('dkim.use_dkim') == '1' - && $domain['dkim'] == '1' - && $domain['dkim_pubkey'] != '') { - // start - $dkim_txt = 'v=DKIM1;'; - - // algorithm - $algorithm = explode(',', Settings::Get('dkim.dkim_algorithm')); - $alg = ''; - foreach ($algorithm as $a) { - if ($a == 'all') { - break; - } else { - $alg.=$a.':'; - } - } - - if ($alg != '') { - $alg = substr($alg, 0, -1); - $dkim_txt.= 'h='.$alg.';'; - } - - // notes - if (trim(Settings::Get('dkim.dkim_notes') != '')) { - $dkim_txt.= 'n='.trim(Settings::Get('dkim.dkim_notes')).';'; - } - - // key - $dkim_txt.= 'k=rsa;p='.trim(preg_replace('/-----BEGIN PUBLIC KEY-----(.+)-----END PUBLIC KEY-----/s', '$1', str_replace("\n", '', $domain['dkim_pubkey']))).';'; - - // service-type - if (Settings::Get('dkim.dkim_servicetype') == '1') { - $dkim_txt.= 's=email;'; - } - - // end-part - $dkim_txt.='t=s'; - - // split if necessary - $txt_record_split=''; - $lbr=50; - for ($pos=0; $pos<=strlen($dkim_txt)-1; $pos+=$lbr) { - $txt_record_split.= (($pos==0) ? '("' : "\t\t\t\t\t \"") . substr($dkim_txt, $pos, $lbr) . (($pos>=strlen($dkim_txt)-$lbr) ? '")' : '"' ) ."\n"; - } - - // dkim-entry - $zone_dkim .= 'dkim_' . $domain['dkim_id'] . '._domainkey IN TXT ' . $txt_record_split; - - // adsp-entry - if (Settings::Get('dkim.dkim_add_adsp') == "1") { - - $zone_dkim .= '_adsp._domainkey IN TXT "dkim='; - switch ((int)Settings::Get('dkim.dkim_add_adsppolicy')) { - case 0: - $zone_dkim .= 'unknown"'. "\n"; - break; - case 1: - $zone_dkim .= 'all"'. "\n"; - break; - case 2: - $zone_dkim .= 'discardable"'. "\n"; - break; - } - } - } - - return $zone_dkim; - } - - - public function writeDKIMconfigs() { - if (Settings::Get('dkim.use_dkim') == '1') { - if (!file_exists(makeCorrectDir(Settings::Get('dkim.dkim_prefix')))) { - $this->logger->logAction(CRON_ACTION, LOG_NOTICE, 'mkdir -p ' . escapeshellarg(makeCorrectDir(Settings::Get('dkim.dkim_prefix')))); - safe_exec('mkdir -p ' . escapeshellarg(makeCorrectDir(Settings::Get('dkim.dkim_prefix')))); - } - - $dkimdomains = ''; - $dkimkeys = ''; - $result_domains_stmt = Database::query(" - SELECT `id`, `domain`, `dkim`, `dkim_id`, `dkim_pubkey`, `dkim_privkey` - FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `dkim` = '1' ORDER BY `id` ASC - "); - - while ($domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) { - - $privkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id']); - $pubkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id'] . '.public'); - - if ($domain['dkim_privkey'] == '' - || $domain['dkim_pubkey'] == '') { - $max_dkim_id_stmt = Database::query("SELECT MAX(`dkim_id`) as `max_dkim_id` FROM `" . TABLE_PANEL_DOMAINS . "`"); - $max_dkim_id = $max_dkim_id_stmt->fetch(PDO::FETCH_ASSOC); - $domain['dkim_id'] = (int)$max_dkim_id['max_dkim_id'] + 1; - $privkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id']); - safe_exec('openssl genrsa -out ' . escapeshellarg($privkey_filename) . ' ' . Settings::Get('dkim.dkim_keylength')); - $domain['dkim_privkey'] = file_get_contents($privkey_filename); - safe_exec("chmod 0640 " . escapeshellarg($privkey_filename)); - $pubkey_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim_' . $domain['dkim_id'] . '.public'); - safe_exec('openssl rsa -in ' . escapeshellarg($privkey_filename) . ' -pubout -outform pem -out ' . escapeshellarg($pubkey_filename)); - $domain['dkim_pubkey'] = file_get_contents($pubkey_filename); - safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename)); - $upd_stmt = Database::prepare(" - UPDATE `" . TABLE_PANEL_DOMAINS . "` SET - `dkim_id` = :dkimid, - `dkim_privkey` = :privkey, - `dkim_pubkey` = :pubkey - WHERE `id` = :id - "); - $upd_data = array( - 'dkimid' => $domain['dkim_id'], - 'privkey' => $domain['dkim_privkey'], - 'pubkey' => $domain['dkim_pubkey'], - 'id' => $domain['id'] - ); - Database::pexecute($upd_stmt, $upd_data); - } - - if (!file_exists($privkey_filename) - && $domain['dkim_privkey'] != '') { - $privkey_file_handler = fopen($privkey_filename, "w"); - fwrite($privkey_file_handler, $domain['dkim_privkey']); - fclose($privkey_file_handler); - safe_exec("chmod 0640 " . escapeshellarg($privkey_filename)); - } - - if (!file_exists($pubkey_filename) - && $domain['dkim_pubkey'] != '') { - $pubkey_file_handler = fopen($pubkey_filename, "w"); - fwrite($pubkey_file_handler, $domain['dkim_pubkey']); - fclose($pubkey_file_handler); - safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename)); - } - - $dkimdomains.= $domain['domain'] . "\n"; - $dkimkeys.= "*@" . $domain['domain'] . ":" . $domain['domain'] . ":" . $privkey_filename . "\n"; - } - - $dkimdomains_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/' . Settings::Get('dkim.dkim_domains')); - $dkimdomains_file_handler = fopen($dkimdomains_filename, "w"); - fwrite($dkimdomains_file_handler, $dkimdomains); - fclose($dkimdomains_file_handler); - $dkimkeys_filename = makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/' . Settings::Get('dkim.dkim_dkimkeys')); - $dkimkeys_file_handler = fopen($dkimkeys_filename, "w"); - fwrite($dkimkeys_file_handler, $dkimkeys); - fclose($dkimkeys_file_handler); - - safe_exec(escapeshellcmd(Settings::Get('dkim.dkimrestart_command'))); - $this->logger->logAction(CRON_ACTION, LOG_INFO, 'Dkim-milter reloaded'); } } } diff --git a/scripts/jobs/cron_tasks.inc.dns.15.bind.php b/scripts/jobs/cron_tasks.inc.dns.15.bind.php deleted file mode 100644 index 4c368be2..00000000 --- a/scripts/jobs/cron_tasks.inc.dns.15.bind.php +++ /dev/null @@ -1,134 +0,0 @@ - (2016-) - * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt - * @package Cron - * - */ -class bind2 extends DnsBase -{ - - public function writeConfigs() - { - // tell the world what we are doing - $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Task4 started - Rebuilding froxlor_bind.conf'); - - // clean up - $this->_cleanZonefiles(); - - // check for subfolder in bind-config-directory - if (! file_exists(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))) { - $this->_logger->logAction(CRON_ACTION, LOG_NOTICE, 'mkdir ' . escapeshellarg(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))); - safe_exec('mkdir -p ' . escapeshellarg(makeCorrectDir(Settings::Get('system.bindconf_directory') . '/domains/'))); - } - - $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"; - - 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'], $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'); - - // generate config - $bindconf_file .= $this->_generateDomainConfig($domain); - } - - // 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'); - } - } - - private function _generateDomainConfig($domain = array()) - { - $this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns config for ' . $domain['domain']); - - $bindconf_file = '# Domain ID: ' . $domain['id'] . ' - CustomerID: ' . $domain['customerid'] . ' - CustomerLogin: ' . $domain['loginname'] . "\n"; - $bindconf_file .= 'zone "' . $domain['domain'] . '" in {' . "\n"; - $bindconf_file .= ' type master;' . "\n"; - $bindconf_file .= ' file "' . makeCorrectFile(Settings::Get('system.bindconf_directory') . '/' . $domain['zonefile']) . '";' . "\n"; - $bindconf_file .= ' allow-query { any; };' . "\n"; - - if (count($this->_ns) > 0 || count($this->_axfr) > 0) { - // open allow-transfer - $bindconf_file .= ' allow-transfer {' . "\n"; - // put nameservers in allow-transfer - if (count($this->_ns) > 0) { - foreach ($this->_ns as $ns) { - foreach ($ns["ips"] as $ip) { - $bindconf_file .= ' ' . $ip . ";\n"; - } - } - } - // AXFR server #100 - if (count($this->_axfr) > 0) { - foreach ($this->_axfr as $axfrserver) { - if (validate_ip($axfrserver, true) !== false) { - $bindconf_file .= ' ' . $axfrserver . ';' . "\n"; - } - } - } - // close allow-transfer - $bindconf_file .= ' };' . "\n"; - } - - $bindconf_file .= '};' . "\n"; - $bindconf_file .= "\n"; - - return $bindconf_file; - } - - private function _cleanZonefiles() - { - $config_dir = makeCorrectFile(Settings::Get('system.bindconf_directory') . '/domains/'); - - $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Cleaning dns zone files from ' . $config_dir); - - // check directory - if (@is_dir($config_dir)) { - - // create directory iterator - $its = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($config_dir)); - - // iterate through all subdirs, look for zone files and delete them - foreach ($its as $it) { - if ($it->isFile()) { - // remove file - safe_exec('rm -f ' . escapeshellarg(makeCorrectFile($its->getPathname()))); - } - } - } - } -} diff --git a/scripts/jobs/cron_tasks.inc.dns.20.pdns.php b/scripts/jobs/cron_tasks.inc.dns.20.pdns.php new file mode 100644 index 00000000..61b11ec1 --- /dev/null +++ b/scripts/jobs/cron_tasks.inc.dns.20.pdns.php @@ -0,0 +1,204 @@ + (2016-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Cron + * + */ +class pdns extends DnsBase +{ + + private $pdns_db = null; + + public function writeConfigs() + { + // tell the world what we are doing + $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Task4 started - Refreshing DNS database'); + + // connect to db + $this->_connectToPdnsDb(); + + // clean up + $this->_cleanZonefiles(); + + $domains = $this->getDomainList(); + + if (! empty($domains)) { + + 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'], $isFroxlorHostname); + + $dom_id = $this->_insertZone($zone->origin, $zone->serial); + $this->_insertRecords($dom_id, $zone->records); + $this->_insertAllowedTransfers($dom_id); + + $this->_logger->logAction(CRON_ACTION, LOG_INFO, '`' . $domain['domain'] . '` zone written'); + } + + $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'); + } + } + + private function _cleanZonefiles() + { + $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Cleaning dns zone entries from database'); + + $this->pdns_db->query("TRUNCATE TABLE `records`"); + $this->pdns_db->query("TRUNCATE TABLE `domains`"); + $this->pdns_db->query("TRUNCATE TABLE `domainmetadata`"); + } + + private function _insertZone($domainname, $serial = 0) + { + $ins_stmt = $this->pdns_db->prepare(" + INSERT INTO domains set `name` = :domainname, `notified_serial` = :serial, `type` = 'NATIVE' + "); + $ins_stmt->execute(array('domainname' => $domainname, 'serial' => $serial)); + $lastid = $this->pdns_db->lastInsertId(); + return $lastid; + } + + private function _insertRecords($domainid = 0, $records) + { + $ins_stmt = $this->pdns_db->prepare(" + INSERT INTO records set + `domain_id` = :did, + `name` = :rec, + `type` = :type, + `content` = :content, + `ttl` = :ttl, + `prio` = :prio, + `disabled` = '0' + "); + + foreach ($records as $record) + { + $ins_data = array( + 'did' => $domainid, + 'rec' => $record->record, + 'type' => $record->type, + 'content' => $record->content, + 'ttl' => $record->ttl, + 'prio' => $record->priority + ); + $ins_stmt->execute($ins_data); + } + } + + private function _insertAllowedTransfers($domainid) + { + $ins_stmt = $this->pdns_db->prepare(" + INSERT INTO domainmetadata set `domain_id` = :did, `kind` = 'ALLOW-AXFR-FROM', `content` = :value + "); + + $ins_data = array( + 'did' => $domainid + ); + + if (count($this->_ns) > 0 || count($this->_axfr) > 0) { + // put nameservers in allow-transfer + if (count($this->_ns) > 0) { + foreach ($this->_ns as $ns) { + foreach ($ns["ips"] as $ip) { + $ins_data['value'] = $ip; + $ins_stmt->execute($ins_data); + } + } + } + // AXFR server #100 + if (count($this->_axfr) > 0) { + foreach ($this->_axfr as $axfrserver) { + if (validate_ip($axfrserver, true) !== false) { + $ins_data['value'] = $axfrserver; + $ins_stmt->execute($ins_data); + } + } + } + } + } + + private function _connectToPdnsDb() + { + // get froxlor pdns config + $cf = Settings::Get('system.bindconf_directory').'/pdns_froxlor.conf'; + $config = makeCorrectFile($cf); + + if (!file_exists($config)) + { + $this->_logger->logAction(CRON_ACTION, LOG_ERROR, 'PowerDNS configuration file not found. Did you go through the configuration templates?'); + die('PowerDNS configuration file not found. Did you go through the configuration templates?'); + } + $lines = file($config); + $mysql_data = array(); + foreach ($lines as $line) + { + $line = trim($line); + if (strtolower(substr($line, 0, 6)) == 'gmysql') + { + $namevalue = explode("=", $line); + $mysql_data[$namevalue[0]] = $namevalue[1]; + } + } + + // build up connection string + $driver = 'mysql'; + $dsn = $driver.":"; + $options = array(PDO::MYSQL_ATTR_INIT_COMMAND => 'set names utf8'); + $attributes = array('ATTR_ERRMODE' => 'ERRMODE_EXCEPTION'); + $dbconf = array(); + + $dbconf["dsn"] = array( + 'dbname' => $mysql_data["gmysql-dbname"], + 'charset' => 'utf8' + ); + + if (isset($mysql_data['gmysql-socket']) && !empty($mysql_data['gmysql-socket'])) { + $dbconf["dsn"]['unix_socket'] = makeCorrectFile($mysql_data['gmysql-socket']); + } else { + $dbconf["dsn"]['host'] = $mysql_data['gmysql-host']; + $dbconf["dsn"]['port'] = $mysql_data['gmysql-port']; + } + + // add options to dsn-string + foreach ($dbconf["dsn"] as $k => $v) { + $dsn .= $k."=".$v.";"; + } + + // clean up + unset($dbconf); + + // try to connect + try { + $this->pdns_db = new PDO($dsn, $mysql_data['gmysql-user'], $mysql_data['gmysql-password'], $options); + } catch (PDOException $e) { + die($e->getMessage()); + } + + // set attributes + foreach ($attributes as $k => $v) { + $this->pdns_db->setAttribute(constant("PDO::".$k), constant("PDO::".$v)); + } + } +} diff --git a/scripts/jobs/cron_tasks.php b/scripts/jobs/cron_tasks.php index 1bf460be..e69a0192 100644 --- a/scripts/jobs/cron_tasks.php +++ b/scripts/jobs/cron_tasks.php @@ -181,14 +181,10 @@ while ($row = $result_tasks_stmt->fetch(PDO::FETCH_ASSOC)) { */ elseif ($row['type'] == '4' && (int)Settings::Get('system.bind_enable') != 0) { - $bindclass ="bind"; - - if (Settings::Get('system.dnsenabled') == '1') { - $bindclass = "bind2"; - } + $dnssrv = Settings::Get('system.dns_server'); if (!isset($nameserver)) { - $nameserver = new $bindclass($cronlog); + $nameserver = new $dnssrv($cronlog); } if (Settings::Get('dkim.use_dkim') == '1') { From f7441df89543c2d09b534ee4f42bccb34016ebf3 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 17 May 2016 08:43:02 +0200 Subject: [PATCH 27/43] add missing file inclusion Signed-off-by: Michael Kaufmann (d00p) --- scripts/jobs/cron_tasks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/jobs/cron_tasks.php b/scripts/jobs/cron_tasks.php index e69a0192..dd7a754f 100644 --- a/scripts/jobs/cron_tasks.php +++ b/scripts/jobs/cron_tasks.php @@ -19,7 +19,7 @@ // necessary includes require_once makeCorrectFile(dirname(__FILE__) . '/cron_tasks.inc.dns.10.bind.php'); -require_once makeCorrectFile(dirname(__FILE__) . '/cron_tasks.inc.dns.15.bind.php'); +require_once makeCorrectFile(dirname(__FILE__) . '/cron_tasks.inc.dns.20.pdns.php'); require_once makeCorrectFile(dirname(__FILE__) . '/cron_tasks.inc.http.10.apache.php'); require_once makeCorrectFile(dirname(__FILE__) . '/cron_tasks.inc.http.15.apache_fcgid.php'); require_once makeCorrectFile(dirname(__FILE__) . '/cron_tasks.inc.http.20.lighttpd.php'); From 1ce5cf6c0010d22bb02f40d014eb9688bc2a9875 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Tue, 17 May 2016 09:12:39 +0200 Subject: [PATCH 28/43] various fixes for dns with froxlor-hostname Signed-off-by: Michael Kaufmann (d00p) --- .../dns/function.createDomainZone.php | 107 ++++++++++-------- scripts/classes/class.DnsBase.php | 8 +- scripts/jobs/cron_tasks.inc.dns.10.bind.php | 2 +- scripts/jobs/cron_tasks.inc.dns.20.pdns.php | 2 +- 4 files changed, 68 insertions(+), 51 deletions(-) diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php index 6d271c02..8a42efb8 100644 --- a/lib/functions/dns/function.createDomainZone.php +++ b/lib/functions/dns/function.createDomainZone.php @@ -16,22 +16,33 @@ */ function createDomainZone($domain_id, $froxlorhostname = false) { - // get domain-name - $dom_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` WHERE id = :did"); - $domain = Database::pexecute_first($dom_stmt, array( - 'did' => $domain_id - )); + if (!$froxlorhostname) + { + // get domain-name + $dom_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` WHERE id = :did"); + $domain = Database::pexecute_first($dom_stmt, array( + 'did' => $domain_id + )); + } + else + { + $domain = $domain_id; + } if ($domain['isbinddomain'] != '1') { return; } - // select all entries - $sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_DOMAIN_DNS . "` WHERE domain_id = :did ORDER BY id ASC"); - Database::pexecute($sel_stmt, array( - 'did' => $domain_id - )); - $dom_entries = $sel_stmt->fetchAll(PDO::FETCH_ASSOC); + $dom_entries = array(); + if (!$froxlorhostname) + { + // select all entries + $sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_DOMAIN_DNS . "` WHERE domain_id = :did ORDER BY id ASC"); + Database::pexecute($sel_stmt, array( + 'did' => $domain_id + )); + $dom_entries = $sel_stmt->fetchAll(PDO::FETCH_ASSOC); + } // check for required records $required_entries = array(); @@ -52,43 +63,46 @@ function createDomainZone($domain_id, $froxlorhostname = false) addRequiredEntry('www', 'AAAA', $required_entries); } - // additional required records for subdomains - $subdomains_stmt = Database::prepare(" - SELECT `domain` FROM `" . TABLE_PANEL_DOMAINS . "` - WHERE `parentdomainid` = :domainid - "); - Database::pexecute($subdomains_stmt, array( - 'domainid' => $domain_id - )); + if (!$froxlorhostname) + { + // additional required records for subdomains + $subdomains_stmt = Database::prepare(" + SELECT `domain` FROM `" . TABLE_PANEL_DOMAINS . "` + WHERE `parentdomainid` = :domainid + "); + Database::pexecute($subdomains_stmt, array( + 'domainid' => $domain_id + )); - while ($subdomain = $subdomains_stmt->fetch(PDO::FETCH_ASSOC)) { - // Listing domains is enough as there currently is no support for choosing - // different ips for a subdomain => use same IPs as toplevel - addRequiredEntry(str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'A', $required_entries); - addRequiredEntry(str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $required_entries); + while ($subdomain = $subdomains_stmt->fetch(PDO::FETCH_ASSOC)) { + // Listing domains is enough as there currently is no support for choosing + // different ips for a subdomain => use same IPs as toplevel + addRequiredEntry(str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'A', $required_entries); + addRequiredEntry(str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $required_entries); - // Check whether to add a www.-prefix - if ($domain['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') { - addRequiredEntry('www.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'A', $required_entries); - addRequiredEntry('www.' . str_replace('.' . $domain['domain'], '', $subdomain['domain']), 'AAAA', $required_entries); + // Check whether to add a www.-prefix + if ($domain['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') { + 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 - )); + // 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); + 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 @@ -136,6 +150,7 @@ function createDomainZone($domain_id, $froxlorhostname = false) $result_ip_stmt = Database::prepare(" SELECT `ip` FROM `" . TABLE_PANEL_IPSANDPORTS . "` GROUP BY `ip` "); + Database::pexecute($result_ip_stmt); } else { $result_ip_stmt = Database::prepare(" SELECT `p`.`ip` AS `ip` @@ -143,10 +158,10 @@ function createDomainZone($domain_id, $froxlorhostname = false) WHERE `di`.`id_domain` = :domainid AND `p`.`id` = `di`.`id_ipandports` GROUP BY `p`.`ip`; "); + Database::pexecute($result_ip_stmt, array( + 'domainid' => $domain_id + )); } - Database::pexecute($result_ip_stmt, array( - 'domainid' => $domain_id - )); $all_ips = $result_ip_stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($all_ips as $ip) { diff --git a/scripts/classes/class.DnsBase.php b/scripts/classes/class.DnsBase.php index 11e06996..ba7a8506 100644 --- a/scripts/classes/class.DnsBase.php +++ b/scripts/classes/class.DnsBase.php @@ -1,11 +1,13 @@ 'none', 'domain' => Settings::Get('system.hostname'), + 'isbinddomain' => '1', 'isemaildomain' => Settings::Get('system.dns_createmailentry'), 'customerid' => 'none', 'loginname' => 'froxlor.panel', @@ -181,5 +184,4 @@ abstract class DnsBase { $this->_logger->logAction(CRON_ACTION, LOG_INFO, 'Dkim-milter reloaded'); } } - -} \ No newline at end of file +} diff --git a/scripts/jobs/cron_tasks.inc.dns.10.bind.php b/scripts/jobs/cron_tasks.inc.dns.10.bind.php index 3e9cff1d..89120e98 100644 --- a/scripts/jobs/cron_tasks.inc.dns.10.bind.php +++ b/scripts/jobs/cron_tasks.inc.dns.10.bind.php @@ -46,7 +46,7 @@ class bind extends DnsBase } // create zone-file $this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns zone for ' . $domain['domain']); - $zone = createDomainZone($domain['id'], $isFroxlorHostname); + $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']); diff --git a/scripts/jobs/cron_tasks.inc.dns.20.pdns.php b/scripts/jobs/cron_tasks.inc.dns.20.pdns.php index 61b11ec1..8352c095 100644 --- a/scripts/jobs/cron_tasks.inc.dns.20.pdns.php +++ b/scripts/jobs/cron_tasks.inc.dns.20.pdns.php @@ -44,7 +44,7 @@ class pdns extends DnsBase } // create zone-file $this->_logger->logAction(CRON_ACTION, LOG_DEBUG, 'Generating dns zone for ' . $domain['domain']); - $zone = createDomainZone($domain['id'], $isFroxlorHostname); + $zone = createDomainZone(($domain['id'] == 'none') ? $domain : $domain['id'], $isFroxlorHostname); $dom_id = $this->_insertZone($zone->origin, $zone->serial); $this->_insertRecords($dom_id, $zone->records); From b14ab6b1c1dcb4b3ca2995a498600d81527a04f6 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 18 May 2016 10:35:47 +0200 Subject: [PATCH 29/43] validate record/label in dns-editor; better escaping for soa-admin mail Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 19 ++++++++++++++++++- .../dns/function.createDomainZone.php | 9 ++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/dns_editor.php b/dns_editor.php index bc9726a3..6dd50fb7 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -51,7 +51,24 @@ if ($action == 'add_record' && ! empty($_POST)) { $record = strtolower($record); - // TODO regex validate record and content for invalid characters + if ($record != '@' && $record != '*') + { + // validate record + if (strpos($record, '--') !== false) { + $errors[] = $lng['error']['domain_nopunycode']; + } + else + { + $record = $idna_convert->encode($record); + $check_dom = $record.'.example.com'; + if (!validateDomain($check_dom)) + { + $errors[] = sprintf($lng['error']['subdomainiswrong'], $idna_convert->decode($record)); + } + } + } + + // TODO regex validate content for invalid characters if ($ttl <= 0) { $ttl = 18000; diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php index 8a42efb8..f7baed6e 100644 --- a/lib/functions/dns/function.createDomainZone.php +++ b/lib/functions/dns/function.createDomainZone.php @@ -266,7 +266,7 @@ function createDomainZone($domain_id, $froxlorhostname = false) } // TODO for now, dummy time-periods - $soa_content = $primary_ns . " " . str_replace('@', '.', Settings::Get('panel.adminmail')) . ". (" . PHP_EOL; + $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; @@ -302,3 +302,10 @@ function encloseTXTContent($txt_content, $isMultiLine = false) } return $txt_content; } + +function escapeSoaAdminMail($email) +{ + $mail_parts = explode("@", $email); + $escpd_mail = str_replace(".", "\.", $mail_parts[0]).".".$mail_parts[1]."."; + return $escpd_mail; +} From 83fa0059deacfe88b1ab0fa675f6775e53b4a9ef Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 18 May 2016 13:25:31 +0200 Subject: [PATCH 30/43] add flag to allow access to dns-editor per customer; clean up dns-entries when domain gets deleted Signed-off-by: Michael Kaufmann (d00p) --- admin_customers.php | 27 ++++++++++++++++++- admin_domains.php | 9 +++++++ customer_domains.php | 7 +++++ .../updates/froxlor/0.9/update_0.9.inc.php | 9 +++++++ .../admin/customer/formfield.customer_add.php | 10 ++++++- .../customer/formfield.customer_edit.php | 11 +++++++- lng/english.lng.php | 1 + lng/german.lng.php | 1 + 8 files changed, 72 insertions(+), 3 deletions(-) diff --git a/admin_customers.php b/admin_customers.php index 0c96941c..406439e6 100644 --- a/admin_customers.php +++ b/admin_customers.php @@ -278,12 +278,14 @@ if ($page == 'customers' Database::pexecute($stmt, array('id' => $id)); $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DATABASES . "` WHERE `customerid` = :id"); Database::pexecute($stmt, array('id' => $id)); - // first gather all domain-id's to clean up panel_domaintoip accordingly + // first gather all domain-id's to clean up panel_domaintoip and dns-entries accordingly $did_stmt = Database::prepare("SELECT `id` FROM `".TABLE_PANEL_DOMAINS."` WHERE `customerid` = :id"); Database::pexecute($did_stmt, array('id' => $id)); while ($row = $did_stmt->fetch(PDO::FETCH_ASSOC)) { $stmt = Database::prepare("DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :did"); Database::pexecute($stmt, array('did' => $row['id'])); + $stmt = Database::prepare("DELETE FROM `" . TABLE_DOMAIN_DNS . "` WHERE `domain_id` = :did"); + Database::pexecute($stmt, array('did' => $row['id'])); } $stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :id"); Database::pexecute($stmt, array('id' => $id)); @@ -526,6 +528,11 @@ if ($page == 'customers' $perlenabled = intval($_POST['perlenabled']); } + $dnsenabled = 0; + if (isset($_POST['dnsenabled'])) { + $dnsenabled = intval($_POST['dnsenabled']); + } + $store_defaultindex = 0; if (isset($_POST['store_defaultindex'])) { $store_defaultindex = intval($_POST['store_defaultindex']); @@ -638,6 +645,10 @@ if ($page == 'customers' $perlenabled = '1'; } + if ($dnsenabled != '0') { + $dnsenabled = '1'; + } + if ($password == '') { $password = generatePassword(); } @@ -676,6 +687,7 @@ if ($page == 'customers' 'imap' => $email_imap, 'pop3' => $email_pop3, 'perlenabled' => $perlenabled, + 'dnsenabled' => $dnsenabled, 'theme' => $_theme, 'custom_notes' => $custom_notes, 'custom_notes_show' => $custom_notes_show @@ -712,9 +724,11 @@ if ($page == 'customers' `mysqls` = :mysqls, `standardsubdomain` = '0', `phpenabled` = :phpenabled, + `dnsenabled` = :dnsenabled, `imap` = :imap, `pop3` = :pop3, `perlenabled` = :perlenabled, + `dnsenabled` = :dnsenabled, `theme` = :theme, `custom_notes` = :custom_notes, `custom_notes_show` = :custom_notes_show" @@ -1186,6 +1200,11 @@ if ($page == 'customers' $perlenabled = intval($_POST['perlenabled']); } + $dnsenabled = 0; + if (isset($_POST['dnsenabled'])) { + $dnsenabled = intval($_POST['dnsenabled']); + } + $diskspace = $diskspace * 1024; $traffic = $traffic * 1024 * 1024; @@ -1317,6 +1336,10 @@ if ($page == 'customers' $perlenabled = '1'; } + if ($dnsenabled != '0') { + $dnsenabled = '1'; + } + if ($phpenabled != $result['phpenabled'] || $perlenabled != $result['perlenabled'] ) { @@ -1427,6 +1450,7 @@ if ($page == 'customers' 'imap' => $email_imap, 'pop3' => $email_pop3, 'perlenabled' => $perlenabled, + 'dnsenabled' => $dnsenabled, 'custom_notes' => $custom_notes, 'custom_notes_show' => $custom_notes_show ); @@ -1460,6 +1484,7 @@ if ($page == 'customers' `imap` = :imap, `pop3` = :pop3, `perlenabled` = :perlenabled, + `dnsenabled` = :dnsenabled `custom_notes` = :custom_notes, `custom_notes_show` = :custom_notes_show WHERE `customerid` = :customerid" diff --git a/admin_domains.php b/admin_domains.php index af3c18a1..c840b277 100644 --- a/admin_domains.php +++ b/admin_domains.php @@ -245,6 +245,15 @@ if ($page == 'domains' || $page == 'overview') { 'domainid' => $id )); + // remove possible existing DNS entries + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_DOMAIN_DNS . "` + WHERE `domain_id` = :domainid + "); + Database::pexecute($del_stmt, array( + 'domainid' => $id + )); + triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $log); $log->logAction(ADM_ACTION, LOG_INFO, "deleted domain/subdomains (#" . $result['id'] . ")"); diff --git a/customer_domains.php b/customer_domains.php index c34f296a..f9432f1b 100644 --- a/customer_domains.php +++ b/customer_domains.php @@ -232,6 +232,13 @@ if ($page == 'overview') { ); Database::pexecute($del_stmt, array('domainid' => $id)); + // remove possible existing DNS entries + $del_stmt = Database::prepare(" + DELETE FROM `" . TABLE_DOMAIN_DNS . "` + WHERE `domain_id` = :domainid + "); + Database::pexecute($del_stmt, array('domainid' => $id)); + inserttask('1'); // Using nameserver, insert a task which rebuilds the server config diff --git a/install/updates/froxlor/0.9/update_0.9.inc.php b/install/updates/froxlor/0.9/update_0.9.inc.php index a7ed3ef1..507add57 100644 --- a/install/updates/froxlor/0.9/update_0.9.inc.php +++ b/install/updates/froxlor/0.9/update_0.9.inc.php @@ -3365,3 +3365,12 @@ if (isDatabaseVersion('201605120')) { updateToDbVersion('201605170'); } + +if (isDatabaseVersion('201605170')) { + + showUpdateStep("Adding new dns-editor setting for customers"); + Database::query("ALTER TABLE `panel_customers` ADD `dnsenabled` tinyint(1) NOT NULL default '0' AFTER `perlenabled`;"); + lastStepStatus(0); + + updateToDbVersion('201605170'); +} diff --git a/lib/formfields/admin/customer/formfield.customer_add.php b/lib/formfields/admin/customer/formfield.customer_add.php index 54a7e863..eaa408aa 100644 --- a/lib/formfields/admin/customer/formfield.customer_add.php +++ b/lib/formfields/admin/customer/formfield.customer_add.php @@ -257,7 +257,15 @@ return array( 'values' => array( array ('label' => $lng['panel']['yes'], 'value' => '1') ) - ) + ), + 'dnsenabled' => array( + 'label' => $lng['admin']['dnsenabled'].'?', + 'type' => 'checkbox', + 'values' => array( + array ('label' => $lng['panel']['yes'], 'value' => '1') + ), + 'visible' => (Settings::Get('system.dnsenabled') == '1' ? true : false) + ), ) ) ) diff --git a/lib/formfields/admin/customer/formfield.customer_edit.php b/lib/formfields/admin/customer/formfield.customer_edit.php index b7e23476..b099651f 100644 --- a/lib/formfields/admin/customer/formfield.customer_edit.php +++ b/lib/formfields/admin/customer/formfield.customer_edit.php @@ -267,7 +267,16 @@ return array( array ('label' => $lng['panel']['yes'], 'value' => '1') ), 'value' => array($result['perlenabled']) - ) + ), + 'dnsenabled' => array( + 'label' => $lng['admin']['dnsenabled'].'?', + 'type' => 'checkbox', + 'values' => array( + array ('label' => $lng['panel']['yes'], 'value' => '1') + ), + 'value' => array($result['dnsenabled']), + 'visible' => (Settings::Get('system.dnsenabled') == '1' ? true : false) + ), ) ), 'section_d' => array( diff --git a/lng/english.lng.php b/lng/english.lng.php index 72873745..da5a5a66 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -2016,3 +2016,4 @@ $lng['serversettings']['dns_server']['title'] = 'DNS server daemon'; $lng['serversettings']['dns_server']['description'] = 'Remember that daemons have to be configured using froxlors configuration templates'; $lng['error']['domain_nopunycode'] = 'You must not specify punycode (IDNA). The domain will automatically be converted'; +$lng['admin']['dnsenabled'] = 'Enable DNS editor'; diff --git a/lng/german.lng.php b/lng/german.lng.php index ee44bec4..9b26a8ff 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1669,3 +1669,4 @@ $lng['serversettings']['dns_server']['title'] = 'DNS Server Dienst'; $lng['serversettings']['dns_server']['description'] = 'Dienste müssen mit den froxlor Konfigurationstemplates konfiguriert werden'; $lng['error']['domain_nopunycode'] = 'Die Eingabe von Punycode (IDNA) ist nicht notwendig. Die Domain wird automatisch konvertiert.'; +$lng['admin']['dnsenabled'] = 'Zugriff auf DNS Editor'; From b4c7fb574c0823067c55396e061d1a71384b286c Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 18 May 2016 13:27:56 +0200 Subject: [PATCH 31/43] increase DB version for db-updates Signed-off-by: Michael Kaufmann (d00p) --- install/updates/froxlor/0.9/update_0.9.inc.php | 2 +- lib/version.inc.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/install/updates/froxlor/0.9/update_0.9.inc.php b/install/updates/froxlor/0.9/update_0.9.inc.php index 507add57..cf1cdec5 100644 --- a/install/updates/froxlor/0.9/update_0.9.inc.php +++ b/install/updates/froxlor/0.9/update_0.9.inc.php @@ -3372,5 +3372,5 @@ if (isDatabaseVersion('201605170')) { Database::query("ALTER TABLE `panel_customers` ADD `dnsenabled` tinyint(1) NOT NULL default '0' AFTER `perlenabled`;"); lastStepStatus(0); - updateToDbVersion('201605170'); + updateToDbVersion('201605180'); } diff --git a/lib/version.inc.php b/lib/version.inc.php index a8bcacfa..924d7cf1 100644 --- a/lib/version.inc.php +++ b/lib/version.inc.php @@ -19,7 +19,7 @@ $version = '0.9.36'; // Database version (YYYYMMDDC where C is a daily counter) -$dbversion = '201605170'; +$dbversion = '201605180'; // Distribution branding-tag (used for Debian etc.) $branding = ''; From fa60c17dbcac637d6feacd0cf8f71c445f760d0e Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 18 May 2016 13:32:44 +0200 Subject: [PATCH 32/43] fix sql-query when editing a customer; check for dnsenabled flag in customer-domain-view template Signed-off-by: Michael Kaufmann (d00p) --- admin_customers.php | 3 +-- templates/Sparkle/customer/domains/domains_edit.tpl | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/admin_customers.php b/admin_customers.php index 406439e6..7c41f687 100644 --- a/admin_customers.php +++ b/admin_customers.php @@ -724,7 +724,6 @@ if ($page == 'customers' `mysqls` = :mysqls, `standardsubdomain` = '0', `phpenabled` = :phpenabled, - `dnsenabled` = :dnsenabled, `imap` = :imap, `pop3` = :pop3, `perlenabled` = :perlenabled, @@ -1484,7 +1483,7 @@ if ($page == 'customers' `imap` = :imap, `pop3` = :pop3, `perlenabled` = :perlenabled, - `dnsenabled` = :dnsenabled + `dnsenabled` = :dnsenabled, `custom_notes` = :custom_notes, `custom_notes_show` = :custom_notes_show WHERE `customerid` = :customerid" diff --git a/templates/Sparkle/customer/domains/domains_edit.tpl b/templates/Sparkle/customer/domains/domains_edit.tpl index d7a3c9ea..1ce66b1b 100644 --- a/templates/Sparkle/customer/domains/domains_edit.tpl +++ b/templates/Sparkle/customer/domains/domains_edit.tpl @@ -4,7 +4,7 @@ $header

{$title}  {$title} - +  ({$lng['dnseditor']['edit']})

From 1f63ea10a0ce0b8be051b7f59532e8a2380f25d3 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 18 May 2016 13:39:46 +0200 Subject: [PATCH 33/43] adjust install sql file for dnsenabled flag and db-version Signed-off-by: Michael Kaufmann (d00p) --- install/froxlor.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/install/froxlor.sql b/install/froxlor.sql index 202d4d5a..77644c5a 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -191,6 +191,7 @@ CREATE TABLE `panel_customers` ( `pop3` tinyint(1) NOT NULL default '1', `imap` tinyint(1) NOT NULL default '1', `perlenabled` tinyint(1) NOT NULL default '0', + `dnsenabled` tinyint(1) NOT NULL default '0' `theme` varchar(255) NOT NULL default 'Sparkle', `custom_notes` text, `custom_notes_show` tinyint(1) NOT NULL default '0', @@ -559,7 +560,7 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES ('panel', 'password_special_char_required', '0'), ('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'version', '0.9.36'), - ('panel', 'db_version', '201605170'); + ('panel', 'db_version', '201605180'); DROP TABLE IF EXISTS `panel_tasks`; From 1e3262d6911737077837d12fb51f9da0ed6eb355 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 18 May 2016 13:54:21 +0200 Subject: [PATCH 34/43] do not show dns-editor to customers if not allowed and they enter the URL manually Signed-off-by: Michael Kaufmann (d00p) --- customer_domains.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/customer_domains.php b/customer_domains.php index f9432f1b..fd8a0425 100644 --- a/customer_domains.php +++ b/customer_domains.php @@ -920,7 +920,7 @@ if ($page == 'overview') { eval("echo \"" . getTemplate("domains/domain_ssleditor") . "\";"); } -} elseif ($page == 'domaindnseditor' && Settings::Get('system.dnsenabled') == '1') { +} elseif ($page == 'domaindnseditor' && $userinfo['dnsenabled'] == '1' && Settings::Get('system.dnsenabled') == '1') { require_once __DIR__.'/dns_editor.php'; } From 076b6143ce75e6811ca00634eaa243c8fc9d2817 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 18 May 2016 15:55:20 +0200 Subject: [PATCH 35/43] limit record length Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 4 ++++ lng/english.lng.php | 1 + lng/german.lng.php | 1 + 3 files changed, 6 insertions(+) diff --git a/dns_editor.php b/dns_editor.php index 6dd50fb7..222a412b 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -65,6 +65,10 @@ if ($action == 'add_record' && ! empty($_POST)) { { $errors[] = sprintf($lng['error']['subdomainiswrong'], $idna_convert->decode($record)); } + if (strlen($record) > 63) + { + $errors[] = $lng['error']['dns_record_toolong']; + } } } diff --git a/lng/english.lng.php b/lng/english.lng.php index da5a5a66..8d84df25 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -2017,3 +2017,4 @@ $lng['serversettings']['dns_server']['description'] = 'Remember that daemons hav $lng['error']['domain_nopunycode'] = 'You must not specify punycode (IDNA). The domain will automatically be converted'; $lng['admin']['dnsenabled'] = 'Enable DNS editor'; +$lng['error']['dns_record_toolong'] = 'Records/labels can only be up to 63 characters'; diff --git a/lng/german.lng.php b/lng/german.lng.php index 9b26a8ff..056712a3 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -1670,3 +1670,4 @@ $lng['serversettings']['dns_server']['description'] = 'Dienste müssen mit den f $lng['error']['domain_nopunycode'] = 'Die Eingabe von Punycode (IDNA) ist nicht notwendig. Die Domain wird automatisch konvertiert.'; $lng['admin']['dnsenabled'] = 'Zugriff auf DNS Editor'; +$lng['error']['dns_record_toolong'] = 'Records/Labels können maximal 63 Zeichen lang sein'; From 96ff346e546630fadbfe53ba91dbf5316fde26c3 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 19 May 2016 09:28:46 +0200 Subject: [PATCH 36/43] fix powerdns-config template Signed-off-by: Michael Kaufmann (d00p) --- lib/configfiles/gentoo.xml | 17 ++++++++++------- scripts/jobs/cron_tasks.inc.dns.20.pdns.php | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/configfiles/gentoo.xml b/lib/configfiles/gentoo.xml index 71374ccb..e86c2478 100644 --- a/lib/configfiles/gentoo.xml +++ b/lib/configfiles/gentoo.xml @@ -396,7 +396,7 @@ mail IN A ################################# # allow-axfr-ips Allow zonetransfers only to these subnets # -allow-axfr-ips= +# allow-axfr-ips=127.0.0.0/8,::1, ################################# # allow-dnsupdate-from A global setting to allow DNS updates from these IP ranges. @@ -908,15 +908,17 @@ version-string=powerdns # # webserver-print-arguments=no -# include froxlor-bind-specific config -include=/etc/powerdns/pdns_froxlor.conf +# include froxlor-specific config +include-dir=/etc/powerdns/froxlor/ ]]>
- + +# allow-axfr-ips=127.0.0.0/8,::1, ################################# # allow-dnsupdate-from A global setting to allow DNS updates from these IP ranges. @@ -1449,11 +1451,12 @@ version-string=powerdns # webserver-print-arguments=no # include froxlor-bind-specific config -include=/etc/powerdns/pdns_froxlor.conf +include-dir=/etc/powerdns/froxlor/ ]]> - + Date: Thu, 19 May 2016 10:29:17 +0200 Subject: [PATCH 37/43] add powerdns config-templates for distros Signed-off-by: Michael Kaufmann (d00p) --- lib/configfiles/jessie.xml | 547 +++++++++++++++++++- lib/configfiles/precise.xml | 38 +- lib/configfiles/trusty.xml | 39 +- lib/configfiles/wheezy.xml | 340 +++++++++++- scripts/jobs/cron_tasks.inc.dns.20.pdns.php | 4 +- 5 files changed, 959 insertions(+), 9 deletions(-) diff --git a/lib/configfiles/jessie.xml b/lib/configfiles/jessie.xml index 27050f9b..f60a1b9f 100644 --- a/lib/configfiles/jessie.xml +++ b/lib/configfiles/jessie.xml @@ -373,7 +373,548 @@ exit "$RETVAL"
- + + + + + +################################# +# allow-dnsupdate-from A global setting to allow DNS updates from these IP ranges. +# +# allow-dnsupdate-from=127.0.0.0/8,::1 + +################################# +# allow-recursion List of subnets that are allowed to recurse +# +allow-recursion=127.0.0.1 + +################################# +# also-notify When notifying a domain, also notify these nameservers +# +# also-notify= + +################################# +# any-to-tcp Answer ANY queries with tc=1, shunting to TCP +# +# any-to-tcp=no + +################################# +# cache-ttl Seconds to store packets in the PacketCache +# +# cache-ttl=20 + +################################# +# carbon-interval Number of seconds between carbon (graphite) updates +# +# carbon-interval=30 + +################################# +# carbon-ourname If set, overrides our reported hostname for carbon stats +# +# carbon-ourname= + +################################# +# carbon-server If set, send metrics in carbon (graphite) format to this server +# +# carbon-server= + +################################# +# chroot If set, chroot to this directory for more security +# +# chroot= + +################################# +# config-dir Location of configuration directory (pdns.conf) +# +config-dir=/etc/powerdns + +################################# +# config-name Name of this virtual configuration - will rename the binary image +# +# config-name= + +################################# +# control-console Debugging switch - don't use +# +# control-console=no + +################################# +# daemon Operate as a daemon +# +daemon=yes + +################################# +# default-ksk-algorithms Default KSK algorithms +# +# default-ksk-algorithms=rsasha256 + +################################# +# default-ksk-size Default KSK size (0 means default) +# +# default-ksk-size=0 + +################################# +# default-soa-mail mail address to insert in the SOA record if none set in the backend +# +# default-soa-mail= + +################################# +# default-soa-name name to insert in the SOA record if none set in the backend +# +# default-soa-name=a.misconfigured.powerdns.server + +################################# +# default-ttl Seconds a result is valid if not set otherwise +# +# default-ttl=3600 + +################################# +# default-zsk-algorithms Default ZSK algorithms +# +# default-zsk-algorithms=rsasha256 + +################################# +# default-zsk-size Default ZSK size (0 means default) +# +# default-zsk-size=0 + +################################# +# direct-dnskey Fetch DNSKEY RRs from backend during DNSKEY synthesis +# +# direct-dnskey=no + +################################# +# disable-axfr Disable zonetransfers but do allow TCP queries +# +# disable-axfr=no + +################################# +# disable-axfr-rectify Disable the rectify step during an outgoing AXFR. Only required for regression testing. +# +# disable-axfr-rectify=no + +################################# +# disable-tcp Do not listen to TCP queries +# +# disable-tcp=no + +################################# +# distributor-threads Default number of Distributor (backend) threads to start +# +# distributor-threads=3 + +################################# +# do-ipv6-additional-processing Do AAAA additional processing +# +# do-ipv6-additional-processing=yes + +################################# +# edns-subnet-processing If we should act on EDNS Subnet options +# +# edns-subnet-processing=no + +################################# +# entropy-source If set, read entropy from this file +# +# entropy-source=/dev/urandom + +################################# +# experimental-api-key REST API Static authentication key (required for API use) +# +# experimental-api-key= + +################################# +# experimental-api-readonly If the JSON API should disallow data modification +# +# experimental-api-readonly=no + +################################# +# experimental-dname-processing If we should support DNAME records +# +# experimental-dname-processing=no + +################################# +# experimental-dnsupdate Enable/Disable DNS update (RFC2136) support. Default is no. +# +# experimental-dnsupdate=no + +################################# +# experimental-json-interface If the webserver should serve JSON data +# +# experimental-json-interface=no + +################################# +# experimental-logfile Filename of the log file for JSON parser +# +# experimental-logfile=/var/log/pdns.log + +################################# +# forward-dnsupdate A global setting to allow DNS update packages that are for a Slave domain, to be forwarded to the master. +# +# forward-dnsupdate=yes + +################################# +# guardian Run within a guardian process +# +guardian=yes + +################################# +# include-dir Include *.conf files from this directory +# +# include-dir= +include-dir=/etc/powerdns/pdns.d + +################################# +# launch Which backends to launch and order to query them in +# +# launch= + +################################# +# load-modules Load this module - supply absolute or relative path +# +# load-modules= + +################################# +# local-address Local IP addresses to which we bind +# +local-address=,127.0.0.1 + +################################# +# local-address-nonexist-fail Fail to start if one or more of the local-address's do not exist on this server +# +# local-address-nonexist-fail=yes + +################################# +# local-ipv6 Local IP address to which we bind +# +# local-ipv6= + +################################# +# local-ipv6-nonexist-fail Fail to start if one or more of the local-ipv6 addresses do not exist on this server +# +# local-ipv6-nonexist-fail=yes + +################################# +# local-port The port on which we listen +# +# local-port=53 + +################################# +# log-dns-details If PDNS should log DNS non-erroneous details +# +# log-dns-details=no + +################################# +# log-dns-queries If PDNS should log all incoming DNS queries +# +# log-dns-queries=no + +################################# +# logging-facility Log under a specific facility +# +# logging-facility= + +################################# +# loglevel Amount of logging. Higher is more. Do not set below 3 +# +# loglevel=4 + +################################# +# lua-prequery-script Lua script with prequery handler +# +# lua-prequery-script= + +################################# +# master Act as a master +# +master=yes + +################################# +# max-cache-entries Maximum number of cache entries +# +# max-cache-entries=1000000 + +################################# +# max-ent-entries Maximum number of empty non-terminals in a zone +# +# max-ent-entries=100000 + +################################# +# max-nsec3-iterations Limit the number of NSEC3 hash iterations +# +# max-nsec3-iterations=500 + +################################# +# max-queue-length Maximum queuelength before considering situation lost +# +# max-queue-length=5000 + +################################# +# max-signature-cache-entries Maximum number of signatures cache entries +# +# max-signature-cache-entries= + +################################# +# max-tcp-connections Maximum number of TCP connections +# +# max-tcp-connections=10 + +################################# +# module-dir Default directory for modules +# +# module-dir=/usr/lib/TRIPLET/pdns + +################################# +# negquery-cache-ttl Seconds to store negative query results in the QueryCache +# +# negquery-cache-ttl=60 + +################################# +# no-shuffle Set this to prevent random shuffling of answers - for regression testing +# +# no-shuffle=off + +################################# +# only-notify Only send AXFR NOTIFY to these IP addresses or netmasks +# +# only-notify=0.0.0.0/0,::/0 + +################################# +# out-of-zone-additional-processing Do out of zone additional processing +# +# out-of-zone-additional-processing=yes + +################################# +# overload-queue-length Maximum queuelength moving to packetcache only +# +# overload-queue-length=0 + +################################# +# pipebackend-abi-version Version of the pipe backend ABI +# +# pipebackend-abi-version=1 + +################################# +# prevent-self-notification Don't send notifications to what we think is ourself +# +# prevent-self-notification=yes + +################################# +# query-cache-ttl Seconds to store query results in the QueryCache +# +# query-cache-ttl=20 + +################################# +# query-local-address Source IP address for sending queries +# +# query-local-address=0.0.0.0 + +################################# +# query-local-address6 Source IPv6 address for sending queries +# +# query-local-address6=:: + +################################# +# query-logging Hint backends that queries should be logged +# +# query-logging=no + +################################# +# queue-limit Maximum number of milliseconds to queue a query +# +# queue-limit=1500 + +################################# +# receiver-threads Default number of receiver threads to start +# +# receiver-threads=1 + +################################# +# recursive-cache-ttl Seconds to store packets for recursive queries in the PacketCache +# +# recursive-cache-ttl=10 + +################################# +# recursor If recursion is desired, IP address of a recursing nameserver +# +# recursor=no + +################################# +# retrieval-threads Number of AXFR-retrieval threads for slave operation +# +# retrieval-threads=2 + +################################# +# reuseport Enable higher performance on compliant kernels by using SO_REUSEPORT allowing each receiver thread to open its own socket +# +# reuseport=no + +################################# +# security-poll-suffix Domain name from which to query security update notifications +# +# security-poll-suffix=secpoll.powerdns.com. + +################################# +# send-root-referral Send out old-fashioned root-referral instead of ServFail in case of no authority +# +# send-root-referral=no + +################################# +# server-id Returned when queried for 'server.id' TXT or NSID, defaults to hostname - disabled or custom +# +# server-id= + +################################# +# setgid If set, change group id to this gid for more security +# +setgid=pdns + +################################# +# setuid If set, change user id to this uid for more security +# +setuid=pdns + +################################# +# signing-threads Default number of signer threads to start +# +# signing-threads=3 + +################################# +# slave Act as a slave +# +# slave=no + +################################# +# slave-cycle-interval Reschedule failed SOA serial checks once every .. seconds +# +# slave-cycle-interval=60 + +################################# +# slave-renotify If we should send out notifications for slaved updates +# +# slave-renotify=no + +################################# +# soa-expire-default Default SOA expire +# +# soa-expire-default=604800 + +################################# +# soa-minimum-ttl Default SOA minimum ttl +# +# soa-minimum-ttl=3600 + +################################# +# soa-refresh-default Default SOA refresh +# +# soa-refresh-default=10800 + +################################# +# soa-retry-default Default SOA retry +# +# soa-retry-default=3600 + +################################# +# socket-dir Where the controlsocket will live +# +# socket-dir=/var/run + +################################# +# tcp-control-address If set, PowerDNS can be controlled over TCP on this address +# +# tcp-control-address= + +################################# +# tcp-control-port If set, PowerDNS can be controlled over TCP on this address +# +# tcp-control-port=53000 + +################################# +# tcp-control-range If set, remote control of PowerDNS is possible over these networks only +# +# tcp-control-range=127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fe80::/10 + +################################# +# tcp-control-secret If set, PowerDNS can be controlled over TCP after passing this secret +# +# tcp-control-secret= + +################################# +# traceback-handler Enable the traceback handler (Linux only) +# +# traceback-handler=yes + +################################# +# trusted-notification-proxy IP address of incoming notification proxy +# +# trusted-notification-proxy= + +################################# +# udp-truncation-threshold Maximum UDP response size before we truncate +# +# udp-truncation-threshold=1680 + +################################# +# version-string PowerDNS version in packets - full, anonymous, powerdns or custom +# + +version-string=powerdns +################################# +# webserver Start a webserver for monitoring +# +# webserver=no + +################################# +# webserver-address IP Address of webserver to listen on +# +# webserver-address=127.0.0.1 + +################################# +# webserver-allow-from Webserver access is only allowed from these subnets +# +# webserver-allow-from=0.0.0.0/0,::/0 + +################################# +# webserver-password Password required for accessing the webserver +# +# webserver-password= + +################################# +# webserver-port Port of webserver to listen on +# +# webserver-port=8081 + +################################# +# webserver-print-arguments If the webserver should print arguments +# +# webserver-print-arguments=no + +include-dir=/etc/powerdns/froxlor +]]> + + + + + + + + + + - + - + + + + + + + + + + + + + - + + + + + + + + + + + + + + - + + + + + +################################# +# allow-recursion List of netmasks that are allowed to recurse +# +allow-recursion=127.0.0.1 + +################################# +# allow-recursion-override Local data even about hosts that don't exist will +# override the internet. (on/off) +# +# allow-recursion-override= + +################################# +# cache-ttl Seconds to store packets in the PacketCache +# +# cache-ttl=20 + +################################# +# chroot If set, chroot to this directory for more security +# +# chroot=/var/spool/powerdns + +################################# +# config-dir Location of configuration directory (pdns.conf) +# +config-dir=/etc/powerdns + +################################# +# config-name Name of this virtual configuration - will rename the binary image +# +# config-name= + +################################# +# control-console Debugging switch - don't use +# +# control-console=no + +################################# +# daemon Operate as a daemon +# +daemon=yes + +################################# +# default-soa-name name to insert in the SOA record if none set in the backend +# +# default-soa-name=a.misconfigured.powerdns.server + +################################# +# disable-axfr Disable zonetransfers but do allow TCP queries +# +disable-axfr=yes + +################################# +# disable-tcp Do not listen to TCP queries +# +# disable-tcp=no + +################################# +# distributor-threads Default number of Distributor (backend) threads to start +# +# distributor-threads=3 + +################################# +# fancy-records Process URL and MBOXFW records +# +# fancy-records=no + +################################# +# guardian Run within a guardian process +# +guardian=yes + +################################# +# launch Which backends to launch and order to query them in +# +# launch= + +################################# +# lazy-recursion Only recurse if question cannot be answered locally +# +lazy-recursion=yes + +################################# +# load-modules Load this module - supply absolute or relative path +# +# load-modules= + +################################# +# local-address Local IP address to which we bind +# +local-address=,127.0.0.1 + +################################# +# local-ipv6 Local IP address to which we bind +# +# local-ipv6= + +################################# +# local-port The port on which we listen +# +local-port=53 + +################################# +# log-dns-details If PDNS should log failed update requests +# +log-dns-details=yes + +################################# +# log-failed-updates If PDNS should log failed update requests +# +# log-failed-updates= + +################################# +# logfile Logfile to use +# +# logfile=/var/log/pdns.log + +################################# +# logging-facility Log under a specific facility +# +# logging-facility= + +################################# +# loglevel Amount of logging. Higher is more. Do not set below 3 +# +# loglevel=4 + +################################# +# master Act as a master +# +master=yes + +################################# +# max-queue-length Maximum queuelength before considering situation lost +# +# max-queue-length=5000 + +################################# +# max-tcp-connections Maximum number of TCP connections +# +# max-tcp-connections=10 + +################################# +# module-dir Default directory for modules +# +module-dir=/usr/lib/powerdns + +################################# +# negquery-cache-ttl Seconds to store packets in the PacketCache +# +# negquery-cache-ttl=60 + +################################# +# out-of-zone-additional-processing Do out of zone additional processing +# +# out-of-zone-additional-processing=no + +################################# +# query-cache-ttl Seconds to store packets in the PacketCache +# +# query-cache-ttl=20 + +################################# +# query-logging Hint backends that queries should be logged +# +# query-logging=no + +################################# +# queue-limit Maximum number of milliseconds to queue a query +# +# queue-limit=1500 + +################################# +# query-local-address The IP address to use as a source address for sending +# queries. +# query-local-address= + +################################# +# receiver-threads Number of receiver threads to launch +# +# receiver-threads=1 + +################################# +# recursive-cache-ttl Seconds to store packets in the PacketCache +# +# recursive-cache-ttl=10 + +################################# +# recursor If recursion is desired, IP address of a recursing nameserver +# +# recursor= + +################################# +# setgid If set, change group id to this gid for more security +# +setgid=pdns + +################################# +# setuid If set, change user id to this uid for more security +# +setuid=pdns + +################################# +# skip-cname Do not perform CNAME indirection for each query +# +# skip-cname=no + +################################# +# slave Act as a slave +# +# slave=no + +################################# +# slave-cycle-interval Reschedule failed SOA serial checks once every .. seconds +# +# slave-cycle-interval=60 + +################################# +# smtpredirector Our smtpredir MX host +# +# smtpredirector=a.misconfigured.powerdns.smtp.server + +################################# +# soa-minimum-ttl Default SOA mininum ttl +# +# soa-minimum-ttl=3600 + +################################# +# soa-refresh-default Default SOA refresh +# +# soa-refresh-default=10800 + +################################# +# soa-retry-default Default SOA retry +# +# soa-retry-default=3600 + +################################# +# soa-expire-default Default SOA expire +# +# soa-expire-default=604800 + +################################# +# soa-serial-offset Make sure that no SOA serial is less than this number +# +# soa-serial-offset=0 + +################################# +# socket-dir Where the controlsocket will live +# +socket-dir=/var/run + +################################# +# strict-rfc-axfrs Perform strictly rfc compliant axfrs (very slow) +# +# strict-rfc-axfrs=no + +################################# +# urlredirector Where we send hosts to that need to be url redirected +# +# urlredirector=127.0.0.1 + +################################# +# use-logfile Use a log file +# +# use-logfile=yes + +################################# +# webserver Start a webserver for monitoring +# +# webserver=no + +################################# +# webserver-address IP Address of webserver to listen on +# +# webserver-address=127.0.0.1 + +################################# +# webserver-password Password required for accessing the webserver +# +# webserver-password= + +################################# +# webserver-port Port of webserver to listen on +# +# webserver-port=8081 + +################################# +# webserver-print-arguments If the webserver should print arguments +# +# webserver-print-arguments=no + +################################# +# wildcard-url Process URL and MBOXFW records +# +# wildcard-url=no + +################################# +# wildcards Honor wildcards in the database +# +# wildcards= + +################################# +# version-string What should PowerDNS return for version +# allowed methods are anonymous / powerdns / full / custom +version-string=powerdns + +include-dir=/etc/powerdns/froxlor +]]> + + + + + + + + + + _logger->logAction(CRON_ACTION, LOG_ERROR, 'PowerDNS configuration file not found. Did you go through the configuration templates?'); - die('PowerDNS configuration file not found. Did you go through the configuration templates?'); + $this->_logger->logAction(CRON_ACTION, LOG_ERROR, 'PowerDNS configuration file ('.$config.') not found. Did you go through the configuration templates?'); + die('PowerDNS configuration file ('.$config.') not found. Did you go through the configuration templates?'.PHP_EOL); } $lines = file($config); $mysql_data = array(); From 5c1079e04bf01027593ecd3a1637d916c8e92fac Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 19 May 2016 12:09:04 +0200 Subject: [PATCH 38/43] Add change_date value to the pdns-record entries Signed-off-by: Michael Kaufmann (d00p) --- scripts/jobs/cron_tasks.inc.dns.20.pdns.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/jobs/cron_tasks.inc.dns.20.pdns.php b/scripts/jobs/cron_tasks.inc.dns.20.pdns.php index b6b609ed..bf892554 100644 --- a/scripts/jobs/cron_tasks.inc.dns.20.pdns.php +++ b/scripts/jobs/cron_tasks.inc.dns.20.pdns.php @@ -90,7 +90,8 @@ class pdns extends DnsBase `content` = :content, `ttl` = :ttl, `prio` = :prio, - `disabled` = '0' + `disabled` = '0', + `change_date` = UNIX_TIMESTAMP() "); foreach ($records as $record) From c2b0714b4a38b3f5ba6d9a5dc37b8bef1bfced82 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Thu, 19 May 2016 13:49:22 +0200 Subject: [PATCH 39/43] powerdns needs the 'whole' record (., e.g. sub.example.com) Signed-off-by: Michael Kaufmann (d00p) --- scripts/jobs/cron_tasks.inc.dns.20.pdns.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/jobs/cron_tasks.inc.dns.20.pdns.php b/scripts/jobs/cron_tasks.inc.dns.20.pdns.php index bf892554..3847657d 100644 --- a/scripts/jobs/cron_tasks.inc.dns.20.pdns.php +++ b/scripts/jobs/cron_tasks.inc.dns.20.pdns.php @@ -47,7 +47,7 @@ class pdns extends DnsBase $zone = createDomainZone(($domain['id'] == 'none') ? $domain : $domain['id'], $isFroxlorHostname); $dom_id = $this->_insertZone($zone->origin, $zone->serial); - $this->_insertRecords($dom_id, $zone->records); + $this->_insertRecords($dom_id, $zone->records, $zone->origin); $this->_insertAllowedTransfers($dom_id); $this->_logger->logAction(CRON_ACTION, LOG_INFO, '`' . $domain['domain'] . '` zone written'); @@ -80,7 +80,7 @@ class pdns extends DnsBase return $lastid; } - private function _insertRecords($domainid = 0, $records) + private function _insertRecords($domainid = 0, $records, $origin) { $ins_stmt = $this->pdns_db->prepare(" INSERT INTO records set @@ -96,9 +96,17 @@ class pdns extends DnsBase foreach ($records as $record) { + if ($record->record == '@') { + $_record = $origin; + } + else + { + $_record = $record->record.".".$origin; + } + $ins_data = array( 'did' => $domainid, - 'rec' => $record->record, + 'rec' => $_record, 'type' => $record->type, 'content' => $record->content, 'ttl' => $record->ttl, From 61eab6fd9370eb916b0b38e6ac2eb86b994c97e1 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 20 May 2016 15:56:06 +0200 Subject: [PATCH 40/43] do not validate fqdn of SRV and TXT entries, as they might use underscores Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/dns_editor.php b/dns_editor.php index 222a412b..adebd536 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -51,22 +51,19 @@ if ($action == 'add_record' && ! empty($_POST)) { $record = strtolower($record); - if ($record != '@' && $record != '*') - { + if ($record != '@' && $record != '*') { // validate record if (strpos($record, '--') !== false) { $errors[] = $lng['error']['domain_nopunycode']; - } - else - { + } else { $record = $idna_convert->encode($record); - $check_dom = $record.'.example.com'; - if (!validateDomain($check_dom)) - { - $errors[] = sprintf($lng['error']['subdomainiswrong'], $idna_convert->decode($record)); + if ($type != 'SRV' && $type != 'TXT') { + $check_dom = $record . '.example.com'; + if (! validateDomain($check_dom)) { + $errors[] = sprintf($lng['error']['subdomainiswrong'], $idna_convert->decode($record)); + } } - if (strlen($record) > 63) - { + if (strlen($record) > 63) { $errors[] = $lng['error']['dns_record_toolong']; } } @@ -301,5 +298,5 @@ foreach ($type_select_values as $_type) { eval("\$record_list=\"" . getTemplate("dns_editor/list", true) . "\";"); $zone = createDomainZone($domain_id); -$zonefile = (string)$zone; +$zonefile = (string) $zone; eval("echo \"" . getTemplate("dns_editor/index", true) . "\";"); From be373e278fcff764e4b99d4b6f65e33344a37cdd Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 20 May 2016 16:17:33 +0200 Subject: [PATCH 41/43] allow defined non-existing entry for SRV target-value Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/dns_editor.php b/dns_editor.php index adebd536..325fe1d7 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -144,11 +144,6 @@ if ($action == 'add_record' && ! empty($_POST)) { if ($prio === null || $prio < 0) { $errors[] = $lng['error']['dns_srv_prioempty']; } - // check for trailing dot - if (substr($content, - 1) == '.') { - // remove it for checks - $content = substr($content, 0, - 1); - } // check only last part of content, as it can look like: // _service._proto.name. TTL class SRV priority weight port target. $_split_content = explode(" ", $content); @@ -157,6 +152,13 @@ if ($action == 'add_record' && ! empty($_POST)) { $errors[] = $lng['error']['dns_srv_invalidcontent']; } $target = trim($_split_content[count($_split_content) - 1]); + if ($target != '.') { + // check for trailing dot + if (substr($target, - 1) == '.') { + // remove it for checks + $target = substr($target, 0, - 1); + } + } if (! validateDomain($target)) { $errors[] = $lng['error']['dns_srv_needdom']; } else { @@ -170,7 +172,9 @@ if ($action == 'add_record' && ! empty($_POST)) { } } // append trailing dot (again) - $content .= '.'; + if ($target != '.') { + $content .= '.'; + } } $new_entry = array( From 7ea1de2a9221d2c30d19e492fe8a456d56a652d8 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Fri, 20 May 2016 16:21:55 +0200 Subject: [PATCH 42/43] allow defined non-existing entry for SRV target-value for real now, i guess Signed-off-by: Michael Kaufmann (d00p) --- dns_editor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dns_editor.php b/dns_editor.php index 325fe1d7..15687b10 100644 --- a/dns_editor.php +++ b/dns_editor.php @@ -159,7 +159,7 @@ if ($action == 'add_record' && ! empty($_POST)) { $target = substr($target, 0, - 1); } } - if (! validateDomain($target)) { + if ($target != '.' && ! validateDomain($target)) { $errors[] = $lng['error']['dns_srv_needdom']; } else { // check whether there is a CNAME-record for the same resource From 339d84736e5f353473c1119c50e5e38d3a054f57 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Sat, 21 May 2016 09:03:21 +0200 Subject: [PATCH 43/43] no quotation of dns data for powerdns Signed-off-by: Michael Kaufmann (d00p) --- lib/functions/dns/function.createDomainZone.php | 11 ++++++++++- .../dns/function.generateDkimEntries.php | 16 ++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/functions/dns/function.createDomainZone.php b/lib/functions/dns/function.createDomainZone.php index f7baed6e..424c6ceb 100644 --- a/lib/functions/dns/function.createDomainZone.php +++ b/lib/functions/dns/function.createDomainZone.php @@ -292,7 +292,7 @@ function addRequiredEntry($record = '@', $type = 'A', &$required) function encloseTXTContent($txt_content, $isMultiLine = false) { // check that TXT content is enclosed in " " - if ($isMultiLine == false) { + if ($isMultiLine == false && Settings::Get('system.dns_server') != 'pdns') { if (substr($txt_content, 0, 1) != '"') { $txt_content = '"' . $txt_content; } @@ -300,6 +300,15 @@ function encloseTXTContent($txt_content, $isMultiLine = false) $txt_content .= '"'; } } + if (Settings::Get('system.dns_server') == 'pdns') { + // no quotation for PowerDNS + if (substr($txt_content, 0, 1) == '"') { + $txt_content = substr($txt_content, 1); + } + if (substr($txt_content, - 1) == '"') { + $txt_content = substr($txt_content, 0, -1); + } + } return $txt_content; } diff --git a/lib/functions/dns/function.generateDkimEntries.php b/lib/functions/dns/function.generateDkimEntries.php index e3d5e95a..622af765 100644 --- a/lib/functions/dns/function.generateDkimEntries.php +++ b/lib/functions/dns/function.generateDkimEntries.php @@ -14,7 +14,6 @@ * @package Functions * */ - function generateDkimEntries($domain) { $zone_dkim = array(); @@ -55,11 +54,16 @@ function generateDkimEntries($domain) // end-part $dkim_txt .= 't=s'; - // split if necessary - $txt_record_split = ''; - $lbr = 50; - for ($pos = 0; $pos <= strlen($dkim_txt) - 1; $pos += $lbr) { - $txt_record_split .= (($pos == 0) ? '("' : "\t\t\t\t\t \"") . substr($dkim_txt, $pos, $lbr) . (($pos >= strlen($dkim_txt) - $lbr) ? '")' : '"') . "\n"; + if (Settings::Get('system.dns_server') == 'pdns') { + // PowerDNS does not need/want splitted content + $txt_record_split = $dkim_txt; + } else { + // split if necessary + $txt_record_split = ''; + $lbr = 50; + for ($pos = 0; $pos <= strlen($dkim_txt) - 1; $pos += $lbr) { + $txt_record_split .= (($pos == 0) ? '("' : "\t\t\t\t\t \"") . substr($dkim_txt, $pos, $lbr) . (($pos >= strlen($dkim_txt) - $lbr) ? '")' : '"') . "\n"; + } } // dkim-entry