diff options
author | Lamont Granquist <lamont@chef.io> | 2019-06-06 16:25:59 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-06 16:25:59 -0700 |
commit | 20f9b8a87f65737c9b5acf3e9b32b539de78a7a2 (patch) | |
tree | 5a317a1714da3dd88ed488af07d50dccff5a3b83 /lib | |
parent | d6f1edb35a2e2a0200b8676cdb7111fc41bda3bb (diff) | |
parent | 4b4d9c89862dacb6396549e2a6ea3555d8c6417a (diff) | |
download | mixlib-shellout-20f9b8a87f65737c9b5acf3e9b32b539de78a7a2.tar.gz |
Merge pull request #177 from visioncritical/load_user_profile
Load and unload user profile as required
Diffstat (limited to 'lib')
-rw-r--r-- | lib/mixlib/shellout/windows.rb | 4 | ||||
-rw-r--r-- | lib/mixlib/shellout/windows/core_ext.rb | 248 |
2 files changed, 185 insertions, 67 deletions
diff --git a/lib/mixlib/shellout/windows.rb b/lib/mixlib/shellout/windows.rb index cde795b..db4fe32 100644 --- a/lib/mixlib/shellout/windows.rb +++ b/lib/mixlib/shellout/windows.rb @@ -88,7 +88,7 @@ module Mixlib # # Start the process # - process = Process.create(create_process_args) + process, profile, token = Process.create(create_process_args) logger.debug(format_process(process, app_name, command_line, timeout)) if logger begin # Start pushing data into input @@ -143,6 +143,8 @@ module Mixlib ensure CloseHandle(process.thread_handle) if process.thread_handle CloseHandle(process.process_handle) if process.process_handle + Process.unload_user_profile(token, profile) if profile + CloseHandle(token) if token end ensure diff --git a/lib/mixlib/shellout/windows/core_ext.rb b/lib/mixlib/shellout/windows/core_ext.rb index 172909f..73f6611 100644 --- a/lib/mixlib/shellout/windows/core_ext.rb +++ b/lib/mixlib/shellout/windows/core_ext.rb @@ -36,10 +36,48 @@ module Process::Constants ERROR_PRIVILEGE_NOT_HELD = 1314 ERROR_LOGON_TYPE_NOT_GRANTED = 0x569 + + # Only documented in Userenv.h ??? + # - ZERO (type Local) is assumed, no docs found + WIN32_PROFILETYPE_LOCAL = 0x00 + WIN32_PROFILETYPE_PT_TEMPORARY = 0x01 + WIN32_PROFILETYPE_PT_ROAMING = 0x02 + WIN32_PROFILETYPE_PT_MANDATORY = 0x04 + WIN32_PROFILETYPE_PT_ROAMING_PREEXISTING = 0x08 + +end + +# Structs required for data handling +module Process::Structs + + class PROFILEINFO < FFI::Struct + layout( + :dwSize, :dword, + :dwFlags, :dword, + :lpUserName, :pointer, + :lpProfilePath, :pointer, + :lpDefaultPath, :pointer, + :lpServerName, :pointer, + :lpPolicyPath, :pointer, + :hProfile, :handle + ) + end + end # Define the functions needed to check with Service windows station module Process::Functions + ffi_lib :userenv + + attach_pfunc :GetProfileType, + [:pointer], :bool + + attach_pfunc :LoadUserProfileW, + [:handle, :pointer], :bool + + attach_pfunc :UnloadUserProfile, + [:handle, :handle], :bool + ffi_lib :advapi32 attach_pfunc :LogonUserW, @@ -64,9 +102,12 @@ end # as of 2015-10-15 from commit cc066e5df25048f9806a610f54bf5f7f253e86f7 module Process + class UnsupportedFeature < StandardError; end + # Explicitly reopen singleton class so that class/constant declarations from # extensions are visible in Modules.nesting. class << self + def create(args) unless args.kind_of?(Hash) raise TypeError, "hash keyword arguments expected" @@ -238,6 +279,7 @@ module Process inherit = hash["inherit"] ? 1 : 0 if hash["with_logon"] + logon, passwd, domain = format_creds_from_hash(hash) hash["creation_flags"] |= CREATE_UNICODE_ENVIRONMENT @@ -255,51 +297,42 @@ module Process # can simulate running a command 'elevated' by running it under a separate # logon as a batch process. if hash["elevated"] || winsta_name =~ /^Service-0x0-.*$/i - logon_type = if hash["elevated"] - LOGON32_LOGON_BATCH - else - LOGON32_LOGON_INTERACTIVE - end - token = logon_user(logon, domain, passwd, logon_type) + logon_type = hash["elevated"] ? LOGON32_LOGON_BATCH : LOGON32_LOGON_INTERACTIVE + token = logon_user(logon, domain, passwd, logon_type) + logon_ptr = FFI::MemoryPointer.from_string(logon) + profile = PROFILEINFO.new.tap do |dat| + dat[:dwSize] = dat.size + dat[:dwFlags] = 1 + dat[:lpUserName] = logon_ptr + end + + if logon_has_roaming_profile? + msg = %w{ + Mixlib does not currently support executing commands as users + configured with Roaming Profiles. [%s] + }.join(" ") % logon.encode("UTF-8").unpack("A*") + raise UnsupportedFeature.new(msg) + end + + load_user_profile(token, profile.pointer) + + create_process_as_user(token, app, cmd, process_security, + thread_security, inherit, hash["creation_flags"], env, + cwd, startinfo, procinfo) - create_process_as_user(token, app, cmd, process_security, thread_security, inherit, hash["creation_flags"], env, cwd, startinfo, procinfo) else - bool = CreateProcessWithLogonW( - logon, # User - domain, # Domain - passwd, # Password - LOGON_WITH_PROFILE, # Logon flags - app, # App name - cmd, # Command line - hash["creation_flags"], # Creation flags - env, # Environment - cwd, # Working directory - startinfo, # Startup Info - procinfo # Process Info - ) - unless bool - raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno) - end + create_process_with_logon(logon, domain, passwd, LOGON_WITH_PROFILE, + app, cmd, hash["creation_flags"], env, cwd, startinfo, procinfo) + end + else - bool = CreateProcessW( - app, # App name - cmd, # Command line - process_security, # Process attributes - thread_security, # Thread attributes - inherit, # Inherit handles? - hash["creation_flags"], # Creation flags - env, # Environment - cwd, # Working directory - startinfo, # Startup Info - procinfo # Process Info - ) - - unless bool - raise SystemCallError.new("CreateProcessW", FFI.errno) - end + + create_process(app, cmd, process_security, thread_security, inherit, + hash["creation_flags"], env, cwd, startinfo, procinfo) + end # Automatically close the process and thread handles in the @@ -314,12 +347,120 @@ module Process procinfo[:hThread] = 0 end - ProcessInfo.new( + process = ProcessInfo.new( procinfo[:hProcess], procinfo[:hThread], procinfo[:dwProcessId], procinfo[:dwThreadId] ) + + [ process, profile, token ] + end + + # See Process::Constants::WIN32_PROFILETYPE + def logon_has_roaming_profile? + get_profile_type >= 2 + end + + def get_profile_type + ptr = FFI::MemoryPointer.new(:uint) + unless GetProfileType(ptr) + raise SystemCallError.new("GetProfileType", FFI.errno) + end + ptr.read_uint + end + + def load_user_profile(token, profile_ptr) + unless LoadUserProfileW(token, profile_ptr) + raise SystemCallError.new("LoadUserProfileW", FFI.errno) + end + true + end + + def unload_user_profile(token, profile) + if profile[:hProfile] == 0 + warn "\n\nWARNING: Profile not loaded\n" + else + unless UnloadUserProfile(token, profile[:hProfile]) + raise SystemCallError.new("UnloadUserProfile", FFI.errno) + end + end + true + end + + def create_process_as_user(token, app, cmd, process_security, + thread_security, inherit, creation_flags, env, cwd, startinfo, procinfo) + + bool = CreateProcessAsUserW( + token, # User token handle + app, # App name + cmd, # Command line + process_security, # Process attributes + thread_security, # Thread attributes + inherit, # Inherit handles + creation_flags, # Creation Flags + env, # Environment + cwd, # Working directory + startinfo, # Startup Info + procinfo # Process Info + ) + + unless bool + msg = case FFI.errno + when ERROR_PRIVILEGE_NOT_HELD + [ + %{CreateProcessAsUserW (User '%s' must hold the 'Replace a process}, + %{level token' and 'Adjust Memory Quotas for a process' permissions.}, + %{Logoff the user after adding this right to make it effective.)}, + ].join(" ") % ::ENV["USERNAME"] + else + "CreateProcessAsUserW failed." + end + raise SystemCallError.new(msg, FFI.errno) + end + end + + def create_process_with_logon(logon, domain, passwd, logon_flags, app, cmd, + creation_flags, env, cwd, startinfo, procinfo) + + bool = CreateProcessWithLogonW( + logon, # User + domain, # Domain + passwd, # Password + logon_flags, # Logon flags + app, # App name + cmd, # Command line + creation_flags, # Creation flags + env, # Environment + cwd, # Working directory + startinfo, # Startup Info + procinfo # Process Info + ) + + unless bool + raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno) + end + end + + def create_process(app, cmd, process_security, thread_security, inherit, + creation_flags, env, cwd, startinfo, procinfo) + + bool = CreateProcessW( + app, # App name + cmd, # Command line + process_security, # Process attributes + thread_security, # Thread attributes + inherit, # Inherit handles? + creation_flags, # Creation flags + env, # Environment + cwd, # Working directory + startinfo, # Startup Info + procinfo # Process Info + ) + + unless bool + raise SystemCallError.new("CreateProcessW", FFI.errno) + end end def logon_user(user, domain, passwd, type, provider = LOGON32_PROVIDER_DEFAULT) @@ -346,32 +487,6 @@ module Process token.read_ulong end - def create_process_as_user(token, app, cmd, process_security, thread_security, inherit, creation_flags, env, cwd, startinfo, procinfo) - bool = CreateProcessAsUserW( - token, # User token handle - app, # App name - cmd, # Command line - process_security, # Process attributes - thread_security, # Thread attributes - inherit, # Inherit handles - creation_flags, # Creation Flags - env, # Environment - cwd, # Working directory - startinfo, # Startup Info - procinfo # Process Info - ) - - unless bool - if FFI.errno == ERROR_PRIVILEGE_NOT_HELD - raise SystemCallError.new("CreateProcessAsUserW (User '#{::ENV['USERNAME']}' must hold the 'Replace a process level token' and 'Adjust Memory Quotas for a process' permissions. Logoff the user after adding this right to make it effective.)", FFI.errno) - else - raise SystemCallError.new("CreateProcessAsUserW failed.", FFI.errno) - end - end - ensure - CloseHandle(token) - end - def get_windows_station_name winsta_name = FFI::MemoryPointer.new(:char, 256) return_size = FFI::MemoryPointer.new(:ulong) @@ -406,5 +521,6 @@ module Process [ logon, passwd, domain ] end + end end |