schleuder 4.0.2 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -9
  3. data/db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb +1 -1
  4. data/db/migrate/20211106112020_change_boolean_values_to_integers.rb +46 -0
  5. data/db/migrate/20211107151309_add_limits_to_string_columns.rb +28 -0
  6. data/db/migrate/20220910170110_add_key_auto_import_from_email.rb +11 -0
  7. data/db/schema.rb +16 -16
  8. data/etc/list-defaults.yml +16 -0
  9. data/etc/schleuder.yml +29 -11
  10. data/lib/schleuder/cli.rb +15 -2
  11. data/lib/schleuder/conf.rb +23 -3
  12. data/lib/schleuder/email_key_importer.rb +91 -0
  13. data/lib/schleuder/filters/post_decryption/35_key_auto_import_from_attachments.rb +21 -0
  14. data/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb +1 -1
  15. data/lib/schleuder/filters/post_decryption/90_strip_html_from_alternative_if_keywords_present.rb +36 -4
  16. data/lib/schleuder/filters/pre_decryption/60_key_auto_import_from_autocrypt_header.rb +9 -0
  17. data/lib/schleuder/filters_runner.rb +1 -30
  18. data/lib/schleuder/gpgme/ctx.rb +34 -93
  19. data/lib/schleuder/gpgme/key.rb +1 -1
  20. data/lib/schleuder/gpgme/key_extractor.rb +30 -0
  21. data/lib/schleuder/http.rb +56 -0
  22. data/lib/schleuder/key_fetcher.rb +89 -0
  23. data/lib/schleuder/keyword_handlers/key_management.rb +2 -2
  24. data/lib/schleuder/keyword_handlers/subscription_management.rb +19 -3
  25. data/lib/schleuder/list.rb +26 -10
  26. data/lib/schleuder/list_builder.rb +1 -1
  27. data/lib/schleuder/logger.rb +1 -1
  28. data/lib/schleuder/mail/gpg/decrypted_part.rb +20 -0
  29. data/lib/schleuder/mail/gpg/delivery_handler.rb +38 -0
  30. data/lib/schleuder/mail/gpg/encrypted_part.rb +29 -5
  31. data/lib/schleuder/mail/gpg/gpgme_ext.rb +8 -0
  32. data/lib/schleuder/mail/gpg/gpgme_helper.rb +155 -0
  33. data/lib/schleuder/mail/gpg/inline_decrypted_message.rb +82 -0
  34. data/lib/schleuder/mail/gpg/inline_signed_message.rb +73 -0
  35. data/lib/schleuder/mail/gpg/mime_signed_message.rb +28 -0
  36. data/lib/schleuder/mail/gpg/missing_keys_error.rb +6 -0
  37. data/lib/schleuder/mail/gpg/sign_part.rb +19 -9
  38. data/lib/schleuder/mail/gpg/signed_part.rb +37 -0
  39. data/lib/schleuder/mail/gpg/verified_part.rb +10 -0
  40. data/lib/schleuder/mail/gpg/verify_result_attribute.rb +32 -0
  41. data/lib/schleuder/mail/gpg/version_part.rb +22 -0
  42. data/lib/schleuder/mail/gpg.rb +236 -7
  43. data/lib/schleuder/mail/message.rb +98 -14
  44. data/lib/schleuder/runner.rb +40 -10
  45. data/lib/schleuder/sks_client.rb +18 -0
  46. data/lib/schleuder/version.rb +1 -1
  47. data/lib/schleuder/vks_client.rb +24 -0
  48. data/lib/schleuder-api-daemon/routes/key.rb +22 -1
  49. data/lib/schleuder.rb +11 -7
  50. data/locales/de.yml +38 -19
  51. data/locales/en.yml +22 -3
  52. metadata +58 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f8275838177195ab78283d9c737de9cbd2d39e816c9f41db2e31d785d787dd8
4
- data.tar.gz: 464e220965c5d0ee43d300a1f988e7a27edd9344d751bae9e06a9a02fc5c2a43
3
+ metadata.gz: 4148fa7b166ff22d2af8980a549f08e7bc659080df8f9579d15a190cbb046033
4
+ data.tar.gz: edb0f987169bdb5585f9350057e5f7114e377d13cec9e9f413a6da24a4e3cd46
5
5
  SHA512:
