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/feature/base.rb (+1 lines)
Lines 23-28 Puppet.features.add(:microsoft_windows) do Link Here
23
    require 'win32ole'
23
    require 'win32ole'
24
    require 'win32/api'
24
    require 'win32/api'
25
    require 'win32/taskscheduler'
25
    require 'win32/taskscheduler'
26
    require 'puppet/util/windows/security'
26
    true
27
    true
27
  rescue LoadError => err
28
  rescue LoadError => err
28
    warn "Cannot run on Microsoft Windows without the sys-admin, win32-process, win32-dir, win32-service and win32-taskscheduler gems: #{err}" unless Puppet.features.posix?
29
    warn "Cannot run on Microsoft Windows without the sys-admin, win32-process, win32-dir, win32-service and win32-taskscheduler gems: #{err}" unless Puppet.features.posix?
(-)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 (-22 / +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-196 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(shadow_file, "r") do |shadow|
187
      shadow = File.read(target_file_path)
172
        File.open("#{shadow_file}_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_arr[2] = Time.now().to_i / 86400
193
        shadow.each_line do |line|
178
              line = line_arr.join(':')
194
          line_arr = line.split(':')
179
            end
195
          if line_arr[0] == @resource[:name]
180
            shadow_tmp.print line
196
            line_arr[1] = cryptopw
197
            line = line_arr.join(':')
181
          end
198
          end
199
          fh.print line
182
        end
200
        end
183
      end
201
      end
184
      File.rename("#{shadow_file}_tmp", shadow_file)
185
    rescue => detail
202
    rescue => detail
186
      fail "Could not write temporary shadow file: #{detail}"
203
      fail "Could not write replace #{target_file_path}: #{detail}"
187
    ensure
188
      # Make sure this *always* gets deleted
189
      File.unlink("#{shadow_file}_tmp") if File.exist?("#{shadow_file}_tmp")
190
    end
204
    end
191
  end
205
  end
192
193
  private
194
195
  def shadow_file; '/etc/shadow'; end
196
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 (-22 / +78 lines)
Lines 2-13 Link Here
2
2
3
require 'English'
3
require 'English'
4
require 'puppet/util/monkey_patches'
4
require 'puppet/util/monkey_patches'
5
require 'sync'
6
require 'tempfile'
7
require 'puppet/external/lock'
5
require 'puppet/external/lock'
8
require 'monitor'
9
require 'puppet/util/execution_stub'
6
require 'puppet/util/execution_stub'
10
require 'uri'
7
require 'uri'
8
require 'sync'
9
require 'monitor'
10
require 'tempfile'
11
require 'pathname'
11
12
12
module Puppet
13
module Puppet
13
  # A command failed to execute.
14
  # A command failed to execute.
Lines 292-299 module Util Link Here
292
293
293
        3.upto(256){|fd| IO::new(fd).close rescue nil}
294
        3.upto(256){|fd| IO::new(fd).close rescue nil}
294
295
295
        Puppet::Util::SUIDManager.change_group(arguments[:gid], true) if arguments[:gid]
296
        Puppet::Util::SUIDManager.change_privileges(arguments[:uid], arguments[:gid], true)
296
        Puppet::Util::SUIDManager.change_user(arguments[:uid], true)  if arguments[:uid]
297
297
298
        ENV['LANG'] = ENV['LC_ALL'] = ENV['LC_MESSAGES'] = ENV['LANGUAGE'] = 'C'
298
        ENV['LANG'] = ENV['LC_ALL'] = ENV['LC_MESSAGES'] = ENV['LANGUAGE'] = 'C'
299
        Kernel.exec(*command)
299
        Kernel.exec(*command)
Lines 459-485 module Util Link Here
459
459
460
  module_function :memory, :thinmark
460
  module_function :memory, :thinmark
461
461
462
  def secure_open(file,must_be_w,&block)
462
  # Replace a file, securely.  This takes a block, and passes it the file
463
    raise Puppet::DevError,"secure_open only works with mode 'w'" unless must_be_w == 'w'
463
  # handle of a file open for writing.  Write the replacement content inside
464
    raise Puppet::DevError,"secure_open only requires a block"    unless block_given?
464
  # the block and it will safely replace the target file.
465
    Puppet.warning "#{file} was a symlink to #{File.readlink(file)}" if File.symlink?(file)
465
  #
466
    if File.exists?(file) or File.symlink?(file)
466
  # This method will make no changes to the target file until the content is
467
      wait = File.symlink?(file) ? 5.0 : 0.1
467
  # successfully written and the block returns without raising an error.
468
      File.delete(file)
468
  #
469
      sleep wait # give it a chance to reappear, just in case someone is actively trying something.
469
  # As far as possible the state of the existing file, such as mode, is
470
  # preserved.  This works hard to avoid loss of any metadata, but will result
471
  # in an inode change for the file.
472
  #
473
  # Arguments: `filename`, `default_mode`
474
  #
475
  # The filename is the file we are going to replace.
476
  #
477
  # The default_mode is the mode to use when the target file doesn't already
478
  # exist; if the file is present we copy the existing mode/owner/group values
479
  # across.
480
  def replace_file(file, default_mode, &block)
481
    raise Puppet::DevError, "replace_file requires a block" unless block_given?
482
    raise Puppet::DevError, "replace_file is non-functional on Windows" if Puppet.features.microsoft_windows?
483
484
    file     = Pathname(file)
485
    tempfile = Tempfile.new(file.basename.to_s, file.dirname.to_s)
486
487
    file_exists = file.exist?
488
489
    # If the file exists, use its current mode/owner/group. If it doesn't, use
490
    # the supplied mode, and default to current user/group.
491
    if file_exists
492
      stat = file.lstat
493
494
      # We only care about the four lowest-order octets. Higher octets are
495
      # filesystem-specific.
496
      mode = stat.mode & 07777
497
      uid = stat.uid
498
      gid = stat.gid
499
    else
500
      mode = default_mode
501
      uid = Process.euid
502
      gid = Process.egid
470
    end
503
    end
504
505
    # Set properties of the temporary file before we write the content, because
506
    # Tempfile doesn't promise to be safe from reading by other people, just
507
    # that it avoids races around creating the file.
508
    tempfile.chmod(mode)
509
    tempfile.chown(uid, gid)
510
511
    # OK, now allow the caller to write the content of the file.
512
    yield tempfile
513
514
    # Now, make sure the data (which includes the mode) is safe on disk.
515
    tempfile.flush
471
    begin
516
    begin
472
      File.open(file,File::CREAT|File::EXCL|File::TRUNC|File::WRONLY,&block)
517
      tempfile.fsync
473
    rescue Errno::EEXIST
518
    rescue NotImplementedError
474
      desc = File.symlink?(file) ? "symlink to #{File.readlink(file)}" : File.stat(file).ftype
519
      # fsync may not be implemented by Ruby on all platforms, but
475
      puts "Warning: #{file} was apparently created by another process (as"
520
      # there is absolutely no recovery path if we detect that.  So, we just
476
      puts "a #{desc}) as soon as it was deleted by this process.  Someone may be trying"
521
      # ignore the return code.
477
      puts "to do something objectionable (such as tricking you into overwriting system"
522
      #
478
      puts "files if you are running as root)."
523
      # However, don't be fooled: that is accepting that we are running in
479
      raise
524
      # an unsafe fashion.  If you are porting to a new platform don't stub
525
      # that out.
480
    end
526
    end
527
528
    tempfile.close
529
530
    File.rename(tempfile.path, file)
531
532
    # Ideally, we would now fsync the directory as well, but Ruby doesn't
533
    # have support for that, and it doesn't matter /that/ much...
534
535
    # Return something true, and possibly useful.
536
    file
481
  end
537
  end
482
  module_function :secure_open
538
  module_function :replace_file
483
end
539
end
484
end
540
end
485
541
(-)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 59-113 module Puppet::Util::SUIDManager Link Here
59
    Puppet::Util::Windows::User.admin?
60
    Puppet::Util::Windows::User.admin?
60
  end
61
  end
61
62
62
  # Runs block setting uid and gid if provided then restoring original ids
63
  # Methods to handle changing uid/gid of the running process. In general,
64
  # these will noop or fail on Windows, and require root to change to anything
65
  # but the current uid/gid (which is a noop).
66
67
  # Runs block setting euid and egid if provided then restoring original ids.
68
  # If running on Windows or without root, the block will be run with the
69
  # current euid/egid.
63
  def asuser(new_uid=nil, new_gid=nil)
70
  def asuser(new_uid=nil, new_gid=nil)
64
    return yield if Puppet.features.microsoft_windows? or !root?
71
    return yield if Puppet.features.microsoft_windows?
72
    return yield unless root?
73
    return yield unless new_uid or new_gid
65
74
66
    old_euid, old_egid = self.euid, self.egid
75
    old_euid, old_egid = self.euid, self.egid
67
    begin
76
    begin
68
      change_group(new_gid) if new_gid
77
      change_privileges(new_uid, new_gid, false)
69
      change_user(new_uid) if new_uid
70
78
71
      yield
79
      yield
72
    ensure
80
    ensure
73
      change_group(old_egid)
81
      change_privileges(new_uid ? old_euid : nil, old_egid, false)
74
      change_user(old_euid)
75
    end
82
    end
76
  end
83
  end
77
  module_function :asuser
84
  module_function :asuser
78
85
86
  # If `permanently` is set, will permanently change the uid/gid of the
87
  # process. If not, it will only set the euid/egid. If only uid is supplied,
88
  # the primary group of the supplied gid will be used. If only gid is
89
  # supplied, only gid will be changed. This method will fail if used on
90
  # Windows.
91
  def change_privileges(uid=nil, gid=nil, permanently=false)
92
    return unless uid or gid
93
94
    unless gid
95
      uid = convert_xid(:uid, uid)
96
      gid = Etc.getpwuid(uid).gid
97
    end
98
99
    change_group(gid, permanently)
100
    change_user(uid, permanently) if uid
101
  end
102
  module_function :change_privileges
103
104
  # Changes the egid of the process if `permanently` is not set, otherwise
105
  # changes gid. This method will fail if used on Windows, or attempting to
106
  # change to a different gid without root.
79
  def change_group(group, permanently=false)
107
  def change_group(group, permanently=false)
80
    gid = convert_xid(:gid, group)
108
    gid = convert_xid(:gid, group)
81
    raise Puppet::Error, "No such group #{group}" unless gid
109
    raise Puppet::Error, "No such group #{group}" unless gid
82
110
83
    if permanently
111
    if permanently
84
      begin
112
      Process::GID.change_privilege(gid)
85
        Process::GID.change_privilege(gid)
86
      rescue NotImplementedError
87
        Process.egid = gid
88
        Process.gid  = gid
89
      end
90
    else
113
    else
91
      Process.egid = gid
114
      Process.egid = gid
92
    end
115
    end
93
  end
116
  end
94
  module_function :change_group
117
  module_function :change_group
95
118
119
  # As change_group, but operates on uids. If changing user permanently,
120
  # supplementary groups will be set the to default groups for the new uid.
96
  def change_user(user, permanently=false)
121
  def change_user(user, permanently=false)
97
    uid = convert_xid(:uid, user)
122
    uid = convert_xid(:uid, user)
98
    raise Puppet::Error, "No such user #{user}" unless uid
123
    raise Puppet::Error, "No such user #{user}" unless uid
99
124
100
    if permanently
125
    if permanently
101
      begin
126
      # If changing uid, we must be root. So initgroups first here.
102
        Process::UID.change_privilege(uid)
127
      initgroups(uid)
103
      rescue NotImplementedError
128
104
        # If changing uid, we must be root. So initgroups first here.
129
      Process::UID.change_privilege(uid)
105
        initgroups(uid)
106
        Process.euid = uid
107
        Process.uid  = uid
108
      end
109
    else
130
    else
110
      # If we're already root, initgroups before changing euid. If we're not,
131
      # We must be root to initgroups, so initgroups before dropping euid if
132
      # we're root, otherwise elevate euid before initgroups.
111
      # change euid (to root) first.
133
      # change euid (to root) first.
112
      if Process.euid == 0
134
      if Process.euid == 0
113
        initgroups(uid)
135
        initgroups(uid)
Lines 132-141 module Puppet::Util::SUIDManager Link Here
132
  end
154
  end
133
  module_function :convert_xid
155
  module_function :convert_xid
134
156
135
  # Initialize supplementary groups
157
  # Initialize primary and supplemental groups to those of the target user.  We
136
  def initgroups(user)
158
  # take the UID and manually look up their details in the system database,
137
    require 'etc'
159
  # including username and primary group. This method will fail on Windows, or
138
    Process.initgroups(Etc.getpwuid(user).name, Process.gid)
160
  # if used without root to initgroups of another user.
161
  def initgroups(uid)
162
    pwent = Etc.getpwuid(uid)
163
    Process.initgroups(pwent.name, pwent.gid)
139
  end
164
  end
140
165
141
  module_function :initgroups
166
  module_function :initgroups
Lines 145-158 module Puppet::Util::SUIDManager Link Here
145
    [output, $CHILD_STATUS.dup]
170
    [output, $CHILD_STATUS.dup]
146
  end
171
  end
147
  module_function :run_and_capture
172
  module_function :run_and_capture
148
149
  def system(command, new_uid=nil, new_gid=nil)
150
    status = nil
151
    asuser(new_uid, new_gid) do
152
      Kernel.system(command)
153
      status = $CHILD_STATUS.dup
154
    end
155
    status
156
  end
157
  module_function :system
158
end
173
end
(-)a/spec/unit/provider/user/user_role_add_spec.rb (-38 / +45 lines)
Lines 1-6 Link Here
1
#!/usr/bin/env rspec
2
require 'spec_helper'
1
require 'spec_helper'
3
require 'puppet_spec/files'
2
require 'puppet_spec/files'
3
require 'tempfile'
4
4
5
provider_class = Puppet::Type.type(:user).provider(:user_role_add)
5
provider_class = Puppet::Type.type(:user).provider(:user_role_add)
6
6
Lines 246-289 describe provider_class do Link Here
246
  end
246
  end
247
247
248
  describe "when setting the password" do
248
  describe "when setting the password" do
249
    before :each do
249
    let(:path) { tmpfile('etc-shadow') }
250
      @shadow_file = tmpfile('shadow')
251
      File.open(@shadow_file, 'w') do |f|
252
        f.puts 'fakeval:password:0'
253
      end
254
      @provider.stubs(:shadow_file).returns(@shadow_file)
255
    end
256
257
    it 'opens #shadow_file for reading' do
258
      File.expects(:open).with(@shadow_file, "r")
259
      File.stubs(:rename)
260
261
      @provider.password = "hashedpassword"
262
    end
263
264
    it 'writes to "#{shadow_file}_tmp"' do
265
      File.stubs(:rename)
266
      File.stubs(:unlink)
267
      @provider.password = 'hashedpassword'
268
269
      File.read("#{@shadow_file}_tmp").should =~ /hashedpassword/
270
    end
271
272
    it 'renames "#{shadow_file}_tmp" to shadow_file' do
273
      File.stubs(:open)
274
      File.expects(:rename).with("#{@shadow_file}_tmp", @shadow_file)
275
276
      @provider.password = "hashedpassword"
277
    end
278
250
279
    it 'updates the last changed field' do
251
    before :each do
280
      Time.stubs(:now).returns(42 * 86400)
252
      @provider.stubs(:target_file_path).returns(path)
281
253
    end
282
      File.read(@shadow_file).should == "fakeval:password:0\n"
254
283
255
    def write_fixture(content)
284
      @provider.password = 'hashedpassword'
256
      File.open(path, 'w') { |f| f.print(content) }
285
257
    end
286
      File.read(@shadow_file).should == "fakeval:hashedpassword:42"
258
259
    it "should update the target user" do
260
      write_fixture <<FIXTURE
261
fakeval:seriously:15315:0:99999:7:::
262
FIXTURE
263
      @provider.password = "totally"
264
      File.read(path).should =~ /^fakeval:totally:/
265
    end
266
267
    it "should only update the target user" do
268
      write_fixture <<FIXTURE
269
before:seriously:15315:0:99999:7:::
270
fakeval:seriously:15315:0:99999:7:::
271
fakevalish:seriously:15315:0:99999:7:::
272
after:seriously:15315:0:99999:7:::
273
FIXTURE
274
      @provider.password = "totally"
275
      File.read(path).should == <<EOT
276
before:seriously:15315:0:99999:7:::
277
fakeval:totally:15315:0:99999:7:::
278
fakevalish:seriously:15315:0:99999:7:::
279
after:seriously:15315:0:99999:7:::
280
EOT
281
    end
282
283
    # This preserves the current semantics, but is it right? --daniel 2012-02-05
284
    it "should do nothing if the target user is missing" do
285
      fixture = <<FIXTURE
286
before:seriously:15315:0:99999:7:::
287
fakevalish:seriously:15315:0:99999:7:::
288
after:seriously:15315:0:99999:7:::
289
FIXTURE
290
291
      write_fixture fixture
292
      @provider.password = "totally"
293
      File.read(path).should == fixture
287
    end
294
    end
288
  end
295
  end
289
296
(-)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 (-83 / +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-70 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)
36
      Puppet.features.stubs(:microsoft_windows?).returns(false)
43
      Puppet.features.stubs(:microsoft_windows?).returns(false)
44
      Process.stubs(:uid).returns(1)
37
45
38
      Process.stubs(:egid).returns(51)
46
      Process.stubs(:egid).returns(51)
39
      Process.stubs(:euid).returns(50)
47
      Process.stubs(:euid).returns(50)
40
48
41
      Puppet::Util::SUIDManager.stubs(:convert_xid).with(:gid, 51).returns(51)
49
      Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) {}
42
      Puppet::Util::SUIDManager.stubs(:convert_xid).with(:uid, 50).returns(50)
43
50
44
      yielded = false
51
      xids.should be_empty
45
      Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) do
