blob: 928a4e4274df242c4f5f0de7ceabeb5b2096239c [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
3require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/spf.inc.php';
4
5define('state_good', '<span class="glyphicon glyphicon-ok text-success"></span>');
6define('state_missing', '<span class="glyphicon glyphicon-remove text-danger"></span>');
7define('state_nomatch', "?");
8define('state_optional', " <sup>2</sup>");
9
10if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin"|| $_SESSION['mailcow_cc_role'] == "domainadmin")) {
11
12$domains = mailbox('get', 'domains');
13$alias_domains = array();
14foreach($domains as $dn) {
15 $alias_domains = array_merge($alias_domains, mailbox('get', 'alias_domains', $dn));
16}
17$domains = array_merge($domains, $alias_domains);
18
19if (isset($_GET['domain'])) {
20 if (is_valid_domain_name($_GET['domain'])) {
21 if (in_array($_GET['domain'], $domains)) {
22 $domain = $_GET['domain'];
23 }
24 else {
25 echo "No such domain in context";
26 exit();
27 }
28 }
29 else {
30 echo "Invalid domain name";
31 exit();
32 }
33}
34
35$ch = curl_init('http://ip4.mailcow.email');
36curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
37curl_setopt($ch, CURLOPT_VERBOSE, false);
38curl_setopt($ch, CURLOPT_HEADER, false);
39curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
40curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
41$ip = curl_exec($ch);
42curl_close($ch);
43
44$ch = curl_init('http://ip6.mailcow.email');
45curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
46curl_setopt($ch, CURLOPT_VERBOSE, false);
47curl_setopt($ch, CURLOPT_HEADER, false);
48curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
49curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
50$ip6 = curl_exec($ch);
51curl_close($ch);
52
53$ptr = implode('.', array_reverse(explode('.', $ip))) . '.in-addr.arpa';
54if (!empty($ip6)) {
55 $ip6_full = str_replace('::', str_repeat(':', 9-substr_count($ip6, ':')), $ip6);
56 $ip6_full = str_replace('::', ':0:', $ip6_full);
57 $ip6_full = str_replace('::', ':0:', $ip6_full);
58 $ptr6 = '';
59 foreach (explode(':', $ip6_full) as $part) {
60 $ptr6 .= str_pad($part, 4, '0', STR_PAD_LEFT);
61 }
62 $ptr6 = implode('.', array_reverse(str_split($ptr6, 1))) . '.ip6.arpa';
63}
64
65$https_port = strpos($_SERVER['HTTP_HOST'], ':');
66if ($https_port === FALSE) {
67 $https_port = 443;
68}
69else {
70 $https_port = substr($_SERVER['HTTP_HOST'], $https_port+1);
71}
72
73if (!isset($autodiscover_config['sieve'])) {
74 $autodiscover_config['sieve'] = array('server' => $mailcow_hostname, 'port' => array_pop(explode(':', getenv('SIEVE_PORT'))));
75}
76
77// Init records array
78$spf_link = '<a href="https://en.wikipedia.org/wiki/Sender_Policy_Framework" target="_blank">SPF Record Syntax</a><br />';
79$dmarc_link = '<a href="https://www.kitterman.com/dmarc/assistant.html" target="_blank">DMARC Assistant</a>';
80
81$records = array();
82if ($_SESSION['mailcow_cc_role'] == "admin") {
83 $records[] = array(
84 $mailcow_hostname,
85 'A',
86 $ip
87 );
88 $records[] = array(
89 $ptr,
90 'PTR',
91 $mailcow_hostname
92 );
93 if (!empty($ip6)) {
94 $records[] = array(
95 $mailcow_hostname,
96 'AAAA',
97 expand_ipv6($ip6)
98 );
99 $records[] = array(
100 $ptr6,
101 'PTR',
102 $mailcow_hostname
103 );
104 }
105 $records[] = array(
106 '_25._tcp.'.$autodiscover_config['smtp']['server'],
107 'TLSA',
108 generate_tlsa_digest($autodiscover_config['smtp']['server'], 25, 1)
109 );
110 if (!in_array($domain, $alias_domains)) {
111 $records[] = array(
112 '_'.$https_port.
113 '._tcp.'.$mailcow_hostname,
114 'TLSA',
115 generate_tlsa_digest($mailcow_hostname, $https_port)
116 );
117 $records[] = array(
118 '_'.$autodiscover_config['pop3']['tlsport'].
119 '._tcp.'.$autodiscover_config['pop3']['server'],
120 'TLSA',
121 generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['tlsport'], 1)
122 );
123 $records[] = array(
124 '_'.$autodiscover_config['imap']['tlsport'].
125 '._tcp.'.$autodiscover_config['imap']['server'],
126 'TLSA',
127 generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['tlsport'], 1)
128 );
129 $records[] = array(
130 '_'.$autodiscover_config['smtp']['port'].
131 '._tcp.'.$autodiscover_config['smtp']['server'],
132 'TLSA',
133 generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['port'])
134 );
135 $records[] = array(
136 '_'.$autodiscover_config['smtp']['tlsport'].
137 '._tcp.'.$autodiscover_config['smtp']['server'],
138 'TLSA',
139 generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['tlsport'], 1)
140 );
141 $records[] = array(
142 '_'.$autodiscover_config['imap']['port'].
143 '._tcp.'.$autodiscover_config['imap']['server'],
144 'TLSA',
145 generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['port'])
146 );
147 $records[] = array(
148 '_'.$autodiscover_config['pop3']['port'].
149 '._tcp.'.$autodiscover_config['pop3']['server'],
150 'TLSA',
151 generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['port'])
152 );
153 $records[] = array(
154 '_'.$autodiscover_config['sieve']['port'].
155 '._tcp.'.$autodiscover_config['sieve']['server'],
156 'TLSA',
157 generate_tlsa_digest($autodiscover_config['sieve']['server'], $autodiscover_config['sieve']['port'], 1)
158 );
159 }
160}
161$records[] = array(
162 $domain,
163 'MX',
164 $mailcow_hostname
165);
166if (!in_array($domain, $alias_domains)) {
167 $records[] = array(
168 'autodiscover.'.$domain,
169 'CNAME',
170 $mailcow_hostname
171 );
172 $records[] = array(
173 '_autodiscover._tcp.'.$domain,
174 'SRV',
175 $mailcow_hostname.
176 ' '.$https_port
177 );
178 $records[] = array(
179 'autoconfig.'.$domain,
180 'CNAME',
181 $mailcow_hostname
182 );
183}
184$records[] = array(
185 $domain,
186 'TXT',
187 $spf_link,
188 state_optional
189);
190$records[] = array(
191 '_dmarc.'.$domain,
192 'TXT',
193 $dmarc_link,
194 state_optional
195);
196
197if (!empty($dkim = dkim('details', $domain))) {
198 $records[] = array(
199 $dkim['dkim_selector'] . '._domainkey.' . $domain,
200 'TXT',
201 $dkim['dkim_txt']
202 );
203}
204if (!in_array($domain, $alias_domains)) {
205 $current_records = dns_get_record('_pop3._tcp.' . $domain, DNS_SRV);
206 if (count($current_records) == 0 || $current_records[0]['target'] != '') {
207 if ($autodiscover_config['pop3']['tlsport'] != '110') {
208 $records[] = array(
209 '_pop3._tcp.' . $domain,
210 'SRV',
211 $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['tlsport']
212 );
213 }
214 }
215 else {
216 $records[] = array(
217 '_pop3._tcp.' . $domain,
218 'SRV',
219 '. 0'
220 );
221 }
222 $current_records = dns_get_record('_pop3s._tcp.' . $domain, DNS_SRV);
223 if (count($current_records) == 0 || $current_records[0]['target'] != '') {
224 if ($autodiscover_config['pop3']['port'] != '995') {
225 $records[] = array(
226 '_pop3s._tcp.' . $domain,
227 'SRV',
228 $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['port']
229 );
230 }
231 }
232 else {
233 $records[] = array(
234 '_pop3s._tcp.' . $domain,
235 'SRV',
236 '. 0'
237 );
238 }
239 if ($autodiscover_config['imap']['tlsport'] != '143') {
240 $records[] = array(
241 '_imap._tcp.' . $domain,
242 'SRV',
243 $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['tlsport']
244 );
245 }
246 if ($autodiscover_config['imap']['port'] != '993') {
247 $records[] = array(
248 '_imaps._tcp.' . $domain,
249 'SRV',
250 $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['port']
251 );
252 }
253 if ($autodiscover_config['smtp']['tlsport'] != '587') {
254 $records[] = array(
255 '_submission._tcp.' . $domain,
256 'SRV',
257 $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['tlsport']
258 );
259 }
260 if ($autodiscover_config['smtp']['port'] != '465') {
261 $records[] = array(
262 '_smtps._tcp.' . $domain,
263 'SRV',
264 $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['port']
265 );
266 }
267 if ($autodiscover_config['sieve']['port'] != '4190') {
268 $records[] = array(
269 '_sieve._tcp.' . $domain,
270 'SRV',
271 $autodiscover_config['sieve']['server'] . ' ' . $autodiscover_config['sieve']['port']
272 );
273 }
274}
275
276$record_types = array(
277 'A' => DNS_A,
278 'AAAA' => DNS_AAAA,
279 'CNAME' => DNS_CNAME,
280 'MX' => DNS_MX,
281 'PTR' => DNS_PTR,
282 'SRV' => DNS_SRV,
283 'TXT' => DNS_TXT,
284);
285$data_field = array(
286 'A' => 'ip',
287 'AAAA' => 'ipv6',
288 'CNAME' => 'target',
289 'MX' => 'target',
290 'PTR' => 'target',
291 'SRV' => 'data',
292 'TLSA' => 'data',
293 'TXT' => 'txt',
294);
295
296?>
297<div class="table-responsive" id="dnstable">
298 <table class="table table-striped">
299 <tr>
300 <th><?=$lang['diagnostics']['dns_records_name'];?></th>
301 <th><?=$lang['diagnostics']['dns_records_type'];?></th>
302 <th><?=$lang['diagnostics']['dns_records_data'];?></th>
303 <th><?=$lang['diagnostics']['dns_records_status'];?></th>
304 </tr>
305<?php
306foreach ($records as &$record) {
307 $record[1] = strtoupper($record[1]);
308 $state = state_missing;
309 if ($record[1] == 'TLSA') {
310 $currents = dns_get_record($record[0], 52, $_, $_, TRUE);
311 foreach ($currents as &$current) {
312 $current['type'] = 'TLSA';
313 $current['cert_usage'] = hexdec(bin2hex($current['data']{0}));
314 $current['selector'] = hexdec(bin2hex($current['data']{1}));
315 $current['match_type'] = hexdec(bin2hex($current['data']{2}));
316 $current['cert_data'] = bin2hex(substr($current['data'], 3));
317 $current['data'] = $current['cert_usage'] . ' ' . $current['selector'] . ' ' . $current['match_type'] . ' ' . $current['cert_data'];
318 }
319 unset($current);
320 }
321 else {
322 $currents = dns_get_record($record[0], $record_types[$record[1]]);
323 if ($record[0] == $mailcow_hostname && ($record[1] == "A" || $record[1] == "AAAA")) {
324 if (!empty(dns_get_record($record[0], DNS_CNAME))) {
325 $currents[0]['ip'] = state_missing . ' <b>(CNAME)</b>';
326 $currents[0]['ipv6'] = state_missing . ' <b>(CNAME)</b>';
327 }
328 }
329 if ($record[1] == 'SRV') {
330 foreach ($currents as &$current) {
331 if ($current['target'] == '') {
332 $current['target'] = '.';
333 $current['port'] = '0';
334 }
335 $current['data'] = $current['target'] . ' ' . $current['port'];
336 }
337 unset($current);
338 }
339 elseif ($record[1] == 'TXT') {
340 foreach ($currents as &$current) {
341 unset($current);
342 }
343 unset($current);
344 }
345 elseif ($record[1] == 'AAAA') {
346 foreach ($currents as &$current) {
347 $current['ipv6'] = expand_ipv6($current['ipv6']);
348 }
349 }
350 }
351
352 if ($record[1] == 'CNAME' && count($currents) == 0) {
353 // A and AAAA are also valid instead of CNAME
354 $a = dns_get_record($record[0], DNS_A);
355 $cname = dns_get_record($record[2], DNS_A);
356 if (count($a) > 0 && count($cname) > 0) {
357 if ($a[0]['ip'] == $cname[0]['ip']) {
358 $currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $record[2]));
359 $aaaa = dns_get_record($record[0], DNS_AAAA);
360 $cname = dns_get_record($record[2], DNS_AAAA);
361 if (count($aaaa) == 0 || count($cname) == 0 || expand_ipv6($aaaa[0]['ipv6']) != expand_ipv6($cname[0]['ipv6'])) {
362 $currents[0]['target'] = expand_ipv6($aaaa[0]['ipv6']) . ' <sup>1</sup>';
363 }
364 }
365 else {
366 $currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $a[0]['ip'] . ' <sup>1</sup>'));
367 }
368 }
369 }
370
371 foreach ($currents as &$current) {
372 if ($current['type'] == 'TXT' &&
373 stripos($current['txt'], 'v=dmarc') === 0 &&
374 $record[2] == $dmarc_link) {
375 $current['txt'] = str_replace(' ', '', $current['txt']);
376 $state = $current[$data_field[$current['type']]] . state_optional;
377 }
378 elseif ($current['type'] == 'TXT' &&
379 stripos($current['txt'], 'v=spf') === 0 &&
380 $record[2] == $spf_link) {
381 $state = state_nomatch;
382 $rslt = get_spf_allowed_hosts($record[0]);
383 if(in_array($ip, $rslt) && in_array(expand_ipv6($ip6), $rslt)){
384 $state = state_good;
385 }
386 $state .= '<br />' . $current[$data_field[$current['type']]].state_optional;
387 }
388 elseif ($current['type'] == 'TXT' &&
389 stripos($current['txt'], 'v=dkim') === 0 &&
390 stripos($record[2], 'v=dkim') === 0) {
391 preg_match('/v=DKIM1;.*k=rsa;.*p=([^;]*).*/i', $current[$data_field[$current['type']]], $dkim_matches_current);
392 preg_match('/v=DKIM1;.*k=rsa;.*p=([^;]*).*/i', $record[2], $dkim_matches_good);
393 if ($dkim_matches_current[1] == $dkim_matches_good[1]) {
394 $state = state_good;
395 }
396 }
397 elseif ($current['type'] != 'TXT' &&
398 isset($data_field[$current['type']]) && $state != state_good) {
399 $state = state_nomatch;
400 if ($current[$data_field[$current['type']]] == $record[2]) {
401 $state = state_good;
402 }
403 }
404 }
405 unset($current);
406
407 if (isset($record[3]) &&
408 $record[3] == state_optional &&
409 ($state == state_missing || $state == state_nomatch)) {
410 $state = state_optional;
411 }
412
413 if ($state == state_nomatch) {
414 $state = array();
415 foreach ($currents as $current) {
416 $state[] = $current[$data_field[$current['type']]];
417 }
418 $state = implode('<br />', $state);
419 }
420 echo sprintf('<tr>
421 <td>%s</td>
422 <td>%s</td>
423 <td class="dns-found">%s</td>
424 <td class="dns-recommended">%s</td>
425 </tr>', $record[0], $record[1], $record[2], $state);
426 $record[3] = explode('<br />', $state);
427}
428unset($record);
429
430$dns_data = sprintf("\$ORIGIN %s.\n", $domain);
431foreach ($records as $record) {
432 if ($domain == substr($record[0], -strlen($domain))) {
433 $label = substr($record[0], 0, -strlen($domain)-1);
434 $val = $record[2];
435 if (strlen($label) == 0) {
436 $label = "@";
437 }
438 $vals = array();
439 if (strpos($val, "<a") !== FALSE) {
440 if(is_array($record[3]) && count($record[3]) == 1 && $record[3][0] == state_optional) {
441 $record[3][0] = "**TODO**";
442 $label = ';' . $label;
443 }
444 foreach ($record[3] as $val) {
445 $val = str_replace(state_optional, '', $val);
446 $val = str_replace(state_good, '', $val);
447 if (strlen($val) > 0) {
448 $vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
449 }
450 }
451 }
452 else {
453 $vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
454 }
455 foreach ($vals as $val) {
456 $dns_data .= str_replace($domain, $domain . '.', $val);
457 }
458 }
459}
460
461?>
462 </table>
463 <a id='download-zonefile' data-zonefile="<?=base64_encode($dns_data);?>" download='<?=$_GET['domain'];?>.txt' type='text/csv'>Download</a>
464 <script>
465 var zonefile_dl_link = document.getElementById('download-zonefile');
466 var zonefile = atob(zonefile_dl_link.getAttribute('data-zonefile'));
467 var data = new Blob([zonefile]);
468 var download_zonefile_link = document.getElementById('download-zonefile');
469 download_zonefile_link.href = URL.createObjectURL(data);
470 </script>
471</div>
472<p class="help-block">
473<sup>1</sup> <?=$lang['diagnostics']['cname_from_a'];?><br />
474<sup>2</sup> <?=$lang['diagnostics']['optional'];?>
475</p>
476<?php
477} else {
478 echo "Session invalid";
479 exit();
480}
481?>