Skip to content

Commit 30a5925

Browse files
committed
session server ssh UPDATE migrate to callback-based auth for libssh 0.12+
1 parent 0770d85 commit 30a5925

9 files changed

Lines changed: 2529 additions & 1005 deletions

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,12 @@ if(ENABLE_SSH_TLS)
273273
list(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBSSH_LIBRARIES})
274274
include_directories(${LIBSSH_INCLUDE_DIRS})
275275

276+
if(LIBSSH_VERSION VERSION_GREATER_EQUAL "0.12.0")
277+
list(APPEND libsrc src/session_server_ssh_auth_callback.c)
278+
else ()
279+
list(APPEND libsrc src/session_server_ssh_auth_message.c)
280+
endif()
281+
276282
# dependencies - libcurl
277283
find_package(CURL 7.30.0 REQUIRED)
278284
if(TARGET CURL::libcurl)

src/session.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838

3939
#ifdef NC_ENABLED_SSH_TLS
4040

41+
#include "session_server_ssh_wrapper.h"
4142
#include "session_wrapper.h"
4243

4344
#include <curl/curl.h>
@@ -944,6 +945,7 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
944945
}
945946
}
946947
ssh_channel_free(session->ti.libssh.channel);
948+
free(session->ti.libssh.channel_cb);
947949
}
948950

949951
if (session->ti.libssh.next) {
@@ -965,6 +967,7 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
965967
/* free starting SSH NETCONF session (channel will be freed in ssh_free()) */
966968
free(siter->username);
967969
free(siter->host);
970+
free(siter->ti.libssh.channel_cb);
968971
if (!(siter->flags & NC_SESSION_SHAREDCTX)) {
969972
ly_ctx_destroy((struct ly_ctx *)siter->ctx);
970973
}
@@ -982,6 +985,10 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
982985
sock = -1;
983986
#endif
984987

988+
/* free heap-allocated callback data and ssh event (libssh >= 0.12) */
989+
nc_server_ssh_cb_data_free(session->ti.libssh.cb_data);
990+
ssh_event_free(session->ti.libssh.event);
991+
985992
/* closes sock if set */
986993
ssh_free(session->ti.libssh.session);
987994
} else {
@@ -994,6 +1001,15 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
9941001
/* there are still multiple sessions, keep the ring list */
9951002
siter->ti.libssh.next = session->ti.libssh.next;
9961003
}
1004+
/* transfer cb_data to a remaining session so it's freed when the SSH session is freed */
1005+
if (session->ti.libssh.cb_data) {
1006+
siter->ti.libssh.cb_data = session->ti.libssh.cb_data;
1007+
session->ti.libssh.cb_data = NULL;
1008+
}
1009+
if (session->ti.libssh.event) {
1010+
siter->ti.libssh.event = session->ti.libssh.event;
1011+
session->ti.libssh.event = NULL;
1012+
}
9971013
}
9981014

9991015
/* SESSION IO UNLOCK */

