Hello community, here is the log from the commit of package rubygem-mixlib-shellout for openSUSE:Factory checked in at 2019-08-06 15:10:00 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rubygem-mixlib-shellout (Old) and /work/SRC/openSUSE:Factory/.rubygem-mixlib-shellout.new.4126 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rubygem-mixlib-shellout" Tue Aug 6 15:10:00 2019 rev:20 rq:717309 version:3.0.4 Changes: -------- --- /work/SRC/openSUSE:Factory/rubygem-mixlib-shellout/rubygem-mixlib-shellout.changes 2019-03-04 09:20:22.108601054 +0100 +++ /work/SRC/openSUSE:Factory/.rubygem-mixlib-shellout.new.4126/rubygem-mixlib-shellout.changes 2019-08-06 15:10:02.551785121 +0200 @@ -1,0 +2,6 @@ +Fri Jul 19 09:22:43 UTC 2019 - Stephan Kulow <co...@suse.com> + +- updated to version 3.0.4 + no changelog found + +------------------------------------------------------------------- Old: ---- mixlib-shellout-2.4.4.gem New: ---- mixlib-shellout-3.0.4.gem ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rubygem-mixlib-shellout.spec ++++++ --- /var/tmp/diff_new_pack.pBvyhy/_old 2019-08-06 15:10:03.211784747 +0200 +++ /var/tmp/diff_new_pack.pBvyhy/_new 2019-08-06 15:10:03.215784745 +0200 @@ -1,7 +1,7 @@ # # spec file for package rubygem-mixlib-shellout # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -24,7 +24,7 @@ # Name: rubygem-mixlib-shellout -Version: 2.4.4 +Version: 3.0.4 Release: 0 %define mod_name mixlib-shellout %define mod_full_name %{mod_name}-%{version} ++++++ mixlib-shellout-2.4.4.gem -> mixlib-shellout-3.0.4.gem ++++++ Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/mixlib/shellout/version.rb new/lib/mixlib/shellout/version.rb --- old/lib/mixlib/shellout/version.rb 2018-12-12 05:20:53.000000000 +0100 +++ new/lib/mixlib/shellout/version.rb 2019-06-07 01:26:21.000000000 +0200 @@ -1,5 +1,5 @@ module Mixlib class ShellOut - VERSION = "2.4.4".freeze + VERSION = "3.0.4".freeze end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/mixlib/shellout/windows/core_ext.rb new/lib/mixlib/shellout/windows/core_ext.rb --- old/lib/mixlib/shellout/windows/core_ext.rb 2018-12-12 05:20:53.000000000 +0100 +++ new/lib/mixlib/shellout/windows/core_ext.rb 2019-06-07 01:26:21.000000000 +0200 @@ -36,10 +36,48 @@ 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 @@ # 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" @@ -85,9 +126,9 @@ # Set default values hash = { - "app_name" => nil, + "app_name" => nil, "creation_flags" => 0, - "close_handles" => true, + "close_handles" => true, } # Validate the keys, and convert symbols and case to lowercase strings. @@ -238,6 +279,7 @@ 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 @@ # 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 @@ 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 @@ 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 @@ [ logon, passwd, domain ] end + end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/mixlib/shellout/windows.rb new/lib/mixlib/shellout/windows.rb --- old/lib/mixlib/shellout/windows.rb 2018-12-12 05:20:53.000000000 +0100 +++ new/lib/mixlib/shellout/windows.rb 2019-06-07 01:26:21.000000000 +0200 @@ -2,7 +2,7 @@ # Author:: Daniel DeLeo (<d...@chef.io>) # Author:: John Keiser (<jkei...@chef.io>) # Author:: Ho-Sheng Hsiao (<h...@chef.io>) -# Copyright:: Copyright (c) 2011-2016 Chef Software, Inc. +# Copyright:: Copyright (c) 2011-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -66,7 +66,7 @@ # # Set cwd, environment, appname, etc. # - app_name, command_line = command_to_run(command) + app_name, command_line = command_to_run(combine_args(*command)) create_process_args = { app_name: app_name, command_line: command_line, @@ -88,7 +88,7 @@ # # 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 @@ 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 @@ -196,6 +198,46 @@ true end + # Use to support array passing semantics on windows + # + # 1. strings with whitespace or quotes in them need quotes around them. + # 2. interior quotes need to get backslash escaped (parser needs to know when it really ends). + # 3. random backlsashes in paths themselves remain untouched. + # 4. if the argument must be quoted by #1 and terminates in a sequence of backslashes then all the backlashes must themselves + # be backslash excaped (double the backslashes). + # 5. if an interior quote that must be escaped by #2 has a sequence of backslashes before it then all the backslashes must + # themselves be backslash excaped along with the backslash ecape of the interior quote (double plus one backslashes). + # + # And to restate. We are constructing a string which will be parsed by the windows parser into arguments, and we want those + # arguments to match the *args array we are passed here. So call the windows parser operation A then we need to apply A^-1 to + # our args to construct the string so that applying A gives windows back our *args. + # + # And when the windows parser sees a series of backslashes followed by a double quote, it has to determine if that double quote + # is terminating or not, and how many backslashes to insert in the args. So what it does is divide it by two (rounding down) to + # get the number of backslashes to insert. Then if it is even the double quotes terminate the argument. If it is even the + # double quotes are interior double quotes (the extra backslash quotes the double quote). + # + # We construct the inverse operation so interior double quotes preceeded by N backslashes get 2N+1 backslashes in front of the quote, + # while trailing N backslashes get 2N backslashes in front of the quote that terminates the argument. + # + # see: https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ + # + # @api private + # @param args [Array<String>] array of command arguments + # @return String + def combine_args(*args) + return args[0] if args.length == 1 + args.map do |arg| + if arg =~ /[ \t\n\v"]/ + arg = arg.gsub(/(\\*)"/, '\1\1\"') # interior quotes with N preceeding backslashes need 2N+1 backslashes + arg = arg.sub(/(\\+)$/, '\1\1') # trailing N backslashes need to become 2N backslashes + "\"#{arg}\"" + else + arg + end + end.join(" ") + end + def command_to_run(command) return run_under_cmd(command) if should_run_under_cmd?(command) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/metadata new/metadata --- old/metadata 2018-12-12 05:20:53.000000000 +0100 +++ new/metadata 2019-06-07 01:26:21.000000000 +0200 @@ -1,14 +1,14 @@ --- !ruby/object:Gem::Specification name: mixlib-shellout version: !ruby/object:Gem::Version - version: 2.4.4 + version: 3.0.4 platform: ruby authors: - Chef Software Inc. autorequire: bindir: bin cert_chain: [] -date: 2018-12-12 00:00:00.000000000 Z +date: 2019-06-06 00:00:00.000000000 Z dependencies: [] description: Run external commands on Unix or Windows email: i...@chef.io @@ -41,8 +41,7 @@ - !ruby/object:Gem::Version version: '0' requirements: [] -rubyforge_project: -rubygems_version: 2.7.6 +rubygems_version: 3.0.3 signing_key: specification_version: 4 summary: Run external commands on Unix or Windows