52
    end
46
        xids[:egid].should == user[:gid]
53
47
        xids[:euid].should == user[:uid]
54
    context "when root and not windows" do
48
        yielded = true
55
      before :each do
56
        Process.stubs(:uid).returns(0)
57
        Puppet.features.stubs(:microsoft_windows?).returns(false)
49
      end
58
      end
50
59
51
      xids[:egid].should == 51
60
      it "should set euid/egid when root" do
52
      xids[:euid].should == 50
61
        Process.stubs(:uid).returns(0)
53
62
54
      # It's possible asuser could simply not yield, so the assertions in the
63
        Process.stubs(:egid).returns(51)
55
      # block wouldn't fail. So verify those actually got checked.
64
        Process.stubs(:euid).returns(50)
56
      yielded.should be_true
57
    end
58
65
59
    it "should not get or set euid/egid when not root" do
66
        Puppet::Util::SUIDManager.stubs(:convert_xid).with(:gid, 51).returns(51)
60
      Process.stubs(:uid).returns(1)
67
        Puppet::Util::SUIDManager.stubs(:convert_xid).with(:uid, 50).returns(50)
68
        Puppet::Util::SUIDManager.stubs(:initgroups).returns([])
61
69
62
      Process.stubs(:egid).returns(51)
70
        yielded = false
