diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index 0a8ed736e..51927ea11 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -329,14 +329,17 @@ query = SELECT goto FROM alias SELECT id FROM alias WHERE address='%s' AND (active='1' OR active='2') + AND sender_allowed='1' ), ( SELECT id FROM alias WHERE address='@%d' AND (active='1' OR active='2') + AND sender_allowed='1' ) ) ) AND active='1' + AND sender_allowed='1' AND (domain IN (SELECT domain FROM domain WHERE domain='%d' diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index eb7d82dff..8d2efea3d 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -695,6 +695,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto'])); $internal = intval($_data['internal']); $active = intval($_data['active']); + $sender_allowed = intval($_data['sender_allowed']); $sogo_visible = intval($_data['sogo_visible']); $goto_null = intval($_data['goto_null']); $goto_spam = intval($_data['goto_spam']); @@ -850,8 +851,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `internal`, `active`) - VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :internal, :active)"); + $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `internal`, `sender_allowed`, `active`) + VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :internal, :sender_allowed, :active)"); if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) { $stmt->execute(array( ':address' => '@'.$domain, @@ -862,6 +863,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':domain' => $domain, ':sogo_visible' => $sogo_visible, ':internal' => $internal, + ':sender_allowed' => $sender_allowed, ':active' => $active )); } @@ -874,6 +876,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':domain' => $domain, ':sogo_visible' => $sogo_visible, ':internal' => $internal, + ':sender_allowed' => $sender_allowed, ':active' => $active )); } @@ -2511,6 +2514,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (!empty($is_now)) { $internal = (isset($_data['internal'])) ? intval($_data['internal']) : $is_now['internal']; $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $sender_allowed = (isset($_data['sender_allowed'])) ? intval($_data['sender_allowed']) : $is_now['sender_allowed']; $sogo_visible = (isset($_data['sogo_visible'])) ? intval($_data['sogo_visible']) : $is_now['sogo_visible']; $goto_null = (isset($_data['goto_null'])) ? intval($_data['goto_null']) : 0; $goto_spam = (isset($_data['goto_spam'])) ? intval($_data['goto_spam']) : 0; @@ -2696,6 +2700,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `goto` = :goto, `sogo_visible`= :sogo_visible, `internal`= :internal, + `sender_allowed`= :sender_allowed, `active`= :active WHERE `id` = :id"); $stmt->execute(array( @@ -2706,6 +2711,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':goto' => $goto, ':sogo_visible' => $sogo_visible, ':internal' => $internal, + ':sender_allowed' => $sender_allowed, ':active' => $active, ':id' => $is_now['id'] )); @@ -3199,9 +3205,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } if (isset($_data['sender_acl'])) { // Get sender_acl items set by admin + $current_sender_acls = mailbox('get', 'sender_acl_handles', $username); $sender_acl_admin = array_merge( - mailbox('get', 'sender_acl_handles', $username)['sender_acl_domains']['ro'], - mailbox('get', 'sender_acl_handles', $username)['sender_acl_addresses']['ro'] + $current_sender_acls['sender_acl_domains']['ro'], + $current_sender_acls['sender_acl_addresses']['ro'] ); // Get sender_acl items from POST array // Set sender_acl_domain_admin to empty array if sender_acl contains "default" to trigger a reset @@ -3289,16 +3296,25 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ':username' => $username )); - $fixed_sender_aliases = mailbox('get', 'sender_acl_handles', $username)['fixed_sender_aliases']; + $sender_acl_handles = mailbox('get', 'sender_acl_handles', $username); + $fixed_sender_aliases_allowed = $sender_acl_handles['fixed_sender_aliases_allowed']; + $fixed_sender_aliases_blocked = $sender_acl_handles['fixed_sender_aliases_blocked']; + foreach ($sender_acl_merged as $sender_acl) { $domain = ltrim($sender_acl, '@'); if (is_valid_domain_name($domain)) { $sender_acl = '@' . $domain; } - // Don't add if allowed by alias - if (in_array($sender_acl, $fixed_sender_aliases)) { + + // Always add to sender_acl table to create explicit permission + // Skip only if it's in allowed list (would be redundant) + // But DO add if it's in blocked list (creates override) + if (in_array($sender_acl, $fixed_sender_aliases_allowed)) { + // Skip: already allowed by sender_allowed=1, no need for sender_acl entry continue; } + + // Add to sender_acl (either override for blocked aliases, or grant for selectable ones) $stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`) VALUES (:sender_acl, :username)"); $stmt->execute(array( @@ -4180,13 +4196,22 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $data['sender_acl_addresses']['rw'] = array(); $data['sender_acl_addresses']['selectable'] = array(); $data['fixed_sender_aliases'] = array(); + $data['fixed_sender_aliases_allowed'] = array(); + $data['fixed_sender_aliases_blocked'] = array(); $data['external_sender_aliases'] = array(); - // Fixed addresses - $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` REGEXP :goto AND `address` NOT LIKE '@%'"); + // Fixed addresses - split by sender_allowed status + $stmt = $pdo->prepare("SELECT `address`, `sender_allowed` FROM `alias` WHERE `goto` REGEXP :goto AND `address` NOT LIKE '@%'"); $stmt->execute(array(':goto' => '(^|,)'.preg_quote($_data, '/').'($|,)')); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($rows)) { + // Keep old array for backward compatibility $data['fixed_sender_aliases'][] = $row['address']; + // Split into allowed/blocked for proper display + if ($row['sender_allowed'] == '1') { + $data['fixed_sender_aliases_allowed'][] = $row['address']; + } else { + $data['fixed_sender_aliases_blocked'][] = $row['address']; + } } $stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias_domain_alias` FROM `mailbox`, `alias_domain` WHERE `alias_domain`.`target_domain` = `mailbox`.`domain` @@ -4746,6 +4771,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `internal`, `active`, `sogo_visible`, + `sender_allowed`, `created`, `modified` FROM `alias` @@ -4779,6 +4805,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $aliasdata['active_int'] = $row['active']; $aliasdata['sogo_visible'] = $row['sogo_visible']; $aliasdata['sogo_visible_int'] = $row['sogo_visible']; + $aliasdata['sender_allowed'] = $row['sender_allowed']; $aliasdata['created'] = $row['created']; $aliasdata['modified'] = $row['modified']; if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdata['domain'])) { diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 67e98a5a8..c64419800 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -185,6 +185,7 @@ function init_db_schema() "public_comment" => "TEXT", "sogo_visible" => "TINYINT(1) NOT NULL DEFAULT '1'", "internal" => "TINYINT(1) NOT NULL DEFAULT '0'", + "sender_allowed" => "TINYINT(1) NOT NULL DEFAULT '1'", "active" => "TINYINT(1) NOT NULL DEFAULT '1'" ), "keys" => array( diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index 762c055af..b86879ded 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -73,6 +73,7 @@ "inactive": "Inaktiv", "internal": "Intern", "internal_info": "Interne Aliasse sind nur von der eigenen Domäne oder Alias-Domänen erreichbar.", + "sender_allowed": "Als dieser Alias senden erlauben", "kind": "Art", "mailbox_quota_def": "Standard-Quota einer Mailbox", "mailbox_quota_m": "Max. Speicherplatz pro Mailbox (MiB)", @@ -694,6 +695,8 @@ "inactive": "Inaktiv", "internal": "Intern", "internal_info": "Interne Aliasse sind nur von der eigenen Domäne oder Alias-Domänen erreichbar.", + "sender_allowed": "Als dieser Alias senden erlauben", + "sender_allowed_info": "Wenn deaktiviert, kann dieser Alias nur E-Mails empfangen. Verwenden Sie Sender-ACL, um bestimmten Postfächern die Berechtigung zum Senden zu erteilen.", "kind": "Art", "last_modified": "Zuletzt geändert", "lookup_mx": "Ziel mit MX vergleichen (Regex, etwa .*\\.google\\.com, um alle Ziele mit MX *google.com zu routen)", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index 1e8525957..79175e578 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -73,6 +73,7 @@ "inactive": "Inactive", "internal": "Internal", "internal_info": "Internal aliases are only accessible from the own domain or alias domains.", + "sender_allowed": "Allow to send as this alias", "kind": "Kind", "mailbox_quota_def": "Default mailbox quota", "mailbox_quota_m": "Max. quota per mailbox (MiB)", @@ -694,6 +695,8 @@ "inactive": "Inactive", "internal": "Internal", "internal_info": "Internal aliases are only accessible from the own domain or alias domains.", + "sender_allowed": "Allow to send as this alias", + "sender_allowed_info": "If disabled, this alias can only receive mail. Use sender ACL to override and grant specific mailboxes permission to send.", "kind": "Kind", "last_modified": "Last modified", "lookup_mx": "Destination is a regular expression to match against MX name (.*\\.google\\.com to route all mail targeted to a MX ending in google.com over this hop)", diff --git a/data/web/templates/edit/alias.twig b/data/web/templates/edit/alias.twig index 64a6e706b..85a946baf 100644 --- a/data/web/templates/edit/alias.twig +++ b/data/web/templates/edit/alias.twig @@ -7,6 +7,7 @@
+ {% if not skip_sogo %} {% endif %} @@ -39,7 +40,11 @@
- {{ lang.edit.internal_info }} + {{ lang.edit.internal_info }} +
+ +
+ {{ lang.edit.sender_allowed_info }}
diff --git a/data/web/templates/edit/mailbox.twig b/data/web/templates/edit/mailbox.twig index 294957ee7..abf0c45ba 100644 --- a/data/web/templates/edit/mailbox.twig +++ b/data/web/templates/edit/mailbox.twig @@ -85,14 +85,6 @@ {{ lang.edit.dont_check_sender_acl|format(domain) }} {% endfor %} - {% for alias in sender_acl_handles.sender_acl_addresses.ro %} - - {% endfor %} - {% for alias in sender_acl_handles.fixed_sender_aliases %} - - {% endfor %} {% for domain in sender_acl_handles.sender_acl_domains.rw %} {% endfor %} {% for address in sender_acl_handles.sender_acl_addresses.rw %} - + {% if address in sender_acl_handles.fixed_sender_aliases_allowed or address in sender_acl_handles.fixed_sender_aliases_blocked %} + + {% else %} + + {% endif %} {% endfor %} {% for address in sender_acl_handles.sender_acl_addresses.selectable %} {% endfor %} + {% for alias in sender_acl_handles.fixed_sender_aliases_allowed %} + {% if alias not in sender_acl_handles.sender_acl_addresses.rw %} + + {% endif %} + {% endfor %} + {% for alias in sender_acl_handles.fixed_sender_aliases_blocked %} + {% if alias not in sender_acl_handles.sender_acl_addresses.rw %} + + {% endif %} + {% endfor %}
{{ lang.edit.sender_acl_disabled|raw }}
{{ lang.edit.sender_acl_info|raw }} diff --git a/data/web/templates/modals/mailbox.twig b/data/web/templates/modals/mailbox.twig index b35c0caa8..9e1889500 100644 --- a/data/web/templates/modals/mailbox.twig +++ b/data/web/templates/modals/mailbox.twig @@ -782,6 +782,7 @@ +
@@ -813,7 +814,11 @@
- {{ lang.edit.internal_info }} + {{ lang.edit.internal_info }} +
+ +
+ {{ lang.edit.sender_allowed_info }}
diff --git a/docker-compose.yml b/docker-compose.yml index f09afca2a..1fc28af6a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -339,7 +339,7 @@ services: - dovecot postfix-mailcow: - image: ghcr.io/mailcow/postfix:3.7.11 + image: ghcr.io/mailcow/postfix:3.7.11-1 depends_on: mysql-mailcow: condition: service_started