schleuder 3.3.0 → 3.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -8
- data/Rakefile +3 -3
- data/db/migrate/20180723173900_add_deliver_selfsent_to_list.rb +11 -0
- data/db/migrate/20190906194820_add_autocrypt_header_to_list.rb +11 -0
- data/db/schema.rb +3 -1
- data/etc/list-defaults.yml +9 -1
- data/lib/schleuder.rb +10 -0
- data/lib/schleuder/cli.rb +9 -3
- data/lib/schleuder/filters/post_decryption/90_strip_html_from_alternative_if_keywords_present.rb +21 -0
- data/lib/schleuder/gpgme/ctx.rb +23 -2
- data/lib/schleuder/gpgme/key.rb +4 -0
- data/lib/schleuder/list.rb +23 -4
- data/lib/schleuder/logger_notifications.rb +8 -1
- data/lib/schleuder/mail/message.rb +33 -25
- data/lib/schleuder/plugins/attach_listkey.rb +6 -10
- data/lib/schleuder/plugins/key_management.rb +2 -19
- data/lib/schleuder/plugins/resend.rb +14 -11
- data/lib/schleuder/runner.rb +30 -2
- data/lib/schleuder/version.rb +1 -1
- data/locales/de.yml +5 -1
- data/locales/en.yml +5 -1
- metadata +32 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8aff8f8ecff4958e630158f6fc5811db96d26699ab0946d16b4fc430e2897387
|
4
|
+
data.tar.gz: daa4d6fbe4941a311dcfc54d5544b1771d809ca2f570f6060473b0f94f4bc577
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b125a6734601100a1a768b9295848c4d3c1de0fb8336e7a6cf070972c78913f71c2ff52418a8a16cff705a546ca27de29155cae0c1f30736f4f8127bffe5b823
|
7
|
+
data.tar.gz: 55bd88009950e4dbc52a3f17f3566ee8f4bd7d43751237f19da514d00e8e3d2dc92817c15660b0d826bb8072cb537f3652cf5e93ebeca43fcc424faf33964978
|
data/README.md
CHANGED
@@ -16,11 +16,11 @@ Requirements
|
|
16
16
|
* sqlite3
|
17
17
|
* openssl
|
18
18
|
|
19
|
-
*If you use Debian
|
19
|
+
*If you use Debian buster or CentOS 7, 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
20
|
|
21
21
|
*🛈 A note regarding Ubuntu: All Ubuntu versions up to and including 17.10 don't meet the requirements with their packaged versions of gnupg! To run Schleuder on Ubuntu you currently have to install a more recent version of gnupg manually. Only Ubuntu 18.04 ("bionic") provides modern enough versions of Schleuder's requirements.*
|
22
22
|
|
23
|
-
On systems that base on Debian
|
23
|
+
On systems that base on Debian 10 ("buster"), install the dependencies via
|
24
24
|
|
25
25
|
apt-get install ruby-dev gnupg2 libgpgme-dev libsqlite3-dev libssl-dev build-essential
|
26
26
|
|
@@ -47,15 +47,15 @@ Additionally these **rubygems** are required (will be installed automatically un
|
|
47
47
|
Installing Schleuder
|
48
48
|
------------
|
49
49
|
|
50
|
-
1. Download [the gem](https://schleuder.org/download/schleuder-3.3.
|
50
|
+
1. Download [the gem](https://schleuder.org/download/schleuder-3.5.3.gem) and [the OpenPGP-signature](https://schleuder.org/download/schleuder-3.5.3.gem.sig) and verify:
|
51
51
|
```
|
52
52
|
gpg --recv-key 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3
|
53
|
-
gpg --verify schleuder-3.3.
|
53
|
+
gpg --verify schleuder-3.5.3.gem.sig
|
54
54
|
```
|
55
55
|
|
56
56
|
2. If all went well install the gem:
|
57
57
|
```
|
58
|
-
gem install schleuder-3.3.
|
58
|
+
gem install schleuder-3.5.3.gem
|
59
59
|
```
|
60
60
|
|
61
61
|
3. Set up schleuder:
|
@@ -65,7 +65,7 @@ Installing Schleuder
|
|
65
65
|
This creates necessary directories, copies example configs, etc. If you see errors about missing write permissions please follow the advice given.
|
66
66
|
|
67
67
|
|
68
|
-
For further information on setup and configuration please read <https://schleuder.org/docs
|
68
|
+
For further information on setup and configuration please read <https://schleuder.org/schleuder/docs/server-admins.html>.
|
69
69
|
|
70
70
|
|
71
71
|
Command line usage
|
@@ -112,7 +112,7 @@ To execute the test suite run:
|
|
112
112
|
|
113
113
|
bundle exec rspec
|
114
114
|
|
115
|
-
Please note: Some of the specs use 'pgrep'. On systems that base on Debian
|
115
|
+
Please note: Some of the specs use 'pgrep'. On systems that base on Debian 10 ("buster") install it via
|
116
116
|
|
117
117
|
apt-get install procps
|
118
118
|
|
@@ -145,4 +145,4 @@ GNU GPL 3.0. Please see [LICENSE.txt](LICENSE.txt).
|
|
145
145
|
Alternative Download
|
146
146
|
--------------------
|
147
147
|
|
148
|
-
Alternatively to the gem-files you can download the latest release as [a tarball](https://schleuder.org/download/schleuder-3.3.
|
148
|
+
Alternatively to the gem-files you can download the latest release as [a tarball](https://schleuder.org/download/schleuder-3.5.3.tar.gz) and [its OpenPGP-signature](https://schleuder.org/download/schleuder-3.5.3.tar.gz.sig).
|
data/Rakefile
CHANGED
@@ -3,7 +3,7 @@ require_relative "lib/#{project}.rb"
|
|
3
3
|
|
4
4
|
@version = Schleuder::VERSION
|
5
5
|
@tagname = "#{project}-#{@version}"
|
6
|
-
@gpguid = '
|
6
|
+
@gpguid = 'B3D190D5235C74E1907EACFE898F2C91E2E6E1F3'
|
7
7
|
@filename_gem = "#{@tagname}.gem"
|
8
8
|
@filename_tarball = "#{@tagname}.tar.gz"
|
9
9
|
|
@@ -110,7 +110,7 @@ end
|
|
110
110
|
desc 'Publish gem-file to rubygems.org'
|
111
111
|
task :publish_gem do
|
112
112
|
puts "Really push #{@filename_gem} to rubygems.org? [yN]"
|
113
|
-
if gets.match(/^y/i)
|
113
|
+
if $stdin.gets.match(/^y/i)
|
114
114
|
puts "Pushing..."
|
115
115
|
`gem push #{@filename_gem}`
|
116
116
|
else
|
@@ -132,7 +132,7 @@ desc 'Check if version-tag already exists'
|
|
132
132
|
task :check_version do
|
133
133
|
# Check if Schleuder::VERSION has been updated since last release
|
134
134
|
if `git tag`.match?(/^#{@tagname}$/)
|
135
|
-
$stderr.puts "Warning: Tag '#{@tagname}' already exists. Did you forget to update
|
135
|
+
$stderr.puts "Warning: Tag '#{@tagname}' already exists. Did you forget to update lib/#{project}/version.rb?"
|
136
136
|
$stderr.print "Delete tag to continue? [yN] "
|
137
137
|
if $stdin.gets.match(/^y/i)
|
138
138
|
`git tag -d #{@tagname}`
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class AddDeliverSelfsentToList < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
if ! column_exists?(:lists, :deliver_selfsent)
|
4
|
+
add_column :lists, :deliver_selfsent, :boolean, default: true
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def down
|
9
|
+
remove_column(:lists, :deliver_selfsent)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class AddAutocryptHeaderToList < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
if ! column_exists?(:lists, :include_autocrypt_header)
|
4
|
+
add_column :lists, :include_autocrypt_header, :boolean, default: true
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def down
|
9
|
+
remove_column(:lists, :include_autocrypt_header)
|
10
|
+
end
|
11
|
+
end
|
data/db/schema.rb
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
#
|
12
12
|
# It's strongly recommended that you check this file into your version control system.
|
13
13
|
|
14
|
-
ActiveRecord::Schema.define(version:
|
14
|
+
ActiveRecord::Schema.define(version: 20190906194820) do
|
15
15
|
|
16
16
|
create_table "lists", force: :cascade do |t|
|
17
17
|
t.datetime "created_at"
|
@@ -37,6 +37,7 @@ ActiveRecord::Schema.define(version: 20180110203100) do
|
|
37
37
|
t.boolean "keep_msgid", default: true
|
38
38
|
t.boolean "bounces_drop_all", default: false
|
39
39
|
t.boolean "bounces_notify_admins", default: true
|
40
|
+
t.boolean "deliver_selfsent", default: true
|
40
41
|
t.boolean "include_list_headers", default: true
|
41
42
|
t.boolean "include_openpgp_header", default: true
|
42
43
|
t.integer "max_message_size_kb", default: 10240
|
@@ -44,6 +45,7 @@ ActiveRecord::Schema.define(version: 20180110203100) do
|
|
44
45
|
t.boolean "forward_all_incoming_to_admins", default: false
|
45
46
|
t.integer "logfiles_to_keep", default: 2
|
46
47
|
t.text "internal_footer", default: ""
|
48
|
+
t.boolean "include_autocrypt_header", default: true
|
47
49
|
end
|
48
50
|
|
49
51
|
create_table "subscriptions", force: :cascade do |t|
|
data/etc/list-defaults.yml
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
#
|
7
7
|
# Options are listed with the behaviour encoded in the database schema.
|
8
8
|
|
9
|
-
# Only send out
|
9
|
+
# Only send out encrypted emails to subscriptions?
|
10
10
|
# (This setting does not affect resend-messages.)
|
11
11
|
send_encrypted_only: true
|
12
12
|
|
@@ -93,6 +93,9 @@ bounces_drop_on_headers:
|
|
93
93
|
# Send a notice to the list-admins whenever an email is bounced or dropped?
|
94
94
|
bounces_notify_admins: true
|
95
95
|
|
96
|
+
# Include Autocrypt header into emails?
|
97
|
+
include_autocrypt_header: true
|
98
|
+
|
96
99
|
# Include RFC-compliant List-* Headers into emails?
|
97
100
|
include_list_headers: true
|
98
101
|
|
@@ -123,3 +126,8 @@ language: en
|
|
123
126
|
# Forward a raw copy of all incoming emails to the list-admins?
|
124
127
|
# Mainly useful for debugging.
|
125
128
|
forward_all_incoming_to_admins: false
|
129
|
+
|
130
|
+
# Should e-mails be delivered to the original subscribed sender?
|
131
|
+
# Disabling this only works for signed e-mails; any e-mail that is unsigned
|
132
|
+
# sent to the list is treated as coming from an unknown source
|
133
|
+
deliver_selfsent: true
|
data/lib/schleuder.rb
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# default to UTF-8 encoding as early as possible for external
|
2
|
+
# data.
|
3
|
+
#
|
4
|
+
# this should ensure we are able to parse most incoming
|
5
|
+
# plain text mails in different charsets.
|
6
|
+
Encoding.default_external = Encoding::UTF_8
|
7
|
+
|
1
8
|
# Stdlib
|
2
9
|
require 'fileutils'
|
3
10
|
require 'singleton'
|
@@ -61,6 +68,9 @@ ENV["SCHLEUDER_CONFIG"] ||= '/etc/schleuder/schleuder.yml'
|
|
61
68
|
ENV["SCHLEUDER_LIST_DEFAULTS"] ||= '/etc/schleuder/list-defaults.yml'
|
62
69
|
ENV["SCHLEUDER_ENV"] ||= 'production'
|
63
70
|
ENV["SCHLEUDER_ROOT"] = rootdir.to_s
|
71
|
+
# Ensure that gnupg never-ever tries to ask for a passphrase.
|
72
|
+
ENV["GPG_TTY"] = "/nonexistant-#{rand}"
|
73
|
+
ENV["DISPLAY"] = nil
|
64
74
|
|
65
75
|
GPGME::Ctx.set_gpg_path_from_env
|
66
76
|
GPGME::Ctx.check_gpg_version
|
data/lib/schleuder/cli.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'thor'
|
2
2
|
require 'yaml'
|
3
3
|
require 'gpgme'
|
4
|
+
require 'charlock_holmes'
|
4
5
|
|
5
6
|
require_relative '../schleuder'
|
6
7
|
require 'schleuder/cli/subcommand_fix'
|
@@ -67,6 +68,7 @@ module Schleuder
|
|
67
68
|
|
68
69
|
desc 'refresh_keys [list1@example.com]', "Refresh all keys of all list from the keyservers sequentially (one by one or on the passed list). (This is supposed to be run from cron weekly.)"
|
69
70
|
def refresh_keys(list=nil)
|
71
|
+
GPGME::Ctx.send_notice_if_gpg_does_not_know_import_filter
|
70
72
|
work_on_lists(:refresh_keys,list)
|
71
73
|
permission_notice
|
72
74
|
end
|
@@ -319,11 +321,15 @@ Please notify the users and admins of this list of these changes.
|
|
319
321
|
private
|
320
322
|
|
321
323
|
def work_on_lists(subj, list=nil)
|
322
|
-
|
323
|
-
List.all
|
324
|
+
if list.nil?
|
325
|
+
selected_lists = List.all
|
324
326
|
else
|
325
|
-
List.where(email: list)
|
327
|
+
selected_lists = List.where(email: list)
|
328
|
+
if selected_lists.blank?
|
329
|
+
error("No list with this address exists: #{list.inspect}")
|
330
|
+
end
|
326
331
|
end
|
332
|
+
|
327
333
|
selected_lists.each do |list|
|
328
334
|
I18n.locale = list.language
|
329
335
|
output = list.send(subj)
|
data/lib/schleuder/filters/post_decryption/90_strip_html_from_alternative_if_keywords_present.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Schleuder
|
2
|
+
module Filters
|
3
|
+
def self.strip_html_from_alternative_if_keywords_present(list, mail)
|
4
|
+
if mail[:content_type].blank? ||
|
5
|
+
mail[:content_type].content_type != 'multipart/alternative' ||
|
6
|
+
mail.keywords.blank?
|
7
|
+
return false
|
8
|
+
end
|
9
|
+
|
10
|
+
Schleuder.logger.debug 'Stripping html-part from multipart/alternative-message because it contains keywords'
|
11
|
+
mail.parts.delete_if do |part|
|
12
|
+
part[:content_type].content_type == 'text/html'
|
13
|
+
end
|
14
|
+
mail.content_type = 'multipart/mixed'
|
15
|
+
mail.add_pseudoheader(:note, I18n.t('pseudoheaders.stripped_html_from_multialt_with_keywords'))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
|
data/lib/schleuder/gpgme/ctx.rb
CHANGED
@@ -103,7 +103,7 @@ module GPGME
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def refresh_key(fingerprint)
|
106
|
-
args = "#{keyserver_arg} --refresh-keys #{fingerprint}"
|
106
|
+
args = "#{keyserver_arg} #{import_filter_arg} --refresh-keys #{fingerprint}"
|
107
107
|
gpgerr, gpgout, exitcode = self.class.gpgcli(args)
|
108
108
|
|
109
109
|
if exitcode > 0
|
@@ -136,7 +136,8 @@ module GPGME
|
|
136
136
|
arguments, error = fetch_key_gpg_arguments_for(input)
|
137
137
|
return error if error
|
138
138
|
|
139
|
-
|
139
|
+
self.class.send_notice_if_gpg_does_not_know_import_filter
|
140
|
+
gpgerr, gpgout, exitcode = self.class.gpgcli("#{import_filter_arg} #{arguments}")
|
140
141
|
|
141
142
|
# Unfortunately gpg doesn't exit with code > 0 if `--fetch-key` fails.
|
142
143
|
if exitcode > 0 || gpgerr.grep(/ unable to fetch /).presence
|
@@ -270,5 +271,25 @@ module GPGME
|
|
270
271
|
""
|
271
272
|
end
|
272
273
|
end
|
274
|
+
|
275
|
+
def self.gpg_knows_import_filter?
|
276
|
+
sufficient_gpg_version?('2.1.15')
|
277
|
+
end
|
278
|
+
|
279
|
+
def import_filter_arg
|
280
|
+
if self.class.gpg_knows_import_filter?
|
281
|
+
%{ --import-filter drop-sig='sig_created_d > 0000-00-00'}
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def self.send_notice_if_gpg_does_not_know_import_filter
|
286
|
+
if ! gpg_knows_import_filter?
|
287
|
+
Schleuder.logger.notify_superadmin(
|
288
|
+
subject: 'Schleuder installation problem',
|
289
|
+
message: "Your version of GnuPG is very old, please update!\n\nWith your version of GnuPG we can not protect your setup against signature flooding. Please update to at least version 2.1.15 to fix this problem. See <https://dkg.fifthhorseman.net/blog/openpgp-certificate-flooding.html> for details on the background."
|
290
|
+
)
|
291
|
+
''
|
292
|
+
end
|
293
|
+
end
|
273
294
|
end
|
274
295
|
end
|
data/lib/schleuder/gpgme/key.rb
CHANGED
@@ -57,6 +57,10 @@ module GPGME
|
|
57
57
|
"#{self.to_s}\n\n#{export(armor: true).read}"
|
58
58
|
end
|
59
59
|
|
60
|
+
def minimal
|
61
|
+
export(minimal: true).to_s
|
62
|
+
end
|
63
|
+
|
60
64
|
# Force encoding, some databases save "ASCII-8BIT" as binary data.
|
61
65
|
alias_method :orig_fingerprint, :fingerprint
|
62
66
|
def fingerprint
|
data/lib/schleuder/list.rb
CHANGED
@@ -19,6 +19,7 @@ module Schleuder
|
|
19
19
|
:receive_admin_only,
|
20
20
|
:keep_msgid,
|
21
21
|
:bounces_drop_all,
|
22
|
+
:deliver_selfsent,
|
22
23
|
:bounces_notify_admins,
|
23
24
|
:include_list_headers,
|
24
25
|
:include_openpgp_header,
|
@@ -146,6 +147,16 @@ module Schleuder
|
|
146
147
|
key.armored
|
147
148
|
end
|
148
149
|
|
150
|
+
def key_minimal_base64_encoded(fingerprint=self.fingerprint)
|
151
|
+
key = keys(fingerprint).first
|
152
|
+
|
153
|
+
if key.blank?
|
154
|
+
return false
|
155
|
+
end
|
156
|
+
|
157
|
+
Base64.strict_encode64(key.minimal)
|
158
|
+
end
|
159
|
+
|
149
160
|
def check_keys
|
150
161
|
now = Time.now
|
151
162
|
checkdate = now + (60 * 60 * 24 * 14) # two weeks
|
@@ -340,16 +351,24 @@ module Schleuder
|
|
340
351
|
true
|
341
352
|
end
|
342
353
|
|
343
|
-
def send_to_subscriptions(mail)
|
354
|
+
def send_to_subscriptions(mail, incoming_mail=nil)
|
344
355
|
logger.debug "Sending to subscriptions."
|
345
356
|
mail.add_internal_footer!
|
346
357
|
self.subscriptions.each do |subscription|
|
347
358
|
begin
|
348
|
-
|
349
|
-
|
350
|
-
else
|
359
|
+
|
360
|
+
if ! subscription.delivery_enabled
|
351
361
|
logger.info "Not sending to #{subscription.email}: delivery is disabled."
|
362
|
+
next
|
363
|
+
end
|
364
|
+
|
365
|
+
if ! self.deliver_selfsent && incoming_mail.was_validly_signed? && ( subscription == incoming_mail.signer )
|
366
|
+
logger.info "Not sending to #{subscription.email}: delivery of self sent is disabled."
|
367
|
+
next
|
352
368
|
end
|
369
|
+
|
370
|
+
subscription.send_mail(mail)
|
371
|
+
|
353
372
|
rescue => exc
|
354
373
|
msg = I18n.t('errors.delivery_error',
|
355
374
|
{ email: subscription.email, error: exc.to_s })
|
@@ -18,9 +18,14 @@ module Schleuder
|
|
18
18
|
notify_admin(string, original_message)
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
21
|
+
def notify_superadmin(message:, original_message: nil, subject: 'Error')
|
22
|
+
notify_admin(message, original_message, subject, superadmin)
|
23
|
+
end
|
24
|
+
|
25
|
+
def notify_admin(thing, original_message=nil, subject='Error', recipients=nil)
|
22
26
|
# Minimize using other classes here, we don't know what caused the error.
|
23
27
|
msg_parts = convert_to_msg_parts(thing, original_message)
|
28
|
+
recipients ||= adminaddresses
|
24
29
|
Array(adminaddresses).each do |address, key|
|
25
30
|
mail = Mail.new
|
26
31
|
mail.from = @from
|
@@ -37,6 +42,8 @@ module Schleuder
|
|
37
42
|
gpg_opts.merge!(encrypt: true, keys: { address => key.fingerprint })
|
38
43
|
end
|
39
44
|
mail.gpg gpg_opts
|
45
|
+
|
46
|
+
mail.header['List-Id'] = "<#{@list.email.gsub('@', '.')}>"
|
40
47
|
end
|
41
48
|
mail.deliver
|
42
49
|
end
|
@@ -17,6 +17,7 @@ module Mail
|
|
17
17
|
attr_accessor :original_message
|
18
18
|
attr_accessor :list
|
19
19
|
attr_accessor :protected_headers_subject
|
20
|
+
attr_writer :dynamic_pseudoheaders
|
20
21
|
|
21
22
|
# TODO: This should be in initialize(), but I couldn't understand the
|
22
23
|
# strange errors about wrong number of arguments when overriding
|
@@ -44,22 +45,19 @@ module Mail
|
|
44
45
|
# might be gone (e.g. request-keywords that delete subscriptions or
|
45
46
|
# keys).
|
46
47
|
new.signer
|
47
|
-
self.dynamic_pseudoheaders.
|
48
|
-
new.add_pseudoheader(str)
|
49
|
-
end
|
48
|
+
new.dynamic_pseudoheaders = self.dynamic_pseudoheaders.dup
|
50
49
|
|
51
50
|
# Store previously protected subject for later access.
|
52
51
|
# mail-gpg pulls headers from the decrypted mime parts "up" into the main
|
53
52
|
# headers, which reveals protected subjects.
|
54
53
|
if self.subject != new.subject
|
55
54
|
new.protected_headers_subject = self.subject.dup
|
56
|
-
|
57
|
-
# Delete the protected headers which might leak information.
|
58
|
-
if new.parts.first.content_type == "text/rfc822-headers; protected-headers=v1"
|
59
|
-
new.parts.shift
|
60
|
-
end
|
61
55
|
end
|
62
56
|
|
57
|
+
# Delete the protected headers which might leak information.
|
58
|
+
if new.parts.first && new.parts.first.content_type == "text/rfc822-headers; protected-headers=v1"
|
59
|
+
new.parts.shift
|
60
|
+
end
|
63
61
|
|
64
62
|
new
|
65
63
|
end
|
@@ -207,11 +205,15 @@ module Mail
|
|
207
205
|
@recipient.match(/-bounce@/).present? ||
|
208
206
|
# Empty Return-Path
|
209
207
|
self.return_path.to_s == '<>' ||
|
210
|
-
# Auto-Submitted exists and does not equal 'no' and
|
211
|
-
#
|
208
|
+
# Auto-Submitted exists and does not equal 'no' and:
|
209
|
+
# - no cron header is present
|
210
|
+
# - no Jenkins job notification header is present
|
211
|
+
# as these emails have the auto-submitted header.
|
212
212
|
( self['Auto-Submitted'].present? && \
|
213
213
|
self['Auto-Submitted'].to_s.downcase != 'no' && \
|
214
|
-
!self['X-Cron-Env'].present?
|
214
|
+
!self['X-Cron-Env'].present? && \
|
215
|
+
!self['X-Jenkins-Job'].present? && \
|
216
|
+
self.subject.to_s !~ /\A\*\*\* SECURITY information.*\*\*\*\Z/)
|
215
217
|
end
|
216
218
|
|
217
219
|
def keywords
|
@@ -245,11 +247,11 @@ module Mail
|
|
245
247
|
# decide itself how to encode, it works. If we don't, some
|
246
248
|
# character-sequences are not properly re-encoded.
|
247
249
|
part.content_transfer_encoding = nil
|
248
|
-
|
249
|
-
#
|
250
|
-
|
251
|
-
|
252
|
-
part.body =
|
250
|
+
|
251
|
+
# Set the right charset on the now parsed body
|
252
|
+
new_body = lines.compact.join
|
253
|
+
part.charset = new_body.encoding.to_s
|
254
|
+
part.body = new_body
|
253
255
|
|
254
256
|
@keywords
|
255
257
|
end
|
@@ -267,20 +269,17 @@ module Mail
|
|
267
269
|
end
|
268
270
|
|
269
271
|
def add_pseudoheader(string_or_key, value=nil)
|
270
|
-
|
271
|
-
if value.present?
|
272
|
-
@dynamic_pseudoheaders << make_pseudoheader(string_or_key, value)
|
273
|
-
else
|
274
|
-
@dynamic_pseudoheaders << string_or_key.to_s
|
275
|
-
end
|
272
|
+
dynamic_pseudoheaders << make_pseudoheader(string_or_key, value)
|
276
273
|
end
|
277
274
|
|
278
275
|
def make_pseudoheader(key, value)
|
279
|
-
"#{key.to_s.camelize}: #{value.to_s}"
|
276
|
+
output = "#{key.to_s.camelize}: #{value.to_s}"
|
277
|
+
# wrap lines after 76 with 2 indents
|
278
|
+
output.gsub(/(.{1,76})( +|$)\n?/, " \\1\n").chomp.lstrip
|
280
279
|
end
|
281
280
|
|
282
281
|
def dynamic_pseudoheaders
|
283
|
-
@dynamic_pseudoheaders
|
282
|
+
@dynamic_pseudoheaders ||= []
|
284
283
|
end
|
285
284
|
|
286
285
|
def signature_state
|
@@ -331,7 +330,8 @@ module Mail
|
|
331
330
|
end
|
332
331
|
|
333
332
|
def pseudoheaders(list)
|
334
|
-
|
333
|
+
separator = '------------------------------------------------------------------------------'
|
334
|
+
(standard_pseudoheaders(list) + dynamic_pseudoheaders).flatten.join("\n") + "\n" + separator + "\n"
|
335
335
|
end
|
336
336
|
|
337
337
|
def add_msgids(list, orig)
|
@@ -346,6 +346,14 @@ module Mail
|
|
346
346
|
end
|
347
347
|
|
348
348
|
def add_list_headers(list)
|
349
|
+
if list.include_autocrypt_header
|
350
|
+
# Inject whitespaces, to let Mail break the string at these points
|
351
|
+
# leading to correct wrapping.
|
352
|
+
keydata = list.key_minimal_base64_encoded.gsub(/(.{78})/, '\1 ')
|
353
|
+
|
354
|
+
self['Autocrypt'] = "addr=#{list.email}; prefer-encrypt=mutual; keydata=#{keydata}"
|
355
|
+
end
|
356
|
+
|
349
357
|
if list.include_list_headers
|
350
358
|
self['List-Id'] = "<#{list.email.gsub('@', '.')}>"
|
351
359
|
self['List-Owner'] = "<mailto:#{list.owner_address}> (Use list's public key)"
|
@@ -1,16 +1,12 @@
|
|
1
1
|
module Schleuder
|
2
2
|
module ListPlugins
|
3
3
|
def self.attach_listkey(arguments, list, mail)
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
})
|
11
|
-
mail.attachments[filename].content_type = 'application/pgp-keys'
|
12
|
-
mail.attachments[filename].content_description = "OpenPGP public key of #{list.email}"
|
13
|
-
mail.attachments[filename].content_disposition = "attachment; filename=#{filename}"
|
4
|
+
new_part = Mail::Part.new
|
5
|
+
new_part.body = list.export_key
|
6
|
+
new_part.content_type = 'application/pgp-keys'
|
7
|
+
new_part.content_description = "OpenPGP public key of #{list.email}"
|
8
|
+
new_part.content_disposition = "attachment; filename=#{list.fingerprint}.pgpkey"
|
9
|
+
mail.add_part new_part
|
14
10
|
nil
|
15
11
|
end
|
16
12
|
end
|
@@ -104,35 +104,18 @@ module Schleuder
|
|
104
104
|
# helper methods
|
105
105
|
private
|
106
106
|
|
107
|
-
def self.is_armored_key?(material)
|
108
|
-
return false unless /^-----BEGIN PGP PUBLIC KEY BLOCK-----$/ =~ material
|
109
|
-
return false unless /^-----END PGP PUBLIC KEY BLOCK-----$/ =~ material
|
110
|
-
|
111
|
-
lines = material.split("\n").reject(&:empty?)
|
112
|
-
# remove header
|
113
|
-
lines.shift
|
114
|
-
# remove tail
|
115
|
-
lines.pop
|
116
|
-
# verify the rest
|
117
|
-
# TODO: verify length except for lasts lines?
|
118
|
-
# headers according to https://tools.ietf.org/html/rfc4880#section-6.2
|
119
|
-
lines.map do |line|
|
120
|
-
/\A((comment|version|messageid|hash|charset):.*|[0-9a-z\/=+]+)\Z/i =~ line
|
121
|
-
end.all?
|
122
|
-
end
|
123
|
-
|
124
107
|
def self.import_keys_from_attachments(list, mail)
|
125
108
|
mail.attachments.map do |attachment|
|
126
109
|
material = attachment.body.to_s
|
127
110
|
|
128
|
-
list.import_key(material)
|
111
|
+
list.import_key(material)
|
129
112
|
end
|
130
113
|
end
|
131
114
|
|
132
115
|
def self.import_key_from_body(list, mail)
|
133
116
|
key_material = mail.first_plaintext_part.body.to_s
|
134
117
|
|
135
|
-
list.import_key(key_material)
|
118
|
+
list.import_key(key_material)
|
136
119
|
end
|
137
120
|
end
|
138
121
|
end
|
@@ -56,6 +56,9 @@ module Schleuder
|
|
56
56
|
|
57
57
|
# Only continue if all recipients are still here.
|
58
58
|
if recip_map.size < arguments.size
|
59
|
+
recip_map.keys.each do |aborted_sender|
|
60
|
+
mail.add_pseudoheader(:error, I18n.t("plugins.resend.aborted", email: aborted_sender))
|
61
|
+
end
|
59
62
|
return
|
60
63
|
end
|
61
64
|
|
@@ -117,22 +120,22 @@ module Schleuder
|
|
117
120
|
Array(recipients).inject({}) do |hash, email|
|
118
121
|
keys = mail.list.keys(email)
|
119
122
|
# Exclude unusable keys.
|
120
|
-
keys.select
|
121
|
-
case
|
123
|
+
usable_keys = keys.select { |key| key.usable_for?(:encrypt) }
|
124
|
+
case usable_keys.size
|
122
125
|
when 1
|
123
|
-
hash[email] =
|
126
|
+
hash[email] = usable_keys.first
|
124
127
|
when 0
|
125
128
|
if encrypted_only
|
126
129
|
# Don't add the email to the result to exclude it from the
|
127
130
|
# recipients.
|
128
|
-
|
131
|
+
add_resend_msg(mail, email, :error, 'not_resent_no_keys', usable_keys.size, keys.size)
|
129
132
|
else
|
130
133
|
hash[email] = ''
|
131
134
|
end
|
132
135
|
else
|
133
136
|
# Always report this situation, regardless of sending or not. It's
|
134
137
|
# bad and should be fixed.
|
135
|
-
|
138
|
+
add_resend_msg(mail, email, :notice, 'not_resent_encrypted_no_keys', usable_keys.size, keys.size)
|
136
139
|
if ! encrypted_only
|
137
140
|
hash[email] = ''
|
138
141
|
end
|
@@ -152,8 +155,8 @@ module Schleuder
|
|
152
155
|
gpg_opts
|
153
156
|
end
|
154
157
|
|
155
|
-
def self.
|
156
|
-
mail.add_pseudoheader(
|
158
|
+
def self.add_resend_msg(mail, email, severity, msg, usable_keys_size, all_keys_size)
|
159
|
+
mail.add_pseudoheader(severity, I18n.t("plugins.resend.#{msg}", email: email, usable_keys: usable_keys_size, all_keys: all_keys_size))
|
157
160
|
end
|
158
161
|
|
159
162
|
def self.add_error_header(mail, recipients_map)
|
@@ -163,15 +166,15 @@ module Schleuder
|
|
163
166
|
def self.add_resent_headers(mail, recipients_map, to_or_cc, sent_encrypted)
|
164
167
|
if sent_encrypted
|
165
168
|
prefix = I18n.t('plugins.resend.encrypted_to')
|
166
|
-
str = recipients_map.map do |email, key|
|
169
|
+
str = "\n" + recipients_map.map do |email, key|
|
167
170
|
"#{email} (#{key.fingerprint})"
|
168
|
-
end.join(
|
171
|
+
end.join(",\n")
|
169
172
|
else
|
170
173
|
prefix = I18n.t('plugins.resend.unencrypted_to')
|
171
|
-
str = recipients_map.keys.join(
|
174
|
+
str = ' ' + recipients_map.keys.join(", ")
|
172
175
|
end
|
173
176
|
headername = resent_header_name(to_or_cc)
|
174
|
-
mail.add_pseudoheader(headername, "#{prefix}
|
177
|
+
mail.add_pseudoheader(headername, "#{prefix}#{str}")
|
175
178
|
end
|
176
179
|
|
177
180
|
def self.resent_header_name(to_or_cc)
|
data/lib/schleuder/runner.rb
CHANGED
@@ -5,15 +5,43 @@ module Schleuder
|
|
5
5
|
return error if error
|
6
6
|
|
7
7
|
logger.info "Parsing incoming email."
|
8
|
+
|
9
|
+
# is it valid utf-8?
|
10
|
+
msg_scrubbed = false
|
11
|
+
unless msg.valid_encoding?
|
12
|
+
logger.warn "Converting message due to invalid characters"
|
13
|
+
detection = CharlockHolmes::EncodingDetector.detect(msg)
|
14
|
+
begin
|
15
|
+
msg = CharlockHolmes::Converter.convert(msg, detection[:encoding], 'UTF-8')
|
16
|
+
rescue ArgumentError
|
17
|
+
# it looks like even icu wasn't able to convert
|
18
|
+
# so we scrub the invalid characters to be able to
|
19
|
+
# at least parse the message somehow. Though this might
|
20
|
+
# result in data loss.
|
21
|
+
logger.warn "Scrubbing message due to invalid characters"
|
22
|
+
msg = msg.scrub
|
23
|
+
msg_scrubbed = true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
8
27
|
@mail = Mail.create_message_to_list(msg, recipient, list)
|
9
28
|
|
29
|
+
if msg_scrubbed
|
30
|
+
@mail.add_pseudoheader(:note, I18n.t("pseudoheaders.scrubbed_message"))
|
31
|
+
end
|
32
|
+
|
10
33
|
error = run_filters('pre')
|
11
34
|
return error if error
|
12
35
|
|
13
36
|
begin
|
14
37
|
# This decrypts, verifies, etc.
|
15
38
|
@mail = @mail.setup
|
16
|
-
|
39
|
+
|
40
|
+
rescue GPGME::Error::BadPassphrase,
|
41
|
+
GPGME::Error::DecryptFailed,
|
42
|
+
GPGME::Error::NoData,
|
43
|
+
GPGME::Error::NoSecretKey
|
44
|
+
|
17
45
|
logger.warn "Decryption of incoming message failed."
|
18
46
|
return Errors::DecryptionFailed.new(list)
|
19
47
|
end
|
@@ -46,7 +74,7 @@ module Schleuder
|
|
46
74
|
# Subscriptions
|
47
75
|
logger.debug "Creating clean copy of message"
|
48
76
|
copy = @mail.clean_copy(list.headers_to_meta.any?)
|
49
|
-
list.send_to_subscriptions(copy)
|
77
|
+
list.send_to_subscriptions(copy, @mail)
|
50
78
|
nil
|
51
79
|
end
|
52
80
|
|
data/lib/schleuder/version.rb
CHANGED
data/locales/de.yml
CHANGED
@@ -121,7 +121,9 @@ de:
|
|
121
121
|
Oder, um einen Schlüssel per HTTP von einem Server zu laden:
|
122
122
|
X-FETCH-KEY: https://example.org/keys/mykey.asc
|
123
123
|
resend:
|
124
|
-
not_resent_no_keys: Resending an <%{email}> fehlgeschlagen (%{
|
124
|
+
not_resent_no_keys: Resending an <%{email}> fehlgeschlagen (%{all_keys} Schlüssel gefunden, davon %{usable_keys} nutzbar. Unverschlüsseltes Senden verboten).
|
125
|
+
not_resent_encrypted_no_keys: Verschlüsseltes Resending an <%{email}> fehlgeschlagen (%{all_keys} Schlüssel gefunden, davon %{usable_keys} nutzbar).
|
126
|
+
aborted: Resending an <%{email}> abgebrochen aufgrund anderer Probleme.
|
125
127
|
encrypted_to: Verschlüsselt an
|
126
128
|
unencrypted_to: Unverschlüsselt an
|
127
129
|
invalid_recipient: "Ungültige Emailadresse für resend: %{address}"
|
@@ -250,7 +252,9 @@ de:
|
|
250
252
|
fetch_key:
|
251
253
|
invalid_input: "Ungültige Angabe. Gültig sind: URLs, OpenPGP-Fingerabdrücke, oder Emailadressen."
|
252
254
|
pseudoheaders:
|
255
|
+
scrubbed_message: Diese Email enthielt ungültige Zeichen, die aus Verarbeitungsgründen möglicherweise entfernt wurden.
|
253
256
|
stripped_html_from_multialt: Diese Email enthielt einen alternativen HTML-Teil, der PGP-Daten beinhaltete. Der HTML-Teil wurde entfernt, um die Email sauberer analysieren zu können.
|
257
|
+
stripped_html_from_multialt_with_keywords: Diese Email enthielt Schlüsselwörter und einen alternativen HTML-Teil. Der HTML-Teil wurde entfernt, um zu verhindern dass diese Schlüsselwörter Aussenstehenden bekannt werden.
|
254
258
|
signature_states:
|
255
259
|
unknown: "Unbekannte Signatur von unbekanntem Schlüssel 0x%{fingerprint}"
|
256
260
|
unsigned: "Unsigniert"
|
data/locales/en.yml
CHANGED
@@ -125,7 +125,9 @@ en:
|
|
125
125
|
Or, to fetch a key keys by URL:
|
126
126
|
X-FETCH-KEY: https://example.org/keys/mykey.asc
|
127
127
|
resend:
|
128
|
-
not_resent_no_keys: Resending to <%{email}> failed (%{
|
128
|
+
not_resent_no_keys: Resending to <%{email}> failed (%{all_keys} keys found, of which %{usable_keys} can be used. Unencrypted sending not allowed).
|
129
|
+
not_resent_encrypted_no_keys: Resending as encrypted email to <%{email}> failed (%{all_keys} keys found, of which %{usable_keys} can be used).
|
130
|
+
aborted: Resending to <%{email}> aborted due to other errors.
|
129
131
|
encrypted_to: Encrypted to
|
130
132
|
unencrypted_to: Unencrypted to
|
131
133
|
invalid_recipient: "Invalid email-address for resending: %{address}"
|
@@ -254,7 +256,9 @@ en:
|
|
254
256
|
fetch_key:
|
255
257
|
invalid_input: "Invalid input. Allowed are: URLs, OpenPGP-fingerprints, or email-addresses."
|
256
258
|
pseudoheaders:
|
259
|
+
scrubbed_message: This message included invalid characters, which might have been removed to be able to process the message properly.
|
257
260
|
stripped_html_from_multialt: This message included an alternating HTML-part that contained PGP-data. The HTML-part was removed to enable parsing the message more properly.
|
261
|
+
stripped_html_from_multialt_with_keywords: This message included keywords and an alternating HTML-part. The HTML-part was removed to prevent the disclosure of these keywords to third parties.
|
258
262
|
signature_states:
|
259
263
|
unknown: "Unknown signature by unknown key 0x%{fingerprint}"
|
260
264
|
unsigned: "Unsigned"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schleuder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.3
|
4
|
+
version: 3.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- schleuder dev team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gpgme
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
version: '2.0'
|
20
20
|
- - ">="
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 2.0.
|
22
|
+
version: 2.0.19
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,21 +29,21 @@ dependencies:
|
|
29
29
|
version: '2.0'
|
30
30
|
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 2.0.
|
32
|
+
version: 2.0.19
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: mail
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: 2.
|
39
|
+
version: 2.7.1
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 2.
|
46
|
+
version: 2.7.1
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: mail-gpg
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -51,9 +51,9 @@ dependencies:
|
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: '0.3'
|
54
|
-
- - "
|
54
|
+
- - "<"
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: 0.
|
56
|
+
version: 0.4.3
|
57
57
|
type: :runtime
|
58
58
|
prerelease: false
|
59
59
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -61,9 +61,9 @@ dependencies:
|
|
61
61
|
- - "~>"
|
62
62
|
- !ruby/object:Gem::Version
|
63
63
|
version: '0.3'
|
64
|
-
- - "
|
64
|
+
- - "<"
|
65
65
|
- !ruby/object:Gem::Version
|
66
|
-
version: 0.
|
66
|
+
version: 0.4.3
|
67
67
|
- !ruby/object:Gem::Dependency
|
68
68
|
name: activerecord
|
69
69
|
requirement: !ruby/object:Gem::Requirement
|
@@ -112,14 +112,14 @@ dependencies:
|
|
112
112
|
requirements:
|
113
113
|
- - "~>"
|
114
114
|
- !ruby/object:Gem::Version
|
115
|
-
version:
|
115
|
+
version: 1.3.6
|
116
116
|
type: :runtime
|
117
117
|
prerelease: false
|
118
118
|
version_requirements: !ruby/object:Gem::Requirement
|
119
119
|
requirements:
|
120
120
|
- - "~>"
|
121
121
|
- !ruby/object:Gem::Version
|
122
|
-
version:
|
122
|
+
version: 1.3.6
|
123
123
|
- !ruby/object:Gem::Dependency
|
124
124
|
name: sinatra
|
125
125
|
requirement: !ruby/object:Gem::Requirement
|
@@ -176,6 +176,20 @@ dependencies:
|
|
176
176
|
- - "~>"
|
177
177
|
- !ruby/object:Gem::Version
|
178
178
|
version: '1'
|
179
|
+
- !ruby/object:Gem::Dependency
|
180
|
+
name: charlock_holmes
|
181
|
+
requirement: !ruby/object:Gem::Requirement
|
182
|
+
requirements:
|
183
|
+
- - "~>"
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: 0.7.6
|
186
|
+
type: :runtime
|
187
|
+
prerelease: false
|
188
|
+
version_requirements: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - "~>"
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: 0.7.6
|
179
193
|
- !ruby/object:Gem::Dependency
|
180
194
|
name: rspec
|
181
195
|
requirement: !ruby/object:Gem::Requirement
|
@@ -249,7 +263,7 @@ dependencies:
|
|
249
263
|
description: |-
|
250
264
|
Schleuder is a group's email-gateway: subscribers can exchange encrypted emails among themselves, receive emails from non-subscribers and send emails to non-subscribers via the list.
|
251
265
|
|
252
|
-
(Please note: For some platforms there's a better way of installing Schleuder than `gem install`. See <https://schleuder.org/docs
|
266
|
+
(Please note: For some platforms there's a better way of installing Schleuder than `gem install`. See <https://schleuder.org/schleuder/docs/server-admins.html#installation> for details.)
|
253
267
|
email: team@schleuder.org
|
254
268
|
executables:
|
255
269
|
- schleuder
|
@@ -275,6 +289,8 @@ files:
|
|
275
289
|
- db/migrate/20160501172700_fix_headers_to_meta_defaults.rb
|
276
290
|
- db/migrate/20170713215059_add_internal_footer_to_list.rb
|
277
291
|
- db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb
|
292
|
+
- db/migrate/20180723173900_add_deliver_selfsent_to_list.rb
|
293
|
+
- db/migrate/20190906194820_add_autocrypt_header_to_list.rb
|
278
294
|
- db/schema.rb
|
279
295
|
- etc/init.d/schleuder-api-daemon
|
280
296
|
- etc/list-defaults.yml
|
@@ -323,6 +339,7 @@ files:
|
|
323
339
|
- lib/schleuder/filters/post_decryption/60_receive_signed_only.rb
|
324
340
|
- lib/schleuder/filters/post_decryption/70_receive_encrypted_only.rb
|
325
341
|
- lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb
|
342
|
+
- lib/schleuder/filters/post_decryption/90_strip_html_from_alternative_if_keywords_present.rb
|
326
343
|
- lib/schleuder/filters/pre_decryption/10_forward_bounce_to_admins.rb
|
327
344
|
- lib/schleuder/filters/pre_decryption/20_forward_all_incoming_to_admins.rb
|
328
345
|
- lib/schleuder/filters/pre_decryption/30_send_key.rb
|
@@ -390,8 +407,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
390
407
|
- !ruby/object:Gem::Version
|
391
408
|
version: '0'
|
392
409
|
requirements: []
|
393
|
-
rubyforge_project:
|
394
|
-
rubygems_version: 2.7.
|
410
|
+
rubyforge_project:
|
411
|
+
rubygems_version: 2.7.6.2
|
395
412
|
signing_key:
|
396
413
|
specification_version: 4
|
397
414
|
summary: Schleuder is a gpg-enabled mailing list manager with remailing-capabilities.
|