vnc.c (17724B)
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 22#include "auth.h" 23#include "client.h" 24#include "clipboard.h" 25#include "common/clipboard.h" 26#include "common/cursor.h" 27#include "common/display.h" 28#include "cursor.h" 29#include "display.h" 30#include "log.h" 31#include "settings.h" 32#include "vnc.h" 33 34#ifdef ENABLE_PULSE 35#include "pulse/pulse.h" 36#endif 37 38#ifdef ENABLE_COMMON_SSH 39#include "common-ssh/sftp.h" 40#include "common-ssh/ssh.h" 41#include "sftp.h" 42#endif 43 44#include <guacamole/client.h> 45#include <guacamole/protocol.h> 46#include <guacamole/recording.h> 47#include <guacamole/socket.h> 48#include <guacamole/timestamp.h> 49#include <guacamole/wol.h> 50#include <rfb/rfbclient.h> 51#include <rfb/rfbconfig.h> 52#include <rfb/rfbproto.h> 53 54#ifdef LIBVNCSERVER_WITH_CLIENT_GCRYPT 55#include <errno.h> 56#include <gcrypt.h> 57#endif 58 59#include <stdlib.h> 60#include <string.h> 61#include <time.h> 62 63#ifdef LIBVNCSERVER_WITH_CLIENT_GCRYPT 64GCRY_THREAD_OPTION_PTHREAD_IMPL; 65#endif 66 67char* GUAC_VNC_CLIENT_KEY = "GUAC_VNC"; 68 69#ifdef ENABLE_VNC_TLS_LOCKING 70/** 71 * A callback function that is called by the VNC library prior to writing 72 * data to a TLS-encrypted socket. This returns the rfbBool FALSE value 73 * if there's an error locking the mutex, or rfbBool TRUE otherwise. 74 * 75 * @param rfb_client 76 * The rfbClient for which to lock the TLS mutex. 77 * 78 * @returns 79 * rfbBool FALSE if an error occurs locking the mutex, otherwise 80 * TRUE. 81 */ 82static rfbBool guac_vnc_lock_write_to_tls(rfbClient* rfb_client) { 83 84 /* Retrieve the Guacamole data structures */ 85 guac_client* gc = rfbClientGetClientData(rfb_client, GUAC_VNC_CLIENT_KEY); 86 guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; 87 88 /* Lock write access */ 89 int retval = pthread_mutex_lock(&(vnc_client->tls_lock)); 90 if (retval) { 91 guac_client_log(gc, GUAC_LOG_ERROR, "Error locking TLS write mutex: %s", 92 strerror(retval)); 93 return FALSE; 94 } 95 return TRUE; 96 97} 98 99/** 100 * A callback function for use by the VNC library that is called once 101 * the client is finished writing to a TLS-encrypted socket. A rfbBool 102 * FALSE value is returned if an error occurs unlocking the mutex, 103 * otherwise TRUE is returned. 104 * 105 * @param rfb_client 106 * The rfbClient for which to unlock the TLS mutex. 107 * 108 * @returns 109 * rfbBool FALSE if an error occurs unlocking the mutex, otherwise 110 * TRUE. 111 */ 112static rfbBool guac_vnc_unlock_write_to_tls(rfbClient* rfb_client) { 113 114 /* Retrieve the Guacamole data structures */ 115 guac_client* gc = rfbClientGetClientData(rfb_client, GUAC_VNC_CLIENT_KEY); 116 guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; 117 118 /* Unlock write access */ 119 int retval = pthread_mutex_unlock(&(vnc_client->tls_lock)); 120 if (retval) { 121 guac_client_log(gc, GUAC_LOG_ERROR, "Error unlocking TLS write mutex: %s", 122 strerror(retval)); 123 return FALSE; 124 } 125 return TRUE; 126} 127#endif 128 129rfbClient* guac_vnc_get_client(guac_client* client) { 130 131 rfbClient* rfb_client = rfbGetClient(8, 3, 4); /* 32-bpp client */ 132 guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; 133 guac_vnc_settings* vnc_settings = vnc_client->settings; 134 135 /* Store Guac client in rfb client */ 136 rfbClientSetClientData(rfb_client, GUAC_VNC_CLIENT_KEY, client); 137 138 /* Framebuffer update handler */ 139 rfb_client->GotFrameBufferUpdate = guac_vnc_update; 140 rfb_client->GotCopyRect = guac_vnc_copyrect; 141 142#ifdef ENABLE_VNC_TLS_LOCKING 143 /* TLS Locking and Unlocking */ 144 rfb_client->LockWriteToTLS = guac_vnc_lock_write_to_tls; 145 rfb_client->UnlockWriteToTLS = guac_vnc_unlock_write_to_tls; 146#endif 147 148#ifdef LIBVNCSERVER_WITH_CLIENT_GCRYPT 149 150 /* Check if GCrypt is initialized, do it if not. */ 151 if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { 152 153 guac_client_log(client, GUAC_LOG_DEBUG, "GCrypt initialization started."); 154 155 /* Initialize thread control. */ 156 gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); 157 158 /* Basic GCrypt library initialization. */ 159 gcry_check_version(NULL); 160 161 /* Mark initialization as completed. */ 162 gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); 163 guac_client_log(client, GUAC_LOG_DEBUG, "GCrypt initialization completed."); 164 165 } 166 167#endif 168 169 /* Do not handle clipboard and local cursor if read-only */ 170 if (vnc_settings->read_only == 0) { 171 172 /* Clipboard */ 173 rfb_client->GotXCutText = guac_vnc_cut_text; 174 175 /* Set remote cursor */ 176 if (vnc_settings->remote_cursor) { 177 rfb_client->appData.useRemoteCursor = FALSE; 178 } 179 180 else { 181 /* Enable client-side cursor */ 182 rfb_client->appData.useRemoteCursor = TRUE; 183 rfb_client->GotCursorShape = guac_vnc_cursor; 184 } 185 186 } 187 188#ifdef ENABLE_VNC_GENERIC_CREDENTIALS 189 /* Authentication */ 190 rfb_client->GetCredential = guac_vnc_get_credentials; 191#endif 192 193 /* Password */ 194 rfb_client->GetPassword = guac_vnc_get_password; 195 196 /* Depth */ 197 guac_vnc_set_pixel_format(rfb_client, vnc_settings->color_depth); 198 199 /* Hook into allocation so we can handle resize. */ 200 vnc_client->rfb_MallocFrameBuffer = rfb_client->MallocFrameBuffer; 201 rfb_client->MallocFrameBuffer = guac_vnc_malloc_framebuffer; 202 rfb_client->canHandleNewFBSize = 1; 203 204 /* Set hostname and port */ 205 rfb_client->serverHost = strdup(vnc_settings->hostname); 206 rfb_client->serverPort = vnc_settings->port; 207 208#ifdef ENABLE_VNC_REPEATER 209 /* Set repeater parameters if specified */ 210 if (vnc_settings->dest_host) { 211 rfb_client->destHost = strdup(vnc_settings->dest_host); 212 rfb_client->destPort = vnc_settings->dest_port; 213 } 214#endif 215 216#ifdef ENABLE_VNC_LISTEN 217 /* If reverse connection enabled, start listening */ 218 if (vnc_settings->reverse_connect) { 219 220 guac_client_log(client, GUAC_LOG_INFO, "Listening for connections on port %i", vnc_settings->port); 221 222 /* Listen for connection from server */ 223 rfb_client->listenPort = vnc_settings->port; 224 if (listenForIncomingConnectionsNoFork(rfb_client, vnc_settings->listen_timeout*1000) <= 0) 225 return NULL; 226 227 } 228#endif 229 230 /* Set encodings if provided */ 231 if (vnc_settings->encodings) 232 rfb_client->appData.encodingsString = strdup(vnc_settings->encodings); 233 234 /* Connect */ 235 if (rfbInitClient(rfb_client, NULL, NULL)) 236 return rfb_client; 237 238 /* If connection fails, return NULL */ 239 return NULL; 240 241} 242 243/** 244 * Waits until data is available to be read from the given rfbClient, and thus 245 * a call to HandleRFBServerMessages() should not block. If the timeout elapses 246 * before data is available, zero is returned. 247 * 248 * @param rfb_client 249 * The rfbClient to wait for. 250 * 251 * @param timeout 252 * The maximum amount of time to wait, in microseconds. 253 * 254 * @returns 255 * A positive value if data is available, zero if the timeout elapses 256 * before data becomes available, or a negative value on error. 257 */ 258static int guac_vnc_wait_for_messages(rfbClient* rfb_client, int timeout) { 259 260 /* Do not explicitly wait while data is on the buffer */ 261 if (rfb_client->buffered) 262 return 1; 263 264 /* If no data on buffer, wait for data on socket */ 265 return WaitForMessage(rfb_client, timeout); 266 267} 268 269void* guac_vnc_client_thread(void* data) { 270 271 guac_client* client = (guac_client*) data; 272 guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; 273 guac_vnc_settings* settings = vnc_client->settings; 274 275 /* If Wake-on-LAN is enabled, attempt to wake. */ 276 if (settings->wol_send_packet) { 277 guac_client_log(client, GUAC_LOG_DEBUG, "Sending Wake-on-LAN packet, " 278 "and pausing for %d seconds.", settings->wol_wait_time); 279 280 /* Send the Wake-on-LAN request. */ 281 if (guac_wol_wake(settings->wol_mac_addr, settings->wol_broadcast_addr, 282 settings->wol_udp_port)) 283 return NULL; 284 285 /* If wait time is specified, sleep for that amount of time. */ 286 if (settings->wol_wait_time > 0) 287 guac_timestamp_msleep(settings->wol_wait_time * 1000); 288 } 289 290 /* Configure clipboard encoding */ 291 if (guac_vnc_set_clipboard_encoding(client, settings->clipboard_encoding)) { 292 guac_client_log(client, GUAC_LOG_INFO, "Using non-standard VNC " 293 "clipboard encoding: '%s'.", settings->clipboard_encoding); 294 } 295 296 /* Set up libvncclient logging */ 297 rfbClientLog = guac_vnc_client_log_info; 298 rfbClientErr = guac_vnc_client_log_error; 299 300 /* Attempt connection */ 301 rfbClient* rfb_client = guac_vnc_get_client(client); 302 int retries_remaining = settings->retries; 303 304 /* If unsuccessful, retry as many times as specified */ 305 while (!rfb_client && retries_remaining > 0) { 306 307 guac_client_log(client, GUAC_LOG_INFO, 308 "Connect failed. Waiting %ims before retrying...", 309 GUAC_VNC_CONNECT_INTERVAL); 310 311 /* Wait for given interval then retry */ 312 guac_timestamp_msleep(GUAC_VNC_CONNECT_INTERVAL); 313 rfb_client = guac_vnc_get_client(client); 314 retries_remaining--; 315 316 } 317 318 /* If the final connect attempt fails, return error */ 319 if (!rfb_client) { 320 guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, 321 "Unable to connect to VNC server."); 322 return NULL; 323 } 324 325#ifdef ENABLE_PULSE 326 /* If audio is enabled, start streaming via PulseAudio */ 327 if (settings->audio_enabled) 328 vnc_client->audio = guac_pa_stream_alloc(client, 329 settings->pa_servername); 330#endif 331 332#ifdef ENABLE_COMMON_SSH 333 guac_common_ssh_init(client); 334 335 /* Connect via SSH if SFTP is enabled */ 336 if (settings->enable_sftp) { 337 338 /* Abort if username is missing */ 339 if (settings->sftp_username == NULL) { 340 guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, 341 "SFTP username is required if SFTP is enabled."); 342 return NULL; 343 } 344 345 guac_client_log(client, GUAC_LOG_DEBUG, 346 "Connecting via SSH for SFTP filesystem access."); 347 348 vnc_client->sftp_user = 349 guac_common_ssh_create_user(settings->sftp_username); 350 351 /* Import private key, if given */ 352 if (settings->sftp_private_key != NULL) { 353 354 guac_client_log(client, GUAC_LOG_DEBUG, 355 "Authenticating with private key."); 356 357 /* Abort if private key cannot be read */ 358 if (guac_common_ssh_user_import_key(vnc_client->sftp_user, 359 settings->sftp_private_key, 360 settings->sftp_passphrase)) { 361 guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, 362 "Private key unreadable."); 363 return NULL; 364 } 365 366 } 367 368 /* Otherwise, use specified password */ 369 else { 370 guac_client_log(client, GUAC_LOG_DEBUG, 371 "Authenticating with password."); 372 guac_common_ssh_user_set_password(vnc_client->sftp_user, 373 settings->sftp_password); 374 } 375 376 /* Attempt SSH connection */ 377 vnc_client->sftp_session = 378 guac_common_ssh_create_session(client, settings->sftp_hostname, 379 settings->sftp_port, vnc_client->sftp_user, settings->sftp_server_alive_interval, 380 settings->sftp_host_key, NULL); 381 382 /* Fail if SSH connection does not succeed */ 383 if (vnc_client->sftp_session == NULL) { 384 /* Already aborted within guac_common_ssh_create_session() */ 385 return NULL; 386 } 387 388 /* Load filesystem */ 389 vnc_client->sftp_filesystem = 390 guac_common_ssh_create_sftp_filesystem(vnc_client->sftp_session, 391 settings->sftp_root_directory, NULL, 392 settings->sftp_disable_download, 393 settings->sftp_disable_upload); 394 395 /* Expose filesystem to connection owner */ 396 guac_client_for_owner(client, 397 guac_common_ssh_expose_sftp_filesystem, 398 vnc_client->sftp_filesystem); 399 400 /* Abort if SFTP connection fails */ 401 if (vnc_client->sftp_filesystem == NULL) { 402 guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, 403 "SFTP connection failed."); 404 return NULL; 405 } 406 407 /* Configure destination for basic uploads, if specified */ 408 if (settings->sftp_directory != NULL) 409 guac_common_ssh_sftp_set_upload_path( 410 vnc_client->sftp_filesystem, 411 settings->sftp_directory); 412 413 guac_client_log(client, GUAC_LOG_DEBUG, 414 "SFTP connection succeeded."); 415 416 } 417#endif 418 419 /* Set remaining client data */ 420 vnc_client->rfb_client = rfb_client; 421 422 /* Set up screen recording, if requested */ 423 if (settings->recording_path != NULL) { 424 vnc_client->recording = guac_recording_create(client, 425 settings->recording_path, 426 settings->recording_name, 427 settings->create_recording_path, 428 !settings->recording_exclude_output, 429 !settings->recording_exclude_mouse, 430 0, /* Touch events not supported */ 431 settings->recording_include_keys); 432 } 433 434 /* Create display */ 435 vnc_client->display = guac_common_display_alloc(client, 436 rfb_client->width, rfb_client->height); 437 438 /* Use lossless compression only if requested (otherwise, use default 439 * heuristics) */ 440 guac_common_display_set_lossless(vnc_client->display, settings->lossless); 441 442 /* If not read-only, set an appropriate cursor */ 443 if (settings->read_only == 0) { 444 if (settings->remote_cursor) 445 guac_common_cursor_set_dot(vnc_client->display->cursor); 446 else 447 guac_common_cursor_set_pointer(vnc_client->display->cursor); 448 449 } 450 451 guac_socket_flush(client->socket); 452 453 guac_timestamp last_frame_end = guac_timestamp_current(); 454 455 /* Handle messages from VNC server while client is running */ 456 while (client->state == GUAC_CLIENT_RUNNING) { 457 458 /* Wait for start of frame */ 459 int wait_result = guac_vnc_wait_for_messages(rfb_client, 460 GUAC_VNC_FRAME_START_TIMEOUT); 461 if (wait_result > 0) { 462 463 int processing_lag = guac_client_get_processing_lag(client); 464 guac_timestamp frame_start = guac_timestamp_current(); 465 466 /* Read server messages until frame is built */ 467 do { 468 469 guac_timestamp frame_end; 470 int frame_remaining; 471 472 /* Handle any message received */ 473 if (!HandleRFBServerMessage(rfb_client)) { 474 guac_client_abort(client, 475 GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, 476 "Error handling message from VNC server."); 477 break; 478 } 479 480 /* Calculate time remaining in frame */ 481 frame_end = guac_timestamp_current(); 482 frame_remaining = frame_start + GUAC_VNC_FRAME_DURATION 483 - frame_end; 484 485 /* Calculate time that client needs to catch up */ 486 int time_elapsed = frame_end - last_frame_end; 487 int required_wait = processing_lag - time_elapsed; 488 489 /* Increase the duration of this frame if client is lagging */ 490 if (required_wait > GUAC_VNC_FRAME_TIMEOUT) 491 wait_result = guac_vnc_wait_for_messages(rfb_client, 492 required_wait*1000); 493 494 /* Wait again if frame remaining */ 495 else if (frame_remaining > 0) 496 wait_result = guac_vnc_wait_for_messages(rfb_client, 497 GUAC_VNC_FRAME_TIMEOUT*1000); 498 else 499 break; 500 501 } while (wait_result > 0); 502 503 /* Record end of frame, excluding server-side rendering time (we 504 * assume server-side rendering time will be consistent between any 505 * two subsequent frames, and that this time should thus be 506 * excluded from the required wait period of the next frame). */ 507 last_frame_end = frame_start; 508 509 } 510 511 /* If an error occurs, log it and fail */ 512 if (wait_result < 0) 513 guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Connection closed."); 514 515 /* Flush frame */ 516 guac_common_surface_flush(vnc_client->display->default_surface); 517 guac_client_end_frame(client); 518 guac_socket_flush(client->socket); 519 520 } 521 522 /* Kill client and finish connection */ 523 guac_client_stop(client); 524 guac_client_log(client, GUAC_LOG_INFO, "Internal VNC client disconnected"); 525 return NULL; 526 527}