schleuder 3.2.2 → 3.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -11
  3. data/Rakefile +18 -10
  4. data/bin/schleuder +2 -1
  5. data/bin/schleuder-api-daemon +3 -2
  6. data/db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb +30 -0
  7. data/db/migrate/20180723173900_add_deliver_selfsent_to_list.rb +11 -0
  8. data/db/migrate/20190906194820_add_autocrypt_header_to_list.rb +11 -0
  9. data/db/schema.rb +4 -2
  10. data/etc/list-defaults.yml +13 -3
  11. data/etc/schleuder.yml +11 -0
  12. data/lib/schleuder-api-daemon.rb +9 -354
  13. data/lib/schleuder-api-daemon/helpers/schleuder-api-daemon-helper.rb +143 -0
  14. data/lib/schleuder-api-daemon/routes/key.rb +40 -0
  15. data/lib/schleuder-api-daemon/routes/list.rb +69 -0
  16. data/lib/schleuder-api-daemon/routes/status.rb +5 -0
  17. data/lib/schleuder-api-daemon/routes/subscription.rb +99 -0
  18. data/lib/schleuder-api-daemon/routes/version.rb +5 -0
  19. data/lib/schleuder.rb +12 -3
  20. data/lib/schleuder/cli.rb +33 -3
  21. data/lib/schleuder/cli/subcommand_fix.rb +1 -1
  22. data/lib/schleuder/conf.rb +7 -1
  23. data/lib/schleuder/errors/active_model_error.rb +2 -5
  24. data/lib/schleuder/errors/decryption_failed.rb +2 -7
  25. data/lib/schleuder/errors/key_adduid_failed.rb +1 -5
  26. data/lib/schleuder/errors/key_generation_failed.rb +1 -8
  27. data/lib/schleuder/errors/keyword_admin_only.rb +1 -5
  28. data/lib/schleuder/errors/list_not_found.rb +1 -5
  29. data/lib/schleuder/errors/listdir_problem.rb +2 -7
  30. data/lib/schleuder/errors/loading_list_settings_failed.rb +2 -5
  31. data/lib/schleuder/errors/message_empty.rb +1 -5
  32. data/lib/schleuder/errors/message_not_from_admin.rb +2 -5
  33. data/lib/schleuder/errors/message_sender_not_subscribed.rb +2 -5
  34. data/lib/schleuder/errors/message_too_big.rb +2 -5
  35. data/lib/schleuder/errors/message_unauthenticated.rb +1 -4
  36. data/lib/schleuder/errors/message_unencrypted.rb +2 -5
  37. data/lib/schleuder/errors/message_unsigned.rb +2 -5
  38. data/lib/schleuder/errors/too_many_keys.rb +1 -8
  39. data/lib/schleuder/filters/{request_filter.rb → post_decryption/10_request.rb} +0 -0
  40. data/lib/schleuder/filters/{max_message_size.rb → post_decryption/20_max_message_size.rb} +0 -0
  41. data/lib/schleuder/filters/{forward_filter.rb → post_decryption/30_forward_to_owner.rb} +0 -0
  42. data/lib/schleuder/filters/post_decryption/40_receive_admin_only.rb +10 -0
  43. data/lib/schleuder/filters/post_decryption/50_receive_authenticated_only.rb +10 -0
  44. data/lib/schleuder/filters/post_decryption/60_receive_signed_only.rb +10 -0
  45. data/lib/schleuder/filters/post_decryption/70_receive_encrypted_only.rb +10 -0
  46. data/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb +10 -0
  47. data/lib/schleuder/filters/post_decryption/90_strip_html_from_alternative_if_keywords_present.rb +21 -0
  48. data/lib/schleuder/filters/{bounces_filter.rb → pre_decryption/10_forward_bounce_to_admins.rb} +0 -0
  49. data/lib/schleuder/filters/{forward_incoming.rb → pre_decryption/20_forward_all_incoming_to_admins.rb} +0 -0
  50. data/lib/schleuder/filters/{send_key_filter.rb → pre_decryption/30_send_key.rb} +0 -0
  51. data/lib/schleuder/filters/{hotmail_message_filter.rb → pre_decryption/40_fix_exchange_messages.rb} +5 -3
  52. data/lib/schleuder/filters/{strip_alternative_filter.rb → pre_decryption/50_strip_html_from_alternative.rb} +1 -1
  53. data/lib/schleuder/filters_runner.rb +41 -31
  54. data/lib/schleuder/gpgme/ctx.rb +24 -3
  55. data/lib/schleuder/gpgme/import_status.rb +13 -7
  56. data/lib/schleuder/gpgme/key.rb +8 -0
  57. data/lib/schleuder/list.rb +26 -4
  58. data/lib/schleuder/logger_notifications.rb +8 -1
  59. data/lib/schleuder/mail/encrypted_part.rb +14 -0
  60. data/lib/schleuder/mail/gpg.rb +15 -0
  61. data/lib/schleuder/mail/message.rb +97 -49
  62. data/lib/schleuder/plugins/attach_listkey.rb +6 -10
  63. data/lib/schleuder/plugins/key_management.rb +34 -26
  64. data/lib/schleuder/plugins/resend.rb +14 -11
  65. data/lib/schleuder/plugins/subscription_management.rb +70 -3
  66. data/lib/schleuder/runner.rb +49 -10
  67. data/lib/schleuder/subscription.rb +5 -9
  68. data/lib/schleuder/validators/fingerprint_validator.rb +1 -1
  69. data/lib/schleuder/version.rb +1 -1
  70. data/locales/de.yml +101 -9
  71. data/locales/en.yml +107 -11
  72. metadata +72 -34
  73. data/lib/schleuder/errors/file_not_found.rb +0 -14
  74. data/lib/schleuder/errors/invalid_listname.rb +0 -13
  75. data/lib/schleuder/errors/list_exists.rb +0 -13
  76. data/lib/schleuder/errors/unknown_list_option.rb +0 -14
  77. data/lib/schleuder/filters/auth_filter.rb +0 -39
