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>'
+            . ']&lt;img/src=&quot;x&quot;/onerror=alert(document.domain)&gt;'
+            . '</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

Attachment: signature.asc
Description: PGP signature

Reply via email to