diff --git a/lib/puppet/util/windows/api_types.rb b/lib/puppet/util/windows/api_types.rb index efb9a4caf..f7325a588 100644 --- a/lib/puppet/util/windows/api_types.rb +++ b/lib/puppet/util/windows/api_types.rb @@ -1,57 +1,86 @@ require 'ffi' +require 'puppet/util/windows/string' module Puppet::Util::Windows::APITypes module ::FFI::Library # Wrapper method for attach_function + private def attach_function_private(*args) attach_function(*args) private args[0] end end + class ::FFI::MemoryPointer + def self.from_string_to_wide_string(str) + str = Puppet::Util::Windows::String.wide_string(str) + ptr = FFI::MemoryPointer.new(:byte, str.bytesize) + # uchar here is synonymous with byte + ptr.put_array_of_uchar(0, str.bytes.to_a) + + ptr + end + + def read_bool + # BOOL is always a 32-bit integer in Win32 + # some Win32 APIs return 1 for true, while others are non-0 + read_int32 != 0 + end + + def read_handle + type_size == 4 ? read_uint32 : read_uint64 + end + + def read_wide_string(char_length) + # char_length is number of wide chars (typically excluding NULLs), *not* bytes + str = get_bytes(0, char_length * 2).force_encoding('UTF-16LE') + str.encode(Encoding.default_external) + end + end + # FFI Types # https://github.com/ffi/ffi/wiki/Types # Windows - Common Data Types # http://msdn.microsoft.com/en-us/library/cc230309.aspx # Windows Data Types # http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx FFI.typedef :uint16, :word FFI.typedef :uint32, :dword # uintptr_t is defined in an FFI conf as platform specific, either # ulong_long on x64 or just ulong on x86 FFI.typedef :uintptr_t, :handle # buffer_inout is similar to pointer (platform specific), but optimized for buffers FFI.typedef :buffer_inout, :lpwstr # buffer_in is similar to pointer (platform specific), but optimized for CONST read only buffers FFI.typedef :buffer_in, :lpcwstr # string is also similar to pointer, but should be used for const char * # NOTE that this is not wide, useful only for A suffixed functions FFI.typedef :string, :lpcstr # pointer in FFI is platform specific # NOTE: for API calls with reserved lpvoid parameters, pass a FFI::Pointer::NULL FFI.typedef :pointer, :lpvoid FFI.typedef :pointer, :lpword FFI.typedef :pointer, :lpdword FFI.typedef :pointer, :pdword FFI.typedef :pointer, :phandle FFI.typedef :pointer, :ulong_ptr + FFI.typedef :pointer, :pbool # any time LONG / ULONG is in a win32 API definition DO NOT USE platform specific width # which is what FFI uses by default # instead create new aliases for these very special cases # NOTE: not a good idea to redefine FFI :ulong since other typedefs may rely on it FFI.typedef :uint32, :win32_ulong FFI.typedef :int32, :win32_long # NOTE: FFI already defines ushort as a 16-bit unsigned like this: # FFI.typedef :uint16, :ushort # 8 bits per byte FFI.typedef :uchar, :byte end diff --git a/lib/puppet/util/windows/process.rb b/lib/puppet/util/windows/process.rb index 1128dd673..c8d04cd08 100644 --- a/lib/puppet/util/windows/process.rb +++ b/lib/puppet/util/windows/process.rb @@ -1,251 +1,295 @@ require 'puppet/util/windows' require 'windows/process' require 'windows/handle' require 'windows/synchronize' require 'ffi' module Puppet::Util::Windows::Process extend ::Windows::Process extend ::Windows::Handle extend ::Windows::Synchronize extend FFI::Library ffi_convention :stdcall # http://msdn.microsoft.com/en-us/library/windows/desktop/ms683179(v=vs.85).aspx # HANDLE WINAPI GetCurrentProcess(void); ffi_lib 'kernel32' attach_function_private :GetCurrentProcess, [], :handle # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx # BOOL WINAPI CloseHandle( # _In_ HANDLE hObject # ); ffi_lib 'kernel32' attach_function_private :CloseHandle, [:handle], :bool # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379295(v=vs.85).aspx # BOOL WINAPI OpenProcessToken( # _In_ HANDLE ProcessHandle, # _In_ DWORD DesiredAccess, # _Out_ PHANDLE TokenHandle # ); ffi_lib 'advapi32' attach_function_private :OpenProcessToken, [:handle, :dword, :phandle], :bool # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379261(v=vs.85).aspx # typedef struct _LUID { # DWORD LowPart; # LONG HighPart; # } LUID, *PLUID; class LUID < FFI::Struct layout :LowPart, :dword, :HighPart, :win32_long end # http://msdn.microsoft.com/en-us/library/Windows/desktop/aa379180(v=vs.85).aspx # BOOL WINAPI LookupPrivilegeValue( # _In_opt_ LPCTSTR lpSystemName, # _In_ LPCTSTR lpName, # _Out_ PLUID lpLuid # ); ffi_lib 'advapi32' attach_function_private :LookupPrivilegeValueA, [:lpcstr, :lpcstr, :pointer], :bool # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379626(v=vs.85).aspx TOKEN_INFORMATION_CLASS = enum( :TokenUser, 1, :TokenGroups, :TokenPrivileges, :TokenOwner, :TokenPrimaryGroup, :TokenDefaultDacl, :TokenSource, :TokenType, :TokenImpersonationLevel, :TokenStatistics, :TokenRestrictedSids, :TokenSessionId, :TokenGroupsAndPrivileges, :TokenSessionReference, :TokenSandBoxInert, :TokenAuditPolicy, :TokenOrigin, :TokenElevationType, :TokenLinkedToken, :TokenElevation, :TokenHasRestrictions, :TokenAccessInformation, :TokenVirtualizationAllowed, :TokenVirtualizationEnabled, :TokenIntegrityLevel, :TokenUIAccess, :TokenMandatoryPolicy, :TokenLogonSid, :TokenIsAppContainer, :TokenCapabilities, :TokenAppContainerSid, :TokenAppContainerNumber, :TokenUserClaimAttributes, :TokenDeviceClaimAttributes, :TokenRestrictedUserClaimAttributes, :TokenRestrictedDeviceClaimAttributes, :TokenDeviceGroups, :TokenRestrictedDeviceGroups, :TokenSecurityAttributes, :TokenIsRestricted, :MaxTokenInfoClass ) # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379263(v=vs.85).aspx # typedef struct _LUID_AND_ATTRIBUTES { # LUID Luid; # DWORD Attributes; # } LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES; class LUID_AND_ATTRIBUTES < FFI::Struct layout :Luid, LUID, :Attributes, :dword end # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379630(v=vs.85).aspx # typedef struct _TOKEN_PRIVILEGES { # DWORD PrivilegeCount; # LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY]; # } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES; class TOKEN_PRIVILEGES < FFI::Struct layout :PrivilegeCount, :dword, :Privileges, [LUID_AND_ATTRIBUTES, 1] # placeholder for offset end + # http://msdn.microsoft.com/en-us/library/windows/desktop/bb530717(v=vs.85).aspx + # typedef struct _TOKEN_ELEVATION { + # DWORD TokenIsElevated; + # } TOKEN_ELEVATION, *PTOKEN_ELEVATION; + class TOKEN_ELEVATION < FFI::Struct + layout :TokenIsElevated, :dword + end + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa446671(v=vs.85).aspx # BOOL WINAPI GetTokenInformation( # _In_ HANDLE TokenHandle, # _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, # _Out_opt_ LPVOID TokenInformation, # _In_ DWORD TokenInformationLength, # _Out_ PDWORD ReturnLength # ); ffi_lib 'advapi32' attach_function_private :GetTokenInformation, [:handle, TOKEN_INFORMATION_CLASS, :lpvoid, :dword, :pdword ], :bool 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 def get_current_process # this pseudo-handle does not require closing per MSDN docs GetCurrentProcess() end module_function :get_current_process def open_process_token(handle, desired_access) token_handle_ptr = FFI::MemoryPointer.new(:handle, 1) result = OpenProcessToken(handle, desired_access, token_handle_ptr) if !result raise Puppet::Util::Windows::Error.new( "OpenProcessToken(#{handle}, #{desired_access.to_s(8)}, #{token_handle_ptr})") end begin yield token_handle = token_handle_ptr.read_uint ensure CloseHandle(token_handle) end end module_function :open_process_token def lookup_privilege_value(name, system_name = '') luid = FFI::MemoryPointer.new(LUID.size) result = LookupPrivilegeValueA( system_name, name.to_s, luid ) return LUID.new(luid) if result raise Puppet::Util::Windows::Error.new( "LookupPrivilegeValue(#{system_name}, #{name}, #{luid})") end module_function :lookup_privilege_value def get_token_information(token_handle, token_information) # to determine buffer size return_length_ptr = FFI::MemoryPointer.new(:dword, 1) result = GetTokenInformation(token_handle, token_information, nil, 0, return_length_ptr) return_length = return_length_ptr.read_uint if return_length <= 0 raise Puppet::Util::Windows::Error.new( "GetTokenInformation(#{token_handle}, #{token_information}, nil, 0, #{return_length_ptr})") end # re-call API with properly sized buffer for all results token_information_buf = FFI::MemoryPointer.new(return_length) result = GetTokenInformation(token_handle, token_information, token_information_buf, return_length, return_length_ptr) if !result raise Puppet::Util::Windows::Error.new( "GetTokenInformation(#{token_handle}, #{token_information}, #{token_information_buf}, " + "#{return_length}, #{return_length_ptr})") end + token_information_buf + end + module_function :get_token_information + + def parse_token_information_as_token_privileges(token_information_buf) raw_privileges = TOKEN_PRIVILEGES.new(token_information_buf) privileges = { :count => raw_privileges[:PrivilegeCount], :privileges => [] } offset = token_information_buf + TOKEN_PRIVILEGES.offset_of(:Privileges) privilege_ptr = FFI::Pointer.new(LUID_AND_ATTRIBUTES, offset) # extract each instance of LUID_AND_ATTRIBUTES 0.upto(privileges[:count] - 1) do |i| privileges[:privileges] << LUID_AND_ATTRIBUTES.new(privilege_ptr[i]) end privileges end - module_function :get_token_information + module_function :parse_token_information_as_token_privileges + + def parse_token_information_as_token_elevation(token_information_buf) + TOKEN_ELEVATION.new(token_information_buf) + end + module_function :parse_token_information_as_token_elevation TOKEN_ALL_ACCESS = 0xF01FF ERROR_NO_SUCH_PRIVILEGE = 1313 def process_privilege_symlink? handle = get_current_process open_process_token(handle, TOKEN_ALL_ACCESS) do |token_handle| luid = lookup_privilege_value('SeCreateSymbolicLinkPrivilege') token_info = get_token_information(token_handle, :TokenPrivileges) - token_info[:privileges].any? { |p| p[:Luid].values == luid.values } + token_privileges = parse_token_information_as_token_privileges(token_info) + token_privileges[:privileges].any? { |p| p[:Luid].values == luid.values } end rescue Puppet::Util::Windows::Error => e if e.code == ERROR_NO_SUCH_PRIVILEGE false # pre-Vista else raise e end end module_function :process_privilege_symlink? + + TOKEN_QUERY = 0x0008 + # Returns whether or not the owner of the current process is running + # with elevated security privileges. + # + # Only supported on Windows Vista or later. + # + def elevated_security? + handle = get_current_process + open_process_token(handle, TOKEN_QUERY) do |token_handle| + token_info = get_token_information(token_handle, :TokenElevation) + token_elevation = parse_token_information_as_token_elevation(token_info) + # TokenIsElevated member of the TOKEN_ELEVATION struct + token_elevation[:TokenIsElevated] != 0 + end + rescue Puppet::Util::Windows::Error => e + if e.code == ERROR_NO_SUCH_PRIVILEGE + false # pre-Vista + else + raise e + end + ensure + CloseHandle(handle) + end + module_function :elevated_security? end diff --git a/lib/puppet/util/windows/string.rb b/lib/puppet/util/windows/string.rb index 13d9839d1..147c92915 100644 --- a/lib/puppet/util/windows/string.rb +++ b/lib/puppet/util/windows/string.rb @@ -1,14 +1,16 @@ require 'puppet/util/windows' module Puppet::Util::Windows::String def wide_string(str) + # if given a nil string, assume caller wants to pass a nil pointer to win32 + return nil if str.nil? # ruby (< 2.1) does not respect multibyte terminators, so it is possible # for a string to contain a single trailing null byte, followed by garbage # causing buffer overruns. # # See http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?revision=41920&view=revision newstr = str + "\0".encode(str.encoding) newstr.encode!('UTF-16LE') end module_function :wide_string end diff --git a/lib/puppet/util/windows/user.rb b/lib/puppet/util/windows/user.rb index 51eef779d..56b1dcfcf 100644 --- a/lib/puppet/util/windows/user.rb +++ b/lib/puppet/util/windows/user.rb @@ -1,108 +1,288 @@ require 'puppet/util/windows' -require 'win32/security' require 'facter' +require 'ffi' module Puppet::Util::Windows::User - include ::Windows::Security - extend ::Windows::Security + extend Puppet::Util::Windows::String + extend FFI::Library 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 + return Puppet::Util::Windows::Process.elevated_security? unless majversion.to_f < 6.0 # otherwise 2003 or less check_token_membership end module_function :admin? + + # http://msdn.microsoft.com/en-us/library/windows/desktop/ee207397(v=vs.85).aspx + SECURITY_MAX_SID_SIZE = 68 + def check_token_membership - sid = 0.chr * 80 - size = [80].pack('L') - member = 0.chr * 4 + sid_pointer = FFI::MemoryPointer.new(:byte, SECURITY_MAX_SID_SIZE) + size_pointer = FFI::MemoryPointer.new(:dword, 1) + size_pointer.write_uint32(SECURITY_MAX_SID_SIZE) - unless CreateWellKnownSid(WinBuiltinAdministratorsSid, nil, sid, size) + unless CreateWellKnownSid(:WinBuiltinAdministratorsSid, FFI::Pointer::NULL, sid_pointer, size_pointer) raise Puppet::Util::Windows::Error.new("Failed to create administrators SID") end - unless IsValidSid(sid) + unless IsValidSid(sid_pointer) raise Puppet::Util::Windows::Error.new("Invalid SID") end - unless CheckTokenMembership(nil, sid, member) + ismember_pointer = FFI::MemoryPointer.new(:bool, 1) + unless CheckTokenMembership(FFI::Pointer::NULL, sid_pointer, ismember_pointer) 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 + ismember_pointer.read_bool end module_function :check_token_membership def password_is?(name, password) logon_user(name, password) true rescue Puppet::Util::Windows::Error 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", ['L'], 'B') - - token = 0.chr * 4 - if logon_user.call(name, ".", password, fLOGON32_LOGON_NETWORK, fLOGON32_PROVIDER_DEFAULT, token) == 0 + token_pointer = FFI::MemoryPointer.new(:handle, 1) + if ! LogonUserW(wide_string(name), wide_string('.'), wide_string(password), + fLOGON32_LOGON_NETWORK, fLOGON32_PROVIDER_DEFAULT, token_pointer) raise Puppet::Util::Windows::Error.new("Failed to logon user #{name.inspect}") end - token = token.unpack('L')[0] + token = token_pointer.read_handle begin yield token if block_given? ensure - close_handle.call(token) + CloseHandle(token) 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', 'L'], 'L') + pi = PROFILEINFO.new + pi[:dwSize] = PROFILEINFO.size + pi[:dwFlags] = 1 # PI_NOUI - prevents display of profile error msgs + pi[:lpUserName] = FFI::MemoryPointer.from_string_to_wide_string(user) # Load the profile. Since it doesn't exist, it will be created - if load_user_profile.call(token, pi) == 0 + if ! LoadUserProfileW(token, pi.pointer) raise Puppet::Util::Windows::Error.new("Failed to load user profile #{user.inspect}") end Puppet.debug("Loaded profile for #{user}") - profile = pi.unpack('LLLLLLLL').last - if unload_user_profile.call(token, profile) == 0 + if ! UnloadUserProfile(token, pi[:hProfile]) raise Puppet::Util::Windows::Error.new("Failed to unload user profile #{user.inspect}") end end end module_function :load_profile + + ffi_convention :stdcall + + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx + # BOOL LogonUser( + # _In_ LPTSTR lpszUsername, + # _In_opt_ LPTSTR lpszDomain, + # _In_opt_ LPTSTR lpszPassword, + # _In_ DWORD dwLogonType, + # _In_ DWORD dwLogonProvider, + # _Out_ PHANDLE phToken + # ); + ffi_lib :advapi32 + attach_function_private :LogonUserW, + [:lpwstr, :lpwstr, :lpwstr, :dword, :dword, :phandle], :bool + + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx + # BOOL WINAPI CloseHandle( + # _In_ HANDLE hObject + # ); + ffi_lib 'kernel32' + attach_function_private :CloseHandle, [:handle], :bool + + # http://msdn.microsoft.com/en-us/library/windows/desktop/bb773378(v=vs.85).aspx + # typedef struct _PROFILEINFO { + # DWORD dwSize; + # DWORD dwFlags; + # LPTSTR lpUserName; + # LPTSTR lpProfilePath; + # LPTSTR lpDefaultPath; + # LPTSTR lpServerName; + # LPTSTR lpPolicyPath; + # HANDLE hProfile; + # } PROFILEINFO, *LPPROFILEINFO; + # technically + # NOTE: that for structs, buffer_* (lptstr alias) cannot be used + class PROFILEINFO < FFI::Struct + layout :dwSize, :dword, + :dwFlags, :dword, + :lpUserName, :pointer, + :lpProfilePath, :pointer, + :lpDefaultPath, :pointer, + :lpServerName, :pointer, + :lpPolicyPath, :pointer, + :hProfile, :handle + end + + # http://msdn.microsoft.com/en-us/library/windows/desktop/bb762281(v=vs.85).aspx + # BOOL WINAPI LoadUserProfile( + # _In_ HANDLE hToken, + # _Inout_ LPPROFILEINFO lpProfileInfo + # ); + ffi_lib :userenv + attach_function_private :LoadUserProfileW, + [:handle, :pointer], :bool + + # http://msdn.microsoft.com/en-us/library/windows/desktop/bb762282(v=vs.85).aspx + # BOOL WINAPI UnloadUserProfile( + # _In_ HANDLE hToken, + # _In_ HANDLE hProfile + # ); + ffi_lib :userenv + attach_function_private :UnloadUserProfile, + [:handle, :handle], :bool + + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa376389(v=vs.85).aspx + # BOOL WINAPI CheckTokenMembership( + # _In_opt_ HANDLE TokenHandle, + # _In_ PSID SidToCheck, + # _Out_ PBOOL IsMember + # ); + ffi_lib :advapi32 + attach_function_private :CheckTokenMembership, + [:handle, :pointer, :pbool], :bool + + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379650(v=vs.85).aspx + WELL_KNOWN_SID_TYPE = enum( + :WinNullSid , 0, + :WinWorldSid , 1, + :WinLocalSid , 2, + :WinCreatorOwnerSid , 3, + :WinCreatorGroupSid , 4, + :WinCreatorOwnerServerSid , 5, + :WinCreatorGroupServerSid , 6, + :WinNtAuthoritySid , 7, + :WinDialupSid , 8, + :WinNetworkSid , 9, + :WinBatchSid , 10, + :WinInteractiveSid , 11, + :WinServiceSid , 12, + :WinAnonymousSid , 13, + :WinProxySid , 14, + :WinEnterpriseControllersSid , 15, + :WinSelfSid , 16, + :WinAuthenticatedUserSid , 17, + :WinRestrictedCodeSid , 18, + :WinTerminalServerSid , 19, + :WinRemoteLogonIdSid , 20, + :WinLogonIdsSid , 21, + :WinLocalSystemSid , 22, + :WinLocalServiceSid , 23, + :WinNetworkServiceSid , 24, + :WinBuiltinDomainSid , 25, + :WinBuiltinAdministratorsSid , 26, + :WinBuiltinUsersSid , 27, + :WinBuiltinGuestsSid , 28, + :WinBuiltinPowerUsersSid , 29, + :WinBuiltinAccountOperatorsSid , 30, + :WinBuiltinSystemOperatorsSid , 31, + :WinBuiltinPrintOperatorsSid , 32, + :WinBuiltinBackupOperatorsSid , 33, + :WinBuiltinReplicatorSid , 34, + :WinBuiltinPreWindows2000CompatibleAccessSid , 35, + :WinBuiltinRemoteDesktopUsersSid , 36, + :WinBuiltinNetworkConfigurationOperatorsSid , 37, + :WinAccountAdministratorSid , 38, + :WinAccountGuestSid , 39, + :WinAccountKrbtgtSid , 40, + :WinAccountDomainAdminsSid , 41, + :WinAccountDomainUsersSid , 42, + :WinAccountDomainGuestsSid , 43, + :WinAccountComputersSid , 44, + :WinAccountControllersSid , 45, + :WinAccountCertAdminsSid , 46, + :WinAccountSchemaAdminsSid , 47, + :WinAccountEnterpriseAdminsSid , 48, + :WinAccountPolicyAdminsSid , 49, + :WinAccountRasAndIasServersSid , 50, + :WinNTLMAuthenticationSid , 51, + :WinDigestAuthenticationSid , 52, + :WinSChannelAuthenticationSid , 53, + :WinThisOrganizationSid , 54, + :WinOtherOrganizationSid , 55, + :WinBuiltinIncomingForestTrustBuildersSid , 56, + :WinBuiltinPerfMonitoringUsersSid , 57, + :WinBuiltinPerfLoggingUsersSid , 58, + :WinBuiltinAuthorizationAccessSid , 59, + :WinBuiltinTerminalServerLicenseServersSid , 60, + :WinBuiltinDCOMUsersSid , 61, + :WinBuiltinIUsersSid , 62, + :WinIUserSid , 63, + :WinBuiltinCryptoOperatorsSid , 64, + :WinUntrustedLabelSid , 65, + :WinLowLabelSid , 66, + :WinMediumLabelSid , 67, + :WinHighLabelSid , 68, + :WinSystemLabelSid , 69, + :WinWriteRestrictedCodeSid , 70, + :WinCreatorOwnerRightsSid , 71, + :WinCacheablePrincipalsGroupSid , 72, + :WinNonCacheablePrincipalsGroupSid , 73, + :WinEnterpriseReadonlyControllersSid , 74, + :WinAccountReadonlyControllersSid , 75, + :WinBuiltinEventLogReadersGroup , 76, + :WinNewEnterpriseReadonlyControllersSid , 77, + :WinBuiltinCertSvcDComAccessGroup , 78, + :WinMediumPlusLabelSid , 79, + :WinLocalLogonSid , 80, + :WinConsoleLogonSid , 81, + :WinThisOrganizationCertificateSid , 82, + :WinApplicationPackageAuthoritySid , 83, + :WinBuiltinAnyPackageSid , 84, + :WinCapabilityInternetClientSid , 85, + :WinCapabilityInternetClientServerSid , 86, + :WinCapabilityPrivateNetworkClientServerSid , 87, + :WinCapabilityPicturesLibrarySid , 88, + :WinCapabilityVideosLibrarySid , 89, + :WinCapabilityMusicLibrarySid , 90, + :WinCapabilityDocumentsLibrarySid , 91, + :WinCapabilitySharedUserCertificatesSid , 92, + :WinCapabilityEnterpriseAuthenticationSid , 93, + :WinCapabilityRemovableStorageSid , 94 + ) + + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa446585(v=vs.85).aspx + # BOOL WINAPI CreateWellKnownSid( + # _In_ WELL_KNOWN_SID_TYPE WellKnownSidType, + # _In_opt_ PSID DomainSid, + # _Out_opt_ PSID pSid, + # _Inout_ DWORD *cbSid + # ); + ffi_lib :advapi32 + attach_function_private :CreateWellKnownSid, + [WELL_KNOWN_SID_TYPE, :pointer, :pointer, :lpdword], :bool + + # http://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], :bool end diff --git a/spec/integration/util/windows/user_spec.rb b/spec/integration/util/windows/user_spec.rb index 0435b2cdc..0bfa595ef 100755 --- a/spec/integration/util/windows/user_spec.rb +++ b/spec/integration/util/windows/user_spec.rb @@ -1,59 +1,92 @@ #! /usr/bin/env ruby require 'spec_helper' describe "Puppet::Util::Windows::User", :if => Puppet.features.microsoft_windows? do describe "2003 without UAC" do before :each do Facter.stubs(:value).with(:kernelmajversion).returns("5.2") end it "should be an admin if user's token contains the Administrators SID" do Puppet::Util::Windows::User.expects(:check_token_membership).returns(true) - Win32::Security.expects(:elevated_security?).never + Puppet::Util::Windows::Process.expects(:elevated_security?).never Puppet::Util::Windows::User.should be_admin end it "should not be an admin if user's token doesn't contain the Administrators SID" do Puppet::Util::Windows::User.expects(:check_token_membership).returns(false) - Win32::Security.expects(:elevated_security?).never + Puppet::Util::Windows::Process.expects(:elevated_security?).never Puppet::Util::Windows::User.should_not be_admin end it "should raise an exception if we can't check token membership" do - Puppet::Util::Windows::User.expects(:check_token_membership).raises(Win32::Security::Error, "Access denied.") - Win32::Security.expects(:elevated_security?).never + Puppet::Util::Windows::User.expects(:check_token_membership).raises(Puppet::Util::Windows::Error, "Access denied.") + Puppet::Util::Windows::Process.expects(:elevated_security?).never - lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Win32::Security::Error, /Access denied./) + lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Puppet::Util::Windows::Error, /Access denied./) end end describe "2008 with UAC" do before :each do Facter.stubs(:value).with(:kernelmajversion).returns("6.0") end it "should be an admin if user is running with elevated privileges" do - Win32::Security.stubs(:elevated_security?).returns(true) + Puppet::Util::Windows::Process.stubs(:elevated_security?).returns(true) Puppet::Util::Windows::User.expects(:check_token_membership).never Puppet::Util::Windows::User.should be_admin end it "should not be an admin if user is not running with elevated privileges" do - Win32::Security.stubs(:elevated_security?).returns(false) + Puppet::Util::Windows::Process.stubs(:elevated_security?).returns(false) Puppet::Util::Windows::User.expects(:check_token_membership).never Puppet::Util::Windows::User.should_not be_admin end it "should raise an exception if the process fails to open the process token" do - Win32::Security.stubs(:elevated_security?).raises(Win32::Security::Error, "Access denied.") + Puppet::Util::Windows::Process.stubs(:elevated_security?).raises(Puppet::Util::Windows::Error, "Access denied.") Puppet::Util::Windows::User.expects(:check_token_membership).never - lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Win32::Security::Error, /Access denied./) + lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Puppet::Util::Windows::Error, /Access denied./) + end + end + + describe "module function" do + let(:username) { 'fabio' } + let(:bad_password) { 'goldilocks' } + let(:logon_fail_msg) { /Failed to logon user "fabio": Logon failure: unknown user name or bad password./ } + + describe "load_profile" do + it "should raise an error when provided with an incorrect username and password" do + lambda { Puppet::Util::Windows::User.load_profile(username, bad_password) }.should raise_error(Puppet::Util::Windows::Error, logon_fail_msg) + end + it "should raise an error when provided with an incorrect username and nil password" do + lambda { Puppet::Util::Windows::User.load_profile(username, nil) }.should raise_error(Puppet::Util::Windows::Error, logon_fail_msg) + end + end + + describe "logon_user" do + it "should raise an error when provided with an incorrect username and password" do + lambda { Puppet::Util::Windows::User.logon_user(username, bad_password) }.should raise_error(Puppet::Util::Windows::Error, logon_fail_msg) + end + it "should raise an error when provided with an incorrect username and nil password" do + lambda { Puppet::Util::Windows::User.logon_user(username, nil) }.should raise_error(Puppet::Util::Windows::Error, logon_fail_msg) + end + end + + describe "password_is?" do + it "should return false given an incorrect username and password" do + Puppet::Util::Windows::User.password_is?(username, bad_password).should be_false + end + it "should return false given an incorrect username and nil password" do + Puppet::Util::Windows::User.password_is?(username, nil).should be_false + end end end end diff --git a/spec/unit/util/windows/api_types_spec.rb b/spec/unit/util/windows/api_types_spec.rb new file mode 100644 index 000000000..75d591edd --- /dev/null +++ b/spec/unit/util/windows/api_types_spec.rb @@ -0,0 +1,24 @@ +# encoding: UTF-8 +#!/usr/bin/env ruby + +require 'spec_helper' + +describe "FFI::MemoryPointer", :if => Puppet.features.microsoft_windows? do + context "read_wide_string" do + let (:string) { "foo_bar" } + + it "should properly roundtrip a given string" do + ptr = FFI::MemoryPointer.from_string_to_wide_string(string) + read_string = ptr.read_wide_string(string.length) + + read_string.should == string + end + + it "should return a given string in the default encoding" do + ptr = FFI::MemoryPointer.from_string_to_wide_string(string) + read_string = ptr.read_wide_string(string.length) + + read_string.encoding.should == Encoding.default_external + end + end +end diff --git a/spec/unit/util/windows/string_spec.rb b/spec/unit/util/windows/string_spec.rb index 60f7e6449..5c6473e70 100644 --- a/spec/unit/util/windows/string_spec.rb +++ b/spec/unit/util/windows/string_spec.rb @@ -1,54 +1,58 @@ # encoding: UTF-8 #!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe "Puppet::Util::Windows::String", :if => Puppet.features.microsoft_windows? do UTF16_NULL = [0, 0] def wide_string(str) Puppet::Util::Windows::String.wide_string(str) end def converts_to_wide_string(string_value) expected = string_value.encode(Encoding::UTF_16LE) expected_bytes = expected.bytes.to_a + UTF16_NULL wide_string(string_value).bytes.to_a.should == expected_bytes end context "wide_string" do it "should return encoding of UTF-16LE" do wide_string("bob").encoding.should == Encoding::UTF_16LE end it "should return valid encoding" do wide_string("bob").valid_encoding?.should be_true end it "should convert an ASCII string" do converts_to_wide_string("bob".encode(Encoding::US_ASCII)) end it "should convert a UTF-8 string" do converts_to_wide_string("bob".encode(Encoding::UTF_8)) end it "should convert a UTF-16LE string" do converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_16LE)) end it "should convert a UTF-16BE string" do converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_16BE)) end it "should convert an UTF-32LE string" do converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_32LE)) end it "should convert an UTF-32BE string" do converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_32BE)) end + + it "should return a nil when given a nil" do + wide_string(nil).should == nil + end end end