user-handlers.c (21882B)
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 "guacamole/mem.h" 23#include "guacamole/client.h" 24#include "guacamole/object.h" 25#include "guacamole/protocol.h" 26#include "guacamole/stream.h" 27#include "guacamole/string.h" 28#include "guacamole/timestamp.h" 29#include "guacamole/user.h" 30#include "user-handlers.h" 31 32#include <inttypes.h> 33#include <stdio.h> 34#include <stdint.h> 35#include <stdlib.h> 36#include <string.h> 37 38/* Guacamole instruction handler map */ 39 40__guac_instruction_handler_mapping __guac_instruction_handler_map[] = { 41 {"sync", __guac_handle_sync}, 42 {"touch", __guac_handle_touch}, 43 {"mouse", __guac_handle_mouse}, 44 {"key", __guac_handle_key}, 45 {"clipboard", __guac_handle_clipboard}, 46 {"disconnect", __guac_handle_disconnect}, 47 {"size", __guac_handle_size}, 48 {"file", __guac_handle_file}, 49 {"pipe", __guac_handle_pipe}, 50 {"ack", __guac_handle_ack}, 51 {"blob", __guac_handle_blob}, 52 {"end", __guac_handle_end}, 53 {"get", __guac_handle_get}, 54 {"put", __guac_handle_put}, 55 {"audio", __guac_handle_audio}, 56 {"argv", __guac_handle_argv}, 57 {"nop", __guac_handle_nop}, 58 {NULL, NULL} 59}; 60 61/* Guacamole handshake handler map */ 62 63__guac_instruction_handler_mapping __guac_handshake_handler_map[] = { 64 {"size", __guac_handshake_size_handler}, 65 {"audio", __guac_handshake_audio_handler}, 66 {"video", __guac_handshake_video_handler}, 67 {"image", __guac_handshake_image_handler}, 68 {"timezone", __guac_handshake_timezone_handler}, 69 {"name", __guac_handshake_name_handler}, 70 {NULL, NULL} 71}; 72 73/** 74 * Parses a 64-bit integer from the given string. It is assumed that the string 75 * will contain only decimal digits, with an optional leading minus sign. 76 * The result of parsing a string which does not conform to this pattern is 77 * undefined. 78 * 79 * @param str 80 * The string to parse, which must contain only decimal digits and an 81 * optional leading minus sign. 82 * 83 * @return 84 * The 64-bit integer value represented by the given string. 85 */ 86static int64_t __guac_parse_int(const char* str) { 87 88 int sign = 1; 89 int64_t num = 0; 90 91 for (; *str != '\0'; str++) { 92 93 if (*str == '-') 94 sign = -sign; 95 else 96 num = num * 10 + (*str - '0'); 97 98 } 99 100 return num * sign; 101 102} 103 104/* Guacamole instruction handlers */ 105 106int __guac_handle_sync(guac_user* user, int argc, char** argv) { 107 108 int frame_duration; 109 110 guac_timestamp current = guac_timestamp_current(); 111 guac_timestamp timestamp = __guac_parse_int(argv[0]); 112 113 /* Error if timestamp is in future */ 114 if (timestamp > user->client->last_sent_timestamp) 115 return -1; 116 117 /* Only update lag calculations if timestamp is sane */ 118 if (timestamp >= user->last_received_timestamp) { 119 120 /* Update stored timestamp */ 121 user->last_received_timestamp = timestamp; 122 123 /* Calculate length of frame, including network and processing lag */ 124 frame_duration = current - timestamp; 125 126 /* Update lag statistics if at least one frame has been rendered */ 127 if (user->last_frame_duration != 0) { 128 129 /* Calculate lag using the previous frame as a baseline */ 130 int processing_lag = frame_duration - user->last_frame_duration; 131 132 /* Adjust back to zero if cumulative error leads to a negative 133 * value */ 134 if (processing_lag < 0) 135 processing_lag = 0; 136 137 user->processing_lag = processing_lag; 138 139 } 140 141 /* Record baseline duration of frame by excluding lag */ 142 user->last_frame_duration = frame_duration - user->processing_lag; 143 144 } 145 146 /* Log received timestamp and calculated lag (at TRACE level only) */ 147 guac_user_log(user, GUAC_LOG_TRACE, 148 "User confirmation of frame %" PRIu64 "ms received " 149 "at %" PRIu64 "ms (processing_lag=%ims)", 150 timestamp, current, user->processing_lag); 151 152 if (user->sync_handler) 153 return user->sync_handler(user, timestamp); 154 return 0; 155} 156 157int __guac_handle_touch(guac_user* user, int argc, char** argv) { 158 if (user->touch_handler) 159 return user->touch_handler( 160 user, 161 atoi(argv[0]), /* id */ 162 atoi(argv[1]), /* x */ 163 atoi(argv[2]), /* y */ 164 atoi(argv[3]), /* x_radius */ 165 atoi(argv[4]), /* y_radius */ 166 atof(argv[5]), /* angle */ 167 atof(argv[6]) /* force */ 168 ); 169 return 0; 170} 171 172int __guac_handle_mouse(guac_user* user, int argc, char** argv) { 173 if (user->mouse_handler) 174 return user->mouse_handler( 175 user, 176 atoi(argv[0]), /* x */ 177 atoi(argv[1]), /* y */ 178 atoi(argv[2]) /* mask */ 179 ); 180 return 0; 181} 182 183int __guac_handle_key(guac_user* user, int argc, char** argv) { 184 if (user->key_handler) 185 return user->key_handler( 186 user, 187 atoi(argv[0]), /* keysym */ 188 atoi(argv[1]) /* pressed */ 189 ); 190 return 0; 191} 192 193/** 194 * Retrieves the existing user-level input stream having the given index. These 195 * will be streams which were created by the remotely-connected user. If the 196 * index is invalid or too large, this function will automatically respond with 197 * an "ack" instruction containing an appropriate error code. 198 * 199 * @param user 200 * The user associated with the stream being retrieved. 201 * 202 * @param stream_index 203 * The index of the stream to retrieve. 204 * 205 * @return 206 * The stream associated with the given user and having the given index, 207 * or NULL if the index is invalid. 208 */ 209static guac_stream* __get_input_stream(guac_user* user, int stream_index) { 210 211 /* Validate stream index */ 212 if (stream_index < 0 || stream_index >= GUAC_USER_MAX_STREAMS) { 213 214 guac_stream dummy_stream; 215 dummy_stream.index = stream_index; 216 217 guac_protocol_send_ack(user->socket, &dummy_stream, 218 "Invalid stream index", GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); 219 return NULL; 220 } 221 222 return &(user->__input_streams[stream_index]); 223 224} 225 226/** 227 * Retrieves the existing, in-progress (open) user-level input stream having 228 * the given index. These will be streams which were created by the 229 * remotely-connected user. If the index is invalid, too large, or the stream 230 * is closed, this function will automatically respond with an "ack" 231 * instruction containing an appropriate error code. 232 * 233 * @param user 234 * The user associated with the stream being retrieved. 235 * 236 * @param stream_index 237 * The index of the stream to retrieve. 238 * 239 * @return 240 * The in-progress (open)stream associated with the given user and having 241 * the given index, or NULL if the index is invalid or the stream is 242 * closed. 243 */ 244static guac_stream* __get_open_input_stream(guac_user* user, int stream_index) { 245 246 guac_stream* stream = __get_input_stream(user, stream_index); 247 248 /* Fail if no such stream */ 249 if (stream == NULL) 250 return NULL; 251 252 /* Validate initialization of stream */ 253 if (stream->index == GUAC_USER_CLOSED_STREAM_INDEX) { 254 255 guac_stream dummy_stream; 256 dummy_stream.index = stream_index; 257 258 guac_protocol_send_ack(user->socket, &dummy_stream, 259 "Invalid stream index", GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); 260 return NULL; 261 } 262 263 return stream; 264 265} 266 267/** 268 * Initializes and returns a new user-level input stream having the given 269 * index, clearing any values that may have been assigned by a past use of the 270 * underlying stream object storage. If the stream was already open, it will 271 * first be closed and its end handlers invoked as if explicitly closed by the 272 * user. 273 * 274 * @param user 275 * The user associated with the stream being initialized. 276 * 277 * @param stream_index 278 * The index of the stream to initialized. 279 * 280 * @return 281 * A new initialized user-level input stream having the given index, or 282 * NULL if the index is invalid. 283 */ 284static guac_stream* __init_input_stream(guac_user* user, int stream_index) { 285 286 guac_stream* stream = __get_input_stream(user, stream_index); 287 288 /* Fail if no such stream */ 289 if (stream == NULL) 290 return NULL; 291 292 /* Force end of previous stream if open */ 293 if (stream->index != GUAC_USER_CLOSED_STREAM_INDEX) { 294 295 /* Call stream handler if defined */ 296 if (stream->end_handler) 297 stream->end_handler(user, stream); 298 299 /* Fall back to global handler if defined */ 300 else if (user->end_handler) 301 user->end_handler(user, stream); 302 303 } 304 305 /* Initialize stream */ 306 stream->index = stream_index; 307 stream->data = NULL; 308 stream->ack_handler = NULL; 309 stream->blob_handler = NULL; 310 stream->end_handler = NULL; 311 312 return stream; 313 314} 315 316int __guac_handle_audio(guac_user* user, int argc, char** argv) { 317 318 /* Pull corresponding stream */ 319 int stream_index = atoi(argv[0]); 320 guac_stream* stream = __init_input_stream(user, stream_index); 321 if (stream == NULL) 322 return 0; 323 324 /* If supported, call handler */ 325 if (user->audio_handler) 326 return user->audio_handler( 327 user, 328 stream, 329 argv[1] /* mimetype */ 330 ); 331 332 /* Otherwise, abort */ 333 guac_protocol_send_ack(user->socket, stream, 334 "Audio input unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); 335 return 0; 336 337} 338 339int __guac_handle_clipboard(guac_user* user, int argc, char** argv) { 340 341 /* Pull corresponding stream */ 342 int stream_index = atoi(argv[0]); 343 guac_stream* stream = __init_input_stream(user, stream_index); 344 if (stream == NULL) 345 return 0; 346 347 /* If supported, call handler */ 348 if (user->clipboard_handler) 349 return user->clipboard_handler( 350 user, 351 stream, 352 argv[1] /* mimetype */ 353 ); 354 355 /* Otherwise, abort */ 356 guac_protocol_send_ack(user->socket, stream, 357 "Clipboard unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); 358 return 0; 359 360} 361 362int __guac_handle_size(guac_user* user, int argc, char** argv) { 363 if (user->size_handler) 364 return user->size_handler( 365 user, 366 atoi(argv[0]), /* width */ 367 atoi(argv[1]) /* height */ 368 ); 369 return 0; 370} 371 372int __guac_handle_file(guac_user* user, int argc, char** argv) { 373 374 /* Pull corresponding stream */ 375 int stream_index = atoi(argv[0]); 376 guac_stream* stream = __init_input_stream(user, stream_index); 377 if (stream == NULL) 378 return 0; 379 380 /* If supported, call handler */ 381 if (user->file_handler) 382 return user->file_handler( 383 user, 384 stream, 385 argv[1], /* mimetype */ 386 argv[2] /* filename */ 387 ); 388 389 /* Otherwise, abort */ 390 guac_protocol_send_ack(user->socket, stream, 391 "File transfer unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); 392 return 0; 393} 394 395int __guac_handle_pipe(guac_user* user, int argc, char** argv) { 396 397 /* Pull corresponding stream */ 398 int stream_index = atoi(argv[0]); 399 guac_stream* stream = __init_input_stream(user, stream_index); 400 if (stream == NULL) 401 return 0; 402 403 /* If supported, call handler */ 404 if (user->pipe_handler) 405 return user->pipe_handler( 406 user, 407 stream, 408 argv[1], /* mimetype */ 409 argv[2] /* name */ 410 ); 411 412 /* Otherwise, abort */ 413 guac_protocol_send_ack(user->socket, stream, 414 "Named pipes unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); 415 return 0; 416} 417 418int __guac_handle_argv(guac_user* user, int argc, char** argv) { 419 420 /* Pull corresponding stream */ 421 int stream_index = atoi(argv[0]); 422 guac_stream* stream = __init_input_stream(user, stream_index); 423 if (stream == NULL) 424 return 0; 425 426 /* If supported, call handler */ 427 if (user->argv_handler) 428 return user->argv_handler( 429 user, 430 stream, 431 argv[1], /* mimetype */ 432 argv[2] /* name */ 433 ); 434 435 /* Otherwise, abort */ 436 guac_protocol_send_ack(user->socket, stream, 437 "Reconfiguring in-progress connections unsupported", 438 GUAC_PROTOCOL_STATUS_UNSUPPORTED); 439 return 0; 440} 441 442int __guac_handle_ack(guac_user* user, int argc, char** argv) { 443 444 guac_stream* stream; 445 446 /* Parse stream index */ 447 int stream_index = atoi(argv[0]); 448 449 /* Ignore indices of client-level streams */ 450 if (stream_index % 2 != 0) 451 return 0; 452 453 /* Determine index within user-level array of streams */ 454 stream_index /= 2; 455 456 /* Validate stream index */ 457 if (stream_index < 0 || stream_index >= GUAC_USER_MAX_STREAMS) 458 return 0; 459 460 stream = &(user->__output_streams[stream_index]); 461 462 /* Validate initialization of stream */ 463 if (stream->index == GUAC_USER_CLOSED_STREAM_INDEX) 464 return 0; 465 466 /* Call stream handler if defined */ 467 if (stream->ack_handler) 468 return stream->ack_handler(user, stream, argv[1], 469 atoi(argv[2])); 470 471 /* Fall back to global handler if defined */ 472 if (user->ack_handler) 473 return user->ack_handler(user, stream, argv[1], 474 atoi(argv[2])); 475 476 return 0; 477} 478 479int __guac_handle_blob(guac_user* user, int argc, char** argv) { 480 481 int stream_index = atoi(argv[0]); 482 guac_stream* stream = __get_open_input_stream(user, stream_index); 483 484 /* Fail if no such stream */ 485 if (stream == NULL) 486 return 0; 487 488 /* Call stream handler if defined */ 489 if (stream->blob_handler) { 490 int length = guac_protocol_decode_base64(argv[1]); 491 return stream->blob_handler(user, stream, argv[1], 492 length); 493 } 494 495 /* Fall back to global handler if defined */ 496 if (user->blob_handler) { 497 int length = guac_protocol_decode_base64(argv[1]); 498 return user->blob_handler(user, stream, argv[1], 499 length); 500 } 501 502 guac_protocol_send_ack(user->socket, stream, 503 "File transfer unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); 504 return 0; 505} 506 507int __guac_handle_end(guac_user* user, int argc, char** argv) { 508 509 int result = 0; 510 int stream_index = atoi(argv[0]); 511 guac_stream* stream = __get_open_input_stream(user, stream_index); 512 513 /* Fail if no such stream */ 514 if (stream == NULL) 515 return 0; 516 517 /* Call stream handler if defined */ 518 if (stream->end_handler) 519 result = stream->end_handler(user, stream); 520 521 /* Fall back to global handler if defined */ 522 else if (user->end_handler) 523 result = user->end_handler(user, stream); 524 525 /* Mark stream as closed */ 526 stream->index = GUAC_USER_CLOSED_STREAM_INDEX; 527 return result; 528} 529 530int __guac_handle_get(guac_user* user, int argc, char** argv) { 531 532 guac_object* object; 533 534 /* Validate object index */ 535 int object_index = atoi(argv[0]); 536 if (object_index < 0 || object_index >= GUAC_USER_MAX_OBJECTS) 537 return 0; 538 539 object = &(user->__objects[object_index]); 540 541 /* Validate initialization of object */ 542 if (object->index == GUAC_USER_UNDEFINED_OBJECT_INDEX) 543 return 0; 544 545 /* Call object handler if defined */ 546 if (object->get_handler) 547 return object->get_handler( 548 user, 549 object, 550 argv[1] /* name */ 551 ); 552 553 /* Fall back to global handler if defined */ 554 if (user->get_handler) 555 return user->get_handler( 556 user, 557 object, 558 argv[1] /* name */ 559 ); 560 561 return 0; 562} 563 564int __guac_handle_put(guac_user* user, int argc, char** argv) { 565 566 guac_object* object; 567 568 /* Validate object index */ 569 int object_index = atoi(argv[0]); 570 if (object_index < 0 || object_index >= GUAC_USER_MAX_OBJECTS) 571 return 0; 572 573 object = &(user->__objects[object_index]); 574 575 /* Validate initialization of object */ 576 if (object->index == GUAC_USER_UNDEFINED_OBJECT_INDEX) 577 return 0; 578 579 /* Pull corresponding stream */ 580 int stream_index = atoi(argv[1]); 581 guac_stream* stream = __init_input_stream(user, stream_index); 582 if (stream == NULL) 583 return 0; 584 585 /* Call object handler if defined */ 586 if (object->put_handler) 587 return object->put_handler( 588 user, 589 object, 590 stream, 591 argv[2], /* mimetype */ 592 argv[3] /* name */ 593 ); 594 595 /* Fall back to global handler if defined */ 596 if (user->put_handler) 597 return user->put_handler( 598 user, 599 object, 600 stream, 601 argv[2], /* mimetype */ 602 argv[3] /* name */ 603 ); 604 605 /* Otherwise, abort */ 606 guac_protocol_send_ack(user->socket, stream, 607 "Object write unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); 608 return 0; 609} 610 611int __guac_handle_nop(guac_user* user, int argc, char** argv) { 612 guac_user_log(user, GUAC_LOG_TRACE, 613 "Received nop instruction"); 614 return 0; 615} 616 617int __guac_handle_disconnect(guac_user* user, int argc, char** argv) { 618 guac_user_stop(user); 619 return 0; 620} 621 622/* Guacamole handshake handler functions. */ 623 624int __guac_handshake_size_handler(guac_user* user, int argc, char** argv) { 625 626 /* Validate size of instruction. */ 627 if (argc < 2) { 628 guac_user_log(user, GUAC_LOG_ERROR, "Received \"size\" " 629 "instruction lacked required arguments."); 630 return 1; 631 } 632 633 /* Parse optimal screen dimensions from size instruction */ 634 user->info.optimal_width = atoi(argv[0]); 635 user->info.optimal_height = atoi(argv[1]); 636 637 /* If DPI given, set the user resolution */ 638 if (argc >= 3) 639 user->info.optimal_resolution = atoi(argv[2]); 640 641 /* Otherwise, use a safe default for rough backwards compatibility */ 642 else 643 user->info.optimal_resolution = 96; 644 645 return 0; 646 647} 648 649int __guac_handshake_audio_handler(guac_user* user, int argc, char** argv) { 650 651 guac_free_mimetypes((char **) user->info.audio_mimetypes); 652 653 /* Store audio mimetypes */ 654 user->info.audio_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); 655 656 return 0; 657 658} 659 660int __guac_handshake_video_handler(guac_user* user, int argc, char** argv) { 661 662 guac_free_mimetypes((char **) user->info.video_mimetypes); 663 664 /* Store video mimetypes */ 665 user->info.video_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); 666 667 return 0; 668 669} 670 671int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) { 672 673 guac_free_mimetypes((char **) user->info.image_mimetypes); 674 675 /* Store image mimetypes */ 676 user->info.image_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); 677 678 return 0; 679 680} 681 682int __guac_handshake_name_handler(guac_user* user, int argc, char** argv) { 683 684 /* Free any past value for the user's name */ 685 guac_mem_free_const(user->info.name); 686 687 /* If a value is provided for the name, copy it into guac_user. */ 688 if (argc > 0 && strcmp(argv[0], "")) 689 user->info.name = (const char*) guac_strdup(argv[0]); 690 691 /* No or empty value was provided, so make sure this is NULLed out. */ 692 else 693 user->info.name = NULL; 694 695 return 0; 696 697} 698 699int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) { 700 701 /* Free any past value */ 702 guac_mem_free_const(user->info.timezone); 703 704 /* Store timezone, if present */ 705 if (argc > 0 && strcmp(argv[0], "")) 706 user->info.timezone = (const char*) guac_strdup(argv[0]); 707 708 else 709 user->info.timezone = NULL; 710 711 return 0; 712 713} 714 715char** guac_copy_mimetypes(char** mimetypes, int count) { 716 717 int i; 718 719 /* Allocate sufficient space for NULL-terminated array of mimetypes */ 720 char** mimetypes_copy = guac_mem_alloc(sizeof(char*), 721 guac_mem_ckd_add_or_die(count, 1)); 722 723 /* Copy each provided mimetype */ 724 for (i = 0; i < count; i++) 725 mimetypes_copy[i] = guac_strdup(mimetypes[i]); 726 727 /* Terminate with NULL */ 728 mimetypes_copy[count] = NULL; 729 730 return mimetypes_copy; 731 732} 733 734void guac_free_mimetypes(char** mimetypes) { 735 736 if (mimetypes == NULL) 737 return; 738 739 char** current_mimetype = mimetypes; 740 741 /* Free all strings within NULL-terminated mimetype array */ 742 while (*current_mimetype != NULL) { 743 guac_mem_free(*current_mimetype); 744 current_mimetype++; 745 } 746 747 /* Free the array itself, now that its contents have been freed */ 748 guac_mem_free(mimetypes); 749 750} 751 752int __guac_user_call_opcode_handler(__guac_instruction_handler_mapping* map, 753 guac_user* user, const char* opcode, int argc, char** argv) { 754 755 /* For each defined instruction */ 756 __guac_instruction_handler_mapping* current = map; 757 while (current->opcode != NULL) { 758 759 /* If recognized, call handler */ 760 if (strcmp(opcode, current->opcode) == 0) 761 return current->handler(user, argc, argv); 762 763 current++; 764 } 765 766 /* If unrecognized, log and ignore */ 767 guac_user_log(user, GUAC_LOG_DEBUG, "Handler not found for \"%s\"", 768 opcode); 769 return 0; 770 771} 772