Gentoo Websites Logo
Go to: Gentoo Home Documentation Forums Lists Bugs Planet Store Wiki Get Gentoo!
View | Details | Raw Unified | Return to bug 403963 | Differences between
and this patch

Collapse All | Expand All

(-)a/lib/puppet/daemon.rb (-2 / +2 lines)
Lines 33-41 class Puppet::Daemon Link Here
33
      Puppet::Util::Log.reopen
33
      Puppet::Util::Log.reopen
34
    rescue => detail
34
    rescue => detail
35
      Puppet.err "Could not start #{Puppet[:name]}: #{detail}"
35
      Puppet.err "Could not start #{Puppet[:name]}: #{detail}"
36
      Puppet::Util::secure_open("/tmp/daemonout", "w") { |f|
36
      Puppet::Util::replace_file("/tmp/daemonout", 0644) do |f|
37
        f.puts "Could not start #{Puppet[:name]}: #{detail}"
37
        f.puts "Could not start #{Puppet[:name]}: #{detail}"
38
      }
38
      end
39
      exit(12)
39
      exit(12)
40
    end
40
    end
41
  end
41
  end
(-)a/lib/puppet/network/server.rb (-1 / +1 lines)
Lines 22-28 class Puppet::Network::Server Link Here
22
      $stderr.reopen $stdout
22
      $stderr.reopen $stdout
23
      Puppet::Util::Log.reopen
23
      Puppet::Util::Log.reopen
24
    rescue => detail
24
    rescue => detail
25
      Puppet::Util.secure_open("/tmp/daemonout", "w") { |f|
25
      Puppet::Util.replace_file("/tmp/daemonout", 0644) { |f|
26
        f.puts "Could not start #{Puppet[:name]}: #{detail}"
26
        f.puts "Could not start #{Puppet[:name]}: #{detail}"
27
      }
27
      }
28
      raise "Could not start #{Puppet[:name]}: #{detail}"
28
      raise "Could not start #{Puppet[:name]}: #{detail}"
(-)a/lib/puppet/provider/user/user_role_add.rb (-17 / +32 lines)
Lines 1-3 Link Here
1
require 'puppet/util'
1
require 'puppet/util/user_attr'
2
require 'puppet/util/user_attr'
2
3
3
Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source => :useradd do
4
Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source => :useradd do
Lines 145-155 Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source => Link Here
145
    run([command(:modify)] + build_keys_cmd(keys_hash) << @resource[:name], "modify attribute key pairs")
146
    run([command(:modify)] + build_keys_cmd(keys_hash) << @resource[:name], "modify attribute key pairs")
146
  end
147
  end
147
148
149
150
  # This helper makes it possible to test this on stub data without having to
151
  # do too many crazy things!
152
  def target_file_path
153
    "/etc/shadow"
154
  end
155
  private :target_file_path
156
148
  #Read in /etc/shadow, find the line for this user (skipping comments, because who knows) and return it
157
  #Read in /etc/shadow, find the line for this user (skipping comments, because who knows) and return it
149
  #No abstraction, all esoteric knowledge of file formats, yay
158
  #No abstraction, all esoteric knowledge of file formats, yay
150
  def shadow_entry
159
  def shadow_entry
151
    return @shadow_entry if defined? @shadow_entry
160
    return @shadow_entry if defined? @shadow_entry
152
    @shadow_entry = File.readlines("/etc/shadow").reject { |r| r =~ /^[^\w]/ }.collect { |l| l.chomp.split(':') }.find { |user, _| user == @resource[:name] }
161
    @shadow_entry = File.readlines(target_file_path).
162
      reject { |r| r =~ /^[^\w]/ }.
163
      collect { |l| l.chomp.split(':') }.
164
      find { |user, _| user == @resource[:name] }
153
  end
165
  end
154
166
155
  def password
167
  def password
Lines 164-191 Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source => Link Here
164
    shadow_entry ? shadow_entry[4] : :absent
176
    shadow_entry ? shadow_entry[4] : :absent
165
  end
177
  end
166
178
167
  #Read in /etc/shadow, find the line for our used and rewrite it with the new pw
179
  # Read in /etc/shadow, find the line for our used and rewrite it with the
168
  #Smooth like 80 grit
180
  # new pw.  Smooth like 80 grit sandpaper.
181
  #
182
  # Now uses the `replace_file` mechanism to minimize the chance that we lose
183
  # data, but it is still terrible.  We still skip platform locking, so a
184
  # concurrent `vipw -s` session will have no idea we risk data loss.
169
  def password=(cryptopw)
185
  def password=(cryptopw)
170
    begin
186
    begin
171
      File.open("/etc/shadow", "r") do |shadow|
187
      shadow = File.read(target_file_path)
172
        File.open("/etc/shadow_tmp", "w", 0600) do |shadow_tmp|
188
173
          while line = shadow.gets
189
      # Go Mifune loves the race here where we can lose data because
174
            line_arr = line.split(':')
190
      # /etc/shadow changed between reading it and writing it.
175
            if line_arr[0] == @resource[:name]
191
      # --daniel 2012-02-05
176
              line_arr[1] = cryptopw
192
      Puppet::Util.replace_file(target_file_path, 0640) do |fh|
177
              line = line_arr.join(':')
193
        shadow.each_line do |line|
178
            end
194
          line_arr = line.split(':')
179
            shadow_tmp.print line
