Hi, On Sun, 05 Jul 2026 at 17:04:05 +0200, Guilhem Moulin wrote: > 1. Infinite loop in TNEF (winmail.dat) decoder > > https://github.com/roundcube/roundcubemail/commit/a007321346380136b3de2bd75b486b04f63c0d38 > > https://github.com/roundcube/roundcubemail/commit/132ac8dd5a55c8466be12de1daf84355697ffa89
CVE-2026-62642 was assigned for this issue. > 2. Various vulnerabilities in the password plugin using > session-injected username > > https://github.com/roundcube/roundcubemail/commit/83150ce04d689a70f92d511bcae40adba8d55476 > > https://github.com/roundcube/roundcubemail/commit/5cdc6a48b40beabff7f0bf5d9035f4491e877e4c CVE-2026-62644 was assigned for this issue. > 3. CVE-2026-54432: Stored XSS via unescaped attachment MIME type on > the attachment-validation warning page > > https://github.com/roundcube/roundcubemail/commit/a3a4482cc9bd5569107e4393d32abb157cb2a568 > 4. SSRF bypass via specific local address URLs > > https://github.com/roundcube/roundcubemail/commit/294c7da6e7284166f040cef8607b677d459e0786 CVE-2026-62643 was assigned for this issue. > 5. CVE-2026-54433: Zero-click stored XSS in plain-text rendering > > https://github.com/roundcube/roundcubemail/commit/63e42e233c6e8b5100e2e61a4d13addcd1a45bd5 > 6. DoS via crafted compressed-RTF size in the TNEF (winmail.dat) file > > https://github.com/roundcube/roundcubemail/commit/bf253c72d4293c93fda511b8464fe9cb34b522c1 CVE-2026-62641 was assigned for this issue. As for the previous uploads, I suggest to follow 1.6.x (upstream's current LTS release) for trixie-security. Tested debdiff attached. The upstream diff [0] is pretty targeted already, although the 2 Enigma changes are not security security related and arguably don't belong to Debian stable. If you prefer I can prepare another debdiff with only the targeted changes linked above. The proposed upload also includes a fix to restore with PHP <8 (unsupported since bookworm, the change is trivial enough to be worth doing), see #1138086. -- Guilhem. [0] https://github.com/roundcube/roundcubemail/compare/1.6.16...1.6.17
diffstat for roundcube-1.6.16+dfsg roundcube-1.6.17+dfsg CHANGELOG.md | 11 debian/changelog | 22 + debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch | 10 debian/patches/Fix-FTBFS-with-phpunit-11.patch | 54 +-- plugins/enigma/composer.json | 2 plugins/enigma/config.inc.php.dist | 15 plugins/enigma/lib/enigma_engine.php | 66 +++- plugins/enigma/lib/enigma_key_lookup.php | 159 ++++++++++ plugins/enigma/lib/enigma_subkey.php | 12 plugins/password/drivers/directadmin.php | 12 plugins/password/drivers/ldap.php | 18 - plugins/password/drivers/ldap_exop.php | 5 plugins/password/drivers/ldap_ppolicy.php | 6 plugins/password/drivers/ldap_samba_ad.php | 4 plugins/password/drivers/ldap_simple.php | 34 +- plugins/password/drivers/sql.php | 8 plugins/password/drivers/xmail.php | 4 plugins/password/drivers/zxcvbn.php | 10 plugins/password/password.php | 20 - program/actions/mail/get.php | 10 program/lib/Roundcube/rcube_message.php | 10 program/lib/Roundcube/rcube_string_replacer.php | 2 program/lib/Roundcube/rcube_tnef_decoder.php | 14 program/lib/Roundcube/rcube_utils.php | 4 public_html/plugins/enigma/composer.json | 2 public_html/plugins/enigma/config.inc.php.dist | 15 public_html/plugins/enigma/lib/enigma_engine.php | 66 +++- public_html/plugins/enigma/lib/enigma_key_lookup.php | 159 ++++++++++ public_html/plugins/enigma/lib/enigma_subkey.php | 12 public_html/plugins/password/drivers/directadmin.php | 12 public_html/plugins/password/drivers/ldap.php | 18 - public_html/plugins/password/drivers/ldap_exop.php | 5 public_html/plugins/password/drivers/ldap_ppolicy.php | 6 public_html/plugins/password/drivers/ldap_samba_ad.php | 4 public_html/plugins/password/drivers/ldap_simple.php | 34 +- public_html/plugins/password/drivers/sql.php | 8 public_html/plugins/password/drivers/xmail.php | 4 public_html/plugins/password/drivers/zxcvbn.php | 10 public_html/plugins/password/password.php | 20 - tests/Framework/Text2Html.php | 17 + tests/Framework/TnefDecoder.php | 29 + tests/Framework/Utils.php | 3 tests/src/winmail-10193.tnef |binary 43 files changed, 767 insertions(+), 169 deletions(-) diff -Nru roundcube-1.6.16+dfsg/CHANGELOG.md roundcube-1.6.17+dfsg/CHANGELOG.md --- roundcube-1.6.16+dfsg/CHANGELOG.md 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/CHANGELOG.md 2026-07-05 12:42:51.000000000 +0200 @@ -2,6 +2,17 @@ ## Unreleased +- Enigma: Support automatic public key lookup (import) using HKP v1 protocol (#5314) +- Enigma: Kolab WOAT Support (#8626) +- Security: Fix an infinite loop in TNEF (winmail.dat) decoder (#10193) +- Security: Fix various vulnerabilities in the password plugin using session-injected username +- Security: Fix stored XSS via unescaped attachment MIME type on the attachment-validation warning page [CVE-2026-54432] +- Security: Fix SSRF bypass via specific local address URLs - two new cases +- Security: Fix zero-click stored XSS in plain-text rendering [CVE-2026-54433] +- Security: Fix DoS via crafted compressed-RTF size in the TNEF (winmail.dat) file + +## Release 1.6.16 + - Fix potential too long value in IMAP ID command (#10136) - Security: Fix stored XSS/HTML/CSS injection in subject field of the draft restore dialog - Security: Fix CSS injection bypass in HTML sanitizer via SVG `<animate attributeName="style">` diff -Nru roundcube-1.6.16+dfsg/debian/changelog roundcube-1.6.17+dfsg/debian/changelog --- roundcube-1.6.16+dfsg/debian/changelog 2026-05-25 23:06:33.000000000 +0200 +++ roundcube-1.6.17+dfsg/debian/changelog 2026-07-08 12:46:28.000000000 +0200 @@ -1,3 +1,25 @@ +roundcube (1.6.17+dfsg-0+deb13u1) trixie-security; urgency=high + + * New upstream security and bugfix release (closes: #1141495). + + Fix CVE-2026-62642: Infinite loop in TNEF (winmail.dat) decoder. + + Fix CVE-2026-62644: Various vulnerabilities in the password plugin using + session-injected username. + + Fix CVE-2026-54432: Stored XSS via unescaped attachment MIME type on the + attachment-validation warning page. + + Fix CVE-2026-62643: SSRF bypass via specific local address URLs. + + Fix CVE-2026-54433: Zero-click stored XSS in plain-text rendering. + + Fix CVE-2026-62641: DoS via crafted compressed-RTF size in the TNEF + (winmail.dat) file. + + Enigma: Add support for automatic public key lookup (import) using HKP + v1 protocol. + + Enigma: Add support for Kolab's Web Of Anti-Trust (WOAT) feature. + * d/p/Avoid-dependency-on-new-package-mlocati-ip-lib.patch: Improve patch + and drop type annotations to restore compatibility with PHP<8 (closes: + #1138086). + * Refresh d/patches. + + -- Guilhem Moulin <[email protected]> Wed, 08 Jul 2026 12:46:28 +0200 + roundcube (1.6.16+dfsg-0+deb13u1) trixie-security; urgency=high * New upstream security and bugfix release (closes: #1137507). diff -Nru roundcube-1.6.16+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch roundcube-1.6.17+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch --- roundcube-1.6.16+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch 2026-05-25 23:06:33.000000000 +0200 +++ roundcube-1.6.17+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch 2026-07-08 12:46:28.000000000 +0200 @@ -30,7 +30,7 @@ "require-dev": { "phpunit/phpunit": "^9" diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php -index be28a85..e9ebf54 100644 +index 8a3d1e9..6c3d04a 100644 --- a/program/lib/Roundcube/rcube_utils.php +++ b/program/lib/Roundcube/rcube_utils.php @@ -1,8 +1,5 @@ @@ -55,7 +55,7 @@ + * + * @return string|false + */ -+ public static function inet_pton2(string $ip) : string|bool { ++ public static function inet_pton2($ip) { + $address = @inet_pton($ip); + if (is_string($address)) { + return $address; @@ -172,9 +172,9 @@ - // IPLib does not seem to work with IPv6 syntax for IPv4 addresses + /* IPv4-mapped IPv6 addresses (RFC4291 2.5.5) */ - $host = preg_replace('/^::ffff:/i', '', $host); + $host = preg_replace('/^[0:]*:ffff:/i', '', $host); - if (preg_match('/([0-9a-f.-]+)\.nip\.io$/i', $host, $matches)) { + if (preg_match('/([0-9a-f.-]+)\.(nip|sslip)\.io$/i', $host, $matches)) { $host = trim($matches[1], '-.'); } @@ -226,7 +226,7 @@ } } diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php -index fe9f435..66e856a 100644 +index 90d8e03..9ad6989 100644 --- a/tests/Framework/Utils.php +++ b/tests/Framework/Utils.php @@ -571,6 +571,86 @@ class Framework_Utils extends TestCase diff -Nru roundcube-1.6.16+dfsg/debian/patches/Fix-FTBFS-with-phpunit-11.patch roundcube-1.6.17+dfsg/debian/patches/Fix-FTBFS-with-phpunit-11.patch --- roundcube-1.6.16+dfsg/debian/patches/Fix-FTBFS-with-phpunit-11.patch 2026-05-25 23:06:33.000000000 +0200 +++ roundcube-1.6.17+dfsg/debian/patches/Fix-FTBFS-with-phpunit-11.patch 2026-07-08 12:46:28.000000000 +0200 @@ -9753,7 +9753,7 @@ $result = $replacer->resolve($result); diff --git a/tests/Framework/Text2Html.php b/tests/Framework/Text2Html.php -index 75405be..0f1ae39 100644 +index df401d2..bc2f96c 100644 --- a/tests/Framework/Text2Html.php +++ b/tests/Framework/Text2Html.php @@ -1,16 +1,19 @@ @@ -9810,7 +9810,7 @@ $html = $t2h->get_html(); -@@ -168,7 +172,7 @@ class Framework_Text2Html extends PHPUnit\Framework\TestCase +@@ -185,7 +189,7 @@ class Framework_Text2Html extends PHPUnit\Framework\TestCase . "<br>\n<br>\n" . '[1] <a href="http://d2.tld">http://d2.tld</a></blockquote></div>'; @@ -9819,7 +9819,7 @@ $html = $t2h->get_html(); $html = preg_replace('/ (rel|target)="(noreferrer|_blank)"/', '', $html); -@@ -208,7 +212,7 @@ class Framework_Text2Html extends PHPUnit\Framework\TestCase +@@ -225,7 +229,7 @@ class Framework_Text2Html extends PHPUnit\Framework\TestCase . "<span style=\"white-space:nowrap\">_</span><br>\n" . "End</div>"; @@ -9829,7 +9829,7 @@ $this->assertEquals($expected, $html); diff --git a/tests/Framework/TnefDecoder.php b/tests/Framework/TnefDecoder.php -index 91f1677..1fa8a5b 100644 +index f895172..ec5b244 100644 --- a/tests/Framework/TnefDecoder.php +++ b/tests/Framework/TnefDecoder.php @@ -1,11 +1,13 @@ @@ -9858,7 +9858,7 @@ $result = $tnef->decompress($body); $this->assertSame('one-file', trim($result['message']['name'])); -@@ -31,7 +33,7 @@ class Framework_TnefDecoder extends PHPUnit\Framework\TestCase +@@ -60,7 +62,7 @@ class Framework_TnefDecoder extends PHPUnit\Framework\TestCase function test_decompress_body() { $body = file_get_contents(TESTS_DIR . 'src/body.tnef'); @@ -9867,7 +9867,7 @@ $result = $tnef->decompress($body); $this->assertSame('Untitled.html', trim($result['message']['name'])); -@@ -41,7 +43,7 @@ class Framework_TnefDecoder extends PHPUnit\Framework\TestCase +@@ -70,7 +72,7 @@ class Framework_TnefDecoder extends PHPUnit\Framework\TestCase $this->assertSame(5360, $result['message']['size']); $this->assertMatchesRegularExpression('/^<\!DOCTYPE HTML/', $result['message']['stream']); @@ -9876,7 +9876,7 @@ $result = $tnef->decompress($body, true); $this->assertCount(0, $result['attachments']); -@@ -55,7 +57,7 @@ class Framework_TnefDecoder extends PHPUnit\Framework\TestCase +@@ -84,7 +86,7 @@ class Framework_TnefDecoder extends PHPUnit\Framework\TestCase function test_rtf2text() { $body = file_get_contents(TESTS_DIR . 'src/sample.rtf'); @@ -9996,7 +9996,7 @@ $idents = $user->list_identities(); diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php -index e4e05e0..fe9f435 100644 +index 8ac3a92..90d8e03 100644 --- a/tests/Framework/Utils.php +++ b/tests/Framework/Utils.php @@ -1,11 +1,15 @@ @@ -10371,7 +10371,7 @@ $this->assertSame($v[2], $result); } } -@@ -630,7 +640,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -633,7 +643,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase ]; foreach ($test as $datetime => $ts) { @@ -10380,7 +10380,7 @@ $this->assertSame($ts, $result, "Error parsing date: $datetime"); } } -@@ -657,7 +667,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -660,7 +670,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase ]; foreach ($test as $datetime => $ts) { @@ -10389,7 +10389,7 @@ $this->assertSame($ts, $result ? $result->format('Y-m-d') : false, "Error parsing date: $datetime"); } -@@ -667,7 +677,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -670,7 +680,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase ]; foreach ($test as $datetime => $ts) { @@ -10398,7 +10398,7 @@ $this->assertSame($ts, $result ? $result->format('Y-m-d H:i:s') : false, "Error parsing date: $datetime"); } -@@ -676,7 +686,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -679,7 +689,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase ]; foreach ($test as $datetime => $ts) { @@ -10407,7 +10407,7 @@ $this->assertSame($ts, $result ? $result->format('Y-m-d H:i:s O') : false, "Error parsing date: $datetime"); } } -@@ -686,17 +696,17 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -689,17 +699,17 @@ class Framework_Utils extends PHPUnit\Framework\TestCase */ function test_anytodatetime_timezone() { @@ -10428,7 +10428,7 @@ if ($result) $result->setTimezone($tz); // move to target timezone for comparison $this->assertSame($ts, $result ? $result->format('Y-m-d H:i') : false, "Error parsing date: $datetime"); } -@@ -715,7 +725,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -718,7 +728,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase ]; foreach ($test as $data) { @@ -10437,7 +10437,7 @@ $this->assertSame($data[2], $result, "Error formatting date: " . $data[0]); } } -@@ -734,7 +744,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -737,7 +747,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase ]; foreach ($test as $input => $output) { @@ -10446,7 +10446,7 @@ $this->assertSame($output, $result); } } -@@ -759,7 +769,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -762,7 +772,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase ]; foreach ($test as $input => $output) { @@ -10455,7 +10455,7 @@ $this->assertSame($output, $result, "Error normalizing '$input'"); } } -@@ -782,7 +792,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -785,7 +795,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase ]; foreach ($test as $idx => $params) { @@ -10464,7 +10464,7 @@ $this->assertSame($params[2], $result, "words_match() at index $idx"); } } -@@ -808,7 +818,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -811,7 +821,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase } foreach ($test as $input => $output) { @@ -10473,7 +10473,7 @@ $this->assertSame($output, $result); } } -@@ -818,17 +828,17 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -821,17 +831,17 @@ class Framework_Utils extends PHPUnit\Framework\TestCase */ function test_random_bytes() { @@ -10497,7 +10497,7 @@ { /* -@@ -865,9 +875,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -868,9 +878,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase * @param string $encoded Encoded email address * @dataProvider data_idn_convert */ @@ -10509,7 +10509,7 @@ } /** -@@ -877,9 +888,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -880,9 +891,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase * @param string $encoded Encoded email address * @dataProvider data_idn_convert */ @@ -10521,7 +10521,7 @@ } /** -@@ -887,14 +899,14 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -890,14 +902,14 @@ class Framework_Utils extends PHPUnit\Framework\TestCase */ function test_idn_to_ascii_special() { @@ -10539,7 +10539,7 @@ { return [ ['%z', 'hostname', 'hostname'], -@@ -909,15 +921,16 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -912,15 +924,16 @@ class Framework_Utils extends PHPUnit\Framework\TestCase * * @dataProvider data_parse_host */ @@ -10558,7 +10558,7 @@ { return [ [['hostname', null, null], ['hostname', null, null]], -@@ -940,15 +953,16 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -943,15 +956,16 @@ class Framework_Utils extends PHPUnit\Framework\TestCase * * @dataProvider data_parse_host_uri */ @@ -10577,7 +10577,7 @@ return [ ['both', 'Fwd: Re: Test subject both', 'Test subject both'], ['both', 'Re: Fwd: Test subject both', 'Test subject both'], -@@ -966,8 +980,9 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -969,8 +983,9 @@ class Framework_Utils extends PHPUnit\Framework\TestCase * * @dataProvider data_remove_subject_prefix */ @@ -10588,7 +10588,7 @@ } /** -@@ -975,13 +990,13 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -978,13 +993,13 @@ class Framework_Utils extends PHPUnit\Framework\TestCase */ function test_server_name() { @@ -10605,7 +10605,7 @@ } /** -@@ -991,31 +1006,31 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -994,31 +1009,31 @@ class Framework_Utils extends PHPUnit\Framework\TestCase { $_SERVER['test'] = 'test.com'; diff -Nru roundcube-1.6.16+dfsg/plugins/enigma/composer.json roundcube-1.6.17+dfsg/plugins/enigma/composer.json --- roundcube-1.6.16+dfsg/plugins/enigma/composer.json 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/enigma/composer.json 2026-07-05 12:42:51.000000000 +0200 @@ -3,7 +3,7 @@ "type": "roundcube-plugin", "description": "Server-side PGP Encryption for Roundcube", "license": "GPL-3.0-or-later", - "version": "0.9", + "version": "1.0", "authors": [ { "name": "Aleksander Machniak", diff -Nru roundcube-1.6.16+dfsg/plugins/enigma/config.inc.php.dist roundcube-1.6.17+dfsg/plugins/enigma/config.inc.php.dist --- roundcube-1.6.16+dfsg/plugins/enigma/config.inc.php.dist 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/enigma/config.inc.php.dist 2026-07-05 12:42:51.000000000 +0200 @@ -78,3 +78,18 @@ // - enigma_options_lock = ['sign'] // - dont_override = ['enigma_sign_all'] $config['enigma_options_lock'] = []; + +// Enable Kolab's Web Of Anti-Trust feature +// Fetches public keys from DNS. Default: false +// To enable set it to True or an array of domain names. +$config['enigma_woat'] = false; + +// Enable automatic public key lookups and import. +// Fetches public keys from a HKP v1 key server. Default: null +// To enable set it to a url e.g. 'https://pgp.roundcube.test' +// or an array map like this: +// $config['enigma_keyserver'] = [ +// 'domain.tld' => 'https://pgp.domain.tld', +// '*' => 'https://keys.openpgp.org', +// ] +$config['enigma_keyserver'] = null; diff -Nru roundcube-1.6.16+dfsg/plugins/enigma/lib/enigma_engine.php roundcube-1.6.17+dfsg/plugins/enigma/lib/enigma_engine.php --- roundcube-1.6.16+dfsg/plugins/enigma/lib/enigma_engine.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/enigma/lib/enigma_engine.php 2026-07-05 12:42:51.000000000 +0200 @@ -28,6 +28,7 @@ private $pgp_driver; private $smime_driver; private $password_time; + private $sender; private $cache = []; public $decryptions = []; @@ -272,6 +273,9 @@ $recipients = array_unique($recipients); + // Fetch keys from external sources, if configured + $this->sync_keys($recipients); + // find recipient public keys foreach ((array) $recipients as $email) { if ($email == $from && $sign_key) { @@ -380,6 +384,17 @@ return; } + // Get the message/part sender + if (!empty($p['object']->sender) && !empty($p['object']->sender['mailto'])) { + $this->sender = $p['object']->sender['mailto']; + } + if (!empty($p['structure']->headers) && !empty($p['structure']->headers['from'])) { + $from = rcube_mime::decode_address_list($p['structure']->headers['from'], 1, false); + if (($from = current($from)) && !empty($from['mailto'])) { + $this->sender = $from['mailto']; + } + } + // Don't be tempted to support encryption in text/html parts // Because of EFAIL vulnerability we should never support this (#6289) @@ -881,6 +896,11 @@ { // @TODO: Handle big bodies using (temp) files + // Import sender's key from external sources, if configured + if ($this->sender) { + $this->sync_keys([$this->sender]); + } + // Get rid of possible non-ascii characters (#5962) $sig_body = preg_replace('/[^\x00-\x7F]/', '', (string) $sig_body); @@ -905,6 +925,11 @@ { // @TODO: Handle big bodies using (temp) files + // Import sender's key from external sources, if configured + if ($this->sender) { + $this->sync_keys([$this->sender]); + } + // Get rid of possible non-ascii characters (#5962) $msg_body = preg_replace('/[^\x00-\x7F]/', '', $msg_body); @@ -1020,19 +1045,25 @@ return; } - $mode = $can_sign ? enigma_key::CAN_SIGN : enigma_key::CAN_ENCRYPT; - $ret = null; + $mode = $can_sign ? enigma_key::CAN_SIGN : enigma_key::CAN_ENCRYPT; + $found = []; // check key validity and type foreach ($result as $key) { if (($subkey = $key->find_subkey($email, $mode)) && (!$can_sign || $key->get_type() == enigma_key::TYPE_KEYPAIR) ) { - $ret = $key; - break; + $found[$subkey->get_creation_date(true)] = $key; } } + // Use the most recent one + if (count($found) > 1) { + ksort($found, SORT_NUMERIC); + } + + $ret = count($found) > 0 ? array_pop($found) : null; + // cache private key info for better performance // we can skip one list_keys() call when signing and attaching a key if ($can_sign) { @@ -1442,4 +1473,31 @@ ); } } + + /** + * Import public keys from configured key lookup sources. + * + * @param array $recipients List of email addresses + */ + protected function sync_keys($recipients) + { + $import = []; + + foreach ($recipients as $recipient) { + if (!strpos($recipient, '@')) { + continue; + } + + if ($key = enigma_key_lookup::woat($recipient)) { + $import[] = $key; + } elseif ($key = enigma_key_lookup::keyserver($recipient)) { + $import[] = $key; + } + } + + // Import the fetched keys + if (!empty($import)) { + $this->import_key(implode("\n", $import)); + } + } } diff -Nru roundcube-1.6.16+dfsg/plugins/enigma/lib/enigma_key_lookup.php roundcube-1.6.17+dfsg/plugins/enigma/lib/enigma_key_lookup.php --- roundcube-1.6.16+dfsg/plugins/enigma/lib/enigma_key_lookup.php 1970-01-01 01:00:00.000000000 +0100 +++ roundcube-1.6.17+dfsg/plugins/enigma/lib/enigma_key_lookup.php 2026-07-05 12:42:51.000000000 +0200 @@ -0,0 +1,159 @@ +<?php + +use GuzzleHttp\Exception\RequestException; + +/* + +-------------------------------------------------------------------------+ + | Key lookup engine of the Enigma Plugin | + | | + | Copyright (C) The Roundcube Dev Team | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + +-------------------------------------------------------------------------+ + | Author: Aleksander Machniak <[email protected]> | + +-------------------------------------------------------------------------+ +*/ + +/** + * PGP public key lookup engine for the Enigma plugin. + */ +class enigma_key_lookup +{ + /** + * Find a public key in DNS (according to Kolab Web-Of-Anti-Trust). + * + * @param string $recipient Email address + */ + public static function woat($recipient): ?string + { + $woat = rcmail::get_instance()->config->get('enigma_woat'); + + if (empty($woat)) { + return null; + } + + [$local, $domain] = explode('@', $recipient); + + // Do this for configured domains only + if (is_array($woat) && !in_array_nocase($domain, $woat)) { + return null; + } + + // remove parts behind a recipient delimiter ("jeroen+Trash" => "jeroen") + $local = preg_replace('/\+.*$/', '', $local); + + $fqdn = sha1($local) . '._woat.' . $domain; + + // Fetch the TXT record(s) + if (($records = dns_get_record($fqdn, \DNS_TXT)) === false) { + return null; + } + + foreach ($records as $record) { + if (strpos($record['txt'], 'v=woat1,') === 0) { + $entry = explode('public_key=', $record['txt']); + if (count($entry) == 2) { + // For now we support only one key + return $entry[1]; + } + } + } + + return null; + } + + /** + * Find a public key in a HKP v1 server. + * + * @param string $recipient Email address + */ + public static function keyserver($recipient): ?string + { + $rcmail = rcmail::get_instance(); + $keyserver = $rcmail->config->get('enigma_keyserver'); + + if (empty($keyserver)) { + return null; + } + + $recipient = strtolower($recipient); + + if (is_array($keyserver)) { + [$local, $domain] = explode('@', $recipient); + + if (!empty($keyserver[$domain])) { + $keyserver = $keyserver[$domain]; + } elseif (!empty($keyserver['*'])) { + $keyserver = $keyserver['*']; + } else { + return null; + } + } + + // TODO: Support key server discovery + + // Get keys metadata + try { + $client = $rcmail->get_http_client(); + + $response = $client->get($keyserver . '/pks/lookup?op=index&options=mr&search=' . rawurlencode($recipient)); + + $source = $response->getBody(); + $ctype = $response->getHeader('Content-Type'); + $ctype = !empty($ctype) ? $ctype[0] : ''; + } catch (\Exception $e) { + if (!($e instanceof RequestException) || $e->getResponse()->getStatusCode() != 404) { + rcube::raise_error($e, true, false); + } + } + + if (!isset($source) || strpos($source, 'info:1:') !== 0) { + return null; + } + + // Process the keys metadata + // TODO: Ignore revoked/expired keys/identities? + $keyid = null; + $list = []; + foreach (preg_split('/\r?\n/', $source) as $line) { + if (strpos($line, 'pub:') === 0) { + $tokens = explode(':', $line); + $keyid = $tokens[1] ?? null; + } elseif ($keyid && strpos($line, 'uid:') === 0) { + $tokens = explode(':', $line); + $identity = strtolower(rawurldecode($tokens[1] ?? '')); + if (strpos($identity, $recipient) !== false) { + $list[] = $keyid; + } + } else { + $keyid = null; + } + } + + // Get keys + $output = []; + foreach (array_unique($list) as $id) { + try { + $response = $client->get($keyserver . '/pks/lookup?op=get&options=mr&search=0x' . $id); + + $source = $response->getBody(); + $ctype = $response->getHeader('Content-Type'); + $ctype = !empty($ctype) ? $ctype[0] : ''; + + if ($ctype === 'application/pgp-keys' + && strpos($source, '-----BEGIN PGP PUBLIC KEY BLOCK-----') === 0 + ) { + $output[] = $source; + } + } catch (\Exception $e) { + if (!($e instanceof RequestException) || $e->getResponse()->getStatusCode() != 404) { + rcube::raise_error($e, true, false); + } + } + } + + return implode("\n", $output); + } +} diff -Nru roundcube-1.6.16+dfsg/plugins/enigma/lib/enigma_subkey.php roundcube-1.6.17+dfsg/plugins/enigma/lib/enigma_subkey.php --- roundcube-1.6.16+dfsg/plugins/enigma/lib/enigma_subkey.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/enigma/lib/enigma_subkey.php 2026-07-05 12:42:51.000000000 +0200 @@ -93,12 +93,18 @@ /** * Returns subkey creation date-time string * - * @return string|null + * @param bool $asInt Return the date as an integer + * + * @return string|null|int */ - function get_creation_date() + function get_creation_date($asInt = false) { if (empty($this->created)) { - return null; + return $asInt ? 0 : null; + } + + if ($asInt) { + return (int) $this->created->format('U'); } $date_format = rcube::get_instance()->config->get('date_format', 'Y-m-d'); diff -Nru roundcube-1.6.16+dfsg/plugins/password/drivers/directadmin.php roundcube-1.6.17+dfsg/plugins/password/drivers/directadmin.php --- roundcube-1.6.16+dfsg/plugins/password/drivers/directadmin.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/password/drivers/directadmin.php 2026-07-05 12:42:51.000000000 +0200 @@ -26,18 +26,17 @@ class rcube_directadmin_password { - public function save($curpass, $passwd) + public function save($curpass, $passwd, $username) { $rcmail = rcmail::get_instance(); $Socket = new HTTPSocket; - $da_user = $_SESSION['username']; $da_curpass = $curpass; $da_newpass = $passwd; $da_host = $rcmail->config->get('password_directadmin_host'); $da_port = $rcmail->config->get('password_directadmin_port'); - if (strpos($da_user, '@') === false) { + if (strpos($username, '@') === false) { return ['code' => PASSWORD_ERROR, 'message' => 'Change the SYSTEM user password through control panel!']; } @@ -47,7 +46,7 @@ $Socket->connect($da_host,$da_port); $Socket->set_method('POST'); $Socket->query('/CMD_CHANGE_EMAIL_PASSWORD', [ - 'email' => $da_user, + 'email' => $username, 'oldpassword' => $da_curpass, 'password1' => $da_newpass, 'password2' => $da_newpass, @@ -56,14 +55,11 @@ $response = $Socket->fetch_parsed_body(); - //DEBUG - //rcube::console("Password Plugin: [USER: $da_user] [HOST: $da_host] - Response: [SOCKET: ".$Socket->result_status_code."] [DA ERROR: ".strip_tags($response['error'])."] [TEXT: ".$response[text]."]"); - if ($Socket->result_status_code != 200) { return ['code' => PASSWORD_CONNECT_ERROR, 'message' => $Socket->error[0]]; } - if ($response['error'] == 1) { + if (isset($response['error']) && $response['error'] == 1) { return ['code' => PASSWORD_ERROR, 'message' => strip_tags($response['text'])]; } diff -Nru roundcube-1.6.16+dfsg/plugins/password/drivers/ldap_exop.php roundcube-1.6.17+dfsg/plugins/password/drivers/ldap_exop.php --- roundcube-1.6.16+dfsg/plugins/password/drivers/ldap_exop.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/password/drivers/ldap_exop.php 2026-07-05 12:42:51.000000000 +0200 @@ -31,7 +31,8 @@ class rcube_ldap_exop_password extends rcube_ldap_simple_password { - function save($curpass, $passwd) + #[Override] + public function save($curpass, $passwd, $username) { if (!function_exists('ldap_exop_passwd')) { rcube::raise_error([ @@ -45,6 +46,8 @@ return PASSWORD_ERROR; } + $this->username = $username; + // Connect and bind $ret = $this->connect($curpass); if ($ret !== true) { diff -Nru roundcube-1.6.16+dfsg/plugins/password/drivers/ldap.php roundcube-1.6.17+dfsg/plugins/password/drivers/ldap.php --- roundcube-1.6.16+dfsg/plugins/password/drivers/ldap.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/password/drivers/ldap.php 2026-07-05 12:42:51.000000000 +0200 @@ -28,17 +28,23 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -class rcube_ldap_password +require_once __DIR__ . '/ldap_simple.php'; + +class rcube_ldap_password extends rcube_ldap_simple_password { - public function save($curpass, $passwd) + #[Override] + public function save($curpass, $passwd, $username) { $rcmail = rcmail::get_instance(); + + $this->username = $username; + require_once 'Net/LDAP2.php'; require_once __DIR__ . '/ldap_simple.php'; // Building user DN if ($userDN = $rcmail->config->get('password_ldap_userDN_mask')) { - $userDN = rcube_ldap_simple_password::substitute_vars($userDN); + $userDN = $this->substitute_vars($userDN); } else { $userDN = $this->search_userdn($rcmail); @@ -155,7 +161,7 @@ * Use search_base and search_filter defined in config file. * Return the found DN. */ - function search_userdn($rcmail) + public function search_userdn($rcmail, $ds = null) { $binddn = $rcmail->config->get('password_ldap_searchDN'); $bindpw = $rcmail->config->get('password_ldap_searchPW'); @@ -180,8 +186,8 @@ return ''; } - $base = rcube_ldap_simple_password::substitute_vars($rcmail->config->get('password_ldap_search_base')); - $filter = rcube_ldap_simple_password::substitute_vars($rcmail->config->get('password_ldap_search_filter')); + $base = $this->substitute_vars($rcmail->config->get('password_ldap_search_base')); + $filter = $this->substitute_vars($rcmail->config->get('password_ldap_search_filter')); $options = [ 'scope' => 'sub', 'attributes' => [], diff -Nru roundcube-1.6.16+dfsg/plugins/password/drivers/ldap_ppolicy.php roundcube-1.6.17+dfsg/plugins/password/drivers/ldap_ppolicy.php --- roundcube-1.6.16+dfsg/plugins/password/drivers/ldap_ppolicy.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/password/drivers/ldap_ppolicy.php 2026-07-05 12:42:51.000000000 +0200 @@ -27,12 +27,6 @@ $bindPW = $rcmail->config->get('password_ldap_ppolicy_searchPW'); $cafile = $rcmail->config->get('password_ldap_ppolicy_cafile'); - $log_dir = $rcmail->config->get('log_dir'); - - if (empty($log_dir)) { - $log_dir = RCUBE_INSTALL_PATH . 'logs'; - } - $descriptorspec = [ 0 => ["pipe", "r"], // stdin is a pipe that the child will read from 1 => ["pipe", "w"], // stdout is a pipe that the child will write to diff -Nru roundcube-1.6.16+dfsg/plugins/password/drivers/ldap_samba_ad.php roundcube-1.6.17+dfsg/plugins/password/drivers/ldap_samba_ad.php --- roundcube-1.6.16+dfsg/plugins/password/drivers/ldap_samba_ad.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/password/drivers/ldap_samba_ad.php 2026-07-05 12:42:51.000000000 +0200 @@ -32,7 +32,7 @@ class rcube_ldap_samba_ad_password extends rcube_ldap_simple_password { #[Override] - public function save($curpass, $passwd) + public function save($curpass, $passwd, $username) { if (!function_exists('ldap_mod_replace')) { rcube::raise_error([ @@ -44,6 +44,8 @@ return PASSWORD_ERROR; } + $this->username = $username; + // Connect and bind $ret = $this->connect($curpass); if ($ret !== true) { diff -Nru roundcube-1.6.16+dfsg/plugins/password/drivers/ldap_simple.php roundcube-1.6.17+dfsg/plugins/password/drivers/ldap_simple.php --- roundcube-1.6.16+dfsg/plugins/password/drivers/ldap_simple.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/password/drivers/ldap_simple.php 2026-07-05 12:42:51.000000000 +0200 @@ -31,19 +31,21 @@ { protected $debug = false; protected $user; + protected $username; protected $conn; - - public function save($curpass, $passwd) + public function save($curpass, $passwd, $username) { + $this->username = $username; + $rcmail = rcmail::get_instance(); + $lchattr = $rcmail->config->get('password_ldap_lchattr'); + $pwattr = $rcmail->config->get('password_ldap_pwattr', 'userPassword'); + $smbpwattr = $rcmail->config->get('password_ldap_samba_pwattr'); + $smblchattr = $rcmail->config->get('password_ldap_samba_lchattr'); + $samba = $rcmail->config->get('password_ldap_samba'); + $pass_mode = $rcmail->config->get('password_ldap_encodage', 'md5-crypt'); - $lchattr = $rcmail->config->get('password_ldap_lchattr'); - $pwattr = $rcmail->config->get('password_ldap_pwattr', 'userPassword'); - $smbpwattr = $rcmail->config->get('password_ldap_samba_pwattr'); - $smblchattr = $rcmail->config->get('password_ldap_samba_lchattr'); - $samba = $rcmail->config->get('password_ldap_samba'); - $pass_mode = $rcmail->config->get('password_ldap_encodage', 'md5-crypt'); $crypted_pass = password::hash_password($passwd, $pass_mode); // Support password_ldap_samba option for backward compat. @@ -162,7 +164,7 @@ $user_dn = $plugin['user_dn']; } else if ($user_dn = $rcmail->config->get('password_ldap_userDN_mask')) { - $user_dn = self::substitute_vars($user_dn); + $user_dn = $this->substitute_vars($user_dn); } else { $user_dn = $this->search_userdn($rcmail, $ds); @@ -210,7 +212,7 @@ * Use search_base and search_filter defined in config file * Return the found DN */ - function search_userdn($rcmail, $ds) + public function search_userdn($rcmail, $ds = null) { $search_user = $rcmail->config->get('password_ldap_searchDN'); $search_pass = $rcmail->config->get('password_ldap_searchPW'); @@ -231,8 +233,8 @@ $this->_debug("S: OK"); - $search_base = self::substitute_vars($search_base); - $search_filter = self::substitute_vars($search_filter); + $search_base = $this->substitute_vars($search_base); + $search_filter = $this->substitute_vars($search_filter); $this->_debug("C: Search $search_base for $search_filter"); @@ -258,12 +260,12 @@ * Substitute %login, %name, %domain, %dc in $str * See plugin config for details */ - public static function substitute_vars($str) + protected function substitute_vars($str) { - $str = str_replace('%login', $_SESSION['username'], $str); - $str = str_replace('%l', $_SESSION['username'], $str); + $str = str_replace('%login', $this->username, $str); + $str = str_replace('%l', $this->username, $str); - $parts = explode('@', $_SESSION['username']); + $parts = explode('@', $this->username); if (count($parts) == 2) { $dc = 'dc='.strtr($parts[1], ['.' => ',dc=']); // hierarchal domain string diff -Nru roundcube-1.6.16+dfsg/plugins/password/drivers/sql.php roundcube-1.6.17+dfsg/plugins/password/drivers/sql.php --- roundcube-1.6.16+dfsg/plugins/password/drivers/sql.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/password/drivers/sql.php 2026-07-05 12:42:51.000000000 +0200 @@ -28,12 +28,13 @@ /** * Update current user password * - * @param string $curpass Current password - * @param string $passwd New password + * @param string $curpass Current password + * @param string $passwd New password + * @param string $username Username * * @return int Result */ - function save($curpass, $passwd) + public function save($curpass, $passwd, $username) { $rcmail = rcmail::get_instance(); @@ -92,7 +93,6 @@ $local_part = $rcmail->user->get_username('local'); $domain_part = $rcmail->user->get_username('domain'); - $username = $_SESSION['username']; $host = $_SESSION['imap_host']; // convert domains to/from punycode diff -Nru roundcube-1.6.16+dfsg/plugins/password/drivers/xmail.php roundcube-1.6.17+dfsg/plugins/password/drivers/xmail.php --- roundcube-1.6.16+dfsg/plugins/password/drivers/xmail.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/password/drivers/xmail.php 2026-07-05 12:42:51.000000000 +0200 @@ -33,10 +33,10 @@ class rcube_xmail_password { - function save($currpass, $newpass) + public function save($currpass, $newpass, $username) { $rcmail = rcmail::get_instance(); - list($user, $domain) = explode('@', $_SESSION['username']); + [$user, $domain] = explode('@', $username); $xmail = new XMail; diff -Nru roundcube-1.6.16+dfsg/plugins/password/drivers/zxcvbn.php roundcube-1.6.17+dfsg/plugins/password/drivers/zxcvbn.php --- roundcube-1.6.16+dfsg/plugins/password/drivers/zxcvbn.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/password/drivers/zxcvbn.php 2026-07-05 12:42:51.000000000 +0200 @@ -58,8 +58,14 @@ return; } - $zxcvbn = new ZxcvbnPhp\Zxcvbn(); - $strength = $zxcvbn->passwordStrength($passwd); + $rcmail = rcmail::get_instance(); + $userData = [ + $rcmail->user->get_username('local'), + $rcmail->user->get_username(), + ]; + + $zxcvbn = new Zxcvbn(); // @phpstan-ignore-line + $strength = $zxcvbn->passwordStrength($passwd, $userData); // @phpstan-ignore-line return [$strength['score'] + 1, $strength['feedback']['warning']]; } diff -Nru roundcube-1.6.16+dfsg/plugins/password/password.php roundcube-1.6.17+dfsg/plugins/password/password.php --- roundcube-1.6.16+dfsg/plugins/password/password.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/plugins/password/password.php 2026-07-05 12:42:51.000000000 +0200 @@ -499,7 +499,8 @@ if ($exceptions = $this->rc->config->get('password_login_exceptions')) { $exceptions = array_map('trim', (array) $exceptions); $exceptions = array_filter($exceptions); - $username = $_SESSION['username']; + + $username = $this->rc->get_user_name(); foreach ($exceptions as $ec) { if ($username === $ec) { @@ -826,15 +827,20 @@ $format = $rcmail->config->get('password_username_format'); } - if (!$format) { - return $_SESSION['username']; - } + $username = $rcmail->get_user_name(); - return strtr($format, [ + if ($format) { + $username = strtr($format, [ '%l' => $rcmail->user->get_username('local'), '%d' => $rcmail->user->get_username('domain'), - '%u' => $_SESSION['username'], - ]); + '%u' => $username, + ]); + } + + // Sanitize the username + $username = preg_replace('/[*()\r\n\0\\\]/', '', $username); + + return $username; } /** diff -Nru roundcube-1.6.16+dfsg/program/actions/mail/get.php roundcube-1.6.17+dfsg/program/actions/mail/get.php --- roundcube-1.6.16+dfsg/program/actions/mail/get.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/program/actions/mail/get.php 2026-07-05 12:42:51.000000000 +0200 @@ -202,11 +202,11 @@ $rcmail->output = new rcmail_html_page(); $rcmail->output->register_inline_warning( $rcmail->gettext([ - 'name' => 'attachmentvalidationerror', - 'vars' => [ - 'expected' => $mimetype . (!empty($file_extension) ? rcube::Q(" (.{$file_extension})") : ''), - 'detected' => $real_mimetype . (!empty($extensions[0]) ? " (.{$extensions[0]})" : ''), - ] + 'name' => 'attachmentvalidationerror', + 'vars' => [ + 'expected' => rcube::Q($mimetype . (!empty($file_extension) ? " (.{$file_extension})" : '')), + 'detected' => rcube::Q($real_mimetype . (!empty($extensions[0]) ? " (.{$extensions[0]})" : '')), + ], ]), $rcmail->gettext('showanyway'), $rcmail->url(array_merge($_GET, ['_nocheck' => 1])) diff -Nru roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_message.php roundcube-1.6.17+dfsg/program/lib/Roundcube/rcube_message.php --- roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_message.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/program/lib/Roundcube/rcube_message.php 2026-07-05 12:42:51.000000000 +0200 @@ -124,6 +124,11 @@ ) ]; + $this->mime = new rcube_mime($this->headers->charset); + $this->subject = str_replace("\n", '', (string) $this->headers->get('subject')); + $from = $this->mime->decode_address_list($this->headers->from, 1); + $this->sender = current($from); + if (!empty($this->headers->structure)) { $this->get_mime_numbers($this->headers->structure); $this->parse_structure($this->headers->structure); @@ -132,11 +137,6 @@ $this->body = $this->storage->get_body($uid); } - $this->mime = new rcube_mime($this->headers->charset); - $this->subject = str_replace("\n", '', (string) $this->headers->get('subject')); - $from = $this->mime->decode_address_list($this->headers->from, 1); - $this->sender = current($from); - // notify plugins and let them analyze this structured message object $this->app->plugins->exec_hook('message_load', ['object' => $this]); } diff -Nru roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_string_replacer.php roundcube-1.6.17+dfsg/program/lib/Roundcube/rcube_string_replacer.php --- roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_string_replacer.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/program/lib/Roundcube/rcube_string_replacer.php 2026-07-05 12:42:51.000000000 +0200 @@ -65,7 +65,7 @@ $this->mailto_pattern = "/(" . "[-\w!\#\$%&*+~\/^`|{}=]+(?:\.[-\w!\#\$%&*+~\/^`|{}=]+)*" // local-part . "@$utf_domain" // domain-part - . "(\?[$url1$url2]+)?" // e.g. ?subject=test... + . '(\?[^<>\s]+)?' // e.g. ?subject=test... . ")/"; } diff -Nru roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_tnef_decoder.php roundcube-1.6.17+dfsg/program/lib/Roundcube/rcube_tnef_decoder.php --- roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_tnef_decoder.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/program/lib/Roundcube/rcube_tnef_decoder.php 2026-07-05 12:42:51.000000000 +0200 @@ -212,9 +212,9 @@ { $value = null; - if (strlen($data) >= $bytes) { + if (strlen($data)) { $value = substr($data, 0, $bytes); - $data = substr($data, $bytes); + $data = substr($data, strlen($value)); } return $value; @@ -243,6 +243,8 @@ } $data = substr($data, $bytes); + } else { + $data = ''; } return $value; @@ -358,6 +360,9 @@ // Read and truncate to length. $value = $this->_getx($data, $datalen); + if ($value === null) { + break; + } } if ($attr_type == self::MAPI_UNICODE_STRING) { @@ -534,6 +539,7 @@ $uncomp = ''; $preload = "{\\rtf1\\ansi\\mac\\deff0\\deftab720{\\fonttbl;}{\\f0\\fnil \\froman \\fswiss \\fmodern \\fscript \\fdecor MS Sans SerifSymbolArialTimes New RomanCourier{\\colortbl\\red0\\green0\\blue0\n\r\\par \\pard\\plain\\f0\\fs20\\b\\i\\u\\tab\\tx"; $length_preload = strlen($preload); + $max_len = strlen($data); for ($cnt = 0; $cnt < $length_preload; $cnt++) { $uncomp .= $preload[$cnt]; @@ -570,6 +576,10 @@ $uncomp .= $data[$in++]; ++$out; } + + if ($in >= $max_len) { + break; + } } return substr($uncomp, $length_preload); diff -Nru roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_utils.php roundcube-1.6.17+dfsg/program/lib/Roundcube/rcube_utils.php --- roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_utils.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/program/lib/Roundcube/rcube_utils.php 2026-07-05 12:42:51.000000000 +0200 @@ -442,9 +442,9 @@ $host = trim($host, '[]'); // IPLib does not seem to work with IPv6 syntax for IPv4 addresses - $host = preg_replace('/^::ffff:/i', '', $host); + $host = preg_replace('/^[0:]*:ffff:/i', '', $host); - if (preg_match('/([0-9a-f.-]+)\.nip\.io$/i', $host, $matches)) { + if (preg_match('/([0-9a-f.-]+)\.(nip|sslip)\.io$/i', $host, $matches)) { $host = trim($matches[1], '-.'); } diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/enigma/composer.json roundcube-1.6.17+dfsg/public_html/plugins/enigma/composer.json --- roundcube-1.6.16+dfsg/public_html/plugins/enigma/composer.json 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/enigma/composer.json 2026-07-05 12:42:51.000000000 +0200 @@ -3,7 +3,7 @@ "type": "roundcube-plugin", "description": "Server-side PGP Encryption for Roundcube", "license": "GPL-3.0-or-later", - "version": "0.9", + "version": "1.0", "authors": [ { "name": "Aleksander Machniak", diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/enigma/config.inc.php.dist roundcube-1.6.17+dfsg/public_html/plugins/enigma/config.inc.php.dist --- roundcube-1.6.16+dfsg/public_html/plugins/enigma/config.inc.php.dist 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/enigma/config.inc.php.dist 2026-07-05 12:42:51.000000000 +0200 @@ -78,3 +78,18 @@ // - enigma_options_lock = ['sign'] // - dont_override = ['enigma_sign_all'] $config['enigma_options_lock'] = []; + +// Enable Kolab's Web Of Anti-Trust feature +// Fetches public keys from DNS. Default: false +// To enable set it to True or an array of domain names. +$config['enigma_woat'] = false; + +// Enable automatic public key lookups and import. +// Fetches public keys from a HKP v1 key server. Default: null +// To enable set it to a url e.g. 'https://pgp.roundcube.test' +// or an array map like this: +// $config['enigma_keyserver'] = [ +// 'domain.tld' => 'https://pgp.domain.tld', +// '*' => 'https://keys.openpgp.org', +// ] +$config['enigma_keyserver'] = null; diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/enigma/lib/enigma_engine.php roundcube-1.6.17+dfsg/public_html/plugins/enigma/lib/enigma_engine.php --- roundcube-1.6.16+dfsg/public_html/plugins/enigma/lib/enigma_engine.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/enigma/lib/enigma_engine.php 2026-07-05 12:42:51.000000000 +0200 @@ -28,6 +28,7 @@ private $pgp_driver; private $smime_driver; private $password_time; + private $sender; private $cache = []; public $decryptions = []; @@ -272,6 +273,9 @@ $recipients = array_unique($recipients); + // Fetch keys from external sources, if configured + $this->sync_keys($recipients); + // find recipient public keys foreach ((array) $recipients as $email) { if ($email == $from && $sign_key) { @@ -380,6 +384,17 @@ return; } + // Get the message/part sender + if (!empty($p['object']->sender) && !empty($p['object']->sender['mailto'])) { + $this->sender = $p['object']->sender['mailto']; + } + if (!empty($p['structure']->headers) && !empty($p['structure']->headers['from'])) { + $from = rcube_mime::decode_address_list($p['structure']->headers['from'], 1, false); + if (($from = current($from)) && !empty($from['mailto'])) { + $this->sender = $from['mailto']; + } + } + // Don't be tempted to support encryption in text/html parts // Because of EFAIL vulnerability we should never support this (#6289) @@ -881,6 +896,11 @@ { // @TODO: Handle big bodies using (temp) files + // Import sender's key from external sources, if configured + if ($this->sender) { + $this->sync_keys([$this->sender]); + } + // Get rid of possible non-ascii characters (#5962) $sig_body = preg_replace('/[^\x00-\x7F]/', '', (string) $sig_body); @@ -905,6 +925,11 @@ { // @TODO: Handle big bodies using (temp) files + // Import sender's key from external sources, if configured + if ($this->sender) { + $this->sync_keys([$this->sender]); + } + // Get rid of possible non-ascii characters (#5962) $msg_body = preg_replace('/[^\x00-\x7F]/', '', $msg_body); @@ -1020,19 +1045,25 @@ return; } - $mode = $can_sign ? enigma_key::CAN_SIGN : enigma_key::CAN_ENCRYPT; - $ret = null; + $mode = $can_sign ? enigma_key::CAN_SIGN : enigma_key::CAN_ENCRYPT; + $found = []; // check key validity and type foreach ($result as $key) { if (($subkey = $key->find_subkey($email, $mode)) && (!$can_sign || $key->get_type() == enigma_key::TYPE_KEYPAIR) ) { - $ret = $key; - break; + $found[$subkey->get_creation_date(true)] = $key; } } + // Use the most recent one + if (count($found) > 1) { + ksort($found, SORT_NUMERIC); + } + + $ret = count($found) > 0 ? array_pop($found) : null; + // cache private key info for better performance // we can skip one list_keys() call when signing and attaching a key if ($can_sign) { @@ -1442,4 +1473,31 @@ ); } } + + /** + * Import public keys from configured key lookup sources. + * + * @param array $recipients List of email addresses + */ + protected function sync_keys($recipients) + { + $import = []; + + foreach ($recipients as $recipient) { + if (!strpos($recipient, '@')) { + continue; + } + + if ($key = enigma_key_lookup::woat($recipient)) { + $import[] = $key; + } elseif ($key = enigma_key_lookup::keyserver($recipient)) { + $import[] = $key; + } + } + + // Import the fetched keys + if (!empty($import)) { + $this->import_key(implode("\n", $import)); + } + } } diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/enigma/lib/enigma_key_lookup.php roundcube-1.6.17+dfsg/public_html/plugins/enigma/lib/enigma_key_lookup.php --- roundcube-1.6.16+dfsg/public_html/plugins/enigma/lib/enigma_key_lookup.php 1970-01-01 01:00:00.000000000 +0100 +++ roundcube-1.6.17+dfsg/public_html/plugins/enigma/lib/enigma_key_lookup.php 2026-07-05 12:42:51.000000000 +0200 @@ -0,0 +1,159 @@ +<?php + +use GuzzleHttp\Exception\RequestException; + +/* + +-------------------------------------------------------------------------+ + | Key lookup engine of the Enigma Plugin | + | | + | Copyright (C) The Roundcube Dev Team | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + +-------------------------------------------------------------------------+ + | Author: Aleksander Machniak <[email protected]> | + +-------------------------------------------------------------------------+ +*/ + +/** + * PGP public key lookup engine for the Enigma plugin. + */ +class enigma_key_lookup +{ + /** + * Find a public key in DNS (according to Kolab Web-Of-Anti-Trust). + * + * @param string $recipient Email address + */ + public static function woat($recipient): ?string + { + $woat = rcmail::get_instance()->config->get('enigma_woat'); + + if (empty($woat)) { + return null; + } + + [$local, $domain] = explode('@', $recipient); + + // Do this for configured domains only + if (is_array($woat) && !in_array_nocase($domain, $woat)) { + return null; + } + + // remove parts behind a recipient delimiter ("jeroen+Trash" => "jeroen") + $local = preg_replace('/\+.*$/', '', $local); + + $fqdn = sha1($local) . '._woat.' . $domain; + + // Fetch the TXT record(s) + if (($records = dns_get_record($fqdn, \DNS_TXT)) === false) { + return null; + } + + foreach ($records as $record) { + if (strpos($record['txt'], 'v=woat1,') === 0) { + $entry = explode('public_key=', $record['txt']); + if (count($entry) == 2) { + // For now we support only one key + return $entry[1]; + } + } + } + + return null; + } + + /** + * Find a public key in a HKP v1 server. + * + * @param string $recipient Email address + */ + public static function keyserver($recipient): ?string + { + $rcmail = rcmail::get_instance(); + $keyserver = $rcmail->config->get('enigma_keyserver'); + + if (empty($keyserver)) { + return null; + } + + $recipient = strtolower($recipient); + + if (is_array($keyserver)) { + [$local, $domain] = explode('@', $recipient); + + if (!empty($keyserver[$domain])) { + $keyserver = $keyserver[$domain]; + } elseif (!empty($keyserver['*'])) { + $keyserver = $keyserver['*']; + } else { + return null; + } + } + + // TODO: Support key server discovery + + // Get keys metadata + try { + $client = $rcmail->get_http_client(); + + $response = $client->get($keyserver . '/pks/lookup?op=index&options=mr&search=' . rawurlencode($recipient)); + + $source = $response->getBody(); + $ctype = $response->getHeader('Content-Type'); + $ctype = !empty($ctype) ? $ctype[0] : ''; + } catch (\Exception $e) { + if (!($e instanceof RequestException) || $e->getResponse()->getStatusCode() != 404) { + rcube::raise_error($e, true, false); + } + } + + if (!isset($source) || strpos($source, 'info:1:') !== 0) { + return null; + } + + // Process the keys metadata + // TODO: Ignore revoked/expired keys/identities? + $keyid = null; + $list = []; + foreach (preg_split('/\r?\n/', $source) as $line) { + if (strpos($line, 'pub:') === 0) { + $tokens = explode(':', $line); + $keyid = $tokens[1] ?? null; + } elseif ($keyid && strpos($line, 'uid:') === 0) { + $tokens = explode(':', $line); + $identity = strtolower(rawurldecode($tokens[1] ?? '')); + if (strpos($identity, $recipient) !== false) { + $list[] = $keyid; + } + } else { + $keyid = null; + } + } + + // Get keys + $output = []; + foreach (array_unique($list) as $id) { + try { + $response = $client->get($keyserver . '/pks/lookup?op=get&options=mr&search=0x' . $id); + + $source = $response->getBody(); + $ctype = $response->getHeader('Content-Type'); + $ctype = !empty($ctype) ? $ctype[0] : ''; + + if ($ctype === 'application/pgp-keys' + && strpos($source, '-----BEGIN PGP PUBLIC KEY BLOCK-----') === 0 + ) { + $output[] = $source; + } + } catch (\Exception $e) { + if (!($e instanceof RequestException) || $e->getResponse()->getStatusCode() != 404) { + rcube::raise_error($e, true, false); + } + } + } + + return implode("\n", $output); + } +} diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/enigma/lib/enigma_subkey.php roundcube-1.6.17+dfsg/public_html/plugins/enigma/lib/enigma_subkey.php --- roundcube-1.6.16+dfsg/public_html/plugins/enigma/lib/enigma_subkey.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/enigma/lib/enigma_subkey.php 2026-07-05 12:42:51.000000000 +0200 @@ -93,12 +93,18 @@ /** * Returns subkey creation date-time string * - * @return string|null + * @param bool $asInt Return the date as an integer + * + * @return string|null|int */ - function get_creation_date() + function get_creation_date($asInt = false) { if (empty($this->created)) { - return null; + return $asInt ? 0 : null; + } + + if ($asInt) { + return (int) $this->created->format('U'); } $date_format = rcube::get_instance()->config->get('date_format', 'Y-m-d'); diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/directadmin.php roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/directadmin.php --- roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/directadmin.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/directadmin.php 2026-07-05 12:42:51.000000000 +0200 @@ -26,18 +26,17 @@ class rcube_directadmin_password { - public function save($curpass, $passwd) + public function save($curpass, $passwd, $username) { $rcmail = rcmail::get_instance(); $Socket = new HTTPSocket; - $da_user = $_SESSION['username']; $da_curpass = $curpass; $da_newpass = $passwd; $da_host = $rcmail->config->get('password_directadmin_host'); $da_port = $rcmail->config->get('password_directadmin_port'); - if (strpos($da_user, '@') === false) { + if (strpos($username, '@') === false) { return ['code' => PASSWORD_ERROR, 'message' => 'Change the SYSTEM user password through control panel!']; } @@ -47,7 +46,7 @@ $Socket->connect($da_host,$da_port); $Socket->set_method('POST'); $Socket->query('/CMD_CHANGE_EMAIL_PASSWORD', [ - 'email' => $da_user, + 'email' => $username, 'oldpassword' => $da_curpass, 'password1' => $da_newpass, 'password2' => $da_newpass, @@ -56,14 +55,11 @@ $response = $Socket->fetch_parsed_body(); - //DEBUG - //rcube::console("Password Plugin: [USER: $da_user] [HOST: $da_host] - Response: [SOCKET: ".$Socket->result_status_code."] [DA ERROR: ".strip_tags($response['error'])."] [TEXT: ".$response[text]."]"); - if ($Socket->result_status_code != 200) { return ['code' => PASSWORD_CONNECT_ERROR, 'message' => $Socket->error[0]]; } - if ($response['error'] == 1) { + if (isset($response['error']) && $response['error'] == 1) { return ['code' => PASSWORD_ERROR, 'message' => strip_tags($response['text'])]; } diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/ldap_exop.php roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/ldap_exop.php --- roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/ldap_exop.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/ldap_exop.php 2026-07-05 12:42:51.000000000 +0200 @@ -31,7 +31,8 @@ class rcube_ldap_exop_password extends rcube_ldap_simple_password { - function save($curpass, $passwd) + #[Override] + public function save($curpass, $passwd, $username) { if (!function_exists('ldap_exop_passwd')) { rcube::raise_error([ @@ -45,6 +46,8 @@ return PASSWORD_ERROR; } + $this->username = $username; + // Connect and bind $ret = $this->connect($curpass); if ($ret !== true) { diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/ldap.php roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/ldap.php --- roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/ldap.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/ldap.php 2026-07-05 12:42:51.000000000 +0200 @@ -28,17 +28,23 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -class rcube_ldap_password +require_once __DIR__ . '/ldap_simple.php'; + +class rcube_ldap_password extends rcube_ldap_simple_password { - public function save($curpass, $passwd) + #[Override] + public function save($curpass, $passwd, $username) { $rcmail = rcmail::get_instance(); + + $this->username = $username; + require_once 'Net/LDAP2.php'; require_once __DIR__ . '/ldap_simple.php'; // Building user DN if ($userDN = $rcmail->config->get('password_ldap_userDN_mask')) { - $userDN = rcube_ldap_simple_password::substitute_vars($userDN); + $userDN = $this->substitute_vars($userDN); } else { $userDN = $this->search_userdn($rcmail); @@ -155,7 +161,7 @@ * Use search_base and search_filter defined in config file. * Return the found DN. */ - function search_userdn($rcmail) + public function search_userdn($rcmail, $ds = null) { $binddn = $rcmail->config->get('password_ldap_searchDN'); $bindpw = $rcmail->config->get('password_ldap_searchPW'); @@ -180,8 +186,8 @@ return ''; } - $base = rcube_ldap_simple_password::substitute_vars($rcmail->config->get('password_ldap_search_base')); - $filter = rcube_ldap_simple_password::substitute_vars($rcmail->config->get('password_ldap_search_filter')); + $base = $this->substitute_vars($rcmail->config->get('password_ldap_search_base')); + $filter = $this->substitute_vars($rcmail->config->get('password_ldap_search_filter')); $options = [ 'scope' => 'sub', 'attributes' => [], diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/ldap_ppolicy.php roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/ldap_ppolicy.php --- roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/ldap_ppolicy.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/ldap_ppolicy.php 2026-07-05 12:42:51.000000000 +0200 @@ -27,12 +27,6 @@ $bindPW = $rcmail->config->get('password_ldap_ppolicy_searchPW'); $cafile = $rcmail->config->get('password_ldap_ppolicy_cafile'); - $log_dir = $rcmail->config->get('log_dir'); - - if (empty($log_dir)) { - $log_dir = RCUBE_INSTALL_PATH . 'logs'; - } - $descriptorspec = [ 0 => ["pipe", "r"], // stdin is a pipe that the child will read from 1 => ["pipe", "w"], // stdout is a pipe that the child will write to diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/ldap_samba_ad.php roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/ldap_samba_ad.php --- roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/ldap_samba_ad.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/ldap_samba_ad.php 2026-07-05 12:42:51.000000000 +0200 @@ -32,7 +32,7 @@ class rcube_ldap_samba_ad_password extends rcube_ldap_simple_password { #[Override] - public function save($curpass, $passwd) + public function save($curpass, $passwd, $username) { if (!function_exists('ldap_mod_replace')) { rcube::raise_error([ @@ -44,6 +44,8 @@ return PASSWORD_ERROR; } + $this->username = $username; + // Connect and bind $ret = $this->connect($curpass); if ($ret !== true) { diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/ldap_simple.php roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/ldap_simple.php --- roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/ldap_simple.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/ldap_simple.php 2026-07-05 12:42:51.000000000 +0200 @@ -31,19 +31,21 @@ { protected $debug = false; protected $user; + protected $username; protected $conn; - - public function save($curpass, $passwd) + public function save($curpass, $passwd, $username) { + $this->username = $username; + $rcmail = rcmail::get_instance(); + $lchattr = $rcmail->config->get('password_ldap_lchattr'); + $pwattr = $rcmail->config->get('password_ldap_pwattr', 'userPassword'); + $smbpwattr = $rcmail->config->get('password_ldap_samba_pwattr'); + $smblchattr = $rcmail->config->get('password_ldap_samba_lchattr'); + $samba = $rcmail->config->get('password_ldap_samba'); + $pass_mode = $rcmail->config->get('password_ldap_encodage', 'md5-crypt'); - $lchattr = $rcmail->config->get('password_ldap_lchattr'); - $pwattr = $rcmail->config->get('password_ldap_pwattr', 'userPassword'); - $smbpwattr = $rcmail->config->get('password_ldap_samba_pwattr'); - $smblchattr = $rcmail->config->get('password_ldap_samba_lchattr'); - $samba = $rcmail->config->get('password_ldap_samba'); - $pass_mode = $rcmail->config->get('password_ldap_encodage', 'md5-crypt'); $crypted_pass = password::hash_password($passwd, $pass_mode); // Support password_ldap_samba option for backward compat. @@ -162,7 +164,7 @@ $user_dn = $plugin['user_dn']; } else if ($user_dn = $rcmail->config->get('password_ldap_userDN_mask')) { - $user_dn = self::substitute_vars($user_dn); + $user_dn = $this->substitute_vars($user_dn); } else { $user_dn = $this->search_userdn($rcmail, $ds); @@ -210,7 +212,7 @@ * Use search_base and search_filter defined in config file * Return the found DN */ - function search_userdn($rcmail, $ds) + public function search_userdn($rcmail, $ds = null) { $search_user = $rcmail->config->get('password_ldap_searchDN'); $search_pass = $rcmail->config->get('password_ldap_searchPW'); @@ -231,8 +233,8 @@ $this->_debug("S: OK"); - $search_base = self::substitute_vars($search_base); - $search_filter = self::substitute_vars($search_filter); + $search_base = $this->substitute_vars($search_base); + $search_filter = $this->substitute_vars($search_filter); $this->_debug("C: Search $search_base for $search_filter"); @@ -258,12 +260,12 @@ * Substitute %login, %name, %domain, %dc in $str * See plugin config for details */ - public static function substitute_vars($str) + protected function substitute_vars($str) { - $str = str_replace('%login', $_SESSION['username'], $str); - $str = str_replace('%l', $_SESSION['username'], $str); + $str = str_replace('%login', $this->username, $str); + $str = str_replace('%l', $this->username, $str); - $parts = explode('@', $_SESSION['username']); + $parts = explode('@', $this->username); if (count($parts) == 2) { $dc = 'dc='.strtr($parts[1], ['.' => ',dc=']); // hierarchal domain string diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/sql.php roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/sql.php --- roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/sql.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/sql.php 2026-07-05 12:42:51.000000000 +0200 @@ -28,12 +28,13 @@ /** * Update current user password * - * @param string $curpass Current password - * @param string $passwd New password + * @param string $curpass Current password + * @param string $passwd New password + * @param string $username Username * * @return int Result */ - function save($curpass, $passwd) + public function save($curpass, $passwd, $username) { $rcmail = rcmail::get_instance(); @@ -92,7 +93,6 @@ $local_part = $rcmail->user->get_username('local'); $domain_part = $rcmail->user->get_username('domain'); - $username = $_SESSION['username']; $host = $_SESSION['imap_host']; // convert domains to/from punycode diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/xmail.php roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/xmail.php --- roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/xmail.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/xmail.php 2026-07-05 12:42:51.000000000 +0200 @@ -33,10 +33,10 @@ class rcube_xmail_password { - function save($currpass, $newpass) + public function save($currpass, $newpass, $username) { $rcmail = rcmail::get_instance(); - list($user, $domain) = explode('@', $_SESSION['username']); + [$user, $domain] = explode('@', $username); $xmail = new XMail; diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/zxcvbn.php roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/zxcvbn.php --- roundcube-1.6.16+dfsg/public_html/plugins/password/drivers/zxcvbn.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/password/drivers/zxcvbn.php 2026-07-05 12:42:51.000000000 +0200 @@ -58,8 +58,14 @@ return; } - $zxcvbn = new ZxcvbnPhp\Zxcvbn(); - $strength = $zxcvbn->passwordStrength($passwd); + $rcmail = rcmail::get_instance(); + $userData = [ + $rcmail->user->get_username('local'), + $rcmail->user->get_username(), + ]; + + $zxcvbn = new Zxcvbn(); // @phpstan-ignore-line + $strength = $zxcvbn->passwordStrength($passwd, $userData); // @phpstan-ignore-line return [$strength['score'] + 1, $strength['feedback']['warning']]; } diff -Nru roundcube-1.6.16+dfsg/public_html/plugins/password/password.php roundcube-1.6.17+dfsg/public_html/plugins/password/password.php --- roundcube-1.6.16+dfsg/public_html/plugins/password/password.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/public_html/plugins/password/password.php 2026-07-05 12:42:51.000000000 +0200 @@ -499,7 +499,8 @@ if ($exceptions = $this->rc->config->get('password_login_exceptions')) { $exceptions = array_map('trim', (array) $exceptions); $exceptions = array_filter($exceptions); - $username = $_SESSION['username']; + + $username = $this->rc->get_user_name(); foreach ($exceptions as $ec) { if ($username === $ec) { @@ -826,15 +827,20 @@ $format = $rcmail->config->get('password_username_format'); } - if (!$format) { - return $_SESSION['username']; - } + $username = $rcmail->get_user_name(); - return strtr($format, [ + if ($format) { + $username = strtr($format, [ '%l' => $rcmail->user->get_username('local'), '%d' => $rcmail->user->get_username('domain'), - '%u' => $_SESSION['username'], - ]); + '%u' => $username, + ]); + } + + // Sanitize the username + $username = preg_replace('/[*()\r\n\0\\\]/', '', $username); + + return $username; } /** diff -Nru roundcube-1.6.16+dfsg/tests/Framework/Text2Html.php roundcube-1.6.17+dfsg/tests/Framework/Text2Html.php --- roundcube-1.6.16+dfsg/tests/Framework/Text2Html.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/tests/Framework/Text2Html.php 2026-07-05 12:42:51.000000000 +0200 @@ -155,6 +155,23 @@ } /** + * Test XSS issue + */ + public function test_text2html_xss53() + { + $input = '[email protected]?]<img/src="x"/onerror=alert(document.domain)>'; + $expected = '<div class="pre">' + . '<a href="mailto:[email protected]?">[email protected]?</a>' + . ']<img/src="x"/onerror=alert(document.domain)>' + . '</div>'; + + $t2h = new \rcube_text2html($input); + $html = $t2h->get_html(); + + $this->assertSame($expected, $html); + } + + /** * Test bug #8021 */ function test_text2html_8021() diff -Nru roundcube-1.6.16+dfsg/tests/Framework/TnefDecoder.php roundcube-1.6.17+dfsg/tests/Framework/TnefDecoder.php --- roundcube-1.6.16+dfsg/tests/Framework/TnefDecoder.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/tests/Framework/TnefDecoder.php 2026-07-05 12:42:51.000000000 +0200 @@ -26,6 +26,35 @@ } /** + * Test TNEF decoding (#10193) + */ + public function test_decompress_10193() + { + $body = file_get_contents(TESTS_DIR . 'src/winmail-10193.tnef'); + $tnef = new \rcube_tnef_decoder(); + $result = $tnef->decompress($body); + + $this->assertSame("..Re: Vyzva_Teplotka_Ordinace_perioperacni_pece\0.html", trim($result['message']['name'])); + $this->assertSame('text', $result['message']['type']); + $this->assertSame('html', $result['message']['subtype']); + $this->assertSame(16664, $result['message']['size']); + $this->assertSame(16664, strlen($result['message']['stream'])); + $this->assertCount(3, $result['attachments']); + $this->assertSame(34965, $result['attachments'][0]['size']); + $this->assertSame(34965, strlen($result['attachments'][0]['stream'])); + $this->assertTrue(str_starts_with($result['attachments'][0]['stream'], "\x89PNG")); + $this->assertSame('73471187-19a4-4a80-ac0b-6e776ff823fc', $result['attachments'][0]['content-id']); + $this->assertSame(39201, $result['attachments'][1]['size']); + $this->assertSame(39201, strlen($result['attachments'][1]['stream'])); + $this->assertTrue(str_starts_with($result['attachments'][1]['stream'], "\x89PNG")); + $this->assertSame('32c2ea33-0a82-4941-9843-9b5bbd7e9cb2', $result['attachments'][1]['content-id']); + $this->assertSame(37205, $result['attachments'][2]['size']); + $this->assertSame(37205, strlen($result['attachments'][2]['stream'])); + $this->assertTrue(str_starts_with($result['attachments'][2]['stream'], "\x89PNG")); + $this->assertSame('2be4b893-2b78-4442-81c0-8ddac66487c5', $result['attachments'][2]['content-id']); + } + + /** * Test TNEF decoding */ function test_decompress_body() diff -Nru roundcube-1.6.16+dfsg/tests/Framework/Utils.php roundcube-1.6.17+dfsg/tests/Framework/Utils.php --- roundcube-1.6.16+dfsg/tests/Framework/Utils.php 2026-05-24 09:40:12.000000000 +0200 +++ roundcube-1.6.17+dfsg/tests/Framework/Utils.php 2026-07-05 12:42:51.000000000 +0200 @@ -598,6 +598,9 @@ ['http://[::ffff:127.0.0.1]:8080', true], ['http://127.0.0.1.nip.io', true], ['http://metadata.google.internal', true], + ['http://127.0.0.1.sslip.io/', true], + ['http://[0:0:0:0:0:ffff:127.0.0.1]/', true], + ['http://0/', true], // Non-local hosts ['http://[2001:470::76:0:0:0:2]', false], ['http://domain.tld', false], Binary files /tmp/q0fNGuandc/roundcube-1.6.16+dfsg/tests/src/winmail-10193.tnef and /tmp/425G8zaV28/roundcube-1.6.17+dfsg/tests/src/winmail-10193.tnef differ
signature.asc
Description: PGP signature

