guac-common-svc.c (12282B)
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 "config.h" 21#include "channels/common-svc.h" 22 23#include <freerdp/svc.h> 24#include <guacamole/client.h> 25#include <guacamole/mem.h> 26#include <winpr/stream.h> 27#include <winpr/wtsapi.h> 28#include <winpr/wtypes.h> 29 30#include <stdlib.h> 31 32/** 33 * Event handler for events which deal with data transmitted over an open SVC, 34 * including receipt of inbound data and completion of outbound writes. 35 * 36 * The FreeRDP requirements for this function follow those of the 37 * VirtualChannelOpenEventEx callback defined within Microsoft's RDP API: 38 * 39 * https://docs.microsoft.com/en-us/previous-versions/windows/embedded/aa514754%28v%3dmsdn.10%29 40 * 41 * @param user_param 42 * The pointer to arbitrary data originally passed via the first parameter 43 * of the pVirtualChannelInitEx() function call when the associated channel 44 * was initialized. The pVirtualChannelInitEx() function is exposed within 45 * the channel entry points structure. 46 * 47 * @param open_handle 48 * The handle which identifies the channel itself, typically referred to 49 * within the FreeRDP source as OpenHandle. 50 * 51 * @param event 52 * An integer representing the event that should be handled. This will be 53 * either CHANNEL_EVENT_DATA_RECEIVED, CHANNEL_EVENT_WRITE_CANCELLED, or 54 * CHANNEL_EVENT_WRITE_COMPLETE. 55 * 56 * @param data 57 * The data received, for CHANNEL_EVENT_DATA_RECEIVED events, and the value 58 * passed as user data to pVirtualChannelWriteEx() for 59 * CHANNEL_EVENT_WRITE_* events (note that user data for 60 * pVirtualChannelWriteEx() as implemented by FreeRDP MUST either be NULL 61 * or a wStream containing the data written). 62 * 63 * @param data_length 64 * The number of bytes of event-specific data. 65 * 66 * @param total_length 67 * The total number of bytes expected to be received from the RDP server 68 * due to this single write (from the server's perspective). Each write may 69 * actually be split into multiple chunks, thus resulting in multiple 70 * receive events for the same logical block of data. The relationship 71 * between chunks is indicated with the CHANNEL_FLAG_FIRST and 72 * CHANNEL_FLAG_LAST flags. 73 * 74 * @param data_flags 75 * The result of a bitwise OR of the CHANNEL_FLAG_* flags which apply to 76 * the data received. This value is relevant only to 77 * CHANNEL_EVENT_DATA_RECEIVED events. Valid flags are CHANNEL_FLAG_FIRST, 78 * CHANNEL_FLAG_LAST, and CHANNEL_FLAG_ONLY. The flag CHANNEL_FLAG_MIDDLE 79 * is not itself a flag, but the absence of both CHANNEL_FLAG_FIRST and 80 * CHANNEL_FLAG_LAST. 81 */ 82static VOID guac_rdp_common_svc_handle_open_event(LPVOID user_param, 83 DWORD open_handle, UINT event, LPVOID data, UINT32 data_length, 84 UINT32 total_length, UINT32 data_flags) { 85 86#ifndef FREERDP_SVC_CORE_FREES_WSTREAM 87 /* Free stream data after send is complete */ 88 if ((event == CHANNEL_EVENT_WRITE_CANCELLED 89 || event == CHANNEL_EVENT_WRITE_COMPLETE) && data != NULL) { 90 Stream_Free((wStream*) data, TRUE); 91 return; 92 } 93#endif 94 95 /* Ignore all events except for received data */ 96 if (event != CHANNEL_EVENT_DATA_RECEIVED) 97 return; 98 99 guac_rdp_common_svc* svc = (guac_rdp_common_svc*) user_param; 100 101 /* Validate relevant handle matches that of SVC */ 102 if (open_handle != svc->_open_handle) { 103 guac_client_log(svc->client, GUAC_LOG_WARNING, "%i bytes of data " 104 "received from within the remote desktop session for SVC " 105 "\"%s\" are being dropped because the relevant open handle " 106 "(0x%X) does not match the open handle of the SVC (0x%X).", 107 data_length, svc->name, open_handle, svc->_open_handle); 108 return; 109 } 110 111 /* If receiving first chunk, allocate sufficient space for all remaining 112 * chunks */ 113 if (data_flags & CHANNEL_FLAG_FIRST) { 114 115 /* Limit maximum received size */ 116 if (total_length > GUAC_SVC_MAX_ASSEMBLED_LENGTH) { 117 guac_client_log(svc->client, GUAC_LOG_WARNING, "RDP server has " 118 "requested to send a sequence of %i bytes, but this " 119 "exceeds the maximum buffer space of %i bytes. Received " 120 "data may be truncated.", total_length, 121 GUAC_SVC_MAX_ASSEMBLED_LENGTH); 122 total_length = GUAC_SVC_MAX_ASSEMBLED_LENGTH; 123 } 124 125 svc->_input_stream = Stream_New(NULL, total_length); 126 } 127 128 /* leave if we don't have a stream. */ 129 if (svc->_input_stream == NULL) 130 return; 131 132 /* Add chunk to buffer only if sufficient space remains */ 133 if (Stream_EnsureRemainingCapacity(svc->_input_stream, data_length)) 134 Stream_Write(svc->_input_stream, data, data_length); 135 else 136 guac_client_log(svc->client, GUAC_LOG_WARNING, "%i bytes of data " 137 "received from within the remote desktop session for SVC " 138 "\"%s\" are being dropped because the maximum available " 139 "space for received data has been exceeded.", data_length, 140 svc->name); 141 142 /* Fire event once last chunk has been received */ 143 if (data_flags & CHANNEL_FLAG_LAST) { 144 145 Stream_SealLength(svc->_input_stream); 146 Stream_SetPosition(svc->_input_stream, 0); 147 148 /* Handle channel-specific data receipt tasks, if any */ 149 if (svc->_receive_handler) 150 svc->_receive_handler(svc, svc->_input_stream); 151 152 Stream_Free(svc->_input_stream, TRUE); 153 svc->_input_stream = NULL; 154 155 } 156 157} 158 159/** 160 * Processes a CHANNEL_EVENT_CONNECTED event, completing the 161 * connection/initialization process of the channel. 162 * 163 * @param rdpsnd 164 * The guac_rdp_common_svc structure representing the channel. 165 */ 166static void guac_rdp_common_svc_process_connect(guac_rdp_common_svc* svc) { 167 168 /* Open FreeRDP side of connected channel */ 169 UINT32 open_status = 170 svc->_entry_points.pVirtualChannelOpenEx(svc->_init_handle, 171 &svc->_open_handle, svc->_channel_def.name, 172 guac_rdp_common_svc_handle_open_event); 173 174 /* Warn if the channel cannot be opened after all */ 175 if (open_status != CHANNEL_RC_OK) { 176 guac_client_log(svc->client, GUAC_LOG_WARNING, "SVC \"%s\" could not " 177 "be opened: %s (error %i)", svc->name, 178 WTSErrorToString(open_status), open_status); 179 return; 180 } 181 182 /* Handle channel-specific connect tasks, if any */ 183 if (svc->_connect_handler) 184 svc->_connect_handler(svc); 185 186 /* Channel is now ready */ 187 guac_client_log(svc->client, GUAC_LOG_DEBUG, "SVC \"%s\" connected.", 188 svc->name); 189 190} 191 192/** 193 * Processes a CHANNEL_EVENT_TERMINATED event, freeing all resources associated 194 * with the channel. 195 * 196 * @param svc 197 * The guac_rdp_common_svc structure representing the channel. 198 */ 199static void guac_rdp_common_svc_process_terminate(guac_rdp_common_svc* svc) { 200 201 /* Handle channel-specific termination tasks, if any */ 202 if (svc->_terminate_handler) 203 svc->_terminate_handler(svc); 204 205 guac_client_log(svc->client, GUAC_LOG_DEBUG, "SVC \"%s\" disconnected.", 206 svc->name); 207 guac_mem_free(svc); 208 209} 210 211/** 212 * Event handler for events which deal with the overall lifecycle of an SVC. 213 * This specific implementation of the event handler currently handles only 214 * CHANNEL_EVENT_CONNECTED and CHANNEL_EVENT_TERMINATED events, delegating 215 * actual handling of those events to guac_rdp_common_svc_process_connect() and 216 * guac_rdp_common_svc_process_terminate() respectively. 217 * 218 * The FreeRDP requirements for this function follow those of the 219 * VirtualChannelInitEventEx callback defined within Microsoft's RDP API: 220 * 221 * https://docs.microsoft.com/en-us/previous-versions/windows/embedded/aa514727%28v%3dmsdn.10%29 222 * 223 * @param user_param 224 * The pointer to arbitrary data originally passed via the first parameter 225 * of the pVirtualChannelInitEx() function call when the associated channel 226 * was initialized. The pVirtualChannelInitEx() function is exposed within 227 * the channel entry points structure. 228 * 229 * @param init_handle 230 * The handle which identifies the client connection, typically referred to 231 * within the FreeRDP source as pInitHandle. 232 * 233 * @param event 234 * An integer representing the event that should be handled. This will be 235 * either CHANNEL_EVENT_CONNECTED, CHANNEL_EVENT_DISCONNECTED, 236 * CHANNEL_EVENT_INITIALIZED, CHANNEL_EVENT_TERMINATED, or 237 * CHANNEL_EVENT_V1_CONNECTED. 238 * 239 * @param data 240 * NULL in all cases except the CHANNEL_EVENT_CONNECTED event, in which 241 * case this is a null-terminated string containing the name of the server. 242 * 243 * @param data_length 244 * The number of bytes of data, if any. 245 */ 246static VOID guac_rdp_common_svc_handle_init_event(LPVOID user_param, 247 LPVOID init_handle, UINT event, LPVOID data, UINT data_length) { 248 249 guac_rdp_common_svc* svc = (guac_rdp_common_svc*) user_param; 250 251 /* Validate relevant handle matches that of SVC */ 252 if (init_handle != svc->_init_handle) { 253 guac_client_log(svc->client, GUAC_LOG_WARNING, "An init event (#%i) " 254 "for SVC \"%s\" has been dropped because the relevant init " 255 "handle (0x%X) does not match the init handle of the SVC " 256 "(0x%X).", event, svc->name, init_handle, svc->_init_handle); 257 return; 258 } 259 260 switch (event) { 261 262 /* The remote desktop side of the SVC has been connected */ 263 case CHANNEL_EVENT_CONNECTED: 264 guac_rdp_common_svc_process_connect(svc); 265 break; 266 267 /* The channel has disconnected and now must be cleaned up */ 268 case CHANNEL_EVENT_TERMINATED: 269 guac_rdp_common_svc_process_terminate(svc); 270 break; 271 272 } 273 274} 275 276/** 277 * Entry point for FreeRDP plugins. This function is automatically invoked when 278 * the plugin is loaded. 279 * 280 * @param entry_points 281 * Functions and data specific to the FreeRDP side of the virtual channel 282 * and plugin. This structure must be copied within implementation-specific 283 * storage such that the functions it references can be invoked when 284 * needed. 285 * 286 * @param init_handle 287 * The handle which identifies the client connection, typically referred to 288 * within the FreeRDP source as pInitHandle. This handle is also provided 289 * to the channel init event handler. The handle must eventually be used 290 * within the channel open event handler to obtain a handle to the channel 291 * itself. 292 * 293 * @return 294 * TRUE if the plugin has initialized successfully, FALSE otherwise. 295 */ 296BOOL VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS_EX entry_points, 297 PVOID init_handle) { 298 299 CHANNEL_ENTRY_POINTS_FREERDP_EX* entry_points_ex = 300 (CHANNEL_ENTRY_POINTS_FREERDP_EX*) entry_points; 301 302 /* Get structure representing the Guacamole side of the SVC from plugin 303 * parameters */ 304 guac_rdp_common_svc* svc = (guac_rdp_common_svc*) entry_points_ex->pExtendedData; 305 306 /* Copy FreeRDP data into SVC structure for future reference */ 307 svc->_entry_points = *entry_points_ex; 308 svc->_init_handle = init_handle; 309 310 /* Complete initialization */ 311 if (svc->_entry_points.pVirtualChannelInitEx(svc, NULL, init_handle, 312 &svc->_channel_def, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, 313 guac_rdp_common_svc_handle_init_event) != CHANNEL_RC_OK) { 314 return FALSE; 315 } 316 317 return TRUE; 318 319} 320