schleuder 4.0.2 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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|
|