schleuder 3.6.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -6
  3. data/Rakefile +12 -14
  4. data/bin/schleuder +1 -1
  5. data/db/migrate/20140501103532_create_lists.rb +1 -1
  6. data/db/migrate/20140501112859_create_subscriptions.rb +1 -1
  7. data/db/migrate/{201508092100_add_language_to_lists.rb → 20150809210000_add_language_to_lists.rb} +1 -1
  8. data/db/migrate/20150812165700_change_keywords_admin_only_defaults.rb +1 -1
  9. data/db/migrate/20150813235800_add_forward_all_incoming_to_admins.rb +1 -1
  10. data/db/migrate/{201508141727_change_send_encrypted_only_default.rb → 20150814172700_change_send_encrypted_only_default.rb} +1 -1
  11. data/db/migrate/{201508222143_add_logfiles_to_keep_to_lists.rb → 20150822214300_add_logfiles_to_keep_to_lists.rb} +1 -1
  12. data/db/migrate/{201508261723_rename_delivery_disabled_to_delivery_enabled_and_change_default.rb → 20150826172300_rename_delivery_disabled_to_delivery_enabled_and_change_default.rb} +1 -1
  13. data/db/migrate/{201508261815_strip_gpg_passphrase.rb → 20150826181500_strip_gpg_passphrase.rb} +1 -1
  14. data/db/migrate/{201508261827_remove_default_mime.rb → 20150826182700_remove_default_mime.rb} +1 -1
  15. data/db/migrate/20160501172700_fix_headers_to_meta_defaults.rb +1 -1
  16. data/db/migrate/20170713215059_add_internal_footer_to_list.rb +1 -1
  17. data/db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb +1 -1
  18. data/db/migrate/20180723173900_add_deliver_selfsent_to_list.rb +1 -1
  19. data/db/migrate/20190906194820_add_autocrypt_header_to_list.rb +1 -1
  20. data/db/migrate/20200118170110_add_set_reply_to_to_sender_and_munge_from.rb +1 -1
  21. data/db/schema.rb +45 -47
  22. data/etc/postfix/schleuder_sqlite.cf +1 -1
  23. data/etc/schleuder-weekly-key-maintenance.service +9 -0
  24. data/etc/schleuder-weekly-key-maintenance.timer +9 -0
  25. data/etc/schleuder.yml +3 -3
  26. data/lib/schleuder-api-daemon/helpers/schleuder-api-daemon-helper.rb +3 -3
  27. data/lib/schleuder-api-daemon/routes/subscription.rb +4 -4
  28. data/lib/schleuder.rb +9 -11
  29. data/lib/schleuder/cli.rb +9 -188
  30. data/lib/schleuder/cli/cert.rb +2 -2
  31. data/lib/schleuder/cli/cli_helper.rb +14 -0
  32. data/lib/schleuder/cli/schleuder_cert_manager.rb +4 -4
  33. data/lib/schleuder/conf.rb +3 -3
  34. data/lib/schleuder/errors/base.rb +2 -2
  35. data/lib/schleuder/errors/decryption_failed.rb +1 -1
  36. data/lib/schleuder/errors/fatal_error.rb +1 -1
  37. data/lib/schleuder/errors/key_adduid_failed.rb +1 -1
  38. data/lib/schleuder/errors/key_generation_failed.rb +1 -1
  39. data/lib/schleuder/errors/message_empty.rb +1 -1
  40. data/lib/schleuder/errors/message_too_big.rb +1 -1
  41. data/lib/schleuder/errors/too_many_keys.rb +1 -1
  42. data/lib/schleuder/filters/post_decryption/10_request.rb +3 -3
  43. data/lib/schleuder/filters/post_decryption/20_max_message_size.rb +1 -1
  44. data/lib/schleuder/filters/post_decryption/30_forward_to_owner.rb +1 -1
  45. data/lib/schleuder/filters/post_decryption/40_receive_admin_only.rb +1 -1
  46. data/lib/schleuder/filters/post_decryption/50_receive_authenticated_only.rb +1 -1
  47. data/lib/schleuder/filters/post_decryption/60_receive_signed_only.rb +1 -1
  48. data/lib/schleuder/filters/post_decryption/70_receive_encrypted_only.rb +1 -1
  49. data/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb +1 -1
  50. data/lib/schleuder/filters/pre_decryption/10_forward_bounce_to_admins.rb +1 -1
  51. data/lib/schleuder/filters/pre_decryption/30_send_key.rb +1 -1
  52. data/lib/schleuder/filters/pre_decryption/40_fix_exchange_messages.rb +1 -1
  53. data/lib/schleuder/filters/pre_decryption/50_strip_html_from_alternative.rb +2 -2
  54. data/lib/schleuder/filters_runner.rb +9 -9
  55. data/lib/schleuder/gpgme/ctx.rb +15 -35
  56. data/lib/schleuder/gpgme/key.rb +4 -136
  57. data/lib/schleuder/gpgme/user_id.rb +2 -0
  58. data/lib/schleuder/keyword_handlers/attach_list_key.rb +17 -0
  59. data/lib/schleuder/keyword_handlers/base.rb +36 -0
  60. data/lib/schleuder/keyword_handlers/get_version.rb +11 -0
  61. data/lib/schleuder/keyword_handlers/key_management.rb +141 -0
  62. data/lib/schleuder/keyword_handlers/list_management.rb +19 -0
  63. data/lib/schleuder/keyword_handlers/resend.rb +208 -0
  64. data/lib/schleuder/keyword_handlers/sign_this.rb +54 -0
  65. data/lib/schleuder/keyword_handlers/subscription_management.rb +213 -0
  66. data/lib/schleuder/keyword_handlers_runner.rb +146 -0
  67. data/lib/schleuder/list.rb +11 -39
  68. data/lib/schleuder/list_builder.rb +16 -5
  69. data/lib/schleuder/listlogger.rb +1 -1
  70. data/lib/schleuder/mail/message.rb +82 -61
  71. data/lib/schleuder/runner.rb +18 -16
  72. data/lib/schleuder/subscription.rb +9 -10
  73. data/lib/schleuder/validators/boolean_validator.rb +1 -1
  74. data/lib/schleuder/validators/email_validator.rb +1 -1
  75. data/lib/schleuder/validators/fingerprint_validator.rb +1 -1
  76. data/lib/schleuder/validators/greater_than_zero_validator.rb +1 -1
  77. data/lib/schleuder/validators/no_line_breaks_validator.rb +1 -1
  78. data/lib/schleuder/version.rb +1 -1
  79. data/locales/de.yml +48 -36
  80. data/locales/en.yml +33 -21
  81. metadata +117 -53
  82. data/bin/pinentry-clearpassphrase +0 -72
  83. data/lib/schleuder/plugin_runners/base.rb +0 -91
  84. data/lib/schleuder/plugin_runners/list_plugins_runner.rb +0 -24
  85. data/lib/schleuder/plugin_runners/request_plugins_runner.rb +0 -27
  86. data/lib/schleuder/plugins/attach_listkey.rb +0 -13
  87. data/lib/schleuder/plugins/get_version.rb +0 -7
  88. data/lib/schleuder/plugins/key_management.rb +0 -121
  89. data/lib/schleuder/plugins/list_management.rb +0 -15
  90. data/lib/schleuder/plugins/resend.rb +0 -199
  91. data/lib/schleuder/plugins/sign_this.rb +0 -46
  92. data/lib/schleuder/plugins/subscription_management.rb +0 -207