@@ -1,16 +1,12 @@
1
1
  module Schleuder
2
2
  module ListPlugins
3
3
  def self.attach_listkey(arguments, list, mail)
4
- filename = "#{list.fingerprint}.pgpkey"
5
- # "Mail" only really converts to multipart if the content-type is blank.
6
- mail.content_type = nil
7
- mail.add_file({
8
- filename: filename,
9
- content: list.export_key
10
- })
11
- mail.attachments[filename].content_type = 'application/pgp-keys'
12
- mail.attachments[filename].content_description = "OpenPGP public key of #{list.email}"
13
- mail.attachments[filename].content_disposition = "attachment; filename=#{filename}"
4
+ new_part = Mail::Part.new
5
+ new_part.body = list.export_key
6
+ new_part.content_type = 'application/pgp-keys'
7
+ new_part.content_description = "OpenPGP public key of #{list.email}"
8
+ new_part.content_disposition = "attachment; filename=#{list.fingerprint}.pgpkey"
9
+ mail.add_part new_part
14
10
  nil
15
11
  end
16
12
  end
@@ -1,7 +1,6 @@
1
1
  module Schleuder
2
2
  module RequestPlugins
3
3
  def self.add_key(arguments, list, mail)
4
- out = [I18n.t('plugins.key_management.import_result')]
5
4
 
6
5
  if mail.has_attachments?
7
6
  results = self.import_keys_from_attachments(list, mail)
@@ -9,15 +8,35 @@ module Schleuder
9
8
  results = [self.import_key_from_body(list, mail)]
10
9
  end
11
10
 
12
- out << results.compact.collect(&:imports).flatten.map do |import_status|
13
- str = I18n.t("plugins.key_management.key_import_status.#{import_status.action}")
14
- "#{import_status.fpr}: #{str}"
11
+ import_stati = results.compact.collect(&:imports).flatten
12
+
13
+ if import_stati.blank?
14
+ return I18n.t('plugins.key_management.no_imports')
15
+ end
16
+
17
+ out = []
18
+
19
+ import_stati.each do |import_status|
20
+ if import_status.action == 'error'
21
+ out << I18n.t("plugins.key_management.key_import_status.error", fingerprint: import_status.fingerprint)
22
+ else
23
+ key = list.gpg.find_distinct_key(import_status.fingerprint)
24
+ if key
25
+ out << I18n.t("plugins.key_management.key_import_status.#{import_status.action}", key_oneline: key.oneline)
26
+ end
27
+ end
15
28
  end
