From 37262b98ef32494e449c17784b8977da2d0e9902 Mon Sep 17 00:00:00 2001 From: Anderson Toshiyuki Sasaki Date: Wed, 7 Nov 2018 17:11:04 +0100 Subject: [PATCH] tests: Added test server The server can be configured through command line options or by providing a state structure with the desired values set. Currently supports only password based authentication. Signed-off-by: Anderson Toshiyuki Sasaki --- tests/CMakeLists.txt | 1 + tests/server/CMakeLists.txt | 45 ++ tests/server/test_server/CMakeLists.txt | 38 + tests/server/test_server/default_cb.c | 880 ++++++++++++++++++++++++ tests/server/test_server/default_cb.h | 160 +++++ tests/server/test_server/main.c | 588 ++++++++++++++++ tests/server/test_server/test_server.c | 310 +++++++++ tests/server/test_server/test_server.h | 73 ++ tests/server/torture_server.c | 345 ++++++++++ 9 files changed, 2440 insertions(+) create mode 100644 tests/server/CMakeLists.txt create mode 100644 tests/server/test_server/CMakeLists.txt create mode 100644 tests/server/test_server/default_cb.c create mode 100644 tests/server/test_server/default_cb.h create mode 100644 tests/server/test_server/main.c create mode 100644 tests/server/test_server/test_server.c create mode 100644 tests/server/test_server/test_server.h create mode 100644 tests/server/torture_server.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e350e59b..ad585cda 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -148,6 +148,7 @@ endif (WITH_BENCHMARKS) if (WITH_SERVER AND SERVER_TESTING) add_subdirectory(pkd) + add_subdirectory(server) endif (WITH_SERVER AND SERVER_TESTING) if (FUZZ_TESTING) diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt new file mode 100644 index 00000000..9430fd23 --- /dev/null +++ b/tests/server/CMakeLists.txt @@ -0,0 +1,45 @@ +project(servertests C) + +if (WITH_SERVER AND UNIX AND NOT WIN32) + +find_package(socket_wrapper) + +add_subdirectory(test_server) + +set(LIBSSH_SERVER_TESTS + torture_server +) + +include_directories( + ${LIBSSH_PUBLIC_INCLUDE_DIRS} + ${CMAKE_BINARY_DIR} + test_server +) + +if (ARGP_INCLUDE_DIR) + include_directories(${ARGP_INCLUDE_DIR}) +endif () + +foreach(_SRV_TEST ${LIBSSH_SERVER_TESTS}) + add_cmocka_test(${_SRV_TEST} + SOURCES ${_SRV_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + LINK_LIBRARIES ${TORTURE_LIBRARY} testserver util + ) + + if (OSX) + set_property( + TEST + ${_SRV_TEST} + PROPERTY + ENVIRONMENT DYLD_FORCE_FLAT_NAMESPACE=1;DYLD_INSERT_LIBRARIES=${SOCKET_WRAPPER_LIBRARY}) + else () + set_property( + TEST + ${_SRV_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_ENVIRONMENT}) + endif() +endforeach() + +endif (WITH_SERVER AND UNIX AND NOT WIN32) diff --git a/tests/server/test_server/CMakeLists.txt b/tests/server/test_server/CMakeLists.txt new file mode 100644 index 00000000..c8a37275 --- /dev/null +++ b/tests/server/test_server/CMakeLists.txt @@ -0,0 +1,38 @@ +project(test_server C) + +if (WITH_SERVER AND UNIX AND NOT WIN32) + +find_package(socket_wrapper) + +set(server_SRCS + main.c +) + +add_library(testserver STATIC + test_server.c + default_cb.c) + +set(LIBSSH_SERVER_TESTS +# torture_server_kbdint +) + +include_directories( + ${LIBSSH_PUBLIC_INCLUDE_DIRS} + ${CMAKE_BINARY_DIR} +) + +if (ARGP_INCLUDE_DIR) + include_directories(${ARGP_INCLUDE_DIR}) +endif () + +if (UNIX AND NOT WIN32) + add_executable(test_server ${server_SRCS}) + target_compile_options(test_server PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(test_server + ${LIBSSH_SHARED_LIBRARY} + ${ARGP_LIBRARY} + util + testserver) +endif () + +endif (WITH_SERVER AND UNIX AND NOT WIN32) diff --git a/tests/server/test_server/default_cb.c b/tests/server/test_server/default_cb.c new file mode 100644 index 00000000..41da1117 --- /dev/null +++ b/tests/server/test_server/default_cb.c @@ -0,0 +1,880 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "test_server.h" +#include "default_cb.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBUTIL_H +#include +#endif +#ifdef HAVE_PTY_H +#include +#endif +#ifdef HAVE_UTMP_H +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif + +/* TODO implement proper pam authentication cb */ +int auth_password_cb(UNUSED_PARAM(ssh_session session), + const char *user, + const char *password, + void *userdata) +{ + bool known_user = false; + bool valid_password = false; + + struct session_data_st *sdata; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto null_userdata; + } + + if (sdata->username == NULL) { + fprintf(stderr, "Error: expected username not set\n"); + goto denied; + } + + if (sdata->password == NULL) { + fprintf(stderr, "Error: expected password not set\n"); + goto denied; + } + + printf("Password authentication\n"); + + known_user = !(strcmp(user, sdata->username)); + valid_password = !(strcmp(password, sdata->password)); + + if (known_user && valid_password) { + sdata->authenticated = 1; + sdata->auth_attempts = 0; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + +denied: + sdata->auth_attempts++; +null_userdata: + return SSH_AUTH_DENIED; +} + +#if WITH_GSSAPI +int auth_gssapi_mic_cb(ssh_session session, + UNUSED_PARAM(const char *user), + UNUSED_PARAM(const char *principal), + void *userdata) +{ + ssh_gssapi_creds creds; + struct session_data_st *sdata; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto null_userdata; + } + + printf("GSSAPI authentication\n"); + + creds = ssh_gssapi_get_creds(session); + if (creds != NULL) { + printf("Received some gssapi credentials\n"); + } else { + printf("Not received any forwardable creds\n"); + goto denied; + } + + printf("Authenticated\n"); + + sdata->authenticated = 1; + sdata->auth_attempts = 0; + + return SSH_AUTH_SUCCESS; + +denied: + sdata->auth_attempts++; +null_userdata: + return SSH_AUTH_DENIED; +} +#endif + +int channel_data_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *data, + uint32_t len, + UNUSED_PARAM(int is_stderr), + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + if (len == 0 || cdata->pid < 1 || kill(cdata->pid, 0) < 0) { + rc = SSH_OK; + goto end; + } + + rc = write(cdata->child_stdin, (char *) data, len); + +end: + return rc; +} + +void channel_eof_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_close_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_signal_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *signal), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_exit_status_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(int exit_status), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_exit_signal_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *signal), + UNUSED_PARAM(int core), + UNUSED_PARAM(const char *errmsg), + UNUSED_PARAM(const char *lang), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +int channel_pty_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *term), + int cols, + int rows, + int py, + int px, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + rc = openpty(&cdata->pty_master, + &cdata->pty_slave, + NULL, + NULL, + cdata->winsize); + if (rc != 0) { + fprintf(stderr, "Failed to open pty\n"); + rc = SSH_ERROR; + goto end; + } + + rc = SSH_OK; + +end: + return rc; +} + +int channel_pty_resize_cb(ssh_session session, + ssh_channel channel, + int cols, + int rows, + int py, + int px, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + (void) session; + (void) channel; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + if (cdata->pty_master != -1) { + rc = ioctl(cdata->pty_master, TIOCSWINSZ, cdata->winsize); + goto end; + } + + rc = SSH_ERROR; + +end: + return rc; +} + +void channel_auth_agent_req_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(void *userdata)) +{ + /* TODO */ +} + +void channel_x11_req_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(int single_connection), + UNUSED_PARAM(const char *auth_protocol), + UNUSED_PARAM(const char *auth_cookie), + UNUSED_PARAM(uint32_t screen_number), + UNUSED_PARAM(void *userdata)) +{ + /* TODO */ +} + +static int exec_pty(const char *mode, + const char *command, + struct channel_data_st *cdata) +{ + int rc; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + cdata->pid = fork(); + switch(cdata->pid) { + case -1: + close(cdata->pty_master); + close(cdata->pty_slave); + fprintf(stderr, "Failed to fork\n"); + rc = SSH_ERROR; + goto end; + case 0: + close(cdata->pty_master); + if (login_tty(cdata->pty_slave) != 0) { + exit(1); + } + execl("/bin/sh", "sh", mode, command, NULL); + exit(0); + default: + close(cdata->pty_slave); + /* pty fd is bi-directional */ + cdata->child_stdout = cdata->child_stdin = cdata->pty_master; + } + + rc = SSH_OK; + +end: + return rc; +} + +static int exec_nopty(const char *command, struct channel_data_st *cdata) +{ + int in[2], out[2], err[2]; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto stdin_failed; + } + + /* Do the plumbing to be able to talk with the child process. */ + if (pipe(in) != 0) { + goto stdin_failed; + } + if (pipe(out) != 0) { + goto stdout_failed; + } + if (pipe(err) != 0) { + goto stderr_failed; + } + + switch(cdata->pid = fork()) { + case -1: + goto fork_failed; + case 0: + /* Finish the plumbing in the child process. */ + close(in[1]); + close(out[0]); + close(err[0]); + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + dup2(err[1], STDERR_FILENO); + close(in[0]); + close(out[1]); + close(err[1]); + /* exec the requested command. */ + execl("/bin/sh", "sh", "-c", command, NULL); + exit(0); + } + + close(in[0]); + close(out[1]); + close(err[1]); + + cdata->child_stdin = in[1]; + cdata->child_stdout = out[0]; + cdata->child_stderr = err[0]; + + return SSH_OK; + +fork_failed: + close(err[0]); + close(err[1]); +stderr_failed: + close(out[0]); + close(out[1]); +stdout_failed: + close(in[0]); + close(in[1]); +stdin_failed: + return SSH_ERROR; +} + +int channel_shell_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + if(cdata->pid > 0) { + rc = SSH_ERROR; + goto end; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + rc = exec_pty("-l", NULL, cdata); + goto end; + } + + /* Client requested a shell without a pty, let's pretend we allow that */ + rc = SSH_OK; + +end: + return rc; +} + +int channel_exec_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + const char *command, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + if(cdata->pid > 0) { + rc = SSH_ERROR; + goto end; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + rc = exec_pty("-c", command, cdata); + goto end; + } + + rc = exec_nopty(command, cdata); + +end: + return rc; +} + +int channel_env_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *env_name), + UNUSED_PARAM(const char *env_value), + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + rc = SSH_OK; + +end: + return rc; +} + +int channel_subsystem_request_cb(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + rc = strcmp(subsystem, "sftp"); + if (rc == 0) { + rc = channel_exec_request_cb(session, + channel, + SFTP_SERVER_PATH, + userdata); + goto end; + } + + /* TODO add other subsystems */ + + rc = SSH_ERROR; + +end: + return rc; +} + +int channel_write_wontblock_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(size_t bytes), + UNUSED_PARAM(void *userdata)) +{ + /* TODO */ + + return 0; +} + +ssh_channel channel_new_session_cb(ssh_session session, void *userdata) +{ + struct session_data_st *sdata = NULL; + ssh_channel chan = NULL; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "NULL userdata"); + goto end; + } + + chan = ssh_channel_new(session); + if (chan == NULL) { + fprintf(stderr, "Error creating channel: %s\n", + ssh_get_error(session)); + goto end; + } + + sdata->channel = chan; + +end: + return chan; +} + +#ifdef WITH_PCAP +static void set_pcap(struct session_data_st *sdata, + ssh_session session, + char *pcap_file) +{ + int rc = 0; + + if (sdata == NULL) { + return; + } + + if (pcap_file == NULL) { + return; + } + + sdata->pcap = ssh_pcap_file_new(); + if (sdata->pcap == NULL) { + return; + } + + rc = ssh_pcap_file_open(sdata->pcap, pcap_file); + if (rc == SSH_ERROR) { + fprintf(stderr, "Error opening pcap file\n"); + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; + return; + } + ssh_set_pcap_file(session, sdata->pcap); +} + +static void cleanup_pcap(struct session_data_st *sdata) +{ + if (sdata == NULL) { + return; + } + + if (sdata->pcap == NULL) { + return; + } + + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; +} +#endif + +static int process_stdout(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write(channel, buf, n); + } + } + + return n; +} + +static int process_stderr(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write_stderr(channel, buf, n); + } + } + + return n; +} + +void default_handle_session_cb(ssh_event event, + ssh_session session, + struct server_state_st *state) +{ + int n; + int rc = 0; + + /* Structure for storing the pty size. */ + struct winsize wsize = { + .ws_row = 0, + .ws_col = 0, + .ws_xpixel = 0, + .ws_ypixel = 0 + }; + + /* Our struct holding information about the channel. */ + struct channel_data_st cdata = { + .pid = 0, + .pty_master = -1, + .pty_slave = -1, + .child_stdin = -1, + .child_stdout = -1, + .child_stderr = -1, + .event = NULL, + .winsize = &wsize + }; + + /* Our struct holding information about the session. */ + struct session_data_st sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD + }; + + struct ssh_channel_callbacks_struct default_channel_cb = { + .userdata = &cdata, + .channel_pty_request_function = channel_pty_request_cb, + .channel_pty_window_change_function = channel_pty_resize_cb, + .channel_shell_request_function = channel_shell_request_cb, + .channel_env_request_function = channel_env_request_cb, + .channel_subsystem_request_function = channel_subsystem_request_cb, + .channel_exec_request_function = channel_exec_request_cb, + .channel_data_function = channel_data_cb + }; + + struct ssh_server_callbacks_struct default_server_cb = { + .userdata = &sdata, + .auth_password_function = auth_password_cb, + .channel_open_request_session_function = channel_new_session_cb, +#if WITH_GSSAPI + .auth_gssapi_mic_function = auth_gssapi_mic_cb +#endif + }; + + if (state == NULL) { + fprintf(stderr, "NULL server state provided\n"); + goto end; + } + +#ifdef WITH_PCAP + set_pcap(&sdata, session, state->pcap_file); +#endif + + if (state->expected_username != NULL) { + sdata.username = state->expected_username; + } + + if (state->expected_password != NULL) { + sdata.password = state->expected_password; + } + + /* If callbacks were provided use them. Otherwise, use default callbacks */ + if (state->server_cb != NULL) { + /* TODO check return values */ + ssh_callbacks_init(state->server_cb); + ssh_set_server_callbacks(session, state->server_cb); + } else { + /* TODO check return values */ + ssh_callbacks_init(&default_server_cb); + ssh_set_server_callbacks(session, &default_server_cb); + } + + if (ssh_handle_key_exchange(session) != SSH_OK) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + + /* Set the supported authentication methods */ + if (state->auth_methods) { + ssh_set_auth_methods(session, state->auth_methods); + } else { + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD); + } + + ssh_event_add_session(event, session); + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= state->max_tries || n >= 100) { + return; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "do_poll error: %s\n", ssh_get_error(session)); + return; + } + n++; + } + + /* TODO check return values */ + if (state->channel_cb != NULL) { + ssh_callbacks_init(state->channel_cb); + ssh_set_channel_callbacks(sdata.channel, state->channel_cb); + } else { + ssh_callbacks_init(&default_channel_cb); + ssh_set_channel_callbacks(sdata.channel, &default_channel_cb); + } + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + if (ssh_event_dopoll(event, -1) == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + + /* If child process's stdout/stderr has been registered with the event, + * or the child process hasn't started yet, continue. */ + if (cdata.event != NULL || cdata.pid == 0) { + continue; + } + /* Executed only once, once the child process starts. */ + cdata.event = event; + /* If stdout valid, add stdout to be monitored by the poll event. */ + if (cdata.child_stdout != -1) { + if (ssh_event_add_fd(event, cdata.child_stdout, POLLIN, process_stdout, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stdout to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + + /* If stderr valid, add stderr to be monitored by the poll event. */ + if (cdata.child_stderr != -1){ + if (ssh_event_add_fd(event, cdata.child_stderr, POLLIN, process_stderr, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stderr to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + } while(ssh_channel_is_open(sdata.channel) && + (cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0)); + + close(cdata.pty_master); + close(cdata.child_stdin); + close(cdata.child_stdout); + close(cdata.child_stderr); + + /* Remove the descriptors from the polling context, since they are now + * closed, they will always trigger during the poll calls. */ + ssh_event_remove_fd(event, cdata.child_stdout); + ssh_event_remove_fd(event, cdata.child_stderr); + + /* If the child process exited. */ + if (kill(cdata.pid, 0) < 0 && WIFEXITED(rc)) { + rc = WEXITSTATUS(rc); + ssh_channel_request_send_exit_status(sdata.channel, rc); + /* If client terminated the channel or the process did not exit nicely, + * but only if something has been forked. */ + } else if (cdata.pid > 0) { + kill(cdata.pid, SIGKILL); + } + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } + +end: +#ifdef WITH_PCAP + cleanup_pcap(&sdata); +#endif + return; +} diff --git a/tests/server/test_server/default_cb.h b/tests/server/test_server/default_cb.h new file mode 100644 index 00000000..90388a75 --- /dev/null +++ b/tests/server/test_server/default_cb.h @@ -0,0 +1,160 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#define SSHD_DEFAULT_USER "libssh" +#define SSHD_DEFAULT_PASSWORD "libssh" +#define SSHD_DEFAULT_PORT 2222 +#define SSHD_DEFAULT_ADDRESS "127.0.0.1" +#define SSHD_DEFAULT_PCAP_FILE "debug.server.pcap" + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define BUF_SIZE 1048576 +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) +#define SFTP_SERVER_PATH "/usr/lib/sftp-server" + +#ifdef HAVE_PTY_H +#include +#endif + +/* A userdata struct for channel. */ +struct channel_data_st { + /* pid of the child process the channel will spawn. */ + pid_t pid; + /* For PTY allocation */ + socket_t pty_master; + socket_t pty_slave; + /* For communication with the child process. */ + socket_t child_stdin; + socket_t child_stdout; + /* Only used for subsystem and exec requests. */ + socket_t child_stderr; + /* Event which is used to poll the above descriptors. */ + ssh_event event; + /* Terminal size struct. */ + struct winsize *winsize; +}; + +/* A userdata struct for session. */ +struct session_data_st { + /* Pointer to the channel the session will allocate. */ + ssh_channel channel; + int auth_attempts; + int authenticated; + const char *username; + const char *password; +#ifdef WITH_PCAP + ssh_pcap_file pcap; +#endif +}; + +int auth_password_cb(ssh_session session, const char *user, + const char *password, void *userdata); + +#if WITH_GSSAPI +int auth_gssapi_mic_cb(ssh_session session, const char *user, + const char *principal, void *userdata); +#endif + +int channel_data_cb(ssh_session session, ssh_channel channel, + void *data, uint32_t len, int is_stderr, void *userdata); + +void channel_eof_cb(ssh_session session, ssh_channel channel, + void *userdata); + +void channel_close_cb(ssh_session session, ssh_channel channel, + void *userdata); + +void channel_signal_cb (ssh_session session, + ssh_channel channel, + const char *signal, + void *userdata); + +void channel_exit_status_cb (ssh_session session, + ssh_channel channel, + int exit_status, + void *userdata); + +void channel_exit_signal_cb(ssh_session session, + ssh_channel channel, + const char *signal, + int core, + const char *errmsg, + const char *lang, + void *userdata); + +int channel_pty_request_cb(ssh_session session, ssh_channel channel, + const char *term, int cols, int rows, int py, int px, void *userdata); + +int channel_pty_resize_cb(ssh_session session, ssh_channel channel, + int cols, int rows, int py, int px, void *userdata); + +int channel_shell_request_cb(ssh_session session, ssh_channel channel, + void *userdata); + +void channel_auth_agent_req_callback(ssh_session session, + ssh_channel channel, void *userdata); + +void channel_x11_req_callback(ssh_session session, + ssh_channel channel, + int single_connection, + const char *auth_protocol, + const char *auth_cookie, + uint32_t screen_number, + void *userdata); + +int channel_exec_request_cb(ssh_session session, + ssh_channel channel, + const char *command, + void *userdata); + +int channel_env_request_cb(ssh_session session, + ssh_channel channel, const char *env_name, const char *env_value, + void *userdata); + +int channel_subsystem_request_cb(ssh_session session, + ssh_channel channel, const char *subsystem, + void *userdata); + +int channel_write_wontblock_cb(ssh_session session, + ssh_channel channel, + size_t bytes, + void *userdata); + +ssh_channel channel_new_session_cb(ssh_session session, void *userdata); + +struct ssh_server_callbacks_struct *get_default_server_cb(void); + +void default_handle_session_cb(ssh_event event, ssh_session session, + struct server_state_st *state); diff --git a/tests/server/test_server/main.c b/tests/server/test_server/main.c new file mode 100644 index 00000000..76ad136f --- /dev/null +++ b/tests/server/test_server/main.c @@ -0,0 +1,588 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "test_server.h" +#include "default_cb.h" + +#include + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif + +#include +#include +#include +#include + +struct arguments_st { + char *address; + char *port; + + char *ecdsa_key; + char *dsa_key; + char *ed25519_key; + char *rsa_key; + char *host_key; + + char *verbosity; + char *auth_methods; + bool with_pcap; + + char *pcap_file; + + char *username; + char *password; +}; + +static void free_arguments(struct arguments_st *arguments) +{ + if (arguments == NULL) { + goto end; + } + + SAFE_FREE(arguments->address); + SAFE_FREE(arguments->port); + + SAFE_FREE(arguments->ecdsa_key); + SAFE_FREE(arguments->dsa_key); + SAFE_FREE(arguments->ed25519_key); + SAFE_FREE(arguments->rsa_key); + SAFE_FREE(arguments->host_key); + + SAFE_FREE(arguments->verbosity); + SAFE_FREE(arguments->auth_methods); + SAFE_FREE(arguments->pcap_file); + + SAFE_FREE(arguments->username); + SAFE_FREE(arguments->password); + +end: + return; +} + +#ifdef HAVE_ARGP_H + +static void print_auth_methods(int auth_methods) +{ + printf("auth_methods = \n"); + if (auth_methods & SSH_AUTH_METHOD_NONE) { + printf("\tSSH_AUTH_METHOD_NONE\n"); + } + if (auth_methods & SSH_AUTH_METHOD_PASSWORD) { + printf("\tSSH_AUTH_METHOD_PASSWORD\n"); + } + if (auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { + printf("\tSSH_AUTH_METHOD_PUBLICKEY\n"); + } + if (auth_methods & SSH_AUTH_METHOD_HOSTBASED) { + printf("\tSSH_AUTH_METHOD_HOSTBASED\n"); + } + if (auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { + printf("\tSSH_AUTH_METHOD_INTERACTIVE\n"); + } + if (auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { + printf("\tSSH_AUTH_METHOD_GSSAPI_MIC\n"); + } +} + +static void print_verbosity(int verbosity) +{ + printf("verbosity = "); + switch(verbosity) { + case SSH_LOG_NOLOG: + printf("NO LOG\n"); + break; + case SSH_LOG_WARNING: + printf("WARNING\n"); + break; + case SSH_LOG_PROTOCOL: + printf("PROTOCOL\n"); + break; + case SSH_LOG_PACKET: + printf("PACKET\n"); + break; + case SSH_LOG_FUNCTIONS: + printf("FUNCTIONS\n"); + break; + default: + printf("UNKNOWN\n");; + break; + } +} + +static void print_server_state(struct server_state_st *state) +{ + if (state) { + printf("===================| STATE |=====================\n"); + printf("address = %s\n", + state->address? state->address: "NULL"); + printf("port = %d\n", + state->port? state->port: 0); + printf("=================================================\n"); + printf("ecdsa_key = %s\n", + state->ecdsa_key? state->ecdsa_key: "NULL"); + printf("dsa_key = %s\n", + state->dsa_key? state->dsa_key: "NULL"); + printf("ed25519_key = %s\n", + state->ed25519_key? state->ed25519_key: "NULL"); + printf("rsa_key = %s\n", + state->rsa_key? state->rsa_key: "NULL"); + printf("host_key = %s\n", + state->host_key? state->host_key: "NULL"); + printf("=================================================\n"); + print_auth_methods(state->auth_methods); + print_verbosity(state->verbosity); + printf("with_pcap = %s\n", + state->with_pcap? "TRUE": "FALSE"); + printf("pcap_file = %s\n", + state->pcap_file? state->pcap_file: "NULL"); + printf("=================================================\n"); + printf("username = %s\n", + state->expected_username? state->expected_username: "NULL"); + printf("password = %s\n", + state->expected_password? state->expected_password: "NULL"); + printf("=================================================\n"); + } +} + +static int init_server_state(struct server_state_st *state, + struct arguments_st *arguments) +{ + int rc = 0; + + if (state == NULL) { + rc = SSH_ERROR; + goto end; + } + + /* Initialize server state. The "arguments structure" */ + if (arguments->address) { + state->address = arguments->address; + arguments->address = NULL; + } else { + state->address = strdup(SSHD_DEFAULT_ADDRESS); + if (state->address == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } + + if (arguments->port) { + state->port = atoi(arguments->port); + } else { + state->port = SSHD_DEFAULT_PORT; + } + + if (arguments->ecdsa_key) { + state->ecdsa_key = arguments->ecdsa_key; + arguments->ecdsa_key = NULL; + } else { + state->ecdsa_key = NULL; + } + + if (arguments->dsa_key) { + state->dsa_key = arguments->dsa_key; + arguments->dsa_key = NULL; + } else { + state->dsa_key = NULL; + } + + if (arguments->ed25519_key) { + state->ed25519_key = arguments->ed25519_key; + arguments->ed25519_key = NULL; + } else { + state->ed25519_key = NULL; + } + + if (arguments->rsa_key) { + state->rsa_key = arguments->rsa_key; + arguments->rsa_key = NULL; + } else { + state->rsa_key = NULL; + } + + if (arguments->host_key) { + state->host_key = arguments->host_key; + arguments->host_key = NULL; + } else { + state->host_key = NULL; + } + + if (arguments->username) { + state->expected_username = arguments->username; + arguments->username = NULL; + } else { + state->expected_username = strdup(SSHD_DEFAULT_USER); + if (state->expected_username == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } + + if (arguments->password) { + state->expected_password = arguments->password; + arguments->password = NULL; + } else { + state->expected_password = strdup(SSHD_DEFAULT_PASSWORD); + if (state->expected_password == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } + + if (arguments->verbosity) { + state->verbosity = atoi(arguments->verbosity); + } else { + state->verbosity = 0; + } + + if (arguments->auth_methods) { + state->auth_methods = atoi(arguments->auth_methods); + } else { + state->auth_methods = 0; + } + + state->with_pcap = arguments->with_pcap; + + if (arguments->pcap_file) { + state->pcap_file = arguments->pcap_file; + arguments->pcap_file = NULL; + } else { + if (arguments->with_pcap) { + state->pcap_file = strdup(SSHD_DEFAULT_PCAP_FILE); + if (state->pcap_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } else { + state->pcap_file = NULL; + } + } + + /* TODO make configurable */ + state->max_tries = 3; + state->error = 0; + + if (state) { + print_server_state(state); + } + + /* TODO make callbacks configurable through command line ? */ + /* Set callbacks to be used */ + state->handle_session = default_handle_session_cb; + + /* Check required parameters */ + if (state->address == NULL) { + rc = SSH_ERROR; + goto end; + } + +end: + if (rc != 0) { + free_server_state(state); + } + + return rc; +} + +const char *argp_program_version = "libssh test server " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "ecdsakey", + .key = 'c', + .arg = "FILE", + .flags = 0, + .doc = "Set the ECDSA key.", + .group = 0 + }, + { + .name = "dsakey", + .key = 'd', + .arg = "FILE", + .flags = 0, + .doc = "Set the DSA key.", + .group = 0 + }, + { + .name = "ed25519key", + .key = 'e', + .arg = "FILE", + .flags = 0, + .doc = "Set the ed25519 key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the RSA key.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "pcapfile", + .key = 'f', + .arg = "FILE", + .flags = 0, + .doc = "Set the pcap output file.", + .group = 0 + }, + { + .name = "auth-methods", + .key = 'a', + .arg = "METHODS", + .flags = 0, + .doc = "Set supported authentication methods.", + .group = 0 + }, + { + .name = "user", + .key = 'u', + .arg = "USERNAME", + .flags = 0, + .doc = "Set expected username.", + .group = 0 + }, + { + .name = "verbosity", + .key = 'v', + .arg = "VERBOSITY", + .flags = 0, + .doc = "Set output verbosity [0-4].", + .group = 0 + }, + { + .name = "with-pcap", + .key = 'w', + .arg = NULL, + .flags = 0, + .doc = "Use PCAP.", + .group = 0 + }, + { .name = NULL } +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct arguments_st *arguments = state->input; + error_t rc = 0; + + if (arguments == NULL) { + fprintf(stderr, "NULL pointer to arguments structure provided\n"); + rc = EINVAL; + goto end; + } + + switch (key) { + case 'c': + arguments->ecdsa_key = strdup(arg); + if (arguments->ecdsa_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'd': + arguments->dsa_key = strdup(arg); + if (arguments->dsa_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'e': + arguments->ed25519_key = strdup(arg); + if (arguments->ed25519_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'f': + arguments->pcap_file = strdup(arg); + if (arguments->pcap_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'k': + arguments->host_key = strdup(arg); + if (arguments->host_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'a': + arguments->auth_methods = strdup(arg); + if (arguments->auth_methods == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'p': + arguments->port = strdup(arg); + if (arguments->port == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'r': + arguments->rsa_key = strdup(arg); + if (arguments->rsa_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'u': + arguments->username = strdup(arg); + if (arguments->username == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'v': + arguments->verbosity = strdup(arg); + if (arguments->verbosity == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'w': + arguments->with_pcap = true; + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + printf("Too many arguments\n"); + argp_usage(state); + } + arguments->address = strdup(arg); + if (arguments->address == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + printf("Too few arguments\n"); + /* Not enough arguments. */ + argp_usage(state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + +end: + return rc; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; + +#endif /* HAVE_ARGP_H */ + +int main(UNUSED_PARAM(int argc), UNUSED_PARAM(char **argv)) +{ + int rc; + + struct arguments_st arguments = { + .address = NULL, + }; + struct server_state_st state = { + .address = NULL, + }; + +#ifdef HAVE_ARGP_H + argp_parse (&argp, argc, argv, 0, 0, &arguments); +#endif + + /* Initialize the state using default or given parameters */ + rc = init_server_state(&state, &arguments); + if (rc != 0) { + goto free_arguments; + } + + /* Free the arguments used to initialize the state before fork */ + free_arguments(&arguments); + + /* Run the server */ + rc = run_server(&state); + if (rc != 0) { + goto free_state; + } + +free_state: + free_server_state(&state); +free_arguments: + free_arguments(&arguments); + return rc; +} diff --git a/tests/server/test_server/test_server.c b/tests/server/test_server/test_server.c new file mode 100644 index 00000000..93a6f9a0 --- /dev/null +++ b/tests/server/test_server/test_server.c @@ -0,0 +1,310 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "test_server.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +void free_server_state(struct server_state_st *state) +{ + if (state == NULL) { + goto end; + } + + SAFE_FREE(state->address); + + SAFE_FREE(state->ecdsa_key); + SAFE_FREE(state->dsa_key); + SAFE_FREE(state->ed25519_key); + SAFE_FREE(state->rsa_key); + SAFE_FREE(state->host_key); + + SAFE_FREE(state->pcap_file); + + SAFE_FREE(state->expected_username); + SAFE_FREE(state->expected_password); + +end: + return; +} + +/* SIGCHLD handler for cleaning up dead children. */ +static void sigchld_handler(int signo) { + (void) signo; + while (waitpid(-1, NULL, WNOHANG) > 0); +} + +int run_server(struct server_state_st *state) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + ssh_event event = NULL; + + struct sigaction sa = {0}; + + int rc; + + /* Check provided state */ + if (state == NULL) { + fprintf(stderr, "Invalid state\n"); + return SSH_ERROR; + } + + /* Set up SIGCHLD handler. */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + + if (sigaction(SIGCHLD, &sa, NULL) != 0) { + fprintf(stderr, "Failed to register SIGCHLD handler\n"); + return SSH_ERROR; + } + + if (state->address == NULL) { + fprintf(stderr, "Missing bind address\n"); + return SSH_ERROR; + } + + if (state->address == NULL) { + fprintf(stderr, "Missing bind address\n"); + return SSH_ERROR; + } + + sshbind = ssh_bind_new(); + if (sshbind == NULL) { + fprintf(stderr, "Out of memory\n"); + return SSH_ERROR; + } + + if (state->verbosity) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + &state->verbosity); + if (rc != 0) { + fprintf(stderr, + "Error setting verbosity level: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + } + + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_BINDADDR, + state->address); + if (rc != 0) { + fprintf(stderr, + "Error setting bind address: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_BINDPORT, + &(state->port)); + if (rc != 0) { + fprintf(stderr, + "Error setting bind port: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + + if (state->dsa_key != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_DSAKEY, + state->dsa_key); + if (rc != 0) { + fprintf(stderr, + "Error setting DSA key: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + } + + if (state->rsa_key != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_RSAKEY, + state->rsa_key); + if (rc != 0) { + fprintf(stderr, + "Error setting RSA key: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + } + + if (state->ecdsa_key != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_ECDSAKEY, + state->ecdsa_key); + if (rc != 0) { + fprintf(stderr, + "Error setting ECDSA key: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + } + + if (state->host_key) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_HOSTKEY, + state->host_key); + if (rc) { + fprintf(stderr, + "Error setting hostkey: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + } + + rc = ssh_bind_listen(sshbind); + if (rc != 0) { + fprintf(stderr, + "Error listening to socket: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + + printf("Started libssh test server on port %d\n", state->port); + + for (;;) { + session = ssh_new(); + if (session == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto free_sshbind; + } + + /* Blocks until there is a new incoming connection. */ + rc = ssh_bind_accept(sshbind, session); + if (rc != SSH_ERROR) { + pid_t pid = fork(); + + switch(pid) { + case 0: + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + /* Remove socket binding, which allows us to restart the + * parent process, without terminating existing sessions. */ + ssh_bind_free(sshbind); + + event = ssh_event_new(); + if (event != NULL) { + /* Blocks until the SSH session ends by either + * child process exiting, or client disconnecting. */ + state->handle_session(event, session, state); + ssh_event_free(event); + } else { + fprintf(stderr, "Could not create polling context\n"); + } + ssh_disconnect(session); + ssh_free(session); + + free_server_state(state); + + exit(0); + case -1: + fprintf(stderr, "Failed to fork\n"); + } + } else { + fprintf(stderr, + "Error accepting a connection: %s\n", + ssh_get_error(sshbind)); + } + + /* Since the session has been passed to a child fork, do some cleaning + * up at the parent process. */ + ssh_disconnect(session); + ssh_free(session); + } + + rc = 0; + +free_sshbind: + ssh_bind_free(sshbind); + return rc; +} + +pid_t fork_run_server(struct server_state_st *state) +{ + pid_t pid; + int rc; + + char err_str[1024] = {0}; + + struct sigaction sa; + + /* Check provided state */ + if (state == NULL) { + fprintf(stderr, "Invalid state\n"); + return -1; + } + + /* Set up SIGCHLD handler. */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + + if (sigaction(SIGCHLD, &sa, NULL) != 0) { + strerror_r(errno, err_str, 1024); + fprintf(stderr, "Failed to register SIGCHLD handler: %s\n", + err_str); + return -1; + } + + pid = fork(); + switch(pid) { + case 0: + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + + /* The child process starts a server which will listen for connections */ + rc = run_server(state); + if (rc != 0) { + exit(rc); + } + + exit(0); + case -1: + strerror_r(errno, err_str, 1024); + fprintf(stderr, "Failed to fork: %s\n", + err_str); + return -1; + default: + /* Return the child pid */ + return pid; + } +} diff --git a/tests/server/test_server/test_server.h b/tests/server/test_server/test_server.h new file mode 100644 index 00000000..3c249f9e --- /dev/null +++ b/tests/server/test_server/test_server.h @@ -0,0 +1,73 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include +#include +#include + +struct server_state_st { + /* Arguments */ + char *address; + int port; + + char *ecdsa_key; + char *dsa_key; + char *ed25519_key; + char *rsa_key; + char *host_key; + + int verbosity; + int auth_methods; + bool with_pcap; + + char *pcap_file; + + char *expected_username; + char *expected_password; + + /* State */ + int max_tries; + int error; + + struct ssh_server_callbacks_struct *server_cb; + struct ssh_channel_callbacks_struct *channel_cb; + + /* Callback to handle the session, should block until disconnected */ + void (*handle_session)(ssh_event event, + ssh_session session, + struct server_state_st *state); +}; + +/*TODO: Add documentation */ +void free_server_state(struct server_state_st *state); + +/*TODO: Add documentation */ +int run_server(struct server_state_st *state); + +/*TODO: Add documentation */ +pid_t fork_run_server(struct server_state_st *state); diff --git a/tests/server/torture_server.c b/tests/server/torture_server.c new file mode 100644 index 00000000..b747bed9 --- /dev/null +++ b/tests/server/torture_server.c @@ -0,0 +1,345 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include "test_server.h" +#include "default_cb.h" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; +}; + +static int setup_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; +#ifdef HAVE_DSA + char dsa_hostkey[1024]; +#endif /* HAVE_DSA */ + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + //char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + struct stat sb; + + const char *sftp_server_locations[] = { + "/usr/lib/ssh/sftp-server", + "/usr/libexec/sftp-server", + "/usr/libexec/openssh/sftp-server", + "/usr/lib/openssh/sftp-server", /* Debian */ + }; + + size_t sftp_sl_size = ARRAY_SIZE(sftp_server_locations); + const char *sftp_server; + + size_t i; + int rc; + + char pid_str[1024]; + + pid_t pid; + + assert_non_null(state); + + tss = (struct test_server_st*)malloc(sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0, 0)); + +#ifdef HAVE_DSA + snprintf(dsa_hostkey, + sizeof(dsa_hostkey), + "%s/sshd/ssh_host_dsa_key", + s->socket_dir); + torture_write_file(dsa_hostkey, torture_get_testkey(SSH_KEYTYPE_DSS, 0, 0)); +#endif /* HAVE_DSA */ + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA, 521, 0)); + + sftp_server = getenv("TORTURE_SFTP_SERVER"); + if (sftp_server == NULL) { + for (i = 0; i < sftp_sl_size; i++) { + sftp_server = sftp_server_locations[i]; + rc = lstat(sftp_server, &sb); + if (rc == 0) { + break; + } + } + } + assert_non_null(sftp_server); + + /* Create default server state */ + ss = (struct server_state_st *)malloc(sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + +#ifdef HAVE_DSA + ss->dsa_key = strdup(dsa_hostkey); + assert_non_null(ss->dsa_key); +#endif /* HAVE_DSA */ + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ed25519_hostkey); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + ss->verbosity = torture_libssh_verbosity(); + + ss->auth_methods = SSH_AUTH_METHOD_PASSWORD; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Start the server using the default values */ + pid = fork_run_server(ss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + //rc = torture_wait_for_daemon(5); + //assert_int_equal(rc, 0); + + /* TODO properly wait for the server (use ping approach) */ + /* Wait 200ms */ + usleep(200 * 1000); + + tss->state = s; + tss->ss = ss; + + *state = tss; + + return 0; +} + +static int teardown_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_server_auth_password(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* TODO: implement proper pam authentication function */ + /* Using the default user for the server */ + 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); + + /* TODO: implement proper pam authentication function */ + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_server_auth_password, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + + ssh_finalize(); + + return rc; +}