diff --git a/interface/lib/classes/tools_monitor.inc.php b/interface/lib/classes/tools_monitor.inc.php index d57b9b7a3f425a291c842ef089183aea1f59a3f4..7a5efea27989c9c05746a408d329c7a363b4d50a 100644 --- a/interface/lib/classes/tools_monitor.inc.php +++ b/interface/lib/classes/tools_monitor.inc.php @@ -576,6 +576,25 @@ class tools_monitor { return $html; } + function showMx_ip_match() { + global $app; + + /* fetch the Data from the DB */ + $record = $app->db->queryOneRecord("SELECT data, state FROM monitor_data WHERE type = 'mx_ip_match' and server_id = ? ORDER BY created DESC", $_SESSION['monitor']['server_id']); + + if(isset($record['data'])) { + $data = unserialize($record['data']); + $html = ''; + foreach ($data as $domain => $text) { + $html .= "$domain: $text<br />"; + } + } else { + $html = '<p>'.$app->lng("no_data_mx_ip_match_txt").'</p>'; + } + + return $html; + } + function getDataTime($type) { global $app; diff --git a/interface/web/monitor/lib/lang/en.lng b/interface/web/monitor/lib/lang/en.lng index c790ed1d763b36452700fb203a175ec3ead3b337..eec736c5488066302d037187151c289619ce6d28 100644 --- a/interface/web/monitor/lib/lang/en.lng +++ b/interface/web/monitor/lib/lang/en.lng @@ -166,4 +166,7 @@ $wb['no_munin_url_defined_txt'] = 'No Munin URL defined.'; $wb['no_permissions_to_view_munin_txt'] = 'You are not allowed to access Munin.'; $wb['Database size'] = 'Database size'; $wb['MySQL Database size'] = 'MySQL Database size'; +$wb['monitor_title_mx_ip_match_txt'] = 'MX record to server IP match'; +$wb['no_data_mx_ip_match_txt'] = 'MX record check data present'; +$wb['monitor_serverstate_mx_ip_matchwarning_txt'] = 'Not all DNS MX records match the server IP'; ?> diff --git a/interface/web/monitor/show_data.php b/interface/web/monitor/show_data.php index 80f246ee2dcec04b6a255c1c5d9bec58adbd5420..13258398413030a45ee9633ad89fa1510fe87fea 100644 --- a/interface/web/monitor/show_data.php +++ b/interface/web/monitor/show_data.php @@ -114,6 +114,14 @@ case 'mailq': $description = ''; $add_padding = true; break; +case 'mx_ip_match': + $template = 'templates/show_data.htm'; + $output .= $app->tools_monitor->showMx_ip_match(); + $time = $app->tools_monitor->getDataTime('mx_ip_match'); + $title = $app->lng("monitor_title_mx_ip_match_txt"). ' ('. $monTransSrv .' : ' . $_SESSION['monitor']['server_name'] . ')'; + $description = ''; + $add_padding = true; + break; case 'raid_state': $template = 'templates/show_data.htm'; $output .= $app->tools_monitor->showRaidState(); diff --git a/interface/web/monitor/show_sys_state.php b/interface/web/monitor/show_sys_state.php index 9e70496a81927067e59eed5f60909d4f5fe709c3..fdfc47e25363cf5b52164f2eb75b44a5b411c037 100644 --- a/interface/web/monitor/show_sys_state.php +++ b/interface/web/monitor/show_sys_state.php @@ -557,6 +557,22 @@ function _processDbState($type, $serverId, $serverState, $messages) { break; } } + if ($type == 'mx_ip_match') { + switch ($record['state']) { + case 'ok': + $messages[$app->lng("monitor_serverstate_listok_txt")][] = $app->lng("monitor_serverstate_mx_ip_matchok_txt") . ' ' . + "<a href='#' data-load-content='monitor/show_data.php?type=mx_ip_match'>[" . $app->lng("monitor_serverstate_more_txt") . "]</a>"; + break; + case 'warning': + $messages[$app->lng("monitor_serverstate_listwarning_txt")][] = $app->lng("monitor_serverstate_mx_ip_matchwarning_txt") . ' ' . + "<a href='#' data-load-content='monitor/show_data.php?type=mx_ip_match'>[" . $app->lng("monitor_serverstate_more_txt") . "]</a>"; + break; + default: + $messages[$app->lng("monitor_serverstate_listunknown_txt")][] = $app->lng("monitor_serverstate_mx_ip_match_unknown_txt") . ' ' . + "<a href='#' data-load-content='monitor/show_data.php?type=mx_ip_match'>[" . $app->lng("monitor_serverstate_more_txt") . "]</a>"; + break; + } + } if ($type == 'sys_log') { switch ($record['state']) { diff --git a/server/lib/classes/cron.d/100-monitor_domain_mx.php b/server/lib/classes/cron.d/100-monitor_domain_mx.php new file mode 100755 index 0000000000000000000000000000000000000000..fe1494ddb5fc32ee251193c84d6daeec1485def8 --- /dev/null +++ b/server/lib/classes/cron.d/100-monitor_domain_mx.php @@ -0,0 +1,138 @@ +<?php + +/* +Copyright (c) 2020, Herman van Rink, Initfour websolutions +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of ISPConfig nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +class cronjob_monitor_domain_mx extends cronjob { + + // job schedule + protected $_schedule = '* 6 * * *'; + protected $_run_at_new = true; + + private $_tools = null; + + /* this function is optional if it contains no custom code */ + public function onPrepare() { + global $app; + + parent::onPrepare(); + } + + /* this function is optional if it contains no custom code */ + public function onBeforeRun() { + global $app; + + return parent::onBeforeRun(); + } + + public function onRunJob() { + global $app, $conf; + + $app->uses('getconf,functions'); + $mail_config = $app->getconf->get_server_config($conf['server_id'], 'server'); + + /* used for all monitor cronjobs */ + $app->load('monitor_tools'); + $this->_tools = new monitor_tools(); + /* end global section for monitor cronjobs */ + + + // Initialize data array + $data = array(); + + // the id of the server as int + $server_id = intval($conf['server_id']); + + $smtpin_ips = gethostbynamel($mail_config['hostname']); + $smtpin_ips_v6 = $app->functions->gethostbynamel6($mail_config['hostname']); + if ($smtpin_ips_v6) { + $smtpin_ips = array_merge($smtpin_ips, $smtpin_ips_v6); + } + $maildomains = $app->db->queryAllRecords("SELECT domain, active FROM mail_domain WHERE server_id = ?", $server_id); + if(is_array($maildomains)) { + foreach ($maildomains as $maildomain) { + $mx = array(); + $found_mx = getmxrr($maildomain['domain'], $mx); + + $app->log('mx re:' . print_r($mx, 1)); + + $first_mx = array_shift($mx); + $mx_ip = gethostbyname($first_mx); + + if (!in_array( $mx_ip, $smtpin_ips)) { + if ($maildomain['active'] == 'y') { + $app->log('Mail domain[' . $maildomain['domain'] . '] is active but the DNS does not match our IP.', LOGLEVEL_WARN); + $state = 'warning'; + $data[$maildomain['domain']] = 'Domain is active but the DNS does not match our IP.'; + } else { + + $app->log('Good, the mail domain[' . $maildomain['domain'] . '] is not active and DNS is not pointing to us.', LOGLEVEL_DEBUG); + } + } + else { + if ($maildomain['active'] == 'n') { + $app->log('DNS points to our IP but the mail domain[' . $maildomain['domain'] . '] is not active.', LOGLEVEL_WARN); + $state = 'warning'; + $data[$maildomain['domain']] = 'DNS points to our IP but the mail domain is not active.'; + } + else { + // DNS OK. + } + } + } + } + + $res = array(); + $res['server_id'] = $server_id; + $res['type'] = 'mx_ip_match'; + $res['data'] = $data; + $res['state'] = $state; + + /** + * Insert the data into the database + */ + $sql = 'REPLACE INTO monitor_data (server_id, type, created, data, state) ' . + 'VALUES (?, ?, UNIX_TIMESTAMP(), ?, ?)'; + $app->dbmaster->query($sql, $res['server_id'], $res['type'], serialize($res['data']), $res['state']); + + // The new data is written, now we can delete the old one. + $this->_tools->delOldRecords($res['type'], $res['server_id']); + + parent::onRunJob(); + } + + /* this function is optional if it contains no custom code */ + public function onAfterRun() { + global $app; + + parent::onAfterRun(); + } + +} + +?> diff --git a/server/lib/classes/functions.inc.php b/server/lib/classes/functions.inc.php index 5296c3012b65cb4bf0d9889c893252e99ec9d4a8..7cd5a62da50eda0777e973a16ffc4699df11b12b 100644 --- a/server/lib/classes/functions.inc.php +++ b/server/lib/classes/functions.inc.php @@ -481,6 +481,63 @@ class functions { } } + /** + * From https://www.php.net/manual/en/function.gethostbyname.php#70936 + */ + public function gethostbyname6($host, $try_a = false) { + // get AAAA record for $host + // if $try_a is true, if AAAA fails, it tries for A + // the first match found is returned + // otherwise returns false + + $dns = gethostbynamel6($host, $try_a); + if ($dns == false) { return false; } + else { return $dns[0]; } + } + + /** + * From https://www.php.net/manual/en/function.gethostbyname.php#70936 + */ + public function gethostbynamel6($host, $try_a = false) { + // get AAAA records for $host, + // if $try_a is true, if AAAA fails, it tries for A + // results are returned in an array of ips found matching type + // otherwise returns false + + $dns6 = dns_get_record($host, DNS_AAAA); + if ($try_a == true) { + $dns4 = dns_get_record($host, DNS_A); + $dns = array_merge($dns4, $dns6); + } + else { $dns = $dns6; } + $ip6 = array(); + $ip4 = array(); + foreach ($dns as $record) { + if ($record["type"] == "A") { + $ip4[] = $record["ip"]; + } + if ($record["type"] == "AAAA") { + $ip6[] = $record["ipv6"]; + } + } + if (count($ip6) < 1) { + if ($try_a == true) { + if (count($ip4) < 1) { + return false; + } + else { + return $ip4; + } + } + else { + return false; + } + } + else { + return $ip6; + } + } + } ?>