63
      Process.stubs(:euid).returns(50)
71
        Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) do
72
          xids[:egid].should == user[:gid]
73
          xids[:euid].should == user[:uid]
74
          yielded = true
75
        end
64
76
65
      Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) {}
77
        xids[:egid].should == 51
78
        xids[:euid].should == 50
66
79
67
      xids.should be_empty
80
        # It's possible asuser could simply not yield, so the assertions in the
81
        # block wouldn't fail. So verify those actually got checked.
82
        yielded.should be_true
83
      end
84
85
      it "should just yield if user and group are nil" do
86
        yielded = false
87
        Puppet::Util::SUIDManager.asuser(nil, nil) { yielded = true }
88
        yielded.should be_true
89
        xids.should == {}
90
      end
91
92
      it "should just change group if only group is given" do
93
        yielded = false
94
        Puppet::Util::SUIDManager.asuser(nil, 42) { yielded = true }
95
        yielded.should be_true
96
        xids.should == { :egid => 42 }
97
      end
98
99
      it "should change gid to the primary group of uid by default" do
100
        Process.stubs(:initgroups)
101
102
        yielded = false
103
        Puppet::Util::SUIDManager.asuser(42) { yielded = true }
104
        yielded.should be_true
105
        xids.should == { :euid => 42, :egid => 42 }
