Merge branch 'PR64' into persistent_timeout_fix_pr
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7ac51f3..37f3834 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,7 +32,7 @@
 # set version
 set(LIBNETCONF2_MAJOR_VERSION 0)
 set(LIBNETCONF2_MINOR_VERSION 11)
-set(LIBNETCONF2_MICRO_VERSION 38)
+set(LIBNETCONF2_MICRO_VERSION 44)
 set(LIBNETCONF2_VERSION ${LIBNETCONF2_MAJOR_VERSION}.${LIBNETCONF2_MINOR_VERSION}.${LIBNETCONF2_MICRO_VERSION})
 set(LIBNETCONF2_SOVERSION ${LIBNETCONF2_MAJOR_VERSION}.${LIBNETCONF2_MINOR_VERSION})
 
diff --git a/schemas/ietf-netconf-acm.yin b/schemas/ietf-netconf-acm.yin
index 390ec0c..3257406 100644
--- a/schemas/ietf-netconf-acm.yin
+++ b/schemas/ietf-netconf-acm.yin
@@ -1,5 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<module xmlns="urn:ietf:params:xml:ns:yang:yin:1" xmlns:nacm="urn:ietf:params:xml:ns:yang:ietf-netconf-acm" xmlns:yang="urn:ietf:params:xml:ns:yang:ietf-yang-types" name="ietf-netconf-acm">
+<module name="ietf-netconf-acm"
+        xmlns="urn:ietf:params:xml:ns:yang:yin:1"
+        xmlns:nacm="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"
+        xmlns:yang="urn:ietf:params:xml:ns:yang:ietf-yang-types">
   <namespace uri="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"/>
   <prefix value="nacm"/>
   <import module="ietf-yang-types">
@@ -9,40 +12,44 @@
     <text>IETF NETCONF (Network Configuration) Working Group</text>
   </organization>
   <contact>
-    <text>WG Web:   &lt;http://tools.ietf.org/wg/netconf/&gt;
+    <text>WG Web:   &lt;https://datatracker.ietf.org/wg/netconf/&gt;
 WG List:  &lt;mailto:netconf@ietf.org&gt;
 
-WG Chair: Mehmet Ersue
-          &lt;mailto:mehmet.ersue@nsn.com&gt;
-
-WG Chair: Bert Wijnen
-          &lt;mailto:bertietf@bwijnen.net&gt;
-
-Editor:   Andy Bierman
+Author:   Andy Bierman
           &lt;mailto:andy@yumaworks.com&gt;
 
-Editor:   Martin Bjorklund
+Author:   Martin Bjorklund
           &lt;mailto:mbj@tail-f.com&gt;</text>
   </contact>
   <description>
-    <text>NETCONF Access Control Model.
+    <text>Network Configuration Access Control Model.
 
-Copyright (c) 2012 IETF Trust and the persons identified as
-authors of the code.  All rights reserved.
+Copyright (c) 2012 - 2018 IETF Trust and the persons
+identified as authors of the code.  All rights reserved.
 
 Redistribution and use in source and binary forms, with or
 without modification, is permitted pursuant to, and subject
 to the license terms contained in, the Simplified BSD
 License set forth in Section 4.c of the IETF Trust's
 Legal Provisions Relating to IETF Documents
