From 0413d268552b845916b94eda54bc3dba0ee12798 Mon Sep 17 00:00:00 2001 From: Josh <75700a85-1205-4740-aebf-4413a352e81b@otake.pw> Date: Thu, 13 Nov 2025 07:05:01 -0800 Subject: [PATCH] Allow making spam aliases permanent (#6888) * Allow making spam aliases permanent * added german translation * updated Spamalias Twig + Rename in Spam Alias * compose: update image tags to align to vendor version --------- Co-authored-by: DerLinkman --- data/Dockerfiles/phpfpm/docker-entrypoint.sh | 2 +- data/Dockerfiles/postfix/postfix.sh | 4 +-- data/web/inc/functions.mailbox.inc.php | 37 ++++++++++++++------ data/web/inc/init_db.inc.php | 5 +-- data/web/js/site/user.js | 22 ++++++++++-- data/web/lang/lang.de-de.json | 7 ++-- data/web/lang/lang.en-gb.json | 5 ++- data/web/lang/lang.es-es.json | 7 +++- data/web/lang/lang.ja-jp.json | 3 ++ data/web/templates/user/SpamAliases.twig | 14 ++++---- docker-compose.yml | 4 +-- 11 files changed, 80 insertions(+), 30 deletions(-) diff --git a/data/Dockerfiles/phpfpm/docker-entrypoint.sh b/data/Dockerfiles/phpfpm/docker-entrypoint.sh index 0d09ac5fc..d7fa15556 100755 --- a/data/Dockerfiles/phpfpm/docker-entrypoint.sh +++ b/data/Dockerfiles/phpfpm/docker-entrypoint.sh @@ -167,7 +167,7 @@ DELIMITER // CREATE EVENT clean_spamalias ON SCHEDULE EVERY 1 DAY DO BEGIN - DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP(); + DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP() AND permanent = 0; END; // DELIMITER ; diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index 0a6494ed6..0a8ed736e 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -390,7 +390,7 @@ hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT goto FROM spamalias WHERE address='%s' - AND validity >= UNIX_TIMESTAMP() + AND (validity >= UNIX_TIMESTAMP() OR permanent != 0) EOF if [ ! -f /opt/postfix/conf/dns_blocklists.cf ]; then @@ -524,4 +524,4 @@ if [[ $? != 0 ]]; then else postfix -c /opt/postfix/conf start sleep 126144000 -fi \ No newline at end of file +fi diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 7638c9bbf..d8e4e178a 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -49,6 +49,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { // Default to 1 yr $_data["validity"] = 8760; } + if (isset($_data["permanent"]) && filter_var($_data["permanent"], FILTER_VALIDATE_BOOL)) { + $permanent = 1; + } + else { + $permanent = 0; + } $domain = $_data['domain']; $description = $_data['description']; $valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain']; @@ -65,13 +71,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } $validity = strtotime("+" . $_data["validity"] . " hour"); - $stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `description`, `goto`, `validity`) VALUES - (:address, :description, :goto, :validity)"); + $stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `description`, `goto`, `validity`, `permanent`) VALUES + (:address, :description, :goto, :validity, :permanent)"); $stmt->execute(array( ':address' => readable_random_string(rand(rand(3, 9), rand(3, 9))) . '.' . readable_random_string(rand(rand(3, 9), rand(3, 9))) . '@' . $domain, ':description' => $description, ':goto' => $username, - ':validity' => $validity + ':validity' => $validity, + ':permanent' => $permanent )); $_SESSION['return'][] = array( 'type' => 'success', @@ -2103,15 +2110,23 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - if (empty($_data['validity'])) { + if (empty($_data['validity']) && empty($_data['permanent'])) { continue; } - $validity = round((int)time() + ($_data['validity'] * 3600)); - $stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity WHERE + if (isset($_data['permanent']) && filter_var($_data['permanent'], FILTER_VALIDATE_BOOL)) { + $permanent = 1; + $validity = 0; + } + else if (isset($_data['validity'])) { + $permanent = 0; + $validity = round((int)time() + ($_data['validity'] * 3600)); + } + $stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity, `permanent` = :permanent WHERE `address` = :address"); $stmt->execute(array( ':address' => $address, - ':validity' => $validity + ':validity' => $validity, + ':permanent' => $permanent )); $_SESSION['return'][] = array( 'type' => 'success', @@ -4584,10 +4599,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `description`, `validity`, `created`, - `modified` + `modified`, + `permanent` FROM `spamalias` WHERE `goto` = :username - AND `validity` >= :unixnow"); + AND (`validity` >= :unixnow + OR `permanent` != 0)"); $stmt->execute(array(':username' => $_data, ':unixnow' => time())); $tladata = $stmt->fetchAll(PDO::FETCH_ASSOC); return $tladata; @@ -5162,7 +5179,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain AND `username` != :username"); $stmt->execute(array(':domain' => $row['domain'], ':username' => $_data)); $MailboxUsage = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND `validity` >= :unixnow"); + $stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND (`validity` >= :unixnow OR `permanent` != 0)"); $stmt->execute(array(':address' => $_data, ':unixnow' => time())); $SpamaliasUsage = $stmt->fetch(PDO::FETCH_ASSOC); $mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use']; diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index b8ab85253..ffaf12093 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -4,7 +4,7 @@ function init_db_schema() try { global $pdo; - $db_version = "07102025_1015"; + $db_version = "10312025_0525"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -554,7 +554,8 @@ function init_db_schema() "description" => "TEXT NOT NULL", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "validity" => "INT(11)" + "validity" => "INT(11)", + "permanent" => "TINYINT(1) NOT NULL DEFAULT '0'" ), "keys" => array( "primary" => array( diff --git a/data/web/js/site/user.js b/data/web/js/site/user.js index 10960c5d9..5eecf2080 100644 --- a/data/web/js/site/user.js +++ b/data/web/js/site/user.js @@ -175,6 +175,10 @@ jQuery(function($){ ''; item.chkbox = ''; item.address = escapeHtml(item.address); + item.validity = { + value: item.validity, + permanent: item.permanent + }; } else { item.chkbox = ''; @@ -218,9 +222,21 @@ jQuery(function($){ title: lang.alias_valid_until, data: 'validity', defaultContent: '', - createdCell: function(td, cellData) { - createSortableDate(td, cellData) - } + render: function (data, type) { + var date = new Date(data.value ? data.value * 1000 : 0); + switch (type) { + case "sort": + if (data.permanent) { + return 0; + } + return date.getTime(); + default: + if (data.permanent) { + return lang.forever; + } + return date.toLocaleDateString(LOCALE, DATETIME_FORMAT); + } + }, }, { title: lang.created_on, diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index dbc6b2f93..762c055af 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -987,7 +987,7 @@ "sogo_visible": "Alias Sichtbarkeit in SOGo", "sogo_visible_n": "Alias in SOGo verbergen", "sogo_visible_y": "Alias in SOGo anzeigen", - "spam_aliases": "Temp. Alias", + "spam_aliases": "Spam-Alias", "stats": "Statistik", "status": "Status", "sync_jobs": "Synchronisationen", @@ -1281,7 +1281,9 @@ "encryption": "Verschlüsselung", "excludes": "Ausschlüsse", "expire_in": "Ungültig in", + "expire_never": "Niemals ungültig", "fido2_webauthn": "FIDO2/WebAuthn", + "forever": "Für immer", "force_pw_update": "Das Passwort für diesen Benutzer muss geändert werden, damit die Zugriffssperre auf die Groupware-Komponenten wieder freigeschaltet wird.", "from": "von", "generate": "generieren", @@ -1346,7 +1348,8 @@ "sogo_profile_reset": "SOGo-Profil zurücksetzen", "sogo_profile_reset_help": "Das Profil wird inklusive aller Kalender- und Kontaktdaten unwiederbringlich gelöscht.", "sogo_profile_reset_now": "Profil jetzt zurücksetzen", - "spam_aliases": "Temporäre E-Mail-Aliasse", + "spam_aliases": "Spam E-Mail-Aliasse", + "spam_aliases_info": "Ein Spam-Alias ist eine temporäre E-Mailadresse, die benutzt werden kann, um eine echte E-Mail Adressen zu schützen.
Optional kann eine Ablaufzeit gesetzt werden, sodass der Alias nach dem definierten Zeitraum automatisch deaktiviert wird, was missbrauchte oder geleakte Adressen effektiv entsorgt.", "spam_score_reset": "Auf Server-Standard zurücksetzen", "spamfilter": "Spamfilter", "spamfilter_behavior": "Bewertung", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index d46e4606c..1e8525957 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -1288,7 +1288,9 @@ "encryption": "Encryption", "excludes": "Excludes", "expire_in": "Expire in", + "expire_never": "Never Expire", "fido2_webauthn": "FIDO2/WebAuthn", + "forever": "Forever", "force_pw_update": "You must set a new password to be able to access groupware related services.", "from": "from", "generate": "generate", @@ -1355,7 +1357,8 @@ "sogo_profile_reset": "Reset SOGo profile", "sogo_profile_reset_help": "This will destroy a user's SOGo profile and delete all contact and calendar data irretrievable.", "sogo_profile_reset_now": "Reset profile now", - "spam_aliases": "Temporary email aliases", + "spam_aliases": "Spam email aliases", + "spam_aliases_info": "A spam alias is a temporary email address that can be used to protect real email addresses.
Optionally, an expiration time can be set so that the alias is automatically deactivated after the defined period, effectively disposing of abused or leaked addresses.", "spam_score_reset": "Reset to server default", "spamfilter": "Spam filter", "spamfilter_behavior": "Rating", diff --git a/data/web/lang/lang.es-es.json b/data/web/lang/lang.es-es.json index f357b9a80..7d86973ac 100644 --- a/data/web/lang/lang.es-es.json +++ b/data/web/lang/lang.es-es.json @@ -1084,6 +1084,7 @@ "aliases_send_as_all": "No verificar permisos del remitente para los siguientes dominios (y sus aliases)", "change_password": "Cambiar contraseña", "create_syncjob": "Crear nuevo trabajo de sincronización", + "created_on": "Creado", "daily": "Cada día", "day": "Día", "description": "Descripción", @@ -1095,6 +1096,9 @@ "edit": "Editar", "encryption": "Cifrado", "excludes": "Excluye", + "expire_in": "Expirará en", + "expire_never": "Nunca expirará", + "forever": "Siempre", "hour": "Hora", "hourly": "Cada hora", "hours": "Horas", @@ -1115,7 +1119,8 @@ "shared_aliases": "Alias compartidos", "shared_aliases_desc": "Los alias compartidos no se ven afectados por la configuración específica del usuario, como el filtro de correo no deseado o la política de cifrado. Los filtros de spam correspondientes solo pueden ser realizados por un administrador como una política de dominio.", "sogo_profile_reset": "Resetear perfil SOGo", - "spam_aliases": "Alias de email temporales", + "spam_aliases": "Alias de email de spam", + "spam_aliases_info": "Un alias de spam es una dirección de correo electrónico temporal que se puede usar para proteger direcciones de correo electrónico reales.
Opcionalmente, se puede establecer un tiempo de expiración para que el alias se desactive automáticamente después del período definido, eliminando efectivamente las direcciones abusadas o filtradas.", "spamfilter": "Filtro anti-spam", "spamfilter_behavior": "Clasificación", "spamfilter_bl": "Lista negra", diff --git a/data/web/lang/lang.ja-jp.json b/data/web/lang/lang.ja-jp.json index 74c04248c..6dfa5d87b 100644 --- a/data/web/lang/lang.ja-jp.json +++ b/data/web/lang/lang.ja-jp.json @@ -1187,6 +1187,7 @@ "created_on": "作成日", "daily": "毎日", "day": "日", + "description": "説明", "delete_ays": "削除プロセスを確認してください。", "direct_aliases": "直接エイリアスアドレス", "direct_aliases_desc": "直接エイリアスアドレスは、スパムフィルターおよびTLSポリシー設定の影響を受けます。", @@ -1201,7 +1202,9 @@ "encryption": "暗号化", "excludes": "除外", "expire_in": "有効期限まで", + "expire_never": "有効期限なし", "fido2_webauthn": "FIDO2/WebAuthn", + "forever": "有効期限なし", "force_pw_update": "グループウェア関連サービスにアクセスするには、新しいパスワードを必ず設定する必要があります。", "from": "送信元", "generate": "生成", diff --git a/data/web/templates/user/SpamAliases.twig b/data/web/templates/user/SpamAliases.twig index 83b056c34..466c283c1 100644 --- a/data/web/templates/user/SpamAliases.twig +++ b/data/web/templates/user/SpamAliases.twig @@ -8,6 +8,7 @@
+

{{ lang.user.spam_aliases_info|raw }}

@@ -18,12 +19,13 @@ {{ lang.mailbox.toggle_all }} {{ lang.mailbox.quick_actions }} diff --git a/docker-compose.yml b/docker-compose.yml index fef738365..f7390c797 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -117,7 +117,7 @@ services: - rspamd php-fpm-mailcow: - image: ghcr.io/mailcow/phpfpm:1.94 + image: ghcr.io/mailcow/phpfpm:8.2.29 command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: - redis-mailcow @@ -339,7 +339,7 @@ services: - dovecot postfix-mailcow: - image: ghcr.io/mailcow/postfix:1.81 + image: ghcr.io/mailcow/postfix:3.7.11 depends_on: mysql-mailcow: condition: service_started