rdpdr-messages.c (14925B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. 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, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "channels/rdpdr/rdpdr-messages.h" 21#include "channels/rdpdr/rdpdr.h" 22#include "rdp.h" 23#include "settings.h" 24 25#include <freerdp/channels/rdpdr.h> 26#include <guacamole/client.h> 27#include <winpr/stream.h> 28 29#include <stdlib.h> 30#include <string.h> 31 32/** 33 * Sends a Client Announce Reply message. The Client Announce Reply message is 34 * required to be sent in response to the Server Announce Request message. See: 35 * 36 * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/d6fe6d1b-c145-4a6f-99aa-4fe3cdcea398 37 * 38 * @param svc 39 * The guac_rdp_common_svc representing the static virtual channel being 40 * used for RDPDR. 41 * 42 * @param major 43 * The major version of the RDPDR protocol in use. This value must always 44 * be 1. 45 * 46 * @param minor 47 * The minor version of the RDPDR protocol in use. This value must be 48 * either 2, 5, 10, 12, or 13. 49 * 50 * @param client_id 51 * The client ID received in the Server Announce Request, or a randomly 52 * generated ID. 53 */ 54static void guac_rdpdr_send_client_announce_reply(guac_rdp_common_svc* svc, 55 unsigned int major, unsigned int minor, unsigned int client_id) { 56 57 wStream* output_stream = Stream_New(NULL, 12); 58 59 /* Write header */ 60 Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE); 61 Stream_Write_UINT16(output_stream, PAKID_CORE_CLIENTID_CONFIRM); 62 63 /* Write content */ 64 Stream_Write_UINT16(output_stream, major); 65 Stream_Write_UINT16(output_stream, minor); 66 Stream_Write_UINT32(output_stream, client_id); 67 68 guac_rdp_common_svc_write(svc, output_stream); 69 70} 71 72/** 73 * Sends a Client Name Request message. The Client Name Request message is used 74 * by the client to announce its own name. See: 75 * 76 * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/902497f1-3b1c-4aee-95f8-1668f9b7b7d2 77 * 78 * @param svc 79 * The guac_rdp_common_svc representing the static virtual channel being 80 * used for RDPDR. 81 * 82 * @param name 83 * The name that should be used for the client. 84 */ 85static void guac_rdpdr_send_client_name_request(guac_rdp_common_svc* svc, 86 const char* name) { 87 88 int name_bytes = strlen(name) + 1; 89 wStream* output_stream = Stream_New(NULL, 16 + name_bytes); 90 91 /* Write header */ 92 Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE); 93 Stream_Write_UINT16(output_stream, PAKID_CORE_CLIENT_NAME); 94 95 /* Write content */ 96 Stream_Write_UINT32(output_stream, 0); /* ASCII */ 97 Stream_Write_UINT32(output_stream, 0); /* 0 required by RDPDR spec */ 98 Stream_Write_UINT32(output_stream, name_bytes); 99 Stream_Write(output_stream, name, name_bytes); 100 101 guac_rdp_common_svc_write(svc, output_stream); 102 103} 104 105/** 106 * Sends a Client Core Capability Response message. The Client Core Capability 107 * Response message is used to announce the client's capabilities, in response 108 * to receiving the server's capabilities via a Server Core Capability Request. 109 * See: 110 * 111 * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/f513bf87-cca0-488a-ac5c-18cf18f4a7e1 112 * 113 * @param svc 114 * The guac_rdp_common_svc representing the static virtual channel being 115 * used for RDPDR. 116 */ 117static void guac_rdpdr_send_client_capability(guac_rdp_common_svc* svc) { 118 119 wStream* output_stream = Stream_New(NULL, 256); 120 guac_client_log(svc->client, GUAC_LOG_DEBUG, "Sending capabilities..."); 121 122 /* Write header */ 123 Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE); 124 Stream_Write_UINT16(output_stream, PAKID_CORE_CLIENT_CAPABILITY); 125 126 /* Capability count + padding */ 127 Stream_Write_UINT16(output_stream, 3); 128 Stream_Write_UINT16(output_stream, 0); /* Padding */ 129 130 /* General capability header */ 131 Stream_Write_UINT16(output_stream, CAP_GENERAL_TYPE); 132 Stream_Write_UINT16(output_stream, 44); 133 Stream_Write_UINT32(output_stream, GENERAL_CAPABILITY_VERSION_02); 134 135 /* General capability data */ 136 Stream_Write_UINT32(output_stream, GUAC_OS_TYPE); /* osType - required to be ignored */ 137 Stream_Write_UINT32(output_stream, 0); /* osVersion */ 138 Stream_Write_UINT16(output_stream, 1); /* protocolMajor - must be set to 1 */ 139 Stream_Write_UINT16(output_stream, RDPDR_MINOR_RDP_VERSION_5_2); /* protocolMinor */ 140 Stream_Write_UINT32(output_stream, 0xFFFF); /* ioCode1 */ 141 Stream_Write_UINT32(output_stream, 0); /* ioCode2 */ 142 Stream_Write_UINT32(output_stream, 143 RDPDR_DEVICE_REMOVE_PDUS 144 | RDPDR_CLIENT_DISPLAY_NAME_PDU 145 | RDPDR_USER_LOGGEDON_PDU); /* extendedPDU */ 146 Stream_Write_UINT32(output_stream, 0); /* extraFlags1 */ 147 Stream_Write_UINT32(output_stream, 0); /* extraFlags2 */ 148 Stream_Write_UINT32(output_stream, 0); /* SpecialTypeDeviceCap */ 149 150 /* Printer support header */ 151 Stream_Write_UINT16(output_stream, CAP_PRINTER_TYPE); 152 Stream_Write_UINT16(output_stream, 8); 153 Stream_Write_UINT32(output_stream, PRINT_CAPABILITY_VERSION_01); 154 155 /* Drive support header */ 156 Stream_Write_UINT16(output_stream, CAP_DRIVE_TYPE); 157 Stream_Write_UINT16(output_stream, 8); 158 Stream_Write_UINT32(output_stream, DRIVE_CAPABILITY_VERSION_02); 159 160 guac_rdp_common_svc_write(svc, output_stream); 161 guac_client_log(svc->client, GUAC_LOG_DEBUG, "Capabilities sent."); 162 163} 164 165/** 166 * Sends a Client Device List Announce Request message. The Client Device List 167 * Announce Request message is used by the client to enumerate all devices 168 * which should be made available within the RDP session via RDPDR. See: 169 * 170 * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/10ef9ada-cba2-4384-ab60-7b6290ed4a9a 171 * 172 * @param svc 173 * The guac_rdp_common_svc representing the static virtual channel being 174 * used for RDPDR. 175 */ 176static void guac_rdpdr_send_client_device_list_announce_request(guac_rdp_common_svc* svc) { 177 178 guac_rdpdr* rdpdr = (guac_rdpdr*) svc->data; 179 180 /* Calculate number of bytes needed for the stream */ 181 int streamBytes = 16; 182 for (int i=0; i < rdpdr->devices_registered; i++) 183 streamBytes += rdpdr->devices[i].device_announce_len; 184 185 /* Allocate the stream */ 186 wStream* output_stream = Stream_New(NULL, streamBytes); 187 188 /* Write header */ 189 Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE); 190 Stream_Write_UINT16(output_stream, PAKID_CORE_DEVICELIST_ANNOUNCE); 191 192 /* Get the stream for each of the devices. */ 193 Stream_Write_UINT32(output_stream, rdpdr->devices_registered); 194 for (int i=0; i<rdpdr->devices_registered; i++) { 195 196 Stream_Write(output_stream, 197 Stream_Buffer(rdpdr->devices[i].device_announce), 198 rdpdr->devices[i].device_announce_len); 199 200 guac_client_log(svc->client, GUAC_LOG_DEBUG, "Registered device %i (%s)", 201 rdpdr->devices[i].device_id, rdpdr->devices[i].device_name); 202 203 } 204 205 guac_rdp_common_svc_write(svc, output_stream); 206 guac_client_log(svc->client, GUAC_LOG_DEBUG, "All supported devices sent."); 207 208} 209 210void guac_rdpdr_process_server_announce(guac_rdp_common_svc* svc, 211 wStream* input_stream) { 212 213 unsigned int major, minor, client_id; 214 215 /* Stream should contain at least 8 bytes (UINT16 + UINT16 + UINT32) */ 216 if (Stream_GetRemainingLength(input_stream) < 8) { 217 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Announce " 218 "Request PDU does not contain the expected number of bytes. " 219 "Device redirection may not work as expected."); 220 return; 221 } 222 223 Stream_Read_UINT16(input_stream, major); 224 Stream_Read_UINT16(input_stream, minor); 225 Stream_Read_UINT32(input_stream, client_id); 226 227 /* Must choose own client ID if minor not >= 12 */ 228 if (minor < 12) 229 client_id = random() & 0xFFFF; 230 231 guac_client_log(svc->client, GUAC_LOG_INFO, "Connected to RDPDR %u.%u as client 0x%04x", major, minor, client_id); 232 233 /* Respond to announce */ 234 guac_rdpdr_send_client_announce_reply(svc, major, minor, client_id); 235 236 /* Name request */ 237 guac_rdpdr_send_client_name_request(svc, ((guac_rdp_client*) svc->client->data)->settings->client_name); 238 239} 240 241void guac_rdpdr_process_clientid_confirm(guac_rdp_common_svc* svc, 242 wStream* input_stream) { 243 guac_client_log(svc->client, GUAC_LOG_DEBUG, "Client ID confirmed"); 244} 245 246void guac_rdpdr_process_device_reply(guac_rdp_common_svc* svc, 247 wStream* input_stream) { 248 249 guac_rdpdr* rdpdr = (guac_rdpdr*) svc->data; 250 251 unsigned int device_id, ntstatus; 252 int severity, c, n, facility, code; 253 254 /* Stream should contain at least 8 bytes (UINT32 + UINT32 ) */ 255 if (Stream_GetRemainingLength(input_stream) < 8) { 256 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Device Announce" 257 "Response PDU does not contain the expected number of bytes." 258 "Device redirection may not work as expected."); 259 return; 260 } 261 262 Stream_Read_UINT32(input_stream, device_id); 263 Stream_Read_UINT32(input_stream, ntstatus); 264 265 severity = (ntstatus & 0xC0000000) >> 30; 266 c = (ntstatus & 0x20000000) >> 29; 267 n = (ntstatus & 0x10000000) >> 28; 268 facility = (ntstatus & 0x0FFF0000) >> 16; 269 code = ntstatus & 0x0000FFFF; 270 271 /* Log error / information */ 272 if (device_id < rdpdr->devices_registered) { 273 274 if (severity == 0x0) 275 guac_client_log(svc->client, GUAC_LOG_DEBUG, "Device %i (%s) connected successfully", 276 device_id, rdpdr->devices[device_id].device_name); 277 278 else 279 guac_client_log(svc->client, GUAC_LOG_ERROR, "Problem connecting device %i (%s): " 280 "severity=0x%x, c=0x%x, n=0x%x, facility=0x%x, code=0x%x", 281 device_id, rdpdr->devices[device_id].device_name, 282 severity, c, n, facility, code); 283 284 } 285 286 else 287 guac_client_log(svc->client, GUAC_LOG_ERROR, "Unknown device ID: 0x%08x", device_id); 288 289} 290 291void guac_rdpdr_process_device_iorequest(guac_rdp_common_svc* svc, 292 wStream* input_stream) { 293 294 guac_rdpdr* rdpdr = (guac_rdpdr*) svc->data; 295 guac_rdpdr_iorequest iorequest; 296 297 /* Check to make sure the Stream contains at least 20 bytes (5 x UINT32 ). */ 298 if (Stream_GetRemainingLength(input_stream) < 20) { 299 guac_client_log(svc->client, GUAC_LOG_WARNING, "Device I/O Request PDU " 300 "does not contain the expected number of bytes. Device " 301 "redirection may not work as expected."); 302 return; 303 } 304 305 /* Read header */ 306 Stream_Read_UINT32(input_stream, iorequest.device_id); 307 Stream_Read_UINT32(input_stream, iorequest.file_id); 308 Stream_Read_UINT32(input_stream, iorequest.completion_id); 309 Stream_Read_UINT32(input_stream, iorequest.major_func); 310 Stream_Read_UINT32(input_stream, iorequest.minor_func); 311 312 /* If printer, run printer handlers */ 313 if (iorequest.device_id >= 0 && iorequest.device_id < rdpdr->devices_registered) { 314 315 /* Call handler on device */ 316 guac_rdpdr_device* device = &(rdpdr->devices[iorequest.device_id]); 317 device->iorequest_handler(svc, device, &iorequest, input_stream); 318 319 } 320 321 else 322 guac_client_log(svc->client, GUAC_LOG_ERROR, "Unknown device ID: " 323 "0x%08x", iorequest.device_id); 324 325} 326 327void guac_rdpdr_process_server_capability(guac_rdp_common_svc* svc, 328 wStream* input_stream) { 329 330 int count; 331 int i; 332 333 /* Check to make sure the Stream has at least 4 bytes (UINT16 + 2) */ 334 if (Stream_GetRemainingLength(input_stream) < 4) { 335 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Core Capability " 336 "Request PDU does not contain the expected number of bytes." 337 "Device redirection may not work as expected."); 338 return; 339 } 340 341 /* Read header */ 342 Stream_Read_UINT16(input_stream, count); 343 Stream_Seek(input_stream, 2); 344 345 /* Parse capabilities */ 346 for (i=0; i<count; i++) { 347 348 int type; 349 int length; 350 351 /* Make sure Stream has at least 4 bytes (UINT16 + UINT16) */ 352 if (Stream_GetRemainingLength(input_stream) < 4) { 353 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Core " 354 "Capability Request PDU does not contain the expected " 355 "number of bytes. Device redirection may not work as " 356 "expected."); 357 break; 358 } 359 360 Stream_Read_UINT16(input_stream, type); 361 Stream_Read_UINT16(input_stream, length); 362 363 /* Make sure Stream has required length remaining for Seek below. */ 364 if (Stream_GetRemainingLength(input_stream) < (length - 4)) { 365 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Core " 366 "Capability Request PDU does not contain the expected " 367 "number of bytes. Device redirection may not work as " 368 "expected."); 369 break; 370 } 371 372 /* Ignore all for now */ 373 guac_client_log(svc->client, GUAC_LOG_DEBUG, "Ignoring server capability set type=0x%04x, length=%i", type, length); 374 Stream_Seek(input_stream, length - 4); 375 376 } 377 378 /* Send own capabilities */ 379 guac_rdpdr_send_client_capability(svc); 380 381} 382 383void guac_rdpdr_process_user_loggedon(guac_rdp_common_svc* svc, 384 wStream* input_stream) { 385 386 guac_client_log(svc->client, GUAC_LOG_INFO, "RDPDR user logged on"); 387 guac_rdpdr_send_client_device_list_announce_request(svc); 388 389} 390 391void guac_rdpdr_process_prn_cache_data(guac_rdp_common_svc* svc, 392 wStream* input_stream) { 393 guac_client_log(svc->client, GUAC_LOG_DEBUG, "Ignoring printer cached configuration data"); 394} 395 396void guac_rdpdr_process_prn_using_xps(guac_rdp_common_svc* svc, 397 wStream* input_stream) { 398 guac_client_log(svc->client, GUAC_LOG_WARNING, "Printer unexpectedly switched to XPS mode"); 399}