schleuder 4.0.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +6 -9
- data/db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb +1 -1
- data/db/migrate/20211106112020_change_boolean_values_to_integers.rb +46 -0
- data/db/migrate/20211107151309_add_limits_to_string_columns.rb +28 -0
- data/db/migrate/20220910170110_add_key_auto_import_from_email.rb +11 -0
- data/db/schema.rb +16 -16
- data/etc/list-defaults.yml +16 -0
- data/etc/schleuder.yml +29 -11
- data/lib/schleuder/cli.rb +15 -2
- data/lib/schleuder/conf.rb +23 -3
- data/lib/schleuder/email_key_importer.rb +91 -0
- data/lib/schleuder/filters/post_decryption/35_key_auto_import_from_attachments.rb +21 -0
- data/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb +1 -1
- data/lib/schleuder/filters/post_decryption/90_strip_html_from_alternative_if_keywords_present.rb +36 -4
- data/lib/schleuder/filters/pre_decryption/60_key_auto_import_from_autocrypt_header.rb +9 -0
- data/lib/schleuder/filters_runner.rb +1 -30
- data/lib/schleuder/gpgme/ctx.rb +34 -93
- data/lib/schleuder/gpgme/key.rb +1 -1
- data/lib/schleuder/gpgme/key_extractor.rb +30 -0
- data/lib/schleuder/http.rb +56 -0
- data/lib/schleuder/key_fetcher.rb +89 -0
- data/lib/schleuder/keyword_handlers/key_management.rb +2 -2
- data/lib/schleuder/keyword_handlers/subscription_management.rb +19 -3
- data/lib/schleuder/list.rb +26 -10
- data/lib/schleuder/list_builder.rb +1 -1
- data/lib/schleuder/logger.rb +1 -1
- data/lib/schleuder/mail/gpg/decrypted_part.rb +20 -0
- data/lib/schleuder/mail/gpg/delivery_handler.rb +38 -0
- data/lib/schleuder/mail/gpg/encrypted_part.rb +29 -5
- data/lib/schleuder/mail/gpg/gpgme_ext.rb +8 -0
- data/lib/schleuder/mail/gpg/gpgme_helper.rb +155 -0
- data/lib/schleuder/mail/gpg/inline_decrypted_message.rb +82 -0
- data/lib/schleuder/mail/gpg/inline_signed_message.rb +73 -0
- data/lib/schleuder/mail/gpg/mime_signed_message.rb +28 -0
- data/lib/schleuder/mail/gpg/missing_keys_error.rb +6 -0
- data/lib/schleuder/mail/gpg/sign_part.rb +19 -9
- data/lib/schleuder/mail/gpg/signed_part.rb +37 -0
- data/lib/schleuder/mail/gpg/verified_part.rb +10 -0
- data/lib/schleuder/mail/gpg/verify_result_attribute.rb +32 -0
- data/lib/schleuder/mail/gpg/version_part.rb +22 -0
- data/lib/schleuder/mail/gpg.rb +236 -7
- data/lib/schleuder/mail/message.rb +98 -14
- data/lib/schleuder/runner.rb +40 -10
- data/lib/schleuder/sks_client.rb +18 -0
- data/lib/schleuder/version.rb +1 -1
- data/lib/schleuder/vks_client.rb +24 -0
- data/lib/schleuder-api-daemon/routes/key.rb +22 -1
- data/lib/schleuder.rb +11 -7
- data/locales/de.yml +38 -19
- data/locales/en.yml +22 -3
- metadata +58 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4148fa7b166ff22d2af8980a549f08e7bc659080df8f9579d15a190cbb046033
|
4
|
+
data.tar.gz: edb0f987169bdb5585f9350057e5f7114e377d13cec9e9f413a6da24a4e3cd46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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-
|
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-
|
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-
|
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-
|
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:
|
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
|
data/etc/list-defaults.yml
CHANGED
@@ -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
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
|
29
|
-
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
|
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
|
-
|
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
|
-
|
148
|
+
fatal result, $?.exitstatus
|
136
149
|
end
|
137
150
|
result
|
138
151
|
end
|
data/lib/schleuder/conf.rb
CHANGED
@@ -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
|
-
'
|
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.
|
119
|
-
instance.config['
|
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
|
+
|
data/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb
CHANGED
@@ -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
|
data/lib/schleuder/filters/post_decryption/90_strip_html_from_alternative_if_keywords_present.rb
CHANGED
@@ -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
|
5
|
-
|
6
|
-
|
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
|
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
|
@@ -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
|
-
|
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|
|