libnetconf-cpp: support specifying callback for keyboard-interactive auth

Change-Id: Id51e0b65ac2e28698b69a358df3c554579618bf6
diff --git a/src/netconf-client.cpp b/src/netconf-client.cpp
index 73d8433..f1cdfe2 100644
--- a/src/netconf-client.cpp
+++ b/src/netconf-client.cpp
@@ -6,6 +6,7 @@
  *
 */
 
+#include <cstring>
 #include <libyang/Tree_Data.hpp>
 #include <mutex>
 extern "C" {
@@ -13,6 +14,7 @@
 }
 #include <sstream>
 #include "netconf-client.h"
+#include "UniqueResource.h"
 
 namespace libnetconf {
 
@@ -58,6 +60,13 @@
     nc_reply_free(reinterpret_cast<nc_reply*>(reply));
 }
 
+char *ssh_auth_interactive_cb(const char *auth_name, const char *instruction, const char *prompt, int echo, void *priv)
+{
+    const auto cb = static_cast<const client::KbdInteractiveCb*>(priv);
+    auto res = (*cb)(auth_name, instruction, prompt, echo);
+    return ::strdup(res.c_str());
+}
+
 template <auto fn>
 using deleter_from_fn = std::integral_constant<decltype(fn), fn>;
 
@@ -222,6 +231,27 @@
     return session;
 }
 
+std::unique_ptr<Session> Session::connectKbdInteractive(const std::string& host, const uint16_t port, const std::string& user, const KbdInteractiveCb& callback)
+{
+    impl::ClientInit::instance();
+
+    std::lock_guard lk(impl::clientOptions);
+    auto cb_guard = make_unique_resource([user, &callback]() {
+        nc_client_ssh_set_username(user.c_str());
+        nc_client_ssh_set_auth_pref(NC_SSH_AUTH_INTERACTIVE, 5);
+        nc_client_ssh_set_auth_interactive_clb(impl::ssh_auth_interactive_cb, static_cast<void *>(&const_cast<KbdInteractiveCb&>(callback)));
+    }, []() {
+        nc_client_ssh_set_auth_interactive_clb(nullptr, nullptr);
+        nc_client_ssh_set_username(nullptr);
+    });
+
+    auto session = std::make_unique<Session>(nc_connect_ssh(host.c_str(), port, nullptr));
+    if (!session->m_session) {
+        throw std::runtime_error{"nc_connect_ssh failed"};
+    }
+    return session;
+}
+
 std::unique_ptr<Session> Session::connectSocket(const std::string& path)
 {
     impl::ClientInit::instance();
diff --git a/src/netconf-client.h b/src/netconf-client.h
index bd89e6b..1923e71 100644
--- a/src/netconf-client.h
+++ b/src/netconf-client.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <functional>
 #include <libnetconf2/messages_client.h>
 #include <memory>
 #include <optional>
@@ -22,11 +23,14 @@
     ~ReportedError() override;
 };
 
+using KbdInteractiveCb = std::function<std::string(const std::string&, const std::string&, const std::string&, bool)>;
+
 class Session {
 public:
     Session(struct nc_session* session);
     ~Session();
     static std::unique_ptr<Session> connectPubkey(const std::string& host, const uint16_t port, const std::string& user, const std::string& pubPath, const std::string& privPath);
+    static std::unique_ptr<Session> connectKbdInteractive(const std::string& host, const uint16_t port, const std::string& user, const KbdInteractiveCb& callback);
     static std::unique_ptr<Session> connectSocket(const std::string& path);
     std::vector<std::string_view> capabilities() const;
     std::shared_ptr<libyang::Data_Node> getConfig(const NC_DATASTORE datastore,