psrp 0.0.1
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/.gitignore +3 -0
- data/DemoDLL_RemoteProcess.dll +0 -0
- data/Invoke-ReflectivePEInjection.ps1 +2952 -0
- data/LICENSE +202 -0
- data/README.md +16 -0
- data/lib/psrp.rb +175 -0
- data/lib/response_handler.rb +134 -0
- data/lib/transport.rb +208 -0
- data/lib/version.rb +7 -0
- data/lib/wsmv/command_output_processor.rb +153 -0
- data/lib/wsmv/commands/base.rb +293 -0
- data/lib/wsmv/commands/close_shell.rb +48 -0
- data/lib/wsmv/commands/create_pipeline.rb +64 -0
- data/lib/wsmv/commands/init_runspace_pool.rb +92 -0
- data/lib/wsmv/commands/receive.rb +81 -0
- data/lib/wsmv/commands/send_data.rb +64 -0
- data/lib/wsmv/psrp_message.rb +289 -0
- data/lib/wsmv/templates/create_pipeline.xml.erb +93 -0
- data/lib/wsmv/templates/init_runspacepool.xml.erb +221 -0
- data/lib/wsmv/templates/runspace_availability.xml.erb +5 -0
- data/lib/wsmv/templates/session_capability.xml.erb +7 -0
- data/psrp.gemspec +39 -0
- data/script.ps1 +2945 -0
- data/test_psrp.rb +32 -0
- metadata +181 -0
data/lib/transport.rb
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2016 Shawn Neal <sneal@sneal.net>
|
4
|
+
# Copyright 2016 Sam Oluwalana <soluwalana@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require_relative "response_handler"
|
20
|
+
|
21
|
+
require 'monitor'
|
22
|
+
|
23
|
+
module PSRP
|
24
|
+
module HTTP
|
25
|
+
|
26
|
+
# straight copy from WinRM
|
27
|
+
# NTLM/Negotiate, secure, HTTP transport
|
28
|
+
class Negotiate
|
29
|
+
|
30
|
+
DEFAULT_RECEIVE_TIMEOUT = 3600
|
31
|
+
|
32
|
+
def initialize(endpoint, user, pass, opts)
|
33
|
+
@endpoint = endpoint.is_a?(String) ? URI.parse(endpoint) : endpoint
|
34
|
+
@httpcli = HTTPClient.new(agent_name: 'Ruby PSRP Client')
|
35
|
+
@httpcli.receive_timeout = DEFAULT_RECEIVE_TIMEOUT
|
36
|
+
@logger = Logging.logger[self]
|
37
|
+
require 'rubyntlm'
|
38
|
+
#p "USING HTTP NEGOTIATE"
|
39
|
+
|
40
|
+
# Deleting SSPI authentication
|
41
|
+
auths = @httpcli.www_auth.instance_variable_get('@authenticator')
|
42
|
+
auths.delete_if { |i| i.is_a? HTTPClient::SSPINegotiateAuth }
|
43
|
+
|
44
|
+
user_parts = user.split('\\')
|
45
|
+
if(user_parts.length > 1)
|
46
|
+
opts[:domain] = user_parts[0]
|
47
|
+
user = user_parts[1]
|
48
|
+
end
|
49
|
+
|
50
|
+
@ntlmcli = Net::NTLM::Client.new(user, pass, opts)
|
51
|
+
@retryable = true
|
52
|
+
no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
|
53
|
+
@ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
|
54
|
+
@httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
# Disable SSL Peer Verification
|
59
|
+
def no_ssl_peer_verification!
|
60
|
+
@httpcli.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
61
|
+
end
|
62
|
+
|
63
|
+
# SSL Peer Fingerprint Verification prior to connecting
|
64
|
+
def ssl_peer_fingerprint_verification!
|
65
|
+
return unless @ssl_peer_fingerprint && ! @ssl_peer_fingerprint_verified
|
66
|
+
|
67
|
+
with_untrusted_ssl_connection do |connection|
|
68
|
+
connection_cert = connection.peer_cert_chain.last
|
69
|
+
verify_ssl_fingerprint(connection_cert)
|
70
|
+
end
|
71
|
+
@logger.info("initial ssl fingerprint #{@ssl_peer_fingerprint} verified\n")
|
72
|
+
@ssl_peer_fingerprint_verified = true
|
73
|
+
no_ssl_peer_verification!
|
74
|
+
end
|
75
|
+
|
76
|
+
# Connect without verification to retrieve untrusted ssl context
|
77
|
+
def with_untrusted_ssl_connection
|
78
|
+
noverify_peer_context = OpenSSL::SSL::SSLContext.new
|
79
|
+
noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
80
|
+
tcp_connection = TCPSocket.new(@endpoint.host, @endpoint.port)
|
81
|
+
begin
|
82
|
+
ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_context)
|
83
|
+
ssl_connection.connect
|
84
|
+
yield ssl_connection
|
85
|
+
ensure
|
86
|
+
tcp_connection.close
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# compare @ssl_peer_fingerprint to current ssl context
|
91
|
+
def verify_ssl_fingerprint(cert)
|
92
|
+
return unless @ssl_peer_fingerprint
|
93
|
+
conn_fingerprint = OpenSSL::Digest::SHA1.new(cert.to_der).to_s
|
94
|
+
return unless @ssl_peer_fingerprint.casecmp(conn_fingerprint) != 0
|
95
|
+
fail "ssl fingerprint mismatch!!!!\n"
|
96
|
+
end
|
97
|
+
|
98
|
+
# HTTP Client receive timeout. How long should a remote call wait for a
|
99
|
+
# for a response from WinRM?
|
100
|
+
def receive_timeout=(sec)
|
101
|
+
@httpcli.receive_timeout = sec
|
102
|
+
end
|
103
|
+
|
104
|
+
def receive_timeout
|
105
|
+
@httpcli.receive_timeout
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def send_request(message, auth_header = nil)
|
110
|
+
ssl_peer_fingerprint_verification!
|
111
|
+
auth_header = init_auth if @ntlmcli.session.nil?
|
112
|
+
|
113
|
+
original_length = message.length
|
114
|
+
|
115
|
+
emessage = @ntlmcli.session.seal_message message
|
116
|
+
signature = @ntlmcli.session.sign_message message
|
117
|
+
seal = "\x10\x00\x00\x00#{signature}#{emessage}"
|
118
|
+
|
119
|
+
hdr = {
|
120
|
+
"Content-Type" => "multipart/encrypted;protocol=\"application/HTTP-SPNEGO-session-encrypted\";boundary=\"Encrypted Boundary\""
|
121
|
+
}
|
122
|
+
hdr.merge!(auth_header) if auth_header
|
123
|
+
|
124
|
+
# p hdr
|
125
|
+
|
126
|
+
body = [
|
127
|
+
"--Encrypted Boundary",
|
128
|
+
"Content-Type: application/HTTP-SPNEGO-session-encrypted",
|
129
|
+
"OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{original_length}",
|
130
|
+
"--Encrypted Boundary",
|
131
|
+
"Content-Type: application/octet-stream",
|
132
|
+
"#{seal}--Encrypted Boundary--",
|
133
|
+
""
|
134
|
+
].join("\r\n")
|
135
|
+
|
136
|
+
@logger.debug("\nOut-Message\n\n")
|
137
|
+
doc = REXML::Document.new message
|
138
|
+
out = ""
|
139
|
+
doc.write(out, 2)
|
140
|
+
@logger.debug(out)
|
141
|
+
@logger.debug("\n\n")
|
142
|
+
|
143
|
+
resp = @httpcli.post(@endpoint, body, hdr)
|
144
|
+
verify_ssl_fingerprint(resp.peer_cert)
|
145
|
+
if resp.status == 401 && @retryable
|
146
|
+
@retryable = false
|
147
|
+
send_request(message, init_auth)
|
148
|
+
else
|
149
|
+
|
150
|
+
|
151
|
+
@retryable = true
|
152
|
+
decrypted_body = resp.body.empty? ? '' : decrypt(resp.body)
|
153
|
+
handler = PSRP::ResponseHandler.new(decrypted_body, resp.status)
|
154
|
+
data = handler.parse_to_xml()
|
155
|
+
|
156
|
+
@logger.debug("Response data\n\n")
|
157
|
+
doc = REXML::Document.new decrypted_body
|
158
|
+
out = ""
|
159
|
+
doc.write(out, 2)
|
160
|
+
@logger.debug(out)
|
161
|
+
|
162
|
+
data
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def decrypt(str)
|
169
|
+
str.force_encoding('BINARY')
|
170
|
+
str.sub!(/^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$/m, '\1')
|
171
|
+
|
172
|
+
signature = str[4..19]
|
173
|
+
message = @ntlmcli.session.unseal_message str[20..-1]
|
174
|
+
if @ntlmcli.session.verify_signature(signature, message)
|
175
|
+
message
|
176
|
+
else
|
177
|
+
raise PSRP::PSRPError, "Could not verify SOAP message."
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def init_auth
|
182
|
+
auth1 = @ntlmcli.init_context
|
183
|
+
hdr = {"Authorization" => "Negotiate #{auth1.encode64}",
|
184
|
+
"Content-Type" => "application/soap+xml;charset=UTF-8"
|
185
|
+
}
|
186
|
+
@logger.debug("Sending HTTP POST for Negotiate Authentication")
|
187
|
+
r = @httpcli.post(@endpoint, "", hdr)
|
188
|
+
verify_ssl_fingerprint(r.peer_cert)
|
189
|
+
itok = r.header["WWW-Authenticate"].pop.split.last
|
190
|
+
binding = r.peer_cert.nil? ? nil : Net::NTLM::ChannelBinding.create(r.peer_cert)
|
191
|
+
auth3 = @ntlmcli.init_context(itok, binding)
|
192
|
+
{ "Authorization" => "Negotiate #{auth3.encode64}" }
|
193
|
+
end
|
194
|
+
|
195
|
+
def log_soap_message(message)
|
196
|
+
return unless @logger.debug?
|
197
|
+
xml_msg = REXML::Document.new(message)
|
198
|
+
formatter = REXML::Formatters::Pretty.new(2)
|
199
|
+
formatter.compact = true
|
200
|
+
formatter.write(xml_msg, @logger)
|
201
|
+
@logger.debug("\n")
|
202
|
+
rescue StandardError => e
|
203
|
+
@logger.debug("Couldn't log SOAP request/response: #{e.message} - #{message}")
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
data/lib/version.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2016 Shawn Neal <sneal@sneal.net>
|
4
|
+
# Copyright 2016 Sam Oluwalana <soluwalana@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require_relative 'commands/receive'
|
19
|
+
|
20
|
+
module PSRP
|
21
|
+
|
22
|
+
module WSMV
|
23
|
+
# Class to handle getting all the output of a command until it completes
|
24
|
+
class CommandOutputProcessor
|
25
|
+
|
26
|
+
attr :msgs
|
27
|
+
|
28
|
+
INPUT_REQUIRED = [
|
29
|
+
PSRP::Message::MESSAGE_TYPES[:PIPELINE_HOST_CALL],
|
30
|
+
PSRP::Message::MESSAGE_TYPES[:RUNSPACEPOOL_HOST_CALL]
|
31
|
+
]
|
32
|
+
|
33
|
+
# Creates a new command output processor
|
34
|
+
# @param connection_opts [ConnectionOpts] The WinRM connection options
|
35
|
+
# @param transport [HttpTransport] The WinRM SOAP transport
|
36
|
+
# @param out_opts [Hash] Additional output options
|
37
|
+
def initialize(connection_opts, transport, out_opts = {})
|
38
|
+
@connection_opts = connection_opts
|
39
|
+
@transport = transport
|
40
|
+
@out_opts = out_opts
|
41
|
+
@has_error = false
|
42
|
+
@command_done = false
|
43
|
+
@input_required = false
|
44
|
+
@msgs = {}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Gets the command output from the remote shell
|
48
|
+
# @param shell_id [UUID] The remote shell id running the command
|
49
|
+
# @param command_id [UUID] The command id to get output for
|
50
|
+
# @param block Optional callback for any output
|
51
|
+
def command_output(shell_id, command_id, reset = false)
|
52
|
+
if reset == true
|
53
|
+
@has_error = false
|
54
|
+
@command_done = false
|
55
|
+
@input_required = false
|
56
|
+
end
|
57
|
+
|
58
|
+
out_message = command_output_message(shell_id, command_id)
|
59
|
+
resp_doc = send_get_output_message(out_message)
|
60
|
+
streams(resp_doc)
|
61
|
+
@command_done = REXML::XPath.match(
|
62
|
+
resp_doc,
|
63
|
+
"//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/" \
|
64
|
+
"CommandState/Done']").any?
|
65
|
+
resp_doc
|
66
|
+
end
|
67
|
+
|
68
|
+
def command_output_message(shell_id, command_id)
|
69
|
+
cmd_out_opts = {
|
70
|
+
shell_id: shell_id
|
71
|
+
}.merge(@out_opts)
|
72
|
+
if command_id
|
73
|
+
cmd_out_opts[:command_id] = command_id
|
74
|
+
end
|
75
|
+
PSRP::WSMV::ReceiveOutput.new(@connection_opts, cmd_out_opts).build
|
76
|
+
end
|
77
|
+
|
78
|
+
def send_get_output_message(message)
|
79
|
+
resp_doc = @transport.send_request(message)
|
80
|
+
rescue PSRP::WSManFault => e
|
81
|
+
# If no output is available before the wsman:OperationTimeout expires,
|
82
|
+
# the server MUST return a WSManFault with the Code attribute equal to
|
83
|
+
# 2150858793. When the client receives this fault, it SHOULD issue
|
84
|
+
# another Receive request.
|
85
|
+
# http://msdn.microsoft.com/en-us/library/cc251676.aspx
|
86
|
+
if e.fault_code == '2150858793'
|
87
|
+
retry
|
88
|
+
else
|
89
|
+
raise
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def exit_code(resp_doc)
|
94
|
+
REXML::XPath.first(resp_doc, "//#{NS_WIN_SHELL}:ExitCode").text.to_i
|
95
|
+
end
|
96
|
+
|
97
|
+
def streams(resp_doc)
|
98
|
+
REXML::XPath.match(resp_doc, "//#{PSRP::WSMV::NS_WIN_SHELL}:Stream").each do |n|
|
99
|
+
next if n.text.nil? || n.text.empty?
|
100
|
+
msg = decode(n.text)
|
101
|
+
if not @msgs.has_key? msg.message_id
|
102
|
+
@msgs[msg.message_id] = []
|
103
|
+
end
|
104
|
+
@msgs[msg.message_id].push(msg)
|
105
|
+
if INPUT_REQUIRED.include? msg.message_type
|
106
|
+
@input_required = true
|
107
|
+
elsif msg.message_type == PSRP::Message::MESSAGE_TYPES[:ERROR_RECORD]
|
108
|
+
@has_error = true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Defragment Messages
|
114
|
+
def defragmented
|
115
|
+
datas = {}
|
116
|
+
@msgs.each do |msg_id, msg_list|
|
117
|
+
msg_list.sort do |a, b|
|
118
|
+
a.fragment_id <=> b.fragment_id
|
119
|
+
end
|
120
|
+
unfragmented = msg_list.inject('') do |data, msg|
|
121
|
+
data += msg.data
|
122
|
+
end
|
123
|
+
datas[msg_id] = {
|
124
|
+
message_type: msg_list[0].message_type,
|
125
|
+
data: unfragmented,
|
126
|
+
}
|
127
|
+
end
|
128
|
+
datas
|
129
|
+
end
|
130
|
+
|
131
|
+
def has_error?
|
132
|
+
@has_error
|
133
|
+
end
|
134
|
+
|
135
|
+
def input_required?
|
136
|
+
@input_required
|
137
|
+
end
|
138
|
+
|
139
|
+
def command_done?
|
140
|
+
@command_done
|
141
|
+
end
|
142
|
+
|
143
|
+
# Decode the raw PSRP output into decoded and human consumable object
|
144
|
+
|
145
|
+
# @param raw_output [String] The raw encoded output
|
146
|
+
# @return [String] The decoded output
|
147
|
+
def decode(raw_output)
|
148
|
+
# TODO: Add better decoding based on MS-PSRP 2.2.5
|
149
|
+
PSRP::MessageDecoder.new(raw_output)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,293 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2016 Shawn Neal <sneal@sneal.net>
|
4
|
+
# Copyright 2016 Sam Oluwalana <soluwalana@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require_relative '../psrp_message'
|
19
|
+
|
20
|
+
module Iso8601Duration
|
21
|
+
# Convert the number of seconds to an ISO8601 duration format
|
22
|
+
# @see http://tools.ietf.org/html/rfc2445#section-4.3.6
|
23
|
+
# @param [Fixnum] seconds The amount of seconds for this duration
|
24
|
+
def self.sec_to_dur(seconds)
|
25
|
+
seconds = seconds.to_i
|
26
|
+
iso_str = 'P'
|
27
|
+
if seconds > 604_800 # more than a week
|
28
|
+
weeks = seconds / 604_800
|
29
|
+
seconds -= (604_800 * weeks)
|
30
|
+
iso_str << "#{weeks}W"
|
31
|
+
end
|
32
|
+
if seconds > 86_400 # more than a day
|
33
|
+
days = seconds / 86_400
|
34
|
+
seconds -= (86_400 * days)
|
35
|
+
iso_str << "#{days}D"
|
36
|
+
end
|
37
|
+
if seconds > 0
|
38
|
+
iso_str << 'T'
|
39
|
+
if seconds > 3600 # more than an hour
|
40
|
+
hours = seconds / 3600
|
41
|
+
seconds -= (3600 * hours)
|
42
|
+
iso_str << "#{hours}H"
|
43
|
+
end
|
44
|
+
if seconds > 60 # more than a minute
|
45
|
+
minutes = seconds / 60
|
46
|
+
seconds -= (60 * minutes)
|
47
|
+
iso_str << "#{minutes}M"
|
48
|
+
end
|
49
|
+
iso_str << "#{seconds}S"
|
50
|
+
end
|
51
|
+
|
52
|
+
iso_str
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module PSRP
|
57
|
+
module WSMV
|
58
|
+
# Base class for all WSMV message classes
|
59
|
+
|
60
|
+
NS_SOAP_ENV = 's' # http://www.w3.org/2003/05/soap-envelope
|
61
|
+
NS_ADDRESSING = 'a' # http://schemas.xmlsoap.org/ws/2004/08/addressing
|
62
|
+
NS_CIMBINDING = 'b' # http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd
|
63
|
+
NS_ENUM = 'n' # http://schemas.xmlsoap.org/ws/2004/09/enumeration
|
64
|
+
NS_TRANSFER = 'x' # http://schemas.xmlsoap.org/ws/2004/09/transfer
|
65
|
+
NS_WSMAN_DMTF = 'w' # http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
|
66
|
+
NS_WSMAN_MSFT = 'p' # http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd
|
67
|
+
NS_SCHEMA_INST = 'xsi' # http://www.w3.org/2001/XMLSchema-instance
|
68
|
+
NS_WIN_SHELL = 'rsp' # http://schemas.microsoft.com/wbem/wsman/1/windows/shell
|
69
|
+
NS_WSMAN_FAULT = 'f' # http://schemas.microsoft.com/wbem/wsman/1/wsmanfault
|
70
|
+
NS_WSMAN_CONF = 'cfg' # http://schemas.microsoft.com/wbem/wsman/1/config
|
71
|
+
|
72
|
+
# WSMan URI of the regular Windows cmd shell
|
73
|
+
RESOURCE_URI_CMD = 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd'
|
74
|
+
|
75
|
+
# WSMan URI for PowerShell
|
76
|
+
RESOURCE_URI_POWERSHELL = 'http://schemas.microsoft.com/powershell/Microsoft.PowerShell'
|
77
|
+
|
78
|
+
class Base
|
79
|
+
|
80
|
+
# Builds the WSMV message XML payload
|
81
|
+
def build
|
82
|
+
builder = Builder::XmlMarkup.new
|
83
|
+
builder.instruct!(:xml, encoding: 'UTF-8')
|
84
|
+
builder.tag! :env, :Envelope, namespaces do |env|
|
85
|
+
env.tag!(:env, :Header) do |env_header|
|
86
|
+
create_header(env_header)
|
87
|
+
end
|
88
|
+
env.tag!(:env, :Body) do |env_body|
|
89
|
+
create_body(env_body)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def namespaces
|
95
|
+
@namespaces ||= {
|
96
|
+
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
|
97
|
+
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
|
98
|
+
'xmlns:env' => 'http://www.w3.org/2003/05/soap-envelope',
|
99
|
+
"xmlns:#{NS_ADDRESSING}" => 'http://schemas.xmlsoap.org/ws/2004/08/addressing',
|
100
|
+
"xmlns:#{NS_CIMBINDING}" => 'http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd',
|
101
|
+
"xmlns:#{NS_ENUM}" => 'http://schemas.xmlsoap.org/ws/2004/09/enumeration',
|
102
|
+
"xmlns:#{NS_TRANSFER}" => 'http://schemas.xmlsoap.org/ws/2004/09/transfer',
|
103
|
+
"xmlns:#{NS_WSMAN_DMTF}" => 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd',
|
104
|
+
"xmlns:#{NS_WSMAN_MSFT}" => 'http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd',
|
105
|
+
"xmlns:#{NS_WIN_SHELL}" => 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell',
|
106
|
+
"xmlns:#{NS_WSMAN_CONF}" => 'http://schemas.microsoft.com/wbem/wsman/1/config'
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
protected
|
111
|
+
|
112
|
+
def create_header
|
113
|
+
fail 'create_header must be implemented'
|
114
|
+
end
|
115
|
+
|
116
|
+
def create_body
|
117
|
+
fail 'create_body must be implemented'
|
118
|
+
end
|
119
|
+
|
120
|
+
def encode_bytes(bytes)
|
121
|
+
Base64.strict_encode64(bytes.pack('C*'))
|
122
|
+
end
|
123
|
+
|
124
|
+
# Merge the various header hashes and make sure we carry all of the attributes
|
125
|
+
# through instead of overwriting them.
|
126
|
+
def merge_headers(*headers)
|
127
|
+
hdr = {}
|
128
|
+
headers.each do |h|
|
129
|
+
hdr.merge!(h) do |k, v1, v2|
|
130
|
+
v1.merge!(v2) if k == :attributes!
|
131
|
+
end
|
132
|
+
end
|
133
|
+
hdr
|
134
|
+
end
|
135
|
+
|
136
|
+
def shared_headers(session_opts)
|
137
|
+
{
|
138
|
+
"#{NS_ADDRESSING}:To" => "#{session_opts[:endpoint]}",
|
139
|
+
"#{NS_ADDRESSING}:ReplyTo" => {
|
140
|
+
"#{NS_ADDRESSING}:Address" =>
|
141
|
+
'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous',
|
142
|
+
:attributes! => {
|
143
|
+
"#{NS_ADDRESSING}:Address" => {
|
144
|
+
'mustUnderstand' => true
|
145
|
+
}
|
146
|
+
}
|
147
|
+
},
|
148
|
+
"#{NS_WSMAN_DMTF}:MaxEnvelopeSize" => session_opts[:max_envelope_size],
|
149
|
+
"#{NS_ADDRESSING}:MessageID" => "uuid:#{SecureRandom.uuid.to_s.upcase}",
|
150
|
+
"#{NS_WSMAN_MSFT}:SessionId" => "uuid:#{session_opts[:session_id]}",
|
151
|
+
"#{NS_WSMAN_DMTF}:Locale/" => '',
|
152
|
+
"#{NS_WSMAN_MSFT}:DataLocale/" => '',
|
153
|
+
# "#{NS_WSMAN_DMTF}:OperationTimeout" =>
|
154
|
+
# Iso8601Duration.sec_to_dur(session_opts[:operation_timeout]),
|
155
|
+
:attributes! => {
|
156
|
+
"#{NS_WSMAN_DMTF}:MaxEnvelopeSize" => { 'mustUnderstand' => true },
|
157
|
+
"#{NS_WSMAN_DMTF}:Locale/" => {
|
158
|
+
'xml:lang' => session_opts[:locale], 'mustUnderstand' => false
|
159
|
+
},
|
160
|
+
"#{NS_WSMAN_MSFT}:DataLocale/" => {
|
161
|
+
'xml:lang' => session_opts[:locale], 'mustUnderstand' => false
|
162
|
+
},
|
163
|
+
"#{NS_WSMAN_MSFT}:SessionId" => { 'mustUnderstand' => false }
|
164
|
+
}
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
# Helper methods for SOAP Headers
|
169
|
+
|
170
|
+
def resource_uri_shell(shell_uri)
|
171
|
+
{
|
172
|
+
"#{NS_WSMAN_DMTF}:ResourceURI" => shell_uri, :attributes! => {
|
173
|
+
"#{NS_WSMAN_DMTF}:ResourceURI" => {
|
174
|
+
'mustUnderstand' => true
|
175
|
+
}
|
176
|
+
}
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
def resource_uri_cmd
|
181
|
+
resource_uri_shell(RESOURCE_URI_CMD)
|
182
|
+
end
|
183
|
+
|
184
|
+
def resource_uri_wmi(namespace = 'root/cimv2/*')
|
185
|
+
{
|
186
|
+
"#{NS_WSMAN_DMTF}:ResourceURI" =>
|
187
|
+
"http://schemas.microsoft.com/wbem/wsman/1/wmi/#{namespace}",
|
188
|
+
:attributes! => {
|
189
|
+
"#{NS_WSMAN_DMTF}:ResourceURI" => {
|
190
|
+
'mustUnderstand' => true
|
191
|
+
}
|
192
|
+
}
|
193
|
+
}
|
194
|
+
end
|
195
|
+
|
196
|
+
def action_create
|
197
|
+
{
|
198
|
+
"#{NS_ADDRESSING}:Action" =>
|
199
|
+
'http://schemas.xmlsoap.org/ws/2004/09/transfer/Create',
|
200
|
+
:attributes! => {
|
201
|
+
"#{NS_ADDRESSING}:Action" => {
|
202
|
+
'mustUnderstand' => true
|
203
|
+
}
|
204
|
+
}
|
205
|
+
}
|
206
|
+
end
|
207
|
+
|
208
|
+
def action_delete
|
209
|
+
{
|
210
|
+
"#{NS_ADDRESSING}:Action" =>
|
211
|
+
'http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete',
|
212
|
+
:attributes! => {
|
213
|
+
"#{NS_ADDRESSING}:Action" => {
|
214
|
+
'mustUnderstand' => true
|
215
|
+
}
|
216
|
+
}
|
217
|
+
}
|
218
|
+
end
|
219
|
+
|
220
|
+
def action_command
|
221
|
+
{
|
222
|
+
"#{NS_ADDRESSING}:Action" =>
|
223
|
+
'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command',
|
224
|
+
:attributes! => {
|
225
|
+
"#{NS_ADDRESSING}:Action" => {
|
226
|
+
'mustUnderstand' => true
|
227
|
+
}
|
228
|
+
}
|
229
|
+
}
|
230
|
+
end
|
231
|
+
|
232
|
+
def action_send
|
233
|
+
{
|
234
|
+
"#{NS_ADDRESSING}:Action" =>
|
235
|
+
'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send',
|
236
|
+
:attributes! => {
|
237
|
+
"#{NS_ADDRESSING}:Action" => {
|
238
|
+
'mustUnderstand' => true
|
239
|
+
}
|
240
|
+
}
|
241
|
+
}
|
242
|
+
end
|
243
|
+
|
244
|
+
def action_receive
|
245
|
+
{
|
246
|
+
"#{NS_ADDRESSING}:Action" =>
|
247
|
+
'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive',
|
248
|
+
:attributes! => {
|
249
|
+
"#{NS_ADDRESSING}:Action" => {
|
250
|
+
'mustUnderstand' => true
|
251
|
+
}
|
252
|
+
}
|
253
|
+
}
|
254
|
+
end
|
255
|
+
|
256
|
+
def action_signal
|
257
|
+
{
|
258
|
+
"#{NS_ADDRESSING}:Action" =>
|
259
|
+
'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal',
|
260
|
+
:attributes! => {
|
261
|
+
"#{NS_ADDRESSING}:Action" => {
|
262
|
+
'mustUnderstand' => true
|
263
|
+
}
|
264
|
+
}
|
265
|
+
}
|
266
|
+
end
|
267
|
+
|
268
|
+
def action_enumerate
|
269
|
+
{
|
270
|
+
"#{NS_ADDRESSING}:Action" =>
|
271
|
+
'http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate',
|
272
|
+
:attributes! => {
|
273
|
+
"#{NS_ADDRESSING}:Action" => {
|
274
|
+
'mustUnderstand' => true
|
275
|
+
}
|
276
|
+
}
|
277
|
+
}
|
278
|
+
end
|
279
|
+
|
280
|
+
def selector_shell_id(shell_id)
|
281
|
+
{
|
282
|
+
"#{NS_WSMAN_DMTF}:SelectorSet" => {
|
283
|
+
"#{NS_WSMAN_DMTF}:Selector" => shell_id, :attributes! => {
|
284
|
+
"#{NS_WSMAN_DMTF}:Selector" => {
|
285
|
+
'Name' => 'ShellId'
|
286
|
+
}
|
287
|
+
}
|
288
|
+
}
|
289
|
+
}
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|