From 9c4a15bcb1af24945601765010de707ccb317e1a Mon Sep 17 00:00:00 2001 From: anfanite396 Date: Mon, 29 Jan 2024 14:01:52 +0530 Subject: [PATCH] Add support for OpenSSH extension 'copy-data' on client side. This is in reference to the #168. The commit also includes a test suite to test the added functionality. Signed-off-by: anfanite396 --- include/libssh/sftp.h | 3 + src/sftp.c | 88 +++++++++++++++ tests/client/CMakeLists.txt | 1 + tests/client/torture_sftp_copy_data.c | 152 ++++++++++++++++++++++++++ 4 files changed, 244 insertions(+) create mode 100644 tests/client/torture_sftp_copy_data.c diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index ffa315c39..6161edd55 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -1104,6 +1104,9 @@ LIBSSH_API sftp_limits_t sftp_limits(sftp_session sftp); */ LIBSSH_API void sftp_limits_free(sftp_limits_t limits); +LIBSSH_API int sftp_copy_data(sftp_session sftp, const char *read_from_handle, uint64_t read_from_offset, + uint64_t read_data_length, const char *write_to_handle, uint64_t write_to_offset); + /** * @brief Canonicalize a sftp path. * diff --git a/src/sftp.c b/src/sftp.c index 29341d558..7d7c0d1fe 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -2736,6 +2736,94 @@ sftp_limits_t sftp_limits(sftp_session sftp) return limits; } +int sftp_copy_data(sftp_session sftp, const char *read_from_handle, uint64_t read_from_offset, + uint64_t read_data_length, const char *write_to_handle, uint64_t write_to_offset) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + int temp; + + if (sftp == NULL) + return -1; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -2; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "bdssqqsq", + SSH_FXP_EXTENDED, + id, + "copy-data", + read_from_handle, + read_from_offset, + read_data_length, + write_to_handle, + write_to_offset); + + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -3; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -4; + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -5; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -6; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * Status should be SSH_FX_OK if the command was successful, + * if it didn't, then there was an error + */ + temp = status->status; + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return (temp); + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to copy data", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -7; +} + void sftp_limits_free(sftp_limits_t limits) { if (limits == NULL) { diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index 0e7aa2883..ba572a7c4 100644 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -56,6 +56,7 @@ if (WITH_SFTP) torture_sftp_rename torture_sftp_expand_path torture_sftp_aio + torture_sftp_copy_data ${SFTP_BENCHMARK_TESTS}) endif (WITH_SFTP) diff --git a/tests/client/torture_sftp_copy_data.c b/tests/client/torture_sftp_copy_data.c new file mode 100644 index 000000000..cf432ce8f --- /dev/null +++ b/tests/client/torture_sftp_copy_data.c @@ -0,0 +1,152 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" +#include "string.h" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_copy_data(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + int rc; + + char read_file_handle[128] = {0}; + char write_file_handle[128] = {0}; + + const char *content_1 = "This is the data from the read file."; + char *content_2; + size_t bytes_written; + size_t bytes_read; + + int link1, link2; + char fd1[128]; + char fd2[128]; + + FILE *fd_read; + FILE *fd_write; + + snprintf(read_file_handle, sizeof(read_file_handle), + "%s/libssh_sftp_copy_data_1.txt", t->testdir); + snprintf(write_file_handle, sizeof(write_file_handle), + "%s/libssh_sftp_copy_data_2.txt", t->testdir); + + /* Create a file to read from. */ + fd_read = fopen(read_file_handle, "w"); + if (fd_read == NULL) { + skip(); + } + + bytes_written = fwrite(content_1, 1, strlen(content_1), fd_read); + if (bytes_written != strlen(content_1)){ + skip(); + } + fclose(fd_read); + + link1 = open(read_file_handle, O_RDONLY, S_IRWXU); + link2 = open(write_file_handle, O_CREAT | O_WRONLY, S_IRWXU); + + sprintf(fd1, "%d", link1); + sprintf(fd2, "%d", link2); + + rc = sftp_copy_data(t->sftp, fd1, 0, 0, fd2, 0); + printf("%i %s %s \n", rc, fd1, fd2); + assert_int_equal(rc, 0); + + fd_write = fopen(write_file_handle, "w"); + if (fd_write == NULL){ + skip(); + } + + content_2 = (char *)malloc(bytes_written); + bytes_read = fread(content_2, 1, bytes_written, fd_write); + if (bytes_read != bytes_written){ + assert_int_equal(1, 0); + skip(); + } + assert_string_equal(content_1, content_2); + + fclose(fd_write); + + /* + * Call the function with same read-from-handle and write-from-handle. + * This should fail. + */ + rc = sftp_copy_data(t->sftp, fd1, 0, strlen(content_1), fd1, 0); + assert_int_not_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_copy_data, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} -- GitLab