@@ -4,7 +4,7 @@ module Schleuder
4
4
  def initialize(list)
5
5
  set_default_locale
6
6
  allowed_size = list.max_message_size_kb
7
- super t('errors.message_too_big', { allowed_size: allowed_size })
7
+ super t('errors.message_too_big', allowed_size: allowed_size)
8
8
  end
9
9
  end
10
10
  end
@@ -2,7 +2,7 @@ module Schleuder
2
2
  module Errors
3
3
  class TooManyKeys < Base
4
4
  def initialize(listdir, listname)
5
- super t('errors.too_many_keys', {listdir: listdir, listname: listname})
5
+ super t('errors.too_many_keys', listdir: listdir, listname: listname)
6
6
  end
7
7
  end
8
8
  end
@@ -3,17 +3,17 @@ module Schleuder
3
3
  def self.request(list, mail)
4
4
  return if ! mail.request?
5
5
 
6
- list.logger.debug "Request-message"
6
+ list.logger.debug 'Request-message'
7
7
 
8
8
  if ! mail.was_encrypted? || ! mail.was_validly_signed?
9
- list.logger.debug "Error: Message was not encrypted and validly signed"
9
+ list.logger.debug 'Error: Message was not encrypted and validly signed'
10
10
  return Errors::MessageUnauthenticated.new
