Skip to content

Commit eb8fabc

Browse files
HanzlikPetrmichalvasko
authored andcommitted
session server ssh UPDATE migrate to callback-based auth for libssh 0.12+
1 parent 0df75c6 commit eb8fabc

11 files changed

Lines changed: 3296 additions & 1546 deletions

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ target_link_libraries(netconf2 ${CMAKE_THREAD_LIBS_INIT})
244244
# check availability for some pthread functions
245245
set(CMAKE_REQUIRED_LIBRARIES pthread)
246246
check_function_exists(pthread_rwlockattr_setkind_np HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP)
247+
check_function_exists(pthread_timedjoin_np HAVE_PTHREAD_TIMEDJOIN_NP)
247248

248249
# header file compatibility
249250
check_include_file("shadow.h" HAVE_SHADOW)
@@ -274,6 +275,12 @@ if(ENABLE_SSH_TLS)
274275
list(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBSSH_LIBRARIES})
275276
include_directories(${LIBSSH_INCLUDE_DIRS})
276277

278+
if(LIBSSH_VERSION VERSION_GREATER_EQUAL "0.12.0")
279+
list(APPEND libsrc src/session_server_ssh_auth_callback.c)
280+
else ()
281+
list(APPEND libsrc src/session_server_ssh_auth_message.c)
282+
endif()
283+
277284
# dependencies - libcurl
278285
find_package(CURL 7.30.0 REQUIRED)
279286
if(TARGET CURL::libcurl)

src/config.h.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393

9494
/* Portability feature-check macros. */
9595
#cmakedefine HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP
96+
#cmakedefine HAVE_PTHREAD_TIMEDJOIN_NP
9697

9798
/* Enable IP_FREEBIND/IPV6_FREEBIND on listening sockets. */
9899
#cmakedefine NC_ENABLE_IP_FREEBIND

src/session.c

Lines changed: 45 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>
@@ -923,6 +924,8 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
923924
case NC_TI_SSH: {
924925
int r;
925926
struct nc_session *siter;
927+
void **channel_cbs = NULL, **channel_cbs_tmp;
928+
uint16_t channel_cbs_count = 0, i;
926929

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

949953
if (session->ti.libssh.next) {
@@ -965,6 +969,18 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
965969
/* free starting SSH NETCONF session (channel will be freed in ssh_free()) */
966970
free(siter->username);
967971
free(siter->host);
972+
if (siter->ti.libssh.channel_cb) {
973+
/* channel callbacks must stay valid until all the channels are freed in
974+
* ssh_free(), so only collect them here and free them afterwards */
975+
channel_cbs_tmp = realloc(channel_cbs, (channel_cbs_count + 1) * sizeof *channel_cbs);
976+
if (channel_cbs_tmp) {
977+
channel_cbs = channel_cbs_tmp;
978+
channel_cbs[channel_cbs_count++] = siter->ti.libssh.channel_cb;
979+
} else {
980+
/* leak this one struct, the channel would otherwise use freed callbacks */
981+
ERRMEM;
982+
}
983+
}
968984
if (!(siter->flags & NC_SESSION_SHAREDCTX)) {
969985
ly_ctx_destroy((struct ly_ctx *)siter->ctx);
970986
}
@@ -982,8 +998,24 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
982998
sock = -1;
983999
#endif
9841000

1001+
#if LIBSSH_0_12
1002+
/* Free ssh event before freeing the session */
1003+
ssh_event_free(session->ti.libssh.event);
1004+
#endif
1005+
9851006
/* closes sock if set */
9861007
ssh_free(session->ti.libssh.session);
1008+
1009+
/* all the SSH channels were freed now, free their callback data */
1010+
for (i = 0; i < channel_cbs_count; ++i) {
1011+
free(channel_cbs[i]);
1012+
}
1013+
free(channel_cbs);
1014+
1015+
#if LIBSSH_0_12
1016+
/* Free callback data after session is destroyed */
1017+
nc_server_ssh_cb_data_free(session->ti.libssh.cb_data);
1018+
#endif
9871019
} else {
9881020
/* remove the session from the list */
9891021
for (siter = session->ti.libssh.next; siter->ti.libssh.next != session; siter = siter->ti.libssh.next) {}
@@ -994,6 +1026,19 @@ nc_session_free_transport(struct nc_session *session, int *multisession)
9941026
/* there are still multiple sessions, keep the ring list */
9951027
siter->ti.libssh.next = session->ti.libssh.next;
9961028
}
1029+
#if LIBSSH_0_12
1030+
/* transfer cb_data to a remaining session so it's freed when the SSH session is freed */
1031+
if (session->ti.libssh.cb_data) {
1032+
/* the callback data now belongs to the surviving session */
1033+
((struct nc_server_ssh_cb_data *)session->ti.libssh.cb_data)->session = siter;
1034+
siter->ti.libssh.cb_data = session->ti.libssh.cb_data;
1035+
session->ti.libssh.cb_data = NULL;
1036+
}
1037+
if (session->ti.libssh.event) {
1038+
siter->ti.libssh.event = session->ti.libssh.event;
1039+
session->ti.libssh.event = NULL;
1040+
}
1041+
#endif
9971042
}
9981043

