diff --git a/.gitignore b/.gitignore
index 8c981d1d..3d547882 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
install/update.log
templates/*
lib/userdata.inc.php
+lib/userdata.inc.php.bak
logs/*
!logs/index.html
.buildpath
diff --git a/actions/admin/settings/160.nameserver.php b/actions/admin/settings/160.nameserver.php
index a7df3322..67260bfa 100644
--- a/actions/admin/settings/160.nameserver.php
+++ b/actions/admin/settings/160.nameserver.php
@@ -107,6 +107,22 @@ return array(
'default' => false,
'save_method' => 'storeSettingField'
),
+ 'system_dns_createcaaentry' => array(
+ 'label' => $lng['serversettings']['caa_entry'],
+ 'settinggroup' => 'system',
+ 'varname' => 'dns_createcaaentry',
+ 'type' => 'bool',
+ 'default' => true,
+ 'save_method' => 'storeSettingField'
+ ),
+ 'caa_caa_entry' => array(
+ 'label' => $lng['serversettings']['caa_entry_custom'],
+ 'settinggroup' => 'caa',
+ 'varname' => 'caa_entry',
+ 'type' => 'text',
+ 'default' => '',
+ 'save_method' => 'storeSettingField'
+ ),
'system_defaultttl' => array(
'label' => $lng['serversettings']['defaultttl'],
'settinggroup' => 'system',
diff --git a/dns_editor.php b/dns_editor.php
index c1d28546..77f121ef 100644
--- a/dns_editor.php
+++ b/dns_editor.php
@@ -108,11 +108,16 @@ if (! empty($dom_entries)) {
$type_select_values = array(
'A',
'AAAA',
- 'NS',
+ 'CAA',
+ 'CNAME',
+ 'DNAME',
+ 'LOC',
'MX',
+ 'NS',
+ 'RP',
'SRV',
+ 'SSHFP',
'TXT',
- 'CNAME'
);
asort($type_select_values);
foreach ($type_select_values as $_type) {
diff --git a/install/froxlor.sql b/install/froxlor.sql
index ec917791..51ffe079 100644
--- a/install/froxlor.sql
+++ b/install/froxlor.sql
@@ -375,6 +375,7 @@ INSERT INTO `panel_settings` (`settinggroup`, `varname`, `value`) VALUES
('admin', 'show_news_feed', '0'),
('admin', 'show_version_login', '0'),
('admin', 'show_version_footer', '0'),
+ ('caa', 'caa_entry', ''),
('spf', 'use_spf', '0'),
('spf', 'spf_entry', '"v=spf1 a mx -all"'),
('dkim', 'dkim_algorithm', 'all'),
@@ -561,6 +562,7 @@ opcache.interned_strings_buffer'),
('system', 'mod_fcgid_defaultini', '1'),
('system', 'ftpserver', 'proftpd'),
('system', 'dns_createmailentry', '0'),
+ ('system', 'dns_createcaaentry', '1'),
('system', 'froxlordirectlyviahostname', '0'),
('system', 'report_enable', '1'),
('system', 'report_webmax', '90'),
@@ -681,7 +683,7 @@ opcache.interned_strings_buffer'),
('panel', 'customer_hide_options', ''),
('panel', 'is_configured', '0'),
('panel', 'version', '0.10.0-rc2'),
- ('panel', 'db_version', '201904250');
+ ('panel', 'db_version', '201907270');
DROP TABLE IF EXISTS `panel_tasks`;
diff --git a/install/updates/froxlor/0.10/update_0.10.inc.php b/install/updates/froxlor/0.10/update_0.10.inc.php
index 030b82a8..bac449ea 100644
--- a/install/updates/froxlor/0.10/update_0.10.inc.php
+++ b/install/updates/froxlor/0.10/update_0.10.inc.php
@@ -263,3 +263,13 @@ if (\Froxlor\Froxlor::isDatabaseVersion('201904100')) {
if (\Froxlor\Froxlor::isFroxlorVersion('0.10.0-rc1')) {
\Froxlor\Froxlor::updateToVersion('0.10.0-rc2');
}
+
+if (\Froxlor\Froxlor::isDatabaseVersion('201904250')) {
+
+ showUpdateStep("Adding new settings for CAA");
+ Settings::AddNew('caa.caa_entry', '', true);
+ Settings::AddNew('system.dns_createcaaentry', 1, true);
+ lastStepStatus(0);
+
+ \Froxlor\Froxlor::updateToDbVersion('201907270');
+}
\ No newline at end of file
diff --git a/lib/Froxlor/Api/Commands/DomainZones.php b/lib/Froxlor/Api/Commands/DomainZones.php
index ccdcad79..276dc571 100644
--- a/lib/Froxlor/Api/Commands/DomainZones.php
+++ b/lib/Froxlor/Api/Commands/DomainZones.php
@@ -138,6 +138,43 @@ class DomainZones extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Resour
$errors[] = $this->lng['error']['dns_arec_noipv4'];
} elseif ($type == 'AAAA' && filter_var($content, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
$errors[] = $this->lng['error']['dns_aaaarec_noipv6'];
+ } elseif ($type == 'CAA' && ! empty($content)) {
+ $re = '/(?\'critical\'\d)\h*(?\'type\'iodef|issue|issuewild)\h*(?\'value\'(?\'issuevalue\'"(?\'domain\'(?=.{3,128}$)(?>(?>[a-zA-Z0-9]+[a-zA-Z0-9-]*[a-zA-Z0-9]+|[a-zA-Z0-9]+)\.)*(?>[a-zA-Z]{2,}|[a-zA-Z0-9]{2,}\.[a-zA-Z]{2,}))[;\h]*(?\'parameters\'(?>[a-zA-Z0-9]{1,60}=[a-zA-Z0-9]{1,60}\h*)+)?")|(?\'iodefvalue\'"(?\'url\'(mailto:.*|http:\/\/.*|https:\/\/.*))"))/';
+ preg_match($re, $content, $matches);
+
+ if (empty($matches)) {
+ $errors[] = $this->lng['error']['dns_content_invalid'];
+ } elseif (($matches['type'] == 'issue' || $matches['type'] == 'issuewild') && !\Froxlor\Validate\Validate::validateDomain($matches['domain'])) {
+ $errors[] = $this->lng['error']['dns_content_invalid'];
+ } elseif ($matches['type'] == 'iodef' && !\Froxlor\Validate\Validate::validateUrl($matches['url'])) {
+ $errors[] = $this->lng['error']['dns_content_invalid'];
+ } else {
+ $content = $matches[0];
+ }
+ } elseif ($type == 'CNAME' || $type == 'DNAME') {
+ // check for trailing dot
+ if (substr($content, - 1) == '.') {
+ // remove it for checks
+ $content = substr($content, 0, - 1);
+ } else {
+ // add domain name
+ $content .= '.' . $domain;
+ }
+ if (! \Froxlor\Validate\Validate::validateDomain($content, true)) {
+ $errors[] = $this->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[] = $this->lng['error']['dns_cname_nomorerr'];
+ break;
+ }
+ }
+ }
+ // append trailing dot (again)
+ $content .= '.';
+ } elseif ($type == 'LOC' && ! empty($content)) {
+ $content = $content;
} elseif ($type == 'MX') {
if ($prio === null || $prio < 0) {
$errors[] = $this->lng['error']['dns_mx_prioempty'];
@@ -161,28 +198,6 @@ class DomainZones extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Resour
}
// append trailing dot (again)
$content .= '.';
- } elseif ($type == 'CNAME') {
- // check for trailing dot
- if (substr($content, - 1) == '.') {
- // remove it for checks
- $content = substr($content, 0, - 1);
- } else {
- // add domain name
- $content .= '.' . $domain;
- }
- if (! \Froxlor\Validate\Validate::validateDomain($content, true)) {
- $errors[] = $this->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[] = $this->lng['error']['dns_cname_nomorerr'];
- break;
- }
- }
- }
- // append trailing dot (again)
- $content .= '.';
} elseif ($type == 'NS') {
// check for trailing dot
if (substr($content, - 1) == '.') {
@@ -194,9 +209,8 @@ class DomainZones extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Resour
}
// append trailing dot (again)
$content .= '.';
- } elseif ($type == 'TXT' && ! empty($content)) {
- // check that TXT content is enclosed in " "
- $content = \Froxlor\Dns\Dns::encloseTXTContent($content);
+ } elseif ($type == 'RP' && ! empty($content)) {
+ $content = $content;
} elseif ($type == 'SRV') {
if ($prio === null || $prio < 0) {
$errors[] = $this->lng['error']['dns_srv_prioempty'];
@@ -232,6 +246,11 @@ class DomainZones extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Resour
if (substr($content, - 1) != '.') {
$content .= '.';
}
+ } elseif ($type == 'SSHFP' && ! empty($content)) {
+ $content = $content;
+ } elseif ($type == 'TXT' && ! empty($content)) {
+ // check that TXT content is enclosed in " "
+ $content = \Froxlor\Dns\Dns::encloseTXTContent($content);
}
$new_entry = array(
diff --git a/lib/Froxlor/Dns/Dns.php b/lib/Froxlor/Dns/Dns.php
index cfc77e93..2bf9c371 100644
--- a/lib/Froxlor/Dns/Dns.php
+++ b/lib/Froxlor/Dns/Dns.php
@@ -130,6 +130,12 @@ class Dns
}
}
+ // additional required records for CAA if activated
+ if (Settings::Get('system.dns_createcaaentry') && Settings::Get('system.use_ssl') == "1" && !empty($domain['p_ssl_ipandports'])) {
+ // check for CAA content later
+ self::addRequiredEntry('@CAA@', 'CAA', $required_entries);
+ }
+
// additional required records for SPF and DKIM if activated
if ($domain['isemaildomain'] == '1') {
if (Settings::Get('spf.use_spf') == '1') {
@@ -150,6 +156,10 @@ class Dns
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 (Settings::Get('system.dns_createcaaentry') == '1' && $entry['type'] == 'CAA' && strtolower(substr($entry['content'], 0, 7)) == '"v=caa1') {
+ // unset special CAA required-entry
+ unset($required_entries[$entry['type']][md5("@CAA@")]);
+ }
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@")]);
@@ -278,6 +288,31 @@ class Dns
}
}
}
+
+ // CAA
+ if (array_key_exists("CAA", $required_entries)) {
+ foreach ($required_entries as $type => $records) {
+ if ($type == 'CAA') {
+ foreach ($records as $record) {
+ if ($record == '@CAA@') {
+ $caa_entries = explode(PHP_EOL, Settings::Get('caa.caa_entry'));
+ if ($domain['letsencrypt'] == 1) {
+ $le_entry = $domain['iswildcarddomain'] == '1' ? '0 issuewild "letsencrypt.org"' : '0 issue "letsencrypt.org"';
+ array_push($caa_entries, $le_entry);
+ }
+
+ foreach ($caa_entries as $entry) {
+ $zonerecords[] = new DnsEntry('@', 'CAA', $entry);
+ // additional required records by subdomain setting
+ if ($domain['wwwserveralias'] == '1') {
+ $zonerecords[] = new DnsEntry('www', 'CAA', $entry);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
if (empty($primary_ns)) {
diff --git a/lib/Froxlor/Froxlor.php b/lib/Froxlor/Froxlor.php
index 53e32359..eff34ca5 100644
--- a/lib/Froxlor/Froxlor.php
+++ b/lib/Froxlor/Froxlor.php
@@ -10,7 +10,7 @@ final class Froxlor
const VERSION = '0.10.0-rc2';
// Database version (YYYYMMDDC where C is a daily counter)
- const DBVERSION = '201904250';
+ const DBVERSION = '201907270';
// Distribution branding-tag (used for Debian etc.)
const BRANDING = '';
diff --git a/lng/english.lng.php b/lng/english.lng.php
index e76477c5..aad8a566 100644
--- a/lng/english.lng.php
+++ b/lng/english.lng.php
@@ -1848,6 +1848,12 @@ $lng['serversettings']['leenabled']['title'] = "Enable Let's Encrypt";
$lng['serversettings']['leenabled']['description'] = "If activated, customers are able to let froxlor automatically generate and renew Let's Encrypt ssl-certificates for domains with a ssl IP/port.
Please remember that you need to go through the webserver-configuration when enabled because this feature needs a special configuration.";
$lng['domains']['ssl_redirect_temporarilydisabled'] = "
The SSL redirect is temporarily deactivated while a new Let's Encrypt certificate is generated. It will be activated again after the certificate was generated.";
+// Added for CAA record support
+$lng['serversettings']['caa_entry']['title'] = 'Generate CAA DNS records';
+$lng['serversettings']['caa_entry']['description'] = 'Automatically generates CAA records for SSL-enabled domains that are using Let\'s Encrypt';
+$lng['serversettings']['caa_custom']['title'] = 'Additional CAA DNS records';
+$lng['serversettings']['caa_custom']['description'] = 'DNS Certification Authority Authorization (CAA) is an Internet security policy mechanism which allows domain name holders to indicate to certificate authorities whether they are authorized to issue digital certificates for a particular domain name. It does this by means of a new "CAA" Domain Name System (DNS) resource record.
The content of this field will be included into the DNS zone directly (each line results in a CAA record). If Let\'s Encrypt is enabled for this domain, this entry will always be added automatically and does not need to be added manually:
0 issue "letsencrypt.org" (If domain is a wildcard domain, issuewild will be used instead).
To enable Incident Reporting, you can add an iodef record. An example for sending such report to me@example.com would be:
0 iodef "mailto:me@example.com"
Attention: The code won\'t be checked for any errors. If it contains errors, your CAA records might not work!';
+
// Autoupdate
$lng['admin']['autoupdate'] = 'Auto-Update';
$lng['error']['customized_version'] = 'It looks like your Froxlor installation has been modified, no support sorry.';
@@ -1886,6 +1892,7 @@ $lng['tasks']['backup_customerfiles'] = 'Backup job for customer %loginname%';
$lng['error']['dns_domain_nodns'] = 'DNS is not enabled for this domain';
$lng['error']['dns_content_empty'] = 'No content given';
+$lng['error']['dns_content_invalid'] = 'DNS content invalid';
$lng['error']['dns_arec_noipv4'] = 'No valid IP address for A-record given';
$lng['error']['dns_aaaarec_noipv6'] = 'No valid IP address for AAAA-record given';
$lng['error']['dns_mx_prioempty'] = 'Invalid MX priority given';
diff --git a/lng/german.lng.php b/lng/german.lng.php
index f1f44c27..9c335afe 100644
--- a/lng/german.lng.php
+++ b/lng/german.lng.php
@@ -1500,6 +1500,12 @@ $lng['serversettings']['leenabled']['title'] = "Let's Encrypt verwenden";
$lng['serversettings']['leenabled']['description'] = "Wenn dies aktiviert ist, können Kunden durch Froxlor automatisch generierte und verlängerbare Let's Encrypt SSL-Zertifikate für Domains mit SSL IP/Port nutzen.
Bitte die Webserver-Konfiguration beachten wenn aktiviert, da dieses Feature eine spezielle Konfiguration benötigt.";
$lng['domains']['ssl_redirect_temporarilydisabled'] = "
Die SSL-Umleitung ist, während ein neues Let's Encrypt - Zertifikat erstellt wird, temporär deaktiviert. Die Umleitung wird nach der Zertifikatserstellung wieder aktiviert.";
+// Added for CAA record support
+$lng['serversettings']['caa_entry']['title'] = 'CAA DNS Einträge generieren';
+$lng['serversettings']['caa_entry']['description'] = 'Generiert CAA Einträge automatisch für alle Domains mit aktiviertem SSL und Let\'s Encrypt';
+$lng['serversettings']['caa_custom']['title'] = 'Zusätzliche CAA DNS Einträge';
+$lng['serversettings']['caa_custom']['description'] = 'DNS Certification Authority Authorization (CAA) verwendet das Domain Name System, um dem Besitzer einer Domain die Möglichkeit zu bieten, gewisse Zertifizierungsstellen (CAs) dazu zu berechtigen, ein Zertifikat für die betroffene Domain auszustellen. CAA Records sollen verhindern, dass Zertifikate fälschlicherweise für eine Domain ausgestellt werden.
Der Inhalt dieses Feldes wird direkt in die DNS Zone übernommen (eine Zeile pro CAA Record). Wenn Let\'s Encrypt für eine Domain aktiviert wurde und die obige Option aktiviert wurde, wird immer automatisch dieser Eintrag angefügt und muss nicht selber angegeben werden:
0 issue "letsencrypt.org" (Wenn wildcard aktiviert ist, wird statdessen issuewild benutzt).
Um Incident Reporting per Mail zu aktivieren, muss eine iodef Zeile angefügt werden. Ein Beispiel für einen Report an me@example.com wäre:
0 iodef "mailto:me@example.com"
ACHTUNG: Der Code wird nicht auf Fehler geprüft. Etwaige Fehler werden also auch übernommen. Die CAA finalen Einträge könnten daher falsch sein!';
+
// Autoupdate
$lng['admin']['autoupdate'] = 'Auto-Update';
$lng['error']['customized_version'] = 'Es scheint als wäre die Froxlor Installation angepasst worden. Kein Support, sorry.';
@@ -1537,6 +1543,7 @@ $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_content_invalid'] = 'DNS Eintrag ungültig';
$lng['error']['dns_arec_noipv4'] = 'Keine gültige IP-Adresse für A-Eintrag angegeben';
$lng['error']['dns_aaaarec_noipv6'] = 'Keine gültige IP-Adresse für AAAA-Eintrag angegeben';
$lng['error']['dns_mx_prioempty'] = 'Ungültige MX Priorität angegeben';
diff --git a/tests/DomainZones/DomainZonesTest.php b/tests/DomainZones/DomainZonesTest.php
index fef9ba2f..c0bd3143 100644
--- a/tests/DomainZones/DomainZonesTest.php
+++ b/tests/DomainZones/DomainZonesTest.php
@@ -277,6 +277,366 @@ class DomainZonesTest extends TestCase
DomainZones::getLocal($admin_userdata, $data)->add();
}
+ public function testAdminDomainZonesAddCAAIssue()
+ {
+ global $admin_userdata;
+
+ $content = '0 issue "letsencrypt.org"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $json_result = DomainZones::getLocal($admin_userdata, $data)->add();
+ $result = json_decode($json_result, true)['data'];
+ $this->assertTrue(count($result) > 1);
+ $found = false;
+ foreach ($result as $entry) {
+ if (substr($entry, -strlen($content)) == $content) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found);
+ $this->assertEquals('@ 18000 IN CAA 0 issue "letsencrypt.org"', $entry);
+ }
+
+ public function testAdminDomainZonesAddCAAIssueWithParameters()
+ {
+ global $admin_userdata;
+
+ $content = '0 issue "letsencrypt.org; account=230123"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $json_result = DomainZones::getLocal($admin_userdata, $data)->add();
+ $result = json_decode($json_result, true)['data'];
+ $this->assertTrue(count($result) > 1);
+ $found = false;
+ foreach ($result as $entry) {
+ if (substr($entry, strlen($content) * - 1) == $content) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found);
+ $this->assertEquals('@ 18000 IN CAA '.$content, $entry);
+ }
+
+ public function testAdminDomainZonesAddCAAIssueWithTwoParameters()
+ {
+ global $admin_userdata;
+
+ $content = '0 issue "letsencrypt.org; account=230123 policy=ev"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $json_result = DomainZones::getLocal($admin_userdata, $data)->add();
+ $result = json_decode($json_result, true)['data'];
+ $this->assertTrue(count($result) > 1);
+ $found = false;
+ foreach ($result as $entry) {
+ if (substr($entry, strlen($content) * - 1) == $content) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found);
+ $this->assertEquals('@ 18000 IN CAA '.$content, $entry);
+ }
+
+ public function testAdminDomainZonesAddCAAInvalidIssueValue()
+ {
+ global $admin_userdata;
+
+ $content = '0 issue ""letsencrypt.org"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $this->expectExceptionMessage("DNS content invalid");
+ DomainZones::getLocal($admin_userdata, $data)->add();
+ }
+
+ public function testAdminDomainZonesAddCAAInvalidIssueDomain()
+ {
+ global $admin_userdata;
+
+ $content = '0 issue "no-valid-domain"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $this->expectExceptionMessage("DNS content invalid");
+ DomainZones::getLocal($admin_userdata, $data)->add();
+ }
+
+ public function testAdminDomainZonesAddCAAInvalidIssueTld()
+ {
+ global $admin_userdata;
+
+ $content = '0 issue "no-valid-domai.n"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $this->expectExceptionMessage("DNS content invalid");
+ DomainZones::getLocal($admin_userdata, $data)->add();
+ }
+
+ public function testAdminDomainZonesAddCAAIssueWild()
+ {
+ global $admin_userdata;
+
+ $content = '0 issuewild "letsencrypt.org"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $json_result = DomainZones::getLocal($admin_userdata, $data)->add();
+ $result = json_decode($json_result, true)['data'];
+ $this->assertTrue(count($result) > 1);
+ $found = false;
+ foreach ($result as $entry) {
+ if (substr($entry, strlen($content) * - 1) == $content) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found);
+ $this->assertEquals('@ 18000 IN CAA '.$content, $entry);
+ }
+
+ public function testAdminDomainZonesAddCAAIssueWildWithParameters()
+ {
+ global $admin_userdata;
+
+ $content = '0 issuewild "letsencrypt.org; account=230123"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $json_result = DomainZones::getLocal($admin_userdata, $data)->add();
+ $result = json_decode($json_result, true)['data'];
+ $this->assertTrue(count($result) > 1);
+ $found = false;
+ foreach ($result as $entry) {
+ if (substr($entry, strlen($content) * - 1) == $content) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found);
+ $this->assertEquals('@ 18000 IN CAA '.$content, $entry);
+ }
+
+ public function testAdminDomainZonesAddCAAIssueWildWithTwoParameters()
+ {
+ global $admin_userdata;
+
+ $content = '0 issuewild "letsencrypt.org; account=230123 policy=ev"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $json_result = DomainZones::getLocal($admin_userdata, $data)->add();
+ $result = json_decode($json_result, true)['data'];
+ $this->assertTrue(count($result) > 1);
+ $found = false;
+ foreach ($result as $entry) {
+ if (substr($entry, strlen($content) * - 1) == $content) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found);
+ $this->assertEquals('@ 18000 IN CAA '.$content, $entry);
+ }
+
+ public function testAdminDomainZonesAddCAAInvalidIssueWildValue()
+ {
+ global $admin_userdata;
+
+ $content = '0 issuewild ""letsencrypt.org"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $this->expectExceptionMessage("DNS content invalid");
+ DomainZones::getLocal($admin_userdata, $data)->add();
+ }
+
+ public function testAdminDomainZonesAddCAAInvalidIssueWildDomain()
+ {
+ global $admin_userdata;
+
+ $content = '0 issuewild "no-valid-domain"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $this->expectExceptionMessage("DNS content invalid");
+ DomainZones::getLocal($admin_userdata, $data)->add();
+ }
+
+ public function testAdminDomainZonesAddCAAInvalidIssueWildTld()
+ {
+ global $admin_userdata;
+
+ $content = '0 issuewild "no-valid-domai.n"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $this->expectExceptionMessage("DNS content invalid");
+ DomainZones::getLocal($admin_userdata, $data)->add();
+ }
+
+ public function testAdminDomainZonesAddCAAIodefMail()
+ {
+ global $admin_userdata;
+
+ $content = '0 iodef "mailto:security@example.com"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $json_result = DomainZones::getLocal($admin_userdata, $data)->add();
+ $result = json_decode($json_result, true)['data'];
+ $this->assertTrue(count($result) > 1);
+ $found = false;
+ foreach ($result as $entry) {
+ if (substr($entry, strlen($content) * - 1) == $content) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found);
+ $this->assertEquals('@ 18000 IN CAA '.$content, $entry);
+ }
+
+ public function testAdminDomainZonesAddCAAIodefMailInvalid()
+ {
+ global $admin_userdata;
+
+ $content = '0 iodef "mailtosecurity@example.com"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $this->expectExceptionMessage("DNS content invalid");
+ DomainZones::getLocal($admin_userdata, $data)->add();
+ }
+
+ public function testAdminDomainZonesAddCAAIodefHttp()
+ {
+ global $admin_userdata;
+
+ $content = '0 iodef "http://iodef.example.com/"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $json_result = DomainZones::getLocal($admin_userdata, $data)->add();
+ $result = json_decode($json_result, true)['data'];
+ $this->assertTrue(count($result) > 1);
+ $found = false;
+ foreach ($result as $entry) {
+ if (substr($entry, strlen($content) * - 1) == $content) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found);
+ $this->assertEquals('@ 18000 IN CAA '.$content, $entry);
+ }
+
+ public function testAdminDomainZonesAddCAAIodefHttpInvalid()
+ {
+ global $admin_userdata;
+
+ $content = '0 iodef "http:/iodef.example.com/"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $this->expectExceptionMessage("DNS content invalid");
+ DomainZones::getLocal($admin_userdata, $data)->add();
+ }
+
+ public function testAdminDomainZonesAddCAAIodefHttps()
+ {
+ global $admin_userdata;
+
+ $content = '0 iodef "https://iodef.example.com/"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $json_result = DomainZones::getLocal($admin_userdata, $data)->add();
+ $result = json_decode($json_result, true)['data'];
+ $this->assertTrue(count($result) > 1);
+ $found = false;
+ foreach ($result as $entry) {
+ if (substr($entry, strlen($content) * - 1) == $content) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found);
+ $this->assertEquals('@ 18000 IN CAA '.$content, $entry);
+ }
+
+ public function testAdminDomainZonesAddCAAIodefHttpsInvalid()
+ {
+ global $admin_userdata;
+
+ $content = '0 iodef "https:/iodef.example.com/"';
+ $data = [
+ 'domainname' => 'test2.local',
+ 'record' => '@',
+ 'type' => 'CAA',
+ 'content' => $content,
+ ];
+ $this->expectExceptionMessage("DNS content invalid");
+ DomainZones::getLocal($admin_userdata, $data)->add();
+ }
+
public function testAdminDomainZonesAddCname()
{
global $admin_userdata;