195
          if line_arr[0] == @resource[:name]
196
            line_arr[1] = cryptopw
197
            line = line_arr.join(':')
180
          end
198
          end
199
          fh.print line
181
        end
200
        end
182
      end
201
      end
183
      File.rename("/etc/shadow_tmp", "/etc/shadow")
184
    rescue => detail
202
    rescue => detail
185
      fail "Could not write temporary shadow file: #{detail}"
203
      fail "Could not write replace #{target_file_path}: #{detail}"
186
    ensure
187
      # Make sure this *always* gets deleted
188
      File.unlink("/etc/shadow_tmp") if File.exist?("/etc/shadow_tmp")
189
    end
204
    end
190
  end
205
  end
191
end
206
end
(-)a/lib/puppet/rails/benchmark.rb (-1 / +1 lines)
Lines 58-63 module Puppet::Rails::Benchmark Link Here
58
      data = {}
58
      data = {}
59
    end
59
    end
60
    data[branch] = $benchmarks
60
    data[branch] = $benchmarks
61
    Puppet::Util.secure_open(file, "w") { |f| f.print YAML.dump(data) }
61
    Puppet::Util.replace_file(file, 0644) { |f| f.print YAML.dump(data) }
62
  end
62
  end
63
end
63
end
(-)a/lib/puppet/type/k5login.rb (-2 / +3 lines)
Lines 1-4 Link Here
1
# Plug-in type for handling k5login files
1
# Plug-in type for handling k5login files
2
require 'puppet/util'
2
3
3
Puppet::Type.newtype(:k5login) do
4
Puppet::Type.newtype(:k5login) do
4
  @doc = "Manage the `.k5login` file for a user.  Specify the full path to
5
  @doc = "Manage the `.k5login` file for a user.  Specify the full path to
Lines 79-86 Puppet::Type.newtype(:k5login) do Link Here
79
80
80
    private
81
    private
81
    def write(value)
82
    def write(value)
82
      Puppet::Util.secure_open(@resource[:name], "w") do |f|
83
      Puppet::Util.replace_file(@resource[:name], 0644) do |f|
83
        f.puts value.join("\n")
84
        f.puts value
84
      end
85
      end
85
    end
86
    end
86
  end
87
  end
(-)a/lib/puppet/util.rb (-23 / +77 lines)
Lines 1-10 Link Here
1
# A module to collect utility functions.
1
# A module to collect utility functions.
2
3
require 'puppet/util/monkey_patches'
2
require 'puppet/util/monkey_patches'
4
require 'sync'
5
require 'puppet/external/lock'
3
require 'puppet/external/lock'
6
require 'monitor'
7
require 'puppet/util/execution_stub'
4
require 'puppet/util/execution_stub'
5
require 'sync'
6
require 'monitor'
7
require 'tempfile'
8
require 'pathname'
8
9
9
module Puppet
10
module Puppet
10
  # A command failed to execute.
11
  # A command failed to execute.
Lines 261-267 module Util Link Here
261
    output_file="/dev/null"
262
    output_file="/dev/null"
262
    error_file="/dev/null"
263
    error_file="/dev/null"
263
    if ! arguments[:squelch]
264
    if ! arguments[:squelch]
264
      require "tempfile"
265
      output_file = Tempfile.new("puppet")
265
      output_file = Tempfile.new("puppet")
266
      error_file=output_file if arguments[:combine]
266
      error_file=output_file if arguments[:combine]
267
    end
267
    end
Lines 287-294 module Util Link Here
287
          $stderr.reopen(error_file)
287
          $stderr.reopen(error_file)
288
288
289
          3.upto(256){|fd| IO::new(fd).close rescue nil}
289
          3.upto(256){|fd| IO::new(fd).close rescue nil}
290
          Puppet::Util::SUIDManager.change_group(arguments[:gid], true) if arguments[:gid]
290
          Puppet::Util::SUIDManager.change_privileges(arguments[:uid], arguments[:gid], true)
291
          Puppet::Util::SUIDManager.change_user(arguments[:uid], true) if arguments[:uid]
292
          ENV['LANG'] = ENV['LC_ALL'] = ENV['LC_MESSAGES'] = ENV['LANGUAGE'] = 'C'
291
          ENV['LANG'] = ENV['LC_ALL'] = ENV['LC_MESSAGES'] = ENV['LANGUAGE'] = 'C'
293
          if command.is_a?(Array)
292
          if command.is_a?(Array)
294
            Kernel.exec(*command)
293
            Kernel.exec(*command)
Lines 409-435 module Util Link Here
409
408
410
  module_function :memory, :thinmark
409
  module_function :memory, :thinmark
411
410
412
  def secure_open(file,must_be_w,&block)
411
  # Replace a file, securely.  This takes a block, and passes it the file
413
    raise Puppet::DevError,"secure_open only works with mode 'w'" unless must_be_w == 'w'
412
  # handle of a file open for writing.  Write the replacement content inside
414
    raise Puppet::DevError,"secure_open only requires a block"    unless block_given?
413
  # the block and it will safely replace the target file.
415
    Puppet.warning "#{file} was a symlink to #{File.readlink(file)}" if File.symlink?(file)
414
  #
416
    if File.exists?(file) or File.symlink?(file)
415
  # This method will make no changes to the target file until the content is
417
      wait = File.symlink?(file) ? 5.0 : 0.1
