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 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
|
require_relative '../../../puppet/util/windows'
module Puppet::Util::Windows
module SID
require 'ffi'
extend FFI::Library
# missing from Windows::Error
ERROR_NONE_MAPPED = 1332
ERROR_INVALID_SID_STRUCTURE = 1337
# Well Known SIDs
Null = 'S-1-0'
Nobody = 'S-1-0-0'
World = 'S-1-1'
Everyone = 'S-1-1-0'
Local = 'S-1-2'
Creator = 'S-1-3'
CreatorOwner = 'S-1-3-0'
CreatorGroup = 'S-1-3-1'
CreatorOwnerServer = 'S-1-3-2'
CreatorGroupServer = 'S-1-3-3'
NonUnique = 'S-1-4'
Nt = 'S-1-5'
Dialup = 'S-1-5-1'
Network = 'S-1-5-2'
Batch = 'S-1-5-3'
Interactive = 'S-1-5-4'
Service = 'S-1-5-6'
Anonymous = 'S-1-5-7'
Proxy = 'S-1-5-8'
EnterpriseDomainControllers = 'S-1-5-9'
PrincipalSelf = 'S-1-5-10'
AuthenticatedUsers = 'S-1-5-11'
RestrictedCode = 'S-1-5-12'
TerminalServerUsers = 'S-1-5-13'
LocalSystem = 'S-1-5-18'
NtLocal = 'S-1-5-19'
NtNetwork = 'S-1-5-20'
BuiltinAdministrators = 'S-1-5-32-544'
BuiltinUsers = 'S-1-5-32-545'
Guests = 'S-1-5-32-546'
PowerUsers = 'S-1-5-32-547'
AccountOperators = 'S-1-5-32-548'
ServerOperators = 'S-1-5-32-549'
PrintOperators = 'S-1-5-32-550'
BackupOperators = 'S-1-5-32-551'
Replicators = 'S-1-5-32-552'
AllAppPackages = 'S-1-15-2-1'
# Convert an account name, e.g. 'Administrators' into a SID string,
# e.g. 'S-1-5-32-544'. The name can be specified as 'Administrators',
# 'BUILTIN\Administrators', or 'S-1-5-32-544', and will return the
# SID. Returns nil if the account doesn't exist.
def name_to_sid(name)
sid = name_to_principal(name)
sid ? sid.sid : nil
end
module_function :name_to_sid
# Convert an account name, e.g. 'Administrators' into a Principal::SID object,
# e.g. 'S-1-5-32-544'. The name can be specified as 'Administrators',
# 'BUILTIN\Administrators', or 'S-1-5-32-544', and will return the
# SID object. Returns nil if the account doesn't exist.
# This method returns a SID::Principal with the account, domain, SID, etc
def name_to_principal(name, allow_unresolved = false)
# Apparently, we accept a symbol..
name = name.to_s.strip if name
# if name is a SID string, convert it to raw bytes for use with lookup_account_sid
raw_sid_bytes = nil
begin
string_to_sid_ptr(name) do |sid_ptr|
raw_sid_bytes = sid_ptr.read_array_of_uchar(get_length_sid(sid_ptr))
end
rescue => e
# Avoid debug logs pollution with valid account names
# https://docs.microsoft.com/en-us/windows/win32/api/sddl/nf-sddl-convertstringsidtosidw#return-value
Puppet.debug("Could not retrieve raw SID bytes from '#{name}': #{e.message}") unless e.code == ERROR_INVALID_SID_STRUCTURE
end
raw_sid_bytes ? Principal.lookup_account_sid(raw_sid_bytes) : Principal.lookup_account_name(name)
rescue => e
Puppet.debug("#{e.message}")
(allow_unresolved && raw_sid_bytes) ? unresolved_principal(name, raw_sid_bytes) : nil
end
module_function :name_to_principal
class << self; alias name_to_sid_object name_to_principal; end
# Converts an octet string array of bytes to a SID::Principal object,
# e.g. [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] is the representation for
# S-1-5-18, the local 'SYSTEM' account.
# Raises an Error for nil or non-array input.
# This method returns a SID::Principal with the account, domain, SID, etc
def octet_string_to_principal(bytes)
if !bytes || !bytes.respond_to?('pack') || bytes.empty?
raise Puppet::Util::Windows::Error.new(_("Octet string must be an array of bytes"))
end
Principal.lookup_account_sid(bytes)
end
module_function :octet_string_to_principal
class << self; alias octet_string_to_sid_object octet_string_to_principal; end
# Converts a COM instance of IAdsUser or IAdsGroup to a SID::Principal object,
# Raises an Error for nil or an object without an objectSID / Name property.
# This method returns a SID::Principal with the account, domain, SID, etc
# This method will return instances even when the SID is unresolvable, as
# may be the case when domain users have been added to local groups, but
# removed from the domain
def ads_to_principal(ads_object)
if !ads_object || !ads_object.respond_to?(:ole_respond_to?) ||
!ads_object.ole_respond_to?(:objectSID) || !ads_object.ole_respond_to?(:Name)
raise Puppet::Error.new("ads_object must be an IAdsUser or IAdsGroup instance")
end
octet_string_to_principal(ads_object.objectSID)
rescue Puppet::Util::Windows::Error => e
# if the error is not a lookup / mapping problem, immediately re-raise
raise if e.code != ERROR_NONE_MAPPED
# if the Name property isn't formatted like a SID, OR
if !valid_sid?(ads_object.Name) ||
# if the objectSID doesn't match the Name property, also raise
((converted = octet_string_to_sid_string(ads_object.objectSID)) != ads_object.Name)
raise Puppet::Error.new("ads_object Name: #{ads_object.Name} invalid or does not match objectSID: #{ads_object.objectSID} (#{converted})", e)
end
unresolved_principal(ads_object.Name, ads_object.objectSID)
end
module_function :ads_to_principal
# Convert a SID string, e.g. "S-1-5-32-544" to a name,
# e.g. 'BUILTIN\Administrators'. Returns nil if an account
# for that SID does not exist.
def sid_to_name(value)
sid_bytes = []
begin
string_to_sid_ptr(value) do |ptr|
sid_bytes = ptr.read_array_of_uchar(get_length_sid(ptr))
end
rescue Puppet::Util::Windows::Error => e
raise if e.code != ERROR_INVALID_SID_STRUCTURE
end
Principal.lookup_account_sid(sid_bytes).domain_account
rescue
nil
end
module_function :sid_to_name
# https://stackoverflow.com/a/1792930 - 68 bytes, 184 characters in a string
MAXIMUM_SID_STRING_LENGTH = 184
# Convert a SID pointer to a SID string, e.g. "S-1-5-32-544".
def sid_ptr_to_string(psid)
if ! psid.kind_of?(FFI::Pointer) || IsValidSid(psid) == FFI::WIN32_FALSE
raise Puppet::Util::Windows::Error.new(_("Invalid SID"))
end
sid_string = nil
FFI::MemoryPointer.new(:pointer, 1) do |buffer_ptr|
if ConvertSidToStringSidW(psid, buffer_ptr) == FFI::WIN32_FALSE
raise Puppet::Util::Windows::Error.new(_("Failed to convert binary SID"))
end
buffer_ptr.read_win32_local_pointer do |wide_string_ptr|
if wide_string_ptr.null?
raise Puppet::Error.new(_("ConvertSidToStringSidW failed to allocate buffer for sid"))
end
sid_string = wide_string_ptr.read_arbitrary_wide_string_up_to(MAXIMUM_SID_STRING_LENGTH)
end
end
sid_string
end
module_function :sid_ptr_to_string
# Convert a SID string, e.g. "S-1-5-32-544" to a pointer (containing the
# address of the binary SID structure). The returned value can be used in
# Win32 APIs that expect a PSID, e.g. IsValidSid. The account for this
# SID may or may not exist.
def string_to_sid_ptr(string_sid, &block)
FFI::MemoryPointer.from_string_to_wide_string(string_sid) do |lpcwstr|
FFI::MemoryPointer.new(:pointer, 1) do |sid_ptr_ptr|
if ConvertStringSidToSidW(lpcwstr, sid_ptr_ptr) == FFI::WIN32_FALSE
raise Puppet::Util::Windows::Error.new(_("Failed to convert string SID: %{string_sid}") % { string_sid: string_sid })
end
sid_ptr_ptr.read_win32_local_pointer do |sid_ptr|
yield sid_ptr
end
end
end
# yielded sid_ptr has already had LocalFree called, nothing to return
nil
end
module_function :string_to_sid_ptr
# Return true if the string is a valid SID, e.g. "S-1-5-32-544", false otherwise.
def valid_sid?(string_sid)
valid = false
begin
string_to_sid_ptr(string_sid) { |ptr| valid = ! ptr.nil? && ! ptr.null? }
rescue Puppet::Util::Windows::Error => e
raise if e.code != ERROR_INVALID_SID_STRUCTURE
end
valid
end
module_function :valid_sid?
def get_length_sid(sid_ptr)
# MSDN states IsValidSid should be called on pointer first
if ! sid_ptr.kind_of?(FFI::Pointer) || IsValidSid(sid_ptr) == FFI::WIN32_FALSE
raise Puppet::Util::Windows::Error.new(_("Invalid SID"))
end
GetLengthSid(sid_ptr)
end
module_function :get_length_sid
def octet_string_to_sid_string(sid_bytes)
sid_string = nil
FFI::MemoryPointer.new(:byte, sid_bytes.length) do |sid_ptr|
sid_ptr.write_array_of_uchar(sid_bytes)
sid_string = Puppet::Util::Windows::SID.sid_ptr_to_string(sid_ptr)
end
sid_string
end
module_function :octet_string_to_sid_string
# @api private
def self.unresolved_principal(name, sid_bytes)
Principal.new(
name, # account
sid_bytes, # sid_bytes
name, # sid string
nil, #domain
# https://msdn.microsoft.com/en-us/library/cc245534.aspx?f=255&MSPPError=-2147217396
# Indicates that the type of object could not be determined. For example, no object with that SID exists.
:SidTypeUnknown)
end
ffi_convention :stdcall
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa379151(v=vs.85).aspx
# BOOL WINAPI IsValidSid(
# _In_ PSID pSid
# );
ffi_lib :advapi32
attach_function_private :IsValidSid,
[:pointer], :win32_bool
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa376399(v=vs.85).aspx
# BOOL ConvertSidToStringSid(
# _In_ PSID Sid,
# _Out_ LPTSTR *StringSid
# );
ffi_lib :advapi32
attach_function_private :ConvertSidToStringSidW,
[:pointer, :pointer], :win32_bool
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa376402(v=vs.85).aspx
# BOOL WINAPI ConvertStringSidToSid(
# _In_ LPCTSTR StringSid,
# _Out_ PSID *Sid
# );
ffi_lib :advapi32
attach_function_private :ConvertStringSidToSidW,
[:lpcwstr, :pointer], :win32_bool
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa446642(v=vs.85).aspx
# DWORD WINAPI GetLengthSid(
# _In_ PSID pSid
# );
ffi_lib :advapi32
attach_function_private :GetLengthSid, [:pointer], :dword
end
end
|