mixlib-shellout 2.4.4-universal-mingw32 → 3.0.4-universal-mingw32

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4709592fd85b38ce2040327ad6be689c349b58b563eb0327ee3038112e0c4f6
4
- data.tar.gz: 5c7520acca04f64210fdbd2b660a216ca2d2b432607dcdedc0985bca9ab69b7e
3
+ metadata.gz: 68567052a253b4c6bba0f3ff4c884402a41dd3175a00a3a4cef1d9fab8ca753a
4
+ data.tar.gz: ca9b2511628cb6655397abf24ea39a9d49a7c72f5550708e1daa2fe553328e91
5
5
  SHA512:
6
- metadata.gz: b98c77f78e9ffe650bb8e108a62db5ac4867b1b6770e0837c362394e789ecda94059a15b258c2a400a456d0581703145c75cc0937d036541a63e05a69d9d092f
7
- data.tar.gz: f0872fa824d2caad075e197fcf9f54963745e5b2f38e9c58341ccc03097358a0e3337760ba0a9b7756b43944559d09cbc31f42fb95ac526dc84c22190c4321bd
6
+ metadata.gz: 215b9612e24919b285530b202cb3e62ab27436f82fb5cb95efc0bccfc4538794800c57c88a66cfab978479c3718d5b9d5fa40de43833fd05c61a8a769831c571
7
+ data.tar.gz: 40458ab0286b067c4811be58fa4ad4c93568f25c5ca036137023690588c0b65e84f2f8dd034e0b0dc1f7070113305a6a262242207cab0eb8f9c53b38ffa125fe
@@ -1,5 +1,5 @@
1
1
  module Mixlib
2
2
  class ShellOut
3
- VERSION = "2.4.4".freeze
3
+ VERSION = "3.0.4".freeze
4
4
  end
5
5
  end
@@ -2,7 +2,7 @@
2
2
  # Author:: Daniel DeLeo (<dan@chef.io>)
3
3
  # Author:: John Keiser (<jkeiser@chef.io>)
4
4
  # Author:: Ho-Sheng Hsiao (<hosh@chef.io>)
5
- # Copyright:: Copyright (c) 2011-2016 Chef Software, Inc.
5
+ # Copyright:: Copyright (c) 2011-2019, Chef Software Inc.
6
6
  # License:: Apache License, Version 2.0
7
7
  #
8
8
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -66,7 +66,7 @@ module Mixlib
66
66
  #
67
67
  # Set cwd, environment, appname, etc.
68
68
  #
