GCC Code Coverage Report


Directory: src/gate/
File: src/gate/net/winrmclients.c
Date: 2025-09-14 13:10:38
Exec Total Coverage
Lines: 0 362 0.0%
Functions: 0 28 0.0%
Branches: 0 118 0.0%

Line Branch Exec Source
1 /* GATE PROJECT LICENSE:
2 +----------------------------------------------------------------------------+
3 | Copyright(c) 2018-2025, 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 static gate_string_t const winrm_uri_wmi = GATE_STRING_INIT_STATIC("http://schemas.microsoft.com/wbem/wsman/1/wmi");
38 static gate_string_t const winrm_uri_wmicimv2 = GATE_STRING_INIT_STATIC("http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2");
39 static gate_string_t const winrm_uri_cimv2 = GATE_STRING_INIT_STATIC("http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2");
40 static gate_string_t const winrm_uri_wsman = GATE_STRING_INIT_STATIC("http://schemas.microsoft.com/wbem/wsman/1");
41 static gate_string_t const winrm_uri_shell = GATE_STRING_INIT_STATIC("http://schemas.microsoft.com/wbem/wsman/1/windows/shell");
42 static gate_string_t const winrm_uri_shell_cmd = GATE_STRING_INIT_STATIC("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd");
43
44 #define WINRM_TOKEN_TARGET_URL "${WINRM_TARGET}"
45 #define WINRM_TOKEN_URI_SHELL_CMD "${WINRM_SHELL_CMD}"
46 #define WINRM_TOKEN_MESSAGE_ID "${WINRM_MESSAGE_ID}"
47 #define WINRM_TOKEN_WORKDIR "${WINRM_WORKDIR}"
48 #define WINRM_TOKEN_SHELL_UID "${WINRM_SHELL_UID}"
49 #define WINRM_TOKEN_SHELL_COMMAND "${WINRM_SHELL_COMMAND}"
50 #define WINRM_TOKEN_SHELL_ARG "${WINRM_SHELL_ARG}"
51 #define WINRM_TOKEN_SHELL_ARGS "${WINRM_SHELL_ARGS}"
52 #define WINRM_TOKEN_COMMAND_UID "${WINRM_COMMAND_UID}"
53 #define WINRM_TOKEN_COMMAND_INPUT "${WINRM_COMMAND_INPUT}"
54 #define WINRM_TOKEN_TIMEOUT "${WINRM_TOKEN_TIMEOUT}"
55
56 /* https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/7f4a1f31-47d8-4599-a23b-c3834ffae21f */
57
58 static gate_bool_t replace_token_raw(gate_strbuilder_t* builder, char const* token, gate_string_t const* value)
59 {
60 gate_size_t const replace_count = gate_strbuilder_replace_str(
61 builder, token, gate_str_length(token), gate_string_ptr(value, 0), gate_string_length(value), 0, 16);
62 return replace_count > 0;
63 }
64
65 static void print_guid(gate_guid_t const* message_id, char* buffer)
66 {
67 if (message_id)
68 {
69 gate_guid_to_string(message_id, buffer);
70 }
71 else
72 {
73 gate_guid_t new_id;
74 gate_guid_generate(&new_id);
75 gate_guid_to_string(message_id, buffer);
76 }
77 }
78
79 static gate_bool_t replace_token_with_guid(gate_strbuilder_t* builder, char const* token, gate_guid_t const* guid)
80 {
81 char guid_text_buffer[128];
82 gate_string_t guid_str = GATE_STRING_INIT_EMPTY;
83
84 print_guid(guid, guid_text_buffer);
85 gate_string_create_static_len(&guid_str, guid_text_buffer, gate_str_length(guid_text_buffer));
86 return replace_token_raw(builder, token, &guid_str);
87 }
88
89 static gate_bool_t xml_encode(gate_string_t const* raw_input, gate_string_t* xml_output)
90 {
91 /* TODO */
92 return gate_string_duplicate(xml_output, raw_input) != NULL;
93 }
94
95 static gate_bool_t replace_token_with_xml_content(gate_strbuilder_t* builder, char const* token, gate_string_t const* value)
96 {
97 gate_bool_t ret = false;
98 gate_string_t xml;
99 if (xml_encode(value, &xml))
100 {
101 ret = replace_token_raw(builder, token, &xml);
102 gate_string_release(&xml);
103 }
104 return ret;
105 }
106
107 static gate_size_t winrm_print_timeout(gate_uint32_t timeout_ms, char* buffer, gate_size_t buffer_len)
108 {
109 gate_strbuilder_t builder;
110 const gate_real64_t timeout = (gate_real64_t)timeout_ms / 1000.0;
111
112 gate_strbuilder_create_static(&builder, buffer, buffer_len, 0);
113 gate_strbuilder_append_cstr(&builder, "PT");
114 gate_strbuilder_append_real(&builder, timeout, 0, 3, 0);
115 gate_strbuilder_append_cstr(&builder, "S");
116 return gate_strbuilder_length(&builder);
117 }
118
119 static gate_bool_t replace_token_with_timeout(gate_strbuilder_t* sb, char const* token, gate_uint32_t timeout_ms)
120 {
121 char buffer[64];
122 gate_size_t len = winrm_print_timeout(timeout_ms, buffer, sizeof(buffer));
123 gate_string_t content = { buffer, len, NULL };
124
125 return replace_token_raw(sb, token, &content);
126 }
127
128
129 static gate_bool_t extract_token(gate_string_t const* content,
130 gate_string_t const* begin_marker, gate_string_t const* end_marker,
131 gate_string_t* extracted_data)
132 {
133 gate_size_t pos_begin;
134 gate_size_t pos_end;
135
136 pos_begin = gate_string_pos(content, begin_marker, 0);
137 if (pos_begin == GATE_STR_NPOS)
138 {
139 return false;
140 }
141 pos_begin += gate_string_length(begin_marker);
142
143 pos_end = gate_string_pos(content, end_marker, pos_begin);
144 if (pos_begin == GATE_STR_NPOS)
145 {
146 return false;
147 }
148
149 return NULL != gate_string_substr(extracted_data, content, pos_begin, pos_end - pos_begin);
150 }
151
152 static gate_bool_t extract_xml_content(gate_string_t const* content,
153 gate_string_t const* begin_marker, gate_string_t const* end_marker,
154 gate_string_t* decoded_data)
155 {
156 gate_bool_t ret = false;
157 gate_string_t data = GATE_STRING_INIT_EMPTY;
158 if (extract_token(content, begin_marker, end_marker, &data))
159 {
160 /* TODO: XML decoding */
161 ret = NULL != gate_string_create_copy(decoded_data, &data);
162 gate_string_release(&data);
163 }
164 return ret;
165 }
166
167
168
169 static char const WINRM_SOAP_MSG_CREATESHELL_REQUEST[] =
170 "<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\">"
171 "<s:Header>"
172 "<a:To>" WINRM_TOKEN_TARGET_URL "</a:To>"
173 "<wsman:ResourceURI s:mustUnderstand=\"true\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</wsman:ResourceURI>"
174 "<a:ReplyTo><a:Address s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"
175 "<a:Action s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/09/transfer/Create</a:Action>"
176 "<wsman:MaxEnvelopeSize s:mustUnderstand=\"true\">153600</wsman:MaxEnvelopeSize>"
177 "<a:MessageID>uuid:" WINRM_TOKEN_MESSAGE_ID "</a:MessageID>"
178 "<wsman:Locale xml:lang=\"en-US\" s:mustUnderstand=\"false\" />"
179 "<wsman:OptionSet xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
180 "<wsman:Option Name=\"WINRS_NOPROFILE\">TRUE</wsman:Option>"
181 "<wsman:Option Name=\"WINRS_CODEPAGE\">437</wsman:Option>"
182 "</wsman:OptionSet>"
183 "<wsman:OperationTimeout>PT60.000S</wsman:OperationTimeout>"
184 "</s:Header>"
185 "<s:Body>"
186 "<rsp:Shell xmlns:rsp=\"http://schemas.microsoft.com/wbem/wsman/1/windows/shell\">"
187 /*
188 "<rsp:Environment>"
189 "<rsp:Variable Name=\"test\">1</rsp:Variable>"
190 "</rsp:Environment>"
191 */
192 WINRM_TOKEN_WORKDIR
193 "<rsp:InputStreams>stdin</rsp:InputStreams>"
194 "<rsp:OutputStreams>stdout stderr</rsp:OutputStreams>"
195 "</rsp:Shell>"
196 "</s:Body>"
197 "</s:Envelope>";
198
199 gate_string_t* winrm_create_shell_request(gate_string_t* output,
200 gate_string_t const* target_url,
201 gate_guid_t const* message_id,
202 gate_string_t const* work_dir)
203 {
204 gate_string_t* ret = NULL;
205 gate_strbuilder_t sb;
206
207 gate_strbuilder_create(&sb, 2048);
208 gate_strbuilder_append_text(&sb, WINRM_SOAP_MSG_CREATESHELL_REQUEST, sizeof(WINRM_SOAP_MSG_CREATESHELL_REQUEST) - 1);
209
210 replace_token_with_xml_content(&sb, WINRM_TOKEN_TARGET_URL, target_url);
211 replace_token_with_guid(&sb, WINRM_TOKEN_MESSAGE_ID, message_id);
212 if (!gate_string_is_empty(work_dir))
213 {
214 gate_strbuilder_t wd_builder = GATE_INIT_EMPTY;
215 gate_string_t wd = GATE_STRING_INIT_EMPTY;
216 gate_strbuilder_create(&wd_builder, 128);
217 gate_strbuilder_append_cstr(&wd_builder, "<rsp:WorkingDirectory>");
218 gate_strbuilder_append_string(&wd_builder, work_dir);
219 gate_strbuilder_append_cstr(&wd_builder, "</rsp:WorkingDirectory>");
220 gate_strbuilder_to_string(&wd_builder, &wd);
221 replace_token_with_xml_content(&sb, WINRM_TOKEN_WORKDIR, &wd);
222 gate_strbuilder_release(&wd_builder);
223 gate_string_release(&wd);
224 }
225
226 ret = gate_strbuilder_to_string(&sb, output);
227 gate_strbuilder_release(&sb);
228 return ret;
229 }
230
231
232 static char const WINRM_SOAP_MSG_EXECUTECOMMAND_REQUEST[] =
233 "<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\">"
234 "<s:Header>"
235 "<a:To>" WINRM_TOKEN_TARGET_URL "</a:To>"
236 "<wsman:ResourceURI s:mustUnderstand=\"true\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</wsman:ResourceURI>"
237 "<a:ReplyTo><a:Address s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"
238 "<a:Action s:mustUnderstand=\"true\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command</a:Action>"
239 "<wsman:MaxEnvelopeSize s:mustUnderstand=\"true\">153600</wsman:MaxEnvelopeSize>"
240 "<a:MessageID>uuid:" WINRM_TOKEN_MESSAGE_ID "</a:MessageID>"
241 "<wsman:Locale xml:lang=\"en-US\" s:mustUnderstand=\"false\" />"
242 "<wsman:SelectorSet><wsman:Selector Name=\"ShellId\">" WINRM_TOKEN_SHELL_UID "</wsman:Selector></wsman:SelectorSet>"
243 "<wsman:OptionSet xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
244 "<wsman:Option Name=\"WINRS_CONSOLEMODE_STDIN\">TRUE</wsman:Option>"
245 /* "<wsman:Option Name=\"WINRS_SKIP_CMD_SHELL\">FALSE</wsman:Option>" */
246 "</wsman:OptionSet>"
247 "<wsman:OperationTimeout>PT60.000S</wsman:OperationTimeout>"
248 "</s:Header>"
249 "<s:Body>"
250 "<rsp:CommandLine xmlns:rsp=\"http://schemas.microsoft.com/wbem/wsman/1/windows/shell\">"
251 "<rsp:Command>" WINRM_TOKEN_SHELL_COMMAND "</rsp:Command>"
252 /* "<rsp:Arguments>" WINRM_TOKEN_SHELL_ARG "</rsp:Arguments>" */
253 WINRM_TOKEN_SHELL_ARGS
254 "</rsp:CommandLine>"
255 "</s:Body>"
256 "</s:Envelope>";
257
258 gate_string_t* winrm_shell_execute_request(gate_string_t* output,
259 gate_string_t const* target_url,
260 gate_guid_t const* message_id,
261 gate_string_t const* shell_uid,
262 gate_string_t const* shell_command,
263 gate_string_t const* shell_args, gate_size_t shell_args_count)
264 {
265 gate_string_t* ret = NULL;
266 gate_strbuilder_t sb;
267 gate_string_t cmd_args = GATE_STRING_INIT_EMPTY;
268
269 gate_strbuilder_create(&sb, 2048);
270 gate_strbuilder_append_text(&sb, WINRM_SOAP_MSG_EXECUTECOMMAND_REQUEST, sizeof(WINRM_SOAP_MSG_EXECUTECOMMAND_REQUEST) - 1);
271
272 if (shell_args_count > 0)
273 {
274 gate_strbuilder_t args_builder;
275 gate_size_t ndx;
276 gate_strbuilder_create(&args_builder, 256);
277 for (ndx = 0; ndx != shell_args_count; ++ndx)
278 {
279 gate_strbuilder_append_cstr(&args_builder, "<rsp:Arguments>");
280 gate_strbuilder_append_string(&args_builder, &shell_args[ndx]);
281 gate_strbuilder_append_cstr(&args_builder, "</rsp:Arguments>");
282 }
283
284 gate_strbuilder_to_string(&args_builder, &cmd_args);
285 gate_strbuilder_release(&args_builder);
286 }
287
288 replace_token_with_xml_content(&sb, WINRM_TOKEN_TARGET_URL, target_url);
289 replace_token_with_guid(&sb, WINRM_TOKEN_MESSAGE_ID, message_id);
290 replace_token_with_xml_content(&sb, WINRM_TOKEN_SHELL_UID, shell_uid);
291 replace_token_with_xml_content(&sb, WINRM_TOKEN_SHELL_COMMAND, shell_command);
292 replace_token_raw(&sb, WINRM_TOKEN_SHELL_ARGS, &cmd_args);
293
294 ret = gate_strbuilder_to_string(&sb, output);
295 gate_strbuilder_release(&sb);
296 gate_string_release(&cmd_args);
297 return ret;
298 }
299
300 static char const WINRM_SOAP_MSG_RECEIVEOUTPUT_REQUEST[] =
301 "<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\">"
302 "<s:Header>"
303 "<a:To>" WINRM_TOKEN_TARGET_URL "</a:To>"
304 "<a:ReplyTo><a:Address s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"
305 "<a:Action s:mustUnderstand=\"true\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive</a:Action>"
306 "<wsman:MaxEnvelopeSize s:mustUnderstand=\"true\">153600</wsman:MaxEnvelopeSize>"
307 "<a:MessageID>uuid:" WINRM_TOKEN_MESSAGE_ID "</a:MessageID>"
308 "<wsman:Locale xml:lang=\"en-US\" s:mustUnderstand=\"false\" />"
309 "<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>"
310 "<wsman:SelectorSet xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\" xmlns=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
311 "<wsman:Selector Name=\"ShellId\">" WINRM_TOKEN_SHELL_UID "</wsman:Selector>"
312 "</wsman:SelectorSet>"
313 "<wsman:OperationTimeout>" WINRM_TOKEN_TIMEOUT "</wsman:OperationTimeout>"
314 "</s:Header>"
315 "<s:Body>"
316 "<rsp:Receive xmlns:rsp=\"http://schemas.microsoft.com/wbem/wsman/1/windows/shell\" SequenceId=\"0\">"
317 "<rsp:DesiredStream CommandId=\"" WINRM_TOKEN_COMMAND_UID "\">stdout stderr</rsp:DesiredStream>"
318 "</rsp:Receive>"
319 "</s:Body>"
320 "</s:Envelope>";
321
322
323 gate_string_t* winrm_shell_receiveoutput_request(gate_string_t* output,
324 gate_string_t const* target_url,
325 gate_guid_t const* message_id,
326 gate_string_t const* shell_id,
327 gate_string_t const* command_id,
328 gate_uint32_t timeout_ms)
329 {
330 gate_string_t* ret = NULL;
331 gate_strbuilder_t sb;
332
333 gate_strbuilder_create(&sb, 2048);
334 gate_strbuilder_append_text(&sb, WINRM_SOAP_MSG_RECEIVEOUTPUT_REQUEST, sizeof(WINRM_SOAP_MSG_RECEIVEOUTPUT_REQUEST) - 1);
335
336 replace_token_with_xml_content(&sb, WINRM_TOKEN_TARGET_URL, target_url);
337 replace_token_with_guid(&sb, WINRM_TOKEN_MESSAGE_ID, message_id);
338 replace_token_with_xml_content(&sb, WINRM_TOKEN_SHELL_UID, shell_id);
339 replace_token_with_xml_content(&sb, WINRM_TOKEN_COMMAND_UID, command_id);
340 replace_token_with_timeout(&sb, WINRM_TOKEN_TIMEOUT, timeout_ms);
341
342 ret = gate_strbuilder_to_string(&sb, output);
343 gate_strbuilder_release(&sb);
344 return ret;
345 }
346
347 static char const WINRM_SOAP_MSG_SENDINPUT_REQUEST[] =
348 "<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\">"
349 "<s:Header>"
350 "<a:To>" WINRM_TOKEN_TARGET_URL "</a:To>"
351 "<a:ReplyTo><a:Address s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"
352 "<a:Action s:mustUnderstand=\"true\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send</a:Action>"
353 "<wsman:MaxEnvelopeSize s:mustUnderstand=\"true\">153600</wsman:MaxEnvelopeSize>"
354 "<a:MessageID>uuid:" WINRM_TOKEN_MESSAGE_ID "</a:MessageID>"
355 "<wsman:Locale xml:lang=\"en-US\" s:mustUnderstand=\"false\" />"
356 "<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>"
357 "<wsman:SelectorSet xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\" xmlns=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
358 "<wsman:Selector Name=\"ShellId\">uuid:" WINRM_TOKEN_SHELL_UID "</wsman:Selector>"
359 "</wsman:SelectorSet>"
360 "<wsman:OperationTimeout>PT60.000S</wsman:OperationTimeout>"
361 "</s:Header>"
362 "<s:Body>"
363 "<rsp:Send xmlns:rsp=\"http://schemas.microsoft.com/wbem/wsman/1/windows/shell\">"
364 "<rsp:Stream xmlns:rsp=\"http://schemas.microsoft.com/wbem/wsman/1/windows/shell\" Name=\"stdin\" CommandId=\"" WINRM_TOKEN_COMMAND_UID "\">"
365 WINRM_TOKEN_COMMAND_INPUT
366 "</rsp:Stream>"
367 "</rsp:Send>"
368 "</s:Body>"
369 "</s:Envelope>";
370
371 gate_string_t* winrm_shell_sendinput_request(gate_string_t* output,
372 gate_string_t const* target_url,
373 gate_guid_t const* message_id,
374 gate_string_t const* shell_id,
375 gate_string_t const* command_id,
376 gate_string_t const* input)
377 {
378 gate_string_t* ret = NULL;
379 gate_strbuilder_t sb;
380
381 gate_strbuilder_create(&sb, 2048);
382 gate_strbuilder_append_text(&sb, WINRM_SOAP_MSG_SENDINPUT_REQUEST, sizeof(WINRM_SOAP_MSG_SENDINPUT_REQUEST) - 1);
383
384 replace_token_with_xml_content(&sb, WINRM_TOKEN_TARGET_URL, target_url);
385 replace_token_with_guid(&sb, WINRM_TOKEN_MESSAGE_ID, message_id);
386 replace_token_with_xml_content(&sb, WINRM_TOKEN_SHELL_UID, shell_id);
387 replace_token_with_xml_content(&sb, WINRM_TOKEN_COMMAND_UID, command_id);
388 replace_token_with_xml_content(&sb, WINRM_TOKEN_COMMAND_INPUT, input);
389
390 ret = gate_strbuilder_to_string(&sb, output);
391 gate_strbuilder_release(&sb);
392 return ret;
393 }
394
395 static char const WINRM_SOAP_MSG_TERMINATEOPERATION_REQUEST[] =
396 "<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\">"
397 "<s:Header>"
398 "<a:To>" WINRM_TOKEN_TARGET_URL "</a:To>"
399 "<a:ReplyTo><a:Address s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"
400 "<a:Action s:mustUnderstand=\"true\">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal</a:Action>"
401 "<wsman:MaxEnvelopeSize s:mustUnderstand=\"true\">153600</wsman:MaxEnvelopeSize>"
402 "<wsa:MessageID>uuid:" WINRM_TOKEN_MESSAGE_ID "</wsa:MessageID>"
403 "<wsman:Locale xml:lang=\"en-US\" s:mustUnderstand=\"false\"/>"
404 "<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>"
405 "<wsman:SelectorSet xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\" xmlns=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
406 "<wsman:Selector Name=\"ShellId\">uuid:" WINRM_TOKEN_SHELL_UID "</wsman:Selector>"
407 "</wsman:SelectorSet>"
408 "<wsman:OperationTimeout>PT60.000S</wsman:OperationTimeout>"
409 "</s:Header>"
410 "<s:Body>"
411 "<rsp:Signal xmlns:rsp=\"http://schemas.microsoft.com/wbem/wsman/1/windows/shell\" CommandId=\"" WINRM_TOKEN_COMMAND_UID "\">"
412 "<rsp:Code>http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/ctrl_c</rsp:Code>"
413 "</rsp:Signal>"
414 "</s:Body>"
415 "</s:Envelope>";
416
417 gate_string_t* winrm_shell_terminate_request(gate_string_t* output,
418 gate_string_t const* target_url,
419 gate_guid_t const* message_id,
420 gate_string_t const* shell_id,
421 gate_string_t const* command_id)
422 {
423 gate_string_t* ret = NULL;
424 gate_strbuilder_t sb;
425
426 gate_strbuilder_create(&sb, 2048);
427 gate_strbuilder_append_text(&sb, WINRM_SOAP_MSG_TERMINATEOPERATION_REQUEST, sizeof(WINRM_SOAP_MSG_TERMINATEOPERATION_REQUEST) - 1);
428
429 replace_token_with_xml_content(&sb, WINRM_TOKEN_TARGET_URL, target_url);
430 replace_token_with_guid(&sb, WINRM_TOKEN_MESSAGE_ID, message_id);
431 replace_token_with_xml_content(&sb, WINRM_TOKEN_SHELL_UID, shell_id);
432 replace_token_with_xml_content(&sb, WINRM_TOKEN_COMMAND_UID, command_id);
433
434 ret = gate_strbuilder_to_string(&sb, output);
435 gate_strbuilder_release(&sb);
436 return ret;
437 }
438
439 static char const WINRM_SOAP_MSG_DELETESHELL_REQUEST[] =
440 "<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\">"
441 "<s:Header>"
442 "<a:To>" WINRM_TOKEN_TARGET_URL "</a:To>"
443 "<a:ReplyTo><a:Address s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"
444 "<a:Action s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete</a:Action>"
445 "<wsman:MaxEnvelopeSize s:mustUnderstand=\"true\">153600</wsman:MaxEnvelopeSize>"
446 "<a:MessageID>uuid:" WINRM_TOKEN_MESSAGE_ID "</a:MessageID>"
447 "<wsman:Locale xml:lang=\"en-US\" s:mustUnderstand=\"false\" />"
448 "<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>"
449 "<wsman:SelectorSet xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\" xmlns=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">"
450 "<wsman:Selector Name=\"ShellId\">uuid:" WINRM_TOKEN_SHELL_UID "</wsman:Selector>"
451 "</wsman:SelectorSet>"
452 "<wsman:OperationTimeout>PT60.000S</wsman:OperationTimeout>"
453 "</s:Header>"
454 "<s:Body></s:Body>"
455 "</s:Envelope>";
456
457 gate_string_t* winrm_delete_shell_request(gate_string_t* output,
458 gate_string_t const* target_url,
459 gate_guid_t const* message_id,
460 gate_string_t const* shell_id)
461 {
462 gate_string_t* ret = NULL;
463 gate_strbuilder_t sb;
464
465 gate_strbuilder_create(&sb, 2048);
466 gate_strbuilder_append_text(&sb, WINRM_SOAP_MSG_DELETESHELL_REQUEST, sizeof(WINRM_SOAP_MSG_DELETESHELL_REQUEST) - 1);
467
468 replace_token_with_xml_content(&sb, WINRM_TOKEN_TARGET_URL, target_url);
469 replace_token_with_guid(&sb, WINRM_TOKEN_MESSAGE_ID, message_id);
470 replace_token_with_xml_content(&sb, WINRM_TOKEN_SHELL_UID, shell_id);
471
472 ret = gate_strbuilder_to_string(&sb, output);
473 gate_strbuilder_release(&sb);
474 return ret;
475 }
476
477
478 gate_result_t gate_winrmclient_create(gate_winrmclient_t* client,
479 gate_string_t const* server, gate_uint16_t port,
480 gate_string_t const* user, gate_string_t const* password,
481 gate_enumint_t flags)
482 {
483 static gate_string_t const wsman_path = GATE_STRING_INIT_STATIC("/wsman");
484 gate_result_t ret = GATE_RESULT_FAILED;
485 gate_bool_t const secure_flag = GATE_FLAG_ENABLED(flags, GATE_WINRMCLIENT_FLAG_SECURE);
486
487 do
488 {
489 if (!client || !server)
490 {
491 ret = GATE_RESULT_INVALIDARG;
492 break;
493 }
494
495 gate_mem_clear(client, sizeof(gate_winrmclient_t));
496
497 ret = gate_uri_init(&client->uri);
498 GATE_BREAK_IF_FAILED(ret);
499
500 gate_string_create_static(&client->uri.scheme, secure_flag ? GATE_URI_SCHEME_HTTPS : GATE_URI_SCHEME_HTTP);
501
502 if (NULL == gate_string_clone(&client->uri.host, server))
503 {
504 ret = GATE_RESULT_OUTOFMEMORY;
505 break;
506 }
507
508 if (NULL == gate_string_clone(&client->uri.absolute_path, &wsman_path))
509 {
510 ret = GATE_RESULT_OUTOFMEMORY;
511 break;
512 }
513
514 if (!gate_string_is_empty(user) || !gate_string_is_empty(password))
515 {
516 ret = gate_uri_build_user_info(user, password, &client->uri.user_info);
517 GATE_BREAK_IF_FAILED(ret);
518 }
519
520 client->uri.port = (port == 0) ? (secure_flag ? 5986 : 5985) : port;
521
522 ret = GATE_RESULT_OK;
523
524 } while (0);
525
526 if (client && GATE_FAILED(ret))
527 {
528 gate_uri_destroy(&client->uri);
529 }
530 return ret;
531 }
532
533 gate_result_t gate_winrmclient_release(gate_winrmclient_t* client)
534 {
535 gate_result_t ret;
536
537 GATE_DEBUG_ASSERT(client != NULL);
538 do
539 {
540 ret = gate_uri_destroy(&client->uri);
541 } while (0);
542
543 return ret;
544 }
545
546 static gate_string_t* winrm_create_target_url(gate_string_t* target_url, gate_winrmclient_t* from_client)
547 {
548 gate_string_t* ret = NULL;
549 gate_uri_t uri;
550
551 do
552 {
553 gate_result_t result;
554 gate_mem_clear(&uri, sizeof(uri));
555
556 result = gate_uri_copy(&uri, &from_client->uri);
557 GATE_BREAK_IF_FAILED(result);
558
559 gate_string_release(&uri.user_info);
560 gate_string_release(&uri.query);
561
562 result = gate_uri_to_string(&uri, target_url, false);
563 GATE_BREAK_IF_FAILED(result);
564
565 ret = target_url;
566 } while (0);
567
568 gate_uri_destroy(&uri);
569 return ret;
570 }
571
572 static gate_string_t* winrm_create_shell_cmd_uri(gate_string_t* target_uri)
573 {
574 return gate_string_duplicate(target_uri, &winrm_uri_shell_cmd);
575 }
576
577 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)
578 {
579 gate_result_t ret;
580 gate_httpclient_t http = GATE_INIT_EMPTY;
581 gate_http_request_t req = GATE_INIT_EMPTY;
582 gate_http_response_t resp = GATE_INIT_EMPTY;
583 gate_memstream_impl_t upload_impl;
584 gate_memstream_t* ptr_upload_stream = NULL;
585 gate_size_t upload_size = gate_string_length(soap_request);
586 gate_stringstream_t* ss = NULL;
587 static gate_string_t const http_post = GATE_STRING_INIT_STATIC(GATE_HTTP_METHOD_POST);
588 static gate_string_t const header_content_type = GATE_STRING_INIT_STATIC(GATE_HTTP_HEADER_CONTENTTYPE);
589 static gate_string_t const header_content_type_value = GATE_STRING_INIT_STATIC("application/soap+xml;charset=UTF-8");
590
591 do
592 {
593 ret = gate_httpclient_create(&http, &client->uri.host, client->uri.port, 0);
594 GATE_BREAK_IF_FAILED(ret);
595
596 ret = gate_http_request_init_uri(&req, &http_post, &client->uri);
597 GATE_BREAK_IF_FAILED(ret);
598
599 req.upload_stream = (gate_stream_t*)gate_memstream_create_static_unmanaged_readonly(
600 &upload_impl, gate_string_ptr(soap_request, 0), upload_size, upload_size);
601
602 gate_map_add(&req.headers, &header_content_type, &header_content_type_value);
603
604 ret = gate_httpclient_send_request(&http, &req, &resp);
605 GATE_BREAK_IF_FAILED(ret);
606
607 ss = gate_stringstream_create(256);
608 if (ss == NULL)
609 {
610 ret = GATE_RESULT_OUTOFMEMORY;
611 break;
612 }
613
614 if (ptr_status_code)
615 {
616 *ptr_status_code = resp.status_code;
617 }
618
619 ret = gate_stream_transfer(resp.response_stream, (gate_stream_t*)ss);
620 GATE_BREAK_IF_FAILED(ret);
621
622 ret = gate_stringstream_to_string(ss, ptr_soap_response);
623 GATE_BREAK_IF_FAILED(ret);
624
625 /* success case */
626 ret = GATE_RESULT_OK;
627 } while (0);
628
629 gate_http_response_release(&resp);
630 gate_http_request_release(&req);
631 gate_httpclient_release(&http);
632
633 return ret;
634 }
635
636 static gate_result_t winrm_prepare_request(gate_winrmclient_t* client, gate_guid_t* ptr_new_msg_id, gate_string_t* ptr_target_url)
637 {
638 gate_result_t ret = gate_guid_generate(ptr_new_msg_id);
639 if (GATE_SUCCEEDED(ret))
640 {
641 if (NULL == winrm_create_target_url(ptr_target_url, client))
642 {
643 ret = GATE_RESULT_OUTOFMEMORY;
644 }
645 }
646 return ret;
647 }
648
649 static gate_result_t check_status_ok(gate_uint32_t status_code)
650 {
651 switch (status_code)
652 {
653 case 200: return GATE_RESULT_OK;
654 default: return GATE_RESULT_FAILED;
655 }
656 }
657
658 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)
659 {
660 gate_result_t ret;
661 gate_string_t soap_request = GATE_STRING_INIT_EMPTY;
662 gate_string_t target_url = GATE_STRING_INIT_EMPTY;
663 gate_guid_t msg_id = GATE_INIT_EMPTY;
664 gate_uint32_t status_code = 0;
665 gate_string_t soap_response = GATE_STRING_INIT_EMPTY;
666 static gate_string_t const response_shell_id_begin = GATE_STRING_INIT_STATIC("ShellId>");
667 static gate_string_t const response_shell_id_end = GATE_STRING_INIT_STATIC("</");
668
669 do
670 {
671 ret = winrm_prepare_request(client, &msg_id, &target_url);
672 GATE_BREAK_IF_FAILED(ret);
673
674 if (NULL == winrm_create_shell_request(&soap_request, &target_url, &msg_id, workdir))
675 {
676 ret = GATE_RESULT_OUTOFMEMORY;
677 break;
678 }
679
680 ret = winrm_send_soap_request(client, &soap_request, &status_code, &soap_response);
681 GATE_BREAK_IF_FAILED(ret);
682
683 ret = check_status_ok(status_code);
684 GATE_BREAK_IF_FAILED(ret);
685
686 if (!extract_xml_content(&soap_response, &response_shell_id_begin, &response_shell_id_end, shell_id))
687 {
688 ret = GATE_RESULT_INVALIDCONTENT;
689 break;
690 }
691
692 ret = GATE_RESULT_OK;
693 } while (0);
694
695 gate_string_release(&soap_response);
696 gate_string_release(&soap_request);
697 gate_string_release(&target_url);
698
699 return ret;
700 }
701
702 gate_result_t gate_winrmclient_shell_delete(gate_winrmclient_t* client, gate_string_t const* shell_id)
703 {
704 gate_result_t ret = GATE_RESULT_FAILED;
705 gate_guid_t msg_id = GATE_INIT_EMPTY;
706 gate_string_t target_url = GATE_STRING_INIT_EMPTY;
707 gate_string_t soap_request = GATE_STRING_INIT_EMPTY;
708 gate_string_t soap_response = GATE_STRING_INIT_EMPTY;
709 gate_uint32_t status_code = 0;
710
711 do
712 {
713 ret = winrm_prepare_request(client, &msg_id, &target_url);
714 GATE_BREAK_IF_FAILED(ret);
715
716 if (NULL == winrm_delete_shell_request(&soap_request, &target_url, &msg_id, shell_id))
717 {
718 ret = GATE_RESULT_OUTOFMEMORY;
719 break;
720 }
721
722 ret = winrm_send_soap_request(client, &soap_request, &status_code, &soap_response);
723 GATE_BREAK_IF_FAILED(ret);
724
725 ret = check_status_ok(status_code);
726 GATE_BREAK_IF_FAILED(ret);
727
728 ret = GATE_RESULT_OK;
729 } while (0);
730
731 gate_string_release(&soap_request);
732 gate_string_release(&soap_response);
733 gate_string_release(&target_url);
734 return ret;
735 }
736
737
738 gate_result_t gate_winrmclient_shell_command_start(
739 gate_winrmclient_t* client, gate_string_t const* shell_id, gate_string_t const* command, gate_array_t const* args,
740 gate_string_t* command_id)
741 {
742 gate_result_t ret = GATE_RESULT_FAILED;
743 gate_guid_t msg_id = GATE_INIT_EMPTY;
744 gate_string_t target_url = GATE_STRING_INIT_EMPTY;
745 gate_string_t soap_request = GATE_STRING_INIT_EMPTY;
746 gate_string_t soap_response = GATE_STRING_INIT_EMPTY;
747 gate_uint32_t status_code = 0;
748
749 do
750 {
751 static gate_string_t const resp_command_id_begin = GATE_STRING_INIT_STATIC("CommandId>");
752 static gate_string_t const resp_command_id_end = GATE_STRING_INIT_STATIC("</");
753
754 ret = winrm_prepare_request(client, &msg_id, &target_url);
755 GATE_BREAK_IF_FAILED(ret);
756
757 if (NULL == winrm_shell_execute_request(&soap_request, &target_url, &msg_id, shell_id, command, NULL, 0))
758 {
759 ret = GATE_RESULT_OUTOFMEMORY;
760 break;
761 }
762
763 ret = winrm_send_soap_request(client, &soap_request, &status_code, &soap_response);
764 GATE_BREAK_IF_FAILED(ret);
765
766 ret = check_status_ok(status_code);
767 GATE_BREAK_IF_FAILED(ret);
768
769 if (!extract_xml_content(&soap_response, &resp_command_id_begin, &resp_command_id_end, command_id))
770 {
771 ret = GATE_RESULT_INVALIDCONTENT;
772 break;
773 }
774
775 ret = GATE_RESULT_OK;
776 } while (0);
777
778 gate_string_release(&soap_request);
779 gate_string_release(&soap_response);
780 gate_string_release(&target_url);
781 return ret;
782 }
783
784
785 gate_result_t gate_winrmclient_shell_command_abort(gate_winrmclient_t* client,
786 gate_string_t const* shell_id, gate_string_t const* command_id)
787 {
788 gate_result_t ret = GATE_RESULT_FAILED;
789 gate_guid_t msg_id = GATE_INIT_EMPTY;
790 gate_string_t target_url = GATE_STRING_INIT_EMPTY;
791 gate_string_t soap_request = GATE_STRING_INIT_EMPTY;
792 gate_string_t soap_response = GATE_STRING_INIT_EMPTY;
793 gate_uint32_t status_code = 0;
794
795 do
796 {
797 static gate_string_t const resp_command_id_begin = GATE_STRING_INIT_STATIC("CommandId>");
798 static gate_string_t const resp_command_id_end = GATE_STRING_INIT_STATIC("</");
799
800 ret = winrm_prepare_request(client, &msg_id, &target_url);
801 GATE_BREAK_IF_FAILED(ret);
802
803 if (NULL == winrm_shell_terminate_request(&soap_request, &target_url, &msg_id, shell_id, command_id))
804 {
805 ret = GATE_RESULT_OUTOFMEMORY;
806 break;
807 }
808
809 ret = winrm_send_soap_request(client, &soap_request, &status_code, &soap_response);
810 GATE_BREAK_IF_FAILED(ret);
811
812 ret = check_status_ok(status_code);
813 GATE_BREAK_IF_FAILED(ret);
814
815 ret = GATE_RESULT_OK;
816 } while (0);
817
818 gate_string_release(&soap_request);
819 gate_string_release(&soap_response);
820 gate_string_release(&target_url);
821 return ret;
822 }
823
824
825 gate_result_t gate_winrmclient_shell_command_send(gate_winrmclient_t* client,
826 gate_string_t const* shell_id, gate_string_t const* command_id, gate_blob_t const* data_buffer)
827 {
828 gate_result_t ret = GATE_RESULT_FAILED;
829 gate_guid_t msg_id = GATE_INIT_EMPTY;
830 gate_string_t target_url = GATE_STRING_INIT_EMPTY;
831 gate_string_t soap_request = GATE_STRING_INIT_EMPTY;
832 gate_string_t soap_response = GATE_STRING_INIT_EMPTY;
833 gate_string_t input_b64 = GATE_STRING_INIT_EMPTY;
834 gate_uint32_t status_code = 0;
835 char const* ptr_data = (char const*)gate_blob_data(data_buffer);
836 gate_size_t data_len = gate_blob_length(data_buffer);
837 gate_string_t input_raw = { ptr_data, data_len, NULL };
838 gate_strbuilder_t input_builder = GATE_INIT_EMPTY;
839
840 do
841 {
842 /* encode input as base64 string */
843 gate_strbuilder_create(&input_builder, data_len * 4 / 3 + 3);
844 gate_base64_encode(&input_raw, &input_builder);
845 gate_strbuilder_to_string(&input_builder, &input_b64);
846
847 ret = winrm_prepare_request(client, &msg_id, &target_url);
848 GATE_BREAK_IF_FAILED(ret);
849
850 if (NULL == winrm_shell_sendinput_request(&soap_request, &target_url, &msg_id, shell_id, command_id, &input_b64))
851 {
852 ret = GATE_RESULT_OUTOFMEMORY;
853 break;
854 }
855
856 ret = winrm_send_soap_request(client, &soap_request, &status_code, &soap_response);
857 GATE_BREAK_IF_FAILED(ret);
858
859 ret = check_status_ok(status_code);
860 GATE_BREAK_IF_FAILED(ret);
861
862 ret = GATE_RESULT_OK;
863 } while (0);
864
865 gate_strbuilder_release(&input_builder);
866 gate_string_release(&input_b64);
867 gate_string_release(&soap_request);
868 gate_string_release(&soap_response);
869 gate_string_release(&target_url);
870 return ret;
871
872 }
873
874
875 gate_result_t gate_winrmclient_shell_command_receive(gate_winrmclient_t* client,
876 gate_string_t const* shell_id, gate_string_t const* command_id, gate_blob_t* data_buffer)
877 {
878 gate_result_t ret = GATE_RESULT_FAILED;
879 gate_guid_t msg_id = GATE_INIT_EMPTY;
880 gate_string_t target_url = GATE_STRING_INIT_EMPTY;
881 gate_string_t soap_request = GATE_STRING_INIT_EMPTY;
882 gate_string_t soap_response = GATE_STRING_INIT_EMPTY;
883 gate_uint32_t status_code = 0;
884
885 do
886 {
887 static gate_string_t const resp_command_id_begin = GATE_STRING_INIT_STATIC("CommandId>");
888 static gate_string_t const resp_command_id_end = GATE_STRING_INIT_STATIC("</");
889
890 ret = winrm_prepare_request(client, &msg_id, &target_url);
891 GATE_BREAK_IF_FAILED(ret);
892
893 if (NULL == winrm_shell_receiveoutput_request(&soap_request, &target_url, &msg_id, shell_id, command_id, 5000))
894 {
895 ret = GATE_RESULT_OUTOFMEMORY;
896 break;
897 }
898
899 ret = winrm_send_soap_request(client, &soap_request, &status_code, &soap_response);
900 GATE_BREAK_IF_FAILED(ret);
901
902 ret = check_status_ok(status_code);
903 GATE_BREAK_IF_FAILED(ret);
904
905 /* TODO: SOAP response decoding */
906
907 gate_blob_create_empty(data_buffer);
908
909 ret = GATE_RESULT_OK;
910 } while (0);
911
912 gate_string_release(&soap_request);
913 gate_string_release(&soap_response);
914 gate_string_release(&target_url);
915 return ret;
916
917 }
918
919