schleuder 2.2.4 → 3.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -1,50 +0,0 @@
|
|
1
|
-
module Schleuder
|
2
|
-
class MetaEmailOutputter < EmailOutputter
|
3
|
-
# TODO: refactor (merge?) with EmailOutputter
|
4
|
-
def send_mail
|
5
|
-
if @send_mail_lock
|
6
|
-
self.level = Log4r::OFF # it's possible that the loglevel haven't yet been changed
|
7
|
-
Schleuder.log.warn 'This is a loop in sending a mail in the MetaEmailOutputter, breaking!'
|
8
|
-
return false
|
9
|
-
end
|
10
|
-
@send_mail_lock = true
|
11
|
-
Schleuder.log.info { 'notifying superadmin' }
|
12
|
-
begin
|
13
|
-
mail = makeemail
|
14
|
-
mail.to = Schleuder.config.superadminaddr
|
15
|
-
r = Member.new('email' => Schleuder.config.superadminaddr)
|
16
|
-
m = mail.individualize(r)
|
17
|
-
if ! Processor.send(m, r, true, Schleuder.config.superadminaddr).first
|
18
|
-
m.body = @altmsg
|
19
|
-
m.content_type = 'text/plain'
|
20
|
-
Processor.send(m, r, false, Schleuder.config.superadminaddr)
|
21
|
-
end
|
22
|
-
rescue => e
|
23
|
-
Schleuder.log.warn "An error occurred while reporting an error: #{e.message}\n#{e.backtrace[0..3].join("\n")}"
|
24
|
-
Schleuder.log.warn "Sending emergency notice to superadmin"
|
25
|
-
mail = "From: root@localhost
|
26
|
-
To: #{Schleuder.config.superadminaddr}
|
27
|
-
Date: #{Time.now.strftime('%a, %d %b %Y %H:%M:%S %z')}
|
28
|
-
Subject: Error
|
29
|
-
|
30
|
-
Serious errors happened working for list #{Schleuder.listname}.
|
31
|
-
Please check the logs!
|
32
|
-
"
|
33
|
-
Mailer.send mail, Schleuder.config.superadminaddr, 'root@localhost', true
|
34
|
-
end
|
35
|
-
Schleuder.log.info { 'Notifying done' }
|
36
|
-
rescue => e
|
37
|
-
# switch off logging per email, else we create loops here!
|
38
|
-
self.level = Log4r::OFF
|
39
|
-
if e.message.frozen?
|
40
|
-
Schleuder.log.warn "A problematic error happened. I'll better dump the original message to preserve it:\n\n#{Schleuder.origmsg}"
|
41
|
-
else
|
42
|
-
e.message << "\nThis is very problematic, I'll better dump the original message to preserve it:\n\n#{Schleuder.origmsg}"
|
43
|
-
end
|
44
|
-
Schleuder.log.fatal { e }
|
45
|
-
ensure
|
46
|
-
@send_mail_lock = false
|
47
|
-
@buff.clear
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
module Schleuder
|
2
|
-
class SchleuderLogger < Log4r::Logger
|
3
|
-
# Instantiates a logger to be used outside of list-contexts and sets
|
4
|
-
# outputters and level.
|
5
|
-
def initialize
|
6
|
-
# The name 'log4r' is special: it is considered as meta-logger by Log4r
|
7
|
-
# and receives log_internal()-messages.
|
8
|
-
super 'log4r'
|
9
|
-
# The initial log_level is inherited by all outputters.
|
10
|
-
@level = eval("Log4r::#{Schleuder.config.log_level.upcase}")
|
11
|
-
if Schleuder.config.log_file == 'syslog'
|
12
|
-
formatter = Log4r::PatternFormatter.new(:pattern => "%M")
|
13
|
-
require 'log4r/outputter/syslogoutputter'
|
14
|
-
add Log4r::SyslogOutputter.new('syslog',
|
15
|
-
{ :level => @level,
|
16
|
-
:ident => 'schleuder',
|
17
|
-
:facility => "LOG_MAIL",
|
18
|
-
:formatter => formatter }
|
19
|
-
)
|
20
|
-
else
|
21
|
-
pattern = "%d Schleuder %l\t%M"
|
22
|
-
formatter = Log4r::PatternFormatter.new(:pattern => pattern)
|
23
|
-
add Log4r::FileOutputter.new('file',
|
24
|
-
{ :level => @level,
|
25
|
-
:filename => Schleuder.config.log_file,
|
26
|
-
:formatter => formatter }
|
27
|
-
)
|
28
|
-
end
|
29
|
-
add MetaEmailOutputter.new('email',
|
30
|
-
{:to => Schleuder.config.superadminaddr,
|
31
|
-
:immediate_at => 'ERROR, FATAL'})
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
data/lib/schleuder/mail.rb
DELETED
@@ -1,873 +0,0 @@
|
|
1
|
-
module Schleuder
|
2
|
-
class Mail < TMail::Mail
|
3
|
-
|
4
|
-
# Schleuder::Member's the mail shall be sent to
|
5
|
-
attr_accessor :recipients
|
6
|
-
|
7
|
-
# Additional data that is to be stored into internally sent
|
8
|
-
# mails. Must be a Hash.
|
9
|
-
attr_accessor :metadata
|
10
|
-
|
11
|
-
# Indicator if the incoming mail was encrypted
|
12
|
-
attr_accessor :in_encrypted
|
13
|
-
|
14
|
-
# Indicator if (and by whom) the incoming mail was signed. Is +false+ or a
|
15
|
-
# GPGME::Signature.
|
16
|
-
attr_accessor :in_signed
|
17
|
-
|
18
|
-
attr_accessor :resend_to
|
19
|
-
|
20
|
-
# The incoming mail in raw form (string). Needed to verify detached
|
21
|
-
# signatures (see +decrypt!+)
|
22
|
-
attr_accessor :original_message
|
23
|
-
|
24
|
-
# Shall we consider this email to be sent by the list-admin?
|
25
|
-
attr_reader :from_admin
|
26
|
-
|
27
|
-
# Shall we consider this email to be sent by a list-member?
|
28
|
-
attr_reader :from_member
|
29
|
-
|
30
|
-
# internal message_id we'll use for all outgoing mails
|
31
|
-
@@schleuder_message_id = nil
|
32
|
-
|
33
|
-
def initialize(*data)
|
34
|
-
super(*data)
|
35
|
-
@resend_to = []
|
36
|
-
@metadata = {:resent_to => [], :notice => [], :warning => [], :error => []}
|
37
|
-
end
|
38
|
-
|
39
|
-
# Overwrites TMail.parse to store the original incoming data. We possibly
|
40
|
-
# need that to verify pgp-signatures (TMail changes headers which
|
41
|
-
# invalidates the signature)
|
42
|
-
def self.parse(string)
|
43
|
-
foo = super(string)
|
44
|
-
foo.original_message = string
|
45
|
-
foo
|
46
|
-
end
|
47
|
-
|
48
|
-
# Desctructivly decrypt/verify +self+.
|
49
|
-
def decrypt!
|
50
|
-
# Note: We don't recurse into nested Mime-parts. Only the first level
|
51
|
-
# will be touched
|
52
|
-
if self.multipart? && !self.type_param('protocol').nil? && self.type_param('protocol').match(/^application\/pgp.*/)
|
53
|
-
Schleuder.log.debug 'mail is pgp/mime-formatted'
|
54
|
-
if self.sub_type == 'signed'
|
55
|
-
Schleuder.log.debug 'mail is signed but not encrypted'
|
56
|
-
Schleuder.log.debug 'Parsing original_message to split content from signature'
|
57
|
-
|
58
|
-
# first, identify the boundary
|
59
|
-
bm = self.original_message.match(/boundary="?'?([^"';]*)/)
|
60
|
-
boundary = "--" + Regexp.escape(bm[1])
|
61
|
-
Schleuder.log.debug "Identified boundary: #{boundary}"
|
62
|
-
# next, find the signed string between the first and the second-last boundary
|
63
|
-
signed_string = self.original_message.match(/.*#{boundary}\r?\n(.*?)\r?\n#{boundary}.*?#{boundary}.*?/m)[1]
|
64
|
-
# Add CRs, which probably have been stripped by the MTA
|
65
|
-
signed_string.gsub!(/\n/, "\r\n") unless signed_string.include?("\r")
|
66
|
-
Schleuder.log.debug "Identified signed string"
|
67
|
-
# third, find the pgp-signature
|
68
|
-
signature = self.original_message.match(/.*(-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*/m)[1] rescue ''
|
69
|
-
Schleuder.log.debug "Identified signature"
|
70
|
-
# finally, verify
|
71
|
-
Schleuder.log.info "Verifying"
|
72
|
-
foo, self.in_signed = self.crypt.verify(signature, signed_string)
|
73
|
-
plaintext = self.parts[0].to_s
|
74
|
-
elsif self.sub_type == 'encrypted'
|
75
|
-
Schleuder.log.debug 'mail is encrypted (and possibly signed)'
|
76
|
-
plaintext, self.in_encrypted, self.in_signed = self.crypt.decrypt(self.parts[1].body)
|
77
|
-
else
|
78
|
-
Schleuder.log.warn "Strange: message claims to be pgp/mime but neither to be signed nor encrypted"
|
79
|
-
end
|
80
|
-
# replace encrypted content with cleartext content
|
81
|
-
plainmsg = Mail.parse(plaintext)
|
82
|
-
#Schleuder.log.debug "plainmsg: #{plainmsg.to_s.inspect}"
|
83
|
-
# test for signed content within the previously encrypted content
|
84
|
-
if plainmsg._content_type_stripped == 'multipart/signed'
|
85
|
-
Schleuder.log.debug "Found signed message as cleartext, recurseing once"
|
86
|
-
plainmsg.original_message = plaintext
|
87
|
-
plainmsg.decrypt!
|
88
|
-
Schleuder.log.debug "End of recursion"
|
89
|
-
self.in_signed = plainmsg.in_signed
|
90
|
-
end
|
91
|
-
# get headers and body(parts) into self
|
92
|
-
if plainmsg.multipart?
|
93
|
-
Schleuder.log.debug "plainmsg.multipart? => true"
|
94
|
-
self.parts.clear
|
95
|
-
plainmsg.parts.each do |p|
|
96
|
-
self.parts.push(Mail.parse(p.to_s))
|
97
|
-
end
|
98
|
-
else
|
99
|
-
Schleuder.log.debug "plainmsg.multipart? => false"
|
100
|
-
self.body = plainmsg.body
|
101
|
-
end
|
102
|
-
self.content_type = plainmsg._content_type
|
103
|
-
self.disposition = plainmsg._disposition
|
104
|
-
else
|
105
|
-
Schleuder.log.debug 'no pgp-mime found, looking for pgp-inline'
|
106
|
-
# Do we simply push everything to crypt as that should return plain
|
107
|
-
# text if it is feeded plain text? Or does crypt return only an error
|
108
|
-
# if there is nothing to do for it?
|
109
|
-
if self.multipart?
|
110
|
-
Schleuder.log.debug 'multipart found, looking for pgp content in each part'
|
111
|
-
self.parts.each do |part|
|
112
|
-
_decrypt_pgp_inline(part)
|
113
|
-
end
|
114
|
-
# If all parts are equally encrypted and signed mark the whole message as such
|
115
|
-
self.in_encrypted = self.parts.map{ |p| p.in_encrypted }.uniq == [true]
|
116
|
-
partsigs = self.parts.map do |p|
|
117
|
-
# Can't use to_s here, gpgme bombs.
|
118
|
-
p.in_signed.respond_to?(:fpr) ? p.in_signed.fpr : p.in_signed
|
119
|
-
end
|
120
|
-
if partsigs.uniq.size == 1
|
121
|
-
self.in_signed = self.parts.first.in_signed
|
122
|
-
end
|
123
|
-
else
|
124
|
-
Schleuder.log.debug 'single inline content found, looking for pgp content'
|
125
|
-
_decrypt_pgp_inline(self)
|
126
|
-
end
|
127
|
-
end
|
128
|
-
_parse_signature
|
129
|
-
end
|
130
|
-
|
131
|
-
# Destructivly encrypt and sign. +receiver+ must be a string or a Schleuder::Member
|
132
|
-
def encrypt!(receiver)
|
133
|
-
if receiver.kind_of?(String)
|
134
|
-
receiver = Member.new({'email' => receiver})
|
135
|
-
elsif ! receiver.kind_of?(Member)
|
136
|
-
raise "need a Member-object as argument, got '#{receiver.inspect}'"
|
137
|
-
end
|
138
|
-
|
139
|
-
Schleuder.log.info "Encrypting for #{receiver.inspect}"
|
140
|
-
|
141
|
-
key, msg = receiver.key
|
142
|
-
if key == false
|
143
|
-
Schleuder.log.warn msg.capitalize
|
144
|
-
return [false, msg]
|
145
|
-
elsif key.nil?
|
146
|
-
Schleuder.log.warn "No public key found for '#{keystring}'"
|
147
|
-
return [false, 'no public key found']
|
148
|
-
end
|
149
|
-
|
150
|
-
# choose pgp-variant from addresses value oder config.default_mime
|
151
|
-
pgpvariant = receiver.mime || Schleuder.list.config.default_mime
|
152
|
-
|
153
|
-
case pgpvariant.upcase
|
154
|
-
when 'PLAIN'
|
155
|
-
Schleuder.log.debug "encrypting as text/plain"
|
156
|
-
if self.multipart?
|
157
|
-
self.parts.each_with_index do |p,i|
|
158
|
-
self.parts[i] = _encrypt_pgp_inline(p, receiver)
|
159
|
-
end
|
160
|
-
self.content_type = 'multipart/mixed'
|
161
|
-
self.encoding = nil
|
162
|
-
else
|
163
|
-
foo = _encrypt_pgp_inline(self, receiver)
|
164
|
-
self.body = foo.body
|
165
|
-
foo.header.each do |k,v|
|
166
|
-
self[k] = v.to_s rescue nil
|
167
|
-
end
|
168
|
-
end
|
169
|
-
when 'MIME'
|
170
|
-
Schleuder.log.debug "encrypting pgp-mime"
|
171
|
-
gpgcontent = Mail.new
|
172
|
-
if self.multipart?
|
173
|
-
self.parts.each do |p|
|
174
|
-
gpgcontent.parts.push(p)
|
175
|
-
end
|
176
|
-
else
|
177
|
-
gpgcontent.body = self.body
|
178
|
-
gpgcontent.content_type = self._content_type
|
179
|
-
gpgcontent.charset = self.charset || 'UTF-8'
|
180
|
-
end
|
181
|
-
|
182
|
-
gpgcontainer = _new_part(crypt.encrypt_str(gpgcontent.to_s, receiver), 'application/octet-stream', self.charset)
|
183
|
-
gpgcontainer.set_disposition('inline', {:filename => 'message.asc'})
|
184
|
-
|
185
|
-
mimecontainer = _new_part("Version: 1\n", 'application/pgp-encrypted')
|
186
|
-
mimecontainer.disposition = 'attachment'
|
187
|
-
|
188
|
-
self.encoding = nil
|
189
|
-
self.body = ""
|
190
|
-
self.parts.clear
|
191
|
-
self.parts.push(mimecontainer)
|
192
|
-
self.parts.push(gpgcontainer)
|
193
|
-
self.set_content_type('multipart', 'encrypted', {:protocol => 'application/pgp-encrypted'})
|
194
|
-
self.mime_version = 1.0
|
195
|
-
|
196
|
-
when 'APPL'
|
197
|
-
Schleuder.log.error "mime-setting 'APPL' is deprecated, use 'MIME' instead for user '#{receiver.email}'"
|
198
|
-
else
|
199
|
-
Schleuder.log.error "Unknown mime-setting for user '#{receiver.email}': #{pgpvariant}"
|
200
|
-
end
|
201
|
-
return true
|
202
|
-
end
|
203
|
-
|
204
|
-
# Clearsign the message. Returns a string (raw msg, which must not be
|
205
|
-
# changed anymore) or a Schleuder::Mail.
|
206
|
-
def sign
|
207
|
-
Schleuder.log.debug "signing message"
|
208
|
-
if (self.to || []).length != 1
|
209
|
-
Schleuder.log.error "I need exactly one recipient in To-header. Found this: #{self.to.inspect}"
|
210
|
-
return false
|
211
|
-
end
|
212
|
-
member = Schleuder.list.find_member_by_email(self.to.to_s)
|
213
|
-
# ugly but necessary: member might be false and member.mime might be nil
|
214
|
-
pgpvariant = member.mime || Schleuder.list.config.default_mime rescue Schleuder.list.config.default_mime
|
215
|
-
case pgpvariant.upcase
|
216
|
-
when 'MIME'
|
217
|
-
Schleuder.log.debug "signing MIME"
|
218
|
-
gpgcontainer = Mail.new
|
219
|
-
if self.multipart?
|
220
|
-
self.parts.each do |p|
|
221
|
-
if p.main_type.eql?('text')
|
222
|
-
# Encode QP (there might be trailing white space
|
223
|
-
p.body = p.body.to_s.split("\n").map do |line|
|
224
|
-
[line].pack("M")
|
225
|
-
end.join("\n")
|
226
|
-
p.encoding = 'Quoted-Printable'
|
227
|
-
end
|
228
|
-
gpgcontainer.parts.push(p)
|
229
|
-
end
|
230
|
-
self.parts.clear
|
231
|
-
else
|
232
|
-
# Encode QP (there might be trailing white space which is illegal
|
233
|
-
# according to RFC 3156).
|
234
|
-
# Don't call body() as that returns the decoded string
|
235
|
-
gpgcontainer.body = body.to_s.split("\n").map do |line|
|
236
|
-
[line].pack("M")
|
237
|
-
end.join("\n")
|
238
|
-
gpgcontainer.encoding = 'Quoted-Printable'
|
239
|
-
gpgcontainer.content_type = self._content_type
|
240
|
-
gpgcontainer.disposition = self._disposition
|
241
|
-
self.body = ''
|
242
|
-
end
|
243
|
-
self.encoding = ''
|
244
|
-
self.parts.push gpgcontainer
|
245
|
-
|
246
|
-
# This following part is quite complicated. We have to do it this way
|
247
|
-
# because TMail changes the mime-boundary on every encoded() (implicit
|
248
|
-
# in to_s()), which invalidates the signature
|
249
|
-
|
250
|
-
# TODO: refactor with encrypt!()
|
251
|
-
|
252
|
-
# First we create the signature-attachment and fill it with a dummy
|
253
|
-
dummy = "dummytobereplaced-#{Time.now.to_f}"
|
254
|
-
sigpart = _new_part(dummy, 'application/pgp-signature')
|
255
|
-
self.parts.push(sigpart)
|
256
|
-
|
257
|
-
# TODO: take care of micalg: the digest used for hashing the plaintext.
|
258
|
-
# RFC 3156 requires it to be set in the content-type. ruby-gpgme
|
259
|
-
# doesn't provide it to us, though.
|
260
|
-
# (Don't use symbols with TMail, it expects strings.)
|
261
|
-
self.set_content_type('multipart', 'signed', {'protocol' => 'application/pgp-signature', 'micalg' => 'pgp-sha1'})
|
262
|
-
|
263
|
-
# Then we dump the crafted msg
|
264
|
-
rawmsg = self.encoded
|
265
|
-
|
266
|
-
# get mime boundary from raw mail
|
267
|
-
bm = rawmsg.match(/boundary="?'?([^"';\r\n]*)/)
|
268
|
-
boundary = "--" + Regexp.escape(bm[1])
|
269
|
-
|
270
|
-
# Now get the to be signed string from the raw message
|
271
|
-
parts = rawmsg.match(/(.*#{boundary}\r?\n)(.*\r?\n)(\r?\n#{boundary}.*?#{boundary}.*)/m)
|
272
|
-
|
273
|
-
if parts
|
274
|
-
# For some reason I don't manage to do this in one gsub-call... *snif*
|
275
|
-
tobesigned_crlf = parts[2].gsub(/\n/, "\r\n").gsub(/\r\r/, "\r")
|
276
|
-
rawmsg = "#{parts[1]}#{tobesigned_crlf}#{parts[3]}"
|
277
|
-
|
278
|
-
# sign the extracted part
|
279
|
-
sig = self.crypt.sign(tobesigned_crlf)
|
280
|
-
|
281
|
-
# replace dummy with signature
|
282
|
-
rawmsg.gsub!(/(.*)#{dummy}(.*?#{boundary}.*?)/m, "\\1#{sig}\\2")
|
283
|
-
rawmsg
|
284
|
-
else
|
285
|
-
Schleuder.log.error "Detection of mime parts in mail for #{self.to} with boundary #{bm} failed. This should not happen. -> Cannot sign outgoing mail."
|
286
|
-
|
287
|
-
false
|
288
|
-
end
|
289
|
-
when 'PLAIN'
|
290
|
-
Schleuder.log.debug "signing PLAIN"
|
291
|
-
if self.multipart?
|
292
|
-
self.parts.each_with_index do |p,i|
|
293
|
-
if p.disposition == 'attachment' || !p.disposition_param('filename').nil?
|
294
|
-
Schleuder.log.debug "found attachment, creating detached signature"
|
295
|
-
# attachment, need detached sig
|
296
|
-
container = Mail.new
|
297
|
-
container.parts.push Mail.parse(p.to_s)
|
298
|
-
|
299
|
-
sig = crypt.sign(p.body)
|
300
|
-
Schleuder.log.debug "sig: #{sig.inspect}"
|
301
|
-
sigpart = _new_part(sig, 'text/plain')
|
302
|
-
sigpart.set_disposition('attachment', {:filename => p.disposition_param('filename') + '.asc'})
|
303
|
-
container.parts.push Mail.parse(sigpart.to_s)
|
304
|
-
|
305
|
-
container.content_type = 'multipart/mixed'
|
306
|
-
|
307
|
-
self.parts[i] = Mail.parse(container.to_s)
|
308
|
-
else
|
309
|
-
p.body = crypt.clearsign(p.body)
|
310
|
-
end
|
311
|
-
end
|
312
|
-
else
|
313
|
-
self.body = crypt.clearsign(self.body)
|
314
|
-
end
|
315
|
-
self
|
316
|
-
|
317
|
-
else
|
318
|
-
Schleuder.log.error "Strange mime-setting: #{pgpvariant}. Don't know what to do with that, ignoring it."
|
319
|
-
self
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
# Create a new Schleuder::Mail-instance and copy/set selected headers.
|
324
|
-
def individualize(recv)
|
325
|
-
Schleuder.log.debug { "Individualizing mail for #{recv.inspect}" }
|
326
|
-
new = Mail.new
|
327
|
-
new.crypt = crypt
|
328
|
-
# add some headers we want to keep
|
329
|
-
new.content_type = self['content-type'].to_s if self['content-type'] && !self['content-type'].illegal?
|
330
|
-
new.disposition = self._disposition
|
331
|
-
new.encoding = self.encoding.to_s
|
332
|
-
new.subject = _quote_if_necessary(self.subject.to_s,'UTF-8')
|
333
|
-
|
334
|
-
new.message_id = Mail._schleuder_message_id
|
335
|
-
|
336
|
-
new.to = recv.email.to_s
|
337
|
-
new.date = Time.now
|
338
|
-
new.from = Schleuder.list.config.myaddr
|
339
|
-
new = _add_openpgp_header(new) if Schleuder.list.config.include_openpgp_header
|
340
|
-
if self.multipart?
|
341
|
-
self.parts.each do |p|
|
342
|
-
new.parts.push(Mail.parse(p.to_s))
|
343
|
-
end
|
344
|
-
else
|
345
|
-
new.body = self.body
|
346
|
-
end
|
347
|
-
new
|
348
|
-
end
|
349
|
-
|
350
|
-
def individualize_member(recv)
|
351
|
-
new = self.individualize(recv)
|
352
|
-
new = _message_ids(new) if Schleuder.list.config.keep_msgid
|
353
|
-
new = _list_headers(new) if Schleuder.list.config.include_list_headers
|
354
|
-
new
|
355
|
-
end
|
356
|
-
|
357
|
-
# Strips keywords from self and stores them in @+keywords+
|
358
|
-
def keywords
|
359
|
-
unless @keywords
|
360
|
-
@keywords = []
|
361
|
-
# we're looking for keywords only in the first mime-part
|
362
|
-
b = self
|
363
|
-
while b.multipart?
|
364
|
-
b = b.parts.first
|
365
|
-
end
|
366
|
-
# split to array to ease parsing
|
367
|
-
a = b.body.split("\n")
|
368
|
-
|
369
|
-
a.map! do |line|
|
370
|
-
if line.match(/^X-.*/i)
|
371
|
-
Schleuder.log.debug "found keyword-line: #{line.chomp}"
|
372
|
-
key, val = $&.split(/:|: | /, 2)
|
373
|
-
keyword = key.slice(2..-1).strip
|
374
|
-
if !self.from_admin && Schleuder.list.config.keywords_admin_only.include?(keyword)
|
375
|
-
Schleuder.log.info "Keyword '#{keyword}' is listed as admin only and mail is not from admin, skipping"
|
376
|
-
self.metadata[:error] << "Keyword '#{keyword}' is configured as admin-only."
|
377
|
-
# return the line to have it stay in the body
|
378
|
-
line
|
379
|
-
else
|
380
|
-
# Split values to catch multiple values separated by comma or the like (deprecated).
|
381
|
-
values = val.to_s.strip.split(/[,;]+/)
|
382
|
-
values << ' ' if values.empty?
|
383
|
-
values.each do |v|
|
384
|
-
Schleuder.log.info "Storing keyword #{keyword} with value #{v.inspect}"
|
385
|
-
@keywords << {keyword => v.to_s.strip}
|
386
|
-
end
|
387
|
-
nil
|
388
|
-
end
|
389
|
-
elsif line.chomp.empty?
|
390
|
-
line
|
391
|
-
else
|
392
|
-
# break on the first non-empty and non-command line so we don't parse
|
393
|
-
# the whole message (could be much data)
|
394
|
-
break
|
395
|
-
end
|
396
|
-
end
|
397
|
-
# delete nil's (lines formerly containing X-Commands) from array
|
398
|
-
a.compact!
|
399
|
-
# rejoin to string
|
400
|
-
b.body = a.join("\n")
|
401
|
-
# The whole procedure makes TMail parse and reformat the message, so now
|
402
|
-
# it's decoded utf-8
|
403
|
-
b.charset = 'UTF-8'
|
404
|
-
b.encoding = nil
|
405
|
-
Schleuder.log.debug "Inspecting found keywords: #{@keywords.inspect}"
|
406
|
-
end
|
407
|
-
@keywords
|
408
|
-
end
|
409
|
-
|
410
|
-
def request?
|
411
|
-
@request ||= self.to.to_a.include?(Schleuder.list.request_addr)
|
412
|
-
end
|
413
|
-
|
414
|
-
def bounce?
|
415
|
-
self.to.to_a.include?(Schleuder.list.bounce_addr) || \
|
416
|
-
( # empty Return-Path
|
417
|
-
['<>'].include?(self['Return-path'].to_s) || \
|
418
|
-
( # Auto-Submitted exists and does not equal 'no'
|
419
|
-
! ["no", ""].include?(self['Auto-Submitted'].to_s) && \
|
420
|
-
self['X-Cron-Env'].nil? # cron mails are also autosubmitted
|
421
|
-
)
|
422
|
-
)
|
423
|
-
end
|
424
|
-
|
425
|
-
# +require+s all found plugins, tests SomePlugin.match and if that returns
|
426
|
-
# true runs SomePlugin.process
|
427
|
-
def process_plugins!
|
428
|
-
if self.keywords.empty?
|
429
|
-
Schleuder.log.info 'No keywords present, skipping plugins'
|
430
|
-
else
|
431
|
-
Schleuder.config.plugins_dir.each do |plugins_dir|
|
432
|
-
if File.directory?(plugins_dir)
|
433
|
-
replymsg = ""
|
434
|
-
# We might want to replace the object later, which we can't do with self.
|
435
|
-
mail = self
|
436
|
-
plugins = {:request => [], :list => []}
|
437
|
-
ptype = (self.request? ? :request : :list)
|
438
|
-
used_keywords = []
|
439
|
-
Plugin.signing_key(self)
|
440
|
-
Dir[plugins_dir + '/*_plugin.rb'].each do |plugfile|
|
441
|
-
Schleuder.log.debug "Instanciating #{plugfile} as plugin"
|
442
|
-
require plugfile
|
443
|
-
# interpreting class name from file name
|
444
|
-
classname = File.basename(plugfile, '.rb').split('_').collect { |p| p.capitalize }.join
|
445
|
-
plugin = instance_eval(classname).new
|
446
|
-
Schleuder.log.debug "Storing plugin in plugin-list '#{plugin.plugin_type}'"
|
447
|
-
plugins[plugin.plugin_type] << plugin
|
448
|
-
end
|
449
|
-
# This is neither elegant nor fast, but it's got to live only until the rewrite.
|
450
|
-
Schleuder.log.debug "Processing #{ptype}-plugins"
|
451
|
-
self.keywords.each do |kwhash|
|
452
|
-
keyword = kwhash.keys.first
|
453
|
-
value = kwhash.values.first
|
454
|
-
Schleuder.log.debug "Looping for keyword #{keyword}"
|
455
|
-
plugins[ptype].each do |plugin|
|
456
|
-
command = keyword.downcase.gsub(/-/, '_')
|
457
|
-
Schleuder.log.debug "Does #{plugin.class}.respond_to? '#{command}'"
|
458
|
-
if plugin.respond_to?(command)
|
459
|
-
Schleuder.log.debug "Yes it does, executing #{plugin.class}.#{command}"
|
460
|
-
used_keywords << keyword
|
461
|
-
foo = plugin.send(command, mail, value)
|
462
|
-
case foo
|
463
|
-
when String
|
464
|
-
Schleuder.log.debug "Method returned a string, saving it for reply to sender."
|
465
|
-
replymsg << foo << "\n\n\n"
|
466
|
-
when Mail
|
467
|
-
Schleuder.log.debug "Method returned a Mail-object, replacing myself by it."
|
468
|
-
mail = foo
|
469
|
-
else
|
470
|
-
Schleuder.log.debug "Method returned '#{foo.inspect}', don't know how to handle, skipping."
|
471
|
-
end
|
472
|
-
next
|
473
|
-
else
|
474
|
-
Schleuder.log.debug "No it doesn't."
|
475
|
-
end
|
476
|
-
end
|
477
|
-
# Generate error-message if no plugin was executed
|
478
|
-
unless used_keywords.include?(keyword)
|
479
|
-
msg = "No #{ptype}-plugin responded to keyword #{keyword}"
|
480
|
-
Schleuder.log.debug msg
|
481
|
-
if mail.request?
|
482
|
-
replymsg << "Error: #{msg}.\n\n"
|
483
|
-
else
|
484
|
-
mail.metadata[:error] << msg
|
485
|
-
end
|
486
|
-
end
|
487
|
-
end
|
488
|
-
# Decide how to go on: reply or list?
|
489
|
-
if mail.request?
|
490
|
-
Schleuder.log.debug "Message is a request, sending plugins-output back to sender."
|
491
|
-
if replymsg.empty?
|
492
|
-
replymsg = 'The keywords you sent did not produce any output. If you feel this is an error please contact the adminisrators of this list.'.fmt
|
493
|
-
end
|
494
|
-
# This exits.
|
495
|
-
Plugin.reply(mail, replymsg, used_keywords)
|
496
|
-
end
|
497
|
-
else
|
498
|
-
Schleuder.log.error "#{plugins_dir} does not exist or is not readable!"
|
499
|
-
end
|
500
|
-
end
|
501
|
-
end
|
502
|
-
end
|
503
|
-
|
504
|
-
def add_prefix!
|
505
|
-
# only add if it's not already present
|
506
|
-
unless self.subject.index(Schleuder.list.config.prefix)
|
507
|
-
Schleuder.log.debug "adding prefix"
|
508
|
-
self.subject = Schleuder.list.config.prefix + " " + self.subject.to_s
|
509
|
-
end
|
510
|
-
end
|
511
|
-
|
512
|
-
def add_prefix_in!
|
513
|
-
# only add if it's not already present
|
514
|
-
unless self.subject.index(Schleuder.list.config.prefix_in)
|
515
|
-
Schleuder.log.debug "adding prefix_in"
|
516
|
-
self.subject = Schleuder.list.config.prefix_in + " " + self.subject.to_s
|
517
|
-
end
|
518
|
-
end
|
519
|
-
|
520
|
-
def add_prefix_out!
|
521
|
-
# only add if it's not already present
|
522
|
-
unless self.subject.index(Schleuder.list.config.prefix_out)
|
523
|
-
Schleuder.log.debug "adding prefix_out"
|
524
|
-
self.subject = Schleuder.list.config.prefix_out + " " + self.subject.to_s
|
525
|
-
end
|
526
|
-
end
|
527
|
-
|
528
|
-
def add_metadata!
|
529
|
-
Schleuder.log.info "Adding meta-information on old mail to new mail"
|
530
|
-
Schleuder.log.debug 'Generating meta-information'
|
531
|
-
meta = ''
|
532
|
-
Schleuder.list.config.headers_to_meta.each do |h|
|
533
|
-
h = h.to_s.capitalize
|
534
|
-
val = self.header_string(h) rescue '(not parsable)'
|
535
|
-
next if val.nil?
|
536
|
-
val = TMail::Unquoter.unquote_and_convert_to(val,'utf-8')
|
537
|
-
meta << "#{h}: #{val}\n"
|
538
|
-
end
|
539
|
-
meta << "Enc: #{_enc_str @in_encrypted}\n"
|
540
|
-
meta << "Sig: #{_sig_str @in_signed}\n"
|
541
|
-
if self.multipart?
|
542
|
-
self.parts.each_with_index do |p,i|
|
543
|
-
unless p.in_encrypted.nil? && p.in_signed.nil?
|
544
|
-
meta << "part #{i+1}:\n"
|
545
|
-
meta << " enc: #{_enc_str p.in_encrypted}\n"
|
546
|
-
meta << " sig: #{_sig_str p.in_signed}\n"
|
547
|
-
end
|
548
|
-
end
|
549
|
-
end
|
550
|
-
|
551
|
-
Schleuder.log.debug 'Adding extra meta data'
|
552
|
-
@metadata.each do |name, content|
|
553
|
-
meta << "#{name.to_s.gsub(/_/, '-').capitalize}: #{(content.join('; ') || content.to_s)}\n" unless content.empty?
|
554
|
-
end
|
555
|
-
meta << "\n"
|
556
|
-
|
557
|
-
# insert oder prepend to the message
|
558
|
-
if self.first_part._content_type_stripped == 'text/plain'
|
559
|
-
Schleuder.log.debug "Glueing meta data into first part of message"
|
560
|
-
self.first_part.body = meta + self.first_part.body
|
561
|
-
# body is now utf-8 and decoded!
|
562
|
-
self.first_part.charset = 'UTF-8'
|
563
|
-
self.first_part.encoding = ''
|
564
|
-
else
|
565
|
-
# make the message multipart and prepend the meta-part
|
566
|
-
self.to_multipart! unless self.multipart?
|
567
|
-
Schleuder.log.debug "Prepending meta data as own mime part to message"
|
568
|
-
self.parts.unshift _new_part(meta, 'text/plain', 'UTF-8')
|
569
|
-
end
|
570
|
-
|
571
|
-
end
|
572
|
-
|
573
|
-
# Adds Schleuder::ListConfig.public_footer to the end of the body of self
|
574
|
-
# or the body of the first mimepart (if one of those is text/plain) or
|
575
|
-
# appends it as a new mimepart.
|
576
|
-
def add_public_footer!
|
577
|
-
if Schleuder.list.config.public_footer.strip.empty?
|
578
|
-
return false
|
579
|
-
end
|
580
|
-
Schleuder.log.debug "appending public footer"
|
581
|
-
footer = "\n\n-- \n#{Schleuder.list.config.public_footer}\n"
|
582
|
-
|
583
|
-
if self.first_part._content_type_stripped == 'text/plain'
|
584
|
-
self.first_part.body = self.first_part.body.to_s + footer
|
585
|
-
else
|
586
|
-
self.to_multipart! unless self.multipart?
|
587
|
-
self.parts.push _new_part(footer, 'text/plain', 'UTF-8')
|
588
|
-
end
|
589
|
-
end
|
590
|
-
|
591
|
-
|
592
|
-
def to_multipart!
|
593
|
-
# skip if already multipart
|
594
|
-
return false if self.multipart?
|
595
|
-
Schleuder.log.debug "Making message multipart"
|
596
|
-
# else move the body into a mime-part
|
597
|
-
p = _new_part(self.body, self['content-type'].to_s, self.charset, self.encoding)
|
598
|
-
p.disposition = self._disposition
|
599
|
-
self.parts.push p
|
600
|
-
self.body = ''
|
601
|
-
self.set_content_type 'multipart', 'mixed', {:boundary => TMail.new_boundary}
|
602
|
-
self.disposition = ''
|
603
|
-
end
|
604
|
-
|
605
|
-
def first_part
|
606
|
-
if self.multipart?
|
607
|
-
self.parts.first
|
608
|
-
else
|
609
|
-
self
|
610
|
-
end
|
611
|
-
end
|
612
|
-
|
613
|
-
def _content_type(default = 'text/plain')
|
614
|
-
(self['content-type'] || default).to_s rescue default
|
615
|
-
end
|
616
|
-
|
617
|
-
def _content_type_stripped(default = nil)
|
618
|
-
( default && _content_type(default) || _content_type() ).to_s.split(';').first
|
619
|
-
end
|
620
|
-
|
621
|
-
def _disposition
|
622
|
-
(self['content-disposition'] || '').to_s rescue ''
|
623
|
-
end
|
624
|
-
|
625
|
-
def from_member_address?
|
626
|
-
from.all? { |a| Schleuder.list.find_member_by_address(a) }
|
627
|
-
end
|
628
|
-
|
629
|
-
def from_admin_address?
|
630
|
-
from.all? { |a| Schleuder.list.find_admin_by_address(a) }
|
631
|
-
end
|
632
|
-
|
633
|
-
# An instance of Schleuder::Crypt
|
634
|
-
attr_writer :crypt
|
635
|
-
def crypt
|
636
|
-
@crypt ||= Crypt.new(Schleuder.list.config.gpg_password)
|
637
|
-
end
|
638
|
-
|
639
|
-
private
|
640
|
-
|
641
|
-
def _decrypt_pgp_inline(msg)
|
642
|
-
if msg._content_type_stripped == 'text/html'
|
643
|
-
Schleuder.log.debug "Content-type is text/html, can't handle that, skipping"
|
644
|
-
elsif msg.body =~ /^-----BEGIN PGP.*/ || ['pgp', 'gpg'].include?(msg.disposition_param('filename').to_s.split('.').last.to_s.downcase)
|
645
|
-
Schleuder.log.debug 'found pgp-inline in input'
|
646
|
-
# We need to do this in three steps:
|
647
|
-
# 1. get the decoded body from TMail
|
648
|
-
# 2. delete the encoding-header
|
649
|
-
# 3. put the plaintext back into TMail
|
650
|
-
# Else TMail either can't decode the body correctly or 'over-decodes' it
|
651
|
-
# on next output
|
652
|
-
# TMail decodes QP if body() is called, Umlauts if to_s() is called.
|
653
|
-
# Life with TMail is hard...
|
654
|
-
if msg.encoding.to_s.downcase.eql?('quoted-printable') || !msg.main_type.eql?('text')
|
655
|
-
str = msg.body
|
656
|
-
else
|
657
|
-
str = msg.to_s
|
658
|
-
end
|
659
|
-
ptxt, enc, sig = crypt.decrypt(str)
|
660
|
-
if ptxt == str
|
661
|
-
Schleuder.log.debug "Output equals input, content was obviously not suitable for gpg. Passing it untouched."
|
662
|
-
else
|
663
|
-
#Schleuder.log.debug "ptxt.mime_encoding: #{FileMagic.fm(:mime_encoding).buffer(ptxt).inspect}"
|
664
|
-
Schleuder.log.debug "Processing plaincontent: #{ptxt.mime}"
|
665
|
-
msg.in_encrypted = enc
|
666
|
-
msg.in_signed = sig
|
667
|
-
if ! msg.disposition_param('filename').nil?
|
668
|
-
msg['content-disposition'].params['filename'] = msg.disposition_param('filename').gsub(/\.(gpg|pgp|asc)$/, '')
|
669
|
-
end
|
670
|
-
if ptxt.content_type.split('/').first == 'text'
|
671
|
-
msg['content-type'] = ptxt.mime
|
672
|
-
msg.encoding = nil
|
673
|
-
msg.body = ptxt
|
674
|
-
else
|
675
|
-
# TMail handles binary data in an "interesting" way, so we manuall encode before handing Tmail the data.
|
676
|
-
msg['content-type'] = ptxt.content_type
|
677
|
-
msg.encoding = 'Base64'
|
678
|
-
msg.body = [ptxt].pack('m')
|
679
|
-
end
|
680
|
-
end
|
681
|
-
msg
|
682
|
-
else
|
683
|
-
Schleuder.log.debug 'no pgp-inline-data found, doing nothing'
|
684
|
-
end
|
685
|
-
end
|
686
|
-
|
687
|
-
def _encrypt_pgp_inline(msg, receiver)
|
688
|
-
msg.body = crypt.encrypt_str(msg.body.to_s, receiver)
|
689
|
-
# reset encoding, TMail has converted the content
|
690
|
-
msg.encoding = nil
|
691
|
-
# if attachment add '.gpg'-suffix to attachments file names
|
692
|
-
# (The query for 'filename' is a work around against buggy client
|
693
|
-
# formatting that doesn't disposition attachments as attachments. This
|
694
|
-
# variant works ok.)
|
695
|
-
unless msg.disposition_param('filename').nil?
|
696
|
-
msg.set_content_type('application', 'octet-stream', {'x-action' => 'pgp-encrypted'})
|
697
|
-
msg.set_disposition('attachment', {:filename => msg.disposition_param('filename') + '.gpg'})
|
698
|
-
else
|
699
|
-
msg.set_content_type('text', 'plain', {'x-action' => 'pgp-encrypted'})
|
700
|
-
msg.charset = 'UTF-8'
|
701
|
-
end
|
702
|
-
msg
|
703
|
-
end
|
704
|
-
|
705
|
-
def _enc_str(arg)
|
706
|
-
arg ? 'encrypted' : 'unencrypted'
|
707
|
-
end
|
708
|
-
|
709
|
-
def _sig_str(arg)
|
710
|
-
# gpgme breaks if we use arg.to_s.empty? here.
|
711
|
-
if arg.nil? || (arg.is_a?(String) && arg.empty?)
|
712
|
-
'No signature'
|
713
|
-
else
|
714
|
-
if crypt.get_key(arg.fpr).first
|
715
|
-
arg.to_s
|
716
|
-
else
|
717
|
-
#Schleuder.log.debug arg.inspect
|
718
|
-
"Unknown signature from #{arg.fpr} (public key not present)"
|
719
|
-
end
|
720
|
-
end
|
721
|
-
end
|
722
|
-
|
723
|
-
# Tests if the signature of the incoming mail (if any) belongs to a
|
724
|
-
# list-member or an admin by testing all gpg-key-ids of the signing
|
725
|
-
# key for matches
|
726
|
-
def _parse_signature
|
727
|
-
@from_admin = @from_member = false
|
728
|
-
|
729
|
-
Schleuder.log.debug 'Testing for valid signature'
|
730
|
-
if !in_signed
|
731
|
-
Schleuder.log.info 'No signature found'
|
732
|
-
elsif in_signed.status != 0
|
733
|
-
Schleuder.log.info 'Invalid or unknown signature found'
|
734
|
-
else
|
735
|
-
Schleuder.log.info 'Valid signature found'
|
736
|
-
key, msg = crypt.get_key(in_signed.fpr)
|
737
|
-
if key
|
738
|
-
Schleuder.log.debug "Testing key for matching some member's or admin's keys"
|
739
|
-
if Schleuder.list.find_admin_by_key(key)
|
740
|
-
@from_admin = true
|
741
|
-
end
|
742
|
-
if Schleuder.list.find_member_by_key(key)
|
743
|
-
@from_member = true
|
744
|
-
end
|
745
|
-
else
|
746
|
-
Schleuder.log.debug "Keylookup for signature failed! Reason: #{msg}"
|
747
|
-
end
|
748
|
-
end
|
749
|
-
end
|
750
|
-
|
751
|
-
# Convert the given text into quoted printable format, with an instruction
|
752
|
-
# that the text be eventually interpreted in the given charset.
|
753
|
-
def _quoted_printable(text, charset)
|
754
|
-
text = text.gsub( /[^a-z ]/i ) { _quoted_printable_encode($&) }.
|
755
|
-
gsub( / /, "_" )
|
756
|
-
"=?#{charset}?Q?#{text}?="
|
757
|
-
end
|
758
|
-
|
759
|
-
# Convert the given character to quoted printable format, taking into
|
760
|
-
# account multi-byte characters (if executing with $KCODE="u", for instance)
|
761
|
-
def _quoted_printable_encode(character)
|
762
|
-
result = ""
|
763
|
-
character.each_byte { |b| result << "=%02X" % b }
|
764
|
-
result
|
765
|
-
end
|
766
|
-
|
767
|
-
# A quick-and-dirty regexp for determining whether a string contains any
|
768
|
-
# characters that need escaping.
|
769
|
-
if !defined?(CHARS_NEEDING_QUOTING)
|
770
|
-
CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/
|
771
|
-
end
|
772
|
-
|
773
|
-
# Quote the given text if it contains any "illegal" characters
|
774
|
-
def _quote_if_necessary(text, charset)
|
775
|
-
text = text.dup.force_encoding(Encoding::ASCII_8BIT) if text.respond_to?(:force_encoding)
|
776
|
-
|
777
|
-
(text =~ CHARS_NEEDING_QUOTING) ?
|
778
|
-
_quoted_printable(text, charset) :
|
779
|
-
text
|
780
|
-
end
|
781
|
-
|
782
|
-
# Helper for creating new message-parts
|
783
|
-
def _new_part(body, content_type, charset='', encoding='')
|
784
|
-
p = Mail.new
|
785
|
-
p.body = body.to_s
|
786
|
-
p.content_type = content_type
|
787
|
-
p.charset = charset.to_s unless charset.to_s.empty?
|
788
|
-
p.encoding = encoding.to_s unless encoding.to_s.empty?
|
789
|
-
p
|
790
|
-
end
|
791
|
-
|
792
|
-
def _collect_message_ids(ids)
|
793
|
-
return nil if ids.nil? || !ids.is_a?(Array) || ids.empty?
|
794
|
-
ids.select{ |id| Utils.schleuder_id?(id, Schleuder.list.listid) }
|
795
|
-
end
|
796
|
-
|
797
|
-
def self._schleuder_message_id
|
798
|
-
@@schleuder_message_id = Utils.generate_message_id(Schleuder.list.listid) unless @@schleuder_message_id
|
799
|
-
@@schleuder_message_id
|
800
|
-
end
|
801
|
-
|
802
|
-
def _message_ids(mail)
|
803
|
-
Schleuder.log.debug "Copying msgid to in-reply-to/references"
|
804
|
-
mail.in_reply_to = _collect_message_ids(self.in_reply_to)
|
805
|
-
mail.references = _collect_message_ids(self.references)
|
806
|
-
mail
|
807
|
-
end
|
808
|
-
|
809
|
-
def _list_headers(mail)
|
810
|
-
Schleuder.log.debug "Generating list-ids"
|
811
|
-
mail['List-Id'] = "<#{Schleuder.list.listid}>"
|
812
|
-
mail['List-Owner'] = _list_owner
|
813
|
-
mail['List-Post'] = _list_post
|
814
|
-
# TODO: adapt URL to version.
|
815
|
-
mail['List-Help'] = '<https://schleuder2.nadir.org/documentation.html>'
|
816
|
-
mail
|
817
|
-
end
|
818
|
-
|
819
|
-
def _list_owner
|
820
|
-
"<mailto:#{Schleuder.list.owner_addr}> (Use list's public key)"
|
821
|
-
end
|
822
|
-
|
823
|
-
def _list_post
|
824
|
-
if Schleuder.list.config.receive_admin_only
|
825
|
-
"NO (Admins only)"
|
826
|
-
elsif Schleuder.list.config.receive_authenticated_only
|
827
|
-
"<mailto:#{Schleuder.list.config.myaddr}> (Subscribers only)"
|
828
|
-
else
|
829
|
-
"<mailto:#{Schleuder.list.config.myaddr}>"
|
830
|
-
end
|
831
|
-
end
|
832
|
-
|
833
|
-
def _add_openpgp_header(mail)
|
834
|
-
Schleuder.log.debug "Add OpenPGP-Headers"
|
835
|
-
mail['OpenPGP'] = "id=#{Schleuder.list.key_fingerprint} "+
|
836
|
-
"(Send an email to #{Schleuder.list.sendkey_addr} to receive the public-key)"+
|
837
|
-
_gen_openpgp_pref_header
|
838
|
-
mail
|
839
|
-
end
|
840
|
-
|
841
|
-
def _gen_openpgp_pref_header
|
842
|
-
unless Schleuder.list.config.openpgp_header_preference == 'none'
|
843
|
-
pref_str = "; preference=#{Schleuder.list.config.openpgp_header_preference} ("
|
844
|
-
if Schleuder.list.config.receive_admin_only
|
845
|
-
pref_str << 'Only encrypted and signed emails by list-admins are accepted'
|
846
|
-
elsif !Schleuder.list.config.receive_authenticated_only
|
847
|
-
if Schleuder.list.config.receive_encrypted_only \
|
848
|
-
&& Schleuder.list.config.receive_signed_only
|
849
|
-
pref_str << 'Only encrypted and signed emails are accepted'
|
850
|
-
elsif Schleuder.list.config.receive_encrypted_only \
|
851
|
-
&& !Schleuder.list.config.receive_signed_only
|
852
|
-
pref_str << 'Only encrypted emails are accepted'
|
853
|
-
elsif !Schleuder.list.config.receive_encrypted_only \
|
854
|
-
&& Schleuder.list.config.receive_signed_only
|
855
|
-
pref_str << 'Only signed emails are accepted'
|
856
|
-
else
|
857
|
-
pref_str << 'All kind of emails are accepted'
|
858
|
-
end
|
859
|
-
elsif Schleuder.list.config.receive_authenticated_only
|
860
|
-
if Schleuder.list.config.receive_encrypted_only
|
861
|
-
pref_str << 'Only encrypted and signed emails by list-members are accepted'
|
862
|
-
else
|
863
|
-
pref_str << 'Only signed emails by list-members are accepted'
|
864
|
-
end
|
865
|
-
else
|
866
|
-
pref_str << 'All kind of emails are accepted'
|
867
|
-
end
|
868
|
-
pref_str << ')'
|
869
|
-
end
|
870
|
-
pref_str || ''
|
871
|
-
end
|
872
|
-
end
|
873
|
-
end
|