schleuder 4.0.2 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -9
  3. data/db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb +1 -1
  4. data/db/migrate/20211106112020_change_boolean_values_to_integers.rb +46 -0
  5. data/db/migrate/20211107151309_add_limits_to_string_columns.rb +28 -0
  6. data/db/migrate/20220910170110_add_key_auto_import_from_email.rb +11 -0
  7. data/db/schema.rb +16 -16
  8. data/etc/list-defaults.yml +16 -0
  9. data/etc/schleuder.yml +29 -11
  10. data/lib/schleuder/cli.rb +15 -2
  11. data/lib/schleuder/conf.rb +23 -3
  12. data/lib/schleuder/email_key_importer.rb +91 -0
  13. data/lib/schleuder/filters/post_decryption/35_key_auto_import_from_attachments.rb +21 -0
  14. data/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb +1 -1
  15. data/lib/schleuder/filters/post_decryption/90_strip_html_from_alternative_if_keywords_present.rb +36 -4
  16. data/lib/schleuder/filters/pre_decryption/60_key_auto_import_from_autocrypt_header.rb +9 -0
  17. data/lib/schleuder/filters_runner.rb +1 -30
  18. data/lib/schleuder/gpgme/ctx.rb +34 -93
  19. data/lib/schleuder/gpgme/key.rb +1 -1
  20. data/lib/schleuder/gpgme/key_extractor.rb +30 -0
  21. data/lib/schleuder/http.rb +56 -0
  22. data/lib/schleuder/key_fetcher.rb +89 -0
  23. data/lib/schleuder/keyword_handlers/key_management.rb +2 -2
  24. data/lib/schleuder/keyword_handlers/subscription_management.rb +19 -3
  25. data/lib/schleuder/list.rb +26 -10
  26. data/lib/schleuder/list_builder.rb +1 -1
  27. data/lib/schleuder/logger.rb +1 -1
  28. data/lib/schleuder/mail/gpg/decrypted_part.rb +20 -0
  29. data/lib/schleuder/mail/gpg/delivery_handler.rb +38 -0
  30. data/lib/schleuder/mail/gpg/encrypted_part.rb +29 -5
  31. data/lib/schleuder/mail/gpg/gpgme_ext.rb +8 -0
  32. data/lib/schleuder/mail/gpg/gpgme_helper.rb +155 -0
  33. data/lib/schleuder/mail/gpg/inline_decrypted_message.rb +82 -0
  34. data/lib/schleuder/mail/gpg/inline_signed_message.rb +73 -0
  35. data/lib/schleuder/mail/gpg/mime_signed_message.rb +28 -0
  36. data/lib/schleuder/mail/gpg/missing_keys_error.rb +6 -0
  37. data/lib/schleuder/mail/gpg/sign_part.rb +19 -9
  38. data/lib/schleuder/mail/gpg/signed_part.rb +37 -0
  39. data/lib/schleuder/mail/gpg/verified_part.rb +10 -0
  40. data/lib/schleuder/mail/gpg/verify_result_attribute.rb +32 -0
  41. data/lib/schleuder/mail/gpg/version_part.rb +22 -0
  42. data/lib/schleuder/mail/gpg.rb +236 -7
  43. data/lib/schleuder/mail/message.rb +98 -14
  44. data/lib/schleuder/runner.rb +40 -10
  45. data/lib/schleuder/sks_client.rb +18 -0
  46. data/lib/schleuder/version.rb +1 -1
  47. data/lib/schleuder/vks_client.rb +24 -0
  48. data/lib/schleuder-api-daemon/routes/key.rb +22 -1
  49. data/lib/schleuder.rb +11 -7
  50. data/locales/de.yml +38 -19
  51. data/locales/en.yml +22 -3
  52. metadata +58 -21
@@ -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
- class << self
4
- alias_method :encrypt_mailgpg, :encrypt
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
- encrypted_mail.subject = cleartext_mail.protected_headers_subject
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
- end
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
- list.subscriptions.where(fingerprint: signing_key.fingerprint).first
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 match = line.match(/^x-([^:\s]*)[:\s]*(.*)/i)
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
- elsif found_blank_line
468
- # Any line that isn't blank and does not start with "x-" stops the keyword parsing.
551
+ else
552
+ # Any other line stops the keyword parsing.
469
553
  break
470
554
  end
471
555
  end
@@ -31,7 +31,13 @@ module Schleuder
31
31
  end
32
32
 
33
33
  error = run_filters('pre')
34
- return error if error
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
- return error if error
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 run_filters(filter_type)
89
- error = filters_runner(filter_type).run(@mail)
90
- if error
91
- if list.bounces_notify_admins?
92
- text = "#{I18n.t('.bounces_notify_admins')}\n\n#{error}"
93
- # TODO: raw_source is mostly blank?
94
- logger.notify_admin text, @mail.original_message, I18n.t('notice')
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
@@ -1,3 +1,3 @@
1
1
  module Schleuder
2
- VERSION = '4.0.2'
2
+ VERSION = '5.0.0'
3
3
  end
@@ -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
- json list(requested_list_id).import_key(input)
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-gpg'
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(0027)
95
+ File.umask(Schleuder::Conf.umask)
92
96
 
93
97
  include Schleuder