blob: 59e116282c49b86d894df9399fb37db2b9944355 [file] [log] [blame]
Michal Vasko086311b2016-01-08 09:53:11 +01001/**
Michal Vasko95ea9ff2021-11-09 12:29:14 +01002 * @file session_server.c
3 * @author Michal Vasko <mvasko@cesnet.cz>
4 * @brief libnetconf2 server session manipulation functions
Michal Vasko086311b2016-01-08 09:53:11 +01005 *
Michal Vasko95ea9ff2021-11-09 12:29:14 +01006 * @copyright
Michal Vasko63b92d62024-04-29 10:04:56 +02007 * Copyright (c) 2015 - 2024 CESNET, z.s.p.o.
Michal Vasko086311b2016-01-08 09:53:11 +01008 *
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
Michal Vasko086311b2016-01-08 09:53:11 +010014 */
apropp-molex4e903c32020-04-20 03:06:58 -040015#define _QNX_SOURCE /* getpeereid */
Michal Vasko63b92d62024-04-29 10:04:56 +020016#define _GNU_SOURCE /* threads, SO_PEERCRED */
Michal Vasko086311b2016-01-08 09:53:11 +010017
Michal Vaskob83a3fa2021-05-26 09:53:42 +020018#include <arpa/inet.h>
Michal Vasko77e83572022-07-21 15:31:15 +020019#include <assert.h>
Michal Vasko086311b2016-01-08 09:53:11 +010020#include <errno.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020021#include <fcntl.h>
Olivier Matzac7fa2f2018-10-11 10:02:04 +020022#include <netinet/in.h>
23#include <netinet/tcp.h>
Michal Vaskob48aa812016-01-18 14:13:09 +010024#include <pthread.h>
Olivier Matzac7fa2f2018-10-11 10:02:04 +020025#include <pwd.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020026#include <stdint.h>
27#include <stdlib.h>
28#include <string.h>
29#include <sys/socket.h>
romanf578cd52023-10-19 09:47:40 +020030#include <sys/stat.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020031#include <sys/types.h>
32#include <sys/un.h>
33#include <time.h>
34#include <unistd.h>
Michal Vasko086311b2016-01-08 09:53:11 +010035
romanf578cd52023-10-19 09:47:40 +020036#ifdef NC_ENABLED_SSH_TLS
37#include <curl/curl.h>
38#endif
39
Michal Vasko7a20d2e2021-05-19 16:40:23 +020040#include "compat.h"
romanf578cd52023-10-19 09:47:40 +020041#include "config.h"
42#include "log_p.h"
43#include "messages_p.h"
44#include "messages_server.h"
45#include "server_config_p.h"
46#include "session.h"
47#include "session_p.h"
Michal Vasko086311b2016-01-08 09:53:11 +010048#include "session_server.h"
Michal Vasko0bdf70b2019-06-24 19:20:20 +020049#include "session_server_ch.h"
Michal Vasko086311b2016-01-08 09:53:11 +010050
Michal Vaskob48aa812016-01-18 14:13:09 +010051struct nc_server_opts server_opts = {
romanf578cd52023-10-19 09:47:40 +020052 .config_lock = PTHREAD_RWLOCK_INITIALIZER,
53 .ch_client_lock = PTHREAD_RWLOCK_INITIALIZER,
Michal Vaskocf898172024-01-15 15:04:28 +010054 .idle_timeout = 180, /**< default idle timeout (not in config for UNIX socket) */
Michal Vaskob48aa812016-01-18 14:13:09 +010055};
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +010056
fanchanghu966f2de2016-07-21 02:28:57 -040057static nc_rpc_clb global_rpc_clb = NULL;
58
roman423cc0d2023-11-24 11:29:47 +010059#ifdef NC_ENABLED_SSH_TLS
Michal Vasko6f865982023-11-21 12:10:42 +010060/**
61 * @brief Lock CH client structures for reading and lock the specific client.
62 *
63 * @param[in] name Name of the CH client.
64 * @return CH client, NULL if not found.
65 */
66static struct nc_ch_client *
67nc_server_ch_client_lock(const char *name)
Michal Vasko3031aae2016-01-27 16:07:18 +010068{
69 uint16_t i;
Michal Vasko2e6defd2016-10-07 15:48:15 +020070 struct nc_ch_client *client = NULL;
Michal Vaskoadf30f02019-06-24 09:34:47 +020071
Michal Vasko6f865982023-11-21 12:10:42 +010072 assert(name);
Michal Vaskoddce1212019-05-24 09:58:49 +020073
Michal Vasko2e6defd2016-10-07 15:48:15 +020074 /* READ LOCK */
75 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
76
77 for (i = 0; i < server_opts.ch_client_count; ++i) {
romanf578cd52023-10-19 09:47:40 +020078 if (server_opts.ch_clients[i].name && !strcmp(server_opts.ch_clients[i].name, name)) {
Michal Vasko2e6defd2016-10-07 15:48:15 +020079 client = &server_opts.ch_clients[i];
80 break;
81 }
82 }
83
84 if (!client) {
Michal Vaskoadf30f02019-06-24 09:34:47 +020085 /* READ UNLOCK */
86 pthread_rwlock_unlock(&server_opts.ch_client_lock);
87 } else {
88 /* CH CLIENT LOCK */
89 pthread_mutex_lock(&client->lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +020090 }
91
Michal Vasko6f865982023-11-21 12:10:42 +010092 return client;
Michal Vasko2e6defd2016-10-07 15:48:15 +020093}
94
Michal Vasko6f865982023-11-21 12:10:42 +010095/**
96 * @brief Unlock CH client strcutures and the specific client.
97 *
98 * @param[in] endpt Locked CH client structure.
99 */
100static void
Michal Vasko2e6defd2016-10-07 15:48:15 +0200101nc_server_ch_client_unlock(struct nc_ch_client *client)
102{
103 /* CH CLIENT UNLOCK */
104 pthread_mutex_unlock(&client->lock);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100105
106 /* READ UNLOCK */
Michal Vasko2e6defd2016-10-07 15:48:15 +0200107 pthread_rwlock_unlock(&server_opts.ch_client_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100108}
Michal Vasko086311b2016-01-08 09:53:11 +0100109
roman423cc0d2023-11-24 11:29:47 +0100110#endif /* NC_ENABLED_SSH_TLS */
111
roman78df0fa2023-11-02 10:33:57 +0100112int
113nc_server_get_referenced_endpt(const char *name, struct nc_endpt **endpt)
114{
115 uint16_t i;
116
117 for (i = 0; i < server_opts.endpt_count; i++) {
118 if (!strcmp(name, server_opts.endpts[i].name)) {
119 *endpt = &server_opts.endpts[i];
120 return 0;
121 }
122 }
123
124 ERR(NULL, "Referenced endpoint \"%s\" was not found.", name);
125 return 1;
126}
127
Michal Vasko1a38c862016-01-15 15:50:07 +0100128API void
129nc_session_set_term_reason(struct nc_session *session, NC_SESSION_TERM_REASON reason)
130{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200131 if (!session) {
romanf578cd52023-10-19 09:47:40 +0200132 ERRARG(session, "session");
Michal Vasko45e53ae2016-04-07 11:46:03 +0200133 return;
134 } else if (!reason) {
romanf578cd52023-10-19 09:47:40 +0200135 ERRARG(session, "reason");
Michal Vasko1a38c862016-01-15 15:50:07 +0100136 return;
137 }
138
Michal Vasko142cfea2017-08-07 10:12:11 +0200139 if ((reason != NC_SESSION_TERM_KILLED) && (session->term_reason == NC_SESSION_TERM_KILLED)) {
140 session->killed_by = 0;
141 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100142 session->term_reason = reason;
143}
144
Michal Vasko142cfea2017-08-07 10:12:11 +0200145API void
146nc_session_set_killed_by(struct nc_session *session, uint32_t sid)
147{
148 if (!session || (session->term_reason != NC_SESSION_TERM_KILLED)) {
romanf578cd52023-10-19 09:47:40 +0200149 ERRARG(session, "session");
Michal Vasko142cfea2017-08-07 10:12:11 +0200150 return;
151 } else if (!sid) {
romanf578cd52023-10-19 09:47:40 +0200152 ERRARG(session, "sid");
Michal Vasko142cfea2017-08-07 10:12:11 +0200153 return;
154 }
155
156 session->killed_by = sid;
157}
158
159API void
160nc_session_set_status(struct nc_session *session, NC_STATUS status)
161{
162 if (!session) {
romanf578cd52023-10-19 09:47:40 +0200163 ERRARG(session, "session");
Michal Vasko142cfea2017-08-07 10:12:11 +0200164 return;
165 } else if (!status) {
romanf578cd52023-10-19 09:47:40 +0200166 ERRARG(session, "status");
Michal Vasko142cfea2017-08-07 10:12:11 +0200167 return;
168 }
169
170 session->status = status;
171}
172
romanf578cd52023-10-19 09:47:40 +0200173API int
174nc_server_init_ctx(struct ly_ctx **ctx)
175{
176 int new_ctx = 0, i, ret = 0;
177 struct lys_module *module;
178 /* all features */
179 const char *ietf_netconf_features[] = {"writable-running", "candidate", "rollback-on-error", "validate", "startup", "url", "xpath", "confirmed-commit", NULL};
180 /* all features (module has no features) */
181 const char *ietf_netconf_monitoring_features[] = {NULL};
182
183 NC_CHECK_ARG_RET(NULL, ctx, 1);
184
185 if (!*ctx) {
186 /* context not given, create a new one */
187 if (ly_ctx_new(NC_SERVER_SEARCH_DIR, 0, ctx)) {
188 ERR(NULL, "Couldn't create new libyang context.\n");
189 ret = 1;
190 goto cleanup;
191 }
192 new_ctx = 1;
193 }
194
195 if (new_ctx) {
196 /* new context created, implement both modules */
197 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
198 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
199 ret = 1;
200 goto cleanup;
201 }
202
203 if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) {
204 ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n");
205 ret = 1;
206 goto cleanup;
207 }
208
209 goto cleanup;
210 }
211
212 module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf");
213 if (module) {
214 /* ietf-netconf module is present, check features */
215 for (i = 0; ietf_netconf_features[i]; i++) {
216 if (lys_feature_value(module, ietf_netconf_features[i])) {
217 /* feature not found, enable all of them */
218 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
219 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
220 ret = 1;
221 goto cleanup;
222 }
223
224 break;
225 }
226 }
227 } else {
228 /* ietf-netconf module not found, add it */
229 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
230 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
231 ret = 1;
232 goto cleanup;
233 }
234 }
235
236 module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf-monitoring");
237 if (!module) {
238 /* ietf-netconf-monitoring module not found, add it */
239 if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) {
240 ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n");
241 ret = 1;
242 goto cleanup;
243 }
244 }
245
246cleanup:
247 if (new_ctx && ret) {
248 ly_ctx_destroy(*ctx);
249 *ctx = NULL;
250 }
251 return ret;
252}
253
roman96c27f92023-11-02 11:09:46 +0100254#ifdef NC_ENABLED_SSH_TLS
255
roman450c00b2023-11-02 10:31:45 +0100256API void
257nc_server_ch_set_dispatch_data(nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
258 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
259 void *new_session_cb_data)
260{
261 NC_CHECK_ARG_RET(NULL, acquire_ctx_cb, release_ctx_cb, new_session_cb, );
262
263 server_opts.ch_dispatch_data.acquire_ctx_cb = acquire_ctx_cb;
264 server_opts.ch_dispatch_data.release_ctx_cb = release_ctx_cb;
265 server_opts.ch_dispatch_data.ctx_cb_data = ctx_cb_data;
266 server_opts.ch_dispatch_data.new_session_cb = new_session_cb;
267 server_opts.ch_dispatch_data.new_session_cb_data = new_session_cb_data;
268}
269
roman96c27f92023-11-02 11:09:46 +0100270#endif
271
Michal Vasko086311b2016-01-08 09:53:11 +0100272int
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200273nc_sock_listen_inet(const char *address, uint16_t port, struct nc_keepalives *ka)
Michal Vasko086311b2016-01-08 09:53:11 +0100274{
Michal Vasko06c860d2018-07-09 16:08:52 +0200275 int opt;
Michal Vasko086311b2016-01-08 09:53:11 +0100276 int is_ipv4, sock;
277 struct sockaddr_storage saddr;
278
279 struct sockaddr_in *saddr4;
280 struct sockaddr_in6 *saddr6;
281
Michal Vasko086311b2016-01-08 09:53:11 +0100282 if (!strchr(address, ':')) {
283 is_ipv4 = 1;
284 } else {
285 is_ipv4 = 0;
286 }
287
288 sock = socket((is_ipv4 ? AF_INET : AF_INET6), SOCK_STREAM, 0);
289 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200290 ERR(NULL, "Failed to create socket (%s).", strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100291 goto fail;
292 }
293
Michal Vaskobe52dc22018-10-17 09:28:17 +0200294 /* these options will be inherited by accepted sockets */
Michal Vasko06c860d2018-07-09 16:08:52 +0200295 opt = 1;
296 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200297 ERR(NULL, "Could not set SO_REUSEADDR socket option (%s).", strerror(errno));
Michal Vasko06c860d2018-07-09 16:08:52 +0200298 goto fail;
299 }
Michal Vasko83ad17e2019-01-30 10:11:37 +0100300 if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200301 ERR(NULL, "Could not set TCP_NODELAY socket option (%s).", strerror(errno));
Michal Vasko83ad17e2019-01-30 10:11:37 +0100302 goto fail;
303 }
Michal Vaskobe52dc22018-10-17 09:28:17 +0200304
roman56acd552024-04-18 16:00:37 +0200305 /* configure keepalives */
306 if (nc_sock_configure_ka(sock, ka->enabled)) {
Michal Vasko086311b2016-01-08 09:53:11 +0100307 goto fail;
308 }
roman56acd552024-04-18 16:00:37 +0200309 if (ka->enabled) {
310 if (nc_sock_configure_ka_idle_time(sock, ka->idle_time)) {
311 goto fail;
312 }
313 if (nc_sock_configure_ka_max_probes(sock, ka->max_probes)) {
314 goto fail;
315 }
316 if (nc_sock_configure_ka_probe_interval(sock, ka->probe_interval)) {
317 goto fail;
318 }
319 }
Michal Vasko086311b2016-01-08 09:53:11 +0100320
Michal Vaskof22d5ff2020-04-15 11:10:27 +0200321 memset(&saddr, 0, sizeof(struct sockaddr_storage));
Michal Vasko086311b2016-01-08 09:53:11 +0100322 if (is_ipv4) {
323 saddr4 = (struct sockaddr_in *)&saddr;
324
325 saddr4->sin_family = AF_INET;
326 saddr4->sin_port = htons(port);
327
328 if (inet_pton(AF_INET, address, &saddr4->sin_addr) != 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200329 ERR(NULL, "Failed to convert IPv4 address \"%s\".", address);
Michal Vasko086311b2016-01-08 09:53:11 +0100330 goto fail;
331 }
332
333 if (bind(sock, (struct sockaddr *)saddr4, sizeof(struct sockaddr_in)) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200334 ERR(NULL, "Could not bind \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100335 goto fail;
336 }
337
338 } else {
339 saddr6 = (struct sockaddr_in6 *)&saddr;
340
341 saddr6->sin6_family = AF_INET6;
342 saddr6->sin6_port = htons(port);
343
344 if (inet_pton(AF_INET6, address, &saddr6->sin6_addr) != 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200345 ERR(NULL, "Failed to convert IPv6 address \"%s\".", address);
Michal Vasko086311b2016-01-08 09:53:11 +0100346 goto fail;
347 }
348
349 if (bind(sock, (struct sockaddr *)saddr6, sizeof(struct sockaddr_in6)) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200350 ERR(NULL, "Could not bind \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100351 goto fail;
352 }
353 }
354
Michal Vaskofb89d772016-01-08 12:25:35 +0100355 if (listen(sock, NC_REVERSE_QUEUE) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200356 ERR(NULL, "Unable to start listening on \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100357 goto fail;
358 }
Michal Vasko086311b2016-01-08 09:53:11 +0100359 return sock;
360
361fail:
362 if (sock > -1) {
363 close(sock);
364 }
365
366 return -1;
367}
368
Michal Vaskoc429a8e2024-01-15 15:04:57 +0100369/**
370 * @brief Create a listening socket (AF_UNIX).
371 *
372 * @param[in] opts The server options (unix permissions and address of the socket).
373 * @return Listening socket, -1 on error.
374 */
375static int
roman83683fb2023-02-24 09:15:23 +0100376nc_sock_listen_unix(const struct nc_server_unix_opts *opts)
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200377{
378 struct sockaddr_un sun;
379 int sock = -1;
380
roman83683fb2023-02-24 09:15:23 +0100381 if (strlen(opts->address) > sizeof(sun.sun_path) - 1) {
382 ERR(NULL, "Socket path \"%s\" is longer than maximum length %d.", opts->address, (int)(sizeof(sun.sun_path) - 1));
Michal Vasko93e96f12021-09-30 10:02:09 +0200383 goto fail;
384 }
385
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200386 sock = socket(AF_UNIX, SOCK_STREAM, 0);
387 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200388 ERR(NULL, "Failed to create socket (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200389 goto fail;
390 }
391
392 memset(&sun, 0, sizeof(sun));
393 sun.sun_family = AF_UNIX;
roman83683fb2023-02-24 09:15:23 +0100394 snprintf(sun.sun_path, sizeof(sun.sun_path) - 1, "%s", opts->address);
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200395
396 unlink(sun.sun_path);
397 if (bind(sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
roman83683fb2023-02-24 09:15:23 +0100398 ERR(NULL, "Could not bind \"%s\" (%s).", opts->address, strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200399 goto fail;
400 }
401
402 if (opts->mode != (mode_t)-1) {
403 if (chmod(sun.sun_path, opts->mode) < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200404 ERR(NULL, "Failed to set unix socket permissions (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200405 goto fail;
406 }
407 }
408
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200409 if ((opts->uid != (uid_t)-1) || (opts->gid != (gid_t)-1)) {
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200410 if (chown(sun.sun_path, opts->uid, opts->gid) < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200411 ERR(NULL, "Failed to set unix socket uid/gid (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200412 goto fail;
413 }
414 }
415
416 if (listen(sock, NC_REVERSE_QUEUE) == -1) {
roman83683fb2023-02-24 09:15:23 +0100417 ERR(NULL, "Unable to start listening on \"%s\" (%s).", opts->address, strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200418 goto fail;
419 }
420
421 return sock;
422
423fail:
424 if (sock > -1) {
425 close(sock);
426 }
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200427 return -1;
428}
429
aPiecek90ff0242021-02-14 14:58:01 +0100430/**
431 * @brief Evaluate socket name for AF_UNIX socket.
432 * @param[in] acc_sock_fd is file descriptor for the accepted socket (a nonnegative).
433 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
434 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
435 * @return 0 if the stream socket is unnamed. Parameter host is set to NULL.
436 * @return -1 in case of error. Parameter host is set to NULL.
437 */
438static int
439sock_host_unix(int acc_sock_fd, char **host)
440{
441 char *sun_path;
442 struct sockaddr_storage saddr;
443 socklen_t addr_len;
444
445 *host = NULL;
446 saddr.ss_family = AF_UNIX;
447 addr_len = sizeof(saddr);
448
449 if (getsockname(acc_sock_fd, (struct sockaddr *)&saddr, &addr_len)) {
Michal Vasko05532772021-06-03 12:12:38 +0200450 ERR(NULL, "getsockname failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100451 return -1;
452 }
453
454 sun_path = ((struct sockaddr_un *)&saddr)->sun_path;
455 if (!sun_path) {
456 /* stream socket is unnamed */
457 return 0;
458 }
459
roman3a95bb22023-10-26 11:07:17 +0200460 NC_CHECK_ERRMEM_RET(!(*host = strdup(sun_path)), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100461
462 return 0;
463}
464
465/**
466 * @brief Evaluate socket name and port number for AF_INET socket.
467 * @param[in] addr is pointing to structure filled by accept function which was successful.
468 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
469 * @param[out] port is pointer to uint16_t to which the port number will be set. It must not be NULL.
470 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
471 * @return -1 in case of error. Parameter host is set to NULL and port is unchanged.
472 */
473static int
474sock_host_inet(const struct sockaddr_in *addr, char **host, uint16_t *port)
475{
476 *host = malloc(INET_ADDRSTRLEN);
roman3a95bb22023-10-26 11:07:17 +0200477 NC_CHECK_ERRMEM_RET(!(*host), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100478
aPiecek3da9b342021-02-18 15:00:03 +0100479 if (!inet_ntop(AF_INET, &addr->sin_addr, *host, INET_ADDRSTRLEN)) {
Michal Vasko69e98752022-12-14 14:20:17 +0100480 ERR(NULL, "inet_ntop failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100481 free(*host);
482 *host = NULL;
483 return -1;
484 }
485
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200486 *port = ntohs(addr->sin_port);
aPiecek90ff0242021-02-14 14:58:01 +0100487
488 return 0;
489}
490
491/**
492 * @brief Evaluate socket name and port number for AF_INET6 socket.
493 * @param[in] addr is pointing to structure filled by accept function which was successful.
494 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
495 * @param[out] port is pointer to uint16_t to which the port number will be set. It must not be NULL.
496 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
497 * @return -1 in case of error. Parameter host is set to the NULL and port is unchanged.
498 */
499static int
500sock_host_inet6(const struct sockaddr_in6 *addr, char **host, uint16_t *port)
501{
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200502 *host = malloc(INET6_ADDRSTRLEN);
roman3a95bb22023-10-26 11:07:17 +0200503 NC_CHECK_ERRMEM_RET(!(*host), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100504
aPiecek3da9b342021-02-18 15:00:03 +0100505 if (!inet_ntop(AF_INET6, &addr->sin6_addr, *host, INET6_ADDRSTRLEN)) {
Michal Vasko69e98752022-12-14 14:20:17 +0100506 ERR(NULL, "inet_ntop failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100507 free(*host);
508 *host = NULL;
509 return -1;
510 }
511
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200512 *port = ntohs(addr->sin6_port);
aPiecek90ff0242021-02-14 14:58:01 +0100513
514 return 0;
515}
516
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200517int
Michal Vasko6f865982023-11-21 12:10:42 +0100518nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, pthread_mutex_t *bind_lock, int timeout, char **host,
519 uint16_t *port, uint16_t *idx)
Michal Vasko086311b2016-01-08 09:53:11 +0100520{
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200521 uint16_t i, j, pfd_count, client_port;
522 char *client_address;
Michal Vasko086311b2016-01-08 09:53:11 +0100523 struct pollfd *pfd;
524 struct sockaddr_storage saddr;
525 socklen_t saddr_len = sizeof(saddr);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200526 int ret, client_sock, sock = -1, flags;
Michal Vasko086311b2016-01-08 09:53:11 +0100527
528 pfd = malloc(bind_count * sizeof *pfd);
roman3a95bb22023-10-26 11:07:17 +0200529 NC_CHECK_ERRMEM_RET(!pfd, -1);
Michal Vasko4eb3c312016-03-01 14:09:37 +0100530
romanf578cd52023-10-19 09:47:40 +0200531 /* LOCK */
532 pthread_mutex_lock(bind_lock);
533
Michal Vaskoac2f6182017-01-30 14:32:03 +0100534 for (i = 0, pfd_count = 0; i < bind_count; ++i) {
Michal Vasko94acafc2016-09-23 13:40:10 +0200535 if (binds[i].sock < 0) {
536 /* invalid socket */
Michal Vasko94acafc2016-09-23 13:40:10 +0200537 continue;
538 }
Michal Vasko0a3f3752016-10-13 14:58:38 +0200539 if (binds[i].pollin) {
540 binds[i].pollin = 0;
541 /* leftover pollin */
542 sock = binds[i].sock;
Michal Vasko086311b2016-01-08 09:53:11 +0100543 break;
544 }
Michal Vaskoac2f6182017-01-30 14:32:03 +0100545 pfd[pfd_count].fd = binds[i].sock;
546 pfd[pfd_count].events = POLLIN;
547 pfd[pfd_count].revents = 0;
548
549 ++pfd_count;
Michal Vasko086311b2016-01-08 09:53:11 +0100550 }
551
Michal Vasko0a3f3752016-10-13 14:58:38 +0200552 if (sock == -1) {
553 /* poll for a new connection */
Michal Vasko63b92d62024-04-29 10:04:56 +0200554 ret = nc_poll(pfd, pfd_count, timeout);
555 if (ret < 1) {
556 free(pfd);
Michal Vaskof54cd352017-02-22 13:42:02 +0100557
romanf578cd52023-10-19 09:47:40 +0200558 /* UNLOCK */
559 pthread_mutex_unlock(bind_lock);
Michal Vasko63b92d62024-04-29 10:04:56 +0200560
561 return ret;
Michal Vasko0a3f3752016-10-13 14:58:38 +0200562 }
Michal Vasko086311b2016-01-08 09:53:11 +0100563
Michal Vaskoac2f6182017-01-30 14:32:03 +0100564 for (i = 0, j = 0; j < pfd_count; ++i, ++j) {
565 /* adjust i so that indices in binds and pfd always match */
566 while (binds[i].sock != pfd[j].fd) {
567 ++i;
568 }
569
570 if (pfd[j].revents & POLLIN) {
Michal Vasko0a3f3752016-10-13 14:58:38 +0200571 --ret;
572
573 if (!ret) {
574 /* the last socket with an event, use it */
Michal Vaskoac2f6182017-01-30 14:32:03 +0100575 sock = pfd[j].fd;
Michal Vasko0a3f3752016-10-13 14:58:38 +0200576 break;
577 } else {
578 /* just remember the event for next time */
579 binds[i].pollin = 1;
580 }
581 }
Michal Vasko086311b2016-01-08 09:53:11 +0100582 }
583 }
584 free(pfd);
Michal Vasko086311b2016-01-08 09:53:11 +0100585 if (sock == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +0100586 ERRINT;
romanf578cd52023-10-19 09:47:40 +0200587 /* UNLOCK */
588 pthread_mutex_unlock(bind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100589 return -1;
590 }
591
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200592 /* accept connection */
593 client_sock = accept(sock, (struct sockaddr *)&saddr, &saddr_len);
594 if (client_sock < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200595 ERR(NULL, "Accept failed (%s).", strerror(errno));
romanf578cd52023-10-19 09:47:40 +0200596 /* UNLOCK */
597 pthread_mutex_unlock(bind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100598 return -1;
599 }
600
Michal Vasko0190bc32016-03-02 15:47:49 +0100601 /* make the socket non-blocking */
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200602 if (((flags = fcntl(client_sock, F_GETFL)) == -1) || (fcntl(client_sock, F_SETFL, flags | O_NONBLOCK) == -1)) {
Michal Vasko05532772021-06-03 12:12:38 +0200603 ERR(NULL, "Fcntl failed (%s).", strerror(errno));
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200604 goto fail;
Michal Vasko0190bc32016-03-02 15:47:49 +0100605 }
606
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200607 /* learn information about the client end */
608 if (saddr.ss_family == AF_UNIX) {
609 if (sock_host_unix(client_sock, &client_address)) {
610 goto fail;
611 }
612 client_port = 0;
613 } else if (saddr.ss_family == AF_INET) {
614 if (sock_host_inet((struct sockaddr_in *)&saddr, &client_address, &client_port)) {
615 goto fail;
616 }
617 } else if (saddr.ss_family == AF_INET6) {
618 if (sock_host_inet6((struct sockaddr_in6 *)&saddr, &client_address, &client_port)) {
619 goto fail;
620 }
621 } else {
622 ERR(NULL, "Source host of an unknown protocol family.");
623 goto fail;
aPiecek90ff0242021-02-14 14:58:01 +0100624 }
Michal Vasko086311b2016-01-08 09:53:11 +0100625
aPiecek90ff0242021-02-14 14:58:01 +0100626 if (saddr.ss_family == AF_UNIX) {
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200627 VRB(NULL, "Accepted a connection on %s.", binds[i].address);
aPiecek90ff0242021-02-14 14:58:01 +0100628 } else {
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200629 VRB(NULL, "Accepted a connection on %s:%u from %s:%u.", binds[i].address, binds[i].port, client_address, client_port);
Michal Vasko086311b2016-01-08 09:53:11 +0100630 }
631
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200632 if (host) {
633 *host = client_address;
634 } else {
635 free(client_address);
636 }
637 if (port) {
638 *port = client_port;
639 }
640 if (idx) {
641 *idx = i;
642 }
romanf578cd52023-10-19 09:47:40 +0200643 /* UNLOCK */
644 pthread_mutex_unlock(bind_lock);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200645 return client_sock;
646
647fail:
648 close(client_sock);
romanf578cd52023-10-19 09:47:40 +0200649 /* UNLOCK */
650 pthread_mutex_unlock(bind_lock);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200651 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100652}
653
Michal Vasko238b6c12021-12-14 15:14:09 +0100654API struct nc_server_reply *
Michal Vasko05532772021-06-03 12:12:38 +0200655nc_clb_default_get_schema(struct lyd_node *rpc, struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100656{
Michal Vasko77367452021-02-16 16:32:18 +0100657 const char *identifier = NULL, *revision = NULL, *format = NULL;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100658 char *model_data = NULL;
Michal Vasko77367452021-02-16 16:32:18 +0100659 struct ly_out *out;
Michal Vasko9b1a9522021-03-15 16:24:26 +0100660 const struct lys_module *module = NULL, *mod;
Michal Vasko77367452021-02-16 16:32:18 +0100661 const struct lysp_submodule *submodule = NULL;
662 struct lyd_node *child, *err, *data = NULL;
663 LYS_OUTFORMAT outformat = 0;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100664
Michal Vasko77367452021-02-16 16:32:18 +0100665 LY_LIST_FOR(lyd_child(rpc), child) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100666 if (!strcmp(child->schema->name, "identifier")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200667 identifier = lyd_get_value(child);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100668 } else if (!strcmp(child->schema->name, "version")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200669 revision = lyd_get_value(child);
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200670 if (revision && (revision[0] == '\0')) {
Michal Vasko77367452021-02-16 16:32:18 +0100671 revision = NULL;
Radek Krejci1afa7792017-03-26 11:24:16 -0500672 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100673 } else if (!strcmp(child->schema->name, "format")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200674 format = lyd_get_value(child);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100675 }
676 }
Michal Vasko5ca5d972022-09-14 13:51:31 +0200677 VRB(session, "Module \"%s@%s\" was requested.", identifier, revision ? revision : "<any>");
Michal Vasko05ba9df2016-01-13 14:40:27 +0100678
Michal Vasko77367452021-02-16 16:32:18 +0100679 /* check revision */
680 if (revision && (strlen(revision) != 10) && strcmp(revision, "1.0")) {
Michal Vasko93224072021-11-09 12:14:28 +0100681 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko1a38c862016-01-15 15:50:07 +0100682 nc_err_set_msg(err, "The requested version is not supported.", "en");
683 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100684 }
685
Michal Vasko77367452021-02-16 16:32:18 +0100686 if (revision) {
687 /* get specific module */
Michal Vasko93224072021-11-09 12:14:28 +0100688 module = ly_ctx_get_module(session->ctx, identifier, revision);
Michal Vasko77367452021-02-16 16:32:18 +0100689 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100690 submodule = ly_ctx_get_submodule(session->ctx, identifier, revision);
Michal Vasko77367452021-02-16 16:32:18 +0100691 }
692 } else {
693 /* try to get implemented, then latest module */
Michal Vasko93224072021-11-09 12:14:28 +0100694 module = ly_ctx_get_module_implemented(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100695 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100696 module = ly_ctx_get_module_latest(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100697 }
698 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100699 submodule = ly_ctx_get_submodule_latest(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100700 }
Michal Vaskod91f6e62016-04-05 11:34:22 +0200701 }
Michal Vasko77367452021-02-16 16:32:18 +0100702 if (!module && !submodule) {
Michal Vasko93224072021-11-09 12:14:28 +0100703 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko5ca5d972022-09-14 13:51:31 +0200704 nc_err_set_msg(err, "The requested module was not found.", "en");
Michal Vasko1a38c862016-01-15 15:50:07 +0100705 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100706 }
707
708 /* check format */
Radek Krejci90fba642016-12-07 15:59:45 +0100709 if (!format || !strcmp(format, "ietf-netconf-monitoring:yang")) {
Michal Vasko77367452021-02-16 16:32:18 +0100710 outformat = LYS_OUT_YANG;
Radek Krejci90fba642016-12-07 15:59:45 +0100711 } else if (!strcmp(format, "ietf-netconf-monitoring:yin")) {
Michal Vasko77367452021-02-16 16:32:18 +0100712 outformat = LYS_OUT_YIN;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100713 } else {
Michal Vasko93224072021-11-09 12:14:28 +0100714 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko1a38c862016-01-15 15:50:07 +0100715 nc_err_set_msg(err, "The requested format is not supported.", "en");
716 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100717 }
Michal Vasko77367452021-02-16 16:32:18 +0100718
719 /* print */
720 ly_out_new_memory(&model_data, 0, &out);
721 if (module) {
722 lys_print_module(out, module, outformat, 0, 0);
723 } else {
724 lys_print_submodule(out, submodule, outformat, 0, 0);
725 }
726 ly_out_free(out, NULL, 0);
Michal Vaskod91f6e62016-04-05 11:34:22 +0200727 if (!model_data) {
728 ERRINT;
729 return NULL;
730 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100731
Michal Vasko9b1a9522021-03-15 16:24:26 +0100732 /* create reply */
Michal Vasko93224072021-11-09 12:14:28 +0100733 mod = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf-monitoring");
Michal Vasko9b1a9522021-03-15 16:24:26 +0100734 if (!mod || lyd_new_inner(NULL, mod, "get-schema", 0, &data)) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100735 ERRINT;
Michal Vaskod91f6e62016-04-05 11:34:22 +0200736 free(model_data);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100737 return NULL;
738 }
Michal Vasko58791da2024-02-26 13:52:59 +0100739 if (lyd_new_any(data, NULL, "data", model_data, LYD_ANYDATA_STRING, LYD_NEW_ANY_USE_VALUE | LYD_NEW_VAL_OUTPUT, NULL)) {
Michal Vasko9b1a9522021-03-15 16:24:26 +0100740 ERRINT;
Michal Vaskoa50f68e2022-02-24 16:10:54 +0100741 free(model_data);
Michal Vasko9b1a9522021-03-15 16:24:26 +0100742 lyd_free_tree(data);
743 return NULL;
744 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100745
Radek Krejci36dfdb32016-09-01 16:56:35 +0200746 return nc_server_reply_data(data, NC_WD_EXPLICIT, NC_PARAMTYPE_FREE);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100747}
748
Michal Vasko238b6c12021-12-14 15:14:09 +0100749API struct nc_server_reply *
Michal Vasko428087d2016-01-14 16:04:28 +0100750nc_clb_default_close_session(struct lyd_node *UNUSED(rpc), struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100751{
Michal Vasko428087d2016-01-14 16:04:28 +0100752 session->term_reason = NC_SESSION_TERM_CLOSED;
753 return nc_server_reply_ok();
Michal Vasko05ba9df2016-01-13 14:40:27 +0100754}
755
Michal Vasko93224072021-11-09 12:14:28 +0100756/**
757 * @brief Initialize a context with default RPC callbacks if none are set.
758 *
759 * @param[in] ctx Context to initialize.
760 */
761static void
romanf578cd52023-10-19 09:47:40 +0200762nc_server_init_cb_ctx(const struct ly_ctx *ctx)
Michal Vasko086311b2016-01-08 09:53:11 +0100763{
Michal Vasko77367452021-02-16 16:32:18 +0100764 struct lysc_node *rpc;
Michal Vaskoa7b8ca52016-03-01 12:09:29 +0100765
Michal Vasko238b6c12021-12-14 15:14:09 +0100766 if (global_rpc_clb) {
767 /* expect it to handle these RPCs as well */
768 return;
769 }
770
Michal Vasko05ba9df2016-01-13 14:40:27 +0100771 /* set default <get-schema> callback if not specified */
Michal Vasko77367452021-02-16 16:32:18 +0100772 rpc = NULL;
773 if (ly_ctx_get_module_implemented(ctx, "ietf-netconf-monitoring")) {
774 rpc = (struct lysc_node *)lys_find_path(ctx, NULL, "/ietf-netconf-monitoring:get-schema", 0);
775 }
Michal Vasko88639e92017-08-03 14:38:10 +0200776 if (rpc && !rpc->priv) {
Michal Vasko77367452021-02-16 16:32:18 +0100777 rpc->priv = nc_clb_default_get_schema;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100778 }
779
Michal Vasko93224072021-11-09 12:14:28 +0100780 /* set default <close-session> callback if not specified */
Michal Vasko77367452021-02-16 16:32:18 +0100781 rpc = (struct lysc_node *)lys_find_path(ctx, NULL, "/ietf-netconf:close-session", 0);
Michal Vasko88639e92017-08-03 14:38:10 +0200782 if (rpc && !rpc->priv) {
Michal Vasko77367452021-02-16 16:32:18 +0100783 rpc->priv = nc_clb_default_close_session;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100784 }
Michal Vasko93224072021-11-09 12:14:28 +0100785}
Michal Vasko05ba9df2016-01-13 14:40:27 +0100786
Michal Vasko93224072021-11-09 12:14:28 +0100787API int
788nc_server_init(void)
789{
Michal Vasko29f2f022024-03-13 09:06:48 +0100790 pthread_rwlockattr_t *attr_p = NULL;
Michal Vasko93224072021-11-09 12:14:28 +0100791 int r;
792
romand82caf12024-03-05 14:21:39 +0100793 ATOMIC_STORE_RELAXED(server_opts.new_session_id, 1);
794 ATOMIC_STORE_RELAXED(server_opts.new_client_id, 1);
Michal Vaskob48aa812016-01-18 14:13:09 +0100795
Michal Vasko93224072021-11-09 12:14:28 +0100796#ifdef HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP
Michal Vasko29f2f022024-03-13 09:06:48 +0100797 pthread_rwlockattr_t attr;
798
Michal Vasko93224072021-11-09 12:14:28 +0100799 if ((r = pthread_rwlockattr_init(&attr))) {
800 ERR(NULL, "%s: failed init attribute (%s).", __func__, strerror(r));
801 goto error;
802 }
803 attr_p = &attr;
804 if ((r = pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP))) {
805 ERR(NULL, "%s: failed set attribute (%s).", __func__, strerror(r));
806 goto error;
807 }
Rosen Penevef2f3ac2019-07-15 18:15:28 -0700808#endif
Michal Vasko93224072021-11-09 12:14:28 +0100809
romanf578cd52023-10-19 09:47:40 +0200810 if ((r = pthread_rwlock_init(&server_opts.config_lock, attr_p))) {
Michal Vasko93224072021-11-09 12:14:28 +0100811 ERR(NULL, "%s: failed to init rwlock(%s).", __func__, strerror(r));
812 goto error;
813 }
814 if ((r = pthread_rwlock_init(&server_opts.ch_client_lock, attr_p))) {
815 ERR(NULL, "%s: failed to init rwlock(%s).", __func__, strerror(r));
816 goto error;
817 }
818
819 if (attr_p) {
820 pthread_rwlockattr_destroy(attr_p);
Frank Rimpler9f838b02018-07-25 06:44:03 +0000821 }
romanf578cd52023-10-19 09:47:40 +0200822
823#ifdef NC_ENABLED_SSH_TLS
824 if (curl_global_init(CURL_GLOBAL_SSL | CURL_GLOBAL_ACK_EINTR)) {
825 ERR(NULL, "%s: failed to init CURL.", __func__);
826 goto error;
827 }
828#endif
829
830 if ((r = pthread_mutex_init(&server_opts.bind_lock, NULL))) {
831 ERR(NULL, "%s: failed to init bind lock(%s).", __func__, strerror(r));
832 goto error;
833 }
834
Michal Vasko086311b2016-01-08 09:53:11 +0100835 return 0;
Michal Vasko93224072021-11-09 12:14:28 +0100836
837error:
838 if (attr_p) {
839 pthread_rwlockattr_destroy(attr_p);
840 }
841 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100842}
843
Michal Vaskob48aa812016-01-18 14:13:09 +0100844API void
845nc_server_destroy(void)
846{
romana2ff4ef2024-01-19 14:41:46 +0100847 uint32_t i, endpt_count;
Radek Krejci658782b2016-12-04 22:04:55 +0100848
849 for (i = 0; i < server_opts.capabilities_count; i++) {
Michal Vasko93224072021-11-09 12:14:28 +0100850 free(server_opts.capabilities[i]);
Radek Krejci658782b2016-12-04 22:04:55 +0100851 }
852 free(server_opts.capabilities);
Michal Vaskodd6e4f72018-06-01 10:21:27 +0200853 server_opts.capabilities = NULL;
854 server_opts.capabilities_count = 0;
Michal Vasko1440a742021-03-31 11:11:03 +0200855 if (server_opts.content_id_data && server_opts.content_id_data_free) {
856 server_opts.content_id_data_free(server_opts.content_id_data);
857 }
Michal Vaskodd6e4f72018-06-01 10:21:27 +0200858
romanf578cd52023-10-19 09:47:40 +0200859 nc_server_config_listen(NULL, NC_OP_DELETE);
860 nc_server_config_ch(NULL, NC_OP_DELETE);
861
romana2ff4ef2024-01-19 14:41:46 +0100862 endpt_count = server_opts.endpt_count;
863 for (i = 0; i < endpt_count; i++) {
864 if (server_opts.endpts[i].ti == NC_TI_UNIX) {
865 _nc_server_del_endpt_unix_socket(&server_opts.endpts[i], &server_opts.binds[i]);
866 }
867 }
868
romanf578cd52023-10-19 09:47:40 +0200869 pthread_mutex_destroy(&server_opts.bind_lock);
870
871#ifdef NC_ENABLED_SSH_TLS
romana9ec3362023-12-21 10:59:57 +0100872 free(server_opts.authkey_path_fmt);
873 server_opts.authkey_path_fmt = NULL;
roman808f3f62023-11-23 16:01:04 +0100874 free(server_opts.pam_config_name);
875 server_opts.pam_config_name = NULL;
Michal Vasko1c2d2652023-10-17 08:53:36 +0200876 if (server_opts.interactive_auth_data && server_opts.interactive_auth_data_free) {
877 server_opts.interactive_auth_data_free(server_opts.interactive_auth_data);
878 }
879 server_opts.interactive_auth_data = NULL;
880 server_opts.interactive_auth_data_free = NULL;
881
romanf578cd52023-10-19 09:47:40 +0200882 nc_server_config_ks_keystore(NULL, NC_OP_DELETE);
883 nc_server_config_ts_truststore(NULL, NC_OP_DELETE);
884 curl_global_cleanup();
885#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskob48aa812016-01-18 14:13:09 +0100886}
887
Michal Vasko086311b2016-01-08 09:53:11 +0100888API int
889nc_server_set_capab_withdefaults(NC_WD_MODE basic_mode, int also_supported)
890{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200891 if (!basic_mode || (basic_mode == NC_WD_ALL_TAG)) {
romanf578cd52023-10-19 09:47:40 +0200892 ERRARG(NULL, "basic_mode");
Michal Vasko45e53ae2016-04-07 11:46:03 +0200893 return -1;
894 } else if (also_supported && !(also_supported & (NC_WD_ALL | NC_WD_ALL_TAG | NC_WD_TRIM | NC_WD_EXPLICIT))) {
romanf578cd52023-10-19 09:47:40 +0200895 ERRARG(NULL, "also_supported");
Michal Vasko086311b2016-01-08 09:53:11 +0100896 return -1;
897 }
898
romanf578cd52023-10-19 09:47:40 +0200899 ATOMIC_STORE_RELAXED(server_opts.wd_basic_mode, basic_mode);
900 ATOMIC_STORE_RELAXED(server_opts.wd_also_supported, also_supported);
Michal Vasko086311b2016-01-08 09:53:11 +0100901 return 0;
902}
903
Michal Vasko1a38c862016-01-15 15:50:07 +0100904API void
Michal Vasko55f03972016-04-13 08:56:01 +0200905nc_server_get_capab_withdefaults(NC_WD_MODE *basic_mode, int *also_supported)
906{
907 if (!basic_mode && !also_supported) {
romanf578cd52023-10-19 09:47:40 +0200908 ERRARG(NULL, "basic_mode and also_supported");
Michal Vasko55f03972016-04-13 08:56:01 +0200909 return;
910 }
911
912 if (basic_mode) {
romanf578cd52023-10-19 09:47:40 +0200913 *basic_mode = ATOMIC_LOAD_RELAXED(server_opts.wd_basic_mode);
Michal Vasko55f03972016-04-13 08:56:01 +0200914 }
915 if (also_supported) {
romanf578cd52023-10-19 09:47:40 +0200916 *also_supported = ATOMIC_LOAD_RELAXED(server_opts.wd_also_supported);
Michal Vasko55f03972016-04-13 08:56:01 +0200917 }
918}
919
Michal Vasko55f03972016-04-13 08:56:01 +0200920API int
Radek Krejci658782b2016-12-04 22:04:55 +0100921nc_server_set_capability(const char *value)
Michal Vasko55f03972016-04-13 08:56:01 +0200922{
Michal Vasko93224072021-11-09 12:14:28 +0100923 void *mem;
Radek Krejci658782b2016-12-04 22:04:55 +0100924
925 if (!value || !value[0]) {
romanf578cd52023-10-19 09:47:40 +0200926 ERRARG(NULL, "value must not be empty");
Radek Krejci658782b2016-12-04 22:04:55 +0100927 return EXIT_FAILURE;
928 }
929
Michal Vasko93224072021-11-09 12:14:28 +0100930 mem = realloc(server_opts.capabilities, (server_opts.capabilities_count + 1) * sizeof *server_opts.capabilities);
roman3a95bb22023-10-26 11:07:17 +0200931 NC_CHECK_ERRMEM_RET(!mem, EXIT_FAILURE);
Michal Vasko93224072021-11-09 12:14:28 +0100932 server_opts.capabilities = mem;
933
934 server_opts.capabilities[server_opts.capabilities_count] = strdup(value);
935 server_opts.capabilities_count++;
Radek Krejci658782b2016-12-04 22:04:55 +0100936
937 return EXIT_SUCCESS;
Michal Vasko55f03972016-04-13 08:56:01 +0200938}
939
Michal Vasko1a38c862016-01-15 15:50:07 +0100940API void
Michal Vasko1440a742021-03-31 11:11:03 +0200941nc_server_set_content_id_clb(char *(*content_id_clb)(void *user_data), void *user_data,
942 void (*free_user_data)(void *user_data))
943{
944 server_opts.content_id_clb = content_id_clb;
945 server_opts.content_id_data = user_data;
946 server_opts.content_id_data_free = free_user_data;
947}
948
Michal Vasko71090fc2016-05-24 16:37:28 +0200949API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +0100950nc_accept_inout(int fdin, int fdout, const char *username, const struct ly_ctx *ctx, struct nc_session **session)
Michal Vasko086311b2016-01-08 09:53:11 +0100951{
Michal Vasko71090fc2016-05-24 16:37:28 +0200952 NC_MSG_TYPE msgtype;
Michal Vasko9fb42272017-10-05 13:50:05 +0200953 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +0200954
romand82caf12024-03-05 14:21:39 +0100955 NC_CHECK_ARG_RET(NULL, ctx, username, fdin >= 0, fdout >= 0, session, NC_MSG_ERROR);
romanf578cd52023-10-19 09:47:40 +0200956
romand82caf12024-03-05 14:21:39 +0100957 NC_CHECK_SRV_INIT_RET(NC_MSG_ERROR);
Michal Vasko086311b2016-01-08 09:53:11 +0100958
Michal Vasko93224072021-11-09 12:14:28 +0100959 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +0200960 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +0100961
Michal Vasko086311b2016-01-08 09:53:11 +0100962 /* prepare session structure */
Michal Vasko131120a2018-05-29 15:44:02 +0200963 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +0200964 NC_CHECK_ERRMEM_RET(!(*session), NC_MSG_ERROR);
Michal Vasko1a38c862016-01-15 15:50:07 +0100965 (*session)->status = NC_STATUS_STARTING;
Michal Vaskoade892d2017-02-22 13:40:35 +0100966
Michal Vasko086311b2016-01-08 09:53:11 +0100967 /* transport specific data */
Michal Vasko1a38c862016-01-15 15:50:07 +0100968 (*session)->ti_type = NC_TI_FD;
969 (*session)->ti.fd.in = fdin;
970 (*session)->ti.fd.out = fdout;
Michal Vasko086311b2016-01-08 09:53:11 +0100971
Michal Vasko93224072021-11-09 12:14:28 +0100972 /* assign context */
Michal Vasko1a38c862016-01-15 15:50:07 +0100973 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +0100974 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko086311b2016-01-08 09:53:11 +0100975
Michal Vaskob48aa812016-01-18 14:13:09 +0100976 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +0200977 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +0100978
Michal Vasko086311b2016-01-08 09:53:11 +0100979 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +0200980 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +0200981 if (msgtype != NC_MSG_HELLO) {
982 nc_session_free(*session, NULL);
983 *session = NULL;
984 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +0100985 }
Michal Vasko9fb42272017-10-05 13:50:05 +0200986
Michal Vaskod8a74192023-02-06 15:51:50 +0100987 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +0200988 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +0100989 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +0100990 (*session)->opts.server.session_start = ts_cur;
Michal Vasko9fb42272017-10-05 13:50:05 +0200991
Michal Vasko1a38c862016-01-15 15:50:07 +0100992 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko086311b2016-01-08 09:53:11 +0100993
Michal Vasko71090fc2016-05-24 16:37:28 +0200994 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +0100995}
Michal Vasko9e036d52016-01-08 10:49:26 +0100996
Michal Vaskob30b99c2016-07-26 11:35:43 +0200997static void
Michal Vasko74c345f2018-02-07 10:37:11 +0100998nc_ps_queue_add_id(struct nc_pollsession *ps, uint8_t *id)
999{
1000 uint8_t q_last;
1001
1002 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
1003 ERRINT;
1004 return;
1005 }
1006
1007 /* get a unique queue value (by adding 1 to the last added value, if any) */
1008 if (ps->queue_len) {
1009 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
1010 *id = ps->queue[q_last] + 1;
1011 } else {
1012 *id = 0;
1013 }
1014
1015 /* add the id into the queue */
1016 ++ps->queue_len;
1017 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
1018 ps->queue[q_last] = *id;
1019}
1020
1021static void
Michal Vaskob30b99c2016-07-26 11:35:43 +02001022nc_ps_queue_remove_id(struct nc_pollsession *ps, uint8_t id)
1023{
Michal Vasko74c345f2018-02-07 10:37:11 +01001024 uint8_t i, q_idx, found = 0;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001025
1026 for (i = 0; i < ps->queue_len; ++i) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001027 /* get the actual queue idx */
1028 q_idx = (ps->queue_begin + i) % NC_PS_QUEUE_SIZE;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001029
1030 if (found) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001031 if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001032 /* another equal value, simply cannot be */
1033 ERRINT;
1034 }
Michal Vaskod8340032018-02-12 14:41:00 +01001035 if (found == 2) {
1036 /* move the following values */
1037 ps->queue[q_idx ? q_idx - 1 : NC_PS_QUEUE_SIZE - 1] = ps->queue[q_idx];
1038 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001039 } else if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001040 /* found our id, there can be no more equal valid values */
Michal Vaskod8340032018-02-12 14:41:00 +01001041 if (i == 0) {
1042 found = 1;
1043 } else {
1044 /* this is not okay, our id is in the middle of the queue */
1045 found = 2;
1046 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001047 }
1048 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001049 if (!found) {
1050 ERRINT;
Michal Vasko103fe632018-02-12 16:37:45 +01001051 return;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001052 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001053
Michal Vasko103fe632018-02-12 16:37:45 +01001054 --ps->queue_len;
Michal Vaskod8340032018-02-12 14:41:00 +01001055 if (found == 1) {
Michal Vasko103fe632018-02-12 16:37:45 +01001056 /* remove the id by moving the queue, otherwise all the values in the queue were moved */
Michal Vaskod8340032018-02-12 14:41:00 +01001057 ps->queue_begin = (ps->queue_begin + 1) % NC_PS_QUEUE_SIZE;
1058 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001059}
1060
Michal Vaskof04a52a2016-04-07 10:52:10 +02001061int
Michal Vasko26043172016-07-26 14:08:59 +02001062nc_ps_lock(struct nc_pollsession *ps, uint8_t *id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001063{
1064 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001065 struct timespec ts;
1066
Michal Vaskobe86fe32016-04-07 10:43:03 +02001067 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001068 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001069 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001070 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001071 return -1;
1072 }
1073
Michal Vasko74c345f2018-02-07 10:37:11 +01001074 /* check that the queue is long enough */
Michal Vasko8bc747c2018-02-09 16:37:04 +01001075 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
Michal Vasko05532772021-06-03 12:12:38 +02001076 ERR(NULL, "%s: pollsession queue size (%d) too small.", func, NC_PS_QUEUE_SIZE);
Michal Vasko8bc747c2018-02-09 16:37:04 +01001077 pthread_mutex_unlock(&ps->lock);
1078 return -1;
1079 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001080
1081 /* add ourselves into the queue */
1082 nc_ps_queue_add_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001083 DBL(NULL, "PS 0x%p TID %lu queue: added %u, head %u, length %u", ps, (long unsigned int)pthread_self(), *id,
Michal Vasko91290952019-09-27 11:30:55 +02001084 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001085
1086 /* is it our turn? */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001087 while (ps->queue[ps->queue_begin] != *id) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001088 nc_timeouttime_get(&ts, NC_PS_QUEUE_TIMEOUT);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001089
Michal Vaskod8a74192023-02-06 15:51:50 +01001090 ret = pthread_cond_clockwait(&ps->cond, &ps->lock, COMPAT_CLOCK_ID, &ts);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001091 if (ret) {
preetbhansalif3edf492018-12-14 17:53:32 +05301092 /**
1093 * This may happen when another thread releases the lock and broadcasts the condition
1094 * and this thread had already timed out. When this thread is scheduled, it returns timed out error
1095 * but when actually this thread was ready for condition.
1096 */
preetbhansali629dfc42018-12-17 16:04:40 +05301097 if ((ETIMEDOUT == ret) && (ps->queue[ps->queue_begin] == *id)) {
preetbhansalif3edf492018-12-14 17:53:32 +05301098 break;
1099 }
Michal Vasko66032bc2019-01-22 15:03:12 +01001100
Michal Vasko05532772021-06-03 12:12:38 +02001101 ERR(NULL, "%s: failed to wait for a pollsession condition (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001102 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001103 nc_ps_queue_remove_id(ps, *id);
1104 pthread_mutex_unlock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001105 return -1;
1106 }
1107 }
1108
Michal Vaskobe86fe32016-04-07 10:43:03 +02001109 /* UNLOCK */
1110 pthread_mutex_unlock(&ps->lock);
1111
1112 return 0;
1113}
1114
Michal Vaskof04a52a2016-04-07 10:52:10 +02001115int
Michal Vasko26043172016-07-26 14:08:59 +02001116nc_ps_unlock(struct nc_pollsession *ps, uint8_t id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001117{
1118 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001119
1120 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001121 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001122 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001123 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001124 ret = -1;
1125 }
1126
Michal Vaskob30b99c2016-07-26 11:35:43 +02001127 /* we must be the first, it was our turn after all, right? */
1128 if (ps->queue[ps->queue_begin] != id) {
1129 ERRINT;
Michal Vaskob1a094b2016-10-05 14:04:52 +02001130 /* UNLOCK */
1131 if (!ret) {
1132 pthread_mutex_unlock(&ps->lock);
1133 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001134 return -1;
1135 }
1136
Michal Vaskobe86fe32016-04-07 10:43:03 +02001137 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001138 nc_ps_queue_remove_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001139 DBL(NULL, "PS 0x%p TID %lu queue: removed %u, head %u, length %u", ps, (long unsigned int)pthread_self(), id,
Michal Vasko91290952019-09-27 11:30:55 +02001140 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001141
1142 /* broadcast to all other threads that the queue moved */
1143 pthread_cond_broadcast(&ps->cond);
1144
Michal Vaskobe86fe32016-04-07 10:43:03 +02001145 /* UNLOCK */
1146 if (!ret) {
1147 pthread_mutex_unlock(&ps->lock);
1148 }
1149
1150 return ret;
1151}
1152
Michal Vasko428087d2016-01-14 16:04:28 +01001153API struct nc_pollsession *
1154nc_ps_new(void)
1155{
Michal Vasko48a63ed2016-03-01 09:48:21 +01001156 struct nc_pollsession *ps;
1157
1158 ps = calloc(1, sizeof(struct nc_pollsession));
roman3a95bb22023-10-26 11:07:17 +02001159 NC_CHECK_ERRMEM_RET(!ps, NULL);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001160 pthread_cond_init(&ps->cond, NULL);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001161 pthread_mutex_init(&ps->lock, NULL);
1162
1163 return ps;
Michal Vasko428087d2016-01-14 16:04:28 +01001164}
1165
1166API void
1167nc_ps_free(struct nc_pollsession *ps)
1168{
fanchanghu3d4e7212017-08-09 09:42:30 +08001169 uint16_t i;
1170
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001171 if (!ps) {
1172 return;
1173 }
1174
Michal Vaskobe86fe32016-04-07 10:43:03 +02001175 if (ps->queue_len) {
Michal Vasko05532772021-06-03 12:12:38 +02001176 ERR(NULL, "FATAL: Freeing a pollsession structure that is currently being worked with!");
Michal Vaskobe86fe32016-04-07 10:43:03 +02001177 }
1178
fanchanghu3d4e7212017-08-09 09:42:30 +08001179 for (i = 0; i < ps->session_count; i++) {
1180 free(ps->sessions[i]);
1181 }
1182
Michal Vasko428087d2016-01-14 16:04:28 +01001183 free(ps->sessions);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001184 pthread_mutex_destroy(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001185 pthread_cond_destroy(&ps->cond);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001186
Michal Vasko428087d2016-01-14 16:04:28 +01001187 free(ps);
1188}
1189
1190API int
1191nc_ps_add_session(struct nc_pollsession *ps, struct nc_session *session)
1192{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001193 uint8_t q_id;
1194
romanf578cd52023-10-19 09:47:40 +02001195 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko428087d2016-01-14 16:04:28 +01001196
Michal Vasko48a63ed2016-03-01 09:48:21 +01001197 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001198 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001199 return -1;
1200 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001201
Michal Vasko428087d2016-01-14 16:04:28 +01001202 ++ps->session_count;
Michal Vasko4eb3c312016-03-01 14:09:37 +01001203 ps->sessions = nc_realloc(ps->sessions, ps->session_count * sizeof *ps->sessions);
Michal Vasko9a327362017-01-11 11:31:46 +01001204 if (!ps->sessions) {
Michal Vasko4eb3c312016-03-01 14:09:37 +01001205 ERRMEM;
1206 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001207 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko4eb3c312016-03-01 14:09:37 +01001208 return -1;
1209 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001210 ps->sessions[ps->session_count - 1] = calloc(1, sizeof **ps->sessions);
1211 if (!ps->sessions[ps->session_count - 1]) {
1212 ERRMEM;
1213 --ps->session_count;
1214 /* UNLOCK */
1215 nc_ps_unlock(ps, q_id, __func__);
1216 return -1;
1217 }
1218 ps->sessions[ps->session_count - 1]->session = session;
1219 ps->sessions[ps->session_count - 1]->state = NC_PS_STATE_NONE;
Michal Vasko428087d2016-01-14 16:04:28 +01001220
Michal Vasko48a63ed2016-03-01 09:48:21 +01001221 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001222 return nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001223}
1224
Michal Vasko48a63ed2016-03-01 09:48:21 +01001225static int
Radek Krejcid5f978f2016-03-03 13:14:45 +01001226_nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session, int index)
Michal Vasko428087d2016-01-14 16:04:28 +01001227{
1228 uint16_t i;
1229
Radek Krejcid5f978f2016-03-03 13:14:45 +01001230 if (index >= 0) {
1231 i = (uint16_t)index;
1232 goto remove;
1233 }
Michal Vasko428087d2016-01-14 16:04:28 +01001234 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001235 if (ps->sessions[i]->session == session) {
Radek Krejcid5f978f2016-03-03 13:14:45 +01001236remove:
Michal Vasko428087d2016-01-14 16:04:28 +01001237 --ps->session_count;
fanchanghu3d4e7212017-08-09 09:42:30 +08001238 if (i <= ps->session_count) {
1239 free(ps->sessions[i]);
Michal Vasko58005732016-02-02 15:50:52 +01001240 ps->sessions[i] = ps->sessions[ps->session_count];
fanchanghu3d4e7212017-08-09 09:42:30 +08001241 }
1242 if (!ps->session_count) {
Michal Vasko58005732016-02-02 15:50:52 +01001243 free(ps->sessions);
1244 ps->sessions = NULL;
Michal Vasko58005732016-02-02 15:50:52 +01001245 }
Michal Vasko11d2f6a2017-02-02 11:15:06 +01001246 ps->last_event_session = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001247 return 0;
1248 }
1249 }
1250
Michal Vaskof0537d82016-01-29 14:42:38 +01001251 return -1;
Michal Vasko428087d2016-01-14 16:04:28 +01001252}
1253
Michal Vasko48a63ed2016-03-01 09:48:21 +01001254API int
1255nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session)
1256{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001257 uint8_t q_id;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001258 int ret, ret2;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001259
romanf578cd52023-10-19 09:47:40 +02001260 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001261
1262 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001263 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001264 return -1;
1265 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001266
Radek Krejcid5f978f2016-03-03 13:14:45 +01001267 ret = _nc_ps_del_session(ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001268
1269 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001270 ret2 = nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001271
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001272 return ret || ret2 ? -1 : 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001273}
1274
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001275API struct nc_session *
Michal Vasko4871c9d2017-10-09 14:48:39 +02001276nc_ps_get_session(const struct nc_pollsession *ps, uint16_t idx)
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001277{
1278 uint8_t q_id;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001279 struct nc_session *ret = NULL;
1280
romanf578cd52023-10-19 09:47:40 +02001281 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001282
1283 /* LOCK */
1284 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1285 return NULL;
1286 }
1287
Michal Vasko4871c9d2017-10-09 14:48:39 +02001288 if (idx < ps->session_count) {
1289 ret = ps->sessions[idx]->session;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001290 }
1291
1292 /* UNLOCK */
1293 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1294
1295 return ret;
1296}
1297
Michal Vasko3ec3b112022-07-21 12:32:33 +02001298API struct nc_session *
1299nc_ps_find_session(const struct nc_pollsession *ps, nc_ps_session_match_cb match_cb, void *cb_data)
1300{
1301 uint8_t q_id;
1302 uint16_t i;
1303 struct nc_session *ret = NULL;
1304
romanf578cd52023-10-19 09:47:40 +02001305 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vasko3ec3b112022-07-21 12:32:33 +02001306
1307 /* LOCK */
1308 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1309 return NULL;
1310 }
1311
1312 for (i = 0; i < ps->session_count; ++i) {
1313 if (match_cb(ps->sessions[i]->session, cb_data)) {
1314 ret = ps->sessions[i]->session;
1315 break;
1316 }
1317 }
1318
1319 /* UNLOCK */
1320 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1321
1322 return ret;
1323}
1324
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001325API uint16_t
1326nc_ps_session_count(struct nc_pollsession *ps)
1327{
Michal Vasko47003942019-03-14 12:25:23 +01001328 uint8_t q_id;
1329 uint16_t session_count;
1330
romanf578cd52023-10-19 09:47:40 +02001331 NC_CHECK_ARG_RET(NULL, ps, 0);
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001332
Michal Vasko47003942019-03-14 12:25:23 +01001333 /* LOCK (just for memory barrier so that we read the current value) */
1334 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1335 return 0;
1336 }
1337
1338 session_count = ps->session_count;
1339
1340 /* UNLOCK */
1341 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1342
1343 return session_count;
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001344}
1345
Michal Vasko77e83572022-07-21 15:31:15 +02001346static NC_MSG_TYPE
1347recv_rpc_check_msgid(struct nc_session *session, const struct lyd_node *envp)
1348{
1349 struct lyd_attr *attr;
1350
1351 assert(envp && !envp->schema);
1352
1353 /* find the message-id attribute */
1354 LY_LIST_FOR(((struct lyd_node_opaq *)envp)->attr, attr) {
1355 if (!strcmp(attr->name.name, "message-id")) {
1356 break;
1357 }
1358 }
1359
1360 if (!attr) {
1361 ERR(session, "Received an <rpc> without a message-id.");
1362 return NC_MSG_REPLY_ERR_MSGID;
1363 }
1364
1365 return NC_MSG_RPC;
1366}
1367
Michal Vasko131120a2018-05-29 15:44:02 +02001368/* should be called holding the session RPC lock! IO lock will be acquired as needed
Michal Vasko71090fc2016-05-24 16:37:28 +02001369 * returns: NC_PSPOLL_ERROR,
Michal Vasko77367452021-02-16 16:32:18 +01001370 * NC_PSPOLL_TIMEOUT,
Michal Vaskof8fba542023-10-23 12:03:50 +02001371 * NC_PSPOLL_BAD_RPC (| NC_PSPOLL_REPLY_ERROR),
Michal Vasko71090fc2016-05-24 16:37:28 +02001372 * NC_PSPOLL_RPC
1373 */
1374static int
Michal Vasko131120a2018-05-29 15:44:02 +02001375nc_server_recv_rpc_io(struct nc_session *session, int io_timeout, struct nc_server_rpc **rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001376{
Michal Vasko77367452021-02-16 16:32:18 +01001377 struct ly_in *msg;
Radek Krejcif93c7d42016-04-06 13:41:15 +02001378 struct nc_server_reply *reply = NULL;
Michal Vasko939ffce2021-04-12 13:02:01 +02001379 struct lyd_node *e;
Michal Vaskof8fba542023-10-23 12:03:50 +02001380 int r, ret = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001381
romanf578cd52023-10-19 09:47:40 +02001382 NC_CHECK_ARG_RET(session, session, rpc, NC_PSPOLL_ERROR);
1383
1384 if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_SERVER)) {
Michal Vasko05532772021-06-03 12:12:38 +02001385 ERR(session, "Invalid session to receive RPCs.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001386 return NC_PSPOLL_ERROR;
Michal Vasko428087d2016-01-14 16:04:28 +01001387 }
1388
Michal Vasko93224072021-11-09 12:14:28 +01001389 *rpc = NULL;
1390
Michal Vasko77367452021-02-16 16:32:18 +01001391 /* get a message */
1392 r = nc_read_msg_io(session, io_timeout, &msg, 0);
1393 if (r == -2) {
1394 /* malformed message */
Michal Vasko93224072021-11-09 12:14:28 +01001395 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
Michal Vasko77e83572022-07-21 15:31:15 +02001396 goto cleanup;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001397 }
1398 if (r == -1) {
Michal Vasko77367452021-02-16 16:32:18 +01001399 return NC_PSPOLL_ERROR;
1400 } else if (!r) {
1401 return NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01001402 }
1403
Michal Vasko77367452021-02-16 16:32:18 +01001404 *rpc = calloc(1, sizeof **rpc);
roman3a95bb22023-10-26 11:07:17 +02001405 NC_CHECK_ERRMEM_GOTO(!*rpc, ret = NC_PSPOLL_ERROR, cleanup);
Michal Vasko77367452021-02-16 16:32:18 +01001406
1407 /* parse the RPC */
Michal Vasko77e83572022-07-21 15:31:15 +02001408 if (!lyd_parse_op(session->ctx, NULL, msg, LYD_XML, LYD_TYPE_RPC_NETCONF, &(*rpc)->envp, &(*rpc)->rpc)) {
1409 /* check message-id */
1410 if (recv_rpc_check_msgid(session, (*rpc)->envp) == NC_MSG_RPC) {
1411 /* valid RPC */
1412 ret = NC_PSPOLL_RPC;
1413 } else {
1414 /* no message-id */
Michal Vasko77e83572022-07-21 15:31:15 +02001415 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MISSING_ATTR, NC_ERR_TYPE_RPC, "message-id", "rpc"));
1416 }
1417 } else {
Michal Vasko77367452021-02-16 16:32:18 +01001418 /* bad RPC received */
Michal Vasko77367452021-02-16 16:32:18 +01001419 if ((*rpc)->envp) {
1420 /* at least the envelopes were parsed */
Michal Vasko93224072021-11-09 12:14:28 +01001421 e = nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
Michal Vasko58791da2024-02-26 13:52:59 +01001422 nc_err_set_msg(e, ly_err_last(session->ctx)->msg, "en");
Michal Vasko939ffce2021-04-12 13:02:01 +02001423 reply = nc_server_reply_err(e);
Michal Vasko77367452021-02-16 16:32:18 +01001424 } else if (session->version == NC_VERSION_11) {
Michal Vasko93224072021-11-09 12:14:28 +01001425 /* completely malformed message, NETCONF version 1.1 defines sending error reply from
1426 * the server (RFC 6241 sec. 3) */
1427 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
Michal Vaskof8fba542023-10-23 12:03:50 +02001428 } else {
1429 /* at least set the return value */
1430 ret = NC_PSPOLL_BAD_RPC;
Michal Vasko77367452021-02-16 16:32:18 +01001431 }
Michal Vasko77367452021-02-16 16:32:18 +01001432 }
1433
1434cleanup:
Michal Vasko77e83572022-07-21 15:31:15 +02001435 if (reply) {
1436 /* send error reply */
1437 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, *rpc ? (*rpc)->envp : NULL, reply);
1438 nc_server_reply_free(reply);
1439 if (r != NC_MSG_REPLY) {
1440 ERR(session, "Failed to write reply (%s), terminating session.", nc_msgtype2str[r]);
1441 if (session->status != NC_STATUS_INVALID) {
1442 session->status = NC_STATUS_INVALID;
1443 session->term_reason = NC_SESSION_TERM_OTHER;
1444 }
1445 }
Michal Vaskof8fba542023-10-23 12:03:50 +02001446
1447 /* bad RPC and an error reply sent */
1448 ret = NC_PSPOLL_BAD_RPC | NC_PSPOLL_REPLY_ERROR;
Michal Vasko77e83572022-07-21 15:31:15 +02001449 }
1450
Michal Vasko77367452021-02-16 16:32:18 +01001451 ly_in_free(msg, 1);
1452 if (ret != NC_PSPOLL_RPC) {
1453 nc_server_rpc_free(*rpc);
1454 *rpc = NULL;
1455 }
Michal Vasko71090fc2016-05-24 16:37:28 +02001456 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001457}
1458
fanchanghu966f2de2016-07-21 02:28:57 -04001459API void
1460nc_set_global_rpc_clb(nc_rpc_clb clb)
1461{
1462 global_rpc_clb = clb;
1463}
1464
Radek Krejci93e80222016-10-03 13:34:25 +02001465API NC_MSG_TYPE
1466nc_server_notif_send(struct nc_session *session, struct nc_server_notif *notif, int timeout)
1467{
Michal Vasko131120a2018-05-29 15:44:02 +02001468 NC_MSG_TYPE ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001469
1470 /* check parameters */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001471 if (!session || (session->side != NC_SERVER) || !nc_session_get_notif_status(session)) {
romanf578cd52023-10-19 09:47:40 +02001472 ERRARG(NULL, "session");
Radek Krejci93e80222016-10-03 13:34:25 +02001473 return NC_MSG_ERROR;
Michal Vasko77367452021-02-16 16:32:18 +01001474 } else if (!notif || !notif->ntf || !notif->eventtime) {
romanf578cd52023-10-19 09:47:40 +02001475 ERRARG(NULL, "notif");
Radek Krejci93e80222016-10-03 13:34:25 +02001476 return NC_MSG_ERROR;
1477 }
1478
Michal Vasko131120a2018-05-29 15:44:02 +02001479 /* we do not need RPC lock for this, IO lock will be acquired properly */
1480 ret = nc_write_msg_io(session, timeout, NC_MSG_NOTIF, notif);
Michal Vasko8fe604c2020-02-10 15:25:04 +01001481 if (ret != NC_MSG_NOTIF) {
Michal Vasko05532772021-06-03 12:12:38 +02001482 ERR(session, "Failed to write notification (%s).", nc_msgtype2str[ret]);
Radek Krejci93e80222016-10-03 13:34:25 +02001483 }
Michal Vaskoade892d2017-02-22 13:40:35 +01001484
Michal Vasko131120a2018-05-29 15:44:02 +02001485 return ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001486}
1487
Michal Vaskof9467762023-03-28 09:02:08 +02001488/**
1489 * @brief Send a reply acquiring IO lock as needed.
1490 * Session RPC lock must be held!
1491 *
1492 * @param[in] session Session to use.
1493 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1494 * @param[in] rpc RPC to sent.
1495 * @return 0 on success.
1496 * @return Bitmask of NC_PSPOLL_ERROR (any fatal error) and NC_PSPOLL_REPLY_ERROR (reply failed to be sent).
1497 * @return NC_PSPOLL_ERROR on other errors.
Michal Vasko71090fc2016-05-24 16:37:28 +02001498 */
1499static int
Michal Vasko93224072021-11-09 12:14:28 +01001500nc_server_send_reply_io(struct nc_session *session, int io_timeout, const struct nc_server_rpc *rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001501{
1502 nc_rpc_clb clb;
1503 struct nc_server_reply *reply;
Michal Vasko77367452021-02-16 16:32:18 +01001504 const struct lysc_node *rpc_act = NULL;
1505 struct lyd_node *elem;
Michal Vasko131120a2018-05-29 15:44:02 +02001506 int ret = 0;
1507 NC_MSG_TYPE r;
Michal Vasko428087d2016-01-14 16:04:28 +01001508
Michal Vasko4a827e52016-03-03 10:59:00 +01001509 if (!rpc) {
1510 ERRINT;
Michal Vasko71090fc2016-05-24 16:37:28 +02001511 return NC_PSPOLL_ERROR;
Michal Vasko4a827e52016-03-03 10:59:00 +01001512 }
1513
Michal Vasko77367452021-02-16 16:32:18 +01001514 if (rpc->rpc->schema->nodetype == LYS_RPC) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001515 /* RPC */
Michal Vasko77367452021-02-16 16:32:18 +01001516 rpc_act = rpc->rpc->schema;
fanchanghu966f2de2016-07-21 02:28:57 -04001517 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001518 /* action */
Michal Vasko77367452021-02-16 16:32:18 +01001519 LYD_TREE_DFS_BEGIN(rpc->rpc, elem) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001520 if (elem->schema->nodetype == LYS_ACTION) {
1521 rpc_act = elem->schema;
1522 break;
1523 }
Michal Vasko77367452021-02-16 16:32:18 +01001524 LYD_TREE_DFS_END(rpc->rpc, elem);
fanchanghu966f2de2016-07-21 02:28:57 -04001525 }
Michal Vasko90e8e692016-07-13 12:27:57 +02001526 if (!rpc_act) {
1527 ERRINT;
1528 return NC_PSPOLL_ERROR;
1529 }
1530 }
1531
1532 if (!rpc_act->priv) {
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001533 if (!global_rpc_clb) {
1534 /* no callback, reply with a not-implemented error */
Michal Vasko93224072021-11-09 12:14:28 +01001535 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_OP_NOT_SUPPORTED, NC_ERR_TYPE_PROT));
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001536 } else {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001537 reply = global_rpc_clb(rpc->rpc, session);
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001538 }
Michal Vasko428087d2016-01-14 16:04:28 +01001539 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001540 clb = (nc_rpc_clb)rpc_act->priv;
Michal Vasko77367452021-02-16 16:32:18 +01001541 reply = clb(rpc->rpc, session);
Michal Vasko428087d2016-01-14 16:04:28 +01001542 }
1543
1544 if (!reply) {
Michal Vasko93224072021-11-09 12:14:28 +01001545 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP));
Michal Vasko428087d2016-01-14 16:04:28 +01001546 }
Michal Vasko77367452021-02-16 16:32:18 +01001547 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, rpc->envp, reply);
Michal Vasko71090fc2016-05-24 16:37:28 +02001548 if (reply->type == NC_RPL_ERROR) {
1549 ret |= NC_PSPOLL_REPLY_ERROR;
1550 }
1551 nc_server_reply_free(reply);
Michal Vasko428087d2016-01-14 16:04:28 +01001552
Michal Vasko131120a2018-05-29 15:44:02 +02001553 if (r != NC_MSG_REPLY) {
Michal Vasko15469492021-06-09 08:40:48 +02001554 ERR(session, "Failed to write reply (%s).", nc_msgtype2str[r]);
Michal Vasko71090fc2016-05-24 16:37:28 +02001555 ret |= NC_PSPOLL_ERROR;
1556 }
Michal Vasko428087d2016-01-14 16:04:28 +01001557
1558 /* special case if term_reason was set in callback, last reply was sent (needed for <close-session> if nothing else) */
1559 if ((session->status == NC_STATUS_RUNNING) && (session->term_reason != NC_SESSION_TERM_NONE)) {
1560 session->status = NC_STATUS_INVALID;
1561 }
1562
Michal Vasko71090fc2016-05-24 16:37:28 +02001563 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001564}
1565
Michal Vaskof9467762023-03-28 09:02:08 +02001566/**
1567 * @brief Poll a session from pspoll acquiring IO lock as needed.
1568 * Session must be running and session RPC lock held!
1569 *
1570 * @param[in] session Session to use.
1571 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1572 * @param[in] now_mono Current monotonic timestamp.
1573 * @param[in,out] msg Message to fill in case of an error.
1574 * @return NC_PSPOLL_RPC if some application data are available.
1575 * @return NC_PSPOLL_TIMEOUT if a timeout elapsed.
1576 * @return NC_PSPOLL_SSH_CHANNEL if a new SSH channel has been created.
1577 * @return NC_PSPOLL_SSH_MSG if just an SSH message has been processed.
1578 * @return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR if session has been terminated (@p msg filled).
1579 * @return NC_PSPOLL_ERROR on other fatal errors (@p msg filled).
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001580 */
1581static int
Michal Vasko131120a2018-05-29 15:44:02 +02001582nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mono, char *msg)
Michal Vasko428087d2016-01-14 16:04:28 +01001583{
Michal Vasko9a327362017-01-11 11:31:46 +01001584 struct pollfd pfd;
Michal Vasko2260f552018-06-06 10:06:12 +02001585 int r, ret = 0;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001586
romanf578cd52023-10-19 09:47:40 +02001587#ifdef NC_ENABLED_SSH_TLS
roman456f92d2023-04-28 10:28:12 +02001588 ssh_message ssh_msg;
Michal Vasko9a327362017-01-11 11:31:46 +01001589 struct nc_session *new;
romanf578cd52023-10-19 09:47:40 +02001590#endif /* NC_ENABLED_SSH_TLS */
Michal Vasko428087d2016-01-14 16:04:28 +01001591
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001592 /* check timeout first */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001593 if (!(session->flags & NC_SESSION_CALLHOME) && !nc_session_get_notif_status(session) && server_opts.idle_timeout &&
romanf578cd52023-10-19 09:47:40 +02001594 (now_mono >= session->opts.server.last_rpc + (unsigned) server_opts.idle_timeout)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001595 sprintf(msg, "Session idle timeout elapsed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001596 session->status = NC_STATUS_INVALID;
1597 session->term_reason = NC_SESSION_TERM_TIMEOUT;
1598 return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1599 }
1600
Michal Vasko131120a2018-05-29 15:44:02 +02001601 r = nc_session_io_lock(session, io_timeout, __func__);
1602 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001603 sprintf(msg, "Session IO lock failed to be acquired");
Michal Vasko131120a2018-05-29 15:44:02 +02001604 return NC_PSPOLL_ERROR;
1605 } else if (!r) {
1606 return NC_PSPOLL_TIMEOUT;
1607 }
1608
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001609 switch (session->ti_type) {
romanf578cd52023-10-19 09:47:40 +02001610#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001611 case NC_TI_LIBSSH:
romanf578cd52023-10-19 09:47:40 +02001612 ssh_msg = ssh_message_get(session->ti.libssh.session);
1613 if (ssh_msg) {
1614 nc_session_ssh_msg(session, NULL, ssh_msg, NULL);
1615 if (session->ti.libssh.next) {
1616 for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) {
1617 if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel &&
1618 (new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1619 /* new NETCONF SSH channel */
1620 ret = NC_PSPOLL_SSH_CHANNEL;
1621 break;
1622 }
1623 }
1624 if (new != session) {
1625 ssh_message_free(ssh_msg);
1626 break;
1627 }
1628 }
1629 if (!ret) {
1630 /* just some SSH message */
1631 ret = NC_PSPOLL_SSH_MSG;
1632 }
1633 ssh_message_free(ssh_msg);
1634
1635 /* break because 1) we don't want to return anything here ORred with NC_PSPOLL_RPC
1636 * and 2) we don't want to delay openning a new channel by waiting for a RPC to get processed
1637 */
1638 break;
1639 }
1640
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001641 r = ssh_channel_poll_timeout(session->ti.libssh.channel, 0, 0);
Michal Vasko8dcaa882017-10-19 14:28:42 +02001642 if (r == SSH_EOF) {
1643 sprintf(msg, "SSH channel unexpected EOF");
1644 session->status = NC_STATUS_INVALID;
1645 session->term_reason = NC_SESSION_TERM_DROPPED;
1646 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1647 } else if (r == SSH_ERROR) {
1648 sprintf(msg, "SSH channel poll error (%s)", ssh_get_error(session->ti.libssh.session));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001649 session->status = NC_STATUS_INVALID;
1650 session->term_reason = NC_SESSION_TERM_OTHER;
1651 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
Michal Vasko8dcaa882017-10-19 14:28:42 +02001652 } else if (!r) {
romanf578cd52023-10-19 09:47:40 +02001653 /* no application data received */
1654 ret = NC_PSPOLL_TIMEOUT;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001655 } else {
1656 /* we have some application data */
1657 ret = NC_PSPOLL_RPC;
1658 }
1659 break;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001660 case NC_TI_OPENSSL:
1661 r = SSL_pending(session->ti.tls);
1662 if (!r) {
1663 /* no data pending in the SSL buffer, poll fd */
1664 pfd.fd = SSL_get_rfd(session->ti.tls);
1665 if (pfd.fd < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001666 sprintf(msg, "Internal error (%s:%d)", __FILE__, __LINE__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001667 ret = NC_PSPOLL_ERROR;
1668 break;
1669 }
1670 pfd.events = POLLIN;
1671 pfd.revents = 0;
Michal Vasko63b92d62024-04-29 10:04:56 +02001672 r = nc_poll(&pfd, 1, 0);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001673
Michal Vasko63b92d62024-04-29 10:04:56 +02001674 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001675 sprintf(msg, "Poll failed (%s)", strerror(errno));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001676 session->status = NC_STATUS_INVALID;
1677 ret = NC_PSPOLL_ERROR;
1678 } else if (r > 0) {
1679 if (pfd.revents & (POLLHUP | POLLNVAL)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001680 sprintf(msg, "Communication socket unexpectedly closed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001681 session->status = NC_STATUS_INVALID;
1682 session->term_reason = NC_SESSION_TERM_DROPPED;
1683 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1684 } else if (pfd.revents & POLLERR) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001685 sprintf(msg, "Communication socket error");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001686 session->status = NC_STATUS_INVALID;
1687 session->term_reason = NC_SESSION_TERM_OTHER;
1688 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1689 } else {
1690 ret = NC_PSPOLL_RPC;
1691 }
1692 } else {
1693 ret = NC_PSPOLL_TIMEOUT;
1694 }
1695 } else {
1696 ret = NC_PSPOLL_RPC;
1697 }
1698 break;
romanf578cd52023-10-19 09:47:40 +02001699#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001700 case NC_TI_FD:
Olivier Matzac7fa2f2018-10-11 10:02:04 +02001701 case NC_TI_UNIX:
1702 pfd.fd = (session->ti_type == NC_TI_FD) ? session->ti.fd.in : session->ti.unixsock.sock;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001703 pfd.events = POLLIN;
1704 pfd.revents = 0;
Michal Vasko63b92d62024-04-29 10:04:56 +02001705 r = nc_poll(&pfd, 1, 0);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001706
Michal Vasko63b92d62024-04-29 10:04:56 +02001707 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001708 sprintf(msg, "Poll failed (%s)", strerror(errno));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001709 session->status = NC_STATUS_INVALID;
1710 ret = NC_PSPOLL_ERROR;
1711 } else if (r > 0) {
1712 if (pfd.revents & (POLLHUP | POLLNVAL)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001713 sprintf(msg, "Communication socket unexpectedly closed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001714 session->status = NC_STATUS_INVALID;
1715 session->term_reason = NC_SESSION_TERM_DROPPED;
1716 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1717 } else if (pfd.revents & POLLERR) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001718 sprintf(msg, "Communication socket error");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001719 session->status = NC_STATUS_INVALID;
1720 session->term_reason = NC_SESSION_TERM_OTHER;
1721 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1722 } else {
1723 ret = NC_PSPOLL_RPC;
1724 }
1725 } else {
1726 ret = NC_PSPOLL_TIMEOUT;
1727 }
1728 break;
1729 case NC_TI_NONE:
Michal Vasko4607daf2024-01-15 15:05:15 +01001730 sprintf(msg, "Internal error (%s:%d)", __FILE__, __LINE__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001731 ret = NC_PSPOLL_ERROR;
1732 break;
1733 }
1734
Michal Vasko131120a2018-05-29 15:44:02 +02001735 nc_session_io_unlock(session, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001736 return ret;
1737}
1738
1739API int
1740nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session)
1741{
Michal Vasko443faa02022-10-20 09:09:03 +02001742 int ret = NC_PSPOLL_ERROR, r;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001743 uint8_t q_id;
1744 uint16_t i, j;
1745 char msg[256];
1746 struct timespec ts_timeout, ts_cur;
1747 struct nc_session *cur_session;
fanchanghu3d4e7212017-08-09 09:42:30 +08001748 struct nc_ps_session *cur_ps_session;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001749 struct nc_server_rpc *rpc = NULL;
1750
romanf578cd52023-10-19 09:47:40 +02001751 NC_CHECK_ARG_RET(NULL, ps, NC_PSPOLL_ERROR);
Michal Vasko428087d2016-01-14 16:04:28 +01001752
Michal Vaskoade892d2017-02-22 13:40:35 +01001753 /* PS LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001754 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001755 return NC_PSPOLL_ERROR;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001756 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001757
Michal Vaskoade892d2017-02-22 13:40:35 +01001758 if (!ps->session_count) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001759 nc_ps_unlock(ps, q_id, __func__);
1760 return NC_PSPOLL_NOSESSIONS;
Michal Vaskoade892d2017-02-22 13:40:35 +01001761 }
Michal Vaskobd8ef262016-01-20 11:09:27 +01001762
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001763 /* fill timespecs */
Michal Vaskod8a74192023-02-06 15:51:50 +01001764 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko36c7be82017-02-22 13:37:59 +01001765 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001766 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001767 }
1768
1769 /* poll all the sessions one-by-one */
Michal Vasko9a327362017-01-11 11:31:46 +01001770 do {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001771 /* loop from i to j once (all sessions) */
Michal Vasko9a327362017-01-11 11:31:46 +01001772 if (ps->last_event_session == ps->session_count - 1) {
1773 i = j = 0;
1774 } else {
1775 i = j = ps->last_event_session + 1;
Michal Vaskobd8ef262016-01-20 11:09:27 +01001776 }
Michal Vasko9a327362017-01-11 11:31:46 +01001777 do {
fanchanghu3d4e7212017-08-09 09:42:30 +08001778 cur_ps_session = ps->sessions[i];
1779 cur_session = cur_ps_session->session;
Michal Vasko428087d2016-01-14 16:04:28 +01001780
Michal Vasko131120a2018-05-29 15:44:02 +02001781 /* SESSION RPC LOCK */
1782 r = nc_session_rpc_lock(cur_session, 0, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001783 if (r == -1) {
1784 ret = NC_PSPOLL_ERROR;
1785 } else if (r == 1) {
1786 /* no one else is currently working with the session, so we can, otherwise skip it */
Michal Vaskoc97cb162017-10-16 12:10:23 +02001787 switch (cur_ps_session->state) {
1788 case NC_PS_STATE_NONE:
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001789 if (cur_session->status == NC_STATUS_RUNNING) {
1790 /* session is fine, work with it */
fanchanghu3d4e7212017-08-09 09:42:30 +08001791 cur_ps_session->state = NC_PS_STATE_BUSY;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001792
Michal Vasko131120a2018-05-29 15:44:02 +02001793 ret = nc_ps_poll_session_io(cur_session, NC_SESSION_LOCK_TIMEOUT, ts_cur.tv_sec, msg);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001794 switch (ret) {
1795 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +02001796 ERR(cur_session, "%s.", msg);
fanchanghu3d4e7212017-08-09 09:42:30 +08001797 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001798 break;
1799 case NC_PSPOLL_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +02001800 ERR(cur_session, "%s.", msg);
fanchanghu3d4e7212017-08-09 09:42:30 +08001801 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001802 break;
1803 case NC_PSPOLL_TIMEOUT:
romanf578cd52023-10-19 09:47:40 +02001804#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001805 case NC_PSPOLL_SSH_CHANNEL:
1806 case NC_PSPOLL_SSH_MSG:
romanf578cd52023-10-19 09:47:40 +02001807#endif /* NC_ENABLED_SSH_TLS */
fanchanghu3d4e7212017-08-09 09:42:30 +08001808 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001809 break;
1810 case NC_PSPOLL_RPC:
1811 /* let's keep the state busy, we are not done with this session */
1812 break;
1813 }
1814 } else {
1815 /* session is not fine, let the caller know */
1816 ret = NC_PSPOLL_SESSION_TERM;
1817 if (cur_session->term_reason != NC_SESSION_TERM_CLOSED) {
1818 ret |= NC_PSPOLL_SESSION_ERROR;
1819 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001820 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001821 }
Michal Vaskoc97cb162017-10-16 12:10:23 +02001822 break;
1823 case NC_PS_STATE_BUSY:
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001824 /* it definitely should not be busy because we have the lock */
1825 ERRINT;
Michal Vasko4992d802017-08-09 12:20:01 +02001826 ret = NC_PSPOLL_ERROR;
Michal Vaskoc97cb162017-10-16 12:10:23 +02001827 break;
1828 case NC_PS_STATE_INVALID:
1829 /* we got it locked, but it will be freed, let it be */
1830 ret = NC_PSPOLL_TIMEOUT;
1831 break;
Michal Vasko428087d2016-01-14 16:04:28 +01001832 }
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001833
Michal Vasko131120a2018-05-29 15:44:02 +02001834 /* keep RPC lock in this one case */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001835 if (ret != NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02001836 /* SESSION RPC UNLOCK */
1837 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vaskoade892d2017-02-22 13:40:35 +01001838 }
Michal Vaskoade892d2017-02-22 13:40:35 +01001839 } else {
1840 /* timeout */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001841 ret = NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01001842 }
Michal Vasko428087d2016-01-14 16:04:28 +01001843
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001844 /* something happened */
1845 if (ret != NC_PSPOLL_TIMEOUT) {
1846 break;
1847 }
1848
Michal Vasko9a327362017-01-11 11:31:46 +01001849 if (i == ps->session_count - 1) {
1850 i = 0;
1851 } else {
1852 ++i;
1853 }
1854 } while (i != j);
1855
Michal Vaskoade892d2017-02-22 13:40:35 +01001856 /* no event, no session remains locked */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001857 if (ret == NC_PSPOLL_TIMEOUT) {
Michal Vasko9a327362017-01-11 11:31:46 +01001858 usleep(NC_TIMEOUT_STEP);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001859
Michal Vaskod8a74192023-02-06 15:51:50 +01001860 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001861 /* final timeout */
1862 break;
Michal Vasko9a327362017-01-11 11:31:46 +01001863 }
Michal Vasko428087d2016-01-14 16:04:28 +01001864 }
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001865 } while (ret == NC_PSPOLL_TIMEOUT);
Michal Vasko428087d2016-01-14 16:04:28 +01001866
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001867 /* do we want to return the session? */
1868 switch (ret) {
1869 case NC_PSPOLL_RPC:
1870 case NC_PSPOLL_SESSION_TERM:
1871 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
romanf578cd52023-10-19 09:47:40 +02001872#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001873 case NC_PSPOLL_SSH_CHANNEL:
1874 case NC_PSPOLL_SSH_MSG:
romanf578cd52023-10-19 09:47:40 +02001875#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001876 if (session) {
1877 *session = cur_session;
1878 }
1879 ps->last_event_session = i;
1880 break;
1881 default:
1882 break;
Michal Vasko71090fc2016-05-24 16:37:28 +02001883 }
Michal Vasko428087d2016-01-14 16:04:28 +01001884
Michal Vaskoade892d2017-02-22 13:40:35 +01001885 /* PS UNLOCK */
1886 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001887
Michal Vasko131120a2018-05-29 15:44:02 +02001888 /* we have some data available and the session is RPC locked (but not IO locked) */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001889 if (ret == NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02001890 ret = nc_server_recv_rpc_io(cur_session, timeout, &rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001891 if (ret & (NC_PSPOLL_ERROR | NC_PSPOLL_BAD_RPC)) {
1892 if (cur_session->status != NC_STATUS_RUNNING) {
1893 ret |= NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
fanchanghu3d4e7212017-08-09 09:42:30 +08001894 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001895 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08001896 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001897 }
1898 } else {
Michal Vasko9fb42272017-10-05 13:50:05 +02001899 cur_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001900
Michal Vasko7f1ee932018-10-11 09:41:42 +02001901 /* process RPC */
Michal Vasko131120a2018-05-29 15:44:02 +02001902 ret |= nc_server_send_reply_io(cur_session, timeout, rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001903 if (cur_session->status != NC_STATUS_RUNNING) {
1904 ret |= NC_PSPOLL_SESSION_TERM;
1905 if (!(cur_session->term_reason & (NC_SESSION_TERM_CLOSED | NC_SESSION_TERM_KILLED))) {
1906 ret |= NC_PSPOLL_SESSION_ERROR;
1907 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001908 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001909 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08001910 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001911 }
Michal Vasko428087d2016-01-14 16:04:28 +01001912 }
Michal Vasko77367452021-02-16 16:32:18 +01001913 nc_server_rpc_free(rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001914
Michal Vasko131120a2018-05-29 15:44:02 +02001915 /* SESSION RPC UNLOCK */
1916 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001917 }
1918
Michal Vasko48a63ed2016-03-01 09:48:21 +01001919 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001920}
1921
Michal Vaskod09eae62016-02-01 10:32:52 +01001922API void
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001923nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *))
Michal Vaskod09eae62016-02-01 10:32:52 +01001924{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001925 uint8_t q_id;
Michal Vaskod09eae62016-02-01 10:32:52 +01001926 uint16_t i;
1927 struct nc_session *session;
1928
Michal Vasko9a25e932016-02-01 10:36:42 +01001929 if (!ps) {
romanf578cd52023-10-19 09:47:40 +02001930 ERRARG(NULL, "ps");
Michal Vasko9a25e932016-02-01 10:36:42 +01001931 return;
1932 }
1933
Michal Vasko48a63ed2016-03-01 09:48:21 +01001934 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001935 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001936 return;
1937 }
Michal Vaskod09eae62016-02-01 10:32:52 +01001938
Michal Vasko48a63ed2016-03-01 09:48:21 +01001939 if (all) {
Radek Krejci4f8042c2016-03-03 13:11:26 +01001940 for (i = 0; i < ps->session_count; i++) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001941 nc_session_free(ps->sessions[i]->session, data_free);
1942 free(ps->sessions[i]);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001943 }
1944 free(ps->sessions);
1945 ps->sessions = NULL;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001946 ps->session_count = 0;
Michal Vasko9a327362017-01-11 11:31:46 +01001947 ps->last_event_session = 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001948 } else {
1949 for (i = 0; i < ps->session_count; ) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001950 if (ps->sessions[i]->session->status != NC_STATUS_RUNNING) {
1951 session = ps->sessions[i]->session;
Radek Krejcid5f978f2016-03-03 13:14:45 +01001952 _nc_ps_del_session(ps, NULL, i);
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001953 nc_session_free(session, data_free);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001954 continue;
1955 }
1956
1957 ++i;
1958 }
Michal Vaskod09eae62016-02-01 10:32:52 +01001959 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001960
1961 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001962 nc_ps_unlock(ps, q_id, __func__);
Michal Vaskod09eae62016-02-01 10:32:52 +01001963}
1964
romanfb3f7cf2023-11-30 16:10:09 +01001965int
1966nc_server_set_address_port(struct nc_endpt *endpt, struct nc_bind *bind, const char *address, uint16_t port)
1967{
1968 int sock = -1, set_addr, ret = 0;
1969
1970 assert((address && !port) || (!address && port) || (endpt->ti == NC_TI_UNIX));
1971
1972 if (address) {
1973 set_addr = 1;
1974 } else {
1975 set_addr = 0;
1976 }
1977
1978 if (set_addr) {
1979 port = bind->port;
1980 } else {
1981 address = bind->address;
1982 }
1983
1984 /* we have all the information we need to create a listening socket */
1985 if ((address && port) || (endpt->ti == NC_TI_UNIX)) {
1986 /* create new socket, close the old one */
1987 if (endpt->ti == NC_TI_UNIX) {
1988 sock = nc_sock_listen_unix(endpt->opts.unixsock);
1989 } else {
1990 sock = nc_sock_listen_inet(address, port, &endpt->ka);
1991 }
1992
1993 if (sock == -1) {
1994 ret = 1;
1995 goto cleanup;
1996 }
1997
1998 if (bind->sock > -1) {
1999 close(bind->sock);
2000 }
2001 bind->sock = sock;
2002 }
2003
2004 if (sock > -1) {
2005 switch (endpt->ti) {
2006 case NC_TI_UNIX:
2007 VRB(NULL, "Listening on %s for UNIX connections.", endpt->opts.unixsock->address);
2008 break;
2009#ifdef NC_ENABLED_SSH_TLS
2010 case NC_TI_LIBSSH:
2011 VRB(NULL, "Listening on %s:%u for SSH connections.", address, port);
2012 break;
2013 case NC_TI_OPENSSL:
2014 VRB(NULL, "Listening on %s:%u for TLS connections.", address, port);
2015 break;
2016#endif /* NC_ENABLED_SSH_TLS */
2017 default:
2018 ERRINT;
2019 ret = 1;
2020 break;
2021 }
2022 }
2023
2024cleanup:
2025 return ret;
2026}
2027
Michal Vasko29f2f022024-03-13 09:06:48 +01002028#if defined (SO_PEERCRED) || defined (HAVE_GETPEEREID)
2029
Michal Vasko6f865982023-11-21 12:10:42 +01002030/**
2031 * @brief Get UID of the owner of a socket.
2032 *
2033 * @param[in] sock Socket to analyze.
2034 * @param[out] uid Socket owner UID.
2035 * @return 0 on success,
2036 * @return -1 on error.
2037 */
Michal Vasko5f352c52019-07-10 16:12:06 +02002038static int
apropp-molex4e903c32020-04-20 03:06:58 -04002039nc_get_uid(int sock, uid_t *uid)
2040{
Michal Vasko6f865982023-11-21 12:10:42 +01002041 int r;
apropp-molex4e903c32020-04-20 03:06:58 -04002042
Michal Vaskod3910912020-04-20 09:12:49 +02002043#ifdef SO_PEERCRED
2044 struct ucred ucred;
2045 socklen_t len;
Michal Vasko292c5542023-02-01 14:33:17 +01002046
Michal Vaskod3910912020-04-20 09:12:49 +02002047 len = sizeof(ucred);
Michal Vasko6f865982023-11-21 12:10:42 +01002048 r = getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
2049 if (!r) {
Michal Vaskod3910912020-04-20 09:12:49 +02002050 *uid = ucred.uid;
2051 }
2052#else
Michal Vasko6f865982023-11-21 12:10:42 +01002053 r = getpeereid(sock, uid, NULL);
Michal Vaskod3910912020-04-20 09:12:49 +02002054#endif
2055
Michal Vasko6f865982023-11-21 12:10:42 +01002056 if (r < 0) {
2057 ERR(NULL, "Failed to get owner UID of a UNIX socket (%s).", strerror(errno));
Michal Vaskod3910912020-04-20 09:12:49 +02002058 return -1;
2059 }
apropp-molex4e903c32020-04-20 03:06:58 -04002060 return 0;
2061}
2062
Michal Vasko29f2f022024-03-13 09:06:48 +01002063#endif
2064
apropp-molex4e903c32020-04-20 03:06:58 -04002065static int
Michal Vasko5f352c52019-07-10 16:12:06 +02002066nc_accept_unix(struct nc_session *session, int sock)
2067{
Michal Vaskob83a3fa2021-05-26 09:53:42 +02002068#if defined (SO_PEERCRED) || defined (HAVE_GETPEEREID)
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002069 struct passwd *pw, pw_buf;
Michal Vasko5f352c52019-07-10 16:12:06 +02002070 char *username;
Michal Vasko292c5542023-02-01 14:33:17 +01002071
Michal Vasko5f352c52019-07-10 16:12:06 +02002072 session->ti_type = NC_TI_UNIX;
Michal Vasko143aa142021-10-01 15:31:48 +02002073 uid_t uid = 0;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002074 char *buf = NULL;
2075 size_t buf_len = 0;
Michal Vasko5f352c52019-07-10 16:12:06 +02002076
Michal Vaskod3910912020-04-20 09:12:49 +02002077 if (nc_get_uid(sock, &uid)) {
2078 close(sock);
Michal Vasko5f352c52019-07-10 16:12:06 +02002079 return -1;
2080 }
2081
romanf6e32012023-04-24 15:51:26 +02002082 pw = nc_getpw(uid, NULL, &pw_buf, &buf, &buf_len);
Michal Vasko5f352c52019-07-10 16:12:06 +02002083 if (pw == NULL) {
Michal Vasko05532772021-06-03 12:12:38 +02002084 ERR(NULL, "Failed to find username for uid=%u (%s).\n", uid, strerror(errno));
Michal Vasko5f352c52019-07-10 16:12:06 +02002085 close(sock);
2086 return -1;
2087 }
2088
2089 username = strdup(pw->pw_name);
Michal Vaskoccd2dd02021-10-11 09:13:01 +02002090 free(buf);
Michal Vasko5f352c52019-07-10 16:12:06 +02002091 if (username == NULL) {
2092 ERRMEM;
2093 close(sock);
2094 return -1;
2095 }
Michal Vasko93224072021-11-09 12:14:28 +01002096 session->username = username;
Michal Vasko5f352c52019-07-10 16:12:06 +02002097
2098 session->ti.unixsock.sock = sock;
2099
2100 return 1;
Claus Klein22091912020-01-20 13:45:47 +01002101#else
Michal Vasko29f2f022024-03-13 09:06:48 +01002102 (void)session;
2103 (void)sock;
2104
Claus Klein22091912020-01-20 13:45:47 +01002105 return -1;
2106#endif
Michal Vasko5f352c52019-07-10 16:12:06 +02002107}
2108
Michal Vaskoe2713da2016-08-22 16:06:40 +02002109API int
romanfb3f7cf2023-11-30 16:10:09 +01002110nc_server_add_endpt_unix_socket_listen(const char *endpt_name, const char *unix_socket_path, mode_t mode, uid_t uid, gid_t gid)
2111{
2112 int ret = 0;
2113 void *tmp;
2114 uint16_t i;
2115
2116 NC_CHECK_ARG_RET(NULL, endpt_name, unix_socket_path, 1);
2117
2118 /* CONFIG LOCK */
2119 pthread_rwlock_wrlock(&server_opts.config_lock);
2120
2121 /* check name uniqueness */
2122 for (i = 0; i < server_opts.endpt_count; i++) {
2123 if (!strcmp(endpt_name, server_opts.endpts[i].name)) {
2124 ERR(NULL, "Endpoint \"%s\" already exists.", endpt_name);
2125 ret = 1;
2126 goto cleanup;
2127 }
2128 }
2129
2130 /* alloc a new endpoint */
2131 tmp = nc_realloc(server_opts.endpts, (server_opts.endpt_count + 1) * sizeof *server_opts.endpts);
2132 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2133 server_opts.endpts = tmp;
2134 memset(&server_opts.endpts[server_opts.endpt_count], 0, sizeof *server_opts.endpts);
2135
2136 /* alloc a new bind */
2137 tmp = nc_realloc(server_opts.binds, (server_opts.endpt_count + 1) * sizeof *server_opts.binds);
2138 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2139 server_opts.binds = tmp;
2140 memset(&server_opts.binds[server_opts.endpt_count], 0, sizeof *server_opts.binds);
2141 server_opts.binds[server_opts.endpt_count].sock = -1;
2142 server_opts.endpt_count++;
2143
2144 /* set name and ti */
2145 server_opts.endpts[server_opts.endpt_count - 1].name = strdup(endpt_name);
2146 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].name, ret = 1, cleanup);
2147 server_opts.endpts[server_opts.endpt_count - 1].ti = NC_TI_UNIX;
2148
2149 /* set the bind data */
2150 server_opts.binds[server_opts.endpt_count - 1].address = strdup(unix_socket_path);
2151 NC_CHECK_ERRMEM_GOTO(!server_opts.binds[server_opts.endpt_count - 1].address, ret = 1, cleanup);
2152
2153 /* alloc unix opts */
2154 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock = calloc(1, sizeof(struct nc_server_unix_opts));
2155 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock, ret = 1, cleanup);
2156
2157 /* set the opts data */
2158 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address = strdup(unix_socket_path);
2159 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address, ret = 1, cleanup);
2160 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->mode = (mode == (mode_t) -1) ? (mode_t) -1 : mode;
2161 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->uid = (uid == (uid_t) -1) ? (uid_t) -1 : uid;
2162 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->gid = (gid == (gid_t) -1) ? (gid_t) -1 : gid;
2163
2164 /* start listening */
2165 ret = nc_server_set_address_port(&server_opts.endpts[server_opts.endpt_count - 1],
2166 &server_opts.binds[server_opts.endpt_count - 1], NULL, 0);
2167 if (ret) {
2168 ERR(NULL, "Listening on UNIX socket \"%s\" failed.", unix_socket_path);
2169 goto cleanup;
2170 }
2171
2172cleanup:
2173 /* CONFIG UNLOCK */
2174 pthread_rwlock_unlock(&server_opts.config_lock);
2175 return ret;
2176}
2177
2178static void
2179nc_server_del_endpt_unix_socket_opts(struct nc_bind *bind, struct nc_server_unix_opts *opts)
2180{
2181 if (bind->sock > -1) {
2182 close(bind->sock);
2183 }
2184
2185 unlink(bind->address);
2186 free(bind->address);
2187 free(opts->address);
2188
2189 free(opts);
2190}
2191
2192void
2193_nc_server_del_endpt_unix_socket(struct nc_endpt *endpt, struct nc_bind *bind)
2194{
2195 free(endpt->name);
2196 nc_server_del_endpt_unix_socket_opts(bind, endpt->opts.unixsock);
2197
2198 server_opts.endpt_count--;
2199 if (!server_opts.endpt_count) {
2200 free(server_opts.endpts);
2201 free(server_opts.binds);
2202 server_opts.endpts = NULL;
2203 server_opts.binds = NULL;
2204 } else if (endpt != &server_opts.endpts[server_opts.endpt_count]) {
2205 memcpy(endpt, &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts);
2206 memcpy(bind, &server_opts.binds[server_opts.endpt_count], sizeof *server_opts.binds);
2207 }
2208}
2209
2210API void
2211nc_server_del_endpt_unix_socket(const char *endpt_name)
2212{
2213 uint16_t i;
2214 struct nc_endpt *endpt = NULL;
2215 struct nc_bind *bind;
2216
romanb6ad37a2023-12-07 13:08:46 +01002217 NC_CHECK_ARG_RET(NULL, endpt_name, );
2218
romanfb3f7cf2023-11-30 16:10:09 +01002219 /* CONFIG LOCK */
2220 pthread_rwlock_wrlock(&server_opts.config_lock);
2221
romanfb3f7cf2023-11-30 16:10:09 +01002222 for (i = 0; i < server_opts.endpt_count; i++) {
2223 if (!strcmp(server_opts.endpts[i].name, endpt_name)) {
2224 endpt = &server_opts.endpts[i];
2225 bind = &server_opts.binds[i];
2226 break;
2227 }
2228 }
2229 if (!endpt) {
2230 ERR(NULL, "Endpoint \"%s\" not found.", endpt_name);
2231 goto end;
2232 }
2233 if (endpt->ti != NC_TI_UNIX) {
2234 ERR(NULL, "Endpoint \"%s\" is not a UNIX socket endpoint.", endpt_name);
2235 goto end;
2236 }
2237
2238 _nc_server_del_endpt_unix_socket(endpt, bind);
2239
2240end:
2241 /* CONFIG UNLOCK */
2242 pthread_rwlock_unlock(&server_opts.config_lock);
2243}
2244
2245API int
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002246nc_server_endpt_count(void)
2247{
2248 return server_opts.endpt_count;
2249}
2250
Michal Vasko71090fc2016-05-24 16:37:28 +02002251API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +01002252nc_accept(int timeout, const struct ly_ctx *ctx, struct nc_session **session)
Michal Vasko9e036d52016-01-08 10:49:26 +01002253{
Michal Vasko71090fc2016-05-24 16:37:28 +02002254 NC_MSG_TYPE msgtype;
Michal Vasko87b42562024-05-03 12:02:01 +02002255 int sock = -1, ret;
Michal Vasko5c2f7952016-01-22 13:16:31 +01002256 char *host = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002257 uint16_t port, bind_idx;
Michal Vasko9fb42272017-10-05 13:50:05 +02002258 struct timespec ts_cur;
Michal Vasko9e036d52016-01-08 10:49:26 +01002259
romanf578cd52023-10-19 09:47:40 +02002260 NC_CHECK_ARG_RET(NULL, ctx, session, NC_MSG_ERROR);
Michal Vasko9e036d52016-01-08 10:49:26 +01002261
romand82caf12024-03-05 14:21:39 +01002262 NC_CHECK_SRV_INIT_RET(NC_MSG_ERROR);
2263
2264 *session = NULL;
2265
Michal Vasko93224072021-11-09 12:14:28 +01002266 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +02002267 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +01002268
romanf578cd52023-10-19 09:47:40 +02002269 /* CONFIG LOCK */
2270 pthread_rwlock_rdlock(&server_opts.config_lock);
Michal Vasko51e514d2016-02-02 15:51:52 +01002271
2272 if (!server_opts.endpt_count) {
Michal Vasko05532772021-06-03 12:12:38 +02002273 ERR(NULL, "No endpoints to accept sessions on.");
Michal Vasko87b42562024-05-03 12:02:01 +02002274 msgtype = NC_MSG_ERROR;
2275 goto cleanup;
Michal Vasko51e514d2016-02-02 15:51:52 +01002276 }
Michal Vaskob48aa812016-01-18 14:13:09 +01002277
romanf578cd52023-10-19 09:47:40 +02002278 ret = nc_sock_accept_binds(server_opts.binds, server_opts.endpt_count, &server_opts.bind_lock, timeout, &host, &port, &bind_idx);
Michal Vasko50456e82016-02-02 12:16:08 +01002279 if (ret < 1) {
Michal Vasko87b42562024-05-03 12:02:01 +02002280 msgtype = (!ret ? NC_MSG_WOULDBLOCK : NC_MSG_ERROR);
2281 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002282 }
Michal Vaskob48aa812016-01-18 14:13:09 +01002283 sock = ret;
Michal Vasko9e036d52016-01-08 10:49:26 +01002284
Michal Vasko131120a2018-05-29 15:44:02 +02002285 *session = nc_new_session(NC_SERVER, 0);
Michal Vasko87b42562024-05-03 12:02:01 +02002286 NC_CHECK_ERRMEM_GOTO(!(*session), msgtype = NC_MSG_ERROR, cleanup);
Michal Vasko1a38c862016-01-15 15:50:07 +01002287 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002288 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko1a38c862016-01-15 15:50:07 +01002289 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +01002290 (*session)->host = host;
Michal Vasko87b42562024-05-03 12:02:01 +02002291 host = NULL;
Michal Vasko1a38c862016-01-15 15:50:07 +01002292 (*session)->port = port;
Michal Vasko9e036d52016-01-08 10:49:26 +01002293
Michal Vaskoc14e3c82016-01-11 16:14:30 +01002294 /* sock gets assigned to session or closed */
romanf578cd52023-10-19 09:47:40 +02002295#ifdef NC_ENABLED_SSH_TLS
Michal Vasko2e6defd2016-10-07 15:48:15 +02002296 if (server_opts.endpts[bind_idx].ti == NC_TI_LIBSSH) {
romanf578cd52023-10-19 09:47:40 +02002297 ret = nc_accept_ssh_session(*session, server_opts.endpts[bind_idx].opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko87b42562024-05-03 12:02:01 +02002298 sock = -1;
Michal Vasko71090fc2016-05-24 16:37:28 +02002299 if (ret < 0) {
2300 msgtype = NC_MSG_ERROR;
2301 goto cleanup;
2302 } else if (!ret) {
2303 msgtype = NC_MSG_WOULDBLOCK;
2304 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002305 }
romanf578cd52023-10-19 09:47:40 +02002306 } else if (server_opts.endpts[bind_idx].ti == NC_TI_OPENSSL) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002307 (*session)->data = server_opts.endpts[bind_idx].opts.tls;
romanf578cd52023-10-19 09:47:40 +02002308 ret = nc_accept_tls_session(*session, server_opts.endpts[bind_idx].opts.tls, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko87b42562024-05-03 12:02:01 +02002309 sock = -1;
Michal Vasko71090fc2016-05-24 16:37:28 +02002310 if (ret < 0) {
2311 msgtype = NC_MSG_ERROR;
2312 goto cleanup;
2313 } else if (!ret) {
2314 msgtype = NC_MSG_WOULDBLOCK;
2315 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002316 }
Michal Vasko3d865d22016-01-28 16:00:53 +01002317 } else
romanf578cd52023-10-19 09:47:40 +02002318#endif /* NC_ENABLED_SSH_TLS */
Olivier Matzac7fa2f2018-10-11 10:02:04 +02002319 if (server_opts.endpts[bind_idx].ti == NC_TI_UNIX) {
2320 (*session)->data = server_opts.endpts[bind_idx].opts.unixsock;
2321 ret = nc_accept_unix(*session, sock);
Michal Vasko87b42562024-05-03 12:02:01 +02002322 sock = -1;
Olivier Matzac7fa2f2018-10-11 10:02:04 +02002323 if (ret < 0) {
2324 msgtype = NC_MSG_ERROR;
2325 goto cleanup;
2326 }
2327 } else {
Michal Vasko9e036d52016-01-08 10:49:26 +01002328 ERRINT;
Michal Vasko71090fc2016-05-24 16:37:28 +02002329 msgtype = NC_MSG_ERROR;
2330 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002331 }
2332
Michal Vasko2cc4c682016-03-01 09:16:48 +01002333 (*session)->data = NULL;
2334
romanf578cd52023-10-19 09:47:40 +02002335 /* CONFIG UNLOCK */
2336 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002337
Michal Vaskob48aa812016-01-18 14:13:09 +01002338 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002339 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +01002340
Michal Vasko9e036d52016-01-08 10:49:26 +01002341 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002342 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002343 if (msgtype != NC_MSG_HELLO) {
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002344 nc_session_free(*session, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +01002345 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002346 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002347 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002348
Michal Vaskod8a74192023-02-06 15:51:50 +01002349 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002350 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002351 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002352 (*session)->opts.server.session_start = ts_cur;
Michal Vasko1a38c862016-01-15 15:50:07 +01002353 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko9e036d52016-01-08 10:49:26 +01002354
Michal Vasko71090fc2016-05-24 16:37:28 +02002355 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002356
Michal Vasko71090fc2016-05-24 16:37:28 +02002357cleanup:
romanf578cd52023-10-19 09:47:40 +02002358 /* CONFIG UNLOCK */
2359 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002360
Michal Vasko87b42562024-05-03 12:02:01 +02002361 free(host);
2362 if (sock > -1) {
2363 close(sock);
2364 }
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002365 nc_session_free(*session, NULL);
Michal Vasko1a38c862016-01-15 15:50:07 +01002366 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002367 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002368}
2369
romanf578cd52023-10-19 09:47:40 +02002370#ifdef NC_ENABLED_SSH_TLS
Michal Vasko2e6defd2016-10-07 15:48:15 +02002371
2372API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002373nc_server_ch_is_client(const char *name)
2374{
2375 uint16_t i;
2376 int found = 0;
2377
2378 if (!name) {
2379 return found;
2380 }
2381
2382 /* READ LOCK */
2383 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2384
2385 /* check name uniqueness */
2386 for (i = 0; i < server_opts.ch_client_count; ++i) {
2387 if (!strcmp(server_opts.ch_clients[i].name, name)) {
2388 found = 1;
2389 break;
2390 }
2391 }
2392
2393 /* UNLOCK */
2394 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2395
2396 return found;
2397}
2398
2399API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002400nc_server_ch_client_is_endpt(const char *client_name, const char *endpt_name)
2401{
2402 uint16_t i;
2403 struct nc_ch_client *client = NULL;
2404 int found = 0;
2405
2406 if (!client_name || !endpt_name) {
2407 return found;
2408 }
2409
2410 /* READ LOCK */
2411 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2412
2413 for (i = 0; i < server_opts.ch_client_count; ++i) {
2414 if (!strcmp(server_opts.ch_clients[i].name, client_name)) {
2415 client = &server_opts.ch_clients[i];
2416 break;
2417 }
2418 }
2419
2420 if (!client) {
2421 goto cleanup;
2422 }
2423
2424 for (i = 0; i < client->ch_endpt_count; ++i) {
2425 if (!strcmp(client->ch_endpts[i].name, endpt_name)) {
2426 found = 1;
2427 break;
2428 }
2429 }
2430
2431cleanup:
2432 /* UNLOCK */
2433 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2434 return found;
2435}
2436
Michal Vasko056f53c2022-10-21 13:38:15 +02002437/**
2438 * @brief Create a connection for an endpoint.
2439 *
2440 * Client lock is expected to be held.
2441 *
2442 * @param[in] endpt Endpoint to use.
2443 * @param[in] acquire_ctx_cb Callback for acquiring the libyang context.
2444 * @param[in] release_ctx_cb Callback for releasing the libyang context.
2445 * @param[in] ctx_cb_data Context callbacks data.
2446 * @param[out] session Created NC session.
2447 * @return NC_MSG values.
2448 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002449static NC_MSG_TYPE
Michal Vasko58bac1c2022-03-24 15:25:26 +01002450nc_connect_ch_endpt(struct nc_ch_endpt *endpt, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
2451 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, struct nc_session **session)
Michal Vaskob05053d2016-01-22 16:12:06 +01002452{
Michal Vasko71090fc2016-05-24 16:37:28 +02002453 NC_MSG_TYPE msgtype;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002454 const struct ly_ctx *ctx = NULL;
Michal Vaskob05053d2016-01-22 16:12:06 +01002455 int sock, ret;
Michal Vasko9fb42272017-10-05 13:50:05 +02002456 struct timespec ts_cur;
Michal Vasko66032bc2019-01-22 15:03:12 +01002457 char *ip_host;
Michal Vaskob05053d2016-01-22 16:12:06 +01002458
Michal Vasko056f53c2022-10-21 13:38:15 +02002459 sock = nc_sock_connect(endpt->address, endpt->port, NC_CH_CONNECT_TIMEOUT, &endpt->ka, &endpt->sock_pending, &ip_host);
Michal Vaskoc61c4492016-01-25 11:13:34 +01002460 if (sock < 0) {
Michal Vasko71090fc2016-05-24 16:37:28 +02002461 return NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002462 }
2463
Michal Vasko93224072021-11-09 12:14:28 +01002464 /* acquire context */
2465 ctx = acquire_ctx_cb(ctx_cb_data);
2466 if (!ctx) {
2467 ERR(NULL, "Failed to acquire context for a new Call Home session.");
2468 close(sock);
2469 free(ip_host);
2470 return NC_MSG_ERROR;
2471 }
2472
romanf578cd52023-10-19 09:47:40 +02002473 /* init ctx as needed */
2474 nc_server_init_cb_ctx(ctx);
2475
Michal Vasko93224072021-11-09 12:14:28 +01002476 /* create session */
Michal Vasko131120a2018-05-29 15:44:02 +02002477 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +02002478 NC_CHECK_ERRMEM_GOTO(!(*session), close(sock); free(ip_host); msgtype = NC_MSG_ERROR, fail);
Michal Vaskob05053d2016-01-22 16:12:06 +01002479 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002480 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vaskodc96bb92023-03-28 08:52:48 +02002481 (*session)->flags = NC_SESSION_SHAREDCTX | NC_SESSION_CALLHOME;
Michal Vasko93224072021-11-09 12:14:28 +01002482 (*session)->host = ip_host;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002483 (*session)->port = endpt->port;
Michal Vaskob05053d2016-01-22 16:12:06 +01002484
Michal Vaskob05053d2016-01-22 16:12:06 +01002485 /* sock gets assigned to session or closed */
Michal Vaskoadf30f02019-06-24 09:34:47 +02002486 if (endpt->ti == NC_TI_LIBSSH) {
romanf578cd52023-10-19 09:47:40 +02002487 ret = nc_accept_ssh_session(*session, endpt->opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002488 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002489
Michal Vasko71090fc2016-05-24 16:37:28 +02002490 if (ret < 0) {
2491 msgtype = NC_MSG_ERROR;
2492 goto fail;
2493 } else if (!ret) {
2494 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002495 goto fail;
2496 }
romanf578cd52023-10-19 09:47:40 +02002497 } else if (endpt->ti == NC_TI_OPENSSL) {
Michal Vaskoadf30f02019-06-24 09:34:47 +02002498 (*session)->data = endpt->opts.tls;
romanf578cd52023-10-19 09:47:40 +02002499 ret = nc_accept_tls_session(*session, endpt->opts.tls, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002500 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002501
Michal Vasko71090fc2016-05-24 16:37:28 +02002502 if (ret < 0) {
2503 msgtype = NC_MSG_ERROR;
2504 goto fail;
2505 } else if (!ret) {
2506 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002507 goto fail;
2508 }
roman423cc0d2023-11-24 11:29:47 +01002509 } else {
Michal Vaskob05053d2016-01-22 16:12:06 +01002510 ERRINT;
2511 close(sock);
Michal Vasko71090fc2016-05-24 16:37:28 +02002512 msgtype = NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002513 goto fail;
2514 }
2515
2516 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002517 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob05053d2016-01-22 16:12:06 +01002518
2519 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002520 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002521 if (msgtype != NC_MSG_HELLO) {
Michal Vaskob05053d2016-01-22 16:12:06 +01002522 goto fail;
2523 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002524
Michal Vaskod8a74192023-02-06 15:51:50 +01002525 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002526 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002527 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002528 (*session)->opts.server.session_start = ts_cur;
Michal Vaskob05053d2016-01-22 16:12:06 +01002529 (*session)->status = NC_STATUS_RUNNING;
2530
Michal Vasko71090fc2016-05-24 16:37:28 +02002531 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002532
2533fail:
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002534 nc_session_free(*session, NULL);
Michal Vaskob05053d2016-01-22 16:12:06 +01002535 *session = NULL;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002536 if (ctx) {
2537 release_ctx_cb(ctx_cb_data);
2538 }
Michal Vasko71090fc2016-05-24 16:37:28 +02002539 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002540}
2541
Michal Vasko6f865982023-11-21 12:10:42 +01002542/**
2543 * @brief Wait for any event after a NC session was established on a CH client.
2544 *
Michal Vasko6f865982023-11-21 12:10:42 +01002545 * @param[in] data CH client thread argument.
roman8341e8b2023-11-23 16:12:42 +01002546 * @param[in] session New NC session. The session is invalid upon being freed (= function exit).
Michal Vasko6f865982023-11-21 12:10:42 +01002547 * @return 0 if session was terminated normally,
2548 * @return 1 if the CH client was removed,
2549 * @return -1 on error.
2550 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002551static int
roman8341e8b2023-11-23 16:12:42 +01002552nc_server_ch_client_thread_session_cond_wait(struct nc_ch_client_thread_arg *data, struct nc_session *session)
Michal Vasko2e6defd2016-10-07 15:48:15 +02002553{
Michal Vasko6f865982023-11-21 12:10:42 +01002554 int rc = 0, r;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002555 uint32_t idle_timeout;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002556 struct timespec ts;
2557 struct nc_ch_client *client;
2558
Michal Vasko2e6defd2016-10-07 15:48:15 +02002559 /* CH LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002560 pthread_mutex_lock(&session->opts.server.ch_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002561
Michal Vaskofeccb312022-03-24 15:24:59 +01002562 session->flags |= NC_SESSION_CH_THREAD;
Michal Vasko0db3db52021-03-03 10:45:42 +01002563
Michal Vasko2e6defd2016-10-07 15:48:15 +02002564 /* give the session to the user */
romanf578cd52023-10-19 09:47:40 +02002565 if (data->new_session_cb(data->client_name, session, data->new_session_cb_data)) {
Michal Vaskof1c26c22021-04-12 16:34:33 +02002566 /* something is wrong, free the session */
Michal Vaskofeccb312022-03-24 15:24:59 +01002567 session->flags &= ~NC_SESSION_CH_THREAD;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002568
2569 /* CH UNLOCK */
2570 pthread_mutex_unlock(&session->opts.server.ch_lock);
2571
Michal Vasko77d56d72022-09-07 10:30:48 +02002572 /* session terminated, free it and release its context */
Michal Vaskof1c26c22021-04-12 16:34:33 +02002573 nc_session_free(session, NULL);
Michal Vasko58bac1c2022-03-24 15:25:26 +01002574 data->release_ctx_cb(data->ctx_cb_data);
Michal Vasko6f865982023-11-21 12:10:42 +01002575 return 0;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002576 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002577
2578 do {
romanf578cd52023-10-19 09:47:40 +02002579 nc_timeouttime_get(&ts, NC_CH_THREAD_IDLE_TIMEOUT_SLEEP);
Michal Vasko6f865982023-11-21 12:10:42 +01002580
Michal Vasko0db3db52021-03-03 10:45:42 +01002581 /* CH COND WAIT */
Michal Vaskod8a74192023-02-06 15:51:50 +01002582 r = pthread_cond_clockwait(&session->opts.server.ch_cond, &session->opts.server.ch_lock, COMPAT_CLOCK_ID, &ts);
Michal Vasko3f05a092018-03-13 10:39:49 +01002583 if (!r) {
2584 /* we were woken up, something probably happened */
2585 if (session->status != NC_STATUS_RUNNING) {
2586 break;
2587 }
2588 } else if (r != ETIMEDOUT) {
Michal Vasko05532772021-06-03 12:12:38 +02002589 ERR(session, "Pthread condition timedwait failed (%s).", strerror(r));
Michal Vasko6f865982023-11-21 12:10:42 +01002590 rc = -1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002591 break;
Michal Vasko2e39ed92018-03-12 13:51:44 +01002592 }
2593
Michal Vasko2e6defd2016-10-07 15:48:15 +02002594 /* check whether the client was not removed */
Michal Vasko6f865982023-11-21 12:10:42 +01002595
Michal Vasko2e6defd2016-10-07 15:48:15 +02002596 /* LOCK */
Michal Vasko6f865982023-11-21 12:10:42 +01002597 client = nc_server_ch_client_lock(data->client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002598 if (!client) {
2599 /* client was removed, finish thread */
Michal Vasko05532772021-06-03 12:12:38 +02002600 VRB(session, "Call Home client \"%s\" removed, but an established session will not be terminated.",
Michal Vaskoadf30f02019-06-24 09:34:47 +02002601 data->client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01002602 rc = 1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002603 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002604 }
2605
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002606 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02002607 idle_timeout = client->idle_timeout;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002608 } else {
2609 idle_timeout = 0;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002610 }
2611
Michal Vaskod8a74192023-02-06 15:51:50 +01002612 nc_timeouttime_get(&ts, 0);
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002613 if (!nc_session_get_notif_status(session) && idle_timeout && (ts.tv_sec >= session->opts.server.last_rpc + idle_timeout)) {
Michal Vasko05532772021-06-03 12:12:38 +02002614 VRB(session, "Call Home client \"%s\": session idle timeout elapsed.", client->name);
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002615 session->status = NC_STATUS_INVALID;
2616 session->term_reason = NC_SESSION_TERM_TIMEOUT;
2617 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002618
2619 /* UNLOCK */
2620 nc_server_ch_client_unlock(client);
2621
2622 } while (session->status == NC_STATUS_RUNNING);
2623
Michal Vaskofeccb312022-03-24 15:24:59 +01002624 /* signal to nc_session_free() that CH thread is terminating */
2625 session->flags &= ~NC_SESSION_CH_THREAD;
2626 pthread_cond_signal(&session->opts.server.ch_cond);
Michal Vasko0db3db52021-03-03 10:45:42 +01002627
Michal Vasko27377422018-03-15 08:59:35 +01002628 /* CH UNLOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002629 pthread_mutex_unlock(&session->opts.server.ch_lock);
Michal Vasko27377422018-03-15 08:59:35 +01002630
Michal Vasko6f865982023-11-21 12:10:42 +01002631 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002632}
2633
romanf578cd52023-10-19 09:47:40 +02002634/**
2635 * @brief Waits for some amount of time while reacting to signals about terminating a Call Home thread.
2636 *
2637 * @param[in] session An established session.
2638 * @param[in] data Call Home thread's data.
2639 * @param[in] cond_wait_time Time in seconds to sleep for, after which a reconnect is attempted.
2640 *
2641 * @return 0 if the thread should stop running, 1 if it should continue.
2642 */
2643static int
2644nc_server_ch_client_thread_is_running_wait(struct nc_session *session, struct nc_ch_client_thread_arg *data, uint64_t cond_wait_time)
2645{
2646 struct timespec ts;
2647 int ret = 0, thread_running;
2648
2649 /* COND LOCK */
2650 pthread_mutex_lock(&data->cond_lock);
2651 /* get reconnect timeout in ms */
2652 nc_timeouttime_get(&ts, cond_wait_time * 1000);
2653 while (!ret && data->thread_running) {
2654 ret = pthread_cond_clockwait(&data->cond, &data->cond_lock, COMPAT_CLOCK_ID, &ts);
2655 }
2656
2657 thread_running = data->thread_running;
2658 /* COND UNLOCK */
2659 pthread_mutex_unlock(&data->cond_lock);
2660
2661 if (!thread_running) {
2662 /* thread is terminating */
2663 VRB(session, "Call Home thread signaled to exit, client \"%s\" probably removed.", data->client_name);
2664 ret = 0;
2665 } else if (ret == ETIMEDOUT) {
2666 /* time to reconnect */
2667 VRB(session, "Call Home client \"%s\" timeout of %" PRIu64 " seconds expired, reconnecting.", data->client_name, cond_wait_time);
2668 ret = 1;
2669 } else if (ret) {
2670 ERR(session, "Pthread condition timedwait failed (%s).", strerror(ret));
2671 ret = 0;
2672 }
2673
2674 return ret;
2675}
2676
2677/**
2678 * @brief Checks if a Call Home thread should terminate.
2679 *
2680 * Checks the shared boolean variable thread_running. This should be done everytime
2681 * before entering a critical section.
2682 *
2683 * @param[in] data Call Home thread's data.
2684 *
2685 * @return 0 if the thread should stop running, -1 if it can continue.
2686 */
2687static int
2688nc_server_ch_client_thread_is_running(struct nc_ch_client_thread_arg *data)
2689{
2690 int ret = -1;
2691
2692 /* COND LOCK */
2693 pthread_mutex_lock(&data->cond_lock);
2694 if (!data->thread_running) {
2695 /* thread should stop running */
2696 ret = 0;
2697 }
2698 /* COND UNLOCK */
2699 pthread_mutex_unlock(&data->cond_lock);
2700
2701 return ret;
2702}
2703
Michal Vasko6f865982023-11-21 12:10:42 +01002704/**
2705 * @brief Lock CH client structures for reading and lock the specific client if it has some endpoints, wait otherwise.
2706 *
2707 * @param[in] name Name of the CH client.
2708 * @return Pointer to the CH client.
2709 */
2710static struct nc_ch_client *
2711nc_server_ch_client_with_endpt_lock(const char *name)
2712{
2713 struct nc_ch_client *client;
2714
2715 while (1) {
2716 /* LOCK */
2717 client = nc_server_ch_client_lock(name);
2718 if (!client) {
2719 return NULL;
2720 }
2721 if (client->ch_endpt_count) {
2722 return client;
2723 }
2724 /* no endpoints defined yet */
2725
2726 /* UNLOCK */
2727 nc_server_ch_client_unlock(client);
2728
2729 usleep(NC_CH_NO_ENDPT_WAIT * 1000);
2730 }
2731
2732 return NULL;
2733}
2734
2735/**
2736 * @brief Call Home client management thread.
2737 *
2738 * @param[in] arg CH client thread argument.
2739 * @return NULL.
2740 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002741static void *
2742nc_ch_client_thread(void *arg)
2743{
Michal Vasko6f865982023-11-21 12:10:42 +01002744 struct nc_ch_client_thread_arg *data = arg;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002745 NC_MSG_TYPE msgtype;
2746 uint8_t cur_attempts = 0;
romanf578cd52023-10-19 09:47:40 +02002747 uint16_t next_endpt_index, max_wait;
Michal Vasko9550cf12017-03-21 15:33:58 +01002748 char *cur_endpt_name = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002749 struct nc_ch_endpt *cur_endpt;
romanf578cd52023-10-19 09:47:40 +02002750 struct nc_session *session = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002751 struct nc_ch_client *client;
romanf578cd52023-10-19 09:47:40 +02002752 uint32_t reconnect_in;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002753
2754 /* LOCK */
2755 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002756 if (!client) {
2757 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
2758 goto cleanup;
2759 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002760
2761 cur_endpt = &client->ch_endpts[0];
2762 cur_endpt_name = strdup(cur_endpt->name);
2763
Michal Vasko6f865982023-11-21 12:10:42 +01002764 while (nc_server_ch_client_thread_is_running(data)) {
Michal Vasko056f53c2022-10-21 13:38:15 +02002765 if (!cur_attempts) {
2766 VRB(NULL, "Call Home client \"%s\" endpoint \"%s\" connecting...", data->client_name, cur_endpt_name);
2767 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002768
Michal Vasko6f865982023-11-21 12:10:42 +01002769 msgtype = nc_connect_ch_endpt(cur_endpt, data->acquire_ctx_cb, data->release_ctx_cb, data->ctx_cb_data, &session);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002770 if (msgtype == NC_MSG_HELLO) {
2771 /* UNLOCK */
2772 nc_server_ch_client_unlock(client);
2773
romanf578cd52023-10-19 09:47:40 +02002774 if (!nc_server_ch_client_thread_is_running(data)) {
2775 /* thread should stop running */
2776 goto cleanup;
2777 }
2778
2779 /* run while the session is established */
2780 VRB(session, "Call Home client \"%s\" session %u established.", data->client_name, session->id);
roman8341e8b2023-11-23 16:12:42 +01002781 if (nc_server_ch_client_thread_session_cond_wait(data, session)) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002782 goto cleanup;
2783 }
roman8341e8b2023-11-23 16:12:42 +01002784 session = NULL;
romanf578cd52023-10-19 09:47:40 +02002785
roman8341e8b2023-11-23 16:12:42 +01002786 VRB(NULL, "Call Home client \"%s\" session terminated.", data->client_name);
romanf578cd52023-10-19 09:47:40 +02002787 if (!nc_server_ch_client_thread_is_running(data)) {
2788 /* thread should stop running */
2789 goto cleanup;
2790 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002791
2792 /* LOCK */
2793 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002794 if (!client) {
2795 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
2796 goto cleanup;
2797 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002798
2799 /* session changed status -> it was disconnected for whatever reason,
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002800 * persistent connection immediately tries to reconnect, periodic connects at specific times */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002801 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02002802 if (client->anchor_time) {
Michal Vasko18e1fa02021-11-29 09:02:05 +01002803 /* anchored */
romanf578cd52023-10-19 09:47:40 +02002804 reconnect_in = (time(NULL) - client->anchor_time) % (client->period * 60);
Michal Vasko18e1fa02021-11-29 09:02:05 +01002805 } else {
2806 /* fixed timeout */
romanf578cd52023-10-19 09:47:40 +02002807 reconnect_in = client->period * 60;
Michal Vasko18e1fa02021-11-29 09:02:05 +01002808 }
2809
Michal Vasko2e6defd2016-10-07 15:48:15 +02002810 /* UNLOCK */
2811 nc_server_ch_client_unlock(client);
2812
romanf578cd52023-10-19 09:47:40 +02002813 /* wait for the timeout to elapse, so we can try to reconnect */
2814 VRB(session, "Call Home client \"%s\" reconnecting in %" PRIu32 " seconds.", data->client_name, reconnect_in);
2815 if (!nc_server_ch_client_thread_is_running_wait(session, data, reconnect_in)) {
2816 goto cleanup;
2817 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002818
2819 /* LOCK */
2820 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02002821 assert(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002822 }
2823
2824 /* set next endpoint to try */
2825 if (client->start_with == NC_CH_FIRST_LISTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00002826 next_endpt_index = 0;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002827 } else if (client->start_with == NC_CH_LAST_CONNECTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00002828 /* we keep the current one but due to unlock/lock we have to find it again */
2829 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
2830 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
2831 break;
2832 }
2833 }
2834 if (next_endpt_index >= client->ch_endpt_count) {
2835 /* endpoint was removed, start with the first one */
2836 next_endpt_index = 0;
2837 }
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002838 } else {
2839 /* just get a random index */
2840 next_endpt_index = rand() % client->ch_endpt_count;
Peter Feiged05f2252018-09-03 08:09:47 +00002841 }
2842
Michal Vasko2e6defd2016-10-07 15:48:15 +02002843 } else {
romanf578cd52023-10-19 09:47:40 +02002844 /* session was not created, wait a little bit and try again */
2845 max_wait = client->max_wait;
2846
Michal Vasko6bb116b2016-10-26 13:53:46 +02002847 /* UNLOCK */
2848 nc_server_ch_client_unlock(client);
2849
romanf578cd52023-10-19 09:47:40 +02002850 /* wait for max_wait seconds */
2851 if (!nc_server_ch_client_thread_is_running_wait(session, data, max_wait)) {
2852 /* thread should stop running */
2853 goto cleanup;
2854 }
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002855
Michal Vasko6bb116b2016-10-26 13:53:46 +02002856 /* LOCK */
2857 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02002858 assert(client);
Michal Vasko6bb116b2016-10-26 13:53:46 +02002859
Michal Vasko2e6defd2016-10-07 15:48:15 +02002860 ++cur_attempts;
Michal Vasko4cb8de52018-04-23 14:38:07 +02002861
2862 /* try to find our endpoint again */
Peter Feiged05f2252018-09-03 08:09:47 +00002863 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
2864 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002865 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002866 }
Michal Vasko4cb8de52018-04-23 14:38:07 +02002867 }
2868
Peter Feiged05f2252018-09-03 08:09:47 +00002869 if (next_endpt_index >= client->ch_endpt_count) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002870 /* endpoint was removed, start with the first one */
romanf578cd52023-10-19 09:47:40 +02002871 VRB(session, "Call Home client \"%s\" endpoint \"%s\" removed.", data->client_name, cur_endpt_name);
Peter Feiged05f2252018-09-03 08:09:47 +00002872 next_endpt_index = 0;
Michal Vasko4cb8de52018-04-23 14:38:07 +02002873 cur_attempts = 0;
2874 } else if (cur_attempts == client->max_attempts) {
2875 /* we have tried to connect to this endpoint enough times */
romanf578cd52023-10-19 09:47:40 +02002876 VRB(session, "Call Home client \"%s\" endpoint \"%s\" failed connection attempt limit %" PRIu8 " reached.",
Michal Vasko056f53c2022-10-21 13:38:15 +02002877 data->client_name, cur_endpt_name, client->max_attempts);
2878
2879 /* clear a pending socket, if any */
2880 cur_endpt = &client->ch_endpts[next_endpt_index];
2881 if (cur_endpt->sock_pending > -1) {
2882 close(cur_endpt->sock_pending);
2883 cur_endpt->sock_pending = -1;
2884 }
2885
Peter Feiged05f2252018-09-03 08:09:47 +00002886 if (next_endpt_index < client->ch_endpt_count - 1) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002887 /* just go to the next endpoint */
Peter Feiged05f2252018-09-03 08:09:47 +00002888 ++next_endpt_index;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002889 } else {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002890 /* cur_endpoint is the last, start with the first one */
Michal Vasko2a225342018-09-05 08:38:34 +02002891 next_endpt_index = 0;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002892 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002893 cur_attempts = 0;
2894 } /* else we keep the current one */
2895 }
Peter Feiged05f2252018-09-03 08:09:47 +00002896
2897 cur_endpt = &client->ch_endpts[next_endpt_index];
2898 free(cur_endpt_name);
2899 cur_endpt_name = strdup(cur_endpt->name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002900 }
Michal Vasko6f865982023-11-21 12:10:42 +01002901
romanf578cd52023-10-19 09:47:40 +02002902 /* UNLOCK if we break out of the loop */
2903 nc_server_ch_client_unlock(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002904
2905cleanup:
romanf578cd52023-10-19 09:47:40 +02002906 VRB(session, "Call Home client \"%s\" thread exit.", data->client_name);
Michal Vasko9550cf12017-03-21 15:33:58 +01002907 free(cur_endpt_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002908 free(data->client_name);
Jan Kundrátfd3a01d2024-04-10 03:03:08 +02002909 pthread_mutex_lock(&data->cond_lock);
roman8341e8b2023-11-23 16:12:42 +01002910 pthread_cond_destroy(&data->cond);
Jan Kundrátfd3a01d2024-04-10 03:03:08 +02002911 pthread_mutex_unlock(&data->cond_lock);
roman8341e8b2023-11-23 16:12:42 +01002912 pthread_mutex_destroy(&data->cond_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002913 free(data);
2914 return NULL;
2915}
2916
2917API int
Michal Vasko93224072021-11-09 12:14:28 +01002918nc_connect_ch_client_dispatch(const char *client_name, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
romanf578cd52023-10-19 09:47:40 +02002919 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
2920 void *new_session_cb_data)
Michal Vasko3f05a092018-03-13 10:39:49 +01002921{
Michal Vasko6f865982023-11-21 12:10:42 +01002922 int rc = 0, r;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002923 pthread_t tid;
Michal Vasko6f865982023-11-21 12:10:42 +01002924 struct nc_ch_client_thread_arg *arg = NULL;
romanf578cd52023-10-19 09:47:40 +02002925 struct nc_ch_client *ch_client;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002926
romanf578cd52023-10-19 09:47:40 +02002927 NC_CHECK_ARG_RET(NULL, client_name, acquire_ctx_cb, release_ctx_cb, new_session_cb, -1);
2928
romand82caf12024-03-05 14:21:39 +01002929 NC_CHECK_SRV_INIT_RET(-1);
2930
Michal Vasko6f865982023-11-21 12:10:42 +01002931 /* LOCK */
2932 ch_client = nc_server_ch_client_lock(client_name);
2933 if (!ch_client) {
romanf578cd52023-10-19 09:47:40 +02002934 ERR(NULL, "Client \"%s\" not found.", client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002935 return -1;
2936 }
2937
Michal Vasko6f865982023-11-21 12:10:42 +01002938 /* create the thread argument */
2939 arg = calloc(1, sizeof *arg);
2940 NC_CHECK_ERRMEM_GOTO(!arg, rc = -1, cleanup);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002941 arg->client_name = strdup(client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01002942 NC_CHECK_ERRMEM_GOTO(!arg->client_name, rc = -1, cleanup);
Michal Vasko93224072021-11-09 12:14:28 +01002943 arg->acquire_ctx_cb = acquire_ctx_cb;
2944 arg->release_ctx_cb = release_ctx_cb;
2945 arg->ctx_cb_data = ctx_cb_data;
2946 arg->new_session_cb = new_session_cb;
romanf578cd52023-10-19 09:47:40 +02002947 arg->new_session_cb_data = new_session_cb_data;
romanf578cd52023-10-19 09:47:40 +02002948 pthread_cond_init(&arg->cond, NULL);
romanf578cd52023-10-19 09:47:40 +02002949 pthread_mutex_init(&arg->cond_lock, NULL);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002950
Michal Vasko6f865982023-11-21 12:10:42 +01002951 /* creating the thread */
2952 arg->thread_running = 1;
2953 if ((r = pthread_create(&tid, NULL, nc_ch_client_thread, arg))) {
2954 ERR(NULL, "Creating a new thread failed (%s).", strerror(r));
2955 rc = -1;
2956 goto cleanup;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002957 }
Michal Vasko6f865982023-11-21 12:10:42 +01002958
Michal Vasko2e6defd2016-10-07 15:48:15 +02002959 /* the thread now manages arg */
romanf578cd52023-10-19 09:47:40 +02002960 ch_client->tid = tid;
2961 ch_client->thread_data = arg;
Michal Vasko6f865982023-11-21 12:10:42 +01002962 arg = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002963
Michal Vasko6f865982023-11-21 12:10:42 +01002964cleanup:
2965 /* UNLOCK */
2966 nc_server_ch_client_unlock(ch_client);
2967
2968 if (arg) {
2969 free(arg->client_name);
2970 free(arg);
2971 }
2972 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002973}
2974
romanf578cd52023-10-19 09:47:40 +02002975#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskof8352352016-05-24 09:11:36 +02002976
roman44efa322023-11-03 13:57:25 +01002977API struct timespec
Michal Vaskoc45ebd32016-05-25 11:17:36 +02002978nc_session_get_start_time(const struct nc_session *session)
Michal Vaskof8352352016-05-24 09:11:36 +02002979{
roman44efa322023-11-03 13:57:25 +01002980 struct timespec fail = {0};
2981
2982 NC_CHECK_ARG_RET(session, session, fail);
romanf578cd52023-10-19 09:47:40 +02002983
2984 if (session->side != NC_SERVER) {
2985 ERRARG(session, "session");
roman44efa322023-11-03 13:57:25 +01002986 return fail;
Michal Vaskof8352352016-05-24 09:11:36 +02002987 }
2988
Michal Vasko2e6defd2016-10-07 15:48:15 +02002989 return session->opts.server.session_start;
Michal Vaskof8352352016-05-24 09:11:36 +02002990}
Michal Vasko3486a7c2017-03-03 13:28:07 +01002991
2992API void
Michal Vasko71dbd772021-03-23 14:08:37 +01002993nc_session_inc_notif_status(struct nc_session *session)
Michal Vasko3486a7c2017-03-03 13:28:07 +01002994{
2995 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02002996 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01002997 return;
2998 }
2999
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003000 /* NTF STATUS LOCK */
3001 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
3002
Michal Vasko71dbd772021-03-23 14:08:37 +01003003 ++session->opts.server.ntf_status;
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003004
3005 /* NTF STATUS UNLOCK */
3006 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko71dbd772021-03-23 14:08:37 +01003007}
3008
3009API void
3010nc_session_dec_notif_status(struct nc_session *session)
3011{
3012 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02003013 ERRARG(session, "session");
Michal Vasko71dbd772021-03-23 14:08:37 +01003014 return;
3015 }
3016
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003017 /* NTF STATUS LOCK */
3018 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
3019
Michal Vasko71dbd772021-03-23 14:08:37 +01003020 if (session->opts.server.ntf_status) {
3021 --session->opts.server.ntf_status;
3022 }
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003023
3024 /* NTF STATUS UNLOCK */
3025 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko3486a7c2017-03-03 13:28:07 +01003026}
3027
3028API int
3029nc_session_get_notif_status(const struct nc_session *session)
3030{
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003031 uint32_t ntf_status;
3032
Michal Vasko3486a7c2017-03-03 13:28:07 +01003033 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02003034 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01003035 return 0;
3036 }
3037
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003038 /* NTF STATUS LOCK */
3039 pthread_mutex_lock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3040
3041 ntf_status = session->opts.server.ntf_status;
3042
3043 /* NTF STATUS UNLOCK */
3044 pthread_mutex_unlock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3045
3046 return ntf_status;
Michal Vasko086311b2016-01-08 09:53:11 +01003047}