schleuder 2.2.1 → 2.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.
- 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
|