blob: 8304a4d3f872440d946388b923bfa64c751469d2 [file] [log] [blame]
Radek Krejci206fcd62015-10-07 15:42:48 +02001/**
Michal Vasko95ea9ff2021-11-09 12:29:14 +01002 * @file session.c
3 * @author Michal Vasko <mvasko@cesnet.cz>
4 * @brief libnetconf2 - general session functions
Radek Krejci206fcd62015-10-07 15:42:48 +02005 *
Michal Vasko95ea9ff2021-11-09 12:29:14 +01006 * @copyright
Michal Vaskod8a74192023-02-06 15:51:50 +01007 * Copyright (c) 2015 - 2023 CESNET, z.s.p.o.
Radek Krejci206fcd62015-10-07 15:42:48 +02008 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +01009 * This source code is licensed under BSD 3-Clause License (the "License").
10 * You may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
Michal Vaskoafd416b2016-02-25 14:51:46 +010012 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +010013 * https://opensource.org/licenses/BSD-3-Clause
Radek Krejci206fcd62015-10-07 15:42:48 +020014 */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +020015#define _GNU_SOURCE
Radek Krejci206fcd62015-10-07 15:42:48 +020016
Michal Vasko18aeb5d2017-02-17 09:23:56 +010017#include <assert.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020018#include <ctype.h>
Radek Krejci206fcd62015-10-07 15:42:48 +020019#include <errno.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020020#include <libyang/libyang.h>
roman3c5b75d2024-04-18 14:56:53 +020021#include <netinet/in.h>
22#include <netinet/tcp.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020023#include <pthread.h>
24#include <stdlib.h>
25#include <string.h>
26#include <sys/socket.h>
27#include <sys/time.h>
28#include <sys/types.h>
Michal Vasko58f31552016-01-19 12:39:15 +010029#include <time.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020030#include <unistd.h>
Radek Krejci206fcd62015-10-07 15:42:48 +020031
Michal Vasko9e8ac262020-04-07 13:06:45 +020032#include "compat.h"
roman3f9b65c2023-06-05 14:26:58 +020033#include "config.h"
34#include "log_p.h"
35#include "netconf.h"
36#include "session_p.h"
Michal Vaskob48aa812016-01-18 14:13:09 +010037
roman2eab4742023-06-06 10:00:26 +020038#ifdef NC_ENABLED_SSH_TLS
Radek Krejci206fcd62015-10-07 15:42:48 +020039
roman2eab4742023-06-06 10:00:26 +020040#include <libssh/libssh.h>
roman008cfe72024-04-05 12:36:18 +020041#include "session_wrapper.h"
Radek Krejci206fcd62015-10-07 15:42:48 +020042
roman2eab4742023-06-06 10:00:26 +020043#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoc14e3c82016-01-11 16:14:30 +010044
Michal Vasko086311b2016-01-08 09:53:11 +010045/* in seconds */
46#define NC_CLIENT_HELLO_TIMEOUT 60
Michal Vaskocf898172024-01-15 15:04:28 +010047#define NC_SERVER_CH_HELLO_TIMEOUT 180
Radek Krejci695d4fa2015-10-22 13:23:54 +020048
Michal Vasko05ba9df2016-01-13 14:40:27 +010049/* in milliseconds */
50#define NC_CLOSE_REPLY_TIMEOUT 200
51
Michal Vasko086311b2016-01-08 09:53:11 +010052extern struct nc_server_opts server_opts;
53
Michal Vaskod8a74192023-02-06 15:51:50 +010054void
55nc_timeouttime_get(struct timespec *ts, uint32_t add_ms)
Radek Krejci7ac16052016-07-15 11:48:18 +020056{
Michal Vaskod8a74192023-02-06 15:51:50 +010057 if (clock_gettime(COMPAT_CLOCK_ID, ts) == -1) {
58 ERR(NULL, "clock_gettime() failed (%s).", strerror(errno));
59 return;
60 }
61
62 if (!add_ms) {
63 return;
64 }
65
66 assert((ts->tv_nsec >= 0) && (ts->tv_nsec < 1000000000L));
67
68 ts->tv_sec += add_ms / 1000;
69 ts->tv_nsec += (add_ms % 1000) * 1000000L;
70
71 if (ts->tv_nsec >= 1000000000L) {
72 ++ts->tv_sec;
73 ts->tv_nsec -= 1000000000L;
74 } else if (ts->tv_nsec < 0) {
75 --ts->tv_sec;
76 ts->tv_nsec += 1000000000L;
77 }
78
79 assert((ts->tv_nsec >= 0) && (ts->tv_nsec < 1000000000L));
80}
81
82int32_t
83nc_timeouttime_cur_diff(const struct timespec *ts)
84{
85 struct timespec cur;
86 int64_t nsec_diff = 0;
87
88 nc_timeouttime_get(&cur, 0);
89
90 nsec_diff += (((int64_t)ts->tv_sec) - ((int64_t)cur.tv_sec)) * 1000000000L;
91 nsec_diff += ((int64_t)ts->tv_nsec) - ((int64_t)cur.tv_nsec);
92
93 return nsec_diff / 1000000L;
94}
95
96void
97nc_realtime_get(struct timespec *ts)
98{
roman6ece9c52022-06-22 09:29:17 +020099 if (clock_gettime(CLOCK_REALTIME, ts)) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100100 ERR(NULL, "clock_gettime() failed (%s).", strerror(errno));
101 return;
roman6ece9c52022-06-22 09:29:17 +0200102 }
Michal Vasko36c7be82017-02-22 13:37:59 +0100103}
104
Michal Vasko63b92d62024-04-29 10:04:56 +0200105int
106nc_poll(struct pollfd *pfd, uint16_t pfd_count, int timeout_ms)
107{
108 int rc;
109 struct timespec start_ts;
110
111 if (timeout_ms > 0) {
112 /* get current time */
113 nc_timeouttime_get(&start_ts, 0);
114 }
115
116 do {
117 /* poll */
118 rc = poll(pfd, pfd_count, timeout_ms);
119
120 if (timeout_ms > 0) {
121 /* adjust the timeout by subtracting the elapsed time (relevant in case of EINTR) */
122 timeout_ms += nc_timeouttime_cur_diff(&start_ts);
123 if (timeout_ms < 0) {
124 /* manual timeout */
125 rc = 0;
126 errno = 0;
127 break;
128 }
129 }
130 } while ((rc == -1) && (errno == EINTR));
131
132 if (rc == -1) {
133 ERR(NULL, "Poll failed (%s).", strerror(errno));
134 }
135 return rc;
136}
137
roman2eab4742023-06-06 10:00:26 +0200138#ifdef NC_ENABLED_SSH_TLS
139
Michal Vaskoddce1212019-05-24 09:58:49 +0200140const char *
roman3f9b65c2023-06-05 14:26:58 +0200141nc_privkey_format_to_str(NC_PRIVKEY_FORMAT format)
Michal Vaskoddce1212019-05-24 09:58:49 +0200142{
roman3f9b65c2023-06-05 14:26:58 +0200143 switch (format) {
144 case NC_PRIVKEY_FORMAT_RSA:
romand0fe5952024-03-21 15:59:33 +0100145 return " RSA ";
roman3f9b65c2023-06-05 14:26:58 +0200146 case NC_PRIVKEY_FORMAT_EC:
romand0fe5952024-03-21 15:59:33 +0100147 return " EC ";
roman3f9b65c2023-06-05 14:26:58 +0200148 case NC_PRIVKEY_FORMAT_X509:
romand0fe5952024-03-21 15:59:33 +0100149 return " ";
roman3f9b65c2023-06-05 14:26:58 +0200150 case NC_PRIVKEY_FORMAT_OPENSSH:
romand0fe5952024-03-21 15:59:33 +0100151 return " OPENSSH ";
Michal Vaskoddce1212019-05-24 09:58:49 +0200152 default:
roman3f9b65c2023-06-05 14:26:58 +0200153 return NULL;
Michal Vaskoddce1212019-05-24 09:58:49 +0200154 }
Michal Vaskoddce1212019-05-24 09:58:49 +0200155}
156
roman13145912023-08-17 15:36:54 +0200157int
roman51a1e192023-09-14 10:13:45 +0200158nc_is_pk_subject_public_key_info(const char *b64)
159{
160 int ret = 0;
161 long len;
romanc95a14f2024-04-23 15:13:00 +0200162 unsigned char *bin = NULL, *tmp;
roman51a1e192023-09-14 10:13:45 +0200163
roman9ba26aa2024-04-05 12:31:54 +0200164 /* decode base64 */
165 len = nc_base64_decode_wrap(b64, &bin);
roman51a1e192023-09-14 10:13:45 +0200166 if (len == -1) {
roman51a1e192023-09-14 10:13:45 +0200167 ret = -1;
168 goto cleanup;
169 }
170
171 /* for deallocation later */
172 tmp = bin;
173
roman9ba26aa2024-04-05 12:31:54 +0200174 /* try to parse the supposed SubjectPublicKeyInfo binary data */
romanc95a14f2024-04-23 15:13:00 +0200175 if (nc_tls_is_der_subpubkey_wrap(tmp, len)) {
roman9ba26aa2024-04-05 12:31:54 +0200176 /* success, it's most likely SubjectPublicKeyInfo */
roman51a1e192023-09-14 10:13:45 +0200177 ret = 1;
178 } else {
roman9ba26aa2024-04-05 12:31:54 +0200179 /* it's most likely not SubjectPublicKeyInfo */
roman51a1e192023-09-14 10:13:45 +0200180 ret = 0;
181 }
182
183cleanup:
roman51a1e192023-09-14 10:13:45 +0200184 free(bin);
185 return ret;
186}
187
roman2eab4742023-06-06 10:00:26 +0200188#endif /* NC_ENABLED_SSH_TLS */
189
Michal Vaskobe52dc22018-10-17 09:28:17 +0200190int
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +0200191nc_sock_configure_ka(int sock, const struct nc_keepalives *ka)
Michal Vaskobe52dc22018-10-17 09:28:17 +0200192{
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +0200193 int opt;
194
195 opt = ka->enabled;
196 if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof opt) == -1) {
roman56acd552024-04-18 16:00:37 +0200197 ERR(NULL, "Failed to set SO_KEEPALIVE (%s).", strerror(errno));
Michal Vaskobe52dc22018-10-17 09:28:17 +0200198 return -1;
199 }
roman56acd552024-04-18 16:00:37 +0200200
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +0200201 if (!ka->enabled) {
202 return 0;
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200203 }
Michal Vaskobe52dc22018-10-17 09:28:17 +0200204
205#ifdef TCP_KEEPIDLE
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +0200206 opt = ka->idle_time;
207 if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &opt, sizeof opt) == -1) {
roman56acd552024-04-18 16:00:37 +0200208 ERR(NULL, "Failed to set TCP_KEEPIDLE (%s).", strerror(errno));
Michal Vaskobe52dc22018-10-17 09:28:17 +0200209 return -1;
210 }
211#endif
212
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200213#ifdef TCP_KEEPCNT
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +0200214 opt = ka->max_probes;
215 if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &opt, sizeof opt) == -1) {
roman56acd552024-04-18 16:00:37 +0200216 ERR(NULL, "Failed to set TCP_KEEPCNT (%s).", strerror(errno));
Michal Vaskobe52dc22018-10-17 09:28:17 +0200217 return -1;
218 }
219#endif
220
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200221#ifdef TCP_KEEPINTVL
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +0200222 opt = ka->probe_interval;
223 if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &opt, sizeof opt) == -1) {
roman56acd552024-04-18 16:00:37 +0200224 ERR(NULL, "Failed to set TCP_KEEPINTVL (%s).", strerror(errno));
Michal Vaskobe52dc22018-10-17 09:28:17 +0200225 return -1;
226 }
227#endif
228
229 return 0;
230}
231
Michal Vaskoade892d2017-02-22 13:40:35 +0100232struct nc_session *
Michal Vasko131120a2018-05-29 15:44:02 +0200233nc_new_session(NC_SIDE side, int shared_ti)
Michal Vaskoade892d2017-02-22 13:40:35 +0100234{
235 struct nc_session *sess;
Michal Vaskocf898172024-01-15 15:04:28 +0100236 struct timespec ts_cur;
Michal Vaskoade892d2017-02-22 13:40:35 +0100237
238 sess = calloc(1, sizeof *sess);
239 if (!sess) {
240 return NULL;
241 }
242
Michal Vasko131120a2018-05-29 15:44:02 +0200243 sess->side = side;
244
245 if (side == NC_SERVER) {
Michal Vaskodf68e7e2022-04-21 11:04:00 +0200246 pthread_mutex_init(&sess->opts.server.ntf_status_lock, NULL);
Michal Vaskoacf98472021-02-04 15:33:57 +0100247 pthread_mutex_init(&sess->opts.server.rpc_lock, NULL);
248 pthread_cond_init(&sess->opts.server.rpc_cond, NULL);
Michal Vaskoacf98472021-02-04 15:33:57 +0100249
250 pthread_mutex_init(&sess->opts.server.ch_lock, NULL);
251 pthread_cond_init(&sess->opts.server.ch_cond, NULL);
Michal Vaskocf898172024-01-15 15:04:28 +0100252
253 /* initialize last_rpc for idle_timeout */
254 nc_timeouttime_get(&ts_cur, 0);
255 sess->opts.server.last_rpc = ts_cur.tv_sec;
tadeas-vintrlik54f142a2021-08-23 10:36:18 +0200256 } else {
257 pthread_mutex_init(&sess->opts.client.msgs_lock, NULL);
Michal Vasko131120a2018-05-29 15:44:02 +0200258 }
259
260 if (!shared_ti) {
261 sess->io_lock = malloc(sizeof *sess->io_lock);
262 if (!sess->io_lock) {
263 goto error;
264 }
265 pthread_mutex_init(sess->io_lock, NULL);
Michal Vaskoade892d2017-02-22 13:40:35 +0100266 }
267
268 return sess;
Michal Vasko131120a2018-05-29 15:44:02 +0200269
270error:
Michal Vasko131120a2018-05-29 15:44:02 +0200271 free(sess);
272 return NULL;
Michal Vaskoade892d2017-02-22 13:40:35 +0100273}
274
Michal Vasko96164bf2016-01-21 15:41:58 +0100275/*
276 * @return 1 - success
277 * 0 - timeout
278 * -1 - error
279 */
280int
Michal Vasko131120a2018-05-29 15:44:02 +0200281nc_session_rpc_lock(struct nc_session *session, int timeout, const char *func)
Michal Vasko96164bf2016-01-21 15:41:58 +0100282{
283 int ret;
Michal Vasko62be1ce2016-03-03 13:24:52 +0100284 struct timespec ts_timeout;
Michal Vasko96164bf2016-01-21 15:41:58 +0100285
Michal Vasko131120a2018-05-29 15:44:02 +0200286 if (session->side != NC_SERVER) {
287 ERRINT;
288 return -1;
289 }
290
Michal Vasko96164bf2016-01-21 15:41:58 +0100291 if (timeout > 0) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100292 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko96164bf2016-01-21 15:41:58 +0100293
Michal Vaskoade892d2017-02-22 13:40:35 +0100294 /* LOCK */
Michal Vaskod8a74192023-02-06 15:51:50 +0100295 ret = pthread_mutex_clocklock(&session->opts.server.rpc_lock, COMPAT_CLOCK_ID, &ts_timeout);
Michal Vaskoade892d2017-02-22 13:40:35 +0100296 if (!ret) {
Michal Vaskoacf98472021-02-04 15:33:57 +0100297 while (session->opts.server.rpc_inuse) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100298 ret = pthread_cond_clockwait(&session->opts.server.rpc_cond, &session->opts.server.rpc_lock,
299 COMPAT_CLOCK_ID, &ts_timeout);
Michal Vaskoade892d2017-02-22 13:40:35 +0100300 if (ret) {
Michal Vaskoacf98472021-02-04 15:33:57 +0100301 pthread_mutex_unlock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100302 break;
303 }
304 }
305 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100306 } else if (!timeout) {
Michal Vaskoade892d2017-02-22 13:40:35 +0100307 /* LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100308 ret = pthread_mutex_trylock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100309 if (!ret) {
Michal Vaskoacf98472021-02-04 15:33:57 +0100310 if (session->opts.server.rpc_inuse) {
311 pthread_mutex_unlock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100312 return 0;
313 }
Michal vasko2f8e4b52016-10-05 13:04:11 +0200314 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100315 } else { /* timeout == -1 */
Michal Vaskoade892d2017-02-22 13:40:35 +0100316 /* LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100317 ret = pthread_mutex_lock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100318 if (!ret) {
Michal Vaskoacf98472021-02-04 15:33:57 +0100319 while (session->opts.server.rpc_inuse) {
320 ret = pthread_cond_wait(&session->opts.server.rpc_cond, &session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100321 if (ret) {
Michal Vaskoacf98472021-02-04 15:33:57 +0100322 pthread_mutex_unlock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100323 break;
324 }
325 }
326 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100327 }
328
Michal Vaskoade892d2017-02-22 13:40:35 +0100329 if (ret) {
330 if ((ret == EBUSY) || (ret == ETIMEDOUT)) {
331 /* timeout */
332 return 0;
333 }
334
Michal Vasko96164bf2016-01-21 15:41:58 +0100335 /* error */
Michal Vasko05532772021-06-03 12:12:38 +0200336 ERR(session, "%s: failed to RPC lock a session (%s).", func, strerror(ret));
Michal Vasko96164bf2016-01-21 15:41:58 +0100337 return -1;
338 }
339
340 /* ok */
Michal Vaskoacf98472021-02-04 15:33:57 +0100341 assert(session->opts.server.rpc_inuse == 0);
342 session->opts.server.rpc_inuse = 1;
Michal Vaskoade892d2017-02-22 13:40:35 +0100343
344 /* UNLOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100345 ret = pthread_mutex_unlock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100346 if (ret) {
347 /* error */
Michal Vasko05532772021-06-03 12:12:38 +0200348 ERR(session, "%s: failed to RPC unlock a session (%s).", func, strerror(ret));
Michal Vaskoade892d2017-02-22 13:40:35 +0100349 return -1;
350 }
351
352 return 1;
353}
354
355int
Michal Vasko131120a2018-05-29 15:44:02 +0200356nc_session_rpc_unlock(struct nc_session *session, int timeout, const char *func)
Michal Vaskoade892d2017-02-22 13:40:35 +0100357{
358 int ret;
359 struct timespec ts_timeout;
360
Michal Vasko131120a2018-05-29 15:44:02 +0200361 if (session->side != NC_SERVER) {
362 ERRINT;
363 return -1;
364 }
365
Michal Vaskoacf98472021-02-04 15:33:57 +0100366 assert(session->opts.server.rpc_inuse);
Michal Vaskoade892d2017-02-22 13:40:35 +0100367
368 if (timeout > 0) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100369 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vaskoade892d2017-02-22 13:40:35 +0100370
371 /* LOCK */
Michal Vaskod8a74192023-02-06 15:51:50 +0100372 ret = pthread_mutex_clocklock(&session->opts.server.rpc_lock, COMPAT_CLOCK_ID, &ts_timeout);
Michal Vaskoade892d2017-02-22 13:40:35 +0100373 } else if (!timeout) {
374 /* LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100375 ret = pthread_mutex_trylock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100376 } else { /* timeout == -1 */
377 /* LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100378 ret = pthread_mutex_lock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100379 }
380
381 if (ret && (ret != EBUSY) && (ret != ETIMEDOUT)) {
382 /* error */
Michal Vasko05532772021-06-03 12:12:38 +0200383 ERR(session, "%s: failed to RPC lock a session (%s).", func, strerror(ret));
Michal Vaskoade892d2017-02-22 13:40:35 +0100384 return -1;
385 } else if (ret) {
Michal Vasko69e98752022-12-14 14:20:17 +0100386 WRN(session, "%s: session RPC lock timeout, should not happen.", func);
Michal Vaskoade892d2017-02-22 13:40:35 +0100387 }
388
Michal Vaskoacf98472021-02-04 15:33:57 +0100389 session->opts.server.rpc_inuse = 0;
390 pthread_cond_signal(&session->opts.server.rpc_cond);
Michal Vaskoade892d2017-02-22 13:40:35 +0100391
392 if (!ret) {
393 /* UNLOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100394 ret = pthread_mutex_unlock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100395 if (ret) {
396 /* error */
Michal Vasko05532772021-06-03 12:12:38 +0200397 ERR(session, "%s: failed to RPC unlock a session (%s).", func, strerror(ret));
Michal Vaskoade892d2017-02-22 13:40:35 +0100398 return -1;
399 }
400 }
401
Michal Vasko96164bf2016-01-21 15:41:58 +0100402 return 1;
403}
404
Michal Vasko131120a2018-05-29 15:44:02 +0200405int
406nc_session_io_lock(struct nc_session *session, int timeout, const char *func)
407{
408 int ret;
409 struct timespec ts_timeout;
410
411 if (timeout > 0) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100412 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko131120a2018-05-29 15:44:02 +0200413
Michal Vaskod8a74192023-02-06 15:51:50 +0100414 ret = pthread_mutex_clocklock(session->io_lock, COMPAT_CLOCK_ID, &ts_timeout);
Michal Vasko131120a2018-05-29 15:44:02 +0200415 } else if (!timeout) {
416 ret = pthread_mutex_trylock(session->io_lock);
417 } else { /* timeout == -1 */
Robin Jarry54ea2962018-10-10 10:33:40 +0200418 ret = pthread_mutex_lock(session->io_lock);
Michal Vasko131120a2018-05-29 15:44:02 +0200419 }
420
421 if (ret) {
422 if ((ret == EBUSY) || (ret == ETIMEDOUT)) {
423 /* timeout */
424 return 0;
425 }
426
427 /* error */
Michal Vasko05532772021-06-03 12:12:38 +0200428 ERR(session, "%s: failed to IO lock a session (%s).", func, strerror(ret));
Michal Vasko131120a2018-05-29 15:44:02 +0200429 return -1;
430 }
431
432 return 1;
433}
434
435int
436nc_session_io_unlock(struct nc_session *session, const char *func)
437{
438 int ret;
439
440 ret = pthread_mutex_unlock(session->io_lock);
441 if (ret) {
442 /* error */
Michal Vasko05532772021-06-03 12:12:38 +0200443 ERR(session, "%s: failed to IO unlock a session (%s).", func, strerror(ret));
Michal Vasko131120a2018-05-29 15:44:02 +0200444 return -1;
445 }
446
447 return 1;
448}
449
Michal Vasko01130bd2021-08-26 11:47:38 +0200450int
451nc_session_client_msgs_lock(struct nc_session *session, int *timeout, const char *func)
452{
453 int ret;
454 int32_t diff_msec;
roman6ece9c52022-06-22 09:29:17 +0200455 struct timespec ts_timeout, ts_start;
Michal Vasko01130bd2021-08-26 11:47:38 +0200456
457 assert(session->side == NC_CLIENT);
458
459 if (*timeout > 0) {
460 /* get current time */
Michal Vaskod8a74192023-02-06 15:51:50 +0100461 nc_timeouttime_get(&ts_start, 0);
Michal Vasko01130bd2021-08-26 11:47:38 +0200462
Michal Vaskod8a74192023-02-06 15:51:50 +0100463 nc_timeouttime_get(&ts_timeout, *timeout);
Michal Vasko01130bd2021-08-26 11:47:38 +0200464
Michal Vaskod8a74192023-02-06 15:51:50 +0100465 ret = pthread_mutex_clocklock(&session->opts.client.msgs_lock, COMPAT_CLOCK_ID, &ts_timeout);
Michal Vasko01130bd2021-08-26 11:47:38 +0200466 if (!ret) {
467 /* update timeout based on what was elapsed */
Michal Vaskod8a74192023-02-06 15:51:50 +0100468 diff_msec = nc_timeouttime_cur_diff(&ts_start);
Michal Vasko01130bd2021-08-26 11:47:38 +0200469 *timeout -= diff_msec;
470 }
471 } else if (!*timeout) {
472 ret = pthread_mutex_trylock(&session->opts.client.msgs_lock);
473 } else { /* timeout == -1 */
474 ret = pthread_mutex_lock(&session->opts.client.msgs_lock);
475 }
476
477 if (ret) {
478 if ((ret == EBUSY) || (ret == ETIMEDOUT)) {
479 /* timeout */
480 return 0;
481 }
482
483 /* error */
484 ERR(session, "%s: failed to MSGS lock a session (%s).", func, strerror(ret));
485 return -1;
486 }
487
488 return 1;
489}
490
491int
492nc_session_client_msgs_unlock(struct nc_session *session, const char *func)
493{
494 int ret;
495
496 assert(session->side == NC_CLIENT);
497
498 ret = pthread_mutex_unlock(&session->opts.client.msgs_lock);
499 if (ret) {
500 /* error */
501 ERR(session, "%s: failed to MSGS unlock a session (%s).", func, strerror(ret));
502 return -1;
503 }
504
505 return 1;
506}
507
Michal Vasko8dadf782016-01-15 10:29:36 +0100508API NC_STATUS
509nc_session_get_status(const struct nc_session *session)
510{
roman40672412023-05-04 11:10:22 +0200511 NC_CHECK_ARG_RET(session, session, NC_STATUS_ERR);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100512
Michal Vasko8dadf782016-01-15 10:29:36 +0100513 return session->status;
514}
515
Radek Krejci6e36bfb2016-12-01 21:40:16 +0100516API NC_SESSION_TERM_REASON
Michal Vasko142cfea2017-08-07 10:12:11 +0200517nc_session_get_term_reason(const struct nc_session *session)
Radek Krejci6e36bfb2016-12-01 21:40:16 +0100518{
roman40672412023-05-04 11:10:22 +0200519 NC_CHECK_ARG_RET(session, session, NC_SESSION_TERM_ERR);
Radek Krejci6e36bfb2016-12-01 21:40:16 +0100520
521 return session->term_reason;
522}
523
Michal Vasko8dadf782016-01-15 10:29:36 +0100524API uint32_t
Michal Vasko142cfea2017-08-07 10:12:11 +0200525nc_session_get_killed_by(const struct nc_session *session)
526{
roman40672412023-05-04 11:10:22 +0200527 NC_CHECK_ARG_RET(session, session, 0);
Michal Vasko142cfea2017-08-07 10:12:11 +0200528
529 return session->killed_by;
530}
531
532API uint32_t
Michal Vasko8dadf782016-01-15 10:29:36 +0100533nc_session_get_id(const struct nc_session *session)
534{
roman40672412023-05-04 11:10:22 +0200535 NC_CHECK_ARG_RET(session, session, 0);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100536
Michal Vasko8dadf782016-01-15 10:29:36 +0100537 return session->id;
538}
539
Michal Vasko174fe8e2016-02-17 15:38:09 +0100540API int
541nc_session_get_version(const struct nc_session *session)
542{
roman40672412023-05-04 11:10:22 +0200543 NC_CHECK_ARG_RET(session, session, -1);
Michal Vasko174fe8e2016-02-17 15:38:09 +0100544
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200545 return session->version == NC_VERSION_10 ? 0 : 1;
Michal Vasko174fe8e2016-02-17 15:38:09 +0100546}
547
Michal Vasko8dadf782016-01-15 10:29:36 +0100548API NC_TRANSPORT_IMPL
549nc_session_get_ti(const struct nc_session *session)
550{
roman40672412023-05-04 11:10:22 +0200551 NC_CHECK_ARG_RET(session, session, 0);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100552
Michal Vasko8dadf782016-01-15 10:29:36 +0100553 return session->ti_type;
554}
555
556API const char *
557nc_session_get_username(const struct nc_session *session)
558{
roman40672412023-05-04 11:10:22 +0200559 NC_CHECK_ARG_RET(session, session, NULL);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100560
Michal Vasko8dadf782016-01-15 10:29:36 +0100561 return session->username;
562}
563
564API const char *
565nc_session_get_host(const struct nc_session *session)
566{
roman40672412023-05-04 11:10:22 +0200567 NC_CHECK_ARG_RET(session, session, NULL);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100568
Michal Vasko8dadf782016-01-15 10:29:36 +0100569 return session->host;
570}
571
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200572API const char *
573nc_session_get_path(const struct nc_session *session)
574{
roman40672412023-05-04 11:10:22 +0200575 NC_CHECK_ARG_RET(session, session, NULL);
576
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200577 if (session->ti_type != NC_TI_UNIX) {
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200578 return NULL;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200579 }
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200580
581 return session->path;
582}
583
Michal Vasko8dadf782016-01-15 10:29:36 +0100584API uint16_t
585nc_session_get_port(const struct nc_session *session)
586{
roman40672412023-05-04 11:10:22 +0200587 NC_CHECK_ARG_RET(session, session, 0);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100588
Michal Vasko8dadf782016-01-15 10:29:36 +0100589 return session->port;
590}
591
Michal Vasko93224072021-11-09 12:14:28 +0100592API const struct ly_ctx *
Michal Vasko9a25e932016-02-01 10:36:42 +0100593nc_session_get_ctx(const struct nc_session *session)
594{
roman40672412023-05-04 11:10:22 +0200595 NC_CHECK_ARG_RET(session, session, NULL);
Michal Vasko9a25e932016-02-01 10:36:42 +0100596
597 return session->ctx;
598}
599
Michal Vasko2cc4c682016-03-01 09:16:48 +0100600API void
601nc_session_set_data(struct nc_session *session, void *data)
602{
603 if (!session) {
roman40672412023-05-04 11:10:22 +0200604 ERRARG(NULL, "session");
Michal Vasko2cc4c682016-03-01 09:16:48 +0100605 return;
606 }
607
608 session->data = data;
609}
610
611API void *
612nc_session_get_data(const struct nc_session *session)
613{
roman40672412023-05-04 11:10:22 +0200614 NC_CHECK_ARG_RET(session, session, NULL);
Michal Vasko2cc4c682016-03-01 09:16:48 +0100615
616 return session->data;
617}
618
Michal Vaskodc96bb92023-03-28 08:52:48 +0200619API int
620nc_session_is_callhome(const struct nc_session *session)
621{
roman40672412023-05-04 11:10:22 +0200622 NC_CHECK_ARG_RET(session, session, 0);
Michal Vaskodc96bb92023-03-28 08:52:48 +0200623
624 if (session->flags & NC_SESSION_CALLHOME) {
625 return 1;
626 }
627
628 return 0;
629}
630
Michal Vasko086311b2016-01-08 09:53:11 +0100631NC_MSG_TYPE
Michal Vasko131120a2018-05-29 15:44:02 +0200632nc_send_msg_io(struct nc_session *session, int io_timeout, struct lyd_node *op)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200633{
Michal Vasko7e1f5fb2021-11-10 10:14:45 +0100634 if (session->ctx != LYD_CTX(op)) {
635 ERR(session, "RPC \"%s\" was created in different context than that of the session.", LYD_NAME(op));
Michal Vasko086311b2016-01-08 09:53:11 +0100636 return NC_MSG_ERROR;
Michal Vasko7df39ec2015-12-09 15:26:24 +0100637 }
638
Michal Vasko131120a2018-05-29 15:44:02 +0200639 return nc_write_msg_io(session, io_timeout, NC_MSG_RPC, op, NULL);
Michal Vasko7df39ec2015-12-09 15:26:24 +0100640}
641
Michal Vaskod4da3632022-05-25 11:49:10 +0200642/**
643 * @brief Send \<close-session\> and read the reply on a session.
644 *
645 * @param[in] session Closing NETCONF session.
646 */
647static void
648nc_session_free_close_session(struct nc_session *session)
649{
650 struct ly_in *msg;
651 struct lyd_node *close_rpc, *envp;
652 const struct lys_module *ietfnc;
653
654 ietfnc = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf");
655 if (!ietfnc) {
Michal Vasko5ca5d972022-09-14 13:51:31 +0200656 WRN(session, "Missing ietf-netconf module in context, unable to send <close-session>.");
Michal Vaskod4da3632022-05-25 11:49:10 +0200657 return;
658 }
659 if (lyd_new_inner(NULL, ietfnc, "close-session", 0, &close_rpc)) {
660 WRN(session, "Failed to create <close-session> RPC.");
661 return;
662 }
663
664 /* send the RPC */
665 nc_send_msg_io(session, NC_SESSION_FREE_LOCK_TIMEOUT, close_rpc);
666
667read_msg:
668 switch (nc_read_msg_poll_io(session, NC_CLOSE_REPLY_TIMEOUT, &msg)) {
669 case 1:
670 if (!strncmp(ly_in_memory(msg, NULL), "<notification", 13)) {
671 /* ignore */
672 ly_in_free(msg, 1);
673 goto read_msg;
674 }
675 if (lyd_parse_op(session->ctx, close_rpc, msg, LYD_XML, LYD_TYPE_REPLY_NETCONF, &envp, NULL)) {
676 WRN(session, "Failed to parse <close-session> reply.");
677 } else if (!lyd_child(envp) || strcmp(LYD_NAME(lyd_child(envp)), "ok")) {
678 WRN(session, "Reply to <close-session> was not <ok> as expected.");
679 }
680 lyd_free_tree(envp);
681 ly_in_free(msg, 1);
682 break;
683 case 0:
684 WRN(session, "Timeout for receiving a reply to <close-session> elapsed.");
685 break;
686 case -1:
687 ERR(session, "Failed to receive a reply to <close-session>.");
688 break;
689 default:
690 /* cannot happen */
691 break;
692 }
693 lyd_free_tree(close_rpc);
694}
695
Michal Vasko33476c32022-09-09 11:21:40 +0200696/**
697 * @brief Free transport implementation members of a session.
698 *
699 * @param[in] session Session to free.
700 * @param[out] multisession Whether there are other NC sessions on the same SSH sessions.
701 */
702static void
703nc_session_free_transport(struct nc_session *session, int *multisession)
704{
705 int connected; /* flag to indicate whether the transport socket is still connected */
Michal Vaskoe44f2702022-12-12 07:58:06 +0100706 int sock = -1;
Michal Vasko33476c32022-09-09 11:21:40 +0200707 struct nc_session *siter;
708
709 *multisession = 0;
710 connected = nc_session_is_connected(session);
711
712 /* transport implementation cleanup */
713 switch (session->ti_type) {
714 case NC_TI_FD:
715 /* nothing needed - file descriptors were provided by caller,
716 * so it is up to the caller to close them correctly
717 * TODO use callbacks
718 */
719 /* just to avoid compiler warning */
720 (void)connected;
721 (void)siter;
722 break;
723
724 case NC_TI_UNIX:
725 sock = session->ti.unixsock.sock;
726 (void)connected;
727 (void)siter;
728 break;
729
roman2eab4742023-06-06 10:00:26 +0200730#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +0200731 case NC_TI_SSH: {
Michal Vaskoe44f2702022-12-12 07:58:06 +0100732 int r;
733
Michal Vasko33476c32022-09-09 11:21:40 +0200734 if (connected) {
Michal Vasko64734402022-09-09 11:22:00 +0200735 ssh_channel_send_eof(session->ti.libssh.channel);
Michal Vasko33476c32022-09-09 11:21:40 +0200736 ssh_channel_free(session->ti.libssh.channel);
737 }
738 /* There can be multiple NETCONF sessions on the same SSH session (NETCONF session maps to
739 * SSH channel). So destroy the SSH session only if there is no other NETCONF session using
740 * it. Also, avoid concurrent free by multiple threads of sessions that share the SSH session.
741 */
742 /* SESSION IO LOCK */
743 r = nc_session_io_lock(session, NC_SESSION_FREE_LOCK_TIMEOUT, __func__);
744
745 if (session->ti.libssh.next) {
746 for (siter = session->ti.libssh.next; siter != session; siter = siter->ti.libssh.next) {
747 if (siter->status != NC_STATUS_STARTING) {
748 *multisession = 1;
749 break;
750 }
751 }
752 }
753
754 if (!*multisession) {
755 /* it's not multisession yet, but we still need to free the starting sessions */
756 if (session->ti.libssh.next) {
757 do {
758 siter = session->ti.libssh.next;
759 session->ti.libssh.next = siter->ti.libssh.next;
760
761 /* free starting SSH NETCONF session (channel will be freed in ssh_free()) */
762 free(siter->username);
763 free(siter->host);
764 if (!(siter->flags & NC_SESSION_SHAREDCTX)) {
765 ly_ctx_destroy((struct ly_ctx *)siter->ctx);
766 }
767
768 free(siter);
769 } while (session->ti.libssh.next != session);
770 }
771 /* remember sock so we can close it */
772 sock = ssh_get_fd(session->ti.libssh.session);
773 if (connected) {
Michal Vasko70e90622023-12-07 08:43:14 +0100774 /* does not close sock */
Michal Vasko33476c32022-09-09 11:21:40 +0200775 ssh_disconnect(session->ti.libssh.session);
Michal Vasko33476c32022-09-09 11:21:40 +0200776 }
777 ssh_free(session->ti.libssh.session);
778 } else {
779 /* remove the session from the list */
780 for (siter = session->ti.libssh.next; siter->ti.libssh.next != session; siter = siter->ti.libssh.next) {}
781 if (session->ti.libssh.next == siter) {
782 /* there will be only one session */
783 siter->ti.libssh.next = NULL;
784 } else {
785 /* there are still multiple sessions, keep the ring list */
786 siter->ti.libssh.next = session->ti.libssh.next;
787 }
Michal Vasko33476c32022-09-09 11:21:40 +0200788 }
789
790 /* SESSION IO UNLOCK */
791 if (r == 1) {
792 nc_session_io_unlock(session, __func__);
793 }
794 break;
Michal Vaskoe44f2702022-12-12 07:58:06 +0100795 }
roman506354a2024-04-11 09:37:22 +0200796 case NC_TI_TLS:
roman9ba26aa2024-04-05 12:31:54 +0200797 sock = nc_tls_get_fd_wrap(session);
Michal Vasko33476c32022-09-09 11:21:40 +0200798
799 if (connected) {
roman9ba26aa2024-04-05 12:31:54 +0200800 /* notify the peer that we're shutting down */
801 nc_tls_close_notify_wrap(session->ti.tls.session);
Michal Vasko33476c32022-09-09 11:21:40 +0200802 }
roman9ba26aa2024-04-05 12:31:54 +0200803
804 nc_tls_ctx_destroy_wrap(&session->ti.tls.ctx);
romanc95a14f2024-04-23 15:13:00 +0200805 memset(&session->ti.tls.ctx, 0, sizeof session->ti.tls.ctx);
roman9ba26aa2024-04-05 12:31:54 +0200806 nc_tls_session_destroy_wrap(session->ti.tls.session);
romanc95a14f2024-04-23 15:13:00 +0200807 session->ti.tls.session = NULL;
roman9ba26aa2024-04-05 12:31:54 +0200808 nc_tls_config_destroy_wrap(session->ti.tls.config);
romanc95a14f2024-04-23 15:13:00 +0200809 session->ti.tls.config = NULL;
Michal Vasko33476c32022-09-09 11:21:40 +0200810
811 if (session->side == NC_SERVER) {
roman9ba26aa2024-04-05 12:31:54 +0200812 nc_tls_cert_destroy_wrap(session->opts.server.client_cert);
Michal Vasko33476c32022-09-09 11:21:40 +0200813 }
roman9ba26aa2024-04-05 12:31:54 +0200814
Michal Vasko33476c32022-09-09 11:21:40 +0200815 break;
roman2eab4742023-06-06 10:00:26 +0200816#endif /* NC_ENABLED_SSH_TLS */
Michal Vasko33476c32022-09-09 11:21:40 +0200817 case NC_TI_NONE:
818 break;
819 }
820
821 /* close socket separately */
822 if (sock > -1) {
823 close(sock);
824 }
825}
826
Radek Krejci695d4fa2015-10-22 13:23:54 +0200827API void
Michal Vaskoe1a64ec2016-03-01 12:21:58 +0100828nc_session_free(struct nc_session *session, void (*data_free)(void *))
Radek Krejci695d4fa2015-10-22 13:23:54 +0200829{
Michal Vasko33476c32022-09-09 11:21:40 +0200830 int r, i, rpc_locked = 0, msgs_locked = 0, timeout;
Michal Vaskob48aa812016-01-18 14:13:09 +0100831 int multisession = 0; /* flag for more NETCONF sessions on a single SSH session */
Michal Vaskoad611702015-12-03 13:41:51 +0100832 struct nc_msg_cont *contiter;
Michal Vasko77367452021-02-16 16:32:18 +0100833 struct ly_in *msg;
roman6ece9c52022-06-22 09:29:17 +0200834 struct timespec ts;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200835 void *p;
836
Michal Vasko428087d2016-01-14 16:04:28 +0100837 if (!session || (session->status == NC_STATUS_CLOSING)) {
Radek Krejci695d4fa2015-10-22 13:23:54 +0200838 return;
839 }
840
Michal Vaskoa8ec54b2022-10-20 09:59:07 +0200841 /* stop notification threads if any */
842 if ((session->side == NC_CLIENT) && ATOMIC_LOAD_RELAXED(session->opts.client.ntf_thread_running)) {
843 /* let the threads know they should quit */
844 ATOMIC_STORE_RELAXED(session->opts.client.ntf_thread_running, 0);
Michal Vasko5bd4a3f2021-06-17 16:40:10 +0200845
Michal Vaskoa8ec54b2022-10-20 09:59:07 +0200846 /* wait for them */
Michal Vaskod8a74192023-02-06 15:51:50 +0100847 nc_timeouttime_get(&ts, NC_SESSION_FREE_LOCK_TIMEOUT);
Michal Vaskoa8ec54b2022-10-20 09:59:07 +0200848 while (ATOMIC_LOAD_RELAXED(session->opts.client.ntf_thread_count)) {
Michal Vasko5bd4a3f2021-06-17 16:40:10 +0200849 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +0100850 if (nc_timeouttime_cur_diff(&ts) < 1) {
Michal Vasko5bd4a3f2021-06-17 16:40:10 +0200851 ERR(session, "Waiting for notification thread exit failed (timed out).");
852 break;
853 }
854 }
Michal Vasko86d357c2016-03-11 13:46:38 +0100855 }
856
Michal Vaskoacf98472021-02-04 15:33:57 +0100857 if (session->side == NC_SERVER) {
Michal Vasko131120a2018-05-29 15:44:02 +0200858 r = nc_session_rpc_lock(session, NC_SESSION_FREE_LOCK_TIMEOUT, __func__);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100859 if (r == -1) {
Michal Vaskoadd4c792015-10-26 15:36:58 +0100860 return;
Michal Vasko131120a2018-05-29 15:44:02 +0200861 } else if (r) {
862 rpc_locked = 1;
Michal Vasko96a28a32021-02-04 15:35:20 +0100863 } else {
864 /* else failed to lock it, too bad */
Michal Vasko05532772021-06-03 12:12:38 +0200865 ERR(session, "Freeing a session while an RPC is being processed.");
Michal Vasko96a28a32021-02-04 15:35:20 +0100866 }
Radek Krejci695d4fa2015-10-22 13:23:54 +0200867 }
868
Michal Vasko8c247822020-09-07 13:23:23 +0200869 if (session->side == NC_CLIENT) {
Michal Vasko01130bd2021-08-26 11:47:38 +0200870 timeout = NC_SESSION_FREE_LOCK_TIMEOUT;
tadeas-vintrlik54f142a2021-08-23 10:36:18 +0200871
Michal Vasko01130bd2021-08-26 11:47:38 +0200872 /* MSGS LOCK */
873 r = nc_session_client_msgs_lock(session, &timeout, __func__);
874 if (r == -1) {
875 return;
876 } else if (r) {
877 msgs_locked = 1;
878 } else {
879 /* else failed to lock it, too bad */
880 ERR(session, "Freeing a session while messages are being received.");
881 }
882
883 /* cleanup message queue */
tadeas-vintrlik54f142a2021-08-23 10:36:18 +0200884 for (contiter = session->opts.client.msgs; contiter; ) {
Michal Vasko77367452021-02-16 16:32:18 +0100885 ly_in_free(contiter->msg, 1);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200886
Michal Vaskoad611702015-12-03 13:41:51 +0100887 p = contiter;
888 contiter = contiter->next;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200889 free(p);
890 }
891
Michal Vasko01130bd2021-08-26 11:47:38 +0200892 if (msgs_locked) {
893 /* MSGS UNLOCK */
894 nc_session_client_msgs_unlock(session, __func__);
895 }
Radek Krejci695d4fa2015-10-22 13:23:54 +0200896
Michal Vasko8c247822020-09-07 13:23:23 +0200897 if (session->status == NC_STATUS_RUNNING) {
Michal Vasko9c6d38c2021-09-03 13:02:53 +0200898 /* receive any leftover messages */
899 while (nc_read_msg_poll_io(session, 0, &msg) == 1) {
900 ly_in_free(msg, 1);
901 }
902
Michal Vasko8c247822020-09-07 13:23:23 +0200903 /* send closing info to the other side */
Michal Vaskod4da3632022-05-25 11:49:10 +0200904 nc_session_free_close_session(session);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200905 }
906
907 /* list of server's capabilities */
Michal Vasko2e6defd2016-10-07 15:48:15 +0200908 if (session->opts.client.cpblts) {
909 for (i = 0; session->opts.client.cpblts[i]; i++) {
Michal Vasko96fc4bb2017-05-23 14:58:34 +0200910 free(session->opts.client.cpblts[i]);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200911 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200912 free(session->opts.client.cpblts);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200913 }
Michal Vasko78939072022-12-12 07:43:18 +0100914
915 /* LY ext data */
roman2eab4742023-06-06 10:00:26 +0200916#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoe44f2702022-12-12 07:58:06 +0100917 struct nc_session *siter;
918
Michal Vasko78939072022-12-12 07:43:18 +0100919 if ((session->flags & NC_SESSION_SHAREDCTX) && session->ti.libssh.next) {
920 for (siter = session->ti.libssh.next; siter != session; siter = siter->ti.libssh.next) {
921 if (siter->status != NC_STATUS_STARTING) {
922 /* move LY ext data to this session */
923 assert(!siter->opts.client.ext_data);
924 siter->opts.client.ext_data = session->opts.client.ext_data;
925 session->opts.client.ext_data = NULL;
926 break;
927 }
928 }
Michal Vaskoe44f2702022-12-12 07:58:06 +0100929 } else
roman2eab4742023-06-06 10:00:26 +0200930#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoe44f2702022-12-12 07:58:06 +0100931 {
Michal Vasko78939072022-12-12 07:43:18 +0100932 lyd_free_siblings(session->opts.client.ext_data);
933 }
Radek Krejci695d4fa2015-10-22 13:23:54 +0200934 }
935
Michal Vaskoe1a64ec2016-03-01 12:21:58 +0100936 if (session->data && data_free) {
937 data_free(session->data);
938 }
939
Michal Vaskoc4bc5812016-10-13 10:59:36 +0200940 if ((session->side == NC_SERVER) && (session->flags & NC_SESSION_CALLHOME)) {
941 /* CH LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100942 pthread_mutex_lock(&session->opts.server.ch_lock);
Michal Vaskoc4bc5812016-10-13 10:59:36 +0200943 }
944
Michal Vasko86d357c2016-03-11 13:46:38 +0100945 /* mark session for closing */
Radek Krejci695d4fa2015-10-22 13:23:54 +0200946 session->status = NC_STATUS_CLOSING;
Michal Vaskoc4bc5812016-10-13 10:59:36 +0200947
Michal Vaskofeccb312022-03-24 15:24:59 +0100948 if ((session->side == NC_SERVER) && (session->flags & NC_SESSION_CH_THREAD)) {
Michal Vaskoacf98472021-02-04 15:33:57 +0100949 pthread_cond_signal(&session->opts.server.ch_cond);
Michal Vaskoc4bc5812016-10-13 10:59:36 +0200950
Michal Vaskod8a74192023-02-06 15:51:50 +0100951 nc_timeouttime_get(&ts, NC_SESSION_FREE_LOCK_TIMEOUT);
Michal Vasko0db3db52021-03-03 10:45:42 +0100952
953 /* wait for CH thread to actually wake up and terminate */
954 r = 0;
Michal Vaskofeccb312022-03-24 15:24:59 +0100955 while (!r && (session->flags & NC_SESSION_CH_THREAD)) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100956 r = pthread_cond_clockwait(&session->opts.server.ch_cond, &session->opts.server.ch_lock, COMPAT_CLOCK_ID, &ts);
Michal Vasko0db3db52021-03-03 10:45:42 +0100957 }
Michal Vasko0db3db52021-03-03 10:45:42 +0100958 if (r) {
Michal Vasko05532772021-06-03 12:12:38 +0200959 ERR(session, "Waiting for Call Home thread failed (%s).", strerror(r));
Michal Vasko3f05a092018-03-13 10:39:49 +0100960 }
Michal Vaskoc4bc5812016-10-13 10:59:36 +0200961 }
962
Michal Vaskofeccb312022-03-24 15:24:59 +0100963 if ((session->side == NC_SERVER) && (session->flags & NC_SESSION_CALLHOME)) {
964 /* CH UNLOCK */
965 pthread_mutex_unlock(&session->opts.server.ch_lock);
966 }
967
Radek Krejci695d4fa2015-10-22 13:23:54 +0200968 /* transport implementation cleanup */
Michal Vasko33476c32022-09-09 11:21:40 +0200969 nc_session_free_transport(session, &multisession);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200970
Michal Vasko33476c32022-09-09 11:21:40 +0200971 /* final cleanup */
Michal Vasko93224072021-11-09 12:14:28 +0100972 free(session->username);
973 free(session->host);
974 free(session->path);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200975
Michal Vaskoacf98472021-02-04 15:33:57 +0100976 if (session->side == NC_SERVER) {
Michal Vaskodf68e7e2022-04-21 11:04:00 +0200977 pthread_mutex_destroy(&session->opts.server.ntf_status_lock);
Michal Vasko131120a2018-05-29 15:44:02 +0200978 if (rpc_locked) {
979 nc_session_rpc_unlock(session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vasko9e99f012016-03-03 13:25:20 +0100980 }
Michal Vaskoacf98472021-02-04 15:33:57 +0100981 pthread_mutex_destroy(&session->opts.server.rpc_lock);
982 pthread_cond_destroy(&session->opts.server.rpc_cond);
Michal Vasko131120a2018-05-29 15:44:02 +0200983 }
984
985 if (session->io_lock && !multisession) {
986 pthread_mutex_destroy(session->io_lock);
987 free(session->io_lock);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200988 }
989
990 if (!(session->flags & NC_SESSION_SHAREDCTX)) {
Michal Vasko93224072021-11-09 12:14:28 +0100991 ly_ctx_destroy((struct ly_ctx *)session->ctx);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200992 }
993
Michal Vaskoc4bc5812016-10-13 10:59:36 +0200994 if (session->side == NC_SERVER) {
Michal Vaskoacf98472021-02-04 15:33:57 +0100995 /* free CH synchronization structures */
996 pthread_cond_destroy(&session->opts.server.ch_cond);
997 pthread_mutex_destroy(&session->opts.server.ch_lock);
tadeas-vintrlik54f142a2021-08-23 10:36:18 +0200998 } else {
999 pthread_mutex_destroy(&session->opts.client.msgs_lock);
Michal Vaskoc4bc5812016-10-13 10:59:36 +02001000 }
1001
Radek Krejci695d4fa2015-10-22 13:23:54 +02001002 free(session);
1003}
1004
Michal Vasko086311b2016-01-08 09:53:11 +01001005static void
Michal Vasko93224072021-11-09 12:14:28 +01001006add_cpblt(const char *capab, char ***cpblts, int *size, int *count)
Michal Vasko086311b2016-01-08 09:53:11 +01001007{
Radek Krejci658782b2016-12-04 22:04:55 +01001008 size_t len;
1009 int i;
1010 char *p;
1011
1012 if (capab) {
1013 /* check if already present */
1014 p = strchr(capab, '?');
1015 if (p) {
1016 len = p - capab;
1017 } else {
1018 len = strlen(capab);
1019 }
1020 for (i = 0; i < *count; i++) {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001021 if (!strncmp((*cpblts)[i], capab, len) && (((*cpblts)[i][len] == '\0') || ((*cpblts)[i][len] == '?'))) {
Radek Krejci658782b2016-12-04 22:04:55 +01001022 /* already present, do not duplicate it */
1023 return;
1024 }
1025 }
1026 }
1027
1028 /* add another capability */
Michal Vasko086311b2016-01-08 09:53:11 +01001029 if (*count == *size) {
1030 *size += 5;
Michal Vasko4eb3c312016-03-01 14:09:37 +01001031 *cpblts = nc_realloc(*cpblts, *size * sizeof **cpblts);
1032 if (!(*cpblts)) {
1033 ERRMEM;
1034 return;
1035 }
Michal Vasko086311b2016-01-08 09:53:11 +01001036 }
1037
Michal Vasko93224072021-11-09 12:14:28 +01001038 (*cpblts)[*count] = capab ? strdup(capab) : NULL;
Michal Vasko086311b2016-01-08 09:53:11 +01001039 ++(*count);
1040}
1041
Michal Vasko93224072021-11-09 12:14:28 +01001042API char **
1043nc_server_get_cpblts_version(const struct ly_ctx *ctx, LYS_VERSION version)
Michal Vasko086311b2016-01-08 09:53:11 +01001044{
Michal Vasko93224072021-11-09 12:14:28 +01001045 char **cpblts;
Michal Vasko77367452021-02-16 16:32:18 +01001046 const struct lys_module *mod;
1047 struct lysp_feature *feat;
1048 int size = 10, count, features_count = 0, dev_count = 0, str_len, len;
Michal Vasko1440a742021-03-31 11:11:03 +02001049 uint32_t i, u;
Michal Vasko77367452021-02-16 16:32:18 +01001050 LY_ARRAY_COUNT_TYPE v;
Michal Vasko1440a742021-03-31 11:11:03 +02001051 char *yl_content_id;
romanc1d2b092023-02-02 08:58:27 +01001052 uint32_t wd_also_supported;
1053 uint32_t wd_basic_mode;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001054
Radek Krejci24a18412018-05-16 15:09:10 +02001055#define NC_CPBLT_BUF_LEN 4096
Michal Vasko2e47ef92016-06-20 10:03:24 +02001056 char str[NC_CPBLT_BUF_LEN];
Michal Vasko086311b2016-01-08 09:53:11 +01001057
roman40672412023-05-04 11:10:22 +02001058 NC_CHECK_ARG_RET(NULL, ctx, NULL);
Michal Vasko4ffa3b22016-05-24 16:36:25 +02001059
Michal Vasko086311b2016-01-08 09:53:11 +01001060 cpblts = malloc(size * sizeof *cpblts);
roman124a4362023-10-26 15:36:22 +02001061 NC_CHECK_ERRMEM_GOTO(!cpblts, , error);
Michal Vasko93224072021-11-09 12:14:28 +01001062 cpblts[0] = strdup("urn:ietf:params:netconf:base:1.0");
1063 cpblts[1] = strdup("urn:ietf:params:netconf:base:1.1");
Michal Vasko086311b2016-01-08 09:53:11 +01001064 count = 2;
1065
1066 /* capabilities */
1067
Michal Vasko77367452021-02-16 16:32:18 +01001068 mod = ly_ctx_get_module_implemented(ctx, "ietf-netconf");
Michal Vasko086311b2016-01-08 09:53:11 +01001069 if (mod) {
Michal Vasko77367452021-02-16 16:32:18 +01001070 if (lys_feature_value(mod, "writable-running") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001071 add_cpblt("urn:ietf:params:netconf:capability:writable-running:1.0", &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001072 }
Michal Vasko77367452021-02-16 16:32:18 +01001073 if (lys_feature_value(mod, "candidate") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001074 add_cpblt("urn:ietf:params:netconf:capability:candidate:1.0", &cpblts, &size, &count);
Michal Vasko77367452021-02-16 16:32:18 +01001075 if (lys_feature_value(mod, "confirmed-commit") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001076 add_cpblt("urn:ietf:params:netconf:capability:confirmed-commit:1.1", &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001077 }
1078 }
Michal Vasko77367452021-02-16 16:32:18 +01001079 if (lys_feature_value(mod, "rollback-on-error") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001080 add_cpblt("urn:ietf:params:netconf:capability:rollback-on-error:1.0", &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001081 }
Michal Vasko77367452021-02-16 16:32:18 +01001082 if (lys_feature_value(mod, "validate") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001083 add_cpblt("urn:ietf:params:netconf:capability:validate:1.1", &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001084 }
Michal Vasko77367452021-02-16 16:32:18 +01001085 if (lys_feature_value(mod, "startup") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001086 add_cpblt("urn:ietf:params:netconf:capability:startup:1.0", &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001087 }
Michal Vaskof0fba4e2020-02-14 17:15:31 +01001088
1089 /* The URL capability must be set manually using nc_server_set_capability()
1090 * because of the need for supported protocols to be included.
1091 * https://tools.ietf.org/html/rfc6241#section-8.8.3
1092 */
Michal Vasko77367452021-02-16 16:32:18 +01001093 // if (lys_feature_value(mod, "url") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001094 // add_cpblt("urn:ietf:params:netconf:capability:url:1.0", &cpblts, &size, &count);
mekleoa8de5e92020-02-13 09:05:56 +01001095 // }
Michal Vaskof0fba4e2020-02-14 17:15:31 +01001096
Michal Vasko77367452021-02-16 16:32:18 +01001097 if (lys_feature_value(mod, "xpath") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001098 add_cpblt("urn:ietf:params:netconf:capability:xpath:1.0", &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001099 }
1100 }
1101
Michal Vasko77367452021-02-16 16:32:18 +01001102 mod = ly_ctx_get_module_implemented(ctx, "ietf-netconf-with-defaults");
Michal Vasko086311b2016-01-08 09:53:11 +01001103 if (mod) {
romanc1d2b092023-02-02 08:58:27 +01001104 wd_basic_mode = ATOMIC_LOAD_RELAXED(server_opts.wd_basic_mode);
1105 if (!wd_basic_mode) {
Michal Vasko05532772021-06-03 12:12:38 +02001106 VRB(NULL, "with-defaults capability will not be advertised even though \"ietf-netconf-with-defaults\" model is present, unknown basic-mode.");
Michal Vasko086311b2016-01-08 09:53:11 +01001107 } else {
Michal Vasko3031aae2016-01-27 16:07:18 +01001108 strcpy(str, "urn:ietf:params:netconf:capability:with-defaults:1.0");
romanc1d2b092023-02-02 08:58:27 +01001109 switch (wd_basic_mode) {
Michal Vasko086311b2016-01-08 09:53:11 +01001110 case NC_WD_ALL:
1111 strcat(str, "?basic-mode=report-all");
1112 break;
1113 case NC_WD_TRIM:
1114 strcat(str, "?basic-mode=trim");
1115 break;
1116 case NC_WD_EXPLICIT:
1117 strcat(str, "?basic-mode=explicit");
1118 break;
1119 default:
Michal Vasko9e036d52016-01-08 10:49:26 +01001120 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +01001121 break;
1122 }
1123
romanc1d2b092023-02-02 08:58:27 +01001124 wd_also_supported = ATOMIC_LOAD_RELAXED(server_opts.wd_also_supported);
1125 if (wd_also_supported) {
Michal Vasko2e47ef92016-06-20 10:03:24 +02001126 strcat(str, "&also-supported=");
romanc1d2b092023-02-02 08:58:27 +01001127 if (wd_also_supported & NC_WD_ALL) {
Michal Vasko086311b2016-01-08 09:53:11 +01001128 strcat(str, "report-all,");
1129 }
romanc1d2b092023-02-02 08:58:27 +01001130 if (wd_also_supported & NC_WD_ALL_TAG) {
Michal Vasko086311b2016-01-08 09:53:11 +01001131 strcat(str, "report-all-tagged,");
1132 }
romanc1d2b092023-02-02 08:58:27 +01001133 if (wd_also_supported & NC_WD_TRIM) {
Michal Vasko086311b2016-01-08 09:53:11 +01001134 strcat(str, "trim,");
1135 }
romanc1d2b092023-02-02 08:58:27 +01001136 if (wd_also_supported & NC_WD_EXPLICIT) {
Michal Vasko086311b2016-01-08 09:53:11 +01001137 strcat(str, "explicit,");
1138 }
1139 str[strlen(str) - 1] = '\0';
1140
Michal Vasko93224072021-11-09 12:14:28 +01001141 add_cpblt(str, &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001142 }
1143 }
1144 }
1145
Radek Krejci658782b2016-12-04 22:04:55 +01001146 /* other capabilities */
1147 for (u = 0; u < server_opts.capabilities_count; u++) {
Michal Vasko93224072021-11-09 12:14:28 +01001148 add_cpblt(server_opts.capabilities[u], &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001149 }
1150
1151 /* models */
Michal Vasko1440a742021-03-31 11:11:03 +02001152 u = 0;
Radek Krejci24a18412018-05-16 15:09:10 +02001153 while ((mod = ly_ctx_get_module_iter(ctx, &u))) {
Radek Krejci24a18412018-05-16 15:09:10 +02001154 if (!strcmp(mod->name, "ietf-yang-library")) {
Michal Vasko77367452021-02-16 16:32:18 +01001155 if (!mod->revision || (strcmp(mod->revision, "2016-06-21") && strcmp(mod->revision, "2019-01-04"))) {
Michal Vasko05532772021-06-03 12:12:38 +02001156 ERR(NULL, "Unknown \"ietf-yang-library\" revision, only 2016-06-21 and 2019-01-04 are supported.");
Michal Vaskod5ada122020-03-19 18:28:06 +01001157 goto error;
Michal Vaskof0fba4e2020-02-14 17:15:31 +01001158 }
Michal Vaskod5ada122020-03-19 18:28:06 +01001159
Michal Vasko1440a742021-03-31 11:11:03 +02001160 /* get content-id */
1161 if (server_opts.content_id_clb) {
1162 yl_content_id = server_opts.content_id_clb(server_opts.content_id_data);
roman124a4362023-10-26 15:36:22 +02001163 NC_CHECK_ERRMEM_GOTO(!yl_content_id, , error);
Michal Vasko1440a742021-03-31 11:11:03 +02001164 } else {
1165 yl_content_id = malloc(11);
roman124a4362023-10-26 15:36:22 +02001166 NC_CHECK_ERRMEM_GOTO(!yl_content_id, , error);
Michal Vasko1440a742021-03-31 11:11:03 +02001167 sprintf(yl_content_id, "%u", ly_ctx_get_change_count(ctx));
1168 }
1169
Michal Vasko77367452021-02-16 16:32:18 +01001170 if (!strcmp(mod->revision, "2019-01-04")) {
Michal Vasko7b5e3d92020-04-08 14:40:31 +02001171 /* new one (capab defined in RFC 8526 section 2) */
Michal Vasko1440a742021-03-31 11:11:03 +02001172 sprintf(str, "urn:ietf:params:netconf:capability:yang-library:1.1?revision=%s&content-id=%s",
1173 mod->revision, yl_content_id);
Michal Vasko93224072021-11-09 12:14:28 +01001174 add_cpblt(str, &cpblts, &size, &count);
Michal Vasko7b5e3d92020-04-08 14:40:31 +02001175 } else {
1176 /* old one (capab defined in RFC 7950 section 5.6.4) */
Michal Vasko1440a742021-03-31 11:11:03 +02001177 sprintf(str, "urn:ietf:params:netconf:capability:yang-library:1.0?revision=%s&module-set-id=%s",
1178 mod->revision, yl_content_id);
Michal Vasko93224072021-11-09 12:14:28 +01001179 add_cpblt(str, &cpblts, &size, &count);
Michal Vaskod5ada122020-03-19 18:28:06 +01001180 }
Michal Vasko1440a742021-03-31 11:11:03 +02001181 free(yl_content_id);
Radek Krejci24a18412018-05-16 15:09:10 +02001182 continue;
Michal Vasko77367452021-02-16 16:32:18 +01001183 } else if ((version == LYS_VERSION_1_0) && (mod->parsed->version > version)) {
Michal Vasko5ca5d972022-09-14 13:51:31 +02001184 /* skip YANG 1.1 modules */
Radek Krejci24a18412018-05-16 15:09:10 +02001185 continue;
Michal Vasko77367452021-02-16 16:32:18 +01001186 } else if ((version == LYS_VERSION_1_1) && (mod->parsed->version != version)) {
Michal Vasko5ca5d972022-09-14 13:51:31 +02001187 /* skip YANG 1.0 modules */
Radek Krejci24a18412018-05-16 15:09:10 +02001188 continue;
1189 }
Michal Vasko086311b2016-01-08 09:53:11 +01001190
Michal Vasko77367452021-02-16 16:32:18 +01001191 str_len = sprintf(str, "%s?module=%s%s%s", mod->ns, mod->name, mod->revision ? "&revision=" : "",
1192 mod->revision ? mod->revision : "");
Radek Krejci24a18412018-05-16 15:09:10 +02001193
Michal Vaskodafdc742020-03-11 16:15:59 +01001194 features_count = 0;
1195 i = 0;
1196 feat = NULL;
Michal Vasko77367452021-02-16 16:32:18 +01001197 while ((feat = lysp_feature_next(feat, mod->parsed, &i))) {
Michal Vaskodafdc742020-03-11 16:15:59 +01001198 if (!(feat->flags & LYS_FENABLED)) {
1199 continue;
Michal Vaskoe90e4d12016-06-20 10:05:01 +02001200 }
Michal Vaskodafdc742020-03-11 16:15:59 +01001201 if (!features_count) {
1202 strcat(str, "&features=");
1203 str_len += 10;
1204 }
1205 len = strlen(feat->name);
1206 if (str_len + 1 + len >= NC_CPBLT_BUF_LEN) {
1207 ERRINT;
1208 break;
1209 }
1210 if (features_count) {
1211 strcat(str, ",");
1212 ++str_len;
1213 }
1214 strcat(str, feat->name);
1215 str_len += len;
1216 features_count++;
Michal Vasko086311b2016-01-08 09:53:11 +01001217 }
Michal Vasko086311b2016-01-08 09:53:11 +01001218
Michal Vasko77367452021-02-16 16:32:18 +01001219 if (mod->deviated_by) {
Radek Krejci24a18412018-05-16 15:09:10 +02001220 strcat(str, "&deviations=");
1221 str_len += 12;
1222 dev_count = 0;
Michal Vasko77367452021-02-16 16:32:18 +01001223 LY_ARRAY_FOR(mod->deviated_by, v) {
1224 len = strlen(mod->deviated_by[v]->name);
1225 if (str_len + 1 + len >= NC_CPBLT_BUF_LEN) {
1226 ERRINT;
1227 break;
Radek Krejci24a18412018-05-16 15:09:10 +02001228 }
Michal Vasko77367452021-02-16 16:32:18 +01001229 if (dev_count) {
1230 strcat(str, ",");
1231 ++str_len;
Radek Krejci24a18412018-05-16 15:09:10 +02001232 }
Michal Vasko77367452021-02-16 16:32:18 +01001233 strcat(str, mod->deviated_by[v]->name);
1234 str_len += len;
1235 dev_count++;
Radek Krejci24a18412018-05-16 15:09:10 +02001236 }
1237 }
1238
Michal Vasko93224072021-11-09 12:14:28 +01001239 add_cpblt(str, &cpblts, &size, &count);
Radek Krejci24a18412018-05-16 15:09:10 +02001240 }
Michal Vasko086311b2016-01-08 09:53:11 +01001241
1242 /* ending NULL capability */
Michal Vasko93224072021-11-09 12:14:28 +01001243 add_cpblt(NULL, &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001244
1245 return cpblts;
Radek Krejcif906e412017-09-22 14:44:45 +02001246
1247error:
Radek Krejcif906e412017-09-22 14:44:45 +02001248 free(cpblts);
Radek Krejcif906e412017-09-22 14:44:45 +02001249 return NULL;
Michal Vasko086311b2016-01-08 09:53:11 +01001250}
1251
Michal Vasko93224072021-11-09 12:14:28 +01001252API char **
1253nc_server_get_cpblts(const struct ly_ctx *ctx)
Radek Krejci24a18412018-05-16 15:09:10 +02001254{
1255 return nc_server_get_cpblts_version(ctx, LYS_VERSION_UNDEF);
1256}
1257
Radek Krejci695d4fa2015-10-22 13:23:54 +02001258static int
Michal Vasko77367452021-02-16 16:32:18 +01001259parse_cpblts(struct lyd_node *capabilities, char ***list)
Radek Krejci695d4fa2015-10-22 13:23:54 +02001260{
Michal Vasko77367452021-02-16 16:32:18 +01001261 struct lyd_node *iter;
1262 struct lyd_node_opaq *cpblt;
Michal Vasko156d3272017-04-11 11:46:49 +02001263 int ver = -1, i = 0;
1264 const char *cpb_start, *cpb_end;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001265
1266 if (list) {
1267 /* get the storage for server's capabilities */
Michal Vasko77367452021-02-16 16:32:18 +01001268 LY_LIST_FOR(lyd_child(capabilities), iter) {
Radek Krejci695d4fa2015-10-22 13:23:54 +02001269 i++;
1270 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001271 /* last item remains NULL */
1272 *list = calloc(i + 1, sizeof **list);
roman3a95bb22023-10-26 11:07:17 +02001273 NC_CHECK_ERRMEM_RET(!*list, -1);
Radek Krejci695d4fa2015-10-22 13:23:54 +02001274 i = 0;
1275 }
1276
Michal Vasko77367452021-02-16 16:32:18 +01001277 LY_LIST_FOR(lyd_child(capabilities), iter) {
1278 cpblt = (struct lyd_node_opaq *)iter;
1279
1280 if (strcmp(cpblt->name.name, "capability") || !cpblt->name.module_ns || strcmp(cpblt->name.module_ns, NC_NS_BASE)) {
Michal Vasko05532772021-06-03 12:12:38 +02001281 ERR(NULL, "Unexpected <%s> element in client's <hello>.", cpblt->name.name);
Radek Krejci695d4fa2015-10-22 13:23:54 +02001282 return -1;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001283 }
1284
Michal Vasko156d3272017-04-11 11:46:49 +02001285 /* skip leading/trailing whitespaces */
Michal Vasko77367452021-02-16 16:32:18 +01001286 for (cpb_start = cpblt->value; isspace(cpb_start[0]); ++cpb_start) {}
1287 for (cpb_end = cpblt->value + strlen(cpblt->value); (cpb_end > cpblt->value) && isspace(cpb_end[-1]); --cpb_end) {}
1288 if (!cpb_start[0] || (cpb_end == cpblt->value)) {
Michal Vasko05532772021-06-03 12:12:38 +02001289 ERR(NULL, "Empty capability \"%s\" received.", cpblt->value);
Michal Vasko156d3272017-04-11 11:46:49 +02001290 return -1;
1291 }
1292
Radek Krejci695d4fa2015-10-22 13:23:54 +02001293 /* detect NETCONF version */
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001294 if ((ver < 0) && !strncmp(cpb_start, "urn:ietf:params:netconf:base:1.0", cpb_end - cpb_start)) {
Radek Krejci695d4fa2015-10-22 13:23:54 +02001295 ver = 0;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001296 } else if ((ver < 1) && !strncmp(cpb_start, "urn:ietf:params:netconf:base:1.1", cpb_end - cpb_start)) {
Radek Krejci695d4fa2015-10-22 13:23:54 +02001297 ver = 1;
1298 }
1299
1300 /* store capabilities */
1301 if (list) {
Michal Vasko156d3272017-04-11 11:46:49 +02001302 (*list)[i] = strndup(cpb_start, cpb_end - cpb_start);
roman3a95bb22023-10-26 11:07:17 +02001303 NC_CHECK_ERRMEM_RET(!(*list)[i], -1);
Radek Krejci695d4fa2015-10-22 13:23:54 +02001304 i++;
1305 }
1306 }
1307
1308 if (ver == -1) {
Michal Vasko05532772021-06-03 12:12:38 +02001309 ERR(NULL, "Peer does not support a compatible NETCONF version.");
Radek Krejci695d4fa2015-10-22 13:23:54 +02001310 }
1311
1312 return ver;
1313}
1314
1315static NC_MSG_TYPE
Michal Vasko131120a2018-05-29 15:44:02 +02001316nc_send_hello_io(struct nc_session *session)
Michal Vasko086311b2016-01-08 09:53:11 +01001317{
Michal Vasko131120a2018-05-29 15:44:02 +02001318 NC_MSG_TYPE ret;
Michal Vaskocf898172024-01-15 15:04:28 +01001319 int i, timeout_io;
Michal Vasko93224072021-11-09 12:14:28 +01001320 char **cpblts;
Michal Vasko131120a2018-05-29 15:44:02 +02001321 uint32_t *sid;
Michal Vasko086311b2016-01-08 09:53:11 +01001322
Michal Vasko131120a2018-05-29 15:44:02 +02001323 if (session->side == NC_CLIENT) {
1324 /* client side hello - send only NETCONF base capabilities */
1325 cpblts = malloc(3 * sizeof *cpblts);
roman3a95bb22023-10-26 11:07:17 +02001326 NC_CHECK_ERRMEM_RET(!cpblts, NC_MSG_ERROR);
Michal Vasko93224072021-11-09 12:14:28 +01001327 cpblts[0] = strdup("urn:ietf:params:netconf:base:1.0");
1328 cpblts[1] = strdup("urn:ietf:params:netconf:base:1.1");
Michal Vasko131120a2018-05-29 15:44:02 +02001329 cpblts[2] = NULL;
1330
Michal Vaskocf898172024-01-15 15:04:28 +01001331 timeout_io = NC_CLIENT_HELLO_TIMEOUT * 1000;
Michal Vasko131120a2018-05-29 15:44:02 +02001332 sid = NULL;
1333 } else {
Michal Vasko77367452021-02-16 16:32:18 +01001334 cpblts = nc_server_get_cpblts_version(session->ctx, LYS_VERSION_1_0);
Michal Vasko5b24b6b2020-12-04 09:29:47 +01001335 if (!cpblts) {
1336 return NC_MSG_ERROR;
1337 }
Michal Vasko131120a2018-05-29 15:44:02 +02001338
Michal Vaskocf898172024-01-15 15:04:28 +01001339 if (session->flags & NC_SESSION_CALLHOME) {
1340 timeout_io = NC_SERVER_CH_HELLO_TIMEOUT * 1000;
1341 } else {
Michal Vasko5c1c9b32024-02-02 09:02:42 +01001342 timeout_io = server_opts.idle_timeout ? server_opts.idle_timeout * 1000 : -1;
Michal Vaskocf898172024-01-15 15:04:28 +01001343 }
Michal Vasko131120a2018-05-29 15:44:02 +02001344 sid = &session->id;
Michal Vasko4eb3c312016-03-01 14:09:37 +01001345 }
Michal Vasko05ba9df2016-01-13 14:40:27 +01001346
Michal Vaskocf898172024-01-15 15:04:28 +01001347 ret = nc_write_msg_io(session, timeout_io, NC_MSG_HELLO, cpblts, sid);
Michal Vasko086311b2016-01-08 09:53:11 +01001348
Michal Vasko086311b2016-01-08 09:53:11 +01001349 for (i = 0; cpblts[i]; ++i) {
Michal Vasko93224072021-11-09 12:14:28 +01001350 free(cpblts[i]);
Michal Vasko086311b2016-01-08 09:53:11 +01001351 }
1352 free(cpblts);
1353
Michal Vasko131120a2018-05-29 15:44:02 +02001354 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +01001355}
1356
1357static NC_MSG_TYPE
Michal Vasko131120a2018-05-29 15:44:02 +02001358nc_recv_client_hello_io(struct nc_session *session)
Radek Krejci695d4fa2015-10-22 13:23:54 +02001359{
Michal Vasko77367452021-02-16 16:32:18 +01001360 struct ly_in *msg;
1361 struct lyd_node *hello = NULL, *iter;
1362 struct lyd_node_opaq *node;
1363 int r, ver = -1, flag = 0;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001364 char *str;
Michal Vasko292c5542023-02-01 14:33:17 +01001365 long long id;
Michal Vasko77367452021-02-16 16:32:18 +01001366 NC_MSG_TYPE rc = NC_MSG_HELLO;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001367
Michal Vasko77367452021-02-16 16:32:18 +01001368 r = nc_read_msg_poll_io(session, NC_CLIENT_HELLO_TIMEOUT * 1000, &msg);
1369 switch (r) {
1370 case 1:
Radek Krejci695d4fa2015-10-22 13:23:54 +02001371 /* parse <hello> data */
Michal Vasko77367452021-02-16 16:32:18 +01001372 if (lyd_parse_data(session->ctx, NULL, msg, LYD_XML, LYD_PARSE_ONLY | LYD_PARSE_OPAQ, 0, &hello)) {
Michal Vasko05532772021-06-03 12:12:38 +02001373 ERR(session, "Failed to parse server <hello>.");
Michal Vasko77367452021-02-16 16:32:18 +01001374 rc = NC_MSG_ERROR;
1375 goto cleanup;
1376 }
1377
1378 LY_LIST_FOR(lyd_child(hello), iter) {
1379 node = (struct lyd_node_opaq *)iter;
1380
1381 if (!node->name.module_ns || strcmp(node->name.module_ns, NC_NS_BASE)) {
Michal Vasko11d142a2016-01-19 15:58:24 +01001382 continue;
Michal Vasko77367452021-02-16 16:32:18 +01001383 } else if (!strcmp(node->name.name, "session-id")) {
1384 if (!node->value || !strlen(node->value)) {
Michal Vasko05532772021-06-03 12:12:38 +02001385 ERR(session, "No value of <session-id> element in server <hello>.");
Michal Vasko77367452021-02-16 16:32:18 +01001386 rc = NC_MSG_ERROR;
1387 goto cleanup;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001388 }
Michal Vasko11d142a2016-01-19 15:58:24 +01001389 str = NULL;
Michal Vasko77367452021-02-16 16:32:18 +01001390 id = strtoll(node->value, &str, 10);
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001391 if (*str || (id < 1) || (id > UINT32_MAX)) {
Michal Vasko05532772021-06-03 12:12:38 +02001392 ERR(session, "Invalid value of <session-id> element in server <hello>.");
Michal Vasko77367452021-02-16 16:32:18 +01001393 rc = NC_MSG_ERROR;
1394 goto cleanup;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001395 }
Michal Vasko11d142a2016-01-19 15:58:24 +01001396 session->id = (uint32_t)id;
1397 continue;
Michal Vasko77367452021-02-16 16:32:18 +01001398 } else if (strcmp(node->name.name, "capabilities")) {
Michal Vasko05532772021-06-03 12:12:38 +02001399 ERR(session, "Unexpected <%s> element in server <hello>.", node->name.name);
Michal Vasko77367452021-02-16 16:32:18 +01001400 rc = NC_MSG_ERROR;
1401 goto cleanup;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001402 }
Michal Vasko11d142a2016-01-19 15:58:24 +01001403
1404 if (flag) {
1405 /* multiple capabilities elements */
Michal Vasko05532772021-06-03 12:12:38 +02001406 ERR(session, "Invalid <hello> message (multiple <capabilities> elements).");
Michal Vasko77367452021-02-16 16:32:18 +01001407 rc = NC_MSG_ERROR;
1408 goto cleanup;
Michal Vasko11d142a2016-01-19 15:58:24 +01001409 }
1410 flag = 1;
1411
Michal Vasko77367452021-02-16 16:32:18 +01001412 if ((ver = parse_cpblts(&node->node, &session->opts.client.cpblts)) < 0) {
1413 rc = NC_MSG_ERROR;
1414 goto cleanup;
Michal Vasko11d142a2016-01-19 15:58:24 +01001415 }
1416 session->version = ver;
1417 }
1418
1419 if (!session->id) {
Michal Vasko05532772021-06-03 12:12:38 +02001420 ERR(session, "Missing <session-id> in server <hello>.");
Michal Vasko77367452021-02-16 16:32:18 +01001421 rc = NC_MSG_ERROR;
1422 goto cleanup;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001423 }
1424 break;
Michal Vasko77367452021-02-16 16:32:18 +01001425 case 0:
Michal Vasko05532772021-06-03 12:12:38 +02001426 ERR(session, "Server <hello> timeout elapsed.");
Michal Vasko77367452021-02-16 16:32:18 +01001427 rc = NC_MSG_WOULDBLOCK;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001428 break;
1429 default:
Michal Vasko77367452021-02-16 16:32:18 +01001430 rc = NC_MSG_ERROR;
Michal Vasko71090fc2016-05-24 16:37:28 +02001431 break;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001432 }
1433
Michal Vasko77367452021-02-16 16:32:18 +01001434cleanup:
1435 ly_in_free(msg, 1);
1436 lyd_free_tree(hello);
1437 return rc;
Radek Krejci5686ff72015-10-09 13:33:56 +02001438}
1439
Michal Vasko11d142a2016-01-19 15:58:24 +01001440static NC_MSG_TYPE
Michal Vasko131120a2018-05-29 15:44:02 +02001441nc_recv_server_hello_io(struct nc_session *session)
Michal Vasko11d142a2016-01-19 15:58:24 +01001442{
Michal Vasko77367452021-02-16 16:32:18 +01001443 struct ly_in *msg;
1444 struct lyd_node *hello = NULL, *iter;
1445 struct lyd_node_opaq *node;
1446 NC_MSG_TYPE rc = NC_MSG_HELLO;
1447 int r, ver = -1, flag = 0, timeout_io;
Michal Vasko11d142a2016-01-19 15:58:24 +01001448
Michal Vaskocf898172024-01-15 15:04:28 +01001449 if (session->flags & NC_SESSION_CALLHOME) {
1450 timeout_io = NC_SERVER_CH_HELLO_TIMEOUT * 1000;
1451 } else {
Michal Vasko5c1c9b32024-02-02 09:02:42 +01001452 timeout_io = server_opts.idle_timeout ? server_opts.idle_timeout * 1000 : -1;
Michal Vaskocf898172024-01-15 15:04:28 +01001453 }
1454
Michal Vasko77367452021-02-16 16:32:18 +01001455 r = nc_read_msg_poll_io(session, timeout_io, &msg);
1456 switch (r) {
1457 case 1:
1458 /* parse <hello> data */
1459 if (lyd_parse_data(session->ctx, NULL, msg, LYD_XML, LYD_PARSE_ONLY | LYD_PARSE_OPAQ, 0, &hello)) {
Michal Vasko05532772021-06-03 12:12:38 +02001460 ERR(session, "Failed to parse client <hello>.");
Michal Vasko77367452021-02-16 16:32:18 +01001461 rc = NC_MSG_ERROR;
1462 goto cleanup;
1463 }
Michal Vasko11d142a2016-01-19 15:58:24 +01001464
Michal Vasko77367452021-02-16 16:32:18 +01001465 /* learn NETCONF version */
1466 LY_LIST_FOR(lyd_child(hello), iter) {
1467 node = (struct lyd_node_opaq *)iter;
1468
1469 if (!node->name.module_ns || strcmp(node->name.module_ns, NC_NS_BASE)) {
Michal Vasko11d142a2016-01-19 15:58:24 +01001470 continue;
Michal Vasko77367452021-02-16 16:32:18 +01001471 } else if (strcmp(node->name.name, "capabilities")) {
Michal Vasko05532772021-06-03 12:12:38 +02001472 ERR(session, "Unexpected <%s> element in client <hello>.", node->name.name);
Michal Vasko77367452021-02-16 16:32:18 +01001473 rc = NC_MSG_BAD_HELLO;
Michal Vasko11d142a2016-01-19 15:58:24 +01001474 goto cleanup;
1475 }
1476
1477 if (flag) {
1478 /* multiple capabilities elements */
Michal Vasko05532772021-06-03 12:12:38 +02001479 ERR(session, "Invalid <hello> message (multiple <capabilities> elements).");
Michal Vasko77367452021-02-16 16:32:18 +01001480 rc = NC_MSG_BAD_HELLO;
Michal Vasko11d142a2016-01-19 15:58:24 +01001481 goto cleanup;
1482 }
1483 flag = 1;
1484
Michal Vasko77367452021-02-16 16:32:18 +01001485 if ((ver = parse_cpblts(&node->node, NULL)) < 0) {
1486 rc = NC_MSG_BAD_HELLO;
Michal Vasko11d142a2016-01-19 15:58:24 +01001487 goto cleanup;
1488 }
1489 session->version = ver;
1490 }
1491 break;
Michal Vasko77367452021-02-16 16:32:18 +01001492 case 0:
Michal Vasko05532772021-06-03 12:12:38 +02001493 ERR(session, "Client <hello> timeout elapsed.");
Michal Vasko77367452021-02-16 16:32:18 +01001494 rc = NC_MSG_WOULDBLOCK;
Michal Vasko5e6f4cc2016-01-20 13:27:44 +01001495 break;
Michal Vasko11d142a2016-01-19 15:58:24 +01001496 default:
Michal Vasko77367452021-02-16 16:32:18 +01001497 rc = NC_MSG_ERROR;
Michal Vasko71090fc2016-05-24 16:37:28 +02001498 break;
Michal Vasko11d142a2016-01-19 15:58:24 +01001499 }
1500
1501cleanup:
Michal Vasko77367452021-02-16 16:32:18 +01001502 ly_in_free(msg, 1);
1503 lyd_free_tree(hello);
1504 return rc;
Michal Vasko11d142a2016-01-19 15:58:24 +01001505}
1506
Michal Vasko71090fc2016-05-24 16:37:28 +02001507NC_MSG_TYPE
Michal Vasko131120a2018-05-29 15:44:02 +02001508nc_handshake_io(struct nc_session *session)
Michal Vasko80cad7f2015-12-08 14:42:27 +01001509{
Michal Vasko086311b2016-01-08 09:53:11 +01001510 NC_MSG_TYPE type;
Michal Vasko80cad7f2015-12-08 14:42:27 +01001511
Michal Vasko131120a2018-05-29 15:44:02 +02001512 type = nc_send_hello_io(session);
Michal Vasko086311b2016-01-08 09:53:11 +01001513 if (type != NC_MSG_HELLO) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001514 return type;
Michal Vasko80cad7f2015-12-08 14:42:27 +01001515 }
1516
Michal Vasko11d142a2016-01-19 15:58:24 +01001517 if (session->side == NC_CLIENT) {
Michal Vasko131120a2018-05-29 15:44:02 +02001518 type = nc_recv_client_hello_io(session);
Michal Vasko11d142a2016-01-19 15:58:24 +01001519 } else {
Michal Vasko131120a2018-05-29 15:44:02 +02001520 type = nc_recv_server_hello_io(session);
Michal Vasko11d142a2016-01-19 15:58:24 +01001521 }
1522
Michal Vasko71090fc2016-05-24 16:37:28 +02001523 return type;
Michal Vasko80cad7f2015-12-08 14:42:27 +01001524}