client session FEATURE ssh auth callbacks can be set
diff --git a/src/session_client_ssh.c b/src/session_client_ssh.c
index 4fc58f8..33c8949 100644
--- a/src/session_client_ssh.c
+++ b/src/session_client_ssh.c
@@ -50,14 +50,24 @@
 #include "session_client_ch.h"
 #include "libnetconf.h"
 
+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);
+
 extern struct nc_client_opts client_opts;
 
 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_pref = {{NC_SSH_AUTH_INTERACTIVE, 3}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 1}},
+    .auth_password = sshauth_password,
+    .auth_interactive = sshauth_interactive,
+    .auth_privkey_passphrase = sshauth_privkey_passphrase
 };
 
 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_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
 };
 
 static void
@@ -264,7 +274,7 @@
 }
 
 static char *
-sshauth_passphrase(const char* privkey_path)
+sshauth_privkey_passphrase(const char* privkey_path)
 {
     char c, *buf, *newbuf;
     int buflen = 1024, len = 0;
@@ -556,6 +566,78 @@
     return -1;
 }
 
+static void
+_nc_client_ssh_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname),
+                                     struct nc_client_ssh_opts *opts)
+{
+    if (auth_password) {
+        opts->auth_password = auth_password;
+    } else {
+        opts->auth_password = sshauth_password;
+    }
+}
+
+API void
+nc_client_ssh_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname))
+{
+    _nc_client_ssh_set_auth_password_clb(auth_password, &ssh_opts);
+}
+
+API void
+nc_client_ssh_ch_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname))
+{
+    _nc_client_ssh_set_auth_password_clb(auth_password, &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),
+                                        struct nc_client_ssh_opts *opts)
+{
+    if (auth_interactive) {
+        opts->auth_interactive = auth_interactive;
+    } else {
+        opts->auth_interactive = sshauth_interactive;
+    }
+}
+
+API void
+nc_client_ssh_set_auth_interactive_clb(char *(*auth_interactive)(const char *auth_name, const char *instruction,
+                                                                  const char *prompt, int echo))
+{
+    _nc_client_ssh_set_auth_interactive_clb(auth_interactive, &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))
+{
+    _nc_client_ssh_set_auth_interactive_clb(auth_interactive, &ssh_ch_opts);
+}
+
+static void
+_nc_client_ssh_set_auth_privkey_passphrase_clb(char *(*auth_privkey_passphrase)(const char *privkey_path),
+                                        struct nc_client_ssh_opts *opts)
+{
+    if (auth_privkey_passphrase) {
+        opts->auth_privkey_passphrase = auth_privkey_passphrase;
+    } else {
+        opts->auth_privkey_passphrase = sshauth_privkey_passphrase;
+    }
+}
+
+API void
+nc_client_ssh_set_auth_privkey_passphrase_clb(char *(*auth_privkey_passphrase)(const char *privkey_path))
+{
+    _nc_client_ssh_set_auth_privkey_passphrase_clb(auth_privkey_passphrase, &ssh_opts);
+}
+
+API void
+nc_client_ssh_ch_set_auth_privkey_passphrase_clb(char *(*auth_privkey_passphrase)(const char *privkey_path))
+{
+    _nc_client_ssh_set_auth_privkey_passphrase_clb(auth_privkey_passphrase, &ssh_ch_opts);
+}
+
 static int
 _nc_client_ssh_add_keypair(const char *pub_key, const char *priv_key, struct nc_client_ssh_opts *opts)
 {
@@ -833,7 +915,7 @@
  * Host, port, username, and a connected socket is expected to be set.
  */
 static int
-connect_ssh_session(struct nc_session *session)
+connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts)
 {
     int j, ret_auth, userauthlist;
     NC_SSH_AUTH_TYPE auth;
@@ -865,15 +947,15 @@
     userauthlist = ssh_userauth_list(ssh_sess, NULL);
 
     /* remove those disabled */
-    if (ssh_opts.auth_pref[0].value < 0) {
+    if (opts->auth_pref[0].value < 0) {
         VRB("Interactive SSH authentication method was disabled.");
         userauthlist &= ~SSH_AUTH_METHOD_INTERACTIVE;
     }
-    if (ssh_opts.auth_pref[1].value < 0) {
+    if (opts->auth_pref[1].value < 0) {
         VRB("Password SSH authentication method was disabled.");
         userauthlist &= ~SSH_AUTH_METHOD_PASSWORD;
     }
-    if (ssh_opts.auth_pref[2].value < 0) {
+    if (opts->auth_pref[2].value < 0) {
         VRB("Publickey SSH authentication method was disabled.");
         userauthlist &= ~SSH_AUTH_METHOD_PUBLICKEY;
     }
@@ -883,13 +965,13 @@
         pref = 0;
         if (userauthlist & SSH_AUTH_METHOD_INTERACTIVE) {
             auth = NC_SSH_AUTH_INTERACTIVE;
-            pref = ssh_opts.auth_pref[0].value;
+            pref = opts->auth_pref[0].value;
         }
-        if ((userauthlist & SSH_AUTH_METHOD_PASSWORD) && (ssh_opts.auth_pref[1].value > pref)) {
+        if ((userauthlist & SSH_AUTH_METHOD_PASSWORD) && (opts->auth_pref[1].value > pref)) {
             auth = NC_SSH_AUTH_PASSWORD;
-            pref = ssh_opts.auth_pref[1].value;
+            pref = opts->auth_pref[1].value;
         }
-        if ((userauthlist & SSH_AUTH_METHOD_PUBLICKEY) && (ssh_opts.auth_pref[2].value > pref)) {
+        if ((userauthlist & SSH_AUTH_METHOD_PUBLICKEY) && (opts->auth_pref[2].value > pref)) {
             auth = NC_SSH_AUTH_PUBLICKEY;
         }
 
@@ -904,7 +986,7 @@
             userauthlist &= ~SSH_AUTH_METHOD_PASSWORD;
 
             VRB("Password authentication (host \"%s\", user \"%s\").", session->host, session->username);
-            s = sshauth_password(session->username, session->host);
+            s = opts->auth_password(session->username, session->host);
             if ((ret_auth = ssh_userauth_password(ssh_sess, session->username, s)) != SSH_AUTH_SUCCESS) {
                 memset(s, 0, strlen(s));
                 VRB("Authentication failed (%s).", ssh_get_error(ssh_sess));
@@ -921,9 +1003,9 @@
                     if (prompt == NULL) {
                         break;
                     }
-                    answer = sshauth_interactive(ssh_userauth_kbdint_getname(ssh_sess),
-                                                 ssh_userauth_kbdint_getinstruction(ssh_sess),
-                                                 prompt, echo);
+                    answer = opts->auth_interactive(ssh_userauth_kbdint_getname(ssh_sess),
+                                                    ssh_userauth_kbdint_getinstruction(ssh_sess),
+                                                    prompt, echo);
                     if (ssh_userauth_kbdint_setanswer(ssh_sess, j, answer) < 0) {
                         free(answer);
                         break;
@@ -943,18 +1025,18 @@
             VRB("Publickey athentication.");
 
             /* if publickeys path not provided, we cannot continue */
-            if (!ssh_opts.key_count) {
+            if (!opts->key_count) {
                 VRB("No key pair specified.");
                 break;
             }
 
-            for (j = 0; j < ssh_opts.key_count; j++) {
+            for (j = 0; j < opts->key_count; j++) {
                 VRB("Trying to authenticate using %spair \"%s\" \"%s\".",
-                     ssh_opts.keys[j].privkey_crypt ? "password-protected " : "", ssh_opts.keys[j].privkey_path,
-                     ssh_opts.keys[j].pubkey_path);
+                     opts->keys[j].privkey_crypt ? "password-protected " : "", opts->keys[j].privkey_path,
+                     opts->keys[j].pubkey_path);
 
-                if (ssh_pki_import_pubkey_file(ssh_opts.keys[j].pubkey_path, &pubkey) != SSH_OK) {
-                    WRN("Failed to import the key \"%s\".", ssh_opts.keys[j].pubkey_path);
+                if (ssh_pki_import_pubkey_file(opts->keys[j].pubkey_path, &pubkey) != SSH_OK) {
+                    WRN("Failed to import the key \"%s\".", opts->keys[j].pubkey_path);
                     continue;
                 }
                 ret_auth = ssh_userauth_try_publickey(ssh_sess, NULL, pubkey);
@@ -968,14 +1050,14 @@
                     break;
                 }
 
-                if (ssh_opts.keys[j].privkey_crypt) {
-                    s = sshauth_passphrase(ssh_opts.keys[j].privkey_path);
+                if (opts->keys[j].privkey_crypt) {
+                    s = opts->auth_privkey_passphrase(opts->keys[j].privkey_path);
                 } else {
                     s = NULL;
                 }
 
-                if (ssh_pki_import_privkey_file(ssh_opts.keys[j].privkey_path, s, NULL, NULL, &privkey) != SSH_OK) {
-                    WRN("Failed to import the key \"%s\".", ssh_opts.keys[j].privkey_path);
+                if (ssh_pki_import_privkey_file(opts->keys[j].privkey_path, s, NULL, NULL, &privkey) != SSH_OK) {
+                    WRN("Failed to import the key \"%s\".", opts->keys[j].privkey_path);
                     if (s) {
                         memset(s, 0, strlen(s));
                         free(s);
@@ -1052,6 +1134,143 @@
     return 0;
 }
 
+static struct nc_session *
+_nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx, struct nc_client_ssh_opts *opts)
+{
+    char *host = NULL, *username = NULL;
+    unsigned short port = 0;
+    int sock;
+    struct passwd *pw;
+    struct nc_session *session = NULL;
+
+    if (!ssh_session) {
+        ERRARG;
+        return NULL;
+    }
+
+    /* prepare session structure */
+    session = calloc(1, sizeof *session);
+    if (!session) {
+        ERRMEM;
+        return NULL;
+    }
+    session->status = NC_STATUS_STARTING;
+    session->side = NC_CLIENT;
+
+    /* transport lock */
+    session->ti_lock = malloc(sizeof *session->ti_lock);
+    if (!session->ti_lock) {
+        ERRMEM;
+        goto fail;
+    }
+    pthread_mutex_init(session->ti_lock, NULL);
+
+    session->ti_type = NC_TI_LIBSSH;
+    session->ti.libssh.session = ssh_session;
+
+    /* was port set? */
+    ssh_options_get_port(ssh_session, (unsigned int *)&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");
+        ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOST, host);
+
+        /* create and connect socket */
+        sock = nc_sock_connect(host, port);
+        if (sock == -1) {
+            goto fail;
+        }
+        ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock);
+    }
+
+    /* 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 = getpwuid(getuid());
+                if (!pw) {
+                    ERR("Unknown username for the SSH connection (%s).", strerror(errno));
+                    goto fail;
+                }
+                username = strdup(pw->pw_name);
+            } else {
+                username = strdup(opts->username);
+            }
+            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)) {
+            goto fail;
+        }
+    }
+
+    /*
+     * Almost done, open a netconf channel. (Transport layer / application layer)
+     */
+    if (open_netconf_channel(session)) {
+        goto fail;
+    }
+
+    /*
+     * SSH session is established and netconf channel opened, create a NETCONF session. (Application layer)
+     */
+
+    /* assign context (dicionary needed for handshake) */
+    if (!ctx) {
+        if (client_opts.schema_searchpath) {
+            ctx = ly_ctx_new(client_opts.schema_searchpath);
+        } else {
+            ctx = ly_ctx_new(SCHEMAS_DIR);
+        }
+    } else {
+        session->flags |= NC_SESSION_SHAREDCTX;
+    }
+    session->ctx = ctx;
+
+    /* NETCONF handshake */
+    if (nc_handshake(session)) {
+        goto fail;
+    }
+    session->status = NC_STATUS_RUNNING;
+
+    if (nc_ctx_check_and_fill(session) == -1) {
+        goto fail;
+    }
+
+    /* store information into the dictionary */
+    if (host) {
+        session->host = lydict_insert_zc(ctx, host);
+    }
+    if (port) {
+        session->port = port;
+    }
+    if (username) {
+        session->username = lydict_insert_zc(ctx, username);
+    }
+
+    return session;
+
+fail:
+    nc_session_free(session);
+    return NULL;
+}
+
 API struct nc_session *
 nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx)
 {
@@ -1131,7 +1350,7 @@
     /* temporarily, for session connection */
     session->host = host;
     session->username = username;
-    if (connect_ssh_session(session) || open_netconf_channel(session)) {
+    if (connect_ssh_session(session, &ssh_opts) || open_netconf_channel(session)) {
         goto fail;
     }
 
@@ -1172,138 +1391,7 @@
 API struct nc_session *
 nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx)
 {
-    char *host = NULL, *username = NULL;
-    unsigned short port = 0;
-    int sock;
-    struct passwd *pw;
-    struct nc_session *session = NULL;
-
-    if (!ssh_session) {
-        ERRARG;
-        return NULL;
-    }
-
-    /* prepare session structure */
-    session = calloc(1, sizeof *session);
-    if (!session) {
-        ERRMEM;
-        return NULL;
-    }
-    session->status = NC_STATUS_STARTING;
-    session->side = NC_CLIENT;
-
-    /* transport lock */
-    session->ti_lock = malloc(sizeof *session->ti_lock);
-    if (!session->ti_lock) {
-        ERRMEM;
-        goto fail;
-    }
-    pthread_mutex_init(session->ti_lock, NULL);
-
-    session->ti_type = NC_TI_LIBSSH;
-    session->ti.libssh.session = ssh_session;
-
-    /* was port set? */
-    ssh_options_get_port(ssh_session, (unsigned int *)&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");
-        ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOST, host);
-
-        /* create and connect socket */
-        sock = nc_sock_connect(host, port);
-        if (sock == -1) {
-            goto fail;
-        }
-        ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock);
-    }
-
-    /* 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 (!ssh_opts.username) {
-                pw = getpwuid(getuid());
-                if (!pw) {
-                    ERR("Unknown username for the SSH connection (%s).", strerror(errno));
-                    goto fail;
-                }
-                username = strdup(pw->pw_name);
-            } else {
-                username = strdup(ssh_opts.username);
-            }
-            ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_USER, username);
-        }
-
-        /* authenticate SSH session */
-        session->host = host;
-        session->username = username;
-        if (connect_ssh_session(session)) {
-            goto fail;
-        }
-    }
-
-    /*
-     * Almost done, open a netconf channel. (Transport layer / application layer)
-     */
-    if (open_netconf_channel(session)) {
-        goto fail;
-    }
-
-    /*
-     * SSH session is established and netconf channel opened, create a NETCONF session. (Application layer)
-     */
-
-    /* assign context (dicionary needed for handshake) */
-    if (!ctx) {
-        if (client_opts.schema_searchpath) {
-            ctx = ly_ctx_new(client_opts.schema_searchpath);
-        } else {
-            ctx = ly_ctx_new(SCHEMAS_DIR);
-        }
-    } else {
-        session->flags |= NC_SESSION_SHAREDCTX;
-    }
-    session->ctx = ctx;
-
-    /* NETCONF handshake */
-    if (nc_handshake(session)) {
-        goto fail;
-    }
-    session->status = NC_STATUS_RUNNING;
-
-    if (nc_ctx_check_and_fill(session) == -1) {
-        goto fail;
-    }
-
-    /* store information into the dictionary */
-    if (host) {
-        session->host = lydict_insert_zc(ctx, host);
-    }
-    if (port) {
-        session->port = port;
-    }
-    if (username) {
-        session->username = lydict_insert_zc(ctx, username);
-    }
-
-    return session;
-
-fail:
-    nc_session_free(session);
-    return NULL;
+    return _nc_connect_libssh(ssh_session, ctx, &ssh_opts);
 }
 
 API struct nc_session *
@@ -1389,6 +1477,7 @@
 {
     const int ssh_timeout = NC_SSH_TIMEOUT;
     struct passwd *pw;
+    struct nc_session *session;
     ssh_session sess;
 
     sess = ssh_new();
@@ -1419,5 +1508,6 @@
         ssh_options_set(sess, SSH_OPTIONS_HOSTKEYS, "ssh-ed25519,ssh-rsa,ssh-dss,ssh-rsa1");
     }
 
-    return nc_connect_libssh(sess, ctx);
+    session = _nc_connect_libssh(sess, ctx, &ssh_ch_opts);
+    return session;
 }