schleuder 3.5.3 → 4.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -21
- data/Rakefile +15 -12
- data/bin/schleuder +1 -1
- data/db/migrate/20140501103532_create_lists.rb +1 -1
- data/db/migrate/20140501112859_create_subscriptions.rb +1 -1
- data/db/migrate/{201508092100_add_language_to_lists.rb → 20150809210000_add_language_to_lists.rb} +1 -1
- data/db/migrate/20150812165700_change_keywords_admin_only_defaults.rb +1 -1
- data/db/migrate/20150813235800_add_forward_all_incoming_to_admins.rb +1 -1
- data/db/migrate/{201508141727_change_send_encrypted_only_default.rb → 20150814172700_change_send_encrypted_only_default.rb} +1 -1
- data/db/migrate/{201508222143_add_logfiles_to_keep_to_lists.rb → 20150822214300_add_logfiles_to_keep_to_lists.rb} +1 -1
- 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
- data/db/migrate/{201508261815_strip_gpg_passphrase.rb → 20150826181500_strip_gpg_passphrase.rb} +1 -1
- data/db/migrate/{201508261827_remove_default_mime.rb → 20150826182700_remove_default_mime.rb} +1 -1
- data/db/migrate/20160501172700_fix_headers_to_meta_defaults.rb +1 -1
- data/db/migrate/20170713215059_add_internal_footer_to_list.rb +1 -1
- data/db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb +1 -1
- data/db/migrate/20180723173900_add_deliver_selfsent_to_list.rb +1 -1
- data/db/migrate/20190906194820_add_autocrypt_header_to_list.rb +1 -1
- data/db/migrate/20200118170110_add_set_reply_to_to_sender_and_munge_from.rb +15 -0
- data/db/schema.rb +45 -45
- data/etc/list-defaults.yml +18 -0
- data/etc/postfix/schleuder_sqlite.cf +1 -1
- data/etc/schleuder-weekly-key-maintenance.service +9 -0
- data/etc/schleuder-weekly-key-maintenance.timer +9 -0
- data/etc/schleuder.yml +3 -3
- data/lib/schleuder-api-daemon/helpers/schleuder-api-daemon-helper.rb +3 -3
- data/lib/schleuder-api-daemon/routes/subscription.rb +4 -4
- data/lib/schleuder.rb +13 -12
- data/lib/schleuder/cli.rb +9 -189
- data/lib/schleuder/cli/cert.rb +2 -2
- data/lib/schleuder/cli/cli_helper.rb +14 -0
- data/lib/schleuder/cli/schleuder_cert_manager.rb +4 -4
- data/lib/schleuder/conf.rb +4 -4
- data/lib/schleuder/errors/base.rb +2 -2
- data/lib/schleuder/errors/decryption_failed.rb +1 -1
- data/lib/schleuder/errors/fatal_error.rb +1 -1
- data/lib/schleuder/errors/key_adduid_failed.rb +1 -1
- data/lib/schleuder/errors/key_generation_failed.rb +1 -1
- data/lib/schleuder/errors/message_empty.rb +1 -1
- data/lib/schleuder/errors/message_too_big.rb +1 -1
- data/lib/schleuder/errors/too_many_keys.rb +1 -1
- data/lib/schleuder/filters/post_decryption/10_request.rb +3 -3
- data/lib/schleuder/filters/post_decryption/20_max_message_size.rb +1 -1
- data/lib/schleuder/filters/post_decryption/30_forward_to_owner.rb +1 -1
- data/lib/schleuder/filters/post_decryption/40_receive_admin_only.rb +1 -1
- data/lib/schleuder/filters/post_decryption/50_receive_authenticated_only.rb +1 -1
- data/lib/schleuder/filters/post_decryption/60_receive_signed_only.rb +1 -1
- data/lib/schleuder/filters/post_decryption/70_receive_encrypted_only.rb +1 -1
- data/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb +1 -1
- data/lib/schleuder/filters/pre_decryption/10_forward_bounce_to_admins.rb +1 -1
- data/lib/schleuder/filters/pre_decryption/30_send_key.rb +1 -1
- data/lib/schleuder/filters/pre_decryption/40_fix_exchange_messages.rb +1 -1
- data/lib/schleuder/filters/pre_decryption/50_strip_html_from_alternative.rb +2 -2
- data/lib/schleuder/filters_runner.rb +9 -9
- data/lib/schleuder/gpgme/ctx.rb +15 -67
- data/lib/schleuder/gpgme/key.rb +4 -136
- data/lib/schleuder/gpgme/user_id.rb +2 -0
- data/lib/schleuder/keyword_handlers/attach_list_key.rb +17 -0
- data/lib/schleuder/keyword_handlers/base.rb +36 -0
- data/lib/schleuder/keyword_handlers/get_version.rb +11 -0
- data/lib/schleuder/keyword_handlers/key_management.rb +141 -0
- data/lib/schleuder/keyword_handlers/list_management.rb +19 -0
- data/lib/schleuder/keyword_handlers/resend.rb +208 -0
- data/lib/schleuder/keyword_handlers/sign_this.rb +54 -0
- data/lib/schleuder/keyword_handlers/subscription_management.rb +213 -0
- data/lib/schleuder/keyword_handlers_runner.rb +146 -0
- data/lib/schleuder/list.rb +28 -40
- data/lib/schleuder/list_builder.rb +16 -5
- data/lib/schleuder/listlogger.rb +1 -1
- data/lib/schleuder/logger.rb +2 -6
- data/lib/schleuder/mail/{encrypted_part.rb → gpg/encrypted_part.rb} +0 -0
- data/lib/schleuder/mail/gpg/sign_part.rb +33 -0
- data/lib/schleuder/mail/message.rb +135 -40
- data/lib/schleuder/runner.rb +18 -16
- data/lib/schleuder/subscription.rb +35 -13
- data/lib/schleuder/validators/boolean_validator.rb +1 -1
- data/lib/schleuder/validators/email_validator.rb +1 -1
- data/lib/schleuder/validators/fingerprint_validator.rb +1 -1
- data/lib/schleuder/validators/greater_than_zero_validator.rb +1 -1
- data/lib/schleuder/validators/no_line_breaks_validator.rb +1 -1
- data/lib/schleuder/version.rb +1 -1
- data/locales/de.yml +49 -36
- data/locales/en.yml +34 -21
- metadata +131 -79
- data/bin/pinentry-clearpassphrase +0 -72
- data/lib/schleuder/plugin_runners/base.rb +0 -91
- data/lib/schleuder/plugin_runners/list_plugins_runner.rb +0 -24
- data/lib/schleuder/plugin_runners/request_plugins_runner.rb +0 -27
- data/lib/schleuder/plugins/attach_listkey.rb +0 -13
- data/lib/schleuder/plugins/get_version.rb +0 -7
- data/lib/schleuder/plugins/key_management.rb +0 -121
- data/lib/schleuder/plugins/list_management.rb +0 -15
- data/lib/schleuder/plugins/resend.rb +0 -199
- data/lib/schleuder/plugins/sign_this.rb +0 -46
- data/lib/schleuder/plugins/subscription_management.rb +0 -207
data/lib/schleuder/gpgme/key.rb
CHANGED
@@ -26,8 +26,8 @@ module GPGME
|
|
26
26
|
expired.present?
|
27
27
|
end
|
28
28
|
|
29
|
-
def
|
30
|
-
@
|
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 <<
|
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
|
-
|
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
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Schleuder
|
2
|
+
module KeywordHandlers
|
3
|
+
class AttachListKey < Base
|
4
|
+
handles_list_keyword 'attach-listkey', with_method: :attach_list_key
|
5
|
+
|
6
|
+
def attach_list_key
|
7
|
+
new_part = Mail::Part.new
|
8
|
+
new_part.body = @list.export_key
|
9
|
+
new_part.content_type = 'application/pgp-keys'
|
10
|
+
new_part.content_description = "OpenPGP public key of #{@list.email}"
|
11
|
+
new_part.content_disposition = "attachment; filename=#{@list.fingerprint}.pgpkey"
|
12
|
+
@mail.add_part new_part
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Schleuder
|
2
|
+
module KeywordHandlers
|
3
|
+
class Base
|
4
|
+
class << self
|
5
|
+
def handles_request_keyword(keyword, with_method:, has_aliases: [])
|
6
|
+
KeywordHandlersRunner.register_keyword(
|
7
|
+
type: :request,
|
8
|
+
keyword: keyword,
|
9
|
+
handler_class: self,
|
10
|
+
handler_method: with_method,
|
11
|
+
aliases: has_aliases
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def handles_list_keyword(keyword, with_method:, has_aliases: [])
|
16
|
+
KeywordHandlersRunner.register_keyword(
|
17
|
+
type: :list,
|
18
|
+
keyword: keyword,
|
19
|
+
handler_class: self,
|
20
|
+
handler_method: with_method,
|
21
|
+
aliases: has_aliases
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :arguments
|
27
|
+
attr_reader :mail
|
28
|
+
|
29
|
+
def initialize(mail:, arguments:)
|
30
|
+
@arguments = arguments
|
31
|
+
@mail = mail
|
32
|
+
@list = mail.list
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Schleuder
|
2
|
+
module KeywordHandlers
|
3
|
+
class KeyManagement < Base
|
4
|
+
handles_request_keyword 'add-key', with_method: 'add_key'
|
5
|
+
handles_request_keyword 'delete-key', with_method: 'delete_key'
|
6
|
+
handles_request_keyword 'list-keys', with_method: 'list_keys'
|
7
|
+
handles_request_keyword 'get-key', with_method: 'get_key'
|
8
|
+
handles_request_keyword 'fetch-key', with_method: 'fetch_key'
|
9
|
+
|
10
|
+
def add_key
|
11
|
+
results =
|
12
|
+
if @mail.has_attachments?
|
13
|
+
import_keys_from_attachments
|
14
|
+
elsif @mail.first_plaintext_part.body.to_s.present?
|
15
|
+
import_key_from_body
|
16
|
+
else
|
17
|
+
@list.logger.debug 'Found no attachments and an empty body - sending error message'
|
18
|
+
I18n.t('keyword_handlers.key_management.no_content_found')
|
19
|
+
end
|
20
|
+
|
21
|
+
import_stati = results.compact.collect(&:imports).flatten
|
22
|
+
|
23
|
+
if import_stati.blank?
|
24
|
+
return I18n.t('keyword_handlers.key_management.no_imports')
|
25
|
+
end
|
26
|
+
|
27
|
+
out = []
|
28
|
+
|
29
|
+
import_stati.each do |import_status|
|
30
|
+
if import_status.action == 'error'
|
31
|
+
out << I18n.t('keyword_handlers.key_management.key_import_status.error', fingerprint: import_status.fingerprint)
|
32
|
+
else
|
33
|
+
key = @list.gpg.find_distinct_key(import_status.fingerprint)
|
34
|
+
if key
|
35
|
+
out << I18n.t("keyword_handlers.key_management.key_import_status.#{import_status.action}", key_summary: key.summary)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
out.join("\n\n")
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete_key
|
44
|
+
if @arguments.blank?
|
45
|
+
return I18n.t(
|
46
|
+
'keyword_handlers.key_management.delete_key_requires_arguments'
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
@arguments.map do |argument|
|
51
|
+
# Force GPG to match only fingerprints.
|
52
|
+
if argument[0..1] == '0x'
|
53
|
+
fingerprint = argument
|
54
|
+
else
|
55
|
+
fingerprint = "0x#{argument}"
|
56
|
+
end
|
57
|
+
|
58
|
+
keys = @list.keys(fingerprint)
|
59
|
+
case keys.size
|
60
|
+
when 0
|
61
|
+
I18n.t('keyword_handlers.key_management.key_not_found', fingerprint: argument)
|
62
|
+
when 1
|
63
|
+
begin
|
64
|
+
keys.first.delete!
|
65
|
+
I18n.t('keyword_handlers.key_management.deleted', key_string: keys.first.summary)
|
66
|
+
rescue GPGME::Error::Conflict
|
67
|
+
I18n.t('keyword_handlers.key_management.not_deletable', key_string: keys.first.summary)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
# Shouldn't happen, but who knows.
|
71
|
+
I18n.t('errors.too_many_matching_keys', {
|
72
|
+
input: argument,
|
73
|
+
key_strings: keys.map(&:to_s).join("\n")
|
74
|
+
})
|
75
|
+
end
|
76
|
+
end.join("\n\n")
|
77
|
+
end
|
78
|
+
|
79
|
+
def list_keys
|
80
|
+
args = Array(@arguments.presence || '')
|
81
|
+
args.map do |argument|
|
82
|
+
# In this case it shall be allowed to match keys by arbitrary
|
83
|
+
# sub-strings, therefore we use `list.gpg` directly to not have the
|
84
|
+
# input filtered.
|
85
|
+
@list.gpg.keys(argument).map do |key|
|
86
|
+
key.to_s
|
87
|
+
end
|
88
|
+
end.join("\n\n")
|
89
|
+
end
|
90
|
+
|
91
|
+
def get_key
|
92
|
+
@arguments.map do |argument|
|
93
|
+
keys = @list.keys(argument)
|
94
|
+
if keys.blank?
|
95
|
+
I18n.t('errors.no_match_for', input: argument)
|
96
|
+
else
|
97
|
+
result = [I18n.t('keyword_handlers.key_management.matching_keys_intro', input: argument)]
|
98
|
+
keys.each do |key|
|
99
|
+
atchm = Mail::Part.new
|
100
|
+
atchm.body = key.armored
|
101
|
+
atchm.content_type = 'application/pgp-keys'
|
102
|
+
atchm.content_disposition = "attachment; filename=#{key.fingerprint}.asc"
|
103
|
+
result << atchm
|
104
|
+
end
|
105
|
+
result.flatten
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def fetch_key
|
111
|
+
if @arguments.blank?
|
112
|
+
return I18n.t(
|
113
|
+
'keyword_handlers.key_management.fetch_key_requires_arguments'
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
@arguments.map do |argument|
|
118
|
+
@list.fetch_keys(argument)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
|
126
|
+
def import_keys_from_attachments
|
127
|
+
@mail.attachments.map do |attachment|
|
128
|
+
import_from_string(attachment.body.raw_source)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def import_key_from_body
|
133
|
+
[import_from_string(@mail.first_plaintext_part.body.to_s)]
|
134
|
+
end
|
135
|
+
|
136
|
+
def import_from_string(string)
|
137
|
+
@list.import_key(string)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Schleuder
|
2
|
+
module KeywordHandlers
|
3
|
+
class ListManagement < Base
|
4
|
+
handles_request_keyword 'get-logfile', with_method: 'get_logfile'
|
5
|
+
|
6
|
+
def get_logfile
|
7
|
+
if File.readable?(@list.logfile)
|
8
|
+
attachment = Mail::Part.new
|
9
|
+
attachment.body = File.read(@list.logfile)
|
10
|
+
attachment.content_disposition = "inline; filename=#{@list.email}.log"
|
11
|
+
intro = I18n.t('keyword_handlers.list_management.logfile_attached', listname: @list.email)
|
12
|
+
[intro, attachment]
|
13
|
+
else
|
14
|
+
I18n.t('keyword_handlers.list_management.no_logfile', listname: @list.email)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
module Schleuder
|
2
|
+
module KeywordHandlers
|
3
|
+
class Resend < Base
|
4
|
+
handles_list_keyword 'resend-encrypted-only', with_method: :resend_encrypted_only, has_aliases: 'resend-enc'
|
5
|
+
|
6
|
+
handles_list_keyword 'resend', with_method: :resend
|
7
|
+
|
8
|
+
handles_list_keyword 'resend-cc', with_method: :resend_cc
|
9
|
+
|
10
|
+
handles_list_keyword 'resend-cc-encrypted-only', with_method: :resend_cc_encrypted_only, has_aliases: 'resend-cc-enc'
|
11
|
+
|
12
|
+
handles_list_keyword 'resend-unencrypted', with_method: :resend_unencrypted
|
13
|
+
|
14
|
+
handles_list_keyword 'resend-cc-unencrypted', with_method: :resend_cc_unencrypted
|
15
|
+
|
16
|
+
|
17
|
+
def resend
|
18
|
+
resend_it
|
19
|
+
end
|
20
|
+
|
21
|
+
def resend_encrypted_only
|
22
|
+
resend_it(encrypted_only: true)
|
23
|
+
end
|
24
|
+
|
25
|
+
def resend_cc
|
26
|
+
resend_it_cc
|
27
|
+
end
|
28
|
+
|
29
|
+
def resend_cc_encrypted_only
|
30
|
+
resend_it_cc(encrypted_only: true)
|
31
|
+
end
|
32
|
+
|
33
|
+
def resend_unencrypted
|
34
|
+
do_resend_unencrypted(:to)
|
35
|
+
end
|
36
|
+
|
37
|
+
def resend_cc_unencrypted
|
38
|
+
do_resend_unencrypted(:cc)
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
|
45
|
+
def do_resend_unencrypted(target)
|
46
|
+
if ! resend_recipients_valid?
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
|
50
|
+
recip_map = Hash[Array(@arguments).map { |email| [email, ''] } ]
|
51
|
+
|
52
|
+
if do_resend(recip_map, target, false)
|
53
|
+
mail.add_subject_prefix_out!
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def resend_it_cc(encrypted_only: false)
|
58
|
+
if ! resend_recipients_valid?
|
59
|
+
return false
|
60
|
+
end
|
61
|
+
|
62
|
+
recip_map = map_with_keys(encrypted_only: encrypted_only)
|
63
|
+
|
64
|
+
# Only continue if all recipients are still here.
|
65
|
+
if recip_map.size < @arguments.size
|
66
|
+
recip_map.keys.each do |aborted_sender|
|
67
|
+
@mail.add_pseudoheader(:error, I18n.t('keyword_handlers.resend.aborted', email: aborted_sender))
|
68
|
+
end
|
69
|
+
return
|
70
|
+
end
|
71
|
+
|
72
|
+
if do_resend(recip_map, :cc, encrypted_only)
|
73
|
+
@mail.add_subject_prefix_out!
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def resend_it(encrypted_only: false)
|
78
|
+
if ! resend_recipients_valid?
|
79
|
+
return false
|
80
|
+
end
|
81
|
+
|
82
|
+
recip_map = map_with_keys(encrypted_only: encrypted_only)
|
83
|
+
|
84
|
+
resent_stati = recip_map.map do |email, key|
|
85
|
+
do_resend({email => key}, :to, encrypted_only)
|
86
|
+
end
|
87
|
+
|
88
|
+
if resent_stati.include?(true)
|
89
|
+
# At least one message has been resent
|
90
|
+
@mail.add_subject_prefix_out!
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def do_resend(recipients_map, to_or_cc, encrypted_only)
|
95
|
+
if recipients_map.empty?
|
96
|
+
return
|
97
|
+
end
|
98
|
+
|
99
|
+
gpg_opts = make_gpg_opts(recipients_map, encrypted_only)
|
100
|
+
if gpg_opts == false
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
|
104
|
+
# Compose and send email
|
105
|
+
new = @mail.clean_copy
|
106
|
+
new[to_or_cc] = recipients_map.keys
|
107
|
+
new.add_public_footer!
|
108
|
+
new.sender = @list.bounce_address
|
109
|
+
# `dup` gpg_opts because `deliver` changes their value and we need them
|
110
|
+
# below to determine encryption!
|
111
|
+
new.gpg gpg_opts.dup
|
112
|
+
|
113
|
+
if new.deliver
|
114
|
+
add_resent_headers(recipients_map, to_or_cc, gpg_opts[:encrypt])
|
115
|
+
return true
|
116
|
+
else
|
117
|
+
add_error_header(recipients_map)
|
118
|
+
return false
|
119
|
+
end
|
120
|
+
rescue Net::SMTPFatalError => exc
|
121
|
+
add_error_header(recipients_map)
|
122
|
+
logger.error "Error while sending: #{exc}"
|
123
|
+
return false
|
124
|
+
end
|
125
|
+
|
126
|
+
def map_with_keys(encrypted_only:)
|
127
|
+
Array(@arguments).inject({}) do |hash, email|
|
128
|
+
keys = @list.keys(email)
|
129
|
+
# Exclude unusable keys.
|
130
|
+
usable_keys = keys.select { |key| key.usable_for?(:encrypt) }
|
131
|
+
case usable_keys.size
|
132
|
+
when 1
|
133
|
+
hash[email] = usable_keys.first
|
134
|
+
when 0
|
135
|
+
if encrypted_only
|
136
|
+
# Don't add the email to the result to exclude it from the
|
137
|
+
# recipients.
|
138
|
+
add_resend_msg(email, :error, 'not_resent_no_keys', usable_keys.size, keys.size)
|
139
|
+
else
|
140
|
+
hash[email] = ''
|
141
|
+
end
|
142
|
+
else
|
143
|
+
# Always report this situation, regardless of sending or not. It's
|
144
|
+
# bad and should be fixed.
|
145
|
+
add_resend_msg(email, :notice, 'not_resent_encrypted_no_keys', usable_keys.size, keys.size)
|
146
|
+
if ! encrypted_only
|
147
|
+
hash[email] = ''
|
148
|
+
end
|
149
|
+
end
|
150
|
+
hash
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def make_gpg_opts(recipients_map, encrypted_only)
|
155
|
+
gpg_opts = @list.gpg_sign_options
|
156
|
+
# Do all recipients have a key?
|
157
|
+
if recipients_map.values.map(&:class).uniq == [GPGME::Key]
|
158
|
+
gpg_opts.merge!(encrypt: true)
|
159
|
+
elsif encrypted_only
|
160
|
+
false
|
161
|
+
end
|
162
|
+
gpg_opts
|
163
|
+
end
|
164
|
+
|
165
|
+
def add_resend_msg(email, severity, msg, usable_keys_size, all_keys_size)
|
166
|
+
@mail.add_pseudoheader(severity, I18n.t("keyword_handlers.resend.#{msg}", email: email, usable_keys: usable_keys_size, all_keys: all_keys_size))
|
167
|
+
end
|
168
|
+
|
169
|
+
def add_error_header(recipients_map)
|
170
|
+
@mail.add_pseudoheader(:error, "Resending to #{recipients_map.keys.join(', ')} failed, please check the logs!")
|
171
|
+
end
|
172
|
+
|
173
|
+
def add_resent_headers(recipients_map, to_or_cc, sent_encrypted)
|
174
|
+
if sent_encrypted
|
175
|
+
prefix = I18n.t('keyword_handlers.resend.encrypted_to')
|
176
|
+
str = "\n" + recipients_map.map do |email, key|
|
177
|
+
"#{email} (#{key.fingerprint})"
|
178
|
+
end.join(",\n")
|
179
|
+
else
|
180
|
+
prefix = I18n.t('keyword_handlers.resend.unencrypted_to')
|
181
|
+
str = ' ' + recipients_map.keys.join(', ')
|
182
|
+
end
|
183
|
+
headername = resent_header_name(to_or_cc)
|
184
|
+
@mail.add_pseudoheader(headername, "#{prefix}#{str}")
|
185
|
+
end
|
186
|
+
|
187
|
+
def resent_header_name(to_or_cc)
|
188
|
+
if to_or_cc.to_s == 'to'
|
189
|
+
'resent'
|
190
|
+
else
|
191
|
+
'resent_cc'
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def resend_recipients_valid?
|
196
|
+
all_valid = true
|
197
|
+
Array(@arguments).each do |address|
|
198
|
+
if ! address.match(Conf::EMAIL_REGEXP)
|
199
|
+
mail.add_pseudoheader(:error, I18n.t('keyword_handlers.resend.invalid_recipient', address: address))
|
200
|
+
all_valid = false
|
201
|
+
end
|
202
|
+
end
|
203
|
+
all_valid
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|