-(http://trustee.ietf.org/license-info).
+(https://trustee.ietf.org/license-info).
 
-This version of this YANG module is part of RFC 6536; see
+This version of this YANG module is part of RFC 8341; see
 the RFC itself for full legal notices.</text>
   </description>
+  <revision date="2018-02-14">
+    <description>
+      <text>Added support for YANG 1.1 actions and notifications tied to
+data nodes.  Clarified how NACM extensions can be used by
+other data models.</text>
+    </description>
+    <reference>
+      <text>RFC 8341: Network Configuration Access Control Model</text>
+    </reference>
+  </revision>
   <revision date="2012-02-22">
     <description>
-      <text>Initial version</text>
+      <text>Initial version.</text>
     </description>
     <reference>
       <text>RFC 6536: Network Configuration Protocol (NETCONF)
@@ -54,11 +61,13 @@
       <text>Used to indicate that the data model node
 represents a sensitive security system parameter.
 
-If present, and the NACM module is enabled (i.e.,
-/nacm/enable-nacm object equals 'true'), the NETCONF server
-will only allow the designated 'recovery session' to have
-write access to the node.  An explicit access control rule is
-required for all other users.
+If present, the NETCONF server will only allow the designated
+'recovery session' to have write access to the node.  An
+explicit access control rule is required for all other users.
+
+If the NACM module is used, then it must be enabled (i.e.,
+/nacm/enable-nacm object equals 'true'), or this extension
+is ignored.
 
 The 'default-deny-write' extension MAY appear within a data
 definition statement.  It is ignored otherwise.</text>
@@ -69,11 +78,14 @@
       <text>Used to indicate that the data model node
 controls a very sensitive security system parameter.
 
-If present, and the NACM module is enabled (i.e.,
-/nacm/enable-nacm object equals 'true'), the NETCONF server
-will only allow the designated 'recovery session' to have
-read, write, or execute access to the node.  An explicit
-access control rule is required for all other users.
+If present, the NETCONF server will only allow the designated
+'recovery session' to have read, write, or execute access to
+the node.  An explicit access control rule is required for all
+other users.
+
+If the NACM module is used, then it must be enabled (i.e.,
+/nacm/enable-nacm object equals 'true'), or this extension
+is ignored.
 
 The 'default-deny-all' extension MAY appear within a data
 definition statement, 'rpc' statement, or 'notification'
@@ -85,7 +97,7 @@
       <length value="1..max"/>
     </type>
     <description>
-      <text>General Purpose Username string.</text>
+      <text>General-purpose username string.</text>
     </description>
   </typedef>
   <typedef name="matchall-string-type">
@@ -130,7 +142,7 @@
       </bit>
     </type>
     <description>
-      <text>NETCONF Access Operation.</text>
+      <text>Access operation.</text>
     </description>
   </typedef>
   <typedef name="group-name-type">
@@ -165,35 +177,40 @@
     <type name="yang:xpath1.0"/>
     <description>
       <text>Path expression used to represent a special
-data node instance identifier string.
+data node, action, or notification instance-identifier
+string.
 
 A node-instance-identifier value is an
 unrestricted YANG instance-identifier expression.
-All the same rules as an instance-identifier apply
-except predicates for keys are optional.  If a key
+All the same rules as an instance-identifier apply,
+except that predicates for keys are optional.  If a key
 predicate is missing, then the node-instance-identifier
 represents all possible server instances for that key.
 
-This XPath expression is evaluated in the following context:
+This XML Path Language (XPath) expression is evaluated in the
+following context:
 
- o  The set of namespace declarations are those in scope on
-    the leaf element where this type is used.
+   o  The set of namespace declarations are those in scope on
+      the leaf element where this type is used.
 
- o  The set of variable bindings contains one variable,
-    'USER', which contains the name of the user of the current
-     session.
+   o  The set of variable bindings contains one variable,
+      'USER', which contains the name of the user of the
+      current session.
 
- o  The function library is the core function library, but
-    note that due to the syntax restrictions of an
-    instance-identifier, no functions are allowed.
+   o  The function library is the core function library, but
+      note that due to the syntax restrictions of an
+      instance-identifier, no functions are allowed.
 
- o  The context node is the root node in the data tree.</text>
+   o  The context node is the root node in the data tree.
+
+The accessible tree includes actions and notifications tied
+to data nodes.</text>
     </description>
   </typedef>
   <container name="nacm">
     <nacm:default-deny-all/>
     <description>
-      <text>Parameters for NETCONF Access Control Model.</text>
+      <text>Parameters for NETCONF access control model.</text>
     </description>
     <leaf name="enable-nacm">
       <type name="boolean"/>
@@ -273,12 +290,12 @@
     </leaf>
     <container name="groups">
       <description>
-        <text>NETCONF Access Control Groups.</text>
+        <text>NETCONF access control groups.</text>
       </description>
       <list name="group">
         <key value="name"/>
         <description>
-          <text>One NACM Group Entry.  This list will only contain
+          <text>One NACM group entry.  This list will only contain
 configured entries, not any entries learned from
 any transport protocols.</text>
         </description>
@@ -335,8 +352,8 @@
 Rules are processed in user-defined order until a match is
 found.  A rule matches if 'module-name', 'rule-type', and
 'access-operations' match the request.  If a rule
-matches, the 'action' leaf determines if access is granted
-or not.</text>
+matches, the 'action' leaf determines whether or not
+access is granted.</text>
         </description>
         <leaf name="name">
           <type name="string">
@@ -396,13 +413,14 @@
               <type name="node-instance-identifier"/>
               <mandatory value="true"/>
               <description>
-                <text>Data Node Instance Identifier associated with the
-data node controlled by this rule.
+                <text>Data node instance-identifier associated with the
+data node, action, or notification controlled by
+this rule.
 
-Configuration data or state data instance
-identifiers start with a top-level data node.  A
-complete instance identifier is required for this
-type of path value.
+Configuration data or state data
+instance-identifiers start with a top-level
+data node.  A complete instance-identifier is
+required for this type of path value.
 
 The special value '/' refers to all possible
 datastore contents.</text>
@@ -428,7 +446,7 @@
           <mandatory value="true"/>
           <description>
             <text>The access control action associated with the
-rule.  If a rule is determined to match a
+rule.  If a rule has been determined to match a
 particular request, then this object is used
 to determine whether to permit or deny the
 request.</text>
diff --git a/src/io.c b/src/io.c
index a4598ea..88f6745 100644
--- a/src/io.c
+++ b/src/io.c
@@ -197,7 +197,7 @@
               struct timespec *ts_act_timeout, char **result)
 {
     char *chunk = NULL;
-    size_t size, count = 0, r, len;
+    size_t size, count = 0, r, len, i, matched = 0;
 
     assert(session);
     assert(endtag);
@@ -223,7 +223,7 @@
         }
 
         /* resize buffer if needed */
-        if (count == size) {
+        if ((count + (len - matched)) >= size) {
             /* get more memory */
             size = size + BUFFERSIZE;
             chunk = realloc(chunk, (size + 1) * sizeof *chunk);
@@ -234,21 +234,28 @@
         }
 
         /* get another character */
-        r = nc_read(session, &(chunk[count]), 1, inact_timeout, ts_act_timeout);
-        if (r != 1) {
+        r = nc_read(session, &(chunk[count]), len - matched, inact_timeout, ts_act_timeout);
+        if (r != len - matched) {
             free(chunk);
             return -1;
         }
 
-        count++;
+        count += len - matched;
 
-        /* check endtag */
-        if (count >= len) {
-            if (!strncmp(endtag, &(chunk[count - len]), len)) {
-                /* endtag found */
+        for (i = len - matched; i > 0; i--) {
+            if (!strncmp(&endtag[matched], &(chunk[count - i]), i)) {
+                /*part of endtag found */
+                matched += i;
                 break;
+            } else {
+                matched = 0;
             }
         }
+
+        /* whole endtag found */
+        if (matched == len) {
+            break;
+        }
     }
 
     /* terminating null byte */
@@ -1264,3 +1271,4 @@
 
     return ret;
 }
+
diff --git a/src/session_client.c b/src/session_client.c
index 31cca1a..eb907cd 100644
--- a/src/session_client.c
+++ b/src/session_client.c
@@ -898,6 +898,87 @@
     return NULL;
 }
 
+/*
+   Helper for a non-blocking connect (which is required because of the locking
+   concept for e.g. call home settings). For more details see nc_sock_connect().
+ */
+static int
+_non_blocking_connect(int timeout, int* sock_pending, struct addrinfo *res)
+{
+    int i=1, flags, ret=0;
+    int sock = -1;
+    fd_set  wset;
+    struct timeval ts;
+    int error = 0;
+    socklen_t len = sizeof(int);
+
+    if (sock_pending && *sock_pending != -1) {
+        VRB("Trying to connect the pending socket=%d.", *sock_pending );
+        sock = *sock_pending;
+    } else {
+        assert(res);
+        VRB("Trying to connect via %s.", (res->ai_family == AF_INET6) ? "IPv6" : "IPv4");
+        /* Connect to a server */
+        sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+        if (sock == -1) {
+            ERR("socket couldn't be created.", strerror(errno));
+            return -1;
+        }
+        /* make the socket non-blocking */
+        if (((flags = fcntl(sock, F_GETFL)) == -1) || (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1)) {
+            ERR("Fcntl failed (%s).", strerror(errno));
+            goto cleanup;
+        }
+        /* non-blocking connect! */
+        if (connect(sock, res->ai_addr, res->ai_addrlen) < 0) {
+            if (errno != EINPROGRESS) {
+                /* network connection failed, try another resource */
+                ERR("connect failed: (%s).", strerror(errno));
+                goto cleanup;
+            }
+        }
+        /* check the usability of the socket */
+        if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+            ERR("getsockopt failed: (%s).", strerror(errno));
+            goto cleanup;
+        }
+        if (error == ECONNREFUSED) {
+            /* network connection failed, try another resource */
+            VRB("getsockopt error: (%s).", strerror(error));
+            goto cleanup;
+        }
+    }
+    ts.tv_sec = timeout;
+    ts.tv_usec = 0;
+
+    FD_ZERO(&wset);
+    FD_SET(sock, &wset);
+
+    if ((ret = select(sock + 1, NULL, &wset, NULL, (timeout != -1) ? &ts : NULL)) < 0) {
+        ERR("select failed: (%s).", strerror(errno));
+        goto cleanup;
+    }
+
+    if (ret == 0) {   //we had a timeout
+        VRB("timed out after %ds (%s).", timeout, strerror(errno));
+        if (sock_pending) {
+            /* no sock-close, we'll try it again */
+            *sock_pending = sock;
+        } else {
+            close(sock);
+        }
+        return -1;
+    }
+    return sock;
+
+cleanup:
+    if (sock_pending) {
+        *sock_pending = -1;
+    }
+    close(sock);
+    return -1;
+}
+
 /* A given timeout value limits the time how long the function blocks. If it has to block
    only for some seconds, a socket connection might not yet have been fully established.
    Therefore the active (pending) socket will be stored in *sock_pending, but the return
@@ -909,15 +990,10 @@
 int
 nc_sock_connect(const char* host, uint16_t port, int timeout, int* sock_pending)
 {
-    int i, flags, ret=0;
+    int i;
     int sock = sock_pending?*sock_pending:-1;
-    fd_set  wset;
     struct addrinfo hints, *res_list, *res;
     char port_s[6]; /* length of string representation of short int */
-    struct timeval  ts;
-
-    ts.tv_sec = timeout;
-    ts.tv_usec = 0;
 
     VRB("nc_sock_connect(%s, %u, %d, %d)", host, port, timeout, sock);
 
@@ -936,68 +1012,20 @@
         }
 
         for (res = res_list; res != NULL; res = res->ai_next) {
-            sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
-            if (sock == -1) {
-                /* socket was not created, try another resource */
+            sock = _non_blocking_connect(timeout, sock_pending, res);
+            if (sock == -1 && (!sock_pending || *sock_pending == -1)) {
+                /* try the next resource */
                 continue;
             }
-            /* make the socket non-blocking */
-            if (((flags = fcntl(sock, F_GETFL)) == -1) || (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1)) {
-                ERR("Fcntl failed (%s).", strerror(errno));
-                close(sock);
-                freeaddrinfo(res_list);
-                return -1;
-            }
-            /* enable keep-alive */
-            i = 1;
-            if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof i) == -1) {
-                ERR("Setsockopt failed (%s).", strerror(errno));
-                close(sock);
-                freeaddrinfo(res_list);
-                return -1;
-            }
-            /* non-blocking connect! */
-            if (connect(sock, res->ai_addr, res->ai_addrlen) < 0) {
-                if (errno != EINPROGRESS) {
-                    /* network connection failed, try another resource */
-                    VRB("connect failed: (%s).", strerror(errno));
-                    close(sock);
-                    sock = -1;
-                    continue;
-                }
-            }
+            VRB("Successfully connected to %s:%s over %s.", host, port_s, (res->ai_family == AF_INET6) ? "IPv6" : "IPv4");
+            break;
         }
         freeaddrinfo(res_list);
