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 }}