schleuder 4.0.2 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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