File: suidmanager.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (166 lines) | stat: -rw-r--r-- 5,593 bytes parent folder | download
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
require_relative '../../puppet/util/warnings'
require 'forwardable'
require 'etc'

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

  # Note groups= is handled specially due to a bug in OS X 10.6, 10.7,
  # and probably upcoming releases...
  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?
    @osx_maj_ver = Puppet.runtime[:facter].value('macosx_productversion_major') || false
  end
  module_function :osx_maj_ver

  def groups=(grouplist)
    begin
      return Process.groups = grouplist
    rescue Errno::EINVAL => e
      #We catch Errno::EINVAL as some operating systems (OS X in particular) can
      # cause troubles when using Process#groups= to change *this* user / process
      # list of supplementary groups membership.  This is done via Ruby's function
      # "static VALUE proc_setgroups(VALUE obj, VALUE ary)" which is effectively
      # a wrapper for "int setgroups(size_t size, const gid_t *list)" (part of SVr4
      # and 4.3BSD but not in POSIX.1-2001) that fails and sets errno to EINVAL.
      #
      # This does not appear to be a problem with Ruby but rather an issue on the
      # operating system side.  Therefore we catch the exception and look whether
      # we run under OS X or not -- if so, then we acknowledge the problem and
      # re-throw the exception otherwise.
      if osx_maj_ver and not osx_maj_ver.empty?
        return true
      else
        raise e
      end
    end
  end
  module_function :groups=

  def self.root?
    if Puppet::Util::Platform.windows?
      require_relative '../../puppet/util/windows/user'
      Puppet::Util::Windows::User.admin?
    else
      Process.uid == 0
    end
  end

  # Methods to handle changing uid/gid of the running process. In general,
  # these will noop or fail on Windows, and require root to change to anything
  # but the current uid/gid (which is a noop).

  # Runs block setting euid and egid if provided then restoring original ids.
  # If running on Windows or without root, the block will be run with the
  # current euid/egid.
  def asuser(new_uid=nil, new_gid=nil)
    return yield if Puppet::Util::Platform.windows?
    return yield unless root?
    return yield unless new_uid or new_gid

    old_euid, old_egid = self.euid, self.egid
    begin
      change_privileges(new_uid, new_gid, false)

      yield
    ensure
      change_privileges(new_uid ? old_euid : nil, old_egid, false)
    end
  end
  module_function :asuser

  # If `permanently` is set, will permanently change the uid/gid of the
  # process. If not, it will only set the euid/egid. If only uid is supplied,
  # the primary group of the supplied gid will be used. If only gid is
  # supplied, only gid will be changed. This method will fail if used on
  # Windows.
  def change_privileges(uid=nil, gid=nil, permanently=false)
    return unless uid or gid

    unless gid
      uid = convert_xid(:uid, uid)
      gid = Etc.getpwuid(uid).gid
    end

    change_group(gid, permanently)
    change_user(uid, permanently) if uid
  end
  module_function :change_privileges

  # Changes the egid of the process if `permanently` is not set, otherwise
  # changes gid. This method will fail if used on Windows, or attempting to
  # change to a different gid without root.
  def change_group(group, permanently=false)
    gid = convert_xid(:gid, group)
    raise Puppet::Error, _("No such group %{group}") % { group: group } unless gid

    return if Process.egid == gid

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

  # As change_group, but operates on uids. If changing user permanently,
  # supplementary groups will be set the to default groups for the new uid.
  def change_user(user, permanently=false)
    uid = convert_xid(:uid, user)
    raise Puppet::Error, _("No such user %{user}") % { user: user } unless uid

    return if Process.euid == uid

    if permanently
      # If changing uid, we must be root. So initgroups first here.
      initgroups(uid)

      Process::UID.change_privilege(uid)
    else
      # We must be root to initgroups, so initgroups before dropping euid if
      # we're root, otherwise elevate euid before initgroups.
      # 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)
    return id if id.kind_of? Integer
    map = {:gid => :group, :uid => :user}
    raise ArgumentError, _("Invalid id type %{type}") % { type: type } unless map.include?(type)
    ret = Puppet::Util.send(type, id)
    if ret == nil
      raise Puppet::Error, _("Invalid %{klass}: %{id}") % { klass: map[type], id: id }
    end
    ret
  end
  module_function :convert_xid

  # Initialize primary and supplemental groups to those of the target user.  We
  # take the UID and manually look up their details in the system database,
  # including username and primary group. This method will fail on Windows, or
  # if used without root to initgroups of another user.
  def initgroups(uid)
    pwent = Etc.getpwuid(uid)
    Process.initgroups(pwent.name, pwent.gid)
  end

  module_function :initgroups
end