11
11
  end
12
12
 
13
13
  if mail.keywords.empty?
14
14
  output = I18n.t(:no_keywords_error)
15
15
  else
16
- output = PluginRunners::RequestPluginsRunner.run(list, mail)
16
+ output = KeywordHandlersRunner.run(type: :request, list: list, mail: mail)
17
17
  output = output.flatten.map(&:presence).compact
18
18
  if output.blank?
19
19
  output = I18n.t(:no_output_result)
@@ -3,7 +3,7 @@ module Schleuder
3
3
 
4
4
  def self.max_message_size(list, mail)
5
5
  if (mail.raw_source.size / 1024) > list.max_message_size_kb
6
- list.logger.info "Rejecting mail as too big"
6
+ list.logger.info 'Rejecting mail as too big'
7
7
  return Errors::MessageTooBig.new(list)
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Schleuder
3
3
  def self.forward_to_owner(list, mail)
4
4
  return if ! mail.to_owner?
5
5
 
6
- list.logger.debug "Forwarding addressed to -owner"
6
+ list.logger.debug 'Forwarding addressed to -owner'
7
7
  mail.add_pseudoheader(:note, I18n.t(:owner_forward_prefix))
8
8
  cleanmail = mail.clean_copy(true)
9
9
  list.admins.each do |admin|
@@ -2,7 +2,7 @@ module Schleuder
2
2
  module Filters
3
3
  def self.receive_admin_only(list, mail)
4
4
  if list.receive_admin_only? && ( ! mail.was_validly_signed? || ! mail.signer.admin? )
5
- list.logger.info "Rejecting mail as not from admin."
5
+ list.logger.info 'Rejecting mail as not from admin.'
6
6
  return Errors::MessageNotFromAdmin.new
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@ module Schleuder
2
2
  module Filters
3
3
  def self.receive_authenticated_only(list, mail)
4
4
  if list.receive_authenticated_only? && ( ! mail.was_encrypted? || ! mail.was_validly_signed? )
5
- list.logger.info "Rejecting mail as unauthenticated"
5
+ list.logger.info 'Rejecting mail as unauthenticated'
6
6
  return Errors::MessageUnauthenticated.new
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@ module Schleuder
2
2
  module Filters
3
3
  def self.receive_signed_only(list, mail)
4
4
  if list.receive_signed_only? && ! mail.was_validly_signed?
5
- list.logger.info "Rejecting mail as unsigned"
5
+ list.logger.info 'Rejecting mail as unsigned'
6
6
  return Errors::MessageUnsigned.new
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@ module Schleuder
2
2
  module Filters
3
3
  def self.receive_encrypted_only(list, mail)
4
4
  if list.receive_encrypted_only? && ! mail.was_encrypted?
5
- list.logger.info "Rejecting mail as unencrypted"
5
+ list.logger.info 'Rejecting mail as unencrypted'
6
6
  return Errors::MessageUnencrypted.new
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@ module Schleuder
2
2
  module Filters
3
3
  def self.receive_from_subscribed_emailaddresses_only(list, mail)
4
4
  if list.receive_from_subscribed_emailaddresses_only? && list.subscriptions.where(email: mail.from.first).blank?
5
- list.logger.info "Rejecting mail as not from subscribed address."
5
+ list.logger.info 'Rejecting mail as not from subscribed address.'
6
6
  return Errors::MessageSenderNotSubscribed.new
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@ module Schleuder
2
2
  module Filters
3
3
  def self.forward_bounce_to_admins(list, mail)
4
4
  if mail.automated_message?
