public
nobgit
read
NobMail
Based on mailcow: dockerized
Languages
Repository composition by tracked source files.
PHP
49%
JavaScript
35%
HTML
9%
CSS
4%
Shell
2%
Python
1%
Lua
0%
Perl
0%
Ruby
0%
SCSS
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git
Commit
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 <[email protected]>
0413d268
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
diff --git a/data/Dockerfiles/phpfpm/docker-entrypoint.sh b/data/Dockerfiles/phpfpm/docker-entrypoint.sh
index 0d09ac5f..d7fa1555 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 0a6494ed..0a8ed736 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 7638c9bb..d8e4e178 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 b8ab8525..ffaf1209 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 10960c5d..5eecf208 100644
--- a/data/web/js/site/user.js
+++ b/data/web/js/site/user.js
@@ -175,6 +175,10 @@ jQuery(function($){
'</div>';
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="tla" name="multi_select" value="' + encodeURIComponent(item.address) + '" />';
item.address = escapeHtml(item.address);
+ item.validity = {
+ value: item.validity,
+ permanent: item.permanent
+ };
}
else {
item.chkbox = '<input type="checkbox" class="form-check-input" disabled />';
@@ -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 dbc6b2f9..762c055a 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 <b>muss</b> 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 <b>aller</b> Kalender- und Kontaktdaten <b>unwiederbringlich gelöscht</b>.",
"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. <br>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 d46e4606..1e852595 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 <b>must</b> 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 <b>delete all contact and calendar data irretrievable</b>.",
"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. <br>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 f357b9a8..7d86973a 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. <br>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 74c04248..6dfa5d87 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": "グループウェア関連サービスにアクセスするには、新しいパスワードを<b>必ず</b>設定する必要があります。",
"from": "送信元",
"generate": "生成",
diff --git a/data/web/templates/user/SpamAliases.twig b/data/web/templates/user/SpamAliases.twig
index 83b056c3..466c283c 100644
--- a/data/web/templates/user/SpamAliases.twig
+++ b/data/web/templates/user/SpamAliases.twig
@@ -8,6 +8,7 @@
</div>
<div id="collapse-tab-SpamAliases" class="card-body collapse" data-bs-parent="#user-content">
<div class="row">
+ <p>{{ lang.user.spam_aliases_info|raw }}</p>
<div class="col-md-12 col-sm-12 col-12">
<table id="tla_table" class="table table-striped dt-responsive w-100"></table>
</div>
@@ -18,12 +19,13 @@
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="tla" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
<ul class="dropdown-menu">
- <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"1"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.hour }}</a></li>
- <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"24"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.day }}</a></li>
- <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"168"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.week }}</a></li>
- <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"744"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.month }}</a></li>
- <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"8760"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.year }}</a></li>
- <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"87600"}' href="#">{{ lang.user.expire_in }} 10 {{ lang.user.years }}</a></li>
+ <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"1","permanent":"0"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.hour }}</a></li>
+ <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"24","permanent":"0"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.day }}</a></li>
+ <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"168","permanent":"0"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.week }}</a></li>
+ <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"744","permanent":"0"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.month }}</a></li>
+ <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"8760","permanent":"0"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.year }}</a></li>
+ <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"87600","permanent":"0"}' href="#">{{ lang.user.expire_in }} 10 {{ lang.user.years }}</a></li>
+ <li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"permanent":"1"}' href="#">{{ lang.user.expire_never }}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="tla" data-api-url='delete/time_limited_alias' href="#">{{ lang.mailbox.remove }}</a></li>
</ul>
diff --git a/docker-compose.yml b/docker-compose.yml
index fef73836..f7390c79 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