Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ target_link_libraries(netconf2 ${CMAKE_THREAD_LIBS_INIT})
# check availability for some pthread functions
set(CMAKE_REQUIRED_LIBRARIES pthread)
check_function_exists(pthread_rwlockattr_setkind_np HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP)
check_function_exists(pthread_timedjoin_np HAVE_PTHREAD_TIMEDJOIN_NP)

# header file compatibility
check_include_file("shadow.h" HAVE_SHADOW)
Expand Down Expand Up @@ -274,6 +275,12 @@ if(ENABLE_SSH_TLS)
list(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBSSH_LIBRARIES})
include_directories(${LIBSSH_INCLUDE_DIRS})

if(LIBSSH_VERSION VERSION_GREATER_EQUAL "0.12.0")
list(APPEND libsrc src/session_server_ssh_auth_callback.c)
else ()
list(APPEND libsrc src/session_server_ssh_auth_message.c)
endif()

# dependencies - libcurl
find_package(CURL 7.30.0 REQUIRED)
if(TARGET CURL::libcurl)
Expand Down
1 change: 1 addition & 0 deletions src/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@

/* Portability feature-check macros. */
#cmakedefine HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP
#cmakedefine HAVE_PTHREAD_TIMEDJOIN_NP

/* Enable IP_FREEBIND/IPV6_FREEBIND on listening sockets. */
#cmakedefine NC_ENABLE_IP_FREEBIND
Expand Down
45 changes: 45 additions & 0 deletions src/session.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

#ifdef NC_ENABLED_SSH_TLS

#include "session_server_ssh_wrapper.h"
#include "session_wrapper.h"

#include <curl/curl.h>
Expand Down Expand Up @@ -923,6 +924,8 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
case NC_TI_SSH: {
int r;
struct nc_session *siter;
void **channel_cbs = NULL, **channel_cbs_tmp;
uint16_t channel_cbs_count = 0, i;

/* There can be multiple NETCONF sessions on the same SSH session (NETCONF session maps to
* SSH channel). So destroy the SSH session only if there is no other NETCONF session using
Expand All @@ -944,6 +947,7 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
}
}
ssh_channel_free(session->ti.libssh.channel);
free(session->ti.libssh.channel_cb);
}

if (session->ti.libssh.next) {
Expand All @@ -965,6 +969,18 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
/* free starting SSH NETCONF session (channel will be freed in ssh_free()) */
free(siter->username);
free(siter->host);
if (siter->ti.libssh.channel_cb) {
/* channel callbacks must stay valid until all the channels are freed in
* ssh_free(), so only collect them here and free them afterwards */
channel_cbs_tmp = realloc(channel_cbs, (channel_cbs_count + 1) * sizeof *channel_cbs);
if (channel_cbs_tmp) {
channel_cbs = channel_cbs_tmp;
channel_cbs[channel_cbs_count++] = siter->ti.libssh.channel_cb;
} else {
/* leak this one struct, the channel would otherwise use freed callbacks */
ERRMEM;
}
}
if (!(siter->flags & NC_SESSION_SHAREDCTX)) {
ly_ctx_destroy((struct ly_ctx *)siter->ctx);
}
Expand All @@ -982,8 +998,24 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
sock = -1;
#endif

#if LIBSSH_0_12
/* Free ssh event before freeing the session */
ssh_event_free(session->ti.libssh.event);
#endif

/* closes sock if set */
ssh_free(session->ti.libssh.session);

/* all the SSH channels were freed now, free their callback data */
for (i = 0; i < channel_cbs_count; ++i) {
free(channel_cbs[i]);
}
free(channel_cbs);

#if LIBSSH_0_12
/* Free callback data after session is destroyed */
nc_server_ssh_cb_data_free(session->ti.libssh.cb_data);
#endif
} else {
/* remove the session from the list */
for (siter = session->ti.libssh.next; siter->ti.libssh.next != session; siter = siter->ti.libssh.next) {}
Expand All @@ -994,6 +1026,19 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
/* there are still multiple sessions, keep the ring list */
siter->ti.libssh.next = session->ti.libssh.next;
}
#if LIBSSH_0_12
/* transfer cb_data to a remaining session so it's freed when the SSH session is freed */
if (session->ti.libssh.cb_data) {
/* the callback data now belongs to the surviving session */
((struct nc_server_ssh_cb_data *)session->ti.libssh.cb_data)->session = siter;
siter->ti.libssh.cb_data = session->ti.libssh.cb_data;
session->ti.libssh.cb_data = NULL;
}
if (session->ti.libssh.event) {
siter->ti.libssh.event = session->ti.libssh.event;
session->ti.libssh.event = NULL;
}
Comment thread
HanzlikPetr marked this conversation as resolved.
#endif
}