5
- list.logger.info "Forwarding automated message to admins"
5
+ list.logger.info 'Forwarding automated message to admins'
6
6
  list.logger.notify_admin I18n.t(:forward_automated_message_to_admins), mail.original_message, I18n.t('automated_message_subject')
7
7
  exit
8
8
  end
@@ -3,7 +3,7 @@ module Schleuder
3
3
  def self.send_key(list, mail)
4
4
  return if ! mail.sendkey_request?
5
5
 
6
- list.logger.debug "Sending public key as reply."
6
+ list.logger.debug 'Sending public key as reply.'
7
7
 
8
8
  out = mail.reply
9
9
  out.from = list.email
@@ -18,7 +18,7 @@ module Schleuder
18
18
  mail.parts[1][:content_type].content_type == 'application/pgp-encrypted' &&
19
19
  mail.parts[2][:content_type].content_type == 'application/octet-stream'
20
20
  mail.parts.delete_at(0)
21
- mail.content_type = [:multipart, :encrypted, {protocol: "application/pgp-encrypted", boundary: mail.boundary}]
21
+ mail.content_type = [:multipart, :encrypted, {protocol: 'application/pgp-encrypted', boundary: mail.boundary}]
22
22
  end
23
23
  end
24
24
  end
@@ -8,12 +8,12 @@ module Schleuder
8
8
  return false
9
9
  end
10
10
 
11
- Schleuder.logger.debug "Stripping html-part from multipart/alternative-message"
11
+ Schleuder.logger.debug 'Stripping html-part from multipart/alternative-message'
12
12
  mail.parts.delete_if do |part|
13
13
  part[:content_type].content_type == 'text/html'
14
14
  end
15
15
  mail.content_type = 'multipart/mixed'
16
- mail.add_pseudoheader(:note, I18n.t("pseudoheaders.stripped_html_from_multialt"))
16
+ mail.add_pseudoheader(:note, I18n.t('pseudoheaders.stripped_html_from_multialt'))
17
17
  end
18
18
  end
19
19
  end
@@ -34,7 +34,7 @@ module Schleuder
34
34
 
35
35
  def bounce?(response, mail)
36
36
  if list.bounces_drop_all
37
- list.logger.debug "Dropping bounce as configurated"
37
+ list.logger.debug 'Dropping bounce as configurated'
38
38
  notify_admins(I18n.t('.bounces_drop_all'), mail.original_message)
39
39
  return false
40
40
  end
@@ -47,7 +47,7 @@ module Schleuder
47
47
  end
48
48
  end
49
49
 
50
- list.logger.debug "Bouncing message"
50
+ list.logger.debug 'Bouncing message'
51
51
  true
52
52
  end
53
53
 
@@ -61,21 +61,21 @@ module Schleuder
61
61
  list.logger.debug "Loading #{filter_type}_decryption filters"
62
62
  sorted_filters.map do |filter_name|
63
63
  require all_filter_files[filter_name]
64
- filter_name.split('_',2).last
64
+ filter_name.split('_', 2).last
65
65
  end
66
66
  end
67
67
 
68
68
  def sorted_filters
69
- @sorted_filters ||= all_filter_files.keys.sort do |a,b|
70
- a.split('_',2).first.to_i <=> b.split('_',2).first.to_i
69
+ @sorted_filters ||= all_filter_files.keys.sort do |a, b|
70
+ a.split('_', 2).first.to_i <=> b.split('_', 2).first.to_i
71
71
  end
72
72
  end
73
73
 
74
74
  def all_filter_files
75
75
  @all_filter_files ||= begin
76
76
  files_in_filter_dirs = Dir[*filter_dirs]
77
- files_in_filter_dirs.inject({}) do |res,file|
78
- filter_name = File.basename(file,'.rb')
77
+ files_in_filter_dirs.inject({}) do |res, file|
78
+ filter_name = File.basename(file, '.rb')
79
79
  res[filter_name] = file
80
80
  res
81
81
  end
@@ -83,9 +83,9 @@ module Schleuder
83
83
  end
84
84
 
85
85
  def filter_dirs
