From e1897b0631db96a9d05604f2eb38132122edb099 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 19 Mar 2020 12:23:48 +0100 Subject: [PATCH] [Web] Allow to set global sieve filters --- data/web/inc/functions.mailbox.inc.php | 88 ++++++++++++++++++++++++++ data/web/inc/functions.rspamd.inc.php | 2 +- data/web/js/site/mailbox.js | 11 ++-- data/web/json_api.php | 33 ++++++++++ data/web/lang/lang.cs.json | 2 +- data/web/lang/lang.de.json | 6 +- data/web/lang/lang.en.json | 6 +- data/web/lang/lang.ru.json | 2 +- data/web/lang/lang.sk.json | 2 +- data/web/lang/lang.sv.json | 2 +- data/web/mailbox.php | 47 +++++++++++++- data/web/modals/mailbox.php | 8 +-- 12 files changed, 193 insertions(+), 16 deletions(-) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index d73af1de..a60f472b 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -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)) { diff --git a/data/web/inc/functions.rspamd.inc.php b/data/web/inc/functions.rspamd.inc.php index 0753a009..77b70f49 100644 --- a/data/web/inc/functions.rspamd.inc.php +++ b/data/web/inc/functions.rspamd.inc.php @@ -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); diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index 55c6e97e..bafe8644 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -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); }, diff --git a/data/web/json_api.php b/data/web/json_api.php index f321e914..a04cb491 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -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": diff --git a/data/web/lang/lang.cs.json b/data/web/lang/lang.cs.json index 1dcfe083..6c4025c4 100644 --- a/data/web/lang/lang.cs.json +++ b/data/web/lang/lang.cs.json @@ -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.
\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ů.
\r\nGlobal sieve prefilter → Prefilter → Uživatelské skripty → Postfilter → Global sieve postfilter", + "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.
\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ů.

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", diff --git a/data/web/lang/lang.de.json b/data/web/lang/lang.de.json index 06bf77f7..d49f7163 100644 --- a/data/web/lang/lang.de.json +++ b/data/web/lang/lang.de.json @@ -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.
\r\nDie Ausführung erfolgt in nachstehender Reihenfolge. Ein fehlgeschlagenes Script sowie der Befehl \"keep;\" stoppen die weitere Verarbeitung nicht.
\r\nGlobal sieve prefilter → Prefilter → User scripts → Postfilter → Global sieve postfilter", + "sieve_info": "Es können mehrere Filter pro Benutzer existieren, aber nur ein Filter eines Typs (Pre-/Postfilter) kann gleichzeitig aktiv sein.
\r\nDie Ausführung erfolgt in nachstehender Reihenfolge. Ein fehlgeschlagenes Script sowie der Befehl \"keep;\" stoppen die weitere Verarbeitung nicht.

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", diff --git a/data/web/lang/lang.en.json b/data/web/lang/lang.en.json index c27508dc..206236d5 100644 --- a/data/web/lang/lang.en.json +++ b/data/web/lang/lang.en.json @@ -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.
\r\nEach filter will be processed in the described order. Neither a failed script nor an issued \"keep;\" will stop processing of further scripts.
\r\nGlobal sieve prefilter → Prefilter → User scripts → Postfilter → Global sieve postfilter", + "sieve_info": "You can store multiple filters per user, but only one prefilter and one postfilter can be active at the same time.
\r\nEach filter will be processed in the described order. Neither a failed script nor an issued \"keep;\" will stop processing of further scripts.

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", diff --git a/data/web/lang/lang.ru.json b/data/web/lang/lang.ru.json index 6fc69b03..ceeb5c72 100644 --- a/data/web/lang/lang.ru.json +++ b/data/web/lang/lang.ru.json @@ -633,7 +633,7 @@ "running": "В процессе выполнения", "set_postfilter": "Использовать как постфильтр", "set_prefilter": "Использовать как предв. фильтр", - "sieve_info": "Вы можете сохранить несколько фильтров для каждого пользователя, но только один предварительный фильтр и один постфильтр могут быть активными одновременно.
\r\n Каждый фильтр будет обработан в описанном порядке. Не сломаный скрипт, не keep; не остановит обработку дальнейших скриптов.
\r\n Global sieve prefilter → Prefilter → User scripts → Postfilter → Global sieve postfilter", + "sieve_info": "Вы можете сохранить несколько фильтров для каждого пользователя, но только один предварительный фильтр и один постфильтр могут быть активными одновременно.
\r\n Каждый фильтр будет обработан в описанном порядке. Не сломаный скрипт, не keep; не остановит обработку дальнейших скриптов.

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", diff --git a/data/web/lang/lang.sk.json b/data/web/lang/lang.sk.json index 4cec7c21..138f7a47 100644 --- a/data/web/lang/lang.sk.json +++ b/data/web/lang/lang.sk.json @@ -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.
\r\n Každý filter bude spracovaný v nastavenom poradí. Ani zlyhanie skriptu alebo zadržanie nezastaví spracovanie ďalších skriptov.
\r\nGlobálny sieve prefilter → Prefilter → Skripty užívateľa → Postfilter → Globálny sieve postfilter", + "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.
\r\n Každý filter bude spracovaný v nastavenom poradí. Ani zlyhanie skriptu alebo zadržanie nezastaví spracovanie ďalších skriptov.

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", diff --git a/data/web/lang/lang.sv.json b/data/web/lang/lang.sv.json index 1e4d6ca4..73990530 100644 --- a/data/web/lang/lang.sv.json +++ b/data/web/lang/lang.sv.json @@ -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.
\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.
\r\nGlobala sieve förfilter → Prefilter → User scripts → Postfilter → Global sieve postfilter", + "sieve_info": "Du kan skapa flera filter per användare, men bara ett förfilter och ett postfilter kan vara aktiva samtidigt.
\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.

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", diff --git a/data/web/mailbox.php b/data/web/mailbox.php index 5dd69306..ab35c908 100644 --- a/data/web/mailbox.php +++ b/data/web/mailbox.php @@ -328,7 +328,9 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; -

+
+


+
@@ -349,6 +351,49 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+
+ +
+
+
Global Prefilter
+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
Global Postfilter
+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php index 68dec41d..b86d185c 100644 --- a/data/web/modals/mailbox.php +++ b/data/web/modals/mailbox.php @@ -592,7 +592,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
- +
@@ -604,9 +604,9 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
-
- - +
+ +