Skip to content
Open
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
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,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
18 changes: 18 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 @@ -944,6 +945,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 +967,7 @@ 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);
free(siter->ti.libssh.channel_cb);
if (!(siter->flags & NC_SESSION_SHAREDCTX)) {
ly_ctx_destroy((struct ly_ctx *)siter->ctx);
}
Expand All @@ -982,6 +985,12 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
sock = -1;
#endif

/* free heap-allocated callback data and ssh event (libssh >= 0.12) */
#if LIBSSH_0_12
nc_server_ssh_cb_data_free(session->ti.libssh.cb_data);
ssh_event_free(session->ti.libssh.event);
#endif

/* closes sock if set */
ssh_free(session->ti.libssh.session);
} else {
Expand All @@ -994,6 +1003,15 @@ 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;
}
/* transfer cb_data to a remaining session so it's freed when the SSH session is freed */
if (session->ti.libssh.cb_data) {
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;
}
}

/* 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 @@ -896,9 +896,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 @@ -1435,17 +1439,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
59 changes: 43 additions & 16 deletions src/session_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "session_p.h"
#include "session_server.h"
#include "session_server_ch.h"
#include "session_server_ssh_wrapper.h"

#ifdef NC_ENABLED_SSH_TLS

Expand Down Expand Up @@ -2294,6 +2295,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 Pointer to the new starting NETCONF session if found, NULL otherwise.
*/
static struct nc_session *
nc_ps_ssh_find_new_channel(struct nc_session *session)
{
struct nc_session *new;

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

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 new;
}
}

return NULL;
}

#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 +2346,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 +2371,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
34 changes: 25 additions & 9 deletions src/session_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -525,26 +525,42 @@ 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).
*/
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