416
  # successfully written and the block returns without raising an error.
418
      File.delete(file)
417
  #
419
      sleep wait # give it a chance to reappear, just in case someone is actively trying something.
418
  # As far as possible the state of the existing file, such as mode, is
419
  # preserved.  This works hard to avoid loss of any metadata, but will result
420
  # in an inode change for the file.
421
  #
422
  # Arguments: `filename`, `default_mode`
423
  #
424
  # The filename is the file we are going to replace.
425
  #
426
  # The default_mode is the mode to use when the target file doesn't already
427
  # exist; if the file is present we copy the existing mode/owner/group values
428
  # across.
429
  def replace_file(file, default_mode, &block)
430
    raise Puppet::DevError, "replace_file requires a block" unless block_given?
431
432
    file     = Pathname(file)
433
    tempfile = Tempfile.new(file.basename.to_s, file.dirname.to_s)
434
435
    file_exists = file.exist?
436
437
    # If the file exists, use its current mode/owner/group. If it doesn't, use
438
    # the supplied mode, and default to current user/group.
439
    if file_exists
440
      stat = file.lstat
441
442
      # We only care about the four lowest-order octets. Higher octets are
443
      # filesystem-specific.
444
      mode = stat.mode & 07777
445
      uid = stat.uid
446
      gid = stat.gid
447
    else
448
      mode = default_mode
449
      uid = Process.euid
450
      gid = Process.egid
420
    end
451
    end
452
453
    # Set properties of the temporary file before we write the content, because
454
    # Tempfile doesn't promise to be safe from reading by other people, just
455
    # that it avoids races around creating the file.
456
    tempfile.chmod(mode)
457
    tempfile.chown(uid, gid)
458
459
    # OK, now allow the caller to write the content of the file.
460
    yield tempfile
461
462
    # Now, make sure the data (which includes the mode) is safe on disk.
463
    tempfile.flush
421
    begin
464
    begin
422
      File.open(file,File::CREAT|File::EXCL|File::TRUNC|File::WRONLY,&block)
465
      tempfile.fsync
423
    rescue Errno::EEXIST
466
    rescue NotImplementedError
424
      desc = File.symlink?(file) ? "symlink to #{File.readlink(file)}" : File.stat(file).ftype
467
      # fsync may not be implemented by Ruby on all platforms, but
425
      puts "Warning: #{file} was apparently created by another process (as"
468
      # there is absolutely no recovery path if we detect that.  So, we just
426
      puts "a #{desc}) as soon as it was deleted by this process.  Someone may be trying"
469
      # ignore the return code.
427
      puts "to do something objectionable (such as tricking you into overwriting system"
470
      #
428
      puts "files if you are running as root)."
471
      # However, don't be fooled: that is accepting that we are running in
429
      raise
472
      # an unsafe fashion.  If you are porting to a new platform don't stub
473
      # that out.
430
    end
474
    end
475
476
    tempfile.close
477
478
    File.rename(tempfile.path, file)
479
480
    # Ideally, we would now fsync the directory as well, but Ruby doesn't
481
    # have support for that, and it doesn't matter /that/ much...
482
483
    # Return something true, and possibly useful.
484
    file
431
  end
485
  end
432
  module_function :secure_open
486
  module_function :replace_file
433
end
487
end
434
end
488
end
435
489
(-)a/lib/puppet/util/reference.rb (-7 / +8 lines)
Lines 36-49 class Puppet::Util::Reference Link Here
36
36
37
  def self.pdf(text)
37
  def self.pdf(text)
38
    puts "creating pdf"
38
    puts "creating pdf"
39
    Puppet::Util.secure_open("/tmp/puppetdoc.txt", "w") do |f|
39
    rst2latex = which('rst2latex') || which('rst2latex.py') ||
40
      f.puts text
40
      raise("Could not find rst2latex")
41
    end
41
42
    rst2latex = which('rst2latex') || which('rst2latex.py') || raise("Could not find rst2latex")
