blob: 4cfee160f8d447fad8581d0cf9cc29de4a817db5 [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
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005define('state_good', '<i class="bi bi-check-lg text-success"></i>');
6define('state_missing', '<i class="bi bi-x-lg text-danger"></i>');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007define('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
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012 $alias_domains = array();
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014 if (isset($_GET['domain'])) {
15 $domain_details = mailbox('get', 'domain_details', $_GET['domain']);
16 if ($domain_details !== false) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017 $domain = $_GET['domain'];
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018 $alias_domains = array_merge($alias_domains, mailbox('get', 'alias_domains', $domain));
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019 }
20 else {
21 echo "No such domain in context";
22 exit();
23 }
24 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010025
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020026 $ch = curl_init('http://ip4.mailcow.email');
27 curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
28 curl_setopt($ch, CURLOPT_VERBOSE, false);
29 curl_setopt($ch, CURLOPT_HEADER, false);
30 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
31 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
32 $ip = curl_exec($ch);
33 curl_close($ch);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010034
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020035 $ch = curl_init('http://ip6.mailcow.email');
36 curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
37 curl_setopt($ch, CURLOPT_VERBOSE, false);
38 curl_setopt($ch, CURLOPT_HEADER, false);
39 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
40 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
41 $ip6 = curl_exec($ch);
42 curl_close($ch);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010043
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020044 $ptr = implode('.', array_reverse(explode('.', $ip))) . '.in-addr.arpa';
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010045 if (!empty($ip6)) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020046 $ip6_full = str_replace('::', str_repeat(':', 9-substr_count($ip6, ':')), $ip6);
47 $ip6_full = str_replace('::', ':0:', $ip6_full);
48 $ip6_full = str_replace('::', ':0:', $ip6_full);
49 $ptr6 = '';
50 foreach (explode(':', $ip6_full) as $part) {
51 $ptr6 .= str_pad($part, 4, '0', STR_PAD_LEFT);
52 }
53 $ptr6 = implode('.', array_reverse(str_split($ptr6, 1))) . '.ip6.arpa';
54 }
55
56 $https_port = strpos($_SERVER['HTTP_HOST'], ':');
57 if ($https_port === FALSE) {
58 $https_port = 443;
59 }
60 else {
61 $https_port = substr($_SERVER['HTTP_HOST'], $https_port+1);
62 }
63
64 if (!isset($autodiscover_config['sieve'])) {
65 $autodiscover_config['sieve'] = array(
66 'server' => $mailcow_hostname,
67 'port' => array_pop(explode(':', getenv('SIEVE_PORT')))
68 );
69 }
70
71 // Init records array
72 $spf_link = '<a href="http://www.open-spf.org/SPF_Record_Syntax/" target="_blank">SPF Record Syntax</a><br />';
73 $dmarc_link = '<a href="https://www.kitterman.com/dmarc/assistant.html" target="_blank">DMARC Assistant</a>';
74
75 $records = array();
76
77 if ($_SESSION['mailcow_cc_role'] == "admin") {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010078 $records[] = array(
79 $mailcow_hostname,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020080 'A',
81 $ip
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010082 );
83 $records[] = array(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020084 $ptr,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010085 'PTR',
86 $mailcow_hostname
87 );
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020088 if (!empty($ip6)) {
89 $records[] = array(
90 $mailcow_hostname,
91 'AAAA',
92 expand_ipv6($ip6)
93 );
94 $records[] = array(
95 $ptr6,
96 'PTR',
97 $mailcow_hostname
98 );
99 }
100 $records[] = array(
101 '_25._tcp.' . $autodiscover_config['smtp']['server'],
102 'TLSA',
103 generate_tlsa_digest($autodiscover_config['smtp']['server'], 25, 1)
104 );
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100105 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200106
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100107 $records[] = array(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200108 $domain,
109 'MX',
110 $mailcow_hostname
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100111 );
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200112
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100113 if (!in_array($domain, $alias_domains)) {
114 $records[] = array(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200115 'autodiscover.' . $domain,
116 'CNAME',
117 $mailcow_hostname
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100118 );
119 $records[] = array(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200120 '_autodiscover._tcp.' . $domain,
121 'SRV',
122 $mailcow_hostname . ' ' . $https_port
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100123 );
124 $records[] = array(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200125 'autoconfig.' . $domain,
126 'CNAME',
127 $mailcow_hostname
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100128 );
129 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100130
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100131 $records[] = array(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200132 $domain,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100133 'TXT',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200134 $spf_link,
135 state_optional
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100136 );
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100137
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200138 $records[] = array(
139 '_dmarc.' . $domain,
140 'TXT',
141 $dmarc_link,
142 state_optional
143 );
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100144
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200145 if (!empty($dkim = dkim('details', $domain))) {
146 $records[] = array(
147 $dkim['dkim_selector'] . '._domainkey.' . $domain,
148 'TXT',
149 $dkim['dkim_txt']
150 );
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100151 }
152
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200153 if (!in_array($domain, $alias_domains)) {
Matthias Andreas Benkard12a57352021-12-28 18:02:04 +0100154 $current_records = (array)dns_get_record('_pop3._tcp.' . $domain, DNS_SRV);
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200155 if (count($current_records) == 0 || $current_records[0]['target'] != '') {
156 if ($autodiscover_config['pop3']['tlsport'] != '110') {
157 $records[] = array(
158 '_pop3._tcp.' . $domain,
159 'SRV',
160 $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['tlsport']
161 );
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100162 }
163 }
164 else {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200165 $records[] = array(
166 '_pop3._tcp.' . $domain,
167 'SRV',
168 '. 0'
169 );
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100170 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200171
Matthias Andreas Benkard12a57352021-12-28 18:02:04 +0100172 $current_records = (array)dns_get_record('_pop3s._tcp.' . $domain, DNS_SRV);
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200173
174 if (count($current_records) == 0 || $current_records[0]['target'] != '') {
175 if ($autodiscover_config['pop3']['port'] != '995') {
176 $records[] = array(
177 '_pop3s._tcp.' . $domain,
178 'SRV',
179 $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['port']
180 );
181 }
182 }
183 else {
184 $records[] = array(
185 '_pop3s._tcp.' . $domain,
186 'SRV',
187 '. 0'
188 );
189 }
190
191 if ($autodiscover_config['imap']['tlsport'] != '143') {
192 $records[] = array(
193 '_imap._tcp.' . $domain,
194 'SRV',
195 $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['tlsport']
196 );
197 }
198
199 if ($autodiscover_config['imap']['port'] != '993') {
200 $records[] = array(
201 '_imaps._tcp.' . $domain,
202 'SRV',
203 $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['port']
204 );
205 }
206
207 if ($autodiscover_config['smtp']['tlsport'] != '587') {
208 $records[] = array(
209 '_submission._tcp.' . $domain,
210 'SRV',
211 $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['tlsport']
212 );
213 }
214
215 if ($autodiscover_config['smtp']['port'] != '465') {
216 $records[] = array(
217 '_smtps._tcp.' . $domain,
218 'SRV',
219 $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['port']
220 );
221 }
222
223 if ($autodiscover_config['sieve']['port'] != '4190') {
224 $records[] = array(
225 '_sieve._tcp.' . $domain,
226 'SRV',
227 $autodiscover_config['sieve']['server'] . ' ' . $autodiscover_config['sieve']['port']
228 );
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100229 }
230 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100231
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200232 $record_types = array(
233 'A' => DNS_A,
234 'AAAA' => DNS_AAAA,
235 'CNAME' => DNS_CNAME,
236 'MX' => DNS_MX,
237 'PTR' => DNS_PTR,
238 'SRV' => DNS_SRV,
239 'TXT' => DNS_TXT,
240 );
241
242 $data_field = array(
243 'A' => 'ip',
244 'AAAA' => 'ipv6',
245 'CNAME' => 'target',
246 'MX' => 'target',
247 'PTR' => 'target',
248 'SRV' => 'data',
249 'TLSA' => 'data',
250 'TXT' => 'txt',
251 );
252
253 ?>
254 <div class="table-responsive" id="dnstable">
255 <table class="table table-striped">
256 <tr>
257 <th><?=$lang['diagnostics']['dns_records_name'];?></th>
258 <th><?=$lang['diagnostics']['dns_records_type'];?></th>
259 <th><?=$lang['diagnostics']['dns_records_data'];?></th>
260 <th><?=$lang['diagnostics']['dns_records_status'];?></th>
261 </tr>
262 <?php
263 foreach ($records as &$record) {
264 $record[1] = strtoupper($record[1]);
265 $state = state_optional;
266
267 if ($record[1] == 'TLSA') {
Matthias Andreas Benkard12a57352021-12-28 18:02:04 +0100268 $currents = (array)dns_get_record($record[0], 52, $_, $_, TRUE);
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200269 foreach ($currents as &$current) {
270 $current['type'] = 'TLSA';
271 $current['cert_usage'] = hexdec(bin2hex($current['data'][0]));
272 $current['selector'] = hexdec(bin2hex($current['data'][1]));
273 $current['match_type'] = hexdec(bin2hex($current['data'][2]));
274 $current['cert_data'] = bin2hex(substr($current['data'], 3));
275 $current['data'] = $current['cert_usage'] . ' ' . $current['selector'] . ' ' . $current['match_type'] . ' ' . $current['cert_data'];
276 }
277 unset($current);
278 }
279 else {
Matthias Andreas Benkard12a57352021-12-28 18:02:04 +0100280 $currents = (array)dns_get_record($record[0], $record_types[$record[1]]);
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200281 if ($record[0] == $mailcow_hostname && ($record[1] == "A" || $record[1] == "AAAA")) {
Matthias Andreas Benkard12a57352021-12-28 18:02:04 +0100282 if (!empty((array)dns_get_record($record[0], DNS_CNAME))) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200283 $currents[0]['ip'] = state_missing . ' <b>(CNAME)</b>';
284 $currents[0]['ipv6'] = state_missing . ' <b>(CNAME)</b>';
285 }
286 }
287 if ($record[1] == 'SRV') {
288 foreach ($currents as &$current) {
289 if ($current['target'] == '') {
290 $current['target'] = '.';
291 $current['port'] = '0';
292 }
293 $current['data'] = $current['target'] . ' ' . $current['port'];
294 }
295 unset($current);
296 }
297 elseif ($record[1] == 'TXT') {
298 foreach ($currents as &$current) {
299 unset($current);
300 }
301 unset($current);
302 }
303 elseif ($record[1] == 'AAAA') {
304 foreach ($currents as &$current) {
305 $current['ipv6'] = expand_ipv6($current['ipv6']);
306 }
307 }
308 }
309
310 if ($record[1] == 'CNAME' && count($currents) == 0) {
311 // A and AAAA are also valid instead of CNAME
Matthias Andreas Benkard12a57352021-12-28 18:02:04 +0100312 $a = (array)dns_get_record($record[0], DNS_A);
313 $cname = (array)dns_get_record($record[2], DNS_A);
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200314 if (count($a) > 0 && count($cname) > 0) {
315 if ($a[0]['ip'] == $cname[0]['ip']) {
316 $currents = array(
317 array(
318 'host' => $record[0],
319 'class' => 'IN',
320 'type' => 'CNAME',
321 'target' => $record[2]
322 )
323 );
Matthias Andreas Benkard12a57352021-12-28 18:02:04 +0100324 $aaaa = (array)dns_get_record($record[0], DNS_AAAA);
325 $cname = (array)dns_get_record($record[2], DNS_AAAA);
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200326 if (count($aaaa) == 0 || count($cname) == 0 || expand_ipv6($aaaa[0]['ipv6']) != expand_ipv6($cname[0]['ipv6'])) {
327 $currents[0]['target'] = expand_ipv6($aaaa[0]['ipv6']) . ' <sup>1</sup>';
328 }
329 }
330 else {
331 $currents = array(
332 array(
333 'host' => $record[0],
334 'class' => 'IN',
335 'type' => 'CNAME',
336 'target' => $a[0]['ip'] . ' <sup>1</sup>'
337 )
338 );
339 }
340 }
341 }
342
343 foreach ($currents as &$current) {
344 if ($current['type'] == 'TXT' &&
345 stripos($current['txt'], 'v=dmarc') === 0 &&
346 $record[2] == $dmarc_link) {
347 $current['txt'] = str_replace(' ', '', $current['txt']);
348 $state = $current[$data_field[$current['type']]] . state_optional;
349 }
350 elseif ($current['type'] == 'TXT' &&
351 stripos($current['txt'], 'v=spf') === 0 &&
352 $record[2] == $spf_link) {
353 $state = state_nomatch;
354 $rslt = get_spf_allowed_hosts($record[0], true);
355 if (in_array($ip, $rslt) && in_array(expand_ipv6($ip6), $rslt)) {
356 $state = state_good;
357 }
358 $state .= '<br />' . $current[$data_field[$current['type']]] . state_optional;
359 }
360 elseif ($current['type'] == 'TXT' &&
361 stripos($current['txt'], 'v=dkim') === 0 &&
362 stripos($record[2], 'v=dkim') === 0) {
363 preg_match('/v=DKIM1;.*k=rsa;.*p=([^;]*).*/i', $current[$data_field[$current['type']]], $dkim_matches_current);
364 preg_match('/v=DKIM1;.*k=rsa;.*p=([^;]*).*/i', $record[2], $dkim_matches_good);
365 if ($dkim_matches_current[1] == $dkim_matches_good[1]) {
366 $state = state_good;
367 }
368 }
369 elseif ($current['type'] != 'TXT' &&
370 isset($data_field[$current['type']]) && $state != state_good) {
371 $state = state_nomatch;
372 if ($current[$data_field[$current['type']]] == $record[2]) {
373 $state = state_good;
374 }
375 }
376 }
377 unset($current);
378
379 if (isset($record[3]) &&
380 $record[3] == state_optional &&
381 ($state == state_missing || $state == state_nomatch)) {
382 $state = state_optional;
383 }
384
385 if ($state == state_nomatch) {
386 $state = array();
387 foreach ($currents as $current) {
388 $state[] = $current[$data_field[$current['type']]];
389 }
390 $state = implode('<br />', $state);
391 }
392 echo sprintf('
393 <tr>
394 <td>%s</td>
395 <td>%s</td>
396 <td class="dns-found">%s</td>
397 <td class="dns-recommended">%s</td>
398 </tr>', $record[0], $record[1], $record[2], $state);
399 $record[3] = explode('<br />', $state);
400 }
401
402 unset($record);
403
404 $dns_data = sprintf("\$ORIGIN %s.\n", $domain);
405 foreach ($records as $record) {
406 if ($domain == substr($record[0], -strlen($domain))) {
407 $label = substr($record[0], 0, -strlen($domain)-1);
408 $val = $record[2];
409
410 if (strlen($label) == 0) {
411 $label = "@";
412 }
413
414 $vals = array();
415 if (strpos($val, "<a") !== FALSE) {
416 if (is_array($record[3]) && count($record[3]) == 1 && $record[3][0] == state_optional) {
417 $record[3][0] = "**TODO**";
418 $label = ';' . $label;
419 }
420 foreach ($record[3] as $val) {
421 $val = str_replace(state_optional, '', $val);
422 $val = str_replace(state_good, '', $val);
423 if (strlen($val) > 0) {
424 $vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
425 }
426 }
427 }
428 else {
429 $vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
430 }
431
432 foreach ($vals as $val) {
433 $dns_data .= str_replace($domain, $domain . '.', $val);
434 }
435 }
436 }
437 ?>
438 </table>
439 <a id='download-zonefile' class="btn btn-sm btn-default visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline" style="margin-top:10px" data-zonefile="<?=base64_encode($dns_data);?>" download='<?=$_GET['domain'];?>.txt' type='text/csv'>Download</a>
440 <script>
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100441 var zonefile_dl_link = document.getElementById('download-zonefile');
442 var zonefile = atob(zonefile_dl_link.getAttribute('data-zonefile'));
443 var data = new Blob([zonefile]);
444 var download_zonefile_link = document.getElementById('download-zonefile');
445 download_zonefile_link.href = URL.createObjectURL(data);
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200446 </script>
447 </div>
448 <p class="help-block">
449 <sup>1</sup> <?=$lang['diagnostics']['cname_from_a'];?><br />
450 <sup>2</sup> <?=$lang['diagnostics']['optional'];?>
451 </p>
452 <?php
453}
454else {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100455 echo "Session invalid";
456 exit();
457}
458?>