module Puppet::Util::Windows::User

Constants

ERROR_ACCOUNT_DISABLED
ERROR_ACCOUNT_RESTRICTION

msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx These error codes indicate successful authentication but failure to logon for a separate reason

ERROR_INVALID_LOGON_HOURS
ERROR_INVALID_WORKSTATION
POLICY_AUDIT_LOG_ADMIN
POLICY_CREATE_ACCOUNT
POLICY_CREATE_PRIVILEGE
POLICY_CREATE_SECRET
POLICY_GET_PRIVATE_INFORMATION
POLICY_LOOKUP_NAMES
POLICY_NOTIFICATION
POLICY_SERVER_ADMIN
POLICY_SET_AUDIT_REQUIREMENTS
POLICY_SET_DEFAULT_QUOTA_LIMITS
POLICY_TRUST_ADMIN
POLICY_VIEW_AUDIT_INFORMATION
POLICY_VIEW_LOCAL_INFORMATION

ACCESS_MASK flags for Policy Objects docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lsad/b61b7268-987a-420b-84f9-6c75f8dc8558

SECURITY_MAX_SID_SIZE

msdn.microsoft.com/en-us/library/windows/desktop/ee207397(v=vs.85).aspx

WELL_KNOWN_SID_TYPE

msdn.microsoft.com/en-us/library/windows/desktop/aa379650(v=vs.85).aspx

Public Class Methods

admin?() click to toggle source
   # File lib/puppet/util/windows/user.rb
10 def admin?
11   return false unless check_token_membership
12 
13   # if Vista or later, check for unrestricted process token
14   elevated_supported = Puppet::Util::Windows::Process.supports_elevated_security?
15   return elevated_supported ? Puppet::Util::Windows::Process.elevated_security? : true
16 end
check_token_membership() click to toggle source
   # File lib/puppet/util/windows/user.rb
47 def check_token_membership
48   is_admin = false
49   FFI::MemoryPointer.new(:byte, SECURITY_MAX_SID_SIZE) do |sid_pointer|
50     FFI::MemoryPointer.new(:dword, 1) do |size_pointer|
51       size_pointer.write_uint32(SECURITY_MAX_SID_SIZE)
52 
53       if CreateWellKnownSid(:WinBuiltinAdministratorsSid, FFI::Pointer::NULL, sid_pointer, size_pointer) == FFI::WIN32_FALSE
54         raise Puppet::Util::Windows::Error.new(_("Failed to create administrators SID"))
55       end
56     end
57 
58     if IsValidSid(sid_pointer) == FFI::WIN32_FALSE
59       raise Puppet::Util::Windows::Error.new(_("Invalid SID"))
60     end
61 
62     FFI::MemoryPointer.new(:win32_bool, 1) do |ismember_pointer|
63       if CheckTokenMembership(FFI::Pointer::NULL_HANDLE, sid_pointer, ismember_pointer) == FFI::WIN32_FALSE
64         raise Puppet::Util::Windows::Error.new(_("Failed to check membership"))
65       end
66 
67       # Is administrators SID enabled in calling thread's access token?
68       is_admin = ismember_pointer.read_win32_bool
69     end
70   end
71 
72   is_admin
73 end
default_system_account?(name) click to toggle source

Check if a given user is one of the default system accounts These accounts do not have a password and all checks done through logon attempt will fail docs.microsoft.com/en-us/windows/security/identity-protection/access-control/local-accounts#default-local-system-accounts

   # File lib/puppet/util/windows/user.rb
30 def default_system_account?(name)
31   user_sid = Puppet::Util::Windows::SID.name_to_sid(name)
32   [Puppet::Util::Windows::SID::LocalSystem, Puppet::Util::Windows::SID::NtLocal, Puppet::Util::Windows::SID::NtNetwork].include?(user_sid)
33 end
get_rights(name) click to toggle source
    # File lib/puppet/util/windows/user.rb
148 def get_rights(name)
149   user_info = Puppet::Util::Windows::SID.name_to_principal(name.sub(/^\.\\/, "#{Puppet::Util::Windows::ADSI.computer_name}\\"))
150   return "" unless user_info
151 
152   rights = []
153   rights_pointer = FFI::MemoryPointer.new(:pointer)
154   number_of_rights = FFI::MemoryPointer.new(:ulong)
155   sid_pointer = FFI::MemoryPointer.new(:byte, user_info.sid_bytes.length).write_array_of_uchar(user_info.sid_bytes)
156 
157   new_lsa_policy_handle do |policy_handle|
158     result = LsaEnumerateAccountRights(policy_handle.read_pointer, sid_pointer, rights_pointer, number_of_rights)
159     check_lsa_nt_status_and_raise_failures(result, "LsaEnumerateAccountRights")
160   end
161 
162   number_of_rights.read_ulong.times do |index|
163     right = LSA_UNICODE_STRING.new(rights_pointer.read_pointer + index * LSA_UNICODE_STRING.size)
164     rights << right[:Buffer].read_arbitrary_wide_string_up_to
165   end
166 
167   result = LsaFreeMemory(rights_pointer.read_pointer)
168   check_lsa_nt_status_and_raise_failures(result, "LsaFreeMemory")
169 
170   rights.join(",")
171 end
load_profile(user, password) click to toggle source
    # File lib/puppet/util/windows/user.rb
