[Web] Allow to set global sieve filters

master
andryyy 2020-03-19 12:23:48 +01:00
parent 32ef5508a0
commit e1897b0631
No known key found for this signature in database
GPG Key ID: 8EC34FF2794E25EF
12 changed files with 193 additions and 16 deletions

View File

@ -62,6 +62,85 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'msg' => array('mailbox_modified', htmlspecialchars($_SESSION['mailcow_cc_username']))
);
break;
case 'global_filter':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
$sieve = new Sieve\SieveParser();
$script_data = $_data['script_data'];
$script_data = str_replace("\r\n", "\n", $script_data); // windows -> unix
$script_data = str_replace("\r", "\n", $script_data); // remaining -> unix
$filter_type = $_data['filter_type'];
try {
$sieve->parse($script_data);
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('sieve_error', $e->getMessage())
);
return false;
}
if ($filter_type == 'prefilter') {
try {
if (file_exists('/global_sieve/before')) {
$filter_handle = fopen('/global_sieve/before', 'w');
if (!$filter_handle) {
throw new Exception($lang['danger']['file_open_error']);
}
fwrite($filter_handle, $script_data);
fclose($filter_handle);
}
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('global_filter_write_error', htmlspecialchars($e->getMessage()))
);
return false;
}
}
elseif ($filter_type == 'postfilter') {
try {
if (file_exists('/global_sieve/after')) {
$filter_handle = fopen('/global_sieve/after', 'w');
if (!$filter_handle) {
throw new Exception($lang['danger']['file_open_error']);
}
fwrite($filter_handle, $script_data);
fclose($filter_handle);
}
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('global_filter_write_error', htmlspecialchars($e->getMessage()))
);
return false;
}
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'invalid_filter_type'
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'global_filter_written'
);
return true;
case 'filter':
$sieve = new Sieve\SieveParser();
if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
@ -2653,6 +2732,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
}
return $filters;
break;
case 'global_filter_details':
$global_filters = array();
if ($_SESSION['mailcow_cc_role'] != "admin") {
return false;
}
$global_filters['prefilter'] = file_get_contents('/global_sieve/before');
$global_filters['postfilter'] = file_get_contents('/global_sieve/after');
return $global_filters;
break;
case 'filter_details':
$filter_details = array();
if (!is_numeric($_data)) {

View File

@ -233,7 +233,7 @@ function rspamd($_action, $_data = null) {
$map_content = trim($_data['rspamd_map_data']);
$map_handle = fopen('/rspamd_custom_maps/' . $map, 'w');
if (!$map_handle) {
throw new Exception('File cannot be opened for writing.');
throw new Exception($lang['danger']['file_open_error']);
}
fwrite($map_handle, $map_content . PHP_EOL);
fclose($map_handle);

View File

@ -146,12 +146,15 @@ $(document).ready(function() {
});
// Disable submit button on script change
$('.textarea-code').on('keyup', function() {
$('#add_filter_btns > #add_sieve_script').attr({"disabled": true});
// Disable all "save" buttons, could be a "related button only" function, todo
$('.add_sieve_script').attr({"disabled": true});
});
// Validate script data
$("#validate_sieve").click(function( event ) {
$(".validate_sieve").click(function( event ) {
event.preventDefault();
var script = $('#script_data').val();
var validation_button = $(this);
// Get script_data textarea content from form the button was clicked in
var script = $('textarea[name="script_data"]', $(this).parents('form:first')).val();
$.ajax({
dataType: 'json',
url: "/inc/ajax/sieve_validation.php",
@ -161,7 +164,7 @@ $(document).ready(function() {
var response = (data.responseText);
response_obj = JSON.parse(response);
if (response_obj.type == "success") {
$('#add_filter_btns > #add_sieve_script').attr({"disabled": false});
$(validation_button).next().attr({"disabled": false});
}
mailcow_alert_box(response_obj.msg, response_obj.type);
},

View File

@ -167,6 +167,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
case "filter":
process_add_return(mailbox('add', 'filter', $attr));
break;
case "global-filter":
process_add_return(mailbox('add', 'global_filter', $attr));
break;
case "domain-policy":
process_add_return(policy('add', 'domain', $attr));
break;
@ -332,6 +335,36 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
}
break;
case "global_filters":
$global_filters = mailbox('get', 'global_filter_details');
switch ($object) {
case "all":
if (!empty($global_filters)) {
process_get_return($global_filters);
}
else {
echo '{}';
}
break;
case "prefilter":
if (!empty($global_filters['prefilter'])) {
process_get_return($global_filters['prefilter']);
}
else {
echo '{}';
}
break;
case "postfilter":
if (!empty($global_filters['postfilter'])) {
process_get_return($global_filters['postfilter']);
}
else {
echo '{}';
}
break;
}
break;
case "rl-domain":
switch ($object) {
case "all":

View File

@ -594,7 +594,7 @@
"last_run": "Naposledy spuštěno",
"excludes": "Vyloučené",
"last_run_reset": "Plánovat další",
"sieve_info": "Můžete uložit více filtrů pro každého uživatele, ale současně může být aktivní pouze jeden prefilter a jeden postfilter.<br>\r\nKaždý filtr bude proveden v daném pořadí. Ani chyba při vykonávání skriptu nebo snaha o pozdržení nezastaví vykonání dalších skriptů.<br>\r\n<a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_before\" target=\"_blank\">Global sieve prefilter</a> → Prefilter → Uživatelské skripty → Postfilter → <a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_after\" target=\"_blank\">Global sieve postfilter</a>",
"sieve_info": "Můžete uložit více filtrů pro každého uživatele, ale současně může být aktivní pouze jeden prefilter a jeden postfilter.<br>\r\nKaždý filtr bude proveden v daném pořadí. Ani chyba při vykonávání skriptu nebo snaha o pozdržení nezastaví vykonání dalších skriptů.<br><br>Global sieve prefilter → Prefilter → Uživatelské skripty → Postfilter → Global sieve postfilter",
"sogo_visible": "Alias dostupný v SOGo",
"sogo_visible_y": "Zobrazit alias v SOGo",
"sogo_visible_n": "Skrýt alias v SOGo",

View File

@ -24,6 +24,8 @@
"apps": "Apps"
},
"danger": {
"invalid_filter_type": "Ungültiger Filtertyp",
"file_open_error": "Datei kann nicht zum Schreiben geöffnet werden",
"transport_dest_exists": "Transport Maps Ziel \"%s\" existiert bereits",
"unlimited_quota_acl": "Unendliche Quota untersagt durch ACL",
"mysql_error": "MySQL Fehler: %s",
@ -60,6 +62,7 @@
"settings_map_invalid": "Regel ID %s ist ungültig",
"app_passwd_id_invalid": "App Passwort ID %s ist ungültig",
"global_map_invalid": "Rspamd Map %s ist ungültig",
"global_filter_write_error": "Kann Filterdatei nicht schreiben: %s",
"global_map_write_error": "Kann globale Map ID %s nicht schreiben: %s",
"invalid_host": "Ungültiger Host: %s",
"relayhost_invalid": "Mapeintrag %s ist ungültig",
@ -132,6 +135,7 @@
"extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain"
},
"success": {
"global_filter_written": "Filterdatei wurde erfolreich geschrieben",
"learned_ham": "ID %s wurde erfolreich als Ham gelernt",
"verified_totp_login": "TOTP Anmeldung verifiziert",
"verified_u2f_login": "U2F Anmeldung verifiziert",
@ -632,7 +636,7 @@
"last_run": "Letzte Ausführung",
"last_run_reset": "Als nächstes ausführen",
"excludes": "Ausschlüsse",
"sieve_info": "Es können mehrere Filter pro Benutzer existieren, aber nur ein Filter eines Typs (Pre-/Postfilter) kann gleichzeitig aktiv sein.<br>\r\nDie Ausführung erfolgt in nachstehender Reihenfolge. Ein fehlgeschlagenes Script sowie der Befehl \"keep;\" stoppen die weitere Verarbeitung <b>nicht</b>.<br>\r\n<a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_before\" target=\"_blank\">Global sieve prefilter</a> → Prefilter → User scripts → Postfilter → <a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_after\" target=\"_blank\">Global sieve postfilter</a>",
"sieve_info": "Es können mehrere Filter pro Benutzer existieren, aber nur ein Filter eines Typs (Pre-/Postfilter) kann gleichzeitig aktiv sein.<br>\r\nDie Ausführung erfolgt in nachstehender Reihenfolge. Ein fehlgeschlagenes Script sowie der Befehl \"keep;\" stoppen die weitere Verarbeitung <b>nicht</b>.<br><br>Global sieve prefilter → Prefilter → User scripts → Postfilter → Global sieve postfilter",
"sogo_visible": "Alias Sichtbarkeit in SOGo",
"sogo_visible_y": "Alias in SOGo anzeigen",
"sogo_visible_n": "Alias in SOGo verbergen",

View File

@ -24,6 +24,8 @@
"hibp_ok": "No match found."
},
"danger": {
"invalid_filter_type": "Invalid filter type",
"file_open_error": "File cannot be opened for writing",
"transport_dest_exists": "Transport destination \"%s\" exists",
"unlimited_quota_acl": "Unlimited quota prohibited by ACL",
"mysql_error": "MySQL error: %s",
@ -61,6 +63,7 @@
"app_passwd_id_invalid": "App password ID %s invalid",
"global_map_invalid": "Global map ID %s invalid",
"global_map_write_error": "Could not write global map ID %s: %s",
"global_filter_write_error": "Could not write filter file: %s",
"invalid_host": "Invalid host specified: %s",
"relayhost_invalid": "Map entry %s is invalid",
"dkim_domain_or_sel_invalid": "DKIM domain or selector invalid: %s",
@ -132,6 +135,7 @@
"extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain"
},
"success": {
"global_filter_written": "Filter was successfully written to file",
"learned_ham": "Successfully learned ID % as ham",
"verified_totp_login": "Verified TOTP login",
"verified_u2f_login": "Verified U2F login",
@ -634,7 +638,7 @@
"last_run": "Last run",
"excludes": "Excludes",
"last_run_reset": "Schedule next",
"sieve_info": "You can store multiple filters per user, but only one prefilter and one postfilter can be active at the same time.<br>\r\nEach filter will be processed in the described order. Neither a failed script nor an issued \"keep;\" will stop processing of further scripts.<br>\r\n<a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_before\" target=\"_blank\">Global sieve prefilter</a> → Prefilter → User scripts → Postfilter → <a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_after\" target=\"_blank\">Global sieve postfilter</a>",
"sieve_info": "You can store multiple filters per user, but only one prefilter and one postfilter can be active at the same time.<br>\r\nEach filter will be processed in the described order. Neither a failed script nor an issued \"keep;\" will stop processing of further scripts.<br><br>Global sieve prefilter → Prefilter → User scripts → Postfilter → Global sieve postfilter",
"sogo_visible": "Alias is visible in SOGo",
"sogo_visible_y": "Show alias in SOGo",
"sogo_visible_n": "Hide alias in SOGo",

View File

@ -633,7 +633,7 @@
"running": "В процессе выполнения",
"set_postfilter": "Использовать как постфильтр",
"set_prefilter": "Использовать как предв. фильтр",
"sieve_info": "Вы можете сохранить несколько фильтров для каждого пользователя, но только один предварительный фильтр и один постфильтр могут быть активными одновременно.<br>\r\n Каждый фильтр будет обработан в описанном порядке. Не сломаный скрипт, не <code>keep;</code> не остановит обработку дальнейших скриптов.<br>\r\n <a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_before\" target=\"_blank\">Global sieve prefilter</a> → Prefilter → User scripts → Postfilter → <a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_after\" target=\"_blank\">Global sieve postfilter</a>",
"sieve_info": "Вы можете сохранить несколько фильтров для каждого пользователя, но только один предварительный фильтр и один постфильтр могут быть активными одновременно.<br>\r\n Каждый фильтр будет обработан в описанном порядке. Не сломаный скрипт, не <code>keep;</code> не остановит обработку дальнейших скриптов.<br><br>Global sieve prefilter → Prefilter → User scripts → Postfilter → Global sieve postfilter",
"sieve_preset_1": "Discard mail with probable dangerous file types",
"sieve_preset_2": "Always mark the e-mail of a specific sender as seen",
"sieve_preset_3": "Discard silently, stop all further sieve processing",

View File

@ -634,7 +634,7 @@
"last_run": "Posledné spustenie",
"excludes": "Vyraďuje",
"last_run_reset": "Naplánovať ďalší",
"sieve_info": "Môžete uchovávať viacero filtrov pre užívateľa, ale iba jeden prefilter a jeden postfilter môže byť aktívny v daný okamih.<br>\r\n Každý filter bude spracovaný v nastavenom poradí. Ani zlyhanie skriptu alebo zadržanie nezastaví spracovanie ďalších skriptov.<br>\r\n<a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_before\" target=\"_blank\">Globálny sieve prefilter</a> → Prefilter → Skripty užívateľa → Postfilter → <a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_after\" target=\"_blank\">Globálny sieve postfilter</a>",
"sieve_info": "Môžete uchovávať viacero filtrov pre užívateľa, ale iba jeden prefilter a jeden postfilter môže byť aktívny v daný okamih.<br>\r\n Každý filter bude spracovaný v nastavenom poradí. Ani zlyhanie skriptu alebo zadržanie nezastaví spracovanie ďalších skriptov.<br><br>Globálny sieve prefilter → Prefilter → Skripty užívateľa → Postfilter → Globálny sieve postfilter",
"sogo_visible": "Alias je viditeľný v SOGo",
"sogo_visible_y": "Ukázať alias v SOGo",
"sogo_visible_n": "Skryť alias v SOGo",

View File

@ -634,7 +634,7 @@
"last_run": "Senaste körningen",
"excludes": "Exkluderar",
"last_run_reset": "Schemalägg nästa",
"sieve_info": "Du kan skapa flera filter per användare, men bara ett förfilter och ett postfilter kan vara aktiva samtidigt.<br>\r\nVarje filter bearbetas i den beskrivna ordningen nedan. Varken ett misslyckat filter som kör fel, eller kommandot \"keep:\" stoppar bearbetningen av övriga filter.<br>\r\n<a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_before\" target=\"_blank\">Globala sieve förfilter</a> → Prefilter → User scripts → Postfilter → <a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_after\" target=\"_blank\">Global sieve postfilter</a>",
"sieve_info": "Du kan skapa flera filter per användare, men bara ett förfilter och ett postfilter kan vara aktiva samtidigt.<br>\r\nVarje filter bearbetas i den beskrivna ordningen nedan. Varken ett misslyckat filter som kör fel, eller kommandot \"keep:\" stoppar bearbetningen av övriga filter.<br><br>Globala sieve förfilter → Prefilter → User scripts → Postfilter → Global sieve postfilter",
"sogo_visible": "Visa detta alias i SOGo",
"sogo_visible_y": "Visa alias i SOGo",
"sogo_visible_n": "Dölj alias i SOGo",

View File

@ -328,7 +328,9 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
</ul>
</div>
</div>
<p style="margin:10px" class="help-block"><?=$lang['mailbox']['sieve_info'];?></p>
<div class="panel-body">
<p class="help-block"><?=$lang['mailbox']['sieve_info'];?></p><br>
</div>
<!-- <div class="mass-actions-mailbox" data-actions-header="true"></div> -->
<div class="table-responsive">
<table class="table table-striped" id="filter_table"></table>
@ -349,6 +351,49 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
<a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addFilterModalAdmin"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_filter'];?></a>
</div>
</div>
<div class="panel-body">
<?php
$global_filters = mailbox('get', 'global_filter_details');
?>
<div class="row">
<div class="col-lg-6">
<h5>Global Prefilter</h5>
<form class="form-horizontal" data-cached-form="false" role="form" data-id="add_prefilter">
<div class="form-group">
<div class="col-sm-12">
<textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code script_data" rows="10" name="script_data" required><?=$global_filters['prefilter'];?></textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-10 add_filter_btns">
<div class="btn-group">
<button class="btn btn-sm btn-default validate_sieve" href="#"><?=$lang['add']['validate'];?></button>
<button class="btn btn-sm btn-success add_sieve_script" data-action="add_item" data-id="add_prefilter" data-api-url='add/global-filter' data-api-attr='{"filter_type":"prefilter"}' href="#" disabled><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
</div>
</div>
</div>
</form>
</div>
<div class="col-lg-6">
<h5>Global Postfilter</h5>
<form class="form-horizontal" data-cached-form="false" role="form" data-id="add_postfilter">
<div class="form-group">
<div class="col-sm-12">
<textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code script_data" rows="10" name="script_data" required><?=$global_filters['postfilter'];?></textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-10 add_filter_btns">
<div class="btn-group">
<button class="btn btn-sm btn-default validate_sieve" href="#"><?=$lang['add']['validate'];?></button>
<button class="btn btn-sm btn-success add_sieve_script" data-action="add_item" data-id="add_postfilter" data-api-url='add/global-filter' data-api-attr='{"filter_type":"postfilter"}' href="#" disabled><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>

View File

@ -592,7 +592,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
<div class="form-group">
<label class="control-label col-sm-2" for="script_data">Script:</label>
<div class="col-sm-10">
<textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code" rows="20" id="script_data" name="script_data" required></textarea>
<textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code script_data" rows="20" name="script_data" required></textarea>
</div>
</div>
<div class="form-group">
@ -604,9 +604,9 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10" id="add_filter_btns">
<button class="btn btn-default" id="validate_sieve" href="#"><?=$lang['add']['validate'];?></button>
<button class="btn btn-success" id="add_sieve_script" data-action="add_item" data-id="add_filter" data-api-url='add/filter' data-api-attr='{}' href="#" disabled><?=$lang['admin']['add'];?></button>
<div class="col-sm-offset-2 col-sm-10 add_filter_btns">
<button class="btn btn-default validate_sieve" href="#"><?=$lang['add']['validate'];?></button>
<button class="btn btn-success add_sieve_script" data-action="add_item" data-id="add_filter" data-api-url='add/filter' data-api-attr='{}' href="#" disabled><?=$lang['admin']['add'];?></button>
</div>
</div>
</form>