summaryrefslogtreecommitdiffstats
path: root/tests/pkd/pkd_daemon.c
diff options
context:
space:
mode:
authorJon Simons <jon@jonsimons.org>2014-10-10 12:38:45 -0700
committerAndreas Schneider <asn@cryptomilk.org>2014-10-12 15:01:02 +0200
commit9dc932c02b6d88b380fdaee0b7184b23e2b85c2f (patch)
treea8b4e4152b218be2f00b4f425c534db16643651f /tests/pkd/pkd_daemon.c
parentaaae6cd97d3b99061fcf5444d491895690fb4cde (diff)
downloadlibssh-9dc932c02b6d88b380fdaee0b7184b23e2b85c2f.tar.gz
libssh-9dc932c02b6d88b380fdaee0b7184b23e2b85c2f.tar.xz
libssh-9dc932c02b6d88b380fdaee0b7184b23e2b85c2f.zip
tests: introduce pkd_hello
Introduce a sample public-key testing daemon to the 'pkd' test directory, and add support code for cycling through various combinations of different key exchange, cipher, and MAC algorithms. The goal of the 'pkd_hello' test is to make it easy to test interactions between non-libssh clients and a libssh-server, and to provide a starting point for testing new implementations for key types, ciphers, MACs, and so on. The thinking is that testing a new algorithm should be as simple as adding a new line for it in the PKDTESTS_* lists. Macros are used to generate the tests and helper functions for a couple of clients -- here, OpenSSH and dropbear are included for the first cut. If binaries are found for these clients, their test lists will be enabled; when binaries are not found for a given client, those tests are skipped. Tests are run in one large batch by default, but can also be run individually to help with tracking down things like signature bugs that may take many iterations to reproduce. Each test logs its stdout and stderr to its own file, which is cleaned up when a test succeeds. For failures, those logs can be combined with verbose libssh output from pkd itself to start debugging things. Some example usages: pkd_hello Run all tests with default number of iterations. pkd_hello --list List available individual test names. pkd_hello -i 1000 -t torture_pkd_openssh_ecdsa_256_ecdh_sha2_nistp256 Run only the torture_pkd_openssh_ecdsa_256_ecdh_sha2_nistp256 testcase 1000 times. pkd_hello -v -v -v -v -e -o Run all tests with maximum libssh and pkd logging. Included in the tests are passes for all existing kex, cipher, and MAC algorithms. BUG: https://red.libssh.org/issues/144 Signed-off-by: Jon Simons <jon@jonsimons.org> Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
Diffstat (limited to 'tests/pkd/pkd_daemon.c')
-rw-r--r--tests/pkd/pkd_daemon.c497
1 files changed, 497 insertions, 0 deletions
diff --git a/tests/pkd/pkd_daemon.c b/tests/pkd/pkd_daemon.c
new file mode 100644
index 00000000..de4e5369
--- /dev/null
+++ b/tests/pkd/pkd_daemon.c
@@ -0,0 +1,497 @@
+/*
+ * pkd_daemon.c -- a sample public-key testing daemon using libssh
+ *
+ * Uses public key authentication to establish an exec channel and
+ * echo back payloads to the user.
+ *
+ * (c) 2014 Jon Simons
+ */
+
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <libssh/callbacks.h>
+#include <libssh/libssh.h>
+#include <libssh/server.h>
+
+#include "pkd_daemon.h"
+
+#include <setjmp.h> // for cmocka
+#include <cmocka.h>
+
+static int pkdout_enabled;
+static int pkderr_enabled;
+
+static void pkdout(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2);
+static void pkderr(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2);
+
+static void pkdout(const char *fmt, ...) {
+ va_list vargs;
+ if (pkdout_enabled) {
+ va_start(vargs, fmt);
+ vfprintf(stdout, fmt, vargs);
+ va_end(vargs);
+ }
+}
+
+static void pkderr(const char *fmt, ...) {
+ va_list vargs;
+ if (pkderr_enabled) {
+ va_start(vargs, fmt);
+ vfprintf(stderr, fmt, vargs);
+ va_end(vargs);
+ }
+}
+
+/*
+ * pkd state: only one thread can run pkd at a time ---------------------
+ */
+
+static struct {
+ int rc;
+ pthread_t tid;
+ int keep_going;
+ int pkd_ready;
+} ctx;
+
+static struct {
+ int server_fd;
+ int req_exec_received;
+ int close_received;
+ int eof_received;
+} pkd_state;
+
+static void pkd_sighandler(int signum) {
+ (void) signum;
+}
+
+static int pkd_init_libssh() {
+ int rc = ssh_threads_set_callbacks(ssh_threads_get_pthread());
+ return (rc == SSH_OK) ? 0 : 1;
+}
+
+static int pkd_init_server_fd(short port) {
+ int rc = 0;
+ int yes = 1;
+ struct sockaddr_in addr;
+
+ int server_fd = socket(PF_INET, SOCK_STREAM, 0);
+ if (server_fd < 0) {
+ rc = -1;
+ goto out;
+ }
+
+ rc = setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
+ if (rc != 0) {
+ goto outclose;
+ }
+
+ memset(&addr, 0x0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr.s_addr = INADDR_ANY;
+ rc = bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (rc != 0) {
+ goto outclose;
+ }
+
+ rc = listen(server_fd, 128);
+ if (rc == 0) {
+ goto out;
+ }
+
+outclose:
+ close(server_fd);
+ server_fd = -1;
+out:
+ pkd_state.server_fd = server_fd;
+ return rc;
+}
+
+static int pkd_accept_fd() {
+ int fd = -1;
+ struct sockaddr_in addr;
+ socklen_t len = sizeof(addr);
+
+ do {
+ fd = accept(pkd_state.server_fd, (struct sockaddr *) &addr, &len);
+ } while ((ctx.keep_going != 0) && (fd < 0) && (errno == EINTR));
+
+ return fd;
+}
+
+static void pkd_eof(ssh_session session,
+ ssh_channel channel,
+ void *userdata) {
+ (void) session;
+ (void) channel;
+ (void) userdata;
+ pkdout("pkd_eof\n");
+ pkd_state.eof_received = 1;
+}
+
+static void pkd_chan_close(ssh_session session,
+ ssh_channel channel,
+ void *userdata) {
+ (void) session;
+ (void) channel;
+ (void) userdata;
+ pkdout("pkd_chan_close\n");
+ pkd_state.close_received = 1;
+}
+
+static int pkd_req_exec(ssh_session s,
+ ssh_channel c,
+ const char *cmd,
+ void *userdata) {
+ (void) s;
+ (void) c;
+ (void) cmd;
+ (void) userdata;
+ /* assumes pubkey authentication has already succeeded */
+ pkdout("pkd_req_exec\n");
+ pkd_state.req_exec_received = 1;
+ return 0;
+}
+
+/* assumes there is only ever a single channel */
+static struct ssh_channel_callbacks_struct pkd_channel_cb = {
+ .channel_eof_function = pkd_eof,
+ .channel_close_function = pkd_chan_close,
+ .channel_exec_request_function = pkd_req_exec,
+};
+
+static int pkd_auth_pubkey_cb(ssh_session s,
+ const char *user,
+ ssh_key key,
+ char state,
+ void *userdata) {
+ (void) s;
+ (void) user;
+ (void) key;
+ (void) state;
+ (void) userdata;
+ pkdout("pkd_auth_pubkey_cb keytype %s, state: %d\n",
+ ssh_key_type_to_char(ssh_key_type(key)), state);
+ if ((state == SSH_PUBLICKEY_STATE_NONE) ||
+ (state == SSH_PUBLICKEY_STATE_VALID)) {
+ return SSH_AUTH_SUCCESS;
+ }
+ return SSH_AUTH_DENIED;
+}
+
+static int pkd_service_request_cb(ssh_session session,
+ const char *service,
+ void *userdata) {
+ (void) session;
+ (void) userdata;
+ pkdout("pkd_service_request_cb: %s\n", service);
+ return (0 == (strcmp(service, "ssh-userauth"))) ? 0 : -1;
+}
+
+static ssh_channel pkd_channel_openreq_cb(ssh_session s,
+ void *userdata) {
+ ssh_channel c = NULL;
+ ssh_channel *out = (ssh_channel *) userdata;
+
+ /* assumes pubkey authentication has already succeeded */
+ pkdout("pkd_channel_openreq_cb\n");
+
+ c = ssh_channel_new(s);
+ if (c == NULL) {
+ pkderr("ssh_channel_new: %s\n", ssh_get_error(s));
+ return NULL;
+ }
+
+ ssh_callbacks_init(&pkd_channel_cb);
+ pkd_channel_cb.userdata = userdata;
+ if (ssh_set_channel_callbacks(c, &pkd_channel_cb) != SSH_OK) {
+ pkderr("ssh_set_channel_callbacks: %s\n", ssh_get_error(s));
+ ssh_channel_free(c);
+ c = NULL;
+ }
+
+ *out = c;
+
+ return c;
+}
+
+static struct ssh_server_callbacks_struct pkd_server_cb = {
+ .auth_pubkey_function = pkd_auth_pubkey_cb,
+ .service_request_function = pkd_service_request_cb,
+ .channel_open_request_session_function = pkd_channel_openreq_cb,
+};
+
+static int pkd_exec_hello(int fd, struct pkd_daemon_args *args) {
+ int rc = -1;
+ ssh_bind b = NULL;
+ ssh_session s = NULL;
+ ssh_event e = NULL;
+ ssh_channel c = NULL;
+ enum ssh_bind_options_e opts = -1;
+
+ int level = args->opts.libssh_log_level;
+ enum pkd_hostkey_type_e type = args->type;
+ const char *hostkeypath = args->hostkeypath;
+
+ pkd_state.eof_received = 0;
+ pkd_state.close_received = 0;
+ pkd_state.req_exec_received = 0;
+
+ b = ssh_bind_new();
+ if (b == NULL) {
+ pkderr("ssh_bind_new\n");
+ goto outclose;
+ }
+
+ if (type == PKD_RSA) {
+ opts = SSH_BIND_OPTIONS_RSAKEY;
+ } else if (type == PKD_DSA) {
+ opts = SSH_BIND_OPTIONS_DSAKEY;
+ } else if (type == PKD_ECDSA) {
+ opts = SSH_BIND_OPTIONS_ECDSAKEY;
+ } else {
+ pkderr("unknown kex algorithm: %d\n", type);
+ rc = -1;
+ goto outclose;
+ }
+
+ rc = ssh_bind_options_set(b, opts, hostkeypath);
+ if (rc != 0) {
+ pkderr("ssh_bind_options_set: %s\n", ssh_get_error(b));
+ goto outclose;
+ }
+
+ rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_LOG_VERBOSITY, &level);
+ if (rc != 0) {
+ pkderr("ssh_bind_options_set log verbosity: %s\n", ssh_get_error(b));
+ goto outclose;
+ }
+
+ s = ssh_new();
+ if (s == NULL) {
+ pkderr("ssh_new\n");
+ goto outclose;
+ }
+
+ /*
+ * ssh_bind_accept loads host key as side-effect. If this
+ * succeeds, the given 'fd' will be closed upon 'ssh_free(s)'.
+ */
+ rc = ssh_bind_accept_fd(b, s, fd);
+ if (rc != SSH_OK) {
+ pkderr("ssh_bind_accept_fd: %s\n", ssh_get_error(b));
+ goto outclose;
+ }
+
+ /* accept only publickey-based auth */
+ ssh_set_auth_methods(s, SSH_AUTH_METHOD_PUBLICKEY);
+
+ /* initialize callbacks */
+ ssh_callbacks_init(&pkd_server_cb);
+ pkd_server_cb.userdata = &c;
+ rc = ssh_set_server_callbacks(s, &pkd_server_cb);
+ if (rc != SSH_OK) {
+ pkderr("ssh_set_server_callbacks: %s\n", ssh_get_error(s));
+ goto out;
+ }
+
+ /* first do key exchange */
+ rc = ssh_handle_key_exchange(s);
+ if (rc != SSH_OK) {
+ pkderr("ssh_handle_key_exchange: %s\n", ssh_get_error(s));
+ goto out;
+ }
+
+ /* setup and pump event to carry out exec channel */
+ e = ssh_event_new();
+ if (e == NULL) {
+ pkderr("ssh_event_new\n");
+ goto out;
+ }
+
+ rc = ssh_event_add_session(e, s);
+ if (rc != SSH_OK) {
+ pkderr("ssh_event_add_session\n");
+ goto out;
+ }
+
+ /* poll until exec channel established */
+ while ((ctx.keep_going != 0) &&
+ (rc != SSH_ERROR) && (pkd_state.req_exec_received == 0)) {
+ rc = ssh_event_dopoll(e, -1 /* infinite timeout */);
+ }
+
+ if (rc == SSH_ERROR) {
+ pkderr("ssh_event_dopoll\n");
+ goto out;
+ } else if (c == NULL) {
+ pkderr("poll loop exited but exec channel not ready\n");
+ rc = -1;
+ goto out;
+ }
+
+ rc = ssh_channel_write(c, "hello\n", 6); /* XXX: customizable payloads */
+ if (rc != 6) {
+ pkderr("ssh_channel_write partial (%d)\n", rc);
+ }
+
+ rc = ssh_channel_request_send_exit_status(c, 0);
+ if (rc != SSH_OK) {
+ pkderr("ssh_channel_request_send_exit_status: %s\n",
+ ssh_get_error(s));
+ goto out;
+ }
+
+ rc = ssh_channel_send_eof(c);
+ if (rc != SSH_OK) {
+ pkderr("ssh_channel_send_eof: %s\n", ssh_get_error(s));
+ goto out;
+ }
+
+ rc = ssh_channel_close(c);
+ if (rc != SSH_OK) {
+ pkderr("ssh_channel_close: %s\n", ssh_get_error(s));
+ goto out;
+ }
+
+ while ((ctx.keep_going != 0) &&
+ (pkd_state.eof_received == 0) &&
+ (pkd_state.close_received == 0) &&
+ (ssh_channel_is_closed(c) == 0)) {
+ rc = ssh_event_dopoll(e, 1000 /* milliseconds */);
+ if (rc == SSH_ERROR) {
+ pkderr("ssh_event_dopoll for eof + close: %s\n", ssh_get_error(s));
+ break;
+ } else {
+ rc = 0;
+ }
+ }
+ goto out;
+
+outclose:
+ close(fd);
+out:
+ if (c != NULL) {
+ ssh_channel_free(c);
+ }
+ if (e != NULL) {
+ ssh_event_remove_session(e, s);
+ ssh_event_free(e);
+ }
+ if (s != NULL) {
+ ssh_disconnect(s);
+ ssh_free(s);
+ }
+ if (b != NULL) {
+ ssh_bind_free(b);
+ }
+ return rc;
+}
+
+/*
+ * main loop ------------------------------------------------------------
+ */
+
+static void *pkd_main(void *args) {
+ int rc = -1;
+ struct pkd_daemon_args *a = (struct pkd_daemon_args *) args;
+
+ struct sigaction act = { .sa_handler = pkd_sighandler, };
+
+ pkd_state.server_fd = -1;
+ pkd_state.req_exec_received = 0;
+ pkd_state.close_received = 0;
+ pkd_state.eof_received = 0;
+
+ /* SIGUSR1 is used to interrupt 'pkd_accept_fd'. */
+ rc = sigaction(SIGUSR1, &act, NULL);
+ if (rc != 0) {
+ pkderr("sigaction: %d\n", rc);
+ goto out;
+ }
+
+ rc = pkd_init_libssh();
+ if (rc != 0) {
+ pkderr("pkd_init_libssh: %d\n", rc);
+ goto out;
+ }
+
+ rc = pkd_init_server_fd(1234);
+ if (rc != 0) {
+ pkderr("pkd_init_server_fd: %d\n", rc);
+ goto out;
+ }
+
+ ctx.pkd_ready = 1;
+
+ while (ctx.keep_going != 0) {
+ int fd = pkd_accept_fd();
+ if (fd < 0) {
+ if (ctx.keep_going != 0) {
+ pkderr("pkd_accept_fd");
+ rc = -1;
+ } else {
+ rc = 0;
+ }
+ break;
+ }
+
+ rc = pkd_exec_hello(fd, a);
+ if (rc != 0) {
+ pkderr("pkd_exec_hello: %d\n", rc);
+ break;
+ }
+ }
+
+ close(pkd_state.server_fd);
+ pkd_state.server_fd = -1;
+out:
+ ctx.rc = rc;
+
+ return NULL;
+}
+
+/*
+ * pkd start and stop used by setup/teardown test scaffolding -----------
+ */
+
+int pkd_start(struct pkd_daemon_args *args) {
+ int rc = 0;
+
+ pkdout_enabled = args->opts.log_stdout;
+ pkderr_enabled = args->opts.log_stderr;
+
+ /* Initialize the pkd context. */
+ ctx.rc = -1;
+ ctx.keep_going = 1;
+ ctx.pkd_ready = 0;
+ rc = pthread_create(&ctx.tid, NULL, &pkd_main, args);
+ assert_int_equal(rc, 0);
+
+ /* Busy-spin until pkd thread is ready. */
+ while (ctx.pkd_ready == 0);
+
+ return rc;
+}
+
+void pkd_stop(struct pkd_result *out) {
+ int rc = 0;
+
+ ctx.keep_going = 0;
+
+ rc = pthread_kill(ctx.tid, SIGUSR1);
+ assert_int_equal(rc, 0);
+
+ rc = pthread_join(ctx.tid, NULL);
+ assert_int_equal(rc, 0);
+
+ assert_non_null(out);
+ out->ok = (ctx.rc == 0);
+
+ return;
+}