106
      end
107
108
      it "should change both uid and gid if given" do
109
        # I don't like the sequence, but it is the only way to assert on the
110
        # internal behaviour in a reliable fashion, given we need multiple
111
        # sequenced calls to the same methods. --daniel 2012-02-05
112
        horror = sequence('of user and group changes')
113
        Puppet::Util::SUIDManager.expects(:change_group).with(43, false).in_sequence(horror)
114
        Puppet::Util::SUIDManager.expects(:change_user).with(42, false).in_sequence(horror)
115
        Puppet::Util::SUIDManager.expects(:change_group).
116
          with(Puppet::Util::SUIDManager.egid, false).in_sequence(horror)
117
        Puppet::Util::SUIDManager.expects(:change_user).
118
          with(Puppet::Util::SUIDManager.euid, false).in_sequence(horror)
119
120
        yielded = false
121
        Puppet::Util::SUIDManager.asuser(42, 43) { yielded = true }
122
        yielded.should be_true
123
      end
68
    end
124
    end
69
125
70
    it "should not get or set euid/egid on Windows" do
126
    it "should not get or set euid/egid on Windows" do
Lines 78-84 describe Puppet::Util::SUIDManager do Link Here
78
134
79
  describe "#change_group" do
135
  describe "#change_group" do
80
    describe "when changing permanently" do
