schleuder 4.0.3 → 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/20220910170110_add_key_auto_import_from_email.rb +11 -0
- data/db/schema.rb +6 -6
- data/etc/list-defaults.yml +16 -0
- data/etc/schleuder.yml +29 -11
- data/lib/schleuder/cli.rb +14 -1
- 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/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/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 +22 -3
- data/locales/en.yml +22 -3
- metadata +72 -24
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,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,11 +10,10 @@
|
|
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"
|
15
|
+
t.datetime "created_at", precision: nil
|
16
|
+
t.datetime "updated_at", precision: nil
|
18
17
|
t.string "email", limit: 255
|
19
18
|
t.string "fingerprint", limit: 255
|
20
19
|
t.string "log_level", limit: 255, default: "warn"
|
@@ -47,6 +46,7 @@ ActiveRecord::Schema.define(version: 2021_11_07_151309) 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|
|
@@ -55,8 +55,8 @@ ActiveRecord::Schema.define(version: 2021_11_07_151309) do
|
|
55
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
|
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
|
data/lib/schleuder/gpgme/ctx.rb
CHANGED
@@ -7,6 +7,9 @@ module GPGME
|
|
7
7
|
'new_subkeys' => 8
|
8
8
|
}
|
9
9
|
|
10
|
+
# This differs from import_filtered() in that it doesn't filter the keys at
|
11
|
+
# all, and that it returns the import-results themselves, not strings based
|
12
|
+
# on those results.
|
10
13
|
def keyimport(keydata)
|
11
14
|
self.import_keys(GPGME::Data.new(keydata))
|
12
15
|
result = self.import_result
|
@@ -33,13 +36,11 @@ module GPGME
|
|
33
36
|
end
|
34
37
|
|
35
38
|
def find_keys(input=nil, secret_only=nil)
|
36
|
-
|
37
|
-
keys(input, secret_only)
|
39
|
+
keys(normalize_key_identifier(input), secret_only)
|
38
40
|
end
|
39
41
|
|
40
42
|
def find_distinct_key(input=nil, secret_only=nil)
|
41
|
-
|
42
|
-
keys = keys(input, secret_only)
|
43
|
+
keys = keys(normalize_key_identifier(input), secret_only)
|
43
44
|
if keys.size == 1
|
44
45
|
keys.first
|
45
46
|
else
|
@@ -47,16 +48,16 @@ module GPGME
|
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
50
|
-
def
|
51
|
+
def normalize_key_identifier(input)
|
51
52
|
case input
|
52
53
|
when /.*?([^ <>]+@[^ <>]+).*?/
|
53
|
-
|
54
|
+
"<#{$1}>"
|
54
55
|
when /^http/
|
55
|
-
|
56
|
+
input
|
56
57
|
when Conf::FINGERPRINT_REGEXP
|
57
|
-
|
58
|
+
"0x#{input.gsub(/^0x/, '')}"
|
58
59
|
else
|
59
|
-
|
60
|
+
input
|
60
61
|
end
|
61
62
|
end
|
62
63
|
|
@@ -88,85 +89,23 @@ module GPGME
|
|
88
89
|
GPGME::Engine.info.find {|e| e.protocol == GPGME::PROTOCOL_OpenPGP }
|
89
90
|
end
|
90
91
|
|
91
|
-
def
|
92
|
-
#
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
output.compact.join("\n")
|
100
|
-
end
|
101
|
-
|
102
|
-
def refresh_key(fingerprint)
|
103
|
-
args = "#{keyserver_arg} #{import_filter_arg} --refresh-keys #{fingerprint}"
|
104
|
-
gpgerr, gpgout, exitcode = self.class.gpgcli(args)
|
105
|
-
|
106
|
-
if exitcode > 0
|
107
|
-
# Return filtered error messages. Include gpgkeys-messages from stdout
|
108
|
-
# (gpg 2.0 does that), which could e.g. report a failure to connect to
|
109
|
-
# the keyserver.
|
110
|
-
# TODO: Revisit this once we don't do network access via GPG
|
111
|
-
# anymore.
|
112
|
-
res = [
|
113
|
-
refresh_key_filter_messages(gpgerr),
|
114
|
-
refresh_key_filter_messages(gpgout).grep(/^gpgkeys: /)
|
115
|
-
].flatten.compact
|
116
|
-
# if there was an error that we don't filter out,
|
117
|
-
# we better kill dirmngr, so it hopefully won't suffer
|
118
|
-
# from the same error during the next run.
|
119
|
-
# See #309 for background
|
120
|
-
if !res.empty?
|
121
|
-
`gpgconf --kill dirmngr`
|
122
|
-
end
|
123
|
-
res.join("\n")
|
124
|
-
else
|
125
|
-
lines = translate_output('key_updated', gpgout).reject do |line|
|
126
|
-
# Reduce the noise a little.
|
127
|
-
line.match(/.* \(unchanged\):$/)
|
92
|
+
def import_filtered(input, gpg_extra_arg='')
|
93
|
+
# Import through gpgcli so we can use import-filter. GPGME still does
|
94
|
+
# not provide that feature (as of summer 2023): <https://dev.gnupg.org/T4721> :(
|
95
|
+
gpgerr, gpgout, exitcode = self.class.gpgcli("#{import_filter_arg} #{gpg_extra_arg} --import") do |stdin, stdout, stderr|
|
96
|
+
# Wrap this into a block because gpg breaks the pipe if it encounters invalid data.
|
97
|
+
begin
|
98
|
+
stdin.print input
|
99
|
+
rescue Errno::EPIPE
|
128
100
|
end
|
129
|
-
|
101
|
+
stdin.close
|
102
|
+
stdout.readlines
|
130
103
|
end
|
131
|
-
|
132
|
-
|
133
|
-
def fetch_key(input)
|
134
|
-
arguments, error = fetch_key_gpg_arguments_for(input)
|
135
|
-
return error if error
|
136
|
-
|
137
|
-
gpgerr, gpgout, exitcode = self.class.gpgcli("#{import_filter_arg} #{arguments}")
|
138
|
-
|
139
|
-
# Unfortunately gpg doesn't exit with code > 0 if `--fetch-key` fails.
|
140
|
-
if exitcode > 0 || gpgerr.grep(/ unable to fetch /).presence
|
141
|
-
"Fetching #{input} did not succeed:\n#{gpgerr.join("\n")}"
|
142
|
-
else
|
143
|
-
translate_output('key_fetched', gpgout).join("\n")
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def fetch_key_gpg_arguments_for(input)
|
148
|
-
case input
|
149
|
-
when Conf::FINGERPRINT_REGEXP
|
150
|
-
"#{keyserver_arg} --recv-key #{input}"
|
151
|
-
when /^http/
|
152
|
-
"--fetch-key #{input}"
|
153
|
-
when /@/
|
154
|
-
# --recv-key doesn't work with email-addresses, so we use --locate-key
|
155
|
-
# restricted to keyservers.
|
156
|
-
"#{keyserver_arg} --auto-key-locate keyserver --locate-key #{input}"
|
104
|
+
if exitcode > 0
|
105
|
+
RuntimeError.new(gpgerr.join("\n"))
|
157
106
|
else
|
158
|
-
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def translate_output(locale_key, gpgoutput)
|
163
|
-
import_states = translate_import_data(gpgoutput)
|
164
|
-
strings = import_states.map do |fingerprint, states|
|
165
|
-
key = find_distinct_key(fingerprint)
|
166
|
-
I18n.t(locale_key, key_summary: key.summary,
|
167
|
-
states: states.to_sentence)
|
107
|
+
translate_import_data(gpgout)
|
168
108
|
end
|
169
|
-
strings
|
170
109
|
end
|
171
110
|
|
172
111
|
def translate_import_data(gpgoutput)
|
@@ -210,7 +149,7 @@ module GPGME
|
|
210
149
|
errors = []
|
211
150
|
output = []
|
212
151
|
base_cmd = gpg_engine.file_name
|
213
|
-
base_args = '--no-greeting --
|
152
|
+
base_args = '--no-greeting --quiet --armor --trust-model always --no-tty --command-fd 0 --status-fd 1'
|
214
153
|
cmd = [base_cmd, base_args, args].flatten.join(' ')
|
215
154
|
Open3.popen3(cmd) do |stdin, stdout, stderr, thread|
|
216
155
|
if block_given?
|
@@ -223,19 +162,21 @@ module GPGME
|
|
223
162
|
exitcode = thread.value.exitstatus
|
224
163
|
end
|
225
164
|
|
165
|
+
# Don't treat warnings as errors but log them.
|
166
|
+
errors = errors.map do |line|
|
167
|
+
if line.match?(/gpg: WARNING: (unsafe permissions on homedir|using insecure memory)/i)
|
168
|
+
Schleuder.logger.warn(line)
|
169
|
+
nil
|
170
|
+
else
|
171
|
+
line
|
172
|
+
end
|
173
|
+
end.compact
|
174
|
+
|
226
175
|
[errors, output, exitcode]
|
227
176
|
rescue Errno::ENOENT
|
228
177
|
raise 'Need gpg in $PATH or in $GPGBIN'
|
229
178
|
end
|
230
179
|
|
231
|
-
def keyserver_arg
|
232
|
-
if Conf.keyserver.present?
|
233
|
-
"--keyserver #{Conf.keyserver}"
|
234
|
-
else
|
235
|
-
''
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
180
|
def import_filter_arg
|
240
181
|
%{ --import-filter drop-sig='sig_created_d > 0000-00-00'}
|
241
182
|
end
|