summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Warsing <brian.warsing@visioncritical.com>2019-01-07 13:39:05 -0800
committerBrian Warsing <brian.warsing@visioncritical.com>2019-01-23 06:49:38 -0800
commita1f96fea184d9c79211eee3179c24fd5b0dd8e78 (patch)
treec869cadcb9520ac4f104419b1cd068f844b5d0c3
parenta74ad86809b2da0ef69bea1f08464f5ef7de2663 (diff)
downloadmixlib-shellout-a1f96fea184d9c79211eee3179c24fd5b0dd8e78.tar.gz
Load and unload user profile as required
- bind related C++ funcs, add types and constants - refactor similar func calls into component methods - make Process#create return trio of process-related objects - ensure delayed profile unload and token destruction Signed-off-by: Brian Warsing <brian.warsing@visioncritical.com>
-rw-r--r--lib/mixlib/shellout/windows.rb4
-rw-r--r--lib/mixlib/shellout/windows/core_ext.rb249
2 files changed, 186 insertions, 67 deletions
diff --git a/lib/mixlib/shellout/windows.rb b/lib/mixlib/shellout/windows.rb
index 27adbbd..bd99ef5 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 b9737fe..4cdf08e 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,10 +102,14 @@ 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"
end
@@ -238,6 +280,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 +298,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 +348,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].zero?
+ 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
+ %w{
+ 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 +488,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 +522,6 @@ module Process
[ logon, passwd, domain ]
end
+
end
end