schleuder 2.2.4 → 3.2.2

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 (141) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +138 -0
  3. data/Rakefile +136 -0
  4. data/bin/pinentry-clearpassphrase +72 -0
  5. data/bin/schleuder +9 -89
  6. data/bin/schleuder-api-daemon +4 -0
  7. data/db/migrate/20140501103532_create_lists.rb +39 -0
  8. data/db/migrate/20140501112859_create_subscriptions.rb +21 -0
  9. data/db/migrate/201508092100_add_language_to_lists.rb +11 -0
  10. data/db/migrate/20150812165700_change_keywords_admin_only_defaults.rb +8 -0
  11. data/db/migrate/20150813235800_add_forward_all_incoming_to_admins.rb +11 -0
  12. data/db/migrate/201508141727_change_send_encrypted_only_default.rb +8 -0
  13. data/db/migrate/201508222143_add_logfiles_to_keep_to_lists.rb +11 -0
  14. data/db/migrate/201508261723_rename_delivery_disabled_to_delivery_enabled_and_change_default.rb +14 -0
  15. data/db/migrate/201508261815_strip_gpg_passphrase.rb +11 -0
  16. data/db/migrate/201508261827_remove_default_mime.rb +9 -0
  17. data/db/migrate/20160501172700_fix_headers_to_meta_defaults.rb +8 -0
  18. data/db/migrate/20170713215059_add_internal_footer_to_list.rb +11 -0
  19. data/db/schema.rb +62 -0
  20. data/etc/init.d/schleuder-api-daemon +87 -0
  21. data/etc/list-defaults.yml +123 -0
  22. data/etc/postfix/schleuder_sqlite.cf +28 -0
  23. data/etc/schleuder-api-daemon.service +10 -0
  24. data/etc/schleuder.cron.weekly +6 -0
  25. data/etc/schleuder.yml +61 -0
  26. data/lib/schleuder-api-daemon.rb +420 -0
  27. data/lib/schleuder.rb +81 -47
  28. data/lib/schleuder/cli.rb +334 -0
  29. data/lib/schleuder/cli/cert.rb +24 -0
  30. data/lib/schleuder/cli/schleuder_cert_manager.rb +84 -0
  31. data/lib/schleuder/cli/subcommand_fix.rb +11 -0
  32. data/lib/schleuder/conf.rb +131 -0
  33. data/lib/schleuder/errors/active_model_error.rb +15 -0
  34. data/lib/schleuder/errors/base.rb +17 -0
  35. data/lib/schleuder/errors/decryption_failed.rb +16 -0
  36. data/lib/schleuder/errors/fatal_error.rb +13 -0
  37. data/lib/schleuder/errors/file_not_found.rb +14 -0
  38. data/lib/schleuder/errors/invalid_listname.rb +13 -0
  39. data/lib/schleuder/errors/key_adduid_failed.rb +13 -0
  40. data/lib/schleuder/errors/key_generation_failed.rb +16 -0
  41. data/lib/schleuder/errors/keyword_admin_only.rb +13 -0
  42. data/lib/schleuder/errors/list_exists.rb +13 -0
  43. data/lib/schleuder/errors/list_not_found.rb +14 -0
  44. data/lib/schleuder/errors/list_property_missing.rb +14 -0
  45. data/lib/schleuder/errors/listdir_problem.rb +16 -0
  46. data/lib/schleuder/errors/loading_list_settings_failed.rb +14 -0
  47. data/lib/schleuder/errors/message_empty.rb +14 -0
  48. data/lib/schleuder/errors/message_not_from_admin.rb +13 -0
  49. data/lib/schleuder/errors/message_sender_not_subscribed.rb +13 -0
  50. data/lib/schleuder/errors/message_too_big.rb +14 -0
  51. data/lib/schleuder/errors/message_unauthenticated.rb +13 -0
  52. data/lib/schleuder/errors/message_unencrypted.rb +13 -0
  53. data/lib/schleuder/errors/message_unsigned.rb +13 -0
  54. data/lib/schleuder/errors/standard_error.rb +5 -0
  55. data/lib/schleuder/errors/too_many_keys.rb +17 -0
  56. data/lib/schleuder/errors/unknown_list_option.rb +14 -0
  57. data/lib/schleuder/filters/auth_filter.rb +39 -0
  58. data/lib/schleuder/filters/bounces_filter.rb +12 -0
  59. data/lib/schleuder/filters/forward_filter.rb +17 -0
  60. data/lib/schleuder/filters/forward_incoming.rb +13 -0
  61. data/lib/schleuder/filters/hotmail_message_filter.rb +25 -0
  62. data/lib/schleuder/filters/max_message_size.rb +14 -0
  63. data/lib/schleuder/filters/request_filter.rb +26 -0
  64. data/lib/schleuder/filters/send_key_filter.rb +20 -0
  65. data/lib/schleuder/filters/strip_alternative_filter.rb +21 -0
  66. data/lib/schleuder/filters_runner.rb +83 -0
  67. data/lib/schleuder/gpgme/ctx.rb +274 -0
  68. data/lib/schleuder/gpgme/import_status.rb +27 -0
  69. data/lib/schleuder/gpgme/key.rb +212 -0
  70. data/lib/schleuder/gpgme/sub_key.rb +13 -0
  71. data/lib/schleuder/gpgme/user_id.rb +22 -0
  72. data/lib/schleuder/list.rb +318 -127
  73. data/lib/schleuder/list_builder.rb +139 -0
  74. data/lib/schleuder/listlogger.rb +31 -0
  75. data/lib/schleuder/logger.rb +23 -0
  76. data/lib/schleuder/logger_notifications.rb +69 -0
  77. data/lib/schleuder/mail/message.rb +482 -0
  78. data/lib/schleuder/mail/parts_list.rb +9 -0
  79. data/lib/schleuder/plugin_runners/base.rb +91 -0
  80. data/lib/schleuder/plugin_runners/list_plugins_runner.rb +24 -0
  81. data/lib/schleuder/plugin_runners/request_plugins_runner.rb +27 -0
  82. data/lib/schleuder/plugins/attach_listkey.rb +17 -0
  83. data/lib/schleuder/plugins/get_version.rb +7 -0
  84. data/lib/schleuder/plugins/key_management.rb +113 -0
  85. data/lib/schleuder/plugins/list_management.rb +15 -0
  86. data/lib/schleuder/plugins/resend.rb +196 -0
  87. data/lib/schleuder/plugins/sign_this.rb +46 -0
  88. data/lib/schleuder/plugins/subscription_management.rb +140 -0
  89. data/lib/schleuder/runner.rb +130 -0
  90. data/lib/schleuder/subscription.rb +98 -0
  91. data/lib/schleuder/validators/boolean_validator.rb +7 -0
  92. data/lib/schleuder/validators/email_validator.rb +7 -0
  93. data/lib/schleuder/validators/fingerprint_validator.rb +7 -0
  94. data/lib/schleuder/validators/greater_than_zero_validator.rb +7 -0
  95. data/lib/schleuder/validators/no_line_breaks_validator.rb +7 -0
  96. data/lib/schleuder/version.rb +1 -1
  97. data/locales/de.yml +179 -0
  98. data/locales/en.yml +179 -0
  99. metadata +305 -108
  100. checksums.yaml.gz.sig +0 -3
  101. data.tar.gz.sig +0 -2
  102. data/LICENSE +0 -339
  103. data/README +0 -32
  104. data/bin/schleuder-fix-gem-dependencies +0 -37
  105. data/bin/schleuder-init-setup +0 -37
  106. data/bin/schleuder-migrate-v2.1-to-v2.2 +0 -225
  107. data/bin/schleuder-newlist +0 -413
  108. data/contrib/check-expired-keys.rb +0 -60
  109. data/contrib/mutt-schleuder-colors.rc +0 -10
  110. data/contrib/mutt-schleuder-resend.vim +0 -24
  111. data/contrib/smtpserver.rb +0 -76
  112. data/ext/default-list.conf +0 -149
  113. data/ext/default-members.conf +0 -7
  114. data/ext/list.conf.example +0 -14
  115. data/ext/schleuder.conf +0 -64
  116. data/lib/schleuder/archiver.rb +0 -46
  117. data/lib/schleuder/crypt.rb +0 -210
  118. data/lib/schleuder/errors.rb +0 -5
  119. data/lib/schleuder/list_config.rb +0 -146
  120. data/lib/schleuder/log/listlogger.rb +0 -57
  121. data/lib/schleuder/log/outputter/emailoutputter.rb +0 -120
  122. data/lib/schleuder/log/outputter/metaemailoutputter.rb +0 -50
  123. data/lib/schleuder/log/schleuderlogger.rb +0 -34
  124. data/lib/schleuder/mail.rb +0 -873
  125. data/lib/schleuder/mailer.rb +0 -26
  126. data/lib/schleuder/member.rb +0 -69
  127. data/lib/schleuder/plugin.rb +0 -54
  128. data/lib/schleuder/processor.rb +0 -363
  129. data/lib/schleuder/schleuder_config.rb +0 -75
  130. data/lib/schleuder/storage.rb +0 -84
  131. data/lib/schleuder/utils.rb +0 -80
  132. data/man/schleuder-newlist.8 +0 -174
  133. data/man/schleuder.8 +0 -416
  134. data/plugins/README +0 -20
  135. data/plugins/manage_keys_plugin.rb +0 -113
  136. data/plugins/manage_members_plugin.rb +0 -156
  137. data/plugins/manage_self_plugin.rb +0 -26
  138. data/plugins/resend_plugin.rb +0 -35
  139. data/plugins/sign_this_plugin.rb +0 -14
  140. data/plugins/version_plugin.rb +0 -12
  141. metadata.gz.sig +0 -0
