schleuder 4.0.2 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -9
- data/db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb +1 -1
- data/db/migrate/20211106112020_change_boolean_values_to_integers.rb +46 -0
- data/db/migrate/20211107151309_add_limits_to_string_columns.rb +28 -0
- data/db/migrate/20220910170110_add_key_auto_import_from_email.rb +11 -0
- data/db/schema.rb +16 -16
- data/etc/list-defaults.yml +16 -0
- data/etc/schleuder.yml +29 -11
- data/lib/schleuder/cli.rb +15 -2
- data/lib/schleuder/conf.rb +23 -3
- data/lib/schleuder/email_key_importer.rb +91 -0
- data/lib/schleuder/filters/post_decryption/35_key_auto_import_from_attachments.rb +21 -0
- data/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb +1 -1
- data/lib/schleuder/filters/post_decryption/90_strip_html_from_alternative_if_keywords_present.rb +36 -4
- data/lib/schleuder/filters/pre_decryption/60_key_auto_import_from_autocrypt_header.rb +9 -0
- data/lib/schleuder/filters_runner.rb +1 -30
- data/lib/schleuder/gpgme/ctx.rb +34 -93
- data/lib/schleuder/gpgme/key.rb +1 -1
- data/lib/schleuder/gpgme/key_extractor.rb +30 -0
- data/lib/schleuder/http.rb +56 -0
- data/lib/schleuder/key_fetcher.rb +89 -0
- data/lib/schleuder/keyword_handlers/key_management.rb +2 -2
- data/lib/schleuder/keyword_handlers/subscription_management.rb +19 -3
- data/lib/schleuder/list.rb +26 -10
- data/lib/schleuder/list_builder.rb +1 -1
- data/lib/schleuder/logger.rb +1 -1
- data/lib/schleuder/mail/gpg/decrypted_part.rb +20 -0
- data/lib/schleuder/mail/gpg/delivery_handler.rb +38 -0
- data/lib/schleuder/mail/gpg/encrypted_part.rb +29 -5
- data/lib/schleuder/mail/gpg/gpgme_ext.rb +8 -0
- data/lib/schleuder/mail/gpg/gpgme_helper.rb +155 -0
- data/lib/schleuder/mail/gpg/inline_decrypted_message.rb +82 -0
- data/lib/schleuder/mail/gpg/inline_signed_message.rb +73 -0
- data/lib/schleuder/mail/gpg/mime_signed_message.rb +28 -0
- data/lib/schleuder/mail/gpg/missing_keys_error.rb +6 -0
- data/lib/schleuder/mail/gpg/sign_part.rb +19 -9
- data/lib/schleuder/mail/gpg/signed_part.rb +37 -0
- data/lib/schleuder/mail/gpg/verified_part.rb +10 -0
- data/lib/schleuder/mail/gpg/verify_result_attribute.rb +32 -0
- data/lib/schleuder/mail/gpg/version_part.rb +22 -0
- data/lib/schleuder/mail/gpg.rb +236 -7
- data/lib/schleuder/mail/message.rb +98 -14
- data/lib/schleuder/runner.rb +40 -10
- data/lib/schleuder/sks_client.rb +18 -0
- data/lib/schleuder/version.rb +1 -1
- data/lib/schleuder/vks_client.rb +24 -0
- data/lib/schleuder-api-daemon/routes/key.rb +22 -1
- data/lib/schleuder.rb +11 -7
- data/locales/de.yml +38 -19
- data/locales/en.yml +22 -3
- metadata +58 -21
data/lib/schleuder/mail/gpg.rb
CHANGED
@@ -1,15 +1,244 @@
|
|
1
|
+
require 'schleuder/mail/gpg/missing_keys_error'
|
2
|
+
require 'schleuder/mail/gpg/version_part'
|
3
|
+
require 'schleuder/mail/gpg/decrypted_part'
|
4
|
+
require 'schleuder/mail/gpg/encrypted_part'
|
5
|
+
require 'schleuder/mail/gpg/inline_decrypted_message'
|
6
|
+
require 'schleuder/mail/gpg/gpgme_helper'
|
7
|
+
require 'schleuder/mail/gpg/signed_part'
|
8
|
+
require 'schleuder/mail/gpg/mime_signed_message'
|
9
|
+
require 'schleuder/mail/gpg/inline_signed_message'
|
10
|
+
|
1
11
|
module Mail
|
2
12
|
module Gpg
|
3
|
-
|
4
|
-
|
13
|
+
BEGIN_PGP_MESSAGE_MARKER = /^-----BEGIN PGP MESSAGE-----/
|
14
|
+
BEGIN_PGP_SIGNED_MESSAGE_MARKER = /^-----BEGIN PGP SIGNED MESSAGE-----/
|
15
|
+
|
16
|
+
# options are:
|
17
|
+
# :sign: sign message using the sender's private key
|
18
|
+
# :sign_as: sign using this key (give the corresponding email address or key fingerprint)
|
19
|
+
# :password: passphrase for the signing key
|
20
|
+
# :keys: A hash mapping recipient email addresses to public keys or public
|
21
|
+
# key ids. Imports any keys given here that are not already part of the
|
22
|
+
# local keychain before sending the mail.
|
23
|
+
# :always_trust: send encrypted mail to untrusted receivers, true by default
|
24
|
+
def self.encrypt(cleartext_mail, options = {})
|
25
|
+
construct_mail(cleartext_mail, options) do
|
26
|
+
receivers = []
|
27
|
+
receivers += cleartext_mail.to if cleartext_mail.to
|
28
|
+
receivers += cleartext_mail.cc if cleartext_mail.cc
|
29
|
+
receivers += cleartext_mail.bcc if cleartext_mail.bcc
|
30
|
+
|
31
|
+
if options[:sign_as]
|
32
|
+
options[:sign] = true
|
33
|
+
options[:signers] = options.delete(:sign_as)
|
34
|
+
elsif options[:sign]
|
35
|
+
options[:signers] = cleartext_mail.from
|
36
|
+
end
|
37
|
+
|
38
|
+
add_part VersionPart.new
|
39
|
+
add_part EncryptedPart.new(cleartext_mail,
|
40
|
+
options.merge({recipients: receivers}))
|
41
|
+
content_type "multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=#{boundary}"
|
42
|
+
body.preamble = options[:preamble] || 'This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)'
|
5
43
|
|
6
|
-
def encrypt(cleartext_mail, options={})
|
7
|
-
encrypted_mail = encrypt_mailgpg(cleartext_mail, options)
|
8
44
|
if cleartext_mail.protected_headers_subject
|
9
|
-
|
45
|
+
subject cleartext_mail.protected_headers_subject
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.sign(cleartext_mail, options = {})
|
51
|
+
options[:sign_as] ||= cleartext_mail.from
|
52
|
+
construct_mail(cleartext_mail, options) do
|
53
|
+
to_be_signed = SignedPart.build(cleartext_mail)
|
54
|
+
add_part to_be_signed
|
55
|
+
add_part to_be_signed.sign(options)
|
56
|
+
|
57
|
+
content_type "multipart/signed; micalg=pgp-sha1; protocol=\"application/pgp-signature\"; boundary=#{boundary}"
|
58
|
+
body.preamble = options[:preamble] || 'This is an OpenPGP/MIME signed message (RFC 4880 and 3156)'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# options are:
|
63
|
+
# :verify: decrypt and verify
|
64
|
+
def self.decrypt(encrypted_mail, options = {})
|
65
|
+
if encrypted_mime?(encrypted_mail)
|
66
|
+
decrypt_pgp_mime(encrypted_mail, options)
|
67
|
+
elsif encrypted_inline?(encrypted_mail)
|
68
|
+
decrypt_pgp_inline(encrypted_mail, options)
|
69
|
+
else
|
70
|
+
raise EncodingError.new("Unsupported encryption format '#{encrypted_mail.content_type}'")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.signature_valid?(signed_mail, options = {})
|
75
|
+
if signed_mime?(signed_mail)
|
76
|
+
signature_valid_pgp_mime?(signed_mail, options)
|
77
|
+
elsif signed_inline?(signed_mail)
|
78
|
+
signature_valid_inline?(signed_mail, options)
|
79
|
+
else
|
80
|
+
raise EncodingError.new("Unsupported signature format '#{signed_mail.content_type}'")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# true if a mail is encrypted
|
85
|
+
def self.encrypted?(mail)
|
86
|
+
return true if encrypted_mime?(mail)
|
87
|
+
return true if encrypted_inline?(mail)
|
88
|
+
false
|
89
|
+
end
|
90
|
+
|
91
|
+
# true if a mail is signed.
|
92
|
+
#
|
93
|
+
# throws EncodingError if called on an encrypted mail (so only call this method if encrypted? is false)
|
94
|
+
def self.signed?(mail)
|
95
|
+
return true if signed_mime?(mail)
|
96
|
+
return true if signed_inline?(mail)
|
97
|
+
if encrypted?(mail)
|
98
|
+
raise EncodingError.new('Unable to determine signature on an encrypted mail, use :verify option on decrypt()')
|
99
|
+
end
|
100
|
+
false
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.construct_mail(cleartext_mail, options, &block)
|
104
|
+
Mail.new do
|
105
|
+
self.perform_deliveries = cleartext_mail.perform_deliveries
|
106
|
+
Mail::Gpg.copy_headers cleartext_mail, self
|
107
|
+
# necessary?
|
108
|
+
if cleartext_mail.message_id
|
109
|
+
header['Message-ID'] = cleartext_mail['Message-ID'].value
|
110
|
+
end
|
111
|
+
instance_eval &block
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# decrypts PGP/MIME (RFC 3156, section 4) encrypted mail
|
116
|
+
def self.decrypt_pgp_mime(encrypted_mail, options)
|
117
|
+
if encrypted_mail.parts.length < 2
|
118
|
+
raise EncodingError.new("RFC 3156 mandates exactly two body parts, found '#{encrypted_mail.parts.length}'")
|
119
|
+
end
|
120
|
+
if !VersionPart.is_version_part? encrypted_mail.parts[0]
|
121
|
+
raise EncodingError.new("RFC 3156 first part not a valid version part '#{encrypted_mail.parts[0]}'")
|
122
|
+
end
|
123
|
+
decrypted = DecryptedPart.new(encrypted_mail.parts[1], options)
|
124
|
+
Mail.new(decrypted.raw_source) do
|
125
|
+
# headers from the encrypted part (set by the initializer above) take
|
126
|
+
# precedence over those from the outer mail.
|
127
|
+
Mail::Gpg.copy_headers encrypted_mail, self, overwrite: false
|
128
|
+
verify_result decrypted.verify_result if options[:verify]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# decrypts inline PGP encrypted mail
|
133
|
+
def self.decrypt_pgp_inline(encrypted_mail, options)
|
134
|
+
InlineDecryptedMessage.setup(encrypted_mail, options)
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.verify(signed_mail, options = {})
|
138
|
+
if signed_mime?(signed_mail)
|
139
|
+
Mail::Gpg::MimeSignedMessage.setup signed_mail, options
|
140
|
+
elsif signed_inline?(signed_mail)
|
141
|
+
Mail::Gpg::InlineSignedMessage.setup signed_mail, options
|
142
|
+
else
|
143
|
+
signed_mail
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# check signature for PGP/MIME (RFC 3156, section 5) signed mail
|
148
|
+
def self.signature_valid_pgp_mime?(signed_mail, options)
|
149
|
+
# MUST contain exactly two body parts
|
150
|
+
if signed_mail.parts.length != 2
|
151
|
+
raise EncodingError.new("RFC 3156 mandates exactly two body parts, found '#{signed_mail.parts.length}'")
|
152
|
+
end
|
153
|
+
result, verify_result = SignPart.verify_signature(signed_mail.parts[0], signed_mail.parts[1], options)
|
154
|
+
signed_mail.verify_result = verify_result
|
155
|
+
return result
|
156
|
+
end
|
157
|
+
|
158
|
+
# check signature for inline signed mail
|
159
|
+
def self.signature_valid_inline?(signed_mail, options)
|
160
|
+
result = nil
|
161
|
+
if signed_mail.multipart?
|
162
|
+
signed_mail.parts.each do |part|
|
163
|
+
if signed_inline?(part)
|
164
|
+
if result.nil?
|
165
|
+
result = true
|
166
|
+
signed_mail.verify_result = []
|
167
|
+
end
|
168
|
+
result &= signature_valid_inline?(part, options)
|
169
|
+
signed_mail.verify_result << part.verify_result
|
170
|
+
end
|
171
|
+
end
|
172
|
+
else
|
173
|
+
result, verify_result = GpgmeHelper.inline_verify(signed_mail.body.to_s, options)
|
174
|
+
signed_mail.verify_result = verify_result
|
175
|
+
end
|
176
|
+
return result
|
177
|
+
end
|
178
|
+
|
179
|
+
# copies all header fields from mail in first argument to that given last
|
180
|
+
def self.copy_headers(from, to, overwrite: true)
|
181
|
+
from.header.fields.each do |field|
|
182
|
+
if overwrite || to.header[field.name].nil?
|
183
|
+
to.header[field.name] = field.value
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# check if PGP/MIME encrypted (RFC 3156)
|
189
|
+
def self.encrypted_mime?(mail)
|
190
|
+
mail.has_content_type? &&
|
191
|
+
mail.mime_type == 'multipart/encrypted' &&
|
192
|
+
mail.content_type_parameters[:protocol] == 'application/pgp-encrypted'
|
193
|
+
end
|
194
|
+
|
195
|
+
# check if inline PGP (i.e. if any parts of the mail includes
|
196
|
+
# the PGP MESSAGE marker)
|
197
|
+
def self.encrypted_inline?(mail)
|
198
|
+
begin
|
199
|
+
return true if mail.body.to_s =~ BEGIN_PGP_MESSAGE_MARKER
|
200
|
+
rescue
|
201
|
+
end
|
202
|
+
if mail.multipart?
|
203
|
+
mail.parts.each do |part|
|
204
|
+
begin
|
205
|
+
return true if part.body.to_s =~ BEGIN_PGP_MESSAGE_MARKER
|
206
|
+
rescue
|
207
|
+
end
|
208
|
+
return true if part.has_content_type? &&
|
209
|
+
/application\/(?:octet-stream|pgp-encrypted)/ =~ part.mime_type &&
|
210
|
+
/.*\.(?:pgp|gpg|asc)$/ =~ part.content_type_parameters[:name] &&
|
211
|
+
part.content_type_parameters[:name] != 'signature.asc'
|
212
|
+
# that last condition above prevents false positives in case e.g.
|
213
|
+
# someone forwards a mime signed mail including signature.
|
214
|
+
end
|
215
|
+
end
|
216
|
+
false
|
217
|
+
end
|
218
|
+
|
219
|
+
# check if PGP/MIME signed (RFC 3156)
|
220
|
+
def self.signed_mime?(mail)
|
221
|
+
mail.has_content_type? &&
|
222
|
+
mail.mime_type == 'multipart/signed' &&
|
223
|
+
mail.content_type_parameters[:protocol] == 'application/pgp-signature'
|
224
|
+
end
|
225
|
+
|
226
|
+
# check if inline PGP (i.e. if any parts of the mail includes
|
227
|
+
# the PGP SIGNED marker)
|
228
|
+
def self.signed_inline?(mail)
|
229
|
+
begin
|
230
|
+
return true if mail.body.to_s =~ BEGIN_PGP_SIGNED_MESSAGE_MARKER
|
231
|
+
rescue
|
232
|
+
end
|
233
|
+
if mail.multipart?
|
234
|
+
mail.parts.each do |part|
|
235
|
+
begin
|
236
|
+
return true if part.body.to_s =~ BEGIN_PGP_SIGNED_MESSAGE_MARKER
|
237
|
+
rescue
|
238
|
+
end
|
10
239
|
end
|
11
|
-
encrypted_mail
|
12
240
|
end
|
13
|
-
|
241
|
+
false
|
242
|
+
end
|
14
243
|
end
|
15
244
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'schleuder/mail/gpg/delivery_handler'
|
2
|
+
require 'schleuder/mail/gpg/verify_result_attribute'
|
3
|
+
|
1
4
|
module Mail
|
2
5
|
# creates a Mail::Message likes schleuder
|
3
6
|
def self.create_message_to_list(msg, recipient, list)
|
@@ -13,11 +16,14 @@ module Mail
|
|
13
16
|
|
14
17
|
# TODO: Test if subclassing breaks integration of mail-gpg.
|
15
18
|
class Message
|
19
|
+
include Mail::Gpg::VerifyResultAttribute #for Mail::Gpg
|
20
|
+
|
16
21
|
attr_accessor :recipient
|
17
22
|
attr_accessor :original_message
|
18
23
|
attr_accessor :list
|
19
24
|
attr_accessor :protected_headers_subject
|
20
25
|
attr_writer :dynamic_pseudoheaders
|
26
|
+
attr_accessor :raise_encryption_errors # for Mail::Gpg
|
21
27
|
|
22
28
|
# TODO: This should be in initialize(), but I couldn't understand the
|
23
29
|
# strange errors about wrong number of arguments when overriding
|
@@ -154,7 +160,12 @@ module Mail
|
|
154
160
|
def signer
|
155
161
|
@signer ||= begin
|
156
162
|
if signing_key.present?
|
157
|
-
|
163
|
+
# Look for a subscription that matches the sending address, in case
|
164
|
+
# there're multiple subscriptions for the same key. As a fallback use
|
165
|
+
# the first subscription found.
|
166
|
+
sender_email = self.from.to_s.downcase
|
167
|
+
subscriptions = list.subscriptions.where(fingerprint: signing_key.fingerprint)
|
168
|
+
subscriptions.where(email: sender_email).first || subscriptions.first
|
158
169
|
end
|
159
170
|
end
|
160
171
|
end
|
@@ -436,36 +447,109 @@ module Mail
|
|
436
447
|
true
|
437
448
|
end
|
438
449
|
|
450
|
+
# turn on gpg encryption / set gpg options.
|
451
|
+
#
|
452
|
+
# options are:
|
453
|
+
#
|
454
|
+
# encrypt: encrypt the message. defaults to true
|
455
|
+
# sign: also sign the message. false by default
|
456
|
+
# sign_as: UIDs to sign the message with
|
457
|
+
#
|
458
|
+
# See Mail::Gpg methods encrypt and sign for more
|
459
|
+
# possible options
|
460
|
+
#
|
461
|
+
# mail.gpg encrypt: true
|
462
|
+
# mail.gpg encrypt: true, sign: true
|
463
|
+
# mail.gpg encrypt: true, sign_as: "other_address@host.com"
|
464
|
+
#
|
465
|
+
# sign-only mode is also supported:
|
466
|
+
# mail.gpg sign: true
|
467
|
+
# mail.gpg sign_as: 'jane@doe.com'
|
468
|
+
#
|
469
|
+
# To turn off gpg encryption use:
|
470
|
+
# mail.gpg false
|
471
|
+
#
|
472
|
+
def gpg(options = nil)
|
473
|
+
case options
|
474
|
+
when nil
|
475
|
+
@gpg
|
476
|
+
when false
|
477
|
+
@gpg = nil
|
478
|
+
if Mail::Gpg::DeliveryHandler == delivery_handler
|
479
|
+
self.delivery_handler = nil
|
480
|
+
end
|
481
|
+
nil
|
482
|
+
else
|
483
|
+
self.raise_encryption_errors = true if raise_encryption_errors.nil?
|
484
|
+
@gpg = options
|
485
|
+
self.delivery_handler ||= Mail::Gpg::DeliveryHandler
|
486
|
+
nil
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
# true if this mail is encrypted
|
491
|
+
def encrypted?
|
492
|
+
Mail::Gpg.encrypted?(self)
|
493
|
+
end
|
494
|
+
|
495
|
+
# returns the decrypted mail object.
|
496
|
+
#
|
497
|
+
# pass verify: true to verify signatures as well. The gpgme verification
|
498
|
+
# result will be available via decrypted_mail.verify_result
|
499
|
+
def decrypt(options = {})
|
500
|
+
Mail::Gpg.decrypt(self, options)
|
501
|
+
end
|
502
|
+
|
503
|
+
# true if this mail is signed (but not encrypted)
|
504
|
+
def signed?
|
505
|
+
Mail::Gpg.signed?(self)
|
506
|
+
end
|
507
|
+
|
508
|
+
# verify signatures. returns a new mail object with signatures removed and
|
509
|
+
# populated verify_result.
|
510
|
+
#
|
511
|
+
# verified = signed_mail.verify()
|
512
|
+
# verified.signature_valid?
|
513
|
+
# signers = mail.signatures.map{|sig| sig.from}
|
514
|
+
#
|
515
|
+
# use import_missing_keys: true in order to try to fetch and import
|
516
|
+
# unknown keys for signature validation
|
517
|
+
def verify(options = {})
|
518
|
+
Mail::Gpg.verify(self, options)
|
519
|
+
end
|
520
|
+
|
521
|
+
def repeat_validation!
|
522
|
+
new = self.original_message.dup.setup
|
523
|
+
self.verify_result = new.verify_result
|
524
|
+
@signatures = new.signatures
|
525
|
+
dynamic_pseudoheaders << new.dynamic_pseudoheaders
|
526
|
+
end
|
527
|
+
|
439
528
|
private
|
440
529
|
|
441
530
|
|
442
531
|
def extract_keywords(content_lines)
|
443
532
|
keywords = []
|
444
533
|
in_keyword_block = false
|
445
|
-
found_blank_line = false
|
446
534
|
content_lines.each_with_index do |line, i|
|
447
|
-
if
|
535
|
+
if line.blank?
|
536
|
+
# Swallow the line: before the actual content or keywords block begins we want to drop blank lines.
|
537
|
+
content_lines[i] = nil
|
538
|
+
# Stop interpreting the following line as argument to the previous keyword.
|
539
|
+
in_keyword_block = false
|
540
|
+
elsif match = line.match(/^x-([^:\s]*)[:\s]*(.*)/i)
|
448
541
|
keyword = match[1].strip.downcase
|
449
542
|
arguments = match[2].to_s.strip.downcase.split(/[,; ]{1,}/)
|
450
543
|
keywords << [keyword, arguments]
|
451
544
|
in_keyword_block = true
|
452
|
-
|
453
545
|
# Set this line to nil to have it stripped from the message.
|
454
546
|
content_lines[i] = nil
|
455
|
-
elsif line.blank? && keywords.any?
|
456
|
-
# Look for blank lines after the first keyword had been found.
|
457
|
-
# These might mark the end of the keywords-block — unless more keywords follow.
|
458
|
-
found_blank_line = true
|
459
|
-
# Swallow the line: before the actual content begins we want to drop blank lines.
|
460
|
-
content_lines[i] = nil
|
461
|
-
# Stop interpreting the following line as argument to the previous keyword.
|
462
|
-
in_keyword_block = false
|
463
547
|
elsif in_keyword_block == true
|
464
548
|
# Interpret line as arguments to the previous keyword.
|
465
549
|
keywords[-1][-1] += line.downcase.strip.split(/[,; ]{1,}/)
|
466
550
|
content_lines[i] = nil
|
467
|
-
|
468
|
-
# Any line
|
551
|
+
else
|
552
|
+
# Any other line stops the keyword parsing.
|
469
553
|
break
|
470
554
|
end
|
471
555
|
end
|
data/lib/schleuder/runner.rb
CHANGED
@@ -31,7 +31,13 @@ module Schleuder
|
|
31
31
|
end
|
32
32
|
|
33
33
|
error = run_filters('pre')
|
34
|
-
|
34
|
+
if error
|
35
|
+
if bounce?(error, @mail)
|
36
|
+
return error
|
37
|
+
else
|
38
|
+
return nil
|
39
|
+
end
|
40
|
+
end
|
35
41
|
|
36
42
|
begin
|
37
43
|
# This decrypts, verifies, etc.
|
@@ -48,7 +54,13 @@ module Schleuder
|
|
48
54
|
end
|
49
55
|
|
50
56
|
error = run_filters('post')
|
51
|
-
|
57
|
+
if error
|
58
|
+
if bounce?(error, @mail)
|
59
|
+
return error
|
60
|
+
else
|
61
|
+
return nil
|
62
|
+
end
|
63
|
+
end
|
52
64
|
|
53
65
|
if ! @mail.was_validly_signed?
|
54
66
|
logger.debug 'Message was not validly signed, adding subject_prefix_in'
|
@@ -85,16 +97,34 @@ module Schleuder
|
|
85
97
|
@list
|
86
98
|
end
|
87
99
|
|
88
|
-
def
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
100
|
+
def notify_admins(reason, original_message)
|
101
|
+
if list.bounces_notify_admins
|
102
|
+
list.logger.notify_admin reason, original_message, I18n.t('notice')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def bounce?(response, mail)
|
107
|
+
if list.bounces_drop_all
|
108
|
+
list.logger.debug 'Dropping bounce as configurated'
|
109
|
+
notify_admins(I18n.t('.bounces_drop_all'), mail.original_message)
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
|
113
|
+
list.bounces_drop_on_headers.each do |key, value|
|
114
|
+
if mail[key].to_s.match(/#{value}/i)
|
115
|
+
list.logger.debug "Incoming message header key '#{key}' matches value '#{value}': dropping the bounce."
|
116
|
+
notify_admins(I18n.t('.bounces_drop_on_headers', key: key, value: value), mail.original_message)
|
117
|
+
return false
|
95
118
|
end
|
96
|
-
return error
|
97
119
|
end
|
120
|
+
|
121
|
+
list.logger.debug 'Bouncing message'
|
122
|
+
notify_admins("#{I18n.t('.bounces_notify_admins')}\n\n#{response}", mail.original_message)
|
123
|
+
true
|
124
|
+
end
|
125
|
+
|
126
|
+
def run_filters(filter_type)
|
127
|
+
filters_runner(filter_type).run(@mail)
|
98
128
|
end
|
99
129
|
|
100
130
|
def filters_runner(filter_type)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Schleuder
|
2
|
+
class SksClient < Http
|
3
|
+
SKS_PATH = '/pks/lookup?&exact=on&op=get&options=mr&search=SEARCH_ARG'
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def get(input)
|
7
|
+
super(url(input))
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def url(input)
|
13
|
+
arg = CGI.escape(input.gsub(/\s/, ''))
|
14
|
+
Conf.sks_keyserver + SKS_PATH.gsub('SEARCH_ARG', arg)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/schleuder/version.rb
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Schleuder
|
2
|
+
class VksClient < Http
|
3
|
+
VKS_PATH = '/vks/v1'
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def get(type, input)
|
7
|
+
if type.to_s == 'fingerprint'
|
8
|
+
input = normalize_fingerprint(input)
|
9
|
+
end
|
10
|
+
super(url(type, input))
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def normalize_fingerprint(input)
|
16
|
+
input.gsub(/^0x/, '').gsub(/\s/, '').upcase
|
17
|
+
end
|
18
|
+
|
19
|
+
def url(type, input)
|
20
|
+
"#{Conf.vks_keyserver}/#{VKS_PATH}/by-#{type}/#{CGI.escape(input)}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -14,7 +14,28 @@ class SchleuderApiDaemon < Sinatra::Base
|
|
14
14
|
if ! input.match('BEGIN PGP')
|
15
15
|
input = Base64.decode64(input)
|
16
16
|
end
|
17
|
-
|
17
|
+
@list = list(requested_list_id)
|
18
|
+
import_result = @list.import_key(input)
|
19
|
+
keys = []
|
20
|
+
messages = []
|
21
|
+
import_result.imports.each do |import_status|
|
22
|
+
if import_status.action == 'error'
|
23
|
+
messages << "The key with the fingerprint #{import_status.fingerprint} could not be imported for unknown reasons"
|
24
|
+
else
|
25
|
+
key = @list.gpg.find_distinct_key(import_status.fingerprint)
|
26
|
+
if key
|
27
|
+
keys << key_to_hash(key).merge({import_action: import_status.action})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
set_x_messages(messages)
|
32
|
+
# Use a Hash as single response object to stay more REST-like. (also,
|
33
|
+
# ActiveResource chokes if we return an array here).
|
34
|
+
# The 'type' attribute is only necessary to keep ActiveResource from
|
35
|
+
# complaining about "expected attributes to be able to convert to Hash",
|
36
|
+
# which for some reason is raised without it (or some other, similar
|
37
|
+
# attribute).
|
38
|
+
json({type: 'keys', keys: keys})
|
18
39
|
end
|
19
40
|
|
20
41
|
get '/check_keys.json' do
|
data/lib/schleuder.rb
CHANGED
@@ -15,15 +15,15 @@ require 'syslog/logger'
|
|
15
15
|
require 'logger'
|
16
16
|
require 'open3'
|
17
17
|
require 'socket'
|
18
|
+
require 'base64'
|
18
19
|
|
19
20
|
# Require mandatory libs. The database-layer-lib is required below.
|
20
|
-
require 'mail
|
21
|
+
require 'mail'
|
22
|
+
require 'gpgme'
|
21
23
|
require 'active_record'
|
22
24
|
require 'active_support'
|
23
25
|
require 'active_support/core_ext/string'
|
24
|
-
|
25
|
-
# An extra from mail-gpg
|
26
|
-
require 'hkp'
|
26
|
+
require 'typhoeus'
|
27
27
|
|
28
28
|
# Load schleuder
|
29
29
|
libdir = Pathname.new(__FILE__).dirname.realpath
|
@@ -34,13 +34,12 @@ $:.unshift libdir
|
|
34
34
|
require 'schleuder/mail/parts_list.rb'
|
35
35
|
require 'schleuder/mail/message.rb'
|
36
36
|
require 'schleuder/mail/gpg.rb'
|
37
|
-
require 'schleuder/mail/gpg/encrypted_part.rb'
|
38
|
-
require 'schleuder/mail/gpg/sign_part.rb'
|
39
37
|
require 'schleuder/gpgme/import_status.rb'
|
40
38
|
require 'schleuder/gpgme/key.rb'
|
41
39
|
require 'schleuder/gpgme/sub_key.rb'
|
42
40
|
require 'schleuder/gpgme/ctx.rb'
|
43
41
|
require 'schleuder/gpgme/user_id.rb'
|
42
|
+
require 'schleuder/gpgme/key_extractor'
|
44
43
|
|
45
44
|
# The Code[tm]
|
46
45
|
require 'schleuder/errors/base'
|
@@ -50,6 +49,10 @@ end
|
|
50
49
|
# Load schleuder/conf before the other classes, it defines constants!
|
51
50
|
require 'schleuder/conf'
|
52
51
|
require 'schleuder/version'
|
52
|
+
require 'schleuder/http'
|
53
|
+
require 'schleuder/key_fetcher'
|
54
|
+
require 'schleuder/vks_client'
|
55
|
+
require 'schleuder/sks_client'
|
53
56
|
require 'schleuder/logger_notifications'
|
54
57
|
require 'schleuder/logger'
|
55
58
|
require 'schleuder/listlogger'
|
@@ -66,6 +69,7 @@ require 'schleuder/runner'
|
|
66
69
|
require 'schleuder/list'
|
67
70
|
require 'schleuder/list_builder'
|
68
71
|
require 'schleuder/subscription'
|
72
|
+
require 'schleuder/email_key_importer'
|
69
73
|
|
70
74
|
# Setup
|
71
75
|
ENV['SCHLEUDER_CONFIG'] ||= '/etc/schleuder/schleuder.yml'
|
@@ -88,6 +92,6 @@ I18n.load_path += Dir["#{rootdir}/locales/*.yml"]
|
|
88
92
|
I18n.enforce_available_locales = true
|
89
93
|
I18n.default_locale = :en
|
90
94
|
|
91
|
-
File.umask(
|
95
|
+
File.umask(Schleuder::Conf.umask)
|
92
96
|
|
93
97
|
include Schleuder
|