43
    cmd = %{#{rst2latex} /tmp/puppetdoc.txt > /tmp/puppetdoc.tex}
42
    cmd = %{#{rst2latex} /tmp/puppetdoc.txt > /tmp/puppetdoc.tex}
44
    Puppet::Util.secure_open("/tmp/puppetdoc.tex","w") do |f|
43
    Puppet::Util.replace_file("/tmp/puppetdoc.txt") {|f| f.puts text }
45
      # If we get here without an error, /tmp/puppetdoc.tex isn't a tricky cracker's symlink
44
    # There used to be an attempt to use secure_open / replace_file to secure
46
    end
45
    # the target, too, but that did nothing: the race was still here.  We can
46
    # get exactly the same benefit from running this effort:
47
    File.unlink('/tmp/puppetdoc.tex') rescue nil
47
    output = %x{#{cmd}}
48
    output = %x{#{cmd}}
48
    unless $CHILD_STATUS == 0
49
    unless $CHILD_STATUS == 0
49
      $stderr.puts "rst2latex failed"
50
      $stderr.puts "rst2latex failed"
(-)a/lib/puppet/util/suidmanager.rb (-35 / +50 lines)
Lines 1-5 Link Here
1
require 'puppet/util/warnings'
1
require 'puppet/util/warnings'
2
require 'forwardable'
2
require 'forwardable'
3
require 'etc'
3
4
4
module Puppet::Util::SUIDManager
5
module Puppet::Util::SUIDManager
5
  include Puppet::Util::Warnings
6
  include Puppet::Util::Warnings
Lines 40-94 module Puppet::Util::SUIDManager Link Here
40
    Process.uid == 0
41
    Process.uid == 0
41
  end
42
  end
42
43
43
  # Runs block setting uid and gid if provided then restoring original ids
44
  # Methods to handle changing uid/gid of the running process. In general,
45
  # these will noop or fail on Windows, and require root to change to anything
46
  # but the current uid/gid (which is a noop).
47
48
  # Runs block setting euid and egid if provided then restoring original ids.
49
  # If running on Windows or without root, the block will be run with the
50
  # current euid/egid.
44
  def asuser(new_uid=nil, new_gid=nil)
51
  def asuser(new_uid=nil, new_gid=nil)
45
    return yield if Puppet.features.microsoft_windows? or !root?
52
    return yield if Puppet.features.microsoft_windows?
53
    return yield unless root?
54
    return yield unless new_uid or new_gid
46
55
47
    old_euid, old_egid = self.euid, self.egid
56
    old_euid, old_egid = self.euid, self.egid
48
    begin
57
    begin
49
      change_group(new_gid) if new_gid
58
      change_privileges(new_uid, new_gid, false)
50
      change_user(new_uid) if new_uid
51
59
52
      yield
60
      yield
53
    ensure
61
    ensure
54
      change_group(old_egid)
62
      change_privileges(new_uid ? old_euid : nil, old_egid, false)
55
      change_user(old_euid)
56
    end
63
    end
57
  end
64
  end
58
  module_function :asuser
65
  module_function :asuser
59
66
67
  # If `permanently` is set, will permanently change the uid/gid of the
68
  # process. If not, it will only set the euid/egid. If only uid is supplied,
69
  # the primary group of the supplied gid will be used. If only gid is
70
  # supplied, only gid will be changed. This method will fail if used on
71
  # Windows.
72
  def change_privileges(uid=nil, gid=nil, permanently=false)
73
    return unless uid or gid
74
75
    unless gid
76
      uid = convert_xid(:uid, uid)
77
      gid = Etc.getpwuid(uid).gid
78
    end
79
80
    change_group(gid, permanently)
81
    change_user(uid, permanently) if uid
82
  end
83
  module_function :change_privileges
84
85
  # Changes the egid of the process if `permanently` is not set, otherwise
86
  # changes gid. This method will fail if used on Windows, or attempting to
87
  # change to a different gid without root.
60
  def change_group(group, permanently=false)
88
  def change_group(group, permanently=false)
61
    gid = convert_xid(:gid, group)
89
    gid = convert_xid(:gid, group)
62
    raise Puppet::Error, "No such group #{group}" unless gid
90
    raise Puppet::Error, "No such group #{group}" unless gid
63
91
64
    if permanently
92
    if permanently
65
      begin
93
      Process::GID.change_privilege(gid)
66
        Process::GID.change_privilege(gid)
67
      rescue NotImplementedError
68
        Process.egid = gid
69
        Process.gid  = gid
70
      end
71
    else
94
    else
72
      Process.egid = gid
95
      Process.egid = gid
73
    end
96
    end
74
  end
97
  end
75
  module_function :change_group
98
  module_function :change_group
76
99
100
  # As change_group, but operates on uids. If changing user permanently,
101
  # supplementary groups will be set the to default groups for the new uid.
77
  def change_user(user, permanently=false)
102
  def change_user(user, permanently=false)
78
    uid = convert_xid(:uid, user)
103
    uid = convert_xid(:uid, user)
79
    raise Puppet::Error, "No such user #{user}" unless uid
104
    raise Puppet::Error, "No such user #{user}" unless uid
80
105
81
    if permanently
106
    if permanently
82
      begin
107
      # If changing uid, we must be root. So initgroups first here.
83
        Process::UID.change_privilege(uid)
108
      initgroups(uid)
84
      rescue NotImplementedError
109
85
        # If changing uid, we must be root. So initgroups first here.
110
      Process::UID.change_privilege(uid)
86
        initgroups(uid)
87
        Process.euid = uid
88
        Process.uid  = uid
89
      end
90
    else
111
    else
91
      # If we're already root, initgroups before changing euid. If we're not,
112
      # We must be root to initgroups, so initgroups before dropping euid if
113
      # we're root, otherwise elevate euid before initgroups.
92
      # change euid (to root) first.
114
      # change euid (to root) first.
93
      if Process.euid == 0
115
      if Process.euid == 0
94
        initgroups(uid)
116
        initgroups(uid)
Lines 113-122 module Puppet::Util::SUIDManager Link Here
113
  end
135
  end
114
  module_function :convert_xid
136
  module_function :convert_xid
115
137
116
  # Initialize supplementary groups
138
  # Initialize primary and supplemental groups to those of the target user.  We
117
  def initgroups(user)
139
  # take the UID and manually look up their details in the system database,
118
    require 'etc'
140
  # including username and primary group. This method will fail on Windows, or
119
    Process.initgroups(Etc.getpwuid(user).name, Process.gid)
141
  # if used without root to initgroups of another user.
142
  def initgroups(uid)
143
    pwent = Etc.getpwuid(uid)
144
    Process.initgroups(pwent.name, pwent.gid)
120
  end
145
  end
121
146
122
  module_function :initgroups
147
  module_function :initgroups
Lines 126-140 module Puppet::Util::SUIDManager Link Here
126
    [output, $CHILD_STATUS.dup]
151
    [output, $CHILD_STATUS.dup]
127
  end
152
  end
128
  module_function :run_and_capture
153
  module_function :run_and_capture
129
130
  def system(command, new_uid=nil, new_gid=nil)
131
    status = nil
132
    asuser(new_uid, new_gid) do
133
      Kernel.system(command)
134
      status = $CHILD_STATUS.dup
135
    end
136
    status
137
  end
138
  module_function :system
139
end
154
end
140
155
(-)a/spec/unit/provider/user/user_role_add_spec.rb (-11 / +48 lines)
Lines 1-10 Link Here
1
#!/usr/bin/env ruby
1
#!/usr/bin/env ruby
2
2
3
require File.dirname(__FILE__) + '/../../../spec_helper'
3
require File.dirname(__FILE__) + '/../../../spec_helper'
4
require 'tempfile'
4
5
5
provider_class = Puppet::Type.type(:user).provider(:user_role_add)
6
provider_class = Puppet::Type.type(:user).provider(:user_role_add)
6
7
7
describe provider_class do
8
describe provider_class do
9
  include PuppetSpec::Files
10
8
  before do
11
  before do
9
    @resource = stub("resource", :name => "myuser", :managehome? => nil)
12
    @resource = stub("resource", :name => "myuser", :managehome? => nil)
10
    @resource.stubs(:should).returns "fakeval"
13
    @resource.stubs(:should).returns "fakeval"
Lines 244-260 describe provider_class do Link Here
244
  end
247
  end
245
248
246
  describe "when setting the password" do
249
  describe "when setting the password" do
247
    #how can you mock these blocks up?
250
    let(:path) { tmpfile('etc-shadow') }
248
    it "should open /etc/shadow for reading and /etc/shadow_tmp for writing" do
251
249
      File.expects(:open).with("/etc/shadow", "r")
252
    before :each do
250
      File.stubs(:rename)
253
      @provider.stubs(:target_file_path).returns(path)
251
      @provider.password=("hashedpassword")
254
    end
252
    end
255
253
256
    def write_fixture(content)
254
    it "should rename the /etc/shadow_tmp to /etc/shadow" do
257
      File.open(path, 'w') { |f| f.print(content) }
255
      File.stubs(:open).with("/etc/shadow", "r")
258
    end
256
      File.expects(:rename).with("/etc/shadow_tmp", "/etc/shadow")
259
257
      @provider.password=("hashedpassword")
260
    it "should update the target user" do
261
      write_fixture <<FIXTURE
262
fakeval:seriously:15315:0:99999:7:::
263
FIXTURE
264
      @provider.password = "totally"
265
      File.read(path).should =~ /^fakeval:totally:/
266
    end
267
268
    it "should only update the target user" do
269
      write_fixture <<FIXTURE
270
before:seriously:15315:0:99999:7:::
271
fakeval:seriously:15315:0:99999:7:::
272
fakevalish:seriously:15315:0:99999:7:::
273
after:seriously:15315:0:99999:7:::
274
FIXTURE
275
      @provider.password = "totally"
276
      File.read(path).should == <<EOT
277
before:seriously:15315:0:99999:7:::
278
fakeval:totally:15315:0:99999:7:::
279
fakevalish:seriously:15315:0:99999:7:::
280
after:seriously:15315:0:99999:7:::
281
EOT
282
    end
283
284
    # This preserves the current semantics, but is it right? --daniel 2012-02-05
285
    it "should do nothing if the target user is missing" do
286
      fixture = <<FIXTURE
287
before:seriously:15315:0:99999:7:::
288
fakevalish:seriously:15315:0:99999:7:::
289
after:seriously:15315:0:99999:7:::
290
FIXTURE
291
292
      write_fixture fixture
293
      @provider.password = "totally"
294
      File.read(path).should == fixture
258
    end
295
    end
259
  end
296
  end
260
297
(-)a/spec/unit/type/k5login_spec.rb (+115 lines)
Line 0 Link Here
1
#!/usr/bin/env ruby
2
require 'spec_helper'
3
require 'fileutils'
4
require 'puppet/type'
5
6
describe Puppet::Type.type(:k5login) do
7
  include PuppetSpec::Files
8
9
  context "the type class" do
10
    subject { described_class }
11
    it { should be_validattr :ensure }
12
    it { should be_validattr :path }
13
    it { should be_validattr :principals }
14
    it { should be_validattr :mode }
15
    # We have one, inline provider implemented.
16
    it { should be_validattr :provider }
17
  end
18
19
  let(:path) { tmpfile('k5login') }
20
21
  def resource(attrs = {})
22
    attrs = {
23
      :ensure     => 'present',
24
      :path       => path,
25
      :principals => 'fred@EXAMPLE.COM'
26
    }.merge(attrs)
27
28
    if content = attrs.delete(:content)
29
      File.open(path, 'w') { |f| f.print(content) }
30
    end
31
32
    resource = described_class.new(attrs)
33
    resource
34
  end
35
36
  before :each do
37
    FileUtils.touch(path)
38
  end
39
40
  context "the provider" do
41
    context "when the file is missing" do
42
      it "should initially be absent" do
43
        File.delete(path)
44
        resource.retrieve[:ensure].must == :absent
45
      end
46
47
      it "should create the file when synced" do
48
        resource(:ensure => 'present').parameter(:ensure).sync
49
        File.should be_exist path
50
      end
51
    end
52
53
    context "when the file is present" do
54
      context "retrieved initial state" do
55
        subject { resource.retrieve }
56
57
        it "should retrieve its properties correctly with zero principals" do
58
          subject[:ensure].should == :present
59
          subject[:principals].should == []
60
          # We don't really care what the mode is, just that it got it
61
          subject[:mode].should_not be_nil
62
        end
63
64
        context "with one principal" do
65
          subject { resource(:content => "daniel@EXAMPLE.COM\n").retrieve }
66
67
          it "should retrieve its principals correctly" do
68
            subject[:principals].should == ["daniel@EXAMPLE.COM"]
69
          end
70
        end
71
72
        context "with two principals" do
73
          subject do
74
            content = ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"].join("\n")
75
            resource(:content => content).retrieve
76
          end
77
78
          it "should retrieve its principals correctly" do
79
            subject[:principals].should == ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"]
80
          end
81
        end
82
      end
83
84
      it "should remove the file ensure is absent" do
85
        resource(:ensure => 'absent').property(:ensure).sync
86
        File.should_not be_exist path
87
      end
88
89
      it "should write one principal to the file" do
90
        File.read(path).should == ""
91
        resource(:principals => ["daniel@EXAMPLE.COM"]).property(:principals).sync
92
        File.read(path).should == "daniel@EXAMPLE.COM\n"
93
      end
94
95
      it "should write multiple principals to the file" do
96
        content = ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"]
97
98
        File.read(path).should == ""
99
        resource(:principals => content).property(:principals).sync
100
        File.read(path).should == content.join("\n") + "\n"
101
      end
102
103
      describe "when setting the mode", :unless => Puppet.features.microsoft_windows? do
104
        # The defined input type is "mode, as an octal string"
105
        ["400", "600", "700", "644", "664"].each do |mode|
106
          it "should update the mode to #{mode}" do
107
            resource(:mode => mode).property(:mode).sync
108
109
            (File.stat(path).mode & 07777).to_s(8).should == mode
110
          end
111
        end
112
      end
113
    end
114
  end
115
end
(-)a/spec/unit/util/suidmanager_spec.rb (-72 / +82 lines)
Lines 13-22 describe Puppet::Util::SUIDManager do Link Here
13
13
14
  before :each do
14
  before :each do
15
    Puppet::Util::SUIDManager.stubs(:convert_xid).returns(42)
15
    Puppet::Util::SUIDManager.stubs(:convert_xid).returns(42)
16
    Puppet::Util::SUIDManager.stubs(:initgroups)
16
    pwent = stub('pwent', :name => 'fred', :uid => 42, :gid => 42)
17
    Etc.stubs(:getpwuid).with(42).returns(pwent)
17
18
18
    [:euid, :egid, :uid, :gid, :groups].each do |id|
19
    [:euid, :egid, :uid, :gid, :groups].each do |id|
19
      Process.stubs("#{id}=").with {|value| xids[id] = value}
20
      Process.stubs("#{id}=").with {|value| xids[id] = value }
21
    end
22
  end
23
24
  describe "#initgroups" do
25
    it "should use the primary group of the user as the 'basegid'" do
26
      Process.expects(:initgroups).with('fred', 42)
27
      described_class.initgroups(42)
20
    end
28
    end
21
  end
29
  end
22
30
Lines 31-75 describe Puppet::Util::SUIDManager do Link Here
31
  end
39
  end
32
40
33
  describe "#asuser" do
41
  describe "#asuser" do
34
    it "should set euid/egid when root" do
42
    it "should not get or set euid/egid when not root" do
35
      Process.stubs(:uid).returns(0)
43
      Process.stubs(:uid).returns(1)
36
44
37
      Process.stubs(:egid).returns(51)
45
      Process.stubs(:egid).returns(51)
38
      Process.stubs(:euid).returns(50)
46
      Process.stubs(:euid).returns(50)
39
47
40
      Puppet::Util::SUIDManager.stubs(:convert_xid).with(:gid, 51).returns(51)
48
      Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) {}
41
      Puppet::Util::SUIDManager.stubs(:convert_xid).with(:uid, 50).returns(50)
49
50
      xids.should be_empty
51
    end
42
52
43
      yielded = false
53
    context "when root and not windows" do
44
      Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) do
54
      before :each do
45
        xids[:egid].should == user[:gid]
55
        Process.stubs(:uid).returns(0)
46
        xids[:euid].should == user[:uid]
56
        Puppet.features.stubs(:microsoft_windows?).returns(false)
47
        yielded = true
48
      end
57
      end
49
58
50
      xids[:egid].should == 51
59
      it "should set euid/egid when root" do
51
      xids[:euid].should == 50
60
        Process.stubs(:uid).returns(0)
52
61
53
      # It's possible asuser could simply not yield, so the assertions in the
62
        Process.stubs(:egid).returns(51)
54
      # block wouldn't fail. So verify those actually got checked.
63
        Process.stubs(:euid).returns(50)
55
      yielded.should be_true
56
    end
57
64
58
    it "should not get or set euid/egid when not root" do
65
        Puppet::Util::SUIDManager.stubs(:convert_xid).with(:gid, 51).returns(51)
59
      Process.stubs(:uid).returns(1)
66
        Puppet::Util::SUIDManager.stubs(:convert_xid).with(:uid, 50).returns(50)
67
        Puppet::Util::SUIDManager.stubs(:initgroups).returns([])
60
68
61
      Process.stubs(:egid).returns(51)
69
        yielded = false
62
      Process.stubs(:euid).returns(50)
70
        Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) do
71
          xids[:egid].should == user[:gid]
72
          xids[:euid].should == user[:uid]
73
          yielded = true
74
        end
63
75
64
      Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) {}