9991044
/* SESSION IO UNLOCK */

src/session_p.h

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -887,9 +887,13 @@ struct nc_session {
887887
struct {
888888
ssh_channel channel;
889889
ssh_session session;
890+
struct ssh_channel_callbacks_struct *channel_cb; /**< channel callbacks used in the
891+
callback-based auth (libssh >= 0.12) */
892+
void *cb_data; /**< heap-allocated nc_server_ssh_cb_data (libssh >= 0.12) */
890893
struct nc_session *next; /**< pointer to the next NETCONF session on the same
891894
SSH session, but different SSH channel. If no such session exists, it is NULL.
892895
otherwise there is a ring list of the NETCONF sessions */
896+
ssh_event event; /**< libssh event structure used for the callback-based auth (libssh >= 0.12) */
893897
} libssh;
894898

895899
struct {
@@ -1426,17 +1430,6 @@ struct nc_session *nc_accept_callhome_ssh_sock(int sock, const char *host, uint1
14261430
*/
14271431
int nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opts, int sock);
14281432

1429-
/**
1430-
* @brief Process a SSH message.
1431-
*
1432-
* @param[in] session Session structure of the connection.
1433-
* @param[in] opts Endpoint SSH options on which the session was created.
1434-
* @param[in] msg SSH message itself.
1435-
* @param[in] auth_state State of the authentication.
1436-
* @return 0 if the message was handled, 1 if it is left up to libssh.
1437-
*/
1438-
int nc_session_ssh_msg(struct nc_session *session, struct nc_server_ssh_opts *opts, ssh_message msg, struct nc_auth_state *auth_state);
1439-
14401433
void nc_client_ssh_destroy_opts(void);
14411434
void _nc_client_ssh_destroy_opts(struct nc_client_ssh_opts *opts);
14421435

src/session_server.c

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545

4646
#ifdef NC_ENABLED_SSH_TLS
4747

48+
#include "session_server_ssh_wrapper.h"
4849
#include "session_wrapper.h"
4950

5051
#include <curl/curl.h>
@@ -102,7 +103,6 @@ nc_server_endpt_get(const char *name, struct nc_endpt **endpt)
102103
}
103104
}
104105

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

@@ -2294,6 +2294,34 @@ nc_server_send_reply_io(struct nc_session *session, int io_timeout, const struct
22942294
return ret;
22952295
}
22962296

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

23192347
#ifdef NC_ENABLED_SSH_TLS
2348+
#if !LIBSSH_0_12
23202349
ssh_message ssh_msg;
2321-
struct nc_session *new;
2350+
#endif
23222351
#endif /* NC_ENABLED_SSH_TLS */
23232352

23242353
/* check timeout first */
@@ -2341,36 +2370,33 @@ nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mon
23412370
switch (session->ti_type) {
23422371
#ifdef NC_ENABLED_SSH_TLS
23432372
case NC_TI_SSH:
2373+
#if LIBSSH_0_12
2374+
if (nc_ps_ssh_find_new_channel(session)) {
2375+
ret = NC_PSPOLL_SSH_CHANNEL;
2376+
break;
2377+
}
2378+
#else
23442379
ssh_msg = ssh_message_get(session->ti.libssh.session);
23452380
if (ssh_msg) {
23462381
if (nc_session_ssh_msg(session, NULL, ssh_msg, NULL)) {
23472382
ssh_message_reply_default(ssh_msg);
23482383
}
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-
}
2384+
if (nc_ps_ssh_find_new_channel(session)) {
2385+
ret = NC_PSPOLL_SSH_CHANNEL;
2386+
ssh_message_free(ssh_msg);
2387+
break;
23622388
}
23632389
if (!ret) {
23642390
/* just some SSH message */
23652391
ret = NC_PSPOLL_SSH_MSG;
23662392
}
23672393
ssh_message_free(ssh_msg);
2368-
23692394
/* 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
2395+
* and 2) we don't want to delay opening a new channel by waiting for a RPC to get processed
23712396
*/
23722397
break;
23732398
}
2399+
#endif
23742400

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

src/session_server.h

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -525,26 +525,45 @@ 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). With libssh 0.12+,
555+
* `SSH_AUTH_PARTIAL` may also be returned if the method succeeded but more authentication
556+
* methods are required based on the server configuration; if none are required, the
557+
* authentication completes instead of returning a partial success.
539558
*/
540559
typedef int (*nc_server_ssh_interactive_auth_clb)(const struct nc_session *session,
541560
ssh_session ssh_sess, ssh_message msg, void *user_data);
542561

543562
/**
544563
* @brief Set the callback for SSH interactive authentication.
545564
*
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.
565+
* @param[in] auth_clb Keyboard interactive authentication callback. Called once per authentication (libssh < 0.12) or once per stage (libssh >= 0.12).
566+
* @param[in] user_data Optional arbitrary user data that will be passed to @p auth_clb.
548567
* @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data.
549568
*/
550569
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)