125 def load_profile(user, password)
126   logon_user(user, password) do |token|
127     FFI::MemoryPointer.from_string_to_wide_string(user) do |lpUserName|
128       pi = PROFILEINFO.new
129       pi[:dwSize] = PROFILEINFO.size
130       pi[:dwFlags] = 1 # PI_NOUI - prevents display of profile error msgs
131       pi[:lpUserName] = lpUserName
132 
133       # Load the profile. Since it doesn't exist, it will be created
134       if LoadUserProfileW(token, pi.pointer) == FFI::WIN32_FALSE
135         raise Puppet::Util::Windows::Error.new(_("Failed to load user profile %{user}") % { user: user.inspect })
136       end
137 
138       Puppet.debug("Loaded profile for #{user}")
139 
140       if UnloadUserProfile(token, pi[:hProfile]) == FFI::WIN32_FALSE
141         raise Puppet::Util::Windows::Error.new(_("Failed to unload user profile %{user}") % { user: user.inspect })
142       end
143     end
144   end
145 end
localsystem?(name) click to toggle source

The name of the account in all locales is `LocalSystem`. `.LocalSystem` or `ComputerNameLocalSystem' can also be used. This account is not recognized by the security subsystem, so you cannot specify its name in a call to the `LookupAccountName` function. docs.microsoft.com/en-us/windows/win32/services/localsystem-account

   # File lib/puppet/util/windows/user.rb
22 def localsystem?(name)
23   ["LocalSystem", ".\\LocalSystem", "#{Puppet::Util::Windows::ADSI.computer_name}\\LocalSystem"].any?{ |s| s.casecmp(name) == 0 }
24 end
logon_user(name, password, domain = '.') { |token = read_handle| ... } click to toggle source
    # File lib/puppet/util/windows/user.rb
 93 def logon_user(name, password, domain = '.', &block)
 94   fLOGON32_PROVIDER_DEFAULT = 0
 95   fLOGON32_LOGON_INTERACTIVE = 2
 96   fLOGON32_LOGON_NETWORK = 3
 97 
 98   token = nil
 99   begin
100     FFI::MemoryPointer.new(:handle, 1) do |token_pointer|
101       #try logon using network else try logon using interactive mode
102       if logon_user_by_logon_type(name, domain, password, fLOGON32_LOGON_NETWORK, fLOGON32_PROVIDER_DEFAULT, token_pointer) == FFI::WIN32_FALSE
103         if logon_user_by_logon_type(name, domain, password, fLOGON32_LOGON_INTERACTIVE, fLOGON32_PROVIDER_DEFAULT, token_pointer) == FFI::WIN32_FALSE
104           raise Puppet::Util::Windows::Error.new(_("Failed to logon user %{name}") % {name: name.inspect})
105         end
106       end
107 
108       yield token = token_pointer.read_handle
109     end
110   ensure
111     FFI::WIN32.CloseHandle(token) if token
112   end
113 
114   # token has been closed by this point
115   true
116 end
password_is?(name, password, domain = '.') click to toggle source
   # File lib/puppet/util/windows/user.rb
76 def password_is?(name, password, domain = '.')
77   begin
78     logon_user(name, password, domain) { |token| }
79   rescue Puppet::Util::Windows::Error => detail
80 
81     authenticated_error_codes = Set[
82       ERROR_ACCOUNT_RESTRICTION,
83       ERROR_INVALID_LOGON_HOURS,
84       ERROR_INVALID_WORKSTATION,
85       ERROR_ACCOUNT_DISABLED,
86     ]
87 
88     return authenticated_error_codes.include?(detail.code)
89   end
90 end
remove_rights(name, rights) click to toggle source
    # File lib/puppet/util/windows/user.rb
186 def remove_rights(name, rights)
187   rights_pointer = new_lsa_unicode_strings_pointer(rights)
188   user_info = Puppet::Util::Windows::SID.name_to_principal(name.sub(/^\.\\/, "#{Puppet::Util::Windows::ADSI.computer_name}\\"))
189   sid_pointer = FFI::MemoryPointer.new(:byte, user_info.sid_bytes.length).write_array_of_uchar(user_info.sid_bytes)
190 
191   new_lsa_policy_handle do |policy_handle|
192     result = LsaRemoveAccountRights(policy_handle.read_pointer, sid_pointer, false, rights_pointer, rights.size)
193     check_lsa_nt_status_and_raise_failures(result, "LsaRemoveAccountRights")
194   end
195 end
set_rights(name, rights) click to toggle source
    # File lib/puppet/util/windows/user.rb
174 def set_rights(name, rights)
175   rights_pointer = new_lsa_unicode_strings_pointer(rights)
176   user_info = Puppet::Util::Windows::SID.name_to_principal(name.sub(/^\.\\/, "#{Puppet::Util::Windows::ADSI.computer_name}\\"))
177   sid_pointer = FFI::MemoryPointer.new(:byte, user_info.sid_bytes.length).write_array_of_uchar(user_info.sid_bytes)
178 
179   new_lsa_policy_handle do |policy_handle|
180     result = LsaAddAccountRights(policy_handle.read_pointer, sid_pointer, rights_pointer, rights.size)
181     check_lsa_nt_status_and_raise_failures(result, "LsaAddAccountRights")
182   end
183 end

Private Class Methods

check_lsa_nt_status_and_raise_failures(status, method_name) click to toggle source

docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/18d8fbe8-a967-4f1c-ae50-99ca8e491d2d

    # File lib/puppet/util/windows/user.rb
247 def self.check_lsa_nt_status_and_raise_failures(status, method_name)
248   error_code = LsaNtStatusToWinError(status)
249 
250   error_reason = case error_code.to_s(16)
251   when '0' # ERROR_SUCCESS
252     return # Method call succeded
253   when '2' # ERROR_FILE_NOT_FOUND
254     return # No rights/privilleges assigned to given user
255   when '5' # ERROR_ACCESS_DENIED
256     "Access is denied. Please make sure that puppet is running as administrator."
257   when '521' # ERROR_NO_SUCH_PRIVILEGE
258     "One or more of the given rights/privilleges are incorrect."
259   when '6ba' # RPC_S_SERVER_UNAVAILABLE
260     "The RPC server is unavailable or given domain name is invalid."
261   end
262 
263   raise Puppet::Error.new("Calling `#{method_name}` returned 'Win32 Error Code 0x%08X'. #{error_reason}" % error_code)
264 end
logon_user_by_logon_type(name, domain, password, logon_type, logon_provider, token) click to toggle source
    # File lib/puppet/util/windows/user.rb
