rdpdr-fs-messages.c (21821B)
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 "channels/common-svc.h" 21#include "channels/rdpdr/rdpdr-fs-messages-dir-info.h" 22#include "channels/rdpdr/rdpdr-fs-messages-file-info.h" 23#include "channels/rdpdr/rdpdr-fs-messages-vol-info.h" 24#include "channels/rdpdr/rdpdr-fs-messages.h" 25#include "channels/rdpdr/rdpdr.h" 26#include "download.h" 27#include "fs.h" 28#include "unicode.h" 29 30#include <freerdp/channels/rdpdr.h> 31#include <guacamole/client.h> 32#include <guacamole/mem.h> 33#include <winpr/nt.h> 34#include <winpr/stream.h> 35#include <winpr/wtypes.h> 36 37#include <stdint.h> 38#include <stdlib.h> 39#include <string.h> 40 41void guac_rdpdr_fs_process_create(guac_rdp_common_svc* svc, 42 guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, 43 wStream* input_stream) { 44 45 wStream* output_stream; 46 int file_id; 47 48 int desired_access, file_attributes; 49 int create_disposition, create_options, path_length; 50 char path[GUAC_RDP_FS_MAX_PATH]; 51 52 /* Check remaining stream data prior to reading. */ 53 if (Stream_GetRemainingLength(input_stream) < 32) { 54 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Create Drive " 55 "Request PDU does not contain the expected number of bytes. " 56 "Drive redirection may not work as expected."); 57 return; 58 } 59 60 /* Read "create" information */ 61 Stream_Read_UINT32(input_stream, desired_access); 62 Stream_Seek_UINT64(input_stream); /* allocation size */ 63 Stream_Read_UINT32(input_stream, file_attributes); 64 Stream_Seek_UINT32(input_stream); /* shared access */ 65 Stream_Read_UINT32(input_stream, create_disposition); 66 Stream_Read_UINT32(input_stream, create_options); 67 Stream_Read_UINT32(input_stream, path_length); 68 69 /* Check to make sure the stream contains path_length bytes. */ 70 if(Stream_GetRemainingLength(input_stream) < path_length) { 71 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Create Drive " 72 "Request PDU does not contain the expected number of bytes. " 73 "Drive redirection may not work as expected."); 74 return; 75 } 76 77 /* Convert path to UTF-8 */ 78 guac_rdp_utf16_to_utf8(Stream_Pointer(input_stream), path_length/2 - 1, 79 path, sizeof(path)); 80 81 /* Open file */ 82 file_id = guac_rdp_fs_open((guac_rdp_fs*) device->data, path, 83 desired_access, file_attributes, 84 create_disposition, create_options); 85 86 guac_client_log(svc->client, GUAC_LOG_DEBUG, 87 "%s: [file_id=%i] " 88 "desired_access=0x%x, file_attributes=0x%x, " 89 "create_disposition=0x%x, create_options=0x%x, path=\"%s\"", 90 __func__, file_id, 91 desired_access, file_attributes, 92 create_disposition, create_options, path); 93 94 /* If an error occurred, notify server */ 95 if (file_id < 0) { 96 guac_client_log(svc->client, GUAC_LOG_ERROR, 97 "File open refused (%i): \"%s\"", file_id, path); 98 99 output_stream = guac_rdpdr_new_io_completion(device, 100 iorequest->completion_id, guac_rdp_fs_get_status(file_id), 5); 101 Stream_Write_UINT32(output_stream, 0); /* fileId */ 102 Stream_Write_UINT8(output_stream, 0); /* information */ 103 } 104 105 /* Otherwise, open succeeded */ 106 else { 107 108 guac_rdp_fs_file* file; 109 110 output_stream = guac_rdpdr_new_io_completion(device, 111 iorequest->completion_id, STATUS_SUCCESS, 5); 112 Stream_Write_UINT32(output_stream, file_id); /* fileId */ 113 Stream_Write_UINT8(output_stream, 0); /* information */ 114 115 /* Create \Download if it doesn't exist */ 116 file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, file_id); 117 if (file != NULL && strcmp(file->absolute_path, "\\") == 0) { 118 119 /* Only create Download folder if downloads are enabled. */ 120 if (!((guac_rdp_fs*) device->data)->disable_download) { 121 int download_id = 122 guac_rdp_fs_open((guac_rdp_fs*) device->data, "\\Download", 123 GENERIC_READ, 0, FILE_OPEN_IF, FILE_DIRECTORY_FILE); 124 125 if (download_id >= 0) 126 guac_rdp_fs_close((guac_rdp_fs*) device->data, download_id); 127 } 128 129 } 130 131 } 132 133 guac_rdp_common_svc_write(svc, output_stream); 134 135} 136 137void guac_rdpdr_fs_process_read(guac_rdp_common_svc* svc, 138 guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, 139 wStream* input_stream) { 140 141 UINT32 length; 142 UINT64 offset; 143 char* buffer; 144 int bytes_read; 145 146 wStream* output_stream; 147 148 /* Check remaining bytes before reading stream. */ 149 if (Stream_GetRemainingLength(input_stream) < 12) { 150 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Drive Read " 151 "Request PDU does not contain the expected number of bytes. " 152 "Drive redirection may not work as expected."); 153 return; 154 } 155 156 /* Read packet */ 157 Stream_Read_UINT32(input_stream, length); 158 Stream_Read_UINT64(input_stream, offset); 159 160 guac_client_log(svc->client, GUAC_LOG_DEBUG, 161 "%s: [file_id=%i] length=%i, offset=%" PRIu64, 162 __func__, iorequest->file_id, length, (uint64_t) offset); 163 164 /* Ensure buffer size does not exceed a safe maximum */ 165 if (length > GUAC_RDP_MAX_READ_BUFFER) 166 length = GUAC_RDP_MAX_READ_BUFFER; 167 168 /* Allocate buffer */ 169 buffer = guac_mem_alloc(length); 170 171 /* Attempt read */ 172 bytes_read = guac_rdp_fs_read((guac_rdp_fs*) device->data, 173 iorequest->file_id, offset, buffer, length); 174 175 /* If error, return invalid parameter */ 176 if (bytes_read < 0) { 177 output_stream = guac_rdpdr_new_io_completion(device, 178 iorequest->completion_id, guac_rdp_fs_get_status(bytes_read), 4); 179 Stream_Write_UINT32(output_stream, 0); /* Length */ 180 } 181 182 /* Otherwise, send bytes read */ 183 else { 184 output_stream = guac_rdpdr_new_io_completion(device, 185 iorequest->completion_id, STATUS_SUCCESS, 4+bytes_read); 186 Stream_Write_UINT32(output_stream, bytes_read); /* Length */ 187 Stream_Write(output_stream, buffer, bytes_read); /* ReadData */ 188 } 189 190 guac_rdp_common_svc_write(svc, output_stream); 191 guac_mem_free(buffer); 192 193} 194 195void guac_rdpdr_fs_process_write(guac_rdp_common_svc* svc, 196 guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, 197 wStream* input_stream) { 198 199 UINT32 length; 200 UINT64 offset; 201 int bytes_written; 202 203 wStream* output_stream; 204 205 /* Check remaining length. */ 206 if (Stream_GetRemainingLength(input_stream) < 32) { 207 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Drive Write " 208 "Request PDU does not contain the expected number of bytes. " 209 "Drive redirection may not work as expected."); 210 return; 211 } 212 213 /* Read packet */ 214 Stream_Read_UINT32(input_stream, length); 215 Stream_Read_UINT64(input_stream, offset); 216 Stream_Seek(input_stream, 20); /* Padding */ 217 218 guac_client_log(svc->client, GUAC_LOG_DEBUG, 219 "%s: [file_id=%i] length=%i, offset=%" PRIu64, 220 __func__, iorequest->file_id, length, (uint64_t) offset); 221 222 /* Check to make sure stream contains at least length bytes */ 223 if (Stream_GetRemainingLength(input_stream) < length) { 224 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Drive Write " 225 "Request PDU does not contain the expected number of bytes. " 226 "Drive redirection may not work as expected."); 227 return; 228 } 229 230 /* Attempt write */ 231 bytes_written = guac_rdp_fs_write((guac_rdp_fs*) device->data, 232 iorequest->file_id, offset, Stream_Pointer(input_stream), length); 233 234 /* If error, return invalid parameter */ 235 if (bytes_written < 0) { 236 output_stream = guac_rdpdr_new_io_completion(device, 237 iorequest->completion_id, guac_rdp_fs_get_status(bytes_written), 5); 238 Stream_Write_UINT32(output_stream, 0); /* Length */ 239 Stream_Write_UINT8(output_stream, 0); /* Padding */ 240 } 241 242 /* Otherwise, send success */ 243 else { 244 output_stream = guac_rdpdr_new_io_completion(device, 245 iorequest->completion_id, STATUS_SUCCESS, 5); 246 Stream_Write_UINT32(output_stream, bytes_written); /* Length */ 247 Stream_Write_UINT8(output_stream, 0); /* Padding */ 248 } 249 250 guac_rdp_common_svc_write(svc, output_stream); 251 252} 253 254void guac_rdpdr_fs_process_close(guac_rdp_common_svc* svc, 255 guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, 256 wStream* input_stream) { 257 258 wStream* output_stream; 259 guac_rdp_fs_file* file; 260 261 guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i]", 262 __func__, iorequest->file_id); 263 264 /* Get file */ 265 file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, iorequest->file_id); 266 if (file == NULL) 267 return; 268 269 /* If file was written to, and it's in the \Download folder, start stream */ 270 if (file->bytes_written > 0 271 && strncmp(file->absolute_path, "\\Download\\", 10) == 0 272 && !((guac_rdp_fs*) device->data)->disable_download) { 273 guac_client_for_owner(svc->client, guac_rdp_download_to_user, file->absolute_path); 274 guac_rdp_fs_delete((guac_rdp_fs*) device->data, iorequest->file_id); 275 } 276 277 /* Close file */ 278 guac_rdp_fs_close((guac_rdp_fs*) device->data, iorequest->file_id); 279 280 output_stream = guac_rdpdr_new_io_completion(device, 281 iorequest->completion_id, STATUS_SUCCESS, 4); 282 Stream_Write(output_stream, "\0\0\0\0", 4); /* Padding */ 283 284 guac_rdp_common_svc_write(svc, output_stream); 285 286} 287 288void guac_rdpdr_fs_process_volume_info(guac_rdp_common_svc* svc, 289 guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, 290 wStream* input_stream) { 291 292 int fs_information_class; 293 294 /* Check remaining length */ 295 if (Stream_GetRemainingLength(input_stream) < 4) { 296 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Drive Query " 297 "Volume Information PDU does not contain the expected number " 298 "of bytes. Drive redirection may not work as expected."); 299 return; 300 } 301 302 Stream_Read_UINT32(input_stream, fs_information_class); 303 304 /* Dispatch to appropriate class-specific handler */ 305 switch (fs_information_class) { 306 307 case FileFsVolumeInformation: 308 guac_rdpdr_fs_process_query_volume_info(svc, device, iorequest, input_stream); 309 break; 310 311 case FileFsSizeInformation: 312 guac_rdpdr_fs_process_query_size_info(svc, device, iorequest, input_stream); 313 break; 314 315 case FileFsDeviceInformation: 316 guac_rdpdr_fs_process_query_device_info(svc, device, iorequest, input_stream); 317 break; 318 319 case FileFsAttributeInformation: 320 guac_rdpdr_fs_process_query_attribute_info(svc, device, iorequest, input_stream); 321 break; 322 323 case FileFsFullSizeInformation: 324 guac_rdpdr_fs_process_query_full_size_info(svc, device, iorequest, input_stream); 325 break; 326 327 default: 328 guac_client_log(svc->client, GUAC_LOG_DEBUG, 329 "Unknown volume information class: 0x%x", fs_information_class); 330 } 331 332} 333 334void guac_rdpdr_fs_process_file_info(guac_rdp_common_svc* svc, 335 guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, 336 wStream* input_stream) { 337 338 int fs_information_class; 339 340 /* Check remaining length */ 341 if (Stream_GetRemainingLength(input_stream) < 4) { 342 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Drive Query " 343 "Information PDU does not contain the expected number of " 344 "bytes. Drive redirection may not work as expected."); 345 return; 346 } 347 348 Stream_Read_UINT32(input_stream, fs_information_class); 349 350 /* Dispatch to appropriate class-specific handler */ 351 switch (fs_information_class) { 352 353 case FileBasicInformation: 354 guac_rdpdr_fs_process_query_basic_info(svc, device, iorequest, input_stream); 355 break; 356 357 case FileStandardInformation: 358 guac_rdpdr_fs_process_query_standard_info(svc, device, iorequest, input_stream); 359 break; 360 361 case FileAttributeTagInformation: 362 guac_rdpdr_fs_process_query_attribute_tag_info(svc, device, iorequest, input_stream); 363 break; 364 365 default: 366 guac_client_log(svc->client, GUAC_LOG_DEBUG, 367 "Unknown file information class: 0x%x", fs_information_class); 368 } 369 370} 371 372void guac_rdpdr_fs_process_set_volume_info(guac_rdp_common_svc* svc, 373 guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, 374 wStream* input_stream) { 375 376 wStream* output_stream = guac_rdpdr_new_io_completion(device, 377 iorequest->completion_id, STATUS_NOT_SUPPORTED, 0); 378 379 guac_client_log(svc->client, GUAC_LOG_DEBUG, 380 "%s: [file_id=%i] Set volume info not supported", 381 __func__, iorequest->file_id); 382 383 guac_rdp_common_svc_write(svc, output_stream); 384 385} 386 387void guac_rdpdr_fs_process_set_file_info(guac_rdp_common_svc* svc, 388 guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, 389 wStream* input_stream) { 390 391 int fs_information_class; 392 int length; 393 394 /* Check remaining length */ 395 if (Stream_GetRemainingLength(input_stream) < 32) { 396 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Drive Set " 397 "Information PDU does not contain the expected number of " 398 "bytes. Drive redirection may not work as expected."); 399 return; 400 } 401 402 Stream_Read_UINT32(input_stream, fs_information_class); 403 Stream_Read_UINT32(input_stream, length); /* Length */ 404 Stream_Seek(input_stream, 24); /* Padding */ 405 406 /* Dispatch to appropriate class-specific handler */ 407 switch (fs_information_class) { 408 409 case FileBasicInformation: 410 guac_rdpdr_fs_process_set_basic_info(svc, device, iorequest, length, input_stream); 411 break; 412 413 case FileEndOfFileInformation: 414 guac_rdpdr_fs_process_set_end_of_file_info(svc, device, iorequest, length, input_stream); 415 break; 416 417 case FileDispositionInformation: 418 guac_rdpdr_fs_process_set_disposition_info(svc, device, iorequest, length, input_stream); 419 break; 420 421 case FileRenameInformation: 422 guac_rdpdr_fs_process_set_rename_info(svc, device, iorequest, length, input_stream); 423 break; 424 425 case FileAllocationInformation: 426 guac_rdpdr_fs_process_set_allocation_info(svc, device, iorequest, length, input_stream); 427 break; 428 429 default: 430 guac_client_log(svc->client, GUAC_LOG_DEBUG, 431 "Unknown file information class: 0x%x", 432 fs_information_class); 433 } 434 435} 436 437void guac_rdpdr_fs_process_device_control(guac_rdp_common_svc* svc, 438 guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, 439 wStream* input_stream) { 440 441 wStream* output_stream = guac_rdpdr_new_io_completion(device, 442 iorequest->completion_id, STATUS_INVALID_PARAMETER, 4); 443 444 guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] IGNORED", 445 __func__, iorequest->file_id); 446 447 /* No content for now */ 448 Stream_Write_UINT32(output_stream, 0); 449 450 guac_rdp_common_svc_write(svc, output_stream); 451 452} 453 454void guac_rdpdr_fs_process_notify_change_directory(guac_rdp_common_svc* svc, 455 guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, 456 wStream* input_stream) { 457 458 guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] Not " 459 "implemented", __func__, iorequest->file_id); 460 461} 462 463void guac_rdpdr_fs_process_query_directory(guac_rdp_common_svc* svc, 464 guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, 465 wStream* input_stream) { 466 467 wStream* output_stream; 468 469 guac_rdp_fs_file* file; 470 int fs_information_class, initial_query; 471 int path_length; 472 473 const char* entry_name; 474 475 /* Get file */ 476 file = guac_rdp_fs_get_file((guac_rdp_fs*) device->data, iorequest->file_id); 477 if (file == NULL) 478 return; 479 480 if (Stream_GetRemainingLength(input_stream) < 9) { 481 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Drive Query " 482 "Directory PDU does not contain the expected number of bytes. " 483 "Drive redirection may not work as expected."); 484 return; 485 } 486 487 /* Read main header */ 488 Stream_Read_UINT32(input_stream, fs_information_class); 489 Stream_Read_UINT8(input_stream, initial_query); 490 Stream_Read_UINT32(input_stream, path_length); 491 492 /* If this is the first query, the path is included after padding */ 493 if (initial_query) { 494 495 /* 496 * Check to make sure Stream has at least the 23 padding bytes in it 497 * prior to seeking. 498 */ 499 if (Stream_GetRemainingLength(input_stream) < (23 + path_length)) { 500 guac_client_log(svc->client, GUAC_LOG_WARNING, "Server Drive Query " 501 "Directory PDU does not contain the expected number of " 502 "bytes. Drive redirection may not work as expected."); 503 return; 504 } 505 506 Stream_Seek(input_stream, 23); /* Padding */ 507 508 /* Convert path to UTF-8 */ 509 guac_rdp_utf16_to_utf8(Stream_Pointer(input_stream), path_length/2 - 1, 510 file->dir_pattern, sizeof(file->dir_pattern)); 511 512 } 513 514 guac_client_log(svc->client, GUAC_LOG_DEBUG, "%s: [file_id=%i] " 515 "initial_query=%i, dir_pattern=\"%s\"", __func__, 516 iorequest->file_id, initial_query, file->dir_pattern); 517 518 /* Find first matching entry in directory */ 519 while ((entry_name = guac_rdp_fs_read_dir((guac_rdp_fs*) device->data, 520 iorequest->file_id)) != NULL) { 521 522 /* Convert to absolute path */ 523 char entry_path[GUAC_RDP_FS_MAX_PATH]; 524 if (guac_rdp_fs_convert_path(file->absolute_path, 525 entry_name, entry_path) == 0) { 526 527 int entry_file_id; 528 529 /* Pattern defined and match fails, continue with next file */ 530 if (guac_rdp_fs_matches(entry_path, file->dir_pattern)) 531 continue; 532 533 /* Open directory entry */ 534 entry_file_id = guac_rdp_fs_open((guac_rdp_fs*) device->data, 535 entry_path, FILE_READ_DATA, 0, FILE_OPEN, 0); 536 537 if (entry_file_id >= 0) { 538 539 /* Dispatch to appropriate class-specific handler */ 540 switch (fs_information_class) { 541 542 case FileDirectoryInformation: 543 guac_rdpdr_fs_process_query_directory_info(svc, device, 544 iorequest, entry_name, entry_file_id); 545 break; 546 547 case FileFullDirectoryInformation: 548 guac_rdpdr_fs_process_query_full_directory_info(svc, 549 device, iorequest, entry_name, entry_file_id); 550 break; 551 552 case FileBothDirectoryInformation: 553 guac_rdpdr_fs_process_query_both_directory_info(svc, 554 device, iorequest, entry_name, entry_file_id); 555 break; 556 557 case FileNamesInformation: 558 guac_rdpdr_fs_process_query_names_info(svc, device, 559 iorequest, entry_name, entry_file_id); 560 break; 561 562 default: 563 guac_client_log(svc->client, GUAC_LOG_DEBUG, 564 "Unknown dir information class: 0x%x", 565 fs_information_class); 566 } 567 568 guac_rdp_fs_close((guac_rdp_fs*) device->data, entry_file_id); 569 return; 570 571 } /* end if file exists */ 572 } /* end if path valid */ 573 } /* end if entry exists */ 574 575 /* 576 * Handle errors as a lack of files. 577 */ 578 579 output_stream = guac_rdpdr_new_io_completion(device, 580 iorequest->completion_id, STATUS_NO_MORE_FILES, 5); 581 582 Stream_Write_UINT32(output_stream, 0); /* Length */ 583 Stream_Write_UINT8(output_stream, 0); /* Padding */ 584 585 guac_rdp_common_svc_write(svc, output_stream); 586 587} 588 589void guac_rdpdr_fs_process_lock_control(guac_rdp_common_svc* svc, 590 guac_rdpdr_device* device, guac_rdpdr_iorequest* iorequest, 591 wStream* input_stream) { 592 593 wStream* output_stream = guac_rdpdr_new_io_completion(device, 594 iorequest->completion_id, STATUS_NOT_SUPPORTED, 5); 595 596 guac_client_log(svc->client, GUAC_LOG_DEBUG, 597 "%s: [file_id=%i] Lock not supported", 598 __func__, iorequest->file_id); 599 600 Stream_Zero(output_stream, 5); /* Padding */ 601 602 guac_rdp_common_svc_write(svc, output_stream); 603 604} 605