From 14d2b3d7632881d1cb208dce7641a5a153bd2fcf Mon Sep 17 00:00:00 2001 From: Michael Kuron Date: Sun, 9 Jul 2017 10:01:27 +0200 Subject: [PATCH 1/3] DNS diagnostics page --- data/web/inc/header.inc.php | 5 + data/web/lang/lang.de.php | 7 + data/web/lang/lang.en.php | 7 + diagnostics.php | 272 ++++++++++++++++++++++++++++++++++++ 4 files changed, 291 insertions(+) create mode 100644 diagnostics.php diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php index 533711de..8e5b6d07 100644 --- a/data/web/inc/header.inc.php +++ b/data/web/inc/header.inc.php @@ -77,6 +77,11 @@ > + > + > diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php index eef13b3f..732171cc 100644 --- a/data/web/lang/lang.de.php +++ b/data/web/lang/lang.de.php @@ -214,6 +214,7 @@ $lang['header']['mailcow_settings'] = 'Konfiguration'; $lang['header']['administration'] = 'Administration'; $lang['header']['mailboxes'] = 'Mailboxen'; $lang['header']['user_settings'] = 'Benutzereinstellungen'; +$lang['header']['diagnostics'] = 'Diagnose'; $lang['header']['login'] = 'Anmeldung'; $lang['header']['logged_in_as_logout'] = 'Eingeloggt als %s (abmelden)'; $lang['header']['logged_in_as_logout_dual'] = 'Eingeloggt als %s [%s]'; @@ -495,3 +496,9 @@ $lang['admin']['add_forwarding_host'] = 'Weiterleitungs-Host hinzufügen'; $lang['delete']['remove_forwardinghost_warning'] = 'Warnung: Sie entfernen den Weiterleitungs-Host %s!'; $lang['success']['forwarding_host_removed'] = "Weiterleitungs-Host %s wurde entfernt"; $lang['success']['forwarding_host_added'] = "Weiterleitungs-Host %s wurde hinzugefügt"; +$lang['diagnostics']['dns_records'] = 'DNS-Einträge'; +$lang['diagnostics']['dns_records_24hours'] = 'Bitte beachten Sie, dass es bis zu 24 Stunden dauern kann, bis Änderungen an Ihren DNS-Einträgen als aktueller Status auf dieser Seite dargestellt werden. Diese Seite ist nur als Hilfsmittel gedacht, um die korrekten Werte für DNS-Einträge zu anzuzeigen und zu überprüfen, ob die Daten im DNS hinterlegt sind.'; +$lang['diagnostics']['dns_records_name'] = 'Name'; +$lang['diagnostics']['dns_records_type'] = 'Typ'; +$lang['diagnostics']['dns_records_data'] = 'Korrekte Daten'; +$lang['diagnostics']['dns_records_status'] = 'Aktueller Status'; diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index 3be72fec..391c7d01 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -216,6 +216,7 @@ $lang['header']['mailcow_settings'] = 'Configuration'; $lang['header']['administration'] = 'Administration'; $lang['header']['mailboxes'] = 'Mailboxes'; $lang['header']['user_settings'] = 'User settings'; +$lang['header']['diagnostics'] = 'Diagnostics'; $lang['header']['login'] = 'Login'; $lang['header']['logged_in_as_logout'] = 'Logged in as %s (logout)'; $lang['header']['logged_in_as_logout_dual'] = 'Logged in as %s [%s]'; @@ -508,3 +509,9 @@ $lang['admin']['add_forwarding_host'] = 'Add Forwarding Host'; $lang['delete']['remove_forwardinghost_warning'] = 'Warning: You are about to remove the forwarding host %s!'; $lang['success']['forwarding_host_removed'] = "Forwarding host %s has been removed"; $lang['success']['forwarding_host_added'] = "Forwarding host %s has been added"; +$lang['diagnostics']['dns_records'] = 'DNS Records'; +$lang['diagnostics']['dns_records_24hours'] = 'Please note that changes made to DNS may take up to 24 hours to correctly have their current state reflected on this page. It is intended as a way for you to easily see how to configure your DNS records and to check whether all your records are correctly stored in DNS.'; +$lang['diagnostics']['dns_records_name'] = 'Name'; +$lang['diagnostics']['dns_records_type'] = 'Type'; +$lang['diagnostics']['dns_records_data'] = 'Correct Data'; +$lang['diagnostics']['dns_records_status'] = 'Current State'; diff --git a/diagnostics.php b/diagnostics.php new file mode 100644 index 00000000..4de0082e --- /dev/null +++ b/diagnostics.php @@ -0,0 +1,272 @@ + 1) { + $mask = $net[1]; + } + $net = inet_pton($net[0]); + $addr = inet_pton($addr); + $length = strlen($net); // 4 for IPv4, 16 for IPv6 + if (strlen($net) != strlen($addr)) { + return false; + } + if (!isset($mask)) { + $mask = $length * 8; + } + $addr_bin = ''; + $net_bin = ''; + for ($i = 0; $i < $length; ++$i) { + $addr_bin .= str_pad(decbin(ord(substr($addr, $i, $i+1))), 8, '0', STR_PAD_LEFT); + $net_bin .= str_pad(decbin(ord(substr($net, $i, $i+1))), 8, '0', STR_PAD_LEFT); + } + return substr($addr_bin, 0, $mask) == substr($net_bin, 0, $mask); +} + +if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") { +require_once("inc/header.inc.php"); + +$ch = curl_init('http://ipv4.mailcow.email'); +curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); +curl_setopt($ch, CURLOPT_VERBOSE, false); +curl_setopt($ch, CURLOPT_HEADER, false); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); +curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); +$ip = curl_exec($ch); +curl_close($ch); + +$ch = curl_init('http://ipv6.mailcow.email'); +curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); +curl_setopt($ch, CURLOPT_VERBOSE, false); +curl_setopt($ch, CURLOPT_HEADER, false); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); +curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); +$ip6 = curl_exec($ch); +curl_close($ch); + +$ptr = implode('.', array_reverse(explode('.', $ip))) . '.in-addr.arpa'; +if (!empty($ip6)) { + $ip6_full = str_replace('::', str_repeat(':', 9-substr_count($ip6, ':')), $ip6); + $ip6_full = str_replace('::', ':0:', $ip6_full); + $ip6_full = str_replace('::', ':0:', $ip6_full); + $ptr6 = ''; + foreach (explode(':', $ip6_full) as $part) { + $ptr6 .= str_pad($part, 4, '0', STR_PAD_LEFT); + } + $ptr6 = implode('.', array_reverse(str_split($ptr6, 1))) . '.ip6.arpa'; +} + +$https_port = strpos($_SERVER['HTTP_HOST'], ':'); +if ($https_port === FALSE) { + $https_port = 443; +} else { + $https_port = substr($_SERVER['HTTP_HOST'], $https_port+1); +} + +$records = array(); +$records[] = array($mailcow_hostname, 'A', $ip); +$records[] = array($ptr, 'PTR', $mailcow_hostname); +if (!empty($ip6)) { + $records[] = array($mailcow_hostname, 'AAAA', $ip6); + $records[] = array($ptr6, 'PTR', $mailcow_hostname); +} +$domains = mailbox('get', 'domains'); +foreach(mailbox('get', 'domains') as $domain) { + $domains = array_merge($domains, mailbox('get', 'alias_domains', $domain)); +} + +if (!isset($autodiscover_config['sieve'])) { + $autodiscover_config['sieve'] = array('server' => $mailcow_hostname, 'port' => array_pop(explode(':', getenv('SIEVE_PORT')))); +} + +$records[] = array('_25._tcp.' . $autodiscover_config['smtp']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['smtp']['server'], 25, 1)); +$records[] = array('_' . $https_port . '._tcp.' . $mailcow_hostname, 'TLSA', generate_tlsa_digest($mailcow_hostname, $https_port)); +$records[] = array('_' . $autodiscover_config['pop3']['tlsport'] . '._tcp.' . $autodiscover_config['pop3']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['tlsport'], 1)); +$records[] = array('_' . $autodiscover_config['imap']['tlsport'] . '._tcp.' . $autodiscover_config['imap']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['tlsport'], 1)); +$records[] = array('_' . $autodiscover_config['smtp']['port'] . '._tcp.' . $autodiscover_config['smtp']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['port'])); +$records[] = array('_' . $autodiscover_config['smtp']['tlsport'] . '._tcp.' . $autodiscover_config['smtp']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['tlsport'], 1)); +$records[] = array('_' . $autodiscover_config['imap']['port'] . '._tcp.' . $autodiscover_config['imap']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['port'])); +$records[] = array('_' . $autodiscover_config['pop3']['port'] . '._tcp.' . $autodiscover_config['pop3']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['port'])); +$records[] = array('_' . $autodiscover_config['sieve']['port'] . '._tcp.' . $autodiscover_config['sieve']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['sieve']['server'], $autodiscover_config['sieve']['port'], 1)); + +foreach ($domains as $domain) { + $records[] = array($domain, 'MX', $mailcow_hostname); + $records[] = array('autodiscover.' . $domain, 'CNAME', $mailcow_hostname); + $records[] = array('_autodiscover._tcp.' . $domain, 'SRV', $mailcow_hostname . ' ' . $https_port); + $records[] = array('autoconfig.' . $domain, 'CNAME', $mailcow_hostname); + $records[] = array($domain, 'TXT', 'v=spf1 mx -all'); + $records[] = array('_dmarc.' . $domain, 'TXT', 'v=DMARC1; p=reject', 'v=DMARC1; p='); + + if (!empty($dkim = dkim('details', $domain))) { + $records[] = array($dkim['dkim_selector'] . '._domainkey.' . $domain, 'TXT', $dkim['dkim_txt']); + } + + $current_records = dns_get_record('_pop3._tcp.' . $domain, DNS_SRV); + if (count($current_records) == 0 || $current_records[0]['target'] != '') { + if ($autodiscover_config['pop3']['tlsport'] != '110') { + $records[] = array('_pop3._tcp.' . $domain, 'SRV', $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['tlsport']); + } + } else { + $records[] = array('_pop3._tcp.' . $domain, 'SRV', '. 0'); + } + $current_records = dns_get_record('_pop3s._tcp.' . $domain, DNS_SRV); + if (count($current_records) == 0 || $current_records[0]['target'] != '') { + if ($autodiscover_config['pop3']['port'] != '995') { + $records[] = array('_pop3s._tcp.' . $domain, 'SRV', $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['port']); + } + } else { + $records[] = array('_pop3s._tcp.' . $domain, 'SRV', '. 0'); + } + if ($autodiscover_config['imap']['tlsport'] != '143') { + $records[] = array('_imap._tcp.' . $domain, 'SRV', $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['tlsport']); + } + if ($autodiscover_config['imap']['port'] != '993') { + $records[] = array('_imaps._tcp.' . $domain, 'SRV', $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['port']); + } + if ($autodiscover_config['smtp']['tlsport'] != '587') { + $records[] = array('_submission._tcp.' . $domain, 'SRV', $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['tlsport']); + } + if ($autodiscover_config['smtp']['port'] != '465') { + $records[] = array('_smtps._tcp.' . $domain, 'SRV', $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['port']); + } + if ($autodiscover_config['sieve']['port'] != '4190') { + $records[] = array('_sieve._tcp.' . $domain, 'SRV', $autodiscover_config['sieve']['server'] . ' ' . $autodiscover_config['sieve']['port']); + } +} + +define('state_good', "✓"); +define('state_missing', "✗"); +define('state_nomatch', "?"); + +$record_types = array( + 'A' => DNS_A, + 'AAAA' => DNS_AAAA, + 'CNAME' => DNS_CNAME, + 'MX' => DNS_MX, + 'PTR' => DNS_PTR, + 'SRV' => DNS_SRV, + 'TXT' => DNS_TXT, +); +$data_field = array( + 'A' => 'ip', + 'AAAA' => 'ipv6', + 'CNAME' => 'target', + 'MX' => 'target', + 'PTR' => 'target', + 'SRV' => 'data', + 'TLSA' => 'data', + 'TXT' => 'txt', +); +?> +
+