6
- metadata.gz: d6ba6aab5c19ae0f8f74a0fe44e84e5a10f4adf434f55454a1d7ca49943557fbad5db23ff06fee1a3e0efa5a248034f8a6a17dcbeb91864bf04ec68f8cd14d1f
7
- data.tar.gz: bd361a32b63c6bcfa146e1369a45fa54e9c3c94d29baaefbbff01a1629b468c896a34c17cd6ff957357eaf67611a5b09b5c2b2d9a023130430a86f29892b19cb
6
+ metadata.gz: 8d86957d1b3b3e6929290f273b2fd39a65c012aa19fba80231aba12c413024c19dd513cf4a61dce7cb93af1996ade5d95de8230ef0a7e855fedaa741aa1f10a4
7
+ data.tar.gz: 00605d42c733d792fffd50652e1f7c7c8b8e98066006bf49eff25bf7d6c7523419229d18537e07f2735ca8b2fe4b243b043966a13079c8d8fce7d274fd2629c1
data/README.md CHANGED
@@ -9,12 +9,13 @@ For more details see <https://schleuder.org/docs/>.
9
9
 
10
10
  Requirements
11
11
  ------------
12
- * ruby >=2.5
12
+ * ruby >=2.7
13
13
  * gnupg >=2.2
14
14
  * gpgme
15
15
  * sqlite3
16
16
  * openssl
17
17
  * icu
18
+ * libcurl
18
19
 
