client UPDATE set known_hosts file/mode
Added two new API calls, one of them sets the path to the known_hosts
file and the other sets the behaviour of host key checking. Now the
client's host key checking functionality is similar to the one described
in man ssh_config under StrictHostKeyChecking.
diff --git a/src/session_client_ssh.c b/src/session_client_ssh.c
index bacfc13..23ee58c 100644
--- a/src/session_client_ssh.c
+++ b/src/session_client_ssh.c
@@ -167,9 +167,11 @@
}
free(opts->keys);
free(opts->username);
+ free(opts->knownhosts_path);
opts->key_count = 0;
opts->keys = NULL;
opts->username = NULL;
+ opts->knownhosts_path = NULL;
}
void
@@ -275,29 +277,33 @@
#endif /* ENABLE_DNSSEC */
-int
-sshauth_hostkey_check(const char *hostname, ssh_session session, void *UNUSED(priv))
+static int
+nc_client_ssh_update_known_hosts(ssh_session session, const char *hostname)
{
- char *hexa = NULL;
+ int ret;
#if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0))
- int c, ret;
- enum ssh_known_hosts_e state;
+ ret = ssh_session_update_known_hosts(session);
#else
- int c, state, ret;
+ ret = ssh_write_knownhost(session);
#endif
+
+ if (ret != SSH_OK) {
+ WRN(NULL, "Adding the known host \"%s\" failed (%s).", hostname, ssh_get_error(session));
+ }
+
+ return ret;
+}
+
+static int
+nc_client_ssh_get_srv_pubkey_data(ssh_session session, enum ssh_keytypes_e *srv_pubkey_type, char **hexa, unsigned char **hash_sha1)
+{
+ int ret;
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
+ *hexa = NULL;
+ *hash_sha1 = NULL;
#if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 8, 0))
ret = ssh_get_server_publickey(session, &srv_pubkey);
@@ -309,15 +315,86 @@
return -1;
}
- srv_pubkey_type = ssh_key_type(srv_pubkey);
- ret = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA1, &hash_sha1, &hlen);
+ *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);
+ *hexa = ssh_get_hexa(*hash_sha1, hlen);
+ if (!*hexa) {
+ ERR(NULL, "Getting the hostkey's hex string failed.");
+ return -1;
+ }
+
+ return 0;
+}
+
+#ifdef ENABLE_DNSSEC
+static int
+nc_client_ssh_do_dnssec_sshfp_check(ssh_session session, enum ssh_keytypes_e srv_pubkey_type, const char *hostname, unsigned char *hash_sha1)
+{
+ int ret;
+
+ if ((srv_pubkey_type != SSH_KEYTYPE_UNKNOWN) && (srv_pubkey_type != SSH_KEYTYPE_RSA1)) {
+ if (srv_pubkey_type == SSH_KEYTYPE_DSS) {
+ /* TODO else branch? */
+ 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
+ }
+
+ return ret;
+ }
+
+ return 1;
+}
+
+#endif
+
+static int
+nc_client_ssh_auth_hostkey_check(const char *hostname, uint16_t port, 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;
+ char answer[5];
+ FILE *out = NULL, *in = NULL;
+ int c, state;
+
+#ifdef ENABLE_DNSSEC
+ int dnssec_ret;
+#endif
+
+ if (knownhosts_mode == NC_SSH_KNOWNHOSTS_SKIP) {
+ /* skip all hostkey checks */
+ return 0;
+ }
+
+ if (nc_client_ssh_get_srv_pubkey_data(session, &srv_pubkey_type, &hexa, &hash_sha1)) {
+ goto error;
+ }
+
+#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
switch (state) {
#if (LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0))
@@ -332,8 +409,14 @@
#else
case SSH_SERVER_KNOWN_CHANGED:
#endif
- ERR(NULL, "Remote host key changed, the connection will be terminated!");
- goto error;
+ if (knownhosts_mode == NC_SSH_KNOWNHOSTS_ACCEPT) {
+ /* is the mode is set to accept, then accept any connection even if the remote key changed */
+ WRN(NULL, "Remote host key changed, but you have requested accept mode so the connection will not be terminated.");
+ break;
+ } else {
+ 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:
@@ -358,37 +441,39 @@
#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;
- }
+ /* do dnssec check, if it's ok then we're done otherwise continue */
+ dnssec_ret = nc_client_ssh_do_dnssec_sshfp_check(session, srv_pubkey_type, hostname, hash_sha1);
+ if (!dnssec_ret) {
+ ssh_clean_pubkey_hash(&hash_sha1);
+ ssh_string_free_char(hexa);
+ return 0;
}
#endif
+ /* open the files for reading/writing */
if (!(in = nc_open_in(1, NULL))) {
goto error;
}
+
if (!(out = nc_open_out())) {
goto error;
}
+ if (knownhosts_mode == NC_SSH_KNOWNHOSTS_STRICT) {
+ /* do not connect if the hostkey is not present in known_hosts file in this mode */
+ ERR(NULL, "No %s host key is known for [%s]:%hu and you have requested strict checking.\n", ssh_key_type_to_char(srv_pubkey_type), hostname, port);
+ goto error;
+ } else if ((knownhosts_mode == NC_SSH_KNOWNHOSTS_ACCEPT_NEW) || (knownhosts_mode == NC_SSH_KNOWNHOSTS_ACCEPT)) {
+ /* add a new entry to the known_hosts file without prompting */
+ if (nc_client_ssh_update_known_hosts(session, hostname)) {
+ goto error;
+ }
+
+ VRB(NULL, "Permanently added '[%s]:%hu' (%s) to the list of known hosts.", hostname, port, ssh_key_type_to_char(srv_pubkey_type));
+
+ break;
+ }
+
/* 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));
@@ -400,12 +485,12 @@
}
#ifdef ENABLE_DNSSEC
- if (ret == 2) {
+ if (dnssec_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) {
+ } else if (dnssec_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;
@@ -428,15 +513,8 @@
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));
- }
+ /* store the key into the known_hosts file */
+ nc_client_ssh_update_known_hosts(session, hostname);
} else if (!strcmp("no", answer)) {
goto error;
} else {
@@ -645,57 +723,29 @@
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)
+API int
+nc_client_ssh_set_knownhosts_path(const char *path)
{
- 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;
+ free(ssh_opts.knownhosts_path);
+
+ if (!path) {
+ ssh_opts.knownhosts_path = NULL;
+ return 0;
}
-}
-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;
+ ssh_opts.knownhosts_path = strdup(path);
+ if (!ssh_opts.knownhosts_path) {
+ ERRMEM;
+ return 1;
}
- if (priv) {
- (*priv) = opts->auth_hostkey_check_priv;
- }
+
+ return 0;
}
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_knownhosts_mode(NC_SSH_KNOWNHOSTS_MODE mode)
{
- _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);
+ ssh_opts.knownhosts_mode = mode;
}
static void
@@ -1193,7 +1243,7 @@
return -1;
}
- if (opts->auth_hostkey_check(session->host, ssh_sess, opts->auth_hostkey_check_priv)) {
+ if (nc_client_ssh_auth_hostkey_check(session->host, session->port, ssh_sess)) {
ERR(session, "Checking the host key failed.");
return -1;
}
@@ -1572,7 +1622,7 @@
/* remember username */
if (!username) {
if (!opts->username) {
- pw = nc_getpwuid(getuid(), &pw_buf, &buf, &buf_len);
+ pw = nc_getpw(getuid(), NULL, &pw_buf, &buf, &buf_len);
if (!pw) {
ERR(NULL, "Unknown username for the SSH connection (%s).", strerror(errno));
goto fail;
@@ -1646,6 +1696,7 @@
struct nc_session *session = NULL;
char *buf = NULL;
size_t buf_len = 0;
+ char *known_hosts_path = NULL;
/* process parameters */
if (!host || strisempty(host)) {
@@ -1658,7 +1709,7 @@
port_uint = port;
if (!ssh_opts.username) {
- pw = nc_getpwuid(getuid(), &pw_buf, &buf, &buf_len);
+ pw = nc_getpw(getuid(), NULL, &pw_buf, &buf, &buf_len);
if (!pw) {
ERR(session, "Unknown username for the SSH connection (%s).", strerror(errno));
goto fail;
@@ -1667,6 +1718,23 @@
}
} else {
username = ssh_opts.username;
+
+ 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);
+ if (!known_hosts_path) {
+ ERRMEM;
+ goto fail;
+ }
+ } else if (pw) {
+ /* path not set explicitly, but current user's username found in /etc/passwd, so create the path */
+ if (asprintf(&known_hosts_path, "%s/.ssh/known_hosts", pw->pw_dir) == -1) {
+ ERRMEM;
+ goto fail;
+ }
}
/* prepare session structure */
@@ -1690,6 +1758,9 @@
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);
+ }
/* create and assign communication socket */
sock = nc_sock_connect(host, port, -1, &client_opts.ka, NULL, &ip_host);
@@ -1703,6 +1774,7 @@
/* store information for session connection */
session->host = strdup(host);
session->username = strdup(username);
+ session->port = port;
if ((connect_ssh_session(session, &ssh_opts, NC_TRANSPORT_TIMEOUT) != 1) ||
(open_netconf_channel(session, NC_TRANSPORT_TIMEOUT) != 1)) {
goto fail;
@@ -1729,10 +1801,12 @@
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;
@@ -1838,7 +1912,7 @@
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);
+ pw = nc_getpw(getuid(), NULL, &pw_buf, &buf, &buf_len);
if (!pw) {
ERR(NULL, "Unknown username for the SSH connection (%s).", strerror(errno));
ssh_free(sess);