@@ -0,0 +1,9 @@
1
+ module Mail
2
+ class PartsList
3
+ # Disable sorting of mime-parts completely.
4
+ # Can't be done during runtime on the body-instance (`body#set_sort_order`)
5
+ # because MailGpg exchanges the body-instances when encrypting/signing.
6
+ def sort!(*args)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,91 @@
1
+ module Schleuder
2
+ module PluginRunners
3
+ module Base
4
+ def run(list, mail)
5
+ list.logger.debug "Starting #{self}"
6
+ @list = list
7
+ @mail = mail
8
+ setup
9
+
10
+ output = mail.keywords.map do |keyword, arguments|
11
+ run_plugin(keyword, arguments)
12
+ end
13
+
14
+ output.flatten.compact
15
+ end
16
+
17
+
18
+ private
19
+
20
+
21
+ def run_plugin(keyword, arguments)
22
+ @list.logger.debug "Running keyword '#{keyword}'"
23
+
24
+ error = check_admin_only(keyword)
25
+ return error if error
26
+
27
+ command = keyword.gsub('-', '_')
28
+ if ['list_name', 'listname'].include? (command)
29
+ return nil
30
+ elsif ! @plugin_module.respond_to?(command)
31
+ return I18n.t('plugins.unknown_keyword', keyword: keyword)
32
+ else
33
+ response = run_command(command, arguments)
34
+ if @list.keywords_admin_notify.include?(keyword)
35
+ notify_admins(keyword, arguments, response)
36
+ end
37
+ return response
38
+ end
39
+ rescue => exc
40
+ # Log to system, this information is probably more useful for
41
+ # system-admins than for list-admins.
42
+ Schleuder.logger.error(exc.message_with_backtrace)
43
+ I18n.t("plugins.plugin_failed", keyword: keyword)
44
+ end
45
+
46
+ def run_command(command, arguments)
47
+ out = @plugin_module.send(command, arguments, @list, @mail)
48
+ Array(out).flatten
49
+ end
50
+
51
+ def check_admin_only(keyword)
52
+ if @list.admin_only?(keyword) && ! @list.from_admin?(@mail)
53
+ @list.logger.debug "Error: Keyword is admin-only, sent by non-admin"
54
+ Schleuder::Errors::KeywordAdminOnly.new(keyword).to_s
55
+ else
56
+ false
57
+ end
58
+ end
59
+
60
+ def setup
61
+ check_listname_keyword
62
+ load_plugin_files
63
+ @plugin_module = self.name.demodulize.gsub("Runner", "").constantize
64
+ end
65
+
66
+ def check_listname_keyword
67
+ return nil if @mail.keywords.blank?
68
+
69
+ listname_kw = @mail.keywords.assoc('list-name') || @mail.keywords.assoc('listname')
70
+ if listname_kw.blank?
71
+ @mail.reply_to_signer I18n.t(:missing_listname_keyword_error)
72
+ exit
73
+ else
74
+ listname_args = listname_kw.last
75
+ if ! [@list.email, @list.request_address].include?(listname_args.first)
76
+ @mail.reply_to_signer I18n.t(:wrong_listname_keyword_error)
77
+ exit
78
+ end
79
+ end
80
+ end
81
+
82
+ def load_plugin_files
83
+ @list.logger.debug "Loading plugins"
84
+ Dir["#{Schleuder::Conf.plugins_dir}/*.rb"].each do |file|
85
+ require file
86
+ end
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,24 @@
1
+ module Schleuder
2
+ module PluginRunners
3
+ module ListPluginsRunner
4
+ extend Base
5
+
6
+ def self.notify_admins(keyword, arguments, response)
7
+ if arguments.blank?
8
+ msg = I18n.t('plugins.keyword_admin_notify_lists_without_arguments',
9
+ signer: @mail.signer,
10
+ keyword: keyword
11
+ )
12
+ else
13
+ msg = I18n.t('plugins.keyword_admin_notify_lists',
14
+ signer: @mail.signer,
15
+ keyword: keyword,
16
+ arguments: arguments.join(' ')
17
+ )
18
+ end
19
+ @list.logger.notify_admin(msg, nil, 'Notice')
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module Schleuder
2
+ module PluginRunners
3
+ module RequestPluginsRunner
4
+ extend Base
5
+
6
+ def self.notify_admins(keyword, arguments, response)
7
+ if arguments.blank?
8
+ explanation = I18n.t('plugins.keyword_admin_notify_request_without_arguments',
9
+ signer: @mail.signer,
10
+ keyword: keyword
11
+ )
12
+ else
13
+ explanation = I18n.t('plugins.keyword_admin_notify_request',
14
+ signer: @mail.signer,
15
+ keyword: keyword,
16
+ arguments: arguments.join(' ')
17
+ )
18
+ end
19
+ response = response.join("\n\n")
20
+ msg = "#{explanation}\n\n#{response}"
21
+ @list.logger.notify_admin(msg, nil, 'Notice')
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,17 @@
1
+ module Schleuder
2
+ module ListPlugins
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}"
14
+ nil
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ module Schleuder
2
+ module RequestPlugins
3
+ def self.get_version(arguments, list, mail)
4
+ Schleuder::VERSION
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,113 @@
1
+ module Schleuder
2
+ module RequestPlugins
3
+ def self.add_key(arguments, list, mail)
4
+ out = [I18n.t('plugins.key_management.import_result')]
5
+
6
+ if mail.has_attachments?
7
+ results = self.import_keys_from_attachments(list, mail)
8
+ else
9
+ results = [self.import_key_from_body(list, mail)]
10
+ end
11
+
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}"
15
+ end
16
+
17
+ out.join("\n")
18
+ end
19
+
20
+ def self.delete_key(arguments, list, mail)
21
+ arguments.map do |argument|
22
+ keys = list.keys(argument)
23
+ case keys.size
24
+ when 0
25
+ I18n.t("errors.no_match_for", input: argument)
26
+ when 1
27
+ begin
28
+ keys.first.delete!
29
+ I18n.t('plugins.key_management.deleted', key_string: keys.first.fingerprint)
30
+ rescue GPGME::Error::Conflict
31
+ I18n.t('plugins.key_management.not_deletable', key_string: keys.first.fingerprint)
32
+ end
33
+ else
34
+ I18n.t('errors.too_many_matching_keys', {
35
+ input: argument,
36
+ key_strings: keys.map(&:to_s).join("\n")
37
+ })
38
+ end
39
+ end.join("\n\n")
40
+ end
41
+
42
+ def self.list_keys(arguments, list, mail)
43
+ args = Array(arguments.presence || '')
44
+ args.map do |argument|
45
+ # In this case it shall be allowed to match keys by arbitrary
46
+ # sub-strings, therefore we use `list.gpg` directly to not have the
47
+ # input filtered.
48
+ list.gpg.keys(argument).map do |key|
49
+ key.to_s
50
+ end
51
+ end.join("\n\n")
52
+ end
53
+
54
+ def self.get_key(arguments, list, mail)
55
+ arguments.map do |argument|
56
+ keys = list.keys(argument)
57
+ if keys.blank?
58
+ I18n.t("errors.no_match_for", input: argument)
59
+ else
60
+ result = [I18n.t('plugins.key_management.matching_keys_intro', input: argument)]
61
+ keys.each do |key|
62
+ atchm = Mail::Part.new
63
+ atchm.body = key.armored
64
+ atchm.content_type = 'application/pgp-keys'
65
+ atchm.content_disposition = "attachment; filename=#{key.fingerprint}.asc"
66
+ result << atchm
67
+ end
68
+ result.flatten
69
+ end
70
+ end
71
+ end
72
+
73
+ def self.fetch_key(arguments, list, mail)
74
+ arguments.map do |argument|
75
+ list.fetch_keys(argument)
76
+ end
77
+ end
78
+
79
+ # helper methods
80
+ private
81
+
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
+ def self.import_keys_from_attachments(list, mail)
100
+ mail.attachments.map do |attachment|
101
+ material = attachment.body.to_s
102
+
103
+ list.import_key(material) if self.is_armored_key?(material)
104
+ end
105
+ end
106
+
107
+ def self.import_key_from_body(list, mail)
108
+ key_material = mail.first_plaintext_part.body.to_s
109
+
110
+ list.import_key(key_material) if self.is_armored_key?(key_material)
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,15 @@
1
+ module Schleuder
2
+ module RequestPlugins
3
+ def self.get_logfile(arguments, list, mail)
4
+ if File.readable?(list.logfile)
5
+ attachment = Mail::Part.new
6
+ attachment.body = File.read(list.logfile)
7
+ attachment.content_disposition = "inline; filename=#{list.email}.log"
8
+ intro = I18n.t("plugins.list_management.logfile_attached", listname: list.email)
9
+ [intro, attachment]
10
+ else
11
+ I18n.t("plugins.list_management.no_logfile", listname: list.email)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,196 @@
1
+ module Schleuder
2
+ module ListPlugins
3
+ def self.resend(arguments, list, mail)
4
+ resend_it(arguments, mail, false)
5
+ end
6
+
7
+ def self.resend_enc(arguments, list, mail)
8
+ resend_encrypted_only(arguments, list, mail)
9
+ end
10
+
11
+ def self.resend_encrypted_only(arguments, list, mail)
12
+ resend_it(arguments, mail, true)
13
+ end
14
+
15
+ def self.resend_cc(arguments, list, mail)
16
+ resend_it_cc(arguments, mail, false)
17
+ end
18
+
19
+ def self.resend_cc_enc(arguments, list, mail)
20
+ resend_cc_encrypted_only(arguments, list, mail)
21
+ end
22
+
23
+ def self.resend_cc_encrypted_only(arguments, list, mail)
24
+ resend_it_cc(arguments, mail, true)
25
+ end
26
+
27
+ def self.resend_unencrypted(arguments, list, mail)
28
+ do_resend_unencrypted(arguments, list, mail, :to)
29
+ end
30
+
31
+ def self.resend_cc_unencrypted(arguments, list, mail)
32
+ do_resend_unencrypted(arguments, list, mail, :cc)
33
+ end
34
+
35
+ # helper methods
36
+ private
37
+
38
+ def self.do_resend_unencrypted(arguments, list, mail, target)
39
+ if ! resend_recipients_valid?(mail, arguments)
40
+ return false
41
+ end
42
+
43
+ recip_map = Hash[Array(arguments).map{|email| [email,''] }]
44
+
45
+ if do_resend(mail, recip_map, target, false)
46
+ mail.add_subject_prefix_out!
47
+ end
48
+ end
49
+
50
+ def self.resend_it_cc(arguments, mail, encrypted_only)
51
+ if ! resend_recipients_valid?(mail, arguments)
52
+ return false
53
+ end
54
+
55
+ recip_map = map_with_keys(mail, arguments, encrypted_only)
56
+
57
+ # Only continue if all recipients are still here.
58
+ if recip_map.size < arguments.size
59
+ return
60
+ end
61
+
62
+ if do_resend(mail, recip_map, :cc, encrypted_only)
63
+ mail.add_subject_prefix_out!
64
+ end
65
+ end
66
+
67
+ def self.resend_it(arguments, mail, encrypted_only)
68
+ if ! resend_recipients_valid?(mail, arguments)
69
+ return false
70
+ end
71
+
72
+ recip_map = map_with_keys(mail, arguments, encrypted_only)
73
+
74
+ resent_stati = recip_map.map do |email, key|
75
+ do_resend(mail, {email => key}, :to, encrypted_only)
76
+ end
77
+
78
+ if resent_stati.include?(true)
79
+ # At least one message has been resent
80
+ mail.add_subject_prefix_out!
81
+ end
82
+ end
83
+
84
+ def self.do_resend(mail, recipients_map, to_or_cc, encrypted_only)
85
+ if recipients_map.empty?
86
+ return
87
+ end
88
+
89
+ gpg_opts = make_gpg_opts(mail, recipients_map, encrypted_only)
90
+ if gpg_opts == false
91
+ return false
92
+ end
93
+
94
+ # Compose and send email
95
+ new = mail.clean_copy
96
+ new[to_or_cc] = recipients_map.keys
97
+ new.add_public_footer!
98
+ new.sender = mail.list.bounce_address
99
+ # `dup` gpg_opts because `deliver` changes their value and we need them
100
+ # below to determine encryption!
101
+ new.gpg gpg_opts.dup
102
+
103
+ if new.deliver
104
+ add_resent_headers(mail, recipients_map, to_or_cc, gpg_opts[:encrypt])
105
+ return true
106
+ else
107
+ add_error_header(mail, recipients_map)
108
+ return false
109
+ end
110
+ rescue Net::SMTPFatalError => exc
111
+ add_error_header(mail, recipients_map)
112
+ logger.error "Error while sending: #{exc}"
113
+ return false
114
+ end
115
+
116
+ def self.map_with_keys(mail, recipients, encrypted_only)
117
+ Array(recipients).inject({}) do |hash, email|
118
+ keys = mail.list.keys(email)
119
+ # Exclude unusable keys.
120
+ keys.select! { |key| key.usable_for?(:encrypt) }
121
+ case keys.size
122
+ when 1
123
+ hash[email] = keys.first
124
+ when 0
125
+ if encrypted_only
126
+ # Don't add the email to the result to exclude it from the
127
+ # recipients.
128
+ add_keys_error(mail, email, keys.size)
129
+ else
130
+ hash[email] = ''
131
+ end
132
+ else
133
+ # Always report this situation, regardless of sending or not. It's
134
+ # bad and should be fixed.
135
+ add_keys_error(mail, email, keys.size)
136
+ if ! encrypted_only
137
+ hash[email] = ''
138
+ end
139
+ end
140
+ hash
141
+ end
142
+ end
143
+
144
+ def self.make_gpg_opts(mail, recipients_map, encrypted_only)
145
+ gpg_opts = mail.list.gpg_sign_options
146
+ # Do all recipients have a key?
147
+ if recipients_map.values.map(&:class).uniq == [GPGME::Key]
148
+ gpg_opts.merge!(encrypt: true)
149
+ elsif encrypted_only
150
+ false
151
+ end
152
+ gpg_opts
153
+ end
154
+
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))
157
+ end
158
+
159
+ def self.add_error_header(mail, recipients_map)
160
+ mail.add_pseudoheader(:error, "Resending to #{recipients_map.keys.join(', ')} failed, please check the logs!")
161
+ end
162
+
163
+ def self.add_resent_headers(mail, recipients_map, to_or_cc, sent_encrypted)
164
+ if sent_encrypted
165
+ prefix = I18n.t('plugins.resend.encrypted_to')
166
+ str = recipients_map.map do |email, key|
167
+ "#{email} (#{key.fingerprint})"
168
+ end.join(', ')
169
+ else
170
+ prefix = I18n.t('plugins.resend.unencrypted_to')
171
+ str = recipients_map.keys.join(', ')
172
+ end
173
+ headername = resent_header_name(to_or_cc)
174
+ mail.add_pseudoheader(headername, "#{prefix} #{str}")
175
+ end
176
+
177
+ def self.resent_header_name(to_or_cc)
178
+ if to_or_cc.to_s == 'to'
179
+ 'resent'
180
+ else
181
+ 'resent_cc'
182
+ end
183
+ end
184
+
185
+ def self.resend_recipients_valid?(mail, recipients)
186
+ all_valid = true
187
+ Array(recipients).each do |address|
188
+ if ! address.match(Conf::EMAIL_REGEXP)
189
+ mail.add_pseudoheader(:error, I18n.t("plugins.resend.invalid_recipient", address: address))
190
+ all_valid = false
191
+ end
192
+ end
193
+ all_valid
194
+ end
195
+ end
196
+ end