19
20
  *If you use Debian buster, CentOS 7 or Archlinux, please have a look at the [installation docs](https://schleuder.org/schleuder/docs/server-admins.html#installation). We do provide packages for those platforms, which simplify the installation a lot.*
20
21
 
@@ -36,15 +37,15 @@ Additionally these **rubygems** are required (will be installed automatically un
36
37
  Installing Schleuder
37
38
  ------------
38
39
 
39
- 1. Download [the gem](https://schleuder.org/download/schleuder-4.0.2.gem) and [the OpenPGP-signature](https://schleuder.org/download/schleuder-4.0.2.gem.sig) and verify:
40
+ 1. Download [the gem](https://schleuder.org/download/schleuder-5.0.0.gem) and [the OpenPGP-signature](https://schleuder.org/download/schleuder-5.0.0.gem.sig) and verify:
40
41
  ```
41
42
  gpg --recv-key 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3
42
- gpg --verify schleuder-4.0.2.gem.sig
43
+ gpg --verify schleuder-5.0.0.gem.sig
43
44
  ```
44
45
 
45
46
  2. If all went well install the gem:
46
47
  ```
47
- gem install schleuder-4.0.2.gem
48
+ gem install schleuder-5.0.0.gem
48
49
  ```
49
50
 
50
51
  3. Set up schleuder:
@@ -101,10 +102,6 @@ To execute the test suite run:
101
102
 
102
103
  bundle exec rspec
103
104
 
104
- Please note: Some of the specs use 'pgrep'. On systems that base on Debian 10 ("buster") install it via
105
-
106
- apt-get install procps
107
-
108
105
  We are working on extendig the test coverage.
109
106
 
110
107
  Contributing
@@ -134,4 +131,4 @@ GNU GPL 3.0. Please see [LICENSE.txt](LICENSE.txt).
134
131
  Alternative Download
135
132
  --------------------
136
133
 
137
- Alternatively to the gem-files you can download the latest release as [a tarball](https://schleuder.org/download/schleuder-4.0.2.tar.gz) and [its OpenPGP-signature](https://schleuder.org/download/schleuder-4.0.2.tar.gz.sig).
134
+ Alternatively to the gem-files you can download the latest release as [a tarball](https://schleuder.org/download/schleuder-5.0.0.tar.gz) and [its OpenPGP-signature](https://schleuder.org/download/schleuder-5.0.0.tar.gz.sig).
@@ -24,7 +24,7 @@ class AddSigEncToHeadersToMetaDefaults < ActiveRecord::Migration[4.2]
24
24
  # complexities of the actual class.
25
25
  Class.new(ActiveRecord::Base) do
26
26
  self.table_name = 'lists'
27
- self.serialize :headers_to_meta, JSON
27
+ self.serialize :headers_to_meta, coder: JSON
28
28
  end
29
29
  end
30
30
  end
@@ -0,0 +1,46 @@
1
+ # Since ActiveRecord >= 6.0, the SQLite3 connection adapter relies on boolean
2
+ # serialization to use 1 and 0, but does not natively recognize 't' and 'f' as
3
+ # booleans were previously serialized.
4
+ #
5
+ # Accordingly, this migration handles conversion of both column defaults and
6
+ # stored data provided by a user.
7
+ #
8
+ # In contrast to other migrations, only a 'forward' method is provided, a
9
+ # mechanism to 'reverse' is not. Given the nature of this migration, the later
10
+ # is not really required.
11
+ #
12
+ # Unfortunately, we missed this breaking change when bumping ActiveRecord to >=
13
+ # 6.0 in Schleuder version 4.0. This caused quite some work upstream, but also
14
+ # in downstream environments and, last but not least, at the side of users.
15
+ #
16
+ # We should extend our CI to explicitly test, and ensure things work as
17
+ # expected, if running a Schleuder setup in real world. As of now, we don't
18
+ # ensure data provided by a user in Schleuder version x still works after
19
+ # upgrading to version y.
20
+
21
+ class ChangeBooleanValuesToIntegers < ActiveRecord::Migration[6.0]
22
+ class Lists < ActiveRecord::Base
23
+ end
24
+
25
+ class Subscriptions < ActiveRecord::Base
26
+ end
27
+
28
+ def up
29
+ [Lists, Subscriptions].each do |table|
30
+ unless table.connection.is_a?(ActiveRecord::ConnectionAdapters::SQLite3Adapter)
31
+ return
32
+ end
33
+
34
+ bool_columns_defaults = table.columns.select { |column| column.type == :boolean }.map{ |column| [column.name, column.default] }
35
+
36
+ bool_columns_defaults.each do |column_name, column_default|
37
+ column_bool = ActiveRecord::Type::Boolean.new.deserialize(column_default)
38
+
39
+ change_column_default :"#{table.table_name}", :"#{column_name}", column_bool
40
+
41
+ table.where("#{column_name} = 'f'").update_all("#{column_name}": 0)
42
+ table.where("#{column_name} = 't'").update_all("#{column_name}": 1)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,28 @@
1
+ # It seems, the change to ActiveRecord >= 6.0 makes this necessary.
2
+ # Without doing this, the auto-generated database schema file would drop
3
+ # these limits, if running migrations.
4
+ #
5
+ # In contrast to other migrations, only a 'forward' method is provided, a
6
+ # mechanism to 'reverse' is not. Given the nature of this migration, the later
7
+ # is not really required.
8
+ #
9
+ # This has been an upstream issue for quite some time. For details, see
10
+ # https://github.com/rails/rails/issues/19001.
11
+
12
+ class AddLimitsToStringColumns < ActiveRecord::Migration[6.0]
13
+ class Lists < ActiveRecord::Base
14
+ end
15
+
16
+ class Subscriptions < ActiveRecord::Base
17
+ end
18
+
19
+ def up
20
+ [Lists, Subscriptions].each do |table|
21
+ string_columns = table.columns.select { |column| column.type == :string }.map(&:name)
22
+
23
+ string_columns.each do |column_name|
24
+ change_column :"#{table.table_name}", :"#{column_name}", :string, :limit => 255
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ class AddKeyAutoImportFromEmail < ActiveRecord::Migration[7.1]
2
+ def up
3
+ if ! column_exists?(:lists, :key_auto_import_from_email)
4
+ add_column :lists, :key_auto_import_from_email, :boolean, default: false
5
+ end
6
+ end
7
+
8
+ def down
9
+ remove_column(:lists, :key_auto_import_from_email)
10
+ end
11
+ end
data/db/schema.rb CHANGED
@@ -10,18 +10,17 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(version: 2020_01_18_170110) do
14
-
13
+ ActiveRecord::Schema[7.1].define(version: 2022_09_10_170110) do
15
14
  create_table "lists", force: :cascade do |t|
16
- t.datetime "created_at"
17
- t.datetime "updated_at"
18
- t.string "email"
19
- t.string "fingerprint"
20
- t.string "log_level", default: "warn"
21
- t.string "subject_prefix", default: ""
22
- t.string "subject_prefix_in", default: ""
23
- t.string "subject_prefix_out", default: ""
24
- t.string "openpgp_header_preference", default: "signencrypt"
15
+ t.datetime "created_at", precision: nil
16
+ t.datetime "updated_at", precision: nil
17
+ t.string "email", limit: 255
18
+ t.string "fingerprint", limit: 255
19
+ t.string "log_level", limit: 255, default: "warn"
20
+ t.string "subject_prefix", limit: 255, default: ""
21
+ t.string "subject_prefix_in", limit: 255, default: ""
22
+ t.string "subject_prefix_out", limit: 255, default: ""
23
+ t.string "openpgp_header_preference", limit: 255, default: "signencrypt"
25
24
  t.text "public_footer", default: ""
26
25
  t.text "headers_to_meta", default: "[\"from\", \"to\", \"cc\", \"date\", \"sig\", \"enc\"]"
27
26
  t.text "bounces_drop_on_headers", default: "{\"x-spam-flag\":\"yes\"}"
@@ -39,7 +38,7 @@ ActiveRecord::Schema.define(version: 2020_01_18_170110) do
39
38
  t.boolean "include_list_headers", default: true
40
39
  t.boolean "include_openpgp_header", default: true
41
40
  t.integer "max_message_size_kb", default: 10240
42
- t.string "language", default: "en"
41
+ t.string "language", limit: 255, default: "en"
43
42
  t.boolean "forward_all_incoming_to_admins", default: false
44
43
  t.integer "logfiles_to_keep", default: 2
45
44
  t.text "internal_footer", default: ""
@@ -47,16 +46,17 @@ ActiveRecord::Schema.define(version: 2020_01_18_170110) do
47
46
  t.boolean "include_autocrypt_header", default: true
48
47
  t.boolean "set_reply_to_to_sender", default: false
49
48
  t.boolean "munge_from", default: false
49
+ t.boolean "key_auto_import_from_email", default: false
50
50
  end
51
51
 
52
52
  create_table "subscriptions", force: :cascade do |t|
53
53
  t.integer "list_id"
54
- t.string "email"
55
- t.string "fingerprint"
54
+ t.string "email", limit: 255
55
+ t.string "fingerprint", limit: 255
56
56
  t.boolean "admin", default: false
57
57
  t.boolean "delivery_enabled", default: true
58
- t.datetime "created_at"
59
- t.datetime "updated_at"
58
+ t.datetime "created_at", precision: nil
59
+ t.datetime "updated_at", precision: nil
60
60
  t.index ["email", "list_id"], name: "index_subscriptions_on_email_and_list_id", unique: true
61
61
  t.index ["list_id"], name: "index_subscriptions_on_list_id"
62
62
  end
@@ -149,3 +149,19 @@ set_reply_to_to_sender: false
149
149
  # This option can enabled for improved usability since this affect
150
150
  # mail client's displayed name.
151
151
  munge_from: false
152
+
153
+ # If enabled, parse Autocrypt-header and look for attached keys in incoming emails.
154
+ # For each found key (a), check if it contains a UID matching the From-header. In
155
+ # case it does not, skip this key and handle the next, if any.
156
+ # Lookup fingerprint of key (a) and check if it's already present in the keyring.
157
+ # If present, import the key (a).
158
+ # Otherwise, check if a key, which contains a UID matching the From-header, is present
159
+ # in the keyring.
160
+ # If not, import the key (a).
161
+ # Before doing any import, ensure via a filter so that only the relevant UID is
162
+ # kept.
163
+ # Please be aware that this might result in a newly imported key that matches a
164
+ # subscriber's email address, which didn't originate from the subscriber (in case
165
+ # of forged From-header). We consider this not a serious problem because that key
166
+ # will not be used for that subscriber's emails unless it is assigned manually.
167
+ key_auto_import_from_email: false
data/etc/schleuder.yml CHANGED
@@ -21,17 +21,28 @@ filters_dir: /usr/local/lib/schleuder/filters
21
21
  # How verbose should Schleuder log to syslog? (list-specific messages are written to the list's log-file).
22
22
  log_level: warn
23
23
 
24
- # Which keyserver to refresh keys from (used by `schleuder refresh_keys`, meant
25
- # to be run from cron or systemd weekly).
26
- # If you have gnupg 2.1, we strongly suggest to use a hkps-keyserver:
27
- #keyserver: hkps://hkps.pool.sks-keyservers.net
28
- # If you have gnupg 2.1 and TOR running locally, use a onion-keyserver:
29
- #keyserver: hkp://jirk5u4osbsr34t5.onion
30
- # If you have an OS-wide defined keyserver, specify a blank value to have that
31
- # one used:
32
- #keyserver:
33
- # The default works for all supported versions of gnupg:
34
- keyserver: pool.sks-keyservers.net
24
+ # Which verifying keyserver to fetch keys from?
25
+ # This server must support the VKS API.
26
+ # To disable lookup via this type of keyserver, set this to a blank value.
27
+ # Note: This must include the procotol scheme (e.g. "https://").
28
+ vks_keyserver: https://keys.openpgp.org
29
+
30
+ # Which traditional keyserver to fetch keys from?
31
+ # This server must support the SKS API.
32
+ # Please consider that public SKS servers don't verify any upload, the keys
33
+ # they deliver should not be trusted without additional verification. This is
34
+ # important if you e.g. allow keys to be fetched automatically, or by email
35
+ # address.
36
+ # This keyserver is queried only if the vks_keyserver didn't have a key.
37
+ # Note: This must include the procotol scheme (e.g. "https://").
38
+ # Beware: Only specify https here if the server does use a commonly accepted
39
+ # TLS certificate. Most servers of the SKS-pool do not!
40
+ sks_keyserver:
41
+
42
+ # Should Schleuder use a proxy for HTTP requests (to fetch OpenPGP keys)?
43
+ # To route HTTP requests via Tor set up a local Tor daemon and enter e.g.
44
+ # socks5h://127.0.0.1:9050 (for a typical Tor service).
45
+ #http_proxy:
35
46
 
36
47
  # Who is maintaining the overall schleuder installation and should be
37
48
  # notified about severe problems with lists.
@@ -40,6 +51,13 @@ keyserver: pool.sks-keyservers.net
40
51
  # Is also used as an envelope sender of admin notifications.
41
52
  superadmin: root@localhost
42
53
 
54
+ # Umask with which to create directories and files. The default (0077) lets
55
+ # only the owners read them, no one else.
56
+ # Only change this in special cases and if you know what you are doing!
57
+ # (Be careful to retain the value type, it must not be quoted and it must start
58
+ # with a zero!)
59
+ umask: 0077
60
+
43
61
  # For these options see documentation for ActionMailer::smtp_settings, e.g. <http://api.rubyonrails.org/classes/ActionMailer/Base.html>.
44
62
  smtp_settings:
45
63
  address: localhost
data/lib/schleuder/cli.rb CHANGED
@@ -109,7 +109,8 @@ module Schleuder
109
109
  end
110
110
  end
111
111
 
112
- if ActiveRecord::SchemaMigration.table_exists?
112
+ # If the table for lists exists we assume the DB has been initialized before.
113
+ if List.table_exists?
113
114
  say shellexec("cd #{root_dir} && rake db:migrate")
114
115
  else
115
116
  say shellexec("cd #{root_dir} && rake db:init")
@@ -122,6 +123,18 @@ module Schleuder
122
123
  Schleuder::Cert.new.generate
123
124
  end
124
125
 
126
+ if Conf.umask == 0077
127
+ # The umask is set to the (new) default, let's have a look at the list-dirs.
128
+ list_dirs = Dir.glob("#{Conf.lists_dir}/*")
129
+ list_dirs.each do |list_dir|
130
+ perm_bits = File.stat(list_dir).mode.to_s(8)[-3..]
131
+ if perm_bits != '700'
132
+ say "WARNING: The directory '#{list_dir}' has permissions that do not match the umask as set in schleuder's config. You should probably fix that by running `chmod -R g-rwx #{list_dir}`."
133
+ end
134
+ end
135
+ end
136
+
137
+
125
138
  say "Schleuder has been set up. You can now create a new list using `schleuder-cli`.\nWe hope you enjoy!"
126
139
  permission_notice
127
140
  rescue => exc
@@ -132,7 +145,7 @@ module Schleuder
132
145
  def shellexec(cmd)
133
146
  result = `#{cmd} 2>&1`
134
147
  if $?.exitstatus > 0
135
- exit $?.exitstatus
148
+ fatal result, $?.exitstatus
136
149
  end
137
150
  result
138
151
  end
@@ -10,12 +10,15 @@ module Schleuder
10
10
 
11
11
  DEFAULTS = {
12
12
  'lists_dir' => '/var/lib/schleuder/lists',
13
+ 'umask' => 0077,
13
14
  'listlogs_dir' => '/var/lib/schleuder/lists',
14
15
  'keyword_handlers_dir' => '/usr/local/lib/schleuder/keyword_handlers',
15
16
  'filters_dir' => '/usr/local/lib/schleuder/filters',
16
17
  'log_level' => 'warn',
17
18
  'superadmin' => 'root@localhost',
18
- 'keyserver' => 'hkp://pool.sks-keyservers.net',
19
+ 'vks_keyserver' => 'https://keys.openpgp.org',
20
+ 'sks_keyserver' => 'http://pool.sks-keyservers.net',
21
+ 'http_proxy' => '',
19
22
  'smtp_settings' => {
20
23
  'address' => 'localhost',
21
24
  'port' => 25,
@@ -48,10 +51,19 @@ module Schleuder
48
51
  @config ||= load_config(ENV['SCHLEUDER_CONFIG'])
49
52
  end
50
53
 
54
+ def reload!
55
+ @config = nil
56
+ config
57
+ end
58
+
51
59
  def self.lists_dir
52
60
  instance.config['lists_dir']
53
61
  end
54
62
 
63
+ def self.umask
64
+ instance.config['umask']
65
+ end
66
+
55
67
  def self.listlogs_dir
56
68
  instance.config['listlogs_dir']
57
69
  end
@@ -115,8 +127,16 @@ module Schleuder
115
127
  settings
116
128
  end
117
129
 
118
- def self.keyserver
119
- instance.config['keyserver']
130
+ def self.vks_keyserver
131
+ instance.config['vks_keyserver']
132
+ end
133
+
134
+ def self.sks_keyserver
135
+ instance.config['sks_keyserver']
136
+ end
137
+
138
+ def self.http_proxy
139
+ instance.config['http_proxy']
120
140
  end
121
141
 
122
142
  private
@@ -0,0 +1,91 @@
1
+ module Schleuder
2
+ class EmailKeyImporter
3
+ class << self
4
+ def import_from_attachments(list, mail)
5
+ # Shouldn't happen, but who knows...
6
+ return if ! mail.from.first.match(Conf::EMAIL_REGEXP)
7
+ mail.attachments.map do |part|
8
+ if part.content_type == 'application/pgp-keys'
9
+ filter_and_maybe_import_keys(mail, part.body.decoded)
10
+ end
11
+ end.compact
12
+ end
13
+
14
+ def import_from_autocrypt_header(list, mail)
15
+ # Shouldn't happen, but who knows...
16
+ return if ! mail.from.first.match(Conf::EMAIL_REGEXP)
17
+ return if mail.header['Autocrypt'].blank?
18
+ keydata_base64 = mail.header['Autocrypt'].to_s.split('keydata=', 2)[1]
19
+ return if keydata_base64.blank?
20
+ keydata = Base64.decode64(keydata_base64)
21
+ return if keydata.blank?
22
+ filter_and_maybe_import_keys(mail, keydata)
23
+ end
24
+
25
+ def filter_and_maybe_import_keys(mail, keydata)
26
+ extracted_keys = GPGME::KeyExtractor.extract_by_email_address(mail.from.first, keydata)
27
+ extracted_keys.map do |fingerprint, filtered_keydata|
28
+ maybe_import_key(mail, fingerprint, filtered_keydata)
29
+ end.compact
30
+ end
31
+
32
+ def maybe_import_key(mail, fingerprint, filtered_keydata)
33
+ if mail.list.keys(fingerprint).size == 1
34
+ return update_key(mail, filtered_keydata)
35
+ end
36
+
37
+ if mail.list.keys(mail.from.first).size > 0
38
+ mail.add_pseudoheader('Note', I18n.t('email_key_importer.key_already_present'))
39
+ return
40
+ end
41
+ add_key(mail, filtered_keydata)
42
+ end
43
+
44
+ def add_key(mail, keydata)
45
+ fingerprint, import_states = import_to_list(mail, keydata)
46
+ return if ! fingerprint
47
+ if ! import_states.include?(I18n.t('import_states.new_key'))
48
+ mail.list.logger.error "Importing key failed! Fingerprint: #{fingerprint.inspect} -- Import-states: #{import_states.inspect}"
49
+ mail.add_pseudoheader('Note',
50
+ I18n.t('email_key_importer.import_error',
51
+ fingerprint: fingerprint.inspect,
52
+ import_states: import_states.inspect))
53
+ return
54
+ end
55
+ key = mail.list.keys(fingerprint).first
56
+ mail.add_pseudoheader('Note', I18n.t('email_key_importer.key_added',
57
+ key_summary: key.summary))
58
+ fingerprint
59
+ end
60
+
61
+ def update_key(mail, keydata)
62
+ fingerprint, import_states = import_to_list(mail, keydata)
63
+ return if ! fingerprint
64
+ key = mail.list.keys(fingerprint).first
65
+ if import_states == ['unchanged']
66
+ mail.add_pseudoheader('Note',
67
+ I18n.t('email_key_importer.key_unchanged',
68
+ key_summary: key.summary))
69
+ return
70
+ end
71
+
72
+ key = mail.list.keys(fingerprint).first
73
+ mail.add_pseudoheader('Note', I18n.t('email_key_importer.key_updated',
74
+ key_summary: key.summary))
75
+ fingerprint
76
+ end
77
+
78
+ def import_to_list(mail, keydata)
79
+ result = mail.list.gpg.import_filtered(keydata)
80
+ # At this point we expect the keydata to only contain one key, and thus
81
+ # only one key and value in the result.
82
+ if ! result.is_a?(Hash) || result.keys.size != 1 || ! result.values.first.is_a?(Array)
83
+ mail.list.logger.error "Unexpected result when importing key from temporary keyring => #{result.inspect}"
84
+ mail.add_pseudoheader('Note', I18n.t('email_key_importer.technical_error'))
85
+ return false
86
+ end
87
+ [result.keys.first, result.values.first]
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,21 @@
1
+ module Schleuder
2
+ module Filters
3
+ def self.key_auto_import_from_attachments(list, mail)
4
+ # Don't run if not enabled.
5
+ return if ! list.key_auto_import_from_email
6
+
7
+ imported_fingerprints = EmailKeyImporter.import_from_attachments(list, mail)
8
+ if imported_fingerprints.size > 0
9
+ # If the message's signature could not be validated before, re-run the
10
+ # validation, because after having imported new or updated keys the
11
+ # validation now might work.
12
+ if mail.signature.present? && ! mail.signature.valid?
13
+ # Re-validate the signature validation, now that a new key was
14
+ # imported that might be the previously unknown signing key.
15
+ mail.repeat_validation!
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -1,7 +1,7 @@
1
1
  module Schleuder
2
2
  module Filters
3
3
  def self.receive_from_subscribed_emailaddresses_only(list, mail)
4
- if list.receive_from_subscribed_emailaddresses_only? && list.subscriptions.where(email: mail.from.first).blank?
4
+ if list.receive_from_subscribed_emailaddresses_only? && list.subscriptions.where(email: mail.from.first.downcase).blank?
5
5
  list.logger.info 'Rejecting mail as not from subscribed address.'
6
6
  return Errors::MessageSenderNotSubscribed.new
7
7
  end
@@ -1,17 +1,49 @@
1
1
  module Schleuder
2
2
  module Filters
3
+
4
+ # If keywords are present, recurse into arbitrary levels of multipart/mixed
5
+ # encapsulation. If multipart/alternative is found, remove all sub-parts
6
+ # but the text/plain part (assuming that every multipart/alternative
7
+ # contains exactly one text/plain). Change the content_type from
8
+ # mutlipart/alternative to mutlipart/mixed.
3
9
  def self.strip_html_from_alternative_if_keywords_present(list, mail)
4
- if mail[:content_type].blank? ||
5
- mail[:content_type].content_type != 'multipart/alternative' ||
6
- mail.keywords.blank?
10
+ # Only strip the text/html-part if keywords are present
11
+ if mail.keywords.blank? then return false end
12
+ return self.recursively_strip_html_from_alternative_if_keywords_present(list, mail)
13
+ end
14
+
15
+ def self.recursively_strip_html_from_alternative_if_keywords_present(list, mail)
16
+ if mail[:content_type].blank? then return false end
17
+ content_type = mail[:content_type].content_type
18
+
19
+ # The multipart/alternative could hide inside an arbitrary number of
20
+ # levels of multipart/mixed encapsulation.
21
+ # see also: https://www.rfc-editor.org/rfc/rfc2046#section-5.1.3
22
+ if content_type == 'multipart/mixed'
23
+ mail.parts.each do |part|
24
+ self.recursively_strip_html_from_alternative_if_keywords_present(list, part)
25
+ end
7
26
  return false
8
27
  end
9
28
 
29
+ # inside the mutlipart/mixed, we only care about multipart/mixed and
30
+ # mutlipart/alternative
31
+ if content_type != 'multipart/alternative' then return false end
32
+
33
+ # Inside multipart/alternative, there could be a text/html-part, or there
34
+ # could be a multipart/related-part which contains the text/html-part.
35
+ # Everything inside the multipart/alternative that is not text/plain
36
+ # should be deleted, since it will contain keywords and we only strip
37
+ # keywords from text/plain-parts.
10
38
  Schleuder.logger.debug 'Stripping html-part from multipart/alternative-message because it contains keywords'
11
39
  mail.parts.delete_if do |part|
12
- part[:content_type].content_type == 'text/html'
40
+ content_type = part[:content_type].content_type
41
+ content_type != 'text/plain'
13
42
  end
43
+
44
+ # NOTE: We could instead unencapsulate it.
14
45
  mail.content_type = 'multipart/mixed'
46
+
15
47
  mail.add_pseudoheader(:note, I18n.t('pseudoheaders.stripped_html_from_multialt_with_keywords'))
16
48
  end
17
49
  end
@@ -0,0 +1,9 @@
1
+ module Schleuder
2
+ module Filters
3
+ def self.key_auto_import_from_autocrypt_header(list, mail)
4
+ if list.key_auto_import_from_email
5
+ EmailKeyImporter.import_from_autocrypt_header(list, mail)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -13,11 +13,7 @@ module Schleuder
13
13
  list.logger.debug "Calling filter #{cmd}"
14
14
  response = Filters.send(cmd, list, mail)
15
15
  if stop?(response)
16
- if bounce?(response, mail)
17
- return response
18
- else
19
- return nil
20
- end
16
+ return response
21
17
  end
22
18
  end
23
19
  nil
@@ -32,31 +28,6 @@ module Schleuder
32
28
  response.kind_of?(StandardError)
33
29
  end
34
30
 
35
- def bounce?(response, mail)
36
- if list.bounces_drop_all
37
- list.logger.debug 'Dropping bounce as configurated'
38
- notify_admins(I18n.t('.bounces_drop_all'), mail.original_message)
39
- return false
40
- end
41
-
42
- list.bounces_drop_on_headers.each do |key, value|
43
- if mail[key].to_s.match(/#{value}/i)
44
- list.logger.debug "Incoming message header key '#{key}' matches value '#{value}': dropping the bounce."
45
- notify_admins(I18n.t('.bounces_drop_on_headers', key: key, value: value), mail.original_message)
46
- return false
47
- end
48
- end
49
-
50
- list.logger.debug 'Bouncing message'
51
- true
52
- end
53
-
54
- def notify_admins(reason, original_message)
55
- if list.bounces_notify_admins?
56
- list.logger.notify_admin reason, original_message, I18n.t('notice')
57
- end
58
- end
59
-
60
31
  def load_filters
61
32
  list.logger.debug "Loading #{filter_type}_decryption filters"
62
33
  sorted_filters.map do |filter_name|