16
29
 
17
- out.join("\n")
30
+ out.join("\n\n")
18
31
  end
19
32
 
20
33
  def self.delete_key(arguments, list, mail)
34
+ if arguments.blank?
35
+ return I18n.t(
36
+ "plugins.key_management.delete_key_requires_arguments"
37
+ )
38
+ end
39
+
21
40
  arguments.map do |argument|
22
41
  keys = list.keys(argument)
23
42
  case keys.size
@@ -26,9 +45,9 @@ module Schleuder
26
45
  when 1
27
46
  begin
28
47
  keys.first.delete!
29
- I18n.t('plugins.key_management.deleted', key_string: keys.first.fingerprint)
48
+ I18n.t('plugins.key_management.deleted', key_string: keys.first.oneline)
30
49
  rescue GPGME::Error::Conflict
31
- I18n.t('plugins.key_management.not_deletable', key_string: keys.first.fingerprint)
50
+ I18n.t('plugins.key_management.not_deletable', key_string: keys.first.oneline)
32
51
  end
33
52
  else
34
53
  I18n.t('errors.too_many_matching_keys', {
@@ -71,6 +90,12 @@ module Schleuder
71
90
  end
72
91
 
73
92
  def self.fetch_key(arguments, list, mail)
93
+ if arguments.blank?
94
+ return I18n.t(
95
+ "plugins.key_management.fetch_key_requires_arguments"
96
+ )
97
+ end
98
+
74
99
  arguments.map do |argument|
75
100
  list.fetch_keys(argument)
76
101
  end
@@ -79,35 +104,18 @@ module Schleuder
79
104
  # helper methods
80
105
  private
81
106
 
82
- def self.is_armored_key?(material)
83
- return false unless /^-----BEGIN PGP PUBLIC KEY BLOCK-----$/ =~ material
84
- return false unless /^-----END PGP PUBLIC KEY BLOCK-----$/ =~ material
85
-
86
- lines = material.split("\n").reject(&:empty?)
87
- # remove header
88
- lines.shift
89
- # remove tail
90
- lines.pop
91
- # verify the rest
92
- # TODO: verify length except for lasts lines?
93
- # headers according to https://tools.ietf.org/html/rfc4880#section-6.2
94
- lines.map do |line|
95
- /\A((comment|version|messageid|hash|charset):.*|[0-9a-z\/=+]+)\Z/i =~ line
96
- end.all?
97
- end
98
-
99
107
  def self.import_keys_from_attachments(list, mail)
100
108
  mail.attachments.map do |attachment|
101
109
  material = attachment.body.to_s
102
110
 
103
- list.import_key(material) if self.is_armored_key?(material)
111
+ list.import_key(material)
104
112
  end
105
113
  end
106
114
 
107
115
  def self.import_key_from_body(list, mail)
108
116
  key_material = mail.first_plaintext_part.body.to_s
109
117
 
110
- list.import_key(key_material) if self.is_armored_key?(key_material)
118
+ list.import_key(key_material)
111
119
  end
112
120
  end
113
121
  end
@@ -56,6 +56,9 @@ module Schleuder
56
56
 
57
57
  # Only continue if all recipients are still here.
58
58
  if recip_map.size < arguments.size
59
+ recip_map.keys.each do |aborted_sender|
60
+ mail.add_pseudoheader(:error, I18n.t("plugins.resend.aborted", email: aborted_sender))
61
+ end
59
62
  return
60
63
  end
61
64
 
@@ -117,22 +120,22 @@ module Schleuder
117
120
  Array(recipients).inject({}) do |hash, email|
118
121
  keys = mail.list.keys(email)
119
122
  # Exclude unusable keys.
120
- keys.select! { |key| key.usable_for?(:encrypt) }
121
- case keys.size
123
+ usable_keys = keys.select { |key| key.usable_for?(:encrypt) }
124
+ case usable_keys.size
122
125
  when 1
123
- hash[email] = keys.first
126
+ hash[email] = usable_keys.first
124
127
  when 0
125
128
  if encrypted_only
126
129
  # Don't add the email to the result to exclude it from the
127
130
  # recipients.
128
- add_keys_error(mail, email, keys.size)
131
+ add_resend_msg(mail, email, :error, 'not_resent_no_keys', usable_keys.size, keys.size)
129
132
  else
130
133
  hash[email] = ''
131
134
  end
132
135
  else
133
136
  # Always report this situation, regardless of sending or not. It's
134
137
  # bad and should be fixed.
135
- add_keys_error(mail, email, keys.size)
138
+ add_resend_msg(mail, email, :notice, 'not_resent_encrypted_no_keys', usable_keys.size, keys.size)
136
139
  if ! encrypted_only
137
140
  hash[email] = ''
138
141
  end
@@ -152,8 +155,8 @@ module Schleuder
152
155
  gpg_opts
153
156
  end
154
157
 
155
- def self.add_keys_error(mail, email, keys_size)
156
- mail.add_pseudoheader(:error, I18n.t("plugins.resend.not_resent_no_keys", email: email, num_keys: keys_size))
158
+ def self.add_resend_msg(mail, email, severity, msg, usable_keys_size, all_keys_size)
159
+ mail.add_pseudoheader(severity, I18n.t("plugins.resend.#{msg}", email: email, usable_keys: usable_keys_size, all_keys: all_keys_size))
157
160
  end
158
161
 
159
162
  def self.add_error_header(mail, recipients_map)
@@ -163,15 +166,15 @@ module Schleuder
163
166
  def self.add_resent_headers(mail, recipients_map, to_or_cc, sent_encrypted)
164
167
  if sent_encrypted
165
168
  prefix = I18n.t('plugins.resend.encrypted_to')
166
- str = recipients_map.map do |email, key|
169
+ str = "\n" + recipients_map.map do |email, key|
167
170
  "#{email} (#{key.fingerprint})"
168
- end.join(', ')
171
+ end.join(",\n")
169
172
  else
170
173
  prefix = I18n.t('plugins.resend.unencrypted_to')
171
- str = recipients_map.keys.join(', ')
174
+ str = ' ' + recipients_map.keys.join(", ")
172
175
  end
173
176
  headername = resent_header_name(to_or_cc)
174
- mail.add_pseudoheader(headername, "#{prefix} #{str}")
177
+ mail.add_pseudoheader(headername, "#{prefix}#{str}")
175
178
  end
176
179
 
177
180
  def self.resent_header_name(to_or_cc)
@@ -1,12 +1,18 @@
1
1
  module Schleuder
2
2
  module RequestPlugins
3
3
  def self.subscribe(arguments, list, mail)
4
+ if arguments.blank?
5
+ return I18n.t(
6
+ "plugins.subscription_management.subscribe_requires_arguments"
7
+ )
8
+ end
9
+
4
10
  email = arguments.shift
5
11
 
6
12
  if arguments.present?
7
13
  # Collect all arguments that look like fingerprint-material
8
14
  fingerprint = ''
9
- while arguments.first.present? && arguments.first.match(/\A(0x)?[a-f0-9]+/i)
15
+ while arguments.first.present? && arguments.first.match(/^(0x)?[a-f0-9]+$/i)
10
16
  fingerprint << arguments.shift
11
17
  end
12
18
  # Use possibly remaining args as flags.
@@ -37,6 +43,13 @@ module Schleuder
37
43
  # If no address was given we unsubscribe the sender.
38
44
  email = arguments.first.presence || mail.signer.email
39
45
 
46
+ # Refuse to unsubscribe the last admin.
47
+ if list.admins.size == 1 && list.admins.first.email == email
48
+ return I18n.t(
49
+ "plugins.subscription_management.cannot_unsubscribe_last_admin", email: email
50
+ )
51
+ end
52
+
40
53
  # TODO: May signers have multiple UIDs? We don't match those currently.
41
54
  if ! list.from_admin?(mail) && email != mail.signer.email
42
55
  # Only admins may unsubscribe others.
@@ -98,6 +111,12 @@ module Schleuder
98
111
  end
99
112
 
100
113
  def self.set_fingerprint(arguments, list, mail)
114
+ if arguments.blank?
115
+ return I18n.t(
116
+ "plugins.subscription_management.set_fingerprint_requires_arguments"
117
+ )
118
+ end
119
+
101
120
  if arguments.first.match(/@/)
102
121
  if arguments.first == mail.signer.email || list.from_admin?(mail)
103
122
  email = arguments.shift
@@ -118,8 +137,15 @@ module Schleuder
118
137
  )
119
138
  end
120
139
 
121
- sub.fingerprint = arguments.join
140
+ fingerprint = arguments.join
141
+ unless GPGME::Key.valid_fingerprint?(fingerprint)
142
+ return I18n.t(
143
+ "plugins.subscription_management.set_fingerprint_requires_valid_fingerprint",
144
+ fingerprint: fingerprint
145
+ )
146
+ end
122
147
 
148
+ sub.fingerprint = fingerprint
123
149
  if sub.save
124
150
  I18n.t(
125
151
  "plugins.subscription_management.fingerprint_set",
@@ -130,7 +156,48 @@ module Schleuder
130
156
  I18n.t(
131
157
  "plugins.subscription_management.setting_fingerprint_failed",
132
158
  email: email,
133
- fingerprint: arguments.last,
159
+ fingerprint: sub.fingerprint,
160
+ errors: sub.errors.to_a.join("\n")
161
+ )
162
+ end
163
+ end
164
+
165
+ def self.unset_fingerprint(arguments, list, mail)
166
+ if arguments.blank?
167
+ return I18n.t(
168
+ "plugins.subscription_management.unset_fingerprint_requires_arguments"
169
+ )
170
+ end
171
+
172
+ email = arguments.first
173
+ unless email == mail.signer.email || list.from_admin?(mail)
174
+ return I18n.t(
175
+ "plugins.subscription_management.unset_fingerprint_only_self"
176
+ )
177
+ end
178
+ if email == mail.signer.email && list.from_admin?(mail) && arguments.last != 'force'
179
+ return I18n.t(
180
+ "plugins.subscription_management.unset_fingerprint_requires_arguments"
181
+ )
182
+ end
183
+
184
+ sub = list.subscriptions.where(email: email).first
185
+ if sub.blank?
186
+ return I18n.t(
187
+ "plugins.subscription_management.is_not_subscribed", email: email
188
+ )
189
+ end
190
+
191
+ sub.fingerprint = ''
192
+ if sub.save
193
+ I18n.t(
194
+ "plugins.subscription_management.fingerprint_unset",
195
+ email: email
196
+ )
197
+ else
198
+ I18n.t(
199
+ "plugins.subscription_management.unsetting_fingerprint_failed",
200
+ email: email,
134
201
  errors: sub.errors.to_a.join("\n")
135
202
  )
136
203
  end
@@ -5,20 +5,48 @@ module Schleuder
5
5
  return error if error
6
6
 
7
7
  logger.info "Parsing incoming email."
8
+
9
+ # is it valid utf-8?
10
+ msg_scrubbed = false
11
+ unless msg.valid_encoding?
12
+ logger.warn "Converting message due to invalid characters"
13
+ detection = CharlockHolmes::EncodingDetector.detect(msg)
14
+ begin
15
+ msg = CharlockHolmes::Converter.convert(msg, detection[:encoding], 'UTF-8')
16
+ rescue ArgumentError
17
+ # it looks like even icu wasn't able to convert
18
+ # so we scrub the invalid characters to be able to
19
+ # at least parse the message somehow. Though this might
20
+ # result in data loss.
21
+ logger.warn "Scrubbing message due to invalid characters"
22
+ msg = msg.scrub
23
+ msg_scrubbed = true
24
+ end
25
+ end
26
+
8
27
  @mail = Mail.create_message_to_list(msg, recipient, list)
9
28
 
10
- error = run_filters(Filters::Runner::PRE_SETUP_FILTERS)
29
+ if msg_scrubbed
30
+ @mail.add_pseudoheader(:note, I18n.t("pseudoheaders.scrubbed_message"))
31
+ end
32
+
33
+ error = run_filters('pre')
11
34
  return error if error
12
35
 
13
36
  begin
14
37
  # This decrypts, verifies, etc.
15
38
  @mail = @mail.setup
16
- rescue GPGME::Error::DecryptFailed
39
+
40
+ rescue GPGME::Error::BadPassphrase,
41
+ GPGME::Error::DecryptFailed,
42
+ GPGME::Error::NoData,
43
+ GPGME::Error::NoSecretKey
44
+
17
45
  logger.warn "Decryption of incoming message failed."
18
46
  return Errors::DecryptionFailed.new(list)
19
47
  end
20
48
 
21
- error = run_filters(Filters::Runner::POST_SETUP_FILTERS)
49
+ error = run_filters('post')
22
50
  return error if error
23
51
 
24
52
  if ! @mail.was_validly_signed?
@@ -45,8 +73,8 @@ module Schleuder
45
73
 
46
74
  # Subscriptions
47
75
  logger.debug "Creating clean copy of message"
48
- copy = @mail.clean_copy(true)
49
- list.send_to_subscriptions(copy)
76
+ copy = @mail.clean_copy(list.headers_to_meta.any?)
77
+ list.send_to_subscriptions(copy, @mail)
50
78
  nil
51
79
  end
52
80
 
@@ -56,8 +84,8 @@ module Schleuder
56
84
  @list
57
85
  end
58
86
 
59
- def run_filters(filters)
60
- error = filters_runner.run(@mail, filters)
87
+ def run_filters(filter_type)
88
+ error = filters_runner(filter_type).run(@mail)
61
89
  if error
62
90
  if list.bounces_notify_admins?
63
91
  text = "#{I18n.t('.bounces_notify_admins')}\n\n#{error}"
@@ -68,8 +96,19 @@ module Schleuder
68
96
  end
69
97
  end
70
98
 
71
- def filters_runner
72
- @filters_runner ||= Filters::Runner.new(list)
99
+ def filters_runner(filter_type)
100
+ if filter_type == 'pre'
101
+ filters_runner_pre_decryption
102
+ else
103
+ filters_runner_post_decryption
104
+ end
105
+ end
106
+
107
+ def filters_runner_pre_decryption
108
+ @filters_runner_pre_decryption ||= Filters::Runner.new(list,'pre')
109
+ end
110
+ def filters_runner_post_decryption
111
+ @filters_runner_post_decryption ||= Filters::Runner.new(list,'post')
73
112
  end
74
113
 
75
114
  def logger
@@ -94,7 +133,7 @@ module Schleuder
94
133
  return log_and_return(Errors::ListNotFound.new(recipient), true)
95
134
  end
96
135
 
97
- # Check neccessary permissions of crucial files.
136
+ # Check necessary permissions of crucial files.
98
137
  if ! File.exist?(@list.listdir)
99
138
  return log_and_return(Errors::ListdirProblem.new(@list.listdir, :not_existing))
100
139
  elsif ! File.directory?(@list.listdir)
@@ -23,10 +23,11 @@ module Schleuder
23
23
  end
24
24
 
25
25
  def fingerprint=(arg)
26
- # Allow input to contain whitespace and '0x'-prefix, but don't store it
27
- # into the DB.
28
- value = arg.to_s.gsub(/\s*/, '').gsub(/^0x/, '').chomp
29
- write_attribute(:fingerprint, value)
26
+ # Always assign the given value, because it must be possible to overwrite
27
+ # the previous fingerprint with an empty value. That value should better
28
+ # be nil instead of a blank string, but currently schleuder-cli (v0.1.0) expects
29
+ # only strings.
30
+ write_attribute(:fingerprint, arg.to_s.gsub(/\s*/, '').gsub(/^0x/, '').chomp.upcase)
30
31
  end
31
32
 
32
33
  def key
@@ -39,11 +40,6 @@ module Schleuder
39
40
  def send_mail(mail)
40
41
  list.logger.debug "Preparing sending to #{self.inspect}"
41
42
 
42
- if ! self.delivery_enabled
43
- list.logger.info "Not sending to #{self.email}: delivery is disabled."
44
- return false
45
- end
46
-
47
43
  mail = ensure_headers(mail)
48
44
  gpg_opts = self.list.gpg_sign_options
49
45
 
@@ -1,6 +1,6 @@
1
1
  class FingerprintValidator < ActiveModel::EachValidator
2
2
  def validate_each(record, attribute, value)
3
- unless value =~ /\A[a-f0-9]{32,}\z/i
3
+ unless GPGME::Key.valid_fingerprint?(value)
4
4
  record.errors[attribute] << (options[:message] || I18n.t("errors.invalid_fingerprint"))
5
5
  end
6
6
  end