136
    describe "when changing permanently" do
81
      it "should try to change_privilege if it is supported" do
137
      it "should change_privilege" do
82
        Process::GID.expects(:change_privilege).with do |gid|
138
        Process::GID.expects(:change_privilege).with do |gid|
83
          Process.gid = gid
139
          Process.gid = gid
84
          Process.egid = gid
140
          Process.egid = gid
Lines 89-103 describe Puppet::Util::SUIDManager do Link Here
89
        xids[:egid].should == 42
145
        xids[:egid].should == 42
90
        xids[:gid].should == 42
146
        xids[:gid].should == 42
91
      end
147
      end
92
93
      it "should change both egid and gid if change_privilege isn't supported" do
94
        Process::GID.stubs(:change_privilege).raises(NotImplementedError)
95
96
        Puppet::Util::SUIDManager.change_group(42, true)
97
98
        xids[:egid].should == 42
99
        xids[:gid].should == 42
100
      end
101
    end
148
    end
102
149
103
    describe "when changing temporarily" do
150
    describe "when changing temporarily" do
Lines 112-132 describe Puppet::Util::SUIDManager do Link Here
112
159
113
  describe "#change_user" do
160
  describe "#change_user" do
114
    describe "when changing permanently" do
161
    describe "when changing permanently" do
