[Pkg-roundcube-maintainers] Bug#1141495: roundcube: Multiple security vulnerabilities
Guilhem Moulin
guilhem at debian.org
Wed Jul 15 00:37:29 BST 2026
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
-------------- next part --------------
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 <guilhem at debian.org> 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 <alec at alec.pl> |
+ +-------------------------------------------------------------------------+
+*/
+
+/**
+ * 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 <alec at alec.pl> |
+ +-------------------------------------------------------------------------+
+*/
+
+/**
+ * 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 = 'a at a.co?]<img/src="x"/onerror=alert(document.domain)>';
+ $expected = '<div class="pre">'
+ . '<a href="mailto:a at a.co?">a at a.co?</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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <http://alioth-lists.debian.net/pipermail/pkg-roundcube-maintainers/attachments/20260715/56e69eb6/attachment-0001.sig>
More information about the Pkg-roundcube-maintainers
mailing list