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.
- checksums.yaml +5 -5
- data/README.md +138 -0
- data/Rakefile +136 -0
- data/bin/pinentry-clearpassphrase +72 -0
- data/bin/schleuder +9 -89
- data/bin/schleuder-api-daemon +4 -0
- data/db/migrate/20140501103532_create_lists.rb +39 -0
- data/db/migrate/20140501112859_create_subscriptions.rb +21 -0
- data/db/migrate/201508092100_add_language_to_lists.rb +11 -0
- data/db/migrate/20150812165700_change_keywords_admin_only_defaults.rb +8 -0
- data/db/migrate/20150813235800_add_forward_all_incoming_to_admins.rb +11 -0
- data/db/migrate/201508141727_change_send_encrypted_only_default.rb +8 -0
- data/db/migrate/201508222143_add_logfiles_to_keep_to_lists.rb +11 -0
- data/db/migrate/201508261723_rename_delivery_disabled_to_delivery_enabled_and_change_default.rb +14 -0
- data/db/migrate/201508261815_strip_gpg_passphrase.rb +11 -0
- data/db/migrate/201508261827_remove_default_mime.rb +9 -0
- data/db/migrate/20160501172700_fix_headers_to_meta_defaults.rb +8 -0
- data/db/migrate/20170713215059_add_internal_footer_to_list.rb +11 -0
- data/db/schema.rb +62 -0
- data/etc/init.d/schleuder-api-daemon +87 -0
- data/etc/list-defaults.yml +123 -0
- data/etc/postfix/schleuder_sqlite.cf +28 -0
- data/etc/schleuder-api-daemon.service +10 -0
- data/etc/schleuder.cron.weekly +6 -0
- data/etc/schleuder.yml +61 -0
- data/lib/schleuder-api-daemon.rb +420 -0
- data/lib/schleuder.rb +81 -47
- data/lib/schleuder/cli.rb +334 -0
- data/lib/schleuder/cli/cert.rb +24 -0
- data/lib/schleuder/cli/schleuder_cert_manager.rb +84 -0
- data/lib/schleuder/cli/subcommand_fix.rb +11 -0
- data/lib/schleuder/conf.rb +131 -0
- data/lib/schleuder/errors/active_model_error.rb +15 -0
- data/lib/schleuder/errors/base.rb +17 -0
- data/lib/schleuder/errors/decryption_failed.rb +16 -0
- data/lib/schleuder/errors/fatal_error.rb +13 -0
- data/lib/schleuder/errors/file_not_found.rb +14 -0
- data/lib/schleuder/errors/invalid_listname.rb +13 -0
- data/lib/schleuder/errors/key_adduid_failed.rb +13 -0
- data/lib/schleuder/errors/key_generation_failed.rb +16 -0
- data/lib/schleuder/errors/keyword_admin_only.rb +13 -0
- data/lib/schleuder/errors/list_exists.rb +13 -0
- data/lib/schleuder/errors/list_not_found.rb +14 -0
- data/lib/schleuder/errors/list_property_missing.rb +14 -0
- data/lib/schleuder/errors/listdir_problem.rb +16 -0
- data/lib/schleuder/errors/loading_list_settings_failed.rb +14 -0
- data/lib/schleuder/errors/message_empty.rb +14 -0
- data/lib/schleuder/errors/message_not_from_admin.rb +13 -0
- data/lib/schleuder/errors/message_sender_not_subscribed.rb +13 -0
- data/lib/schleuder/errors/message_too_big.rb +14 -0
- data/lib/schleuder/errors/message_unauthenticated.rb +13 -0
- data/lib/schleuder/errors/message_unencrypted.rb +13 -0
- data/lib/schleuder/errors/message_unsigned.rb +13 -0
- data/lib/schleuder/errors/standard_error.rb +5 -0
- data/lib/schleuder/errors/too_many_keys.rb +17 -0
- data/lib/schleuder/errors/unknown_list_option.rb +14 -0
- data/lib/schleuder/filters/auth_filter.rb +39 -0
- data/lib/schleuder/filters/bounces_filter.rb +12 -0
- data/lib/schleuder/filters/forward_filter.rb +17 -0
- data/lib/schleuder/filters/forward_incoming.rb +13 -0
- data/lib/schleuder/filters/hotmail_message_filter.rb +25 -0
- data/lib/schleuder/filters/max_message_size.rb +14 -0
- data/lib/schleuder/filters/request_filter.rb +26 -0
- data/lib/schleuder/filters/send_key_filter.rb +20 -0
- data/lib/schleuder/filters/strip_alternative_filter.rb +21 -0
- data/lib/schleuder/filters_runner.rb +83 -0
- data/lib/schleuder/gpgme/ctx.rb +274 -0
- data/lib/schleuder/gpgme/import_status.rb +27 -0
- data/lib/schleuder/gpgme/key.rb +212 -0
- data/lib/schleuder/gpgme/sub_key.rb +13 -0
- data/lib/schleuder/gpgme/user_id.rb +22 -0
- data/lib/schleuder/list.rb +318 -127
- data/lib/schleuder/list_builder.rb +139 -0
- data/lib/schleuder/listlogger.rb +31 -0
- data/lib/schleuder/logger.rb +23 -0
- data/lib/schleuder/logger_notifications.rb +69 -0
- data/lib/schleuder/mail/message.rb +482 -0
- data/lib/schleuder/mail/parts_list.rb +9 -0
- data/lib/schleuder/plugin_runners/base.rb +91 -0
- data/lib/schleuder/plugin_runners/list_plugins_runner.rb +24 -0
- data/lib/schleuder/plugin_runners/request_plugins_runner.rb +27 -0
- data/lib/schleuder/plugins/attach_listkey.rb +17 -0
- data/lib/schleuder/plugins/get_version.rb +7 -0
- data/lib/schleuder/plugins/key_management.rb +113 -0
- data/lib/schleuder/plugins/list_management.rb +15 -0
- data/lib/schleuder/plugins/resend.rb +196 -0
- data/lib/schleuder/plugins/sign_this.rb +46 -0
- data/lib/schleuder/plugins/subscription_management.rb +140 -0
- data/lib/schleuder/runner.rb +130 -0
- data/lib/schleuder/subscription.rb +98 -0
- data/lib/schleuder/validators/boolean_validator.rb +7 -0
- data/lib/schleuder/validators/email_validator.rb +7 -0
- data/lib/schleuder/validators/fingerprint_validator.rb +7 -0
- data/lib/schleuder/validators/greater_than_zero_validator.rb +7 -0
- data/lib/schleuder/validators/no_line_breaks_validator.rb +7 -0
- data/lib/schleuder/version.rb +1 -1
- data/locales/de.yml +179 -0
- data/locales/en.yml +179 -0
- metadata +305 -108
- checksums.yaml.gz.sig +0 -3
- data.tar.gz.sig +0 -2
- data/LICENSE +0 -339
- data/README +0 -32
- data/bin/schleuder-fix-gem-dependencies +0 -37
- data/bin/schleuder-init-setup +0 -37
- data/bin/schleuder-migrate-v2.1-to-v2.2 +0 -225
- data/bin/schleuder-newlist +0 -413
- data/contrib/check-expired-keys.rb +0 -60
- data/contrib/mutt-schleuder-colors.rc +0 -10
- data/contrib/mutt-schleuder-resend.vim +0 -24
- data/contrib/smtpserver.rb +0 -76
- data/ext/default-list.conf +0 -149
- data/ext/default-members.conf +0 -7
- data/ext/list.conf.example +0 -14
- data/ext/schleuder.conf +0 -64
- data/lib/schleuder/archiver.rb +0 -46
- data/lib/schleuder/crypt.rb +0 -210
- data/lib/schleuder/errors.rb +0 -5
- data/lib/schleuder/list_config.rb +0 -146
- data/lib/schleuder/log/listlogger.rb +0 -57
- data/lib/schleuder/log/outputter/emailoutputter.rb +0 -120
- data/lib/schleuder/log/outputter/metaemailoutputter.rb +0 -50
- data/lib/schleuder/log/schleuderlogger.rb +0 -34
- data/lib/schleuder/mail.rb +0 -873
- data/lib/schleuder/mailer.rb +0 -26
- data/lib/schleuder/member.rb +0 -69
- data/lib/schleuder/plugin.rb +0 -54
- data/lib/schleuder/processor.rb +0 -363
- data/lib/schleuder/schleuder_config.rb +0 -75
- data/lib/schleuder/storage.rb +0 -84
- data/lib/schleuder/utils.rb +0 -80
- data/man/schleuder-newlist.8 +0 -174
- data/man/schleuder.8 +0 -416
- data/plugins/README +0 -20
- data/plugins/manage_keys_plugin.rb +0 -113
- data/plugins/manage_members_plugin.rb +0 -156
- data/plugins/manage_self_plugin.rb +0 -26
- data/plugins/resend_plugin.rb +0 -35
- data/plugins/sign_this_plugin.rb +0 -14
- data/plugins/version_plugin.rb +0 -12
- 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,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
|