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 "fs.h"
21 #include "download.h"
22 #include "upload.h"
23 
24 #include <guacamole/client.h>
25 #include <guacamole/object.h>
26 #include <guacamole/pool.h>
27 #include <guacamole/protocol.h>
28 #include <guacamole/socket.h>
29 #include <guacamole/string.h>
30 #include <guacamole/user.h>
31 #include <winpr/file.h>
32 #include <winpr/nt.h>
33 
34 #include <dirent.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <fnmatch.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sys/stat.h>
42 #include <sys/statvfs.h>
43 #include <unistd.h>
44 
guac_rdp_fs_alloc(guac_client * client,const char * drive_path,int create_drive_path,int disable_download,int disable_upload)45 guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path,
46         int create_drive_path, int disable_download, int disable_upload) {
47 
48     /* Create drive path if it does not exist */
49     if (create_drive_path) {
50         guac_client_log(client, GUAC_LOG_DEBUG,
51                "%s: Creating directory \"%s\" if necessary.",
52                __func__, drive_path);
53 
54         /* Log error if directory creation fails */
55         if (mkdir(drive_path, S_IRWXU) && errno != EEXIST) {
56             guac_client_log(client, GUAC_LOG_ERROR,
57                     "Unable to create directory \"%s\": %s",
58                     drive_path, strerror(errno));
59         }
60     }
61 
62     guac_rdp_fs* fs = malloc(sizeof(guac_rdp_fs));
63 
64     fs->client = client;
65     fs->drive_path = strdup(drive_path);
66     fs->file_id_pool = guac_pool_alloc(0);
67     fs->open_files = 0;
68     fs->disable_download = disable_download;
69     fs->disable_upload = disable_upload;
70 
71     return fs;
72 
73 }
74 
guac_rdp_fs_free(guac_rdp_fs * fs)75 void guac_rdp_fs_free(guac_rdp_fs* fs) {
76     guac_pool_free(fs->file_id_pool);
77     free(fs->drive_path);
78     free(fs);
79 }
80 
guac_rdp_fs_alloc_object(guac_rdp_fs * fs,guac_user * user)81 guac_object* guac_rdp_fs_alloc_object(guac_rdp_fs* fs, guac_user* user) {
82 
83     /* Init filesystem */
84     guac_object* fs_object = guac_user_alloc_object(user);
85     fs_object->get_handler = guac_rdp_download_get_handler;
86 
87     /* Assign handler only if uploads are not disabled. */
88     if (!fs->disable_upload)
89         fs_object->put_handler = guac_rdp_upload_put_handler;
90 
91     fs_object->data = fs;
92 
93     /* Send filesystem to user */
94     guac_protocol_send_filesystem(user->socket, fs_object, "Shared Drive");
95     guac_socket_flush(user->socket);
96 
97     return fs_object;
98 
99 }
100 
guac_rdp_fs_expose(guac_user * user,void * data)101 void* guac_rdp_fs_expose(guac_user* user, void* data) {
102 
103     guac_rdp_fs* fs = (guac_rdp_fs*) data;
104 
105     /* No need to expose if there is no filesystem or the user has left */
106     if (user == NULL || fs == NULL)
107         return NULL;
108 
109     /* Allocate and expose filesystem object for user */
110     return guac_rdp_fs_alloc_object(fs, user);
111 
112 }
113 
114 /**
115  * Translates an absolute Windows path to an absolute path which is within the
116  * "drive path" specified in the connection settings. No checking is performed
117  * on the path provided, which is assumed to have already been normalized and
118  * validated as absolute.
119  *
120  * @param fs
121  *     The filesystem containing the file whose path is being translated.
122  *
123  * @param virtual_path
124  *     The absolute path to the file on the simulated filesystem, relative to
125  *     the simulated filesystem root.
126  *
127  * @param real_path
128  *     The buffer in which to store the absolute path to the real file on the
129  *     local filesystem.
130  */
__guac_rdp_fs_translate_path(guac_rdp_fs * fs,const char * virtual_path,char * real_path)131 static void __guac_rdp_fs_translate_path(guac_rdp_fs* fs,
132         const char* virtual_path, char* real_path) {
133 
134     /* Get drive path */
135     char* drive_path = fs->drive_path;
136 
137     int i;
138 
139     /* Start with path from settings */
140     for (i=0; i<GUAC_RDP_FS_MAX_PATH-1; i++) {
141 
142         /* Break on end-of-string */
143         char c = *(drive_path++);
144         if (c == 0)
145             break;
146 
147         /* Copy character */
148         *(real_path++) = c;
149 
150     }
151 
152     /* Translate path */
153     for (; i<GUAC_RDP_FS_MAX_PATH-1; i++) {
154 
155         /* Stop at end of string */
156         char c = *(virtual_path++);
157         if (c == 0)
158             break;
159 
160         /* Translate backslashes to forward slashes */
161         if (c == '\\')
162             c = '/';
163 
164         /* Store in real path buffer */
165         *(real_path++)= c;
166 
167     }
168 
169     /* Null terminator */
170     *real_path = 0;
171 
172 }
173 
guac_rdp_fs_get_errorcode(int err)174 int guac_rdp_fs_get_errorcode(int err) {
175 
176     /* Translate errno codes to GUAC_RDP_FS codes */
177     if (err == ENFILE)  return GUAC_RDP_FS_ENFILE;
178     if (err == ENOENT)  return GUAC_RDP_FS_ENOENT;
179     if (err == ENOTDIR) return GUAC_RDP_FS_ENOTDIR;
180     if (err == ENOSPC)  return GUAC_RDP_FS_ENOSPC;
181     if (err == EISDIR)  return GUAC_RDP_FS_EISDIR;
182     if (err == EACCES)  return GUAC_RDP_FS_EACCES;
183     if (err == EEXIST)  return GUAC_RDP_FS_EEXIST;
184     if (err == EINVAL)  return GUAC_RDP_FS_EINVAL;
185     if (err == ENOSYS)  return GUAC_RDP_FS_ENOSYS;
186     if (err == ENOTSUP) return GUAC_RDP_FS_ENOTSUP;
187 
188     /* Default to invalid parameter */
189     return GUAC_RDP_FS_EINVAL;
190 
191 }
192 
guac_rdp_fs_get_status(int err)193 int guac_rdp_fs_get_status(int err) {
194 
195     /* Translate GUAC_RDP_FS error code to RDPDR status code */
196     if (err == GUAC_RDP_FS_ENFILE)  return STATUS_NO_MORE_FILES;
197     if (err == GUAC_RDP_FS_ENOENT)  return STATUS_NO_SUCH_FILE;
198     if (err == GUAC_RDP_FS_ENOTDIR) return STATUS_NOT_A_DIRECTORY;
199     if (err == GUAC_RDP_FS_ENOSPC)  return STATUS_DISK_FULL;
200     if (err == GUAC_RDP_FS_EISDIR)  return STATUS_FILE_IS_A_DIRECTORY;
201     if (err == GUAC_RDP_FS_EACCES)  return STATUS_ACCESS_DENIED;
202     if (err == GUAC_RDP_FS_EEXIST)  return STATUS_OBJECT_NAME_COLLISION;
203     if (err == GUAC_RDP_FS_EINVAL)  return STATUS_INVALID_PARAMETER;
204     if (err == GUAC_RDP_FS_ENOSYS)  return STATUS_NOT_IMPLEMENTED;
205     if (err == GUAC_RDP_FS_ENOTSUP) return STATUS_NOT_SUPPORTED;
206 
207     /* Default to invalid parameter */
208     return STATUS_INVALID_PARAMETER;
209 
210 }
211 
guac_rdp_fs_open(guac_rdp_fs * fs,const char * path,int access,int file_attributes,int create_disposition,int create_options)212 int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path,
213         int access, int file_attributes, int create_disposition,
214         int create_options) {
215 
216     char real_path[GUAC_RDP_FS_MAX_PATH];
217     char normalized_path[GUAC_RDP_FS_MAX_PATH];
218 
219     struct stat file_stat;
220     int fd;
221     int file_id;
222     guac_rdp_fs_file* file;
223 
224     int flags = 0;
225 
226     guac_client_log(fs->client, GUAC_LOG_DEBUG,
227             "%s: path=\"%s\", access=0x%x, file_attributes=0x%x, "
228             "create_disposition=0x%x, create_options=0x%x",
229             __func__, path, access, file_attributes,
230             create_disposition, create_options);
231 
232     /* If no files available, return too many open */
233     if (fs->open_files >= GUAC_RDP_FS_MAX_FILES) {
234         guac_client_log(fs->client, GUAC_LOG_DEBUG,
235                 "%s: Too many open files.",
236                 __func__, path);
237         return GUAC_RDP_FS_ENFILE;
238     }
239 
240     /* If path empty, transform to root path */
241     if (path[0] == '\0')
242         path = "\\";
243 
244     /* If path is relative, the file does not exist */
245     else if (path[0] != '\\' && path[0] != '/') {
246         guac_client_log(fs->client, GUAC_LOG_DEBUG,
247                 "%s: Access denied - supplied path \"%s\" is relative.",
248                 __func__, path);
249         return GUAC_RDP_FS_ENOENT;
250     }
251 
252     /* Translate access into flags */
253     if (access & GENERIC_ALL)
254         flags = O_RDWR;
255     else if ((access & ( GENERIC_WRITE
256                        | FILE_WRITE_DATA
257                        | FILE_APPEND_DATA))
258           && (access & (GENERIC_READ  | FILE_READ_DATA)))
259         flags = O_RDWR;
260     else if (access & ( GENERIC_WRITE
261                       | FILE_WRITE_DATA
262                       | FILE_APPEND_DATA))
263         flags = O_WRONLY;
264     else
265         flags = O_RDONLY;
266 
267     /* Normalize path, return no-such-file if invalid  */
268     if (guac_rdp_fs_normalize_path(path, normalized_path)) {
269         guac_client_log(fs->client, GUAC_LOG_DEBUG,
270                 "%s: Normalization of path \"%s\" failed.", __func__, path);
271         return GUAC_RDP_FS_ENOENT;
272     }
273 
274     guac_client_log(fs->client, GUAC_LOG_DEBUG,
275             "%s: Normalized path \"%s\" to \"%s\".",
276             __func__, path, normalized_path);
277 
278     /* Translate normalized path to real path */
279     __guac_rdp_fs_translate_path(fs, normalized_path, real_path);
280 
281     guac_client_log(fs->client, GUAC_LOG_DEBUG,
282             "%s: Translated path \"%s\" to \"%s\".",
283             __func__, normalized_path, real_path);
284 
285     switch (create_disposition) {
286 
287         /* Create if not exist, fail otherwise */
288         case FILE_CREATE:
289             flags |= O_CREAT | O_EXCL;
290             break;
291 
292         /* Open file if exists and do not overwrite, fail otherwise */
293         case FILE_OPEN:
294             /* No flag necessary - default functionality of open */
295             break;
296 
297         /* Open if exists, create otherwise */
298         case FILE_OPEN_IF:
299             flags |= O_CREAT;
300             break;
301 
302         /* Overwrite if exists, fail otherwise */
303         case FILE_OVERWRITE:
304             flags |= O_TRUNC;
305             break;
306 
307         /* Overwrite if exists, create otherwise */
308         case FILE_OVERWRITE_IF:
309             flags |= O_CREAT | O_TRUNC;
310             break;
311 
312         /* Supersede (replace) if exists, otherwise create */
313         case FILE_SUPERSEDE:
314             unlink(real_path);
315             flags |= O_CREAT | O_TRUNC;
316             break;
317 
318         /* Unrecognised disposition */
319         default:
320             return GUAC_RDP_FS_ENOSYS;
321 
322     }
323 
324     /* Create directory first, if necessary */
325     if ((create_options & FILE_DIRECTORY_FILE) && (flags & O_CREAT)) {
326 
327         /* Create directory */
328         if (mkdir(real_path, S_IRWXU)) {
329             if (errno != EEXIST || (flags & O_EXCL)) {
330                 guac_client_log(fs->client, GUAC_LOG_DEBUG,
331                         "%s: mkdir() failed: %s",
332                         __func__, strerror(errno));
333                 return guac_rdp_fs_get_errorcode(errno);
334             }
335         }
336 
337         /* Unset O_CREAT and O_EXCL as directory must exist before open() */
338         flags &= ~(O_CREAT | O_EXCL);
339 
340     }
341 
342     guac_client_log(fs->client, GUAC_LOG_DEBUG,
343             "%s: native open: real_path=\"%s\", flags=0x%x",
344             __func__, real_path, flags);
345 
346     /* Open file */
347     fd = open(real_path, flags, S_IRUSR | S_IWUSR);
348 
349     /* If file open failed as we're trying to write a dir, retry as read-only */
350     if (fd == -1 && errno == EISDIR) {
351         flags &= ~(O_WRONLY | O_RDWR);
352         flags |= O_RDONLY;
353         fd = open(real_path, flags, S_IRUSR | S_IWUSR);
354     }
355 
356     if (fd == -1) {
357         guac_client_log(fs->client, GUAC_LOG_DEBUG,
358                 "%s: open() failed: %s", __func__, strerror(errno));
359         return guac_rdp_fs_get_errorcode(errno);
360     }
361 
362     /* Get file ID, init file */
363     file_id = guac_pool_next_int(fs->file_id_pool);
364     file = &(fs->files[file_id]);
365     file->id = file_id;
366     file->fd  = fd;
367     file->dir = NULL;
368     file->dir_pattern[0] = '\0';
369     file->absolute_path = strdup(normalized_path);
370     file->real_path = strdup(real_path);
371     file->bytes_written = 0;
372 
373     guac_client_log(fs->client, GUAC_LOG_DEBUG,
374             "%s: Opened \"%s\" as file_id=%i",
375             __func__, normalized_path, file_id);
376 
377     /* Attempt to pull file information */
378     if (fstat(fd, &file_stat) == 0) {
379 
380         /* Load size and times */
381         file->size  = file_stat.st_size;
382         file->ctime = WINDOWS_TIME(file_stat.st_ctime);
383         file->mtime = WINDOWS_TIME(file_stat.st_mtime);
384         file->atime = WINDOWS_TIME(file_stat.st_atime);
385 
386         /* Set type */
387         if (S_ISDIR(file_stat.st_mode))
388             file->attributes = FILE_ATTRIBUTE_DIRECTORY;
389         else
390             file->attributes = FILE_ATTRIBUTE_NORMAL;
391 
392     }
393 
394     /* If information cannot be retrieved, fake it */
395     else {
396 
397         /* Init information to 0, lacking any alternative */
398         file->size  = 0;
399         file->ctime = 0;
400         file->mtime = 0;
401         file->atime = 0;
402         file->attributes = FILE_ATTRIBUTE_NORMAL;
403 
404     }
405 
406     fs->open_files++;
407 
408     return file_id;
409 
410 }
411 
guac_rdp_fs_read(guac_rdp_fs * fs,int file_id,uint64_t offset,void * buffer,int length)412 int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, uint64_t offset,
413         void* buffer, int length) {
414 
415     int bytes_read;
416 
417     guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
418     if (file == NULL) {
419         guac_client_log(fs->client, GUAC_LOG_DEBUG,
420                 "%s: Read from bad file_id: %i", __func__, file_id);
421         return GUAC_RDP_FS_EINVAL;
422     }
423 
424     /* Attempt read */
425     lseek(file->fd, offset, SEEK_SET);
426     bytes_read = read(file->fd, buffer, length);
427 
428     /* Translate errno on error */
429     if (bytes_read < 0)
430         return guac_rdp_fs_get_errorcode(errno);
431 
432     return bytes_read;
433 
434 }
435 
guac_rdp_fs_write(guac_rdp_fs * fs,int file_id,uint64_t offset,void * buffer,int length)436 int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, uint64_t offset,
437         void* buffer, int length) {
438 
439     int bytes_written;
440 
441     guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
442     if (file == NULL) {
443         guac_client_log(fs->client, GUAC_LOG_DEBUG,
444                 "%s: Write to bad file_id: %i", __func__, file_id);
445         return GUAC_RDP_FS_EINVAL;
446     }
447 
448     /* Attempt write */
449     lseek(file->fd, offset, SEEK_SET);
450     bytes_written = write(file->fd, buffer, length);
451 
452     /* Translate errno on error */
453     if (bytes_written < 0)
454         return guac_rdp_fs_get_errorcode(errno);
455 
456     file->bytes_written += bytes_written;
457     return bytes_written;
458 
459 }
460 
guac_rdp_fs_rename(guac_rdp_fs * fs,int file_id,const char * new_path)461 int guac_rdp_fs_rename(guac_rdp_fs* fs, int file_id,
462         const char* new_path) {
463 
464     char real_path[GUAC_RDP_FS_MAX_PATH];
465     char normalized_path[GUAC_RDP_FS_MAX_PATH];
466 
467     guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
468     if (file == NULL) {
469         guac_client_log(fs->client, GUAC_LOG_DEBUG,
470                 "%s: Rename of bad file_id: %i", __func__, file_id);
471         return GUAC_RDP_FS_EINVAL;
472     }
473 
474     /* Normalize path, return no-such-file if invalid  */
475     if (guac_rdp_fs_normalize_path(new_path, normalized_path)) {
476         guac_client_log(fs->client, GUAC_LOG_DEBUG,
477                 "%s: Normalization of path \"%s\" failed.",
478                 __func__, new_path);
479         return GUAC_RDP_FS_ENOENT;
480     }
481 
482     /* Translate normalized path to real path */
483     __guac_rdp_fs_translate_path(fs, normalized_path, real_path);
484 
485     guac_client_log(fs->client, GUAC_LOG_DEBUG,
486             "%s: Renaming \"%s\" -> \"%s\"",
487             __func__, file->real_path, real_path);
488 
489     /* Perform rename */
490     if (rename(file->real_path, real_path)) {
491         guac_client_log(fs->client, GUAC_LOG_DEBUG,
492                 "%s: rename() failed: \"%s\" -> \"%s\"",
493                 __func__, file->real_path, real_path);
494         return guac_rdp_fs_get_errorcode(errno);
495     }
496 
497     return 0;
498 
499 }
500 
guac_rdp_fs_delete(guac_rdp_fs * fs,int file_id)501 int guac_rdp_fs_delete(guac_rdp_fs* fs, int file_id) {
502 
503     /* Get file */
504     guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
505     if (file == NULL) {
506         guac_client_log(fs->client, GUAC_LOG_DEBUG,
507                 "%s: Delete of bad file_id: %i", __func__, file_id);
508         return GUAC_RDP_FS_EINVAL;
509     }
510 
511     /* If directory, attempt removal */
512     if (file->attributes & FILE_ATTRIBUTE_DIRECTORY) {
513         if (rmdir(file->real_path)) {
514             guac_client_log(fs->client, GUAC_LOG_DEBUG,
515                     "%s: rmdir() failed: \"%s\"", __func__, file->real_path);
516             return guac_rdp_fs_get_errorcode(errno);
517         }
518     }
519 
520     /* Otherwise, attempt deletion */
521     else if (unlink(file->real_path)) {
522         guac_client_log(fs->client, GUAC_LOG_DEBUG,
523                 "%s: unlink() failed: \"%s\"", __func__, file->real_path);
524         return guac_rdp_fs_get_errorcode(errno);
525     }
526 
527     return 0;
528 
529 }
530 
guac_rdp_fs_truncate(guac_rdp_fs * fs,int file_id,int length)531 int guac_rdp_fs_truncate(guac_rdp_fs* fs, int file_id, int length) {
532 
533     /* Get file */
534     guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
535     if (file == NULL) {
536         guac_client_log(fs->client, GUAC_LOG_DEBUG,
537                 "%s: Delete of bad file_id: %i", __func__, file_id);
538         return GUAC_RDP_FS_EINVAL;
539     }
540 
541     /* Attempt truncate */
542     if (ftruncate(file->fd, length)) {
543         guac_client_log(fs->client, GUAC_LOG_DEBUG,
544                 "%s: ftruncate() to %i bytes failed: \"%s\"",
545                 __func__, length, file->real_path);
546         return guac_rdp_fs_get_errorcode(errno);
547     }
548 
549     return 0;
550 
551 }
552 
guac_rdp_fs_close(guac_rdp_fs * fs,int file_id)553 void guac_rdp_fs_close(guac_rdp_fs* fs, int file_id) {
554 
555     guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
556     if (file == NULL) {
557         guac_client_log(fs->client, GUAC_LOG_DEBUG,
558                 "%s: Ignoring close for bad file_id: %i",
559                 __func__, file_id);
560         return;
561     }
562 
563     file = &(fs->files[file_id]);
564 
565     guac_client_log(fs->client, GUAC_LOG_DEBUG,
566             "%s: Closed \"%s\" (file_id=%i)",
567             __func__, file->absolute_path, file_id);
568 
569     /* Close directory, if open */
570     if (file->dir != NULL)
571         closedir(file->dir);
572 
573     /* Close file */
574     close(file->fd);
575 
576     /* Free name */
577     free(file->absolute_path);
578     free(file->real_path);
579 
580     /* Free ID back to pool */
581     guac_pool_free_int(fs->file_id_pool, file_id);
582     fs->open_files--;
583 
584 }
585 
guac_rdp_fs_read_dir(guac_rdp_fs * fs,int file_id)586 const char* guac_rdp_fs_read_dir(guac_rdp_fs* fs, int file_id) {
587 
588     guac_rdp_fs_file* file;
589 
590     struct dirent* result;
591 
592     /* Only read if file ID is valid */
593     if (file_id < 0 || file_id >= GUAC_RDP_FS_MAX_FILES)
594         return NULL;
595 
596     file = &(fs->files[file_id]);
597 
598     /* Open directory if not yet open, stop if error */
599     if (file->dir == NULL) {
600         file->dir = fdopendir(file->fd);
601         if (file->dir == NULL)
602             return NULL;
603     }
604 
605     /* Read next entry, stop if error or no more entries */
606     if ((result = readdir(file->dir)) == NULL)
607         return NULL;
608 
609     /* Return filename */
610     return result->d_name;
611 
612 }
613 
guac_rdp_fs_basename(const char * path)614 const char* guac_rdp_fs_basename(const char* path) {
615 
616     for (const char* c = path; *c != '\0'; c++) {
617 
618         /* Reset beginning of path if a path separator is found */
619         if (*c == '/' || *c == '\\')
620             path = c + 1;
621 
622     }
623 
624     /* path now points to the first character after the last path separator */
625     return path;
626 
627 }
628 
guac_rdp_fs_normalize_path(const char * path,char * abs_path)629 int guac_rdp_fs_normalize_path(const char* path, char* abs_path) {
630 
631     int path_depth = 0;
632     const char* path_components[GUAC_RDP_MAX_PATH_DEPTH];
633 
634     /* If original path is not absolute, normalization fails */
635     if (path[0] != '\\' && path[0] != '/')
636         return 1;
637 
638     /* Create scratch copy of path excluding leading slash (we will be
639      * replacing path separators with null terminators and referencing those
640      * substrings directly as path components) */
641     char path_scratch[GUAC_RDP_FS_MAX_PATH - 1];
642     int length = guac_strlcpy(path_scratch, path + 1,
643             sizeof(path_scratch));
644 
645     /* Fail if provided path is too long */
646     if (length >= sizeof(path_scratch))
647         return 1;
648 
649     /* Locate all path components within path */
650     const char* current_path_component = &(path_scratch[0]);
651     for (int i = 0; i <= length; i++) {
652 
653         /* If current character is a path separator, parse as component */
654         char c = path_scratch[i];
655         if (c == '/' || c == '\\' || c == '\0') {
656 
657             /* Terminate current component */
658             path_scratch[i] = '\0';
659 
660             /* If component refers to parent, just move up in depth */
661             if (strcmp(current_path_component, "..") == 0) {
662                 if (path_depth > 0)
663                     path_depth--;
664             }
665 
666             /* Otherwise, if component not current directory, add to list */
667             else if (strcmp(current_path_component, ".") != 0
668                     && strcmp(current_path_component, "") != 0) {
669 
670                 /* Fail normalization if path is too deep */
671                 if (path_depth >= GUAC_RDP_MAX_PATH_DEPTH)
672                     return 1;
673 
674                 path_components[path_depth++] = current_path_component;
675 
676             }
677 
678             /* Update start of next component */
679             current_path_component = &(path_scratch[i+1]);
680 
681         } /* end if separator */
682 
683         /* We do not currently support named streams */
684         else if (c == ':')
685             return 1;
686 
687     } /* end for each character */
688 
689     /* Add leading slash for resulting absolute path */
690     abs_path[0] = '\\';
691 
692     /* Append normalized components to path, separated by slashes */
693     guac_strljoin(abs_path + 1, path_components, path_depth,
694             "\\", GUAC_RDP_FS_MAX_PATH - 1);
695 
696     return 0;
697 
698 }
699 
guac_rdp_fs_convert_path(const char * parent,const char * rel_path,char * abs_path)700 int guac_rdp_fs_convert_path(const char* parent, const char* rel_path, char* abs_path) {
701 
702     int length;
703     char combined_path[GUAC_RDP_FS_MAX_PATH];
704 
705     /* Copy parent path */
706     length = guac_strlcpy(combined_path, parent, sizeof(combined_path));
707 
708     /* Add trailing slash */
709     length += guac_strlcpy(combined_path + length, "\\",
710             sizeof(combined_path) - length);
711 
712     /* Copy remaining path */
713     length += guac_strlcpy(combined_path + length, rel_path,
714             sizeof(combined_path) - length);
715 
716     /* Normalize into provided buffer */
717     return guac_rdp_fs_normalize_path(combined_path, abs_path);
718 
719 }
720 
guac_rdp_fs_get_file(guac_rdp_fs * fs,int file_id)721 guac_rdp_fs_file* guac_rdp_fs_get_file(guac_rdp_fs* fs, int file_id) {
722 
723     /* Validate ID */
724     if (file_id < 0 || file_id >= GUAC_RDP_FS_MAX_FILES)
725         return NULL;
726 
727     /* Return file at given ID */
728     return &(fs->files[file_id]);
729 
730 }
731 
guac_rdp_fs_matches(const char * filename,const char * pattern)732 int guac_rdp_fs_matches(const char* filename, const char* pattern) {
733     return fnmatch(pattern, filename, FNM_NOESCAPE) != 0;
734 }
735 
guac_rdp_fs_get_info(guac_rdp_fs * fs,guac_rdp_fs_info * info)736 int guac_rdp_fs_get_info(guac_rdp_fs* fs, guac_rdp_fs_info* info) {
737 
738     /* Read FS information */
739     struct statvfs fs_stat;
740     if (statvfs(fs->drive_path, &fs_stat))
741         return guac_rdp_fs_get_errorcode(errno);
742 
743     /* Assign to structure */
744     info->blocks_available = fs_stat.f_bfree;
745     info->blocks_total = fs_stat.f_blocks;
746     info->block_size = fs_stat.f_bsize;
747     return 0;
748 
749 }
750 
guac_rdp_fs_append_filename(char * fullpath,const char * path,const char * filename)751 int guac_rdp_fs_append_filename(char* fullpath, const char* path,
752         const char* filename) {
753 
754     int i;
755 
756     /* Disallow "." as a filename */
757     if (strcmp(filename, ".") == 0)
758         return 0;
759 
760     /* Disallow ".." as a filename */
761     if (strcmp(filename, "..") == 0)
762         return 0;
763 
764     /* Copy path, append trailing slash */
765     for (i=0; i<GUAC_RDP_FS_MAX_PATH; i++) {
766 
767         /*
768          * Append trailing slash only if:
769          *  1) Trailing slash is not already present
770          *  2) Path is non-empty
771          */
772 
773         char c = path[i];
774         if (c == '\0') {
775             if (i > 0 && path[i-1] != '/' && path[i-1] != '\\')
776                 fullpath[i++] = '/';
777             break;
778         }
779 
780         /* Copy character if not end of string */
781         fullpath[i] = c;
782 
783     }
784 
785     /* Append filename */
786     for (; i<GUAC_RDP_FS_MAX_PATH; i++) {
787 
788         char c = *(filename++);
789         if (c == '\0')
790             break;
791 
792         /* Filenames may not contain slashes */
793         if (c == '\\' || c == '/')
794             return 0;
795 
796         /* Append each character within filename */
797         fullpath[i] = c;
798 
799     }
800 
801     /* Verify path length is within maximum */
802     if (i == GUAC_RDP_FS_MAX_PATH)
803         return 0;
804 
805     /* Terminate path string */
806     fullpath[i] = '\0';
807 
808     /* Append was successful */
809     return 1;
810 
811 }
812 
813