76
        xids[:egid].should == 51
77
        xids[:euid].should == 50
65
78
66
      xids.should be_empty
79
        # It's possible asuser could simply not yield, so the assertions in the
80
        # block wouldn't fail. So verify those actually got checked.
81
        yielded.should be_true
82
      end
83
84
      it "should just yield if user and group are nil" do
85
        yielded = false
86
        Puppet::Util::SUIDManager.asuser(nil, nil) { yielded = true }
87
        yielded.should be_true
88
        xids.should == {}
89
      end
90
91
      it "should just change group if only group is given" do
92
        yielded = false
93
        Puppet::Util::SUIDManager.asuser(nil, 42) { yielded = true }
94
        yielded.should be_true
95
        xids.should == { :egid => 42 }
96
      end
97
98
      it "should change gid to the primary group of uid by default" do
99
        Process.stubs(:initgroups)
100
101
        yielded = false
102
        Puppet::Util::SUIDManager.asuser(42) { yielded = true }
103
        yielded.should be_true
104
        xids.should == { :euid => 42, :egid => 42 }
105
      end
106
107
      it "should change both uid and gid if given" do
108
        # I don't like the sequence, but it is the only way to assert on the
109
        # internal behaviour in a reliable fashion, given we need multiple
110
        # sequenced calls to the same methods. --daniel 2012-02-05
