sftp.c (32931B)
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 "common-ssh/sftp.h" 21#include "common-ssh/ssh.h" 22 23#include <guacamole/client.h> 24#include <guacamole/mem.h> 25#include <guacamole/object.h> 26#include <guacamole/protocol.h> 27#include <guacamole/socket.h> 28#include <guacamole/string.h> 29#include <guacamole/user.h> 30#include <libssh2.h> 31 32#include <fcntl.h> 33#include <libgen.h> 34#include <stdlib.h> 35#include <string.h> 36 37int guac_common_ssh_sftp_normalize_path(char* fullpath, 38 const char* path) { 39 40 int path_depth = 0; 41 const char* path_components[GUAC_COMMON_SSH_SFTP_MAX_DEPTH]; 42 43 /* If original path is not absolute, normalization fails */ 44 if (path[0] != '\\' && path[0] != '/') 45 return 0; 46 47 /* Create scratch copy of path excluding leading slash (we will be 48 * replacing path separators with null terminators and referencing those 49 * substrings directly as path components) */ 50 char path_scratch[GUAC_COMMON_SSH_SFTP_MAX_PATH - 1]; 51 int length = guac_strlcpy(path_scratch, path + 1, 52 sizeof(path_scratch)); 53 54 /* Fail if provided path is too long */ 55 if (length >= sizeof(path_scratch)) 56 return 0; 57 58 /* Locate all path components within path */ 59 const char* current_path_component = &(path_scratch[0]); 60 for (int i = 0; i <= length; i++) { 61 62 /* If current character is a path separator, parse as component */ 63 char c = path_scratch[i]; 64 if (c == '/' || c == '\\' || c == '\0') { 65 66 /* Terminate current component */ 67 path_scratch[i] = '\0'; 68 69 /* If component refers to parent, just move up in depth */ 70 if (strcmp(current_path_component, "..") == 0) { 71 if (path_depth > 0) 72 path_depth--; 73 } 74 75 /* Otherwise, if component not current directory, add to list */ 76 else if (strcmp(current_path_component, ".") != 0 77 && strcmp(current_path_component, "") != 0) { 78 79 /* Fail normalization if path is too deep */ 80 if (path_depth >= GUAC_COMMON_SSH_SFTP_MAX_DEPTH) 81 return 0; 82 83 path_components[path_depth++] = current_path_component; 84 85 } 86 87 /* Update start of next component */ 88 current_path_component = &(path_scratch[i+1]); 89 90 } /* end if separator */ 91 92 } /* end for each character */ 93 94 /* Add leading slash for resulting absolute path */ 95 fullpath[0] = '/'; 96 97 /* Append normalized components to path, separated by slashes */ 98 guac_strljoin(fullpath + 1, path_components, path_depth, 99 "/", GUAC_COMMON_SSH_SFTP_MAX_PATH - 1); 100 101 return 1; 102 103} 104 105/** 106 * Translates the last error message received by the SFTP layer of an SSH 107 * session into a Guacamole protocol status code. 108 * 109 * @param filesystem 110 * The object (not guac_object) defining the filesystem associated with the 111 * SFTP and SSH sessions. 112 * 113 * @return 114 * The Guacamole protocol status code corresponding to the last reported 115 * error of the SFTP layer, if nay, or GUAC_PROTOCOL_STATUS_SUCCESS if no 116 * error has occurred. 117 */ 118static guac_protocol_status guac_sftp_get_status( 119 guac_common_ssh_sftp_filesystem* filesystem) { 120 121 /* Get libssh2 objects */ 122 LIBSSH2_SFTP* sftp = filesystem->sftp_session; 123 LIBSSH2_SESSION* session = filesystem->ssh_session->session; 124 125 /* Return success code if no error occurred */ 126 if (libssh2_session_last_errno(session) != LIBSSH2_ERROR_SFTP_PROTOCOL) 127 return GUAC_PROTOCOL_STATUS_SUCCESS; 128 129 /* Translate SFTP error codes defined by 130 * https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 (the most 131 * commonly-implemented standard) */ 132 switch (libssh2_sftp_last_error(sftp)) { 133 134 /* SSH_FX_OK (not an error) */ 135 case 0: 136 return GUAC_PROTOCOL_STATUS_SUCCESS; 137 138 /* SSH_FX_EOF (technically not an error) */ 139 case 1: 140 return GUAC_PROTOCOL_STATUS_SUCCESS; 141 142 /* SSH_FX_NO_SUCH_FILE */ 143 case 2: 144 return GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND; 145 146 /* SSH_FX_PERMISSION_DENIED */ 147 case 3: 148 return GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN; 149 150 /* SSH_FX_FAILURE */ 151 case 4: 152 return GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR; 153 154 /* SSH_FX_BAD_MESSAGE */ 155 case 5: 156 return GUAC_PROTOCOL_STATUS_SERVER_ERROR; 157 158 /* SSH_FX_NO_CONNECTION / SSH_FX_CONNECTION_LOST */ 159 case 6: 160 case 7: 161 return GUAC_PROTOCOL_STATUS_UPSTREAM_TIMEOUT; 162 163 /* SSH_FX_OP_UNSUPPORTED */ 164 case 8: 165 return GUAC_PROTOCOL_STATUS_UNSUPPORTED; 166 167 /* Return generic error if cause unknown */ 168 default: 169 return GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR; 170 171 } 172 173} 174 175/** 176 * Concatenates the given filename with the given path, separating the two 177 * with a single forward slash. The full result must be no more than 178 * GUAC_COMMON_SSH_SFTP_MAX_PATH bytes long, counting null terminator. 179 * 180 * @param fullpath 181 * The buffer to store the result within. This buffer must be at least 182 * GUAC_COMMON_SSH_SFTP_MAX_PATH bytes long. 183 * 184 * @param path 185 * The path to append the filename to. 186 * 187 * @param filename 188 * The filename to append to the path. 189 * 190 * @return 191 * Non-zero if the filename is valid and was successfully appended to the 192 * path, zero otherwise. 193 */ 194static int guac_ssh_append_filename(char* fullpath, const char* path, 195 const char* filename) { 196 197 int length; 198 199 /* Disallow "." as a filename */ 200 if (strcmp(filename, ".") == 0) 201 return 0; 202 203 /* Disallow ".." as a filename */ 204 if (strcmp(filename, "..") == 0) 205 return 0; 206 207 /* Filenames may not contain slashes */ 208 if (strchr(filename, '/') != NULL) 209 return 0; 210 211 /* Copy base path */ 212 length = guac_strlcpy(fullpath, path, GUAC_COMMON_SSH_SFTP_MAX_PATH); 213 214 /* 215 * Append trailing slash only if: 216 * 1) Trailing slash is not already present 217 * 2) Path is non-empty 218 */ 219 if (length > 0 && fullpath[length - 1] != '/') 220 length += guac_strlcpy(fullpath + length, "/", 221 GUAC_COMMON_SSH_SFTP_MAX_PATH - length); 222 223 /* Append filename */ 224 length += guac_strlcpy(fullpath + length, filename, 225 GUAC_COMMON_SSH_SFTP_MAX_PATH - length); 226 227 /* Verify path length is within maximum */ 228 if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH) 229 return 0; 230 231 /* Append was successful */ 232 return 1; 233 234} 235 236/** 237 * Concatenates the given paths, separating the two with a single forward 238 * slash. The full result must be no more than GUAC_COMMON_SSH_SFTP_MAX_PATH 239 * bytes long, counting null terminator. 240 * 241 * @param fullpath 242 * The buffer to store the result within. This buffer must be at least 243 * GUAC_COMMON_SSH_SFTP_MAX_PATH bytes long. 244 * 245 * @param path_a 246 * The path to place at the beginning of the resulting path. 247 * 248 * @param path_b 249 * The path to append after path_a within the resulting path. 250 * 251 * @return 252 * Non-zero if the paths were successfully concatenated together, zero 253 * otherwise. 254 */ 255static int guac_ssh_append_path(char* fullpath, const char* path_a, 256 const char* path_b) { 257 258 int length; 259 260 /* Copy first half of path */ 261 length = guac_strlcpy(fullpath, path_a, GUAC_COMMON_SSH_SFTP_MAX_PATH); 262 if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH) 263 return 0; 264 265 /* Ensure path ends with trailing slash */ 266 if (length == 0 || fullpath[length - 1] != '/') 267 length += guac_strlcpy(fullpath + length, "/", 268 GUAC_COMMON_SSH_SFTP_MAX_PATH - length); 269 270 /* Skip past leading slashes in second path */ 271 while (*path_b == '/') 272 path_b++; 273 274 /* Append final half of path */ 275 length += guac_strlcpy(fullpath + length, path_b, 276 GUAC_COMMON_SSH_SFTP_MAX_PATH - length); 277 278 /* Verify path length is within maximum */ 279 if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH) 280 return 0; 281 282 /* Append was successful */ 283 return 1; 284 285} 286 287/** 288 * Handler for blob messages which continue an inbound SFTP data transfer 289 * (upload). The data associated with the given stream is expected to be a 290 * pointer to an open LIBSSH2_SFTP_HANDLE for the file to which the data 291 * should be written. 292 * 293 * @param user 294 * The user receiving the blob message. 295 * 296 * @param stream 297 * The Guacamole protocol stream associated with the received blob message. 298 * 299 * @param data 300 * The data received within the blob. 301 * 302 * @param length 303 * The length of the received data, in bytes. 304 * 305 * @return 306 * Zero if the blob is handled successfully, or non-zero on error. 307 */ 308static int guac_common_ssh_sftp_blob_handler(guac_user* user, 309 guac_stream* stream, void* data, int length) { 310 311 /* Pull file from stream */ 312 LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; 313 314 /* Attempt write */ 315 if (libssh2_sftp_write(file, data, length) == length) { 316 guac_user_log(user, GUAC_LOG_DEBUG, "%i bytes written", length); 317 guac_protocol_send_ack(user->socket, stream, "SFTP: OK", 318 GUAC_PROTOCOL_STATUS_SUCCESS); 319 guac_socket_flush(user->socket); 320 } 321 322 /* Inform of any errors */ 323 else { 324 guac_user_log(user, GUAC_LOG_INFO, "Unable to write to file"); 325 guac_protocol_send_ack(user->socket, stream, "SFTP: Write failed", 326 GUAC_PROTOCOL_STATUS_SERVER_ERROR); 327 guac_socket_flush(user->socket); 328 } 329 330 return 0; 331 332} 333 334/** 335 * Handler for end messages which terminate an inbound SFTP data transfer 336 * (upload). The data associated with the given stream is expected to be a 337 * pointer to an open LIBSSH2_SFTP_HANDLE for the file to which the data 338 * has been written and which should now be closed. 339 * 340 * @param user 341 * The user receiving the end message. 342 * 343 * @param stream 344 * The Guacamole protocol stream associated with the received end message. 345 * 346 * @return 347 * Zero if the file is closed successfully, or non-zero on error. 348 */ 349static int guac_common_ssh_sftp_end_handler(guac_user* user, 350 guac_stream* stream) { 351 352 /* Pull file from stream */ 353 LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; 354 355 /* Attempt to close file */ 356 if (libssh2_sftp_close(file) == 0) { 357 guac_user_log(user, GUAC_LOG_DEBUG, "File closed"); 358 guac_protocol_send_ack(user->socket, stream, "SFTP: OK", 359 GUAC_PROTOCOL_STATUS_SUCCESS); 360 guac_socket_flush(user->socket); 361 } 362 else { 363 guac_user_log(user, GUAC_LOG_INFO, "Unable to close file"); 364 guac_protocol_send_ack(user->socket, stream, "SFTP: Close failed", 365 GUAC_PROTOCOL_STATUS_SERVER_ERROR); 366 guac_socket_flush(user->socket); 367 } 368 369 return 0; 370 371} 372 373int guac_common_ssh_sftp_handle_file_stream( 374 guac_common_ssh_sftp_filesystem* filesystem, guac_user* user, 375 guac_stream* stream, char* mimetype, char* filename) { 376 377 char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH]; 378 LIBSSH2_SFTP_HANDLE* file; 379 380 /* Ignore upload if uploads have been disabled */ 381 if (filesystem->disable_upload) { 382 guac_user_log(user, GUAC_LOG_WARNING, "A upload attempt has " 383 "been blocked due to uploads being disabled, however it " 384 "should have been blocked at a higher level. This is likely " 385 "a bug."); 386 guac_protocol_send_ack(user->socket, stream, "SFTP: Upload disabled", 387 GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); 388 guac_socket_flush(user->socket); 389 return 0; 390 } 391 392 /* Concatenate filename with path */ 393 if (!guac_ssh_append_filename(fullpath, filesystem->upload_path, 394 filename)) { 395 396 guac_user_log(user, GUAC_LOG_DEBUG, 397 "Filename \"%s\" is invalid or resulting path is too long", 398 filename); 399 400 /* Abort transfer - invalid filename */ 401 guac_protocol_send_ack(user->socket, stream, 402 "SFTP: Illegal filename", 403 GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); 404 405 guac_socket_flush(user->socket); 406 return 0; 407 } 408 409 /* Open file via SFTP */ 410 file = libssh2_sftp_open(filesystem->sftp_session, fullpath, 411 LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC, 412 S_IRUSR | S_IWUSR); 413 414 /* Inform of status */ 415 if (file != NULL) { 416 417 guac_user_log(user, GUAC_LOG_DEBUG, 418 "File \"%s\" opened", 419 fullpath); 420 421 guac_protocol_send_ack(user->socket, stream, "SFTP: File opened", 422 GUAC_PROTOCOL_STATUS_SUCCESS); 423 guac_socket_flush(user->socket); 424 } 425 else { 426 guac_user_log(user, GUAC_LOG_INFO, 427 "Unable to open file \"%s\"", fullpath); 428 guac_protocol_send_ack(user->socket, stream, "SFTP: Open failed", 429 guac_sftp_get_status(filesystem)); 430 guac_socket_flush(user->socket); 431 } 432 433 /* Set handlers for file stream */ 434 stream->blob_handler = guac_common_ssh_sftp_blob_handler; 435 stream->end_handler = guac_common_ssh_sftp_end_handler; 436 437 /* Store file within stream */ 438 stream->data = file; 439 return 0; 440 441} 442 443/** 444 * Handler for ack messages which continue an outbound SFTP data transfer 445 * (download), signaling the current status and requesting additional data. 446 * The data associated with the given stream is expected to be a pointer to an 447 * open LIBSSH2_SFTP_HANDLE for the file from which the data is to be read. 448 * 449 * @param user 450 * The user receiving the ack message. 451 * 452 * @param stream 453 * The Guacamole protocol stream associated with the received ack message. 454 * 455 * @param message 456 * An arbitrary human-readable message describing the nature of the 457 * success or failure denoted by the ack message. 458 * 459 * @param status 460 * The status code associated with the ack message, which may indicate 461 * success or an error. 462 * 463 * @return 464 * Zero if the file is read from successfully, or non-zero on error. 465 */ 466static int guac_common_ssh_sftp_ack_handler(guac_user* user, 467 guac_stream* stream, char* message, guac_protocol_status status) { 468 469 /* Pull file from stream */ 470 LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; 471 472 /* If successful, read data */ 473 if (status == GUAC_PROTOCOL_STATUS_SUCCESS) { 474 475 /* Attempt read into buffer */ 476 char buffer[4096]; 477 int bytes_read = libssh2_sftp_read(file, buffer, sizeof(buffer)); 478 479 /* If bytes read, send as blob */ 480 if (bytes_read > 0) { 481 guac_protocol_send_blob(user->socket, stream, 482 buffer, bytes_read); 483 484 guac_user_log(user, GUAC_LOG_DEBUG, "%i bytes sent to user", 485 bytes_read); 486 487 } 488 489 /* If bytes could not be read, handle EOF or error condition */ 490 else { 491 492 /* If EOF, send end */ 493 if (bytes_read == 0) { 494 guac_user_log(user, GUAC_LOG_DEBUG, "File sent"); 495 guac_protocol_send_end(user->socket, stream); 496 guac_user_free_stream(user, stream); 497 } 498 499 /* Otherwise, fail stream */ 500 else { 501 guac_user_log(user, GUAC_LOG_INFO, "Error reading file"); 502 guac_protocol_send_end(user->socket, stream); 503 guac_user_free_stream(user, stream); 504 } 505 506 /* Close file */ 507 if (libssh2_sftp_close(file) == 0) 508 guac_user_log(user, GUAC_LOG_DEBUG, "File closed"); 509 else 510 guac_user_log(user, GUAC_LOG_INFO, "Unable to close file"); 511 512 } 513 514 guac_socket_flush(user->socket); 515 516 } 517 518 /* Otherwise, return stream to user */ 519 else 520 guac_user_free_stream(user, stream); 521 522 return 0; 523} 524 525guac_stream* guac_common_ssh_sftp_download_file( 526 guac_common_ssh_sftp_filesystem* filesystem, guac_user* user, 527 char* filename) { 528 529 guac_stream* stream; 530 LIBSSH2_SFTP_HANDLE* file; 531 532 /* Ignore download if downloads have been disabled */ 533 if (filesystem->disable_download) { 534 guac_user_log(user, GUAC_LOG_WARNING, "A download attempt has " 535 "been blocked due to downloads being disabled, however it " 536 "should have been blocked at a higher level. This is likely " 537 "a bug."); 538 return NULL; 539 } 540 541 /* Attempt to open file for reading */ 542 file = libssh2_sftp_open(filesystem->sftp_session, filename, 543 LIBSSH2_FXF_READ, 0); 544 if (file == NULL) { 545 guac_user_log(user, GUAC_LOG_INFO, 546 "Unable to read file \"%s\"", filename); 547 return NULL; 548 } 549 550 /* Allocate stream */ 551 stream = guac_user_alloc_stream(user); 552 stream->ack_handler = guac_common_ssh_sftp_ack_handler; 553 stream->data = file; 554 555 /* Send stream start, strip name */ 556 filename = basename(filename); 557 guac_protocol_send_file(user->socket, stream, 558 "application/octet-stream", filename); 559 guac_socket_flush(user->socket); 560 561 guac_user_log(user, GUAC_LOG_DEBUG, "Sending file \"%s\"", filename); 562 return stream; 563 564} 565 566void guac_common_ssh_sftp_set_upload_path( 567 guac_common_ssh_sftp_filesystem* filesystem, const char* path) { 568 569 guac_client* client = filesystem->ssh_session->client; 570 571 /* Ignore requests which exceed maximum-allowed path */ 572 int length = strnlen(path, GUAC_COMMON_SSH_SFTP_MAX_PATH)+1; 573 if (length > GUAC_COMMON_SSH_SFTP_MAX_PATH) { 574 guac_client_log(client, GUAC_LOG_ERROR, 575 "Submitted path exceeds limit of %i bytes", 576 GUAC_COMMON_SSH_SFTP_MAX_PATH); 577 return; 578 } 579 580 /* Copy path */ 581 memcpy(filesystem->upload_path, path, length); 582 guac_client_log(client, GUAC_LOG_DEBUG, "Upload path set to \"%s\"", path); 583 584} 585 586/** 587 * Handler for ack messages received due to receipt of a "body" or "blob" 588 * instruction associated with a SFTP directory list operation. 589 * 590 * @param user 591 * The user receiving the ack message. 592 * 593 * @param stream 594 * The Guacamole protocol stream associated with the received ack message. 595 * 596 * @param message 597 * An arbitrary human-readable message describing the nature of the 598 * success or failure denoted by this ack message. 599 * 600 * @param status 601 * The status code associated with this ack message, which may indicate 602 * success or an error. 603 * 604 * @return 605 * Zero on success, non-zero on error. 606 */ 607static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user, 608 guac_stream* stream, char* message, guac_protocol_status status) { 609 610 int bytes_read; 611 612 char filename[GUAC_COMMON_SSH_SFTP_MAX_PATH]; 613 LIBSSH2_SFTP_ATTRIBUTES attributes; 614 615 guac_common_ssh_sftp_ls_state* list_state = 616 (guac_common_ssh_sftp_ls_state*) stream->data; 617 618 guac_common_ssh_sftp_filesystem* filesystem = list_state->filesystem; 619 620 LIBSSH2_SFTP* sftp = filesystem->sftp_session; 621 622 /* If unsuccessful, free stream and abort */ 623 if (status != GUAC_PROTOCOL_STATUS_SUCCESS) { 624 libssh2_sftp_closedir(list_state->directory); 625 guac_user_free_stream(user, stream); 626 guac_mem_free(list_state); 627 return 0; 628 } 629 630 /* While directory entries remain */ 631 while ((bytes_read = libssh2_sftp_readdir(list_state->directory, 632 filename, sizeof(filename), &attributes)) > 0) { 633 634 char absolute_path[GUAC_COMMON_SSH_SFTP_MAX_PATH]; 635 636 /* Skip current and parent directory entries */ 637 if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) 638 continue; 639 640 /* Concatenate into absolute path - skip if invalid */ 641 if (!guac_ssh_append_filename(absolute_path, 642 list_state->directory_name, filename)) { 643 644 guac_user_log(user, GUAC_LOG_DEBUG, 645 "Skipping filename \"%s\" - filename is invalid or " 646 "resulting path is too long", filename); 647 648 continue; 649 } 650 651 /* Stat explicitly if symbolic link (might point to directory) */ 652 if (LIBSSH2_SFTP_S_ISLNK(attributes.permissions)) 653 libssh2_sftp_stat(sftp, absolute_path, &attributes); 654 655 /* Determine mimetype */ 656 const char* mimetype; 657 if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) 658 mimetype = GUAC_USER_STREAM_INDEX_MIMETYPE; 659 else 660 mimetype = "application/octet-stream"; 661 662 /* Write entry, waiting for next ack if a blob is written */ 663 if (guac_common_json_write_property(user, stream, 664 &list_state->json_state, absolute_path, mimetype)) 665 break; 666 667 } 668 669 /* Complete JSON and cleanup at end of directory */ 670 if (bytes_read <= 0) { 671 672 /* Complete JSON object */ 673 guac_common_json_end_object(user, stream, &list_state->json_state); 674 guac_common_json_flush(user, stream, &list_state->json_state); 675 676 /* Clean up resources */ 677 libssh2_sftp_closedir(list_state->directory); 678 guac_mem_free(list_state); 679 680 /* Signal of stream */ 681 guac_protocol_send_end(user->socket, stream); 682 guac_user_free_stream(user, stream); 683 684 } 685 686 guac_socket_flush(user->socket); 687 return 0; 688 689} 690 691/** 692 * Translates a stream name for the given SFTP filesystem object into the 693 * absolute path corresponding to the actual file it represents. 694 * 695 * @param fullpath 696 * The buffer to populate with the translated path. This buffer MUST be at 697 * least GUAC_COMMON_SSH_SFTP_MAX_PATH bytes in size. 698 * 699 * @param object 700 * The Guacamole protocol object associated with the SFTP filesystem. 701 * 702 * @param name 703 * The name of the stream (file) to translate into an absolute path. 704 * 705 * @return 706 * Non-zero if translation succeeded, zero otherwise. 707 */ 708static int guac_common_ssh_sftp_translate_name(char* fullpath, 709 guac_object* object, char* name) { 710 711 char normalized_name[GUAC_COMMON_SSH_SFTP_MAX_PATH]; 712 713 guac_common_ssh_sftp_filesystem* filesystem = 714 (guac_common_ssh_sftp_filesystem*) object->data; 715 716 /* Normalize stream name into a path, and append to the root path */ 717 return guac_common_ssh_sftp_normalize_path(normalized_name, name) 718 && guac_ssh_append_path(fullpath, filesystem->root_path, 719 normalized_name); 720 721} 722 723/** 724 * Handler for get messages. In context of SFTP and the filesystem exposed via 725 * the Guacamole protocol, get messages request the body of a file within the 726 * filesystem. 727 * 728 * @param user 729 * The user who sent the get message. 730 * 731 * @param object 732 * The Guacamole protocol object associated with the get request itself. 733 * 734 * @param name 735 * The name of the input stream (file) being requested. 736 * 737 * @return 738 * Zero on success, non-zero on error. 739 */ 740static int guac_common_ssh_sftp_get_handler(guac_user* user, 741 guac_object* object, char* name) { 742 743 char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH]; 744 745 guac_common_ssh_sftp_filesystem* filesystem = 746 (guac_common_ssh_sftp_filesystem*) object->data; 747 748 LIBSSH2_SFTP* sftp = filesystem->sftp_session; 749 LIBSSH2_SFTP_ATTRIBUTES attributes; 750 751 /* Translate stream name into filesystem path */ 752 if (!guac_common_ssh_sftp_translate_name(fullpath, object, name)) { 753 guac_user_log(user, GUAC_LOG_INFO, "Unable to generate real path " 754 "for stream \"%s\"", name); 755 return 0; 756 } 757 758 /* Attempt to read file information */ 759 if (libssh2_sftp_stat(sftp, fullpath, &attributes)) { 760 guac_user_log(user, GUAC_LOG_INFO, "Unable to read file \"%s\"", 761 fullpath); 762 return 0; 763 } 764 765 /* If directory, send contents of directory */ 766 if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) { 767 768 /* Open as directory */ 769 LIBSSH2_SFTP_HANDLE* dir = libssh2_sftp_opendir(sftp, fullpath); 770 if (dir == NULL) { 771 guac_user_log(user, GUAC_LOG_INFO, 772 "Unable to read directory \"%s\"", fullpath); 773 return 0; 774 } 775 776 /* Init directory listing state */ 777 guac_common_ssh_sftp_ls_state* list_state = 778 guac_mem_alloc(sizeof(guac_common_ssh_sftp_ls_state)); 779 780 list_state->directory = dir; 781 list_state->filesystem = filesystem; 782 783 int length = guac_strlcpy(list_state->directory_name, name, 784 sizeof(list_state->directory_name)); 785 786 /* Bail out if directory name is too long to store */ 787 if (length >= sizeof(list_state->directory_name)) { 788 guac_user_log(user, GUAC_LOG_INFO, "Unable to read directory " 789 "\"%s\": Path too long", fullpath); 790 guac_mem_free(list_state); 791 return 0; 792 } 793 794 /* Allocate stream for body */ 795 guac_stream* stream = guac_user_alloc_stream(user); 796 stream->ack_handler = guac_common_ssh_sftp_ls_ack_handler; 797 stream->data = list_state; 798 799 /* Init JSON object state */ 800 guac_common_json_begin_object(user, stream, &list_state->json_state); 801 802 /* Associate new stream with get request */ 803 guac_protocol_send_body(user->socket, object, stream, 804 GUAC_USER_STREAM_INDEX_MIMETYPE, name); 805 806 } 807 808 /* Otherwise, send file contents */ 809 else { 810 811 /* If downloads are disabled, log and return. */ 812 if (filesystem->disable_download) { 813 guac_user_log(user, GUAC_LOG_INFO, 814 "Unable to download file \"%s\", " 815 "file downloads have been disabled.", fullpath); 816 return 0; 817 } 818 819 /* Open as normal file */ 820 LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, fullpath, 821 LIBSSH2_FXF_READ, 0); 822 if (file == NULL) { 823 guac_user_log(user, GUAC_LOG_INFO, 824 "Unable to read file \"%s\"", fullpath); 825 return 0; 826 } 827 828 /* Allocate stream for body */ 829 guac_stream* stream = guac_user_alloc_stream(user); 830 stream->ack_handler = guac_common_ssh_sftp_ack_handler; 831 stream->data = file; 832 833 /* Associate new stream with get request */ 834 guac_protocol_send_body(user->socket, object, stream, 835 "application/octet-stream", name); 836 837 } 838 839 guac_socket_flush(user->socket); 840 return 0; 841} 842 843/** 844 * Handler for put messages. In context of SFTP and the filesystem exposed via 845 * the Guacamole protocol, put messages request write access to a file within 846 * the filesystem. 847 * 848 * @param user 849 * The user who sent the put message. 850 * 851 * @param object 852 * The Guacamole protocol object associated with the put request itself. 853 * 854 * @param stream 855 * The Guacamole protocol stream along which the user will be sending 856 * file data. 857 * 858 * @param mimetype 859 * The mimetype of the data being send along the stream. 860 * 861 * @param name 862 * The name of the input stream (file) being requested. 863 * 864 * @return 865 * Zero on success, non-zero on error. 866 */ 867static int guac_common_ssh_sftp_put_handler(guac_user* user, 868 guac_object* object, guac_stream* stream, char* mimetype, char* name) { 869 870 char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH]; 871 872 guac_common_ssh_sftp_filesystem* filesystem = 873 (guac_common_ssh_sftp_filesystem*) object->data; 874 875 /* Ignore upload if uploads have been disabled */ 876 if (filesystem->disable_upload) { 877 guac_user_log(user, GUAC_LOG_WARNING, "A upload attempt has " 878 "been blocked due to uploads being disabled, however it " 879 "should have been blocked at a higher level. This is likely " 880 "a bug."); 881 guac_protocol_send_ack(user->socket, stream, "SFTP: Upload disabled", 882 GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); 883 guac_socket_flush(user->socket); 884 return 0; 885 } 886 887 LIBSSH2_SFTP* sftp = filesystem->sftp_session; 888 889 /* Translate stream name into filesystem path */ 890 if (!guac_common_ssh_sftp_translate_name(fullpath, object, name)) { 891 guac_user_log(user, GUAC_LOG_INFO, "Unable to generate real path " 892 "for stream \"%s\"", name); 893 return 0; 894 } 895 896 /* Open file via SFTP */ 897 LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, fullpath, 898 LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC, 899 S_IRUSR | S_IWUSR); 900 901 /* Acknowledge stream if successful */ 902 if (file != NULL) { 903 guac_user_log(user, GUAC_LOG_DEBUG, "File \"%s\" opened", fullpath); 904 guac_protocol_send_ack(user->socket, stream, "SFTP: File opened", 905 GUAC_PROTOCOL_STATUS_SUCCESS); 906 } 907 908 /* Abort on failure */ 909 else { 910 guac_user_log(user, GUAC_LOG_INFO, 911 "Unable to open file \"%s\"", fullpath); 912 guac_protocol_send_ack(user->socket, stream, "SFTP: Open failed", 913 guac_sftp_get_status(filesystem)); 914 } 915 916 /* Set handlers for file stream */ 917 stream->blob_handler = guac_common_ssh_sftp_blob_handler; 918 stream->end_handler = guac_common_ssh_sftp_end_handler; 919 920 /* Store file within stream */ 921 stream->data = file; 922 923 guac_socket_flush(user->socket); 924 return 0; 925} 926 927void* guac_common_ssh_expose_sftp_filesystem(guac_user* user, void* data) { 928 929 guac_common_ssh_sftp_filesystem* filesystem = 930 (guac_common_ssh_sftp_filesystem*) data; 931 932 /* No need to expose if there is no filesystem or the user has left */ 933 if (user == NULL || filesystem == NULL) 934 return NULL; 935 936 /* Allocate and expose filesystem object for user */ 937 return guac_common_ssh_alloc_sftp_filesystem_object(filesystem, user); 938 939} 940 941guac_object* guac_common_ssh_alloc_sftp_filesystem_object( 942 guac_common_ssh_sftp_filesystem* filesystem, guac_user* user) { 943 944 /* Init filesystem */ 945 guac_object* fs_object = guac_user_alloc_object(user); 946 fs_object->get_handler = guac_common_ssh_sftp_get_handler; 947 948 /* Only handle uploads if not disabled. */ 949 if (!filesystem->disable_upload) 950 fs_object->put_handler = guac_common_ssh_sftp_put_handler; 951 952 fs_object->data = filesystem; 953 954 /* Send filesystem to user */ 955 guac_protocol_send_filesystem(user->socket, fs_object, filesystem->name); 956 guac_socket_flush(user->socket); 957 958 return fs_object; 959 960} 961 962guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem( 963 guac_common_ssh_session* session, const char* root_path, 964 const char* name, int disable_download, int disable_upload) { 965 966 /* Request SFTP */ 967 LIBSSH2_SFTP* sftp_session = libssh2_sftp_init(session->session); 968 if (sftp_session == NULL) 969 return NULL; 970 971 /* Allocate data for SFTP session */ 972 guac_common_ssh_sftp_filesystem* filesystem = 973 guac_mem_alloc(sizeof(guac_common_ssh_sftp_filesystem)); 974 975 /* Associate SSH session with SFTP data and user */ 976 filesystem->ssh_session = session; 977 filesystem->sftp_session = sftp_session; 978 979 /* Copy over disable flags */ 980 filesystem->disable_download = disable_download; 981 filesystem->disable_upload = disable_upload; 982 983 /* Normalize and store the provided root path */ 984 if (!guac_common_ssh_sftp_normalize_path(filesystem->root_path, 985 root_path)) { 986 guac_client_log(session->client, GUAC_LOG_WARNING, "Cannot create " 987 "SFTP filesystem - \"%s\" is not a valid path.", root_path); 988 guac_mem_free(filesystem); 989 return NULL; 990 } 991 992 /* Generate filesystem name from root path if no name is provided */ 993 if (name != NULL) 994 filesystem->name = guac_strdup(name); 995 else 996 filesystem->name = guac_strdup(filesystem->root_path); 997 998 /* Initially upload files to current directory */ 999 strcpy(filesystem->upload_path, "."); 1000 1001 /* Return allocated filesystem */ 1002 return filesystem; 1003 1004} 1005 1006void guac_common_ssh_destroy_sftp_filesystem( 1007 guac_common_ssh_sftp_filesystem* filesystem) { 1008 1009 /* Shutdown SFTP session */ 1010 libssh2_sftp_shutdown(filesystem->sftp_session); 1011 1012 /* Free associated memory */ 1013 guac_mem_free(filesystem->name); 1014 guac_mem_free(filesystem); 1015 1016} 1017