GCC Code Coverage Report


Directory: src/gate/
File: src/gate/net/winrmclients.c
Date: 2026-06-15 21:53:51
Exec Total Coverage
Lines: 0 454 0.0%
Functions: 0 31 0.0%
Branches: 0 180 0.0%

Line Branch Exec Source
1 /* GATE PROJECT LICENSE:
2 +----------------------------------------------------------------------------+
3 | Copyright (c) 2018-2026, Stefan Meislinger <sm@opengate.at> |
4 | All rights reserved. |
5 | |
6 | Redistribution and use in source and binary forms, with or without |
7 | modification, are permitted provided that the following conditions are met:|
8 | |
9 | 1. Redistributions of source code must retain the above copyright notice, |
10 | this list of conditions and the following disclaimer. |
11 | 2. Redistributions in binary form must reproduce the above copyright |
12 | notice, this list of conditions and the following disclaimer in the |
13 | documentation and/or other materials provided with the distribution. |
14 | |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"|
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
18 | ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
25 | THE POSSIBILITY OF SUCH DAMAGE. |
26 +----------------------------------------------------------------------------+
27 */
28
29 #include "gate/net/winrmclients.h"
30 #include "gate/results.h"
31 #include "gate/debugging.h"
32 #include "gate/uris.h"
33 #include "gate/net/httpclients.h"
34 #include "gate/encode/base64.h"
35 #include "gate/encode/xml.h"
36
37 #define WINRM_TOKEN_TARGET_URL "${WINRM_TARGET}"
38 #define WINRM_TOKEN_URI_SHELL_CMD "${WINRM_SHELL_CMD}"
39 #define WINRM_TOKEN_MESSAGE_ID "${WINRM_MESSAGE_ID}"
40 #define WINRM_TOKEN_WORKDIR "${WINRM_WORKDIR}"
41 #define WINRM_TOKEN_SHELL_UID "${WINRM_SHELL_UID}"
42 #define WINRM_TOKEN_SHELL_COMMAND "${WINRM_SHELL_COMMAND}"
43 #define WINRM_TOKEN_SHELL_ARG "${WINRM_SHELL_ARG}"
44 #define WINRM_TOKEN_SHELL_ARGS "${WINRM_SHELL_ARGS}"
45 #define WINRM_TOKEN_COMMAND_UID "${WINRM_COMMAND_UID}"
46 #define WINRM_TOKEN_COMMAND_INPUT "${WINRM_COMMAND_INPUT}"
47 #define WINRM_TOKEN_TIMEOUT "${WINRM_TOKEN_TIMEOUT}"
48
49 /* https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/7f4a1f31-47d8-4599-a23b-c3834ffae21f */
50
51 ✗ static gate_bool_t replace_token_raw(gate_strbuilder_t* builder, char const* token, gate_string_t const* value)
52 {
53 ✗ gate_size_t const replace_count = gate_strbuilder_replace_str(
54 ✗ builder, token, gate_str_length(token), gate_string_ptr(value, 0), gate_string_length(value), 0, 16);
55 ✗ return replace_count > 0;
56 }
57
58 ✗ static void print_guid(gate_guid_t const* message_id, char* buffer)
59 {
60 ✗ if (message_id)
61 {
62 ✗ gate_guid_to_string(message_id, buffer);
63 }
64 else
65 {
66 gate_guid_t new_id;
67 ✗ gate_guid_generate(&new_id);
68 ✗ gate_guid_to_string(message_id, buffer);
69 }
70 ✗ }
71
72 ✗ static gate_bool_t replace_token_with_guid(gate_strbuilder_t* builder, char const* token, gate_guid_t const* guid)
73 {
74 char guid_text_buffer[128];
75 ✗ gate_string_t guid_str = GATE_STRING_INIT_EMPTY;
76
77 ✗ print_guid(guid, guid_text_buffer);
78 ✗ gate_string_create_static_len(&guid_str, guid_text_buffer, gate_str_length(guid_text_buffer));
79 ✗ return replace_token_raw(builder, token, &guid_str);
80 }
81
82 ✗ static gate_bool_t xml_encode(gate_string_t const* raw_input, gate_string_t* xml_output)
83 {
84 /* TODO */
85 ✗ return gate_string_duplicate(xml_output, raw_input) != NULL;
86 }
87
88 ✗ static gate_bool_t replace_token_with_xml_content(gate_strbuilder_t* builder, char const* token, gate_string_t const* value)
89 {
90 ✗ gate_bool_t ret = false;
91 gate_string_t xml;
92 ✗ if (xml_encode(value, &xml))
93 {
94 ✗ ret = replace_token_raw(builder, token, &xml);
95 ✗ gate_string_release(&xml);
96 }
97 ✗ return ret;
98 }
99
100 ✗ static gate_size_t winrm_print_timeout(gate_uint32_t timeout_ms, char* buffer, gate_size_t buffer_len)
101 {
102 gate_strbuilder_t builder;
103 ✗ const gate_real64_t timeout = (gate_real64_t)timeout_ms / 1000.0;
104
105 ✗ gate_strbuilder_create_static(&builder, buffer, buffer_len, 0);
106 ✗ gate_strbuilder_append_cstr(&builder, "PT");
107 ✗ gate_strbuilder_append_real(&builder, timeout, 0, 3, 0);
108 ✗ gate_strbuilder_append_cstr(&builder, "S");
109 ✗ return gate_strbuilder_length(&builder);
110 }
111
112 ✗ static gate_bool_t replace_token_with_timeout(gate_strbuilder_t* sb, char const* token, gate_uint32_t timeout_ms)
113 {
114 char buffer[64];
115 ✗ gate_size_t len = winrm_print_timeout(timeout_ms, buffer, sizeof(buffer));
116 ✗ gate_string_t content = { buffer, len, NULL };
117
118 ✗ return replace_token_raw(sb, token, &content);
119 }
120
121
122 ✗ static gate_bool_t extract_token(gate_string_t const* content,
123 gate_string_t const* begin_marker, gate_string_t const* end_marker,
124 gate_string_t* extracted_data)
125 {
126 gate_size_t pos_begin;
127 gate_size_t pos_end;
128
129 ✗ pos_begin = gate_string_pos(content, begin_marker, 0);
130 ✗ if (pos_begin == GATE_STR_NPOS)
131 {
132 ✗ return false;
133 }
134 ✗ pos_begin += gate_string_length(begin_marker);
135
136 ✗ pos_end = gate_string_pos(content, end_marker, pos_begin);
137 ✗ if (pos_begin == GATE_STR_NPOS)
138 {
139 ✗ return false;
140 }
141
142 ✗ return NULL != gate_string_substr(extracted_data, content, pos_begin, pos_end - pos_begin);
143 }
144
145 ✗ static gate_bool_t extract_xml_content(gate_string_t const* content,
146 gate_string_t const* begin_marker, gate_string_t const* end_marker,
147 gate_string_t* decoded_data)
148 {
149 ✗ gate_bool_t ret = false;
150 ✗ gate_string_t data = GATE_STRING_INIT_EMPTY;
151 ✗ if (extract_token(content, begin_marker, end_marker, &data))
152 {
153 /* TODO: XML decoding */
154 ✗ ret = NULL != gate_string_create_copy(decoded_data, &data);
155 ✗ gate_string_release(&data);
156 }
157 ✗ return ret;
158 }
159
160
161
162 static char const WINRM_SOAP_MSG_CREATESHELL_REQUEST[] =
163 "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:a=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
164 "<s:Header>"
165 "<a:To>" WINRM_TOKEN_TARGET_URL "</a:To>"
166 "<wsman:ResourceURI s:mustUnderstand=\"true\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</wsman:ResourceURI>"
167 "<a:ReplyTo><a:Address s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"
168 "<a:Action s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/09/transfer/Create</a:Action>"
169 "<wsman:MaxEnvelopeSize s:mustUnderstand=\"true\">153600</wsman:MaxEnvelopeSize>"
170 "<a:MessageID>uuid:" WINRM_TOKEN_MESSAGE_ID "</a:MessageID>"
171 "<wsman:Locale xml:lang=\"en-US\" s:mustUnderstand=\"false\" />"
172 "<wsman:OptionSet xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
173 "<wsman:Option Name=\"WINRS_NOPROFILE\">TRUE</wsman:Option>"
174 "<wsman:Option Name=\"WINRS_CODEPAGE\">437</wsman:Option>"
175 "</wsman:OptionSet>"
176 "<wsman:OperationTimeout>PT60.000S</wsman:OperationTimeout>"
177 "</s:Header>"
178 "<s:Body>"
179 "<rsp:Shell xmlns:rsp=\"http://schemas.microsoft.com/wbem/wsman/1/windows/shell\">"
180 /*
181 "<rsp:Environment>"
182 "<rsp:Variable Name=\"test\">1</rsp:Variable>"
183 "</rsp:Environment>"
184 */
185 WINRM_TOKEN_WORKDIR
186 "<rsp:InputStreams>stdin</rsp:InputStreams>"
187 "<rsp:OutputStreams>stdout stderr</rsp:OutputStreams>"
188 "</rsp:Shell>"
189 "</s:Body>"
190 "</s:Envelope>";
191
192 ✗ gate_string_t* winrm_create_shell_request(gate_string_t* output,
193 gate_string_t const* target_url,
194 gate_guid_t const* message_id,
195 gate_string_t const* work_dir)
196 {
197 ✗ gate_string_t* ret = NULL;
198 gate_strbuilder_t sb;
199
200 ✗ gate_strbuilder_create(&sb, 2048);
201 ✗ gate_strbuilder_append_text(&sb, WINRM_SOAP_MSG_CREATESHELL_REQUEST, sizeof(WINRM_SOAP_MSG_CREATESHELL_REQUEST) - 1);
202
203 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_TARGET_URL, target_url);
204 ✗ replace_token_with_guid(&sb, WINRM_TOKEN_MESSAGE_ID, message_id);
205 ✗ if (!gate_string_is_empty(work_dir))
206 {
207 ✗ gate_strbuilder_t wd_builder = GATE_INIT_EMPTY;
208 ✗ gate_string_t wd = GATE_STRING_INIT_EMPTY;
209 ✗ gate_strbuilder_create(&wd_builder, 128);
210 ✗ gate_strbuilder_append_cstr(&wd_builder, "<rsp:WorkingDirectory>");
211 ✗ gate_strbuilder_append_string(&wd_builder, work_dir);
212 ✗ gate_strbuilder_append_cstr(&wd_builder, "</rsp:WorkingDirectory>");
213 ✗ gate_strbuilder_to_string(&wd_builder, &wd);
214 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_WORKDIR, &wd);
215 ✗ gate_strbuilder_release(&wd_builder);
216 ✗ gate_string_release(&wd);
217 }
218
219 ✗ ret = gate_strbuilder_to_string(&sb, output);
220 ✗ gate_strbuilder_release(&sb);
221 ✗ return ret;
222 }
223
224
225 static char const WINRM_SOAP_MSG_EXECUTECOMMAND_REQUEST[] =
226 "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:a=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
227 "<s:Header>"
228 "<a:To>" WINRM_TOKEN_TARGET_URL "</a:To>"
229 "<wsman:ResourceURI s:mustUnderstand=\"true\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</wsman:ResourceURI>"
230 "<a:ReplyTo><a:Address s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"
231 "<a:Action s:mustUnderstand=\"true\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command</a:Action>"
232 "<wsman:MaxEnvelopeSize s:mustUnderstand=\"true\">153600</wsman:MaxEnvelopeSize>"
233 "<a:MessageID>uuid:" WINRM_TOKEN_MESSAGE_ID "</a:MessageID>"
234 "<wsman:Locale xml:lang=\"en-US\" s:mustUnderstand=\"false\" />"
235 "<wsman:SelectorSet><wsman:Selector Name=\"ShellId\">" WINRM_TOKEN_SHELL_UID "</wsman:Selector></wsman:SelectorSet>"
236 "<wsman:OptionSet xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
237 "<wsman:Option Name=\"WINRS_CONSOLEMODE_STDIN\">TRUE</wsman:Option>"
238 /* "<wsman:Option Name=\"WINRS_SKIP_CMD_SHELL\">FALSE</wsman:Option>" */
239 "</wsman:OptionSet>"
240 "<wsman:OperationTimeout>PT60.000S</wsman:OperationTimeout>"
241 "</s:Header>"
242 "<s:Body>"
243 "<rsp:CommandLine xmlns:rsp=\"http://schemas.microsoft.com/wbem/wsman/1/windows/shell\">"
244 "<rsp:Command>" WINRM_TOKEN_SHELL_COMMAND "</rsp:Command>"
245 /* "<rsp:Arguments>" WINRM_TOKEN_SHELL_ARG "</rsp:Arguments>" */
246 WINRM_TOKEN_SHELL_ARGS
247 "</rsp:CommandLine>"
248 "</s:Body>"
249 "</s:Envelope>";
250
251 ✗ gate_string_t* winrm_shell_execute_request(gate_string_t* output,
252 gate_string_t const* target_url,
253 gate_guid_t const* message_id,
254 gate_string_t const* shell_uid,
255 gate_string_t const* shell_command,
256 gate_string_t const* shell_args, gate_size_t shell_args_count)
257 {
258 ✗ gate_string_t* ret = NULL;
259 gate_strbuilder_t sb;
260 ✗ gate_string_t cmd_args = GATE_STRING_INIT_EMPTY;
261
262 ✗ gate_strbuilder_create(&sb, 2048);
263 ✗ gate_strbuilder_append_text(&sb, WINRM_SOAP_MSG_EXECUTECOMMAND_REQUEST, sizeof(WINRM_SOAP_MSG_EXECUTECOMMAND_REQUEST) - 1);
264
265 ✗ if (shell_args_count > 0)
266 {
267 gate_strbuilder_t args_builder;
268 gate_size_t ndx;
269 ✗ gate_strbuilder_create(&args_builder, 256);
270 ✗ for (ndx = 0; ndx != shell_args_count; ++ndx)
271 {
272 ✗ gate_strbuilder_append_cstr(&args_builder, "<rsp:Arguments>");
273 ✗ gate_strbuilder_append_string(&args_builder, &shell_args[ndx]);
274 ✗ gate_strbuilder_append_cstr(&args_builder, "</rsp:Arguments>");
275 }
276
277 ✗ gate_strbuilder_to_string(&args_builder, &cmd_args);
278 ✗ gate_strbuilder_release(&args_builder);
279 }
280
281 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_TARGET_URL, target_url);
282 ✗ replace_token_with_guid(&sb, WINRM_TOKEN_MESSAGE_ID, message_id);
283 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_SHELL_UID, shell_uid);
284 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_SHELL_COMMAND, shell_command);
285 ✗ replace_token_raw(&sb, WINRM_TOKEN_SHELL_ARGS, &cmd_args);
286
287 ✗ ret = gate_strbuilder_to_string(&sb, output);
288 ✗ gate_strbuilder_release(&sb);
289 ✗ gate_string_release(&cmd_args);
290 ✗ return ret;
291 }
292
293 static char const WINRM_SOAP_MSG_RECEIVEOUTPUT_REQUEST[] =
294 "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:a=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
295 "<s:Header>"
296 "<a:To>" WINRM_TOKEN_TARGET_URL "</a:To>"
297 "<a:ReplyTo><a:Address s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"
298 "<a:Action s:mustUnderstand=\"true\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive</a:Action>"
299 "<wsman:MaxEnvelopeSize s:mustUnderstand=\"true\">153600</wsman:MaxEnvelopeSize>"
300 "<a:MessageID>uuid:" WINRM_TOKEN_MESSAGE_ID "</a:MessageID>"
301 "<wsman:Locale xml:lang=\"en-US\" s:mustUnderstand=\"false\" />"
302 "<wsman:ResourceURI xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</wsman:ResourceURI>"
303 "<wsman:SelectorSet xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\" xmlns=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
304 "<wsman:Selector Name=\"ShellId\">" WINRM_TOKEN_SHELL_UID "</wsman:Selector>"
305 "</wsman:SelectorSet>"
306 "<wsman:OperationTimeout>" WINRM_TOKEN_TIMEOUT "</wsman:OperationTimeout>"
307 "</s:Header>"
308 "<s:Body>"
309 "<rsp:Receive xmlns:rsp=\"http://schemas.microsoft.com/wbem/wsman/1/windows/shell\" SequenceId=\"0\">"
310 "<rsp:DesiredStream CommandId=\"" WINRM_TOKEN_COMMAND_UID "\">stdout stderr</rsp:DesiredStream>"
311 "</rsp:Receive>"
312 "</s:Body>"
313 "</s:Envelope>";
314
315
316 ✗ gate_string_t* winrm_shell_receiveoutput_request(gate_string_t* output,
317 gate_string_t const* target_url,
318 gate_guid_t const* message_id,
319 gate_string_t const* shell_id,
320 gate_string_t const* command_id,
321 gate_uint32_t timeout_ms)
322 {
323 ✗ gate_string_t* ret = NULL;
324 gate_strbuilder_t sb;
325
326 ✗ gate_strbuilder_create(&sb, 2048);
327 ✗ gate_strbuilder_append_text(&sb, WINRM_SOAP_MSG_RECEIVEOUTPUT_REQUEST, sizeof(WINRM_SOAP_MSG_RECEIVEOUTPUT_REQUEST) - 1);
328
329 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_TARGET_URL, target_url);
330 ✗ replace_token_with_guid(&sb, WINRM_TOKEN_MESSAGE_ID, message_id);
331 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_SHELL_UID, shell_id);
332 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_COMMAND_UID, command_id);
333 ✗ replace_token_with_timeout(&sb, WINRM_TOKEN_TIMEOUT, timeout_ms);
334
335 ✗ ret = gate_strbuilder_to_string(&sb, output);
336 ✗ gate_strbuilder_release(&sb);
337 ✗ return ret;
338 }
339
340 static char const WINRM_SOAP_MSG_SENDINPUT_REQUEST[] =
341 "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:a=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
342 "<s:Header>"
343 "<a:To>" WINRM_TOKEN_TARGET_URL "</a:To>"
344 "<a:ReplyTo><a:Address s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"
345 "<a:Action s:mustUnderstand=\"true\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send</a:Action>"
346 "<wsman:MaxEnvelopeSize s:mustUnderstand=\"true\">153600</wsman:MaxEnvelopeSize>"
347 "<a:MessageID>uuid:" WINRM_TOKEN_MESSAGE_ID "</a:MessageID>"
348 "<wsman:Locale xml:lang=\"en-US\" s:mustUnderstand=\"false\" />"
349 "<wsman:ResourceURI xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</wsman:ResourceURI>"
350 "<wsman:SelectorSet xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\" xmlns=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
351 "<wsman:Selector Name=\"ShellId\">" WINRM_TOKEN_SHELL_UID "</wsman:Selector>"
352 "</wsman:SelectorSet>"
353 "<wsman:OperationTimeout>PT60.000S</wsman:OperationTimeout>"
354 "</s:Header>"
355 "<s:Body>"
356 "<rsp:Send xmlns:rsp=\"http://schemas.microsoft.com/wbem/wsman/1/windows/shell\">"
357 "<rsp:Stream xmlns:rsp=\"http://schemas.microsoft.com/wbem/wsman/1/windows/shell\" Name=\"stdin\" CommandId=\"" WINRM_TOKEN_COMMAND_UID "\">"
358 WINRM_TOKEN_COMMAND_INPUT
359 "</rsp:Stream>"
360 "</rsp:Send>"
361 "</s:Body>"
362 "</s:Envelope>";
363
364 ✗ gate_string_t* winrm_shell_sendinput_request(gate_string_t* output,
365 gate_string_t const* target_url,
366 gate_guid_t const* message_id,
367 gate_string_t const* shell_id,
368 gate_string_t const* command_id,
369 gate_string_t const* input)
370 {
371 ✗ gate_string_t* ret = NULL;
372 gate_strbuilder_t sb;
373
374 ✗ gate_strbuilder_create(&sb, 2048);
375 ✗ gate_strbuilder_append_text(&sb, WINRM_SOAP_MSG_SENDINPUT_REQUEST, sizeof(WINRM_SOAP_MSG_SENDINPUT_REQUEST) - 1);
376
377 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_TARGET_URL, target_url);
378 ✗ replace_token_with_guid(&sb, WINRM_TOKEN_MESSAGE_ID, message_id);
379 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_SHELL_UID, shell_id);
380 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_COMMAND_UID, command_id);
381 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_COMMAND_INPUT, input);
382
383 ✗ ret = gate_strbuilder_to_string(&sb, output);
384 ✗ gate_strbuilder_release(&sb);
385 ✗ return ret;
386 }
387
388 static char const WINRM_SOAP_MSG_TERMINATEOPERATION_REQUEST[] =
389 "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:a=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
390 "<s:Header>"
391 "<a:To>" WINRM_TOKEN_TARGET_URL "</a:To>"
392 "<a:ReplyTo><a:Address s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"
393 "<a:Action s:mustUnderstand=\"true\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal</a:Action>"
394 "<wsman:MaxEnvelopeSize s:mustUnderstand=\"true\">153600</wsman:MaxEnvelopeSize>"
395 "<wsa:MessageID>uuid:" WINRM_TOKEN_MESSAGE_ID "</wsa:MessageID>"
396 "<wsman:Locale xml:lang=\"en-US\" s:mustUnderstand=\"false\"/>"
397 "<wsman:ResourceURI xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</wsman:ResourceURI>"
398 "<wsman:SelectorSet xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\" xmlns=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
399 "<wsman:Selector Name=\"ShellId\">uuid:" WINRM_TOKEN_SHELL_UID "</wsman:Selector>"
400 "</wsman:SelectorSet>"
401 "<wsman:OperationTimeout>PT60.000S</wsman:OperationTimeout>"
402 "</s:Header>"
403 "<s:Body>"
404 "<rsp:Signal xmlns:rsp=\"http://schemas.microsoft.com/wbem/wsman/1/windows/shell\" CommandId=\"" WINRM_TOKEN_COMMAND_UID "\">"
405 "<rsp:Code>http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/ctrl_c</rsp:Code>"
406 "</rsp:Signal>"
407 "</s:Body>"
408 "</s:Envelope>";
409
410 ✗ gate_string_t* winrm_shell_terminate_request(gate_string_t* output,
411 gate_string_t const* target_url,
412 gate_guid_t const* message_id,
413 gate_string_t const* shell_id,
414 gate_string_t const* command_id)
415 {
416 ✗ gate_string_t* ret = NULL;
417 gate_strbuilder_t sb;
418
419 ✗ gate_strbuilder_create(&sb, 2048);
420 ✗ gate_strbuilder_append_text(&sb, WINRM_SOAP_MSG_TERMINATEOPERATION_REQUEST, sizeof(WINRM_SOAP_MSG_TERMINATEOPERATION_REQUEST) - 1);
421
422 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_TARGET_URL, target_url);
423 ✗ replace_token_with_guid(&sb, WINRM_TOKEN_MESSAGE_ID, message_id);
424 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_SHELL_UID, shell_id);
425 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_COMMAND_UID, command_id);
426
427 ✗ ret = gate_strbuilder_to_string(&sb, output);
428 ✗ gate_strbuilder_release(&sb);
429 ✗ return ret;
430 }
431
432 static char const WINRM_SOAP_MSG_DELETESHELL_REQUEST[] =
433 "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:a=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
434 "<s:Header>"
435 "<a:To>" WINRM_TOKEN_TARGET_URL "</a:To>"
436 "<a:ReplyTo><a:Address s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"
437 "<a:Action s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete</a:Action>"
438 "<wsman:MaxEnvelopeSize s:mustUnderstand=\"true\">153600</wsman:MaxEnvelopeSize>"
439 "<a:MessageID>uuid:" WINRM_TOKEN_MESSAGE_ID "</a:MessageID>"
440 "<wsman:Locale xml:lang=\"en-US\" s:mustUnderstand=\"false\" />"
441 "<wsman:ResourceURI xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</wsman:ResourceURI>"
442 "<wsman:SelectorSet xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\" xmlns=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
443 "<wsman:Selector Name=\"ShellId\">uuid:" WINRM_TOKEN_SHELL_UID "</wsman:Selector>"
444 "</wsman:SelectorSet>"
445 "<wsman:OperationTimeout>PT60.000S</wsman:OperationTimeout>"
446 "</s:Header>"
447 "<s:Body></s:Body>"
448 "</s:Envelope>";
449
450 ✗ gate_string_t* winrm_delete_shell_request(gate_string_t* output,
451 gate_string_t const* target_url,
452 gate_guid_t const* message_id,
453 gate_string_t const* shell_id)
454 {
455 ✗ gate_string_t* ret = NULL;
456 gate_strbuilder_t sb;
457
458 ✗ gate_strbuilder_create(&sb, 2048);
459 ✗ gate_strbuilder_append_text(&sb, WINRM_SOAP_MSG_DELETESHELL_REQUEST, sizeof(WINRM_SOAP_MSG_DELETESHELL_REQUEST) - 1);
460
461 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_TARGET_URL, target_url);
462 ✗ replace_token_with_guid(&sb, WINRM_TOKEN_MESSAGE_ID, message_id);
463 ✗ replace_token_with_xml_content(&sb, WINRM_TOKEN_SHELL_UID, shell_id);
464
465 ✗ ret = gate_strbuilder_to_string(&sb, output);
466 ✗ gate_strbuilder_release(&sb);
467 ✗ return ret;
468 }
469
470
471 ✗ gate_result_t gate_winrmclient_create(gate_winrmclient_t* client,
472 gate_string_t const* server, gate_uint16_t port,
473 gate_string_t const* user, gate_string_t const* password,
474 gate_enumint_t flags)
475 {
476 static gate_string_t const wsman_path = GATE_STRING_INIT_STATIC("/wsman");
477 ✗ gate_result_t ret = GATE_RESULT_FAILED;
478 ✗ gate_bool_t const secure_flag = GATE_FLAG_ENABLED(flags, GATE_WINRMCLIENT_FLAG_SECURE);
479
480 do
481 {
482 ✗ if (!client || !server)
483 {
484 ✗ ret = GATE_RESULT_INVALIDARG;
485 ✗ break;
486 }
487
488 ✗ gate_mem_clear(client, sizeof(gate_winrmclient_t));
489
490 ✗ ret = gate_uri_init(&client->uri);
491 ✗ GATE_BREAK_IF_FAILED(ret);
492
493 ✗ gate_string_create_static(&client->uri.scheme, secure_flag ? GATE_URI_SCHEME_HTTPS : GATE_URI_SCHEME_HTTP);
494
495 ✗ if (NULL == gate_string_clone(&client->uri.host, server))
496 {
497 ✗ ret = GATE_RESULT_OUTOFMEMORY;
498 ✗ break;
499 }
500
501 ✗ if (NULL == gate_string_clone(&client->uri.absolute_path, &wsman_path))
502 {
503 ✗ ret = GATE_RESULT_OUTOFMEMORY;
504 ✗ break;
505 }
506
507 ✗ if (!gate_string_is_empty(user) || !gate_string_is_empty(password))
508 {
509 ✗ ret = gate_uri_build_user_info(user, password, &client->uri.user_info);
510 ✗ GATE_BREAK_IF_FAILED(ret);
511 }
512
513 ✗ client->uri.port = (port == 0) ? (secure_flag ? 5986 : 5985) : port;
514
515 ✗ ret = GATE_RESULT_OK;
516
517 } while (0);
518
519 ✗ if (client && GATE_FAILED(ret))
520 {
521 ✗ gate_uri_destroy(&client->uri);
522 }
523 ✗ return ret;
524 }
525
526 ✗ gate_result_t gate_winrmclient_release(gate_winrmclient_t* client)
527 {
528 gate_result_t ret;
529
530 ✗ GATE_DEBUG_ASSERT(client != NULL);
531 do
532 {
533 ✗ ret = gate_uri_destroy(&client->uri);
534 } while (0);
535
536 ✗ return ret;
537 }
538
539 ✗ static gate_string_t* winrm_create_target_url(gate_string_t* target_url, gate_winrmclient_t* from_client)
540 {
541 ✗ gate_string_t* ret = NULL;
542 gate_uri_t uri;
543
544 do
545 {
546 gate_result_t result;
547 ✗ gate_mem_clear(&uri, sizeof(uri));
548
549 ✗ result = gate_uri_copy(&uri, &from_client->uri);
550 ✗ GATE_BREAK_IF_FAILED(result);
551
552 ✗ gate_string_release(&uri.user_info);
553 ✗ gate_string_release(&uri.query);
554
555 ✗ result = gate_uri_to_string(&uri, target_url, false);
556 ✗ GATE_BREAK_IF_FAILED(result);
557
558 ✗ ret = target_url;
559 } while (0);
560
561 ✗ gate_uri_destroy(&uri);
562 ✗ return ret;
563 }
564
565 ✗ static gate_result_t winrm_send_soap_request(gate_winrmclient_t* client, gate_string_t const* soap_request, gate_uint32_t* ptr_status_code, gate_string_t* ptr_soap_response)
566 {
567 gate_result_t ret;
568 ✗ gate_httpclient_t http = GATE_INIT_EMPTY;
569 ✗ gate_http_request_t req = GATE_INIT_EMPTY;
570 ✗ gate_http_response_t resp = GATE_INIT_EMPTY;
571 gate_memstream_impl_t upload_impl;
572 ✗ gate_size_t upload_size = gate_string_length(soap_request);
573 ✗ gate_stringstream_t* ss = NULL;
574 static gate_string_t const http_post = GATE_STRING_INIT_STATIC(GATE_HTTP_METHOD_POST);
575 static gate_string_t const header_content_type = GATE_STRING_INIT_STATIC(GATE_HTTP_HEADER_CONTENTTYPE);
576 static gate_string_t const header_content_type_value = GATE_STRING_INIT_STATIC("application/soap+xml;charset=UTF-8");
577 static gate_string_t const header_connection = GATE_STRING_INIT_STATIC(GATE_HTTP_HEADER_CONNECTION);
578 static gate_string_t const header_connection_keepalive = GATE_STRING_INIT_STATIC(GATE_HTTP_HEADERVALUE_CONNECTION_KEEPALIVE);
579
580 do
581 {
582 ✗ ret = gate_httpclient_create(&http, &client->uri.host, client->uri.port, 0);
583 ✗ GATE_BREAK_IF_FAILED(ret);
584
585 ✗ ret = gate_http_request_init_uri(&req, &http_post, &client->uri);
586 ✗ GATE_BREAK_IF_FAILED(ret);
587
588 ✗ req.upload_stream = (gate_stream_t*)gate_memstream_create_static_unmanaged_readonly(
589 ✗ &upload_impl, gate_string_ptr(soap_request, 0), upload_size, upload_size);
590
591 ✗ gate_map_add(&req.headers, &header_content_type, &header_content_type_value);
592 ✗ gate_map_add(&req.headers, &header_connection, &header_connection_keepalive);
593
594 ✗ ret = gate_httpclient_send_request(&http, &req, &resp);
595 ✗ GATE_BREAK_IF_FAILED(ret);
596
597 ✗ ss = gate_stringstream_create(256);
598 ✗ if (ss == NULL)
599 {
600 ✗ ret = GATE_RESULT_OUTOFMEMORY;
601 ✗ break;
602 }
603
604 ✗ if (ptr_status_code)
605 {
606 ✗ *ptr_status_code = resp.status_code;
607 }
608
609 ✗ ret = gate_stream_transfer(resp.response_stream, (gate_stream_t*)ss);
610 ✗ GATE_BREAK_IF_FAILED(ret);
611
612 ✗ ret = gate_stringstream_to_string(ss, ptr_soap_response);
613 ✗ GATE_BREAK_IF_FAILED(ret);
614
615 /* success case */
616 ✗ ret = GATE_RESULT_OK;
617 } while (0);
618
619 ✗ gate_http_response_release(&resp);
620 ✗ gate_http_request_release(&req);
621 ✗ gate_httpclient_release(&http);
622
623 ✗ return ret;
624 }
625
626 ✗ static gate_result_t winrm_prepare_request(gate_winrmclient_t* client, gate_guid_t* ptr_new_msg_id, gate_string_t* ptr_target_url)
627 {
628 ✗ gate_result_t ret = gate_guid_generate(ptr_new_msg_id);
629 ✗ if (GATE_SUCCEEDED(ret))
630 {
631 ✗ if (NULL == winrm_create_target_url(ptr_target_url, client))
632 {
633 ✗ ret = GATE_RESULT_OUTOFMEMORY;
634 }
635 }
636 ✗ return ret;
637 }
638
639 ✗ static gate_result_t check_status_ok(gate_uint32_t status_code)
640 {
641 ✗ switch (status_code)
642 {
643 ✗ case 200: return GATE_RESULT_OK;
644 ✗ default: return GATE_RESULT_FAILED;
645 }
646 }
647
648 ✗ gate_result_t gate_winrmclient_shell_new(gate_winrmclient_t* client, gate_string_t const* workdir, gate_array_t const* env_vars, gate_string_t* shell_id)
649 {
650 gate_result_t ret;
651 ✗ gate_string_t soap_request = GATE_STRING_INIT_EMPTY;
652 ✗ gate_string_t target_url = GATE_STRING_INIT_EMPTY;
653 ✗ gate_guid_t msg_id = GATE_INIT_EMPTY;
654 ✗ gate_uint32_t status_code = 0;
655 ✗ gate_string_t soap_response = GATE_STRING_INIT_EMPTY;
656 static gate_string_t const response_shell_id_begin = GATE_STRING_INIT_STATIC("ShellId>");
657 static gate_string_t const response_shell_id_end = GATE_STRING_INIT_STATIC("</");
658
659 do
660 {
661 ✗ ret = winrm_prepare_request(client, &msg_id, &target_url);
662 ✗ GATE_BREAK_IF_FAILED(ret);
663
664 ✗ if (NULL == winrm_create_shell_request(&soap_request, &target_url, &msg_id, workdir))
665 {
666 ✗ ret = GATE_RESULT_OUTOFMEMORY;
667 ✗ break;
668 }
669
670 ✗ ret = winrm_send_soap_request(client, &soap_request, &status_code, &soap_response);
671 ✗ GATE_BREAK_IF_FAILED(ret);
672
673 ✗ ret = check_status_ok(status_code);
674 ✗ GATE_BREAK_IF_FAILED(ret);
675
676 ✗ if (!extract_xml_content(&soap_response, &response_shell_id_begin, &response_shell_id_end, shell_id))
677 {
678 ✗ ret = GATE_RESULT_INVALIDCONTENT;
679 ✗ break;
680 }
681
682 ✗ ret = GATE_RESULT_OK;
683 } while (0);
684
685 ✗ gate_string_release(&soap_response);
686 ✗ gate_string_release(&soap_request);
687 ✗ gate_string_release(&target_url);
688
689 ✗ return ret;
690 }
691
692 ✗ gate_result_t gate_winrmclient_shell_delete(gate_winrmclient_t* client, gate_string_t const* shell_id)
693 {
694 ✗ gate_result_t ret = GATE_RESULT_FAILED;
695 ✗ gate_guid_t msg_id = GATE_INIT_EMPTY;
696 ✗ gate_string_t target_url = GATE_STRING_INIT_EMPTY;
697 ✗ gate_string_t soap_request = GATE_STRING_INIT_EMPTY;
698 ✗ gate_string_t soap_response = GATE_STRING_INIT_EMPTY;
699 ✗ gate_uint32_t status_code = 0;
700
701 do
702 {
703 ✗ ret = winrm_prepare_request(client, &msg_id, &target_url);
704 ✗ GATE_BREAK_IF_FAILED(ret);
705
706 ✗ if (NULL == winrm_delete_shell_request(&soap_request, &target_url, &msg_id, shell_id))
707 {
708 ✗ ret = GATE_RESULT_OUTOFMEMORY;
709 ✗ break;
710 }
711
712 ✗ ret = winrm_send_soap_request(client, &soap_request, &status_code, &soap_response);
713 ✗ GATE_BREAK_IF_FAILED(ret);
714
715 ✗ ret = check_status_ok(status_code);
716 ✗ GATE_BREAK_IF_FAILED(ret);
717
718 ✗ ret = GATE_RESULT_OK;
719 } while (0);
720
721 ✗ gate_string_release(&soap_request);
722 ✗ gate_string_release(&soap_response);
723 ✗ gate_string_release(&target_url);
724 ✗ return ret;
725 }
726
727
728 ✗ gate_result_t gate_winrmclient_shell_command_start(
729 gate_winrmclient_t* client, gate_string_t const* shell_id, gate_string_t const* command, gate_array_t const* args,
730 gate_string_t* command_id)
731 {
732 ✗ gate_result_t ret = GATE_RESULT_FAILED;
733 ✗ gate_guid_t msg_id = GATE_INIT_EMPTY;
734 ✗ gate_string_t target_url = GATE_STRING_INIT_EMPTY;
735 ✗ gate_string_t soap_request = GATE_STRING_INIT_EMPTY;
736 ✗ gate_string_t soap_response = GATE_STRING_INIT_EMPTY;
737 ✗ gate_uint32_t status_code = 0;
738
739 do
740 {
741 static gate_string_t const resp_command_id_begin = GATE_STRING_INIT_STATIC("CommandId>");
742 static gate_string_t const resp_command_id_end = GATE_STRING_INIT_STATIC("</");
743
744 ✗ ret = winrm_prepare_request(client, &msg_id, &target_url);
745 ✗ GATE_BREAK_IF_FAILED(ret);
746
747 ✗ if (NULL == winrm_shell_execute_request(&soap_request, &target_url, &msg_id, shell_id, command, NULL, 0))
748 {
749 ✗ ret = GATE_RESULT_OUTOFMEMORY;
750 ✗ break;
751 }
752
753 ✗ ret = winrm_send_soap_request(client, &soap_request, &status_code, &soap_response);
754 ✗ GATE_BREAK_IF_FAILED(ret);
755
756 ✗ ret = check_status_ok(status_code);
757 ✗ GATE_BREAK_IF_FAILED(ret);
758
759 ✗ if (!extract_xml_content(&soap_response, &resp_command_id_begin, &resp_command_id_end, command_id))
760 {
761 ✗ ret = GATE_RESULT_INVALIDCONTENT;
762 ✗ break;
763 }
764
765 ✗ ret = GATE_RESULT_OK;
766 } while (0);
767
768 ✗ gate_string_release(&soap_request);
769 ✗ gate_string_release(&soap_response);
770 ✗ gate_string_release(&target_url);
771 ✗ return ret;
772 }
773
774
775 ✗ gate_result_t gate_winrmclient_shell_command_abort(gate_winrmclient_t* client,
776 gate_string_t const* shell_id, gate_string_t const* command_id)
777 {
778 ✗ gate_result_t ret = GATE_RESULT_FAILED;
779 ✗ gate_guid_t msg_id = GATE_INIT_EMPTY;
780 ✗ gate_string_t target_url = GATE_STRING_INIT_EMPTY;
781 ✗ gate_string_t soap_request = GATE_STRING_INIT_EMPTY;
782 ✗ gate_string_t soap_response = GATE_STRING_INIT_EMPTY;
783 ✗ gate_uint32_t status_code = 0;
784
785 do
786 {
787 ✗ ret = winrm_prepare_request(client, &msg_id, &target_url);
788 ✗ GATE_BREAK_IF_FAILED(ret);
789
790 ✗ if (NULL == winrm_shell_terminate_request(&soap_request, &target_url, &msg_id, shell_id, command_id))
791 {
792 ✗ ret = GATE_RESULT_OUTOFMEMORY;
793 ✗ break;
794 }
795
796 ✗ ret = winrm_send_soap_request(client, &soap_request, &status_code, &soap_response);
797 ✗ GATE_BREAK_IF_FAILED(ret);
798
799 ✗ ret = check_status_ok(status_code);
800 ✗ GATE_BREAK_IF_FAILED(ret);
801
802 ✗ ret = GATE_RESULT_OK;
803 } while (0);
804
805 ✗ gate_string_release(&soap_request);
806 ✗ gate_string_release(&soap_response);
807 ✗ gate_string_release(&target_url);
808 ✗ return ret;
809 }
810
811
812 ✗ gate_result_t gate_winrmclient_shell_command_send(gate_winrmclient_t* client,
813 gate_string_t const* shell_id, gate_string_t const* command_id, gate_blob_t const* data_buffer)
814 {
815 ✗ gate_result_t ret = GATE_RESULT_FAILED;
816 ✗ gate_guid_t msg_id = GATE_INIT_EMPTY;
817 ✗ gate_string_t target_url = GATE_STRING_INIT_EMPTY;
818 ✗ gate_string_t soap_request = GATE_STRING_INIT_EMPTY;
819 ✗ gate_string_t soap_response = GATE_STRING_INIT_EMPTY;
820 ✗ gate_string_t input_b64 = GATE_STRING_INIT_EMPTY;
821 ✗ gate_uint32_t status_code = 0;
822 ✗ char const* ptr_data = (char const*)gate_blob_data(data_buffer);
823 ✗ gate_size_t data_len = gate_blob_length(data_buffer);
824 ✗ gate_string_t input_raw = { ptr_data, data_len, NULL };
825 ✗ gate_strbuilder_t input_builder = GATE_INIT_EMPTY;
826
827 do
828 {
829 /* encode input as base64 string */
830 ✗ gate_strbuilder_create(&input_builder, data_len * 4 / 3 + 3);
831 ✗ gate_base64_encode(&input_raw, &input_builder);
832 ✗ gate_strbuilder_to_string(&input_builder, &input_b64);
833
834 ✗ ret = winrm_prepare_request(client, &msg_id, &target_url);
835 ✗ GATE_BREAK_IF_FAILED(ret);
836
837 ✗ if (NULL == winrm_shell_sendinput_request(&soap_request, &target_url, &msg_id, shell_id, command_id, &input_b64))
838 {
839 ✗ ret = GATE_RESULT_OUTOFMEMORY;
840 ✗ break;
841 }
842
843 ✗ ret = winrm_send_soap_request(client, &soap_request, &status_code, &soap_response);
844 ✗ GATE_BREAK_IF_FAILED(ret);
845
846 ✗ ret = check_status_ok(status_code);
847 ✗ GATE_BREAK_IF_FAILED(ret);
848
849 ✗ ret = GATE_RESULT_OK;
850 } while (0);
851
852 ✗ gate_strbuilder_release(&input_builder);
853 ✗ gate_string_release(&input_b64);
854 ✗ gate_string_release(&soap_request);
855 ✗ gate_string_release(&soap_response);
856 ✗ gate_string_release(&target_url);
857 ✗ return ret;
858
859 }
860
861 ✗ static gate_size_t get_child_index_by_local_name(gate_xml_node_t const* parent, gate_string_t const* search_local_name, gate_size_t begin_index)
862 {
863 ✗ gate_size_t const count = gate_xml_node_children_count(parent);
864 gate_size_t ndx;
865 ✗ for (ndx = begin_index; ndx < count; ++ndx)
866 {
867 ✗ gate_xml_node_t const* const child = gate_xml_node_child_ptr(parent, ndx);
868 ✗ if (!child)
869 {
870 ✗ continue;
871 }
872 else
873 {
874 ✗ gate_string_t loc_name = GATE_STRING_INIT_EMPTY;
875 ✗ gate_result_t const result = gate_xml_parse_decompose_name(&child->tag, NULL, &loc_name);
876 ✗ if (GATE_SUCCEEDED(result))
877 {
878 ✗ gate_bool_t const tag_found = gate_string_equals(&loc_name, search_local_name);
879 ✗ gate_string_release(&loc_name);
880 ✗ if (tag_found)
881 {
882 ✗ return ndx;
883 }
884 }
885 }
886 }
887 ✗ return GATE_STR_NPOS;
888 }
889
890 ✗ static gate_xml_node_t const* get_child_ptr_by_local_name(gate_xml_node_t const* parent, gate_string_t const* search_local_name)
891 {
892 ✗ gate_size_t ndx = get_child_index_by_local_name(parent, search_local_name, 0);
893 ✗ if(ndx == GATE_STR_NPOS)
894 {
895 ✗ return NULL;
896 }
897 ✗ return gate_xml_node_child_ptr(parent, ndx);
898 }
899
900
901 ✗ static gate_result_t extract_received_output(gate_string_t const* xml,
902 gate_winrmclient_output_callback output_callback,
903 void* user_param)
904 {
905 ✗ gate_result_t ret = GATE_RESULT_FAILED;
906 ✗ gate_xml_doc_t xdoc = GATE_INIT_EMPTY;
907 gate_strbuilder_t outtext;
908 static gate_string_t const xtag_body = GATE_STRING_INIT_STATIC("Body");
909 static gate_string_t const xtag_receive = GATE_STRING_INIT_STATIC("ReceiveResponse");
910 static gate_string_t const xtag_stream = GATE_STRING_INIT_STATIC("Stream");
911 static gate_string_t const xtag_state = GATE_STRING_INIT_STATIC("CommandState");
912 static gate_string_t const xtag_exit = GATE_STRING_INIT_STATIC("ExitCode");
913 static gate_string_t const xattr_name = GATE_STRING_INIT_STATIC("Name");
914 static gate_string_t const xattr_end = GATE_STRING_INIT_STATIC("End");
915 static gate_string_t const xattr_state = GATE_STRING_INIT_STATIC("State");
916
917 do
918 {
919 ✗ gate_xml_node_t const* xroot = NULL;
920 ✗ gate_xml_node_t const* xbody = NULL;
921 ✗ gate_xml_node_t const* xrec = NULL;
922 ✗ gate_size_t next_stream_ndx = 0;
923 ✗ gate_bool_t continue_processing = true;
924
925 ✗ gate_strbuilder_create(&outtext, 32);
926
927 ✗ ret = gate_xml_doc_load_text(&xdoc, xml);
928 ✗ GATE_BREAK_IF_FAILED(ret);
929
930 ✗ ret = gate_xml_doc_root_element(&xdoc, &xroot);
931 ✗ GATE_BREAK_IF_FAILED(ret);
932
933 ✗ xbody = get_child_ptr_by_local_name(xroot, &xtag_body);
934 ✗ if (xbody == NULL)
935 {
936 ✗ ret = GATE_RESULT_INVALIDCONTENT;
937 ✗ break;
938 }
939 ✗ xrec = get_child_ptr_by_local_name(xbody, &xtag_receive);
940 ✗ if (xbody == NULL)
941 {
942 ✗ ret = GATE_RESULT_INVALIDCONTENT;
943 ✗ break;
944 }
945
946 ✗ gate_strbuilder_create(&outtext, 64);
947 ✗ for (next_stream_ndx = 0;
948 ✗ GATE_STR_NPOS != (next_stream_ndx = get_child_index_by_local_name(xrec, &xtag_stream, next_stream_ndx));
949 ✗ ++next_stream_ndx)
950 {
951 gate_blob_t chunk;
952 gate_result_t result;
953 gate_string_t attr_text;
954 ✗ gate_bool_t is_stderr = false;
955 ✗ gate_bool_t is_endofstream = false;
956 ✗ gate_xml_node_t const* xstream = gate_xml_node_child_ptr(xrec, next_stream_ndx);
957
958 ✗ result = gate_xml_node_attribute_by_name(xstream, &xattr_name, &attr_text);
959 ✗ if(GATE_SUCCEEDED(result))
960 {
961 ✗ is_stderr = gate_string_equals_str_ic(&attr_text, "stderr");
962 ✗ gate_string_release(&attr_text);
963 }
964
965 ✗ result = gate_xml_node_attribute_by_name(xstream, &xattr_end, &attr_text);
966 ✗ if(GATE_SUCCEEDED(result))
967 {
968 ✗ is_endofstream = gate_string_equals_str_ic(&attr_text, "true");
969 ✗ gate_string_release(&attr_text);
970 }
971
972 /* reset local output buffer */
973 ✗ gate_strbuilder_discard(&outtext, gate_strbuilder_length(&outtext));
974 ✗ result = gate_base64_decode(&xstream->content, &outtext);
975
976 ✗ if(GATE_SUCCEEDED(result) && (output_callback != NULL))
977 {
978 ✗ if(gate_strbuilder_length(&outtext) > 0)
979 {
980 /* notify output content */
981 ✗ gate_blob_create_static(&chunk, gate_strbuilder_ptr(&outtext, 0), gate_strbuilder_length(&outtext));
982 ✗ continue_processing = output_callback(
983 is_stderr ? GATE_WINRMCLIENT_OUTPUT_TYPE_STDERR : GATE_WINRMCLIENT_OUTPUT_TYPE_STDOUT,
984 &chunk, user_param);
985 ✗ gate_blob_release(&chunk);
986 ✗ if(!continue_processing)
987 {
988 ✗ break;
989 }
990 }
991 ✗ if(is_endofstream)
992 {
993 /* notify end of stream */
994 ✗ continue_processing = output_callback(
995 is_stderr ? GATE_WINRMCLIENT_OUTPUT_TYPE_STDERR : GATE_WINRMCLIENT_OUTPUT_TYPE_STDOUT,
996 NULL, user_param);
997 ✗ if(!continue_processing)
998 {
999 ✗ break;
1000 }
1001 }
1002 }
1003 } /* for */
1004 ✗ gate_strbuilder_release(&outtext);
1005
1006 ✗ if(continue_processing)
1007 {
1008 /* try to detect process-exit */
1009 gate_result_t result;
1010 gate_string_t attr_text;
1011 ✗ gate_xml_node_t const* xstate = get_child_ptr_by_local_name(xrec, &xtag_state);
1012 ✗ if(xstate == NULL)
1013 {
1014 ✗ break;
1015 }
1016
1017 /* command state was transmitted */
1018 ✗ result = gate_xml_node_attribute_by_name(xstate, &xattr_state, &attr_text);
1019 ✗ if(GATE_SUCCEEDED(result))
1020 {
1021 ✗ gate_bool_t process_terminated = gate_string_ends_with_str(&attr_text, "Done");
1022 ✗ if(process_terminated)
1023 {
1024 ✗ gate_blob_t chunk = GATE_INIT_EMPTY;
1025 ✗ gate_xml_node_t const* xexit = get_child_ptr_by_local_name(xstate, &xtag_exit);
1026 ✗ if(xexit != NULL)
1027 {
1028 ✗ gate_blob_create_static(&chunk, gate_string_ptr(&xexit->content, 0), gate_string_length(&xexit->content));
1029 }
1030 ✗ if((output_callback != NULL))
1031 {
1032 ✗ output_callback(GATE_WINRMCLIENT_OUTPUT_TYPE_EXIT, &chunk, user_param);
1033 }
1034 ✗ gate_blob_release(&chunk);
1035 }
1036 ✗ gate_string_release(&attr_text);
1037 }
1038 }
1039
1040 ✗ ret = GATE_RESULT_OK;
1041 } while (0);
1042
1043 ✗ gate_strbuilder_release(&outtext);
1044
1045 ✗ gate_xml_doc_destroy(&xdoc);
1046 ✗ return ret;
1047 }
1048
1049 ✗ static gate_bool_t check_timeout(gate_uint32_t status_code, gate_string_t const* response)
1050 {
1051 static gate_string_t const token_fault = GATE_STRING_INIT_STATIC("Fault>");
1052 static gate_string_t const token_timeout = GATE_STRING_INIT_STATIC("TimedOut</");
1053
1054 ✗ if(status_code < 500)
1055 {
1056 ✗ return false;
1057 }
1058 ✗ return gate_string_contains(response, &token_fault) && gate_string_contains(response, &token_timeout);
1059 }
1060
1061 ✗ gate_result_t gate_winrmclient_shell_command_receive(gate_winrmclient_t* client,
1062 gate_string_t const* shell_id,
1063 gate_string_t const* command_id,
1064 gate_uint32_t timeout_ms,
1065 gate_winrmclient_output_callback output_callback,
1066 void* user_param)
1067 {
1068 ✗ gate_result_t ret = GATE_RESULT_FAILED;
1069 ✗ gate_guid_t msg_id = GATE_INIT_EMPTY;
1070 ✗ gate_string_t target_url = GATE_STRING_INIT_EMPTY;
1071 ✗ gate_string_t soap_request = GATE_STRING_INIT_EMPTY;
1072 ✗ gate_string_t soap_response = GATE_STRING_INIT_EMPTY;
1073 ✗ gate_uint32_t status_code = 0;
1074
1075 do
1076 {
1077 ✗ ret = winrm_prepare_request(client, &msg_id, &target_url);
1078 ✗ GATE_BREAK_IF_FAILED(ret);
1079
1080 ✗ if (NULL == winrm_shell_receiveoutput_request(&soap_request, &target_url, &msg_id, shell_id, command_id, timeout_ms))
1081 {
1082 ✗ ret = GATE_RESULT_OUTOFMEMORY;
1083 ✗ break;
1084 }
1085
1086 ✗ ret = winrm_send_soap_request(client, &soap_request, &status_code, &soap_response);
1087 ✗ GATE_BREAK_IF_FAILED(ret);
1088
1089 ✗ if(check_timeout(status_code, &soap_response))
1090 {
1091 /* no output within specified timeout */
1092 ✗ ret = GATE_RESULT_OK;
1093 }
1094 else
1095 {
1096 ✗ ret = check_status_ok(status_code);
1097 ✗ GATE_BREAK_IF_FAILED(ret);
1098
1099 ✗ ret = extract_received_output(&soap_response, output_callback, user_param);
1100 }
1101 } while (0);
1102
1103 ✗ gate_string_release(&soap_request);
1104 ✗ gate_string_release(&soap_response);
1105 ✗ gate_string_release(&target_url);
1106 ✗ return ret;
1107
1108 }
1109
1110