schleuder 3.2.2 → 3.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +21 -11
- data/Rakefile +18 -10
- data/bin/schleuder +2 -1
- data/bin/schleuder-api-daemon +3 -2
- data/db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb +30 -0
- 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 +4 -2
- data/etc/list-defaults.yml +13 -3
- data/etc/schleuder.yml +11 -0
- data/lib/schleuder-api-daemon.rb +9 -354
- data/lib/schleuder-api-daemon/helpers/schleuder-api-daemon-helper.rb +143 -0
- data/lib/schleuder-api-daemon/routes/key.rb +40 -0
- data/lib/schleuder-api-daemon/routes/list.rb +69 -0
- data/lib/schleuder-api-daemon/routes/status.rb +5 -0
- data/lib/schleuder-api-daemon/routes/subscription.rb +99 -0
- data/lib/schleuder-api-daemon/routes/version.rb +5 -0
- data/lib/schleuder.rb +12 -3
- data/lib/schleuder/cli.rb +33 -3
- data/lib/schleuder/cli/subcommand_fix.rb +1 -1
- data/lib/schleuder/conf.rb +7 -1
- data/lib/schleuder/errors/active_model_error.rb +2 -5
- data/lib/schleuder/errors/decryption_failed.rb +2 -7
- data/lib/schleuder/errors/key_adduid_failed.rb +1 -5
- data/lib/schleuder/errors/key_generation_failed.rb +1 -8
- data/lib/schleuder/errors/keyword_admin_only.rb +1 -5
- data/lib/schleuder/errors/list_not_found.rb +1 -5
- data/lib/schleuder/errors/listdir_problem.rb +2 -7
- data/lib/schleuder/errors/loading_list_settings_failed.rb +2 -5
- data/lib/schleuder/errors/message_empty.rb +1 -5
- data/lib/schleuder/errors/message_not_from_admin.rb +2 -5
- data/lib/schleuder/errors/message_sender_not_subscribed.rb +2 -5
- data/lib/schleuder/errors/message_too_big.rb +2 -5
- data/lib/schleuder/errors/message_unauthenticated.rb +1 -4
- data/lib/schleuder/errors/message_unencrypted.rb +2 -5
- data/lib/schleuder/errors/message_unsigned.rb +2 -5
- data/lib/schleuder/errors/too_many_keys.rb +1 -8
- data/lib/schleuder/filters/{request_filter.rb → post_decryption/10_request.rb} +0 -0
- data/lib/schleuder/filters/{max_message_size.rb → post_decryption/20_max_message_size.rb} +0 -0
- data/lib/schleuder/filters/{forward_filter.rb → post_decryption/30_forward_to_owner.rb} +0 -0
- data/lib/schleuder/filters/post_decryption/40_receive_admin_only.rb +10 -0
- data/lib/schleuder/filters/post_decryption/50_receive_authenticated_only.rb +10 -0
- data/lib/schleuder/filters/post_decryption/60_receive_signed_only.rb +10 -0
- data/lib/schleuder/filters/post_decryption/70_receive_encrypted_only.rb +10 -0
- data/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb +10 -0
- data/lib/schleuder/filters/post_decryption/90_strip_html_from_alternative_if_keywords_present.rb +21 -0
- data/lib/schleuder/filters/{bounces_filter.rb → pre_decryption/10_forward_bounce_to_admins.rb} +0 -0
- data/lib/schleuder/filters/{forward_incoming.rb → pre_decryption/20_forward_all_incoming_to_admins.rb} +0 -0
- data/lib/schleuder/filters/{send_key_filter.rb → pre_decryption/30_send_key.rb} +0 -0
- data/lib/schleuder/filters/{hotmail_message_filter.rb → pre_decryption/40_fix_exchange_messages.rb} +5 -3
- data/lib/schleuder/filters/{strip_alternative_filter.rb → pre_decryption/50_strip_html_from_alternative.rb} +1 -1
- data/lib/schleuder/filters_runner.rb +41 -31
- data/lib/schleuder/gpgme/ctx.rb +24 -3
- data/lib/schleuder/gpgme/import_status.rb +13 -7
- data/lib/schleuder/gpgme/key.rb +8 -0
- data/lib/schleuder/list.rb +26 -4
- data/lib/schleuder/logger_notifications.rb +8 -1
- data/lib/schleuder/mail/encrypted_part.rb +14 -0
- data/lib/schleuder/mail/gpg.rb +15 -0
- data/lib/schleuder/mail/message.rb +97 -49
- data/lib/schleuder/plugins/attach_listkey.rb +6 -10
- data/lib/schleuder/plugins/key_management.rb +34 -26
- data/lib/schleuder/plugins/resend.rb +14 -11
- data/lib/schleuder/plugins/subscription_management.rb +70 -3
- data/lib/schleuder/runner.rb +49 -10
- data/lib/schleuder/subscription.rb +5 -9
- data/lib/schleuder/validators/fingerprint_validator.rb +1 -1
- data/lib/schleuder/version.rb +1 -1
- data/locales/de.yml +101 -9
- data/locales/en.yml +107 -11
- metadata +72 -34
- data/lib/schleuder/errors/file_not_found.rb +0 -14
- data/lib/schleuder/errors/invalid_listname.rb +0 -13
- data/lib/schleuder/errors/list_exists.rb +0 -13
- data/lib/schleuder/errors/unknown_list_option.rb +0 -14
- data/lib/schleuder/filters/auth_filter.rb +0 -39
data/lib/schleuder/gpgme/ctx.rb
CHANGED
@@ -19,7 +19,7 @@ module GPGME
|
|
19
19
|
case import_result.imports.size
|
20
20
|
when 1
|
21
21
|
import_status = import_result.imports.first
|
22
|
-
if import_status.action == '
|
22
|
+
if import_status.action == 'error'
|
23
23
|
[nil, "Key #{import_status.fpr} could not be imported!"]
|
24
24
|
else
|
25
25
|
[import_status.fpr, nil]
|
@@ -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
|
@@ -2,17 +2,23 @@ module GPGME
|
|
2
2
|
class ImportStatus
|
3
3
|
attr_reader :action
|
4
4
|
|
5
|
-
# Unfortunately in initialize() @status and @result are not yet
|
5
|
+
# Unfortunately in initialize() @status and @result are not yet initialized.
|
6
6
|
def set_action
|
7
|
-
@action ||= if self.
|
8
|
-
'imported'
|
9
|
-
elsif self.result == 0
|
10
|
-
'unchanged'
|
11
|
-
else
|
7
|
+
@action ||= if self.result > 0
|
12
8
|
# An error happened.
|
13
9
|
# TODO: Give details by going through the list of errors in
|
14
10
|
# "gpg-errors.h" and find out which is present here.
|
15
|
-
'
|
11
|
+
'error'
|
12
|
+
else
|
13
|
+
# TODO: refactor with Ctx#translate_import_data
|
14
|
+
case self.status
|
15
|
+
when 0
|
16
|
+
'unchanged'
|
17
|
+
when IMPORT_NEW
|
18
|
+
'imported'
|
19
|
+
else
|
20
|
+
'updated'
|
21
|
+
end
|
16
22
|
end
|
17
23
|
self
|
18
24
|
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
|
@@ -208,5 +212,9 @@ module GPGME
|
|
208
212
|
pinentry = File.join(ENV['SCHLEUDER_ROOT'], 'bin', 'pinentry-clearpassphrase')
|
209
213
|
GPGME::Ctx.spawn_daemon('gpg-agent', "--use-standard-socket --pinentry-program #{pinentry}")
|
210
214
|
end
|
215
|
+
|
216
|
+
def self.valid_fingerprint?(fp)
|
217
|
+
fp =~ Schleuder::Conf::FINGERPRINT_REGEXP
|
218
|
+
end
|
211
219
|
end
|
212
220
|
end
|
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,
|
@@ -124,7 +125,7 @@ module Schleuder
|
|
124
125
|
|
125
126
|
def import_key_and_find_fingerprint(key_material)
|
126
127
|
return nil if key_material.blank?
|
127
|
-
|
128
|
+
|
128
129
|
import_result = import_key(key_material)
|
129
130
|
gpg.interpret_import_result(import_result)
|
130
131
|
end
|
@@ -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
|
@@ -251,9 +262,8 @@ module Schleuder
|
|
251
262
|
end
|
252
263
|
|
253
264
|
def fingerprint=(arg)
|
254
|
-
# Strip whitespace from incoming arg.
|
255
265
|
if arg
|
256
|
-
write_attribute(:fingerprint, arg.gsub(/\s*/, '').chomp)
|
266
|
+
write_attribute(:fingerprint, arg.gsub(/\s*/, '').gsub(/^0x/, '').chomp.upcase)
|
257
267
|
end
|
258
268
|
end
|
259
269
|
|
@@ -341,12 +351,24 @@ module Schleuder
|
|
341
351
|
true
|
342
352
|
end
|
343
353
|
|
344
|
-
def send_to_subscriptions(mail)
|
354
|
+
def send_to_subscriptions(mail, incoming_mail=nil)
|
345
355
|
logger.debug "Sending to subscriptions."
|
346
356
|
mail.add_internal_footer!
|
347
357
|
self.subscriptions.each do |subscription|
|
348
358
|
begin
|
359
|
+
|
360
|
+
if ! subscription.delivery_enabled
|
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
|
368
|
+
end
|
369
|
+
|
349
370
|
subscription.send_mail(mail)
|
371
|
+
|
350
372
|
rescue => exc
|
351
373
|
msg = I18n.t('errors.delivery_error',
|
352
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
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Mail
|
2
|
+
module Gpg
|
3
|
+
class EncryptedPart < Mail::Part
|
4
|
+
alias_method :initialize_mailgpg, :initialize
|
5
|
+
|
6
|
+
def initialize(cleartext_mail, options = {})
|
7
|
+
if cleartext_mail.protected_headers_subject
|
8
|
+
cleartext_mail.content_type_parameters['protected-headers'] = 'v1'
|
9
|
+
end
|
10
|
+
initialize_mailgpg(cleartext_mail, options)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Mail
|
2
|
+
module Gpg
|
3
|
+
class << self
|
4
|
+
alias_method :encrypt_mailgpg, :encrypt
|
5
|
+
|
6
|
+
def encrypt(cleartext_mail, options={})
|
7
|
+
encrypted_mail = encrypt_mailgpg(cleartext_mail, options)
|
8
|
+
if cleartext_mail.protected_headers_subject
|
9
|
+
encrypted_mail.subject = cleartext_mail.protected_headers_subject
|
10
|
+
end
|
11
|
+
encrypted_mail
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -16,6 +16,8 @@ module Mail
|
|
16
16
|
attr_accessor :recipient
|
17
17
|
attr_accessor :original_message
|
18
18
|
attr_accessor :list
|
19
|
+
attr_accessor :protected_headers_subject
|
20
|
+
attr_writer :dynamic_pseudoheaders
|
19
21
|
|
20
22
|
# TODO: This should be in initialize(), but I couldn't understand the
|
21
23
|
# strange errors about wrong number of arguments when overriding
|
@@ -23,12 +25,9 @@ module Mail
|
|
23
25
|
def setup
|
24
26
|
if self.encrypted?
|
25
27
|
new = self.decrypt(verify: true)
|
26
|
-
## Work around a bug in mail-gpg: when decrypting pgp/mime the
|
27
|
-
## Date-header is not copied.
|
28
|
-
#new.date ||= self.date
|
29
28
|
# Test if there's a signed multipart inside the ciphertext
|
30
29
|
# ("encapsulated" format of pgp/mime).
|
31
|
-
if new
|
30
|
+
if encapsulated_signed?(new)
|
32
31
|
new = new.verify
|
33
32
|
end
|
34
33
|
elsif self.signed?
|
@@ -46,9 +45,20 @@ module Mail
|
|
46
45
|
# might be gone (e.g. request-keywords that delete subscriptions or
|
47
46
|
# keys).
|
48
47
|
new.signer
|
49
|
-
self.dynamic_pseudoheaders.
|
50
|
-
|
48
|
+
new.dynamic_pseudoheaders = self.dynamic_pseudoheaders.dup
|
49
|
+
|
50
|
+
# Store previously protected subject for later access.
|
51
|
+
# mail-gpg pulls headers from the decrypted mime parts "up" into the main
|
52
|
+
# headers, which reveals protected subjects.
|
53
|
+
if self.subject != new.subject
|
54
|
+
new.protected_headers_subject = self.subject.dup
|
55
|
+
end
|
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
|
51
60
|
end
|
61
|
+
|
52
62
|
new
|
53
63
|
end
|
54
64
|
|
@@ -58,6 +68,7 @@ module Mail
|
|
58
68
|
clean.gpg self.list.gpg_sign_options
|
59
69
|
clean.from = list.email
|
60
70
|
clean.subject = self.subject
|
71
|
+
clean.protected_headers_subject = self.protected_headers_subject
|
61
72
|
|
62
73
|
clean.add_msgids(list, self)
|
63
74
|
clean.add_list_headers(list)
|
@@ -69,6 +80,13 @@ module Mail
|
|
69
80
|
clean.add_part new_part
|
70
81
|
end
|
71
82
|
|
83
|
+
if self.protected_headers_subject.present?
|
84
|
+
new_part = Mail::Part.new
|
85
|
+
new_part.content_type = "text/rfc822-headers; protected-headers=v1"
|
86
|
+
new_part.body = "Subject: #{self.subject}\n"
|
87
|
+
clean.add_part new_part
|
88
|
+
end
|
89
|
+
|
72
90
|
# Attach body or mime-parts in a new wrapper-part, to preserve the
|
73
91
|
# original mime-structure.
|
74
92
|
# We can't use self.to_s here — that includes all the headers we *don't*
|
@@ -187,11 +205,15 @@ module Mail
|
|
187
205
|
@recipient.match(/-bounce@/).present? ||
|
188
206
|
# Empty Return-Path
|
189
207
|
self.return_path.to_s == '<>' ||
|
190
|
-
# Auto-Submitted exists and does not equal 'no' and
|
191
|
-
#
|
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.
|
192
212
|
( self['Auto-Submitted'].present? && \
|
193
213
|
self['Auto-Submitted'].to_s.downcase != 'no' && \
|
194
|
-
!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/)
|
195
217
|
end
|
196
218
|
|
197
219
|
def keywords
|
@@ -203,15 +225,19 @@ module Mail
|
|
203
225
|
end
|
204
226
|
|
205
227
|
@keywords = []
|
228
|
+
look_for_keywords = true
|
206
229
|
lines = part.decoded.lines.map do |line|
|
207
230
|
# TODO: Find multiline arguments (add-key). Currently add-key has to
|
208
231
|
# read the whole body and hope for the best.
|
209
|
-
if line.match(/^x-([^:\s]*)[:\s]*(.*)/i)
|
210
|
-
command =
|
211
|
-
arguments =
|
232
|
+
if look_for_keywords && (m = line.match(/^x-([^:\s]*)[:\s]*(.*)/i))
|
233
|
+
command = m[1].strip.downcase
|
234
|
+
arguments = m[2].to_s.strip.downcase.split(/[,; ]{1,}/)
|
212
235
|
@keywords << [command, arguments]
|
213
236
|
nil
|
214
237
|
else
|
238
|
+
if look_for_keywords && line.match(/\S+/i)
|
239
|
+
look_for_keywords = false
|
240
|
+
end
|
215
241
|
line
|
216
242
|
end
|
217
243
|
end
|
@@ -221,11 +247,11 @@ module Mail
|
|
221
247
|
# decide itself how to encode, it works. If we don't, some
|
222
248
|
# character-sequences are not properly re-encoded.
|
223
249
|
part.content_transfer_encoding = nil
|
224
|
-
|
225
|
-
#
|
226
|
-
|
227
|
-
|
228
|
-
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
|
229
255
|
|
230
256
|
@keywords
|
231
257
|
end
|
@@ -243,33 +269,20 @@ module Mail
|
|
243
269
|
end
|
244
270
|
|
245
271
|
def add_pseudoheader(string_or_key, value=nil)
|
246
|
-
|
247
|
-
if value.present?
|
248
|
-
@dynamic_pseudoheaders << make_pseudoheader(string_or_key, value)
|
249
|
-
else
|
250
|
-
@dynamic_pseudoheaders << string_or_key.to_s
|
251
|
-
end
|
272
|
+
dynamic_pseudoheaders << make_pseudoheader(string_or_key, value)
|
252
273
|
end
|
253
274
|
|
254
275
|
def make_pseudoheader(key, value)
|
255
|
-
"#{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
|
256
279
|
end
|
257
280
|
|
258
281
|
def dynamic_pseudoheaders
|
259
|
-
@dynamic_pseudoheaders
|
282
|
+
@dynamic_pseudoheaders ||= []
|
260
283
|
end
|
261
284
|
|
262
|
-
def
|
263
|
-
if @standard_pseudoheaders.present?
|
264
|
-
return @standard_pseudoheaders
|
265
|
-
else
|
266
|
-
@standard_pseudoheaders = []
|
267
|
-
end
|
268
|
-
|
269
|
-
Array(list.headers_to_meta).each do |field|
|
270
|
-
@standard_pseudoheaders << make_pseudoheader(field.to_s, self.header[field.to_s])
|
271
|
-
end
|
272
|
-
|
285
|
+
def signature_state
|
273
286
|
# Careful to add information about the incoming signature. GPGME
|
274
287
|
# throws exceptions if it doesn't know the key.
|
275
288
|
if self.signature.present?
|
@@ -277,28 +290,48 @@ module Mail
|
|
277
290
|
# for that manually and provide our own fallback. (Calling
|
278
291
|
# `signature.key` results in an EOFError in that case.)
|
279
292
|
if signing_key.present?
|
280
|
-
|
293
|
+
signature_state = signature.to_s
|
281
294
|
else
|
282
|
-
|
283
|
-
msg = "Unknown signature by unknown key 0x#{self.signature.fingerprint}"
|
295
|
+
signature_state = I18n.t("signature_states.unknown", fingerprint: self.signature.fingerprint)
|
284
296
|
end
|
285
297
|
else
|
286
|
-
|
287
|
-
|
298
|
+
signature_state = I18n.t("signature_states.unsigned")
|
299
|
+
end
|
300
|
+
signature_state
|
301
|
+
end
|
302
|
+
|
303
|
+
def encryption_state
|
304
|
+
if was_encrypted?
|
305
|
+
encryption_state = I18n.t("encryption_states.encrypted")
|
306
|
+
else
|
307
|
+
encryption_state = I18n.t("encryption_states.unencrypted")
|
308
|
+
end
|
309
|
+
encryption_state
|
310
|
+
end
|
311
|
+
|
312
|
+
def standard_pseudoheaders(list)
|
313
|
+
if @standard_pseudoheaders.present?
|
314
|
+
return @standard_pseudoheaders
|
315
|
+
else
|
316
|
+
@standard_pseudoheaders = []
|
317
|
+
end
|
318
|
+
|
319
|
+
Array(list.headers_to_meta).each do |field|
|
320
|
+
value = case field.to_s
|
321
|
+
when 'sig' then signature_state
|
322
|
+
when 'enc' then encryption_state
|
323
|
+
else self.header[field.to_s]
|
324
|
+
end
|
325
|
+
@standard_pseudoheaders << make_pseudoheader(field.to_s, value)
|
288
326
|
end
|
289
|
-
@standard_pseudoheaders << make_pseudoheader(:sig, msg)
|
290
327
|
|
291
|
-
# TODO: I18n
|
292
|
-
@standard_pseudoheaders << make_pseudoheader(
|
293
|
-
:enc,
|
294
|
-
was_encrypted? ? 'Encrypted' : 'Unencrypted'
|
295
|
-
)
|
296
328
|
|
297
329
|
@standard_pseudoheaders
|
298
330
|
end
|
299
331
|
|
300
332
|
def pseudoheaders(list)
|
301
|
-
|
333
|
+
separator = '------------------------------------------------------------------------------'
|
334
|
+
(standard_pseudoheaders(list) + dynamic_pseudoheaders).flatten.join("\n") + "\n" + separator + "\n"
|
302
335
|
end
|
303
336
|
|
304
337
|
def add_msgids(list, orig)
|
@@ -313,10 +346,18 @@ module Mail
|
|
313
346
|
end
|
314
347
|
|
315
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
|
+
|
316
357
|
if list.include_list_headers
|
317
358
|
self['List-Id'] = "<#{list.email.gsub('@', '.')}>"
|
318
359
|
self['List-Owner'] = "<mailto:#{list.owner_address}> (Use list's public key)"
|
319
|
-
self['List-Help'] = '<https://schleuder.
|
360
|
+
self['List-Help'] = '<https://schleuder.org/>'
|
320
361
|
|
321
362
|
postmsg = if list.receive_admin_only
|
322
363
|
"NO (Admins only)"
|
@@ -410,6 +451,13 @@ module Mail
|
|
410
451
|
|
411
452
|
private
|
412
453
|
|
454
|
+
# mail.signed? throws an error if it finds
|
455
|
+
# pgp boundaries, so we must use the Mail::Gpg
|
456
|
+
# methods.
|
457
|
+
def encapsulated_signed?(mail)
|
458
|
+
(mail.verify_result.nil? || mail.verify_result.signatures.empty?) && \
|
459
|
+
(Mail::Gpg.signed_mime?(mail) || Mail::Gpg.signed_inline?(mail))
|
460
|
+
end
|
413
461
|
|
414
462
|
def add_footer!(footer_attribute)
|
415
463
|
if self.list.blank? || self.list.send(footer_attribute).to_s.empty?
|