diff --git a/lib/puppet/util/windows/user.rb b/lib/puppet/util/windows/user.rb index d1639f47c..a2277f66c 100644 --- a/lib/puppet/util/windows/user.rb +++ b/lib/puppet/util/windows/user.rb @@ -1,292 +1,292 @@ require 'puppet/util/windows' require 'facter' require 'ffi' module Puppet::Util::Windows::User 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 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 is_admin = false FFI::MemoryPointer.new(:byte, SECURITY_MAX_SID_SIZE) do |sid_pointer| FFI::MemoryPointer.new(:dword, 1) do |size_pointer| size_pointer.write_uint32(SECURITY_MAX_SID_SIZE) if CreateWellKnownSid(:WinBuiltinAdministratorsSid, FFI::Pointer::NULL, sid_pointer, size_pointer) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new("Failed to create administrators SID") end end if IsValidSid(sid_pointer) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new("Invalid SID") end FFI::MemoryPointer.new(:win32_bool, 1) do |ismember_pointer| if CheckTokenMembership(FFI::Pointer::NULL_HANDLE, sid_pointer, ismember_pointer) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new("Failed to check membership") end # Is administrators SID enabled in calling thread's access token? is_admin = ismember_pointer.read_win32_bool != FFI::WIN32_FALSE end end is_admin end module_function :check_token_membership def password_is?(name, password) - logon_user(name, password) + logon_user(name, password) { |token| } 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 token = nil begin FFI::MemoryPointer.new(:handle, 1) do |token_pointer| if LogonUserW(wide_string(name), wide_string('.'), wide_string(password), fLOGON32_LOGON_NETWORK, fLOGON32_PROVIDER_DEFAULT, token_pointer) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new("Failed to logon user #{name.inspect}") end yield token = token_pointer.read_handle end ensure FFI::WIN32.CloseHandle(token) if token end # token has been closed by this point true end module_function :logon_user def load_profile(user, password) logon_user(user, password) do |token| FFI::MemoryPointer.from_string_to_wide_string(user) do |lpUserName| pi = PROFILEINFO.new pi[:dwSize] = PROFILEINFO.size pi[:dwFlags] = 1 # PI_NOUI - prevents display of profile error msgs pi[:lpUserName] = lpUserName # Load the profile. Since it doesn't exist, it will be created if LoadUserProfileW(token, pi.pointer) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new("Failed to load user profile #{user.inspect}") end Puppet.debug("Loaded profile for #{user}") if UnloadUserProfile(token, pi[:hProfile]) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new("Failed to unload user profile #{user.inspect}") end 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], :win32_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], :win32_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], :win32_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], :win32_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], :win32_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], :win32_bool end diff --git a/spec/integration/util/windows/user_spec.rb b/spec/integration/util/windows/user_spec.rb index faa32c195..d3a94afc1 100755 --- a/spec/integration/util/windows/user_spec.rb +++ b/spec/integration/util/windows/user_spec.rb @@ -1,99 +1,106 @@ #! /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) 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) 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(Puppet::Util::Windows::Error, "Access denied.") Puppet::Util::Windows::Process.expects(:elevated_security?).never 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 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 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 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(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 + + it "should return false given a nil username and an incorrect password" do + Puppet::Util::Windows::User.password_is?(nil, bad_password).should be_false + end end describe "check_token_membership" do it "should not raise an error" do # added just to call an FFI code path on all platforms lambda { Puppet::Util::Windows::User.check_token_membership }.should_not raise_error end end end end