schleuder 4.0.3 → 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/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
|