mixlib-shellout 3.2.7-x64-mingw-ucrt
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/lib/mixlib/shellout/exceptions.rb +9 -0
- data/lib/mixlib/shellout/helper.rb +197 -0
- data/lib/mixlib/shellout/unix.rb +420 -0
- data/lib/mixlib/shellout/version.rb +5 -0
- data/lib/mixlib/shellout/windows/core_ext.rb +629 -0
- data/lib/mixlib/shellout/windows.rb +431 -0
- data/lib/mixlib/shellout.rb +373 -0
- metadata +106 -0
@@ -0,0 +1,629 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Daniel DeLeo (<dan@chef.io>)
|
3
|
+
# Author:: John Keiser (<jkeiser@chef.io>)
|
4
|
+
# Copyright:: Copyright (c) Chef Software Inc.
|
5
|
+
# License:: Apache License, Version 2.0
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
require "win32/process"
|
21
|
+
require "ffi/win32/extensions"
|
22
|
+
|
23
|
+
# Add new constants for Logon
|
24
|
+
module Process::Constants
|
25
|
+
|
26
|
+
LOGON32_LOGON_INTERACTIVE = 0x00000002
|
27
|
+
LOGON32_LOGON_BATCH = 0x00000004
|
28
|
+
LOGON32_PROVIDER_DEFAULT = 0x00000000
|
29
|
+
UOI_NAME = 0x00000002
|
30
|
+
|
31
|
+
WAIT_OBJECT_0 = 0
|
32
|
+
WAIT_TIMEOUT = 0x102
|
33
|
+
WAIT_ABANDONED = 128
|
34
|
+
WAIT_ABANDONED_0 = WAIT_ABANDONED
|
35
|
+
WAIT_FAILED = 0xFFFFFFFF
|
36
|
+
|
37
|
+
ERROR_PRIVILEGE_NOT_HELD = 1314
|
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
|
+
# The environment block list ends with two nulls (\0\0).
|
49
|
+
ENVIRONMENT_BLOCK_ENDS = "\0\0".freeze
|
50
|
+
end
|
51
|
+
|
52
|
+
# Structs required for data handling
|
53
|
+
module Process::Structs
|
54
|
+
|
55
|
+
class PROFILEINFO < FFI::Struct
|
56
|
+
layout(
|
57
|
+
:dwSize, :dword,
|
58
|
+
:dwFlags, :dword,
|
59
|
+
:lpUserName, :pointer,
|
60
|
+
:lpProfilePath, :pointer,
|
61
|
+
:lpDefaultPath, :pointer,
|
62
|
+
:lpServerName, :pointer,
|
63
|
+
:lpPolicyPath, :pointer,
|
64
|
+
:hProfile, :handle
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
# Define the functions needed to check with Service windows station
|
71
|
+
module Process::Functions
|
72
|
+
ffi_lib :userenv
|
73
|
+
|
74
|
+
attach_pfunc :GetProfileType,
|
75
|
+
[:pointer], :bool
|
76
|
+
|
77
|
+
attach_pfunc :LoadUserProfileW,
|
78
|
+
%i{handle pointer}, :bool
|
79
|
+
|
80
|
+
attach_pfunc :UnloadUserProfile,
|
81
|
+
%i{handle handle}, :bool
|
82
|
+
|
83
|
+
attach_pfunc :CreateEnvironmentBlock,
|
84
|
+
%i{pointer ulong bool}, :bool
|
85
|
+
|
86
|
+
attach_pfunc :DestroyEnvironmentBlock,
|
87
|
+
%i{pointer}, :bool
|
88
|
+
|
89
|
+
ffi_lib :advapi32
|
90
|
+
|
91
|
+
attach_pfunc :LogonUserW,
|
92
|
+
%i{buffer_in buffer_in buffer_in ulong ulong pointer}, :bool
|
93
|
+
|
94
|
+
attach_pfunc :CreateProcessAsUserW,
|
95
|
+
%i{ulong buffer_in buffer_inout pointer pointer int
|
96
|
+
ulong buffer_in buffer_in pointer pointer}, :bool
|
97
|
+
|
98
|
+
ffi_lib :user32
|
99
|
+
|
100
|
+
attach_pfunc :GetProcessWindowStation,
|
101
|
+
[], :ulong
|
102
|
+
|
103
|
+
attach_pfunc :GetUserObjectInformationA,
|
104
|
+
%i{ulong uint buffer_out ulong pointer}, :bool
|
105
|
+
end
|
106
|
+
|
107
|
+
# Override Process.create to check for running in the Service window station and doing
|
108
|
+
# a full logon with LogonUser, instead of a CreateProcessWithLogon
|
109
|
+
# Cloned from https://github.com/djberg96/win32-process/blob/ffi/lib/win32/process.rb
|
110
|
+
# as of 2015-10-15 from commit cc066e5df25048f9806a610f54bf5f7f253e86f7
|
111
|
+
module Process
|
112
|
+
|
113
|
+
class UnsupportedFeature < StandardError; end
|
114
|
+
|
115
|
+
# Explicitly reopen singleton class so that class/constant declarations from
|
116
|
+
# extensions are visible in Modules.nesting.
|
117
|
+
class << self
|
118
|
+
|
119
|
+
def create(args)
|
120
|
+
create3(args).first
|
121
|
+
end
|
122
|
+
|
123
|
+
def create3(args)
|
124
|
+
unless args.is_a?(Hash)
|
125
|
+
raise TypeError, "hash keyword arguments expected"
|
126
|
+
end
|
127
|
+
|
128
|
+
valid_keys = %w{
|
129
|
+
app_name command_line inherit creation_flags cwd environment
|
130
|
+
startup_info thread_inherit process_inherit close_handles with_logon
|
131
|
+
domain password elevated
|
132
|
+
}
|
133
|
+
|
134
|
+
valid_si_keys = %w{
|
135
|
+
startf_flags desktop title x y x_size y_size x_count_chars
|
136
|
+
y_count_chars fill_attribute sw_flags stdin stdout stderr
|
137
|
+
}
|
138
|
+
|
139
|
+
# Set default values
|
140
|
+
hash = {
|
141
|
+
"app_name" => nil,
|
142
|
+
"creation_flags" => 0,
|
143
|
+
"close_handles" => true,
|
144
|
+
}
|
145
|
+
|
146
|
+
# Validate the keys, and convert symbols and case to lowercase strings.
|
147
|
+
args.each do |key, val|
|
148
|
+
key = key.to_s.downcase
|
149
|
+
unless valid_keys.include?(key)
|
150
|
+
raise ArgumentError, "invalid key '#{key}'"
|
151
|
+
end
|
152
|
+
|
153
|
+
hash[key] = val
|
154
|
+
end
|
155
|
+
|
156
|
+
si_hash = {}
|
157
|
+
|
158
|
+
# If the startup_info key is present, validate its subkeys
|
159
|
+
hash["startup_info"]&.each do |key, val|
|
160
|
+
key = key.to_s.downcase
|
161
|
+
unless valid_si_keys.include?(key)
|
162
|
+
raise ArgumentError, "invalid startup_info key '#{key}'"
|
163
|
+
end
|
164
|
+
|
165
|
+
si_hash[key] = val
|
166
|
+
end
|
167
|
+
|
168
|
+
# The +command_line+ key is mandatory unless the +app_name+ key
|
169
|
+
# is specified.
|
170
|
+
unless hash["command_line"]
|
171
|
+
if hash["app_name"]
|
172
|
+
hash["command_line"] = hash["app_name"]
|
173
|
+
hash["app_name"] = nil
|
174
|
+
else
|
175
|
+
raise ArgumentError, "command_line or app_name must be specified"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
env = nil
|
180
|
+
|
181
|
+
# Retrieve the environment variables for the specified user.
|
182
|
+
if hash["with_logon"]
|
183
|
+
logon, passwd, domain = format_creds_from_hash(hash)
|
184
|
+
logon_type = hash["elevated"] ? LOGON32_LOGON_BATCH : LOGON32_LOGON_INTERACTIVE
|
185
|
+
token = logon_user(logon, domain, passwd, logon_type)
|
186
|
+
logon_ptr = FFI::MemoryPointer.from_string(logon)
|
187
|
+
profile = PROFILEINFO.new.tap do |dat|
|
188
|
+
dat[:dwSize] = dat.size
|
189
|
+
dat[:dwFlags] = 1
|
190
|
+
dat[:lpUserName] = logon_ptr
|
191
|
+
end
|
192
|
+
|
193
|
+
load_user_profile(token, profile.pointer)
|
194
|
+
env_list = retrieve_environment_variables(token)
|
195
|
+
end
|
196
|
+
|
197
|
+
# The env string should be passed as a string of ';' separated paths.
|
198
|
+
if hash["environment"]
|
199
|
+
env = env_list.nil? ? hash["environment"] : merge_env_variables(env_list, hash["environment"])
|
200
|
+
|
201
|
+
unless env.respond_to?(:join)
|
202
|
+
env = hash["environment"].split(File::PATH_SEPARATOR)
|
203
|
+
end
|
204
|
+
|
205
|
+
env = env.map { |e| e + 0.chr }.join("") + 0.chr
|
206
|
+
env.to_wide_string! if hash["with_logon"]
|
207
|
+
end
|
208
|
+
|
209
|
+
# Process SECURITY_ATTRIBUTE structure
|
210
|
+
process_security = nil
|
211
|
+
|
212
|
+
if hash["process_inherit"]
|
213
|
+
process_security = SECURITY_ATTRIBUTES.new
|
214
|
+
process_security[:nLength] = 12
|
215
|
+
process_security[:bInheritHandle] = 1
|
216
|
+
end
|
217
|
+
|
218
|
+
# Thread SECURITY_ATTRIBUTE structure
|
219
|
+
thread_security = nil
|
220
|
+
|
221
|
+
if hash["thread_inherit"]
|
222
|
+
thread_security = SECURITY_ATTRIBUTES.new
|
223
|
+
thread_security[:nLength] = 12
|
224
|
+
thread_security[:bInheritHandle] = 1
|
225
|
+
end
|
226
|
+
|
227
|
+
# Automatically handle stdin, stdout and stderr as either IO objects
|
228
|
+
# or file descriptors. This won't work for StringIO, however. It also
|
229
|
+
# will not work on JRuby because of the way it handles internal file
|
230
|
+
# descriptors.
|
231
|
+
#
|
232
|
+
%w{stdin stdout stderr}.each do |io|
|
233
|
+
if si_hash[io]
|
234
|
+
if si_hash[io].respond_to?(:fileno)
|
235
|
+
handle = get_osfhandle(si_hash[io].fileno)
|
236
|
+
else
|
237
|
+
handle = get_osfhandle(si_hash[io])
|
238
|
+
end
|
239
|
+
|
240
|
+
if handle == INVALID_HANDLE_VALUE
|
241
|
+
ptr = FFI::MemoryPointer.new(:int)
|
242
|
+
|
243
|
+
if windows_version >= 6 && get_errno(ptr) == 0
|
244
|
+
errno = ptr.read_int
|
245
|
+
else
|
246
|
+
errno = FFI.errno
|
247
|
+
end
|
248
|
+
|
249
|
+
raise SystemCallError.new("get_osfhandle", errno)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Most implementations of Ruby on Windows create inheritable
|
253
|
+
# handles by default, but some do not. RF bug #26988.
|
254
|
+
bool = SetHandleInformation(
|
255
|
+
handle,
|
256
|
+
HANDLE_FLAG_INHERIT,
|
257
|
+
HANDLE_FLAG_INHERIT
|
258
|
+
)
|
259
|
+
|
260
|
+
raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool
|
261
|
+
|
262
|
+
si_hash[io] = handle
|
263
|
+
si_hash["startf_flags"] ||= 0
|
264
|
+
si_hash["startf_flags"] |= STARTF_USESTDHANDLES
|
265
|
+
hash["inherit"] = true
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
procinfo = PROCESS_INFORMATION.new
|
270
|
+
startinfo = STARTUPINFO.new
|
271
|
+
|
272
|
+
unless si_hash.empty?
|
273
|
+
startinfo[:cb] = startinfo.size
|
274
|
+
startinfo[:lpDesktop] = si_hash["desktop"] if si_hash["desktop"]
|
275
|
+
startinfo[:lpTitle] = si_hash["title"] if si_hash["title"]
|
276
|
+
startinfo[:dwX] = si_hash["x"] if si_hash["x"]
|
277
|
+
startinfo[:dwY] = si_hash["y"] if si_hash["y"]
|
278
|
+
startinfo[:dwXSize] = si_hash["x_size"] if si_hash["x_size"]
|
279
|
+
startinfo[:dwYSize] = si_hash["y_size"] if si_hash["y_size"]
|
280
|
+
startinfo[:dwXCountChars] = si_hash["x_count_chars"] if si_hash["x_count_chars"]
|
281
|
+
startinfo[:dwYCountChars] = si_hash["y_count_chars"] if si_hash["y_count_chars"]
|
282
|
+
startinfo[:dwFillAttribute] = si_hash["fill_attribute"] if si_hash["fill_attribute"]
|
283
|
+
startinfo[:dwFlags] = si_hash["startf_flags"] if si_hash["startf_flags"]
|
284
|
+
startinfo[:wShowWindow] = si_hash["sw_flags"] if si_hash["sw_flags"]
|
285
|
+
startinfo[:cbReserved2] = 0
|
286
|
+
startinfo[:hStdInput] = si_hash["stdin"] if si_hash["stdin"]
|
287
|
+
startinfo[:hStdOutput] = si_hash["stdout"] if si_hash["stdout"]
|
288
|
+
startinfo[:hStdError] = si_hash["stderr"] if si_hash["stderr"]
|
289
|
+
end
|
290
|
+
|
291
|
+
app = nil
|
292
|
+
cmd = nil
|
293
|
+
|
294
|
+
# Convert strings to wide character strings if present
|
295
|
+
if hash["app_name"]
|
296
|
+
app = hash["app_name"].to_wide_string
|
297
|
+
end
|
298
|
+
|
299
|
+
if hash["command_line"]
|
300
|
+
cmd = hash["command_line"].to_wide_string
|
301
|
+
end
|
302
|
+
|
303
|
+
if hash["cwd"]
|
304
|
+
cwd = hash["cwd"].to_wide_string
|
305
|
+
end
|
306
|
+
|
307
|
+
inherit = hash["inherit"] ? 1 : 0
|
308
|
+
|
309
|
+
if hash["with_logon"]
|
310
|
+
|
311
|
+
logon, passwd, domain = format_creds_from_hash(hash)
|
312
|
+
|
313
|
+
hash["creation_flags"] |= CREATE_UNICODE_ENVIRONMENT
|
314
|
+
|
315
|
+
winsta_name = get_windows_station_name
|
316
|
+
|
317
|
+
# If running in the service windows station must do a log on to get
|
318
|
+
# to the interactive desktop. The running process user account must have
|
319
|
+
# the 'Replace a process level token' permission. This is necessary as
|
320
|
+
# the logon (which happens with CreateProcessWithLogon) must have an
|
321
|
+
# interactive windows station to attach to, which is created with the
|
322
|
+
# LogonUser call with the LOGON32_LOGON_INTERACTIVE flag.
|
323
|
+
#
|
324
|
+
# User Access Control (UAC) only applies to interactive logons, so we
|
325
|
+
# can simulate running a command 'elevated' by running it under a separate
|
326
|
+
# logon as a batch process.
|
327
|
+
if hash["elevated"] || winsta_name =~ /^Service-0x0-.*$/i
|
328
|
+
|
329
|
+
logon_type = hash["elevated"] ? LOGON32_LOGON_BATCH : LOGON32_LOGON_INTERACTIVE
|
330
|
+
token = logon_user(logon, domain, passwd, logon_type)
|
331
|
+
logon_ptr = FFI::MemoryPointer.from_string(logon)
|
332
|
+
profile = PROFILEINFO.new.tap do |dat|
|
333
|
+
dat[:dwSize] = dat.size
|
334
|
+
dat[:dwFlags] = 1
|
335
|
+
dat[:lpUserName] = logon_ptr
|
336
|
+
end
|
337
|
+
|
338
|
+
if logon_has_roaming_profile?
|
339
|
+
msg = %w{
|
340
|
+
Mixlib does not currently support executing commands as users
|
341
|
+
configured with Roaming Profiles. [%s]
|
342
|
+
}.join(" ") % logon.encode("UTF-8").unpack("A*")
|
343
|
+
raise UnsupportedFeature.new(msg)
|
344
|
+
end
|
345
|
+
|
346
|
+
load_user_profile(token, profile.pointer)
|
347
|
+
|
348
|
+
create_process_as_user(token, app, cmd, process_security,
|
349
|
+
thread_security, inherit, hash["creation_flags"], env,
|
350
|
+
cwd, startinfo, procinfo)
|
351
|
+
|
352
|
+
else
|
353
|
+
|
354
|
+
create_process_with_logon(logon, domain, passwd, LOGON_WITH_PROFILE,
|
355
|
+
app, cmd, hash["creation_flags"], env, cwd, startinfo, procinfo)
|
356
|
+
|
357
|
+
end
|
358
|
+
|
359
|
+
else
|
360
|
+
|
361
|
+
create_process(app, cmd, process_security, thread_security, inherit,
|
362
|
+
hash["creation_flags"], env, cwd, startinfo, procinfo)
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
# Automatically close the process and thread handles in the
|
367
|
+
# PROCESS_INFORMATION struct unless explicitly told not to.
|
368
|
+
if hash["close_handles"]
|
369
|
+
CloseHandle(procinfo[:hProcess])
|
370
|
+
CloseHandle(procinfo[:hThread])
|
371
|
+
# Clear these fields so callers don't attempt to close the handle
|
372
|
+
# which can result in the wrong handle being closed or an
|
373
|
+
# exception in some circumstances.
|
374
|
+
procinfo[:hProcess] = 0
|
375
|
+
procinfo[:hThread] = 0
|
376
|
+
end
|
377
|
+
|
378
|
+
process = ProcessInfo.new(
|
379
|
+
procinfo[:hProcess],
|
380
|
+
procinfo[:hThread],
|
381
|
+
procinfo[:dwProcessId],
|
382
|
+
procinfo[:dwThreadId]
|
383
|
+
)
|
384
|
+
|
385
|
+
[ process, profile, token ]
|
386
|
+
end
|
387
|
+
|
388
|
+
# See Process::Constants::WIN32_PROFILETYPE
|
389
|
+
def logon_has_roaming_profile?
|
390
|
+
get_profile_type >= 2
|
391
|
+
end
|
392
|
+
|
393
|
+
def get_profile_type
|
394
|
+
ptr = FFI::MemoryPointer.new(:uint)
|
395
|
+
unless GetProfileType(ptr)
|
396
|
+
raise SystemCallError.new("GetProfileType", FFI.errno)
|
397
|
+
end
|
398
|
+
|
399
|
+
ptr.read_uint
|
400
|
+
end
|
401
|
+
|
402
|
+
def load_user_profile(token, profile_ptr)
|
403
|
+
unless LoadUserProfileW(token, profile_ptr)
|
404
|
+
raise SystemCallError.new("LoadUserProfileW", FFI.errno)
|
405
|
+
end
|
406
|
+
|
407
|
+
true
|
408
|
+
end
|
409
|
+
|
410
|
+
def unload_user_profile(token, profile)
|
411
|
+
if profile[:hProfile] == 0
|
412
|
+
warn "\n\nWARNING: Profile not loaded\n"
|
413
|
+
else
|
414
|
+
unless UnloadUserProfile(token, profile[:hProfile])
|
415
|
+
raise SystemCallError.new("UnloadUserProfile", FFI.errno)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
true
|
419
|
+
end
|
420
|
+
|
421
|
+
# Retrieves the environment variables for the specified user.
|
422
|
+
#
|
423
|
+
# @param env_pointer [Pointer] The environment block is an array of null-terminated Unicode strings.
|
424
|
+
# @param token [Integer] User token handle.
|
425
|
+
# @return [Boolean] true if successfully retrieves the environment variables for the specified user.
|
426
|
+
#
|
427
|
+
def create_environment_block(env_pointer, token)
|
428
|
+
unless CreateEnvironmentBlock(env_pointer, token, false)
|
429
|
+
raise SystemCallError.new("CreateEnvironmentBlock", FFI.errno)
|
430
|
+
end
|
431
|
+
|
432
|
+
true
|
433
|
+
end
|
434
|
+
|
435
|
+
# Frees environment variables created by the CreateEnvironmentBlock function.
|
436
|
+
#
|
437
|
+
# @param env_pointer [Pointer] The environment block is an array of null-terminated Unicode strings.
|
438
|
+
# @return [Boolean] true if successfully frees environment variables created by the CreateEnvironmentBlock function.
|
439
|
+
#
|
440
|
+
def destroy_environment_block(env_pointer)
|
441
|
+
unless DestroyEnvironmentBlock(env_pointer)
|
442
|
+
raise SystemCallError.new("DestroyEnvironmentBlock", FFI.errno)
|
443
|
+
end
|
444
|
+
|
445
|
+
true
|
446
|
+
end
|
447
|
+
|
448
|
+
def create_process_as_user(token, app, cmd, process_security,
|
449
|
+
thread_security, inherit, creation_flags, env, cwd, startinfo, procinfo)
|
450
|
+
|
451
|
+
bool = CreateProcessAsUserW(
|
452
|
+
token, # User token handle
|
453
|
+
app, # App name
|
454
|
+
cmd, # Command line
|
455
|
+
process_security, # Process attributes
|
456
|
+
thread_security, # Thread attributes
|
457
|
+
inherit, # Inherit handles
|
458
|
+
creation_flags, # Creation Flags
|
459
|
+
env, # Environment
|
460
|
+
cwd, # Working directory
|
461
|
+
startinfo, # Startup Info
|
462
|
+
procinfo # Process Info
|
463
|
+
)
|
464
|
+
|
465
|
+
unless bool
|
466
|
+
msg = case FFI.errno
|
467
|
+
when ERROR_PRIVILEGE_NOT_HELD
|
468
|
+
[
|
469
|
+
%{CreateProcessAsUserW (User '%s' must hold the 'Replace a process},
|
470
|
+
%{level token' and 'Adjust Memory Quotas for a process' permissions.},
|
471
|
+
%{Logoff the user after adding this right to make it effective.)},
|
472
|
+
].join(" ") % ::ENV["USERNAME"]
|
473
|
+
else
|
474
|
+
"CreateProcessAsUserW failed."
|
475
|
+
end
|
476
|
+
raise SystemCallError.new(msg, FFI.errno)
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
def create_process_with_logon(logon, domain, passwd, logon_flags, app, cmd,
|
481
|
+
creation_flags, env, cwd, startinfo, procinfo)
|
482
|
+
|
483
|
+
bool = CreateProcessWithLogonW(
|
484
|
+
logon, # User
|
485
|
+
domain, # Domain
|
486
|
+
passwd, # Password
|
487
|
+
logon_flags, # Logon flags
|
488
|
+
app, # App name
|
489
|
+
cmd, # Command line
|
490
|
+
creation_flags, # Creation flags
|
491
|
+
env, # Environment
|
492
|
+
cwd, # Working directory
|
493
|
+
startinfo, # Startup Info
|
494
|
+
procinfo # Process Info
|
495
|
+
)
|
496
|
+
|
497
|
+
unless bool
|
498
|
+
raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
def create_process(app, cmd, process_security, thread_security, inherit,
|
503
|
+
creation_flags, env, cwd, startinfo, procinfo)
|
504
|
+
|
505
|
+
bool = CreateProcessW(
|
506
|
+
app, # App name
|
507
|
+
cmd, # Command line
|
508
|
+
process_security, # Process attributes
|
509
|
+
thread_security, # Thread attributes
|
510
|
+
inherit, # Inherit handles?
|
511
|
+
creation_flags, # Creation flags
|
512
|
+
env, # Environment
|
513
|
+
cwd, # Working directory
|
514
|
+
startinfo, # Startup Info
|
515
|
+
procinfo # Process Info
|
516
|
+
)
|
517
|
+
|
518
|
+
unless bool
|
519
|
+
raise SystemCallError.new("CreateProcessW", FFI.errno)
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
def logon_user(user, domain, passwd, type, provider = LOGON32_PROVIDER_DEFAULT)
|
524
|
+
token = FFI::MemoryPointer.new(:ulong)
|
525
|
+
|
526
|
+
bool = LogonUserW(
|
527
|
+
user, # User
|
528
|
+
domain, # Domain
|
529
|
+
passwd, # Password
|
530
|
+
type, # Logon Type
|
531
|
+
provider, # Logon Provider
|
532
|
+
token # User token handle
|
533
|
+
)
|
534
|
+
|
535
|
+
unless bool
|
536
|
+
if (FFI.errno == ERROR_LOGON_TYPE_NOT_GRANTED) && (type == LOGON32_LOGON_BATCH)
|
537
|
+
user_utf8 = user.encode( "UTF-8", invalid: :replace, undef: :replace, replace: "" ).delete("\0")
|
538
|
+
raise SystemCallError.new("LogonUserW (User '#{user_utf8}' must hold 'Log on as a batch job' permissions.)", FFI.errno)
|
539
|
+
else
|
540
|
+
raise SystemCallError.new("LogonUserW", FFI.errno)
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
token.read_ulong
|
545
|
+
end
|
546
|
+
|
547
|
+
def get_windows_station_name
|
548
|
+
winsta_name = FFI::MemoryPointer.new(:char, 256)
|
549
|
+
return_size = FFI::MemoryPointer.new(:ulong)
|
550
|
+
|
551
|
+
bool = GetUserObjectInformationA(
|
552
|
+
GetProcessWindowStation(), # Window station handle
|
553
|
+
UOI_NAME, # Information to get
|
554
|
+
winsta_name, # Buffer to receive information
|
555
|
+
winsta_name.size, # Size of buffer
|
556
|
+
return_size # Size filled into buffer
|
557
|
+
)
|
558
|
+
|
559
|
+
unless bool
|
560
|
+
raise SystemCallError.new("GetUserObjectInformationA", FFI.errno)
|
561
|
+
end
|
562
|
+
|
563
|
+
winsta_name.read_string(return_size.read_ulong)
|
564
|
+
end
|
565
|
+
|
566
|
+
def format_creds_from_hash(hash)
|
567
|
+
logon = hash["with_logon"].to_wide_string
|
568
|
+
|
569
|
+
if hash["password"]
|
570
|
+
passwd = hash["password"].to_wide_string
|
571
|
+
else
|
572
|
+
raise ArgumentError, "password must be specified if with_logon is used"
|
573
|
+
end
|
574
|
+
|
575
|
+
if hash["domain"]
|
576
|
+
domain = hash["domain"].to_wide_string
|
577
|
+
end
|
578
|
+
|
579
|
+
[ logon, passwd, domain ]
|
580
|
+
end
|
581
|
+
|
582
|
+
# Retrieves the environment variables for the specified user.
|
583
|
+
#
|
584
|
+
# @param token [Integer] User token handle.
|
585
|
+
# @return env_list [Array<String>] Environment variables of specified user.
|
586
|
+
#
|
587
|
+
def retrieve_environment_variables(token)
|
588
|
+
env_list = []
|
589
|
+
env_pointer = FFI::MemoryPointer.new(:pointer)
|
590
|
+
create_environment_block(env_pointer, token)
|
591
|
+
str_ptr = env_pointer.read_pointer
|
592
|
+
offset = 0
|
593
|
+
loop do
|
594
|
+
new_str_pointer = str_ptr + offset
|
595
|
+
break if new_str_pointer.read_string(2) == ENVIRONMENT_BLOCK_ENDS
|
596
|
+
|
597
|
+
environment = new_str_pointer.read_wstring
|
598
|
+
env_list << environment
|
599
|
+
offset = offset + environment.length * 2 + 2
|
600
|
+
end
|
601
|
+
|
602
|
+
# To free the buffer when we have finished with the environment block
|
603
|
+
destroy_environment_block(str_ptr)
|
604
|
+
env_list
|
605
|
+
end
|
606
|
+
|
607
|
+
# Merge environment variables of specified user and current environment variables.
|
608
|
+
#
|
609
|
+
# @param fetched_env [Array<String>] environment variables of specified user.
|
610
|
+
# @param current_env [Array<String>] current environment variables.
|
611
|
+
# @return [Array<String>] Merged environment variables.
|
612
|
+
#
|
613
|
+
def merge_env_variables(fetched_env, current_env)
|
614
|
+
env_hash_1 = environment_list_to_hash(fetched_env)
|
615
|
+
env_hash_2 = environment_list_to_hash(current_env)
|
616
|
+
merged_env = env_hash_2.merge(env_hash_1)
|
617
|
+
merged_env.map { |k, v| "#{k}=#{v}" }
|
618
|
+
end
|
619
|
+
|
620
|
+
# Convert an array to a hash.
|
621
|
+
#
|
622
|
+
# @param env_var [Array<String>] Environment variables.
|
623
|
+
# @return [Hash] Converted an array to hash.
|
624
|
+
#
|
625
|
+
def environment_list_to_hash(env_var)
|
626
|
+
Hash[ env_var.map { |pair| pair.split("=", 2) } ]
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|