119 def self.logon_user_by_logon_type(name, domain, password, logon_type, logon_provider, token)
120   LogonUserW(wide_string(name), wide_string(domain), password.nil? ? FFI::Pointer::NULL : wide_string(password), logon_type, logon_provider, token)
121 end
new_lsa_policy_handle() { |policy_handle| ... } click to toggle source
    # File lib/puppet/util/windows/user.rb
214 def self.new_lsa_policy_handle
215   access = 0
216   access |= POLICY_LOOKUP_NAMES
217   access |= POLICY_CREATE_ACCOUNT
218   policy_handle = FFI::MemoryPointer.new(:pointer)
219 
220   result = LsaOpenPolicy(nil, LSA_OBJECT_ATTRIBUTES.new, access, policy_handle)
221   check_lsa_nt_status_and_raise_failures(result, "LsaOpenPolicy")
222 
223   begin
224     yield policy_handle
225   ensure
226     result = LsaClose(policy_handle.read_pointer)
227     check_lsa_nt_status_and_raise_failures(result, "LsaClose")
228   end
229 end
new_lsa_unicode_strings_pointer(strings) click to toggle source
    # File lib/puppet/util/windows/user.rb
232 def self.new_lsa_unicode_strings_pointer(strings)
233   lsa_unicode_strings_pointer = FFI::MemoryPointer.new(LSA_UNICODE_STRING, strings.size)
234 
235   strings.each_with_index do |string, index|
236     lsa_string = LSA_UNICODE_STRING.new(lsa_unicode_strings_pointer + index * LSA_UNICODE_STRING.size)
237     lsa_string[:Buffer] = FFI::MemoryPointer.from_string(wide_string(string))
238     lsa_string[:Length] = string.length * 2
239     lsa_string[:MaximumLength] = lsa_string[:Length] + 2
240   end
241 
242   lsa_unicode_strings_pointer
243 end

Private Instance Methods

