From 9a9cafeed56b9c1d009a4cc9e41d643a38e290c4 Mon Sep 17 00:00:00 2001 From: Praneeth Sarode Date: Wed, 5 Feb 2025 17:01:20 +0530 Subject: [PATCH] sftp: add users-groups-by-id@openssh.com extension for client Signed-off-by: Praneeth Sarode Reviewed-by: Jakub Jelen Reviewed-by: Eshan Kelkar --- include/libssh/sftp.h | 69 ++++++++++++++ src/libssh.map | 8 ++ src/sftp.c | 212 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 289 insertions(+) diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index dbe3f991..5f15b1f2 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -79,6 +79,7 @@ typedef struct sftp_status_message_struct* sftp_status_message; typedef struct sftp_statvfs_struct* sftp_statvfs_t; typedef struct sftp_limits_struct* sftp_limits_t; typedef struct sftp_aio_struct* sftp_aio; +typedef struct sftp_name_id_map_struct *sftp_name_id_map; struct sftp_session_struct { ssh_session session; @@ -213,6 +214,22 @@ struct sftp_limits_struct { uint64_t max_open_handles; /** maximum number of active handles allowed by server */ }; +/** + * @brief SFTP names map structure to store the mapping between ids and names. + * + * This is mainly for the use of sftp_get_users_groups_by_id() function. + */ +struct sftp_name_id_map_struct { + /** @brief Count of name-id pairs in the map */ + uint32_t count; + + /** @brief Array of ids, ids[i] mapped to names[i] */ + uint32_t *ids; + + /** @brief Array of names, names[i] mapped to ids[i] */ + char **names; +}; + /** * @brief Creates a new sftp session. * @@ -1194,6 +1211,58 @@ LIBSSH_API char *sftp_expand_path(sftp_session sftp, const char *path); */ LIBSSH_API char *sftp_home_directory(sftp_session sftp, const char *username); +/** + * @brief Create a new sftp_name_id_map struct. + * + * @param count The number of ids/names to store in the map. + * + * @return A pointer to the newly allocated sftp_name_id_map + * struct. + */ +LIBSSH_API sftp_name_id_map sftp_name_id_map_new(uint32_t count); + +/** + * @brief Free the memory of an allocated `sftp_name_id_map` struct. + * + * @param map A pointer to the `sftp_name_id_map` struct to free. + */ +LIBSSH_API void sftp_name_id_map_free(sftp_name_id_map map); + +/** + * @brief Retrieves usernames and group names based on provided user and group + * IDs. + * + * The retrieved names are stored in the `names` field of the + * `sftp_name_id_map` structure. In case a uid or gid is not found, an empty + * string is stored. + * + * This calls the "users-groups-by-id@openssh.com" extension. + * You should check if the extension is supported using: + * + * @code + * int supported = sftp_extension_supported(sftp, + * "users-groups-by-id@openssh.com", "1"); + * @endcode + * + * @param sftp The SFTP session handle. + * + * @param users_map A pointer to a `sftp_name_id_map` struct with the user + * IDs. Can be NULL if only group names are needed. + * + * @param groups_map A pointer to a `sftp_name_id_map` struct with the group + * IDs. Can be NULL if only user names are needed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @note The caller needs to free the memory used for + * the maps later using `sftp_name_id_map_free()`. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_get_users_groups_by_id(sftp_session sftp, + sftp_name_id_map users_map, + sftp_name_id_map groups_map); + #ifdef WITH_SERVER /** * @brief Create a new sftp server session. diff --git a/src/libssh.map b/src/libssh.map index 02f5f683..4a2e8372 100644 --- a/src/libssh.map +++ b/src/libssh.map @@ -481,3 +481,11 @@ LIBSSH_4_10_0 # Released ssh_pki_export_privkey_file_format; ssh_request_no_more_sessions; } LIBSSH_4_9_0; + +LIBSSH_AFTER_4_10_0 +{ + global: + sftp_get_users_groups_by_id; + sftp_name_id_map_free; + sftp_name_id_map_new; +} LIBSSH_4_10_0; diff --git a/src/sftp.c b/src/sftp.c index 0c7e5777..3445a270 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -3305,4 +3305,216 @@ sftp_home_directory(sftp_session sftp, const char *username) return NULL; } +sftp_name_id_map sftp_name_id_map_new(uint32_t count) +{ + sftp_name_id_map map = NULL; + + map = calloc(1, sizeof(struct sftp_name_id_map_struct)); + if (map == NULL) { + return NULL; + } + + map->count = count; + + map->ids = calloc(count, sizeof(uint32_t)); + if (map->ids == NULL) { + SAFE_FREE(map); + return NULL; + } + + map->names = calloc(count, sizeof(char *)); + if (map->names == NULL) { + SAFE_FREE(map->ids); + SAFE_FREE(map); + return NULL; + } + + return map; +} + +void sftp_name_id_map_free(sftp_name_id_map map) +{ + if (map == NULL) { + return; + } + + SAFE_FREE(map->ids); + + if (map->names != NULL) { + for (uint32_t i = 0; i < map->count; i++) { + SAFE_FREE(map->names[i]); + } + SAFE_FREE(map->names); + } + + SAFE_FREE(map); +} + +static int sftp_buffer_add_ids(ssh_buffer buffer, sftp_name_id_map map) +{ + uint32_t id_count = map ? map->count : 0; + int rc; + + rc = ssh_buffer_pack(buffer, "d", sizeof(uint32_t) * id_count); + if (rc != SSH_OK) { + return -1; + } + + for (uint32_t i = 0; i < id_count; i++) { + rc = ssh_buffer_pack(buffer, "d", map->ids[i]); + if (rc != SSH_OK) { + return -1; + } + } + + return 0; +} + +static int sftp_parse_names(ssh_buffer buffer, sftp_name_id_map map) +{ + uint32_t name_buf_len = 0; + char *name = NULL; + uint32_t id_count = map ? map->count : 0; + int rc; + + rc = ssh_buffer_unpack(buffer, "d", &name_buf_len); + if (rc != SSH_OK) { + return -1; + } + + for (uint32_t i = 0; i < id_count; i++) { + rc = ssh_buffer_unpack(buffer, "s", &name); + if (rc != SSH_OK) { + return -1; + } + + name_buf_len -= strlen(name) + sizeof(uint32_t); + map->names[i] = name; + } + + if (name_buf_len != 0) { + return -1; + } + + return 0; +} + +int sftp_get_users_groups_by_id(sftp_session sftp, + sftp_name_id_map users_map, + sftp_name_id_map groups_map) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + if (sftp == NULL) { + return -1; + } + + /* check if the user has provided the correct arguments */ + if (users_map == NULL && groups_map == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Both users map and groups map cannot be NULL"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, "ds", id, "users-groups-by-id@openssh.com"); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* pack all uids */ + rc = sftp_buffer_add_ids(buffer, users_map); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* pack all gids */ + rc = sftp_buffer_add_ids(buffer, groups_map); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + rc = sftp_parse_names(msg->payload, users_map); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse usernames"); + sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_message_free(msg); + return -1; + } + + rc = sftp_parse_names(msg->payload, groups_map); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse groupnames"); + sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_message_free(msg); + return -1; + } + + sftp_message_free(msg); + return 0; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to get user and " + "group names by id", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + #endif /* WITH_SFTP */