115
      it "should try to change_privilege if it is supported" do
162
      it "should change_privilege" do
116
        Process::UID.expects(:change_privilege).with do |uid|
163
        Process::UID.expects(:change_privilege).with do |uid|
117
          Process.uid = uid
164
          Process.uid = uid
118
          Process.euid = uid
165
          Process.euid = uid
119
        end
166
        end
120
167
121
        Puppet::Util::SUIDManager.change_user(42, true)
122
123
        xids[:euid].should == 42
124
        xids[:uid].should == 42
125
      end
126
127
      it "should change euid and uid and groups if change_privilege isn't supported" do
128
        Process::UID.stubs(:change_privilege).raises(NotImplementedError)
129
130
        Puppet::Util::SUIDManager.expects(:initgroups).with(42)
168
        Puppet::Util::SUIDManager.expects(:initgroups).with(42)
131
169
132
        Puppet::Util::SUIDManager.change_user(42, true)
170
        Puppet::Util::SUIDManager.change_user(42, true)
Lines 138-143 describe Puppet::Util::SUIDManager do Link Here
138
176
139
    describe "when changing temporarily" do
177
    describe "when changing temporarily" do
140
      it "should change only euid and groups" do
178
      it "should change only euid and groups" do
179
        Puppet::Util::SUIDManager.stubs(:initgroups).returns([])
