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(); }