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 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
- spec = Gem::Specification.find_by_name 'schleuder'
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(spec.spec_file.untaint, "w") do |f|
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
@@ -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(listdir,mailuser)
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
- #$stderr.puts line
298
+ $stderr.puts line if $DEBUG
294
299
  if reply
295
- #$stderr.puts reply
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(listdir, mailuser)
357
- File.chown(mailuser,nil,listdir)
358
- File.chmod(0700,listdir)
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)
@@ -8,6 +8,7 @@
8
8
 
9
9
  $VERBOSE = nil
10
10
 
11
+ $:.unshift File.dirname(__FILE__) + '/../lib'
11
12
  require 'schleuder'
12
13
  include Schleuder
13
14
 
@@ -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
@@ -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
- # Check file permissions. ruby-gpgme unfortunately returns unspecific
8
- # errors if it can't read files.
9
- %w(pubring.gpg secring.gpg trustdb.gpg).each do |fn|
10
- f = File.join(ENV['GNUPGHOME'], fn)
11
- if ! File.readable?(f)
12
- raise Errno::EACCES.new('%s is not readable' % f)
13
- elsif ! File.writable?(f)
14
- raise Errno::EACCES.new('%s is not writable' % f)
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. Sensible e.g. if it is part
60
- # of a nested MIME-message and encrypted to someone else on purpose.
61
- # Breaking if even the whole message is not decryptable is a job for
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
- # return input instead of empty String if not encrypted.
65
- # String#content_type is provided by filemagic/ext.
66
- unless str =~ /^-----BEGIN PGP MESSAGE-----/ || str.content_type.split('/').last.eql?('pgp')
67
- output, in_signed = verify(str)
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
- Schleuder.log.debug 'found pgp content, decrypting and verifying with gpgme'
78
- in_encrypted = true
79
- output = GPGME.decrypt(str, :passphrase_callback => method(:passfunc)) do |sig|
80
- in_signed = sig
81
- end
82
- if output.empty?
83
- Exception.new("Output from GPGME.decrypt was empty!")
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| [:revoked,:expired].include?(key.trust) } if only_valid_keys
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 { |key| key_descr(key) + (!only_valid_keys && [:revoked, :expired].include?(key.trust)) ? " *#{key.trust}*" : '' }
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)
@@ -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 = _load_config(false) if newlist
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,false)
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,false) : 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
- # fromfile = Whether to load the config from file.
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),fromfile)
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, ['SAVE-MEMBERS', 'DEL-KEY']
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, [ 'X-ADD-KEY' ]
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(config_file, fromfile=true)
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(config_file, fromfile)
119
+ super(config)
120
120
 
121
121
  # load admins as members
122
122
  self.admins = self.admins
@@ -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.body.content_type.split('/').last.eql?('pgp')
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
- # Look for charset-armor-header. In most cases useless but nobody shall say we didn't try.
633
- msg.body.each_line do |l|
634
- break if l.strip.empty?
635
- next unless m = l.match(/^Charset:\s(.*)$/)
636
- @charset = m[1]
637
- end
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 msg.main_type.eql?('text')
653
- msg.body = ptxt
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
- # base64-encode if not text as we don't know what's in there. Might
658
- # be binary data, which could break otherweise.
659
- msg.body = [ptxt].pack("m")
660
- msg.encoding = 'base64'
661
- end
662
- msg.in_encrypted = enc
663
- msg.in_signed = sig
664
- # RFC 2440 defines that the armored text by default is utf-8
665
- if msg.content_type && msg['content-type'].params['x-action'] =~ /^pgp/
666
- msg['content-type'].params['x-action'] = nil
667
- end
668
- unless msg.disposition_param('filename').nil?
669
- msg['content-disposition'].params['filename'] = msg.disposition_param('filename').gsub(/\.(gpg|pgp|asc)$/, '')
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
 
@@ -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(config_file, fromfile=true)
10
- super(config_file, fromfile)
9
+ def initialize(config=nil)
10
+ super(config)
11
11
 
12
12
  # compress fingerprint
13
13
  self.key_fingerprint = self.key_fingerprint