Compare commits

..
27 Commits
Author SHA1 Message Date
FreddleSpl0it 88fbaa459a Merge pull request #7594 from mailcow/staging
Update 2026-11
2026-11-05 11:02:42 +01:00
FreddleSpl0it 55982ac74b Merge pull request #7537 from mailcow/staging
Update 2026-10
2026-10-21 19:36:04 +02:00
FreddleSpl0it 81f6f7b002 Merge pull request #7506 from mailcow/staging
Update 2026-09a
2026-10-06 12:28:36 +02:00
FreddleSpl0it ca07d8d333 Merge pull request #7477 from mailcow/staging
Update 2026-09
2026-09-21 10:30:34 +02:00
FreddleSpl0it 02552ffefd Merge pull request #7427 from mailcow/staging
Update 2026-07b
2026-08-18 09:29:10 +02:00
FreddleSpl0it 06424670fa Merge pull request #7393 from mailcow/staging
update README.md sponsors
2026-08-05 09:00:52 +02:00
FreddleSpl0it 489db90512 Merge pull request #7391 from mailcow/staging
Update 2026-07a
2026-07-30 11:17:15 +02:00
FreddleSpl0it 641ed63782 Merge pull request #7353 from mailcow/staging
update README.md sponsors
2026-07-15 08:31:16 +02:00
FreddleSpl0it 96a70652c3 Merge pull request #7328 from mailcow/staging
Update 2026-07
2026-07-13 10:42:55 +02:00
FreddleSpl0it 2ac4b1deae Merge pull request #7260 from mailcow/staging
Update 2026-05c
2026-05-26 10:50:58 +02:00
FreddleSpl0it 1eb6d2e26c Merge pull request #7243 from mailcow/staging
Update 2025-05b
2026-05-21 08:33:36 +02:00
FreddleSpl0it 384e2f6ac1 Merge pull request #7227 from mailcow/staging
Update 2026-05a
2026-05-13 10:42:14 +02:00
FreddleSpl0it 32156a337a Merge pull request #7221 from mailcow/staging
Update 2026-05
2026-05-12 08:46:25 +02:00
FreddleSpl0it 281cf93db3 Merge pull request #7174 from mailcow/staging
Update 2026-03b
2026-03-31 09:57:16 +02:00
FreddleSpl0it f399c07c85 Merge pull request #7137 from mailcow/staging
Hotfix 2026-03a
2026-03-13 14:15:04 +01:00
FreddleSpl0it a693325fe6 Merge pull request #7135 from mailcow/staging
Update 2026-03a
2026-03-13 13:16:32 +01:00
FreddleSpl0it 9ad84eee92 Merge pull request #7113 from mailcow/staging
Hotfix 2026-03
2026-03-10 13:50:33 +01:00
FreddleSpl0it b59869b720 Merge pull request #7109 from mailcow/staging
Hotfix 2026-03
2026-03-10 10:39:43 +01:00
FreddleSpl0it 7515bef66c Merge pull request #7104 from mailcow/staging
Hotfix 2026-03
2026-03-10 10:05:39 +01:00
FreddleSpl0it b84ba8ded1 Merge pull request #7101 from mailcow/staging
Update 2026-03
2026-03-10 08:08:29 +01:00
FreddleSpl0it 4845928e7a Merge pull request #7027 from mailcow/staging
[Hotfix] Update 2026-01
2026-01-29 10:31:23 +01:00
FreddleSpl0it 4ccfedd6b3 Merge pull request #7024 from mailcow/staging
🐄🛡️ January 2026 Update | Limited EAS/DAV Access and Restricted Alias Sending
2026-01-29 07:25:09 +01:00
Ashitaka e8d9315d4a Merge pull request #6905 from Ashitaka57/6646-pbkdf2-sha512-verify-hash
Support for PBKDF2-SHA512 hash algorithm in verify_hash() (FreeIPA compatibility) (issue 6646)
2025-12-12 14:08:21 +01:00
DerLinkman d977ddb501 backup: add image prefetch function to verify latest image is used 2025-12-12 14:07:57 +01:00
DerLinkman e76f5237ed ofelia: revert fixed cron syntax for sa-rules download 2025-12-12 14:07:47 +01:00
CopilotandDerLinkman c11ed5dd1e Prevent duplicate/plaintext login announcement rendering (#6963)
* Initial plan

* Fix duplicate login announcement display

Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 14:07:36 +01:00
DerLinkman 4ef65fc382 Merge pull request #6948 from mailcow/staging
2025-12
2025-12-09 13:29:15 +01:00
3 changed files with 2 additions and 126 deletions

No files matched your search

+2 -2
View File
@@ -500,10 +500,10 @@ function apppass_login($user, $pass, $extra = null, &$denied = false){
$key = strtolower($extra['service']) . '_access';
$mailbox_attributes = json_decode($row['mailbox_attributes'], true);
// the protocols of the mailbox only limit direct user access, app passwords have their own
// app passwords never grant a protocol the mailbox itself is denied
if ($row['active'] != '1' || $row['mailbox_active'] != '1' || $row['domain_active'] != '1'
|| ($mailbox_attributes['shared'] ?? '0') == '1'
|| ($extra['service'] != 'NONE' && $row[$key] != '1')) {
|| ($extra['service'] != 'NONE' && ($row[$key] != '1' || (isset($mailbox_attributes[$key]) && $mailbox_attributes[$key] != '1')))) {
// right password, but the app password does not allow this login
$denied = true;
continue;
-113
View File
@@ -1,113 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Auth;
use PDO;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
require_once __DIR__ . '/../Support/legacy_auth_functions.php';
require_once __DIR__ . '/../../../data/web/inc/functions.auth.inc.php';
// Legacy apppass_login() with its own query, on an in-memory SQLite database
class AppPasswordLoginTest extends TestCase
{
private const PROTOCOLS = ['imap_access', 'smtp_access', 'pop3_access', 'sieve_access', 'eas_access', 'dav_access'];
private PDO $pdo;
protected function setUp(): void
{
$this->pdo = new PDO('sqlite::memory:');
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->pdo->sqliteCreateFunction('regexp', fn ($pattern, $value) => preg_match('/' . $pattern . '/', (string)$value) === 1, 2);
$this->pdo->exec("CREATE TABLE `domain` (`domain` TEXT PRIMARY KEY, `active` INTEGER NOT NULL DEFAULT 1)");
$this->pdo->exec("CREATE TABLE `mailbox` (`username` TEXT PRIMARY KEY, `domain` TEXT, `kind` TEXT NOT NULL DEFAULT '',
`active` INTEGER NOT NULL DEFAULT 1, `attributes` TEXT)");
$this->pdo->exec("CREATE TABLE `app_passwd` (`id` INTEGER PRIMARY KEY, `name` TEXT, `mailbox` TEXT, `password` TEXT,
`active` INTEGER NOT NULL DEFAULT 1, " . implode(', ', array_map(fn ($p) => "`$p` INTEGER NOT NULL DEFAULT 0", self::PROTOCOLS)) . ")");
$GLOBALS['pdo'] = $this->pdo;
$_SESSION = [];
}
protected function tearDown(): void
{
unset($GLOBALS['pdo']);
$_SESSION = [];
}
public static function logins(): array
{
$off = array_fill_keys(self::PROTOCOLS, '0');
return [
// [mailbox, domain active, app password, service, password, result, denied]
'imap' => [[], 1, ['imap_access' => 1], 'imap', 'app-secret', 'user', false],
'imap, mailbox imap off' => [['attributes' => $off], 1, ['imap_access' => 1, 'smtp_access' => 1], 'imap', 'app-secret', 'user', false],
'smtp, mailbox smtp off' => [['attributes' => $off], 1, ['imap_access' => 1, 'smtp_access' => 1], 'smtp', 'app-secret', 'user', false],
'eas, mailbox eas off' => [['attributes' => $off], 1, ['eas_access' => 1], 'EAS', 'app-secret', 'user', false],
'dav, mailbox dav off' => [['attributes' => $off], 1, ['dav_access' => 1], 'DAV', 'app-secret', 'user', false],
// denied: right password, login not allowed
'protocol not in app password' => [[], 1, ['imap_access' => 1, 'smtp_access' => 1], 'pop3', 'app-secret', false, true],
'app password inactive' => [[], 1, ['imap_access' => 1, 'active' => 0], 'imap', 'app-secret', false, true],
'mailbox inactive' => [['active' => 0], 1, ['imap_access' => 1], 'imap', 'app-secret', false, true],
'mailbox login disabled' => [['active' => 2], 1, ['imap_access' => 1], 'imap', 'app-secret', false, true],
'domain inactive' => [[], 0, ['imap_access' => 1], 'imap', 'app-secret', false, true],
'shared mailbox' => [['attributes' => ['shared' => '1']], 1, ['imap_access' => 1], 'imap', 'app-secret', false, true],
// failed: counted by netfilter
'wrong password' => [[], 1, ['imap_access' => 1], 'imap', 'wrong', false, false],
'resource mailbox' => [['kind' => 'location'], 1, ['imap_access' => 1], 'imap', 'app-secret', false, false],
];
}
#[DataProvider('logins')]
public function testLogin(array $mailbox, int $domainActive, array $appPasswd, string $service, string $password, string|false $result, bool $denied): void
{
$this->addMailbox($mailbox, $domainActive);
$id = $this->addAppPasswd($appPasswd);
$isDenied = false;
$this->assertSame($result, apppass_login('user@example.org', $password, ['service' => $service, 'is_internal' => true], $isDenied));
$this->assertSame($denied, $isDenied);
if ($result !== false) {
$this->assertEquals($id, $_SESSION['app_passwd_id']);
}
}
public function testUiLoginWithoutServiceIsRefused(): void
{
$this->addMailbox([], 1);
$this->addAppPasswd(array_fill_keys(self::PROTOCOLS, 1));
$this->assertFalse(apppass_login('user@example.org', 'app-secret', ['is_internal' => false]));
}
public function testOtherAppPasswordOfTheMailboxIsTried(): void
{
$this->addMailbox(['attributes' => array_fill_keys(self::PROTOCOLS, '0')], 1);
$this->addAppPasswd(['eas_access' => 1], 'other-secret');
$id = $this->addAppPasswd(['imap_access' => 1]);
$this->assertSame('user', apppass_login('user@example.org', 'app-secret', ['service' => 'imap', 'is_internal' => true]));
$this->assertEquals($id, $_SESSION['app_passwd_id']);
}
private function addMailbox(array $mailbox, int $domainActive): void
{
$attributes = array_merge(array_fill_keys(self::PROTOCOLS, '1'), ['shared' => '0'], $mailbox['attributes'] ?? []);
$this->pdo->prepare("INSERT INTO `domain` (`domain`, `active`) VALUES ('example.org', ?)")->execute([$domainActive]);
$this->pdo->prepare("INSERT INTO `mailbox` (`username`, `domain`, `kind`, `active`, `attributes`) VALUES ('user@example.org', 'example.org', ?, ?, ?)")
->execute([$mailbox['kind'] ?? '', $mailbox['active'] ?? 1, json_encode($attributes)]);
}
private function addAppPasswd(array $appPasswd, string $password = 'app-secret'): int
{
$row = array_merge(array_fill_keys(self::PROTOCOLS, 0), ['active' => 1], $appPasswd);
$hash = '{BLF-CRYPT}' . password_hash($password, PASSWORD_BCRYPT, ['cost' => 4]);
$this->pdo->prepare("INSERT INTO `app_passwd` (`name`, `mailbox`, `password`, " . implode(', ', array_map(fn ($c) => "`$c`", array_keys($row))) . ")
VALUES ('test', 'user@example.org', ?, " . implode(', ', array_fill(0, count($row), '?')) . ")")
->execute(array_merge([$hash], array_values($row)));
return (int)$this->pdo->lastInsertId();
}
}
@@ -1,11 +0,0 @@
<?php
// Stand-in for verify_hash() of data/web/inc/functions.inc.php, used by AppPasswordLoginTest only:
// app passwords are stored as {BLF-CRYPT} (bcrypt), the default scheme of hash_password().
if (!function_exists('verify_hash')) {
function verify_hash($hash, $password)
{
return str_starts_with($hash, '{BLF-CRYPT}') && password_verify($password, substr($hash, strlen('{BLF-CRYPT}')));
}
}