1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2025-12-15 19:06:03 +00:00

[Rspamd][Web] Internal alias support

This commit is contained in:
FreddleSpl0it
2025-09-09 10:37:54 +02:00
parent 06db1d6a72
commit f67c0530f5
8 changed files with 86 additions and 16 deletions

View File

@@ -56,7 +56,7 @@ function normalize_email($email) {
$email = explode('@', $email); $email = explode('@', $email);
$email[0] = str_replace('.', '', $email[0]); $email[0] = str_replace('.', '', $email[0]);
$email = implode('@', $email); $email = implode('@', $email);
} }
$gm_alt = "@googlemail.com"; $gm_alt = "@googlemail.com";
if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) { if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) {
$email = explode('@', $email); $email = explode('@', $email);
@@ -114,7 +114,7 @@ function ucl_rcpts($object, $type) {
$rcpt[] = str_replace('/', '\/', $row['address']); $rcpt[] = str_replace('/', '\/', $row['address']);
} }
// Aliases by alias domains // Aliases by alias domains
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox` $stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox`
LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain` LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain`
WHERE `mailbox`.`username` = :object"); WHERE `mailbox`.`username` = :object");
$stmt->execute(array( $stmt->execute(array(
@@ -184,7 +184,7 @@ while ($row = array_shift($rows)) {
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>; rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
<?php <?php
} }
$stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf` $stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel') WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel')
AND `object`= :object"); AND `object`= :object");
$stmt->execute(array(':object' => $row['object'])); $stmt->execute(array(':object' => $row['object']));
@@ -468,4 +468,36 @@ while ($row = array_shift($rows)) {
<?php <?php
} }
?> ?>
<?php
// Start internal aliases
$stmt = $pdo->query("SELECT `id`, `address`, `domain` FROM `alias` WHERE `active` = '1' AND `internal` = '1'");
$aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($alias = array_shift($aliases)) {
// build allowed_domains regex and add target domain and alias domains
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `active` = '1' AND `target_domain` = :target_domain");
$stmt->execute(array(':target_domain' => $alias['domain']));
$allowed_domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
$allowed_domains = array_map(function($item) {
return str_replace('.', '\.', $item['alias_domain']);
}, $allowed_domains);
$allowed_domains[] = str_replace('.', '\.', $alias['domain']);
$allowed_domains = implode('|', $allowed_domains);
?>
internal_alias_<?=$alias['id'];?> {
priority = 10;
rcpt = "<?=$alias['address'];?>";
from = "/^((?!.*@(<?=$allowed_domains;?>)).)*$/";
apply "default" {
MAILCOW_INTERNAL_ALIAS = 9999.0;
}
symbols [
"MAILCOW_INTERNAL_ALIAS"
]
}
<?php
}
?>
} }

View File

@@ -684,15 +684,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return true; return true;
break; break;
case 'alias': case 'alias':
$addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address'])); $addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address']));
$gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto'])); $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto']));
$active = intval($_data['active']); $internal = intval($_data['internal']);
$sogo_visible = intval($_data['sogo_visible']); $active = intval($_data['active']);
$goto_null = intval($_data['goto_null']); $sogo_visible = intval($_data['sogo_visible']);
$goto_spam = intval($_data['goto_spam']); $goto_null = intval($_data['goto_null']);
$goto_ham = intval($_data['goto_ham']); $goto_spam = intval($_data['goto_spam']);
$goto_ham = intval($_data['goto_ham']);
$private_comment = $_data['private_comment']; $private_comment = $_data['private_comment'];
$public_comment = $_data['public_comment']; $public_comment = $_data['public_comment'];
if (strlen($private_comment) > 160 | strlen($public_comment) > 160){ if (strlen($private_comment) > 160 | strlen($public_comment) > 160){
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@@ -842,8 +843,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
continue; continue;
} }
$stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `active`) $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `internal`, `active`)
VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :active)"); VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :internal, :active)");
if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) { if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) {
$stmt->execute(array( $stmt->execute(array(
':address' => '@'.$domain, ':address' => '@'.$domain,
@@ -853,6 +854,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':goto' => $goto, ':goto' => $goto,
':domain' => $domain, ':domain' => $domain,
':sogo_visible' => $sogo_visible, ':sogo_visible' => $sogo_visible,
':internal' => $internal,
':active' => $active ':active' => $active
)); ));
} }
@@ -864,6 +866,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':goto' => $goto, ':goto' => $goto,
':domain' => $domain, ':domain' => $domain,
':sogo_visible' => $sogo_visible, ':sogo_visible' => $sogo_visible,
':internal' => $internal,
':active' => $active ':active' => $active
)); ));
} }
@@ -2481,6 +2484,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
foreach ($ids as $id) { foreach ($ids as $id) {
$is_now = mailbox('get', 'alias_details', $id); $is_now = mailbox('get', 'alias_details', $id);
if (!empty($is_now)) { if (!empty($is_now)) {
$internal = (isset($_data['internal'])) ? intval($_data['internal']) : $is_now['internal'];
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
$sogo_visible = (isset($_data['sogo_visible'])) ? intval($_data['sogo_visible']) : $is_now['sogo_visible']; $sogo_visible = (isset($_data['sogo_visible'])) ? intval($_data['sogo_visible']) : $is_now['sogo_visible'];
$goto_null = (isset($_data['goto_null'])) ? intval($_data['goto_null']) : 0; $goto_null = (isset($_data['goto_null'])) ? intval($_data['goto_null']) : 0;
@@ -2666,6 +2670,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`domain` = :domain, `domain` = :domain,
`goto` = :goto, `goto` = :goto,
`sogo_visible`= :sogo_visible, `sogo_visible`= :sogo_visible,
`internal`= :internal,
`active`= :active `active`= :active
WHERE `id` = :id"); WHERE `id` = :id");
$stmt->execute(array( $stmt->execute(array(
@@ -2675,6 +2680,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':domain' => $domain, ':domain' => $domain,
':goto' => $goto, ':goto' => $goto,
':sogo_visible' => $sogo_visible, ':sogo_visible' => $sogo_visible,
':internal' => $internal,
':active' => $active, ':active' => $active,
':id' => $is_now['id'] ':id' => $is_now['id']
)); ));
@@ -4700,6 +4706,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`address`, `address`,
`public_comment`, `public_comment`,
`private_comment`, `private_comment`,
`internal`,
`active`, `active`,
`sogo_visible`, `sogo_visible`,
`created`, `created`,
@@ -4730,6 +4737,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$aliasdata['goto'] = $row['goto']; $aliasdata['goto'] = $row['goto'];
$aliasdata['address'] = $row['address']; $aliasdata['address'] = $row['address'];
(!filter_var($aliasdata['address'], FILTER_VALIDATE_EMAIL)) ? $aliasdata['is_catch_all'] = 1 : $aliasdata['is_catch_all'] = 0; (!filter_var($aliasdata['address'], FILTER_VALIDATE_EMAIL)) ? $aliasdata['is_catch_all'] = 1 : $aliasdata['is_catch_all'] = 0;
$aliasdata['internal'] = $row['internal'];
$aliasdata['active'] = $row['active']; $aliasdata['active'] = $row['active'];
$aliasdata['active_int'] = $row['active']; $aliasdata['active_int'] = $row['active'];
$aliasdata['sogo_visible'] = $row['sogo_visible']; $aliasdata['sogo_visible'] = $row['sogo_visible'];

View File

@@ -184,6 +184,7 @@ function init_db_schema()
"private_comment" => "TEXT", "private_comment" => "TEXT",
"public_comment" => "TEXT", "public_comment" => "TEXT",
"sogo_visible" => "TINYINT(1) NOT NULL DEFAULT '1'", "sogo_visible" => "TINYINT(1) NOT NULL DEFAULT '1'",
"internal" => "TINYINT(1) NOT NULL DEFAULT '0'",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'" "active" => "TINYINT(1) NOT NULL DEFAULT '1'"
), ),
"keys" => array( "keys" => array(

View File

@@ -1972,6 +1972,15 @@ jQuery(function($){
data: 'private_comment', data: 'private_comment',
defaultContent: '' defaultContent: ''
}, },
{
title: lang.internal,
data: 'internal',
defaultContent: '',
responsivePriority: 6,
render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
}
},
{ {
title: lang.active, title: lang.active,
data: 'active', data: 'active',

View File

@@ -71,6 +71,8 @@
"goto_spam": "Nachrichten als <span class=\"text-danger\"><b>Spam</b></span> lernen", "goto_spam": "Nachrichten als <span class=\"text-danger\"><b>Spam</b></span> lernen",
"hostname": "Host", "hostname": "Host",
"inactive": "Inaktiv", "inactive": "Inaktiv",
"internal": "Intern",
"internal_info": "Interne Aliasse sind nur von der eigenen Domäne oder Alias-Domänen erreichbar.",
"kind": "Art", "kind": "Art",
"mailbox_quota_def": "Standard-Quota einer Mailbox", "mailbox_quota_def": "Standard-Quota einer Mailbox",
"mailbox_quota_m": "Max. Speicherplatz pro Mailbox (MiB)", "mailbox_quota_m": "Max. Speicherplatz pro Mailbox (MiB)",
@@ -690,6 +692,8 @@
"grant_types": "Grant-types", "grant_types": "Grant-types",
"hostname": "Servername", "hostname": "Servername",
"inactive": "Inaktiv", "inactive": "Inaktiv",
"internal": "Intern",
"internal_info": "Interne Aliasse sind nur von der eigenen Domäne oder Alias-Domänen erreichbar.",
"kind": "Art", "kind": "Art",
"last_modified": "Zuletzt geändert", "last_modified": "Zuletzt geändert",
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*\\.google\\.com</code>, um alle Ziele mit MX *google.com zu routen)", "lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*\\.google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
@@ -923,6 +927,7 @@
"in_use": "Prozentualer Gebrauch", "in_use": "Prozentualer Gebrauch",
"inactive": "Inaktiv", "inactive": "Inaktiv",
"insert_preset": "Beispiel \"%s\" laden", "insert_preset": "Beispiel \"%s\" laden",
"internal": "Intern",
"kind": "Art", "kind": "Art",
"last_mail_login": "Letzter Mail-Login", "last_mail_login": "Letzter Mail-Login",
"last_modified": "Zuletzt geändert", "last_modified": "Zuletzt geändert",

View File

@@ -71,6 +71,8 @@
"goto_spam": "Learn as <span class=\"text-danger\"><b>spam</b></span>", "goto_spam": "Learn as <span class=\"text-danger\"><b>spam</b></span>",
"hostname": "Host", "hostname": "Host",
"inactive": "Inactive", "inactive": "Inactive",
"internal": "Internal",
"internal_info": "Internal aliases are only accessible from the own domain or alias domains.",
"kind": "Kind", "kind": "Kind",
"mailbox_quota_def": "Default mailbox quota", "mailbox_quota_def": "Default mailbox quota",
"mailbox_quota_m": "Max. quota per mailbox (MiB)", "mailbox_quota_m": "Max. quota per mailbox (MiB)",
@@ -690,6 +692,8 @@
"grant_types": "Grant types", "grant_types": "Grant types",
"hostname": "Hostname", "hostname": "Hostname",
"inactive": "Inactive", "inactive": "Inactive",
"internal": "Internal",
"internal_info": "Internal aliases are only accessible from the own domain or alias domains.",
"kind": "Kind", "kind": "Kind",
"last_modified": "Last modified", "last_modified": "Last modified",
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)", "lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
@@ -928,6 +932,7 @@
"in_use": "In use (%)", "in_use": "In use (%)",
"inactive": "Inactive", "inactive": "Inactive",
"insert_preset": "Insert example preset \"%s\"", "insert_preset": "Insert example preset \"%s\"",
"internal": "Internal",
"kind": "Kind", "kind": "Kind",
"last_mail_login": "Last mail login", "last_mail_login": "Last mail login",
"last_modified": "Last modified", "last_modified": "Last modified",

View File

@@ -6,6 +6,7 @@
<br> <br>
<form class="form-horizontal" data-id="editalias" role="form" method="post"> <form class="form-horizontal" data-id="editalias" role="form" method="post">
<input type="hidden" value="0" name="active"> <input type="hidden" value="0" name="active">
<input type="hidden" value="0" name="internal">
{% if not skip_sogo %} {% if not skip_sogo %}
<input type="hidden" value="0" name="sogo_visible"> <input type="hidden" value="0" name="sogo_visible">
{% endif %} {% endif %}
@@ -33,8 +34,12 @@
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="sogo_visible"{% if result.sogo_visible == '1' %} checked{% endif %}> {{ lang.edit.sogo_visible }}</label> <label><input type="checkbox" class="form-check-input" value="1" name="sogo_visible"{% if result.sogo_visible == '1' %} checked{% endif %}> {{ lang.edit.sogo_visible }}</label>
</div> </div>
<p class="text-muted">{{ lang.edit.sogo_visible_info }}</p> <small class="text-muted d-block mb-2">{{ lang.edit.sogo_visible_info }}</small>
{% endif %} {% endif %}
<div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="internal"{% if result.internal == '1' %} checked{% endif %}> {{ lang.edit.internal }}</label>
</div>
<small class="text-muted d-block">{{ lang.edit.internal_info }}</small>
</div> </div>
</div> </div>
<hr> <hr>

View File

@@ -777,6 +777,7 @@
<div class="modal-body"> <div class="modal-body">
<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_alias"> <form class="form-horizontal" data-cached-form="true" role="form" data-id="add_alias">
<input type="hidden" value="0" name="active"> <input type="hidden" value="0" name="active">
<input type="hidden" value="0" name="internal">
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="address">{{ lang.add.alias_address }}</label> <label class="control-label col-sm-2 text-sm-end" for="address">{{ lang.add.alias_address }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
@@ -803,11 +804,15 @@
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="sogo_visible" checked> {{ lang.edit.sogo_visible }}</label> <label><input type="checkbox" class="form-check-input" value="1" name="sogo_visible" checked> {{ lang.edit.sogo_visible }}</label>
</div> </div>
<p class="text-muted">{{ lang.edit.sogo_visible_info }}</p> <small class="text-muted d-block mb-2">{{ lang.edit.sogo_visible_info }}</small>
{% endif %} {% endif %}
<div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="internal"> {{ lang.add.internal }}</label>
</div>
<small class="text-muted d-block">{{ lang.edit.internal_info }}</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-4">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="active" checked> {{ lang.add.active }}</label> <label><input type="checkbox" class="form-check-input" value="1" name="active" checked> {{ lang.add.active }}</label>