src/session_p.h

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -896,9 +896,13 @@ struct nc_session {
896896
struct {
897897
ssh_channel channel;
898898
ssh_session session;
899+
struct ssh_channel_callbacks_struct *channel_cb; /**< channel callbacks used in the
900+
callback-based auth (libssh >= 0.12) */
901+
void *cb_data; /**< heap-allocated nc_server_ssh_cb_data (libssh >= 0.12) */
899902
struct nc_session *next; /**< pointer to the next NETCONF session on the same
900903
SSH session, but different SSH channel. If no such session exists, it is NULL.
901904
otherwise there is a ring list of the NETCONF sessions */
905+
ssh_event event; /**< libssh event structure used for the callback-based auth (libssh >= 0.12) */
902906
} libssh;
903907

904908
struct {
@@ -1435,17 +1439,6 @@ struct nc_session *nc_accept_callhome_ssh_sock(int sock, const char *host, uint1
14351439
*/
14361440
int nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opts, int sock);
14371441

1438-
/**
1439-
* @brief Process a SSH message.
1440-
*
1441-
* @param[in] session Session structure of the connection.
1442-
* @param[in] opts Endpoint SSH options on which the session was created.
1443-
* @param[in] msg SSH message itself.
1444-
* @param[in] auth_state State of the authentication.
1445-
* @return 0 if the message was handled, 1 if it is left up to libssh.
1446-
*/
1447-
int nc_session_ssh_msg(struct nc_session *session, struct nc_server_ssh_opts *opts, ssh_message msg, struct nc_auth_state *auth_state);
1448-
14491442
void nc_client_ssh_destroy_opts(void);
14501443
void _nc_client_ssh_destroy_opts(struct nc_client_ssh_opts *opts);
14511444

src/session_server.c

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "session_p.h"
4343
#include "session_server.h"
4444
#include "session_server_ch.h"
45+
#include "session_server_ssh_wrapper.h"
4546

4647
#ifdef NC_ENABLED_SSH_TLS
4748

@@ -2294,6 +2295,34 @@ nc_server_send_reply_io(struct nc_session *session, int io_timeout, const struct
22942295
return ret;
22952296
}
22962297

2298+
#ifdef NC_ENABLED_SSH_TLS
2299+
/**
2300+
* @brief Scan the session ring for a newly established NETCONF SSH channel.
2301+
*
2302+
* @param[in] session Session whose SSH channel ring to scan.
2303+
* @return Pointer to the new starting NETCONF session if found, NULL otherwise.
2304+
*/
2305+
static struct nc_session *
2306+
nc_ps_ssh_find_new_channel(struct nc_session *session)
2307+
{
2308+
struct nc_session *new;
2309+
2310+
if (!session->ti.libssh.next) {
2311+
return NULL;
2312+
}
2313+
2314+
for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) {
2315+
if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel &&
2316+
(new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
2317+
return new;
2318+
}
2319+
}
2320+
2321+
return NULL;
2322+
}
2323+
2324+
#endif /* NC_ENABLED_SSH_TLS */
2325+
22972326
/**
22982327
* @brief Poll a session from pspoll acquiring IO lock as needed.
22992328
* Session must be running and session RPC lock held!
@@ -2317,8 +2346,9 @@ nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mon
23172346
uint16_t idle_timeout;
23182347

23192348
#ifdef NC_ENABLED_SSH_TLS
2349+
#if !LIBSSH_0_12
23202350
ssh_message ssh_msg;
2321-
struct nc_session *new;
2351+
#endif
23222352
#endif /* NC_ENABLED_SSH_TLS */
23232353

23242354
/* check timeout first */
@@ -2341,36 +2371,33 @@ nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mon
23412371
switch (session->ti_type) {
23422372
#ifdef NC_ENABLED_SSH_TLS
23432373
case NC_TI_SSH:
2374+
#if LIBSSH_0_12
2375+
if (nc_ps_ssh_find_new_channel(session)) {
2376+
ret = NC_PSPOLL_SSH_CHANNEL;
2377+
break;
2378+
}
2379+
#else
23442380
ssh_msg = ssh_message_get(session->ti.libssh.session);
23452381
if (ssh_msg) {
23462382
if (nc_session_ssh_msg(session, NULL, ssh_msg, NULL)) {
23472383
ssh_message_reply_default(ssh_msg);
23482384
}
2349-
if (session->ti.libssh.next) {
2350-
for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) {
2351-
if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel &&
2352-
(new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
2353-
/* new NETCONF SSH channel */
2354-
ret = NC_PSPOLL_SSH_CHANNEL;
2355-
break;
2356-
}
2357-
}
2358-
if (new != session) {
2359-
ssh_message_free(ssh_msg);
2360-
break;
2361-
}
2385+
if (nc_ps_ssh_find_new_channel(session)) {
2386+
ret = NC_PSPOLL_SSH_CHANNEL;
2387+
ssh_message_free(ssh_msg);
2388+
break;
23622389
}
23632390
if (!ret) {
23642391
/* just some SSH message */
23652392
ret = NC_PSPOLL_SSH_MSG;
23662393
}
23672394
ssh_message_free(ssh_msg);
2368-
23692395
/* break because 1) we don't want to return anything here ORred with NC_PSPOLL_RPC
2370-
* and 2) we don't want to delay openning a new channel by waiting for a RPC to get processed
2396+
* and 2) we don't want to delay opening a new channel by waiting for a RPC to get processed
23712397
*/
23722398
break;
23732399
}
2400+
#endif
23742401

23752402
r = ssh_channel_poll_timeout(session->ti.libssh.channel, 0, 0);
23762403
if (r == SSH_EOF) {

src/session_server.h

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -525,26 +525,42 @@ int nc_server_ssh_set_authkey_path_format(const char *path);
525525
* @brief Keyboard interactive authentication callback.
526526
*
527527
* The callback has to handle sending interactive challenges and receiving responses by itself.
528-
* An example callback may fit the following description:
529-
* Prepare all prompts for the user and send them via `ssh_message_auth_interactive_request()`.
530-
* Get the answers either by calling `ssh_message_get()` or `nc_server_ssh_kbdint_get_nanswers()`.
531-
* Return value based on your authentication logic and user answers retrieved by
532-
* calling `ssh_userauth_kbdint_getanswer()`.
528+
* The exact workflow depends on the libssh version the library was compiled with.
529+
*
530+
* **libssh older than 0.12 (message-based workflow):**
531+
* The callback is invoked exactly once per authentication attempt, with the initial
532+
* keyboard-interactive request message. Prepare all prompts for the user and send them via
533+
* `ssh_message_auth_interactive_request()`. Get the answers either by calling `ssh_message_get()`
534+
* or `nc_server_ssh_kbdint_get_nanswers()`, and then `ssh_userauth_kbdint_getanswer()` for each
535+
* of them. Multiple challenge-response rounds can be performed within this single invocation.
536+
*
537+
* **libssh 0.12 and newer (callback-based workflow):**
538+
* Authentication is driven by libssh server callbacks, so this callback is invoked separately
539+
* for every stage of the keyboard-interactive exchange and each invocation must return promptly
540+
* (blocking helpers such as `ssh_message_get()` or `nc_server_ssh_kbdint_get_nanswers()` must
541+
* not be used). Determine the current stage with `ssh_message_auth_kbdint_is_response()`:
542+
* - not a response: send a challenge via `ssh_message_auth_interactive_request()` and return
543+
* `SSH_AUTH_INFO`;
544+
* - a response: retrieve the answers with `ssh_userauth_kbdint_getnanswers()` and
545+
* `ssh_userauth_kbdint_getanswer()` and return the authentication result, or send another
546+
* challenge and return `SSH_AUTH_INFO` to start the next round.
533547
*
534548
* @param[in] session NETCONF session.
535549
* @param[in] ssh_sess libssh session.
536-
* @param[in] msg SSH message that contains the interactive request and which expects a reply with prompts.
550+
* @param[in] msg SSH message with the interactive request (a response message with libssh 0.12+).
537551
* @param[in] user_data Arbitrary user data.
538-
* @return 0 for successful authentication, non-zero to deny the user.
552+
* @return 0 for successful authentication, non-zero to deny the user; with libssh 0.12+
553+
* `SSH_AUTH_INFO` may be returned when a challenge was sent and the client's response
554+
* is expected (the callback is then invoked again once it arrives).
539555
*/
540556
typedef int (*nc_server_ssh_interactive_auth_clb)(const struct nc_session *session,
541557
ssh_session ssh_sess, ssh_message msg, void *user_data);
542558

543559
/**
544560
* @brief Set the callback for SSH interactive authentication.
545561
*
546-
* @param[in] auth_clb Keyboard interactive authentication callback. This callback is only called once per authentication.
547-
* @param[in] user_data Optional arbitrary user data that will be passed to @p interactive_auth_clb.
562+
* @param[in] auth_clb Keyboard interactive authentication callback. Called once per authentication (libssh < 0.12) or once per stage (libssh >= 0.12).
563+
* @param[in] user_data Optional arbitrary user data that will be passed to @p auth_clb.
548564
* @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data.
549565
*/
550566
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));

0 commit comments

Comments
 (0)