/** * \file session_client_ssh.c * \author Radek Krejci <rkrejci@cesnet.cz> * \author Michal Vasko <mvasko@cesnet.cz> * \brief libnetconf2 - SSH specific client session transport functions * * This source is compiled only with libssh. * * Copyright (c) 2015 - 2021 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause */ #define _GNU_SOURCE #include <assert.h> #include <errno.h> #include <fcntl.h> #include <pthread.h> #include <pwd.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <termios.h> #include <time.h> #include <unistd.h> #ifdef ENABLE_DNSSEC # include <validator/resolver.h> # include <validator/validator.h> # include <validator/validator-config.h> # include <validator/validator-compat.h> #endif #include <libssh/libssh.h> #include <libyang/libyang.h> #include "compat.h" #include "libnetconf.h" #include "session_client.h" #include "session_client_ch.h" struct nc_client_context *nc_client_context_location(void); int nc_session_new_ctx(struct nc_session *session, struct ly_ctx *ctx); #define client_opts nc_client_context_location()->opts #define ssh_opts nc_client_context_location()->ssh_opts #define ssh_ch_opts nc_client_context_location()->ssh_ch_opts static FILE * open_tty_noecho(const char *path, struct termios *oldterm) { struct termios newterm; FILE *ret; if (!(ret = fopen(path, "r"))) { ERR(NULL, "Unable to open terminal \"%s\" for reading (%s).", path, strerror(errno)); return NULL; } if (tcgetattr(fileno(ret), oldterm)) { ERR(NULL, "Unable to get terminal \"%s\" settings (%s).", path, strerror(errno)); fclose(ret); return NULL; } newterm = *oldterm; newterm.c_lflag &= ~ECHO; newterm.c_lflag &= ~ICANON; tcflush(fileno(ret), TCIFLUSH); if (tcsetattr(fileno(ret), TCSANOW, &newterm)) { ERR(NULL, "Unable to change terminal \"%s\" settings for hiding password (%s).", path, strerror(errno)); fclose(ret); return NULL; } return ret; } static FILE * nc_open_in(int echo, struct termios *oldterm) { char buf[512]; int buflen = 512, ret; FILE *in; if (!echo) { in = open_tty_noecho("/dev/tty", oldterm); } else { in = fopen("/dev/tty", "r"); if (!in) { ERR(NULL, "Unable to open terminal \"/dev/tty\" for reading (%s).", strerror(errno)); } } if (!in) { if ((ret = ttyname_r(STDIN_FILENO, buf, buflen))) { ERR(NULL, "ttyname_r failed (%s).", strerror(ret)); return NULL; } if (!echo) { in = open_tty_noecho(buf, oldterm); } else { in = fopen(buf, "r"); if (!in) { ERR(NULL, "Unable to open terminal \"%s\" for reading (%s).", buf, strerror(errno)); } } } return in; } static FILE * nc_open_out(void) { char buf[512]; int buflen = 512, ret; FILE *out; out = fopen("/dev/tty", "w"); if (!out) { ERR(NULL, "Unable to open terminal \"/dev/tty\" for writing (%s).", strerror(errno)); if ((ret = ttyname_r(STDOUT_FILENO, buf, buflen))) { ERR(NULL, "ttyname_r failed (%s).", strerror(ret)); return NULL; } out = fopen(buf, "w"); if (!out) { ERR(NULL, "Unable to open terminal \"%s\" for writing (%s).", buf, strerror(errno)); } } return out; } static void nc_close_inout(FILE *inout, int echo, struct termios *oldterm) { if (inout) { if (!echo && (tcsetattr(fileno(inout), TCSANOW, oldterm) != 0)) { ERR(NULL, "Unable to restore terminal settings (%s).", strerror(errno)); } fclose(inout); } } void _nc_client_ssh_destroy_opts(struct nc_client_ssh_opts *opts) { int i; for (i = 0; i < opts->key_count; ++i) { free(opts->keys[i].pubkey_path); free(opts->keys[i].privkey_path); } free(opts->keys); free(opts->username); opts->keys = NULL; opts->username = NULL; } void nc_client_ssh_destroy_opts(void) { _nc_client_ssh_destroy_opts(&ssh_opts); _nc_client_ssh_destroy_opts(&ssh_ch_opts); } #ifdef ENABLE_DNSSEC /* return 0 (DNSSEC + key valid), 1 (unsecure DNS + key valid), 2 (key not found or an error) */ /* type - 1 (RSA), 2 (DSA), 3 (ECDSA); alg - 1 (SHA1), 2 (SHA-256) */ static int sshauth_hostkey_hash_dnssec_check(const char *hostname, const unsigned char *sha1hash, int type, int alg) { ns_msg handle; ns_rr rr; val_status_t val_status; const unsigned char *rdata; unsigned char buf[4096]; int buf_len = 4096; int ret = 0, i, j, len; /* class 1 - internet, type 44 - SSHFP */ len = val_res_query(NULL, hostname, 1, 44, buf, buf_len, &val_status); if ((len < 0) || !val_istrusted(val_status)) { ret = 2; goto finish; } if (ns_initparse(buf, len, &handle) < 0) { ERR(NULL, "Failed to initialize DNSSEC response parser."); ret = 2; goto finish; } if ((i = libsres_msg_getflag(handle, ns_f_rcode))) { ERR(NULL, "DNSSEC query returned %d.", i); ret = 2; goto finish; } if (!libsres_msg_getflag(handle, ns_f_ad)) { /* response not secured by DNSSEC */ ret = 1; } /* query section */ if (ns_parserr(&handle, ns_s_qd, 0, &rr)) { ERR(NULL, "DNSSEC query section parser fail."); ret = 2; goto finish; } if (strcmp(hostname, ns_rr_name(rr)) || (ns_rr_type(rr) != 44) || (ns_rr_class(rr) != 1)) { ERR(NULL, "DNSSEC query in the answer does not match the original query."); ret = 2; goto finish; } /* answer section */ i = 0; while (!ns_parserr(&handle, ns_s_an, i, &rr)) { if (ns_rr_type(rr) != 44) { ++i; continue; } rdata = ns_rr_rdata(rr); if (rdata[0] != type) { ++i; continue; } if (rdata[1] != alg) { ++i; continue; } /* we found the correct SSHFP entry */ rdata += 2; for (j = 0; j < 20; ++j) { if (rdata[j] != (unsigned char)sha1hash[j]) { ret = 2; goto finish; } } /* server fingerprint is supported by a DNS entry, * we have already determined if DNSSEC was used or not */ goto finish; } /* no match */ ret = 2; finish: val_free_validator_state(); return ret; } #endif /* ENABLE_DNSSEC */ int sshauth_hostkey_check(const char *hostname, ssh_session session, void *UNUSED(priv)) { char *hexa = NULL; #if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)) int c, ret; enum ssh_known_hosts_e state; #else int c, state, ret; #endif ssh_key srv_pubkey; unsigned char *hash_sha1 = NULL; size_t hlen; enum ssh_keytypes_e srv_pubkey_type; char answer[5]; FILE *out = NULL, *in = NULL; #if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)) state = ssh_session_is_known_server(session); #else state = ssh_is_server_known(session); #endif #if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 8, 0)) ret = ssh_get_server_publickey(session, &srv_pubkey); #else ret = ssh_get_publickey(session, &srv_pubkey); #endif if (ret < 0) { ERR(NULL, "Unable to get server public key."); return -1; } srv_pubkey_type = ssh_key_type(srv_pubkey); ret = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA1, &hash_sha1, &hlen); ssh_key_free(srv_pubkey); if (ret < 0) { ERR(NULL, "Failed to calculate SHA1 hash of the server public key."); return -1; } hexa = ssh_get_hexa(hash_sha1, hlen); switch (state) { #if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)) case SSH_KNOWN_HOSTS_OK: #else case SSH_SERVER_KNOWN_OK: #endif break; /* ok */ #if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)) case SSH_KNOWN_HOSTS_CHANGED: #else case SSH_SERVER_KNOWN_CHANGED: #endif ERR(NULL, "Remote host key changed, the connection will be terminated!"); goto error; #if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)) case SSH_KNOWN_HOSTS_OTHER: #else case SSH_SERVER_FOUND_OTHER: #endif WRN(NULL, "Remote host key is not known, but a key of another type for this host is known. Continue with caution."); goto hostkey_not_known; #if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)) case SSH_KNOWN_HOSTS_NOT_FOUND: #else case SSH_SERVER_FILE_NOT_FOUND: #endif WRN(NULL, "Could not find the known hosts file."); goto hostkey_not_known; #if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)) case SSH_KNOWN_HOSTS_UNKNOWN: #else case SSH_SERVER_NOT_KNOWN: #endif hostkey_not_known: #ifdef ENABLE_DNSSEC if ((srv_pubkey_type != SSH_KEYTYPE_UNKNOWN) && (srv_pubkey_type != SSH_KEYTYPE_RSA1)) { if (srv_pubkey_type == SSH_KEYTYPE_DSS) { ret = sshauth_hostkey_hash_dnssec_check(hostname, hash_sha1, 2, 1); } else if (srv_pubkey_type == SSH_KEYTYPE_RSA) { ret = sshauth_hostkey_hash_dnssec_check(hostname, hash_sha1, 1, 1); } else if (srv_pubkey_type == SSH_KEYTYPE_ECDSA) { ret = sshauth_hostkey_hash_dnssec_check(hostname, hash_sha1, 3, 1); } /* DNSSEC SSHFP check successful, that's enough */ if (!ret) { VRB(NULL, "DNSSEC SSHFP check successful."); #if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)) ssh_session_update_known_hosts(session); #else ssh_write_knownhost(session); #endif ssh_clean_pubkey_hash(&hash_sha1); ssh_string_free_char(hexa); return 0; } } #endif if (!(in = nc_open_in(1, NULL))) { goto error; } if (!(out = nc_open_out())) { goto error; } /* try to get result from user */ if (fprintf(out, "The authenticity of the host \'%s\' cannot be established.\n", hostname) < 1) { ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno)); goto error; } if (fprintf(out, "%s key fingerprint is %s.\n", ssh_key_type_to_char(srv_pubkey_type), hexa) < 1) { ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno)); goto error; } #ifdef ENABLE_DNSSEC if (ret == 2) { if (fprintf(out, "No matching host key fingerprint found using DNS.\n") < 1) { ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno)); goto error; } } else if (ret == 1) { if (fprintf(out, "Matching host key fingerprint found using DNS.\n") < 1) { ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno)); goto error; } } #endif if (fprintf(out, "Are you sure you want to continue connecting (yes/no)? ") < 1) { ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno)); goto error; } fflush(out); do { if (fscanf(in, "%4s", answer) == EOF) { ERR(NULL, "Reading from input failed (%s).", feof(in) ? "EOF" : strerror(errno)); goto error; } while (((c = getc(in)) != EOF) && (c != '\n')) {} fflush(in); if (!strcmp("yes", answer)) { /* store the key into the host file */ #if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)) ret = ssh_session_update_known_hosts(session); #else ret = ssh_write_knownhost(session); #endif if (ret != SSH_OK) { WRN(NULL, "Adding the known host \"%s\" failed (%s).", hostname, ssh_get_error(session)); } } else if (!strcmp("no", answer)) { goto error; } else { if (fprintf(out, "Please type 'yes' or 'no': ") < 1) { ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno)); goto error; } fflush(out); } } while (strcmp(answer, "yes") && strcmp(answer, "no")); break; #if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)) case SSH_KNOWN_HOSTS_ERROR: #else case SSH_SERVER_ERROR: #endif ERR(NULL, "SSH error: %s", ssh_get_error(session)); goto error; } nc_close_inout(in, 1, NULL); nc_close_inout(out, 1, NULL); ssh_clean_pubkey_hash(&hash_sha1); ssh_string_free_char(hexa); return 0; error: nc_close_inout(in, 1, NULL); nc_close_inout(out, 1, NULL); ssh_clean_pubkey_hash(&hash_sha1); ssh_string_free_char(hexa); return -1; } char * sshauth_password(const char *username, const char *hostname, void *UNUSED(priv)) { char *buf = NULL; int c, buflen = 1024, len; struct termios oldterm; FILE *in = NULL, *out = NULL; buf = malloc(buflen * sizeof *buf); if (!buf) { ERRMEM; return NULL; } if (!(in = nc_open_in(0, &oldterm))) { goto error; } if (!(out = nc_open_out())) { goto error; } if (fprintf(out, "%s@%s password: ", username, hostname) < 1) { ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno)); goto error; } fflush(out); len = 0; while (((c = fgetc(in)) != EOF) && (c != '\n')) { if (len >= buflen - 1) { buflen *= 2; buf = nc_realloc(buf, buflen * sizeof *buf); if (!buf) { ERRMEM; goto error; } } buf[len++] = (char)c; } buf[len++] = 0; /* terminating null byte */ fprintf(out, "\n"); nc_close_inout(in, 0, &oldterm); nc_close_inout(out, 1, NULL); return buf; error: nc_close_inout(in, 0, &oldterm); nc_close_inout(out, 1, NULL); free(buf); return NULL; } char * sshauth_interactive(const char *auth_name, const char *instruction, const char *prompt, int echo, void *UNUSED(priv)) { unsigned int buflen = 64, cur_len; int c; struct termios oldterm; char *buf = NULL; FILE *in = NULL, *out = NULL; buf = malloc(buflen * sizeof *buf); if (!buf) { ERRMEM; return NULL; } if (!(in = nc_open_in(echo, &oldterm))) { goto error; } if (!(out = nc_open_out())) { goto error; } if (auth_name && (fprintf(out, "%s\n", auth_name) < 1)) { ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno)); goto error; } if (instruction && (fprintf(out, "%s\n", instruction) < 1)) { ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno)); goto error; } if (fputs(prompt, out) == EOF) { ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno)); goto error; } fflush(out); cur_len = 0; while (((c = fgetc(in)) != EOF) && (c != '\n')) { if (cur_len >= buflen - 1) { buflen *= 2; buf = nc_realloc(buf, buflen * sizeof *buf); if (!buf) { ERRMEM; goto error; } } buf[cur_len++] = (char)c; } /* terminating null byte */ buf[cur_len] = '\0'; fprintf(out, "\n"); nc_close_inout(in, echo, &oldterm); nc_close_inout(out, 1, NULL); return buf; error: nc_close_inout(in, echo, &oldterm); nc_close_inout(out, 1, NULL); free(buf); return NULL; } char * sshauth_privkey_passphrase(const char *privkey_path, void *UNUSED(priv)) { char *buf = NULL; int c, buflen = 1024, len; struct termios oldterm; FILE *in = NULL, *out = NULL; buf = malloc(buflen * sizeof *buf); if (!buf) { ERRMEM; return NULL; } if (!(in = nc_open_in(0, &oldterm))) { goto error; } if (!(out = nc_open_out())) { goto error; } if (fprintf(out, "Enter passphrase for the key '%s': ", privkey_path) < 1) { ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno)); goto error; } fflush(out); len = 0; while (((c = fgetc(in)) != EOF) && (c != '\n')) { if (len >= buflen - 1) { buflen *= 2; buf = nc_realloc(buf, buflen * sizeof *buf); if (!buf) { ERRMEM; goto error; } } buf[len++] = (char)c; } buf[len] = 0; /* terminating null byte */ fprintf(out, "\n"); nc_close_inout(in, 0, &oldterm); nc_close_inout(out, 1, NULL); return buf; error: nc_close_inout(in, 0, &oldterm); nc_close_inout(out, 1, NULL); free(buf); return NULL; } static void _nc_client_ssh_set_auth_hostkey_check_clb(int (*auth_hostkey_check)(const char *hostname, ssh_session session, void *priv), void *priv, struct nc_client_ssh_opts *opts) { if (auth_hostkey_check) { opts->auth_hostkey_check = auth_hostkey_check; opts->auth_hostkey_check_priv = priv; } else { opts->auth_hostkey_check = sshauth_hostkey_check; opts->auth_hostkey_check_priv = NULL; } } static void _nc_client_ssh_get_auth_hostkey_check_clb(int (**auth_hostkey_check)(const char *hostname, ssh_session session, void *priv), void **priv, struct nc_client_ssh_opts *opts) { if (auth_hostkey_check) { (*auth_hostkey_check) = opts->auth_hostkey_check == sshauth_hostkey_check ? NULL : opts->auth_hostkey_check; } if (priv) { (*priv) = opts->auth_hostkey_check_priv; } } API void nc_client_ssh_set_auth_hostkey_check_clb(int (*auth_hostkey_check)(const char *hostname, ssh_session session, void *priv), void *priv) { _nc_client_ssh_set_auth_hostkey_check_clb(auth_hostkey_check, priv, &ssh_opts); } API void nc_client_ssh_ch_set_auth_hostkey_check_clb(int (*auth_hostkey_check)(const char *hostname, ssh_session session, void *priv), void *priv) { _nc_client_ssh_set_auth_hostkey_check_clb(auth_hostkey_check, priv, &ssh_ch_opts); } API void nc_client_ssh_get_auth_hostkey_check_clb(int (**auth_hostkey_check)(const char *hostname, ssh_session session, void *priv), void **priv) { _nc_client_ssh_get_auth_hostkey_check_clb(auth_hostkey_check, priv, &ssh_opts); } API void nc_client_ssh_ch_get_auth_hostkey_check_clb(int (**auth_hostkey_check)(const char *hostname, ssh_session session, void *priv), void **priv) { _nc_client_ssh_get_auth_hostkey_check_clb(auth_hostkey_check, priv, &ssh_ch_opts); } static void _nc_client_ssh_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname, void *priv), void *priv, struct nc_client_ssh_opts *opts) { if (auth_password) { opts->auth_password = auth_password; opts->auth_password_priv = priv; } else { opts->auth_password = sshauth_password; opts->auth_password_priv = NULL; } } static void _nc_client_ssh_get_auth_password_clb(char *(**auth_password)(const char *username, const char *hostname, void *priv), void **priv, struct nc_client_ssh_opts *opts) { if (auth_password) { (*auth_password) = opts->auth_password == sshauth_password ? NULL : opts->auth_password; } if (priv) { (*priv) = opts->auth_password_priv; } } API void nc_client_ssh_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname, void *priv), void *priv) { _nc_client_ssh_set_auth_password_clb(auth_password, priv, &ssh_opts); } API void nc_client_ssh_ch_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname, void *priv), void *priv) { _nc_client_ssh_set_auth_password_clb(auth_password, priv, &ssh_ch_opts); } API void nc_client_ssh_get_auth_password_clb(char *(**auth_password)(const char *username, const char *hostname, void *priv), void **priv) { _nc_client_ssh_get_auth_password_clb(auth_password, priv, &ssh_opts); } API void nc_client_ssh_ch_get_auth_password_clb(char *(**auth_password)(const char *username, const char *hostname, void *priv), void **priv) { _nc_client_ssh_get_auth_password_clb(auth_password, priv, &ssh_ch_opts); } static void _nc_client_ssh_set_auth_interactive_clb(char *(*auth_interactive)(const char *auth_name, const char *instruction, const char *prompt, int echo, void *priv), void *priv, struct nc_client_ssh_opts *opts) { if (auth_interactive) { opts->auth_interactive = auth_interactive; opts->auth_interactive_priv = priv; } else { opts->auth_interactive = sshauth_interactive; opts->auth_interactive_priv = NULL; } } static void _nc_client_ssh_get_auth_interactive_clb(char *(**auth_interactive)(const char *auth_name, const char *instruction, const char *prompt, int echo, void *priv), void **priv, struct nc_client_ssh_opts *opts) { if (auth_interactive) { (*auth_interactive) = opts->auth_interactive == sshauth_interactive ? NULL : opts->auth_interactive; } if (priv) { (*priv) = opts->auth_interactive_priv; } } API void nc_client_ssh_set_auth_interactive_clb(char *(*auth_interactive)(const char *auth_name, const char *instruction, const char *prompt, int echo, void *priv), void *priv) { _nc_client_ssh_set_auth_interactive_clb(auth_interactive, priv, &ssh_opts); } API void nc_client_ssh_ch_set_auth_interactive_clb(char *(*auth_interactive)(const char *auth_name, const char *instruction, const char *prompt, int echo, void *priv), void *priv) { _nc_client_ssh_set_auth_interactive_clb(auth_interactive, priv, &ssh_ch_opts); } API void nc_client_ssh_get_auth_interactive_clb(char *(**auth_interactive)(const char *auth_name, const char *instruction, const char *prompt, int echo, void *priv), void **priv) { _nc_client_ssh_get_auth_interactive_clb(auth_interactive, priv, &ssh_opts); } API void nc_client_ssh_ch_get_auth_interactive_clb(char *(**auth_interactive)(const char *auth_name, const char *instruction, const char *prompt, int echo, void *priv), void **priv) { _nc_client_ssh_get_auth_interactive_clb(auth_interactive, priv, &ssh_ch_opts); } static void _nc_client_ssh_set_auth_privkey_passphrase_clb(char *(*auth_privkey_passphrase)(const char *privkey_path, void *priv), void *priv, struct nc_client_ssh_opts *opts) { if (auth_privkey_passphrase) { opts->auth_privkey_passphrase = auth_privkey_passphrase; opts->auth_privkey_passphrase_priv = priv; } else { opts->auth_privkey_passphrase = sshauth_privkey_passphrase; opts->auth_privkey_passphrase_priv = NULL; } } static void _nc_client_ssh_get_auth_privkey_passphrase_clb(char *(**auth_privkey_passphrase)(const char *privkey_path, void *priv), void **priv, struct nc_client_ssh_opts *opts) { if (auth_privkey_passphrase) { (*auth_privkey_passphrase) = opts->auth_privkey_passphrase == sshauth_privkey_passphrase ? NULL : opts->auth_privkey_passphrase; } if (priv) { (*priv) = opts->auth_privkey_passphrase_priv; } } API void nc_client_ssh_set_auth_privkey_passphrase_clb(char *(*auth_privkey_passphrase)(const char *privkey_path, void *priv), void *priv) { _nc_client_ssh_set_auth_privkey_passphrase_clb(auth_privkey_passphrase, priv, &ssh_opts); } API void nc_client_ssh_ch_set_auth_privkey_passphrase_clb(char *(*auth_privkey_passphrase)(const char *privkey_path, void *priv), void *priv) { _nc_client_ssh_set_auth_privkey_passphrase_clb(auth_privkey_passphrase, priv, &ssh_ch_opts); } API void nc_client_ssh_get_auth_privkey_passphrase_clb(char *(**auth_privkey_passphrase)(const char *privkey_path, void *priv), void **priv) { _nc_client_ssh_get_auth_privkey_passphrase_clb(auth_privkey_passphrase, priv, &ssh_opts); } API void nc_client_ssh_ch_get_auth_privkey_passphrase_clb(char *(**auth_privkey_passphrase)(const char *privkey_path, void *priv), void **priv) { _nc_client_ssh_get_auth_privkey_passphrase_clb(auth_privkey_passphrase, priv, &ssh_ch_opts); } static int _nc_client_ssh_add_keypair(const char *pub_key, const char *priv_key, struct nc_client_ssh_opts *opts) { int i; FILE *key; char line[128]; if (!pub_key) { ERRARG("pub_key"); return -1; } else if (!priv_key) { ERRARG("priv_key"); return -1; } for (i = 0; i < opts->key_count; ++i) { if (!strcmp(opts->keys[i].pubkey_path, pub_key) || !strcmp(opts->keys[i].privkey_path, priv_key)) { if (strcmp(opts->keys[i].pubkey_path, pub_key)) { WRN(NULL, "Private key \"%s\" found with another public key \"%s\".", priv_key, opts->keys[i].pubkey_path); continue; } else if (strcmp(opts->keys[i].privkey_path, priv_key)) { WRN(NULL, "Public key \"%s\" found with another private key \"%s\".", pub_key, opts->keys[i].privkey_path); continue; } ERR(NULL, "SSH key pair already set."); return -1; } } /* add the keys */ ++opts->key_count; opts->keys = nc_realloc(opts->keys, opts->key_count * sizeof *opts->keys); if (!opts->keys) { ERRMEM; return -1; } opts->keys[opts->key_count - 1].pubkey_path = strdup(pub_key); opts->keys[opts->key_count - 1].privkey_path = strdup(priv_key); opts->keys[opts->key_count - 1].privkey_crypt = 0; if (!opts->keys[opts->key_count - 1].pubkey_path || !opts->keys[opts->key_count - 1].privkey_path) { ERRMEM; return -1; } /* check encryption */ if ((key = fopen(priv_key, "r"))) { /* 1st line - key type */ if (!fgets(line, sizeof line, key)) { fclose(key); ERR(NULL, "fgets() on %s failed.", priv_key); return -1; } /* 2nd line - encryption information or key */ if (!fgets(line, sizeof line, key)) { fclose(key); ERR(NULL, "fgets() on %s failed.", priv_key); return -1; } fclose(key); if (strcasestr(line, "encrypted")) { opts->keys[opts->key_count - 1].privkey_crypt = 1; } } return 0; } API int nc_client_ssh_add_keypair(const char *pub_key, const char *priv_key) { return _nc_client_ssh_add_keypair(pub_key, priv_key, &ssh_opts); } API int nc_client_ssh_ch_add_keypair(const char *pub_key, const char *priv_key) { return _nc_client_ssh_add_keypair(pub_key, priv_key, &ssh_ch_opts); } static int _nc_client_ssh_del_keypair(int idx, struct nc_client_ssh_opts *opts) { if (idx >= opts->key_count) { ERRARG("idx"); return -1; } free(opts->keys[idx].pubkey_path); free(opts->keys[idx].privkey_path); --opts->key_count; if (idx < opts->key_count) { memcpy(&opts->keys[idx], &opts->keys[opts->key_count], sizeof *opts->keys); } if (opts->key_count) { opts->keys = nc_realloc(opts->keys, opts->key_count * sizeof *opts->keys); if (!opts->keys) { ERRMEM; return -1; } } else { free(opts->keys); opts->keys = NULL; } return 0; } API int nc_client_ssh_del_keypair(int idx) { return _nc_client_ssh_del_keypair(idx, &ssh_opts); } API int nc_client_ssh_ch_del_keypair(int idx) { return _nc_client_ssh_del_keypair(idx, &ssh_ch_opts); } static int _nc_client_ssh_get_keypair_count(struct nc_client_ssh_opts *opts) { return opts->key_count; } API int nc_client_ssh_get_keypair_count(void) { return _nc_client_ssh_get_keypair_count(&ssh_opts); } API int nc_client_ssh_ch_get_keypair_count(void) { return _nc_client_ssh_get_keypair_count(&ssh_ch_opts); } static int _nc_client_ssh_get_keypair(int idx, const char **pub_key, const char **priv_key, struct nc_client_ssh_opts *opts) { if (idx >= opts->key_count) { ERRARG("idx"); return -1; } else if (!pub_key && !priv_key) { ERRARG("pub_key and priv_key"); return -1; } if (pub_key) { *pub_key = opts->keys[idx].pubkey_path; } if (priv_key) { *priv_key = opts->keys[idx].privkey_path; } return 0; } API int nc_client_ssh_get_keypair(int idx, const char **pub_key, const char **priv_key) { return _nc_client_ssh_get_keypair(idx, pub_key, priv_key, &ssh_opts); } API int nc_client_ssh_ch_get_keypair(int idx, const char **pub_key, const char **priv_key) { return _nc_client_ssh_get_keypair(idx, pub_key, priv_key, &ssh_ch_opts); } static void _nc_client_ssh_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, int16_t pref, struct nc_client_ssh_opts *opts) { if (pref < 0) { pref = -1; } if (auth_type == NC_SSH_AUTH_INTERACTIVE) { opts->auth_pref[0].value = pref; } else if (auth_type == NC_SSH_AUTH_PASSWORD) { opts->auth_pref[1].value = pref; } else if (auth_type == NC_SSH_AUTH_PUBLICKEY) { opts->auth_pref[2].value = pref; } } API void nc_client_ssh_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, int16_t pref) { _nc_client_ssh_set_auth_pref(auth_type, pref, &ssh_opts); } API void nc_client_ssh_ch_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, int16_t pref) { _nc_client_ssh_set_auth_pref(auth_type, pref, &ssh_ch_opts); } static int16_t _nc_client_ssh_get_auth_pref(NC_SSH_AUTH_TYPE auth_type, struct nc_client_ssh_opts *opts) { int16_t pref = 0; if (auth_type == NC_SSH_AUTH_INTERACTIVE) { pref = opts->auth_pref[0].value; } else if (auth_type == NC_SSH_AUTH_PASSWORD) { pref = opts->auth_pref[1].value; } else if (auth_type == NC_SSH_AUTH_PUBLICKEY) { pref = opts->auth_pref[2].value; } return pref; } API int16_t nc_client_ssh_get_auth_pref(NC_SSH_AUTH_TYPE auth_type) { return _nc_client_ssh_get_auth_pref(auth_type, &ssh_opts); } API int16_t nc_client_ssh_ch_get_auth_pref(NC_SSH_AUTH_TYPE auth_type) { return _nc_client_ssh_get_auth_pref(auth_type, &ssh_ch_opts); } static int _nc_client_ssh_set_username(const char *username, struct nc_client_ssh_opts *opts) { if (opts->username) { free(opts->username); } if (username) { opts->username = strdup(username); if (!opts->username) { ERRMEM; return -1; } } else { opts->username = NULL; } return 0; } API int nc_client_ssh_set_username(const char *username) { return _nc_client_ssh_set_username(username, &ssh_opts); } API int nc_client_ssh_ch_set_username(const char *username) { return _nc_client_ssh_set_username(username, &ssh_ch_opts); } static const char * _nc_client_ssh_get_username(struct nc_client_ssh_opts *opts) { return opts->username; } API const char * nc_client_ssh_get_username(void) { return _nc_client_ssh_get_username(&ssh_opts); } API const char * nc_client_ssh_ch_get_username(void) { return _nc_client_ssh_get_username(&ssh_ch_opts); } API int nc_client_ssh_ch_add_bind_listen(const char *address, uint16_t port) { return nc_client_ch_add_bind_listen(address, port, NC_TI_LIBSSH); } API int nc_client_ssh_ch_del_bind(const char *address, uint16_t port) { return nc_client_ch_del_bind(address, port, NC_TI_LIBSSH); } /* Establish a secure SSH connection and authenticate. * Host, port, username, and a connected socket is expected to be set. * * return values * -1 failure * 0 try again * 1 success */ static int connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts, int timeout) { int j, ret_auth, userauthlist, ret, attempt = 0; NC_SSH_AUTH_TYPE auth; int16_t pref; const char *prompt; char *s, *answer, echo; ssh_key pubkey, privkey; ssh_session ssh_sess; struct timespec ts_timeout, ts_cur; ssh_sess = session->ti.libssh.session; nc_gettimespec_mono(&ts_timeout); nc_addtimespec(&ts_timeout, NC_TRANSPORT_TIMEOUT); while ((ret = ssh_connect(ssh_sess)) == SSH_AGAIN) { usleep(NC_TIMEOUT_STEP); nc_gettimespec_mono(&ts_cur); if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { break; } } if (ret == SSH_AGAIN) { ERR(session, "SSH connect timeout."); return 0; } else if (ret != SSH_OK) { ERR(session, "Starting the SSH session failed (%s).", ssh_get_error(ssh_sess)); DBG(session, "Error code %d.", ssh_get_error_code(ssh_sess)); return -1; } if (opts->auth_hostkey_check(session->host, ssh_sess, opts->auth_hostkey_check_priv)) { ERR(session, "Checking the host key failed."); return -1; } if (timeout > -1) { nc_gettimespec_mono(&ts_timeout); nc_addtimespec(&ts_timeout, timeout); } while ((ret_auth = ssh_userauth_none(ssh_sess, NULL)) == SSH_AUTH_AGAIN) { usleep(NC_TIMEOUT_STEP); if (timeout > -1) { nc_gettimespec_mono(&ts_cur); if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { break; } } } if (ret_auth == SSH_AUTH_AGAIN) { ERR(session, "Request authentication methods timeout."); return 0; } else if (ret_auth == SSH_AUTH_ERROR) { ERR(session, "Authentication failed (%s).", ssh_get_error(ssh_sess)); return -1; } else if (ret_auth == SSH_AUTH_SUCCESS) { WRN(session, "Server accepts \"none\" authentication method.") return 1; } /* check what authentication methods are available */ userauthlist = ssh_userauth_list(ssh_sess, NULL); /* remove those disabled */ if (opts->auth_pref[0].value < 0) { VRB(session, "Interactive SSH authentication method was disabled."); userauthlist &= ~SSH_AUTH_METHOD_INTERACTIVE; } if (opts->auth_pref[1].value < 0) { VRB(session, "Password SSH authentication method was disabled."); userauthlist &= ~SSH_AUTH_METHOD_PASSWORD; } if (opts->auth_pref[2].value < 0) { VRB(session, "Publickey SSH authentication method was disabled."); userauthlist &= ~SSH_AUTH_METHOD_PUBLICKEY; } do { auth = 0; pref = 0; if (userauthlist & SSH_AUTH_METHOD_INTERACTIVE) { auth = NC_SSH_AUTH_INTERACTIVE; pref = opts->auth_pref[0].value; } if ((userauthlist & SSH_AUTH_METHOD_PASSWORD) && (opts->auth_pref[1].value > pref)) { auth = NC_SSH_AUTH_PASSWORD; pref = opts->auth_pref[1].value; } if ((userauthlist & SSH_AUTH_METHOD_PUBLICKEY) && (opts->auth_pref[2].value > pref)) { auth = NC_SSH_AUTH_PUBLICKEY; } if (!auth) { if (!attempt) { ERR(session, "Unable to authenticate to the remote server (no supported authentication methods detected)."); } else { ERR(session, "Unable to authenticate to the remote server (all attempts via supported authentication " "methods failed)."); } return -1; } /* found common authentication method */ switch (auth) { case NC_SSH_AUTH_PASSWORD: userauthlist &= ~SSH_AUTH_METHOD_PASSWORD; VRB(session, "Password authentication (host \"%s\", user \"%s\").", session->host, session->username); s = opts->auth_password(session->username, session->host, opts->auth_password_priv); if (s == NULL) { ERR(session, "Unable to get the password."); return -1; } if (timeout > -1) { nc_gettimespec_mono(&ts_timeout); nc_addtimespec(&ts_timeout, timeout); } while ((ret_auth = ssh_userauth_password(ssh_sess, session->username, s)) == SSH_AUTH_AGAIN) { usleep(NC_TIMEOUT_STEP); if (timeout > -1) { nc_gettimespec_mono(&ts_cur); if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { break; } } } memset(s, 0, strlen(s)); free(s); break; case NC_SSH_AUTH_INTERACTIVE: userauthlist &= ~SSH_AUTH_METHOD_INTERACTIVE; VRB(session, "Keyboard-interactive authentication."); if (timeout > -1) { nc_gettimespec_mono(&ts_timeout); nc_addtimespec(&ts_timeout, timeout); } while (((ret_auth = ssh_userauth_kbdint(ssh_sess, NULL, NULL)) == SSH_AUTH_INFO) || (ret_auth == SSH_AUTH_AGAIN)) { if (ret_auth == SSH_AUTH_AGAIN) { usleep(NC_TIMEOUT_STEP); if (timeout > -1) { nc_gettimespec_mono(&ts_cur); if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { break; } } continue; } for (j = 0; j < ssh_userauth_kbdint_getnprompts(ssh_sess); ++j) { prompt = ssh_userauth_kbdint_getprompt(ssh_sess, j, &echo); if (!prompt) { ret_auth = SSH_AUTH_ERROR; break; } /* libssh BUG - echo is always 1 for some reason, assume always 0 */ echo = 0; answer = opts->auth_interactive(ssh_userauth_kbdint_getname(ssh_sess), ssh_userauth_kbdint_getinstruction(ssh_sess), prompt, echo, opts->auth_interactive_priv); if (ssh_userauth_kbdint_setanswer(ssh_sess, j, answer) < 0) { free(answer); ret_auth = SSH_AUTH_ERROR; break; } free(answer); } if (ret_auth == SSH_AUTH_ERROR) { break; } if (timeout > -1) { nc_gettimespec_mono(&ts_timeout); nc_addtimespec(&ts_timeout, timeout); } } break; case NC_SSH_AUTH_PUBLICKEY: userauthlist &= ~SSH_AUTH_METHOD_PUBLICKEY; VRB(session, "Publickey athentication."); /* if publickeys path not provided, we cannot continue */ if (!opts->key_count) { VRB(session, "No key pair specified."); break; } for (j = 0; j < opts->key_count; j++) { VRB(session, "Trying to authenticate using %spair \"%s\" \"%s\".", opts->keys[j].privkey_crypt ? "password-protected " : "", opts->keys[j].privkey_path, opts->keys[j].pubkey_path); ret = ssh_pki_import_pubkey_file(opts->keys[j].pubkey_path, &pubkey); if (ret == SSH_EOF) { WRN(session, "Failed to import the key \"%s\" (File access problem).", opts->keys[j].pubkey_path); continue; } else if (ret == SSH_ERROR) { WRN(session, "Failed to import the key \"%s\" (SSH error).", opts->keys[j].pubkey_path); continue; } if (timeout > -1) { nc_gettimespec_mono(&ts_timeout); nc_addtimespec(&ts_timeout, timeout); } while ((ret_auth = ssh_userauth_try_publickey(ssh_sess, NULL, pubkey)) == SSH_AUTH_AGAIN) { usleep(NC_TIMEOUT_STEP); if (timeout > -1) { nc_gettimespec_mono(&ts_cur); if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { break; } } } ssh_key_free(pubkey); if (ret_auth == SSH_AUTH_DENIED) { continue; } else if (ret_auth != SSH_AUTH_SUCCESS) { break; } if (opts->keys[j].privkey_crypt) { s = opts->auth_privkey_passphrase(opts->keys[j].privkey_path, opts->auth_privkey_passphrase_priv); } else { s = NULL; } ret = ssh_pki_import_privkey_file(opts->keys[j].privkey_path, s, NULL, NULL, &privkey); if (s) { memset(s, 0, strlen(s)); free(s); } if (ret == SSH_EOF) { WRN(session, "Failed to import the key \"%s\" (File access problem).", opts->keys[j].privkey_path); continue; } else if (ret == SSH_ERROR) { WRN(session, "Failed to import the key \"%s\" (SSH error).", opts->keys[j].privkey_path); continue; } if (timeout > -1) { nc_gettimespec_mono(&ts_timeout); nc_addtimespec(&ts_timeout, timeout); } while ((ret_auth = ssh_userauth_publickey(ssh_sess, NULL, privkey)) == SSH_AUTH_AGAIN) { usleep(NC_TIMEOUT_STEP); if (timeout > -1) { nc_gettimespec_mono(&ts_cur); if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { break; } } } ssh_key_free(privkey); if (ret_auth != SSH_AUTH_DENIED) { break; } } break; } switch (ret_auth) { case SSH_AUTH_AGAIN: ERR(session, "Authentication response timeout."); return 0; case SSH_AUTH_ERROR: ERR(session, "Authentication failed (%s).", ssh_get_error(ssh_sess)); return -1; case SSH_AUTH_DENIED: WRN(session, "Authentication denied."); break; case SSH_AUTH_PARTIAL: VRB(session, "Partial authentication success."); break; case SSH_AUTH_SUCCESS: VRB(session, "Authentication successful."); break; case SSH_AUTH_INFO: ERRINT; return -1; } attempt++; } while (ret_auth != SSH_AUTH_SUCCESS); return 1; } /* Open new SSH channel and request the 'netconf' subsystem. * SSH connection is expected to be established. */ static int open_netconf_channel(struct nc_session *session, int timeout) { ssh_session ssh_sess; int ret; struct timespec ts_timeout, ts_cur; ssh_sess = session->ti.libssh.session; if (!ssh_is_connected(ssh_sess)) { ERR(session, "SSH session not connected."); return -1; } if (session->ti.libssh.channel) { ERR(session, "SSH channel already created."); return -1; } /* open a channel */ if (timeout > -1) { nc_gettimespec_mono(&ts_timeout); nc_addtimespec(&ts_timeout, timeout); } session->ti.libssh.channel = ssh_channel_new(ssh_sess); while ((ret = ssh_channel_open_session(session->ti.libssh.channel)) == SSH_AGAIN) { usleep(NC_TIMEOUT_STEP); if (timeout > -1) { nc_gettimespec_mono(&ts_cur); if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { break; } } } if (ret == SSH_AGAIN) { ERR(session, "Opening an SSH channel timeout elapsed."); ssh_channel_free(session->ti.libssh.channel); session->ti.libssh.channel = NULL; return 0; } else if (ret == SSH_ERROR) { ERR(session, "Opening an SSH channel failed (%s).", ssh_get_error(ssh_sess)); ssh_channel_free(session->ti.libssh.channel); session->ti.libssh.channel = NULL; return -1; } /* execute the NETCONF subsystem on the channel */ if (timeout > -1) { nc_gettimespec_mono(&ts_timeout); nc_addtimespec(&ts_timeout, timeout); } while ((ret = ssh_channel_request_subsystem(session->ti.libssh.channel, "netconf")) == SSH_AGAIN) { usleep(NC_TIMEOUT_STEP); if (timeout > -1) { nc_gettimespec_mono(&ts_cur); if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { break; } } } if (ret == SSH_AGAIN) { ERR(session, "Starting the \"netconf\" SSH subsystem timeout elapsed."); ssh_channel_free(session->ti.libssh.channel); session->ti.libssh.channel = NULL; return 0; } else if (ret == SSH_ERROR) { ERR(session, "Starting the \"netconf\" SSH subsystem failed (%s).", ssh_get_error(ssh_sess)); ssh_channel_free(session->ti.libssh.channel); session->ti.libssh.channel = NULL; return -1; } return 1; } static struct nc_session * _nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx, struct nc_keepalives *ka, struct nc_client_ssh_opts *opts, int timeout) { char *host = NULL, *username = NULL, *ip_host; unsigned int port = 0; int sock; struct passwd *pw, pw_buf; struct nc_session *session = NULL; char *buf = NULL; size_t buf_len = 0; if (!ssh_session) { ERRARG("ssh_session"); return NULL; } /* prepare session structure */ session = nc_new_session(NC_CLIENT, 0); if (!session) { ERRMEM; return NULL; } session->status = NC_STATUS_STARTING; session->ti_type = NC_TI_LIBSSH; session->ti.libssh.session = ssh_session; /* was port set? */ ssh_options_get_port(ssh_session, &port); if (ssh_options_get(ssh_session, SSH_OPTIONS_HOST, &host) != SSH_OK) { /* * There is no file descriptor (detected based on the host, there is no way to check * the SSH_OPTIONS_FD directly :/), we need to create it. (TCP/IP layer) */ /* remember host */ host = strdup("localhost"); if (!host) { ERRMEM; goto fail; } ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOST, host); /* create and connect socket */ sock = nc_sock_connect(host, port, -1, ka, NULL, &ip_host); if (sock == -1) { ERR(NULL, "Unable to connect to %s:%u (%s).", host, port, strerror(errno)); goto fail; } ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock); ssh_set_blocking(session->ti.libssh.session, 0); free(host); host = ip_host; } /* was username set? */ ssh_options_get(ssh_session, SSH_OPTIONS_USER, &username); if (!ssh_is_connected(ssh_session)) { /* * We are connected, but not SSH authenticated. (Transport layer) */ /* remember username */ if (!username) { if (!opts->username) { pw = nc_getpwuid(getuid(), &pw_buf, &buf, &buf_len); if (!pw) { ERR(NULL, "Unknown username for the SSH connection (%s).", strerror(errno)); goto fail; } username = strdup(pw->pw_name); free(buf); } else { username = strdup(opts->username); } if (!username) { ERRMEM; goto fail; } ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_USER, username); } /* connect and authenticate SSH session */ session->host = host; session->username = username; if (connect_ssh_session(session, opts, timeout) != 1) { goto fail; } } /* * Almost done, open a netconf channel. (Transport layer / application layer) */ if (open_netconf_channel(session, timeout) != 1) { goto fail; } /* * SSH session is established and netconf channel opened, create a NETCONF session. (Application layer) */ if (nc_session_new_ctx(session, ctx) != EXIT_SUCCESS) { goto fail; } ctx = session->ctx; /* NETCONF handshake */ if (nc_handshake_io(session) != NC_MSG_HELLO) { goto fail; } session->status = NC_STATUS_RUNNING; if (nc_ctx_check_and_fill(session) == -1) { goto fail; } /* store information into the dictionary */ if (host) { lydict_insert_zc(ctx, host, &session->host); } if (port) { session->port = port; } if (username) { lydict_insert_zc(ctx, username, &session->username); } return session; fail: free(host); session->host = NULL; free(username); session->username = NULL; nc_session_free(session, NULL); return NULL; } API struct nc_session * nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx) { const long timeout = NC_SSH_TIMEOUT; int sock; uint32_t port_uint; char *username, *ip_host = NULL; struct passwd *pw, pw_buf; struct nc_session *session = NULL; char *buf = NULL; size_t buf_len = 0; /* process parameters */ if (!host || strisempty(host)) { host = "localhost"; } if (!port) { port = NC_PORT_SSH; } port_uint = port; if (!ssh_opts.username) { pw = nc_getpwuid(getuid(), &pw_buf, &buf, &buf_len); if (!pw) { ERR(session, "Unknown username for the SSH connection (%s).", strerror(errno)); goto fail; } else { username = pw->pw_name; } } else { username = ssh_opts.username; } /* prepare session structure */ session = nc_new_session(NC_CLIENT, 0); if (!session) { ERRMEM; goto fail; } session->status = NC_STATUS_STARTING; /* transport-specific data */ session->ti_type = NC_TI_LIBSSH; session->ti.libssh.session = ssh_new(); if (!session->ti.libssh.session) { ERR(session, "Unable to initialize SSH session."); goto fail; } /* set some basic SSH session options */ ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOST, host); ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_PORT, &port_uint); ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_USER, username); ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_TIMEOUT, &timeout); /* create and assign communication socket */ sock = nc_sock_connect(host, port, -1, &client_opts.ka, NULL, &ip_host); if (sock == -1) { ERR(session, "Unable to connect to %s:%u (%s).", host, port, strerror(errno)); goto fail; } ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock); ssh_set_blocking(session->ti.libssh.session, 0); /* temporarily, for session connection */ session->host = host; session->username = username; if ((connect_ssh_session(session, &ssh_opts, NC_TRANSPORT_TIMEOUT) != 1) || (open_netconf_channel(session, NC_TRANSPORT_TIMEOUT) != 1)) { goto fail; } if (nc_session_new_ctx(session, ctx) != EXIT_SUCCESS) { goto fail; } ctx = session->ctx; /* NETCONF handshake */ if (nc_handshake_io(session) != NC_MSG_HELLO) { goto fail; } session->status = NC_STATUS_RUNNING; if (nc_ctx_check_and_fill(session) == -1) { goto fail; } /* store information into the dictionary */ lydict_insert_zc(ctx, ip_host, &session->host); session->port = port; lydict_insert(ctx, username, 0, &session->username); free(buf); return session; fail: free(buf); free(ip_host); nc_session_free(session, NULL); return NULL; } API struct nc_session * nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx) { return _nc_connect_libssh(ssh_session, ctx, &client_opts.ka, &ssh_opts, NC_TRANSPORT_TIMEOUT); } API struct nc_session * nc_connect_ssh_channel(struct nc_session *session, struct ly_ctx *ctx) { struct nc_session *new_session, *ptr; if (!session) { ERRARG("session"); return NULL; } /* prepare session structure */ new_session = nc_new_session(NC_CLIENT, 1); if (!new_session) { ERRMEM; return NULL; } new_session->status = NC_STATUS_STARTING; /* share some parameters including the IO lock (we are using one socket for both sessions) */ new_session->ti_type = NC_TI_LIBSSH; new_session->ti.libssh.session = session->ti.libssh.session; new_session->io_lock = session->io_lock; /* append to the session ring list */ if (!session->ti.libssh.next) { session->ti.libssh.next = new_session; new_session->ti.libssh.next = session; } else { ptr = session->ti.libssh.next; session->ti.libssh.next = new_session; new_session->ti.libssh.next = ptr; } /* create the channel safely */ if (nc_session_io_lock(new_session, -1, __func__) != 1) { goto fail; } if (open_netconf_channel(new_session, NC_TRANSPORT_TIMEOUT) != 1) { goto fail; } nc_session_io_unlock(new_session, __func__); if (nc_session_new_ctx(new_session, ctx) != EXIT_SUCCESS) { goto fail; } ctx = session->ctx; /* NETCONF handshake */ if (nc_handshake_io(new_session) != NC_MSG_HELLO) { goto fail; } new_session->status = NC_STATUS_RUNNING; if (nc_ctx_check_and_fill(new_session) == -1) { goto fail; } /* store information into session and the dictionary */ lydict_insert(ctx, session->host, 0, &new_session->host); new_session->port = session->port; lydict_insert(ctx, session->username, 0, &new_session->username); return new_session; fail: nc_session_free(new_session, NULL); return NULL; } struct nc_session * nc_accept_callhome_ssh_sock(int sock, const char *host, uint16_t port, struct ly_ctx *ctx, int timeout) { const long ssh_timeout = NC_SSH_TIMEOUT; unsigned int uint_port; struct passwd *pw, pw_buf; struct nc_session *session; ssh_session sess; char *buf = NULL; size_t buf_len = 0; sess = ssh_new(); if (!sess) { ERR(NULL, "Unable to initialize an SSH session."); close(sock); return NULL; } ssh_options_set(sess, SSH_OPTIONS_FD, &sock); ssh_set_blocking(sess, 0); ssh_options_set(sess, SSH_OPTIONS_HOST, host); uint_port = port; ssh_options_set(sess, SSH_OPTIONS_PORT, &uint_port); ssh_options_set(sess, SSH_OPTIONS_TIMEOUT, &ssh_timeout); if (!ssh_ch_opts.username) { pw = nc_getpwuid(getuid(), &pw_buf, &buf, &buf_len); if (!pw) { ERR(NULL, "Unknown username for the SSH connection (%s).", strerror(errno)); ssh_free(sess); return NULL; } ssh_options_set(sess, SSH_OPTIONS_USER, pw->pw_name); free(buf); } else { ssh_options_set(sess, SSH_OPTIONS_USER, ssh_ch_opts.username); } ssh_options_set(sess, SSH_OPTIONS_HOSTKEYS, "ssh-ed25519,ecdsa-sha2-nistp256," "ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-rsa,rsa-sha2-512,rsa-sha2-256,ssh-dss"); #ifdef HAVE_LIBSSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES ssh_options_set(sess, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, "ssh-ed25519,ecdsa-sha2-nistp256," "ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-rsa,rsa-sha2-512,rsa-sha2-256,ssh-dss"); #endif session = _nc_connect_libssh(sess, ctx, &client_opts.ka, &ssh_ch_opts, timeout); if (!session) { /* sess is freed */ return NULL; } session->flags |= NC_SESSION_CALLHOME; return session; }