diff --git a/tests/server/torture_sftpserver.c b/tests/server/torture_sftpserver.c index 8ef67100..21ea3f62 100644 --- a/tests/server/torture_sftpserver.c +++ b/tests/server/torture_sftpserver.c @@ -22,6 +22,7 @@ */ #include "config.h" +#include "libssh/sftp.h" #define LIBSSH_STATIC @@ -263,6 +264,59 @@ static int session_setup(void **state) return 0; } +static int session_setup_sftp(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + ssh_session session; + sftp_session sftp; + int rc; + + assert_non_null(tss); + + rc = session_setup(state); + assert_int_equal(rc, 0); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + ssh_get_issue_banner(session); + + /* init sftp session */ + tsftp = s->ssh.tsftp; + + printf("in establish before sftp_new\n"); + sftp = sftp_new(session); + assert_non_null(sftp); + tsftp->sftp = sftp; + + rc = sftp_init(sftp); + assert_int_equal(rc, SSH_OK); + + return 0; +} + static int session_teardown(void **state) { struct test_server_st *tss = *state; @@ -344,7 +398,6 @@ static void torture_server_establish_sftp(void **state) assert_int_equal(rc, SSH_OK); } - static void torture_server_test_sftp_function(void **state) { struct test_server_st *tss = *state; @@ -406,6 +459,7 @@ static void torture_server_test_sftp_function(void **state) rv_str = sftp_readlink(sftp, "/tmp/sftp_symlink_test"); assert_non_null(rv_str); + ssh_string_free_char(rv_str); rc = sftp_unlink(sftp, "/tmp/sftp_symlink_test"); assert_int_equal(rc, SSH_OK); @@ -438,6 +492,481 @@ static void torture_server_test_sftp_function(void **state) assert_int_equal(rc, SSH_OK); } +static void torture_server_sftp_open_read_write(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_attributes a = NULL; + sftp_file new_file = NULL; + char tmp_file[PATH_MAX] = {0}; + char data[10] = "0123456789"; + char read_data[10] = {0}; + struct stat sb; + int rc, write_len, read_len; + + 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); + + snprintf(tmp_file, sizeof(tmp_file), "%s/newfile", tss->temp_dir); + + /* + * Create a new file + */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0751); + assert_non_null(new_file); + + /* Write should work ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + /* Reading should fail */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, SSH_ERROR); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* Verify locally the mode is correct */ + rc = stat(tmp_file, &sb); + assert_int_equal(rc, 0); + assert_int_equal(sb.st_mode, S_IFREG | 0751); + assert_int_equal(sb.st_size, sizeof(data)); /* 10b written */ + + /* Remote stat */ + a = sftp_stat(sftp, tmp_file); + assert_non_null(a); + assert_int_equal(a->permissions, S_IFREG | 0751); + assert_int_equal(a->size, sizeof(data)); /* 10b written */ + assert_int_equal(a->type, SSH_FILEXFER_TYPE_REGULAR); + sftp_attributes_free(a); + + /* + * Now, lets try O_APPEND, mode is ignored + */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_APPEND, 0); + assert_non_null(new_file); + + /* fstat is not implemented */ + a = sftp_fstat(new_file); + assert_null(a); + + /* Write should work ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + /* Reading should fail */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, SSH_ERROR); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* + * Now, lets try read+write, mode is ignored + */ + new_file = sftp_open(sftp, tmp_file, O_RDWR, 0); + assert_non_null(new_file); + + /* Reading should work */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, sizeof(read_data)); + assert_int_equal(sizeof(read_data), sizeof(data)); /* sanity */ + assert_memory_equal(read_data, data, sizeof(data)); + + rc = sftp_seek(new_file, 20); + assert_ssh_return_code(session, rc); + + /* Write should work also ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* Remove the file */ + rc = sftp_unlink(sftp, tmp_file); + assert_ssh_return_code(session, rc); + + /* again: the file does not exist anymore so we should fail now */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_ERROR); + + /* + * Now, lets try read+write+create + */ + new_file = sftp_open(sftp, tmp_file, O_RDWR | O_CREAT, 0700); + assert_non_null(new_file); + + /* Reading should not fail but return no data */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, 0); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* be nice */ + rc = sftp_unlink(sftp, tmp_file); + assert_ssh_return_code(session, rc); + + /* null flags should be invalid */ + /* but there is no way in libssh client to force null flags so skip this + new_file = sftp_open(sftp, tmp_file, 0, 0700); + assert_null(new_file); + */ + + /* Only O_CREAT is invalid on file which does not exist. Read is implicit */ + new_file = sftp_open(sftp, tmp_file, O_CREAT, 0700); + assert_null(new_file); +} + +static void torture_server_sftp_mkdir(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_file new_file = NULL; + char tmp_dir[PATH_MAX] = {0}; + char tmp_file[PATH_MAX] = {0}; + sftp_attributes a = NULL; + struct stat sb; + int rc; + + 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); + + snprintf(tmp_dir, sizeof(tmp_dir), "%s/newdir", tss->temp_dir); + + /* create a test dir */ + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_ssh_return_code(session, rc); + + /* try the same path again -- we should get an error */ + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_int_equal(rc, SSH_ERROR); + + /* Verify locally the mode is correct */ + rc = stat(tmp_dir, &sb); + assert_int_equal(rc, 0); + assert_int_equal(sb.st_mode, S_IFDIR | 0751); + + /* Remote stat */ + a = sftp_stat(sftp, tmp_dir); + assert_non_null(a); + assert_int_equal(a->permissions, S_IFDIR | 0751); + assert_int_equal(a->type, SSH_FILEXFER_TYPE_DIRECTORY); + sftp_attributes_free(a); + + snprintf(tmp_file, sizeof(tmp_file), "%s/newdir/newfile", tss->temp_dir); + + /* create a file in there */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* remove of non-empty directory fails */ + rc = sftp_rmdir(sftp, tmp_dir); + assert_int_equal(rc, SSH_ERROR); + + /* Unlink can not remove directory either */ + rc = sftp_unlink(sftp, tmp_dir); + assert_int_equal(rc, SSH_ERROR); + + /* Remove the file */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_OK); + + /* Now it should work */ + rc = sftp_rmdir(sftp, tmp_dir); + assert_ssh_return_code(session, rc); +} + +static void torture_server_sftp_realpath(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + char path[PATH_MAX] = {0}; + char exp_path[PATH_MAX] = {0}; + char *new_path = NULL; + + 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); + + /* first try with the empty string, which should be equivalent to CWD */ + new_path = sftp_canonicalize_path(sftp, path); + assert_non_null(new_path); + assert_string_equal(new_path, tss->cwd); + ssh_string_free_char(new_path); + + /* now, lets try some more complicated paths relative to the CWD */ + snprintf(path, sizeof(path), "%s/.././%s", + tss->temp_dir, tss->temp_dir); + new_path = sftp_canonicalize_path(sftp, path); + assert_non_null(new_path); + snprintf(exp_path, sizeof(exp_path), "%s/%s", + tss->cwd, tss->temp_dir); + assert_string_equal(new_path, exp_path); + ssh_string_free_char(new_path); + + /* and this one does not exists, which is an error */ + snprintf(path, sizeof(path), "%s/.././%s/nodir", + tss->temp_dir, tss->temp_dir); + new_path = sftp_canonicalize_path(sftp, path); + assert_null(new_path); + ssh_string_free_char(new_path); +} + +static void torture_server_sftp_symlink(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_file new_file = NULL; + char tmp_dir[PATH_MAX] = {0}; + char tmp_file[PATH_MAX] = {0}; + char path[PATH_MAX] = {0}; + char abs_path[PATH_MAX] = {0}; + char data[42] = "012345678901234567890123456789012345678901"; + char *new_path = NULL; + sftp_attributes a = NULL; + sftp_dir dir; + int write_len, num_files = 0; + int rc; + + 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); + + /* create a test dir */ + snprintf(tmp_dir, sizeof(tmp_dir), "%s/newdir", tss->temp_dir); + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_ssh_return_code(session, rc); + + /* create a file in there */ + snprintf(tmp_file, sizeof(tmp_file), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* now, lets create a (relative) symlink to the new file */ + snprintf(path, sizeof(path), "%s/newdir/linkname", tss->temp_dir); + rc = sftp_symlink(sftp, tmp_file, path); + assert_ssh_return_code(session, rc); + + /* when the destination exists, it should fail */ + rc = sftp_symlink(sftp, tmp_dir, tmp_file); + assert_int_equal(rc, SSH_ERROR); + + /* now, there are different versions of stat that follow symlinks or not */ + /* lstat should not follow the symlink and show information about the link + * itself */ + a = sftp_lstat(sftp, path); + assert_non_null(a); + assert_int_not_equal(a->size, sizeof(data)); + assert_int_equal(a->type, SSH_FILEXFER_TYPE_SYMLINK); + sftp_attributes_free(a); + + /* readlink should give us more information about the target of the symlink + */ + new_path = sftp_readlink(sftp, path); + assert_non_null(new_path); + snprintf(abs_path, sizeof(abs_path), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + assert_string_equal(new_path, abs_path); + ssh_string_free_char(new_path); + + /* stat should follow the symlink and show information about the link + * target */ + a = sftp_stat(sftp, path); + assert_non_null(a); + assert_int_equal(a->size, sizeof(data)); + assert_int_equal(a->permissions, S_IFREG | 0700); + assert_int_equal(a->type, SSH_FILEXFER_TYPE_REGULAR); + sftp_attributes_free(a); + + /* on non-existing path, they fail */ + a = sftp_lstat(sftp, "non-existing"); + assert_null(a); + a = sftp_stat(sftp, "non-existing"); + assert_null(a); + + /**** readdir ****/ + dir = sftp_opendir(sftp, tmp_dir); + assert_non_null(dir); + while ((a = sftp_readdir(sftp, dir))) { + if (strcmp(a->name, ".") != 0 && + strcmp(a->name, "..") != 0 && + strcmp(a->name, "newfile") != 0 && + strcmp(a->name, "linkname") != 0) { + /* There is a file we did not create */ + assert_true(false); + } + + num_files++; + sftp_attributes_free(a); + } + assert_int_equal(num_files, 4); + rc = sftp_dir_eof(dir); + assert_int_equal(rc, 1); + rc = sftp_closedir(dir); + assert_ssh_return_code(session, rc); + + /* now, remove the target of the link, the stat should not handle that, + * while lstat should keep working */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_OK); + + a = sftp_lstat(sftp, path); + assert_non_null(a); + assert_int_not_equal(a->size, sizeof(data)); + sftp_attributes_free(a); + + a = sftp_stat(sftp, path); + assert_null(a); + + /* readlink works ok on broken symlinks */ + new_path = sftp_readlink(sftp, path); + assert_non_null(new_path); + snprintf(abs_path, sizeof(abs_path), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + assert_string_equal(new_path, abs_path); + ssh_string_free_char(new_path); + + /* readlink should fail on directories */ + new_path = sftp_readlink(sftp, tmp_dir); + assert_null(new_path); + /* readlink should fail on or on non-existing files */ + new_path = sftp_readlink(sftp, tmp_file); + assert_null(new_path); + + /* Clean up symlink */ + rc = sftp_unlink(sftp, path); + assert_int_equal(rc, SSH_OK); + /* Clean up temporary directory */ + rc = sftp_rmdir(sftp, tmp_dir); + assert_int_equal(rc, SSH_OK); +} + +static void torture_server_sftp_extended(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_file new_file = NULL; + char tmp_dir[PATH_MAX] = {0}; + char tmp_file[PATH_MAX] = {0}; + sftp_statvfs_t st = NULL; + int rc; + + 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); + + /* create a test dir */ + snprintf(tmp_dir, sizeof(tmp_dir), "%s/newdir", tss->temp_dir); + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_ssh_return_code(session, rc); + + /* create a file in there */ + snprintf(tmp_file, sizeof(tmp_file), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + + /* extended fstatvsf is not advertised nor supported now but calling this + * message will keep hanging the server. The extension protocol says that + * the clients can not request extension that are not supported by the + * server so before doing this, we should use sftp_extension_supported() + * anyway */ + /* st = sftp_fstatvfs(new_file); + assert_null(st); */ + + /* close */ + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* extended statvsf */ + st = sftp_statvfs(sftp, tmp_file); + assert_non_null(st); + /* probably hard to check more */ + sftp_statvfs_free(st); + + /* Clean up temporary directory */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_OK); + rc = sftp_rmdir(sftp, tmp_dir); + assert_int_equal(rc, SSH_OK); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -447,6 +976,21 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_server_test_sftp_function, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_open_read_write, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_mkdir, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_realpath, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_symlink, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_extended, + session_setup_sftp, + session_teardown), }; ssh_init();