diff --git a/data/web/inc/lib/mailcow/MailPolicy/Service/PolicyEngine.php b/data/web/inc/lib/mailcow/MailPolicy/Service/PolicyEngine.php index 89faa03d1..066732fb1 100644 --- a/data/web/inc/lib/mailcow/MailPolicy/Service/PolicyEngine.php +++ b/data/web/inc/lib/mailcow/MailPolicy/Service/PolicyEngine.php @@ -105,9 +105,11 @@ class PolicyEngine } } return match ($mode) { + 'all' => true, 'internal' => $internal || $this->any($allow, $matches), 'list' => $this->any($allow, $matches), - default => true, + // an unknown mode lets nothing through + default => false, }; } diff --git a/data/web/inc/lib/mailcow/MailPolicy/Service/PolicyService.php b/data/web/inc/lib/mailcow/MailPolicy/Service/PolicyService.php index 2379edba8..3ffafc6e9 100644 --- a/data/web/inc/lib/mailcow/MailPolicy/Service/PolicyService.php +++ b/data/web/inc/lib/mailcow/MailPolicy/Service/PolicyService.php @@ -288,11 +288,15 @@ class PolicyService } $entry = 'group:' . $id; } elseif (str_contains(ltrim($entry, '@'), '@')) { - if (!filter_var($entry, FILTER_VALIDATE_EMAIL)) { + // in the form the engine compares: domain without the final dot, never a +tag or an IP literal + $at = strrpos($entry, '@'); + $local = substr($entry, 0, $at); + $entry = $local . '@' . rtrim(substr($entry, $at + 1), '.'); + if (!filter_var($entry, FILTER_VALIDATE_EMAIL) || str_contains(substr($local, 1), '+') || str_contains($entry, '@[')) { throw new UserError('mail_policy_entry_invalid', [htmlspecialchars($entry)]); } } else { - $entry = ltrim($entry, '@'); + $entry = rtrim(ltrim($entry, '@'), '.'); if (!str_contains($entry, '.') || !filter_var($entry, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) { throw new UserError('mail_policy_entry_invalid', [htmlspecialchars($entry)]); } diff --git a/tests/php/MailPolicy/Service/PolicyEngineTest.php b/tests/php/MailPolicy/Service/PolicyEngineTest.php index 57b77f4bd..79f2aeade 100644 --- a/tests/php/MailPolicy/Service/PolicyEngineTest.php +++ b/tests/php/MailPolicy/Service/PolicyEngineTest.php @@ -58,6 +58,13 @@ class PolicyEngineTest extends TestCase $this->assertSame('DUNNO', $this->engine->check('azubi@firma.de', 'x@extern.example', 'azubi@firma.de')->action()); } + public function testUnknownModeLetsNothingThrough(): void + { + $this->lookup->mailboxPolicies['azubi@firma.de'] = [self::policy(1, ['sendMode' => 'nobody', 'receiveMode' => ''])]; + $this->assertSame([false, 'send'], $this->check('azubi@firma.de', 'kollege@firma.de', 'azubi@firma.de')); + $this->assertSame([false, 'receive'], $this->check('kollege@firma.de', 'azubi@firma.de', 'kollege@firma.de')); + } + public function testSendOnlyInternalWithAllowedAndBlockedEntries(): void { $this->lookup->mailboxPolicies['azubi@firma.de'] = [self::policy(1, [ diff --git a/tests/php/MailPolicy/Service/PolicyServiceTest.php b/tests/php/MailPolicy/Service/PolicyServiceTest.php index 6dae83f67..e870b8eb2 100644 --- a/tests/php/MailPolicy/Service/PolicyServiceTest.php +++ b/tests/php/MailPolicy/Service/PolicyServiceTest.php @@ -113,6 +113,9 @@ class PolicyServiceTest extends TestCase 'mode' => [['send_mode' => 'nobody'], 'mail_policy_mode_invalid'], 'domain without dot' => [['send_allow' => 'localhost'], ['mail_policy_entry_invalid', 'localhost']], 'address' => [['receive_block' => 'a@@b.de'], ['mail_policy_entry_invalid', 'a@@b.de']], + 'address with +tag' => [['receive_block' => 'news+promo@vendor.example'], ['mail_policy_entry_invalid', 'news+promo@vendor.example']], + 'IP literal' => [['send_allow' => 'u@[127.0.0.1]'], ['mail_policy_entry_invalid', 'u@[127.0.0.1]']], + 'top level domain' => [['send_block' => 'com.'], ['mail_policy_entry_invalid', 'com']], 'unknown group' => [['receive_block_groups' => ['8']], ['mail_policy_entry_invalid', 'group:8']], 'reject text with line break' => [['reject_text' => "a\r\nb"], 'mail_policy_reject_text_invalid'], 'reject text not ASCII' => [['reject_text' => 'Nur für Azubis'], 'mail_policy_reject_text_invalid'], @@ -122,6 +125,12 @@ class PolicyServiceTest extends TestCase ]; } + public function testEntriesInTheFormTheEngineCompares(): void + { + $policy = $this->service()->create(['domain' => 'firma.de', 'name' => 'Test', 'receive_block' => "Spam.Example.\n@news.example\nBoss@Ext.Example.\n+x@ext.example"]); + $this->assertSame(['spam.example', 'news.example', 'boss@ext.example', '+x@ext.example'], $policy->receiveBlock); + } + #[DataProvider('invalidInput')] public function testInvalidInput(array $input, string|array $error): void {