diff --git a/lib/puppet/util/windows/error.rb b/lib/puppet/util/windows/error.rb index 749a64160..2893b6a42 100644 --- a/lib/puppet/util/windows/error.rb +++ b/lib/puppet/util/windows/error.rb @@ -1,16 +1,16 @@ require 'puppet/util/windows' # represents an error resulting from a Win32 error code class Puppet::Util::Windows::Error < Puppet::Error require 'windows/error' - include Windows::Error + include ::Windows::Error attr_reader :code def initialize(message, code = GetLastError.call) super(message + ": #{get_last_error(code)}") @code = code end end diff --git a/lib/puppet/util/windows/process.rb b/lib/puppet/util/windows/process.rb index abeb59244..f2220ebc9 100644 --- a/lib/puppet/util/windows/process.rb +++ b/lib/puppet/util/windows/process.rb @@ -1,33 +1,33 @@ require 'puppet/util/windows' module Puppet::Util::Windows::Process - extend Windows::Process - extend Windows::Handle - extend Windows::Synchronize + extend ::Windows::Process + extend ::Windows::Handle + extend ::Windows::Synchronize def execute(command, arguments, stdin, stdout, stderr) Process.create( :command_line => command, :startup_info => {:stdin => stdin, :stdout => stdout, :stderr => stderr}, :close_handles => false ) end module_function :execute def wait_process(handle) while WaitForSingleObject(handle, 0) == Windows::Synchronize::WAIT_TIMEOUT sleep(1) end exit_status = [0].pack('L') unless GetExitCodeProcess(handle, exit_status) raise Puppet::Util::Windows::Error.new("Failed to get child process exit code") end exit_status = exit_status.unpack('L').first # $CHILD_STATUS is not set when calling win32/process Process.create # and since it's read-only, we can't set it. But we can execute a # a shell that simply returns the desired exit status, which has the # desired effect. %x{#{ENV['COMSPEC']} /c exit #{exit_status}} exit_status end module_function :wait_process end diff --git a/lib/puppet/util/windows/security.rb b/lib/puppet/util/windows/security.rb index 9b7e10706..d00ad30b3 100644 --- a/lib/puppet/util/windows/security.rb +++ b/lib/puppet/util/windows/security.rb @@ -1,659 +1,659 @@ # This class maps POSIX owner, group, and modes to the Windows # security model, and back. # # The primary goal of this mapping is to ensure that owner, group, and # modes can be round-tripped in a consistent and deterministic # way. Otherwise, Puppet might think file resources are out-of-sync # every time it runs. A secondary goal is to provide equivalent # permissions for common use-cases. For example, setting the owner to # "Administrators", group to "Users", and mode to 750 (which also # denies access to everyone else. # # There are some well-known problems mapping windows and POSIX # permissions due to differences between the two security # models. Search for "POSIX permission mapping leak". In POSIX, access # to a file is determined solely based on the most specific class # (user, group, other). So a mode of 460 would deny write access to # the owner even if they are a member of the group. But in Windows, # the entire access control list is walked until the user is # explicitly denied or allowed (denied take precedence, and if neither # occurs they are denied). As a result, a user could be allowed access # based on their group membership. To solve this problem, other people # have used deny access control entries to more closely model POSIX, # but this introduces a lot of complexity. # # In general, this implementation only supports "typical" permissions, # where group permissions are a subset of user, and other permissions # are a subset of group, e.g. 754, but not 467. However, there are # some Windows quirks to be aware of. # # * The owner can be either a user or group SID, and most system files # are owned by the Administrators group. # * The group can be either a user or group SID. # * Unexpected results can occur if the owner and group are the # same, but the user and group classes are different, e.g. 750. In # this case, it is not possible to allow write access to the owner, # but not the group. As a result, the actual permissions set on the # file would be 770. # * In general, only privileged users can set the owner, group, or # change the mode for files they do not own. In 2003, the user must # be a member of the Administrators group. In Vista/2008, the user # must be running with elevated privileges. # * A file/dir can be deleted by anyone with the DELETE access right # OR by anyone that has the FILE_DELETE_CHILD access right for the # parent. See http://support.microsoft.com/kb/238018. But on Unix, # the user must have write access to the file/dir AND execute access # to all of the parent path components. # * Many access control entries are inherited from parent directories, # and it is common for file/dirs to have more than 3 entries, # e.g. Users, Power Users, Administrators, SYSTEM, etc, which cannot # be mapped into the 3 class POSIX model. The get_mode method will # set the S_IEXTRA bit flag indicating that an access control entry # was found whose SID is neither the owner, group, or other. This # enables Puppet to detect when file/dirs are out-of-sync, # especially those that Puppet did not create, but is attempting # to manage. # * On Unix, the owner and group can be modified without changing the # mode. But on Windows, an access control entry specifies which SID # it applies to. As a result, the set_owner and set_group methods # automatically rebuild the access control list based on the new # (and different) owner or group. require 'puppet/util/windows' require 'pathname' require 'win32/security' require 'windows/file' require 'windows/handle' require 'windows/security' require 'windows/process' require 'windows/memory' require 'windows/volume' module Puppet::Util::Windows::Security - include Windows::File - include Windows::Handle - include Windows::Security - include Windows::Process - include Windows::Memory - include Windows::MSVCRT::Buffer - include Windows::Volume + include ::Windows::File + include ::Windows::Handle + include ::Windows::Security + include ::Windows::Process + include ::Windows::Memory + include ::Windows::MSVCRT::Buffer + include ::Windows::Volume extend Puppet::Util::Windows::Security # file modes S_IRUSR = 0000400 S_IRGRP = 0000040 S_IROTH = 0000004 S_IWUSR = 0000200 S_IWGRP = 0000020 S_IWOTH = 0000002 S_IXUSR = 0000100 S_IXGRP = 0000010 S_IXOTH = 0000001 S_IRWXU = 0000700 S_IRWXG = 0000070 S_IRWXO = 0000007 S_ISVTX = 0001000 S_IEXTRA = 02000000 # represents an extra ace # constants that are missing from Windows::Security PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000 UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000 NO_INHERITANCE = 0x0 # Set the owner of the object referenced by +path+ to the specified # +owner_sid+. The owner sid should be of the form "S-1-5-32-544" # and can either be a user or group. Only a user with the # SE_RESTORE_NAME privilege in their process token can overwrite the # object's owner to something other than the current user. def set_owner(owner_sid, path) old_sid = get_owner(path) change_sid(old_sid, owner_sid, OWNER_SECURITY_INFORMATION, path) end # Get the owner of the object referenced by +path+. The returned # value is a SID string, e.g. "S-1-5-32-544". Any user with read # access to an object can get the owner. Only a user with the # SE_BACKUP_NAME privilege in their process token can get the owner # for objects they do not have read access to. def get_owner(path) return unless supports_acl?(path) get_sid(OWNER_SECURITY_INFORMATION, path) end # Set the owner of the object referenced by +path+ to the specified # +group_sid+. The group sid should be of the form "S-1-5-32-544" # and can either be a user or group. Any user with WRITE_OWNER # access to the object can change the group (regardless of whether # the current user belongs to that group or not). def set_group(group_sid, path) old_sid = get_group(path) change_sid(old_sid, group_sid, GROUP_SECURITY_INFORMATION, path) end # Get the group of the object referenced by +path+. The returned # value is a SID string, e.g. "S-1-5-32-544". Any user with read # access to an object can get the group. Only a user with the # SE_BACKUP_NAME privilege in their process token can get the group # for objects they do not have read access to. def get_group(path) return unless supports_acl?(path) get_sid(GROUP_SECURITY_INFORMATION, path) end def supports_acl?(path) flags = 0.chr * 4 root = Pathname.new(path).enum_for(:ascend).to_a.last.to_s # 'A trailing backslash is required' root = "#{root}\\" unless root =~ /[\/\\]$/ unless GetVolumeInformation(root, nil, 0, nil, nil, flags, nil, 0) raise Puppet::Util::Windows::Error.new("Failed to get volume information") end (flags.unpack('L')[0] & Windows::File::FILE_PERSISTENT_ACLS) != 0 end def change_sid(old_sid, new_sid, info, path) if old_sid != new_sid mode = get_mode(path) string_to_sid_ptr(new_sid) do |psid| with_privilege(SE_RESTORE_NAME) do open_file(path, WRITE_OWNER) do |handle| set_security_info(handle, info, psid) end end end # rebuild dacl now that sid has changed set_mode(mode, path) end end def get_sid(info, path) with_privilege(SE_BACKUP_NAME) do open_file(path, READ_CONTROL) do |handle| get_security_info(handle, info) end end end def get_attributes(path) attributes = GetFileAttributes(path) raise Puppet::Util::Windows::Error.new("Failed to get file attributes") if attributes == INVALID_FILE_ATTRIBUTES attributes end def add_attributes(path, flags) oldattrs = get_attributes(path) if (oldattrs | flags) != oldattrs set_attributes(path, oldattrs | flags) end end def remove_attributes(path, flags) oldattrs = get_attributes(path) if (oldattrs & ~flags) != oldattrs set_attributes(path, oldattrs & ~flags) end end def set_attributes(path, flags) raise Puppet::Util::Windows::Error.new("Failed to set file attributes") unless SetFileAttributes(path, flags) end MASK_TO_MODE = { FILE_GENERIC_READ => S_IROTH, FILE_GENERIC_WRITE => S_IWOTH, (FILE_GENERIC_EXECUTE & ~FILE_READ_ATTRIBUTES) => S_IXOTH } # Get the mode of the object referenced by +path+. The returned # integer value represents the POSIX-style read, write, and execute # modes for the user, group, and other classes, e.g. 0640. Any user # with read access to an object can get the mode. Only a user with # the SE_BACKUP_NAME privilege in their process token can get the # mode for objects they do not have read access to. def get_mode(path) return unless supports_acl?(path) owner_sid = get_owner(path) group_sid = get_group(path) well_known_world_sid = Win32::Security::SID::Everyone well_known_nobody_sid = Win32::Security::SID::Nobody with_privilege(SE_BACKUP_NAME) do open_file(path, READ_CONTROL) do |handle| mode = 0 get_dacl(handle).each do |ace| case ace[:sid] when owner_sid MASK_TO_MODE.each_pair do |k,v| if (ace[:mask] & k) == k mode |= (v << 6) end end when group_sid MASK_TO_MODE.each_pair do |k,v| if (ace[:mask] & k) == k mode |= (v << 3) end end when well_known_world_sid MASK_TO_MODE.each_pair do |k,v| if (ace[:mask] & k) == k mode |= (v << 6) | (v << 3) | v end end if File.directory?(path) and (ace[:mask] & (FILE_WRITE_DATA | FILE_EXECUTE | FILE_DELETE_CHILD)) == (FILE_WRITE_DATA | FILE_EXECUTE) mode |= S_ISVTX; end when well_known_nobody_sid if (ace[:mask] & FILE_APPEND_DATA).nonzero? mode |= S_ISVTX end else #puts "Warning, unable to map SID into POSIX mode: #{ace[:sid]}" mode |= S_IEXTRA end # if owner and group the same, then user and group modes are the OR of both if owner_sid == group_sid mode |= ((mode & S_IRWXG) << 3) | ((mode & S_IRWXU) >> 3) #puts "owner: #{group_sid}, 0x#{ace[:mask].to_s(16)}, #{mode.to_s(8)}" end end #puts "get_mode: #{mode.to_s(8)}" mode end end end MODE_TO_MASK = { S_IROTH => FILE_GENERIC_READ, S_IWOTH => FILE_GENERIC_WRITE, S_IXOTH => (FILE_GENERIC_EXECUTE & ~FILE_READ_ATTRIBUTES), } # Set the mode of the object referenced by +path+ to the specified # +mode+. The mode should be specified as POSIX-stye read, write, # and execute modes for the user, group, and other classes, # e.g. 0640. The sticky bit, S_ISVTX, is supported, but is only # meaningful for directories. If set, group and others are not # allowed to delete child objects for which they are not the owner. # By default, the DACL is set to protected, meaning it does not # inherit access control entries from parent objects. This can be # changed by setting +protected+ to false. The owner of the object # (with READ_CONTROL and WRITE_DACL access) can always change the # mode. Only a user with the SE_BACKUP_NAME and SE_RESTORE_NAME # privileges in their process token can change the mode for objects # that they do not have read and write access to. def set_mode(mode, path, protected = true) owner_sid = get_owner(path) group_sid = get_group(path) well_known_world_sid = Win32::Security::SID::Everyone well_known_nobody_sid = Win32::Security::SID::Nobody owner_allow = STANDARD_RIGHTS_ALL | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES group_allow = STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | SYNCHRONIZE other_allow = STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | SYNCHRONIZE nobody_allow = 0 MODE_TO_MASK.each do |k,v| if ((mode >> 6) & k) == k owner_allow |= v end if ((mode >> 3) & k) == k group_allow |= v end if (mode & k) == k other_allow |= v end end if (mode & S_ISVTX).nonzero? nobody_allow |= FILE_APPEND_DATA; end isdir = File.directory?(path) if isdir if (mode & (S_IWUSR | S_IXUSR)) == (S_IWUSR | S_IXUSR) owner_allow |= FILE_DELETE_CHILD end if (mode & (S_IWGRP | S_IXGRP)) == (S_IWGRP | S_IXGRP) and (mode & S_ISVTX) == 0 group_allow |= FILE_DELETE_CHILD end if (mode & (S_IWOTH | S_IXOTH)) == (S_IWOTH | S_IXOTH) and (mode & S_ISVTX) == 0 other_allow |= FILE_DELETE_CHILD end end # if owner and group the same, then map group permissions to the one owner ACE isownergroup = owner_sid == group_sid if isownergroup owner_allow |= group_allow end # if any ACE allows write, then clear readonly bit, but do this before we overwrite # the DACl and lose our ability to set the attribute if ((owner_allow | group_allow | other_allow ) & FILE_WRITE_DATA) == FILE_WRITE_DATA remove_attributes(path, FILE_ATTRIBUTE_READONLY) end set_acl(path, protected) do |acl| #puts "ace: owner #{owner_sid}, mask 0x#{owner_allow.to_s(16)}" add_access_allowed_ace(acl, owner_allow, owner_sid) unless isownergroup #puts "ace: group #{group_sid}, mask 0x#{group_allow.to_s(16)}" add_access_allowed_ace(acl, group_allow, group_sid) end #puts "ace: other #{well_known_world_sid}, mask 0x#{other_allow.to_s(16)}" add_access_allowed_ace(acl, other_allow, well_known_world_sid) #puts "ace: nobody #{well_known_nobody_sid}, mask 0x#{nobody_allow.to_s(16)}" add_access_allowed_ace(acl, nobody_allow, well_known_nobody_sid) # add inherit-only aces for child dirs and files that are created within the dir if isdir inherit = INHERIT_ONLY_ACE | CONTAINER_INHERIT_ACE add_access_allowed_ace(acl, owner_allow, Win32::Security::SID::CreatorOwner, inherit) add_access_allowed_ace(acl, group_allow, Win32::Security::SID::CreatorGroup, inherit) inherit = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE add_access_allowed_ace(acl, owner_allow & ~FILE_EXECUTE, Win32::Security::SID::CreatorOwner, inherit) add_access_allowed_ace(acl, group_allow & ~FILE_EXECUTE, Win32::Security::SID::CreatorGroup, inherit) end end nil end # setting DACL requires both READ_CONTROL and WRITE_DACL access rights, # and their respective privileges, SE_BACKUP_NAME and SE_RESTORE_NAME. def set_acl(path, protected = true) with_privilege(SE_BACKUP_NAME) do with_privilege(SE_RESTORE_NAME) do open_file(path, READ_CONTROL | WRITE_DAC) do |handle| acl = 0.chr * 1024 # This can be increased later as needed unless InitializeAcl(acl, acl.size, ACL_REVISION) raise Puppet::Util::Windows::Error.new("Failed to initialize ACL") end raise Puppet::Util::Windows::Error.new("Invalid DACL") unless IsValidAcl(acl) yield acl # protected means the object does not inherit aces from its parent info = DACL_SECURITY_INFORMATION info |= protected ? PROTECTED_DACL_SECURITY_INFORMATION : UNPROTECTED_DACL_SECURITY_INFORMATION # set the DACL set_security_info(handle, info, acl) end end end end def add_access_allowed_ace(acl, mask, sid, inherit = NO_INHERITANCE) string_to_sid_ptr(sid) do |sid_ptr| raise Puppet::Util::Windows::Error.new("Invalid SID") unless IsValidSid(sid_ptr) unless AddAccessAllowedAceEx(acl, ACL_REVISION, inherit, mask, sid_ptr) raise Puppet::Util::Windows::Error.new("Failed to add access control entry") end end end def add_access_denied_ace(acl, mask, sid) string_to_sid_ptr(sid) do |sid_ptr| raise Puppet::Util::Windows::Error.new("Invalid SID") unless IsValidSid(sid_ptr) unless AddAccessDeniedAce(acl, ACL_REVISION, mask, sid_ptr) raise Puppet::Util::Windows::Error.new("Failed to add access control entry") end end end def get_dacl(handle) get_dacl_ptr(handle) do |dacl_ptr| # REMIND: need to handle NULL DACL raise Puppet::Util::Windows::Error.new("Invalid DACL") unless IsValidAcl(dacl_ptr) # ACL structure, size and count are the important parts. The # size includes both the ACL structure and all the ACEs. # # BYTE AclRevision # BYTE Padding1 # WORD AclSize # WORD AceCount # WORD Padding2 acl_buf = 0.chr * 8 memcpy(acl_buf, dacl_ptr, acl_buf.size) ace_count = acl_buf.unpack('CCSSS')[3] dacl = [] # deny all return dacl if ace_count == 0 0.upto(ace_count - 1) do |i| ace_ptr = [0].pack('L') next unless GetAce(dacl_ptr, i, ace_ptr) # ACE structures vary depending on the type. All structures # begin with an ACE header, which specifies the type, flags # and size of what follows. We are only concerned with # ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACEs, which have the # same structure: # # BYTE C AceType # BYTE C AceFlags # WORD S AceSize # DWORD L ACCESS_MASK # DWORD L Sid # .. ... # DWORD L Sid ace_buf = 0.chr * 8 memcpy(ace_buf, ace_ptr.unpack('L')[0], ace_buf.size) ace_type, ace_flags, size, mask = ace_buf.unpack('CCSL') # skip aces that only serve to propagate inheritance next if (ace_flags & INHERIT_ONLY_ACE).nonzero? case ace_type when ACCESS_ALLOWED_ACE_TYPE sid_ptr = ace_ptr.unpack('L')[0] + 8 # address of ace_ptr->SidStart raise Puppet::Util::Windows::Error.new("Failed to read DACL, invalid SID") unless IsValidSid(sid_ptr) sid = sid_ptr_to_string(sid_ptr) dacl << {:sid => sid, :type => ace_type, :mask => mask} else Puppet.warning "Unsupported access control entry type: 0x#{ace_type.to_s(16)}" end end dacl end end def get_dacl_ptr(handle) dacl = [0].pack('L') sd = [0].pack('L') rv = GetSecurityInfo( handle, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nil, nil, dacl, #dacl nil, #sacl sd) #sec desc raise Puppet::Util::Windows::Error.new("Failed to get DACL") unless rv == ERROR_SUCCESS begin yield dacl.unpack('L')[0] ensure LocalFree(sd.unpack('L')[0]) end end # Set the security info on the specified handle. def set_security_info(handle, info, ptr) rv = SetSecurityInfo( handle, SE_FILE_OBJECT, info, (info & OWNER_SECURITY_INFORMATION) == OWNER_SECURITY_INFORMATION ? ptr : nil, (info & GROUP_SECURITY_INFORMATION) == GROUP_SECURITY_INFORMATION ? ptr : nil, (info & DACL_SECURITY_INFORMATION) == DACL_SECURITY_INFORMATION ? ptr : nil, nil) raise Puppet::Util::Windows::Error.new("Failed to set security information") unless rv == ERROR_SUCCESS end # Get the SID string, e.g. "S-1-5-32-544", for the specified handle # and type of information (owner, group). def get_security_info(handle, info) sid = [0].pack('L') sd = [0].pack('L') rv = GetSecurityInfo( handle, SE_FILE_OBJECT, info, # security info info == OWNER_SECURITY_INFORMATION ? sid : nil, info == GROUP_SECURITY_INFORMATION ? sid : nil, nil, #dacl nil, #sacl sd) #sec desc raise Puppet::Util::Windows::Error.new("Failed to get security information") unless rv == ERROR_SUCCESS begin return sid_ptr_to_string(sid.unpack('L')[0]) ensure LocalFree(sd.unpack('L')[0]) end end # Convert a SID pointer to a string, e.g. "S-1-5-32-544". def sid_ptr_to_string(psid) sid_buf = 0.chr * 256 str_ptr = 0.chr * 4 raise Puppet::Util::Windows::Error.new("Invalid SID") unless IsValidSid(psid) raise Puppet::Util::Windows::Error.new("Failed to convert binary SID") unless ConvertSidToStringSid(psid, str_ptr) begin strncpy(sid_buf, str_ptr.unpack('L')[0], sid_buf.size - 1) sid_buf[sid_buf.size - 1] = 0.chr return sid_buf.strip ensure LocalFree(str_ptr.unpack('L')[0]) end end # 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. def string_to_sid_ptr(string) sid_buf = 0.chr * 80 string_addr = [string].pack('p*').unpack('L')[0] raise Puppet::Util::Windows::Error.new("Failed to convert string SID: #{string}") unless ConvertStringSidToSid(string_addr, sid_buf) sid_ptr = sid_buf.unpack('L')[0] begin if block_given? yield sid_ptr else true end ensure LocalFree(sid_ptr) end end # Open an existing file with the specified access mode, and execute a # block with the opened file HANDLE. def open_file(path, access) handle = CreateFile( path, access, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, # security_attributes OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0) # template raise Puppet::Util::Windows::Error.new("Failed to open '#{path}'") if handle == INVALID_HANDLE_VALUE begin yield handle ensure CloseHandle(handle) end end # Execute a block with the specified privilege enabled def with_privilege(privilege) set_privilege(privilege, true) yield ensure set_privilege(privilege, false) end # Enable or disable a privilege. Note this doesn't add any privileges the # user doesn't already has, it just enables privileges that are disabled. def set_privilege(privilege, enable) return unless Puppet.features.root? with_process_token(TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY) do |token| tmpLuid = 0.chr * 8 # Get the LUID for specified privilege. unless LookupPrivilegeValue("", privilege, tmpLuid) raise Puppet::Util::Windows::Error.new("Failed to lookup privilege") end # DWORD + [LUID + DWORD] tkp = [1].pack('L') + tmpLuid + [enable ? SE_PRIVILEGE_ENABLED : 0].pack('L') unless AdjustTokenPrivileges(token, 0, tkp, tkp.length , nil, nil) raise Puppet::Util::Windows::Error.new("Failed to adjust process privileges") end end end # Execute a block with the current process token def with_process_token(access) token = 0.chr * 4 unless OpenProcessToken(GetCurrentProcess(), access, token) raise Puppet::Util::Windows::Error.new("Failed to open process token") end begin token = token.unpack('L')[0] yield token ensure CloseHandle(token) end end end diff --git a/lib/puppet/util/windows/user.rb b/lib/puppet/util/windows/user.rb index 4a0c438bf..dc9f99455 100644 --- a/lib/puppet/util/windows/user.rb +++ b/lib/puppet/util/windows/user.rb @@ -1,106 +1,106 @@ require 'puppet/util/windows' require 'win32/security' require 'facter' module Puppet::Util::Windows::User - include Windows::Security - extend Windows::Security + include ::Windows::Security + extend ::Windows::Security def admin? majversion = Facter.value(:kernelmajversion) return false unless majversion # if Vista or later, check for unrestricted process token return Win32::Security.elevated_security? unless majversion.to_f < 6.0 # otherwise 2003 or less check_token_membership end module_function :admin? def check_token_membership sid = 0.chr * 80 size = [80].pack('L') member = 0.chr * 4 unless CreateWellKnownSid(WinBuiltinAdministratorsSid, nil, sid, size) raise Puppet::Util::Windows::Error.new("Failed to create administrators SID") end unless IsValidSid(sid) raise Puppet::Util::Windows::Error.new("Invalid SID") end unless CheckTokenMembership(nil, sid, member) raise Puppet::Util::Windows::Error.new("Failed to check membership") end # Is administrators SID enabled in calling thread's access token? member.unpack('L')[0] == 1 end module_function :check_token_membership def password_is?(name, password) logon_user(name, password) true rescue Puppet::Util::Windows::Error => e false end module_function :password_is? def logon_user(name, password, &block) fLOGON32_LOGON_NETWORK = 3 fLOGON32_PROVIDER_DEFAULT = 0 logon_user = Win32API.new("advapi32", "LogonUser", ['P', 'P', 'P', 'L', 'L', 'P'], 'L') close_handle = Win32API.new("kernel32", "CloseHandle", ['P'], 'V') token = 0.chr * 4 if logon_user.call(name, ".", password, fLOGON32_LOGON_NETWORK, fLOGON32_PROVIDER_DEFAULT, token) == 0 raise Puppet::Util::Windows::Error.new("Failed to logon user #{name.inspect}") end begin yield token.unpack('L')[0] if block_given? ensure close_handle.call(token.unpack('L')[0]) end end module_function :logon_user def load_profile(user, password) logon_user(user, password) do |token| # Set up the PROFILEINFO structure that will be used to load the # new user's profile # typedef struct _PROFILEINFO { # DWORD dwSize; # DWORD dwFlags; # LPTSTR lpUserName; # LPTSTR lpProfilePath; # LPTSTR lpDefaultPath; # LPTSTR lpServerName; # LPTSTR lpPolicyPath; # HANDLE hProfile; # } PROFILEINFO, *LPPROFILEINFO; fPI_NOUI = 1 profile = 0.chr * 4 pi = [4 * 8, fPI_NOUI, user, nil, nil, nil, nil, profile].pack('LLPPPPPP') load_user_profile = Win32API.new('userenv', 'LoadUserProfile', ['L', 'P'], 'L') unload_user_profile = Win32API.new('userenv', 'UnloadUserProfile', ['L', 'P'], 'L') # Load the profile. Since it doesn't exist, it will be created if load_user_profile.call(token, pi) == 0 raise Puppet::Util::Windows::Error.new("Failed to load user profile #{user.inspect}") end Puppet.debug("Loaded profile for #{user}") if unload_user_profile.call(token, pi.unpack('LLLLLLLL').last) == 0 raise Puppet::Util::Windows::Error.new("Failed to unload user profile #{user.inspect}") end end end module_function :load_profile end