From 21627509f5279a76dcfc1615de44ddcb4c50cd76 Mon Sep 17 00:00:00 2001 From: Abdelrahman Youssef Date: Thu, 11 Apr 2024 13:30:33 +0200 Subject: [PATCH] support for setstat on server Signed-off-by: Abdelrahman Youssef Reviewed-by: Jakub Jelen --- src/sftpserver.c | 109 +++++++++++++++++++++++++++++- tests/server/torture_sftpserver.c | 91 +++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 1 deletion(-) diff --git a/src/sftpserver.c b/src/sftpserver.c index 7d8070b1..bceb28f7 100644 --- a/src/sftpserver.c +++ b/src/sftpserver.c @@ -31,10 +31,19 @@ #include #include #endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#ifdef HAVE_SYS_UTIME_H +#include +#endif /* HAVE_SYS_UTIME_H */ + #include #include #include #include +#include #include #include "libssh/libssh.h" @@ -829,6 +838,7 @@ SSH_SFTP_CALLBACK(process_readlink); SSH_SFTP_CALLBACK(process_symlink); SSH_SFTP_CALLBACK(process_remove); SSH_SFTP_CALLBACK(process_extended_statvfs); +SSH_SFTP_CALLBACK(process_setstat); const struct sftp_message_handler message_handlers[] = { {"open", NULL, SSH_FXP_OPEN, process_open}, @@ -837,7 +847,7 @@ const struct sftp_message_handler message_handlers[] = { {"write", NULL, SSH_FXP_WRITE, process_write}, {"lstat", NULL, SSH_FXP_LSTAT, process_lstat}, {"fstat", NULL, SSH_FXP_FSTAT, process_unsupposed}, - {"setstat", NULL, SSH_FXP_SETSTAT, process_unsupposed}, + {"setstat", NULL, SSH_FXP_SETSTAT, process_setstat}, {"fsetstat", NULL, SSH_FXP_FSETSTAT, process_unsupposed}, {"opendir", NULL, SSH_FXP_OPENDIR, process_opendir}, {"readdir", NULL, SSH_FXP_READDIR, process_readdir}, @@ -1437,6 +1447,103 @@ process_stat(sftp_client_message client_msg) return ret; } +static int +process_setstat(sftp_client_message client_msg) +{ + int rv; + int ret = SSH_OK; + int status = SSH_FX_OK; + uint32_t msg_flags = client_msg->attr->flags; + const char *filename = sftp_client_message_get_filename(client_msg); + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing setstat %s", filename); + + if (filename == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + if (msg_flags & SSH_FILEXFER_ATTR_SIZE) { + rv = truncate(filename, client_msg->attr->size); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "changing size failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return rv; + } + } + + if (msg_flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rv = chmod(filename, client_msg->attr->permissions); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "chmod failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return rv; + } + } + + if (msg_flags & SSH_FILEXFER_ATTR_UIDGID) { + rv = chown(filename, client_msg->attr->uid, client_msg->attr->gid); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "chwon failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return rv; + } + } + + if (msg_flags & SSH_FILEXFER_ATTR_ACMODTIME) { +#ifdef HAVE_SYS_TIME_H + struct timeval tv[2]; + + tv[0].tv_sec = client_msg->attr->atime; + tv[0].tv_usec = 0; + tv[1].tv_sec = client_msg->attr->mtime; + tv[1].tv_usec = 0; + + rv = utimes(filename, tv); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "utimes failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return rv; + } +#else + struct _utimbuf tf; + + tf.actime = client_msg->attr->atime; + tf.modtime = client_msg->attr->mtime; + + rv = _utime(filename, &tf); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "utimes failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return rv; + } +#endif + } + + sftp_reply_status(client_msg, status, NULL); + return ret; +} + static int process_readlink(sftp_client_message client_msg) { diff --git a/tests/server/torture_sftpserver.c b/tests/server/torture_sftpserver.c index 275f4508..8786154e 100644 --- a/tests/server/torture_sftpserver.c +++ b/tests/server/torture_sftpserver.c @@ -229,8 +229,10 @@ static int session_setup(void **state) struct test_server_st *tss = *state; struct torture_state *s; int verbosity = torture_libssh_verbosity(); + char template2[] = "/tmp/ssh_torture_XXXXXX"; char *cwd = NULL; char *tmp_dir = NULL; + char *p = NULL; bool b = false; int rc; @@ -243,6 +245,8 @@ static int session_setup(void **state) assert_non_null(cwd); tmp_dir = torture_make_temp_dir(template); + p = mkdtemp(template2); + assert_non_null(p); assert_non_null(tmp_dir); tss->cwd = cwd; @@ -256,6 +260,8 @@ static int session_setup(void **state) s->ssh.tsftp = (struct torture_sftp*)calloc(1, sizeof(struct torture_sftp)); assert_non_null(s->ssh.tsftp); + s->ssh.tsftp->testdir = strdup(p); + assert_non_null(s->ssh.tsftp->testdir); rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); assert_ssh_return_code(s->ssh.session, rc); @@ -332,6 +338,7 @@ static int session_teardown(void **state) s = tss->state; assert_non_null(s); + SAFE_FREE(s->ssh.tsftp->testdir); sftp_free(s->ssh.tsftp->sftp); SAFE_FREE(s->ssh.tsftp); @@ -973,6 +980,87 @@ static void torture_server_sftp_extended(void **state) assert_int_equal(rc, SSH_OK); } +static void +torture_server_sftp_setstat(void **state) +{ + + char name[128] = {0}; + char data[10] = "0123456789"; + int rc; + size_t len; + int atime = 10676, mtime = 13467; + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP; + + struct passwd *pwd = NULL; + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + struct sftp_attributes_struct attr; + sftp_attributes tmp_attr = NULL; + + sftp_session sftp = NULL; + ssh_session session = NULL; + sftp_file new_file = NULL; + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + assert_non_null(tsftp->testdir); + snprintf(name, sizeof(name), "%s/server_setstat_test", tsftp->testdir); + new_file = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(len, sizeof(data)); + rc = sftp_close(new_file); + assert_int_equal(rc, SSH_OK); + + ZERO_STRUCT(attr); + attr.flags = SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS | + SSH_FILEXFER_ATTR_UIDGID | SSH_FILEXFER_ATTR_ACMODTIME; + + attr.size = len; + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + attr.permissions = mode; + attr.atime = atime; + attr.mtime = mtime; + + rc = sftp_setstat(sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(rc, SSH_OK); + + tmp_attr = sftp_stat(sftp, name); + assert_non_null(tmp_attr); + + assert_int_equal(tmp_attr->uid, pwd->pw_uid); + assert_int_equal(tmp_attr->gid, pwd->pw_gid); + + assert_int_equal(len, tmp_attr->size); + assert_int_equal(tmp_attr->permissions & ACCESSPERMS, mode); + assert_int_equal(tmp_attr->mtime, mtime); + assert_int_equal(tmp_attr->atime, atime); + + /*negative tests*/ + rc = sftp_setstat(sftp, "not existing", &attr); + assert_int_equal(rc, SSH_ERROR); + sftp_unlink(sftp, name); + sftp_attributes_free(tmp_attr); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -997,6 +1085,9 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_server_sftp_extended, session_setup_sftp, session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_setstat, + session_setup_sftp, + session_teardown), }; ssh_init();