1
1
libssh/tests/pkd/pkd_daemon.c
Juraj Vijtiuk 778652460f add mbedtls crypto support
Summary:
This patch adds support for mbedTLS as a crypto backend for libssh.
mbedTLS is an SSL/TLS library that has been designed to mainly be used
in embedded systems.  It is loosely coupled and has a low memory
footprint.  mbedTLS also provides a cryptography library (libmbedcrypto)
that can be used without the TLS modules.
The patch is unfortunately quite big, since several new files had to
be added.
DSA is disabled at compile time, since mbedTLS doesn't support DSA
Patch review and feedback would be appreciated, and if any issues or
suggestions appear, I'm willing to work on them.

Signed-off-by: Juraj Vijtiuk <juraj.vijtiuk@sartura.hr>

Test Plan:
* The patch has been tested with a Debug and MinSizeRel build, with
libssh unit tests, client tests and the pkd tests.
* All the tests have been run with valgrind's memcheck, drd and helgrind
tools.
* The examples/samplessh client works when built with the patch.

Reviewers: asn, aris

Subscribers: simonsj

Differential Revision: https://bugs.libssh.org/D1
2017-12-28 11:17:39 +01:00

516 строки
12 KiB
C

/*
* 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 <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.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;
volatile 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;
#ifdef HAVE_DSA
} else if (type == PKD_DSA) {
opts = SSH_BIND_OPTIONS_DSAKEY;
#endif
} 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)) {
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;
}
}
while ((ctx.keep_going != 0) &&
(ssh_is_connected(s))) {
rc = ssh_event_dopoll(e, 1000 /* milliseconds */);
if (rc == SSH_ERROR) {
pkderr("ssh_event_dopoll for session connection: %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;
}
}
if (pkd_state.server_fd != -1) {
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;
close(pkd_state.server_fd);
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;
}