/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include "pkd_daemon.h" #include // for cmocka #include 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; } } 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; 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; }