admin?() click to toggle source
   # File lib/puppet/util/windows/user.rb
10 def admin?
11   return false unless check_token_membership
12 
13   # if Vista or later, check for unrestricted process token
14   elevated_supported = Puppet::Util::Windows::Process.supports_elevated_security?
15   return elevated_supported ? Puppet::Util::Windows::Process.elevated_security? : true
16 end
check_token_membership() click to toggle source
   # File lib/puppet/util/windows/user.rb
47 def check_token_membership
48   is_admin = false
49   FFI::MemoryPointer.new(:byte, SECURITY_MAX_SID_SIZE) do |sid_pointer|
50     FFI::MemoryPointer.new(:dword, 1) do |size_pointer|
51       size_pointer.write_uint32(SECURITY_MAX_SID_SIZE)
52 
53       if CreateWellKnownSid(:WinBuiltinAdministratorsSid, FFI::Pointer::NULL, sid_pointer, size_pointer) == FFI::WIN32_FALSE
54         raise Puppet::Util::Windows::Error.new(_("Failed to create administrators SID"))
55       end
56     end
57 
58     if IsValidSid(sid_pointer) == FFI::WIN32_FALSE
59       raise Puppet::Util::Windows::Error.new(_("Invalid SID"))
60     end
61 
62     FFI::MemoryPointer.new(:win32_bool, 1) do |ismember_pointer|
63       if CheckTokenMembership(FFI::Pointer::NULL_HANDLE, sid_pointer, ismember_pointer) == FFI::WIN32_FALSE
64         raise Puppet::Util::Windows::Error.new(_("Failed to check membership"))
65       end
66 
67       # Is administrators SID enabled in calling thread's access token?
68       is_admin = ismember_pointer.read_win32_bool
69     end
70   end
71 
72   is_admin
73 end
default_system_account?(name) click to toggle source

Check if a given user is one of the default system accounts These accounts do not have a password and all checks done through logon attempt will fail docs.microsoft.com/en-us/windows/security/identity-protection/access-control/local-accounts#default-local-system-accounts

   # File lib/puppet/util/windows/user.rb
30 def default_system_account?(name)
31   user_sid = Puppet::Util::Windows::SID.name_to_sid(name)
32   [Puppet::Util::Windows::SID::LocalSystem, Puppet::Util::Windows::SID::NtLocal, Puppet::Util::Windows::SID::NtNetwork].include?(user_sid)
33 end
get_rights(name) click to toggle source
    # File lib/puppet/util/windows/user.rb
148 def get_rights(name)
149   user_info = Puppet::Util::Windows::SID.name_to_principal(name.sub(/^\.\\/, "#{Puppet::Util::Windows::ADSI.computer_name}\\"))
150   return "" unless user_info
151 
152   rights = []
153   rights_pointer = FFI::MemoryPointer.new(:pointer)
154   number_of_rights = FFI::MemoryPointer.new(:ulong)
155   sid_pointer = FFI::MemoryPointer.new(:byte, user_info.sid_bytes.length).write_array_of_uchar(user_info.sid_bytes)
156 
157   new_lsa_policy_handle do |policy_handle|
158     result = LsaEnumerateAccountRights(policy_handle.read_pointer, sid_pointer, rights_pointer, number_of_rights)
159     check_lsa_nt_status_and_raise_failures(result, "LsaEnumerateAccountRights")
160   end
161 
162   number_of_rights.read_ulong.times do |index|
163     right = LSA_UNICODE_STRING.new(rights_pointer.read_pointer + index * LSA_UNICODE_STRING.size)
164     rights << right[:Buffer].read_arbitrary_wide_string_up_to
165   end
166 
167   result = LsaFreeMemory(rights_pointer.read_pointer)
168   check_lsa_nt_status_and_raise_failures(result, "LsaFreeMemory")
169 
170   rights.join(",")
171 end
load_profile(user, password) click to toggle source
    # File lib/puppet/util/windows/user.rb
125 def load_profile(user, password)
126   logon_user(user, password) do |token|
127     FFI::MemoryPointer.from_string_to_wide_string(user) do |lpUserName|
128       pi = PROFILEINFO.new
129       pi[:dwSize] = PROFILEINFO.size
130       pi[:dwFlags] = 1 # PI_NOUI - prevents display of profile error msgs
131       pi[:lpUserName] = lpUserName
132 
133       # Load the profile. Since it doesn't exist, it will be created
134       if LoadUserProfileW(token, pi.pointer) == FFI::WIN32_FALSE
135         raise Puppet::Util::Windows::Error.new(_("Failed to load user profile %{user}") % { user: user.inspect })
136       end
137 
138       Puppet.debug("Loaded profile for #{user}")
139 
140       if UnloadUserProfile(token, pi[:hProfile]) == FFI::WIN32_FALSE
141         raise Puppet::Util::Windows::Error.new(_("Failed to unload user profile %{user}") % { user: user.inspect })
142       end
143     end
144   end
145 end
localsystem?(name) click to toggle source

