client session FEATURE hostkey check user clb
diff --git a/src/session_client_ssh.c b/src/session_client_ssh.c
index 2d3197c..4391211 100644
--- a/src/session_client_ssh.c
+++ b/src/session_client_ssh.c
@@ -50,6 +50,7 @@
#include "session_client_ch.h"
#include "libnetconf.h"
+static int sshauth_hostkey_check(const char *hostname, ssh_session session);
static char *sshauth_password(const char *username, const char *hostname);
static char *sshauth_interactive(const char *auth_name, const char *instruction, const char *prompt, int echo);
static char *sshauth_privkey_passphrase(const char* privkey_path);
@@ -58,6 +59,7 @@
static struct nc_client_ssh_opts ssh_opts = {
.auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 3}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 1}},
+ .auth_hostkey_check = sshauth_hostkey_check,
.auth_password = sshauth_password,
.auth_interactive = sshauth_interactive,
.auth_privkey_passphrase = sshauth_privkey_passphrase
@@ -65,6 +67,7 @@
static struct nc_client_ssh_opts ssh_ch_opts = {
.auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 1}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 3}},
+ .auth_hostkey_check = sshauth_hostkey_check,
.auth_password = sshauth_password,
.auth_interactive = sshauth_interactive,
.auth_privkey_passphrase = sshauth_privkey_passphrase
@@ -90,6 +93,222 @@
_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 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("Failed to initialize DNSSEC response parser.");
+ ret = 2;
+ goto finish;
+ }
+
+ if ((i = libsres_msg_getflag(handle, ns_f_rcode))) {
+ ERR("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)) {
+ ERROR("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)) {
+ ERROR("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 */
+
+static int
+sshauth_hostkey_check(const char *hostname, ssh_session session)
+{
+ char *hexa;
+ int c, state, ret;
+ ssh_key srv_pubkey;
+ unsigned char *hash_sha1 = NULL;
+ size_t hlen;
+ enum ssh_keytypes_e srv_pubkey_type;
+ char answer[5];
+
+ state = ssh_is_server_known(session);
+
+ ret = ssh_get_publickey(session, &srv_pubkey);
+ if (ret < 0) {
+ ERR("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("Failed to calculate SHA1 hash of the server public key.");
+ return -1;
+ }
+
+ hexa = ssh_get_hexa(hash_sha1, hlen);
+
+ switch (state) {
+ case SSH_SERVER_KNOWN_OK:
+ break; /* ok */
+
+ case SSH_SERVER_KNOWN_CHANGED:
+ ERR("Remote host key changed, the connection will be terminated!");
+ goto fail;
+
+ case SSH_SERVER_FOUND_OTHER:
+ WRN("Remote host key is not known, but a key of another type for this host is known. Continue with caution.");
+ goto hostkey_not_known;
+
+ case SSH_SERVER_FILE_NOT_FOUND:
+ WRN("Could not find the known hosts file.");
+ goto hostkey_not_known;
+
+ case SSH_SERVER_NOT_KNOWN:
+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 = callback_ssh_hostkey_hash_dnssec_check(hostname, hash_sha1, 2, 1);
+ } else if (srv_pubkey_type == SSH_KEYTYPE_RSA) {
+ ret = callback_ssh_hostkey_hash_dnssec_check(hostname, hash_sha1, 1, 1);
+ } else if (srv_pubkey_type == SSH_KEYTYPE_ECDSA) {
+ ret = callback_ssh_hostkey_hash_dnssec_check(hostname, hash_sha1, 3, 1);
+ }
+
+ /* DNSSEC SSHFP check successful, that's enough */
+ if (!ret) {
+ VRB("DNSSEC SSHFP check successful.");
+ ssh_write_knownhost(session);
+ ssh_clean_pubkey_hash(&hash_sha1);
+ ssh_string_free_char(hexa);
+ return 0;
+ }
+ }
+#endif
+
+ /* try to get result from user */
+ fprintf(stdout, "The authenticity of the host \'%s\' cannot be established.\n", hostname);
+ fprintf(stdout, "%s key fingerprint is %s.\n", ssh_key_type_to_char(srv_pubkey_type), hexa);
+
+#ifdef ENABLE_DNSSEC
+ if (ret == 2) {
+ fprintf(stdout, "No matching host key fingerprint found using DNS.\n");
+ } else if (ret == 1) {
+ fprintf(stdout, "Matching host key fingerprint found using DNS.\n");
+ }
+#endif
+
+ fprintf(stdout, "Are you sure you want to continue connecting (yes/no)? ");
+
+ do {
+ if (fscanf(stdin, "%4s", answer) == EOF) {
+ ERR("fscanf() failed (%s).", strerror(errno));
+ goto fail;
+ }
+ while (((c = getchar()) != EOF) && (c != '\n'));
+
+ fflush(stdin);
+ if (!strcmp("yes", answer)) {
+ /* store the key into the host file */
+ ret = ssh_write_knownhost(session);
+ if (ret != SSH_OK) {
+ WRN("Adding the known host \"%s\" failed (%s).", hostname, ssh_get_error(session));
+ }
+ } else if (!strcmp("no", answer)) {
+ goto fail;
+ } else {
+ fprintf(stdout, "Please type 'yes' or 'no': ");
+ }
+ } while (strcmp(answer, "yes") && strcmp(answer, "no"));
+
+ break;
+
+ case SSH_SERVER_ERROR:
+ ssh_clean_pubkey_hash(&hash_sha1);
+ fprintf(stderr,"%s",ssh_get_error(session));
+ return -1;
+ }
+
+ ssh_clean_pubkey_hash(&hash_sha1);
+ ssh_string_free_char(hexa);
+ return 0;
+
+fail:
+ ssh_clean_pubkey_hash(&hash_sha1);
+ ssh_string_free_char(hexa);
+ return -1;
+}
+
static char *
sshauth_password(const char *username, const char *hostname)
{
@@ -350,222 +569,30 @@
return NULL;
}
-#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 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("Failed to initialize DNSSEC response parser.");
- ret = 2;
- goto finish;
- }
-
- if ((i = libsres_msg_getflag(handle, ns_f_rcode))) {
- ERR("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)) {
- ERROR("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)) {
- ERROR("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 */
-
-static int
-sshauth_hostkey_check(const char *hostname, ssh_session session)
+static void
+_nc_client_ssh_set_auth_hostkey_check_clb(int (*auth_hostkey_check)(const char *hostname, ssh_session session),
+ struct nc_client_ssh_opts *opts)
{
- char *hexa;
- int c, state, ret;
- ssh_key srv_pubkey;
- unsigned char *hash_sha1 = NULL;
- size_t hlen;
- enum ssh_keytypes_e srv_pubkey_type;
- char answer[5];
-
- state = ssh_is_server_known(session);
-
- ret = ssh_get_publickey(session, &srv_pubkey);
- if (ret < 0) {
- ERR("Unable to get server public key.");
- return -1;
+ if (auth_hostkey_check) {
+ opts->auth_hostkey_check = auth_hostkey_check;
+ } else {
+ opts->auth_hostkey_check = sshauth_hostkey_check;
}
-
- 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("Failed to calculate SHA1 hash of the server public key.");
- return -1;
- }
-
- hexa = ssh_get_hexa(hash_sha1, hlen);
-
- switch (state) {
- case SSH_SERVER_KNOWN_OK:
- break; /* ok */
-
- case SSH_SERVER_KNOWN_CHANGED:
- ERR("Remote host key changed, the connection will be terminated!");
- goto fail;
-
- case SSH_SERVER_FOUND_OTHER:
- WRN("Remote host key is not known, but a key of another type for this host is known. Continue with caution.");
- goto hostkey_not_known;
-
- case SSH_SERVER_FILE_NOT_FOUND:
- WRN("Could not find the known hosts file.");
- goto hostkey_not_known;
-
- case SSH_SERVER_NOT_KNOWN:
-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 = callback_ssh_hostkey_hash_dnssec_check(hostname, hash_sha1, 2, 1);
- } else if (srv_pubkey_type == SSH_KEYTYPE_RSA) {
- ret = callback_ssh_hostkey_hash_dnssec_check(hostname, hash_sha1, 1, 1);
- } else if (srv_pubkey_type == SSH_KEYTYPE_ECDSA) {
- ret = callback_ssh_hostkey_hash_dnssec_check(hostname, hash_sha1, 3, 1);
- }
-
- /* DNSSEC SSHFP check successful, that's enough */
- if (!ret) {
- VRB("DNSSEC SSHFP check successful.");
- ssh_write_knownhost(session);
- ssh_clean_pubkey_hash(&hash_sha1);
- ssh_string_free_char(hexa);
- return 0;
- }
- }
-#endif
-
- /* try to get result from user */
- fprintf(stdout, "The authenticity of the host \'%s\' cannot be established.\n", hostname);
- fprintf(stdout, "%s key fingerprint is %s.\n", ssh_key_type_to_char(srv_pubkey_type), hexa);
-
-#ifdef ENABLE_DNSSEC
- if (ret == 2) {
- fprintf(stdout, "No matching host key fingerprint found using DNS.\n");
- } else if (ret == 1) {
- fprintf(stdout, "Matching host key fingerprint found using DNS.\n");
- }
-#endif
-
- fprintf(stdout, "Are you sure you want to continue connecting (yes/no)? ");
-
- do {
- if (fscanf(stdin, "%4s", answer) == EOF) {
- ERR("fscanf() failed (%s).", strerror(errno));
- goto fail;
- }
- while (((c = getchar()) != EOF) && (c != '\n'));
-
- fflush(stdin);
- if (!strcmp("yes", answer)) {
- /* store the key into the host file */
- ret = ssh_write_knownhost(session);
- if (ret != SSH_OK) {
- WRN("Adding the known host \"%s\" failed (%s).", hostname, ssh_get_error(session));
- }
- } else if (!strcmp("no", answer)) {
- goto fail;
- } else {
- fprintf(stdout, "Please type 'yes' or 'no': ");
- }
- } while (strcmp(answer, "yes") && strcmp(answer, "no"));
-
- break;
-
- case SSH_SERVER_ERROR:
- ssh_clean_pubkey_hash(&hash_sha1);
- fprintf(stderr,"%s",ssh_get_error(session));
- return -1;
- }
-
- ssh_clean_pubkey_hash(&hash_sha1);
- ssh_string_free_char(hexa);
- return 0;
-
-fail:
- ssh_clean_pubkey_hash(&hash_sha1);
- ssh_string_free_char(hexa);
- return -1;
}
+API void
+nc_client_ssh_set_auth_hostkey_check_clb(int (*auth_hostkey_check)(const char *hostname, ssh_session session))
+{
+ _nc_client_ssh_set_auth_hostkey_check_clb(auth_hostkey_check, &ssh_opts);
+}
+
+API void
+nc_client_ssh_ch_set_auth_hostkey_check_clb(int (*auth_hostkey_check)(const char *hostname, ssh_session session))
+{
+ _nc_client_ssh_set_auth_hostkey_check_clb(auth_hostkey_check, &ssh_ch_opts);
+}
+
+
static void
_nc_client_ssh_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname),
struct nc_client_ssh_opts *opts)
@@ -933,7 +960,7 @@
return -1;
}
- if (sshauth_hostkey_check(session->host, ssh_sess)) {
+ if (opts->auth_hostkey_check(session->host, ssh_sess)) {
ERR("Checking the host key failed.");
return -1;
}