summaryrefslogtreecommitdiffstats
path: root/lib/puppet/util/suidmanager.rb
blob: 697bce111152289895d8a1d0cb3e522384d3e348 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
require 'puppet/util/warnings'
require 'forwardable'

module Puppet::Util::SUIDManager
  include Puppet::Util::Warnings
  extend Forwardable

  # Note groups= is handled specially due to a bug in OS X 10.6
  to_delegate_to_process = [ :euid=, :euid, :egid=, :egid, :uid=, :uid, :gid=, :gid, :groups ]

  to_delegate_to_process.each do |method|
    def_delegator Process, method
    module_function method
  end

  def osx_maj_ver
    return @osx_maj_ver unless @osx_maj_ver.nil?
    require 'facter'
    # 'kernel' is available without explicitly loading all facts
    if Facter.value('kernel') != 'Darwin'
      @osx_maj_ver = false
      return @osx_maj_ver
    end
    # But 'macosx_productversion_major' requires it.
    Facter.loadfacts
    @osx_maj_ver = Facter.value('macosx_productversion_major')
  end
  module_function :osx_maj_ver

  def groups=(grouplist)
    if osx_maj_ver == '10.6'
      return true
    else
      return Process.groups = grouplist
    end
  end
  module_function :groups=

  def self.root?
    Process.uid == 0
  end

  # Runs block setting uid and gid if provided then restoring original ids
  def asuser(new_uid=nil, new_gid=nil)
    return yield if Puppet.features.microsoft_windows? or !root?

    old_euid, old_egid = self.euid, self.egid
    begin
      change_group(new_gid) if new_gid
      change_user(new_uid) if new_uid

      yield
    ensure
      change_group(old_egid)
      change_user(old_euid)
    end
  end
  module_function :asuser

  def change_group(group, permanently=false)
    gid = convert_xid(:gid, group)
    raise Puppet::Error, "No such group #{group}" unless gid

    if permanently
      begin
        Process::GID.change_privilege(gid)
      rescue NotImplementedError
        Process.egid = gid
        Process.gid  = gid
      end
    else
      Process.egid = gid
    end
  end
  module_function :change_group

  def change_user(user, permanently=false)
    uid = convert_xid(:uid, user)
    raise Puppet::Error, "No such user #{user}" unless uid

    if permanently
      begin
        Process::UID.change_privilege(uid)
      rescue NotImplementedError
        # If changing uid, we must be root. So initgroups first here.
        initgroups(uid)
        Process.euid = uid
        Process.uid  = uid
      end
    else
      # If we're already root, initgroups before changing euid. If we're not,
      # change euid (to root) first.
      if Process.euid == 0
        initgroups(uid)
        Process.euid = uid
      else
        Process.euid = uid
        initgroups(uid)
      end
    end
  end
  module_function :change_user

  # Make sure the passed argument is a number.
  def convert_xid(type, id)
    map = {:gid => :group, :uid => :user}
    raise ArgumentError, "Invalid id type #{type}" unless map.include?(type)
    ret = Puppet::Util.send(type, id)
    if ret == nil
      raise Puppet::Error, "Invalid #{map[type]}: #{id}"
    end
    ret
  end
  module_function :convert_xid

  # Initialize supplementary groups
  def initgroups(user)
    require 'etc'
    Process.initgroups(Etc.getpwuid(user).name, Process.gid)
  end

  module_function :initgroups

  def run_and_capture(command, new_uid=nil, new_gid=nil)
    output = Puppet::Util.execute(command, :failonfail => false, :combine => true, :uid => new_uid, :gid => new_gid)
    [output, $CHILD_STATUS.dup]
  end
  module_function :run_and_capture

  def system(command, new_uid=nil, new_gid=nil)
    status = nil
    asuser(new_uid, new_gid) do
      Kernel.system(command)
      status = $CHILD_STATUS.dup
    end
    status
  end
  module_function :system
end