-    }
-    /* new socket or pending socket */
-    if (sock != -1) {
 
-        FD_ZERO(&wset);
-        FD_SET(sock, &wset);
-
-        if ((ret = select(sock + 1, NULL, &wset, NULL, (timeout != -1) ? &ts : NULL)) < 0) {
-            ERR("select failed: (%s).", strerror(errno));
-            close(sock);
-            return -1;
-        }
-
-        if (ret == 0) {   //we had a timeout
-            VRB("timed out after %ds (%s).", timeout, strerror(errno));
-            /* in that case we need to store it as pending for another attempt */
-            if (sock_pending) {
-                *sock_pending = sock;
-            } else {
-                close(sock);
-            }
-            return -1;
-        }
-
-        if (!FD_ISSET(sock, &wset)) {
-            ERR("FD_ISSET failed: (%s).", strerror(errno));
-            close(sock);
-            return -1;
-        }
-        VRB("Successfully connected to %s:%s.", host, port_s);
+    } else {
+        /* try to get a connection with the pending socket */
+        assert(sock_pending);
+        sock = _non_blocking_connect(timeout, sock_pending, NULL);
     }
 
     return sock;
@@ -1345,7 +1373,11 @@
             rpc_gen = (struct nc_rpc_act_generic *)rpc;
 
             if (rpc_gen->has_data) {
-                rpc_act = rpc_gen->content.data;
+                rpc_act = lyd_dup(rpc_gen->content.data, 1);
+                if (!rpc_act) {
+                    ERR("Failed to duplicate a generic RPC/action.");
+                    return NULL;
+                }
             } else {
                 rpc_act = lyd_parse_mem(ctx, rpc_gen->content.xml_str, LYD_XML, LYD_OPT_RPC | parseroptions, NULL);
                 if (!rpc_act) {
diff --git a/src/session_client_ssh.c b/src/session_client_ssh.c
index 677867a..f9a2630 100644
--- a/src/session_client_ssh.c
+++ b/src/session_client_ssh.c
@@ -1105,11 +1105,16 @@
 
 /* Establish a secure SSH connection and authenticate.
  * Host, port, username, and a connected socket is expected to be set.
+ *
+ * return values
+ *  -1  failure
+ *   0  try again
+ *   1  success
  */
 static int
 connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts, int timeout)
 {
-    int j, ret_auth, userauthlist, ret;
+    int j, ret_auth, userauthlist, ret, attempt = 0;
     NC_SSH_AUTH_TYPE auth;
     int16_t pref;
     const char* prompt;
@@ -1162,6 +1167,9 @@
     } else if (ret_auth == SSH_AUTH_ERROR) {
         ERR("Authentication failed (%s).", ssh_get_error(ssh_sess));
         return -1;
+    } else if (ret_auth == SSH_AUTH_SUCCESS) {
+        WRN("Server accepts \"none\" authentication method.")
+        return 1;
     }
 
     /* check what authentication methods are available */
@@ -1197,7 +1205,11 @@
         }
 
         if (!auth) {
-            ERR("Unable to authenticate to the remote server (no supported authentication methods left).");
+            if (!attempt) {
+                ERR("Unable to authenticate to the remote server (no supported authentication methods detected).");
+            } else {
+                ERR("Unable to authenticate to the remote server (all attempts via supported authentication methods failed).");
+            }
             return -1;
         }
 