+

+
+ + + 0 && count($cname) > 0) { + if ($a[0]['ip'] == $cname[0]['ip']) { + $currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $record[2])); + + $aaaa = dns_get_record($record[0], DNS_AAAA); + $cname = dns_get_record($record[2], DNS_AAAA); + if (count($aaaa) == 0 || count($cname) == 0 || $aaaa[0]['ipv6'] != $cname[0]['ipv6']) { + $currents[0]['target'] = $aaaa[0]['ipv6']; + } + } else { + $currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $a[0]['ip'])); + } + } + } + + foreach ($currents as $current) { + $current['type'] == strtoupper($current['type']); + if ($current['type'] != $record[1]) + { + continue; + } + + elseif ($current['type'] == 'TXT' && strpos($record[0], '_dmarc.') === 0) { + $state = state_nomatch; + if (strpos($current[$data_field[$current['type']]], $record[3]) === 0) + $state = state_good . ' (' . current[$data_field[$current['type']]] . ')'; + } + else if ($current['type'] == 'TXT' && strpos($current['txt'], 'v=spf1') === 0) { + $allowed = get_spf_allowed_hosts($record[0]); + $spf_ok = FALSE; + $spf_ok6 = FALSE; + foreach ($allowed as $net) + { + if (in_net($ip, $net)) + $spf_ok = TRUE; + if (in_net($ip6, $net)) + $spf_ok6 = TRUE; + } + if ($spf_ok && (empty($ip6) || $spf_ok6)) + $state = state_good . ' (' . $current[$data_field[$current['type']]] . ')'; + } + else if ($current['type'] != 'TXT' && isset($data_field[$current['type']]) && $state != state_good) { + $state = state_nomatch; + if ($current[$data_field[$current['type']]] == $record[2]) + $state = state_good; + } + } + + if ($state == state_nomatch) { + $state = array(); + foreach ($currents as $current) { + $state[] = $current[$data_field[$current['type']]]; + } + $state = implode('
', $state); + } + + echo sprintf('', $record[0], $record[1], $record[2], $state); +} +?> +
%s%s%s%s
+
+
+ From 98be90c4943bf05f488d7cefc8299283336369df Mon Sep 17 00:00:00 2001 From: Michael Kuron Date: Mon, 10 Jul 2017 21:41:45 +0200 Subject: [PATCH 2/3] Remove SPF and DMARC checks --- diagnostics.php => data/web/diagnostics.php | 57 +++++---------------- 1 file changed, 13 insertions(+), 44 deletions(-) rename diagnostics.php => data/web/diagnostics.php (88%) diff --git a/diagnostics.php b/data/web/diagnostics.php similarity index 88% rename from diagnostics.php rename to data/web/diagnostics.php index 4de0082e..5a199007 100644 --- a/diagnostics.php +++ b/data/web/diagnostics.php @@ -2,28 +2,10 @@ require_once 'inc/prerequisites.inc.php'; require_once 'inc/spf.inc.php'; -function in_net($addr, $net) { - $net = explode('/', $net); - if (count($net) > 1) { - $mask = $net[1]; - } - $net = inet_pton($net[0]); - $addr = inet_pton($addr); - $length = strlen($net); // 4 for IPv4, 16 for IPv6 - if (strlen($net) != strlen($addr)) { - return false; - } - if (!isset($mask)) { - $mask = $length * 8; - } - $addr_bin = ''; - $net_bin = ''; - for ($i = 0; $i < $length; ++$i) { - $addr_bin .= str_pad(decbin(ord(substr($addr, $i, $i+1))), 8, '0', STR_PAD_LEFT); - $net_bin .= str_pad(decbin(ord(substr($net, $i, $i+1))), 8, '0', STR_PAD_LEFT); - } - return substr($addr_bin, 0, $mask) == substr($net_bin, 0, $mask); -} +define('state_good', "✓"); +define('state_missing', "✗"); +define('state_nomatch', "?"); +define('state_optional', "(optional)"); if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") { require_once("inc/header.inc.php"); @@ -96,8 +78,8 @@ foreach ($domains as $domain) { $records[] = array('autodiscover.' . $domain, 'CNAME', $mailcow_hostname); $records[] = array('_autodiscover._tcp.' . $domain, 'SRV', $mailcow_hostname . ' ' . $https_port); $records[] = array('autoconfig.' . $domain, 'CNAME', $mailcow_hostname); - $records[] = array($domain, 'TXT', 'v=spf1 mx -all'); - $records[] = array('_dmarc.' . $domain, 'TXT', 'v=DMARC1; p=reject', 'v=DMARC1; p='); + $records[] = array($domain, 'TXT', 'SPF Record Syntax', state_optional); + $records[] = array('_dmarc.' . $domain, 'TXT', 'DMARC Assistant', state_optional); if (!empty($dkim = dkim('details', $domain))) { $records[] = array($dkim['dkim_selector'] . '._domainkey.' . $domain, 'TXT', $dkim['dkim_txt']); @@ -136,10 +118,6 @@ foreach ($domains as $domain) { } } -define('state_good', "✓"); -define('state_missing', "✗"); -define('state_nomatch', "?"); - $record_types = array( 'A' => DNS_A, 'AAAA' => DNS_AAAA, @@ -224,23 +202,10 @@ foreach ($records as $record) } elseif ($current['type'] == 'TXT' && strpos($record[0], '_dmarc.') === 0) { - $state = state_nomatch; - if (strpos($current[$data_field[$current['type']]], $record[3]) === 0) - $state = state_good . ' (' . current[$data_field[$current['type']]] . ')'; + $state = state_optional . '
' . $current[$data_field[$current['type']]]; } else if ($current['type'] == 'TXT' && strpos($current['txt'], 'v=spf1') === 0) { - $allowed = get_spf_allowed_hosts($record[0]); - $spf_ok = FALSE; - $spf_ok6 = FALSE; - foreach ($allowed as $net) - { - if (in_net($ip, $net)) - $spf_ok = TRUE; - if (in_net($ip6, $net)) - $spf_ok6 = TRUE; - } - if ($spf_ok && (empty($ip6) || $spf_ok6)) - $state = state_good . ' (' . $current[$data_field[$current['type']]] . ')'; + $state = state_optional . '
' . $current[$data_field[$current['type']]]; } else if ($current['type'] != 'TXT' && isset($data_field[$current['type']]) && $state != state_good) { $state = state_nomatch; @@ -249,6 +214,10 @@ foreach ($records as $record) } } + if (isset($record[3]) && $record[3] == state_optional && ($state == state_missing || $state == state_nomatch)) { + $state = state_optional; + } + if ($state == state_nomatch) { $state = array(); foreach ($currents as $current) { @@ -256,7 +225,7 @@ foreach ($records as $record) } $state = implode('
', $state); } - + echo sprintf('%s%s%s%s', $record[0], $record[1], $record[2], $state); } ?> From 651c1cac23d77988521d96283043f476d3361ed8 Mon Sep 17 00:00:00 2001 From: Phoenix Eve Aspacio Date: Thu, 21 Sep 2017 07:22:33 +0800 Subject: [PATCH 3/3] Fixed broken link --- data/web/diagnostics.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/diagnostics.php b/data/web/diagnostics.php index 5a199007..d35c4d47 100644 --- a/data/web/diagnostics.php +++ b/data/web/diagnostics.php @@ -10,7 +10,7 @@ define('state_optional', "(optional)"); if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") { require_once("inc/header.inc.php"); -$ch = curl_init('http://ipv4.mailcow.email'); +$ch = curl_init('http://ip4.mailcow.email'); curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); curl_setopt($ch, CURLOPT_VERBOSE, false); curl_setopt($ch, CURLOPT_HEADER, false); @@ -19,7 +19,7 @@ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); $ip = curl_exec($ch); curl_close($ch); -$ch = curl_init('http://ipv6.mailcow.email'); +$ch = curl_init('http://ip6.mailcow.email'); curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); curl_setopt($ch, CURLOPT_VERBOSE, false); curl_setopt($ch, CURLOPT_HEADER, false);