69
- app_name, command_line = command_to_run(command)
69
+ app_name, command_line = command_to_run(combine_args(*command))
70
70
  create_process_args = {
71
71
  app_name: app_name,
72
72
  command_line: command_line,
@@ -88,7 +88,7 @@ module Mixlib
88
88
  #
89
89
  # Start the process
90
90
  #
91
- process = Process.create(create_process_args)
91
+ process, profile, token = Process.create(create_process_args)
92
92
  logger.debug(format_process(process, app_name, command_line, timeout)) if logger
93
93
  begin
94
94
  # Start pushing data into input
@@ -143,6 +143,8 @@ module Mixlib
143
143
  ensure
144
144
  CloseHandle(process.thread_handle) if process.thread_handle
145
145
  CloseHandle(process.process_handle) if process.process_handle
146
+ Process.unload_user_profile(token, profile) if profile
147
+ CloseHandle(token) if token
146
148
  end
147
149
 
148
150
  ensure
@@ -196,6 +198,46 @@ module Mixlib
196
198
  true
197
199
  end
198
200
 
201
+ # Use to support array passing semantics on windows
202
+ #
203
+ # 1. strings with whitespace or quotes in them need quotes around them.
204
+ # 2. interior quotes need to get backslash escaped (parser needs to know when it really ends).
205
+ # 3. random backlsashes in paths themselves remain untouched.
206
+ # 4. if the argument must be quoted by #1 and terminates in a sequence of backslashes then all the backlashes must themselves
207
+ # be backslash excaped (double the backslashes).
208
+ # 5. if an interior quote that must be escaped by #2 has a sequence of backslashes before it then all the backslashes must
209
+ # themselves be backslash excaped along with the backslash ecape of the interior quote (double plus one backslashes).
210
+ #
211
+ # And to restate. We are constructing a string which will be parsed by the windows parser into arguments, and we want those
212
+ # 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
213
+ # our args to construct the string so that applying A gives windows back our *args.
214
+ #
215
+ # And when the windows parser sees a series of backslashes followed by a double quote, it has to determine if that double quote
216
+ # 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
217
+ # get the number of backslashes to insert. Then if it is even the double quotes terminate the argument. If it is even the
218
+ # double quotes are interior double quotes (the extra backslash quotes the double quote).
219
+ #
220
+ # We construct the inverse operation so interior double quotes preceeded by N backslashes get 2N+1 backslashes in front of the quote,
221
+ # while trailing N backslashes get 2N backslashes in front of the quote that terminates the argument.
222
+ #
223
+ # see: https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
224
+ #
225
+ # @api private
226
+ # @param args [Array<String>] array of command arguments
227
+ # @return String
228
+ def combine_args(*args)
229
+ return args[0] if args.length == 1
230
+ args.map do |arg|
231
+ if arg =~ /[ \t\n\v"]/
232
+ arg = arg.gsub(/(\\*)"/, '\1\1\"') # interior quotes with N preceeding backslashes need 2N+1 backslashes
233
+ arg = arg.sub(/(\\+)$/, '\1\1') # trailing N backslashes need to become 2N backslashes
234
+ "\"#{arg}\""
235
+ else
236
+ arg
237
+ end
238
+ end.join(" ")
239
+ end
240
+
199
241
  def command_to_run(command)
200
242
  return run_under_cmd(command) if should_run_under_cmd?(command)
201
243
 
@@ -36,10 +36,48 @@ module Process::Constants
36
36
 
37
37
  ERROR_PRIVILEGE_NOT_HELD = 1314
38
38
  ERROR_LOGON_TYPE_NOT_GRANTED = 0x569
39
+
40
+ # Only documented in Userenv.h ???
41
+ # - ZERO (type Local) is assumed, no docs found
42
+ WIN32_PROFILETYPE_LOCAL = 0x00
43
+ WIN32_PROFILETYPE_PT_TEMPORARY = 0x01
44
+ WIN32_PROFILETYPE_PT_ROAMING = 0x02
45
+ WIN32_PROFILETYPE_PT_MANDATORY = 0x04
46
+ WIN32_PROFILETYPE_PT_ROAMING_PREEXISTING = 0x08
47
+
48
+ end
49
+
50
+ # Structs required for data handling
51
+ module Process::Structs
52
+
53
+ class PROFILEINFO < FFI::Struct
54
+ layout(
55
+ :dwSize, :dword,
56
+ :dwFlags, :dword,
57
+ :lpUserName, :pointer,
58
+ :lpProfilePath, :pointer,
59
+ :lpDefaultPath, :pointer,
60
+ :lpServerName, :pointer,
61
+ :lpPolicyPath, :pointer,
62
+ :hProfile, :handle
63
+ )
64
+ end
65
+
39
66
  end
40
67
 
41
68
  # Define the functions needed to check with Service windows station
42
69
  module Process::Functions
70
+ ffi_lib :userenv
71
+
72
+ attach_pfunc :GetProfileType,
73
+ [:pointer], :bool
74
+
75
+ attach_pfunc :LoadUserProfileW,
76
+ [:handle, :pointer], :bool
77
+
78
+ attach_pfunc :UnloadUserProfile,
79
+ [:handle, :handle], :bool
80
+
43
81
  ffi_lib :advapi32
44
82
 
45
83
  attach_pfunc :LogonUserW,
@@ -64,9 +102,12 @@ end
64
102
  # as of 2015-10-15 from commit cc066e5df25048f9806a610f54bf5f7f253e86f7
65
103
  module Process
66
104
 
105
+ class UnsupportedFeature < StandardError; end
106
+
67
107
  # Explicitly reopen singleton class so that class/constant declarations from
68
108
  # extensions are visible in Modules.nesting.
69
109
  class << self
110
+
70
111
  def create(args)
71
112
  unless args.kind_of?(Hash)
72
113
  raise TypeError, "hash keyword arguments expected"
@@ -85,9 +126,9 @@ module Process
85
126
 
86
127
  # Set default values
87
128
  hash = {
88
- "app_name" => nil,
129
+ "app_name" => nil,
89
130
  "creation_flags" => 0,
90
- "close_handles" => true,
131
+ "close_handles" => true,
91
132
  }
92
133
 
93
134
  # Validate the keys, and convert symbols and case to lowercase strings.
@@ -238,6 +279,7 @@ module Process
238
279
  inherit = hash["inherit"] ? 1 : 0
239
280
 
240
281
  if hash["with_logon"]
282
+
241
283
  logon, passwd, domain = format_creds_from_hash(hash)
242
284
 
243
285
  hash["creation_flags"] |= CREATE_UNICODE_ENVIRONMENT
@@ -255,51 +297,42 @@ module Process
255
297
  # can simulate running a command 'elevated' by running it under a separate
256
298
  # logon as a batch process.
257
299
  if hash["elevated"] || winsta_name =~ /^Service-0x0-.*$/i
258
- logon_type = if hash["elevated"]
259
- LOGON32_LOGON_BATCH
260
- else
261
- LOGON32_LOGON_INTERACTIVE
262
- end
263
300
 
264
- token = logon_user(logon, domain, passwd, logon_type)
301
+ logon_type = hash["elevated"] ? LOGON32_LOGON_BATCH : LOGON32_LOGON_INTERACTIVE
302
+ token = logon_user(logon, domain, passwd, logon_type)
303
+ logon_ptr = FFI::MemoryPointer.from_string(logon)
304
+ profile = PROFILEINFO.new.tap do |dat|
305
+ dat[:dwSize] = dat.size
306
+ dat[:dwFlags] = 1
307
+ dat[:lpUserName] = logon_ptr
308
+ end
309
+
310
+ if logon_has_roaming_profile?
311
+ msg = %w{
312
+ Mixlib does not currently support executing commands as users
313
+ configured with Roaming Profiles. [%s]
314
+ }.join(" ") % logon.encode("UTF-8").unpack("A*")
315
+ raise UnsupportedFeature.new(msg)
316
+ end
317
+
318
+ load_user_profile(token, profile.pointer)
319
+
320
+ create_process_as_user(token, app, cmd, process_security,
321
+ thread_security, inherit, hash["creation_flags"], env,
322
+ cwd, startinfo, procinfo)
265
323
 
266
- create_process_as_user(token, app, cmd, process_security, thread_security, inherit, hash["creation_flags"], env, cwd, startinfo, procinfo)
267
324
  else
268
- bool = CreateProcessWithLogonW(
269
- logon, # User
270
- domain, # Domain
271
- passwd, # Password
272
- LOGON_WITH_PROFILE, # Logon flags
273
- app, # App name
274
- cmd, # Command line
275
- hash["creation_flags"], # Creation flags
276
- env, # Environment
277
- cwd, # Working directory
278
- startinfo, # Startup Info
279
- procinfo # Process Info
280
- )
281
325
 
282
- unless bool
283
- raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
284
- end
326
+ create_process_with_logon(logon, domain, passwd, LOGON_WITH_PROFILE,
327
+ app, cmd, hash["creation_flags"], env, cwd, startinfo, procinfo)
328
+
285
329
  end
330
+
286
331
  else
287
- bool = CreateProcessW(
288
- app, # App name
289
- cmd, # Command line
290
- process_security, # Process attributes
291
- thread_security, # Thread attributes
292
- inherit, # Inherit handles?
293
- hash["creation_flags"], # Creation flags
294
- env, # Environment
295
- cwd, # Working directory
296
- startinfo, # Startup Info
297
- procinfo # Process Info
298
- )
299
-
300
- unless bool
301
- raise SystemCallError.new("CreateProcessW", FFI.errno)
302
- end
332
+
333
+ create_process(app, cmd, process_security, thread_security, inherit,
334
+ hash["creation_flags"], env, cwd, startinfo, procinfo)
335
+
303
336
  end
304
337
 
305
338
  # Automatically close the process and thread handles in the
@@ -314,12 +347,120 @@ module Process
314
347
  procinfo[:hThread] = 0
315
348
  end
316
349
 
317
- ProcessInfo.new(
350
+ process = ProcessInfo.new(
318
351
  procinfo[:hProcess],
319
352
  procinfo[:hThread],
320
353
  procinfo[:dwProcessId],
321
354
  procinfo[:dwThreadId]
322
355
  )
356
+
357
+ [ process, profile, token ]
358
+ end
359
+
360
+ # See Process::Constants::WIN32_PROFILETYPE
361
+ def logon_has_roaming_profile?
362
+ get_profile_type >= 2
363
+ end
364
+
365
+ def get_profile_type
366
+ ptr = FFI::MemoryPointer.new(:uint)
367
+ unless GetProfileType(ptr)
368
+ raise SystemCallError.new("GetProfileType", FFI.errno)
369
+ end
370
+ ptr.read_uint
371
+ end
372
+
373
+ def load_user_profile(token, profile_ptr)
374
+ unless LoadUserProfileW(token, profile_ptr)
375
+ raise SystemCallError.new("LoadUserProfileW", FFI.errno)
376
+ end
377
+ true
378
+ end
379
+
380
+ def unload_user_profile(token, profile)
381
+ if profile[:hProfile] == 0
382
+ warn "\n\nWARNING: Profile not loaded\n"
383
+ else
384
+ unless UnloadUserProfile(token, profile[:hProfile])
385
+ raise SystemCallError.new("UnloadUserProfile", FFI.errno)
386
+ end
387
+ end
388
+ true
389
+ end
390
+
391
+ def create_process_as_user(token, app, cmd, process_security,
392
+ thread_security, inherit, creation_flags, env, cwd, startinfo, procinfo)
393
+
394
+ bool = CreateProcessAsUserW(
395
+ token, # User token handle
396
+ app, # App name
397
+ cmd, # Command line
398
+ process_security, # Process attributes
399
+ thread_security, # Thread attributes
400
+ inherit, # Inherit handles
401
+ creation_flags, # Creation Flags
402
+ env, # Environment
403
+ cwd, # Working directory
404
+ startinfo, # Startup Info
405
+ procinfo # Process Info
406
+ )
407
+
408
+ unless bool
409
+ msg = case FFI.errno
410
+ when ERROR_PRIVILEGE_NOT_HELD
411
+ [
412
+ %{CreateProcessAsUserW (User '%s' must hold the 'Replace a process},
413
+ %{level token' and 'Adjust Memory Quotas for a process' permissions.},
414
+ %{Logoff the user after adding this right to make it effective.)},
415
+ ].join(" ") % ::ENV["USERNAME"]
416
+ else
417
+ "CreateProcessAsUserW failed."
418
+ end
419
+ raise SystemCallError.new(msg, FFI.errno)
420
+ end
421
+ end
422
+
423
+ def create_process_with_logon(logon, domain, passwd, logon_flags, app, cmd,
424
+ creation_flags, env, cwd, startinfo, procinfo)
425
+
426
+ bool = CreateProcessWithLogonW(
427
+ logon, # User
428
+ domain, # Domain
429
+ passwd, # Password
430
+ logon_flags, # Logon flags
431
+ app, # App name
432
+ cmd, # Command line
433
+ creation_flags, # Creation flags
434
+ env, # Environment
435
+ cwd, # Working directory
436
+ startinfo, # Startup Info
437
+ procinfo # Process Info
438
+ )
439
+
440
+ unless bool
441
+ raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
442
+ end
443
+ end
444
+
445
+ def create_process(app, cmd, process_security, thread_security, inherit,
446
+ creation_flags, env, cwd, startinfo, procinfo)
447
+
448
+ bool = CreateProcessW(
449
+ app, # App name
450
+ cmd, # Command line
451
+ process_security, # Process attributes
452
+ thread_security, # Thread attributes
453
+ inherit, # Inherit handles?
454
+ creation_flags, # Creation flags
455
+ env, # Environment
456
+ cwd, # Working directory
457
+ startinfo, # Startup Info
458
+ procinfo # Process Info
459
+ )
460
+
461
+ unless bool
462
+ raise SystemCallError.new("CreateProcessW", FFI.errno)
463
+ end
323
464
  end
324
465
 
325
466
  def logon_user(user, domain, passwd, type, provider = LOGON32_PROVIDER_DEFAULT)
@@ -346,32 +487,6 @@ module Process
346
487
  token.read_ulong
347
488
  end
348
489
 
349
- def create_process_as_user(token, app, cmd, process_security, thread_security, inherit, creation_flags, env, cwd, startinfo, procinfo)
350
- bool = CreateProcessAsUserW(
351
- token, # User token handle
352
- app, # App name
353
- cmd, # Command line
354
- process_security, # Process attributes
355
- thread_security, # Thread attributes
356
- inherit, # Inherit handles
357
- creation_flags, # Creation Flags
358
- env, # Environment
359
- cwd, # Working directory
360
- startinfo, # Startup Info
361
- procinfo # Process Info
362
- )
363
-
364
- unless bool
365
- if FFI.errno == ERROR_PRIVILEGE_NOT_HELD
366
- 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)
367
- else
368
- raise SystemCallError.new("CreateProcessAsUserW failed.", FFI.errno)
369
- end
370
- end
371
- ensure
372
- CloseHandle(token)
373
- end
374
-
375
490
  def get_windows_station_name
376
491
  winsta_name = FFI::MemoryPointer.new(:char, 256)
377
492
  return_size = FFI::MemoryPointer.new(:ulong)
@@ -406,5 +521,6 @@ module Process
406
521
 
407
522
  [ logon, passwd, domain ]
408
523
  end
524
+
409
525
  end
410
526
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixlib-shellout
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.4
4
+ version: 3.0.4
5
5
  platform: universal-mingw32
6
6
  authors:
7
7
  - Chef Software Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-12 00:00:00.000000000 Z
11
+ date: 2019-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: win32-process
@@ -69,8 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
69
  - !ruby/object:Gem::Version
70
70
  version: '0'
71
71
  requirements: []
72
- rubyforge_project:
73
- rubygems_version: 2.7.6
72
+ rubygems_version: 3.0.3
74
73
  signing_key:
75
74
  specification_version: 4
76
75
  summary: Run external commands on Unix or Windows