/* SESSION IO UNLOCK */
Expand Down
15 changes: 4 additions & 11 deletions src/session_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -887,9 +887,13 @@ struct nc_session {
struct {
ssh_channel channel;
ssh_session session;
struct ssh_channel_callbacks_struct *channel_cb; /**< channel callbacks used in the
callback-based auth (libssh >= 0.12) */
void *cb_data; /**< heap-allocated nc_server_ssh_cb_data (libssh >= 0.12) */
struct nc_session *next; /**< pointer to the next NETCONF session on the same
SSH session, but different SSH channel. If no such session exists, it is NULL.
otherwise there is a ring list of the NETCONF sessions */
ssh_event event; /**< libssh event structure used for the callback-based auth (libssh >= 0.12) */
} libssh;

struct {
Expand Down Expand Up @@ -1426,17 +1430,6 @@ struct nc_session *nc_accept_callhome_ssh_sock(int sock, const char *host, uint1
*/
int nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opts, int sock);

/**
* @brief Process a SSH message.
*
* @param[in] session Session structure of the connection.
* @param[in] opts Endpoint SSH options on which the session was created.
* @param[in] msg SSH message itself.
* @param[in] auth_state State of the authentication.
* @return 0 if the message was handled, 1 if it is left up to libssh.
*/
int nc_session_ssh_msg(struct nc_session *session, struct nc_server_ssh_opts *opts, ssh_message msg, struct nc_auth_state *auth_state);

void nc_client_ssh_destroy_opts(void);
void _nc_client_ssh_destroy_opts(struct nc_client_ssh_opts *opts);

Expand Down
60 changes: 43 additions & 17 deletions src/session_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

#ifdef NC_ENABLED_SSH_TLS

#include "session_server_ssh_wrapper.h"
#include "session_wrapper.h"

#include <curl/curl.h>
Expand Down Expand Up @@ -102,7 +103,6 @@ nc_server_endpt_get(const char *name, struct nc_endpt **endpt)
}
}

ERR(NULL, "Endpoint \"%s\" not found in the configuration.", name);
return 1;
}

Expand Down Expand Up @@ -2294,6 +2294,34 @@ nc_server_send_reply_io(struct nc_session *session, int io_timeout, const struct
return ret;
}

#ifdef NC_ENABLED_SSH_TLS
/**
* @brief Scan the session ring for a newly established NETCONF SSH channel.
*
* @param[in] session Session whose SSH channel ring to scan.
* @return 1 if a new SSH channel is found, 0 otherwise.
*/
static int
nc_ps_ssh_find_new_channel(struct nc_session *session)
{
struct nc_session *new;

if (!session->ti.libssh.next) {
return 0;
}

for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) {
if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel &&
(new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
return 1;
}
}

return 0;
}

#endif /* NC_ENABLED_SSH_TLS */

/**
* @brief Poll a session from pspoll acquiring IO lock as needed.
* Session must be running and session RPC lock held!
Expand All @@ -2317,8 +2345,9 @@ nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mon
uint16_t idle_timeout;

#ifdef NC_ENABLED_SSH_TLS
#if !LIBSSH_0_12
ssh_message ssh_msg;
struct nc_session *new;
#endif
#endif /* NC_ENABLED_SSH_TLS */

