psrp 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|