[Web] Mail policies: store list entries the way they are compared, refuse unknown modes

This commit is contained in:
FreddleSpl0it committed 2026-11-09 03:06:19 +01:00
1 parent 7a1b68c5e2
commit c304040140
4 files changed
+25 -3

No files matched your search

@@ -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,
};
}
@@ -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)]);
}
@@ -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, [
@@ -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
{