blob: 90911430fc0b36c92a46fa4c2be6dd58aa84f87b [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>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020021#include <pthread.h>
22#include <stdlib.h>
23#include <string.h>
24#include <sys/socket.h>
25#include <sys/time.h>
26#include <sys/types.h>
Michal Vasko58f31552016-01-19 12:39:15 +010027#include <time.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020028#include <unistd.h>
Radek Krejci206fcd62015-10-07 15:42:48 +020029
Michal Vasko9e8ac262020-04-07 13:06:45 +020030#include "compat.h"
roman3f9b65c2023-06-05 14:26:58 +020031#include "config.h"
32#include "log_p.h"
33#include "netconf.h"
34#include "session_p.h"
Michal Vaskob48aa812016-01-18 14:13:09 +010035
roman2eab4742023-06-06 10:00:26 +020036#ifdef NC_ENABLED_SSH_TLS
Radek Krejci206fcd62015-10-07 15:42:48 +020037
roman2eab4742023-06-06 10:00:26 +020038#include <libssh/libssh.h>
roman13145912023-08-17 15:36:54 +020039#include <openssl/bio.h>
roman2eab4742023-06-06 10:00:26 +020040#include <openssl/conf.h>
41#include <openssl/err.h>
roman51a1e192023-09-14 10:13:45 +020042#include <openssl/evp.h>
43#include <openssl/x509.h>
Radek Krejci206fcd62015-10-07 15:42:48 +020044
roman2eab4742023-06-06 10:00:26 +020045#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoc14e3c82016-01-11 16:14:30 +010046
Michal Vasko086311b2016-01-08 09:53:11 +010047/* in seconds */
48#define NC_CLIENT_HELLO_TIMEOUT 60
Michal Vaskocf898172024-01-15 15:04:28 +010049#define NC_SERVER_CH_HELLO_TIMEOUT 180
Radek Krejci695d4fa2015-10-22 13:23:54 +020050
Michal Vasko05ba9df2016-01-13 14:40:27 +010051/* in milliseconds */
52#define NC_CLOSE_REPLY_TIMEOUT 200
53
Michal Vasko086311b2016-01-08 09:53:11 +010054extern struct nc_server_opts server_opts;
55
Michal Vaskod8a74192023-02-06 15:51:50 +010056void
57nc_timeouttime_get(struct timespec *ts, uint32_t add_ms)
Radek Krejci7ac16052016-07-15 11:48:18 +020058{
Michal Vaskod8a74192023-02-06 15:51:50 +010059 if (clock_gettime(COMPAT_CLOCK_ID, ts) == -1) {
60 ERR(NULL, "clock_gettime() failed (%s).", strerror(errno));
61 return;
62 }
63
64 if (!add_ms) {
65 return;
66 }
67
68 assert((ts->tv_nsec >= 0) && (ts->tv_nsec < 1000000000L));
69
70 ts->tv_sec += add_ms / 1000;
71 ts->tv_nsec += (add_ms % 1000) * 1000000L;
72
73 if (ts->tv_nsec >= 1000000000L) {
74 ++ts->tv_sec;
75 ts->tv_nsec -= 1000000000L;
76 } else if (ts->tv_nsec < 0) {
77 --ts->tv_sec;
78 ts->tv_nsec += 1000000000L;
79 }
80
81 assert((ts->tv_nsec >= 0) && (ts->tv_nsec < 1000000000L));
82}
83
84int32_t
85nc_timeouttime_cur_diff(const struct timespec *ts)
86{
87 struct timespec cur;
88 int64_t nsec_diff = 0;
89
90 nc_timeouttime_get(&cur, 0);
91
92 nsec_diff += (((int64_t)ts->tv_sec) - ((int64_t)cur.tv_sec)) * 1000000000L;
93 nsec_diff += ((int64_t)ts->tv_nsec) - ((int64_t)cur.tv_nsec);
94
95 return nsec_diff / 1000000L;
96}
97
98void
99nc_realtime_get(struct timespec *ts)
100{
roman6ece9c52022-06-22 09:29:17 +0200101 if (clock_gettime(CLOCK_REALTIME, ts)) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100102 ERR(NULL, "clock_gettime() failed (%s).", strerror(errno));
103 return;
roman6ece9c52022-06-22 09:29:17 +0200104 }
Michal Vasko36c7be82017-02-22 13:37:59 +0100105}
106
roman2eab4742023-06-06 10:00:26 +0200107#ifdef NC_ENABLED_SSH_TLS
108
Michal Vaskoddce1212019-05-24 09:58:49 +0200109const char *
roman3f9b65c2023-06-05 14:26:58 +0200110nc_privkey_format_to_str(NC_PRIVKEY_FORMAT format)
Michal Vaskoddce1212019-05-24 09:58:49 +0200111{
roman3f9b65c2023-06-05 14:26:58 +0200112 switch (format) {
113 case NC_PRIVKEY_FORMAT_RSA:
romand0fe5952024-03-21 15:59:33 +0100114 return " RSA ";
roman3f9b65c2023-06-05 14:26:58 +0200115 case NC_PRIVKEY_FORMAT_EC:
romand0fe5952024-03-21 15:59:33 +0100116 return " EC ";
roman3f9b65c2023-06-05 14:26:58 +0200117 case NC_PRIVKEY_FORMAT_X509:
romand0fe5952024-03-21 15:59:33 +0100118 return " ";
roman3f9b65c2023-06-05 14:26:58 +0200119 case NC_PRIVKEY_FORMAT_OPENSSH:
romand0fe5952024-03-21 15:59:33 +0100120 return " OPENSSH ";
Michal Vaskoddce1212019-05-24 09:58:49 +0200121 default:
roman3f9b65c2023-06-05 14:26:58 +0200122 return NULL;
Michal Vaskoddce1212019-05-24 09:58:49 +0200123 }
Michal Vaskoddce1212019-05-24 09:58:49 +0200124}
125
roman13145912023-08-17 15:36:54 +0200126int
127nc_base64_to_bin(const char *base64, char **bin)
128{
roman46172ac2023-11-08 15:12:40 +0100129 BIO *bio, *bio64 = NULL;
roman13145912023-08-17 15:36:54 +0200130 size_t used = 0, size = 0, r = 0;
131 void *tmp = NULL;
roman46172ac2023-11-08 15:12:40 +0100132 int nl_count, i, remainder, ret = 0;
roman13145912023-08-17 15:36:54 +0200133 char *b64;
134
135 /* insert new lines into the base64 string, so BIO_read works correctly */
136 nl_count = strlen(base64) / 64;
137 remainder = strlen(base64) - 64 * nl_count;
138 b64 = calloc(strlen(base64) + nl_count + 1, 1);
roman3a95bb22023-10-26 11:07:17 +0200139 NC_CHECK_ERRMEM_RET(!b64, -1);
roman13145912023-08-17 15:36:54 +0200140
141 for (i = 0; i < nl_count; i++) {
142 /* copy 64 bytes and add a NL */
143 strncpy(b64 + i * 65, base64 + i * 64, 64);
144 b64[i * 65 + 64] = '\n';
145 }
146
147 /* copy the rest */
148 strncpy(b64 + i * 65, base64 + i * 64, remainder);
149
150 bio64 = BIO_new(BIO_f_base64());
151 if (!bio64) {
152 ERR(NULL, "Error creating a bio (%s).", ERR_reason_error_string(ERR_get_error()));
roman46172ac2023-11-08 15:12:40 +0100153 ret = -1;
154 goto cleanup;
roman13145912023-08-17 15:36:54 +0200155 }
156
157 bio = BIO_new_mem_buf(b64, strlen(b64));
158 if (!bio) {
159 ERR(NULL, "Error creating a bio (%s).", ERR_reason_error_string(ERR_get_error()));
roman46172ac2023-11-08 15:12:40 +0100160 ret = -1;
161 goto cleanup;
roman13145912023-08-17 15:36:54 +0200162 }
163
164 BIO_push(bio64, bio);
165
166 /* store the decoded base64 in bin */
167 *bin = NULL;
168 do {
169 size += 64;
170
171 tmp = realloc(*bin, size);
172 if (!tmp) {
roman46172ac2023-11-08 15:12:40 +0100173 ERRMEM;
roman13145912023-08-17 15:36:54 +0200174 free(*bin);
roman46172ac2023-11-08 15:12:40 +0100175 *bin = NULL;
176 ret = -1;
177 goto cleanup;
roman13145912023-08-17 15:36:54 +0200178 }
179 *bin = tmp;
180
181 r = BIO_read(bio64, *bin + used, 64);
182 used += r;
183 } while (r == 64);
184
roman46172ac2023-11-08 15:12:40 +0100185 ret = size;
186
187cleanup:
roman13145912023-08-17 15:36:54 +0200188 free(b64);
189 BIO_free_all(bio64);
roman46172ac2023-11-08 15:12:40 +0100190 return ret;
roman13145912023-08-17 15:36:54 +0200191}
192
roman51a1e192023-09-14 10:13:45 +0200193int
194nc_is_pk_subject_public_key_info(const char *b64)
195{
196 int ret = 0;
197 long len;
198 char *bin = NULL, *tmp;
199 EVP_PKEY *pkey = NULL;
200
201 /* base64 2 binary */
202 len = nc_base64_to_bin(b64, &bin);
203 if (len == -1) {
204 ERR(NULL, "Decoding base64 public key to binary failed.");
205 ret = -1;
206 goto cleanup;
207 }
208
209 /* for deallocation later */
210 tmp = bin;
211
212 /* try to create EVP_PKEY from the supposed SubjectPublicKeyInfo binary data */
213 pkey = d2i_PUBKEY(NULL, (const unsigned char **)&tmp, len);
214 if (pkey) {
215 /* success, it's most likely SubjectPublicKeyInfo pubkey */
216 ret = 1;
217 } else {
218 /* fail, it's most likely not SubjectPublicKeyInfo pubkey */
219 ret = 0;
220 }
221
222cleanup:
223 EVP_PKEY_free(pkey);
224 free(bin);
225 return ret;
226}
227
roman2eab4742023-06-06 10:00:26 +0200228#endif /* NC_ENABLED_SSH_TLS */
229
Michal Vaskobe52dc22018-10-17 09:28:17 +0200230int
romanc1d2b092023-02-02 08:58:27 +0100231nc_sock_configure_keepalive(int sock, struct nc_keepalives *ka)
Michal Vaskobe52dc22018-10-17 09:28:17 +0200232{
233 int opt;
234
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200235 opt = ka->enabled;
Michal Vaskobe52dc22018-10-17 09:28:17 +0200236 if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200237 ERR(NULL, "Could not set SO_KEEPALIVE option (%s).", strerror(errno));
Michal Vaskobe52dc22018-10-17 09:28:17 +0200238 return -1;
239 }
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200240 if (!ka->enabled) {
241 return 0;
242 }
Michal Vaskobe52dc22018-10-17 09:28:17 +0200243
244#ifdef TCP_KEEPIDLE
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200245 opt = ka->idle_time;
Michal Vaskobe52dc22018-10-17 09:28:17 +0200246 if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200247 ERR(NULL, "Setsockopt failed (%s).", strerror(errno));
Michal Vaskobe52dc22018-10-17 09:28:17 +0200248 return -1;
249 }
250#endif
251
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200252#ifdef TCP_KEEPCNT
253 opt = ka->max_probes;
254 if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200255 ERR(NULL, "Setsockopt failed (%s).", strerror(errno));
Michal Vaskobe52dc22018-10-17 09:28:17 +0200256 return -1;
257 }
258#endif
259
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200260#ifdef TCP_KEEPINTVL
261 opt = ka->probe_interval;
262 if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200263 ERR(NULL, "Setsockopt failed (%s).", strerror(errno));
Michal Vaskobe52dc22018-10-17 09:28:17 +0200264 return -1;
265 }
266#endif
267
268 return 0;
269}
270
Michal Vaskoade892d2017-02-22 13:40:35 +0100271struct nc_session *
Michal Vasko131120a2018-05-29 15:44:02 +0200272nc_new_session(NC_SIDE side, int shared_ti)
Michal Vaskoade892d2017-02-22 13:40:35 +0100273{
274 struct nc_session *sess;
Michal Vaskocf898172024-01-15 15:04:28 +0100275 struct timespec ts_cur;
Michal Vaskoade892d2017-02-22 13:40:35 +0100276
277 sess = calloc(1, sizeof *sess);
278 if (!sess) {
279 return NULL;
280 }
281
Michal Vasko131120a2018-05-29 15:44:02 +0200282 sess->side = side;
283
284 if (side == NC_SERVER) {
Michal Vaskodf68e7e2022-04-21 11:04:00 +0200285 pthread_mutex_init(&sess->opts.server.ntf_status_lock, NULL);
Michal Vaskoacf98472021-02-04 15:33:57 +0100286 pthread_mutex_init(&sess->opts.server.rpc_lock, NULL);
287 pthread_cond_init(&sess->opts.server.rpc_cond, NULL);
Michal Vaskoacf98472021-02-04 15:33:57 +0100288
289 pthread_mutex_init(&sess->opts.server.ch_lock, NULL);
290 pthread_cond_init(&sess->opts.server.ch_cond, NULL);
Michal Vaskocf898172024-01-15 15:04:28 +0100291
292 /* initialize last_rpc for idle_timeout */
293 nc_timeouttime_get(&ts_cur, 0);
294 sess->opts.server.last_rpc = ts_cur.tv_sec;
tadeas-vintrlik54f142a2021-08-23 10:36:18 +0200295 } else {
296 pthread_mutex_init(&sess->opts.client.msgs_lock, NULL);
Michal Vasko131120a2018-05-29 15:44:02 +0200297 }
298
299 if (!shared_ti) {
300 sess->io_lock = malloc(sizeof *sess->io_lock);
301 if (!sess->io_lock) {
302 goto error;
303 }
304 pthread_mutex_init(sess->io_lock, NULL);
Michal Vaskoade892d2017-02-22 13:40:35 +0100305 }
306
307 return sess;
Michal Vasko131120a2018-05-29 15:44:02 +0200308
309error:
Michal Vasko131120a2018-05-29 15:44:02 +0200310 free(sess);
311 return NULL;
Michal Vaskoade892d2017-02-22 13:40:35 +0100312}
313
Michal Vasko96164bf2016-01-21 15:41:58 +0100314/*
315 * @return 1 - success
316 * 0 - timeout
317 * -1 - error
318 */
319int
Michal Vasko131120a2018-05-29 15:44:02 +0200320nc_session_rpc_lock(struct nc_session *session, int timeout, const char *func)
Michal Vasko96164bf2016-01-21 15:41:58 +0100321{
322 int ret;
Michal Vasko62be1ce2016-03-03 13:24:52 +0100323 struct timespec ts_timeout;
Michal Vasko96164bf2016-01-21 15:41:58 +0100324
Michal Vasko131120a2018-05-29 15:44:02 +0200325 if (session->side != NC_SERVER) {
326 ERRINT;
327 return -1;
328 }
329
Michal Vasko96164bf2016-01-21 15:41:58 +0100330 if (timeout > 0) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100331 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko96164bf2016-01-21 15:41:58 +0100332
Michal Vaskoade892d2017-02-22 13:40:35 +0100333 /* LOCK */
Michal Vaskod8a74192023-02-06 15:51:50 +0100334 ret = pthread_mutex_clocklock(&session->opts.server.rpc_lock, COMPAT_CLOCK_ID, &ts_timeout);
Michal Vaskoade892d2017-02-22 13:40:35 +0100335 if (!ret) {
Michal Vaskoacf98472021-02-04 15:33:57 +0100336 while (session->opts.server.rpc_inuse) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100337 ret = pthread_cond_clockwait(&session->opts.server.rpc_cond, &session->opts.server.rpc_lock,
338 COMPAT_CLOCK_ID, &ts_timeout);
Michal Vaskoade892d2017-02-22 13:40:35 +0100339 if (ret) {
Michal Vaskoacf98472021-02-04 15:33:57 +0100340 pthread_mutex_unlock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100341 break;
342 }
343 }
344 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100345 } else if (!timeout) {
Michal Vaskoade892d2017-02-22 13:40:35 +0100346 /* LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100347 ret = pthread_mutex_trylock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100348 if (!ret) {
Michal Vaskoacf98472021-02-04 15:33:57 +0100349 if (session->opts.server.rpc_inuse) {
350 pthread_mutex_unlock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100351 return 0;
352 }
Michal vasko2f8e4b52016-10-05 13:04:11 +0200353 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100354 } else { /* timeout == -1 */
Michal Vaskoade892d2017-02-22 13:40:35 +0100355 /* LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100356 ret = pthread_mutex_lock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100357 if (!ret) {
Michal Vaskoacf98472021-02-04 15:33:57 +0100358 while (session->opts.server.rpc_inuse) {
359 ret = pthread_cond_wait(&session->opts.server.rpc_cond, &session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100360 if (ret) {
Michal Vaskoacf98472021-02-04 15:33:57 +0100361 pthread_mutex_unlock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100362 break;
363 }
364 }
365 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100366 }
367
Michal Vaskoade892d2017-02-22 13:40:35 +0100368 if (ret) {
369 if ((ret == EBUSY) || (ret == ETIMEDOUT)) {
370 /* timeout */
371 return 0;
372 }
373
Michal Vasko96164bf2016-01-21 15:41:58 +0100374 /* error */
Michal Vasko05532772021-06-03 12:12:38 +0200375 ERR(session, "%s: failed to RPC lock a session (%s).", func, strerror(ret));
Michal Vasko96164bf2016-01-21 15:41:58 +0100376 return -1;
377 }
378
379 /* ok */
Michal Vaskoacf98472021-02-04 15:33:57 +0100380 assert(session->opts.server.rpc_inuse == 0);
381 session->opts.server.rpc_inuse = 1;
Michal Vaskoade892d2017-02-22 13:40:35 +0100382
383 /* UNLOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100384 ret = pthread_mutex_unlock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100385 if (ret) {
386 /* error */
Michal Vasko05532772021-06-03 12:12:38 +0200387 ERR(session, "%s: failed to RPC unlock a session (%s).", func, strerror(ret));
Michal Vaskoade892d2017-02-22 13:40:35 +0100388 return -1;
389 }
390
391 return 1;
392}
393
394int
Michal Vasko131120a2018-05-29 15:44:02 +0200395nc_session_rpc_unlock(struct nc_session *session, int timeout, const char *func)
Michal Vaskoade892d2017-02-22 13:40:35 +0100396{
397 int ret;
398 struct timespec ts_timeout;
399
Michal Vasko131120a2018-05-29 15:44:02 +0200400 if (session->side != NC_SERVER) {
401 ERRINT;
402 return -1;
403 }
404
Michal Vaskoacf98472021-02-04 15:33:57 +0100405 assert(session->opts.server.rpc_inuse);
Michal Vaskoade892d2017-02-22 13:40:35 +0100406
407 if (timeout > 0) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100408 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vaskoade892d2017-02-22 13:40:35 +0100409
410 /* LOCK */
Michal Vaskod8a74192023-02-06 15:51:50 +0100411 ret = pthread_mutex_clocklock(&session->opts.server.rpc_lock, COMPAT_CLOCK_ID, &ts_timeout);
Michal Vaskoade892d2017-02-22 13:40:35 +0100412 } else if (!timeout) {
413 /* LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100414 ret = pthread_mutex_trylock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100415 } else { /* timeout == -1 */
416 /* LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100417 ret = pthread_mutex_lock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100418 }
419
420 if (ret && (ret != EBUSY) && (ret != ETIMEDOUT)) {
421 /* error */
Michal Vasko05532772021-06-03 12:12:38 +0200422 ERR(session, "%s: failed to RPC lock a session (%s).", func, strerror(ret));
Michal Vaskoade892d2017-02-22 13:40:35 +0100423 return -1;
424 } else if (ret) {
Michal Vasko69e98752022-12-14 14:20:17 +0100425 WRN(session, "%s: session RPC lock timeout, should not happen.", func);
Michal Vaskoade892d2017-02-22 13:40:35 +0100426 }
427
Michal Vaskoacf98472021-02-04 15:33:57 +0100428 session->opts.server.rpc_inuse = 0;
429 pthread_cond_signal(&session->opts.server.rpc_cond);
Michal Vaskoade892d2017-02-22 13:40:35 +0100430
431 if (!ret) {
432 /* UNLOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100433 ret = pthread_mutex_unlock(&session->opts.server.rpc_lock);
Michal Vaskoade892d2017-02-22 13:40:35 +0100434 if (ret) {
435 /* error */
Michal Vasko05532772021-06-03 12:12:38 +0200436 ERR(session, "%s: failed to RPC unlock a session (%s).", func, strerror(ret));
Michal Vaskoade892d2017-02-22 13:40:35 +0100437 return -1;
438 }
439 }
440
Michal Vasko96164bf2016-01-21 15:41:58 +0100441 return 1;
442}
443
Michal Vasko131120a2018-05-29 15:44:02 +0200444int
445nc_session_io_lock(struct nc_session *session, int timeout, const char *func)
446{
447 int ret;
448 struct timespec ts_timeout;
449
450 if (timeout > 0) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100451 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko131120a2018-05-29 15:44:02 +0200452
Michal Vaskod8a74192023-02-06 15:51:50 +0100453 ret = pthread_mutex_clocklock(session->io_lock, COMPAT_CLOCK_ID, &ts_timeout);
Michal Vasko131120a2018-05-29 15:44:02 +0200454 } else if (!timeout) {
455 ret = pthread_mutex_trylock(session->io_lock);
456 } else { /* timeout == -1 */
Robin Jarry54ea2962018-10-10 10:33:40 +0200457 ret = pthread_mutex_lock(session->io_lock);
Michal Vasko131120a2018-05-29 15:44:02 +0200458 }
459
460 if (ret) {
461 if ((ret == EBUSY) || (ret == ETIMEDOUT)) {
462 /* timeout */
463 return 0;
464 }
465
466 /* error */
Michal Vasko05532772021-06-03 12:12:38 +0200467 ERR(session, "%s: failed to IO lock a session (%s).", func, strerror(ret));
Michal Vasko131120a2018-05-29 15:44:02 +0200468 return -1;
469 }
470
471 return 1;
472}
473
474int
475nc_session_io_unlock(struct nc_session *session, const char *func)
476{
477 int ret;
478
479 ret = pthread_mutex_unlock(session->io_lock);
480 if (ret) {
481 /* error */
Michal Vasko05532772021-06-03 12:12:38 +0200482 ERR(session, "%s: failed to IO unlock a session (%s).", func, strerror(ret));
Michal Vasko131120a2018-05-29 15:44:02 +0200483 return -1;
484 }
485
486 return 1;
487}
488
Michal Vasko01130bd2021-08-26 11:47:38 +0200489int
490nc_session_client_msgs_lock(struct nc_session *session, int *timeout, const char *func)
491{
492 int ret;
493 int32_t diff_msec;
roman6ece9c52022-06-22 09:29:17 +0200494 struct timespec ts_timeout, ts_start;
Michal Vasko01130bd2021-08-26 11:47:38 +0200495
496 assert(session->side == NC_CLIENT);
497
498 if (*timeout > 0) {
499 /* get current time */
Michal Vaskod8a74192023-02-06 15:51:50 +0100500 nc_timeouttime_get(&ts_start, 0);
Michal Vasko01130bd2021-08-26 11:47:38 +0200501
Michal Vaskod8a74192023-02-06 15:51:50 +0100502 nc_timeouttime_get(&ts_timeout, *timeout);
Michal Vasko01130bd2021-08-26 11:47:38 +0200503
Michal Vaskod8a74192023-02-06 15:51:50 +0100504 ret = pthread_mutex_clocklock(&session->opts.client.msgs_lock, COMPAT_CLOCK_ID, &ts_timeout);
Michal Vasko01130bd2021-08-26 11:47:38 +0200505 if (!ret) {
506 /* update timeout based on what was elapsed */
Michal Vaskod8a74192023-02-06 15:51:50 +0100507 diff_msec = nc_timeouttime_cur_diff(&ts_start);
Michal Vasko01130bd2021-08-26 11:47:38 +0200508 *timeout -= diff_msec;
509 }
510 } else if (!*timeout) {
511 ret = pthread_mutex_trylock(&session->opts.client.msgs_lock);
512 } else { /* timeout == -1 */
513 ret = pthread_mutex_lock(&session->opts.client.msgs_lock);
514 }
515
516 if (ret) {
517 if ((ret == EBUSY) || (ret == ETIMEDOUT)) {
518 /* timeout */
519 return 0;
520 }
521
522 /* error */
523 ERR(session, "%s: failed to MSGS lock a session (%s).", func, strerror(ret));
524 return -1;
525 }
526
527 return 1;
528}
529
530int
531nc_session_client_msgs_unlock(struct nc_session *session, const char *func)
532{
533 int ret;
534
535 assert(session->side == NC_CLIENT);
536
537 ret = pthread_mutex_unlock(&session->opts.client.msgs_lock);
538 if (ret) {
539 /* error */
540 ERR(session, "%s: failed to MSGS unlock a session (%s).", func, strerror(ret));
541 return -1;
542 }
543
544 return 1;
545}
546
Michal Vasko8dadf782016-01-15 10:29:36 +0100547API NC_STATUS
548nc_session_get_status(const struct nc_session *session)
549{
roman40672412023-05-04 11:10:22 +0200550 NC_CHECK_ARG_RET(session, session, NC_STATUS_ERR);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100551
Michal Vasko8dadf782016-01-15 10:29:36 +0100552 return session->status;
553}
554
Radek Krejci6e36bfb2016-12-01 21:40:16 +0100555API NC_SESSION_TERM_REASON
Michal Vasko142cfea2017-08-07 10:12:11 +0200556nc_session_get_term_reason(const struct nc_session *session)
Radek Krejci6e36bfb2016-12-01 21:40:16 +0100557{
roman40672412023-05-04 11:10:22 +0200558 NC_CHECK_ARG_RET(session, session, NC_SESSION_TERM_ERR);
Radek Krejci6e36bfb2016-12-01 21:40:16 +0100559
560 return session->term_reason;
561}
562
Michal Vasko8dadf782016-01-15 10:29:36 +0100563API uint32_t
Michal Vasko142cfea2017-08-07 10:12:11 +0200564nc_session_get_killed_by(const struct nc_session *session)
565{
roman40672412023-05-04 11:10:22 +0200566 NC_CHECK_ARG_RET(session, session, 0);
Michal Vasko142cfea2017-08-07 10:12:11 +0200567
568 return session->killed_by;
569}
570
571API uint32_t
Michal Vasko8dadf782016-01-15 10:29:36 +0100572nc_session_get_id(const struct nc_session *session)
573{
roman40672412023-05-04 11:10:22 +0200574 NC_CHECK_ARG_RET(session, session, 0);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100575
Michal Vasko8dadf782016-01-15 10:29:36 +0100576 return session->id;
577}
578
Michal Vasko174fe8e2016-02-17 15:38:09 +0100579API int
580nc_session_get_version(const struct nc_session *session)
581{
roman40672412023-05-04 11:10:22 +0200582 NC_CHECK_ARG_RET(session, session, -1);
Michal Vasko174fe8e2016-02-17 15:38:09 +0100583
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200584 return session->version == NC_VERSION_10 ? 0 : 1;
Michal Vasko174fe8e2016-02-17 15:38:09 +0100585}
586
Michal Vasko8dadf782016-01-15 10:29:36 +0100587API NC_TRANSPORT_IMPL
588nc_session_get_ti(const struct nc_session *session)
589{
roman40672412023-05-04 11:10:22 +0200590 NC_CHECK_ARG_RET(session, session, 0);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100591
Michal Vasko8dadf782016-01-15 10:29:36 +0100592 return session->ti_type;
593}
594
595API const char *
596nc_session_get_username(const struct nc_session *session)
597{
roman40672412023-05-04 11:10:22 +0200598 NC_CHECK_ARG_RET(session, session, NULL);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100599
Michal Vasko8dadf782016-01-15 10:29:36 +0100600 return session->username;
601}
602
603API const char *
604nc_session_get_host(const struct nc_session *session)
605{
roman40672412023-05-04 11:10:22 +0200606 NC_CHECK_ARG_RET(session, session, NULL);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100607
Michal Vasko8dadf782016-01-15 10:29:36 +0100608 return session->host;
609}
610
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200611API const char *
612nc_session_get_path(const struct nc_session *session)
613{
roman40672412023-05-04 11:10:22 +0200614 NC_CHECK_ARG_RET(session, session, NULL);
615
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200616 if (session->ti_type != NC_TI_UNIX) {
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200617 return NULL;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200618 }
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200619
620 return session->path;
621}
622
Michal Vasko8dadf782016-01-15 10:29:36 +0100623API uint16_t
624nc_session_get_port(const struct nc_session *session)
625{
roman40672412023-05-04 11:10:22 +0200626 NC_CHECK_ARG_RET(session, session, 0);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100627
Michal Vasko8dadf782016-01-15 10:29:36 +0100628 return session->port;
629}
630
Michal Vasko93224072021-11-09 12:14:28 +0100631API const struct ly_ctx *
Michal Vasko9a25e932016-02-01 10:36:42 +0100632nc_session_get_ctx(const struct nc_session *session)
633{
roman40672412023-05-04 11:10:22 +0200634 NC_CHECK_ARG_RET(session, session, NULL);
Michal Vasko9a25e932016-02-01 10:36:42 +0100635
636 return session->ctx;
637}
638
Michal Vasko2cc4c682016-03-01 09:16:48 +0100639API void
640nc_session_set_data(struct nc_session *session, void *data)
641{
642 if (!session) {
roman40672412023-05-04 11:10:22 +0200643 ERRARG(NULL, "session");
Michal Vasko2cc4c682016-03-01 09:16:48 +0100644 return;
645 }
646
647 session->data = data;
648}
649
650API void *
651nc_session_get_data(const struct nc_session *session)
652{
roman40672412023-05-04 11:10:22 +0200653 NC_CHECK_ARG_RET(session, session, NULL);
Michal Vasko2cc4c682016-03-01 09:16:48 +0100654
655 return session->data;
656}
657
Michal Vaskodc96bb92023-03-28 08:52:48 +0200658API int
659nc_session_is_callhome(const struct nc_session *session)
660{
roman40672412023-05-04 11:10:22 +0200661 NC_CHECK_ARG_RET(session, session, 0);
Michal Vaskodc96bb92023-03-28 08:52:48 +0200662
663 if (session->flags & NC_SESSION_CALLHOME) {
664 return 1;
665 }
666
667 return 0;
668}
669
Michal Vasko086311b2016-01-08 09:53:11 +0100670NC_MSG_TYPE
Michal Vasko131120a2018-05-29 15:44:02 +0200671nc_send_msg_io(struct nc_session *session, int io_timeout, struct lyd_node *op)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200672{
Michal Vasko7e1f5fb2021-11-10 10:14:45 +0100673 if (session->ctx != LYD_CTX(op)) {
674 ERR(session, "RPC \"%s\" was created in different context than that of the session.", LYD_NAME(op));
Michal Vasko086311b2016-01-08 09:53:11 +0100675 return NC_MSG_ERROR;
Michal Vasko7df39ec2015-12-09 15:26:24 +0100676 }
677
Michal Vasko131120a2018-05-29 15:44:02 +0200678 return nc_write_msg_io(session, io_timeout, NC_MSG_RPC, op, NULL);
Michal Vasko7df39ec2015-12-09 15:26:24 +0100679}
680
Michal Vaskod4da3632022-05-25 11:49:10 +0200681/**
682 * @brief Send \<close-session\> and read the reply on a session.
683 *
684 * @param[in] session Closing NETCONF session.
685 */
686static void
687nc_session_free_close_session(struct nc_session *session)
688{
689 struct ly_in *msg;
690 struct lyd_node *close_rpc, *envp;
691 const struct lys_module *ietfnc;
692
693 ietfnc = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf");
694 if (!ietfnc) {
Michal Vasko5ca5d972022-09-14 13:51:31 +0200695 WRN(session, "Missing ietf-netconf module in context, unable to send <close-session>.");
Michal Vaskod4da3632022-05-25 11:49:10 +0200696 return;
697 }
698 if (lyd_new_inner(NULL, ietfnc, "close-session", 0, &close_rpc)) {
699 WRN(session, "Failed to create <close-session> RPC.");
700 return;
701 }
702
703 /* send the RPC */
704 nc_send_msg_io(session, NC_SESSION_FREE_LOCK_TIMEOUT, close_rpc);
705
706read_msg:
707 switch (nc_read_msg_poll_io(session, NC_CLOSE_REPLY_TIMEOUT, &msg)) {
708 case 1:
709 if (!strncmp(ly_in_memory(msg, NULL), "<notification", 13)) {
710 /* ignore */
711 ly_in_free(msg, 1);
712 goto read_msg;
713 }
714 if (lyd_parse_op(session->ctx, close_rpc, msg, LYD_XML, LYD_TYPE_REPLY_NETCONF, &envp, NULL)) {
715 WRN(session, "Failed to parse <close-session> reply.");
716 } else if (!lyd_child(envp) || strcmp(LYD_NAME(lyd_child(envp)), "ok")) {
717 WRN(session, "Reply to <close-session> was not <ok> as expected.");
718 }
719 lyd_free_tree(envp);
720 ly_in_free(msg, 1);
721 break;
722 case 0:
723 WRN(session, "Timeout for receiving a reply to <close-session> elapsed.");
724 break;
725 case -1:
726 ERR(session, "Failed to receive a reply to <close-session>.");
727 break;
728 default:
729 /* cannot happen */
730 break;
731 }
732 lyd_free_tree(close_rpc);
733}
734
Michal Vasko33476c32022-09-09 11:21:40 +0200735/**
736 * @brief Free transport implementation members of a session.
737 *
738 * @param[in] session Session to free.
739 * @param[out] multisession Whether there are other NC sessions on the same SSH sessions.
740 */
741static void
742nc_session_free_transport(struct nc_session *session, int *multisession)
743{
744 int connected; /* flag to indicate whether the transport socket is still connected */
Michal Vaskoe44f2702022-12-12 07:58:06 +0100745 int sock = -1;
Michal Vasko33476c32022-09-09 11:21:40 +0200746 struct nc_session *siter;
747
748 *multisession = 0;
749 connected = nc_session_is_connected(session);
750
751 /* transport implementation cleanup */
752 switch (session->ti_type) {
753 case NC_TI_FD:
754 /* nothing needed - file descriptors were provided by caller,
755 * so it is up to the caller to close them correctly
756 * TODO use callbacks
757 */
758 /* just to avoid compiler warning */
759 (void)connected;
760 (void)siter;
761 break;
762
763 case NC_TI_UNIX:
764 sock = session->ti.unixsock.sock;
765 (void)connected;
766 (void)siter;
767 break;
768
roman2eab4742023-06-06 10:00:26 +0200769#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoe44f2702022-12-12 07:58:06 +0100770 case NC_TI_LIBSSH: {
771 int r;
772
Michal Vasko33476c32022-09-09 11:21:40 +0200773 if (connected) {
Michal Vasko64734402022-09-09 11:22:00 +0200774 ssh_channel_send_eof(session->ti.libssh.channel);
Michal Vasko33476c32022-09-09 11:21:40 +0200775 ssh_channel_free(session->ti.libssh.channel);
776 }
777 /* There can be multiple NETCONF sessions on the same SSH session (NETCONF session maps to
778 * SSH channel). So destroy the SSH session only if there is no other NETCONF session using
779 * it. Also, avoid concurrent free by multiple threads of sessions that share the SSH session.
780 */
781 /* SESSION IO LOCK */
782 r = nc_session_io_lock(session, NC_SESSION_FREE_LOCK_TIMEOUT, __func__);
783
784 if (session->ti.libssh.next) {
785 for (siter = session->ti.libssh.next; siter != session; siter = siter->ti.libssh.next) {
786 if (siter->status != NC_STATUS_STARTING) {
787 *multisession = 1;
788 break;
789 }
790 }
791 }
792
793 if (!*multisession) {
794 /* it's not multisession yet, but we still need to free the starting sessions */
795 if (session->ti.libssh.next) {
796 do {
797 siter = session->ti.libssh.next;
798 session->ti.libssh.next = siter->ti.libssh.next;
799
800 /* free starting SSH NETCONF session (channel will be freed in ssh_free()) */
801 free(siter->username);
802 free(siter->host);
803 if (!(siter->flags & NC_SESSION_SHAREDCTX)) {
804 ly_ctx_destroy((struct ly_ctx *)siter->ctx);
805 }
806
807 free(siter);
808 } while (session->ti.libssh.next != session);
809 }
810 /* remember sock so we can close it */
811 sock = ssh_get_fd(session->ti.libssh.session);
812 if (connected) {
Michal Vasko70e90622023-12-07 08:43:14 +0100813 /* does not close sock */
Michal Vasko33476c32022-09-09 11:21:40 +0200814 ssh_disconnect(session->ti.libssh.session);
Michal Vasko33476c32022-09-09 11:21:40 +0200815 }
816 ssh_free(session->ti.libssh.session);
817 } else {
818 /* remove the session from the list */
819 for (siter = session->ti.libssh.next; siter->ti.libssh.next != session; siter = siter->ti.libssh.next) {}
820 if (session->ti.libssh.next == siter) {
821 /* there will be only one session */
822 siter->ti.libssh.next = NULL;
823 } else {
824 /* there are still multiple sessions, keep the ring list */
825 siter->ti.libssh.next = session->ti.libssh.next;
826 }
Michal Vasko33476c32022-09-09 11:21:40 +0200827 }
828
829 /* SESSION IO UNLOCK */
830 if (r == 1) {
831 nc_session_io_unlock(session, __func__);
832 }
833 break;
Michal Vaskoe44f2702022-12-12 07:58:06 +0100834 }
Michal Vasko33476c32022-09-09 11:21:40 +0200835 case NC_TI_OPENSSL:
836 /* remember sock so we can close it */
837 sock = SSL_get_fd(session->ti.tls);
838
839 if (connected) {
840 SSL_shutdown(session->ti.tls);
841 }
842 SSL_free(session->ti.tls);
843
844 if (session->side == NC_SERVER) {
845 X509_free(session->opts.server.client_cert);
846 }
847 break;
roman2eab4742023-06-06 10:00:26 +0200848#endif /* NC_ENABLED_SSH_TLS */
Michal Vasko33476c32022-09-09 11:21:40 +0200849 case NC_TI_NONE:
850 break;
851 }
852
853 /* close socket separately */
854 if (sock > -1) {
855 close(sock);
856 }
857}
858
Radek Krejci695d4fa2015-10-22 13:23:54 +0200859API void
Michal Vaskoe1a64ec2016-03-01 12:21:58 +0100860nc_session_free(struct nc_session *session, void (*data_free)(void *))
Radek Krejci695d4fa2015-10-22 13:23:54 +0200861{
Michal Vasko33476c32022-09-09 11:21:40 +0200862 int r, i, rpc_locked = 0, msgs_locked = 0, timeout;
Michal Vaskob48aa812016-01-18 14:13:09 +0100863 int multisession = 0; /* flag for more NETCONF sessions on a single SSH session */
Michal Vaskoad611702015-12-03 13:41:51 +0100864 struct nc_msg_cont *contiter;
Michal Vasko77367452021-02-16 16:32:18 +0100865 struct ly_in *msg;
roman6ece9c52022-06-22 09:29:17 +0200866 struct timespec ts;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200867 void *p;
868
Michal Vasko428087d2016-01-14 16:04:28 +0100869 if (!session || (session->status == NC_STATUS_CLOSING)) {
Radek Krejci695d4fa2015-10-22 13:23:54 +0200870 return;
871 }
872
Michal Vaskoa8ec54b2022-10-20 09:59:07 +0200873 /* stop notification threads if any */
874 if ((session->side == NC_CLIENT) && ATOMIC_LOAD_RELAXED(session->opts.client.ntf_thread_running)) {
875 /* let the threads know they should quit */
876 ATOMIC_STORE_RELAXED(session->opts.client.ntf_thread_running, 0);
Michal Vasko5bd4a3f2021-06-17 16:40:10 +0200877
Michal Vaskoa8ec54b2022-10-20 09:59:07 +0200878 /* wait for them */
Michal Vaskod8a74192023-02-06 15:51:50 +0100879 nc_timeouttime_get(&ts, NC_SESSION_FREE_LOCK_TIMEOUT);
Michal Vaskoa8ec54b2022-10-20 09:59:07 +0200880 while (ATOMIC_LOAD_RELAXED(session->opts.client.ntf_thread_count)) {
Michal Vasko5bd4a3f2021-06-17 16:40:10 +0200881 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +0100882 if (nc_timeouttime_cur_diff(&ts) < 1) {
Michal Vasko5bd4a3f2021-06-17 16:40:10 +0200883 ERR(session, "Waiting for notification thread exit failed (timed out).");
884 break;
885 }
886 }
Michal Vasko86d357c2016-03-11 13:46:38 +0100887 }
888
Michal Vaskoacf98472021-02-04 15:33:57 +0100889 if (session->side == NC_SERVER) {
Michal Vasko131120a2018-05-29 15:44:02 +0200890 r = nc_session_rpc_lock(session, NC_SESSION_FREE_LOCK_TIMEOUT, __func__);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100891 if (r == -1) {
Michal Vaskoadd4c792015-10-26 15:36:58 +0100892 return;
Michal Vasko131120a2018-05-29 15:44:02 +0200893 } else if (r) {
894 rpc_locked = 1;
Michal Vasko96a28a32021-02-04 15:35:20 +0100895 } else {
896 /* else failed to lock it, too bad */
Michal Vasko05532772021-06-03 12:12:38 +0200897 ERR(session, "Freeing a session while an RPC is being processed.");
Michal Vasko96a28a32021-02-04 15:35:20 +0100898 }
Radek Krejci695d4fa2015-10-22 13:23:54 +0200899 }
900
Michal Vasko8c247822020-09-07 13:23:23 +0200901 if (session->side == NC_CLIENT) {
Michal Vasko01130bd2021-08-26 11:47:38 +0200902 timeout = NC_SESSION_FREE_LOCK_TIMEOUT;
tadeas-vintrlik54f142a2021-08-23 10:36:18 +0200903
Michal Vasko01130bd2021-08-26 11:47:38 +0200904 /* MSGS LOCK */
905 r = nc_session_client_msgs_lock(session, &timeout, __func__);
906 if (r == -1) {
907 return;
908 } else if (r) {
909 msgs_locked = 1;
910 } else {
911 /* else failed to lock it, too bad */
912 ERR(session, "Freeing a session while messages are being received.");
913 }
914
915 /* cleanup message queue */
tadeas-vintrlik54f142a2021-08-23 10:36:18 +0200916 for (contiter = session->opts.client.msgs; contiter; ) {
Michal Vasko77367452021-02-16 16:32:18 +0100917 ly_in_free(contiter->msg, 1);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200918
Michal Vaskoad611702015-12-03 13:41:51 +0100919 p = contiter;
920 contiter = contiter->next;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200921 free(p);
922 }
923
Michal Vasko01130bd2021-08-26 11:47:38 +0200924 if (msgs_locked) {
925 /* MSGS UNLOCK */
926 nc_session_client_msgs_unlock(session, __func__);
927 }
Radek Krejci695d4fa2015-10-22 13:23:54 +0200928
Michal Vasko8c247822020-09-07 13:23:23 +0200929 if (session->status == NC_STATUS_RUNNING) {
Michal Vasko9c6d38c2021-09-03 13:02:53 +0200930 /* receive any leftover messages */
931 while (nc_read_msg_poll_io(session, 0, &msg) == 1) {
932 ly_in_free(msg, 1);
933 }
934
Michal Vasko8c247822020-09-07 13:23:23 +0200935 /* send closing info to the other side */
Michal Vaskod4da3632022-05-25 11:49:10 +0200936 nc_session_free_close_session(session);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200937 }
938
939 /* list of server's capabilities */
Michal Vasko2e6defd2016-10-07 15:48:15 +0200940 if (session->opts.client.cpblts) {
941 for (i = 0; session->opts.client.cpblts[i]; i++) {
Michal Vasko96fc4bb2017-05-23 14:58:34 +0200942 free(session->opts.client.cpblts[i]);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200943 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200944 free(session->opts.client.cpblts);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200945 }
Michal Vasko78939072022-12-12 07:43:18 +0100946
947 /* LY ext data */
roman2eab4742023-06-06 10:00:26 +0200948#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoe44f2702022-12-12 07:58:06 +0100949 struct nc_session *siter;
950
Michal Vasko78939072022-12-12 07:43:18 +0100951 if ((session->flags & NC_SESSION_SHAREDCTX) && session->ti.libssh.next) {
952 for (siter = session->ti.libssh.next; siter != session; siter = siter->ti.libssh.next) {
953 if (siter->status != NC_STATUS_STARTING) {
954 /* move LY ext data to this session */
955 assert(!siter->opts.client.ext_data);
956 siter->opts.client.ext_data = session->opts.client.ext_data;
957 session->opts.client.ext_data = NULL;
958 break;
959 }
960 }
Michal Vaskoe44f2702022-12-12 07:58:06 +0100961 } else
roman2eab4742023-06-06 10:00:26 +0200962#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoe44f2702022-12-12 07:58:06 +0100963 {
Michal Vasko78939072022-12-12 07:43:18 +0100964 lyd_free_siblings(session->opts.client.ext_data);
965 }
Radek Krejci695d4fa2015-10-22 13:23:54 +0200966 }
967
Michal Vaskoe1a64ec2016-03-01 12:21:58 +0100968 if (session->data && data_free) {
969 data_free(session->data);
970 }
971
Michal Vaskoc4bc5812016-10-13 10:59:36 +0200972 if ((session->side == NC_SERVER) && (session->flags & NC_SESSION_CALLHOME)) {
973 /* CH LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +0100974 pthread_mutex_lock(&session->opts.server.ch_lock);
Michal Vaskoc4bc5812016-10-13 10:59:36 +0200975 }
976
Michal Vasko86d357c2016-03-11 13:46:38 +0100977 /* mark session for closing */
Radek Krejci695d4fa2015-10-22 13:23:54 +0200978 session->status = NC_STATUS_CLOSING;
Michal Vaskoc4bc5812016-10-13 10:59:36 +0200979
Michal Vaskofeccb312022-03-24 15:24:59 +0100980 if ((session->side == NC_SERVER) && (session->flags & NC_SESSION_CH_THREAD)) {
Michal Vaskoacf98472021-02-04 15:33:57 +0100981 pthread_cond_signal(&session->opts.server.ch_cond);
Michal Vaskoc4bc5812016-10-13 10:59:36 +0200982
Michal Vaskod8a74192023-02-06 15:51:50 +0100983 nc_timeouttime_get(&ts, NC_SESSION_FREE_LOCK_TIMEOUT);
Michal Vasko0db3db52021-03-03 10:45:42 +0100984
985 /* wait for CH thread to actually wake up and terminate */
986 r = 0;
Michal Vaskofeccb312022-03-24 15:24:59 +0100987 while (!r && (session->flags & NC_SESSION_CH_THREAD)) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100988 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 +0100989 }
Michal Vasko0db3db52021-03-03 10:45:42 +0100990 if (r) {
Michal Vasko05532772021-06-03 12:12:38 +0200991 ERR(session, "Waiting for Call Home thread failed (%s).", strerror(r));
Michal Vasko3f05a092018-03-13 10:39:49 +0100992 }
Michal Vaskoc4bc5812016-10-13 10:59:36 +0200993 }
994
Michal Vaskofeccb312022-03-24 15:24:59 +0100995 if ((session->side == NC_SERVER) && (session->flags & NC_SESSION_CALLHOME)) {
996 /* CH UNLOCK */
997 pthread_mutex_unlock(&session->opts.server.ch_lock);
998 }
999
Radek Krejci695d4fa2015-10-22 13:23:54 +02001000 /* transport implementation cleanup */
Michal Vasko33476c32022-09-09 11:21:40 +02001001 nc_session_free_transport(session, &multisession);
Radek Krejci695d4fa2015-10-22 13:23:54 +02001002
Michal Vasko33476c32022-09-09 11:21:40 +02001003 /* final cleanup */
Michal Vasko93224072021-11-09 12:14:28 +01001004 free(session->username);
1005 free(session->host);
1006 free(session->path);
Radek Krejci695d4fa2015-10-22 13:23:54 +02001007
Michal Vaskoacf98472021-02-04 15:33:57 +01001008 if (session->side == NC_SERVER) {
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001009 pthread_mutex_destroy(&session->opts.server.ntf_status_lock);
Michal Vasko131120a2018-05-29 15:44:02 +02001010 if (rpc_locked) {
1011 nc_session_rpc_unlock(session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vasko9e99f012016-03-03 13:25:20 +01001012 }
Michal Vaskoacf98472021-02-04 15:33:57 +01001013 pthread_mutex_destroy(&session->opts.server.rpc_lock);
1014 pthread_cond_destroy(&session->opts.server.rpc_cond);
Michal Vasko131120a2018-05-29 15:44:02 +02001015 }
1016
1017 if (session->io_lock && !multisession) {
1018 pthread_mutex_destroy(session->io_lock);
1019 free(session->io_lock);
Radek Krejci695d4fa2015-10-22 13:23:54 +02001020 }
1021
1022 if (!(session->flags & NC_SESSION_SHAREDCTX)) {
Michal Vasko93224072021-11-09 12:14:28 +01001023 ly_ctx_destroy((struct ly_ctx *)session->ctx);
Radek Krejci695d4fa2015-10-22 13:23:54 +02001024 }
1025
Michal Vaskoc4bc5812016-10-13 10:59:36 +02001026 if (session->side == NC_SERVER) {
Michal Vaskoacf98472021-02-04 15:33:57 +01001027 /* free CH synchronization structures */
1028 pthread_cond_destroy(&session->opts.server.ch_cond);
1029 pthread_mutex_destroy(&session->opts.server.ch_lock);
tadeas-vintrlik54f142a2021-08-23 10:36:18 +02001030 } else {
1031 pthread_mutex_destroy(&session->opts.client.msgs_lock);
Michal Vaskoc4bc5812016-10-13 10:59:36 +02001032 }
1033
Radek Krejci695d4fa2015-10-22 13:23:54 +02001034 free(session);
1035}
1036
Michal Vasko086311b2016-01-08 09:53:11 +01001037static void
Michal Vasko93224072021-11-09 12:14:28 +01001038add_cpblt(const char *capab, char ***cpblts, int *size, int *count)
Michal Vasko086311b2016-01-08 09:53:11 +01001039{
Radek Krejci658782b2016-12-04 22:04:55 +01001040 size_t len;
1041 int i;
1042 char *p;
1043
1044 if (capab) {
1045 /* check if already present */
1046 p = strchr(capab, '?');
1047 if (p) {
1048 len = p - capab;
1049 } else {
1050 len = strlen(capab);
1051 }
1052 for (i = 0; i < *count; i++) {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001053 if (!strncmp((*cpblts)[i], capab, len) && (((*cpblts)[i][len] == '\0') || ((*cpblts)[i][len] == '?'))) {
Radek Krejci658782b2016-12-04 22:04:55 +01001054 /* already present, do not duplicate it */
1055 return;
1056 }
1057 }
1058 }
1059
1060 /* add another capability */
Michal Vasko086311b2016-01-08 09:53:11 +01001061 if (*count == *size) {
1062 *size += 5;
Michal Vasko4eb3c312016-03-01 14:09:37 +01001063 *cpblts = nc_realloc(*cpblts, *size * sizeof **cpblts);
1064 if (!(*cpblts)) {
1065 ERRMEM;
1066 return;
1067 }
Michal Vasko086311b2016-01-08 09:53:11 +01001068 }
1069
Michal Vasko93224072021-11-09 12:14:28 +01001070 (*cpblts)[*count] = capab ? strdup(capab) : NULL;
Michal Vasko086311b2016-01-08 09:53:11 +01001071 ++(*count);
1072}
1073
Michal Vasko93224072021-11-09 12:14:28 +01001074API char **
1075nc_server_get_cpblts_version(const struct ly_ctx *ctx, LYS_VERSION version)
Michal Vasko086311b2016-01-08 09:53:11 +01001076{
Michal Vasko93224072021-11-09 12:14:28 +01001077 char **cpblts;
Michal Vasko77367452021-02-16 16:32:18 +01001078 const struct lys_module *mod;
1079 struct lysp_feature *feat;
1080 int size = 10, count, features_count = 0, dev_count = 0, str_len, len;
Michal Vasko1440a742021-03-31 11:11:03 +02001081 uint32_t i, u;
Michal Vasko77367452021-02-16 16:32:18 +01001082 LY_ARRAY_COUNT_TYPE v;
Michal Vasko1440a742021-03-31 11:11:03 +02001083 char *yl_content_id;
romanc1d2b092023-02-02 08:58:27 +01001084 uint32_t wd_also_supported;
1085 uint32_t wd_basic_mode;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001086
Radek Krejci24a18412018-05-16 15:09:10 +02001087#define NC_CPBLT_BUF_LEN 4096
Michal Vasko2e47ef92016-06-20 10:03:24 +02001088 char str[NC_CPBLT_BUF_LEN];
Michal Vasko086311b2016-01-08 09:53:11 +01001089
roman40672412023-05-04 11:10:22 +02001090 NC_CHECK_ARG_RET(NULL, ctx, NULL);
Michal Vasko4ffa3b22016-05-24 16:36:25 +02001091
Michal Vasko086311b2016-01-08 09:53:11 +01001092 cpblts = malloc(size * sizeof *cpblts);
roman124a4362023-10-26 15:36:22 +02001093 NC_CHECK_ERRMEM_GOTO(!cpblts, , error);
Michal Vasko93224072021-11-09 12:14:28 +01001094 cpblts[0] = strdup("urn:ietf:params:netconf:base:1.0");
1095 cpblts[1] = strdup("urn:ietf:params:netconf:base:1.1");
Michal Vasko086311b2016-01-08 09:53:11 +01001096 count = 2;
1097
1098 /* capabilities */
1099
Michal Vasko77367452021-02-16 16:32:18 +01001100 mod = ly_ctx_get_module_implemented(ctx, "ietf-netconf");
Michal Vasko086311b2016-01-08 09:53:11 +01001101 if (mod) {
Michal Vasko77367452021-02-16 16:32:18 +01001102 if (lys_feature_value(mod, "writable-running") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001103 add_cpblt("urn:ietf:params:netconf:capability:writable-running:1.0", &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001104 }
Michal Vasko77367452021-02-16 16:32:18 +01001105 if (lys_feature_value(mod, "candidate") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001106 add_cpblt("urn:ietf:params:netconf:capability:candidate:1.0", &cpblts, &size, &count);
Michal Vasko77367452021-02-16 16:32:18 +01001107 if (lys_feature_value(mod, "confirmed-commit") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001108 add_cpblt("urn:ietf:params:netconf:capability:confirmed-commit:1.1", &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001109 }
1110 }
Michal Vasko77367452021-02-16 16:32:18 +01001111 if (lys_feature_value(mod, "rollback-on-error") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001112 add_cpblt("urn:ietf:params:netconf:capability:rollback-on-error:1.0", &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001113 }
Michal Vasko77367452021-02-16 16:32:18 +01001114 if (lys_feature_value(mod, "validate") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001115 add_cpblt("urn:ietf:params:netconf:capability:validate:1.1", &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001116 }
Michal Vasko77367452021-02-16 16:32:18 +01001117 if (lys_feature_value(mod, "startup") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001118 add_cpblt("urn:ietf:params:netconf:capability:startup:1.0", &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001119 }
Michal Vaskof0fba4e2020-02-14 17:15:31 +01001120
1121 /* The URL capability must be set manually using nc_server_set_capability()
1122 * because of the need for supported protocols to be included.
1123 * https://tools.ietf.org/html/rfc6241#section-8.8.3
1124 */
Michal Vasko77367452021-02-16 16:32:18 +01001125 // if (lys_feature_value(mod, "url") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001126 // add_cpblt("urn:ietf:params:netconf:capability:url:1.0", &cpblts, &size, &count);
mekleoa8de5e92020-02-13 09:05:56 +01001127 // }
Michal Vaskof0fba4e2020-02-14 17:15:31 +01001128
Michal Vasko77367452021-02-16 16:32:18 +01001129 if (lys_feature_value(mod, "xpath") == LY_SUCCESS) {
Michal Vasko93224072021-11-09 12:14:28 +01001130 add_cpblt("urn:ietf:params:netconf:capability:xpath:1.0", &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001131 }
1132 }
1133
Michal Vasko77367452021-02-16 16:32:18 +01001134 mod = ly_ctx_get_module_implemented(ctx, "ietf-netconf-with-defaults");
Michal Vasko086311b2016-01-08 09:53:11 +01001135 if (mod) {
romanc1d2b092023-02-02 08:58:27 +01001136 wd_basic_mode = ATOMIC_LOAD_RELAXED(server_opts.wd_basic_mode);
1137 if (!wd_basic_mode) {
Michal Vasko05532772021-06-03 12:12:38 +02001138 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 +01001139 } else {
Michal Vasko3031aae2016-01-27 16:07:18 +01001140 strcpy(str, "urn:ietf:params:netconf:capability:with-defaults:1.0");
romanc1d2b092023-02-02 08:58:27 +01001141 switch (wd_basic_mode) {
Michal Vasko086311b2016-01-08 09:53:11 +01001142 case NC_WD_ALL:
1143 strcat(str, "?basic-mode=report-all");
1144 break;
1145 case NC_WD_TRIM:
1146 strcat(str, "?basic-mode=trim");
1147 break;
1148 case NC_WD_EXPLICIT:
1149 strcat(str, "?basic-mode=explicit");
1150 break;
1151 default:
Michal Vasko9e036d52016-01-08 10:49:26 +01001152 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +01001153 break;
1154 }
1155
romanc1d2b092023-02-02 08:58:27 +01001156 wd_also_supported = ATOMIC_LOAD_RELAXED(server_opts.wd_also_supported);
1157 if (wd_also_supported) {
Michal Vasko2e47ef92016-06-20 10:03:24 +02001158 strcat(str, "&also-supported=");
romanc1d2b092023-02-02 08:58:27 +01001159 if (wd_also_supported & NC_WD_ALL) {
Michal Vasko086311b2016-01-08 09:53:11 +01001160 strcat(str, "report-all,");
1161 }
romanc1d2b092023-02-02 08:58:27 +01001162 if (wd_also_supported & NC_WD_ALL_TAG) {
Michal Vasko086311b2016-01-08 09:53:11 +01001163 strcat(str, "report-all-tagged,");
1164 }
romanc1d2b092023-02-02 08:58:27 +01001165 if (wd_also_supported & NC_WD_TRIM) {
Michal Vasko086311b2016-01-08 09:53:11 +01001166 strcat(str, "trim,");
1167 }
romanc1d2b092023-02-02 08:58:27 +01001168 if (wd_also_supported & NC_WD_EXPLICIT) {
Michal Vasko086311b2016-01-08 09:53:11 +01001169 strcat(str, "explicit,");
1170 }
1171 str[strlen(str) - 1] = '\0';
1172
Michal Vasko93224072021-11-09 12:14:28 +01001173 add_cpblt(str, &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001174 }
1175 }
1176 }
1177
Radek Krejci658782b2016-12-04 22:04:55 +01001178 /* other capabilities */
1179 for (u = 0; u < server_opts.capabilities_count; u++) {
Michal Vasko93224072021-11-09 12:14:28 +01001180 add_cpblt(server_opts.capabilities[u], &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001181 }
1182
1183 /* models */
Michal Vasko1440a742021-03-31 11:11:03 +02001184 u = 0;
Radek Krejci24a18412018-05-16 15:09:10 +02001185 while ((mod = ly_ctx_get_module_iter(ctx, &u))) {
Radek Krejci24a18412018-05-16 15:09:10 +02001186 if (!strcmp(mod->name, "ietf-yang-library")) {
Michal Vasko77367452021-02-16 16:32:18 +01001187 if (!mod->revision || (strcmp(mod->revision, "2016-06-21") && strcmp(mod->revision, "2019-01-04"))) {
Michal Vasko05532772021-06-03 12:12:38 +02001188 ERR(NULL, "Unknown \"ietf-yang-library\" revision, only 2016-06-21 and 2019-01-04 are supported.");
Michal Vaskod5ada122020-03-19 18:28:06 +01001189 goto error;
Michal Vaskof0fba4e2020-02-14 17:15:31 +01001190 }
Michal Vaskod5ada122020-03-19 18:28:06 +01001191
Michal Vasko1440a742021-03-31 11:11:03 +02001192 /* get content-id */
1193 if (server_opts.content_id_clb) {
1194 yl_content_id = server_opts.content_id_clb(server_opts.content_id_data);
roman124a4362023-10-26 15:36:22 +02001195 NC_CHECK_ERRMEM_GOTO(!yl_content_id, , error);
Michal Vasko1440a742021-03-31 11:11:03 +02001196 } else {
1197 yl_content_id = malloc(11);
roman124a4362023-10-26 15:36:22 +02001198 NC_CHECK_ERRMEM_GOTO(!yl_content_id, , error);
Michal Vasko1440a742021-03-31 11:11:03 +02001199 sprintf(yl_content_id, "%u", ly_ctx_get_change_count(ctx));
1200 }
1201
Michal Vasko77367452021-02-16 16:32:18 +01001202 if (!strcmp(mod->revision, "2019-01-04")) {
Michal Vasko7b5e3d92020-04-08 14:40:31 +02001203 /* new one (capab defined in RFC 8526 section 2) */
Michal Vasko1440a742021-03-31 11:11:03 +02001204 sprintf(str, "urn:ietf:params:netconf:capability:yang-library:1.1?revision=%s&content-id=%s",
1205 mod->revision, yl_content_id);
Michal Vasko93224072021-11-09 12:14:28 +01001206 add_cpblt(str, &cpblts, &size, &count);
Michal Vasko7b5e3d92020-04-08 14:40:31 +02001207 } else {
1208 /* old one (capab defined in RFC 7950 section 5.6.4) */
Michal Vasko1440a742021-03-31 11:11:03 +02001209 sprintf(str, "urn:ietf:params:netconf:capability:yang-library:1.0?revision=%s&module-set-id=%s",
1210 mod->revision, yl_content_id);
Michal Vasko93224072021-11-09 12:14:28 +01001211 add_cpblt(str, &cpblts, &size, &count);
Michal Vaskod5ada122020-03-19 18:28:06 +01001212 }
Michal Vasko1440a742021-03-31 11:11:03 +02001213 free(yl_content_id);
Radek Krejci24a18412018-05-16 15:09:10 +02001214 continue;
Michal Vasko77367452021-02-16 16:32:18 +01001215 } else if ((version == LYS_VERSION_1_0) && (mod->parsed->version > version)) {
Michal Vasko5ca5d972022-09-14 13:51:31 +02001216 /* skip YANG 1.1 modules */
Radek Krejci24a18412018-05-16 15:09:10 +02001217 continue;
Michal Vasko77367452021-02-16 16:32:18 +01001218 } else if ((version == LYS_VERSION_1_1) && (mod->parsed->version != version)) {
Michal Vasko5ca5d972022-09-14 13:51:31 +02001219 /* skip YANG 1.0 modules */
Radek Krejci24a18412018-05-16 15:09:10 +02001220 continue;
1221 }
Michal Vasko086311b2016-01-08 09:53:11 +01001222
Michal Vasko77367452021-02-16 16:32:18 +01001223 str_len = sprintf(str, "%s?module=%s%s%s", mod->ns, mod->name, mod->revision ? "&revision=" : "",
1224 mod->revision ? mod->revision : "");
Radek Krejci24a18412018-05-16 15:09:10 +02001225
Michal Vaskodafdc742020-03-11 16:15:59 +01001226 features_count = 0;
1227 i = 0;
1228 feat = NULL;
Michal Vasko77367452021-02-16 16:32:18 +01001229 while ((feat = lysp_feature_next(feat, mod->parsed, &i))) {
Michal Vaskodafdc742020-03-11 16:15:59 +01001230 if (!(feat->flags & LYS_FENABLED)) {
1231 continue;
Michal Vaskoe90e4d12016-06-20 10:05:01 +02001232 }
Michal Vaskodafdc742020-03-11 16:15:59 +01001233 if (!features_count) {
1234 strcat(str, "&features=");
1235 str_len += 10;
1236 }
1237 len = strlen(feat->name);
1238 if (str_len + 1 + len >= NC_CPBLT_BUF_LEN) {
1239 ERRINT;
1240 break;
1241 }
1242 if (features_count) {
1243 strcat(str, ",");
1244 ++str_len;
1245 }
1246 strcat(str, feat->name);
1247 str_len += len;
1248 features_count++;
Michal Vasko086311b2016-01-08 09:53:11 +01001249 }
Michal Vasko086311b2016-01-08 09:53:11 +01001250
Michal Vasko77367452021-02-16 16:32:18 +01001251 if (mod->deviated_by) {
Radek Krejci24a18412018-05-16 15:09:10 +02001252 strcat(str, "&deviations=");
1253 str_len += 12;
1254 dev_count = 0;
Michal Vasko77367452021-02-16 16:32:18 +01001255 LY_ARRAY_FOR(mod->deviated_by, v) {
1256 len = strlen(mod->deviated_by[v]->name);
1257 if (str_len + 1 + len >= NC_CPBLT_BUF_LEN) {
1258 ERRINT;
1259 break;
Radek Krejci24a18412018-05-16 15:09:10 +02001260 }
Michal Vasko77367452021-02-16 16:32:18 +01001261 if (dev_count) {
1262 strcat(str, ",");
1263 ++str_len;
Radek Krejci24a18412018-05-16 15:09:10 +02001264 }
Michal Vasko77367452021-02-16 16:32:18 +01001265 strcat(str, mod->deviated_by[v]->name);
1266 str_len += len;
1267 dev_count++;
Radek Krejci24a18412018-05-16 15:09:10 +02001268 }
1269 }
1270
Michal Vasko93224072021-11-09 12:14:28 +01001271 add_cpblt(str, &cpblts, &size, &count);
Radek Krejci24a18412018-05-16 15:09:10 +02001272 }
Michal Vasko086311b2016-01-08 09:53:11 +01001273
1274 /* ending NULL capability */
Michal Vasko93224072021-11-09 12:14:28 +01001275 add_cpblt(NULL, &cpblts, &size, &count);
Michal Vasko086311b2016-01-08 09:53:11 +01001276
1277 return cpblts;
Radek Krejcif906e412017-09-22 14:44:45 +02001278
1279error:
Radek Krejcif906e412017-09-22 14:44:45 +02001280 free(cpblts);
Radek Krejcif906e412017-09-22 14:44:45 +02001281 return NULL;
Michal Vasko086311b2016-01-08 09:53:11 +01001282}
1283
Michal Vasko93224072021-11-09 12:14:28 +01001284API char **
1285nc_server_get_cpblts(const struct ly_ctx *ctx)
Radek Krejci24a18412018-05-16 15:09:10 +02001286{
1287 return nc_server_get_cpblts_version(ctx, LYS_VERSION_UNDEF);
1288}
1289
Radek Krejci695d4fa2015-10-22 13:23:54 +02001290static int
Michal Vasko77367452021-02-16 16:32:18 +01001291parse_cpblts(struct lyd_node *capabilities, char ***list)
Radek Krejci695d4fa2015-10-22 13:23:54 +02001292{
Michal Vasko77367452021-02-16 16:32:18 +01001293 struct lyd_node *iter;
1294 struct lyd_node_opaq *cpblt;
Michal Vasko156d3272017-04-11 11:46:49 +02001295 int ver = -1, i = 0;
1296 const char *cpb_start, *cpb_end;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001297
1298 if (list) {
1299 /* get the storage for server's capabilities */
Michal Vasko77367452021-02-16 16:32:18 +01001300 LY_LIST_FOR(lyd_child(capabilities), iter) {
Radek Krejci695d4fa2015-10-22 13:23:54 +02001301 i++;
1302 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001303 /* last item remains NULL */
1304 *list = calloc(i + 1, sizeof **list);
roman3a95bb22023-10-26 11:07:17 +02001305 NC_CHECK_ERRMEM_RET(!*list, -1);
Radek Krejci695d4fa2015-10-22 13:23:54 +02001306 i = 0;
1307 }
1308
Michal Vasko77367452021-02-16 16:32:18 +01001309 LY_LIST_FOR(lyd_child(capabilities), iter) {
1310 cpblt = (struct lyd_node_opaq *)iter;
1311
1312 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 +02001313 ERR(NULL, "Unexpected <%s> element in client's <hello>.", cpblt->name.name);
Radek Krejci695d4fa2015-10-22 13:23:54 +02001314 return -1;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001315 }
1316
Michal Vasko156d3272017-04-11 11:46:49 +02001317 /* skip leading/trailing whitespaces */
Michal Vasko77367452021-02-16 16:32:18 +01001318 for (cpb_start = cpblt->value; isspace(cpb_start[0]); ++cpb_start) {}
1319 for (cpb_end = cpblt->value + strlen(cpblt->value); (cpb_end > cpblt->value) && isspace(cpb_end[-1]); --cpb_end) {}
1320 if (!cpb_start[0] || (cpb_end == cpblt->value)) {
Michal Vasko05532772021-06-03 12:12:38 +02001321 ERR(NULL, "Empty capability \"%s\" received.", cpblt->value);
Michal Vasko156d3272017-04-11 11:46:49 +02001322 return -1;
1323 }
1324
Radek Krejci695d4fa2015-10-22 13:23:54 +02001325 /* detect NETCONF version */
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001326 if ((ver < 0) && !strncmp(cpb_start, "urn:ietf:params:netconf:base:1.0", cpb_end - cpb_start)) {
Radek Krejci695d4fa2015-10-22 13:23:54 +02001327 ver = 0;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001328 } 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 +02001329 ver = 1;
1330 }
1331
1332 /* store capabilities */
1333 if (list) {
Michal Vasko156d3272017-04-11 11:46:49 +02001334 (*list)[i] = strndup(cpb_start, cpb_end - cpb_start);
roman3a95bb22023-10-26 11:07:17 +02001335 NC_CHECK_ERRMEM_RET(!(*list)[i], -1);
Radek Krejci695d4fa2015-10-22 13:23:54 +02001336 i++;
1337 }
1338 }
1339
1340 if (ver == -1) {
Michal Vasko05532772021-06-03 12:12:38 +02001341 ERR(NULL, "Peer does not support a compatible NETCONF version.");
Radek Krejci695d4fa2015-10-22 13:23:54 +02001342 }
1343
1344 return ver;
1345}
1346
1347static NC_MSG_TYPE
Michal Vasko131120a2018-05-29 15:44:02 +02001348nc_send_hello_io(struct nc_session *session)
Michal Vasko086311b2016-01-08 09:53:11 +01001349{
Michal Vasko131120a2018-05-29 15:44:02 +02001350 NC_MSG_TYPE ret;
Michal Vaskocf898172024-01-15 15:04:28 +01001351 int i, timeout_io;
Michal Vasko93224072021-11-09 12:14:28 +01001352 char **cpblts;
Michal Vasko131120a2018-05-29 15:44:02 +02001353 uint32_t *sid;
Michal Vasko086311b2016-01-08 09:53:11 +01001354
Michal Vasko131120a2018-05-29 15:44:02 +02001355 if (session->side == NC_CLIENT) {
1356 /* client side hello - send only NETCONF base capabilities */
1357 cpblts = malloc(3 * sizeof *cpblts);
roman3a95bb22023-10-26 11:07:17 +02001358 NC_CHECK_ERRMEM_RET(!cpblts, NC_MSG_ERROR);
Michal Vasko93224072021-11-09 12:14:28 +01001359 cpblts[0] = strdup("urn:ietf:params:netconf:base:1.0");
1360 cpblts[1] = strdup("urn:ietf:params:netconf:base:1.1");
Michal Vasko131120a2018-05-29 15:44:02 +02001361 cpblts[2] = NULL;
1362
Michal Vaskocf898172024-01-15 15:04:28 +01001363 timeout_io = NC_CLIENT_HELLO_TIMEOUT * 1000;
Michal Vasko131120a2018-05-29 15:44:02 +02001364 sid = NULL;
1365 } else {
Michal Vasko77367452021-02-16 16:32:18 +01001366 cpblts = nc_server_get_cpblts_version(session->ctx, LYS_VERSION_1_0);
Michal Vasko5b24b6b2020-12-04 09:29:47 +01001367 if (!cpblts) {
1368 return NC_MSG_ERROR;
1369 }
Michal Vasko131120a2018-05-29 15:44:02 +02001370
Michal Vaskocf898172024-01-15 15:04:28 +01001371 if (session->flags & NC_SESSION_CALLHOME) {
1372 timeout_io = NC_SERVER_CH_HELLO_TIMEOUT * 1000;
1373 } else {
Michal Vasko5c1c9b32024-02-02 09:02:42 +01001374 timeout_io = server_opts.idle_timeout ? server_opts.idle_timeout * 1000 : -1;
Michal Vaskocf898172024-01-15 15:04:28 +01001375 }
Michal Vasko131120a2018-05-29 15:44:02 +02001376 sid = &session->id;
Michal Vasko4eb3c312016-03-01 14:09:37 +01001377 }
Michal Vasko05ba9df2016-01-13 14:40:27 +01001378
Michal Vaskocf898172024-01-15 15:04:28 +01001379 ret = nc_write_msg_io(session, timeout_io, NC_MSG_HELLO, cpblts, sid);
Michal Vasko086311b2016-01-08 09:53:11 +01001380
Michal Vasko086311b2016-01-08 09:53:11 +01001381 for (i = 0; cpblts[i]; ++i) {
Michal Vasko93224072021-11-09 12:14:28 +01001382 free(cpblts[i]);
Michal Vasko086311b2016-01-08 09:53:11 +01001383 }
1384 free(cpblts);
1385
Michal Vasko131120a2018-05-29 15:44:02 +02001386 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +01001387}
1388
1389static NC_MSG_TYPE
Michal Vasko131120a2018-05-29 15:44:02 +02001390nc_recv_client_hello_io(struct nc_session *session)
Radek Krejci695d4fa2015-10-22 13:23:54 +02001391{
Michal Vasko77367452021-02-16 16:32:18 +01001392 struct ly_in *msg;
1393 struct lyd_node *hello = NULL, *iter;
1394 struct lyd_node_opaq *node;
1395 int r, ver = -1, flag = 0;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001396 char *str;
Michal Vasko292c5542023-02-01 14:33:17 +01001397 long long id;
Michal Vasko77367452021-02-16 16:32:18 +01001398 NC_MSG_TYPE rc = NC_MSG_HELLO;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001399
Michal Vasko77367452021-02-16 16:32:18 +01001400 r = nc_read_msg_poll_io(session, NC_CLIENT_HELLO_TIMEOUT * 1000, &msg);
1401 switch (r) {
1402 case 1:
Radek Krejci695d4fa2015-10-22 13:23:54 +02001403 /* parse <hello> data */
Michal Vasko77367452021-02-16 16:32:18 +01001404 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 +02001405 ERR(session, "Failed to parse server <hello>.");
Michal Vasko77367452021-02-16 16:32:18 +01001406 rc = NC_MSG_ERROR;
1407 goto cleanup;
1408 }
1409
1410 LY_LIST_FOR(lyd_child(hello), iter) {
1411 node = (struct lyd_node_opaq *)iter;
1412
1413 if (!node->name.module_ns || strcmp(node->name.module_ns, NC_NS_BASE)) {
Michal Vasko11d142a2016-01-19 15:58:24 +01001414 continue;
Michal Vasko77367452021-02-16 16:32:18 +01001415 } else if (!strcmp(node->name.name, "session-id")) {
1416 if (!node->value || !strlen(node->value)) {
Michal Vasko05532772021-06-03 12:12:38 +02001417 ERR(session, "No value of <session-id> element in server <hello>.");
Michal Vasko77367452021-02-16 16:32:18 +01001418 rc = NC_MSG_ERROR;
1419 goto cleanup;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001420 }
Michal Vasko11d142a2016-01-19 15:58:24 +01001421 str = NULL;
Michal Vasko77367452021-02-16 16:32:18 +01001422 id = strtoll(node->value, &str, 10);
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001423 if (*str || (id < 1) || (id > UINT32_MAX)) {
Michal Vasko05532772021-06-03 12:12:38 +02001424 ERR(session, "Invalid value of <session-id> element in server <hello>.");
Michal Vasko77367452021-02-16 16:32:18 +01001425 rc = NC_MSG_ERROR;
1426 goto cleanup;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001427 }
Michal Vasko11d142a2016-01-19 15:58:24 +01001428 session->id = (uint32_t)id;
1429 continue;
Michal Vasko77367452021-02-16 16:32:18 +01001430 } else if (strcmp(node->name.name, "capabilities")) {
Michal Vasko05532772021-06-03 12:12:38 +02001431 ERR(session, "Unexpected <%s> element in server <hello>.", node->name.name);
Michal Vasko77367452021-02-16 16:32:18 +01001432 rc = NC_MSG_ERROR;
1433 goto cleanup;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001434 }
Michal Vasko11d142a2016-01-19 15:58:24 +01001435
1436 if (flag) {
1437 /* multiple capabilities elements */
Michal Vasko05532772021-06-03 12:12:38 +02001438 ERR(session, "Invalid <hello> message (multiple <capabilities> elements).");
Michal Vasko77367452021-02-16 16:32:18 +01001439 rc = NC_MSG_ERROR;
1440 goto cleanup;
Michal Vasko11d142a2016-01-19 15:58:24 +01001441 }
1442 flag = 1;
1443
Michal Vasko77367452021-02-16 16:32:18 +01001444 if ((ver = parse_cpblts(&node->node, &session->opts.client.cpblts)) < 0) {
1445 rc = NC_MSG_ERROR;
1446 goto cleanup;
Michal Vasko11d142a2016-01-19 15:58:24 +01001447 }
1448 session->version = ver;
1449 }
1450
1451 if (!session->id) {
Michal Vasko05532772021-06-03 12:12:38 +02001452 ERR(session, "Missing <session-id> in server <hello>.");
Michal Vasko77367452021-02-16 16:32:18 +01001453 rc = NC_MSG_ERROR;
1454 goto cleanup;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001455 }
1456 break;
Michal Vasko77367452021-02-16 16:32:18 +01001457 case 0:
Michal Vasko05532772021-06-03 12:12:38 +02001458 ERR(session, "Server <hello> timeout elapsed.");
Michal Vasko77367452021-02-16 16:32:18 +01001459 rc = NC_MSG_WOULDBLOCK;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001460 break;
1461 default:
Michal Vasko77367452021-02-16 16:32:18 +01001462 rc = NC_MSG_ERROR;
Michal Vasko71090fc2016-05-24 16:37:28 +02001463 break;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001464 }
1465
Michal Vasko77367452021-02-16 16:32:18 +01001466cleanup:
1467 ly_in_free(msg, 1);
1468 lyd_free_tree(hello);
1469 return rc;
Radek Krejci5686ff72015-10-09 13:33:56 +02001470}
1471
Michal Vasko11d142a2016-01-19 15:58:24 +01001472static NC_MSG_TYPE
Michal Vasko131120a2018-05-29 15:44:02 +02001473nc_recv_server_hello_io(struct nc_session *session)
Michal Vasko11d142a2016-01-19 15:58:24 +01001474{
Michal Vasko77367452021-02-16 16:32:18 +01001475 struct ly_in *msg;
1476 struct lyd_node *hello = NULL, *iter;
1477 struct lyd_node_opaq *node;
1478 NC_MSG_TYPE rc = NC_MSG_HELLO;
1479 int r, ver = -1, flag = 0, timeout_io;
Michal Vasko11d142a2016-01-19 15:58:24 +01001480
Michal Vaskocf898172024-01-15 15:04:28 +01001481 if (session->flags & NC_SESSION_CALLHOME) {
1482 timeout_io = NC_SERVER_CH_HELLO_TIMEOUT * 1000;
1483 } else {
Michal Vasko5c1c9b32024-02-02 09:02:42 +01001484 timeout_io = server_opts.idle_timeout ? server_opts.idle_timeout * 1000 : -1;
Michal Vaskocf898172024-01-15 15:04:28 +01001485 }
1486
Michal Vasko77367452021-02-16 16:32:18 +01001487 r = nc_read_msg_poll_io(session, timeout_io, &msg);
1488 switch (r) {
1489 case 1:
1490 /* parse <hello> data */
1491 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 +02001492 ERR(session, "Failed to parse client <hello>.");
Michal Vasko77367452021-02-16 16:32:18 +01001493 rc = NC_MSG_ERROR;
1494 goto cleanup;
1495 }
Michal Vasko11d142a2016-01-19 15:58:24 +01001496
Michal Vasko77367452021-02-16 16:32:18 +01001497 /* learn NETCONF version */
1498 LY_LIST_FOR(lyd_child(hello), iter) {
1499 node = (struct lyd_node_opaq *)iter;
1500
1501 if (!node->name.module_ns || strcmp(node->name.module_ns, NC_NS_BASE)) {
Michal Vasko11d142a2016-01-19 15:58:24 +01001502 continue;
Michal Vasko77367452021-02-16 16:32:18 +01001503 } else if (strcmp(node->name.name, "capabilities")) {
Michal Vasko05532772021-06-03 12:12:38 +02001504 ERR(session, "Unexpected <%s> element in client <hello>.", node->name.name);
Michal Vasko77367452021-02-16 16:32:18 +01001505 rc = NC_MSG_BAD_HELLO;
Michal Vasko11d142a2016-01-19 15:58:24 +01001506 goto cleanup;
1507 }
1508
1509 if (flag) {
1510 /* multiple capabilities elements */
Michal Vasko05532772021-06-03 12:12:38 +02001511 ERR(session, "Invalid <hello> message (multiple <capabilities> elements).");
Michal Vasko77367452021-02-16 16:32:18 +01001512 rc = NC_MSG_BAD_HELLO;
Michal Vasko11d142a2016-01-19 15:58:24 +01001513 goto cleanup;
1514 }
1515 flag = 1;
1516
Michal Vasko77367452021-02-16 16:32:18 +01001517 if ((ver = parse_cpblts(&node->node, NULL)) < 0) {
1518 rc = NC_MSG_BAD_HELLO;
Michal Vasko11d142a2016-01-19 15:58:24 +01001519 goto cleanup;
1520 }
1521 session->version = ver;
1522 }
1523 break;
Michal Vasko77367452021-02-16 16:32:18 +01001524 case 0:
Michal Vasko05532772021-06-03 12:12:38 +02001525 ERR(session, "Client <hello> timeout elapsed.");
Michal Vasko77367452021-02-16 16:32:18 +01001526 rc = NC_MSG_WOULDBLOCK;
Michal Vasko5e6f4cc2016-01-20 13:27:44 +01001527 break;
Michal Vasko11d142a2016-01-19 15:58:24 +01001528 default:
Michal Vasko77367452021-02-16 16:32:18 +01001529 rc = NC_MSG_ERROR;
Michal Vasko71090fc2016-05-24 16:37:28 +02001530 break;
Michal Vasko11d142a2016-01-19 15:58:24 +01001531 }
1532
1533cleanup:
Michal Vasko77367452021-02-16 16:32:18 +01001534 ly_in_free(msg, 1);
1535 lyd_free_tree(hello);
1536 return rc;
Michal Vasko11d142a2016-01-19 15:58:24 +01001537}
1538
Michal Vasko71090fc2016-05-24 16:37:28 +02001539NC_MSG_TYPE
Michal Vasko131120a2018-05-29 15:44:02 +02001540nc_handshake_io(struct nc_session *session)
Michal Vasko80cad7f2015-12-08 14:42:27 +01001541{
Michal Vasko086311b2016-01-08 09:53:11 +01001542 NC_MSG_TYPE type;
Michal Vasko80cad7f2015-12-08 14:42:27 +01001543
Michal Vasko131120a2018-05-29 15:44:02 +02001544 type = nc_send_hello_io(session);
Michal Vasko086311b2016-01-08 09:53:11 +01001545 if (type != NC_MSG_HELLO) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001546 return type;
Michal Vasko80cad7f2015-12-08 14:42:27 +01001547 }
1548
Michal Vasko11d142a2016-01-19 15:58:24 +01001549 if (session->side == NC_CLIENT) {
Michal Vasko131120a2018-05-29 15:44:02 +02001550 type = nc_recv_client_hello_io(session);
Michal Vasko11d142a2016-01-19 15:58:24 +01001551 } else {
Michal Vasko131120a2018-05-29 15:44:02 +02001552 type = nc_recv_server_hello_io(session);
Michal Vasko11d142a2016-01-19 15:58:24 +01001553 }
1554
Michal Vasko71090fc2016-05-24 16:37:28 +02001555 return type;
Michal Vasko80cad7f2015-12-08 14:42:27 +01001556}