111
        horror = sequence('of user and group changes')
112
        Puppet::Util::SUIDManager.expects(:change_group).with(43, false).in_sequence(horror)
113
        Puppet::Util::SUIDManager.expects(:change_user).with(42, false).in_sequence(horror)
114
        Puppet::Util::SUIDManager.expects(:change_group).
115
          with(Puppet::Util::SUIDManager.egid, false).in_sequence(horror)
116
        Puppet::Util::SUIDManager.expects(:change_user).
117
          with(Puppet::Util::SUIDManager.euid, false).in_sequence(horror)
118
119
        yielded = false
120
        Puppet::Util::SUIDManager.asuser(42, 43) { yielded = true }
121
        yielded.should be_true
122
      end
67
    end
123
    end
68
  end
124
  end
69
125
70
  describe "#change_group" do
126
  describe "#change_group" do
71
    describe "when changing permanently" do
127
    describe "when changing permanently" do
72
      it "should try to change_privilege if it is supported" do
128
      it "should change_privilege" do
73
        Process::GID.expects(:change_privilege).with do |gid|
129
        Process::GID.expects(:change_privilege).with do |gid|
74
          Process.gid = gid
130
          Process.gid = gid
75
          Process.egid = gid
131
          Process.egid = gid
Lines 80-94 describe Puppet::Util::SUIDManager do Link Here
80
        xids[:egid].should == 42
