[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1068411: bookworm-pu: package schleuder/4.0.3-7+deb12u1



Package: release.debian.org
Severity: normal
Tags: bookworm
User: release.debian.org@packages.debian.org
Usertags: pu
Control: affects -1 + src:schleuder

Dear release team,

Schleuder, as currently present in bookworm, 4.0.3-7, is affected by
multiple bugs, which I would like to address via this proposed-update,
4.0.3-7+deb12u1.

All fixes as detailed below were made upstream, and fixed in unstable
via 4.0.3-11.

[ Reasons ]
- The 'x-subscribe' keyword doesn't enforce proper validation of
  optional flags. Due to this, an email address might get erroneously
  subscribed with 'admin' permissions.

- The 'x-add-key' keyword fails to import keys from attachments as
  Thunderbird sends them. This regression was introduced in 4.0.0.

- The 'x-add-key' keyword doesn't return a proper error message, if an
  email contains no further content or attachments.

- The 'message handler' strips keywords in the middle of emails. People
  might send "usage instructions", including keywords and details how to
  use them, within an email to a Schleuder list. Due to the keywords
  being removed, the value of such emails is greatly limited.

- The list option 'receive_from_subscribed_emailaddresses_only' checks
  an email address case-sensitive. Valid emails might get erroneously
  rejected, if an uppercase email address was used in the From: header
  of the incoming email.

- The 'message handler' doesn't consider the email address as used in
  the From: header of the incoming mail, when finding the email address
  to send the reply to. A reply might be send to a wrong email address,
  in case of multiple subscriptions which rely on the same key.

[ Impacts ]
- Severe; due to insufficient input validation, subscriptions might get
  'admin' privileges assigned, while they must not. This poses a
  security threat.

- Bad UX; a workaround might be to send an email which inlines keys in
  the email body.

- Bad UX; users might be left clueless if the import of keys was
  successful or wasn't, as no reply is send.

- Bad UX; a workaround might be to send such an email offlist, which
  might be challenging if: such an email should be encrypted, as all
  relevant keys would be required; or: the list of subscriptions of a
  Schleuder list is (partially) unknown.

- Bad UX; a workaround might be to disable this list option.
  Alternatively, the casing of the email address in the From: header
  could be adjusted, which might be impossible depending on the
  environment people operate in.

- Bad UX, possible implications of privacy and security if a user
  subscribes two email addresses, which rely on the same key, but are
  intended to be used for different list actions, like "admin" vs.
  "regular"; a workaround might be to rely on distinct keys.

[ Tests ]
Tests were done via upstream and Debian CI, manually and on production
systems for several weeks.

[ Risks ]
Risks should be low, as extensive testing happened. All fixes are
targeted and only change related code. The vast majority of the attached
debdiff makes changes to the testsuite, the actual code changes are
quite limited.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
- x-subscribe: fix parsing of arguments. Optional flags provided at the
  end are only taken into account if there is a valid fingerprint, and
  they are checked to be "true", or "false". Before, due to insufficient
  validation, an email address might have been erroneously subscribed
  with 'admin' permissions. (Closes: #1068330)

- x-add-key: fix importing keys from attachments as Thunderbird sends
  them. Before, such attachments were ignored. This regression was
  introduced in 4.0.0. (Closes: #1068331)

- x-add-key: fix handling of emails without further content or
  attachments. A proper error message is returned. Before, a traceback
  was thrown. (Closes: #1068332)

- Stop looking for keywords if email starts with other content. This
  allows to send usage instructions including keywords within an email
  to a Schleuder list. Before, these keywords would have been stripped,
  which limited the value of such an email. This change matches
  documentation, which tells that keywords require that they are written
  in the beginning of an email body. (Closes: #1068333)

- receive_from_subscribed_emailaddresses_only(): check downcased email
  address. This follows and mirrors changes made in both schleuder-cli
  and schleuder-web, which nowadays enforce lowercase if adding
  subscriptions. Before, incoming emails might have been erroneously
  rejected, if an uppercase email address was used in the From: header.
  (Closes: #1068334)

- Consider From: when finding reply address. Look for a subscription
  that matches the sending email address, in case of multiple
  subscriptions which rely on the same key. As a fallback, the first
  subscription found is used. Before, people might have received a reply
  to a different email address. (Closes: #1068335)

Thanks for your work!

Cheers,
Georg
diff -Nru schleuder-4.0.3/debian/changelog schleuder-4.0.3/debian/changelog
--- schleuder-4.0.3/debian/changelog	2022-12-26 18:49:28.000000000 +0000
+++ schleuder-4.0.3/debian/changelog	2024-04-04 18:15:10.000000000 +0000
@@ -1,3 +1,36 @@
+schleuder (4.0.3-7+deb12u1) bookworm; urgency=medium
+
+  * debian/patches:
+    - x-subscribe: fix parsing of arguments. Optional flags provided at the
+      end are only taken into account if there is a valid fingerprint, and
+      they are checked to be "true", or "false". Before, due to insufficient
+      validation, an email address might have been erroneously subscribed with
+      'admin' permissions. (Closes: #1068330)
+    - x-add-key: fix importing keys from attachments as Thunderbird sends
+      them. Before, such attachments were ignored. This regression was
+      introduced in 4.0.0. (Closes: #1068331)
+    - x-add-key: fix handling of emails without further content or
+      attachments. A proper error message is returned. Before, a traceback was
+      thrown. (Closes: #1068332)
+    - Stop looking for keywords if email starts with other content. This
+      allows to send usage instructions including keywords within an email to
+      a Schleuder list. Before, these keywords would have been stripped, which
+      limited the value of such an email. This change matches documentation,
+      which tells that keywords require that they are written in the beginning
+      of an email body. (Closes: #1068333)
+    - receive_from_subscribed_emailaddresses_only(): check downcased email
+      address. This follows and mirrors changes made in both schleuder-cli and
+      schleuder-web, which nowadays enforce lowercase if adding subscriptions.
+      Before, incoming emails might have been erroneously rejected, if an
+      uppercase email address was used in the From: header. (Closes: #1068334)
+    - Consider From: when finding reply address. Look for a subscription that
+      matches the sending email address, in case of multiple subscriptions
+      which rely on the same key. As a fallback, the first subscription found
+      is used. Before, people might have received a reply to a different email
+      address. (Closes: #1068335)
+
+ -- Georg Faerber <georg@debian.org>  Thu, 04 Apr 2024 18:15:10 +0000
+
 schleuder (4.0.3-7) unstable; urgency=medium
 
   * Team upload
diff -Nru schleuder-4.0.3/debian/patches/0032-Improve-parsing-arguments-of-x-subscribe.patch schleuder-4.0.3/debian/patches/0032-Improve-parsing-arguments-of-x-subscribe.patch
--- schleuder-4.0.3/debian/patches/0032-Improve-parsing-arguments-of-x-subscribe.patch	1970-01-01 00:00:00.000000000 +0000
+++ schleuder-4.0.3/debian/patches/0032-Improve-parsing-arguments-of-x-subscribe.patch	2024-04-04 18:15:10.000000000 +0000
@@ -0,0 +1,135 @@
+From: paz <paz@schleuder.org>
+Date: Mon, 29 Jan 2024 18:09:12 +0100
+Subject: Improve parsing arguments of x-subscribe
+
+The flags at the end now are only taken into account if there's a valid
+fingerprint, and they are checked to be "true", or "false".
+---
+ lib/schleuder/gpgme/key.rb                         |  2 +-
+ .../keyword_handlers/subscription_management.rb    | 22 +++++++++++--
+ locales/de.yml                                     |  2 +-
+ locales/en.yml                                     |  2 +-
+ spec/schleuder/integration/keywords_spec.rb        | 38 ++++++++++++++++++++++
+ 5 files changed, 60 insertions(+), 6 deletions(-)
+
+diff --git a/lib/schleuder/gpgme/key.rb b/lib/schleuder/gpgme/key.rb
+index 0039e1c..f5d1a7b 100644
+--- a/lib/schleuder/gpgme/key.rb
++++ b/lib/schleuder/gpgme/key.rb
+@@ -82,7 +82,7 @@ module GPGME
+     end
+ 
+     def self.valid_fingerprint?(fp)
+-      fp =~ Schleuder::Conf::FINGERPRINT_REGEXP
++      fp.present? && fp.match?(Schleuder::Conf::FINGERPRINT_REGEXP)
+     end
+   end
+ end
+diff --git a/lib/schleuder/keyword_handlers/subscription_management.rb b/lib/schleuder/keyword_handlers/subscription_management.rb
+index 1fc7f1b..68ca6ec 100644
+--- a/lib/schleuder/keyword_handlers/subscription_management.rb
++++ b/lib/schleuder/keyword_handlers/subscription_management.rb
+@@ -22,9 +22,25 @@ module Schleuder
+           while @arguments.first.present? && @arguments.first.match(/^(0x)?[a-f0-9]+$/i)
+             fingerprint << @arguments.shift.downcase
+           end
+-          # Use possibly remaining args as flags.
+-          adminflag = @arguments.shift.to_s.downcase.presence
+-          deliveryflag = @arguments.shift.to_s.downcase.presence
++          # If the collected values aren't a valid fingerprint, then the input
++          # didn't conform with what this code expects, and then the other
++          # values shouldn't be used.
++          unless GPGME::Key.valid_fingerprint?(fingerprint)
++            return I18n.t('keyword_handlers.subscription_management.subscribe_requires_arguments')
++          end
++          if @arguments.present?
++            # Use possibly remaining args as flags.
++            adminflag = @arguments.shift.to_s.downcase.presence
++            unless ['true', 'false'].include?(adminflag)
++              return I18n.t('keyword_handlers.subscription_management.subscribe_requires_arguments')
++            end
++            if @arguments.present?
++              deliveryflag = @arguments.shift.to_s.downcase.presence
++              unless ['true', 'false'].include?(deliveryflag)
++                return I18n.t('keyword_handlers.subscription_management.subscribe_requires_arguments')
++              end
++            end
++          end
+         end
+ 
+         sub, _ = @list.subscribe(email, fingerprint, adminflag, deliveryflag)
+diff --git a/locales/de.yml b/locales/de.yml
+index c179a2c..1ca97fe 100644
+--- a/locales/de.yml
++++ b/locales/de.yml
+@@ -202,7 +202,7 @@ de:
+         Als Admin musst du um deinen eigenen Fingerabdruck zu entfernen, noch zusätzlich das Argument force mitgeben. bspw.:
+         X-UNSET-FINGERPRINT: adminsubscription2@hostname force
+       subscribe_requires_arguments: |
+-        Fehler: Du hast zu dem Schlüsselwort 'SUBSCRIBE' keinen Wert angegeben.
++        Fehler: Du hast zu dem Schlüsselwort 'SUBSCRIBE' keine passenden Werte angegeben.
+ 
+         Mindestens ein Wert ist nötig, drei weitere sind optional. Bspw.:
+         X-SUBSCRIBE: new-subscription@hostname
+diff --git a/locales/en.yml b/locales/en.yml
+index 966584b..a75204b 100644
+--- a/locales/en.yml
++++ b/locales/en.yml
+@@ -206,7 +206,7 @@ en:
+         As an admin to unset your own fingerprint you must additionally pass the argument force. E.g.:
+         X-UNSET-FINGERPRINT: adminsubscription2@hostname force
+       subscribe_requires_arguments: |
+-        Error: You did not send any arguments for the keyword 'SUBSCRIBE'.
++        Error: You did not send proper arguments for the keyword 'SUBSCRIBE'.
+ 
+         At least one argument is required, three more are optional. E.g.:
+         X-SUBSCRIBE: new-subscription@hostname
+diff --git a/spec/schleuder/integration/keywords_spec.rb b/spec/schleuder/integration/keywords_spec.rb
+index e7fc4dc..c56d16a 100644
+--- a/spec/schleuder/integration/keywords_spec.rb
++++ b/spec/schleuder/integration/keywords_spec.rb
+@@ -298,6 +298,44 @@ describe 'user sends keyword' do
+     teardown_list_and_mailer(list)
+   end
+ 
++  it 'x-subscribe with invalid arguments' do
++    list = create(:list)
++    list.subscribe('schleuder@example.org', '59C71FB38AEE22E091C78259D06350440F759BD3', true)
++    ENV['GNUPGHOME'] = list.listdir
++    mail = Mail.new
++    mail.to = list.request_address
++    mail.from = list.admins.first.email
++    gpg_opts = {
++      encrypt: true,
++      keys: {list.request_address => list.fingerprint},
++      sign: true,
++      sign_as: list.admins.first.fingerprint
++    }
++    mail.gpg(gpg_opts)
++    mail.body = "x-list-name: #{list.email}\nX-SUBSCRIBE: test@example.org <test@example.org> 0x#{list.fingerprint}"
++    mail.deliver
++
++    encrypted_mail = Mail::TestMailer.deliveries.first
++    Mail::TestMailer.deliveries.clear
++
++    begin
++      Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address)
++    rescue SystemExit
++    end
++    raw = Mail::TestMailer.deliveries.first
++    message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup
++    subscription = list.subscriptions.where(email: 'test@example.org').first
++
++    expect(message.to).to eql(['schleuder@example.org'])
++    expect(message.to_s).not_to include('test@example.org has been subscribed')
++    expect(message.to_s).not_to include('translation missing')
++    expect(message.first_plaintext_part.body.to_s).to eql(I18n.t('keyword_handlers.subscription_management.subscribe_requires_arguments'))
++
++    expect(subscription).to be_blank
++
++    teardown_list_and_mailer(list)
++  end
++
+   it 'x-subscribe without arguments' do
+     list = create(:list)
+     list.subscribe('schleuder@example.org', '59C71FB38AEE22E091C78259D06350440F759BD3', true)
diff -Nru schleuder-4.0.3/debian/patches/0033-Fix-importing-keys-from-attachments-like-e.g.-Thunde.patch schleuder-4.0.3/debian/patches/0033-Fix-importing-keys-from-attachments-like-e.g.-Thunde.patch
--- schleuder-4.0.3/debian/patches/0033-Fix-importing-keys-from-attachments-like-e.g.-Thunde.patch	1970-01-01 00:00:00.000000000 +0000
+++ schleuder-4.0.3/debian/patches/0033-Fix-importing-keys-from-attachments-like-e.g.-Thunde.patch	2024-04-04 18:15:10.000000000 +0000
@@ -0,0 +1,486 @@
+From: paz <paz@schleuder.org>
+Date: Tue, 31 Oct 2023 14:09:02 +0100
+Subject: Fix importing keys from attachments like e.g. Thunderbird 115 sends
+ them
+
+---
+ lib/schleuder/keyword_handlers/key_management.rb   |   2 +-
+ spec/fixtures/example_key_binary.pgp               |   1 +
+ .../mutt-2.0.5-linux-xaddkey-attachment-binary.eml |  95 +++++++++++++++
+ ...hunderbird-115.2.3-macos-xaddkey-attachment.eml | 134 +++++++++++++++++++++
+ ...ey_421C19AF8B9C33B8B62D76EBDB2F7E271D773073.sec | 105 ++++++++++++++++
+ .../unit/keyword_handlers/key_management_spec.rb   |  66 +++++++++-
+ 6 files changed, 397 insertions(+), 6 deletions(-)
+ create mode 120000 spec/fixtures/example_key_binary.pgp
+ create mode 100644 spec/fixtures/mails/mutt-2.0.5-linux-xaddkey-attachment-binary.eml
+ create mode 100644 spec/fixtures/mails/thunderbird-115.2.3-macos-xaddkey-attachment.eml
+ create mode 100644 spec/fixtures/openpgpkey_421C19AF8B9C33B8B62D76EBDB2F7E271D773073.sec
+
+diff --git a/lib/schleuder/keyword_handlers/key_management.rb b/lib/schleuder/keyword_handlers/key_management.rb
+index 9b8c1b6..b07c8a2 100644
+--- a/lib/schleuder/keyword_handlers/key_management.rb
++++ b/lib/schleuder/keyword_handlers/key_management.rb
+@@ -125,7 +125,7 @@ module Schleuder
+ 
+       def import_keys_from_attachments
+         @mail.attachments.map do |attachment|
+-          import_from_string(attachment.body.raw_source)
++          import_from_string(attachment.body.decoded)
+         end
+       end
+ 
+diff --git a/spec/fixtures/example_key_binary.pgp b/spec/fixtures/example_key_binary.pgp
+new file mode 120000
+index 0000000..b093bf6
+--- /dev/null
++++ b/spec/fixtures/example_key_binary.pgp
+@@ -0,0 +1 @@
++example_key_binary.txt
+\ No newline at end of file
+diff --git a/spec/fixtures/mails/mutt-2.0.5-linux-xaddkey-attachment-binary.eml b/spec/fixtures/mails/mutt-2.0.5-linux-xaddkey-attachment-binary.eml
+new file mode 100644
+index 0000000..aa79858
+--- /dev/null
++++ b/spec/fixtures/mails/mutt-2.0.5-linux-xaddkey-attachment-binary.eml
+@@ -0,0 +1,95 @@
++Date: Wed, 1 Nov 2023 16:37:18 +0000
++From: user@example.org
++To: schleuder@example.org
++Subject: test
++Message-ID: <950124.162336@example.org>
++MIME-Version: 1.0
++Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
++	boundary="K5LL32V+kDX3P/yR"
++Content-Disposition: inline
++
++
++--K5LL32V+kDX3P/yR
++Content-Type: application/pgp-encrypted
++Content-Disposition: attachment
++
++Version: 1
++
++--K5LL32V+kDX3P/yR
++Content-Type: application/octet-stream
++Content-Disposition: attachment; filename="msg.asc"
++
++-----BEGIN PGP MESSAGE-----
++
++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
++=nHjy
++-----END PGP MESSAGE-----
++
++--K5LL32V+kDX3P/yR--
+diff --git a/spec/fixtures/mails/thunderbird-115.2.3-macos-xaddkey-attachment.eml b/spec/fixtures/mails/thunderbird-115.2.3-macos-xaddkey-attachment.eml
+new file mode 100644
+index 0000000..0fa470b
+--- /dev/null
++++ b/spec/fixtures/mails/thunderbird-115.2.3-macos-xaddkey-attachment.eml
+@@ -0,0 +1,134 @@
++Message-ID: <114ec1bc-0930-4501-94c0-a379cbf85101@schleuder.org>
++Date: Tue, 10 Oct 2023 15:00:09 +0200
++MIME-Version: 1.0
++User-Agent: Mozilla Thunderbird
++From: "paz@schleuder.org" <paz@schleuder.org>
++Subject: xak7
++To: schleuder@example.org
++Content-Language: en-US
++Content-Type: multipart/encrypted;
++ protocol="application/pgp-encrypted";
++ boundary="------------45oMbxrqyacC32ZCjsBuwZE1"
++
++This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)
++--------------45oMbxrqyacC32ZCjsBuwZE1
++Content-Type: application/pgp-encrypted
++Content-Description: PGP/MIME version identification
++
++Version: 1
++
++--------------45oMbxrqyacC32ZCjsBuwZE1
++Content-Type: application/octet-stream; name="encrypted.asc"
++Content-Description: OpenPGP encrypted message
++Content-Disposition: inline; filename="encrypted.asc"
++
++-----BEGIN PGP MESSAGE-----
++
++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
++0l54bNQDMlk7ZuK627Ko641ARyyUeo4=
++=fQ/g
++-----END PGP MESSAGE-----
++
++--------------45oMbxrqyacC32ZCjsBuwZE1--
+diff --git a/spec/fixtures/openpgpkey_421C19AF8B9C33B8B62D76EBDB2F7E271D773073.sec b/spec/fixtures/openpgpkey_421C19AF8B9C33B8B62D76EBDB2F7E271D773073.sec
+new file mode 100644
+index 0000000..cc5756b
+--- /dev/null
++++ b/spec/fixtures/openpgpkey_421C19AF8B9C33B8B62D76EBDB2F7E271D773073.sec
+@@ -0,0 +1,105 @@
++-----BEGIN PGP PRIVATE KEY BLOCK-----
++
++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
++m46seS0yc8ZGUOq12qfulPyqttemDfM=
++=v+pJ
++-----END PGP PRIVATE KEY BLOCK-----
+diff --git a/spec/schleuder/unit/keyword_handlers/key_management_spec.rb b/spec/schleuder/unit/keyword_handlers/key_management_spec.rb
+index 09b7409..3b56d17 100644
+--- a/spec/schleuder/unit/keyword_handlers/key_management_spec.rb
++++ b/spec/schleuder/unit/keyword_handlers/key_management_spec.rb
+@@ -56,10 +56,10 @@ describe Schleuder::KeywordHandlers::KeyManagement do
+     it 'imports a key from attached quoted-printable binary material' do
+       mail = Mail.new
+       mail.list = create(:list)
+-      mail.attachments['example_key_binary.txt'] = {
+-        :content_type => '"application/pgp-keys"; name="example_key_binary.txt"',
++      mail.attachments['example_key_binary.pgp'] = {
++        :mime_type => 'application/pgp-keys',
+         :content_transfer_encoding => 'quoted-printable',
+-        :content => File.open('spec/fixtures/example_key_binary.txt', 'rb', &:read)
++        :content => File.open('spec/fixtures/example_key_binary.pgp', 'rb', &:read)
+       }
+       mail.to_s
+ 
+@@ -70,11 +70,53 @@ describe Schleuder::KeywordHandlers::KeyManagement do
+       expect(mail.list.keys.size).to eql(list_keys.size + 1)
+     end
+ 
+-    it 'imports from attached quoted-printable ascii-armored key-material (as produced by Thunderbird)' do
++    it 'imports a key from attached binary material (without specified encoding)' do
++      mail = Mail.new
++      mail.list = create(:list)
++      mail.add_file('spec/fixtures/example_key_binary.pgp')
++
++      list_keys = mail.list.keys
++      output = KeywordHandlers::KeyManagement.new(mail: mail, arguments: []).add_key
++
++      expect(output).to eql("This key was newly added:\n0xC4D60F8833789C7CAA44496FD3FFA6613AB10ECE schleuder2@example.org 2016-12-12\n")
++      expect(mail.list.keys.size).to eql(list_keys.size + 1)
++    end
++
++    it 'imports a key from attached explicitly base64-encoded binary material' do
++      mail = Mail.new
++      mail.list = create(:list)
++      mail.attachments['example_key_binary.pgp'] = {
++        :mime_type => 'application/pgp-keys',
++        :content_transfer_encoding => 'base64',
++        :content => Base64.encode64(File.binread('spec/fixtures/example_key_binary.pgp'))
++      }
++
++      list_keys = mail.list.keys
++      output = KeywordHandlers::KeyManagement.new(mail: mail, arguments: []).add_key
++
++      expect(output).to eql("This key was newly added:\n0xC4D60F8833789C7CAA44496FD3FFA6613AB10ECE schleuder2@example.org 2016-12-12\n")
++      expect(mail.list.keys.size).to eql(list_keys.size + 1)
++    end
++
++    it 'imports from attached quoted-printable binary key-material (as produced by Mutt 2.0.5)' do
++      encrypted_email = Mail.read('spec/fixtures/mails/mutt-2.0.5-linux-xaddkey-attachment-binary.eml')
++      encrypted_email.list = create(:list, fingerprint: '421C19AF8B9C33B8B62D76EBDB2F7E271D773073')
++      encrypted_email.list.import_key(File.read('spec/fixtures/openpgpkey_421C19AF8B9C33B8B62D76EBDB2F7E271D773073.sec'))
++      mail = encrypted_email.setup
++      mail.to_s
++
++      list_keys = mail.list.keys
++      output = KeywordHandlers::KeyManagement.new(mail: mail, arguments: []).add_key
++
++      expect(output).to eql("This key was newly added:\n0xC4D60F8833789C7CAA44496FD3FFA6613AB10ECE schleuder2@example.org 2016-12-12\n")
++      expect(mail.list.keys.size).to eql(list_keys.size + 1)
++    end
++
++    it 'imports from attached quoted-printable ascii-armored key-material' do
+       mail = Mail.new
+       mail.list = create(:list)
+       mail.attachments['example_key.txt'] = {
+-        :content_type => '"application/pgp-keys"; name="example_key.txt"',
++        :mime_type => 'application/pgp-keys',
+         :content_transfer_encoding => 'quoted-printable',
+         :content => File.read('spec/fixtures/example_key.txt')
+       }
+@@ -87,6 +129,20 @@ describe Schleuder::KeywordHandlers::KeyManagement do
+       expect(mail.list.keys.size).to eql(list_keys.size + 1)
+     end
+ 
++    it 'imports from attached quoted-printable key-material (as produced by Thunderbird 115)' do
++      encrypted_email = Mail.read('spec/fixtures/mails/thunderbird-115.2.3-macos-xaddkey-attachment.eml')
++      encrypted_email.list = create(:list, fingerprint: '421C19AF8B9C33B8B62D76EBDB2F7E271D773073')
++      encrypted_email.list.import_key(File.read('spec/fixtures/openpgpkey_421C19AF8B9C33B8B62D76EBDB2F7E271D773073.sec'))
++      mail = encrypted_email.setup
++      mail.to_s
++
++      list_keys = mail.list.keys
++      output = KeywordHandlers::KeyManagement.new(mail: mail, arguments: []).add_key
++
++      expect(output).to eql("This key was newly added:\n0x769B651054DB697FEB26E408717574BD0A6591ED paz@schleuder.org 2018-10-16 [expired: 2024-03-16]\n")
++      expect(mail.list.keys.size).to eql(list_keys.size + 1)
++    end
++
+     it 'ignores body if an ascii-armored attachment is present' do
+       mail = Mail.new
+       mail.list = create(:list)
diff -Nru schleuder-4.0.3/debian/patches/0034-add-key-Fix-returning-error-message-if-email-contain.patch schleuder-4.0.3/debian/patches/0034-add-key-Fix-returning-error-message-if-email-contain.patch
--- schleuder-4.0.3/debian/patches/0034-add-key-Fix-returning-error-message-if-email-contain.patch	1970-01-01 00:00:00.000000000 +0000
+++ schleuder-4.0.3/debian/patches/0034-add-key-Fix-returning-error-message-if-email-contain.patch	2024-04-04 18:15:10.000000000 +0000
@@ -0,0 +1,45 @@
+From: paz <paz@schleuder.org>
+Date: Sat, 16 Dec 2023 17:10:28 +0100
+Subject: add-key: Fix returning error message if email contained no content
+
+---
+ lib/schleuder/keyword_handlers/key_management.rb            |  2 +-
+ spec/schleuder/unit/keyword_handlers/key_management_spec.rb | 13 +++++++++++++
+ 2 files changed, 14 insertions(+), 1 deletion(-)
+
+diff --git a/lib/schleuder/keyword_handlers/key_management.rb b/lib/schleuder/keyword_handlers/key_management.rb
+index d2e0081..b07c8a2 100644
+--- a/lib/schleuder/keyword_handlers/key_management.rb
++++ b/lib/schleuder/keyword_handlers/key_management.rb
+@@ -15,7 +15,7 @@ module Schleuder
+             import_key_from_body
+           else
+             @list.logger.debug 'Found no attachments and an empty body - sending error message'
+-            I18n.t('keyword_handlers.key_management.no_content_found')
++            return I18n.t('keyword_handlers.key_management.no_content_found')
+           end
+ 
+         import_stati = results.compact.collect(&:imports).flatten
+diff --git a/spec/schleuder/unit/keyword_handlers/key_management_spec.rb b/spec/schleuder/unit/keyword_handlers/key_management_spec.rb
+index 94c3190..3b56d17 100644
+--- a/spec/schleuder/unit/keyword_handlers/key_management_spec.rb
++++ b/spec/schleuder/unit/keyword_handlers/key_management_spec.rb
+@@ -257,5 +257,18 @@ describe Schleuder::KeywordHandlers::KeyManagement do
+       expect(output).to eql("Error: You did not send any arguments for the keyword 'DELETE-KEY'.\n\nOne is required, more are optional, e.g.:\nX-DELETE-KEY: 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3\n\nOr, to delete multiple keys at once:\nX-DELETE-KEY: 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 a-subscription@hostname\n\nThe matching keys will be deleted only if the argument matches them distinctly.\n")
+       expect(mail.list.keys.size).to eql(list_keys.size)
+     end
++
++    it 'returns a string as error message if input message has no content' do
++      mail = Mail.new
++      mail.list = create(:list)
++      mail.body = ''
++      mail.to_s
++
++      list_keys = mail.list.keys
++      output = KeywordHandlers::KeyManagement.new(mail: mail, arguments: []).add_key
++
++      expect(output).to eql('Your message did not contain any attachments nor text content. Therefore no key could be imported.')
++      expect(mail.list.keys.size).to eql(list_keys.size)
++    end
+   end
+ end
diff -Nru schleuder-4.0.3/debian/patches/0035-Check-email-address-for-receive_from_subscribed_emai.patch schleuder-4.0.3/debian/patches/0035-Check-email-address-for-receive_from_subscribed_emai.patch
--- schleuder-4.0.3/debian/patches/0035-Check-email-address-for-receive_from_subscribed_emai.patch	1970-01-01 00:00:00.000000000 +0000
+++ schleuder-4.0.3/debian/patches/0035-Check-email-address-for-receive_from_subscribed_emai.patch	2024-04-04 18:15:10.000000000 +0000
@@ -0,0 +1,96 @@
+From: paz <paz@schleuder.org>
+Date: Sun, 11 Sep 2022 21:04:06 +0200
+Subject: Check email address for receive_from_subscribed_emailaddresses_only
+ downcased
+
+Theoretically, local parts of email addresses should or could be
+case-sensitive, but in reality they aren't ever. Some people write their
+email addresses in mixed case, though, which makes this filter fail to
+recognize them.
+This change fixes that.
+It might still fail in case people used a mixed case email address for
+their subscription earlier than schleuder v4.0, but then they should
+change it, please. Our default is that all email addresses a lower case
+only.
+---
+ ..._receive_from_subscribed_emailaddresses_only.rb |  2 +-
+ spec/schleuder/unit/filters_spec.rb                | 55 ++++++++++++++++++++++
+ 2 files changed, 56 insertions(+), 1 deletion(-)
+
+diff --git a/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb b/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb
+index 6284230..7d38c2f 100644
+--- a/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb
++++ b/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb
+@@ -1,7 +1,7 @@
+ module Schleuder
+   module Filters
+     def self.receive_from_subscribed_emailaddresses_only(list, mail)
+-      if list.receive_from_subscribed_emailaddresses_only? && list.subscriptions.where(email: mail.from.first).blank?
++      if list.receive_from_subscribed_emailaddresses_only? && list.subscriptions.where(email: mail.from.first.downcase).blank?
+         list.logger.info 'Rejecting mail as not from subscribed address.'
+         return Errors::MessageSenderNotSubscribed.new
+       end
+diff --git a/spec/schleuder/unit/filters_spec.rb b/spec/schleuder/unit/filters_spec.rb
+index 2599682..9d7fe09 100644
+--- a/spec/schleuder/unit/filters_spec.rb
++++ b/spec/schleuder/unit/filters_spec.rb
+@@ -129,4 +129,59 @@ describe Schleuder::Filters do
+       expect(mail.dynamic_pseudoheaders).to be_blank
+     end
+   end
++
++  context '.receive_from_subscribed_emailaddresses_only' do
++    it 'does not reject a message with a non-subscribed address as From-header if list.receive_from_subscribed_emailaddresses_only is not set' do
++      list = create(:list, receive_from_subscribed_emailaddresses_only: false)
++      list.subscribe('admin@example.org', nil, true)
++      mail = Mail.new
++      mail.list = list
++      mail.to = list.email
++      mail.from = 'outside@example.org'
++
++      result = Schleuder::Filters.receive_from_subscribed_emailaddresses_only(list, mail)
++
++      expect(result).to eql(nil)
++    end
++
++    it 'rejects a message with a non-subscribed address as From-header if list.receive_from_subscribed_emailaddresses_only is set' do
++      list = create(:list, receive_from_subscribed_emailaddresses_only: true)
++      list.subscribe('admin@example.org', nil, true)
++      mail = Mail.new
++      mail.list = list
++      mail.to = list.email
++      mail.from = 'outside@example.org'
++
++      result = Schleuder::Filters.receive_from_subscribed_emailaddresses_only(list, mail)
++
++      expect(result).to be_a(Errors::MessageSenderNotSubscribed)
++    end
++
++    it 'does not reject a message with a subscribed address as From-header if list.receive_from_subscribed_emailaddresses_only is set' do
++      list = create(:list, receive_from_subscribed_emailaddresses_only: true)
++      list.subscribe('admin@example.org', nil, true)
++      mail = Mail.new
++      mail.list = list
++      mail.to = list.email
++      mail.from = list.subscriptions.first.email
++
++      result = Schleuder::Filters.receive_from_subscribed_emailaddresses_only(list, mail)
++
++      expect(result).to eql(nil)
++    end
++
++    it 'does not reject a message with a subscribed address as From-header with different letter case if list.receive_from_subscribed_emailaddresses_only is set' do
++      list = create(:list, receive_from_subscribed_emailaddresses_only: true)
++      list.subscribe('admin@example.org', nil, true)
++      mail = Mail.new
++      mail.list = list
++      mail.to = list.email
++      mail.from = 'AdMin@example.org'
++
++      result = Schleuder::Filters.receive_from_subscribed_emailaddresses_only(list, mail)
++
++      expect(result).to eql(nil)
++    end
++  end
++
+ end
diff -Nru schleuder-4.0.3/debian/patches/0036-Consider-From-when-finding-reply-address.patch schleuder-4.0.3/debian/patches/0036-Consider-From-when-finding-reply-address.patch
--- schleuder-4.0.3/debian/patches/0036-Consider-From-when-finding-reply-address.patch	1970-01-01 00:00:00.000000000 +0000
+++ schleuder-4.0.3/debian/patches/0036-Consider-From-when-finding-reply-address.patch	2024-04-04 18:15:10.000000000 +0000
@@ -0,0 +1,29 @@
+From: paz <paz@schleuder.org>
+Date: Tue, 30 Jan 2024 10:19:48 +0100
+Subject: Consider From: when finding reply address
+
+If the email-spec of the incoming From: header matches one of the
+possible reply addresses, use it. Previously people might receive a
+reply to a different address, which is a little confusing.
+---
+ lib/schleuder/mail/message.rb | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/lib/schleuder/mail/message.rb b/lib/schleuder/mail/message.rb
+index ecac52e..6e867fc 100644
+--- a/lib/schleuder/mail/message.rb
++++ b/lib/schleuder/mail/message.rb
+@@ -154,7 +154,12 @@ module Mail
+     def signer
+       @signer ||= begin
+         if signing_key.present?
+-          list.subscriptions.where(fingerprint: signing_key.fingerprint).first
++          # Look for a subscription that matches the sending address, in case
++          # there're multiple subscriptions for the same key. As a fallback use
++          # the first subscription found.
++          sender_email = self.from.to_s.downcase
++          subscriptions = list.subscriptions.where(fingerprint: signing_key.fingerprint)
++          subscriptions.where(email: sender_email).first || subscriptions.first
+         end
+       end
+     end
diff -Nru schleuder-4.0.3/debian/patches/0037-Stop-looking-for-keywords-if-email-starts-with-other.patch schleuder-4.0.3/debian/patches/0037-Stop-looking-for-keywords-if-email-starts-with-other.patch
--- schleuder-4.0.3/debian/patches/0037-Stop-looking-for-keywords-if-email-starts-with-other.patch	1970-01-01 00:00:00.000000000 +0000
+++ schleuder-4.0.3/debian/patches/0037-Stop-looking-for-keywords-if-email-starts-with-other.patch	2024-04-04 18:15:10.000000000 +0000
@@ -0,0 +1,236 @@
+From: paz <paz@schleuder.org>
+Date: Wed, 13 Mar 2024 14:53:13 +0100
+Subject: Stop looking for keywords if email starts with other content
+
+Previously we only stopped at a blank or "content" line if there'd been
+a keyword. Now we also stop if there wasn't, which is correct.
+---
+ lib/schleuder/mail/message.rb       |  21 +++---
+ spec/schleuder/unit/message_spec.rb | 129 ++++++++++++++++++++++++++++++++++--
+ 2 files changed, 131 insertions(+), 19 deletions(-)
+
+diff --git a/lib/schleuder/mail/message.rb b/lib/schleuder/mail/message.rb
+index 6e867fc..108df4f 100644
+--- a/lib/schleuder/mail/message.rb
++++ b/lib/schleuder/mail/message.rb
+@@ -447,30 +447,25 @@ module Mail
+     def extract_keywords(content_lines)
+       keywords = []
+       in_keyword_block = false
+-      found_blank_line = false
+       content_lines.each_with_index do |line, i|
+-        if match = line.match(/^x-([^:\s]*)[:\s]*(.*)/i)
++        if line.blank?
++          # Swallow the line: before the actual content or keywords block begins we want to drop blank lines.
++          content_lines[i] = nil
++          # Stop interpreting the following line as argument to the previous keyword.
++          in_keyword_block = false
++        elsif match = line.match(/^x-([^:\s]*)[:\s]*(.*)/i)
+           keyword = match[1].strip.downcase
+           arguments = match[2].to_s.strip.downcase.split(/[,; ]{1,}/)
+           keywords << [keyword, arguments]
+           in_keyword_block = true
+-
+           # Set this line to nil to have it stripped from the message.
+           content_lines[i] = nil
+-        elsif line.blank? && keywords.any?
+-          # Look for blank lines after the first keyword had been found.
+-          # These might mark the end of the keywords-block — unless more keywords follow.
+-          found_blank_line = true
+-          # Swallow the line: before the actual content begins we want to drop blank lines.
+-          content_lines[i] = nil
+-          # Stop interpreting the following line as argument to the previous keyword.
+-          in_keyword_block = false
+         elsif in_keyword_block == true
+           # Interpret line as arguments to the previous keyword.
+           keywords[-1][-1] += line.downcase.strip.split(/[,; ]{1,}/)
+           content_lines[i] = nil
+-        elsif found_blank_line
+-          # Any line that isn't blank and does not start with "x-" stops the keyword parsing.
++        else
++          # Any other line stops the keyword parsing.
+           break
+         end
+       end
+diff --git a/spec/schleuder/unit/message_spec.rb b/spec/schleuder/unit/message_spec.rb
+index d0d3d52..11bb812 100644
+--- a/spec/schleuder/unit/message_spec.rb
++++ b/spec/schleuder/unit/message_spec.rb
+@@ -228,7 +228,13 @@ describe Mail::Message do
+ 
+   context '.keywords' do
+     it 'stops looking for keywords when a blank line that is not followed by another keyword is met' do
+-      string = "x-something: bla\nx-somethingelse: ok\n\nsomething\nx-toolate: tralafiti\n"
++      string = <<~EOS
++        x-something: bla
++        x-somethingelse: ok
++      
++        something
++        x-toolate: tralafiti
++      EOS
+       m = Mail.new
+       m.body = string
+       m.to_s
+@@ -240,7 +246,14 @@ describe Mail::Message do
+     end
+ 
+     it 'reads multiple lines as keyword arguments' do
+-      string = "x-something: first\nsecond\nthird\nx-somethingelse: ok\n\ntralafiti\n"
++      string = <<~EOS
++        x-something: first
++        second
++        third
++        x-somethingelse: ok
++
++        tralafiti
++      EOS
+       m = Mail.new
+       m.body = string
+       m.to_s
+@@ -252,7 +265,13 @@ describe Mail::Message do
+     end
+ 
+     it 'takes the whole rest of the body as keyword argument if blank lines are present' do
+-      string = "x-something: first\nsecond\nthird\nok\ntralafiti\n"
++      string = <<~EOS
++        x-something: first
++        second
++        third
++        ok
++        tralafiti
++      EOS
+       m = Mail.new
+       m.body = string
+       m.to_s
+@@ -264,7 +283,14 @@ describe Mail::Message do
+     end
+ 
+     it 'drops empty lines in keyword arguments parsing' do
+-      string = "x-something: first\nthird\n\nx-somethingelse: ok\n\ntralafiti\n"
++      string = <<~EOS
++        x-something: first
++        third
++
++        x-somethingelse: ok
++
++        tralafiti
++      EOS
+       m = Mail.new
+       m.body = string
+       m.to_s
+@@ -276,7 +302,15 @@ describe Mail::Message do
+     end
+ 
+     it 'drops multiple empty lines between keywords and content' do
+-      string = "x-something: first\nthird\nx-somethingelse: ok\n\n\n\ntralafiti\n"
++      string = <<~EOS
++        x-something: first
++        third
++        x-somethingelse: ok
++
++
++
++        tralafiti
++      EOS
+       m = Mail.new
+       m.body = string
+       m.to_s
+@@ -288,7 +322,14 @@ describe Mail::Message do
+     end
+ 
+     it 'splits lines into words and downcases them in keyword arguments' do
+-      string = "x-something: first\nSECOND     end\nthird\nx-somethingelse: ok\n\ntralafiti\n"
++      string = <<~EOS
++        x-something: first
++        SECOND     end
++        third
++        x-somethingelse: ok
++
++        tralafiti
++      EOS
+       m = Mail.new
+       m.body = string
+       m.to_s
+@@ -298,6 +339,82 @@ describe Mail::Message do
+       expect(keywords).to eql([['something', ['first', 'second', 'end', 'third']], ['somethingelse', ['ok']]])
+       expect(m.body.to_s).to eql("tralafiti\n")
+     end
++
++    it 'ignores empty lines before keywords' do
++      string = <<~EOS
++
++
++        x-something: first
++        third
++        x-somethingelse: ok
++
++
++
++        tralafiti
++      EOS
++      m = Mail.new
++      m.body = string
++      m.to_s
++
++      keywords = m.keywords
++
++      expect(keywords).to eql([['something', ['first', 'third']], ['somethingelse', ['ok']]])
++      expect(m.body.to_s).to eql("tralafiti\n")
++    end
++
++    it 'stops looking for keywords when the first line is already email content' do
++      string = <<~EOS
++        Please ignore this message, i am trying to debug a possible schleuder
++        bug.
++
++        Here is a schleuder keyword command in the middle of the message text:
++
++        X-LIST-NAME: foo@example.org
++        X-ATTACH-LISTKEY:
++        -----BEGIN PGP PUBLIC KEY BLOCK-----
++
++        nothing to see here.
++
++        And here is some followup text.
++
++      EOS
++      m = Mail.new
++      m.body = string
++      m.to_s
++
++      keywords = m.keywords
++
++      expect(keywords).to eql([])
++      expect(m.body.to_s).to include("X-LIST-NAME: foo@example.org\nX-ATTACH-LISTKEY:\n-----BEGIN PGP PUBLIC KEY BLOCK-----")
++    end
++
++    it 'stops looking for keywords when already the first line is blank followed by email content' do
++      string = <<~EOS
++
++
++        Please ignore this message, i am trying to debug a possible schleuder
++        bug.
++
++        Here is a schleuder keyword command in the middle of the message text:
++
++        X-LIST-NAME: foo@example.org
++        X-ATTACH-LISTKEY:
++        -----BEGIN PGP PUBLIC KEY BLOCK-----
++
++        nothing to see here.
++
++        And here is some followup text.
++
++      EOS
++      m = Mail.new
++      m.body = string
++      m.to_s
++
++      keywords = m.keywords
++
++      expect(keywords).to eql([])
++      expect(m.body.to_s).to include("X-LIST-NAME: foo@example.org\nX-ATTACH-LISTKEY:\n-----BEGIN PGP PUBLIC KEY BLOCK-----")
++    end
+   end
+ 
+   it 'verifies an encapsulated (signed-then-encrypted) message' do
diff -Nru schleuder-4.0.3/debian/patches/series schleuder-4.0.3/debian/patches/series
--- schleuder-4.0.3/debian/patches/series	2022-12-26 18:49:28.000000000 +0000
+++ schleuder-4.0.3/debian/patches/series	2024-04-04 18:15:10.000000000 +0000
@@ -1,3 +1,9 @@
+0037-Stop-looking-for-keywords-if-email-starts-with-other.patch
+0036-Consider-From-when-finding-reply-address.patch
+0035-Check-email-address-for-receive_from_subscribed_emai.patch
+0034-add-key-Fix-returning-error-message-if-email-contain.patch
+0033-Fix-importing-keys-from-attachments-like-e.g.-Thunde.patch
+0032-Improve-parsing-arguments-of-x-subscribe.patch
 0029-spec-ensure-daemon-calls-are-ruby-version-aware.patch
 0028-spec-relax-gpg-keyserver-refresh-failed-expectation.patch
 0027-spec-force-tz-and-locale.patch

Reply to: