Forwarding hosts: use SPF records if present
This commit is contained in:
@@ -98,6 +98,8 @@ function init_db_schema() {
|
||||
'msg' => 'Database initialization completed.'
|
||||
);
|
||||
}
|
||||
// Add newly added tables
|
||||
$stmt = $pdo->query("CREATE TABLE IF NOT EXISTS `forwarding_hosts` (`host` VARCHAR(255) NOT NULL, PRIMARY KEY (`host`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC");
|
||||
// Add newly added columns
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'kind'");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
@@ -124,9 +126,11 @@ function init_db_schema() {
|
||||
if ($num_results == 0) {
|
||||
$pdo->query("ALTER TABLE `tfa` ADD `key_id` VARCHAR(255) DEFAULT 'unidentified'");
|
||||
}
|
||||
|
||||
// Add newly added tables
|
||||
$stmt = $pdo->query("CREATE TABLE IF NOT EXISTS `forwarding_hosts` (`host` VARCHAR(255) NOT NULL, PRIMARY KEY (`host`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC");
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM `forwarding_hosts` LIKE 'source'");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
if ($num_results == 0) {
|
||||
$pdo->query("ALTER TABLE `forwarding_hosts` ADD `source` VARCHAR(255) DEFAULT ''");
|
||||
}
|
||||
}
|
||||
function verify_ssha256($hash, $password) {
|
||||
// Remove tag if any
|
||||
@@ -5044,11 +5048,12 @@ function get_u2f_registrations($username) {
|
||||
}
|
||||
function get_forwarding_hosts() {
|
||||
global $pdo;
|
||||
$sel = $pdo->prepare("SELECT host FROM `forwarding_hosts`");
|
||||
$sel = $pdo->prepare("SELECT host, source FROM `forwarding_hosts`");
|
||||
$sel->execute();
|
||||
return $sel->fetchAll(PDO::FETCH_COLUMN);
|
||||
return $sel->fetchAll(PDO::FETCH_OBJ);
|
||||
}
|
||||
function add_forwarding_host($postarray) {
|
||||
require_once 'spf.inc.php';
|
||||
global $pdo;
|
||||
global $lang;
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
@@ -5058,23 +5063,47 @@ function add_forwarding_host($postarray) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$source = $postarray['hostname'];
|
||||
$host = $postarray['hostname'];
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO `forwarding_hosts` (`host`) VALUES (:host)");
|
||||
$stmt->execute(array(
|
||||
':host' => $host,
|
||||
));
|
||||
$hosts = array();
|
||||
if (preg_match('/^[0-9a-fA-F:\/]+$/', $host)) { // IPv6 address
|
||||
$hosts = array($host);
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
elseif (preg_match('/^[0-9\.\/]+$/', $host)) { // IPv4 address
|
||||
$hosts = array($host);
|
||||
}
|
||||
else {
|
||||
$hosts = get_outgoing_hosts_best_guess($host);
|
||||
}
|
||||
if (!$hosts)
|
||||
{
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
'msg' => 'Invalid host specified: '. htmlspecialchars($host)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
foreach ($hosts as $host) {
|
||||
if ($source == $host)
|
||||
$source = '';
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT IGNORE INTO `forwarding_hosts` (`host`, `source`) VALUES (:host, :source)");
|
||||
$stmt->execute(array(
|
||||
':host' => $host,
|
||||
':source' => $source,
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'MySQL: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$_SESSION['return'] = array(
|
||||
'type' => 'success',
|
||||
'msg' => sprintf($lang['success']['forwarding_host_added'], htmlspecialchars($host))
|
||||
'msg' => sprintf($lang['success']['forwarding_host_added'], htmlspecialchars(implode(', ', $hosts)))
|
||||
);
|
||||
}
|
||||
function delete_forwarding_host($postarray) {
|
||||
|
@@ -142,6 +142,7 @@ CREATE TABLE IF NOT EXISTS `tfa` (
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `forwarding_hosts` (
|
||||
`host` VARCHAR(255) NOT NULL,
|
||||
`source` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`host`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
|
||||
|
||||
|
127
data/web/inc/spf.inc.php
Normal file
127
data/web/inc/spf.inc.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
function get_spf_allowed_hosts($domain)
|
||||
{
|
||||
$hosts = array();
|
||||
|
||||
$records = dns_get_record($domain, DNS_TXT);
|
||||
foreach ($records as $record)
|
||||
{
|
||||
$txt = explode(' ', $record['entries'][0]);
|
||||
if (array_shift($txt) != 'v=spf1') // only handle SPF records
|
||||
continue;
|
||||
|
||||
foreach ($txt as $mech)
|
||||
{
|
||||
$qual = substr($mech, 0, 1);
|
||||
if ($qual == '-' || $qual == '~') // only handle pass or neutral records
|
||||
continue(2);
|
||||
|
||||
if ($qual == '+' || $qual == '?')
|
||||
$mech = substr($mech, 1); // remove the qualifier
|
||||
|
||||
if (strpos($mech, '=') !== FALSE) // handle a modifier
|
||||
{
|
||||
$mod = explode('=', $mech);
|
||||
if ($mod[0] == 'redirect') // handle a redirect
|
||||
{
|
||||
$hosts = get_spf_allowed_hosts($mod[1]);
|
||||
return $hosts;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unset($cidr);
|
||||
if (strpos($mech, ':') !== FALSE) // handle a domain specification
|
||||
{
|
||||
$split = explode(':', $mech);
|
||||
$mech = array_shift($split);
|
||||
$domain = implode(':', $split);
|
||||
if (strpos($domain, '/') !== FALSE) // remove CIDR specification
|
||||
{
|
||||
$split = explode('/', $domain);
|
||||
$domain = $split[0];
|
||||
$cidr = $split[1];
|
||||
}
|
||||
}
|
||||
|
||||
$new_hosts = array();
|
||||
if ($mech == 'include') // handle an inclusion
|
||||
{
|
||||
$new_hosts = get_spf_allowed_hosts($domain);
|
||||
}
|
||||
elseif ($mech == 'a') // handle a mechanism
|
||||
{
|
||||
$new_hosts = get_a_hosts($domain);
|
||||
}
|
||||
elseif ($mech == 'mx') // handle mx mechanism
|
||||
{
|
||||
$new_hosts = get_mx_hosts($domain);
|
||||
}
|
||||
elseif ($mech == 'ip4' || $mech == 'ip6') // handle ip mechanism
|
||||
{
|
||||
$new_hosts = array($domain);
|
||||
}
|
||||
|
||||
if (isset($cidr)) // add CIDR specification if present
|
||||
{
|
||||
foreach ($new_hosts as &$host)
|
||||
{
|
||||
$host .= '/' . $cidr;
|
||||
}
|
||||
unset($host);
|
||||
}
|
||||
|
||||
$hosts = array_unique(array_merge($hosts,$new_hosts), SORT_REGULAR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $hosts;
|
||||
}
|
||||
|
||||
function get_mx_hosts($domain)
|
||||
{
|
||||
$hosts = array();
|
||||
|
||||
$mx_records = dns_get_record($domain, DNS_MX);
|
||||
foreach ($mx_records as $mx_record)
|
||||
{
|
||||
$new_hosts = get_a_hosts($mx_record['target']);
|
||||
$hosts = array_unique(array_merge($hosts,$new_hosts), SORT_REGULAR);
|
||||
}
|
||||
|
||||
return $hosts;
|
||||
}
|
||||
|
||||
function get_a_hosts($domain)
|
||||
{
|
||||
$hosts = array();
|
||||
|
||||
$a_records = dns_get_record($domain, DNS_A);
|
||||
foreach ($a_records as $a_record)
|
||||
{
|
||||
$hosts[] = $a_record['ip'];
|
||||
}
|
||||
$a_records = dns_get_record($domain, DNS_AAAA);
|
||||
foreach ($a_records as $a_record)
|
||||
{
|
||||
$hosts[] = $a_record['ipv6'];
|
||||
}
|
||||
|
||||
return $hosts;
|
||||
}
|
||||
|
||||
function get_outgoing_hosts_best_guess($domain)
|
||||
{
|
||||
// try the SPF record to get hosts that are allowed to send outgoing mails for this domain
|
||||
$hosts = get_spf_allowed_hosts($domain);
|
||||
if ($hosts) return $hosts;
|
||||
|
||||
// try the MX record to get mail servers for this domain
|
||||
$hosts = get_mx_hosts($domain);
|
||||
if ($hosts) return $hosts;
|
||||
|
||||
// fall back to the A record to get the host name for this domain
|
||||
return get_a_hosts($domain);
|
||||
}
|
||||
?>
|
Reference in New Issue
Block a user