rdp.c (29072B)
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 "argv.h" 21#include "beep.h" 22#include "bitmap.h" 23#include "channels/audio-input/audio-buffer.h" 24#include "channels/audio-input/audio-input.h" 25#include "channels/cliprdr.h" 26#include "channels/disp.h" 27#include "channels/pipe-svc.h" 28#include "channels/rail.h" 29#include "channels/rdpdr/rdpdr.h" 30#include "channels/rdpei.h" 31#include "channels/rdpsnd/rdpsnd.h" 32#include "client.h" 33#include "color.h" 34#include "common/cursor.h" 35#include "common/display.h" 36#include "config.h" 37#include "error.h" 38#include "fs.h" 39#include "gdi.h" 40#include "glyph.h" 41#include "keyboard.h" 42#include "plugins/channels.h" 43#include "pointer.h" 44#include "print-job.h" 45#include "rdp.h" 46#include "settings.h" 47 48#ifdef ENABLE_COMMON_SSH 49#include "common-ssh/sftp.h" 50#include "common-ssh/ssh.h" 51#include "common-ssh/user.h" 52#endif 53 54#include <freerdp/addin.h> 55#include <freerdp/cache/bitmap.h> 56#include <freerdp/cache/brush.h> 57#include <freerdp/cache/glyph.h> 58#include <freerdp/cache/offscreen.h> 59#include <freerdp/cache/palette.h> 60#include <freerdp/cache/pointer.h> 61#include <freerdp/channels/channels.h> 62#include <freerdp/client/channels.h> 63#include <freerdp/freerdp.h> 64#include <freerdp/gdi/gdi.h> 65#include <freerdp/graphics.h> 66#include <freerdp/primary.h> 67#include <freerdp/settings.h> 68#include <freerdp/update.h> 69#include <guacamole/argv.h> 70#include <guacamole/audio.h> 71#include <guacamole/client.h> 72#include <guacamole/mem.h> 73#include <guacamole/protocol.h> 74#include <guacamole/recording.h> 75#include <guacamole/socket.h> 76#include <guacamole/string.h> 77#include <guacamole/timestamp.h> 78#include <guacamole/wol.h> 79#include <winpr/error.h> 80#include <winpr/synch.h> 81#include <winpr/wtypes.h> 82 83#include <stdlib.h> 84#include <time.h> 85 86BOOL rdp_freerdp_pre_connect(freerdp* instance) { 87 88 rdpContext* context = instance->context; 89 rdpGraphics* graphics = context->graphics; 90 91 guac_client* client = ((rdp_freerdp_context*) context)->client; 92 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 93 guac_rdp_settings* settings = rdp_client->settings; 94 95 /* Push desired settings to FreeRDP */ 96 guac_rdp_push_settings(client, settings, instance); 97 98 /* Init FreeRDP add-in provider */ 99 freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0); 100 101 /* Load "disp" plugin for display update */ 102 if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) 103 guac_rdp_disp_load_plugin(context); 104 105 /* Load "rdpei" plugin for multi-touch support */ 106 if (settings->enable_touch) 107 guac_rdp_rdpei_load_plugin(context); 108 109 /* Load "AUDIO_INPUT" plugin for audio input*/ 110 if (settings->enable_audio_input) { 111 rdp_client->audio_input = guac_rdp_audio_buffer_alloc(client); 112 guac_rdp_audio_load_plugin(instance->context); 113 } 114 115 /* Load "cliprdr" service if not disabled */ 116 if (!(settings->disable_copy && settings->disable_paste)) 117 guac_rdp_clipboard_load_plugin(rdp_client->clipboard, context); 118 119 /* If RDPSND/RDPDR required, load them */ 120 if (settings->printing_enabled 121 || settings->drive_enabled 122 || settings->audio_enabled) { 123 guac_rdpdr_load_plugin(context); 124 guac_rdpsnd_load_plugin(context); 125 } 126 127 /* Load RAIL plugin if RemoteApp in use */ 128 if (settings->remote_app != NULL) 129 guac_rdp_rail_load_plugin(context); 130 131 /* Load SVC plugin instances for all static channels */ 132 if (settings->svc_names != NULL) { 133 134 char** current = settings->svc_names; 135 do { 136 guac_rdp_pipe_svc_load_plugin(context, *current); 137 } while (*(++current) != NULL); 138 139 } 140 141 /* Load plugin providing Dynamic Virtual Channel support, if required */ 142 if (instance->settings->SupportDynamicChannels && 143 guac_freerdp_channels_load_plugin(context, "drdynvc", 144 instance->settings)) { 145 guac_client_log(client, GUAC_LOG_WARNING, 146 "Failed to load drdynvc plugin. Display update and audio " 147 "input support will be disabled."); 148 } 149 150 /* Init FreeRDP internal GDI implementation */ 151 if (!gdi_init(instance, guac_rdp_get_native_pixel_format(FALSE))) 152 return FALSE; 153 154 /* Set up bitmap handling */ 155 rdpBitmap bitmap = *graphics->Bitmap_Prototype; 156 bitmap.size = sizeof(guac_rdp_bitmap); 157 bitmap.New = guac_rdp_bitmap_new; 158 bitmap.Free = guac_rdp_bitmap_free; 159 bitmap.Paint = guac_rdp_bitmap_paint; 160 bitmap.SetSurface = guac_rdp_bitmap_setsurface; 161 graphics_register_bitmap(graphics, &bitmap); 162 163 /* Set up glyph handling */ 164 rdpGlyph glyph = *graphics->Glyph_Prototype; 165 glyph.size = sizeof(guac_rdp_glyph); 166 glyph.New = guac_rdp_glyph_new; 167 glyph.Free = guac_rdp_glyph_free; 168 glyph.Draw = guac_rdp_glyph_draw; 169 glyph.BeginDraw = guac_rdp_glyph_begindraw; 170 glyph.EndDraw = guac_rdp_glyph_enddraw; 171 graphics_register_glyph(graphics, &glyph); 172 173 /* Set up pointer handling */ 174 rdpPointer pointer = *graphics->Pointer_Prototype; 175 pointer.size = sizeof(guac_rdp_pointer); 176 pointer.New = guac_rdp_pointer_new; 177 pointer.Free = guac_rdp_pointer_free; 178 pointer.Set = guac_rdp_pointer_set; 179 pointer.SetNull = guac_rdp_pointer_set_null; 180 pointer.SetDefault = guac_rdp_pointer_set_default; 181 graphics_register_pointer(graphics, &pointer); 182 183 /* Beep on receipt of Play Sound PDU */ 184 instance->update->PlaySound = guac_rdp_beep_play_sound; 185 186 /* Automatically synchronize keyboard locks when changed server-side */ 187 instance->update->SetKeyboardIndicators = guac_rdp_keyboard_set_indicators; 188 189 /* Set up GDI */ 190 instance->update->DesktopResize = guac_rdp_gdi_desktop_resize; 191 instance->update->EndPaint = guac_rdp_gdi_end_paint; 192 instance->update->SetBounds = guac_rdp_gdi_set_bounds; 193 194 rdpPrimaryUpdate* primary = instance->update->primary; 195 primary->DstBlt = guac_rdp_gdi_dstblt; 196 primary->PatBlt = guac_rdp_gdi_patblt; 197 primary->ScrBlt = guac_rdp_gdi_scrblt; 198 primary->MemBlt = guac_rdp_gdi_memblt; 199 primary->OpaqueRect = guac_rdp_gdi_opaquerect; 200 201 pointer_cache_register_callbacks(instance->update); 202 glyph_cache_register_callbacks(instance->update); 203 brush_cache_register_callbacks(instance->update); 204 bitmap_cache_register_callbacks(instance->update); 205 offscreen_cache_register_callbacks(instance->update); 206 palette_cache_register_callbacks(instance->update); 207 208 return TRUE; 209 210} 211 212/** 213 * Callback invoked by FreeRDP when authentication is required but the required 214 * parameters have not been provided. In the case of Guacamole clients that 215 * support the "required" instruction, this function will send any of the three 216 * unpopulated RDP authentication parameters back to the client so that the 217 * connection owner can provide the required information. If the values have 218 * been provided in the original connection parameters the user will not be 219 * prompted for updated parameters. If the version of Guacamole Client in use 220 * by the connection owner does not support the "required" instruction then the 221 * connection will fail. This function always returns true. 222 * 223 * @param instance 224 * The FreeRDP instance associated with the RDP session requesting 225 * credentials. 226 * 227 * @param username 228 * Pointer to a string which will receive the user's username. 229 * 230 * @param password 231 * Pointer to a string which will receive the user's password. 232 * 233 * @param domain 234 * Pointer to a string which will receive the domain associated with the 235 * user's account. 236 * 237 * @return 238 * Always TRUE. 239 */ 240static BOOL rdp_freerdp_authenticate(freerdp* instance, char** username, 241 char** password, char** domain) { 242 243 rdpContext* context = instance->context; 244 guac_client* client = ((rdp_freerdp_context*) context)->client; 245 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 246 guac_rdp_settings* settings = rdp_client->settings; 247 char* params[4] = {NULL}; 248 int i = 0; 249 250 /* If the client does not support the "required" instruction, warn and 251 * quit. 252 */ 253 if (!guac_client_owner_supports_required(client)) { 254 guac_client_log(client, GUAC_LOG_WARNING, "Client does not support the " 255 "\"required\" instruction. No authentication parameters will " 256 "be requested."); 257 return TRUE; 258 } 259 260 /* If the username is undefined, add it to the requested parameters. */ 261 if (settings->username == NULL) { 262 guac_argv_register(GUAC_RDP_ARGV_USERNAME, guac_rdp_argv_callback, NULL, 0); 263 params[i] = GUAC_RDP_ARGV_USERNAME; 264 i++; 265 266 /* If username is undefined and domain is also undefined, request domain. */ 267 if (settings->domain == NULL) { 268 guac_argv_register(GUAC_RDP_ARGV_DOMAIN, guac_rdp_argv_callback, NULL, 0); 269 params[i] = GUAC_RDP_ARGV_DOMAIN; 270 i++; 271 } 272 273 } 274 275 /* If the password is undefined, add it to the requested parameters. */ 276 if (settings->password == NULL) { 277 guac_argv_register(GUAC_RDP_ARGV_PASSWORD, guac_rdp_argv_callback, NULL, 0); 278 params[i] = GUAC_RDP_ARGV_PASSWORD; 279 i++; 280 } 281 282 /* NULL-terminate the array. */ 283 params[i] = NULL; 284 285 if (i > 0) { 286 287 /* Send required parameters to the owner and wait for the response. */ 288 guac_client_owner_send_required(client, (const char**) params); 289 guac_argv_await((const char**) params); 290 291 /* Free old values and get new values from settings. */ 292 guac_mem_free(*username); 293 guac_mem_free(*password); 294 guac_mem_free(*domain); 295 *username = guac_strdup(settings->username); 296 *password = guac_strdup(settings->password); 297 *domain = guac_strdup(settings->domain); 298 299 } 300 301 /* Always return TRUE allowing connection to retry. */ 302 return TRUE; 303 304} 305 306#ifdef HAVE_FREERDP_VERIFYCERTIFICATEEX 307/** 308 * Callback invoked by FreeRDP when the SSL/TLS certificate of the RDP server 309 * needs to be verified. If this ever happens, this function implementation 310 * will always fail unless the connection has been configured to ignore 311 * certificate validity. 312 * 313 * @param instance 314 * The FreeRDP instance associated with the RDP session whose SSL/TLS 315 * certificate needs to be verified. 316 * 317 * @param hostname 318 * The hostname or address of the RDP server being connected to. 319 * 320 * @param port 321 * The TCP port number of the RDP server being connected to. 322 * 323 * @param common_name 324 * The name of the server protected by the certificate. This should match 325 * the hostname/address of the RDP server. 326 * 327 * @param subject 328 * The subject to whom the certificate was issued. 329 * 330 * @param issuer 331 * The authority that issued the certificate, 332 * 333 * @param fingerprint 334 * The cryptographic fingerprint of the certificate. 335 * 336 * @param flags 337 * Bitwise OR of any applicable certificate verification flags. Valid flags are 338 * VERIFY_CERT_FLAG_NONE, VERIFY_CERT_FLAG_LEGACY, VERIFY_CERT_FLAG_REDIRECT, 339 * VERIFY_CERT_FLAG_GATEWAY, VERIFY_CERT_FLAG_CHANGED, and 340 * VERIFY_CERT_FLAG_MISMATCH. 341 * 342 * @return 343 * 1 to accept the certificate and store within FreeRDP's configuration 344 * directory, 2 to accept the certificate only within this session, or 0 to 345 * reject the certificate. 346 */ 347static DWORD rdp_freerdp_verify_certificate(freerdp* instance, 348 const char* hostname, UINT16 port, const char* common_name, 349 const char* subject, const char* issuer, const char* fingerprint, 350 DWORD flags) { 351#else 352/** 353 * Callback invoked by FreeRDP when the SSL/TLS certificate of the RDP server 354 * needs to be verified. If this ever happens, this function implementation 355 * will always fail unless the connection has been configured to ignore 356 * certificate validity. 357 * 358 * @param instance 359 * The FreeRDP instance associated with the RDP session whose SSL/TLS 360 * certificate needs to be verified. 361 * 362 * @param subject 363 * The subject to whom the certificate was issued. 364 * 365 * @param issuer 366 * The authority that issued the certificate, 367 * 368 * @param fingerprint 369 * The cryptographic fingerprint of the certificate. 370 * 371 * @param host_mismatch 372 * TRUE if the certificate does not match the destination hostname, FALSE 373 * otherwise. 374 * 375 * @return 376 * 1 to accept the certificate and store within FreeRDP's configuration 377 * directory, 2 to accept the certificate only within this session, or 0 to 378 * reject the certificate. 379 */ 380static DWORD rdp_freerdp_verify_certificate(freerdp* instance, 381 const char* common_name, const char* subject, const char* issuer, 382 const char* fingerprint, BOOL host_mismatch) { 383#endif 384 385 rdpContext* context = instance->context; 386 guac_client* client = ((rdp_freerdp_context*) context)->client; 387 guac_rdp_client* rdp_client = 388 (guac_rdp_client*) client->data; 389 390 /* Bypass validation if ignore_certificate given */ 391 if (rdp_client->settings->ignore_certificate) { 392 guac_client_log(client, GUAC_LOG_INFO, "Certificate validation bypassed"); 393 return 2; /* Accept only for this session */ 394 } 395 396 guac_client_log(client, GUAC_LOG_INFO, "Certificate validation failed"); 397 return 0; /* Reject certificate */ 398 399} 400 401/** 402 * Waits for messages from the RDP server for the given number of milliseconds. 403 * 404 * @param client 405 * The client associated with the current RDP session. 406 * 407 * @param timeout_msecs 408 * The maximum amount of time to wait, in milliseconds. 409 * 410 * @return 411 * A positive value if messages are ready, zero if the specified timeout 412 * period elapsed, or a negative value if an error occurs. 413 */ 414static int rdp_guac_client_wait_for_messages(guac_client* client, 415 int timeout_msecs) { 416 417 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 418 freerdp* rdp_inst = rdp_client->rdp_inst; 419 420 HANDLE handles[GUAC_RDP_MAX_FILE_DESCRIPTORS]; 421 int num_handles = freerdp_get_event_handles(rdp_inst->context, handles, 422 GUAC_RDP_MAX_FILE_DESCRIPTORS); 423 424 /* Wait for data and construct a reasonable frame */ 425 int result = WaitForMultipleObjects(num_handles, handles, FALSE, 426 timeout_msecs); 427 428 /* Translate WaitForMultipleObjects() return values */ 429 switch (result) { 430 431 /* Timeout elapsed before wait could complete */ 432 case WAIT_TIMEOUT: 433 return 0; 434 435 /* Attempt to wait failed due to an error */ 436 case WAIT_FAILED: 437 return -1; 438 439 } 440 441 /* Wait was successful */ 442 return 1; 443 444} 445 446/** 447 * Connects to an RDP server as described by the guac_rdp_settings structure 448 * associated with the given client, allocating and freeing all objects 449 * directly related to the RDP connection. It is expected that all objects 450 * which are independent of FreeRDP's state (the clipboard, display update 451 * management, etc.) will already be allocated and associated with the 452 * guac_rdp_client associated with the given guac_client. This function blocks 453 * for the duration of the RDP session, returning only after the session has 454 * completely disconnected. 455 * 456 * @param client 457 * The guac_client associated with the RDP settings describing the 458 * connection that should be established. 459 * 460 * @return 461 * Zero if the connection successfully terminated and a reconnect is 462 * desired, non-zero if an error occurs or the connection was disconnected 463 * and a reconnect is NOT desired. 464 */ 465static int guac_rdp_handle_connection(guac_client* client) { 466 467 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 468 guac_rdp_settings* settings = rdp_client->settings; 469 470 /* Init random number generator */ 471 srandom(time(NULL)); 472 473 pthread_rwlock_wrlock(&(rdp_client->lock)); 474 475 /* Create display */ 476 rdp_client->display = guac_common_display_alloc(client, 477 rdp_client->settings->width, 478 rdp_client->settings->height); 479 480 /* Use lossless compression only if requested (otherwise, use default 481 * heuristics) */ 482 guac_common_display_set_lossless(rdp_client->display, settings->lossless); 483 484 rdp_client->current_surface = rdp_client->display->default_surface; 485 486 rdp_client->available_svc = guac_common_list_alloc(); 487 488 /* Init client */ 489 freerdp* rdp_inst = freerdp_new(); 490 rdp_inst->PreConnect = rdp_freerdp_pre_connect; 491 rdp_inst->Authenticate = rdp_freerdp_authenticate; 492 493#ifdef HAVE_FREERDP_VERIFYCERTIFICATEEX 494 rdp_inst->VerifyCertificateEx = rdp_freerdp_verify_certificate; 495#else 496 rdp_inst->VerifyCertificate = rdp_freerdp_verify_certificate; 497#endif 498 499 /* Allocate FreeRDP context */ 500 rdp_inst->ContextSize = sizeof(rdp_freerdp_context); 501 502 if (!freerdp_context_new(rdp_inst)) { 503 guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, 504 "FreeRDP initialization failed before connecting. Please " 505 "check for errors earlier in the logs and/or enable " 506 "debug-level logging for guacd."); 507 goto fail; 508 } 509 510 ((rdp_freerdp_context*) rdp_inst->context)->client = client; 511 512 /* Load keymap into client */ 513 rdp_client->keyboard = guac_rdp_keyboard_alloc(client, 514 settings->server_layout); 515 516 /* Set default pointer */ 517 guac_common_cursor_set_pointer(rdp_client->display->cursor); 518 519 /* Connect to RDP server */ 520 if (!freerdp_connect(rdp_inst)) { 521 guac_rdp_client_abort(client, rdp_inst); 522 goto fail; 523 } 524 525 /* Connection complete */ 526 rdp_client->rdp_inst = rdp_inst; 527 528 guac_timestamp last_frame_end = guac_timestamp_current(); 529 530 /* Signal that reconnect has been completed */ 531 guac_rdp_disp_reconnect_complete(rdp_client->disp); 532 533 pthread_rwlock_unlock(&(rdp_client->lock)); 534 535 /* Handle messages from RDP server while client is running */ 536 while (client->state == GUAC_CLIENT_RUNNING 537 && !guac_rdp_disp_reconnect_needed(rdp_client->disp)) { 538 539 /* Update remote display size */ 540 guac_rdp_disp_update_size(rdp_client->disp, settings, rdp_inst); 541 542 /* Wait for data and construct a reasonable frame */ 543 int wait_result = rdp_guac_client_wait_for_messages(client, 544 GUAC_RDP_FRAME_START_TIMEOUT); 545 if (wait_result > 0) { 546 547 int processing_lag = guac_client_get_processing_lag(client); 548 guac_timestamp frame_start = guac_timestamp_current(); 549 550 /* Read server messages until frame is built */ 551 do { 552 553 guac_timestamp frame_end; 554 int frame_remaining; 555 556 /* Handle any queued FreeRDP events (this may result in RDP 557 * messages being sent) */ 558 pthread_mutex_lock(&(rdp_client->message_lock)); 559 int event_result = freerdp_check_event_handles(rdp_inst->context); 560 pthread_mutex_unlock(&(rdp_client->message_lock)); 561 562 /* Abort if FreeRDP event handling fails */ 563 if (!event_result) { 564 wait_result = -1; 565 break; 566 } 567 568 /* Calculate time remaining in frame */ 569 frame_end = guac_timestamp_current(); 570 frame_remaining = frame_start + GUAC_RDP_FRAME_DURATION 571 - frame_end; 572 573 /* Calculate time that client needs to catch up */ 574 int time_elapsed = frame_end - last_frame_end; 575 int required_wait = processing_lag - time_elapsed; 576 577 /* Increase the duration of this frame if client is lagging */ 578 if (required_wait > GUAC_RDP_FRAME_TIMEOUT) 579 wait_result = rdp_guac_client_wait_for_messages(client, 580 required_wait); 581 582 /* Wait again if frame remaining */ 583 else if (frame_remaining > 0) 584 wait_result = rdp_guac_client_wait_for_messages(client, 585 GUAC_RDP_FRAME_TIMEOUT); 586 else 587 break; 588 589 } while (wait_result > 0); 590 591 /* Record end of frame, excluding server-side rendering time (we 592 * assume server-side rendering time will be consistent between any 593 * two subsequent frames, and that this time should thus be 594 * excluded from the required wait period of the next frame). */ 595 last_frame_end = frame_start; 596 597 } 598 599 /* Test whether the RDP server is closing the connection */ 600 int connection_closing = freerdp_shall_disconnect(rdp_inst); 601 602 /* Close connection cleanly if server is disconnecting */ 603 if (connection_closing) 604 guac_rdp_client_abort(client, rdp_inst); 605 606 /* If a low-level connection error occurred, fail */ 607 else if (wait_result < 0) 608 guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE, 609 "Connection closed."); 610 611 /* Flush frame only if successful */ 612 else { 613 guac_common_display_flush(rdp_client->display); 614 guac_client_end_frame(client); 615 guac_socket_flush(client->socket); 616 } 617 618 } 619 620 pthread_rwlock_wrlock(&(rdp_client->lock)); 621 622 /* Clean up print job, if active */ 623 if (rdp_client->active_job != NULL) { 624 guac_rdp_print_job_kill(rdp_client->active_job); 625 guac_rdp_print_job_free(rdp_client->active_job); 626 } 627 628 /* Disconnect client and channels */ 629 pthread_mutex_lock(&(rdp_client->message_lock)); 630 freerdp_disconnect(rdp_inst); 631 pthread_mutex_unlock(&(rdp_client->message_lock)); 632 633 /* Clean up FreeRDP internal GDI implementation */ 634 gdi_free(rdp_inst); 635 636 /* Clean up RDP client context */ 637 freerdp_context_free(rdp_inst); 638 639 /* Clean up RDP client */ 640 freerdp_free(rdp_inst); 641 rdp_client->rdp_inst = NULL; 642 643 /* Free SVC list */ 644 guac_common_list_free(rdp_client->available_svc, NULL); 645 rdp_client->available_svc = NULL; 646 647 /* Free RDP keyboard state */ 648 guac_rdp_keyboard_free(rdp_client->keyboard); 649 rdp_client->keyboard = NULL; 650 651 /* Free display */ 652 guac_common_display_free(rdp_client->display); 653 rdp_client->display = NULL; 654 655 pthread_rwlock_unlock(&(rdp_client->lock)); 656 657 /* Client is now disconnected */ 658 guac_client_log(client, GUAC_LOG_INFO, "Internal RDP client disconnected"); 659 660 return 0; 661 662fail: 663 pthread_rwlock_unlock(&(rdp_client->lock)); 664 return 1; 665 666} 667 668void* guac_rdp_client_thread(void* data) { 669 670 guac_client* client = (guac_client*) data; 671 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 672 guac_rdp_settings* settings = rdp_client->settings; 673 674 /* If Wake-on-LAN is enabled, try to wake. */ 675 if (settings->wol_send_packet) { 676 guac_client_log(client, GUAC_LOG_DEBUG, "Sending Wake-on-LAN packet, " 677 "and pausing for %d seconds.", settings->wol_wait_time); 678 679 /* Send the Wake-on-LAN request. */ 680 if (guac_wol_wake(settings->wol_mac_addr, settings->wol_broadcast_addr, 681 settings->wol_udp_port)) 682 return NULL; 683 684 /* If wait time is specified, sleep for that amount of time. */ 685 if (settings->wol_wait_time > 0) 686 guac_timestamp_msleep(settings->wol_wait_time * 1000); 687 } 688 689 /* If audio enabled, choose an encoder */ 690 if (settings->audio_enabled) { 691 692 rdp_client->audio = guac_audio_stream_alloc(client, NULL, 693 GUAC_RDP_AUDIO_RATE, 694 GUAC_RDP_AUDIO_CHANNELS, 695 GUAC_RDP_AUDIO_BPS); 696 697 /* Warn if no audio encoding is available */ 698 if (rdp_client->audio == NULL) 699 guac_client_log(client, GUAC_LOG_INFO, 700 "No available audio encoding. Sound disabled."); 701 702 } /* end if audio enabled */ 703 704 /* Load filesystem if drive enabled */ 705 if (settings->drive_enabled) { 706 707 /* Allocate actual emulated filesystem */ 708 rdp_client->filesystem = 709 guac_rdp_fs_alloc(client, settings->drive_path, 710 settings->create_drive_path, settings->disable_download, 711 settings->disable_upload); 712 713 /* Expose filesystem to owner */ 714 guac_client_for_owner(client, guac_rdp_fs_expose, 715 rdp_client->filesystem); 716 717 } 718 719#ifdef ENABLE_COMMON_SSH 720 /* Connect via SSH if SFTP is enabled */ 721 if (settings->enable_sftp) { 722 723 /* Abort if username is missing */ 724 if (settings->sftp_username == NULL) { 725 guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, 726 "A username or SFTP-specific username is required if " 727 "SFTP is enabled."); 728 return NULL; 729 } 730 731 guac_client_log(client, GUAC_LOG_DEBUG, 732 "Connecting via SSH for SFTP filesystem access."); 733 734 rdp_client->sftp_user = 735 guac_common_ssh_create_user(settings->sftp_username); 736 737 /* Import private key, if given */ 738 if (settings->sftp_private_key != NULL) { 739 740 guac_client_log(client, GUAC_LOG_DEBUG, 741 "Authenticating with private key."); 742 743 /* Abort if private key cannot be read */ 744 if (guac_common_ssh_user_import_key(rdp_client->sftp_user, 745 settings->sftp_private_key, 746 settings->sftp_passphrase)) { 747 guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, 748 "Private key unreadable."); 749 return NULL; 750 } 751 752 } 753 754 /* Otherwise, use specified password */ 755 else { 756 757 guac_client_log(client, GUAC_LOG_DEBUG, 758 "Authenticating with password."); 759 760 guac_common_ssh_user_set_password(rdp_client->sftp_user, 761 settings->sftp_password); 762 763 } 764 765 /* Attempt SSH connection */ 766 rdp_client->sftp_session = 767 guac_common_ssh_create_session(client, settings->sftp_hostname, 768 settings->sftp_port, rdp_client->sftp_user, settings->sftp_server_alive_interval, 769 settings->sftp_host_key, NULL); 770 771 /* Fail if SSH connection does not succeed */ 772 if (rdp_client->sftp_session == NULL) { 773 /* Already aborted within guac_common_ssh_create_session() */ 774 return NULL; 775 } 776 777 /* Load and expose filesystem */ 778 rdp_client->sftp_filesystem = 779 guac_common_ssh_create_sftp_filesystem(rdp_client->sftp_session, 780 settings->sftp_root_directory, NULL, 781 settings->sftp_disable_download, 782 settings->sftp_disable_upload); 783 784 /* Expose filesystem to connection owner */ 785 guac_client_for_owner(client, 786 guac_common_ssh_expose_sftp_filesystem, 787 rdp_client->sftp_filesystem); 788 789 /* Abort if SFTP connection fails */ 790 if (rdp_client->sftp_filesystem == NULL) { 791 guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE, 792 "SFTP connection failed."); 793 return NULL; 794 } 795 796 /* Configure destination for basic uploads, if specified */ 797 if (settings->sftp_directory != NULL) 798 guac_common_ssh_sftp_set_upload_path( 799 rdp_client->sftp_filesystem, 800 settings->sftp_directory); 801 802 guac_client_log(client, GUAC_LOG_DEBUG, 803 "SFTP connection succeeded."); 804 805 } 806#endif 807 808 /* Set up screen recording, if requested */ 809 if (settings->recording_path != NULL) { 810 rdp_client->recording = guac_recording_create(client, 811 settings->recording_path, 812 settings->recording_name, 813 settings->create_recording_path, 814 !settings->recording_exclude_output, 815 !settings->recording_exclude_mouse, 816 !settings->recording_exclude_touch, 817 settings->recording_include_keys); 818 } 819 820 /* Continue handling connections until error or client disconnect */ 821 while (client->state == GUAC_CLIENT_RUNNING) { 822 if (guac_rdp_handle_connection(client)) 823 break; 824 } 825 826 return NULL; 827 828} 829