86
- @filter_dirs ||= [File.join(File.dirname(__FILE__),"filters"),
86
+ @filter_dirs ||= [File.join(File.dirname(__FILE__), 'filters'),
87
87
  Schleuder::Conf.filters_dir].map do |d|
88
- File.join(d,"#{filter_type}_decryption/[0-9]*_*.rb")
88
+ File.join(d, "#{filter_type}_decryption/[0-9]*_*.rb")
89
89
  end
90
90
  end
91
91
  end
@@ -25,10 +25,10 @@ module GPGME
25
25
  [import_status.fpr, nil]
26
26
  end
27
27
  when 0
28
- [nil, "The given key material did not contain any keys!"]
28
+ [nil, 'The given key material did not contain any keys!']
29
29
  else
30
30
  # TODO: report import-stati of the keys?
31
- [nil, "The given key material contained more than one key, could not determine which fingerprint to use. Please set it manually!"]
31
+ [nil, 'The given key material contained more than one key, could not determine which fingerprint to use. Please set it manually!']
32
32
  end
33
33
  end
34
34
 
@@ -78,8 +78,8 @@ module GPGME
78
78
  end
79
79
 
80
80
  def self.check_gpg_version
81
- if ! sufficient_gpg_version?('2.0')
82
- $stderr.puts "Error: GnuPG version >= 2.0 required.\nPlease install it and/or provide the path to the binary via the environment-variable GPGBIN.\nExample: GPGBIN=/opt/gpg2/bin/gpg ..."
81
+ if ! sufficient_gpg_version?('2.2')
82
+ $stderr.puts "Error: GnuPG version >= 2.2 required.\nPlease install it and/or provide the path to the binary via the environment-variable GPGBIN.\nExample: GPGBIN=/opt/gpg2/bin/gpg ..."
83
83
  exit 1
84
84
  end
85
85
  end
@@ -95,10 +95,7 @@ module GPGME
95
95
  sleep rand(1.0..5.0)
96
96
  refresh_key(key.fingerprint).presence
97
97
  end
98
- # TODO: drop version check once we killed gpg 2.0 support.
99
- if GPGME::Ctx.sufficient_gpg_version?('2.1')
100
- `gpgconf --kill dirmngr`
101
- end
98
+ `gpgconf --kill dirmngr`
102
99
  output.compact.join("\n")
103
100
  end
104
101
 
@@ -110,6 +107,8 @@ module GPGME
110
107
  # Return filtered error messages. Include gpgkeys-messages from stdout
111
108
  # (gpg 2.0 does that), which could e.g. report a failure to connect to
112
109
  # the keyserver.
110
+ # TODO: Revisit this once we don't do network access via GPG
111
+ # anymore.
113
112
  res = [
114
113
  refresh_key_filter_messages(gpgerr),
115
114
  refresh_key_filter_messages(gpgout).grep(/^gpgkeys: /)
@@ -118,8 +117,7 @@ module GPGME
118
117
  # we better kill dirmngr, so it hopefully won't suffer
119
118
  # from the same error during the next run.
120
119
  # See #309 for background
121
- # TODO: drop version check once we killed gpg 2.0 support.
122
- if !res.empty? && GPGME::Ctx.sufficient_gpg_version?('2.1')
120
+ if !res.empty?
123
121
  `gpgconf --kill dirmngr`
124
122
  end
125
123
  res.join("\n")
@@ -158,7 +156,7 @@ module GPGME
158
156
  # restricted to keyservers.
159
157
  "#{keyserver_arg} --auto-key-locate keyserver --locate-key #{input}"
160
158
  else
161
- [nil, I18n.t("fetch_key.invalid_input")]
159
+ [nil, I18n.t('fetch_key.invalid_input')]
162
160
  end
163
161
  end
164
162
 
@@ -166,8 +164,8 @@ module GPGME
166
164
  import_states = translate_import_data(gpgoutput)
167
165
  strings = import_states.map do |fingerprint, states|
168
166
  key = find_distinct_key(fingerprint)
169
- I18n.t(locale_key, { key_oneline: key.oneline,
170
- states: states.to_sentence })
167
+ I18n.t(locale_key, key_summary: key.summary,
168
+ states: states.to_sentence)
171
169
  end
172
170
  strings
173
171
  end
@@ -182,7 +180,7 @@ module GPGME
182
180
  states = []
183
181
 
184
182
  if import_status == 0
185
- states << I18n.t("import_states.unchanged")
183
+ states << I18n.t('import_states.unchanged')
186
184
  else
187
185
  IMPORT_FLAGS.each do |text, int|
188
186
  if (import_status & int) > 0
@@ -213,7 +211,7 @@ module GPGME
213
211
  errors = []
214
212
  output = []
215
213
  base_cmd = gpg_engine.file_name
216
- base_args = "--no-greeting --no-permission-warning --quiet --armor --trust-model always --no-tty --command-fd 0 --status-fd 1"
214
+ base_args = '--no-greeting --no-permission-warning --quiet --armor --trust-model always --no-tty --command-fd 0 --status-fd 1'
217
215
  cmd = [base_cmd, base_args, args].flatten.join(' ')
218
216
  Open3.popen3(cmd) do |stdin, stdout, stderr, thread|
219
217
  if block_given?
@@ -231,24 +229,6 @@ module GPGME
231
229
  raise 'Need gpg in $PATH or in $GPGBIN'
232
230
  end
233
231
 
234
- def self.gpgcli_expect(args)
235
- gpgcli(args) do |stdin, stdout, stderr|
236
- counter = 0
237
- while line = stdout.gets rescue nil
238
- counter += 1
239
- if counter > 1042
240
- return "Too many input-lines from gpg, something went wrong"
241
- end
242
- output, error = yield(line.chomp)
243
- if output == false
244
- return error
245
- elsif output
246
- stdin.puts output
247
- end
248
- end
249
- end
250
- end
251
-
252
232
  def self.spawn_daemon(name, args)
253
233
  delete_daemon_socket(name)
254
234
  cmd = "#{name} #{args} --daemon > /dev/null 2>&1"
@@ -258,7 +238,7 @@ module GPGME
258
238
  end
259
239
 
260
240
  def self.delete_daemon_socket(name)
261
- path = File.join(ENV["GNUPGHOME"], "S.#{name}")
241
+ path = File.join(ENV['GNUPGHOME'], "S.#{name}")
262
242
  if File.exist?(path)
263
243
  File.delete(path)
264
244
  end
@@ -268,7 +248,7 @@ module GPGME
268
248
  if Conf.keyserver.present?
269
249
  "--keyserver #{Conf.keyserver}"
270
250
  else
271
- ""
251
+ ''
272
252
  end
273
253
  end
274
254
 
@@ -26,8 +26,8 @@ module GPGME
26
26
  expired.present?
27
27
  end
28
28
 
29
- def oneline
30
- @oneline ||=
29
+ def summary
30
+ @summary ||=
31
31
  begin
32
32
  datefmt = '%Y-%m-%d'
33
33
  attribs = [
@@ -41,7 +41,7 @@ module GPGME
41
41
  attribs << "[expired: #{expires.strftime(datefmt)}]"
42
42
  when :revoked
43
43
  # TODO: add revocation date when it's available.
44
- attribs << "[revoked]"
44
+ attribs << '[revoked]'
45
45
  else
46
46
  attribs << "[#{usability_issue}]"
47
47
  end
@@ -75,144 +75,12 @@ module GPGME
75
75
  if trust.present?
76
76
  trust
77
77
  elsif ! usable_for?(:encrypt)
78
- "not capable of encryption"
78
+ 'not capable of encryption'
79
79
  else
80
80
  nil
81
81
  end
82
82
  end
83
83
 
84
- def set_primary_uid(email)
85
- # We rely on the order of UIDs here. Seems to work.
86
- index = self.uids.map(&:email).index(email)
87
- uid_number = index + 1
88
- primary_set = false
89
- args = "--edit-key '#{self.fingerprint}' #{uid_number}"
90
- errors, _ = GPGME::Ctx.gpgcli_expect(args) do |line|
91
- case line.chomp
92
- when /keyedit.prompt/
93
- if ! primary_set
94
- primary_set = true
95
- "primary"
96
- else
97
- "save"
98
- end
99
- else
100
- nil
101
- end
102
- end
103
- errors.join
104
- end
105
-
106
- def adduid(uid, email)
107
- # This block can be deleted once we cease to support gnupg 2.0.
108
- if ! GPGME::Ctx.sufficient_gpg_version?('2.1.4')
109
- return adduid_expect(uid, email)
110
- end
111
-
112
- # Specifying the key via fingerprint apparently doesn't work.
113
- errors, _ = GPGME::Ctx.gpgcli("--quick-adduid #{uid} '#{uid} <#{email}>'")
114
- errors.join
115
- end
116
-
117
- # This method can be deleted once we cease to support gnupg 2.0.
118
- def adduid_expect(uid, email)
119
- args = "--allow-freeform-uid --edit-key '#{self.fingerprint}' adduid"
120
- errors, _ = GPGME::Ctx.gpgcli_expect(args) do |line|
121
- case line.chomp
122
- when /keygen.name/
123
- uid
124
- when /keygen.email/
125
- email
126
- when /keygen.comment/
127
- ''
128
- when /keyedit.prompt/
129
- "save"
130
- else
131
- nil
132
- end
133
- end
134
- errors.join
135
- end
136
-
137
- def clearpassphrase(oldpw)
138
- # This block can be deleted once we cease to support gnupg 2.0.
139
- if ! GPGME::Ctx.sufficient_gpg_version?('2.1.0')
140
- return clearpassphrase_v20(oldpw)
141
- end
142
-
143
- oldpw_given = false
144
- # Don't use '--passwd', it claims to fail (even though it factually doesn't).
145
- args = "--pinentry-mode loopback --edit-key '#{self.fingerprint}' passwd"
146
- errors, _, exitcode = GPGME::Ctx.gpgcli_expect(args) do |line|
147
- case line
148
- when /passphrase.enter/
149
- if ! oldpw_given
150
- oldpw_given = true
151
- oldpw
152
- else
153
- ""
154
- end
155
- when /BAD_PASSPHRASE/
156
- [false, 'bad passphrase']
157
- when /change_passwd.empty.okay/
158
- 'y'
159
- when /keyedit.prompt/
160
- "save"
161
- else
162
- nil
163
- end
164
- end
165
-
166
- # Only show errors if something apparently went wrong. Otherwise we might
167
- # leak useless strings from gpg and make the caller report errors even
168
- # though this method succeeded.
169
- if exitcode > 0
170
- errors.join
171
- else
172
- nil
173
- end
174
- end
175
-
176
- # This method can be deleted once we cease to support gnupg 2.0.
177
- def clearpassphrase_v20(oldpw)
178
- start_gpg_agent(oldpw)
179
- # Don't use '--passwd', it claims to fail (even though it factually doesn't).
180
- errors, _, exitcode = GPGME::Ctx.gpgcli_expect("--edit-key '#{self.fingerprint}' passwd") do |line|
181
- case line
182
- when /BAD_PASSPHRASE/
183
- [false, 'bad passphrase']
184
- when /change_passwd.empty.okay/
185
- 'y'
186
- when /keyedit.prompt/
187
- "save"
188
- else
189
- nil
190
- end
191
- end
192
- stop_gpg_agent
193
-
194
- # Only show errors if something apparently went wrong. Otherwise we might
195
- # leak useless strings from gpg and make the caller report errors even
196
- # though this method succeeded.
197
- if exitcode > 0
198
- errors.join
199
- else
200
- nil
201
- end
202
- end
203
-
204
- # This method can be deleted once we cease to support gnupg 2.0.
205
- def stop_gpg_agent
206
- # gpg-agent terminates itself if its socket goes away.
207
- GPGME::Ctx.delete_daemon_socket('gpg-agent')
208
- end
209
-
210
- def start_gpg_agent(oldpw)
211
- ENV['PINENTRY_USER_DATA'] = oldpw
212
- pinentry = File.join(ENV['SCHLEUDER_ROOT'], 'bin', 'pinentry-clearpassphrase')
213
- GPGME::Ctx.spawn_daemon('gpg-agent', "--use-standard-socket --pinentry-program #{pinentry}")
214
- end
215
-
216
84
  def self.valid_fingerprint?(fp)
217
85
  fp =~ Schleuder::Conf::FINGERPRINT_REGEXP
218
86
  end