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);