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 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
# Utility methods for interacting with POSIX objects; mostly user and group
module Puppet::Util::POSIX
# This is a list of environment variables that we will set when we want to override the POSIX locale
LOCALE_ENV_VARS = ['LANG', 'LC_ALL', 'LC_MESSAGES', 'LANGUAGE',
'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME']
# This is a list of user-related environment variables that we will unset when we want to provide a pristine
# environment for "exec" runs
USER_ENV_VARS = ['HOME', 'USER', 'LOGNAME']
class << self
# Returns an array of all the groups that the user's a member of.
def groups_of(user)
begin
require_relative '../../puppet/ffi/posix'
groups = get_groups_list(user)
rescue StandardError, LoadError => e
Puppet.debug("Falling back to Puppet::Etc.group: #{e.message}")
groups = []
Puppet::Etc.group do |group|
groups << group.name if group.mem.include?(user)
end
end
uniq_groups = groups.uniq
if uniq_groups != groups
Puppet.debug(_('Removing any duplicate group entries'))
end
uniq_groups
end
private
def get_groups_list(user)
raise LoadError, "The 'getgrouplist' method is not available" unless Puppet::FFI::POSIX::Functions.respond_to?(:getgrouplist)
user_gid = Puppet::Etc.getpwnam(user).gid
ngroups = Puppet::FFI::POSIX::Constants::MAXIMUM_NUMBER_OF_GROUPS
while true do # rubocop:disable Lint/LiteralInCondition
FFI::MemoryPointer.new(:int) do |ngroups_ptr|
FFI::MemoryPointer.new(:uint, ngroups) do |groups_ptr|
old_ngroups = ngroups
ngroups_ptr.write_int(ngroups)
if Puppet::FFI::POSIX::Functions::getgrouplist(user, user_gid, groups_ptr, ngroups_ptr) != -1
groups_gids = groups_ptr.get_array_of_uint(0, ngroups_ptr.read_int)
result = []
groups_gids.each do |group_gid|
group_info = Puppet::Etc.getgrgid(group_gid)
result |= [group_info.name] if group_info.mem.include?(user)
end
return result
end
ngroups = ngroups_ptr.read_int
if ngroups <= old_ngroups
ngroups *= 2
end
end
end
end
end
end
# Retrieve a field from a POSIX Etc object. The id can be either an integer
# or a name. This only works for users and groups. It's also broken on
# some platforms, unfortunately, which is why we fall back to the other
# method search_posix_field in the gid and uid methods if a sanity check
# fails
def get_posix_field(space, field, id)
raise Puppet::DevError, _("Did not get id from caller") unless id
if id.is_a?(Integer)
if id > Puppet[:maximum_uid].to_i
Puppet.err _("Tried to get %{field} field for silly id %{id}") % { field: field, id: id }
return nil
end
method = methodbyid(space)
else
method = methodbyname(space)
end
begin
return Etc.send(method, id).send(field)
rescue NoMethodError, ArgumentError
# ignore it; we couldn't find the object
return nil
end
end
# A degenerate method of retrieving name/id mappings. The job of this method is
# to retrieve all objects of a certain type, search for a specific entry
# and then return a given field from that entry.
def search_posix_field(type, field, id)
idmethod = idfield(type)
integer = false
if id.is_a?(Integer)
integer = true
if id > Puppet[:maximum_uid].to_i
Puppet.err _("Tried to get %{field} field for silly id %{id}") % { field: field, id: id }
return nil
end
end
Etc.send(type) do |object|
if integer and object.send(idmethod) == id
return object.send(field)
elsif object.name == id
return object.send(field)
end
end
# Apparently the group/passwd methods need to get reset; if we skip
# this call, then new users aren't found.
case type
when :passwd; Etc.send(:endpwent)
when :group; Etc.send(:endgrent)
end
nil
end
# Determine what the field name is for users and groups.
def idfield(space)
case space.intern
when :gr, :group; return :gid
when :pw, :user, :passwd; return :uid
else
raise ArgumentError.new(_("Can only handle users and groups"))
end
end
# Determine what the method is to get users and groups by id
def methodbyid(space)
case space.intern
when :gr, :group; return :getgrgid
when :pw, :user, :passwd; return :getpwuid
else
raise ArgumentError.new(_("Can only handle users and groups"))
end
end
# Determine what the method is to get users and groups by name
def methodbyname(space)
case space.intern
when :gr, :group; return :getgrnam
when :pw, :user, :passwd; return :getpwnam
else
raise ArgumentError.new(_("Can only handle users and groups"))
end
end
# Get the GID
def gid(group)
get_posix_value(:group, :gid, group)
end
# Get the UID
def uid(user)
get_posix_value(:passwd, :uid, user)
end
private
# Get the specified id_field of a given field (user or group),
# whether an ID name is provided
def get_posix_value(location, id_field, field)
begin
field = Integer(field)
rescue ArgumentError
# pass
end
if field.is_a?(Integer)
name = get_posix_field(location, :name, field)
return nil unless name
id = get_posix_field(location, id_field, name)
check_value = id
else
id = get_posix_field(location, id_field, field)
return nil unless id
name = get_posix_field(location, :name, id)
check_value = name
end
if check_value != field
check_value_id = get_posix_field(location, id_field, check_value) if check_value
if id == check_value_id
Puppet.debug("Multiple entries found for resource: '#{location}' with #{id_field}: #{id}")
return id
else
Puppet.debug("The value retrieved: '#{check_value}' is different than the required state: '#{field}', searching in all entries")
return search_posix_field(location, id_field, field)
end
else
return id
end
end
end
|