/* check timeout first */
Expand All @@ -2341,36 +2370,33 @@ nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mon
switch (session->ti_type) {
#ifdef NC_ENABLED_SSH_TLS
case NC_TI_SSH:
#if LIBSSH_0_12
if (nc_ps_ssh_find_new_channel(session)) {
ret = NC_PSPOLL_SSH_CHANNEL;
break;
}
#else
ssh_msg = ssh_message_get(session->ti.libssh.session);
if (ssh_msg) {
if (nc_session_ssh_msg(session, NULL, ssh_msg, NULL)) {
ssh_message_reply_default(ssh_msg);
}
if (session->ti.libssh.next) {
for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) {
if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel &&
(new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
/* new NETCONF SSH channel */
ret = NC_PSPOLL_SSH_CHANNEL;
break;
}
}
if (new != session) {
ssh_message_free(ssh_msg);
break;
}
if (nc_ps_ssh_find_new_channel(session)) {
ret = NC_PSPOLL_SSH_CHANNEL;
ssh_message_free(ssh_msg);
break;
}
if (!ret) {
/* just some SSH message */
ret = NC_PSPOLL_SSH_MSG;
}
ssh_message_free(ssh_msg);

/* break because 1) we don't want to return anything here ORred with NC_PSPOLL_RPC
* and 2) we don't want to delay openning a new channel by waiting for a RPC to get processed
* and 2) we don't want to delay opening a new channel by waiting for a RPC to get processed
*/
break;
}
#endif

r = ssh_channel_poll_timeout(session->ti.libssh.channel, 0, 0);
if (r == SSH_EOF) {
Expand Down
37 changes: 28 additions & 9 deletions src/session_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -525,26 +525,45 @@ int nc_server_ssh_set_authkey_path_format(const char *path);
* @brief Keyboard interactive authentication callback.
*
* The callback has to handle sending interactive challenges and receiving responses by itself.
* An example callback may fit the following description:
* Prepare all prompts for the user and send them via `ssh_message_auth_interactive_request()`.
* Get the answers either by calling `ssh_message_get()` or `nc_server_ssh_kbdint_get_nanswers()`.
* Return value based on your authentication logic and user answers retrieved by
* calling `ssh_userauth_kbdint_getanswer()`.
* The exact workflow depends on the libssh version the library was compiled with.
*
* **libssh older than 0.12 (message-based workflow):**
* The callback is invoked exactly once per authentication attempt, with the initial
* keyboard-interactive request message. Prepare all prompts for the user and send them via
* `ssh_message_auth_interactive_request()`. Get the answers either by calling `ssh_message_get()`
* or `nc_server_ssh_kbdint_get_nanswers()`, and then `ssh_userauth_kbdint_getanswer()` for each
* of them. Multiple challenge-response rounds can be performed within this single invocation.
*
* **libssh 0.12 and newer (callback-based workflow):**
* Authentication is driven by libssh server callbacks, so this callback is invoked separately
* for every stage of the keyboard-interactive exchange and each invocation must return promptly
* (blocking helpers such as `ssh_message_get()` or `nc_server_ssh_kbdint_get_nanswers()` must
* not be used). Determine the current stage with `ssh_message_auth_kbdint_is_response()`:
* - not a response: send a challenge via `ssh_message_auth_interactive_request()` and return
* `SSH_AUTH_INFO`;
* - a response: retrieve the answers with `ssh_userauth_kbdint_getnanswers()` and
* `ssh_userauth_kbdint_getanswer()` and return the authentication result, or send another
* challenge and return `SSH_AUTH_INFO` to start the next round.
*
* @param[in] session NETCONF session.
* @param[in] ssh_sess libssh session.
* @param[in] msg SSH message that contains the interactive request and which expects a reply with prompts.
* @param[in] msg SSH message with the interactive request (a response message with libssh 0.12+).
* @param[in] user_data Arbitrary user data.
* @return 0 for successful authentication, non-zero to deny the user.
* @return 0 for successful authentication, non-zero to deny the user; with libssh 0.12+
* `SSH_AUTH_INFO` may be returned when a challenge was sent and the client's response
* is expected (the callback is then invoked again once it arrives). With libssh 0.12+,
* `SSH_AUTH_PARTIAL` may also be returned if the method succeeded but more authentication
* methods are required based on the server configuration; if none are required, the
* authentication completes instead of returning a partial success.
*/
typedef int (*nc_server_ssh_interactive_auth_clb)(const struct nc_session *session,
ssh_session ssh_sess, ssh_message msg, void *user_data);

/**
* @brief Set the callback for SSH interactive authentication.
*
* @param[in] auth_clb Keyboard interactive authentication callback. This callback is only called once per authentication.
* @param[in] user_data Optional arbitrary user data that will be passed to @p interactive_auth_clb.
* @param[in] auth_clb Keyboard interactive authentication callback. Called once per authentication (libssh < 0.12) or once per stage (libssh >= 0.12).
* @param[in] user_data Optional arbitrary user data that will be passed to @p auth_clb.
* @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data.
*/
void nc_server_ssh_set_interactive_auth_clb(nc_server_ssh_interactive_auth_clb auth_clb, void *user_data, void (*free_user_data)(void *user_data));
Expand Down
Loading