The name of the account in all locales is `LocalSystem`. `.LocalSystem` or `ComputerNameLocalSystem' can also be used. This account is not recognized by the security subsystem, so you cannot specify its name in a call to the `LookupAccountName` function. docs.microsoft.com/en-us/windows/win32/services/localsystem-account

   # File lib/puppet/util/windows/user.rb
22 def localsystem?(name)
23   ["LocalSystem", ".\\LocalSystem", "#{Puppet::Util::Windows::ADSI.computer_name}\\LocalSystem"].any?{ |s| s.casecmp(name) == 0 }
24 end
logon_user(name, password, domain = '.') { |token = read_handle| ... } click to toggle source
    # File lib/puppet/util/windows/user.rb
 93 def logon_user(name, password, domain = '.', &block)
 94   fLOGON32_PROVIDER_DEFAULT = 0
 95   fLOGON32_LOGON_INTERACTIVE = 2
 96   fLOGON32_LOGON_NETWORK = 3
 97 
 98   token = nil
 99   begin
100     FFI::MemoryPointer.new(:handle, 1) do |token_pointer|
101       #try logon using network else try logon using interactive mode
102       if logon_user_by_logon_type(name, domain, password, fLOGON32_LOGON_NETWORK, fLOGON32_PROVIDER_DEFAULT, token_pointer) == FFI::WIN32_FALSE
103         if logon_user_by_logon_type(name, domain, password, fLOGON32_LOGON_INTERACTIVE, fLOGON32_PROVIDER_DEFAULT, token_pointer) == FFI::WIN32_FALSE
104           raise Puppet::Util::Windows::Error.new(_("Failed to logon user %{name}") % {name: name.inspect})
105         end
106       end
107 
108       yield token = token_pointer.read_handle
109     end
110   ensure
111     FFI::WIN32.CloseHandle(token) if token
112   end
113 
114   # token has been closed by this point
115   true
116 end
password_is?(name, password, domain = '.') click to toggle source
   # File lib/puppet/util/windows/user.rb
76 def password_is?(name, password, domain = '.')
77   begin
78     logon_user(name, password, domain) { |token| }
79   rescue Puppet::Util::Windows::Error => detail
80 
81     authenticated_error_codes = Set[
82       ERROR_ACCOUNT_RESTRICTION,
83       ERROR_INVALID_LOGON_HOURS,
84       ERROR_INVALID_WORKSTATION,
85       ERROR_ACCOUNT_DISABLED,
86     ]
87 
88     return authenticated_error_codes.include?(detail.code)
89   end
90 end
remove_rights(name, rights) click to toggle source
    # File lib/puppet/util/windows/user.rb
186 def remove_rights(name, rights)
187   rights_pointer = new_lsa_unicode_strings_pointer(rights)
188   user_info = Puppet::Util::Windows::SID.name_to_principal(name.sub(/^\.\\/, "#{Puppet::Util::Windows::ADSI.computer_name}\\"))
189   sid_pointer = FFI::MemoryPointer.new(:byte, user_info.sid_bytes.length).write_array_of_uchar(user_info.sid_bytes)
190 
191   new_lsa_policy_handle do |policy_handle|
192     result = LsaRemoveAccountRights(policy_handle.read_pointer, sid_pointer, false, rights_pointer, rights.size)
193     check_lsa_nt_status_and_raise_failures(result, "LsaRemoveAccountRights")
194   end
195 end
set_rights(name, rights) click to toggle source
    # File lib/puppet/util/windows/user.rb
174 def set_rights(name, rights)
175   rights_pointer = new_lsa_unicode_strings_pointer(rights)
176   user_info = Puppet::Util::Windows::SID.name_to_principal(name.sub(/^\.\\/, "#{Puppet::Util::Windows::ADSI.computer_name}\\"))
177   sid_pointer = FFI::MemoryPointer.new(:byte, user_info.sid_bytes.length).write_array_of_uchar(user_info.sid_bytes)
178 
179   new_lsa_policy_handle do |policy_handle|
180     result = LsaAddAccountRights(policy_handle.read_pointer, sid_pointer, rights_pointer, rights.size)
181     check_lsa_nt_status_and_raise_failures(result, "LsaAddAccountRights")
182   end
183 end