@@ -1389,6 +1401,8 @@
             ERRINT;
             return -1;
         }
+
+        attempt++;
     } while (ret_auth != SSH_AUTH_SUCCESS);
 
     return 1;
diff --git a/src/session_p.h b/src/session_p.h
index f28f392..a6dae22 100644
--- a/src/session_p.h
+++ b/src/session_p.h
@@ -169,6 +169,14 @@
     int (*passwd_auth_clb)(const struct nc_session *session, const char *password, void *user_data);
     void *passwd_auth_data;
     void (*passwd_auth_data_free)(void *data);
+
+    int (*pubkey_auth_clb)(const struct nc_session *session, ssh_key key, void *user_data);
+    void *pubkey_auth_data;
+    void (*pubkey_auth_data_free)(void *data);
+
+    int (*interactive_auth_clb)(const struct nc_session *session, ssh_message msg, void *user_data);
+    void *interactive_auth_data;
+    void (*interactive_auth_data_free)(void *data);
 #endif
 #ifdef NC_ENABLED_TLS
     int (*user_verify_clb)(const struct nc_session *session);
diff --git a/src/session_server.c b/src/session_server.c
index fa79a8e..f7a6e94 100644
--- a/src/session_server.c
+++ b/src/session_server.c
@@ -21,6 +21,7 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
+#include <netinet/tcp.h>
 #include <arpa/inet.h>
 #include <unistd.h>
 #include <fcntl.h>
