server FEATURE ssh session creation functions
Cannot process any messages yet.
diff --git a/src/session_server_ssh.c b/src/session_server_ssh.c
new file mode 100644
index 0000000..5bebd20
--- /dev/null
+++ b/src/session_server_ssh.c
@@ -0,0 +1,859 @@
+/**
+ * \file session_server_ssh.c
+ * \author Michal Vasko <mvasko@cesnet.cz>
+ * \brief libnetconf2 SSH server session manipulation functions
+ *
+ * Copyright (c) 2015 CESNET, z.s.p.o.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. Neither the name of the Company nor the names of its contributors
+ * may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ */
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <crypt.h>
+#include <errno.h>
+
+#include "session_server.h"
+#include "session_p.h"
+
+extern struct nc_server_opts server_opts;
+static struct nc_ssh_server_opts ssh_opts = {
+ .auth_methods = NC_SSH_AUTH_PUBLICKEY | NC_SSH_AUTH_PASSWORD | NC_SSH_AUTH_INTERACTIVE,
+ .auth_attempts = 3,
+ .auth_timeout = 10
+};
+
+API int
+nc_ssh_server_set_hostkeys(const char *dsakey, const char *rsakey, const char *ecdsakey)
+{
+ if (!dsakey && !rsakey && !ecdsakey) {
+ ERRARG;
+ return -1;
+ }
+
+ if (!ssh_opts.sshbind) {
+ ssh_opts.sshbind = ssh_bind_new();
+ if (!ssh_opts.sshbind) {
+ ERR("%s: failed to create a new ssh_bind", __func__);
+ return -1;
+ }
+ }
+
+ if (dsakey) {
+ ssh_bind_options_set(ssh_opts.sshbind, SSH_BIND_OPTIONS_DSAKEY, dsakey);
+ }
+ if (rsakey) {
+ ssh_bind_options_set(ssh_opts.sshbind, SSH_BIND_OPTIONS_RSAKEY, rsakey);
+ }
+ if (ecdsakey) {
+ ssh_bind_options_set(ssh_opts.sshbind, SSH_BIND_OPTIONS_ECDSAKEY, ecdsakey);
+ }
+ return 0;
+}
+
+API int
+nc_ssh_server_set_banner(const char *banner)
+{
+ if (!banner) {
+ ERRARG;
+ return -1;
+ }
+
+ if (!ssh_opts.sshbind) {
+ ssh_opts.sshbind = ssh_bind_new();
+ if (!ssh_opts.sshbind) {
+ ERR("%s: failed to create a new ssh_bind", __func__);
+ return -1;
+ }
+ }
+
+ ssh_bind_options_set(ssh_opts.sshbind, SSH_BIND_OPTIONS_BANNER, banner);
+ return 0;
+}
+
+API int
+nc_ssh_server_set_auth_methods(int auth_methods)
+{
+ if (!(auth_methods & NC_SSH_AUTH_PUBLICKEY) && !(auth_methods & NC_SSH_AUTH_PASSWORD)
+ && !(auth_methods & NC_SSH_AUTH_INTERACTIVE)) {
+ ERRARG;
+ return -1;
+ }
+
+ ssh_opts.auth_methods = auth_methods;
+ return 0;
+}
+
+API int
+nc_ssh_server_set_auth_attempts(uint16_t auth_attempts)
+{
+ if (!auth_attempts) {
+ ERRARG;
+ return -1;
+ }
+
+ ssh_opts.auth_attempts = auth_attempts;
+ return 0;
+}
+
+API int
+nc_ssh_server_set_auth_timeout(uint16_t auth_timeout)
+{
+ if (!auth_timeout) {
+ ERRARG;
+ return -1;
+ }
+
+ ssh_opts.auth_timeout = auth_timeout;
+ return 0;
+}
+
+API int
+nc_ssh_server_add_authkey(const char *keypath, const char *username)
+{
+ if (!keypath || !username) {
+ ERRARG;
+ return -1;
+ }
+
+ ++ssh_opts.authkey_count;
+ ssh_opts.authkeys = realloc(ssh_opts.authkeys, ssh_opts.authkey_count * sizeof *ssh_opts.authkeys);
+
+ ssh_opts.authkeys[ssh_opts.authkey_count - 1].path = strdup(keypath);
+ ssh_opts.authkeys[ssh_opts.authkey_count - 1].username = strdup(username);
+
+ return 0;
+}
+
+API int
+nc_ssh_server_del_authkey(const char *keypath, const char *username)
+{
+ uint32_t i;
+ int ret = -1;
+
+ for (i = 0; i < ssh_opts.authkey_count; ++i) {
+ if ((!keypath || !strcmp(ssh_opts.authkeys[i].path, keypath))
+ && (!username || !strcmp(ssh_opts.authkeys[i].username, username))) {
+ free(ssh_opts.authkeys[i].path);
+ free(ssh_opts.authkeys[i].username);
+
+ --ssh_opts.authkey_count;
+ memmove(&ssh_opts.authkeys[i], &ssh_opts.authkeys[i + 1], (ssh_opts.authkey_count - i) * sizeof *ssh_opts.authkeys);
+
+ ret = 0;
+ }
+ }
+
+ return ret;
+}
+
+API int
+nc_ssh_server_add_bind_listen(const char *address, uint16_t port)
+{
+ int sock;
+
+ if (!address || !port) {
+ ERRARG;
+ return -1;
+ }
+
+ sock = nc_sock_listen(address, port);
+ if (sock == -1) {
+ return -1;
+ }
+
+ ++ssh_opts.bind_count;
+ ssh_opts.binds = realloc(ssh_opts.binds, ssh_opts.bind_count * sizeof *ssh_opts.binds);
+
+ ssh_opts.binds[ssh_opts.bind_count - 1].address = strdup(address);
+ ssh_opts.binds[ssh_opts.bind_count - 1].port = port;
+ ssh_opts.binds[ssh_opts.bind_count - 1].sock = sock;
+
+ return 0;
+}
+
+API int
+nc_ssh_server_del_bind(const char *address, uint16_t port)
+{
+ uint32_t i;
+ int ret = -1;
+
+ for (i = 0; i < ssh_opts.bind_count; ++i) {
+ if ((!address || !strcmp(ssh_opts.binds[i].address, address)) && (!port || (ssh_opts.binds[i].port == port))) {
+ close(ssh_opts.binds[i].sock);
+ free(ssh_opts.binds[i].address);
+
+ --ssh_opts.bind_count;
+ memmove(&ssh_opts.binds[i], &ssh_opts.binds[i + 1], (ssh_opts.bind_count - i) * sizeof *ssh_opts.binds);
+
+ ret = 0;
+ }
+ }
+
+ return ret;
+}
+
+API void
+nc_ssh_server_destroy(void)
+{
+ int i;
+
+ if (ssh_opts.binds) {
+ for (i = 0; i < ssh_opts.bind_count; ++i) {
+ free(ssh_opts.binds[i].address);
+ close(ssh_opts.binds[i].sock);
+ }
+ free(ssh_opts.binds);
+ }
+
+ if (ssh_opts.sshbind) {
+ ssh_bind_free(ssh_opts.sshbind);
+ }
+
+ if (ssh_opts.authkeys) {
+ for (i = 0; i < ssh_opts.authkey_count; ++i) {
+ free(ssh_opts.authkeys[i].path);
+ free(ssh_opts.authkeys[i].username);
+ }
+ free(ssh_opts.authkeys);
+ }
+}
+
+static char *
+auth_password_get_pwd_hash(const char *username)
+{
+ struct passwd *pwd, pwd_buf;
+ struct spwd *spwd, spwd_buf;
+ char *pass_hash = NULL, buf[256];
+
+ getpwnam_r(username, &pwd_buf, buf, 256, &pwd);
+ if (!pwd) {
+ VRB("User '%s' not found locally.", username);
+ return NULL;
+ }
+
+ if (!strcmp(pwd->pw_passwd, "x")) {
+ getspnam_r(username, &spwd_buf, buf, 256, &spwd);
+ if (!spwd) {
+ VRB("Failed to retrieve the shadow entry for \"%s\".", username);
+ return NULL;
+ }
+
+ pass_hash = spwd->sp_pwdp;
+ } else {
+ pass_hash = pwd->pw_passwd;
+ }
+
+ if (!pass_hash) {
+ ERR("%s: no password could be retrieved for \"%s\".", __func__, username);
+ return NULL;
+ }
+
+ /* check the hash structure for special meaning */
+ if (!strcmp(pass_hash, "*") || !strcmp(pass_hash, "!")) {
+ VRB("User \"%s\" is not allowed to authenticate using a password.", username);
+ return NULL;
+ }
+ if (!strcmp(pass_hash, "*NP*")) {
+ VRB("Retrieving password for \"%s\" from a NIS+ server not supported.", username);
+ return NULL;
+ }
+
+ return strdup(pass_hash);
+}
+
+static int
+auth_password_compare_pwd(const char *pass_hash, const char *pass_clear)
+{
+ char *new_pass_hash;
+ struct crypt_data cdata;
+
+ if (!pass_hash[0]) {
+ if (!pass_clear[0]) {
+ WRN("User authentication successful with an empty password!");
+ return 0;
+ } else {
+ /* the user did now know he does not need any password,
+ * (which should not be used) so deny authentication */
+ return 1;
+ }
+ }
+
+ cdata.initialized = 0;
+ new_pass_hash = crypt_r(pass_clear, pass_hash, &cdata);
+ return strcmp(new_pass_hash, pass_hash);
+}
+
+static void
+nc_sshcb_auth_password(struct nc_session *session, ssh_message msg)
+{
+ char *pass_hash;
+
+ pass_hash = auth_password_get_pwd_hash(session->username);
+ if (pass_hash && !auth_password_compare_pwd(pass_hash, ssh_message_auth_password(msg))) {
+ VRB("User '%s' authenticated.", session->username);
+ ssh_message_auth_reply_success(msg, 0);
+ session->flags |= NC_SESSION_SSH_AUTHENTICATED;
+ free(pass_hash);
+ return;
+ }
+
+ free(pass_hash);
+ ++session->auth_attempts;
+ VRB("Failed user '%s' authentication attempt (#%d).", session->username, session->auth_attempts);
+ ssh_message_reply_default(msg);
+}
+
+static void
+nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg)
+{
+ char *pass_hash;
+
+ if (!ssh_message_auth_kbdint_is_response(msg)) {
+ const char *prompts[] = {"Password: "};
+ char echo[] = {0};
+
+ ssh_message_auth_interactive_request(msg, "Interactive SSH Authentication", "Type your password:", 1, prompts, echo);
+ } else {
+ if (ssh_userauth_kbdint_getnanswers(session->ti.libssh.session) != 1) {
+ ssh_message_reply_default(msg);
+ return;
+ }
+ pass_hash = auth_password_get_pwd_hash(session->username);
+ if (!pass_hash) {
+ ssh_message_reply_default(msg);
+ return;
+ }
+ if (!auth_password_compare_pwd(pass_hash, ssh_userauth_kbdint_getanswer(session->ti.libssh.session, 0))) {
+ VRB("User \"%s\" authenticated.", session->username);
+ session->flags |= NC_SESSION_SSH_AUTHENTICATED;
+ ssh_message_auth_reply_success(msg, 0);
+ } else {
+ ++session->auth_attempts;
+ VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->auth_attempts);
+ ssh_message_reply_default(msg);
+ }
+ }
+}
+
+static const char *
+auth_pubkey_compare_key(ssh_key key)
+{
+ uint32_t i;
+ ssh_key pub_key;
+ char *username = NULL;
+
+ for (i = 0; i < ssh_opts.authkey_count; ++i) {
+ if (ssh_pki_import_pubkey_file(ssh_opts.authkeys[i].path, &pub_key) != SSH_OK) {
+ if (eaccess(ssh_opts.authkeys[i].path, R_OK)) {
+ VRB("%s: failed to import the public key \"%s\" (%s)", __func__, ssh_opts.authkeys[i].path, strerror(errno));
+ } else {
+ VRB("%s: failed to import the public key \"%s\" (%s)", __func__, ssh_opts.authkeys[i].path, ssh_get_error(pub_key));
+ }
+ continue;
+ }
+
+ if (!ssh_key_cmp(key, pub_key, SSH_KEY_CMP_PUBLIC)) {
+ ssh_key_free(pub_key);
+ break;
+ }
+
+ ssh_key_free(pub_key);
+ }
+
+ if (i < ssh_opts.authkey_count) {
+ username = ssh_opts.authkeys[i].username;
+ }
+
+ return username;
+}
+
+static void
+nc_sshcb_auth_pubkey(struct nc_session *session, ssh_message msg)
+{
+ const char *username;
+ int signature_state;
+
+ signature_state = ssh_message_auth_publickey_state(msg);
+ if (signature_state == SSH_PUBLICKEY_STATE_VALID) {
+ VRB("User \"%s\" authenticated.", session->username);
+ session->flags |= NC_SESSION_SSH_AUTHENTICATED;
+ ssh_message_auth_reply_success(msg, 0);
+ return;
+
+ } else if (signature_state == SSH_PUBLICKEY_STATE_NONE) {
+ if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) {
+ VRB("User \"%s\" tried to use an unknown (unauthorized) public key.", session->username);
+
+ } else if (strcmp(session->username, username)) {
+ VRB("User \"%s\" is not the username identified with the presented public key.", session->username);
+
+ } else {
+ /* accepting only the use of a public key */
+ ssh_message_auth_reply_pk_ok_simple(msg);
+ return;
+ }
+ }
+
+ ++session->auth_attempts;
+ VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->auth_attempts);
+ ssh_message_reply_default(msg);
+}
+
+static int
+nc_sshcb_channel_open(struct nc_session *session, ssh_channel channel)
+{
+ while (session->ti.libssh.next) {
+ if (session->status == NC_STATUS_STARTING) {
+ ERR("%s: internal error (%s:%d)", __FILE__, __LINE__);
+ return -1;
+ }
+ session = session->ti.libssh.next;
+ }
+
+ if ((session->status != NC_STATUS_STARTING) || session->ti.libssh.channel) {
+ ERR("%s: internal error (%s:%d)", __FILE__, __LINE__);
+ return -1;
+ }
+
+ session->ti.libssh.channel = channel;
+
+ return 0;
+}
+
+static int
+nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, const char *subsystem)
+{
+ while (session && (session->ti.libssh.channel != channel)) {
+ session = session->ti.libssh.next;
+ }
+
+ if (!session) {
+ ERR("%s: internal error (%s:%d)", __FILE__, __LINE__);
+ return -1;
+ }
+
+ if (!strcmp(subsystem, "netconf")) {
+ if (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) {
+ WRN("Client \"%s\" requested subsystem 'netconf' for the second time.", session->username);
+ } else {
+ session->flags |= NC_SESSION_SSH_SUBSYS_NETCONF;
+ }
+ } else {
+ WRN("Client \"%s\" requested an unknown subsystem '%s'.", session->username, subsystem);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+nc_sshcb_msg(ssh_session sshsession, ssh_message msg, void *data)
+{
+ const char *str_type, *str_subtype = NULL, *username;
+ int subtype, type;
+ struct nc_session *session = (struct nc_session *)data;
+ (void)sshsession;
+
+ type = ssh_message_type(msg);
+ subtype = ssh_message_subtype(msg);
+
+ switch (type) {
+ case SSH_REQUEST_AUTH:
+ str_type = "request-auth";
+ switch (subtype) {
+ case SSH_AUTH_METHOD_NONE:
+ str_subtype = "none";
+ break;
+ case SSH_AUTH_METHOD_PASSWORD:
+ str_subtype = "password";
+ break;
+ case SSH_AUTH_METHOD_PUBLICKEY:
+ str_subtype = "publickey";
+ break;
+ case SSH_AUTH_METHOD_HOSTBASED:
+ str_subtype = "hostbased";
+ break;
+ case SSH_AUTH_METHOD_INTERACTIVE:
+ str_subtype = "interactive";
+ break;
+ case SSH_AUTH_METHOD_GSSAPI_MIC:
+ str_subtype = "gssapi-mic";
+ break;
+ }
+ break;
+
+ case SSH_REQUEST_CHANNEL_OPEN:
+ str_type = "request-channel-open";
+ switch (subtype) {
+ case SSH_CHANNEL_SESSION:
+ str_subtype = "session";
+ break;
+ case SSH_CHANNEL_DIRECT_TCPIP:
+ str_subtype = "direct-tcpip";
+ break;
+ case SSH_CHANNEL_FORWARDED_TCPIP:
+ str_subtype = "forwarded-tcpip";
+ break;
+ case (int)SSH_CHANNEL_X11:
+ str_subtype = "channel-x11";
+ break;
+ case SSH_CHANNEL_UNKNOWN:
+ /* fallthrough */
+ default:
+ str_subtype = "unknown";
+ break;
+ }
+ break;
+
+ case SSH_REQUEST_CHANNEL:
+ str_type = "request-channel";
+ switch (subtype) {
+ case SSH_CHANNEL_REQUEST_PTY:
+ str_subtype = "pty";
+ break;
+ case SSH_CHANNEL_REQUEST_EXEC:
+ str_subtype = "exec";
+ break;
+ case SSH_CHANNEL_REQUEST_SHELL:
+ str_subtype = "shell";
+ break;
+ case SSH_CHANNEL_REQUEST_ENV:
+ str_subtype = "env";
+ break;
+ case SSH_CHANNEL_REQUEST_SUBSYSTEM:
+ str_subtype = "subsystem";
+ break;
+ case SSH_CHANNEL_REQUEST_WINDOW_CHANGE:
+ str_subtype = "window-change";
+ break;
+ case SSH_CHANNEL_REQUEST_X11:
+ str_subtype = "x11";
+ break;
+ case SSH_CHANNEL_REQUEST_UNKNOWN:
+ /* fallthrough */
+ default:
+ str_subtype = "unknown";
+ break;
+ }
+ break;
+
+ case SSH_REQUEST_SERVICE:
+ str_type = "request-service";
+ str_subtype = ssh_message_service_service(msg);
+ break;
+
+ case SSH_REQUEST_GLOBAL:
+ str_type = "request-global";
+ switch (subtype) {
+ case SSH_GLOBAL_REQUEST_TCPIP_FORWARD:
+ str_subtype = "tcpip-forward";
+ break;
+ case SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD:
+ str_subtype = "cancel-tcpip-forward";
+ break;
+ case SSH_GLOBAL_REQUEST_UNKNOWN:
+ /* fallthrough */
+ default:
+ str_subtype = "unknown";
+ break;
+ }
+ break;
+
+ default:
+ str_type = "unknown";
+ str_subtype = "unknown";
+ break;
+ }
+
+ VRB("Received an SSH message \"%s\" of subtype \"%s\".", str_type, str_subtype);
+
+ /*
+ * process known messages
+ */
+ if (type == SSH_REQUEST_AUTH) {
+ if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
+ ERR("User \"%s\" authenticated, but requested another authentication.", session->username);
+ ssh_message_reply_default(msg);
+ return 0;
+ }
+
+ if (session->auth_attempts >= ssh_opts.auth_attempts) {
+ /* too many failed attempts */
+ ssh_message_reply_default(msg);
+ return 0;
+ }
+
+ /* save the username, do not let the client change it */
+ username = ssh_message_auth_user(msg);
+ if (!session->username) {
+ if (!username) {
+ ERR("Denying an auth request without a username.");
+ return 1;
+ }
+
+ session->username = lydict_insert(session->ctx, username, 0);
+ } else if (username) {
+ if (strcmp(username, session->username)) {
+ ERR("User \"%s\" changed its username to \"%s\".", session->username, username);
+ session->status = NC_STATUS_INVALID;
+ return 1;
+ }
+ }
+
+ if (subtype == SSH_AUTH_METHOD_NONE) {
+ /* libssh will return the supported auth methods */
+ return 1;
+ } else if (subtype == SSH_AUTH_METHOD_PASSWORD) {
+ nc_sshcb_auth_password(session, msg);
+ return 0;
+ } else if (subtype == SSH_AUTH_METHOD_PUBLICKEY) {
+ nc_sshcb_auth_pubkey(session, msg);
+ return 0;
+ } else if (subtype == SSH_AUTH_METHOD_INTERACTIVE) {
+ nc_sshcb_auth_kbdint(session, msg);
+ return 0;
+ }
+ } else if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
+ if ((type == SSH_REQUEST_CHANNEL_OPEN) && (subtype == (int)SSH_CHANNEL_SESSION)) {
+ ssh_channel chan;
+ if ((chan = ssh_message_channel_request_open_reply_accept(msg)) == NULL) {
+ ssh_message_reply_default(msg);
+ return 0;
+ }
+ nc_sshcb_channel_open(session, chan);
+ return 0;
+ } else if ((type == SSH_REQUEST_CHANNEL) && (subtype == (int)SSH_CHANNEL_REQUEST_SUBSYSTEM)) {
+ if (!nc_sshcb_channel_subsystem(session, ssh_message_channel_request_channel(msg),
+ ssh_message_channel_request_subsystem(msg))) {
+ ssh_message_channel_request_reply_success(msg);
+ } else {
+ ssh_message_reply_default(msg);
+ }
+ return 0;
+ }
+ }
+
+ /* we did not process it */
+ return 1;
+}
+
+static int
+nc_open_netconf_channel(struct nc_session *session, int timeout)
+{
+ int elapsed = 0;
+
+ /* message callback is executed twice to give chance for the channel to be
+ * created if timeout == 0 (it takes 2 messages, channel-open, subsystem-request) */
+ do {
+ if (ssh_execute_message_callbacks(session->ti.libssh.session) != SSH_OK) {
+ ERR("%s: failed to receive new messages on the SSH session (%s)",
+ __func__, ssh_get_error(session->ti.libssh.session));
+ return -1;
+ }
+
+ if (session->ti.libssh.channel && (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
+ return 0;
+ }
+
+ usleep(NC_TIMEOUT_STEP);
+ elapsed += NC_TIMEOUT_STEP;
+ if ((timeout > NC_TIMEOUT_STEP) && (elapsed >= timeout)) {
+ break;
+ }
+
+ if (ssh_execute_message_callbacks(session->ti.libssh.session) != SSH_OK) {
+ ERR("%s: failed to receive new messages on the SSH session (%s)",
+ __func__, ssh_get_error(session->ti.libssh.session));
+ return -1;
+ }
+
+ if (session->ti.libssh.channel && (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
+ return 0;
+ }
+
+ usleep(NC_TIMEOUT_STEP);
+ elapsed += NC_TIMEOUT_STEP;
+ } while ((timeout == -1) || (timeout && (elapsed < timeout)));
+
+ return 1;
+}
+
+API struct nc_session *
+nc_accept_ssh(int timeout)
+{
+ int sock, elapsed = 0, libssh_auth_methods = 0;
+ char *host;
+ uint16_t port;
+ struct nc_session *session = NULL;
+
+ if (!server_opts.ctx || !ssh_opts.binds || !ssh_opts.sshbind) {
+ return NULL;
+ }
+
+ sock = nc_sock_accept(ssh_opts.binds, ssh_opts.bind_count, timeout, &host, &port);
+ if (sock == -1) {
+ return NULL;
+ }
+
+ session = calloc(1, sizeof *session);
+ if (!session) {
+ ERRMEM;
+ goto fail;
+ }
+ session->status = NC_STATUS_STARTING;
+ session->side = NC_SERVER;
+ session->ctx = server_opts.ctx;
+ session->flags = NC_SESSION_SHAREDCTX;
+ session->host = lydict_insert_zc(session->ctx, host);
+ session->port = port;
+
+ /* transport lock */
+ session->ti_lock = malloc(sizeof *session->ti_lock);
+ if (!session->ti_lock) {
+ ERRMEM;
+ goto fail;
+ }
+ pthread_mutex_init(session->ti_lock, NULL);
+
+ /* other transport-specific data */
+ session->ti_type = NC_TI_LIBSSH;
+ session->ti.libssh.session = ssh_new();
+ if (!session->ti.libssh.session) {
+ ERR("%s: failed to initialize SSH session", __func__);
+ goto fail;
+ }
+
+ if (ssh_opts.auth_methods & NC_SSH_AUTH_PUBLICKEY) {
+ libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
+ }
+ if (ssh_opts.auth_methods & NC_SSH_AUTH_PASSWORD) {
+ libssh_auth_methods |= SSH_AUTH_METHOD_PASSWORD;
+ }
+ if (ssh_opts.auth_methods & NC_SSH_AUTH_INTERACTIVE) {
+ libssh_auth_methods |= SSH_AUTH_METHOD_INTERACTIVE;
+ }
+ ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods);
+
+ ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, session);
+
+ if (ssh_bind_accept_fd(ssh_opts.sshbind, session->ti.libssh.session, sock) == SSH_ERROR) {
+ ERR("%s: SSH failed to accept a new connection (%s)", __func__, ssh_get_error(ssh_opts.sshbind));
+ goto fail;
+ }
+
+ if (ssh_handle_key_exchange(session->ti.libssh.session) != SSH_OK) {
+ ERR("%s: SSH key exchange error (%s)", __func__, ssh_get_error(session->ti.libssh.session));
+ goto fail;
+ }
+
+ /* authenticate */
+ do {
+ if (ssh_execute_message_callbacks(session->ti.libssh.session) != SSH_OK) {
+ ERR("%s: failed to receive new messages on the SSH session (%s)",
+ __func__, ssh_get_error(session->ti.libssh.session));
+ goto fail;
+ }
+
+ if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
+ break;
+ }
+
+ usleep(NC_TIMEOUT_STEP);
+ elapsed += NC_TIMEOUT_STEP;
+ } while ((timeout == -1) || (timeout && (elapsed < timeout)));
+
+ if (!(session->flags & NC_SESSION_SSH_AUTHENTICATED)) {
+ /* timeout */
+ goto fail;
+ }
+
+ if (timeout > 0) {
+ timeout -= elapsed;
+ }
+
+ /* open channel */
+ if (nc_open_netconf_channel(session, timeout)) {
+ goto fail;
+ }
+
+ /* NETCONF handshake */
+ if (nc_handshake(session)) {
+ goto fail;
+ }
+ session->status = NC_STATUS_RUNNING;
+
+ return session;
+
+fail:
+ if (sock > -1) {
+ close(sock);
+ }
+ nc_session_free(session);
+
+ return NULL;
+}
+
+API struct nc_session *
+nc_accept_ssh_channel(struct nc_session *session, int timeout)
+{
+ struct nc_session *new_session;
+ int ret;
+
+ for (new_session = session; new_session->ti.libssh.next; new_session = new_session->ti.libssh.next);
+ new_session->ti.libssh.next = calloc(1, sizeof *new_session);
+ new_session = new_session->ti.libssh.next;
+
+ new_session->status = NC_STATUS_STARTING;
+ new_session->side = NC_SERVER;
+ new_session->ti_type = NC_TI_LIBSSH;
+ new_session->ti_lock = session->ti_lock;
+ new_session->ti.libssh.session = session->ti.libssh.session;
+ new_session->flags = NC_SESSION_SSH_AUTHENTICATED | NC_SESSION_SHAREDCTX;
+ new_session->ctx = session->ctx;
+
+ new_session->username = lydict_insert(new_session->ctx, session->username, 0);
+ new_session->host = lydict_insert(new_session->ctx, session->host, 0);
+ new_session->port = session->port;
+
+ ret = nc_open_netconf_channel(new_session, timeout);
+ if (ret) {
+ if (ret == -1) {
+ do {
+ session->status = NC_STATUS_INVALID;
+ session = session->ti.libssh.next;
+ } while (session);
+ }
+ goto fail;
+ }
+
+ /* NETCONF handshake */
+ if (nc_handshake(new_session)) {
+ goto fail;
+ }
+ new_session->status = NC_STATUS_RUNNING;
+
+ return new_session;
+
+fail:
+ nc_session_free(new_session);
+
+ return NULL;
+}