141
        Puppet::Util::SUIDManager.change_user(42, false)
180
        Puppet::Util::SUIDManager.change_user(42, false)
142
181
143
        xids[:euid].should == 42
182
        xids[:euid].should == 42
Lines 174-219 describe Puppet::Util::SUIDManager do Link Here
174
      Kernel.system '' if $CHILD_STATUS.nil?
213
      Kernel.system '' if $CHILD_STATUS.nil?
175
    end
214
    end
176
215
177
    describe "with #system" do
178
      it "should set euid/egid when root" do
179
        Process.stubs(:uid).returns(0)
180
        Puppet.features.stubs(:microsoft_windows?).returns(false)
181
182
        Process.stubs(:egid).returns(51)
183
        Process.stubs(:euid).returns(50)
184
185
        Puppet::Util::SUIDManager.stubs(:convert_xid).with(:gid, 51).returns(51)
186
        Puppet::Util::SUIDManager.stubs(:convert_xid).with(:uid, 50).returns(50)
187
188
        Puppet::Util::SUIDManager.expects(:change_group).with(user[:uid])
189
        Puppet::Util::SUIDManager.expects(:change_user).with(user[:uid])
190
191
        Puppet::Util::SUIDManager.expects(:change_group).with(51)
192
        Puppet::Util::SUIDManager.expects(:change_user).with(50)
193
194
        Kernel.expects(:system).with('blah')
195
        Puppet::Util::SUIDManager.system('blah', user[:uid], user[:gid])
196
      end
197
198
      it "should not get or set euid/egid when not root" do
199
        Process.stubs(:uid).returns(1)
200
        Kernel.expects(:system).with('blah')
201
202
        Puppet::Util::SUIDManager.system('blah', user[:uid], user[:gid])
203
204
        xids.should be_empty
205
      end
206
207
      it "should not get or set euid/egid on Windows" do
208
        Puppet.features.stubs(:microsoft_windows?).returns true
209
        Kernel.expects(:system).with('blah')
210
211
        Puppet::Util::SUIDManager.system('blah', user[:uid], user[:gid])
212
213
        xids.should be_empty
214
      end
215
    end
216
217
    describe "with #run_and_capture" do
216
    describe "with #run_and_capture" do
218
      it "should capture the output and return process status" do
217
      it "should capture the output and return process status" do
219
        Puppet::Util.
218
        Puppet::Util.
(-)a/spec/unit/util_spec.rb (-1 / +126 lines)
Lines 5-10 require 'spec_helper' Link Here
5
describe Puppet::Util do
5
describe Puppet::Util do
6
  include PuppetSpec::Files
6
  include PuppetSpec::Files
7
7
8
  if Puppet.features.microsoft_windows?
9
    def set_mode(mode, file)
10
      Puppet::Util::Windows::Security.set_mode(mode, file)
11
    end
12
13
    def get_mode(file)
14
      Puppet::Util::Windows::Security.get_mode(file) & 07777
15
    end
16
  else
17
    def set_mode(mode, file)
18
      File.chmod(mode, file)
19
    end