136
        xids[:egid].should == 42
81
        xids[:gid].should == 42
137
        xids[:gid].should == 42
82
      end
138
      end
83
84
      it "should change both egid and gid if change_privilege isn't supported" do
85
        Process::GID.stubs(:change_privilege).raises(NotImplementedError)
86
87
        Puppet::Util::SUIDManager.change_group(42, true)
88
89
        xids[:egid].should == 42
90
        xids[:gid].should == 42
91
      end
92
    end
139
    end
93
140
94
    describe "when changing temporarily" do
141
    describe "when changing temporarily" do
Lines 103-123 describe Puppet::Util::SUIDManager do Link Here
103
150
104
  describe "#change_user" do
151
  describe "#change_user" do
105
    describe "when changing permanently" do
152
    describe "when changing permanently" do
106
      it "should try to change_privilege if it is supported" do
153
      it "should change_privilege" do
107
        Process::UID.expects(:change_privilege).with do |uid|
154
        Process::UID.expects(:change_privilege).with do |uid|
108
          Process.uid = uid
155
          Process.uid = uid
109
          Process.euid = uid
156
          Process.euid = uid
110
        end
157
        end
111
158
112
        Puppet::Util::SUIDManager.change_user(42, true)
113
114
        xids[:euid].should == 42
115
        xids[:uid].should == 42
116
      end
117
118
      it "should change euid and uid and groups if change_privilege isn't supported" do
119
        Process::UID.stubs(:change_privilege).raises(NotImplementedError)
120
121
        Puppet::Util::SUIDManager.expects(:initgroups).with(42)
159
        Puppet::Util::SUIDManager.expects(:initgroups).with(42)
122
160
123
        Puppet::Util::SUIDManager.change_user(42, true)
161
        Puppet::Util::SUIDManager.change_user(42, true)
Lines 129-134 describe Puppet::Util::SUIDManager do Link Here
129
167
130
    describe "when changing temporarily" do
168
    describe "when changing temporarily" do
131
      it "should change only euid and groups" do
169
      it "should change only euid and groups" do
