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