guacamole-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From vn...@apache.org
Subject [1/3] incubator-guacamole-server git commit: GUACAMOLE-303: Extend common SFTP filesystem such that arbitrary directories can be used as the root of the filesystem.
Date Mon, 03 Jul 2017 21:52:09 GMT
Repository: incubator-guacamole-server
Updated Branches:
  refs/heads/master 836fc3eaa -> 07db9808a


GUACAMOLE-303: Extend common SFTP filesystem such that arbitrary directories can be used as
the root of the filesystem.


Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/commit/0474f86c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/tree/0474f86c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/diff/0474f86c

Branch: refs/heads/master
Commit: 0474f86c46309ddae785359c92374820cc768525
Parents: 836fc3e
Author: Michael Jumper <mjumper@apache.org>
Authored: Thu Jun 29 15:28:21 2017 -0700
Committer: Michael Jumper <mjumper@apache.org>
Committed: Thu Jun 29 15:36:10 2017 -0700

----------------------------------------------------------------------
 src/common-ssh/common-ssh/sftp.h |  21 ++-
 src/common-ssh/sftp.c            | 259 ++++++++++++++++++++++++++++++++--
 src/protocols/rdp/rdp.c          |   2 +-
 src/protocols/ssh/ssh.c          |   2 +-
 src/protocols/vnc/vnc.c          |   2 +-
 5 files changed, 270 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/0474f86c/src/common-ssh/common-ssh/sftp.h
----------------------------------------------------------------------
diff --git a/src/common-ssh/common-ssh/sftp.h b/src/common-ssh/common-ssh/sftp.h
index 038a577..0ec2d12 100644
--- a/src/common-ssh/common-ssh/sftp.h
+++ b/src/common-ssh/common-ssh/sftp.h
@@ -34,6 +34,11 @@
 #define GUAC_COMMON_SSH_SFTP_MAX_PATH 2048
 
 /**
+ * Maximum number of path components per path.
+ */
+#define GUAC_COMMON_SSH_SFTP_MAX_DEPTH 1024
+
+/**
  * Representation of an SFTP-driven filesystem object. Unlike guac_object, this
  * structure is not tied to any particular user.
  */