@@ -163,8 +164,7 @@
 int
 nc_sock_listen(const char *address, uint16_t port)
 {
-    const int optVal = 1;
-    const socklen_t optLen = sizeof(optVal);
+    int opt;
     int is_ipv4, sock;
     struct sockaddr_storage saddr;
 
@@ -184,8 +184,14 @@
         goto fail;
     }
 
-    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&optVal, optLen) == -1) {
-        ERR("Could not set socket SO_REUSEADDR socket option (%s).", strerror(errno));
+    opt = 1;
+    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) {
+        ERR("Could not set SO_REUSEADDR socket option (%s).", strerror(errno));
+        goto fail;
+    }
+    opt = 1;
+    if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof opt) == -1) {
+        ERR("Could not set SO_KEEPALIVE option (%s).", strerror(errno));
         goto fail;
     }
 
@@ -331,12 +337,30 @@
     }
 
     /* enable keep-alive */
+#ifdef TCP_KEEPIDLE
     flags = 1;
-    if (setsockopt(ret, SOL_SOCKET, SO_KEEPALIVE, &flags, sizeof flags) == -1) {
+    if (setsockopt(ret, IPPROTO_TCP, TCP_KEEPIDLE, &flags, sizeof flags) == -1) {
         ERR("Setsockopt failed (%s).", strerror(errno));
         close(ret);
         return -1;
     }
+#endif
+#ifdef TCP_KEEPINTVL
+    flags = 5;
+    if (setsockopt(ret, IPPROTO_TCP, TCP_KEEPINTVL, &flags, sizeof flags) == -1) {
+        ERR("Setsockopt failed (%s).", strerror(errno));
+        close(ret);
+        return -1;
+    }
+#endif
+#ifdef TCP_KEEPCNT
+    flags = 10;
+    if (setsockopt(ret, IPPROTO_TCP, TCP_KEEPCNT, &flags, sizeof flags) == -1) {
+        ERR("Setsockopt failed (%s).", strerror(errno));
+        close(ret);
+        return -1;
+    }
+#endif
 
     if (idx) {
         *idx = i;
diff --git a/src/session_server.h b/src/session_server.h
index 106a1a1..a0203ec 100644
--- a/src/session_server.h
+++ b/src/session_server.h
@@ -22,6 +22,12 @@
 #   include <openssl/x509.h>
 #endif
 
+#ifdef NC_ENABLED_SSH
+#   include <libssh/libssh.h>
+#   include <libssh/callbacks.h>
+#   include <libssh/server.h>
+#endif
+
 #include "session.h"
 #include "netconf.h"
 
@@ -508,6 +514,29 @@
                                        void *user_data, void (*free_user_data)(void *user_data));
 
 /**
+ * @brief Set the callback for SSH interactive authentication. If none is set, local system users are used.
+ *
+ * @param[in] interactive_auth_clb Callback that should authenticate the user.
+ *                            Zero return indicates success, non-zero an error.
+ * @param[in] user_data Optional arbitrary user data that will be passed to \p passwd_auth_clb.
+ * @param[in] free_user_data Optional callback that will be called during cleanup to free any \p user_data.
+ */
+void ncserver_ssh_set_interactive_auth_clb(int (*interactive_auth_clb)(const struct nc_session *session, const ssh_message msg,
+                                                              void *user_data),
+                                           void *user_data, void (*free_user_data)(void *user_data));
+
+/**
+ * @brief Set the callback for SSH public key authentication. If none is set, local system users are used.
+ *
+ * @param[in] pubkey_auth_clb Callback that should authenticate the user.
+ *                            Zero return indicates success, non-zero an error.
+ * @param[in] user_data Optional arbitrary user data that will be passed to \p passwd_auth_clb.
+ * @param[in] free_user_data Optional callback that will be called during cleanup to free any \p user_data.
+ */
+ void ncserver_ssh_set_pubkey_auth_clb(int (*pubkey_auth_clb)(const struct nc_session *session, ssh_key key, void *user_data),
+                                       void *user_data, void (*free_user_data)(void *user_data));
+
+/**
  * @brief Set the callback for retrieving host keys. Any RSA, DSA, and ECDSA keys can be added. However,
  *        a maximum of one key of each type will be used during SSH authentication, later keys replacing
  *        the earlier ones.
diff --git a/src/session_server_ssh.c b/src/session_server_ssh.c
index 08614a3..2c19210 100644
--- a/src/session_server_ssh.c
+++ b/src/session_server_ssh.c
@@ -142,6 +142,25 @@
     server_opts.passwd_auth_data_free = free_user_data;
 }
 
+API void
+nc_server_ssh_set_interactive_auth_clb(int (*interactive_auth_clb)(const struct nc_session *session, ssh_message msg, void *user_data),
+                                  void *user_data, void (*free_user_data)(void *user_data))
+{
+    server_opts.interactive_auth_clb = interactive_auth_clb;
+    server_opts.interactive_auth_data = user_data;
+    server_opts.interactive_auth_data_free = free_user_data;
+}
+
+API void
+nc_server_ssh_set_pubkey_auth_clb(int (*pubkey_auth_clb)(const struct nc_session *session, ssh_key key, void *user_data),
+                                  void *user_data, void (*free_user_data)(void *user_data))
+{
+    server_opts.pubkey_auth_clb = pubkey_auth_clb;
+    server_opts.pubkey_auth_data = user_data;
+    server_opts.pubkey_auth_data_free = free_user_data;
+}
+
+
 API int
 nc_server_ssh_ch_client_add_hostkey(const char *client_name, const char *name, int16_t idx)
 {
@@ -825,33 +844,39 @@
 static void
 nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg)
 {
+    int auth_ret = 1;
     char *pass_hash;
 
-    if (!ssh_message_auth_kbdint_is_response(msg)) {
-        const char *prompts[] = {"Password: "};
-        char echo[] = {0};
-
-        ssh_message_auth_interactive_request(msg, "Interactive SSH Authentication", "Type your password:", 1, prompts, echo);
+    if (server_opts.interactive_auth_clb) {
+        auth_ret = server_opts.interactive_auth_clb(session, msg, server_opts.interactive_auth_data);
     } else {
-        if (ssh_userauth_kbdint_getnanswers(session->ti.libssh.session) != 1) {
-            ssh_message_reply_default(msg);
-            return;
-        }
-        pass_hash = auth_password_get_pwd_hash(session->username);
-        if (!pass_hash) {
-            ssh_message_reply_default(msg);
-            return;
-        }
-        if (!auth_password_compare_pwd(pass_hash, ssh_userauth_kbdint_getanswer(session->ti.libssh.session, 0))) {
-            VRB("User \"%s\" authenticated.", session->username);
-            session->flags |= NC_SESSION_SSH_AUTHENTICATED;
-            ssh_message_auth_reply_success(msg, 0);
+        if (!ssh_message_auth_kbdint_is_response(msg)) {
+            const char *prompts[] = {"Password: "};
+            char echo[] = {0};
+
+            ssh_message_auth_interactive_request(msg, "Interactive SSH Authentication", "Type your password:", 1, prompts, echo);
         } else {
-            ++session->opts.server.ssh_auth_attempts;
-            VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
-            ssh_message_reply_default(msg);
+            if (ssh_userauth_kbdint_getnanswers(session->ti.libssh.session) != 1) {// failed session
+                ssh_message_reply_default(msg);
+                return;
+            }
+            pass_hash = auth_password_get_pwd_hash(session->username);// get hashed password
+            if (pass_hash) {
+                auth_ret = auth_password_compare_pwd(pass_hash, ssh_userauth_kbdint_getanswer(session->ti.libssh.session, 0));
+                free(pass_hash);// free hashed password
+            }
         }
-        free(pass_hash);
+    }
+
+    /* Authenticate message based on outcome */
+    if (!auth_ret) {
+        session->flags |= NC_SESSION_SSH_AUTHENTICATED;
+        VRB("User \"%s\" authenticated.", session->username);
+        ssh_message_auth_reply_success(msg, 0);
+    } else {
+        ++session->opts.server.ssh_auth_attempts;
+        VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
+        ssh_message_reply_default(msg);
     }
 }
 
@@ -914,12 +939,18 @@
     const char *username;
     int signature_state;
 
-    if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) {
-        VRB("User \"%s\" tried to use an unknown (unauthorized) public key.", session->username);
-        goto fail;
-    } else if (strcmp(session->username, username)) {
-        VRB("User \"%s\" is not the username identified with the presented public key.", session->username);
-        goto fail;
+    if (server_opts.pubkey_auth_clb) {
+        if (server_opts.pubkey_auth_clb(session, ssh_message_auth_pubkey(msg), server_opts.pubkey_auth_data)) {
+            goto fail;
+        }
+    } else {
+        if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) {
+            VRB("User \"%s\" tried to use an unknown (unauthorized) public key.", session->username);
+            goto fail;
+        } else if (strcmp(session->username, username)) {
+            VRB("User \"%s\" is not the username identified with the presented public key.", session->username);
+            goto fail;
+        }
     }
 
     signature_state = ssh_message_auth_publickey_state(msg);