diff --git a/src/session_client.c b/src/session_client.c index a7ed9689..dafa0d53 100644 --- a/src/session_client.c +++ b/src/session_client.c @@ -78,13 +78,15 @@ static struct nc_client_context context_main = { .auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 1}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 3}}, .auth_password = sshauth_password, .auth_interactive = sshauth_interactive, - .auth_privkey_passphrase = sshauth_privkey_passphrase + .auth_privkey_passphrase = sshauth_privkey_passphrase, + .knownhosts_mode = NC_SSH_KNOWNHOSTS_ASK }, .ssh_ch_opts = { .auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 1}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 3}}, .auth_password = sshauth_password, .auth_interactive = sshauth_interactive, - .auth_privkey_passphrase = sshauth_privkey_passphrase + .auth_privkey_passphrase = sshauth_privkey_passphrase, + .knownhosts_mode = NC_SSH_KNOWNHOSTS_ASK }, #endif /* NC_ENABLED_SSH_TLS */ /* .tls_ structures zeroed */ diff --git a/src/session_client.h b/src/session_client.h index b144a0fb..faf7e424 100644 --- a/src/session_client.h +++ b/src/session_client.h @@ -190,6 +190,8 @@ struct nc_session *nc_connect_unix(const char *address, struct ly_ctx *ctx); /** * @brief Set the behaviour of checking the host key and adding/reading entries to/from the known_hosts file. * + * The default mode is ::NC_SSH_KNOWNHOSTS_ASK. + * * @param[in] mode Server host key checking mode. */ void nc_client_ssh_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_MODE mode); diff --git a/src/session_client_ch.h b/src/session_client_ch.h index 44bc20bd..c4d28c09 100644 --- a/src/session_client_ch.h +++ b/src/session_client_ch.h @@ -63,6 +63,27 @@ int nc_accept_callhome(int timeout, struct ly_ctx *ctx, struct nc_session **sess * @{ */ +/** + * @brief Set SSH Call Home behaviour of checking the host key and adding/reading entries to/from the known_hosts file. + * + * The default mode is ::NC_SSH_KNOWNHOSTS_ASK. + * + * @param[in] mode Server host key checking mode. + */ +void nc_client_ssh_ch_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_MODE mode); + +/** + * @brief Set SSH Call Home path to the known_hosts file for connections. + * + * Repetetive calling replaces the value. If the given file doesn't exist and the process has sufficient + * rights, it gets created whenever the file is needed, otherwise an error occurs. If NULL is passed or the + * path isn't set, the default known_hosts file will be used. + * + * @param[in] path Path to the known_hosts file. + * @return 0 on success, 1 on error. + */ +int nc_client_ssh_ch_set_knownhosts_path(const char *path); + /** * @brief Set SSH Call Home password authentication callback. * diff --git a/src/session_client_ssh.c b/src/session_client_ssh.c index bc08e63f..676ccdf7 100644 --- a/src/session_client_ssh.c +++ b/src/session_client_ssh.c @@ -401,12 +401,56 @@ nc_client_ssh_do_dnssec_sshfp_check(ssh_session session, enum ssh_keytypes_e srv #endif +/** + * @brief Convert knownhosts mode to string. + * + * @param[in] knownhosts_mode Knownhosts mode. + * @return Knownhosts mode string. + */ +static const char * +nc_client_ssh_knownhosts_mode2str(NC_SSH_KNOWNHOSTS_MODE knownhosts_mode) +{ + const char *mode_str; + + switch (knownhosts_mode) { + case NC_SSH_KNOWNHOSTS_ASK: + mode_str = "ask"; + break; + case NC_SSH_KNOWNHOSTS_STRICT: + mode_str = "strict"; + break; + case NC_SSH_KNOWNHOSTS_ACCEPT_NEW: + mode_str = "accept-new"; + break; + case NC_SSH_KNOWNHOSTS_ACCEPT: + mode_str = "accept"; + break; + case NC_SSH_KNOWNHOSTS_SKIP: + mode_str = "skip"; + break; + default: + mode_str = "unknown"; + break; + } + + return mode_str; +} + +/** + * @brief Perform the hostkey check. + * + * @param[in] hostname Expected hostname. + * @param[in] port Expected port. + * @param[in] knownhosts_mode Knownhosts mode. + * @param[in] session libssh session. + * @return 0 on success, -1 on error. + */ static int -nc_client_ssh_auth_hostkey_check(const char *hostname, uint16_t port, ssh_session session) +nc_client_ssh_auth_hostkey_check(const char *hostname, uint16_t port, + NC_SSH_KNOWNHOSTS_MODE knownhosts_mode, ssh_session session) { char *hexa = NULL; unsigned char *hash_sha1 = NULL; - NC_SSH_KNOWNHOSTS_MODE knownhosts_mode = ssh_opts.knownhosts_mode; enum ssh_keytypes_e srv_pubkey_type; int state; @@ -420,6 +464,8 @@ nc_client_ssh_auth_hostkey_check(const char *hostname, uint16_t port, ssh_sessio int dnssec_ret; #endif + VRB(NULL, "Server hostkey check mode: %s.", nc_client_ssh_knownhosts_mode2str(knownhosts_mode)); + if (knownhosts_mode == NC_SSH_KNOWNHOSTS_SKIP) { /* skip all hostkey checks */ return 0; @@ -744,28 +790,46 @@ sshauth_privkey_passphrase(const char *privkey_path, void *UNUSED(priv)) #endif } -API int -nc_client_ssh_set_knownhosts_path(const char *path) +static int +_nc_client_ssh_set_knownhosts_path(const char *path, struct nc_client_ssh_opts *opts) { - free(ssh_opts.knownhosts_path); + free(opts->knownhosts_path); if (!path) { - ssh_opts.knownhosts_path = NULL; + opts->knownhosts_path = NULL; return 0; } - ssh_opts.knownhosts_path = strdup(path); - NC_CHECK_ERRMEM_RET(!ssh_opts.knownhosts_path, 1); + opts->knownhosts_path = strdup(path); + NC_CHECK_ERRMEM_RET(!opts->knownhosts_path, 1); return 0; } +API int +nc_client_ssh_set_knownhosts_path(const char *path) +{ + return _nc_client_ssh_set_knownhosts_path(path, &ssh_opts); +} + +API int +nc_client_ssh_ch_set_knownhosts_path(const char *path) +{ + return _nc_client_ssh_set_knownhosts_path(path, &ssh_ch_opts); +} + API void nc_client_ssh_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_MODE mode) { ssh_opts.knownhosts_mode = mode; } +API void +nc_client_ssh_ch_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_MODE mode) +{ + ssh_ch_opts.knownhosts_mode = mode; +} + 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) @@ -1254,7 +1318,7 @@ connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts, return -1; } - if (nc_client_ssh_auth_hostkey_check(session->host, session->port, ssh_sess)) { + if (nc_client_ssh_auth_hostkey_check(session->host, session->port, opts->knownhosts_mode, ssh_sess)) { ERR(session, "Checking the host key failed."); return -1; } @@ -1701,7 +1765,6 @@ nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx) struct nc_session *session = NULL; char *buf = NULL; size_t buf_len = 0; - char *known_hosts_path = NULL; /* process parameters */ if (!host || (host[0] == '\0')) { @@ -1727,15 +1790,6 @@ nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx) pw = nc_getpw(0, username, &pw_buf, &buf, &buf_len); } - if (ssh_opts.knownhosts_path) { - /* known_hosts file path was set so use it */ - known_hosts_path = strdup(ssh_opts.knownhosts_path); - NC_CHECK_ERRMEM_GOTO(!known_hosts_path, , fail); - } else if (pw) { - /* path not set explicitly, but current user's username found in /etc/passwd, so create the path */ - NC_CHECK_ERRMEM_GOTO(asprintf(&known_hosts_path, "%s/.ssh/known_hosts", pw->pw_dir) == -1, , fail); - } - /* prepare session structure */ session = nc_new_session(NC_CLIENT, 0); NC_CHECK_ERRMEM_GOTO(!session, , fail); @@ -1757,8 +1811,8 @@ nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx) 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); - if (known_hosts_path) { - ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_path); + if (ssh_opts.knownhosts_path) { + ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_KNOWNHOSTS, ssh_opts.knownhosts_path); } /* create and assign communication socket */ @@ -1800,12 +1854,10 @@ nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx) session->port = port; free(buf); - free(known_hosts_path); return session; fail: free(buf); - free(known_hosts_path); free(ip_host); nc_session_free(session, NULL); return NULL; @@ -1922,6 +1974,10 @@ nc_accept_callhome_ssh_sock(int sock, const char *host, uint16_t port, struct ly ssh_options_set(sess, SSH_OPTIONS_USER, ssh_ch_opts.username); } + if (ssh_ch_opts.knownhosts_path) { + ssh_options_set(sess, SSH_OPTIONS_KNOWNHOSTS, ssh_ch_opts.knownhosts_path); + } + 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 diff --git a/tests/test_ch.c b/tests/test_ch.c index def7da93..451051f1 100644 --- a/tests/test_ch.c +++ b/tests/test_ch.c @@ -132,7 +132,7 @@ client_thread_ssh(void *arg) struct ln2_test_ctx *test_ctx = arg; /* skip all hostkey and known_hosts checks */ - nc_client_ssh_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_SKIP); + nc_client_ssh_ch_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_SKIP); /* set directory where to search for modules */ ret = nc_client_set_schema_searchpath(MODULES_DIR);