@@ -55,6 +60,11 @@ typedef struct guac_common_ssh_sftp_filesystem {
     LIBSSH2_SFTP* sftp_session;
 
     /**
+     * The path to the directory to expose to the user as a filesystem object.
+     */
+    char root_path[GUAC_COMMON_SSH_SFTP_MAX_PATH];
+
+    /**
      * The path files will be sent to, if uploaded directly via a "file"
      * instruction.
      */
@@ -103,15 +113,22 @@ typedef struct guac_common_ssh_sftp_ls_state {
  *     The session to use to provide SFTP. This session will automatically be
  *     destroyed when this filesystem is destroyed.
  *
+ * @param root_path
+ *     The path accessible via SFTP to consider the root path of the filesystem
+ *     exposed to the user. Only the contents of this path will be available
+ *     via the filesystem object.
+ *
  * @param name
  *     The name to send as the name of the filesystem whenever it is exposed
- *     to a user.
+ *     to a user, or NULL to automatically generate a name from the provided
+ *     root_path.
  *
  * @return
  *     A new SFTP filesystem object, not yet exposed to users.
  */
 guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem(
-        guac_common_ssh_session* session, const char* name);
+        guac_common_ssh_session* session, const char* root_path,
+        const char* name);
 
 /**
  * Destroys the given filesystem object, disconnecting from SFTP and freeing

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/0474f86c/src/common-ssh/sftp.c
----------------------------------------------------------------------
diff --git a/src/common-ssh/sftp.c b/src/common-ssh/sftp.c
index 4be1568..e0e029f 100644
--- a/src/common-ssh/sftp.c
+++ b/src/common-ssh/sftp.c
@@ -33,6 +33,111 @@
 #include <string.h>
 
 /**
+ * Given an arbitrary absolute path, which may contain "..", ".", and
+ * backslashes, creates an equivalent absolute path which does NOT contain
+ * relative path components (".." or "."), backslashes, or empty path
+ * components. With the exception of paths referring to the root directory, the
+ * resulting path is guaranteed to not contain trailing slashes.
+ *
+ * Normalization will fail if the given path is not absolute, is too long, or
+ * contains more than GUAC_COMMON_SSH_SFTP_MAX_DEPTH path components.
+ *
+ * @param fullpath
+ *     The buffer to populate with the normalized path. The normalized path
+ *     will not contain relative path components like ".." or ".", nor will it
+ *     contain backslashes. This buffer MUST be at least
+ *     GUAC_COMMON_SSH_SFTP_MAX_PATH bytes in size.
+ *
+ * @param path
+ *     The absolute path to normalize.
+ *
+ * @return
+ *     Non-zero if normalization succeeded, zero otherwise.
+ */
+static int guac_common_ssh_sftp_normalize_path(char* fullpath,
+        const char* path) {
+
+    int i;
+
+    int path_depth = 0;
+    char path_component_data[GUAC_COMMON_SSH_SFTP_MAX_PATH];
+    const char* path_components[GUAC_COMMON_SSH_SFTP_MAX_DEPTH];
+
+    const char** current_path_component      = &(path_components[0]);
+    const char*  current_path_component_data = &(path_component_data[0]);
+
+    /* If original path is not absolute, normalization fails */
+    if (path[0] != '\\' && path[0] != '/')
+        return 1;
+
+    /* Skip past leading slash */
+    path++;
+
+    /* Copy path into component data for parsing */
+    strncpy(path_component_data, path, sizeof(path_component_data) - 1);
+
+    /* Find path components within path */
+    for (i = 0; i < sizeof(path_component_data); i++) {
+
+        /* If current character is a path separator, parse as component */
+        char c = path_component_data[i];
+        if (c == '/' || c == '\\' || c == '\0') {
+
+            /* Terminate current component */
+            path_component_data[i] = '\0';
+
+            /* If component refers to parent, just move up in depth */
+            if (strcmp(current_path_component_data, "..") == 0) {
+                if (path_depth > 0)
+                    path_depth--;
+            }
+
+            /* Otherwise, if component not current directory, add to list */
+            else if (strcmp(current_path_component_data,   ".") != 0
+                     && strcmp(current_path_component_data, "") != 0)
+                path_components[path_depth++] = current_path_component_data;
+
+            /* If end of string, stop */
+            if (c == '\0')
+                break;
+
+            /* Update start of next component */
+            current_path_component_data = &(path_component_data[i+1]);
+
+        } /* end if separator */
+
+    } /* end for each character */
+
+    /* If no components, the path is simply root */
+    if (path_depth == 0) {
+        strcpy(fullpath, "/");
+        return 1;
+    }
+
+    /* Ensure last component is null-terminated */
+    path_component_data[i] = 0;
+
+    /* Convert components back into path */
+    for (; path_depth > 0; path_depth--) {
+
+        const char* filename = *(current_path_component++);
+
+        /* Add separator */
+        *(fullpath++) = '/';
+
+        /* Copy string */
+        while (*filename != 0)
+            *(fullpath++) = *(filename++);
+
+    }
+
+    /* Terminate absolute path */
+    *(fullpath++) = 0;
+    return 1;
+
+}
+
+/**
  * Translates the last error message received by the SFTP layer of an SSH
  * session into a Guacamole protocol status code.
  *
@@ -184,6 +289,73 @@ static int guac_ssh_append_filename(char* fullpath, const char* path,
 }
 
 /**
+ * Concatenates the given paths, separating the two with a single forward
+ * slash. The full result must be no more than GUAC_COMMON_SSH_SFTP_MAX_PATH
+ * bytes long, counting null terminator.
+ *
+ * @param fullpath
+ *     The buffer to store the result within. This buffer must be at least
+ *     GUAC_COMMON_SSH_SFTP_MAX_PATH bytes long.
+ *
+ * @param path_a
+ *     The path to place at the beginning of the resulting path.
+ *
+ * @param path_b
+ *     The path to append after path_a within the resulting path.
+ *
+ * @return
+ *     Non-zero if the paths were successfully concatenated together, zero
+ *     otherwise.
+ */
+static int guac_ssh_append_path(char* fullpath, const char* path_a,
+        const char* path_b) {
+
+    int i;
+
+    /* Copy path, appending a trailing slash */
+    for (i = 0; i < GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
+
+        char c = path_a[i];
+        if (c == '\0') {
+            if (i > 0 && path_a[i-1] != '/')
+                fullpath[i++] = '/';
+            break;
+        }
+
+        /* Copy character if not end of string */
+        fullpath[i] = c;
+
+    }
+
+    /* Skip past leading slashes in second path */
+    while (*path_b == '/')
+       path_b++;
+
+    /* Append path */
+    for (; i < GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
+
+        char c = *(path_b++);
+        if (c == '\0')
+            break;
+
+        /* Append each character within path */
+        fullpath[i] = c;
+
+    }
+
+    /* Verify path length is within maximum */
+    if (i == GUAC_COMMON_SSH_SFTP_MAX_PATH)
+        return 0;
+
+    /* Terminate path string */
+    fullpath[i] = '\0';
+
+    /* Append was successful */
+    return 1;
+
+}
+
+/**
  * Handler for blob messages which continue an inbound SFTP data transfer
  * (upload). The data associated with the given stream is expected to be a
  * pointer to an open LIBSSH2_SFTP_HANDLE for the file to which the data
@@ -568,6 +740,38 @@ static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user,
 }
 
 /**
+ * Translates a stream name for the given SFTP filesystem object into the
+ * absolute path corresponding to the actual file it represents.
+ *
+ * @param fullpath
+ *     The buffer to populate with the translated path. This buffer MUST be at
+ *     least GUAC_COMMON_SSH_SFTP_MAX_PATH bytes in size.
+ *
+ * @param object
+ *     The Guacamole protocol object associated with the SFTP filesystem.
+ *
+ * @param name
+ *     The name of the stream (file) to translate into an absolute path.
+ *
+ * @return
+ *     Non-zero if translation succeeded, zero otherwise.
+ */
+static int guac_common_ssh_sftp_translate_name(char* fullpath,
+        guac_object* object, char* name) {
+
+    char normalized_name[GUAC_COMMON_SSH_SFTP_MAX_PATH];
+
+    guac_common_ssh_sftp_filesystem* filesystem =
+        (guac_common_ssh_sftp_filesystem*) object->data;
+
+    /* Normalize stream name into a path, and append to the root path */
+    return guac_common_ssh_sftp_normalize_path(normalized_name, name)
+        && guac_ssh_append_path(fullpath, filesystem->root_path,
+                normalized_name);
+
+}
+
+/**
  * Handler for get messages. In context of SFTP and the filesystem exposed via
  * the Guacamole protocol, get messages request the body of a file within the
  * filesystem.
@@ -587,16 +791,25 @@ static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user,
 static int guac_common_ssh_sftp_get_handler(guac_user* user,
         guac_object* object, char* name) {
 
+    char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH];
+
     guac_common_ssh_sftp_filesystem* filesystem =
         (guac_common_ssh_sftp_filesystem*) object->data;
 
     LIBSSH2_SFTP* sftp = filesystem->sftp_session;
     LIBSSH2_SFTP_ATTRIBUTES attributes;
 
+    /* Translate stream name into filesystem path */
+    if (!guac_common_ssh_sftp_translate_name(fullpath, object, name)) {
+        guac_user_log(user, GUAC_LOG_INFO, "Unable to generate real path "
+                "for stream \"%s\"", name);
+        return 0;
+    }
+
     /* Attempt to read file information */
-    if (libssh2_sftp_stat(sftp, name, &attributes)) {
+    if (libssh2_sftp_stat(sftp, fullpath, &attributes)) {
         guac_user_log(user, GUAC_LOG_INFO, "Unable to read file \"%s\"",
-                name);
+                fullpath);
         return 0;
     }
 
@@ -604,10 +817,10 @@ static int guac_common_ssh_sftp_get_handler(guac_user* user,
     if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) {
 
         /* Open as directory */
-        LIBSSH2_SFTP_HANDLE* dir = libssh2_sftp_opendir(sftp, name);
+        LIBSSH2_SFTP_HANDLE* dir = libssh2_sftp_opendir(sftp, fullpath);
         if (dir == NULL) {
             guac_user_log(user, GUAC_LOG_INFO,
-                    "Unable to read directory \"%s\"", name);
+                    "Unable to read directory \"%s\"", fullpath);
             return 0;
         }
 
@@ -638,11 +851,11 @@ static int guac_common_ssh_sftp_get_handler(guac_user* user,
     else {
 
         /* Open as normal file */
-        LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name,
+        LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, fullpath,
             LIBSSH2_FXF_READ, 0);
         if (file == NULL) {
             guac_user_log(user, GUAC_LOG_INFO,
-                    "Unable to read file \"%s\"", name);
+                    "Unable to read file \"%s\"", fullpath);
             return 0;
         }
 
@@ -688,19 +901,28 @@ static int guac_common_ssh_sftp_get_handler(guac_user* user,
 static int guac_common_ssh_sftp_put_handler(guac_user* user,
         guac_object* object, guac_stream* stream, char* mimetype, char* name) {
 
+    char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH];
+
     guac_common_ssh_sftp_filesystem* filesystem =
         (guac_common_ssh_sftp_filesystem*) object->data;
 
     LIBSSH2_SFTP* sftp = filesystem->sftp_session;
 
+    /* Translate stream name into filesystem path */
+    if (!guac_common_ssh_sftp_translate_name(fullpath, object, name)) {
+        guac_user_log(user, GUAC_LOG_INFO, "Unable to generate real path "
+                "for stream \"%s\"", name);
+        return 0;
+    }
+
     /* Open file via SFTP */
-    LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name,
+    LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, fullpath,
             LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
             S_IRUSR | S_IWUSR);
 
     /* Acknowledge stream if successful */
     if (file != NULL) {
-        guac_user_log(user, GUAC_LOG_DEBUG, "File \"%s\" opened", name);
+        guac_user_log(user, GUAC_LOG_DEBUG, "File \"%s\" opened", fullpath);
         guac_protocol_send_ack(user->socket, stream, "SFTP: File opened",
                 GUAC_PROTOCOL_STATUS_SUCCESS);
     }
@@ -708,7 +930,7 @@ static int guac_common_ssh_sftp_put_handler(guac_user* user,
     /* Abort on failure */
     else {
         guac_user_log(user, GUAC_LOG_INFO,
-                "Unable to open file \"%s\"", name);
+                "Unable to open file \"%s\"", fullpath);
         guac_protocol_send_ack(user->socket, stream, "SFTP: Open failed",
                 guac_sftp_get_status(filesystem));
     }
@@ -756,7 +978,8 @@ guac_object* guac_common_ssh_alloc_sftp_filesystem_object(
 }
 
 guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem(
-        guac_common_ssh_session* session, const char* name) {
+        guac_common_ssh_session* session, const char* root_path,
+        const char* name) {
 
     /* Request SFTP */
     LIBSSH2_SFTP* sftp_session = libssh2_sftp_init(session->session);
@@ -768,10 +991,24 @@ guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem(
         malloc(sizeof(guac_common_ssh_sftp_filesystem));
 
     /* Associate SSH session with SFTP data and user */
-    filesystem->name = strdup(name);
     filesystem->ssh_session = session;
     filesystem->sftp_session = sftp_session;
 
+    /* Normalize and store the provided root path */
+    if (!guac_common_ssh_sftp_normalize_path(filesystem->root_path,
+                root_path)) {
+        guac_client_log(session->client, GUAC_LOG_WARNING, "Cannot create "
+                "SFTP filesystem - \"%s\" is not a valid path.", root_path);
+        free(filesystem);
+        return NULL;
+    }
+
+    /* Generate filesystem name from root path if no name is provided */
+    if (name != NULL)
+        filesystem->name = strdup(name);
+    else
+        filesystem->name = strdup(filesystem->root_path);
+
     /* Initially upload files to current directory */
     strcpy(filesystem->upload_path, ".");
 

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/0474f86c/src/protocols/rdp/rdp.c
----------------------------------------------------------------------
diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c
index d06fad3..c1be71e 100644
--- a/src/protocols/rdp/rdp.c
+++ b/src/protocols/rdp/rdp.c
@@ -988,7 +988,7 @@ void* guac_rdp_client_thread(void* data) {
         /* Load and expose filesystem */
         rdp_client->sftp_filesystem =
             guac_common_ssh_create_sftp_filesystem(
-                    rdp_client->sftp_session, "/");
+                    rdp_client->sftp_session, "/", NULL);
 
         /* Expose filesystem to connection owner */
         guac_client_for_owner(client,

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/0474f86c/src/protocols/ssh/ssh.c
----------------------------------------------------------------------
diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c
index aa9fdae..18a0dcb 100644
--- a/src/protocols/ssh/ssh.c
+++ b/src/protocols/ssh/ssh.c
@@ -266,7 +266,7 @@ void* ssh_client_thread(void* data) {
 
         /* Request SFTP */
         ssh_client->sftp_filesystem = guac_common_ssh_create_sftp_filesystem(
-                    ssh_client->sftp_session, "/");
+                    ssh_client->sftp_session, "/", NULL);
 
         /* Expose filesystem to connection owner */
         guac_client_for_owner(client,

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/0474f86c/src/protocols/vnc/vnc.c
----------------------------------------------------------------------
diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c
index 81d46f1..2b7263a 100644
--- a/src/protocols/vnc/vnc.c
+++ b/src/protocols/vnc/vnc.c
@@ -272,7 +272,7 @@ void* guac_vnc_client_thread(void* data) {
         /* Load filesystem */
         vnc_client->sftp_filesystem =
             guac_common_ssh_create_sftp_filesystem(
-                    vnc_client->sftp_session, "/");
+                    vnc_client->sftp_session, "/", NULL);
 
         /* Expose filesystem to connection owner */
         guac_client_for_owner(client,


Mime
View raw message