schleuder 2.2.1 → 2.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/bin/schleuder-fix-gem-dependencies +9 -2
- data/bin/schleuder-newlist +23 -10
- data/contrib/check-expired-keys.rb +1 -0
- data/ext/schleuder.conf +0 -4
- data/lib/schleuder/crypt.rb +58 -36
- data/lib/schleuder/list.rb +5 -6
- data/lib/schleuder/list_config.rb +4 -4
- data/lib/schleuder/mail.rb +36 -33
- data/lib/schleuder/member.rb +2 -2
- data/lib/schleuder/processor.rb +1 -1
- data/lib/schleuder/schleuder_config.rb +7 -7
- data/lib/schleuder/storage.rb +19 -19
- data/lib/schleuder/version.rb +1 -1
- data/man/schleuder-newlist.8 +66 -90
- data/man/schleuder.8 +195 -179
- metadata +7 -12
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
Binary file
|
@@ -8,7 +8,14 @@ require 'rubygems'
|
|
8
8
|
require 'rubygems/dependency_installer.rb'
|
9
9
|
|
10
10
|
inst = Gem::DependencyInstaller.new
|
11
|
-
|
11
|
+
|
12
|
+
if Gem::Specification.respond_to? :find_by_name
|
13
|
+
spec = Gem::Specification.find_by_name 'schleuder'
|
14
|
+
spec_file = spec.spec_file
|
15
|
+
else
|
16
|
+
spec = Gem.source_index.find_name('schleuder').first
|
17
|
+
spec_file = spec.loaded_from
|
18
|
+
end
|
12
19
|
|
13
20
|
begin
|
14
21
|
if RUBY_VERSION < "1.9"
|
@@ -21,7 +28,7 @@ begin
|
|
21
28
|
inst.install name, ver
|
22
29
|
spec.add_dependency name, ver
|
23
30
|
# Write spec back to file, from rubygems/installer.rb
|
24
|
-
File.open(
|
31
|
+
File.open(spec_file.untaint, "w") do |f|
|
25
32
|
f << spec.to_ruby_for_cache
|
26
33
|
end
|
27
34
|
rescue Gem::FilePermissionError => e
|
data/bin/schleuder-newlist
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
trap ("INT") { exit 1 }
|
4
|
+
|
3
5
|
$:.unshift File.dirname(__FILE__) + '/../lib'
|
4
6
|
require 'schleuder'
|
5
7
|
require 'etc'
|
@@ -90,7 +92,9 @@ class ListCreator
|
|
90
92
|
listdir = File.join([Schleuder.config.lists_dir, listname.split('@').reverse].flatten)
|
91
93
|
raise NewListError, "List or parts of a list named: #{listname} already exists!" if File.directory?(listdir)
|
92
94
|
list_email = ListCreator::verify_emailvar(listname,interactive,"The lists's email address")
|
93
|
-
list_realname = ListCreator::verify_strvar(args[:list_realname],interactive,"'Realname' (for GPG-key and email-headers)")
|
95
|
+
list_realname = ListCreator::verify_strvar(args[:list_realname],interactive,"'Realname' (for GPG-key and email-headers)") do |var|
|
96
|
+
raise NewListError, "Realname must be at least 5 characters long (requirement of GnuPG)" if var.size < 5
|
97
|
+
end
|
94
98
|
list_adminaddress = ListCreator::verify_emailvar(args[:list_adminaddress],interactive,"Admin email address")
|
95
99
|
|
96
100
|
raise NewListError,"Lists' email address and the admin address can't be the same" if list_email == list_adminaddress
|
@@ -183,7 +187,7 @@ class ListCreator
|
|
183
187
|
Schleuder.log.debug "Store list config..."
|
184
188
|
list.config = list.config
|
185
189
|
Schleuder.log.debug "Changing ownership..."
|
186
|
-
ListCreator::filepermissions(
|
190
|
+
ListCreator::filepermissions(list, mailuser)
|
187
191
|
Schleuder.log.debug "List successfully created..."
|
188
192
|
ListCreator::print_list_infos(list) if interactive
|
189
193
|
end
|
@@ -206,7 +210,7 @@ class ListCreator
|
|
206
210
|
end
|
207
211
|
end
|
208
212
|
|
209
|
-
def self.verify_strvar(var,interactive,question, echo=true)
|
213
|
+
def self.verify_strvar(var,interactive,question, echo=true, &block)
|
210
214
|
if (var.nil? or var.empty?) and interactive then
|
211
215
|
str = question+": "
|
212
216
|
if echo
|
@@ -215,6 +219,7 @@ class ListCreator
|
|
215
219
|
var = ask(str) { |question| question.echo = '*' }
|
216
220
|
end
|
217
221
|
end
|
222
|
+
yield(var) if block_given?
|
218
223
|
raise NewListError,"Missing mandatory variable: "+question if (var.nil? or var.empty?)
|
219
224
|
var
|
220
225
|
end
|
@@ -290,9 +295,9 @@ class ListCreator
|
|
290
295
|
else
|
291
296
|
reply = nil
|
292
297
|
end
|
293
|
-
|
298
|
+
$stderr.puts line if $DEBUG
|
294
299
|
if reply
|
295
|
-
|
300
|
+
$stderr.puts reply if $DEBUG
|
296
301
|
stdin.puts reply
|
297
302
|
end
|
298
303
|
end
|
@@ -332,7 +337,7 @@ class ListCreator
|
|
332
337
|
end
|
333
338
|
|
334
339
|
def self.import_keypair(list,list_privatekeyfile,list_publickeyfile)
|
335
|
-
crypt = Schleuder::Crypt.new(list.config.gpg_password)
|
340
|
+
crypt = Schleuder::Crypt.new(list.config.gpg_password, :new_keyring => true)
|
336
341
|
Schleuder.log.debug "Importing private key from #{list_privatekeyfile}"
|
337
342
|
crypt.add_key_from_file(list_privatekeyfile)
|
338
343
|
Schleuder.log.debug "Importing public key from #{list_publickeyfile}"
|
@@ -353,15 +358,23 @@ Passphrase: #{pass}
|
|
353
358
|
</GnupgKeyParms>"
|
354
359
|
end
|
355
360
|
|
356
|
-
def self.filepermissions(
|
357
|
-
|
358
|
-
File.
|
361
|
+
def self.filepermissions(list, mailuser)
|
362
|
+
listdir = list.listdir
|
363
|
+
dirs = [File.dirname(listdir), listdir]
|
364
|
+
FileUtils.chown mailuser, nil, dirs
|
365
|
+
FileUtils.chmod 0700, dirs
|
359
366
|
Dir.new(listdir).each{ |f|
|
360
367
|
unless f =~ /^\./
|
361
368
|
File.chown(mailuser,nil,listdir+"/"+f)
|
362
|
-
File.chmod(0600)
|
369
|
+
File.chmod(0600,listdir+"/"+f)
|
363
370
|
end
|
364
371
|
}
|
372
|
+
if list.config.log_file
|
373
|
+
log_file = list.config.log_file
|
374
|
+
log_file = File.join(listdir, log_file) unless log_file.start_with?('/')
|
375
|
+
FileUtils.chown mailuser, nil, log_file
|
376
|
+
FileUtils.chmod 0600, log_file
|
377
|
+
end
|
365
378
|
end
|
366
379
|
|
367
380
|
def self.print_list_infos(list)
|
data/ext/schleuder.conf
CHANGED
@@ -26,10 +26,6 @@
|
|
26
26
|
# Name of the per list config file.
|
27
27
|
#lists_configfile: list.conf
|
28
28
|
#
|
29
|
-
# Per list logfile name. Will be written into the directory
|
30
|
-
# of the list.
|
31
|
-
#lists_logfile: list.log
|
32
|
-
#
|
33
29
|
# Name of the per list file containing all members and their
|
34
30
|
# options.
|
35
31
|
#lists_memberfile: members.conf
|
data/lib/schleuder/crypt.rb
CHANGED
@@ -3,15 +3,17 @@ module Schleuder
|
|
3
3
|
# change but aliases will be set up then.
|
4
4
|
class Crypt
|
5
5
|
# Instantiates and stores password
|
6
|
-
def initialize(password)
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
def initialize(password, options={})
|
7
|
+
unless options[:new_keyring]
|
8
|
+
# Check file permissions. ruby-gpgme unfortunately returns unspecific
|
9
|
+
# errors if it can't read files.
|
10
|
+
%w(pubring.gpg secring.gpg trustdb.gpg).each do |fn|
|
11
|
+
f = File.join(ENV['GNUPGHOME'], fn)
|
12
|
+
if ! File.readable?(f)
|
13
|
+
raise Errno::EACCES.new('%s is not readable' % f)
|
14
|
+
elsif ! File.writable?(f)
|
15
|
+
raise Errno::EACCES.new('%s is not writable' % f)
|
16
|
+
end
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
@@ -21,7 +23,7 @@ module Schleuder
|
|
21
23
|
end
|
22
24
|
@ctx = GPGME::Ctx.new
|
23
25
|
# feed the passphrase into the Context
|
24
|
-
@ctx.set_passphrase_cb(method(:passfunc))
|
26
|
+
@ctx.set_passphrase_cb(method(:passfunc))
|
25
27
|
end
|
26
28
|
|
27
29
|
# Verify a gpg-signature. Use +signed_string+ if the signature is
|
@@ -56,35 +58,41 @@ module Schleuder
|
|
56
58
|
in_encrypted = nil
|
57
59
|
in_signed = nil
|
58
60
|
|
59
|
-
# TODO: return ciphertext if missing key
|
60
|
-
#
|
61
|
-
#
|
62
|
-
# the processor.
|
61
|
+
# TODO: return ciphertext if missing key when decrypting inline
|
62
|
+
# attachments or recursing into mime-messages. Breaking if even the
|
63
|
+
# whole message is not decryptable is a job for the processor.
|
63
64
|
|
64
|
-
#
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
# match pgp-mime- and inline-pgp-signatures
|
69
|
-
if str =~ /^-----BEGIN PGP SIG/
|
70
|
-
Schleuder.log.debug 'found signed, not encrypted message, verifying'
|
71
|
-
output, in_signed = verify(str)
|
72
|
-
else
|
73
|
-
Schleuder.log.debug 'found not signed, not encrypted message, returning input'
|
74
|
-
output = str
|
75
|
-
end
|
65
|
+
# match pgp-mime- and inline-pgp-signatures
|
66
|
+
if str =~ /^-----BEGIN PGP SIG/
|
67
|
+
Schleuder.log.debug 'found ascii-armored, signed, not encrypted message, verifying'
|
68
|
+
output, in_signed = verify(str)
|
76
69
|
else
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
70
|
+
begin
|
71
|
+
Schleuder.log.debug 'Trying to decrypt'
|
72
|
+
output = GPGME.decrypt(str, :passphrase_callback => method(:passfunc)) do |sig|
|
73
|
+
in_signed = sig
|
74
|
+
end
|
75
|
+
in_encrypted = true
|
76
|
+
rescue GPGME::Error::NoData => exc
|
77
|
+
Schleuder.log.debug "Caught NoData-exception from gpgme. This probably means we're dealing with a binary signature, trying to verify."
|
78
|
+
output, in_signed = verify(str)
|
79
|
+
if output.empty?
|
80
|
+
Schleuder.log.debug "Empty output from verification. No more options, returning."
|
81
|
+
end
|
84
82
|
end
|
85
|
-
# TODO: return mailadresses or keys instead of signature-objects?
|
86
83
|
end
|
84
|
+
Schleuder.log.debug "mime_type of decrypted content: #{output.mime.inspect}"
|
85
|
+
Schleuder.log.debug "in_signed: #{in_signed.inspect}"
|
86
|
+
Schleuder.log.debug "in_encrypted: #{in_encrypted.inspect}"
|
87
|
+
if output.empty?
|
88
|
+
Schleuder.log.debug "Empty output, returning input"
|
89
|
+
output = str
|
90
|
+
end
|
91
|
+
# TODO: return mailadresses or keys instead of signature-objects?
|
87
92
|
[output, in_encrypted, in_signed]
|
93
|
+
rescue GPGME::Error::General => exc
|
94
|
+
Schleuder.log.warn = "gpgme returned a general error. Most likely this is due to unexpected input, but as you never know here's the input and the exception:\ninput:\n#{str.inspect}\n#{exc.to_s}\n#{exc.backtrace[0..9].join("\n")}"
|
95
|
+
[str, nil, nil]
|
88
96
|
end
|
89
97
|
|
90
98
|
# Encrypt a string to a single receiver and sign it. +receiver+ must be a
|
@@ -114,7 +122,7 @@ module Schleuder
|
|
114
122
|
pattern = "<#{pattern}>" if pattern =~ /.*@.*/ && !(pattern =~ /^<.*>$/)
|
115
123
|
keys = list_keys(pattern)
|
116
124
|
|
117
|
-
keys.reject! { |key|
|
125
|
+
keys.reject! { |key| unusable_key?(key) } if only_valid_keys
|
118
126
|
|
119
127
|
if keys.empty?
|
120
128
|
[false, "no key found for #{pattern}."]
|
@@ -175,7 +183,21 @@ module Schleuder
|
|
175
183
|
private
|
176
184
|
|
177
185
|
def key_infos(keys, only_valid_keys=false)
|
178
|
-
keys.collect
|
186
|
+
keys.collect do |key|
|
187
|
+
info = key_descr(key)
|
188
|
+
unless only_valid_keys
|
189
|
+
info << trust_info(key)
|
190
|
+
end
|
191
|
+
info
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def trust_info(key)
|
196
|
+
unusable_key?(key) ? " *#{key.trust}*" : ''
|
197
|
+
end
|
198
|
+
|
199
|
+
def unusable_key?(key)
|
200
|
+
[:revoked, :expired].include?(key.trust)
|
179
201
|
end
|
180
202
|
|
181
203
|
def passfunc(hook, uid_hint, passphrase_info, prev_was_bad, fd)
|
data/lib/schleuder/list.rb
CHANGED
@@ -8,7 +8,7 @@ module Schleuder
|
|
8
8
|
# Prepare some variables, set up the SchleuderLogger and set GNUPGHOME
|
9
9
|
def initialize(listname,newlist=false)
|
10
10
|
@listname = listname
|
11
|
-
@config =
|
11
|
+
@config = ListConfig.new if newlist
|
12
12
|
@log = ListLogger.new listname, listdir, config
|
13
13
|
|
14
14
|
# setting GNUPGHOME to list's home, to make use of the keys there
|
@@ -22,7 +22,7 @@ module Schleuder
|
|
22
22
|
unless @members
|
23
23
|
Schleuder.log.debug("reading #{members_file}")
|
24
24
|
@members = YAML::load_file(members_file).collect do |h|
|
25
|
-
h.kind_of?(Schleuder::Member) ? h : Schleuder::Member.new(h
|
25
|
+
h.kind_of?(Schleuder::Member) ? h : Schleuder::Member.new(h)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
@members
|
@@ -36,7 +36,7 @@ module Schleuder
|
|
36
36
|
def members=(arr)
|
37
37
|
Schleuder.log.debug 'writing members'
|
38
38
|
Schleuder.log.info("writing #{members_file}")
|
39
|
-
@members = arr.collect { |m| m.kind_of?(Hash) ? Member.new(m
|
39
|
+
@members = arr.collect { |m| m.kind_of?(Hash) ? Member.new(m) : m }
|
40
40
|
_write(YAML.dump(@members.collect { |m| m.to_hash }), members_file)
|
41
41
|
@members
|
42
42
|
end
|
@@ -144,10 +144,9 @@ module Schleuder
|
|
144
144
|
private
|
145
145
|
|
146
146
|
# Loads the configuration
|
147
|
-
|
148
|
-
def _load_config(fromfile=true)
|
147
|
+
def _load_config
|
149
148
|
Schleuder.log.debug("reading list-config for: #{@listname}") unless Schleuder.log.nil?
|
150
|
-
@config = ListConfig.new(File.join(listdir, Schleuder.config.lists_configfile)
|
149
|
+
@config = ListConfig.new(File.join(listdir, Schleuder.config.lists_configfile))
|
151
150
|
end
|
152
151
|
|
153
152
|
def find_by_key(ary, key)
|
@@ -75,10 +75,10 @@ module Schleuder
|
|
75
75
|
schleuder_attr :headers_to_meta, [:from, :to, :cc, :date]
|
76
76
|
|
77
77
|
# Restrict specific plugins to admin
|
78
|
-
schleuder_attr :keywords_admin_only,
|
78
|
+
schleuder_attr :keywords_admin_only, ['ADD-MEMBER', 'DELETE-MEMBER', 'DELETE-KEY', 'SAVE-MEMBERS', 'DEL-KEY' ]
|
79
79
|
|
80
80
|
# Notify admin if these keywords triggered commands.
|
81
|
-
schleuder_attr :keywords_admin_notify, [ '
|
81
|
+
schleuder_attr :keywords_admin_notify, [ 'ADD-KEY' ]
|
82
82
|
|
83
83
|
# Drop any bounces (incoming email not passing the receive_*_only-rules)
|
84
84
|
schleuder_attr :bounces_drop_all, false
|
@@ -112,11 +112,11 @@ module Schleuder
|
|
112
112
|
|
113
113
|
### END OF CONFIG OPTIONS
|
114
114
|
|
115
|
-
def initialize(
|
115
|
+
def initialize(config=nil)
|
116
116
|
# First Overload with default-list.conf then load our config
|
117
117
|
overload_from_file!(Schleuder.config.lists_default_conf)
|
118
118
|
# overload with config_file
|
119
|
-
super(
|
119
|
+
super(config)
|
120
120
|
|
121
121
|
# load admins as members
|
122
122
|
self.admins = self.admins
|
data/lib/schleuder/mail.rb
CHANGED
@@ -111,6 +111,11 @@ module Schleuder
|
|
111
111
|
self.parts.each do |part|
|
112
112
|
_decrypt_pgp_inline(part)
|
113
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
|
+
if self.parts.map { |p| p.in_signed.fpr }.uniq.size == 1
|
117
|
+
self.in_signed = self.parts.first.in_signed
|
118
|
+
end
|
114
119
|
else
|
115
120
|
Schleuder.log.debug 'single inline content found, looking for pgp content'
|
116
121
|
_decrypt_pgp_inline(self)
|
@@ -627,14 +632,14 @@ module Schleuder
|
|
627
632
|
def _decrypt_pgp_inline(msg)
|
628
633
|
if msg._content_type_stripped == 'text/html'
|
629
634
|
Schleuder.log.debug "Content-type is text/html, can't handle that, skipping"
|
630
|
-
elsif msg.body =~ /^-----BEGIN PGP.*/ || msg.
|
635
|
+
elsif msg.body =~ /^-----BEGIN PGP.*/ || ['pgp', 'gpg'].include?(msg.disposition_param('filename').to_s.split('.').last.to_s.downcase)
|
631
636
|
Schleuder.log.debug 'found pgp-inline in input'
|
632
|
-
#
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
637
|
+
# We need to do this in three steps:
|
638
|
+
# 1. get the decoded body from TMail
|
639
|
+
# 2. delete the encoding-header
|
640
|
+
# 3. put the plaintext back into TMail
|
641
|
+
# Else TMail either can't decode the body correctly or 'over-decodes' it
|
642
|
+
# on next output
|
638
643
|
# TMail decodes QP if body() is called, Umlauts if to_s() is called.
|
639
644
|
# Life with TMail is hard...
|
640
645
|
if msg.encoding.to_s.downcase.eql?('quoted-printable') || !msg.main_type.eql?('text')
|
@@ -642,32 +647,29 @@ module Schleuder
|
|
642
647
|
else
|
643
648
|
str = msg.to_s
|
644
649
|
end
|
645
|
-
# We need to do this in three steps:
|
646
|
-
# 1. get the decoded body from TMail
|
647
|
-
# 2. delete the encoding-header
|
648
|
-
# 3. put the plaintext back into TMail
|
649
|
-
# Else TMail either can't decode the body correctly or 'over-decodes' it
|
650
|
-
# on next output
|
651
650
|
ptxt, enc, sig = crypt.decrypt(str)
|
652
|
-
if
|
653
|
-
|
654
|
-
msg.charset = @charset || 'UTF-8'
|
655
|
-
msg.encoding = nil
|
651
|
+
if ptxt == str
|
652
|
+
Schleuder.log.debug "Output equals input, content was obviously not suitable for gpg. Passing it untouched."
|
656
653
|
else
|
657
|
-
#
|
658
|
-
|
659
|
-
msg.
|
660
|
-
msg.
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
654
|
+
#Schleuder.log.debug "ptxt.mime_encoding: #{FileMagic.fm(:mime_encoding).buffer(ptxt).inspect}"
|
655
|
+
Schleuder.log.debug "Processing plaincontent: #{ptxt.mime}"
|
656
|
+
msg.in_encrypted = enc
|
657
|
+
msg.in_signed = sig
|
658
|
+
if ! msg.disposition_param('filename').nil?
|
659
|
+
msg['content-disposition'].params['filename'] = msg.disposition_param('filename').gsub(/\.(gpg|pgp|asc)$/, '')
|
660
|
+
end
|
661
|
+
if ptxt.content_type.split('/').first == 'text'
|
662
|
+
msg['content-type'] = ptxt.mime
|
663
|
+
msg.encoding = nil
|
664
|
+
msg.body = ptxt
|
665
|
+
else
|
666
|
+
# TMail handles binary data in an "interesting" way, so we manuall encode before handing Tmail the data.
|
667
|
+
msg['content-type'] = ptxt.content_type
|
668
|
+
msg.encoding = 'Base64'
|
669
|
+
msg.body = [ptxt].pack('m')
|
670
|
+
end
|
670
671
|
end
|
672
|
+
msg
|
671
673
|
else
|
672
674
|
Schleuder.log.debug 'no pgp-inline-data found, doing nothing'
|
673
675
|
end
|
@@ -696,15 +698,16 @@ module Schleuder
|
|
696
698
|
end
|
697
699
|
|
698
700
|
def _sig_str(arg)
|
699
|
-
if arg
|
701
|
+
# gpgme breaks if we use arg.to_s.empty? here.
|
702
|
+
if arg.nil? || (arg.is_a?(String) && arg.empty?)
|
703
|
+
'No signature'
|
704
|
+
else
|
700
705
|
if crypt.get_key(arg.fpr).first
|
701
706
|
arg.to_s
|
702
707
|
else
|
703
708
|
#Schleuder.log.debug arg.inspect
|
704
709
|
"Unknown signature from #{arg.fpr} (public key not present)"
|
705
710
|
end
|
706
|
-
else
|
707
|
-
'No signature'
|
708
711
|
end
|
709
712
|
end
|
710
713
|
|
data/lib/schleuder/member.rb
CHANGED
@@ -6,8 +6,8 @@ module Schleuder
|
|
6
6
|
schleuder_attr :encrypted_only, false
|
7
7
|
schleuder_attr :key_fingerprint, nil
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
super(
|
9
|
+
def initialize(config=nil)
|
10
|
+
super(config)
|
11
11
|
|
12
12
|
# compress fingerprint
|
13
13
|
self.key_fingerprint = self.key_fingerprint
|