170
        Puppet::Util::SUIDManager.stubs(:initgroups).returns([])
132
        Puppet::Util::SUIDManager.change_user(42, false)
171
        Puppet::Util::SUIDManager.change_user(42, false)
133
172
134
        xids[:euid].should == 42
173
        xids[:euid].should == 42
Lines 165-199 describe Puppet::Util::SUIDManager do Link Here
165
      Kernel.system '' if $CHILD_STATUS.nil?
204
      Kernel.system '' if $CHILD_STATUS.nil?
166
    end
205
    end
167
206
168
    describe "with #system" do
169
      it "should set euid/egid when root" do
170
        Process.stubs(:uid).returns(0)
171
        Process.stubs(:egid).returns(51)
172
        Process.stubs(:euid).returns(50)
173
174
        Puppet::Util::SUIDManager.stubs(:convert_xid).with(:gid, 51).returns(51)
175
        Puppet::Util::SUIDManager.stubs(:convert_xid).with(:uid, 50).returns(50)
176
177
        Puppet::Util::SUIDManager.expects(:change_group).with(user[:uid])
178
        Puppet::Util::SUIDManager.expects(:change_user).with(user[:uid])
179
180
        Puppet::Util::SUIDManager.expects(:change_group).with(51)
181
        Puppet::Util::SUIDManager.expects(:change_user).with(50)
182
183
        Kernel.expects(:system).with('blah')
184
        Puppet::Util::SUIDManager.system('blah', user[:uid], user[:gid])
185
      end
186
187
      it "should not get or set euid/egid when not root" do
188
        Process.stubs(:uid).returns(1)
189
        Kernel.expects(:system).with('blah')
190
191
        Puppet::Util::SUIDManager.system('blah', user[:uid], user[:gid])
192
193
        xids.should be_empty
194
      end
195
    end
196
197
    describe "with #run_and_capture" do
207
    describe "with #run_and_capture" do
198
      it "should capture the output and return process status" do
208
      it "should capture the output and return process status" do
199
        Puppet::Util.
209
        Puppet::Util.
(-)a/spec/unit/util_spec.rb (-1 / +104 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/env rspec
2
require 'spec_helper'
3
4
describe Puppet::Util do
5
  subject { Puppet::Util }
6
  include PuppetSpec::Files
7
8
  context "#replace_file" do
9
    it { should respond_to :replace_file }
10
11
    let :target do
12
      target = Tempfile.new("puppet-util-replace-file")
13
      target.puts("hello, world")
14
      target.flush              # make sure content is on disk.
15
      target.fsync rescue nil
16
      target.close
17
      target
18
    end
19
20
    it "should fail if no block is given" do
21
      expect { subject.replace_file(target.path, 0600) }.to raise_error /block/
22
    end
23
24
    it "should replace a file when invoked" do
25
      # Check that our file has the expected content.
26
      File.read(target.path).should == "hello, world\n"
27
28
      # Replace the file.
29
      subject.replace_file(target.path, 0600) do |fh|
30
        fh.puts "I am the passenger..."
31
      end
32
33
      # ...and check the replacement was complete.
34
      File.read(target.path).should == "I am the passenger...\n"
35
    end
36
37
    [0555, 0600, 0660, 0700, 0770].each do |mode|
38
      it "should copy 0#{mode.to_s(8)} permissions from the target file by default" do
39
        File.chmod(mode, target.path)
40
41
        (File.stat(target.path).mode & 07777).should == mode
42
43
        subject.replace_file(target.path, 0000) {|fh| fh.puts "bazam" }
44
45
        (File.stat(target.path).mode & 07777).should == mode
46
        File.read(target.path).should == "bazam\n"
47
      end
48
    end
49
50
    it "should copy the permissions of the source file before yielding" do
51
      File.chmod(0555, target.path)
52
      inode = File.stat(target.path).ino
53
54
      yielded = false
55
      subject.replace_file(target.path, 0600) do |fh|
56
        (File.stat(fh.path).mode & 07777).should == 0555
57
        yielded = true
58
      end
59
      yielded.should be_true
60
61
      # We can't check inode on Windows
62
      File.stat(target.path).ino.should_not == inode
63
64
      (File.stat(target.path).mode & 07777).should == 0555
65
    end
66
67
    it "should use the default permissions if the source file doesn't exist" do
68
      new_target = target.path + '.foo'
69
      File.should_not be_exist(new_target)
70
71
      begin
72
        subject.replace_file(new_target, 0555) {|fh| fh.puts "foo" }
73
        (File.stat(new_target).mode & 07777).should == 0555
74
      ensure
75
        File.unlink(new_target) if File.exists?(new_target)
76
      end
77
    end
78
79
    it "should not replace the file if an exception is thrown in the block" do
80
      yielded = false
81
      threw   = false
82
83
      begin
84
        subject.replace_file(target.path, 0600) do |fh|
85
          yielded = true
86
          fh.puts "different content written, then..."
87
          raise "...throw some random failure"
88
        end
89
      rescue Exception => e
90
        if e.to_s =~ /some random failure/
91
          threw = true
92
        else
93
          raise
94
        end
95
      end
96
97
      yielded.should be_true
98
      threw.should be_true
99
100
      # ...and check the replacement was complete.
101
      File.read(target.path).should == "hello, world\n"
102
    end
103
  end
104
end

Return to bug 403963