20
21
    def get_mode(file)
22
      File.lstat(file).mode & 07777
23
    end
24
  end
25
8
  def process_status(exitstatus)
26
  def process_status(exitstatus)
9
    return exitstatus if Puppet.features.microsoft_windows?
27
    return exitstatus if Puppet.features.microsoft_windows?
10
28
Lines 545-548 describe Puppet::Util do Link Here
545
      end
563
      end
546
    end
564
    end
547
  end
565
  end
566
567
  context "#replace_file" do
568
    describe "on POSIX platforms", :if => Puppet.features.posix? do
569
      subject { Puppet::Util }
570
      it { should respond_to :replace_file }
571
572
      let :target do
573
        target = Tempfile.new("puppet-util-replace-file")
574
        target.puts("hello, world")
575
        target.flush              # make sure content is on disk.
576
        target.fsync rescue nil
577
        target.close
578
        target
579
      end
580
581
      it "should fail if no block is given" do
582
        expect { subject.replace_file(target.path, 0600) }.to raise_error /block/
583
      end
584
585
      it "should replace a file when invoked" do
586
        # Check that our file has the expected content.
587
        File.read(target.path).should == "hello, world\n"
588
589
        # Replace the file.
590
        subject.replace_file(target.path, 0600) do |fh|
591
          fh.puts "I am the passenger..."
592
        end
593
594
        # ...and check the replacement was complete.
595
        File.read(target.path).should == "I am the passenger...\n"
596
      end
597
598
      [0555, 0600, 0660, 0700, 0770].each do |mode|
599
        it "should copy 0#{mode.to_s(8)} permissions from the target file by default" do
600
          set_mode(mode, target.path)
601
602
          get_mode(target.path).should == mode
603
604
          subject.replace_file(target.path, 0000) {|fh| fh.puts "bazam" }
605
606
          get_mode(target.path).should == mode
607
          File.read(target.path).should == "bazam\n"
608
        end
609
      end
610
611
      it "should copy the permissions of the source file before yielding" do
612
        set_mode(0555, target.path)
613
        inode = File.stat(target.path).ino unless Puppet.features.microsoft_windows?
614
615
        yielded = false
616
        subject.replace_file(target.path, 0600) do |fh|
617
          get_mode(fh.path).should == 0555
618
          yielded = true
619
        end
620
        yielded.should be_true
621
622
        # We can't check inode on Windows
623
        File.stat(target.path).ino.should_not == inode unless Puppet.features.microsoft_windows?
624
625
        get_mode(target.path).should == 0555
626
      end
627
628
      it "should use the default permissions if the source file doesn't exist" do
629
        new_target = target.path + '.foo'
630
        File.should_not be_exist(new_target)
631
632
        begin
633
          subject.replace_file(new_target, 0555) {|fh| fh.puts "foo" }
634
          get_mode(new_target).should == 0555
635
        ensure
636
          File.unlink(new_target) if File.exists?(new_target)
637
        end
638
      end
639
640
      it "should not replace the file if an exception is thrown in the block" do
641
        yielded = false
642
        threw   = false
643
644
        begin
645
          subject.replace_file(target.path, 0600) do |fh|
646
            yielded = true
647
            fh.puts "different content written, then..."
648
            raise "...throw some random failure"
649
          end
650
        rescue Exception => e
651
          if e.to_s =~ /some random failure/
652
            threw = true
653
          else
654
            raise
655
          end
656
        end
657
658
        yielded.should be_true
659
        threw.should be_true
660
661
        # ...and check the replacement was complete.
662
        File.read(target.path).should == "hello, world\n"
663
      end
664
    end
665
666
    describe "on Windows platforms" do
667
      it "should fail and complain" do
668
        Puppet.features.stubs(:microsoft_windows?).returns true
669
670
        expect { Puppet::Util.replace_file("C:/foo", 0644) {} }.to raise_error(Puppet::DevError, "replace_file is non-functional on Windows")
671
      end
672
    end
673
  end
548
end
674
end
549
- 

Return to bug 403963